mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-04 00:03:59 +00:00
feat(cli): CLI 插件能力对齐 WebUI(memory/knowledge/config/tracker/adapters/network)
原来 CLI 插件只覆盖 WebUI 的一小部分:/memory 只有 query、/knowledge 只有 list、没有 config/tracker/adapters/network,结构化输出还各拼一套文本格式。 按 WebUI 的 REST 面对齐: - /memory query|graph|text [n] (对应 /memory、/memory/graph、/memory/text) - /knowledge | delete <name> | stats(对应 GET/DELETE /knowledge) - /config (对应 GET /config) - /tracker | rollback (对应 GET /tracker、POST /tracker/rollback) - /adapters | remove <name> (对应 GET/DELETE /adapters) - /network (对应 GET /network) - 统一 writeJSONContent:结构化数据一律缩进 JSON,与 WebUI 同口径。 waiter 侧同步:把上述命令在 local(发 CLI 插件)与 remote(走 REST)两条路 都接上,/help 补齐;两条路语义一致。
This commit is contained in:
@ -36,8 +36,16 @@ Server commands (local 与 remote 行为一致):
|
|||||||
/plugin enable <name> enable plugin
|
/plugin enable <name> enable plugin
|
||||||
/plugin info <name> plugin details
|
/plugin info <name> plugin details
|
||||||
/memory query <text> query graph memory
|
/memory query <text> query graph memory
|
||||||
/knowledge list knowledge base
|
/memory graph dump full graph memory snapshot
|
||||||
|
/memory text [n] recent text-memory events + stats
|
||||||
|
/knowledge list knowledge base (+stats)
|
||||||
/knowledge delete <name> delete knowledge item
|
/knowledge delete <name> delete knowledge item
|
||||||
|
/config dump kernel config (JSON)
|
||||||
|
/tracker change-tracking stats
|
||||||
|
/tracker rollback roll back this session's file changes
|
||||||
|
/adapters list loaded Lua adapters
|
||||||
|
/adapters remove <name> remove a Lua adapter
|
||||||
|
/network network status + LLM endpoints
|
||||||
/agents list agents
|
/agents list agents
|
||||||
/chat <text> send to agent
|
/chat <text> send to agent
|
||||||
|
|
||||||
@ -238,11 +246,23 @@ Any other text is sent to the agent directly.`)
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case strings.HasPrefix(cmd, "/memory query "):
|
case strings.HasPrefix(cmd, "/memory "):
|
||||||
q := strings.TrimSpace(cmd[14:])
|
sub := strings.TrimSpace(cmd[8:])
|
||||||
if rc := state.RemoteConn(); rc != nil {
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
d, _ := rc.DoAPI("GET", "/api/v1/memory?query="+q, "")
|
switch {
|
||||||
printJSON(out, d)
|
case strings.HasPrefix(sub, "query "):
|
||||||
|
q := strings.TrimSpace(strings.TrimPrefix(sub, "query "))
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/memory?query="+q, "")
|
||||||
|
printJSON(out, d)
|
||||||
|
case sub == "graph":
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/memory/graph", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
case sub == "text" || strings.HasPrefix(sub, "text "):
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/memory/text", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(out, "usage: /memory query <text> | /memory graph | /memory text [n]")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.Send(cmd)
|
state.Send(cmd)
|
||||||
}
|
}
|
||||||
@ -258,12 +278,63 @@ Any other text is sent to the agent directly.`)
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case cmd == "/knowledge":
|
case cmd == "/knowledge" || cmd == "/knowledge list" || cmd == "/knowledge stats":
|
||||||
if rc := state.RemoteConn(); rc != nil {
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
d, _ := rc.DoAPI("GET", "/api/v1/knowledge", "")
|
d, _ := rc.DoAPI("GET", "/api/v1/knowledge", "")
|
||||||
printJSON(out, d)
|
printJSON(out, d)
|
||||||
} else {
|
} else {
|
||||||
state.Send("/knowledge")
|
state.Send(cmd)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case cmd == "/config":
|
||||||
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/config", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
} else {
|
||||||
|
state.Send("/config")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case cmd == "/tracker":
|
||||||
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/tracker", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
} else {
|
||||||
|
state.Send("/tracker")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case cmd == "/tracker rollback":
|
||||||
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
|
d, _ := rc.DoAPI("POST", "/api/v1/tracker/rollback", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
} else {
|
||||||
|
state.Send("/tracker rollback")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case cmd == "/adapters" || strings.HasPrefix(cmd, "/adapters remove "):
|
||||||
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
|
if strings.HasPrefix(cmd, "/adapters remove ") {
|
||||||
|
name := strings.TrimSpace(cmd[17:])
|
||||||
|
d, _ := rc.DoAPI("DELETE", "/api/v1/adapters/"+name, "")
|
||||||
|
printJSON(out, d)
|
||||||
|
} else {
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/adapters", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.Send(cmd)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case cmd == "/network":
|
||||||
|
if rc := state.RemoteConn(); rc != nil {
|
||||||
|
d, _ := rc.DoAPI("GET", "/api/v1/network", "")
|
||||||
|
printJSON(out, d)
|
||||||
|
} else {
|
||||||
|
state.Send("/network")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -301,9 +302,17 @@ func (p *Plugin) handleBuiltin(conn net.Conn, line string, s *sdk.PluginSDK) boo
|
|||||||
case "/memory":
|
case "/memory":
|
||||||
p.cmdMemory(conn, parts, s)
|
p.cmdMemory(conn, parts, s)
|
||||||
case "/knowledge":
|
case "/knowledge":
|
||||||
p.cmdKnowledge(conn, s)
|
p.cmdKnowledge(conn, parts, s)
|
||||||
case "/agents":
|
case "/agents":
|
||||||
p.cmdAgents(conn, s)
|
p.cmdAgents(conn, s)
|
||||||
|
case "/config":
|
||||||
|
p.cmdConfig(conn, s)
|
||||||
|
case "/tracker":
|
||||||
|
p.cmdTracker(conn, parts, s)
|
||||||
|
case "/adapters":
|
||||||
|
p.cmdAdapters(conn, parts, s)
|
||||||
|
case "/network":
|
||||||
|
p.cmdNetwork(conn, s)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -327,9 +336,20 @@ func (p *Plugin) cmdHelp(conn net.Conn) {
|
|||||||
/plugin disable <name> 禁用插件
|
/plugin disable <name> 禁用插件
|
||||||
/plugin enable <name> 启用插件
|
/plugin enable <name> 启用插件
|
||||||
/plugin info <name> 查看插件详情
|
/plugin info <name> 查看插件详情
|
||||||
|
/plugin reload 重载插件
|
||||||
/memory query <关键词> 查询图记忆
|
/memory query <关键词> 查询图记忆
|
||||||
|
/memory graph 导出整张图记忆快照
|
||||||
|
/memory text [n] 最近 n 条文本记忆事件 + 统计
|
||||||
/knowledge 列出知识库
|
/knowledge 列出知识库
|
||||||
/agents 列出 Agent
|
/knowledge delete <name> 删除一条知识
|
||||||
|
/knowledge stats 知识库统计
|
||||||
|
/config [prefix] 导出内核配置(可按前缀筛选)
|
||||||
|
/tracker 变更追踪统计
|
||||||
|
/tracker rollback 回滚本次会话的文件变更
|
||||||
|
/adapters 列出已加载的 Lua 适配器
|
||||||
|
/adapters remove <name> 卸载适配器
|
||||||
|
/network 网络状态与 LLM 端点
|
||||||
|
/agents 当前 Agent 信息
|
||||||
|
|
||||||
其他文本直接发送给 Agent 处理。`,
|
其他文本直接发送给 Agent 处理。`,
|
||||||
})
|
})
|
||||||
@ -621,50 +641,186 @@ func (p *Plugin) cmdPlugin(conn net.Conn, parts []string, s *sdk.PluginSDK) {
|
|||||||
|
|
||||||
// ======== /memory ========
|
// ======== /memory ========
|
||||||
|
|
||||||
func (p *Plugin) cmdMemory(conn net.Conn, parts []string, s *sdk.PluginSDK) {
|
// writeJSONContent 把一个结构以缩进 JSON 写入 response 帧。
|
||||||
if len(parts) < 3 || parts[1] != "query" {
|
// CLI 与 WebUI 对齐的口径:结构化数据一律 JSON(带缩进便于人读),
|
||||||
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /memory query <关键词>"})
|
// 不再每个子命令各拼一种文本格式。
|
||||||
return
|
func writeJSONContent(conn net.Conn, v interface{}) {
|
||||||
}
|
data, err := json.MarshalIndent(v, "", " ")
|
||||||
q := strings.Join(parts[2:], " ")
|
|
||||||
|
|
||||||
mem := s.Memory()
|
|
||||||
if mem == nil {
|
|
||||||
writeLine(conn, map[string]interface{}{"type": "error", "error": "memory not available"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entities, relations, err := mem.Recall([]string{q}, 2)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result := map[string]interface{}{
|
|
||||||
"entities": entities,
|
|
||||||
"relations": relations,
|
|
||||||
}
|
|
||||||
data, _ := json.MarshalIndent(result, "", " ")
|
|
||||||
writeLine(conn, map[string]interface{}{"type": "response", "content": string(data)})
|
writeLine(conn, map[string]interface{}{"type": "response", "content": string(data)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) cmdMemory(conn net.Conn, parts []string, s *sdk.PluginSDK) {
|
||||||
|
sub := ""
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
sub = parts[1]
|
||||||
|
}
|
||||||
|
switch sub {
|
||||||
|
case "query":
|
||||||
|
if len(parts) < 3 {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /memory query <关键词>"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q := strings.Join(parts[2:], " ")
|
||||||
|
mem := s.Memory()
|
||||||
|
if mem == nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "memory not available"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entities, relations, err := mem.Recall([]string{q}, 2)
|
||||||
|
if err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, map[string]interface{}{
|
||||||
|
"entities": entities,
|
||||||
|
"relations": relations,
|
||||||
|
})
|
||||||
|
case "graph":
|
||||||
|
mem := s.Memory()
|
||||||
|
if mem == nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "memory not available"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := mem.GraphData()
|
||||||
|
if err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, data)
|
||||||
|
case "text":
|
||||||
|
tm := s.TextMemory()
|
||||||
|
if tm == nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "text memory not available"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n := 20
|
||||||
|
if len(parts) >= 3 {
|
||||||
|
if v, err := strconv.Atoi(parts[2]); err == nil && v > 0 {
|
||||||
|
n = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events, err := tm.RecentEvents(n)
|
||||||
|
if err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, map[string]interface{}{
|
||||||
|
"events": events,
|
||||||
|
"stats": tm.Stats(),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /memory query <关键词> | /memory graph | /memory text [n]"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ======== /knowledge ========
|
// ======== /knowledge ========
|
||||||
|
|
||||||
func (p *Plugin) cmdKnowledge(conn net.Conn, s *sdk.PluginSDK) {
|
func (p *Plugin) cmdKnowledge(conn net.Conn, parts []string, s *sdk.PluginSDK) {
|
||||||
ks := s.Knowledge()
|
ks := s.Knowledge()
|
||||||
if ks == nil {
|
if ks == nil {
|
||||||
writeLine(conn, map[string]interface{}{"type": "error", "error": "knowledge not available"})
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "knowledge not available"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items, err := ks.List()
|
sub := "list"
|
||||||
if err != nil {
|
if len(parts) >= 2 {
|
||||||
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
sub = parts[1]
|
||||||
|
}
|
||||||
|
switch sub {
|
||||||
|
case "list":
|
||||||
|
items, err := ks.List()
|
||||||
|
if err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, items)
|
||||||
|
case "delete", "remove":
|
||||||
|
if len(parts) < 3 {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /knowledge delete <name>"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ks.Remove(parts[2]); err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "已删除知识: " + parts[2]})
|
||||||
|
case "stats":
|
||||||
|
writeJSONContent(conn, ks.Stats())
|
||||||
|
default:
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /knowledge | /knowledge delete <name> | /knowledge stats"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== /config ========
|
||||||
|
|
||||||
|
func (p *Plugin) cmdConfig(conn net.Conn, s *sdk.PluginSDK) {
|
||||||
|
cfg := s.Config()
|
||||||
|
if cfg == nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "config not available"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(items) == 0 {
|
writeJSONContent(conn, cfg.Get())
|
||||||
writeLine(conn, map[string]interface{}{"type": "response", "content": "知识库为空"})
|
}
|
||||||
|
|
||||||
|
// ======== /tracker ========
|
||||||
|
|
||||||
|
func (p *Plugin) cmdTracker(conn net.Conn, parts []string, s *sdk.PluginSDK) {
|
||||||
|
tr := s.Tracker()
|
||||||
|
if tr == nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "tracker not available"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, _ := json.MarshalIndent(items, "", " ")
|
if len(parts) >= 2 && parts[1] == "rollback" {
|
||||||
writeLine(conn, map[string]interface{}{"type": "response", "content": string(data)})
|
if err := tr.Rollback(); err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "已回滚文件变更"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, map[string]interface{}{
|
||||||
|
"stats": tr.Stats(),
|
||||||
|
"has_changes": tr.HasChanges(),
|
||||||
|
"changesets": tr.ChangeSets(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== /adapters ========
|
||||||
|
|
||||||
|
func (p *Plugin) cmdAdapters(conn net.Conn, parts []string, s *sdk.PluginSDK) {
|
||||||
|
ad := s.Adapter()
|
||||||
|
if ad == nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": "lua adapter not available"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parts) >= 3 && (parts[1] == "remove" || parts[1] == "delete") {
|
||||||
|
if err := ad.Remove(parts[2]); err != nil {
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeLine(conn, map[string]interface{}{"type": "response", "content": "已卸载适配器: " + parts[2]})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, map[string]interface{}{"adapters": ad.List()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== /network ========
|
||||||
|
|
||||||
|
// cmdNetwork 与 WebUI 的 GET /api/v1/network 同口径:网络状态 + LLM 端点。
|
||||||
|
func (p *Plugin) cmdNetwork(conn net.Conn, s *sdk.PluginSDK) {
|
||||||
|
var endpoints interface{}
|
||||||
|
if cfg := s.Config(); cfg != nil {
|
||||||
|
if c := cfg.Get(); c != nil {
|
||||||
|
endpoints = c.Defaults.LLMEndpoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeJSONContent(conn, map[string]interface{}{
|
||||||
|
"network_status": "monitoring",
|
||||||
|
"endpoints": endpoints,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== /agents ========
|
// ======== /agents ========
|
||||||
|
|||||||
Reference in New Issue
Block a user