Skip to content

Add runtime file system related methods#31

Merged
veithly merged 1 commit intomasterfrom
feat/fs_methods
Mar 11, 2026
Merged

Add runtime file system related methods#31
veithly merged 1 commit intomasterfrom
feat/fs_methods

Conversation

@joeqian10
Copy link
Contributor

Summary

Implement sandbox filesystem RPC support in the runtime and route file watching through spoon-bot instead of relying on the API server's local filesystem view.

Changes

  • add runtime support for fs.list, fs.stat, fs.read, fs.write, fs.mkdir, fs.rename, and fs.remove
  • add websocket protocol enums for the new fs.* methods and sandbox.file.changed
  • add WorkspaceFSService to execute filesystem operations inside the runtime workspace with /workspace path semantics
  • add runtime-backed fs.watch / fs.unwatch handling in the websocket handler
  • normalize watch event paths to /workspace/...
  • fix the initial watch snapshot race so newly created files emit created events reliably
  • ensure watch resources are cleaned up when the websocket connection closes
  • add service-level tests for workspace fs operations and watch behavior

Why

The previous fs.watch flow depended on the API server being able to see a mirrored host-side workspace, which breaks in local and sandboxed environments. This PR moves filesystem ownership to the runtime, aligns behavior with the documented sandbox protocol, and fills in the missing fs.* methods the frontend already expects.

Validation

  • python -m py_compile spoon_bot/gateway/websocket/protocol.py spoon_bot/gateway/websocket/workspace_fs.py spoon_bot/gateway/websocket/workspace_watch.py spoon_bot/gateway/websocket/handler.py tests/test_workspace_fs_service.py tests/test_workspace_watch_service.py
  • manual runtime check via uv run python - covering mkdir, write, read, list, rename, remove, and fs.watch

Copy link
Contributor

@veithly veithly left a comment

Choose a reason for hiding this comment

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

Code Review: PR #31 — Add runtime file system related methods

Test Results: 59/59 PASSED

Ran comprehensive E2E test suite (54 additional tests beyond the 5 included in the PR) in an isolated worktree checked out from origin/feat/fs_methods at eaec29e.

Test coverage areas:

  • Core FS operations (9 tests): Full lifecycle write→read→stat→remove, mkdir recursive, list pagination, read with offset/limit, write append mode, rename with/without overwrite, remove recursive
  • Path resolution (4 tests): Relative paths, workspace root, empty path, /workspace prefix
  • Security boundaries (10 tests): /etc/passwd, /etc/shadow, /tmp write, path traversal via ../../, symlink escape (both stat and read), rename source/dest outside workspace
  • Error handling (13 tests): Nonexistent files, directory-as-file, invalid/unknown encoding, negative offset, zero limit, mkdir on file, rename to non-empty dir
  • Edge cases (8 tests): Unicode filenames/content, empty dirs/files, 100KB files, filenames with spaces, sort order, mtime format, mkdir idempotency
  • Watch service (6 tests): File creation/modification/deletion events, cleanup on close, nonexistent/outside-workspace path rejection, unknown watch_id handling
  • Protocol & integration (4 tests): ClientMethod enum completeness, ServerEvent.SANDBOX_FILE_CHANGED, handler wiring, py_compile validation

Code Quality Assessment: Good

Strengths:

  • Clean separation: WorkspaceFSService (sync ops via asyncio.to_thread) and WorkspaceWatchService (async polling) are well-isolated
  • Solid path security: resolve(strict=False) + relative_to() double-check prevents traversal attacks; symlinks that escape workspace are correctly blocked
  • WebSocket lifecycle: Watch resources are properly cleaned up on connection disconnect via _cleanup_resources()
  • Comprehensive RPC coverage: All 9 fs.* methods + fs.watch/fs.unwatch are wired into the handler

Minor Suggestions (non-blocking)

  1. DRY path resolutionworkspace_fs.py:279-299 and workspace_watch.py:73-100 contain near-identical path resolution logic. Consider extracting a shared utility.

  2. Duplicate constantSANDBOX_WORKSPACE_ROOT = "/workspace" is defined in both workspace_fs.py:12 and workspace_watch.py:16. Unify to a single source.

  3. Watch error notificationworkspace_watch.py:118-119: when _watch_loop catches a non-cancellation exception, it only logs a warning. Consider emitting an error event to the WebSocket client so the frontend can react.

  4. Workspace path fallbackhandler.py:246: getattr(agent, "workspace", Path.home() / ".spoon-bot" / "workspace") silently falls back to a home directory path. A log or validation check would help catch misconfiguration.

Verdict

Approve — well-structured implementation with strong security boundaries. The suggestions above are all S3 (nice-to-have) improvements that can be addressed in follow-up PRs.

@veithly veithly merged commit d53414e into master Mar 11, 2026
1 of 2 checks passed
@joeqian10 joeqian10 deleted the feat/fs_methods branch March 12, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants