From c61b0b3968b2bea5bf5ab546a6ad682364129e1d Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 6 Aug 2026 20:53:48 +0400 Subject: [PATCH] wiring the poller into both boot paths (V-637) It reaches the daemon through ipc.CoreAPI and nothing else, so a telegram turn takes the path POST /api/chat already takes: Chat returns the reply and the trace id it collected off the context (V-630), and CorrectTurn writes the label. Nothing in internal/delivery learns what a handler is. Wired on the unlocked start and on the passkey unlock, like the mail intake, so telegram behaves the same either way. A sink that will not build is logged rather than fatal here, because wireDispatcher already failed the boot on the same config. --- cmd/mavend/main.go | 6 ++++ cmd/mavend/telegramintake.go | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 cmd/mavend/telegramintake.go diff --git a/cmd/mavend/main.go b/cmd/mavend/main.go index 8408d76..391bd71 100644 --- a/cmd/mavend/main.go +++ b/cmd/mavend/main.go @@ -364,6 +364,9 @@ func run(args []string) error { if !locked { wireMailIntake(srv, st, phr, cfg, evBus) wireModelSwap(srv, phr, cfg) + // Inbound telegram (V-637). Dark unless the telegram block says intake, + // and it reads one chat. + wireTelegramIntake(ctx, &wg, coreAPI, cfg) // Vision + the media blob store (Vikunja #252). Both stay dark without a // media block; MethodDescribeImage answers ErrUnknownMethod then. keeper := wireVision(ctx, &wg, srv, st, embedderOf(voiceW), cfg) @@ -511,6 +514,9 @@ func run(args []string) error { srv.Check = (&auth.Gate{Enrollment: auth.NewFloorEnrollment(), Session: passkeySess}).Check wireMailIntake(srv, st, phr, cfg, evBus) wireModelSwap(srv, phr, cfg) + // Same on the unlock path, with the API that has just replaced the + // locked placeholder (V-637). + wireTelegramIntake(ctx, &wg, newAPI, cfg) keeper := wireVision(ctx, &wg, srv, st, embedderOf(voiceW), cfg) wireCapture(ctx, &wg, srv, keeper, st, voiceW, phr, cfg) // Voice identification (Vikunja #255). Enrolment plumbing only until a diff --git a/cmd/mavend/telegramintake.go b/cmd/mavend/telegramintake.go new file mode 100644 index 0000000..ed63bba --- /dev/null +++ b/cmd/mavend/telegramintake.go @@ -0,0 +1,59 @@ +// 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.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 + } +}