Files
Maven/cmd/mavend/telegramintake.go
claude 35c6ff5a71 Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
2026-08-13 02:50:59 +04:00

60 lines
2.0 KiB
Go

// 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
}
}