- 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>
- Users page: one Edit user form (display name, email, role, active, a new or
generated password, workspaces given or taken back, sign out everywhere),
validated as a whole before anything is written (PATCH /api/users/:id,
GET /api/users/:id); users.display_name/email added migration-safe.
- Settings → Account for every signed-in user: profile, own password (the
current one required, this login kept), sign out other devices
(/api/me/account, /api/me/password, /api/me/sign-out-others; login cookie
only, never a pane token).
- Display names wherever a person is named (sidebar, workspace owner groups,
Users page and audit log); owner_display_name / reader_display additive.
- Users page: admins add and remove Claude/Codex pool accounts.
- Folder browser: an admin with their own Linux account opens in their home;
the "Server folder" tick opens the host's (never remembered).
- AI Proxy: "Config 9router" beside Add profile when 9router is installed,
opened on the dashboard's own host (GET /api/ai-proxies/9router).
- Terminal: a tmux selection reaches the clipboard over plain HTTP too
(writeClipboard's textarea fallback, focus handed back).
- Chat: a file the session touched outside the workspace previews
(GET /api/sessions/:id/file).
- install-system: `!requiretty` only when visudo accepts it (sudo-rs on
Ubuntu 25.10+ rejects the setting).
- openapi.yaml regenerated; docs (API, SERVER-MODE, LANES) updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.7.3's ENOCWD left a new member no web path to a first project: their
home is no workspace (EFOLDER) and a not-yet-existing folder could not
start. run-as.makeMemberFolder: mkdir -p through write-as, AS the member,
only inside their own home (never the home itself, never a host folder,
never for a member with no account). Test: server-s3-lanes.
- self-restart: a server in our unit's cgroup (/agenthub.service, system
or user) restarts via `systemctl restart --no-block` (sudo -n for the
system unit), our launchd job by exiting into KeepAlive. The detached
restart-helper replacement held the port while Restart=always started
the unit every 3 s (v2.7.2 on the S3 box: 240+ restarts, MainPID 0).
- run-as.mayHaveTmux: only ENOENT means "no tmux". A dev server in server
mode without CAP_DAC_READ_SEARCH got EACCES on a member's 0700
.agenthub/tmux, dropped the member from the list and ended their runs
within seconds — the real cause of `401 not a live pane's token`.
- tmux: a member's list is re-asked every 5 s, not 1 s (one sudo + three
auth.log lines each).
- POST /api/lanes/:id/start: 409 ENOCWD for a folder that no longer
exists (the pane started in the account's home otherwise).
- Settings: Remote sources only for staff (a developer got a 403).
- scripts/test-server.js drops every inherited AGENTHUB_* (pane env).
Tests: self-restart.test.js, server-s3-panes (mayHaveTmux), lanes-api
(ENOCWD); touched suites 771 + client pages 191, tsc -b.
Server mode can now run each account as its own Linux user (ah-<name>)
when `agenthub server install-system` has set S3 up. The dashboard
reaches a member only through sudo -u / write-as / pane-launch, reads a
member's files only through owner- and type-checked opens, and hooks
are authenticated by peer uid. Local mode and server mode without S3
are unchanged.
EXPERIMENTAL: tested with fake sudo and fake accounts only. It has not
yet been verified on a real install with real member accounts, sudo or
a real Codex login. Do not offer it to real users yet.
- S3 core: provisioning, per-member tmux servers, the proxy key
gateway, shared tools in /opt, dropped capabilities for children
(P0–P5)
- P4: plan and quota per member home
- P6: ~/.claude.json seed; opt-in plugin baseline installed as the
member; managed-settings spike script (not run)
- P7: Codex account pool (`agenthub accounts add --tool codex`)
- Roles merged with main's admin / analyst / developer: analysts write
only their own rows, under S3 as well
- Per-account prefs: one implementation (Chat UI + onboarding)
- Security reviews F1–F49 (five rounds), each fix with a regression
test: symlink/TOCTOU-safe member reads, no host writes in a member
home, credentials and the data dir never served, owner and folder
account must agree, pane tokens bound to the pane's uid, bounded
sudo/tmux/git calls and member reads (no hang or crash from a member),
async route rejections answer 500 instead of exiting
- Docs: docs/SERVER-MODE.md (S3 model, accepted ceilings, operator
runbook), docs/plans/server-mode/S3-PLAN.md, CLAUDE.md S3 rules
Tests: server 2838/2841 (3 need 127.0.0.1:3000, held on this box);
client 1162/1162; tsc clean; headers check passes.
Co-Authored-By: Claude <noreply@anthropic.com>