Add early detection and graceful degradation when the model exhausts its
iteration budget without producing a text response:
- Inject 75% budget nudge telling model to start summarizing
- Strip all tool schemas on final iteration to force text-only response
- Adaptive web_fetch maxChars (60K→20K→10K) via iteration progress context
- Bump DefaultMaxIterations 20→30 for web-research workloads
- Reduce quickClassify threshold from 60 bytes to 15 runes to prevent
false positives (e.g. "làm đơn giản thôi" no longer matches "thôi")
- Use utf8.RuneCountInString for accurate Unicode length check
- Replace substring match with whole-word boundary check to avoid
false positives like "nonstop" matching "stop"
- Remove statusKeywords; only exact "?" is fast-pathed as status query,
all other status queries go through LLM classification
- Increase LLM classify timeout from 5s to 10s
- Differentiate steer (inject into running loop) from newTask (queue
behind active run) instead of treating both as mid-run injection
- Fix TrimLeft → TrimPrefix to strip only one underscore separator
- Add registryName to indexedResult for parallel path cache
- Use registryName for bootstrapToolAllowlist and loopDetector checks
- Add server-side sanitization for ToolCallPrefix input
- Remove dead chatMessages alias and unrelated ParseStripAssistantPrefill
- Remove orphaned stripAssistantPrefill i18n keys
- Add 13 unit tests for StripToolPrefix
* feat(agent): support tool call prefix stripping for proxy providers
Proxy providers like LiteLLM and OpenRouter may prepend a prefix to
tool call names returned by the model (e.g. "proxy_exec" instead of
"exec"). This broke tool policy validation, registry lookup, and
hardcoded name checks for "team_tasks" and "spawn" in both serial
and parallel execution paths.
Add per-agent toolCallPrefix configuration that strips the configured
prefix from incoming tool call names before registry resolution. The
stripping is applied at resolveToolCallName() which is called before
permission checks, registry execution, and spawn/team_tasks detection.
- Add StripToolPrefix() supporting literal ("proxy_") and template
("{tool_name}") patterns
- Add toolCallPrefix to ToolPolicySpec with backward compat from old
"toolPrefix" JSON key
- Fix config save using spread operator to prevent dropping new fields
- Add UI input in Tool Policy section with i18n (en/vi/zh)
* chore: ignore AI tool config directories
Add .gemini/, .claude/, .opencode/ to .gitignore to prevent
committing user-specific AI tool configurations.
Replace single-value idRemap with queue-based idQueue so multiple tool
results sharing the same original ID pair correctly in encounter order.
Previously the map overwrote on duplicates, causing the second result to
be dropped and a synthetic placeholder to be emitted instead.
The squash merge of #291 missed the parallel tool execution path.
Tool errors running in parallel still displayed as "Done" instead
of "Failed" when reloading chat history.
Simplified approach to fix duplicate tool_call_ids that cause HTTP 400 on OpenAI-compatible APIs (OpenRouter, vLLM, DeepSeek).
- uniquifyToolCallIDs: appends runID+iteration+index to all IDs at response time, guaranteeing cross-turn uniqueness via UUID
- sanitizeHistory: simple seen-set dedup for legacy sessions with pre-existing duplicate IDs
- Anthropic guard: skips ID rewriting when RawAssistantContent is present
- Unit tests for both paths + edge cases
Closes#283
After the first agent run completes, generate a short title from the
user's message using a 10s/50-token LLM call. Title is stored in the
session label column and pushed to clients via session.updated event.
Detect agents stuck in read-only loops (consecutive non-mutating tool
calls) and same-result patterns (identical outputs from different args).
Warns at threshold, force-stops at critical.
* feat(feishu): add list_group_members tool + native @mention support
Add agent tool to list all members in a Lark group chat via
GET /open-apis/im/v1/chats/{chat_id}/members with auto-pagination.
- New tool `list_group_members`: returns member IDs + display names
- Native @mention conversion: @ou_xxx in outbound messages becomes
real Lark mentions (post: "at" element, card: <at> markdown tag)
- Auto-sync discovered members into ContactStore
- GroupMemberProvider interface on Channel for extensibility
- Add im:chat.members:read scope to channel setup UI
Requires Lark app scope: im:chat.members:read
* fix(feishu): URL-encode API params + channel-aware tool filtering
- URL-encode chatID (PathEscape) and pageToken (QueryEscape) in
ListChatMembers to prevent path traversal and query corruption
- Add slog.Warn when ListGroupMembers fails for observability
- Add ChannelAware interface so tools can declare required channel types
- ListGroupMembersTool now declares RequiredChannelTypes=["feishu"]
- loop.go filters out channel-specific tools when channel type mismatches,
so list_group_members only appears for Feishu conversations
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
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
- 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>
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.
- 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)
- 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
- 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
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.
* 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>
- 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
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)
- Lead agents: auto-resolve team workspace as default (relative paths)
- Dispatched members: team workspace as default via req.TeamWorkspace
- Direct-chat members: own workspace default, team workspace accessible
- Add dataDir field to Loop/LoopConfig for global workspace root
- System prompt shows team workspace absolute path for model guidance
- Remove orphan task detector (superseded by post-turn dispatch)
- Log warning on OpenAI tool call argument parse failures
- Move task dispatch from mid-turn to post-turn to prevent dependent
tasks from completing before the current agent's run finishes
- Add team create lock to serialize list→create flows across concurrent
group chat sessions, preventing duplicate task creation
- Require list-before-create gate: agents must call team_tasks(list)
before creating tasks
- Make assignee required on task creation
- Add pagination (50 per page) to task list with offset support
- Slim task list/get/search responses with dedicated structs to reduce
context token usage
- Add task board snapshot in announce messages to leader
- Workspace: allow subdirectory paths in read/delete, show directories
in list output
- UI: reduce kanban card title font size for better visual balance
Team task announce was writing to wrong session (direct instead of group)
because origin_peer_kind was hardcoded as "direct" in dispatch metadata.
This caused leaders to miss completed task results in group conversations.
- Store peer_kind and local_key in task metadata at creation time
- Resolve peer_kind from context → metadata → "direct" fallback in all
dispatch paths (tool, gateway, unblocked)
- Use actual origPeerKind in announce handler session key + request
- Add origin_local_key to gateway dispatch for forum topic routing
- Clarify ask_user guidance: bot must present question directly
- Guide members to use team_tasks progress instead of team_message
- Improve error message when non-owner calls progress action
- Move cache_control from request root (ignored by API) to per-block
placement on last system block and last tool definition
- Change system prompt time format to date-only for better cache stability
- Add builtin datetime tool for precise timestamps (cron, memory, etc.)
- Add atMs past-time validation in cron handleUpdate (was only in handleAdd)
- Update cron description to guide model to use datetime tool first
Remove ModelThinkingCapable interface and ChatRequest.ModelSupportsThinking
hint field — DashScope handles per-model checks internally via its own
whitelist. Fix double applyThinkingGuard on ChatStream→Chat tool fallback
by calling OpenAIProvider.Chat directly.
* feat(providers/dashscope): guard enable_thinking injection by per-model capability check
Introduces ModelThinkingCapable interface and ModelSupportsThinking field
on ChatRequest so DashScope can skip thinking-param injection for models
that do not support it (e.g. qwen3-plus, qwen3-turbo), preventing
\"model not supported\" API errors.
- types.go: add ModelThinkingCapable interface + ModelSupportsThinking *bool on ChatRequest
- dashscope.go: add dashscopeThinkingModels whitelist + ModelSupportsThinking(); honour pre-computed hint
- loop.go: detect ModelThinkingCapable and set hint on ChatRequest before LLM call
- provider_models.go: add qwen3.5-plus / qwen3.5-turbo to DashScope model list
- dashscope_test.go: full test suite for whitelist, injection, hint override, budget mapping
* Fix review code.
---------
Co-authored-by: Nguyen Gia Hoang Vinh <vinhngh@runsystem.net>
- Add terminal-state check in executeCreate(): reject blocked_by
referencing completed/cancelled/failed tasks with actionable error
- Add full validation in executeUpdate(): batch query via GetTasksByIDs,
check existence + team membership + terminal state
- Add GetTasksByIDs batch query to TeamStore interface + pg implementation
- Refactor: modularize gateway, skills store, and team tools into
focused files
- Update TEAM.md leader prompt: prefer delegation, plan full task graph
upfront, create tasks in order with blocked_by UUIDs
- Add card/list view toggle on teams page (matching agents page pattern)
- Card view: member chips with emoji/Bot icon, frontmatter, crown for lead
- List view: comma-separated member names with frontmatter tooltip on hover
- Show version badge from team settings in both views
- Enrich ListTeams API to bulk-fetch members with emoji from other_config
- Add agent emoji field (other_config.emoji) to create/update forms
- Show emoji in team members dialog instead of Bot icon when available
- Force restrict_to_workspace=true system-wide, remove UI toggle
- Add i18n keys for all 3 locales (en/vi/zh)
- Fix storage file listing to skip subtree root when ?path= is used,
preventing duplicate folder nesting in the file browser tree
- Comment out os.Remove calls in persistMedia to keep original workspace
files after persisting to media store
Major refactoring of the team system with multiple improvements:
## Removed legacy delegation tools
- Delete `delegate.go`, `delegate_async.go`, `delegate_sync.go`, `delegate_events.go`,
`delegate_policy.go`, `delegate_prep.go`, `delegate_state.go`, `delegate_search_tool.go`
- Delete `evaluate_loop_tool.go`, `handoff_tool.go`
- Remove all references and registrations from tool manager and policy
- Clean up TEAM_PLAYBOOK_IDEAS.md and TEAM_SYSTEM.md (moved to docs)
## Rename await_reply → ask_user
- Rename action `await_reply` → `ask_user`, `clear_followup` → `clear_ask_user`
- Rename functions `executeAwaitReply` → `executeAskUser`, `executeClearFollowup` → `executeClearAskUser`
- Update system prompt with stronger wording to prevent model misuse
- Model was confusing "await_reply" with general waiting; "ask_user" is unambiguous
## Fix auto-followup false positives
- Add `HasActiveMemberTasks(ctx, teamID, excludeAgentID)` store method
- Guard `autoSetFollowup()` in consumer: skip when lead has active member tasks
- Prevents auto-followup when lead is orchestrating teammates (not waiting for user)
## Task identifier zero-padding
- Change format from `T-1-xxxx` → `T-001-xxxx` (3-digit minimum)
## Refactor workspace WS handlers to filesystem-only
- Rewrite `teams.workspace.list/read/delete` to use pure filesystem (os.ReadDir/ReadFile/Remove)
- Remove DB dependency from workspace WS handlers
- Consistent with storage handler and workspace tools
- Simplify TeamWorkspaceFile type and frontend hook
## Add team events listing API
- New WS method `teams.events.list` with team_id, limit, offset params
- New HTTP endpoint `GET /v1/teams/{id}/events` with bearer auth
- New `ListTeamEvents(ctx, teamID, limit, offset)` store method
- JOIN with team_tasks for team-wide event filtering
## Extract team access policy
- New `team_access_policy.go` — centralized team tool access control
## Migration 000019: team_id columns
- Add team_id foreign key columns to relevant tables
## Other improvements
- Add team_id propagation through agent loop, tracing, sessions
- Update i18n locale files (en/vi/zh) for new tool labels
- Update frontend builtin-tools page and require-setup component
- Bump RequiredSchemaVersion for migration 000019