Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114package tui
import ( "cmp" "context" "errors" "os/exec" "slices"
tea "charm.land/bubbletea/v2"
"albedo/cli/internal/daemon")
// hostSignIn is the session's host needing a person to sign in over ssh// before a waiting turn can start. ctrl+l opens the daemon's ControlMaster// in this terminal when the daemon shares this machine, so ssh can ask for// the passphrase or code here; then the host is probed again and the// waiting turn starts on the daemon's next try.type hostSignIn struct { host, controlPath, notice string here bool}
// chatHostAuthMsg is the probe of a host a blocked turn may be waiting on.type chatHostAuthMsg struct { SessionID string Generation int64 Status daemon.HostStatus}
type chatSignedInMsg struct { SessionID string Err error}
// hostAuthCmd asks the host of a remote session whose waiting turn is// blocked whether it needs a sign-in: the daemon's reason is ssh's words,// and the probe carries the control path the sign-in opens.func (m *ChatModel) hostAuthCmd(blocked string) tea.Cmd { host, _ := daemon.SplitLocation(m.Workspace) if host == "" || blocked == "" || m.hostAuth != nil { return nil } client, ctx, id, generation := m.client, m.streamCtx, m.SessionID, m.Generation return func() tea.Msg { status, err := client.WarmHost(ctx, host) if err != nil || status.State != "needs_auth" { return nil } return chatHostAuthMsg{SessionID: id, Generation: generation, Status: status} }}
// offerSignIn says how to sign in: here when the daemon shares this machine// (its master socket is ours to open), otherwise on the daemon's machine.func (m *ChatModel) offerSignIn(msg chatHostAuthMsg) { if msg.SessionID != m.SessionID || msg.Generation != m.Generation || m.hostAuth != nil { return } host := msg.Status.Host auth := &hostSignIn{host: host, controlPath: msg.Status.ControlPath, here: m.client.LocalDaemon()} auth.notice = "Run `ssh " + host + "` on the machine albedo runs on; the waiting message goes after." if auth.here { auth.notice = "Press ctrl+l to sign in to " + cmp.Or(m.Host, host) + " here; the waiting message goes after." } m.hostAuth = auth m.AddError(auth.notice)}
// signInCmd runs `ssh -M -fN` on the daemon's control path: ssh prompts in// the terminal, forks the master into the background once signed in, and the// daemon's BatchMode connections ride it from then on.func (m *ChatModel) signInCmd() tea.Cmd { auth, client, id := m.hostAuth, m.client, m.SessionID return tea.ExecProcess(signInCommand(auth.host, auth.controlPath), func(err error) tea.Msg { if err == nil { var status daemon.HostStatus status, err = client.WarmHost(context.Background(), auth.host) if err == nil && status.State != "ready" { err = errors.New(cmp.Or(status.Detail, status.State)) } } return chatSignedInMsg{SessionID: id, Err: err} })}
// signInCommand opens host's master on controlPath, asking in the terminal.func signInCommand(host, controlPath string) *exec.Cmd { return exec.Command("ssh", "-M", "-fN", "-o", "ControlPath="+controlPath, "-o", "ControlPersist=600", host)}
func (m *ChatModel) signedIn(msg chatSignedInMsg) { auth := m.hostAuth if msg.SessionID != m.SessionID || auth == nil { return } if msg.Err != nil { m.AddError("Signing in to " + auth.host + " failed: " + msg.Err.Error()) return } m.dropSignIn()}
// dropSignIn forgets the offer once nothing waits on it: signed in, or the// turn it was for started or went away.func (m *ChatModel) dropSignIn() { if auth := m.hostAuth; auth != nil { m.hostAuth = nil m.Notices = slices.DeleteFunc(m.Notices, func(n Notice) bool { return n.Message == auth.notice }) m.syncViewportHeight() }}