Files
HomeAgent/cmd/homed/main.go

369 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"flag"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
agentCore "gitcode.com/JianFeeeee/HomeAgent/internal/agent/core"
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
agentPkg "gitcode.com/JianFeeeee/HomeAgent/internal/agent"
"gitcode.com/JianFeeeee/HomeAgent/internal/api"
"gitcode.com/JianFeeeee/HomeAgent/config"
internalConfig "gitcode.com/JianFeeeee/HomeAgent/internal/config"
"gitcode.com/JianFeeeee/HomeAgent/internal/events"
"gitcode.com/JianFeeeee/HomeAgent/internal/plugins/test_deepseek"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/plugin/sdk"
"gitcode.com/JianFeeeee/HomeAgent/internal/knowledge"
luapkg "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/document"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/pipeline"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/social"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
"gitcode.com/JianFeeeee/HomeAgent/internal/onebot"
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
"gitcode.com/JianFeeeee/HomeAgent/internal/skill"
"gitcode.com/JianFeeeee/HomeAgent/internal/supervisor"
"gitcode.com/JianFeeeee/HomeAgent/internal/tracker"
)
func main() {
configPath := flag.String("config", config.DefaultConfigPath, "path to config file")
dataDir := flag.String("data", "/var/lib/homeagent", "data directory")
flag.Parse()
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Printf("[homed] starting HomeAgent v0.1.0")
cfg, err := config.Load(*configPath)
if err != nil {
log.Fatalf("load config: %v", err)
}
cfg.Daemon.DataDir = *dataDir
agentWorkDir := filepath.Join(cfg.Daemon.DataDir, "agentfs")
dirs := []string{
cfg.Daemon.DataDir,
filepath.Join(cfg.Daemon.DataDir, "snapshots"),
filepath.Join(cfg.Daemon.DataDir, "skills"),
filepath.Join(cfg.Daemon.DataDir, "plugins"),
filepath.Join(cfg.Daemon.DataDir, "changesets"),
filepath.Join(cfg.Daemon.DataDir, "memory"),
filepath.Join(cfg.Daemon.DataDir, "memory", "raw"),
filepath.Join(cfg.Daemon.DataDir, "adapters"),
agentWorkDir,
}
for _, d := range dirs {
if err := os.MkdirAll(d, 0755); err != nil {
log.Fatalf("create dir %s: %v", d, err)
}
}
// === Graph Memory ===
memDB, err := memory.NewGraphDB(filepath.Join(cfg.Daemon.DataDir, "memory", "graph.db"))
if err != nil {
log.Printf("[homed] warning: memory init failed: %v", err)
memDB = nil
} else {
log.Printf("[homed] graph memory initialized")
}
if memDB != nil {
defer memDB.Close()
}
memIdx := memory.NewIndexer(memDB)
// === Social Store人物特质与关系网===
socialStore := social.New(memDB)
if memDB != nil {
log.Printf("[homed] social store initialized")
}
// === Memory Pipeline ===
distiller := pipeline.NewDistiller(memDB, cfg.Daemon.DataDir, pipeline.DistillerConfig{
Interval: 10 * time.Minute,
RetentionDays: 7,
BatchSize: 50,
})
if memDB != nil {
distiller.Start()
defer distiller.Stop()
}
// === Skills ===
skMgr := skill.NewManager(filepath.Join(cfg.Daemon.DataDir, "skills"))
if err := skMgr.Init(); err != nil {
log.Printf("[homed] warning: skill init failed: %v", err)
}
// === Plugin Registry (OpenClaw SKILL.md compatible) ===
pluginReg := plugin.NewRegistry()
// 注册内置原生插件工厂
pluginReg.RegisterNative("qq", func(name string, config map[string]interface{}, iom *agentIO.IOManager) (agentIO.Device, error) {
wsURL, _ := config["entry"].(string)
if wsURL == "" {
wsURL = "ws://127.0.0.1:6700"
}
accessToken, _ := config["access_token"].(string)
return onebot.NewDevice(name, wsURL, accessToken, iom), nil
})
// === Config Registry (统一配置中心SQLite 持久化) ===
// 所有配置收敛到 SQLiteYAML 仅作首次 seed
cfgReg := internalConfig.NewConfigRegistry(filepath.Join(cfg.Daemon.DataDir, "config.db"))
defer cfgReg.Close()
cfgReg.SeedFrom(cfg)
cfg = cfgReg.ToConfig() // 此后全从 DB 读取
pluginReg.SetConfigRegistry(cfgReg)
log.Printf("[homed] config registry seeded from YAML, %d keys in SQLite", len(cfgReg.List("")))
// === Supervisor ===
sup := supervisor.New(cfg)
if err := sup.Start(); err != nil {
log.Fatalf("start supervisor: %v", err)
}
// === Lua VM ===
luaVM := luapkg.NewVM(filepath.Join(cfg.Daemon.DataDir, "adapters"))
if err := luaVM.Start(); err != nil {
log.Printf("[homed] warning: lua vm init failed: %v", err)
} else {
defer luaVM.Stop()
}
// === IO Abstraction Layer (唯一输入路径) ===
iom := agentIO.NewIOManager()
// 插件绑定 IO 管理器 → 插件自动注册为 IO 设备
pluginReg.SetIOManager(iom)
// 首次加载插件
if result, err := pluginReg.Reload(filepath.Join(cfg.Daemon.DataDir, "plugins")); err != nil {
log.Printf("[homed] warning: load plugins: %v", err)
} else {
log.Printf("[homed] %s", result)
}
// === Text Memory (三层记忆: Context → Text → Graph) ===
textMem := text.New(filepath.Join(cfg.Daemon.DataDir, "memory", "text"))
if err := textMem.Start(); err != nil {
log.Printf("[homed] warning: text memory start: %v", err)
} else {
defer textMem.Stop()
log.Printf("[homed] text memory active at %s", filepath.Join(cfg.Daemon.DataDir, "memory", "text"))
}
// Wire IO output events → TextMemory + distiller → GraphMemory
if distiller != nil {
go func() {
for evt := range iom.OutputChan() {
if evt.Target == "memory" && evt.Type == "memory_candidate" {
source, _ := evt.Payload["source"].(string)
input, _ := evt.Payload["input"].(string)
response, _ := evt.Payload["response"].(string)
toolsUsed, _ := evt.Payload["tools_used"].([]string)
agentID, _ := evt.Payload["agent_id"].(string)
// 1. 写文本记忆(持久化原始日志)
if input != "" && textMem != nil {
te := text.Event{
Timestamp: time.Now().Unix(),
Source: source,
Input: input,
Response: response,
ToolsUsed: toolsUsed,
AgentID: agentID,
}
if err := textMem.Append(te); err != nil {
log.Printf("[homed] text memory append: %v", err)
}
}
// 2. 喂蒸馏器(生成三元组 → 图记忆)
if input != "" {
distiller.Append("agent", "user", input)
}
if response != "" {
distiller.Append("agent", "assistant", response)
}
}
}
}()
}
// === Change Tracker (overlayfs-based, 追踪所有修改) ===
trk := tracker.NewTracker(cfg.Daemon.DataDir, agentWorkDir)
if err := trk.Init(); err != nil {
log.Printf("[homed] warning: tracker init: %v", err)
} else {
if err := trk.Start(); err != nil {
log.Printf("[homed] warning: tracker mount overlay: %v (non-fatal: no overlayfs support?)", err)
} else {
log.Printf("[homed] change tracker active at %s", trk.MergeDir())
}
}
// === API Provider Manager多 LLM 源,通过 Lua 适配器兼容不同 API===
apiKey := cfg.LLM.APIKey
if apiKey == "" {
apiKey = os.Getenv("DEEPSEEK_API_KEY")
}
baseAPIKey := apiKey
providerMgr := agentAPI.NewProviderManager()
for _, src := range cfg.LLM.Sources {
key := src.APIKey
if key == "" {
key = baseAPIKey
}
luaProvider := agentAPI.NewLuaAdaptedProvider(agentAPI.BaseConfig{
Model: src.Model,
BaseURL: src.BaseURL,
APIKey: key,
Temperature: cfg.LLM.Temperature,
MaxTokens: cfg.LLM.MaxTokens,
}, luaVM, src.Adapter)
providerMgr.Register(src.Name, luaProvider)
}
// 默认源由 config 指定
if cfg.LLM.Provider != "" {
providerMgr.SetDefault(cfg.LLM.Provider)
}
provider := providerMgr.Default()
// === Personality (固定人格内核) ===
personalPath := filepath.Join(cfg.Daemon.DataDir, "personal", "personal.md")
personality, err := agentPkg.LoadPersonality(personalPath)
if err != nil {
log.Printf("[homed] warning: load personality: %v", err)
}
if personality != nil && personality.Content != "" {
log.Printf("[homed] personality loaded (%d bytes)", len(personality.Content))
}
// === Document Memory (第二层记忆:上下文→文档) ===
docStore := document.NewStore(filepath.Join(cfg.Daemon.DataDir, "memory", "documents"))
if err := docStore.Start(); err != nil {
log.Printf("[homed] warning: document store: %v", err)
}
// === Knowledge Store (知识库) ===
ks := knowledge.NewStore(filepath.Join(cfg.Daemon.DataDir, "knowledge"))
if err := ks.Start(); err != nil {
log.Printf("[homed] warning: knowledge store: %v", err)
} else {
log.Printf("[homed] knowledge store active with %d items", len(ks.List()))
}
// === Event Bus (系统事件总线) ===
evBus := events.NewBus()
log.Printf("[homed] event bus initialized")
// === Stage Host (阶段管道编排) ===
stageHost := agentCore.NewStageHost()
stageHost.SyncFromRegistry(pluginReg)
log.Printf("[homed] stage host initialized with %d plugin sdks", pluginReg.SDKPluginCount())
// === Test DeepSeek Plugin ===
tdBus := sdk.NewInProcessBus()
tdPlugin := test_deepseek.New(tdBus)
// 插件作用域的配置表RegisterPluginAPI 自动设置 config_test_deepseek 表)
if err := pluginReg.RegisterPluginAPI(tdPlugin); err != nil {
log.Printf("[homed] warning: register test_deepseek plugin: %v", err)
} else {
stageHost.RegisterPlugin(tdPlugin)
log.Printf("[homed] test_deepseek plugin registered (config table: config_test_deepseek)")
}
// === Single Agent Core ===
agent := agentCore.New(agentCore.AgentConfig{
ID: "main",
SystemPrompt: `你是 HomeAgent一个持续运行的个人管家。
你的每次回复会自动发送到当前输出通道(默认=输入源),无需额外工具。
如需切换回复通道,使用 output_set_channel。
如需异步发送消息或通知,使用 output_send 指定通道和内容。
使用 output_list_channels 查看可用通道及其能力。
你有以下核心工具:
1. memory_recall — 查询图记忆(历史/个人信息)
2. memory_commit — 写入图记忆(记住新信息)
3. memory_introspect — 查看记忆统计
4. person_query — 查询人物特质与社交关系网
5. person_set_trait — 记录人物特质(性格/喜好等)
6. person_relate — 建立人物间社交关系
7. person_network — 查看社交网络
8. knowledge_search — 搜索知识库
9. doc_query — 查询文档记忆
10. doc_commit — 写入文档记忆
11. plgreload — 热重载插件
12. llm_list_sources — 列出所有可用的 LLM 源
13. llm_set_source — 切换到指定 LLM 源
当用户问及个人信息或历史时,调用 memory_recall。
当用户告诉了你新的个人信息时,调用 memory_commit。
当用户提到某个人的性格、喜好或人际关系时,使用 person_set_trait 和 person_relate 记录。
需要查询知识时使用 knowledge_search。
如需切换 LLM 供应商(如从 DeepSeek 切到 OpenAI使用 llm_list_sources 查看可用源,再用 llm_set_source 切换。
回复你的真实想法,用自然语言与用户交流。`,
Provider: provider,
ProviderManager: providerMgr,
IO: iom,
Memory: memDB,
Indexer: memIdx,
Skills: skMgr,
Tracker: trk,
MaxToolTurns: 10,
DocStore: docStore,
Knowledge: ks,
SocialStore: socialStore,
TextMemory: textMem,
Personality: personality,
PluginReg: pluginReg,
PluginDir: filepath.Join(cfg.Daemon.DataDir, "plugins"),
ContextSavePath: filepath.Join(cfg.Daemon.DataDir, "memory", "context.json"),
StageHost: stageHost,
EventBus: evBus,
})
agent.Start()
defer agent.Stop()
// Wire supervisor with tracker + agent registration (after both exist)
sup.SetTracker(trk)
sup.RegisterAgent("main")
log.Printf("[homed] main agent started, model=%s base=%s sources=%d adapters=%d",
cfg.LLM.Model, cfg.LLM.BaseURL, len(cfg.LLM.Sources), len(luaVM.ListAdapters()))
// === Built-in HTTP API & WebUI Plugin ===
webui := api.NewWebUIPlugin(
"webui", cfg.Daemon.ListenAddr,
sup, memDB, skMgr, luaVM, cfg, iom, textMem, ks, trk, cfgReg, pluginReg,
)
iom.RegisterDevice(webui)
webui.Start()
defer webui.Stop()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Printf("[homed] shutting down...")
if trk != nil {
trk.Stop()
}
if err := cfgReg.Flush(); err != nil {
log.Printf("[homed] flush config: %v", err)
}
sup.Shutdown()
log.Printf("[homed] stopped")
}