package main import ( "context" "fmt" "os/signal" "syscall" "time" "laodingbot/internal/agent" "laodingbot/internal/config" "laodingbot/internal/knowledge" "laodingbot/internal/llm" "laodingbot/internal/logger" "laodingbot/internal/memory" "laodingbot/internal/tools" "laodingbot/internal/tools/filetool" "laodingbot/internal/tools/shelltool" "laodingbot/internal/transport/feishu" "laodingbot/internal/transport/telegram" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() cfg, err := config.Load() if err != nil { panic(fmt.Sprintf("load config failed: %v", err)) } appLogger, err := logger.New(cfg.LogLevel) if err != nil { panic(fmt.Sprintf("init logger failed: %v", err)) } appLogger = appLogger.WithComponent("main") appLogger.Infof("config loaded; channel=%s, log_level=%s", cfg.MessageChannel, cfg.LogLevel) store, err := memory.NewSQLiteStore(cfg.SQLitePath, appLogger.WithComponent("memory")) if err != nil { appLogger.Errorf("init memory store failed: %v", err) panic(err) } defer store.Close() toolRegistry := tools.NewRegistry(appLogger.WithComponent("tools.registry")) toolRegistry.Register(filetool.New(cfg.Security.AllowedDirs, appLogger.WithComponent("tools.file"))) toolRegistry.Register(shelltool.New(cfg.Security.AllowedCommands, cfg.Security.WorkDir, 15*time.Second, appLogger.WithComponent("tools.shell"))) soul, err := knowledge.LoadSoul(cfg.SoulPath) if err != nil { appLogger.Errorf("load soul failed path=%s err=%v", cfg.SoulPath, err) panic(err) } skillsDoc, err := knowledge.LoadSkills(cfg.SkillsDir) if err != nil { appLogger.Errorf("load skills failed dir=%s err=%v", cfg.SkillsDir, err) panic(err) } skillSet, err := knowledge.LoadSkillSet(cfg.SkillsDir) if err != nil { appLogger.Errorf("load skill set failed dir=%s err=%v", cfg.SkillsDir, err) panic(err) } appLogger.Infof("knowledge loaded soul_path=%s skills_dir=%s", cfg.SoulPath, cfg.SkillsDir) llmClient := llm.NewOpenAICompatibleClient(cfg.LLM, appLogger.WithComponent("llm")) engine := agent.NewOrchestrator( llmClient, store, toolRegistry, soul, skillSet, skillsDoc, cfg.ReactMaxSteps, appLogger.WithComponent("agent"), ) appLogger.Infof("LaodingBot started, channel=%s", cfg.MessageChannel) if err := runMessageChannel(ctx, cfg, engine, appLogger); err != nil && ctx.Err() == nil { appLogger.Errorf("message channel run failed: %v", err) panic(err) } appLogger.Infof("LaodingBot stopped") } func runMessageChannel(ctx context.Context, cfg config.Config, engine *agent.Orchestrator, lg *logger.Logger) error { switch cfg.MessageChannel { case "telegram": tg, err := telegram.NewBot(cfg.Telegram.Token, cfg.Telegram.PollTimeoutSeconds, lg.WithComponent("transport.telegram")) if err != nil { return fmt.Errorf("init telegram bot failed: %w", err) } lg.Infof("starting telegram transport") return tg.Run(ctx, func(ctx context.Context, msg telegram.IncomingMessage) (string, error) { return engine.HandleMessage(ctx, msg.ChatID, msg.UserID, msg.Text) }) case "feishu": fs, err := feishu.NewBot( cfg.Feishu.AppID, cfg.Feishu.AppSecret, cfg.Feishu.VerifyToken, cfg.Feishu.ListenAddr, cfg.Feishu.EventPath, lg.WithComponent("transport.feishu"), ) if err != nil { return fmt.Errorf("init feishu bot failed: %w", err) } lg.Infof("starting feishu transport") return fs.Run(ctx, func(ctx context.Context, msg feishu.IncomingMessage) (string, error) { return engine.HandleMessage(ctx, msg.ChatID, msg.UserID, msg.Text) }) default: return fmt.Errorf("unsupported message channel: %s", cfg.MessageChannel) } }