Skip to content

feat(desktop): browse remote backend files - #43434

Closed
yoniebans wants to merge 5 commits into
mainfrom
feat/desktop-remote-filesystem
Closed

feat(desktop): browse remote backend files#43434
yoniebans wants to merge 5 commits into
mainfrom
feat/desktop-remote-filesystem

Conversation

@yoniebans

@yoniebans yoniebans commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Desktop remote sessions already carry a backend cwd, but the Files panel still read through Electron's local filesystem IPC. Backend paths were therefore resolved on the client machine, so remote-backed Desktop sessions could show unreadable folders and could not preview backend-only files.

This routes the Files surface by connection mode:

  • Local Desktop keeps the existing Electron filesystem IPC.
  • Remote Desktop uses authenticated backend REST through window.hermesDesktop.api, matching the existing Desktop remote connection model.

The backend slice is read-only: directory listing, capped text previews, capped data URLs, git-root detection, and backend default cwd under /api/fs/*. It does not add terminal access, PTYs, SSH/SFTP, uploads, writes, deletes, mkdir, rename, chmod, or sync.

Also in here

  • Backend-scoped remote cwd memory — remote remembered cwd is keyed by connection identity, so client-local cwd/default-project paths do not seed a remote backend. First-time remote mode asks the backend for its default cwd; existing sessions keep their stored/runtime cwd.
  • Remote folder picker — remote mode uses a small in-app directory picker for changing cwd instead of opening the client machine's native folder dialog.
  • Remote .gitignore + git-root support — existing filtering behavior is preserved, with caches keyed by connection identity so the same path on two backends does not share stale rules.
  • Remote preview reads — text and image previews route through the filesystem facade; image previews still prefer an existing dataUrl when one was already provided by the caller.
  • No local watch noise for remote files — backend-only file previews skip Electron's local file watcher.
  • Directory-picker guardrail — the remote picker only handles single directory selection; non-directory or multi-select requests return empty rather than pretending remote file picking exists.

Fixes #42878.

Test plan

  • scripts/run_tests.sh tests/hermes_cli/test_web_server_fs.py — 13 passed
  • cd apps/desktop && npm run test:ui -- src/store/session.test.ts src/lib/desktop-fs.test.ts src/app/right-sidebar/files/use-project-tree.test.ts src/lib/preview-targets.test.ts src/app/chat/right-rail/preview-pane.test.tsx — 39 passed
  • cd apps/desktop && npm run type-check — clean
  • git diff --check — clean
  • Manual remote Linux backend check: remote Files can traverse the backend filesystem and no longer starts on a client-local cwd.
  • Manual remote macOS backend check: /api/fs/* auth, list, default-cwd, and text preview work from a remote Desktop client.
  • Local-mode guardrails checked during testing: terminal remains local/out of scope for this PR; Files local mode remains separate from remote cwd memory.
  • Manual Windows remote backend check: not tested yet; should not block this read-only Files PR.

Follow-up notes

The integrated terminal is intentionally unchanged here. Remote terminal behavior belongs to the separate remote terminal work; this PR only makes the Files browser/preview use the active backend filesystem in remote mode.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: feat/desktop-remote-filesystem vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10703 on HEAD, 10701 on base (🆕 +2)

🆕 New issues (2):

Rule Count
unresolved-import 2
First entries
tests/hermes_cli/test_web_server_fs.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `starlette.testclient`
tests/hermes_cli/test_web_server_fs.py:4: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues: none

Unchanged: 5598 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have labels Jun 10, 2026
@austinpickett

Copy link
Copy Markdown
Collaborator

Reviewed by Hermes Agent — deferred to maintainer for product sign-off. Feature PRs (type/feature) need a product/design decision on whether this capability is wanted before merge review proceeds. Please await maintainer feedback.

Hermes Agent triage

@OutThisLife

Copy link
Copy Markdown
Collaborator

Reviewed the code. The fix is real and well scoped. #42878 is a genuine bug: the Files panel read backend cwd paths through Electron's local filesystem IPC, so remote sessions resolved server paths on the client machine (ENOENT). Routing the Files surface by connection mode fixes it, and the connection-keyed cwd/cache scoping is the part that makes it hold up across different backends.

Backend tests pass (13/13). Whitespace is clean, i18n landed in all four locales, and the /api/fs/* response shapes match the desktop Hermes*Result types.

One design decision worth calling out explicitly

/api/fs/* reads any path the backend process can access, with no root sandboxing. The sibling /api/media endpoint does the opposite, restricting reads to _media_serve_roots() with this rationale:

This stops an authenticated client from reading image-extension files anywhere on disk (e.g. a renamed key or a screenshot outside the cache) merely because the suffix passes the allowlist.

So the codebase already takes a defense-in-depth stance for authenticated clients, and these endpoints don't follow it.

This is probably fine anyway: the same server already exposes /api/pty to authenticated clients, which is a full hermes --tui shell (arbitrary read/write/exec). A read-only file API doesn't widen that boundary. Auth is correct here too. The routes live under /api/, aren't in PUBLIC_API_PATHS, and test_fs_endpoints_require_auth proves they 401 without a token.

It still deserves a conscious decision because the endpoints are registered unconditionally, so the web dashboard gets them as well, not just desktop-remote. If you'd rather scope the browser to the workspace subtree (or reuse the media-roots model), now is the cheap time to decide. My lean is that it's acceptable given the terminal precedent, but it deserves one sentence of explicit sign-off.

Minor, non-blocking

  • A few remote FS calls stay local-only and will misbehave in remote mode, all in chat-media paths rather than the Files panel: markdown-text.tsx:66, directive-text.tsx:347, use-prompt-actions.ts:103, use-composer-actions.ts:263,327,373. Out of this PR's declared scope (chat media has its own /api/media route), so not a regression, but a good follow-up.
  • preview-file.tsx:196 back-compat fallback is unreachable in remote mode. It only triggers on the literal "No handler registered for 'hermes:readFileText'" message, which /api/fs/read-text never emits. Harmless.
  • Local-mode robustness: readDesktopFileText/readDesktopDir call desktop.readFileText(path) without the old if (window.hermesDesktop.readFileText) guard. A stale Electron preload missing that method would throw a TypeError instead of falling through to the renderer-side path. Edge case only.
  • _fs_regular_file resolves the path twice: the caller passes _fs_path(path) and the function calls _fs_path(str(path)) again. Idempotent, just redundant.
  • fs_list has no cap on entry count, so a very large directory returns everything. The read-text/data-url byte caps are good; a directory cap would match.
  • Symlinked dirs list as isDirectory: false, and the remote picker assumes POSIX separators. Both are fine for the Linux/macOS backends this targets, and Windows-remote is already noted as untested.

Tests

Behavior and invariant style, not snapshots. Coverage is solid: sorting plus noise-hiding, relative paths, structured ENOENT, truncation, source-cap 413, binary detection, data-url cap, git-root (nested and outside), default-cwd preference and fallback, and auth. I didn't run the 39 desktop vitest specs (no node_modules in my review worktree), but the types line up on inspection and the author reports green.

@alt-glitch alt-glitch added comp/desktop Electron desktop app (apps/desktop/*) comp/dashboard Web dashboard / control panel UI (dashboard/, landing) and removed comp/cli CLI entry point, hermes_cli/, setup wizard labels Jun 26, 2026
@yoniebans yoniebans closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop file browser shows 'unreadable' (ENOENT) when connected to remote gateway — reads server cwd on local filesystem

4 participants