mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-18 14:13:34 +00:00
4e9f155a4c8ca9400a50cd4fedc90dfd896f4e54
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
23d0b5eb0b |
fix(providers): auto-clamp max_tokens on model rejection (#267)
* fix(providers): auto-clamp max_tokens on model rejection + fix verify for reasoning models When OpenAI-compat models reject max_tokens as too large (e.g. gpt-3.5-turbo supports 4096 but we send 8192), parse the model's stated limit from the 400 error, clamp the value, and retry once. This fixes agent creation for models with lower output token limits without hardcoding model names. Also increase the provider verify endpoint's max_tokens from 1 to 50 so reasoning models (gpt-5, o-series) have enough headroom for internal reasoning during the check call. Closes #248, closes #245 * refactor(providers): extract chat retry closure + fix clamp log key - Extract duplicate retry closure into chatRequestFn() to follow DRY - Fix slog logging wrong key: body["max_tokens"] was nil for reasoning models that use max_completion_tokens — now uses clampedLimit() helper - Remove unnecessary _ = resp in provider verify endpoint --------- Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
2cc9d68cdc |
fix(tts): config save, Edge provider, media dispatch + dark mode chat (#265)
* fix(tts): config save + Edge provider registration + dark mode chat bubbles - Wrap TTS config payload in `raw` field for config.patch RPC (#229) - Always register Edge TTS provider (free, no API key) instead of gating on `enabled` flag - Fix low-contrast user message bubbles in dark mode chat * fix(tts): skip duplicate media dispatch when temp file already delivered When both the agent loop and the message tool dispatch the same TTS temp file, the first dispatch succeeds and cleanup deletes it. Filter out missing temp media files before sending to prevent "file not found" errors and spurious error notifications on Telegram/Slack/Discord. * feat(tts): include edge-tts in Docker image when Python enabled Edge TTS is free (no API key) and serves as a universal TTS fallback. Install it alongside Python in both ENABLE_PYTHON and ENABLE_FULL_SKILLS builds. * chore(docker): expose build args from .env for compose builds Pass ENABLE_OTEL, ENABLE_PYTHON, ENABLE_FULL_SKILLS as env-driven build args so .env can control Docker build features without editing docker-compose.yml directly. * fix(tts): hot-reload TTS config on settings change via pub/sub TTS providers were only registered at startup, so changing provider/API key via the Web UI had no effect until container restart. Add a tts-config-reload bus subscriber that rebuilds the TTS manager on config changes, matching the pattern used by quota, cron, and web_fetch. Always create a TtsTool at startup (even without providers) so the reload subscriber can populate it when settings are first configured. * fix(tts): protect TtsTool.UpdateManager with RWMutex to prevent data race UpdateManager() can be called from the config reload goroutine while Execute() reads t.manager concurrently from agent goroutines. Add sync.RWMutex following the same pattern as WebFetchTool.UpdatePolicy(). Also update setupTTS doc comment which incorrectly stated it could return nil — Edge TTS is now always registered. --------- Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
dc51018563 |
fix: subagent provider routing + api_base fallback (#262)
* fix(subagent): inherit parent agent's provider instead of alphabetical fallback Subagents previously used a fixed provider (alphabetically first from the registry, often "anthropic") regardless of which provider the parent agent used. This caused invalid combos like anthropic/glm-5 when a zai-coding agent spawned subagents. - Pass provider registry to SubagentManager for runtime resolution - Inject parent provider name into context (WithParentProvider) - Resolve activeProvider from parent context before LLM call - Fix trace spans to show actual resolved provider, not default * fix(providers): api_base fallback from config/env for DB providers DB providers with empty api_base now inherit from config/env vars (e.g., GOCLAW_ANTHROPIC_BASE_URL). Prevents proxy API keys from being sent to the real provider API endpoint. - Add APIBaseForType() method on ProvidersConfig - registerProvidersFromDB falls back to config when api_base is empty - ProvidersHandler uses resolveAPIBase() for model listing - Add api_base, display_name, settings to provider validation whitelist * fix(tracing): pass resolved provider name to subagent span emitters - emitSubagentSpanStart now accepts providerName param instead of reading sm.provider.Name() — ensures root subagent span reflects the inherited parent provider, not the fallback default - registerInMemory now uses resolveAPIBase() so DB providers with empty api_base inherit the config/env fallback (same as startup path) --------- Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
7436d6b646 |
fix(i18n): add missing config.json locale files (#108)
* fix(i18n): add missing config.json locale files The config namespace was registered in i18n/index.ts but the actual JSON files were never created, breaking the UI Docker build (tsc error). Also fix .gitignore: config.json → /config.json so only the root gateway config is ignored, not the i18n locale files. Closes #107 * feat(ui): replace language cycle button with dropdown select Users can now pick a language directly from a dropdown instead of cycling through all options on each click. * fix(i18n): keep "Subagent" in Vietnamese translations "Agent con" and "sinh sản" sound unnatural; keep the English term. * chore: align .dockerignore config.json pattern with .gitignore |
||
|
|
e63ff014cb |
feat: add Z.ai provider support (general API + coding plan) (#102)
* feat: add Z.ai provider support (general API + coding plan) Add Z.ai (GLM) as a new LLM provider with two variants: - `zai`: general API (api.z.ai/api/paas/v4) - `zai_coding`: coding plan (api.z.ai/api/coding/paas/v4) Reuses OpenAIProvider — Z.ai API is OpenAI-compatible with Bearer token auth, SSE streaming, and reasoning_content support. Includes: store constants, config struct fields, env var loading (GOCLAW_ZAI_API_KEY, GOCLAW_ZAI_CODING_API_KEY), secret masking, config + DB registration, onboard wizard, and UI provider types. Default model: glm-5 Closes #100 * docs: add Z.ai provider entries to providers documentation |
||
|
|
e05a4018c9 |
fix: use platform type instead of instance name in system prompt + Zalo group routing (#90)
* 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. |
||
|
|
137a986d4f |
feat(channels): add Slack channel (#83)
* feat(channels): add Slack channel via Socket Mode (#37) Implement Slack integration using Socket Mode (xapp-/xoxb- tokens): - Event-driven messaging via app_mention + message events - Policy checks: open, pairing, allowlist, disabled (DM + group) - Thread participation with configurable TTL - Markdown-to-mrkdwn formatting pipeline - Streaming support (edit-in-place + native ChatStreamer) - SSRF-protected file downloads - Debounce, dedup, reactions, group history context - 170 unit tests (format, helpers, stream, SSRF) Fix BaseChannel.HandleMessage allowlist to also check chatID, enabling group allowlist with channel IDs across all channels. Closes #37 * feat(slack): add file/media support and edit-to-mention handling - Wire inbound file download into handleMessage (images, audio, documents) - Add media.go with resolveMedia, classifyMime, buildMediaTags - Extract shared ExtractDocumentContent to channels/media_utils.go (DRY with Telegram) - Support file_share and message_changed subtypes - Handle edit-to-mention: respond when user edits old message to add @bot - Add MediaMaxBytes config field (default 20MB) - Fix debounce media accumulation (was silently dropping files) - Add 60s HTTP client timeout on file downloads - Refactor downloadFile signature for slack.File compatibility |
||
|
|
84113cff2f |
fix(channels): fix Zalo personal group pairing bypass and reply thread type (#76)
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. |
||
|
|
0f5dd08f76 |
feat(channels): introduce Zalo Personal channel integration (#32)
* feat(channels): implement Zalo Personal Chat (ZCA) protocol layer Implement complete Zalo Personal Chat integration including: - Message protocol layer (request/response/event types) - Connection management with auth flow - Message sending/receiving with text and media support - User/group management and sync - Telegram-style contact and conversation handling - Comprehensive unit tests with 85%+ coverage Architecture follows existing channel patterns (Telegram, Feishu) with raw API calls for session management and message delivery. Includes error handling, rate limiting awareness, and logging. * feat(channels): add Zalo Personal channel integration layer Wire protocol package to GoClaw's channel system: - channel.go: Channel struct, Start/Stop/Send, listenLoop, message handlers - auth.go: credential resolution (preloaded > file > QR), persistence - policy.go: DM/group policy, @mention gating, pairing with debounce - factory.go: managed mode factory (requires credentials, no QR) - cmd/gateway.go: register standalone + managed factory * feat(ui): add Zalo Personal channel type to web dashboard Add zalo_personal to channel type dropdown, credential fields (IMEI, cookie, userAgent), and config schema (DM/group policy, require_mention, allow_from). * feat(channels): add WebSocket QR login for Zalo Personal channel Add real-time QR code login flow for zalo_personal channel instances in managed mode. Users create an instance without credentials, then trigger QR login from the web dashboard. Backend: - New RPC method zalo.personal.qr.start with per-instance mutex - QR PNG pushed via client-scoped WS events (not broadcast) - Credentials encrypted and saved to DB on successful scan - Cache invalidation triggers automatic channel reload/start - Factory returns nil,nil for missing credentials (skip, not error) - Instance loader handles nil-channel gracefully Frontend: - ZaloPersonalQRDialog with auto-start, retry, and auto-close - QR button in channel instances table for zalo_personal type - Credential fields no longer required (auto-populated via QR) * fix(channels): skip redundant LoginWithCredentials after QR login QR flow already validates session via qrCheckSession + qrGetUserInfo. Calling LoginWithCredentials again conflicts with the active QR session state, causing "empty response" errors. Credentials are validated when the channel starts instead. Also rename log prefix from "zca" to "Zalo Personal". * fix(channels): fix Zalo Personal cookie domain for login API BuildCookieJar only set cookies for chat.zalo.me but the login API uses wpa.chat.zalo.me. Cookies weren't sent to the subdomain, causing "empty response" on channel startup. Now sets cookies for both hosts. * fix(channels): move UTF-8 check after gzip decompression in Zalo listener The UTF-8 validity check in decryptAESGCMPayload ran on raw decrypted bytes before gzip decompression, causing all encType=2 (AES-GCM+gzip) messages to fail with "decrypted payload is not valid UTF-8". Move the check to decryptEventData so it runs after all processing (decryption + decompression) is complete. * feat(channels): add QR-only onboarding and contacts picker for Zalo Personal - Remove credential text fields for zalo_personal, show QR auth info banner - Add has_credentials boolean to HTTP and WS mask functions - Implement FetchFriends/FetchGroups protocol (encrypted Zalo API) - Add zalo.personal.contacts WS RPC method with parallel fetch - Create ZaloContactsPicker component with search, selection, manual entry - Integrate picker in channel instance edit dialog for allow_from config * refactor(channels): rename zca error prefix to zalo_personal across protocol package * fix(channels): unwrap inner response envelope in Zalo contacts decryption The Zalo API returns double-wrapped responses: outer envelope contains encrypted base64 data, which when decrypted yields another Response envelope with error_code and data fields. The decryptDataField helper was returning the raw decrypted bytes without unwrapping the inner envelope, causing json unmarshal failures when parsing friends/groups. * fix(channels): pass version 0 for group details to get full data The Zalo group info endpoint uses a version-based caching mechanism. Passing the actual version from step 1 causes the server to return the group in "unchangedsGroup" with empty "gridInfoMap". By passing version 0 for all groups, we force the server to return full group info including name, avatar, and member count. * fix(ui): auto-load contacts on modal reopen to resolve display names When the edit modal is reopened with already-selected contact IDs, contacts are now auto-fetched so badges show display names instead of raw numeric IDs. * fix(channels): handle gzip-compressed response in Zalo SendMessage SendMessage used io.ReadAll + json.Unmarshal directly but the response is gzip-compressed (Accept-Encoding: gzip header). Use readJSON() which handles gzip decompression, fixing "invalid character '\x1f'" errors. * fix(channels): decrypt encrypted send response in Zalo SendMessage The Zalo send message API response is encrypted like all other endpoints. Parse outer envelope, decrypt the data field, then extract msgId from the decrypted inner response. * feat(channels): improve Zalo listener reliability and UI channel wizard - Migrate WebSocket client from gorilla to coder/websocket, eliminating unsafe/reflect hacks for RSV1 decompression and buffer inspection - Add channel-level restart with exponential backoff (2s→60s cap, max 10) so channels auto-recover instead of stopping permanently - Reset listener retry counters after 60s stable connection to prevent long-lived connections from exhausting retry budget - Add code 3000 (duplicate session) recovery with 60s initial delay - Detect silent disconnects via read deadline (2.5x ping interval) - Fix Stop() to always cancel context, preventing reconnect timer leaks - Refactor UI channel form into wizard-based flow with registry pattern - Auto-refresh channel status after create/update dialog closes * refactor(channels): move Zalo RPC methods to zalomethods package Move Zalo personal channel RPC handlers from internal/gateway/methods to internal/channels/zalo/personal/zalomethods, improving code organization and removing prefix redundancy. Rename types: ZaloPersonalQRMethods → QRMethods, ZaloPersonalContactsMethods → ContactsMethods. - Move zalo_personal_qr.go → zalomethods/qr.go - Move zalo_personal_contacts.go → zalomethods/contacts.go - Update imports in cmd/gateway.go (2 call sites) - Update internal/channels/zalo/personal imports * feat(channels): add typing indicator to Zalo Personal channel Show "typing..." in Zalo while the LLM processes messages, matching the Telegram/Discord pattern. Uses the shared typing.Controller with 4s keepalive (Zalo typing expires ~5s) and 60s TTL safety net. * feat(channels): handle image attachments in Zalo Personal channel - Add Raw field to Content struct to preserve non-string JSON payloads - Add Attachment struct with IsImage() detection (ext + Zalo CDN paths) - Add AttachmentText() for human-readable placeholders (image/file/other) - Download image attachments to temp files for agent vision pipeline - Non-image files get text placeholder only (no download) - Fix URL query param stripping in file extension detection * fix(channels): switch Zalo WS client to gorilla/websocket with cookie jar fix coder/websocket did not propagate session cookies for wss:// URLs, causing Zalo backend to reject connections with "zpw_sek not found". Switch to gorilla/websocket which handles wss→https scheme conversion natively. Add wsJar safety wrapper and fix Close() mutex consistency. Also update Makefile `up` target to use --no-cache builds. * fix(channels): inject cookies manually for Zalo WS connection Replace wsJar wrapper with direct cookie injection from chat.zalo.me base domain. Fixes host-only cookies (zpw_sek) not matching WS subdomains (ws*-msg.chat.zalo.me) due to Go cookiejar limitations. * fix(channels): harden Zalo Personal channel security and concurrency - Add SSRF protection to downloadFile using CheckSSRF (URL validation, private IP blocking, DNS pinning) with context and 30s timeout - Protect c.sess/c.listener with sync.RWMutex to eliminate data races during restart; add thread-safe session()/getListener() accessors - Add stopped flag + reconnTimer to Listener to prevent zombie reconnects after Stop(); timer cancelled on Stop(), checked before Start() - Fix QR flow using context.Background() detached from WS client; now derives from parent ctx so flow cancels on client disconnect - Set initial 30s read deadline for cipher key handshake to prevent indefinite blocking before ping loop starts - Use defer in WSClient.Close() to prevent connection leak on panic - Document ReadMessage ctx limitation and two-layer reconnect design * chore: remove unused gobwas/ws dependency from go.mod gobwas/ws was a leftover from the previous coder/websocket usage, no longer imported by any Go source files. * fix(channels): align Zalo Personal policy defaults across UI and backend Policy defaults were inconsistent across three layers causing group/DM allowlist enforcement to silently fail. New() applied "allowlist" default to local vars but never wrote back to config; checkGroupPolicy() then read empty string and defaulted to "open", bypassing the allowlist. UI Select components displayed schema defaults visually without persisting them to configValues, so DB config never stored the policy. |
||
|
|
f1397081d2 |
feat(skills): per-agent skill filtering with grant-based access control (#45)
* fix(store): expand tilde in skills storage directory path The default skillsDir (~/.goclaw/skills-store) was not expanded, causing os.MkdirAll to fail when creating skill upload directories. * feat(skills): per-agent skill filtering with grant-based access control (#42) Wire skill_agent_grants into the agent resolver so each agent only sees skills explicitly granted to it. Add Skills tab to the web UI for managing per-agent skill grants with toggle switches. - Add SkillAccessStore interface to avoid import cycles - Filter skills in resolver via ListAccessible + filesystem union - Add GET /v1/agents/:id/skills endpoint with grant status - Invoke onGrantChange callback to invalidate agent caches on grant/revoke - Add agent-skills-tab React component with Switch toggles - Allow read_file access to managed skills-store directory - Fix rows.Err() propagation in ListAccessible/ListWithGrantStatus Closes #42 |
||
|
|
4c67dff24d |
feat(providers): support custom base URL for Anthropic provider (#16)
Allow overriding the Anthropic API base URL via GOCLAW_ANTHROPIC_BASE_URL env var, config JSON, or DB provider record. Enables use of Anthropic- compatible proxies and custom endpoints. Also adds Makefile shortcuts for docker compose (up/down/logs). |