]> git.giacomolli.uk - onechat/blobdiff - telegram.go
Give the root agent the schedule tools
[onechat] / telegram.go
index 6d5332de8c9868eeabc0210a566063be8ae320f7..814f4544a2e7c942aeb9cb2741774a7960345f6f 100644 (file)
@@ -160,10 +160,15 @@ type TelegramBot struct {
 }
 
 // Notify queues event for the chat. It never blocks, so a slow network does not
-// stop the agent.
+// stop the agent. The WhatsApp batches do not go to the chat: the reply of the
+// agent tells the user what needs the user.
 func (b *TelegramBot) Notify(event Event) {
        switch event.Kind {
-       case "busy", "idle", "reply", "error", "notice":
+       case "busy", "idle", "reply", "error", "notice", "subagent":
+       case "work":
+               if event.Source == whatsappSource {
+                       return
+               }
        case "user":
                if event.Source == telegramSource {
                        return
@@ -185,7 +190,7 @@ func (b *TelegramBot) Notify(event Event) {
 // Run polls for the messages of the owner and sends the queued events to the
 // chat until ctx ends. It returns an error only when a message or the offset
 // cannot be written, because then the bot would lose or repeat messages.
-func (b *TelegramBot) Run(ctx context.Context, l *TurnLoop) error {
+func (b *TelegramBot) Run(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) error {
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()
 
@@ -197,7 +202,7 @@ func (b *TelegramBot) Run(ctx context.Context, l *TurnLoop) error {
                b.send(ctx)
        }()
 
-       err := b.poll(ctx, l)
+       err := b.poll(ctx, l, whatsapp)
        cancel()
        sender.Wait()
 
@@ -247,7 +252,7 @@ func (b *TelegramBot) send(ctx context.Context) {
 // poll gets the updates from the stored offset on and handles each one, until
 // ctx ends. Each update is handled to its end also when ctx ends during it, so
 // a stop does not repeat its message.
-func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop) error {
+func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) error {
        offset, err := telegramLoadOffset(b.offsetPath)
        if err != nil {
                return err
@@ -273,7 +278,7 @@ func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop) error {
                                return nil
                        }
 
-                       err = b.handle(context.WithoutCancel(ctx), l, update)
+                       err = b.handle(context.WithoutCancel(ctx), l, whatsapp, update)
                        if err != nil {
                                return err
                        }
@@ -349,16 +354,20 @@ func (b *TelegramBot) pause(ctx context.Context, err error, wait, asked time.Dur
 
 // handle runs one update. A text message of the owner in the private chat goes
 // to the agent, and handle returns when it is durable in the log. The line
-// /stop interrupts the turn. Any other message of the owner gets the answer
+// /stop interrupts the turn, the line /stop NAME stops the subagent NAME, and a
+// line that starts with /whatsapp runs a WhatsApp command on whatsapp, whose
+// answers go to the chat only. Any other message of the owner gets the answer
 // that only text is supported. Every other update is ignored, and its content
 // is not logged.
-func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, update TelegramUpdate) error {
+func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp, update TelegramUpdate) error {
        message := update.Message
        if message == nil || message.Chat.Type != "private" || message.Chat.ID != b.owner || message.From == nil || message.From.ID != b.owner {
                b.logger.Print("update ignored: not a message of the owner in the private chat")
                return nil
        }
 
+       name, stop := stopSubagentName(message.Text)
+       args, isWhatsApp := whatsappCommandArgs(message.Text)
        switch {
        case message.Text == "":
                b.notice(telegramTextOnly)
@@ -366,6 +375,17 @@ func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, update TelegramUp
                if !l.Interrupt() {
                        b.notice("No turn runs.")
                }
+       case stop:
+               found, err := l.StopSubagent(name)
+               if err != nil {
+                       return fmt.Errorf("stop subagent %s: %w", name, err)
+               }
+
+               if !found {
+                       b.notice("No subagent " + name + " runs.")
+               }
+       case isWhatsApp:
+               whatsapp.Command(args, b.notice)
        default:
                _, err := l.Submit(telegramSource, message.Text)
                if err != nil {
@@ -530,11 +550,17 @@ func telegramSplitMessage(text string, limit int) []string {
 }
 
 // telegramText returns the text of event for the chat. A user message of
-// another client has its source as a mark.
+// another client has its source as a mark. A subagent shows only its start and
+// how its run ended, because the reply of the agent carries the content of the
+// report.
 func telegramText(event Event) string {
        switch event.Kind {
        case "user":
                return "[" + event.Source + "] " + event.Text
+       case "subagent":
+               return "[" + event.Text + "] started"
+       case "work":
+               return "[" + event.Source + "] " + event.Status
        case "error":
                return "Error: " + event.Text
        default: