Commit Graph
850 Commits
Author SHA1 Message Date
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
viettranx e27c2cc45e fix: add missing MigrateUserDataOnMerge to test stubs
Interface method added in agent-transfer branch was missing from
seedStubStore and createCaptureStore, breaking CI on main.
2026-03-30 11:49:48 +07:00
Thieu Nguyen 82e86bcce1 fix(cron): resolve default agent from DB and fix update payload (#549) (#562)
- Backend: resolve real default agent UUID from DB when cron job has no
  agent_id, instead of using literal "default" string that fails lookup
- UI: replace free-text agent input with dropdown in create form
- UI: wrap update params in `patch` object matching backend expectation
- UI: send empty string for agentId to explicitly clear agent selection
- UI: add name attribute to Select elements to fix form field warning
2026-03-30 11:45:54 +07:00
viettranx 216b8abac6 fix: Telegram @mention linking + conditional media read prompts
- Convert @mentions to <a href="https://t.me/username"> in HTML formatter
  (Telegram Bot API does NOT auto-link @username in parse_mode: HTML)
- Protect @mentions from italic regex mangling underscored usernames
- Change read_image/read_audio/read_video system prompt from "REQUIRED"
  to conditional — model only reads media when relevant to user's question
- Annotate history media tags so model distinguishes group context images
  from current-turn images
2026-03-30 11:45:33 +07:00
Thieu Nguyenandviettranx e7afa83245 fix: dep_scanner false positive — JS import inside Python f-string (#564)
* fix: skip JS ES module imports in Python string literals during dep scan

dep_scanner falsely matched JS `import mermaid from '...'` inside Python
f-strings as a pip dependency, causing functional skills to be archived.

Add jsFromStringRe check: if a line matching pyImportRe also contains
`from '...'`/`from "..."` (JS ES module pattern), skip it. Valid Python
`import X` never has `from '...'` on the same line.

Closes #544

* fix: add missing stub method + additional edge case tests

- Add MigrateUserDataOnMerge to stubAgentStore in context_file_interceptor_test.go
  (fixes CI build failure — stub was missing new AgentStore interface method)
- Add TestScanFile_MultipleJSImportsInsidePythonString for broader false positive coverage

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-03-30 08:37:30 +07:00
viettranx 7f897fe9f5 fix: propagate TenantID in team task dispatch InboundMessage
dispatchTaskToAgent() constructed InboundMessage without TenantID,
causing DB-level MCP servers to be invisible when dispatching team
tasks from the dashboard. Same root cause as #560.
2026-03-30 08:13:36 +07:00
daleandClaude Opus 4.6 7b066b7056 fix: propagate TenantID in channel InboundMessage for tenant-scoped MCP resolution (#561)
All channel handlers (Telegram, Discord, Slack, Feishu) bypassed
BaseChannel.HandleMessage() and manually constructed InboundMessage
without setting TenantID. This caused the consumer to fall back to
MasterTenantID, making DB-level MCP server grants invisible on
non-master tenants while config-file MCP servers worked fine.

Closes #560

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 08:12:00 +07:00
daleandClaude Opus 4.6 4ac9e01288 fix(secutiry): include tenant ID header in skill upload requests (#558)
The upload() method in http-client.ts was manually building headers
(only Authorization + User-Id), missing X-GoClaw-Tenant-Id. This caused
custom skills to always be created on the Master tenant. Replace with
authHeaders() which includes all required headers.

Closes #557

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 08:06:13 +07:00
Keithandviettranx 172684a5a7 Add prepare-compose.sh for composable docker-compose (#363)
* Add prepare-compose.sh for composible modules, options and overlays

* feat(prepare-compose): add --quiet and --skip-validation flags

Add loud() function for conditional output. Use --quiet to suppress
normal output, --skip-validation to bypass docker compose config check.

* chore: add mise config to gitignore

* chore: add GOCLAW_DIR env var and update context paths

- Add GOCLAW_DIR to prepare-compose.sh for .env generation
- Fix context paths in docker-compose files to use ${GOCLAW_DIR:-.}
- Restore symlinks in compose.d/ and compose.options/

* fix: broken symlink, safe .env update, export COMPOSE_FILE before validate

- Fix compose.d/00-goclaw.yml symlink: docker-compose.yml → ../docker-compose.yml
- Add compose.d/.gitkeep so directory is tracked
- Export COMPOSE_FILE before running docker compose config validation
- Replace rm+sed .env update pattern with safe temp file approach
- Quote GOCLAW_DIR value in .env to handle paths with spaces

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-03-30 08:02:57 +07:00
viettranx 23c227e18d fix(ui): combobox viewport flip, multi-user picker, merge data migration
- fix(ui): combobox dropdown flips above input near viewport bottom using
  bottom-anchored positioning so filtered items shrink upward correctly
- fix(ui): add onSelect callback to Combobox for commit-only events,
  preventing keystroke-adds-badge bug in multi-select mode
- feat(ui): MultiUserPicker shared component for allow/deny user fields
- fix(ui): migrate channel allow_from textarea, team allowed/denied users,
  and config allow_from input to MultiUserPicker with unified search
- fix(ui): remove dead getKnownUsers from use-teams hook
- feat: migrate per-user data on contact merge (context files, overrides,
  profiles, memory) with DO NOTHING conflict strategy — canonical tenant
  user data always wins over newly merged contact data
2026-03-29 23:51:53 +07:00
viettranx 63ed4e9351 fix: filter channel team task events from web UI chat
Admin users were seeing team task notifications from other channels
(Discord, Telegram, Lark) in their web UI chat due to bypassing the
UserID event filter. Added channel check to handleTeamTaskEvent matching
the existing pattern in handleAgentEvent.
2026-03-29 22:37:01 +07:00
viettranx 21b6c454ca feat: merge pipeline, per-user credentials, unified picker, group contacts
- Enable merge UI for linking channel contacts to tenant_users
- Contact → tenant_user resolution with cached lookup (60s TTL)
- MCP per-user credentials via user-keyed connection pool
- Secure CLI per-user credentials with AES-256-GCM encryption
- Unified UserPickerCombobox searching contacts + tenant_users
- Group contact collection with chat title in all channels
- Group permission inheritance via wildcard user_id="*"
- Fix heartbeat using wrong userID in group chats
- Filter internal senders from contact collection
- Add contact_type column (user/group) to channel_contacts
- SQLite schema v2 migration for desktop edition
2026-03-29 22:33:17 +07:00
viettranx 4cb6991fc6 fix: default noisy team notifications to off
Disable progress, failed, commented, and slow_tool notifications
by default — these are too verbose for most teams.
2026-03-29 19:41:35 +07:00
Viet Tran db7a34e4f3 feat: standalone Import & Export page with Teams, Agents, Skills & MCP
Refactors Agent Transfer into standalone /import-export page with 3 scope tabs.

Teams: auto-bundle member agents (config, context files, memory, KG, cron, profiles, workspace). 
Agents: section picker with presets, merge mode support.
Skills & MCP: custom skills + MCP server configs (stripped secrets), conflict detection.

Security: scopeClauseAlias for JOINs, tenant scope on all queries, zip bomb protection, adminMiddleware on import routes.
DRY: shared storeExportToken + readTarGzEntries helpers.
i18n: complete en/vi/zh coverage.
2026-03-29 18:01:08 +07:00
Thieu Nguyen 7b42ab04ab fix(desktop): use 127.0.0.1 instead of localhost and suppress lite startup logs (#546)
- GetGatewayURL/IsGatewayReady: use 127.0.0.1 to avoid WebKit resolving
  localhost to ::1 (IPv6) when gateway only binds IPv4, causing "Load failed"
- Skip CORS warning for lite edition (localhost-only, no production exposure)
- Skip server IP detection for lite edition (no multi-tenant risk, avoids
  leaking user's public IP in logs and unnecessary network calls)
2026-03-29 16:03:09 +07:00
viettranx b59a623649 fix(deps): upgrade major packages across web and desktop UIs
Web UI:
- i18next 25→26, react-i18next 16→17
- lucide-react 0.468→1.7 (no brand icons used)
- tailwind-merge 2→3 (Tailwind CSS 4 compat)
- typescript 5.7→6.0 (remove deprecated baseUrl from tsconfig)

Desktop UI:
- i18next 25→26, react-i18next 16→17
- immer 10→11 (internal perf improvements)
- react-markdown 9→10, react-syntax-highlighter 15→16
- typescript 5.9→6.0
- Fix MarkdownRenderer: wrap ReactMarkdown in div for className (v10 removed prop)

Both: build verified, pnpm audit 0 vulnerabilities
2026-03-29 13:03:39 +07:00
viettranx d803539f1c fix(ui): correct context pruning default toggle state
Backend enables pruning by default (only skips when mode="off"), but
UI showed toggle OFF when no config was saved (null). Also fixed global
config dropdown defaulting to "off" instead of "auto".
2026-03-29 13:00:19 +07:00
viettranx c5cd1ad412 fix(deps): upgrade Vite 6→8, plugin-react 4→6, and patch/minor updates
Desktop + Web UI:
- @vitejs/plugin-react ^4.3.4 → ^6.0.1 (Vite 8 compat)
- vite ^6.0.0 → ^8.0.3 (CVE: Rollup path traversal, Picomatch ReDoS)
- tailwindcss/vite 4.2.0 → 4.2.2, tailwindcss 4.2.0 → 4.2.2

Web UI patch/minor:
- @tanstack/react-query 5.90→5.95, react-router 7.13.0→7.13.2
- recharts 3.8.0→3.8.1, zustand 5.0.11→5.0.12, ws 8.19→8.20
- i18next 25.8→25.10, react-i18next 16.5→16.6, framer-motion 12.34→12.38

Both: pnpm audit reports 0 vulnerabilities
2026-03-29 12:59:04 +07:00
viettranx db41e58319 fix(security): SSRF validation, path hardening, and dependency CVE fixes (#525)
Provider SSRF prevention:
- Add validateProviderURL() blocking internal/private network targets
- Whitelist http/https schemes only (blocks file://, gopher://, etc.)
- Normalize IPv6-mapped IPv4 (::ffff:127.0.0.1) before private IP check
- Validate both api_base and base_url fields on create and update

Path hardening:
- Symlink resolution in DooD workspace resolver (resolve-then-match)
- Strict regex for media ID validation (replaces ad-hoc string checks)

Dependency CVE fixes:
- Upgrade Vite 6→8 (Rollup path traversal, Picomatch ReDoS)
2026-03-29 12:54:12 +07:00
Kai (Tam Nhu) Tran a524b457ba feat(providers): add provider-level Codex pool activity monitor (#539)
Add provider-scoped runtime monitor for Codex pool owners:

- New `GET /v1/providers/{id}/codex-pool-activity` endpoint aggregates pool health across all agents
- New Pool Activity section on provider detail page (pool owners only)
- Shows aggregate member health, recent requests, top agents with drill-down links
- 7-day time window on provider-scoped span query for performance
- Reuses `buildCodexPoolActivity()` — zero duplicated aggregation logic
- i18n complete (en/vi/zh), accessible markup

Closes #499
2026-03-29 12:46:23 +07:00
Kai (Tam Nhu) Tran 789c9613be fix(cron): harden toggle and scheduler state (#540)
- recompute next_run_at transactionally on toggle/update instead of non-atomic read/compute/write
- keep repeated EnableJob(true) idempotent by preserving existing next_run_at
- reject re-enabling expired one-shot at jobs with ErrCronJobNoFutureRun
- claim due jobs conditionally, reload before execution, conditional writeback
- align PG and SQLite behavior with shared helpers (DRY)
- atomic scheduler writeback via single UPDATE with CASE expression

Closes #378
2026-03-29 12:43:19 +07:00
viettranx 2e869f4ece feat(kg): add tsvector FTS, entity deduplication, and embedding fixes
- Replace ILIKE search with PostgreSQL tsvector/GIN full-text search
  for KG entities (migration 000031)
- Add entity deduplication system with dual-threshold strategy:
  auto-merge at 0.98+ similarity with Jaro-Winkler name match,
  flag 0.90-0.98 as candidates for manual review
- Add ScanDuplicates for bulk on-demand duplicate detection
- Add MergeEntities with advisory lock and tenant-scoped relation
  re-pointing (delete-then-update to avoid ON CONFLICT on UPDATE)
- Wire dedup inline after KG extraction pipeline
- Fix BackfillKGEmbeddings: was failing silently due to
  context.Background() missing tenant_id; now runs cross-tenant
- Fix BackfillKGEmbeddings: break on error → continue with failed
  ID tracking and max consecutive error cap
- Add EmbedEntity helper; UpsertEntity now generates embeddings
  in background goroutine
- Add HTTP endpoints: POST /kg/dedup/scan, GET /kg/dedup,
  POST /kg/merge, POST /kg/dedup/dismiss
- Add web UI: Dedup dialog with Scan All button, side-by-side
  entity comparison, merge/dismiss actions
- Add Jaro-Winkler similarity algorithm with 34 unit tests
- Update IngestExtraction to return upserted entity IDs
- Bump RequiredSchemaVersion to 31
2026-03-29 12:15:21 +07:00
Luan VuandLuvu182 97f4942943 fix(channels): add display_name to session metadata for Feishu, Zalo, Slack (#536)
Sessions page shows raw IDs instead of sender names because these channels
didn't include display_name in their message metadata. The gateway's
extractSessionMetadata() already looks for this key but it was missing
from Feishu, Zalo Personal, and Slack message payloads.

Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
2026-03-29 11:25:05 +07:00
viettranx a506b297cf fix(i18n): update maxHistoryShare tooltip from 0.75 to 0.85 default 2026-03-29 10:18:27 +07:00
viettranx 3695353de7 refactor(providers): extract Novita defaults into constants and add to desktop UI
DRY: replace hardcoded Novita API base/model across 3 files with
store.NovitaDefaultAPIBase and store.NovitaDefaultModel constants.
Add Novita to desktop frontend provider list.
2026-03-29 10:16:28 +07:00
Alex bd1411028b feat(providers): add Novita AI as new LLM provider (#444)
Add Novita AI as a new LLM provider with OpenAI-compatible API endpoint.

- Add ProviderNovita constant and valid type entry
- Add Novita to ProvidersConfig with APIBaseForType and HasAnyProvider
- Register Novita provider in gateway startup (config and DB paths)
- Add Novita case in HTTP handler's registerInMemory
- Add Novita to UI provider selection dropdown

Default model: moonshotai/kimi-k2.5
Default endpoint: https://api.novita.ai/openai
2026-03-29 10:15:14 +07:00
viettranx 0c6cd02285 refactor(compaction): remove minMessages guard, use token-only threshold
Message count-based compaction trigger (default 200) was a legacy safety
net from when token estimation was unreliable. With calibrated overhead
estimation using actual LLM PromptTokens, the token threshold alone is
sufficient — matching industry standard (Claude Code, Anthropic API,
LangChain all use token-only).

Also fixes EstimateTokens vs EstimateHistoryTokens inconsistency in
end-of-run overhead calculation (loop_history.go), and updates stale
CLAUDE.md docs from 75% to 85%.
2026-03-29 10:14:52 +07:00
viettranx db03bf92e0 feat(web): add KG extraction settings to system settings modal and tighten code block spacing
- Knowledge Graph section: provider/model picker + min confidence threshold
- Saves via builtin tools API (knowledge_graph_search settings)
- Reduce code block vertical margin and padding in markdown renderer
2026-03-29 09:24:06 +07:00
viettranx 028ea8a7d8 fix(security): elevate management HTTP routes from Operator to Admin role
Management endpoints (MCP, Agents, Skills, Channels, Builtin Tools)
previously used auto-detect auth which allowed Operator-level users
to perform CRUD operations. Now consistent with WS policy: Admin+
required for writes, Viewer+ for reads.

Also moves WS skills.update to isAdminMethod and improves 403 error
messages to include required role info.
2026-03-29 09:02:48 +07:00
viettranx 8b75e095e9 feat(cancel): add run.cancelled event and fix stop across web UI, desktop, and channels
- Add distinct `run.cancelled` agent event to differentiate user-initiated
  cancellation from real errors (previously both emitted `run.failed`)
- Fix desktop stop button: wire onStop callback to InputBar, add abort()
  function in useChat hook, restore isRunning state on session switch
- Fix web UI: handle run.cancelled without showing error message, preserve
  partial streamed content on cancel
- Fix hanging chat.send promise: send {cancelled: true} OK response instead
  of silently dropping (was hanging 600s until timeout)
- Fix channel stream cleanup: handle run.cancelled for stream stop + run
  context deletion (prevents memory leak) + reaction status clear
- Fix child trace status: cancelled subagent runs now correctly get
  TraceStatusCancelled instead of TraceStatusError
- Add run.cancelled to all event consumers: query invalidation, session
  detail refresh, trace dialog refresh, gateway activity clearing
2026-03-29 09:00:06 +07:00
viettranx 635870acd2 fix(agent): prevent premature compaction by separating overhead from history tokens
Root cause: compaction threshold compared total prompt tokens (including
system prompt, tool definitions, context files) against budget. Sessions
with only 50-60k of conversation could trigger compaction when overhead
pushed total to 120k+.

Changes:
- Enable context pruning by default for all providers (was Anthropic-only)
- Add EstimateHistoryTokens() that excludes system messages from count
- Fix EstimateTokens() to include ToolCall argument sizes
- Calibrate overhead on first LLM response, subtract from threshold checks
- Mid-loop: prune old tool results first (at 70% budget), compact only if
  still over budget after pruning
- maybeSummarize: subtract overhead from calibration base
- Remove applyContextPruningDefaults() (Anthropic-only auto-enable)
- Simplify context pruning UI: show only Keep Last Assistants by default,
  hide advanced params behind collapsible, remove Mode selector
2026-03-29 07:50:00 +07:00
viettranx f8278cfaff fix(mcp): strip placeholder values from optional tool args and guide LLMs on optional params
LLMs often send empty strings, "null", "SHOULD_NOT_BE_HERE" etc. for optional
MCP tool parameters instead of omitting them, causing servers to reject invalid
values (e.g. UUID validation fails on "").

- Add stripEmptyOptionalArgs() to clean optional args before MCP calls
- Detect known placeholders (null/none/nil) and all-caps patterns
- Add system prompt guidance for optional parameter handling
- Empty string "" is intentionally NOT stripped (may be valid for text fields)
2026-03-29 07:48:19 +07:00
viettranx 243656a7b6 feat(mcp): add reconnect button to evict stale MCP connections
When MCP servers update their tools externally, the gateway keeps
stale cached connections. Add POST /v1/mcp/servers/{id}/reconnect
endpoint that evicts the pooled connection, forcing re-discovery
on next agent use. Exposed via reconnect button on both web and
desktop MCP management pages.
2026-03-29 07:15:03 +07:00
viettranx 985d2fa50d fix(heartbeat): log provider override and include in trace tags 2026-03-28 22:55:11 +07:00
viettranx 5c8e0b7af2 fix(heartbeat): apply provider override from heartbeat config
Heartbeat's ProviderID was stored in DB but never used during execution —
the agent's default provider always won. Add ProviderOverride to RunRequest,
resolve hb.ProviderID in the ticker, and use the override in the agent loop.
2026-03-28 22:48:27 +07:00
viettranx 175e05287a fix(cache): heartbeat/cron used UUID in session keys causing stale cache after agent update
Session keys must use agentKey (human-readable, e.g. "default") not UUID.
Cache invalidation via InvalidateAgent(agentKey) only matches agentKey suffix,
so UUID-keyed entries were never cleared — causing stale provider/model after
agent updates from the web UI.

- heartbeat: use agentKey (already resolved) instead of UUID for session key
  and HasActiveSessionsForAgent check
- cron: resolve agentKey from UUID via agentStore before building session key
- agents_update: also invalidate by UUID as safety net for legacy entries
- startup migration: auto-fix existing UUID-based session keys in DB
- tests: 7 new tests covering session key format and cache invalidation
2026-03-28 19:02:53 +07:00
viettranx 3d84e72934 fix(build): filter git describe to exclude desktop lite-v* tags
Add --match "v[0-9]*" so VERSION only picks up server release tags,
preventing lite-v* desktop tags from being used as the build version.
2026-03-28 18:58:39 +07:00
viettranx c70e50c96a fix(tools): harden session tools — phantom sessions, JSON injection, fail-closed checks
- session_status: GetOrCreate → Get to prevent phantom session creation
- sessions_history: replace string concat with json.Marshal (2 paths) to prevent JSON injection
- sessions_send: use s.Label from List() directly (eliminates N+1 Get calls), add self-send protection, use json.Marshal for response
- All tools: fail-closed agentID check (error when empty instead of skipping validation)
- Add 22 unit tests covering all session tools
2026-03-28 18:25:03 +07:00
viettranx 90b7396d74 fix(bus,cron): panic recovery + deadlock fix + unit tests for 9 packages
Bug fixes:
- bus: add panic recovery in Broadcast() — panicking subscriber no longer
  crashes entire event bus goroutine
- cron: fix deadlock in RunJob() — recordRun called while holding mutex,
  extracted recordRunLocked for callers already holding lock
- cron: add run log recording to executeJobByID — automatic scheduler
  was not populating run log, only manual RunJob did

New tests (~170 cases across 9 packages):
- crypto: roundtrip, key derivation (hex/b64/raw), nonce uniqueness,
  backward compat, wrong key, corruption
- permissions: role hierarchy, RoleFromScopes, CanAccess, scope precedence
- bus: pub/sub delivery, buffer full, panic recovery, concurrent safety
- providers/retry: IsRetryableError, backoff, jitter, Retry-After, context
  cancellation, hook callback
- sessions: idempotency, concurrent writes, defensive copy, save/load
  roundtrip, metadata accumulation
- scheduler: draining, drop policies, adaptive throttle, stale completion,
  lane concurrency, debounce, interrupt mode
- config: JSON5 parsing, env overrides, FlexibleStringSlice, owner IDs
- cron: schedule validation, computeNextRun, CRUD, job execution, failure
  tracking, persistence roundtrip
- agent: history sanitization edge cases — all-tool history, partial results,
  cross-turn dedup, orphaned tools, 1000-msg performance
2026-03-28 18:15:46 +07:00
viettranx a7800604b6 fix(web): streaming broken on first chat send due to expectingRunRef race
When sending the first message (no session key), handleSend calls
navigate() then send(). The navigation triggers a session-change effect
that resets expectingRunRef=false before run.started arrives, causing
all streaming events to be silently dropped.

Only reset expectingRunRef when switching between existing sessions,
not on the "" → new-key transition that is part of the send flow.
2026-03-28 18:14:08 +07:00
viettranx 2d344e4f35 fix(tools): block message tool self-send to prevent duplicate media delivery
When a team member creates an image and completes a task, the announce
flow already carries media to the leader agent, which dispatches it to
the channel via ForwardMedia. If the leader also calls the message tool
to send the same image to its own chat, the image appears twice.

Block message tool calls where channel+target match the agent's own
context, returning an error that guides the LLM to include content in
its normal response instead.
2026-03-28 17:35:51 +07:00