feat: v2.7.7 — File Explorer on workspaces and the user's home, clone with per-user git identity
- File Explorer (Files in the sidebar, /files; also from a workspace's Files tab, portalled so no panel clips it), laid out like GNOME Files: places (Home, workspaces, Trash), Back/Forward/Up, path bar, search, icon grid with image thumbnails or list, labelled New/Upload, selection action bar, hidden files behind Ctrl+H, right-click menus and the usual keys. Make, rename, move, copy (within a workspace, between workspaces, home ↔ workspace), delete into a per-account trash restorable for 7 days, streamed upload of files and whole folders (≤ 500 MB a batch, Replace / Keep both / Skip on a taken name), download a file or a zip of anything. server/lib/file-explorer.js, /api/lanes/:id/fs/*, /api/homes/:who/fs/*. - The user's home: every dot entry at its top stays hidden and refused (also via a symlink, download, zip or as a copy source); an admin may view other users' homes read-only, audited. - write-as learns move/copy (re-run `sudo agenthub server install-system`). - Hardening: a write to a workspace re-checks its owner by the live role; S3 keeps its server.conf if the file stops reading and a tmux call never falls back to the host's server; the AI Proxy gateway marks what it relays and the dashboard refuses it; a NUL in a pane's argv/env is refused. - OpenAPI for the new routes (server/openapi-extra/files.js); docs (API, SERVER-MODE, CLAUDE.md) updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+55
@@ -258,6 +258,61 @@ folder name inside an existing folder, `400 EBADNAME`/`ENOTFOUND`,
|
||||
not at all without one (`403 EFOLDER`); a member's folder is made as the
|
||||
member (S3).
|
||||
|
||||
#### File Explorer
|
||||
|
||||
```
|
||||
GET /api/lanes/:id/fs/list?path= { path, parent, entries: [{ name, path, type: dir|file|link|linkdir|other, size, mtime }], truncated }
|
||||
POST /api/lanes/:id/fs/mkdir { path } → 201 { path }
|
||||
POST /api/lanes/:id/fs/newfile { path } → 201 { path } (empty file)
|
||||
POST /api/lanes/:id/fs/rename { path, name } → { path }
|
||||
POST /api/lanes/:id/fs/move { paths, dest, conflict? } → { moved }
|
||||
POST /api/lanes/:id/fs/copy { paths, dest, conflict?, fromLane? } → { copied }
|
||||
POST /api/lanes/:id/fs/delete { paths } → { trashed: [ids] }
|
||||
GET /api/lanes/:id/fs/trash → { items: [{ id, name, laneId, path, isDir, deletedAt }], keepDays }
|
||||
POST /api/lanes/:id/fs/trash/restore { id } → { path, laneId }
|
||||
POST /api/lanes/:id/fs/trash/purge { id | "all" } → { purged }
|
||||
POST /api/lanes/:id/fs/upload?path=&conflict= raw body, streamed → 201 { path } | 200 { skipped: true }
|
||||
GET /api/lanes/:id/fs/zip?p=<path>&p=… application/zip, streamed
|
||||
```
|
||||
|
||||
The workspace **File Explorer** — the **Files** page in the sidebar (`/files?lane=<id>&path=<folder>`), laid out like GNOME Files (workspaces as places, Back/Forward, a path bar, icon grid or list, hidden files behind Ctrl+H), and the same manager in a dialog from a workspace's Files tab (the folder button; "Open in File Explorer" on a folder). `?session=` / `?source=` pick the same folder the
|
||||
git reads do. Paths go through the read path's guards (`lanePath`, the parent
|
||||
`resolveInLane`-confined, the last segment kept as a name so a symlink is acted
|
||||
on as a link); nothing in `.git`, no secret file, never the workspace folder
|
||||
itself (`400 EBADPATH` / `403 ESECRET`). Writes are same-origin and run as the
|
||||
folder's account (a member's through `write-as`). `conflict` = `fail`
|
||||
(default: `409 EEXISTS` with `error.names`), `skip`, `keep` (a free
|
||||
"name (1).ext") or `replace` (the old one goes to the trash first). Delete
|
||||
moves into the account's trash — a member's `~/.local/share/agenthub/trash/`,
|
||||
the host's `<data dir>/trash/<user id>/` — restorable for 7 days. A copy may
|
||||
come `fromLane` another workspace the caller can see, of the same account
|
||||
(`403 EFOREIGN` otherwise). An upload is at most 500 MB (`413 ETOOBIG`, from
|
||||
`Content-Length` before a byte is stored), a zip at most 2 GB / 50,000 files.
|
||||
Under S3 a move/copy/delete needs the installed `write-as` to know
|
||||
`move`/`copy`: `409 EUPGRADE` until `sudo agenthub server install-system` is
|
||||
re-run.
|
||||
|
||||
#### File Explorer on a home
|
||||
|
||||
```
|
||||
GET /api/homes { homes: [{ id: "me" | <username>, label, readOnly }] }
|
||||
GET /api/homes/:who/fs/list|trash|zip|download as the workspace routes above
|
||||
POST /api/homes/:who/fs/mkdir|newfile|rename|move|copy|delete|upload|trash/restore|trash/purge
|
||||
```
|
||||
|
||||
The same File Explorer on a user's HOME (`server/lib/homes.js`): under S3 their
|
||||
own Linux home, in server mode without S3 their folder under
|
||||
`AGENTHUB_USERS_ROOT`, in local mode (or for an admin with no Linux account)
|
||||
the host account's home. Every dot entry at the TOP of a home (`.ssh`,
|
||||
`.claude`, `.config`, `.agenthub`, `.bashrc`…) is hidden from listings and
|
||||
refused for every read and write — also through a symlink, a download or a
|
||||
zip (`403 ESECRET`); dot files deeper down are ordinary. `:who` is `me`; an
|
||||
admin may also name another user — read-only (`403 EREADONLY` on every
|
||||
write), each read written to the audit log. A copy goes home ↔ workspace
|
||||
(`fromHome` on the workspace route, `fromLane` on the home one) when both are
|
||||
one Linux account; each side is checked under its own rule. A home is never a
|
||||
workspace. The **Files** page opens on `?home=me` by default.
|
||||
|
||||
#### Clone a repository into a new workspace
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user