feat(vault): tenant-wide rescan with nullable agent_id + media preview

Vault rescan redesigned from per-agent to tenant-wide:
- POST /v1/vault/rescan replaces POST /v1/agents/{id}/vault/rescan
- agent_id nullable in vault_documents (PG migration 046 + SQLite v14)
- Path-based scope inference: agents/{key}/ → personal, teams/{uuid}/ → team, root → shared
- Interceptor sets agent_id=NULL for team-scoped file writes
- Enrichment worker batch key handles empty agent_id
- web-fetch/ directory excluded from vault scan at any depth
- Media preview: images render via authenticated blob URL, binary files show metadata
- Scan button no longer requires agent selection
This commit is contained in:
viettranx committed 2026-04-10 16:32:54 +07:00
1 parent 139b454efd
commit 9f77bfe711
49 files changed
+1550 -312

No files matched your search

+60 -29
View File
@@ -181,17 +181,16 @@ func runGateway() {
}
setupMemoryEmbeddings(pgStores, providerRegistry)
// Resolve background provider for consolidation + vault enrichment.
// Fallback: background.provider → agent.default_provider → first registered provider.
bgProvider, bgModel := resolveBackgroundProvider(cfg, providerRegistry)
// V3: Wire consolidation pipeline (episodic → semantic → KG → dreaming)
if pgStores.Episodic != nil {
var consolidationProvider providers.Provider
if names := providerRegistry.ListForTenant(providers.MasterTenantID); len(names) > 0 {
consolidationProvider, _ = providerRegistry.GetForTenant(providers.MasterTenantID, names[0])
}
if consolidationProvider != nil {
// Create KG extractor for semantic worker (entity/relation extraction from episodic summaries)
if bgProvider != nil {
var kgExtractor *kg.Extractor
if pgStores.KnowledgeGraph != nil {
kgExtractor = kg.NewExtractor(consolidationProvider, consolidationProvider.DefaultModel(), 0)
kgExtractor = kg.NewExtractor(bgProvider, bgModel, 0)
}
cleanupConsolidation := consolidation.Register(consolidation.ConsolidationDeps{
EpisodicStore: pgStores.Episodic,
@@ -199,36 +198,28 @@ func runGateway() {
KGStore: pgStores.KnowledgeGraph,
SessionStore: pgStores.Sessions,
EventBus: domainBus,
Provider: consolidationProvider,
Model: consolidationProvider.DefaultModel(),
Provider: bgProvider,
Model: bgModel,
Extractor: kgExtractor,
// Per-agent dreaming overrides (MemoryConfig.Dreaming JSONB).
AgentStore: pgStores.Agents,
AgentStore: pgStores.Agents,
})
defer cleanupConsolidation()
slog.Info("consolidation pipeline registered")
slog.Info("consolidation pipeline registered", "provider", bgProvider.Name(), "model", bgModel)
} else {
slog.Warn("consolidation pipeline skipped: no provider available")
}
}
// V3: Wire vault enrichment worker (async summary + embedding + auto-linking).
// Resolves provider independently from consolidation pipeline.
if pgStores.Vault != nil {
var vaultProvider providers.Provider
if names := providerRegistry.ListForTenant(providers.MasterTenantID); len(names) > 0 {
vaultProvider, _ = providerRegistry.GetForTenant(providers.MasterTenantID, names[0])
}
if vaultProvider != nil {
cleanupVaultEnrich := vault.RegisterEnrichWorker(vault.EnrichWorkerDeps{
VaultStore: pgStores.Vault,
Provider: vaultProvider,
Model: vaultProvider.DefaultModel(),
EventBus: domainBus,
})
defer cleanupVaultEnrich()
slog.Info("vault enrichment worker registered")
}
if pgStores.Vault != nil && bgProvider != nil {
cleanupVaultEnrich := vault.RegisterEnrichWorker(vault.EnrichWorkerDeps{
VaultStore: pgStores.Vault,
Provider: bgProvider,
Model: bgModel,
EventBus: domainBus,
})
defer cleanupVaultEnrich()
slog.Info("vault enrichment worker registered", "provider", bgProvider.Name(), "model", bgModel)
}
loadBootstrapFiles(pgStores, workspace, agentCfg)
@@ -319,7 +310,11 @@ func runGateway() {
// Wire dependencies for system prompt preview parity.
if agentsH != nil {
agentsH.SetPreviewDeps(toolsReg, skillsLoader)
agentsH.SetPreviewStores(pgStores.Teams, pgStores.AgentLinks)
var skillAccess store.SkillAccessStore
if pgStores.Skills != nil {
skillAccess, _ = pgStores.Skills.(store.SkillAccessStore)
}
agentsH.SetPreviewStores(pgStores.Teams, pgStores.AgentLinks, skillAccess)
}
// External wake/trigger API
@@ -543,3 +538,39 @@ func runGateway() {
sigCh: sigCh,
})
}
// resolveBackgroundProvider picks the LLM provider+model for background workers
// (vault enrichment, consolidation). Fallback chain:
//
// background.provider/model → agent.default_provider/model → first registered provider.
func resolveBackgroundProvider(cfg *config.Config, reg *providers.Registry) (providers.Provider, string) {
try := func(name, model string) (providers.Provider, string, bool) {
if name == "" {
return nil, "", false
}
p, err := reg.GetForTenant(providers.MasterTenantID, name)
if err != nil || p == nil {
return nil, "", false
}
if model == "" {
model = p.DefaultModel()
}
return p, model, true
}
// 1. Explicit background config
if p, m, ok := try(cfg.Gateway.BackgroundProvider, cfg.Gateway.BackgroundModel); ok {
return p, m
}
// 2. Agent default provider
if p, m, ok := try(cfg.Agents.Defaults.Provider, cfg.Agents.Defaults.Model); ok {
return p, m
}
// 3. First registered provider (legacy fallback)
if names := reg.ListForTenant(providers.MasterTenantID); len(names) > 0 {
if p, m, ok := try(names[0], ""); ok {
return p, m
}
}
return nil, ""
}
+1 -1
View File
@@ -175,7 +175,7 @@ func (d *gatewayDeps) wireHTTPHandlersOnServer(
// V3: Knowledge Vault document API
if d.pgStores != nil && d.pgStores.Vault != nil {
d.server.SetVaultHandler(httpapi.NewVaultHandler(d.pgStores.Vault, d.pgStores.Teams, d.workspace, d.domainBus))
d.server.SetVaultHandler(httpapi.NewVaultHandler(d.pgStores.Vault, d.pgStores.Teams, d.workspace, d.domainBus, d.pgStores.Agents, d.pgStores.Teams))
}
// V3: Episodic memory summaries API
+4
View File
@@ -106,6 +106,10 @@ func seedConfigForContext(ctx context.Context, sc store.SystemConfigStore, cfg *
setBool("gateway.tool_status", cfg.Gateway.ToolStatus)
setInt("gateway.task_recovery_interval_sec", cfg.Gateway.TaskRecoveryIntervalSec)
// Background workers
set("background.provider", cfg.Gateway.BackgroundProvider)
set("background.model", cfg.Gateway.BackgroundModel)
// Tools
set("tools.profile", cfg.Tools.Profile)
setInt("tools.rate_limit_per_hour", cfg.Tools.RateLimitPerHour)