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:
2026-10-14 14:17:39 +07:00
parent b441f32949
commit e94038481e
38 changed files with 5961 additions and 27 deletions
+23 -6
View File
@@ -16,16 +16,27 @@
# private same, 0600
# mkdir the directory, 0755, parents included
# remove the path, recursively (rm never follows a symlink it removes)
# move the path to the 3rd argument (rename; never onto an existing
# name — checked just before, and the dashboard checks first)
# copy the path, recursively, to the 3rd argument (symlinks copied as
# links, never followed; mode and times kept; never onto an
# existing name)
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
set -eu
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
echo "usage: write-as <absolute path> [append|replace|private|mkdir|remove]" >&2
usage() {
echo "usage: write-as <absolute path> [append|replace|private|mkdir|remove] | <path> move|copy <absolute dest>" >&2
exit 2
fi
case "$1" in
/*) ;;
*) echo "write-as: an absolute path is required" >&2; exit 2 ;;
}
case "${2:-}" in
move|copy) [ $# -eq 3 ] || usage ;;
*) { [ $# -ge 1 ] && [ $# -le 2 ]; } || usage ;;
esac
for p in "$1" "${3:-/}"; do
case "$p" in
/*) ;;
*) echo "write-as: an absolute path is required" >&2; exit 2 ;;
esac
done
swap() {
t=$(mktemp "$1.XXXXXX")
cat >"$t" && chmod "$2" "$t" && mv -f "$t" "$1" || { rm -f "$t"; exit 1; }
@@ -37,5 +48,11 @@ case "${2:-}" in
private) swap "$1" 0600 ;;
mkdir) exec mkdir -p -m 0755 -- "$1" ;;
remove) exec rm -rf -- "$1" ;;
move)
[ -e "$3" ] || [ -L "$3" ] && { echo "write-as: $3 exists" >&2; exit 1; }
exec mv -T -- "$1" "$3" ;;
copy)
[ -e "$3" ] || [ -L "$3" ] && { echo "write-as: $3 exists" >&2; exit 1; }
exec cp -R -P -T --preserve=mode,timestamps -- "$1" "$3" ;;
*) echo "write-as: unknown mode: $2" >&2; exit 2 ;;
esac