Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,35 @@ MASTRA_API_URL='http://localhost:4111'
# Example placeholders for local testing
LOCAL_DEV='true'

# Chrome / browser automation
# Start Chrome with remote debugging enabled before using the browser agent:
# chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\temp\chrome-debug"
CHROME_CDP_URL='http://127.0.0.1:9222'
CHROME_REMOTE_DEBUGGING_URL='http://127.0.0.1:9222'

# Discord Bot Token https://discordapp.com/developers/applications/
DISCORD_BOT_TOKEN='your_discord_bot_token_here'
DISCORD_PUBLIC_KEY='your_discord_public_key_here'
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"
GOOGLE_CLIENT_SECRET="fake_google_client_secret_for_local_dev"
GOOGLE_CLIENT_CALLBACK_URL="https://localhost:3000/api/callback"

#Authorized redirect URIs

#https://localhost:3000/api/callback

#https://localhost:3000/callback

#https://127.0.0.1:3000/api/callback

#http://127.0.0.1:3000/api/callback

#https://127.0.0.1:3000/callback


# Opencode Zen API Key
OPENCODE_API_KEY='your_opencode_zen_api_key_here'

Expand Down
110 changes: 95 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- AGENTS-META {"title":"AgentStack README","version":"1.3.3","applies_to":"/","last_updated":"2026-03-29","status":"stable"} -->
<!-- AGENTS-META {"title":"AgentStack README","version":"1.0.43","applies_to":"/","last_updated":"2026-04-14","status":"stable"} -->

<div align="center">

Expand Down Expand Up @@ -74,6 +74,8 @@ AgentStack bridges the gap between basic AI chatbots and enterprise-grade multi-
| **Financial Intelligence** | ✅ **Polygon/Finnhub/AlphaVantage (30+ endpoints)** | ❌ None | ❌ None | ❌ None |
| **RAG Pipeline** | ✅ **LibSQL HNSW + rerank + graphRAG** | ⚠️ Basic | ⚠️ Basic | ✅ External |
| **Multi-Agent Orchestration** | ✅ **A2A MCP + supervisor networks (25+ agents)** | ✅ Advanced | ✅ Basic | ✅ Partial |
| **Live Browser Automation** | ✅ **Local Chrome/CDP browser agent + shared runtime** | ⚠️ Basic | ⚠️ Partial | ⚠️ Partial |
| **Workspaces / Sandboxes** | ✅ **AgentFS + Daytona + local sandboxes + persistence** | ⚠️ Basic | ❌ None | ⚠️ Partial |
| **Enterprise Security** | ✅ **Better Auth + RBAC + path traversal protection + HTML sanitization** | ⚠️ Partial | ⚠️ Partial | ✅ Partial |
| **Type Safety** | ✅ **Zod schemas everywhere (57 tools)** | ⚠️ Limited | ⚠️ Limited | ✅ Partial |
| **UI Components** | ✅ **105 components (AI Elements + shadcn/ui)** | ✅ 30+ | ✅ 50+ | ✅ 30+ |
Expand All @@ -98,6 +100,8 @@ While other AI agent platforms offer basic chatbot functionality, AgentStack pro
- **🤖 25+ Agents**: Individual specialized agents (research, stock analysis, copywriting, etc.)
- **📋 10+ Workflows**: Multi-step orchestrated processes (weather analysis, content creation, financial reports)
- **🌐 12+ Supervisor Networks**: Coordinator agents that route tasks to specialized agents using delegation hooks (primary router, coding team, financial intelligence, content creation, etc.)
- **🧭 Live Browser Automation**: Shared Chrome/CDP browser runtime for local verification, screenshots, and interaction testing
- **🧩 Workspaces & Sandboxes**: AgentFS, Daytona, and local sandbox support with persistent LibSQL-backed state
- **🔌 A2A/MCP**: MCP server coordinates parallel agents (research+stock→report), A2A coordinator for cross-agent communication
- **🎨 105 UI Components**: AI Elements (50 chat/reasoning/canvas components) + shadcn/ui (55 base primitives)
- **📊 Enterprise Observability**: Default tracing + Langfuse integration + 10+ custom scorers + middleware logging
Expand Down Expand Up @@ -1472,11 +1476,23 @@ AgentStack is engineered for high-performance production workloads with comprehe

```mermaid
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#58a6ff', 'primaryTextColor': '#c9d1d9', 'primaryBorderColor': '#30363d', 'lineColor': '#58a6ff', 'sectionBkgColor': '#161b22', 'altSectionBkgColor': '#0d1117', 'sectionTextColor': '#c9d1d9', 'gridColor': '#30363d', 'tertiaryColor': '#161b22' }}}%%
xychart-beta
title "Requests Per Second (RPS) by Endpoint"
x-axis ["/chat", "/workflow", "/rag/query", "/dataset", "/tools/execute", "/observability", "/network", "/workspace"]
y-axis "Requests per second" 0 --> 220
bar [150, 85, 200, 180, 120, 95, 75, 110]
sequenceDiagram
participant Dev as Developer
participant Script as npm run chrome:debug
participant Chrome as Local Chrome
participant CDP as CDP Endpoint<br/>127.0.0.1:9222
participant BrowserAgent as browserAgent
participant Browser as Shared Browser Runtime
participant App as Target Web App

Dev->>Script: Start remote-debug Chrome
Script->>Chrome: Launch Chrome with CDP enabled
Chrome-->>CDP: Publish DevTools endpoint
BrowserAgent->>CDP: Connect via CHROME_CDP_URL
BrowserAgent->>Browser: Reuse shared browser session
BrowserAgent->>App: Navigate, click, type, inspect
App-->>BrowserAgent: DOM, console, screenshots
BrowserAgent-->>Dev: Structured result
```

| Endpoint | RPS | Avg Latency | Use Case |
Expand Down Expand Up @@ -1656,7 +1672,8 @@ export const myCustomTool = createTool({
// src/mastra/agents/my-agent.ts
import { Agent } from '@mastra/core/agent'
import { googleAI } from '../config/google'
import { myCustomTool, pgQueryTool } from '../tools'
import { myCustomTool } from '../tools'
import { LibsqlMemory, libsqlQueryTool } from '../config/libsql'

export const myAgent = new Agent({
id: 'my-agent',
Expand All @@ -1669,12 +1686,73 @@ export const myAgent = new Agent({
model: googleAI, // or openAI, anthropic, openrouter
tools: {
myCustomTool,
pgQueryTool,
libsqlQueryTool,
},
memory: pgMemory, // Enable conversation memory
memory: LibsqlMemory, // Enable conversation memory
})
```

### **Live Browser Automation**

AgentStack can attach to a local Chrome session through Chrome DevTools
Protocol (CDP). This powers the live browser agent and the shared browser
runtime.

#### Start Chrome in remote-debug mode

```bash
npm run chrome:debug
```

That script launches Chrome with:

```bash
chrome.exe --remote-debugging-port=9222 --user-data-dir="%TEMP%\\chrome-debug"
```

#### Environment variables

```env
CHROME_CDP_URL=http://127.0.0.1:9222
CHROME_REMOTE_DEBUGGING_URL=http://127.0.0.1:9222
```

#### Browser runtime packages

| Package | Version | Purpose |
| --- | --- | --- |
| `@mastra/agent-browser` | `^0.1.0` | Browser agent integration over CDP |
| `@mastra/stagehand` | `^0.1.0` | Shared browser automation helpers |
| `playwright` | `^1.59.1` | Browser automation / verification |
| `@mastra/core` | `^1.24.1` | Agent runtime and browser orchestration |

#### How it works

```mermaid
sequenceDiagram
participant Dev as Developer
participant Script as npm run chrome:debug
participant Chrome as Local Chrome
participant CDP as CHROME_CDP_URL<br/>127.0.0.1:9222
participant Agent as browserAgent / shared browser
participant Page as Target Web App

Dev->>Script: Start Chrome in remote-debug mode
Script->>Chrome: Launch with remote debugging enabled
Chrome-->>CDP: Expose DevTools endpoint
Agent->>CDP: Connect through agentBrowser
Agent->>Page: Navigate / click / type / inspect
Page-->>Agent: DOM, console, screenshots, network state
Agent-->>Dev: Structured result
```

#### Browser agent files

- `src/mastra/agents/browserAgent.ts` uses the shared browser runtime.
- `src/mastra/browsers.ts` centralizes the Chrome CDP configuration.
- `src/mastra/config/libsql.ts` provides the shared LibSQL-backed vector and
memory utilities used by agents and workflows.

**Agent Development Best Practices:**

- ✅ Write clear, detailed instructions with examples
Expand Down Expand Up @@ -1807,8 +1885,8 @@ vi.mock('../config/polygon', () => ({
}))

// Example: Database mock
vi.mock('../config/pg-storage', () => ({
pgQueryTool: {
vi.mock('../config/libsql', () => ({
libsqlQueryTool: {
execute: vi.fn().mockResolvedValue({
data: [{ id: 1, result: 'success' }],
error: null,
Expand Down Expand Up @@ -1917,7 +1995,7 @@ export const fileTool = createTool({
})

// Mask sensitive data in logs
import { maskSensitiveMessageData } from '../config/pg-storage'
import { maskSensitiveMessageData } from '../config/libsql'

logger.info('Processing request', {
data: maskSensitiveMessageData(requestData),
Expand Down Expand Up @@ -2131,12 +2209,14 @@ Interactive workflow visualization (`/workflows`) with AI Elements Canvas:
```ts
// src/mastra/agents/my-agent.ts
import { Agent } from '@mastra/core/agent'
import { googleAI } from '../config/google'
import { LibsqlMemory, libsqlQueryTool } from '../config/libsql'
export const myAgent = new Agent({
id: 'my-agent',
tools: { polygonStockQuotesTool, pgQueryTool },
tools: { polygonStockQuotesTool, libsqlQueryTool },
instructions: 'Analyze stocks with Polygon + RAG...',
model: googleAI, // From model registry
memory: pgMemory,
memory: LibsqlMemory,
})
// Auto-registers in index.ts
```
Expand Down Expand Up @@ -2368,7 +2448,7 @@ We are committed to providing a welcoming and inclusive experience for everyone:
🐦 **Follow [@ssdeanx](https://x.com/ssdeanx)**
📘 **[Docs](https://agentstack.ai)** (Coming Q1 2026)

_Last updated: 2026-03-17 | v1.3.1_
_Last updated: 2026-04-14 | v1.0.43_

## 🧠 **Chat**

Expand Down
1 change: 1 addition & 0 deletions app/api/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GET, POST } from '../auth/[...all]/route'
58 changes: 57 additions & 1 deletion app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ export default function LoginPage() {
}
}, [authQuery.data, authQuery.isPending, nextPath, router])

useEffect(() => {
if (authQuery.isPending || authQuery.data) {
return
}

void authClient.oneTap({
callbackURL: nextPath,
})
Comment on lines +62 to +64
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authClient.oneTap(...) call is void'd inside useEffect without handling rejections. If One Tap fails (blocked third-party cookies, missing client id, user dismissed), this can surface as an unhandled promise rejection in the browser.

Suggested fix: attach a .catch(...) handler (and optionally log at debug level) so failures are handled gracefully without noisy console errors.

Suggested change
void authClient.oneTap({
callbackURL: nextPath,
})
void authClient
.oneTap({
callbackURL: nextPath,
})
.catch(() => {
return
})

Copilot uses AI. Check for mistakes.
}, [authQuery.data, authQuery.isPending, nextPath])

useEffect(() => {
const savedIdentifier = window.localStorage.getItem(REMEMBERED_IDENTIFIER_KEY)

Expand All @@ -65,6 +75,29 @@ export default function LoginPage() {
}
}, [])

/** Starts the Google OAuth flow through Better Auth. */
const handleGoogleSignIn = async () => {
setIsLoading(true)
setErrorMessage('')

try {
const response = await authClient.signIn.social({
provider: 'google',
callbackURL: nextPath,
})

if (response.error) {
setErrorMessage(response.error.message ?? 'Unable to sign in with Google.')
setIsLoading(false)
}
} catch (error) {
setErrorMessage(
error instanceof Error ? error.message : 'Unable to sign in with Google.',
)
setIsLoading(false)
}
}

const handleLogin = async (e: LoginSubmitEvent) => {
e.preventDefault()
setIsLoading(true)
Expand Down Expand Up @@ -118,7 +151,7 @@ export default function LoginPage() {
</div>

<div className="relative mx-auto grid min-h-[calc(100vh-2rem)] w-full max-w-6xl items-center gap-6 lg:grid-cols-[1.05fr_0.95fr]">
<section className="hidden h-full flex-col justify-between rounded-[2rem] border border-border/40 bg-card/70 p-8 shadow-2xl shadow-black/5 backdrop-blur-xl lg:flex">
<section className="hidden h-full flex-col justify-between rounded-4xl border border-border/40 bg-card/70 p-8 shadow-2xl shadow-black/5 backdrop-blur-xl lg:flex">
<div className="space-y-8">
<Link href="/" className="group inline-flex items-center gap-3">
<div className="flex size-14 items-center justify-center rounded-3xl bg-primary text-primary-foreground shadow-lg shadow-primary/20 transition-transform duration-200 group-hover:scale-105">
Expand Down Expand Up @@ -206,6 +239,29 @@ export default function LoginPage() {
</p>
</div>

<Button
type="button"
variant="outline"
className="h-11 w-full rounded-xl text-sm font-medium"
onClick={handleGoogleSignIn}
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Signing in with Google...
</>
) : (
'Continue with Google'
)}
</Button>

<div className="flex items-center gap-3 text-xs uppercase tracking-[0.2em] text-muted-foreground">
<span className="h-px flex-1 bg-border/70" />
<span>Or use your account</span>
<span className="h-px flex-1 bg-border/70" />
</div>

<form onSubmit={handleLogin} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="identifier">Email or username</Label>
Expand Down
56 changes: 56 additions & 0 deletions app/login/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,39 @@ export default function SignupPage() {
}
}, [authQuery.data, authQuery.isPending, nextPath, router])

useEffect(() => {
if (authQuery.isPending || authQuery.data) {
return
}

void authClient.oneTap({
callbackURL: nextPath,
})
Comment on lines +71 to +73
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authClient.oneTap(...) call is not awaited and has no rejection handler. One Tap failures (blocked scripts, missing client id, user dismissal) can cause unhandled promise rejections.

Suggested fix: add .catch(...) inside the effect to swallow/handle expected failures (and optionally show a non-blocking message only when helpful).

Suggested change
void authClient.oneTap({
callbackURL: nextPath,
})
void authClient
.oneTap({
callbackURL: nextPath,
})
.catch((error) => {
console.debug('Google One Tap was unavailable or dismissed.', error)
})

Copilot uses AI. Check for mistakes.
}, [authQuery.data, authQuery.isPending, nextPath])

/** Starts the Google OAuth flow through Better Auth. */
const handleGoogleSignIn = async () => {
setIsLoading(true)
setErrorMessage('')

try {
const response = await authClient.signIn.social({
provider: 'google',
callbackURL: nextPath,
})

if (response.error) {
setErrorMessage(response.error.message ?? 'Unable to sign in with Google.')
setIsLoading(false)
}
} catch (error) {
setErrorMessage(
error instanceof Error ? error.message : 'Unable to sign in with Google.',
)
setIsLoading(false)
}
}

type SignupSubmitEvent = SyntheticEvent<HTMLFormElement, SubmitEvent>

const handleSignup = async (e: SignupSubmitEvent) => {
Expand Down Expand Up @@ -212,6 +245,29 @@ export default function SignupPage() {
</p>
</div>

<Button
type="button"
variant="outline"
className="h-11 w-full rounded-xl text-sm font-medium"
onClick={handleGoogleSignIn}
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Signing in with Google...
</>
) : (
'Continue with Google'
)}
</Button>

<div className="flex items-center gap-3 text-xs uppercase tracking-[0.2em] text-muted-foreground">
<span className="h-px flex-1 bg-border/70" />
<span>Or create an account below</span>
<span className="h-px flex-1 bg-border/70" />
</div>

<form onSubmit={handleSignup} className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
Expand Down
Loading
Loading