diff --git a/.gitignore b/.gitignore index 4e7c0dc..71bcc3b 100644 --- a/.gitignore +++ b/.gitignore @@ -226,3 +226,8 @@ start-dev.js .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 +src/mastra/public/workspace/swingtimer.lua +.playwright-mcp/page-2026-04-16T00-19-52-354Z.yml +.playwright-mcp/*.yml +src/mastra/public/workspace/swingtimer.md +src/mastra/public/workspace/tbc-shaman.md diff --git a/app/chat/AGENTS.md b/app/chat/AGENTS.md index f418304..de250b6 100644 --- a/app/chat/AGENTS.md +++ b/app/chat/AGENTS.md @@ -4,6 +4,12 @@ ## Recent Update (2026-04-15) +- The main chat shell no longer relies on `app/chat/config/agents.ts` / `app/chat/config/models.ts` for the active agent/model presentation layer: + - `app/chat/lib/runtime-chat-catalog.ts` now derives chat UI metadata from live Mastra agent payloads. + - `app/chat/providers/chat-context.tsx` now builds `agentConfig` from `useAgent(selectedAgent)`. + - `app/chat/components/chat-header.tsx` now groups agents from `useAgents()` and models from `useAgentModelProviders()`. + - `app/chat/components/chat-sidebar.tsx` now exposes runtime browser/workspace/skill counts from the active agent payload. +- The installed `@mastra/client-js` package exposes capability metadata like `browserTools`, `workspaceTools`, and `skills`, but this repo still does not have a first-class browser/editor runtime-control client resource comparable to tool providers; UI work in this area should assume capability display first and add server routes only if real session/control features are needed. - 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 @@ -11,11 +17,42 @@ - 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. +- `lib/hooks/use-mastra-query.ts` was also tightened to track the installed `@mastra/client-js` API more closely: + - `useToolProvider(...)` now returns the real provider resource instead of duplicating the toolkits query + - stored resource update/version mutations now accept explicit request-context-aware inputs + - workspace skill hooks now support `skillPath?: string` for disambiguating duplicate skill names + - `useCompareStoredScorerVersions(...)` now uses the scorer-specific compare response type +- The chat provider/runtime UX now also exposes provider readiness directly through `ChatContext`: + - active provider connection state and env-var hint come from live `useAgentModelProviders()` data + - the composer shows a green/amber provider status light and avoids presenting provider-level model options that the current agent cannot actually use + - the message surface shows provider/model/runtime capability badges and a configuration warning when the provider is unavailable +- `/chat/builder` now uses the exact stored-agent creation contract from `@mastra/client-js`: + - live provider/model options instead of hardcoded Gemini defaults + - typed tool-map payloads instead of `unknown` casts + - save is gated on a connected provider so users cannot submit an obviously invalid agent snapshot +- Tool cancellation now depends on the runtime `abortSignal` that comes from `createTool()` / `useChat.stop()`, not on a shared global controller. - 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 +## Recent Update (2026-04-16) + +- The shared chat visual system was tightened around `ChatPageShell`, `ChatSettingsShell`, and `MainSidebar` instead of route-level bespoke chrome. +- `ChatPageShell` now supports `fullBleed` mode for immersive routes that still need the persistent shared sidebar, and `/chat/workflows` now uses that path. +- `/chat/builder` no longer uses its own standalone header/layout; it now mounts inside the same shared shell/sidebar composition as the rest of the chat workspace. +- `app/layout.tsx` no longer applies `mesh-gradient` to the entire app body, and `app/globals.css` now defines calmer chat-specific surface utilities: + - `chat-shell-bg` + - `chat-sidebar-surface` + - `chat-panel` + - `chat-panel-muted` + - `chat-toolbar` + - `chat-canvas-surface` +- The older glow/glass/bento utilities were softened rather than removed blindly so existing routes keep working while the chat area adopts a more restrained professional theme. +- Live runtime verification also fixed two important chat-shell contracts: + - `lib/mastra-client.ts` exports `MASTRA_API_BASE_URL` for frontend consumers like `chat-context.tsx` + - `ChatProvider` now derives runtime agent ids from the `useAgents()` array payload instead of array indexes, which prevents invalid `/api/agents/0` fetches on shell pages + ## 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. @@ -69,8 +106,8 @@ graph TB AgentArtifact["agent-artifact.tsx
Code artifacts"] end - subgraph Config["config/"] - AgentConfig["agents.ts
26+ agent configs"] + subgraph RuntimeCatalog["runtime metadata"] + RuntimeAgentConfig["runtime-chat-catalog.ts
live-derived agent metadata"] end subgraph AIElements["AI Elements (imported)"] @@ -91,7 +128,7 @@ graph TB ChatPage --> Providers ChatPage --> Components - Components --> Config + Components --> RuntimeCatalog Components --> AIElements ``` @@ -104,8 +141,8 @@ app/chat/ ├── providers/ │ ├── chat-context.tsx # React Context with AI SDK v5 types │ └── chat-context-types.ts # Type definitions -├── config/ -│ └── agents.ts # 26+ agent configurations with feature flags +├── lib/ +│ └── runtime-chat-catalog.ts # Runtime-derived chat agent/model metadata └── components/ ├── chat-header.tsx # Header with ModelSelector ├── chat-messages.tsx # Message list with streaming, Attachments, AudioPlayer diff --git a/app/chat/builder/page.tsx b/app/chat/builder/page.tsx new file mode 100644 index 0000000..bfc19a4 --- /dev/null +++ b/app/chat/builder/page.tsx @@ -0,0 +1,454 @@ +'use client' + +import type { + CreateStoredAgentParams, + Provider, + StoredAgentToolConfig, +} from '@mastra/client-js' +import { useEffect, useMemo, useState } from 'react' +import { useRouter } from 'next/navigation' +import { + useAgentModelProviders, + useCreateStoredAgentMutation, + useTools, +} from '@/lib/hooks/use-mastra-query' +import { useAuthQuery } from '@/lib/hooks/use-auth-query' +import { Button } from '@/ui/button' +import { Input } from '@/ui/input' +import { Label } from '@/ui/label' +import { Textarea } from '@/ui/textarea' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/ui/select' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/ui/card' +import { Badge } from '@/ui/badge' +import { ScrollArea } from '@/ui/scroll-area' +import { Separator } from '@/ui/separator' +import { Switch } from '@/ui/switch' +import { Skeleton } from '@/ui/skeleton' +import { ChatPageShell } from '../components/chat-page-shell' +import { MainSidebar } from '../components/main-sidebar' +import { ChatProvider } from '../providers/chat-context' +import { + SaveIcon, + ArrowLeftIcon, + BotIcon, + WrenchIcon, + BrainIcon, + Loader2Icon, + AlertCircleIcon, + SparklesIcon +} from 'lucide-react' +import { cn } from '@/lib/utils' + +export default function AgentBuilderPage() { + const router = useRouter() + const authQuery = useAuthQuery() + const userId = authQuery.data?.user.id + + // Mutations + const createAgent = useCreateStoredAgentMutation() + + // Queries + const { data: toolsData, isLoading: isLoadingTools } = useTools() + const modelProvidersQuery = useAgentModelProviders() + + // Form State + const [name, setName] = useState('') + const [description, setDescription] = useState('') + const [instructions, setInstructions] = useState('') + const [selectedProviderId, setSelectedProviderId] = useState('') + const [selectedModelId, setSelectedModelId] = useState('') + const [selectedTools, setSelectedTools] = useState([]) + + // UI State + const [isSaving, setIsSaving] = useState(false) + const [error, setError] = useState(null) + + const tools = useMemo(() => toolsData ?? [], [toolsData]) + const modelProviders = useMemo( + () => modelProvidersQuery.data?.providers ?? [], + [modelProvidersQuery.data] + ) + const selectedProvider = useMemo( + () => + modelProviders.find((provider) => provider.id === selectedProviderId) ?? + null, + [modelProviders, selectedProviderId] + ) + const availableModels = useMemo( + () => selectedProvider?.models ?? [], + [selectedProvider] + ) + const canSave = + name.trim().length > 0 && + instructions.trim().length > 0 && + selectedProviderId.length > 0 && + selectedModelId.length > 0 && + (selectedProvider?.connected ?? false) + + useEffect(() => { + if (modelProviders.length === 0) { + return + } + + const fallbackProvider = + modelProviders.find((provider) => provider.connected) ?? + modelProviders[0] + + if ( + selectedProviderId.length === 0 || + !modelProviders.some((provider) => provider.id === selectedProviderId) + ) { + setSelectedProviderId(fallbackProvider.id) + } + }, [modelProviders, selectedProviderId]) + + useEffect(() => { + if (availableModels.length === 0) { + if (selectedModelId.length > 0) { + setSelectedModelId('') + } + return + } + + if (!availableModels.includes(selectedModelId)) { + setSelectedModelId(availableModels[0]) + } + }, [availableModels, selectedModelId]) + + const handleToggleTool = (toolId: string) => { + setSelectedTools(prev => + prev.includes(toolId) + ? prev.filter(id => id !== toolId) + : [...prev, toolId] + ) + } + + const handleSave = async () => { + if (!canSave) { + setError( + selectedProvider && !selectedProvider.connected + ? `Configure ${selectedProvider.envVar} before creating an agent with ${selectedProvider.name}.` + : 'Name, instructions, provider, and model are required.' + ) + return + } + + setIsSaving(true) + setError(null) + + try { + const toolConfigMap: Record = + Object.fromEntries(selectedTools.map((toolId) => [toolId, {}])) + + const agentData: CreateStoredAgentParams = { + name: name.trim(), + description: + description.trim().length > 0 ? description.trim() : undefined, + instructions: instructions.trim(), + model: { + provider: selectedProviderId, + name: selectedModelId, + }, + tools: selectedTools.length > 0 ? toolConfigMap : undefined, + authorId: userId, + metadata: { + createdFrom: 'chat-builder', + }, + } + + await createAgent.mutateAsync(agentData) + router.push('/chat') + } catch (err: unknown) { + setError( + err instanceof Error ? err.message : 'Failed to save agent' + ) + } finally { + setIsSaving(false) + } + } + + return ( + + } + actions={ + <> + + + + } + > +
+
+
+
+
+ +

+ Runtime-backed creation +

+
+

+ Build agents with live runtime contracts +

+

+ Provider readiness, model availability, and tool selection all come from the + current Mastra runtime so the builder stays aligned with production. +

+
+
+ + {modelProviders.length} providers + + + {availableModels.length} models + + + {tools.length} tools + +
+
+
+ + {error ? ( +
+
+ +

{error}

+
+
+ ) : null} + +
+
+ + +
+
+ +
+
+ Basic Configuration + Define your agent's identity and directives +
+
+
+ +
+ + { setName(e.target.value) }} + placeholder="e.g. Analysis Engine v1" + className="h-11 border-border/70 bg-background/75 font-medium placeholder:text-muted-foreground/40" + /> +
+ +
+ + { setDescription(e.target.value) }} + placeholder="What this agent helps users do" + className="h-11 border-border/70 bg-background/75 font-medium placeholder:text-muted-foreground/40" + /> +
+ +
+ +