2026-10-15 12:06:50 -07:00
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
|
|
"github.com/coder/websocket"
|
|
|
|
|
|
2026-10-18 15:32:07 -07:00
|
|
|
"g.fc.run/ai/byre/internal/api"
|
|
|
|
|
"g.fc.run/ai/byre/internal/terminal"
|
2026-10-15 12:06:50 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
terminalReadLimit = 1 << 20
|
|
|
|
|
terminalExited = websocket.StatusCode(4000)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (s *Server) GetTaskTerminal(w http.ResponseWriter, r *http.Request, taskID string, params api.GetTaskTerminalParams) {
|
|
|
|
|
t, _, ok := s.activeTask(w, r, taskID)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
size := api.TerminalSize{Cols: params.Cols, Rows: params.Rows}
|
2026-10-18 15:05:28 -07:00
|
|
|
if !terminal.ValidSize(size) {
|
|
|
|
|
writeError(w, http.StatusBadRequest, fmt.Errorf("cols and rows must be between 1 and %d", terminal.MaxSize))
|
2026-10-15 12:06:50 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conn, err := websocket.Accept(w, r, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
slog.Warn("accept terminal websocket", "task", taskID, "err", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer conn.CloseNow()
|
|
|
|
|
conn.SetReadLimit(terminalReadLimit)
|
|
|
|
|
|
2026-10-18 15:05:28 -07:00
|
|
|
term, err := s.terminals.Attach(t, size)
|
2026-10-15 12:06:50 -07:00
|
|
|
if err != nil {
|
|
|
|
|
slog.Error("start terminal", "task", taskID, "err", err)
|
|
|
|
|
conn.Close(websocket.StatusInternalError, "couldn't start the shell; see byre's log")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-10-18 15:05:28 -07:00
|
|
|
term.Resize(size)
|
2026-10-15 12:06:50 -07:00
|
|
|
|
2026-10-18 15:05:28 -07:00
|
|
|
backlog, out := term.Subscribe()
|
2026-10-15 12:06:50 -07:00
|
|
|
if out == nil {
|
2026-10-18 15:05:28 -07:00
|
|
|
exit, _ := term.ExitStatus()
|
2026-10-15 12:06:50 -07:00
|
|
|
conn.Close(terminalExited, exit)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-10-18 15:05:28 -07:00
|
|
|
defer term.Unsubscribe(out)
|
2026-10-15 12:06:50 -07:00
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(r.Context())
|
|
|
|
|
defer cancel()
|
|
|
|
|
go func() {
|
|
|
|
|
defer cancel()
|
2026-10-17 23:24:26 -07:00
|
|
|
terminalInput(ctx, conn, term, func() { s.acted(actor(r), r) })
|
2026-10-15 12:06:50 -07:00
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
if len(backlog) > 0 {
|
|
|
|
|
err = conn.Write(ctx, websocket.MessageBinary, backlog)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case b, open := <-out:
|
|
|
|
|
if open {
|
|
|
|
|
err = conn.Write(ctx, websocket.MessageBinary, b)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-10-18 15:05:28 -07:00
|
|
|
exit, exited := term.ExitStatus()
|
2026-10-15 12:06:50 -07:00
|
|
|
if exited {
|
|
|
|
|
conn.Close(terminalExited, exit)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
conn.Close(websocket.StatusTryAgainLater, "fell behind the terminal's output")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-18 15:05:28 -07:00
|
|
|
func terminalInput(ctx context.Context, conn *websocket.Conn, term *terminal.Terminal, acted func()) {
|
2026-10-15 12:06:50 -07:00
|
|
|
for {
|
|
|
|
|
typ, b, err := conn.Read(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if typ == websocket.MessageBinary {
|
2026-10-17 23:24:26 -07:00
|
|
|
acted()
|
2026-10-18 15:05:28 -07:00
|
|
|
err = term.Write(b)
|
2026-10-15 12:06:50 -07:00
|
|
|
if err != nil {
|
|
|
|
|
slog.Debug("terminal input", "err", err)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
size := api.TerminalSize{}
|
|
|
|
|
err = json.Unmarshal(b, &size)
|
2026-10-18 15:05:28 -07:00
|
|
|
if err != nil || !terminal.ValidSize(size) {
|
2026-10-15 12:06:50 -07:00
|
|
|
conn.Close(websocket.StatusUnsupportedData, "resize must be a TerminalSize")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-10-18 15:05:28 -07:00
|
|
|
term.Resize(size)
|
2026-10-15 12:06:50 -07:00
|
|
|
}
|
|
|
|
|
}
|