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
12 changes: 9 additions & 3 deletions apps/server/src/orchestration-v2/WireProjection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ describe("orchestration V2 wire projection", () => {
});

it("truncates detail at a UTF-8 boundary without changing the source", () => {
const progress = "a".repeat(32_767) + "😀" + "x".repeat(100_000);
const marker = "\n… output truncated for transport";
const prefixBytes = 32_768 - Buffer.byteLength(marker);
const progress = "a".repeat(prefixBytes - 1) + "😀" + "x".repeat(100_000);
const item = {
...base,
type: "subagent" as const,
Expand All @@ -184,13 +186,17 @@ describe("orchestration V2 wire projection", () => {
driver: ProviderDriverKind.make("codex"),
providerInstanceId: ProviderInstanceId.make("codex"),
childThreadId: null,
prompt: "Inspect code",
prompt: "漢".repeat(12_000),
result: null,
progress,
};
const projected = projectTurnItemForWire(item);
expect(projectTurnItemForWire(projected)).toEqual(projected);
expect(
Buffer.byteLength(projected.type === "subagent" ? projected.prompt : ""),
).toBeLessThanOrEqual(32_768);
expect(projected.type === "subagent" ? projected.progress : null).toBe(
"a".repeat(32_767) + "\n… output truncated for transport",
"a".repeat(prefixBytes - 1) + marker,
);
expect(item.progress).toBe(progress);
});
Expand Down
10 changes: 7 additions & 3 deletions apps/server/src/orchestration-v2/WireProjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const MAX_WIRE_CHECKPOINT_FILES = 200;
const MAX_DETAIL_STRING_BYTES = 32_768;
const MAX_DYNAMIC_VALUE_BYTES = 16_384;
const MAX_ON_DEMAND_BYTES = 256 * 1024;
const TRUNCATION_MARKER = "\n… output truncated for transport";
const TRUNCATION_MARKER_BYTES = Buffer.byteLength(TRUNCATION_MARKER, "utf8");

function truncateDetail(
value: string | undefined,
Expand All @@ -37,12 +39,14 @@ function truncateDetail(
) {
return value;
}
// The marker counts toward the cap, so projecting truncated text again leaves it unchanged.
const budget = maxBytes - TRUNCATION_MARKER_BYTES;
// UTF-8 needs at least one byte per UTF-16 unit; encode only a potentially fitting prefix.
const prefix = Buffer.from(value.slice(0, maxBytes), "utf8")
.subarray(0, maxBytes)
const prefix = Buffer.from(value.slice(0, budget), "utf8")
.subarray(0, budget)
.toString("utf8")
.replace(/\uFFFD$/u, "");
return `${prefix}\n… output truncated for transport`;
return `${prefix}${TRUNCATION_MARKER}`;
}

/** Bound stored diffs to the client wire limit to avoid retaining invisible data in SQLite and heap. */
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ export const subscribeOrchestrationV2Thread = Effect.fn("ws.orchestrationV2.subs
Stream.map((stored) => ({
kind: "event" as const,
sequence: stored.sequence,
event: projectDomainEventForWire(stored.event),
event: stored.event,
})),
coalesceThreadLiveStream,
Stream.mapError(
Expand Down
Loading