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
|
||||
|
||||
```
|
||||
|
||||
@@ -713,6 +713,20 @@ Every command below runs as the **host account** (the one that runs the
|
||||
dashboard) unless it says `sudo`. `agenthub server install-system` calls sudo
|
||||
itself, once.
|
||||
|
||||
### After an update that changes the member helpers
|
||||
|
||||
`write-as`, `pane-launch` and the sudoers file are copied to
|
||||
`/opt/agenthub/libexec` by `install-system` only — an update of AgentHub does
|
||||
not touch root-owned files. When a release changes them (v2.7.7: `write-as`
|
||||
learned `move`/`copy` for the File Explorer), re-run it once:
|
||||
|
||||
```bash
|
||||
sudo agenthub server install-system
|
||||
```
|
||||
|
||||
Until then the File Explorer's move, copy and delete in a member's folder
|
||||
answer `409 EUPGRADE` (listing, upload, new folder/file and zip work).
|
||||
|
||||
### What a new machine needs before `install-system`
|
||||
|
||||
`install-system` does not do any of this:
|
||||
@@ -955,6 +969,8 @@ To remove everything:
|
||||
| `409 ENOTSHARED` on a member's start | That tool is not published to `/opt/agenthub/tools` yet: `agenthub server tools`. |
|
||||
| `403 EMEMBERLANE` | Worktree mode and the runtime layer are not offered in a member's folder; use Repo mode and the prompt box's New worktree. |
|
||||
| `400 EMEMBERCRED` | Older builds only: a member's typed git login was refused. Now it goes into their own store (Settings → Git → **Your git identity**). |
|
||||
| `409 EOWNERFOLDER` (on a write to an existing workspace) | Its owner may no longer own that folder by their CURRENT role — typically an admin demoted to developer whose workspace is outside the users root (it runs as the host). Reads still work; an admin reassigns the workspace or restores the role. |
|
||||
| `403 EGATEWAY` | A request relayed by the AI Proxy gateway reached the dashboard itself (a proxy profile whose base URL points back at AgentHub). Point the profile at the real proxy. |
|
||||
| `409 ESTOREUNREADABLE` | The dashboard cannot read the member's `~/.config/agenthub/git-credentials` (the unit lost `CAP_DAC_READ_SEARCH`, or the file is not the member's own regular file); nothing is rewritten. Check `grep CapAmb /proc/<pid>/status` shows `0000000000000004`. |
|
||||
| `403 EPEER` on a hook | The hook came from an account that is neither the host nor an enabled member, or the uid could not be read. |
|
||||
| `403 EFOREIGN` on a hook | A member's hook named a folder, session or pane outside their own home. |
|
||||
|
||||
Reference in New Issue
Block a user