Two issues with the 0.37.0 folder zip-download:
1. Hang / server error (reported as 501) on "some folders". archive_dir
tried to zip every entry, including non-regular files. Opening a FIFO
blocks forever (no writer); a unix socket / unreadable file raised an
OSError that aborted the whole archive. Now only real regular files are
zipped — FIFOs, sockets, devices and symlinks are skipped without ever
open()-ing them, and a per-file read error skips just that file instead
of failing the download.
2. No feedback while a large folder is being prepared. The zip is built
server-side before any bytes flow, so the click felt dead. The Files
page now shows an indeterminate "Preparing <name>…" bar from click,
switching to a real percentage during the transfer (Content-Length is
known for the finished zip). filesApi.download forwards onDownloadProgress.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The file browser/editor could only download individual files. Add a
recursive directory download that streams the folder as a zip archive,
on the local host and on every remote agent.
- file_service.archive_dir(): zip a directory recursively into a temp
file, preserving the folder name as the archive root and empty
subdirectories; symlinks are skipped (no sandbox escape / loops).
- /api/files/download and /agent/files/download branch on directories
and return application/zip, cleaning up the temp file afterwards.
- Files page: show the download button for folders too (as <name>.zip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Folder upload: the Files page gained an "Upload folder" picker
(webkitdirectory); each file is sent with its webkitRelativePath and the
backend recreates the directory tree. upload_target now accepts an optional
rel_path, creating intermediate dirs (mkdir -p) inside the sandbox with each
component validated against traversal.
Copy/move: new file_service.copy/move + POST /api/files/{copy,move}
(admin, audit-logged). The UI adds per-row copy/cut actions, a clipboard bar
to paste into the current directory, and an overwrite prompt on conflict.
Both refuse to move/copy a folder into itself or its own subtree and are
sandbox-checked on source and destination.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a full host filesystem browser reachable from the sidebar (/files):
breadcrumb navigation, browse-root chips, show-hidden toggle, and a table
with size/permissions/mtime. Text files open in a Monaco editor (language by
extension); binary/oversized files fall back to download. Admins can create
folders/files, rename, delete (recursive for dirs), upload, and save edits;
download is available to all users. Every mutation is audit-logged.
Backend: new services/file_service.py reuses device_service's sandbox helpers
(confined to ALLOWED_BROWSE_ROOTS, mapped via HOST_ROOT_PREFIX) and rejects
path traversal and deleting a browse root. routers/files.py exposes
/api/files/{list,read,download,write,mkdir,touch,rename,upload,DELETE}
(reads: any user; mutations: admin). device_service.browse entries gained
mtime + symlink (non-breaking).
Deployment: ALLOWED_BROWSE_ROOTS + HOST_ROOT_PREFIX are now env-wired in
docker-compose.yml and .env.example, with a commented /:/host_root mount to
browse/manage the real host filesystem.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>