package main import ( "context" "fmt" "path/filepath" "strings" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session" "git.yeij.top/AskaEth/Cyrene_v2/internal/config" "git.yeij.top/AskaEth/Cyrene_v2/internal/iot" "git.yeij.top/AskaEth/Cyrene_v2/internal/logger" "git.yeij.top/AskaEth/Cyrene_v2/internal/platform" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/documents" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/exec" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/task" "git.yeij.top/AskaEth/Cyrene_v2/internal/services/automations" "git.yeij.top/AskaEth/Cyrene_v2/internal/services/backthink" ) // wireAuxiliary 组装执行提供者、任务管理器、IoT 管理器、渠道会话管理器、 // 自动化服务与后台思考服务。 func (a *app) wireAuxiliary() error { provider, err := exec.New(a.cfg.ExecSandbox, a.log) if err != nil { return fmt.Errorf("exec provider: %w", err) } a.execProv = provider tmgr, err := task.New(a.cfg, provider, a.log, task.WithBus(a.bus)) if err != nil { return fmt.Errorf("task manager: %w", err) } a.tasks = tmgr iotMgr, err := iot.New(a.cfg.IoT, a.log, iot.WithBus(a.bus)) if err != nil { return fmt.Errorf("iot: %w", err) } a.iot = iotMgr sess, err := session.New(context.Background(), a.db, a.log) if err != nil { return fmt.Errorf("session manager: %w", err) } a.sessions = sess hist, herr := history.NewStore(context.Background(), a.db, a.log, history.WithDocumentRefs(documents.NewRefCounter(a.log))) if herr != nil { return fmt.Errorf("history store: %w", herr) } a.history = hist automation, err := automations.NewService(automations.Config{}, automations.Deps{ Bus: a.bus, Loop: a.loop, Log: logger.WithSource(a.log, "services/automations"), }) if err != nil { return fmt.Errorf("automations: %w", err) } a.automation = automation bt, err := backthink.New(backthink.ConfigFromBackground(a.cfg.Background), backthink.Deps{ Bus: a.bus, Thinker: backthink.NewLoopThinker(a.loop, a.history), Recent: a.history, State: backthink.NewIoTStateProvider(iotMgr, a.log), Exporter: backthink.NewPushOutlet(a.pushOutletConfig()), DB: a.db, Log: logger.WithSource(a.log, "services/backthink"), }) if err != nil { return fmt.Errorf("backthink: %w", err) } a.backthink = bt a.wireVoice() a.wireMCP() a.wireSkills() a.wireTerminalTool() a.wireSubagentTool() a.wireSessionReadTool() return nil } // pushOutletConfig 把环境变量里的推送出口配置块映射为 backthink 的导出器配置。 // 个推(Getui)凭据只被 config 读取、并不消费:备用 SDK 尚未接入,因此出口在 // 待接入该档位时记日志,而不是伪造一次推送。token 只透传,绝不写日志。 func (a *app) pushOutletConfig() backthink.PushOutletConfig { po := a.cfg.PushOutlet return backthink.PushOutletConfig{ Enabled: po.Enabled, URL: po.URL, Token: po.Token, Timeout: time.Duration(po.TimeoutSec) * time.Second, Log: a.log, } } // close 按依赖顺序关闭每个子系统并释放资源。 func (a *app) close(ctx context.Context) { // 文档回收循环要先用连接池(每轮一次查询),因此排在关库之前。 a.docGC.stop() // 用量落库与保留期清理同样要用连接池:先把队列里剩下的样本写完,再停清理。 a.stopUsage() if a.backthink != nil { _ = a.backthink.Stop(ctx) } if a.automation != nil { _ = a.automation.Stop(ctx) } if a.channelBridge != nil { a.channelBridge.Stop(ctx) } if a.scheduler != nil { _ = a.scheduler.CancelAll(ctx) } if a.gw != nil { a.gw.Close() } for _, reg := range a.mcpRegs { _ = reg.Unregister(ctx) } if a.plugins != nil { a.plugins.Close(ctx) } if a.tasks != nil { _ = a.tasks.Close() } if a.vindex != nil { _ = a.vindex.Close() } if a.bus != nil { _ = a.bus.Close() } if a.db != nil { _ = a.db.Close() } a.log.Info("cyrene shutdown complete") } // resolveDataDir 返回配置的数据目录(来自 .env / 环境变量,经 cfg.DataDir); // 只有在没有配置目录时才回落到与操作系统相称的默认值。 func resolveDataDir(cfg *config.Config) string { if cfg.DataDir != "" { return cfg.DataDir } return platform.DefaultDataDir() } // resolveSQLitePath 返回实际使用的 SQLite 路径:当配置里仍是默认值时, // 把它推导到数据目录下。 func resolveSQLitePath(cfg *config.Config, dataDir string) string { p := cfg.DB.SQLitePath if strings.TrimSpace(p) == "" || p == defaultSQLitePath { return filepath.Join(dataDir, "cyrene.db") } return p }