Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,21 @@ SRT with:
- bounded time and aggregate output, with best-effort process-group termination
on cancellation, timeout, and completion.

Native command output keeps a prefix and rolling suffix for each stream, so late
summaries and errors survive truncation. Each stream stores at most
`maxOutputBytes` of copied raw bytes while the command runs, independent of output
volume or chunk count. When both streams are noisy they split the existing combined
response budget equally, with the odd byte reserved for stderr; a quiet stream gives
its unused allowance to the other based on rendered UTF-8 bytes, including replacement
characters for malformed input. Sandbox violation annotations enter the same
stderr window before rendering. UTF-8 boundaries and inline
`[... N bytes omitted ...]` markers count toward the combined byte limit. The count
reports omitted raw bytes for that stream, including annotation bytes. If a stream's
allowance cannot fit its marker, only the retained text and the existing `truncated`
flag are returned. Truncation does not stop execution. Exit codes, timeout/signal
fields, and cancellation errors keep their existing semantics, and no new request,
result, or capability keys are introduced.

The Git-metadata denies are applied to the registered root directly rather
than relying on SRT's Linux mandatory denies, which are derived from the
worker process's own current directory — the worker home, not the workspace —
Expand Down
142 changes: 140 additions & 2 deletions packages/code/src/native-sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from './native-sandbox.js';
import { restoreScratchTraversal } from './native-scratch.js';
import { WorkspaceToolError } from './workspace.js';
import { isWorkspaceToolResult } from './protocol.js';

const request = {
protocolVersion: 1 as const,
Expand Down Expand Up @@ -1355,14 +1356,151 @@ test('executes in the canonical workspace and bounds aggregate output', async t
operation: 'execute_command',
workspaceId: 'primary',
exitCode: 0,
stdout: '1234567890',
stderr: 'ab',
stdout: '123890',
stderr: 'abchij',
truncated: true,
timedOut: false,
},
);
});

test('retains real command summaries on both streams under the legacy combined budget', async t => {
const root = await mkdtemp(join(tmpdir(), 'librechat-code-native-'));
t.after(() => rm(root, { recursive: true, force: true }));
const sandbox = new NativeSrtWorkspaceCommandSandbox({
workspaceRoot: root,
manager: fakeManager().manager,
});
t.after(() => sandbox.close());
const commandRequest = {
...request,
maxOutputBytes: 256,
command:
"printf 'OUT\\n'; printf '%20000d' 0; printf '\\n42 tests passed\\n'; printf 'ERR\\n' >&2; printf '%20000d' 0 >&2; printf '\\nlate stderr summary\\n' >&2",
};
const result = await sandbox.execute(commandRequest);
assert.equal(result.exitCode, 0);
assert.equal(result.timedOut, false);
assert.equal(result.truncated, true);
assert.ok(result.stdout.startsWith('OUT\n'));
assert.ok(result.stderr.startsWith('ERR\n'));
assert.ok(result.stdout.endsWith('\n42 tests passed\n'));
assert.ok(result.stderr.endsWith('\nlate stderr summary\n'));
assert.ok(result.stdout.includes('bytes omitted'));
assert.ok(result.stderr.includes('bytes omitted'));
assert.equal(isWorkspaceToolResult(commandRequest, result), true);
});

test('partly filled command buffers retain final summaries regardless of stream order', async t => {
const root = await mkdtemp(join(tmpdir(), 'librechat-code-native-'));
t.after(() => rm(root, { recursive: true, force: true }));
const sandbox = new NativeSrtWorkspaceCommandSandbox({
workspaceRoot: root,
manager: fakeManager().manager,
});
t.after(() => sandbox.close());
const summary = 'late stderr summary\n';
const quiet = `printf '%109d' 0; printf 'late stderr summary\\n'`;
const noisy = `printf '%20000d' 0`;
for (const [command, stream] of [
[`{ ${quiet}; } >&2; ${noisy}`, 'stderr'],
[`${noisy}; { ${quiet}; } >&2`, 'stderr'],
[`{ ${quiet}; }; ${noisy} >&2`, 'stdout'],
[`${noisy} >&2; { ${quiet}; }`, 'stdout'],
] as const) {
const commandRequest = { ...request, command, maxOutputBytes: 256 };
const result = await sandbox.execute(commandRequest);
assert.ok(result[stream].endsWith(summary));
assert.equal(result.truncated, true);
assert.equal(result.exitCode, 0);
assert.equal(isWorkspaceToolResult(commandRequest, result), true);
}
});

test('quiet malformed command output is retained beside a noisy stream', async t => {
const root = await mkdtemp(join(tmpdir(), 'librechat-code-native-'));
t.after(() => rm(root, { recursive: true, force: true }));
const sandbox = new NativeSrtWorkspaceCommandSandbox({
workspaceRoot: root,
manager: fakeManager().manager,
});
t.after(() => sandbox.close());
for (const [command, stream] of [
["printf '%20000d' 0; printf '\\377\\377' >&2", 'stderr'],
["printf '\\377\\377'; printf '%20000d' 0 >&2", 'stdout'],
] as const) {
const commandRequest = { ...request, command, maxOutputBytes: 32 };
const result = await sandbox.execute(commandRequest);
assert.equal(result[stream], '\ufffd\ufffd');
assert.equal(result.truncated, true);
assert.equal(result.exitCode, 0);
assert.equal(isWorkspaceToolResult(commandRequest, result), true);
}
});

test('sandbox annotations survive full stdout and remain bounded when stderr is noisy', async t => {
const root = await mkdtemp(join(tmpdir(), 'librechat-code-native-'));
t.after(() => rm(root, { recursive: true, force: true }));
const fake = fakeManager();
fake.manager.annotateStderrWithSandboxFailures = (_commandId, stderr) =>
stderr + '\n<sandbox_violations>\ndenied write\n</sandbox_violations>';
const sandbox = new NativeSrtWorkspaceCommandSandbox({
workspaceRoot: root,
manager: fake.manager,
});
t.after(() => sandbox.close());
for (const stderrCommand of ['', "printf '%20000d' 0 >&2;"]) {
const commandRequest = {
...request,
maxOutputBytes: 512,
command: `printf '%20000d' 0; ${stderrCommand} true`,
};
const result = await sandbox.execute(commandRequest);
assert.ok(
result.stderr.endsWith(
'<sandbox_violations>\ndenied write\n</sandbox_violations>'
)
);
assert.equal(isWorkspaceToolResult(commandRequest, result), true);
assert.equal(result.truncated, true);
}
fake.manager.annotateStderrWithSandboxFailures = () => {
throw new Error('annotation unavailable');
};
const result = await sandbox.execute({
...request,
maxOutputBytes: 128,
command: "printf '%20000d' 0; printf 'child failure' >&2",
});
assert.equal(result.stderr, 'child failure');
});

test('timeout settlement keeps late diagnostics, signal, and truncation without widening the result', async t => {
const root = await mkdtemp(join(tmpdir(), 'librechat-code-native-'));
t.after(() => rm(root, { recursive: true, force: true }));
const sandbox = new NativeSrtWorkspaceCommandSandbox({
workspaceRoot: root,
manager: fakeManager().manager,
});
t.after(() => sandbox.close());
const commandRequest = {
...request,
maxOutputBytes: 128,
timeoutMs: 100,
command:
"printf 'START\\n'; printf '%20000d' 0; printf '\\nlast progress\\n'; printf 'last failure' >&2; sleep 30",
};
const result = await sandbox.execute(commandRequest);
assert.ok(result.stdout.startsWith('START\n'));
assert.ok(result.stdout.endsWith('\nlast progress\n'));
assert.equal(result.stderr, 'last failure');
assert.equal(result.timedOut, true);
assert.equal(result.truncated, true);
assert.equal(result.exitCode, null);
assert.equal(result.signal, 'SIGKILL');
assert.equal(isWorkspaceToolResult(commandRequest, result), true);
});

test('rejects an escaping or unavailable command working directory', async t => {
const root = await mkdtemp(join(tmpdir(), 'librechat-code-native-'));
t.after(() => rm(root, { recursive: true, force: true }));
Expand Down
57 changes: 12 additions & 45 deletions packages/code/src/native-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
removePrivateStorageAcl,
} from './private-storage.js';
import { WorkspaceToolError } from './workspace.js';
import { OutputBuffer, renderCommandOutput } from './output.js';
import { restoreScratchTraversal } from './native-scratch.js';
import { writeLinkedWorktreeGitGuard } from './linked-worktree-git-guard.js';

Expand Down Expand Up @@ -1301,28 +1302,10 @@ export class NativeSrtWorkspaceCommandSandbox implements WorkspaceCommandSandbox
}
let settled = false;
let timedOut = false;
let outputBytes = 0;
let truncated = false;
const stdout: Buffer[] = [];
const stderr: Buffer[] = [];
const append = (target: Buffer[], chunk: Buffer): void => {
const remaining = outputLimit - outputBytes;
if (remaining <= 0) {
truncated = true;
return;
}
const accepted = chunk.subarray(0, remaining);
target.push(accepted);
outputBytes += accepted.byteLength;
if (accepted.byteLength !== chunk.byteLength)
truncated = true;
};
child.stdout.on('data', (chunk: Buffer) =>
append(stdout, chunk),
);
child.stderr.on('data', (chunk: Buffer) =>
append(stderr, chunk),
);
const stdout = new OutputBuffer(outputLimit);
const stderr = new OutputBuffer(outputLimit);
child.stdout.on('data', (chunk: Buffer) => stdout.append(chunk));
child.stderr.on('data', (chunk: Buffer) => stderr.append(chunk));
const abort = (): void => {
if (settled) return;
this.killCommandTree(child);
Expand Down Expand Up @@ -1371,29 +1354,17 @@ export class NativeSrtWorkspaceCommandSandbox implements WorkspaceCommandSandbox
);
return;
}
const stdoutValue = boundedUtf8(
Buffer.concat(stdout),
outputLimit,
);
const stderrBudget = Math.max(
0,
outputLimit - Buffer.byteLength(stdoutValue),
);
const rawStderr = Buffer.concat(stderr).toString('utf8');
let annotatedStderr = rawStderr;
try {
annotatedStderr =
this.manager.annotateStderrWithSandboxFailures(
commandId,
rawStderr,
// SRT appends violations to its input. Capture them in the same bounded stderr window.
stderr.append(
Buffer.from(
this.manager.annotateStderrWithSandboxFailures(commandId, ''),
),
);
} catch {
// Preserve the bounded child error if optional violation annotation fails.
}
const stderrValue = boundedUtf8(
Buffer.from(annotatedStderr),
stderrBudget,
);
const output = renderCommandOutput(stdout, stderr, outputLimit);
resolvePromise({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
operation: 'execute_command',
Expand All @@ -1403,11 +1374,7 @@ export class NativeSrtWorkspaceCommandSandbox implements WorkspaceCommandSandbox
? null
: this.protocolExitCode(code),
...(childSignal ? { signal: childSignal } : {}),
stdout: stdoutValue,
stderr: stderrValue,
truncated:
truncated ||
Buffer.byteLength(annotatedStderr) > stderrBudget,
...output,
timedOut,
});
});
Expand Down
Loading
Loading