Expand description
The filesystem layer behind the generic fs.* RPCs and the Files pane
(“Hangar”): paginated listing, known places, name validation, the
mutation safety policy, and the mutations themselves.
Everything here is blocking and is called from spawn_blocking (or the
trash thread): on macOS the first access to a protected folder blocks the
calling thread while a privacy prompt is on screen (spec §9.1.1 fact 10).
Spec: docs/specs/SPEC_FILE_BROWSER_PANE_2026_10_01.md §6.3, §7, §9.
Modules§
- git
fs.git_status: what git says about the entries of a listed folder, for the Files pane’s markers (modified, untracked, ignored…) and its status line (branch, ahead/behind).- host_
jobs - Copy and move between this computer and an SSH host, or within one host
(spec §6.3 of SPEC_REMOTE_TERMINALS_AND_DURABLE_SESSIONS_2026_10_02.md):
the same
fs.opas a local copy, with the same progress, cancel and conflict questions (jobs::Jobs::start_host), the host’s side carried by its helper (remote::files). - jobs
- Copy and move as background jobs:
fs.op.start,fs.op.resolve,fs.op.cancel, with progress published asfiles:opevents. - platform
- The OS-specific half of the Files pane’s filesystem layer: path display form, hidden attributes, a rename that never overwrites, a delete that never follows a link, and the commands that open or reveal a path.
- remote
- Hangar’s file operations on an SSH connection (spec §6.3 of
SPEC_REMOTE_TERMINALS_AND_DURABLE_SESSIONS_2026_10_02.md): the same
requests and results as a local folder, carried out by the host’s helper
(
remote::files). Paths there are the host’s POSIX paths;~is its home. - trash_
worker - Every call into the
trashcrate runs on one dedicated thread.
Structs§
- Cursor
Store - Open listings by cursor id.
- List
Cursor - An open listing between pages.
- Protected
Paths - What a mutation must never touch (spec §9, “Security”): a filesystem
root, the home folder itself, AgentMux’s own data and installation, the
OS’s system folders, and the inside of a macOS app bundle. Nor anything
that CONTAINS one of those: deleting
C:\Userswould delete the home folder too.
Constants§
- CURSOR_
TTL - A cursor is dropped after this long without a page being asked for. The frontend navigating away simply stops asking, so this is the cancellation (spec §6.3).
- DEFAULT_
LIST_ LIMIT - Page size when the request names none.
- MAX_
CONSECUTIVE_ 🔒ENTRY_ ERRORS - Consecutive unreadable directory entries after which a listing gives up, so a directory whose iterator keeps failing ends instead of looping.
- MAX_
CURSORS 🔒 - Open listings held at once. Each holds an OS directory handle, so a burst of abandoned listings is bounded rather than left to the TTL.
- MAX_
LIST_ LIMIT - Largest page a request may ask for.
- MAX_
NAME_ 🔒LEN - Longest name most filesystems accept (NTFS: 255 UTF-16 units; ext4 and APFS: 255 bytes). Checked as UTF-16 units and bytes both.
- ROOT_
REFUSAL 🔒 - WINDOWS_
RESERVED 🔒 - Device names Windows reserves in every folder, with or without an
extension (
nul.txtis still the null device). The superscript digits are reserved too.
Statics§
- CURSORS
- The process-wide cursor store. Process-wide rather than per connection because a window reload reconnects; a cursor id is a random UUID, so one connection can’t guess another’s.
Functions§
- build_
entry 🔒 - One listing row. Never fails: what can’t be read becomes the row’s
error, and the page goes on (spec §9: errors are per entry). - classify_
folder_ error - create
fs.create: a new empty file or folder. Creation itself is the existence check (create_new/create_dirfail if the name is taken), so nothing can be overwritten.- delete_
permanently fs.delete, one path: PERMANENT. A link is unlinked, never followed.- ensure_
cursor_ sweeper - Start the periodic cursor sweep, once per process. Without it, cursors
abandoned after the last
fs.listcall would keep their directory handles open until the next call. - entry_
error_ 🔒message - exists_
message 🔒 - file_
name_ 🔒string - folder_
failure 🔒 - The
fs.listerror for a folder that couldn’t be opened. - home_
and_ known_ places - Home and the known folders, by name only. Nothing here reads or stats a
known folder: on macOS that alone can raise a privacy prompt the user
didn’t ask for (spec §9.1.5). A folder
dirscan’t name is left out. - install_
dir_ 🔒of - The installation a (canonical) executable belongs to: on macOS the whole
bundle, not just Contents/MacOS. The outermost
.app, because srv’s real file sits in a nested helper app (AgentMux.app/Contents/Helpers/AgentMux Server.app, scripts/package-macos.sh), and protecting only that would leave the rest of AgentMux.app open to changes. - list_
failure 🔒 - list_
page - One page of
fs.list, against the process-wide cursor store. - list_
page_ in - One page of
fs.list. A failure to open the directory is anOkanswer witherrorset, never anErr(spec §9: a denied folder must never look empty, and must never fail the pane). - lock_
store 🔒 - The store’s lock, recovered if a panic elsewhere poisoned it: the map is left consistent by every operation on it, and a poisoned lock must not silently end every listing.
- missing_
or 🔒 - open_
listing 🔒 - Canonicalize and open the directory for a first page.
- open_
or_ reveal fs.open/fs.reveal: hand an existing path to the OS. Not limited to home (unlikeopeninshell): opening and revealing change nothing.- path_
key 🔒 - Comparison key for paths: the display form, case-folded on Windows,
whose filesystems are case-insensitive. Component-wise
starts_withon these is a correct “is inside” test. - rename
fs.rename: a new name in the same folder. Never overwrites; a case-only rename goes through a temporary name, because on a case-insensitive filesystem the new name already “exists” (it is the file itself) and some filesystems ignore a direct case-only rename (spec §7.4).- rename_
case_ 🔒only - resolve_
entry_ path - The path a mutation acts on: the request path with its PARENT canonicalized and its own last component kept as given.
- resolve_
request_ path - A request path as an absolute host path:
~expanded, an MSYS-style/c/…converted on Windows, NUL refused, relative refused (a relative path would resolve against srv’s own working directory, which means nothing to the user). - restore
fs.restore: put each path back from the Trash. The paths are the original locations. Must run on the trash thread (trash_worker::run). One result per path, in order.- system_
roots 🔒 - The OS’s own folders (spec §9).
- trash
fs.trash, one path. Must run on the trash thread (trash_worker::run).- validate_
name_ rules - The name rules (spec §7.4), with the Windows-only ones switched by
windowsso every rule can be tested on any platform. Errors are sentences, shown to the user as they are. - validate_
new_ name - Validate a new file or folder name with the current platform’s rules.