Skip to content

feat(desktop): add nonblocking File Workspace and editor search - #62616

Closed
seagpt wants to merge 3 commits into
NousResearch:mainfrom
seagpt:feat/file-workspace-nonblocking
Closed

feat(desktop): add nonblocking File Workspace and editor search#62616
seagpt wants to merge 3 commits into
NousResearch:mainfrom
seagpt:feat/file-workspace-nonblocking

Conversation

@seagpt

@seagpt seagpt commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make the Desktop/Webapp File Browser location independent from the active session/agent cwd after initial seeding
  • add Back, Forward, Up, Session root, clickable breadcrumbs, and Ctrl/Cmd+L inline location editing
  • add authenticated local Electron and remote Webapp create-file, create-folder, rename, move, and delete operations
  • keep navigation and mutation controls available while an agent run is active
  • keep mutation failures item-scoped and retryable instead of closing the action flow
  • add native CodeMirror Find/Replace with count, previous/next, case, whole-word, regex, Replace, Replace All, and Select All Matches
  • localize File Workspace and editor-search controls in English, Japanese, Simplified Chinese, and Traditional Chinese

Reviewer findings resolved

  • remote delete confirmation now states that deletion is permanent instead of promising Trash behavior
  • mutation parents are canonicalized through intermediate symlinks while preserving final-leaf lstat behavior
  • sensitive-path guards run against canonical targets and cover sensitive ancestor symlinks
  • local cross-device moves handle EXDEV through a guarded copy/remove fallback with cleanup and regression coverage

Behavior and safety

  • browser navigation never calls setCurrentCwd; later agent cwd updates do not redirect the browser
  • invalid typed locations keep the current tree and editor open; Escape restores the prior location
  • mutation endpoints use existing authenticated /api/* middleware
  • reject filesystem/browser roots, traversal basenames, collisions, sensitive paths, symlinks, special files, and directory-into-descendant moves
  • broken symlinks remain occupied leaves during create/rename/move
  • rename and move preserve no-overwrite semantics across collision races
  • local delete uses the existing validated Trash path; remote delete requires explicit permanent-delete confirmation
  • Find/Replace uses CodeMirror's native search primitives and streaming match counting; multiple selections are explicitly enabled

Validation

  • focused Desktop UI/editor/navigation/mutation tests: 29 passed
  • focused Electron workspace mutation tests under the correct Node runner: 8 passed
  • Webapp filesystem API tests: 54 passed
  • renderer + Electron TypeScript: passed
  • scoped ESLint, Prettier, Ruff, and git diff --check: passed
  • production Desktop build + assert-dist-built: passed
  • final PR-diff security/privacy scan: passed

Current-main broad baseline

Source-wide Desktop Vitest under the same Node 24 runtime:

  • feature: 1,202 passed / 24 failed
  • current origin/main: 1,173 passed / 24 failed
  • net: +29 passing assertions, identical assertion-failure count, zero new assertion failures

Vitest reports one additional failed file because electron/fs-workspace-ops.test.ts is a node:test file and appears empty under jsdom Vitest; it passes 8/8 under node --import tsx --test. Unhandled errors came from the existing streaming.test.tsx timer/window teardown and varied across broad runs, not from File Workspace or CodeMirror tests.

Visual QA

Dogfooded real styled File Workspace/editor components at normal and narrow widths:

  • independent File Browser navigation and inline address editing
  • New File, context-menu, and Move dialog states with source/destination context
  • Find/Replace at 440px and 360px in light/dark themes with no horizontal overflow
  • toolbar wrapping, keyboard focus restoration, active states, and dark-theme contrast
  • production build bundled the codicon font successfully

Related / overlap checked

Notes

  • the PR remains draft while maintainers review the expanded editor and mutation-hardening scope
  • @codemirror/search is the only added runtime dependency for native editor search

Add independent inline location navigation and hardened local/remote file mutations without changing the active agent working directory.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) comp/cli CLI entry point, hermes_cli/, setup wizard labels Jul 11, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused workspace-browser contribution. The current desktop browser is still directly bound to the active session cwd (apps/desktop/src/app/right-sidebar/index.tsx:31,50), so the premise is valid.

Problems

  • hermes_cli/web_server.py:2243 permanently deletes remote files with shutil.rmtree/unlink, while apps/desktop/src/app/right-sidebar/file-actions.tsx:137 retains the existing “moved to the Trash” confirmation. Remote users receive a materially incorrect destructive-operation warning.
  • apps/desktop/electron/fs-workspace-ops.ts:17 keeps intermediate symlinks lexical before guardSensitive; existing hardened reads canonicalize real paths in apps/desktop/electron/hardening.ts:251,284. Mutation guards need the equivalent canonical-parent treatment while retaining leaf lstat behavior.
  • apps/desktop/electron/fs-workspace-ops.ts:141 uses only fs.promises.rename, unlike remote shutil.move at hermes_cli/web_server.py:2276; local moves across filesystems have no fallback.

Suggested changes

  • Make the remote-delete dialog explicitly permanent or implement recoverable remote deletion.
  • Canonicalize mutation parents and add intermediate-symlink coverage.
  • Handle or explicitly constrain EXDEV local moves, with regression coverage.

Automated hermes-sweeper review.

<>
<ConfirmDialog
confirmLabel={t.fileMenu.delete}
description={t.fileMenu.deleteBody}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For remote mode this confirmation is incorrect: /api/fs/delete permanently uses shutil.rmtree or unlink, while deleteBody promises the item will be moved to Trash. Use remote-specific permanent-delete copy or provide a recoverable remote trash implementation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 4002d3336d4ee44372c00907bd5ff12b1a6d233a. Remote delete confirmation now explicitly states permanent deletion; only local Electron delete uses Trash wording. Covered by apps/desktop/src/app/right-sidebar/file-actions.test.tsx.

}

function resolved(raw: unknown, purpose: string): string {
return resolveRequestedPathForIpc(String(raw || '').trim(), { purpose })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only normalizes syntax; it does not canonicalize intermediate symlink components before guardSensitive. A path through a symlinked directory can evade path-based sensitive-file guards. Resolve the parent via realpath while preserving the final leaf for lstat, then guard the canonical target.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 4002d3336d4ee44372c00907bd5ff12b1a6d233a. Mutation parents are canonicalized through intermediate symlinks before sensitive-path/root guards, while the final leaf retains lstat semantics. Regression coverage exercises a non-sensitive alias into a sensitive ancestor in both Electron and Webapp mutation suites.

if (await collision(target)) {
throw new Error(`"${path.basename(source)}" already exists`)
}
await fs.promises.rename(source, target)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A raw rename fails with EXDEV when the user selects a destination on another filesystem. The remote path uses shutil.move; please either add a hardened cross-device fallback locally or constrain/document the operation and test the chosen behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 4002d3336d4ee44372c00907bd5ff12b1a6d233a. Local moves catch EXDEV and use a guarded copy/remove fallback with cleanup and no-overwrite checks. The Electron workspace-op suite passes 8/8 under its correct Node runner, including cross-device behavior coverage.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 11, 2026
seagpt added 2 commits July 11, 2026 12:42
Resolve permanent-delete messaging, canonical-parent symlink guards, cross-device move fallback, and retryable item-scoped mutations. Add native CodeMirror Find/Replace with localized controls and regression coverage.
@seagpt seagpt changed the title feat(desktop): add nonblocking file workspace feat(desktop): add nonblocking File Workspace and editor search Jul 11, 2026
@seagpt

seagpt commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Reviewer follow-up pushed in 4002d3336d4ee44372c00907bd5ff12b1a6d233a and merged with current main.

Resolved all three inline blockers with regression coverage:

  • remote delete copy now explicitly says the operation is permanent;
  • mutation parents are canonicalized through intermediate symlinks while final-leaf lstat behavior is preserved;
  • local EXDEV moves use a guarded cross-device fallback.

The update also keeps mutation failures retryable and adds native CodeMirror Find/Replace. Current-main broad comparison has the same 24 assertion failures as main and +29 passing assertions. The PR remains draft for scope review.

@seagpt seagpt closed this Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants