mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 08:17:09 +00:00
dev
506
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a9aedc843e |
fix(security): add tenant membership check to tenant-config endpoints
Prevent cross-tenant config manipulation by verifying caller has owner/admin role within the target tenant before allowing set/delete operations. Also adds MaxBytesReader, audit trail, and fixes middleware consistency between skills and builtin_tools endpoints. |
||
|
|
c5164255ef |
feat(skills): add per-tenant skill config HTTP endpoints (#428)
Add PUT/DELETE /v1/skills/{id}/tenant-config routes for managing
per-tenant skill visibility overrides. Enrich GET /v1/skills list
with tenant_enabled field when tenant-scoped. Add ListAll method
to SkillTenantConfigStore interface + PG implementation.
|
||
|
|
d3a4398e78 |
fix(gateway): sign file URLs in run.completed events for real-time display
OnEvent callback only handled map[string]string payloads, but run.completed uses map[string]any (contains usage map + media slice). File URLs in content were never signed, and media paths were sent as raw local paths without ?ft= hash tokens. Switch on payload type to handle both shapes: sign content URLs and convert media local paths to /v1/files/basename?ft=hash. |
||
|
|
7382aceb7e |
refactor(pkg-helper): extract hardcoded GID to constant and log chown errors
Replace magic number 1000 with goclawGID constant across all chown calls. Add slog.Warn on chown failures instead of silently ignoring them, improving debuggability for permission issues in production. |
||
|
|
18824f841d |
fix(pkg-helper): restore group ownership on apk-packages persist file (#456)
* fix(pkg-helper): restore group ownership on apk-packages persist file persistRemove() uses write-to-temp-then-rename which creates the new file as root:root. The goclaw process (uid 1000, gid 1000) can't read it, causing ListInstalledPackages to return nil for system packages. After any package uninstall, the UI shows an empty package list. Fix: chown listFile to root:goclaw (0:1000) after rename in persistRemove and after initial creation in persistAdd. * fix(ui): reset uninstall success icon to trash after timeout After uninstalling a package, the success checkmark icon stayed permanently instead of reverting to the delete (trash) icon. Add setTimeout to reset actionStatus from "success" to "idle" after 2s, matching the existing pattern used for error states. |
||
|
|
81c8414423 |
refactor(store): fix goroutine safety issues across cron, sessions, skills, and gateway
- Replace 16x bare db.Query/db.Exec with context-aware variants to prevent orphan DB operations when parent context is cancelled - Thread baseCtx through PGCronStore lifecycle (Start/Stop) for all internal scheduler, executor, and job cache operations - Add ctx parameter to StoreMissingDeps and ListSystemSkillDirs, update all callers - Add sync.WaitGroup tracking to fire-and-forget goroutines in gateway consumer handlers (subagent announce, teammate message) with safego.Recover panic protection - Add safego.Recover to 3 Slack channel goroutines to prevent WaitGroup hang on panic |
||
|
|
9ee82be219 |
feat(ui): add file upload to Team Workspace and Storage pages
Allow admins/operators to upload files via the web UI so agents can
read them with read_file during collaboration.
Backend:
- POST /v1/teams/{teamId}/workspace/upload — multipart upload with
team membership check, tenant isolation, blocked extensions, 10MB
limit, 100-file quota, symlink escape prevention, and real-time
EventWorkspaceFileChanged broadcast.
- POST /v1/storage/files — admin-only storage upload with protected
dir rejection and size cache invalidation.
- Export MaxFileSizeBytes, MaxFilesPerScope, IsBlockedExtension from
tools package for reuse in HTTP handlers.
Frontend:
- Reusable FileUploadDialog component (drop zone, multi-file,
per-file status, client-side validation).
- Upload button added to Team Workspace dialog (scope-aware) and
Storage page (folder-aware).
- i18n strings for en/vi/zh.
|
||
|
|
6bfbfe06c1 |
fix(slack): enable thread-based session isolation for DMs (AI Panel support) (#449)
Remove the !isDM restriction in the Slack handler, allowing thread_ts to be propagated for DMs. Add BuildScopedThreadSessionKey for string-based thread IDs (Slack timestamps) and update the message consumer to detect thread-scoped local_key values and override the session key. This enables Slack's AI Panel "New Chat" to correctly start fresh GoClaw sessions with clean history. Closes #435 Co-authored-by: Erudition <erudition@users.noreply.github.com> |
||
|
|
7aa47d1baf |
refactor(security): remove cross-tenant bypass, enforce strict tenant isolation
- Add RoleOwner ("owner") to permissions hierarchy (above admin)
- Replace all IsCrossTenant/client.IsCrossTenant() permission guards with
IsOwnerRole/client.IsOwner() role-based checks (20+ locations)
- Remove cross-tenant bypass from tenantClauseN SQL helper — always adds
AND tenant_id = $N (fail-closed)
- Add explicit unscoped store methods for startup: ListAllProviders,
ListAllInstances, ListAllEnabled, GetByIDUnscoped, GetTeamUnscoped
- Remove CrossTenant from auth layer: owner users get role "owner" with
concrete tenant ID (MasterTenantID fallback), no bypass flag
- Remove crossTenant field from WS Client; connect response sends
is_owner instead of cross_tenant
- Simplify event filter from 3-mode to 2-mode (tenant-scoped + owner sees system events)
- Fix channel instance loader to use inst.TenantID for agent lookup
- Fix agent embedding goroutine to capture tenant from caller context
- Fix provider cache invalidation to use event.TenantID
- Separate skills store: ListAllSystemSkills (system only) vs
ListAllSkills (system + tenant-scoped), StoreMissingDeps restricted
to system skills only
- Frontend: isCrossTenant → isOwner, add "owner" to UserRole type,
RequireAdmin uses hasMinRole for owner > admin hierarchy
|
||
|
|
651072a9ea |
feat(config): add system_configs DB table with per-tenant isolation and System Settings modal
- Add system_configs table (migration 029) with per-tenant key-value config - Add SystemConfigStore interface with strict tenant isolation (no cross-tenant fallback) - Add ApplySystemConfigs overlay: DB values → in-memory cfg at startup + after save - Add System Settings modal (navbar gear icon) with 3 sections: - Embedding: verified 1536d models (OpenAI, OpenRouter, Mistral), custom verify endpoint - UX Behavior: tool_status, block_reply, intent_classify toggles - Pending Compaction: provider/model + threshold/keepRecent/maxTokens - Fix block_reply label inversion: ON = delivers intermediate text (not suppresses) - Fix embedding status endpoint to read from system_configs DB (not provider JSONB) - Fix config page save to only sync current tenant (not all tenants) - Fix bus event payload to use fresh context (prevent canceled request context) - Remove config.json fallback for embedding provider/model resolution - Add "More Config" link in modal footer to full config page - Add i18n for system-settings namespace (en/vi/zh) |
||
|
|
c43a8f125b |
fix(security): harden MCP bridge auth and add startup security warnings (#306)
MCP bridge now returns 403 when no gateway token is configured instead of serving tools without authentication. Startup warnings added for default Postgres password and open CORS configuration. |
||
|
|
aadc4d6498 |
feat(teams): smart post-turn task decision, stale detection, and notification guardrails
- Add TaskActionFlags context-based tracking for member tool calls (Completed, Reviewed, Escalated, Progressed, Commented, Claimed) - Replace aggressive auto-complete with flag-based switch/case: tool-completed → skip, reviewed → skip + renew lock, progress-only → renew lock (don't auto-complete), no flags → auto-complete (backward compat) - Fix executeReject to use RejectTask (strict in_review guard) with auto re-dispatch that preserves dependent task blocking - Add ticker Phase 4: mark in_review tasks stale after 4 hours - Add ticker Phase 5: fix orphaned blocked tasks (all blockers terminal) - Keep owner_agent_id during lock recovery for auto re-dispatch - Add RunKind="notification" to team notify inbound messages with mutation blocking in TeamTasksTool (read-only enforced) - Increase MessageBus buffer 500 → 1000 - Add ResetTaskStatus support for cancelled and in_review sources |
||
|
|
a7c34a6087 |
fix: address 4 bugs from issue #294 — context propagation, cron reliability, crash safety
1. Stale user_context_files after agent file update (#294.1)
- Added PropagateContextFile() to AgentStore — single CTE query copies
agent-level file to all existing user instances
- Added `propagate` param to agents.files.set WS handler
- Response includes `propagated` count
2. Cron session reuse — tool errors block future runs (#294.2)
- Reset + Save cron session before each run
- Each run starts clean, persisted to DB to survive restarts
3. Write-behind cache risk — crash loses messages (#294.3)
- Added periodic checkpoint flush every 5 tool iterations
- Messages persisted to session incrementally during long runs
- Token calibration tracks checkpointed messages correctly
4. Silent cron delivery failures (#294.4)
- Added slog.Warn when deliver=true but channel/chatID missing
Closes #294
|
||
|
|
e1cb5c411b |
refactor(http): move gateway token to package-level auth state
Replace per-handler `token string` field with package-level `pkgGatewayToken` via `InitGatewayToken()`, matching the existing pattern for `pkgAPIKeyCache`, `pkgOwnerIDs`, etc. - Remove `token` from ~30 HTTP handler structs and constructors - Simplify `requireAuth(minRole, next)` signature (was 3 params) - Simplify `resolveAuth(r)` signature (was 2 params) - Rename `resolveAuthBearer` → `resolveAuthWithBearer` (clearer) - Add `store.WithRole()`/`RoleFromContext()` to propagate caller role through context - Inject role into context in `requireAuth` and `requireAuthBearer` |
||
|
|
6e80a1af61 |
fix(skills): unblock agent read_file on skill paths and fix web UI file browser
- Add builtinSkillsDir + tenant-scoped dirs to read_file AllowPaths - Use DB file_path in HTTP skill file handlers instead of hardcoded baseDir - Add bundled dir fallback (system skills only) when managed copy is missing - Tenant-scope publish_skill tool destination directory - Extract walkSkillFiles/readSkillFile/skillSlugDir helpers (DRY) - Return is_system from GetSkillFilePath for security-guarded fallback |
||
|
|
4868f6c4d8 |
feat(gateway): add version update checker (#374)
* feat(gateway): add version update checker Check GitHub releases periodically (1h) and surface update availability in the health endpoint + dashboard header. Version is auto-detected from git tags via Makefile, falling back to VERSION file or build arg. Backend: new UpdateChecker goroutine, health response includes latestVersion/updateAvailable/updateUrl fields. Frontend: version badge in overview header with clickable "available" link when a newer release exists. * fix(gateway): fix update checker compile errors and harden GitHub API call - Export UpdateChecker/NewUpdateChecker to match server.go references - Move StartUpdateChecker(ctx) after ctx declaration in gateway startup - Add User-Agent header for GitHub API compliance - Limit response body to 1MB via io.LimitReader - Use strconv.Atoi instead of manual int parsing in parseSemver --------- Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com> Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
ae6615d962 |
feat(teams): blocker escalation, audit events, review workflow + docs
- Add comment_type column (note/blocker) with migration 000028 - Blocker comments auto-fail task, cancel member session via EventTeamTaskFailed broadcast, and escalate to leader via InboundMessage (system:escalation bypasses debounce) - Enrich audit events: fix tenant scope bug (context.Background → WithTenantID), add 4 missing types (commented/progress/updated/stale), populate data field (reason, comment_text, progress_percent) - Add blocker escalation config toggle in team settings UI - Add audit logs modal with paginated team events (Clock icon) - Update features modal: all features now available (no coming soon) - Update code comments: reviewer role not yet active, all flows via leader - Update docs: remove v1/v2 versioning, add blocker escalation and review workflow sections, document enriched audit events |
||
|
|
8d3eb5bb6f |
feat(teams): task hints for weak models + force v2
- Add lead hint in search/list results with member+model info and task creation guidelines (description quality, complexity, model matching) - Add member instructions in dispatch message (progress, comment, complete) - Include member comments and attachments in announce content to leader - Cap announce content at 50k runes to prevent context blowup - Remove duplicate comment block in announce path - Force all teams to v2: remove IsTeamV2 gate, v2Actions map, isConsumerTeamV2, and v1 TEAM.md branch - Replace version comparison modal with features showcase modal - Remove version selector from team settings UI |
||
|
|
cd022699f6 |
feat: multi-tenant isolation — complete implementation (#359)
* feat(security): multi-tenant user data isolation (Plan 1)
Comprehensive user data isolation for non-owner system users:
- API key identity binding: owner_id column forces user_id on auth,
prevents spoofing via X-GoClaw-User-Id header
- Sessions: ownership checks on list/preview/patch/delete/reset,
non-admin users see only their own sessions
- Cron: user_id filtering on list, ownership checks on mutations
- Server-side WS event filtering: agent/chat/session/cron/team events
scoped per-user instead of broadcast to all clients
- Web UI role guards: RequireAdmin on 15 admin-only pages, role
propagated from WS connect response to auth store
- Tracing/activity: user_id enforcement for non-admin HTTP callers
- Teams: HasTeamAccess membership checks on get/delete/list
- Skills: fail-closed ownership check (deny non-admin if store
doesn't support owner lookup)
- HTTP auth: requireAuthBearer now enforces owner_id + user context
for file/media downloads (was missing)
- Dead code: removed delegation_history, handoff_routes tables and
all related handlers/store code
- New: team_user_grants table for user-to-team access control
Migration 000026: api_keys.owner_id + team_user_grants + DROP legacy tables
* feat(security): multi-tenant foundation — tenants table, tenant_id propagation, permission cache (Plan 2)
Add tenant isolation infrastructure across the entire gateway:
Schema (migration 000027):
- Create tenants + tenant_users tables with master tenant seed
- Add tenant_id column to 30 user-scoped tables (NOT NULL DEFAULT master)
- api_keys.tenant_id nullable (NULL = system-level cross-tenant key)
- Create builtin_tool_tenant_configs + skill_tenant_configs for per-tenant overrides
- Drop custom_tools table (agent loop integration never wired)
Store layer:
- TenantStore interface + PGTenantStore (CRUD tenants + tenant_users)
- TenantID field on AgentData + APIKeyData
- tenant_id in agents/api_keys/skills SQL (Create, Get, List)
Context propagation:
- WithTenantID/TenantIDFromContext (uuid.Nil = fail-closed)
- WithCrossTenant/IsCrossTenant (owner/system admin flag)
Auth tenant resolution:
- HTTP: resolveAuthBearer sets TenantID/CrossTenant on all 5 auth paths
- WS: handleConnect sets tenantID/crossTenant on Client
- API key 2-tier: NULL = cross-tenant (system), set = tenant-scoped
Runtime isolation:
- Event bus: TenantID field on Event, fail-closed filter in event_filter.go
- Cron: tenant context injected in RunJob handler
- Subagent: tenant validation prevents cross-tenant spawn
- Security logging: tenant_id in auth resolution logs
Tenant management:
- WS RPC: 7 methods (tenants.list/get/create/update, tenants.users.*)
- HTTP: 7 endpoints (/v1/tenants/*)
- Slug validation + path traversal prevention
- Role validation (owner/admin/operator/member/viewer)
Infrastructure:
- PermissionCache: 4 sub-caches (tenant resolve, role, agent access, team access)
- tenant_paths.go: filesystem path helpers with master-tenant backward compat
- i18n: MsgInvalidRole key + translations (en/vi/zh)
Dead code removed: custom_tools store, HTTP handler, DynamicToolLoader (-828 lines)
* feat(security): tenant query filtering + workspace isolation (Plan 3)
Add WHERE tenant_id filtering to all 30+ tenant-scoped store queries,
wire workspace filesystem isolation, and harden restrict_to_workspace.
Store query filtering:
- Add tenantClauseN/tenantIDForInsert/requireTenantID helpers
- Filter all SELECT/INSERT/UPDATE/DELETE by tenant_id for non-cross-tenant
- Refactor SessionStore.GetOrCreate and CronStore.AddJob/ListJobs to
accept context.Context for tenant propagation
- System skills (is_system=true) bypass tenant filter for all tenants
- Special cases: GetByKey (channels), GetByHash (auth) skip filter
Workspace isolation:
- Resolver computes tenant-scoped workspace + dataDir for non-master tenants
- Add WithTenantSlug/TenantSlugFromContext to context propagation
- Add TenantStore + Workspace to ResolverDeps
- Force effectiveRestrict() to always return true (multi-tenant security)
- Remove restrict_to_workspace from agentAllowedFields
UI cleanup:
- Remove custom-tools pages, types, routes, constants (backend removed in Plan 2)
- Clean tool-name-select component of custom tools references
* feat(security): session ctx propagation + execMapUpdate tenant guard (Plan 4)
Session store:
- Add ctx to AddMessage, SetSessionMetadata, SetAgentInfo, List, Save
- List now filters by tenant_id for non-cross-tenant callers
- Save uses ExecContext for cancellation support
- All ~15 callers updated to pass ctx
execMapUpdate tenant guard:
- Remove deleted_at IS NULL from execMapUpdateWhereTenant (only agents has soft-delete)
- Migrate 8 callers to execMapUpdateWhereTenant: agent_links, channel_instances,
mcp_servers, secure_cli, tracing, teams, skills_crud, cron_update
- Add ctx to UpdateSkill, UpdateJob interfaces + all callers
Deferred: cron scheduler global cache (correct by design — system process),
browser per-tenant isolation (separate plan).
* refactor(store): add context.Context to all SessionStore interface methods
Complete ctx propagation across all 24 SessionStore methods for:
- Future tenant-aware DB operations
- Request cancellation/timeout support
- Distributed tracing capability
Updated ~15 files including all callers in agent loop, gateway methods,
heartbeat ticker, tools, and CLI commands.
* fix(security): remove context.Background() shadowing in gateway handlers
Critical fix from code review: gateway agent handlers (create, update,
delete, identity, files, links, teams) were creating ctx := context.Background()
which shadowed the handler's ctx that carries tenant_id. This breaks
tenant-scoped agent queries for non-master tenants.
- Remove ctx shadowing in 7 agent handler files
- Add ctx param to resolveAgentUUID/resolveAgentInfo helpers
- Use store.WithCrossTenant in resolver (system-level operation)
* feat(security): tenant-scoped UNIQUE constraints for multi-tenant isolation
Update UNIQUE indexes to include tenant_id, allowing same names across tenants:
- agents: (agent_key) → (tenant_id, agent_key) WHERE deleted_at IS NULL
- sessions: (session_key) → (tenant_id, session_key)
- skills: (slug) → (tenant_id, slug)
- mcp_servers: (name) → (tenant_id, name)
- channel_contacts: (channel_type, sender_id) → (tenant_id, channel_type, sender_id)
Code changes:
- GetByKey now filters by tenant_id (same pattern as GetByID)
- ON CONFLICT clauses updated for sessions and skills
- Channel consumer uses WithCrossTenant for agent resolution
- Down migration restores original constraints
* fix(security): close remaining tenant isolation gaps from final audit
Critical fixes:
- gateway_setup: WithCrossTenant for default agent lookup at startup (C6)
- channel_contacts: ON CONFLICT updated to (tenant_id, channel_type, sender_id) (Q15)
- agents.Delete: tenant filter on DELETE (Q1)
High priority fixes:
- agents: List, GetDefault, ShareAgent, RevokeShare, ListShares, CanAccess,
ListAccessible, Update unset-default — all now tenant-scoped
- skills_crud: DeleteSkill now takes ctx, verifies tenant ownership
- mcp_servers, channel_instances, secure_cli: Delete methods tenant-scoped
- WithCrossTenant added to: gateway team notifications, team_tool_cache,
pending_messages GetDefault
* fix(migration): add tenant_id to usage_snapshots unique index
Update idx_usage_snapshots_unique to include tenant_id, preventing
cross-tenant upsert collisions when different tenants have agents
with same provider/model/channel combination.
* feat(security): cron tenant guard + browser per-tenant isolation
Phase 3 — Cron API tenant guard:
- Add ctx to 5 CronStore methods (GetJob, RemoveJob, EnableJob, RunJob, GetRunLog)
- All API-facing cron ops now filter by tenant_id (prevents cross-tenant CRUD)
- RemoveJob/EnableJob return "not found" on tenant mismatch (no enumeration)
- GetRunLog JOINs cron_jobs for tenant filtering
- UpdateJob internal reads scoped by tenant (defense-in-depth)
- Scheduler-internal methods (GetDueJobs, refreshJobCache) unchanged (system-level)
Phase 4 — Browser per-tenant isolation:
- Per-tenant incognito browser contexts via rod Incognito() (separate cookie jars)
- All page access (Snapshot, Screenshot, Navigate, Click, Type, etc.) validated
via getPageForTenant — blocks cross-tenant access by targetID
- OpenTab creates pages in tenant's incognito context
- ListTabs scoped to tenant's incognito context
- ConsoleMessages validates page ownership
- Stop/reconnect properly cleans up incognito contexts
* feat(security): isolation gaps + per-tenant config (Plan 5)
Part A — Isolation Gap Fixes:
- Merge migration 028 into 027: add tenant_id to llm_providers +
config_secrets, fix UNIQUE constraints for paired_devices +
channel_instances
- providers.go: tenant filtering on all CRUD queries
- config_secrets.go: ON CONFLICT (key, tenant_id)
- pairing_store: add ctx to all 7 interface methods, remove hardcoded
MasterTenantID, update ~15 channel caller files
- Session cache: prefix keys with tenantID to prevent cross-tenant
collision. DB queries (loadFromDB, Save, Delete, LastUsedChannel)
add tenant filter
- config_permissions cache: prefix keys with tenantID
- Cron ListJobs: fail-closed when tenant context missing
Part B — Per-Tenant Configuration:
- Provider Registry: compound key tenantID/name with fallback to
master tenant. GetForTenant/ListForTenant/RegisterForTenant
- Resolver: uses tenant-aware provider lookup + disabled tools query
- Agent loop: filter disabled tools from LLM tool definitions
- Builtin tool tenant configs: store interface + PG implementation +
PUT/DELETE HTTP endpoints
- Skill tenant configs: store interface + PG + ListAccessible LEFT
JOIN to exclude disabled skills per tenant
- OAuth: DBTokenSource with tenantID field for tenant-scoped token
refresh
- All HTTP provider handlers use RegisterForTenant/UnregisterForTenant
* feat(security): channel tenant propagation + MCP per-user credentials (Plan 6)
- Propagate tenant_id from channel_instances through BaseChannel →
InboundMessage → agent loop context (fixes 5-point break in tenant flow)
- Inject tenant context in WS router dispatch for all gateway methods
- Add MCP per-user credential overrides (api_key, headers, env) with
AES-256-GCM encryption and HTTP API endpoints
- Rewrite MCP pool with tenant-scoped keys, slot semaphore, idle eviction,
and credential rotation support (Evict per tenant+server)
- Bypass pool for users with custom credentials (separate connections)
- Fix MCP APIKey never passed to connections (inject as Authorization header)
* fix(security): close remaining tenant isolation gaps from Plan 1-6 audit
- Add tenant_id to 6 missing tables: agent_context_files,
skill_agent_grants, mcp_agent_grants, team_tasks, spans,
embedding_cache (migration 027)
- Fix tid==uuid.Nil fallback to fail-closed (return error) in 8 update
methods: agent_links, teams, skills, channel_instances, secure_cli,
cron, mcp_servers, tracing
- Add tenant filter to bare DELETEs: DeleteLink, DeleteTeam
- Add tenant filter to queries: ListChildTraces, GetMonthlyAgentCost,
CountAgentGrantsByServer, ListAccessible (MCP), ReviewRequest,
ResolveGroupTitles, buildTraceWhere
- Fix missing tenant_id in INSERTs: CreateSkill, GrantToUser,
ReviewRequest grant INSERTs
- Add tenant filter to api_keys: List, Revoke, Delete
- Fix cron scanJob/RemoveJob/EnableJob fallthrough patterns
* fix(security): inject tenant context into channel handler entry points
Channel handlers used context.Background() which lost tenant context,
causing store operations to either fail-closed or default to master
tenant. Now all 10 handler entry points inject tenant from BaseChannel.
* fix(security): tenant filters for teams, tasks, skills (Plan 6b audit)
- Teams: add tenant filter to GetTeamForAgent, ListMembers,
ListIdleMembers, KnownUserIDs (JOIN agent_teams for tenant check)
- Teams: add tenant_id to GrantTeamAccess INSERT, tenant filter to
RevokeTeamAccess, ListTeamGrants, HasTeamAccess
- Team tasks: add tenant_id to CreateTask INSERT, fail-closed
UpdateTask, tenant filter on all 7 query/delete methods
- Skills: add tenant filter to RevokeFromAgent, ListAgentGrants
- Skills: add ctx param + tenant filter to ToggleSkill
- History: annotate context.Background() locations with TODOs for
future tenant injection (requires PendingHistory struct refactor)
* fix(security): add tenant_id to 4 missing team tables + fix INSERTs
Add tenant_id column to: agent_team_members, team_task_comments,
team_task_events, team_task_attachments (migration 027).
Fix INSERT statements to include tenant_id: AddMember,
AddTaskComment, RecordTaskEvent, AttachFileToTask.
* fix(migration): cast UUID literals in tenant_users seed + usage_snapshots index
PostgreSQL doesn't auto-cast string to UUID in SELECT and expression
index contexts. Add explicit ::uuid casts to prevent migration failure.
* docs: add multi-tenant architecture guide for integrators
Comprehensive solution doc covering auth model, WS protocol, event
system, data isolation, API reference, and integration patterns.
Target audience: developers building custom frontends or SaaS on GoClaw.
* feat(ui): multi-tenant awareness + tenant admin page (Plan 7)
Backend:
- Enrich WS connect response with tenant_name, tenant_slug, cross_tenant
- Add tenants.mine WS method (any user, returns own memberships)
- Parse tenant_hint in connect params for browser pairing multi-tenant
- Wire tenantStore to MethodRouter for connect-time tenant lookup
Frontend:
- Auth store: tenantId, tenantName, tenantSlug, isCrossTenant, availableTenants
- WS client: capture tenant fields from connect, send tenant_hint
- WS provider: auto-fetch tenants.mine on connect
- useTenants() shared hook for all tenant-aware components
- Tenant indicator in sidebar connection status
- Tenant admin page (/admin/tenants) with list + create dialog
- Tenants nav in sidebar (cross-tenant admin only)
- i18n: tenants namespace (en/vi/zh)
- Type updates: tenant_id on AgentData, ApiKeyData
* refactor(ui): move tenant selector into user menu dropdown in topbar
Replace simple logout button with a Radix Popover user menu showing:
- User ID display
- Tenant selector (when multi-tenant: list all tenants with check mark)
- Logout button
Remove tenant indicator from connection-status.tsx (now in topbar).
Tenant switch saves slug to localStorage and reloads for reconnect.
* feat(ui): add logout confirmation dialog
Show destructive confirm dialog before logout via ConfirmDialog
component. Added logoutConfirm i18n key for en/vi/zh.
* fix(ui): security hardening — hide admin nav, fix route guard, fix refresh
- Hide System nav group for non-admin roles in sidebar (was visible to all)
- Replace RequireAdmin with RequireCrossTenant guard on /admin/tenants route
- Add RequireCrossTenant component to require-role.tsx
- Fix refresh button animation: use isFetching instead of isLoading
- Clean up connection-status.tsx (remove tenant indicator, now in topbar)
* feat: cross-tenant admin tenant scope selector
Backend: add tenant_scope connect param. Cross-tenant clients can
narrow their scope to a specific tenant (slug). applyTenantScope()
sets client.tenantID and clears crossTenant flag.
UI: user menu shows "All Tenants" option for cross-tenant admins.
Selecting a tenant saves slug to localStorage as tenant_scope,
reload reconnects with narrowed scope. "All Tenants" clears scope.
* feat: provisioning API key scope + tenant detail page (Plan 8)
Backend:
- Add operator.provision scope for limited tenant management
- Add HasScope() method to gateway Client
- Allow provision-scoped keys to create tenants + add users
- Allow provision-scoped keys to create tenant-bound API keys
Frontend:
- Tenant detail page with user management (list, add, remove)
- Clickable tenant list rows navigate to detail
- i18n: tenant detail keys (en/vi/zh)
- Route /admin/tenants/:id with RequireCrossTenant guard
* fix: tenant scope keeps admin privileges + UI pattern fixes
Backend:
- applyTenantScope keeps crossTenant=true (retains admin features)
- Router: scoped cross-tenant injects WithTenantID (filters data)
while keeping admin role for method access
UI:
- Fix "All Tenants" check mark (compare against nil UUID string)
- Fix tenant label when scope active (show selected tenant name)
- Use ConfirmDialog for user removal (was hand-rolled)
- Add DialogDescription to add-user dialog (Radix a11y)
- Fix table min-w-[600px] consistency
- Fix column header mismatch (was "role", should be "created")
* fix(ui): clean up tenant detail header — remove redundant info panel
Remove duplicate slug/status/created panel. Info now shown in
PageHeader description (slug + date). Status badge removed (redundant
with description). Cleaner, consistent with other admin pages.
* fix(ui): redesign tenant detail with info cards + user cards
* feat(ui): tenant selection gate — require tenant before app access
- Add tenantSelected flag to auth store (persisted via localStorage)
- WS provider auto-selects: single-tenant user auto, cross-tenant
admin defaults to "All Tenants", zero-tenant user blocked
- RequireAuth gate: redirect to /select-tenant when connected but
no tenant selected
- New TenantSelectorPage: centered card layout matching login page,
"All Tenants" amber card for cross-tenant admin, per-tenant cards
with role badges, no-access state with logout button
- i18n: selectTenant, noAccess keys (en/vi/zh)
* fix(security): scope events for cross-tenant admin with tenant_scope
Event filter was checking !crossTenant before filtering — scoped
cross-tenant admins (crossTenant=true + tenantID set) bypassed
tenant event filtering. Now checks tenantID != Nil regardless of
crossTenant flag, ensuring scoped admins only see their chosen
tenant's events.
* fix(security): HTTP API now respects tenant_scope for gateway token
Root cause: UI uses HTTP API (/v1/agents, /v1/mcp/servers, etc.)
for data fetching. HTTP auth middleware with gateway token always
set CrossTenant=true with no tenant filtering. tenant_scope only
worked for WS connection, not HTTP requests.
Fix:
- HTTP client sends X-GoClaw-Tenant-Scope header from localStorage
- HTTP auth resolves header slug → tenant UUID via tenantStore
- requireAuth: CrossTenant + TenantID → WithTenantID (scoped)
- Wire InitTenantStore(pgStores.Tenants) in gateway startup
* feat(security): tenant-aware provider registry, event filter, and membership validation
- Refactor providers.Registry: Get(ctx, name) / List(ctx) extract tenant
from context via injected TenantFromCtx func (avoids circular import)
- Event filter: fail-closed 3-mode tenant filtering
Mode 1: unscoped admin sees all
Mode 2: scoped admin sees tenant events + system events
Mode 3: regular user sees only own tenant (fail-closed)
- WS connect: resolveTenantHint validates membership via GetUserRole
with PermissionCache (30s TTL, bus invalidation)
- BroadcastForTenant helper for tenant-scoped event emission
- Session list: add TenantID to SessionListOpts from context
- Cron handleRun: preserve tenant in background goroutine context
- GOCLAW_LOG_LEVEL env var (debug|info|warn|error) for Docker/K8s
- Cache debug logging: tenant_cache, permission_cache, api_key_cache
- Friendly verify error: timeout → user-readable message
- Verify timeout: 15s → 30s
* feat(ui): setup wizard improvements + agent preset enrichment
- Setup: skip link with confirm dialog, language selector (en/vi/zh)
- Setup: card padding fix (py-0 gap-0 on Card, py-5 on CardContent)
- Setup: remove duplicate skip link from layout
- Step Model: verify countdown timer (30s), stops on result
- Step Agent: default Fox Spirit preset, selected state styling,
hide agent key/name inputs, auto-derive from preset, emoji in config
- Summoning modal: elapsed timer (m:ss format)
- Agent presets: enriched prompts with human-like quirks
Fox Spirit: playful personality, care reminders
Artisan: portrait/banner/ads/logo expertise
Astrologer: reference sites (astro.com, cafeastrology, labyrinthos)
- i18n: "triệu hồi linh hồn" fix, all 3 locales updated
* feat(ui): API Key tenant support + card layout + provider chain fix
- API Key create: tenant selector for cross-tenant admin, provision scope
- API Key create: redesigned dialog with scope cards, Radix Select, icons
- API Key list: card layout with badges (status, tenant, scopes)
- API Key: shortcut in user menu (topbar)
- API Key: keep "API Key" untranslated across all locales
- Provider chain: empty state fix — skip legacy entry when provider
not found in current tenant
- i18n: form.cancel key added to all 3 locales
* fix(ui): add bottom padding to all page layouts + misc improvements
- Add pb-10 to all 24 page containers to prevent content touching
bottom edge of viewport
- Various UI polish from user modifications (summoning colors,
layout icon, agent cards, sidebar adjustments)
* feat(ui): MCP user credentials dialog + builtin tool tenant toggle
- MCP: per-user credentials dialog (api_key, headers, env KV editor)
with status badges, delete all, save
- MCP: "My Credentials" button on each server row
- Builtin Tools: per-tenant enable/disable override toggle
with "Using default" / "Enabled/Disabled for tenant" badges
and reset-to-default button
- Setup: larger logo (h-16) and bolder title (text-4xl font-bold)
- i18n: all keys added to en/vi/zh for both features
* fix(ui): API key card spacing + remove pagination border
- Card padding: px-4 py-3.5 (was px-3 py-2), rows spaced with gap-2
- Scopes on separate row from dates for readability
- Card gap: space-y-2.5 between cards
- Pagination: add className prop, remove border-t on API keys page
- Badge/icon sizes bumped to text-xs / h-3.5 (was text-[10px] / h-3)
* fix(security): comprehensive tenant isolation audit — SQL, events, cache, skills, files
Defense-in-depth hardening across 12 audit phases:
- SQL: add tenant_id WHERE to teams_tasks lifecycle/activity/followup/progress/embedding (~30 functions)
- Events: broadcastTeamEvent + task_ticker + subagent announce now carry TenantID
- Cache: agentKeyCache scoped by tenant (agent keys per-tenant, not globally unique)
- Skills: SkillStore interface accepts ctx, SQL filter (is_system OR tenant_id=$N), per-tenant list cache, GrantToAgent includes tenant_id, tenant-scoped file storage
- Files: StorageHandler/FilesHandler/TeamAttachments/teamWorkspaceDir use config.TenantDataDir/TenantTeamDir
- Security: HMAC signed file tokens (file_token.go) replace gateway token in URLs
- Audit: AuditEventPayload carries TenantID for async subscriber tenant scoping
- InboundMessage: subagent/dispatch/validation/session_send propagate TenantID
- Pending messages: DeleteStale scoped by tenant
* fix(security): skip gateway token in URLs with signed file tokens
toFileUrl() now skips appending ?token=GATEWAY_TOKEN when the URL
already contains ?ft= (HMAC signed file token). Prevents gateway
token exposure via browser history, logs, and referrer headers.
* fix(security): stop persisting auth tokens in session media URLs
mediaToMarkdown() now stores clean paths (/v1/files/path) without
any auth tokens. Previously embedded ?token=GATEWAY_TOKEN (or ?ft=)
into markdown which gets persisted in session messages DB.
Frontend toFileUrl() adds auth at render time — tokens never stored.
* fix(security): migration 027 strips leaked gateway tokens from session URLs
Adds cleanup step to tenant foundation migration: removes ?token=xxx
from persisted media URLs in session messages. Old code embedded the
gateway token; new code stores clean paths only.
* fix(security): sign file URLs at delivery time, not persist time
Add SignFileURLs() utility that finds /v1/files/ and /v1/media/ URLs
in content and appends HMAC signed ?ft= tokens before delivery.
Applied at 4 delivery points:
- WS agent events (OnEvent callback in gateway_managed.go)
- WS chat.history response
- WS sessions.preview response
- HTTP /v1/chat/completions response
Sessions store clean paths only. Tokens are generated per-delivery
with 1h TTL — never persisted in DB. Frontend toFileUrl() skips
appending gateway token when ?ft= is already present.
* fix: file token verify path must match signed path (/v1/files/ prefix)
SignFileURLs() signs the full URL path "/v1/files/{path}" but the
verify in files.go auth() was using "/{path}" (without prefix).
HMAC mismatch caused all signed file tokens to return 401.
* fix(security): scope storage size cache per-tenant
sizeCache was a single global entry — all tenants shared one cached
size. Changed to sync.Map keyed by tenantBaseDir so each tenant gets
its own cached size calculation.
* feat(ui): redesign API keys page — table layout + code snippet dialog
Replace card-based API keys list with table layout matching MCP Servers
pattern. Add "API Key Usage" dialog with tabbed code snippets (cURL,
TypeScript, Go) showing gateway connection examples with syntax
highlighting and copy-to-clipboard.
* fix(builtin-tools): seed media tools disabled, fix tenant toggle, add unconfigured warning
- Seed media tools with Enabled=false and no default provider settings
(user must configure provider chain before enabling)
- Fix provider chain form ghost entries: validate provider exists in
tenant before showing (parseInitialEntries new-format path)
- Fix double toggle: show only tenant override OR global toggle, not both
- Fix list API: merge tenant_enabled from builtin_tool_tenant_configs
into response when tenant-scoped (was always null)
- Add ListAll() to BuiltinToolTenantConfigStore for full override map
- Add amber warning banner for enabled media tools missing provider config
* feat(mcp): require_user_credentials setting + KeyValueEditor for user creds
- Add require_user_credentials setting in mcp_servers.settings JSONB
- Backend: skip MCP server in LoadForAgent when user lacks credentials
- Frontend: toggle in MCP form dialog, persisted in settings field
- Redesign MCP user credentials dialog: replace raw Textarea with
KeyValueEditor (sensitive key masking for auth/token/secret fields)
- Add settings to mcpServerAllowedFields for HTTP update
* fix(security): restrict cross-tenant to owner IDs, config to owners only
- Gateway token + non-owner user ID: admin role but tenant-scoped
(no cross-tenant access). Fallback: only "system" is owner when
GOCLAW_OWNER_IDS not configured (fail-closed).
- Config page (WS config.* methods): wrapped with requireCrossTenant
middleware — non-owner admins get permission denied
- Config sidebar link: hidden for non-cross-tenant users
- Logout: clear tenant_id and tenant_hint from localStorage
(prevents tenant scope leak to next user session)
- Refactor: LOCAL_STORAGE_KEYS.TENANT_ID/TENANT_HINT constants
* fix(ui): chat bubble contrast, login logo, tenant no-access UX
- Chat bubble: use --chat-bubble-user CSS var (darker orange, L=0.50/0.52)
with text-white for WCAG AA contrast (~5.5:1)
- Login page: logo h-20 w-20, title text-3xl font-bold
- Tenant selector no-access: shield icon + hint text explaining
user needs admin to add them to a tenant
- Sidebar: GoClaw text uses text-sidebar-primary (brand color)
* feat(contacts): merge/unmerge contacts to tenant users
Add API and UI for linking channel contacts to tenant_users identity,
enabling cross-channel user identification within a tenant.
Backend:
- POST /v1/contacts/merge — link contacts to existing or new tenant_user
- POST /v1/contacts/unmerge — remove merged_id from contacts
- GET /v1/contacts/merged/{id} — list contacts by tenant_user
- GET /v1/tenant-users — list users for current tenant
- Add display_name + metadata columns to tenant_users (migration 27)
- All endpoints enforce tenant isolation via context tenant_id
Frontend:
- Checkbox multi-select on contacts table
- Selection toolbar with Merge/Unmerge buttons
- Merge dialog: link to existing user or create new
- Link2 icon indicator for merged contacts
- i18n: en/vi/zh translations for merge section
* fix(security): add tenant_id to span and embedding_cache inserts
SpanData struct was missing TenantID field — all span inserts failed
with NOT NULL constraint violation after migration 027 dropped defaults.
Fix captures tenant_id from context at emit time (6 call sites in
loop_tracing.go + subagent_tracing.go), then includes it in both
CreateSpan() and BatchCreateSpans() SQL (25→26 columns).
Also fixes embedding_cache writeEmbeddingCache() which was missing
tenant_id in its batch INSERT — same class of bug.
Both use MasterTenantID fallback for backward compatibility.
* feat: Introduce tenant switcher UI and enhance multi-tenant architecture documentation.
* fix(security): enforce tenant scoping, fix session isolation and UI cleanup
- Force cross-tenant admins to always have a concrete tenant_id (default
MasterTenantID) instead of unscoped WithCrossTenant — prevents mismatch
between session listing (no filter) and writes (MasterTenantID fallback)
- Make agent router tenant-aware: Get(ctx, agentID) resolves agent for
the caller's tenant, preventing cross-tenant agent cache collisions
- Fix context.Background() in title goroutine and summarization — now
uses tenant-aware context (WithoutCancel) so titles and compaction
persist to the correct tenant
- Add read-only SessionStore.Get() method; replace GetOrCreate in auth
checks (preview/patch/delete/reset) to prevent phantom session creation
- Inject tenant from channel instance into inbound message processing
- Remove "All Tenants" option from tenant selector, topbar switcher,
and ws-provider auto-select — admin must always operate within a tenant
- Fix contacts page selection toolbar layout shift (always rendered)
- Widen MCP credentials sensitive header regex to catch API_KEY etc.
* fix(security): propagate tenant_id in consumer handlers and background ops
- InjectTeamDispatch: use context.WithoutCancel instead of context.Background
to preserve tenant_id while avoiding cancel propagation from HTTP/WS handlers
- handleTeammateMessage/handleSubagentAnnounce: inject tenant_id from msg
- Add nil guard for outcome.Result to prevent panic on agent-not-found
- Use BroadcastForTenant for EventTeamTaskFailed/Completed/LeaderProcessing
- Remove unnecessary WithCrossTenant in autoSetFollowup (ctx already scoped)
- resolveAgentByKey: accept ctx param for tenant-scoped agent lookup
- pending_messages: use request ctx instead of cross-tenant for GetDefault
* fix(security): tenant-scope EnsureContact and PendingHistory DB operations
- All channel EnsureContact calls now use tenant-scoped ctx instead of
context.Background (whatsapp, slack, discord, telegram, feishu, zalo)
- PendingHistory: add tenantID field, thread through constructors
- All PendingHistory DB ops (load, flush, compact, delete) use tenantCtx()
- Normalize timeouts: 10s for simple queries, 15s for batch writes
* feat(teams): auto-attach media, retry completed tasks, improve tool messages
- Auto-attach workspace media from any tool (create_image/audio/video) to
team tasks via loop-level hook, not just write_file interceptor
- Store absolute paths in team_task_attachments instead of relative
- Extend retry action to support completed tasks (reopen for follow-up)
- Context-aware comment result messages with next-action guidance for
leader vs member roles and task status
- All tool results include task_id for agent follow-up actions
- Use #N "subject" format instead of raw UUIDs in tool messages
* feat(multi-tenant): tenant isolation for media, events, providers and UI
- Tenant-scoped media store, event filter, provider registry
- Tenant header propagation in WS/HTTP clients
- UI: tenant-aware chat messages, markdown renderer improvements
- Protocol: tenant error codes and event definitions
* docs: add multi-tenant architecture documentation
* fix(teams): store absolute paths in team_task_attachments
- AutoAttachWorkspaceFile: use cleanPath consistently instead of raw absPath
- executeAttach: resolve relative paths to absolute via team workspace
- AfterWrite interceptor already uses filepath.Clean (verified)
* fix(teams): attachment download handles both absolute and relative paths
filepath.Join with an absolute att.Path discards the teamBase prefix,
causing path traversal check to fail and download to serve wrong file.
Now checks IsAbs first — uses path directly for new absolute entries,
falls back to legacy join for old relative entries.
* fix(teams): attachment download validates against workspace root not tenant dir
Absolute paths stored in DB don't match TenantTeamDir structure
(master tenant has no tenants/ prefix). Now validates absolute paths
against dataDir (workspace root) instead. Legacy relative paths still
resolve via TenantTeamDir as before. IDOR check on att.TeamID ensures
cross-team isolation.
* fix(teams): attachment download uses workspace root, not data dir
Files are stored under GOCLAW_WORKSPACE/teams/ but handler was passed
dataDir (GOCLAW_DATA_DIR) — completely different directory. Now passes
workspace. Legacy relative paths resolve via {workspace}/teams/{teamID}/{chatID}/{path}.
* fix(security): use HMAC-signed file tokens for attachment downloads
Replace gateway token exposure (?token=) with HMAC-signed short-lived
file tokens (?ft=) for team task attachment downloads — same mechanism
used by chat file URLs.
Backend:
- team_attachments auth: accept ?ft= signed token (priority 1), Bearer (priority 2)
- teams_tasks RPC: sign download_url with HMAC at delivery time
- Add fileTokenSecret to TeamsMethods, thread through wireChannelRPCMethods
Frontend:
- Use server-signed download_url from attachment data instead of ?token=
- Remove useAuthStore dependency from task-detail-dialog
* fix(security): decouple file token signing from gateway token
- Generate random 256-bit HMAC key at startup (crypto/rand, memory-only)
- All file signing/verification uses FileSigningKey() instead of gateway token
- Remove ?token= query param fallback from /v1/files/, /v1/media/, attachments
- Only ?ft= signed tokens and Bearer header accepted for file access
- Reduce file token TTL from 1h to 5min
- Frontend: remove gateway token from all file URLs and imports
- Note: tokens invalidate on restart (acceptable for 5min TTL + WS reconnect)
* fix(ui): use signed download_url for task attachments
- Add download_url to TeamTaskAttachment type
- Use a.download_url (server-signed ?ft=) instead of bare URL
* fix(security): tenant-scope team workspace paths + show user/tenant in topbar
- WorkspaceDir callers now use config.TenantWorkspace() to resolve
tenant-scoped base dir (non-master tenants get workspace/tenants/{slug}/)
- Fixes: all tenants previously wrote to global /app/workspace/teams/
without filesystem-level isolation
- Affected: loop.go (agent run), team_tasks_mutations.go (task creation)
- teams_workspace.go already correct (uses TenantTeamDir)
- UI topbar: show "userId (tenantName)" in user menu
* feat(ui): redesign task detail dialog with improved UX
- Split monolithic 343-line component into 5 focused files
- New header: subject as title, identifier + status badges above
- Metadata grid with soft bg-muted/30 background, priority icons
- Attachments: card-style with mime-type icons + proper download Button
- Description/Result: markdown rendering via MarkdownRenderer
- Comments: avatar circles + markdown rendering for content
- All sections collapsible with chevron + count badge
- Timeline: vertical dot-line pattern, collapsed by default
- Fix kanban card hover layout shift (opacity instead of display toggle)
* fix(security): tenant-scoped workspace paths and tool cache isolation
- Scope team workspace paths to tenant directory
- Add tenant isolation to tool cache and task reads
- Shell deny pattern improvements
- Agent resolver and context file tenant scoping
- Sidebar tenant/user display fix
- Add tests for workspace, boundary, and context file interceptor
* fix(ui): tenant visibility fallback, merge coming-soon, task detail tweaks
- Tenants page: try tenants.list (owner), fall back to tenants.mine
for regular users; hide create button for non-owners
- Merge contacts dialog: add coming-soon banner (i18n en/vi/zh),
disable form and submit button
- Task detail: collapse attachments/comments by default,
guard download_url before rendering
|
||
|
|
13139e08ca |
feat: extractive memory fallback when LLM flush fails
Add regex-based extraction as safety net when LLM memory flush returns NO_REPLY or fails. Extracts decisions, key facts, URLs, file paths, dates, and user preferences from conversation history before compaction. - Wire extractiveMemoryFallback into runMemoryFlush (NO_REPLY + LLM error) - Add memStore to Loop for direct memory writes - Use store.MemoryUserID(ctx) for correct shared memory scoping - Limit regex input to last 20 messages for performance - Drop FTS language config (tsv column is stored with 'simple', changing query language without matching stored config breaks search) Closes #328 |
||
|
|
90716a2694 |
fix(kg): complete semantic search implementation and shared KG consistency
- Add batch embedding generation in IngestExtraction (was missing, making vector search dead code) - Add BackfillKGEmbeddings for existing entities without embeddings - Add KGUserID() context helper to prevent entity duplication in shared KG mode - Add IsSharedKG handling to DeleteEntity, DeleteRelation, ListEntities, ListAllRelations, PruneByConfidence - Replace O(n²) bubble sort with slices.SortFunc in hybridMergeEntities - Fix vectorSearchEntities duplicate param (reuse same $N for SELECT and ORDER BY) - Update KG tool and memory interceptor to use KGUserID instead of MemoryUserID - Remove unrelated MemoryStore additions from Loop/Resolver (should be separate PR) - Fix comments: "team_id" → "agent-level sharing" to match actual implementation |
||
|
|
1015c9f73c |
feat: add pgvector semantic search to KG entities + team KG sharing
- Add embedding column to kg_entities with HNSW index - Generate entity embeddings during IngestExtraction - Hybrid search in SearchEntities (ILIKE 0.3 + vector 0.7) - Add team_id scope for shared KG across team members - Add IsSharedKG/WithSharedKG context helpers Closes #327 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
f3b344d731 |
fix(pkg-helper): fix apk-packages persist file not writable in Docker (#324)
The .runtime directory on the data volume may be owned by goclaw:goclaw (from older images or Docker volume initialization). pkg-helper runs as root but without CAP_DAC_OVERRIDE, so it cannot create files in goclaw-owned directories. This caused persistAdd() to fail silently — runtime-installed system packages (bash, pandoc, etc.) were lost on container recreate. Fix: set .runtime directory ownership to root:goclaw (mode 0750) so pkg-helper can write apk-packages while goclaw can still traverse. Three layers for robustness: - Dockerfile: pre-create .runtime with correct split ownership in image - docker-entrypoint.sh: fix ownership on existing volumes (upgrade path) - pkg-helper: self-healing ensurePersistDir() at startup as defense-in-depth Subdirs (pip/, npm-global/, pip-cache/) remain goclaw-owned since those are written by the app process, not pkg-helper. Fixes #323 Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com> |
||
|
|
66a8029d26 |
fix(teams): wire post-turn processor for HTTP API and wake endpoints
Post-turn team task dispatch was only wired for WS chat.send path.
Now also wired for /v1/chat/completions, /v1/responses, and
/v1/agents/{id}/wake HTTP endpoints so team tasks dispatch correctly
from all API entry points.
|
||
|
|
be770252e6 |
fix(teams): inject PendingTeamDispatch in WS chat.send path
WS handleSend called loop.Run() directly without injecting PendingTeamDispatch into context, bypassing the gate that requires search/list before team task creation (ptd was nil → gate skipped). - Add InjectTeamDispatch helper: creates ptd, returns drain func that dispatches pending tasks + releases team lock (panic-safe) - Wire PostTurnProcessor to ChatMethods via setter - defer drainTeamDispatch() in handleSend goroutine |
||
|
|
32ced98f6a |
fix(tools): add extractor chain retry/timeout, fix openai_compat media routing
- ExtractorChain now supports per-entry retry (max_retries) and chain-level timeout (context.WithTimeout), matching MediaProviderChain pattern - Low quality content breaks out of retry (not transient), errors retry - Fix ResolveProviderType: skip generic "openai_compat" DB type so OpenRouter routes via name-based inference to correct /chat/completions endpoint instead of falling through to /images/generations (caused 404) - Remove dead "openai_compat" entry from dbTypeToMediaType map - Seed data: defuddle default max_retries=2 |
||
|
|
9ad3043fe9 |
feat(ui): team leader processing indicator and announce run streaming
- Add team.leader.processing backend event (emitted from onDrain before announce run starts) so all WS clients show processing status - Capture announce run.started events to stream leader's summarization in real-time (thinking, tool calls, text) - Add leader_processing phase to ActivityIndicator and ChatTopBar - isBusy stays true during leader processing → stop button visible |
||
|
|
001fe2721c |
fix(files): fallback workspace search for generated file links
LLMs hallucinate file paths in responses (e.g. ./system/generated/file.png) when the actual file is at /app/workspace/teams/.../generated/file.png. Backend: /v1/files/ now falls back to searching the workspace directory tree by basename when exact path isn't found. Generated filenames (goclaw_gen_*) include nanosecond timestamps and are globally unique. Frontend: relative file paths now use just the basename in the /v1/files/ URL, letting the backend fallback find the actual file. Auth token appended as ?token= query param for authenticated file access. |
||
|
|
7040081756 |
feat(discord): add writer management commands (#309)
* feat(discord): add writer management commands (!addwriter, !removewriter, !writers)
Port Telegram's file writer commands to Discord with guild-wide scope.
Uses wildcard scope (guild:{guildID}:*) to match per-user contexts.
Also fixes system prompt injection to find guild-wide writers.
* fix(discord): use per-user scope for writer self-check
CheckPermission with guild-wide wildcard scope (guild:{guildID}:*)
fails to match auto-bootstrapped per-user perms. Use per-user scope
(guild:{guildID}:user:{senderID}) which matches both per-user exact
and guild-wide wildcard stored patterns via matchWildcard.
* fix(discord): deduplicate writers in prompt injection, add context timeout
- Deduplicate writers by UserID after merging guild-wide and per-user
scope results in buildGroupWriterPrompt to prevent duplicates when a
user has both grant types.
- Add 10s context timeout to Discord writer command handlers instead of
using context.Background() to prevent DB calls from hanging.
---------
Co-authored-by: viettranx <viettranx@gmail.com>
|
||
|
|
84650c5c14 |
feat(teams): attachments refactor, semantic search, improved prompting (#310)
* feat(teams): refactor attachments, remove team_message, add task comments UI Major team system refactoring: - Drop team_workspace_files, team_workspace_file_versions, team_workspace_comments, team_messages tables; replace team_task_attachments with path-based schema - Add denormalized comment_count/attachment_count on team_tasks for dashboard perf - Auto-track file writes as task attachments via WorkspaceInterceptor - Remove team_message tool entirely (tool, store, i18n, builtin_tools, MCP bridge) - Members communicate via task comments; approve/reject use comments for audit trail - Add commented/new_task notification types to TeamNotifyConfig - Enrich task completion announce with member comments - User-created tasks stay pending (backlog) — no auto-assign to leader - Configurable member request tasks (member_requests.enabled in team settings) - Structured task description template in TEAM.md for v2 leads - HTTP attachment download endpoint with IDOR + path traversal protection - Web UI: count badges on task list, comments section with input, download button - Team settings UI: completed/commented/new_task toggles, member requests section * feat(teams): priority dispatch, compact prompting, realtime comments - Priority dispatch: DispatchUnblockedTasks dispatches only 1 task per owner per round (highest priority first). Fixes cancel bug where CancelSession killed innocent queued tasks. - Prompt rework: Replace verbose Task Decomposition (25 lines) with compact Task Planning (8 lines). Add explicit UUID warning and sequencing guidance for weak models (Qwen, MiniMax). - Recent comments in dispatch: buildRecentCommentsSummary appends 3 most recent comments to re-dispatched tasks (reject, retry, stale). - Enrich comment event payload with TaskNumber, Subject, CommentText (truncated 500 runes, UTF-8 safe). - UI: Board subscribes to TEAM_TASK_COMMENTED for realtime comment_count badge updates. Task detail dialog auto-refreshes comments on event. - Tool description hint: guide models to write self-contained task descriptions with clear objectives and context. * perf(teams): add ListRecentTaskComments with SQL LIMIT Dispatch only needs 3 most recent comments — avoid fetching all. New ListRecentTaskComments(ctx, taskID, limit) uses ORDER BY DESC LIMIT N then reverses to chronological order. * feat(teams): add subject embedding for semantic task search + improve prompting - Add vector(1536) embedding column to team_tasks with HNSW index - Implement hybrid search: FTS (0.3) + cosine similarity (0.7) with graceful fallback - Auto-generate embeddings on task create/update, backfill existing tasks on startup - Wire embedding provider into PGTeamStore via gateway_setup - Change FTS from OR to AND with prefix matching for precise keyword search - Reduce search page size from 30 to 5 to save tokens - Rename migration 000023 → 000024, bump RequiredSchemaVersion to 24 - Update TEAM.md hints: prefer search over list, batch task creation with blocked_by - Add anti-pattern examples to prevent sequential task creation |
||
|
|
38dfcf8bb0 |
feat(tools): add Defuddle extractor chain for web_fetch (#296)
Add Cloudflare Worker (fetch.goclaw.sh) as primary markdown extractor with waterfall fallback to built-in HTML→Markdown converter. Architecture: - ExtractorChain pattern with quality gate (min 100 chars, 10 words) - Settings stored in builtin_tools DB table (not config.json5) - ResolveExtractorChain reads chain from context per-request - InProcessExtractor delegates to fetchRawContent (full SSRF + domain policy checks on redirects) - DefuddleExtractor with configurable base_url + timeout - Seed default chain [defuddle, html-to-markdown] for new deployments - Backfill migration for existing deployments Web UI: - Dedicated DnD extractor chain form on builtin tools page - Drag-and-drop ordering, enable/disable per extractor - Timeout + base URL config for Defuddle - i18n support (en/vi/zh) |
||
|
|
b2330086ad |
fix(agent): harden intent classify — stricter quickClassify, steer vs newTask split
- Reduce quickClassify threshold from 60 bytes to 15 runes to prevent false positives (e.g. "làm đơn giản thôi" no longer matches "thôi") - Use utf8.RuneCountInString for accurate Unicode length check - Replace substring match with whole-word boundary check to avoid false positives like "nonstop" matching "stop" - Remove statusKeywords; only exact "?" is fast-pathed as status query, all other status queries go through LLM classification - Increase LLM classify timeout from 5s to 10s - Differentiate steer (inject into running loop) from newTask (queue behind active run) instead of treating both as mid-run injection |
||
|
|
9f80842bbc |
feat(telegram): add voiceguard error sanitization and STT concurrency control (#33)
Cherry-pick two features from PR #33: - Voiceguard: intercept technical errors (rate limits, tool failures, exit codes) in voice agent replies and replace with user-friendly fallback messages. Configurable error markers and fallback templates via TelegramConfig. - STT: shared HTTP client with connection pooling (sync.Once) and concurrency semaphore (max 4 concurrent calls) to prevent STT proxy overload. |
||
|
|
4da85913a2 |
feat(chat): auto-generate conversation titles via lightweight LLM call
After the first agent run completes, generate a short title from the user's message using a 10s/50-token LLM call. Title is stored in the session label column and pushed to clients via session.updated event. |
||
|
|
78ffc57a8e |
fix(ws): propagate origin_session_key for team task announces
Team task announces rebuilt session keys via BuildScopedSessionKey(), producing different format than WS sessions (ws:direct:uuid vs ws-userId-ts). Now stores and forwards origin_session_key through task metadata and dispatch, matching subagent announce pattern. Also adds "ws" to InternalChannels to suppress spurious warnings. |
||
|
|
bb783b952c |
feat(feishu): add list_group_members tool + native @mention support (#280)
* feat(feishu): add list_group_members tool + native @mention support
Add agent tool to list all members in a Lark group chat via
GET /open-apis/im/v1/chats/{chat_id}/members with auto-pagination.
- New tool `list_group_members`: returns member IDs + display names
- Native @mention conversion: @ou_xxx in outbound messages becomes
real Lark mentions (post: "at" element, card: <at> markdown tag)
- Auto-sync discovered members into ContactStore
- GroupMemberProvider interface on Channel for extensibility
- Add im:chat.members:read scope to channel setup UI
Requires Lark app scope: im:chat.members:read
* fix(feishu): URL-encode API params + channel-aware tool filtering
- URL-encode chatID (PathEscape) and pageToken (QueryEscape) in
ListChatMembers to prevent path traversal and query corruption
- Add slog.Warn when ListGroupMembers fails for observability
- Add ChannelAware interface so tools can declare required channel types
- ListGroupMembersTool now declares RequiredChannelTypes=["feishu"]
- loop.go filters out channel-specific tools when channel type mismatches,
so list_group_members only appears for Feishu conversations
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
|
||
|
|
ec24b982f3 |
fix(subagent): replace bare count with deterministic roster in announce messages
Subagent announce messages told the parent LLM "There are still X active runs" — a bare number. The LLM had to reconstruct which subagents were done vs running from conversation history, causing hallucination when results arrived out of order. Replace with a full roster listing each subagent by label and status, including per-agent config limits. Remove dead CountRunningForParent() and unused rosterFunc field on AnnounceQueue. |
||
|
|
73897b9481 |
refactor: replace magic numbers and hardcoded strings with named constants (#269)
Extract scattered magic numbers and hardcoded agent type strings into centralized named constants to improve maintainability and prevent silent inconsistencies when defaults need to change. Changes: - Add config/defaults.go: DefaultContextWindow, DefaultMaxTokens, DefaultMaxMessageChars, DefaultMaxIterations, DefaultTemperature, DefaultHistoryShare - Add providers/defaults.go: DefaultHTTPTimeout, SSE/Stdio scanner buffer size constants - Replace 6 hardcoded 200000 (context window) across agent/, http/, cmd/ with config.DefaultContextWindow - Replace 4 hardcoded 32000/32_000 (max message chars) with constant - Replace 3 hardcoded 20 (max iterations) with constant - Replace 2 hardcoded 0.75 (history share) with constant - Replace 3 hardcoded 300*time.Second (HTTP timeout) with constant - Replace 5 hardcoded scanner buffer sizes with named constants - Replace 10 hardcoded "predefined" string comparisons with store.AgentTypePredefined constant (already defined but unused) - Use config.DefaultTemperature instead of hardcoded 0.7 in agent loop Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com> |
||
|
|
6595c0185a |
refactor: merge file writers into config permissions + agent hard delete
Migration 000023: - Migrate group_file_writers data into agent_config_permissions (config_type='file_writer') - Add FK CASCADE/SET NULL on 16 tables for agent hard delete - Clean up soft-deleted zombie agents - ALTER scope VARCHAR(100) → VARCHAR(255) - Partial unique index on agent_key (allows reuse after delete) Store layer: - Remove GroupWriterCache, GroupFileWriterData, 5 AgentStore methods - Add CheckFileWriterPermission (fail-open), ListFileWriters (cached) - Add scope filter to ConfigPermissionStore.List() Tools: Replace GroupWriterCache with ConfigPermissionStore in read_file, write_file, edit, cron, context_file_interceptor Agent loop: buildGroupWriterPrompt uses ListFileWriters + metadata parsing Channels: Telegram commands rewritten to use configPermStore.Grant/Revoke/List HTTP API: Writer endpoints rewired to configPermStore (same response shapes) Hard delete: agents.go DELETE FROM instead of soft delete |
||
|
|
68a4ee39b4 |
feat: config permissions RPC + UI tab + contact search improvements
Backend: - Add config.permissions.list/grant/revoke RPC methods - Auto-fill granted_by from caller's context identity - Fix Telegram contact display_name: FirstName + LastName (was FirstName only) - Change contact search to prefix match (term%) for B-tree index usage Frontend: - Add Permissions tab to agent detail with inline add row layout - Contact search combobox shows name, @username, sender_id, [channel] - Reduce contact search debounce from 300ms to 150ms - i18n keys for en/vi/zh |
||
|
|
4e9f155a4c |
feat(agent): adaptive tool timing with slow tool notification
Track per-tool execution time statistics in session metadata. When a tool call exceeds its adaptive threshold (2x historical max, min 120s default), send a direct outbound notification to the user. - ToolTimingMap: parse/serialize/record/threshold from session metadata - StartSlowTimer: fires once per tool call, auto-cancels on completion - Team config: slow_tool toggle (default on, always direct, never leader) - UI: toggle in team settings with i18n (en/vi/zh) - Store: add GetSessionMetadata to session store interface |
||
|
|
2cc9d68cdc |
fix(tts): config save, Edge provider, media dispatch + dark mode chat (#265)
* fix(tts): config save + Edge provider registration + dark mode chat bubbles - Wrap TTS config payload in `raw` field for config.patch RPC (#229) - Always register Edge TTS provider (free, no API key) instead of gating on `enabled` flag - Fix low-contrast user message bubbles in dark mode chat * fix(tts): skip duplicate media dispatch when temp file already delivered When both the agent loop and the message tool dispatch the same TTS temp file, the first dispatch succeeds and cleanup deletes it. Filter out missing temp media files before sending to prevent "file not found" errors and spurious error notifications on Telegram/Slack/Discord. * feat(tts): include edge-tts in Docker image when Python enabled Edge TTS is free (no API key) and serves as a universal TTS fallback. Install it alongside Python in both ENABLE_PYTHON and ENABLE_FULL_SKILLS builds. * chore(docker): expose build args from .env for compose builds Pass ENABLE_OTEL, ENABLE_PYTHON, ENABLE_FULL_SKILLS as env-driven build args so .env can control Docker build features without editing docker-compose.yml directly. * fix(tts): hot-reload TTS config on settings change via pub/sub TTS providers were only registered at startup, so changing provider/API key via the Web UI had no effect until container restart. Add a tts-config-reload bus subscriber that rebuilds the TTS manager on config changes, matching the pattern used by quota, cron, and web_fetch. Always create a TtsTool at startup (even without providers) so the reload subscriber can populate it when settings are first configured. * fix(tts): protect TtsTool.UpdateManager with RWMutex to prevent data race UpdateManager() can be called from the config reload goroutine while Execute() reads t.manager concurrently from agent goroutines. Add sync.RWMutex following the same pattern as WebFetchTool.UpdatePolicy(). Also update setupTTS doc comment which incorrectly stated it could return nil — Edge TTS is now always registered. --------- Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
5b349db7eb |
feat(heartbeat): provider/model override + fix cache invalidation
- Add ProviderModelSelect to heartbeat config dialog (allowEmpty, verify button) - Backend: accept providerName in HEARTBEAT.SET, resolve to UUID via GetProviderByName - Add ModelOverride to RunRequest, used by Loop when set (cheaper model for heartbeat) - Ticker passes heartbeat model override to agent RunRequest - Fix: InvalidateCache after UpdateState so ListDue picks up new next_run_at immediately - i18n: add sectionModel/modelHint keys (en/vi/zh) |
||
|
|
dc51018563 |
fix: subagent provider routing + api_base fallback (#262)
* fix(subagent): inherit parent agent's provider instead of alphabetical fallback Subagents previously used a fixed provider (alphabetically first from the registry, often "anthropic") regardless of which provider the parent agent used. This caused invalid combos like anthropic/glm-5 when a zai-coding agent spawned subagents. - Pass provider registry to SubagentManager for runtime resolution - Inject parent provider name into context (WithParentProvider) - Resolve activeProvider from parent context before LLM call - Fix trace spans to show actual resolved provider, not default * fix(providers): api_base fallback from config/env for DB providers DB providers with empty api_base now inherit from config/env vars (e.g., GOCLAW_ANTHROPIC_BASE_URL). Prevents proxy API keys from being sent to the real provider API endpoint. - Add APIBaseForType() method on ProvidersConfig - registerProvidersFromDB falls back to config when api_base is empty - ProvidersHandler uses resolveAPIBase() for model listing - Add api_base, display_name, settings to provider validation whitelist * fix(tracing): pass resolved provider name to subagent span emitters - emitSubagentSpanStart now accepts providerName param instead of reading sm.provider.Name() — ensures root subagent span reflects the inherited parent provider, not the fallback default - registerInMemory now uses resolveAPIBase() so DB providers with empty api_base inherit the config/env fallback (same as startup path) --------- Co-authored-by: viettranx <viettranx@gmail.com> |
||
|
|
96cfd1bf08 |
feat(heartbeat): improve prompting, suppression, delivery targets and session cleanup
- Rewrite heartbeat prompt to instruct agent to EXECUTE checklist tasks, not echo them - Simplify suppression: HEARTBEAT_OK present = always suppress, absent = always deliver - Add delivery targets RPC (heartbeat.targets) for channel/chatId picker - Sanitize backend errors — never expose raw SQL to client - Add session cleanup for isolated heartbeat sessions after run - Cap StaggerOffset at 10% of interval to avoid user-visible delay - Fix Upsert to persist next_run_at correctly |
||
|
|
29816db0ab |
feat(heartbeat): cron wakeMode, queue-aware scheduling, lightContext
- CronPayload.WakeHeartbeat triggers heartbeat immediately after cron job completes - Cron tool supports wake_heartbeat param on add/update actions - Scheduler.HasActiveSessionsForAgent() detects busy agents for heartbeat skip - RunRequest.LightContext skips loading context files during heartbeat runs |
||
|
|
08a2d95c0c |
feat: agent heartbeat system — periodic proactive check-ins (#245)
Phase 1 (Core):
- Migration 000022: agent_heartbeats, heartbeat_run_logs, agent_config_permissions tables
- HeartbeatStore + ConfigPermissionStore interfaces with PG implementations
- HeartbeatTicker: background poll → active hours filter → queue-aware skip → run → smart suppression → deliver/log
- Heartbeat tool: status/get/set/toggle/set_checklist/get_checklist/test/logs actions
- Permission check with wildcard scope matching + TTL cache (60s)
- RPC methods: heartbeat.get/set/toggle/test/logs/checklist.get/checklist.set
- HEARTBEAT.md routed via context file interceptor (read/write for both open + predefined agents)
- Session keys: agent:{id}:heartbeat or agent:{id}💓{ts} (isolated)
- PromptMinimal for heartbeat sessions (like cron/subagent)
- Event broadcasting + cache invalidation via bus (heartbeat + config_perms)
- Gateway wiring: ticker init, event wiring, graceful shutdown
Phase 2 (Integration):
- wakeMode: CronPayload.WakeHeartbeat triggers heartbeat after cron job completes
- Queue-aware: Scheduler.HasActiveSessionsForAgent() skips busy agents
- Stagger: deterministic FNV offset spreads heartbeats across interval
- lightContext: RunRequest.LightContext skips context files, only injects checklist
- System prompt distinguishes cron (user-scheduled tasks) vs heartbeat (autonomous monitoring)
|
||
|
|
5a4c72018a |
fix(teams): include agent display name in task create, list, and announce
- Add display_name to task create response (assignee name) - Add owner_display_name and created_by_display_name to list/get items - Pass to_agent_display via dispatch metadata (zero extra DB queries) - Use display name in announce messages to leader for correct attribution |
||
|
|
49441f7305 |
refactor: remove dead delegate code, rename lane/channel to team/teammate
- Remove handleDelegateAnnounce() dead code (no sender emits delegate:* messages) - Remove delegate tool reference from intent_classify.go - Rename LaneDelegate → LaneTeam with backward-compat env var fallback - Rename ChannelDelegate → ChannelTeammate across all team tool files - Comment out lifecycle guards in team_tasks_lifecycle.go (TODO: reviewer workflow) - Update string literals in cron.go, task_ticker.go - Gate tool_status placeholder_update to non-streaming runs only - Skip FinalizeStream on tool.call to prevent mid-run content loss |
||
|
|
843b550651 |
feat: runtime packages UI, pkg-helper, configurable shell deny groups (#244)
Runtime package management with security hardening: - pkg-helper: root-privileged daemon for apk install/uninstall via Unix socket - HTTP API: /v1/packages (list/install/uninstall/runtimes), admin role required for writes - Shell deny groups: 15 configurable groups (per-agent overrides via context) - Packages UI: Web page for managing system/pip/npm packages with confirmation dialogs - Docker: privilege separation (root entrypoint → su-exec drop), init for zombie reaping - Security: umask socket creation, persist file validation, deny pattern hardening (Node.js fetch/http, Python from/import, curl localhost, sensitive env vars) - Auth: empty gateway token → admin role (dev/single-user mode) |