feat: stream files as binary frames over WebSocket (#659)

Replace base64-in-JSON file transfers with binary WebSocket frames to
unblock the JS thread when assistant markdown messages contain images.
Receive cost (JSON.parse over multi-MB base64) and persist cost (base64
re-encode to disk) both removed.

- Split daemon-client `exploreFileSystem` into `listDirectory` + `readFile`;
  `readFile` returns `{ bytes, mime, size, modifiedAt }`. Encoding stays
  private to the seam.
- New `packages/server/src/shared/binary-frames/` module hosts terminal
  frames (moved from `terminal-stream-protocol.ts`) and new file-transfer
  opcodes (`FileBegin` / `FileChunk` / `FileEnd`) with request-id correlation.
- Add optional `acceptBinary` to `FileExplorerRequestSchema`. New daemons
  emit binary frames when set; legacy JSON path remains for old clients.
  Compat fallback lives only inside `readFile`.
- New `persistAttachmentFromBytes` writes bytes directly: native via
  `expo-file-system` `File.write(Uint8Array)`, web via Blob -> IndexedDB,
  desktop via new `write_attachment_bytes` IPC. `persistAttachmentFromBase64`
  removed.
This commit is contained in:
Mohamed Boudra
2026-05-02 14:02:27 +08:00
committed by GitHub
parent b6c43088fb
commit bb889dae99
44 changed files with 1630 additions and 436 deletions
@@ -12,6 +12,7 @@ import {
deleteDesktopAttachmentFile,
garbageCollectDesktopAttachmentFiles,
writeDesktopAttachmentBase64,
writeDesktopAttachmentBytes,
} from "@/desktop/attachments/desktop-file-commands";
import {
readDesktopFileBase64,
@@ -132,6 +133,32 @@ async function saveDesktopAttachmentFromBase64(input: {
});
}
async function saveDesktopAttachmentFromBytes(input: {
id: string;
bytes: Uint8Array;
mimeType: string;
fileName?: string | null;
}): Promise<AttachmentMetadata> {
const extension = extensionForAttachment({
fileName: input.fileName,
mimeType: input.mimeType,
});
const result = await writeDesktopAttachmentBytes({
attachmentId: input.id,
bytes: input.bytes,
extension,
});
return toDesktopMetadata({
id: input.id,
mimeType: input.mimeType,
storagePath: result.path,
byteSize: result.byteSize,
fileName: input.fileName,
});
}
function assertDesktopAttachment(attachment: AttachmentMetadata): void {
if (attachment.storageType !== "desktop-file") {
throw new Error(`Unsupported desktop attachment storage type '${attachment.storageType}'.`);
@@ -166,11 +193,11 @@ export function createDesktopAttachmentStore(): AttachmentStore {
});
}
if (input.source.kind === "base64") {
if (input.source.kind === "bytes") {
const mimeType = normalizeMimeType(input.mimeType);
return await saveDesktopAttachmentFromBase64({
return await saveDesktopAttachmentFromBytes({
id,
base64: input.source.base64,
bytes: input.source.bytes,
mimeType,
fileName,
});