- Add read_audio tool with Gemini File API, OpenAI input_audio, and fallback support
- Add read_video tool with Gemini File API and base64 fallback for video analysis
- Add create_video tool with Gemini Veo and OpenRouter chat completions support
- Add shared gemini_file_api.go for upload → poll → generateContent pipeline
- Add shared openai_compat_call.go for custom JSON chat completions
- Fix system prompt showing denied tools: use filteredToolNames() instead of tools.List()
- Wire audio/video MediaRef context propagation in agent loop
- Register new tools in seed data, policy groups, and web UI settings
- Enforce duration (max 30s) and aspect_ratio limits on create_video
Self-Evolution: predefined agents can now optionally evolve their SOUL.md
(communication style/tone only) when self_evolve is enabled in other_config.
Identity, name, and operating instructions remain locked. Context propagation
flows through LoopConfig → Loop → context.WithValue → interceptor carve-out.
System prompt guides the agent on what it can/cannot evolve.
Instances Tab: new HTTP endpoints and UI tab for viewing/editing per-user
USER.md files on predefined agents. Includes owner-only access checks,
fileName validation (USER.md only), and cache invalidation.
UI: self-evolve toggle in General tab, create dialog, and setup wizard.
Agent type and evolve/static badges with tooltip explanations on cards
and detail header. TooltipProvider added to agents list and detail pages.
- Add persistent media storage (internal/media/) replacing temp file deletion
- Add MediaRef type for lightweight media references in session messages
- Refactor media pipeline to use bus.MediaFile{Path, MimeType} across all channels
- Add read_document builtin tool for PDF/DOCX/XLSX analysis via Gemini native API
- Move image sanitization from Telegram to shared agent/media layer
- Add media reload for multi-turn conversations (images from last 5 messages)
- Add reply-to-message media resolution for Telegram (re-download on reply)
- Add media inventory to compaction summary to preserve awareness after truncation
- Fix coreToolSummaries for read_image, read_document, create_image tools
- Add real-time trace update events via WebSocket broadcast
- Improve trace detail UI with media refs and tool result display
Add Storage page under Monitoring menu to browse, view, and manage
files inside ~/.goclaw/. Skills directories are visible but
deletion-protected. Extract shared file browser components from
skills page for reuse.
- Add highlight.js with tree-shaken language imports for code viewer
- Add CSV file rendering as styled table with sticky headers
- Add resizable file tree panel with drag handle
- Add mobile stacked layout with back navigation
- Strip frontmatter from .md files in file viewer
- Add responsive dialog breakpoints (sm/md/lg/xl/2xl)
- Split skill-detail-dialog into 4 focused modules (<200 lines each)
- Add highlight.js CSS with dark mode overrides
- Add skill file browser with tree panel and content viewer (syntax highlight per file type)
- Add version selector to browse all skill versions on disk
- Add 3 new API endpoints: GET /v1/skills/{id}/versions, files, files/{path}
- Make skill name clickable to open detail dialog (remove redundant View button)
- Filter system artifacts (__MACOSX, .DS_Store, Thumbs.db) from ZIP extraction and file listing
- Add symlink protection in extraction, file listing, and file reading
- Strengthen path traversal prevention with prefix validation
- Strip frontmatter from .md files in file viewer
- Fix double scrollbar in file content panel
ReactMarkdown wraps fenced code blocks in <pre><code>, but CodeBlock
renders its own <pre>. The outer <pre> inherited prose dark background,
causing nested styling conflicts. Override pre component to render
fragment, use not-prose on CodeBlock to fully escape prose styling.
- Replace target=_blank image links with in-page lightbox modal
(click image to view full-size, click backdrop or Escape to close)
- Force scroll-to-bottom when user sends a message, regardless of
current scroll position
- FilesHandler now serves files by absolute path (auth-token protected)
instead of requiring a workspace root, supporting multiple agent workspaces
- mediaToMarkdown generates relative URLs (/v1/files/...) instead of
absolute http://host:port URLs so images work from any client origin
- Deduplicate media collection in agent loop: prefer result.Media over
MEDIA: prefix parsing to prevent duplicate images
- Add skill edit dialog (name, description, visibility, tags)
- Add drag-and-drop support to upload modal with visual feedback
- Fix frontmatter not stripped in skill detail view (CRLF normalization)
- Include visibility, tags, version in skill list/detail API responses
- Change default skill upload visibility from private to internal
- Add visibility column and version display to skills table
Override Tailwind Typography prose defaults that set dark pre background
with hardcoded light text. Use theme-aware CSS variables (--muted, --foreground)
so code blocks are readable in both light and dark modes.
Root cause: create_image tool only set ForLLM:"MEDIA:path" but never
populated result.Media. The main agent loop parses MEDIA: prefix via
parseMediaResult(), but the subagent exec loop only checked result.Media
— so media paths were silently lost for all subagent/spawn workflows.
This caused the entire downstream pipeline (task.Media → AnnounceQueue →
PublishInbound → ContentSuffix → session) to receive empty media, making
images invisible in WS chat despite Telegram working fine.
Fixes:
- create_image.go: set result.Media = []string{imagePath}
- subagent_exec.go: add MEDIA: prefix fallback parsing as safety net
tool.result updated React state via functional update but didn't sync
toolStreamRef.current. When the next tool.call arrived and set state
to toolStreamRef.current (direct value), it overwrote completed
statuses with stale "calling" phase — causing tool cards to stay
stuck at "Running..." forever.
Root cause: SubagentTask had no Media field. Tool results with media
(e.g. image generation) captured result.Media but it was discarded.
AnnounceQueueItem also had no Media field, so PublishInbound for
subagent announces always had empty Media — ContentSuffix never triggered.
Changes:
- Add Media []string to SubagentTask (subagent.go)
- Capture result.Media from tool executions in subagent_exec.go
- Add Media []string to AnnounceQueueItem (announce_queue.go)
- Pass task.Media through direct publish and batched announce drain
- Collect batch media in gateway.go announce queue drain callback
Delegations from different channels/chats sharing the same source agent
were incorrectly treated as siblings. This caused cross-session artifact
accumulation: media from a Telegram delegation could be routed to WS
and vice versa, with the first-completing delegation's announce being
suppressed entirely.
Add originKey() (sourceAgentID:channel:chatID) to DelegationTask and
use it for ListActiveForOrigin, accumulateArtifacts, collectArtifacts.
When an async delegation failed, the team task stayed in_progress
forever because only the success path called autoCompleteTeamTask().
Now autoFailTeamTask() marks the team task as failed, stores the
error message, unblocks dependents, and logs an audit trail message.
Previous approach failed because:
1. PublishOutbound with channel "ws" is silently dropped (no handler)
2. LLM strips image URLs despite instruction text
3. Post-run AddMessage races with frontend loadHistory()
New approach: ContentSuffix field on RunRequest is appended to the
assistant response inside the agent loop BEFORE saving to session
and BEFORE emitting run.completed. This guarantees image markdown
is in the session when frontend calls loadHistory().
Only affects WS channel — other channels still use ForwardMedia
and their respective outbound handlers.
- Add GET /v1/files/{path} endpoint with Bearer token + query param auth
- Pre-convert media to markdown HTTP URLs in announce messages for WS channel
- Add HideInput flag to skip persisting system messages in session history
- Add fallback to append image URLs if LLM strips them from announce response
- Fix stream-to-history DOM flash by promoting streamed text locally
- Fix tool call card word-wrap and expandable arguments panel
- Fix snake_case mismatch in Message types (toolCalls → tool_calls)
- Add clickable image rendering in markdown renderer
- Use errors.Is() instead of direct sentinel comparison (13 instances)
- Convert if/else-if chains to switch/case for same-variable comparisons
- Remove redundant bitwise OR with zero
- Add post-implementation checklist to CLAUDE.md
- Scan web_fetch/web_search tool results for prompt injection patterns via inputGuard
- Strip hidden HTML elements (display:none, aria-hidden, sr-only classes) during conversion
- Scope web tool caches per channel to prevent cross-channel cache poisoning
- Enforce domain blocklist and allowlist checks on HTTP redirect targets
- Add untrusted content reminder to external content wrapper
- Log redirect source URL in fetch results for transparency
Co-authored-by: Nam Nguyen Ngoc <namnn.0911@gmail.com>
Add optional Redis cache support via `go build -tags redis`, following
the same paired-stub pattern as OTel and Tailscale. The Cache[V] interface
is unchanged; Redis and in-memory implementations are injected at startup
without altering usage logic.
- Add RedisCache[V] implementation with JSON serialization, fail-open on errors
- Add gateway_redis.go / gateway_redis_noop.go paired wiring files
- Refactor GroupWriterCache and ContextFileInterceptor to accept injected caches
- Add GOCLAW_REDIS_DSN env var, docker-compose.redis.yml overlay
- Update Dockerfile and GitHub Actions with ENABLE_REDIS build arg
- Add Redis variant to CI matrix (5 variants: latest, otel, tsnet, redis, full)
- Chat: change send/stop buttons from size-icon (36px) to size-icon-lg
(40px) to match textarea height with py-3 padding
- Teams: remove max-w-2xl on members tab to fill parent width
Root cause: ws object is a stable ref — useCallback deps never changed
when WS connected, so useEffect never re-fired to fetch data.
Fix: subscribe to reactive `connected` state from auth store in all
data-loading hooks. For manual hooks, add connected to useCallback deps
and initialize loading=true. For TanStack Query hooks, add
enabled: connected and use isPending (true while disabled with no cache).
Detail pages now use DetailPageSkeleton matching final layout shape
instead of DeferredSpinner to prevent layout shift.
Add ConfirmDeleteDialog component that requires typing the entity name
to confirm deletion, preventing accidental misclicks. Applied to agents,
teams, providers, MCP servers, channels, and skills. Remove redundant
agent danger tab (delete button in header is sufficient).
MCP tool search mode: when an agent has >30 MCP tools, tools are
deferred and a single mcp_tool_search meta-tool is registered instead.
Uses BM25 index for keyword search, activating matched tools on demand.
Shared connection pool: introduces Pool that maintains one physical
connection per MCP server name, shared across all agents via refcount.
Eliminates N×M duplicate connections (N agents × M servers).
Older UI versions saved select-based bool fields (block_reply, etc.)
as strings ("true"/"false") instead of JSON booleans. This caused
json.Unmarshal to fail when loading channel instances. Normalize
config in loadInstance before passing to factories.
UpdateServer type-asserted json.RawMessage but json.Decoder into
map[string]interface{} produces map[string]interface{} for nested
objects — so the assertion always failed and encryption was silently
skipped. Marshal any type to []byte before encrypting.
Backend:
- Add POST /v1/mcp/servers/test endpoint reusing DiscoverTools()
- Add GET /v1/mcp/servers/{id}/tools for discovered tool listing
- Add MCPToolLister interface and wire through gateway
Frontend:
- Add Test Connection button with success/error feedback in MCP form
- Replace agent ID text input with agent select dropdown in grants dialog
- Replace tool allow/deny text inputs with multi-select from server tools
- Add MCP tools dialog for viewing connected server tools
- Widen MCP dialogs to sm:max-w-xl
Render tool-call-only assistant messages as compact cards with wrench
icon instead of empty bubbles. Click to expand and view tool arguments
as formatted JSON. Make thinking block full-width in chat thread.
Reduce ring-[3px] to ring-2 on Input and ToolNameSelect for a subtler
focus indicator. Add px-0.5 -mx-0.5 to scrollable form dialog bodies
so the focus ring is not clipped by overflow-y-auto.
When a user refreshes the page and resumes an existing chat session,
the frontend agentId could default to "default" instead of the correct
agent embedded in the session key. This caused the backend to resolve
the wrong agent, blocking MCP tools that were granted to the intended
agent.
Backend: parse agent key from session key format "agent:{key}:{rest}"
when agentId param is empty in chat.send.
Frontend: initialize agentId from URL session key on page load and
sync it when selecting a different session.
Two bugs in Zalo personal channel policy:
1. Group pairing bypass: checkGroupPolicy() called IsAllowed(groupID)
directly, which returns true when allowlist is empty — effectively
skipping pairing for all groups. Fixed to match the DM pairing
pattern: HasAllowList() && IsAllowed(groupID).
2. Wrong thread type for group pairing reply: sendPairingReply() always
used ThreadTypeUser even when replying to a group. Now detects
group sender IDs (prefixed "group:") and uses ThreadTypeGroup.
Add Claude CLI as an LLM provider (subscription-based, no API key needed).
The CLI manages session history, tool execution, and context while GoClaw
forwards messages and streams responses.
Key features:
- Claude CLI provider with session persistence (--resume)
- MCP bridge server exposing GoClaw tools to CLI via streamable-http
- Security hooks (shell deny patterns, workspace path restrictions)
- Per-session mutex preventing concurrent CLI calls
- Onboard wizard for Claude CLI setup and auth verification
- Web UI for adding/managing Claude CLI provider with auth status
- Provider registry Close() for proper shutdown cleanup
Security:
- CLI path validation (only "claude" or absolute paths from DB)
- Token auth middleware for MCP bridge endpoint
- Shell injection prevention in hook scripts (single-quoted paths)
- Relative path resolution before workspace boundary checks
- Resource leak prevention on provider replace/unregister
Co-authored-by: nhokboo <nhokboo@users.noreply.github.com>
Try generating all context files (SOUL.md, IDENTITY.md, USER_PREDEFINED.md)
in a single LLM call with 120s timeout. On timeout/retryable errors, fall
back to the existing 2-call sequential approach. Non-retryable errors fail
immediately.
- Restore buildCreatePrompt for single-call path
- Add isRetryableError helper (context deadline, net timeout)
- Extract finishSummon and loadExistingFiles helpers
- Update Tiểu La preset to image generation specialist
Add block.reply event that delivers intermediate assistant text to non-streaming
channels during multi-tool iterations. Includes 2-tier config toggle:
gateway-level default (disabled) + per-channel override (inherit/on/off).
Backend:
- Emit block.reply events from agent loop between tool iterations
- Add BlockReply *bool to GatewayConfig and all 6 channel config structs
- Add BlockReplyChannel interface with ResolveBlockReply() resolution
- Guard delivery in HandleAgentEvent by RunContext.BlockReplyEnabled
- Resolve config at RegisterRun time, pass to consumer goroutine
- Conditional dedup: skip final message if identical to last block reply
UI:
- Gateway settings: Switch toggle for global default
- Per-channel: tri-state select (Inherit from gateway / Enabled / Disabled)
- Protocol: BLOCK_REPLY constant in AgentEventTypes
- Form: coerceBoolSelects for proper JSON boolean serialization
- Fix CI: use %s for json.Number in e2e test (was %d)
- Fix path traversal in parseMediaPath: restrict to os.TempDir()
- Add 25MB file size limit (checkFileSize) before upload reads
- Drain stale uploadCallbacks on Listener.reset() to prevent leaks
- Split send.go (688 LOC) into send.go, send_image.go, send_file.go, send_helpers.go
- Add unit tests for parseMediaPath (12 cases incl. traversal attacks)
The list pages had ConfirmDialog for deletion wired up but no UI
element to trigger it. Detail pages had no delete option at all.
- Add delete button with trash icon to agent and team cards
- Wire onDelete callbacks to existing setDeleteTarget state
- Add delete button + confirm dialog to agent detail page header
- Add delete button + confirm dialog to team detail page header
Closes#17
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Split agent summoning into two sequential LLM calls:
- Step 1: Generate SOUL.md (personality)
- Step 2: Generate IDENTITY.md + USER_PREDEFINED.md using SOUL.md as context
This enables real-time progress updates in the UI as each file completes.
On retry, previously generated files are detected by comparing against
the default template and skipped, so only missing files are regenerated.
Also increase HTTP client timeout from 120s to 300s for both OpenAI and
Anthropic providers. Some models (e.g. Kimi) with deep reasoning can
take 160s+ for non-streaming requests, which exceeded the 120s limit
and caused cascading retry failures until the context deadline expired.