mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-18 08:14:39 +00:00
* fix(vault): prevent vault_read id-namespace collision vault_search was leaking KG/episodic entity ids into result sets even when narrow `types` were requested, and callers then passed those ids to vault_read which returned a generic "document not found". The cause was threefold: 1. `types` filter was only applied to the vault fan-out; KG and episodic ran unconditionally. Now gated by shouldFanout(types, key). 2. vault_search output lacked a per-source tool hint. Each result now ends with " → use <tool>" naming the correct follow-up (vault_read, knowledge_graph_search, or memory_search). 3. vault_read miss returned "document not found" without checking whether the id belonged to a foreign namespace. It now probes KG then episodic and returns a namespace-specific redirect error. Stores are injected via SetKGStore/SetEpisodicStore, nil-safe, tenant-scoped. Adds red→green characterization tests plus an end-to-end integration scenario seeding a vault doc + KG entity with identical basenames. * test(agent): bump none-mode prompt size budget to 3100 vault_read wiring (#948) added ~95 chars to read_file tool summary, pushing none-mode prompt from <3000 to 3075 chars. Bump budget to 3100 (~775 tokens) to match the intentional addition. * test(integration): ensure data_migrations table exists in reset helper The reset helper runs before RunPendingHooks, but RunPendingHooks is what normally creates data_migrations. On a fresh CI database the DELETE fails with 'relation does not exist'. Create the table defensively so reset works regardless of execution order. * refactor(vault): per-source id fields + wire episodic into search Align vault_search output fields with downstream tool input params: doc_id (vault_read), entity_id (knowledge_graph_search), episodic_id (memory_expand). Prevents LLMs from pattern-matching a generic `id:` and misrouting a foreign-namespace uuid into vault_read. Fallback redirect in vault_read now quotes id + names the correct param so the LLM can self-correct in one turn. Also wire stores.Episodic into VaultSearchService (stale comment claimed pending-impl; PGEpisodicStore has existed and been in use since v3). Unifies search fan-out with vault_read namespace probe. --------- Co-authored-by: viettranx <viettranx@gmail.com>
93 lines
3.3 KiB
Go
93 lines
3.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"log/slog"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/eventbus"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
"github.com/nextlevelbuilder/goclaw/internal/tools"
|
|
"github.com/nextlevelbuilder/goclaw/internal/vault"
|
|
)
|
|
|
|
// wireVault wires Knowledge Vault tools and interceptors into the tool registry.
|
|
// All wiring is skipped if stores.Vault is nil.
|
|
// Pattern mirrors wireExtras KG wiring: register tools, set stores, set interceptors.
|
|
// wireVault wires Knowledge Vault tools and interceptors into the tool registry.
|
|
// Returns the shared VaultInterceptor for use by other subsystems (e.g. agent upload hook).
|
|
// Returns nil if stores.Vault is nil.
|
|
func wireVault(stores *store.Stores, toolsReg *tools.Registry, workspace string, bus eventbus.DomainEventBus) *tools.VaultInterceptor {
|
|
if stores.Vault == nil {
|
|
return nil
|
|
}
|
|
|
|
// Register vault tools — these are always available when vault store is present.
|
|
vaultSearchTool := tools.NewVaultSearchTool()
|
|
toolsReg.Register(vaultSearchTool)
|
|
|
|
// vault_read: fetch full content of a vault doc by doc_id (chained from vault_search).
|
|
vaultReadTool := tools.NewVaultReadTool()
|
|
vaultReadTool.SetVaultStore(stores.Vault)
|
|
vaultReadTool.SetWorkspace(workspace)
|
|
// Namespace-fallback stores — nil-safe; used to return a redirect error
|
|
// when a caller passes a KG / episodic id to vault_read by mistake.
|
|
if stores.KnowledgeGraph != nil {
|
|
vaultReadTool.SetKGStore(stores.KnowledgeGraph)
|
|
}
|
|
if stores.Episodic != nil {
|
|
vaultReadTool.SetEpisodicStore(stores.Episodic)
|
|
}
|
|
toolsReg.Register(vaultReadTool)
|
|
|
|
// Build VaultSearchService: fan-out across vault + episodic + KG.
|
|
// Each store is nil-safe inside the service (skipped when absent).
|
|
searchSvc := vault.NewVaultSearchService(stores.Vault, stores.Episodic, stores.KnowledgeGraph)
|
|
vaultSearchTool.SetSearchService(searchSvc)
|
|
|
|
// Build shared VaultInterceptor for read/write tool vault registration.
|
|
vaultIntc := tools.NewVaultInterceptor(stores.Vault, workspace, bus)
|
|
|
|
// Wire interceptor into write_file (registers doc on write).
|
|
if writeTool, ok := toolsReg.Get("write_file"); ok {
|
|
if wt, ok := writeTool.(*tools.WriteFileTool); ok {
|
|
wt.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
|
|
// Wire interceptor into read_file (lazy hash sync on read).
|
|
if readTool, ok := toolsReg.Get("read_file"); ok {
|
|
if rt, ok := readTool.(*tools.ReadFileTool); ok {
|
|
rt.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
|
|
// Wire interceptor into media generation tools.
|
|
if imgTool, ok := toolsReg.Get("create_image"); ok {
|
|
if it, ok := imgTool.(*tools.CreateImageTool); ok {
|
|
it.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
if vidTool, ok := toolsReg.Get("create_video"); ok {
|
|
if vt, ok := vidTool.(*tools.CreateVideoTool); ok {
|
|
vt.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
if audTool, ok := toolsReg.Get("create_audio"); ok {
|
|
if at, ok := audTool.(*tools.CreateAudioTool); ok {
|
|
at.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
if ttsTool, ok := toolsReg.Get("tts"); ok {
|
|
if tt, ok := ttsTool.(*tools.TtsTool); ok {
|
|
tt.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
if editTool, ok := toolsReg.Get("edit"); ok {
|
|
if et, ok := editTool.(*tools.EditTool); ok {
|
|
et.SetVaultInterceptor(vaultIntc)
|
|
}
|
|
}
|
|
|
|
slog.Info("vault tools registered", "tools", "vault_search,vault_read,create_image,create_video,create_audio,tts,edit")
|
|
return vaultIntc
|
|
}
|