Add tts namespace to desktop i18n (en/vi/zh). Extend tools.json with STT form keys including WhatsApp privacy banner. Mirrors web ui/web locale structure.
Port web voice picker and STT provider form to desktop frontend. Singleton audio preview (hide when preview_url null), whatsapp_enabled toggle with privacy banner. Reuses desktop's custom Combobox primitive. Voice id persists via other_config.tts_voice_id merge in AgentDetailPanel. ToolSettingsDialog routes stt tool to new SttProviderForm.
Add stt-provider-form.tsx React component with form fields for model, language, and API key configuration. Mirror TTS provider form structure for consistency. Integrate into builtin-tool-settings-dialog.tsx. Includes comprehensive unit tests for form validation, submission, and error handling.
Add STT builtin tools seeding:
- PG: migration 000050 (49→50) with stt_scribe and stt_proxy entries
- SQLite: schema version 17→18 with inline seed
- Both: register in gateway_builtin_tools.go with encrypted API key support
Migrations support rollback. Version bumps gated to prevent schema drift.
Implement legacy STT bridge adapter to support existing per-channel STTProxyURL database configuration. Wraps arbitrary proxy endpoints and auto-registers them via Manager at boot. Includes integration tests for URL validation, authentication, and error handling.
Add Transcribe() method to Manager with STT provider dispatch and channel-specific overrides. Implements channelSTTOverrides map, WithChannel context propagation, RegisterChannelSTT registration, and resolveSTTChain fallback logic. Includes manager-level unit tests.
Implement STT proxy provider that wraps arbitrary HTTP-based STT services. Ported 12 test cases from legacy Telegram STT integration to validate multipart/form-data submission, language/model overrides, and API error handling.
Implement native ElevenLabs Scribe STT provider with POST /v1/speech-to-text endpoint. Supports 20MB audio cap, multipart form submission, configurable model and language. Includes comprehensive unit tests for happy path, audio size validation, API errors, and edge cases.
Add STTInput, STTOptions, and TranscriptResult types to support native STT providers (ElevenLabs Scribe). Includes audio_duration_secs JSON tag for transcription metadata.
Update architecture overview with streaming TTS provider layer. Expand tools
system docs with voice/model resolution. Update HTTP and WebSocket RPC
documentation for voice endpoints. Record Phase 02 completion in changelog.
Update CLAUDE.md with voice picker and streaming TTS patterns.
Add VoicePicker with live search and preview button. Integrate into prompt
settings section. Add voice API hooks (useVoices, useRefreshVoices). Localize
UI strings for en/vi/zh. Fix snake_case JSON field mapping (voice_id, preview_url).
Add StreamingTTSProvider interface for audio streaming support. Implement voice
caching with TTL+LRU. Add ElevenLabs model validation and voice list retrieval.
Update module map to list internal/audio/ (unified manager, TTS active,
STT/Music/SFX stubbed/partial) and clarify internal/tts/ as a
24-symbol backward-compat alias layer. Add changelog entry for Phase 1.
Add optional Audio *AudioConfig pointer field on Config with STT and
Music sub-structs. Nil-safe — absent in JSON5 decodes as nil, no
breaking change. cfg.Tts retained unchanged for backward compat.
setupAudioExtras stub wired for Phase 3/4 STT/Music provider
registration.
internal/tts becomes a thin backward-compat alias over internal/audio:
15 type aliases, 6 constants, 5 constructors, 5 compile-time signature
guards. All pre-refactor callers compile unchanged. alias_test.go
enforces symbol coverage and type identity. Old per-provider files
(manager, types, elevenlabs, openai, edge, minimax) are removed in
the same commit to keep history bisectable.
MCP servers with require_user_credentials (e.g. Notion) were defined
but never loaded into the agent's tool registry. Three gaps:
1. getUserMCPTools was defined but never called — add call in
makeBuildFilteredTools before FilterTools runs each iteration.
2. hasMCPTools stayed false when only user-credential servers existed,
so agentToolPolicyWithMCP never injected "group:mcp" into alsoAllow.
Now set true when mcpUserCredSrvs is non-empty.
3. Per-user BridgeTools were registered in the registry but never added
to the "mcp" tool group, so expandSpec("group:mcp") returned empty.
Add MergeToolGroup helper for additive group updates.
Also add debug log when getUserMCPTools skips due to empty userID.
Stale recovery sweeps traces by `start_time < NOW() - threshold`, which
measures trace age rather than inactivity. Any threshold low enough to
be useful (2-10 min) kills legitimate long-running agent runs: research
chains, large code generation, extended shell commands routinely exceed
10 minutes.
Disabled in Start() — function kept in place for easy re-enable once a
`last_span_at` column is added so recovery can gate on "no activity for
N minutes" instead of "started > N min ago".
Trade-off: zombie traces from gateway crashes may remain `running` in
DB. Accepted: primary abort path (router 2-phase + trace.status WS
event) handles the common case; safety-net gap preferred over false
kills of healthy runs.
Integration test RecoverStaleNow() still works (manual trigger, not
loop-dependent) so coverage of the recovery function itself is
preserved for when it's re-enabled.
matchesBinaryDeny used unanchored regex on joined args, causing `-v` pattern
to false-positive on `--version`. Split deny_verbose into matchesBinaryVerbose
with start-anchored per-arg matching: `-v` blocks `-v`, `-vv`, `-v=1` but not
`--version`. deny_args keeps joined matching for multi-token patterns.
v3 pipeline's parallel path (makeExecuteToolRaw) skipped the tool.call
WebSocket event, so web UI and desktop UI silently dropped tool cards
during real-time streaming. Only page refresh (which reloads history)
revealed the tool calls. Both UIs rely on tool.call to create entries
that later tool.result events can update.
Fix: mirror the sequential path's emission in makeExecuteToolRaw.
Bus.Broadcast is RWMutex-guarded, safe to call from parallel goroutines.
Add tests at two layers to prevent regression:
- Pipeline layer (stages_test.go): guards the dispatch contract —
multiple tool calls route through ExecuteToolRaw + ProcessToolResult
rather than ExecuteToolCall. Previously the parallel path had zero
test coverage, which is why this bug escaped.
- Agent layer (loop_pipeline_tool_callbacks_test.go): guards the
emission contract — both sequential and parallel wrappers emit
tool.call with correct payload and routing context. Mutation-verified.
- AddError() now skips broadcast after Finish() to prevent cancelled
goroutines from emitting error events to UI after user stops enrichment
- batchSummarize skips AddError when context is cancelled (expected on stop)
- Rescan always re-enqueues unenriched docs alongside new/updated files,
worker-level dedup prevents double-processing
* fix: handle ignored errors, unsafe type assertions, missing panic recovery
- Cron scheduler (PG + SQLite): check all ExecContext errors in
recomputeStaleJobs, run log insert, job delete, and post-run update.
Previously these errors were silently discarded, which could leave
job state inconsistent without any log trace.
- Discord: use comma-ok type assertions on sync.Map placeholder loads
to prevent potential panics from bare type assertions.
- Slack: use comma-ok type assertions in sweepMaps for dedup and
thread participation eviction to prevent potential panics.
- Feishu: add safego.Recover to WebSocket goroutine so a panic in
the WS client doesn't silently kill the goroutine.
- Agent export: add tenant owner/admin permission check to canExport.
Previously only agent owner and system owner could export — tenant
admins were incorrectly denied.
- Channel health: use errors.Is/errors.As for context.DeadlineExceeded,
net.DNSError, and net.OpError before falling back to string matching.
DNS NXDOMAIN is now correctly classified as non-retryable.
* fix(review): revert export to system-only + add missing rows.Err check
- Revert canExport tenant role check — export/import is restricted to
agent owner and system owner by design
- Add rows.Err() check after recomputeStaleJobs loop in PG cron
(parity with SQLite implementation)
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
* fix(facebook): preserve fb_mode metadata in outbound routing + admin reply detection
Two issues fixed:
1. Messenger auto-reply never delivered because fb_mode metadata was
stripped during outbound message construction. The routing whitelist
in gateway_consumer_normal.go and channels/events.go only copied
thread_id/local_key/group_id — facebook-specific keys (fb_mode,
sender_id, page_id, reply_to_comment_id) were dropped, causing
facebook.Send() to fall into the comment path and fail with
"reply_to_comment_id missing".
2. Added admin reply detection: before sending a bot reply, check via
Graph API if the last page message in the conversation was sent by
an admin (human) rather than the bot itself. Skips bot reply when
admin already responded, preventing duplicate messages.
Uses timestamp comparison with bot's own send history to distinguish
bot-sent vs admin-sent page messages (both have from.id = page_id).
* chore: exclude compiled binary from git
* fix(facebook): ignore bot echoes in messenger cooldown
* fix(facebook): add memory cleanup for admin reply maps and reduce echo window
- Add adminReplied and botSentAt eviction to runDedupCleaner to prevent
unbounded memory growth on high-traffic pages
- Reduce botEchoWindow from 60s to 15s to avoid misclassifying real admin
replies as bot echoes
- Restore doc comments on routingMetaKeys and copyRoutingMeta
- Add cross-reference comment between consumer and events routing key lists
- Simplify admin-reply skip log (remove redundant map re-read)
---------
Co-authored-by: khanhtran <>
Co-authored-by: Plateau Nguyen <nguyennlt.ncc@gmail.com>
Co-authored-by: viettranx <viettranx@gmail.com>
release.yaml is tag-triggered (v[0-9]+.[0-9]+.[0-9]+), not
branch-triggered. There is no go-semantic-release — releases
are manual tag pushes. Merging to main only triggers CI.