mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-06-07 08:13:08 +08:00
improve nodes relay path and accelerate memory search with token index cache
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
- `action=status|describe`:查看已配对节点状态与能力矩阵
|
- `action=status|describe`:查看已配对节点状态与能力矩阵
|
||||||
- `action=run|invoke|camera_snap|screen_record|location_get`:已接入路由框架
|
- `action=run|invoke|camera_snap|screen_record|location_get`:已接入路由框架
|
||||||
- `mode=auto|p2p|relay`:默认 `auto`(优先 p2p,失败回退 relay)
|
- `mode=auto|p2p|relay`:默认 `auto`(优先 p2p,失败回退 relay)
|
||||||
|
- relay 已接入本地 handler 调用链,便于逐步替换为真实跨节点传输
|
||||||
|
|
||||||
实现位置:
|
实现位置:
|
||||||
- `pkg/nodes/types.go`
|
- `pkg/nodes/types.go`
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ A `nodes` tool control-plane PoC is now available:
|
|||||||
- `action=status|describe`: inspect paired node status and capability matrix
|
- `action=status|describe`: inspect paired node status and capability matrix
|
||||||
- `action=run|invoke|camera_snap|screen_record|location_get`: routing framework is in place
|
- `action=run|invoke|camera_snap|screen_record|location_get`: routing framework is in place
|
||||||
- `mode=auto|p2p|relay`: default `auto` (prefer p2p, fallback to relay)
|
- `mode=auto|p2p|relay`: default `auto` (prefer p2p, fallback to relay)
|
||||||
|
- relay now uses local handler invocation path, ready for real cross-node transport replacement
|
||||||
|
|
||||||
Implementation:
|
Implementation:
|
||||||
- `pkg/nodes/types.go`
|
- `pkg/nodes/types.go`
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
|||||||
toolsRegistry.Register(tools.NewExecTool(cfg.Tools.Shell, workspace, processManager))
|
toolsRegistry.Register(tools.NewExecTool(cfg.Tools.Shell, workspace, processManager))
|
||||||
toolsRegistry.Register(tools.NewProcessTool(processManager))
|
toolsRegistry.Register(tools.NewProcessTool(processManager))
|
||||||
nodesManager := nodes.NewManager()
|
nodesManager := nodes.NewManager()
|
||||||
nodesRouter := &nodes.Router{P2P: &nodes.StubP2PTransport{}, Relay: &nodes.StubRelayTransport{}}
|
nodesManager.Upsert(nodes.NodeInfo{ID: "local", Name: "local", Capabilities: nodes.Capabilities{Run: true, Invoke: true}, Online: true})
|
||||||
|
nodesManager.RegisterHandler("local", func(req nodes.Request) nodes.Response {
|
||||||
|
return nodes.Response{OK: true, Node: "local", Action: req.Action, Payload: map[string]interface{}{"echo": req.Args, "transport": "relay-local"}}
|
||||||
|
})
|
||||||
|
nodesRouter := &nodes.Router{P2P: &nodes.StubP2PTransport{}, Relay: &nodes.StubRelayTransport{Manager: nodesManager}}
|
||||||
toolsRegistry.Register(tools.NewNodesTool(nodesManager, nodesRouter))
|
toolsRegistry.Register(tools.NewNodesTool(nodesManager, nodesRouter))
|
||||||
|
|
||||||
if cs != nil {
|
if cs != nil {
|
||||||
|
|||||||
@@ -2,18 +2,22 @@ package nodes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager keeps paired node metadata and basic routing helpers.
|
// Manager keeps paired node metadata and basic routing helpers.
|
||||||
|
type Handler func(req Request) Response
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
nodes map[string]NodeInfo
|
nodes map[string]NodeInfo
|
||||||
|
handlers map[string]Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
return &Manager{nodes: map[string]NodeInfo{}}
|
return &Manager{nodes: map[string]NodeInfo{}, handlers: map[string]Handler{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Upsert(info NodeInfo) {
|
func (m *Manager) Upsert(info NodeInfo) {
|
||||||
@@ -51,6 +55,32 @@ func (m *Manager) List() []NodeInfo {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) RegisterHandler(nodeID string, h Handler) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if strings.TrimSpace(nodeID) == "" || h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.handlers[nodeID] = h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Invoke(req Request) (Response, bool) {
|
||||||
|
m.mu.RLock()
|
||||||
|
h, ok := m.handlers[req.Node]
|
||||||
|
m.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return Response{}, false
|
||||||
|
}
|
||||||
|
resp := h(req)
|
||||||
|
if strings.TrimSpace(resp.Node) == "" {
|
||||||
|
resp.Node = req.Node
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(resp.Action) == "" {
|
||||||
|
resp.Action = req.Action
|
||||||
|
}
|
||||||
|
return resp, true
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) PickFor(action string) (NodeInfo, bool) {
|
func (m *Manager) PickFor(action string) (NodeInfo, bool) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|||||||
@@ -57,10 +57,16 @@ func (s *StubP2PTransport) Send(ctx context.Context, req Request) (Response, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StubRelayTransport provides executable placeholder until real bridge lands.
|
// StubRelayTransport provides executable placeholder until real bridge lands.
|
||||||
type StubRelayTransport struct{}
|
type StubRelayTransport struct{ Manager *Manager }
|
||||||
|
|
||||||
func (s *StubRelayTransport) Name() string { return "relay" }
|
func (s *StubRelayTransport) Name() string { return "relay" }
|
||||||
func (s *StubRelayTransport) Send(ctx context.Context, req Request) (Response, error) {
|
func (s *StubRelayTransport) Send(ctx context.Context, req Request) (Response, error) {
|
||||||
_ = ctx
|
_ = ctx
|
||||||
return Response{OK: false, Node: req.Node, Action: req.Action, Error: "relay bridge not implemented yet"}, nil
|
if s.Manager == nil {
|
||||||
|
return Response{OK: false, Node: req.Node, Action: req.Action, Error: "relay manager not configured"}, nil
|
||||||
|
}
|
||||||
|
if resp, ok := s.Manager.Invoke(req); ok {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
return Response{OK: false, Node: req.Node, Action: req.Action, Error: "relay handler not found for node"}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ type memoryBlock struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cachedMemoryFile struct {
|
type cachedMemoryFile struct {
|
||||||
modTime time.Time
|
modTime time.Time
|
||||||
blocks []memoryBlock
|
blocks []memoryBlock
|
||||||
|
tokenIndex map[string][]int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemorySearchTool(workspace string) *MemorySearchTool {
|
func NewMemorySearchTool(workspace string) *MemorySearchTool {
|
||||||
@@ -206,12 +207,17 @@ func dedupeStrings(items []string) []string {
|
|||||||
|
|
||||||
// searchFile searches parsed markdown blocks with cache by file modtime.
|
// searchFile searches parsed markdown blocks with cache by file modtime.
|
||||||
func (t *MemorySearchTool) searchFile(path string, keywords []string) ([]searchResult, error) {
|
func (t *MemorySearchTool) searchFile(path string, keywords []string) ([]searchResult, error) {
|
||||||
blocks, err := t.getOrParseBlocks(path)
|
blocks, tokenIndex, err := t.getOrParseBlocks(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
candidate := candidateBlockIndexes(tokenIndex, keywords, len(blocks))
|
||||||
results := make([]searchResult, 0, 8)
|
results := make([]searchResult, 0, 8)
|
||||||
for _, b := range blocks {
|
for _, idx := range candidate {
|
||||||
|
if idx < 0 || idx >= len(blocks) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b := blocks[idx]
|
||||||
score := 0
|
score := 0
|
||||||
for _, kw := range keywords {
|
for _, kw := range keywords {
|
||||||
if strings.Contains(b.lower, kw) {
|
if strings.Contains(b.lower, kw) {
|
||||||
@@ -233,34 +239,35 @@ func (t *MemorySearchTool) searchFile(path string, keywords []string) ([]searchR
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemorySearchTool) getOrParseBlocks(path string) ([]memoryBlock, error) {
|
func (t *MemorySearchTool) getOrParseBlocks(path string) ([]memoryBlock, map[string][]int, error) {
|
||||||
st, err := os.Stat(path)
|
st, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
mod := st.ModTime()
|
mod := st.ModTime()
|
||||||
t.mu.RLock()
|
t.mu.RLock()
|
||||||
if c, ok := t.cache[path]; ok && c.modTime.Equal(mod) {
|
if c, ok := t.cache[path]; ok && c.modTime.Equal(mod) {
|
||||||
blocks := c.blocks
|
blocks := c.blocks
|
||||||
|
idx := c.tokenIndex
|
||||||
t.mu.RUnlock()
|
t.mu.RUnlock()
|
||||||
return blocks, nil
|
return blocks, idx, nil
|
||||||
}
|
}
|
||||||
t.mu.RUnlock()
|
t.mu.RUnlock()
|
||||||
|
|
||||||
blocks, err := parseMarkdownBlocks(path)
|
blocks, tokenIndex, err := parseMarkdownBlocks(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
t.cache[path] = cachedMemoryFile{modTime: mod, blocks: blocks}
|
t.cache[path] = cachedMemoryFile{modTime: mod, blocks: blocks, tokenIndex: tokenIndex}
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
return blocks, nil
|
return blocks, tokenIndex, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMarkdownBlocks(path string) ([]memoryBlock, error) {
|
func parseMarkdownBlocks(path string) ([]memoryBlock, map[string][]int, error) {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
@@ -312,5 +319,78 @@ func parseMarkdownBlocks(path string) ([]memoryBlock, error) {
|
|||||||
current.WriteString(line + "\n")
|
current.WriteString(line + "\n")
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
return blocks, nil
|
tokenIndex := buildTokenIndex(blocks)
|
||||||
|
return blocks, tokenIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTokenIndex(blocks []memoryBlock) map[string][]int {
|
||||||
|
idx := make(map[string][]int, 256)
|
||||||
|
for i, b := range blocks {
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
for _, tok := range tokenizeForIndex(b.lower + " " + strings.ToLower(b.heading)) {
|
||||||
|
if _, ok := seen[tok]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[tok] = struct{}{}
|
||||||
|
idx[tok] = append(idx[tok], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenizeForIndex(s string) []string {
|
||||||
|
s = strings.ToLower(strings.TrimSpace(s))
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s = strings.NewReplacer("\n", " ", "\t", " ", ",", " ", ".", " ", ":", " ", ";", " ", "(", " ", ")", " ", "[", " ", "]", " ", "{", " ", "}", " ", "`", " ", "\"", " ", "'", " ").Replace(s)
|
||||||
|
parts := strings.Fields(s)
|
||||||
|
out := make([]string, 0, len(parts))
|
||||||
|
for _, p := range parts {
|
||||||
|
if len(p) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func candidateBlockIndexes(tokenIndex map[string][]int, keywords []string, total int) []int {
|
||||||
|
if total <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(keywords) == 0 || len(tokenIndex) == 0 {
|
||||||
|
out := make([]int, 0, total)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
out = append(out, i)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
candMap := map[int]int{}
|
||||||
|
for _, kw := range keywords {
|
||||||
|
kw = strings.TrimSpace(strings.ToLower(kw))
|
||||||
|
if kw == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for tok, ids := range tokenIndex {
|
||||||
|
if strings.Contains(tok, kw) {
|
||||||
|
for _, id := range ids {
|
||||||
|
candMap[id]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(candMap) == 0 {
|
||||||
|
out := make([]int, 0, total)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
out = append(out, i)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
out := make([]int, 0, len(candMap))
|
||||||
|
for id := range candMap {
|
||||||
|
out = append(out, id)
|
||||||
|
}
|
||||||
|
sort.Slice(out, func(i, j int) bool { return candMap[out[i]] > candMap[out[j]] })
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user