mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 02:16:56 +00:00
dev
380
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8f56ddaa64 |
feat(v3): core architecture redesign — pipeline, memory, vault, evolution, providers, orchestration (#790)
* feat(v3): add core interface contracts and migration for v3 redesign
Foundation interfaces: TokenCounter, WorkspaceContext, DomainEventBus,
ProviderAdapter/Capabilities. Pipeline: Stage, RunState, MessageBuffer,
substates, Pipeline orchestrator. Memory: EpisodicStore, AutoInjector,
KG temporal extensions, consolidation workers. System integration:
PromptConfig, ToolCapability, Retriever. Orchestration: OrchestrationMode,
EvolutionMetrics/SuggestionStore. Migration 000037: episodic_summaries,
evolution tables, KG temporal columns. Schema version 36→37.
* refactor(plans): mark all v3 design phases complete with file references
* fix(v3): address code review findings on design contracts
- C1: add missing l0_abstract column to episodic_summaries migration
- C2: align EpisodicSummary ID/TenantID/AgentID to uuid.UUID
- H1: document tenant_id scoping requirement on EpisodicStore
- H2: add UNIQUE constraint on (agent_id, user_id, source_id) for dedup
- H4: clarify ProviderAdapter vs Provider relationship in doc
- M3: set state.ExitCode on BreakLoop/AbortRun in pipeline
- M6: store full PipelineConfig in Pipeline struct
- Edge: add WHERE embedding IS NOT NULL on HNSW index
* fix(v3): second-pass review fixes
- H1: use context.WithoutCancel for finalize + set ExitCode on ctx cancel
- H2: use utf8.RuneCountInString consistently in FallbackCounter
- H3: longest-prefix-match in ModelContextWindow (prevents wrong tokenizer)
- H4: return unsubscribe cleanup func from consolidation.Register
* feat(v3): implement DomainEventBus with worker pool, dedup, and retry
Worker pool processes events from buffered channel. SourceID-based dedup
prevents duplicate processing. Exponential backoff retry on handler error.
Panic recovery per handler. Graceful shutdown via Drain(). 8/8 tests pass
with race detector.
* feat(v3): implement ProviderAdapter for Anthropic, OpenAI, DashScope, Codex
Add CapabilitiesAware to all 6 providers. Create ProviderAdapter
implementations that delegate to existing buildRequestBody/parseResponse
for DRY. ClaudeCLI and ACP get capabilities only (subprocess transport).
DashScope wraps OpenAI adapter with StreamWithTools=false override.
* feat(v3): implement WorkspaceContext Resolver for 6 scenarios
Stateless resolver produces immutable WorkspaceContext at run start.
Handles personal/group/predefined/team-shared/team-isolated/delegation.
Wired into loop_context.go behind v3PipelineEnabled flag (additive,
v2 path unchanged). Includes delegation path boundary check,
master tenant bypass, and tenant slug path composition.
* feat(v3): implement tiktoken TokenCounter with BPE encoding + cache
Adds tiktoken-go for accurate cl100k_base/o200k_base token counting.
Per-message FNV-1a hash cache avoids re-encoding unchanged history.
Falls back to rune/3 heuristic for unknown models. NewTokenCounter
factory selects implementation at build time.
* feat(v3): promote 12 other_config JSONB fields to dedicated agent columns
Extract emoji, agent_description, thinking_level, max_tokens,
self_evolve, skill_evolve, skill_nudge_interval, reasoning_config,
workspace_sharing, chatgpt_oauth_routing, shell_deny_groups, and
kg_dedup_config from the catch-all other_config JSONB into proper
columns with DB-level types and defaults.
- Migration: PG (000037) + SQLite (schema v6→7) with backfill
- Go: AgentData struct + simplified Parse* methods
- Store: SELECT/INSERT/scan updated for both PG and SQLite
- Gateway: create/update handlers accept promoted fields
- HTTP: export/import with legacy backward compat
- Web UI: all 15 frontend files read/write from top level
* feat(v3): implement Knowledge Vault with unified search, wikilinks, and FS sync
Migration 000038 adds vault_documents (FTS+pgvector), vault_links, vault_versions
tables. VaultStore interface with PG implementation for document CRUD, hybrid
FTS+vector search, and bidirectional link management. All queries enforce
tenant_id isolation including JOIN-based scoping on link operations.
FS sync layer: SHA-256 content hashing, VaultInterceptor hooks into write_file/
read_file for auto-registration and lazy sync, fsnotify watcher with 500ms
debounce. Wikilink engine parses [[target]] syntax, resolves targets via
3-step strategy, and maintains vault_links on write.
VaultSearchService fans out queries across vault, episodic, and KG stores in
parallel with per-source score normalization and weighted merge. AutoInjector
and Retriever implementations for pipeline integration.
Three agent tools: vault_search (unified discovery), vault_link (explicit
linking), vault_backlinks (dependency tracing). Feature-flagged via
v3_vault_enabled agent setting.
* feat(v3): wire vault into gateway startup + add unit tests
Wire VaultStore embedding provider, VaultSearchService, VaultInterceptor
on read/write tools, and register vault_search/vault_link/vault_backlinks
tools in gateway_vault_wiring.go. All wiring gated by stores.Vault != nil.
Add 28 unit tests for ContentHash, ContentHashFile, and ExtractWikilinks
covering edge cases, unicode, display text, context windows, and offsets.
* feat(v3): implement stage-based pipeline loop with 8 pluggable stages
Decompose monolithic agent loop into internal/pipeline/ package:
- 6 stages: Context, Think, Prune+MemoryFlush, Tool, Observe+Checkpoint, Finalize
- Foundation types: Stage interface, RunState with 7 typed substates, MessageBuffer
- Pipeline orchestrator with setup/iteration/finalize 3-phase execution
- Callback-based PipelineDeps avoids circular import with agent package
- Feature-flagged via v3PipelineEnabled in Loop.Run()
- All 7 exit conditions preserved (no tools, max iter, truncation, loop kill,
read-only streak, tool budget, ctx cancel)
* feat(v3): wire pipeline callbacks to Loop methods + add 71 unit tests
Wire 15 of 17 PipelineDeps callbacks from Loop methods via closures:
- Context: LoadContextFiles, BuildMessages, EnrichMedia, InjectReminders
- Think: BuildFilteredTools, CallLLM (stream/sync)
- Prune: PruneMessages, CompactMessages
- Memory: RunMemoryFlush
- Finalize: SanitizeContent, FlushMessages, UpdateMetadata, BootstrapCleanup, MaybeSummarize
- Remaining: ExecuteToolCall, CheckReadOnly (deep loop.go integration)
Add comprehensive test suite (71 tests, all passing with -race):
- MessageBuffer: 10 tests (append, flush, replace, counts)
- Pipeline.Run: 14 tests (3-phase flow, exit conditions, ctx cancel)
- Stage tests: 47 tests (ThinkStage nudges/truncation, PruneStage budget,
ToolStage parallel/exit, ObserveStage content, CheckpointStage interval,
FinalizeStage cleanup)
* feat(v3): wire remaining 2 callbacks (ExecuteToolCall, CheckReadOnly)
Complete callback wiring — 17/17 PipelineDeps callbacks now active:
- ExecuteToolCall: resolves tool name, executes via registry, processes
result via existing processToolResult with loop detection bridge
- CheckReadOnly: delegates to checkReadOnlyStreak via bridge runState
- Bridge runState shares loop detection state between pipeline and agent
* fix(v3): eliminate data race in tool execution + capture injected messages
- Remove parallel tool execution path — serialize all tool calls to avoid
data races on shared bridgeRS (loop detector, media results, deliverables)
- Loop kill checked after each tool (mid-batch early exit)
- BuildFilteredTools: capture and append injected tool-awareness messages
- Rename test to reflect sequential execution
* feat(v3): wire ResolveWorkspace, safe parallel tools, ContextStage tests
- Wire ResolveWorkspace callback via workspace.NewResolver() with
ResolveParams from Loop fields (no longer a nil stub)
- Re-add safe parallel tool execution: split into ExecuteToolRaw
(parallel I/O) + ProcessToolResult (sequential state mutation)
with opaque rawData pass-through (no double execution)
- Add 12 unit tests for ContextStage (8) + MemoryFlushStage (3)
- Split tool callbacks to loop_pipeline_tool_callbacks.go (under 200 lines)
- Capture buildFilteredTools injected messages
* feat(v3): add episodic memory store + temporal KG columns
Phase 1 — Episodic Store:
- Migration 000039: episodic_summaries table with pgvector, FTS, L0 abstracts
- EpisodicStore PG impl: CRUD, hybrid FTS+vector search, ExistsBySourceID,
PruneExpired. Idempotent via source_id UNIQUE constraint.
Phase 2 — Temporal KG:
- Migration 000040: valid_from/valid_until on kg_entities + kg_relations,
partial indexes for current-facts queries, epoch→timestamptz backfill
- ListEntitiesTemporal: current-only, point-in-time, or include-expired modes
- SupersedeEntity: atomic expire-old + insert-new in single transaction
Schema version bumped to 40.
* fix(v3): review fixes for episodic store + temporal KG
- C1: Fix column name mismatch turn_count vs message_count in Go SQL
- C2: Remove redundant migration 000040 (000037 already adds temporal KG columns)
- H1: Use time.Time not int64 for TIMESTAMPTZ columns in SupersedeEntity
- H2: Add tenant_id scoping to Get/Delete for tenant isolation
- M2: Fix scanEntityTemporal to convert TIMESTAMPTZ→UnixMilli correctly
- L1: Remove unused uuid import from episodic_search.go
- Schema version corrected to 39 (only 000039 is new)
* feat(v3): implement consolidation pipeline with 3 event-driven workers
Event chain: session.completed → EpisodicWorker → episodic.created →
SemanticWorker → entity.upserted → DedupWorker
- EpisodicWorker: reuses compaction summary or calls LLM, generates L0
abstract (extractive), idempotent via source_id check
- SemanticWorker: extracts KG facts from episodic summary via existing
Extractor, sets temporal valid_from, publishes entity.upserted
- DedupWorker: runs DedupAfterExtraction on new entity IDs (terminal)
- L0 abstract: sentence-based extraction (~50 tokens), no LLM needed
- All workers registered via DomainEventBus.Subscribe()
* feat(v3): implement progressive loading with L0 auto-inject + unified search
- AutoInjector: searches episodic store, builds L0 prompt section (~200 tokens),
skips trivial messages via stopword filter
- L1Cache: in-memory LRU (500 entries, 1h TTL) for structured overviews
- UnifiedSearch: cross-tier search merging episodic + document results by score
- ContextStage integration: AutoInject callback appends memory section to system prompt
- MemorySection field added to ContextState for observability
* feat(v3): add memory_expand tool for L2 episodic retrieval
New tool: memory_expand(id) returns full episodic summary with metadata.
Complements memory_search L0/L1 results with deep L2 access.
Nil-safe: returns error message when episodic store not available.
Gateway wiring + memory_search depth param + kg_search temporal param
deferred to runtime integration phase.
* feat(v3): complete Phase 5 — tool extensions + gateway wiring
- memory_search: add depth param + episodic tier search merged with docs
- kg_search: add as_of temporal param, use ListEntitiesTemporal
- memory_expand: registered in gateway startup
- Gateway: Episodic field in Stores, PGEpisodicStore in factory,
embedding provider wired, tools connected to episodic store
* fix(v3): Phase 3 review fixes — tenant isolation + AutoInject args
- C1: Add tenant_id filter to ftsSearch, vectorSearch, List queries
(prevents cross-tenant episodic memory leaks)
- C2: Fix AutoInject callback signature — agent/tenant captured by
closure, only userMessage + userID passed explicitly
- H1: Add tenant_id to List query
* feat(v3): wire per-agent v3 flags from DB into dual-mode gate
Parse v3_pipeline_enabled, v3_memory_enabled, v3_retrieval_enabled from
agent other_config JSONB via ParseV3Flags(). Resolver now sets all flags
on LoopConfig so the existing gate in loop_run.go reads from DB.
- V3Flags struct + ParseV3Flags() + ValidateV3Flags() in store layer
- v3MemoryEnabled/v3RetrievalEnabled added to Loop, LoopConfig, PipelineConfig
- Auto-inject gated on V3RetrievalEnabled (was unconditional)
- Structured perf logging for v3 pipeline runs
- v3 flag validation on both WS agent.update and HTTP PUT endpoints
* feat(v3): wire AutoInjector into pipeline for L0 memory auto-inject
Create AutoInjector at gateway startup from episodic store, pass through
ResolverDeps → LoopConfig → Loop. Pipeline adapter builds AutoInject
callback capturing agent/tenant context via closure.
ContextStage already gates on V3RetrievalEnabled + AutoInject != nil.
* feat(v3): add tool metadata map + capability-based deny rules
Registry gains per-tool ToolMetadata map with RegisterWithMetadata()
and GetMetadata() (infers defaults from tool name when not explicit).
PolicyEngine gains DenyCapability() for RBAC integration — tools with
denied capabilities filtered at step 8 after existing 7-step pipeline.
* fix(v3): add RWMutex to PolicyEngine capability deny fields
DenyCapability() and SetRegistry() now guarded by sync.RWMutex.
FilterTools reads snapshot under RLock. Prevents data race when
capability rules are modified concurrently with tool filtering.
* feat(v3): implement delegate tool for inter-agent task delegation
New `delegate` tool wraps existing agent_links infrastructure
(CanDelegate, DelegateTargets). Supports async (fire-and-forget)
and sync (block with timeout) modes. Permission checked via
AgentLinkStore. Events emitted: delegate.sent/completed/failed.
DelegateRunFunc injected by gateway to avoid circular dependency.
* feat(v3): complete 3 deferred implementations
1. OrchestrationMode resolution: ResolveOrchestrationMode() checks
team membership → delegate links → spawn (priority order).
2. PG EvolutionMetricsStore: RecordMetric, QueryMetrics, aggregate
tool/retrieval metrics, TTL cleanup. All queries tenant-scoped.
3. BridgePromptBuilder: implements PromptBuilder interface by
delegating to existing BuildSystemPrompt(). Appends v3 memory
L0 section when enabled. Ready for template engine swap later.
* fix(v3): address code review findings on commits 5-6
- C1: CanDelegate now tenant-scoped (fail-closed on missing tenant)
- H1: Sync delegate timeout capped at 600s
- H2: Async goroutine gets 10min deadline (prevents leaks)
- H3: JSONB casts use COALESCE/NULLIF guards (handles missing fields)
- M1/M2: Remove dead code (formatVaultSection, memoryL0ToStrings)
* fix(teams): stop auto-creating agent_links for team members
Teams use agent_team_members table directly — agent_links caused
context confusion between team dispatch and delegation systems.
- Remove autoCreateTeamLinks() calls from team create + member add
- Remove link cleanup from member remove
- Remove dead autoCreateTeamLinks() function
- Append DELETE to migration 000039: clear team-created agent_links
* fix(v3): tenant isolation for all agent_links queries + PromptBuilder Instructions
- DelegateTargets, GetLinkBetween, SearchDelegateTargets,
SearchDelegateTargetsByEmbedding, DeleteTeamLinksForAgent all now
scoped by tenant_id (fail-closed on missing tenant)
- BridgePromptBuilder now maps Instructions/InstructionContent to
AGENTS.md context file (was silently dropped)
* feat(v3): wire orchestration mode + evolution metrics into agent loop
- Orchestration mode: resolver resolves mode from team/links, tool filter
hides delegate/team_tasks based on mode, prompt builder injects delegation
targets section
- Evolution metrics: non-blocking goroutine records tool execution metrics
(name, success, duration) via EvolutionMetricsStore in both v2 loop and
v3 pipeline paths (sequential + parallel)
- Fix review findings: tenant ID propagated via store.WithTenantID in
background goroutine, 5s timeout prevents goroutine leak
* feat(v3): implement suggestion engine with pluggable analysis rules
- PG EvolutionSuggestionStore: CRUD for agent_evolution_suggestions table
- SuggestionEngine: aggregates 7-day metrics, runs rules, deduplicates
pending suggestions per type before creating new ones
- 3 initial rules: LowRetrievalUsage (usage_rate<0.2), ToolFailure
(success_rate<0.1), RepeatedTool (>100 calls/week → suggest skill)
- EventSuggestionCreated event type added to eventbus
- Cron wiring deferred to gateway startup integration pass
* feat(v3): implement auto-adapt guardrails with apply/rollback
- AdaptationGuardrails: max delta per cycle, min data points, locked
params, rollback-on-drop percentage
- ApplySuggestion: applies threshold suggestions to agent other_config
JSONB, stores baseline for rollback
- RollbackSuggestion: restores baseline values from suggestion params
- EvaluateApplied: compares post-apply metrics to baseline, auto-rolls
back when quality drops beyond threshold
- Scope limited to retrieval params only (never security settings)
* feat(v3): wire evolution stores + daily/weekly cron for suggestions
- Add EvolutionMetrics + EvolutionSuggestions to Stores struct + PG factory
- Wire EvolutionMetricsStore into ResolverDeps (cmd/gateway_managed.go)
- Add gateway_evolution_cron.go: daily suggestion analysis + weekly
evaluation/rollback for applied suggestions
- Cron runs as background goroutine with 5-min timeout per cycle
* fix(v3): address code review findings on evolution engine
- C1: persist baseline parameters before marking suggestion as applied
(was building map but never saving — rollback would always fail)
- H1: add tenant_id isolation to UpdateSuggestionStatus, GetSuggestion,
and new UpdateSuggestionParameters method
* test(v3): add unit tests for orchestration, suggestions, guardrails, prompt
- orchestration_mode_test: orchModeDenyTools (4 modes) + ResolveOrchestrationMode
(4 scenarios with mock stores)
- suggestion_rules_test: LowRetrievalUsage, ToolFailure, RepeatedTool with
threshold boundary tests (at/below/above min data points)
- evolution_guardrails_test: DefaultGuardrails values + CheckGuardrails
(insufficient data, locked params, zero-min fallback)
- prompt_builder_orchestration_test: BridgePromptBuilder orchestration section
presence/absence across 4 scenarios + target content verification
* test(v3): add integration tests for evolution metrics + suggestions
- Test helper: shared PG connection with sync.Once migration, per-test
tenant+agent seed with cleanup
- Evolution metrics: RecordMetric, AggregateToolMetrics (success rate),
Cleanup (TTL deletion)
- Evolution suggestions: full CRUD, UpdateSuggestionParameters (baseline
persist), tenant isolation (cross-tenant read blocked)
- Pipeline E2E: seed 25 failed tools + 55 low-usage retrievals, verify
SuggestionEngine creates suggestions, verify dedup on second run
- Fix: migration 039 de-duped (episodic_summaries already in 037)
- Fix: NULL reviewed_by scan via sql.NullString
* feat(v3): add HTTP API handlers for evolution, vault, episodic, orchestration, v3-flags
5 new handler files exposing v3 backend stores as REST endpoints:
- evolution_handlers.go: metrics query/aggregate + suggestions CRUD
- vault_handlers.go: cross-agent document listing + search + links
- episodic_handlers.go: episodic summaries list + hybrid search
- orchestration_handlers.go: computed mode + delegate targets (read-only)
- v3_flags_handlers.go: per-agent v3 feature flag get/toggle
Store fixes from code review:
- episodic FTS: use inline to_tsvector (no stored tsv column)
- episodic: conditional user_id filter in List + Search (admin view)
- episodic: add tenant_id to ExistsBySourceID + PruneExpired
- evolution: require tenant_id in context (no struct fallback)
- evolution: check RowsAffected on suggestion updates
- vault: optional agent_id filter in ListDocuments (cross-agent)
* feat(v3): add web UI for evolution tab, v3 settings, vault page, episodic memory
Agent Detail enhancements:
- V3 Settings section: pipeline/memory/retrieval flag toggles
- Orchestration section: mode badge + delegate targets display
- Evolution section: added metrics + suggestions v3 flag toggles
- Evolution tab: Recharts metrics charts + suggestion review table
with approve/reject/rollback actions + guardrails card
New pages:
- /vault: Knowledge Vault document registry with cross-agent listing,
hybrid search dialog, document detail with wikilinks
- Memory page: added Episodic Memory tab with summary cards,
expandable details, key topic badges, and hybrid search
Infrastructure:
- HttpClient: added patch() method
- Query keys: v3Flags, orchestration, evolution namespaces
- 4 new hooks: use-v3-flags, use-orchestration, use-evolution-metrics,
use-evolution-suggestions, use-vault, use-episodic
- i18n: vault namespace (en/vi/zh), agents + memory keys updated
- Reused formatRelativeTime from lib/format.ts (eliminated 3 duplicates)
* refactor(http): add bindJSON helper and migrate all decode call sites
Replace 36 json.NewDecoder(r.Body).Decode + error blocks with bindJSON
across 20 HTTP handler files. Standardizes decode error responses to
structured writeError format. Fixes unchecked decode in handleIndexAll.
* refactor(store): adopt sqlx for PG scan operations (Phase 1+2)
Add jmoiron/sqlx v1.4.0 with camelToSnake json tag mapper.
Migrate scan-heavy PG store methods to sqlx Get/Select:
- tracing.go: GetTrace, ListTraces, ListChildTraces, GetTraceSpans, GetCostSummary
- heartbeat.go: Get, ListDue, ListLogs
- providers.go: GetProvider, GetProviderByName, ListProviders, ListAllProviders
- mcp_servers.go: GetServer, GetServerByName, ListServers
- pairing.go: ListPending, ListPaired
- agents_export_queries.go: 5 export functions
- agents_export_team_queries.go: exportTeamMembers, ExportAgentLinks
All writes (INSERT/UPDATE/DELETE), execMapUpdate, and dynamic WHERE
builders remain raw SQL. Zero behavior change.
* refactor(store): adopt sqlx for SQLite scan operations (Phase 3)
Migrate SQLite store scan methods to sqlx Get/Select:
- providers.go: GetProvider, GetProviderByName, ListProviders, ListAllProviders
- tenants.go: GetTenant, GetTenantBySlug, ListTenants, GetTenantUser, ListUsers, ListUserTenants
- mcp_servers.go: GetServer, GetServerByName, ListServers
Create sqlx_scan_structs.go with sqliteTime-aware scan structs
(providerRow, tenantRow, tenantUserRow, mcpServerRow) to handle
SQLite TEXT timestamp parsing via StructScan.
* refactor(store): migrate PG bulk scan operations to sqlx (Phase 4)
Migrate scan-heavy methods across 6 PG store files:
- tenant_store.go: GetTenant, GetTenantBySlug, ListTenants, GetTenantUser,
ListUsers, ListUserTenants — removed 3 scan helpers
- teams.go: ListTeams, GetTeam, ListMembers, ListMembersByTenant
- teams_tasks_activity.go: ListComments, ListEvents, ListFollowUps
- pending_message_store.go: ListPending, ListByHistoryKey
- skills_grants.go: ListAgentGrants
- config_permissions.go: CheckPermission
~20 scan ops converted. Files with encryption post-processing,
pq.Array, pgvector, or dynamic SQL kept raw.
* refactor(store): extract shared CamelToSnake mapper, add UUIDArray usage note
- Move camelToSnake to internal/store/column_mapper.go (DRY)
- Both pg and sqlitestore packages now import shared CamelToSnake
- Add planned-use comment on UUIDArray type
* refactor(cli): migrate commands from config.json to HTTP API, add providers/setup/TUI
- Add unified HTTP client (gateway_http_client.go) with auth, error parsing, typed generics
- Rewrite agent list/add/delete to use gateway HTTP API instead of config.json
- Rewrite channels list to HTTP API, add channels add/delete subcommands
- Replace models command with full providers CRUD (list/add/update/delete/verify)
- Add setup wizard command (provider → agent → channel post-onboard flow)
- Add Bubble Tea TUI behind build tag (tui/!tui with noop fallback)
- Update onboard next-steps to mention goclaw setup
- Add build-tui Makefile target
- Fix URL path injection (url.PathEscape on all user-supplied path segments)
- Fix UTF-8 truncation in skills description display
* refactor(store): add explicit db struct tags, fix sqlx mapper for heartbeat scan error
Switch sqlx mapper from NewMapperFunc (which only applies CamelToSnake to
field names, not tag values) to NewMapperFunc("db", CamelToSnake) with
explicit db:"column_name" tags on all store structs.
Root cause: NewMapperFunc("json", fn) sets mapFunc but not tagMapFunc,
so camelCase json tags like "agentId" were used as-is instead of being
converted to "agent_id", causing "missing destination name" scan errors.
Fix: use db struct tags as the source of truth for column mapping.
Every DB entity field gets db:"column_name", nested JSON configs and
runtime-only structs get db:"-".
* test(store): add integration tests for 13 store interfaces (70 tests)
Cover Tier 1 (critical) + Tier 2 (security) stores with integration tests
running against pgvector pg18. Coverage from 2.4% to ~54%.
Stores tested: Session, Agent, Team/Task, Memory, KnowledgeGraph, Vault,
MCP Server, API Key, ConfigPermission, Contact.
Infrastructure: fixture builders (seedTeam, seedMCPServer, etc.),
mock EmbeddingProvider, multi-tenant helpers, expanded cleanup.
* fix(store): resolve NULL scan bugs in MCP server and task metadata
- mcp_servers: COALESCE nullable TEXT columns (display_name, command,
url, api_key, tool_prefix) to prevent sqlx scan failures
- mcp_servers_access: COALESCE nullable JSONB columns in ListAgentGrants
(tool_allow, tool_deny, config_overrides) to prevent silent row drops
- teams_tasks: default task metadata to '{}' instead of nil to satisfy
NOT NULL constraint on CreateTask
- sqlx_helpers: export InitSqlx for integration test setup
* feat(pipeline): fix v3 pipeline context injection, tracing, KG temporal filters
- Pipeline context: add InjectContext + LoadSessionHistory callbacks to
ContextStage, propagate enriched ctx via state.Ctx for iteration stages
- Pipeline tracing: wrap makeCallLLM with emitLLMSpanStart/End, wrap
makeExecuteToolCall/Raw with emitToolSpanStart/End
- Token counter: switch pipeline from FallbackCounter to TiktokenCounter
- KG temporal: add valid_until IS NULL filter to all entity/relation
queries (list, search, vector, FTS, traversal CTE, stats)
- Skills: add SkillEmbedder interface for future hybrid BM25+vector search
- Cache: remove unused tenantResolve dead code from PermissionCache
- Store: fix NULL scan bugs in tracing metadata and agent skill_nudge
- Test: add TestStoreKG_TemporalFilter integration test
- UI: add v3 version badge, evolution section, memory/traces improvements
* refactor(store): migrate KG store from raw sql.Rows to sqlx StructScan
Migrate 6 knowledge graph store files from manual rows.Scan() to
pkgSqlxDB.GetContext/SelectContext with intermediate scan row structs.
- Add entityRow, relationRow, traversalRow, dedupCandidateRow structs
with json.RawMessage for jsonb and time.Time for timestamptz columns
- Add toEntity()/toRelation() converters (UnixMilli + json.Unmarshal)
- Add sqlxTx() helper for wrapping *sql.Tx with sqlx mapper
- Fix ScanDuplicates passing time.Now().Unix() to TIMESTAMPTZ column
- Fix ListEntitiesTemporal missing tenant scope (scopeClause)
- Fix SupersedeEntity missing tenant scope and tenant_id on INSERT
- Fix DedupCandidate.CreatedAt using Unix() instead of UnixMilli()
- Update agents_export_queries.go to reuse new scan row structs
- Net -160 lines of manual scan boilerplate removed
* refactor(store): migrate memory, skills, agents, sessions, mcp, cron, vault stores to sqlx
Batch migration of 19 store files from raw rows.Scan() to
pkgSqlxDB.GetContext/SelectContext with intermediate scan row structs.
Groups migrated:
- Memory: memory_docs, memory_admin, memory_search, memory_embedding_cache
- Episodic: episodic_search, episodic_summaries
- Skills: skills, skills_admin, skills_embedding, skills_export_queries
- Agents: agents (backfill+shares), agents_context, agents_export_team_standalone
- Sessions: sessions_list (List, ListPaged, ListPagedRich)
- MCP: mcp_servers_access, mcp_export_queries
- Cron: cron_exec (GetRunLog)
- Vault: vault_documents (ListDocuments, ftsSearch, vectorSearch)
- Tenant: tenant_configs (ListDisabled, ListAll)
7 new scan row files created. Net -510 lines of manual scan boilerplate.
INSERT/UPDATE/DELETE and scalar COUNT queries kept as raw SQL.
* fix(store): fix 3 sqlx scan struct db tag issues found by audit
- Fix vault FTS alias mismatch: `AS rank` → `AS score` (critical: runtime scan error)
- Fix episodic key_topics type: json.RawMessage → pq.StringArray (TEXT[] column)
- Fix agentShareRow.CreatedAt: string → time.Time, wire to output struct
* feat(providers): implement Wave 2 provider resilience and intelligence
9-phase implementation covering:
- Request middleware chain with composable body transformers
- OpenAI prompt caching, service tier, and fast mode middlewares
- Error classification (9 categories) with two-tier failover
- Model registry with forward-compat resolvers (Anthropic + OpenAI)
- Embedding providers (OpenAI + Voyage) with 1536-dim validation
- Cooldown/probe system with per-provider:model state tracking
- Markdown-aware chunking shared across 5 channels
- Session recall via FTS + pgvector on episodic summaries
- Dreaming/promotion pipeline for long-term memory consolidation
Migrations: 000040 (episodic search index), 000041 (promoted_at column)
Schema version: 39 → 41
* feat(providers): wire model registry into gateway provider construction
Create InMemoryRegistry with Anthropic + OpenAI forward-compat resolvers
at gateway startup. Pass to all Anthropic and OpenAI providers created
from both config and DB sources.
* feat(consolidation): wire DomainEventBus and consolidation pipeline
Create DomainEventBus at gateway startup, thread through resolver →
LoopConfig → Loop → PipelineDeps. Emit session.completed event after
each run finalization. Register consolidation pipeline (episodic →
semantic → KG dedup → dreaming) with event bus subscriptions.
* fix(store): fix episodic key_topics pq.Array, ON CONFLICT, and migration 040 immutability
- episodic_summaries.go Create: json.Marshal(KeyTopics) → pq.Array (text[] column)
- episodic_search.go scanEpisodic/scanEpisodicRow: json.RawMessage → pq.StringArray
- episodic_summaries.go Create: ON CONFLICT add WHERE source_id IS NOT NULL for partial index
- migration 040: add immutable_array_to_string wrapper (array_to_string is STABLE in PG)
* test(store): add 17 integration tests for skills, cron, episodic, tenant configs
- Skills store: 6 tests (CRUD, grants, tenant isolation)
- Cron store: 4 tests (job CRUD, run log sqlx scan, pagination, tenant isolation)
- Episodic store: 4 tests (summary CRUD, list, FTS search, tenant isolation)
- Tenant configs: 3 tests (tool/skill disable, list, tenant isolation)
- Test helper: add cleanup for skills, cron, episodic tables
* fix(permissions): use cron-specific permission check for cron tool (#725)
* fix(security): harden exec path exemption matching (#721)
- Add absolute path exemption for dataDir/skills-store/ (fixes skill
scripts using absolute paths like /app/data/skills-store/ being denied)
- Strip surrounding quotes before prefix matching (LLMs often quote paths)
- Reject path traversal ("..") in exempt fields to prevent escape
- Switch from "any field exempt → skip" to per-field matching: only exempt
if ALL fields that match the deny pattern are individually exempt
- Closes pipe/comment bypass vectors where an exempt path in one argument
would exempt the entire command including non-exempt paths
Includes 27 test cases covering: legitimate access, quoted paths,
path traversal, unicode bypass, pipe/comment bypass, mixed args.
* fix(permissions): use cron-specific permission check for cron tool
Cron tool was hardcoded to check `file_writer` configType via
CheckFileWriterPermission(), ignoring the `cron` configType that
the UI actually saves when granting cron permissions. This caused
agents in group chats to be denied cron access even with correct
permission configured.
Add ConfigTypeCron constant and CheckCronPermission() that checks
`cron` configType first, falling back to `file_writer`.
---------
Co-authored-by: Viet Tran <viettranx@gmail.com>
* fix(chat): load message history on first conversation click (#730)
* fix(chat): load message history when selecting existing conversation from clean state
The skipNextHistoryRef was unconditionally set when sessionKey transitioned
from empty to non-empty. This prevented loadHistory() from running when
clicking an existing conversation from the initial /chat page. The skip
was only intended for the new-chat send flow where the optimistic message
is already displayed.
Guard the skip with expectingRunRef so it only activates when a message
send is in flight.
Closes #729
* docs: add UI diff evidence for PR #730
Before/after screenshots and HTML comparison report showing
first conversation click behavior fix.
* feat(whatsapp): port native WhatsApp channel with whatsmeow from dev
Cherry-pick
|
||
|
|
473679d28d |
fix(telegram): handle group-to-supergroup migration seamlessly
When a Telegram group upgrades to a supergroup, the chat ID changes and all existing references become stale. This caused send failures (400), orphaned sessions, and required manual re-pairing. Add dual-path migration handling: - Proactive: intercept inbound MigrateToChatID before isServiceMessage - Reactive: detect 400 + MigrateToChatID on send, migrate DB, retry DB migration updates in a single transaction (scoped by tenant + channel): - paired_devices: sender_id, chat_id - sessions: session_key, user_id - channel_contacts: sender_id - channel_pending_messages: history_key Also invalidates in-memory caches (approvedGroups, pairingReplySent, groupHistory) and handles media sends via migration retry in Send(). |
||
|
|
b65b23b212 |
fix(pool): strip stale member names from settings on save
When a pool member provider is deleted/disabled, its name persists in the owner's extra_provider_names config. Validation already skips stale refs, but the names accumulated as garbage in the DB. Now stripStalePoolMembers() runs after validation passes, removing member names that don't exist in the active provider set before the settings are written to the database. |
||
|
|
e9733e08c4 |
fix(pool): improve pool management UX — clickable affordance, stale ref handling, managed-by banner (#671)
* fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query (#641) 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. * fix(agent): defer warning messages after parallel tool results (#644) 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 * fix(docker): resolve @rollup/rollup-linux-arm64-musl missing on Alpine (#647) Added ui/web/.npmrc with supportedArchitectures for musl+glibc/arm64+x64. Updated Dockerfile to use --no-frozen-lockfile so pnpm fetches native rollup binding compatible with Alpine's musl libc. Lockfile still pinned by copy order. * docs(README): add history stars (#462) * fix(pool): skip stale pool member references during validation Unknown pool member references (deleted or disabled providers) now continue instead of returning an error. Prevents stale data from blocking provider saves. Closes #670 * fix(ui): redesign pool member selector and add managed-by banner Pool member selector: - Replace invisible outline button with custom element using dashed primary border, + icon badge, and "Click to add" hint text - Visible in both light and dark themes; hover transitions to solid border with shadow; active press scales down for tactile feedback Managed-by banner: - Show "Pool Defaults" section on pool members with info banner explaining which provider owns the pool, plus a Link navigation - Previously this section was completely hidden with no explanation i18n: add poolManagedByDescription and clickToAdd keys (en/vi/zh) * docs: add before/after UI evidence for PR #671 Annotated screenshots with red callout borders marking review areas. Self-contained HTML comparison report with dark/light theme toggle. * feat(ui): add pool discovery badges and setup wizard Replace verbose info banner with per-card "Pool available" badge on unpooled ChatGPT OAuth providers. Clicking the badge opens a new pool setup wizard dialog where users select owner, members, and strategy in one step. * docs: update UI evidence with pool discovery before/after * fix(ui): hide pool members from provider selector in agent forms Pool member providers are managed via the pool owner's routing config. Showing them as standalone options in the agent Provider dropdown is confusing — users may select a member directly instead of the owner, bypassing pool routing entirely. Filter out providers that exist in ownerByMember from the enabled providers list in ProviderModelSelect. * fix(ui): hide pool members from provider selector and add Pool badge Pool member providers are filtered out of the agent Provider dropdown in both the Create Agent dialog and the shared ProviderModelSelect component. Pool owners display a "Pool" badge so users know the provider routes to multiple accounts automatically. * docs: add provider selector before/after evidence * fix: revert stale merge in secure_cli.go and fix hardcoded i18n strings - Revert secureCLISelectColsAliased: b.agent_id → b.is_global (agent_id was dropped in migration 36, stale merge conflict artifact) - Replace hardcoded "Pool" badge text with t("providers:list.poolBadge") in provider-model-select and agent-identity-and-model-fields - Replace hardcoded "Disabled" with t("common:disabled") in pool wizard - Add list.poolBadge key to en/vi/zh locale files --------- Co-authored-by: Viet Tran <viettranx@gmail.com> Co-authored-by: Plateau Nguyen <nguyennlt.ncc@gmail.com> Co-authored-by: DNT <ducconit@gmail.com> |
||
|
|
156b2dd96c |
feat(secure-cli): per-agent grants with setting overrides
Replace agent_id column on secure_cli_binaries with is_global flag
and new secure_cli_agent_grants table for per-agent access control
with optional deny_args, deny_verbose, timeout_seconds, tips overrides.
- Migration 000036: create grants table, migrate agent-specific rows,
dedup binaries, drop agent_id, add is_global
- Store layer: SecureCLIAgentGrantStore interface + PG implementation,
LookupByBinary with LEFT JOIN grant merge, ListForAgent
- HTTP API: CRUD endpoints at /v1/cli-credentials/{id}/agent-grants
- Agent loop: buildCredentialCLIContext uses ListForAgent for scoped
system prompt (agents only see authorized CLIs)
- Web UI: grants dialog with card list + inline form, is_global toggle
replaces agent dropdown, i18n for en/vi/zh
|
||
|
|
2b1180f59d |
fix(http): allow localhost URLs for local provider types (Ollama, Claude CLI) (#674)
SSRF validation in validateProviderURL() blocked all localhost/loopback addresses, preventing local providers like Ollama from being configured with http://localhost:11434/v1. Introduce localProviderTypes map to skip SSRF checks for inherently local provider types (ollama, claude_cli, acp). Closes #673 |
||
|
|
c2bd5349e5 |
fix(ui,kg): fix credential user picker, KG filters, graph limits, and traversal depth
- Fix MCP/CLI credential dialogs: separate search text from selected user ID to prevent keystroke-as-selection bug (use onSelect pattern) - Fix Combobox: suppress value-sync after selection to prevent label flicker - Fix MCP dialog: prevent full-page spinner flash when switching users - Fix KG page filter: replace session-based scope with stats-based user_ids from KG entities (more reliable user list) - Increase KG graph visualization limit from 50 to 200 nodes with optimized D3 force layout breakpoints for 100+/150+ nodes - Cap graph API server-side limit at 500 to prevent unbounded queries - Increase KG traversal max_depth from 3 to 5 (HTTP API + agent tool) - Add dedicated Zustand store for KG detail dialog (isolated from main graph) - Add depth selector UI (2-5 hops) in entity detail dialog |
||
|
|
80c2c0e0ee |
fix(providers): normalize Ollama api_base URL to include /v1 suffix
- Write-time normalization via normalizeOllamaAPIBase() on create/update - Read-time safety net in resolveAPIBase() for pre-existing DB records - Covers both ProviderOllama and ProviderOllamaCloud - Stop hardcoding +"/v1" in registerInMemory/startup registration Closes #654 |
||
|
|
78a2152053 |
refactor(http): split provider_models.go into focused modules
- provider_models.go (154L): types, handler, reasoning helpers - provider_models_fetch.go (191L): remote API fetchers (Anthropic, Gemini, OpenAI, Ollama) - provider_models_catalog.go (103L): hardcoded model lists All files under 200-line guideline. No logic changes. |
||
|
|
e8579f5e1c |
feat(providers): add rich Ollama model listing via /api/tags (#659)
- Ollama/OllamaCloud providers now use native /api/tags for richer metadata - Display name built from family + parameter_size + quantization_level - Strips /v1 suffix, DockerLocalhost support, graceful fallback on error - 7 comprehensive tests covering all edge cases Closes #656 |
||
|
|
99720892a7 |
fix(storage): hide tenant roots from master storage API
Master tenant's base dir is the global data dir, which contains the tenants/ cross-tenant isolation root. Without this fix, the Storage page exposes tenant slugs, file names, and sizes. - Extract topLevelPath() helper, remove dead code - Add isHiddenPath() scoped to master tenant only - Guard handleList, handleRead, handleSize with hidden path checks - handleSize uses filepath.SkipDir to skip entire tenant subtree - Add regression tests for list, read, size, subpath, and non-master tenant isolation |
||
|
|
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). |
||
|
|
19d8faf28f | refactor(knowledge-graph): minor UI and handler adjustments | ||
|
|
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 |
||
|
|
173f1cea53 |
fix(cli-credentials): show existing env keys on edit and support merge/remove (#629)
fix(cli-credentials): show existing env keys on edit and support merge/remove - Expose env_keys on secure CLI list/get and per-user credential list (values stay hidden) - PUT merges env with stored secrets: empty value keeps existing, removed keys dropped - Generic error messages to prevent env key enumeration - Audit logging for user credential set/delete - DRY: reuse envKeysFromDecryptedJSON helper - Client-side env key name validation (en/vi/zh i18n) |
||
|
|
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() |
||
|
|
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. |
||
|
|
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. |
||
|
|
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) |
||
|
|
41c827c65f |
fix(agent): update setAgentStatus to include tenantID parameter (#621)
Summoning failures (e.g. provider timeout) left agents stuck in summoning because failure paths called setAgentStatus(context.Background(), ...) without tenant scope, causing agent not found on update. Pass tenantID to setAgentStatus and re-attach tenant context when missing. |
||
|
|
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 |
||
|
|
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). |
||
|
|
238d639de7 |
fix(providers): skip URL validation for ACP provider api_base (#610)
* fix(providers): skip URL validation for ACP provider api_base ACP uses api_base to store agent command/binary, so enforcing http/https URL validation rejects valid ACP configs. Bypass provider URL checks for ACP while keeping URL validation for HTTP-based providers. * fix(message): enhance target handling in message tool to support numeric chat IDs - Introduced argString function to correctly parse numeric chat IDs from tool arguments, ensuring they are treated as strings when necessary. - Added a new test case to verify that numeric targets are correctly processed and sent via the Telegram channel. |
||
|
|
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. |
||
|
|
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 |
||
|
|
3ca3bb2062 |
feat: add capability-aware reasoning effort controls (#593)
* feat(reasoning): add capability-aware effort resolution - resolve requested reasoning levels against exact model capabilities - persist requested effort on agents and expose effective effort in traces - add backend tests for provider models, agent store, and resolution logic Refs #591 * feat(ui): gate reasoning controls by model capabilities - only show supported reasoning levels when provider model metadata is available - preserve expert reasoning selections during async model loading - surface effective reasoning details in trace dialogs and localized copy Refs #591 * docs(api): document capability-aware reasoning controls - describe exact-match capability lookup and downgrade behavior - update provider model metadata and trace response documentation - refresh the generated OpenAPI spec for the new reasoning fields Refs #591 * feat: add provider-first reasoning controls * docs: refresh PR 593 UI evidence callouts * refactor: deduplicate reasoning normalize functions and remove PR evidence - Export NormalizeReasoningEffort/NormalizeReasoningFallback from providers package; store package now delegates instead of duplicating - Store reasoning fallback constants alias providers canonical definitions - Export deriveLegacyThinkingLevel from types/provider.ts; remove local copies from agent-advanced-dialog and provider-overview - Remove unused _providerType param from useProviderModels hook - Fix reasoning debug log to fire for all cases with a reason (not just non-off efforts) - Remove docs/pr-593-evidence/ binary screenshots from repo --------- Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
3fe0633d35 |
fix: auto-install deps on skill upload before archiving (#559)
Upload handler previously archived skills immediately when deps were missing. Now calls InstallDeps() first (owner/master tenant only) and falls back to archive on failure. Changes: - Auto-install missing deps during skill upload (same flow as seeder) - Atomic DB persist: deps state written with CreateSkillManaged in one call - Per-slug upload mutex prevents concurrent race conditions - Frontend: warning state (amber triangle) instead of throwing error - Non-cancellable context for DB write after dep install - SQLite StoreMissingDeps now works for custom skills (not just system) - Comprehensive unit + integration tests Closes #468 |
||
|
|
65d0d62acf |
fix(memory): guard provider embedding dimensions to 1536 schema (#563)
- Reject incompatible explicit provider embedding dimensions on create/update - Ignore previously saved incompatible dimensions at runtime, fall back to 1536 - Share RequiredMemoryEmbeddingDimensions constant from store package - Remove dimensions input from provider UI (always 1536 per pgvector schema) - Add HTTP, runtime, and validation unit tests Closes #548 Supersedes #410 |
||
|
|
88a5793030 |
fix(agents): sync IDENTITY.md Name field on agent rename (#582)
## Summary - Agent rename now updates Name field in IDENTITY.md (agent-level + per-user copies for open agents) via both HTTP and WS paths - Fixes lossy identity rebuild: previous logic reconstructed IDENTITY.md from only Name/Emoji/Avatar, silently dropping LLM-written fields (Creature, Purpose, Vibe, etc.) - Uses targeted field replacement (bootstrap.UpdateIdentityField) preserving original formatting - Adds ListUserContextFilesByName to AgentContextStore (PG + SQLite) - Fixes LastIndex bug in UpdateIdentityField where values containing colons (e.g. Avatar URLs) would break field replacement - Removes ~335 lines of dead config.json fallback code (agentStore is always set) - Adds 9 unit tests for UpdateIdentityField covering plain/markdown formats, URLs, edge cases |
||
|
|
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)
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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. |
||
|
|
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) |
||
|
|
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
|
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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.
|
||
|
|
4b6ed8cfce |
fix(agent): broken cache invalidation + WS handler parity gaps
Root cause: InvalidateAgent() and Remove() used plain agentKey for cache deletion, but Get() stores with tenant-scoped key "tenantID:agentKey" — invalidation never matched, so old provider/model persisted up to 10min (TTL). Also fixed: - WS agents.update missing provider, status, context_window, max_tool_iterations, frontmatter, is_default, budget fields - WS agents.create ignoring user provider/model (hardcoded defaults) - WS channel_instances.update bypassing column allowlist (security) - HTTP agentAllowedFields missing sandbox_config, context_pruning |
||
|
|
8b5a05a4b7 |
fix(web): sign media_refs paths in session history delivery (#519) (#520)
File downloads in web chat returned 401 when loading session history because MediaRefs paths were not signed with ?ft= HMAC tokens. - Add SignMediaPath() to clean legacy corrupted paths (stacked /v1/files/ prefixes, stale ?ft= tokens) and produce fresh signed URLs - Sign MediaRefs in chat.history and sessions.get WS handlers - Strip ?ft= from filename display in frontend - Add path traversal defense-in-depth check - Add unit tests for SignMediaPath legacy data healing Closes #519 |
||
|
|
8b08dec04c |
fix: address code review findings (critical + high + medium)
Critical: - retryOnBusy: return lastErr after exhausted retries instead of nil High: - Remove debug console.log from use-paired-devices.ts Medium: - ChannelGeneralTab: sync state from instance prop via useEffect - i18n: add MsgCannotRemoveLastWriter key + translations (en/vi/zh) - use-channel-crud: refetch after delete instead of optimistic remove |
||
|
|
0a8ca13d08 |
refactor(http): standardize error responses with writeError helper
- Create response_helpers.go with writeError(w, status, code, msg) for structured errors - Move writeJSON to response_helpers.go for co-location - Migrate agents.go, channel_instances.go, api_keys.go to writeError with protocol error codes - Replace raw err.Error() with i18n messages + slog.Error for debugging - Fix channel creation: map camelCase input to snake_case for backend HTTP API - Fix PairedDevicesSection reload button: use RefreshButton component |
||
|
|
6ea9b4d762 |
feat(desktop): add channel management, paired devices, and multiple fixes
Channel Management:
- Add Channels settings tab with full CRUD for Telegram/Discord (Lite: max 1 each)
- Channel detail panel with tabs: General, Credentials, Managers
- Advanced settings dialog (network, limits, streaming, behavior, access control)
- Schema-driven field renderer with Combobox selects, Switch toggles
- Paired Devices section: approve/deny pending, revoke paired, WS event auto-refresh
- Pairing notification badge in sidebar footer with pending count
- i18n support (en/vi/zh) for all channel strings
Bug Fixes:
- Fix Vietnamese slug generation (NFD normalize + đ/Đ handling) in lib/slug.ts
- Make agent key field editable instead of read-only
- Fix trace detail input/output: use MarkdownRenderer with copy button instead of forced-dark CodePreview
- Fix API error parsing to handle both {error: "string"} and {error: {message}} formats
- Add Accept-Language header to desktop API client for i18n error messages
- Wrap raw err.Error() with i18n messages in HTTP handlers (agents, channel_instances)
- Increase SQLite MaxOpenConns from 2 to 4 to reduce SQLITE_BUSY contention
- Add retryOnBusy wrapper for context file seeding writes
- Update EditionCompareModal: channels moved from "false" to "1 Telegram + 1 Discord"
|
||
|
|
2c94e8070f |
fix(desktop): image rendering for Windows paths and MEDIA: prefix
- toFileUrl: normalize backslashes, detect Windows drive-letter paths - resolveFileUrl: strip MEDIA: prefix from tool results, Windows path support - handleServe: skip prepending "/" for Windows drive-letter URL paths |
||
|
|
74dc086a80 |
fix(providers): post-merge fixes for Codex OAuth pools (#450)
- fix Rules of Hooks violation in chatgpt-oauth-routing-section - add stale-while-revalidate with atomic dedup for RouteEligibility - move raw SQL from HTTP handler to TracingStore.ListCodexPoolSpans - persist round-robin state in Registry shared counter - extract duplicated frontend helpers to agent-display-utils - split oversized frontend files (964→214 lines max) - add GIN indexes for spans.metadata and sessions.metadata - fix tenant-aware provider lookup in handleQuota - separate empty-role vs error handling in resolveTenantHint - scope pool validation to chatgpt_oauth providers only - wrap buildEntries in useCallback for stable useMemo deps - document OAuth concurrent auth limitation and RoleAdmin breaking change |
||
|
|
30708ae79d |
feat(providers): support Codex OAuth pools with inherited routing defaults
* feat(auth): support named chatgpt oauth providers - add provider-scoped ChatGPT OAuth routes and CLI support - persist refresh tokens per provider and reject provider-type collisions - wire provider OAuth setup flows in the dashboard and setup UI Refs #448 * feat(agent): add chatgpt oauth account routing - add agent other_config routing for manual and round-robin selection - reuse routed provider resolution across resolver and pending loaders - add router, parser, and agent advanced dialog coverage for multi-account use Refs #448 * docs(api): describe chatgpt oauth routing - document named-provider ChatGPT OAuth auth routes - describe agent-side account routing and round-robin behavior - update OpenAPI agent config schema and provider type enum Refs #448 * fix(store): add missing agent key context helpers * feat(ui): clarify chatgpt oauth account setup and routing * docs(providers): align chatgpt oauth alias examples * feat(agent): add codex pool activity dashboard * fix(providers): harden codex oauth alias setup * feat(codex-pool): improve routing dashboard UX - redesign the Codex/OpenAI pool page around saved-pool checkpoints and live evidence - add clearer selection, attention, and recent-proof states for pool members - make the lower panels fill the remaining desktop viewport while staying responsive * fix(store): resolve context helper merge duplication * feat(oauth): add codex pool quota and observation APIs - add quota inspection and observation endpoints for ChatGPT Subscription (OAuth) providers - teach codex routing to surface pool activity, observation metadata, and quota-aware readiness - extend tests and HTTP docs/OpenAPI for the new pool monitoring flows * feat(web): add codex pool quota monitor and controls - add provider quota fetching, readiness badges, and live routing evidence on the account pool page - redesign pool setup and activity panels for multi-account management with localized copy updates - keep the live monitor internally scrollable and compact the account cards for better viewport fit * fix(web): clarify pool routing labels - rename the recent request badge from Direct to Selected - restore compact quota bars in the live pool cards * feat(codex-pool): add runtime health dashboard - derive per-provider success and failure health from routed Codex traces - surface routing, quota, and recent request evidence in the pool UI - align provider alias guidance and owner access with the dashboard role model * docs(auth): document tenant scoping and key roles * fix(auth): harden tenant and codex pool access control * fix(providers): align codex pool runtime defaults * feat(ui): tighten codex pool responsive layout * feat(chatgpt-oauth): refine codex pool management UX * feat(chatgpt-oauth): surface quota bars on provider pages - add compact quota bars to Codex provider rows and provider detail - fetch quota only for ready visible provider rows and ready detail aliases - fix managed-member detail visibility and tighten provider locale copy |
||
|
|
e183b459c9 |
feat: SQLite desktop edition — full desktop app with team tasks (#505)
* feat(store): add SQLite backend foundation with build-tag injection
Add sqlitestore package (//go:build sqlite) as alternative to PostgreSQL:
- pool.go: WAL mode, busy_timeout, 4 concurrent read connections
- helpers.go: ? param helpers, JSON array storage, nullable/update utils
- scope.go: tenant scope with ? placeholders (vs PG's $N)
- schema.sql: 1296-line flattened DDL from 29 PG migrations (51 tables)
- schema.go: embedded schema with transactional apply + version tracking
- factory.go: NewSQLiteStores() stub (stores wired in Phase 2)
Build-tag split for store initialization:
- cmd/gateway_stores_pg.go (//go:build !sqlite) — default PG-only
- cmd/gateway_stores_sqlite.go (//go:build sqlite) — runtime PG/SQLite switch
- cmd/gateway_setup.go: extracted wireTracingAndCron() shared helper
Config: GOCLAW_STORAGE_BACKEND + GOCLAW_SQLITE_PATH env vars.
Security: goclaw.db added to DenyPaths (exec, read_file, filesystem tools).
* feat(edition): add centralized edition package for feature tier limits
New internal/edition/ package — single source of truth for all edition limits:
- Edition struct with MaxAgents, MaxTeams, MaxChannels, KGEnabled, TeamFullMode, etc.
- Standard (default, all features) and Lite (desktop, 5 agents, 1 team) presets
- atomic.Pointer for thread-safe Current()/SetCurrent()
Wiring:
- cmd/gateway.go: GOCLAW_EDITION env override (lite/standard) at startup
- cmd/gateway_stores_sqlite.go: auto-set Lite when backend=sqlite
- /v1/edition HTTP endpoint for UI comparison modal (public, no auth)
* feat(sqlitestore): implement Phase 2A core stores + sqliteonly build tag
Implement 12 SQLite store backends (4200+ LOC) mirroring existing PG stores:
- SessionStore, AgentStore, ProviderStore, TracingStore, SnapshotStore
- ConfigSecretsStore, SystemConfigStore, TenantStore, HeartbeatStore
- BuiltinToolStore, BuiltinToolTenantConfigStore, SkillTenantConfigStore
All stores wired in factory.go. Remaining stores (Memory, Cron, Skills,
Teams, etc.) left nil — gateway handles gracefully.
Add sqliteonly build tag for PG-free desktop builds:
- go build . → PG only
- go build -tags sqlite . → PG + SQLite (runtime switch)
- go build -tags sqliteonly . → SQLite only (no pgx dependency)
Key SQLite adaptations:
- ? placeholders (not $N), json_extract/json_each/json_array_length
- DISTINCT ON → GROUP BY + Go dedup, ANY($1) → IN (?,?...)
- rows.Err() checks on all scan loops, execMapUpdateWhereTenant in helpers
- GetOrCreateUserProfile uses RowsAffected() instead of PG's xmax trick
* feat(sqlitestore): implement Phase 2B feature stores
Port 8 additional store backends to SQLite (19 new files, ~6000 LOC):
Cron: cron.go, cron_crud.go, cron_exec.go, cron_scheduler.go
- Job scheduling with cache, ListDue, MarkRunning, MarkComplete
Skills: skills.go, skills_crud.go, skills_content.go, skills_grants.go
- CRUD, grants, content management. LIKE search (no FTS/vector in Lite)
MCP: mcp_servers.go, mcp_servers_access.go, mcp_user_credentials.go
- Server CRUD, agent/user grants, encrypted credentials
Channels: channel_instances.go, pairing.go, pending_messages.go, contacts.go
- Channel management, device pairing, message queue, contact store
Teams: teams.go, teams_tasks.go, teams_tasks_lifecycle.go, teams_tasks_activity.go
- Team/task CRUD, lifecycle transitions, activity log, progress tracking
- JSON array for members/blocked_by (replaces PG text[])
Remaining nil stores: Memory, AgentLinks, KnowledgeGraph, Activity,
SecureCLI, APIKeys, ConfigPermissions — gateway handles gracefully.
* fix(sqlitestore): fix critical arg ordering + data races in Phase 2B stores
C1: ListTasks arg mismatch — limit+1 in userID slot, wrong results
C2: SearchTasks missing duplicate userID arg for (? = '' OR t.user_id = ?)
H3: cron_crud.go cacheLoaded written without mutex → use InvalidateCache()
H4: cron_exec.go discarded ExecContext error → log warning
H1: Add rows.Err() checks in cron_crud, cron_scheduler, cron_exec, pairing
* feat(sqlitestore): implement Phase 2C — Memory, Activity, APIKeys, ConfigPermissions
Complete remaining essential stores (6 new files, ~900 LOC):
Memory: memory.go, memory_docs.go, memory_search.go
- Document/chunk CRUD, LIKE-based search (no vector in Lite edition)
- Embedding methods return empty results gracefully
Activity: activity.go — simple activity logging
APIKeys: api_keys.go — API key CRUD with SHA-256 hash lookup
ConfigPermissions: config_permissions.go — permission rules with TTL cache
24/27 stores now wired. Only AgentLinks, KnowledgeGraph, SecureCLI
remain nil (disabled in Lite edition by design).
Total: 49 files, 11,435 LOC in internal/store/sqlitestore/
* fix(sqlitestore): fix variable shadow in GetDocument + handle chunk delete error
C1: GetDocument scopeClause used := inside if/else blocks, shadowing
outer err variable — query errors silently swallowed. Fixed by renaming
to tcErr matching PG pattern.
H1: IndexDocument chunk deletion ExecContext error was discarded, could
cause duplicate chunks. Now returns error on failure.
* feat(desktop): Phase 3 — Wails v2 desktop app shell with chat UI
Desktop app (ui/desktop/) using Wails v2 + React 19 + Tailwind CSS 4:
Go backend:
- main.go: Wails entry point with embedded frontend assets
- app.go: gateway embedding via goroutine, health check, Wails bindings
- keyring.go: OS keyring secrets with file fallback
- cmd/gateway_export.go: exports RunGateway() for desktop embedding
React frontend:
- WS v3 client: protocol handshake, exponential backoff, call queue
- Chat system: Zustand stores, RAF-batched streaming, 10 event handlers
- Components: MessageBubble, MarkdownRenderer (rehype-sanitize),
CodeBlock, ToolCallBlock, ThinkingBlock, ActivityIndicator, InputBar
- Layout: AppShell (2-column), Sidebar with agent/session list, TopBar
- Onboarding wizard (5 steps): welcome, gateway, provider, agent, ready
- Magic Blue theme (dark/light), Inter + JetBrains Mono typography
Build: all 3 variants pass (PG, sqlite, sqliteonly)
* chore: ignore Wails build artifacts (wailsjs, build, package.json.md5)
* fix(desktop): correct onboarding provider list and agent creation API contract
- ProviderStep: expand from 3 to 16 providers in 4 groups (Popular, Cloud, Local, Regional)
with correct provider_type values and api_base defaults
- AgentStep: fix API payload — use agent_key (slug), provider (name string),
agent_type=predefined with description in other_config
- use-agents: fix field mapping — agent_key, display_name from backend response
- Clean up failed provider on verify error
* fix(desktop): add missing providers — Bailian Coding, Z.ai Coding, Ollama Cloud
* fix(desktop): match web dashboard brand colors + persist onboarding in store
- Replace Magic Blue theme with web dashboard's warm blue OKLCH palette
- Move onboarding state from localStorage to Zustand persist store
- Add "Run Setup Wizard" option in TopBar settings menu to re-trigger onboarding
- Fix light mode theme activation (explicit :root:not(.dark) overrides)
* fix(desktop): apply dark theme before first paint + better error messages
- Add class="dark" default on <html> + inline script to read persisted theme
before React hydrate (prevents light flash on dark mode)
- Improve provider test error message for network failures
* feat(desktop): add GoClaw logo from web UI to onboarding + topbar
* fix(desktop): add Vite proxy for dev mode to avoid CORS gateway errors
- Proxy /v1, /ws, /health to localhost:18790 in Vite dev server
- Use relative URLs in dev mode (import.meta.env.DEV) so proxy handles CORS
- Production build uses direct gateway URL from Wails binding
* fix(desktop): GoClaw dock icon + fix duplicate provider slug on re-test
- Convert goclaw-icon.svg to 1024x1024 PNG for macOS dock icon
- Fix provider creation: handle existing slug by finding and updating
- Track build/appicon.png in git (exclude only build/bin/)
* fix(sqlitestore): UUID text/blob mismatch in scopeClause breaks all queries
scopeClause passed uuid.UUID (16-byte array) to SQLite ? placeholder,
but tenant_id column stores TEXT strings. SQLite compared BLOB vs TEXT
→ no match → all scoped queries returned empty results.
Fix: pass scope.TenantID.String() to ensure TEXT comparison.
Also includes:
- macOS dock icon (.icns from GoClaw logo)
- Onboarding auto-detect existing providers/agents
- Debug logging for token + API URL
* fix(desktop): CORS for dev mode + direct gateway URL
- Wails dev server (port 34115) doesn't proxy API calls, causing 405
- Frontend now connects directly to gateway URL from Wails binding
- Added GOCLAW_DESKTOP=1 env → enables CORS middleware on gateway
- desktopCORS wraps mux with Access-Control-Allow-* headers + OPTIONS
- Simplified provider test flow: list-then-create/update
* fix(desktop): split onboarding into 6 steps matching web UI flow
Web UI flow: create provider → select model + verify → create agent.
Desktop was incorrectly trying to verify before creating provider.
Changes:
- ProviderStep: now only creates/saves provider (no verify)
- NEW ModelVerifyStep: loads models from provider, test connection
- AgentStep: receives pre-selected model, shows read-only model field
- OnboardingWizard: 6 steps with auto-detect skip logic
- Auto-detect: has agents→Ready, has providers→ModelVerify, nothing→Provider
* feat(desktop): add Combobox component, use searchable model selector
- New Combobox: searchable dropdown with custom value support
- ModelVerifyStep: replace plain input/select with Combobox
- Models loaded from GET /v1/providers/{id}/models API
- Allows typing custom model name if API returns empty list
* fix(desktop): rename icon to iconfile.icns matching Wails convention
* refactor(desktop): overhaul UI/UX, fix onboarding, fix event handling
Desktop app major refactor:
UI/UX:
- Chat-focused layout with floating panels on dotted canvas
- Sidebar: agent list + sessions grouped by date (no resource counts)
- Modern input bar: rounded pill with attach/send buttons
- User bubble: card style matching web UI (not solid color)
- Thinking block: collapsible, max-height, proper label
- ErrorBoundary wrapping app
- Dock icon: regenerated with transparent bg + dark rounded frame
Onboarding:
- 3-step flow matching web UI (Provider → Model → Agent)
- Proper create vs update (check DB before POST)
- SetupStepper with step circles + connectors
- Agent presets from web UI (Fox Spirit, Artisan, Astrologer)
- Auto-detect existing setup via use-bootstrap-status hook
Event handling (verified from Go source):
- chunk: payload.content (not payload.chunk)
- thinking: payload.content (not payload.thinking)
- tool.call: payload.id/name (not toolId/toolName)
- tool.result: payload.is_error/content (not error field)
- run.completed: usage.prompt_tokens/completion_tokens
- New: block.reply, run.retrying handlers
Backend fixes:
- SQLite scanTime helper for modernc.org/sqlite text timestamps
- X-GoClaw-User-Id header in desktop API client (not X-User-ID)
- user_id: system (owner role in desktop single-user mode)
- CORS: allow X-GoClaw-User-Id header
- SQLite busy timeout: 5s → 10s
- Snapshot SQL: cross-DB compatible (FILTER→CASE, ::BIGINT→CAST)
- Promise.allSettled for bootstrap status (one fail doesn't block other)
* feat(desktop): chat polish, SQLite fixes, summoning modal, bootstrap guard
Chat Polish (Phase 1):
- ToolCallBlock: Wrench/Zap icons, phase badges, arg summary, grouped rendering
- ThinkingBlock: auto-expand on stream, Brain icon, cursor pulse
- MessageBubble: isStreaming prop, streaming cursor, grouped tool calls
- ImageLightbox: fullscreen overlay, gallery nav, keyboard shortcuts, download
- MediaBlock: grid layout, click-to-open lightbox, hover overlay
- ActivityIndicator: phase-specific icons (Brain/Wrench/RefreshCw)
- ChatCanvas: track lastAssistantId for streaming, EmptyState with prompts
- Filter [System] nudge messages and tool-role messages from chat history
SQLite Fixes:
- sessions_list: fix time.Time scan failure (3 sites) using sqliteTime scanner
- snapshots: fix ON CONFLICT expression mismatch with unique index
- snapshots: fix GetLatestBucket using nullSqliteTime
- pool: explicit PRAGMAs (busy_timeout=15s, WAL, synchronous=NORMAL)
- schema: seed master tenant (was missing, causing FK violations)
- schema: incremental migration framework (version-gated patches)
Onboarding:
- SummoningModal: port from web UI with framer-motion animations
- AgentStep: show summoning modal after create, continue button
- App: auto-detect empty DB and reset onboarded flag
- ChatCanvas: loading spinner while agent loads after onboarding
Bootstrap Guard:
- After auto-cleanup of BOOTSTRAP.md, check if USER.md is still empty
- Inject reminder if agent cleared BOOTSTRAP but didn't fill USER.md
Session Management:
- Load chat history on session click (was missing useEffect)
- Fix race condition: don't clear messages on session switch (atomic replace)
- SidebarFooter: center New Chat button text
* fix(desktop): session management, delete confirm, event listener race fix
- New Chat: only clears state, no empty session creation (sendMessage auto-creates)
- Delete session: hover X button with ConfirmDialog confirmation
- Event listener: use sessionKeyRef instead of closure to prevent stale events
- Remove "skip to dashboard" link from onboarding
- Add reusable ConfirmDialog + ConfirmDeleteDialog common components
* feat(desktop): settings view with tabbed layout (Phase 4)
- SettingsView: tab container with header, close button, canvas-dots bg
- SettingsTabBar: 9 tabs (Appearance, Providers, Agents, MCP, Skills, Tools, Cron, Traces, About)
- AppearanceTab: dark/light theme toggle, language + timezone placeholders
- AboutTab: version, edition limits, runtime info
- ui-store: activeView, settingsTab, openSettings(), closeSettings()
- AppShell: switch between chat and settings views
- SidebarFooter: gear icon opens settings (was "Run Setup Wizard")
- Keyboard: Cmd+, opens settings, Escape closes
- Branding: "GoClaw Lite" in sidebar header
- Agent status: online (green) instead of idle for desktop
- Tab content wrapped in solid bg card with border
* feat(desktop): provider management CRUD in settings (Phase 5)
- use-providers hook: list, create, update, delete, verify via HTTP API
- ProviderList: list view with Add button, empty state
- ProviderRow: status dot, type badge, edit/delete actions
- ProviderFormDialog: create/edit with type selector, masked API key, test connection
- Wire ProviderList into Settings Providers tab
* fix(desktop): remove Test Connection from provider form (requires model + provider ID)
* feat(desktop): agent management CRUD in settings (Phase 6)
- AgentData/AgentInput types matching web UI contracts
- use-agent-crud hook: list, create, update, delete, resummon (5 agent limit)
- AgentCard: emoji, status dot, provider/model/type badges, edit/delete/resummon
- AgentFormDialog: provider Combobox + model Combobox (from /v1/providers/{id}/models)
- Create: agent_type selector, "Check & Create" verifies model before create
- Edit: no type change, no re-verify needed
- Personality textarea for predefined agents
- AgentList: grid, edition limit warning, create triggers SummoningModal
- Delete uses ConfirmDeleteDialog (type name to confirm)
- Sidebar agent list refreshes after CRUD
* feat(desktop): agent detail panel with full config (Phase 6 polish)
Agent detail panel (fullscreen overlay covering sidebar):
- PersonalitySection: emoji editor, display name, description, status select, default toggle, agent key display
- ModelBudgetSection: provider/model Combobox with verify-before-save, context window, max tool iterations
- EvolutionSection: self_evolve toggle with info callout (predefined agents only)
- Sticky save bar: backdrop blur, cancel/save buttons, spinner on save
- Save blocked if provider/model changed but not verified
AgentList: card click opens detail panel, create dialog separate
AgentFormDialog: create-only, Check & Create with verify
* fix(desktop): resummon confirm, summoning z-index, save bar UX
- Resummon requires confirm dialog before triggering
- SummoningModal z-index z-50 → z-[70] (above detail panel z-[60])
- Save bar: show error inline, "Verify model first" when blocked
* feat(desktop): agent detail quality polish — files tab, memory config, rich cards
Types (synced with web UI):
- AgentData: added owner_id, workspace, restrict_to_workspace, frontmatter, context_window, max_tool_iterations as required fields
- MemoryConfig, CompactionConfig interfaces
- BootstrapFile type for WS file operations
AgentCard (matching web UI agent-card.tsx):
- Star icon for default agent
- Animated pulse badge for summoning status
- Self-evolve sparkle indicator (orange when active)
- Frontmatter/expertise with line-clamp-3
- Context window display (e.g. "200K ctx")
- Safe emoji extraction, UUID name detection
AgentDetailPanel:
- Tab navigation: Overview + Files tabs
- Overview: Personality + ModelBudget + Memory + Evolution sections
- Files tab: WS-based file editor (agents.files.list/get/set)
- File sidebar with selection
- Textarea editor with dirty tracking
- Save button with spinner
- Sticky save bar with backdrop blur (overview only)
- Resummon with confirm dialog
MemorySection (matching web UI memory-section.tsx):
- Enable/disable toggle
- 6 config fields: max_results, min_score, max_chunk_len, chunk_overlap, vector_weight, text_weight
- "Using global defaults" when disabled
* feat(desktop): agent files — hide USER/HEARTBEAT, add Edit with AI
- Hide USER.md, USER_PREDEFINED.md, HEARTBEAT.md from files tab (managed by bootstrap/cron)
- Add "Edit with AI" button → RegenerateDialog → POST /v1/agents/{id}/regenerate
- Auto-select first file on load
- Show file size in sidebar
- Pass agentId to files tab for regenerate API call
* fix(desktop): remove memory config section (no embedding in SQLite)
* fix(desktop): remove misleading bytes display from file sidebar
* fix(desktop): replace native checkboxes/selects with custom Switch + Combobox
- New Switch component matching Radix UI toggle style
- Replace all native <input type="checkbox"> with Switch in agent/provider forms
- Replace native <select> for status with Combobox
- All interactive elements have cursor-pointer
* fix(desktop): evolution callout colors — use opacity-based for both themes
* fix(desktop): global cursor-pointer for all interactive elements
* fix(desktop): improve dark mode contrast for text and status colors
- text-secondary: 0.62 → 0.68 lightness (better readability on dark bg)
- text-muted: 0.52 → 0.58 lightness (was below WCAG AA 4.5:1 minimum)
- success: 0.45 → 0.55 (green was too dim on dark bg)
- warning: 0.65 → 0.70 (slightly brighter)
- idle: 0.52 → 0.58 (match text-muted)
* fix(desktop): revert color values to exact web UI match (0.62/0.52/0.45)
* fix(desktop): agent card badge colors — match web UI badge variants exactly
* feat(desktop): add MCP servers + Builtin Tools settings tabs
Phase 7 implementation:
MCP Tab:
- Full CRUD with 5-server edition limit
- Form dialog with transport-conditional fields (stdio/SSE/streamable-http)
- Test Connection with inline success/error feedback
- Agent grants dialog (grant/revoke per agent)
- Tools discovery dialog (view server tools)
- KeyValueEditor with sensitive field masking (auth/token/secret)
Tools Tab:
- Category-grouped list of 41 seeded builtin tools
- Toggle enable/disable with optimistic update
- Specialized settings forms: web_fetch extractor chain, media provider chain
- Generic JSON editor fallback for other tools
- Provider/model Combobox selection for media tools
Common:
- RefreshButton component with 500ms min spin animation
- KeyValueEditor with password masking for sensitive keys
Fixes:
- SQLite builtin_tools scan: use scanTimePair() for timestamps
- Session click while in settings: now closes settings view
- Dark mode text contrast: bumped text-secondary/text-muted lightness
- Light mode text contrast: darkened text-secondary/text-muted
- Focus ring thickness: ring-2 → ring-1 globally
- Misleading "memory layering (Postgres)" log label
* feat(desktop): add Skills tab, agent skill grants, emoji avatar, agent form redesign
Skills:
- Skills settings tab with upload ZIP, toggle, delete, runtime check
- Agent skill grants section in agent detail panel (toggle per agent)
- SQLite SkillManageStore interface compliance fixes
Agent form:
- 2-column layout, wider modal (max-w-3xl)
- 6 personality presets (Fox Spirit, Artisan, Astrologer, Researcher, Writer, Coder)
- Separate Verify Model + Summon buttons
- Always predefined type (removed open option)
Fixes:
- SQLite time.Time scan: mcp_servers, mcp_grants, activity_logs
- Combobox portal with scroll/resize tracking
- AgentAvatar shows emoji from other_config
- uploadFile sends X-GoClaw-User-Id header + generic type
- isApiClientReady guard prevents ErrorBoundary crash
- Verify model field: valid (not success)
* fix(sqlitestore): comprehensive timestamp scan sweep + UI fixes
SQLite timestamp sweep (13 files, 25+ sites):
All time.Time direct scans replaced with sqliteTime/scanTimePair/nullSqliteTime.
Files: activity, teams, teams_tasks, teams_tasks_activity, config_permissions,
agents_access, tracing_spans, tracing_scan, channel_instances, tenants,
pending_messages, api_keys, heartbeat.
UI fixes:
- Agent switching: clear active session + chat when agent changes
- MCP table: vertical align middle on row cells
* fix(sqlitestore): fix json.RawMessage scan + sqliteVal for dynamic updates
- mcp_servers_access: scan json columns via string intermediates (SQLite
TEXT → json.RawMessage incompatible, use string then convert)
- helpers: add sqliteVal() to auto-marshal map/slice/struct to JSON
string in execMapUpdate/execMapUpdateWhereTenant — fixes agent save
500 error when updating other_config, tools_config, etc.
- Add AgentMcpSection: toggle MCP server grants per agent in detail panel
- Clean up debug logging from McpGrantsDialog
* feat(desktop): add i18n (react-i18next) + toast system + language/timezone pickers
i18n:
- Install react-i18next + i18next
- Copy 12 web locale namespaces (en/vi/zh) + desktop.json namespace
- Create i18n/index.ts with browser language detection + localStorage persist
- Replace ~300 hardcoded strings across 40+ components with t() calls
- Language picker in ChatTopBar (top-right) + Settings > Appearance
- Timezone picker in ChatTopBar with search + Intl.supportedValuesOf fallback
- All 6 agent presets fully translated (vi/zh) with prompts from locale files
- Agent Key never translated (uses stable English slugs)
Toast:
- Zustand toast store (success/error/warning/default, 4s auto-dismiss)
- Toaster component (bottom-right, z-100, slide-in animation)
- Toast calls in all CRUD hooks (agents, providers, MCP, skills, tools)
* feat(desktop): add Cron Jobs + Traces settings tabs with syntax highlighting
Cron Jobs (Phase 8):
- WS RPC hook (cron.list/create/delete/toggle/run/runs)
- CronList table with schedule formatting, status badges, run/toggle/delete
- CronFormDialog with slug name, agent selector, 3-way schedule (every/cron/once)
- CronRunsDialog for execution history
Traces (Phase 9):
- HTTP REST hook (GET /v1/traces with pagination + agent filter)
- TraceList table with duration, tokens, spans, relative time
- TraceDetailDialog with metadata, collapsible input/output, flat span list
- Syntax-highlighted JSON/code previews via react-syntax-highlighter
* fix(sqlitestore): fix tracing scan — endTime *time.Time → nullSqliteTime
Both trace and span scan functions used *time.Time for nullable end_time
column, which fails on SQLite TEXT timestamps. Changed to nullSqliteTime
with Valid check before assigning pointer.
* fix(desktop): fix cron schedule type label — map 'at' kind to 'once' i18n key
* fix(desktop): prevent flash on cron/traces refresh — keep data while refetching
* feat(desktop): add file attachment rendering — FileButton + FilePreviewDialog
- FileButton: compact attachment button with emoji icon, filename, size, download
- FilePreviewDialog: modal with type-detected preview (image/video/audio/markdown/code/text)
- MarkdownRenderer: override a/img for /v1/files/ links → FileButton + resolved URLs
- MediaBlock: non-image files render as FileButton instead of plain links
- api.ts: add getBaseUrl() for file URL resolution
* fix(desktop): resolve file URLs in chat — toFileUrl for media_refs + relative paths
- use-chat: add toFileUrl() to convert raw paths to /v1/files/{basename} URLs
for both run.completed media and history media_refs
- MarkdownRenderer: detect relative file paths (./path/file.ext) via isFileLink
in addition to /v1/files/ links, resolve all to gateway URL
* fix(desktop): authenticated file serving — media cache + blob URLs
Security fix: all /v1/files/ requests now use Bearer auth via fetchFile().
No more raw <a href> or <img src> with unauthenticated file URLs.
- Add media-cache.ts (blob cache with 5-min TTL, dedup inflight fetches)
- Add use-media-url.ts hook (returns cached blob URL for authenticated media)
- Add AuthImage component (loads images via auth blob) + downloadFile helper
- Update MarkdownRenderer: file images use AuthImage, downloads use downloadFile
- Update FileButton: authenticated download via blob
- Update FilePreviewDialog: authenticated fetch for text/preview content
- Clean filename display (strip timestamps + query params)
* fix(desktop): fix file attachments — use media_refs.id for URL, auth all media
Root cause: media_refs from backend has {id, mime_type, kind} but no path/url.
The id IS the filename basename. Fixed toFileUrl to use ref.id as fallback.
Also: AuthImage for lightbox, authenticated audio/video in MediaBlock,
clean filename display (strip timestamps).
* fix(desktop): file serving, traces, and media rendering improvements
Backend:
- Store MediaRef.Path in loop_finalize for direct file serving
- Use full path (not basename) in gateway_managed event signing
- Add fuzzyMatchInDir for LLM-hallucinated filenames
- Add findInWorkspace support for agent dirs and ws/ directory
- Add POST /v1/files/sign endpoint for client-side URL signing
- Always save LLM span input_preview (not just verbose mode)
- Truncate previews from tail (keep recent context), limit 2000 chars
- Add exact filename hint to create_image/video/audio tool results
Desktop frontend:
- File attachments: FileButton, FilePreviewDialog, AuthImage with blob cache
- Media cache: sign URLs via API for non-ft URLs, Bearer auth fallback
- Download via Wails SaveFile binding (native Save As dialog)
- OpenFile + DownloadURL Wails Go bindings
- Traces: rewrite with span tree hierarchy, formatTokens (90.8K),
formatDuration with start/end fallback, expandable spans
- Traces: export via DownloadURL, copy with checkmark state
- Code preview: JSON = oneDark syntax highlight, text = light pre block
- MarkdownRenderer: baseUrl prop for relative image resolution
- ImageLightbox: preventDefault on keyboard nav (no macOS beep)
- ErrorBoundary: reload instead of re-render on retry
- Combobox: compact sizing (py-1.5, text-sm)
- FileButton: fix nested button HTML violation
* feat(desktop): team tasks kanban board + edition policy + file preview fixes
Phase 11 implementation:
Backend:
- TeamActionPolicy interface — lite/full edition gating for team_tasks tool
- Filter blocked actions from schema enum + early guard in Execute()
- System prompt: edition-specific team member guidance
- Skip skill_manage/publish_skill registration + seeding in lite
- teams.create: require at least 1 member
- files.go: 2-layer path isolation (workspace boundary + tenant scope)
Desktop UI:
- Kanban board with 6 status columns + framer-motion layout animation
- Task detail modal with collapsible description/result sections
- Team create dialog with styled member checkboxes
- Sidebar: teams section with create button + Lite edition badge
- Chat TaskPanel: compact active tasks with real-time WS updates
- Real-time: debounced get-light fetch (300ms) + progress patch (1s)
- Edition comparison modal (Lite vs Standard feature table)
- Custom dropdown filter replacing native select
- i18n: teams namespace (en/vi/zh)
Fixes:
- WS params: camelCase (teamId, taskId, sessionKey) matching backend
- FileButton: span wrapper (HTML nesting) + createPortal for dialog
- FilePreviewDialog: defensive filename normalization for extension check
- use-chat: prevSessionRef init null — fix blank chat on view switch
- Scrollbar: 3px auto-hide
- Agent click in sidebar returns to chat view
* feat: Update frontend build assets, change the default agent thinking level to 'low', and update Go module dependencies.
|