feat: add workspace file management for agents#2093
Open
BingqingLyu wants to merge 22 commits into
Open
Conversation
Add RPC methods for agent workspace file operations: - agents.workspace.list: browse directory contents with optional recursion - agents.workspace.get: read file content (utf8/base64) - agents.workspace.set: write file content with optional directory creation - agents.workspace.delete: remove files/directories with optional recursion - agents.workspace.mkdir: create directories - agents.workspace.move: rename/move files with overwrite control - agents.workspace.stat: retrieve file metadata All operations enforce path traversal protection and 10MB file size limits. Includes comprehensive test coverage for security, CRUD, and edge cases. Closes openclaw#61368
Add a Workspace tab to the agents panel with: - File tree browser with directory navigation and breadcrumbs - File editor with textarea for viewing and editing file content - Dirty state tracking with separate edited content state - Save button that only enables when content has changed - File upload, download, delete, and mkdir operations - Responsive layout with toolbar, file tree, and editor panes - Workspace controller with state management helpers - Type definitions for workspace API results
- Fix overwrite=false never being enforced in workspaceMove: the throw was inside the try block and swallowed by the catch. Use a separate destExists flag so the error propagates correctly. - Fix file size limit bypass for multi-byte UTF-8 content: use Buffer.byteLength() instead of string.length to count actual bytes, preventing content with emoji/CJK from exceeding 10MB. - Add comment clarifying that mkdirPathWithinRoot always creates intermediate directories (recursive: true) regardless of the parents flag.
…y uploads - Enforce workspace root boundary in workspaceList and workspaceMove by resolving symlinks via realpath and verifying the resolved path stays within the agent workspace root directory. - Fix stale file content responses: gate the file content update on the current workspaceSelectedFile so a slow response from a previously selected file does not overwrite the editor. - Fix binary file upload corruption: use arrayBuffer() + base64 encoding instead of file.text() so non-text files (images, archives) are preserved correctly through the RPC transport.
- Enforce workspace root boundary in workspaceStat via realpath containment check, preventing symlink-based metadata leaks. - Add 10MB file size guard on workspaceGet reads so large workspace artifacts cannot force the gateway to allocate oversized buffers. - Fix binary file downloads: request base64 encoding and decode to Uint8Array before creating the download blob, preserving original bytes for non-text files.
- Validate agent ID against configured agents list using listAgentIds before resolving workspace path, rejecting unknown agent IDs with an explicit error instead of silently creating fallback workspace directories. - Clear workspaceFileContent immediately when selecting a new file so a failed fetch does not leave stale content from the previous file in the editor.
…tate - Normalize entry paths to POSIX separators in workspaceList so breadcrumbs and navigation work correctly on Windows hosts. - Clear workspaceError on navigation start and on successful list response so transient errors do not persist over fresh results. - Clear editor selection, content, and edited state when the currently open file is deleted, preventing accidental re-creation on save.
- Implement actual recursive directory removal: use fs.rm with recursive flag after verifying the path stays within workspace root, since removePathWithinRoot does not support recursive mode. - Use lstat instead of stat for symlink entries in workspaceList to avoid following symlinks that point outside the workspace, preventing external target metadata from leaking through the listing RPC.
- Walk up to the nearest existing ancestor when validating the move destination path, so symlinked ancestors that point outside the workspace root are caught even when intermediate directories do not exist yet. - Guard workspace navigation list responses against stale delivery by checking the current workspacePath before applying results, preventing a slow response from overwriting a newer navigation.
…pty downloads - Block recursive deletion when the resolved path equals the workspace root, preventing callers from erasing all contents. - Capture the requested agent ID when loading workspace entries on tab switch and ignore stale responses if the user switched agents before the response arrived. - Use result.content != null instead of truthiness check so empty files (zero-byte, empty base64 string) can still be downloaded.
- Use lstat instead of stat in workspaceDelete so symlinks are checked as entries rather than following to their targets, fixing incorrect 'not empty' errors for symlinked directories and ENOENT for dangling symlinks. - Honor parents=false in workspaceMkdir by using non-recursive fs.mkdir with root boundary validation, so callers can detect missing parent directories instead of silently creating them.
Run protocol:gen:swift to update GatewayModels.swift with the new agents.workspace.* request/response types.
Reset workspace state and fetch the new agent's file listing when the user switches agents while already on the Workspace tab, preventing stale content from the previous agent.
Capture the selected file, agent ID, and workspace path at save time and verify they still match before updating editor content or refreshing the file list, preventing stale save responses from overwriting a different file or agent's view.
- Add agent/path staleness guards to the post-delete list refresh callback so late responses do not overwrite a different context. - Replace quadratic per-byte string concatenation in upload base64 encoding with chunked String.fromCharCode to avoid UI stalls on larger files.
- Add agentsSelectedId check to the file-load stale guard so same-named files across different agents cannot cross-pollinate. - Add agent/path staleness guards to the post-mkdir list refresh callback to prevent late responses from overwriting a different workspace context.
- Use lstat instead of access for destination existence checks in workspaceMove so dangling symlinks and permission-denied entries are correctly detected, only treating ENOENT as 'not exists'. - Add agent/path staleness guards to the post-upload list refresh callback to prevent late responses from overwriting a different workspace context.
…hemas The Swift codegen does not emit enum types for string unions, so exporting WorkspaceEntryType as a named schema produced a reference to an undefined type. Remove it from ProtocolSchemas so the codegen inlines the field type instead of generating a broken reference.
- Auto-create workspace directory and return empty listing when the workspace root does not exist yet, instead of failing with ENOENT on first use. - Freeze the upload destination path at the start of the batch so navigating during a multi-file upload does not scatter files across different folders.
Reset workspaceError when the post-save list refresh succeeds so a previous transient error does not persist and hide fresh results.
…g symlinks - Only treat ENOENT as empty listing in workspaceList; propagate EACCES and other errors so real access issues are not hidden. - Use lstat as the authoritative existence check in workspaceStat so dangling symlinks are reported as type symlink instead of throwing 'File not found'. Fall back to lstat metadata when stat fails on broken symlink targets.
Pass maxBytes to readFileWithinRoot so the size check happens before the full buffer is allocated, preventing OOM on large workspace files. The Refresh button already uses a dedicated onRefresh handler that preserves editor state.
This was referenced May 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
agents.workspace.*gateway RPC methods (list, get, set, delete, mkdir, move, stat) and a Workspace tab in the agents panel with file browser, editor, and file operations.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
N/A
Regression Test Plan (if applicable)
src/gateway/server-methods/agents-workspace.test.tsUser-visible / Behavior Changes
Diagram (if applicable)
Security Impact (required)
agents.workspace.*RPC methodsfs-safehelpers with path traversal protection. Relative paths are validated to reject..and absolute paths. File size capped at 10MB. Operations are scoped to the resolved agent workspace directory.Repro + Verification
Environment
--allow-unconfigured --bind lanSteps
openclaw gateway --allow-unconfiguredExpected
Actual
Evidence
Unit tests: 40+ test cases covering security (path traversal), CRUD, edge cases, and error handling all pass.
Human Verification (required)
Review Conversations
Compatibility / Migration
Risks and Mitigations
validateRelativePath()rejecting..and absolute paths, plusfs-safehelpers enforce root directory containment