diff --git a/main.go b/main.go index c7d694cb..5fb55c7b 100644 --- a/main.go +++ b/main.go @@ -142,10 +142,7 @@ func main() { } }() - // Start Telegram bot (if TELEGRAM_BOT_TOKEN is configured) - go telegram.Start(cfg, st, telegramReloadCh) - - // Start NOFXi Agent (proactive intelligence layer) + // Start NOFXi Agent (proactive intelligence layer) — must be created BEFORE Telegram bot nofxiAgent := nofxiagent.New(traderManager, st, nil, slog.Default()) // Try to get an AI client from existing trader configs @@ -164,6 +161,13 @@ func main() { agentWeb := nofxiagent.NewWebHandler(nofxiAgent, slog.Default()) server.RegisterAgentHandler(agentWeb) + // Wire NOFXi Agent into Telegram bot — messages route through the unified agent + telegram.SetNOFXiAgent(nofxiAgent) + logger.Info("🔗 NOFXi Agent connected to Telegram bot") + + // Start Telegram bot (if token is configured) + go telegram.Start(cfg, st, telegramReloadCh) + logger.Info("🧠 NOFXi Agent started (sentinel + brain + scheduler + web:8900)") // Wait for interrupt signal diff --git a/telegram/bot.go b/telegram/bot.go index 6cf81dc6..05024e7a 100644 --- a/telegram/bot.go +++ b/telegram/bot.go @@ -1,6 +1,7 @@ package telegram import ( + "context" "nofx/api" "nofx/config" "nofx/logger" @@ -17,6 +18,30 @@ import ( tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) +// NOFXiAgent is the interface that the new agent.Agent satisfies. +// When set, Telegram messages are routed through it instead of the legacy telegram/agent package. +type NOFXiAgent interface { + HandleMessage(ctx context.Context, userID int64, text string) (string, error) +} + +var ( + nofxiAgentMu sync.RWMutex + nofxiAgent NOFXiAgent +) + +// SetNOFXiAgent registers the new NOFXi agent for Telegram message routing. +func SetNOFXiAgent(a NOFXiAgent) { + nofxiAgentMu.Lock() + defer nofxiAgentMu.Unlock() + nofxiAgent = a +} + +func getNOFXiAgent() NOFXiAgent { + nofxiAgentMu.RLock() + defer nofxiAgentMu.RUnlock() + return nofxiAgent +} + // Start initializes and runs the Telegram bot in a blocking supervisor loop. // Supports hot-reload: when a signal is sent on reloadCh, the bot restarts // with the latest token (re-read from DB or env). Must be called as a goroutine from main.go. @@ -208,25 +233,46 @@ func runBot(token string, cfg *config.Config, st *store.Store) bool { placeholderID = sent.MessageID } - var ( - mu sync.Mutex - lastEdit time.Time - ) - onChunk := func(accumulated string) { - if placeholderID == 0 { - return - } - mu.Lock() - defer mu.Unlock() - if accumulated != "⏳" && time.Since(lastEdit) < time.Second { - return - } - lastEdit = time.Now() - edit := tgbotapi.NewEditMessageText(chatID, placeholderID, accumulated) - bot.Send(edit) //nolint:errcheck - } + var reply string - reply := agents.Run(chatID, text, onChunk) + // Route through NOFXi Agent if available (new unified architecture), + // otherwise fall back to legacy telegram/agent manager. + if na := getNOFXiAgent(); na != nil { + lang := st.TelegramConfig().GetLanguage() + msg := text + if lang != "" { + msg = "[lang:" + lang + "] " + text + } + ctx, cancel := context.WithTimeout(context.Background(), 55*time.Second) + resp, err := na.HandleMessage(ctx, chatID, msg) + cancel() + if err != nil { + logger.Errorf("NOFXi agent error: %v", err) + reply = "AI assistant temporarily unavailable. Please try again." + } else { + reply = resp + } + } else { + // Legacy path: stream via telegram/agent + var ( + mu sync.Mutex + lastEdit time.Time + ) + onChunk := func(accumulated string) { + if placeholderID == 0 { + return + } + mu.Lock() + defer mu.Unlock() + if accumulated != "⏳" && time.Since(lastEdit) < time.Second { + return + } + lastEdit = time.Now() + edit := tgbotapi.NewEditMessageText(chatID, placeholderID, accumulated) + bot.Send(edit) //nolint:errcheck + } + reply = agents.Run(chatID, text, onChunk) + } if placeholderID != 0 { edit := tgbotapi.NewEditMessageText(chatID, placeholderID, reply)