40 Commits
Author SHA1 Message Date
ナムandCollective Developer fc7d183961 Enhance passive memory extraction tuning (#1393)
* feat(channelmemory): add passive extraction tuning

* feat(ui): expose passive memory tuning controls

* docs(memory): document passive extraction tuning

---------

Co-authored-by: Collective Developer <man@collective.dev>
2026-07-08 20:53:19 +07:00
ナムandCollective Developer f31214f5ce feat(channel-memory): enhance passive extraction for Discord (#1361)
* fix(channels): persist pending history under instance names

* feat(channel-memory): add run-all extraction workflow

* feat(discord): resolve passive memory channel titles

* feat(web): enhance passive memory controls

* docs(channel-memory): document extraction endpoints

---------

Co-authored-by: Collective Developer <man@collective.dev>
2026-07-06 11:51:07 +07:00
Duy /zuey/ 53ef912441 fix(channels): add reasoning delivery modes (#135) 2026-06-03 11:01:14 +07:00
Goon d78f97b9d7 feat(channel-memory): merge dev for passive extraction 2026-05-31 14:01:29 +07:00
Duy /zuey/ 2cd750ac44 feat(channels): add context capability admin surface
Squash merge PR #115 after resolving changelog and SQLite migration-map conflicts with current dev. Renumbered channel-context PostgreSQL migration to 000075 and bumped PG required schema to 75 plus SQLite schema to 44 so it follows the run timeline migration. Local checks passed: go test ./..., go build ./..., go build -tags sqliteonly ./..., go vet ./..., and pnpm -C ui/web build. PR CI run 26705617311 passed release-versioning, go, and web.
2026-05-31 13:50:31 +07:00
Duy /zuey/ 3569f8d681 fix(channels): load WhatsApp QR channel on create
Squash merge PR #98 in priority order after #97. Existing PR checks were green; branch workflow status will be tracked.
2026-05-31 11:33:41 +07:00
Goon 8d45168b35 feat(channel-memory): run passive extraction pipeline 2026-05-29 20:48:19 +07:00
Goon c7aac69ca0 fix(channels): harden context capability admin 2026-05-29 20:04:33 +07:00
Goon bd34e2917e feat(channels): manage context scoped capabilities 2026-05-29 19:29:31 +07:00
Goon f20d776ebb feat(channels): expose context capability matrix 2026-05-29 19:17:18 +07:00
Goon 29f3eb3874 feat(channels): expose read-only channel contexts 2026-05-29 19:11:17 +07:00
Duy /zuey/ 43049d3b3f feat(http): unblock CLI P6 backend APIs (#44)
* feat(api): unblock trace follow and provider reconnect

* test(cron): stabilize run log execution test

* feat(http): add CLI P6 backend endpoints
2026-05-22 20:59:47 +07:00
Duy /zuey/ ef829c1ab4 fix(bitrix24): resolve PR 8-10 stack findings
Resolve the PR #8/#9/#10 stack on current dev, including Bitrix24 install callback hardening, migration renumbering, duplicate-domain fail-closed routing, UI textarea/mobile cleanup, and review hardening.
2026-05-22 20:53:01 +07:00
viettranx 59a8c25349 revert(http): remove admin backfill endpoint — lazy heal on /writers is enough 2026-04-18 21:43:19 +07:00
viettranx e66badb803 fix(permissions): auto-enrich file_writer metadata on grant from Web UI 2026-04-18 21:35:46 +07:00
Plateau Nguyen fb8afd41bf feat(channels): add Facebook Messenger and Pancake channel integrations (#731)
Add two new channel implementations for Facebook Fanpage (comment + Messenger
auto-reply, first inbox DM) and Pancake/pages.fm (multi-platform inbox via
Facebook, Zalo, Instagram, TikTok, WhatsApp, LINE).

Key features:
- Facebook: comment auto-reply, Messenger auto-reply, first inbox DM,
  HMAC-SHA256 webhook verification, multi-page webhook routing
- Pancake: multi-platform inbox, outbound echo dedup with HTML normalization,
  race-condition-safe echo fingerprinting, platform-aware formatting
- Bootstrap skip: pre-fill USER.md from channel metadata (Pancake)
- SanitizeDisplayName across all channels (defense in depth)

Code audit fixes:
- Fix truncateForTikTok byte→rune slicing (UTF-8 corruption)
- Fix empty message.ID shared dedup slot (silent message loss)
- Fix DisplayName markdown injection in buildPrefilledUser
- Consolidate duplicate ChannelMeta type (agent→bootstrap)
- Compile-time interface assertions, alphabetical type constants
- Per-message logs demoted to slog.Debug, errors.As for wrapped errors
- UI: alphabetical channel ordering, complete config schemas
- Remove deprecated WhatsApp bridge_url, fix nested error parsing
2026-04-09 23:15:08 +07:00
viettranx 39bb90bd60 refactor(permissions): remove auto-add file writer, add config type constants
- Remove auto-add logic that granted file_writer permission to the first
  group/guild member who chatted with the bot
- Add ConfigTypeFileWriter and ConfigTypeHeartbeat constants, replace all
  hardcoded config_type strings across callers
- Add bootstrap exception: /addwriter and !addwriter allow first writer
  to be added when no writers exist yet
- Optimize writer commands: reuse cached ListFileWriters result for both
  permission check and last-writer guard, reducing DB queries per command
- Add freshness directive to file writer system prompt so bot prioritizes
  current list over stale references in conversation history
2026-04-01 13:59:56 +07:00
viettranx 4e9ce0e0e1 fix(contacts): consistent sender_id format and show contact_type in UI
- Use senderID (id|username) instead of userID in group no-mention path,
  preventing duplicate contacts for the same Telegram user
- Use full name (FirstName + LastName) in both contact insert paths
- Show contact_type (User/Group) instead of peer_kind (Direct/Group) in
  contacts table TYPE column and filter dropdown
- Add contact_type filter support in HTTP handler, PG and SQLite stores
2026-03-31 14:18:09 +07:00
viettranx 21b6c454ca feat: merge pipeline, per-user credentials, unified picker, group contacts
- Enable merge UI for linking channel contacts to tenant_users
- Contact → tenant_user resolution with cached lookup (60s TTL)
- MCP per-user credentials via user-keyed connection pool
- Secure CLI per-user credentials with AES-256-GCM encryption
- Unified UserPickerCombobox searching contacts + tenant_users
- Group contact collection with chat title in all channels
- Group permission inheritance via wildcard user_id="*"
- Fix heartbeat using wrong userID in group chats
- Filter internal senders from contact collection
- Add contact_type column (user/group) to channel_contacts
- SQLite schema v2 migration for desktop edition
2026-03-29 22:33:17 +07:00
viettranx 028ea8a7d8 fix(security): elevate management HTTP routes from Operator to Admin role
Management endpoints (MCP, Agents, Skills, Channels, Builtin Tools)
previously used auto-detect auth which allowed Operator-level users
to perform CRUD operations. Now consistent with WS policy: Admin+
required for writes, Viewer+ for reads.

Also moves WS skills.update to isAdminMethod and improves 403 error
messages to include required role info.
2026-03-29 09:02:48 +07:00
viettranx 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
2026-03-27 20:53:05 +07:00
viettranx 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
2026-03-27 20:15:26 +07:00
viettranx 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"
2026-03-27 19:52:28 +07:00
viettranx 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`
2026-03-23 22:57:26 +07:00
Viet Tran 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
2026-03-23 08:08:23 +07:00
viettranx 6595c0185a refactor: merge file writers into config permissions + agent hard delete
Migration 000023:
- Migrate group_file_writers data into agent_config_permissions (config_type='file_writer')
- Add FK CASCADE/SET NULL on 16 tables for agent hard delete
- Clean up soft-deleted zombie agents
- ALTER scope VARCHAR(100) → VARCHAR(255)
- Partial unique index on agent_key (allows reuse after delete)

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

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

Agent loop: buildGroupWriterPrompt uses ListFileWriters + metadata parsing

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

Hard delete: agents.go DELETE FROM instead of soft delete
2026-03-19 13:35:57 +07:00
Luan VuandLuvu182 a7f5acc1e3 fix(security): harden SQL injection, MCP prompt injection, sandbox fallback, and file serving (#246)
- execMapUpdate: validate column names with strict regex to prevent SQL injection
- HTTP update handlers: add field allowlists (agents, providers, custom_tools, mcp, channel_instances)
- pqStringArray: properly escape array elements to prevent PostgreSQL array literal injection
- scanStringArray: handle quoted elements in PostgreSQL array format
- MCP bridge: wrap tool results as external/untrusted content to prevent prompt injection
- File serving: block access to sensitive system directories (/etc, /proc, /sys, etc.)
- Sandbox: fail closed when Docker unavailable instead of silent fallback to host
- Shell deny: fix base64 --decode bypass, add host exec 1MB output limit
- ILIKE queries: escape % and _ wildcards in knowledge_graph, custom_tools, channel_instances

Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
2026-03-18 07:42:38 +07:00
Goon 75c570e951 feat(security): credentialed exec + HTTP RBAC + API key cache (#197)
- Secure CLI credential injection via AES-256-GCM encrypted env vars
- API key management with fine-grained RBAC scopes
- resolveAuth/requireAuth middleware across all 25+ HTTP handlers
- In-memory API key cache with TTL, negative caching, pubsub invalidation
- Sandbox-first execution (fails if unavailable, no silent fallback)
- Credential scrubbing, constant-time token comparison, Admin-only CLI creds
- SQL migration 000020: secure_cli_binaries + api_keys tables
- 14 unit tests for cache and RBAC with race detector

Closes #197
2026-03-15 20:13:18 +07:00
viettranx 28fab9507a feat(storage): add lazy folder loading, SSE size endpoint, and enhanced file viewer
- Backend: depth-limited WalkDir (max 3 levels default) with on-demand subtree loading
- Backend: new GET /v1/storage/size SSE endpoint with 60min in-memory cache
- Backend: raw binary file serving (?raw=true) with MIME detection and download support
- Frontend: lazy tree expansion with loading spinners for deep folders
- Frontend: streaming size display with cache info tooltip
- Frontend: image viewer (blob URL), unsupported file UI, download button, colored size badges
- Frontend: file-type icons for 13 categories (md, json, yaml, images, video, etc.)
- Fix sidebar connection status text overflow on collapse
- Apply go fix modernization (interface{} → any) across http handlers
2026-03-14 18:13:52 +07:00
viettranx 9115169c03 feat: expand audit logging via pub/sub event pattern
Replace direct ActivityStore injection with event-driven audit system.
Handlers emit audit events via msgBus.Broadcast(), a single subscriber
with buffered channel persists to activity_logs table.

Coverage expanded from 3 agent CRUD actions to ~65 audit points across
all HTTP handlers and WebSocket RPC methods including agents, providers,
skills, MCP servers, cron, sessions, teams, pairing, and more.
2026-03-12 18:34:56 +07:00
viettranx f1953203c4 feat(contacts): batch resolver API, shared hooks & contact integration across agent UI
- Add GetContactsBySenderIDs batch lookup to ContactStore (DISTINCT ON sender_id)
- Add GET /v1/contacts/resolve?ids= endpoint (max 100 IDs)
- Extract useContactPicker hook from managers tab (DRY refactor)
- Create useContactResolver hook for batch ID→name resolution via React Query
- Agent Shares tab: replace Input with Combobox contact picker + resolve names in list
- Agent Instances tab: resolve user_id to contact name as fallback
- Update i18n placeholders for contact search (en/vi/zh)
2026-03-10 16:56:17 +07:00
viettranx c8dc9917fe feat(contacts): channel contacts table, auto-collector, contacts page & managers tab redesign
- Add channel_contacts migration (000014) with UNIQUE(channel_type, sender_id)
- Add ContactStore interface with UPSERT, list, count, merge operations
- Add ContactCollector with 30-min TTL cache to skip redundant DB writes
- Wire auto-collection into gateway consumer on every inbound message
- Add GET /v1/contacts API with pagination, search, channel_type & peer_kind filters
- Rename Writers tab → Managers tab (UI-only; backend routes unchanged)
- Extract InlineAddForm with scoped state, debounce cleanup, aria-expanded
- Add Combobox contact picker with debounced search + auto-fill
- Add Contacts page with server-side pagination, filters, i18n (en/vi/zh)
- Add shared ChannelContact type, sidebar nav entry, route & query keys
- Fix ILIKE wildcard escape, log CountContacts errors, extract shared type
2026-03-10 14:29:01 +07:00
viettranx 344e2ac7d1 feat(i18n): add full i18n support for backend and web UI
- Add i18next + react-i18next with namespace-split locale files (27 namespaces x 3 languages)
- Add language switcher in topbar (EN/VI/ZH) with localStorage persistence
- Replace hardcoded strings in 160+ React components with t() translations
- Add Go message catalog (internal/i18n) with T(locale, key, args...) function
- Replace 81 hardcoded error strings in gateway methods and HTTP handlers
- Add locale context propagation: WS connect param + HTTP Accept-Language header
- Keep technical terms in English: Agent, Session, Channel, Provider, Skill, Team, MCP, Cron
- Update CLAUDE.md and review-pr skill with i18n compliance checks
2026-03-09 22:22:42 +07:00
Duc Nguyen 137a986d4f feat(channels): add Slack channel (#83)
* feat(channels): add Slack channel via Socket Mode (#37)

Implement Slack integration using Socket Mode (xapp-/xoxb- tokens):
- Event-driven messaging via app_mention + message events
- Policy checks: open, pairing, allowlist, disabled (DM + group)
- Thread participation with configurable TTL
- Markdown-to-mrkdwn formatting pipeline
- Streaming support (edit-in-place + native ChatStreamer)
- SSRF-protected file downloads
- Debounce, dedup, reactions, group history context
- 170 unit tests (format, helpers, stream, SSRF)

Fix BaseChannel.HandleMessage allowlist to also check chatID,
enabling group allowlist with channel IDs across all channels.

Closes #37

* feat(slack): add file/media support and edit-to-mention handling

- Wire inbound file download into handleMessage (images, audio, documents)
- Add media.go with resolveMedia, classifyMime, buildMediaTags
- Extract shared ExtractDocumentContent to channels/media_utils.go (DRY with Telegram)
- Support file_share and message_changed subtypes
- Handle edit-to-mention: respond when user edits old message to add @bot
- Add MediaMaxBytes config field (default 20MB)
- Fix debounce media accumulation (was silently dropping files)
- Add 60s HTTP client timeout on file downloads
- Refactor downloadFile signature for slack.File compatibility
2026-03-09 07:02:37 +07:00
Viet Tran 6895e369f6 refactor: remove standalone mode, consolidate to managed-only (PostgreSQL) (#70)
- Remove standalone mode code: file-based stores, standalone gateway,
  heartbeat service, SQLite memory, standalone docker-compose
- Rename docker-compose.managed.yml → docker-compose.postgres.yml
- Clean up ~130 Go comments referencing "managed mode" qualifier
- Simplify docker-compose.yml env vars (providers/channels via web UI)
- Update .env.example to essential vars only (token + encryption key)
- Add setup wizard UI (provider → agent → channel bootstrap flow)
- Add logs.tail WebSocket handler for live log streaming
- Add cursor-pointer to interactive UI components
- Clean up config page (remove standalone-only sections)
- Update README and docs for managed-only architecture
2026-03-06 18:51:11 +07:00
Duc Nguyen 0f5dd08f76 feat(channels): introduce Zalo Personal channel integration (#32)
* feat(channels): implement Zalo Personal Chat (ZCA) protocol layer

Implement complete Zalo Personal Chat integration including:
- Message protocol layer (request/response/event types)
- Connection management with auth flow
- Message sending/receiving with text and media support
- User/group management and sync
- Telegram-style contact and conversation handling
- Comprehensive unit tests with 85%+ coverage

Architecture follows existing channel patterns (Telegram, Feishu) with
raw API calls for session management and message delivery. Includes
error handling, rate limiting awareness, and logging.

* feat(channels): add Zalo Personal channel integration layer

Wire protocol package to GoClaw's channel system:
- channel.go: Channel struct, Start/Stop/Send, listenLoop, message handlers
- auth.go: credential resolution (preloaded > file > QR), persistence
- policy.go: DM/group policy, @mention gating, pairing with debounce
- factory.go: managed mode factory (requires credentials, no QR)
- cmd/gateway.go: register standalone + managed factory

* feat(ui): add Zalo Personal channel type to web dashboard

Add zalo_personal to channel type dropdown, credential fields
(IMEI, cookie, userAgent), and config schema (DM/group policy,
require_mention, allow_from).

* feat(channels): add WebSocket QR login for Zalo Personal channel

Add real-time QR code login flow for zalo_personal channel instances
in managed mode. Users create an instance without credentials, then
trigger QR login from the web dashboard.

Backend:
- New RPC method zalo.personal.qr.start with per-instance mutex
- QR PNG pushed via client-scoped WS events (not broadcast)
- Credentials encrypted and saved to DB on successful scan
- Cache invalidation triggers automatic channel reload/start
- Factory returns nil,nil for missing credentials (skip, not error)
- Instance loader handles nil-channel gracefully

Frontend:
- ZaloPersonalQRDialog with auto-start, retry, and auto-close
- QR button in channel instances table for zalo_personal type
- Credential fields no longer required (auto-populated via QR)

* fix(channels): skip redundant LoginWithCredentials after QR login

QR flow already validates session via qrCheckSession + qrGetUserInfo.
Calling LoginWithCredentials again conflicts with the active QR session
state, causing "empty response" errors. Credentials are validated when
the channel starts instead. Also rename log prefix from "zca" to
"Zalo Personal".

* fix(channels): fix Zalo Personal cookie domain for login API

BuildCookieJar only set cookies for chat.zalo.me but the login API
uses wpa.chat.zalo.me. Cookies weren't sent to the subdomain, causing
"empty response" on channel startup. Now sets cookies for both hosts.

* fix(channels): move UTF-8 check after gzip decompression in Zalo listener

The UTF-8 validity check in decryptAESGCMPayload ran on raw decrypted
bytes before gzip decompression, causing all encType=2 (AES-GCM+gzip)
messages to fail with "decrypted payload is not valid UTF-8".

Move the check to decryptEventData so it runs after all processing
(decryption + decompression) is complete.

* feat(channels): add QR-only onboarding and contacts picker for Zalo Personal

- Remove credential text fields for zalo_personal, show QR auth info banner
- Add has_credentials boolean to HTTP and WS mask functions
- Implement FetchFriends/FetchGroups protocol (encrypted Zalo API)
- Add zalo.personal.contacts WS RPC method with parallel fetch
- Create ZaloContactsPicker component with search, selection, manual entry
- Integrate picker in channel instance edit dialog for allow_from config

* refactor(channels): rename zca error prefix to zalo_personal across protocol package

* fix(channels): unwrap inner response envelope in Zalo contacts decryption

The Zalo API returns double-wrapped responses: outer envelope contains
encrypted base64 data, which when decrypted yields another Response
envelope with error_code and data fields. The decryptDataField helper
was returning the raw decrypted bytes without unwrapping the inner
envelope, causing json unmarshal failures when parsing friends/groups.

* fix(channels): pass version 0 for group details to get full data

The Zalo group info endpoint uses a version-based caching mechanism.
Passing the actual version from step 1 causes the server to return
the group in "unchangedsGroup" with empty "gridInfoMap". By passing
version 0 for all groups, we force the server to return full group
info including name, avatar, and member count.

* fix(ui): auto-load contacts on modal reopen to resolve display names

When the edit modal is reopened with already-selected contact IDs,
contacts are now auto-fetched so badges show display names instead
of raw numeric IDs.

* fix(channels): handle gzip-compressed response in Zalo SendMessage

SendMessage used io.ReadAll + json.Unmarshal directly but the response
is gzip-compressed (Accept-Encoding: gzip header). Use readJSON() which
handles gzip decompression, fixing "invalid character '\x1f'" errors.

* fix(channels): decrypt encrypted send response in Zalo SendMessage

The Zalo send message API response is encrypted like all other endpoints.
Parse outer envelope, decrypt the data field, then extract msgId from
the decrypted inner response.

* feat(channels): improve Zalo listener reliability and UI channel wizard

- Migrate WebSocket client from gorilla to coder/websocket, eliminating
  unsafe/reflect hacks for RSV1 decompression and buffer inspection
- Add channel-level restart with exponential backoff (2s→60s cap, max 10)
  so channels auto-recover instead of stopping permanently
- Reset listener retry counters after 60s stable connection to prevent
  long-lived connections from exhausting retry budget
- Add code 3000 (duplicate session) recovery with 60s initial delay
- Detect silent disconnects via read deadline (2.5x ping interval)
- Fix Stop() to always cancel context, preventing reconnect timer leaks
- Refactor UI channel form into wizard-based flow with registry pattern
- Auto-refresh channel status after create/update dialog closes

* refactor(channels): move Zalo RPC methods to zalomethods package

Move Zalo personal channel RPC handlers from internal/gateway/methods to
internal/channels/zalo/personal/zalomethods, improving code organization
and removing prefix redundancy. Rename types: ZaloPersonalQRMethods →
QRMethods, ZaloPersonalContactsMethods → ContactsMethods.

- Move zalo_personal_qr.go → zalomethods/qr.go
- Move zalo_personal_contacts.go → zalomethods/contacts.go
- Update imports in cmd/gateway.go (2 call sites)
- Update internal/channels/zalo/personal imports

* feat(channels): add typing indicator to Zalo Personal channel

Show "typing..." in Zalo while the LLM processes messages, matching
the Telegram/Discord pattern. Uses the shared typing.Controller with
4s keepalive (Zalo typing expires ~5s) and 60s TTL safety net.

* feat(channels): handle image attachments in Zalo Personal channel

- Add Raw field to Content struct to preserve non-string JSON payloads
- Add Attachment struct with IsImage() detection (ext + Zalo CDN paths)
- Add AttachmentText() for human-readable placeholders (image/file/other)
- Download image attachments to temp files for agent vision pipeline
- Non-image files get text placeholder only (no download)
- Fix URL query param stripping in file extension detection

* fix(channels): switch Zalo WS client to gorilla/websocket with cookie jar fix

coder/websocket did not propagate session cookies for wss:// URLs,
causing Zalo backend to reject connections with "zpw_sek not found".
Switch to gorilla/websocket which handles wss→https scheme conversion
natively. Add wsJar safety wrapper and fix Close() mutex consistency.

Also update Makefile `up` target to use --no-cache builds.

* fix(channels): inject cookies manually for Zalo WS connection

Replace wsJar wrapper with direct cookie injection from chat.zalo.me
base domain. Fixes host-only cookies (zpw_sek) not matching WS
subdomains (ws*-msg.chat.zalo.me) due to Go cookiejar limitations.

* fix(channels): harden Zalo Personal channel security and concurrency

- Add SSRF protection to downloadFile using CheckSSRF (URL validation,
  private IP blocking, DNS pinning) with context and 30s timeout
- Protect c.sess/c.listener with sync.RWMutex to eliminate data races
  during restart; add thread-safe session()/getListener() accessors
- Add stopped flag + reconnTimer to Listener to prevent zombie reconnects
  after Stop(); timer cancelled on Stop(), checked before Start()
- Fix QR flow using context.Background() detached from WS client; now
  derives from parent ctx so flow cancels on client disconnect
- Set initial 30s read deadline for cipher key handshake to prevent
  indefinite blocking before ping loop starts
- Use defer in WSClient.Close() to prevent connection leak on panic
- Document ReadMessage ctx limitation and two-layer reconnect design

* chore: remove unused gobwas/ws dependency from go.mod

gobwas/ws was a leftover from the previous coder/websocket usage,
no longer imported by any Go source files.

* fix(channels): align Zalo Personal policy defaults across UI and backend

Policy defaults were inconsistent across three layers causing group/DM
allowlist enforcement to silently fail. New() applied "allowlist" default
to local vars but never wrote back to config; checkGroupPolicy() then
read empty string and defaulted to "open", bypassing the allowlist.
UI Select components displayed schema defaults visually without
persisting them to configValues, so DB config never stored the policy.
2026-03-03 14:21:07 +07:00
viettranxandClaude Opus 4.6 4387f6b1ba fix: improve spawn tool team_task_id validation and orphan detection
When LLMs call team_tasks create + spawn in parallel, the spawn
tool receives a hallucinated task_id that fails uuid.Parse, causing
a misleading error and bypassing orphan detection.

- Include pending task IDs in spawn error message so LLM can retry
  with the correct UUID
- Move spawn counting to post-execution so failed spawns don't
  increment teamTaskSpawns, allowing orphan detection to fire

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:36:46 +07:00
viettranxandClaude Opus 4.6 813c602bca feat(ui): migrate to TanStack Query, add server-side pagination, and improve error handling
- Add @tanstack/react-query with shared cache and centralized query keys
- Migrate all 14 CRUD hooks from manual useState/useCallback to useQuery/useMutation
- Add WS event-driven query invalidation (sessions, traces auto-refresh on run completion)
- Consolidate LlmConfigSection to delegate provider/model UI to ProviderModelSelect
- Add server-side pagination for custom-tools and channel-instances (Go store + HTTP handler)
- Extract shared types into dedicated files (provider, custom-tool, mcp, channel, skill, trace)
- Add network error handling in HTTP client and connectivity check on login
- Add disconnect banner in app layout when gateway connection is lost

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:25:59 +07:00
viettranx dfd91556f8 feat: Introduce agent teams, agent linking, and advanced agent orchestration features. 2026-02-25 23:24:52 +07:00
Viet TranandClaude Opus 4.6 f3f4c67b36 Initial commit: GoClaw AI agent gateway
Multi-agent AI gateway with WebSocket RPC, HTTP API, and messaging channel integrations.
Go port of OpenClaw with multi-tenant PostgreSQL, per-user isolation, security hardening,
and production observability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:58:07 +07:00