mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-10 06:10:46 +00:00
9.3 KiB
9.3 KiB
GoClaw Gateway
PostgreSQL multi-tenant AI agent gateway with WebSocket RPC + HTTP API.
Tech Stack
Backend: Go 1.26, Cobra CLI, gorilla/websocket, pgx/v5 (database/sql, no ORM), golang-migrate, go-rod/rod, telego (Telegram)
Web UI: React 19, Vite 6, TypeScript, Tailwind CSS 4, Radix UI, Zustand, React Router 7. Located in ui/web/. Use pnpm (not npm).
Database: PostgreSQL 18 with pgvector. Raw SQL with $1, $2 positional params. Nullable columns: *string, *time.Time, etc.
Project Structure
cmd/ CLI commands, gateway startup, onboard wizard, migrations
internal/
├── agent/ Agent loop (think→act→observe), router, resolver, input guard
├── bootstrap/ System prompt files (SOUL.md, IDENTITY.md) + seeding + per-user seed
├── bus/ Event bus system
├── cache/ Caching layer
├── channels/ Channel manager: Telegram, Feishu/Lark, Zalo, Discord, WhatsApp
├── config/ Config loading (JSON5) + env var overlay
├── crypto/ AES-256-GCM encryption for API keys
├── cron/ Cron scheduling (at/every/cron expr)
├── gateway/ WS + HTTP server, client, method router
│ └── methods/ RPC handlers (chat, agents, sessions, config, skills, cron, pairing)
├── hooks/ Hook system for extensibility
├── http/ HTTP API (/v1/chat/completions, /v1/agents, /v1/skills, etc.)
├── i18n/ Message catalog: T(locale, key, args...) + per-locale catalogs (en/vi/zh)
├── knowledgegraph/ Knowledge graph storage and traversal
├── mcp/ Model Context Protocol bridge/server
├── media/ Media handling utilities
├── memory/ Memory system (pgvector)
├── oauth/ OAuth authentication
├── permissions/ RBAC (admin/operator/viewer)
├── providers/ LLM providers: Anthropic (native HTTP+SSE), OpenAI-compat (HTTP+SSE), DashScope (Alibaba Qwen), Claude CLI (stdio+MCP bridge), ACP (Anthropic Console Proxy), Codex (OpenAI)
├── sandbox/ Docker-based code sandbox
├── scheduler/ Lane-based concurrency (main/subagent/cron)
├── sessions/ Session management
├── skills/ SKILL.md loader + BM25 search
├── store/ Store interfaces + pg/ (PostgreSQL) implementations
├── tasks/ Task management
├── tools/ Tool registry, filesystem, exec, web, memory, subagent, MCP bridge
├── tracing/ LLM call tracing + optional OTel export (build-tag gated)
├── tts/ Text-to-Speech (OpenAI, ElevenLabs, Edge, MiniMax)
├── upgrade/ Database schema version tracking
pkg/protocol/ Wire types (frames, methods, errors, events)
pkg/browser/ Browser automation (Rod + CDP)
migrations/ PostgreSQL migration files
ui/web/ React SPA (pnpm, Vite, Tailwind, Radix UI)
Key Patterns
- Store layer: Interface-based (
store.SessionStore,store.AgentStore, etc.) with pg/ (PostgreSQL) implementations. Usesdatabase/sql+pgx/v5/stdlib, raw SQL,execMapUpdate()helper inpg/helpers.go - Agent types:
open(per-user context, 7 files) vspredefined(shared context + USER.md per-user) - Context files:
agent_context_files(agent-level) +user_context_files(per-user), routed viaContextFileInterceptor - Providers: Anthropic (native HTTP+SSE), OpenAI-compat (HTTP+SSE), DashScope (Alibaba Qwen), Claude CLI (stdio+MCP bridge), ACP (Anthropic Console Proxy), Codex (OpenAI). All use
RetryDo()for retries. Loads fromllm_providerstable with encrypted API keys - Agent loop:
RunRequest→ think→act→observe →RunResult. Events:run.started,run.completed,chunk,tool.call,tool.result. Auto-summarization at >75% context - Context propagation:
store.WithAgentType(ctx),store.WithUserID(ctx),store.WithAgentID(ctx),store.WithLocale(ctx) - WebSocket protocol (v3): Frame types
req/res/event. First request must beconnect - Config: JSON5 at
GOCLAW_CONFIGenv. Secrets in.env.localor env vars, never in config.json - Security: Rate limiting, input guard (detection-only), CORS, shell deny patterns, SSRF protection, path traversal prevention, AES-256-GCM encryption. All security logs:
slog.Warn("security.*") - Telegram formatting: LLM output →
SanitizeAssistantContent()→markdownToTelegramHTML()→chunkHTML()→sendHTML(). Tables rendered as ASCII in<pre>tags - i18n: Web UI uses
i18nextwith namespace-split locale files inui/web/src/i18n/locales/{lang}/. Backend usesinternal/i18nmessage catalog withi18n.T(locale, key, args...). Locale propagated viastore.WithLocale(ctx)— WSconnectparamlocale, HTTPAccept-Languageheader. Supported: en (default), vi, zh. New user-facing strings: add key tointernal/i18n/keys.go, add translations to all 3 catalog files. New UI strings: add key to all 3 locale dirs. Bootstrap templates (SOUL.md, etc.) stay English-only (LLM consumption).
Running
go build -o goclaw . && ./goclaw onboard && source .env.local && ./goclaw
./goclaw migrate up # DB migrations
go test -v ./tests/integration/ # Integration tests
cd ui/web && pnpm install && pnpm dev # Web dashboard (dev)
Post-Implementation Checklist
After implementing or modifying Go code, run these checks:
go fix ./... # Apply Go version upgrades (run before commit)
go build ./... # Compile check
go vet ./... # Static analysis
go test -race ./tests/integration/ # Integration tests with race detector
Go conventions to follow:
- Use
errors.Is(err, sentinel)instead oferr == sentinel - Use
switch/caseinstead ofif/else ifchains on the same variable - Use
append(dst, src...)instead of loop-based append - Always handle errors; don't ignore return values
- Migrations: When adding a new SQL migration file in
migrations/, bumpRequiredSchemaVersionininternal/upgrade/version.goto match the new migration number - i18n strings: When adding user-facing error messages, add key to
internal/i18n/keys.goand translations tocatalog_en.go,catalog_vi.go,catalog_zh.go. For UI strings, add to all locale JSON files inui/web/src/i18n/locales/{en,vi,zh}/ - SQL safety: When implementing or modifying SQL store code (
store/pg/*.go), always verify: (1) All user inputs use parameterized queries ($1, $2, ...), never string concatenation — prevents SQL injection. (2) Queries are optimized — no N+1 queries, no unnecessary full table scans. (3) WHERE clauses, JOINs, and ORDER BY columns use existing indices — check migration files for available indexes
Mobile UI/UX Rules
When implementing or modifying web UI components, follow these rules to ensure mobile compatibility:
- Viewport height: Use
h-dvh(dynamic viewport height), neverh-screen.h-screencauses content to hide behind mobile browser chrome and virtual keyboards - Input font-size: All
<input>,<textarea>,<select>must usetext-base md:text-sm(16px on mobile). Font-size < 16px triggers iOS Safari auto-zoom on focus - Safe areas: Root layout must use
viewport-fit=covermeta tag. Applysafe-top,safe-bottom,safe-left,safe-rightutility classes on edge-anchored elements (app shell, sidebar, toasts, chat input) for notched devices - Touch targets: Icon buttons must have ≥44px hit area on touch devices. CSS in
index.cssuses@media (pointer: coarse)with::afterpseudo-elements to expand targets - Tables: Always wrap
<table>in<div className="overflow-x-auto">and setmin-w-[600px]on the table for horizontal scroll on narrow screens - Grid layouts: Use mobile-first responsive grids:
grid-cols-1 sm:grid-cols-2 lg:grid-cols-N. Never use fixedgrid-cols-Nwithout a mobile breakpoint - Dialogs: Full-screen on mobile with slide-up animation (
max-sm:inset-0), centered with zoom on desktop (sm:max-w-lg). Handled inui/dialog.tsx - Virtual keyboard: Chat input uses
useVirtualKeyboard()hook +var(--keyboard-height, 0px)CSS var to stay above the keyboard - Scroll behavior: Use
overscroll-containon scrollable areas to prevent background scroll. Auto-scroll: smooth for incoming messages, instant on user send - Landscape: Use
landscape-compactclass on top bars to reduce padding in phone landscape orientation (max-height: 500px) - Portal dropdowns in dialogs: Custom dropdown components using
createPortal(content, document.body)MUST addpointer-events-autoclass to the dropdown element. Radix Dialog setspointer-events: noneondocument.body— without this class, dropdowns are unclickable. Radix-native portals (Select, Popover) handle this automatically - Timezone: User timezone stored in Zustand (
useUiStore). Charts useformatBucketTz()fromlib/format.tswith nativeIntl.DateTimeFormat— no date-fns-tz dependency