- docs/agent-hooks.md: handler reference, lifecycle events, security model - examples/hooks/: 5 runnable JSON configs (audit, lint, block-rm-rf, Discord notify, context injector) - docs/17-changelog.md: Wave 0 entry - Makefile: hooks-specific test targets - CLAUDE.md: cross-reference
64 KiB
Changelog
All notable changes to GoClaw Gateway are documented here. Format follows Keep a Changelog.
[Unreleased] — 2026-04-15
Agent Hooks System — Phase 3: Prompt Handler + Web UI (2026-04-15)
Third handler type (prompt) with full cost safeguards, WS RPC surface, complete Web UI, 3-language i18n, and production wiring. Closes Issue #875 feature parity with Claude-Code-style hooks.
Added
internal/hooks/handlers/prompt.go: LLM hook handler with structured tool-call output (decidetool), anti-injection system preamble, fenced user-input delimiter, in-memory decision cache (60s TTL keyed bysha256(hook_id||version||tool_name||tool_input)), per-turn invocation cap (default 5), provider resolver abstractioninternal/hooks/handlers/prompt_resolver.go:RegistryResolvermaps model aliases (haiku/sonnet/opus, gpt-, gemini-, qwen-*) to tenant providers; falls back to system configs (hooks.prompt.provider,background.provider) then first registeredinternal/hooks/budget/package:Store+Dialectinterface for atomic per-tenant monthly token budget deduction;ShouldWarnhelper at 20% threshold- Budget store implementations:
pg.PGHookBudget(single UPDATE + RETURNING with UPSERT seed on month rollover) andsqlitestore.SqliteHookBudget(tx-wrapped equivalent) - WS RPC methods (
internal/gateway/methods/hooks.go):hooks.list(viewer+),hooks.create/update/delete/toggle(admin+, master-scope for global),hooks.test(operator+, dryRun no audit write),hooks.history(viewer+, stub pending phase 4 pagination) - Protocol constants:
MethodHooksList/Create/Update/Delete/Toggle/Test/Historyinpkg/protocol/methods.go - Web UI
/hookspage: single route withuseParams().id— list view when no id, detail view when present (CLAUDE.md "route params as source of truth"). Tabs: Overview | Config | Test | History - Web UI components:
hook-list-row,hook-form-dialog(3 sub-forms per handler type; command radio disabled on Standard with tooltip),hook-test-panel(firesdryRun=true, decision badge + duration + stdout/stderr/status),hook-diff-viewer(side-by-side JSON diff via highlight.js),hook-history-table,hook-overview-tab - Zod + TanStack Query:
schemas/hooks.schema.ts(prompt requires matcher|if_expr + prompt_template),hooks/use-hooks.ts(useHooksList/Hook/CreateHook/UpdateHook/DeleteHook/ToggleHook/TestHook/HookHistory) - i18n backend: 6 new keys (
hook.invalid_matcher,hook.command_disabled_standard,hook.prompt_requires_matcher,hook.circuit_breaker_tripped,hook.budget_exceeded,hook.per_turn_cap_reached) in en/vi/zh catalogs - i18n UI:
ui/web/src/i18n/locales/{en,vi,zh}/hooks.jsonwith identical key ordering (per CLAUDE.md memory rule) - Production wiring:
cmd/gateway_managed.goregistersHandlerPromptin dispatcher handlers map;cmd/gateway.goregistersHookMethodsin router - Sidebar nav: "Hooks" entry with
Webhookicon in Capabilities group
Security / cost safeguards
- Structured output enforcement: evaluator MUST call the
decidetool; free-text responses fail-closed to block - Sanitized input: only
tool_inputreaches the evaluator inside a fencedUSER INPUTblock; raw user message never included - Injection detection flag: evaluator can signal
injection_detected=truevia structured output - Version-busted cache: hook config edits bump
version→ cache key changes → forces re-evaluation - Atomic budget deduct: no select-then-update race (L2 mitigation)
Testing
- Unit:
prompt_test.go(14 tests — cache hit/miss, version bust, per-turn cap, fail-closed paths, schema assertions),prompt_injection_test.go(4 tests — hostile inputs, unicode homoglyphs, nested JSON, fenced delimiter integrity),budget_test.go(9 tests — atomic deduct, exceed-returns-err, month rollover, zero-cost, nil tenant, warn threshold) - WS RPC unit:
gateway/methods/hooks_test.go(param parsing + HookTestResult struct round-trip) - UI: 56/56 vitest passing including 20 new hook-specific tests (schema validation, list rendering, test panel, diff viewer, i18n contracts)
Agent Hooks System — Phase 4: Hardening + Docs (2026-04-15)
Integration / chaos / RBAC / tracing coverage + user documentation + copy-paste example hooks library. Merge gate.
Added
- Integration tests (
tests/integration/hooks_e2e_test.go): 7 events × HTTP/command handler coverage, tenant isolation (cross-tenant events don't hit other tenants' hooks), context-update injection path, edition gate at dispatch time - Chaos tests (
hooks_chaos_test.go): provider down (fail-closed block on blocking event), per-hook timeout respected, circuit breaker auto-disables after 3 consecutive blocks and persistsenabled=false,ErrLoopDepthExceededwhen depth >MaxLoopDepth(M5),dedup_keyunique index suppresses double audit rows on retry (H6), 5xx retry-once-then-error - RBAC tests (
hooks_rbac_test.go): store tenant isolation (List/Get/Update/Delete all respect tenant_id), global-scope visible to all tenants, ResolveForEvent unions tenant + global hooks,HasMinRolematrix over the hooks.* method surface - Tracing tests (
hooks_tracing_test.go):EmitHookSpanwrites row with canonical namehook.<handler_type>.<event>; dispatcher + traced-handler wrapper emits spans end-to-end - User docs
docs/agent-hooks.md: concepts, security model, handler reference, matcher guide, safeguard table, Web UI walkthrough, observability (audit table + tracing spans + slog keys), troubleshooting, migration notes, known limitations - Example library
examples/hooks/:block-rm-rf.json(command, Lite),auto-lint-after-write.json(http async),audit-tool-usage.json(tenant-wide audit),session-context-injector.json(context bootstrap),notify-discord-on-stop.json(webhook notification),README.mdwith safety guidance
Fixed
PGHookStore.GetByID+SqliteHookStore.GetByIDnow enforce tenant scope: non-master callers only see own + global rows. Previously returned any row by UUID which leaked cross-tenant.- Existing pipeline integration tests now call
security.SetAllowLoopbackForTest(true)via shared helper; no longer flake on SSRF block for httptest endpoints.
Testing
- All hook integration tests pass under
TEST_DATABASE_URLPG container (7 new test files, 18 new top-level tests) go build+go build -tags sqliteonly+go vetcleanpnpm test56/56 pass- Race detector clean for
internal/hooks/...+internal/gateway/methods/
Deferred (post-MVP)
- Cluster-wide prompt decision cache via Redis
- Skill-frontmatter hooks
agenthandler type (inter-agent delegation via prompt instead)- Desktop Wails UI parity for hooks page
- Load/throughput benchmarks (explicitly dropped from Phase 4 scope)
Agent Hooks System — Phase 1: Foundation (2026-04-15)
Lifecycle hook infrastructure: event dispatcher (sync/async paths), audit logging, database schema (PostgreSQL + SQLite), store interface, config validation, edition gating. Handlers + pipeline integration deferred to Phase 2.
Added
internal/hooks/package: Foundational types (HookConfig, HookExecution, Event), StdDispatcher (fire-and-decide with sequential execution), CEL+regex matcher, audit writer (PII-redacted, encrypted), circuit breaker with auto-disable- PostgreSQL migration 000052:
agent_hookstable (id, tenant_id, agent_id, event, handler_type, config, matcher, if_expr, priority, enabled, created_at, updated_at) +hook_executionstable (hook_id, session_id, event, input_hash, decision, duration_ms, error, created_at) with unique dedup_key index - SQLite schema entries: Full schema in
schema.sql+ migration map bump for Lite/desktop edition - HookStore interface + implementations:
PGHookStore(database/sql + pgx/v5) andSQLiteHookStore(modernc.org/sqlite) with List, Create, Update, Delete, LogExecution methods - Security primitives: Loop-depth guard (M5 mitigation, MaxLoopDepth=3), per-hook timeout, chain budget (10s wall-time), circuit breaker with auto-disable (C4 mitigation), fail-closed policy for blocking events
- Edition gating:
HookEditionPolicyallows command handler on Lite only; master-scope blocks command on Standard tenant - Config validation: CEL cost limits, required matcher for prompt handlers, bounded queue per-turn limits
Changed
- Store layer: Added
store.Hooksfield (typed asanyto avoid import cycles) - Base store:
TablesWithUpdatedAt["agent_hooks"] = truefor touch-all update helpers
Testing
- Integration test suite:
tests/integration/v3_hooks_store_test.gocovers multi-tenant isolation, dedup key uniqueness, schema migration up/down for both PG + SQLite
Deferred to Phase 3+
- Prompt handler (modifies input/context dynamically)
- Web UI (CRUD, test panel, history)
- Tracing integration
- i18n keys (en/vi/zh)
Agent Hooks System — Phase 2: Handlers + Pipeline Integration (2026-04-15)
Concrete handler implementations + pipeline wiring. Command handler (shell, Lite-only), HTTP handler (SSRF-hardened), synced at ContextStage/ToolStage/FinalizeStage, async at SessionStart/PostToolUse/Stop, delegate bridging (SubagentStart/SubagentStop).
Added
internal/hooks/handlers/package:CommandHandler(exec wrapper, JSON I/O, env allowlist, edition policy recheck),HTTPHandler(SSRF-hardened HTTP client, retry-once on 5xx, 4xx error no-retry, response parsing for decision/context/updatedInput)- Pipeline dispatcher integration:
PipelineDeps.HookDispatcherfield (nil-safe noop fallback); stages calldeps.Hooks.Fire(ctx, evt)at: ContextStage (SessionStart async + UserPromptSubmit sync), ToolStage (PreToolUse sync before exec + PostToolUse async), FinalizeStage (Stop async) - Copy-on-write staging: UserPromptSubmit + PreToolUse updatedInput buffered per-call; commit only when ALL sync hooks succeed; rejection/timeout discards buffer (H2)
- Delegate event bridge:
delegate_bridge.gosubscribes DelegateCompleted/DelegateFailed events from domain bus, fires SubagentStop async; SubagentStart sync fired directly in delegate_tool pre-call - Command handler edition gating: Re-checks
HookEditionPolicyat dispatch (defense-in-depth); Lite-only with master-scope block on Standard tenant - HTTP handler SSRF hardening: Uses caller-supplied net.Dialer that pins resolved IP, blocks loopback/link-local/private ranges; auth headers (e.g. Authorization) encrypted at rest in cfg.Config["headers"], decrypted only at send-time; no redirects allowed (CheckRedirect returns ErrUseLastResponse)
- Server wiring:
cmd/gateway_managed.goconstructshooks.NewStdDispatcherwith both handlers registered; passes dispatcher throughagent.ResolverDeps.HookDispatcheranddelegateTool.SetHookDispatcher(...); falls back tohooks.NewNoopDispatcher()whenstores.Hooksis nil - Encryption key handling: Reads
GOCLAW_ENCRYPTION_KEYenv for HTTP handler header decryption; optional (empty → no decryption)
Testing
- Unit tests:
internal/hooks/handlers/command_test.go(8 tests — exit codes, timeout, env allowlist, edition gate, ctx cancel),http_test.go(9 tests — response parsing, retry logic, SSRF blocking pre+post DNS, no redirects, auth header security) - Integration tests:
tests/integration/hooks_pipeline_test.go(4 tests — SessionStart async, UserPromptSubmit block + context + updatedInput COW, PreToolUse block + args mutation, PostToolUse async, zero-overhead no-hooks path);tests/integration/hooks_delegate_test.go— SubagentStart/Stop with loop-depth guard M5
Changed
- Pipeline architecture: 8-stage loop now fires hooks via
PipelineDeps.Dispatcher; stage callbacks support nil-safe dispatcher (noop when not configured) - Delegate tool:
delegateTool.SetHookDispatcher(...)enables async SubagentStop via existing domain events
Desktop Voice Management + STT Admin (2026-04-15)
Added
- Desktop voice picker: Agent detail panel now includes a voice selector with live preview. Users can browse ElevenLabs voices, hear a sample, and persist the choice per agent on the desktop app.
- Desktop STT admin form: Speech-to-text provider configuration (API keys, providers, WhatsApp opt-in toggle + privacy banner) available in desktop tool settings.
- Singleton voice preview: Only one voice sample plays at a time; preview button hides automatically when a voice has no sample URL.
- Desktop i18n
ttsnamespace: Voice-management strings translated to English, Vietnamese, and Chinese; STT form labels added to the tools namespace. - Desktop test harness: Vitest + Testing Library infrastructure; voice picker and STT form covered by unit tests.
Changed
- Agent voice persistence (desktop): Voice selection merges into
other_configwithout overwriting sibling fields. - Tool settings routing (desktop): Speech-to-text tool opens the dedicated admin form instead of the generic JSON editor.
ElevenLabs Audio Manager Refactor — Phase 5 (2026-04-15)
Channel STT migration (Telegram/Feishu/Discord) + WhatsApp voice transcription with tenant opt-in (default disabled due to E2E encryption trade-off, resolves privacy red-team finding).
Added
- WhatsApp voice transcription:
internal/channels/whatsapp/stt.go— opt-in per-tenant viabuiltin_tools[stt].settings.whatsapp_enabled(defaultfalseper Decision 6) - Synchronous 10s timeout: All 4 channels (Telegram/Feishu/Discord/WhatsApp) enforce ctx timeout for voice message processing; timeout/error → fallback label
- i18n fallback key:
MsgVoiceMessageFallback("voice message" placeholder in en/vi/zh) rendered when STT unavailable or disabled - Privacy banner: Admin UI displays E2E encryption trade-off warning when enabling WhatsApp STT
Changed
- Channel STT handlers: All 4 channels now call
audio.Manager.Transcribe()with 10s timeout instead of per-channeltranscribeAudio()helpers - Channel factory signatures: All 4 factories (
NewChannel) acceptaudioMgr *audio.Managerparameter - WhatsApp messaging: Voice messages now transcribed (opt-in) and appended to message text for agent understanding
Removed
- Per-channel STT helpers: Deleted
internal/channels/{telegram,feishu,discord}/stt.go(consolidated into Manager) - Telegram STT tests: Deleted
internal/channels/telegram/stt_test.go— coverage preserved ininternal/audio/proxy_stt/provider_test.go(12 ported scenarios, deliberate deletion documented)
Resolved
- Audit finding P5-B1: Gate G4 verified; Manager.Transcribe chain wired
- Audit finding P5-H1: All transcribeAudio call sites migrated; grep confirms zero direct channel STT calls
- Audit finding P5-H2 + Decision 6 (privacy): WhatsApp STT opt-in with default=false; privacy banner + docs note E2E trade-off
- Cross-phase XP-3: Telegram test ported/deleted with 12-scenario coverage guarantee
- Cross-phase XP-4:
MsgVoiceMessageFallbackpre-wired in Phase 5 step 1 (blocking)
Security / Privacy
- WhatsApp E2E encryption: Voice transcription breaks end-to-end guarantee. Tenant opt-in required with explicit admin acknowledgment (privacy banner in UI)
- Temp file cleanup: Existing
scheduleMediaCleanuppreserves file handles during 10s STT window; no race condition risk - Audit logging: WhatsApp STT invocation logged at info level for audit trail
ElevenLabs Audio Manager Refactor — Phase 4 (2026-04-15)
STT via ElevenLabs Scribe + proxy fallback with tenant config migration + admin UI form. Legacy per-channel STT config bridged with 2-week deprecation grace.
Added
- ElevenLabs Scribe STT provider:
internal/audio/elevenlabs/stt.go— POST/v1/speech-to-text, multipart upload, 20MB cap, configurable language + diarization - Proxy STT wrapper:
internal/audio/proxy_stt/provider.go— backward-compat bridge for legacymedia.TranscribeAudiowith automatic temp-file handling - STT admin form:
ui/web/src/pages/builtin-tools/stt-provider-form.tsx— mirrors TTS form pattern, includes tenant config (API keys, providers list, WhatsApp opt-in toggle) audio.Manager.Transcribe(): Chain-based STT dispatch with channel-override precedence (per-channel STTProxyURL wins over tenant builtin_tools[stt])- i18n keys:
MsgSTTAllProvidersFailed,MsgSTTLegacyConfigDeprecated,MsgSTTWhatsappPrivacyWarningacross en/vi/zh
Changed
- STT config location: Migrated from per-channel fields (Telegram/Feishu/Discord
STTProxyURL) to tenantbuiltin_tools[stt].settings(Decision 1 per audit) builtin_tools[stt]table entry: New seed row via migration 000050 (PG) + schema.go incremental (SQLite)- Legacy bridge: Startup-time scan of per-channel STT configs → auto-registers
proxy_stt.Providerwith deprecation warn when tenant lacksbuiltin_tools[stt](2-week grace period)
Resolved
- Audit finding P4-H1 (channel-override precedence): Decision 2 — per-channel config takes priority; tenant fallback when channel config absent
- Audit finding P4-H2 (Scribe endpoint verification): ElevenLabs Scribe v2
/v1/speech-to-textconfirmed with xi-api-key auth - Audit finding P4-B3 (explicit bridge loop): 3-channel explicit loop (Telegram, Feishu, Discord) replaces ambiguous iteration
- Cross-phase XP-3 (12 test ported): All 12 Telegram STT scenarios (NoProxy, EmptyFile, Success, Error, etc.) ported into
proxy_stt/provider_test.gobefore Phase 5 channel migration
Deprecated
- Per-channel
STTProxyURL,STTAPIKey,STTTenantIDfields (Telegram, Feishu, Discord configs) — soft deprecation with 2-week grace; migration tobuiltin_tools[stt]required for Phase 5+ channels
Fixed
- JSON schema divergence:
duration_secs→audio_duration_secsin Scribe response parsing (matches ElevenLabs API) - Provider registration: Scribe + SetSTTChain wired into setupAudioExtras for Phase 5 channel integration
ElevenLabs Audio Manager Refactor — Phase 3 (2026-04-14)
Music generation via ElevenLabs + MiniMax with fallback chain. Suno provider fully removed from codebase (no official API, ToS violation risk).
Added
- ElevenLabs Music provider:
internal/audio/elevenlabs/music.go— POST/v1/music, modelmusic_v1, binary MP3 response, 300s timeout, prompt + optional lyrics - MiniMax Music provider:
internal/audio/minimax/music.go— POST{base}/music_generation, 2-step URL-download pattern, 200 MB cap, optional instrumentation toggle
Changed
create_audiotool simplified: Now thin Manager dispatcher (NewCreateAudioTool(audioMgr *audio.Manager)), removed inline provider logic + per-provider switch casesaudio.Manager.GenerateMusic/GenerateSFX: Chain-based resolution (elevenlabs → minimax for music, elevenlabs-only for SFX)createAudiobuiltin tool: Unified dispatch via Manager instead ofproviders.Registrydependency injection
Removed
- Suno provider: Fully excised (10 atomic locations) — research confirms no official API + ToS violations. Files deleted:
create_audio_suno.go,create_audio_minimax.go,create_audio_elevenlabs.goshim. HTTP route removed. TS schema entry removed. Config provider removed.
Types
audio.MusicOptions: AddedInstrumental bool,Model string,TimeoutSec intfieldsaudio.AudioResult: AddedProvider stringfield for observability (tool span metadata)
[v2.66.0] — 2026-04-05
Security
- Session IDOR fix: All 5 chat.* WS methods (send, history, inject, abort, session.status) now verify session ownership. Non-admin users cannot read, write, or disrupt other users' sessions
requireSessionOwnerhelper: Extracted shared ownership check toaccess.go(DRY — pattern was repeated 9x in sessions.go)
Added
- BytePlus ModelArk provider: Seedream image generation + Seedance video generation via BytePlus/Volcengine API
- Per-agent CLI grants: Secure CLI binaries can now be granted/denied per agent with setting overrides
- Beta release pipeline:
release-beta.yaml— pushv*-beta*tag from dev to create prerelease with Docker images + binaries
Fixed
- Scheduler test hang: Defer ordering fix prevents CI timeout when test fails before unblocking goroutines
- Semantic-release branch:
--no-ciflag bypasses default branch check (repo default is dev, releases cut from main) - OpenAI compat: Together/Mistral reasoning, streaming, and vision gating; Mistral tool call ID normalization
Changed
- Docker builds: Removed redundant
docker-publish.yaml—release.yamlhandles all Docker builds on release - Desktop prerelease:
release-desktop.yamlauto-detects beta/rc tags and marks as prerelease
Refactored
- Web UI: React-arch audit — RHF+Zod forms, Zustand persist, adapter layer, component modularization
- Desktop UI: React-arch audit — schemas, RHF forms, file splits, services, store cleanup
[Unreleased]
Added
ElevenLabs Audio Manager Refactor — Phase 1 (2026-04-14)
Unified audio provider management via new internal/audio/ package with pluggable interface-based architecture. Phase 1 wires TTS providers (ElevenLabs, OpenAI, Edge, MiniMax); STT/Music/SFX interfaces defined for Phase 3-4.
What changed:
internal/audio/package: CentralManagerorchestrates 4 provider kinds via interfaces (TTSProvider,STTProvider,MusicProvider,SFXProvider)- Provider organization: Implementations in
internal/audio/{elevenlabs,openai,edge,minimax}/. ElevenLabs shared HTTP client (xi-api-keyheader) for both TTS and SFX subproviders internal/tts/→ backward-compat alias: 24-symbol package (15 types + 6 consts + 5 constructors + 5 signature guards). All pre-refactor callers compile unchanged, zero breaking changes- Config extension:
config.Audiooptional pointer (nil-safe) added for future STT/Music subsections.cfg.Ttsretained unchanged - ElevenLabs SFX tool:
internal/tools/create_audio_elevenlabs.gorewritten as thin shim callingelevenlabs.NewSFXProvider(...).GenerateSFX(ctx, audio.SFXOptions{...}) - WS
tts.*namespace: 6 methods unchanged externally
Impact: Existing TTS flows fully compatible. New code can import internal/audio directly. STT/Music/SFX wiring deferred to Phase 3-4.
ElevenLabs Audio Manager Enhancements — Phase 2 (2026-04-14)
Voice discovery and agent-level audio config via new backend endpoints, in-memory cache, and web UI picker. Bundles producer/consumer context pattern (store.WithAgentAudio) for seamless voice/model resolution throughout the tool execution pipeline.
What changed:
- Voice cache (
internal/audio/voice_cache.go): In-memory LRU (cap 1000 tenants) with TTL 1h, shared between HTTP + WS handlers, thread-safe under concurrent access - Streaming TTS interface (
internal/audio/types.go): NewStreamingTTSProvideroptional interface for ElevenLabs/v1/text-to-speech/{voice_id}/streamchunked playback - ElevenLabs enhancements: Model allowlist (11_v3, eleven_flash_v2_5, eleven_multilingual_v2, eleven_turbo_v2_5),
SynthesizeStream()method,ListVoices()via/v1/voices - Agent audio context (
internal/store/context.go): NewWithAgentAudio/AgentAudioFromCtxbundle; producer wires snapshot at dispatcher level (internal/agent/), consumer (TtsTool.Execute) reads voice/model overrides from agent config - Agent config extension (
agents.other_configJSONB): Newtts_voice_idandtts_model_idfields with resolution precedence: args → agent → tenant → provider default - HTTP + WS endpoints: GET /v1/voices (cached), POST /v1/voices/refresh (admin-only), WS method
voices.list+voices.refresh - Web voice picker (
ui/web/src/components/voice-picker.tsx): Combobox with search, preview button (HTML audio + onError → refresh), embedded in PromptSettingsSection - i18n: 10 new frontend keys (voice_label, voice_placeholder, voice_refresh, voice_preview, model_label, etc.) + 2 new backend keys (MsgTtsUnknownModel, MsgVoicesListFailed) across en/vi/zh
Impact: Existing TTS callers fully compatible (backward-compat via Phase 1 alias layer). Web UI gains voice discovery + per-agent voice/model overrides. Zero breaking changes. Integration test validates producer+consumer context flow.
Trace Stop/Abort Redesign — Cascading 4-Layer Fix (2026-04-14)
The Stop button on the traces page now reliably aborts running traces. Previous implementation had independent race conditions across HTTP streaming, agent router, trace persistence, and UI polling; this redesign fixes all four layers atomically.
What changed:
- HTTP streaming ctx-aware: Provider clients use transport-level
ResponseHeaderTimeout+IdleConnTimeoutinstead of socket-levelClient.Timeout. SSE body closes immediately on ctx cancel via goroutine-based wrapper (prevents 5-minute socket block). - Router 2-phase abort:
AbortRun()atomically transitions to aborting state, waits 3s for goroutine exit viaDonechannel, then force-marks tracecancelledif timeout. No orphaned goroutines, no "not found" race. - Trace status persistence:
SetTraceStatus()detached from request context with 5s timeout, 3-try exponential backoff, and bounded in-memory retry queue (10 max tries). Stale recovery worker runs every 30s, catches zombie traces in 2 minutes instead of 30. - Real-time UI updates: New WS event
trace.statusbroadcasts status changes immediately after persist succeeds. UI drops 60s polling interval, subscribes to events instead. - Tool execution audit: Shell commands use process-group kill (
SIGTERM→3s→SIGKILL). Browser automation (Rod) closes pages on ctx cancel. MCP delegates timeout after 5s. - i18n: 6 abort toast variants (success/timeout/not-found/already-done/db-error/unknown) + translations for en/vi/zh.
Impact: Existing traces and sessions unaffected. UI now reflects backend state accurately. Zero breaking changes.
Preserve User-Provided Filenames for Media Uploads (2026-04-14)
Filenames provided by users for chat media uploads now survive the channel adapter → agent → disk persistence round trip, enabling vault enrichment to process human-readable document names instead of falling back to generic UUID-only storage.
Why: Vault enrichment was skipping UUID-only disk names (design safety to avoid noisy auto-generated files), causing documents with Vietnamese or CJK stems to remain unenriched and lose semantic context.
What changed:
bus.MediaFile.Filenamefield: Channel adapters now populate this field when source provides original filename (e.g., user-selected file upload, Telegram file_name, WhatsApp display_name)- Sanitizer (
internal/agent/media_filename.go): Derives safe stems viasanitizeFilename()with:- Vietnamese pre-NFD map:
đ/Đ → d(Unicode NFD does not decompose these precomposed letters) - CJK passthrough: Dominant-script heuristic detects Vietnamese/CJK inputs and preserves original runes (no ASCII slugification)
- Filesystem safety: Removes control chars, path traversal markers (
..,/,\\), and reserved names - Max length: 60 runes (script-aware, not byte-based) to avoid platform path limits
- Vietnamese pre-NFD map:
- Disk naming scheme:
{sanitized-stem}-{8hex}{ext}(e.g.,bao-cao-q4-a1b2c3d4.pdf) when sanitizer returns non-empty stem; UUID fallback{uuid}{ext}for empty stems (voice notes, clipboard pastes, tool-generated media) - Vault enrichment gating (
internal/vault/enrich_skip_filter.go): Now skips UUID-only filenames (matching^[0-9a-f]{8}-...$pattern) while processing named stems - 6 channel adapters wired: Telegram, Slack, Discord, Feishu/Lark, Zalo OA, WhatsApp all set
MediaFile.Filenamewhen available - Tools + orchestration:
web_searchtool (PDF downloads), delegate/subagent media propagation, all preserve filenames end-to-end
Impact: Existing flows with empty Filename are unaffected (UUID-named as before). New flows with filenames produce human-readable, enrichable disk names. Zero breaking changes.
Security
Tenant-Scope Hotfix (2026-04-12)
3 privilege-escalation vulnerabilities closed, same class as b419f352 (Phase 1 config.* hotfix):
- CRITICAL
PUT /v1/tools/builtin/{name}— non-master admin could corrupt global tool defaults - CRITICAL
POST /v1/packages/install|uninstall— non-master admin could runpip/npm/apkserver-wide - HIGH
POST /v1/api-keys/{id}/revoke(HTTP + WS) — tenant admin could revoke NULL-tenant system keys
Fix adds shared store.IsMasterScope(ctx) predicate + http.requireMasterScope guard on all three endpoints. APIKeyStore.Delete dropped (YAGNI + dormant same-class vuln). WS router now injects role into ctx. Tests: 17 new unit tests. Audit: plans/reports/debugger-260412-0922-tenant-scope-audit.md.
Added
Per-Tenant Tool Configuration — 4-Tier Overlay (2026-04-12)
Tenant admins can override tool configuration without affecting other tenants. Overlay: per-agent > tenant > global > hardcoded, resolved at Execute time via tools.BuiltinToolSettingsFromCtx(ctx) — no Tool interface changes. See docs/03-tools-system.md § 14. Applies to web_search, web_fetch, tts. Web UI dialog is tenant-scope aware.
- Phase 5 (
1e5e84d5): Builtin tools settings editor on web UI - Phase 7 rest (
30a40bbe): Exa + Tavily web search providers with ranked ordering viaprovider_orderconfig. Credit: @kaitranntt for original PR 825 work, ported to tenant settings storage pattern- New files:
internal/tools/{web_search_exa,web_search_tavily,web_search_config}.go - 11 new unit tests for provider chain + normalization
- Provider-selection helper:
NormalizeWebSearchProviderOrder(order []string) → []string(DuckDuckGo always last as free fallback)
- New files:
- Phase 8 (
def1712f,43ee918b): Tenant-aware singleton pool pattern for stateful toolsweb_fetch: Domain policy override viaresolvePolicy(ctx)reads tenant config; 6 new unit teststts: Primary provider override viaresolvePrimary(ctx, mgr)reads tenant config; 5 new unit tests- Feature flag:
config.Tools.TenantScopedSingletons(default: false) gates per-tenant pool instances with LRU eviction (64 tenants) + 30 min idle timeout
Fixed
Pancake Platform Field — Mandatory Select (2026-04-14)
platformfield changed from optional text to required dropdown:channel-schemas.tsreplaces the free-textplatformfield with aselecttype (11 options: facebook, instagram, threads, tiktok, youtube, shopee, line, google, chat_plugin, lazada, tokopedia).required: trueis now enforced by the form dialog on create.- Config required-field validation (create-only):
channel-instance-form-dialog.tsxnow enforcesrequiredon config fields at submit time. Validation is scoped to create flows only — edit flows are unchanged to avoid silent breakage on existing channels with no storedplatform. - i18n: Platform field label options and config field metadata added to
channels.jsonfor en/vi/zh. - Auto-detect log level:
pancake.goauto-detect block demoted fromslog.Infotoslog.Debugto stop log noise on every channel start; auto-detect remains as a fallback for existing channels with no storedplatform.
Feishu/Lark Writer Management Commands — Issue #818 Closed (2026-04-11)
- Issue #818 resolution: Closes UX gap where users saw
/addwritererror messages but Feishu had no handler - Phase 1 — Thread reply routing: Inbound messages with
thread_idnow properly route responses back to the same Feishu thread via/open-apis/im/v1/messages/{id}/reply. Newfeishu_reply_target_idmetadata key included inroutingMetaKeysallowlist. Graceful fallback toSendMessage()if thread root deleted - Phase 2 — Document auto-fetch: Pasted Lark docx URLs auto-detected and fetched via
/open-apis/docx/v1/documents/{id}/raw_content. Content injected as[Lark Doc: URL]markers. LRU cache (128 entries, 5-min TTL) + 8000-rune truncation per document. Requiresdocx:document:readonlypermission + owner grant - Phase 3 — Writer management commands: Added
/addwriter <@user or reply>,/removewriter,/writersfor group file-write permission control. Group-only (DMs rejected early). Requires existing writer authorization. Last-writer guard prevents removing final writer. Empty-writer groups allow bootstrap via explicit/addwriter @self. 10s timeout bounds Feishu API calls
Added
Vault & Knowledge Graph 10k Optimization (2026-04-12)
- Graph visualization endpoints:
GET /v1/vault/graph(cross-tenant) +GET /v1/agents/{agentID}/kg/graph/compactfor rendering document relationships and semantic entities with support for up to 10k nodes (up from 2k default limit) - FA2 layout optimization: Graph layout computation moved to web worker (non-blocking frontend rendering)
- Semantic zoom: UI-level semantic zoom support for graph visualization
DEFAULT_NODE_LIMITincrease: 200 → 2000 nodes per graph view to support larger knowledge bases
Testing
Test Speed-Up + Coverage Ratchet Removal (2026-04-11)
- Philosophy shift: Signal over coverage %. Reject mock-heavy/slow/low-signal tests even if % drops. Coverage ratchet gate removed — was creating pressure to write forced tests instead of fast, valuable ones
internal/vaultretry tests: 16.3s → 0.6s. NewfastBackoffsForTest(t)helper overrides package-levelenrichRetryBackoffs/enrichRetryTimeoutsso retry tests don't wait through real exponential backoff (was 6s per all-retry test). Production behavior unchangedinternal/cronscheduler tests: 11.7s → 1.5s.runLoopTickIntervalextracted as package var (default 1s, unchanged); test-onlysetFastTick(t)helper overrides to 20ms so 6 scheduler tests don't sleep 1.5s each waiting for ticksinternal/channels/facebookretry tests: 6.3s → 3.0s.graphBackoffBaseextracted as package var (default 1s, unchanged);newFakeGraphhelper overrides to 1ms so HTTP retry tests don't burn 3+2+1s of real waits- Vault duplicates removed:
TestCallClassifyWithRetry_FirstAttemptSuccess(dup of_Success) and_MaxRetriesConstant(dup of_RetriesAndBackoffs) - Total saved: ~29s wall-clock (3 packages: 34.3s → 5.1s). Full
go test -race ./...now runs in ~57s (was ≥90s with hangs) - Removed:
scripts/check_coverage.go+scripts/coverage_thresholds.json+ "Coverage ratchet gate" CI step. Coverage profile +go tool cover -funcsummary preserved as informational only
Test Coverage Improvement — Wave 1-3 (2026-04-11)
- CI ratchet gate:
scripts/check_coverage.goparsescoverage.outper package and fails CI if coverage drops below stored floors inscripts/coverage_thresholds.json.--updateflag ratchets thresholds upward when coverage improves. 61 packages locked. -coverpkg=./...: CI now runsgo test -race -coverpkg=./...so integration tests intests/integration/are attributed to the source packages under test.internal/testutil: shared helpers —TestDB()(integration-tagged), context builders (TenantCtx,UserCtx,AgentCtx,FullCtx), mockgen generate hooks forSessionStore/AgentStore/ContactStore.- ~663 new test functions across 36 files:
- Wave 1 —
store/pgintegration test depth (session pagination/isolation, agent context files/profiles, agent_links CRUD, cron CRUD+state, vault CRUD/search, memory BM25/isolation);gatewayunit tests (ratelimit, event_filter, server auth);gateway/methodshandlers (sessions, skills, cron);httpauth helpers + path security;tasks.TaskTicker(lifecycle, recoverAll, followupInterval);agent(pruning, extractive memory, intent classify, loop utils, inject, evolution guardrails). - Wave 2 —
config(normalize, expand/contract home, env overlays, system configs);skills(BM25 tokenize/index/search/rebuild, frontmatter parser, loader/context);mcp(pool, manager status, bridge BM25, env resolution);backup(ArchiveDirectory, SanitizeDSN, WritePgpass, Backup.Run);channels/slack(mention, user cache, classifyMime);channels/discord(resolveDisplayName, command routing, classifyMediaType);channels/telegram(markdown→HTML, table rendering, detectMention, service message);channels/whatsapp(extractTextContent, chunkText, markdown→WhatsApp, mimeToExt, classifyDownloadError). - Wave 3 —
cache.PermissionCache(9 methods + invalidation);sessionskey builders + manager edge cases;knowledgegraphextractor (mock provider success/filter/error/invalid-JSON/long-text chunking), splitChunks, mergeResults.
- Wave 1 —
- Coverage deltas (local
go test, no DB):internal/knowledgegraph47.1% → 91.8% (+44.7pp)internal/skills7.7% → 37.5% (+29.8pp)internal/config19.3% → 48.2% (+28.9pp)internal/cache72.9% → 96.9% (+24.0pp)internal/sessions70.7% → 94.4% (+23.7pp)internal/gateway0% → 15.1%internal/mcp12.1% → 26.3% (+14.2pp)internal/channels/whatsapp8.8% → 21.3% (+12.5pp)internal/channels/discord15.6% → 27.7% (+12.1pp)internal/tasks0% → 55.4%internal/agent28.8% → 36.8%- store/pg integration test depth improved — coverage attribution requires live pgvector in CI
- Deferred to separate plans:
channels/feishu(0%, 102 funcs),providers/acp(0%, 41 funcs),channels/zalo(regressed to 5%),providers(56%, 325 funcs),channels/facebook(31.8%)
Deferred Coverage Waves A-C — Resolved (2026-04-11)
Follow-up to Wave 1-3 above. Addresses the 6 modules deferred as too-large/greenfield/regression. Plan: plans/260411-2020-deferred-coverage-waves/.
- Wave A —
internal/providers57.0 → 62.5% (hotspot tests for adapter/retry/SSE);channels/zalo7.2 → 65.3% (regression fix + parse/policy/HTTP coverage) - Wave B —
channels/facebook23.1 → 81.9% (full bot lifecycle, media, policy);store/pg1.3 → 3.5% (⚠️ capped at unit-test-only; 30% target requires CI integration wiring + pre-existing failing tenant-isolation tests — deferred separately) - Wave C —
providers/acp0.0 → 80.0% greenfield (7 test files, 2560 LOC; JSON-RPC framing with adversarial input fuzz, terminal sandbox + allowlist + deny-pattern enforcement, ProcessPool lifecycle, tool_bridge with 3 permission modes);channels/feishu20.6 → 63.9% (15 test files; AES-CBC webhook decrypt + tamper detection, WS proto framing, larkclient HTTP error paths, media send/receive, bot parse/policy, lifecycle) - Security tests added: ACP JSON-RPC parser no-panic on 7 adversarial inputs; sandbox path-traversal + binary allowlist + deny-pattern (
rm -rfeven underbash); env sanitization strips 8 prefixes + 13 exact-name vars (GOCLAW/ANTHROPIC/OPENAI/DATABASE/AWS/GITHUB/SSH/STRIPE/DB_DSN/PG*/NPM_TOKEN/SECRET_KEY/JWT_SECRET). Feishu AES-CBC tamper detection + token mismatch drop; no real credentials in any fixture - Scale: ~375 new test functions across 22 files (~5858 LOC); zero source modifications — pure additive coverage
- Ratchet bumped:
scripts/coverage_thresholds.json— feishu 0 → 63.89, acp 0 → 80.05
Added
Episodic Memory Weighted Scoring — Dreaming Enhancement (2026-04-10, Phase 10)
- Recall signal tracking:
episodic_summariestable gains 3 columns:recall_count INT,recall_score DOUBLE PRECISION,last_recalled_at TIMESTAMPTZto track usefulness of each memory - ComputeRecallScore formula: 4-component running average (30% frequency + 35% relevance + 20% recency + 15% freshness, 14-day half-life) quantifies memory value
- DreamingWorker prioritization:
ListUnpromotedScored()queries sort byrecall_score DESCinstead ofcreated_at ASC, promoting high-signal summaries for synthesis - fire-and-forget updates:
memory_searchtool fire-and-forget tasks increment recall counts asynchronously without blocking search results - Index optimization: New partial index
idx_episodic_recall_unpromoted ON episodic_summaries(agent_id, user_id, recall_score DESC) WHERE promoted_at IS NULLfor efficient DreamingWorker queries - Migration 000045: PG schema v44→45 + SQLite schema v12→13
Compaction Telemetry — Message Context Tracking (2026-04-10, Phase 5 Follow-up)
- Session metadata tracking:
sessions.metadataJSONB gains well-known keylast_compaction_at(RFC3339 timestamp) after successful message compaction - Dual execution paths: Both v3
PruneStage.CompactMessagesand v2 legacymaybeSummarizeupdate timestamp on successful compaction - Operator visibility:
GetSessionMetadata()exposes compaction timestamp; web UI shows in context-usage tooltip - Go constant export:
agent.SessionMetaKeyLastCompactionAt = "last_compaction_at"
Provider Reasoning Content Stripping (2026-04-10, Phase 6)
- Auto-strip known leakers: Models known to leak chain-of-thought at effort="off" (Kimi family, DeepSeek-Reasoner) auto-enable
StripThinkingso user-visibleChatResponse.Thinkingstays empty - Multi-provider support: Guard clauses in Anthropic streaming, Anthropic non-streaming
Chat(), OpenAIChatStream/Chat, CodexprocessSSEEvent; DashScope inherits via OpenAIProvider embedding - Billing-safe invariants:
Usage.ThinkingTokensstill counted from raw bytes;RawAssistantContentuntouched so Anthropic tool-use passback continues to work - Option plumbing: New
providers.OptStripThinkingkey propagated viaChatRequest.Options;ReasoningDecision.StripThinkingauto-set inResolveReasoningDecisiondefer - Helper:
modelLeaksReasoning(model string) bool— extensible allowlist
Dreaming Config Per-Agent (2026-04-10, Phase 8)
- Per-agent overrides:
MemoryConfig.DreamingJSONB onagents.memory_config(nested, no migration) controls dreaming worker behaviour per-agent - Fields:
Enabled *bool,DebounceMs int,Threshold int,VerboseLog *bool— all pointer/zero-default for partial-override merge semantics - Resolver pattern:
DreamingConfigResolver func(ctx, agentID) *DreamingConfigwired vianewAgentStoreResolver(AgentStore).ConsolidationDepsgains optionalAgentStore store.AgentCRUDStore - Backward compat: Nil resolver → struct defaults; empty JSONB → defaults via zero-value short-circuit
- Merge helper:
mergeDreamingConfigapplies override fields only when explicitly set, preserving base values
Per-Provider Context Window & Hardening (2026-04-10, Phase 4, commit 8d37dc45)
- EffectiveContextWindow: Resolved once per run in ContextStage from
ModelRegistry(via provider+model lookup);PruneStageuses it with fallback toConfig.ContextWindow - ReserveTokens safety buffer: New
PipelineConfig.ReserveTokenssubtracted from history budget so PruneStage compacts slightly before the hard limit - InMemoryCache hardening: TTL sweep (60s) + max-size cap (10k) with oldest-first eviction;
Close()wired to gateway shutdown - ContactCollector tenant fix: Cache key now includes
tenantID + channelInstance(was silently skipping upserts for same sender across tenants)
Context-Aware Auto-Inject Query (2026-04-10, Phase 9, commit 2731f99a)
- RecentContext enrichment:
InjectParams.RecentContextfield supplies last 1-2 user turns;pgAutoInjector.Injectprepends "Context:" frame before "Query:" focus - Helper:
pipeline.buildRecentContext()walks history backward, picks last 2 user turns, caps at 300 chars - Callback signature:
AutoInject func(ctx, msg, userID, recentContext string)— additive, backward compatible - Why: Vector search on "what's my favorite?" alone returns nothing useful; context-aware query captures conversational intent
Cost Calculation Thinking Tokens Fix (2026-04-10, Phase 1, commit 77a80680)
- Critical billing bug:
CalculateCostnow properly handlesThinkingTokensas sub-count ofCompletionTokens(not double-counted for OpenAI o3/o4-mini, Codex/GPT-5; properly split for Anthropic extended thinking) - Provider-aware: Splits only when
ReasoningPerMillion > 0, otherwise leaves as-is (default matches provider billing)
Web UI Enhancements (2026-04-10, Phase 11)
- Context usage badge: Chat top bar shows
{used}/{max} ({percent}%)with color ramp (amber ≥75%, destructive ≥90%); hidden on mobile viahidden sm:flex - Compaction indicator: Context badge tooltip includes compaction count + last compaction timestamp (read from
session.metadata.last_compaction_at) - DreamingConfig UI: Agent detail MemorySection renders nested dreaming block (4 controls: enabled, threshold, debounce_ms, verbose_log)
- i18n: New keys in en/vi/zh:
agents.configSections.dreaming.*,chat.contextUsage.* - Not shipped (YAGNI): "Memory recall config section" (existing MemorySection already covers all MemoryConfig fields), "Session types extension" (fields already present or out of scope)
Refactored
V3 Architecture Refactor — Phase 6 Completion (2026-04-08)
- Store unification: Created
internal/store/base/with shared Dialect interface, common helpers (NilStr, BuildMapUpdate, BuildScopeClause, execMapUpdate, etc.). PostgreSQL (pg/) and SQLite (sqlitestore/) now use base/ abstractions via type aliases, eliminating code duplication - Orchestration module: New
internal/orchestration/with orchestration primitives: BatchQueue[T] generic for result aggregation, ChildResult structure for capturing child agent outputs, media conversion helpers - Forced V3 pipeline: Deleted legacy v2
runLoop()(~745 LOC). Removedv3PipelineEnabledconditional flag — all agents now always execute the unified 8-stage pipeline (context→history→prompt→think→act→observe→memory→summarize) - Gateway decomposition: Split monolithic gateway.go (1295 LOC → 476 LOC) into focused modules: gateway_deps.go, gateway_http_wiring.go, gateway_events.go, gateway_lifecycle.go, gateway_tools_wiring.go for better maintainability
- SSE extraction: Created shared SSEScanner in
providers/sse_reader.go— unified streaming implementation used by OpenAI, Codex, and Anthropic streaming providers, eliminating provider-level duplication - UI cleanup: Removed v2/v3 toggle from web UI settings since v3 is now the only execution path
- Build compatibility: All builds (PostgreSQL standard + SQLite desktop) compile cleanly. Dual-DB store pattern enables seamless database backend switching
Added
Knowledge Vault UI/Backend Enhancements (2026-04-09)
- Doc type inference:
vault_linktool now infers document type from file path instead of hardcoding "note" - Link type parameter:
vault_linkaccepts optionallink_typeparam (wikilink or reference, default wikilink) - Pagination support:
/v1/vault/documentsand/v1/agents/{id}/vault/documentsreturn{documents: [...], total: N}for pagination - CountDocuments store method: Added to VaultStore interface with PostgreSQL and SQLite implementations
- Frontend pagination UI: Vault documents table shows 100 items per page with Previous/Next navigation, "Showing X-Y of Z" indicator
- Team filter dropdown: Vault page has team selector alongside agent selector for multi-team document filtering
- Graph view upgrade: Independent graph data fetching (limit 500) with KG-level features:
- Node click highlight + neighbor emphasis + dim non-neighbors
- Double-click opens document detail dialog
- Zoom controls (ZoomIn/ZoomOut buttons + percentage display)
- Node limit selector (100/200/300/500 by degree centrality)
- Link labels on highlighted links + directional particles
- Stats bar showing doc/link counts
- Fit-to-view button to auto-center graph
- Background click clears selection
- Works in all-agents mode (shows nodes without agent-specific links)
- VaultDocument type updates: Added team_id, summary, custom_scope, media type fields for richer metadata
- Files modified:
internal/tools/vault_link.go— doc type inference + link_type paraminternal/http/vault_handlers.go— pagination response wrapperinternal/store/vault_store.go,pg/vault_documents.go,sqlitestore/vault_documents.go— CountDocumentsui/web/src/pages/vault/*— pagination, team filter, graph upgradeui/web/src/adapters/vault-graph-adapter.ts— degree centrality limitingui/web/src/i18n/locales/{en,vi,zh}/*— pagination + vault strings
Vault Enrich Worker — Auto Summary + Semantic Linking (2026-04-09)
- Async document enrichment: EventBus-driven worker auto-summarizes new/updated vault documents via LLM
- Vector embeddings: Document summaries automatically embedded and indexed for semantic search
- Auto-linking: Vector similarity search (0.7 threshold, top-5 neighbors) auto-creates bidirectional vault links
- Efficient batching: BatchQueue[T] batches documents by tenantID:agentID, bounded dedup map (10K cap) prevents memory leaks
- Provider independence: Separate provider resolution from consolidation pipeline, reuses master tenant provider
- Dual-DB support: PostgreSQL includes full embed+link workflow; SQLite (desktop) summarizes only (no vector ops)
- Files added:
internal/vault/enrich_worker.go— BatchQueue-driven worker with bounded dedupinternal/eventbus/event_types.go— EventVaultDocUpserted event type- Updated
internal/store/vault_store.gowith UpdateSummaryAndReembed, FindSimilarDocs methods - Updated PostgreSQL and SQLite vault document stores
WhatsApp Native Protocol Integration (2026-04-06)
- Direct protocol migration: Replaced Node.js Baileys bridge with direct in-process WhatsApp connectivity
- Database auth persistence: Auth state, device keys, and client metadata stored in PostgreSQL (standard) or SQLite (desktop)
- QR authentication: Interactive QR code authentication for device linking without external bridge relay
- No more bridge_url: Removed
bridge_urlconfiguration, eliminateddocker-compose.whatsapp.yml, removedbridge/whatsapp/sidecar service - Enhanced media handling: Direct media download/upload to WhatsApp servers with automatic type detection and streaming
- Improved mention detection: Group mention detection now uses LID (Local ID) + JID (standard format) for robust message routing
- Files added:
internal/channels/whatsapp/factory.go— Dialect detection and channel factoryinternal/channels/whatsapp/qr_methods.go— QR code generation and authentication flowinternal/channels/whatsapp/format.go— HTML-to-WhatsApp message formatting- Database-backed auth persistence for cross-platform support
Refactored
Parallel Sub-Agent Enhancement (#600) (2026-03-31)
- Smart leader delegation: Conditional leader delegation prompt instead of forced delegation for all subagent spawns
- Compaction prompt persistence: Preserves pending subagent and team task state across context summarization to maintain work continuity
- DB persistence:
subagent_taskstable (migration 000034) withSubagentTaskStoreinterface and PostgreSQL implementation. Write-through persistence from SubagentManager ensures durable task tracking - Token cost tracking: Per-subagent input/output token accumulation. Token costs included in announce messages and persisted in DB for billing/observability
- Per-edition rate limiting:
MaxSubagentConcurrentandMaxSubagentDepthlimits on Edition struct. Tenant-scoped concurrency prevents single tenant from hogging subagent resources - WaitAll orchestration:
spawn(action=wait, timeout=N)blocks parent until all spawned children complete. Enables coordinated multi-step workflows - Auto-retry with backoff: Configurable
MaxRetries(default 2) with linear backoff for LLM failures. Improves reliability without manual intervention - Producer-consumer announce queue: Merges staggered subagent results into single LLM run announcement. Reduces token overhead vs per-result notifications
- Telegram subagent commands:
/subagentslists all active subagent tasks with status./subagent <id>shows detailed view from DB - Subagent blocking in subagents:
SubagentDenyAlwaysblocksteam_taskstool to prevent nested task delegation - Functional options pattern: Telegram provider refactored to
telegram.New()withWithXxxStore()option setters for cleaner initialization - File organization: Subagent code split into focused modules:
subagent.go,subagent_roster.go,subagent_spawn.go. Spawn tool split:spawn_tool.go+spawn_tool_actions.go
Runtime & Packages Management (2026-03-17)
- Packages page: New "Packages" page in Web UI under System group for managing installed packages
- HTTP API endpoints: GET/POST
/v1/packages,/v1/packages/install,/v1/packages/uninstall, GET/v1/packages/runtimes - Three package categories: System (apk), Python (pip), Node (npm) with version tracking
- pkg-helper binary: Root-privileged helper service for secure system package management via Unix socket
/tmp/pkg.sock - Package persistence: System packages persisted to
/app/data/.runtime/apk-packagesfor container recreation - Input validation: Regex + MaxBytesReader (4096 bytes) for package names to prevent injection
Docker Security Hardening (2026-03-17)
- Privilege separation: Entrypoint drops privileges to non-root goclaw user after installing packages
- pkg-helper service: Started as root, listens on Unix socket with 0660 permissions (root:goclaw group)
- Runtime directories: Python and Node.js packages install to writable
/app/data/.runtimedirectories - su-exec integration: Used instead of USER directive for cleaner privilege transition
- Docker capabilities: Added SETUID/SETGID/CHOWN/DAC_OVERRIDE for pkg-helper and user switching
- Environment variables: PIP_TARGET, NPM_CONFIG_PREFIX, PYTHONPATH configured for runtime installs
Auth Fix (2026-03-17)
- Empty gateway token handling: When GOCLAW_GATEWAY_TOKEN is empty (dev/single-user mode), all requests get admin role
- CLI credentials access: Admin-only endpoints (/v1/cli-credentials) now accessible in dev mode
Team Workspace Improvements (2026-03-16)
- Team workspace resolution: Lead agents resolve per-team workspace directories for both lead and member agents
- WorkspaceInterceptor: Transparently rewrites file tool requests to team workspace context
- File tool access: Member agents can access workspace files with automatic path resolution
- Team workspace UI: Workspace scope setting UI, file view/download, storage depth control
- Lazy folder loading: Improved performance with lazy-load folder UI and SSE size endpoint
- Task enhancements: Task snapshots in board view, task delete action, improved task dispatch concurrency
- Board toolbar: Moved workspace button and added agent emoji display
- Status filter: Default status filter changed to all with page size reduced to 30
Agent & Workspace Enhancements (2026-03-16)
- Agent emoji: Display emoji icon from
other_configin agent list and detail views - Lead orchestration: Improved leader orchestration prompt with better team context
- Task blocking validation: Validate blocked_by terminal state to prevent circular dependencies
- Prevent premature task creation: Team V2 leads cannot manually create tasks before spawn
Team System V2 & Task Workflow (2026-03-13 - 2026-03-15)
- Kanban board layout: Redesigned team detail page with visual task board
- Card/list toggle: Teams list with card/list view toggle
- Member enrichment: Team member info enriched with agent metadata
- Task approval workflow: Approve/reject/cancel tasks with new statuses and filtering
- Workspace scope: Per-agent DM/group/user controls with workspace sharing configuration
- i18n for channels: Channel config fields now support internationalization
- Memory/KG sharing: Decoupled memory and KG sharing from workspace folder sharing
- Events API: New /v1/teams/{id}/events endpoint for task lifecycle events
Security & Pairing Hardening (2026-03-16)
- Browser approval fix: Fixed browser approval stuck condition
- Pairing auth hardening: Fail-closed auth, rate limiting, TTL enforcement for pairing codes
- DB error handling: Handle transient DB errors in IsPaired check
- Transient recovery: Prevent spurious pair requests
Internationalization (i18n) Expansion (2026-03-15)
- Complete web UI localization: Full internationalization for en/vi/zh across all UI components
- Config centralization: Centralized hardcoded ~/.goclaw paths via config resolution
- Channel DM streaming: Enable DM streaming by default with i18n field support
Provider Enhancements (2026-03-14 - 2026-03-16)
- Qwen 3.5 support: Added Qwen 3.5 series support with per-model thinking capability
- Anthropic prompt caching: Corrected Anthropic prompt caching implementation
- Anthropic model aliases: Model alias resolution for Anthropic API
- Datetime tool: Added datetime tool for provider context
- DashScope per-model thinking: Simplified per-model thinking guard logic
- OpenAI GPT-5/o-series: Use max_completion_tokens and skip temperature for GPT-5/o-series models
ACP Provider (2026-03-14)
- External coding agents: ACP provider for orchestrating external agents (Claude Code, Codex CLI, Gemini CLI) as JSON-RPC subprocesses
- ProcessPool management: Subprocess lifecycle with idle TTL reaping and crash recovery
- ToolBridge: Agent→client requests for filesystem operations and terminal spawning
- Workspace sandboxing: Security features with deny pattern matching and permission modes
- Streaming support: Both streaming and non-streaming modes with context cancellation
Storage & Media Enhancements (2026-03-14)
- Lazy folder loading: Lazy-load folder UI for improved performance
- SSE size endpoint: Server-sent events endpoint for dynamic size calculation
- Enhanced file viewer: Improved file viewing capabilities with media preservation
- Web fetch enhancement: Increased limit to 60K with temp file save for oversized content
- Discord media enrichment: Persist media IDs for Discord image attachments
Knowledge Graph Improvements (2026-03-14)
- LLM JSON sanitization: Sanitize LLM JSON output before parsing to handle edge cases
CI/CD & Release Pipeline (2026-03-16)
- Semantic release: Automated versioning via
go-semantic-releaseon push tomain - Cross-platform binaries: Build and attach
linux/darwin × amd64/arm64tarballs to GitHub Releases - Discord webhook notification: Post release embed to Discord with changelog, version, Docker pull command, and install script link after successful build
- Install scripts: One-liner binary installer (
scripts/install.sh) and interactive Docker setup (scripts/setup-docker.sh) with variant selection (alpine/node/python/full) - Docker image publishing: Publish multi-arch images to GHCR and Docker Hub via GitHub Actions
Traces & Observability (2026-03-16)
- Trace UI improvements: Added timestamps, copy button, syntax highlighting to trace/span views
- Trace export: Added gzip export with recursive sub-trace collection
Skills & System Tools (Previous releases)
- System skills: Toggle, dependency checking, per-item installation
- Tool aliases: Alias registry for Claude Code skill compatibility
- Multi-skill upload: Client-side validation for bulk skill uploads
- Audio handling: Fixed media tag enrichment and literal media:audio handling
Credential & Configuration (Previous releases)
- Credential merge: Handle DB errors to prevent silent data loss
- OAuth provider routing: Complete media provider type routing for Suno, DashScope, OAuth providers
- API base resolution: Respect API base when listing Anthropic models
- Per-agent DB settings: Honor per-agent restrictions, subagents, memory, sandbox, embedding provider settings
Changed
- Docker entrypoint: Reimplemented for privilege separation with pkg-helper lifecycle management
- Team workspace refactor: Removed legacy
workspace_read/workspace_writetools in favor of file tools for team workspace - Config hardcoding: Centralized ~/goclaw paths via config resolution instead of hardcoded values
- Workspace media files: Preserve workspace media files during subtree lazy-loading
Fixed
- Teams status filter: Default to all statuses instead of subset, reduced page size to 30
- Select crash: Filter empty chat_id scopes to prevent dropdown crash
- File viewer: Improved workspace file view/download and storage depth control
- Pairing DB errors: Handle transient errors gracefully
- Provider thinking: Corrected DashScope per-model thinking logic
- Pancake Page loop guard: Narrowed webhook ingress to
messaging+INBOXevents and normalized HTML-formatted echoes before short-TTL outbound echo suppression, reducing Facebook Page self-reply loops in Pancake inbox conversations
Documentation
- Updated
05-channels-messaging.md— RefreshedWebhookChannel/BlockReplyChannelimplementation tables for Facebook, Pancake, Discord, and Zalo-family channels - Updated
18-http-api.md— Added section 17 for Runtime & Packages Management endpoints - Updated
09-security.md— Added Docker entrypoint documentation, pkg-helper architecture, privilege separation - Updated
17-changelog.md— New entries for packages management, Docker security, and auth fix - Added
18-http-api.md— Complete HTTP REST API reference (all endpoints, auth, error codes) - Added
19-websocket-rpc.md— Complete WebSocket RPC method catalog (64+ methods, permission matrix) - Added
20-api-keys-auth.md— API key authentication, RBAC scopes, security model, usage examples - Updated
02-providers.md— ACP provider documentation with architecture, configuration, security model - Updated
00-architecture-overview.md— Added ACP provider component and module references
[ACP Provider Release]
Added
ACP Provider (Agent Client Protocol)
- New provider: ACP provider enables orchestration of external coding agents (Claude Code, Codex CLI, Gemini CLI) as JSON-RPC 2.0 subprocesses over stdio
- ProcessPool: Manages subprocess lifecycle with idle TTL reaping and automatic crash recovery
- ToolBridge: Handles agent→client requests for filesystem operations and terminal spawning with workspace sandboxing
- Security features: Workspace isolation, deny pattern matching, configurable permission modes (approve-all, approve-reads, deny-all)
- Streaming support: Both streaming and non-streaming modes supported with context cancellation
- Config integration: New
ACPConfigstruct in configuration with binary, args, model, work_dir, idle_ttl, perm_mode - Database providers: ACP providers can be registered in
llm_providerstable with encrypted credentials - Files added:
internal/providers/acp_provider.go— ACPProvider implementationinternal/providers/acp/types.go— ACP protocol typesinternal/providers/acp/process.go— Process pool managementinternal/providers/acp/jsonrpc.go— JSON-RPC 2.0 marshalinginternal/providers/acp/tool_bridge.go— Request handlinginternal/providers/acp/terminal.go— Terminal lifecycleinternal/providers/acp/session.go— Session tracking
Changed
- Updated
02-providers.mdto document ACP provider architecture, configuration, session management, security, and streaming - Updated
00-architecture-overview.mdcomponent diagram to include ACP provider - Updated Module Map in architecture overview to reference
internal/providers/acp/package
Documentation
- Added comprehensive ACP provider documentation with architecture diagrams, configuration examples, security model, and file reference
- Added
17-changelog.mdfor tracking project changes
[Previous Releases]
v1.0.0 and Earlier
- Initial release of GoClaw Gateway with Anthropic and OpenAI-compatible providers
- WebSocket RPC v3 protocol and HTTP API
- PostgreSQL multi-tenant backend with pgvector embeddings
- Agent loop with think→act→observe cycle
- Tool system: filesystem, exec, web, memory, browser, MCP bridge, custom tools
- Channel adapters: Telegram, Discord, Feishu, Zalo, WhatsApp
- Extended thinking support for Anthropic and select OpenAI models
- Scheduler with lane-based concurrency control
- Cron scheduling system
- Agent teams with task delegation
- Skills system with hot-reload
- Tracing and observability with optional OpenTelemetry export
- Browser automation via Rod
- Code sandbox with Docker
- Text-to-speech (OpenAI, ElevenLabs, Edge, MiniMax)
- i18n support (English, Vietnamese, Chinese)
- RBAC permission system
- Device pairing with 8-character codes
- MCP server integration with stdio, SSE, streamable-HTTP transports