From ad41afb7ca899ebdf8bc83b4d965e584689b95cb Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:24:02 +0000 Subject: [PATCH 1/5] computer: Expose ranged workspace reads Mirror readRange on the Workers RPC filesystem facade and have WorkspaceFileStore request fixed-size byte windows instead of streaming and discarding every skipped byte. --- packages/computer/src/stub.test.ts | 8 +++ packages/computer/src/stub.ts | 9 +++ packages/computer/src/tools/ai.test.ts | 74 +++++++++++++++------ packages/computer/src/tools/fs/store.ts | 85 ++++++++++--------------- 4 files changed, 106 insertions(+), 70 deletions(-) diff --git a/packages/computer/src/stub.test.ts b/packages/computer/src/stub.test.ts index 8d7dad13..2ec8eb63 100644 --- a/packages/computer/src/stub.test.ts +++ b/packages/computer/src/stub.test.ts @@ -205,6 +205,14 @@ describe("WorkspaceStub", () => { }); }); + it("fs.readRange forwards bounded byte reads", async () => { + await withStub(async (ws) => { + const stub = ws.stub(); + await stub.fs.writeFile("/bin", new Uint8Array([1, 2, 3, 4, 5])); + expect(Array.from(await stub.fs.readRange("/bin", 1, 3))).toEqual([2, 3, 4]); + }); + }); + it("fs.readdir forwards bounded-read options", async () => { await withStub(async (ws) => { const stub = ws.stub(); diff --git a/packages/computer/src/stub.ts b/packages/computer/src/stub.ts index 844d8452..00994248 100644 --- a/packages/computer/src/stub.ts +++ b/packages/computer/src/stub.ts @@ -114,6 +114,15 @@ export class WorkspaceFilesystemStub extends RpcTarget { ); } + readRange(path: string, offset: number, length: number): Promise { + return withSpan( + this.#ws.observer, + "workspace.fs.readRange", + { "workspace.fs.path": path, "workspace.fs.offset": offset, "workspace.fs.length": length }, + () => this.#ws.fs.readRange(path, offset, length), + ); + } + exists(path: string): Promise { return withSpan( this.#ws.observer, diff --git a/packages/computer/src/tools/ai.test.ts b/packages/computer/src/tools/ai.test.ts index b2636f94..707e343c 100644 --- a/packages/computer/src/tools/ai.test.ts +++ b/packages/computer/src/tools/ai.test.ts @@ -179,33 +179,63 @@ function memoryStore(options: { } describe("WorkspaceFileStore", () => { - it("slices byte ranges while reading chunks from Workspace.fs", async () => { - const workspace = makeWorkspace(); - await workspace.fs.mkdir("/workspace", { recursive: true }); - await workspace.fs.writeFile("/workspace/range.txt", bytes("abcdefghij")); + it("uses readRange without streaming skipped bytes from Workspace.fs", async () => { + const calls: Array<{ offset: number; length: number }> = []; + const content = bytes("abcdefghij"); + const workspace = { + fs: { + async stat() { + return { + size: content.length, + mtime: 1, + mode: 0o100644, + isFile: true, + isDirectory: false, + }; + }, + async readRange(_path: string, offset: number, length: number) { + calls.push({ offset, length }); + return content.slice(offset, offset + length); + }, + async readFile(): Promise> { + throw new Error("readFile must not be called for ranged reads"); + }, + async writeFile() {}, + async mkdir() {}, + async rm() {}, + async readdir() { + return []; + }, + }, + }; const store = new WorkspaceFileStore(workspace); await expect( drainChunks(store.readChunks("/workspace/range.txt", 2, 5)).then(decode), ).resolves.toBe("cdefg"); + expect(calls).toEqual([{ offset: 2, length: 5 }]); }); - it("cancels read streams when a byte range stops before EOF", async () => { - let cancelled = false; + it("splits unbounded reads into fixed-size ranges", async () => { + const calls: Array<{ offset: number; length: number }> = []; + const content = new Uint8Array(150_000).fill(7); const workspace = { fs: { async stat() { - return { size: 10, mtime: 1, mode: 0o100644, isFile: true, isDirectory: false }; + return { + size: content.length, + mtime: 1, + mode: 0o100644, + isFile: true, + isDirectory: false, + }; }, - async readFile() { - return new ReadableStream({ - start(controller) { - controller.enqueue(bytes("abcdefghij")); - }, - cancel() { - cancelled = true; - }, - }); + async readRange(_path: string, offset: number, length: number) { + calls.push({ offset, length }); + return content.slice(offset, offset + length); + }, + async readFile(): Promise> { + throw new Error("readFile must not be called for ranged reads"); }, async writeFile() {}, async mkdir() {}, @@ -217,10 +247,14 @@ describe("WorkspaceFileStore", () => { }; const store = new WorkspaceFileStore(workspace); - await expect( - drainChunks(store.readChunks("/workspace/range.txt", 2, 5)).then(decode), - ).resolves.toBe("cdefg"); - expect(cancelled).toBe(true); + await expect(drainChunks(store.readChunks("/workspace/large.bin"))).resolves.toHaveLength( + content.length, + ); + expect(calls).toEqual([ + { offset: 0, length: 65_536 }, + { offset: 65_536, length: 65_536 }, + { offset: 131_072, length: 18_928 }, + ]); }); }); diff --git a/packages/computer/src/tools/fs/store.ts b/packages/computer/src/tools/fs/store.ts index 739d97cd..76172e8f 100644 --- a/packages/computer/src/tools/fs/store.ts +++ b/packages/computer/src/tools/fs/store.ts @@ -6,13 +6,14 @@ * class. This adapter is the bridge from that contract to the public * `workspace.fs` surface. * - * Reads go through `fs.readFile(path)` as a `ReadableStream` - * and are stitched together either chunk-by-chunk (`readChunks`) or all - * at once (`readAll`). + * Bounded reads go through `fs.readRange`; whole-file reads used by edit + * and multimodal output still drain `fs.readFile(path)`. */ import type { FileStat, FileStore } from "./types.js"; +const RANGE_CHUNK_BYTES = 64 * 1024; + /** * Structural subset of `@cloudflare/computer.Workspace` the tools * depend on. @@ -27,10 +28,23 @@ export interface WorkspaceLike { isDirectory: boolean; }>; readFile(path: string): Promise>; + readRange(path: string, offset: number, length: number): Promise; writeFile(path: string, content: Uint8Array, options?: { mode?: number }): Promise; mkdir(path: string, options?: { recursive?: boolean }): Promise; rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise; - readdir(path: string): Promise>; + readdir( + path: string, + options?: { limit?: number; offset?: number }, + ): Promise< + Array<{ + name: string; + size: number; + mtime: number; + isFile: boolean; + isDirectory: boolean; + isSymbolicLink: boolean; + }> + >; }; } @@ -64,55 +78,26 @@ export class WorkspaceFileStore implements FileStore { } async *readChunks(path: string, byteOffset = 0, byteLength?: number): AsyncIterable { - if (byteOffset < 0) throw new Error("readChunks: byteOffset must be non-negative"); - if (byteLength !== undefined && byteLength < 0) { - throw new Error("readChunks: byteLength must be non-negative"); + if (!Number.isSafeInteger(byteOffset) || byteOffset < 0) { + throw new Error("readChunks: byteOffset must be a non-negative safe integer"); + } + if (byteLength !== undefined && (!Number.isSafeInteger(byteLength) || byteLength < 0)) { + throw new Error("readChunks: byteLength must be a non-negative safe integer"); } if (byteLength === 0) return; - const stream = await this.ws.fs.readFile(path); - const reader = stream.getReader(); - let skipped = 0; - let yielded = 0; - let completed = false; - try { - while (true) { - const { value, done } = await reader.read(); - if (done) { - completed = true; - break; - } - if (!value || value.byteLength === 0) continue; - - let start = 0; - if (skipped < byteOffset) { - const needed = byteOffset - skipped; - if (value.byteLength <= needed) { - skipped += value.byteLength; - continue; - } - start = needed; - skipped = byteOffset; - } - - let end = value.byteLength; - if (byteLength !== undefined) { - const remaining = byteLength - yielded; - if (remaining <= 0) break; - end = Math.min(end, start + remaining); - } - - if (end > start) { - const chunk = value.slice(start, end); - yielded += chunk.byteLength; - yield chunk; - } - - if (byteLength !== undefined && yielded >= byteLength) break; - } - } finally { - if (!completed) await reader.cancel(); - reader.releaseLock(); + const stat = await this.ws.fs.stat(path); + let remaining = Math.max(0, stat.size - byteOffset); + if (byteLength !== undefined) remaining = Math.min(remaining, byteLength); + let offset = byteOffset; + while (remaining > 0) { + const requested = Math.min(remaining, RANGE_CHUNK_BYTES); + const chunk = await this.ws.fs.readRange(path, offset, requested); + if (chunk.byteLength === 0) return; + yield chunk; + offset += chunk.byteLength; + remaining -= chunk.byteLength; + if (chunk.byteLength < requested) return; } } } From d851014840c7c80d59edad1a63a7ecd3903aa4d3 Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:25:00 +0000 Subject: [PATCH 2/5] computer: Paginate ls results Return file size and modification time from ls, and expose limit, offset, and nextOffset so large directories can be read in stable pages. --- packages/computer/src/tools/ai.test.ts | 38 +++++++++++++++- packages/computer/src/tools/fs/list.ts | 61 +++++++++++++++++++++----- 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/packages/computer/src/tools/ai.test.ts b/packages/computer/src/tools/ai.test.ts index 707e343c..1b861411 100644 --- a/packages/computer/src/tools/ai.test.ts +++ b/packages/computer/src/tools/ai.test.ts @@ -289,7 +289,17 @@ describe("createAITools filesystem tools", () => { ); await expect(executeTool(tools.ls, { path: "/workspace/notes" })).resolves.toEqual({ path: "/workspace/notes", - entries: [{ name: "todo.txt", isFile: true, isDirectory: false }], + count: 1, + entries: [ + { + name: "todo.txt", + size: 8, + mtime: 1_700_000_000_000, + isFile: true, + isDirectory: false, + isSymbolicLink: false, + }, + ], }); await expect( executeTool(tools.read, { path: "/workspace/notes/todo.txt", limit: 1 }), @@ -313,6 +323,32 @@ describe("createAITools filesystem tools", () => { ); }); + it("paginates ls results and reports a continuation offset", async () => { + const workspace = makeWorkspace(); + await workspace.fs.mkdir("/workspace", { recursive: true }); + for (const name of ["a", "b", "c"]) { + await workspace.fs.writeFile(`/workspace/${name}`, name); + } + const tools = createAITools({ workspace }); + + await expect( + executeTool(tools.ls, { path: "/workspace", limit: 2, offset: 0 }), + ).resolves.toMatchObject({ + count: 2, + entries: [ + { name: "a", size: 1 }, + { name: "b", size: 1 }, + ], + nextOffset: 2, + }); + await expect( + executeTool(tools.ls, { path: "/workspace", limit: 2, offset: 2 }), + ).resolves.toMatchObject({ + count: 1, + entries: [{ name: "c", size: 1 }], + }); + }); + it("preserves file mode when write overwrites an existing file", async () => { const writes: Array<{ path: string; content: string; mode?: number }> = []; const tool = createWriteTool({ diff --git a/packages/computer/src/tools/fs/list.ts b/packages/computer/src/tools/fs/list.ts index d5e91490..eafe7097 100644 --- a/packages/computer/src/tools/fs/list.ts +++ b/packages/computer/src/tools/fs/list.ts @@ -3,7 +3,19 @@ import { z } from "zod"; export interface ListWorkspaceLike { fs: { - readdir(path: string): Promise>; + readdir( + path: string, + options?: { limit?: number; offset?: number }, + ): Promise< + Array<{ + name: string; + size: number; + mtime: number; + isFile: boolean; + isDirectory: boolean; + isSymbolicLink: boolean; + }> + >; }; } @@ -11,26 +23,55 @@ export interface ListToolOptions { workspace: ListWorkspaceLike; } +const DEFAULT_LIMIT = 200; +const MAX_LIMIT = 1000; + const inputSchema = z.object({ path: z.string().describe("Absolute directory path to list, e.g. /workspace/src."), + limit: z + .number() + .int() + .min(1) + .max(MAX_LIMIT) + .optional() + .describe(`Maximum entries to return. Defaults to ${DEFAULT_LIMIT}.`), + offset: z.number().int().min(0).optional().describe("Number of entries to skip in name order."), }); export function createListTool(options: ListToolOptions): Tool> { return tool({ description: - "List entries in a workspace directory. Returns each entry name and whether it is a file or directory.", + "List entries in a workspace directory with file sizes and modification times. Use limit and offset to page through large directories.", inputSchema, - execute: async ({ path }) => { + execute: async ({ path, limit, offset }) => { try { - const entries = await options.workspace.fs.readdir(path); - return { + const pageSize = limit ?? DEFAULT_LIMIT; + const pageOffset = offset ?? 0; + const entries = await options.workspace.fs.readdir(path, { + limit: pageSize + 1, + offset: pageOffset, + }); + const truncated = entries.length > pageSize; + const page = (truncated ? entries.slice(0, pageSize) : entries).map((entry) => ({ + name: entry.name, + size: entry.size, + mtime: entry.mtime, + isFile: entry.isFile, + isDirectory: entry.isDirectory, + isSymbolicLink: entry.isSymbolicLink, + })); + const result: { + path: string; + count: number; + entries: typeof page; + nextOffset?: number; + } = { path, - entries: entries.map((entry) => ({ - name: entry.name, - isFile: entry.isFile, - isDirectory: entry.isDirectory, - })), + count: page.length, + entries: page, }; + if (truncated) result.nextOffset = pageOffset + pageSize; + return result; } catch (err) { return { error: err instanceof Error ? err.message : String(err) }; } From 738d3144c32b5e29ecfa1923a0869bdde2642906 Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:24:21 +0000 Subject: [PATCH 3/5] computer: Add bounded filesystem changeset Record ranged workspace reads and paginated directory metadata with the Computer package that exposes them. --- .changeset/computer-bounded-filesystem.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/computer-bounded-filesystem.md diff --git a/.changeset/computer-bounded-filesystem.md b/.changeset/computer-bounded-filesystem.md new file mode 100644 index 00000000..0b5f7249 --- /dev/null +++ b/.changeset/computer-bounded-filesystem.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/computer": minor +--- + +Expose bounded workspace byte reads through RPC and return paginated directory listings with file metadata. From 493bdf0a6d85d6c2961f31551fcd356a1a8b452c Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:52:44 +0000 Subject: [PATCH 4/5] computer: Stream ranged tool reads --- packages/computer/src/stub.test.ts | 10 ++ packages/computer/src/stub.ts | 5 + packages/computer/src/tools/ai.test.ts | 128 ++++++++++++++++-------- packages/computer/src/tools/fs/store.ts | 45 +++++---- 4 files changed, 125 insertions(+), 63 deletions(-) diff --git a/packages/computer/src/stub.test.ts b/packages/computer/src/stub.test.ts index 2ec8eb63..84f42362 100644 --- a/packages/computer/src/stub.test.ts +++ b/packages/computer/src/stub.test.ts @@ -205,6 +205,16 @@ describe("WorkspaceStub", () => { }); }); + it("fs.readFile forwards ranged stream options", async () => { + await withStub(async (ws) => { + const stub = ws.stub(); + await stub.fs.writeFile("/bin", new Uint8Array([1, 2, 3, 4, 5])); + const stream = await stub.fs.readFile("/bin", { byteOffset: 1, byteLength: 3 }); + const bytes = new Uint8Array(await new Response(stream).arrayBuffer()); + expect(Array.from(bytes)).toEqual([2, 3, 4]); + }); + }); + it("fs.readRange forwards bounded byte reads", async () => { await withStub(async (ws) => { const stub = ws.stub(); diff --git a/packages/computer/src/stub.ts b/packages/computer/src/stub.ts index 00994248..90eda3b1 100644 --- a/packages/computer/src/stub.ts +++ b/packages/computer/src/stub.ts @@ -104,6 +104,11 @@ export class WorkspaceFilesystemStub extends RpcTarget { readFile(path: string): Promise>; readFile(path: string, encoding: "utf8"): Promise; + readFile( + path: string, + options: ReadFileOptions & { encoding?: undefined }, + ): Promise>; + readFile(path: string, options: ReadFileOptions & { encoding: "utf8" }): Promise; readFile(path: string, options: ReadFileOptions): Promise>; readFile( path: string, diff --git a/packages/computer/src/tools/ai.test.ts b/packages/computer/src/tools/ai.test.ts index 1b861411..e58c1054 100644 --- a/packages/computer/src/tools/ai.test.ts +++ b/packages/computer/src/tools/ai.test.ts @@ -179,33 +179,34 @@ function memoryStore(options: { } describe("WorkspaceFileStore", () => { - it("uses readRange without streaming skipped bytes from Workspace.fs", async () => { - const calls: Array<{ offset: number; length: number }> = []; + it("opens one ranged stream instead of issuing repeated range calls", async () => { + const calls: Array<{ byteOffset?: number; byteLength?: number }> = []; const content = bytes("abcdefghij"); const workspace = { fs: { async stat() { - return { - size: content.length, - mtime: 1, - mode: 0o100644, - isFile: true, - isDirectory: false, - }; + throw new Error("stat must not be called by readChunks"); }, - async readRange(_path: string, offset: number, length: number) { - calls.push({ offset, length }); - return content.slice(offset, offset + length); + async readRange() { + throw new Error("readRange must not be called by readChunks"); }, - async readFile(): Promise> { - throw new Error("readFile must not be called for ranged reads"); + async readFile( + _path: string, + options: { byteOffset?: number; byteLength?: number } = {}, + ): Promise> { + calls.push(options); + const start = options.byteOffset ?? 0; + const end = options.byteLength === undefined ? undefined : start + options.byteLength; + return new ReadableStream({ + start(controller) { + controller.enqueue(content.slice(start, end)); + controller.close(); + }, + }); }, async writeFile() {}, async mkdir() {}, async rm() {}, - async readdir() { - return []; - }, }, }; const store = new WorkspaceFileStore(workspace); @@ -213,48 +214,89 @@ describe("WorkspaceFileStore", () => { await expect( drainChunks(store.readChunks("/workspace/range.txt", 2, 5)).then(decode), ).resolves.toBe("cdefg"); - expect(calls).toEqual([{ offset: 2, length: 5 }]); + expect(calls).toEqual([{ byteOffset: 2, byteLength: 5 }]); + }); + + it("still validates the path for a zero-length read", async () => { + const store = new WorkspaceFileStore(makeWorkspace()); + + await expect(drainChunks(store.readChunks("/missing", 0, 0))).rejects.toMatchObject({ + code: "ENOENT", + }); }); - it("splits unbounded reads into fixed-size ranges", async () => { - const calls: Array<{ offset: number; length: number }> = []; - const content = new Uint8Array(150_000).fill(7); + it("rejects directories instead of treating them as empty files", async () => { + const workspace = makeWorkspace(); + await workspace.fs.mkdir("/directory"); + const store = new WorkspaceFileStore(workspace); + + await expect(drainChunks(store.readChunks("/directory"))).rejects.toMatchObject({ + code: "EISDIR", + }); + }); + + it("keeps a real multi-chunk workspace read on one snapshot", async () => { + const workspace = makeWorkspace(); + await workspace.fs.mkdir("/workspace", { recursive: true }); + const original = new Uint8Array(600_000); + original.fill(0x41, 0, 500_000); + original.fill(0x42, 500_000); + await workspace.fs.writeFile("/workspace/large.bin", original); + const store = new WorkspaceFileStore(workspace); + + const chunks = store.readChunks("/workspace/large.bin")[Symbol.asyncIterator](); + const first = await chunks.next(); + expect(first.done).toBe(false); + await workspace.fs.writeFile( + "/workspace/large.bin", + new Uint8Array(original.length).fill(0x43), + ); + + const parts = [first.value]; + while (true) { + const next = await chunks.next(); + if (next.done) break; + parts.push(next.value); + } + const result = await drainChunks( + (async function* () { + yield* parts; + })(), + ); + expect(result.byteLength).toBe(original.byteLength); + expect(result.every((value, index) => value === original[index])).toBe(true); + }); + + it("cancels a ranged stream when its consumer stops early", async () => { + let cancelled = false; const workspace = { fs: { async stat() { - return { - size: content.length, - mtime: 1, - mode: 0o100644, - isFile: true, - isDirectory: false, - }; + throw new Error("stat must not be called by readChunks"); }, - async readRange(_path: string, offset: number, length: number) { - calls.push({ offset, length }); - return content.slice(offset, offset + length); + async readRange() { + throw new Error("readRange must not be called by readChunks"); }, async readFile(): Promise> { - throw new Error("readFile must not be called for ranged reads"); + return new ReadableStream({ + start(controller) { + controller.enqueue(bytes("first")); + controller.enqueue(bytes("second")); + }, + cancel() { + cancelled = true; + }, + }); }, async writeFile() {}, async mkdir() {}, async rm() {}, - async readdir() { - return []; - }, }, }; const store = new WorkspaceFileStore(workspace); - await expect(drainChunks(store.readChunks("/workspace/large.bin"))).resolves.toHaveLength( - content.length, - ); - expect(calls).toEqual([ - { offset: 0, length: 65_536 }, - { offset: 65_536, length: 65_536 }, - { offset: 131_072, length: 18_928 }, - ]); + for await (const _chunk of store.readChunks("/workspace/range.txt")) break; + expect(cancelled).toBe(true); }); }); diff --git a/packages/computer/src/tools/fs/store.ts b/packages/computer/src/tools/fs/store.ts index 76172e8f..442de1c4 100644 --- a/packages/computer/src/tools/fs/store.ts +++ b/packages/computer/src/tools/fs/store.ts @@ -6,14 +6,13 @@ * class. This adapter is the bridge from that contract to the public * `workspace.fs` surface. * - * Bounded reads go through `fs.readRange`; whole-file reads used by edit - * and multimodal output still drain `fs.readFile(path)`. + * Chunked and ranged reads use one `fs.readFile` stream so remote workspaces + * keep one snapshot and one RPC invocation. Whole-file reads used by edit and + * multimodal output drain the same stream interface. */ import type { FileStat, FileStore } from "./types.js"; -const RANGE_CHUNK_BYTES = 64 * 1024; - /** * Structural subset of `@cloudflare/computer.Workspace` the tools * depend on. @@ -27,8 +26,10 @@ export interface WorkspaceLike { isFile: boolean; isDirectory: boolean; }>; - readFile(path: string): Promise>; - readRange(path: string, offset: number, length: number): Promise; + readFile( + path: string, + options?: { byteOffset?: number; byteLength?: number }, + ): Promise>; writeFile(path: string, content: Uint8Array, options?: { mode?: number }): Promise; mkdir(path: string, options?: { recursive?: boolean }): Promise; rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise; @@ -84,20 +85,24 @@ export class WorkspaceFileStore implements FileStore { if (byteLength !== undefined && (!Number.isSafeInteger(byteLength) || byteLength < 0)) { throw new Error("readChunks: byteLength must be a non-negative safe integer"); } - if (byteLength === 0) return; - - const stat = await this.ws.fs.stat(path); - let remaining = Math.max(0, stat.size - byteOffset); - if (byteLength !== undefined) remaining = Math.min(remaining, byteLength); - let offset = byteOffset; - while (remaining > 0) { - const requested = Math.min(remaining, RANGE_CHUNK_BYTES); - const chunk = await this.ws.fs.readRange(path, offset, requested); - if (chunk.byteLength === 0) return; - yield chunk; - offset += chunk.byteLength; - remaining -= chunk.byteLength; - if (chunk.byteLength < requested) return; + const stream = await this.ws.fs.readFile(path, { byteOffset, byteLength }); + const reader = stream.getReader(); + let completed = false; + try { + while (true) { + const { value, done } = await reader.read(); + if (done) { + completed = true; + return; + } + if (value !== undefined && value.byteLength > 0) yield value; + } + } finally { + try { + if (!completed) await reader.cancel(); + } finally { + reader.releaseLock(); + } } } } From ae6a9e28b8d1225f7fd03ea5651248b651434c7b Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:06:11 +0000 Subject: [PATCH 5/5] computer: Remove the readRange RPC method --- packages/computer/src/stub.test.ts | 8 -------- packages/computer/src/stub.ts | 9 --------- 2 files changed, 17 deletions(-) diff --git a/packages/computer/src/stub.test.ts b/packages/computer/src/stub.test.ts index 84f42362..77a81c18 100644 --- a/packages/computer/src/stub.test.ts +++ b/packages/computer/src/stub.test.ts @@ -215,14 +215,6 @@ describe("WorkspaceStub", () => { }); }); - it("fs.readRange forwards bounded byte reads", async () => { - await withStub(async (ws) => { - const stub = ws.stub(); - await stub.fs.writeFile("/bin", new Uint8Array([1, 2, 3, 4, 5])); - expect(Array.from(await stub.fs.readRange("/bin", 1, 3))).toEqual([2, 3, 4]); - }); - }); - it("fs.readdir forwards bounded-read options", async () => { await withStub(async (ws) => { const stub = ws.stub(); diff --git a/packages/computer/src/stub.ts b/packages/computer/src/stub.ts index 90eda3b1..657ebc6f 100644 --- a/packages/computer/src/stub.ts +++ b/packages/computer/src/stub.ts @@ -119,15 +119,6 @@ export class WorkspaceFilesystemStub extends RpcTarget { ); } - readRange(path: string, offset: number, length: number): Promise { - return withSpan( - this.#ws.observer, - "workspace.fs.readRange", - { "workspace.fs.path": path, "workspace.fs.offset": offset, "workspace.fs.length": length }, - () => this.#ws.fs.readRange(path, offset, length), - ); - } - exists(path: string): Promise { return withSpan( this.#ws.observer,