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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289package daemon
import ( "context" "encoding/json" "errors" "io" "net/http" "time"
"albedo/cli/internal/daemon/protocol")
type FolderList struct { Path string `json:"path"` Home string `json:"home"` Entries []FolderEntry `json:"entries"` Truncated bool `json:"truncated"`}
type FolderEntry struct { Name string `json:"name"` // VCS is "jj", "git", or empty when the folder is no repository root. VCS string `json:"vcs"` Modified int64 `json:"modified"` // Unix seconds; zero means unknown. Hidden bool `json:"hidden"`}
// Repo is the repository a folder is in. Fields whose command failed are// empty; Kind and Root are always there.type Repo struct { Bookmark *Bookmark `json:"bookmark"` Changed *int `json:"changed"` Touched *int64 `json:"touched"` // Unix seconds, when observed. Kind string `json:"kind"` Root string `json:"root"` Branch string `json:"branch"` Commit string `json:"commit"` Change string `json:"change"`}
type Bookmark struct { Name string `json:"name"` Ahead int `json:"ahead"`}
type FolderPreview struct { Path string `json:"path"` Repo *Repo `json:"repo"` Languages []LanguageShare `json:"languages"` Tree []FolderNode `json:"tree"` More int `json:"more"`}
type LanguageShare struct { Color *string `json:"color"` Name string `json:"name"` Share float64 `json:"share"`}
type FolderNode struct { Name string `json:"name"` Language string `json:"language"` Children []FolderNode `json:"children"` Changed int `json:"changed"` More int `json:"more"` Dir bool `json:"dir"`}
type HostStatus struct { Host string `json:"host"` State string `json:"state"` Detail string `json:"detail"` Step string `json:"step"` OS string `json:"os"` Arch string `json:"arch"` Home string `json:"home"` ControlPath string `json:"control_path"`}
// KnownHost is a host worth offering: one sessions worked on ("recent"), or// a Host entry of the daemon's ssh config ("config"). State is the cached// probe's, empty when there is none.type KnownHost struct { Host string `json:"host"` Label string `json:"label"` Source string `json:"source"` State string `json:"state"`}
func workspaceDirectory(ctx context.Context, conn *Connection, location string, preview bool) (protocol.WorkspaceDirectory, error) { params := protocol.GetWorkspacesParams{Location: &location, Limit: new(int64(200))} if preview { params.Include = new("preview") } var result protocol.WorkspaceDirectory err := walkPages(func(next *string) (protocol.WorkspaceDirectory, *string, error) { params.Next = next var page protocol.WorkspaceDirectory pageErr := executeRead(ctx, conn, operation{Capability: "workspace_browsing", Name: "browse workspace", BuildRequest: func(base string, body io.Reader) (*http.Request, error) { return protocol.NewGetWorkspacesRequest(base, ¶ms) }, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &page) }) return page, page.Next, pageErr }, func(page protocol.WorkspaceDirectory) error { if page.Items == nil { return fieldError("workspace entries") } if result.Directory == "" { result = page } else { if result.Directory != page.Directory { return fieldError("workspace page") } result.Items = append(result.Items, page.Items...) } return nil }, "workspace page cursor") if err != nil { return result, err } return result, nil}
func ListFolders(ctx context.Context, conn *Connection, path string) (FolderList, error) { w, err := workspaceDirectory(ctx, conn, path, false) result := FolderList{Path: w.Directory, Home: value(w.Home), Entries: []FolderEntry{}} for _, row := range w.Items { var modified int64 if t := timestampSeconds(value(row.ModifiedAt)); t != nil { modified = *t } result.Entries = append(result.Entries, FolderEntry{Name: row.Name, VCS: value(row.Vcs), Modified: modified, Hidden: row.Hidden}) } return result, err}func previewValue(w protocol.WorkspacePreview, path string) (FolderPreview, error) { result := FolderPreview{Path: path, More: int(w.More), Languages: []LanguageShare{}, Tree: []FolderNode{}} if len(w.Repository) > 0 && string(w.Repository) != "null" { var kind struct { Kind string `json:"kind"` } if err := json.Unmarshal(w.Repository, &kind); err != nil { return result, err } switch kind.Kind { case "git": var r protocol.GitFacts if err := decodeRequired(w.Repository, &r); err != nil { return result, err } result.Repo = &Repo{Kind: r.Kind, Root: r.Root, Branch: value(r.Branch), Commit: value(r.Revision), Changed: intPointer(r.Changed), Touched: timestampSeconds(value(r.TouchedAt))} case "jj": var r protocol.JJFacts if err := decodeRequired(w.Repository, &r); err != nil { return result, err } result.Repo = &Repo{Kind: r.Kind, Root: r.Root, Change: value(r.ChangeID), Commit: value(r.Revision), Changed: intPointer(r.Changed), Touched: timestampSeconds(value(r.TouchedAt))} if r.Bookmark != nil { result.Repo.Bookmark = &Bookmark{Name: r.Bookmark.Name, Ahead: int(r.Bookmark.Ahead)} } default: return result, fieldError("repository kind") } } for _, row := range w.Languages { result.Languages = append(result.Languages, LanguageShare{Name: row.Name, Color: row.Color, Share: row.Share}) } for _, row := range w.Tree { node := FolderNode{Name: row.Name, Language: value(row.Language), Dir: row.Kind == "directory", Changed: int(row.Changed), More: int(row.More)} for _, child := range row.Children { node.Children = append(node.Children, FolderNode{Name: child.Name, Dir: child.Kind == "directory", Language: value(child.Language), Changed: int(child.Changed)}) } result.Tree = append(result.Tree, node) } return result, nil}func PreviewFolder(ctx context.Context, conn *Connection, path string) (FolderPreview, error) { w, err := workspaceDirectory(ctx, conn, path, true) if err != nil { return FolderPreview{}, err } if w.Preview == nil { return FolderPreview{}, fieldError("workspace preview") } return previewValue(*w.Preview, w.Directory)}func FolderRepo(ctx context.Context, conn *Connection, path string) (*Repo, error) { preview, err := PreviewFolder(ctx, conn, path) return preview.Repo, err}func MoveSession(ctx context.Context, conn *Connection, id, workspace string, condition SessionCondition) (Session, error) { return patchSession(ctx, conn, id, condition.ETag, map[string]any{"workspace": workspace, "family_revision": condition.FamilyRevision})}func hostValue(w protocol.Host) HostStatus { r := HostStatus{Host: w.Target, State: w.State, Step: value(w.Step), OS: value(w.Os), Arch: value(w.Architecture), Home: value(w.Home)} if w.Detail != nil { r.Detail = w.Detail.Detail } if w.Authentication != nil { r.ControlPath = value(w.Authentication.ControlPath) } return r}func hosts(ctx context.Context, conn *Connection, target *string) ([]protocol.Host, error) { result := []protocol.Host{} params := protocol.ListHostsParams{Limit: new(int64(200)), Target: target} err := walkPages(func(next *string) (protocol.HostPage, *string, error) { params.Next = next var page protocol.HostPage pageErr := executeRead(ctx, conn, operation{Capability: "host_probes", Name: "list hosts", BuildRequest: func(base string, body io.Reader) (*http.Request, error) { return protocol.NewListHostsRequest(base, ¶ms) }, Policy: readRecovery}, func(data []byte) error { return decodeRequired(data, &page) }) return page, page.Next, pageErr }, func(page protocol.HostPage) error { result = append(result, page.Items...) return nil }, "host page cursor") if err != nil { return nil, err } return result, nil}
func ListHosts(ctx context.Context, conn *Connection) ([]KnownHost, error) { rows, err := hosts(ctx, conn, nil) result := []KnownHost{} for _, row := range rows { result = append(result, KnownHost{Host: row.Target, Label: row.Target, State: row.State}) } return result, err}func GetHost(ctx context.Context, conn *Connection, host string) (HostStatus, error) { rows, err := hosts(ctx, conn, &host) if err != nil { return HostStatus{}, err } for _, row := range rows { if row.Target == host { return hostValue(row), nil } } return HostStatus{Host: host, State: "unknown"}, nil}func WarmHost(ctx context.Context, conn *Connection, host string) (HostStatus, error) { if ctx == nil { ctx = context.Background() } ctx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() var w protocol.Host op := operation{Capability: "host_probes", Name: "probe host", BuildRequest: func(base string, body io.Reader) (*http.Request, error) { return protocol.NewProbeHostRequestWithBody(base, host, "application/json", body) }, Body: struct{}{}, Policy: noRecovery} body, err := requestBytes(ctx, conn, op, responseLimits{successStatus: http.StatusAccepted, bodyBytes: 1024 * 1024, errorBytes: 64 * 1024}) if err != nil { return HostStatus{}, err } if err = decodeRequired(body, &w); err != nil { return HostStatus{}, invalidResponse(op, "", err) } if w.Target != host { return HostStatus{}, errors.New("host probe returned another host") } status := hostValue(w) for status.State == "probing" || status.State == "unknown" { timer := time.NewTimer(200 * time.Millisecond) select { case <-ctx.Done(): timer.Stop() return HostStatus{}, ctx.Err() case <-timer.C: } status, err = GetHost(ctx, conn, host) if err != nil { return HostStatus{}, err } } return status, nil}func (c *ChatClient) WarmHost(ctx context.Context, host string) (HostStatus, error) { return WarmHost(ctx, c.conn, host)}func (c *ChatClient) Host(ctx context.Context, host string) (HostStatus, error) { return GetHost(ctx, c.conn, host)}func (c *ChatClient) LocalDaemon() bool { return c.conn.Local() }