diff --git a/packages/code/README.md b/packages/code/README.md
index d421e444..acae9bb8 100644
--- a/packages/code/README.md
+++ b/packages/code/README.md
@@ -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 —
diff --git a/packages/code/src/native-sandbox.test.ts b/packages/code/src/native-sandbox.test.ts
index 9001102f..90215156 100644
--- a/packages/code/src/native-sandbox.test.ts
+++ b/packages/code/src/native-sandbox.test.ts
@@ -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,
@@ -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\ndenied write\n';
+ 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(
+ '\ndenied write\n'
+ )
+ );
+ 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 }));
diff --git a/packages/code/src/native-sandbox.ts b/packages/code/src/native-sandbox.ts
index 5d39d405..26c848e5 100644
--- a/packages/code/src/native-sandbox.ts
+++ b/packages/code/src/native-sandbox.ts
@@ -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';
@@ -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);
@@ -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',
@@ -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,
});
});
diff --git a/packages/code/src/output.test.ts b/packages/code/src/output.test.ts
new file mode 100644
index 00000000..f78b0993
--- /dev/null
+++ b/packages/code/src/output.test.ts
@@ -0,0 +1,353 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { OutputBuffer, renderCommandOutput } from './output.js';
+
+function collect(
+ content: Buffer,
+ budget: number,
+ chunkSize = content.length || 1
+): OutputBuffer {
+ const output = new OutputBuffer(budget);
+ for (let offset = 0; offset < content.length; offset += chunkSize) {
+ output.append(content.subarray(offset, offset + chunkSize));
+ }
+ return output;
+}
+
+function assertBounded(text: string, budget: number): void {
+ assert.ok(Buffer.byteLength(text) <= budget);
+ assert.equal(Buffer.from(text).toString('utf8'), text);
+}
+
+test('small output stays byte-for-byte unchanged, including split UTF-8 and exact limits', () => {
+ for (const content of ['', '1234567', 'hello\n世界🌍\n']) {
+ const bytes = Buffer.from(content);
+ const budget = Math.max(1, bytes.length);
+ for (const chunkSize of [1, 2, 7, 64]) {
+ assert.deepEqual(collect(bytes, budget, chunkSize).render(budget), {
+ text: content,
+ truncated: false,
+ });
+ }
+ }
+});
+
+test('a rolling suffix keeps the last summary and reports the exact dropped-byte count', () => {
+ const budget = 128;
+ const content = Buffer.from(
+ 'START\n' + 'x'.repeat(10_000) + '\n42 tests passed\n'
+ );
+ for (const chunkSize of [1, 3, 63, 64, 65, 127, 1024, content.length]) {
+ const result = collect(content, budget, chunkSize).render(budget);
+ assert.equal(result.truncated, true);
+ assert.ok(result.text.startsWith('START\n'));
+ assert.ok(result.text.endsWith('\n42 tests passed\n'));
+ const marker = /\n\[\.\.\. (\d+) bytes omitted \.\.\.\]\n/.exec(
+ result.text
+ );
+ assert.ok(marker);
+ assert.equal(
+ Number(marker[1]),
+ content.length -
+ Buffer.byteLength(result.text.replace(marker[0], ''))
+ );
+ assertBounded(result.text, budget);
+ }
+});
+
+test('either quiet stream donates its budget, and stdout cannot starve stderr', () => {
+ const budget = 256;
+ const empty = collect(Buffer.alloc(0), budget);
+ const exact = collect(Buffer.alloc(budget, 'a'), budget, 1);
+ assert.deepEqual(renderCommandOutput(exact, empty, budget), {
+ stdout: 'a'.repeat(budget),
+ stderr: '',
+ truncated: false,
+ });
+ assert.deepEqual(renderCommandOutput(empty, exact, budget), {
+ stdout: '',
+ stderr: 'a'.repeat(budget),
+ truncated: false,
+ });
+ const stdout = collect(
+ Buffer.from('OUT\n' + 'x'.repeat(4096) + '\nstdout summary'),
+ budget
+ );
+ const stderr = collect(
+ Buffer.from('ERR\n' + 'y'.repeat(4096) + '\nstderr summary'),
+ budget
+ );
+ const result = renderCommandOutput(stdout, stderr, budget);
+ assert.ok(result.stdout.startsWith('OUT\n'));
+ assert.ok(result.stderr.startsWith('ERR\n'));
+ assert.ok(result.stdout.endsWith('stdout summary'));
+ assert.ok(result.stderr.endsWith('stderr summary'));
+ assert.ok(result.stdout.includes('bytes omitted'));
+ assert.ok(result.stderr.includes('bytes omitted'));
+ assert.ok(
+ Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr) <=
+ budget
+ );
+ assert.equal(result.truncated, true);
+
+ const shortError = collect(Buffer.from('late failure'), budget);
+ assert.equal(
+ renderCommandOutput(stdout, shortError, budget).stderr,
+ 'late failure'
+ );
+ assert.equal(
+ renderCommandOutput(shortError, stdout, budget).stdout,
+ 'late failure'
+ );
+});
+
+test('partly filled buffers retain final summaries on either stream', () => {
+ const capacity = 256;
+ const summary = 'late stderr summary\n';
+ const content = Buffer.from('y'.repeat(129 - summary.length) + summary);
+ for (const chunkSize of [1, 7, 128, content.length]) {
+ const quiet = collect(content, capacity, chunkSize);
+ const noisy = collect(Buffer.alloc(20_000, 'x'), capacity, chunkSize);
+ for (const [stdout, stderr, stream] of [
+ [noisy, quiet, 'stderr'],
+ [quiet, noisy, 'stdout'],
+ ] as const) {
+ const result = renderCommandOutput(stdout, stderr, capacity);
+ assert.ok(result[stream].endsWith(summary));
+ assert.ok(
+ Buffer.byteLength(result.stdout) +
+ Buffer.byteLength(result.stderr) <=
+ capacity
+ );
+ }
+ }
+});
+
+test('rendered windows match logical stream ends across capture and allowance boundaries', () => {
+ for (const capacity of [32, 63, 64, 127, 128, 255, 256, 257]) {
+ const half = Math.floor(capacity / 2);
+ for (const length of [
+ 0,
+ 1,
+ half - 1,
+ half,
+ half + 1,
+ capacity - 1,
+ capacity,
+ capacity + 1,
+ capacity * 4,
+ ]) {
+ const content = Buffer.from(
+ Array.from({ length }, (_, index) =>
+ String.fromCharCode(65 + (index % 26))
+ ).join('')
+ );
+ for (const budget of [
+ 0,
+ 1,
+ Math.floor(half / 2),
+ half,
+ capacity - 1,
+ capacity,
+ ]) {
+ let expected = content.toString('utf8');
+ if (length > budget) {
+ const marker = (bytes: number): string =>
+ `\n[... ${bytes} bytes omitted ...]\n`;
+ const markerBytes = Buffer.byteLength(marker(length));
+ const includeMarker = budget >= markerBytes;
+ const remaining = includeMarker
+ ? budget - markerBytes
+ : budget;
+ const prefix = Math.floor(remaining / 2);
+ const suffix = Math.ceil(remaining / 2);
+ expected =
+ content.subarray(0, prefix).toString('utf8') +
+ (includeMarker
+ ? marker(length - prefix - suffix)
+ : '') +
+ content.subarray(length - suffix).toString('utf8');
+ }
+ for (const chunkSize of [1, half, capacity + 7]) {
+ assert.deepEqual(
+ collect(content, capacity, chunkSize).render(budget),
+ {
+ text: expected,
+ truncated: length > budget,
+ },
+ `capacity ${capacity}, length ${length}, budget ${budget}, chunk ${chunkSize}`
+ );
+ }
+ }
+ }
+ }
+});
+
+test('UTF-8 summaries survive capture transitions and split code-point chunks', () => {
+ const summary = 'end 世界🌍\n';
+ for (const capacity of [128, 255, 256, 257]) {
+ const half = Math.floor(capacity / 2);
+ for (const length of [
+ half - 1,
+ half,
+ half + 1,
+ capacity - 1,
+ capacity,
+ capacity + 1,
+ ]) {
+ const content = Buffer.from(
+ 'x'.repeat(length - Buffer.byteLength(summary)) + summary
+ );
+ for (const chunkSize of [1, 2, content.length]) {
+ const quiet = collect(content, capacity, chunkSize);
+ const noisy = collect(
+ Buffer.alloc(capacity * 4, 'y'),
+ capacity,
+ chunkSize
+ );
+ for (const [stdout, stderr, stream] of [
+ [noisy, quiet, 'stderr'],
+ [quiet, noisy, 'stdout'],
+ ] as const) {
+ const result = renderCommandOutput(
+ stdout,
+ stderr,
+ capacity
+ );
+ assert.ok(result[stream].endsWith(summary));
+ assert.ok(!result[stream].includes('\ufffd'));
+ assert.ok(
+ Buffer.byteLength(result.stdout) +
+ Buffer.byteLength(result.stderr) <=
+ capacity
+ );
+ }
+ }
+ }
+ }
+});
+
+test('budgeting includes UTF-8 boundaries and marker overhead for every small limit', () => {
+ const content = Buffer.from('世界🌍 café Ελληνικά\n'.repeat(100));
+ for (let budget = 1; budget <= 256; budget += 1) {
+ const result = collect(content, budget, 1).render(budget);
+ assertBounded(result.text, budget);
+ assert.equal(result.truncated, true);
+ assert.ok(
+ !result.text.includes('\ufffd'),
+ `split code point at budget ${budget}`
+ );
+ const marker = /\n\[\.\.\. (\d+) bytes omitted \.\.\.\]\n/.exec(
+ result.text
+ );
+ if (marker) {
+ assert.equal(
+ Number(marker[1]),
+ content.length -
+ Buffer.byteLength(result.text.replace(marker[0], ''))
+ );
+ }
+ const streams = renderCommandOutput(
+ collect(content, budget, 7),
+ collect(content, budget, 3),
+ budget
+ );
+ assert.ok(
+ Buffer.byteLength(streams.stdout) +
+ Buffer.byteLength(streams.stderr) <=
+ budget
+ );
+ assert.ok(
+ !streams.stdout.includes('\ufffd') &&
+ !streams.stderr.includes('\ufffd')
+ );
+ }
+});
+
+test('malformed output cannot expand past the byte budget during decoding', () => {
+ for (const content of [
+ Buffer.alloc(1024, 0xff),
+ Buffer.from([0xf0, 0x80, 0xff, 0xc2, 0xa2, 0xe0, 0xa0]),
+ Buffer.from('a\ufffdb'.repeat(300)),
+ ]) {
+ for (let budget = 1; budget <= 128; budget += 1) {
+ const result = collect(content, budget, 1).render(budget);
+ assertBounded(result.text, budget);
+ if (content.length > budget) assert.equal(result.truncated, true);
+ }
+ }
+});
+
+test('short decoded output is preserved when replacement characters fit the combined budget', () => {
+ const budget = 32;
+ assert.deepEqual(
+ renderCommandOutput(
+ collect(Buffer.from([0xff, 0xff]), budget),
+ collect(Buffer.from('child failure'), budget),
+ budget
+ ),
+ { stdout: '\ufffd\ufffd', stderr: 'child failure', truncated: false }
+ );
+});
+
+test('quiet-stream donation counts decoded bytes beside noisy output', () => {
+ for (const bytes of [
+ Buffer.from([0xff, 0xff]),
+ Buffer.from([0x80, 0x80]),
+ Buffer.from([0xf0, 0x9f]),
+ Buffer.from([0x61, 0xff, 0x62]),
+ ]) {
+ const decoded = bytes.toString('utf8');
+ for (const budget of [16, 31, 32, 63, 64, 128]) {
+ const quiet = collect(bytes, budget, 1);
+ const noisy = collect(Buffer.alloc(budget * 4, 'x'), budget, 7);
+ for (const [stdout, stderr, stream] of [
+ [noisy, quiet, 'stderr'],
+ [quiet, noisy, 'stdout'],
+ ] as const) {
+ const result = renderCommandOutput(stdout, stderr, budget);
+ assert.equal(result[stream], decoded);
+ assert.equal(result.truncated, true);
+ assert.ok(
+ Buffer.byteLength(result.stdout) +
+ Buffer.byteLength(result.stderr) <=
+ budget
+ );
+ }
+ }
+ }
+});
+
+test('tiny budgets still flag truncation and give stderr the odd byte', () => {
+ for (let budget = 1; budget < 32; budget += 1) {
+ const result = renderCommandOutput(
+ collect(Buffer.from('a'.repeat(256)), budget),
+ collect(Buffer.from('b'.repeat(256)), budget),
+ budget
+ );
+ assert.equal(result.truncated, true);
+ assert.ok(result.stderr.length > 0);
+ assert.ok(
+ Buffer.byteLength(result.stdout) +
+ Buffer.byteLength(result.stderr) <=
+ budget
+ );
+ }
+});
+
+test('copies bounded windows instead of retaining or repeatedly concatenating source chunks', () => {
+ const budget = 128;
+ const output = new OutputBuffer(budget);
+ const oversized = Buffer.alloc(1024 * 1024, 'a');
+ output.append(oversized);
+ oversized.fill('z');
+ const byte = Buffer.from('b');
+ for (let i = 0; i < 100_000; i += 1) output.append(byte);
+ byte[0] = 0x7a;
+ const result = output.render(budget);
+ assert.ok(result.text.startsWith('a'.repeat(40)));
+ assert.ok(result.text.endsWith('b'.repeat(40)));
+ assert.ok(!result.text.includes('z'));
+ assert.equal(output.bytes, 1024 * 1024 + 100_000);
+ assertBounded(result.text, budget);
+});
diff --git a/packages/code/src/output.ts b/packages/code/src/output.ts
new file mode 100644
index 00000000..4cb34211
--- /dev/null
+++ b/packages/code/src/output.ts
@@ -0,0 +1,182 @@
+interface OutputWindow {
+ text: string;
+ bytes: number;
+}
+
+function utf8Window(
+ buffer: Buffer,
+ length: number,
+ tail: boolean
+): OutputWindow {
+ let start = tail ? buffer.length - length : 0;
+ let end = tail ? buffer.length : length;
+ if (tail) {
+ while (start < end && (buffer[start] & 0xc0) === 0x80) start += 1;
+ } else if (end > 0) {
+ let last = end - 1;
+ while (last > 0 && (buffer[last] & 0xc0) === 0x80) last -= 1;
+ const lead = buffer[last];
+ const width =
+ lead >= 0xc2 && lead <= 0xdf
+ ? 2
+ : lead >= 0xe0 && lead <= 0xef
+ ? 3
+ : lead >= 0xf0 && lead <= 0xf4
+ ? 4
+ : 1;
+ if (end - last < width) end = last;
+ }
+ return {
+ text: buffer.subarray(start, end).toString('utf8'),
+ bytes: end - start,
+ };
+}
+
+function boundedWindow(
+ buffer: Buffer,
+ budget: number,
+ tail: boolean
+): OutputWindow {
+ const length = Math.min(buffer.length, budget);
+ const window = utf8Window(buffer, length, tail);
+ if (Buffer.byteLength(window.text) <= budget) return window;
+
+ // Malformed bytes expand to replacement characters. Valid UTF-8 takes the fast path.
+ let low = 0;
+ let high = length;
+ while (low < high) {
+ const middle = Math.ceil((low + high) / 2);
+ if (
+ Buffer.byteLength(utf8Window(buffer, middle, tail).text) <= budget
+ ) {
+ low = middle;
+ } else {
+ high = middle - 1;
+ }
+ }
+ return utf8Window(buffer, low, tail);
+}
+
+/** Copies a prefix and rolling suffix, never retaining child-process chunk buffers. */
+export class OutputBuffer {
+ private readonly headCapacity: number;
+ private readonly tailCapacity: number;
+ private head?: Buffer;
+ private tail?: Buffer;
+ private headLength = 0;
+ private tailLength = 0;
+ private tailOffset = 0;
+ private totalBytes = 0;
+
+ constructor(capacity: number) {
+ this.headCapacity = Math.floor(capacity / 2);
+ this.tailCapacity = capacity - this.headCapacity;
+ }
+
+ get bytes(): number {
+ return this.totalBytes;
+ }
+
+ append(chunk: Buffer): void {
+ this.totalBytes += chunk.length;
+ const headBytes = Math.min(
+ chunk.length,
+ this.headCapacity - this.headLength
+ );
+ if (headBytes > 0) {
+ this.head ??= Buffer.allocUnsafe(this.headCapacity);
+ chunk.copy(this.head, this.headLength, 0, headBytes);
+ this.headLength += headBytes;
+ }
+ const remaining = chunk.length - headBytes;
+ if (remaining === 0) return;
+ this.tail ??= Buffer.allocUnsafe(this.tailCapacity);
+ if (remaining >= this.tailCapacity) {
+ chunk.copy(this.tail, 0, chunk.length - this.tailCapacity);
+ this.tailOffset = 0;
+ this.tailLength = this.tailCapacity;
+ return;
+ }
+ const first = Math.min(remaining, this.tailCapacity - this.tailOffset);
+ chunk.copy(this.tail, this.tailOffset, headBytes, headBytes + first);
+ chunk.copy(this.tail, 0, headBytes + first);
+ this.tailOffset = (this.tailOffset + remaining) % this.tailCapacity;
+ this.tailLength = Math.min(
+ this.tailCapacity,
+ this.tailLength + remaining
+ );
+ }
+
+ private suffix(): Buffer {
+ if (!this.tail) return Buffer.alloc(0);
+ if (this.tailLength < this.tailCapacity)
+ return this.tail.subarray(0, this.tailLength);
+ if (this.tailOffset === 0) return this.tail;
+ return Buffer.concat([
+ this.tail.subarray(this.tailOffset),
+ this.tail.subarray(0, this.tailOffset),
+ ]);
+ }
+
+ render(budget: number): { text: string; truncated: boolean } {
+ const head = this.head?.subarray(0, this.headLength) ?? Buffer.alloc(0);
+ const tail = this.suffix();
+ // Before overflow the buffers are adjacent; either window may cross their boundary.
+ const contiguous =
+ this.totalBytes === head.length + tail.length
+ ? tail.length === 0
+ ? head
+ : Buffer.concat([head, tail])
+ : undefined;
+ if (contiguous && this.totalBytes <= budget) {
+ const text = contiguous.toString('utf8');
+ if (Buffer.byteLength(text) <= budget)
+ return { text, truncated: false };
+ }
+ const marker = (bytes: number): string =>
+ `\n[... ${bytes} bytes omitted ...]\n`;
+ const markerBytes = Buffer.byteLength(marker(this.totalBytes));
+ const includeMarker = budget >= markerBytes;
+ const contentBudget = includeMarker ? budget - markerBytes : budget;
+ const prefix = boundedWindow(
+ contiguous ?? head,
+ Math.floor(contentBudget / 2),
+ false
+ );
+ const suffix = boundedWindow(
+ contiguous ?? tail,
+ Math.ceil(contentBudget / 2),
+ true
+ );
+ const omitted = this.totalBytes - prefix.bytes - suffix.bytes;
+ return {
+ text:
+ prefix.text +
+ (includeMarker ? marker(omitted) : '') +
+ suffix.text,
+ truncated: omitted > 0,
+ };
+ }
+}
+
+/** Each stream can use the whole budget when the other is quiet; stderr cannot be starved. */
+export function renderCommandOutput(
+ stdout: OutputBuffer,
+ stderr: OutputBuffer,
+ budget: number
+): {
+ stdout: string;
+ stderr: string;
+ truncated: boolean;
+} {
+ let err = stderr.render(Math.ceil(budget / 2));
+ const out = stdout.render(budget - Buffer.byteLength(err.text));
+ if (!out.truncated) {
+ err = stderr.render(budget - Buffer.byteLength(out.text));
+ }
+ return {
+ stdout: out.text,
+ stderr: err.text,
+ truncated: out.truncated || err.truncated,
+ };
+}