执行方案阶段 0 · G0-A:组合根 926 行是全仓第一大超限文件,且被 10+ 条裁决争用 (1.4/12.4.1、03.4.3、06.4.2、07.4.4、10.4.5、12.4.2、13.4.3、14.4.1、14.4.3、16.4.2) ⇒ 不先拆,后续全部被迫串行且必然破 500 行红线。 - wire.go(181):入口与共享 —— const 块、app struct、newApp - wire_runtime.go(362):loop/agent/model/memory/capacity/trace(buildPersonas、wireMemory、 wireRuntime、buildCapacity、onCapacity、onToolAudit、defaultPersonaID、defaultInstructions…) - wire_services.go(158):exec/task/iot/session/history/automations/backthink (wireAuxiliary、pushOutletConfig、close、resolveDataDir、resolveSQLitePath) - wire_channels.go(172):渠道/桥(ownerRegistrar、wireChannels、runtimeChannels、reviewFor…) - wire_frontend.go(117):gateway/webui/观测扇出(capacityEmitter、wireFrontend) 纯搬移机械证明:去 package/import/空行后两侧 **811=811 行、排序后 diff 为空、md5 相同**; 顶层声明集合 30 条守恒;唯一新增 = 各文件的 package 行与 import 块(原 import 块按声明归属 重分配给新文件)。未重命名/重排/改注释。 验证:go build/vet 绿;go test ./cmd/... 绿;linux/amd64 + darwin/arm64 交叉编译绿; 五文件 181/362/158/172/117 行均 ≤500;gofmt -l cmd/cyrene/ 无输出。 注:全量 go test 当前有 1 处红 —— internal/contract/enums 的 TestModeTableMatchesFrontendModes 读 frontend/web/src/lib/types.ts 找 MODES,而该文件 正被并行的前端拆分任务搬走;与本提交无关,随后修正该测试的读取路径。
159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
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/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 builds the execution provider, task manager, IoT manager,
|
|
// channel session manager, automations and background thinking services.
|
|
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)
|
|
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 maps the environment push-outlet block into the backthink
|
|
// exporter config. The Getui credentials are read by config but not consumed:
|
|
// the fallback SDK is not integrated yet, so the outlet logs the pending tier
|
|
// instead of faking a push. The token is passed through and never logged.
|
|
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 shuts down every subsystem in dependency order and releases resources.
|
|
func (a *app) close(ctx context.Context) {
|
|
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 returns the configured data directory (from .env / env, via
|
|
// cfg.DataDir), falling back to the OS-appropriate default only when no
|
|
// directory is configured.
|
|
func resolveDataDir(cfg *config.Config) string {
|
|
if cfg.DataDir != "" {
|
|
return cfg.DataDir
|
|
}
|
|
return platform.DefaultDataDir()
|
|
}
|
|
|
|
// resolveSQLitePath returns the SQLite path to use, deriving it under the data
|
|
// directory when the configured default is left untouched.
|
|
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
|
|
}
|