mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-10 08:11:23 +00:00
6eb33f9cea
Add independent `share_memory` config flag to control memory and knowledge graph sharing separately from workspace folder isolation. - Add ShareMemory field to WorkspaceSharingConfig - Decouple WithSharedMemory(ctx) from shouldShareWorkspace() in loop.go - Add shouldShareMemory() helper independent of workspace sharing - Fix KG Traverse CTE to scope user_id in recursive step (pre-existing bug) - Add memory toggle UI with violet styling in workspace sharing section - Add i18n translations (en/vi/zh) for new memory sharing controls - Add unit tests for shouldShareMemory() independence
113 lines
2.7 KiB
Go
113 lines
2.7 KiB
Go
package pg
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// Traverse walks the knowledge graph from startEntityID up to maxDepth hops
|
|
// using a recursive CTE. Returns all reachable entities (excluding the start node).
|
|
// A 5-second statement timeout is applied for safety.
|
|
func (s *PGKnowledgeGraphStore) Traverse(ctx context.Context, agentID, userID, startEntityID string, maxDepth int) ([]store.TraversalResult, error) {
|
|
if maxDepth <= 0 {
|
|
maxDepth = 3
|
|
}
|
|
|
|
aid := mustParseUUID(agentID)
|
|
startID := mustParseUUID(startEntityID)
|
|
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback() //nolint:errcheck
|
|
|
|
if _, err := tx.ExecContext(ctx, `SET LOCAL statement_timeout = '5000'`); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := tx.QueryContext(ctx, `
|
|
WITH RECURSIVE paths AS (
|
|
SELECT
|
|
e.id, e.agent_id, e.user_id, e.external_id,
|
|
e.name, e.entity_type, e.description,
|
|
e.properties, e.source_id, e.confidence,
|
|
e.created_at, e.updated_at,
|
|
1 AS depth,
|
|
ARRAY[e.id::text] AS path,
|
|
''::text AS via
|
|
FROM kg_entities e
|
|
WHERE e.id = $1 AND e.agent_id = $2 AND e.user_id = $3
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
e.id, e.agent_id, e.user_id, e.external_id,
|
|
e.name, e.entity_type, e.description,
|
|
e.properties, e.source_id, e.confidence,
|
|
e.created_at, e.updated_at,
|
|
p.depth + 1,
|
|
p.path || e.id::text,
|
|
r.relation_type
|
|
FROM paths p
|
|
JOIN kg_relations r ON p.id = r.source_entity_id AND r.user_id = $3
|
|
JOIN kg_entities e ON r.target_entity_id = e.id AND e.user_id = $3
|
|
WHERE p.depth < $4
|
|
AND NOT e.id::text = ANY(p.path)
|
|
)
|
|
SELECT
|
|
id, agent_id, user_id, external_id,
|
|
name, entity_type, description,
|
|
properties, source_id, confidence,
|
|
created_at, updated_at,
|
|
depth, path, via
|
|
FROM paths WHERE depth > 1`,
|
|
startID, aid, userID, maxDepth,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var results []store.TraversalResult
|
|
for rows.Next() {
|
|
var e store.Entity
|
|
var props []byte
|
|
var createdAt, updatedAt time.Time
|
|
var depth int
|
|
var path []string
|
|
var via string
|
|
|
|
if err := rows.Scan(
|
|
&e.ID, &e.AgentID, &e.UserID, &e.ExternalID,
|
|
&e.Name, &e.EntityType, &e.Description,
|
|
&props, &e.SourceID, &e.Confidence,
|
|
&createdAt, &updatedAt,
|
|
&depth, pq.Array(&path), &via,
|
|
); err != nil {
|
|
continue
|
|
}
|
|
if len(props) > 0 {
|
|
json.Unmarshal(props, &e.Properties) //nolint:errcheck
|
|
}
|
|
e.CreatedAt = createdAt.UnixMilli()
|
|
e.UpdatedAt = updatedAt.UnixMilli()
|
|
|
|
results = append(results, store.TraversalResult{
|
|
Entity: e,
|
|
Depth: depth,
|
|
Path: path,
|
|
Via: via,
|
|
})
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return results, tx.Commit()
|
|
}
|