]> git.giacomolli.uk - onechat/blobdiff - daemon.go
Add local context to memory operation failures
[onechat] / daemon.go
index 9f71d801be1c985ee70590e7118e69eeba91abbb..d6aa6aaa8cf247cb2ec4a613fab3344cf0b7eaaf 100644 (file)
--- a/daemon.go
+++ b/daemon.go
@@ -44,9 +44,9 @@ type DaemonClient struct {
        once   sync.Once
 }
 
-// Accept serves each client of listener until the listener closes. A new
-// client gets a ready event with the name of the agent, and a busy event when a
-// turn runs.
+// Accept serves each client of listener until the listener closes. A new client
+// gets a ready event with the name of the agent, and a busy event when a turn
+// runs.
 func (h *DaemonHub) Accept(ctx context.Context, listener net.Listener, l *TurnLoop) {
        for {
                conn, err := listener.Accept()
@@ -167,14 +167,16 @@ func (h *DaemonHub) send(client *DaemonClient, event Event) {
        }
 }
 
-// remove removes client, closes its connection, and ends its writer. The
-// caller holds the mutex.
+// remove removes client, closes its connection, and ends its writer. The caller
+// holds the mutex.
 func (h *DaemonHub) remove(client *DaemonClient) {
        delete(h.clients, client)
        client.once.Do(func() {
                close(client.events)
 
-               _ = client.conn.Close()
+               // The client is removed already, so a close failure cannot
+               // restore it.
+               client.conn.Close() // nolint: errcheck
        })
 }
 
@@ -201,6 +203,61 @@ func (h *DaemonHub) CloseAll() {
        h.workers.Wait()
 }
 
+// daemonRunRequest runs one request of a client. The answers of a WhatsApp
+// command go to answer.
+func daemonRunRequest(l *TurnLoop, whatsapp *WhatsApp, request SocketRequest, answer func(text string)) error {
+       switch request.Type {
+       case "send":
+               if request.Text == "" {
+                       return errors.New("empty message")
+               }
+
+               _, err := l.Submit(terminalSource, request.Text)
+
+               return err
+       case "image":
+               if !filepath.IsAbs(request.Path) {
+                       return errors.New("use /image PATH [caption]")
+               }
+
+               data, err := imageReadFile(request.Path)
+               if err != nil {
+                       return fmt.Errorf("read terminal image: %w", err)
+               }
+
+               name, err := l.agent.Images().Save(data)
+               if err != nil {
+                       return fmt.Errorf("store terminal image: %w", err)
+               }
+
+               _, err = l.Submit(terminalSource, imageText([]string{name}, request.Text))
+               if err != nil {
+                       return fmt.Errorf("submit terminal image: %w", err)
+               }
+
+               return nil
+       case "interrupt":
+               if !l.Interrupt() {
+                       return errors.New("no turn runs")
+               }
+
+               return nil
+       case "whatsapp":
+               whatsapp.Command(request.Text, answer)
+
+               return nil
+       case "stop":
+               found, err := l.StopSubagent(request.Text)
+               if err == nil && !found {
+                       err = fmt.Errorf("no subagent %s runs", request.Text)
+               }
+
+               return err
+       default:
+               return fmt.Errorf("unknown request %q", request.Type)
+       }
+}
+
 // daemon runs "onechat daemon": it owns the root, its children, and their
 // files, and serves the clients on the Unix socket in the data directory until
 // ctx ends. When telegram.json exists, it also runs the Telegram bot. It also
@@ -489,58 +546,3 @@ func daemonRunChild(ctx context.Context, loop *TurnLoop, schedules *Schedules) e
 
        return errors.Join(err, <-done)
 }
-
-// daemonRunRequest runs one request of a client. The answers of a WhatsApp
-// command go to answer.
-func daemonRunRequest(l *TurnLoop, whatsapp *WhatsApp, request SocketRequest, answer func(text string)) error {
-       switch request.Type {
-       case "send":
-               if request.Text == "" {
-                       return errors.New("empty message")
-               }
-
-               _, err := l.Submit(terminalSource, request.Text)
-
-               return err
-       case "image":
-               if !filepath.IsAbs(request.Path) {
-                       return errors.New("use /image PATH [caption]")
-               }
-
-               data, err := imageReadFile(request.Path)
-               if err != nil {
-                       return fmt.Errorf("read terminal image: %w", err)
-               }
-
-               name, err := l.agent.Images().Save(data)
-               if err != nil {
-                       return fmt.Errorf("store terminal image: %w", err)
-               }
-
-               _, err = l.Submit(terminalSource, imageText([]string{name}, request.Text))
-               if err != nil {
-                       return fmt.Errorf("submit terminal image: %w", err)
-               }
-
-               return nil
-       case "interrupt":
-               if !l.Interrupt() {
-                       return errors.New("no turn runs")
-               }
-
-               return nil
-       case "whatsapp":
-               whatsapp.Command(request.Text, answer)
-
-               return nil
-       case "stop":
-               found, err := l.StopSubagent(request.Text)
-               if err == nil && !found {
-                       err = fmt.Errorf("no subagent %s runs", request.Text)
-               }
-
-               return err
-       default:
-               return fmt.Errorf("unknown request %q", request.Type)
-       }
-}