Commit Graph
1228 Commits
Author SHA1 Message Date
viettranx 2c2e2cb536 fix(vault): skip goclaw_gen media in enrichment, improve graph UX
Backend:
- Skip goclaw_gen_* files in Handle, EnqueueUnenriched, gatherCandidates
- Skip in RescanWorkspace PendingEvents (progress count)
- Reduces noisy auto-links by ~59%

Frontend:
- Theme-aware node colors (Tailwind -600/-400 matching sidebar)
- Neutral gray edges (zinc-500/300), lighter inactive edges
- Disable hover label white box (defaultDrawNodeHover)
- Auto-expand + load first-level folders
- Group hover text colors for tree items
- Edge density based on camera zoom
2026-04-12 18:56:27 +07:00
viettranx 0546850df8 fix(vault): softer graph colors for dark mode, thinner edge lines 2026-04-12 18:18:31 +07:00
viettranx 9538886175 feat(vault): auto-expand first level folders in sidebar by default 2026-04-12 18:17:19 +07:00
viettranx 1808523f3f feat(vault): add confirmation dialog for stop enrichment button 2026-04-12 18:16:42 +07:00
viettranx 853255a227 fix(vault): stricter classify prompt - ctx under 50 chars, compact JSON example 2026-04-12 18:11:48 +07:00
viettranx 28c49e244c fix(vault): increase classify max_tokens to 4096 for verbose models 2026-04-12 18:10:49 +07:00
viettranx 30354fd762 refactor(vault): merge rescan and stop enrichment buttons into single toggle 2026-04-12 18:09:42 +07:00
viettranx 50821b6207 fix(vault): re-enqueue unenriched docs on rescan when all files unchanged
When rescan finds no new/updated files but some docs still lack summaries
(e.g. previous enrichment failed due to provider timeout), automatically
re-enqueue them for enrichment retry.

- Add VaultStore.ListUnenrichedDocs() to fetch docs with empty summary
- Add EnrichWorker.EnqueueUnenriched() to emit events for retry
- Add RescanResult.Reenqueued field to track re-enqueued count
- Update UI to show "X re-queued for enrichment" toast
2026-04-12 18:06:51 +07:00
viettranx 607ef556b5 feat(web): add ESLint 10 with flat config and CI integration
- Install ESLint 10 + TypeScript-ESLint + React hooks plugins
- Create eslint.config.js with flat config format
- Fix conditional hooks violations in agents-page, task-list, knowledge-chart
- Fix unused variables and useless assignments
- Add lint step to CI workflow before build
- Remove unused eslint-disable comments

Rules configured:
- rules-of-hooks: error (critical)
- exhaustive-deps: off (intentional patterns)
- no-explicit-any: off (pragmatic casts)
- no-console: off (dev convenience)
2026-04-12 17:40:33 +07:00
viettranx 08b51adaf1 fix(sqlitestore): add tenant_id filter to GetSkillHashBySlug and GetNextVersion
SQLite implementations were missing tenant_id WHERE clauses that the
PostgreSQL equivalents have. While Lite edition is single-tenant in
practice, this maintains dual-DB parity and prevents potential issues
if the codebase evolves.

Follows the same pattern as other methods in the file (GetSkillFilePath,
CreateSkillManaged, etc.) that use store.TenantIDFromContext with
MasterTenantID fallback.
2026-04-12 17:32:34 +07:00
viettranx 0e28a48885 fix(web): add test scripts to package.json
Add vitest scripts for running tests:
- test: run tests once
- test⌚ watch mode
- test:ui: vitest UI
2026-04-12 17:28:32 +07:00
Kai (Tam Nhu) Tran cbbdbc992f feat(skills): multi-skill ZIP upload with hash-based idempotency (#846)
feat(skills): multi-skill ZIP upload with hash-based idempotency

- Multi-skill ZIP detection: upload single ZIP containing multiple skill directories
- Hash-based idempotency: SHA-256 of SKILL.md content deduplicates re-uploads
- Grouped upload UI: ZIP group headers with per-skill badges (NEW/UNCHANGED/ERROR)
- Safety: 50-skill per-ZIP limit, TOCTOU-safe hash check under lock
- Performance: cached ZIP parsing O(N) vs O(N*M)
- Tests: 17 frontend + 9 backend tests

Closes #845
2026-04-12 17:26:31 +07:00
viettranx 034187ac9c fix(pipeline): block_reply dedup falsely suppresses final message (#838)
Only count BlockReplies when tool calls are present (matching when
EmitBlockReply actually fires in think_stage). Final answers without
tool calls must not increment the counter, otherwise gateway dedup
falsely suppresses delivery on non-streaming channels.

- Update ObserveStage to check len(resp.ToolCalls) > 0
- Rename test to reflect new behavior (tool calls required)
- Add regression test for #838 scenario

Closes #838
2026-04-12 17:17:51 +07:00
viettranx 45b9240b3a fix(pancake): correct test assertion and remove unused config field
- Fix TestPrivateReply_ReturnsError: use HTTP 400 instead of 200 since
  doRequest only returns error for status >= 400
- Remove unused MaxThreadDepth config field from CommentReplyOptions
2026-04-12 17:13:53 +07:00
Plateau Nguyen 18c09be21b feat(pancake): comment auto-reply + first-inbox DM (#841)
* feat(pancake): add comment auto-reply + first-inbox inbox implementation

- Route COMMENT webhook events with feature gate and self-reply prevention
- Add PostFetcher with sync.Map cache + singleflight for comment context
- Implement ReplyComment() and PrivateReply() API client methods
- Split Send() into sendCommentReply() / sendInboxReply() flows
- Add sendFirstInbox() for one-time inbox DM with configurable greeting
- New config: Features.FirstInbox, CommentReplyOptions, FirstInboxMessage, PostContextCacheTTL
- 72 tests passing with race detection; both PG and SQLite builds clean

* fix(pancake): address PR 841 review issues

- Add slog.Debug when post context fetch fails in buildCommentContent
- Add comment to GetPosts explaining why it bypasses doRequest
- Add 72h TTL eviction for firstInboxSent in runDedupCleaner
- Add 30s timeout to sendCommentReply to bound API hang risk
- Replace custom containsStr helper with strings.Contains in tests
2026-04-12 17:12:57 +07:00
badgerbeesandviettranx 6608e3dafe fix(telegram): preserve Telegram topic routing for delayed notifications (#850)
* Fix delayed Telegram topic routing

* refactor: export TaskLocalKeyMetadata and fix formatting

- Export TaskLocalKeyMetadata from tools package for reuse
- Remove duplicate taskLocalKeyMetadata from tasks package
- Fix gofmt indentation issue in notifyLeaders
- Add trailing newline to team_tool_helpers_test.go

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-04-12 17:05:57 +07:00
viettranx 7e798a5d13 fix(providerresolve): add debug logging for background provider resolution
- Log loaded system_configs (background.provider, agent.default_provider)
- Log provider lookup attempts with source and error details
- Warn when falling back to first registered provider
- Surface config load errors instead of silently swallowing them

Helps diagnose provider resolution failures in episodic/vault workers.
2026-04-12 16:52:28 +07:00
viettranx 42966a5742 feat(vault): add enrichment stop button + error toast
Backend:
- Add error tracking to EnrichProgress (error_count, last_error)
- Broadcast error events when LLM calls fail
- Add POST /v1/vault/enrichment/stop endpoint
- Wire enrichWorker to VaultHandler for stop functionality

Web UI:
- Add stop button (appears when enriching)
- Show error toast when enrichment errors occur
- Display error count in progress bar
- Add useStopEnrichment hook

i18n: en/vi/zh translations for new strings
2026-04-12 16:28:54 +07:00
viettranx 3fa3fb5ebf refactor(workers): per-tenant provider resolution for background workers
Replace static provider/model fields with per-tenant resolution at
processing time. Fixes architectural mismatch where singleton workers
used tenant-specific system_configs.

Changes:
- Add shared ResolveBackgroundProvider() in providerresolve package
- Refactor vault enrichWorker, episodic, dreaming workers to resolve
  provider per-event using registry + systemConfigs
- Remove hot-reload machinery (no longer needed)
- Update ConsolidationDeps to use Registry/SystemConfigs

Fallback chain: background.provider → agent.default_provider → first
registered provider.
2026-04-12 16:19:55 +07:00
viettranx e021934e68 fix(vault): wire provider hot-reload to config.patch event
The previous commit only listened to TopicSystemConfigChanged (periodic
DB refresh), but config.patch from UI fires TopicConfigChanged. Add
subscriber in gateway_lifecycle so vault enrichment picks up provider/
model changes immediately when user saves config.
2026-04-12 15:13:53 +07:00
viettranx ba66a7b67d fix(vault): increase max_tokens + hot-reload provider for enrichment
- Increase classify max_tokens 1024→2048, summarize 1536→4096 to
  prevent truncated JSON from models like gemini-2.5-pro
- Add debug logging: raw LLM output on parse failures, finish_reason
  truncation warnings with model name
- Hot-swap vault enrichment provider/model on config change without
  restart (wired into TopicSystemConfigChanged handler)
- Use RWMutex-guarded llm() accessor for thread-safe provider reads
2026-04-12 15:08:59 +07:00
viettranx fbfae1e618 fix(agents): prevent NOT NULL violation on promoted columns during update
Frontend sent null for empty emoji (emoji.trim() || null), which violated
the NOT NULL constraint on the promoted emoji column (migration 000037).
This caused all agent saves to fail with 500 when emoji was unset.

- Frontend: send empty string instead of null for emoji field
- Backend (PG + SQLite): add null-coercion for all promoted NOT NULL
  columns — TEXT (emoji, agent_description, thinking_level) coerce to "",
  INT (skill_nudge_interval, max_tokens) coerce to 0
2026-04-12 14:31:51 +07:00
viettranx 28d29ba046 fix(vault): enrichment pipeline reliability + cross-agent classify
9 fixes for vault enrichment pipeline:

1. Queue key = tenant-only (was per-agent, caused multiple batches
   blocking EventBus workers and progress bar flashing)
2. Classify chunks 5 candidates per LLM call (prevents response
   truncation that caused parse_still_failed errors)
3. Classify prompt improved: explicit "EXACTLY one entry per
   candidate", 5-entry example, ctx capped at 30 words
4. max_tokens kept at 1024 (sufficient for 5 candidates)
5. Progress AddDone removes !running guard (safe before Start)
6. Rescan defers event publishing via PendingEvents — Start()
   called before workers receive events, eliminating race
7. Upload handler same deferred publish pattern
8. Frontend enrichment timer cancels stale "complete" timeout
   when new enrichment starts (prevents bar disappearing)
9. Sidebar tree reloads after rescan completes

Classify now searches across entire tenant (empty agentID) to
build cross-agent links for future vault sharing. Access control
enforced at query time — agents only see their own docs.
2026-04-12 14:31:36 +07:00
viettranx bfdaeb2382 fix(vault): increase classify max_tokens to prevent truncated JSON
With 10 candidates, classify response can exceed 1024 tokens when
ctx descriptions are verbose, causing json unmarshal failures.
Bump to 2048 to accommodate worst-case 10-candidate responses.
2026-04-12 13:39:52 +07:00
viettranx 5b52ce27a8 fix(vault): aggregate enrichment progress across per-agent batches
After rescan agent_key fix, docs split into per-agent batch queues.
Each batch was calling Start()/Finish() independently, causing the
progress bar to flash 0/1 per agent instead of showing global progress.

Now: handler Start(total) sets global count, worker batches call
TrackBatch()/MarkBatchDone() for lifecycle, AddDone(n) auto-completes
when done >= total. Progress bar shows smooth 0→N across all agents.
2026-04-12 13:38:44 +07:00
viettranx d58e0366fd fix(vault): only reset enrichment progress on new batch, not mid-drain
Start() is called each drain loop in processBatch to update total.
Only reset done=0 when transitioning from idle→running (new batch),
not when already running (same batch, total growing).
2026-04-12 13:35:06 +07:00
viettranx c3d07bf6e9 fix(vault): reset enrichment progress counter on rescan start
Start() now resets done=0 before broadcasting, preventing stale
counter from previous run causing progress bar to jump/reset.
2026-04-12 13:31:40 +07:00
viettranx 7810b8b78a fix(vault): agent filter support + rescan agent_key path matching
- Add AgentID to VaultTreeOptions, pass agent_id filter through
  backend tree endpoint and frontend hook
- Fix rescan inferOwnerFromPath: match root-level {agent_key}/...
  folders against agentMap (workspace uses agent_key directly,
  not agents/ prefix). Keep legacy agents/ prefix for compat
- Preserve full relPath for DB storage in all patterns
- Show filename with extension in tree (use path basename, not title)
- Start tree folders collapsed (prevent stuck loading state)
- Remove orphaned useVaultGraphData call from vault-page
- Tenant isolation verified: agentMap built per-tenant via scopeClause
2026-04-12 13:25:15 +07:00
viettranx e043241dea fix(tools): add missing tools to goclaw group and relax agent update ownership
- Add 10 missing tools to the goclaw group in policy.go: skill_manage,
  publish_skill, use_skill, delegate, memory_expand, knowledge_graph_search,
  vault_search, create_audio, datetime, heartbeat. Fixes skill creation
  permission denied when agents use group:goclaw allow list.

- Relax agent update ownership check: tenant admins can now update any
  agent in their tenant (adminMiddleware + tenant-scoped GetByID already
  ensures proper authorization). Previously only agent owner or system
  owner could update.

- Improve agent update error logging: include user_id and tenant_id in
  structured log, return actual error message instead of generic
  "internal error" for better debugging.
2026-04-12 12:37:53 +07:00
viettranx 35ce8cc2c5 feat(vault): tree sidebar with lazy-load folder hierarchy
Replace flat paginated document list with collapsible tree view.
Backend: two-query-per-level approach (files + DISTINCT deeper paths
for virtual folder derivation), path parsing in Go for PG+SQLite
compatibility, text_pattern_ops index for prefix range scans.
Frontend: VaultTree component with doc_type colored icons, compact
single-line nodes, truncate-middle filenames, scope dots, hover
tooltip for date. Sidebar expanded to 384px on large screens.

Also fixes pre-existing SQLite scanVaultDocRow missing path_basename.
2026-04-12 12:37:10 +07:00
viettranx 31b2662ecf feat(tools): API key management in web search chain form
API keys for Exa, Tavily, and Brave can now be set directly in the
web search provider chain settings dialog. Keys are saved to
config_secrets (AES-256-GCM encrypted, tenant-scoped) and stripped
from the persisted settings blob. Raw key values are never returned
in API responses — only boolean set/unset status.

Backend:
- Add ConfigSecretsStore to BuiltinToolsHandler
- extractAndSaveSecrets: parse api_key from settings, save to
  config_secrets, strip before persisting to builtin_tool_tenant_configs
- getSecretsStatus: returns boolean map per tool (never raw values)
- Enriched handleList response with secrets_set per tool

Frontend:
- Per-provider API key input in web search chain cards
- "Key set" status badge when key exists, "Change" button to replace
- Staged keys sent with settings on save, backend extracts
2026-04-12 12:02:40 +07:00
viettranx 81cd6ef24b fix(ui): add API key hint to web search chain form
Users had no indication where to configure Exa/Tavily/Brave API keys.
Added hint text pointing to Config → Secrets management.
2026-04-12 11:51:23 +07:00
viettranx f994335cb1 fix(ui): always show Settings button for tools with dedicated forms
hasEditableSettings() only checked if tool.settings was non-empty,
hiding the Settings button for web_search, tts, etc. when no settings
existed yet. Now tools with dedicated form components always show the
button so admins can create initial settings.
2026-04-12 11:45:06 +07:00
viettranx c07aa5cf4b feat(ui): web_search chain editor + TTS settings form
Add dedicated UI forms for web_search and TTS builtin tool settings,
replacing the raw JSON editor for these tools.

- web-search-chain-form: drag-drop provider order (Exa/Tavily/Brave),
  DuckDuckGo locked as always-on fallback, color-coded rails,
  per-provider max_results config
- tts-provider-form: primary provider selector + auto mode dropdown
  with inline descriptions (off/inbound/tagged/always)
- Wire both into builtin-tool-settings-dialog dispatch
- i18n strings for en/vi/zh
2026-04-12 11:41:16 +07:00
viettranx 3a8470b8da docs(tools): tenant tool config refactor - phases 7-8 completion
- 03-tools-system.md: Add § 14 Per-Tenant Tool Configuration (4-tier overlay)
  - Comprehensive overlay explanation (per-agent > tenant > global > hardcoded)
  - Opt-in pattern for tool authors with code example
  - Schema contracts for web_search (provider_order), web_fetch (policy), tts (primary)
  - Secret vs non-secret split guidance
  - Tenant admin workflow (Settings → Builtin Tools UI)
  - Feature flag documentation (TenantScopedSingletons)

- 17-changelog.md: Expand Per-Tenant Tool Configuration entry with phase details
  - Phase 5: Builtin tools settings editor on web UI
  - Phase 7 rest (30a40bbe): Exa + Tavily web search providers
    - Credit @kaitranntt for PR 825 original work
    - 11 new unit tests, provider_order config
  - Phase 8 (def1712f, 43ee918b): Tenant-aware singleton pools
    - web_fetch domain policy resolver (6 new tests)
    - tts primary provider resolver (5 new tests)
    - Feature flag gating (LRU pool, 64 tenant limit, 30 min idle timeout)

All phases 1-8 now shipped on dev branch. Docs integrated with existing
23-multi-tenant-architecture.md § 14 reference. Ready for Phase 9 completion.
2026-04-12 11:25:12 +07:00
viettranx 43ee918b9e feat(tools): tenant-aware TTS primary provider resolution
TTS tool now reads per-tenant primary provider override from
BuiltinToolSettingsFromCtx before falling back to the manager's
default. Same per-call resolution pattern as web_search/web_fetch.

Simpler path A approach (no pool) — tenant can select which
registered provider their agents use. Per-tenant API keys deferred
to a future pool implementation if needed.

- Add resolvePrimary(ctx, mgr) with ttsOverride struct
- Refactor Execute to use resolved primary with fallback chain
- 5 new unit tests for tenant provider override scenarios
2026-04-12 11:22:14 +07:00
viettranx def1712f3b feat(tools): tenant-aware web_fetch domain policy resolution
web_fetch now reads per-tenant policy overrides from
BuiltinToolSettingsFromCtx before falling back to the tool's
default policy. Same per-call resolution pattern as web_search.

- Add resolvePolicy(ctx) with webFetchPolicyOverride struct
- Refactor Execute + doFetch + fetchRawContent to use webFetchPolicy
- InProcessExtractor also resolves policy from ctx
- Remove isDomainAllowed/isDomainBlocked (replaced by matchDomainList)
- 6 new unit tests for tenant policy override scenarios
2026-04-12 11:19:00 +07:00
viettranx 30a40bbe59 feat(tools): add Exa + Tavily web search providers with ranked ordering
Port Exa and Tavily provider clients from PR #825 into the tenant-aware
overlay architecture (builtin_tool_tenant_configs.settings).

- Add ExaConfig + TavilyConfig to WebToolsConfig with provider_order
- Add Exa HTTP client (api.exa.ai/search, x-api-key auth)
- Add Tavily HTTP client (api.tavily.com/search, Bearer auth)
- Extend Brave + DDG constructors with per-provider maxResults
- Add config_secrets plumbing for exa/tavily API keys (apply/collect/mask/strip)
- Refactor gateway_setup.go to use WebSearchConfigFromConfig
- Add NormalizeWebSearchProviderOrder (DDG always last, dedup, unknown skip)
- Extract web_search_config.go (builder, normalizer, shared helpers)
- 11 new unit tests for provider order, builder, clamp, normalize

Credit: @kaitranntt (PR #825) for the original Exa + Tavily implementation.
2026-04-12 11:09:32 +07:00
viettranx 7ae9721c2b docs(contrib): tenant-scope guard rules for admin writes
Role checks are not tenant checks — a non-master tenant admin holds
RoleAdmin in their own tenant and passes role-only middleware by
design. CLAUDE.md gains a one-line directive pointing at CONTRIBUTING
for the full decision table + anti-patterns. CONTRIBUTING gains:

- Target-table decision table (global vs tenant-scoped) with the
  matching guard for each (requireMasterScope vs requireTenantAdmin).
- Shared predicate reference: store.IsMasterScope(ctx).
- Anti-pattern list for reviewers: writes to no-tenant_id tables
  without master-scope check, SQL tenant_id IS NULL arms on write
  paths, role-only admin gates, revoke/delete handlers that skip
  pre-fetch ownership verification.
2026-04-12 10:48:31 +07:00
viettranx 714600f2a9 docs: tenant tool config 4-tier overlay + Phase 0b security hotfix
- docs/03-tools-system.md § 14: 4-tier overlay architecture, opt-in
  pattern for tool authors, secret vs non-secret split, cache
  invalidation, master-scope guard pattern.
- docs/17-changelog.md: concise Security + Added entries for the
  2026-04-12 hotfix and refactor.
- docs/23-multi-tenant-architecture.md: extend Per-Tenant Overrides
  table with settings column + cross-reference to § 14; add
  master-scope guard row to the Security table.

Phase 9 of tenant tool config refactor. Documents what shipped in
commits b419f352, 933c2e10, 56eb6869, 96e38c59, ed32f6e6, fbbba5e8,
6d7473b5, 1e5e84d5.
2026-04-12 10:44:05 +07:00
viettranx 1e5e84d55c feat(ui): tenant-scope aware builtin tool settings editor
Phase 5 of tenant tool config refactor. Wires the existing builtin tools
page + settings dialog to the tenant-config HTTP endpoint shipped in
Phase 4, so tenant admins can edit per-tenant tool overrides from the
same "Settings" button master admins already use.

- BuiltinToolData DTO gains tenant_settings (already returned by the
  GET /v1/tools/builtin enrich path when request is tenant-scoped).
- useBuiltinTools hook gains setTenantSettings + clearTenantSettings —
  both hit PUT /v1/tools/builtin/{name}/tenant-config, with null clearing
  the settings column while preserving tenant_enabled (column-list
  upsert on the store side, shipped in Phase 2).
- BuiltinToolSettingsDialog accepts a tenantScope flag. In tenant mode:
    - initial values come from tenant_settings ?? settings (pre-fill
      with global defaults when creating a fresh override)
    - a small badge + hint line communicates the mode
    - optional "Reset to global default" appears when an override exists
- BuiltinToolsPage branches the save callback: master scope → updateTool
  (global settings), non-master → setTenantSettings. The backend enforces
  the same rule defensively per Phase 0b, so this branch is a UX guard
  (avoid 403) not a security boundary.
- New en/vi/zh keys: tenantOverrideBadge, tenantOverrideHint,
  tenantOverrideNewHint, resetToGlobalDefault.

Verified: pnpm build clean (tsc -b + vite).
2026-04-12 10:27:44 +07:00
viettranx 6d7473b56f fix(http): master-scope guards on builtin_tools, packages, api-keys
Phase 0b of tenant tool config refactor. Closes 3 privilege-escalation
vulnerabilities in the same bug class as commit b419f352 (Phase 1
config.* hotfix):

- CRITICAL: PUT /v1/tools/builtin/{name} — non-master tenant admin
  could overwrite global builtin_tools.settings, corrupting tool
  defaults for every tenant.
- CRITICAL: POST /v1/packages/install|uninstall — non-master tenant
  admin could run pip/npm/apk server-wide. Supply-chain vector.
- HIGH: POST /v1/api-keys/{id}/revoke (HTTP + WS) — tenant admin
  could revoke NULL-tenant system keys because store SQL matches
  (tenant_id = \$N OR tenant_id IS NULL).

Implementation:
- Export store.IsMasterScope as the single predicate; rewire Phase 1
  config.* middleware to delegate (no behaviour change).
- Add http.requireMasterScope helper symmetric to requireTenantAdmin.
- Guard handleUpdate (builtin_tools) and handleInstall/handleUninstall
  (packages) with master-scope check before any mutation or shell exec.
- Fix api_keys.Revoke at the handler layer: fetch key via new
  APIKeyStore.Get, verify key.TenantID matches caller tenant for
  non-owner callers. Applies to both HTTP and WS paths.
- Harden WS router to inject role into ctx so store.IsOwnerRole works
  from WS handlers (closes a latent drift between the HTTP and WS
  layers that broke the initial WS api_keys.revoke fix).
- Drop unused APIKeyStore.Delete (YAGNI + removes dormant vuln with
  the same tenant_id IS NULL arm).
- Emit security.tenant_scope_violation and security.api_key_revoke_
  forbidden slog events on every rejection for future SIEM alerting.
- New MsgMasterScopeRequired i18n key + en/vi/zh catalogs.

Tests cover the guard predicate, all 3 HTTP endpoints, and the full
WS api_keys.revoke matrix (cross-tenant deny, system-key deny for
tenant admins, system owner bypass, own-tenant happy path). 14 other
admin-gated write endpoints verified safe by static audit.
2026-04-12 10:24:07 +07:00
viettranx fbbba5e8bb feat(tools): tenant-aware web_search provider chain resolution
Refactor web_search Execute to resolve its provider chain per-request
via BuiltinToolSettingsFromCtx instead of iterating the singleton's
hardcoded provider list. Tenant admins can now reorder or disable
providers via builtin_tool_tenant_configs.settings using the shape
{"provider_order":[...], "brave":{"enabled":false}, ...}. Unknown
provider names in provider_order are logged + skipped — no injection
path via this settings blob. Malformed JSON falls back to defaults
(fail-open so a bad admin paste never crashes the tool). Secrets stay
in config_secrets — tenant cannot inject API keys through this path.
11 unit tests cover reorder, filter, disable, unknown-skip, malformed,
global-layer, and tenant-vs-global precedence.

Note: This is the MVP tenant adoption half of the plan's Phase 7. The
Exa + Tavily feature port from PR 825 and coordination with its author
is deferred to a separate follow-up (the port is net-new feature work
orthogonal to the overlay adoption).
2026-04-12 09:01:01 +07:00
viettranx ed32f6e6ed test(tools): verify tenant tool settings flow through media provider chain
Phase 6 of the tenant tool config refactor adopts zero production code
changes for media tools — media_provider_chain.go:77 already reads via
BuiltinToolSettingsFromCtx, and the Phase 3 merge automatically feeds
tenant overrides into that call. This adds 5 verification tests proving
the end-to-end flow: tenant layer beats global, per-agent arg still wins
over tenant, fallback to global when no tenant row, tenant-only layer
works, and cross-tool isolation (create_image override does not leak
into create_audio). Covers the 4-tier overlay on a real tool without
touching any media tool's Execute signature.
2026-04-12 09:00:58 +07:00
viettranx 96e38c5912 feat(http): tenant-config settings DTO + GET endpoint
Extend PUT /v1/tools/builtin/{name}/tenant-config to accept optional
enabled + settings fields (at least one required). Add GET endpoint for
the combined tenant override view. Enrich list handler with tenant_settings
alongside existing tenant_enabled. Pointer *bool + json.RawMessage DTO
distinguishes "not set" from "explicit false/null". 16KB body cap via
MaxBytesReader prevents trivial DoS. isValidSettingsJSON rejects non-object
non-null payloads so tool-specific schemas stay predictable. Backward
compat: old clients sending {"enabled": bool} still decode cleanly.
17 tests: validator subcases + DTO decode + stub-backed httptest handler.
2026-04-12 08:55:25 +07:00
viettranx 56eb686934 feat(agent): tenant tool settings overlay via loop ctx injection
Plumb per-tenant tool settings into the agent Loop without touching any
tool's Execute signature. Adds WithTenantToolSettings ctx helper and
rewrites BuiltinToolSettingsFromCtx with fast-path merge semantics —
tenant layer wins over global defaults at tool-name level (no field-level
deep merge). Resolver preloads ListAllSettings for the agent's tenant at
Loop construction; store errors log + fall back to global. Zero allocs
on single-tier reads. Tier 1 (future per-agent override) is reserved and
documented in context_keys.go. 8 unit tests cover empty / single-tier /
both-merged / RunContext fallback / fast-path semantics.
2026-04-12 08:55:20 +07:00
viettranx 933c2e10d9 feat(store): tenant tool settings with column-preservation upsert
Wake up the dead builtin_tool_tenant_configs.settings column (exists in
migrations 000027/SQLite 1180 since v3 tenant foundation, never read/written).
Add GetSettings/SetSettings/ListAllSettings interface methods with a
json.RawMessage Settings field on BuiltinToolTenantConfig. Both DBs use
explicit column-list DO UPDATE SET so Set(enabled) and SetSettings(raw)
never clobber each other. Add ErrInvalidTenant sentinel so nil-tenant
callers fail fast (no silent master fallback). ListAll now filters
enabled IS NOT NULL — rows created via SetSettings stay in their own lane.
9 SQLite unit tests + 4 new PG integration tests (round-trip,
column coexist, cross-tenant isolation, nil-tenant guard).
2026-04-12 08:55:15 +07:00
viettranx b419f35248 fix(gateway): guard config.* methods against non-master tenant scope
Non-master tenant admin calling config.patch/apply/get/schema would
corrupt master *config.Config + config.json on disk via m.cfg.ReplaceFrom
and m.Save, leaking master config across tenants. Add requireMasterScope
middleware that rejects non-master tenant ctx (system-owner role still
bypasses for consistency with requireTenantAdmin). Chain before
requireOwner on all 4 config.* methods. Adds MsgConfigMasterScopeOnly
i18n key (en/vi/zh) and 10 unit tests covering helper + middleware +
chained fail-closed behavior.
2026-04-12 08:55:09 +07:00
viettranx dc482ff169 ci: run integration suite against pgvector service container
Integration tests were running locally only — CI never exercised
the 29 v3_*_test.go files under tests/integration/. This left the
session tenant-isolation + vault scan-arity bugs undetected until
a manual run this session.

Changes:
- tests/integration/v3_test_helper.go: testDB() now calls
  pg.InitSqlx(db) inside sharedDBOnce. Previously the test suite
  relied on whichever test ran first happening to call InitSqlx,
  so running with `-run <filter>` could segfault with a nil
  pkgSqlxDB. Removes the ordering-dependency land mine.
- .github/workflows/ci.yaml: add services.pg block running
  pgvector/pgvector:pg18 with pg_isready healthcheck, set
  TEST_DATABASE_URL at job level, and add a new "Integration
  tests" step after unit tests. Uses -timeout=180s (vs 90s unit)
  because the first test runs migrations from scratch.

Local integration suite: 2.9s. CI cold start expected ~30-60s
with image pull + migrations.
2026-04-12 07:18:01 +07:00
viettranx 8893cdbf8b fix(store/pg): session tenant isolation + vault scan arity
Two unrelated integration-test failures, both real source bugs:

1. Session ListPaged/ListPagedRich leaked cross-tenant data.
   buildSessionFilter only honored opts.TenantID and never read
   tenant from ctx — so callers relying on ctx scoping (the common
   case) got no tenant filter at all. List() already had the
   correct pattern using store.TenantIDFromContext(ctx) gated by
   !store.IsCrossTenant(ctx). Replicate inside buildSessionFilter;
   thread ctx into the helper; opts.TenantID still wins as admin
   override. Fixes TestStoreSession_ListPaged_Pagination/tenant_isolation
   and TestStoreSession_ListPagedRich_TenantIsolation.

2. Vault GetDocument / GetDocumentByBasename panicked with
   "sql: expected 15 destination arguments in Scan, not 14".
   Migration 000047 added `path_basename` as a generated column;
   the SELECT lists were updated but the Scan arg lists were not.
   GetDocumentByID had the correct 15-target pattern — copy it.
   vaultDocRow.PathBasename field already exists. Audit sweep of
   vault_links.go + vault_documents_enrichment.go confirms no
   other drift. Fixes TestStoreVault_UpsertAndGetDocument.
2026-04-12 07:17:54 +07:00