Add mid-loop compaction to prevent context overflow during long-running
delegated agent runs (e.g. 225K+ tokens causing DashScope timeouts).
Uses same threshold as maybeSummarize (contextWindow * historyShare)
with actual PromptTokens from LLM response. Only compacts the in-memory
messages slice; pendingMsgs preserves full history for session flush.
Add user_id/channel columns to team_tasks so end users only see their
own tasks. Delegate/system channels bypass the filter to see all tasks.
Group chats use the group-scoped UserID (group:channel:chatID) so all
members share visibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix 2 data races in policy.go: sendPairingReply and checkGroupPolicy
accessed c.sess without the read lock — use c.session() accessor
- Fix credentials directory permissions: 0755 → 0700 to prevent other
users from listing contents
- Revert Makefile --no-cache (debugging leftover that disables Docker
layer caching)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 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.
If probeBotInfo fails (missing bot:read permission), botOpenID is empty
and mention detection always returns false — causing all group messages
to be silently recorded to history instead of processed.
Now treats any mention as bot mention when botOpenID is unknown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed Lark group pairing to use "group:<chatID>" as sender_id
(matching Telegram pattern) so one approval covers the entire group.
Added approvedGroups in-memory cache to avoid DB queries per message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When LLMs call team_tasks create + spawn in parallel, the spawn
tool receives a hallucinated task_id that fails uuid.Parse, causing
a misleading error and bypassing orphan detection.
- Include pending task IDs in spawn error message so LLM can retry
with the correct UUID
- Move spawn counting to post-execution so failed spawns don't
increment teamTaskSpawns, allowing orphan detection to fire
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 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
* fix(store): handle NULL JSONB columns in MCP server scan
Scan JSONB nullable columns (args, headers, env, settings) into *[]byte
instead of directly into json.RawMessage to prevent silent scan failures
when database values are SQL NULL. Also initialize result slices with
make() to return empty JSON arrays instead of null.
* fix(store): keep settings scanning direct since column is NOT NULL
Port 4 missing Telegram forum/topic features from TypeScript OpenClaw:
1. Thread-not-found fallback: retry sends without message_thread_id when
a topic is deleted (sendHTML, sendPhoto, sendVideo, sendAudio,
sendDocument, stream flush).
2. Per-topic config: hierarchical config resolution (global → wildcard
group "*" → specific group → specific topic) for groupPolicy,
requireMention, allowFrom, enabled, skills, systemPrompt.
New TelegramGroupConfig/TelegramTopicConfig structs, resolveTopicConfig()
with 10 unit tests.
3. DM topic support: preserve message_thread_id in private chats for
session isolation. New BuildDMThreadSessionKey, parseRawChatID handles
🧵 suffix.
4. createForumTopic agent tool: ForumTopicCreator interface decoupled
from telego, lazy bot resolution via channel manager.
5. Web UI: structured group/topic config form with tri-state booleans
(Inherit/Yes/No), nested collapsible group and topic entries.
Also fix: forum group pairing reply and approval notification now
correctly set MessageThreadID so messages land in the right topic.
Send() extracts threadID from localKey suffix as fallback for cases
where metadata is absent (e.g. pairing approval via SendToChannel).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix resolvePath for nested non-existent dirs (use resolveThroughExistingAncestors)
- Channel-isolated workspace: user_agent_profiles.workspace stores channel prefix,
used as source of truth with backward compat for existing users
- Loop caches workspace per-user with CacheKindUserWorkspace invalidation via pubsub
- ContractHome/ExpandHome for portable ~-based paths in DB
- create_image saves to workspace/generated/YYYY-MM-DD/ instead of OS temp dir
- SOUL.md template: add ## Expertise section for domain knowledge
- Summoner buildEditPrompt: section guide, complete file output, frontmatter update
- Bus: Topic* constants for Subscribe/Broadcast keys, CacheKind* for payload kinds
- Teams, delegates, sessions, agent links: various enhancements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update sandbox image name, container prefix, Docker labels, and MCP client info
from 'openclaw' to 'goclaw' to match the repository project name.
Changes:
- internal/sandbox/sandbox.go: image and container prefix
- internal/sandbox/docker.go: Docker label and fallback prefix
- docker-compose.sandbox.yml: image reference in comments and env
- internal/mcp/manager_connect.go: MCP client info name
* fix: invalidate ContextFileInterceptor cache on agents.files.set
When agents.files.set (or agents.update) wrote context files to
agent_context_files in Postgres, only the agent router cache was
invalidated via agents.InvalidateAgent(). The ContextFileInterceptor
maintains its own separate in-memory cache with a 5-minute TTL which
was never cleared, causing the agent to serve stale file content
(e.g. wizard-written SOUL.md/IDENTITY.md) for up to 5 minutes.
Fix: inject *tools.ContextFileInterceptor into AgentsMethods and call
intc.InvalidateAgent(ag.ID) immediately after each successful DB write
in handleFilesSet and handleUpdate.
Wire path: wireManagedExtras now returns the interceptor it creates;
gateway.go captures it and passes it through registerAllMethods →
NewAgentsMethods. The interceptor is nil in standalone mode so all
nil guards are in place.
* test: add unit tests for ContextFileInterceptor cache invalidation
Covers:
- Cache hit: second read does not call store again
- InvalidateAgent clears agent cache → fresh content served from store
- InvalidateAgent clears user cache for that agent
- InvalidateAgent does not affect other agents' cache entries
- TTL expiry causes re-fetch from store
Replaces List(ctx, "") + loop with a single-row query
(ORDER BY is_default DESC, created_at ASC LIMIT 1).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In managed mode, agents live in the database, not in config files.
ResolveDefaultAgentID() always returns "default" which doesn't exist
as a real agent, causing heartbeat to silently fail.
Add resolveDefaultAgentManaged() helper that checks the managed DB
for the agent marked is_default=true, falling back to the first
available agent. Apply it in setupHeartbeat().
* fix: SeedUserFiles uses agent-level USER.md as seed for predefined agents
For predefined agents, the wizard writes a populated USER.md (owner
name, language, notes) via agents.files.set into agent_context_files
(agent-level). On the first chat message, SeedUserFiles was seeding a
blank embedded template into user_context_files (per-user level).
LoadContextFiles for predefined agents lets per-user content override
agent-level content, so the blank per-user USER.md shadowed the
wizard-written content. The owner profile was stored in the DB but
never served to the agent, triggering unnecessary onboarding.
Fix: before falling back to the embedded template, check whether
agent_context_files already has a non-empty USER.md for the agent.
If it does, use that content as the per-user seed. The check is
conditional (predefined agent + USER.md not yet seeded per-user) so
it adds at most one extra DB read per new user.
The existing "don't overwrite" guard (hasFile check) is unchanged —
personalized per-user content written through conversation is still
never overwritten.
* test: add unit tests for SeedUserFiles agent-level USER.md seeding
Covers:
- Predefined agent: wizard-written USER.md at agent level is used as
per-user seed (primary regression test for the bug fix)
- Predefined agent: falls back to blank embedded template when no
agent-level USER.md exists
- Predefined agent: existing per-user content is never overwritten
- Open agent: embedded template path is unaffected
- Predefined agent: BOOTSTRAP.md uses BOOTSTRAP_PREDEFINED.md template
- Idempotency: second SeedUserFiles call seeds nothing
* fix(bootstrap): SeedUserFiles uses agent-level USER.md as seed for predefined agents
Use map[string]string for agent-level files (more general, extensible)
and clean continue-based loop flow, matching the investigation spec
in plans/goclaw-pr/goclaw-pr-seed-user-files.md exactly.
Previous implementation used a single string and if/else structure —
functionally correct but diverged from the spec in variable type,
load condition, and loop exit pattern.
* fix: SeedUserFiles uses agent-level USER.md as seed for predefined agents
For predefined agents, the wizard writes a populated USER.md (owner
name, language, notes) via agents.files.set into agent_context_files
(agent-level). On the first chat message, SeedUserFiles was seeding a
blank embedded template into user_context_files (per-user level).
LoadContextFiles for predefined agents lets per-user content override
agent-level content, so the blank per-user USER.md shadowed the
wizard-written content. The owner profile was stored in the DB but
never served to the agent, triggering unnecessary onboarding.
Fix: before falling back to the embedded template, check whether
agent_context_files already has a non-empty USER.md for the agent.
If it does, use that content as the per-user seed. The check is
conditional (predefined agent + USER.md not yet seeded per-user) so
it adds at most one extra DB read per new user.
The existing "don't overwrite" guard (hasFile check) is unchanged —
personalized per-user content written through conversation is still
never overwritten.
* test: add unit tests for SeedUserFiles agent-level USER.md seeding
Covers:
- Predefined agent: wizard-written USER.md at agent level is used as
per-user seed (primary regression test for the bug fix)
- Predefined agent: falls back to blank embedded template when no
agent-level USER.md exists
- Predefined agent: existing per-user content is never overwritten
- Open agent: embedded template path is unaffected
- Predefined agent: BOOTSTRAP.md uses BOOTSTRAP_PREDEFINED.md template
- Idempotency: second SeedUserFiles call seeds nothing
* fix(bootstrap): SeedUserFiles uses agent-level USER.md as seed for predefined agents
Use map[string]string for agent-level files (more general, extensible)
and clean continue-based loop flow, matching the investigation spec
in plans/goclaw-pr/goclaw-pr-seed-user-files.md exactly.
Previous implementation used a single string and if/else structure —
functionally correct but diverged from the spec in variable type,
load condition, and loop exit pattern.
* fix(agents): predefined agents visible in web UI for channel members
Two bugs combined to make wizard-installed predefined agents invisible
in the web UI for all real users.
Bug 1 — agents.create WS hardcoded owner_id = "system":
- Add `owner_ids []string` to WS params struct (omitempty, backward compat)
- Resolve ownerID: use first entry if provided, fall back to "system"
- Allows external provisioning tools to set a real user as owner
Bug 2 — ListAccessible SQL excluded system-owned predefined agents:
- Extend WHERE clause with 4th OR: predefined agents are visible when
the requesting user appears in any enabled channel's allow_from list
- Uses EXISTS + jsonb_array_elements_text(ci.config->'allow_from')
- No schema migration needed — allow_from already populated by wizard
Also adds 4 unit tests for Bug 1 (agents_create_owner_test.go):
- UsesProvidedOwnerID, FallsBackToSystem_WhenAbsent,
FallsBackToSystem_WhenEmpty, MultipleOwnerIDs_UsesFirst
* fix(channels): start outbound dispatcher before channel check
StartAll() returned early when no channels existed at boot,
skipping the dispatchOutbound goroutine. Channels loaded later
via Reload() assumed the dispatcher was running, causing outbound
messages (agent responses) to never reach Telegram.
Move dispatcher startup before the empty-channel early return so
dynamically loaded channels always have a running consumer.
* feat(ui): add LLM provider warning on overview page and ignore plans dir
Show alert when no providers configured or all disabled, linking to provider settings. Add plans/ to .gitignore.
* feat(onboard): add provider connectivity verification and placeholder seeding
- Add onboard_verify.go: verify API keys via POST to chat/completions
endpoint (401/403 = fatal, 400/422 = key valid, 5xx = warn)
- Verify all configured providers before seeding in auto-onboard
- Seed disabled placeholder providers (OpenRouter, Synthetic, AliCloud
API/Sub) for UI discoverability after managed data seeding
* refactor: reuse provider layer for bootstrap key verification
- Replace raw HTTP requests with provider.Chat() calls, matching the
existing verify pattern in internal/http/provider_verify.go
- Respect config base URL overrides (GOCLAW_ANTHROPIC_BASE_URL, etc.)
- Only block bootstrap if the primary provider key is invalid;
secondary provider failures are logged as warnings
- Add friendlyProviderError for human-readable error extraction
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: ntduc <ntduc@cpp.ai.vn>
Co-authored-by: viettranx <viettranx@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add GitHub Actions workflow to publish Docker images to GHCR
Build and push all 4 variants (latest, otel, tsnet, full) on version
tags with multi-platform support (linux/amd64, linux/arm64).
Closes#19
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add web UI image build to GHCR publish workflow
Adds a separate job to build and push the ui/web Dockerfile as
ghcr.io/<repo>-web with the same multi-platform and tagging strategy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Bruno Clermont <bruno.clermont@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(channels): start outbound dispatcher before channel check
StartAll() returned early when no channels existed at boot,
skipping the dispatchOutbound goroutine. Channels loaded later
via Reload() assumed the dispatcher was running, causing outbound
messages (agent responses) to never reach Telegram.
Move dispatcher startup before the empty-channel early return so
dynamically loaded channels always have a running consumer.
* feat(ui): add LLM provider warning on overview page and ignore plans dir
Show alert when no providers configured or all disabled, linking to provider settings. Add plans/ to .gitignore.
---------
Co-authored-by: ntduc <ntduc@cpp.ai.vn>
[]byte fields were base64-encoded during JSON serialization, causing
frontend TypeError when calling .join() on non-array values. Added
Array.isArray() guards in UI as defensive fallback.
* fix(channels): start outbound dispatcher before channel check
StartAll() returned early when no channels existed at boot,
skipping the dispatchOutbound goroutine. Channels loaded later
via Reload() assumed the dispatcher was running, causing outbound
messages (agent responses) to never reach Telegram.
Move dispatcher startup before the empty-channel early return so
dynamically loaded channels always have a running consumer.
* feat(ui): add LLM provider warning on overview page and ignore plans dir
Show alert when no providers configured or all disabled, linking to provider settings. Add plans/ to .gitignore.
* fix: use model ID as display name in OpenAI-compatible provider list
The `owned_by` field (e.g. "system") was incorrectly used as the model
display name, causing all models to show as "system" in the UI dropdown
for providers like AliCloud DashScope.
---------
Co-authored-by: ntduc <ntduc@cpp.ai.vn>
Team delegation:
- Unify spawn/subagent/delegate into single spawn tool
- Sibling-aware announce suppression with artifact accumulation
- Fix auto-complete race (isLastDelegation guard)
- Add team tasks list limit (20) with search guidance
- Multi-round orchestration patterns in TEAM.md
- Communication guidance for initial vs follow-up delegations
Telegram resilience:
- Add retrySend wrapper (3 attempts, escalating delay) for network errors
- Fix HTML fallback: strip tags + unescape entities instead of showing raw HTML
- Pre-process HTML tags in LLM output to markdown before conversion pipeline
- Skip caption truncation entirely when > 1024 bytes, send text separately
- Auto-send large images (>5MB) as documents to avoid compression
Artifact forwarding:
- Fix missing ContentType on forwarded media (mimeFromExt for result.Media/ForwardMedia)
- Add deliver parameter to write_file for file attachment delivery
- Extend mimeFromExt with document MIME types
UI: fix regenerate dialog overflow, improve task list layout, delegation detail view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add teams.members.add and teams.members.remove RPC methods with
bidirectional link management and cache invalidation
- Add DeleteTeamLinksForAgent to AgentLinkStore for link cleanup
- Add member add/remove UI in team detail Members tab
- Widen agent create dialog (sm:max-w-4xl) to fix cramped layout
- Fix combobox dropdown truncation (min-w-full instead of w-full)
- Fix traces refresh button animation using isFetching instead of isLoading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Simplify cron session key to `agent:{agentId}:cron:{jobID}` (remove redundant `:run:{runID}`)
- Route cron jobs through scheduler's cron lane instead of calling loop.Run() directly
- Scheduler enforces per-session maxConcurrent=1, preventing same job from running concurrently
- Parallelize due job execution with goroutines + WaitGroup (PG and file store)
- Move scheduler creation before cron setup in gateway.go initialization order
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add @tanstack/react-query with shared cache and centralized query keys
- Migrate all 14 CRUD hooks from manual useState/useCallback to useQuery/useMutation
- Add WS event-driven query invalidation (sessions, traces auto-refresh on run completion)
- Consolidate LlmConfigSection to delegate provider/model UI to ProviderModelSelect
- Add server-side pagination for custom-tools and channel-instances (Go store + HTTP handler)
- Extract shared types into dedicated files (provider, custom-tool, mcp, channel, skill, trace)
- Add network error handling in HTTP client and connectivity check on login
- Add disconnect banner in app layout when gateway connection is lost
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement full thinking mode system: per-agent thinking_level config (off/low/medium/high)
stored in other_config JSONB, per-provider param injection (Anthropic budget_tokens, OpenAI
reasoning_effort, DashScope enable_thinking+thinking_budget), Anthropic extended thinking with
streaming parse and tool use block preservation via RawAssistantContent, thinking token tracking
in trace spans, and Web UI with agent config selector, chat thinking block rendering, and trace
thinking token display. No DB migration needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add DashScope (Qwen) native provider with tools+streaming fallback
- Add Bailian Coding provider with hardcoded model list (no /v1/models API)
- Parse reasoning_content in OpenAI-compat streaming/non-streaming responses
- Emit ChatEventThinking events in agent loop for thinking models
- Add vision support for DashScope (qwen3-vl)
- Fix provider form dialog not updating API base URL when switching types
- Update README provider count from 11+ to 13+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>