package backthink // The production composition root builds the gateway hub and the channel bridge // *after* backthink.New (the hub needs the loop, the bridge needs the loop and // owner profile, both of which are assembled later), so the ActiveClient and // IMAdapter sinks are unavailable at construction time. These setters inject them // once the gateway / bridge exist and rebuild the pusher, keeping the resolved // rate gate so a later push routes correctly. // SetActiveSink wires the production active web/native client sink (the gateway // hub) and rebuilds the pusher. Call it after the gateway exists, before Start. func (b *Backthink) SetActiveSink(active ActiveClient) { b.mu.Lock() defer b.mu.Unlock() b.deps.Active = active b.rebuildPusherLocked() } // SetIMSink wires the production IM bridge sink (the channel bridge) and rebuilds // the pusher. Call it after the channel bridge exists, before Start. func (b *Backthink) SetIMSink(im IMAdapter) { b.mu.Lock() defer b.mu.Unlock() b.deps.IM = im b.rebuildPusherLocked() } // rebuildPusherLocked replaces the pusher from the current sinks, gate and push // outlet. The caller must hold b.mu. func (b *Backthink) rebuildPusherLocked() { b.pusher = newPusher(b.deps.Active, b.deps.IM, b.gate, b.log).withExporter(b.deps.Exporter) } // currentPusher returns the active pusher under the lifecycle lock, so a sink // swap never races with a concurrent thinking pass that is about to push. func (b *Backthink) currentPusher() Pusher { b.mu.Lock() defer b.mu.Unlock() return b.pusher }