From 33d4f064680d76fed4c51dfdcc16a38a7e349c4b Mon Sep 17 00:00:00 2001 From: ssdeanx Date: Wed, 15 Apr 2026 09:20:53 -0400 Subject: [PATCH] feat: add output guardrails workflow for processing - Introduced a new workflow `outputGuardrails` in `output-guardrails.ts` to enhance output processing. - Implemented a sequential processing flow: - Added a `TokenLimiterProcessor` to limit tokens to a maximum of 256,000. - Included a `BatchPartsProcessor` to batch stream chunks with a size of 10, emitting on non-text. - Added parallel processing for independent checks: - Integrated a `PIIDetector` with a redaction strategy using the model `openrouter/google/gemma-4-31b-it:free`. - Integrated a `ModerationProcessor` with a blocking strategy using the same model. - Mapped the output to retain transformed messages from the `PIIDetector`. - Added a sequential step for `SystemPromptScrubber` to scrub the output based on the previous redaction, using a placeholder text of `[REDACTED]`. --- .env.example | 16 +- .gitignore | 18 +- README.md | 1 + app/chat/AGENTS.md | 14 + .../_components/admin-management-panel.tsx | 27 +- app/chat/admin/layout.tsx | 39 ++ app/chat/admin/page.tsx | 58 +- app/chat/admin/runtime/page.tsx | 5 + app/chat/admin/users/page.tsx | 5 + app/chat/components/chat-layout.tsx | 35 +- app/chat/components/chat-messages.tsx | 46 +- app/chat/components/chat-page-shell.tsx | 46 +- app/chat/components/chat-settings-shell.tsx | 96 +++ app/chat/components/chat-sidebar.tsx | 92 ++- app/chat/components/chat.utils.ts | 44 ++ app/chat/components/main-sidebar.tsx | 550 ++++++++++++------ app/chat/components/nested-agent-chat.tsx | 1 + app/chat/config/agents.ts | 2 +- app/chat/dataset/page.tsx | 13 + app/chat/evaluation/page.tsx | 15 +- app/chat/harness/page.tsx | 13 + app/chat/layout.tsx | 12 +- app/chat/logs/page.tsx | 13 + app/chat/mcp-a2a/page.tsx | 13 + app/chat/observability/page.tsx | 15 +- app/chat/providers/chat-context.tsx | 97 ++- app/chat/tools/page.tsx | 15 +- .../user/_components/user-settings-panel.tsx | 61 +- app/chat/user/api-keys/page.tsx | 5 + app/chat/user/danger-zone/page.tsx | 5 + app/chat/user/layout.tsx | 54 ++ app/chat/user/page.tsx | 73 ++- app/chat/user/profile/page.tsx | 5 + app/chat/user/security/page.tsx | 5 + app/chat/user/sessions/page.tsx | 5 + app/chat/workflows/[workflowId]/page.tsx | 15 +- app/chat/workflows/page.tsx | 13 + app/chat/workspaces/page.tsx | 56 +- app/login/page.tsx | 68 ++- app/login/signup/page.tsx | 57 +- app/networks/providers/network-context.tsx | 1 + app/workflows/providers/workflow-context.tsx | 1 + lib/auth-client.ts | 47 +- lib/hooks/use-mastra-query.ts | 12 +- lib/hooks/use-persistent-store.ts | 9 +- memory-bank/activeContext.md | 84 +++ memory-bank/progress.md | 122 ++++ package-lock.json | 73 +-- package.json | 1 + page-2026-04-15T07-04-23-082Z.png | Bin 0 -> 334774 bytes src/mastra/agents/AGENTS.md | 10 +- src/mastra/agents/browserAgent.ts | 41 +- src/mastra/agents/businessLegalAgents.ts | 6 +- src/mastra/agents/calendarAgent.ts | 2 +- src/mastra/agents/contentStrategistAgent.ts | 4 +- src/mastra/agents/customerSupportAgent.ts | 278 +++------ src/mastra/agents/dane.ts | 20 +- src/mastra/agents/dataExportAgent.ts | 4 +- src/mastra/agents/documentProcessingAgent.ts | 4 +- src/mastra/agents/editorAgent.ts | 4 +- src/mastra/agents/excalidraw_validator.ts | 8 +- src/mastra/agents/graphingAgents.ts | 6 +- src/mastra/agents/image.ts | 2 +- src/mastra/agents/image_to_csv.ts | 4 +- src/mastra/agents/index.ts | 1 + src/mastra/agents/learningExtractionAgent.ts | 4 +- src/mastra/agents/noteTakerAgent.ts | 2 +- src/mastra/agents/package-publisher.ts | 4 +- src/mastra/agents/projectManagementAgent.ts | 271 +++------ src/mastra/agents/recharts.ts | 14 +- src/mastra/agents/reportAgent.ts | 13 +- src/mastra/agents/researchAgent.ts | 218 ++++++- src/mastra/agents/researchPaperAgent.ts | 17 +- src/mastra/agents/scriptWriterAgent.ts | 4 +- src/mastra/agents/seoAgent.ts | 266 +++------ src/mastra/agents/socialMediaAgent.ts | 267 +++------ src/mastra/agents/stockAnalysisAgent.ts | 3 +- src/mastra/agents/supervisor-agent.ts | 335 +++-------- src/mastra/agents/translationAgent.ts | 258 ++------ src/mastra/agents/weather-agent.ts | 3 - src/mastra/agents/webResearchAgent.ts | 3 - src/mastra/auth.ts | 69 ++- src/mastra/browsers.ts | 380 ++++++++++-- src/mastra/config/libsql.ts | 5 +- src/mastra/index.ts | 2 + src/mastra/networks/AGENTS.md | 3 +- .../networks/businessIntelligenceNetwork.ts | 268 +++------ src/mastra/networks/codingTeamNetwork.ts | 229 ++------ src/mastra/networks/contentCreationNetwork.ts | 248 ++------ src/mastra/networks/dataPipelineNetwork.ts | 194 ++---- src/mastra/networks/devopsNetwork.ts | 239 ++------ .../networks/financialIntelligenceNetwork.ts | 154 ++--- src/mastra/networks/index.ts | 140 ++--- src/mastra/networks/learningNetwork.ts | 231 ++------ .../networks/marketingAutomationNetwork.ts | 257 +++----- .../networks/reportGenerationNetwork.ts | 258 ++------ .../networks/researchPipelineNetwork.ts | 260 ++------- src/mastra/networks/securityNetwork.ts | 231 ++------ src/mastra/processors/output-guardrails.ts | 52 ++ src/mastra/scorers/supervisor-scorers.ts | 312 ++++++++++ 100 files changed, 3779 insertions(+), 3902 deletions(-) create mode 100644 app/chat/admin/layout.tsx create mode 100644 app/chat/admin/runtime/page.tsx create mode 100644 app/chat/admin/users/page.tsx create mode 100644 app/chat/components/chat-settings-shell.tsx create mode 100644 app/chat/user/api-keys/page.tsx create mode 100644 app/chat/user/danger-zone/page.tsx create mode 100644 app/chat/user/layout.tsx create mode 100644 app/chat/user/profile/page.tsx create mode 100644 app/chat/user/security/page.tsx create mode 100644 app/chat/user/sessions/page.tsx create mode 100644 page-2026-04-15T07-04-23-082Z.png create mode 100644 src/mastra/processors/output-guardrails.ts diff --git a/.env.example b/.env.example index f2b4de8b..a433d1be 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,8 @@ GOOGLE_API_KEY='your-google-api-key' WORKSPACE_PATH='./workspace' # default local workspace path for tools that write to disk, can be overridden by tools with custom paths or when using AgentFS -BETTER_AUTH_URL='https://localhost:3000' # Base App URL +BETTER_AUTH_URL='http://localhost:3000' # Base App URL +NEXT_PUBLIC_BETTER_AUTH_URL='http://localhost:3000' # Browser-side Better Auth base URL BETTER_AUTH_SECRET='KQh7DvS4PtsNqJ1PZSoYheGOo1k13SUZqUBwNazc28U=' # openssl rand -base64 32 <--- run this to make ur own DEV_AUTH_ENABLED=true # Set to false to disable authentication in development (not recommended, but can be useful for quick testing) # Next.js + Mastra Client SDK @@ -29,20 +30,17 @@ DISCORD_CLIENT_ID='your_discord_client_id_here' DISCORD_SECRET_KEY='your_discord_secret_key_here' DISCORD_WEBHOOK_URL='your_discord_webhook_url_here' GOOGLE_CLIENT_ID="******************-**********************.apps.googleusercontent.com" +NEXT_PUBLIC_GOOGLE_CLIENT_ID="******************-**********************.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET="fake_google_client_secret_for_local_dev" -GOOGLE_CLIENT_CALLBACK_URL="https://localhost:3000/api/callback" +GOOGLE_CLIENT_CALLBACK_URL="http://localhost:3000/api/auth/callback/google" #Authorized redirect URIs -#https://localhost:3000/api/callback +#http://localhost:3000/api/auth/callback/google -#https://localhost:3000/callback +#http://127.0.0.1:3000/api/auth/callback/google -#https://127.0.0.1:3000/api/callback - -#http://127.0.0.1:3000/api/callback - -#https://127.0.0.1:3000/callback +#https://your-domain.com/api/auth/callback/google # Opencode Zen API Key diff --git a/.gitignore b/.gitignore index 971ab63a..f143f203 100644 --- a/.gitignore +++ b/.gitignore @@ -145,7 +145,7 @@ gha-creds-*.json .zencoder/rules/*.md # Stakpak local files -.stakpak +.stakpak/session* stakpak.backup stakpak.exe opnapi.json @@ -208,3 +208,19 @@ src/mastra/public/workspace/workspace/iran-war-report.md .env.local.bak thoughts/ledgers/CONTINUITY_ses_303c.md thoughts/ledgers/CONTINUITY_ses_303d.md +.mastra-project.json +start-server.js +start-dev.js +.playwright-mcp/page-2026-04-15T07-03-10-932Z.yml +.playwright-mcp/page-2026-04-15T07-04-06-556Z.yml +.playwright-mcp/page-2026-04-15T08-44-34-033Z.yml +.playwright-mcp/page-2026-04-15T07-36-00-846Z.yml +.playwright-mcp/page-2026-04-15T07-34-28-100Z.yml +.gitignore +.playwright-mcp/page-2026-04-15T07-29-52-207Z.yml +.playwright-mcp/page-2026-04-15T07-17-59-962Z.yml +.playwright-mcp/page-2026-04-15T07-31-15-475Z.yml +.playwright-mcp/page-2026-04-15T07-26-05-653Z.yml +.playwright-mcp/page-2026-04-15T07-24-17-682Z.yml +.playwright-mcp/page-2026-04-15T07-20-32-715Z.yml +.playwright-mcp/page-2026-04-15T07-18-30-461Z.yml diff --git a/README.md b/README.md index ac06e27d..da5ad3bd 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ ## Development ![Networks Custom Tool v1.0.0](networksCustomToolv1.png) +![Home Page v1.0.0](page-2026-04-15T07-04-23-082Z.png) diff --git a/app/chat/AGENTS.md b/app/chat/AGENTS.md index d8c2dffa..f418304b 100644 --- a/app/chat/AGENTS.md +++ b/app/chat/AGENTS.md @@ -2,6 +2,20 @@ # App/Chat +## Recent Update (2026-04-15) + +- Settings pages are now modular route groups instead of two monolithic screens: + - `/chat/user` overview + focused routes for profile, security, sessions, API keys, and danger zone + - `/chat/admin` overview + focused routes for runtime and user operations +- Use `app/chat/components/chat-settings-shell.tsx` when a chat route needs the shared `ChatProvider` + `ChatPageShell` + `MainSidebar` composition plus an in-section settings nav. +- Use the same shared shell composition for non-settings dashboard surfaces that need the persistent chat sidebar; the current wrapped set includes datasets, evaluation, observability, tools, logs, harness, MCP/A2A, workflows, and workflow detail pages. +- `UserSettingsPanel` and `AdminSettingsPanel` now support section-based rendering so new routes can reuse the same Better Auth mutations without duplicating form logic. +- `useWorkspaces()` in `lib/hooks/use-mastra-query.ts` now returns normalized `WorkspaceItem[]`; new chat UI code should consume that array directly instead of re-decoding `{ workspaces: [...] }` response shapes in components. +- Prefer shared tooltip and scroll affordances on high-density chat surfaces: + - add tooltip descriptions for navigation items and overview cards when a route’s purpose is not obvious + - use `ScrollArea` for long sidebars, thread lists, or horizontally dense settings navs rather than letting layout overflow + - keep shell spacing consistent through `ChatPageShell` instead of per-page padding drift + ## Overview The `/chat` route provides a rich AI chat interface built with **AI Elements** (52 components) integrated with **26+ Mastra agents**. Uses `@ai-sdk/react` with `useChat` and `DefaultChatTransport` to stream responses from Mastra's `/chat` route. diff --git a/app/chat/admin/_components/admin-management-panel.tsx b/app/chat/admin/_components/admin-management-panel.tsx index e12a81aa..cee0cbf9 100644 --- a/app/chat/admin/_components/admin-management-panel.tsx +++ b/app/chat/admin/_components/admin-management-panel.tsx @@ -81,6 +81,7 @@ type PasswordFormState = { } type AdminRole = 'user' | 'admin' +export type AdminSettingsPanelSection = 'all' | 'runtime' | 'users' const pageSize = 20 @@ -183,7 +184,11 @@ function mapUserToEditForm(user: AdminUser | null | undefined): EditUserFormStat /** * Admin user-management panel powered entirely by Better Auth hooks. */ -export function AdminSettingsPanel() { +export function AdminSettingsPanel({ + section = 'all', +}: { + section?: AdminSettingsPanelSection +}) { const { data: authSession } = useAuthQuery() const { data: modelProvidersData } = useAgentModelProviders() const [search, setSearch] = React.useState('') @@ -366,6 +371,8 @@ export function AdminSettingsPanel() { const refreshDisabled = usersQuery.isFetching const detailBusy = selectedUserQuery.isLoading || selectedSessionsQuery.isLoading + const showRuntime = section === 'all' || section === 'runtime' + const showUsers = section === 'all' || section === 'users' return ( @@ -549,7 +556,8 @@ export function AdminSettingsPanel() { - + {showUsers ? ( + Find users @@ -626,9 +634,11 @@ export function AdminSettingsPanel() { - + + ) : null} - + {showRuntime ? ( + Runtime context @@ -672,9 +682,11 @@ export function AdminSettingsPanel() { ))} - + + ) : null} -
+ {showUsers ? ( +
Users @@ -1041,7 +1053,8 @@ export function AdminSettingsPanel() { -
+
+ ) : null}
) diff --git a/app/chat/admin/layout.tsx b/app/chat/admin/layout.tsx new file mode 100644 index 00000000..6c3ffe8e --- /dev/null +++ b/app/chat/admin/layout.tsx @@ -0,0 +1,39 @@ +'use client' + +import type { ReactNode } from 'react' + +import { ChatSettingsShell } from '../components/chat-settings-shell' + +const adminSettingsSections = [ + { + href: '/chat/admin', + title: 'Overview', + description: 'Start from the admin summary and branch into runtime or user operations.', + }, + { + href: '/chat/admin/runtime', + title: 'Runtime', + description: 'Inspect active auth/runtime context and connected model providers.', + }, + { + href: '/chat/admin/users', + title: 'Users', + description: 'Search users, change roles, moderate access, impersonate, and revoke sessions.', + }, +] as const + +export default function AdminSettingsLayout({ + children, +}: { + children: ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/app/chat/admin/page.tsx b/app/chat/admin/page.tsx index 71e99c0e..4ef60539 100644 --- a/app/chat/admin/page.tsx +++ b/app/chat/admin/page.tsx @@ -1,18 +1,54 @@ -import { ChatPageShell } from '../components/chat-page-shell' -import { MainSidebar } from '../components/main-sidebar' -import { AdminSettingsPanel } from './_components/admin-management-panel' +import Link from 'next/link' + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/ui/tooltip' /** - * Admin settings page for chat operators. + * Admin settings overview for chat operators. */ export default function AdminPage() { + const sections = [ + { + href: '/chat/admin/runtime', + title: 'Runtime context', + description: 'Inspect the live Better Auth session and Mastra provider inventory.', + }, + { + href: '/chat/admin/users', + title: 'Users', + description: 'Search users, edit records, manage roles, moderate bans, and revoke sessions.', + }, + ] as const + return ( - } - > - - + +
+ {sections.map((section) => ( + + + + + + {section.title} + {section.description} + + + Open the focused {section.title.toLowerCase()} admin route. + + + + + + {section.description} + + + ))} +
+
) } diff --git a/app/chat/admin/runtime/page.tsx b/app/chat/admin/runtime/page.tsx new file mode 100644 index 00000000..d93d74da --- /dev/null +++ b/app/chat/admin/runtime/page.tsx @@ -0,0 +1,5 @@ +import { AdminSettingsPanel } from '../_components/admin-management-panel' + +export default function AdminRuntimeSettingsPage() { + return +} diff --git a/app/chat/admin/users/page.tsx b/app/chat/admin/users/page.tsx new file mode 100644 index 00000000..838b6992 --- /dev/null +++ b/app/chat/admin/users/page.tsx @@ -0,0 +1,5 @@ +import { AdminSettingsPanel } from '../_components/admin-management-panel' + +export default function AdminUsersSettingsPage() { + return +} diff --git a/app/chat/components/chat-layout.tsx b/app/chat/components/chat-layout.tsx index 86f2c23d..70e6378e 100644 --- a/app/chat/components/chat-layout.tsx +++ b/app/chat/components/chat-layout.tsx @@ -6,6 +6,7 @@ import { ChatInput } from './chat-input' import { ChatSidebar } from './chat-sidebar' import { MainSidebar } from './main-sidebar' import { SidebarProvider, SidebarInset } from '@/ui/sidebar' +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/ui/resizable' import { useChatContext } from '../providers/chat-context-hooks' import { cn } from '@/lib/utils' @@ -23,18 +24,30 @@ export function ChatLayout() { -
-
- - -
- - {!isFocusMode && ( -
- + + +
+ +
- )} -
+ + + {!isFocusMode ? ( + <> + + +
+ +
+
+ + ) : null} + diff --git a/app/chat/components/chat-messages.tsx b/app/chat/components/chat-messages.tsx index 3a1128ce..36f0d0cb 100644 --- a/app/chat/components/chat-messages.tsx +++ b/app/chat/components/chat-messages.tsx @@ -72,6 +72,7 @@ import { AgentConfirmation } from './agent-confirmation' import { AgentWorkflow, type WorkflowNode, type WorkflowEdge } from './agent-workflow' import { extractPlanFromText, + extractThoughtSummaryFromProviderMetadata, parseReasoningToSteps, tokenizeInlineCitations, } from './chat.utils' @@ -743,28 +744,17 @@ function extractThoughtSummaryFromParts( } for (const part of parts) { - const pm = (part as { providerMetadata?: ProviderMetadata | undefined }) - .providerMetadata - - const googleMeta = (pm as Record).google - if ( - googleMeta === undefined || - googleMeta === null || - typeof googleMeta !== 'object' - ) { - continue - } - - const candidates = [ - (googleMeta as Record).thoughtSummary, - (googleMeta as Record).thoughts, - (googleMeta as Record).thinkingSummary, - ] - - for (const c of candidates) { - if (typeof c === 'string' && c.trim().length > 0) { - return c - } + const providerMetadata = + 'providerMetadata' in part + ? (part.providerMetadata as ProviderMetadata | undefined) + : 'callProviderMetadata' in part + ? (part.callProviderMetadata as ProviderMetadata | undefined) + : undefined + + const summary = + extractThoughtSummaryFromProviderMetadata(providerMetadata) + if (summary.length > 0) { + return summary } } @@ -2158,7 +2148,15 @@ export function ChatMessages(_props?: Partial) { const timeoutId = window.setTimeout(() => { const validateMessages = async () => { - if (messages.length === 0) { + const hasIncompleteMessage = messages.some((message) => { + if (!Array.isArray(message.parts)) { + return true + } + + return message.parts.length === 0 + }) + + if (messages.length === 0 || isLoading || hasIncompleteMessage) { setValidationError(null) return } @@ -2186,7 +2184,7 @@ export function ChatMessages(_props?: Partial) { isMounted = false window.clearTimeout(timeoutId) } - }, [messages]) + }, [isLoading, messages]) const showReasoning = agentConfig?.features.reasoning ?? false const showChainOfThought = agentConfig?.features.chainOfThought ?? false diff --git a/app/chat/components/chat-page-shell.tsx b/app/chat/components/chat-page-shell.tsx index 3a58b974..c1a3c3fb 100644 --- a/app/chat/components/chat-page-shell.tsx +++ b/app/chat/components/chat-page-shell.tsx @@ -13,6 +13,7 @@ interface ChatPageShellProps { eyebrow?: string contentClassName?: string sidebar?: ReactNode + hideHeader?: boolean } /** @@ -26,37 +27,40 @@ export function ChatPageShell({ eyebrow = 'AgentStack command center', contentClassName, sidebar, + hideHeader = false, }: ChatPageShellProps) { return ( {sidebar ?? null}
-
-
-
-

- {eyebrow} -

-

- {title} -

-

- {description} -

-
- - {actions ? ( -
- {actions} + {hideHeader ? null : ( +
+
+
+

+ {eyebrow} +

+

+ {title} +

+

+ {description} +

- ) : null} -
-
+ + {actions ? ( +
+ {actions} +
+ ) : null} +
+
+ )}
diff --git a/app/chat/components/chat-settings-shell.tsx b/app/chat/components/chat-settings-shell.tsx new file mode 100644 index 00000000..8fce08b6 --- /dev/null +++ b/app/chat/components/chat-settings-shell.tsx @@ -0,0 +1,96 @@ +'use client' + +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import type { ReactNode } from 'react' + +import { MainSidebar } from './main-sidebar' +import { ChatPageShell } from './chat-page-shell' +import { ChatProvider } from '../providers/chat-context' +import { cn } from '@/lib/utils' +import { ScrollArea, ScrollBar } from '@/ui/scroll-area' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/ui/tooltip' + +type SettingsSection = { + href: string + title: string + description: string +} + +interface ChatSettingsShellProps { + title: string + description: string + sections: SettingsSection[] + children: ReactNode +} + +/** + * Shared shell for modular chat settings routes that need the main sidebar and + * a secondary in-section navigation. + */ +export function ChatSettingsShell({ + title, + description, + sections, + children, +}: ChatSettingsShellProps) { + const pathname = usePathname() + + return ( + + } + > + +
+ + + + + + {children} +
+
+
+
+ ) +} diff --git a/app/chat/components/chat-sidebar.tsx b/app/chat/components/chat-sidebar.tsx index 1a74cb19..5f629186 100644 --- a/app/chat/components/chat-sidebar.tsx +++ b/app/chat/components/chat-sidebar.tsx @@ -2,6 +2,7 @@ import { useChatContext } from '@/app/chat/providers/chat-context-hooks' import { + DEFAULT_VECTOR_STORE_NAME, useAgent, useAgentEnhanceInstructionsMutation, useAgents, @@ -10,6 +11,7 @@ import { useTools, useTraces, useVectorIndexes, + useVectors, useWorkflows, useProcessors, useScorers, @@ -66,6 +68,11 @@ type TabKey = | 'config' type TraceRecord = Record +type WorkspaceRecord = { + id?: string + name?: string + agentName?: string +} const TRACE_STATUS_COLORS: Record = { ok: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/20', @@ -233,7 +240,7 @@ export function ChatSidebar() { const workflowsQuery = useWorkflows() const tracesQuery = useTraces({ pagination: { page: 1, perPage: 20 } }) const threadsQuery = useThreads({ resourceId }) - const vectorsQuery = useVectorIndexes() + const vectorStoresQuery = useVectors() const memoryStatusQuery = useMemoryStatus(agentConfig?.id ?? '') const agentDetailsQuery = useAgent(selectedAgent) @@ -253,10 +260,6 @@ export function ChatSidebar() { void tracesQuery.refetch() }, [tracesQuery]) - const onRefreshVectors = useCallback(() => { - void vectorsQuery.refetch() - }, [vectorsQuery]) - const onRefreshThreads = useCallback(() => { void threadsQuery.refetch() @@ -281,19 +284,28 @@ export function ChatSidebar() { const threads = threadsQuery.data ?? [] const loadingThreads = threadsQuery.isLoading - const vectors = vectorsQuery.data ?? [] - const loadingVectors = vectorsQuery.isLoading - const workspacesQuery = useWorkspaces() const workspaceSkillsQuery = useWorkspaceSkills(resourceId) const storedSkillsQuery = useStoredSkills() const processorsQuery = useProcessors() const scorersQuery = useScorers() - const workspaceLabels = useMemo( - () => normalizeCollection(workspacesQuery.data), + const workspaceItems = useMemo( + () => (workspacesQuery.data ?? []) as WorkspaceRecord[], [workspacesQuery.data] ) + const workspaceLabels = useMemo( + () => + workspaceItems + .map((workspace) => + safeString( + workspace.name ?? workspace.agentName ?? workspace.id, + '' + ) + ) + .filter((label) => label.length > 0), + [workspaceItems] + ) const workspaceSkillLabels = useMemo( () => normalizeCollection(workspaceSkillsQuery.data), [workspaceSkillsQuery.data] @@ -316,8 +328,47 @@ export function ChatSidebar() { [workflows] ) + const activeWorkspace = useMemo( + () => workspaceItems.find((workspace) => workspace.id === resourceId) ?? null, + [resourceId, workspaceItems] + ) + const preferredVectorStoreName = useMemo(() => { + const vectorStores = Array.isArray(vectorStoresQuery.data) + ? vectorStoresQuery.data + : [] + const vectorNames: string[] = vectorStores + .map((vectorStore) => { + if (typeof vectorStore === 'string') { + return vectorStore + } + + if (isRecord(vectorStore)) { + return safeString(vectorStore.name ?? vectorStore.id, '') + } + + return '' + }) + .filter((name: string) => name.length > 0) + + return ( + vectorNames.find((name) => name === DEFAULT_VECTOR_STORE_NAME) ?? + vectorNames[0] ?? + DEFAULT_VECTOR_STORE_NAME + ) + }, [vectorStoresQuery.data]) + const vectorsQuery = useVectorIndexes(preferredVectorStoreName) + const vectors = vectorsQuery.data ?? [] + const loadingVectors = vectorsQuery.isLoading || vectorStoresQuery.isLoading + + const onRefreshVectors = useCallback(() => { + void Promise.all([vectorStoresQuery.refetch(), vectorsQuery.refetch()]) + }, [vectorStoresQuery, vectorsQuery]) + const workspaceName = - workspaceLabels[0] ?? safeString(resourceId, 'Current workspace') + safeString( + activeWorkspace?.name ?? activeWorkspace?.agentName ?? activeWorkspace?.id, + safeString(resourceId, 'Current workspace') + ) const memoryStatusRes = memoryStatusQuery.data @@ -434,7 +485,7 @@ export function ChatSidebar() { { setActiveTab(v as TabKey); }} - className="flex flex-col flex-1 overflow-hidden" + className="flex flex-col flex-1 min-h-0 overflow-hidden" >
@@ -497,8 +548,8 @@ export function ChatSidebar() {
- -
+ +
{/* ──── Threads Tab ──── */}
-
- -

- Vector Indexes -

+
+ +

+ Vector Indexes +

- + + + + + + + + + Return to the main chat dashboard. + + + - - - - Pages - - - - {pageItems.map((item) => { - const Icon = item.icon + + +
+ + + Pages + + + + {pageItems.map((item) => { + const Icon = item.icon - return ( - - - -
- -
-
-
- {item.label} -
-
- -
-
- ) - })} -
-
-
+ return ( + + + + + +
+ +
+
+
+ {item.label} +
+
+ {item.description} +
+
+ +
+
+ + {item.description} + +
+
+ ) + })} + + + - - - Current Threads - - - {threadsResult.isLoading ? ( -
- -
- ) : threads.length === 0 ? ( -
- No threads for this agent yet -
- ) : ( - - {threads.map((thread) => { - const currentThreadId = getThreadId(thread) - if (!currentThreadId) { - return null - } + + + Current Threads + + + {threadsResult.isLoading ? ( +
+ +
+ ) : threads.length === 0 ? ( +
+ No threads for this agent yet. +
+ ) : ( + + + {threads.map((thread) => { + const currentThreadId = getThreadId(thread) + if (!currentThreadId) { + return null + } - const label = getThreadLabel(thread) - const meta = formatThreadMeta(thread) + const label = getThreadLabel(thread) + const meta = formatThreadMeta(thread) - return ( - - { - handleThreadClick(currentThreadId) - }} - className={cn( - 'w-full cursor-pointer items-start gap-3 rounded-xl px-3 py-2.5 text-left transition-all duration-200', - threadId === currentThreadId - ? 'bg-primary/10 text-primary font-medium' - : 'text-muted-foreground hover:bg-muted/50 hover:text-foreground' - )} - > -
- -
-
-
- {label} -
-
- {meta || currentThreadId} -
-
-
-
- ) - })} -
- )} -
-
- + return ( + + + + { + handleThreadClick( + currentThreadId + ) + }} + className={cn( + 'w-full cursor-pointer items-start gap-3 rounded-2xl px-3 py-3 text-left transition-all duration-200', + threadId === currentThreadId + ? 'bg-primary/10 text-primary font-medium' + : 'text-muted-foreground hover:bg-muted/50 hover:text-foreground' + )} + > +
+ +
+
+
+ {label} +
+
+ {meta || + currentThreadId} +
+
+
+
+ +
+ {label} +
+
+ {meta || + currentThreadId} +
+
+
+
+ ) + })} +
+ + )} +
+
+
+
+
- -
- {session?.user ? ( -
-
- Account -
-
- {session.user.name ?? 'Account'} -
-
- {session.user.email} -
-
- - - - User settings - - - {session.user.role === 'admin' ? ( + +
+ {session?.user ? ( +
+
+ Account +
+
+ {session.user.name ?? 'Account'} +
+
+ {session.user.email} +
+
+ - - Admin settings - + + + + + User settings + + + + + Open your personal settings routes. + + - ) : null} - + {session.user.role === 'admin' ? ( + + + + + + Admin settings + + + + + Open runtime and user administration controls. + + + + ) : null} + +
-
- ) : null} + ) : null} - - - { - router.push('/chat/agents') - }} - > - - Choose agent - - - - - - -
- - + + + + + { + router.push('/chat/agents') + }} + > + + Choose agent + + + + Open the agent directory and start a new thread. + + + + + + +
+ +
+
+ + Sign out of the current Better Auth session. + +
+
+
+
+ + + ) } diff --git a/app/chat/components/nested-agent-chat.tsx b/app/chat/components/nested-agent-chat.tsx index ec693641..baa0aa00 100644 --- a/app/chat/components/nested-agent-chat.tsx +++ b/app/chat/components/nested-agent-chat.tsx @@ -8,6 +8,7 @@ export function NestedAgentChat() { const { messages, sendMessage, status } = useChat({ transport: new DefaultChatTransport({ api: 'http://localhost:4111/chat/weatherAgent', + credentials: 'include', }), }) diff --git a/app/chat/config/agents.ts b/app/chat/config/agents.ts index d5c292ef..e835d97a 100644 --- a/app/chat/config/agents.ts +++ b/app/chat/config/agents.ts @@ -140,7 +140,7 @@ export const AGENT_CONFIGS: Record = { "knowledgeIndexingAgent": { id: 'knowledgeIndexingAgent', name: 'Knowledge Indexing Agent', - description: 'Index documents into PgVector for semantic search', + description: 'Index documents into the live vector store for semantic search', category: 'research', features: { ...defaultFeatures, diff --git a/app/chat/dataset/page.tsx b/app/chat/dataset/page.tsx index 87c45e22..b3b0c216 100644 --- a/app/chat/dataset/page.tsx +++ b/app/chat/dataset/page.tsx @@ -30,6 +30,9 @@ import { TooltipProvider, TooltipTrigger, } from '@/ui/tooltip' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' import { Dialog, DialogContent, @@ -343,6 +346,14 @@ export default function DatasetPage() { ) return ( + + } + hideHeader + contentClassName="p-0" + >
{showHelpPanel ? ( @@ -1489,5 +1500,7 @@ export default function DatasetPage() {
+ + ) } diff --git a/app/chat/evaluation/page.tsx b/app/chat/evaluation/page.tsx index fb64d152..4184a05d 100644 --- a/app/chat/evaluation/page.tsx +++ b/app/chat/evaluation/page.tsx @@ -59,6 +59,9 @@ import { SparklesIcon, } from 'lucide-react' import { Panel } from '@/src/components/ai-elements/panel' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' type AnyRecord = Record @@ -236,6 +239,14 @@ export default function EvaluationPage() { } return ( + + } + hideHeader + contentClassName="p-0" + >
{showHelpPanel ? ( @@ -810,5 +821,7 @@ export default function EvaluationPage() {
+ + ) -} \ No newline at end of file +} diff --git a/app/chat/harness/page.tsx b/app/chat/harness/page.tsx index 4d02e283..82d1f35e 100644 --- a/app/chat/harness/page.tsx +++ b/app/chat/harness/page.tsx @@ -126,6 +126,9 @@ import { StackTraceHeader, StackTraceFrames, } from '@/src/components/ai-elements/stack-trace' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' import { Terminal, TerminalActions, @@ -1098,6 +1101,14 @@ export default function HarnessPage() { } return ( + + } + hideHeader + contentClassName="p-0" + >
@@ -1730,5 +1741,7 @@ export default function HarnessPage() {
+
+
) } diff --git a/app/chat/layout.tsx b/app/chat/layout.tsx index 9e40b348..e5f1fbf0 100644 --- a/app/chat/layout.tsx +++ b/app/chat/layout.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react' +import { Suspense, type ReactNode } from 'react' import { headers } from 'next/headers' import { redirect } from 'next/navigation' @@ -11,7 +11,7 @@ import { auth } from '@/auth' * This prevents unauthenticated users from briefly rendering protected chat * surfaces before the client redirect runs. */ -export default async function ChatLayout({ +async function ChatSessionGate({ children, }: { children: ReactNode @@ -26,3 +26,11 @@ export default async function ChatLayout({ return children } + +export default function ChatLayout({ + children, +}: { + children: ReactNode +}) { + return {children} +} diff --git a/app/chat/logs/page.tsx b/app/chat/logs/page.tsx index ea7ff150..0a2a1888 100644 --- a/app/chat/logs/page.tsx +++ b/app/chat/logs/page.tsx @@ -62,6 +62,9 @@ import { PanelRightCloseIcon, } from 'lucide-react' import { cn } from '@/lib/utils' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' type LogRecord = Record @@ -243,6 +246,14 @@ export default function LogsPage() { }, [rawLogs]) return ( + + } + hideHeader + contentClassName="p-0" + >
{/* Header */} @@ -630,5 +641,7 @@ export default function LogsPage() {
+ + ) } diff --git a/app/chat/mcp-a2a/page.tsx b/app/chat/mcp-a2a/page.tsx index 05c71445..917faf52 100644 --- a/app/chat/mcp-a2a/page.tsx +++ b/app/chat/mcp-a2a/page.tsx @@ -27,6 +27,9 @@ import { TooltipTrigger, } from '@/ui/tooltip' import { Panel } from '@/src/components/ai-elements/panel' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' import { BotIcon, CircleHelpIcon, @@ -105,6 +108,14 @@ export default function McpA2APage() { agents.find((agent) => agent.id === activeAgentId) ?? agents[0] return ( + + } + hideHeader + contentClassName="p-0" + >
@@ -384,5 +395,7 @@ export default function McpA2APage() {
+ + ) } diff --git a/app/chat/observability/page.tsx b/app/chat/observability/page.tsx index 7e482ffe..1c5e372d 100644 --- a/app/chat/observability/page.tsx +++ b/app/chat/observability/page.tsx @@ -39,6 +39,9 @@ import { TooltipTrigger, } from '@/ui/tooltip' import { cn } from '@/lib/utils' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' type SpanRecord = Record & { id?: string @@ -410,6 +413,14 @@ export default function ObservabilityPage() { const visibleSpans = spans return ( + + } + hideHeader + contentClassName="p-0" + >
@@ -670,5 +681,7 @@ export default function ObservabilityPage() {
+
+
) -} \ No newline at end of file +} diff --git a/app/chat/providers/chat-context.tsx b/app/chat/providers/chat-context.tsx index 6aa3cebe..6997ac1f 100644 --- a/app/chat/providers/chat-context.tsx +++ b/app/chat/providers/chat-context.tsx @@ -68,6 +68,7 @@ import { import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react' import { useAuthQuery } from '@/lib/hooks/use-auth-query' import { useAgent, useAgentModelProviders } from '@/lib/hooks/use-mastra-query' +import { extractThoughtSummaryFromProviderMetadata } from '../components/chat.utils' import { ChatContext } from './chat-context-hooks' const CHAT_PROVIDER_ID_CONTEXT_KEY = 'provider-id' as const @@ -316,13 +317,9 @@ export function ChatProvider({ null ) - const [resourceId, setResourceIdState] = useState( - defaultResourceId ?? '' - ) + const [resourceIdOverride, setResourceIdState] = useState('') - const [threadId, setThreadIdState] = useState( - defaultThreadId ?? '' - ) + const [threadIdOverride, setThreadIdState] = useState('') const [selectedModelId, setSelectedModelId] = useState('') const [isFocusMode, setFocusMode] = useState(false) @@ -335,41 +332,36 @@ export function ChatProvider({ [modelProvidersQuery.data] ) - useEffect(() => { + // Ref to track message snapshots for checkpoint restore + const messageSnapshotsRef = useRef>(new Map()) + + const resourceId = useMemo(() => { + if (resourceIdOverride.length > 0) { + return resourceIdOverride + } + if (defaultResourceId !== undefined && defaultResourceId.trim().length > 0) { - queueMicrotask(() => { - setResourceIdState(defaultResourceId) - }) - return + return defaultResourceId } - if (userId.length > 0) { - queueMicrotask(() => { - setResourceIdState(userId) - }) + return userId + }, [defaultResourceId, resourceIdOverride, userId]) + + const threadId = useMemo(() => { + if (threadIdOverride.length > 0) { + return threadIdOverride } - }, [defaultResourceId, userId]) - useEffect(() => { - if ( - defaultThreadId !== undefined && - defaultThreadId.trim().length > 0 - ) { - queueMicrotask(() => { - setThreadIdState(defaultThreadId) - }) - return + if (defaultThreadId !== undefined && defaultThreadId.trim().length > 0) { + return defaultThreadId } if (userId.length > 0) { - queueMicrotask(() => { - setThreadIdState(`thread:${userId}:${defaultAgent}`) - }) + return `thread:${userId}:${defaultAgent}` } - }, [defaultAgent, defaultThreadId, userId]) - // Ref to track message snapshots for checkpoint restore - const messageSnapshotsRef = useRef>(new Map()) + return '' + }, [defaultAgent, defaultThreadId, threadIdOverride, userId]) const availableModels = useMemo(() => { const modelsById = new Map() @@ -441,6 +433,7 @@ export function ChatProvider({ new DefaultChatTransport({ // Use stable endpoint - agentId passed in body, not URL path api: `${MASTRA_API_URL}/chat/${selectedAgent}`, + credentials: 'include', prepareSendMessagesRequest({ messages: outgoingMessages, requestMetadata, @@ -515,6 +508,20 @@ export function ChatProvider({ useEffect(() => { let cancelled = false + const hasIncompleteMessage = messages.some((message) => { + if (!Array.isArray(message.parts)) { + return true + } + + return message.parts.length === 0 + }) + + if (messages.length === 0 || aiStatus !== 'ready' || hasIncompleteMessage) { + return () => { + cancelled = true + } + } + void safeValidateUIMessages({ messages }).then((result) => { if (cancelled) { return @@ -530,7 +537,7 @@ export function ChatProvider({ return () => { cancelled = true } - }, [messages]) + }, [aiStatus, messages]) const aiErrorMessage = useMemo( () => (aiError ? normalizeChatError(aiError) : null), @@ -586,28 +593,10 @@ export function ChatProvider({ ? (part.callProviderMetadata as ProviderMetadata) : undefined - const googleMeta = providerMetadata?.google as - | Record - | undefined - - if (googleMeta === undefined) { - continue - } - - // Different SDKs/versions surface this under slightly different keys. - const candidates = [ - googleMeta.thoughtSummary, - googleMeta.thoughts, - googleMeta.thinkingSummary, - ] - - for (const candidate of candidates) { - if ( - typeof candidate === 'string' && - candidate.trim().length > 0 - ) { - return candidate - } + const summary = + extractThoughtSummaryFromProviderMetadata(providerMetadata) + if (summary.length > 0) { + return summary } } } diff --git a/app/chat/tools/page.tsx b/app/chat/tools/page.tsx index 56ab3cba..f5de57ea 100644 --- a/app/chat/tools/page.tsx +++ b/app/chat/tools/page.tsx @@ -37,6 +37,9 @@ import { SparklesIcon, WrenchIcon, } from 'lucide-react' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' type ToolRecord = { id: string @@ -145,6 +148,14 @@ export default function ChatToolsPage() { const selectedToolDetails = toolDetailsQuery.data ?? selectedTool return ( + + } + hideHeader + contentClassName="p-0" + >
@@ -505,5 +516,7 @@ export default function ChatToolsPage() {
+
+
) -} \ No newline at end of file +} diff --git a/app/chat/user/_components/user-settings-panel.tsx b/app/chat/user/_components/user-settings-panel.tsx index 35bfd7df..5e49d2a9 100644 --- a/app/chat/user/_components/user-settings-panel.tsx +++ b/app/chat/user/_components/user-settings-panel.tsx @@ -112,6 +112,14 @@ type ProfileFormState = { image: string } +export type UserSettingsPanelSection = + | 'all' + | 'profile' + | 'security' + | 'sessions' + | 'api-keys' + | 'danger-zone' + const emptyApiKeyForm: ApiKeyFormState = { name: '', prefix: 'ak_', @@ -171,7 +179,11 @@ function badgeVariantFromBoolean(value: boolean): React.ComponentProps) { event.preventDefault() @@ -414,8 +431,10 @@ export function UserSettingsPanel() {
-
- + {showProfile || showSecurity ? ( +
+ {showProfile ? ( + Profile @@ -502,9 +521,11 @@ export function UserSettingsPanel() { Save profile - + + ) : null} - + {showSecurity ? ( + Security @@ -629,10 +650,13 @@ export function UserSettingsPanel() { Sign out only - -
+
+ ) : null} +
+ ) : null} - + {showSessions ? ( + Live sessions @@ -704,9 +728,11 @@ export function UserSettingsPanel() { - + + ) : null} - + {showApiKeys ? ( + API keys @@ -1016,9 +1042,11 @@ export function UserSettingsPanel() { - + + ) : null} - + {showDangerZone ? ( + Danger zone @@ -1064,10 +1092,12 @@ export function UserSettingsPanel() { - + + ) : null} - !open && setEditingKey(null)}> + {showApiKeys ? ( + !open && setEditingKey(null)}> Edit API key @@ -1192,7 +1222,8 @@ export function UserSettingsPanel() { ) : null} - + + ) : null} ) } diff --git a/app/chat/user/api-keys/page.tsx b/app/chat/user/api-keys/page.tsx new file mode 100644 index 00000000..3e78a6c5 --- /dev/null +++ b/app/chat/user/api-keys/page.tsx @@ -0,0 +1,5 @@ +import { UserSettingsPanel } from '../_components/user-settings-panel' + +export default function UserApiKeysSettingsPage() { + return +} diff --git a/app/chat/user/danger-zone/page.tsx b/app/chat/user/danger-zone/page.tsx new file mode 100644 index 00000000..c16d0bd9 --- /dev/null +++ b/app/chat/user/danger-zone/page.tsx @@ -0,0 +1,5 @@ +import { UserSettingsPanel } from '../_components/user-settings-panel' + +export default function UserDangerZoneSettingsPage() { + return +} diff --git a/app/chat/user/layout.tsx b/app/chat/user/layout.tsx new file mode 100644 index 00000000..67608822 --- /dev/null +++ b/app/chat/user/layout.tsx @@ -0,0 +1,54 @@ +'use client' + +import type { ReactNode } from 'react' + +import { ChatSettingsShell } from '../components/chat-settings-shell' + +const userSettingsSections = [ + { + href: '/chat/user', + title: 'Overview', + description: 'Start from the account summary and jump to the right settings surface.', + }, + { + href: '/chat/user/profile', + title: 'Profile', + description: 'Edit your name, username, and avatar identity.', + }, + { + href: '/chat/user/security', + title: 'Security', + description: 'Change your password, send resets, and manage sign-out posture.', + }, + { + href: '/chat/user/sessions', + title: 'Sessions', + description: 'Inspect active devices and revoke live sessions.', + }, + { + href: '/chat/user/api-keys', + title: 'API keys', + description: 'Issue, rotate, and revoke account-scoped API keys.', + }, + { + href: '/chat/user/danger-zone', + title: 'Danger zone', + description: 'Handle irreversible account deletion controls.', + }, +] as const + +export default function UserSettingsLayout({ + children, +}: { + children: ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/app/chat/user/page.tsx b/app/chat/user/page.tsx index 6bd82815..be9b0c66 100644 --- a/app/chat/user/page.tsx +++ b/app/chat/user/page.tsx @@ -1,18 +1,69 @@ -import { ChatPageShell } from '../components/chat-page-shell' -import { MainSidebar } from '../components/main-sidebar' -import { UserSettingsPanel } from './_components/user-settings-panel' +import Link from 'next/link' + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/ui/tooltip' /** - * User settings page for the signed-in chat account. + * User settings overview for the signed-in chat account. */ export default function UserPage() { + const sections = [ + { + href: '/chat/user/profile', + title: 'Profile', + description: 'Update your display name, username, and avatar.', + }, + { + href: '/chat/user/security', + title: 'Security', + description: 'Rotate your password and control your sign-out posture.', + }, + { + href: '/chat/user/sessions', + title: 'Sessions', + description: 'Inspect and revoke active devices tied to your account.', + }, + { + href: '/chat/user/api-keys', + title: 'API keys', + description: 'Create and manage account-scoped API keys.', + }, + { + href: '/chat/user/danger-zone', + title: 'Danger zone', + description: 'Handle irreversible account deletion actions.', + }, + ] as const + return ( - } - > - - + +
+ {sections.map((section) => ( + + + + + + {section.title} + {section.description} + + + Open the focused {section.title.toLowerCase()} route. + + + + + + {section.description} + + + ))} +
+
) } diff --git a/app/chat/user/profile/page.tsx b/app/chat/user/profile/page.tsx new file mode 100644 index 00000000..7129ee99 --- /dev/null +++ b/app/chat/user/profile/page.tsx @@ -0,0 +1,5 @@ +import { UserSettingsPanel } from '../_components/user-settings-panel' + +export default function UserProfileSettingsPage() { + return +} diff --git a/app/chat/user/security/page.tsx b/app/chat/user/security/page.tsx new file mode 100644 index 00000000..d11e9028 --- /dev/null +++ b/app/chat/user/security/page.tsx @@ -0,0 +1,5 @@ +import { UserSettingsPanel } from '../_components/user-settings-panel' + +export default function UserSecuritySettingsPage() { + return +} diff --git a/app/chat/user/sessions/page.tsx b/app/chat/user/sessions/page.tsx new file mode 100644 index 00000000..a8b73143 --- /dev/null +++ b/app/chat/user/sessions/page.tsx @@ -0,0 +1,5 @@ +import { UserSettingsPanel } from '../_components/user-settings-panel' + +export default function UserSessionsSettingsPage() { + return +} diff --git a/app/chat/workflows/[workflowId]/page.tsx b/app/chat/workflows/[workflowId]/page.tsx index c0c1e382..a8721375 100644 --- a/app/chat/workflows/[workflowId]/page.tsx +++ b/app/chat/workflows/[workflowId]/page.tsx @@ -36,6 +36,9 @@ import { RefreshCwIcon, WorkflowIcon, } from 'lucide-react' +import { ChatPageShell } from '../../components/chat-page-shell' +import { MainSidebar } from '../../components/main-sidebar' +import { ChatProvider } from '../../providers/chat-context' type WorkflowRunRecord = Record @@ -128,6 +131,14 @@ export default function WorkflowDetailPage() { ) return ( + + } + hideHeader + contentClassName="p-0" + >
@@ -399,5 +410,7 @@ export default function WorkflowDetailPage() {
+
+
) -} \ No newline at end of file +} diff --git a/app/chat/workflows/page.tsx b/app/chat/workflows/page.tsx index afabbd3b..c28db423 100644 --- a/app/chat/workflows/page.tsx +++ b/app/chat/workflows/page.tsx @@ -22,6 +22,9 @@ import { GitBranchIcon, PanelRightCloseIcon, } from 'lucide-react' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' interface WorkflowRecord { id?: string @@ -67,6 +70,14 @@ export default function ChatWorkflowsPage() { }, [query, workflows]) return ( + + } + hideHeader + contentClassName="p-0" + >
@@ -216,5 +227,7 @@ export default function ChatWorkflowsPage() {
+
+
) } diff --git a/app/chat/workspaces/page.tsx b/app/chat/workspaces/page.tsx index dfd8387c..eb372ba5 100644 --- a/app/chat/workspaces/page.tsx +++ b/app/chat/workspaces/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo, useState } from 'react' +import { Suspense, useEffect, useMemo, useState } from 'react' import type { SkillMetadata, @@ -25,6 +25,9 @@ import { useWorkspaceSkills, useWorkspaces, } from '@/lib/hooks/use-mastra-query' +import { ChatPageShell } from '@/app/chat/components/chat-page-shell' +import { MainSidebar } from '@/app/chat/components/main-sidebar' +import { ChatProvider } from '@/app/chat/providers/chat-context' import { Badge } from '@/ui/badge' import { Button } from '@/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card' @@ -165,6 +168,24 @@ function parentWorkspacePath(path: string): string { * Explorer for live workspace metadata, skills, and sandbox files. */ export default function WorkspacesPage() { + return ( + + + } + contentClassName="p-0" + hideHeader + > + + + + + ) +} + +function WorkspacesPageContent() { const workspacesQuery = useWorkspaces() const [showHelpPanel, setShowHelpPanel] = useState(true) const [workspaceSearch, setWorkspaceSearch] = useState('') @@ -185,7 +206,7 @@ export default function WorkspacesPage() { const [terminalOutput, setTerminalOutput] = useState('') const workspaces = useMemo( - () => normalizeCollection(workspacesQuery.data, 'workspaces'), + () => workspacesQuery.data ?? [], [workspacesQuery.data] ) @@ -328,6 +349,35 @@ export default function WorkspacesPage() { ] ) + useEffect(() => { + const fallbackWorkspaceId = filteredWorkspaces[0]?.id ?? workspaces[0]?.id ?? '' + const workspaceStillExists = + selectedWorkspaceId.length === 0 || + workspaces.some((workspace) => workspace.id === selectedWorkspaceId) + + if (workspaceStillExists && selectedWorkspaceId.length > 0) { + return + } + + queueMicrotask(() => { + setSelectedWorkspaceId(fallbackWorkspaceId) + }) + }, [filteredWorkspaces, selectedWorkspaceId, workspaces]) + + useEffect(() => { + queueMicrotask(() => { + setSelectedSkillName('') + setFilesystemPath('/') + setSelectedEntryPath('') + setSelectedEntryType('') + setEditorContent('') + setNewFilePath('') + setNewFileContent('') + setNewFolderPath('') + setFilesystemSearch('') + }) + }, [activeWorkspaceId]) + return (
@@ -1388,4 +1438,4 @@ export default function WorkspacesPage() {
) -} \ No newline at end of file +} diff --git a/app/login/page.tsx b/app/login/page.tsx index c1c019ba..31c48ed7 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -3,10 +3,21 @@ import Link from 'next/link' import type { Route } from 'next' import { useRouter, useSearchParams } from 'next/navigation' -import { useEffect, useMemo, useState, type SyntheticEvent } from 'react' +import { + Suspense, + useEffect, + useMemo, + useState, + type SyntheticEvent, +} from 'react' import { Eye, EyeOff, Loader2, LogIn, ShieldCheck, Sparkles } from 'lucide-react' -import { authClient } from '@/lib/auth-client' +import { + authClient, + hasGoogleOneTapClient, + signInWithUsername, + startGoogleOneTap, +} from '@/lib/auth-client' import { useAuthQuery } from '@/lib/hooks/use-auth-query' import { Button } from '@/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/ui/card' @@ -25,10 +36,22 @@ function getSafeNextPath(next: string | null): Route { const REMEMBERED_IDENTIFIER_KEY = 'agentstack.auth.remembered-identifier' type LoginSubmitEvent = SyntheticEvent -export default function LoginPage() { +function LoginPageFallback() { + return ( +
+
+ + Loading sign-in... +
+
+ ) +} + +function LoginPageContent() { const router = useRouter() const searchParams = useSearchParams() const authQuery = useAuthQuery() + const isHydrated = true const [isLoading, setIsLoading] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [identifier, setIdentifier] = useState('') @@ -45,6 +68,10 @@ export default function LoginPage() { const canSubmit = normalizedIdentifier.length > 0 && password.length > 0 && !isLoading useEffect(() => { + if (!isHydrated) { + return + } + if (authQuery.isPending) { return } @@ -52,28 +79,21 @@ export default function LoginPage() { if (authQuery.data) { router.replace(nextPath) } - }, [authQuery.data, authQuery.isPending, nextPath, router]) + }, [authQuery.data, authQuery.isPending, isHydrated, nextPath, router]) useEffect(() => { - if (authQuery.isPending || authQuery.data) { + if (!isHydrated) { return } - void authClient.oneTap({ + if (authQuery.isPending || authQuery.data || !hasGoogleOneTapClient) { + return + } + + void startGoogleOneTap({ callbackURL: nextPath, }) - }, [authQuery.data, authQuery.isPending, nextPath]) - - useEffect(() => { - const savedIdentifier = window.localStorage.getItem(REMEMBERED_IDENTIFIER_KEY) - - if (savedIdentifier) { - queueMicrotask(() => { - setIdentifier(savedIdentifier) - setRememberIdentifier(true) - }) - } - }, []) + }, [authQuery.data, authQuery.isPending, isHydrated, nextPath]) /** Starts the Google OAuth flow through Better Auth. */ const handleGoogleSignIn = async () => { @@ -115,7 +135,7 @@ export default function LoginPage() { password, callbackURL: nextPath, }) - : await authClient.signIn.username({ + : await signInWithUsername({ username: normalizedIdentifier, password, callbackURL: nextPath, @@ -131,7 +151,7 @@ export default function LoginPage() { router.replace(nextPath) } - if (authQuery.isPending || authQuery.data) { + if (!isHydrated || authQuery.isPending || authQuery.data) { return (
@@ -366,3 +386,11 @@ export default function LoginPage() {
) } + +export default function LoginPage() { + return ( + }> + + + ) +} diff --git a/app/login/signup/page.tsx b/app/login/signup/page.tsx index a4c96a36..1acb3ac6 100644 --- a/app/login/signup/page.tsx +++ b/app/login/signup/page.tsx @@ -3,10 +3,21 @@ import Link from 'next/link' import type { Route } from 'next' import { useRouter, useSearchParams } from 'next/navigation' -import { useEffect, useMemo, useState, type SyntheticEvent } from 'react' +import { + Suspense, + useEffect, + useMemo, + useState, + type SyntheticEvent, +} from 'react' import { Eye, EyeOff, Loader2, ShieldCheck, Sparkles, UserPlus } from 'lucide-react' -import { authClient } from '@/lib/auth-client' +import { + authClient, + hasGoogleOneTapClient, + signUpWithUsername, + startGoogleOneTap, +} from '@/lib/auth-client' import { useAuthQuery } from '@/lib/hooks/use-auth-query' import { Button } from '@/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/ui/card' @@ -22,10 +33,22 @@ function getSafeNextPath(next: string | null): Route { return '/chat' } -export default function SignupPage() { +function SignupPageFallback() { + return ( +
+
+ + Loading sign-up... +
+
+ ) +} + +function SignupPageContent() { const router = useRouter() const searchParams = useSearchParams() const authQuery = useAuthQuery() + const isHydrated = true const [isLoading, setIsLoading] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [name, setName] = useState('') @@ -54,6 +77,10 @@ export default function SignupPage() { !isLoading useEffect(() => { + if (!isHydrated) { + return + } + if (authQuery.isPending) { return } @@ -61,17 +88,21 @@ export default function SignupPage() { if (authQuery.data) { router.replace(nextPath) } - }, [authQuery.data, authQuery.isPending, nextPath, router]) + }, [authQuery.data, authQuery.isPending, isHydrated, nextPath, router]) useEffect(() => { - if (authQuery.isPending || authQuery.data) { + if (!isHydrated) { return } - void authClient.oneTap({ + if (authQuery.isPending || authQuery.data || !hasGoogleOneTapClient) { + return + } + + void startGoogleOneTap({ callbackURL: nextPath, }) - }, [authQuery.data, authQuery.isPending, nextPath]) + }, [authQuery.data, authQuery.isPending, isHydrated, nextPath]) /** Starts the Google OAuth flow through Better Auth. */ const handleGoogleSignIn = async () => { @@ -119,7 +150,7 @@ export default function SignupPage() { setIsLoading(true) setErrorMessage('') - const response = await authClient.signUp.email({ + const response = await signUpWithUsername({ name: normalizedName, username: normalizedUsername, email: normalizedEmail, @@ -137,7 +168,7 @@ export default function SignupPage() { router.replace(nextPath) } - if (authQuery.isPending || authQuery.data) { + if (!isHydrated || authQuery.isPending || authQuery.data) { return (
@@ -428,3 +459,11 @@ export default function SignupPage() {
) } + +export default function SignupPage() { + return ( + }> + + + ) +} diff --git a/app/networks/providers/network-context.tsx b/app/networks/providers/network-context.tsx index fb42d812..058a0097 100644 --- a/app/networks/providers/network-context.tsx +++ b/app/networks/providers/network-context.tsx @@ -272,6 +272,7 @@ export function NetworkProvider({ } = useChat({ transport: new DefaultChatTransport({ api: `${MASTRA_API_URL}/network/${selectedNetwork}`, + credentials: 'include', prepareSendMessagesRequest({ messages: msgs }) { const last = msgs[msgs.length - 1] const textPart = last?.parts?.find( diff --git a/app/workflows/providers/workflow-context.tsx b/app/workflows/providers/workflow-context.tsx index 6200f71e..36285c2f 100644 --- a/app/workflows/providers/workflow-context.tsx +++ b/app/workflows/providers/workflow-context.tsx @@ -354,6 +354,7 @@ export function WorkflowProvider({ const { messages, sendMessage, stop, status } = useChat({ transport: new DefaultChatTransport({ api: `${MASTRA_API_URL}/workflow/${selectedWorkflow}`, + credentials: 'include', prepareSendMessagesRequest({ messages: msgs }) { const last = msgs[msgs.length - 1] const textPart = last?.parts?.find( diff --git a/lib/auth-client.ts b/lib/auth-client.ts index 3ef5a3ed..b389bfdc 100644 --- a/lib/auth-client.ts +++ b/lib/auth-client.ts @@ -1,5 +1,6 @@ import { createAuthClient } from 'better-auth/react' import { + type GoogleOneTapActionOptions, adminClient, multiSessionClient, oneTapClient, @@ -8,28 +9,60 @@ import { import { apiKeyClient } from '@better-auth/api-key/client' //import { agentAuthClient } from "@better-auth/agent-auth/client"; +const authBaseUrl = + process.env.NEXT_PUBLIC_BETTER_AUTH_URL ?? 'http://localhost:3000' +const publicGoogleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID?.trim() +export const hasGoogleOneTapClient = Boolean(publicGoogleClientId) + export const authClient = createAuthClient({ plugins: [ adminClient(), apiKeyClient(), multiSessionClient(), oneTapClient({ - clientId: process.env.GOOGLE_CLIENT_ID ?? 'your-google-client-id', + clientId: publicGoogleClientId ?? 'missing-google-client-id', autoSelect: true, context: 'signin', uxMode: 'redirect', additionalOptions: { - // Any extra options for the Google initialize method + // Any extra options for the Google initialize method }, - // Configure prompt behavior and exponential backoff: promptOptions: { - baseDelay: 1000, // Base delay in ms (default: 1000) - maxAttempts: 5 // Maximum number of attempts before triggering onPromptNotification (default: 5) - } + baseDelay: 1000, + maxAttempts: 5, + }, }), usernameClient(), // agentAuthClient(), ], - baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL ?? 'http://localhost:3000', + baseURL: authBaseUrl, credentials: 'include', }) + +export async function startGoogleOneTap( + options?: GoogleOneTapActionOptions +) { + if (!hasGoogleOneTapClient) { + return + } + + await authClient.oneTap(options) +} + +export async function signInWithUsername(input: { + username: string + password: string + callbackURL?: string +}) { + return authClient.signIn.username(input) +} + +export async function signUpWithUsername(input: { + name: string + username: string + email: string + password: string + callbackURL?: string +}) { + return authClient.signUp.email(input) +} diff --git a/lib/hooks/use-mastra-query.ts b/lib/hooks/use-mastra-query.ts index f510854e..fcd151c1 100644 --- a/lib/hooks/use-mastra-query.ts +++ b/lib/hooks/use-mastra-query.ts @@ -102,9 +102,9 @@ import type { WorkspaceFsListResponse, WorkspaceFsReadResponse, WorkspaceFsStatResponse, - ListWorkspacesResponse, WorkspaceIndexParams, WorkspaceInfoResponse, + WorkspaceItem, WorkspaceSearchParams, WorkspaceSearchResponse, WorkflowRunResult, @@ -173,6 +173,7 @@ type TracesResponse = CoreListTracesResponse type TraceTrajectoryResponse = Trajectory type ObservabilityLogsResponse = CoreListLogsResponse type VectorIndex = GetVectorIndexResponse & { name: string } +export const DEFAULT_VECTOR_STORE_NAME = 'libsqlvector' as const type McpToolExecuteArgs = Parameters< ReturnType['execute'] >[0] @@ -1838,7 +1839,7 @@ export const useLogTransports: () => UseQueryResult = () => export const useVectorIndexes: ( vectorName?: string -) => UseQueryResult = (vectorName = 'pgVector') => +) => UseQueryResult = (vectorName = DEFAULT_VECTOR_STORE_NAME) => useQuery({ queryKey: mastraQueryKeys.vectors.indexes(vectorName), queryFn: async () => { @@ -1876,9 +1877,12 @@ export const useEmbedders = () => // --- WORKSPACES --- export const useWorkspaces = () => - useQuery({ + useQuery({ queryKey: mastraQueryKeys.workspaces.list(), - queryFn: () => mastraClient.listWorkspaces(), + queryFn: async () => { + const result = await mastraClient.listWorkspaces() + return Array.isArray(result) ? result : result.workspaces ?? [] + }, }) export const useWorkspace = (id: string) => diff --git a/lib/hooks/use-persistent-store.ts b/lib/hooks/use-persistent-store.ts index 53728cbe..409b9451 100644 --- a/lib/hooks/use-persistent-store.ts +++ b/lib/hooks/use-persistent-store.ts @@ -107,13 +107,16 @@ export function usePersistentStore({ } const handleStorage = (event: StorageEvent) => { - if (event.key !== key || event.newValue === null) { + const newValue = event.newValue + + if (event.key !== key || newValue === null) { return } try { const parse = deserialize ?? defaultDeserialize - store.setState(parse(event.newValue)) + // Use functional updater to satisfy Store.setState overload expecting a function + store.setState(() => parse(newValue)) } catch { // Ignore malformed storage payloads. } @@ -135,7 +138,7 @@ export function usePersistentStore({ ) const resetValue = useCallback(() => { - store.setState(initialValue) + store.setState(() => initialValue) }, [initialValue, store]) return { diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 4d2c622c..9e926c48 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -1,3 +1,77 @@ +# Active Context Update (2026-04-15 - chat settings routing and workspace hook cleanup) + +- `app/chat/user/*` and `app/chat/admin/*` now use route-level layouts (`layout.tsx`) built on `app/chat/components/chat-settings-shell.tsx`, which centralizes `ChatProvider`, `ChatPageShell`, and `MainSidebar` for modular settings routes. +- `/chat/user` and `/chat/admin` are now overview landing pages, and focused settings subpages exist for: + - `/chat/user/profile` + - `/chat/user/security` + - `/chat/user/sessions` + - `/chat/user/api-keys` + - `/chat/user/danger-zone` + - `/chat/admin/runtime` + - `/chat/admin/users` +- `UserSettingsPanel` and `AdminSettingsPanel` now accept a section prop so the new routes can render focused slices of the existing Better Auth management surfaces instead of duplicating mutation logic. +- `lib/hooks/use-mastra-query.ts` now normalizes `useWorkspaces()` to return `WorkspaceItem[]` directly, which removed duplicate raw-response normalization from `app/chat/workspaces/page.tsx` and `app/chat/components/chat-sidebar.tsx`. +- The active vector-store default in the chat-facing hook layer remains `libsqlvector`. +- Additional chat routes now also run inside the shared chat shell (`ChatProvider` + `ChatPageShell` + `MainSidebar`) instead of bypassing the sidebar: + - `app/chat/dataset/page.tsx` + - `app/chat/evaluation/page.tsx` + - `app/chat/observability/page.tsx` + - `app/chat/tools/page.tsx` + - `app/chat/logs/page.tsx` + - `app/chat/harness/page.tsx` + - `app/chat/mcp-a2a/page.tsx` + - `app/chat/workflows/page.tsx` + - `app/chat/workflows/[workflowId]/page.tsx` +- The last chat-facing `PgVector` label in `app/chat/config/agents.ts` was replaced with vector-store-neutral wording. +- Shared chat UX was further refined: + - `app/chat/components/main-sidebar.tsx` now uses denser route metadata, tooltip guidance, and `ScrollArea` for long page/thread lists. + - `app/chat/components/chat-settings-shell.tsx` now uses horizontal scrolling plus tooltip-backed section cards for settings navigation. + - `app/chat/components/chat-page-shell.tsx` now uses tighter responsive shell spacing. + - `app/chat/user/page.tsx` and `app/chat/admin/page.tsx` now use tooltip-backed overview cards. +- Targeted IDE diagnostics are clean for the updated settings routes, panels, workspace page, chat sidebar, and Mastra hook file. +- `app/chat/components/main-sidebar.tsx` still shows a stale ESLint diagnostic in the editor even after the flagged line no longer contains any effect or state-setting logic; the current file contents suggest this is a cached lint/server issue rather than a live code problem. + +# Active Context Update (2026-04-15 - research agent model fallback) + +- `src/mastra/agents/researchAgent.ts` no longer pins the route to `google/gemma-4-31b-it:free`. +- The research agent now uses role-aware runtime model selection: + - admin requests use `google.chat('gemini-3.1-pro-preview')` + - standard requests use `google.chat('gemini-3.1-flash-lite-preview')` +- This aligns `researchAgent` with the repo's production-oriented research-agent pattern and avoids the failing free-model default that was breaking `/chat/agents/researchAgent`. + +# Active Context Update (2026-04-15 - supervisor split, GitHub channel, browser hooks) + +- The shared supervisor scorer layer is now split into two tiers: + - `createSupervisorPatternScorer(...)` remains the lower-level coordinator primitive + - `createSupervisorAgentPatternScorer(...)` is the higher-level shared helper for supervisor-style agents +- Additional future-facing shared helpers now exist for channel-oriented and structured-output-oriented supervisors: + - `createSupervisorChannelPatternScorer(...)` + - `createStructuredOutputSupervisorPatternScorer(...)` +- `src/mastra/browsers.ts` is now the central browser policy surface for: + - deterministic `agentBrowser` + - adaptive `stagehandBrowser` + - lifecycle logging hooks + - environment-driven viewport, timeout, and screencast settings +- `browserAgent` now has a much stricter verification prompt focused on evidence, deterministic tool sequencing, and non-destructive browsing. +- `researchAgent` now supports an optional GitHub channel adapter when `GITHUB_WEBHOOK_SECRET` plus PAT or GitHub App credentials are configured; Discord remains enabled as before. +- `researchAgent` channel handling now uses valid Mastra handler overrides (`onDirectMessage`, `onMention`, `onSubscribedMessage`) instead of a non-existent per-platform `github` handler key. +- The subscribed-thread path intentionally ignores acknowledgement-only follow-ups to reduce wasted research cycles in long channel threads. +- The hook layer is now centralized through `handleResearchChannelEvent(...)`, which adds consistent logging and GitHub-thread awareness to the research channel surface. +- Better Auth Google sign-in now routes through the correct Better Auth callback path (`/api/auth/callback/google`), and the auth client no longer depends on client-side access to private env vars. +- The login and signup routes now wrap their `useSearchParams()` usage in `Suspense`, which cleared the Next.js 16 blocking-route runtime error and made browser-based auth testing viable again. +- CLI lint/test execution is still blocked in this session because `pwsh` is unavailable, so browser/research validation relied on targeted IDE diagnostics. + +# Active Context Update (2026-04-15 - supervisor/browser rollout) + +- `src/mastra/scorers/supervisor-scorers.ts` now exposes `createSupervisorPatternScorer(...)` as the shared primitive for supervisor/coordinator completion scoring. +- The current supervisor-style agent set and coordinator-network set now use local scorer wrappers on top of that shared primitive instead of duplicating the full scorer preprocessing pipeline in each file. +- `browserAgent` is now part of the main supervisor surface: + - exported from `src/mastra/agents/index.ts` + - registered in `src/mastra/index.ts` + - mounted in `src/mastra/agents/supervisor-agent.ts` +- `supervisor-agent` delegation guidance now treats browser work as an opt-in verification path for live claims, page behavior, and browser-state evidence rather than a default research step. +- Validation could not be executed from the CLI runtime in this session because `pwsh` is unavailable, so follow-up validation should be run in a shell-enabled environment. + # Active Context Update (2026-04-14 - strict typing and inferred tool cleanup) - `BinanceAvgPrice` is now used in the Binance spot tool via the `BinanceSpotAvgPriceData` type. @@ -1276,3 +1350,13 @@ Added Vercel-style navigation and footer to `app/page.tsx`: 5. Add loading skeletons throughout 6. Type MastraClient responses properly 7. Add unit tests for hooks +# Active Context Update (2026-04-15 - chat route hardening) + +- `app/chat/components/chat.utils.ts` now exposes provider-agnostic thought-summary extraction so chat surfaces no longer assume `providerMetadata.google`. +- `app/chat/components/chat-messages.tsx` and `app/chat/providers/chat-context.tsx` now tolerate arbitrary provider metadata shapes and suppress validation while streamed assistant messages are still incomplete. +- `app/chat/providers/chat-context.tsx` no longer raises a false `Messages array must not be empty` error on empty initial chat state. +- `app/chat/providers/chat-context.tsx`, `app/networks/providers/network-context.tsx`, `app/workflows/providers/workflow-context.tsx`, and `app/chat/components/nested-agent-chat.tsx` now set `credentials: 'include'` on `DefaultChatTransport` so the frontend can authenticate to the cross-origin Mastra server on `http://localhost:4111`. +- `/chat/agents/researchAgent` was reproduced in a real authenticated browser session by creating a Better Auth test user directly against `/api/auth/sign-up/email`. +- The protected research-agent route no longer reproduces the original provider-metadata crash or the initial empty-message error; direct browser fetches confirmed the Mastra backend now returns a valid SSE stream start and tool-input chunks when credentials are included. +- `app/login/page.tsx`, `app/login/signup/page.tsx`, and `app/chat/components/main-sidebar.tsx` now use hydration guards so client-only auth/session/thread UI does not mismatch server HTML during hydration. +- Final browser re-verification after the last chat-message validation patch is blocked in this session because the local Next.js dev server stopped and this environment cannot restart it without `pwsh` or another shell tool. diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 64f5deea..b6ef8c58 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -1,3 +1,104 @@ +# Progress Update (2026-04-15 - modular chat settings and workspace hook normalization) + +- Added a shared `app/chat/components/chat-settings-shell.tsx` wrapper so route-level settings pages consistently use `ChatProvider`, `ChatPageShell`, and `MainSidebar`. +- Split settings navigation into overview + focused routes: + - user: `profile`, `security`, `sessions`, `api-keys`, `danger-zone` + - admin: `runtime`, `users` +- Updated `app/chat/user/_components/user-settings-panel.tsx` and `app/chat/admin/_components/admin-management-panel.tsx` to accept section props instead of duplicating Better Auth logic across new pages. +- Normalized `useWorkspaces()` in `lib/hooks/use-mastra-query.ts` to return `WorkspaceItem[]`, then removed duplicate raw workspace-response decoding from: + - `app/chat/workspaces/page.tsx` + - `app/chat/components/chat-sidebar.tsx` +- Brought the remaining major chat dashboard surfaces under the shared shell/sidebar composition: + - `dataset` + - `evaluation` + - `observability` + - `tools` + - `logs` + - `harness` + - `mcp-a2a` + - `workflows` + - `workflows/[workflowId]` +- Removed the last chat-facing `PgVector` wording from `app/chat/config/agents.ts`. +- Improved shared UI/UX/cx on the core shell surfaces: + - `main-sidebar.tsx`: added route descriptions, tooltip guidance, and scroll containers for long lists + - `chat-settings-shell.tsx`: added horizontal scroll support and tooltips for section navigation + - `chat-page-shell.tsx`: tightened responsive shell spacing + - `user/page.tsx` and `admin/page.tsx`: added tooltip-backed overview cards +- Validation: + - ✅ targeted IDE diagnostics are clean for: + - `app/chat/components/chat-settings-shell.tsx` + - `app/chat/user/layout.tsx` + - `app/chat/admin/layout.tsx` + - `app/chat/user/page.tsx` + - `app/chat/admin/page.tsx` + - `app/chat/user/_components/user-settings-panel.tsx` + - `app/chat/admin/_components/admin-management-panel.tsx` + - `app/chat/workspaces/page.tsx` + - `app/chat/components/chat-sidebar.tsx` + - `lib/hooks/use-mastra-query.ts` + - `app/chat/dataset/page.tsx` + - `app/chat/evaluation/page.tsx` + - `app/chat/observability/page.tsx` + - `app/chat/tools/page.tsx` + - `app/chat/logs/page.tsx` + - `app/chat/harness/page.tsx` + - `app/chat/mcp-a2a/page.tsx` + - `app/chat/workflows/page.tsx` + - `app/chat/workflows/[workflowId]/page.tsx` + - `app/chat/config/agents.ts` + - `app/chat/components/chat-page-shell.tsx` + - `app/chat/components/chat-settings-shell.tsx` + - `app/chat/user/page.tsx` + - `app/chat/admin/page.tsx` + - ⚠️ `app/chat/components/main-sidebar.tsx` still shows a stale ESLint diagnostic in the editor even though the flagged line no longer contains effect-driven state logic. + +# Progress Update (2026-04-15 - research agent model default repair) + +- Replaced the hard-coded `google/gemma-4-31b-it:free` model in `src/mastra/agents/researchAgent.ts`. +- `researchAgent` now uses a role-aware runtime model selector: + - admin → `google.chat('gemini-3.1-pro-preview')` + - default → `google.chat('gemini-3.1-flash-lite-preview')` +- Validation: + - ✅ targeted VS Code error check on `src/mastra/agents/researchAgent.ts` + +# Progress Update (2026-04-15 - browser and channel hardening) + +- Added a second shared scorer layer for supervisor-style agents in `src/mastra/scorers/supervisor-scorers.ts`: + - `createSupervisorAgentPatternScorer(...)` + - `createSupervisorChannelPatternScorer(...)` + - `createStructuredOutputSupervisorPatternScorer(...)` +- Migrated the active supervisor-style agents to the supervisor-specific shared scorer helper instead of the lower-level base export. +- Hardened `src/mastra/browsers.ts` with: + - environment-driven viewport/timeout/screencast settings + - lifecycle hooks for both deterministic and Stagehand providers + - stronger Stagehand operating instructions +- Upgraded `src/mastra/agents/browserAgent.ts` with a production-grade verification contract and deterministic browser operating workflow. +- Added optional GitHub channel support to `src/mastra/agents/researchAgent.ts`, gated behind the required webhook/auth environment variables so startup remains safe when GitHub is not configured. +- Replaced the invalid per-platform `channels.handlers.github` attempt in `researchAgent` with valid Mastra channel handlers: + - `onDirectMessage` + - `onMention` + - `onSubscribedMessage` +- The subscribed-thread handler now skips acknowledgement-only follow-ups such as `thanks`, `resolved`, or `lgtm` instead of spending another research turn on low-signal churn. +- Strengthened the same handler layer with a shared `handleResearchChannelEvent(...)` helper, GitHub thread detection, and consistent metadata logging across DM, mention, and subscribed-thread events. +- Hardened Better Auth Google wiring by normalizing legacy callback env values onto `/api/auth/callback/google`, tightening client/plugin usage in `lib/auth-client.ts`, and fixing the `/login` + `/login/signup` Suspense boundary issue so the auth pages render cleanly under Next.js 16. +- Enriched `src/mastra/browsers.ts` browser hooks so launch/close events now include connection mode, runtime config, viewport, screencast, environment, and session duration metadata, plus Browserbase credential guardrails. +- Validation: + - ✅ targeted IDE diagnostics are clean for `src/mastra/scorers/supervisor-scorers.ts` + - ✅ targeted IDE diagnostics are clean for `src/mastra/browsers.ts` + - ✅ targeted IDE diagnostics are clean for `src/mastra/agents/browserAgent.ts` + - ⚠️ CLI lint/test validation remains blocked in this session because the runtime shell requires `pwsh`, which is not installed. + +# Progress Update (2026-04-15 - supervisor and coordinator scorer standardization) + +- Added a reusable `createSupervisorPatternScorer(...)` primitive in `src/mastra/scorers/supervisor-scorers.ts` and migrated the active supervisor-style agents plus coordinator networks to local wrappers built on that shared scorer pipeline. +- Wired `browserAgent` into the main `supervisor-agent` surface end to end: + - exported from `src/mastra/agents/index.ts` + - registered in `src/mastra/index.ts` + - mounted as a child agent in `src/mastra/agents/supervisor-agent.ts` +- Tightened `supervisor-agent` delegation guidance so browser work is used only for high-value live verification rather than as a default hop. +- Validation: + - ⚠️ CLI validation commands were blocked in this session because the runtime shell requires `pwsh`, which is not installed in the environment. + # Progress Update (2026-04-14 - strict typing and inferred tool cleanup) - Used `BinanceAvgPrice` in the Binance tool instead of leaving it as an unused helper import. @@ -905,3 +1006,24 @@ - Requires correct environment configuration (database connection, model API keys, financial API keys, `PHOENIX_ENDPOINT`/`PHOENIX_API_KEY`/`PHOENIX_PROJECT_NAME`, etc.) to exercise all capabilities. - A2A coordination complexity grows with new agents; needs careful documentation and evaluation to avoid misalignment. - JWT auth is currently stubbed; until verification is implemented and policies are enforced, flows that depend on strict auth should be treated as experimental. +# Progress Update (2026-04-15 - chat route auth and metadata hardening) + +- Hardened chat-provider metadata handling so the chat UI no longer assumes a Google-specific provider payload: + - `app/chat/components/chat.utils.ts` + - `app/chat/components/chat-messages.tsx` + - `app/chat/providers/chat-context.tsx` +- Fixed the false initial empty-chat validation error in `app/chat/providers/chat-context.tsx`. +- Added `credentials: 'include'` to all `DefaultChatTransport` instances that talk to the Mastra server so authenticated frontend requests can reach `http://localhost:4111`: + - chat + - networks + - workflows + - nested agent demo +- Fixed hydration mismatches on: + - `app/login/page.tsx` + - `app/login/signup/page.tsx` + - `app/chat/components/main-sidebar.tsx` +- Real-browser repro findings: + - ✅ `/chat/agents/researchAgent` no longer crashed on provider metadata access. + - ✅ the route no longer showed the false `Messages array must not be empty` error on first load. + - ✅ authenticated direct fetches to `http://localhost:4111/chat/researchAgent` returned a valid SSE stream start and tool-input chunks once cookies were included. + - ⚠️ the local Next.js dev server stopped before the final post-patch browser pass could be repeated, and this session cannot restart it because the runtime lacks `pwsh` or another usable shell tool. diff --git a/package-lock.json b/package-lock.json index caf07ff0..7756d706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -215,6 +215,7 @@ "unpdf": "^1.6.0", "use-stick-to-bottom": "^1.1.3", "v0-sdk": "^0.16.4", + "zlib-sync": "^0.1.10", "zod": "^4.3.6" }, "devDependencies": { @@ -30763,36 +30764,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/gcp-metadata": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", - "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gcp-metadata/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/geckodriver": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-6.1.0.tgz", @@ -44456,22 +44427,6 @@ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "license": "MIT" }, - "node_modules/tsup/node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "extraneous": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/tsyringe": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", @@ -45539,22 +45494,6 @@ } } }, - "node_modules/vitest/node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "extraneous": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/vscode-jsonrpc": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", @@ -46624,6 +46563,16 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/zlib-sync": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/zlib-sync/-/zlib-sync-0.1.10.tgz", + "integrity": "sha512-t7/pYg5tLBznL1RuhmbAt8rNp5tbhr+TSrJFnMkRtrGIaPJZ6Dc0uR4u3OoQI2d6cGlVI62E3Gy6gwkxyIqr/w==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nan": "^2.18.0" + } + }, "node_modules/zlibjs": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", diff --git a/package.json b/package.json index c9edda31..aa5c7161 100644 --- a/package.json +++ b/package.json @@ -257,6 +257,7 @@ "unpdf": "^1.6.0", "use-stick-to-bottom": "^1.1.3", "v0-sdk": "^0.16.4", + "zlib-sync": "^0.1.10", "zod": "^4.3.6" }, "devDependencies": { diff --git a/page-2026-04-15T07-04-23-082Z.png b/page-2026-04-15T07-04-23-082Z.png new file mode 100644 index 0000000000000000000000000000000000000000..95338ac83aa4d5c31923a2e5d218e8ddb927577d GIT binary patch literal 334774 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
O_QxX$o9P22# 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�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Ⓢp<$t! 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 IM6N<$f;>`@fdBvi literal 0 HcmV?d00001 diff --git a/src/mastra/agents/AGENTS.md b/src/mastra/agents/AGENTS.md index 6811b9c7..08f45702 100644 --- a/src/mastra/agents/AGENTS.md +++ b/src/mastra/agents/AGENTS.md @@ -1,4 +1,4 @@ - + # Agents (`/src/mastra/agents`) @@ -94,9 +94,14 @@ This directory contains 22+ agent definitions that map use-case intents to seque - **Tool Typing**: Avoid adding `ToolsInput` annotations to agent definitions unless they are truly required. Prefer inferred tool maps (`const tools = { ... }`) with `typeof tools`, or `Record` for tool-less agents. - **Supervisor Delegation**: For agents that define `agents: { ... }`, keep delegation hooks and completion scorers local to that agent so domain-specific routing prompts, failure feedback, and completion checks stay easy to audit. - **Completion Scoring**: Prefer more than one local scorer for supervisor-style agents when a task can be "complete" in different valid ways (for example, a comprehensive answer scorer plus an execution-readiness scorer). +- **Shared Scorer Primitives**: Reuse `createSupervisorPatternScorer(...)` from `src/mastra/scorers/supervisor-scorers.ts` for coordinator-style agents, but keep the final scorer instances and domain regexes local to each agent file. +- **Supervisor-Specific Shared Helpers**: For supervisor-style agent files, prefer `createSupervisorAgentPatternScorer(...)` so agent supervisors can share stronger user-facing defaults without forcing networks or other coordinators onto the exact same shared export. - **Child Agent Boundaries**: Do not mutate unrelated child-agent public generics just to satisfy one parent registration. If a supervisor relationship becomes awkward, prefer changing the parent composition instead of forcing a type-shape change into the child agent. - **Request Context**: Prefer dynamic `instructions: ({ requestContext }) => ...` plus `requestContextSchema` for user-facing supervisor agents so tier, language, user identity, and workspace hints can shape the final output contract safely. - **Supported Hook Surface**: For the current Mastra version in this repo, the main supervisor-style execution hooks are `onDelegationStart`, `onDelegationComplete`, `messageFilter`, `onIterationComplete`, and `isTaskComplete`. Prefer using these directly rather than inventing extra abstraction layers. +- **Browser Delegation**: When a supervisor has access to `browserAgent`, use it only for high-value live verification, page inspection, or browser-state confirmation. Do not browse by default when static research is sufficient. +- **Channel Wiring**: Gate optional channel adapters such as GitHub behind explicit environment checks so local/dev boots do not fail when webhook secrets or auth credentials are absent. +- **Browser Config**: Keep browser-provider lifecycle hooks and provider-specific defaults centralized in `src/mastra/browsers.ts` so agents share one production-grade browser policy surface. ## Execution & Testing @@ -126,6 +131,9 @@ npm test src/mastra/__tests__/agents/your-agent.test.ts | Version | Date (UTC) | Changes | | ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| 2.2.10 | 2026-04-15 | Replaced `researchAgent`'s hard-coded free-model default with role-aware Gemini selection so the chat route uses the same production model pattern as the other research agents. | +| 2.2.9 | 2026-04-15 | Added the supervisor-specific shared scorer helper convention (`createSupervisorAgentPatternScorer(...)`), documented environment-gated channel adapters, and standardized centralized browser-provider hooks/config guidance. | +| 2.2.8 | 2026-04-15 | Standardized supervisor-style agents on shared `createSupervisorPatternScorer(...)` primitives with local wrappers and made `supervisor-agent` browser-aware by wiring in `browserAgent` with verification-first delegation guidance. | | 2.2.7 | 2026-03-28 | Made the active supervisor-style agents request-context-aware, added request-context schema validation, and expanded them to use the full supported execution-hook surface from the installed Mastra types. | | 2.2.6 | 2026-03-28 | Added dual completion scorers and explicit final-answer contracts to the active supervisor-style agents; restored `calendarAgent` shape and removed nested PM registration instead of changing the child agent public generic. | | 2.2.5 | 2026-03-27 | Added per-agent delegation hooks and local completion scorers for the active supervisor-style agents (`supervisor-agent`, customer support, project management, SEO, social media, translation) instead of introducing a shared helper layer. | diff --git a/src/mastra/agents/browserAgent.ts b/src/mastra/agents/browserAgent.ts index 7cafd1ec..9e1b6c41 100644 --- a/src/mastra/agents/browserAgent.ts +++ b/src/mastra/agents/browserAgent.ts @@ -8,15 +8,40 @@ export const browserAgent = new Agent({ id: 'browser-agent', name: 'Browser Agent', description: - 'Deterministic browser agent connected to a local Chrome instance through CDP.', - instructions: `You can browse the web using deterministic browser tools. + 'Deterministic browser verification agent connected to a Chrome session through CDP for reproducible live-page inspection.', + instructions: `You are a deterministic browser verification specialist. -Use browser_snapshot first to inspect the page structure, then interact with -elements by their refs (for example @e5). Prefer precise, repeatable actions. -When the task depends on the user’s local browser state, keep the interaction -focused on the connected Chrome session rather than opening a new browser. -`, +Mission: +- Verify live web claims, page behavior, and browser state with the fewest possible actions. +- Produce evidence the caller can trust: URLs, page titles, visible text, control states, and observed outcomes. +- Prefer reproducible browser tools over guesswork or narrative filler. + +Operating workflow: +1. Navigate with browser_goto only when you know the target URL or the caller explicitly asks you to open a page. +2. Start each page interaction with browser_snapshot so you can reason from stable refs like @e5. +3. Use browser_click, browser_type, browser_select, browser_press, browser_scroll, browser_hover, and browser_drag only after you have the correct ref from a fresh snapshot. +4. Use browser_wait after actions that trigger navigation, loading, or deferred UI updates. +5. Use browser_tabs deliberately when comparison or multi-page verification is needed. +6. Use browser_evaluate only as a last-resort escape hatch when deterministic tools cannot expose the required signal. +7. Use browser_dialog only when the page presents an alert, confirm, or prompt that must be handled intentionally. + +Evidence contract: +- Return what you verified, not what you assume. +- Distinguish clearly between verified facts, observed blockers, and unresolved uncertainty. +- Include the final URL and the most important visible evidence for each conclusion. +- If the task depends on the user's local browser state, stay focused on the connected session instead of opening unrelated pages. + +Safety rules: +- Do not perform destructive, account-changing, or purchase-like actions unless the user explicitly asks. +- Do not browse broadly when one or two targeted checks can answer the question. +- If the page is inaccessible or the evidence is weak, say so explicitly.`, model: 'google/gemini-3.1-flash-lite-preview', browser: agentBrowser, memory: LibsqlMemory, -}) \ No newline at end of file + defaultOptions: { + maxSteps: 12, + toolCallConcurrency: 1, + toolChoice: 'auto', + includeRawChunks: true, + }, +}) diff --git a/src/mastra/agents/businessLegalAgents.ts b/src/mastra/agents/businessLegalAgents.ts index 8b58814d..e6a23ef4 100644 --- a/src/mastra/agents/businessLegalAgents.ts +++ b/src/mastra/agents/businessLegalAgents.ts @@ -2,7 +2,7 @@ import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import { Agent } from '@mastra/core/agent' import { - TokenLimiterProcessor, + //TokenLimiterProcessor, UnicodeNormalizer, } from '@mastra/core/processors' import type { RequestContext } from '@mastra/core/request-context' @@ -246,7 +246,7 @@ You are a Senior Contract Analyst. Analyze legal documents for risks, obligation collapseWhitespace: true, }), ], - outputProcessors: [new TokenLimiterProcessor(1048576)], + //outputProcessors: [new TokenLimiterProcessor(1048576)], defaultOptions: { autoResumeSuspendedTools: true, }, @@ -419,7 +419,7 @@ You are a Chief Strategy Officer with legal expertise. Align business strategy w collapseWhitespace: true, }), ], - outputProcessors: [new TokenLimiterProcessor(1048576)], + //outputProcessors: [new TokenLimiterProcessor(1048576)], //defaultOptions: { // autoResumeSuspendedTools: true, // }, diff --git a/src/mastra/agents/calendarAgent.ts b/src/mastra/agents/calendarAgent.ts index dd6b1ac3..70c5ce0e 100644 --- a/src/mastra/agents/calendarAgent.ts +++ b/src/mastra/agents/calendarAgent.ts @@ -56,7 +56,7 @@ Current user: ${userId ?? 'anonymous'}`, }, } }, - model: "google/gemini-3.1-flash-lite-preview", + model: "google/gemma-4-31b-it", memory: LibsqlMemory, tools: { listEvents, diff --git a/src/mastra/agents/contentStrategistAgent.ts b/src/mastra/agents/contentStrategistAgent.ts index 40855ba8..b3e5d416 100644 --- a/src/mastra/agents/contentStrategistAgent.ts +++ b/src/mastra/agents/contentStrategistAgent.ts @@ -130,12 +130,12 @@ User: ${userId} | Role: ${role} | Style: ${strategy} thinkingBudget: -1, }, mediaResolution: 'MEDIA_RESOLUTION_MEDIUM', - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: 'google/gemini-3.1-flash-preview', + model: 'google/gemma-4-31b-it', memory: LibsqlMemory, tools: contentStrategistTools, options: { diff --git a/src/mastra/agents/customerSupportAgent.ts b/src/mastra/agents/customerSupportAgent.ts index 7f33f65b..b6d5fd22 100644 --- a/src/mastra/agents/customerSupportAgent.ts +++ b/src/mastra/agents/customerSupportAgent.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { log } from '../config/logger' import { InternalSpans } from '@mastra/core/observability' @@ -22,6 +11,7 @@ import { getRoleFromContext, } from './request-context' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorAgentPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Customer Support Agent...') @@ -29,220 +19,88 @@ log.info('Initializing Customer Support Agent...') * Evaluates whether a customer-support response contains empathy, a practical resolution, * and clear follow-up guidance. */ -const customerSupportTaskCompleteScorer = createScorer({ +const customerSupportTaskCompleteScorer = createSupervisorAgentPatternScorer({ id: 'customer-support-task-complete', name: 'Customer Support Task Completeness', description: 'Checks whether a support reply includes empathy, concrete next steps, and resolution guidance.', - type: 'agent', + label: 'customer support completeness', + emptyReason: 'No usable customer support response was produced.', + weakReason: 'The response is present but lacks the main support signals.', + strongReasonPrefix: 'This support response is strong because', + responseLengthThresholds: [ + { min: 140, weight: 0.15 }, + { min: 300, weight: 0.1 }, + ], + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it opens with empathy', + regex: /understand|sorry|happy to help|i can help|thanks for sharing/i, + weight: 0.2, + }, + { + label: 'it includes clear actions', + regex: /step 1|1\.|next step|please try|follow these steps|here's what to do/i, + weight: 0.2, + }, + { + label: 'it includes a resolution or escalation path', + regex: /if this does not work|if the issue persists|contact|follow up|escalate/i, + weight: 0.15, + }, + { + label: 'it adds verification guidance', + regex: /verify|confirm|check|test|expected result|what should happen/i, + weight: 0.05, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasEmpathy: - /understand|sorry|happy to help|i can help|thanks for sharing/i.test( - responseText - ), - hasActionableSteps: - /step 1|1\.|next step|please try|follow these steps|here's what to do/i.test( - responseText - ), - hasResolutionPath: - /if this does not work|if the issue persists|contact|follow up|escalate/i.test( - responseText - ), - hasVerification: - /verify|confirm|check|test|expected result|what should happen/i.test( - responseText - ), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.hasUserMessage) score += 0.05 - if (analysis.systemMessageCount > 0) score += 0.05 - if (analysis.responseLength >= 140) score += 0.15 - if (analysis.responseLength >= 300) score += 0.1 - if (analysis.hasEmpathy) score += 0.2 - if (analysis.hasActionableSteps) score += 0.2 - if (analysis.hasResolutionPath) score += 0.15 - if (analysis.hasVerification) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable customer support response was produced.' - } - - if (analysis.hasEmpathy) parts.push('it opens with empathy') - if (analysis.hasActionableSteps) parts.push('it includes clear actions') - if (analysis.hasResolutionPath) parts.push('it includes a resolution or escalation path') - if (analysis.hasVerification) parts.push('it adds verification guidance') - if (analysis.hasReasoning) parts.push('it includes reasoning support') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This support response is strong because ${parts.join(', ')}.` : 'The response is present but lacks the main support signals.'}` - }) /** * Evaluates whether the support response is concise, operationally clear, and * ready to send to the customer. */ -const customerSupportResolutionScorer = createScorer({ +const customerSupportResolutionScorer = createSupervisorAgentPatternScorer({ id: 'customer-support-resolution-readiness', name: 'Customer Support Resolution Readiness', description: 'Checks whether a support reply provides a usable resolution flow, clear next steps, and escalation guidance.', - type: 'agent', + label: 'customer support resolution', + emptyReason: 'No usable customer support resolution was produced.', + weakReason: 'The response is present but not yet resolution-ready.', + strongReasonPrefix: 'This resolution is strong because', + responseLengthThresholds: [ + { min: 140, weight: 0.2 }, + { min: 260, weight: 0.1 }, + ], + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it gives a step-by-step flow', + regex: /1\.|2\.|step 1|step-by-step|follow these steps/i, + weight: 0.25, + }, + { + label: 'it tells the user how to verify the fix', + regex: /expected result|what should happen|confirm|verify/i, + weight: 0.2, + }, + { + label: 'it includes escalation guidance', + regex: /escalate|contact support|follow up|reply back|reach out/i, + weight: 0.15, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasSequence: - /1\.|2\.|step 1|step-by-step|follow these steps/i.test(responseText), - hasVerification: - /expected result|what should happen|confirm|verify/i.test(responseText), - hasEscalation: - /escalate|contact support|follow up|reply back|reach out/i.test( - responseText - ), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 140) score += 0.2 - if (analysis.responseLength >= 260) score += 0.1 - if (analysis.hasSequence) score += 0.25 - if (analysis.hasVerification) score += 0.2 - if (analysis.hasEscalation) score += 0.15 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable customer support resolution was produced.' - } - - if (analysis.hasSequence) parts.push('it gives a step-by-step flow') - if (analysis.hasVerification) parts.push('it tells the user how to verify the fix') - if (analysis.hasEscalation) parts.push('it includes escalation guidance') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This resolution is strong because ${parts.join(', ')}.` : 'The response is present but not yet resolution-ready.'}` - }) /** * Customer Support Agent. diff --git a/src/mastra/agents/dane.ts b/src/mastra/agents/dane.ts index e34d11d2..bddd5e39 100644 --- a/src/mastra/agents/dane.ts +++ b/src/mastra/agents/dane.ts @@ -38,12 +38,12 @@ export const daneCommitMessage = new Agent({ includeThoughts: true, thinkingBudget: -1, }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: "google/gemini-3.1-flash-lite-preview", + model: "google/gemma-4-31b-it", memory: LibsqlMemory, options: { tracingPolicy: { @@ -83,12 +83,12 @@ export const daneIssueLabeler = new Agent({ includeThoughts: true, thinkingBudget: -1, }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: "google/gemini-3.1-flash-lite-preview", + model: "google/gemma-4-31b-it", memory: LibsqlMemory, outputProcessors: [ // new TokenLimiterProcessor(128576), @@ -136,12 +136,12 @@ export const daneLinkChecker = new Agent({ includeThoughts: true, thinkingBudget: -1, }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: "google/gemini-3.1-flash-lite-preview", + model: "google/gemma-4-31b-it", memory: LibsqlMemory, options: { tracingPolicy: { @@ -188,12 +188,12 @@ export const daneChangeLog = new Agent({ includeThoughts: true, thinkingBudget: -1, }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: "google/gemini-3.1-flash-lite-preview", + model: "google/gemma-4-31b-it", memory: LibsqlMemory, defaultOptions: { autoResumeSuspendedTools: true, @@ -258,12 +258,12 @@ export const dane = new Agent({ includeThoughts: true, thinkingLevel: 'low', }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: 'google/gemini-3.1-flash-preview', + model: 'google/gemma-4-31b-it', memory: LibsqlMemory, tools: { browserTool, diff --git a/src/mastra/agents/dataExportAgent.ts b/src/mastra/agents/dataExportAgent.ts index 60493c1e..23446318 100644 --- a/src/mastra/agents/dataExportAgent.ts +++ b/src/mastra/agents/dataExportAgent.ts @@ -3,7 +3,7 @@ import { Agent } from '@mastra/core/agent' import { log } from '../config/logger' import { InternalSpans } from '@mastra/core/observability' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { USER_ID_CONTEXT_KEY, type AgentRequestContext } from './request-context' import { jsonToCsvTool } from '../tools/json-to-csv.tool' import { LibsqlMemory } from '../config/libsql' @@ -65,7 +65,7 @@ User: ${userId} | Out: ${outputDirectory} | Overwrite: ${overwriteExisting} internal: InternalSpans.ALL, }, }, - outputProcessors: [new TokenLimiterProcessor(1048576)], + //outputProcessors: [new TokenLimiterProcessor(1048576)], // defaultOptions: { // autoResumeSuspendedTools: true, // }, diff --git a/src/mastra/agents/documentProcessingAgent.ts b/src/mastra/agents/documentProcessingAgent.ts index 16826d3c..69b9058c 100644 --- a/src/mastra/agents/documentProcessingAgent.ts +++ b/src/mastra/agents/documentProcessingAgent.ts @@ -4,7 +4,7 @@ import { log } from '../config/logger' import { libsqlChunker, mastraChunker } from '../tools/document-chunking.tool' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { InternalSpans } from '@mastra/core/observability' import type { AgentRequestContext } from './request-context' import { USER_ID_CONTEXT_KEY } from './request-context' @@ -81,7 +81,7 @@ User: ${userId} | In: ${inputDirectory} | Out: ${outputDirectory} internal: InternalSpans.ALL, }, }, - outputProcessors: [new TokenLimiterProcessor(1048576)], + //outputProcessors: [new TokenLimiterProcessor(1048576)], }) log.info('Document Processing Agent initialized') diff --git a/src/mastra/agents/editorAgent.ts b/src/mastra/agents/editorAgent.ts index b2388e7d..9c0661e5 100644 --- a/src/mastra/agents/editorAgent.ts +++ b/src/mastra/agents/editorAgent.ts @@ -59,7 +59,7 @@ Refine clarity, coherence, grammar, and style across Technical, Business, Creati includeThoughts: true, thinkingLevel: 'medium', }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], } satisfies GoogleLanguageModelOptions, }, } @@ -74,7 +74,7 @@ Refine clarity, coherence, grammar, and style across Technical, Business, Creati return google.chat('gemini-3.1-pro-preview') } // cheaper/faster model for user tier - return google.chat('gemini-3.1-flash-lite-preview') + return "google/gemma-4-31b-it" }, memory: LibsqlMemory, tools: {}, diff --git a/src/mastra/agents/excalidraw_validator.ts b/src/mastra/agents/excalidraw_validator.ts index 35807819..38311f0f 100644 --- a/src/mastra/agents/excalidraw_validator.ts +++ b/src/mastra/agents/excalidraw_validator.ts @@ -1,7 +1,7 @@ import { Agent } from '@mastra/core/agent' import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import type { RequestContext } from '@mastra/core/request-context' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { InternalSpans } from '@mastra/core/observability' import type { AgentRequestContext } from './request-context' import { LibsqlMemory } from '../config/libsql' @@ -100,13 +100,13 @@ You can update the JSON to be valid and ensure it matches the expected excalidra includeThoughts: true, thinkingBudget: -1, }, - responseModalities: ['TEXT'], + responseModalities: ['TEXT', 'IMAGE'], mediaResolution: 'MEDIA_RESOLUTION_MEDIUM', } satisfies GoogleGenerativeAIProviderOptions, }, } }, - model: "google/gemini-3.1-flash-lite-preview", + model: "google/gemma-4-31b-it", memory: LibsqlMemory, tools: {}, scorers: {}, @@ -117,5 +117,5 @@ You can update the JSON to be valid and ensure it matches the expected excalidra }, }, maxRetries: 5, - outputProcessors: [new TokenLimiterProcessor(1048576)], + //outputProcessors: [new TokenLimiterProcessor(1048576)], }) diff --git a/src/mastra/agents/graphingAgents.ts b/src/mastra/agents/graphingAgents.ts index 7d3df465..ca44958b 100644 --- a/src/mastra/agents/graphingAgents.ts +++ b/src/mastra/agents/graphingAgents.ts @@ -1,7 +1,7 @@ import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import { Agent } from '@mastra/core/agent' import { - TokenLimiterProcessor, + //TokenLimiterProcessor, UnicodeNormalizer, } from '@mastra/core/processors' import type { RequestContext } from '@mastra/core/request-context' @@ -141,7 +141,7 @@ Rules and best practices: }, inputProcessors: [ new UnicodeNormalizer({ - stripControlChars: false, + stripControlChars: true, collapseWhitespace: true, preserveEmojis: true, trim: true, @@ -274,7 +274,7 @@ export const fetchAgent = new Agent({ internal: InternalSpans.ALL, }, }, - outputProcessors: [new TokenLimiterProcessor(32768)], + //outputProcessors: [new TokenLimiterProcessor(32768)], }) /** diff --git a/src/mastra/agents/image.ts b/src/mastra/agents/image.ts index bf8f9b49..3cece2a4 100644 --- a/src/mastra/agents/image.ts +++ b/src/mastra/agents/image.ts @@ -56,7 +56,7 @@ export const imageAgent = new Agent({ }, } }, - model: googleAINanoBanana, + model: 'google/gemma-4-31b-it', memory: LibsqlMemory, options: { tracingPolicy: { diff --git a/src/mastra/agents/image_to_csv.ts b/src/mastra/agents/image_to_csv.ts index ebde344b..fec9c193 100644 --- a/src/mastra/agents/image_to_csv.ts +++ b/src/mastra/agents/image_to_csv.ts @@ -3,7 +3,7 @@ import { googleAI } from '../config' import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import type { RequestContext } from '@mastra/core/request-context' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { InternalSpans } from '@mastra/core/observability' import type { AgentRequestContext } from './request-context' import { LibsqlMemory } from '../config/libsql' @@ -157,5 +157,5 @@ IMPORTANT: Only return the CSV string including the header row. Do not include a }, workflows: {}, maxRetries: 5, - outputProcessors: [new TokenLimiterProcessor(1048576)], + // outputProcessors: [new TokenLimiterProcessor(1048576)], }) diff --git a/src/mastra/agents/index.ts b/src/mastra/agents/index.ts index 1ead0928..4e52ba05 100644 --- a/src/mastra/agents/index.ts +++ b/src/mastra/agents/index.ts @@ -22,6 +22,7 @@ export type { CalendarContext } from './calendarAgent' export type { ScriptWriterRuntimeContext } from './scriptWriterAgent' export { acpAgent } from './acpAgent' +export { browserAgent } from './browserAgent' export { legalResearchAgent, contractAnalysisAgent, diff --git a/src/mastra/agents/learningExtractionAgent.ts b/src/mastra/agents/learningExtractionAgent.ts index ec1705fd..db4cd532 100644 --- a/src/mastra/agents/learningExtractionAgent.ts +++ b/src/mastra/agents/learningExtractionAgent.ts @@ -2,7 +2,7 @@ import { Agent } from '@mastra/core/agent' import { log } from '../config/logger' import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { InternalSpans } from '@mastra/core/observability' import { getRoleFromContext, @@ -73,5 +73,5 @@ Extract the single most important learning and create one relevant follow-up que }, workflows: {}, maxRetries: 5, - outputProcessors: [new TokenLimiterProcessor(128000)], + // outputProcessors: [new TokenLimiterProcessor(128000)], }) diff --git a/src/mastra/agents/noteTakerAgent.ts b/src/mastra/agents/noteTakerAgent.ts index 9e96a7bb..c4f020c8 100644 --- a/src/mastra/agents/noteTakerAgent.ts +++ b/src/mastra/agents/noteTakerAgent.ts @@ -13,7 +13,7 @@ export const noteTakerAgent = new Agent({ instructions: instructions1, memory: LibsqlMemory, // tools: [], - model: 'google/gemini-3.1-flash-lite-preview', + model: 'google/gemma-4-31b-it', options: { tracingPolicy: { internal: InternalSpans.ALL, diff --git a/src/mastra/agents/package-publisher.ts b/src/mastra/agents/package-publisher.ts index 74372d69..5926bea1 100644 --- a/src/mastra/agents/package-publisher.ts +++ b/src/mastra/agents/package-publisher.ts @@ -1,7 +1,7 @@ import { Agent } from '@mastra/core/agent' import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { getLanguageFromContext, getRoleFromContext, @@ -168,5 +168,5 @@ export const danePackagePublisher = new Agent({ }, }, scorers: {}, - outputProcessors: [new TokenLimiterProcessor(128000)], + //outputProcessors: [new TokenLimiterProcessor(128000)], }) diff --git a/src/mastra/agents/projectManagementAgent.ts b/src/mastra/agents/projectManagementAgent.ts index cc99d06b..8af43250 100644 --- a/src/mastra/agents/projectManagementAgent.ts +++ b/src/mastra/agents/projectManagementAgent.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { log } from '../config/logger' import { reportAgent } from './reportAgent' @@ -22,6 +11,7 @@ import { getRoleFromContext, } from './request-context' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorAgentPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Project Management Agent...') @@ -29,207 +19,94 @@ log.info('Initializing Project Management Agent...') * Evaluates whether a project-management response contains a practical plan, * scheduling awareness, and visible risk management. */ -const projectManagementTaskCompleteScorer = createScorer({ +const projectManagementTaskCompleteScorer = createSupervisorAgentPatternScorer({ id: 'project-management-task-complete', name: 'Project Management Task Completeness', description: 'Checks whether a project response includes timeline, deliverables, risks, and next actions.', - type: 'agent', + label: 'project management completeness', + emptyReason: 'No usable project management response was produced.', + weakReason: 'The response is present but lacks planning depth.', + strongReasonPrefix: 'This project response is strong because', + responseLengthThresholds: [ + { min: 180, weight: 0.15 }, + { min: 350, weight: 0.15 }, + ], + minParagraphsForStructure: 3, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it includes timeline or milestone guidance', + regex: /timeline|schedule|deadline|milestone/i, + weight: 0.2, + }, + { + label: 'it calls out risks or blockers', + regex: /risk|blocker|dependency|constraint/i, + weight: 0.2, + }, + { + label: 'it includes ownership or stakeholder guidance', + regex: /owner|stakeholder|resource|team/i, + weight: 0.15, + }, + { + label: 'it ends with concrete next actions', + regex: /next step|action item|deliverable/i, + weight: 0.15, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasTimeline: /timeline|schedule|deadline|milestone/i.test(responseText), - hasRisk: /risk|blocker|dependency|constraint/i.test(responseText), - hasOwnership: /owner|stakeholder|resource|team/i.test(responseText), - hasActions: /next step|action item|deliverable/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText) || - responseText.split(/\n\s*\n/).filter(Boolean).length >= 3, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 180) score += 0.15 - if (analysis.responseLength >= 350) score += 0.15 - if (analysis.hasTimeline) score += 0.2 - if (analysis.hasRisk) score += 0.2 - if (analysis.hasOwnership) score += 0.15 - if (analysis.hasActions) score += 0.15 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable project management response was produced.' - } - - if (analysis.hasTimeline) parts.push('it includes timeline or milestone guidance') - if (analysis.hasRisk) parts.push('it calls out risks or blockers') - if (analysis.hasOwnership) parts.push('it includes ownership or stakeholder guidance') - if (analysis.hasActions) parts.push('it ends with concrete next actions') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This project response is strong because ${parts.join(', ')}.` : 'The response is present but lacks planning depth.'}` - }) /** * Evaluates whether the project-management response is execution-ready with a * clear plan shape, sequencing, and ownership or decision guidance. */ -const projectManagementExecutionScorer = createScorer({ +const projectManagementExecutionScorer = createSupervisorAgentPatternScorer({ id: 'project-management-execution-readiness', name: 'Project Management Execution Readiness', description: 'Checks whether a PM response includes priorities, sequencing, accountability, and decision-ready next actions.', - type: 'agent', + label: 'project management execution', + emptyReason: 'No usable project management execution plan was produced.', + weakReason: 'The response is present but still lacks execution detail.', + strongReasonPrefix: 'This execution plan is strong because', + responseLengthThresholds: [ + { min: 160, weight: 0.15 }, + { min: 280, weight: 0.1 }, + ], + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it clarifies priority or sequencing', + regex: /priority|p0|p1|phase|sequence|order/i, + weight: 0.2, + }, + { + label: 'it names owners or accountable parties', + regex: /owner|stakeholder|team|responsible/i, + weight: 0.2, + }, + { + label: 'it calls out decisions or trade-offs', + regex: /decision|assumption|trade-off|escalation/i, + weight: 0.2, + }, + { + label: 'it includes immediate next actions', + regex: /next step|immediate action|this week|milestone/i, + weight: 0.1, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasPriority: /priority|p0|p1|phase|sequence|order/i.test(responseText), - hasAccountability: - /owner|stakeholder|team|responsible/i.test(responseText), - hasDecisionSupport: - /decision|assumption|trade-off|escalation/i.test(responseText), - hasUrgency: - /next step|immediate action|this week|milestone/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 160) score += 0.15 - if (analysis.responseLength >= 280) score += 0.1 - if (analysis.hasPriority) score += 0.2 - if (analysis.hasAccountability) score += 0.2 - if (analysis.hasDecisionSupport) score += 0.2 - if (analysis.hasUrgency) score += 0.1 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable project management execution plan was produced.' - } - - if (analysis.hasPriority) parts.push('it clarifies priority or sequencing') - if (analysis.hasAccountability) parts.push('it names owners or accountable parties') - if (analysis.hasDecisionSupport) parts.push('it calls out decisions or trade-offs') - if (analysis.hasUrgency) parts.push('it includes immediate next actions') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution plan is strong because ${parts.join(', ')}.` : 'The response is present but still lacks execution detail.'}` - }) export const projectManagementAgent = new Agent< 'project-management-agent', diff --git a/src/mastra/agents/recharts.ts b/src/mastra/agents/recharts.ts index 5dce6cc8..bf41f9d4 100644 --- a/src/mastra/agents/recharts.ts +++ b/src/mastra/agents/recharts.ts @@ -2,10 +2,9 @@ import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import { Agent } from '@mastra/core/agent' import { InternalSpans } from '@mastra/core/observability' import { - TokenLimiterProcessor, + //TokenLimiterProcessor, UnicodeNormalizer } from '@mastra/core/processors' -import { googleAI, googleAIFlashLite } from '../config' import { log } from '../config/logger' import { alphaVantageStockTool } from '../tools/alpha-vantage.tool' import { @@ -140,7 +139,7 @@ You are a Financial Data Visualization Specialist focused on recommending optima }, } }, - model: googleAIFlashLite, + model: 'google/gemma-4-31b-it', memory: LibsqlMemory, tools: {}, maxRetries: 3, @@ -149,7 +148,6 @@ You are a Financial Data Visualization Specialist focused on recommending optima internal: InternalSpans.ALL, }, }, - outputProcessors: [new TokenLimiterProcessor(1048576)], }) /** @@ -199,7 +197,7 @@ You are a Financial Data Processing Specialist that transforms raw API data into }, } }, - model: googleAIFlashLite, + model: 'google/gemma-4-31b-it', tools: chartDataProcessorTools, memory: LibsqlMemory, options: { @@ -208,7 +206,7 @@ You are a Financial Data Processing Specialist that transforms raw API data into }, }, maxRetries: 3, - outputProcessors: [new TokenLimiterProcessor(1048576)], + //outputProcessors: [new TokenLimiterProcessor(1048576)], }) /** @@ -259,7 +257,7 @@ You are a Senior React Developer specializing in Recharts financial visualizatio }, } }, - model: googleAI, + model: 'google/gemma-4-31b-it', memory: LibsqlMemory, tools: {}, maxRetries: 3, @@ -324,7 +322,7 @@ You are the Financial Chart Supervisor, orchestrating the complete chart creatio }, } }, - model: googleAI, + model: 'google/gemma-4-31b-it', tools: chartSupervisorTools, memory: LibsqlMemory, options: { diff --git a/src/mastra/agents/reportAgent.ts b/src/mastra/agents/reportAgent.ts index 039008fe..833f2ba3 100644 --- a/src/mastra/agents/reportAgent.ts +++ b/src/mastra/agents/reportAgent.ts @@ -3,9 +3,6 @@ import { log } from '../config/logger' import type { GoogleLanguageModelOptions } from '@ai-sdk/google' import { InternalSpans } from '@mastra/core/observability' -import { - TokenLimiterProcessor -} from '@mastra/core/processors' import { getLanguageFromContext, getRoleFromContext, @@ -66,7 +63,7 @@ export const reportAgent = new Agent({ return "google/gemini-3.1-flash-lite-preview" } // cheaper/faster model for free tier - return "google/gemini-3.1-flash-lite-preview" + return "google/gemma-4-31b-it" }, memory: LibsqlMemory, tools: {}, @@ -78,14 +75,6 @@ export const reportAgent = new Agent({ scorers: {}, workflows: {}, maxRetries: 5, - outputProcessors: [ - new TokenLimiterProcessor(1048576), - // new BatchPartsProcessor({ - // batchSize: 5, - // maxWaitTime: 75, - // emitOnNonText: true, - // }), - ], }) // --- IGNORE --- diff --git a/src/mastra/agents/researchAgent.ts b/src/mastra/agents/researchAgent.ts index 12dc1470..246c31c1 100644 --- a/src/mastra/agents/researchAgent.ts +++ b/src/mastra/agents/researchAgent.ts @@ -1,6 +1,7 @@ import { libsqlQueryTool, libsqlgraphQueryTool } from './../config/libsql'; import { libsqlChunker, mdocumentChunker } from './../tools/document-chunking.tool'; import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' +import type { Message, Thread } from 'chat' import { Agent } from '@mastra/core/agent' import { log } from '../config/logger' import { evaluateResultTool } from '../tools/evaluateResultTool' @@ -24,8 +25,8 @@ import { yahooFinanceStockQuotesTool } from '../tools/yahoo-finance-stock.tool' // Scorers import { InternalSpans } from '@mastra/core/observability' -import { AgentChannels } from '@mastra/core/channels' -import { mainWorkspace } from '../workspaces' +import type { ChannelHandlers } from '@mastra/core/channels' +import * as workspaces from '../workspaces' import { getLanguageFromContext, getRoleFromContext, @@ -35,16 +36,180 @@ import { researchArxivDownloadWorkflow } from '../workflows/research/research-ar import { researchArxivSearchWorkflow } from '../workflows/research/research-arxiv-search.workflow' import { LibsqlMemory } from '../config/libsql' import { listRepositories } from '../tools/github'; -import { agentBrowser } from '../browsers'; -//import { createGitHubAdapter } from "@chat-adapter/github"; +import * as browsers from '../browsers'; +import { createGitHubAdapter } from '@chat-adapter/github' import { createDiscordAdapter } from '@chat-adapter/discord' - +import { google } from '../config/google' +import { + ToolSearchProcessor, + //TokenLimiter +} from '@mastra/core/processors' //const github = createGitHubAdapter({ // //appId: process.env.GITHUB_APP_ID!, // //privateKey: process.env.GITHUB_PRIVATE_KEY!, // // webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!, //}); +/** + * Enables the GitHub channel only when the webhook secret and at least one + * supported authentication path are configured in the environment. + */ +function isGitHubChannelConfigured(): boolean { + const hasWebhookSecret = Boolean(process.env.GITHUB_WEBHOOK_SECRET?.trim()) + const hasToken = Boolean(process.env.GITHUB_TOKEN?.trim()) + const hasAppAuth = Boolean( + process.env.GITHUB_APP_ID?.trim() && + process.env.GITHUB_PRIVATE_KEY?.trim() + ) + + return hasWebhookSecret && (hasToken || hasAppAuth) +} + +const researchAgentChannelAdapters = { + discord: { + adapter: createDiscordAdapter(), + gateway: false, + }, + ...(isGitHubChannelConfigured() + ? { + github: { + adapter: createGitHubAdapter({ + userName: + process.env.GITHUB_BOT_USERNAME?.trim() ?? 'research-agent', + }), + gateway: false, + cards: false, + }, + } + : {}), +} + +/** + * Normalizes the message text available from channel adapters so handler logic + * can make lightweight decisions without depending on one platform shape. + */ +function getChannelMessageText(message: { + text?: string + content?: unknown +}): string { + if (typeof message.text === 'string' && message.text.trim().length > 0) { + return message.text.trim() + } + + if (typeof message.content === 'string' && message.content.trim().length > 0) { + return message.content.trim() + } + + return '' +} + +/** + * Detects low-signal follow-up messages that do not warrant another full + * research pass when the agent is already subscribed to the thread. + */ +function isAcknowledgementOnlyMessage(messageText: string): boolean { + return /^(thanks|thank you|resolved|done|fixed|closed|lgtm|sgtm|looks good)[.!]?$/i.test( + messageText.trim() + ) +} + +/** + * Detects GitHub-backed channel threads from the Chat SDK thread ID format. + */ +function isGitHubThread(thread: Thread): boolean { + return thread.id.startsWith('github:') +} + +type ResearchChannelEvent = + | 'direct-message' + | 'mention' + | 'subscribed-message' + +/** + * Centralizes research-channel hook behavior so every handler logs the same + * metadata and applies the same low-signal suppression rules. + */ +async function handleResearchChannelEvent( + event: ResearchChannelEvent, + thread: Thread, + message: Message, + defaultHandler: (thread: Thread, message: Message) => Promise, + options?: { + skipAcknowledgements?: boolean + } +): Promise { + const messageText = getChannelMessageText(message) + const acknowledgementOnly = isAcknowledgementOnlyMessage(messageText) + const githubThread = isGitHubThread(thread) + + log.info('Research channel event', { + event, + threadId: thread.id, + platform: githubThread ? 'github' : 'chat', + textLength: messageText.length, + acknowledgementOnly, + }) + + if (options?.skipAcknowledgements && acknowledgementOnly) { + log.info('Research channel event skipped', { + event, + threadId: thread.id, + reason: 'acknowledgement-only', + platform: githubThread ? 'github' : 'chat', + }) + return + } + + await defaultHandler(thread, message) +} + +const researchChannelHandlers: ChannelHandlers = { + onDirectMessage: async (thread, message, defaultHandler) => { + await handleResearchChannelEvent( + 'direct-message', + thread, + message, + defaultHandler + ) + }, + onMention: async (thread, message, defaultHandler) => { + await handleResearchChannelEvent( + 'mention', + thread, + message, + defaultHandler, + { + skipAcknowledgements: true, + } + ) + }, + onSubscribedMessage: async (thread, message, defaultHandler) => { + await handleResearchChannelEvent( + 'subscribed-message', + thread, + message, + defaultHandler, + { + skipAcknowledgements: true, + } + ) + }, +} + +/** + * Returns the shared workspace used by the research agent. + */ +function getResearchAgentWorkspace() { + return workspaces.mainWorkspace +} + +/** + * Returns the deterministic browser configured for research verification. + */ +function getResearchAgentBrowser() { + return browsers.agentBrowser +} + type ResearchPhase = 'initial' | 'followup' | 'validation' const RESEARCH_PHASE_CONTEXT_KEY = 'researchPhase' as const @@ -117,7 +282,8 @@ Role: ${role} | Lang: ${language} | Phase: ${researchPhase} ## Tool Selection Guide - **Web**: Prefer 'fetchTool' for reliable URL fetch/search to markdown. -- **News/Trends**: 'googleNewsTool', 'googleTrendsTool', 'googleFinanceTool'. +- **Live browser verification**: Use the attached browser only when page state, interaction results, or live UI evidence materially matters more than static fetch output. +- **News/Trends**: 'googleNewsLiteTool', 'googleTrendsTool', 'googleFinanceTool'. - **Academic**: 'googleScholarTool'. - **Financial**: Use 'polygon*' for stocks/crypto. - **Financial**: Use 'polygon*' for stocks/crypto when you need paid/commercial feeds; use 'binanceSpotMarketDataTool' for free crypto spot data and batch lookups of 1-10 symbols; use 'coinbaseExchangeMarketDataTool', 'stooqStockQuotesTool', and 'yahooFinanceStockQuotesTool' for free public market data. @@ -129,6 +295,7 @@ Role: ${role} | Lang: ${language} | Phase: ${researchPhase} - **Efficiency**: No repetitive or back-to-back tool calls for the same query. - **Specificity**: Use focused queries; cite sources with confidence levels. - **Fallback**: If tools fail, use internal knowledge and state failure. +- **GitHub channel delivery**: If the request arrives from a GitHub issue or PR comment thread, respond in concise GitHub-flavored Markdown with a direct answer, bullet findings, source links, and the clearest next action or blocker. `, providerOptions: { google: { @@ -141,11 +308,14 @@ Role: ${role} | Lang: ${language} | Phase: ${researchPhase} }, } }, - model: { - url: "https://api.kilo.ai/api/gateway", - id:'kilo/x-ai/grok-code-fast-1:optimized:free', - apiKey: process.env.KILO_API_KEY, - provider: 'kilo', + model: ({ requestContext }) => { + const role = getRoleFromContext(requestContext) + + if (role === 'admin') { + return google.chat('gemini-3.1-pro-preview') + } + + return google.chat('gemini-3.1-flash-lite-preview') }, tools: researchAgentTools, workflows: { researchArxivDownloadWorkflow, researchArxivSearchWorkflow }, @@ -162,6 +332,13 @@ Role: ${role} | Lang: ${language} | Phase: ${researchPhase} }, }, //voice: gvoice, + inputProcessors: [ + new ToolSearchProcessor({ + tools: researchAgentTools, + search: { topK: 5 }, + }), + //new TokenLimiter(2048), + ], outputProcessors: [ // new TokenLimiterProcessor(128000), // new BatchPartsProcessor({ @@ -170,16 +347,17 @@ Role: ${role} | Lang: ${language} | Phase: ${researchPhase} // emitOnNonText: true, // }), ], - workspace: mainWorkspace, - browser: agentBrowser, - channels: new AgentChannels({ - adapters: { - discord: { - adapter: createDiscordAdapter(), - gateway: false, - }, + workspace: getResearchAgentWorkspace(), + browser: getResearchAgentBrowser(), + channels: { + inlineLinks: ['*'], + inlineMedia: ['image/*', 'video/*', 'audio/*'], + adapters: researchAgentChannelAdapters, + threadContext: { + maxMessages: 15, }, - }), + handlers: researchChannelHandlers, + }, // defaultOptions: { // autoResumeSuspendedTools: true, // }, diff --git a/src/mastra/agents/researchPaperAgent.ts b/src/mastra/agents/researchPaperAgent.ts index 8327e3d2..9478ca82 100644 --- a/src/mastra/agents/researchPaperAgent.ts +++ b/src/mastra/agents/researchPaperAgent.ts @@ -2,9 +2,6 @@ import { Agent } from '@mastra/core/agent' import { log } from '../config/logger' import { InternalSpans } from '@mastra/core/observability' -import { - TokenLimiterProcessor -} from '@mastra/core/processors' import { arxivPaperDownloaderTool, arxivPdfParserTool, @@ -78,14 +75,14 @@ User: ${role} | Lang: ${language} internal: InternalSpans.ALL, }, }, - outputProcessors: [ - new TokenLimiterProcessor(128000), - // new BatchPartsProcessor({ - // batchSize: 5, - // maxWaitTime: 75, - // emitOnNonText: true, + // outputProcessors: [ + // new TokenLimiterProcessor(128000), + // // new BatchPartsProcessor({ + // // batchSize: 5, + // // maxWaitTime: 75, + // // emitOnNonText: true, // }), - ], + //], // defaultOptions: { // autoResumeSuspendedTools: true, // }, diff --git a/src/mastra/agents/scriptWriterAgent.ts b/src/mastra/agents/scriptWriterAgent.ts index 1e9140b6..f20fca19 100644 --- a/src/mastra/agents/scriptWriterAgent.ts +++ b/src/mastra/agents/scriptWriterAgent.ts @@ -2,7 +2,7 @@ import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' import { google } from '@ai-sdk/google' import { Agent } from '@mastra/core/agent' import { InternalSpans } from '@mastra/core/observability' -import { TokenLimiterProcessor } from '@mastra/core/processors' +//import { TokenLimiterProcessor } from '@mastra/core/processors' import { fetchTool } from '../tools/fetch.tool' import { getLanguageFromContext, @@ -72,7 +72,7 @@ User: ${userTier} | Lang: ${language} }, scorers: {}, tools: scriptWriterTools, - outputProcessors: [new TokenLimiterProcessor(1048576)], + // outputProcessors: [new TokenLimiterProcessor(1048576)], // defaultOptions: { // autoResumeSuspendedTools: true, // }, diff --git a/src/mastra/agents/seoAgent.ts b/src/mastra/agents/seoAgent.ts index 94d4b0fa..930115db 100644 --- a/src/mastra/agents/seoAgent.ts +++ b/src/mastra/agents/seoAgent.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { log } from '../config/logger' import { InternalSpans } from '@mastra/core/observability' @@ -22,6 +11,7 @@ import { getRoleFromContext, } from './request-context' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorAgentPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing SEO Agent...') @@ -29,207 +19,89 @@ log.info('Initializing SEO Agent...') * Evaluates whether an SEO response covers research-backed optimization guidance, * prioritization, and concrete implementation detail. */ -const seoTaskCompleteScorer = createScorer({ +const seoTaskCompleteScorer = createSupervisorAgentPatternScorer({ id: 'seo-task-complete', name: 'SEO Task Completeness', description: 'Checks whether an SEO response includes keyword or SERP insight, actionable optimization guidance, and prioritization.', - type: 'agent', + label: 'SEO completeness', + emptyReason: 'No usable SEO response was produced.', + weakReason: 'The response is present but still lacks optimization depth.', + strongReasonPrefix: 'This SEO response is strong because', + responseLengthThresholds: [ + { min: 160, weight: 0.15 }, + { min: 350, weight: 0.15 }, + ], + minParagraphsForStructure: 3, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it includes keyword or SERP insight', + regex: /keyword|serp|search intent|competitor/i, + weight: 0.2, + }, + { + label: 'it covers on-page optimization', + regex: /title|meta|header|internal link|content/i, + weight: 0.15, + }, + { + label: 'it addresses technical SEO', + regex: /technical|core web vitals|schema|crawl|index/i, + weight: 0.15, + }, + { + label: 'it prioritizes the recommendations', + regex: /priority|impact|effort|next step/i, + weight: 0.15, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasKeyword: - /keyword|serp|search intent|competitor/i.test(responseText), - hasOnPage: - /title|meta|header|internal link|content/i.test(responseText), - hasTechnical: - /technical|core web vitals|schema|crawl|index/i.test(responseText), - hasPriority: - /priority|impact|effort|next step/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText) || - responseText.split(/\n\s*\n/).filter(Boolean).length >= 3, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 160) score += 0.15 - if (analysis.responseLength >= 350) score += 0.15 - if (analysis.hasKeyword) score += 0.2 - if (analysis.hasOnPage) score += 0.15 - if (analysis.hasTechnical) score += 0.15 - if (analysis.hasPriority) score += 0.15 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable SEO response was produced.' - } - - if (analysis.hasKeyword) parts.push('it includes keyword or SERP insight') - if (analysis.hasOnPage) parts.push('it covers on-page optimization') - if (analysis.hasTechnical) parts.push('it addresses technical SEO') - if (analysis.hasPriority) parts.push('it prioritizes the recommendations') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This SEO response is strong because ${parts.join(', ')}.` : 'The response is present but still lacks optimization depth.'}` - }) /** * Evaluates whether the SEO response is execution-ready, prioritized, and tied * to measurable search performance outcomes. */ -const seoActionabilityScorer = createScorer({ +const seoActionabilityScorer = createSupervisorAgentPatternScorer({ id: 'seo-actionability-readiness', name: 'SEO Actionability Readiness', description: 'Checks whether an SEO response is prioritized, measurable, and easy to implement.', - type: 'agent', + label: 'SEO actionability', + emptyReason: 'No usable SEO actionability response was produced.', + weakReason: 'The response is present but still lacks implementable detail.', + strongReasonPrefix: 'This SEO plan is strong because', + responseLengthThresholds: [ + { min: 140, weight: 0.15 }, + { min: 260, weight: 0.1 }, + ], + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it prioritizes by impact or effort', + regex: /priority|high impact|quick win|effort|impact/i, + weight: 0.25, + }, + { + label: 'it ties changes to metrics', + regex: /ranking|ctr|traffic|conversion|metric|measure/i, + weight: 0.2, + }, + { + label: 'it gives actionable implementation steps', + regex: /next step|implement|update|add|fix/i, + weight: 0.2, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasImpact: /priority|high impact|quick win|effort|impact/i.test(responseText), - hasMetrics: - /ranking|ctr|traffic|conversion|metric|measure/i.test(responseText), - hasAction: - /next step|implement|update|add|fix/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 140) score += 0.15 - if (analysis.responseLength >= 260) score += 0.1 - if (analysis.hasImpact) score += 0.25 - if (analysis.hasMetrics) score += 0.2 - if (analysis.hasAction) score += 0.2 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable SEO actionability response was produced.' - } - - if (analysis.hasImpact) parts.push('it prioritizes by impact or effort') - if (analysis.hasMetrics) parts.push('it ties changes to metrics') - if (analysis.hasAction) parts.push('it gives actionable implementation steps') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This SEO plan is strong because ${parts.join(', ')}.` : 'The response is present but still lacks implementable detail.'}` - }) export const seoAgent = new Agent({ id: 'seo-agent', diff --git a/src/mastra/agents/socialMediaAgent.ts b/src/mastra/agents/socialMediaAgent.ts index e5da0be2..4511bc37 100644 --- a/src/mastra/agents/socialMediaAgent.ts +++ b/src/mastra/agents/socialMediaAgent.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { log } from '../config/logger' import { InternalSpans } from '@mastra/core/observability' @@ -18,12 +7,13 @@ import { contentStrategistAgent } from './contentStrategistAgent' import { copywriterAgent } from './copywriterAgent' import { researchAgent } from './researchAgent' import { - baseAgentRequestContextSchema, + //baseAgentRequestContextSchema, getLanguageFromContext, getUserIdFromContext, getRoleFromContext, } from './request-context' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorAgentPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Social Media Agent...') @@ -31,206 +21,89 @@ log.info('Initializing Social Media Agent...') * Evaluates whether a social-media response includes platform-aware content, * campaign guidance, and execution-ready details. */ -const socialMediaTaskCompleteScorer = createScorer({ +const socialMediaTaskCompleteScorer = createSupervisorAgentPatternScorer({ id: 'social-media-task-complete', name: 'Social Media Task Completeness', description: 'Checks whether a social-media response includes platform targeting, content direction, and publishing guidance.', - type: 'agent', + label: 'social media completeness', + emptyReason: 'No usable social media response was produced.', + weakReason: 'The response is present but still lacks campaign detail.', + strongReasonPrefix: 'This social response is strong because', + responseLengthThresholds: [ + { min: 120, weight: 0.15 }, + { min: 250, weight: 0.1 }, + ], + minParagraphsForStructure: 2, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it specifies the target platform', + regex: /linkedin|twitter|x|instagram|facebook|tiktok/i, + weight: 0.2, + }, + { + label: 'it uses channel-native content formats', + regex: /post|thread|carousel|reel|story|caption/i, + weight: 0.2, + }, + { + label: 'it includes cadence or posting guidance', + regex: /schedule|posting time|calendar|cadence/i, + weight: 0.15, + }, + { + label: 'it includes hooks, CTAs, or engagement guidance', + regex: /cta|hashtag|engagement|hook/i, + weight: 0.15, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasPlatform: - /linkedin|twitter|x|instagram|facebook|tiktok/i.test(responseText), - hasFormat: - /post|thread|carousel|reel|story|caption/i.test(responseText), - hasCadence: - /schedule|posting time|calendar|cadence/i.test(responseText), - hasEngagement: - /cta|hashtag|engagement|hook/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText) || - responseText.split(/\n\s*\n/).filter(Boolean).length >= 2, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 120) score += 0.15 - if (analysis.responseLength >= 250) score += 0.1 - if (analysis.hasPlatform) score += 0.2 - if (analysis.hasFormat) score += 0.2 - if (analysis.hasCadence) score += 0.15 - if (analysis.hasEngagement) score += 0.15 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable social media response was produced.' - } - - if (analysis.hasPlatform) parts.push('it specifies the target platform') - if (analysis.hasFormat) parts.push('it uses channel-native content formats') - if (analysis.hasCadence) parts.push('it includes cadence or posting guidance') - if (analysis.hasEngagement) parts.push('it includes hooks, CTAs, or engagement guidance') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This social response is strong because ${parts.join(', ')}.` : 'The response is present but still lacks campaign detail.'}` - }) /** * Evaluates whether the social-media response is campaign-ready with clear * channel fit, hooks, and execution guidance. */ -const socialMediaExecutionScorer = createScorer({ +const socialMediaExecutionScorer = createSupervisorAgentPatternScorer({ id: 'social-media-execution-readiness', name: 'Social Media Execution Readiness', description: 'Checks whether a social-media response contains usable channel tactics, hooks, and next actions.', - type: 'agent', + label: 'social media execution', + emptyReason: 'No usable social media execution plan was produced.', + weakReason: 'The response is present but still needs execution detail.', + strongReasonPrefix: 'This execution plan is strong because', + responseLengthThresholds: [ + { min: 120, weight: 0.15 }, + { min: 220, weight: 0.1 }, + ], + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it opens with a hook or CTA', + regex: /hook|opening line|cta|call to action/i, + weight: 0.25, + }, + { + label: 'it includes cadence guidance', + regex: /cadence|schedule|posting time|weekly|daily/i, + weight: 0.15, + }, + { + label: 'it ties the plan to engagement outcomes', + regex: /engagement|reach|click|reply|save|share/i, + weight: 0.2, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasHook: /hook|opening line|cta|call to action/i.test(responseText), - hasCadence: /cadence|schedule|posting time|weekly|daily/i.test(responseText), - hasEngagement: - /engagement|reach|click|reply|save|share/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 120) score += 0.15 - if (analysis.responseLength >= 220) score += 0.1 - if (analysis.hasHook) score += 0.25 - if (analysis.hasCadence) score += 0.15 - if (analysis.hasEngagement) score += 0.2 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable social media execution plan was produced.' - } - - if (analysis.hasHook) parts.push('it opens with a hook or CTA') - if (analysis.hasCadence) parts.push('it includes cadence guidance') - if (analysis.hasEngagement) parts.push('it ties the plan to engagement outcomes') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution plan is strong because ${parts.join(', ')}.` : 'The response is present but still needs execution detail.'}` - }) export const socialMediaAgent = new Agent({ id: 'social-media-agent', diff --git a/src/mastra/agents/stockAnalysisAgent.ts b/src/mastra/agents/stockAnalysisAgent.ts index e0d73757..cfd4951b 100644 --- a/src/mastra/agents/stockAnalysisAgent.ts +++ b/src/mastra/agents/stockAnalysisAgent.ts @@ -18,7 +18,6 @@ import { } from '../tools/polygon-tools' import { googleFinanceTool } from '../tools/serpapi-academic-local.tool' //import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' -import { TokenLimiterProcessor } from '@mastra/core/processors' import { InternalSpans } from '@mastra/core/observability' import { getLanguageFromContext, @@ -119,7 +118,7 @@ export const stockAnalysisAgent = new Agent({ internal: InternalSpans.ALL, }, }, - outputProcessors: [new TokenLimiterProcessor(1048576)], + // outputProcessors: [new TokenLimiterProcessor(1048576)], maxRetries: 5, defaultOptions: { autoResumeSuspendedTools: true, diff --git a/src/mastra/agents/supervisor-agent.ts b/src/mastra/agents/supervisor-agent.ts index a664c995..b347a521 100644 --- a/src/mastra/agents/supervisor-agent.ts +++ b/src/mastra/agents/supervisor-agent.ts @@ -1,17 +1,7 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { InternalSpans } from '@mastra/core/observability' +import { browserAgent } from './browserAgent' import { researchAgent } from './researchAgent' import { copywriterAgent } from './copywriterAgent' import { libsqlgraphQueryTool, LibsqlMemory, libsqlQueryTool, libsqlvector } from '../config/libsql' @@ -29,6 +19,7 @@ import { } from './request-context' import { embed } from 'ai'; import { ModelRouterEmbeddingModel } from '@mastra/core/llm'; +import { createSupervisorAgentPatternScorer } from '../scorers/supervisor-scorers' const workspace = new Workspace({ id: 'supervisor-workspace', @@ -66,238 +57,95 @@ const workspace = new Workspace({ * Evaluates whether the supervisor produced a complete research-backed final answer * instead of stopping at a partial delegation summary. */ -const supervisorTaskCompleteScorer = createScorer({ +const supervisorTaskCompleteScorer = createSupervisorAgentPatternScorer({ id: 'supervisor-task-complete', name: 'Supervisor Task Completeness', description: 'Checks whether the supervisor returned a structured, substantial, research-backed final response.', - type: 'agent', + label: 'supervisor completeness', + emptyReason: 'No usable supervisor response was produced.', + weakReason: 'The response is present but lacks several synthesis signals.', + strongReasonPrefix: 'This supervisor response is strong because', + responseLengthThresholds: [ + { min: 220, weight: 0.15 }, + { min: 600, weight: 0.15 }, + ], + minParagraphsForStructure: 3, + structureWeight: 0.15, + reasoningWeight: 0.1, + toolWeight: 0.05, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it starts with a direct synthesis', + regex: /summary|executive summary|top line|bottom line|direct answer|recommend/i, + weight: 0.05, + }, + { + label: 'it includes evidence or source anchors', + regex: /source|sources|citation|citations|http|www\.|\b20\d{2}\b/i, + weight: 0.15, + }, + { + label: 'it ends with next steps or follow-up guidance', + regex: /next step|next steps|action|follow-up|open question/i, + weight: 0.05, + }, + { + label: 'it acknowledges uncertainty or caveats', + regex: /risk|caveat|uncertain|unknown|assumption/i, + weight: 0.05, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - const paragraphCount = responseText.split(/\n\s*\n/).filter(Boolean).length - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - paragraphCount, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasEvidence: - /source|sources|citation|citations|http|www\.|\b20\d{2}\b/i.test( - responseText - ), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText) || paragraphCount >= 3, - hasDirectAnswer: - /summary|executive summary|top line|bottom line|direct answer|recommend/i.test( - responseText - ), - hasNextSteps: - /next step|next steps|action|follow-up|open question/i.test(responseText), - hasCaveat: - /risk|caveat|uncertain|unknown|assumption/i.test(responseText), - mentionsSynthesis: - /synthes|delegate|research|writing/i.test(responseText) || - /synthes|delegate|research|writing/i.test(systemPrompt), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.hasUserMessage) score += 0.05 - if (analysis.systemMessageCount > 0) score += 0.05 - if (analysis.responseLength >= 220) score += 0.15 - if (analysis.responseLength >= 600) score += 0.15 - if (analysis.paragraphCount >= 3) score += 0.15 - if (analysis.hasReasoning) score += 0.1 - if (analysis.toolCount > 0) score += 0.05 - if (analysis.hasEvidence) score += 0.15 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasDirectAnswer) score += 0.05 - if (analysis.hasNextSteps) score += 0.05 - if (analysis.hasCaveat) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable supervisor response was produced.' - } - - if (analysis.hasDirectAnswer) parts.push('it starts with a direct synthesis') - if (analysis.hasEvidence) parts.push('it includes evidence or source anchors') - if (analysis.hasNextSteps) parts.push('it ends with next steps or follow-up guidance') - if (analysis.hasCaveat) parts.push('it acknowledges uncertainty or caveats') - if (analysis.hasReasoning) parts.push('it shows reasoning support') - if (analysis.toolCount > 0) parts.push(`it used ${analysis.toolCount} tool call(s)`) - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This supervisor response is strong because ${parts.join(', ')}.` : 'The response is present but lacks several synthesis signals.'}` - }) /** * Evaluates whether the supervisor delivered a user-ready synthesis with a * direct answer, actionable guidance, and explicit caveats or next steps. */ -const supervisorSynthesisScorer = createScorer({ +const supervisorSynthesisScorer = createSupervisorAgentPatternScorer({ id: 'supervisor-synthesis-readiness', name: 'Supervisor Synthesis Readiness', description: 'Checks whether the supervisor response is actionable, synthesized, and ready for the user without another iteration.', - type: 'agent', + label: 'supervisor synthesis', + emptyReason: 'No usable supervisor response was produced.', + weakReason: 'The response is present but lacks several synthesis signals.', + strongReasonPrefix: 'This supervisor response is strong because', + responseLengthThresholds: [ + { min: 220, weight: 0.2 }, + { min: 600, weight: 0.1 }, + ], + minParagraphsForStructure: 3, + structureWeight: 0.15, + reasoningWeight: 0.1, + toolWeight: 0.05, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'the answer starts with a direct synthesis', + regex: /summary|in short|bottom line|recommend|recommended/i, + weight: 0.05, + }, + { + label: 'it includes evidence or source anchors', + regex: /source|sources|citation|citations|http|www\.|\b20\d{2}\b/i, + weight: 0.15, + }, + { + label: 'it ends with next steps or follow-up guidance', + regex: /next step|next steps|action|follow-up/i, + weight: 0.05, + }, + { + label: 'it acknowledges uncertainty or caveats', + regex: /risk|caveat|uncertain|unknown|assumption/i, + weight: 0.05, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - const paragraphCount = responseText.split(/\n\s*\n/).filter(Boolean).length - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - paragraphCount, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasEvidence: - /source|sources|citation|citations|http|www\.|\b20\d{2}\b/i.test( - responseText - ), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText) || paragraphCount >= 3, - hasDirectAnswer: - /summary|in short|bottom line|recommend|recommended/i.test(responseText), - hasNextSteps: - /next step|next steps|action|follow-up/i.test(responseText), - hasCaveat: - /risk|caveat|uncertain|unknown|assumption/i.test(responseText), - mentionsDelegation: - /research|writing|delegate|synthes/i.test(responseText) || - /research|writing|delegate|synthes/i.test(systemPrompt), - hasSupportSignal: Boolean(responseText.trim()), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.hasUserMessage) score += 0.05 - if (analysis.systemMessageCount > 0) score += 0.05 - if (analysis.responseLength >= 220) score += 0.2 - if (analysis.responseLength >= 600) score += 0.1 - if (analysis.paragraphCount >= 3) score += 0.15 - if (analysis.hasReasoning) score += 0.1 - if (analysis.toolCount > 0) score += 0.05 - if (analysis.hasEvidence) score += 0.15 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasDirectAnswer) score += 0.05 - if (analysis.hasNextSteps) score += 0.05 - if (analysis.hasCaveat) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable supervisor response was produced.' - } - - if (analysis.hasDirectAnswer) parts.push('the answer starts with a direct synthesis') - if (analysis.hasEvidence) parts.push('it includes evidence or source anchors') - if (analysis.hasNextSteps) parts.push('it ends with next steps or follow-up guidance') - if (analysis.hasCaveat) parts.push('it acknowledges uncertainty or caveats') - if (analysis.hasReasoning) parts.push('it shows reasoning support') - if (analysis.toolCount > 0) parts.push(`it used ${analysis.toolCount} tool call(s)`) - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This supervisor response is strong because ${parts.join(', ')}.` : 'The response is present but lacks several synthesis signals.'}` - }) export const supervisorAgent = new Agent({ id: 'supervisor-agent', @@ -318,13 +166,14 @@ You coordinate research and writing tasks using specialized agents. Available resources: - researchAgent: Gathers factual data and sources (returns bullet points) +- browserAgent: Verifies live pages, browser state, and web claims when static research is not enough - writing-agent: Transforms research into well-structured articles (returns full paragraphs) - judge: Evaluates the quality and completeness of the supervisor agent's output Delegation strategy: -1. For research requests: Delegate to research-agent first to gather facts +1. For research requests: Delegate to research-agent first to gather facts, and use browserAgent when live verification, page inspection, or browser-state evidence would materially improve confidence 2. For writing requests: Delegate to writing-agent with any available research context -3. For comprehensive reports: Delegate to research-agent first, then writing-agent +3. For comprehensive reports: Delegate to research-agent first, then writing-agent, and pull in browserAgent only for high-value verification 4. Always ensure you have gathered sufficient information before producing final output Success criteria: @@ -340,20 +189,18 @@ Final answer contract: Operating rules: - Prefer the minimum number of delegations needed for a trustworthy answer. +- Use browserAgent only when live verification will materially improve the answer; do not browse by default. - Preserve user language when possible. - If evidence is weak, say so explicitly instead of overcommitting. - Do not return raw delegation summaries as the final answer; convert them into a single coherent response.`, } }, - model: { - url: "https://api.kilo.ai/api/gateway", - id:'kilo/x-ai/grok-code-fast-1:optimized:free', - apiKey: process.env.KILO_API_KEY, - }, + model: 'google/gemma-4-31b-it:free', tools: {libsqlgraphQueryTool, libsqlQueryTool, }, agents: { researchAgent, + browserAgent, copywriterAgent, }, memory: LibsqlMemory, @@ -365,19 +212,21 @@ Operating rules: }, }, defaultOptions: { - maxSteps: 10, + maxSteps: 20, providerOptions: { anthropic: { sendReasoning: true, thinking: { type: 'adaptive', }, + cacheControl: { type: 'ephemeral' } }, google: { thinkingConfig: { includeThoughts: true, thinkingLevel: 'medium', }, + responseModalities: ['TEXT', 'IMAGE'], mediaResolution: 'MEDIA_RESOLUTION_MEDIUM', } satisfies GoogleLanguageModelOptions, openai: { @@ -440,13 +289,23 @@ Operating rules: if (context.primitiveId === 'researchAgent') { return { proceed: true, - modifiedPrompt: `${context.prompt}\n\nReturn concise research notes with sources, dated evidence, unresolved gaps, and the most decision-relevant findings first. Focus on recent developments from 2024-2026 unless the user explicitly asks for historical coverage.`, + modifiedPrompt: `${context.prompt}\n\nReturn concise research notes with sources, dated evidence, unresolved gaps, and the most decision-relevant findings first. If live page verification or browser-state evidence would materially improve confidence, explicitly say that browserAgent should verify it. Focus on recent developments from 2024-2026 unless the user explicitly asks for historical coverage.`, modifiedInstructions: 'Act as a senior research analyst. Prioritize evidence quality, source attribution, dated findings, and unresolved gaps. Return only research-ready material for synthesis.', modifiedMaxSteps: 8, } } + if (context.primitiveId === 'browserAgent') { + return { + proceed: true, + modifiedPrompt: `${context.prompt}\n\nUse deterministic browser verification to confirm live claims, page behavior, or browser-state details. Prefer the minimum navigation needed, capture concrete evidence such as URLs, timestamps, visible page text, or interaction results, and clearly separate verified facts from anything still unresolved.`, + modifiedInstructions: + 'Act as a deterministic browser verification specialist. Verify only what matters, avoid exploratory browsing, and return concise evidence that the supervisor can synthesize for the user.', + modifiedMaxSteps: 6, + } + } + if (context.primitiveId === 'copywriterAgent') { return { proceed: true, @@ -507,4 +366,4 @@ Operating rules: suppressFeedback: false, // Show feedback from the scorer }, }, -}) \ No newline at end of file +}) diff --git a/src/mastra/agents/translationAgent.ts b/src/mastra/agents/translationAgent.ts index 8dc669a2..e07041ac 100644 --- a/src/mastra/agents/translationAgent.ts +++ b/src/mastra/agents/translationAgent.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { log } from '../config/logger' import { InternalSpans } from '@mastra/core/observability' @@ -21,6 +10,7 @@ import { getRoleFromContext, } from './request-context' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorAgentPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Translation Agent...') @@ -28,204 +18,84 @@ log.info('Initializing Translation Agent...') * Evaluates whether a translation response includes the translated result, * localization reasoning, and quality-assurance guidance. */ -const translationTaskCompleteScorer = createScorer({ +const translationTaskCompleteScorer = createSupervisorAgentPatternScorer({ id: 'translation-task-complete', name: 'Translation Task Completeness', description: 'Checks whether a translation response covers translation output, cultural adaptation, and review notes.', - type: 'agent', + label: 'translation completeness', + emptyReason: 'No usable translation response was produced.', + weakReason: 'The response is present but still needs translation detail.', + strongReasonPrefix: 'This translation response is strong because', + responseLengthThresholds: [ + { min: 120, weight: 0.15 }, + { min: 250, weight: 0.15 }, + ], + minParagraphsForStructure: 2, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it identifies the source or target language', + regex: /source language|target language|translate|translation/i, + weight: 0.25, + }, + { + label: 'it explains localization or tone choices', + regex: /localization|cultural|tone|audience/i, + weight: 0.2, + }, + { + label: 'it includes QA or proofreading guidance', + regex: /quality|review|qa|proofread|validation/i, + weight: 0.15, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasLanguagePair: - /source language|target language|translate|translation/i.test(responseText), - hasLocalization: - /localization|cultural|tone|audience/i.test(responseText), - hasQA: - /quality|review|qa|proofread|validation/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText) || - responseText.split(/\n\s*\n/).filter(Boolean).length >= 2, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 120) score += 0.15 - if (analysis.responseLength >= 250) score += 0.15 - if (analysis.hasLanguagePair) score += 0.25 - if (analysis.hasLocalization) score += 0.2 - if (analysis.hasQA) score += 0.15 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable translation response was produced.' - } - - if (analysis.hasLanguagePair) parts.push('it identifies the source or target language') - if (analysis.hasLocalization) parts.push('it explains localization or tone choices') - if (analysis.hasQA) parts.push('it includes QA or proofreading guidance') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This translation response is strong because ${parts.join(', ')}.` : 'The response is present but still needs translation detail.'}` - }) /** * Evaluates whether the translation response is delivery-ready with a clear * translated result and notes on localization trade-offs. */ -const translationDeliveryScorer = createScorer({ +const translationDeliveryScorer = createSupervisorAgentPatternScorer({ id: 'translation-delivery-readiness', name: 'Translation Delivery Readiness', description: 'Checks whether a translation response contains a usable translation plus localization notes or alternatives.', - type: 'agent', + label: 'translation delivery', + emptyReason: 'No usable translation delivery response was produced.', + weakReason: 'The response is present but still lacks delivery-ready detail.', + strongReasonPrefix: 'This delivery response is strong because', + responseLengthThresholds: [ + { min: 100, weight: 0.15 }, + { min: 200, weight: 0.1 }, + ], + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, + userMessageWeight: 0.05, + systemMessageWeight: 0.05, + signals: [ + { + label: 'it includes the translated or localized text', + regex: /translation|translated text|localized version|target text/i, + weight: 0.25, + }, + { + label: 'it adds notes about nuance or terminology', + regex: /note|alternative|nuance|idiom|terminology/i, + weight: 0.2, + }, + { + label: 'it reflects the target audience or locale', + regex: /audience|tone|region|locale/i, + weight: 0.15, + }, + ], }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasTranslation: - /translation|translated text|localized version|target text/i.test(responseText), - hasNotes: - /note|alternative|nuance|idiom|terminology/i.test(responseText), - hasLocale: - /audience|tone|region|locale/i.test(responseText), - hasStructure: - /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) { - return 0 - } - - let score = 0 - if (analysis.responseLength >= 100) score += 0.15 - if (analysis.responseLength >= 200) score += 0.1 - if (analysis.hasTranslation) score += 0.25 - if (analysis.hasNotes) score += 0.2 - if (analysis.hasLocale) score += 0.15 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - if (analysis.hasStructure) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - const parts: string[] = [] - - if (!analysis?.hasResponse) { - return 'No usable translation delivery response was produced.' - } - - if (analysis.hasTranslation) parts.push('it includes the translated or localized text') - if (analysis.hasNotes) parts.push('it adds notes about nuance or terminology') - if (analysis.hasLocale) parts.push('it reflects the target audience or locale') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This delivery response is strong because ${parts.join(', ')}.` : 'The response is present but still lacks delivery-ready detail.'}` - }) export const translationAgent = new Agent({ id: 'translation-agent', diff --git a/src/mastra/agents/weather-agent.ts b/src/mastra/agents/weather-agent.ts index a76798c5..9bee10e0 100644 --- a/src/mastra/agents/weather-agent.ts +++ b/src/mastra/agents/weather-agent.ts @@ -1,10 +1,7 @@ import { Agent } from '@mastra/core/agent' -import type { RequestContext } from '@mastra/core/request-context' - import { libsqlChunker,} from '../tools/document-chunking.tool' import { weatherTool } from '../tools/weather-tool' import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' -import { TokenLimiterProcessor } from '@mastra/core/processors' import { InternalSpans } from '@mastra/core/observability' import { mainWorkspace } from '../workspaces' import type { AgentRequestContext } from './request-context' diff --git a/src/mastra/agents/webResearchAgent.ts b/src/mastra/agents/webResearchAgent.ts index cdd33d03..bbf00c4a 100644 --- a/src/mastra/agents/webResearchAgent.ts +++ b/src/mastra/agents/webResearchAgent.ts @@ -1,8 +1,5 @@ import type { GoogleLanguageModelOptions } from '@ai-sdk/google' import { Agent } from '@mastra/core/agent' -import { - TokenLimiterProcessor, -} from '@mastra/core/processors' import type { RequestContext } from '@mastra/core/request-context' import { google } from '../config/google' diff --git a/src/mastra/auth.ts b/src/mastra/auth.ts index 6aaa396a..1dd998ef 100644 --- a/src/mastra/auth.ts +++ b/src/mastra/auth.ts @@ -5,6 +5,7 @@ import { betterAuth, type Auth, type BetterAuthOptions } from 'better-auth' import { admin, multiSession, oAuthProxy, oneTap, username } from 'better-auth/plugins' import { apiKey } from '@better-auth/api-key' import { Kysely, type ColumnType } from 'kysely' +import { log } from './config/logger' type AuthDateColumn = ColumnType; type AuthNullableDateColumn = ColumnType< @@ -111,19 +112,52 @@ interface BetterAuthDatabase { } const isDevelopment = process.env.NODE_ENV !== 'production' -const githubClientId = process.env.GITHUB_CLIENT_ID?.trim() -const githubClientSecret = process.env.GITHUB_CLIENT_SECRET?.trim() + +function trimTrailingSlash(url: string): string { + return url.replace(/\/+$/, '') +} + +/** + * Normalizes legacy OAuth callback env values onto Better Auth's default + * Next.js callback route so older local env files do not break Google sign-in. + */ +function resolveGoogleRedirectUri(baseUrl: string): string { + const configuredRedirectUri = process.env.GOOGLE_CLIENT_CALLBACK_URL?.trim() + const defaultRedirectUri = `${trimTrailingSlash(baseUrl)}/api/auth/callback/google` + + if (!configuredRedirectUri) { + return defaultRedirectUri + } + + if (/\/api\/callback\/?$/.test(configuredRedirectUri)) { + log.warn('Normalizing legacy Google callback URL', { + configuredRedirectUri, + normalizedRedirectUri: defaultRedirectUri, + }) + return defaultRedirectUri + } + + return configuredRedirectUri +} + +const baseURL = + process.env.BETTER_AUTH_URL?.trim() ?? + process.env.NEXT_PUBLIC_BETTER_AUTH_URL?.trim() ?? + (isDevelopment ? 'http://localhost:3000' : undefined) const trustedOrigins = [ - process.env.BETTER_AUTH_TRUSTED_ORIGIN, + process.env.BETTER_AUTH_TRUSTED_ORIGIN?.trim(), + process.env.NEXT_PUBLIC_BETTER_AUTH_URL?.trim(), + baseURL, isDevelopment ? 'http://localhost:3000' : undefined, isDevelopment ? 'http://127.0.0.1:3000' : undefined, -].filter((origin): origin is string => Boolean(origin)) - -const baseURL = - process.env.BETTER_AUTH_URL ?? (isDevelopment ? 'http://localhost:3000' : undefined) +].filter((origin, index, values): origin is string => Boolean(origin) && values.indexOf(origin) === index) const socialProviders: BetterAuthOptions['socialProviders'] = {} +const githubClientId = process.env.GITHUB_CLIENT_ID?.trim() +const githubClientSecret = process.env.GITHUB_CLIENT_SECRET?.trim() +const googleClientId = process.env.GOOGLE_CLIENT_ID?.trim() +const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET?.trim() if (githubClientId && githubClientSecret) { socialProviders.github = { @@ -132,6 +166,14 @@ if (githubClientId && githubClientSecret) { } } +if (googleClientId && googleClientSecret && baseURL) { + socialProviders.google = { + clientId: googleClientId, + clientSecret: googleClientSecret, + redirectURI: resolveGoogleRedirectUri(baseURL), + } +} + const authDatabase = new Kysely({ dialect: new LibsqlDialect({ url: process.env.TURSO_DATABASE_URL ?? process.env.TURSO_URL ?? 'file:./database.db', @@ -176,17 +218,7 @@ const authOptions: BetterAuthOptions = { db: authDatabase, type: 'sqlite', }, - socialProviders: { - github: { - clientId: process.env.GITHUB_CLIENT_ID ?? '', - clientSecret: process.env.GITHUB_CLIENT_SECRET ?? '', - }, - google: { - clientId: process.env.GOOGLE_CLIENT_ID ?? '', - clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '', - redirectURI: process.env.GOOGLE_CLIENT_CALLBACK_URL ?? undefined, - } - }, + socialProviders, baseURL: process.env.BETTER_AUTH_URL ?? 'http://localhost:3000', secret: process.env.BETTER_AUTH_SECRET ?? 'supersecret', plugins: [ @@ -221,4 +253,3 @@ export const mastraAuth = new MastraAuthBetterAuth({ signUpEnabled: true, }) - diff --git a/src/mastra/browsers.ts b/src/mastra/browsers.ts index 1adbae12..e248a7e7 100644 --- a/src/mastra/browsers.ts +++ b/src/mastra/browsers.ts @@ -3,55 +3,367 @@ import { StagehandBrowser } from '@mastra/stagehand' import { log } from './config/logger' +type BrowserScope = 'shared' | 'thread' +type BrowserConnectionMode = 'browserbase' | 'cdp' +type ScreencastFormat = 'jpeg' | 'png' +type StagehandEnvironment = 'LOCAL' | 'BROWSERBASE' + +const DEFAULT_CHROME_CDP_URL = 'http://127.0.0.1:9222' +const DEFAULT_BROWSER_TIMEOUT_MS = 30000 +const DEFAULT_STAGEHAND_DOM_SETTLE_TIMEOUT_MS = 5000 +const DEFAULT_STAGEHAND_MODEL = 'google/gemini-3.1-flash-lite-preview' +const DEFAULT_VIEWPORT_WIDTH = 1440 +const DEFAULT_VIEWPORT_HEIGHT = 900 + +interface BrowserRuntimeProfile { + browserLabel: string + connectionMode: BrowserConnectionMode + scope: BrowserScope + headless: boolean + timeoutMs: number + viewport: { + width: number + height: number + } + screencast: { + format: ScreencastFormat + quality: number + maxWidth: number + maxHeight: number + everyNthFrame: number + } + cdpUrl?: string + environment?: StagehandEnvironment +} + +/** + * Reads the first non-empty environment value from the provided keys. + */ +function readStringEnv( + keys: readonly string[], + fallback?: string +): string | undefined { + for (const key of keys) { + const value = process.env[key]?.trim() + + if (value) { + return value + } + } + + return fallback +} + +/** + * Reads a positive numeric environment variable with a safe fallback. + */ +function readNumberEnv(keys: readonly string[], fallback: number): number { + const rawValue = readStringEnv(keys) + + if (!rawValue) { + return fallback + } + + const parsedValue = Number(rawValue) + return Number.isFinite(parsedValue) && parsedValue > 0 + ? parsedValue + : fallback +} + +/** + * Reads a boolean-like environment variable using common true/false forms. + */ +function readBooleanEnv(keys: readonly string[], fallback: boolean): boolean { + const rawValue = readStringEnv(keys)?.toLowerCase() + + if (!rawValue) { + return fallback + } + + if (['1', 'true', 'yes', 'on'].includes(rawValue)) { + return true + } + + if (['0', 'false', 'no', 'off'].includes(rawValue)) { + return false + } + + return fallback +} + +/** + * Reads the screencast image format while preserving Mastra-supported values. + */ +function readScreencastFormatEnv( + keys: readonly string[], + fallback: ScreencastFormat +): ScreencastFormat { + const rawValue = readStringEnv(keys)?.toLowerCase() + + return rawValue === 'jpeg' || rawValue === 'png' ? rawValue : fallback +} + +/** + * Reads the Stagehand execution environment. + */ +function readStagehandEnvironmentEnv( + keys: readonly string[], + fallback: StagehandEnvironment +): StagehandEnvironment { + const rawValue = readStringEnv(keys)?.toUpperCase() + + return rawValue === 'LOCAL' || rawValue === 'BROWSERBASE' + ? rawValue + : fallback +} + +/** + * Reads the Stagehand verbosity value while preserving supported levels only. + */ +function readStagehandVerboseEnv( + keys: readonly string[], + fallback: 0 | 1 | 2 +): 0 | 1 | 2 { + const parsedValue = readNumberEnv(keys, fallback) + + return parsedValue === 0 || parsedValue === 1 || parsedValue === 2 + ? parsedValue + : fallback +} + +/** + * Resolves the Chrome CDP endpoint used for deterministic and Stagehand + * browser connections. + */ function resolveChromeCdpUrl(): string { return ( - process.env.CHROME_CDP_URL?.trim() ?? - process.env.CHROME_REMOTE_DEBUGGING_URL?.trim() ?? - 'http://127.0.0.1:9222' + readStringEnv( + ['CHROME_CDP_URL', 'CHROME_REMOTE_DEBUGGING_URL'], + DEFAULT_CHROME_CDP_URL + ) ?? DEFAULT_CHROME_CDP_URL ) } -const sharedViewport = { - width: 1440, - height: 900, +/** + * Creates lifecycle hooks that log browser readiness and teardown using the + * same production metadata across browser providers. + */ +function createBrowserLifecycleHooks(profile: BrowserRuntimeProfile) { + const launchTimes = new Map() + + return { + onLaunch: async ({ + browser, + }: { + browser: { + id: string + name: string + provider: string + status: string + headless: boolean + } + }) => { + launchTimes.set(browser.id, Date.now()) + + log.info(`${profile.browserLabel} ready`, { + browserId: browser.id, + browserName: browser.name, + provider: browser.provider, + status: browser.status, + headless: browser.headless, + connectionMode: profile.connectionMode, + environment: profile.environment, + scope: profile.scope, + timeoutMs: profile.timeoutMs, + viewport: profile.viewport, + screencast: profile.screencast, + cdpUrl: profile.cdpUrl, + }) + }, + onClose: async ({ + browser, + }: { + browser: { + id: string + name: string + provider: string + status: string + headless: boolean + } + }) => { + const launchedAt = launchTimes.get(browser.id) + log.info(`${profile.browserLabel} closed`, { + browserId: browser.id, + browserName: browser.name, + provider: browser.provider, + status: browser.status, + headless: browser.headless, + connectionMode: profile.connectionMode, + environment: profile.environment, + scope: profile.scope, + sessionDurationMs: + typeof launchedAt === 'number' ? Date.now() - launchedAt : undefined, + }) + launchTimes.delete(browser.id) + }, + } } +const chromeCdpUrl = resolveChromeCdpUrl() +const sharedViewport = { + width: readNumberEnv( + ['BROWSER_VIEWPORT_WIDTH', 'AGENT_BROWSER_VIEWPORT_WIDTH'], + DEFAULT_VIEWPORT_WIDTH + ), + height: readNumberEnv( + ['BROWSER_VIEWPORT_HEIGHT', 'AGENT_BROWSER_VIEWPORT_HEIGHT'], + DEFAULT_VIEWPORT_HEIGHT + ), +} const sharedScreencast = { - format: 'png' as const, - quality: 80, - maxWidth: 1440, - maxHeight: 900, + format: readScreencastFormatEnv( + ['BROWSER_SCREENCAST_FORMAT'], + 'png' + ), + quality: readNumberEnv(['BROWSER_SCREENCAST_QUALITY'], 80), + maxWidth: readNumberEnv( + ['BROWSER_SCREENCAST_MAX_WIDTH'], + sharedViewport.width + ), + maxHeight: readNumberEnv( + ['BROWSER_SCREENCAST_MAX_HEIGHT'], + sharedViewport.height + ), + everyNthFrame: readNumberEnv(['BROWSER_SCREENCAST_EVERY_NTH_FRAME'], 2), +} + +const agentBrowserScope = 'shared' as const +const stagehandEnvironment = readStagehandEnvironmentEnv( + ['STAGEHAND_ENV'], + 'LOCAL' +) +const stagehandScope = 'shared' as const +const agentBrowserHeadless = readBooleanEnv( + ['AGENT_BROWSER_HEADLESS', 'BROWSER_HEADLESS'], + false +) +const agentBrowserTimeoutMs = readNumberEnv( + ['AGENT_BROWSER_TIMEOUT_MS', 'BROWSER_TIMEOUT_MS'], + DEFAULT_BROWSER_TIMEOUT_MS +) +const stagehandHeadless = readBooleanEnv( + ['STAGEHAND_HEADLESS', 'BROWSER_HEADLESS'], + false +) +const stagehandTimeoutMs = readNumberEnv( + ['STAGEHAND_TIMEOUT_MS', 'BROWSER_TIMEOUT_MS'], + DEFAULT_BROWSER_TIMEOUT_MS +) + +function resolveStagehandConnectionOptions( + environment: StagehandEnvironment, + scope: 'shared', + cdpUrl: string +) { + if (environment === 'BROWSERBASE') { + const apiKey = readStringEnv(['BROWSERBASE_API_KEY']) + const projectId = readStringEnv(['BROWSERBASE_PROJECT_ID']) + + if (!apiKey || !projectId) { + throw new Error( + 'StagehandBrowser requires BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID when STAGEHAND_ENV=BROWSERBASE.' + ) + } + + return { + apiKey, + connectionMode: 'browserbase' as const, + projectId, + scope, + } + } + + return { + cdpUrl, + connectionMode: 'cdp' as const, + scope, + } +} + +const stagehandConnectionOptions = resolveStagehandConnectionOptions( + stagehandEnvironment, + stagehandScope, + chromeCdpUrl +) + +const agentBrowserProfile: BrowserRuntimeProfile = { + browserLabel: 'deterministic-agent-browser', + cdpUrl: chromeCdpUrl, + connectionMode: 'cdp', + headless: agentBrowserHeadless, + scope: agentBrowserScope, + screencast: sharedScreencast, + timeoutMs: agentBrowserTimeoutMs, + viewport: sharedViewport, +} + +const stagehandBrowserProfile: BrowserRuntimeProfile = { + browserLabel: 'stagehand-browser', + cdpUrl: + stagehandConnectionOptions.connectionMode === 'cdp' ? chromeCdpUrl : undefined, + connectionMode: stagehandConnectionOptions.connectionMode, + environment: stagehandEnvironment, + headless: stagehandHeadless, + scope: stagehandScope, + screencast: sharedScreencast, + timeoutMs: stagehandTimeoutMs, + viewport: sharedViewport, +} + +export const browserRuntimeConfig = { + chromeCdpUrl, + sharedScreencast, + sharedViewport, } export const agentBrowser = new AgentBrowser({ - headless: false, + headless: agentBrowserHeadless, viewport: sharedViewport, - timeout: 30000, - cdpUrl: resolveChromeCdpUrl, - scope: 'shared', + timeout: agentBrowserTimeoutMs, + cdpUrl: () => chromeCdpUrl, + scope: agentBrowserScope, screencast: sharedScreencast, - onLaunch: async ({ browser }) => { - log.info('Shared browser connected to Chrome', { - browserName: browser.name, - browserId: browser.id, - }) - }, - onClose: async ({ browser }) => { - log.info('Shared browser disconnected from Chrome', { - browserName: browser.name, - browserId: browser.id, - }) - }, + ...createBrowserLifecycleHooks(agentBrowserProfile), }) -export const stagehand = new StagehandBrowser({ - headless: false, - model: 'google/gemini-3.1-flash-lite-preview', - selfHeal: true, - env: 'LOCAL', - scope: 'shared', - verbose: 2, +export const stagehandBrowser = new StagehandBrowser({ + headless: stagehandHeadless, + model: + readStringEnv(['STAGEHAND_MODEL'], DEFAULT_STAGEHAND_MODEL) ?? + DEFAULT_STAGEHAND_MODEL, + selfHeal: readBooleanEnv(['STAGEHAND_SELF_HEAL'], true), + domSettleTimeout: readNumberEnv( + ['STAGEHAND_DOM_SETTLE_TIMEOUT_MS'], + DEFAULT_STAGEHAND_DOM_SETTLE_TIMEOUT_MS + ), + env: stagehandEnvironment, + verbose: readStagehandVerboseEnv(['STAGEHAND_VERBOSE'], 2), viewport: sharedViewport, + timeout: stagehandTimeoutMs, screencast: sharedScreencast, - systemPrompt: 'You can browse the web using natural language. Use stagehand_act to perform actions like "click the login button". Use stagehand_extract to get data from pages, stagehand_observe Discover actionable elements on a page, stagehand_navigate Navigate to a URL, stagehand_tabs Manage browser tabs, stagehand_close Close the browser' + ...stagehandConnectionOptions, + systemPrompt: `You are a production browser operator for high-signal research and verification. + +Use stagehand_navigate to reach the exact target page, stagehand_observe to discover the actionable surface, stagehand_act for natural-language interactions, and stagehand_extract when the caller needs structured facts pulled from the page. + +Operating rules: +- Prefer the smallest set of navigations and interactions needed to verify the claim. +- Treat visible page text, URLs, titles, timestamps, and state transitions as evidence. +- Use tabs intentionally and close irrelevant tabs when they no longer help the task. +- Do not perform destructive, account-changing, or purchase-like actions unless the caller explicitly asks for them. +- If a page blocks progress, report the blocker clearly instead of guessing. +- When extracting data, keep the schema tight and separate verified facts from unresolved uncertainty.`, + ...createBrowserLifecycleHooks(stagehandBrowserProfile), }) + +export const stagehand = stagehandBrowser diff --git a/src/mastra/config/libsql.ts b/src/mastra/config/libsql.ts index 5924c12b..49b3e366 100644 --- a/src/mastra/config/libsql.ts +++ b/src/mastra/config/libsql.ts @@ -47,7 +47,7 @@ export const LibsqlMemory = new Memory({ logOutputs: true, }, }, - maxParallelCalls: 5, // Limit parallel embedding calls to avoid rate limits + //maxParallelCalls: 10, // Limit parallel embedding calls to avoid rate limits providerOptions: { google: { outputDimensions: 3072, @@ -121,7 +121,8 @@ export const LibsqlMemory = new Memory({ }) log.info('LibSQLStore and Memory initialized with LibSQLVector support', { - url: process.env.TURSO_DATABASE_URL ?? 'file:./vectors.db', + storage: process.env.TURSO_DATABASE_URL ?? 'file:./database.db', + vector: process.env.TURSO_DATABASE_URL ?? 'file:./vectors.db', // schema: process.env.DB_SCHEMA ?? 'mastra', // maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS ?? '20'), memoryOptions: { diff --git a/src/mastra/index.ts b/src/mastra/index.ts index 29165a16..a70debc3 100644 --- a/src/mastra/index.ts +++ b/src/mastra/index.ts @@ -47,6 +47,7 @@ import { researchPaperAgent } from './agents/researchPaperAgent' // Utility Agents import { bgColorAgent } from './agents/bgColorAgent' +import { browserAgent } from './agents/browserAgent' import { calendarAgent } from './agents/calendarAgent' import { noteTakerAgent } from './agents/noteTakerAgent' import { danePackagePublisher } from './agents/package-publisher' @@ -289,6 +290,7 @@ export const mastra = new Mastra({ // Calendar and misc calendarAgent, bgColorAgent, + browserAgent, // Package publisher danePackagePublisher, // Financial Chart Agents diff --git a/src/mastra/networks/AGENTS.md b/src/mastra/networks/AGENTS.md index 90cc5c55..d4931e69 100644 --- a/src/mastra/networks/AGENTS.md +++ b/src/mastra/networks/AGENTS.md @@ -196,7 +196,7 @@ Networks are routing agents that coordinate multiple specialized agents to handl - Preserve context when passing between agents - Log routing decisions for debugging - When a network delegates to child agents, prefer inline `defaultOptions.delegation` hooks in that network file to refine prompts, handle delegation failure feedback, and trim parent message context before handoff. -- Keep network completion checks local to the network file with a network-scoped `createScorer(...)` rather than relying on shared coordinator abstractions. +- Keep network completion checks local to the network file, but prefer building them with shared primitives such as `createSupervisorPatternScorer(...)` from `src/mastra/scorers/supervisor-scorers.ts` so the network keeps auditable local signals without duplicating scorer plumbing. - Prefer multiple local scorers when a network can validly finish through different answer shapes (for example, a broad completeness scorer plus a shorter decision-readiness or execution-readiness scorer). - Add an explicit final-answer contract in long coordinator instructions so the network knows what a user-ready synthesis should look like after delegation. - Do **not** rely on `nestedAgents` adapters or broad parent-side casts when wiring child agents. @@ -208,6 +208,7 @@ Networks are routing agents that coordinate multiple specialized agents to handl | Version | Date (UTC) | Changes | | ------- | ---------- | ------------------------------------------------------------- | +| 2.0.5 | 2026-04-15 | Standardized coordinator networks on local `createSupervisorPatternScorer(...)` wrappers so they keep auditable network-specific signals while sharing the common scorer pipeline. | | 2.0.4 | 2026-03-28 | Added the current standard for dual local completion scorers and explicit final-answer contracts in coordinator networks. | | 2.0.3 | 2026-03-27 | Added the current standard for inline network delegation hooks and network-local completion scorers. | | 2.0.2 | 2026-03-17 | Replaced adapter guidance with the current source-level child-agent typing standard (`unknown` public request context, internal runtime parsing). | diff --git a/src/mastra/networks/businessIntelligenceNetwork.ts b/src/mastra/networks/businessIntelligenceNetwork.ts index c7323684..374c6b50 100644 --- a/src/mastra/networks/businessIntelligenceNetwork.ts +++ b/src/mastra/networks/businessIntelligenceNetwork.ts @@ -1,18 +1,5 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' -import { - TokenLimiterProcessor -} from '@mastra/core/processors' + import { dataIngestionAgent } from '../agents/dataIngestionAgent' import { dataTransformationAgent } from '../agents/dataTransformationAgent' import { evaluationAgent } from '../agents/evaluationAgent' @@ -22,6 +9,7 @@ import { researchAgent } from '../agents/researchAgent' import { stockAnalysisAgent } from '../agents/stockAnalysisAgent' import { log } from '../config/logger' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Business Intelligence Network...') @@ -29,189 +17,79 @@ log.info('Initializing Business Intelligence Network...') * Checks that the business-intelligence network returns actionable BI output * with findings, metrics, or recommendations. */ -const businessIntelligenceNetworkTaskCompleteScorer = createScorer({ - id: 'business-intelligence-network-task-complete', - name: 'Business Intelligence Network Task Completeness', - description: - 'Checks whether the BI network returned concrete analysis, reporting, or visualization guidance.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasBiLanguage: - /kpi|dashboard|analysis|metric|report|forecast|insight|trend|visualization|recommendation/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasDecision: - /decision|business impact|recommendation|priority|next step/i.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.2 - if (analysis.responseLength >= 160) score += 0.1 - if (analysis.hasBiLanguage) score += 0.35 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasDecision) score += 0.1 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable BI response was produced.' - - const parts: string[] = [] - if (analysis.hasBiLanguage) parts.push('it includes BI-specific analysis language') - if (analysis.hasStructure) parts.push('it is structured for reporting') - if (analysis.hasDecision) parts.push('it points toward a decision or next step') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This BI response is strong because ${parts.join(', ')}.` : 'The response is present but still needs analytics detail.'}` +const businessIntelligenceNetworkTaskCompleteScorer = + createSupervisorPatternScorer({ + id: 'business-intelligence-network-task-complete', + name: 'Business Intelligence Network Task Completeness', + description: + 'Checks whether the BI network returned concrete analysis, reporting, or visualization guidance.', + label: 'Business intelligence response', + emptyReason: 'No usable BI response was produced.', + weakReason: 'The response is present but still needs analytics detail.', + strongReasonPrefix: 'This BI response is strong because', + signals: [ + { + label: 'it includes BI-specific analysis language', + regex: + /kpi|dashboard|analysis|metric|report|forecast|insight|trend|visualization|recommendation/i, + weight: 0.35, + }, + { + label: 'it points toward a decision or next step', + regex: /decision|business impact|recommendation|priority|next step/i, + weight: 0.1, + }, + ], + responseLengthThresholds: [ + { min: 80, weight: 0.2 }, + { min: 160, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) /** * Checks that the BI answer is decision-ready with key metrics, findings, and * recommended next actions. */ -const businessIntelligenceNetworkDecisionScorer = createScorer({ - id: 'business-intelligence-network-decision-readiness', - name: 'Business Intelligence Network Decision Readiness', - description: - 'Checks whether the BI response includes metrics, business interpretation, and decision-oriented next steps.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasKpi: /kpi|metric|dashboard|forecast|trend/i.test(responseText), - hasDecision: - /business impact|decision|recommend|opportunity|risk/i.test(responseText), - hasAction: /next step|investigate|monitor|act/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 160) score += 0.2 - if (analysis.responseLength >= 260) score += 0.1 - if (analysis.hasKpi) score += 0.25 - if (analysis.hasDecision) score += 0.2 - if (analysis.hasAction) score += 0.15 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable BI decision response was produced.' - - const parts: string[] = [] - if (analysis.hasKpi) parts.push('it includes KPI or trend language') - if (analysis.hasDecision) parts.push('it frames a business decision or risk') - if (analysis.hasAction) parts.push('it suggests next actions') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This BI decision response is strong because ${parts.join(', ')}.` : 'The response is present but still needs decision-ready detail.'}` +const businessIntelligenceNetworkDecisionScorer = + createSupervisorPatternScorer({ + id: 'business-intelligence-network-decision-readiness', + name: 'Business Intelligence Network Decision Readiness', + description: + 'Checks whether the BI response includes metrics, business interpretation, and decision-oriented next steps.', + label: 'Business intelligence decision response', + emptyReason: 'No usable BI decision response was produced.', + weakReason: + 'The response is present but still needs decision-ready detail.', + strongReasonPrefix: 'This BI decision response is strong because', + signals: [ + { + label: 'it includes KPI or trend language', + regex: /kpi|metric|dashboard|forecast|trend/i, + weight: 0.25, + }, + { + label: 'it frames a business decision or risk', + regex: /business impact|decision|recommend|opportunity|risk/i, + weight: 0.2, + }, + { + label: 'it suggests next actions', + regex: /next step|investigate|monitor|act/i, + weight: 0.15, + }, + ], + responseLengthThresholds: [ + { min: 160, weight: 0.2 }, + { min: 260, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) export const businessIntelligenceNetwork = new Agent({ @@ -387,14 +265,6 @@ export const businessIntelligenceNetwork = new Agent({ }, options: {}, // tools: { confirmationTool }, - outputProcessors: [ - new TokenLimiterProcessor(128000), - // new BatchPartsProcessor({ - // batchSize: 20, - // maxWaitTime: 100, - // emitOnNonText: true, - // }), - ], defaultOptions: { maxSteps: 20, delegation: { diff --git a/src/mastra/networks/codingTeamNetwork.ts b/src/mastra/networks/codingTeamNetwork.ts index e310ddd8..d69b2781 100644 --- a/src/mastra/networks/codingTeamNetwork.ts +++ b/src/mastra/networks/codingTeamNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import type { Processor, ProcessorMessageResult, @@ -31,6 +20,7 @@ import { repoIngestionWorkflow } from '../workflows/repo-ingestion-workflow' import { researchSynthesisWorkflow } from '../workflows/research-synthesis-workflow' import { specGenerationWorkflow } from '../workflows/spec-generation-workflow' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Coding Team Network...') @@ -38,187 +28,72 @@ log.info('Initializing Coding Team Network...') * Checks that the coding network returns a concrete engineering deliverable, * review, or plan instead of only stating that specialists are available. */ -const codingTeamNetworkTaskCompleteScorer = createScorer({ +const codingTeamNetworkTaskCompleteScorer = createSupervisorPatternScorer({ id: 'coding-team-network-task-complete', name: 'Coding Team Network Task Completeness', description: 'Checks whether the coding network returned actionable engineering guidance or output.', - type: 'agent', + label: 'Coding team response', + emptyReason: 'No usable engineering response was produced.', + weakReason: 'The response is present but still needs engineering depth.', + strongReasonPrefix: 'This coding response is strong because', + signals: [ + { + label: 'it includes engineering-specific guidance', + regex: + /architecture|review|test|refactor|implementation|risk|trade-off|code|plan/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [ + { min: 80, weight: 0.2 }, + { min: 160, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasEngineeringLanguage: - /architecture|review|test|refactor|implementation|risk|trade-off|code|plan/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.2 - if (analysis.responseLength >= 160) score += 0.1 - if (analysis.hasEngineeringLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable engineering response was produced.' - - const parts: string[] = [] - if (analysis.hasEngineeringLanguage) parts.push('it includes engineering-specific guidance') - if (analysis.hasStructure) parts.push('it is structured and actionable') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This coding response is strong because ${parts.join(', ')}.` : 'The response is present but still needs engineering depth.'}` - }) /** * Checks that the coding-team answer is execution-ready with priorities, * engineering rationale, and concrete next actions. */ -const codingTeamNetworkExecutionScorer = createScorer({ +const codingTeamNetworkExecutionScorer = createSupervisorPatternScorer({ id: 'coding-team-network-execution-readiness', name: 'Coding Team Network Execution Readiness', description: 'Checks whether the coding-team response includes implementation order, validation guidance, or risk-aware next steps.', - type: 'agent', + label: 'Coding team execution response', + emptyReason: 'No usable execution-ready coding plan was produced.', + weakReason: 'The response is present but still lacks execution detail.', + strongReasonPrefix: 'This execution plan is strong because', + signals: [ + { + label: 'it clarifies ordering or phases', + regex: /priority|sequence|phase|first|then/i, + weight: 0.25, + }, + { + label: 'it names risks or constraints', + regex: /trade-off|risk|assumption|constraint/i, + weight: 0.2, + }, + { + label: 'it includes validation or next-step guidance', + regex: /test|validate|review|next step|implement/i, + weight: 0.2, + }, + ], + responseLengthThresholds: [ + { min: 160, weight: 0.2 }, + { min: 280, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasPriority: /priority|sequence|phase|first|then/i.test(responseText), - hasRisk: - /trade-off|risk|assumption|constraint/i.test(responseText), - hasValidation: - /test|validate|review|next step|implement/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 160) score += 0.2 - if (analysis.responseLength >= 280) score += 0.1 - if (analysis.hasPriority) score += 0.25 - if (analysis.hasRisk) score += 0.2 - if (analysis.hasValidation) score += 0.2 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable execution-ready coding plan was produced.' - - const parts: string[] = [] - if (analysis.hasPriority) parts.push('it clarifies ordering or phases') - if (analysis.hasRisk) parts.push('it names risks or constraints') - if (analysis.hasValidation) parts.push('it includes validation or next-step guidance') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution plan is strong because ${parts.join(', ')}.` : 'The response is present but still lacks execution detail.'}` - }) export class QualityChecker implements Processor { id = 'quality-checker' diff --git a/src/mastra/networks/contentCreationNetwork.ts b/src/mastra/networks/contentCreationNetwork.ts index 41a4a6a5..18ce03e5 100644 --- a/src/mastra/networks/contentCreationNetwork.ts +++ b/src/mastra/networks/contentCreationNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { contentStrategistAgent } from '../agents/contentStrategistAgent' import { copywriterAgent } from '../agents/copywriterAgent' @@ -18,6 +7,7 @@ import { evaluationAgent } from '../agents/evaluationAgent' import { scriptWriterAgent } from '../agents/scriptWriterAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { contentReviewWorkflow } from '../workflows/content-review-workflow' import { contentStudioWorkflow } from '../workflows/content-studio-workflow' @@ -29,187 +19,73 @@ log.info('Initializing Content Creation Network...') * Checks that the content-creation network produces a usable content deliverable * or a concrete editorial plan. */ -const contentCreationNetworkTaskCompleteScorer = createScorer({ - id: 'content-creation-network-task-complete', - name: 'Content Creation Network Task Completeness', - description: - 'Checks whether the network returned a substantial draft, edit, strategy, or quality review.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasContentLanguage: - /headline|audience|draft|edit|tone|script|cta|content|strategy|quality/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 70) score += 0.2 - if (analysis.responseLength >= 140) score += 0.1 - if (analysis.hasContentLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable content creation response was produced.' - - const parts: string[] = [] - if (analysis.hasContentLanguage) parts.push('it includes content or editorial language') - if (analysis.hasStructure) parts.push('it is structured for handoff') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This content response is strong because ${parts.join(', ')}.` : 'The response is present but still needs creative detail.'}` +const contentCreationNetworkTaskCompleteScorer = + createSupervisorPatternScorer({ + id: 'content-creation-network-task-complete', + name: 'Content Creation Network Task Completeness', + description: + 'Checks whether the network returned a substantial draft, edit, strategy, or quality review.', + label: 'Content creation response', + emptyReason: 'No usable content creation response was produced.', + weakReason: 'The response is present but still needs creative detail.', + strongReasonPrefix: 'This content response is strong because', + signals: [ + { + label: 'it includes content or editorial language', + regex: + /headline|audience|draft|edit|tone|script|cta|content|strategy|quality/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [ + { min: 70, weight: 0.2 }, + { min: 140, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) /** * Checks that the content-creation answer is delivery-ready with a draft, * editorial direction, or clear revision guidance. */ -const contentCreationNetworkDeliveryScorer = createScorer({ - id: 'content-creation-network-delivery-readiness', - name: 'Content Creation Network Delivery Readiness', - description: - 'Checks whether the content response is ready to publish, revise, or hand off to the next editorial step.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasDraft: - /draft|outline|headline|script|copy/i.test(responseText), - hasVoice: - /tone|audience|positioning|voice/i.test(responseText), - hasDelivery: - /next step|revise|publish|review|qa/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 140) score += 0.2 - if (analysis.responseLength >= 240) score += 0.1 - if (analysis.hasDraft) score += 0.25 - if (analysis.hasVoice) score += 0.2 - if (analysis.hasDelivery) score += 0.2 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable content delivery response was produced.' - - const parts: string[] = [] - if (analysis.hasDraft) parts.push('it includes draft or outline language') - if (analysis.hasVoice) parts.push('it addresses tone, audience, or positioning') - if (analysis.hasDelivery) parts.push('it includes delivery or revision guidance') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This delivery response is strong because ${parts.join(', ')}.` : 'The response is present but still needs handoff detail.'}` +const contentCreationNetworkDeliveryScorer = + createSupervisorPatternScorer({ + id: 'content-creation-network-delivery-readiness', + name: 'Content Creation Network Delivery Readiness', + description: + 'Checks whether the content response is ready to publish, revise, or hand off to the next editorial step.', + label: 'Content delivery response', + emptyReason: 'No usable content delivery response was produced.', + weakReason: 'The response is present but still needs handoff detail.', + strongReasonPrefix: 'This delivery response is strong because', + signals: [ + { + label: 'it includes draft or outline language', + regex: /draft|outline|headline|script|copy/i, + weight: 0.25, + }, + { + label: 'it addresses tone, audience, or positioning', + regex: /tone|audience|positioning|voice/i, + weight: 0.2, + }, + { + label: 'it includes delivery or revision guidance', + regex: /next step|revise|publish|review|qa/i, + weight: 0.2, + }, + ], + responseLengthThresholds: [ + { min: 140, weight: 0.2 }, + { min: 240, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) export const contentCreationNetwork = new Agent({ diff --git a/src/mastra/networks/dataPipelineNetwork.ts b/src/mastra/networks/dataPipelineNetwork.ts index 80610190..d8083dec 100644 --- a/src/mastra/networks/dataPipelineNetwork.ts +++ b/src/mastra/networks/dataPipelineNetwork.ts @@ -1,21 +1,11 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { dataExportAgent } from '../agents/dataExportAgent' import { dataIngestionAgent } from '../agents/dataIngestionAgent' import { dataTransformationAgent } from '../agents/dataTransformationAgent' import { reportAgent } from '../agents/reportAgent' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { confirmationTool } from '../tools/confirmation.tool' import { stockAnalysisWorkflow } from '../workflows/stock-analysis-workflow' import { LibsqlMemory } from '../config/libsql' @@ -26,151 +16,63 @@ log.info('Initializing Data Pipeline Network...') * Validates that the data pipeline network returns a concrete pipeline outcome * instead of only describing a potential handoff. */ -const dataPipelineNetworkTaskCompleteScorer = createScorer({ +const dataPipelineNetworkTaskCompleteScorer = createSupervisorPatternScorer({ id: 'data-pipeline-network-task-complete', name: 'Data Pipeline Network Task Completeness', description: 'Checks whether the network returned a concrete import, transform, export, or reporting outcome.', - type: 'agent', + label: 'Data pipeline response', + emptyReason: 'No usable data pipeline response was produced.', + weakReason: 'The response is present but still needs more report detail.', + strongReasonPrefix: 'This pipeline response is strong because', + signals: [ + { + label: 'it includes pipeline or transformation language', + regex: /csv|json|xml|transform|schema|columns|rows|report|summary|file/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [{ min: 60, weight: 0.25 }], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { response, responseMessages, reasoning, tools, toolCallInfos } = - results.preprocessStepResult - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasResponse: responseText.length > 0, - responseLength: responseText.length, - hasPipelineLanguage: - /csv|json|xml|transform|schema|columns|rows|report|summary|file/i.test( - responseText - ), - hasStructure: /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 60) score += 0.25 - if (analysis.hasPipelineLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable data pipeline response was produced.' - - const parts: string[] = [] - if (analysis.hasPipelineLanguage) parts.push('it includes pipeline or transformation language') - if (analysis.hasStructure) parts.push('it is structured and readable') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This pipeline response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more report detail.'}` - }) /** * Checks that the data pipeline answer is execution-ready with explicit output, * validation, or next-step guidance. */ -const dataPipelineNetworkExecutionScorer = createScorer({ - id: 'data-pipeline-network-execution-readiness', - name: 'Data Pipeline Network Execution Readiness', - description: - 'Checks whether the data-pipeline response tells the user what was produced, what changed, and what to do next.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { response, responseMessages, reasoning, tools, toolCallInfos } = - results.preprocessStepResult - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasResponse: responseText.length > 0, - responseLength: responseText.length, - hasPipelineLanguage: - /output|result|generated|exported|transformed/i.test(responseText), - categoryMatches: [ - /validation|mismatch|missing|error|warning/i.test(responseText), - /next step|download|import|review|fix/i.test(responseText), - ].filter(Boolean).length, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 120) score += 0.25 - if (analysis.hasPipelineLanguage) score += 0.35 - if (analysis.categoryMatches >= 2) score += 0.2 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable pipeline execution result was produced.' - - const parts: string[] = [] - if (analysis.hasPipelineLanguage) parts.push('it includes pipeline or transformation language') - if (analysis.categoryMatches >= 2) parts.push('it covers validation and follow-up actions') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more execution detail.'}` +const dataPipelineNetworkExecutionScorer = + createSupervisorPatternScorer({ + id: 'data-pipeline-network-execution-readiness', + name: 'Data Pipeline Network Execution Readiness', + description: + 'Checks whether the data-pipeline response tells the user what was produced, what changed, and what to do next.', + label: 'Data pipeline execution response', + emptyReason: 'No usable pipeline execution result was produced.', + weakReason: 'The response is present but still needs more execution detail.', + strongReasonPrefix: 'This execution response is strong because', + signals: [ + { + label: 'it includes pipeline or transformation language', + regex: /output|result|generated|exported|transformed/i, + weight: 0.35, + }, + { + label: 'it covers validation detail', + regex: /validation|mismatch|missing|error|warning/i, + weight: 0.1, + }, + { + label: 'it covers follow-up actions', + regex: /next step|download|import|review|fix/i, + weight: 0.1, + }, + ], + responseLengthThresholds: [{ min: 120, weight: 0.25 }], + reasoningWeight: 0.05, + toolWeight: 0.05, }) export const dataPipelineNetwork = new Agent({ diff --git a/src/mastra/networks/devopsNetwork.ts b/src/mastra/networks/devopsNetwork.ts index af4e7795..2ef85690 100644 --- a/src/mastra/networks/devopsNetwork.ts +++ b/src/mastra/networks/devopsNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { codeArchitectAgent, codeReviewerAgent, @@ -20,6 +9,7 @@ import { evaluationAgent } from '../agents/evaluationAgent' import { danePackagePublisher } from '../agents/package-publisher' import { projectManagementAgent } from '../agents/projectManagementAgent' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { confirmationTool } from '../tools/confirmation.tool' import { LibsqlMemory } from '../config/libsql' log.info('Initializing DevOps Network...') @@ -28,192 +18,77 @@ log.info('Initializing DevOps Network...') * Checks that the DevOps network returns a deployable, testable, or operable * recommendation set instead of generic platform advice. */ -const devopsNetworkTaskCompleteScorer = createScorer({ +const devopsNetworkTaskCompleteScorer = createSupervisorPatternScorer({ id: 'devops-network-task-complete', name: 'DevOps Network Task Completeness', description: 'Checks whether the DevOps network returned a concrete delivery, deployment, or operations result.', - type: 'agent', + label: 'DevOps response', + emptyReason: 'No usable DevOps response was produced.', + weakReason: 'The response is present but still needs delivery detail.', + strongReasonPrefix: 'This DevOps response is strong because', + signals: [ + { + label: 'it includes DevOps-specific guidance', + regex: + /deploy|pipeline|release|monitor|infrastructure|ci\/cd|incident|rollback|test|package/i, + weight: 0.35, + }, + { + label: 'it includes operational or rollout detail', + regex: /monitor|rollback|incident|deploy|release|rollback|health|validation/i, + weight: 0.1, + }, + ], + responseLengthThresholds: [ + { min: 80, weight: 0.2 }, + { min: 160, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasDevopsLanguage: - /deploy|pipeline|release|monitor|infrastructure|ci\/cd|incident|rollback|test|package/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasOps: - /monitor|rollback|incident|deploy|release|rollback|health|validation/i.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.2 - if (analysis.responseLength >= 160) score += 0.1 - if (analysis.hasDevopsLanguage) score += 0.35 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasOps) score += 0.1 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable DevOps response was produced.' - - const parts: string[] = [] - if (analysis.hasDevopsLanguage) parts.push('it includes DevOps-specific guidance') - if (analysis.hasStructure) parts.push('it is structured for execution') - if (analysis.hasOps) parts.push('it includes operational or rollout detail') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This DevOps response is strong because ${parts.join(', ')}.` : 'The response is present but still needs delivery detail.'}` - }) /** * Checks that the DevOps answer is operationally actionable with rollout, * validation, and risk-management guidance. */ -const devopsNetworkExecutionScorer = createScorer({ +const devopsNetworkExecutionScorer = createSupervisorPatternScorer({ id: 'devops-network-execution-readiness', name: 'DevOps Network Execution Readiness', description: 'Checks whether the DevOps response includes rollout steps, validation gates, and operational risk guidance.', - type: 'agent', + label: 'DevOps execution response', + emptyReason: 'No usable DevOps execution response was produced.', + weakReason: 'The response is present but still lacks operational detail.', + strongReasonPrefix: 'This execution plan is strong because', + signals: [ + { + label: 'it includes deployment or rollout guidance', + regex: /deploy|release|rollout|pipeline|gate/i, + weight: 0.25, + }, + { + label: 'it includes validation gates or checks', + regex: /monitor|verify|smoke test|rollback|incident/i, + weight: 0.2, + }, + { + label: 'it names owners, milestones, or risks', + regex: /next step|owner|milestone|risk/i, + weight: 0.2, + }, + ], + responseLengthThresholds: [ + { min: 160, weight: 0.2 }, + { min: 280, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasDeploy: - /deploy|release|rollout|pipeline|gate/i.test(responseText), - hasValidation: - /monitor|verify|smoke test|rollback|incident/i.test(responseText), - hasRisk: - /next step|owner|milestone|risk/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 160) score += 0.2 - if (analysis.responseLength >= 280) score += 0.1 - if (analysis.hasDeploy) score += 0.25 - if (analysis.hasValidation) score += 0.2 - if (analysis.hasRisk) score += 0.2 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable DevOps execution response was produced.' - - const parts: string[] = [] - if (analysis.hasDeploy) parts.push('it includes deployment or rollout guidance') - if (analysis.hasValidation) parts.push('it includes validation gates or checks') - if (analysis.hasRisk) parts.push('it names owners, milestones, or risks') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution plan is strong because ${parts.join(', ')}.` : 'The response is present but still lacks operational detail.'}` - }) export const devopsNetwork = new Agent({ id: 'devops-network', diff --git a/src/mastra/networks/financialIntelligenceNetwork.ts b/src/mastra/networks/financialIntelligenceNetwork.ts index f19d0311..8dd3920f 100644 --- a/src/mastra/networks/financialIntelligenceNetwork.ts +++ b/src/mastra/networks/financialIntelligenceNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { chartDataProcessorAgent, chartGeneratorAgent, @@ -21,6 +10,7 @@ import { researchAgent } from '../agents/researchAgent' import { stockAnalysisAgent } from '../agents/stockAnalysisAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { financialReportWorkflow } from '../workflows/financial-report-workflow' import { stockAnalysisWorkflow } from '../workflows/stock-analysis-workflow' import { LibsqlMemory } from '../config/libsql' @@ -31,100 +21,66 @@ log.info('Initializing Financial Intelligence Network...') * Checks that the financial-intelligence network returns actionable market * analysis, chart guidance, or reporting output. */ -const financialIntelligenceNetworkTaskCompleteScorer = createScorer({ - id: 'financial-intelligence-network-task-complete', - name: 'Financial Intelligence Network Task Completeness', - description: - 'Checks whether the network returned a substantive financial analysis or report.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { response, responseMessages, reasoning, tools, toolCallInfos } = - results.preprocessStepResult - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasResponse: responseText.length > 0, - responseLength: responseText.length, - hasFinanceLanguage: - /price|valuation|risk|chart|market|financial|report|trend|portfolio|disclaimer/i.test( - responseText - ), - hasStructure: /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.3 - if (analysis.hasFinanceLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable financial intelligence response was produced.' - - const parts: string[] = [] - if (analysis.hasFinanceLanguage) parts.push('it includes financial analysis language') - if (analysis.hasStructure) parts.push('it is structured for review') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This financial response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more financial detail.'}` +const financialIntelligenceNetworkTaskCompleteScorer = + createSupervisorPatternScorer({ + id: 'financial-intelligence-network-task-complete', + name: 'Financial Intelligence Network Task Completeness', + description: + 'Checks whether the network returned a substantive financial analysis or report.', + label: 'Financial intelligence response', + emptyReason: 'No usable financial intelligence response was produced.', + weakReason: + 'The response is present but still needs more financial detail.', + strongReasonPrefix: 'This financial response is strong because', + signals: [ + { + label: 'it includes financial analysis language', + regex: + /price|valuation|risk|chart|market|financial|report|trend|portfolio|disclaimer/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [{ min: 80, weight: 0.3 }], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) /** * Checks that the financial-intelligence answer is investor-ready with thesis, * risk framing, and a clear next action or caveat. */ -const financialIntelligenceNetworkDecisionScorer = createScorer({ - id: 'financial-intelligence-network-decision-readiness', - name: 'Financial Intelligence Network Decision Readiness', - description: - 'Checks whether the financial response includes thesis, risks, and decision-ready follow-up guidance.', - type: 'agent', -}).generateScore(async context => { - const normalizedText = ( - getAssistantMessageFromRunOutput(context.run.output) ?? - String(context.run.output ?? '') - ).trim() - const categoryMatches = [ - /thesis|outlook|valuation|trend|support|resistance/i.test(normalizedText), - /risk|downside|volatility|uncertain|disclaimer/i.test(normalizedText), - /next step|watch|monitor|consider|review/i.test(normalizedText), - ].filter(Boolean).length - - return normalizedText.length >= 160 && categoryMatches >= 2 ? 1 : 0 -}) +const financialIntelligenceNetworkDecisionScorer = + createSupervisorPatternScorer({ + id: 'financial-intelligence-network-decision-readiness', + name: 'Financial Intelligence Network Decision Readiness', + description: + 'Checks whether the financial response includes thesis, risks, and decision-ready follow-up guidance.', + label: 'Financial decision response', + emptyReason: 'No investor-ready financial response was produced.', + weakReason: + 'The response is present but still needs clearer thesis, risk framing, or follow-up guidance.', + strongReasonPrefix: 'This financial decision response is strong because', + signals: [ + { + label: 'it includes an investment thesis or outlook', + regex: /thesis|outlook|valuation|trend|support|resistance/i, + weight: 0.25, + }, + { + label: 'it frames risks or uncertainty', + regex: /risk|downside|volatility|uncertain|disclaimer/i, + weight: 0.25, + }, + { + label: 'it includes a clear follow-up action', + regex: /next step|watch|monitor|consider|review/i, + weight: 0.25, + }, + ], + responseLengthThresholds: [{ min: 160, weight: 0.2 }], + }) export const financialIntelligenceNetwork = new Agent({ id: 'financial-intelligence-network', diff --git a/src/mastra/networks/index.ts b/src/mastra/networks/index.ts index 5efd59f8..1623dbe6 100644 --- a/src/mastra/networks/index.ts +++ b/src/mastra/networks/index.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { copywriterAgent } from '../agents/copywriterAgent' import { editorAgent } from '../agents/editorAgent' import { reportAgent } from '../agents/reportAgent' @@ -27,6 +16,7 @@ import { translationAgent } from '../agents/translationAgent' import { LibsqlMemory } from '../config/libsql' import { googleAI } from '../config/google' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { weatherWorkflow } from '../workflows/weather-workflow' // CSV/Data Pipeline Networks @@ -68,101 +58,67 @@ import { * Checks that the primary network returns a useful routed answer instead of * stopping at a vague handoff explanation. */ -const agentNetworkTaskCompleteScorer = createScorer({ +const agentNetworkTaskCompleteScorer = createSupervisorPatternScorer({ id: 'primary-network-task-complete', name: 'Primary Network Task Completeness', description: 'Checks whether the primary network returned a concrete answer or actionable routed result.', - type: 'agent', + label: 'Primary network response', + emptyReason: 'No usable routed answer was produced.', + weakReason: 'The response is present but still needs more routing detail.', + strongReasonPrefix: 'This primary network response is strong because', + signals: [ + { + label: 'it includes useful routing language', + regex: + /recommend|summary|analysis|report|plan|translation|support|seo|next step/i, + weight: 0.3, + }, + { + label: 'it reads like a complete routed answer', + regex: /(?:[^.!?]+[.!?]){2,}/s, + weight: 0.2, + }, + ], + responseLengthThresholds: [{ min: 40, weight: 0.2 }], + minParagraphsForStructure: 999, + structureWeight: 0.1, + reasoningWeight: 0.05, + toolWeight: 0.05, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { response, responseMessages, reasoning, tools, toolCallInfos } = - results.preprocessStepResult - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasResponse: responseText.length > 0, - responseLength: responseText.length, - hasUsefulRouting: - /recommend|summary|analysis|report|plan|translation|support|seo|next step/i.test( - responseText - ), - sentenceCount: responseText.match(/[^.!?]+/g)?.length ?? 0, - hasStructure: /^#{1,6}\s|^[-*]\s|^\d+\.\s/m.test(responseText), - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 40) score += 0.2 - if (analysis.sentenceCount >= 2) score += 0.2 - if (analysis.hasUsefulRouting) score += 0.3 - if (analysis.hasStructure) score += 0.1 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable routed answer was produced.' - - const parts: string[] = [] - if (analysis.hasUsefulRouting) parts.push('it includes useful routing language') - if (analysis.hasStructure) parts.push('it is structured and readable') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This primary network response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more routing detail.'}` - }) /** * Checks that the primary network answer is user-ready, concise, and ends with * a clear resolution path or next action. */ -const agentNetworkResolutionScorer = createScorer({ +const agentNetworkResolutionScorer = createSupervisorPatternScorer({ id: 'primary-network-resolution-readiness', name: 'Primary Network Resolution Readiness', description: 'Checks whether the primary network returned a direct answer with clear next steps or decision guidance.', - type: 'agent', -}).generateScore(async context => { - const normalizedText = ( - getAssistantMessageFromRunOutput(context.run.output) ?? - String(context.run.output ?? '') - ).trim() - const categoryMatches = [ - /recommend|suggest|best option|answer/i.test(normalizedText), - /next step|follow-up|if needed|you can/i.test(normalizedText), - /because|based on|given that/i.test(normalizedText), - ].filter(Boolean).length - - return normalizedText.length >= 120 && categoryMatches >= 2 ? 1 : 0 + label: 'Primary network resolution', + emptyReason: 'No resolution-ready response was produced.', + weakReason: + 'The response is present but still needs clearer next steps or decision guidance.', + strongReasonPrefix: 'This primary network resolution is strong because', + signals: [ + { + label: 'it includes a direct answer or recommendation', + regex: /recommend|suggest|best option|answer/i, + weight: 0.25, + }, + { + label: 'it includes next-step guidance', + regex: /next step|follow-up|if needed|you can/i, + weight: 0.25, + }, + { + label: 'it explains the reasoning behind the recommendation', + regex: /because|based on|given that/i, + weight: 0.25, + }, + ], + responseLengthThresholds: [{ min: 120, weight: 0.2 }], }) export const agentNetwork = new Agent({ diff --git a/src/mastra/networks/learningNetwork.ts b/src/mastra/networks/learningNetwork.ts index bdc039cc..1a427545 100644 --- a/src/mastra/networks/learningNetwork.ts +++ b/src/mastra/networks/learningNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { TokenLimiterProcessor } from '@mastra/core/processors' @@ -20,6 +9,7 @@ import { learningExtractionAgent } from '../agents/learningExtractionAgent' import { researchAgent } from '../agents/researchAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { learningExtractionWorkflow } from '../workflows/learning-extraction-workflow' import { researchSynthesisWorkflow } from '../workflows/research-synthesis-workflow' import { LibsqlMemory } from '../config/libsql' @@ -30,188 +20,73 @@ log.info('Initializing Learning Network...') * Checks that the learning network returns actionable learning outcomes, * knowledge-organization guidance, or research-backed educational output. */ -const learningNetworkTaskCompleteScorer = createScorer({ +const learningNetworkTaskCompleteScorer = createSupervisorPatternScorer({ id: 'learning-network-task-complete', name: 'Learning Network Task Completeness', description: 'Checks whether the learning network returned concrete learnings, indexed knowledge guidance, or educational recommendations.', - type: 'agent', + label: 'Learning response', + emptyReason: 'No usable learning network response was produced.', + weakReason: 'The response is present but still needs educational detail.', + strongReasonPrefix: 'This learning response is strong because', + signals: [ + { + label: 'it includes learning or knowledge language', + regex: + /learning|knowledge|insight|objective|resource|curriculum|index|research|assessment/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [ + { min: 70, weight: 0.2 }, + { min: 140, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasLearningLanguage: - /learning|knowledge|insight|objective|resource|curriculum|index|research|assessment/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 70) score += 0.2 - if (analysis.responseLength >= 140) score += 0.1 - if (analysis.hasLearningLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable learning network response was produced.' - - const parts: string[] = [] - if (analysis.hasLearningLanguage) parts.push('it includes learning or knowledge language') - if (analysis.hasStructure) parts.push('it is structured for handoff') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This learning response is strong because ${parts.join(', ')}.` : 'The response is present but still needs educational detail.'}` - }) /** * Checks that the learning answer is instructionally useful with outcomes, * structure, and a recommended next study step. */ -const learningNetworkOutcomeScorer = createScorer({ +const learningNetworkOutcomeScorer = createSupervisorPatternScorer({ id: 'learning-network-outcome-readiness', name: 'Learning Network Outcome Readiness', description: 'Checks whether the learning response includes practical takeaways, learning structure, and next-study guidance.', - type: 'agent', + label: 'Learning outcome response', + emptyReason: 'No usable learning outcome response was produced.', + weakReason: + 'The response is present but still needs clearer learning direction.', + strongReasonPrefix: 'This learning outcome response is strong because', + signals: [ + { + label: 'it includes practical takeaways', + regex: /takeaway|learning|objective|insight|concept/i, + weight: 0.25, + }, + { + label: 'it lays out a learning sequence or structure', + regex: /step|sequence|curriculum|resource|practice/i, + weight: 0.2, + }, + { + label: 'it suggests the next study action', + regex: /next step|study next|review|apply/i, + weight: 0.2, + }, + ], + responseLengthThresholds: [ + { min: 140, weight: 0.2 }, + { min: 240, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasTakeaway: - /takeaway|learning|objective|insight|concept/i.test(responseText), - hasSequence: - /step|sequence|curriculum|resource|practice/i.test(responseText), - hasNextStep: - /next step|study next|review|apply/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 140) score += 0.2 - if (analysis.responseLength >= 240) score += 0.1 - if (analysis.hasTakeaway) score += 0.25 - if (analysis.hasSequence) score += 0.2 - if (analysis.hasNextStep) score += 0.2 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable learning outcome response was produced.' - - const parts: string[] = [] - if (analysis.hasTakeaway) parts.push('it includes practical takeaways') - if (analysis.hasSequence) parts.push('it lays out a learning sequence or structure') - if (analysis.hasNextStep) parts.push('it suggests the next study action') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This learning outcome response is strong because ${parts.join(', ')}.` : 'The response is present but still needs clearer learning direction.'}` - }) export const learningNetwork = new Agent({ id: 'learning-network', diff --git a/src/mastra/networks/marketingAutomationNetwork.ts b/src/mastra/networks/marketingAutomationNetwork.ts index 180f34ec..dcd4f823 100644 --- a/src/mastra/networks/marketingAutomationNetwork.ts +++ b/src/mastra/networks/marketingAutomationNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { contentStrategistAgent } from '../agents/contentStrategistAgent' import { copywriterAgent } from '../agents/copywriterAgent' import { researchAgent } from '../agents/researchAgent' @@ -19,6 +8,7 @@ import { translationAgent } from '../agents/translationAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Marketing Automation Network...') @@ -26,190 +16,79 @@ log.info('Initializing Marketing Automation Network...') * Checks that the marketing network returns a campaign-ready deliverable rather * than only describing possible marketing work. */ -const marketingAutomationNetworkTaskCompleteScorer = createScorer({ - id: 'marketing-automation-network-task-complete', - name: 'Marketing Automation Network Task Completeness', - description: - 'Checks whether the marketing network returned a concrete strategy, campaign asset, or optimization plan.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasMarketingLanguage: - /campaign|audience|channel|seo|social|conversion|cta|timeline|kpi|localization/i.test( - responseText - ), - hasStructure: /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasStrategy: - /strategy|plan|brief|calendar|workflow|optimi[sz]e/i.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.2 - if (analysis.responseLength >= 160) score += 0.1 - if (analysis.hasMarketingLanguage) score += 0.35 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasStrategy) score += 0.1 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable marketing automation response was produced.' - - const parts: string[] = [] - if (analysis.hasMarketingLanguage) parts.push('it includes campaign or channel guidance') - if (analysis.hasStructure) parts.push('it is structured for execution') - if (analysis.hasStrategy) parts.push('it includes strategy or planning detail') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This marketing response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more concrete marketing detail.'}` +const marketingAutomationNetworkTaskCompleteScorer = + createSupervisorPatternScorer({ + id: 'marketing-automation-network-task-complete', + name: 'Marketing Automation Network Task Completeness', + description: + 'Checks whether the marketing network returned a concrete strategy, campaign asset, or optimization plan.', + label: 'Marketing automation response', + emptyReason: 'No usable marketing automation response was produced.', + weakReason: + 'The response is present but still needs more concrete marketing detail.', + strongReasonPrefix: 'This marketing response is strong because', + signals: [ + { + label: 'it includes campaign or channel guidance', + regex: + /campaign|audience|channel|seo|social|conversion|cta|timeline|kpi|localization/i, + weight: 0.35, + }, + { + label: 'it includes strategy or planning detail', + regex: /strategy|plan|brief|calendar|workflow|optimi[sz]e/i, + weight: 0.1, + }, + ], + responseLengthThresholds: [ + { min: 80, weight: 0.2 }, + { min: 160, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) /** * Checks that the marketing answer is campaign-ready with priorities, * measurement, and a channel execution path. */ -const marketingAutomationNetworkExecutionScorer = createScorer({ - id: 'marketing-automation-network-execution-readiness', - name: 'Marketing Automation Network Execution Readiness', - description: - 'Checks whether the marketing answer includes execution sequencing, KPIs, and channel-ready next actions.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasExecution: - /priority|phase|timeline|sequence|launch/i.test(responseText), - hasMetrics: - /kpi|metric|conversion|engagement|roi/i.test(responseText), - hasNextStep: - /next step|rollout|test|optimi[sz]e|publish/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 160) score += 0.2 - if (analysis.responseLength >= 260) score += 0.1 - if (analysis.hasExecution) score += 0.25 - if (analysis.hasMetrics) score += 0.2 - if (analysis.hasNextStep) score += 0.15 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable marketing execution response was produced.' - - const parts: string[] = [] - if (analysis.hasExecution) parts.push('it provides sequencing or launch guidance') - if (analysis.hasMetrics) parts.push('it ties work to metrics') - if (analysis.hasNextStep) parts.push('it includes concrete next actions') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution plan is strong because ${parts.join(', ')}.` : 'The response is present but still needs execution detail.'}` +const marketingAutomationNetworkExecutionScorer = + createSupervisorPatternScorer({ + id: 'marketing-automation-network-execution-readiness', + name: 'Marketing Automation Network Execution Readiness', + description: + 'Checks whether the marketing answer includes execution sequencing, KPIs, and channel-ready next actions.', + label: 'Marketing execution response', + emptyReason: 'No usable marketing execution response was produced.', + weakReason: 'The response is present but still needs execution detail.', + strongReasonPrefix: 'This execution plan is strong because', + signals: [ + { + label: 'it provides sequencing or launch guidance', + regex: /priority|phase|timeline|sequence|launch/i, + weight: 0.25, + }, + { + label: 'it ties work to metrics', + regex: /kpi|metric|conversion|engagement|roi/i, + weight: 0.2, + }, + { + label: 'it includes concrete next actions', + regex: /next step|rollout|test|optimi[sz]e|publish/i, + weight: 0.15, + }, + ], + responseLengthThresholds: [ + { min: 160, weight: 0.2 }, + { min: 260, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) export const marketingAutomationNetwork = new Agent({ diff --git a/src/mastra/networks/reportGenerationNetwork.ts b/src/mastra/networks/reportGenerationNetwork.ts index 9bae6c90..38eb7369 100644 --- a/src/mastra/networks/reportGenerationNetwork.ts +++ b/src/mastra/networks/reportGenerationNetwork.ts @@ -1,25 +1,11 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' - -import { - TokenLimiterProcessor -} from '@mastra/core/processors' import { dataIngestionAgent } from '../agents/dataIngestionAgent' import { dataTransformationAgent } from '../agents/dataTransformationAgent' import { reportAgent } from '../agents/reportAgent' import { researchAgent } from '../agents/researchAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { financialReportWorkflow } from '../workflows/financial-report-workflow' import { learningExtractionWorkflow } from '../workflows/learning-extraction-workflow' import { researchSynthesisWorkflow } from '../workflows/research-synthesis-workflow' @@ -32,185 +18,73 @@ log.info('Initializing Report Generation Network...') * Checks whether the report-generation coordinator returned a concrete report * artifact, synthesis, or actionable report plan. */ -const reportGenerationNetworkTaskCompleteScorer = createScorer({ - id: 'report-generation-network-task-complete', - name: 'Report Generation Network Task Completeness', - description: - 'Checks whether the network returned a structured report outcome with findings or next actions.', - type: 'agent', -}).generateScore(async context => { - const normalizedText = ( - getAssistantMessageFromRunOutput(context.run.output) ?? - String(context.run.output ?? '') - ).trim() - const hasReportLanguage = - /report|summary|findings|sources|analysis|recommendation|executive/i.test( - normalizedText - ) - const paragraphCount = normalizedText - .split(/\n\s*\n/) - .filter(Boolean).length - - return normalizedText.length >= 80 && (hasReportLanguage || paragraphCount >= 2) - ? 1 - : 0 -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { response, responseMessages, reasoning, tools, toolCallInfos } = - results.preprocessStepResult - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasResponse: responseText.length > 0, - responseLength: responseText.length, - hasReportLanguage: - /report|summary|findings|sources|analysis|recommendation|executive/i.test( - responseText - ), - paragraphCount: responseText.split(/\n\s*\n/).filter(Boolean).length, - hasStructure: /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.25 - if (analysis.hasReportLanguage) score += 0.35 - if (analysis.paragraphCount >= 2) score += 0.15 - if (analysis.hasStructure) score += 0.1 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable report-generation response was produced.' - - const parts: string[] = [] - if (analysis.hasReportLanguage) parts.push('it includes report or synthesis language') - if (analysis.hasStructure) parts.push('it is structured and readable') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This report response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more report detail.'}` +const reportGenerationNetworkTaskCompleteScorer = + createSupervisorPatternScorer({ + id: 'report-generation-network-task-complete', + name: 'Report Generation Network Task Completeness', + description: + 'Checks whether the network returned a structured report outcome with findings or next actions.', + label: 'Report generation response', + emptyReason: 'No usable report-generation response was produced.', + weakReason: 'The response is present but still needs more report detail.', + strongReasonPrefix: 'This report response is strong because', + signals: [ + { + label: 'it includes report or synthesis language', + regex: + /report|summary|findings|sources|analysis|recommendation|executive/i, + weight: 0.35, + }, + { + label: 'it spans multiple report sections', + regex: /\n\s*\n/, + weight: 0.15, + }, + ], + responseLengthThresholds: [{ min: 80, weight: 0.25 }], + minParagraphsForStructure: 999, + structureWeight: 0.1, + reasoningWeight: 0.05, + toolWeight: 0.05, }) /** * Checks that the report-generation answer is synthesis-ready with findings, * evidence framing, and clear follow-up guidance. */ -const reportGenerationNetworkSynthesisScorer = createScorer({ - id: 'report-generation-network-synthesis-readiness', - name: 'Report Generation Network Synthesis Readiness', - description: - 'Checks whether the report answer contains findings, supporting rationale, and next actions.', - type: 'agent', -}).generateScore(async context => { - const normalizedText = ( - getAssistantMessageFromRunOutput(context.run.output) ?? - String(context.run.output ?? '') - ).trim() - const categoryMatches = [ - /finding|insight|summary|executive/i.test(normalizedText), - /source|evidence|data|based on/i.test(normalizedText), - /next step|recommend|follow-up|decision/i.test(normalizedText), - ].filter(Boolean).length - - return normalizedText.length >= 150 && categoryMatches >= 2 ? 1 : 0 -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { response, responseMessages, reasoning, tools, toolCallInfos } = - results.preprocessStepResult - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasResponse: responseText.length > 0, - responseLength: responseText.length, - hasReportLanguage: - /finding|insight|summary|executive/i.test(responseText), - hasEvidence: - /source|evidence|data|based on/i.test(responseText), - hasNextAction: - /next step|recommend|follow-up|decision/i.test(responseText), - hasStructure: /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 150) score += 0.25 - if (analysis.hasReportLanguage) score += 0.25 - if (analysis.hasEvidence) score += 0.2 - if (analysis.hasNextAction) score += 0.15 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable pipeline execution result was produced.' - - const parts: string[] = [] - if (analysis.hasReportLanguage) parts.push('it communicates report-ready output') - if (analysis.hasEvidence) parts.push('it includes evidence or data grounding') - if (analysis.hasNextAction) parts.push('it includes clear next actions') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This execution response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more execution detail.'}` +const reportGenerationNetworkSynthesisScorer = + createSupervisorPatternScorer({ + id: 'report-generation-network-synthesis-readiness', + name: 'Report Generation Network Synthesis Readiness', + description: + 'Checks whether the report answer contains findings, supporting rationale, and next actions.', + label: 'Report synthesis response', + emptyReason: 'No usable report synthesis response was produced.', + weakReason: + 'The response is present but still needs findings, evidence, or next-action detail.', + strongReasonPrefix: 'This report synthesis response is strong because', + signals: [ + { + label: 'it communicates report-ready output', + regex: /finding|insight|summary|executive/i, + weight: 0.25, + }, + { + label: 'it includes evidence or data grounding', + regex: /source|evidence|data|based on/i, + weight: 0.2, + }, + { + label: 'it includes clear next actions', + regex: /next step|recommend|follow-up|decision/i, + weight: 0.15, + }, + ], + responseLengthThresholds: [{ min: 150, weight: 0.25 }], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.05, + toolWeight: 0.05, }) export const reportGenerationNetwork = new Agent({ @@ -336,14 +210,6 @@ export const reportGenerationNetwork = new Agent({ researchSynthesisWorkflow, learningExtractionWorkflow, }, - outputProcessors: [ - new TokenLimiterProcessor(128000), - // new BatchPartsProcessor({ - // batchSize: 20, - // maxWaitTime: 100, - // emitOnNonText: true, - // }), - ], defaultOptions: { maxSteps: 20, delegation: { diff --git a/src/mastra/networks/researchPipelineNetwork.ts b/src/mastra/networks/researchPipelineNetwork.ts index b2449769..82433484 100644 --- a/src/mastra/networks/researchPipelineNetwork.ts +++ b/src/mastra/networks/researchPipelineNetwork.ts @@ -1,24 +1,11 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' -import { - TokenLimiterProcessor -} from '@mastra/core/processors' import { documentProcessingAgent } from '../agents/documentProcessingAgent' import { knowledgeIndexingAgent } from '../agents/knowledgeIndexingAgent' import { researchAgent } from '../agents/researchAgent' import { researchPaperAgent } from '../agents/researchPaperAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' import { contentReviewWorkflow } from '../workflows/content-review-workflow' import { documentProcessingWorkflow } from '../workflows/document-processing-workflow' import { LibsqlMemory } from '../config/libsql' @@ -29,187 +16,74 @@ log.info('Initializing Research Pipeline Network...') * Checks that the research pipeline returns a concrete discovery, indexing, * retrieval, or synthesis outcome. */ -const researchPipelineNetworkTaskCompleteScorer = createScorer({ - id: 'research-pipeline-network-task-complete', - name: 'Research Pipeline Network Task Completeness', - description: - 'Checks whether the research pipeline returned a substantive research or indexing result.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasResearchLanguage: - /paper|arxiv|index|chunk|retriev|knowledge|citation|source|synthesis/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.2 - if (analysis.responseLength >= 160) score += 0.1 - if (analysis.hasResearchLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable research pipeline response was produced.' - - const parts: string[] = [] - if (analysis.hasResearchLanguage) parts.push('it includes research or indexing language') - if (analysis.hasStructure) parts.push('it is structured for handoff') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This research response is strong because ${parts.join(', ')}.` : 'The response is present but still lacks pipeline detail.'}` +const researchPipelineNetworkTaskCompleteScorer = + createSupervisorPatternScorer({ + id: 'research-pipeline-network-task-complete', + name: 'Research Pipeline Network Task Completeness', + description: + 'Checks whether the research pipeline returned a substantive research or indexing result.', + label: 'Research pipeline response', + emptyReason: 'No usable research pipeline response was produced.', + weakReason: 'The response is present but still lacks pipeline detail.', + strongReasonPrefix: 'This research response is strong because', + signals: [ + { + label: 'it includes research or indexing language', + regex: + /paper|arxiv|index|chunk|retriev|knowledge|citation|source|synthesis/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [ + { min: 80, weight: 0.2 }, + { min: 160, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) /** * Checks that the research-pipeline answer communicates concrete discovery, * indexing, or retrieval value plus the next research step. */ -const researchPipelineNetworkOutcomeScorer = createScorer({ - id: 'research-pipeline-network-outcome-readiness', - name: 'Research Pipeline Network Outcome Readiness', - description: - 'Checks whether the research-pipeline response communicates usable findings, artifacts, or next retrieval actions.', - type: 'agent', -}) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasArtifacts: - /paper|document|chunk|index|knowledge base/i.test(responseText), - hasRetrieval: - /retriev|query|search|result|citation/i.test(responseText), - hasNextStep: - /next step|index next|query next|review|refine/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 150) score += 0.2 - if (analysis.responseLength >= 260) score += 0.1 - if (analysis.hasArtifacts) score += 0.25 - if (analysis.hasRetrieval) score += 0.2 - if (analysis.hasNextStep) score += 0.15 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable research outcome response was produced.' - - const parts: string[] = [] - if (analysis.hasArtifacts) parts.push('it references papers, documents, or indexed artifacts') - if (analysis.hasRetrieval) parts.push('it includes retrieval or citation context') - if (analysis.hasNextStep) parts.push('it gives a next retrieval or review step') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This outcome response is strong because ${parts.join(', ')}.` : 'The response is present but still needs more actionable research detail.'}` +const researchPipelineNetworkOutcomeScorer = + createSupervisorPatternScorer({ + id: 'research-pipeline-network-outcome-readiness', + name: 'Research Pipeline Network Outcome Readiness', + description: + 'Checks whether the research-pipeline response communicates usable findings, artifacts, or next retrieval actions.', + label: 'Research pipeline outcome response', + emptyReason: 'No usable research outcome response was produced.', + weakReason: + 'The response is present but still needs more actionable research detail.', + strongReasonPrefix: 'This outcome response is strong because', + signals: [ + { + label: 'it references papers, documents, or indexed artifacts', + regex: /paper|document|chunk|index|knowledge base/i, + weight: 0.25, + }, + { + label: 'it includes retrieval or citation context', + regex: /retriev|query|search|result|citation/i, + weight: 0.2, + }, + { + label: 'it gives a next retrieval or review step', + regex: /next step|index next|query next|review|refine/i, + weight: 0.15, + }, + ], + responseLengthThresholds: [ + { min: 150, weight: 0.2 }, + { min: 260, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) /** @@ -350,14 +224,6 @@ Use for: building research knowledge bases, literature reviews, indexing academi }, // tools: { confirmationTool }, options: {}, - outputProcessors: [ - new TokenLimiterProcessor(128000), - // new BatchPartsProcessor({ - // batchSize: 20, - // maxWaitTime: 100, - // emitOnNonText: true, - // }), - ], defaultOptions: { maxSteps: 22, delegation: { diff --git a/src/mastra/networks/securityNetwork.ts b/src/mastra/networks/securityNetwork.ts index d169b731..d32961fa 100644 --- a/src/mastra/networks/securityNetwork.ts +++ b/src/mastra/networks/securityNetwork.ts @@ -1,15 +1,4 @@ import { Agent } from '@mastra/core/agent' -import { createScorer } from '@mastra/core/evals' -import { - extractAgentResponseMessages, - extractInputMessages, - extractToolCalls, - getAssistantMessageFromRunOutput, - getCombinedSystemPrompt, - getReasoningFromRunOutput, - getSystemMessagesFromRunInput, - getUserMessageFromRunInput, -} from '@mastra/evals/scorers/utils' import { codeReviewerAgent } from '../agents/codingAgents' import { evaluationAgent } from '../agents/evaluationAgent' import { reportAgent } from '../agents/reportAgent' @@ -17,6 +6,7 @@ import { researchAgent } from '../agents/researchAgent' import { googleAI3 } from '../config/google' import { log } from '../config/logger' import { LibsqlMemory } from '../config/libsql' +import { createSupervisorPatternScorer } from '../scorers/supervisor-scorers' log.info('Initializing Security Network...') @@ -24,188 +14,73 @@ log.info('Initializing Security Network...') * Checks that the security network returns a concrete assessment, mitigation, * or reporting outcome instead of only generic security posture language. */ -const securityNetworkTaskCompleteScorer = createScorer({ +const securityNetworkTaskCompleteScorer = createSupervisorPatternScorer({ id: 'security-network-task-complete', name: 'Security Network Task Completeness', description: 'Checks whether the security network returned actionable security findings or mitigation guidance.', - type: 'agent', + label: 'Security response', + emptyReason: 'No usable security response was produced.', + weakReason: 'The response is present but still needs mitigation detail.', + strongReasonPrefix: 'This security response is strong because', + signals: [ + { + label: 'it includes security or risk language', + regex: + /security|vulnerability|risk|mitigation|compliance|incident|control|assessment|finding/i, + weight: 0.4, + }, + ], + responseLengthThresholds: [ + { min: 80, weight: 0.2 }, + { min: 160, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.15, + reasoningWeight: 0.05, + toolWeight: 0.05, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasSecurityLanguage: - /security|vulnerability|risk|mitigation|compliance|incident|control|assessment|finding/i.test( - responseText - ), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 80) score += 0.2 - if (analysis.responseLength >= 160) score += 0.1 - if (analysis.hasSecurityLanguage) score += 0.4 - if (analysis.hasStructure) score += 0.15 - if (analysis.hasReasoning) score += 0.05 - if (analysis.toolCount > 0) score += 0.05 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable security response was produced.' - - const parts: string[] = [] - if (analysis.hasSecurityLanguage) parts.push('it includes security or risk language') - if (analysis.hasStructure) parts.push('it is structured for handoff') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This security response is strong because ${parts.join(', ')}.` : 'The response is present but still needs mitigation detail.'}` - }) /** * Checks that the security answer is remediation-ready with priority, impact, * and next mitigation actions. */ -const securityNetworkRemediationScorer = createScorer({ +const securityNetworkRemediationScorer = createSupervisorPatternScorer({ id: 'security-network-remediation-readiness', name: 'Security Network Remediation Readiness', description: 'Checks whether the security response includes severity, mitigation, and follow-up guidance.', - type: 'agent', + label: 'Security remediation response', + emptyReason: 'No usable security remediation response was produced.', + weakReason: + 'The response is present but still needs concrete remediation detail.', + strongReasonPrefix: 'This remediation response is strong because', + signals: [ + { + label: 'it classifies severity or priority', + regex: /critical|high|medium|low|severity|priority/i, + weight: 0.2, + }, + { + label: 'it includes mitigation or remediation guidance', + regex: /mitigation|fix|control|remediation|contain/i, + weight: 0.25, + }, + { + label: 'it includes follow-up or monitoring steps', + regex: /next step|owner|monitor|validate|follow-up/i, + weight: 0.2, + }, + ], + responseLengthThresholds: [ + { min: 160, weight: 0.2 }, + { min: 280, weight: 0.1 }, + ], + minParagraphsForStructure: 999, + structureWeight: 0.05, + reasoningWeight: 0.03, + toolWeight: 0.02, }) - .preprocess(({ run }) => { - const userMessage = getUserMessageFromRunInput(run.input) - const inputMessages = extractInputMessages(run.input) - const systemMessages = getSystemMessagesFromRunInput(run.input) - const systemPrompt = getCombinedSystemPrompt(run.input) - const response = getAssistantMessageFromRunOutput(run.output) - const responseMessages = extractAgentResponseMessages(run.output) - const reasoning = getReasoningFromRunOutput(run.output) - const { tools, toolCallInfos } = extractToolCalls(run.output) - - return { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } - }) - .analyze(({ results }) => { - const { - userMessage, - inputMessages, - systemMessages, - systemPrompt, - response, - responseMessages, - reasoning, - tools, - toolCallInfos, - } = results.preprocessStepResult - - const responseText = (response ?? responseMessages.join('\n')).trim() - - return { - hasUserMessage: Boolean(userMessage), - inputMessageCount: inputMessages.length, - systemMessageCount: systemMessages.length, - systemPromptLength: systemPrompt.length, - responseLength: responseText.length, - hasResponse: responseText.length > 0, - hasReasoning: Boolean(reasoning), - toolCount: tools.length, - toolCallCount: toolCallInfos.length, - hasSeverity: - /critical|high|medium|low|severity|priority/i.test(responseText), - hasMitigation: - /mitigation|fix|control|remediation|contain/i.test(responseText), - hasFollowUp: - /next step|owner|monitor|validate|follow-up/i.test(responseText), - hasStructure: - /^[-*]\s|^\d+\.\s|^#{1,6}\s/m.test(responseText), - } - }) - .generateScore(({ results }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 0 - - let score = 0 - if (analysis.responseLength >= 160) score += 0.2 - if (analysis.responseLength >= 280) score += 0.1 - if (analysis.hasSeverity) score += 0.2 - if (analysis.hasMitigation) score += 0.25 - if (analysis.hasFollowUp) score += 0.2 - if (analysis.hasStructure) score += 0.05 - if (analysis.hasReasoning) score += 0.03 - if (analysis.toolCount > 0) score += 0.02 - - return Math.max(0, Math.min(1, score)) - }) - .generateReason(({ results, score }) => { - const analysis = results.analyzeStepResult - if (!analysis?.hasResponse) return 'No usable security remediation response was produced.' - - const parts: string[] = [] - if (analysis.hasSeverity) parts.push('it classifies severity or priority') - if (analysis.hasMitigation) parts.push('it includes mitigation or remediation guidance') - if (analysis.hasFollowUp) parts.push('it includes follow-up or monitoring steps') - - return `Score: ${score.toFixed(2)}. ${parts.length > 0 ? `This remediation response is strong because ${parts.join(', ')}.` : 'The response is present but still needs concrete remediation detail.'}` - }) export const securityNetwork = new Agent({ id: 'security-network', diff --git a/src/mastra/processors/output-guardrails.ts b/src/mastra/processors/output-guardrails.ts new file mode 100644 index 00000000..362b5551 --- /dev/null +++ b/src/mastra/processors/output-guardrails.ts @@ -0,0 +1,52 @@ +import { createWorkflow, createStep } from '@mastra/core/workflows' +import { + ProcessorStepSchema, + PIIDetector, + ModerationProcessor, + SystemPromptScrubber, + TokenLimiterProcessor, + BatchPartsProcessor, +} from '@mastra/core/processors' + +export const outputGuardrails = createWorkflow({ + id: 'output-guardrails', + inputSchema: ProcessorStepSchema, + outputSchema: ProcessorStepSchema, + type: 'processor' +}) + // Sequential: limit tokens first, then batch stream chunks + .then(createStep(new TokenLimiterProcessor({ limit: 256000 }))) + .then(createStep(new BatchPartsProcessor( + { batchSize: 10, emitOnNonText: false }, + + ))) + // Parallel: run independent checks at the same time + .parallel([ + createStep( + new PIIDetector({ + strategy: 'redact', + model: 'openrouter/google/gemma-4-31b-it:free', + }), + ), + createStep( + new ModerationProcessor({ + strategy: 'block', + model: 'openrouter/google/gemma-4-31b-it:free', + }), + ), + ]) + // Map to the redact branch to keep its transformed messages + .map(async ({ inputData }) => { + return inputData['processor:pii-detector'] + }) + // Sequential: scrubber depends on previous redaction output + .then( + createStep( + new SystemPromptScrubber({ + strategy: 'redact', + placeholderText: '[REDACTED]', + model: 'openrouter/google/gemma-4-31b-it:free', + }), + ), + ) + .commit() \ No newline at end of file diff --git a/src/mastra/scorers/supervisor-scorers.ts b/src/mastra/scorers/supervisor-scorers.ts index c57de9dd..0602a2e5 100644 --- a/src/mastra/scorers/supervisor-scorers.ts +++ b/src/mastra/scorers/supervisor-scorers.ts @@ -40,6 +40,69 @@ interface SupervisorSignals extends SupervisorSnapshot { keyTermCoverage: number } +interface SupervisorPatternSignal { + label: string + regex: RegExp + weight: number +} + +interface SupervisorPatternPenalty { + label: string + regex: RegExp + weight: number +} + +interface SupervisorPatternThreshold { + min: number + weight: number +} + +interface SupervisorPatternScorerOptions { + id: string + name: string + description: string + label: string + emptyReason: string + weakReason: string + strongReasonPrefix: string + signals: SupervisorPatternSignal[] + penaltySignals?: SupervisorPatternPenalty[] + responseLengthThresholds?: SupervisorPatternThreshold[] + minParagraphsForStructure?: number + structureWeight?: number + reasoningWeight?: number + toolWeight?: number + toolFallbackWeight?: number + userMessageWeight?: number + systemMessageWeight?: number +} + +interface SupervisorPatternAnalysis { + hasUserMessage: boolean + systemMessageCount: number + responseLength: number + hasResponse: boolean + hasReasoning: boolean + toolCount: number + hasStructure: boolean + matchedSignals: string[] + matchedPenaltySignals: string[] +} + +interface SupervisorAgentPatternScorerOptions + extends Omit { + signals?: SupervisorPatternSignal[] + penaltySignals?: SupervisorPatternPenalty[] +} + +interface SupervisorStructuredOutputPatternScorerOptions + extends Omit { + requiredFields: string[] + includeMarkdownFencePenalty?: boolean + signals?: SupervisorPatternSignal[] + penaltySignals?: SupervisorPatternPenalty[] +} + const STOPWORDS = new Set([ 'a', 'an', @@ -234,6 +297,255 @@ function generateSupervisorReason( return `Score: ${score.toFixed(2)}. ${label} is strong because ${details.join(', ')}.` } +/** + * Builds a reusable scorer for supervisor/coordinator responses that can stay local + * to each agent or network file while sharing the same preprocessing and scoring core. + */ +export function createSupervisorPatternScorer( + options: SupervisorPatternScorerOptions +) { + return createScorer({ + id: options.id, + name: options.name, + description: options.description, + type: 'agent', + }) + .preprocess(({ run }) => buildSupervisorSnapshot(run)) + .analyze(({ results }) => { + const snapshot = results.preprocessStepResult + const responseText = snapshot.responseText + const minParagraphsForStructure = options.minParagraphsForStructure ?? 2 + const matchedSignals = options.signals + .filter((signal) => signal.regex.test(responseText)) + .map((signal) => signal.label) + const matchedPenaltySignals = (options.penaltySignals ?? []) + .filter((signal) => signal.regex.test(responseText)) + .map((signal) => signal.label) + + return { + hasUserMessage: snapshot.userMessage.length > 0, + systemMessageCount: snapshot.systemMessageCount, + responseLength: responseText.length, + hasResponse: responseText.length > 0, + hasReasoning: snapshot.reasoningText.length > 0, + toolCount: snapshot.toolCount, + hasStructure: + STRUCTURE_REGEX.test(responseText) || + snapshot.paragraphCount >= minParagraphsForStructure, + matchedSignals, + matchedPenaltySignals, + } satisfies SupervisorPatternAnalysis + }) + .generateScore(({ results }) => { + const analysis = results.analyzeStepResult as + | SupervisorPatternAnalysis + | undefined + + if (!analysis?.hasResponse) { + return 0 + } + + let score = 0 + + if (analysis.hasUserMessage) { + score += options.userMessageWeight ?? 0 + } + + if (analysis.systemMessageCount > 0) { + score += options.systemMessageWeight ?? 0 + } + + for (const threshold of options.responseLengthThresholds ?? []) { + if (analysis.responseLength >= threshold.min) { + score += threshold.weight + } + } + + for (const signal of options.signals) { + if ( + analysis.matchedSignals.some( + (matchedSignal) => matchedSignal === signal.label + ) + ) { + score += signal.weight + } + } + + for (const signal of options.penaltySignals ?? []) { + if ( + analysis.matchedPenaltySignals.some( + (matchedSignal) => matchedSignal === signal.label + ) + ) { + score -= signal.weight + } + } + + if (analysis.hasStructure) { + score += options.structureWeight ?? 0 + } + + if (analysis.hasReasoning) { + score += options.reasoningWeight ?? 0 + } + + if (analysis.toolCount > 0) { + score += options.toolWeight ?? 0 + } else { + score += options.toolFallbackWeight ?? 0 + } + + return clamp(score) + }) + .generateReason(({ results, score }) => { + const analysis = results.analyzeStepResult as + | SupervisorPatternAnalysis + | undefined + + if (!analysis?.hasResponse) { + return options.emptyReason + } + + const details = [...analysis.matchedSignals] + + if (analysis.hasStructure && (options.structureWeight ?? 0) > 0) { + details.push('it is structured for execution') + } + + if (analysis.hasReasoning && (options.reasoningWeight ?? 0) > 0) { + details.push('it includes reasoning support') + } + + if (analysis.toolCount > 0 && (options.toolWeight ?? 0) > 0) { + details.push( + `it used ${analysis.toolCount} delegation signal(s)` + ) + } + + if (analysis.matchedPenaltySignals.length > 0) { + details.push( + `it should still improve ${analysis.matchedPenaltySignals.join( + ', ' + )}` + ) + } + + if (details.length === 0) { + return `Score: ${score.toFixed(2)}. ${options.weakReason}` + } + + return `Score: ${score.toFixed(2)}. ${options.strongReasonPrefix} ${details.join(', ')}.` + }) +} + +const SUPERVISOR_AGENT_DEFAULT_SIGNALS: SupervisorPatternSignal[] = [ + { + label: 'it opens with a direct summary or answer', + regex: SUMMARY_REGEX, + weight: 0.05, + }, + { + label: 'it includes evidence anchors or dated support', + regex: EVIDENCE_REGEX, + weight: 0.05, + }, + { + label: 'it includes next steps or follow-up guidance', + regex: NEXT_STEPS_REGEX, + weight: 0.05, + }, +] + +const SUPERVISOR_AGENT_DEFAULT_PENALTIES: SupervisorPatternPenalty[] = [ + { + label: 'raw routing chatter', + regex: ROUTING_CHATTER_REGEX, + weight: 0.1, + }, +] + +const SUPERVISOR_CHANNEL_DEFAULT_SIGNALS: SupervisorPatternSignal[] = [ + { + label: 'it stays concise enough for a public channel reply', + regex: /summary|quick take|top line|recommend/i, + weight: 0.05, + }, + { + label: 'it assigns a next action, owner, or follow-up', + regex: /owner|assignee|follow-up|next step|action item/i, + weight: 0.05, + }, +] + +/** + * Builds a supervisor-agent-specific scorer layer that preserves local domain + * signals while adding the shared answer-quality expectations we want across + * user-facing supervisor agents. + */ +export function createSupervisorAgentPatternScorer( + options: SupervisorAgentPatternScorerOptions +) { + return createSupervisorPatternScorer({ + ...options, + signals: [...SUPERVISOR_AGENT_DEFAULT_SIGNALS, ...(options.signals ?? [])], + penaltySignals: [ + ...SUPERVISOR_AGENT_DEFAULT_PENALTIES, + ...(options.penaltySignals ?? []), + ], + }) +} + +/** + * Builds a channel-oriented supervisor scorer for supervisors that need + * short, public, action-oriented replies on platforms such as Discord, Slack, + * or GitHub issues and pull requests. + */ +export function createSupervisorChannelPatternScorer( + options: SupervisorAgentPatternScorerOptions +) { + return createSupervisorAgentPatternScorer({ + ...options, + signals: [...SUPERVISOR_CHANNEL_DEFAULT_SIGNALS, ...(options.signals ?? [])], + }) +} + +/** + * Builds a structured-output-oriented supervisor scorer for supervisors that + * are expected to return stable fields or JSON-like payloads instead of only + * natural-language prose. + */ +export function createStructuredOutputSupervisorPatternScorer( + options: SupervisorStructuredOutputPatternScorerOptions +) { + const fieldSignals: SupervisorPatternSignal[] = options.requiredFields.map( + (fieldName) => ({ + label: `it includes the structured field "${fieldName}"`, + regex: new RegExp(`["'\`]?${fieldName}["'\`]?\\s*:`, 'i'), + weight: 0.05, + }) + ) + + const structuredPenaltySignals: SupervisorPatternPenalty[] = + options.includeMarkdownFencePenalty === false + ? [] + : [ + { + label: 'markdown fences around structured output', + regex: /```(?:json|yaml)?/i, + weight: 0.1, + }, + ] + + return createSupervisorAgentPatternScorer({ + ...options, + signals: [...fieldSignals, ...(options.signals ?? [])], + penaltySignals: [ + ...structuredPenaltySignals, + ...(options.penaltySignals ?? []), + ], + }) +} + /** * Measures whether the supervisor response stays user-facing, avoids raw routing chatter, * and presents a synthesized answer rather than exposing delegation mechanics.