Commit Graph
180 Commits
Author SHA1 Message Date
viettranx dba3f4e4e6 fix(agent): prevent "..." fallback when iteration budget exhausted
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
2026-03-20 17:55:51 +07:00
Viet TranandGitHub 84650c5c14 feat(teams): attachments refactor, semantic search, improved prompting (#310)
* feat(teams): refactor attachments, remove team_message, add task comments UI

Major team system refactoring:

- Drop team_workspace_files, team_workspace_file_versions, team_workspace_comments,
  team_messages tables; replace team_task_attachments with path-based schema
- Add denormalized comment_count/attachment_count on team_tasks for dashboard perf
- Auto-track file writes as task attachments via WorkspaceInterceptor
- Remove team_message tool entirely (tool, store, i18n, builtin_tools, MCP bridge)
- Members communicate via task comments; approve/reject use comments for audit trail
- Add commented/new_task notification types to TeamNotifyConfig
- Enrich task completion announce with member comments
- User-created tasks stay pending (backlog) — no auto-assign to leader
- Configurable member request tasks (member_requests.enabled in team settings)
- Structured task description template in TEAM.md for v2 leads
- HTTP attachment download endpoint with IDOR + path traversal protection
- Web UI: count badges on task list, comments section with input, download button
- Team settings UI: completed/commented/new_task toggles, member requests section

* feat(teams): priority dispatch, compact prompting, realtime comments

- Priority dispatch: DispatchUnblockedTasks dispatches only 1 task per
  owner per round (highest priority first). Fixes cancel bug where
  CancelSession killed innocent queued tasks.
- Prompt rework: Replace verbose Task Decomposition (25 lines) with
  compact Task Planning (8 lines). Add explicit UUID warning and
  sequencing guidance for weak models (Qwen, MiniMax).
- Recent comments in dispatch: buildRecentCommentsSummary appends 3
  most recent comments to re-dispatched tasks (reject, retry, stale).
- Enrich comment event payload with TaskNumber, Subject, CommentText
  (truncated 500 runes, UTF-8 safe).
- UI: Board subscribes to TEAM_TASK_COMMENTED for realtime comment_count
  badge updates. Task detail dialog auto-refreshes comments on event.
- Tool description hint: guide models to write self-contained task
  descriptions with clear objectives and context.

* perf(teams): add ListRecentTaskComments with SQL LIMIT

Dispatch only needs 3 most recent comments — avoid fetching all.
New ListRecentTaskComments(ctx, taskID, limit) uses ORDER BY DESC
LIMIT N then reverses to chronological order.

* feat(teams): add subject embedding for semantic task search + improve prompting

- Add vector(1536) embedding column to team_tasks with HNSW index
- Implement hybrid search: FTS (0.3) + cosine similarity (0.7) with graceful fallback
- Auto-generate embeddings on task create/update, backfill existing tasks on startup
- Wire embedding provider into PGTeamStore via gateway_setup
- Change FTS from OR to AND with prefix matching for precise keyword search
- Reduce search page size from 30 to 5 to save tokens
- Rename migration 000023 → 000024, bump RequiredSchemaVersion to 24
- Update TEAM.md hints: prefer search over list, batch task creation with blocked_by
- Add anti-pattern examples to prevent sequential task creation
2026-03-20 17:51:40 +07:00
viettranx 38dfcf8bb0 feat(tools): add Defuddle extractor chain for web_fetch (#296)
Add Cloudflare Worker (fetch.goclaw.sh) as primary markdown extractor
with waterfall fallback to built-in HTML→Markdown converter.

Architecture:
- ExtractorChain pattern with quality gate (min 100 chars, 10 words)
- Settings stored in builtin_tools DB table (not config.json5)
- ResolveExtractorChain reads chain from context per-request
- InProcessExtractor delegates to fetchRawContent (full SSRF + domain
  policy checks on redirects)
- DefuddleExtractor with configurable base_url + timeout
- Seed default chain [defuddle, html-to-markdown] for new deployments
- Backfill migration for existing deployments

Web UI:
- Dedicated DnD extractor chain form on builtin tools page
- Drag-and-drop ordering, enable/disable per extractor
- Timeout + base URL config for Defuddle
- i18n support (en/vi/zh)
2026-03-20 14:21:46 +07:00
viettranx 6e94c5fdf8 fix(agent): review fixes for tool call prefix stripping (#259)
- 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
2026-03-20 09:14:28 +07:00
HXD.VNandGitHub ffb536861f feat(agent): tool call prefix stripping for proxy providers (#259)
* 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.
2026-03-20 09:10:06 +07:00
viettranx 89937edb22 fix(team): prevent task progress regression from model actions 2026-03-20 08:27:18 +07:00
viettranx a4a7a59b6a fix(sandbox): path isolation, DooD volume mounting, hints, and nginx DNS (#129, #136)
Address issues identified in PR #137 with a cleaner approach:

- Sandbox isolation: add SandboxCwd/ResolveSandboxPath helpers to map
  filesystem tool paths to agent-scoped container subdirectories,
  preventing cross-agent file access via read/write/edit/list tools
- DooD volume mounting: add resolveHostWorkspacePath with multi-strategy
  container ID detection (/proc/self/mountinfo, HOSTNAME, os.Hostname)
  and 5s timeout on docker inspect
- Sandbox hints: expand from 1 pattern (binary not found) to 6 patterns
  (permission denied, network disabled, read-only FS, missing file,
  resource limits) with MaybeFsBridgeHint for filesystem tools
- Nginx DNS: add Docker resolver (127.0.0.11) with dynamic upstream
  variable to handle backend container IP changes
- MCP args: switch from comma-separated to space-separated parsing
  with quote support for --flag="value with spaces" patterns
- Refactor: rename ExecTool.workingDir to workspace for consistency,
  extract sandbox.DefaultContainerWorkdir constant
2026-03-20 07:32:23 +07:00
viettranx b80ee0ae43 feat(sandbox): hint LLM when tool/binary missing in sandbox container
Detect exit code 127 and "command not found" patterns in sandbox
execution paths and append a [SANDBOX] hint to the error output,
so the model can inform the user about sandbox environment limitations.

Only applies to sandbox execution — host paths are untouched.
2026-03-19 22:17:29 +07:00
viettranx 78ffc57a8e fix(ws): propagate origin_session_key for team task announces
Team task announces rebuilt session keys via BuildScopedSessionKey(),
producing different format than WS sessions (ws:direct:uuid vs
ws-userId-ts). Now stores and forwards origin_session_key through
task metadata and dispatch, matching subagent announce pattern.

Also adds "ws" to InternalChannels to suppress spurious warnings.
2026-03-19 19:01:10 +07:00
2c1efa29ec fix(memory): prevent silent data loss on memory file overwrites (#279)
* fix(memory): auto-backup + overwrite warning for memory files

When an agent writes to a memory file that already has content:
1. Old content is backed up to memory/.prev/{filename} (best-effort)
2. Write succeeds normally (no blocking)
3. Tool result includes warning with previous content (truncated to
   4000 chars) so the agent can notice and merge if needed

Backup files are hidden from list_files and not indexed/embedded.
No database migration needed - uses existing PutDocument.

* fix(memory): support append mode + fix UTF-8 truncation in overwrite warning

- Add appendMode parameter to MemoryInterceptor.WriteFile() so
  write_file(append=true) actually appends to memory files instead
  of silently replacing content. Uses "\n\n---\n\n" separator.
- Fix UTF-8 truncation in overwrite warning: use []rune instead of
  byte slicing to avoid cutting multi-byte characters.
- Fix char count in warning message: use len([]rune()) not len().
- Remove .prev/ backup mechanism (prevBackupPath, isPrevPath, backup
  writes, ListFiles filter) in favor of simpler approach: append
  mode prevents data loss, warning enables self-correction.

---------

Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
2026-03-19 17:42:03 +07:00
bb783b952c feat(feishu): add list_group_members tool + native @mention support (#280)
* 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>
2026-03-19 17:21:38 +07:00
viettranx ec24b982f3 fix(subagent): replace bare count with deterministic roster in announce messages
Subagent announce messages told the parent LLM "There are still X active
runs" — a bare number. The LLM had to reconstruct which subagents were
done vs running from conversation history, causing hallucination when
results arrived out of order.

Replace with a full roster listing each subagent by label and status,
including per-agent config limits. Remove dead CountRunningForParent()
and unused rosterFunc field on AnnounceQueue.
2026-03-19 16:33:54 +07:00
efd3d15c70 fix: agent add cron within invalid params (#277)
Co-authored-by: Chung Tran <chung.tran@diqit.io>
2026-03-19 14:15:46 +07:00
viettranx 6595c0185a refactor: merge file writers into config permissions + agent hard delete
Migration 000023:
- Migrate group_file_writers data into agent_config_permissions (config_type='file_writer')
- Add FK CASCADE/SET NULL on 16 tables for agent hard delete
- Clean up soft-deleted zombie agents
- ALTER scope VARCHAR(100) → VARCHAR(255)
- Partial unique index on agent_key (allows reuse after delete)

Store layer:
- Remove GroupWriterCache, GroupFileWriterData, 5 AgentStore methods
- Add CheckFileWriterPermission (fail-open), ListFileWriters (cached)
- Add scope filter to ConfigPermissionStore.List()

Tools: Replace GroupWriterCache with ConfigPermissionStore in read_file,
write_file, edit, cron, context_file_interceptor

Agent loop: buildGroupWriterPrompt uses ListFileWriters + metadata parsing

Channels: Telegram commands rewritten to use configPermStore.Grant/Revoke/List
HTTP API: Writer endpoints rewired to configPermStore (same response shapes)

Hard delete: agents.go DELETE FROM instead of soft delete
2026-03-19 13:35:57 +07:00
viettranx 4e9f155a4c feat(agent): adaptive tool timing with slow tool notification
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
2026-03-19 13:35:57 +07:00
viettranx 0df619023c feat(tools): block binary in read_file, add workspace path to read_image
- read_file: reject binary files (images, audio, video, archives) with
  helpful error pointing to the correct specialized tool
- read_image: add optional `path` parameter to analyze workspace/generated
  images via vision API (with workspace restriction + denied path checks)
2026-03-19 13:35:57 +07:00
2cc9d68cdc fix(tts): config save, Edge provider, media dispatch + dark mode chat (#265)
* 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>
2026-03-19 08:21:06 +07:00
dc51018563 fix: subagent provider routing + api_base fallback (#262)
* 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>
2026-03-18 22:40:49 +07:00
viettranx 2504095dfe fix(agents): complete shell deny groups propagation chain
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.
2026-03-18 17:04:26 +07:00
viettranx 29816db0ab feat(heartbeat): cron wakeMode, queue-aware scheduling, lightContext
- CronPayload.WakeHeartbeat triggers heartbeat immediately after cron job completes
- Cron tool supports wake_heartbeat param on add/update actions
- Scheduler.HasActiveSessionsForAgent() detects busy agents for heartbeat skip
- RunRequest.LightContext skips loading context files during heartbeat runs
2026-03-18 13:11:58 +07:00
viettranx 08a2d95c0c feat: agent heartbeat system — periodic proactive check-ins (#245)
Phase 1 (Core):
- Migration 000022: agent_heartbeats, heartbeat_run_logs, agent_config_permissions tables
- HeartbeatStore + ConfigPermissionStore interfaces with PG implementations
- HeartbeatTicker: background poll → active hours filter → queue-aware skip → run → smart suppression → deliver/log
- Heartbeat tool: status/get/set/toggle/set_checklist/get_checklist/test/logs actions
- Permission check with wildcard scope matching + TTL cache (60s)
- RPC methods: heartbeat.get/set/toggle/test/logs/checklist.get/checklist.set
- HEARTBEAT.md routed via context file interceptor (read/write for both open + predefined agents)
- Session keys: agent:{id}:heartbeat or agent:{id}💓{ts} (isolated)
- PromptMinimal for heartbeat sessions (like cron/subagent)
- Event broadcasting + cache invalidation via bus (heartbeat + config_perms)
- Gateway wiring: ticker init, event wiring, graceful shutdown

Phase 2 (Integration):
- wakeMode: CronPayload.WakeHeartbeat triggers heartbeat after cron job completes
- Queue-aware: Scheduler.HasActiveSessionsForAgent() skips busy agents
- Stagger: deterministic FNV offset spreads heartbeats across interval
- lightContext: RunRequest.LightContext skips context files, only injects checklist
- System prompt distinguishes cron (user-scheduled tasks) vs heartbeat (autonomous monitoring)
2026-03-18 13:11:44 +07:00
viettranx da59377387 fix(teams): use OR logic for task search instead of AND
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.
2026-03-18 13:02:07 +07:00
viettranx c7d0bc19f8 fix(teams): auto-copy media files to team workspace on task creation, scope task_number per chat
- 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)
2026-03-18 12:58:09 +07:00
viettranx 48ebcf999b fix(tools): add append mode to write_file, warn about content size limits
- 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
2026-03-18 11:05:09 +07:00
viettranx 5a4c72018a fix(teams): include agent display name in task create, list, and announce
- 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
2026-03-18 11:04:56 +07:00
viettranx 49441f7305 refactor: remove dead delegate code, rename lane/channel to team/teammate
- 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
2026-03-18 11:04:45 +07:00
viettranx 120fc2d09c fix(media): chain provider format, post-write verification, group media history (#206)
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
2026-03-18 08:12:10 +07:00
Viet TranandGitHub ce333c70f3 fix(security): followup hardening — ILIKE ESCAPE, allowlist logging, shell deny, tests (#251)
- Add explicit ESCAPE '\' clause to all ILIKE queries (knowledge_graph,
  custom_tools, channel_instances, channel_contacts) for correct wildcard
  escaping regardless of PostgreSQL standard_conforming_strings setting
- Log warning when filterAllowedKeys drops unknown fields for debuggability
- Widen base64 decode shell deny pattern to catch -di, -dw0 variants
- Add unit tests for filterAllowedKeys, pqStringArray, scanStringArray,
  pqStringArray↔scanStringArray roundtrip, limitedBuffer, base64 deny
2026-03-18 07:48:48 +07:00
a7f5acc1e3 fix(security): harden SQL injection, MCP prompt injection, sandbox fallback, and file serving (#246)
- execMapUpdate: validate column names with strict regex to prevent SQL injection
- HTTP update handlers: add field allowlists (agents, providers, custom_tools, mcp, channel_instances)
- pqStringArray: properly escape array elements to prevent PostgreSQL array literal injection
- scanStringArray: handle quoted elements in PostgreSQL array format
- MCP bridge: wrap tool results as external/untrusted content to prevent prompt injection
- File serving: block access to sensitive system directories (/etc, /proc, /sys, etc.)
- Sandbox: fail closed when Docker unavailable instead of silent fallback to host
- Shell deny: fix base64 --decode bypass, add host exec 1MB output limit
- ILIKE queries: escape % and _ wildcards in knowledge_graph, custom_tools, channel_instances

Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
2026-03-18 07:42:38 +07:00
viettranx 843b550651 feat: runtime packages UI, pkg-helper, configurable shell deny groups (#244)
Runtime package management with security hardening:

- pkg-helper: root-privileged daemon for apk install/uninstall via Unix socket
- HTTP API: /v1/packages (list/install/uninstall/runtimes), admin role required for writes
- Shell deny groups: 15 configurable groups (per-agent overrides via context)
- Packages UI: Web page for managing system/pip/npm packages with confirmation dialogs
- Docker: privilege separation (root entrypoint → su-exec drop), init for zombie reaping
- Security: umask socket creation, persist file validation, deny pattern hardening
  (Node.js fetch/http, Python from/import, curl localhost, sensitive env vars)
- Auth: empty gateway token → admin role (dev/single-user mode)
2026-03-17 19:50:26 +07:00
viettranx 31ff5eefca fix(tools): prevent duplicate media delivery from write_file
Tell LLM that auto-delivered files should not be sent again via
message tool, preventing double sends (document + photo) on Telegram.
2026-03-17 18:18:20 +07:00
viettranx b735c16d93 feat(teams): split dispatched/assigned events + add completed notifications
- Change agent-side broadcasts from EventTeamTaskAssigned to
  EventTeamTaskDispatched (post-turn, fallback, unblock, retry)
- Add completed notification with leader-completion skip logic
- Add Completed field to TeamNotifyConfig with *bool backwards compat
- Differentiate dispatched messages: unblocked vs regular dispatch
- Add EventTeamTaskDispatched to audit event mapper
2026-03-17 18:02:54 +07:00
viettranx 6106d3d04f fix(teams): show task_number in create result and allow search to satisfy gate 2026-03-17 17:11:22 +07:00
viettranx 946cbb9eb1 fix(teams): allow search action to satisfy list-before-create gate
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.
2026-03-17 16:00:59 +07:00
viettranx 70f3e1f5d5 feat(tools): auto-deliver write_file results as channel attachments
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.
2026-03-17 15:43:24 +07:00
viettranx aeadb20ba7 fix(teams): deduplicate task notifications and batch with debounce
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.
2026-03-17 14:29:52 +07:00
99dd363b13 feat(mcp): lazy-activate deferred tools on direct call in search mode (#235)
* 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>
2026-03-17 13:18:27 +07:00
viettranx 97cacfe68b feat(teams): member task progress reminder + fix broken progress notifications
- 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)
2026-03-17 12:43:09 +07:00
viettranx b2a74ba487 feat(skills): skill_manage tool + skill_evolve learning loop (#218)
Adds skill_manage — a first-class agent tool for creating, updating, and
deleting skills from within a conversation — paired with per-agent
skill_evolve that nudges predefined agents to capture reusable workflows.

Tool (skill_manage):
- create: write skill from SKILL.md content string (auto-grant, dep scan)
- patch: find/replace producing new immutable version (advisory-locked)
- delete: soft-delete (archive in DB, move to .trash/)
- Security guard: 25 regex patterns block shell injection, credential exfil,
  path traversal, SQL injection, privilege escalation
- Ownership enforced: only skill owner can patch/delete (admin bypass)
- Content size limit: 100KB; companion file copy: 20MB, symlink-safe (WalkDir)
- Enabled: false by default — admin opt-in per agent

Learning loop (skill_evolve, predefined agents only):
- System prompt: SHOULD/SHOULD NOT guidance for skill creation
- Budget nudges: [System] prefix at 70%/90% iteration budget (ephemeral, i18n)
- Postscript: once-per-run suggestion with explicit user consent
- Config: other_config.skill_evolve + skill_nudge_interval (default 15)

Security hardening (pre-existing + new):
- CreateSkillManaged: RETURNING id + pg_advisory_xact_lock (atomic upsert)
- GetNextVersionLocked: advisory lock for race-safe patch versioning
- Ownership checks on HTTP update/delete, gateway update, 4 grant/revoke handlers
- copyOtherFiles: filepath.WalkDir for real symlink detection

UI: Skill Learning toggle + nudge interval in Agent General Tab
i18n: backend (en/vi/zh catalogs) + frontend (en/vi/zh locale files)
2026-03-17 11:38:35 +07:00
viettranx fe68bd86bc perf(tools): cache LoadContextFiles + fix task detail dialog UX
- Cache agent/user context files via existing agentCache/userCache (TTL 5min)
- Extract cachedAgentFiles/cachedUserFiles helpers, DRY readAgentFile/readUserFile
- Move delete button away from close in task detail modal
- Replace native window.confirm with ConfirmDialog component
2026-03-17 09:25:37 +07:00
viettranx 1ca1e41844 refactor(teams): slim down task list/detail JSON for model
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.
2026-03-17 07:15:18 +07:00
viettranx eee79d111e feat(teams): granular progress notifications with direct/leader mode
- Replace progress_notifications toggle with granular config:
  dispatched (on), progress (on), failed (on) + delivery mode
- Direct mode: outbound to channel, no AI processing
- Leader mode: inject into leader session with NO-ACTION instructions
- Add consumer.team-notify subscriber for event forwarding
- Enrich TeamTaskEventPayload with TaskNumber, ProgressPercent/Step
- Add auto-status system prompt section
- UI: card-select for delivery mode (Zap/Bot icons), 3 toggles
2026-03-16 22:46:51 +07:00
viettranx 2c5ae04c01 feat(teams): implement workspace scope setting (shared vs isolated)
- IsSharedWorkspace() reads team.settings.workspace_scope
- Shared: workspace at teams/{teamID}/ (all chats share)
- Isolated (default): workspace at teams/{teamID}/{chatID}/
- Remove _default fallback; isolated mode requires chat_id
- Update loop.go, task creation, task listing, message dispatch,
  workspace API (list/read/delete), task board snapshot
- Update UI descriptions to reflect per-conversation scoping
2026-03-16 22:46:35 +07:00
viettranx 514c5e0bfc refactor(teams): batch TaskTicker queries + leader notifications
- Replace per-team loop with batch SQL (v2 filter in JOIN)
- RecoverAllStaleTasks/ForceRecoverAllTasks/MarkAllStaleTasks return
  RecoveredTaskInfo for notification routing
- Notify leaders per (teamID, channel, chatID) scope with actionable hints
- Fix notifyLeaderCycleError routing (was silently DROPPED)
- Stale threshold: 24h → 2h default
- Remove per-session RecoverStaleTasks from loop.go (ticker handles it)
- Add rows.Err() check to scanRecoveredTaskInfoRows
2026-03-16 22:46:00 +07:00
viettranx 96898a3daa fix(teams): status filter default=all, reduce page size to 30, update WorkspaceDir callers
- status="" now returns all tasks (was active-only); add explicit "active" filter
- Reduce list/search page size from 50 to 30
- Update WorkspaceDir callers after signature change (remove unused channel param)
- Update team_tasks schema descriptions for status and page params
2026-03-16 20:06:15 +07:00
viettranx 8d7259f637 feat(tools): add team workspace context + WorkspaceInterceptor + file tools access
- Add WithToolTeamWorkspace/ToolTeamWorkspaceFromCtx context key for
  team workspace path (accessible but not necessarily default)
- Create WorkspaceInterceptor for team-specific write validation
  (RBAC, quota, blocked extensions, event broadcasting)
- File tools (read_file, write_file, list_files, edit) allow access
  to team workspace via allowedWithTeamWorkspace() helper
- read_file/list_files hint team workspace path when file not found
- Registry detects empty tool call args and returns actionable hint
  (DashScope/Qwen large-output truncation workaround)
2026-03-16 20:05:42 +07:00
viettranx 27fb900510 refactor(tools): remove workspace_read/workspace_write, use file tools for team workspace
Remove dedicated workspace tools in favor of making existing file tools
(read_file, write_file, list_files, edit) team-workspace-aware.

- Delete workspace_tool_read.go and workspace_tool_write.go
- Clean up workspace_dir.go: export WorkspaceDir, remove dead code
  (workspaceRelPath, sanitizeFilePath, inferMimeType, templates, etc.)
- Remove workspace tool registration from gateway_managed.go
- Remove workspace tool references from policy, subagent, MCP bridge
- Add PathAllowable/PathDenyable to types.go for interface abstraction
2026-03-16 20:05:26 +07:00
viettranx 8d6729e959 feat(teams): improve task dispatch, concurrency, and tool ergonomics
- 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
2026-03-16 15:26:25 +07:00
viettranx 0dc3124607 fix(teams): propagate peer_kind and local_key through task dispatch chain
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
2026-03-16 09:01:13 +07:00
viettranx 0857321a6b fix(providers): correct Anthropic prompt caching + add datetime tool
- 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
2026-03-16 08:14:03 +07:00