-// checkSocketPath makes sure that path fits in the address of a Unix socket.
-func checkSocketPath(path string) error {
- if len(path) > socketPathBytes {
- return fmt.Errorf("socket path %s is longer than %d bytes: set ONECHAT_DIR to a shorter path", path, socketPathBytes)
+// daemonServe runs the loop that start makes and serves the clients of listener
+// until ctx ends or the loop fails. It closes listener. It runs whatsapp, which
+// logs its batches and notes in the loop and tells all clients its notices, and
+// a failure of whatsapp stops the agent. It runs schedules, which log their
+// runs in the loop, and a failure of schedules stops the agent too. When bot is
+// not nil, it runs the bot too, and a failure of the bot stops the agent.
+func daemonServe(ctx context.Context, listener net.Listener, bot *TelegramBot, whatsapp *WhatsApp, schedules *Schedules, start func(notify func(Event)) (*TurnLoop, error)) error {
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ hub := &DaemonHub{
+ whatsapp: whatsapp,
+ clients: make(map[*DaemonClient]bool),
+ }
+
+ notify := hub.Broadcast
+ if bot != nil {
+ notify = func(event Event) {
+ hub.Broadcast(event)
+ bot.Notify(event)
+ }
+ }
+
+ l, err := start(notify)
+ if err != nil {
+ return errors.Join(err, listener.Close())
+ }
+
+ var workers sync.WaitGroup
+ workers.Add(2)
+ go func() {
+ defer workers.Done()
+
+ hub.Accept(ctx, listener, l)
+ }()
+
+ var whatsappErr error
+ go func() {
+ defer workers.Done()
+
+ whatsappErr = whatsapp.Run(ctx, l, notify)
+ cancel()
+ }()
+
+ var schedulesErr error
+ workers.Add(1)
+ go func() {
+ defer workers.Done()
+
+ schedulesErr = schedules.Run(ctx, l)
+ cancel()
+ }()
+
+ var botErr error
+ if bot != nil {
+ workers.Add(1)
+ go func() {
+ defer workers.Done()
+
+ botErr = bot.Run(ctx, l, whatsapp)
+ cancel()
+ }()