Fixes#94. The stream-json scanner never checked scanner.Err() after the
scan loop, so if a line exceeded the 1MB buffer limit the scanner would
stop with bufio.ErrTooLong and the response would be silently truncated.
- Check scanner.Err() after the loop and return an explicit error
- Increase max buffer from 1MB to 10MB to handle large tool outputs
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
* fix(mcp-bridge): add per-session agent context and HMAC verification
- Add per-session MCP config with X-Agent-ID/X-User-ID headers instead
of shared global config file
- Sign bridge context headers with HMAC-SHA256 to prevent forgery
- Add bridgeContextMiddleware to verify signatures on MCP bridge requests
- Store MCP configs in ~/.goclaw/mcp-configs/ outside agent workDir
- Use atomic writes (tmp + rename) for MCP config files
- Fix provider rename leaving ghost registry entries
- Remove provider_type from mutable fields on update
- Tighten temp dir permissions from 0755 to 0700
* feat(mcp-bridge): propagate channel routing context through MCP bridge
- Pass channel, chat_id, and peer_kind from agent loop to CLI provider options
- Inject X-Channel, X-Chat-ID, X-Peer-Kind headers in bridge context middleware
- Add BridgeContext struct to bundle per-call context for MCP config generation
- Include channel routing headers in per-session MCP config files
- Expose "message" tool via MCP bridge for cross-channel messaging
- Add extract helpers for new option keys in claude_cli_session.go
* feat(mcp-bridge): forward media attachments to outbound message bus
- Wire MessageBus into gateway server and MCP bridge handler
- Publish tool result media files to outbound bus for channel delivery
- Extract channel/chatID/peerKind from tool context for proper routing
- Add mimeFromExt helper for content-type detection on attachments
* feat(mcp-bridge): inject per-agent DB-backed MCP servers into Claude CLI config
- Add MCPServerLookup type to resolve agent-specific MCP servers from DB
- Wire MCPServerStore through provider registration and HTTP handler
- Extract mcpServerEntryToConfig helper to deduplicate transport config logic
- Add JSON-to-Go helpers (jsonToStringSlice, jsonToStringMap) for DB fields
- Merge per-agent MCP servers at config write time without overriding static entries
* fix(mcp-bridge): use Media struct fields and prefer explicit MimeType
- Map Media.Path to attachment URL instead of treating Media as string
- Use Media.MimeType when available, fall back to extension-based detection
* refactor(providers): deduplicate option extractors and extract bridge media forwarding
- Replace per-field extractors (extractSessionKey, extractAgentID, etc.) with generic extractStringOpt/extractBoolOpt
- Add bridgeContextFromOpts helper to build BridgeContext in one call
- Extract forwardMediaToOutbound from inline block in makeToolHandler
- Change NewBridgeServer msgBus param from variadic to explicit pointer
* fix(providers): validate provider_type on update instead of silently dropping it
- Add explicit validation against ValidProviderTypes with 400 response
- Remove silent delete(updates, "provider_type") that hid invalid values
- Caller now receives clear error when submitting unsupported provider_type
* fix(providers): add header injection validation to MCP bridge headers
- Extend CRLF/null-byte checks to agentID, channel, chatID, and peerKind
- Previously only userID had header injection prevention
- Prevents HTTP header injection via crafted values in MCP config
* fix(mcp-bridge): sign all context fields in HMAC and remove legacy code
- Sign all 5 bridge context fields (agentID|userID|channel|chatID|peerKind)
in HMAC instead of only agentID|userID to prevent channel routing forgery
- Propagate context.Context into MCPServerLookup to respect request
cancellation instead of using context.Background()
- Remove legacy BuildCLIMCPConfig, WithClaudeCLIMCPConfig, mcpConfigPath,
and mcpCleanup (dead code since system is PG-only)
- Use mime.TypeByExtension before custom fallback in mimeFromExt
- Add debug log when media forwarding is skipped due to missing context
- Add thread-safety comment to SetMCPServerLookup
---------
Co-authored-by: Nam Nguyen Ngoc <namnn.0911@gmail.com>
Co-authored-by: viettranx <viettranx@gmail.com>
Fixes#87. crypto.randomUUID() is unavailable in browsers older than
Chrome 92 / Firefox 95 / Safari 15.4, causing an uncaught TypeError
that crashes the React tree and renders a blank page when opening
builtin-tool settings or MCP grant dialogs.
Add a uniqueId() helper that uses crypto.randomUUID() when available
and falls back to a Math.random-based UUID v4 generator otherwise.
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Zalo API does not support any markup rendering. LLM output containing
markdown artifacts (**bold**, `code`, headers, etc.) was sent as-is,
making messages hard to read. Add StripMarkdown() to convert markdown
to clean plain text, and inject a system prompt hint instructing the
model to output plain text on Zalo channels.
Members could create/cancel tasks, broadcast to team, and auto-create
tasks via spawn — all without role checks. System relied entirely on
LLM prompt behavior. Now enforced at system level via requireLead().
- Add /cron/:id detail page with job info, payload, run history
- Make cron.run async: respond immediately, execute in background
- Set last_status="running" before execution, emit CronEvent via WS
- Add CronEvent (running/completed/error) broadcast to all WS clients
- Add server-side pagination to GetRunLog (offset + total count)
- Show loading spinner on Run button when job is running (list + detail)
- Enrich CronRunLogEntry with duration, input/output tokens
- Make job names clickable in overview card → /cron/:id
- Fix refresh button animation using isFetching instead of isPending
The Zalo personal listener's control event handler assumed content.data
was always an object with a URL field. In practice it can also be a
plain string, causing JSON unmarshal errors. Use json.RawMessage and
inspect the first byte to handle both forms.
* fix(agent): use ChannelType in system prompt for proper channel context
The system prompt was using the channel instance name (e.g. "zep-lao") instead
of the platform type (e.g. "zalo_personal"), causing the LLM to not understand
which messaging platform it's running on. This led to context confusion where
the bot would ask users which channel to send to instead of using the current one.
Changes:
- Add ChannelType field to RunRequest and SystemPromptConfig
- Thread channel type from consumer/cron → agent loop → system prompt
- Add WithToolChannelType/ToolChannelTypeFromCtx for tool context
- Register channel types for both config-based and DB-loaded instances
- Fix Zalo group thread type detection with approvedGroups cache
- Update cron handler to resolve channel type for cron-triggered runs
* refactor(channels): add Type() to Channel interface, remove channelTypes map
Move channel type from a separate map in Manager to the Channel interface
itself. BaseChannel.Type() falls back to Name() for config-based channels
where name == type. Extracts resolveChannelType helper to DRY up 6
repeated resolution blocks across consumer and cron handlers.
* feat(zalo): add pending group history for conversation context
Zalo personal groups now record non-@mentioned messages in a ring buffer
(default 50, configurable via history_limit). When the bot IS mentioned,
pending history is flushed as context — matching Telegram/Discord/Feishu.
Separated mention gating from policy gating in checkGroupPolicy for
cleaner control flow.
MiniMax music generation API silently ignores duration_seconds — audio
length is determined by lyrics length (vocal) or model default (~3-4 min
for instrumental). Updated tool description to clarify this limitation.
Replace simple JSON settings modal with sortable provider chain cards for
media tools. Each card supports provider/model selection, timeout, retries,
and typed provider-specific params via schema.
- Add @dnd-kit/core + @dnd-kit/sortable for drag-and-drop reordering
- New media-provider-chain-form.tsx: sortable card list with DnD
- New media-provider-params-schema.ts: typed params per tool x provider_type
- Combobox: add portalContainer prop to escape dialog overflow clipping
- Title Case formatting for dialog titles
Refactor read_image, read_document, read_video, read_audio to use
ResolveMediaProviderChain + ExecuteWithChain for consistent fallback behavior.
Add hardcoded model lists for MiniMax, DashScope, and Suno providers.
New tool supporting MiniMax music generation, ElevenLabs sound effects,
and Suno music (stub). Registers Suno as a valid provider type.
- create_audio.go: tool definition with provider chain for music, direct ElevenLabs for SFX
- create_audio_minimax.go: MiniMax music API (/music_generation)
- create_audio_elevenlabs.go: ElevenLabs sound effects API (/v1/sound-generation)
- create_audio_suno.go: stub for future Suno integration
- Add ProviderSuno type to store and gateway registration
Refactor create_image and create_video to use a shared provider chain system.
Each tool now supports an ordered list of providers with per-entry timeout,
max retries, and provider-specific params. Includes MiniMax and DashScope
image/video generation implementations.
- New media_provider_chain.go: shared chain resolution, retry execution, limitedReadAll
- create_image: refactored to ExecuteWithChain, added MiniMax + DashScope providers
- create_video: refactored to ExecuteWithChain, added MiniMax async video generation
- Backward compatible with legacy {provider, model} settings format
Show skill name instead of raw "use_skill" tool name, amber-colored
text, "activated" badge, and skip displaying raw arguments for skill
activation events.
Add a no-op use_skill tool that generates tool.call/tool.result events
in tracing spans and realtime, making skill activations visible in
observability. The actual skill loading still happens via read_file.
Web UI renders use_skill events with a distinct Zap icon and skill name
instead of the generic wrench icon.
Extract shared media utilities (MediaInfo, BuildMediaTags, TranscribeAudio,
DetectMIMEType) into internal/channels/media/ and refactor Telegram to use
them. Add full inbound/outbound media support to Discord and Feishu channels
(STT transcription, document extraction, media tags, voice agent routing).
Add WebSocket media upload/serve endpoints and MIME-aware media tags in
chat.send. Split large channel files for maintainability.
- Fix create_video: use predictLongRunning API instead of generateContent
(async polling flow: POST → poll every 10s → download video from URI)
- Fix durationSeconds as int (not string) per actual Gemini API requirement
- Fix MediaRef collection order: historical first, current last, so
refs[len-1] always returns the most recent file (fixes read_audio
picking up old file instead of current voice message)
- Remove misleading "video not yet supported" text from Telegram handler
that prevented LLM from calling read_video tool
- Add isNonChatModel() to skip chat-based verify for generation models
(veo-*, dall-e-*, imagen-*, gemini-*-image)
- 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