+// RouteWhatsApp queues a routed batch as work and wakes its child. It returns
+// false for an unrouted source or a missing child. A saved receipt survives
+// turn acknowledgement until the inbox removes the batch. A repeat after a stop
+// between queueing and inbox removal cannot append a second entry.
+func (m *DaemonMessages) RouteWhatsApp(batch WhatsAppBatch, text string) (bool, error) {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ if m.failure != nil {
+ return false, fmt.Errorf("agent message queue stopped: %w", m.failure)
+ }
+
+ if batch.Number <= 0 {
+ return false, errors.New("invalid WhatsApp batch number")
+ }
+
+ if slices.Contains(m.state.WhatsAppBatches, batch.Number) {
+ return true, nil
+ }
+
+ root := m.ordered[0].agent.config.Name
+ name := m.routes.Target(whatsappSource + ":" + batch.Chat.ID)
+ child := m.loops[name]
+ if child == nil || child.agent.config.Parent != root {
+ return false, nil
+ }
+
+ m.add(root, name, "work", text)
+
+ message := &m.state.Pending[len(m.state.Pending)-1]
+ message.WhatsAppBatch = batch.Number
+ m.state.WhatsAppBatches = append(m.state.WhatsAppBatches, batch.Number)
+ err := m.save()
+ if err == nil {
+ err = m.deliver(message)
+ }
+
+ if err != nil {
+ return false, fmt.Errorf("route WhatsApp batch: %w", m.failed(err))
+ }
+
+ return true, nil
+}
+
+// PruneWhatsApp drops receipts only for batches absent from the durable inbox.
+// Queued turns keep their own log IDs until they finish. Receipts left by a
+// stop after inbox removal are removed by the next inbox delivery.
+func (m *DaemonMessages) PruneWhatsApp(batches []WhatsAppBatch) error {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ if m.failure != nil {
+ return fmt.Errorf("agent message queue stopped: %w", m.failure)
+ }
+
+ held := make(map[int]bool, len(batches))
+ for _, batch := range batches {
+ held[batch.Number] = true
+ }
+
+ before := len(m.state.WhatsAppBatches)
+ m.state.WhatsAppBatches = slices.DeleteFunc(m.state.WhatsAppBatches, func(number int) bool {
+ return !held[number]
+ })
+ if len(m.state.WhatsAppBatches) == before {
+ return nil
+ }
+
+ err := m.save()
+ if err != nil {
+ return fmt.Errorf("prune WhatsApp receipts: %w", m.failed(err))
+ }
+
+ return nil
+}
+