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, and the line /stop interrupts the running turn. 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.
+// 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 {
func terminalSendLines(ctx context.Context, conn net.Conn, in io.Reader) error {
encoder := json.NewEncoder(conn)
scanner := bufio.NewScanner(in)
- scanner.Buffer(make([]byte, 64*1024), requestBytes)
+ scanner.Buffer(make([]byte, 64*1024), socketRequestBytes)
for ctx.Err() == nil && scanner.Scan() {
line := scanner.Text()
- request := clientRequest{
+ request := SocketRequest{
Type: "send",
Text: line,
}
- switch strings.TrimSpace(line) {
- case "":
+ name, stop := stopSubagentName(line)
+ args, whatsapp := whatsappCommandArgs(line)
+ switch {
+ case strings.TrimSpace(line) == "":
continue
- case stopCommand:
- request = clientRequest{
+ case strings.TrimSpace(line) == stopCommand:
+ request = SocketRequest{
Type: "interrupt",
}
+ case stop:
+ request = SocketRequest{
+ Type: "stop",
+ Text: name,
+ }
+ case whatsapp:
+ request = SocketRequest{
+ Type: "whatsapp",
+ Text: args,
+ }
}
err := encoder.Encode(request)
// connection closes.
func terminalShowEvents(conn net.Conn, out io.Writer) error {
scanner := bufio.NewScanner(conn)
- scanner.Buffer(make([]byte, 64*1024), requestBytes)
+ scanner.Buffer(make([]byte, 64*1024), socketRequestBytes)
for scanner.Scan() {
var event Event
err := json.Unmarshal(scanner.Bytes(), &event)
// terminalEventText returns the text that the client shows for event, or "" for
// none. The client shows a user message only from another source, such as
// telegram, because the user typed the others. A tool result shows its first
-// terminalResultLines lines.
+// terminalResultLines lines. The report of a subagent shows whole, and it
+// starts with the name of the subagent.
func terminalEventText(event Event) string {
switch event.Kind {
case "ready":
return "[tool] " + event.Text
case "echo":
return "[result] " + terminalFirstLines(event.Text, terminalResultLines)
+ case "subagent":
+ return "[" + event.Text + "] started"
+ case "work":
+ return event.Text
case "busy":
return "[working]"
case "idle":
return "[done]"
case "error":
return "[error] " + event.Text
+ case "notice":
+ return event.Text
default:
return ""
}