small gleam coding and persistent agent daemon with a detachable cli and a canvas ui albedo.sh
albedo robot-docs workspaces.md
6.2 kB · 122 lines
Markdown

workspaces #

a session's workspace is the directory its python kernel, run jobs, memory, skills and AGENTS.md resolve against. /cd in the tui moves a session to another folder; the folder picker browses through the daemon, so it only ever offers folders the daemon itself can see (and works the same once the daemon is remote).

moving a session #

POST /sessions/:id/workspace {"workspace": "/abs/dir"} answers the session's info. the session must be idle and the target an existing absolute directory. the kernel is dropped (python variables start fresh), the transcript stays, and a note records the move.

every descendant of the session (children, their children; open or closed) whose workspace was the same folder follows it. an idle descendant moves at once; a running one moves when its current turn ends. descendants that were working somewhere else keep their own folder. if the session itself cannot move, nobody moves.

browsing #

all three routes take path: absolute, or starting with ~, which expands to the daemon's home. anything else is a 400; a path that is not an existing directory is a 404. errors are {"error": "..."}. /health lists workspace_browser when these routes exist.

GET /fs/list?path=P #

the directories directly inside P, for the picker's list.

{"path": "/Users/dawn/proj", "home": "/Users/dawn", "truncated": false,
 "entries": [{"name": "albedo", "modified": 1790618149, "hidden": false, "vcs": "jj"}]}
  • path is P expanded and normalised (no trailing slash, except /).
  • only directories, symlinks to directories included; sorted by name, case-insensitively. at most 2000; truncated says there were more.
  • modified is the directory's mtime in unix seconds.
  • hidden is a leading dot. the picker hides these unless the query asks.
  • vcs is "jj" when the entry itself contains .jj, else "git" when it contains .git (file or directory), else null. a stat, never a process.

GET /fs/repo?path=P #

the repository P is in, for list rows: {"repo": Repo | null}.

{"kind": "git", "root": "/Users/dawn/proj/tangled", "branch": "master",
 "commit": "4fd0f30", "changed": 3, "touched": 1790610000}
{"kind": "jj", "root": "/Users/dawn/proj/albedo", "change": "nznu",
 "bookmark": {"name": "main", "ahead": 2}, "changed": 1, "touched": 1790618150}
  • the nearest enclosing root wins, walking up from P. at one level .jj beats .git: a colocated repo's git reports a detached HEAD.
  • git: branch is null when HEAD is detached; commit is the short HEAD id, null in an unborn repo. changed counts git status --porcelain=v2 entries, untracked included. touched is the last commit's time.
  • jj: change is @'s shortest change id (at least 4 characters). bookmark is the bookmark @ works on and how many changes @ is past the fork, the newest change in @'s history a bookmark contains (heads(::@ & ::bookmarks())), or null. the name is one at the fork itself, local or remote (a colocated repo's name@git counts), else the newest bookmark that grew from the fork. the nearest bookmark behind @ would often be an old backup of a bookmark that has since moved on. changed is the files @ changes. jj runs with --ignore-working-copy, so browsing never snapshots or writes an operation: changed is as fresh as the last jj command.
  • every vcs command has a short deadline. a field whose command failed or timed out is null; kind and root are always there.

GET /fs/preview?path=P #

everything the preview pane shows for P.

{"path": "/Users/dawn/proj/tangled", "repo": Repo,
 "languages": [{"name": "Go", "color": "#00ADD8", "share": 0.71}],
 "tree": [{"name": "spindle", "dir": true, "changed": 3, "more": 0,
           "children": [{"name": "engine", "dir": true, "changed": 0}]},
          {"name": "flake.nix", "dir": false, "language": "Nix", "changed": 0}],
 "more": 11}
  • languages: the tracked files under P (git ls-files / jj file list), bytes per language, counting only linguist's programming and markup types, as github's language bar does. largest first, at most 4; share is of all counted bytes. empty outside a repository. color is linguist's own hex (or null): the tui adapts it to the terminal.
  • tree: two levels. directories first, then files, each by name; dot entries left out. inside a repository only tracked or changed paths show, so build output and other ignored files stay out of the way. at most 12 top-level entries (more counts the rest); a top-level directory lists at most 4 children (its more counts the rest) and deeper directories list none. language names a file's linguist language, or null. changed counts the changed files at or under an entry.
  • language data is github linguist's languages.yml, shipped in priv/linguist/ and read once. a file is matched by exact filename, then by its longest extension. an extension several languages claim goes to a fixed ranking that favours linguist's popular languages and their primary extensions (popular.yml), so .md is Markdown and .h is C++.
  • share is unrounded.

the picker #

/cd opens it full screen; /cd <path> moves at once. an empty query lists recent folders (the workspaces of your sessions, ranked by how often and how recently they were used). typing a path lists that folder's directories, filtered by the last segment. tab enters the highlighted folder, shift+tab goes up, enter moves there, esc goes back. the same picker replaces the plain text box shown when a session's workspace has gone missing.

in the sessions view, ctrl+f (or ~ or / typed into an empty search) opens the same picker to browse sessions by folder: enter starts a new session in the highlighted folder instead of moving one. in either mode → steps into the highlighted folder's sessions, where enter opens one and ← goes back.

the preview keeps its sessions on screen: the tree fits around a third of the pane kept for them, showing fewer children per directory, then only the top level, then fewer top entries, and the sessions fill whatever the tree leaves.