Files
PaperClipAI/packages/adapter-utils
Priya RamanandPaperclip f1af57bbfc fix(adapter-utils): stop duplex bridge body-read amplification and bound the retained peak
The in-sandbox gateway charged request-body admission on the raw source
bytes, but it retained and forwarded more bytes than it charged. Two
defects allowed a body to exceed the charged bytes.

First, readBody returned a UTF-8 string. The duplex gateway re-encoded
that string to a Buffer. Each invalid byte became the 3-byte U+FFFD
replacement, so a cap-sized malformed body expanded to about 3x the
charged bytes after admission. Preserve the raw request bytes end to end:
readBody now returns the raw Buffer and the duplex gateway uses it
directly. The base64 frames and the envelope byte count now equal the
wire bytes, so a malformed body cannot expand.

Second, the duplex path held the raw body plus the full base64 frame
array plus the full encoded-line array at once, about 3.7x the raw body.
Stream the body as body_chunk frames one at a time, so the gateway holds
the raw body plus at most one frame. The retained peak per read is now
about the charged bytes plus 1 MB, so the inflight bound bounds the true
retained bytes.

The file gateway writes the body as a JSON string that the host reader
consumes. It rejects an invalid-UTF-8 body with a clean 400 before it
converts the body, so it does not amplify a malformed body and keeps the
on-disk payload contract unchanged.

Tests: a cap-sized malformed-UTF-8 body forwards byte-for-byte with no
expanded frame; the admission bound caps concurrent reads at the
retained-byte model; the file gateway answers 400 for an invalid body
and forwards nothing.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-23 18:54:53 +00:00
..

@paperclipai/adapter-utils

Shared utilities for Paperclip adapters: process spawning, environment injection, sandbox/SSH transport, workspace sync, and the round-trip helpers that move code between the local execution-workspace cwd and wherever the agent actually runs.

For the adapter-author guide see docs/adapters/creating-an-adapter.md and the in-repo notes at packages/adapters/AUTHORING.md.

No-remote-git contract

The local execution-workspace cwd is the only persistence boundary across runs. No adapter may depend on a git remote for cross-run state.

Adapters that run the agent on a different host should use the SSH round-trip helpers in src/ssh.ts:

  • prepareWorkspaceForSshExecution({ spec, localDir, remoteDir }) — bundles the local cwd (tracked files, dirty edits, untracked additions, and the git history needed to reconstruct it) to remoteDir before the run starts. Runs with no git remote configured.
  • restoreWorkspaceFromSshExecution({ spec, localDir, remoteDir, ... }) — syncs the remote cwd back into localDir after the run, including any new commits the agent created. Also runs with no git remote configured.

prepareRemoteManagedRuntime in src/remote-managed-runtime.ts wraps both calls for adapters that want a per-run remote workspace and an automatic restoreWorkspace() finally hook.

The invariant is pinned by the no-remote-git contract case in src/ssh-fixture.test.ts, which asserts that a remote-only commit propagates to the local worktree through the prepare → restore round-trip with no git remote configured at any point. Do not regress that test.