// errTerminalDaemonClosed tells that the daemon closed the connection.
var errTerminalDaemonClosed = errors.New("the daemon closed the connection")
-// terminal runs "onechat chat": the terminal client of the daemon. Each line of
-// input is a user message, the line /stop interrupts the running turn, the line
-// /stop NAME stops the subagent NAME, and a line that starts with /whatsapp is
-// a WhatsApp command. The client shows the replies, the tool calls, and their
-// results as they happen. It ends at the end of the input or when the daemon
-// closes the connection.
+// terminal runs "onechat" and "onechat chat": the terminal client of the
+// daemon. Each line of input is a user message, the line /stop interrupts the
+// running turn, the line /stop NAME stops the subagent NAME, and a line that
+// starts with /whatsapp is a WhatsApp command. The client shows the replies,
+// the tool calls, and their results as they happen. It ends at the end of the
+// input or when the daemon closes the connection.
func terminal(ctx context.Context, in io.Reader, out io.Writer) error {
dir, err := dataDir()
if err != nil {
}
var dialer net.Dialer
+
conn, err := dialer.DialContext(ctx, "unix", path)
if err != nil {
return fmt.Errorf(`connect to the daemon (start it with "onechat daemon"): %w`, err)
}()
var err error
+
select {
case err = <-sent:
err = errors.Join(err, conn.Close())
+
<-shown
case err = <-shown:
err = errors.Join(terminalCmpError(err, errTerminalDaemonClosed), conn.Close())
case <-ctx.Done():
err = conn.Close()
+
<-shown
}
- return err
+ if err != nil {
+ return fmt.Errorf("run terminal client: %w", err)
+ }
+
+ return nil
}
// terminalSendLines sends each line of in as a request until the input ends.
encoder := json.NewEncoder(conn)
scanner := bufio.NewScanner(in)
scanner.Buffer(make([]byte, 64*1024), socketRequestBytes)
+
for ctx.Err() == nil && scanner.Scan() {
line := scanner.Text()
request := SocketRequest{
}
}
- return scanner.Err()
+ err := scanner.Err()
+ if err != nil {
+ return fmt.Errorf("read terminal input: %w", err)
+ }
+
+ return nil
}
// terminalShowEvents writes each event from the daemon to out until the
func terminalShowEvents(conn net.Conn, out io.Writer) error {
scanner := bufio.NewScanner(conn)
scanner.Buffer(make([]byte, 64*1024), socketRequestBytes)
+
for scanner.Scan() {
var event Event
+
err := json.Unmarshal(scanner.Bytes(), &event)
if err != nil {
return fmt.Errorf("decode event: %w", err)
_, err = io.WriteString(out, text+"\n")
if err != nil {
- return err
+ return fmt.Errorf("write terminal event: %w", err)
}
}
- return scanner.Err()
+ err := scanner.Err()
+ if err != nil {
+ return fmt.Errorf("read daemon events: %w", err)
+ }
+
+ return nil
}
// terminalCmpError returns err, or fallback when err is nil.