Skip to content

feat(sandbox): unify worker tool execution with tools.ts via runner injection#83

Closed
kirang89 wants to merge 5 commits intomainfrom
feat/sandbox-runner-injection
Closed

feat(sandbox): unify worker tool execution with tools.ts via runner injection#83
kirang89 wants to merge 5 commits intomainfrom
feat/sandbox-runner-injection

Conversation

@kirang89
Copy link
Copy Markdown
Contributor

@kirang89 kirang89 commented Apr 2, 2026

Summary

Replaces the sandbox worker's duplicated tool logic with a single call to the shared executeTool from src/tools.ts, using a CommandRunner injection to wrap commands with bwrap + seccomp isolation.

This is Phase 2 of the unified tool execution plan. Phase 1 (#77, merged in #80) added fallback command support and switched all tools to use runCommand.

Changes

src/tools.ts:

  • Export CommandRunner type: (cmd: string[], cwd: string) => Promise<string>
  • executeTool accepts optional runner param (default: Bun.spawn-based runCommand)
  • Threads runner through all execute* functions
  • Export ALLOWED_GIT_COMMANDS constant (same set as TypeBox schema literals)

src/sandbox/worker.ts:

  • makeSandboxRunner(worktree) — creates a CommandRunner that wraps commands with runToolIsolated (bwrap + seccomp)
  • handleTool: non-git tools → executeTool(name, args, worktree, sandboxRunner)
  • handleGitTool: extracted git-specific handler using shared ALLOWED_GIT_COMMANDS and isolatedGitToolCommand (different bwrap topology — needs bare repo mount)
  • Deleted: all duplicated rg, find, ls, read switch cases, validatePath function

src/sandbox/Containerfile:

  • Install fd (apk add fd)
  • Copy tools.ts into container at /app/tools.ts
  • Install runtime deps (@sinclair/typebox, @mariozechner/pi-ai) via bun install --production

docker-compose.yml:

  • Build context changed from ./src/sandbox to . (project root) so Containerfile can access src/tools.ts

What's fixed

The sandbox worker now automatically gets:

  • All rg parameters (max_count, max_results, word) — previously ignored
  • fd instead of find — with full parameter support
  • read with [File: path] header and path validation — previously used raw cat -n
  • rggrep and fdfind fallback from Phase 1
  • Any future tool changes in tools.ts — no more dual maintenance

Test Coverage

18 new tests (300 total, 12 files):

  • CommandRunner injection: mock runner verifies correct command arrays for all 5 tools
  • Path validation runs before runner (runner never called for invalid paths)
  • ALLOWED_GIT_COMMANDS matches TypeBox schema literals
  • Worker structure: no duplicated switch cases, uses shared imports, git uses separate isolation

Why git stays special

Git commands in the sandbox need isolatedGitToolCommand which mounts both the worktree and bare repo read-only — a different bwrap topology from other tools. The sandbox runner only mounts the worktree.

Closes #78

kirang89 added 5 commits April 2, 2026 17:26
…njection

- Add CommandRunner type and optional runner param to executeTool,
  defaulting to Bun.spawn-based runCommand for backward compatibility
- Export ALLOWED_GIT_COMMANDS constant from tools.ts, derived from the
  same set as the TypeBox schema
- Replace worker's duplicated rg/find/ls/read switch cases with single
  executeTool(name, args, worktree, sandboxRunner) call
- Add makeSandboxRunner() that wraps commands with bwrap + seccomp
- Keep git as special case (different bwrap topology for bare repo mount)
- Remove duplicated validatePath from worker (handled by tools.ts)
- Update Containerfile: install fd, copy tools.ts, install runtime deps
- Change docker-compose build context to project root for tools.ts access
- 18 new tests: runner injection (mock runner for each tool, path
  validation before runner), worker parity, ALLOWED_GIT_COMMANDS

Closes #78
- Replace string-prefix error dispatch with structured CommandResult
  type (exitCode + output). CommandRunner now returns CommandResult
  instead of string. Worker checks tool names against TOOL_NAMES set
  instead of parsing output prefixes.
- Derive TypeBox git schema from ALLOWED_GIT_COMMANDS_LIST constant
  (single source of truth, was duplicated before)
- Add git subcommand allowlist validation in executeGit (was only
  enforced in sandbox worker, not in local mode)
- Replace source-text-based worker tests with behavioral tests
- Add .dockerignore to exclude node_modules/test/.git from build context
- Reorder Containerfile layers: deps install before source copy for
  better Docker layer caching
…ror HTTP responses

makeSandboxRunner now returns a SandboxRunner with a lastExitCode
field. handleTool checks this after executeTool returns:
- exitCode !== 0 → { ok: false, error, status: 500 }
- exitCode === 0 → { ok: true, output }

This restores the old behavior where tool failures returned HTTP 500
with ok:false, which the SandboxClient uses for warning logs.
Path validation errors (which don't go through the runner) return
ok:true with the error in output — correct since they're not server
errors, just invalid input the LLM should see and retry.
@kirang89 kirang89 closed this Apr 6, 2026
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.

Unify sandbox worker tool execution with tools.ts via runner injection

1 participant