// mavend/telegramintake.go — wiring the inbound telegram poller (V-637). // // The poller reaches the daemon through ipc.CoreAPI and nothing else, so a // telegram turn takes exactly the path the web's POST /api/chat takes: Chat // returns the reply and the persisted trace id, and CorrectTurn writes the // label. Nothing in internal/delivery knows what a handler is. package main import ( "context" "log" "sync" "github.com/kami/maven/internal/config" "github.com/kami/maven/internal/delivery/telegramsink" "github.com/kami/maven/internal/ipc" ) // wireTelegramIntake starts the poller, or returns having done nothing. It is // nil-safe in every argument, because it is called from both boot paths — the // unlocked start and the passkey unlock — and telegram must behave the same on // either. // // A sink that will not build is logged rather than fatal here. The push half // already failed the boot in wireDispatcher for the same config, so a second // hard failure would only lose that message. func wireTelegramIntake(ctx context.Context, wg *sync.WaitGroup, api ipc.CoreAPI, cfg *config.Config) { if cfg == nil || cfg.Telegram == nil || cfg.Telegram.Disabled || !cfg.Telegram.Intake || api == nil { return } sink, err := telegramsink.New(*cfg.Telegram) if err != nil { log.Printf("telegram intake: %v", err) return } poller, err := telegramsink.NewPoller(sink, chatTurnFn(api), api.CorrectTurn) if err != nil { log.Printf("telegram intake: %v", err) return } wg.Add(1) go func() { defer wg.Done() poller.Run(ctx) }() } // chatTurnFn adapts ipc.Chat to the poller's Turn. The trace id comes back on // the reply because the daemon's Chat collects it off the context (V-630), so // the chat can offer the same correction the web does without a second op. func chatTurnFn(api ipc.CoreAPI) telegramsink.Turn { return func(ctx context.Context, conversation, text string) (string, int64, error) { reply, err := api.Chat(ctx, conversation, text) if err != nil { return "", 0, err } return reply.Reply, reply.TraceID, nil } }