"errors"
"fmt"
"io"
+ "log"
"net"
"os"
"path/filepath"
// socketPathBytes is the longest path of a Unix socket on Linux.
socketPathBytes = 107
+
+ // terminalSource is the source of the user messages from the clients of
+ // the socket.
+ terminalSource = "terminal"
)
// errTokensInUse tells that another onechat process uses the token file.
}
// daemon runs "onechat daemon": it owns the root agent and its files and serves
-// the clients on the Unix socket in the data directory until ctx ends. It holds
-// the lock of the token file, so it is the only process that refreshes the
-// tokens.
+// the clients on the Unix socket in the data directory until ctx ends. When
+// telegram.json exists, it also runs the Telegram bot. It holds the lock of the
+// token file, so it is the only process that refreshes the tokens.
func daemon(ctx context.Context, out io.Writer) (err error) {
dir, err := dataDir()
if err != nil {
return err
}
+ botSettings, hasBot, err := loadBotConfig(dir)
+ if err != nil {
+ return err
+ }
+
tokens, err := lockTokens(dir)
if err != nil {
return err
return errors.Join(err, listener.Close())
}
- return serveAgent(ctx, listener, func(notify func(Event)) (*loop, error) {
+ var telegram *telegramBot
+ if hasBot {
+ telegram = newTelegramBot(newBotAPI(telegramAPI, botSettings.Token), botSettings.Owner, filepath.Join(agent.dir, offsetFileName), log.New(out, "telegram: ", log.LstdFlags))
+ _, err = fmt.Fprintln(out, "onechat: the Telegram bot waits for messages of its owner")
+ if err != nil {
+ return errors.Join(err, listener.Close())
+ }
+ }
+
+ return serveAgent(ctx, listener, telegram, func(notify func(Event)) (*loop, error) {
return newLoop(agent, memory, client, time.Now, notify)
})
}
// serveAgent runs the loop that start makes and serves the clients of
-// listener until ctx ends or the loop fails. It closes listener.
-func serveAgent(ctx context.Context, listener net.Listener, start func(notify func(Event)) (*loop, error)) error {
+// listener until ctx ends or the loop fails. It closes listener. When bot is
+// not nil, it runs the bot too, and a failure of the bot stops the agent.
+func serveAgent(ctx context.Context, listener net.Listener, bot *telegramBot, start func(notify func(Event)) (*loop, error)) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
clients: make(map[*hubClient]bool),
}
- l, err := start(hub.Broadcast)
+ 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())
}
hub.Accept(ctx, listener, l)
}()
+ var botErr error
+ if bot != nil {
+ workers.Add(1)
+ go func() {
+ defer workers.Done()
+
+ botErr = bot.Run(ctx, l)
+ cancel()
+ }()
+ }
+
err = l.Run(ctx)
+ if errors.Is(err, context.Canceled) {
+ err = nil
+ }
+
cancel()
err = errors.Join(err, listener.Close())
hub.CloseAll()
workers.Wait()
- if errors.Is(err, context.Canceled) {
- return nil
- }
-
- return err
+ return errors.Join(err, botErr)
}
// hub holds the connected clients and sends each event to all of them.
return errors.New("empty message")
}
- _, err := l.Submit(request.Text)
+ _, err := l.Submit(terminalSource, request.Text)
return err
case "interrupt":