Commit Graph
100 Commits
Author SHA1 Message Date
viettranx 484d434f6c fix(security): harden sandbox, auth, and shell deny patterns
Sandbox: add noexec/nosuid/nodev to tmpfs mounts, remove SETUID/SETGID/CHOWN
caps, add PidsLimit 256 default, keep no-new-privileges from base.

Auth: reject X-GoClaw-User-Id header spoofing in dev mode (no gateway token),
use full 32-byte HMAC for file tokens instead of truncated 16-byte.

Shell: add NFKC Unicode normalization + zero-width character stripping before
deny pattern matching, add 5 export-prefixed env var deny patterns, fix
exemption logic to check per-argument prefix instead of whole-command substring
(prevents bypass via comments while preserving skill store access).
2026-04-02 18:58:24 +07:00
viettranx 4c485d6fa1 fix(mcp): retry stdio transport initialization with exponential backoff
Heavy MCP servers (FastMCP with 80+ tools, OAuth servers) take 3-5s to
start their stdin read loop. Without retries, connectAndDiscover sends
the initialize request immediately after fork/exec, gets EOF, and
permanently fails with "transport closed".

Add a retry loop (4 attempts, 2s/4s/8s backoff) for stdio transports
only. SSE/HTTP transports fail immediately since connection-refused is
definitive. The retry respects context cancellation to avoid blocking
shutdown.

Closes #385
2026-04-02 18:43:41 +07:00
viettranx 7418cb62aa fix(agent): reduce system prompt size and fix team context injection (#613)
Prompt compression (Issue #613):
- Truncate skill descriptions to 200 runes in inline XML (matches mcpToolDescMaxLen)
- Lower skill inline token threshold from 5000 to 3000
- Compact tool descriptions (risk-audited: preserve behavioral hints)
- Compact boilerplate sections: media hint, safety, tool call style, spawn,
  self-evolve, skill creation, team workspace
- Fix token estimator to account for description truncation
- Restore safety anti-manipulation clauses dropped during compaction

Team context injection fix (found during prod audit):
- Add IsTeamLead field to LoopConfig/Loop, plumbed from resolver
- Gate team context (TEAM.md, workspace, members) on session type:
  leader inbound → team context; member-only inbound → spawn section;
  team dispatch → team context
- Filter TEAM.md from context files for member-only inbound chat
- Skip team member DB query when team context not needed
- Rename HasTeam → IsTeamContext for semantic clarity
- Add 8 table-driven tests for team context injection scenarios
2026-04-02 18:32:43 +07:00
viettranx cace4dcf31 fix(web): improve advanced settings dialog layout and width
Widen settings dialogs from sm:max-w-2xl (672px) to sm:max-w-3xl (768px)
to prevent cramped toggle groups. Move boolean field help text to a
separate line below the switch+label for cleaner readability.
2026-04-02 18:30:30 +07:00
viettranx 4e4a835795 fix(agent): defer warning messages after parallel tool results
When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".

Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.

Closes #642
2026-04-02 16:43:53 +07:00
viettranx bfd65828a6 feat(web): add merge hint to CLI and MCP user credentials dialogs
Both dialogs now show an amber callout explaining that chat users
(Telegram, Discord, etc.) must be merged into a tenant user via
Contacts page before they can have per-user credentials.
i18n: en/vi/zh for both cli-credentials and mcp namespaces.
2026-04-02 15:18:06 +07:00
viettranx 5bad031908 fix(web): align CLI user credentials picker with MCP pattern
Replace raw Combobox + useUserPicker with UserPickerCombobox
component (source="tenant_user") to match MCP user credentials
dialog. Both systems use the same runtime user ID from context.
2026-04-02 15:15:36 +07:00
viettranx ddc5964b85 feat(web): replace user ID text input with Combobox user picker in CLI user credentials
Use existing useUserPicker hook + Combobox component to search
contacts + tenant_users instead of manual UUID entry.
Supports allowCustom for IDs not in the search results.
2026-04-02 15:10:30 +07:00
viettranx 79dce7e523 Merge branch 'hotfix/secure-cli-lookup-ambiguous-column' into dev 2026-04-02 15:02:43 +07:00
viettranx 9c92dead74 fix(web): force remount KG entity detail dialog on entity change
Add key={viewEntity?.id} to prevent stale relations/traversal data
when switching between entities without closing the dialog.
2026-04-02 15:01:20 +07:00
viettranx 19d8faf28f refactor(knowledge-graph): minor UI and handler adjustments 2026-04-02 14:58:03 +07:00
viettranx 3da84fcded fix(web): prevent tool card overflow during streaming
Add min-w-0 to ActiveRunZone flex content container.
Without it, long tool results (e.g. SQL output) push beyond viewport
creating horizontal scroll. MergedToolGroup already had this fix.
2026-04-02 14:57:58 +07:00
viettranx 10cd911dc7 feat(secure-cli): check-binary endpoint, agent select dropdown, fix ambiguous column
Backend:
- Add POST /v1/cli-credentials/check-binary to resolve binary via exec.LookPath
- Security: safeBinaryNameRe regex prevents filesystem probing
- Fix ambiguous column in LookupByBinary LEFT JOIN (secureCLISelectColsAliased)
- Add diagnostic logging to lookupCredentialedBinary

Frontend:
- Replace agent ID text input with Select dropdown (reuse useAgents hook)
- Add Check Binary button next to binary name (auto-fills resolved path)
- Add binary path hint explaining auto-detection from PATH
- Update i18n keys (en/vi/zh) for new UI elements
2026-04-02 14:57:50 +07:00
viettranx ec05c80e90 fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query
LookupByBinary uses LEFT JOIN with secure_cli_user_credentials but
SELECT columns lacked table alias prefix, causing PostgreSQL error:
"column reference 'id' is ambiguous (SQLSTATE 42702)"

This silently broke ALL credentialed CLI exec — commands fell through
to regular shell exec without injected env vars.

Fix: use b.-prefixed column names for JOIN queries.
Also add diagnostic logging to lookupCredentialedBinary for future debugging.
2026-04-02 14:56:49 +07:00
viettranx 0370cabdc9 chore: stabilize main branch with dev workflow + PR guidelines
- Set dev as default branch, protect main (owner-only merge)
- Add CI trigger for PRs targeting dev
- Add PR template with checklist and branch targeting guide
- Add CONTRIBUTING.md with branch strategy and review criteria
- Update README clone command to use -b main for stable
2026-04-02 12:36:30 +07:00
viettranx 62724780b8 feat(web): show richer context in traces list (agent, user, source, input preview)
Traces list Name column now displays agent display name, user label,
source type badge (Direct/Group/Cron/Team/Web), channel badge, and
truncated input preview — making it easy to identify which chat each
trace belongs to.
2026-04-02 12:15:09 +07:00
viettranx 6baef0fb9c fix(telegram): add /reactions to bot menu commands
Register /reactions in DefaultMenuCommands() so it appears in
Telegram's command autocomplete menu after deploy.
2026-04-02 12:05:25 +07:00
viettranx a1323e0d1d feat(telegram): remap reaction emojis and add /reactions command
Remap status reaction emojis for clarity (tool: 🔥→✍, error: 😱→💔)
and reduce overlap between statuses. Add /reactions command to show
emoji legend table to users.
2026-04-02 12:02:12 +07:00
viettranx 899c8eb0b4 fix(skills): fix dep detection false positives and all-or-nothing install
- Filter Python stdlib modules at scan time to prevent false positives
  when the runtime checker fails (e.g. pip:argparse, pip:sys)
- Install pip/npm packages one-by-one instead of batch so partial
  success is preserved when one package fails
- Persist missing deps to DB after install via StoreMissingDeps() so
  reload reflects actual state instead of stale data
- Use explicit master tenant context in handleInstallDeps for
  consistency with rescanAndUpdate()
2026-04-02 11:42:14 +07:00
viettranx ecf6463d98 fix(summon): preserve user's custom display_name during summon/regenerate
Previously, finishSummon() and RegenerateAgent() unconditionally overwrote
display_name with the LLM-extracted name from IDENTITY.md. Now if the user
already set a custom name, it is preserved and IDENTITY.md Name field is
synced to match — keeping UI label and agent self-identity consistent.
2026-04-02 11:04:56 +07:00
viettranx 9d74e39625 fix(telegram): stop auto-linking @mentions to t.me profile URLs
LLM @mentions are not necessarily Telegram usernames. Auto-wrapping
them in <a href="https://t.me/..."> caused unwanted profile cards.
Keep placeholder protection for italic conversion, restore as plain text.
2026-04-02 10:55:41 +07:00
viettranx 1a2d5789d3 fix(ui): show group names in permission scope dropdown
Replace raw scope strings (e.g. group:nta7-goclaw:-5101523...) with
human-readable group names by fetching delivery targets from
channel_contacts via heartbeat.targets API. Scope labels are also
resolved in the permissions list display.
2026-04-02 10:37:19 +07:00
viettranx 3e4d614aa1 feat(media): add Veo 3.1 Lite video generation with image-to-video support (#638)
- Switch default Gemini video model to veo-3.1-lite-generate-preview (50% cheaper)
- Add image_path tool param for image-to-video generation (Gemini Veo only)
- Add resolution and generate_audio as per-provider chain params in UI
- Support dual response formats (Veo 3.1 generatedVideos + Veo 3.0 generatedSamples)
- Gracefully skip image-to-video on providers that don't support it (MiniMax, chat)
- Split create_video into per-provider files (gemini, minimax, chat) for maintainability
2026-04-02 10:33:35 +07:00
viettranx 14c375e33b fix(media): update native provider API defaults and UI schema
- OpenAI image default: dall-e-3 → gpt-image-1.5 (dall-e-3 deprecated May 12)
- DashScope: add missing 4:3 and 3:4 aspect ratio mappings
- UI: remove stale MiniMax size/prompt_optimizer params (backend uses aspect_ratio since #630)
2026-04-02 09:53:01 +07:00
viettranx 83bcb16597 refactor(channels): extract health model and modularize UI components
Post-merge cleanup for #634 channel health diagnostics:

Backend:
- Extract health types, ClassifyChannelError, mergeChannelHealth,
  buildRemediation into internal/channels/health.go
- Fix case-true anti-pattern → default in Slack config
- Remove duplicate connection-refused case in ClassifyChannelError
- Remove unused _ bool param from BaseChannel.setHealth
- Fix snapshot.Enabled == false → !snapshot.Enabled

Web UI:
- Extract channel status utilities to channels-status-utils.ts
- Extract ChannelDiagnosticsCard from channel-detail-page.tsx
- Extract ChannelAttentionPanel from system-health-card.tsx

Desktop UI:
- Extract shared getChannelStatusDisplay() to utils/channel-status.ts
- Add explicit stopped state case for visual consistency with web UI
- Add missing vi/zh locale keys for new health status states
2026-04-02 09:45:41 +07:00
viettranx abcb9614bc feat(providers): allow provider_type changes on update with SSRF re-validation
Remove blanket rejection of provider_type on update. Instead, when
provider_type changes, re-validate the existing api_base against the
new type to prevent SSRF via ACP→non-ACP type switch.

This enables provider type changes in onboarding/setup flows while
maintaining security: ACP providers skip URL validation, so switching
from ACP to another type now forces URL re-validation.
2026-04-02 08:49:54 +07:00
viettranx 3266a66299 feat(workspace): split sharing UI - separate Memory & KG from Workspace
Reorganize workspace sharing section to distinguish shared workspace
from memory/knowledge graph sharing controls.
2026-04-02 08:23:19 +07:00
viettranx fbcff96a49 feat(pending-messages): add topic suffix to group titles
Show topic/thread context in pending messages group titles for clarity.
2026-04-02 08:23:17 +07:00
viettranx 99f4679876 feat(contacts-page): add topic badge, filter, and thread_id display
Add topic filtering and display thread_id column on contacts page.
Show topic badge in contact listing for better organization.
2026-04-02 08:23:16 +07:00
viettranx 7291f31364 feat(cron-ui): move advanced settings inline to overview tab
Refactor cron detail page to consolidate settings in overview tab.
Remove separate advanced dialog, simplifying navigation and reducing layout complexity.
2026-04-02 08:23:14 +07:00
viettranx ab0e2051a6 refactor(heartbeat): simplify and optimize heartbeat store operations
Clean up heartbeat query logic and improve query efficiency across
PostgreSQL and SQLite implementations.
2026-04-02 08:23:11 +07:00
viettranx e682c53f73 feat(heartbeat): reformat delivery layout with channel and chat ID
Improve layout in heartbeat delivery list with 140px channel column
and flexible chat ID column for better readability.
2026-04-02 08:23:09 +07:00
viettranx b663d92fa4 fix(cron): skip loadClaimedJob for manual runs
Allow manual job runs even when job is disabled by checking job ID before
claiming. Prevents skipping manually triggered executions.
2026-04-02 08:23:07 +07:00
viettranx c48639369b fix(cron): apply timezone to all schedule kinds, not just cron
Move TZ application before schedule kind switch in MergeCronSchedule.
Ensure timezone applies consistently to cron, every, and at expressions.
2026-04-02 08:23:05 +07:00
viettranx 7d5c670708 feat(channels): add thread support to all channel handlers
Pass threadID and threadType to EnsureContact across all channel integrations:
- Discord, Feishu, Slack, Telegram, WhatsApp, Zalo
- Include General topic in contact collection
2026-04-02 08:23:03 +07:00
viettranx ac3af93df5 feat(contacts): implement thread_id persistence in PG and SQLite stores
- Update UpsertContact to handle threadID and threadType
- Strip username from sender_id compound key
- Implement in both PostgreSQL and SQLite backends
2026-04-02 08:23:01 +07:00
viettranx bf98a32e9b feat(store): add thread support to contact model and collector
- Add threadID and threadType to ContactCollector.EnsureContact
- Update ContactStore.UpsertContact signature
- Update Contact type in web UI
2026-04-02 08:22:57 +07:00
viettranx 670000651d feat(sqlite): add thread_id columns to contacts schema
Update SQLite schema to include thread_id and thread_type columns.
2026-04-02 08:22:54 +07:00
viettranx 5d632a522e feat(db): add migration 35 for contact thread_id columns
Add thread_id, thread_type columns to track forum topics and threaded conversations.
2026-04-02 08:22:53 +07:00
viettranx 28aa667c77 refactor(cron): redesigned run history with cleaner layout + status icons
- StatusIcon component with CheckCircle2/XCircle for visual feedback
- Compact timeline layout: icon, timestamp, duration badge, summary, tokens, status
- Error messages in monospace <pre> blocks with error-specific styling
- Mobile: duration + tokens move to expanded section
- Hide pagination when single page
2026-04-01 23:24:48 +07:00
viettranx cac3974e7c feat(cron): delivery UI with Select dropdowns + targets fetching
- Replace free-text Input with Select for Channel + To fields
- Fetch delivery targets via HEARTBEAT_TARGETS RPC
- Populate To options based on selected Channel
- Fallback to Input when no targets available
2026-04-01 23:24:44 +07:00
viettranx 4a0d938452 fix(ui): combobox dropdown auto-focus only on user interaction
- Check relatedTarget and document.hasFocus() to distinguish user-initiated focus
- Prevent programmatic focus from opening dropdown unintentionally
2026-04-01 23:24:40 +07:00
viettranx 523b084478 fix(telegram): retry pairing message without thread ID on hidden topic
- Handle 'thread not found' error when forum General topic is hidden/deleted
- Retry without messageThreadID for graceful fallback to main chat
2026-04-01 23:24:38 +07:00
viettranx 2f97d58c83 feat(heartbeat): support topic suffix in delivery targets
- Use array_to_string([5:]) in PG to capture full chatId with topic:N suffix
- Add extractSessionKeyTail() for SQLite forum group support
- Join on base chatId for contact display name resolution
2026-04-01 23:24:36 +07:00
viettranx 8ba4bd7c4a fix(cron): RunJob NULL-safe comparison and timezone persistence
- Use IS DISTINCT FROM instead of != for NULL handling in RunJob claim
- Always apply patch TZ (empty = UTC default). Callers send full schedule
2026-04-01 23:24:33 +07:00
viettranx 04dc34e32c fix(schema): exempt multi-action tools from OpenAI strict mode
Strict mode forces ALL properties to be required, causing models to
send ~13 empty params per tool call on multi-action tools like
team_tasks (17 actions, 16 params). This wastes ~200-300 output tokens
per call.

- Add IsMultiActionSchema() to detect tools with action+enum pattern
- Per-tool strict decision in CleanToolSchemas (exempt multi-action)
- Extract normalizeWithProfile() for per-tool profile overrides
- Add per-action param guide to team_tasks description
- Fix schema/impl mismatches: file_id→path, add task_type property
- Gate param guide on policy (lite edition only sees allowed actions)
2026-04-01 22:00:05 +07:00
viettranx 43232c6189 fix(heartbeat): clarify HEARTBEAT_OK decision criteria to suppress "no news" responses
Model was delivering formatted "nothing new" status updates instead of using
HEARTBEAT_OK because instructions were ambiguous about what "nothing to deliver"
means. Now explicitly states: "A no news summary is NOT worth delivering."
2026-04-01 21:16:44 +07:00
viettranx da240a5e09 fix: pool validation false positive + OAuth step advancement + pool routing sync (#583, #594)
- Skip disabled providers in pool graph validation to prevent false
  "member already exists" errors when stale pool configs linger (#583)
- Add retry logic (3 attempts, 1s delay) in setup wizard OAuth flow
  to handle backend commit race condition (#594)
- Reset chatgpt_oauth_routing in agent other_config when provider
  changes, with amber warning in UI (#583)
- Add 3 pool validation tests for disabled provider scenarios
- Add providerChangedWarning i18n key (en/vi/zh)
2026-04-01 19:31:02 +07:00
viettranx d819e08071 fix(security): fix media upload permission denied + symlink protection
- Fix workspace dir ownership in Docker entrypoint: chown dirs not owned
  by goclaw on startup (handles dirs created by root in previous lifecycle)
- Add symlink check on .uploads/ via os.Lstat before file creation to
  prevent symlink-based attacks replacing .uploads with link to sensitive dir
2026-04-01 18:11:42 +07:00
viettranx 1b190fa0bb fix(prompt): reduce mechanical chat behavior + optimize system prompt
- Add Tool Call Style section with narration minimalism + non-disclosure
  rule (from TS reference): agents must never expose tool names to users
- Consolidate 3 redundant memory recall reminders into 1 dedicated section
- Remove "tell the user you checked but found nothing" instruction that
  caused agents to describe internal tool mechanics in responses
- Remove 11 tool aliases from system prompt listing (~300 tokens saved);
  aliases still work via provider definitions
- Filter alias tool names out of system prompt ToolNames in loop_history
- Update AGENTS.md: remove tool name references from Memory section,
  add group chat framing from V1 ("participant, not their proxy")
2026-04-01 16:27:41 +07:00
viettranx c388364d2c fix(ui): fix chat streaming race condition + require agent selection + improve chat UX
- Fix race condition where session-change effect cleared runIdRef after
  run.started already captured it, causing chunk events to be filtered
  out (user saw "thinking" but no streamed tokens on new chats)
- Add SessionRunID to router + return runId in session status response
  as backup restoration for event filtering
- Require explicit agent selection before chat input is shown
- Redesign ChatInput: attach icon inside input container, aligned send
- Port desktop UX: wobble animation for tool calls, auto-expand thinking
  block on stream start, amber icon for streaming, iteration step count
2026-04-01 16:12:44 +07:00
Viet Tran 52c67d6d92 feat(build): embed web UI in backend binary + simplify Docker variants (#620)
- Add internal/webui/ package with //go:build embedui tag for optional
  SPA embedding (handler.go serves static files with SPA fallback)
- Add internal/version/ shared semver comparison (DRY: extracted from
  gateway/update_check.go and updater/updater.go)
- Enhance UpdateChecker: release notes, ETag caching, filter lite-v* tags
- Add web UI build stage to Dockerfile with ENABLE_EMBEDUI build arg
- Simplify CI: 7 Docker variants → 4 (base, latest, full, otel)
- Add SHA256 checksums job to release workflow
- Add Makefile build-full target (embeds web UI in Go binary)
- Default make up now embeds web UI (no separate nginx needed)
- Add WITH_WEB_NGINX=1 flag for optional nginx reverse proxy
- Update README + 30 translated READMEs: make up, port 18790
- Update docker-compose comments and prepare-env.sh
- About dialog: show release notes with markdown rendering
- Health card: amber badge for available updates

BREAKING: Default Docker setup no longer requires selfservice overlay.
Web dashboard served at :18790 (same port as API).
2026-04-01 15:25:59 +07:00
viettranx 39bb90bd60 refactor(permissions): remove auto-add file writer, add config type constants
- Remove auto-add logic that granted file_writer permission to the first
  group/guild member who chatted with the bot
- Add ConfigTypeFileWriter and ConfigTypeHeartbeat constants, replace all
  hardcoded config_type strings across callers
- Add bootstrap exception: /addwriter and !addwriter allow first writer
  to be added when no writers exist yet
- Optimize writer commands: reuse cached ListFileWriters result for both
  permission check and last-writer guard, reducing DB queries per command
- Add freshness directive to file writer system prompt so bot prioritizes
  current list over stale references in conversation history
2026-04-01 13:59:56 +07:00
viettranx f4369c51e4 fix(providers): make provider_type immutable + add argString tests
Prevent SSRF bypass via provider_type change on update — ACP skips URL
validation, so changing type post-creation could circumvent the check.
Add table-driven unit tests for argString() covering all JSON type
coercion paths (float64, int, NaN, json.Number, nil).
2026-04-01 11:59:21 +07:00
viettranx 9c2b4cbf0f fix(agent): improve memory recall accuracy and flush safety
- Add "low confidence" instruction to memory_search tool description
  to prevent models from fabricating memories when no results found
- Add dedicated ## Memory Recall section in system prompt (supplements
  recency reminder) with clear instructions for memory_search/memory_get
- Update flush prompts: replace YYYY-MM-DD with actual date at runtime,
  cleaner append-only wording
- Update AGENTS.md memory privacy section for multi-tenant: remove
  implementation details (per-user scoping), keep group chat output
  guardrails that work for both shared and isolated memory configs
2026-04-01 09:16:16 +07:00
viettranx 5b66d64545 fix(ui): decouple Edit with AI from SummoningModal, improve agent create UX
- RegenerateDialog now handles its own progress via WS events instead of
  opening SummoningModal (inline spinner + auto-close on completion)
- Clean up SummoningModal: remove mode/isRegenerate prop, summon-only
- Agent create: default to Predefined, collapse Open type behind toggle
  with warning banner explaining per-user context trade-off
- Add 4 new agent presets: Coder, Support, Writer, Translator (en/vi/zh)
- Remove 15 dead summoning.regenerate* i18n keys
2026-04-01 09:06:41 +07:00
viettranx 2092c50a04 fix(agent): use DB provider_type for SOUL echo and strict mode detection
- Add providerTypeOf() to extract provider_type via type assertion
  (e.g. "chatgpt_oauth") instead of config name (user-set "openai")
- Exclude "compat" providers (openai_compat → OpenRouter/DeepSeek/Groq)
  from strict mode and SOUL echo — they proxy to non-OpenAI models
- Fix isOpenAIStrict matching openai_compat incorrectly
2026-03-31 23:38:48 +07:00
viettranx 2a9400949b fix(agent): SOUL echo in recency zone for OpenAI/Codex providers
GPT models have strong recency bias and lose persona in long prompts.
Extract Style/Vibe sections from SOUL.md and echo them at the end of
the system prompt (~200 chars each) so GPT sees personality traits right
before generating. Only applies to OpenAI/Codex — Claude respects early
system prompt instructions well and doesn't need this.
2026-03-31 23:32:21 +07:00
viettranx ff4d370d27 fix(mcp): require 3 consecutive ping failures before marking server disconnected
Single transient errors (e.g. 504 from upstream proxy) no longer
instantly disconnect MCP servers. Requires healthFailThreshold (3)
consecutive failures before setting connected=false and triggering
reconnect. Applies to both Manager.healthLoop and poolHealthLoop.
2026-03-31 23:15:59 +07:00
viettranx e48ba1df7f fix(mcp): prevent LLM hallucination of optional tool parameters
3-layer defense against GPT-5.4 filling all optional MCP tool params
with fabricated values (e.g. api_key:"optional", proxyUrl:"http://example.com"):

Layer 1 — bridge_tool.go: expand placeholder detection to catch "optional",
"skip", example URLs; type-aware empty string handling (keep for string-typed,
strip for non-string); add propertyType() helper.

Layer 2 — schema_strict.go: OpenAI strict mode transform — optional props
become nullable unions, all props required, additionalProperties:false.
Constrained decoding prevents invalid output. Only enabled for first-party
OpenAI/Codex providers.

Layer 3 — systemprompt_sections.go: concrete WRONG/RIGHT examples in MCP
optional param instruction.
2026-03-31 23:15:59 +07:00
viettranx f623ef9d55 feat(mcp): hybrid search mode — keep first 40 tools inline, defer rest
Instead of all-or-nothing when MCP tool count exceeds threshold,
keep first 40 tools registered inline and only defer the excess
to BM25 search via mcp_tool_search. System prompt now shows both
inline descriptions and search guidance in hybrid mode.

Also raises skill inline count from 40 to 60 (token limit is the
real bottleneck for skills).
2026-03-31 23:14:12 +07:00
viettranx aaa56ff004 fix(ui): summoning modal mode, dialog widths, and MCP refresh animation
- Add mode prop to SummoningModal (summon vs regenerate) so Edit with AI
  shows appropriate text instead of summoning language
- Fix memory document and KG entity detail dialogs using sm:max-w-* to
  properly override base sm:max-w-lg from DialogContent
- Expose isFetching from useMCP hook so refresh button animation works
2026-03-31 23:07:15 +07:00
viettranx ef0de0d760 fix(kg): merge recursive CTE branches and widen detail dialog
Combine forward+reverse traversal into single recursive branch using
CASE to fix SQLSTATE 42P19 (PostgreSQL parses triple UNION ALL as
left-associative, putting recursive ref in non-recursive term).

Also widen entity detail dialog from max-w-5xl to max-w-7xl.
2026-03-31 19:33:01 +07:00
viettranx 1d39626a88 fix(agent): use forward-scan replaceFirstMediaTag for correct multi-ref ordering
Replace replaceLastMediaTag with replaceFirstMediaTag across all 5 media
enrichment functions. Forward iteration + first-match produces natural
positional pairing, fixing reversed tag alignment when multiple media
refs exist in one message.

Also fixes same latent bug in enrichDocumentPaths, enrichAudioIDs, and
enrichVideoIDs. Supersedes #608.
2026-03-31 19:26:40 +07:00
viettranx 49f51da81c fix(kg): raise extraction temperature from 0.0 to 0.2
Zero temperature was too rigid, causing LLM to miss implied entities
and relations. 0.2 allows picking up contextual connections while
staying deterministic for structured JSON output.
2026-03-31 19:06:38 +07:00
viettranx c49952a1e5 feat(kg): expand entity types and improve extraction prompt
Add 3 new entity types: technology, product, document — reducing
concept catch-all bucket. Add 4 new relation types: authored,
references, provides, requires. Improve prompt with disambiguation
guide between similar types, stricter related_to usage, and varied
confidence examples. Update graph view colors and mass for new types.
2026-03-31 19:03:43 +07:00
viettranx 09a7823498 feat(providers): add tool schema normalization for MCP tools
Port TypeScript schema normalization pipeline to Go. MCP tools with
complex JSON Schemas ($ref, anyOf/oneOf, const, constraints) were
being rejected by OpenAI, Gemini, Codex, and xAI providers.

Pipeline per provider:
- Anthropic: resolve $ref → strip ref keys
- OpenAI/Codex/default: resolve $ref → flatten unions → inject type
- Gemini: resolve $ref → strip nulls → flatten → const→enum → strip 20+ keys
- xAI: resolve $ref → flatten → inject type → strip constraints

Key fixes:
- $ref resolution with circular detection (was: stripped → empty schema)
- anyOf/oneOf flattening into merged objects (was: raw → provider reject)
- type:"object" injection for OpenAI/Codex (was: 400 error)
- Codex now runs normalization (was: zero cleaning)
- Gemini strips 20+ constraint keywords (was: 5)
- xAI constraint keyword stripping (was: no profile)
- Builtin web_search/web_fetch minimum/maximum now stripped for Gemini/xAI
- DB provider type detection via schemaProviderName() for reliable Gemini matching
- Recursion depth guard (maxSchemaDepth=64) prevents DoS from malicious schemas
- Type inference from const values when explicit type is omitted
2026-03-31 17:56:24 +07:00
viettranx 37058918a0 feat(kg): enlarge entity detail dialog with table/graph tabs
Enlarge dialog max-w-3xl→5xl. Split relations into Table and Graph
tabs — graph tab renders traversal results as ReactFlow visualization.
Auto-traverse on open, bump depth 2→3 hops. Update i18n (en/vi/zh).
2026-03-31 17:55:35 +07:00
viettranx b99f119e37 feat(kg): bidirectional multi-hop traversal
Add reverse-edge UNION ALL to recursive CTE so traversal follows both
source→target and target→source edges. Reverse edges prefixed with ~
in via field (e.g. ~manages). Tool output shows directional arrows.
2026-03-31 17:55:27 +07:00
viettranx 8372298fba fix(providers): xAI generation models bypass chat verification
grok-imagine-video and grok-2-image were not in the isNonChatModel()
allowlist, causing verify to call Chat API which fails with malformed
error. Also fix friendlyVerifyError() fallback splitting inside JSON
values via LastIndex.
2026-03-31 14:35:57 +07:00
viettranx a565ad8402 fix: preserve FinishReason on truncated tool calls + ParseError propagation (#605)
Three layered bugs caused OpenAI-compatible providers to silently
produce empty tool call arguments when max_tokens was hit mid-JSON:

1. FinishReason override: all providers unconditionally overwrote
   "length" → "tool_calls" when tool calls existed, preventing the
   agent loop's truncation guard from firing.

2. Silent parse failure: JSON unmarshal errors were logged but args
   stayed as empty map with no signal to the caller.

3. No fallback for unreliable providers: some proxies don't emit
   finish_reason:"length" at all, leaving no detection path.

Fixes:
- Add ParseError field to ToolCall struct for explicit error signal
- Guard FinishReason override with `!= "length"` in OpenAI, Codex
- Set ParseError in all provider parsers (OpenAI, Anthropic, Codex)
- Add hasParseErrors() fallback guard in agent loop for EC-5 scenario
- Cap consecutive truncation retries (maxTruncationRetries=3) to
  prevent burning all iterations when max_tokens is persistently low

Closes #605
2026-03-31 14:33:33 +07:00
viettranx 4e9ce0e0e1 fix(contacts): consistent sender_id format and show contact_type in UI
- Use senderID (id|username) instead of userID in group no-mention path,
  preventing duplicate contacts for the same Telegram user
- Use full name (FirstName + LastName) in both contact insert paths
- Show contact_type (User/Group) instead of peer_kind (Direct/Group) in
  contacts table TYPE column and filter dropdown
- Add contact_type filter support in HTTP handler, PG and SQLite stores
2026-03-31 14:18:09 +07:00
viettranx 36b43ed7f1 feat(workspace): leader dual workspace with auto-copy to team on delegate
- Revert leader workspace override: leader keeps personal workspace as
  default, team workspace accessible via ToolTeamWorkspaceFromCtx
- Auto-copy: when leader creates team_tasks, scan subject+description
  for file paths → copy from personal to team workspace so members can
  access them
- Safety: Lstat (reject symlinks), 10MB size cap, .env excluded from
  allowed extensions, path traversal blocked
- Prompt hint: clarify members can only access team workspace files,
  referenced files are auto-copied
2026-03-31 12:10:55 +07:00
viettranx 8193d10fe9 docs: update architecture and changelog for subagent enhancement (#600) 2026-03-31 11:54:00 +07:00
viettranx 63878b16ca feat(telegram): /subagents commands + functional options refactor (#600)
- New /subagents and /subagent <id> commands for viewing subagent tasks
  from the persistent DB table
- Inline keyboard with sa: callback prefix for detail view
- Refactor telegram.New() to functional options pattern (WithAgentStore,
  WithTeamStore, WithSubagentTaskStore, WithPendingMessageStore)
- Wire SubagentTaskStore via WithSubagentTaskStore option
2026-03-31 11:45:25 +07:00
viettranx 2c1ef25392 feat(subagent): token tracking, edition limits, waitAll, auto-retry, producer-consumer announce (#600)
- Token cost tracking: accumulate input/output tokens per subagent,
  include in announce messages and persist to DB
- Per-edition rate limits: MaxSubagentConcurrent/Depth on Edition struct,
  tenant-scoped concurrency enforcement in Spawn/RunSync
- WaitAll action: spawn(action=wait, timeout=N) blocks until all
  children complete, returns merged summary
- Auto-retry: configurable MaxRetries (default 2) with linear backoff
  for transient LLM failures
- Producer-consumer announce queue: merges staggered subagent results
  into single LLM run (same pattern as team task announces)
- Raw metadata in bus messages to prevent double-formatting
- Fire-and-forget DB persistence with detached context + tenant scope
- Split oversized files for <200 line compliance
2026-03-31 11:45:16 +07:00
viettranx d8fc97ec63 feat(store): persist subagent tasks to PostgreSQL (#600)
- Migration 000034: subagent_tasks table with tenant scope, JSONB
  metadata + GIN index, partial index for archival candidates
- SubagentTaskStore interface with Create/Get/UpdateStatus/List/Archive
- PG implementation with parameterized queries and tenant isolation
- SQLite schema v3→4 migration + no-op stub for Lite edition
- Wire into store.Stores and factories
2026-03-31 11:45:03 +07:00
viettranx 7d35ee53c7 fix(agent): smart delegation prompt + compaction pending state + block team tools in subagents
- Leader prompt: replace blanket "prefer delegation" with conditional —
  delegate complex work, handle simple requests directly
- Compaction prompt: explicitly preserve pending subagent/team task state
  and "waiting for" expectations across summarization
- Add team_tasks to SubagentDenyAlways — subagents must not use team
  orchestration tools

Closes partially #600
2026-03-31 11:44:54 +07:00
viettranx 0f6cebc783 feat: structured compaction summary with identifier preservation
Compaction summaries were too generic ("provide a concise summary"),
causing loss of task progress, decisions, and identifier corruption
after summarization.

Port from OpenClaw TS (compaction.ts):
- Structured MUST PRESERVE sections: active tasks, progress, last
  request, decisions, TODOs, commitments
- Identifier preservation: preserve UUIDs, hashes, URLs, file names
  exactly as written (no shortening/reconstruction)
- Prioritize recent context over older history
- Shared prompt constant used by both mid-loop and background
  compaction paths
2026-03-31 09:32:38 +07:00
viettranx ab5bad11ff feat: cap tool output at source + improve context pruning pipeline
Problem: Agent sessions accumulated 71K+ input tokens (83% history)
because read_file and exec had no output limits. SOUL personality
drowned by massive context.

Changes:
- read_file: add offset/limit params + 50K char output cap with
  pagination hints (model can re-read with offset)
- exec/shell: cap output at 30K chars with smart head+tail truncation
  (preserves errors/summaries at tail)
- pruning: add per-result 30% context guard, tune softTrimRatio
  0.3→0.25 and softTrimMaxChars 4K→3K, add tail-aware soft trim
- mid-loop: allow pruning to re-trigger each iteration (was one-shot)

Design: cap at source, preserve full data in session, prune at
consumption time. read_file offset/limit enables recovery of
truncated content.
2026-03-31 08:35:06 +07:00
viettranx fe163eca30 fix(claude-cli): preserve CLAUDE_CODE_OAUTH_TOKEN in env filter
filterCLIEnv was stripping all CLAUDE* env vars including the OAuth
token needed for CLI subprocess authentication.

Closes #541
2026-03-31 08:19:58 +07:00
viettranx 1dee22aeb7 fix(telegram): add human-like writing instructions for group chats
Port missing group chat style guidance from OpenClaw TS:
- "Write like a human" — prevents robotic/formal GPT responses
- "Avoid Markdown tables" — GPT tends to spam tables in groups
- "Use real line breaks sparingly"

These instructions exist in OpenClaw TS (groups.ts buildGroupIntro)
but were missing in GoClaw's group prompt.
2026-03-31 07:27:57 +07:00
viettranx a47d7f9f4f fix(providers): use developer role for native OpenAI endpoints (GPT-4o+)
GPT-4o+ models prioritize "developer" messages over "system" for
instruction adherence. GoClaw was sending "system" for all providers,
causing GPT models to poorly follow SOUL/system prompts.

Map "system" → "developer" only for native OpenAI endpoints
(api.openai.com). Non-OpenAI backends keep "system" role unchanged.

Ported from OpenClaw TS: model-compat.ts → isOpenAINativeEndpoint()
2026-03-30 23:56:51 +07:00
viettranx 7fd31c34aa fix(telegram): retry 429 rate limit errors and preserve stream messages
429 (Too Many Requests) was not recognized as retryable, causing:
1. editMessage fails with 429 → no retry
2. Stream message deleted as fallback
3. Fresh sendMessage also 429 → message lost entirely

Changes:
- Add 429 to isRetryableNetworkErr (matching OpenClaw TS TELEGRAM_RETRY_RE)
- Honor Telegram retry_after parameter for backoff delay
- Don't delete stream message on retryable errors (keep valid content)
2026-03-30 23:04:26 +07:00
viettranx 33f975edf0 fix: use agent_key instead of UUID for session tool authorization (#573)
Session tools (sessions_list, session_status, sessions_history, sessions_send)
were using resolveAgentIDString(ctx) which returns the agent UUID, but session
keys are built using agent_key. This caused all session tool operations to fail
silently or return "access denied" for every channel.

Replace resolveAgentIDString() with ToolAgentKeyFromCtx() in all four session
tools. Add fail-closed guard for empty agent key.
2026-03-30 22:28:00 +07:00
viettranx 53932cf532 fix(channels): include display name in [From:] annotation for Telegram and WhatsApp
Telegram: senderLabel now includes full name alongside username, e.g.
[From: @nguyennlt (Nguyễn Trần)] instead of just [From: @nguyennlt].
Also uses FirstName + LastName when no username is set.

WhatsApp: add missing [From:] annotation — was the only channel not
annotating sender identity for the LLM.
2026-03-30 21:24:39 +07:00
viettranx 989e4f3123 fix(ui): KG graph review fixes — edge colors, fetch limit, double fitView
- Fix hardcoded dark edge color on deselect — use neutral #64748b
- Reduce graph fetch limit 200→50 to match component render cap
- Fix O(n*m) node lookup on theme change — use Map for O(1)
- Remove double fitView (prop + manual call)
2026-03-30 16:53:14 +07:00
viettranx 88f4fa264a fix(ui): KG graph perf + theme support + entity limit
- Limit graph to 50 entities for smooth DOM-based rendering
- Dual-theme node colors (light: solid bg/dark text, dark: translucent/light)
- Follow user theme via colorMode (no hardcoded dark)
- Theme change updates node colors without re-layout or fitView
- Remove MiniMap, backdrop-filter blur, translateZ GPU layers
- Thicker edges (strokeWidth 2, opacity 0.6) for visibility
- Simplified footer: entity/relation counts + limit note
- memo EntityNode to prevent re-render during pan/zoom
- i18n: add limitNote/limitHint keys (en/vi/zh)
2026-03-30 16:46:25 +07:00
viettranx 1cb7d3cb70 perf(ui): optimize KG graph view for large graphs
- Add KGEdgeData interface replacing `as any` casts for type safety
- Use entityMap (useMemo Map) for O(1) entity lookup instead of O(n) find
- Pre-compute EDGE_STYLE_DEFAULT/FADED constants to reduce GC pressure
- Skip edge object recreation when style unchanged during selection
- Wrap EntityNode in memo() with memoized container style
- Cap force simulation ticks at 200 for graphs with 100+ nodes
2026-03-30 15:54:34 +07:00
viettranx 3a6c63904c feat(cron): add stateless mode + promote payload fields to columns
- New `stateless` column: when true, cron runs skip session Reset/Save
  (fresh ephemeral session each run, saves tokens). Default true for new crons.
- Promote deliver, deliver_channel, deliver_to, wake_heartbeat from
  payload JSONB to dedicated columns for queryability and type safety.
- CronPayload now content-only: kind, message, command.
- PG migration 000033: backfills from JSONB, strips promoted keys.
- SQLite migration v2→v3: backfills with json_extract().
- Web UI: stateless toggle in advanced dialog, i18n (en/vi/zh).
- Desktop UI: types + i18n updated.
2026-03-30 15:24:41 +07:00
viettranx 014f74ec15 fix(agent): group session unresponsive during team task execution (#266)
Two fixes:

1. Remove assistant prefill from team task reminders. The injected
   [user]+[assistant]+[user] pattern caused LLMs to treat the canned
   ack as "turn complete", returning NO_REPLY for every user message
   in group sessions with active tasks. Reminders are now merged into
   the user message as prefix tags.

2. Add PeerKind propagation to team notification routing. TaskTicker
   and progress notifications were missing PeerKind on InboundMessage,
   causing them to route to phantom DM sessions instead of the correct
   group session. PeerKind is now carried through event payloads,
   notify queue metadata, and all inbound message publications.
2026-03-30 15:20:01 +07:00
viettranx 24717b0f51 refactor(cron): normalize payload columns into dedicated DB fields (#33)
Extract wake_heartbeat and stateless from JSON payload into first-class
columns on cron_jobs. Adds migration 000033 with backfill from existing
payload data. Updates PG + SQLite stores, RPC handlers, and UI i18n.
2026-03-30 15:19:43 +07:00
viettranx d10236241e fix(agent): add panic recovery to prevent zombie state after agent loop crash (#39)
Root cause: runLoop() had no recover() — panics killed the goroutine while
leaving the session queue's activeRuns entry orphaned. Subsequent messages
to the same session would hang indefinitely (zombie state).

Fix: Two-layer panic recovery:
1. loop.go: runLoop() now returns error on panic instead of crashing goroutine,
   allowing normal cleanup flow (session queue, traces, events)
2. queue.go: executeRun() defense-in-depth recovery ensures activeRuns cleanup
   and scheduleNext() even if agent-level recovery fails
2026-03-30 14:36:25 +07:00
viettranx 4ac611530a fix(store): provider CreateProvider uses UPSERT to handle orphaned duplicates (#295)
CreateProvider now uses ON CONFLICT (tenant_id, name) DO UPDATE instead of
plain INSERT. When a provider with the same name already exists (e.g. orphaned
after agent deletion), it updates the existing record instead of failing with
a unique constraint violation.

Applied to both PG and SQLite implementations.
2026-03-30 14:29:33 +07:00
viettranx bdfc0fadfb fix(config): MCP env: resolution, channel field filter, orphan provider event, workspace fallback (#348, #297, #295, #431)
- manager.go: add resolveEnvVars() to expand env:VARNAME in MCP headers (copy-safe)
- manager_env_test.go: 3 test cases for env resolution
- validate.go: add "name" to channelInstanceAllowedFields
- validate_test.go: test channel instance name field retention
- bus/types.go: add TopicAgentDeleted event + AgentDeletedPayload
- agents_delete.go: emit agent:deleted event with provider name for async cleanup
- gateway.go: log-only subscriber for orphaned provider warning (no auto-delete — FK safety)
- resolver.go: fallback to deps.Workspace for master tenant agents with empty ag.Workspace
  (fixes filesystem escape where filepath.Join("","system") resolved to /system)
2026-03-30 14:12:18 +07:00
viettranx 8eb4ce6d6f fix(store): session Save() UPSERT fallback, memory index-all user_id header (#379, #517)
- sessions_list.go (PG): check rowsAffected after UPDATE, INSERT with ON CONFLICT DO UPDATE
  when session not yet in DB (cron/heartbeat sessions)
- sessions_ops.go (SQLite): same UPSERT pattern with ? placeholders
- memory_handlers.go: fallback to X-GoClaw-User-Id header when body.user_id is empty
2026-03-30 14:12:08 +07:00
viettranx ec060ad7e1 fix(agent): unblock stuck agent on /stop, auto-complete nil-result tasks, graceful shutdown (#527, #504, #39)
- loop.go: replace blocking range with select+ctx.Done() for parallel tool collection;
  document finalization trade-off on early cancel
- loop_cancel_test.go: tests for context cancellation and normal completion paths
- gateway_consumer_post_turn.go: always auto-complete team task in default case,
  fallback message when outcome.Result is nil
- gateway.go: call sched.Stop() with 5s drain before context cancel on shutdown
2026-03-30 14:12:00 +07:00
viettranx e1c52cbda8 fix(providers): prevent crash on cancel, capture thinking signature, nil guard (#287, #188, #566, #335)
- anthropic_stream.go: check ctx.Err() in scanner loop, bounds check toolCallJSON index,
  accumulate signature_delta events into ThinkingSignature
- anthropic_request.go: include signature field in buildRawBlock for thinking blocks
- types.go: add ThinkingSignature field to ChatResponse
- loop_run.go: nil guard on result before FinishTrace access
- gateway.go: defensive ApplyDBSecrets before setupTTS in config reload subscriber
2026-03-30 14:11:52 +07:00
viettranx 011f5f1a3d fix(security): harden env file permissions and block NUL byte injection (#306, #44)
- prepare-env.sh: chmod 600 after .env creation to prevent world-readable secrets
- shell.go: reject commands containing NUL bytes before execution
- credentialed_exec.go: defense-in-depth NUL check in credentialed path
- shell_deny_test.go: add TestExecute_RejectsNULByte with 6 test cases
2026-03-30 14:11:42 +07:00
viettranx 533ca44e99 fix(config): add GOCLAW_ALLOWED_ORIGINS env var for CORS config (#543)
Add env var support for gateway.allowed_origins so container deployments
can set CORS origins reliably even if config file is overwritten by UI
save cycles. Follows same pattern as GOCLAW_OWNER_IDS.
2026-03-30 12:06:51 +07:00