Track per-tool execution time statistics in session metadata. When a tool
call exceeds its adaptive threshold (2x historical max, min 120s default),
send a direct outbound notification to the user.
- ToolTimingMap: parse/serialize/record/threshold from session metadata
- StartSlowTimer: fires once per tool call, auto-cancels on completion
- Team config: slow_tool toggle (default on, always direct, never leader)
- UI: toggle in team settings with i18n (en/vi/zh)
- Store: add GetSessionMetadata to session store interface
- Team lead: no completion language after delegating, no question phrasing
- Group chat: inject reply context hint (NO_REPLY when reply addresses others)
- Both v1 and v2 team lead sections updated
* feat(ui): show required API scopes for Feishu/Lark channels
Add a collapsible info panel listing the required Lark/Feishu API
permissions (scopes) on the channel create/edit dialog and config
detail tab. Includes reminder about Contact Range and app publishing.
* fix(feishu): annotate DM messages with sender name
Feishu DMs were missing the [From: ...] annotation, so the agent
couldn't identify who was messaging. Group messages already had this.
Align with Telegram channel which annotates both DM and group messages.
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
* fix(providers): auto-clamp max_tokens on model rejection + fix verify for reasoning models
When OpenAI-compat models reject max_tokens as too large (e.g. gpt-3.5-turbo
supports 4096 but we send 8192), parse the model's stated limit from the 400
error, clamp the value, and retry once. This fixes agent creation for models
with lower output token limits without hardcoding model names.
Also increase the provider verify endpoint's max_tokens from 1 to 50 so
reasoning models (gpt-5, o-series) have enough headroom for internal
reasoning during the check call.
Closes#248, closes#245
* refactor(providers): extract chat retry closure + fix clamp log key
- Extract duplicate retry closure into chatRequestFn() to follow DRY
- Fix slog logging wrong key: body["max_tokens"] was nil for reasoning
models that use max_completion_tokens — now uses clampedLimit() helper
- Remove unnecessary _ = resp in provider verify endpoint
---------
Co-authored-by: viettranx <viettranx@gmail.com>
* fix(tts): config save + Edge provider registration + dark mode chat bubbles
- Wrap TTS config payload in `raw` field for config.patch RPC (#229)
- Always register Edge TTS provider (free, no API key) instead of gating on `enabled` flag
- Fix low-contrast user message bubbles in dark mode chat
* fix(tts): skip duplicate media dispatch when temp file already delivered
When both the agent loop and the message tool dispatch the same TTS
temp file, the first dispatch succeeds and cleanup deletes it. Filter
out missing temp media files before sending to prevent "file not found"
errors and spurious error notifications on Telegram/Slack/Discord.
* feat(tts): include edge-tts in Docker image when Python enabled
Edge TTS is free (no API key) and serves as a universal TTS fallback.
Install it alongside Python in both ENABLE_PYTHON and ENABLE_FULL_SKILLS builds.
* chore(docker): expose build args from .env for compose builds
Pass ENABLE_OTEL, ENABLE_PYTHON, ENABLE_FULL_SKILLS as env-driven
build args so .env can control Docker build features without editing
docker-compose.yml directly.
* fix(tts): hot-reload TTS config on settings change via pub/sub
TTS providers were only registered at startup, so changing provider/API
key via the Web UI had no effect until container restart. Add a
tts-config-reload bus subscriber that rebuilds the TTS manager on
config changes, matching the pattern used by quota, cron, and web_fetch.
Always create a TtsTool at startup (even without providers) so the
reload subscriber can populate it when settings are first configured.
* fix(tts): protect TtsTool.UpdateManager with RWMutex to prevent data race
UpdateManager() can be called from the config reload goroutine while
Execute() reads t.manager concurrently from agent goroutines. Add
sync.RWMutex following the same pattern as WebFetchTool.UpdatePolicy().
Also update setupTTS doc comment which incorrectly stated it could
return nil — Edge TTS is now always registered.
---------
Co-authored-by: viettranx <viettranx@gmail.com>
- Add ProviderModelSelect to heartbeat config dialog (allowEmpty, verify button)
- Backend: accept providerName in HEARTBEAT.SET, resolve to UUID via GetProviderByName
- Add ModelOverride to RunRequest, used by Loop when set (cheaper model for heartbeat)
- Ticker passes heartbeat model override to agent RunRequest
- Fix: InvalidateCache after UpdateState so ListDue picks up new next_run_at immediately
- i18n: add sectionModel/modelHint keys (en/vi/zh)
* fix(subagent): inherit parent agent's provider instead of alphabetical fallback
Subagents previously used a fixed provider (alphabetically first from the
registry, often "anthropic") regardless of which provider the parent agent
used. This caused invalid combos like anthropic/glm-5 when a zai-coding
agent spawned subagents.
- Pass provider registry to SubagentManager for runtime resolution
- Inject parent provider name into context (WithParentProvider)
- Resolve activeProvider from parent context before LLM call
- Fix trace spans to show actual resolved provider, not default
* fix(providers): api_base fallback from config/env for DB providers
DB providers with empty api_base now inherit from config/env vars
(e.g., GOCLAW_ANTHROPIC_BASE_URL). Prevents proxy API keys from being
sent to the real provider API endpoint.
- Add APIBaseForType() method on ProvidersConfig
- registerProvidersFromDB falls back to config when api_base is empty
- ProvidersHandler uses resolveAPIBase() for model listing
- Add api_base, display_name, settings to provider validation whitelist
* fix(tracing): pass resolved provider name to subagent span emitters
- emitSubagentSpanStart now accepts providerName param instead of
reading sm.provider.Name() — ensures root subagent span reflects
the inherited parent provider, not the fallback default
- registerInMemory now uses resolveAPIBase() so DB providers with
empty api_base inherit the config/env fallback (same as startup path)
---------
Co-authored-by: viettranx <viettranx@gmail.com>
Backend — WSClient protocol fixes (larkws.go):
- Parse service_id from WS URL query params instead of hardcoding 0
- Update all 4 server config values from pong payload (PingInterval,
ReconnectCount, ReconnectInterval, ReconnectNonce)
- Use server-configured reconnect params instead of hardcoded 120s wait
- Return HTTP 500 in ACK when event handler fails (enables Lark retry)
- Filter data frames by type header — only process "event" frames
- Report actual processing time in biz_rt header (was hardcoded "0")
Backend — event adapter (feishu.go):
- Return parse error from HandleEvent so ACK reflects failure status
UI — fix incorrect Feishu channel form labels:
- Remove "webhook only" from Lark Global domain label (WebSocket works
on both Lark Global and Feishu China)
- Remove "Feishu only" from WebSocket option label
- Change default connection_mode from "webhook" to "websocket" (matches
backend default)
- Add showWhen conditional field support to ChannelFields component
- Hide webhook_port, webhook_path, encrypt_key, verification_token when
WebSocket mode is selected
- Update i18n labels in all 3 locales (en, vi, zh)
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
ShellDenyGroups was defined in SystemPromptConfig but lacked full propagation
through parser, Loop fields, context injection, and system prompt population.
Per-agent overrides from other_config JSONB had zero runtime effect.
Changes:
- agent_store.go: Add ParseShellDenyGroups() to extract overrides from JSONB
- loop_types.go: Add shellDenyGroups field to Loop and LoopConfig, wire in NewLoop
- resolver.go: Wire agent-parsed shell deny groups into LoopConfig
- loop.go: Inject shellDenyGroups into context via store.WithShellDenyGroups
- loop_history.go: Populate ShellDenyGroups in system prompt config
- message_test.go: Fix macOS symlink path normalization in test expectations
Fixes test failures on macOS where /var/folders symlinks to /private/var/folders.
Team agents now see a ## Team Members section listing all teammates with
agent_key, display_name, role, and frontmatter excerpt. This allows the
agent to correctly assign tasks via team_tasks instead of guessing keys.
- Rewrite heartbeat prompt to instruct agent to EXECUTE checklist tasks, not echo them
- Simplify suppression: HEARTBEAT_OK present = always suppress, absent = always deliver
- Add delivery targets RPC (heartbeat.targets) for channel/chatId picker
- Sanitize backend errors — never expose raw SQL to client
- Add session cleanup for isolated heartbeat sessions after run
- Cap StaggerOffset at 10% of interval to avoid user-visible delay
- Fix Upsert to persist next_run_at correctly
plainto_tsquery ANDs all terms, so queries like "teenager coffee
instagram" return 0 results when any single term is missing from the
tsvector. Switch to to_tsquery with OR-joined terms so tasks matching
any keyword are returned, ranked by relevance. Sanitize input with
a whitelist (letters, digits, hyphens, underscores) to prevent
tsquery syntax injection.
- Add RunMediaPaths context key to track media files from current run
- Collect persisted media paths in agent loop after enrichment
- Auto-copy media files to {workspace}/attachments/ when leader creates task
- Append attached files hint in dispatch content so members know what to read
- Scope task_number per (team_id, chat_id) instead of global per team
- Fix NULL chat_id comparison with COALESCE
- Use hard link first, copy fallback to save disk space
- Validate filenames and use restrictive file permissions (0640)
- Skip FinalizeStream on tool.call — keep streamed message visible
- Gate tool_status placeholder_update to non-streaming runs only
- Prevents streamed text from being overwritten by tool status emoji
- Add append=true parameter for chunked file writing
- Add ~12000 char warning in tool description and system prompt
- Helps models avoid API truncation on large file writes
- Add display_name to task create response (assignee name)
- Add owner_display_name and created_by_display_name to list/get items
- Pass to_agent_display via dispatch metadata (zero extra DB queries)
- Use display name in announce messages to leader for correct attribution
- Remove handleDelegateAnnounce() dead code (no sender emits delegate:* messages)
- Remove delegate tool reference from intent_classify.go
- Rename LaneDelegate → LaneTeam with backward-compat env var fallback
- Rename ChannelDelegate → ChannelTeammate across all team tool files
- Comment out lifecycle guards in team_tasks_lifecycle.go (TODO: reviewer workflow)
- Update string literals in cron.go, task_ticker.go
- Gate tool_status placeholder_update to non-streaming runs only
- Skip FinalizeStream on tool.call to prevent mid-run content loss
Cherry-picked valuable changes from PR #206:
- hasReadImageProvider supports chain format {"providers":[...]} config
- create_image/video/audio verify file persistence after write with diagnostic logging
- HistoryEntry gains Media field + CollectMedia() for group media context on @mention
- Zalo extractContentAndMedia refactored: all media types via DetectMIMEType/BuildMediaTags, 20MB limit
- Discord/Zalo pass media paths to Record() and collect historical media on @mention
- Zalo send_helpers logs directory contents when checkFileSize stat fails
Telegram:
- Fix context cancellation in reaction timer callbacks — use context.Background()
so emoji reactions still work after request context is cancelled
- Add comma-ok safety on sync.Map type assertions in OnReactionEvent/ClearReaction
Zalo:
- Add comma-ok safety on sync.Map type assertions for typing controllers
- Validate non-empty senderID before processing text/image messages
Feishu/Lark:
- Handle json.Unmarshal errors in all 7 LarkClient messaging methods instead of
silently returning empty values
- Add 30s HTTP timeout for WebSocket endpoint request (was using DefaultClient)
- Eliminate double decryption in webhook handler — cache first result
- Replace goroutine+sleep with time.AfterFunc for dedup cleanup
- Add comma-ok safety on pairing debounce type assertion
- Use json.Marshal instead of fmt.Sprintf for image/file key JSON construction
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
* fix(telegram): thread transport policy into media downloads with SSRF guard
* fix(telegram): trust configured APIServer during media downloads
* fix(telegram): use proper download timeout and clone DefaultTransport
- Clone http.DefaultTransport when proxy is configured to preserve
connection pool, TLS handshake timeout, and keep-alive defaults
- Use dedicated 5-minute context timeout for media downloads instead
of the shared 30s client timeout, preventing large file timeouts
(local Bot API supports up to 200 MB)
---------
Co-authored-by: viettranx <viettranx@gmail.com>
- Read existing IDENTITY.md before overwriting to preserve emoji field
- Config tab: merge existing other_config to prevent wiping emoji on save
- Emoji input: validate single emoji only with extractSingleEmoji()
- Select-all on focus for easy emoji replacement
The delegation system this depended on was previously removed,
leaving internal/hooks/ as dead code with zero imports. Remove
the entire hook engine, UI config section, protocol types, i18n
keys, and all documentation references.
Search now acquires the team create lock and marks listed, so leaders
can use search instead of list before creating tasks — reduces token
consumption by returning only matching tasks instead of the full board.
Flip write_file deliver param default from false to true so result files
(reports, articles, generated content) are automatically sent as document
attachments to chat channels without requiring explicit LLM opt-in.
Add .md MIME type to mimeFromExt for proper markdown file delivery.
Add checkbox column to task list view for selecting terminal-status
tasks (completed/failed/cancelled). Header checkbox supports select-all
with indeterminate state. Bulk action bar appears on selection with
delete button that opens ConfirmDeleteDialog requiring user to type
"delete" to confirm.
Backend: new teams.tasks.delete-bulk RPC method with DeleteTasks batch
SQL (DELETE ... WHERE id = ANY($1) RETURNING id). Broadcasts delete
event per task for real-time UI sync.
i18n: added bulk action keys for en/vi/zh.
Remove premature EventTeamTaskAssigned broadcast in executeCreate() that
caused duplicate "assigned to" Telegram notifications. Assignment
notification now only fires at actual dispatch (post-turn, fallback, or
unblocked).
Add TeamNotifyQueue (2s debounce, cap 20) to batch rapid-fire task
notifications per chat — reduces N messages to 1 when leader dispatches
multiple tasks at once. In leader mode this also reduces agent turns
from N to 1.
Also fix: ResetTaskStatus now clears progress_percent/progress_step on
retry, and retry broadcast includes TaskNumber/Subject for correct
notification formatting.
Clear progress_percent in DB on all terminal transitions (complete,
cancel, fail, approve, reject). Also hide progress bar in UI for
terminal statuses as a safety net (kanban, list, detail dialog).
* feat: Implement MCP manager for server connections, tool registration, and deferred tool loading for agents.
* feat: Add tests for deferred tool activation logic within the tool registry and agent loop.
* fix(mcp): prevent deny list bypass via lazy activation + fix idempotency race
- Add PolicyEngine.IsDenied() to check deny patterns (incl. group: expansion)
before allowing lazily-activated deferred tools to execute
- Check IsDenied() in both single-tool and parallel execution paths in loop.go
- Make ActivateToolIfDeferred idempotent by checking activatedTools before
returning false, preventing concurrent goroutines from being blocked
- Add tests for deny-on-first-call, group deny patterns, and idempotent
concurrent activation
---------
Co-authored-by: viettranx <viettranx@gmail.com>
* feat(telegram): implement robust message splitting and dynamic HTML retry logic
* fix(telegram): fix sendHTML error chain regression and add split depth limit
- Re-check err.Error() in thread-not-found handler instead of stale errStr,
restoring the original chained fallback behavior
- Add maxSplitDepth (5) to prevent unbounded recursion when Telegram
repeatedly rejects split chunks
- Rename misleading test case to reflect actual monolithic fallback behavior
---------
Co-authored-by: viettranx <viettranx@gmail.com>
- Fix progress event payload missing TaskNumber, Subject, OwnerAgentKey,
ProgressPercent, ProgressStep — notifications were rendering empty
- Fix progress notification format to include task name (consistent with
dispatched/failed) and guard empty ProgressStep
- Change percent tool schema from number to integer for clarity
- Add pre-run member task reminder injecting task context before LLM loop
- Add mid-loop progress nudge every 10 iterations with suggested percent
based on iteration ratio (handles maxIter=0 unlimited case)
- Enhance leader cross-session reminder to show progress % when available
- Strengthen TEAM.md member guidance: focus, result quality, progress rules
- Add progress bar to task list table view (matches kanban card pattern)
- skill_manage builtin tool default Enabled: true (available in registry)
- When skill_evolve=false: filter skill_manage from both tool definitions
(API params) and system prompt tooling section — agent has zero awareness
- When skill_evolve=true: tool visible + system prompt guidance + nudges
- Update UI hints to reflect tool is available by default
sendMessageDraft causes "reply to deleted message" artifacts on some
Telegram clients (tdesktop#10315). Disable by default so content
streaming uses message transport (same as reasoning stream).
Predefined agents now retain all tools and system prompt sections when
BOOTSTRAP.md is present, instead of entering slim mode with only write_file.
Open agents keep the existing slim bootstrap mode.
- Gate tool filtering and IsBootstrap on agentType != "predefined"
- Add FIRST RUN reminder for predefined agents (without tool restriction)
- Skip bootstrap/user seeding for team-dispatched sessions (IsTeamSession)
- Group chats skip BOOTSTRAP.md entirely
- Track bootstrapWriteDetected + inject nudge after 2 turns without write_file
- Update templates: never reveal process, no capability listing, no "locked"
- Cache LoadContextFiles via existing agentCache/userCache (TTL 5min)
Remove fields the model doesn't need from tool response DTOs:
- owner_agent_id → model uses owner_agent_key
- created_by_agent_id → model uses created_by_agent_key
- task_number → model uses identifier (e.g. "T-018-5e65")
Reduces context token consumption. UI (WS API) unaffected —
uses separate TeamTaskData struct with all fields.