diff --git a/.agents/skills/capnweb/SKILL.md b/.agents/skills/capnweb/SKILL.md index fc037b6e..23d1f38c 100644 --- a/.agents/skills/capnweb/SKILL.md +++ b/.agents/skills/capnweb/SKILL.md @@ -3,7 +3,7 @@ name: capnweb description: | capnweb RPC patterns for this repo, with a heavy focus on stub lifecycle and disposal. Load when touching anything that crosses the Durable Object - ↔ wsd boundary: packages/rpc, packages/workspace, the wsd client, or the + ↔ computerd boundary: packages/rpc, packages/computer, the computerd client, or the Durable Object server. Triggers include "capnweb", "RpcTarget", "stub", "RPC wire", "promise pipelining", "stub disposal", "RpcPromise". --- @@ -11,7 +11,7 @@ description: | # capnweb in this repo [capnweb](https://github.com/cloudflare/capnweb) is the RPC framing -between the Durable Object and `wsd`. It's an object-capability RPC +between the Durable Object and `computerd`. It's an object-capability RPC system with promise pipelining, structured-clone-style transfer of stubs, and bidirectional calls. The wire format used here is text JSON over a long-lived WebSocket, with an HTTP batch alternative. @@ -23,7 +23,7 @@ JSON over a long-lived WebSocket, with an HTTP batch alternative. `SyncRPC` and `ShellRPC`. Add new methods here first. - [`packages/rpc/src/server.ts`](../../../packages/rpc/src/server.ts) — `Database`-backed implementation. Imported by the Durable Object - and the in-container workspace-server. + and the in-container computerd. - [`packages/rpc/src/client.ts`](../../../packages/rpc/src/client.ts) — typed stubs over a WebSocket carrier. The Durable Object uses a deferred transport so the stub can be created before the upgrade @@ -50,7 +50,7 @@ side of the connection. This matters more here than in many capnweb deployments because the connection is **long-lived**. HTTP batch sessions auto-dispose everything when the batch ends, but our WebSocket between the -Durable Object and `wsd` stays up for the lifetime of the workspace. +Durable Object and `computerd` stays up for the lifetime of the workspace. Every undisposed stub stays alive until that connection drops. ## The caller-disposes rule @@ -163,7 +163,7 @@ dispose call per stub. To collapse them into one, wrap the target in `stub.onRpcBroken(cb)` fires when the stub becomes unusable — typically because the underlying connection dropped or, for a promise, because the promise rejected. After the callback runs every -method call on that stub will throw. `packages/workspace` already +method call on that stub will throw. `packages/computer` already folds `onRpcBroken` into the workspace's closed promise; reuse that plumbing rather than wiring up a parallel listener. @@ -232,7 +232,7 @@ envelope to release every stub it contains. contain stubs — futureproofing is cheap. - **Do** call `.dup()` rather than awaiting twice when you need a stub copy to outlive a callee's auto-dispose. -- **Do** run the leak harness (`script/wsd-stub-soak`) when you +- **Do** run the leak harness (`script/computerd-stub-soak`) when you change anything around RPC lifecycle, and check `session.getStats()` for drift. - **Don't** send binary WebSocket frames. The wire is text JSON. @@ -249,11 +249,11 @@ envelope to release every stub it contains. - Use `enableStubTracking()` + `stubSnapshot()` in a test to assert no stub survives a round trip you expected to clean up. -- Soak the boundary with `script/wsd-stub-soak` for +- Soak the boundary with `script/computerd-stub-soak` for disposal-sensitive changes; it reads `session.getStats()` to detect drift. - Server-side RPC behavior is tested against the real `Database` - and real driver helpers in `packages/rpc` and `packages/workspace`. + and real driver helpers in `packages/rpc` and `packages/computer`. ## Further reading diff --git a/.agents/skills/cloudflare/SKILL.md b/.agents/skills/cloudflare/SKILL.md index eaa8267f..fe6a3294 100644 --- a/.agents/skills/cloudflare/SKILL.md +++ b/.agents/skills/cloudflare/SKILL.md @@ -27,7 +27,7 @@ These three cover almost every change in this repo: | Skill | Load when | |---|---| -| [`durable-objects`](https://github.com/cloudflare/skills/tree/main/skills/durable-objects) | Writing or reviewing Durable Object code: RPC methods, SQLite storage, alarms, WebSockets, hibernation. Most work in `packages/dofs` and `packages/workspace` qualifies. | +| [`durable-objects`](https://github.com/cloudflare/skills/tree/main/skills/durable-objects) | Writing or reviewing Durable Object code: RPC methods, SQLite storage, alarms, WebSockets, hibernation. Most work in `packages/dofs` and `packages/computer` qualifies. | | [`workers-best-practices`](https://github.com/cloudflare/skills/tree/main/skills/workers-best-practices) | Writing or reviewing Worker code: streaming, floating promises, global state, bindings, secrets, observability, `wrangler.jsonc` configuration. | | [`agents-sdk`](https://github.com/cloudflare/skills/tree/main/skills/agents-sdk) | Building on the Cloudflare Agents SDK: stateful agents, Workflows integration, scheduled tasks, MCP servers. Relevant to [`examples/think`](../../../examples/think). | @@ -39,7 +39,7 @@ Load these when their trigger applies: |---|---| | [`wrangler`](https://github.com/cloudflare/skills/tree/main/skills/wrangler) | Running `wrangler` commands: deploy, dev, secrets, bindings for KV, R2, D1, Vectorize, Hyperdrive, Queues, Workflows, Containers. | | [`cloudflare`](https://github.com/cloudflare/skills/tree/main/skills/cloudflare) | General Cloudflare platform questions outside the more specific skills above — KV, R2, D1, Vectorize, networking, security, infrastructure-as-code. | -| [`sandbox-sdk`](https://github.com/cloudflare/skills/tree/main/skills/sandbox-sdk) | Building or reviewing sandboxed-execution code paths. Relevant to [`examples/container`](../../../examples/container) and to the `wsd` container model in general. | +| [`sandbox-sdk`](https://github.com/cloudflare/skills/tree/main/skills/sandbox-sdk) | Building or reviewing sandboxed-execution code paths. Relevant to [`examples/container`](../../../examples/container) and to the `computerd` container model in general. | | [`web-perf`](https://github.com/cloudflare/skills/tree/main/skills/web-perf) | Profiling page load, Core Web Vitals, or render-blocking issues. Rarely relevant in this repo, but listed for completeness. | | [`cloudflare-email-service`](https://github.com/cloudflare/skills/tree/main/skills/cloudflare-email-service) | Working with Cloudflare Email Routing or the Email Workers binding. Not currently used in this repo. | @@ -50,12 +50,12 @@ Load these when their trigger applies: before changing storage shape, RPC methods, or alarm handling. Load [`workers-best-practices`](https://github.com/cloudflare/skills/tree/main/skills/workers-best-practices) for the surrounding Worker glue. -- **`packages/workspace`** runs inside the Durable Object and - exposes the capnweb `WorkspaceRPC` to `wsd`. Load +- **`packages/computer`** runs inside the Durable Object and + exposes the capnweb `WorkspaceRPC` to `computerd`. Load [`durable-objects`](https://github.com/cloudflare/skills/tree/main/skills/durable-objects) for the hosting model and the [`capnweb`](../capnweb/SKILL.md) skill for the RPC surface itself. -- **`packages/wsd`** runs in a sandbox container, not in a Worker. +- **`packages/computerd`** runs in a sandbox container, not in a Worker. Load [`sandbox-sdk`](https://github.com/cloudflare/skills/tree/main/skills/sandbox-sdk) if you're working on the container boundary; the FUSE and HTTP/WebSocket internals are local to the package. diff --git a/.agents/skills/debugging-wsd-fuse/SKILL.md b/.agents/skills/debugging-computerd-fuse/SKILL.md similarity index 72% rename from .agents/skills/debugging-wsd-fuse/SKILL.md rename to .agents/skills/debugging-computerd-fuse/SKILL.md index 5a039f45..c609979d 100644 --- a/.agents/skills/debugging-wsd-fuse/SKILL.md +++ b/.agents/skills/debugging-computerd-fuse/SKILL.md @@ -1,11 +1,11 @@ --- -name: debugging-wsd-fuse -description: Debug wsd in real-FUSE mode end-to-end without workerd, vitest-pool-workers, or wrangler in the loop. Boot the linux-x64 binary in a privileged docker container, drive its capnweb /ws endpoint from Node, simulate DO-side sync from a SQLiteTestStorage, and isolate FUSE-related deadlocks. Load when a real-FUSE bug reproduces locally but unit tests pass, when the harness vitest tests hang against a real container, or when you need to attribute a wedge to FUSE vs sync vs exec. +name: debugging-computerd-fuse +description: Debug computerd in real-FUSE mode end-to-end without workerd, vitest-pool-workers, or wrangler in the loop. Boot the linux-x64 binary in a privileged docker container, drive its capnweb /ws endpoint from Node, simulate DO-side sync from a SQLiteTestStorage, and isolate FUSE-related deadlocks. Load when a real-FUSE bug reproduces locally but unit tests pass, when the harness vitest tests hang against a real container, or when you need to attribute a wedge to FUSE vs sync vs exec. --- -# Debugging wsd against real FUSE +# Debugging computerd against real FUSE -This skill captures the recipe for isolating bugs in wsd that only +This skill captures the recipe for isolating bugs in computerd that only fire when the kernel-FUSE backend is the one wired up. Most unit tests run against `SQLiteTestStorage` and an in-process VFS or the userspace `FUSE_MOUNT=shim` polling driver — neither exercises the @@ -15,21 +15,21 @@ have shipped to `next` and not been caught by the regular suites because the harness is hard to drive end-to-end from a dev box. The workflow here bypasses workerd, wrangler, and the -vitest-pool-workers harness. It boots the wsd SEA binary in a real +vitest-pool-workers harness. It boots the computerd SEA binary in a real docker container with `/dev/fuse` and SYS_ADMIN, and drives its capnweb endpoints from a plain Node script. ## When to reach for this - A user report of "wrote a file via FUSE, RPC reads see 0 bytes." -- A user report of "exec hangs forever" or "wsd `/health` stops +- A user report of "exec hangs forever" or "computerd `/health` stops responding after the first shell call." -- Adding a new wsd op (FUSE callback, sync RPC, runner feature) +- Adding a new computerd op (FUSE callback, sync RPC, runner feature) and you want a smoke test that doesn't depend on Docker-in-DO or Cloudflare Containers. -- The harness vitest suite (`packages/workspace/src/test-harness/`) +- The harness vitest suite (`packages/computer/src/test-harness/`) hangs at `ws.shell.exec` against the docker container and you - need to attribute the wedge to wsd vs workerd vs the network + need to attribute the wedge to computerd vs workerd vs the network shim. - Confirming a fix actually settles in the deployed binary: the SEA build is what wrangler ships, and reproducing locally with @@ -41,14 +41,14 @@ capnweb endpoints from a plain Node script. sandbox this works through DinD; on a Linux dev box it works natively; on macOS you need `colima start --vm-type=vz` (the default qemu vm doesn't surface `/dev/fuse`). -- A fresh wsd binary at `artifacts/wsd/wsd-linux-x64`. Build it - with `npm run build:bin --workspace @cloudflare/workspace-wsd`, +- A fresh computerd binary at `artifacts/computerd/computerd-linux-x64`. Build it + with `npm run build:bin --workspace @cloudflare/computerd`, or just `npm run build:all` from the repo root if you need the docker image too. -## Boot a real-FUSE wsd container +## Boot a real-FUSE computerd container -There's already a recipe — `packages/workspace/test-harness/run-wsd.sh`. +There's already a recipe — `packages/computer/test-harness/run-computerd.sh`. It picks a host port, runs the binary with `--privileged --device /dev/fuse --cap-add SYS_ADMIN --cap-add MKNOD`, installs fuse3 + libfuse2t64 from apt, waits for `/health`, prints the URL @@ -56,14 +56,14 @@ on stdout and the container id on stderr. ```bash # Start -WSD_HARNESS_PORT=18080 bash packages/workspace/test-harness/run-wsd.sh +COMPUTERD_HARNESS_PORT=18080 bash packages/computer/test-harness/run-computerd.sh # prints: http://127.0.0.1:18080 on stdout -# prints: WSD_HARNESS_CID= on stderr +# prints: COMPUTERD_HARNESS_CID= on stderr # Verify curl -sf http://127.0.0.1:18080/health docker logs | tail -# should see: wsd listening on 0.0.0.0:8080 mount=/workspace backend=linux +# should see: computerd listening on 0.0.0.0:8080 mount=/workspace backend=linux # Tear down docker kill @@ -72,8 +72,8 @@ docker kill In a tmux session: ```bash -tmux new-session -d -s wsd \ - "WSD_HARNESS_PORT=18080 bash packages/workspace/test-harness/run-wsd.sh; sleep 600" +tmux new-session -d -s computerd \ + "COMPUTERD_HARNESS_PORT=18080 bash packages/computer/test-harness/run-computerd.sh; sleep 600" for i in 1 2 3 4 5; do sleep 5 if curl -sf http://127.0.0.1:18080/health 2>/dev/null; then echo ready; break; fi @@ -85,24 +85,24 @@ the FUSE mount didn't take and you're testing the wrong path. `backend=none` means `FUSE_MOUNT=none` was set; `FUSE_MOUNT=fuse` with a missing `/dev/fuse` would have failed startup outright. -## Drive wsd from a Node script +## Drive computerd from a Node script -wsd serves a composite `WorkspaceRPC` over `/ws` (capnweb WebSocket) -and `/api` (capnweb HTTP batch). The `@cloudflare/workspace-rpc/client` +computerd serves a composite `WorkspaceRPC` over `/ws` (capnweb WebSocket) +and `/api` (capnweb HTTP batch). The `@cloudflare/computer-rpc/client` package wraps the WS form and the `/driver` subpath exposes `pushOnce`/`pullOnce` against a Node-side `Database`. Set up a probe project once: ```bash -mkdir -p /tmp/wsd-probe && cd /tmp/wsd-probe +mkdir -p /tmp/computerd-probe && cd /tmp/computerd-probe cat > package.json <<'EOF' { "type": "module", "private": true, "dependencies": { "@cloudflare/dofs": "file:/workspace/packages/dofs", - "@cloudflare/workspace-rpc": "file:/workspace/packages/rpc", + "@cloudflare/computer-rpc": "file:/workspace/packages/rpc", "ws": "^8.18.0" } } @@ -113,16 +113,16 @@ npm install --ignore-scripts --no-audit --no-fund Now you can write probes that hold both sides of the wire. The canonical shape, with the WebSocket impl pinned to the `ws` package (Node's built-in `WebSocket` doesn't negotiate the -permessage-deflate extension that wsd advertises): +permessage-deflate extension that computerd advertises): ```js import { Database, WorkspaceFilesystem, initializeSchema } from "@cloudflare/dofs"; import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; -import { createWorkspaceClient } from "@cloudflare/workspace-rpc/client"; -import { pullOnce, pushOnce } from "@cloudflare/workspace-rpc/driver"; +import { createWorkspaceClient } from "@cloudflare/computer-rpc/client"; +import { pullOnce, pushOnce } from "@cloudflare/computer-rpc/driver"; import { WebSocket } from "ws"; -const url = process.env.WSD_URL; // e.g. http://127.0.0.1:18080 +const url = process.env.COMPUTERD_URL; // e.g. http://127.0.0.1:18080 const wsUrl = `${url.replace(/^http(s?):\/\//, "ws$1://")}/ws`; const storage = new SQLiteTestStorage(); @@ -131,8 +131,8 @@ initializeSchema(db, () => Date.now()); const fs = new WorkspaceFilesystem(db, { now: () => Date.now() }); // Stage some local state. IMPORTANT: paths must be inside the -// container's MOUNT_POINT (default /workspace) or wsd's FUSE -// driver won't surface them on the host — wsd serves only the +// container's MOUNT_POINT (default /workspace) or computerd's FUSE +// driver won't surface them on the host — computerd serves only the // `/workspace/...` subtree of vfs_nodes through the kernel mount. await fs.mkdir("/workspace", { recursive: true }); await fs.writeFile("/workspace/probe.txt", "hello from probe"); @@ -148,7 +148,7 @@ try { } ``` -Run with `WSD_URL=http://127.0.0.1:18080 node probe.mjs`. +Run with `COMPUTERD_URL=http://127.0.0.1:18080 node probe.mjs`. To verify the push actually landed on the FUSE side: @@ -163,22 +163,22 @@ them. Check that: - Your absolute paths start with `MOUNT_POINT` (default `/workspace`). Writing to `/probe.txt` succeeds at the RPC layer but FUSE only serves the `MOUNT_POINT` subtree. -- wsd actually logs `backend=linux` (not `backend=shim`). +- computerd actually logs `backend=linux` (not `backend=shim`). - You aren't reading the wrong container — `docker ps` after a zombie `docker rm -f` can show stale entries. ## Drive container-side writes The cleanest way to simulate a container-side FUSE write — the -exact path wsd was designed to handle — is `docker exec` into the +exact path computerd was designed to handle — is `docker exec` into the running container: ```bash docker exec sh -c 'echo container-write > /workspace/r2/hello.txt' ``` -This is a real FUSE write: kernel → fuse-native → wsd's -`writeBuf` op → wsd's in-memory buffer for that file → on +This is a real FUSE write: kernel → fuse-native → computerd's +`writeBuf` op → computerd's in-memory buffer for that file → on `release`/`flush`/`fsync` the buffer spills into the backing VFS (commit `68407fc`). On the host side you can then `pullOnce` and see the new entry land in `applyChanges`. @@ -188,7 +188,7 @@ environment.** It calls `spawn` with `cwd=MOUNT_POINT` by default, which can deadlock under real FUSE (see "Known deadlock patterns" below). If you need exec-shaped behaviour from outside the container, `docker exec` is closer to what wrangler's container -runtime would do anyway — wsd isn't the parent of the spawned +runtime would do anyway — computerd isn't the parent of the spawned process. ## Known deadlock patterns @@ -198,7 +198,7 @@ process. Symptom: container `cat /workspace/foo` returns the bytes you wrote, but a host-side `pullOnce` sees the file at size 0. -Mechanism: the FUSE driver in `packages/wsd/src/fuse/driver.ts` +Mechanism: the FUSE driver in `packages/computerd/src/fuse/driver.ts` buffers writes in an in-memory `files` Map keyed by path. Reads through FUSE pull from that buffer, but the backing VFS only sees the empty inode the `create` op registered. If `release` / `flush` @@ -207,13 +207,13 @@ the empty inode the `create` op registered. If `release` / `flush` Fix shipped: commit `68407fc` — `flushEntry(path)` runs on each of those ops. -Test for it: `packages/wsd/src/fuse/driver.test.ts` and the +Test for it: `packages/computerd/src/fuse/driver.test.ts` and the companion test at `8111d68`. Mirror that shape if you find a new buffer-vs-VFS desync. -### wsd's event loop blocked in spawn +### computerd's event loop blocked in spawn -Symptom: `client.shell.exec` against a real-FUSE wsd hangs +Symptom: `client.shell.exec` against a real-FUSE computerd hangs forever. `/health` stops responding. `docker exec ls /workspace` also hangs once the deadlock fires. @@ -221,14 +221,14 @@ Mechanism: `Runner.exec` calls `spawn("/bin/sh", { cwd: "/workspace" })`. libuv's `uv_spawn` forks and waits on a status pipe for the child to exec. The child, between fork and exec, does `chdir("/workspace")`. The -kernel issues a FUSE `LOOKUP` against the wsd mount. The callback -needs to run on wsd's event loop, which is blocked in the pipe +kernel issues a FUSE `LOOKUP` against the computerd mount. The callback +needs to run on computerd's event loop, which is blocked in the pipe read. Deadlock. Diagnostic: ```js -// against a fresh wsd +// against a fresh computerd const t = Date.now(); await client.shell.exec({ command: "echo hi", cwd: "/tmp", timeoutMs: 5000 }); // returns in <100ms — control case @@ -236,23 +236,23 @@ await client.shell.exec({ command: "echo hi", cwd: "/workspace", timeoutMs: 5000 // hangs forever — under real FUSE ``` -Same wsd, same client, same network — only `cwd` differs. If +Same computerd, same client, same network — only `cwd` differs. If that flips success→hang, you've reproduced the deadlock. Fix: do the chdir inside the **shell**, not inside `spawn`'s fork-and-exec dance. Prefix the command with `cd ... && exec ...` -so the chdir runs after the shell is up and wsd's event loop is +so the chdir runs after the shell is up and computerd's event loop is responsive again. Pre-flight existence via dofs's `stat(db, path)` (reads SQLite directly, no FUSE callbacks) to preserve the existing ENOENT-cwd error contract. ### Other event-loop blockers to suspect -Any wsd code path that does sync work against +Any computerd code path that does sync work against `MOUNT_POINT`/the FUSE mount can wedge: `fs.statSync(cwd)`, `fs.readdirSync(MOUNT_POINT)`, `fs.realpathSync(...)` — all synchronous, all block the event loop, all issue FUSE callbacks -to wsd itself. Rule of thumb when adding wsd code: anything that +to computerd itself. Rule of thumb when adding computerd code: anything that goes through Node's `fs` against the mount point must be async (so libuv's threadpool services the call) OR must read the dofs `Database` / VFS directly (pure SQL, no FUSE involvement). @@ -261,7 +261,7 @@ goes through Node's `fs` against the mount point must be async ### Reset between probes -A wedged wsd container can leave fuse-native zombie processes +A wedged computerd container can leave fuse-native zombie processes that `docker kill` refuses to reap. The cleanest fix: ```bash @@ -269,7 +269,7 @@ that `docker kill` refuses to reap. The cleanest fix: docker kill 2>&1 # If it complains about a PID being a zombie, just abandon that # port and pick a new one -WSD_HARNESS_PORT=18081 bash packages/workspace/test-harness/run-wsd.sh +COMPUTERD_HARNESS_PORT=18081 bash packages/computer/test-harness/run-computerd.sh ``` The zombie containers don't hold the host port once docker has @@ -331,7 +331,7 @@ db.run("UPDATE _vfs_mounts SET mode = 'read-only', indexed = 1 WHERE root = ?", invalidateReadOnlyMountCache(db); ``` -After staging this and `pushOnce`'ing to wsd, the container's +After staging this and `pushOnce`'ing to computerd, the container's FUSE view will surface `/workspace/r2/hello.txt`. Container-side writes to that subtree will propagate back via the next `pullOnce`, where `applyChanges` skips them with @@ -342,7 +342,7 @@ writes to that subtree will propagate back via the next A working end-to-end probe that stages the mount, pushes, container-writes via `docker exec`, pulls, and asserts the expected applied/skipped split lives at -`script/wsd-mount-probe.mjs` if it's still in the tree. Use it as +`script/computerd-mount-probe.mjs` if it's still in the tree. Use it as the template for new probes. ## What this skill is NOT @@ -356,18 +356,18 @@ the template for new probes. ## When to escalate -If you've isolated a wedge to wsd's event loop and the fix isn't +If you've isolated a wedge to computerd's event loop and the fix isn't obvious from the patterns above, the next step is `node ---inspect-brk` against the wsd binary. The SEA bundle preserves +--inspect-brk` against the computerd binary. The SEA bundle preserves source maps for the bundled JS, so breakpoints in -`packages/wsd/src/exec/runner.ts` resolve. Run wsd outside docker +`packages/computerd/src/exec/runner.ts` resolve. Run computerd outside docker (it'll fall back to `backend=shim` or `backend=none` depending on your host) so the inspector port is reachable, or `docker run --publish 9229:9229 ...` and pass `--inspect-brk=0.0.0.0:9229` -through wsd's launch env. +through computerd's launch env. If even that doesn't surface it, the wedge is probably below the JS layer — in fuse-native or libuv. At that point reach for -`strace -fp ` inside the container; the FUSE callbacks +`strace -fp ` inside the container; the FUSE callbacks all surface as `read(/dev/fuse, ...)` and the per-call timing will show you exactly which op deadlocks. diff --git a/.agents/skills/prose/SKILL.md b/.agents/skills/prose/SKILL.md index e8202415..c32cd9fc 100644 --- a/.agents/skills/prose/SKILL.md +++ b/.agents/skills/prose/SKILL.md @@ -116,8 +116,8 @@ Signed-off-by: Name The capitalization rule applies to the part **after** the scope prefix colon. Lowercase scope prefixes are preserved when they - match the project convention: `wsd:`, `rpc:`, `examples/think:`, - `wsd, rpc:`, `docs:`. + match the project convention: `computerd:`, `rpc:`, `examples/think:`, + `computerd, rpc:`, `docs:`. 2. **Blank line** between subject and body. The body is wrapped at 72 characters per line. @@ -245,9 +245,9 @@ Set the field to false and document the reasoning inline. ### Before ``` -wsd, rpc: tests for the FUSE_SHIM flush hook +computerd, rpc: tests for the FUSE_SHIM flush hook -Two complementary test pairs for bc9827b. The wsd side proves the +Two complementary test pairs for bc9827b. The computerd side proves the shim materialises on demand without relying on its 250ms poll ... shim.test.ts: @@ -263,14 +263,14 @@ file-name headings), rule 4 (`handy for`), rule 10 (`materialises`). ### After ``` -wsd, rpc: test the FUSE shim flush hook +computerd, rpc: test the FUSE shim flush hook -Cover the flush hook from both ends. The wsd side proves the shim +Cover the flush hook from both ends. The computerd side proves the shim materializes on demand without relying on its 250ms poll; the rpc side proves SyncRPC fires afterApply exactly when it should and tolerates a hook that throws. -On the wsd side, flush() materializes pending VFS writes onto disk +On the computerd side, flush() materializes pending VFS writes onto disk before resolving, is idempotent against an unchanged tree, and is safe to call on an unmounted shim. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d806caaf..522416bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ # Per-package + per-example check matrix. One job per workspace so a -# regression in (say) wsd's tests doesn't hide a typecheck failure in +# regression in (say) computerd's tests doesn't hide a typecheck failure in # workspace. Each job is self-contained: install at the repo root, # build siblings whose dist/ this package's typecheck depends on, # then run biome + typecheck + tests scoped to that package. @@ -50,16 +50,16 @@ jobs: path: packages/dofs needs-siblings: false - name: rpc - workspace: "@cloudflare/workspace-rpc" + workspace: "@cloudflare/computer-rpc" path: packages/rpc needs-siblings: true - - name: workspace - workspace: "@cloudflare/workspace" - path: packages/workspace + - name: computer + workspace: "@cloudflare/computer" + path: packages/computer needs-siblings: true - - name: wsd - workspace: "@cloudflare/workspace-wsd" - path: packages/wsd + - name: computerd + workspace: "@cloudflare/computerd" + path: packages/computerd needs-siblings: true # libfuse2 (libfuse.so.2) is what fuse-native binds to at # runtime; fuse3 ships /dev/fuse helpers and the @@ -78,11 +78,11 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends ${{ matrix.system-deps }} - # Build every workspace's dist/ unconditionally for now. The - # workspace + wsd jobs need dofs + rpc dist, the rpc job needs + # Build every npm workspace's dist/ unconditionally for now. The + # computer + computerd jobs need dofs + rpc dist, the rpc job needs # dofs dist, and the cost of an extra `tsc` pass is small # compared to the test runtime. Revisit per-package once - # @cloudflare/workspace bundles dofs + rpc internally. + # @cloudflare/computer bundles dofs + rpc internally. - name: Build all workspaces if: matrix.needs-siblings run: npm run build --workspaces --if-present @@ -112,7 +112,7 @@ jobs: workspace: "@cloudflare/example-think" path: examples/think - name: container - workspace: "@example/workspace-container" + workspace: "@example/computer-container" path: examples/container steps: - uses: actions/checkout@v6 @@ -121,9 +121,9 @@ jobs: - uses: ./.github/actions/install - # Examples consume the workspace packages via the - # local symlink, so the workspace's dist/ has to exist before - # the example's typecheck resolves its imports. + # Examples consume the monorepo packages via local symlinks, so + # @cloudflare/computer's dist/ has to exist before the example's + # typecheck resolves its imports. - run: npm run build --workspaces --if-present - name: Generate worker types @@ -157,4 +157,4 @@ jobs: - run: npm run build --workspaces --if-present - - run: npx pkg-pr-new publish --peerDeps ./packages/workspace + - run: npx pkg-pr-new publish --peerDeps ./packages/computer diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d94af890..b745aa9e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,28 +2,28 @@ # # 1. Builds every workspace. # 2. Runs all package tests. -# 3. Builds the wsd linux-x64 SEA binary (build-only — proves the +# 3. Builds the computerd linux-x64 SEA binary (build-only — proves the # release tag still produces a working binary; not published). # 4. Stamps the tag's version into the publishable package. -# 5. Publishes @cloudflare/workspace under a dist-tag picked from +# 5. Publishes @cloudflare/computer under a dist-tag picked from # the version: prerelease tags (`-alpha.N`, `-beta.N`, `-rc.N`) # publish under the matching suffix (`alpha`, `beta`, `rc`) -# so plain `npm i @cloudflare/workspace` doesn't pick them up; +# so plain `npm i @cloudflare/computer` doesn't pick them up; # stable tags (no suffix) publish under `latest`. -# 6. Pushes the wsd binary image to two registries: ghcr.io (the +# 6. Pushes the computerd binary image to two registries: ghcr.io (the # canonical public source the example Dockerfiles reference) # and registry.cloudflare.com (so wrangler / Containers can # pull from inside Cloudflare's platform without an outbound # hop). Stable releases also move each registry's `:latest` # tag; prereleases only push the version-specific tag. # -# The wsd binary is shipped two ways: -# - inside the @cloudflare/workspace npm tarball at dist/bin/ -# wsd-linux-x64, so consumers installing the package get it for +# The computerd binary is shipped two ways: +# - inside the @cloudflare/computer npm tarball at dist/bin/ +# computerd-linux-x64, so consumers installing the package get it for # free; and # - as a single-layer container image, mirrored to both -# ghcr.io/cloudflare/workspace-wsd-linux-x64 and -# registry.cloudflare.com/library/workspace-wsd-linux-x64. The +# ghcr.io/cloudflare/computer-computerd-linux-x64 and +# registry.cloudflare.com/library/computer-computerd-linux-x64. The # example Dockerfiles pin the GHCR coordinate (anonymous pulls, # no Cloudflare-account dependency); the CF copy is kept for # consumers that prefer to stay on the in-platform registry. @@ -43,7 +43,7 @@ # # One manual step is needed once, after the first push of the GHCR # package: flip the package visibility to Public at -# https://github.com/orgs/cloudflare/packages/container/workspace-wsd-linux-x64/settings +# https://github.com/orgs/cloudflare/packages/container/computer-computerd-linux-x64/settings # Subsequent pushes inherit the public visibility automatically. # # Triggers: @@ -98,9 +98,9 @@ jobs: with: registry-url: "https://registry.npmjs.org" - # wsd's tests spawn the binary, which dynamically links + # computerd's tests spawn the binary, which dynamically links # libfuse2 (fuse-native) and uses fuse3 helpers for the - # FUSE_MOUNT=shim userspace mount path. Without these the wsd test + # FUSE_MOUNT=shim userspace mount path. Without these the computerd test # suite fails with `libfuse.so.2: cannot open shared object # file`. Mirrors the install in ci.yml. - name: Install system dependencies @@ -114,20 +114,20 @@ jobs: - name: Test run: npm test --workspaces --if-present - - name: Build wsd binary - run: npm run build:bin --workspace @cloudflare/workspace-wsd + - name: Build computerd binary + run: npm run build:bin --workspace @cloudflare/computerd - # Short-term bundling: ship the linux-x64 wsd binary inside - # @cloudflare/workspace under dist/bin/ — not registered as a + # Short-term bundling: ship the linux-x64 computerd binary inside + # @cloudflare/computer under dist/bin/ — not registered as a # "bin" entry, so npm install won't try to symlink it on # non-linux hosts. Docker workflows reach in and copy it out # explicitly. Replace with a proper platform-package install # once we have more than one target. - - name: Stage binary into @cloudflare/workspace + - name: Stage binary into @cloudflare/computer run: | - mkdir -p packages/workspace/dist/bin - cp artifacts/wsd/wsd-linux-x64 packages/workspace/dist/bin/wsd-linux-x64 - chmod +x packages/workspace/dist/bin/wsd-linux-x64 + mkdir -p packages/computer/dist/bin + cp artifacts/computerd/computerd-linux-x64 packages/computer/dist/bin/computerd-linux-x64 + chmod +x packages/computer/dist/bin/computerd-linux-x64 - name: Sync versions run: node script/set-versions.mjs "${RELEASE_TAG}" @@ -154,18 +154,18 @@ jobs: echo "npm_tag=${npm_tag}" >> "$GITHUB_OUTPUT" echo "is_stable=${is_stable}" >> "$GITHUB_OUTPUT" - - name: Publish @cloudflare/workspace - run: npm publish --workspace @cloudflare/workspace --tag "${{ steps.release-meta.outputs.npm_tag }}" --provenance --access public + - name: Publish @cloudflare/computer + run: npm publish --workspace @cloudflare/computer --tag "${{ steps.release-meta.outputs.npm_tag }}" --provenance --access public - # Stage the SEA binary into the wsd-linux-x64 package's bin/ - # directory — that package's Dockerfile copies from `bin/wsd` + # Stage the SEA binary into the computerd-linux-x64 package's bin/ + # directory — that package's Dockerfile copies from `bin/computerd` # against a `scratch` base, producing a single-layer image # whose only purpose is to ship the binary at a known path. - - name: Stage binary into @cloudflare/workspace-wsd-linux-x64 + - name: Stage binary into @cloudflare/computer-computerd-linux-x64 run: | - mkdir -p packages/wsd-linux-x64/bin - cp artifacts/wsd/wsd-linux-x64 packages/wsd-linux-x64/bin/wsd - chmod +x packages/wsd-linux-x64/bin/wsd + mkdir -p packages/computer-computerd-linux-x64/bin + cp artifacts/computerd/computerd-linux-x64 packages/computer-computerd-linux-x64/bin/computerd + chmod +x packages/computer-computerd-linux-x64/bin/computerd - name: Set up buildx uses: docker/setup-buildx-action@v3 @@ -195,16 +195,16 @@ jobs: # trailing lines in the `tags` list are tolerated by # build-push-action. One buildx invocation pushes to both # registries in parallel, sharing the layer build. - - name: Build and push wsd-linux-x64 image + - name: Build and push computerd-linux-x64 image uses: docker/build-push-action@v6 with: - context: packages/wsd-linux-x64 - file: packages/wsd-linux-x64/Dockerfile + context: packages/computer-computerd-linux-x64 + file: packages/computer-computerd-linux-x64/Dockerfile platforms: linux/amd64 push: true tags: | - ghcr.io/cloudflare/workspace-wsd-linux-x64:${{ steps.release-meta.outputs.version }} - ${{ steps.release-meta.outputs.is_stable == 'true' && 'ghcr.io/cloudflare/workspace-wsd-linux-x64:latest' || '' }} - registry.cloudflare.com/library/workspace-wsd-linux-x64:${{ steps.release-meta.outputs.version }} - ${{ steps.release-meta.outputs.is_stable == 'true' && 'registry.cloudflare.com/library/workspace-wsd-linux-x64:latest' || '' }} + ghcr.io/cloudflare/computer-computerd-linux-x64:${{ steps.release-meta.outputs.version }} + ${{ steps.release-meta.outputs.is_stable == 'true' && 'ghcr.io/cloudflare/computer-computerd-linux-x64:latest' || '' }} + registry.cloudflare.com/library/computer-computerd-linux-x64:${{ steps.release-meta.outputs.version }} + ${{ steps.release-meta.outputs.is_stable == 'true' && 'registry.cloudflare.com/library/computer-computerd-linux-x64:latest' || '' }} provenance: false diff --git a/.gitignore b/.gitignore index 4d2d513a..8bf20f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,19 @@ node_modules/ dist/ PLAN.md -# Build output at the repo root only. The @cloudflare/workspace -# Artifacts source tree (packages/workspace/src/artifacts/) is real, +# Build output at the repo root only. The @cloudflare/computer +# Artifacts source tree (packages/computer/src/artifacts/) is real, # tracked code and must not be caught by this rule. /artifacts/ # Generated bundle for the worker backend's ShellWorker. Built -# by packages/workspace/src/backends/worker/script/build-bundle.mjs +# by packages/computer/src/backends/worker/script/build-bundle.mjs # on prepare / pretest / pretypecheck. -packages/workspace/src/backends/worker/generated-bundle.ts +packages/computer/src/backends/worker/generated-bundle.ts # SEA binary destinations populated at publish time from -# artifacts/wsd/ via the build-bin step. The @cloudflare/workspace +# artifacts/computerd/ via the build-bin step. The @cloudflare/computer # copy lives under dist/bin/, which is already covered by the # blanket dist/ rule above; listed here for discoverability. -packages/wsd-*/bin/wsd -packages/workspace/dist/bin/wsd-* +packages/computer-computerd-*/bin/computerd +packages/computer/dist/bin/computerd-* diff --git a/AGENTS.md b/AGENTS.md index 81d2fd50..bfb688bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,8 @@ - [`docs/README.md`](docs/README.md) — design specification. Forward- looking; treat as intent, not as a description of `main`. - [`docs/08_capnweb_interface.md`](docs/08_capnweb_interface.md) — - the RPC contract between the Durable Object and `wsd`. Required - reading before touching `packages/rpc` or `packages/workspace`. + the RPC contract between the Durable Object and `computerd`. Required + reading before touching `packages/rpc` or `packages/computer`. - Each package's own `README.md` — implementation status and package-specific notes. @@ -25,7 +25,7 @@ the file directly when the trigger applies: | [`prose`](.agents/skills/prose/SKILL.md) | Writing code comments, commit messages, READMEs, or documentation. | | [`pull-requests`](.agents/skills/pull-requests/SKILL.md) | Writing or editing a pull request description. | | [`test-driven-development`](.agents/skills/test-driven-development/SKILL.md) | Implementing logic, fixing a bug, or changing behavior. | -| [`capnweb`](.agents/skills/capnweb/SKILL.md) | Touching anything that crosses the RPC boundary: `packages/rpc`, `packages/workspace`, the `wsd` client, or the Durable Object server. | +| [`capnweb`](.agents/skills/capnweb/SKILL.md) | Touching anything that crosses the RPC boundary: `packages/rpc`, `packages/computer`, the `computerd` client, or the Durable Object server. | | [`cloudflare`](.agents/skills/cloudflare/SKILL.md) | Index of host-side Cloudflare skills — Workers, Durable Objects, wrangler, sandbox SDK, agents SDK. | ## Environment setup @@ -33,7 +33,7 @@ the file directly when the trigger applies: A fresh container does not have everything the tests need. The traps below cost real time if you discover them one failure at a time. -**Native build tools.** `packages/wsd` depends on `fuse-native`, a +**Native build tools.** `packages/computerd` depends on `fuse-native`, a native addon. Building it needs a C toolchain and the libfuse2 headers. On Debian or Ubuntu: @@ -61,15 +61,15 @@ cd node_modules/fuse-native && npx node-gyp rebuild **Build before you test.** The test scripts don't build the sibling packages first. Several suites need build output that is absent in -a clean checkout: `packages/wsd` imports the sibling `@cloudflare/dofs` -and `@cloudflare/workspace-rpc` packages from their `dist/` -directories, `packages/wsd`'s `src/cli/wsd.test.ts` spawns the bundled -CLI at `dist/cli/wsd.cjs`, and `examples/think-compare-runtimes` -imports `@cloudflare/workspace/backends/container`, which exists only -after the `workspace` package is built. Run `npm run build` across the -workspace before `npm test` on a clean checkout. - -**Real FUSE needs privilege.** `packages/wsd`'s `src/cli/wsd.test.ts` +a clean checkout: `packages/computerd` imports the sibling `@cloudflare/dofs` +and `@cloudflare/computer-rpc` packages from their `dist/` +directories, `packages/computerd`'s `src/cli/computerd.test.ts` spawns the bundled +CLI at `dist/cli/computerd.cjs`, and `examples/think-compare-runtimes` +imports `@cloudflare/computer/backends/container`, which exists only +after the `computer` package is built. Run `npm run build` across the +npm workspace before `npm test` on a clean checkout. + +**Real FUSE needs privilege.** `packages/computerd`'s `src/cli/computerd.test.ts` runs its real-FUSE case only when `/dev/fuse` is reachable; otherwise it resolves to the shim and skips. The guard is a bare existence check, so a `mknod`'d `/dev/fuse` in an unprivileged container defeats the @@ -77,9 +77,9 @@ skip and the mount then fails with `EPERM`, turning a clean skip into a hard failure. Leave the device absent unless the container is privileged (`--privileged`, or `CAP_SYS_ADMIN` with device access). The `src/exec/runner.fuse.test.ts` suite is separate: it skips unless both -Docker and the prebuilt `wsd` binary are available, and runs `wsd` +Docker and the prebuilt `computerd` binary are available, and runs `computerd` inside a privileged container. See the -[`debugging-wsd-fuse`](.agents/skills/debugging-wsd-fuse/SKILL.md) skill +[`debugging-computerd-fuse`](.agents/skills/debugging-computerd-fuse/SKILL.md) skill for the privileged Docker setup. ## Checks before you finish @@ -110,8 +110,8 @@ Full details, including typecheck and build commands, are in Follow [`CONTRIBUTING.md`](CONTRIBUTING.md). The short version: - One logical change per commit. -- Imperative subject prefixed with the scope (`dofs:`, `rpc:`, `wsd:`, - `workspace:`, `examples/think:`, `docs:`, `ci:`). Multiple scopes +- Imperative subject prefixed with the scope (`dofs:`, `rpc:`, `computer:`, + `computerd:`, `examples/think:`, `docs:`, `ci:`). Multiple scopes joined with commas. - Self-contained body wrapped at 72 characters. No references to chat history, agent sessions, sibling commit SHAs, or task identifiers. @@ -124,29 +124,29 @@ and [`.agents/skills/pull-requests/SKILL.md`](.agents/skills/pull-requests/SKILL ## Scripts Two top-level directories hold helper scripts. They're outside the -workspace package set on purpose — they're tooling, not shipped code. +npm workspace package set on purpose — they're tooling, not shipped code. -[`script/`](script/) holds maintainance scripts and operator-facing harnesses for `wsd` and the sync +[`script/`](script/) holds maintainance scripts and operator-facing harnesses for `computerd` and the sync loop. Reach for these when you're chasing a behavior the unit tests don't cover. - `set-versions.mjs` syncs the version of every published package in lockstep. Invoked from the release pipeline. -- `shell` boots a debian-slim container with the linux `wsd` binary +- `shell` boots a debian-slim container with the linux `computerd` binary mounted under `/usr/local/bin`. The starting point for anything that needs a real FUSE mount. -- `wsd-soak.mjs` boots two `wsd` containers wired peer-to-peer and +- `computerd-soak.mjs` boots two `computerd` containers wired peer-to-peer and soaks the sync loop. Use it to chase convergence or churn bugs. -- `wsd-stub-soak.mjs` soaks the long-lived WebSocket session and +- `computerd-stub-soak.mjs` soaks the long-lived WebSocket session and reads `session.getStats()` to detect stub-disposal drift. Run it for changes around the capnweb lifecycle. -- `wsd-fuse-flush.mjs` end-to-end checks that the FUSE driver spills +- `computerd-fuse-flush.mjs` end-to-end checks that the FUSE driver spills its in-memory write buffer into the backing VFS, so a capnweb-side `pullOnce` actually sees the bytes. - `fs-tests.sh` / `run-fs-tests.sh` run the filesystem conformance harness against the FUSE mount. - `fs-bench.sh` / `run-fs-bench.sh` benchmark common development tasks against the mount with a tmpfs baseline for comparison. -- `exec-tests` boots `wsd` in docker with FUSE disabled and exercises +- `exec-tests` boots `computerd` in docker with FUSE disabled and exercises a few `shell.exec` scenarios. - `npm-bench.sh` / `run-npm-bench.sh` benchmark npm package installs on native disk vs the FUSE mount. `run-npm-bench.sh` is the @@ -162,14 +162,14 @@ file and add it to the list above. - **RPC.** The wire contract is capnweb. Read [`docs/08_capnweb_interface.md`](docs/08_capnweb_interface.md) and the [`capnweb`](.agents/skills/capnweb/SKILL.md) skill before - changing anything that crosses the Durable Object ↔ `wsd` boundary. + changing anything that crosses the Durable Object ↔ `computerd` boundary. Stubs are object-capabilities; dispose them. Leaks are tracked by the harness in `packages/rpc`. - **Storage.** `packages/dofs` is the authoritative SQLite layer. Filesystem primitives operate on a `Database` handle; the sync protocol building blocks share the same handle. The package README enumerates the exported surface. -- **FUSE shim.** `packages/wsd` runs in the sandbox container. FUSE- +- **FUSE shim.** `packages/computerd` runs in the sandbox container. FUSE- backed tests only run on Linux and are skipped elsewhere automatically. - **Examples are real consumers.** `examples/think`, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa873d9b..e583b5ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -Thanks for working on the Cloudflare Workspace prototype. This document +Thanks for working on the Cloudflare Computer prototype. This document covers the day-to-day mechanics: how to set up the repo, how to run the checks, and how to shape commits and pull requests. @@ -11,17 +11,17 @@ which points at the in-repo skills under [`.agents/skills/`](.agents/skills/). Requirements: -- Node 22 or newer (`packages/wsd` declares `"engines": { "node": ">=22" }`). +- Node 22 or newer (`packages/computerd` declares `"engines": { "node": ">=22" }`). - npm — this repo uses npm workspaces, not pnpm or yarn. -- Linux with FUSE if you want to run `packages/wsd` end-to-end. The +- Linux with FUSE if you want to run `packages/computerd` end-to-end. The rest of the workspace builds and tests on macOS as well. - Docker, optionally, for `examples/container`. Clone and install from the repo root: ```bash -git clone https://github.com/cloudflare/workspace.git -cd workspace +git clone https://github.com/cloudflare/computer.git +cd computer npm install ``` @@ -38,11 +38,11 @@ with package-specific status and usage notes: filesystem, sync protocol building blocks, and a `@platformatic/vfs` provider for Node. - [`packages/rpc`](packages/rpc/) — capnweb-based wire types and - server/client helpers shared between the Durable Object and `wsd`. -- [`packages/wsd`](packages/wsd/) — the `wsd` daemon: a FUSE mount plus + server/client helpers shared between the Durable Object and `computerd`. +- [`packages/computerd`](packages/computerd/) — the `computerd` daemon: a FUSE mount plus HTTP/WebSocket RPC server that runs inside the sandbox container. -- [`packages/workspace`](packages/workspace/) — the top-level - `@cloudflare/workspace` package consumed by Durable Objects. +- [`packages/computer`](packages/computer/) — the top-level + `@cloudflare/computer` package consumed by Durable Objects. [`docs/`](docs/) holds the design specification. It is forward-looking and has diverged from `main` in places — treat it as intent, not as a @@ -94,7 +94,7 @@ For a single test file inside a package: npm test --workspace @cloudflare/dofs -- src/path/to/file.test.ts ``` -`packages/wsd` includes FUSE-backed tests that only run on Linux. On +`packages/computerd` includes FUSE-backed tests that only run on Linux. On other platforms they're skipped automatically. New behavior needs a test. Bug fixes need a reproduction test that @@ -124,9 +124,9 @@ short version: - **Subject line.** Imperative mood, ≤ 50 characters where possible, 72 hard maximum. No trailing period. Prefix with the package or - scope: `dofs:`, `rpc:`, `wsd:`, `workspace:`, `examples/think:`, + scope: `dofs:`, `rpc:`, `computer:`, `computerd:`, `examples/think:`, `docs:`, `ci:`. Multiple scopes are joined with commas, as in - `wsd, rpc: …`. + `computerd, rpc: …`. - **Blank line**, then a body wrapped at 72 characters. Explain *what and why*, not *how* — the diff already shows how. - **One logical change per commit.** Don't bundle unrelated edits. diff --git a/README.md b/README.md index 21687582..323f6969 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Cloudflare Workspace +# Cloudflare Computer -Cloudflare Workspace is a virtual filesystem that lives inside a +Cloudflare Computer is a virtual filesystem that lives inside a Durable Object. The Durable Object holds the authoritative state in SQLite and exposes the filesystem to a shell through a pluggable backend. Two backends ship today: - **Container** projects the SQLite state into a sandbox container as - a real FUSE mount. A sandbox-side daemon (`wsd`) mounts the state + a real FUSE mount. A sandbox-side daemon (`computerd`) mounts the state as a filesystem and syncs changes back over a capnweb RPC channel. Full Linux userland, real binaries, real network. - **Worker** runs the shell as [just-bash](https://github.com/vercel-labs/just-bash) @@ -42,7 +42,7 @@ callers the filesystem on its own. - Node 22 or newer. - npm. This repo uses npm workspaces. -- Linux with FUSE if you want to run `packages/wsd` end-to-end. Other +- Linux with FUSE if you want to run `packages/computerd` end-to-end. Other packages build and test on macOS as well. - Docker, optionally, for [`examples/container`](examples/container). The worker example needs no Docker. @@ -50,8 +50,8 @@ callers the filesystem on its own. ## Quick start ```bash -git clone https://github.com/cloudflare/workspace.git -cd workspace +git clone https://github.com/cloudflare/computer.git +cd computer npm install npm run build:all npm test @@ -59,7 +59,7 @@ npm test To see the pieces working together, start with the examples: -- [`examples/container`](examples/container) — runs `wsd` inside a +- [`examples/container`](examples/container) — runs `computerd` inside a container, mounts a workspace, and talks to a Durable Object over capnweb. - [`examples/worker`](examples/worker) — same HTTP surface as the @@ -77,13 +77,13 @@ package-specific status and usage notes. Durable Object SQLite-backed virtual filesystem, sync protocol building blocks, and a `@platformatic/vfs` provider for Node. - [`packages/rpc`](packages/rpc/README.md) - (`@cloudflare/workspace-rpc`) — capnweb wire types and - server/client helpers shared between the Durable Object and `wsd`. -- [`packages/wsd`](packages/wsd/README.md) - (`@cloudflare/workspace-wsd`) — the `wsd` daemon: a FUSE mount plus + (`@cloudflare/computer-rpc`) — capnweb wire types and + server/client helpers shared between the Durable Object and `computerd`. +- [`packages/computerd`](packages/computerd/README.md) + (`@cloudflare/computerd`) — the `computerd` daemon: a FUSE mount plus HTTP/WebSocket RPC server that runs inside the sandbox container. -- [`packages/workspace`](packages/workspace/README.md) - (`@cloudflare/workspace`) — the top-level Workspace package +- [`packages/computer`](packages/computer/README.md) + (`@cloudflare/computer`) — the top-level Computer package consumed by Durable Objects. Work in progress. ## Performance @@ -91,18 +91,18 @@ package-specific status and usage notes. Numbers from `script/fs-bench.sh` and a full `npm install` of [`cloudflare/sandbox-sdk`](https://github.com/cloudflare/sandbox-sdk) (854 packages, 36,675 files), running -[`examples/wsd-container`](examples/wsd-container) on a Cloudflare +[`examples/container`](examples/container) on a Cloudflare Containers **standard-2** instance (1 vCPU, 6 GiB memory, 12 GB disk). -The wsd FUSE mount lives at `/workspace`; the comparison columns are +The computerd FUSE mount lives at `/workspace`; the comparison columns are an in-memory `tmpfs` at `/tmp` and the container's ext4 root disk at `/var/tmp`. -Ratios are `wsd / baseline` — lower is faster, values below 1.0 mean -wsd beats the baseline. +Ratios are `computerd / baseline` — lower is faster, values below 1.0 mean +computerd beats the baseline. ### `fs-bench` (REPS=3, WARMUP=1, randomized targets) -| Scenario | wsd | tmpfs | tmpfs ratio | ext4 disk | disk ratio | +| Scenario | computerd | tmpfs | tmpfs ratio | ext4 disk | disk ratio | |---|---:|---:|---:|---:|---:| | **tiny-file churn** | | | | | | | create 1000 files | 560.6 ms | 83.2 ms | 6.7x | 303.2 ms | 1.85x | @@ -129,14 +129,14 @@ wsd beats the baseline. | Target | Duration | |---|---:| | tmpfs (`/tmp`) | 34.3 s | -| wsd FUSE (`/workspace`) | 124.7 s | +| computerd FUSE (`/workspace`) | 124.7 s | | ext4 disk (`/var/tmp`) | 63.9 s | -wsd is ~2x slower than the container's ext4 disk for the full +computerd is ~2x slower than the container's ext4 disk for the full `npm install`, and ~3.6x slower than tmpfs. The disk comparison is the more realistic baseline for general usage. -### Where wsd is faster than the disk baseline +### Where computerd is faster than the disk baseline The in-memory inode store beats real disk on metadata-heavy work: `stat`, `rm`, `mkdir tree`, `find tree`, `git init`, `git clone`, @@ -144,9 +144,9 @@ The in-memory inode store beats real disk on metadata-heavy work: of tools like `git status`, module resolution, and incremental builds. -### Where wsd is slower +### Where computerd is slower -Large sequential file I/O. The wsd write path hashes each +Large sequential file I/O. The computerd write path hashes each [`CHUNK_SIZE`](packages/dofs/src/fs/writeFile.ts) (512 KiB) chunk into a content-addressed blob store on every release; that's how the Durable Object can sync only the chunks that changed and @@ -161,7 +161,7 @@ baseline despite `pure read 64 MiB` being 30x slower. bash script/run-fs-bench.sh ``` -or against a deployed `wsd-container` instance, upload +or against a deployed `computerd-container` instance, upload [`script/fs-bench.sh`](script/fs-bench.sh) and run it with `MOUNT=/workspace BASE=/tmp`. diff --git a/docs/01_vfs.md b/docs/01_vfs.md index 0d363b88..b230d5d1 100644 --- a/docs/01_vfs.md +++ b/docs/01_vfs.md @@ -11,12 +11,12 @@ The workspace exposes a single absolute path namespace rooted at `/`. The host-side VFS root is always `/` (`ROOT_INODE`); it is not configurable on the `Workspace` constructor. By convention user data lives under `/workspace`, which is the default *container* mount point -(configured via the `MOUNT_POINT` env var read by `wsd` inside the +(configured via the `MOUNT_POINT` env var read by `computerd` inside the container, not on `WorkspaceOptions`). ```ts -import { Workspace } from "@cloudflare/workspace"; -import { CloudflareContainerBackend } from "@cloudflare/workspace/backends/container"; +import { Workspace } from "@cloudflare/computer"; +import { CloudflareContainerBackend } from "@cloudflare/computer/backends/container"; new Workspace({ storage: ctx.storage, @@ -119,7 +119,7 @@ to use any naming convention you like for your own data. ## Sandbox view -`wsd` (the in-container daemon) mounts the VFS at `MOUNT_POINT` +`computerd` (the in-container daemon) mounts the VFS at `MOUNT_POINT` (default `/workspace`) via FUSE by default. The backend is picked by the in-image `FUSE_MOUNT` env var (`auto` by default; see doc 07). On Cloudflare Containers `/dev/fuse` is exposed and the real kernel @@ -144,7 +144,7 @@ pinned `DISABLE_FUSE=1`, which produced a degraded mode where: FUSE is the default for full sandbox parity — when enabled, reads route through the FUSE driver to the in-container VFS mirror and writes are recorded as dirty and pulled back to the DO on the next -bracket. It is implemented in `wsd` (see `packages/wsd/src/fuse/`) +bracket. It is implemented in `computerd` (see `packages/computerd/src/fuse/`) and selected via `FUSE_MOUNT` (any value other than `none`). See [06. Mount Interface](./06_mount_interface.md) for mount diff --git a/docs/02_sync_protocol.md b/docs/02_sync_protocol.md index d5699e09..aacec7e8 100644 --- a/docs/02_sync_protocol.md +++ b/docs/02_sync_protocol.md @@ -13,7 +13,7 @@ The workspace keeps two copies of the filesystem tree in sync: - **Container side** — a VFS exposed to the sandbox via a FUSE mount at the configured workspace root. The container store is the same `Database` abstraction used DO-side; whether it persists across - container restarts is a deployment choice (today's `wsd` runs against + container restarts is a deployment choice (today's `computerd` runs against a process-lifetime DB, so a container restart loses local state and the next push from the DO re-baselines it). @@ -204,7 +204,7 @@ one name. The DO watermarks live in the `_vfs_watermark` table so they survive DO restarts. The container's watermarks live in the same `Database` abstraction; whether they survive a container restart is a deployment -choice. Today's `wsd` runs against a process-lifetime DB, so a container +choice. Today's `computerd` runs against a process-lifetime DB, so a container restart loses local watermarks and the next push from the DO is treated as an authoritative baseline (the `senderRev === 0` branch below covers the symmetric case where an external orchestrator writes against a @@ -229,7 +229,7 @@ records, both probe with `hasObjects`, both transfer bytes by hash. Naming follows git's vocabulary — the DO *pushes* entries and objects to the container, and *fetches* entries and objects back. -The DO and `wsd` are deployed as a matched pair. The protocol has no +The DO and `computerd` are deployed as a matched pair. The protocol has no version negotiation, so changes to request or response shapes are hard wire breaks and require lockstep rollout. @@ -269,7 +269,7 @@ edited file) shows up exactly once on the wire. See - **Container restart mid-exec.** The DO's connection detects the closed WebSocket and self-destructs. The next call transparently - rebuilds against the still-running `wsd` (or restarts it if needed). + rebuilds against the still-running `computerd` (or restarts it if needed). `pushRev` and the fetch cursor mean the catch-up is incremental, modulo whatever the container's deployment chose for its DB lifetime. - **Container crash mid-apply.** `push` is atomic from the DO's @@ -289,7 +289,7 @@ edited file) shows up exactly once on the wire. See entries (256), not the whole stream. End state is correct either way — apply is idempotent. - **DO restart.** Watermarks are persisted, so the new DO instance - picks up where the old one left off. The container keeps `wsd` + picks up where the old one left off. The container keeps `computerd` alive across the gap. - **Concurrent mutators.** `Workspace.push()` and `Workspace.pull()` go through a per-Workspace tail-promise FIFO. Two concurrent @@ -307,7 +307,7 @@ edited file) shows up exactly once on the wire. See A *conflict* arises when two writers both mutate the same path without seeing each other's change first. Understanding where conflicts can and -cannot happen in `@cloudflare/workspace` is essential before reasoning +cannot happen in `@cloudflare/computer` is essential before reasoning about the guarantees the system provides. ### Within a single Workspace instance (DO) @@ -328,7 +328,7 @@ same FIFO automatically. ### Across two containers sharing one Workspace This is where conflicts can occur. If two containers (two separate -`wsd` mounts or two separate `WorkspaceBackend` instances) are wired +`computerd` mounts or two separate `WorkspaceBackend` instances) are wired to the same DO and both write to the same path, the outcome depends entirely on sync order: diff --git a/docs/05_shell_interface.md b/docs/05_shell_interface.md index e20e78fb..e6741cd8 100644 --- a/docs/05_shell_interface.md +++ b/docs/05_shell_interface.md @@ -212,7 +212,7 @@ from inside it (`getWorkspace(this)`); the surface is identical both ways: ```ts -import { getWorkspace } from "@cloudflare/workspace"; +import { getWorkspace } from "@cloudflare/computer"; using ws = await getWorkspace(env.MyDO.get(id)); @@ -231,7 +231,7 @@ available. Use it when you need `cwd` or `backend`, and wrap an interpolated command in the `sh` tag to escape it: ```ts -import { sh } from "@cloudflare/workspace"; +import { sh } from "@cloudflare/computer"; await ws.shell.exec(sh`cat ${file}`, { cwd: "/workspace" }); await ws.shell.exec("npm test", { cwd: "/workspace", encoding: "utf8" }); diff --git a/docs/07_injected_service.md b/docs/07_injected_service.md index 545abb5b..246c373a 100644 --- a/docs/07_injected_service.md +++ b/docs/07_injected_service.md @@ -1,24 +1,24 @@ # 07. Injected Service > [!NOTE] -> This doc now reflects shipped code in `packages/wsd/` and -> `packages/workspace/src/backends/`. Items marked **(planned)** are +> This doc now reflects shipped code in `packages/computerd/` and +> `packages/computer/src/backends/`. Items marked **(planned)** are > deferred work. -The "injected service" is the workspace daemon that runs *inside* the +The "injected service" is the computer daemon that runs *inside* the sandbox container. It owns the FUSE mount, the in-container VFS, the exec runner, and the capnweb RPC endpoint the DO talks to. The package ships it as a single self-contained Node SEA binary — -**`wsd`** — produced by `packages/wsd/` (npm package -`@cloudflare/workspace-wsd`, bin name `wsd`). The binary embeds Node, +**`computerd`** — produced by `packages/computerd/` (npm package +`@cloudflare/computerd`, bin name `computerd`). The binary embeds Node, the `fuse-native` prebuilds, and `libfuse` as SEA assets, so the host image does **not** need a Node runtime. Build it with: ```bash -npm run build:bin --workspace @cloudflare/workspace-wsd -# → artifacts/wsd/wsd-linux-x64 -# → artifacts/wsd/wsd-macos-x64 +npm run build:bin --workspace @cloudflare/computerd +# → artifacts/computerd/computerd-linux-x64 +# → artifacts/computerd/computerd-macos-x64 ``` `examples/container/Dockerfile` is the canonical recipe for @@ -45,16 +45,16 @@ staging the binary into a container image. ## HTTP / WS surface -`wsd` listens on a single port (default `45678`; the Cloudflare +`computerd` listens on a single port (default `45678`; the Cloudflare backend pins it to `8080`) and serves: | Route | Method | Purpose | | --- | --- | --- | | `/health` | `GET`, `HEAD` | Liveness probe; `200 ok\n` as soon as the HTTP server binds. | -| `/__wsd/info` | `GET` | Runtime info: FUSE backend, mount point, port. | +| `/__computerd/info` | `GET` | Runtime info: FUSE backend, mount point, port. | | `/api` | `POST` | HTTP-batch capnweb transport. | | `/ws` | `GET` (upgrade) | WebSocket capnweb transport — the bootstrap stub is `WorkspaceRPC`. | -| `/connect` | `POST` | Tells `wsd` to dial *out* to a caller-supplied URL and serve a `WorkspaceRPC` session over that outbound WebSocket. Used by the Cloudflare backend (see below). | +| `/connect` | `POST` | Tells `computerd` to dial *out* to a caller-supplied URL and serve a `WorkspaceRPC` session over that outbound WebSocket. Used by the Cloudflare backend (see below). | | `/` | `GET` | Banner/info page. | The capnweb bootstrap interface is **`WorkspaceRPC`** (defined in @@ -72,15 +72,15 @@ RUN apt-get update \ fuse3 libfuse2t64 ca-certificates \ && rm -rf /var/lib/apt/lists/* -COPY build/wsd-linux-x64 /usr/local/bin/wsd -RUN chmod +x /usr/local/bin/wsd +COPY build/computerd-linux-x64 /usr/local/bin/computerd +RUN chmod +x /usr/local/bin/computerd ENV PORT=8080 ENV MOUNT_POINT=/workspace ENV FUSE_MOUNT=auto EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] ``` Notes: @@ -91,7 +91,7 @@ Notes: install of `fuse3 libfuse2t64` provides the userland tooling and `/dev/fuse` plumbing for the host kernel. - `EXPOSE 8080` matches the Cloudflare backend's pinned port. If you - run `wsd` outside Cloudflare Containers, leave `PORT` unset (default + run `computerd` outside Cloudflare Containers, leave `PORT` unset (default `45678`) or pick your own. - The port is currently hard-coded in code via `DEFAULT_PORT`; making it a build-time variable is on the roadmap **(planned)**. @@ -101,7 +101,7 @@ Notes: Provider-agnostic shape — three steps, in order: 1. **Start the binary.** The host-side workspace asks its sandbox - provider to launch `wsd` as the container's entrypoint. + provider to launch `computerd` as the container's entrypoint. 2. **Poll the health endpoint.** The host issues `HEAD /health` until it returns `200`. Caveat: `/health` is wired by the HTTP server and answers `200` as soon as the socket binds. In the FUSE-enabled @@ -109,13 +109,13 @@ Provider-agnostic shape — three steps, in order: `/health` answers FUSE is up too. With `FUSE_MOUNT=none` there is no FUSE step at all. 3. **Open the capnweb session.** Either the host upgrades to `/ws` - directly, or it asks `wsd` (via `POST /connect`) to dial *out* to a + directly, or it asks `computerd` (via `POST /connect`) to dial *out* to a URL it controls and serve the session over that outbound socket. Either way, the bootstrap stub is `WorkspaceRPC`. ### Cloudflare Containers specifics -`CloudflareContainerBackend` (`packages/workspace/src/backends/cloudflare-container.ts`) +`CloudflareContainerBackend` (`packages/computer/src/backends/cloudflare-container.ts`) wires it like this: 1. **Start.** `container.start({ enableInternet, env })` on the @@ -123,7 +123,7 @@ wires it like this: Idempotence comes from `container.running` plus a cached `#handle`; there is no process-name registry, no `startProcess`/`getProcess`, and no `node /app/...` command (the container's `ENTRYPOINT` runs - `wsd` directly). `containerEnv` pins `PORT=8080` and lets the + `computerd` directly). `containerEnv` pins `PORT=8080` and lets the image's own `FUSE_MOUNT` value (typically `auto`) win. 2. **Wire egress.** `container.interceptOutboundHttp(egressHost, egress)` routes outbound HTTP from the container at `egressHost` back to a @@ -132,7 +132,7 @@ wires it like this: repeated until it returns `200`. 4. **Invert the WebSocket.** The DO arms an upgrade slot (`#armUpgrade`) and then `POST`s to `/connect` on the container - (`#postConnect`). `wsd` reads that request and dials *out* to the + (`#postConnect`). `computerd` reads that request and dials *out* to the egress at `${egressHost}/ws`. Because the egress is intercepted, that outbound dial loops back to the DO's `handleFetch()`, which accepts the upgrade and resolves the in-flight `#pendingUpgrade`. @@ -142,7 +142,7 @@ wires it like this: Sharp edges actually present in `cloudflare-container.ts`: -- `#armUpgrade` must be set up *before* `#postConnect`, because `wsd` +- `#armUpgrade` must be set up *before* `#postConnect`, because `computerd` can dial back before the `POST /connect` response returns. - A `#monitoring` flag watches container exit and drops the cached handle so the next call rebuilds from scratch. @@ -153,19 +153,19 @@ Sharp edges actually present in `cloudflare-container.ts`: ## Environment variables -These are the variables `wsd` actually consumes (see -`packages/wsd/src/cli/wsd.ts` and `packages/wsd/src/fuse/backend.ts`): +These are the variables `computerd` actually consumes (see +`packages/computerd/src/cli/computerd.ts` and `packages/computerd/src/fuse/backend.ts`): | Variable | Default | Meaning | | --- | --- | --- | | `PORT` | `45678` | Port the HTTP/WS server listens on. CF backend pins this to `8080`. | | `MOUNT_POINT` | `/workspace` | Absolute path inside the container to mount the FUSE filesystem at. Ignored when `FUSE_MOUNT=none`. | | `FUSE_MOUNT` | `auto` | Backend selector: `auto` probes `/dev/fuse` (linux) or macFUSE (darwin) and falls back to the userspace shim; `fuse` / `macfuse` require the corresponding real backend; `shim` forces the userspace shim; `none` skips the mount entirely. | -| `UPSTREAM_URL` | unset | If set, `wsd` starts a sync client against this URL to push/pull VFS revisions. | +| `UPSTREAM_URL` | unset | If set, `computerd` starts a sync client against this URL to push/pull VFS revisions. | | `EXEC_LOG_MAX_BYTES` | runner default | Caps the per-exec stdout/stderr log retained in-memory. | | `LOG_FILE` | unset | If set, every `console.log` / `console.error` line and any `uncaughtException` / `unhandledRejection` is also appended to this file. Stdout/stderr behaviour is unchanged. | -When `LOG_FILE` is set, `wsd` mirrors console output into the file in +When `LOG_FILE` is set, `computerd` mirrors console output into the file in addition to stdout/stderr. See "Failure handling" below for the crash handlers that share the same logger. @@ -173,7 +173,7 @@ handlers that share the same logger. Today: -- `wsd` installs `uncaughtException` and `unhandledRejection` +- `computerd` installs `uncaughtException` and `unhandledRejection` handlers via `installLogging()` (in `cli/logger.ts`). Each handler writes a formatted entry to the same logger — `console.error` and, if `LOG_FILE` is set, the file too — then calls `process.exit(1)`. @@ -190,19 +190,19 @@ Today: **Planned**: - Soft-fail on FUSE-detect failure: the server still starts, exposes - RPC, and reports `fuseActive=false` via `/__wsd/info`. Whether + RPC, and reports `fuseActive=false` via `/__computerd/info`. Whether that includes a host-FS mirror for in-container writes is still open. ## Lifetime -The `wsd` process is long-lived and outlives DO restarts — the +The `computerd` process is long-lived and outlives DO restarts — the sandbox container is reaped only when its lifetime policy says so, and a fresh DO incarnation reconnects to the same running daemon over a new WebSocket (the Cloudflare backend's `#monitoring` flag drops the cached handle if the container itself exits, forcing a rebuild). -Caveat: **no on-disk persistence yet** (`packages/wsd/README.md`). +Caveat: **no on-disk persistence yet** (`packages/computerd/README.md`). The "same in-memory VFS across DO restarts" picture only holds while the container process is alive. A container restart loses VFS state; sync via `UPSTREAM_URL` is what brings state back across container @@ -223,10 +223,10 @@ use case depends on a particular resolution. provisioned at boot. The wire surface ([08. Capnweb Interface](./08_capnweb_interface.md)) will need a hello/auth phase before the bootstrap stub is exposed. -- **Process user and file ownership.** `wsd` currently runs as +- **Process user and file ownership.** `computerd` currently runs as whatever user the sandbox image's `ENTRYPOINT` runs as — typically `root`, which is a poor default for a process that mounts FUSE and - spawns arbitrary shell commands. The intent is to run `wsd` as an + spawns arbitrary shell commands. The intent is to run `computerd` as an unprivileged user so a misbehaving exec can't escalate, *but* exec'd commands need to be able to read and write the FUSE-mounted tree. Open: which user owns the mount, what user `exec` runs as diff --git a/docs/08_capnweb_interface.md b/docs/08_capnweb_interface.md index b28aa5e8..ca654e05 100644 --- a/docs/08_capnweb_interface.md +++ b/docs/08_capnweb_interface.md @@ -5,7 +5,7 @@ > **(planned)** are deferred work. [capnweb](https://github.com/cloudflare/capnweb) is the RPC framing used -between the Durable Object and the in-container `wsd` workspace-server. +between the Durable Object and the in-container `computerd` computerd. The wire format is text JSON over a single WebSocket (with an HTTP-batch alternative). The interface served is `WorkspaceRPC`, defined in `packages/rpc/src/interface.ts` and consumed by both sides. @@ -13,7 +13,7 @@ alternative). The interface served is `WorkspaceRPC`, defined in ## Transport - **Carrier.** One long-lived WebSocket per Workspace. The DO opens it - against the workspace-server's `/ws` endpoint, with `/api` available + against the computerd's `/ws` endpoint, with `/api` available as an HTTP-batch alternative (single POST per call) for callers that can't hold a socket. Default port is `45678`; it will become a build-time variable so hosts can pin a non-default port. The stale @@ -28,7 +28,7 @@ alternative). The interface served is `WorkspaceRPC`, defined in - **Reconnect.** On close or error the DO-side connection self-destructs synchronously from the event handler. The next RPC call transparently rebuilds against the still-running - workspace-server. + computerd. The DO uses a deferred transport so the RPC stub can be created before the WebSocket upgrade completes — queued sends flush as soon as the @@ -124,7 +124,7 @@ interface SyncRPC { } ``` -The durable object and `wsd` are deployed as a matched pair. This +The durable object and `computerd` are deployed as a matched pair. This interface has no version negotiation, so request and response shape changes are hard wire breaks and require lockstep rollout. @@ -269,7 +269,7 @@ relying on stream-pull semantics. See ## Stream replay and durability Every `ExecEvent` is written straight to the SQLite-backed -`wsd_exec_events` table (`packages/wsd/src/exec/log.ts`). `getExec({ +`computerd_exec_events` table (`packages/computerd/src/exec/log.ts`). `getExec({ id, after })` resumes by selecting rows with `seq > after`. There is no separate in-memory log and no file spill — SQLite is the durable substrate. @@ -347,7 +347,7 @@ already uses. These behaviours aren't fully specified yet. File an issue if your use case depends on a particular resolution. -- **Compatibility dates.** The DO and the workspace-server are +- **Compatibility dates.** The DO and the computerd are versioned independently — the DO ships with its host Worker, the server ships in the sandbox image. They can drift. The intent is to follow Workers' compatibility-date model: the DO declares a diff --git a/docs/09_tool_interface.md b/docs/09_tool_interface.md index d49f1857..ae4cd063 100644 --- a/docs/09_tool_interface.md +++ b/docs/09_tool_interface.md @@ -1,6 +1,6 @@ # 09. Tool interface (agents) -`@cloudflare/workspace/tools` ships ready-made [AI SDK](https://github.com/vercel/ai) tools for agents that use a `Workspace`. The first provider target is the AI SDK because it is the tool layer used by the `agents` SDK and the Think example. +`@cloudflare/computer/tools` ships ready-made [AI SDK](https://github.com/vercel/ai) tools for agents that use a `Workspace`. The first provider target is the AI SDK because it is the tool layer used by the `agents` SDK and the Think example. The tools are thin wrappers over the existing `Workspace` surfaces: @@ -28,8 +28,8 @@ The fixed tool names from `createAITools()` are `read`, `write`, `edit`, and `ls ## Wiring up ```ts -import { Workspace } from "@cloudflare/workspace"; -import { createAITools } from "@cloudflare/workspace/tools"; +import { Workspace } from "@cloudflare/computer"; +import { createAITools } from "@cloudflare/computer/tools"; export class Agent { workspace: Workspace; diff --git a/docs/10_project_layout.md b/docs/10_project_layout.md index d5e6ac11..19e4b5a7 100644 --- a/docs/10_project_layout.md +++ b/docs/10_project_layout.md @@ -14,33 +14,33 @@ The workspace ships as a monorepo. Each published package lives under ``` ``` -workspace/ +computer/ ├── packages/ -│ ├── workspace/ # @cloudflare/workspace — DO-side facade, backends, proxy -│ ├── vfs/ # @cloudflare/dofs — SQLite-backed VFS + sync -│ ├── rpc/ # @cloudflare/workspace-rpc — capnweb wire interface -│ ├── wsd/ # @cloudflare/workspace-wsd — in-container daemon (binary) +│ ├── computer/ # @cloudflare/computer — DO-side facade, backends, proxy +│ ├── dofs/ # @cloudflare/dofs — SQLite-backed VFS + sync +│ ├── rpc/ # @cloudflare/computer-rpc — capnweb wire interface +│ ├── computerd/ # @cloudflare/computerd — in-container daemon (binary) │ ├── fs-tools/ # (planned) AI SDK file tools + FileStore │ └── git-tools/ # (planned) AI SDK git tools ├── examples/ -│ └── container/ # Reference container image for the wsd daemon +│ └── container/ # Reference container image for the computerd daemon ├── docs/ # This documentation set -└── package.json # Workspace root (workspaces: packages/*, examples/*) +└── package.json # Monorepo root (workspaces: packages/*, examples/*) ``` ### Folder rename history Two renames have landed: -- `packages/workspace-rpc/` → `packages/rpc/` (folder only). The npm - package is still `@cloudflare/workspace-rpc`. -- `packages/workspace-fs/` → `packages/dofs/`, and the npm package - was renamed `@cloudflare/workspace-fs` → `@cloudflare/dofs`. +- `packages/computer-rpc/` → `packages/rpc/` (folder only). The npm + package is still `@cloudflare/computer-rpc`. +- `packages/computer-fs/` → `packages/dofs/`, and the npm package + was renamed `@cloudflare/computer-fs` → `@cloudflare/dofs`. If you grep older history or other docs and find the old folder paths, they refer to the same code under the new names. -## `packages/workspace/` — `@cloudflare/workspace` +## `packages/computer/` — `@cloudflare/computer` The DO-side facade. Owns the `Workspace` class, re-exports `WorkspaceFilesystem` from `@cloudflare/dofs`, exposes the @@ -50,14 +50,14 @@ The DO-side facade. Owns the `Workspace` class, re-exports through an RPC stub. ``` -packages/workspace/ +packages/computer/ ├── src/ │ ├── index.ts # Public entrypoint │ ├── workspace.ts # Workspace facade │ ├── shell.ts # WorkspaceShell │ ├── backend.ts # Backend interface │ ├── backends/ -│ │ ├── container/ # Cloudflare Container + wsd backend +│ │ ├── container/ # Cloudflare Container + computerd backend │ │ ├── worker/ # Dynamic Worker + just-bash backend │ │ └── test.ts # In-process test backend │ ├── proxy.ts # WorkspaceProxy @@ -75,8 +75,8 @@ Build: dual ESM + CJS via two `tsc` invocations (`tsc -p tsconfig.build.json && tsc -p tsconfig.cjs.json`). The `package.json` declares a single `.` export resolving to `dist/cjs/index.js` (CJS) with ESM types alongside. There is no -`ws.js` or `shared.js` — the injected service is the separate `wsd` -package, and shared wire types live in `@cloudflare/workspace-rpc`. +`ws.js` or `shared.js` — the injected service is the separate `computerd` +package, and shared wire types live in `@cloudflare/computer-rpc`. ## `packages/dofs/` — `@cloudflare/dofs` @@ -112,7 +112,7 @@ packages/dofs/ Exports resolve to `dist/index.js` and `dist/testing.js`. -## `packages/rpc/` — `@cloudflare/workspace-rpc` +## `packages/rpc/` — `@cloudflare/computer-rpc` The capnweb wire interface that joins DO-side and container-side processes. `WorkspaceRPC` is the union of the sync and shell @@ -133,20 +133,20 @@ packages/rpc/ └── package.json # exports: `.`, `./server`, `./client`, `./driver` ``` -## `packages/wsd/` — `@cloudflare/workspace-wsd` +## `packages/computerd/` — `@cloudflare/computerd` The in-container daemon. Built as a single-file native binary named -`wsd` that runs inside the sandbox container. It owns the FUSE +`computerd` that runs inside the sandbox container. It owns the FUSE mount, the exec runner, and dials back to the DO over WebSocket via the `rpc` package. (Replaces the historical `ws.js` injected script; see doc 07.) ``` -packages/wsd/ +packages/computerd/ ├── src/ │ ├── cli/ -│ │ └── wsd.ts # CLI entry +│ │ └── computerd.ts # CLI entry │ ├── fuse/ │ │ ├── driver.ts │ │ ├── backend.ts @@ -160,22 +160,22 @@ packages/wsd/ │ ├── log.ts │ └── index.ts ├── scripts/ -│ ├── build.mjs # → dist/cli/wsd.cjs +│ ├── build.mjs # → dist/cli/computerd.cjs │ ├── build-bin.mjs # SEA driver │ └── sea/ │ └── bundle.mjs # esbuild → SEA bundle ├── artifacts/ -│ └── wsd/ -│ ├── wsd-linux-x64 -│ └── wsd-macos-x64 +│ └── computerd/ +│ ├── computerd-linux-x64 +│ └── computerd-macos-x64 ├── tsconfig.json └── package.json ``` -Build pipeline: `scripts/build.mjs` emits `dist/cli/wsd.cjs`; +Build pipeline: `scripts/build.mjs` emits `dist/cli/computerd.cjs`; `scripts/build-bin.mjs` together with `scripts/sea/bundle.mjs` produces the Node SEA single-file binary at -`artifacts/wsd/wsd-{linux,macos}-x64`. +`artifacts/computerd/computerd-{linux,macos}-x64`. ## `packages/fs-tools/` — **(planned)** @@ -187,7 +187,7 @@ implemented yet. ## Git Git access ships through `workspace.git` on the main -`@cloudflare/workspace` package rather than a separate package. +`@cloudflare/computer` package rather than a separate package. Both a typed JavaScript API and an argv-driven entry point are available; the worker backend's shell isolate also exposes a built-in `git` command that forwards to the same dispatcher. @@ -200,7 +200,7 @@ Runnable examples live at the repo root, not inside any package: ``` examples/ -└── container/ # Reference container image for wsd +└── container/ # Reference container image for computerd ``` The root `package.json` includes `examples/*` in its workspaces glob @@ -211,8 +211,8 @@ so each example can declare its own dependencies and scripts. - **Unit tests live next to source.** Every package follows the `foo.ts` + `foo.test.ts` convention. There is no top-level `tests/` directory anywhere in the repo. -- **Integration / harness tests** for the workspace package live in - `packages/workspace/test-harness/`: +- **Integration / harness tests** for the computer package live in + `packages/computer/test-harness/`: - `end-to-end.test.ts` — DO ↔ container round-trip - `shell.test.ts` — shell surface against a real backend - `load.bench.ts` — load / soak benchmark @@ -227,9 +227,9 @@ so each example can declare its own dependencies and scripts. - **Biome.** Both linter and formatter are enabled in `biome.jsonc` at the repo root (`biome check` covers lint + format; `biome format` formats only). No ESLint, no Prettier. -- **esbuild.** Used by `packages/wsd/scripts/sea/bundle.mjs` to - produce the single-file `wsd` SEA bundle. Application bundling is +- **esbuild.** Used by `packages/computerd/scripts/sea/bundle.mjs` to + produce the single-file `computerd` SEA bundle. Application bundling is left to consumers. -- **vitest.** Drives unit tests in every package. `wsd` additionally +- **vitest.** Drives unit tests in every package. `computerd` additionally uses `node --experimental-strip-types --test` for some scripts given its native-binary nature. diff --git a/docs/11_lifecycle.md b/docs/11_lifecycle.md index 55e0157c..ff4a3c0e 100644 --- a/docs/11_lifecycle.md +++ b/docs/11_lifecycle.md @@ -12,7 +12,7 @@ ## Architecture A workspace pairs **one Durable Object** with **one container instance** -running `wsd`. The DO owns the source-of-truth VFS in its SQLite +running `computerd`. The DO owns the source-of-truth VFS in its SQLite storage; the container owns a transient in-memory mirror exposed at the FUSE mount inside `MOUNT_POINT`. They talk over a single long-lived capnweb WebSocket session. @@ -21,7 +21,7 @@ capnweb WebSocket session. ┌───────────────────────────┐ ┌────────────────────────┐ │ Durable Object │ │ Container │ │ ┌─────────────────────┐ │ │ ┌──────────────────┐ │ -│ │ Workspace │ │ │ │ wsd │ │ +│ │ Workspace │ │ │ │ computerd │ │ │ │ fs: WorkspaceFS │ │ │ │ HTTP server │ │ │ │ shell: ShellRPC │ │ │ │ /health │ │ │ │ sync: SyncRPC │◀─┼── capnweb WS ───▶│ │ /connect /ws │ │ @@ -49,7 +49,7 @@ The 1:1 mapping is load-bearing for several reasons: - Hibernation enablement (see below) becomes tractable because the DO doesn't have to multiplex multiple WS peers. -The DO is the WebSocket *server* in this pairing, even though `wsd` +The DO is the WebSocket *server* in this pairing, even though `computerd` exposes its own `/ws` server-side and could be dialed directly. The reason for the inversion is documented in [07. Injected Service §Bootstrap sequence](./07_injected_service.md): @@ -115,11 +115,11 @@ lifetime policy. From the DO's perspective: | Event | What survives | What's lost | | --- | --- | --- | -| **DO restart, container alive** | The container's in-memory VFS, FUSE mount, `wsd` process | The DO's `#handle` | -| **Container SIGTERM / restart** | The DO's `ctx.storage`, the DO instance itself | The container's in-memory VFS, FUSE mount state, `wsd` process state | +| **DO restart, container alive** | The container's in-memory VFS, FUSE mount, `computerd` process | The DO's `#handle` | +| **Container SIGTERM / restart** | The DO's `ctx.storage`, the DO instance itself | The container's in-memory VFS, FUSE mount state, `computerd` process state | | **Both die** (host OOM, region failure) | `ctx.storage` | Everything else | -`wsd` is a long-lived process. It outlives DO restarts — the +`computerd` is a long-lived process. It outlives DO restarts — the `Container.monitor()` promise resolves only when the container itself exits, and the backend's `#monitoring` flag drops the cached handle at that point so the next call rebuilds from scratch @@ -162,9 +162,9 @@ WebSocket, which means **the DO must be alive in memory to receive frames**. There is no hibernation-aware variant today. On the container side: `acceptWebSocketSession(ws, rpc)` in -`wsd.ts:181` for inbound `/ws` upgrades, or the same call in the -`/connect` outbound dial path at `wsd.ts:266`. Both attach to a `ws` -package WebSocket and require the `wsd` process to be live. +`computerd.ts:181` for inbound `/ws` upgrades, or the same call in the +`/connect` outbound dial path at `computerd.ts:266`. Both attach to a `ws` +package WebSocket and require the `computerd` process to be live. ### Session lifecycle, today @@ -214,9 +214,9 @@ accumulate until someone calls it. There are two boundaries where stubs cross in this codebase, and they have slightly different rules. -**Boundary 1: Worker / DO ↔ wsd (capnweb over WebSocket).** +**Boundary 1: Worker / DO ↔ computerd (capnweb over WebSocket).** This is the long-lived session described above. The driver code -(`packages/rpc/src/sync-driver.ts`, `packages/workspace/src/shell.ts`) +(`packages/rpc/src/sync-driver.ts`, `packages/computer/src/shell.ts`) holds the only client-side references to result envelopes. When a call returns `{ stream, ... }` or `{ events, ... }`, the driver binds the envelope to a `using` variable so it disposes at the end @@ -231,7 +231,7 @@ result to `using`, or call `result[Symbol.dispose]()` after draining. **Boundary 2: Worker ↔ DO (Workers RPC).** This is the boundary -`env.WSD.get(id).getWorkspace()` crosses. Returns are +`env.COMPUTERD.get(id).getWorkspace()` crosses. Returns are `RpcTarget`-derived stubs, not plain objects. Three live across the boundary: @@ -252,8 +252,8 @@ The minimal correct pattern from a Worker is: ```ts export default { async fetch(request: Request, env: Env): Promise { - const id = env.WSD.idFromName("user-123"); - using ws = await env.WSD.get(id).getWorkspace(); + const id = env.COMPUTERD.idFromName("user-123"); + using ws = await env.COMPUTERD.get(id).getWorkspace(); using handle = await ws.shell.exec("npm test"); const result = await handle.result(); @@ -276,10 +276,10 @@ long-lived isolates that keep grabbing fresh `WorkspaceStub`s workloads inside a single request. Leak discovery is instrumented via `CAPNWEB_TRACK_STUBS=1` and -`stubSnapshot()` from `@cloudflare/workspace-rpc/debug`. wsd -exposes the snapshot at `GET /__wsd/stubs` when the flag is set; -the soak script at `script/wsd-stub-soak.mjs` and the workerd -soak at `packages/workspace/tests/stub-soak.test.ts` use it to +`stubSnapshot()` from `@cloudflare/computer-rpc/debug`. computerd +exposes the snapshot at `GET /__computerd/stubs` when the flag is set; +the soak script at `script/computerd-stub-soak.mjs` and the workerd +soak at `packages/computer/tests/stub-soak.test.ts` use it to prove no unbounded growth under sustained workloads. ## Hibernation @@ -376,7 +376,7 @@ between agent turns, and it's exactly where hibernation pays off. ### What about the alternative — invert the dial direction? Adopting PartySocket for reconnect/backoff would require the DO -to be the WebSocket *client* dialing `wsd`'s +to be the WebSocket *client* dialing `computerd`'s `/ws` endpoint. That model is appealing for reconnect, but hibernatable WebSockets only work server-side via `ctx.acceptWebSocket()` — there is no hibernation API for outbound @@ -412,7 +412,7 @@ durability work still to ship. - [02. Sync Protocol](./02_sync_protocol.md) — wire format and rev watermark semantics. -- [07. Injected Service](./07_injected_service.md) — `wsd` boot +- [07. Injected Service](./07_injected_service.md) — `computerd` boot sequence and the egress-interception dial-back. - [08. Capnweb Interface](./08_capnweb_interface.md) — RPC surface and transport assumptions. diff --git a/docs/12_worker_backend.md b/docs/12_worker_backend.md index 3cba92c3..a511d221 100644 --- a/docs/12_worker_backend.md +++ b/docs/12_worker_backend.md @@ -2,7 +2,7 @@ > [!NOTE] > This doc reflects shipped code in -> `packages/workspace/src/backends/worker/`. The example deployment +> `packages/computer/src/backends/worker/`. The example deployment > lives at `examples/worker/`. The worker backend is the second `WorkspaceBackend` shape the @@ -17,12 +17,12 @@ Import via the sub-path so the bundled just-bash payload tree-shakes out of consumers that don't use it: ```ts -import { WorkerBackend } from "@cloudflare/workspace/backends/worker"; +import { WorkerBackend } from "@cloudflare/computer/backends/worker"; ``` ## When to reach for it -The container backend (`@cloudflare/workspace/backends/container`) +The container backend (`@cloudflare/computer/backends/container`) gives you a real Linux environment with arbitrary binaries on `$PATH`, network, and a full POSIX filesystem. It costs a container per session and a real roundtrip on every filesystem op. @@ -116,7 +116,7 @@ values passed through the Worker Loader's `env` go through structured clone, and a raw `DurableObjectNamespace` doesn't survive that. -`WorkspaceServiceProxy` (in `@cloudflare/workspace`) is a tiny +`WorkspaceServiceProxy` (in `@cloudflare/computer`) is a tiny `WorkerEntrypoint` whose `getWorkspace()` method does the namespace lookup on the host side. The backend mints a stub through `ctx.exports.WorkspaceServiceProxy({ props: { binding, @@ -154,7 +154,7 @@ shape across the isolate hop. The backend decodes the frames into structured `ExecEvent` values and re-encodes string payloads (`stdout` / `stderr`) into `Uint8Array` so the existing `WorkspaceShell` utf8 decoder -transforms in `packages/workspace/src/shell.ts` see the shape +transforms in `packages/computer/src/shell.ts` see the shape they already handle. ## Fetcher factory escape hatch @@ -251,7 +251,7 @@ network-bound `git` subcommands do. See `/c//file/...` and `/c//exec` routes the container example also exposes). - No Dockerfile, no build script. The shell bundle ships with - `@cloudflare/workspace/backends/worker` as `SHELL_MODULES` + `@cloudflare/computer/backends/worker` as `SHELL_MODULES` (a record of module name → source covering the entry plus every code-split chunk); the backend hands the whole record to the Loader callback itself. @@ -266,6 +266,6 @@ new WorkerBackend({ }) ``` -Run with `npm run dev --workspace @example/workspace-worker`. +Run with `npm run dev --workspace @example/computer-worker`. The same `curl` recipes from the container example work without changes. diff --git a/docs/13_git_interface.md b/docs/13_git_interface.md index cbe68be5..b679febc 100644 --- a/docs/13_git_interface.md +++ b/docs/13_git_interface.md @@ -1,9 +1,9 @@ # 13. Git interface > [!NOTE] -> This doc describes shipped code in `packages/workspace/src/git/` +> This doc describes shipped code in `packages/computer/src/git/` > and the `git` custom command in -> `packages/workspace/src/backends/worker/`. Everything below +> `packages/computer/src/backends/worker/`. Everything below > works today. `workspace.git` is the third major surface on `Workspace`, @@ -111,7 +111,7 @@ diff against HEAD — through each entry point. ### Typed API from a durable object ```ts -import { Workspace } from "@cloudflare/workspace"; +import { Workspace } from "@cloudflare/computer"; const ws = new Workspace({ storage: ctx.storage }); await ws.git.clone({ url: "https://github.com/example/repo.git" }); @@ -304,7 +304,7 @@ form with `??` for untracked files; `--short` / `-s` produces the short form (` ?` for untracked). The typed surface returns the underlying `StatusEntry[]`; format it with `formatPorcelainV2`, `formatPorcelainV1`, or `formatShort` -from `@cloudflare/workspace/git`. +from `@cloudflare/computer/git`. *Not mapped:* `--branch`, `--ignored`, `--untracked-files`, the long human-readable form. The structured return is the @@ -1066,7 +1066,7 @@ commands receive the live host stub the shell already reached, so they share its lifetime without refetching. ```ts -import { ShellWorker, defineGitCommand } from "@cloudflare/workspace/backends/worker"; +import { ShellWorker, defineGitCommand } from "@cloudflare/computer/backends/worker"; import { type CustomCommand } from "just-bash"; class MyShell extends ShellWorker { diff --git a/docs/14_assets_interface.md b/docs/14_assets_interface.md index 56c7597d..ae7a773e 100644 --- a/docs/14_assets_interface.md +++ b/docs/14_assets_interface.md @@ -18,7 +18,7 @@ webhook without exposing the bucket or the rest of the workspace. ## Creating a client ```ts -import { createAssets } from "@cloudflare/workspace/assets"; +import { createAssets } from "@cloudflare/computer/assets"; const assets = createAssets({ ws, diff --git a/docs/15_artifacts_interface.md b/docs/15_artifacts_interface.md index 6e738273..541eaf5e 100644 --- a/docs/15_artifacts_interface.md +++ b/docs/15_artifacts_interface.md @@ -2,8 +2,8 @@ > [!NOTE] > This doc describes shipped code in -> `packages/workspace/src/artifacts/` and the `artifacts` custom -> command in `packages/workspace/src/backends/worker/`. +> `packages/computer/src/artifacts/` and the `artifacts` custom +> command in `packages/computer/src/backends/worker/`. [Cloudflare Artifacts](https://developers.cloudflare.com/artifacts/) is versioned, Git-speaking repository storage. A Worker reaches it @@ -11,13 +11,13 @@ through a namespace binding (`env.ARTIFACTS`) that can create, inspect, import, and delete repositories and mint Git tokens scoped to a repository. -`@cloudflare/workspace/artifacts` wraps that binding with a +`@cloudflare/computer/artifacts` wraps that binding with a **session-scoped** facade. `createArtifact(binding, sessionId)` binds a namespace binding and a session id once and returns a client whose every operation is implicitly scoped to that session. ```ts -import { createArtifact } from "@cloudflare/workspace/artifacts"; +import { createArtifact } from "@cloudflare/computer/artifacts"; const artifacts = createArtifact(env.ARTIFACTS, agentId); diff --git a/docs/README.md b/docs/README.md index b7bc105d..a2411363 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# `@cloudflare/workspace` +# `@cloudflare/computer` > [!IMPORTANT] > **PREVIEW ONLY** This package is provided as a preview for feedback only. @@ -10,7 +10,7 @@ > The specification under [`docs/`](docs/) is forward-looking — read it for > intent, not as description of the code today. -The `@cloudflare/workspace` package provides an out of the box virtual filesystem for use in any Durable Object — it's persistent and backed by SQLite. It's primarily designed for agents that need small, portable filesystems and tools to work with. +The `@cloudflare/computer` package provides an out of the box virtual filesystem for use in any Durable Object — it's persistent and backed by SQLite. It's primarily designed for agents that need small, portable filesystems and tools to work with. ![Architecture overview](./assets/arch.png) @@ -19,9 +19,9 @@ It provides: - A fs API for working with files and directories compatible with Worker bindings. - R2-backed mounts for pre-filling read-only data into the workspace tree. - Durability over DO restarts for all file operations. - - A pluggable shell backend: a Cloudflare Container running the `wsd` FUSE daemon (full Linux userland) or a Dynamic Worker running [just-bash](https://github.com/vercel-labs/just-bash) (no container, broad textual tooling). + - A pluggable shell backend: a Cloudflare Container running the `computerd` FUSE daemon (full Linux userland) or a Dynamic Worker running [just-bash](https://github.com/vercel-labs/just-bash) (no container, broad textual tooling). - Workspace constructable without a backend, for filesystem-only use cases. - - Out-of-the-box AI SDK tools for `@cloudflare/agents` through `@cloudflare/workspace/tools`. + - Out-of-the-box AI SDK tools for `@cloudflare/agents` through `@cloudflare/computer/tools`. It comes with the following limitations: @@ -34,28 +34,28 @@ It comes with the following limitations: Install the package into your Worker/Agent project: ```sh -npm install @cloudflare/workspace +npm install @cloudflare/computer ``` The package ships several entrypoints: | Entrypoint | Purpose | | --- | --- | -| `@cloudflare/workspace` | The Workspace facade, stub types, the R2 mount, and proxy classes. | -| `@cloudflare/workspace/backends/container` | `CloudflareContainerBackend` and `withWorkspaceContainer`. Pulls in the wsd / capnweb sync plumbing. | -| `@cloudflare/workspace/backends/worker` | `WorkerBackend` and the bundled just-bash shell. The shell ships as a record of code-split modules the Dynamic Worker loads on demand: a ~290 KB entry parsed on cold start, plus ~2.5 MB of chunks that stay cold until a script reaches for them. | -| `@cloudflare/workspace/git` | Isomorphic-git glue for working with checkouts inside the workspace. | -| `@cloudflare/workspace/artifacts` | `createArtifact`, a session-scoped facade over the Cloudflare Artifacts Workers binding, plus its argv CLI. | -| `@cloudflare/workspace/tools` | AI SDK tools for agents: read, write, edit, ls, optional exec, and optional publish. | +| `@cloudflare/computer` | The Workspace facade, stub types, the R2 mount, and proxy classes. | +| `@cloudflare/computer/backends/container` | `CloudflareContainerBackend` and `withWorkspaceContainer`. Pulls in the computerd / capnweb sync plumbing. | +| `@cloudflare/computer/backends/worker` | `WorkerBackend` and the bundled just-bash shell. The shell ships as a record of code-split modules the Dynamic Worker loads on demand: a ~290 KB entry parsed on cold start, plus ~2.5 MB of chunks that stay cold until a script reaches for them. | +| `@cloudflare/computer/git` | Isomorphic-git glue for working with checkouts inside the workspace. | +| `@cloudflare/computer/artifacts` | `createArtifact`, a session-scoped facade over the Cloudflare Artifacts Workers binding, plus its argv CLI. | +| `@cloudflare/computer/tools` | AI SDK tools for agents: read, write, edit, ls, optional exec, and optional publish. | A consumer that only uses the container backend never imports the worker subpath, so the just-bash payload tree-shakes away. -Wire types shared with the in-container service live in the sibling package `@cloudflare/workspace-rpc` (subpaths `./server`, `./client`, `./driver`). +Wire types shared with the in-container service live in the sibling package `@cloudflare/computer-rpc` (subpaths `./server`, `./client`, `./driver`). ### Sandbox container image -The container needs the `wsd` daemon alongside a FUSE runtime. The recommended pattern mirrors [`examples/container/Dockerfile`](../examples/container/Dockerfile): build `wsd` as a single Node SEA binary (`npm run build:bin --workspace @cloudflare/workspace-wsd`), stage it into the image's build context, and copy it into a thin Debian base: +The container needs the `computerd` daemon alongside a FUSE runtime. The recommended pattern mirrors [`examples/container/Dockerfile`](../examples/container/Dockerfile): build `computerd` as a single Node SEA binary (`npm run build:bin --workspace @cloudflare/computerd`), stage it into the image's build context, and copy it into a thin Debian base: ```dockerfile FROM --platform=linux/amd64 debian:stable-slim @@ -65,24 +65,24 @@ RUN apt-get update \ fuse3 libfuse2t64 ca-certificates \ && rm -rf /var/lib/apt/lists/* -COPY build/wsd-linux-x64 /usr/local/bin/wsd -RUN chmod +x /usr/local/bin/wsd +COPY build/computerd-linux-x64 /usr/local/bin/computerd +RUN chmod +x /usr/local/bin/computerd ENV PORT=8080 ENV MOUNT_POINT=/workspace EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] ``` -`wsd`'s own default port is `45678`; the Cloudflare container backend pins the in-image listener to `8080`, which is what `examples/container/` uses. See [07. Injected Service](./07_injected_service.md) for the env vars (`PORT`, `MOUNT_POINT`, `FUSE_MOUNT`, `UPSTREAM_URL`, `EXEC_LOG_MAX_BYTES`) and the reverse-dial boot sequence. +`computerd`'s own default port is `45678`; the Cloudflare container backend pins the in-image listener to `8080`, which is what `examples/container/` uses. See [07. Injected Service](./07_injected_service.md) for the env vars (`PORT`, `MOUNT_POINT`, `FUSE_MOUNT`, `UPSTREAM_URL`, `EXEC_LOG_MAX_BYTES`) and the reverse-dial boot sequence. ## Example ```ts import { AIChatAgent } from "@cloudflare/ai-chat"; -import { Workspace } from "@cloudflare/workspace"; -import { CloudflareContainerBackend } from "@cloudflare/workspace/backends/container"; +import { Workspace } from "@cloudflare/computer"; +import { CloudflareContainerBackend } from "@cloudflare/computer/backends/container"; export class Agent extends AIChatAgent { readonly workspace: Workspace; @@ -227,7 +227,7 @@ above, then dive into the area you're working on. | [04. Filesystem Interface](./04_filesystem_interface.md) | `Workspace.fs` API: `readFile`, `writeFile`, `mkdir`, `grep`, etc. | | [05. Shell Interface](./05_shell_interface.md) | `Workspace.shell.exec` and streamed command execution. | | [06. Mount Interface](./06_mount_interface.md) | Pre-filling paths from R2, Artifacts, GitHub, and custom sources. **(not yet implemented)** | -| [07. Injected Service](./07_injected_service.md) | The in-container `wsd` service that backs FUSE and shell. | +| [07. Injected Service](./07_injected_service.md) | The in-container `computerd` service that backs FUSE and shell. | | [08. Capnweb Interface](./08_capnweb_interface.md) | RPC wire protocol between the DO and the sandbox. | | [09. Tool Interface (Agents)](./09_tool_interface.md) | Ready-made AI SDK tools for `@cloudflare/agents`. | | [10. Project Layout](./10_project_layout.md) | Source tree of this package and how the pieces fit together. | diff --git a/examples/artifacts/README.md b/examples/artifacts/README.md index 61cb62dd..be4dc3ee 100644 --- a/examples/artifacts/README.md +++ b/examples/artifacts/README.md @@ -5,7 +5,7 @@ This example generates a Worker project in a Workspace, publishes it to Cloudfla Run it with Wrangler: ```sh -npm run dev --workspace @example/workspace-artifacts +npm run dev --workspace @example/computer-artifacts ``` Or deploy it and test against the remote runtime: @@ -34,7 +34,7 @@ The Worker endpoint owns the orchestration. The durable object stays minimal: it `POST /create` does the following through `ws.shell.exec(...)`: -1. clones `https://github.com/cloudflare/workspace` into `/workspace/-source`; +1. clones `https://github.com/cloudflare/computer` into `/workspace/-source`; 2. copies `/workspace/-source/examples/worker` to `/workspace/`; 3. rewrites the copied Worker name with `sed`; 4. initializes and commits the generated project with the shell `git` command; @@ -48,11 +48,11 @@ A successful response looks like: { "name": "my-generated-worker", "artifactRepo": "my-generated-worker", - "remote": "https://.artifacts.cloudflare.net/git/workspace-artifacts-example/.git", + "remote": "https://.artifacts.cloudflare.net/git/computer-artifacts-example/.git", "branch": "main", "projectDir": "/workspace/my-generated-worker", - "shareLink": "https://x:@.artifacts.cloudflare.net/git/workspace-artifacts-example/.git", - "cloneCommand": "git clone 'https://x:@.artifacts.cloudflare.net/git/workspace-artifacts-example/.git' my-generated-worker" + "shareLink": "https://x:@.artifacts.cloudflare.net/git/computer-artifacts-example/.git", + "cloneCommand": "git clone 'https://x:@.artifacts.cloudflare.net/git/computer-artifacts-example/.git' my-generated-worker" } ``` diff --git a/examples/artifacts/package.json b/examples/artifacts/package.json index afcb90a4..82966a7a 100644 --- a/examples/artifacts/package.json +++ b/examples/artifacts/package.json @@ -1,16 +1,16 @@ { - "name": "@example/workspace-artifacts", + "name": "@example/computer-artifacts", "version": "0.0.0", "private": true, "type": "module", - "description": "Example Worker that turns examples/worker into a cloneable Cloudflare Artifacts repo through @cloudflare/workspace.", + "description": "Example Worker that turns examples/worker into a cloneable Cloudflare Artifacts repo through @cloudflare/computer.", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", "typecheck": "tsc --noEmit" }, "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", diff --git a/examples/artifacts/src/index.ts b/examples/artifacts/src/index.ts index 23004848..17d1e747 100644 --- a/examples/artifacts/src/index.ts +++ b/examples/artifacts/src/index.ts @@ -17,8 +17,8 @@ import { type WorkspaceClient, WorkspaceServiceProxy, withWorkspace, -} from "@cloudflare/workspace"; -import { WorkerBackend, type WorkerBackendOptions } from "@cloudflare/workspace/backends/worker"; +} from "@cloudflare/computer"; +import { WorkerBackend, type WorkerBackendOptions } from "@cloudflare/computer/backends/worker"; export { WorkspaceServiceProxy }; @@ -44,7 +44,7 @@ interface ArtifactCreateOutput { } const WORKSPACE_ROOT = "/workspace"; -const SOURCE_REPO = "https://github.com/cloudflare/workspace"; +const SOURCE_REPO = "https://github.com/cloudflare/computer"; const EXAMPLE_PATH = "examples/worker"; const GIT_REMOTE = "origin"; const SHARE_TOKEN_TTL = "24h"; @@ -143,7 +143,7 @@ async function handleCreate(request: Request, env: Env): Promise { sh`git init --initial-branch=main ${projectDir}`, sh`cat ${`${projectDir}/.git/HEAD`} >/dev/null`, "git add .", - sh`git commit -m ${`Create ${name} worker example`} --author ${"Cloudflare Workspace Artifacts Example "}`, + sh`git commit -m ${`Create ${name} worker example`} --author ${"Cloudflare Computer Artifacts Example "}`, ].join(" && "), { cwd: projectDir }, ); diff --git a/examples/artifacts/wrangler.jsonc b/examples/artifacts/wrangler.jsonc index b9a3f90c..545963df 100644 --- a/examples/artifacts/wrangler.jsonc +++ b/examples/artifacts/wrangler.jsonc @@ -3,7 +3,7 @@ // generates examples/worker in a Workspace and publishes it to a // Cloudflare Artifacts repo. "$schema": "node_modules/wrangler/config-schema.json", - "name": "workspace-artifacts-example", + "name": "computer-artifacts-example", "main": "src/index.ts", "compatibility_date": "2026-06-17", "compatibility_flags": ["nodejs_compat"], @@ -17,7 +17,7 @@ "artifacts": [ { "binding": "ARTIFACTS", - "namespace": "workspace-artifacts-example", + "namespace": "computer-artifacts-example", "remote": true } ], diff --git a/examples/assets/README.md b/examples/assets/README.md index 2e0f654b..a08dcf59 100644 --- a/examples/assets/README.md +++ b/examples/assets/README.md @@ -11,7 +11,7 @@ get a URL. The Durable Object runs the prompt through a Workers AI text-to-image model, writes the generated PNG into its `Workspace`, then uploads that file to R2 and returns a presigned URL through -[`@cloudflare/workspace/assets`](../../docs/14_assets_interface.md). +[`@cloudflare/computer/assets`](../../docs/14_assets_interface.md). > [!NOTE] > This is a **production-only** example. The presigner needs R2 S3 @@ -65,7 +65,7 @@ the `bucket_name` on the `ASSETS` binding. Create the bucket once before the first deploy: ```sh -wrangler r2 bucket create workspace-assets-example +wrangler r2 bucket create computer-assets-example ``` ## HTTP surface @@ -78,9 +78,9 @@ POST /prompt { "prompt": "..." } ## Deploy and run ```sh -npm run deploy --workspace @example/workspace-assets +npm run deploy --workspace @example/computer-assets -curl -X POST https://workspace-assets-example..workers.dev/prompt \ +curl -X POST https://computer-assets-example..workers.dev/prompt \ -H 'content-type: application/json' \ -d '{"prompt":"a sunset over the alps, oil painting"}' ``` diff --git a/examples/assets/package.json b/examples/assets/package.json index 194d4c2d..1ca45f56 100644 --- a/examples/assets/package.json +++ b/examples/assets/package.json @@ -1,16 +1,16 @@ { - "name": "@example/workspace-assets", + "name": "@example/computer-assets", "version": "0.0.0", "private": true, "type": "module", - "description": "Example Worker + Durable Object that turns a prompt into an image with Workers AI, writes it to the workspace, and returns a shareable link via @cloudflare/workspace/assets.", + "description": "Example Worker + Durable Object that turns a prompt into an image with Workers AI, writes it to the workspace, and returns a shareable link via @cloudflare/computer/assets.", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", "typecheck": "tsc --noEmit" }, "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", diff --git a/examples/assets/src/index.ts b/examples/assets/src/index.ts index a3fef446..f6655d71 100644 --- a/examples/assets/src/index.ts +++ b/examples/assets/src/index.ts @@ -5,7 +5,7 @@ // Object runs the prompt through a Workers AI text-to-image model, // writes the generated PNG into its Workspace, then uploads that // file to R2 and returns a presigned URL through -// @cloudflare/workspace/assets. +// @cloudflare/computer/assets. // // Wire shape: // @@ -25,8 +25,8 @@ // won't produce a working link. Deploy it and hit the deployed URL. import { DurableObject } from "cloudflare:workers"; -import { type DurableObjectStorageLike, Workspace } from "@cloudflare/workspace"; -import { createAssets } from "@cloudflare/workspace/assets"; +import { type DurableObjectStorageLike, Workspace } from "@cloudflare/computer"; +import { createAssets } from "@cloudflare/computer/assets"; // Black Forest Labs FLUX.2 [klein] 9B — a fast text-to-image model. // Returns the image as a base64 string. diff --git a/examples/assets/wrangler.jsonc b/examples/assets/wrangler.jsonc index 273374aa..f52a8d2f 100644 --- a/examples/assets/wrangler.jsonc +++ b/examples/assets/wrangler.jsonc @@ -1,14 +1,14 @@ { // Example: Worker + Durable Object that turns a prompt into an // image with Workers AI, writes it into a Workspace, and returns - // a shareable link via @cloudflare/workspace/assets. + // a shareable link via @cloudflare/computer/assets. // // Production-only. The presigner needs R2 S3 credentials set as // secrets (see the README), and the image model runs on // Cloudflare's network, so `wrangler dev` against a local stack // won't produce a working link. "$schema": "node_modules/wrangler/config-schema.json", - "name": "workspace-assets-example", + "name": "computer-assets-example", "main": "src/index.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat"], @@ -33,12 +33,12 @@ "r2_buckets": [ { "binding": "ASSETS", - "bucket_name": "workspace-assets-example" + "bucket_name": "computer-assets-example" } ], "vars": { - "ASSETS_BUCKET_NAME": "workspace-assets-example" + "ASSETS_BUCKET_NAME": "computer-assets-example" }, "migrations": [ diff --git a/examples/container/Dockerfile b/examples/container/Dockerfile index d709b44c..0641c58e 100644 --- a/examples/container/Dockerfile +++ b/examples/container/Dockerfile @@ -1,19 +1,20 @@ # Container image for the container example. # -# Pulls the wsd binary out of the public GHCR image. That image is +# Pulls the computerd binary out of the public GHCR image. That image is # a single layer over `scratch` whose only contents are the SEA -# binary at /usr/local/bin/wsd; we COPY it into a slim debian +# binary at /usr/local/bin/computerd; we COPY it into a slim debian # runtime below. The :VERSION tag is rewritten in lockstep with # the rest of the monorepo by script/set-versions.mjs. # -# wsd mounts a FUSE filesystem at MOUNT_POINT so exec'd commands +# computerd mounts a FUSE filesystem at MOUNT_POINT so exec'd commands # see the same VFS the RPC surface reads and writes. With # FUSE_MOUNT=auto (below) the same image works in both directions: # Cloudflare Containers expose /dev/fuse to the workload, so the -# real FUSE backend mounts; `wrangler dev` doesn't, so wsd falls +# real FUSE backend mounts; `wrangler dev` doesn't, so computerd falls # back to the userspace shim transparently. -FROM ghcr.io/cloudflare/workspace-wsd-linux-x64:0.0.0-alpha.13 AS wsd + +FROM ghcr.io/cloudflare/computer-computerd-linux-x64:0.0.0-alpha.13 AS computerd FROM debian:stable-slim @@ -29,9 +30,9 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* -COPY --from=wsd /usr/local/bin/wsd /usr/local/bin/wsd +COPY --from=computerd /usr/local/bin/computerd /usr/local/bin/computerd -# wsd's defaults: HTTP+WS on :8080, FUSE mount on MOUNT_POINT. +# computerd's defaults: HTTP+WS on :8080, FUSE mount on MOUNT_POINT. # FUSE_MOUNT=auto picks real FUSE on Cloudflare Containers (where # /dev/fuse is exposed) and the userspace shim under wrangler dev. ENV PORT=8080 @@ -39,4 +40,4 @@ ENV MOUNT_POINT=/workspace ENV FUSE_MOUNT=auto EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] diff --git a/examples/container/README.md b/examples/container/README.md index 8c9206ec..863b29c4 100644 --- a/examples/container/README.md +++ b/examples/container/README.md @@ -8,7 +8,7 @@ > for production use at this time. A Cloudflare Worker + Durable Object that boots a Container running -the `wsd` daemon and exposes a minimal `write` / `read` / `exec` +the `computerd` daemon and exposes a minimal `write` / `read` / `exec` HTTP surface, modelled on the [cloudflare/sandbox-sdk](https://github.com/cloudflare/sandbox-sdk) bridge. @@ -19,30 +19,30 @@ bridge. client ─► Worker /c//{file,exec} │ (DO RPC calls) ▼ - DO (ContainerExample) ──► Container ──► wsd (:8080) + DO (ContainerExample) ──► Container ──► computerd (:8080) ▲ │ - │ ws://workspace.internal/ws │ + │ ws://computer.internal/ws │ └────────── capnweb session ◄──────┘ ``` 1. The DO constructs a `CloudflareContainerBackend` from - `@cloudflare/workspace/backends/container` and hands it to a - `Workspace` instance. That backend owns the entire wsd lifecycle: + `@cloudflare/computer/backends/container` and hands it to a + `Workspace` instance. That backend owns the entire computerd lifecycle: container start, outbound egress interception, port-readiness polling, POST - `/connect` to wsd, `/ws` upgrade routing, and capnweb session + `/connect` to computerd, `/ws` upgrade routing, and capnweb session attach. -2. wsd reaches the Worker through the container's **outbound - interception** (`ctx.container.interceptOutboundHttp("workspace.internal", +2. computerd reaches the Worker through the container's **outbound + interception** (`ctx.container.interceptOutboundHttp("computer.internal", …)`, set up by the backend). The DO passes `ctx.exports.WorkspaceProxy({ props: { binding, id } })` as the egress fetcher; that `WorkerEntrypoint` (re-exported from - `@cloudflare/workspace`) routes `/ws` upgrades back to the owning DO. + `@cloudflare/computer`) routes `/ws` upgrades back to the owning DO. 3. When `Workspace.ready()` is called for the first time, the - backend posts `/connect` into wsd with - `{ url: "http://workspace.internal" }`. wsd polls - `workspace.internal/health`, then dials - `ws://workspace.internal/ws`. + backend posts `/connect` into computerd with + `{ url: "http://computer.internal" }`. computerd polls + `computer.internal/health`, then dials + `ws://computer.internal/ws`. 4. `WorkspaceProxy.fetch` forwards the upgrade to the DO's `fetch()` via the DO binding looked up from its props. The DO's `fetch()` delegates to `backend.handleFetch(req)`, which performs the @@ -60,7 +60,7 @@ The DO extends the plain `DurableObject` class from `cloudflare:workers`. The container lifecycle plumbing all lives in `CloudflareContainerBackend` — the DO is a thin host. -The container mounts wsd's VFS at `MOUNT_POINT` (`/workspace`) so +The container mounts computerd's VFS at `MOUNT_POINT` (`/workspace`) so `exec`'d commands see the same tree the RPC surface reads and writes. On Cloudflare Containers `/dev/fuse` is exposed and the real FUSE backend mounts; under `wrangler dev` the same image @@ -97,10 +97,10 @@ which contains the bytes `hello world`): ```sh # Local miniflare bucket — use this with `wrangler dev`. -npm run seed:r2:local --workspace @example/workspace-container +npm run seed:r2:local --workspace @example/computer-container # Real Cloudflare R2 bucket — use this after `wrangler deploy`. -npm run seed:r2 --workspace @example/workspace-container +npm run seed:r2 --workspace @example/computer-container ``` Then: @@ -128,7 +128,7 @@ POST /c//exec { command | argv, cwd?, encoding? } The Worker enables Cloudflare's built-in tracing in `wrangler.jsonc` (`observability.traces.enabled = true`) and wires the workspace observer to `ctx.tracing` via the -`@cloudflare/workspace/observe/cloudflare` adapter. Every workspace +`@cloudflare/computer/observe/cloudflare` adapter. Every workspace operation (`workspace.connect`, `workspace.sync.push`, `workspace.sync.pull`, `workspace.shell.exec`, and the `workspace.fs.*` family) opens a span on the runtime, so the @@ -143,18 +143,18 @@ required in this example. ## Run it locally Requires Docker. The Dockerfile pulls -`ghcr.io/cloudflare/workspace-wsd-linux-x64:` from the +`ghcr.io/cloudflare/computer-computerd-linux-x64:` from the public GitHub Container Registry on first build, so no local image prep is needed. ```sh -npm run dev --workspace @example/workspace-container +npm run dev --workspace @example/computer-container ``` Smoke test: ```sh -# Trigger the container (first call also boots wsd + the capnweb session). +# Trigger the container (first call also boots computerd + the capnweb session). curl http://127.0.0.1:8787/ # Write a file at /workspace/hello.txt @@ -174,7 +174,7 @@ curl -X POST http://127.0.0.1:8787/c/demo/exec \ ``` examples/container/ - Dockerfile debian + libfuse + wsd binary (ENTRYPOINT) + Dockerfile debian + libfuse + computerd binary (ENTRYPOINT) wrangler.jsonc Worker + DO + Container binding src/index.ts Worker handler, DO (ContainerExample) ``` @@ -186,9 +186,9 @@ examples/container/ the DO to expose an async-iterable RPC; v1 keeps the surface flat. - **No auth.** The egress proxy trusts anything the container sends. Fine for in-DO traffic (only the owning Worker can address it), - but the moment we expose `workspace.internal` more broadly we need + but the moment we expose `computer.internal` more broadly we need a handshake. -- **One-shot session.** If wsd's WebSocket drops mid-session, the +- **One-shot session.** If computerd's WebSocket drops mid-session, the cached `BackendHandle` goes stale and the next call throws. `Workspace.ready()` will retry on the next call, but in-flight operations are lost. Transparent reconnect is deferred work. diff --git a/examples/container/package.json b/examples/container/package.json index a477563c..06b34929 100644 --- a/examples/container/package.json +++ b/examples/container/package.json @@ -1,18 +1,18 @@ { - "name": "@example/workspace-container", + "name": "@example/computer-container", "version": "0.0.0", "private": true, "type": "module", - "description": "Example Worker + Durable Object that uses @cloudflare/workspace inside a Cloudflare Container.", + "description": "Example Worker + Durable Object that uses @cloudflare/computer inside a Cloudflare Container.", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", "typecheck": "tsc --noEmit", - "seed:r2": "wrangler r2 object put workspace-container-hello/hello.txt --file ./seed/data/hello.txt --remote", - "seed:r2:local": "wrangler r2 object put workspace-container-hello/hello.txt --file ./seed/data/hello.txt --local" + "seed:r2": "wrangler r2 object put computer-container-hello/hello.txt --file ./seed/data/hello.txt --remote", + "seed:r2:local": "wrangler r2 object put computer-container-hello/hello.txt --file ./seed/data/hello.txt --local" }, "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", diff --git a/examples/container/script/run b/examples/container/script/run index f01d6c4a..2e8242e5 100755 --- a/examples/container/script/run +++ b/examples/container/script/run @@ -3,7 +3,7 @@ # example. Demonstrates the round-trip between the file HTTP # surface (which writes through the DO into the workspace VFS) and # the exec HTTP surface (which runs commands inside the container, -# where wsd's FUSE / shim mount exposes the same tree at +# where computerd's FUSE / shim mount exposes the same tree at # /workspace). # # Run `npm run dev` in another terminal first, then: @@ -40,7 +40,7 @@ curl -fsS -X POST "${BASE_URL}/c/${NAME}/exec" \ -H 'content-type: application/json' \ -d "{\"command\":\"echo hello-from-exec > /workspace/${PATH_VIA_EXEC} && cat /workspace/${PATH_VIA_EXEC}\",\"encoding\":\"utf8\"}" printf '\n' -# No settle wait needed: wsd's `beforeFetch` hook runs the shim's +# No settle wait needed: computerd's `beforeFetch` hook runs the shim's # disk→VFS reconcile synchronously inside `fetchChanges`, so the # GET below is guaranteed to see whatever the exec just wrote. diff --git a/examples/container/src/index.ts b/examples/container/src/index.ts index b09c3df9..7e503972 100644 --- a/examples/container/src/index.ts +++ b/examples/container/src/index.ts @@ -12,9 +12,9 @@ // client ─► Worker /c//{file,exec} // │ (DO RPC) // ▼ -// ContainerExample DO ──► Container ──► wsd (:8080) +// ContainerExample DO ──► Container ──► computerd (:8080) // ▲ │ -// │ ws://workspace.internal/ws │ +// │ ws://computer.internal/ws │ // └─── capnweb session ◀─────────────┘ import { DurableObject, tracing } from "cloudflare:workers"; @@ -26,16 +26,16 @@ import { type WorkspaceOptions, WorkspaceProxy, withWorkspace, -} from "@cloudflare/workspace"; +} from "@cloudflare/computer"; import { CloudflareContainerBackend, withWorkspaceContainer, -} from "@cloudflare/workspace/backends/container"; -import { createCloudflareObserver } from "@cloudflare/workspace/observe/cloudflare"; +} from "@cloudflare/computer/backends/container"; +import { createCloudflareObserver } from "@cloudflare/computer/observe/cloudflare"; // Re-export so the runtime can build a loopback binding for the // container egress (ctx.exports.WorkspaceProxy below). The class -// itself lives in @cloudflare/workspace; the re-export is what +// itself lives in @cloudflare/computer; the re-export is what // puts it in the worker's top-level module graph. export { WorkspaceProxy }; @@ -89,9 +89,9 @@ function workspaceOptions(self: InstanceType): WorkspaceOp // withWorkspace owns the Workspace and installs the prototype // accessor `getWorkspace` dispatches to. Methods on the client it // hands back round-trip into this DO; the actual SyncRPC + ShellRPC -// traffic stays on the wsd ↔ DO capnweb wire. +// traffic stays on the computerd ↔ DO capnweb wire. export class ContainerExample extends withWorkspace(ContainerBase, workspaceOptions) { - // ---- WebSocket: wsd's outbound /ws upgrade --------------------- + // ---- WebSocket: computerd's outbound /ws upgrade --------------------- override async fetch(request: Request): Promise { return this.backend.handleFetch(request); @@ -109,11 +109,11 @@ interface ExecRequest { encoding?: "utf8"; } -// wsd mounts the VFS at /workspace inside the container. The file +// computerd mounts the VFS at /workspace inside the container. The file // handler enforces that every path it touches sits under that // root; callers pass the absolute VFS path verbatim in the URL // (e.g. PUT /c//file/workspace/hello.txt writes -// /workspace/hello.txt, exactly as wsd sees it). Anything outside +// /workspace/hello.txt, exactly as computerd sees it). Anything outside // /workspace is rejected up front — the example only wants to // expose the mounted tree, not the container's full filesystem. const MOUNT_ROOT = "/workspace"; diff --git a/examples/container/wrangler.jsonc b/examples/container/wrangler.jsonc index 4593517b..7c79e33e 100644 --- a/examples/container/wrangler.jsonc +++ b/examples/container/wrangler.jsonc @@ -1,10 +1,10 @@ { // Example: Worker + Durable Object hosting a Cloudflare Container whose - // ENTRYPOINT is the wsd daemon. This scaffold proves the wiring — the + // ENTRYPOINT is the computerd daemon. This scaffold proves the wiring — the // Worker reaches the DO, the DO boots the container, the container's - // wsd process answers /health on port 8080. + // computerd process answers /health on port 8080. "$schema": "node_modules/wrangler/config-schema.json", - "name": "workspace-container-example", + "name": "computer-container-example", "main": "src/index.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat"], @@ -12,7 +12,7 @@ "containers": [ { "class_name": "ContainerExample", - // Built from the local Dockerfile, which COPYs the wsd binary + // Built from the local Dockerfile, which COPYs the computerd binary // staged into ./build/ by the predev / predeploy hook. "image": "./Dockerfile", "instance_type": "standard-2", @@ -33,17 +33,17 @@ // R2 bucket mounted into every Workspace at /workspace/r2 via // R2Bucket(...). Seed it once with: - // npm run seed:r2 --workspace @example/workspace-container + // npm run seed:r2 --workspace @example/computer-container // which uploads ./seed/data/hello.txt to the bucket. // `bucket_name` is what `wrangler dev` and `wrangler deploy` both // look at; omitting `preview_bucket_name` makes the same bucket // back the local miniflare and the deployed worker, which keeps // the seed scripts honest (a `--local` put and a `--remote` put - // both target `workspace-container-hello`). + // both target `computer-container-hello`). "r2_buckets": [ { "binding": "Bucket", - "bucket_name": "workspace-container-hello" + "bucket_name": "computer-container-hello" } ], diff --git a/examples/think-compare-runtimes/.dev.vars.example b/examples/think-compare-runtimes/.dev.vars.example index edd885ed..dcc2985a 100644 --- a/examples/think-compare-runtimes/.dev.vars.example +++ b/examples/think-compare-runtimes/.dev.vars.example @@ -2,6 +2,6 @@ # # wrangler dev / vite dev under colima or Docker Desktop does not # expose /dev/fuse to Workers Containers. This tells the Workspace -# container to run wsd with its userspace shim locally. Production -# leaves this unset so wsd uses real kernel FUSE when available. +# container to run computerd with its userspace shim locally. Production +# leaves this unset so computerd uses real kernel FUSE when available. FUSE_MOUNT=shim diff --git a/examples/think-compare-runtimes/Dockerfile.workspace b/examples/think-compare-runtimes/Dockerfile.workspace index 9c1df801..dcb73622 100644 --- a/examples/think-compare-runtimes/Dockerfile.workspace +++ b/examples/think-compare-runtimes/Dockerfile.workspace @@ -1,17 +1,18 @@ -# Workspace runtime image for the comparison example. It runs the -# wsd daemon so shell commands can see the same /workspace tree that +# Computer runtime image for the comparison example. It runs the +# computerd daemon so shell commands can see the same /workspace tree that # Workspace.fs stores in Durable Object storage. # # Base image is node:22-trixie-slim so the agent has node + npm # available out of the box for in-container verification (e.g. npm -# install / npm test). It's also Debian-glibc, which is what the wsd +# install / npm test). It's also Debian-glibc, which is what the computerd # SEA binary is built against. # -# FUSE_MOUNT=auto lets wsd use real kernel FUSE when available and +# FUSE_MOUNT=auto lets computerd use real kernel FUSE when available and # fall back to the userspace shim in local development environments # that do not expose /dev/fuse. -FROM ghcr.io/cloudflare/workspace-wsd-linux-x64:0.0.0-alpha.13 AS wsd + +FROM ghcr.io/cloudflare/computer-computerd-linux-x64:0.0.0-alpha.13 AS computerd FROM --platform=linux/amd64 node:22-trixie-slim @@ -20,11 +21,11 @@ RUN apt-get update \ ca-certificates git fuse3 libfuse2t64 \ && rm -rf /var/lib/apt/lists/* -COPY --from=wsd /usr/local/bin/wsd /usr/local/bin/wsd +COPY --from=computerd /usr/local/bin/computerd /usr/local/bin/computerd ENV PORT=8080 ENV MOUNT_POINT=/workspace ENV FUSE_MOUNT=auto EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] diff --git a/examples/think-compare-runtimes/package.json b/examples/think-compare-runtimes/package.json index d5181895..2e8f0713 100644 --- a/examples/think-compare-runtimes/package.json +++ b/examples/think-compare-runtimes/package.json @@ -4,9 +4,9 @@ "version": "0.0.0", "type": "module", "scripts": { - "build:wsd": "npm run build:docker --workspace @cloudflare/workspace-wsd", - "predev": "npm run build:wsd", - "predeploy": "npm run build:wsd", + "build:computerd": "npm run build:docker --workspace @cloudflare/computerd", + "predev": "npm run build:computerd", + "predeploy": "npm run build:computerd", "dev": "vite", "build": "tsc -b && vite build", "test": "vitest run", @@ -16,12 +16,12 @@ "cf-typegen": "wrangler types" }, "dependencies": { + "@cloudflare/computer": "*", + "@cloudflare/computer-rpc": "*", "@cloudflare/kumo": "^2.4.1", "@cloudflare/sandbox": "^0.11.0", "@cloudflare/shell": "^0.4.3", "@cloudflare/think": "^0.15.1", - "@cloudflare/workspace": "*", - "@cloudflare/workspace-rpc": "*", "@phosphor-icons/react": "^2.1.10", "@shikijs/core": "^4.2.0", "@shikijs/engine-javascript": "^4.2.0", diff --git a/examples/think-compare-runtimes/shared/events.ts b/examples/think-compare-runtimes/shared/events.ts index 20a531b9..b81e1696 100644 --- a/examples/think-compare-runtimes/shared/events.ts +++ b/examples/think-compare-runtimes/shared/events.ts @@ -1,6 +1,6 @@ export type RuntimeId = "workspace" | "sandbox"; export type EventRuntime = RuntimeId | "both"; -export type ExecutionTarget = "worker-shell" | "workspace-container" | "sandbox-container"; +export type ExecutionTarget = "worker-shell" | "computer-container" | "sandbox-container"; export type RunEventKind = | "run_started" diff --git a/examples/think-compare-runtimes/src/dashboard-model.test.ts b/examples/think-compare-runtimes/src/dashboard-model.test.ts index 95b7146b..3634532f 100644 --- a/examples/think-compare-runtimes/src/dashboard-model.test.ts +++ b/examples/think-compare-runtimes/src/dashboard-model.test.ts @@ -100,7 +100,7 @@ describe("buildDashboardModel", () => { title: "exec called", detail: JSON.stringify({ command: "npm run check", - executionTarget: "workspace-container", + executionTarget: "computer-container", cwd: "/workspace/repo", }), timestamp: "2026-06-04T00:00:06.000Z", diff --git a/examples/think-compare-runtimes/src/dashboard-model.ts b/examples/think-compare-runtimes/src/dashboard-model.ts index 25172760..2edc3825 100644 --- a/examples/think-compare-runtimes/src/dashboard-model.ts +++ b/examples/think-compare-runtimes/src/dashboard-model.ts @@ -53,7 +53,7 @@ export function buildDashboardModel(events: RunEvent[], nowIso: string | null): (fact) => fact.executionTarget === "worker-shell", ).length; const workspaceContainerExecs = execs.filter( - (fact) => fact.executionTarget === "workspace-container", + (fact) => fact.executionTarget === "computer-container", ).length; const sandboxContainerExecs = execs.filter( (fact) => fact.executionTarget === "sandbox-container", diff --git a/examples/think-compare-runtimes/src/run-event-facts.test.ts b/examples/think-compare-runtimes/src/run-event-facts.test.ts index 42e2998e..a535012a 100644 --- a/examples/think-compare-runtimes/src/run-event-facts.test.ts +++ b/examples/think-compare-runtimes/src/run-event-facts.test.ts @@ -88,7 +88,7 @@ describe("run-event-facts", () => { exitCode: 0, command: "npm run check", cwd: "/workspace/repo", - executionTarget: "workspace-container", + executionTarget: "computer-container", }), }), ); @@ -97,7 +97,7 @@ describe("run-event-facts", () => { { label: "command", value: "npm run check" }, { label: "path", value: "/workspace/repo/docs/index.md" }, { label: "cwd", value: "/workspace/repo" }, - { label: "executionTarget", value: "workspace-container" }, + { label: "executionTarget", value: "computer-container" }, { label: "exitCode", value: "0" }, { label: "stdout", value: "ok\n" }, ]); diff --git a/examples/think-compare-runtimes/src/run-event-facts.ts b/examples/think-compare-runtimes/src/run-event-facts.ts index d32cd855..b960aea0 100644 --- a/examples/think-compare-runtimes/src/run-event-facts.ts +++ b/examples/think-compare-runtimes/src/run-event-facts.ts @@ -153,7 +153,7 @@ function executionTargetForEvent( const backend = stringField(detail, "backend"); if (event.runtime === "workspace" && backend === "shell") return "worker-shell"; - if (event.runtime === "workspace" && backend === "container") return "workspace-container"; + if (event.runtime === "workspace" && backend === "container") return "computer-container"; if (event.runtime === "sandbox" && toolForEvent(event, detail) === "exec") { return "sandbox-container"; } @@ -202,6 +202,6 @@ function isToolName(value: string | null): value is ToolName { function isExecutionTarget(value: string | null): value is ExecutionTarget { return ( - value === "worker-shell" || value === "workspace-container" || value === "sandbox-container" + value === "worker-shell" || value === "computer-container" || value === "sandbox-container" ); } diff --git a/examples/think-compare-runtimes/src/runtime-panel-model.test.ts b/examples/think-compare-runtimes/src/runtime-panel-model.test.ts index 56450934..e4fab0ec 100644 --- a/examples/think-compare-runtimes/src/runtime-panel-model.test.ts +++ b/examples/think-compare-runtimes/src/runtime-panel-model.test.ts @@ -53,7 +53,7 @@ describe("buildRuntimePanelModel", () => { detail: JSON.stringify({ command: "npm run check", cwd: "/workspace/repo", - executionTarget: "workspace-container", + executionTarget: "computer-container", exitCode: 0, stdout: "docs check passed", stderr: "", @@ -114,7 +114,7 @@ describe("buildRuntimePanelModel", () => { kind: "exec", label: "Ran command", command: "npm run check", - executionTarget: "workspace-container", + executionTarget: "computer-container", exitCode: 0, presentation: "terminal", }, @@ -419,8 +419,8 @@ describe("buildRuntimePanelModel", () => { runtime: "workspace", kind: "container_acquired" as RunEvent["kind"], detail: JSON.stringify({ - executionTarget: "workspace-container", - containerId: "workspace-container-1", + executionTarget: "computer-container", + containerId: "computer-container-1", }), timestamp: "2026-06-04T00:00:02.000Z", }), @@ -440,7 +440,7 @@ describe("buildRuntimePanelModel", () => { detail: JSON.stringify({ command: "npm run check", cwd: "/workspace/repo", - executionTarget: "workspace-container", + executionTarget: "computer-container", exitCode: 0, stdout: "docs check passed", stderr: "", @@ -452,8 +452,8 @@ describe("buildRuntimePanelModel", () => { runtime: "workspace", kind: "container_released" as RunEvent["kind"], detail: JSON.stringify({ - executionTarget: "workspace-container", - containerId: "workspace-container-1", + executionTarget: "computer-container", + containerId: "computer-container-1", }), timestamp: "2026-06-04T00:00:12.000Z", }), @@ -462,8 +462,8 @@ describe("buildRuntimePanelModel", () => { runtime: "workspace", kind: "container_release_scheduled" as RunEvent["kind"], detail: JSON.stringify({ - executionTarget: "workspace-container", - containerId: "workspace-container-1", + executionTarget: "computer-container", + containerId: "computer-container-1", sleepAfterMs: 120_000, }), timestamp: "2026-06-04T00:00:12.000Z", @@ -472,7 +472,7 @@ describe("buildRuntimePanelModel", () => { const telemetry = buildDashboardModel(events, "2026-06-04T00:00:13.000Z").runtimes.workspace; const model = buildRuntimePanelModel(events, "workspace", telemetry); - const containerLane = model.lanes.find((lane) => lane.id === "workspace-container"); + const containerLane = model.lanes.find((lane) => lane.id === "computer-container"); expect(containerLane?.segments.map((segment) => [segment.label, segment.status])).toEqual([ ["Container assigned", "lease"], diff --git a/examples/think-compare-runtimes/src/runtime-panel-model.ts b/examples/think-compare-runtimes/src/runtime-panel-model.ts index b2dadfa1..f54f16c6 100644 --- a/examples/think-compare-runtimes/src/runtime-panel-model.ts +++ b/examples/think-compare-runtimes/src/runtime-panel-model.ts @@ -107,10 +107,10 @@ function workspaceLanes(events: RunEvent[]): TimelineLane[] { markers: [], }, { - id: "workspace-container", + id: "computer-container", label: "Container", tone: "container", - segments: containerSegments(events, "workspace-container"), + segments: containerSegments(events, "computer-container"), markers: [], }, ]; @@ -573,7 +573,7 @@ function stringDetail(fact: ReturnType, key: string): strin function executionTargetLabel(target: ExecutionTarget): string { if (target === "worker-shell") return "worker shell"; - if (target === "workspace-container") return "workspace container"; + if (target === "computer-container") return "workspace container"; return "sandbox"; } diff --git a/examples/think-compare-runtimes/src/runtime-wing.tsx b/examples/think-compare-runtimes/src/runtime-wing.tsx index 948bf297..6e0ad54e 100644 --- a/examples/think-compare-runtimes/src/runtime-wing.tsx +++ b/examples/think-compare-runtimes/src/runtime-wing.tsx @@ -22,7 +22,7 @@ const runtimeCopy: Record< > = { workspace: { label: "Workspace", - packageName: "@cloudflare/workspace", + packageName: "@cloudflare/computer", }, sandbox: { label: "Sandbox", @@ -361,7 +361,7 @@ function compactToneClass(item: AgentWorkItem): string { function executionTargetCopy(target: NonNullable): string { if (target === "worker-shell") return "dynamic worker"; - if (target === "workspace-container") return "workspace container"; + if (target === "computer-container") return "workspace container"; return "sandbox container"; } diff --git a/examples/think-compare-runtimes/worker/workspace-container-pool.test.ts b/examples/think-compare-runtimes/worker/computer-container-pool.test.ts similarity index 91% rename from examples/think-compare-runtimes/worker/workspace-container-pool.test.ts rename to examples/think-compare-runtimes/worker/computer-container-pool.test.ts index 9912b644..75ab3c6c 100644 --- a/examples/think-compare-runtimes/worker/workspace-container-pool.test.ts +++ b/examples/think-compare-runtimes/worker/computer-container-pool.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test, vi } from "vitest"; -import type { WorkspaceContainerHost } from "./workspace-container-pool"; +import type { WorkspaceContainerHost } from "./computer-container-pool"; vi.mock("cloudflare:workers", () => ({ DurableObject: class {}, @@ -8,7 +8,7 @@ vi.mock("cloudflare:workers", () => ({ describe("createWorkspaceWarmPoolRuntime", () => { test("keeps Workspace warm-start readiness inside the container host", async () => { - const { createWorkspaceWarmPoolRuntime } = await import("./workspace-container-pool"); + const { createWorkspaceWarmPoolRuntime } = await import("./computer-container-pool"); const calls: string[] = []; const host = { async startWarmContainer(env: Record, inactivityTimeoutMs: number) { @@ -40,7 +40,7 @@ describe("createWorkspaceWarmPoolRuntime", () => { }); test("retries Workspace container placement while waiting for health", async () => { - const { startWorkspaceContainerAndWait } = await import("./workspace-container-pool"); + const { startWorkspaceContainerAndWait } = await import("./computer-container-pool"); const calls: string[] = []; let healthAttempts = 0; const container = { diff --git a/examples/think-compare-runtimes/worker/workspace-container-pool.ts b/examples/think-compare-runtimes/worker/computer-container-pool.ts similarity index 99% rename from examples/think-compare-runtimes/worker/workspace-container-pool.ts rename to examples/think-compare-runtimes/worker/computer-container-pool.ts index 3461dabe..dbee7364 100644 --- a/examples/think-compare-runtimes/worker/workspace-container-pool.ts +++ b/examples/think-compare-runtimes/worker/computer-container-pool.ts @@ -2,7 +2,7 @@ import { DurableObject } from "cloudflare:workers"; import { type IWorkspaceContainerAPI, withWorkspaceContainer, -} from "@cloudflare/workspace/backends/container"; +} from "@cloudflare/computer/backends/container"; import { type ContainerPoolConfigEnv, containerSleepAfterMs } from "./container-config"; import type { WarmPoolRuntime } from "./container-pool-manager"; import { ContainerWarmPool } from "./container-warm-pool"; diff --git a/examples/think-compare-runtimes/worker/container-pools.ts b/examples/think-compare-runtimes/worker/container-pools.ts index e2f85e4b..bf67bd8c 100644 --- a/examples/think-compare-runtimes/worker/container-pools.ts +++ b/examples/think-compare-runtimes/worker/container-pools.ts @@ -1,3 +1,9 @@ +export { + WorkspaceContainerHost, + type WorkspaceContainerHostHandle, + type WorkspacePoolEnv, + WorkspaceWarmPool, +} from "./computer-container-pool"; export { containerSleepAfter, containerSleepAfterMs } from "./container-config"; export { type ContainerWarmPoolHandle, @@ -5,9 +11,3 @@ export { getWarmPoolHandle, } from "./container-warm-pool"; export { type SandboxPoolEnv, SandboxWarmPool } from "./sandbox-container-pool"; -export { - WorkspaceContainerHost, - type WorkspaceContainerHostHandle, - type WorkspacePoolEnv, - WorkspaceWarmPool, -} from "./workspace-container-pool"; diff --git a/examples/think-compare-runtimes/worker/index.ts b/examples/think-compare-runtimes/worker/index.ts index 336a161c..1181cc74 100644 --- a/examples/think-compare-runtimes/worker/index.ts +++ b/examples/think-compare-runtimes/worker/index.ts @@ -1,5 +1,5 @@ +import type { WorkerBackendOptions } from "@cloudflare/computer/backends/worker"; import type { Sandbox as SandboxDO } from "@cloudflare/sandbox"; -import type { WorkerBackendOptions } from "@cloudflare/workspace/backends/worker"; import { getServerByName, routePartykitRequest, Server } from "partyserver"; import type { RunEvent } from "../shared/events"; import { comparisonFixture } from "../shared/fixture"; diff --git a/examples/think-compare-runtimes/worker/runtime/adapter.test.ts b/examples/think-compare-runtimes/worker/runtime/adapter.test.ts index 0d1aeb29..6cb8c706 100644 --- a/examples/think-compare-runtimes/worker/runtime/adapter.test.ts +++ b/examples/think-compare-runtimes/worker/runtime/adapter.test.ts @@ -40,7 +40,7 @@ describe("runtime adapters", () => { exitCode: 0, stdout: "node --version\n", stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }); expect(files.get("/workspace/repo/src/created.ts")).toBe("created"); expect(recorder.events().map((event) => event.runtime)).toEqual([ diff --git a/examples/think-compare-runtimes/worker/runtime/comparison-run.test.ts b/examples/think-compare-runtimes/worker/runtime/comparison-run.test.ts index 7979c765..f3efcf6f 100644 --- a/examples/think-compare-runtimes/worker/runtime/comparison-run.test.ts +++ b/examples/think-compare-runtimes/worker/runtime/comparison-run.test.ts @@ -50,7 +50,7 @@ describe("runFixtureComparison", () => { exitCode: 0, stdout: `workspace ${command}\n`, stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }; }, }, diff --git a/examples/think-compare-runtimes/worker/runtime/exec-tools.test.ts b/examples/think-compare-runtimes/worker/runtime/exec-tools.test.ts index 4a31e94b..089759ce 100644 --- a/examples/think-compare-runtimes/worker/runtime/exec-tools.test.ts +++ b/examples/think-compare-runtimes/worker/runtime/exec-tools.test.ts @@ -19,7 +19,7 @@ describe("createRuntimeExecTool", () => { exitCode: 0, stdout: "ok\n", stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }; }, }, @@ -31,7 +31,7 @@ describe("createRuntimeExecTool", () => { exitCode: 0, stdout: "ok\n", stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }); expect(calls).toEqual([ diff --git a/examples/think-compare-runtimes/worker/runtime/workspace.test.ts b/examples/think-compare-runtimes/worker/runtime/workspace.test.ts index d6a76213..262e83e3 100644 --- a/examples/think-compare-runtimes/worker/runtime/workspace.test.ts +++ b/examples/think-compare-runtimes/worker/runtime/workspace.test.ts @@ -86,7 +86,7 @@ describe("createWorkspaceFixtureRuntime", () => { exitCode: 0, stdout: "workspace\n", stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }); expect(calls).toEqual([ "ready container", @@ -138,36 +138,39 @@ describe("createWorkspaceFixtureRuntime", () => { "npx vitest", "tsc --noEmit", "./scripts/check-docs.mjs", - ])("exec routes runtime and package commands to the Workspace container backend: %s", async (command) => { - const calls: string[] = []; - const runner = createWorkspaceCommandRunner({ - async ready(backend?: string) { - calls.push(`ready ${backend ?? "default"}`); - }, - shell: { - async exec( - actualCommand: string, - options?: { backend?: string; cwd?: string; encoding?: "utf8" }, - ) { - calls.push(`${actualCommand} ${options?.backend} ${options?.cwd} ${options?.encoding}`); - return { - async result() { - return { exitCode: 0, stdout: "workspace\n", stderr: "", pushed: 1, pulled: 1 }; - }, - }; + ])( + "exec routes runtime and package commands to the Workspace container backend: %s", + async (command) => { + const calls: string[] = []; + const runner = createWorkspaceCommandRunner({ + async ready(backend?: string) { + calls.push(`ready ${backend ?? "default"}`); }, - }, - }); - - await expect(runner.exec(command, { cwd: "/workspace/repo" })).resolves.toMatchObject({ - exitCode: 0, - stdout: "workspace\n", - stderr: "", - executionTarget: "workspace-container", - }); - - expect(calls).toEqual(["ready container", `${command} container /workspace/repo utf8`]); - }); + shell: { + async exec( + actualCommand: string, + options?: { backend?: string; cwd?: string; encoding?: "utf8" }, + ) { + calls.push(`${actualCommand} ${options?.backend} ${options?.cwd} ${options?.encoding}`); + return { + async result() { + return { exitCode: 0, stdout: "workspace\n", stderr: "", pushed: 1, pulled: 1 }; + }, + }; + }, + }, + }); + + await expect(runner.exec(command, { cwd: "/workspace/repo" })).resolves.toMatchObject({ + exitCode: 0, + stdout: "workspace\n", + stderr: "", + executionTarget: "computer-container", + }); + + expect(calls).toEqual(["ready container", `${command} container /workspace/repo utf8`]); + }, + ); }); function expectedSeedCalls(): Array<{ type: "mkdir" | "write"; path: string; contents?: string }> { diff --git a/examples/think-compare-runtimes/worker/runtime/workspace.ts b/examples/think-compare-runtimes/worker/runtime/workspace.ts index 47527d37..78f5ff28 100644 --- a/examples/think-compare-runtimes/worker/runtime/workspace.ts +++ b/examples/think-compare-runtimes/worker/runtime/workspace.ts @@ -211,5 +211,5 @@ function isEnvironmentAssignment(token: string): boolean { } function workspaceExecutionTarget(backend: "container" | "shell") { - return backend === "shell" ? "worker-shell" : "workspace-container"; + return backend === "shell" ? "worker-shell" : "computer-container"; } diff --git a/examples/think-compare-runtimes/worker/think/agents.test.ts b/examples/think-compare-runtimes/worker/think/agents.test.ts index 6d1cb784..031f429f 100644 --- a/examples/think-compare-runtimes/worker/think/agents.test.ts +++ b/examples/think-compare-runtimes/worker/think/agents.test.ts @@ -32,7 +32,7 @@ vi.mock("@cloudflare/think", () => ({ }, })); -vi.mock("@cloudflare/workspace", () => ({ +vi.mock("@cloudflare/computer", () => ({ Workspace: class { readonly fs = { mkdir: async () => {}, @@ -59,7 +59,7 @@ vi.mock("@cloudflare/workspace", () => ({ WorkspaceServiceProxy: class {}, })); -vi.mock("@cloudflare/workspace/backends/container", () => ({ +vi.mock("@cloudflare/computer/backends/container", () => ({ CloudflareContainerBackend: class { readonly id: string; @@ -74,7 +74,7 @@ vi.mock("@cloudflare/workspace/backends/container", () => ({ }, })); -vi.mock("@cloudflare/workspace/backends/worker", () => ({ +vi.mock("@cloudflare/computer/backends/worker", () => ({ WorkerBackend: class { readonly id: string; @@ -252,14 +252,14 @@ describe("WorkspaceThinkAgent", () => { { kind: "container_acquired", detail: JSON.stringify({ - executionTarget: "workspace-container", + executionTarget: "computer-container", containerId: "sandbox-physical-1", }), }, { kind: "container_released", detail: JSON.stringify({ - executionTarget: "workspace-container", + executionTarget: "computer-container", containerId: "sandbox-physical-1", }), }, diff --git a/examples/think-compare-runtimes/worker/think/agents.ts b/examples/think-compare-runtimes/worker/think/agents.ts index e0a65f44..23e6387a 100644 --- a/examples/think-compare-runtimes/worker/think/agents.ts +++ b/examples/think-compare-runtimes/worker/think/agents.ts @@ -1,14 +1,14 @@ -import { getSandbox, type Sandbox as SandboxDO } from "@cloudflare/sandbox"; -import { type ChunkContext, type StepContext, Think } from "@cloudflare/think"; import { type DurableObjectStorageLike, Workspace, WorkspaceProxy, WorkspaceServiceProxy, type WorkspaceStub, -} from "@cloudflare/workspace"; -import { CloudflareContainerBackend } from "@cloudflare/workspace/backends/container"; -import { WorkerBackend, type WorkerBackendOptions } from "@cloudflare/workspace/backends/worker"; +} from "@cloudflare/computer"; +import { CloudflareContainerBackend } from "@cloudflare/computer/backends/container"; +import { WorkerBackend, type WorkerBackendOptions } from "@cloudflare/computer/backends/worker"; +import { getSandbox, type Sandbox as SandboxDO } from "@cloudflare/sandbox"; +import { type ChunkContext, type StepContext, Think } from "@cloudflare/think"; import type { ToolSet } from "ai"; import { getServerByName } from "partyserver"; import type { ExecutionTarget, RunEventKind, RuntimeId } from "../../shared/events"; @@ -314,7 +314,7 @@ export class WorkspaceThinkAgent extends RuntimeThinkAgent { await recordContainerLifecycle(recorder, { runtime: this.runtime, kind: "container_acquired", - executionTarget: "workspace-container", + executionTarget: "computer-container", containerId, }); } @@ -359,7 +359,7 @@ export class WorkspaceThinkAgent extends RuntimeThinkAgent { await recordContainerLifecycle(recorder, { runtime: this.runtime, kind: "container_released", - executionTarget: "workspace-container", + executionTarget: "computer-container", containerId, }); } diff --git a/examples/think-compare-runtimes/worker/think/prompts.test.ts b/examples/think-compare-runtimes/worker/think/prompts.test.ts index 0c295931..153115ef 100644 --- a/examples/think-compare-runtimes/worker/think/prompts.test.ts +++ b/examples/think-compare-runtimes/worker/think/prompts.test.ts @@ -40,7 +40,7 @@ describe("runtime Think prompts", () => { test("adds Workspace-specific guidance for durable files, worker shell, and container validation", () => { const prompt = createRuntimeSystemPrompt("workspace"); - expect(prompt).toContain("Cloudflare Workspace"); + expect(prompt).toContain("Cloudflare Computer"); expect(prompt).toContain("durable workspace storage"); expect(prompt).toContain("Start Workspace discovery with the worker shell"); expect(prompt).toContain("workspace container is for Node, npm, package scripts"); diff --git a/examples/think-compare-runtimes/worker/think/prompts.ts b/examples/think-compare-runtimes/worker/think/prompts.ts index 3c1bd7ac..6e8f7499 100644 --- a/examples/think-compare-runtimes/worker/think/prompts.ts +++ b/examples/think-compare-runtimes/worker/think/prompts.ts @@ -98,7 +98,7 @@ function sharedCodingPrompt(): string { function workspaceRuntimePrompt(): string { return [ - "Runtime: Cloudflare Workspace.", + "Runtime: Cloudflare Computer.", "- Workspace file tools read and write durable workspace storage directly.", "- Start Workspace discovery with the worker shell for grep, find, ls, cat, pwd, head, tail, sed, and wc.", "- The workspace container is for Node, npm, package scripts, tests, and executable project scripts.", diff --git a/examples/think-compare-runtimes/worker/think/real-turn.test.ts b/examples/think-compare-runtimes/worker/think/real-turn.test.ts index 2ce6f6e4..54c60ff3 100644 --- a/examples/think-compare-runtimes/worker/think/real-turn.test.ts +++ b/examples/think-compare-runtimes/worker/think/real-turn.test.ts @@ -21,7 +21,7 @@ describe("runRealThinkTurn", () => { }, runner: { async exec() { - return { exitCode: 0, stdout: "", stderr: "", executionTarget: "workspace-container" }; + return { exitCode: 0, stdout: "", stderr: "", executionTarget: "computer-container" }; }, }, }); @@ -75,7 +75,7 @@ describe("runRealThinkTurn", () => { }, runner: { async exec() { - return { exitCode: 0, stdout: "", stderr: "", executionTarget: "workspace-container" }; + return { exitCode: 0, stdout: "", stderr: "", executionTarget: "computer-container" }; }, }, }); @@ -124,7 +124,7 @@ describe("runRealThinkTurn", () => { }, runner: { async exec() { - return { exitCode: 0, stdout: "", stderr: "", executionTarget: "workspace-container" }; + return { exitCode: 0, stdout: "", stderr: "", executionTarget: "computer-container" }; }, }, }); diff --git a/examples/think-compare-runtimes/worker/think/runtime-tools.test.ts b/examples/think-compare-runtimes/worker/think/runtime-tools.test.ts index 661d2f89..a19772c1 100644 --- a/examples/think-compare-runtimes/worker/think/runtime-tools.test.ts +++ b/examples/think-compare-runtimes/worker/think/runtime-tools.test.ts @@ -34,7 +34,7 @@ function createAdapter(): RuntimeAdapter { exitCode: 0, stdout: `${command} ${options?.cwd ?? ""}`.trim(), stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }; }, }; @@ -74,7 +74,7 @@ describe("createRuntimeThinkTools", () => { exitCode: 0, stdout: "node --version /workspace/repo", stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }); expect(recorder.events().map(({ runtime, kind, title }) => ({ runtime, kind, title }))).toEqual( diff --git a/examples/think-compare-runtimes/worker/think/scripted-turn.test.ts b/examples/think-compare-runtimes/worker/think/scripted-turn.test.ts index 0bb54a12..28b336cf 100644 --- a/examples/think-compare-runtimes/worker/think/scripted-turn.test.ts +++ b/examples/think-compare-runtimes/worker/think/scripted-turn.test.ts @@ -30,7 +30,7 @@ describe("runScriptedThinkToolSmoke", () => { exitCode: 0, stdout: `${command}\n`, stderr: "", - executionTarget: "workspace-container", + executionTarget: "computer-container", }; }, }, diff --git a/examples/think/Dockerfile b/examples/think/Dockerfile index 2ee693f5..ac5d1a29 100644 --- a/examples/think/Dockerfile +++ b/examples/think/Dockerfile @@ -1,14 +1,15 @@ # Container image for the think example. Identical wiring to -# examples/container — we pull a wsd binary out of the public +# examples/container — we pull a computerd binary out of the public # GHCR image, drop it into a slim debian, and run it as PID 1. # The :VERSION tag is rewritten in lockstep with the rest of the # monorepo by script/set-versions.mjs. # -# wsd mounts a FUSE filesystem at MOUNT_POINT so exec'd commands +# computerd mounts a FUSE filesystem at MOUNT_POINT so exec'd commands # (git, npm, vitest, …) see the same files the Workspace RPC # surface reads and writes. -FROM ghcr.io/cloudflare/workspace-wsd-linux-x64:0.0.0-alpha.13 AS wsd + +FROM ghcr.io/cloudflare/computer-computerd-linux-x64:0.0.0-alpha.13 AS computerd FROM debian:stable-slim @@ -31,7 +32,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* -COPY --from=wsd /usr/local/bin/wsd /usr/local/bin/wsd +COPY --from=computerd /usr/local/bin/computerd /usr/local/bin/computerd ENV PORT=8080 ENV MOUNT_POINT=/workspace @@ -43,4 +44,4 @@ ENV MOUNT_POINT=/workspace ENV FUSE_MOUNT=auto EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] diff --git a/examples/think/README.md b/examples/think/README.md index 570fcd21..ea7e5aff 100644 --- a/examples/think/README.md +++ b/examples/think/README.md @@ -9,9 +9,9 @@ A minimal example that puts [`@cloudflare/think`][think] behind a terminal chat interface. The agent is a Durable Object with a -[`@cloudflare/workspace`][workspace] VFS for a working directory and +[`@cloudflare/computer`][workspace] VFS for a working directory and the shared file and shell tools from -[`@cloudflare/workspace/tools`][tools]. The Workspace has both the +[`@cloudflare/computer/tools`][tools]. The Workspace has both the fast worker shell backend and a container backend, so the same `exec` tool can run quick text commands or full Linux userland commands. There is no task workflow: you open a terminal, type, and talk to the @@ -23,8 +23,8 @@ talks to the agent over the same WebSocket chat protocol a browser would use, so no bespoke HTTP route or transport is involved. [think]: https://www.npmjs.com/package/@cloudflare/think -[workspace]: ../../packages/workspace -[tools]: ../../packages/workspace/src/tools +[workspace]: ../../packages/computer +[tools]: ../../packages/computer/src/tools [aisdk7]: https://vercel.com/blog/ai-sdk-7 ## Shape @@ -35,9 +35,9 @@ client (npm run chat) worker │ AgentClient WebSocket │ ├────────────────────────────────────▶ Assistant DO (Think) │ /agents/assistant/ │ ├── Workers AI model - │ │ └── @cloudflare/workspace VFS + │ │ └── @cloudflare/computer VFS │ ◀───────── streamed reply ─────────┤ ├── worker backend (env.LOADER) - │ └── container backend (wsd) + │ └── container backend (computerd) ``` `src/index.ts` hands every request to `routeAgentRequest`, which @@ -53,7 +53,7 @@ model, a Workspace, and the workspace tools. ## Tools The tools come from `createAITools()` in -[`@cloudflare/workspace/tools`][tools]. This example enables the file +[`@cloudflare/computer/tools`][tools]. This example enables the file tools and opts into `exec` by passing a shell backend description; it does not configure the assets publisher, so `publish` is not offered. @@ -75,7 +75,7 @@ does not configure the assets publisher, so `publish` is not offered. and `git log` work from inside `exec` even though the shell isolate has no public network of its own. Only `https://` URLs are supported. -- `"container"` — a Cloudflare Container running `wsd` over capnweb, +- `"container"` — a Cloudflare Container running `computerd` over capnweb, modelled on [`examples/container`](../container). It has full Linux userland, public network, `npm`, `node`, `python`, package managers, test runners, and other real binaries on `$PATH`. It cold-starts @@ -132,7 +132,7 @@ The worker is configured in [`wrangler.jsonc`](./wrangler.jsonc): - `LOADER` — Worker Loader binding. The Assistant's Workspace uses it to mint the Dynamic Worker that hosts the `exec` shell backend. - `containers` — builds [`Dockerfile`](./Dockerfile), which stages the - published `wsd` binary into a Debian image with Node 22, npm, npx, + published `computerd` binary into a Debian image with Node 22, npm, npx, git, and FUSE runtime libraries. The Assistant DO owns one container instance when the `container` backend is first used. - `Assistant` — the SQLite-backed Durable Object and container class. diff --git a/examples/think/package.json b/examples/think/package.json index 97f703a4..b4e8233c 100644 --- a/examples/think/package.json +++ b/examples/think/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "type": "module", - "description": "Minimal @cloudflare/think chat agent backed by a @cloudflare/workspace VFS. Talk to it from a terminal with the AI SDK v7 TUI.", + "description": "Minimal @cloudflare/think chat agent backed by a @cloudflare/computer VFS. Talk to it from a terminal with the AI SDK v7 TUI.", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", @@ -16,8 +16,8 @@ "dependencies": { "@ai-sdk/react": "^4.0.45", "@ai-sdk/tui": "^1.0.43", + "@cloudflare/computer": "*", "@cloudflare/think": "^0.15.1", - "@cloudflare/workspace": "*", "@modelcontextprotocol/client": "2.0.0", "@modelcontextprotocol/sdk": "1.30.0", "@modelcontextprotocol/server": "2.0.0", diff --git a/examples/think/src/agent.ts b/examples/think/src/agent.ts index 9535f068..d0fa05f7 100644 --- a/examples/think/src/agent.ts +++ b/examples/think/src/agent.ts @@ -1,11 +1,11 @@ /** * Assistant — a minimal `@cloudflare/think` chat agent backed by a - * `@cloudflare/workspace` VFS. + * `@cloudflare/computer` VFS. * * Think gives the Durable Object a streaming chat protocol, message * persistence, resumable streams, and the agentic tool loop. This * example keeps the surface as small as possible: one agent, one - * Workspace, the shared `@cloudflare/workspace/tools`, and nothing + * Workspace, the shared `@cloudflare/computer/tools`, and nothing * task-specific. You talk to it from a terminal (see `cli/chat.mjs`) * and it can read, write, and edit files in its workspace and run * shell commands through either workspace backend. @@ -13,18 +13,17 @@ * Wiring: * - `Think` (via the Durable Object base) hands us the message * store, agentic loop, and chat protocol. - * - We own a `@cloudflare/workspace.Workspace` with two backends: + * - We own a `@cloudflare/computer.Workspace` with two backends: * a WorkerBackend (`"shell"`) for fast just-bash text tooling and * a CloudflareContainerBackend (`"container"`) for full Linux - * userland through wsd. This mirrors examples/container while + * userland through computerd. This mirrors examples/container while * keeping the chat surface unchanged. * - `useThink: true` adds the string-based compatibility surface * Think expects; the cast promotes it from optional to present. - * `workspaceBash` is off because `@cloudflare/workspace/tools` + * `workspaceBash` is off because `@cloudflare/computer/tools` * provides the `exec` tool. */ -import { Think } from "@cloudflare/think"; import { type DurableObjectStorageLike, type ThinkWorkspaceCompatibility, @@ -32,13 +31,14 @@ import { WorkspaceProxy, WorkspaceServiceProxy, type WorkspaceStub, -} from "@cloudflare/workspace"; +} from "@cloudflare/computer"; import { CloudflareContainerBackend, withWorkspaceContainer, -} from "@cloudflare/workspace/backends/container"; -import { WorkerBackend } from "@cloudflare/workspace/backends/worker"; -import { createAITools } from "@cloudflare/workspace/tools"; +} from "@cloudflare/computer/backends/container"; +import { WorkerBackend } from "@cloudflare/computer/backends/worker"; +import { createAITools } from "@cloudflare/computer/tools"; +import { Think } from "@cloudflare/think"; import type { ToolSet } from "ai"; import { createWorkersAI } from "workers-ai-provider"; @@ -85,7 +85,7 @@ export class Assistant extends withWorkspaceContainer(AssistantBase) { /** * Think's workspace, owned outright. The first backend in the list * is the default, so unqualified exec calls use the fast just-bash - * shell. Passing `{ backend: "container" }` routes a call to wsd in + * shell. Passing `{ backend: "container" }` routes a call to computerd in * the Cloudflare Container. */ override workspace = new Workspace({ @@ -102,7 +102,7 @@ export class Assistant extends withWorkspaceContainer(AssistantBase) { useThink: true, }) as Workspace & ThinkWorkspaceCompatibility; - /** Forwarded by WorkspaceProxy for wsd's outbound /ws upgrade. */ + /** Forwarded by WorkspaceProxy for computerd's outbound /ws upgrade. */ override async fetch(request: Request): Promise { const url = new URL(request.url); if (url.pathname === "/ws") { @@ -173,7 +173,7 @@ export class Assistant extends withWorkspaceContainer(AssistantBase) { }, container: { description: - "Cloudflare Container running wsd over capnweb. Full Linux " + + "Cloudflare Container running computerd over capnweb. Full Linux " + "userland: npm, node, python, package managers, test " + "runners, real binaries on $PATH, and public network. Cold " + "start is much slower because the container must boot; " + diff --git a/examples/think/src/index.ts b/examples/think/src/index.ts index f5551646..1fb31ee2 100644 --- a/examples/think/src/index.ts +++ b/examples/think/src/index.ts @@ -10,7 +10,7 @@ * * The Assistant, WorkspaceProxy, and WorkspaceServiceProxy classes are * re-exported so the runtime can resolve them by name: Assistant is - * the DO binding and container class, WorkspaceProxy carries wsd's + * the DO binding and container class, WorkspaceProxy carries computerd's * outbound /ws upgrade back to the DO, and WorkspaceServiceProxy is * the loopback Fetcher the worker backend hands into its Dynamic * Worker so the in-isolate shell can reach back into the host diff --git a/examples/think/wrangler.jsonc b/examples/think/wrangler.jsonc index b94d304d..03cf2b88 100644 --- a/examples/think/wrangler.jsonc +++ b/examples/think/wrangler.jsonc @@ -1,5 +1,5 @@ // Worker + Durable Object hosting a minimal @cloudflare/think chat -// agent. The Assistant DO owns one @cloudflare/workspace VFS with a +// agent. The Assistant DO owns one @cloudflare/computer VFS with a // fast Dynamic Worker shell backend and a Cloudflare Container backend // for full Linux commands. Connect to it from a terminal with // `npm run chat`. @@ -15,7 +15,7 @@ "containers": [ { "class_name": "Assistant", - // Built from the local Dockerfile, which copies wsd into a + // Built from the local Dockerfile, which copies computerd into a // small Debian image with Node/npm/git for real build and test // workflows. "image": "./Dockerfile", diff --git a/examples/worker/README.md b/examples/worker/README.md index 52ef361b..5d8dee73 100644 --- a/examples/worker/README.md +++ b/examples/worker/README.md @@ -31,7 +31,7 @@ client ─► Worker /c//{file,exec} ``` 1. The DO constructs a `WorkerBackend` from - `@cloudflare/workspace/backends/worker`, passing the Loader + `@cloudflare/computer/backends/worker`, passing the Loader binding, a `{binding, id}` reference to itself, and `ctx`. The backend handles the rest internally: it builds the Loader callback (with the code-split shell modules + the seek-bzip @@ -47,7 +47,7 @@ client ─► Worker /c//{file,exec} survive structured clone into the loader's env; the binding-shape Fetcher the proxy produces does. 3. `ShellWorker` (shipped in - `@cloudflare/workspace/backends/worker`) lives inside that + `@cloudflare/computer/backends/worker`) lives inside that Dynamic Worker. Each `exec(input)` calls `env.HOST.getWorkspace()`, builds a fresh `Bash` around a `WorkspaceFsAdapter` wrapping the stub's `.fs`, runs the @@ -82,10 +82,10 @@ so the shell can't reach the public internet on its own. Identical to the container example. Seed once with: ```sh -npm run seed:r2:local --workspace @example/workspace-worker +npm run seed:r2:local --workspace @example/computer-worker # or after deploy -npm run seed:r2 --workspace @example/workspace-worker +npm run seed:r2 --workspace @example/computer-worker ``` ## HTTP surface @@ -103,7 +103,7 @@ POST /c//exec { command | argv, cwd?, encoding? } No Docker, no extra build step. The shell ships as a record of pre-bundled modules (`SHELL_MODULES`) inside -`@cloudflare/workspace/backends/worker`; `WorkerBackend` spreads +`@cloudflare/computer/backends/worker`; `WorkerBackend` spreads the whole record into the Loader callback internally so the DO constructor stays a three-line backend invocation. The entry module parses on cold start; the dynamic chunks (python, js-exec, @@ -111,7 +111,7 @@ sqlite, curl, html-to-markdown) stay cold until a script reaches for them. ```sh -npm run dev --workspace @example/workspace-worker +npm run dev --workspace @example/computer-worker ``` Smoke test (same recipes as the container example): @@ -138,7 +138,7 @@ examples/worker/ ``` Nothing else. The Dynamic Worker source ships from -`@cloudflare/workspace/backends/worker` as a pre-built module +`@cloudflare/computer/backends/worker` as a pre-built module string. ## Known limitations diff --git a/examples/worker/package.json b/examples/worker/package.json index 268cd00c..0df4c423 100644 --- a/examples/worker/package.json +++ b/examples/worker/package.json @@ -1,18 +1,18 @@ { - "name": "@example/workspace-worker", + "name": "@example/computer-worker", "version": "0.0.0", "private": true, "type": "module", - "description": "Example Worker + Durable Object that runs just-bash inside a Dynamic Worker via @cloudflare/workspace's WorkerBackend.", + "description": "Example Worker + Durable Object that runs just-bash inside a Dynamic Worker via @cloudflare/computer's WorkerBackend.", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", "typecheck": "tsc --noEmit", - "seed:r2": "wrangler r2 object put workspace-worker-hello/hello.txt --file ./seed/data/hello.txt --remote", - "seed:r2:local": "wrangler r2 object put workspace-worker-hello/hello.txt --file ./seed/data/hello.txt --local" + "seed:r2": "wrangler r2 object put computer-worker-hello/hello.txt --file ./seed/data/hello.txt --remote", + "seed:r2:local": "wrangler r2 object put computer-worker-hello/hello.txt --file ./seed/data/hello.txt --local" }, "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", diff --git a/examples/worker/src/index.ts b/examples/worker/src/index.ts index d8d8140b..2f1c1590 100644 --- a/examples/worker/src/index.ts +++ b/examples/worker/src/index.ts @@ -34,8 +34,8 @@ import { R2Bucket, WorkspaceServiceProxy, withWorkspace, -} from "@cloudflare/workspace"; -import { WorkerBackend } from "@cloudflare/workspace/backends/worker"; +} from "@cloudflare/computer"; +import { WorkerBackend } from "@cloudflare/computer/backends/worker"; // Re-export so the runtime can wrap WorkspaceServiceProxy into a // loopback Fetcher binding. The DO reaches the wrapped class diff --git a/examples/worker/wrangler.jsonc b/examples/worker/wrangler.jsonc index cea63908..6f2b0274 100644 --- a/examples/worker/wrangler.jsonc +++ b/examples/worker/wrangler.jsonc @@ -4,9 +4,9 @@ // // Mirrors examples/container beat for beat — same DO class // name, same routes, same R2 mount. The only difference is the - // shell: a Dynamic Worker instead of a container running wsd. + // shell: a Dynamic Worker instead of a container running computerd. "$schema": "node_modules/wrangler/config-schema.json", - "name": "workspace-worker-example", + "name": "computer-worker-example", "main": "src/index.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat", "experimental"], @@ -31,12 +31,12 @@ // R2 bucket mounted into every Workspace at /workspace/r2 via // R2Bucket(...). Seed it once with: - // npm run seed:r2 --workspace @example/workspace-worker + // npm run seed:r2 --workspace @example/computer-worker // which uploads ./seed/data/hello.txt to the bucket. "r2_buckets": [ { "binding": "Bucket", - "bucket_name": "workspace-worker-hello" + "bucket_name": "computer-worker-hello" } ], diff --git a/package-lock.json b/package-lock.json index 1ea2401a..64ad5075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { - "name": "@cloudflare/workspace", + "name": "@cloudflare/computer", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@cloudflare/workspace", + "name": "@cloudflare/computer", "workspaces": [ "packages/dofs", "packages/rpc", - "packages/workspace", - "packages/wsd", + "packages/computer", + "packages/computerd", "examples/*" ], "devDependencies": { @@ -23,10 +23,10 @@ } }, "examples/artifacts": { - "name": "@example/workspace-artifacts", + "name": "@example/computer-artifacts", "version": "0.0.0", "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", @@ -42,10 +42,10 @@ "license": "MIT OR Apache-2.0" }, "examples/assets": { - "name": "@example/workspace-assets", + "name": "@example/computer-assets", "version": "0.0.0", "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", @@ -61,10 +61,10 @@ "license": "MIT OR Apache-2.0" }, "examples/container": { - "name": "@example/workspace-container", + "name": "@example/computer-container", "version": "0.0.0", "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", @@ -85,8 +85,8 @@ "dependencies": { "@ai-sdk/react": "^4.0.45", "@ai-sdk/tui": "^1.0.43", + "@cloudflare/computer": "*", "@cloudflare/think": "^0.15.1", - "@cloudflare/workspace": "*", "@modelcontextprotocol/client": "2.0.0", "@modelcontextprotocol/sdk": "1.30.0", "@modelcontextprotocol/server": "2.0.0", @@ -110,12 +110,12 @@ "name": "@cloudflare/example-think-compare-runtimes", "version": "0.0.0", "dependencies": { + "@cloudflare/computer": "*", + "@cloudflare/computer-rpc": "*", "@cloudflare/kumo": "^2.4.1", "@cloudflare/sandbox": "^0.11.0", "@cloudflare/shell": "^0.4.3", "@cloudflare/think": "^0.15.1", - "@cloudflare/workspace": "*", - "@cloudflare/workspace-rpc": "*", "@phosphor-icons/react": "^2.1.10", "@shikijs/core": "^4.2.0", "@shikijs/engine-javascript": "^4.2.0", @@ -198,10 +198,10 @@ "license": "MIT OR Apache-2.0" }, "examples/worker": { - "name": "@example/workspace-worker", + "name": "@example/computer-worker", "version": "0.0.0", "dependencies": { - "@cloudflare/workspace": "*" + "@cloudflare/computer": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20260616.1", @@ -1082,6 +1082,18 @@ } } }, + "node_modules/@cloudflare/computer": { + "resolved": "packages/computer", + "link": true + }, + "node_modules/@cloudflare/computer-rpc": { + "resolved": "packages/rpc", + "link": true + }, + "node_modules/@cloudflare/computerd": { + "resolved": "packages/computerd", + "link": true + }, "node_modules/@cloudflare/containers": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/@cloudflare/containers/-/containers-0.3.7.tgz", @@ -1357,13 +1369,6 @@ "node": ">=16" } }, - "node_modules/@cloudflare/vitest-pool-workers/node_modules/@cloudflare/workers-types": { - "version": "4.20260702.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz", - "integrity": "sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==", - "extraneous": true, - "license": "MIT OR Apache-2.0" - }, "node_modules/@cloudflare/vitest-pool-workers/node_modules/@emnapi/runtime": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", @@ -2167,18 +2172,6 @@ "license": "MIT OR Apache-2.0", "peer": true }, - "node_modules/@cloudflare/workspace": { - "resolved": "packages/workspace", - "link": true - }, - "node_modules/@cloudflare/workspace-rpc": { - "resolved": "packages/rpc", - "link": true - }, - "node_modules/@cloudflare/workspace-wsd": { - "resolved": "packages/wsd", - "link": true - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2790,19 +2783,19 @@ "node": ">=18" } }, - "node_modules/@example/workspace-artifacts": { + "node_modules/@example/computer-artifacts": { "resolved": "examples/artifacts", "link": true }, - "node_modules/@example/workspace-assets": { + "node_modules/@example/computer-assets": { "resolved": "examples/assets", "link": true }, - "node_modules/@example/workspace-container": { + "node_modules/@example/computer-container": { "resolved": "examples/container", "link": true }, - "node_modules/@example/workspace-worker": { + "node_modules/@example/computer-worker": { "resolved": "examples/worker", "link": true }, @@ -12147,59 +12140,19 @@ "url": "https://github.com/sponsors/wooorm" } }, - "packages/dofs": { - "name": "@cloudflare/dofs", - "version": "0.0.0", - "devDependencies": { - "@cloudflare/vitest-pool-workers": "^0.16.10", - "@cloudflare/workers-types": "^4.20260616.1", - "typescript": "^6.0.3", - "vitest": "^4.1.7", - "wrangler": "^4.107.1" - } - }, - "packages/dofs/node_modules/@cloudflare/workers-types": { - "version": "4.20260702.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz", - "integrity": "sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, - "packages/rpc": { - "name": "@cloudflare/workspace-rpc", - "version": "0.0.0", - "dependencies": { - "@cloudflare/dofs": "*", - "capnweb": "^0.8.0" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20260616.1", - "@types/ws": "^8.18.1", - "typescript": "^6.0.3", - "vitest": "^4.1.7", - "ws": "^8.20.1" - } - }, - "packages/rpc/node_modules/@cloudflare/workers-types": { - "version": "4.20260702.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz", - "integrity": "sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, - "packages/workspace": { - "name": "@cloudflare/workspace", - "version": "0.0.0-alpha.11", + "packages/computer": { + "name": "@cloudflare/computer", + "version": "0.0.0-alpha.13", "license": "MIT", "dependencies": { "capnweb": "^0.8.0", "just-bash": "^3.0.1" }, "devDependencies": { + "@cloudflare/computer-rpc": "*", "@cloudflare/dofs": "*", "@cloudflare/vitest-pool-workers": "^0.16.10", "@cloudflare/workers-types": "^4.20260616.1", - "@cloudflare/workspace-rpc": "*", "@platformatic/vfs": "^0.4.0", "ai": "^7.0.0", "diff": "^9.0.0", @@ -12234,25 +12187,25 @@ } } }, - "packages/workspace/node_modules/@cloudflare/workers-types": { - "version": "4.20260702.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz", - "integrity": "sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==", + "packages/computer/node_modules/@cloudflare/workers-types": { + "version": "4.20260626.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260626.1.tgz", + "integrity": "sha512-fBnpQyFRS3Ce1l2IUd3k+aUxgy/7VMlVXF4F672/eSrpXFeezCy3Ha6Z2uTyGgqu9sGvQPOj8nqKBv2yeI+ciw==", "dev": true, "license": "MIT OR Apache-2.0" }, - "packages/wsd": { - "name": "@cloudflare/workspace-wsd", - "version": "0.0.0-alpha.11", + "packages/computerd": { + "name": "@cloudflare/computerd", + "version": "0.0.0-alpha.13", "license": "MIT", "dependencies": { + "@cloudflare/computer-rpc": "*", "@cloudflare/dofs": "*", - "@cloudflare/workspace-rpc": "*", "@platformatic/vfs": "^0.4.0", "fuse-native": "^2.2.6" }, "bin": { - "wsd": "dist/cli/wsd.cjs" + "computerd": "dist/cli/computerd.cjs" }, "devDependencies": { "vitest": "^4.1.7" @@ -12260,6 +12213,46 @@ "engines": { "node": ">=22" } + }, + "packages/dofs": { + "name": "@cloudflare/dofs", + "version": "0.0.0", + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.16.10", + "@cloudflare/workers-types": "^4.20260616.1", + "typescript": "^6.0.3", + "vitest": "^4.1.7", + "wrangler": "^4.107.1" + } + }, + "packages/dofs/node_modules/@cloudflare/workers-types": { + "version": "4.20260702.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz", + "integrity": "sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "packages/rpc": { + "name": "@cloudflare/computer-rpc", + "version": "0.0.0", + "dependencies": { + "@cloudflare/dofs": "*", + "capnweb": "^0.8.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20260616.1", + "@types/ws": "^8.18.1", + "typescript": "^6.0.3", + "vitest": "^4.1.7", + "ws": "^8.20.1" + } + }, + "packages/rpc/node_modules/@cloudflare/workers-types": { + "version": "4.20260702.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260702.1.tgz", + "integrity": "sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==", + "dev": true, + "license": "MIT OR Apache-2.0" } } } diff --git a/package.json b/package.json index acf56a40..11ca2af7 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "@cloudflare/workspace", + "name": "@cloudflare/computer", "private": true, "packageManager": "npm", "workspaces": [ "packages/dofs", "packages/rpc", - "packages/workspace", - "packages/wsd", + "packages/computer", + "packages/computerd", "examples/*" ], "scripts": { diff --git a/packages/wsd-linux-x64/.dockerignore b/packages/computer-computerd-linux-x64/.dockerignore similarity index 90% rename from packages/wsd-linux-x64/.dockerignore rename to packages/computer-computerd-linux-x64/.dockerignore index d2084d9f..391e3ec6 100644 --- a/packages/wsd-linux-x64/.dockerignore +++ b/packages/computer-computerd-linux-x64/.dockerignore @@ -1,4 +1,4 @@ # Only the binary needs to enter the docker build context. README and # package.json belong in the npm-published package, not in the image. * -!bin/wsd +!bin/computerd diff --git a/packages/wsd-linux-x64/Dockerfile b/packages/computer-computerd-linux-x64/Dockerfile similarity index 59% rename from packages/wsd-linux-x64/Dockerfile rename to packages/computer-computerd-linux-x64/Dockerfile index 74870257..ef75e6f8 100644 --- a/packages/wsd-linux-x64/Dockerfile +++ b/packages/computer-computerd-linux-x64/Dockerfile @@ -1,14 +1,14 @@ -# Image-only distribution of the wsd-linux-x64 SEA binary. +# Image-only distribution of the computerd-linux-x64 SEA binary. # -# Build via `npm run build:docker --workspace @cloudflare/workspace-wsd`, -# which stages the SEA artifact into ./bin/wsd first and then runs +# Build via `npm run build:docker --workspace @cloudflare/computerd`, +# which stages the SEA artifact into ./bin/computerd first and then runs # `docker build` against this directory. The image isn't meant to be # run as a service — its sole job is to ship the binary at a known # path so downstream Dockerfiles can `COPY --from`: # -# FROM cloudflare/workspace-wsd-linux-x64:0.1.1 AS wsd +# FROM cloudflare/computer-computerd-linux-x64:0.1.1 AS computerd # FROM debian:stable-slim -# COPY --from=wsd /usr/local/bin/wsd /usr/local/bin/wsd +# COPY --from=computerd /usr/local/bin/computerd /usr/local/bin/computerd # # `scratch` keeps the layer count minimal (one layer, ~125 MB binary, # zero base-image surface). The image has no shell, no libc, nothing @@ -16,9 +16,9 @@ FROM scratch -COPY bin/wsd /usr/local/bin/wsd +COPY bin/computerd /usr/local/bin/computerd # Documentation only. scratch has no PID 1 capable of execing this, # so the ENTRYPOINT is informational. Downstream consumers redeclare # their own ENTRYPOINT in the runtime image. -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] diff --git a/packages/wsd-linux-x64/README.md b/packages/computer-computerd-linux-x64/README.md similarity index 69% rename from packages/wsd-linux-x64/README.md rename to packages/computer-computerd-linux-x64/README.md index f99ccaa5..4ab94753 100644 --- a/packages/wsd-linux-x64/README.md +++ b/packages/computer-computerd-linux-x64/README.md @@ -1,4 +1,4 @@ -# @cloudflare/workspace-wsd-linux-x64 +# @cloudflare/computer-computerd-linux-x64 > [!IMPORTANT] > **PREVIEW ONLY** This package is provided as a preview for feedback only. @@ -7,8 +7,8 @@ > Suitable for experiments, exploration and prototypes. It is NOT suitable > for production use at this time. -Prebuilt `wsd` binary for linux-x64. `wsd` is the workspace daemon -side of [`@cloudflare/workspace`](../workspace) — see [`docs/`](../../docs) +Prebuilt `computerd` binary for linux-x64. `computerd` is the daemon +side of [`@cloudflare/computer`](../computer) — see [`docs/`](../../docs) for the wire protocol and architecture. The binary is a Node SEA (Single Executable Application). Everything @@ -19,10 +19,10 @@ nothing else. ## Install ```sh -npm install @cloudflare/workspace-wsd-linux-x64 +npm install @cloudflare/computer-computerd-linux-x64 ``` -Adds `wsd` to `node_modules/.bin/`. On any host that isn't linux-x64 +Adds `computerd` to `node_modules/.bin/`. On any host that isn't linux-x64 npm refuses the install via the package's `os` / `cpu` constraints — that's intentional. @@ -32,9 +32,9 @@ The intended path. Multi-stage build pulls the binary from npm, copies it into a minimal runtime image: ```dockerfile -FROM node:22-slim AS wsd +FROM node:22-slim AS computerd RUN npm install --no-save --omit=dev \ - @cloudflare/workspace-wsd-linux-x64@0.1.1 + @cloudflare/computer-computerd-linux-x64@0.1.1 FROM debian:stable-slim RUN apt-get update \ @@ -42,13 +42,13 @@ RUN apt-get update \ fuse3 libfuse2t64 ca-certificates \ && rm -rf /var/lib/apt/lists/* -COPY --from=wsd \ - /node_modules/@cloudflare/workspace-wsd-linux-x64/bin/wsd \ - /usr/local/bin/wsd +COPY --from=computerd \ + /node_modules/@cloudflare/computer-computerd-linux-x64/bin/computerd \ + /usr/local/bin/computerd ENV PORT=8080 MOUNT_POINT=/workspace EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/wsd"] +ENTRYPOINT ["/usr/local/bin/computerd"] ``` No local SEA build, no binary staged into the build context. Pin the @@ -57,11 +57,11 @@ production when wire-protocol changes land. ## Configuration -`wsd` reads its config from environment variables. The interesting ones: +`computerd` reads its config from environment variables. The interesting ones: | var | default | meaning | |---|---|---| | `PORT` | `8080` | HTTP + WebSocket listener port. | | `MOUNT_POINT` | `/workspace` | Path the FUSE filesystem mounts at. | | `FUSE_MOUNT` | `auto` | Backend selector. `auto` probes `/dev/fuse` (linux) or macFUSE (darwin) and falls back to the userspace shim. `fuse` and `macfuse` require their respective real backend. `shim` forces the userspace shim. `none` skips the mount entirely; HTTP / WS still come up. | -| `UPSTREAM_URL` | unset | If set, wsd dials this WebSocket on boot and runs a bidirectional sync loop against it. | +| `UPSTREAM_URL` | unset | If set, computerd dials this WebSocket on boot and runs a bidirectional sync loop against it. | diff --git a/packages/wsd-linux-x64/bin/.gitkeep b/packages/computer-computerd-linux-x64/bin/.gitkeep similarity index 100% rename from packages/wsd-linux-x64/bin/.gitkeep rename to packages/computer-computerd-linux-x64/bin/.gitkeep diff --git a/packages/computer-computerd-linux-x64/package.json b/packages/computer-computerd-linux-x64/package.json new file mode 100644 index 00000000..3a7696f7 --- /dev/null +++ b/packages/computer-computerd-linux-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@cloudflare/computer-computerd-linux-x64", + "version": "0.0.0-alpha.13", + "description": "Prebuilt computerd binary for linux-x64. Single-platform distribution; install on a linux-x64 host or COPY --from a docker build stage that installed it.", + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin/", + "README.md" + ], + "bin": { + "computerd": "./bin/computerd" + } +} diff --git a/packages/workspace/README.md b/packages/computer/README.md similarity index 93% rename from packages/workspace/README.md rename to packages/computer/README.md index 88e6ce90..5ee035f9 100644 --- a/packages/workspace/README.md +++ b/packages/computer/README.md @@ -1,4 +1,4 @@ -# `@cloudflare/workspace` +# `@cloudflare/computer` > [!IMPORTANT] > **PREVIEW ONLY** This package is provided as a preview for feedback only. @@ -10,19 +10,19 @@ > The specification under [`docs/`](docs/) is forward-looking — read it for > intent, not as description of the code today. -Durable Object-side facade for a Cloudflare Workspace. Pairs a local +Durable Object-side facade for Cloudflare Computer. Pairs a local SQLite-backed VFS (via `@cloudflare/dofs`) with a pluggable backend that decides where shell commands run. Two backends ship today, each on its own sub-path so the large dependencies they carry can be tree-shaken when you only use one: -- [`@cloudflare/workspace/backends/container`](./src/backends/container/) — - runs the shell inside a Cloudflare Container against a `wsd` +- [`@cloudflare/computer/backends/container`](./src/backends/container/) — + runs the shell inside a Cloudflare Container against a `computerd` daemon. Full Linux userland, real binaries, real network. The container owns its own SQLite-backed VFS and the package syncs the two stores across a capnweb WebSocket. -- [`@cloudflare/workspace/backends/worker`](./src/backends/worker/) — +- [`@cloudflare/computer/backends/worker`](./src/backends/worker/) — runs the shell as [just-bash](https://github.com/vercel-labs/just-bash) inside a Dynamic Worker minted through `env.LOADER`. Every filesystem operation forwards back to the same Durable Object; @@ -52,7 +52,7 @@ uniform; the counts are just always zero. worker backend's shell exposes the same dispatcher through a built-in `git` custom command. See [`docs/13_git_interface.md`](../../docs/13_git_interface.md). -- `createAssets` (from `@cloudflare/workspace/assets`) — `share` a +- `createAssets` (from `@cloudflare/computer/assets`) — `share` a workspace file to an R2 bucket and get back a presigned URL. Binds the workspace and bucket once, like `workspace.git`. When attached through `WorkspaceOptions.assets`, the worker backend's @@ -60,7 +60,7 @@ uniform; the counts are just always zero. [`docs/14_assets_interface.md`](../../docs/14_assets_interface.md). - `createArtifact(binding, sessionId)` — a session-scoped facade over the [Cloudflare Artifacts](https://developers.cloudflare.com/artifacts/) - Workers binding, on the `@cloudflare/workspace/artifacts` + Workers binding, on the `@cloudflare/computer/artifacts` subpath. Every repository name is implicitly prefixed with the session id, so one namespace hosts many isolated sessions. Like git, it surfaces both a typed API and an argv CLI @@ -68,7 +68,7 @@ uniform; the counts are just always zero. an Artifacts binding, the worker backend exposes the same CLI as an `artifacts` custom command. See [`docs/15_artifacts_interface.md`](../../docs/15_artifacts_interface.md). -- `createAITools` (from `@cloudflare/workspace/tools`) — AI SDK +- `createAITools` (from `@cloudflare/computer/tools`) — AI SDK tools for agents: `read`, `write`, `edit`, `ls`, optional `exec`, and optional `publish`. See [`docs/09_tool_interface.md`](../../docs/09_tool_interface.md). @@ -78,9 +78,9 @@ uniform; the counts are just always zero. Container backend: ```ts -import { withWorkspace, WorkspaceProxy } from "@cloudflare/workspace"; +import { withWorkspace, WorkspaceProxy } from "@cloudflare/computer"; import { CloudflareContainerBackend, withWorkspaceContainer } - from "@cloudflare/workspace/backends/container"; + from "@cloudflare/computer/backends/container"; import { DurableObject } from "cloudflare:workers"; export { WorkspaceProxy }; @@ -107,8 +107,8 @@ export class ContainerExample extends withWorkspace( Worker backend: ```ts -import { withWorkspace, WorkspaceServiceProxy } from "@cloudflare/workspace"; -import { WorkerBackend } from "@cloudflare/workspace/backends/worker"; +import { withWorkspace, WorkspaceServiceProxy } from "@cloudflare/computer"; +import { WorkerBackend } from "@cloudflare/computer/backends/worker"; import { DurableObject } from "cloudflare:workers"; export { WorkspaceServiceProxy }; @@ -169,7 +169,7 @@ in and out local; the session id is added as a prefix on the way to the namespace and stripped on the way back. ```ts -import { createArtifact } from "@cloudflare/workspace/artifacts"; +import { createArtifact } from "@cloudflare/computer/artifacts"; const artifacts = createArtifact(env.ARTIFACTS, agentId); @@ -257,7 +257,7 @@ import { type SyncRetryIntent, type SyncRetryScheduler, Workspace, -} from "@cloudflare/workspace"; +} from "@cloudflare/computer"; const RETRY_PREFIX = "workspace:sync-retry:"; @@ -344,7 +344,7 @@ returns `status: "idle"`. ## Worker-side consumption ```ts -import { getWorkspace } from "@cloudflare/workspace"; +import { getWorkspace } from "@cloudflare/computer"; export default { async fetch(request: Request, env: Env): Promise { @@ -393,7 +393,7 @@ need `cwd` or `backend`, and wrap an interpolated command in the `sh` tag to escape it: ```ts -import { sh } from "@cloudflare/workspace"; +import { sh } from "@cloudflare/computer"; await ws.shell.exec(sh`cat ${file}`, { cwd: "/workspace" }); await ws.shell.exec("npm test", { cwd: "/workspace", encoding: "utf8" }); @@ -425,7 +425,7 @@ The package emits one span per documented operation through an optional observer hook. Pass an observer to the `Workspace` constructor: ```ts -import { Workspace, type WorkspaceObserver } from "@cloudflare/workspace"; +import { Workspace, type WorkspaceObserver } from "@cloudflare/computer"; const observer: WorkspaceObserver = { async span(name, attributes, run) { @@ -482,14 +482,14 @@ observability cost when callers do not opt in. capnweb does not garbage-collect remote stubs. On the long-lived sessions this package depends on (Worker ↔ DO over Workers RPC, -DO ↔ wsd over capnweb), undisposed stubs accumulate on the peer +DO ↔ computerd over capnweb), undisposed stubs accumulate on the peer side until the session ends. The worker backend uses Workers RPC over an isolate boundary rather than capnweb, but the disposal discipline is the same. The minimum a caller needs to know: -- `using` the value returned from `env.WSD.get(id).getWorkspace()`. +- `using` the value returned from `env.COMPUTERD.get(id).getWorkspace()`. - `using` the handle returned from `ws.shell.exec(...)`. - Don't worry about `ws.fs`, `ws.shell`, or `ws.git` — those are property accessors that ride with the parent. @@ -507,8 +507,8 @@ and direct streaming callers, and how it interacts with hibernation and reconnect — is in [`docs/11_lifecycle.md`](../../docs/11_lifecycle.md#stub-disposal-contract). Leak discovery: set `CAPNWEB_TRACK_STUBS=1` and read the snapshot -via `stubSnapshot()` from `@cloudflare/workspace-rpc/debug`, or -hit `GET /__wsd/stubs` on a wsd instance. The soak scripts at -[`script/wsd-stub-soak.mjs`](../../script/wsd-stub-soak.mjs) and +via `stubSnapshot()` from `@cloudflare/computer-rpc/debug`, or +hit `GET /__computerd/stubs` on a computerd instance. The soak scripts at +[`script/computerd-stub-soak.mjs`](../../script/computerd-stub-soak.mjs) and [`tests/stub-soak.test.ts`](./tests/stub-soak.test.ts) exercise both boundaries. diff --git a/packages/workspace/package.json b/packages/computer/package.json similarity index 89% rename from packages/workspace/package.json rename to packages/computer/package.json index e5a0fe05..0c205c05 100644 --- a/packages/workspace/package.json +++ b/packages/computer/package.json @@ -1,12 +1,12 @@ { - "name": "@cloudflare/workspace", + "name": "@cloudflare/computer", "version": "0.0.0-alpha.13", - "description": "Cloudflare Workspace — a SQLite-backed virtual filesystem with sync to a container-side daemon (wsd).", + "description": "Cloudflare Computer — a SQLite-backed virtual filesystem with sync to a container-side daemon (computerd).", "license": "MIT", "repository": { - "directory": "packages/workspace", + "directory": "packages/computer", "type": "git", - "url": "git+https://github.com/cloudflare/workspace.git" + "url": "git+https://github.com/cloudflare/computer.git" }, "private": false, "type": "module", @@ -51,7 +51,7 @@ "README.md" ], "scripts": { - "build:deps": "npm run build --workspace @cloudflare/workspace-rpc", + "build:deps": "npm run build --workspace @cloudflare/computer-rpc", "build:shell-bundle": "node ./src/backends/worker/script/build-bundle.mjs", "prebuild": "npm run build:deps && npm run build:shell-bundle", "pretest": "npm run build:shell-bundle", @@ -91,10 +91,10 @@ } }, "devDependencies": { + "@cloudflare/computer-rpc": "*", "@cloudflare/dofs": "*", "@cloudflare/vitest-pool-workers": "^0.16.10", "@cloudflare/workers-types": "^4.20260616.1", - "@cloudflare/workspace-rpc": "*", "@platformatic/vfs": "^0.4.0", "ai": "^7.0.0", "diff": "^9.0.0", diff --git a/packages/workspace/rolldown.config.ts b/packages/computer/rolldown.config.ts similarity index 89% rename from packages/workspace/rolldown.config.ts rename to packages/computer/rolldown.config.ts index a4c91e57..db90774f 100644 --- a/packages/workspace/rolldown.config.ts +++ b/packages/computer/rolldown.config.ts @@ -1,5 +1,5 @@ -// Bundle @cloudflare/workspace's source plus its internal deps -// (@cloudflare/dofs, @cloudflare/workspace-rpc) into a single ESM +// Bundle @cloudflare/computer's source plus its internal deps +// (@cloudflare/dofs, @cloudflare/computer-rpc) into a single ESM // artefact that downstream callers can install with no other // @cloudflare/* dependencies. // @@ -48,11 +48,11 @@ export default defineConfig({ alias: { "@cloudflare/dofs": resolve(here, "../dofs/src/index.ts"), "@cloudflare/dofs/testing": resolve(here, "../dofs/src/testing.ts"), - "@cloudflare/workspace-rpc": resolve(here, "../rpc/src/index.ts"), - "@cloudflare/workspace-rpc/driver": resolve(here, "../rpc/src/sync-driver.ts"), + "@cloudflare/computer-rpc": resolve(here, "../rpc/src/index.ts"), + "@cloudflare/computer-rpc/driver": resolve(here, "../rpc/src/sync-driver.ts"), }, }, - // ESM only. Nothing in-tree loads CJS — the example Worker, wsd, + // ESM only. Nothing in-tree loads CJS — the example Worker, computerd, // and tests are all ESM — and a published-as-alpha API doesn't // have any external CJS consumers to maintain back-compat for. output: { diff --git a/packages/workspace/src/artifacts/cli.test.ts b/packages/computer/src/artifacts/cli.test.ts similarity index 100% rename from packages/workspace/src/artifacts/cli.test.ts rename to packages/computer/src/artifacts/cli.test.ts diff --git a/packages/workspace/src/artifacts/cli.ts b/packages/computer/src/artifacts/cli.ts similarity index 100% rename from packages/workspace/src/artifacts/cli.ts rename to packages/computer/src/artifacts/cli.ts diff --git a/packages/workspace/src/artifacts/client.test.ts b/packages/computer/src/artifacts/client.test.ts similarity index 100% rename from packages/workspace/src/artifacts/client.test.ts rename to packages/computer/src/artifacts/client.test.ts diff --git a/packages/workspace/src/artifacts/client.ts b/packages/computer/src/artifacts/client.ts similarity index 100% rename from packages/workspace/src/artifacts/client.ts rename to packages/computer/src/artifacts/client.ts diff --git a/packages/workspace/src/artifacts/duration.test.ts b/packages/computer/src/artifacts/duration.test.ts similarity index 100% rename from packages/workspace/src/artifacts/duration.test.ts rename to packages/computer/src/artifacts/duration.test.ts diff --git a/packages/workspace/src/artifacts/duration.ts b/packages/computer/src/artifacts/duration.ts similarity index 100% rename from packages/workspace/src/artifacts/duration.ts rename to packages/computer/src/artifacts/duration.ts diff --git a/packages/workspace/src/artifacts/errors.ts b/packages/computer/src/artifacts/errors.ts similarity index 100% rename from packages/workspace/src/artifacts/errors.ts rename to packages/computer/src/artifacts/errors.ts diff --git a/packages/workspace/src/artifacts/index.ts b/packages/computer/src/artifacts/index.ts similarity index 90% rename from packages/workspace/src/artifacts/index.ts rename to packages/computer/src/artifacts/index.ts index d3c84657..5494bca5 100644 --- a/packages/workspace/src/artifacts/index.ts +++ b/packages/computer/src/artifacts/index.ts @@ -1,4 +1,4 @@ -// Public surface of @cloudflare/workspace/artifacts. +// Public surface of @cloudflare/computer/artifacts. // // `createArtifact(binding, sessionId)` is the one entry point. It // binds a Cloudflare Artifacts namespace binding and a session id @@ -11,7 +11,7 @@ // command dispatches through the CLI so the in-shell tool and the // JS API cannot drift. // -// import { createArtifact } from "@cloudflare/workspace/artifacts"; +// import { createArtifact } from "@cloudflare/computer/artifacts"; // // The binding, its repo handle, and the result shapes (`Artifacts`, // `ArtifactsRepo`, `ArtifactsCreateRepoResult`, ...) are the global diff --git a/packages/workspace/src/artifacts/scope.test.ts b/packages/computer/src/artifacts/scope.test.ts similarity index 100% rename from packages/workspace/src/artifacts/scope.test.ts rename to packages/computer/src/artifacts/scope.test.ts diff --git a/packages/workspace/src/artifacts/scope.ts b/packages/computer/src/artifacts/scope.ts similarity index 100% rename from packages/workspace/src/artifacts/scope.ts rename to packages/computer/src/artifacts/scope.ts diff --git a/packages/workspace/src/artifacts/types.ts b/packages/computer/src/artifacts/types.ts similarity index 100% rename from packages/workspace/src/artifacts/types.ts rename to packages/computer/src/artifacts/types.ts diff --git a/packages/workspace/src/assets/base32.test.ts b/packages/computer/src/assets/base32.test.ts similarity index 100% rename from packages/workspace/src/assets/base32.test.ts rename to packages/computer/src/assets/base32.test.ts diff --git a/packages/workspace/src/assets/base32.ts b/packages/computer/src/assets/base32.ts similarity index 100% rename from packages/workspace/src/assets/base32.ts rename to packages/computer/src/assets/base32.ts diff --git a/packages/workspace/src/assets/index.test.ts b/packages/computer/src/assets/index.test.ts similarity index 100% rename from packages/workspace/src/assets/index.test.ts rename to packages/computer/src/assets/index.test.ts diff --git a/packages/workspace/src/assets/index.ts b/packages/computer/src/assets/index.ts similarity index 98% rename from packages/workspace/src/assets/index.ts rename to packages/computer/src/assets/index.ts index da68a42d..043d81c6 100644 --- a/packages/workspace/src/assets/index.ts +++ b/packages/computer/src/assets/index.ts @@ -1,11 +1,11 @@ -// Public surface of @cloudflare/workspace/assets. +// Public surface of @cloudflare/computer/assets. // // `createAssets({ ws, bucket, s3 })` binds a workspace and an R2 // bucket once and returns a client whose `share(path, opts)` // uploads a VFS file to R2 and returns a time-limited presigned // GET URL. // -// import { createAssets } from "@cloudflare/workspace/assets"; +// import { createAssets } from "@cloudflare/computer/assets"; // // const assets = createAssets({ ws, bucket: env.ASSETS, s3: { bucket: "agent-assets" } }); // const url = await assets.share("/workspace/out/image.png", { diff --git a/packages/workspace/src/assets/mime.test.ts b/packages/computer/src/assets/mime.test.ts similarity index 100% rename from packages/workspace/src/assets/mime.test.ts rename to packages/computer/src/assets/mime.test.ts diff --git a/packages/workspace/src/assets/mime.ts b/packages/computer/src/assets/mime.ts similarity index 100% rename from packages/workspace/src/assets/mime.ts rename to packages/computer/src/assets/mime.ts diff --git a/packages/workspace/src/assets/sigv4.test.ts b/packages/computer/src/assets/sigv4.test.ts similarity index 100% rename from packages/workspace/src/assets/sigv4.test.ts rename to packages/computer/src/assets/sigv4.test.ts diff --git a/packages/workspace/src/assets/sigv4.ts b/packages/computer/src/assets/sigv4.ts similarity index 100% rename from packages/workspace/src/assets/sigv4.ts rename to packages/computer/src/assets/sigv4.ts diff --git a/packages/workspace/src/assets/upload.test.ts b/packages/computer/src/assets/upload.test.ts similarity index 100% rename from packages/workspace/src/assets/upload.test.ts rename to packages/computer/src/assets/upload.test.ts diff --git a/packages/workspace/src/assets/upload.ts b/packages/computer/src/assets/upload.ts similarity index 100% rename from packages/workspace/src/assets/upload.ts rename to packages/computer/src/assets/upload.ts diff --git a/packages/workspace/src/backend.ts b/packages/computer/src/backend.ts similarity index 94% rename from packages/workspace/src/backend.ts rename to packages/computer/src/backend.ts index 18eaaab2..8ca50a1d 100644 --- a/packages/workspace/src/backend.ts +++ b/packages/computer/src/backend.ts @@ -18,7 +18,7 @@ // not see method signatures; they only produce handles and tear // them down. -import type { WorkspaceRPC } from "@cloudflare/workspace-rpc"; +import type { WorkspaceRPC } from "@cloudflare/computer-rpc"; export interface WorkspaceBackend { // User-supplied selector. Passed in `ExecOptions.backend` and @@ -46,13 +46,13 @@ export interface WorkspaceBackend { } export interface BackendHandle { - // The composite WorkspaceRPC stub pointing at the wsd + // The composite WorkspaceRPC stub pointing at the computerd // backend produced. rpc: WorkspaceRPC; // Declares whether this backend pairs with an independent // remote store that the Workspace must sync against. // - // "remote" — the default. A wsd-style backend with its own + // "remote" — the default. A computerd-style backend with its own // SQLite-backed VFS; push/pull move entries between // the host store and that remote. // "none" — the backend reuses the host store as its sole diff --git a/packages/workspace/src/backends/container/cloudflare-container.test.ts b/packages/computer/src/backends/container/cloudflare-container.test.ts similarity index 97% rename from packages/workspace/src/backends/container/cloudflare-container.test.ts rename to packages/computer/src/backends/container/cloudflare-container.test.ts index 3fee7a0c..70c86b95 100644 --- a/packages/workspace/src/backends/container/cloudflare-container.test.ts +++ b/packages/computer/src/backends/container/cloudflare-container.test.ts @@ -131,7 +131,7 @@ describe("CloudflareContainerBackend", () => { const names = fake.calls.map((c) => c.name); expect(names).toContain("start"); expect(names).toContain("interceptOutboundHttp"); - expect(fake.interceptedHost).toBe("workspace.internal"); + expect(fake.interceptedHost).toBe("computer.internal"); expect(fake.interceptedWorkspace).toEqual(fakeWorkspace); }); @@ -140,11 +140,11 @@ describe("CloudflareContainerBackend", () => { const backend = new CloudflareContainerBackend({ container: () => ({ getWorkspaceContainer: () => fake.host }), workspace: fakeWorkspace, - egressHost: "wsd.local", + egressHost: "computerd.local", connectTimeoutMs: 300, }); await expect(backend.connect()).rejects.toThrow(); - expect(fake.interceptedHost).toBe("wsd.local"); + expect(fake.interceptedHost).toBe("computerd.local"); }); test("containerEnv option merges onto the start() env", async () => { @@ -220,7 +220,7 @@ describe("CloudflareContainerBackend", () => { container: () => ({ getWorkspaceContainer: () => fake.host }), workspace: fakeWorkspace, }); - const res = await backend.handleFetch(new Request("http://workspace.internal/other")); + const res = await backend.handleFetch(new Request("http://computer.internal/other")); expect(res.status).toBe(404); }); @@ -230,7 +230,7 @@ describe("CloudflareContainerBackend", () => { container: () => ({ getWorkspaceContainer: () => fake.host }), workspace: fakeWorkspace, }); - const res = await backend.handleFetch(new Request("http://workspace.internal/ws")); + const res = await backend.handleFetch(new Request("http://computer.internal/ws")); expect(res.status).toBe(426); }); diff --git a/packages/workspace/src/backends/container/cloudflare-container.ts b/packages/computer/src/backends/container/cloudflare-container.ts similarity index 94% rename from packages/workspace/src/backends/container/cloudflare-container.ts rename to packages/computer/src/backends/container/cloudflare-container.ts index fc5df7df..d18b0bf1 100644 --- a/packages/workspace/src/backends/container/cloudflare-container.ts +++ b/packages/computer/src/backends/container/cloudflare-container.ts @@ -1,4 +1,4 @@ -// CloudflareContainerBackend — backs Workspace with a wsd instance +// CloudflareContainerBackend — backs Workspace with a computerd instance // running inside a Cloudflare Container. // // The backend drives container lifecycle through an IWorkspaceContainerAPI @@ -8,10 +8,10 @@ // // Same-DO shape (one DO owns both the container and the Workspace): // -// class WsdContainer extends DurableObject { +// class ComputerdContainer extends DurableObject { // #backend = new CloudflareContainerBackend({ // container: () => this.ws, -// workspace: { binding: "WsdContainer", id: this.ctx.id.toString() }, +// workspace: { binding: "ComputerdContainer", id: this.ctx.id.toString() }, // }); // #workspace = new Workspace({ backends: [this.#backend] }); // @@ -27,7 +27,7 @@ // #backend = new CloudflareContainerBackend({ // container: async () => { // const memberId = await pickPoolMember(this.env, this.ctx.id); -// return this.env.WsdHost.get(this.env.WsdHost.idFromString(memberId)); +// return this.env.ComputerdHost.get(this.env.ComputerdHost.idFromString(memberId)); // }, // workspace: { binding: "AgentDO", id: this.ctx.id.toString() }, // }); @@ -44,13 +44,13 @@ // listens for and uses to drop its cached handle so the next call // rebuilds against a fresh session. -import type { WorkspaceRPC } from "@cloudflare/workspace-rpc"; +import type { WorkspaceRPC } from "@cloudflare/computer-rpc"; import { newWebSocketRpcSession, type RpcStub } from "capnweb"; import type { BackendHandle, WorkspaceBackend } from "../../backend.js"; import { startHeartbeat } from "../../heartbeat.js"; import type { IWorkspaceContainerAPI, WorkspaceRef } from "./container-host.js"; -import { probeWsdHealth } from "./health-probe.js"; +import { probeComputerdHealth } from "./health-probe.js"; // What the backend's `container` factory returns: anything with // a getWorkspaceContainer() method — the shape withWorkspaceContainer @@ -78,12 +78,12 @@ export interface CloudflareContainerBackendOptions { // survives the Workers RPC hop to a cross-DO container host. workspace: WorkspaceRef; - // Hostname wsd will dial back. Defaults to "workspace.internal". + // Hostname computerd will dial back. Defaults to "computer.internal". // Override for tests or to avoid collisions with other backends // sharing the same container host. egressHost?: string; - // TCP port wsd listens on inside the container. Default 8080, + // TCP port computerd listens on inside the container. Default 8080, // matching the Dockerfile shipped with examples/container. containerPort?: number; @@ -102,14 +102,14 @@ export interface CloudflareContainerBackendOptions { heartbeatIntervalMs?: number; // Number of forced restart attempts after startup readiness - // fails. The first attempt runs host.start() then probes wsd; - // each restart attempt runs host.restart() then probes wsd + // fails. The first attempt runs host.start() then probes computerd; + // each restart attempt runs host.restart() then probes computerd // again. Defaults to 1 (one restart after the initial start). // Set 0 to disable restart on failed readiness. restartAttempts?: number; // Per-probe timeout for the startup health probe. Defaults to - // 2 seconds. The shared probeWsdHealth helper aborts the request + // 2 seconds. The shared probeComputerdHealth helper aborts the request // when it elapses; the next probe in the loop carries the // remaining readiness budget. healthProbeTimeoutMs?: number; @@ -129,7 +129,7 @@ export interface CloudflareContainerBackendOptions { id?: string; } -const DEFAULT_EGRESS_HOST = "workspace.internal"; +const DEFAULT_EGRESS_HOST = "computer.internal"; const DEFAULT_CONTAINER_PORT = 8080; const DEFAULT_CONNECT_TIMEOUT_MS = 30_000; const DEFAULT_HEARTBEAT_INTERVAL_MS = 20_000; @@ -198,7 +198,7 @@ export class CloudflareContainerBackend implements WorkspaceBackend { await host.start(env); await host.interceptOutboundHttp(this.#options.egressHost, this.#options.workspace); - // Arm the upgrade promise before posting /connect — wsd + // Arm the upgrade promise before posting /connect — computerd // dials back as soon as /health on the egress answers, so // the upgrade can arrive before the POST resolves. this.#armUpgrade(); @@ -331,7 +331,7 @@ export class CloudflareContainerBackend implements WorkspaceBackend { // Drive startup readiness with bounded restart attempts. Each // attempt runs the shared probe in a backoff loop until either - // wsd answers, the per-attempt budget elapses, or the overall + // computerd answers, the per-attempt budget elapses, or the overall // connect deadline elapses. On a failed attempt with restarts // remaining, run host.restart(env) and try again. async #readyWithRestarts( @@ -383,7 +383,7 @@ export class CloudflareContainerBackend implements WorkspaceBackend { } } - this.#rejectUpgrade?.(new Error("wsd never became healthy")); + this.#rejectUpgrade?.(new Error("computerd never became healthy")); this.#clearUpgrade(); throw new Error( this.#formatStageError("health", { @@ -402,7 +402,7 @@ export class CloudflareContainerBackend implements WorkspaceBackend { let lastError: unknown; while (Date.now() < deadline) { try { - await probeWsdHealth(host, { + await probeComputerdHealth(host, { port: this.#options.containerPort, path: "/health", timeoutMs: Math.min( @@ -419,7 +419,7 @@ export class CloudflareContainerBackend implements WorkspaceBackend { delay = Math.min(delay * 2, this.#options.healthRetryMaxDelayMs); } } - throw lastError ?? new Error("wsd health probe timed out"); + throw lastError ?? new Error("computerd health probe timed out"); } #formatStageError( diff --git a/packages/workspace/src/backends/container/container-host.ts b/packages/computer/src/backends/container/container-host.ts similarity index 96% rename from packages/workspace/src/backends/container/container-host.ts rename to packages/computer/src/backends/container/container-host.ts index fffe09f9..aabcd354 100644 --- a/packages/workspace/src/backends/container/container-host.ts +++ b/packages/computer/src/backends/container/container-host.ts @@ -45,7 +45,7 @@ export interface WorkspaceRef { export interface IWorkspaceContainerAPI { // Idempotent start. Returns once the runtime has accepted the // start command; readiness is verified by the backend through - // probeWsdHealth against port(). + // probeComputerdHealth against port(). start(env: Record): Promise; // Wire `host` → workspace inside the container's egress table. @@ -72,8 +72,8 @@ export interface IWorkspaceContainerAPI { // Coarse diagnostic state. The `running` flag reports whether // the platform still has a container instance attached; it does - // not prove that wsd is listening or responsive. Use - // probeWsdHealth for readiness; use status() only for logs and + // not prove that computerd is listening or responsive. Use + // probeComputerdHealth for readiness; use status() only for logs and // tracing. `exit` is populated from the in-memory monitor() // signal when the most recent container generation exited; it // resets on the next successful start(). @@ -129,7 +129,7 @@ export class WorkspaceContainerAPI extends RpcTarget implements IWorkspaceContai async restart(env: Record) { // destroy() resolves once the platform has torn down the // attached container. A subsequent start() launches a fresh - // generation — ports re-bind, the wsd daemon comes up clean. + // generation — ports re-bind, the computerd daemon comes up clean. // destroyContainerExpectingExit flips the lifecycle flag so // the monitor handler logs the exit as intentional. try { @@ -212,12 +212,12 @@ type DOCtor = new (...args: any[]) => object; // // Cross-DO usage (pool member owns the container): // -// export class WsdHost extends withWorkspaceContainer( +// export class ComputerdHost extends withWorkspaceContainer( // class extends DurableObject {}, // ) {} // // #backend = new CloudflareContainerBackend({ -// container: () => this.env.WsdHost.get(memberId), +// container: () => this.env.ComputerdHost.get(memberId), // workspace: { binding: "Agent", id: this.ctx.id.toString() }, // }); // diff --git a/packages/workspace/src/backends/container/container-lifecycle.test.ts b/packages/computer/src/backends/container/container-lifecycle.test.ts similarity index 100% rename from packages/workspace/src/backends/container/container-lifecycle.test.ts rename to packages/computer/src/backends/container/container-lifecycle.test.ts diff --git a/packages/workspace/src/backends/container/container-lifecycle.ts b/packages/computer/src/backends/container/container-lifecycle.ts similarity index 100% rename from packages/workspace/src/backends/container/container-lifecycle.ts rename to packages/computer/src/backends/container/container-lifecycle.ts diff --git a/packages/workspace/src/backends/container/health-probe.test.ts b/packages/computer/src/backends/container/health-probe.test.ts similarity index 73% rename from packages/workspace/src/backends/container/health-probe.test.ts rename to packages/computer/src/backends/container/health-probe.test.ts index 327acf9c..dd0c2c2f 100644 --- a/packages/workspace/src/backends/container/health-probe.test.ts +++ b/packages/computer/src/backends/container/health-probe.test.ts @@ -1,9 +1,9 @@ import { describe, expect, test, vi } from "vitest"; import type { IWorkspaceContainerAPI } from "./container-host.js"; -import { probeWsdHealth } from "./health-probe.js"; +import { probeComputerdHealth } from "./health-probe.js"; -// probeWsdHealth only consumes fetchPort. The helper is typed +// probeComputerdHealth only consumes fetchPort. The helper is typed // against the wider IWorkspaceContainerAPI to make ergonomic // same-isolate calls cheap, but the test scope is narrower — use // the minimal structural type so the fake doesn't have to stub @@ -19,11 +19,11 @@ function fakeHost( return host as IWorkspaceContainerAPI; } -describe("probeWsdHealth", () => { +describe("probeComputerdHealth", () => { test("resolves on a 2xx response", async () => { const host = fakeHost(async () => new Response(null, { status: 200 })); await expect( - probeWsdHealth(host, { port: 8080, path: "/health", timeoutMs: 1_000 }), + probeComputerdHealth(host, { port: 8080, path: "/health", timeoutMs: 1_000 }), ).resolves.toBeUndefined(); }); @@ -34,14 +34,16 @@ describe("probeWsdHealth", () => { calls.push({ port, url: req.url, method: req.method }); return new Response(null, { status: 200 }); }); - await probeWsdHealth(host, { port: 9090, path: "/__wsd/info", timeoutMs: 1_000 }); - expect(calls).toEqual([{ port: 9090, url: "http://container/__wsd/info", method: "HEAD" }]); + await probeComputerdHealth(host, { port: 9090, path: "/__computerd/info", timeoutMs: 1_000 }); + expect(calls).toEqual([ + { port: 9090, url: "http://container/__computerd/info", method: "HEAD" }, + ]); }); test("rejects on a non-2xx response with the status in the message", async () => { const host = fakeHost(async () => new Response("bad", { status: 503 })); await expect( - probeWsdHealth(host, { port: 8080, path: "/health", timeoutMs: 1_000 }), + probeComputerdHealth(host, { port: 8080, path: "/health", timeoutMs: 1_000 }), ).rejects.toThrow(/503/); }); @@ -50,13 +52,13 @@ describe("probeWsdHealth", () => { throw new Error("connection refused"); }); await expect( - probeWsdHealth(host, { port: 8080, path: "/health", timeoutMs: 1_000 }), + probeComputerdHealth(host, { port: 8080, path: "/health", timeoutMs: 1_000 }), ).rejects.toThrow(/connection refused/); }); test("aborts the request after timeoutMs", async () => { const host = fakeHost(async (_port, _input, init) => { - // Simulate a never-responding wsd: wait until the AbortSignal + // Simulate a never-responding computerd: wait until the AbortSignal // fires, then reject with an AbortError so the helper sees the // timeout surface as a rejection. await new Promise((_, reject) => { @@ -65,7 +67,7 @@ describe("probeWsdHealth", () => { return new Response(null, { status: 200 }); }); await expect( - probeWsdHealth(host, { port: 8080, path: "/health", timeoutMs: 20 }), + probeComputerdHealth(host, { port: 8080, path: "/health", timeoutMs: 20 }), ).rejects.toThrow(/aborted|timeout/i); }); }); diff --git a/packages/workspace/src/backends/container/health-probe.ts b/packages/computer/src/backends/container/health-probe.ts similarity index 79% rename from packages/workspace/src/backends/container/health-probe.ts rename to packages/computer/src/backends/container/health-probe.ts index c57c6f3e..fe819139 100644 --- a/packages/workspace/src/backends/container/health-probe.ts +++ b/packages/computer/src/backends/container/health-probe.ts @@ -1,4 +1,4 @@ -// Shared wsd health probe. +// Shared computerd health probe. // // Used in two places that must agree on what "healthy" means: // @@ -8,7 +8,7 @@ // // A single HEAD against the configured healthPath on the container // port. ctx.container.running tells the runtime the container is -// attached, not that wsd is listening; this probe closes the gap. +// attached, not that computerd is listening; this probe closes the gap. // // Probe failures bubble out as rejections; callers apply their own // backoff / budget. No retries here — keeping the helper a single @@ -16,8 +16,8 @@ import type { IWorkspaceContainerAPI } from "./container-host.js"; -export interface WsdHealthProbeOptions { - // TCP port wsd listens on inside the container. +export interface ComputerdHealthProbeOptions { + // TCP port computerd listens on inside the container. port: number; // Path to probe. Defaults to /health at the call site; required // here so both callers thread the same value. @@ -27,9 +27,9 @@ export interface WsdHealthProbeOptions { timeoutMs: number; } -export async function probeWsdHealth( +export async function probeComputerdHealth( host: IWorkspaceContainerAPI, - options: WsdHealthProbeOptions, + options: ComputerdHealthProbeOptions, ): Promise { const signal = AbortSignal.timeout(options.timeoutMs); const res = await host.fetchPort(options.port, `http://container${options.path}`, { @@ -41,6 +41,6 @@ export async function probeWsdHealth( // though it should be empty; cancel() is a no-op for null. void res.body?.cancel(); if (!res.ok) { - throw new Error(`wsd health returned ${res.status}`); + throw new Error(`computerd health returned ${res.status}`); } } diff --git a/packages/workspace/src/backends/container/index.ts b/packages/computer/src/backends/container/index.ts similarity index 64% rename from packages/workspace/src/backends/container/index.ts rename to packages/computer/src/backends/container/index.ts index be252e41..df9b0055 100644 --- a/packages/workspace/src/backends/container/index.ts +++ b/packages/computer/src/backends/container/index.ts @@ -1,7 +1,7 @@ -// Public surface of @cloudflare/workspace/backends/container. +// Public surface of @cloudflare/computer/backends/container. // -// The container backend pairs a Workspace with a wsd daemon -// running inside a Cloudflare Container. wsd owns its own +// The container backend pairs a Workspace with a computerd daemon +// running inside a Cloudflare Container. computerd owns its own // SQLite-backed VFS; the package syncs the two stores across a // capnweb WebSocket. // @@ -10,7 +10,7 @@ // import { // CloudflareContainerBackend, // withWorkspaceContainer, -// } from "@cloudflare/workspace/backends/container"; +// } from "@cloudflare/computer/backends/container"; export { CloudflareContainerBackend, diff --git a/packages/workspace/src/backends/test.ts b/packages/computer/src/backends/test.ts similarity index 88% rename from packages/workspace/src/backends/test.ts rename to packages/computer/src/backends/test.ts index cb68937c..75ca24c2 100644 --- a/packages/workspace/src/backends/test.ts +++ b/packages/computer/src/backends/test.ts @@ -1,18 +1,18 @@ // TestBackend — the simplest possible backend. Takes a URL -// pointing at an already-running wsd instance and constructs +// pointing at an already-running computerd instance and constructs // a SyncRPC client against it. // // No subprocesses, no Docker calls, no file IO. The package // runs unchanged under workerd — TestBackend is just URL // plumbing. The test harness outside the package is what -// stands up the wsd container and exposes its port. +// stands up the computerd container and exposes its port. -import { createWorkspaceClient } from "@cloudflare/workspace-rpc/client"; +import { createWorkspaceClient } from "@cloudflare/computer-rpc/client"; import type { BackendHandle, WorkspaceBackend } from "../backend.js"; export interface TestBackendOptions { - // URL pointing at the wsd HTTP server. ws://, wss://, + // URL pointing at the computerd HTTP server. ws://, wss://, // http://, and https:// are all accepted; the http(s) // schemes are normalised to ws(s) when constructing the // capnweb WebSocket session. @@ -76,7 +76,7 @@ async function probeHealth(url: string): Promise { } catch (cause) { throw new Error( `TestBackend: ${healthUrl} is not reachable. ` + - `Is the wsd container running? (${cause instanceof Error ? cause.message : String(cause)})`, + `Is the computerd container running? (${cause instanceof Error ? cause.message : String(cause)})`, ); } if (!response.ok) { diff --git a/packages/workspace/src/backends/worker/adapter.test.ts b/packages/computer/src/backends/worker/adapter.test.ts similarity index 100% rename from packages/workspace/src/backends/worker/adapter.test.ts rename to packages/computer/src/backends/worker/adapter.test.ts diff --git a/packages/workspace/src/backends/worker/adapter.ts b/packages/computer/src/backends/worker/adapter.ts similarity index 100% rename from packages/workspace/src/backends/worker/adapter.ts rename to packages/computer/src/backends/worker/adapter.ts diff --git a/packages/workspace/src/backends/worker/artifacts-command.test.ts b/packages/computer/src/backends/worker/artifacts-command.test.ts similarity index 100% rename from packages/workspace/src/backends/worker/artifacts-command.test.ts rename to packages/computer/src/backends/worker/artifacts-command.test.ts diff --git a/packages/workspace/src/backends/worker/artifacts-command.ts b/packages/computer/src/backends/worker/artifacts-command.ts similarity index 100% rename from packages/workspace/src/backends/worker/artifacts-command.ts rename to packages/computer/src/backends/worker/artifacts-command.ts diff --git a/packages/workspace/src/backends/worker/assets-command.test.ts b/packages/computer/src/backends/worker/assets-command.test.ts similarity index 100% rename from packages/workspace/src/backends/worker/assets-command.test.ts rename to packages/computer/src/backends/worker/assets-command.test.ts diff --git a/packages/workspace/src/backends/worker/assets-command.ts b/packages/computer/src/backends/worker/assets-command.ts similarity index 100% rename from packages/workspace/src/backends/worker/assets-command.ts rename to packages/computer/src/backends/worker/assets-command.ts diff --git a/packages/workspace/src/backends/worker/entrypoint.test.ts b/packages/computer/src/backends/worker/entrypoint.test.ts similarity index 99% rename from packages/workspace/src/backends/worker/entrypoint.test.ts rename to packages/computer/src/backends/worker/entrypoint.test.ts index 6782f38c..63dc5b8a 100644 --- a/packages/workspace/src/backends/worker/entrypoint.test.ts +++ b/packages/computer/src/backends/worker/entrypoint.test.ts @@ -272,7 +272,7 @@ describe("ShellWorker", () => { }[]; const stdout = events.find((e) => e.name === "stdout"); const exit = events.find((e) => e.name === "exit"); - expect(stdout?.value).toContain("@cloudflare/workspace"); + expect(stdout?.value).toContain("@cloudflare/computer"); expect(exit?.value).toBe(0); }); diff --git a/packages/workspace/src/backends/worker/entrypoint.ts b/packages/computer/src/backends/worker/entrypoint.ts similarity index 98% rename from packages/workspace/src/backends/worker/entrypoint.ts rename to packages/computer/src/backends/worker/entrypoint.ts index c1d8460d..531b8efe 100644 --- a/packages/workspace/src/backends/worker/entrypoint.ts +++ b/packages/computer/src/backends/worker/entrypoint.ts @@ -1,5 +1,5 @@ // ShellWorker — the WorkerEntrypoint a user Worker exposes for -// the WorkerBackend in @cloudflare/workspace to call. +// the WorkerBackend in @cloudflare/computer to call. // // Each exec call independently reaches the host Workspace // through a DurableObjectNamespace binding wired into env by @@ -75,7 +75,7 @@ export interface HostWorkspaceStub extends GitCommandHost, AssetsCommandHost { const DEFAULT_CWD = "/workspace"; -// Wire event shape. Matches @cloudflare/workspace-rpc's ExecEvent +// Wire event shape. Matches @cloudflare/computer-rpc's ExecEvent // but with stdout/stderr values as utf8 strings (the host-side // decoder re-encodes to Uint8Array before WorkspaceShell sees // them). diff --git a/packages/workspace/src/backends/worker/generated-bundle.test.ts b/packages/computer/src/backends/worker/generated-bundle.test.ts similarity index 100% rename from packages/workspace/src/backends/worker/generated-bundle.test.ts rename to packages/computer/src/backends/worker/generated-bundle.test.ts diff --git a/packages/workspace/src/backends/worker/git-command.test.ts b/packages/computer/src/backends/worker/git-command.test.ts similarity index 100% rename from packages/workspace/src/backends/worker/git-command.test.ts rename to packages/computer/src/backends/worker/git-command.test.ts diff --git a/packages/workspace/src/backends/worker/git-command.ts b/packages/computer/src/backends/worker/git-command.ts similarity index 100% rename from packages/workspace/src/backends/worker/git-command.ts rename to packages/computer/src/backends/worker/git-command.ts diff --git a/packages/workspace/src/backends/worker/index.ts b/packages/computer/src/backends/worker/index.ts similarity index 91% rename from packages/workspace/src/backends/worker/index.ts rename to packages/computer/src/backends/worker/index.ts index ebd00a42..1dd9cf23 100644 --- a/packages/workspace/src/backends/worker/index.ts +++ b/packages/computer/src/backends/worker/index.ts @@ -1,4 +1,4 @@ -// Public surface of @cloudflare/workspace/backends/worker. +// Public surface of @cloudflare/computer/backends/worker. // // The worker backend pairs a Workspace with a just-bash shell // running in a Dynamic Worker minted through env.LOADER. Every @@ -8,7 +8,7 @@ // // Imported via: // -// import { WorkerBackend } from "@cloudflare/workspace/backends/worker"; +// import { WorkerBackend } from "@cloudflare/computer/backends/worker"; // // The package ships SHELL_MODULES — a record of module name → // source string covering the pre-built ShellWorker entry plus diff --git a/packages/workspace/src/backends/worker/runtime-modules.ts b/packages/computer/src/backends/worker/runtime-modules.ts similarity index 100% rename from packages/workspace/src/backends/worker/runtime-modules.ts rename to packages/computer/src/backends/worker/runtime-modules.ts diff --git a/packages/workspace/src/backends/worker/script/build-bundle.mjs b/packages/computer/src/backends/worker/script/build-bundle.mjs similarity index 98% rename from packages/workspace/src/backends/worker/script/build-bundle.mjs rename to packages/computer/src/backends/worker/script/build-bundle.mjs index 473e0354..c749c349 100644 --- a/packages/workspace/src/backends/worker/script/build-bundle.mjs +++ b/packages/computer/src/backends/worker/script/build-bundle.mjs @@ -16,7 +16,7 @@ // (~650 KB) and optional features stay free until used. // // The output file is gitignored. It's regenerated by the -// workspace package's prepare / pretest / pretypecheck scripts +// computer package's prepare / pretest / pretypecheck scripts // so any install from this repo or downstream consumer sees a // fresh bundle matching the source. diff --git a/packages/workspace/src/backends/worker/worker.test.ts b/packages/computer/src/backends/worker/worker.test.ts similarity index 100% rename from packages/workspace/src/backends/worker/worker.test.ts rename to packages/computer/src/backends/worker/worker.test.ts diff --git a/packages/workspace/src/backends/worker/worker.ts b/packages/computer/src/backends/worker/worker.ts similarity index 99% rename from packages/workspace/src/backends/worker/worker.ts rename to packages/computer/src/backends/worker/worker.ts index 4635c2a2..c0ebf291 100644 --- a/packages/workspace/src/backends/worker/worker.ts +++ b/packages/computer/src/backends/worker/worker.ts @@ -22,7 +22,7 @@ // sync: "none". Workspace.push and Workspace.pull short-circuit; // reconcileWatermarks on connect is skipped. -import type { ExecEvent, ShellRPC, SyncRPC, WorkspaceRPC } from "@cloudflare/workspace-rpc"; +import type { ExecEvent, ShellRPC, SyncRPC, WorkspaceRPC } from "@cloudflare/computer-rpc"; import type { BackendHandle, WorkspaceBackend } from "../../backend.js"; import type { WorkspaceServiceProxyProps } from "../../proxy.js"; @@ -103,7 +103,7 @@ export interface WorkerBackendOptions { loaderId?: string; // Compatibility date for the Dynamic Worker. Defaults to the - // compatibility date the workspace package was published with. + // compatibility date the computer package was published with. compatibilityDate?: string; // Extra compatibility flags merged onto the default of diff --git a/packages/workspace/src/client.test.ts b/packages/computer/src/client.test.ts similarity index 100% rename from packages/workspace/src/client.test.ts rename to packages/computer/src/client.test.ts diff --git a/packages/workspace/src/client.ts b/packages/computer/src/client.ts similarity index 100% rename from packages/workspace/src/client.ts rename to packages/computer/src/client.ts diff --git a/packages/workspace/src/exec-wire.test.ts b/packages/computer/src/exec-wire.test.ts similarity index 100% rename from packages/workspace/src/exec-wire.test.ts rename to packages/computer/src/exec-wire.test.ts diff --git a/packages/workspace/src/exec-wire.ts b/packages/computer/src/exec-wire.ts similarity index 100% rename from packages/workspace/src/exec-wire.ts rename to packages/computer/src/exec-wire.ts diff --git a/packages/workspace/src/git/adapter.ts b/packages/computer/src/git/adapter.ts similarity index 96% rename from packages/workspace/src/git/adapter.ts rename to packages/computer/src/git/adapter.ts index c9782b03..68b5a087 100644 --- a/packages/workspace/src/git/adapter.ts +++ b/packages/computer/src/git/adapter.ts @@ -2,7 +2,7 @@ // FsClient that isomorphic-git can consume directly. // // Mirrors the recipe in `examples/think/src/tools/git/vfs.ts` -// (which mirrors the trick wsd uses for its FUSE mount): +// (which mirrors the trick computerd uses for its FUSE mount): // // 1. SQLiteWorkspaceProvider already implements the full node:fs // surface @platformatic/vfs's VirtualFileSystem needs, @@ -20,7 +20,7 @@ // fixes the detection. // // `@platformatic/vfs` is imported lazily so the base -// `@cloudflare/workspace` bundle has no static reference to it. +// `@cloudflare/computer` bundle has no static reference to it. import type { SQLiteWorkspaceProvider } from "@cloudflare/dofs"; @@ -113,7 +113,7 @@ export async function workspaceIsomorphicGitClient( ({ create, VirtualProvider } = await import("@platformatic/vfs")); } catch (cause) { throw new Error( - "@cloudflare/workspace/git requires @platformatic/vfs as an optional peer dependency. " + + "@cloudflare/computer/git requires @platformatic/vfs as an optional peer dependency. " + "Install it, or pass `fs` to clone() explicitly.", { cause }, ); diff --git a/packages/workspace/src/git/cli.test.ts b/packages/computer/src/git/cli.test.ts similarity index 99% rename from packages/workspace/src/git/cli.test.ts rename to packages/computer/src/git/cli.test.ts index 86adf23d..a05e1c86 100644 --- a/packages/workspace/src/git/cli.test.ts +++ b/packages/computer/src/git/cli.test.ts @@ -374,7 +374,7 @@ describe("runGitCli — dispatch", () => { const { client } = fakeClient(); const res = await runGitCli(client, { argv: ["version"] }); expect(res.exitCode).toBe(0); - expect(res.stdout).toContain("@cloudflare/workspace"); + expect(res.stdout).toContain("@cloudflare/computer"); }); it("unknown subcommands exit 1 with a git-shaped stderr line", async () => { @@ -455,25 +455,25 @@ describe("runGitCli — clone argv parsing", () => { it("derives the dir from the URL basename, stripping a .git suffix", async () => { const { client, calls } = fakeClient(); await runGitCli(client, { - argv: ["clone", "https://github.com/cloudflare/workspace"], + argv: ["clone", "https://github.com/cloudflare/computer"], cwd: "/workspace", }); - expect(calls.clone[0].dir).toBe("/workspace/workspace"); + expect(calls.clone[0].dir).toBe("/workspace/computer"); }); it("derives the dir from a URL with a trailing slash", async () => { const { client, calls } = fakeClient(); await runGitCli(client, { - argv: ["clone", "https://github.com/cloudflare/workspace/"], + argv: ["clone", "https://github.com/cloudflare/computer/"], cwd: "/workspace", }); - expect(calls.clone[0].dir).toBe("/workspace/workspace"); + expect(calls.clone[0].dir).toBe("/workspace/computer"); }); it("prefers an explicit destination over the derived basename", async () => { const { client, calls } = fakeClient(); await runGitCli(client, { - argv: ["clone", "https://github.com/cloudflare/workspace", "/dst/cf-workspace"], + argv: ["clone", "https://github.com/cloudflare/computer", "/dst/cf-workspace"], cwd: "/work", }); expect(calls.clone[0].dir).toBe("/dst/cf-workspace"); diff --git a/packages/workspace/src/git/cli.ts b/packages/computer/src/git/cli.ts similarity index 99% rename from packages/workspace/src/git/cli.ts rename to packages/computer/src/git/cli.ts index 7f86fb0d..8b62d372 100644 --- a/packages/workspace/src/git/cli.ts +++ b/packages/computer/src/git/cli.ts @@ -201,7 +201,7 @@ function printVersion(): GitCliResult { // Consumers that fingerprint via `git --version` will see this // and can branch on it. return { - stdout: "git version 0.0.0 (@cloudflare/workspace)\n", + stdout: "git version 0.0.0 (@cloudflare/computer)\n", stderr: "", exitCode: 0, }; diff --git a/packages/workspace/src/git/clone.test.ts b/packages/computer/src/git/clone.test.ts similarity index 99% rename from packages/workspace/src/git/clone.test.ts rename to packages/computer/src/git/clone.test.ts index 58ffba25..7b429c0c 100644 --- a/packages/workspace/src/git/clone.test.ts +++ b/packages/computer/src/git/clone.test.ts @@ -1,4 +1,4 @@ -// Tests for `@cloudflare/workspace/git`'s `cloneWith` wrapper. +// Tests for `@cloudflare/computer/git`'s `cloneWith` wrapper. // // `cloneWith` is fundamentally an option-translator: it sets a // handful of defaults, splits the user's request into a diff --git a/packages/workspace/src/git/clone.ts b/packages/computer/src/git/clone.ts similarity index 100% rename from packages/workspace/src/git/clone.ts rename to packages/computer/src/git/clone.ts diff --git a/packages/workspace/src/git/commit.test.ts b/packages/computer/src/git/commit.test.ts similarity index 100% rename from packages/workspace/src/git/commit.test.ts rename to packages/computer/src/git/commit.test.ts diff --git a/packages/workspace/src/git/commit.ts b/packages/computer/src/git/commit.ts similarity index 100% rename from packages/workspace/src/git/commit.ts rename to packages/computer/src/git/commit.ts diff --git a/packages/workspace/src/git/diff.test.ts b/packages/computer/src/git/diff.test.ts similarity index 100% rename from packages/workspace/src/git/diff.test.ts rename to packages/computer/src/git/diff.test.ts diff --git a/packages/workspace/src/git/diff.ts b/packages/computer/src/git/diff.ts similarity index 100% rename from packages/workspace/src/git/diff.ts rename to packages/computer/src/git/diff.ts diff --git a/packages/workspace/src/git/errors.ts b/packages/computer/src/git/errors.ts similarity index 100% rename from packages/workspace/src/git/errors.ts rename to packages/computer/src/git/errors.ts diff --git a/packages/workspace/src/git/index.test.ts b/packages/computer/src/git/index.test.ts similarity index 100% rename from packages/workspace/src/git/index.test.ts rename to packages/computer/src/git/index.test.ts diff --git a/packages/workspace/src/git/index.ts b/packages/computer/src/git/index.ts similarity index 98% rename from packages/workspace/src/git/index.ts rename to packages/computer/src/git/index.ts index 8647a504..478324b5 100644 --- a/packages/workspace/src/git/index.ts +++ b/packages/computer/src/git/index.ts @@ -1,4 +1,4 @@ -// Public surface of @cloudflare/workspace/git. +// Public surface of @cloudflare/computer/git. // // `createGitClient({ ws })` is the one entry point. It binds a // workspace handle once and returns a `GitClient` whose methods @@ -701,7 +701,7 @@ async function loadIsomorphicGit(): Promise { return (mod.default ?? mod) as unknown as T; } catch (cause) { throw new Error( - "@cloudflare/workspace/git requires isomorphic-git as an optional peer dependency. " + + "@cloudflare/computer/git requires isomorphic-git as an optional peer dependency. " + "Install isomorphic-git.", { cause }, ); @@ -723,7 +723,7 @@ async function loadCreatePatch(): Promise { return mod.createPatch; } catch (cause) { throw new Error( - "@cloudflare/workspace/git requires `diff` as an optional peer dependency. " + + "@cloudflare/computer/git requires `diff` as an optional peer dependency. " + "Install `diff`.", { cause }, ); diff --git a/packages/workspace/src/git/init.test.ts b/packages/computer/src/git/init.test.ts similarity index 100% rename from packages/workspace/src/git/init.test.ts rename to packages/computer/src/git/init.test.ts diff --git a/packages/workspace/src/git/init.ts b/packages/computer/src/git/init.ts similarity index 100% rename from packages/workspace/src/git/init.ts rename to packages/computer/src/git/init.ts diff --git a/packages/workspace/src/git/network.test.ts b/packages/computer/src/git/network.test.ts similarity index 100% rename from packages/workspace/src/git/network.test.ts rename to packages/computer/src/git/network.test.ts diff --git a/packages/workspace/src/git/network.ts b/packages/computer/src/git/network.ts similarity index 100% rename from packages/workspace/src/git/network.ts rename to packages/computer/src/git/network.ts diff --git a/packages/workspace/src/git/plumbing.test.ts b/packages/computer/src/git/plumbing.test.ts similarity index 100% rename from packages/workspace/src/git/plumbing.test.ts rename to packages/computer/src/git/plumbing.test.ts diff --git a/packages/workspace/src/git/plumbing.ts b/packages/computer/src/git/plumbing.ts similarity index 100% rename from packages/workspace/src/git/plumbing.ts rename to packages/computer/src/git/plumbing.ts diff --git a/packages/workspace/src/git/reads.test.ts b/packages/computer/src/git/reads.test.ts similarity index 100% rename from packages/workspace/src/git/reads.test.ts rename to packages/computer/src/git/reads.test.ts diff --git a/packages/workspace/src/git/reads.ts b/packages/computer/src/git/reads.ts similarity index 100% rename from packages/workspace/src/git/reads.ts rename to packages/computer/src/git/reads.ts diff --git a/packages/workspace/src/git/refs.test.ts b/packages/computer/src/git/refs.test.ts similarity index 100% rename from packages/workspace/src/git/refs.test.ts rename to packages/computer/src/git/refs.test.ts diff --git a/packages/workspace/src/git/refs.ts b/packages/computer/src/git/refs.ts similarity index 100% rename from packages/workspace/src/git/refs.ts rename to packages/computer/src/git/refs.ts diff --git a/packages/workspace/src/git/staging.test.ts b/packages/computer/src/git/staging.test.ts similarity index 100% rename from packages/workspace/src/git/staging.test.ts rename to packages/computer/src/git/staging.test.ts diff --git a/packages/workspace/src/git/staging.ts b/packages/computer/src/git/staging.ts similarity index 100% rename from packages/workspace/src/git/staging.ts rename to packages/computer/src/git/staging.ts diff --git a/packages/workspace/src/git/status.test.ts b/packages/computer/src/git/status.test.ts similarity index 100% rename from packages/workspace/src/git/status.test.ts rename to packages/computer/src/git/status.test.ts diff --git a/packages/workspace/src/git/status.ts b/packages/computer/src/git/status.ts similarity index 100% rename from packages/workspace/src/git/status.ts rename to packages/computer/src/git/status.ts diff --git a/packages/workspace/src/git/worktree.test.ts b/packages/computer/src/git/worktree.test.ts similarity index 100% rename from packages/workspace/src/git/worktree.test.ts rename to packages/computer/src/git/worktree.test.ts diff --git a/packages/workspace/src/git/worktree.ts b/packages/computer/src/git/worktree.ts similarity index 100% rename from packages/workspace/src/git/worktree.ts rename to packages/computer/src/git/worktree.ts diff --git a/packages/workspace/src/heartbeat.test.ts b/packages/computer/src/heartbeat.test.ts similarity index 100% rename from packages/workspace/src/heartbeat.test.ts rename to packages/computer/src/heartbeat.test.ts diff --git a/packages/workspace/src/heartbeat.ts b/packages/computer/src/heartbeat.ts similarity index 100% rename from packages/workspace/src/heartbeat.ts rename to packages/computer/src/heartbeat.ts diff --git a/packages/workspace/src/index.ts b/packages/computer/src/index.ts similarity index 89% rename from packages/workspace/src/index.ts rename to packages/computer/src/index.ts index fa374209..dc925d44 100644 --- a/packages/workspace/src/index.ts +++ b/packages/computer/src/index.ts @@ -1,16 +1,16 @@ -// Public surface of @cloudflare/workspace. +// Public surface of @cloudflare/computer. // // The package runs inside a Cloudflare Worker / Durable // Object. It picks a backend, holds a SyncRPC connection to -// wsd, and exposes a file-shaped facade. +// computerd, and exposes a file-shaped facade. // // Backends ship under sub-path entries so the large built // dependencies they carry (a bundled just-bash for the worker // backend, etc.) can be tree-shaken when a consumer only uses // one of them: // -// import { CloudflareContainerBackend } from "@cloudflare/workspace/backends/container"; -// import { WorkerBackend } from "@cloudflare/workspace/backends/worker"; +// import { CloudflareContainerBackend } from "@cloudflare/computer/backends/container"; +// import { WorkerBackend } from "@cloudflare/computer/backends/worker"; // // TestBackend stays on the main entry because it's a thin // test-only fake with no payload. diff --git a/packages/workspace/src/mounts/index.test.ts b/packages/computer/src/mounts/index.test.ts similarity index 99% rename from packages/workspace/src/mounts/index.test.ts rename to packages/computer/src/mounts/index.test.ts index 2a637930..2295d933 100644 --- a/packages/workspace/src/mounts/index.test.ts +++ b/packages/computer/src/mounts/index.test.ts @@ -474,7 +474,7 @@ describe("mount indexer", () => { // Wire a no-op SyncRPC just rich enough for #connect() to // succeed and reconcileWatermarks() to find what it needs. // Mirrors the fakeRpc() pattern from workspace.test.ts. - const sync: import("@cloudflare/workspace-rpc").SyncRPC = { + const sync: import("@cloudflare/computer-rpc").SyncRPC = { async push(input) { const reader = input.changes.getReader(); try { @@ -527,7 +527,7 @@ describe("mount indexer", () => { }, }; const notWired = () => Promise.reject(new Error("shell not wired")); - const shell: import("@cloudflare/workspace-rpc").ShellRPC = { + const shell: import("@cloudflare/computer-rpc").ShellRPC = { exec: notWired, getExec: notWired, killExec: notWired, diff --git a/packages/workspace/src/mounts/index.ts b/packages/computer/src/mounts/index.ts similarity index 100% rename from packages/workspace/src/mounts/index.ts rename to packages/computer/src/mounts/index.ts diff --git a/packages/workspace/src/mounts/providers/r2.test.ts b/packages/computer/src/mounts/providers/r2.test.ts similarity index 100% rename from packages/workspace/src/mounts/providers/r2.test.ts rename to packages/computer/src/mounts/providers/r2.test.ts diff --git a/packages/workspace/src/mounts/providers/r2.ts b/packages/computer/src/mounts/providers/r2.ts similarity index 100% rename from packages/workspace/src/mounts/providers/r2.ts rename to packages/computer/src/mounts/providers/r2.ts diff --git a/packages/workspace/src/mounts/registry.test.ts b/packages/computer/src/mounts/registry.test.ts similarity index 100% rename from packages/workspace/src/mounts/registry.test.ts rename to packages/computer/src/mounts/registry.test.ts diff --git a/packages/workspace/src/mounts/registry.ts b/packages/computer/src/mounts/registry.ts similarity index 100% rename from packages/workspace/src/mounts/registry.ts rename to packages/computer/src/mounts/registry.ts diff --git a/packages/workspace/src/mounts/types.ts b/packages/computer/src/mounts/types.ts similarity index 100% rename from packages/workspace/src/mounts/types.ts rename to packages/computer/src/mounts/types.ts diff --git a/packages/workspace/src/observe-integration.test.ts b/packages/computer/src/observe-integration.test.ts similarity index 97% rename from packages/workspace/src/observe-integration.test.ts rename to packages/computer/src/observe-integration.test.ts index 25f6909f..1fe197e1 100644 --- a/packages/workspace/src/observe-integration.test.ts +++ b/packages/computer/src/observe-integration.test.ts @@ -4,8 +4,8 @@ // attributes. The recorder lives in `./observe.test.ts` and is shared // across both files. +import type { SyncRPC, WorkspaceRPC } from "@cloudflare/computer-rpc"; import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; -import type { SyncRPC, WorkspaceRPC } from "@cloudflare/workspace-rpc"; import { describe, expect, it } from "vitest"; import type { BackendHandle, WorkspaceBackend } from "./backend.js"; @@ -240,12 +240,12 @@ describe("Workspace observer — shell stub", () => { // resolve right away. A minimal envelope is enough — the test only // asserts on span shape, not on stdout content. const execed: string[] = []; - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec(input) { execed.push(input.command); return { id: "exec-1", - events: new ReadableStream({ + events: new ReadableStream({ start(c) { c.enqueue({ id: "exec-1", seq: 0, name: "exit", value: 0 }); c.close(); @@ -308,7 +308,7 @@ describe("Workspace observer — shell stub", () => { sync.fetchChanges = async () => { throw new Error(`WebSocket closed token=${secret}`); }; - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { return { id: "exec-pending", diff --git a/packages/workspace/src/observe-recorder.ts b/packages/computer/src/observe-recorder.ts similarity index 100% rename from packages/workspace/src/observe-recorder.ts rename to packages/computer/src/observe-recorder.ts diff --git a/packages/workspace/src/observe.test.ts b/packages/computer/src/observe.test.ts similarity index 100% rename from packages/workspace/src/observe.test.ts rename to packages/computer/src/observe.test.ts diff --git a/packages/workspace/src/observe.ts b/packages/computer/src/observe.ts similarity index 100% rename from packages/workspace/src/observe.ts rename to packages/computer/src/observe.ts diff --git a/packages/workspace/src/observe/cloudflare.test.ts b/packages/computer/src/observe/cloudflare.test.ts similarity index 100% rename from packages/workspace/src/observe/cloudflare.test.ts rename to packages/computer/src/observe/cloudflare.test.ts diff --git a/packages/workspace/src/observe/cloudflare.ts b/packages/computer/src/observe/cloudflare.ts similarity index 96% rename from packages/workspace/src/observe/cloudflare.ts rename to packages/computer/src/observe/cloudflare.ts index 4448394a..30ede19f 100644 --- a/packages/workspace/src/observe/cloudflare.ts +++ b/packages/computer/src/observe/cloudflare.ts @@ -1,6 +1,6 @@ // Cloudflare runtime adapter for the workspace observer hook. // -// Wires `@cloudflare/workspace`'s span-shaped observer to the Cloudflare +// Wires `@cloudflare/computer`'s span-shaped observer to the Cloudflare // runtime's built-in user tracing surface (`Tracing.enterSpan`). The // runtime owns the span lifecycle: it opens the span, propagates the // active context through `AsyncContextFrame` so nested spans become @@ -37,7 +37,7 @@ // The adapter takes the `Tracing` instance as an argument rather than // importing it directly. That keeps this file resolvable under the // node-based unit tests (where `cloudflare:workers` is not present), -// and it keeps the workspace package free of an implicit dependency on +// and it keeps the computer package free of an implicit dependency on // the runtime module specifier in tools that do not understand it. import type { WorkspaceObserver, WorkspaceSpan } from "../observe.js"; diff --git a/packages/workspace/src/proxy-stub.ts b/packages/computer/src/proxy-stub.ts similarity index 100% rename from packages/workspace/src/proxy-stub.ts rename to packages/computer/src/proxy-stub.ts diff --git a/packages/workspace/src/proxy.ts b/packages/computer/src/proxy.ts similarity index 94% rename from packages/workspace/src/proxy.ts rename to packages/computer/src/proxy.ts index 6d016dac..be8882a8 100644 --- a/packages/workspace/src/proxy.ts +++ b/packages/computer/src/proxy.ts @@ -1,5 +1,5 @@ // WorkspaceProxy — the WorkerEntrypoint a container DO hands to -// ctx.container.interceptOutboundHttp(...) so wsd can dial back +// ctx.container.interceptOutboundHttp(...) so computerd can dial back // into the DO. // // Why this exists as a separate class rather than passing the DO @@ -13,9 +13,9 @@ // // // Worker entry point — re-export the class so the runtime can // // wrap it into a loopback binding. -// export { WorkspaceProxy } from "@cloudflare/workspace"; +// export { WorkspaceProxy } from "@cloudflare/computer"; // -// class WsdContainer extends DurableObject { +// class ComputerdContainer extends DurableObject { // constructor(ctx: DurableObjectState, env: Env) { // super(ctx, env); // this.#backend = new CloudflareContainerBackend({ @@ -23,7 +23,7 @@ // egress: ctx.exports.WorkspaceProxy({ // props: { // // Binding name in env that points back at this DO. -// binding: "WSD", +// binding: "COMPUTERD", // // The DO instance the upgrade should route to. // id: ctx.id.toString(), // }, @@ -45,7 +45,7 @@ // clone and DurableObjectNamespace references aren't clonable. The // proxy looks up `env[binding]` at fetch time and falls back to a // clear error if the name doesn't resolve. The DO class doesn't -// need to live in @cloudflare/workspace — the proxy works for any +// need to live in @cloudflare/computer — the proxy works for any // DO that implements a fetch() handler answering /health and /ws. import { RpcTarget, WorkerEntrypoint } from "cloudflare:workers"; @@ -119,7 +119,7 @@ export class ArtifactsCLITarget extends RpcTarget { // Usage: // // // Worker entry: re-export so the runtime can wrap it. -// export { WorkspaceServiceProxy } from "@cloudflare/workspace"; +// export { WorkspaceServiceProxy } from "@cloudflare/computer"; // // // Inside the DO that owns the Workspace: // const host = ctx.exports.WorkspaceServiceProxy({ @@ -135,7 +135,7 @@ export class ArtifactsCLITarget extends RpcTarget { // // The proxy resolves env[binding] at call time — the same lazy // lookup WorkspaceProxy does for the /ws upgrade path — so the -// DO class doesn't need to live in @cloudflare/workspace. Any +// DO class doesn't need to live in @cloudflare/computer. Any // DO that exposes a `__getWorkspaceStub(): WorkspaceStub` RPC method // works. export interface WorkspaceServiceProxyProps { diff --git a/packages/workspace/src/retry.test.ts b/packages/computer/src/retry.test.ts similarity index 98% rename from packages/workspace/src/retry.test.ts rename to packages/computer/src/retry.test.ts index a3680544..b751bd3b 100644 --- a/packages/workspace/src/retry.test.ts +++ b/packages/computer/src/retry.test.ts @@ -32,10 +32,10 @@ class MemoryRetryScheduler implements SyncRetryScheduler { function retryBackend(options: { onExec(): void; - fetchChanges: import("@cloudflare/workspace-rpc").SyncRPC["fetchChanges"]; + fetchChanges: import("@cloudflare/computer-rpc").SyncRPC["fetchChanges"]; close?: () => Promise; }): WorkspaceBackend { - const sync: import("@cloudflare/workspace-rpc").SyncRPC = { + const sync: import("@cloudflare/computer-rpc").SyncRPC = { async push(input) { return { rev: 0, appliedPushCursor: { rev: input.senderRev, path: null } }; }, diff --git a/packages/workspace/src/sh.test.ts b/packages/computer/src/sh.test.ts similarity index 100% rename from packages/workspace/src/sh.test.ts rename to packages/computer/src/sh.test.ts diff --git a/packages/workspace/src/sh.ts b/packages/computer/src/sh.ts similarity index 100% rename from packages/workspace/src/sh.ts rename to packages/computer/src/sh.ts diff --git a/packages/workspace/src/shell.test.ts b/packages/computer/src/shell.test.ts similarity index 99% rename from packages/workspace/src/shell.test.ts rename to packages/computer/src/shell.test.ts index 23d9d3f5..b3d1d50f 100644 --- a/packages/workspace/src/shell.test.ts +++ b/packages/computer/src/shell.test.ts @@ -1,11 +1,11 @@ // Unit tests for WorkspaceShell. The harness shell.test.ts under -// src/test-harness covers the wire end-to-end against a real wsd +// src/test-harness covers the wire end-to-end against a real computerd // container; these tests run in-process with a fake WorkspaceRPC so // the host-side facade (RPC forwarding, handle shape, encoding, // result accumulation, push/pull bracket math) is exercised // without needing Docker. -import type { ExecEvent, ShellRPC, SyncRPC, WorkspaceRPC } from "@cloudflare/workspace-rpc"; +import type { ExecEvent, ShellRPC, SyncRPC, WorkspaceRPC } from "@cloudflare/computer-rpc"; import { describe, expect, it } from "vitest"; import type { KillSignal } from "./shell.js"; @@ -717,7 +717,7 @@ describe("WorkspaceShell.get — reattach", () => { it("skips the pre-exec push but still runs the post-drain pull", async () => { // Reattach doesn't own the original push frame: pushed = 0. - // The post-drain pull still fires — anything wsd produced + // The post-drain pull still fires — anything computerd produced // between reattach and drain lands locally. const f = fakeRpc({ events: [exit(1, 0)] }); let pushCalls = 0; diff --git a/packages/workspace/src/shell.ts b/packages/computer/src/shell.ts similarity index 98% rename from packages/workspace/src/shell.ts rename to packages/computer/src/shell.ts index f067c789..fa3d5ada 100644 --- a/packages/workspace/src/shell.ts +++ b/packages/computer/src/shell.ts @@ -26,11 +26,11 @@ // to an already-running exec doesn't represent a new push frame. // The result() of a reattached handle reports pushed = 0 and the // pulled count from a pull that runs after its own drain — best- -// effort, can be 0 if nothing landed in wsd between reattach and +// effort, can be 0 if nothing landed in computerd between reattach and // drain. +import type { ExecEvent, ShellRPC } from "@cloudflare/computer-rpc"; import type { ApplyResult, SkippedEntry } from "@cloudflare/dofs"; -import type { ExecEvent, ShellRPC } from "@cloudflare/workspace-rpc"; import { noopObserver, safeErrorMessage, type WorkspaceObserver, withSpan } from "./observe.js"; import { assertNotTemplate } from "./sh.js"; @@ -355,7 +355,7 @@ async function drainToResult( } finally { reader.releaseLock(); } - // Post-drain pull: apply anything wsd produced during the exec. + // Post-drain pull: apply anything computerd produced during the exec. // Failures non-fatal per docs/05 ("failed pushes/pulls do not // abort the command"); pulled / skipped stay at their empty // values in that case. diff --git a/packages/workspace/src/stub.test.ts b/packages/computer/src/stub.test.ts similarity index 95% rename from packages/workspace/src/stub.test.ts rename to packages/computer/src/stub.test.ts index 76187169..45d493f7 100644 --- a/packages/workspace/src/stub.test.ts +++ b/packages/computer/src/stub.test.ts @@ -11,8 +11,8 @@ // class's own contract: accessor shape, eager spawn, readFile // overload routing, stat ENOENT propagation, close() idempotency. +import { enableStubTracking, stubSnapshot } from "@cloudflare/computer-rpc/debug"; import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; -import { enableStubTracking, stubSnapshot } from "@cloudflare/workspace-rpc/debug"; import { beforeAll, describe, expect, it } from "vitest"; import type { BackendHandle, WorkspaceBackend } from "./backend.js"; @@ -28,9 +28,9 @@ import { import { Workspace } from "./workspace.js"; function composite( - sync: import("@cloudflare/workspace-rpc").SyncRPC, - shell?: Partial, -): import("@cloudflare/workspace-rpc").WorkspaceRPC { + sync: import("@cloudflare/computer-rpc").SyncRPC, + shell?: Partial, +): import("@cloudflare/computer-rpc").WorkspaceRPC { const notWired = () => Promise.reject(new Error("not wired in this test")); return { sync, @@ -44,7 +44,7 @@ function composite( }; } -function fakeSync(): import("@cloudflare/workspace-rpc").SyncRPC { +function fakeSync(): import("@cloudflare/computer-rpc").SyncRPC { return { async push() { return { rev: 0, appliedPushCursor: { rev: 0, path: null } }; @@ -82,14 +82,12 @@ function fakeSync(): import("@cloudflare/workspace-rpc").SyncRPC { }; } -function backend( - rpc?: Partial, -): WorkspaceBackend { +function backend(rpc?: Partial): WorkspaceBackend { return { id: "test", async connect(): Promise { const sync = rpc?.sync ?? fakeSync(); - const shell = rpc?.shell as Partial | undefined; + const shell = rpc?.shell as Partial | undefined; return { rpc: composite(sync, shell), close: async () => {} }; }, }; @@ -209,7 +207,7 @@ describe("WorkspaceStub", () => { let signalClosed!: () => void; let connectCount = 0; let execCalls = 0; - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { execCalls += 1; return { @@ -386,7 +384,7 @@ describe("WorkspaceStub", () => { // a clock, but we can pin that the returned handle is the right // shape and that result() resolves. let execCalls = 0; - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { execCalls += 1; return { @@ -425,7 +423,7 @@ describe("WorkspaceStub", () => { // for the wire. Decoding it back yields the original events, // including a binary stdout chunk carried base64. const payload = new Uint8Array([0x00, 0xff, 0x41]); - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { return { id: "e-1", @@ -466,7 +464,7 @@ describe("WorkspaceStub", () => { it("shell.exec handle.stream() waits for the wire consumer to pull", async () => { let pulls = 0; - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { return { id: "e-1", @@ -512,7 +510,7 @@ describe("WorkspaceStub", () => { }); it("shell.exec handle rejects result() after stream() has claimed it", async () => { - const shellRpc: import("@cloudflare/workspace-rpc").ShellRPC = { + const shellRpc: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { return { id: "e-1", diff --git a/packages/workspace/src/stub.ts b/packages/computer/src/stub.ts similarity index 98% rename from packages/workspace/src/stub.ts rename to packages/computer/src/stub.ts index 6154557c..7d5893c2 100644 --- a/packages/workspace/src/stub.ts +++ b/packages/computer/src/stub.ts @@ -6,8 +6,8 @@ // // Usage shape: // -// // Inside a DO that owns the live wsd connection: -// class WsdContainer extends DurableObject { +// // Inside a DO that owns the live computerd connection: +// class ComputerdContainer extends DurableObject { // #workspace = new Workspace({ backends: [...] }); // async getWorkspace(): Promise { // await this.#workspace.ready(); @@ -16,7 +16,7 @@ // } // // // From a Worker (or another DO): -// const ws = await env.WSD.get(id).getWorkspace(); +// const ws = await env.COMPUTERD.get(id).getWorkspace(); // await ws.fs.writeFile("/foo", bytes); // const handle = await ws.shell.exec("ls /workspace"); // const { exitCode, stdout, stderr } = await handle.result(); @@ -42,6 +42,7 @@ // under both workerd and node (tests, type-only consumers), while // `cloudflare:workers` only resolves under workerd. +import { trackStub, untrackStub } from "@cloudflare/computer-rpc/debug"; import type { GrepOptions, MkdirOptions, @@ -54,7 +55,6 @@ import type { WriteFileContent, WriteFileOptions, } from "@cloudflare/dofs"; -import { trackStub, untrackStub } from "@cloudflare/workspace-rpc/debug"; import { RpcTarget } from "capnweb"; import type { @@ -321,7 +321,7 @@ export class WorkspaceExecHandleStub e [Symbol.dispose](): void { // If neither result() nor stream() ran, release the exec span so - // it doesn't hang open, and cancel the handle's stream so wsd + // it doesn't hang open, and cancel the handle's stream so computerd // stops the command rather than buffering forever. if (!this.#consumed) { this.#consumed = true; @@ -664,8 +664,8 @@ export class WorkspaceShellStub extends RpcTarget { // Workspace owns the lifecycle and the stub just delegates. // // Note the name collision: the *type* `WorkspaceRPC` is also -// exported by @cloudflare/workspace-rpc as the wire contract -// between wsd and the DO. WorkspaceStub here is a different thing +// exported by @cloudflare/computer-rpc as the wire contract +// between computerd and the DO. WorkspaceStub here is a different thing // (the Workers-RPC value carried between the DO and a Worker), so // the name doesn't clash. export class WorkspaceStub extends RpcTarget { diff --git a/packages/workspace/src/test-harness-worker.ts b/packages/computer/src/test-harness-worker.ts similarity index 88% rename from packages/workspace/src/test-harness-worker.ts rename to packages/computer/src/test-harness-worker.ts index 8d4b6afe..05b1a629 100644 --- a/packages/workspace/src/test-harness-worker.ts +++ b/packages/computer/src/test-harness-worker.ts @@ -2,7 +2,7 @@ // // The DO exists so harness tests can grab a real // DurableObjectStorage instance via runInDurableObject() and pass -// it to the Workspace constructor. The harness boots wsd in a +// it to the Workspace constructor. The harness boots computerd in a // sibling docker container; tests connect to it through // TestBackend, but the Workspace itself runs inside the DO so it // has a host store to read / write against. @@ -10,7 +10,7 @@ import { DurableObject } from "cloudflare:workers"; export interface HarnessBindings { - WSD_HARNESS_URL: string; + COMPUTERD_HARNESS_URL: string; TEST_DO: DurableObjectNamespace; } diff --git a/packages/workspace/src/test-harness/end-to-end.test.ts b/packages/computer/src/test-harness/end-to-end.test.ts similarity index 85% rename from packages/workspace/src/test-harness/end-to-end.test.ts rename to packages/computer/src/test-harness/end-to-end.test.ts index 5a4c4518..834251e7 100644 --- a/packages/workspace/src/test-harness/end-to-end.test.ts +++ b/packages/computer/src/test-harness/end-to-end.test.ts @@ -1,8 +1,8 @@ -// End-to-end harness for @cloudflare/workspace. +// End-to-end harness for @cloudflare/computer. // // Runs inside workerd via @cloudflare/vitest-pool-workers. The -// surrounding vitest config's globalSetup boots a wsd container -// on the host and writes its URL into the WSD_HARNESS_URL +// surrounding vitest config's globalSetup boots a computerd container +// on the host and writes its URL into the COMPUTERD_HARNESS_URL // binding (or leaves it blank when docker isn't available). // // The test skips when the binding is empty so contributors @@ -14,13 +14,13 @@ import { describe, expect, it } from "vitest"; import { withWorkspace } from "./with-workspace.js"; interface HarnessEnv { - WSD_HARNESS_URL: string; + COMPUTERD_HARNESS_URL: string; } -const url = (env as HarnessEnv).WSD_HARNESS_URL; +const url = (env as HarnessEnv).COMPUTERD_HARNESS_URL; const describeIfDocker = url.length > 0 ? describe : describe.skip; -describeIfDocker("Workspace end-to-end against a real wsd container", () => { +describeIfDocker("Workspace end-to-end against a real computerd container", () => { it("connects via TestBackend and round-trips a small file", async () => { await withWorkspace(url, async (ws) => { await ws.ready(); diff --git a/packages/workspace/src/test-harness/load.bench.ts b/packages/computer/src/test-harness/load.bench.ts similarity index 93% rename from packages/workspace/src/test-harness/load.bench.ts rename to packages/computer/src/test-harness/load.bench.ts index 4291131c..848b0698 100644 --- a/packages/workspace/src/test-harness/load.bench.ts +++ b/packages/computer/src/test-harness/load.bench.ts @@ -1,8 +1,8 @@ -// End-to-end load characterisation against a real wsd +// End-to-end load characterisation against a real computerd // container. Runs via 'npm run bench:harness' which wraps // the same docker bootstrap used by the regression test // suite. The package code runs inside workerd; the wire -// traverses miniflare → host port → docker → wsd. +// traverses miniflare → host port → docker → computerd. // // Skips when the harness URL isn't set (docker not // available); contributors without docker still see the @@ -14,10 +14,10 @@ import { bench, describe } from "vitest"; import { withWorkspace } from "./with-workspace.js"; interface HarnessEnv { - WSD_HARNESS_URL: string; + COMPUTERD_HARNESS_URL: string; } -const url = (env as HarnessEnv).WSD_HARNESS_URL; +const url = (env as HarnessEnv).COMPUTERD_HARNESS_URL; const describeIfDocker = url.length > 0 ? describe : describe.skip; // Each bench iteration mints its own Workspace so leftover diff --git a/packages/workspace/src/test-harness/shell.test.ts b/packages/computer/src/test-harness/shell.test.ts similarity index 93% rename from packages/workspace/src/test-harness/shell.test.ts rename to packages/computer/src/test-harness/shell.test.ts index 14519892..f47be0e6 100644 --- a/packages/workspace/src/test-harness/shell.test.ts +++ b/packages/computer/src/test-harness/shell.test.ts @@ -1,5 +1,5 @@ // Shell end-to-end harness. Same shape as end-to-end.test.ts — -// boots wsd via run-harness.sh, dials with TestBackend, drives +// boots computerd via run-harness.sh, dials with TestBackend, drives // Workspace.shell against the real container. import { env } from "cloudflare:test"; @@ -8,13 +8,13 @@ import { describe, expect, it } from "vitest"; import { withWorkspace } from "./with-workspace.js"; interface HarnessEnv { - WSD_HARNESS_URL: string; + COMPUTERD_HARNESS_URL: string; } -const url = (env as HarnessEnv).WSD_HARNESS_URL; +const url = (env as HarnessEnv).COMPUTERD_HARNESS_URL; const describeIfDocker = url.length > 0 ? describe : describe.skip; -describeIfDocker("Workspace.shell against a real wsd container", () => { +describeIfDocker("Workspace.shell against a real computerd container", () => { it("exec captures stdout and exit code (utf8)", async () => { await withWorkspace(url, async (ws) => { await ws.ready(); @@ -95,10 +95,10 @@ describeIfDocker("Workspace.shell against a real wsd container", () => { }); }); - it("exec result.pulled counts wsd revs that landed during the run", async () => { + it("exec result.pulled counts computerd revs that landed during the run", async () => { await withWorkspace(url, async (ws) => { await ws.ready(); - // Touch wsd via the FUSE mount so currentRev advances + // Touch computerd via the FUSE mount so currentRev advances // during the exec. Each `touch` is one applyChanges round // on the server, which bumps the rev exactly once. const handle = await ws.shell.exec( diff --git a/packages/workspace/src/test-harness/with-workspace.ts b/packages/computer/src/test-harness/with-workspace.ts similarity index 100% rename from packages/workspace/src/test-harness/with-workspace.ts rename to packages/computer/src/test-harness/with-workspace.ts diff --git a/packages/workspace/src/tools/ai.test.ts b/packages/computer/src/tools/ai.test.ts similarity index 100% rename from packages/workspace/src/tools/ai.test.ts rename to packages/computer/src/tools/ai.test.ts diff --git a/packages/workspace/src/tools/ai.ts b/packages/computer/src/tools/ai.ts similarity index 100% rename from packages/workspace/src/tools/ai.ts rename to packages/computer/src/tools/ai.ts diff --git a/packages/workspace/src/tools/exec.ts b/packages/computer/src/tools/exec.ts similarity index 100% rename from packages/workspace/src/tools/exec.ts rename to packages/computer/src/tools/exec.ts diff --git a/packages/workspace/src/tools/fs/edit-diff.ts b/packages/computer/src/tools/fs/edit-diff.ts similarity index 100% rename from packages/workspace/src/tools/fs/edit-diff.ts rename to packages/computer/src/tools/fs/edit-diff.ts diff --git a/packages/workspace/src/tools/fs/edit.ts b/packages/computer/src/tools/fs/edit.ts similarity index 100% rename from packages/workspace/src/tools/fs/edit.ts rename to packages/computer/src/tools/fs/edit.ts diff --git a/packages/workspace/src/tools/fs/list.ts b/packages/computer/src/tools/fs/list.ts similarity index 100% rename from packages/workspace/src/tools/fs/list.ts rename to packages/computer/src/tools/fs/list.ts diff --git a/packages/workspace/src/tools/fs/read.ts b/packages/computer/src/tools/fs/read.ts similarity index 100% rename from packages/workspace/src/tools/fs/read.ts rename to packages/computer/src/tools/fs/read.ts diff --git a/packages/workspace/src/tools/fs/store.ts b/packages/computer/src/tools/fs/store.ts similarity index 98% rename from packages/workspace/src/tools/fs/store.ts rename to packages/computer/src/tools/fs/store.ts index 5b7d06e6..739d97cd 100644 --- a/packages/workspace/src/tools/fs/store.ts +++ b/packages/computer/src/tools/fs/store.ts @@ -14,7 +14,7 @@ import type { FileStat, FileStore } from "./types.js"; /** - * Structural subset of `@cloudflare/workspace.Workspace` the tools + * Structural subset of `@cloudflare/computer.Workspace` the tools * depend on. */ export interface WorkspaceLike { diff --git a/packages/workspace/src/tools/fs/types.ts b/packages/computer/src/tools/fs/types.ts similarity index 100% rename from packages/workspace/src/tools/fs/types.ts rename to packages/computer/src/tools/fs/types.ts diff --git a/packages/workspace/src/tools/fs/write.ts b/packages/computer/src/tools/fs/write.ts similarity index 100% rename from packages/workspace/src/tools/fs/write.ts rename to packages/computer/src/tools/fs/write.ts diff --git a/packages/workspace/src/tools/index.ts b/packages/computer/src/tools/index.ts similarity index 100% rename from packages/workspace/src/tools/index.ts rename to packages/computer/src/tools/index.ts diff --git a/packages/workspace/src/tools/publish.ts b/packages/computer/src/tools/publish.ts similarity index 100% rename from packages/workspace/src/tools/publish.ts rename to packages/computer/src/tools/publish.ts diff --git a/packages/workspace/src/transport-failure.test.ts b/packages/computer/src/transport-failure.test.ts similarity index 100% rename from packages/workspace/src/transport-failure.test.ts rename to packages/computer/src/transport-failure.test.ts diff --git a/packages/workspace/src/transport-failure.ts b/packages/computer/src/transport-failure.ts similarity index 100% rename from packages/workspace/src/transport-failure.ts rename to packages/computer/src/transport-failure.ts diff --git a/packages/workspace/src/with-workspace.ts b/packages/computer/src/with-workspace.ts similarity index 100% rename from packages/workspace/src/with-workspace.ts rename to packages/computer/src/with-workspace.ts diff --git a/packages/workspace/src/workspace.test.ts b/packages/computer/src/workspace.test.ts similarity index 96% rename from packages/workspace/src/workspace.test.ts rename to packages/computer/src/workspace.test.ts index 89ea22c3..352b2d30 100644 --- a/packages/workspace/src/workspace.test.ts +++ b/packages/computer/src/workspace.test.ts @@ -24,12 +24,12 @@ function expectThinkWorkspace( // In-process fakes. We never spawn anything from the package // code; the backend's only contract is "produce a SyncRPC -// stub that wsd would speak". A plain object is enough. +// stub that computerd would speak". A plain object is enough. function composite( - sync: import("@cloudflare/workspace-rpc").SyncRPC, -): import("@cloudflare/workspace-rpc").WorkspaceRPC { + sync: import("@cloudflare/computer-rpc").SyncRPC, +): import("@cloudflare/computer-rpc").WorkspaceRPC { const notWired = () => Promise.reject(new Error("shell not wired in this test")); - const shell: import("@cloudflare/workspace-rpc").ShellRPC = { + const shell: import("@cloudflare/computer-rpc").ShellRPC = { exec: notWired, getExec: notWired, killExec: notWired, @@ -38,7 +38,7 @@ function composite( return { sync, shell }; } -function fakeRpc(): import("@cloudflare/workspace-rpc").SyncRPC { +function fakeRpc(): import("@cloudflare/computer-rpc").SyncRPC { const blobs = new Map(); const files = new Map< string, @@ -132,7 +132,7 @@ function fakeRpc(): import("@cloudflare/workspace-rpc").SyncRPC { function makeBackend( id: string, - rpc?: import("@cloudflare/workspace-rpc").SyncRPC, + rpc?: import("@cloudflare/computer-rpc").SyncRPC, ): WorkspaceBackend { return { id, @@ -148,13 +148,13 @@ function makeBackend( // the WorkspaceShell exec bracket settles right away. Used by the // multi-backend selection tests. function execBackend(id: string, onExec: (command: string) => void): WorkspaceBackend { - const shell: import("@cloudflare/workspace-rpc").ShellRPC = { + const shell: import("@cloudflare/computer-rpc").ShellRPC = { async exec(input) { onExec(input.command); const execId = input.id ?? `${id}-${Math.random().toString(36).slice(2)}`; return { id: execId, - events: new ReadableStream({ + events: new ReadableStream({ start(c) { c.enqueue({ id: execId, seq: 1, name: "exit", value: 0 }); c.close(); @@ -380,7 +380,7 @@ describe("Workspace backend selection", () => { const ws = new Workspace({ storage: makeStorage() }); const res = await ws.git.cli({ argv: ["version"] }); expect(res.exitCode).toBe(0); - expect(res.stdout).toContain("@cloudflare/workspace"); + expect(res.stdout).toContain("@cloudflare/computer"); }); }); @@ -531,7 +531,7 @@ describe("Workspace backend selection", () => { it("reconciles watermarks on the lazy connect when the remote is behind", async () => { let watermarksCalls = 0; - const sync: import("@cloudflare/workspace-rpc").SyncRPC = { + const sync: import("@cloudflare/computer-rpc").SyncRPC = { ...fakeRpc(), async watermarks() { watermarksCalls++; @@ -565,7 +565,7 @@ describe("Workspace backend selection", () => { touched.push(name); throw new Error(`sync.${name} must not be reached when sync: 'none'`); }; - const sync: import("@cloudflare/workspace-rpc").SyncRPC = { + const sync: import("@cloudflare/computer-rpc").SyncRPC = { push: () => tripwire("push"), fetchChanges: () => tripwire("fetchChanges"), readEntry: () => tripwire("readEntry"), @@ -698,7 +698,7 @@ describe("Workspace mutation serialization", () => { releasePush2 = r; }), ]; - const rpc: import("@cloudflare/workspace-rpc").SyncRPC = { + const rpc: import("@cloudflare/computer-rpc").SyncRPC = { ...fakeRpc(), async push(input) { inFlight.push++; @@ -744,7 +744,7 @@ describe("Workspace mutation serialization", () => { // While a push() is held in flight, reads on the local store // must still resolve. The FIFO only gates mutating entry points. let releasePush: (() => void) | undefined; - const rpc: import("@cloudflare/workspace-rpc").SyncRPC = { + const rpc: import("@cloudflare/computer-rpc").SyncRPC = { ...fakeRpc(), async push(input) { await new Promise((r) => { @@ -789,7 +789,7 @@ describe("Workspace transport-failure invalidation", () => { onConnect: () => void, ): { backend: WorkspaceBackend; failNext: () => void } { let shouldFail = false; - const sync: import("@cloudflare/workspace-rpc").SyncRPC = { + const sync: import("@cloudflare/computer-rpc").SyncRPC = { ...fakeRpc(), async push(input) { if (shouldFail) throw new WorkspaceTransportError("WebSocket closed"); @@ -880,7 +880,7 @@ describe("Workspace transport-failure invalidation", () => { it("non-transport errors do not invalidate the cached handle", async () => { let connects = 0; - const sync: import("@cloudflare/workspace-rpc").SyncRPC = { + const sync: import("@cloudflare/computer-rpc").SyncRPC = { ...fakeRpc(), async push() { throw new Error("EROFS: read-only file system"); @@ -910,7 +910,7 @@ describe("Workspace transport-failure invalidation", () => { it("shell.exec invalidates the cached handle on a transport error", async () => { let connects = 0; - const shell: import("@cloudflare/workspace-rpc").ShellRPC = { + const shell: import("@cloudflare/computer-rpc").ShellRPC = { async exec() { throw new WorkspaceTransportError("RPC session was shut down"); }, @@ -946,12 +946,12 @@ describe("Workspace transport-failure invalidation", () => { // the wrap around the returned handle must invalidate the // cached backend handle so the next operation reconnects. let connects = 0; - const shell: import("@cloudflare/workspace-rpc").ShellRPC = { + const shell: import("@cloudflare/computer-rpc").ShellRPC = { async exec(input) { const execId = input.id ?? "mid-stream"; return { id: execId, - events: new ReadableStream({ + events: new ReadableStream({ start(c) { c.error(new WorkspaceTransportError("WebSocket closed mid-stream")); }, @@ -1000,14 +1000,14 @@ describe("Workspace transport-failure invalidation", () => { // Per-exec stream controllers so each handle's stream can be // settled independently. Indexed by exec call order. const execStreams: ReadableStreamDefaultController< - import("@cloudflare/workspace-rpc").ExecEvent + import("@cloudflare/computer-rpc").ExecEvent >[] = []; - const shell: import("@cloudflare/workspace-rpc").ShellRPC = { + const shell: import("@cloudflare/computer-rpc").ShellRPC = { async exec(input) { const execId = input.id ?? `exec-${execStreams.length}`; return { id: execId, - events: new ReadableStream({ + events: new ReadableStream({ start(c) { execStreams.push(c); }, diff --git a/packages/workspace/src/workspace.ts b/packages/computer/src/workspace.ts similarity index 99% rename from packages/workspace/src/workspace.ts rename to packages/computer/src/workspace.ts index cd2db51c..302e22fb 100644 --- a/packages/workspace/src/workspace.ts +++ b/packages/computer/src/workspace.ts @@ -2,13 +2,14 @@ // // Runs inside a Cloudflare Worker / Durable Object. Owns a local // dofs Database (the host store) and a SyncRPC connection -// to wsd. Filesystem operations on Workspace.fs mutate the local +// to computerd. Filesystem operations on Workspace.fs mutate the local // store directly via the WorkspaceFilesystem class from -// @cloudflare/dofs; sync between the host store and wsd +// @cloudflare/dofs; sync between the host store and computerd // is driven explicitly via Workspace.push() / Workspace.pull(). // The shell-side pre-exec push / post-exec pull bracket lives // on Workspace.shell.exec. +import { pullOnce, pushOnce, reconcileWatermarks } from "@cloudflare/computer-rpc/driver"; import { type ApplyResult, Database, @@ -17,7 +18,6 @@ import { SQLiteWorkspaceProvider, WorkspaceFilesystem, } from "@cloudflare/dofs"; -import { pullOnce, pushOnce, reconcileWatermarks } from "@cloudflare/workspace-rpc/driver"; import { type ArtifactClient, diff --git a/packages/workspace/test-harness/run-wsd.sh b/packages/computer/test-harness/run-computerd.sh similarity index 79% rename from packages/workspace/test-harness/run-wsd.sh rename to packages/computer/test-harness/run-computerd.sh index a116bb0a..3f6f6614 100755 --- a/packages/workspace/test-harness/run-wsd.sh +++ b/packages/computer/test-harness/run-computerd.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Boots a wsd container under FUSE and prints the host URL. +# Boots a computerd container under FUSE and prints the host URL. # # Used by the vitest harness to give TestBackend a -# real wsd to talk to. The harness sets WSD_HARNESS_URL from +# real computerd to talk to. The harness sets COMPUTERD_HARNESS_URL from # this script's stdout, then runs the workerd-backed test # against the URL. # @@ -18,8 +18,8 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" -BINARY="${WSD_BINARY:-$ROOT/artifacts/wsd/wsd-linux-x64}" -HOST_PORT="${WSD_HARNESS_PORT:-0}" +BINARY="${COMPUTERD_BINARY:-$ROOT/artifacts/computerd/computerd-linux-x64}" +HOST_PORT="${COMPUTERD_HARNESS_PORT:-0}" if ! command -v docker >/dev/null 2>&1; then echo "docker not found in PATH" >&2 @@ -27,15 +27,15 @@ if ! command -v docker >/dev/null 2>&1; then fi if [[ ! -x "$BINARY" ]]; then - echo "wsd binary not found at $BINARY" >&2 - echo "Run 'npm run build:bin --workspace @cloudflare/workspace-wsd' first" >&2 + echo "computerd binary not found at $BINARY" >&2 + echo "Run 'npm run build:bin --workspace @cloudflare/computerd' first" >&2 exit 1 fi # Pre-pull the libfuse2 packages once per container by using # our own intermediate image. Skipped if the image already # exists. -IMAGE_TAG="wsd-harness:libfuse2" +IMAGE_TAG="computerd-harness:libfuse2" if ! docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then echo "building $IMAGE_TAG..." >&2 docker build --platform linux/amd64 -t "$IMAGE_TAG" - <<'DOCKERFILE' >&2 @@ -58,15 +58,15 @@ CID=$(docker run --rm -d \ --cap-add SYS_ADMIN --cap-add MKNOD \ --security-opt apparmor=unconfined \ --security-opt seccomp=unconfined \ - -v "$BINARY:/usr/local/bin/wsd:ro" \ + -v "$BINARY:/usr/local/bin/computerd:ro" \ -p "$HOST_PORT:8080" \ -e PORT=8080 \ -e MOUNT_POINT=/workspace \ "$IMAGE_TAG" \ - /usr/local/bin/wsd) + /usr/local/bin/computerd) # Report the container id on stderr so the caller can kill it. -echo "WSD_HARNESS_CID=$CID" >&2 +echo "COMPUTERD_HARNESS_CID=$CID" >&2 # Resolve the host port. With -p 0:8080 docker maps a random # ephemeral port; we read it from `docker port`. @@ -80,7 +80,7 @@ fi URL="http://127.0.0.1:$HOST_BOUND_PORT" -# Wait for /health. wsd takes a couple of seconds in the +# Wait for /health. computerd takes a couple of seconds in the # container because it has to load the experimental SQLite # binding and bind FUSE. for _ in $(seq 1 60); do @@ -91,7 +91,7 @@ for _ in $(seq 1 60); do sleep 0.5 done -echo "wsd did not become healthy at $URL" >&2 +echo "computerd did not become healthy at $URL" >&2 docker logs "$CID" >&2 || true docker kill "$CID" >/dev/null 2>&1 || true exit 1 diff --git a/packages/workspace/test-harness/run-harness.sh b/packages/computer/test-harness/run-harness.sh similarity index 63% rename from packages/workspace/test-harness/run-harness.sh rename to packages/computer/test-harness/run-harness.sh index 26f9c087..1843be2c 100755 --- a/packages/workspace/test-harness/run-harness.sh +++ b/packages/computer/test-harness/run-harness.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Wrapper that runs the docker harness vitest project end-to-end. # -# - Boots the wsd container via run-wsd.sh, exporting the URL into -# WSD_HARNESS_URL so the vitest config can wire it into the +# - Boots the computerd container via run-computerd.sh, exporting the URL into +# COMPUTERD_HARNESS_URL so the vitest config can wire it into the # worker's bindings. # - Runs vitest against test-harness/vitest config. # - Kills the container on any exit path, including SIGINT. @@ -12,7 +12,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)" WORKSPACE_ROOT="$(cd "$HERE/.." && pwd)" cleanup() { - local cid="${WSD_HARNESS_CID:-}" + local cid="${COMPUTERD_HARNESS_CID:-}" if [[ -n "$cid" ]]; then docker kill "$cid" >/dev/null 2>&1 || true fi @@ -28,15 +28,15 @@ if ! docker info >/dev/null 2>&1; then exec npx vitest "${1:-run}" --run --config vitest.config.harness.ts fi -# Capture the container id from run-wsd.sh's stderr while -# letting the URL on stdout reach $WSD_HARNESS_URL. +# Capture the container id from run-computerd.sh's stderr while +# letting the URL on stdout reach $COMPUTERD_HARNESS_URL. STDERR_FILE="$(mktemp)" trap 'rm -f "$STDERR_FILE"; cleanup' EXIT -WSD_HARNESS_URL="$("$HERE/run-wsd.sh" 2>"$STDERR_FILE")" -WSD_HARNESS_CID="$(grep -oE 'WSD_HARNESS_CID=[0-9a-f]+' "$STDERR_FILE" | head -1 | cut -d= -f2)" -export WSD_HARNESS_URL WSD_HARNESS_CID +COMPUTERD_HARNESS_URL="$("$HERE/run-computerd.sh" 2>"$STDERR_FILE")" +COMPUTERD_HARNESS_CID="$(grep -oE 'COMPUTERD_HARNESS_CID=[0-9a-f]+' "$STDERR_FILE" | head -1 | cut -d= -f2)" +export COMPUTERD_HARNESS_URL COMPUTERD_HARNESS_CID -echo "harness: wsd at $WSD_HARNESS_URL (container $WSD_HARNESS_CID)" >&2 +echo "harness: computerd at $COMPUTERD_HARNESS_URL (container $COMPUTERD_HARNESS_CID)" >&2 # First positional arg picks the vitest mode (run|bench). # Default is `run` so the existing scripts keep working. diff --git a/packages/workspace/test-harness/wrangler.jsonc b/packages/computer/test-harness/wrangler.jsonc similarity index 87% rename from packages/workspace/test-harness/wrangler.jsonc rename to packages/computer/test-harness/wrangler.jsonc index a1c8faf3..80814c6a 100644 --- a/packages/workspace/test-harness/wrangler.jsonc +++ b/packages/computer/test-harness/wrangler.jsonc @@ -1,12 +1,12 @@ { // Test-only worker for the docker harness. Not deployed. - // The Workspace facade runs inside this worker; the wsd + // The Workspace facade runs inside this worker; the computerd // container is a sibling process the harness boots in // globalSetup, addressable from the worker via the URL the // setup writes into a per-test binding. The DO gives each // test a fresh ctx.storage to hand to Workspace as its local // store. - "name": "workspace-harness", + "name": "computer-harness", "main": "../src/test-harness-worker.ts", "compatibility_date": "2025-05-01", "compatibility_flags": ["nodejs_compat"], diff --git a/packages/workspace/test-helpers/cloudflare-workers-stub.ts b/packages/computer/test-helpers/cloudflare-workers-stub.ts similarity index 100% rename from packages/workspace/test-helpers/cloudflare-workers-stub.ts rename to packages/computer/test-helpers/cloudflare-workers-stub.ts diff --git a/packages/workspace/tests/proxy-worker.ts b/packages/computer/tests/proxy-worker.ts similarity index 93% rename from packages/workspace/tests/proxy-worker.ts rename to packages/computer/tests/proxy-worker.ts index ace29f1b..4b43f34b 100644 --- a/packages/workspace/tests/proxy-worker.ts +++ b/packages/computer/tests/proxy-worker.ts @@ -19,7 +19,7 @@ import { DurableObject, WorkerEntrypoint } from "cloudflare:workers"; export { WorkspaceProxy } from "../src/proxy.js"; export interface Env { - WSD: DurableObjectNamespace; + COMPUTERD: DurableObjectNamespace; } export class TestStorageDO extends DurableObject { @@ -34,7 +34,7 @@ export class TestStorageDO extends DurableObject { export default class TestDriver extends WorkerEntrypoint { override async fetch(request: Request): Promise { - const binding = request.headers.get("x-test-binding") ?? "WSD"; + const binding = request.headers.get("x-test-binding") ?? "COMPUTERD"; const id = request.headers.get("x-test-id") ?? ""; // biome-ignore lint/suspicious/noExplicitAny: ctx.exports isn't in @cloudflare/workers-types yet const proxy = (this.ctx as any).exports.WorkspaceProxy({ props: { binding, id } }); diff --git a/packages/workspace/tests/proxy.test.ts b/packages/computer/tests/proxy.test.ts similarity index 90% rename from packages/workspace/tests/proxy.test.ts rename to packages/computer/tests/proxy.test.ts index 49d67914..59276552 100644 --- a/packages/workspace/tests/proxy.test.ts +++ b/packages/computer/tests/proxy.test.ts @@ -1,5 +1,5 @@ // WorkspaceProxy is the WorkerEntrypoint a container DO hands to -// ctx.container.interceptOutboundHttp(...) so wsd can dial back +// ctx.container.interceptOutboundHttp(...) so computerd can dial back // into the DO. Two-and-a-half pieces of logic to pin: // // - /health answers 200 ok\n (the port-readiness probe). @@ -21,7 +21,7 @@ declare module "cloudflare:test" { function freshId(): string { // Mint a fresh DO id each test so cases stay independent. - return env.WSD.newUniqueId().toString(); + return env.COMPUTERD.newUniqueId().toString(); } describe("WorkspaceProxy", () => { @@ -36,7 +36,7 @@ describe("WorkspaceProxy", () => { it("/ws forwards to the DO at env[binding]", async () => { const res = await SELF.fetch("http://proxy.test/ws", { - headers: { "x-test-id": freshId(), "x-test-binding": "WSD" }, + headers: { "x-test-id": freshId(), "x-test-binding": "COMPUTERD" }, }); expect(res.status).toBe(200); expect(await res.text()).toBe("from-do"); diff --git a/packages/workspace/tests/stub-soak-worker.ts b/packages/computer/tests/stub-soak-worker.ts similarity index 89% rename from packages/workspace/tests/stub-soak-worker.ts rename to packages/computer/tests/stub-soak-worker.ts index 8c7f7a87..9e1bca15 100644 --- a/packages/workspace/tests/stub-soak-worker.ts +++ b/packages/computer/tests/stub-soak-worker.ts @@ -2,23 +2,23 @@ // // Two exports: // -// - WsdContainer (DO): owns a Workspace backed by an in-process -// fake backend (no wsd, no network). Has two RPC methods: +// - ComputerdContainer (DO): owns a Workspace backed by an in-process +// fake backend (no computerd, no network). Has two RPC methods: // getWorkspace() — returns ws.stub() across the Workers // RPC boundary. The caller's stub goes // through capnweb-style disposal. // stubSnapshot() — reads the per-class live counter from -// @cloudflare/workspace-rpc/debug. +// @cloudflare/computer-rpc/debug. // // - TestDriver (default): a WorkerEntrypoint with a single // fetch() that runs the soak workload. Lets the test drive // the cross-boundary calls with one round trip per test case. import { DurableObject, WorkerEntrypoint } from "cloudflare:workers"; +import type { ShellRPC, SyncRPC, WorkspaceRPC } from "@cloudflare/computer-rpc"; +import { enableStubTracking, stubSnapshot } from "@cloudflare/computer-rpc/debug"; import type { ChangeEntry } from "@cloudflare/dofs"; import { Database, initializeSchema } from "@cloudflare/dofs"; -import type { ShellRPC, SyncRPC, WorkspaceRPC } from "@cloudflare/workspace-rpc"; -import { enableStubTracking, stubSnapshot } from "@cloudflare/workspace-rpc/debug"; import type { BackendHandle, WorkspaceBackend } from "../src/backend.js"; import type { WorkspaceStub } from "../src/stub.js"; @@ -29,10 +29,10 @@ import { Workspace } from "../src/workspace.js"; enableStubTracking(); export interface Env { - WSD: DurableObjectNamespace; + COMPUTERD: DurableObjectNamespace; } -// Fake backend so the Workspace doesn't try to dial wsd. Sync +// Fake backend so the Workspace doesn't try to dial computerd. Sync // methods return empty/no-op; exec returns a single exit event so // shell.exec resolves without a real subprocess. function fakeBackend(): WorkspaceBackend { @@ -105,7 +105,7 @@ function fakeBackend(): WorkspaceBackend { }; } -export class WsdContainer extends DurableObject { +export class ComputerdContainer extends DurableObject { #ws: Workspace | undefined; async #ensure(): Promise { @@ -146,8 +146,8 @@ export default class TestDriver extends WorkerEntrypoint { afterClose: Record; iterations: number; }> { - const id = this.env.WSD.idFromName("soak"); - const stub = this.env.WSD.get(id); + const id = this.env.COMPUTERD.idFromName("soak"); + const stub = this.env.COMPUTERD.get(id); const baseline = await stub.stubSnapshot(); diff --git a/packages/workspace/tests/stub-soak.test.ts b/packages/computer/tests/stub-soak.test.ts similarity index 91% rename from packages/workspace/tests/stub-soak.test.ts rename to packages/computer/tests/stub-soak.test.ts index 11f8c672..08dbe51f 100644 --- a/packages/workspace/tests/stub-soak.test.ts +++ b/packages/computer/tests/stub-soak.test.ts @@ -3,14 +3,14 @@ // The unit tests in src/stub.test.ts exercise the WorkspaceStub // classes in-process; they don't see the Workers-RPC disposal // contract because no boundary is crossed. This soak does: it -// reaches into the DO via the WSD binding, pulls a WorkspaceStub +// reaches into the DO via the COMPUTERD binding, pulls a WorkspaceStub // across the boundary, exercises fs + shell, and samples the // per-class live counter on the DO side. import { env, runInDurableObject } from "cloudflare:test"; import { describe, expect, it } from "vitest"; -import type { Env, WsdContainer } from "./stub-soak-worker.js"; +import type { ComputerdContainer, Env } from "./stub-soak-worker.js"; declare module "cloudflare:test" { interface ProvidedEnv extends Env {} @@ -27,8 +27,8 @@ async function runSoak( iterations: number, opts: { disposeStubs: boolean; disposeExecHandles: boolean }, ): Promise { - const id = env.WSD.idFromName(`soak-${Math.random()}`); - const stub = env.WSD.get(id); + const id = env.COMPUTERD.idFromName(`soak-${Math.random()}`); + const stub = env.COMPUTERD.get(id); const baseline = await stub.stubSnapshot(); @@ -103,8 +103,8 @@ describe("WorkspaceStub disposal soak (DO ↔ Worker)", () => { it("snapshot reads do not themselves grow counters", async () => { // Sanity check: stubSnapshot() must not leak. Without this // pin a regression in the snapshot path could mask real leaks. - const id = env.WSD.idFromName("snapshot-only"); - const stub = env.WSD.get(id); + const id = env.COMPUTERD.idFromName("snapshot-only"); + const stub = env.COMPUTERD.get(id); const a = await stub.stubSnapshot(); for (let i = 0; i < 5; i++) await stub.stubSnapshot(); const b = await stub.stubSnapshot(); @@ -116,9 +116,9 @@ describe("WorkspaceStub disposal soak (DO ↔ Worker)", () => { // (string → number) as the RPC one. The actual numbers depend // on whatever ran before in this isolate — counters are // module-level state — so we only assert on the type. - const id = env.WSD.idFromName("direct-access"); - const stub = env.WSD.get(id); - const snap = await runInDurableObject(stub, async (instance: WsdContainer) => { + const id = env.COMPUTERD.idFromName("direct-access"); + const stub = env.COMPUTERD.get(id); + const snap = await runInDurableObject(stub, async (instance: ComputerdContainer) => { return instance.stubSnapshot(); }); for (const v of Object.values(snap)) expect(typeof v).toBe("number"); diff --git a/packages/workspace/tests/utilities/fake-artifacts-binding.ts b/packages/computer/tests/utilities/fake-artifacts-binding.ts similarity index 100% rename from packages/workspace/tests/utilities/fake-artifacts-binding.ts rename to packages/computer/tests/utilities/fake-artifacts-binding.ts diff --git a/packages/workspace/tests/worker-backend-worker.ts b/packages/computer/tests/worker-backend-worker.ts similarity index 98% rename from packages/workspace/tests/worker-backend-worker.ts rename to packages/computer/tests/worker-backend-worker.ts index 4d964df2..b4d9f9ab 100644 --- a/packages/workspace/tests/worker-backend-worker.ts +++ b/packages/computer/tests/worker-backend-worker.ts @@ -44,7 +44,7 @@ export class HostDO extends withWorkspace(class extends DurableObject {}, ( #seeded: Promise | undefined; // The VFS is empty on a fresh DO — not even /workspace exists. - // The wsd-container example seeds the mount root through wsd's + // The computerd-container example seeds the mount root through computerd's // boot path; the worker example happens to seed it through an // R2 mount at /workspace/r2. This harness has neither, so seed // /workspace directly. diff --git a/packages/workspace/tests/worker-backend.test.ts b/packages/computer/tests/worker-backend.test.ts similarity index 100% rename from packages/workspace/tests/worker-backend.test.ts rename to packages/computer/tests/worker-backend.test.ts diff --git a/packages/workspace/tests/wrangler.proxy.jsonc b/packages/computer/tests/wrangler.proxy.jsonc similarity index 89% rename from packages/workspace/tests/wrangler.proxy.jsonc rename to packages/computer/tests/wrangler.proxy.jsonc index 3a8e77be..d4949869 100644 --- a/packages/workspace/tests/wrangler.proxy.jsonc +++ b/packages/computer/tests/wrangler.proxy.jsonc @@ -3,14 +3,14 @@ // Mounts the real WorkspaceProxy entrypoint via ctx.exports and a // TestStorageDO that answers /ws with a marker so we can verify // routing landed. - "name": "workspace-proxy-tests", + "name": "computer-proxy-tests", "main": "./proxy-worker.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat"], "durable_objects": { "bindings": [ { - "name": "WSD", + "name": "COMPUTERD", "class_name": "TestStorageDO" } ] diff --git a/packages/workspace/tests/wrangler.stub-soak.jsonc b/packages/computer/tests/wrangler.stub-soak.jsonc similarity index 68% rename from packages/workspace/tests/wrangler.stub-soak.jsonc rename to packages/computer/tests/wrangler.stub-soak.jsonc index bcd3261b..5736fcdb 100644 --- a/packages/workspace/tests/wrangler.stub-soak.jsonc +++ b/packages/computer/tests/wrangler.stub-soak.jsonc @@ -1,10 +1,10 @@ { // Test-only worker for the WorkspaceStub disposal soak. Not deployed. // - // Exposes a single DO (WsdContainer) that holds a Workspace backed + // Exposes a single DO (ComputerdContainer) that holds a Workspace backed // by an in-process fake backend, plus a TestDriver WorkerEntrypoint // that the vitest harness reaches via SELF / cloudflareTest exports. - "name": "workspace-stub-soak", + "name": "computer-stub-soak", "main": "./stub-soak-worker.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat"], @@ -14,15 +14,15 @@ "durable_objects": { "bindings": [ { - "name": "WSD", - "class_name": "WsdContainer" + "name": "COMPUTERD", + "class_name": "ComputerdContainer" } ] }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["WsdContainer"] + "new_sqlite_classes": ["ComputerdContainer"] } ] } diff --git a/packages/workspace/tests/wrangler.worker-backend.jsonc b/packages/computer/tests/wrangler.worker-backend.jsonc similarity index 96% rename from packages/workspace/tests/wrangler.worker-backend.jsonc rename to packages/computer/tests/wrangler.worker-backend.jsonc index b963cc82..9db69a1e 100644 --- a/packages/workspace/tests/wrangler.worker-backend.jsonc +++ b/packages/computer/tests/wrangler.worker-backend.jsonc @@ -13,7 +13,7 @@ // driver pokes through SELF.fetch — it owns no logic, it just // routes by URL into the DO so each case can spin up a fresh // workspace id without rebuilding the worker. - "name": "workspace-worker-backend-tests", + "name": "computer-worker-backend-tests", "main": "./worker-backend-worker.ts", "compatibility_date": "2026-05-26", "compatibility_flags": ["nodejs_compat"], diff --git a/packages/workspace/tsconfig.build.json b/packages/computer/tsconfig.build.json similarity index 100% rename from packages/workspace/tsconfig.build.json rename to packages/computer/tsconfig.build.json diff --git a/packages/workspace/tsconfig.json b/packages/computer/tsconfig.json similarity index 100% rename from packages/workspace/tsconfig.json rename to packages/computer/tsconfig.json diff --git a/packages/workspace/vitest.config.harness.ts b/packages/computer/vitest.config.harness.ts similarity index 83% rename from packages/workspace/vitest.config.harness.ts rename to packages/computer/vitest.config.harness.ts index 829c1200..2c5b396d 100644 --- a/packages/workspace/vitest.config.harness.ts +++ b/packages/computer/vitest.config.harness.ts @@ -1,11 +1,11 @@ -// Docker harness for the workspace package. +// Docker harness for the computer package. // // The package runs inside a Cloudflare Worker / Durable // Object; this config drives it through workerd via -// @cloudflare/vitest-pool-workers. The wsd container itself +// @cloudflare/vitest-pool-workers. The computerd container itself // is booted by test-harness/run-harness.sh (a shell wrapper // that owns the docker lifecycle), which exports -// WSD_HARNESS_URL into the environment before invoking vitest. +// COMPUTERD_HARNESS_URL into the environment before invoking vitest. // // Why a wrapper instead of globalSetup: vitest reads the // `miniflare.bindings` map at config-load time, before any @@ -25,7 +25,7 @@ export default defineConfig({ // through to the worker's `env` binding. miniflare: { bindings: { - WSD_HARNESS_URL: process.env.WSD_HARNESS_URL ?? "", + COMPUTERD_HARNESS_URL: process.env.COMPUTERD_HARNESS_URL ?? "", }, }, }), diff --git a/packages/workspace/vitest.config.proxy.ts b/packages/computer/vitest.config.proxy.ts similarity index 100% rename from packages/workspace/vitest.config.proxy.ts rename to packages/computer/vitest.config.proxy.ts diff --git a/packages/workspace/vitest.config.stub-soak.ts b/packages/computer/vitest.config.stub-soak.ts similarity index 100% rename from packages/workspace/vitest.config.stub-soak.ts rename to packages/computer/vitest.config.stub-soak.ts diff --git a/packages/workspace/vitest.config.ts b/packages/computer/vitest.config.ts similarity index 100% rename from packages/workspace/vitest.config.ts rename to packages/computer/vitest.config.ts diff --git a/packages/workspace/vitest.config.worker-backend.ts b/packages/computer/vitest.config.worker-backend.ts similarity index 100% rename from packages/workspace/vitest.config.worker-backend.ts rename to packages/computer/vitest.config.worker-backend.ts diff --git a/packages/wsd/README.md b/packages/computerd/README.md similarity index 68% rename from packages/wsd/README.md rename to packages/computerd/README.md index 8301e4e8..f80cd05f 100644 --- a/packages/wsd/README.md +++ b/packages/computerd/README.md @@ -1,4 +1,4 @@ -# `@cloudflare/workspace-wsd` +# `@cloudflare/computerd` > [!IMPORTANT] > **PREVIEW ONLY** This package is provided as a preview for feedback only. @@ -10,25 +10,25 @@ > The specification under [`docs/`](docs/) is forward-looking — read it for > intent, not as description of the code today. -Workspace daemon CLI and FUSE mount package. +Computer daemon CLI and FUSE mount package. -## `wsd` +## `computerd` -`wsd` starts a FUSE-backed virtual filesystem and an HTTP server. The filesystem is backed by `@platformatic/vfs`, while the FUSE mount is provided by `fuse-native`. +`computerd` starts a FUSE-backed virtual filesystem and an HTTP server. The filesystem is backed by `@platformatic/vfs`, while the FUSE mount is provided by `fuse-native`. The HTTP server listens on the port provided by the `PORT` environment variable, defaulting to `45678`. The FUSE mount point is provided by `MOUNT_POINT`, defaulting to `/workspace`. The backing VFS stores files under the same absolute prefix: VFS `/workspace/repo/a.txt` is visible to container processes as `/workspace/repo/a.txt`, so capnweb reads, shim materialisation, and shell `exec` agree on absolute paths. ```sh -PORT=45678 MOUNT_POINT=/tmp/workspace npx -p @cloudflare/workspace-wsd wsd +PORT=45678 MOUNT_POINT=/tmp/workspace npx -p @cloudflare/computerd computerd ``` Current endpoints: - `GET /health` returns `200 OK` with `ok\n` once the HTTP server is up (it does not currently block on FUSE readiness). -- `GET /__wsd/info` returns JSON with the selected FUSE backend, mount point, and bound port. -- `GET /__wsd/stats` returns JSON with DOFS table row counts, total inline and blob byte sizes, the orphan-blob subset, and process resident memory. Useful for watching how the store grows under load. +- `GET /__computerd/info` returns JSON with the selected FUSE backend, mount point, and bound port. +- `GET /__computerd/stats` returns JSON with DOFS table row counts, total inline and blob byte sizes, the orphan-blob subset, and process resident memory. Useful for watching how the store grows under load. - `GET /` returns `200 OK` with an empty JSON object: `{}`. -- `POST /api` is a capnweb HTTP-batch RPC endpoint backed by `@cloudflare/workspace-rpc`. Non-POST methods return `405`. +- `POST /api` is a capnweb HTTP-batch RPC endpoint backed by `@cloudflare/computer-rpc`. Non-POST methods return `405`. - `GET /ws` upgrades to a WebSocket carrying the same capnweb RPC surface. This is the container's primary sync carrier. All other paths and methods return `404`/`405` with a `text/plain` body. @@ -39,14 +39,14 @@ Current filesystem support: - FUSE operation adapter covering the full `fuse-native` operation surface. - Unsupported FUSE operations return `ENOSYS` to the kernel; the binding logs a one-shot warning per operation. - capnweb RPC over `/api` and `/ws` exposes the workspace database and an `exec` runner to clients. -- Optional host/DO synchronization: when `UPSTREAM_URL` is set, `wsd` opens a `SyncClient` from `@cloudflare/workspace-rpc/client` against that URL and runs the sync loop in the background. +- Optional host/DO synchronization: when `UPSTREAM_URL` is set, `computerd` opens a `SyncClient` from `@cloudflare/computer-rpc/client` against that URL and runs the sync loop in the background. - No on-disk persistence yet — the in-memory VFS is rebuilt on each start, with sync pulling state back from the upstream when configured. ## FUSE write model The FUSE driver in `src/fuse/driver.ts` is a thin adapter over the DOFS provider. The byte owner is DOFS, not the FUSE driver: there -is no per-file staging buffer inside `wsd` for normal writes. +is no per-file staging buffer inside `computerd` for normal writes. When the backing provider advertises the buffered-write surface (`openWriteBufferForCreateSync`, `openWriteBufferSync`, @@ -74,10 +74,10 @@ old staged path: per-file in-memory `FileEntry` buffer that spills on `release` / `flush` / `fsync`. The fallback is exercised by tests that explicitly disable the direct-write methods on the VFS. -### `/__wsd/stats` for diagnosis +### `/__computerd/stats` for diagnosis When a workload is misbehaving — orphan blobs piling up, RSS growing -faster than expected, dirty buffers stuck — `GET /__wsd/stats` is the +faster than expected, dirty buffers stuck — `GET /__computerd/stats` is the first port of call. It returns table counts, total and orphan blob byte sizes, inline byte totals, and the process's RSS/heap/external figures. Poll it during a long-running install or test to watch @@ -111,15 +111,15 @@ UPSTREAM_URL=https://example/ws # open a SyncClient against this capnweb endpoi EXEC_LOG_MAX_BYTES=1048576 # cap the in-memory exec log buffer (bytes) ``` -`FUSE_MOUNT=auto` is the friendly default: if `/dev/fuse` (or macFUSE) is available `wsd` mounts a real FUSE filesystem, otherwise it transparently falls back to the userspace shim. Pin the value (`fuse` / `macfuse` / `shim` / `none`) when a test needs to assert a specific code path. +`FUSE_MOUNT=auto` is the friendly default: if `/dev/fuse` (or macFUSE) is available `computerd` mounts a real FUSE filesystem, otherwise it transparently falls back to the userspace shim. Pin the value (`fuse` / `macfuse` / `shim` / `none`) when a test needs to assert a specific code path. ## `FUSE_MOUNT=shim` — userspace dev shim -When `FUSE_MOUNT=shim` is set (or auto-detection picked it because no kernel FUSE was available), `wsd` materialises the VFS subtree rooted at `MOUNT_POINT` onto the host filesystem at the same path and keeps the two in sync without touching the kernel. The shim is intended for local development on machines that can't run FUSE (most CI, macOS without macFUSE, Linux containers without `/dev/fuse`). +When `FUSE_MOUNT=shim` is set (or auto-detection picked it because no kernel FUSE was available), `computerd` materialises the VFS subtree rooted at `MOUNT_POINT` onto the host filesystem at the same path and keeps the two in sync without touching the kernel. The shim is intended for local development on machines that can't run FUSE (most CI, macOS without macFUSE, Linux containers without `/dev/fuse`). How it works: -- On boot, `wsd` walks the VFS subtree under `MOUNT_POINT` and writes every file out to the host at the same path. +- On boot, `computerd` walks the VFS subtree under `MOUNT_POINT` and writes every file out to the host at the same path. - `vfs.watchAsync(MOUNT_POINT, { recursive: true })` drives VFS → disk: each VFS revision turns into a host-fs `writeFile`/`mkdir`/`rm`. - A periodic poll (~250 ms) walks `MOUNT_POINT`, diffs it against a content-hash shadow, and pushes any new or changed entries into the VFS. - The shadow doubles as a loop suppressor: after a write in either direction the shadow matches both sides, so the next tick on the opposite side sees no diff. @@ -131,28 +131,28 @@ Caveats. The shim is dev-only: - Conflicting writes across the seam are resolved on the next reconcile tick; the shim does not guarantee process-level coherence. - Symlinks, xattrs, chmod/chown, and watch fan-out are not modelled. Real FUSE keeps them; the shim treats files and directories only. - Large files cost a full read on every change. Don't park multi-GB blobs in the shim path. -- Migration: `DISABLE_FUSE`, `FUSE_SHIM`, and `WSD_FUSE_BACKEND` have been removed in favour of `FUSE_MOUNT`. `wsd` exits non-zero at startup if any of the old vars are set. +- Migration: `DISABLE_FUSE`, `FUSE_SHIM`, and `WSD_FUSE_BACKEND` have been removed in favour of `FUSE_MOUNT`. `computerd` exits non-zero at startup if any of the old vars are set. ## Tests Tests live next to the source files and are written in TypeScript. Vitest runs them directly: ```sh -npm test --workspace=@cloudflare/workspace-wsd +npm test --workspace=@cloudflare/computerd ``` -The test command does not build first. Some suites need build output that is not there in a clean checkout: the tests import the sibling `@cloudflare/dofs` and `@cloudflare/workspace-rpc` packages from their `dist/` directories, and `src/cli/wsd.test.ts` spawns the bundled CLI at `dist/cli/wsd.cjs`. Run `npm run build` across the workspace before `npm test`, or those tests fail to resolve the imports or exit early with no bundle to spawn. +The test command does not build first. Some suites need build output that is not there in a clean checkout: the tests import the sibling `@cloudflare/dofs` and `@cloudflare/computer-rpc` packages from their `dist/` directories, and `src/cli/computerd.test.ts` spawns the bundled CLI at `dist/cli/computerd.cjs`. Run `npm run build` across the workspace before `npm test`, or those tests fail to resolve the imports or exit early with no bundle to spawn. This package requires Node.js 22+ because `@platformatic/vfs` does. -The two real-FUSE suites gate themselves differently. `src/cli/wsd.test.ts` runs its real-FUSE case only when `/dev/fuse` is reachable; otherwise auto-detection resolves to the shim and the case skips. The guard is a bare existence check, so a `mknod`'d `/dev/fuse` in an unprivileged container defeats the skip and the mount then fails with `EPERM` — leave the device absent unless the container is privileged (`--privileged`, or `CAP_SYS_ADMIN` with device access). `src/exec/runner.fuse.test.ts` is separate: it skips unless both Docker and the prebuilt `wsd` binary are available, and runs `wsd` inside a privileged container, so the host's `/dev/fuse` does not matter. See the [`debugging-wsd-fuse`](../../.agents/skills/debugging-wsd-fuse/SKILL.md) skill for the privileged Docker setup. +The two real-FUSE suites gate themselves differently. `src/cli/computerd.test.ts` runs its real-FUSE case only when `/dev/fuse` is reachable; otherwise auto-detection resolves to the shim and the case skips. The guard is a bare existence check, so a `mknod`'d `/dev/fuse` in an unprivileged container defeats the skip and the mount then fails with `EPERM` — leave the device absent unless the container is privileged (`--privileged`, or `CAP_SYS_ADMIN` with device access). `src/exec/runner.fuse.test.ts` is separate: it skips unless both Docker and the prebuilt `computerd` binary are available, and runs `computerd` inside a privileged container, so the host's `/dev/fuse` does not matter. See the [`debugging-computerd-fuse`](../../.agents/skills/debugging-computerd-fuse/SKILL.md) skill for the privileged Docker setup. ## Standalone release artifacts Standalone binaries are release artifacts, not files published in the npm package: ```sh -npm run build:bin --workspace=@cloudflare/workspace-wsd +npm run build:bin --workspace=@cloudflare/computerd ``` The binary is produced with Node's Single Executable Application (SEA) feature: `scripts/build-bin.mjs` bundles the CLI with `esbuild`, generates a SEA blob via `node --experimental-sea-config`, downloads the target's Node binary, and injects the blob with `postject`. macOS targets are stripped and re-signed ad-hoc. `fuse-native` prebuilds and `libfuse` are embedded as SEA assets per target. diff --git a/packages/wsd/bench-results.md b/packages/computerd/bench-results.md similarity index 87% rename from packages/wsd/bench-results.md rename to packages/computerd/bench-results.md index 337a7c4f..e0629968 100644 --- a/packages/wsd/bench-results.md +++ b/packages/computerd/bench-results.md @@ -1,7 +1,7 @@ # FUSE mount option benchmarks Numbers from running `script/run-fs-bench.sh` against the linux-x64 -`wsd` binary in a privileged docker container, with the bench's pure +`computerd` binary in a privileged docker container, with the bench's pure large-file scenarios. Measurements were taken on Apple Silicon under qemu/x86 emulation, which inflates the absolute numbers but the relative comparisons hold. Re-run the harness on a native Linux host @@ -15,14 +15,14 @@ does not exercise cross-open kernel page-cache reuse. ## Setup ```bash -# Build the linux-x64 wsd binary. -npm run build:bin --workspace @cloudflare/workspace-wsd +# Build the linux-x64 computerd binary. +npm run build:bin --workspace @cloudflare/computerd -# Boot wsd in a docker container, run the bench inside it, drop the +# Boot computerd in a docker container, run the bench inside it, drop the # JSON output on the host. docker run --rm --platform linux/amd64 --privileged \ --device /dev/fuse --cap-add SYS_ADMIN --cap-add MKNOD \ - -v $PWD/artifacts/wsd/wsd-linux-x64:/usr/local/bin/wsd:ro \ + -v $PWD/artifacts/computerd/computerd-linux-x64:/usr/local/bin/computerd:ro \ -v $PWD/script/fs-bench.sh:/usr/local/bin/fs-bench:ro \ -v $PWD/script/run-fs-bench.sh:/run-bench.sh:ro \ -v $PWD/bench-out:/out \ @@ -34,8 +34,8 @@ docker run --rm --platform linux/amd64 --privileged \ The production-safe profile (auto_cache plus one-second metadata timeouts) is the built-in default; the numbers below were captured -with no WSD_FUSE_* env vars set. To opt out of auto_cache for a -run, set WSD_FUSE_AUTO_CACHE=0. +with no COMPUTERD_FUSE_* env vars set. To opt out of auto_cache for a +run, set COMPUTERD_FUSE_AUTO_CACHE=0. ## Results @@ -44,7 +44,7 @@ Mean over three reps with one warmup. All times in milliseconds. The default column reflects the production-safe profile that the daemon ships with today (auto_cache plus one-second attr_timeout, entry_timeout, and ac_attr_timeout). The kernel_cache column ran -with WSD_FUSE_AUTO_CACHE=0 and WSD_FUSE_KERNEL_CACHE=1. +with COMPUTERD_FUSE_AUTO_CACHE=0 and COMPUTERD_FUSE_KERNEL_CACHE=1. | Scenario | native baseline | default (auto_cache) | kernel_cache | |-------------------|----------------:|---------------------:|-------------:| @@ -87,7 +87,7 @@ container is the only writer. `auto_cache` is the production-safe default. It invalidates the page cache on open when mtime or size changed. The contract rests on -three tests. Two in `packages/wsd/src/fuse/driver.test.ts` pin that +three tests. Two in `packages/computerd/src/fuse/driver.test.ts` pin that the FUSE driver's `getattr` surfaces fresh mtime and size after an external VFS write and after a buffered local write. Four more in `packages/dofs/src/sync/apply.test.ts` pin that the sync apply path diff --git a/packages/wsd/package.json b/packages/computerd/package.json similarity index 74% rename from packages/wsd/package.json rename to packages/computerd/package.json index 7728fe79..1be83ea3 100644 --- a/packages/wsd/package.json +++ b/packages/computerd/package.json @@ -1,12 +1,12 @@ { - "name": "@cloudflare/workspace-wsd", + "name": "@cloudflare/computerd", "version": "0.0.0-alpha.13", - "description": "Workspace daemon CLI and FUSE mount. Source-only; the distributable lives at @cloudflare/workspace-wsd-linux-x64.", + "description": "Computer daemon CLI and FUSE mount. Source-only; the distributable lives at @cloudflare/computer-computerd-linux-x64.", "license": "MIT", "private": true, "type": "commonjs", "bin": { - "wsd": "./dist/cli/wsd.cjs" + "computerd": "./dist/cli/computerd.cjs" }, "files": [ "dist", @@ -25,8 +25,8 @@ "node": ">=22" }, "dependencies": { + "@cloudflare/computer-rpc": "*", "@cloudflare/dofs": "*", - "@cloudflare/workspace-rpc": "*", "@platformatic/vfs": "^0.4.0", "fuse-native": "^2.2.6" }, diff --git a/packages/wsd/scripts/build-bin.mjs b/packages/computerd/scripts/build-bin.mjs similarity index 88% rename from packages/wsd/scripts/build-bin.mjs rename to packages/computerd/scripts/build-bin.mjs index ce01c477..34f56148 100644 --- a/packages/wsd/scripts/build-bin.mjs +++ b/packages/computerd/scripts/build-bin.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// Cross-compile wsd to a self-contained Node SEA binary for each supported +// Cross-compile computerd to a self-contained Node SEA binary for each supported // target. Steps per target: // 1. esbuild a single ESM bundle (see scripts/sea/bundle.mjs). // 2. Write a sea-config.json that names the bundle as main and lists the @@ -21,21 +21,21 @@ import { pipeline } from "node:stream/promises"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import { bundleWsd } from "./sea/bundle.mjs"; +import { bundleComputerd } from "./sea/bundle.mjs"; const execFileP = promisify(execFile); const here = dirname(fileURLToPath(import.meta.url)); -const wsdRoot = resolve(here, ".."); -const repoRoot = resolve(wsdRoot, "../.."); -const outputDir = resolve(repoRoot, "artifacts/wsd"); -const seaWorkDir = resolve(wsdRoot, "dist/sea"); +const computerdRoot = resolve(here, ".."); +const repoRoot = resolve(computerdRoot, "../.."); +const outputDir = resolve(repoRoot, "artifacts/computerd"); +const seaWorkDir = resolve(computerdRoot, "dist/sea"); const nodeCacheDir = resolve(repoRoot, ".devbox/node-binaries"); const nodeVersion = "v22.22.3"; const targets = [ { name: "linux-x64", - outputName: "wsd-linux-x64", + outputName: "computerd-linux-x64", nodeArchive: `node-${nodeVersion}-linux-x64.tar.xz`, nodeBinaryInArchive: `node-${nodeVersion}-linux-x64/bin/node`, addon: "node_modules/fuse-native/prebuilds/linux-x64/node.napi.node", @@ -46,7 +46,7 @@ const targets = [ }, { name: "macos-x64", - outputName: "wsd-macos-x64", + outputName: "computerd-macos-x64", nodeArchive: `node-${nodeVersion}-darwin-x64.tar.xz`, nodeBinaryInArchive: `node-${nodeVersion}-darwin-x64/bin/node`, addon: "node_modules/fuse-native/prebuilds/darwin-x64/node.napi.node", @@ -69,7 +69,7 @@ async function main() { await mkdir(nodeCacheDir, { recursive: true }); for (const target of targets) { - console.log(`[wsd-bin] building ${target.name}`); + console.log(`[computerd-bin] building ${target.name}`); await buildTarget(target); } @@ -82,7 +82,7 @@ async function buildTarget(target) { const configPath = resolve(seaWorkDir, `${target.name}.sea-config.json`); const outBin = resolve(outputDir, target.outputName); - await bundleWsd({ outfile: bundlePath, target }); + await bundleComputerd({ outfile: bundlePath, target }); await writeFile( configPath, @@ -141,7 +141,7 @@ async function ensureTargetNodeBinary(target) { const archivePath = resolve(nodeCacheDir, target.nodeArchive); if (!(await exists(archivePath))) { const url = `https://nodejs.org/dist/${nodeVersion}/${target.nodeArchive}`; - console.log(`[wsd-bin] downloading ${url}`); + console.log(`[computerd-bin] downloading ${url}`); const res = await fetch(url); if (!res.ok || !res.body) throw new Error(`failed to download ${url}: ${res.status}`); const tmpPath = `${archivePath}.${process.pid}.tmp`; @@ -169,13 +169,13 @@ async function maybe(promise) { try { return await promise; } catch (err) { - console.warn(`[wsd-bin] non-fatal: ${err.message}`); + console.warn(`[computerd-bin] non-fatal: ${err.message}`); } } function runNpm(script) { return new Promise((res, rej) => { - const child = execFile("npm", ["run", script], { cwd: wsdRoot }); + const child = execFile("npm", ["run", script], { cwd: computerdRoot }); child.stdout?.pipe(process.stdout); child.stderr?.pipe(process.stderr); child.once("exit", (code) => diff --git a/packages/wsd/scripts/build-docker.mjs b/packages/computerd/scripts/build-docker.mjs similarity index 67% rename from packages/wsd/scripts/build-docker.mjs rename to packages/computerd/scripts/build-docker.mjs index e40a513c..1431f87e 100644 --- a/packages/wsd/scripts/build-docker.mjs +++ b/packages/computerd/scripts/build-docker.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -// Build the wsd-linux-x64 SEA binary, stage it into the platform +// Build the computerd-linux-x64 SEA binary, stage it into the platform // package, and produce a docker image that downstream Dockerfiles -// can `COPY --from`. Tagged `cloudflare/workspace-wsd-linux-x64` +// can `COPY --from`. Tagged `cloudflare/computer-computerd-linux-x64` // (matching the npm package name so consumers don't have to // remember a second identifier) at both the source-of-truth // version and `latest`. @@ -11,7 +11,7 @@ // node ./scripts/build-docker.mjs --push # build + docker push // // Idempotent: build-bin's no-op detection skips the expensive parts -// when artifacts/wsd/wsd-linux-x64 is already current. +// when artifacts/computerd/computerd-linux-x64 is already current. import { execFileSync } from "node:child_process"; import { chmodSync, copyFileSync, readFileSync } from "node:fs"; @@ -19,24 +19,24 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); -const wsdRoot = resolve(here, ".."); -const repoRoot = resolve(wsdRoot, "../.."); -const platformDir = resolve(repoRoot, "packages/wsd-linux-x64"); +const computerdRoot = resolve(here, ".."); +const repoRoot = resolve(computerdRoot, "../.."); +const platformDir = resolve(repoRoot, "packages/computer-computerd-linux-x64"); -const { version } = JSON.parse(readFileSync(resolve(wsdRoot, "package.json"), "utf8")); +const { version } = JSON.parse(readFileSync(resolve(computerdRoot, "package.json"), "utf8")); -const IMAGE = "cloudflare/workspace-wsd-linux-x64"; +const IMAGE = "cloudflare/computer-computerd-linux-x64"; const push = process.argv.includes("--push"); // 1. Build the SEA binary. -console.log("[build-docker] building wsd-linux-x64"); -execFileSync("node", ["./scripts/build-bin.mjs"], { cwd: wsdRoot, stdio: "inherit" }); +console.log("[build-docker] building computerd-linux-x64"); +execFileSync("node", ["./scripts/build-bin.mjs"], { cwd: computerdRoot, stdio: "inherit" }); // 2. Stage the binary into the platform package's bin/. The -// .dockerignore restricts the build context to bin/wsd, so the +// .dockerignore restricts the build context to bin/computerd, so the // Dockerfile's COPY hits this file directly. -const src = resolve(repoRoot, "artifacts/wsd/wsd-linux-x64"); -const dst = resolve(platformDir, "bin/wsd"); +const src = resolve(repoRoot, "artifacts/computerd/computerd-linux-x64"); +const dst = resolve(platformDir, "bin/computerd"); copyFileSync(src, dst); chmodSync(dst, 0o755); console.log(`[build-docker] staged ${src} -> ${dst}`); diff --git a/packages/wsd/scripts/build.mjs b/packages/computerd/scripts/build.mjs similarity index 88% rename from packages/wsd/scripts/build.mjs rename to packages/computerd/scripts/build.mjs index 6f4ad4e0..ad97dc7a 100644 --- a/packages/wsd/scripts/build.mjs +++ b/packages/computerd/scripts/build.mjs @@ -11,8 +11,8 @@ const tscBin = process.platform === "win32" ? "tsc.cmd" : "tsc"; await rm(resolve(packageRoot, "dist"), { recursive: true, force: true }); await run(tscBin, ["-p", "tsconfig.json"], packageRoot); -const jsEntry = resolve(packageRoot, "dist/cli/wsd.js"); -const cjsEntry = resolve(packageRoot, "dist/cli/wsd.cjs"); +const jsEntry = resolve(packageRoot, "dist/cli/computerd.js"); +const cjsEntry = resolve(packageRoot, "dist/cli/computerd.cjs"); await copyFile(jsEntry, cjsEntry); await chmod(cjsEntry, 0o755); diff --git a/packages/wsd/scripts/publish-linux-x64.mjs b/packages/computerd/scripts/publish-linux-x64.mjs similarity index 65% rename from packages/wsd/scripts/publish-linux-x64.mjs rename to packages/computerd/scripts/publish-linux-x64.mjs index 07888d0f..6ac12809 100644 --- a/packages/wsd/scripts/publish-linux-x64.mjs +++ b/packages/computerd/scripts/publish-linux-x64.mjs @@ -1,10 +1,10 @@ #!/usr/bin/env node // Build the SEA binary, stamp the platform package's version to match -// packages/wsd/package.json (the source of truth), copy the binary -// into packages/wsd-linux-x64/bin/, and npm publish. +// packages/computerd/package.json (the source of truth), copy the binary +// into packages/computer-computerd-linux-x64/bin/, and npm publish. // // Run manually for now: -// npm run publish:linux-x64 --workspace @cloudflare/workspace-wsd +// npm run publish:linux-x64 --workspace @cloudflare/computerd // // The script is idempotent — build-bin's no-op detection skips // re-downloading Node and re-running postject when the artifact is @@ -16,16 +16,16 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); -const wsdRoot = resolve(here, ".."); -const repoRoot = resolve(wsdRoot, "../.."); -const platformDir = resolve(repoRoot, "packages/wsd-linux-x64"); +const computerdRoot = resolve(here, ".."); +const repoRoot = resolve(computerdRoot, "../.."); +const platformDir = resolve(repoRoot, "packages/computer-computerd-linux-x64"); -const { version } = JSON.parse(readFileSync(resolve(wsdRoot, "package.json"), "utf8")); +const { version } = JSON.parse(readFileSync(resolve(computerdRoot, "package.json"), "utf8")); // 1. Build the SEA binary. Always re-run; build-bin caches the // expensive bits internally. -console.log("[publish-linux-x64] building wsd-linux-x64"); -execFileSync("node", ["./scripts/build-bin.mjs"], { cwd: wsdRoot, stdio: "inherit" }); +console.log("[publish-linux-x64] building computerd-linux-x64"); +execFileSync("node", ["./scripts/build-bin.mjs"], { cwd: computerdRoot, stdio: "inherit" }); // 2. Stamp the platform package version so it never drifts from the // binary's source version. Same number on both sides is the @@ -40,15 +40,15 @@ if (pkg.version !== version) { // 3. Copy the binary in. The COPY --from path in downstream // Dockerfiles depends on this exact location. -const src = resolve(repoRoot, "artifacts/wsd/wsd-linux-x64"); -const dst = resolve(platformDir, "bin/wsd"); +const src = resolve(repoRoot, "artifacts/computerd/computerd-linux-x64"); +const dst = resolve(platformDir, "bin/computerd"); copyFileSync(src, dst); chmodSync(dst, 0o755); console.log(`[publish-linux-x64] copied ${src} -> ${dst}`); // 4. Publish. --access public because npm defaults scoped packages // to restricted, which would fail without a paid org plan. -console.log(`[publish-linux-x64] publishing @cloudflare/workspace-wsd-linux-x64@${version}`); +console.log(`[publish-linux-x64] publishing @cloudflare/computer-computerd-linux-x64@${version}`); execFileSync("npm", ["publish", "--access", "public"], { cwd: platformDir, stdio: "inherit", diff --git a/packages/wsd/scripts/sea/bootstrap.cjs b/packages/computerd/scripts/sea/bootstrap.cjs similarity index 100% rename from packages/wsd/scripts/sea/bootstrap.cjs rename to packages/computerd/scripts/sea/bootstrap.cjs diff --git a/packages/wsd/scripts/sea/bundle.mjs b/packages/computerd/scripts/sea/bundle.mjs similarity index 80% rename from packages/wsd/scripts/sea/bundle.mjs rename to packages/computerd/scripts/sea/bundle.mjs index 37e320c4..e6cb0341 100644 --- a/packages/wsd/scripts/sea/bundle.mjs +++ b/packages/computerd/scripts/sea/bundle.mjs @@ -1,7 +1,7 @@ -// Bundle wsd into a single ESM file for Node's native SEA. +// Bundle computerd into a single ESM file for Node's native SEA. // // Strategy: -// * esbuild produces an ESM bundle of `dist/cli/wsd.cjs`. ESM is required so +// * esbuild produces an ESM bundle of `dist/cli/computerd.cjs`. ESM is required so // capnweb's top-level await survives without an async-IIFE wrapper. A banner // re-establishes `require`, `__filename`, and `__dirname` for any CJS code // transpiled into the bundle. @@ -21,10 +21,10 @@ import { fileURLToPath } from "node:url"; import { build } from "esbuild"; const here = dirname(fileURLToPath(import.meta.url)); -const wsdRoot = resolve(here, "../.."); -const repoRoot = resolve(wsdRoot, "../.."); +const computerdRoot = resolve(here, "../.."); +const repoRoot = resolve(computerdRoot, "../.."); -export async function bundleWsd({ outfile, target }) { +export async function bundleComputerd({ outfile, target }) { const nodeGypBuildShim = ` const { writeFileSync, chmodSync, existsSync, mkdirSync } = require("node:fs"); const { tmpdir } = require("node:os"); @@ -41,7 +41,7 @@ function loadNative() { const addonBuf = Buffer.from(sea.getAsset("fuse-native.node")); const libBuf = Buffer.from(sea.getAsset(LIBFUSE_NAME)); const hash = createHash("sha256").update(addonBuf).update(libBuf).digest("hex").slice(0, 16); - const dir = join(tmpdir(), "wsd-sea-" + hash); + const dir = join(tmpdir(), "computerd-sea-" + hash); try { mkdirSync(dir, { recursive: true }); } catch {} const libPath = join(dir, LIBFUSE_NAME); const addonPath = join(dir, "fuse-native.node"); @@ -68,13 +68,13 @@ module.exports = { beforeMount: noop, beforeUnmount: noop, configure: noop, unco setup(b) { b.onResolve({ filter: /^node-gyp-build$/ }, () => ({ path: "node-gyp-build", - namespace: "wsd-shim", + namespace: "computerd-shim", })); b.onResolve({ filter: /^fuse-shared-library$/ }, () => ({ path: "fuse-shared-library", - namespace: "wsd-shim", + namespace: "computerd-shim", })); - b.onLoad({ filter: /.*/, namespace: "wsd-shim" }, (args) => { + b.onLoad({ filter: /.*/, namespace: "computerd-shim" }, (args) => { if (args.path === "node-gyp-build") { return { contents: nodeGypBuildShim, loader: "js", resolveDir: repoRoot }; } @@ -86,9 +86,9 @@ module.exports = { beforeMount: noop, beforeUnmount: noop, configure: noop, unco }, }; - // WSD_DEFAULT_PORT lets a build pipeline stamp the compiled-in + // COMPUTERD_DEFAULT_PORT lets a build pipeline stamp the compiled-in // port without editing source. Runtime PORT env still wins. - const buildPortEnv = process.env.WSD_DEFAULT_PORT; + const buildPortEnv = process.env.COMPUTERD_DEFAULT_PORT; const buildPort = buildPortEnv === undefined || buildPortEnv === "" ? undefined : Number(buildPortEnv); if ( @@ -96,21 +96,21 @@ module.exports = { beforeMount: noop, beforeUnmount: noop, configure: noop, unco (!Number.isInteger(buildPort) || buildPort < 0 || buildPort > 65_535) ) { throw new Error( - `WSD_DEFAULT_PORT must be an integer between 0 and 65535, got ${JSON.stringify(buildPortEnv)}`, + `COMPUTERD_DEFAULT_PORT must be an integer between 0 and 65535, got ${JSON.stringify(buildPortEnv)}`, ); } await build({ - entryPoints: [resolve(wsdRoot, "dist/cli/wsd.cjs")], + entryPoints: [resolve(computerdRoot, "dist/cli/computerd.cjs")], bundle: true, platform: "node", target: "node22", format: "esm", define: { - __WSD_BUILD_DEFAULT_PORT__: buildPort === undefined ? "undefined" : String(buildPort), + __COMPUTERD_BUILD_DEFAULT_PORT__: buildPort === undefined ? "undefined" : String(buildPort), }, - // wsd is compiled to CJS, so esbuild walks it via the `require` - // condition. Add `import` to that list so dofs and workspace-rpc + // computerd is compiled to CJS, so esbuild walks it via the `require` + // condition. Add `import` to that list so dofs and computer-rpc // (ESM-only after the SEA migration) still resolve via their exports map. conditions: ["import", "node"], outfile, diff --git a/packages/wsd/src/cli/bundle-port.test.ts b/packages/computerd/src/cli/bundle-port.test.ts similarity index 50% rename from packages/wsd/src/cli/bundle-port.test.ts rename to packages/computerd/src/cli/bundle-port.test.ts index 95dcb7be..8d2341e2 100644 --- a/packages/wsd/src/cli/bundle-port.test.ts +++ b/packages/computerd/src/cli/bundle-port.test.ts @@ -5,47 +5,47 @@ import { fileURLToPath } from "node:url"; import { expect, onTestFinished, test } from "vitest"; -// Confirm WSD_DEFAULT_PORT on the build host stamps into the bundled +// Confirm COMPUTERD_DEFAULT_PORT on the build host stamps into the bundled // source so downstream builds can fix a custom default without // patching source. The bundle script is exercised directly; the // runtime PORT env still wins (covered separately by the CLI // integration tests). const here = path.dirname(fileURLToPath(import.meta.url)); -const distEntry = path.resolve(here, "../../dist/cli/wsd.cjs"); +const distEntry = path.resolve(here, "../../dist/cli/computerd.cjs"); const bundleScript = path.resolve(here, "../../scripts/sea/bundle.mjs"); -type BundleWsd = (opts: { +type BundleComputerd = (opts: { outfile: string; target: { libfuseName: string; envVar: string }; }) => Promise; async function loadBundler(ctx: { skip: (reason?: string) => void; -}): Promise { +}): Promise { try { await fs.access(distEntry); } catch { - ctx.skip("dist/cli/wsd.cjs not built; run `npm run build` first"); + ctx.skip("dist/cli/computerd.cjs not built; run `npm run build` first"); return undefined; } - const mod = (await import(bundleScript)) as { bundleWsd: BundleWsd }; - return mod.bundleWsd; + const mod = (await import(bundleScript)) as { bundleComputerd: BundleComputerd }; + return mod.bundleComputerd; } -test("WSD_DEFAULT_PORT stamps into the SEA bundle", async (ctx) => { - const bundleWsd = await loadBundler(ctx); - if (bundleWsd === undefined) return; +test("COMPUTERD_DEFAULT_PORT stamps into the SEA bundle", async (ctx) => { + const bundleComputerd = await loadBundler(ctx); + if (bundleComputerd === undefined) return; - const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-bundle-")); + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-bundle-")); onTestFinished(async () => { await fs.rm(tmp, { recursive: true, force: true }); - delete process.env.WSD_DEFAULT_PORT; + delete process.env.COMPUTERD_DEFAULT_PORT; }); - const outfile = path.join(tmp, "wsd.bundle.mjs"); - process.env.WSD_DEFAULT_PORT = "12345"; + const outfile = path.join(tmp, "computerd.bundle.mjs"); + process.env.COMPUTERD_DEFAULT_PORT = "12345"; - await bundleWsd({ + await bundleComputerd({ outfile, target: { libfuseName: "libfuse.so.2", envVar: "LD_LIBRARY_PATH" }, }); @@ -56,16 +56,16 @@ test("WSD_DEFAULT_PORT stamps into the SEA bundle", async (ctx) => { expect(bundle).toMatch(/12345/); }); -test("missing WSD_DEFAULT_PORT keeps the in-source 45678 fallback", async (ctx) => { - const bundleWsd = await loadBundler(ctx); - if (bundleWsd === undefined) return; +test("missing COMPUTERD_DEFAULT_PORT keeps the in-source 45678 fallback", async (ctx) => { + const bundleComputerd = await loadBundler(ctx); + if (bundleComputerd === undefined) return; - const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-bundle-")); + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-bundle-")); onTestFinished(async () => fs.rm(tmp, { recursive: true, force: true })); - const outfile = path.join(tmp, "wsd.bundle.mjs"); - delete process.env.WSD_DEFAULT_PORT; + const outfile = path.join(tmp, "computerd.bundle.mjs"); + delete process.env.COMPUTERD_DEFAULT_PORT; - await bundleWsd({ + await bundleComputerd({ outfile, target: { libfuseName: "libfuse.so.2", envVar: "LD_LIBRARY_PATH" }, }); @@ -75,21 +75,21 @@ test("missing WSD_DEFAULT_PORT keeps the in-source 45678 fallback", async (ctx) expect(bundle).toMatch(/45678/); }); -test("WSD_DEFAULT_PORT rejects non-integer values", async (ctx) => { - const bundleWsd = await loadBundler(ctx); - if (bundleWsd === undefined) return; +test("COMPUTERD_DEFAULT_PORT rejects non-integer values", async (ctx) => { + const bundleComputerd = await loadBundler(ctx); + if (bundleComputerd === undefined) return; - const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-bundle-")); + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-bundle-")); onTestFinished(async () => { await fs.rm(tmp, { recursive: true, force: true }); - delete process.env.WSD_DEFAULT_PORT; + delete process.env.COMPUTERD_DEFAULT_PORT; }); - process.env.WSD_DEFAULT_PORT = "not-a-port"; + process.env.COMPUTERD_DEFAULT_PORT = "not-a-port"; await expect( - bundleWsd({ - outfile: path.join(tmp, "wsd.bundle.mjs"), + bundleComputerd({ + outfile: path.join(tmp, "computerd.bundle.mjs"), target: { libfuseName: "libfuse.so.2", envVar: "LD_LIBRARY_PATH" }, }), - ).rejects.toThrow(/WSD_DEFAULT_PORT/); + ).rejects.toThrow(/COMPUTERD_DEFAULT_PORT/); }); diff --git a/packages/wsd/src/cli/wsd.test.ts b/packages/computerd/src/cli/computerd.test.ts similarity index 82% rename from packages/wsd/src/cli/wsd.test.ts rename to packages/computerd/src/cli/computerd.test.ts index 9cf132b5..f9b38b9d 100644 --- a/packages/wsd/src/cli/wsd.test.ts +++ b/packages/computerd/src/cli/computerd.test.ts @@ -12,9 +12,9 @@ import { resolveFuseBackend } from "../fuse/index.js"; const here = path.dirname(fileURLToPath(import.meta.url)); const packageRoot = path.resolve(here, "../.."); -const cliPath = path.join(packageRoot, "dist", "cli", "wsd.cjs"); +const cliPath = path.join(packageRoot, "dist", "cli", "computerd.cjs"); -test("wsd rejects relative MOUNT_POINT values", async () => { +test("computerd rejects relative MOUNT_POINT values", async () => { const port = await getAvailablePort(); const child = spawn(cliPath, { cwd: packageRoot, @@ -27,7 +27,7 @@ test("wsd rejects relative MOUNT_POINT values", async () => { expect(stderr).toMatch(/MOUNT_POINT must be an absolute path/); }); -test("wsd rejects non-numeric EXEC_LOG_MAX_BYTES values", async () => { +test("computerd rejects non-numeric EXEC_LOG_MAX_BYTES values", async () => { // Boot the daemon with garbage in EXEC_LOG_MAX_BYTES; it should // refuse to start. Previously Number('foo') -> NaN silently // disabled log eviction (every append exceeded the cap). @@ -36,7 +36,7 @@ test("wsd rejects non-numeric EXEC_LOG_MAX_BYTES values", async () => { cwd: packageRoot, env: { ...process.env, - MOUNT_POINT: "/tmp/wsd-mount-not-used", + MOUNT_POINT: "/tmp/computerd-mount-not-used", PORT: String(port), EXEC_LOG_MAX_BYTES: "foo", FUSE_MOUNT: "none", @@ -49,16 +49,16 @@ test("wsd rejects non-numeric EXEC_LOG_MAX_BYTES values", async () => { expect(stderr).toMatch(/EXEC_LOG_MAX_BYTES must be a positive integer/); }); -test("wsd appends to LOG_FILE when set, in addition to stdout/stderr", async (_t) => { +test("computerd appends to LOG_FILE when set, in addition to stdout/stderr", async (_t) => { // Boot the daemon with LOG_FILE pointed at a temp file. The // startup banner line on stdout should also show up in the file, // proving the console patch landed and didn't replace the original // writers. - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-log-")); - const logFile = path.join(dir, "wsd.log"); + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-log-")); + const logFile = path.join(dir, "computerd.log"); const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-mount-")); - await startWsd({ + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-mount-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "none", LOG_FILE: logFile }, @@ -66,10 +66,10 @@ test("wsd appends to LOG_FILE when set, in addition to stdout/stderr", async (_t onTestFinished(() => fs.rm(dir, { recursive: true, force: true })); const contents = await fs.readFile(logFile, "utf8"); - expect(contents).toMatch(/\[info\] wsd listening on/); + expect(contents).toMatch(/\[info\] computerd listening on/); }); -test("wsd exposes file IO through real FUSE when FUSE_MOUNT=fuse", async (ctx) => { +test("computerd exposes file IO through real FUSE when FUSE_MOUNT=fuse", async (ctx) => { // Only meaningful on linux hosts with /dev/fuse available. Use // the explicit FUSE_MOUNT=fuse value so the test fails loudly if // /dev/fuse goes missing rather than silently sliding onto the @@ -81,8 +81,8 @@ test("wsd exposes file IO through real FUSE when FUSE_MOUNT=fuse", async (ctx) = } const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-mount-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "fuse" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-mount-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "fuse" } }); const health = await request(`http://127.0.0.1:${port}/health`); expect(health.statusCode).toBe(200); @@ -92,7 +92,7 @@ test("wsd exposes file IO through real FUSE when FUSE_MOUNT=fuse", async (ctx) = expect(root.statusCode).toBe(200); expect(JSON.parse(root.body)).toEqual({}); - const info = await request(`http://127.0.0.1:${port}/__wsd/info`); + const info = await request(`http://127.0.0.1:${port}/__computerd/info`); expect(info.statusCode).toBe(200); expect(JSON.parse(info.body)).toEqual({ backend: { kind: "fuse" }, @@ -106,10 +106,10 @@ test("wsd exposes file IO through real FUSE when FUSE_MOUNT=fuse", async (ctx) = }); test("/ws serves a capnweb WorkspaceRPC session", async (_ctx) => { - const { createWorkspaceClient } = await import("@cloudflare/workspace-rpc/client"); + const { createWorkspaceClient } = await import("@cloudflare/computer-rpc/client"); const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-mount-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-mount-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); const client = createWorkspaceClient({ url: `ws://127.0.0.1:${port}/ws` }); try { @@ -136,20 +136,20 @@ test("/ws serves a capnweb WorkspaceRPC session", async (_ctx) => { test("/api serves a capnweb HTTP-batch WorkspaceRPC session", async (_ctx) => { const { newHttpBatchRpcSession } = await import("capnweb"); const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-mount-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-mount-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); // HTTP batch flushes on first await; each call is a fresh session. const stub = newHttpBatchRpcSession(`http://127.0.0.1:${port}/api`); expect(await stub.sync.hasObjects([])).toEqual([]); }); -test("/__wsd/stats returns DOFS table sizes and process memory", async (_ctx) => { +test("/__computerd/stats returns DOFS table sizes and process memory", async (_ctx) => { const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-stats-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-stats-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); - const stats = await request(`http://127.0.0.1:${port}/__wsd/stats`); + const stats = await request(`http://127.0.0.1:${port}/__computerd/stats`); expect(stats.statusCode).toBe(200); expect(stats.headers["content-type"]).toMatch(/application\/json/); @@ -186,15 +186,15 @@ test("/__wsd/stats returns DOFS table sizes and process memory", async (_ctx) => } }); -test("wsd exposes file IO through the userspace shim when FUSE_MOUNT=shim", async (_ctx) => { +test("computerd exposes file IO through the userspace shim when FUSE_MOUNT=shim", async (_ctx) => { // No FUSE backend required — the shim runs in user space and is // explicitly opt-in via FUSE_MOUNT=shim. Mirrors the real-FUSE // test above but for the dev fallback path. const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "shim" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "shim" } }); - const info = await request(`http://127.0.0.1:${port}/__wsd/info`); + const info = await request(`http://127.0.0.1:${port}/__computerd/info`); expect(info.statusCode).toBe(200); const parsed = JSON.parse(info.body); expect(parsed.backend.kind).toBe("shim"); @@ -209,18 +209,18 @@ test("wsd exposes file IO through the userspace shim when FUSE_MOUNT=shim", asyn test("FUSE_MOUNT=shim materialises an RPC push under the mount point", async (_ctx) => { // End-to-end version of the cross-namespace fix: a peer pushes a - // file at `${MOUNT_POINT}/repo/a.txt` into wsd's VFS over + // file at `${MOUNT_POINT}/repo/a.txt` into computerd's VFS over // capnweb, and the shim drops it on disk at the same absolute // path. The on-disk read is what proves the mountPoint plumbing // works — a regression would surface here as ENOENT. const { Database, initializeSchema, WorkspaceFilesystem } = await import("@cloudflare/dofs"); const { SQLiteTestStorage } = await import("@cloudflare/dofs/testing"); - const { createWorkspaceClient } = await import("@cloudflare/workspace-rpc/client"); - const { pushOnce } = await import("@cloudflare/workspace-rpc/driver"); + const { createWorkspaceClient } = await import("@cloudflare/computer-rpc/client"); + const { pushOnce } = await import("@cloudflare/computer-rpc/driver"); const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-push-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "shim" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-push-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "shim" } }); const client = createWorkspaceClient({ url: `ws://127.0.0.1:${port}/ws` }); onTestFinished(() => client.close()); @@ -239,13 +239,13 @@ test("FUSE_MOUNT=shim materialises an RPC push under the mount point", async (_c expect(await fs.readFile(path.join(mountPoint, "repo", "a.txt"), "utf8")).toBe("alpha"); }); -test("wsd rejects unknown FUSE_MOUNT values", async () => { +test("computerd rejects unknown FUSE_MOUNT values", async () => { const port = await getAvailablePort(); const child = spawn(cliPath, { cwd: packageRoot, env: { ...process.env, - MOUNT_POINT: "/tmp/wsd-mount-not-used", + MOUNT_POINT: "/tmp/computerd-mount-not-used", PORT: String(port), FUSE_MOUNT: "bogus", }, @@ -261,13 +261,13 @@ test.each([ ["DISABLE_FUSE", "1"], ["FUSE_SHIM", "1"], ["WSD_FUSE_BACKEND", "linux"], -])("wsd refuses to boot when legacy %s is set", async (name, value) => { +])("computerd refuses to boot when legacy %s is set", async (name, value) => { const port = await getAvailablePort(); const child = spawn(cliPath, { cwd: packageRoot, env: { ...process.env, - MOUNT_POINT: "/tmp/wsd-mount-not-used", + MOUNT_POINT: "/tmp/computerd-mount-not-used", PORT: String(port), [name]: value, }, @@ -283,7 +283,7 @@ test.each([ test("/connect re-dial tears down the prior WebSocket session", async (_ctx) => { // After a DO hibernate, the new incarnation calls POST /connect // again to bootstrap a fresh capnweb session against the still- - // running wsd. wsd must close the previous outbound socket before + // running computerd. computerd must close the previous outbound socket before // opening the new one — otherwise the old session leaks for the // life of the container and the DO ends up with two halves of two // sessions tangled together. @@ -323,7 +323,7 @@ test("/connect re-dial tears down the prior WebSocket session", async (_ctx) => () => new Promise((resolve) => { // Force-destroy any lingering TCP sockets so peerServer.close() - // can return; otherwise an unkilled wsd-side WS keeps the + // can return; otherwise an unkilled computerd-side WS keeps the // server open and the test hangs at teardown. for (const sock of peerSockets) sock.destroy(); wss.close(); @@ -332,8 +332,8 @@ test("/connect re-dial tears down the prior WebSocket session", async (_ctx) => ); const port = await getAvailablePort(); - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-mount-")); - await startWsd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-mount-")); + await startComputerd({ port, mountPoint, env: { FUSE_MOUNT: "none" } }); const peerUrl = `http://127.0.0.1:${peerPort}`; const connect = async () => { @@ -354,7 +354,7 @@ test("/connect re-dial tears down the prior WebSocket session", async (_ctx) => expect(opened[1].closed).toBe(false, "second peer WS should still be open"); }); -async function startWsd({ +async function startComputerd({ port, mountPoint, env = {}, @@ -416,7 +416,7 @@ async function waitForExit(child, timeoutMs = 2_000) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { child.kill("SIGKILL"); - reject(new Error("timed out waiting for wsd to exit")); + reject(new Error("timed out waiting for computerd to exit")); }, timeoutMs); child.once("exit", (code, signal) => { @@ -431,7 +431,7 @@ async function waitForHTTPOK(url, child, output, timeoutMs = 5_000) { while (Date.now() - started < timeoutMs) { if (child.exitCode !== null) { - throw new Error(`wsd exited before becoming ready: ${child.exitCode}\n${output()}`); + throw new Error(`computerd exited before becoming ready: ${child.exitCode}\n${output()}`); } try { @@ -522,7 +522,7 @@ function stopProcess(child) { const timeout = setTimeout(() => { child.kill("SIGKILL"); - reject(new Error("timed out waiting for wsd to exit")); + reject(new Error("timed out waiting for computerd to exit")); }, 2_000); child.once("exit", () => { diff --git a/packages/wsd/src/cli/wsd.ts b/packages/computerd/src/cli/computerd.ts similarity index 93% rename from packages/wsd/src/cli/wsd.ts rename to packages/computerd/src/cli/computerd.ts index 3b564064..2727c6e3 100644 --- a/packages/wsd/src/cli/wsd.ts +++ b/packages/computerd/src/cli/computerd.ts @@ -4,19 +4,19 @@ import { mkdir } from "node:fs/promises"; import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; import type { Socket } from "node:net"; import { isAbsolute } from "node:path"; -import type { Database } from "@cloudflare/dofs"; -import type { ExecEvent as RpcExecEvent } from "@cloudflare/workspace-rpc"; -import { createWorkspaceClient, type WorkspaceClient } from "@cloudflare/workspace-rpc/client"; -import { isStubTrackingEnabled, stubSnapshot } from "@cloudflare/workspace-rpc/debug"; -import type { RunnerLike } from "@cloudflare/workspace-rpc/server"; +import type { ExecEvent as RpcExecEvent } from "@cloudflare/computer-rpc"; +import { createWorkspaceClient, type WorkspaceClient } from "@cloudflare/computer-rpc/client"; +import { isStubTrackingEnabled, stubSnapshot } from "@cloudflare/computer-rpc/debug"; +import type { RunnerLike } from "@cloudflare/computer-rpc/server"; import { acceptWebSocketSession, createWorkspaceServer, serveHTTPBatch, -} from "@cloudflare/workspace-rpc/server"; +} from "@cloudflare/computer-rpc/server"; +import type { Database } from "@cloudflare/dofs"; import { WebSocket, WebSocketServer } from "ws"; import { Runner } from "../exec/index.js"; -import type { ExecEvent as WsdExecEvent } from "../exec/types.js"; +import type { ExecEvent as ComputerdExecEvent } from "../exec/types.js"; import { createNodeVirtualFileSystem, type FUSEBackend, @@ -29,12 +29,14 @@ import { mountShim, type ShimMount } from "../shim/index.js"; import { installLogging } from "./logger.js"; // The compiled-in default port. esbuild's `define` substitutes the -// real value at SEA bundle time when WSD_DEFAULT_PORT is set on +// real value at SEA bundle time when COMPUTERD_DEFAULT_PORT is set on // the build host; tsc-only builds keep the literal 45678 below. // Runtime `PORT` env still wins over this default. -declare const __WSD_BUILD_DEFAULT_PORT__: number | undefined; +declare const __COMPUTERD_BUILD_DEFAULT_PORT__: number | undefined; const BUILD_DEFAULT_PORT: number | undefined = - typeof __WSD_BUILD_DEFAULT_PORT__ === "number" ? __WSD_BUILD_DEFAULT_PORT__ : undefined; + typeof __COMPUTERD_BUILD_DEFAULT_PORT__ === "number" + ? __COMPUTERD_BUILD_DEFAULT_PORT__ + : undefined; const DEFAULT_PORT = BUILD_DEFAULT_PORT ?? 45678; const DEFAULT_MOUNT_POINT = "/workspace"; const HOST = "0.0.0.0"; @@ -104,10 +106,10 @@ function requestPath(request: IncomingMessage): string { // Strip heartbeat events from a Runner stream before it reaches the // RPC layer. Heartbeats are a local observability signal only; the -// workspace-rpc wire contract carries only stdout, stderr, and exit. -function dropHeartbeats(stream: ReadableStream): ReadableStream { +// computer-rpc wire contract carries only stdout, stderr, and exit. +function dropHeartbeats(stream: ReadableStream): ReadableStream { return stream.pipeThrough( - new TransformStream({ + new TransformStream({ transform(event, controller) { if (event.name !== "heartbeat") { controller.enqueue(event as RpcExecEvent); @@ -117,7 +119,7 @@ function dropHeartbeats(stream: ReadableStream): ReadableStream { // biome-ignore lint/suspicious/noExplicitAny: small ad-hoc shape const out: Record = {}; @@ -163,7 +165,7 @@ interface HTTPHandle { } function createHTTPServer( - info: WSDInfo, + info: ComputerdInfo, rpc: ReturnType, getStats?: () => Record, ): HTTPHandle { @@ -231,7 +233,7 @@ function createHTTPServer( return; } - if (path === "/__wsd/stubs") { + if (path === "/__computerd/stubs") { if (!isStubTrackingEnabled()) { send(response, 404, "stub tracking disabled (set CAPNWEB_TRACK_STUBS=1)\n", { "content-type": "text/plain; charset=utf-8", @@ -245,7 +247,7 @@ function createHTTPServer( return; } - if (path === "/__wsd/stats") { + if (path === "/__computerd/stats") { const stats = getStats?.() ?? {}; const body = request.method === "HEAD" ? "" : JSON.stringify(stats); send(response, 200, body, { @@ -254,7 +256,7 @@ function createHTTPServer( return; } - if (path === "/__wsd/info") { + if (path === "/__computerd/info") { const body = request.method === "HEAD" ? "" : JSON.stringify(info); send(response, 200, body, { "content-type": "application/json; charset=utf-8", @@ -278,7 +280,7 @@ function createHTTPServer( // /ws — capnweb WebSocket endpoint. Long-lived, bidirectional, // streaming-friendly. The container's primary sync carrier. // perMessageDeflate compresses each WS frame with zlib. Defaults - // off in the `ws` package; we turn it on so wsd-to-wsd peers + // off in the `ws` package; we turn it on so computerd-to-computerd peers // (and any Node-side client that negotiates the extension) save // bytes on the wire. Clients that don't advertise the extension // negotiate down to plain frames, so no flag day for workerd or @@ -457,7 +459,7 @@ async function main(): Promise { // or macFUSE and falls back to the userspace shim. fuse / macfuse // require their respective real backend. shim forces the userspace // polling shim. none skips the mount entirely; HTTP + /api + /ws - // still come up so tests and tooling can talk to wsd's RPC surface. + // still come up so tests and tooling can talk to computerd's RPC surface. const fuseMountMode = parseFuseMountMode(process.env.FUSE_MOUNT); const backend: FUSEBackend = await resolveFuseBackend(fuseMountMode); console.log(`[info] FUSE_MOUNT=${fuseMountMode} resolved to backend=${backend.kind}`); @@ -477,7 +479,7 @@ async function main(): Promise { const { vfs, db, stopSync } = await createNodeVirtualFileSystem({ upstream: upstreamClient?.sync, }); - const info: WSDInfo = { backend, mountPoint, port }; + const info: ComputerdInfo = { backend, mountPoint, port }; let fuse: FuseMount | undefined; // When running on the userspace shim, capture the typed handle @@ -524,7 +526,7 @@ async function main(): Promise { ...(fuse !== undefined ? { cwd: mountPoint } : {}), ...(logMaxBytesOverride !== undefined ? { logMaxBytes: logMaxBytesOverride } : {}), }); - // Heartbeat events are wsd-local and must not cross the RPC boundary. + // Heartbeat events are computerd-local and must not cross the RPC boundary. // Wrap the runner so every exec/get stream drops heartbeat events before // they reach the wire. RunnerLike expects only stdout, stderr, and exit. const rpcRunner: RunnerLike = { @@ -605,7 +607,7 @@ async function main(): Promise { info.port = boundPort; const mountLabel = backend.kind === "none" ? "(disabled)" : mountPoint; console.log( - `wsd listening on ${HOST}:${boundPort} mount=${mountLabel} backend=${backend.kind}`, + `computerd listening on ${HOST}:${boundPort} mount=${mountLabel} backend=${backend.kind}`, ); resolve(); }); diff --git a/packages/wsd/src/cli/logger.test.ts b/packages/computerd/src/cli/logger.test.ts similarity index 86% rename from packages/wsd/src/cli/logger.test.ts rename to packages/computerd/src/cli/logger.test.ts index c469dcfb..8dcd0263 100644 --- a/packages/wsd/src/cli/logger.test.ts +++ b/packages/computerd/src/cli/logger.test.ts @@ -10,7 +10,7 @@ import { expect, onTestFinished, test } from "vitest"; // Pure logger primitives: a single appendToLogFile that's idempotent // over a file handle and a formatError that surfaces stack traces. // Wiring into console.{log,error} and into process.on('uncaughtException') -// lives in wsd.ts and is exercised end-to-end in wsd.test.ts; the unit +// lives in computerd.ts and is exercised end-to-end in computerd.test.ts; the unit // tests here pin the helper contract so a regression on either side // can be diagnosed without booting the daemon. @@ -35,8 +35,8 @@ test("formatLogEntry: prefixes with ISO timestamp and level", () => { }); test("createFileLogger: appends entries to the given path", async (_ctx) => { - const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "wsd-logger-")); - const logPath = path.join(dir, "wsd.log"); + const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "computerd-logger-")); + const logPath = path.join(dir, "computerd.log"); onTestFinished(() => fsp.rm(dir, { recursive: true, force: true })); const logger = createFileLogger(logPath); @@ -50,8 +50,8 @@ test("createFileLogger: appends entries to the given path", async (_ctx) => { }); test("createFileLogger: appends (does not truncate) when reopening", async (_ctx) => { - const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "wsd-logger-")); - const logPath = path.join(dir, "wsd.log"); + const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "computerd-logger-")); + const logPath = path.join(dir, "computerd.log"); onTestFinished(() => fsp.rm(dir, { recursive: true, force: true })); // Seed with a prior session. @@ -67,8 +67,8 @@ test("createFileLogger: appends (does not truncate) when reopening", async (_ctx }); test("createFileLogger: creates the file if it doesn't exist", async (_ctx) => { - const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "wsd-logger-")); - const logPath = path.join(dir, "nested/wsd.log"); + const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "computerd-logger-")); + const logPath = path.join(dir, "nested/computerd.log"); onTestFinished(() => fsp.rm(dir, { recursive: true, force: true })); const logger = createFileLogger(logPath); @@ -79,8 +79,8 @@ test("createFileLogger: creates the file if it doesn't exist", async (_ctx) => { }); test("installLogging: mirrors console.{log,error} into the log file", async (_t) => { - const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "wsd-logger-")); - const logPath = path.join(dir, "wsd.log"); + const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "computerd-logger-")); + const logPath = path.join(dir, "computerd.log"); onTestFinished(() => fsp.rm(dir, { recursive: true, force: true })); const teardown = installLogging(logPath); @@ -114,8 +114,8 @@ test("installLogging: uncaughtException handler writes to LOG_FILE before exit", // throws asynchronously to trigger uncaughtException. The exit // code should be 1 and the log file should contain the formatted // error. - const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "wsd-logger-")); - const logPath = path.join(dir, "wsd.log"); + const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "computerd-logger-")); + const logPath = path.join(dir, "computerd.log"); const scriptPath = path.join(dir, "crash.cjs"); onTestFinished(() => fsp.rm(dir, { recursive: true, force: true })); diff --git a/packages/wsd/src/cli/logger.ts b/packages/computerd/src/cli/logger.ts similarity index 95% rename from packages/wsd/src/cli/logger.ts rename to packages/computerd/src/cli/logger.ts index d3b5e4ae..235bc006 100644 --- a/packages/wsd/src/cli/logger.ts +++ b/packages/computerd/src/cli/logger.ts @@ -1,15 +1,15 @@ -// Minimal logger primitives for wsd. Two responsibilities: +// Minimal logger primitives for computerd. Two responsibilities: // // 1. formatLogEntry turns a console-shaped argv (mixed primitives, // objects, Errors) into a single line prefixed with an ISO // timestamp and a level tag. // 2. createFileLogger opens a file for append-only writes and -// exposes write(level, args) / close(). Wsd patches +// exposes write(level, args) / close(). Computerd patches // console.{log,error} on top of these so the daemon's stdout/ // stderr also lands in LOG_FILE when set. // // Kept in its own module so the contract is unit-testable without -// spawning the binary; cli/wsd.ts wires it up. +// spawning the binary; cli/computerd.ts wires it up. import { closeSync, mkdirSync, openSync, writeSync } from "node:fs"; import { dirname } from "node:path"; diff --git a/packages/wsd/src/exec/index.ts b/packages/computerd/src/exec/index.ts similarity index 100% rename from packages/wsd/src/exec/index.ts rename to packages/computerd/src/exec/index.ts diff --git a/packages/wsd/src/exec/log.ts b/packages/computerd/src/exec/log.ts similarity index 83% rename from packages/wsd/src/exec/log.ts rename to packages/computerd/src/exec/log.ts index bbf325c0..7583f6a5 100644 --- a/packages/wsd/src/exec/log.ts +++ b/packages/computerd/src/exec/log.ts @@ -44,13 +44,13 @@ interface EventRow { value: Uint8Array; } -// Create a new log row in wsd_exec_meta. Throws if a row with the +// Create a new log row in computerd_exec_meta. Throws if a row with the // same id already exists — the runner is responsible for disposing // stale records before spawning a recycled id. export function createLog(db: Database, id: string, opts: LogOptions): EventLog { const startedAt = opts.now(); db.run( - `INSERT INTO wsd_exec_meta (exec_id, started_at, bytes, evicted) VALUES (?, ?, 0, 0)`, + `INSERT INTO computerd_exec_meta (exec_id, started_at, bytes, evicted) VALUES (?, ?, 0, 0)`, id, startedAt, ); @@ -61,7 +61,7 @@ export function createLog(db: Database, id: string, opts: LogOptions): EventLog // row exists for the id (i.e. the exec was never spawned, or has // been disposed). export function openLog(db: Database, id: string, opts: LogOptions): EventLog | undefined { - const meta = db.one(`SELECT * FROM wsd_exec_meta WHERE exec_id = ?`, id); + const meta = db.one(`SELECT * FROM computerd_exec_meta WHERE exec_id = ?`, id); if (meta === undefined) return undefined; return new EventLog(db, id, opts); } @@ -81,7 +81,7 @@ export class EventLog { // the DB. For fresh logs the COALESCE returns 0. const lastSeq = this.db.scalar( - `SELECT COALESCE(MAX(seq), 0) AS s FROM wsd_exec_log WHERE exec_id = ?`, + `SELECT COALESCE(MAX(seq), 0) AS s FROM computerd_exec_log WHERE exec_id = ?`, id, ) ?? 0; this.nextSeq = lastSeq + 1; @@ -108,14 +108,14 @@ export class EventLog { } this.db.transactionSync(() => { this.db.run( - `INSERT INTO wsd_exec_log (exec_id, seq, ts, kind, value) VALUES (?, ?, ?, ?, ?)`, + `INSERT INTO computerd_exec_log (exec_id, seq, ts, kind, value) VALUES (?, ?, ?, ?, ?)`, this.id, seq, ts, kind, value, ); - this.db.run(`UPDATE wsd_exec_meta SET bytes = ? WHERE exec_id = ?`, newBytes, this.id); + this.db.run(`UPDATE computerd_exec_meta SET bytes = ? WHERE exec_id = ?`, newBytes, this.id); }); return seq; } @@ -138,7 +138,7 @@ export class EventLog { const buf = Buffer.alloc(4); buf.writeInt32LE(code, 0); this.db.run( - `INSERT INTO wsd_exec_log (exec_id, seq, ts, kind, value) VALUES (?, ?, ?, ?, ?)`, + `INSERT INTO computerd_exec_log (exec_id, seq, ts, kind, value) VALUES (?, ?, ?, ?, ?)`, this.id, seq, ts, @@ -147,7 +147,7 @@ export class EventLog { ); } this.db.run( - `UPDATE wsd_exec_meta SET exited_at = ?, exit_code = ? WHERE exec_id = ?`, + `UPDATE computerd_exec_meta SET exited_at = ?, exit_code = ? WHERE exec_id = ?`, ts, code, this.id, @@ -161,8 +161,8 @@ export class EventLog { // openLog() will return undefined for the id. dispose(): void { this.db.transactionSync(() => { - this.db.run(`DELETE FROM wsd_exec_log WHERE exec_id = ?`, this.id); - this.db.run(`DELETE FROM wsd_exec_meta WHERE exec_id = ?`, this.id); + this.db.run(`DELETE FROM computerd_exec_log WHERE exec_id = ?`, this.id); + this.db.run(`DELETE FROM computerd_exec_meta WHERE exec_id = ?`, this.id); }); } @@ -181,7 +181,7 @@ export class EventLog { } if (after === "tail") return; const rows = this.db.all( - `SELECT seq, kind, value FROM wsd_exec_log + `SELECT seq, kind, value FROM computerd_exec_log WHERE exec_id = ? AND seq > ? ORDER BY seq`, this.id, @@ -193,13 +193,16 @@ export class EventLog { } private meta(): MetaRow | undefined { - return this.db.one(`SELECT * FROM wsd_exec_meta WHERE exec_id = ?`, this.id); + return this.db.one(`SELECT * FROM computerd_exec_meta WHERE exec_id = ?`, this.id); } private evict(): void { this.db.transactionSync(() => { - this.db.run(`DELETE FROM wsd_exec_log WHERE exec_id = ?`, this.id); - this.db.run(`UPDATE wsd_exec_meta SET bytes = 0, evicted = 1 WHERE exec_id = ?`, this.id); + this.db.run(`DELETE FROM computerd_exec_log WHERE exec_id = ?`, this.id); + this.db.run( + `UPDATE computerd_exec_meta SET bytes = 0, evicted = 1 WHERE exec_id = ?`, + this.id, + ); }); } } diff --git a/packages/wsd/src/exec/runner.fuse.test.ts b/packages/computerd/src/exec/runner.fuse.test.ts similarity index 75% rename from packages/wsd/src/exec/runner.fuse.test.ts rename to packages/computerd/src/exec/runner.fuse.test.ts index 12dcc478..9a7fa5b5 100644 --- a/packages/wsd/src/exec/runner.fuse.test.ts +++ b/packages/computerd/src/exec/runner.fuse.test.ts @@ -1,32 +1,32 @@ -// Regression: wsd must not deadlock when shell.exec passes a cwd +// Regression: computerd must not deadlock when shell.exec passes a cwd // that lives inside its own FUSE mount. // // The shape of the bug, which `runner.ts` now defends against: // -// 1. wsd holds the FUSE session for MOUNT_POINT (e.g. /workspace). +// 1. computerd holds the FUSE session for MOUNT_POINT (e.g. /workspace). // 2. Runner.exec calls Node's child_process.spawn. libuv's // uv_spawn does fork() + chdir(cwd) + execve() in the child // while the parent blocks on a status pipe. -// 3. If the child's chdir target is inside wsd's own FUSE mount, +// 3. If the child's chdir target is inside computerd's own FUSE mount, // the kernel issues a FUSE LOOKUP that needs to be answered -// by wsd's event loop. -// 4. wsd's event loop is blocked in uv_spawn waiting for the +// by computerd's event loop. +// 4. computerd's event loop is blocked in uv_spawn waiting for the // child's status, so the callback can never run. Deadlock. // // The fix: don't pass `cwd` to spawn at all. Pre-flight the path // via dofs's `stat` (reads SQLite directly, no FUSE callback) for // the historical ENOENT-cwd contract; then prefix the user's // command with `cd && exec ` so the shell does the -// chdir after the spawn dance has returned and wsd's event loop +// chdir after the spawn dance has returned and computerd's event loop // is responsive again. // // Running this test requires: // - Docker with /dev/fuse, --privileged, SYS_ADMIN (DinD or -// native Linux). It boots the linux-x64 wsd SEA binary from -// artifacts/wsd/ in a privileged container so the kernel FUSE +// native Linux). It boots the linux-x64 computerd SEA binary from +// artifacts/computerd/ in a privileged container so the kernel FUSE // driver is actually in the loop. -// - The wsd binary built and staged at the path -// packages/workspace/test-harness/run-wsd.sh expects. The +// - The computerd binary built and staged at the path +// packages/computer/test-harness/run-computerd.sh expects. The // test skips itself when either prerequisite is missing so // contributors without docker still see green. @@ -35,13 +35,13 @@ import { accessSync } from "node:fs"; import { join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { createWorkspaceClient } from "@cloudflare/workspace-rpc/client"; +import { createWorkspaceClient } from "@cloudflare/computer-rpc/client"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { WebSocket } from "ws"; const REPO_ROOT = resolve(fileURLToPath(import.meta.url), "../../../../.."); -const WSD_BINARY = join(REPO_ROOT, "artifacts/wsd/wsd-linux-x64"); -const RUN_WSD_SCRIPT = join(REPO_ROOT, "packages/workspace/test-harness/run-wsd.sh"); +const COMPUTERD_BINARY = join(REPO_ROOT, "artifacts/computerd/computerd-linux-x64"); +const RUN_COMPUTERD_SCRIPT = join(REPO_ROOT, "packages/computer/test-harness/run-computerd.sh"); function dockerAvailable(): boolean { try { @@ -54,7 +54,7 @@ function dockerAvailable(): boolean { function binaryAvailable(): boolean { try { - accessSync(WSD_BINARY); + accessSync(COMPUTERD_BINARY); return true; } catch { return false; @@ -69,25 +69,25 @@ describeIfReal("Runner shell.exec under real FUSE", () => { let url: string | undefined; beforeAll(async () => { - // Boot a real-FUSE wsd container via the existing harness + // Boot a real-FUSE computerd container via the existing harness // helper. The helper prints the URL on stdout and the - // container id on stderr (WSD_HARNESS_CID=...). - const proc = spawn("bash", [RUN_WSD_SCRIPT], { - env: { ...process.env, WSD_HARNESS_PORT: "0" }, + // container id on stderr (COMPUTERD_HARNESS_CID=...). + const proc = spawn("bash", [RUN_COMPUTERD_SCRIPT], { + env: { ...process.env, COMPUTERD_HARNESS_PORT: "0" }, stdio: ["ignore", "pipe", "pipe"], }); if (!proc.stdout || !proc.stderr) { - throw new Error("run-wsd.sh did not expose stdout/stderr pipes"); + throw new Error("run-computerd.sh did not expose stdout/stderr pipes"); } const stdout = await drainStream(proc.stdout); const stderr = await drainStream(proc.stderr); await waitForExit(proc); if (proc.exitCode !== 0) { - throw new Error(`run-wsd.sh exited ${proc.exitCode}: ${stderr}`); + throw new Error(`run-computerd.sh exited ${proc.exitCode}: ${stderr}`); } url = stdout.trim(); - const match = stderr.match(/WSD_HARNESS_CID=([0-9a-f]+)/); + const match = stderr.match(/COMPUTERD_HARNESS_CID=([0-9a-f]+)/); if (!match) throw new Error(`could not parse container id from stderr: ${stderr}`); cid = match[1]; }, 90_000); @@ -105,13 +105,13 @@ describeIfReal("Runner shell.exec under real FUSE", () => { }); test("exec(cwd inside the FUSE mount) returns quickly and runs the command", async () => { - if (!url) throw new Error("wsd container did not start"); + if (!url) throw new Error("computerd container did not start"); const client = createWorkspaceClient({ url: `${url.replace(/^http(s?):\/\//, "ws$1://")}/ws`, WebSocketImpl: WebSocket, }); try { - // Pre-fix this hangs forever and wedges the wsd event loop. + // Pre-fix this hangs forever and wedges the computerd event loop. // The 5s ceiling is well under that and well above the // ~100ms a healthy exec takes locally, so a regression // shows up as a clean failure rather than a CI-wide stall. diff --git a/packages/wsd/src/exec/runner.test.ts b/packages/computerd/src/exec/runner.test.ts similarity index 98% rename from packages/wsd/src/exec/runner.test.ts rename to packages/computerd/src/exec/runner.test.ts index 14d177e6..149b54b9 100644 --- a/packages/wsd/src/exec/runner.test.ts +++ b/packages/computerd/src/exec/runner.test.ts @@ -276,8 +276,8 @@ test("exit-event surfaces an error on the subscriber when setExit throws", async const handle = runner.exec("sleep 0.1", { id: "setexit-throws" }); // Drop the meta row out from under the runner. setExit() will // throw 'setExit after dispose' when the child exits. - db.run("DELETE FROM wsd_exec_meta WHERE exec_id = ?", "setexit-throws"); - db.run("DELETE FROM wsd_exec_log WHERE exec_id = ?", "setexit-throws"); + db.run("DELETE FROM computerd_exec_meta WHERE exec_id = ?", "setexit-throws"); + db.run("DELETE FROM computerd_exec_log WHERE exec_id = ?", "setexit-throws"); let caught: unknown; const reader = handle.events.getReader(); @@ -355,7 +355,7 @@ test("exec(cwd) quotes path segments with spaces and single quotes", async () => // it, and mkdir the matching entry into the dofs DB so the // pre-flight passes. const { runner, fs, dispose } = fixture(); - const tricky = "/tmp/wsd test 'quoted' dir"; + const tricky = "/tmp/computerd test 'quoted' dir"; try { const { mkdirSync } = await import("node:fs"); mkdirSync(tricky, { recursive: true }); diff --git a/packages/wsd/src/exec/runner.ts b/packages/computerd/src/exec/runner.ts similarity index 97% rename from packages/wsd/src/exec/runner.ts rename to packages/computerd/src/exec/runner.ts index c6743d26..ab7fb148 100644 --- a/packages/wsd/src/exec/runner.ts +++ b/packages/computerd/src/exec/runner.ts @@ -64,7 +64,7 @@ const DEFAULTS = { export interface RunnerInit extends RunnerOptions { db: Database; - // Clear any orphaned exec rows from a previous wsd process. + // Clear any orphaned exec rows from a previous computerd process. // Defaults true; tests can opt out. resetSchema?: boolean; } @@ -116,7 +116,7 @@ export class Runner { // vfs_dirents in SQLite directly — no node:fs.statSync, no // FUSE callback. Preserves the historical ENOENT-cwd error // shape callers see when the path doesn't exist, without - // taking a route that could deadlock against wsd's own FUSE + // taking a route that could deadlock against computerd's own FUSE // server. if (cwd !== undefined) { try { @@ -127,11 +127,11 @@ export class Runner { } // Don't pass cwd to spawn. libuv's uv_spawn does fork + // chdir + execve in the child while the parent blocks on a - // status pipe. If cwd lives inside wsd's own FUSE mount, the - // child's chdir issues a FUSE LOOKUP that wsd can't service + // status pipe. If cwd lives inside computerd's own FUSE mount, the + // child's chdir issues a FUSE LOOKUP that computerd can't service // (its event loop is stuck in uv_spawn), and the whole // process deadlocks. Have /bin/sh do the chdir instead: by - // the time the shell runs its `cd`, wsd's event loop is back + // the time the shell runs its `cd`, computerd's event loop is back // and can answer the FUSE callback normally. // // No `exec` prefix on the user command: the user's argument @@ -319,14 +319,14 @@ export class Runner { } // Also reap orphan rows whose in-memory record is gone. this.db.run( - `DELETE FROM wsd_exec_log WHERE exec_id IN ( - SELECT exec_id FROM wsd_exec_meta + `DELETE FROM computerd_exec_log WHERE exec_id IN ( + SELECT exec_id FROM computerd_exec_meta WHERE exited_at IS NOT NULL AND exited_at <= ? )`, cutoff, ); this.db.run( - `DELETE FROM wsd_exec_meta + `DELETE FROM computerd_exec_meta WHERE exited_at IS NOT NULL AND exited_at <= ?`, cutoff, ); diff --git a/packages/wsd/src/exec/schema.ts b/packages/computerd/src/exec/schema.ts similarity index 70% rename from packages/wsd/src/exec/schema.ts rename to packages/computerd/src/exec/schema.ts index 8bc723f6..8f9d1ea1 100644 --- a/packages/wsd/src/exec/schema.ts +++ b/packages/computerd/src/exec/schema.ts @@ -1,6 +1,6 @@ -// wsd-owned tables in the same SQLite DB as dofs. +// computerd-owned tables in the same SQLite DB as dofs. // -// The exec log is a wsd runtime concern, not a dofs +// The exec log is a computerd runtime concern, not a dofs // storage concern, so the schema lives here. CREATE TABLE IF NOT // EXISTS keeps it idempotent across restarts; the runner calls // initializeExecSchema() once at construction. @@ -10,11 +10,11 @@ import type { Database } from "@cloudflare/dofs"; // Single table, middle-ground shape: one row per event, kind // discriminates stdout/stderr/exit, value // holds the raw bytes (or a 4-byte LE exit code for kind=2). An -// auxiliary `wsd_exec_meta` row sticks around after eviction so +// auxiliary `computerd_exec_meta` row sticks around after eviction so // replay() can distinguish ELOG_TRUNCATED from ENOENT. export function initializeExecSchema(db: Database): void { db.run(` - CREATE TABLE IF NOT EXISTS wsd_exec_log ( + CREATE TABLE IF NOT EXISTS computerd_exec_log ( exec_id TEXT NOT NULL, seq INTEGER NOT NULL, ts INTEGER NOT NULL, @@ -24,7 +24,7 @@ export function initializeExecSchema(db: Database): void { ) WITHOUT ROWID `); db.run(` - CREATE TABLE IF NOT EXISTS wsd_exec_meta ( + CREATE TABLE IF NOT EXISTS computerd_exec_meta ( exec_id TEXT PRIMARY KEY, started_at INTEGER NOT NULL, exited_at INTEGER, @@ -37,16 +37,16 @@ export function initializeExecSchema(db: Database): void { // exited_at predates the cutoff". With workloads of a few dozen // active execs this is overkill, but it's free. db.run(` - CREATE INDEX IF NOT EXISTS wsd_exec_meta_exited_at - ON wsd_exec_meta (exited_at) + CREATE INDEX IF NOT EXISTS computerd_exec_meta_exited_at + ON computerd_exec_meta (exited_at) WHERE exited_at IS NOT NULL `); } -// Wipe any state left behind by a previous wsd process. No exec +// Wipe any state left behind by a previous computerd process. No exec // survives a restart (the children are gone), so the safe // behaviour is to clear the slate. export function clearExecState(db: Database): void { - db.run(`DELETE FROM wsd_exec_log`); - db.run(`DELETE FROM wsd_exec_meta`); + db.run(`DELETE FROM computerd_exec_log`); + db.run(`DELETE FROM computerd_exec_meta`); } diff --git a/packages/wsd/src/exec/types.ts b/packages/computerd/src/exec/types.ts similarity index 96% rename from packages/wsd/src/exec/types.ts rename to packages/computerd/src/exec/types.ts index 573906b2..49fc2d99 100644 --- a/packages/wsd/src/exec/types.ts +++ b/packages/computerd/src/exec/types.ts @@ -1,6 +1,6 @@ // Wire-compatible shapes for the exec surface. Mirrors docs/08 -// (ExecEvent) and docs/05 (ExecHandle) but stays internal to wsd -// until the workspace-rpc surface (E2) picks them up. +// (ExecEvent) and docs/05 (ExecHandle) but stays internal to computerd +// until the computer-rpc surface (E2) picks them up. // // Every event carries a per-id monotonic `seq`. Resume math // (`getExec({ after })`) is integer comparisons — a monotonic diff --git a/packages/wsd/src/fuse/backend.test.ts b/packages/computerd/src/fuse/backend.test.ts similarity index 100% rename from packages/wsd/src/fuse/backend.test.ts rename to packages/computerd/src/fuse/backend.test.ts diff --git a/packages/wsd/src/fuse/backend.ts b/packages/computerd/src/fuse/backend.ts similarity index 100% rename from packages/wsd/src/fuse/backend.ts rename to packages/computerd/src/fuse/backend.ts diff --git a/packages/wsd/src/fuse/driver.test.ts b/packages/computerd/src/fuse/driver.test.ts similarity index 100% rename from packages/wsd/src/fuse/driver.test.ts rename to packages/computerd/src/fuse/driver.test.ts diff --git a/packages/wsd/src/fuse/driver.ts b/packages/computerd/src/fuse/driver.ts similarity index 98% rename from packages/wsd/src/fuse/driver.ts rename to packages/computerd/src/fuse/driver.ts index c4c993be..afefd202 100644 --- a/packages/wsd/src/fuse/driver.ts +++ b/packages/computerd/src/fuse/driver.ts @@ -442,7 +442,7 @@ export function makeFUSEOps(vfs: NodeVirtualFileSystem, mountPoint = "/"): FuseO return (...args: unknown[]) => { if (!warnedOperations.has(operation)) { warnedOperations.add(operation); - console.warn(`wsd: FUSE op ${operation} not implemented; returning ENOSYS`); + console.warn(`computerd: FUSE op ${operation} not implemented; returning ENOSYS`); } const cb = args[args.length - 1]; if (typeof cb === "function") { @@ -964,12 +964,12 @@ export async function mountFuse(options: { // biome-ignore lint/suspicious/noExplicitAny: fuse-native ships no types const fuseModule: any = await import("fuse-native"); const Fuse = fuseModule.default ?? fuseModule; - // Optional op tracing. `WSD_FUSE_TRACE=summary` records per-op call + // Optional op tracing. `COMPUTERD_FUSE_TRACE=summary` records per-op call // counts and timings; the summary is emitted on SIGUSR2 and on - // unmount, to stderr by default or to `WSD_FUSE_TRACE_FILE` when set. + // unmount, to stderr by default or to `COMPUTERD_FUSE_TRACE_FILE` when set. // Disabled means zero wrapping overhead — the unwrapped ops object // is handed to fuse-native directly. - const traceMode = process.env.WSD_FUSE_TRACE; + const traceMode = process.env.COMPUTERD_FUSE_TRACE; const tracer: FuseTracer | undefined = traceMode === "summary" ? createFuseTracer() : undefined; const baseOps = makeFUSEOps(options.vfs, options.mountPoint); const { getBufferStats: _getBufferStats, ...fuseOps } = baseOps; @@ -985,18 +985,18 @@ export async function mountFuse(options: { const emitTrace = (reason: string): void => { if (tracer === undefined) return; const json = tracer.formatJson(); - const target = process.env.WSD_FUSE_TRACE_FILE; + const target = process.env.COMPUTERD_FUSE_TRACE_FILE; if (target !== undefined && target !== "") { try { nodeWriteFileSync(target, `${json}\n`); } catch (error) { process.stderr.write( - `wsd: failed to write FUSE trace to ${target} (${reason}): ${String(error)}\n${json}\n`, + `computerd: failed to write FUSE trace to ${target} (${reason}): ${String(error)}\n${json}\n`, ); } return; } - process.stderr.write(`wsd: FUSE trace (${reason})\n${json}\n`); + process.stderr.write(`computerd: FUSE trace (${reason})\n${json}\n`); }; if (tracer !== undefined) { @@ -1007,7 +1007,7 @@ export async function mountFuse(options: { // through opts, so monkey-patch _fuseOptions() to append them. big_writes // lets the kernel batch up to max_write bytes per FUSE op instead of the // default 4 KiB, cutting per-op round-trips ~32x on large sequential I/O. - // buildFuseOptionString reads WSD_FUSE_* env vars; with none set it + // buildFuseOptionString reads COMPUTERD_FUSE_* env vars; with none set it // emits the production-safe profile (auto_cache plus one-second // metadata timeouts) backed by the auto_cache contract tests. const origFuseOptions = fuse._fuseOptions.bind(fuse); diff --git a/packages/wsd/src/fuse/fuse-native.d.ts b/packages/computerd/src/fuse/fuse-native.d.ts similarity index 100% rename from packages/wsd/src/fuse/fuse-native.d.ts rename to packages/computerd/src/fuse/fuse-native.d.ts diff --git a/packages/wsd/src/fuse/index.ts b/packages/computerd/src/fuse/index.ts similarity index 100% rename from packages/wsd/src/fuse/index.ts rename to packages/computerd/src/fuse/index.ts diff --git a/packages/wsd/src/fuse/options.test.ts b/packages/computerd/src/fuse/options.test.ts similarity index 71% rename from packages/wsd/src/fuse/options.test.ts rename to packages/computerd/src/fuse/options.test.ts index 7ab84f20..40a4b29e 100644 --- a/packages/wsd/src/fuse/options.test.ts +++ b/packages/computerd/src/fuse/options.test.ts @@ -26,8 +26,8 @@ describe("buildFuseOptionString", () => { test("overrides max_read and max_write from the environment", () => { const out = buildFuseOptionString({ - WSD_FUSE_MAX_READ: "1048576", - WSD_FUSE_MAX_WRITE: "1048576", + COMPUTERD_FUSE_MAX_READ: "1048576", + COMPUTERD_FUSE_MAX_WRITE: "1048576", }); expect(out).toContain("max_write=1048576"); expect(out).toContain("max_read=1048576"); @@ -35,54 +35,56 @@ describe("buildFuseOptionString", () => { }); test("ignores non-numeric size overrides and falls back to the default", () => { - const out = buildFuseOptionString({ WSD_FUSE_MAX_READ: "wat" }); + const out = buildFuseOptionString({ COMPUTERD_FUSE_MAX_READ: "wat" }); expect(out).toContain("max_read=524288"); }); test("rejects non-positive sizes", () => { - const a = buildFuseOptionString({ WSD_FUSE_MAX_READ: "0" }); + const a = buildFuseOptionString({ COMPUTERD_FUSE_MAX_READ: "0" }); expect(a).toContain("max_read=524288"); - const b = buildFuseOptionString({ WSD_FUSE_MAX_WRITE: "-1" }); + const b = buildFuseOptionString({ COMPUTERD_FUSE_MAX_WRITE: "-1" }); expect(b).toContain("max_write=524288"); }); - test("keeps auto_cache when WSD_FUSE_AUTO_CACHE is truthy or unset", () => { - expect(buildFuseOptionString({ WSD_FUSE_AUTO_CACHE: "1" })).toContain("auto_cache"); - expect(buildFuseOptionString({ WSD_FUSE_AUTO_CACHE: "true" })).toContain("auto_cache"); + test("keeps auto_cache when COMPUTERD_FUSE_AUTO_CACHE is truthy or unset", () => { + expect(buildFuseOptionString({ COMPUTERD_FUSE_AUTO_CACHE: "1" })).toContain("auto_cache"); + expect(buildFuseOptionString({ COMPUTERD_FUSE_AUTO_CACHE: "true" })).toContain("auto_cache"); expect(buildFuseOptionString(empty)).toContain("auto_cache"); }); - test("drops auto_cache when WSD_FUSE_AUTO_CACHE is explicitly disabled", () => { + test("drops auto_cache when COMPUTERD_FUSE_AUTO_CACHE is explicitly disabled", () => { // Operators who hit a correctness issue under auto_cache need // a way to turn it off without rebuilding. Explicit "0" / // "false" / "no" / "off" disables it; an empty string is // treated the same way because that's what an unset env var // looks like once it reaches the shell. - expect(buildFuseOptionString({ WSD_FUSE_AUTO_CACHE: "0" })).not.toContain("auto_cache"); - expect(buildFuseOptionString({ WSD_FUSE_AUTO_CACHE: "false" })).not.toContain("auto_cache"); - expect(buildFuseOptionString({ WSD_FUSE_AUTO_CACHE: "" })).not.toContain("auto_cache"); + expect(buildFuseOptionString({ COMPUTERD_FUSE_AUTO_CACHE: "0" })).not.toContain("auto_cache"); + expect(buildFuseOptionString({ COMPUTERD_FUSE_AUTO_CACHE: "false" })).not.toContain( + "auto_cache", + ); + expect(buildFuseOptionString({ COMPUTERD_FUSE_AUTO_CACHE: "" })).not.toContain("auto_cache"); }); test("drops metadata timeouts when explicitly disabled with zero", () => { // Same opt-out story as auto_cache. "0" or empty turns the // default off; anything positive overrides the default value. const off = buildFuseOptionString({ - WSD_FUSE_ATTR_TIMEOUT: "", - WSD_FUSE_ENTRY_TIMEOUT: "", - WSD_FUSE_AC_ATTR_TIMEOUT: "", + COMPUTERD_FUSE_ATTR_TIMEOUT: "", + COMPUTERD_FUSE_ENTRY_TIMEOUT: "", + COMPUTERD_FUSE_AC_ATTR_TIMEOUT: "", }); expect(off).not.toContain("attr_timeout"); expect(off).not.toContain("entry_timeout"); expect(off).not.toContain("ac_attr_timeout"); }); - test("WSD_FUSE_KERNEL_CACHE=1 alone loses to the default auto_cache", () => { + test("COMPUTERD_FUSE_KERNEL_CACHE=1 alone loses to the default auto_cache", () => { // The auto_cache default is sticky. Setting only kernel_cache // looks like asking for both, and the mutual-exclusion rule // resolves in favor of auto_cache. Operators who want the // fast / single-writer profile must also disable auto_cache, // which the next test pins. - const out = buildFuseOptionString({ WSD_FUSE_KERNEL_CACHE: "1" }); + const out = buildFuseOptionString({ COMPUTERD_FUSE_KERNEL_CACHE: "1" }); expect(out).toContain("auto_cache"); expect(out).not.toContain("kernel_cache"); }); @@ -94,8 +96,8 @@ describe("buildFuseOptionString", () => { // both is a configuration mistake. We prefer the safer (auto_cache) // option and warn-by-fact: the option string will say auto_cache only. const out = buildFuseOptionString({ - WSD_FUSE_AUTO_CACHE: "1", - WSD_FUSE_KERNEL_CACHE: "1", + COMPUTERD_FUSE_AUTO_CACHE: "1", + COMPUTERD_FUSE_KERNEL_CACHE: "1", }); expect(out).toContain("auto_cache"); expect(out).not.toContain("kernel_cache"); @@ -108,8 +110,8 @@ describe("buildFuseOptionString", () => { // and silently fall back to the safer default, defeating the // intent. Document the working incantation here. const out = buildFuseOptionString({ - WSD_FUSE_AUTO_CACHE: "0", - WSD_FUSE_KERNEL_CACHE: "1", + COMPUTERD_FUSE_AUTO_CACHE: "0", + COMPUTERD_FUSE_KERNEL_CACHE: "1", }); expect(out).toContain("kernel_cache"); expect(out).not.toContain("auto_cache"); @@ -117,10 +119,10 @@ describe("buildFuseOptionString", () => { test("overrides default metadata timeouts when explicit values are set", () => { const out = buildFuseOptionString({ - WSD_FUSE_ATTR_TIMEOUT: "5", - WSD_FUSE_ENTRY_TIMEOUT: "4", - WSD_FUSE_NEGATIVE_TIMEOUT: "2", - WSD_FUSE_AC_ATTR_TIMEOUT: "3", + COMPUTERD_FUSE_ATTR_TIMEOUT: "5", + COMPUTERD_FUSE_ENTRY_TIMEOUT: "4", + COMPUTERD_FUSE_NEGATIVE_TIMEOUT: "2", + COMPUTERD_FUSE_AC_ATTR_TIMEOUT: "3", }); expect(out).toContain("attr_timeout=5"); expect(out).toContain("entry_timeout=4"); @@ -129,28 +131,28 @@ describe("buildFuseOptionString", () => { }); test("accepts fractional timeouts because libfuse documents them that way", () => { - const out = buildFuseOptionString({ WSD_FUSE_ATTR_TIMEOUT: "0.5" }); + const out = buildFuseOptionString({ COMPUTERD_FUSE_ATTR_TIMEOUT: "0.5" }); expect(out).toContain("attr_timeout=0.5"); }); test("rejects non-numeric timeouts", () => { // The substring "attr_timeout=" with the equals sign avoids // matching the ac_attr_timeout default that still emits. - const out = buildFuseOptionString({ WSD_FUSE_ATTR_TIMEOUT: "nope" }); + const out = buildFuseOptionString({ COMPUTERD_FUSE_ATTR_TIMEOUT: "nope" }); expect(out).not.toMatch(/(?:^|,)attr_timeout=/); }); test("rejects negative timeouts", () => { - const out = buildFuseOptionString({ WSD_FUSE_ATTR_TIMEOUT: "-1" }); + const out = buildFuseOptionString({ COMPUTERD_FUSE_ATTR_TIMEOUT: "-1" }); expect(out).not.toMatch(/(?:^|,)attr_timeout=/); }); - test("appends WSD_FUSE_EXTRA_OPTS verbatim for last-resort experimentation", () => { + test("appends COMPUTERD_FUSE_EXTRA_OPTS verbatim for last-resort experimentation", () => { const out = buildFuseOptionString({ - WSD_FUSE_EXTRA_OPTS: "use_ino,fsname=wsd", + COMPUTERD_FUSE_EXTRA_OPTS: "use_ino,fsname=computerd", }); expect(out).toContain("use_ino"); - expect(out).toContain("fsname=wsd"); + expect(out).toContain("fsname=computerd"); }); test("does not emit writeback_cache even with EXTRA_OPTS asking for it", () => { @@ -160,15 +162,21 @@ describe("buildFuseOptionString", () => { // Strip it defensively so a typo in EXTRA_OPTS doesn't take the // daemon down. const out = buildFuseOptionString({ - WSD_FUSE_EXTRA_OPTS: "writeback_cache,use_ino", + COMPUTERD_FUSE_EXTRA_OPTS: "writeback_cache,use_ino", }); expect(out).not.toContain("writeback_cache"); expect(out).toContain("use_ino"); }); test("returns the same options regardless of env var order", () => { - const a = buildFuseOptionString({ WSD_FUSE_AUTO_CACHE: "1", WSD_FUSE_ATTR_TIMEOUT: "1" }); - const b = buildFuseOptionString({ WSD_FUSE_ATTR_TIMEOUT: "1", WSD_FUSE_AUTO_CACHE: "1" }); + const a = buildFuseOptionString({ + COMPUTERD_FUSE_AUTO_CACHE: "1", + COMPUTERD_FUSE_ATTR_TIMEOUT: "1", + }); + const b = buildFuseOptionString({ + COMPUTERD_FUSE_ATTR_TIMEOUT: "1", + COMPUTERD_FUSE_AUTO_CACHE: "1", + }); expect(a).toBe(b); }); }); diff --git a/packages/wsd/src/fuse/options.ts b/packages/computerd/src/fuse/options.ts similarity index 79% rename from packages/wsd/src/fuse/options.ts rename to packages/computerd/src/fuse/options.ts index 630f434a..b86a2b08 100644 --- a/packages/wsd/src/fuse/options.ts +++ b/packages/computerd/src/fuse/options.ts @@ -21,13 +21,13 @@ // the dofs CHUNK_SIZE so a single FUSE read maps to a single chunk // fetch instead of four 128 KiB slices of the same blob. // -// Every default is opt-out via the matching WSD_FUSE_* env var. +// Every default is opt-out via the matching COMPUTERD_FUSE_* env var. // Setting an option to "0", "false", "no", "off", or "" turns it // off; a positive value overrides the default. // // Disallowed options (writeback_cache) are stripped defensively because // libfuse 2.9 fails the whole mount with "unknown option" when it sees -// them. A typo in WSD_FUSE_EXTRA_OPTS shouldn't take the daemon down. +// them. A typo in COMPUTERD_FUSE_EXTRA_OPTS shouldn't take the daemon down. // 512 KiB matches the dofs CHUNK_SIZE so a single FUSE read maps to a // single chunk fetch. Earlier defaults at 128 KiB issued four reads @@ -45,15 +45,15 @@ const DEFAULT_NEGATIVE_TIMEOUT = "0"; const DISALLOWED_OPTS = new Set(["writeback_cache"]); export interface FuseOptionEnv { - WSD_FUSE_MAX_READ?: string; - WSD_FUSE_MAX_WRITE?: string; - WSD_FUSE_AUTO_CACHE?: string; - WSD_FUSE_KERNEL_CACHE?: string; - WSD_FUSE_ATTR_TIMEOUT?: string; - WSD_FUSE_ENTRY_TIMEOUT?: string; - WSD_FUSE_NEGATIVE_TIMEOUT?: string; - WSD_FUSE_AC_ATTR_TIMEOUT?: string; - WSD_FUSE_EXTRA_OPTS?: string; + COMPUTERD_FUSE_MAX_READ?: string; + COMPUTERD_FUSE_MAX_WRITE?: string; + COMPUTERD_FUSE_AUTO_CACHE?: string; + COMPUTERD_FUSE_KERNEL_CACHE?: string; + COMPUTERD_FUSE_ATTR_TIMEOUT?: string; + COMPUTERD_FUSE_ENTRY_TIMEOUT?: string; + COMPUTERD_FUSE_NEGATIVE_TIMEOUT?: string; + COMPUTERD_FUSE_AC_ATTR_TIMEOUT?: string; + COMPUTERD_FUSE_EXTRA_OPTS?: string; } /** @@ -64,8 +64,8 @@ export interface FuseOptionEnv { export function buildFuseOptionString(env: FuseOptionEnv): string { const opts: string[] = ["big_writes", "use_ino"]; - const maxWrite = parsePositiveInt(env.WSD_FUSE_MAX_WRITE) ?? DEFAULT_MAX_WRITE; - const maxRead = parsePositiveInt(env.WSD_FUSE_MAX_READ) ?? DEFAULT_MAX_READ; + const maxWrite = parsePositiveInt(env.COMPUTERD_FUSE_MAX_WRITE) ?? DEFAULT_MAX_WRITE; + const maxRead = parsePositiveInt(env.COMPUTERD_FUSE_MAX_READ) ?? DEFAULT_MAX_READ; opts.push(`max_write=${maxWrite}`); opts.push(`max_read=${maxRead}`); @@ -76,20 +76,25 @@ export function buildFuseOptionString(env: FuseOptionEnv): string { // invalidates. The driver could warn here, but the option string // itself is the durable artifact operators inspect when chasing // misconfiguration, so just emit the safer one and move on. - const autoCache = parseBoolWithDefault(env.WSD_FUSE_AUTO_CACHE, DEFAULT_AUTO_CACHE); - const kernelCache = parseBoolWithDefault(env.WSD_FUSE_KERNEL_CACHE, false); + const autoCache = parseBoolWithDefault(env.COMPUTERD_FUSE_AUTO_CACHE, DEFAULT_AUTO_CACHE); + const kernelCache = parseBoolWithDefault(env.COMPUTERD_FUSE_KERNEL_CACHE, false); if (autoCache) { opts.push("auto_cache"); } else if (kernelCache) { opts.push("kernel_cache"); } - pushTimeout(opts, "attr_timeout", env.WSD_FUSE_ATTR_TIMEOUT, DEFAULT_ATTR_TIMEOUT); - pushTimeout(opts, "entry_timeout", env.WSD_FUSE_ENTRY_TIMEOUT, DEFAULT_ENTRY_TIMEOUT); - pushTimeout(opts, "negative_timeout", env.WSD_FUSE_NEGATIVE_TIMEOUT, DEFAULT_NEGATIVE_TIMEOUT); - pushTimeout(opts, "ac_attr_timeout", env.WSD_FUSE_AC_ATTR_TIMEOUT, DEFAULT_AC_ATTR_TIMEOUT); + pushTimeout(opts, "attr_timeout", env.COMPUTERD_FUSE_ATTR_TIMEOUT, DEFAULT_ATTR_TIMEOUT); + pushTimeout(opts, "entry_timeout", env.COMPUTERD_FUSE_ENTRY_TIMEOUT, DEFAULT_ENTRY_TIMEOUT); + pushTimeout( + opts, + "negative_timeout", + env.COMPUTERD_FUSE_NEGATIVE_TIMEOUT, + DEFAULT_NEGATIVE_TIMEOUT, + ); + pushTimeout(opts, "ac_attr_timeout", env.COMPUTERD_FUSE_AC_ATTR_TIMEOUT, DEFAULT_AC_ATTR_TIMEOUT); - const extra = env.WSD_FUSE_EXTRA_OPTS; + const extra = env.COMPUTERD_FUSE_EXTRA_OPTS; if (extra !== undefined && extra !== "") { for (const part of extra.split(",")) { const trimmed = part.trim(); @@ -121,7 +126,7 @@ function parseNonNegativeNumber(value: string | undefined): number | undefined { // (undefined) returns the default; an explicit "0" / "false" / "no" / // "off" / "" turns the option off; anything else turns it on. The // asymmetry between unset and empty matters: an operator who shells -// out `WSD_FUSE_AUTO_CACHE=` expects to disable the option, and that +// out `COMPUTERD_FUSE_AUTO_CACHE=` expects to disable the option, and that // reaches this function as the empty string. function parseBoolWithDefault(value: string | undefined, fallback: boolean): boolean { if (value === undefined) return fallback; diff --git a/packages/wsd/src/fuse/tracer.test.ts b/packages/computerd/src/fuse/tracer.test.ts similarity index 100% rename from packages/wsd/src/fuse/tracer.test.ts rename to packages/computerd/src/fuse/tracer.test.ts diff --git a/packages/wsd/src/fuse/tracer.ts b/packages/computerd/src/fuse/tracer.ts similarity index 97% rename from packages/wsd/src/fuse/tracer.ts rename to packages/computerd/src/fuse/tracer.ts index 8f434588..12cc838f 100644 --- a/packages/wsd/src/fuse/tracer.ts +++ b/packages/computerd/src/fuse/tracer.ts @@ -2,7 +2,7 @@ // // Wraps each FUSE op so we can count calls, count errors, and measure // per-op wall time. Output is JSON, dumped to stderr (or a file via -// `WSD_FUSE_TRACE_FILE`) on SIGUSR2 and on unmount, or whenever an +// `COMPUTERD_FUSE_TRACE_FILE`) on SIGUSR2 and on unmount, or whenever an // operator calls `tracer.formatJson()`. // // The aggregator (`summarizeFuseTrace`) is pure so it can be unit @@ -12,7 +12,7 @@ // makes double-wrapping a no-op so tests and accidental rewrapping // stay cheap. -const WRAPPED = Symbol("wsd.fuseTracerWrapped"); +const WRAPPED = Symbol("computerd.fuseTracerWrapped"); export interface FuseOpStats { count: number; @@ -46,7 +46,7 @@ export interface FuseTracer { wrap void>(op: string, fn: F): F; /** Raw stats keyed by op name. The returned object is a defensive snapshot. */ snapshot(): Record; - /** JSON summary suitable for stderr or `WSD_FUSE_TRACE_FILE`. */ + /** JSON summary suitable for stderr or `COMPUTERD_FUSE_TRACE_FILE`. */ formatJson(opts?: { pretty?: boolean }): string; /** Reset all counters; used by tests. */ reset(): void; diff --git a/packages/wsd/src/fuse/vfs.test.ts b/packages/computerd/src/fuse/vfs.test.ts similarity index 100% rename from packages/wsd/src/fuse/vfs.test.ts rename to packages/computerd/src/fuse/vfs.test.ts diff --git a/packages/wsd/src/fuse/vfs.ts b/packages/computerd/src/fuse/vfs.ts similarity index 93% rename from packages/wsd/src/fuse/vfs.ts rename to packages/computerd/src/fuse/vfs.ts index ddd9ed52..432eeff1 100644 --- a/packages/wsd/src/fuse/vfs.ts +++ b/packages/computerd/src/fuse/vfs.ts @@ -1,7 +1,7 @@ +import type { SyncRPC } from "@cloudflare/computer-rpc"; +import { pullOnce, tick } from "@cloudflare/computer-rpc/driver"; import { Database, initializeSchema, SQLiteWorkspaceProvider } from "@cloudflare/dofs"; import { SQLiteTestStorage } from "@cloudflare/dofs/testing"; -import type { SyncRPC } from "@cloudflare/workspace-rpc"; -import { pullOnce, tick } from "@cloudflare/workspace-rpc/driver"; import { create, type VirtualFileSystem, VirtualProvider } from "@platformatic/vfs"; export type NodeVirtualFileSystem = VirtualFileSystem; @@ -10,7 +10,7 @@ export type NodeVirtualFileSystem = VirtualFileSystem; // VirtualProvider` and silently falls back to MemoryProvider when // the check fails. dofs's SQLiteWorkspaceProvider can't import // @platformatic/vfs (workerd target), so we splice VirtualProvider -// onto its prototype chain at the wsd boundary. The splice happens +// onto its prototype chain at the computerd boundary. The splice happens // only here, never in dofs, so the workerd build stays clean. // // One-time splice: SQLiteWorkspaceProvider.prototype -> VirtualProvider.prototype. @@ -53,12 +53,12 @@ const EXTRA_VFS_METHODS = [ export interface CreateOptions { // Optional upstream sync surface. When set, the local store - // performs an initial pull on construction. When unset, wsd runs + // performs an initial pull on construction. When unset, computerd runs // standalone against an in-memory store. // // The caller owns the carrier (WebSocket, in-process direct // binding, or any future flavour). This package only needs the - // typed surface; the transport seam lives in workspace-rpc. + // typed surface; the transport seam lives in computer-rpc. // Future RPCs (exec, mounts, watchers) will travel beside // SyncRPC on the same connection, so the caller may pass a // composite stub — we accept the narrow SyncRPC subset @@ -132,7 +132,7 @@ function startSyncLoop(db: Database, upstream: SyncRPC): () => void { console.error("sync tick failed:", error); }); }, SYNC_TICK_MS); - // Don't block process exit on the timer. wsd's shutdown path + // Don't block process exit on the timer. computerd's shutdown path // calls stopSync() explicitly; this is belt-and-braces. handle.unref?.(); return () => { diff --git a/packages/wsd/src/shim/index.ts b/packages/computerd/src/shim/index.ts similarity index 100% rename from packages/wsd/src/shim/index.ts rename to packages/computerd/src/shim/index.ts diff --git a/packages/wsd/src/shim/shim.test.ts b/packages/computerd/src/shim/shim.test.ts similarity index 95% rename from packages/wsd/src/shim/shim.test.ts rename to packages/computerd/src/shim/shim.test.ts index 3e970fcb..f92e8aef 100644 --- a/packages/wsd/src/shim/shim.test.ts +++ b/packages/computerd/src/shim/shim.test.ts @@ -30,7 +30,7 @@ async function eventually( } async function setup() { - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-")); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-")); const { vfs } = await createNodeVirtualFileSystem(); const shim = await mountShim({ vfs, mountPoint, pollIntervalMs: TICK_MS }); onTestFinished(async () => { @@ -131,7 +131,7 @@ test("shim.flush() settles VFS writes onto disk before resolving", async (_ctx) // serve the assertion. If flush() works, the file is on disk before // any tick fires; if it doesn't, the read fails because nothing else // has materialised it yet. - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-flush-")); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-flush-")); const { vfs } = await createNodeVirtualFileSystem(); const shim = await mountShim({ vfs, mountPoint, pollIntervalMs: 60_000 }); onTestFinished(async () => { @@ -162,7 +162,7 @@ test("shim.flush() is idempotent and cheap on a clean tree", async (_ctx) => { }); test("shim.flush() resolves on an unmounted shim without throwing", async (_ctx) => { - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-flush-unmount-")); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-flush-unmount-")); const { vfs } = await createNodeVirtualFileSystem(); const shim = await mountShim({ vfs, mountPoint, pollIntervalMs: TICK_MS }); onTestFinished(async () => { @@ -177,7 +177,7 @@ test("shim.reconcileNow() settles disk writes into the VFS before resolving", as // A very slow poll guarantees the periodic reconcile can't be // serving the assertion; if reconcileNow() works the file is in // the VFS as soon as the call returns. - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-reconcile-")); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-reconcile-")); const { vfs } = await createNodeVirtualFileSystem(); const shim = await mountShim({ vfs, mountPoint, pollIntervalMs: 60_000 }); onTestFinished(async () => { @@ -210,7 +210,7 @@ test("shim.reconcileNow() is idempotent and cheap on a clean tree", async (_ctx) }); test("shim.reconcileNow() resolves on an unmounted shim without throwing", async (_ctx) => { - const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "wsd-shim-reconcile-unmount-")); + const mountPoint = await fs.mkdtemp(path.join(os.tmpdir(), "computerd-shim-reconcile-unmount-")); const { vfs } = await createNodeVirtualFileSystem(); const shim = await mountShim({ vfs, mountPoint, pollIntervalMs: TICK_MS }); onTestFinished(async () => { diff --git a/packages/wsd/src/shim/shim.ts b/packages/computerd/src/shim/shim.ts similarity index 99% rename from packages/wsd/src/shim/shim.ts rename to packages/computerd/src/shim/shim.ts index 3cfb2c5b..65de9d1f 100644 --- a/packages/wsd/src/shim/shim.ts +++ b/packages/computerd/src/shim/shim.ts @@ -124,7 +124,7 @@ export async function mountShim(options: MountShimOptions): Promise { // If the mount point already has content from a previous run we // leave it alone — the disk-poll on the first reconcile tick // will treat any extra files as disk-side additions and push - // them into the VFS. That matches the "wsd is not the only + // them into the VFS. That matches the "computerd is not the only // writer" relaxation the shim explicitly takes on. await run(async () => { await materialiseVfsToDisk(vfs, mountPoint, shadow); diff --git a/packages/wsd/tsconfig.json b/packages/computerd/tsconfig.json similarity index 100% rename from packages/wsd/tsconfig.json rename to packages/computerd/tsconfig.json diff --git a/packages/wsd/vitest.config.ts b/packages/computerd/vitest.config.ts similarity index 88% rename from packages/wsd/vitest.config.ts rename to packages/computerd/vitest.config.ts index 6b5431c7..81edc0e4 100644 --- a/packages/wsd/vitest.config.ts +++ b/packages/computerd/vitest.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from "vitest/config"; -// wsd's test suite spins up real subprocesses (wsd binary boots, +// computerd's test suite spins up real subprocesses (computerd binary boots, // websocket peers, FUSE shim mount points) so the runner needs // node — not workerd, and not jsdom. Sequencing is per-file to // keep concurrent suites from racing on shared port allocators diff --git a/packages/dofs/README.md b/packages/dofs/README.md index 737c9e0b..79ac3a17 100644 --- a/packages/dofs/README.md +++ b/packages/dofs/README.md @@ -10,14 +10,14 @@ > The specification under [`docs/`](docs/) is forward-looking — read it for > intent, not as description of the code today. -Durable Object SQLite-backed virtual filesystem for Cloudflare Workspace. +Durable Object SQLite-backed virtual filesystem for Cloudflare Computer. This package exposes a JavaScript module, not a CLI. It bundles three layers that can be used independently: - A `Database` wrapper around Durable Object SQL storage plus `initializeSchema` for the `vfs_*` tables. - Filesystem primitives under `src/fs/*` (`mkdir`, `writeFile`, `readFile`, `rm`, `readdir`, `stat`, `lstat`, `chmod`, `find`, `ls`, `grep`, `symlink`, `readlink`, `gc`, `watch`) operating on a `Database`. -- `SQLiteWorkspaceProvider`, a `@platformatic/vfs` adapter that composes those primitives into a node-shaped filesystem (fd table, positional `readSync`/`writeSync`, `watchSync`, symlinks). This is what `wsd` mounts via FUSE. -- Sync protocol building blocks operating on the same `Database`: `applyChanges`, `stageBlob`, `materialiseChange`, `coalesceChanges`, `fetchChanges`, `fetchObjects`, `hasObjects`, `pushObjects`, `buildManifest`, `currentRev`, `compareChangeCursors`, `readWatermark`/`writeWatermark`, `assertAppliedPushCursor`, and `DEFAULT_IGNORE`/`isIgnored`. The wire wiring lives in `@cloudflare/workspace-rpc`. +- `SQLiteWorkspaceProvider`, a `@platformatic/vfs` adapter that composes those primitives into a node-shaped filesystem (fd table, positional `readSync`/`writeSync`, `watchSync`, symlinks). This is what `computerd` mounts via FUSE. +- Sync protocol building blocks operating on the same `Database`: `applyChanges`, `stageBlob`, `materialiseChange`, `coalesceChanges`, `fetchChanges`, `fetchObjects`, `hasObjects`, `pushObjects`, `buildManifest`, `currentRev`, `compareChangeCursors`, `readWatermark`/`writeWatermark`, `assertAppliedPushCursor`, and `DEFAULT_IGNORE`/`isIgnored`. The wire wiring lives in `@cloudflare/computer-rpc`. Minimal DO-side usage — initialize the schema; the `Database` becomes the handle every other helper takes: @@ -35,7 +35,7 @@ export class WorkspaceDO extends DurableObject { } ``` -> The `src/fs/*` primitives (`mkdir`, `writeFile`, `readFile`, `rm`, `readdir`, `stat`, `find`, `ls`, `grep`, `symlink`, `readlink`, `gc`, `watch`) are not re-exported from the package root yet — they are consumed in-tree by `SQLiteWorkspaceProvider` and by the sync `applyChanges` path. On the node side, instantiate `SQLiteWorkspaceProvider` (the `@platformatic/vfs` adapter) for a familiar node:fs-shaped surface; this is what `@cloudflare/workspace-wsd` mounts via FUSE. A higher-level DO-side `Workspace` class with the `fs`/`shell`/`push`/`pull` surface described in [`../../docs/README.md`](../../docs/README.md) is still future work. +> The `src/fs/*` primitives (`mkdir`, `writeFile`, `readFile`, `rm`, `readdir`, `stat`, `find`, `ls`, `grep`, `symlink`, `readlink`, `gc`, `watch`) are not re-exported from the package root yet — they are consumed in-tree by `SQLiteWorkspaceProvider` and by the sync `applyChanges` path. On the node side, instantiate `SQLiteWorkspaceProvider` (the `@platformatic/vfs` adapter) for a familiar node:fs-shaped surface; this is what `@cloudflare/computerd` mounts via FUSE. A higher-level DO-side `Workspace` class with the `fs`/`shell`/`push`/`pull` surface described in [`../../docs/README.md`](../../docs/README.md) is still future work. ## Implementation status @@ -45,7 +45,7 @@ export class WorkspaceDO extends DurableObject { - `incrementRev()` shared sequencer in place. FS writes stamp the returned value into `vfs_nodes.rev` and pass it to `sync/changes.ts` for tombstones. - `SQLiteTestStorage` (backed by `node:sqlite`) available from `./testing` for unit tests against a real in-memory database; `RecordingStorage` available from the package root for workerd-safe schema assertions. - All filesystem primitives listed above are implemented and unit-tested. -- `SQLiteWorkspaceProvider` (the `@platformatic/vfs` adapter) implemented and exported from the package entrypoint; consumed by `@cloudflare/workspace-wsd`. +- `SQLiteWorkspaceProvider` (the `@platformatic/vfs` adapter) implemented and exported from the package entrypoint; consumed by `@cloudflare/computerd`. - Buffered-write surface for the FUSE driver: `createFileSync`, `writeRangeSync`, `truncateFileSync`, `readRangeSync`, `chmodSync`, `openWriteBufferSync`, `openWriteBufferForCreateSync`, and @@ -58,4 +58,4 @@ export class WorkspaceDO extends DurableObject { helper share a per-`Database` LRU keyed by `vfs_blob_bytes.hash`. Repeated reads of dedup'd chunks (a file of zeroes, a re-used package payload) skip SQLite after the first fetch. -- Sync protocol building blocks implemented and exported; the typed RPC surface on top of them lives in `@cloudflare/workspace-rpc`. +- Sync protocol building blocks implemented and exported; the typed RPC surface on top of them lives in `@cloudflare/computer-rpc`. diff --git a/packages/dofs/src/fs/mount-guard.ts b/packages/dofs/src/fs/mount-guard.ts index 5fa58a95..6a2aadd3 100644 --- a/packages/dofs/src/fs/mount-guard.ts +++ b/packages/dofs/src/fs/mount-guard.ts @@ -10,7 +10,7 @@ // The set of read-only roots is small (one row per registered // mount per workspace, typically <10) and changes only at indexer // write time. Cache it per Database in a WeakMap so repeat lookups -// don't hit SQLite. The mount indexer in @cloudflare/workspace +// don't hit SQLite. The mount indexer in @cloudflare/computer // invalidates the cache via `invalidateReadOnlyMountCache(db)` after // it writes _vfs_mounts. diff --git a/packages/dofs/src/index.ts b/packages/dofs/src/index.ts index 6277a640..82c31682 100644 --- a/packages/dofs/src/index.ts +++ b/packages/dofs/src/index.ts @@ -33,7 +33,7 @@ export { initializeSchema, ROOT_INODE, SCHEMA_VERSION } from "./schema/index.js" export { Database } from "./storage.js"; export type { ApplyOptions, ApplyResult, SkippedEntry } from "./sync/apply.js"; // Sync protocol building blocks. The wire wiring lives in -// @cloudflare/workspace-rpc; these are the helpers that wiring binds +// @cloudflare/computer-rpc; these are the helpers that wiring binds // to a Database. export { applyChanges, applyChangesSync } from "./sync/apply.js"; export { stageBlob } from "./sync/blobs.js"; diff --git a/packages/dofs/src/provider.ts b/packages/dofs/src/provider.ts index e9dd5741..5adcafc8 100644 --- a/packages/dofs/src/provider.ts +++ b/packages/dofs/src/provider.ts @@ -528,7 +528,7 @@ export class SQLiteWorkspaceProvider { } internalModuleStat(_path: string): number { - // Used by node:vfs module-resolution hooks. The wsd driver doesn't + // Used by node:vfs module-resolution hooks. The computerd driver doesn't // need it; if this provider is ever mounted via `vfs.mount()` we'll // need to return 0 for files, 1 for dirs, -1 for not-found. throw notImplemented("internalModuleStat"); diff --git a/packages/dofs/src/sync/apply.test.ts b/packages/dofs/src/sync/apply.test.ts index eeb39f53..5ced0d0e 100644 --- a/packages/dofs/src/sync/apply.test.ts +++ b/packages/dofs/src/sync/apply.test.ts @@ -972,7 +972,7 @@ describe("applyChanges with read-only mount roots", () => { }); describe("applyChanges mtime propagation (auto_cache contract)", () => { - // The FUSE driver mounts with WSD_FUSE_AUTO_CACHE=1 in the + // The FUSE driver mounts with COMPUTERD_FUSE_AUTO_CACHE=1 in the // production-safe profile. auto_cache tells the kernel to keep // file data in the page cache until the file is reopened with a // different mtime or size; the kernel then drops the cached diff --git a/packages/rpc/README.md b/packages/rpc/README.md index 2b9b8631..446a66a5 100644 --- a/packages/rpc/README.md +++ b/packages/rpc/README.md @@ -1,4 +1,4 @@ -# `@cloudflare/workspace-rpc` +# `@cloudflare/computer-rpc` > [!IMPORTANT] > **PREVIEW ONLY** This package is provided as a preview for feedback only. @@ -11,13 +11,13 @@ > intent, not as description of the code today. capnweb-based RPC wire types and server/client helpers shared -between the DO and `wsd`. The package is split into four entry +between the DO and `computerd`. The package is split into four entry points: - `.` — the typed wire interface (`SyncRPC`, `ShellRPC`, `WorkspaceRPC`, `WireError`). - `./server` — a `Database`-backed implementation. Imported by - DO code and by the in-container workspace-server. + DO code and by the in-container computerd. - `./client` — typed stubs over a WebSocket carrier. - `./driver` — `pullOnce` / `pushOnce` / `tick` helpers that drive a SyncRPC client through a sync round. @@ -50,7 +50,7 @@ import { enableStubTracking, isStubTrackingEnabled, stubSnapshot, -} from "@cloudflare/workspace-rpc/debug"; +} from "@cloudflare/computer-rpc/debug"; // Either set CAPNWEB_TRACK_STUBS=1 in the environment, or call // enableStubTracking() at module init in runtimes (like workerd) @@ -61,5 +61,5 @@ enableStubTracking(); console.log(stubSnapshot()); ``` -The counter is opt-in and has no cost when disabled. `wsd` -exposes the snapshot at `GET /__wsd/stubs` when tracking is on. +The counter is opt-in and has no cost when disabled. `computerd` +exposes the snapshot at `GET /__computerd/stubs` when tracking is on. diff --git a/packages/rpc/package.json b/packages/rpc/package.json index 3b67f38f..b14a7469 100644 --- a/packages/rpc/package.json +++ b/packages/rpc/package.json @@ -1,5 +1,5 @@ { - "name": "@cloudflare/workspace-rpc", + "name": "@cloudflare/computer-rpc", "version": "0.0.0", "private": true, "type": "module", diff --git a/packages/rpc/src/index.ts b/packages/rpc/src/index.ts index ef587ede..738c2c7e 100644 --- a/packages/rpc/src/index.ts +++ b/packages/rpc/src/index.ts @@ -1,11 +1,11 @@ -// Public surface of @cloudflare/workspace-rpc. +// Public surface of @cloudflare/computer-rpc. // // The package is split into four entry points: // // - . — the typed wire interface and error shape. // - ./server — a Database-backed implementation of the interface. // Imported by DO code and by the in-container -// workspace-server. +// computerd. // - ./client — a typed stub over a WebSocket carrier. // - ./driver — the pull/push sync loop helpers that drive a // SyncRPC client through one tick of work. diff --git a/packages/rpc/src/interface.test.ts b/packages/rpc/src/interface.test.ts index 71025dae..257f78ac 100644 --- a/packages/rpc/src/interface.test.ts +++ b/packages/rpc/src/interface.test.ts @@ -17,16 +17,17 @@ import type { WireErrorCode } from "./interface.js"; // map covers every WireErrorCode in the union. const COVERAGE: Record = { ENOENT: - "dofs:fs/stat throws ENOENT for a missing path; wsd:runner throws on get() of unknown id.", + "dofs:fs/stat throws ENOENT for a missing path; computerd:runner throws on get() of unknown id.", EUNKNOWN_HASH: "workspace:WorkspaceFs#assembleChunks throws when fetchObjects skipped a hash.", ESHUTDOWN: - "TBD — wsd shutdown path will raise this when the runner refuses new exec after disposeAll.", - EAUTH: "TBD — wired when the WSD_AUTH_TOKEN handshake lands.", + "TBD — computerd shutdown path will raise this when the runner refuses new exec after disposeAll.", + EAUTH: "TBD — wired when the COMPUTERD_AUTH_TOKEN handshake lands.", EPROTOCOL: "TBD — wired when frame-size / version-mismatch enforcement lands (docs/08 open question).", EEXEC_BUSY: - "wsd:exec/runner.ts throws when an id is reused while live, or a second subscriber attaches.", - ELOG_TRUNCATED: "wsd:exec/log.ts throws on replay() when the meta row marks the log as evicted.", + "computerd:exec/runner.ts throws when an id is reused while live, or a second subscriber attaches.", + ELOG_TRUNCATED: + "computerd:exec/log.ts throws on replay() when the meta row marks the log as evicted.", }; describe("WireErrorCode coverage registry", () => { diff --git a/packages/rpc/src/interface.ts b/packages/rpc/src/interface.ts index a618b509..50b5ab79 100644 --- a/packages/rpc/src/interface.ts +++ b/packages/rpc/src/interface.ts @@ -1,5 +1,5 @@ // The wire contract for the workspace sync RPC. The DO side and the -// container-side workspace-server both implement this interface +// container-side computerd both implement this interface // against a SQLite-backed VFS; the only thing that differs is which // direction each method is called from. // @@ -87,7 +87,7 @@ export interface SyncRPC { pushObjects(objects: ReadableStream<{ hash: Uint8Array; bytes: Uint8Array }>): Promise; } -// Process supervision surface. Lives alongside SyncRPC. wsd's +// Process supervision surface. Lives alongside SyncRPC. computerd's // Runner is the concrete implementation today; the interface keeps // that dependency out of the wire contract. export interface ShellRPC { diff --git a/packages/rpc/src/server.ts b/packages/rpc/src/server.ts index 70287367..760b4cb2 100644 --- a/packages/rpc/src/server.ts +++ b/packages/rpc/src/server.ts @@ -1,7 +1,7 @@ // Server-side adapter: a SQLite-backed Database becomes a SyncRPC. // // The DO uses this to expose its sync surface to the container, and -// the in-container workspace-server uses it to expose its mirror to +// the in-container computerd uses it to expose its mirror to // the DO. Same code on both ends; what differs is who calls whom. import { @@ -26,10 +26,10 @@ import { newWebSocketRpcSession, nodeHttpBatchRpcResponse, RpcTarget } from "cap import { trackStub, untrackStub } from "./debug.js"; import type { ExecEvent, ShellRPC, SyncRPC, WorkspaceRPC } from "./interface.js"; -// Subset of wsd's Runner that the shell server needs. Defining +// Subset of computerd's Runner that the shell server needs. Defining // the shape here (instead of importing the concrete class) keeps -// workspace-rpc free of a wsd dependency — the package builds and -// runs without wsd's process-supervision code on the path. +// computer-rpc free of a computerd dependency — the package builds and +// runs without computerd's process-supervision code on the path. export interface RunnerLike { exec( command: string, @@ -54,7 +54,7 @@ export interface ServerOptions { /** * Optional hook fired inside the SyncRPC `push` handler, right * after a successful peer batch has been committed. Resolved - * before `push()` returns to the caller. Used by wsd to settle + * before `push()` returns to the caller. Used by computerd to settle * the userspace shim layer so a subsequent `shell.exec` sees the * just-pushed files on disk. * @@ -66,7 +66,7 @@ export interface ServerOptions { /** * Optional hook fired inside the SyncRPC `fetchChanges` handler, * right before the receiver computes the change set the puller - * will see. Resolved before any entries stream. Used by wsd to + * will see. Resolved before any entries stream. Used by computerd to * settle the userspace shim's disk→VFS reconcile so a * `Workspace.pull()` issued right after `shell.exec` returns the * files the exec'd process wrote, without waiting on the shim's @@ -300,7 +300,7 @@ export function createSyncServer(db: Database, options: ServerOptions = {}): Syn }); } -// Construct a ShellRPC bound to a Runner. wsd holds the only +// Construct a ShellRPC bound to a Runner. computerd holds the only // Runner today; tests can pass a fake that implements RunnerLike. export function createShellServer(runner: RunnerLike): ShellRPC { return new ShellRPCServer(runner); @@ -334,7 +334,7 @@ export function acceptWebSocketSession( } // Serve a single capnweb HTTP-batch session against a SyncRPC. Wraps -// capnweb's nodeHttpBatchRpcResponse so wsd never directly imports +// capnweb's nodeHttpBatchRpcResponse so computerd never directly imports // capnweb (which would split capnweb's module identity in mixed // ESM/CJS contexts — the RpcTarget instanceof check then fails). export function serveHTTPBatch( diff --git a/packages/rpc/src/sync-driver.bench.ts b/packages/rpc/src/sync-driver.bench.ts index f44bd205..052300d8 100644 --- a/packages/rpc/src/sync-driver.bench.ts +++ b/packages/rpc/src/sync-driver.bench.ts @@ -8,8 +8,8 @@ // // All scenarios run two SQLite-backed VFSes in-process, // driving the driver against direct stubs (no WebSocket). -// That isolates the dofs + workspace-rpc cost from -// docker / FUSE / capnweb framing. The wsd harness covers +// That isolates the dofs + computer-rpc cost from +// docker / FUSE / capnweb framing. The computerd harness covers // the integration cost separately. import { diff --git a/packages/rpc/src/sync-driver.test.ts b/packages/rpc/src/sync-driver.test.ts index 340884e1..46c6e294 100644 --- a/packages/rpc/src/sync-driver.test.ts +++ b/packages/rpc/src/sync-driver.test.ts @@ -372,7 +372,7 @@ describe("SyncRPC server — afterApply hook", () => { describe("SyncRPC server — beforeFetch hook", () => { // Symmetric to the afterApply spy above. beforeFetch runs on the // receiver right before fetchChanges streams entries, giving the - // host a chance to settle any out-of-band writes (e.g. wsd's shim + // host a chance to settle any out-of-band writes (e.g. computerd's shim // pulling disk changes into the VFS) into the store the fetch is // about to read. function makeReceiverWithSpy(): { @@ -720,7 +720,7 @@ describe("sync driver — cross-side invariant", () => { it("pullOnce resets pushRev and retries when fetchChanges echoes a lower appliedPushCursor", async () => { // The remote reporting an appliedPushCursor below our localPushRev // means the remote forgot what we pushed — typically a process- - // lifetime wsd restart while the WebSocket stayed up, so the + // lifetime computerd restart while the WebSocket stayed up, so the // reconcileWatermarks pass we run on connect never re-ran. The // pull path now treats this inline: cancel the in-flight // stream, reset pushRev to 0, and retry. The next pushOnce @@ -1181,7 +1181,7 @@ describe("sync driver — push atomicity", () => { describe("sync driver — reconcileWatermarks", () => { // Run on (re)connect. The DO's watermarks survive across DO and // container lifetimes; the container's watermarks are - // process-lifetime in today's wsd. After a container restart with + // process-lifetime in today's computerd. After a container restart with // no new DO-side writes, pushOnce's localRev <= sincePush // early-return means the assertAppliedPushCursor check never runs and // the container's empty FUSE mount is invisible to the DO. The diff --git a/packages/rpc/src/sync-driver.ts b/packages/rpc/src/sync-driver.ts index 50033266..fba350a5 100644 --- a/packages/rpc/src/sync-driver.ts +++ b/packages/rpc/src/sync-driver.ts @@ -2,7 +2,7 @@ // SyncRPC stub and runs pull + push ticks against the wire. // // Both sides of the prototype use the same driver: the container -// (wsd) drives an upstream DO stub, and once the DO has a real +// (computerd) drives an upstream DO stub, and once the DO has a real // runtime it'll drive a container stub the same way. // // The driver doesn't own a timer. The caller decides when to call @@ -109,7 +109,7 @@ async function pullOnceImpl( const { currentCursor, appliedPushCursor } = fetchResult; // Cross-side watermark divergence. Two shapes are recoverable: // * appliedPushCursor.rev < localPushRev: the remote forgot - // what we pushed (typically a process-lifetime wsd restart + // what we pushed (typically a process-lifetime computerd restart // while the WebSocket survived, so reconcileWatermarks on // connect never re-ran). // * currentCursor < after: the remote's log is shorter than we @@ -144,7 +144,7 @@ async function pullOnceImpl( await fetchResult.stream.cancel().catch(() => {}); // Surface the divergence at debug level so an operator with // log access can spot a persistently broken remote. We do not - // throw: a one-shot divergence is normal after a wsd restart + // throw: a one-shot divergence is normal after a computerd restart // under the same WebSocket, and the inline reset + retry is // the intended recovery. A persistently-lying remote will log // this on every pull, which is the operational signal that @@ -380,7 +380,7 @@ export async function tick( // Called on (re)connect, before any push or pull tick. // // The asymmetry that makes this necessary: the DO's watermarks live -// in durable storage and survive its incarnations, but today's wsd +// in durable storage and survive its incarnations, but today's computerd // runs against a process-lifetime DB so a container restart wipes // the container-side state. Without a check, pushOnce's early-return // (`localRev <= sincePush`) skips talking to the container entirely diff --git a/packages/rpc/tests/shell-and-composite.test.ts b/packages/rpc/tests/shell-and-composite.test.ts index 949dd0b2..bc964df5 100644 --- a/packages/rpc/tests/shell-and-composite.test.ts +++ b/packages/rpc/tests/shell-and-composite.test.ts @@ -3,9 +3,9 @@ // Two surfaces shipped with no direct coverage: // // - ShellRpcServer wraps a RunnerLike. The Runner type lives in -// @cloudflare/workspace-wsd; rpc only depends on the structural +// @cloudflare/computerd; rpc only depends on the structural // RunnerLike interface from server.ts. Tests wire up a fake -// RunnerLike rather than dragging wsd into the rpc test graph. +// RunnerLike rather than dragging computerd into the rpc test graph. // // - createWorkspaceServer / createWorkspaceClient compose sync // and shell over one capnweb session. The composite exposes diff --git a/packages/rpc/tests/wire.test.ts b/packages/rpc/tests/wire.test.ts index c432ce03..3d62380d 100644 --- a/packages/rpc/tests/wire.test.ts +++ b/packages/rpc/tests/wire.test.ts @@ -222,8 +222,8 @@ describe("SyncRPC push convergence", () => { describe("SyncRPC pull convergence", () => { // Reproduces the FUSE → RPC direction observed broken in the // production container example: an exec'd command writes a - // file through FUSE on the wsd side, and the host's - // pullOnce(b, wsdRpc) is supposed to bring the bytes back. The + // file through FUSE on the computerd side, and the host's + // pullOnce(b, computerdRpc) is supposed to bring the bytes back. The // bug surfaced as HTTP 200 / 0 bytes on the subsequent RPC read, // suggesting the dirent transferred but the chunks didn't. let harness: Harness | undefined; @@ -495,7 +495,7 @@ describe("push semantics — external vs sync peer", () => { // The push landed: currentRev bumped past 1. expect(currentRev(harness.db)).toBeGreaterThan(1); // pushRev was NOT advanced. Anything driving the - // outbound sync loop (a wsd with UPSTREAM_URL set) would + // outbound sync loop (a computerd with UPSTREAM_URL set) would // see the new entry on the next tick. expect(readWatermark(harness.db, "pushRev")).toBe(0); // The fetch cursor was NOT advanced either — the sender has diff --git a/packages/wsd-linux-x64/package.json b/packages/wsd-linux-x64/package.json deleted file mode 100644 index 6ca5845b..00000000 --- a/packages/wsd-linux-x64/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@cloudflare/workspace-wsd-linux-x64", - "version": "0.0.0-alpha.13", - "description": "Prebuilt wsd binary for linux-x64. Single-platform distribution; install on a linux-x64 host or COPY --from a docker build stage that installed it.", - "license": "MIT", - "os": [ - "linux" - ], - "cpu": [ - "x64" - ], - "files": [ - "bin/", - "README.md" - ], - "bin": { - "wsd": "./bin/wsd" - } -} diff --git a/script/wsd-fuse-flush.mjs b/script/computerd-fuse-flush.mjs similarity index 83% rename from script/wsd-fuse-flush.mjs rename to script/computerd-fuse-flush.mjs index b0d1eec9..420cf3c7 100755 --- a/script/wsd-fuse-flush.mjs +++ b/script/computerd-fuse-flush.mjs @@ -1,12 +1,12 @@ #!/usr/bin/env node -// End-to-end verification that wsd's FUSE driver spills its +// End-to-end verification that computerd's FUSE driver spills its // in-memory write buffer into the backing VFS so capnweb-side // pullOnce sees the bytes. Reproduces the production container // failure (FUSE-write \u2192 RPC-read returns 0 bytes) locally against -// a freshly-built wsd binary. +// a freshly-built computerd binary. // -// Boots one wsd container with FUSE mounted on /workspace, writes +// Boots one computerd container with FUSE mounted on /workspace, writes // a file inside the container via `docker exec` (kernel \u2192 FUSE // driver \u2192 in-memory buffer \u2192 release/flush spill), then dials // the container's WebSocket from the host, calls pullOnce, and @@ -14,7 +14,7 @@ // fresh receiver DB. // // Knobs: -// WSD_BINARY path to wsd-linux-x64 (default artifacts/wsd/...) +// COMPUTERD_BINARY path to computerd-linux-x64 (default artifacts/computerd/...) // KEEP=1 leave the container running on failure for poking import { execFile } from "node:child_process"; @@ -27,12 +27,13 @@ const execFileP = promisify(execFile); const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = resolve(__dirname, ".."); -const BINARY = process.env.WSD_BINARY ?? resolve(REPO_ROOT, "artifacts/wsd/wsd-linux-x64"); +const BINARY = + process.env.COMPUTERD_BINARY ?? resolve(REPO_ROOT, "artifacts/computerd/computerd-linux-x64"); const KEEP = process.env.KEEP === "1"; if (!existsSync(BINARY)) { - console.error(`wsd binary not found at ${BINARY}`); - console.error("run `npm run build:bin --workspace @cloudflare/workspace-wsd` first"); + console.error(`computerd binary not found at ${BINARY}`); + console.error("run `npm run build:bin --workspace @cloudflare/computerd` first"); process.exit(2); } @@ -62,7 +63,7 @@ async function bootContainer() { } args.push( "-v", - `${BINARY}:/usr/local/bin/wsd:ro`, + `${BINARY}:/usr/local/bin/computerd:ro`, "-p", "0:8080", "-e", @@ -73,15 +74,15 @@ async function bootContainer() { "bash", "-c", // libfuse2 + a long-lived shell so we can `docker exec` into the - // same mount namespace where wsd mounted FUSE. wsd backgrounds - // and we tail /tmp/wsd.log for debugging. + // same mount namespace where computerd mounted FUSE. computerd backgrounds + // and we tail /tmp/computerd.log for debugging. "apt-get update >/dev/null 2>&1 && " + "apt-get install -y --no-install-recommends fuse3 libfuse2t64 >/dev/null 2>&1 && " + "mkdir -p /workspace && " + - "/usr/local/bin/wsd >/tmp/wsd.log 2>&1 & " + - "WSD_PID=$!; " + - "trap 'kill $WSD_PID 2>/dev/null' EXIT; " + - "wait $WSD_PID", + "/usr/local/bin/computerd >/tmp/computerd.log 2>&1 & " + + "COMPUTERD_PID=$!; " + + "trap 'kill $COMPUTERD_PID 2>/dev/null' EXIT; " + + "wait $COMPUTERD_PID", ); const { stdout } = await execFileP("docker", args); const cid = stdout.trim(); @@ -120,7 +121,7 @@ async function killContainer(cid) { } async function main() { - process.stderr.write("booting wsd container ...\n"); + process.stderr.write("booting computerd container ...\n"); const container = await bootContainer(); process.stderr.write(` ${container.url} (${container.cid.slice(0, 12)})\n`); @@ -144,7 +145,7 @@ async function main() { // 3. Pull from the container's WebSocket on the host. Mirrors // what the host DO does after exec returns. const { createWorkspaceClient } = await import( - `${REPO_ROOT}/node_modules/@cloudflare/workspace-rpc/dist/client.js` + `${REPO_ROOT}/node_modules/@cloudflare/computer-rpc/dist/client.js` ); const { Database, SQLiteWorkspaceProvider, initializeSchema } = await import( `${REPO_ROOT}/node_modules/@cloudflare/dofs/dist/index.js` @@ -153,7 +154,7 @@ async function main() { `${REPO_ROOT}/node_modules/@cloudflare/dofs/dist/testing.js` ); const { pullOnce } = await import( - `${REPO_ROOT}/node_modules/@cloudflare/workspace-rpc/dist/sync-driver.js` + `${REPO_ROOT}/node_modules/@cloudflare/computer-rpc/dist/sync-driver.js` ); const wsUrl = `${container.url.replace("http://", "ws://")}/ws`; @@ -190,10 +191,10 @@ async function main() { failed = true; process.stderr.write(`\nFAIL: ${err.message}\n`); - // Dump wsd's logs to help diagnose. + // Dump computerd's logs to help diagnose. try { - const { stdout } = await dockerExec(container.cid, "cat", "/tmp/wsd.log"); - process.stderr.write(`\n--- wsd logs ---\n${stdout}\n----------------\n`); + const { stdout } = await dockerExec(container.cid, "cat", "/tmp/computerd.log"); + process.stderr.write(`\n--- computerd logs ---\n${stdout}\n----------------\n`); } catch { /* container might already be dead */ } diff --git a/script/wsd-soak.mjs b/script/computerd-soak.mjs similarity index 93% rename from script/wsd-soak.mjs rename to script/computerd-soak.mjs index 624ce4cd..fbbaf019 100755 --- a/script/wsd-soak.mjs +++ b/script/computerd-soak.mjs @@ -1,10 +1,10 @@ #!/usr/bin/env node -// wsd-soak.mjs — soak test for the wsd sync loop. +// computerd-soak.mjs — soak test for the computerd sync loop. // -// Boots two wsd containers wired as peer-to-peer: -// A: standalone wsd, port mapped to the host. -// B: standalone wsd, port mapped to the host AND +// Boots two computerd containers wired as peer-to-peer: +// A: standalone computerd, port mapped to the host. +// B: standalone computerd, port mapped to the host AND // UPSTREAM_URL pointing at A's host port. B's sync // loop pulls from A and pushes to A. // @@ -20,7 +20,7 @@ // // Knobs (env vars): // -// WSD_BINARY path to wsd-linux-x64 binary +// COMPUTERD_BINARY path to computerd-linux-x64 binary // SOAK_DURATION_MS total wall time of the soak phase (default 30000) // SOAK_WRITES_PER_S target writes/second sustained against A (default 200) // SOAK_PAYLOAD_B bytes per write (default 64) @@ -39,7 +39,8 @@ const execFileP = promisify(execFile); const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = resolve(__dirname, ".."); -const BINARY = process.env.WSD_BINARY ?? resolve(REPO_ROOT, "artifacts/wsd/wsd-linux-x64"); +const BINARY = + process.env.COMPUTERD_BINARY ?? resolve(REPO_ROOT, "artifacts/computerd/computerd-linux-x64"); const DURATION_MS = Number(process.env.SOAK_DURATION_MS ?? 30_000); const WRITES_PER_S = Number(process.env.SOAK_WRITES_PER_S ?? 200); const PAYLOAD_B = Number(process.env.SOAK_PAYLOAD_B ?? 64); @@ -59,10 +60,10 @@ const DISABLE_FUSE = process.env.SOAK_DISABLE_FUSE === "1" || !existsSync("/dev/ // docker-desktop on macOS/Windows already provides this. const ADD_HOST_GATEWAY = process.platform === "linux"; -const IMAGE_TAG = "wsd-harness:libfuse2"; +const IMAGE_TAG = "computerd-harness:libfuse2"; if (!existsSync(BINARY)) { - console.error(`wsd binary not found at ${BINARY}`); + console.error(`computerd binary not found at ${BINARY}`); process.exit(1); } @@ -111,7 +112,7 @@ async function bootContainer(extraEnv = {}) { } args.push( "-v", - `${BINARY}:/usr/local/bin/wsd:ro`, + `${BINARY}:/usr/local/bin/computerd:ro`, "-p", "0:8080", "-e", @@ -126,7 +127,7 @@ async function bootContainer(extraEnv = {}) { args.push("-e", `${k}=${v}`); } const image = DISABLE_FUSE ? "debian:stable-slim" : IMAGE_TAG; - args.push(image, "/usr/local/bin/wsd"); + args.push(image, "/usr/local/bin/computerd"); const { stdout } = await execFileP("docker", args); const cid = stdout.trim(); const { stdout: portOut } = await execFileP("docker", ["port", cid, "8080/tcp"]); @@ -211,7 +212,7 @@ function payloadBytes(seed) { // // SOAK_NO_DEFLATE=1 forces the dial to negotiate without // permessage-deflate so the soak can compare compressed and -// uncompressed wire costs without rebuilding the wsd binary. +// uncompressed wire costs without rebuilding the computerd binary. function wsStub(url) { const wsUrl = `${url.replace("http://", "ws://")}/ws`; const ws = new WebSocket(wsUrl, { @@ -220,7 +221,7 @@ function wsStub(url) { return newWebSocketRpcSession(ws); } -// One write into wsd via the SyncRPC push path. senderRev=0 +// One write into computerd via the SyncRPC push path. senderRev=0 // marks us as an external writer — the server applies as // a local write (bumps vfs_meta.rev, leaves pushRev alone) so // the outbound sync loop picks the entry up on the next tick. @@ -297,7 +298,7 @@ async function main() { // B's outbound sync loop ships them to A on the next // tick. This is the path an external orchestrator (a // DO accepting agent requests, the agent itself) would - // take — the same wire surface a wsd-to-wsd peer + // take — the same wire surface a computerd-to-computerd peer // uses, just with a different senderRev value. const writeStub = wsStub(b.url); @@ -366,7 +367,7 @@ async function main() { // capnweb's WebSocket session doesn't expose an explicit // close on the stub; the container kill below tears it - // down at the wsd end. + // down at the computerd end. await Promise.all([kill(a.cid), kill(b.cid)]); // Summary on stderr. diff --git a/script/wsd-stub-soak.mjs b/script/computerd-stub-soak.mjs similarity index 92% rename from script/wsd-stub-soak.mjs rename to script/computerd-stub-soak.mjs index 9449553c..54b640fc 100755 --- a/script/wsd-stub-soak.mjs +++ b/script/computerd-stub-soak.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -// wsd-stub-soak.mjs — soak the long-lived WebSocket session against a -// running wsd and watch two signals: +// computerd-stub-soak.mjs — soak the long-lived WebSocket session against a +// running computerd and watch two signals: // // 1. capnweb session stats. We construct the client-side RpcSession // ourselves (using the same WebSocketTransport shape capnweb's @@ -9,7 +9,7 @@ // between phases. Stats are { imports, exports } — entries in the // session's stub tables. Unbounded growth there is the leak. // -// 2. Our per-class RpcTarget counter (GET /__wsd/stubs). Less precise +// 2. Our per-class RpcTarget counter (GET /__computerd/stubs). Less precise // but catches leaks in our own code rather than capnweb's tables. // // Workload (defaults — override via env): @@ -36,7 +36,8 @@ import WebSocket from "ws"; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = resolve(__dirname, ".."); -const WSD_BINARY = process.env.WSD_BINARY ?? join(REPO_ROOT, "packages/wsd/dist/cli/wsd.cjs"); +const COMPUTERD_BINARY = + process.env.COMPUTERD_BINARY ?? join(REPO_ROOT, "packages/computerd/dist/cli/computerd.cjs"); const SYNC_TICKS = Number(process.env.SOAK_SYNC_TICKS ?? "50"); const EXEC_CALLS = Number(process.env.SOAK_EXEC_CALLS ?? "100"); @@ -164,7 +165,7 @@ async function waitForHealth(port, child, deadlineMs = 5000) { const started = Date.now(); while (Date.now() - started < deadlineMs) { if (child.exitCode !== null) { - throw new Error(`wsd exited early with code ${child.exitCode}`); + throw new Error(`computerd exited early with code ${child.exitCode}`); } try { const r = await httpGet(`http://127.0.0.1:${port}/health`); @@ -172,13 +173,13 @@ async function waitForHealth(port, child, deadlineMs = 5000) { } catch {} await sleep(50); } - throw new Error("wsd never reported healthy"); + throw new Error("computerd never reported healthy"); } async function targetSnapshot(port) { - const r = await httpGet(`http://127.0.0.1:${port}/__wsd/stubs`); + const r = await httpGet(`http://127.0.0.1:${port}/__computerd/stubs`); if (r.statusCode !== 200) { - throw new Error(`/__wsd/stubs returned ${r.statusCode}: ${r.body.slice(0, 200)}`); + throw new Error(`/__computerd/stubs returned ${r.statusCode}: ${r.body.slice(0, 200)}`); } return JSON.parse(r.body); } @@ -188,7 +189,7 @@ async function targetSnapshot(port) { async function main() { const port = await getAvailablePort(); - const mountPoint = await mkdtemp(join(tmpdir(), "wsd-stub-soak-")); + const mountPoint = await mkdtemp(join(tmpdir(), "computerd-stub-soak-")); const env = { ...process.env, @@ -198,8 +199,8 @@ async function main() { CAPNWEB_TRACK_STUBS: "1", }; - console.error(`[soak] starting wsd on :${port} (mount=${mountPoint})`); - const child = spawn(WSD_BINARY, { + console.error(`[soak] starting computerd on :${port} (mount=${mountPoint})`); + const child = spawn(COMPUTERD_BINARY, { cwd: REPO_ROOT, env, stdio: ["ignore", "pipe", "pipe"], @@ -233,7 +234,7 @@ async function main() { try { await waitForHealth(port, child); - console.error("[soak] wsd healthy"); + console.error("[soak] computerd healthy"); const ws = new WebSocket(`ws://127.0.0.1:${port}/ws`); await new Promise((res, rej) => { @@ -369,8 +370,8 @@ async function main() { } } finally { await cleanup(); - if (process.env.SOAK_DUMP_WSD_STDERR === "1") { - process.stderr.write(`\n--- wsd stderr ---\n${stderrBuf}`); + if (process.env.SOAK_DUMP_COMPUTERD_STDERR === "1") { + process.stderr.write(`\n--- computerd stderr ---\n${stderrBuf}`); } } } diff --git a/script/exec-tests b/script/exec-tests index 822c6f9a..39104cdf 100755 --- a/script/exec-tests +++ b/script/exec-tests @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Boot wsd in docker (FUSE-disabled), drive a few exec scenarios +# Boot computerd in docker (FUSE-disabled), drive a few exec scenarios # over the WebSocket from the host, assert outputs, tear down. # -# Mirrors `script/shell` for the mount point — the linux wsd +# Mirrors `script/shell` for the mount point — the linux computerd # binary is the only thing bind-mounted into the container. The # repo isn't mounted: everything else lives in the binary. # @@ -10,14 +10,14 @@ # script/exec-tests # # Requires: -# - artifacts/wsd/wsd-linux-x64 (run `npm run build:bin --workspace -# @cloudflare/workspace-wsd` first) +# - artifacts/computerd/computerd-linux-x64 (run `npm run build:bin --workspace +# @cloudflare/computerd` first) # - docker # - node 22+ on the host set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -binary="${repo_root}/artifacts/wsd/wsd-linux-x64" +binary="${repo_root}/artifacts/computerd/computerd-linux-x64" port="${PORT:-45678}" cid_file="$(mktemp -u)" trap 'cleanup; rm -f "${cid_file}"' EXIT @@ -30,11 +30,11 @@ cleanup() { if [[ ! -x "${binary}" ]]; then echo "missing or non-executable binary: ${binary}" >&2 - echo "run \`npm run build:bin --workspace @cloudflare/workspace-wsd\` first" >&2 + echo "run \`npm run build:bin --workspace @cloudflare/computerd\` first" >&2 exit 2 fi -echo "==> booting wsd in docker" +echo "==> booting computerd in docker" docker run --rm -d \ --cidfile "${cid_file}" \ --platform linux/amd64 \ @@ -42,11 +42,11 @@ docker run --rm -d \ -e FUSE_MOUNT=none \ -e EXEC_LOG_MAX_BYTES=256 \ -p "${port}:${port}" \ - -v "${binary}:/usr/local/bin/wsd:ro" \ + -v "${binary}:/usr/local/bin/computerd:ro" \ debian:stable-slim \ - /usr/local/bin/wsd >/dev/null + /usr/local/bin/computerd >/dev/null -# Wait for /health to come up. wsd binds quickly but pkg-bundled +# Wait for /health to come up. computerd binds quickly but pkg-bundled # binaries do a v8 snapshot warm-up on first launch. for _ in $(seq 1 30); do if curl -sf "http://localhost:${port}/health" >/dev/null 2>&1; then @@ -55,11 +55,11 @@ for _ in $(seq 1 30); do sleep 0.5 done if ! curl -sf "http://localhost:${port}/health" >/dev/null 2>&1; then - echo "wsd did not become healthy on port ${port}" >&2 + echo "computerd did not become healthy on port ${port}" >&2 docker logs "$(cat "${cid_file}")" 2>&1 | tail -20 >&2 exit 1 fi -echo "==> wsd healthy on :${port}" +echo "==> computerd healthy on :${port}" # Drive the smoke from a node script. Inline so we don't add a # new file to mount; the script is self-contained. @@ -67,7 +67,7 @@ PORT="${port}" REPO_ROOT="${repo_root}" node --input-type=module -e ' const repoRoot = process.env.REPO_ROOT; const port = process.env.PORT; const { createWorkspaceClient } = await import( - `${repoRoot}/node_modules/@cloudflare/workspace-rpc/dist/client.js` + `${repoRoot}/node_modules/@cloudflare/computer-rpc/dist/client.js` ); const assert = (cond, msg) => { @@ -239,10 +239,10 @@ try { // WorkerEntrypoint from cloudflare:workers — that scheme // only resolves under workerd, not plain Node. const { TestBackend } = await import( - `${repoRoot}/packages/workspace/dist/backends/test.js` + `${repoRoot}/packages/computer/dist/backends/test.js` ); const { Workspace } = await import( - `${repoRoot}/packages/workspace/dist/workspace.js` + `${repoRoot}/packages/computer/dist/workspace.js` ); // Workspace needs DurableObjectStorageLike; supply the // node:sqlite-backed test storage so this runs under plain diff --git a/script/fs-bench.sh b/script/fs-bench.sh index 14b4a020..338dd9a8 100755 --- a/script/fs-bench.sh +++ b/script/fs-bench.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Benchmark common development tasks against the wsd FUSE mount. +# Benchmark common development tasks against the computerd FUSE mount. # Compares against a tmpfs/native baseline so you can see the overhead. # # Usage (inside the container): @@ -12,9 +12,9 @@ # RANDOMIZE_TARGETS=1 shuffle target order per rep to avoid cache-order # bias (default: 1; set to 0 for deterministic order) # OUTPUT_JSON=path also write a JSON summary to `path` -# TRACE_FILE=path hint: set WSD_FUSE_TRACE=summary and -# WSD_FUSE_TRACE_FILE on the daemon side to capture -# FUSE op stats; this script does not start wsd. +# TRACE_FILE=path hint: set COMPUTERD_FUSE_TRACE=summary and +# COMPUTERD_FUSE_TRACE_FILE on the daemon side to capture +# FUSE op stats; this script does not start computerd. # # Note: if the workspace mount is owned by another uid the script file # may not be executable in place. Run it via `bash ./script/fs-bench.sh` @@ -82,7 +82,7 @@ shuffle_targets() { } label_for() { - if [[ "$1" == "$MOUNT" ]]; then echo "wsd"; else echo "base"; fi + if [[ "$1" == "$MOUNT" ]]; then echo "computerd"; else echo "base"; fi } # Run one (scenario, target) pair once, returning wall-clock ns on stdout @@ -319,18 +319,18 @@ fi section "summary" if (( ${#TARGETS[@]} > 1 )); then printf " %-30s %12s %12s %12s %12s %10s\n" \ - "scenario" "wsd mean" "wsd p95" "base mean" "base p95" "ratio" - declare -A wsd_mean wsd_p95 base_mean base_p95 + "scenario" "computerd mean" "computerd p95" "base mean" "base p95" "ratio" + declare -A computerd_mean computerd_p95 base_mean base_p95 for r in "${results[@]}"; do IFS='|' read -r name label mean median p95 mn mx count <<<"$r" - if [[ "$label" == "wsd" ]]; then - wsd_mean[$name]=$mean; wsd_p95[$name]=$p95 + if [[ "$label" == "computerd" ]]; then + computerd_mean[$name]=$mean; computerd_p95[$name]=$p95 else base_mean[$name]=$mean; base_p95[$name]=$p95 fi done - for name in "${!wsd_mean[@]}"; do - wm="${wsd_mean[$name]}"; wp="${wsd_p95[$name]}" + for name in "${!computerd_mean[@]}"; do + wm="${computerd_mean[$name]}"; wp="${computerd_p95[$name]}" bm="${base_mean[$name]:-0}"; bp="${base_p95[$name]:-0}" if [[ "$bm" != "0" ]]; then ratio=$(awk -v w="$wm" -v b="$bm" 'BEGIN{printf "%.2fx", w/b}') diff --git a/script/fs-tests.sh b/script/fs-tests.sh index c6680da8..a5097bc1 100755 --- a/script/fs-tests.sh +++ b/script/fs-tests.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Filesystem conformance test harness for the wsd FUSE mount. +# Filesystem conformance test harness for the computerd FUSE mount. # Each test runs in its own subdirectory with `set -e` so failures don't cascade. # Run from inside the container: MOUNT=/workspace fs-tests set -u diff --git a/script/npm-bench.sh b/script/npm-bench.sh index a3547353..0407f35c 100755 --- a/script/npm-bench.sh +++ b/script/npm-bench.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Benchmarks npm package installs on native disk vs the wsd FUSE mount. +# Benchmarks npm package installs on native disk vs the computerd FUSE mount. # -# Runs each scenario on both a native target directory and the wsd +# Runs each scenario on both a native target directory and the computerd # FUSE mount, repeating REPS times with WARMUP untimed runs first. # Emits periodic heartbeats while npm is quiet so the run is # distinguishable from a hang. Writes a JSON summary when OUTPUT_JSON @@ -9,7 +9,7 @@ # # Scenarios: # express npm install express --prefer-offline -# workspace npm install for the cloudflare/workspace monorepo +# computer npm install for the cloudflare/computer monorepo # synthetic a synthetic package tree with many tiny files # # Knobs (environment variables): @@ -20,7 +20,7 @@ # SCENARIOS comma-separated list of scenario names (default: express) # OUTPUT_JSON path to write the JSON results file (optional) # HEARTBEAT_SEC heartbeat interval in seconds (default: 10) -# WSD_FUSE_TRACE set to "summary" to collect a FUSE op trace per run +# COMPUTERD_FUSE_TRACE set to "summary" to collect a FUSE op trace per run # NPM_CACHE_DIR npm cache directory (default: /tmp/npm-cache) set -euo pipefail @@ -31,7 +31,7 @@ WARMUP="${WARMUP:-1}" SCENARIOS="${SCENARIOS:-express}" OUTPUT_JSON="${OUTPUT_JSON:-}" HEARTBEAT_SEC="${HEARTBEAT_SEC:-10}" -WSD_FUSE_TRACE="${WSD_FUSE_TRACE:-}" +COMPUTERD_FUSE_TRACE="${COMPUTERD_FUSE_TRACE:-}" NPM_CACHE_DIR="${NPM_CACHE_DIR:-/tmp/npm-cache}" # Warm the npm cache for express so the FUSE install is not I/O bound. @@ -51,10 +51,10 @@ warm_npm_cache() { --no-fund \ >/dev/null 2>&1 || true ;; - workspace) - if [ -d /tmp/workspace-repo ]; then + computer) + if [ -d /tmp/computer-repo ]; then npm install \ - --prefix /tmp/workspace-repo \ + --prefix /tmp/computer-repo \ --cache "$NPM_CACHE_DIR" \ --prefer-offline \ --ignore-scripts \ @@ -124,14 +124,14 @@ run_install() { --loglevel warn \ >"$npm_log" 2>&1 || exit_code=$? ;; - workspace) - if [ ! -d /tmp/workspace-repo ]; then - echo "[bench] workspace-repo not found; clone it to /tmp/workspace-repo to use this scenario" + computer) + if [ ! -d /tmp/computer-repo ]; then + echo "[bench] computer-repo not found; clone it to /tmp/computer-repo to use this scenario" stop_heartbeat - echo '{"error":"workspace-repo not found"}' + echo '{"error":"computer-repo not found"}' return fi - cp -r /tmp/workspace-repo "$work_dir/repo" + cp -r /tmp/computer-repo "$work_dir/repo" npm install \ --prefix "$work_dir/repo" \ --cache "$NPM_CACHE_DIR" \ diff --git a/script/run-fs-bench.sh b/script/run-fs-bench.sh index 46c59eb2..d3a4c8a1 100755 --- a/script/run-fs-bench.sh +++ b/script/run-fs-bench.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # Non-interactive harness for ./script/shell that runs fs-bench. -# Mount the wsd binary, fs-bench, and this script into a debian -# container, then invoke this as the entrypoint. wsd runs in the +# Mount the computerd binary, fs-bench, and this script into a debian +# container, then invoke this as the entrypoint. computerd runs in the # background; fs-bench is executed against the FUSE mount with a -# /tmp/baseline directory for the wsd-vs-native comparison; the -# wsd process is shut down before the script exits with +# /tmp/baseline directory for the computerd-vs-native comparison; the +# computerd process is shut down before the script exits with # fs-bench's status code. # # Mirrors run-fs-tests.sh; differs only in that fs-bench needs @@ -19,26 +19,26 @@ apt-get install -y --no-install-recommends fuse3 libfuse2t64 attr util-linux cor # previous version forgot to create it, so fs-bench silently dropped the # baseline column from its output. mkdir -p /tmp/workspace /tmp/baseline -# Forward optional tracer config to wsd. When WSD_FUSE_TRACE=summary is +# Forward optional tracer config to computerd. When COMPUTERD_FUSE_TRACE=summary is # set the daemon writes a JSON summary on unmount or SIGUSR2; with -# WSD_FUSE_TRACE_FILE pointed at a host-mounted path the trace survives +# COMPUTERD_FUSE_TRACE_FILE pointed at a host-mounted path the trace survives # the container. -WSD_FUSE_TRACE="${WSD_FUSE_TRACE:-}" \ - WSD_FUSE_TRACE_FILE="${WSD_FUSE_TRACE_FILE:-}" \ - PORT=45678 MOUNT_POINT=/tmp/workspace /usr/local/bin/wsd >/tmp/wsd.log 2>&1 & -WSD_PID=$! +COMPUTERD_FUSE_TRACE="${COMPUTERD_FUSE_TRACE:-}" \ + COMPUTERD_FUSE_TRACE_FILE="${COMPUTERD_FUSE_TRACE_FILE:-}" \ + PORT=45678 MOUNT_POINT=/tmp/workspace /usr/local/bin/computerd >/tmp/computerd.log 2>&1 & +COMPUTERD_PID=$! for i in $(seq 1 60); do if curl -fsS http://127.0.0.1:45678/health >/dev/null 2>&1; then - echo "wsd ready after ${i}s" + echo "computerd ready after ${i}s" break fi sleep 1 done -if ! kill -0 "$WSD_PID" 2>/dev/null; then - echo "wsd died:" - cat /tmp/wsd.log +if ! kill -0 "$COMPUTERD_PID" 2>/dev/null; then + echo "computerd died:" + cat /tmp/computerd.log exit 1 fi @@ -47,10 +47,10 @@ fi MOUNT=/tmp/workspace BASE=/tmp/baseline /usr/local/bin/fs-bench status=$? -# Ask wsd to dump the FUSE trace (if enabled) before the SIGTERM. The +# Ask computerd to dump the FUSE trace (if enabled) before the SIGTERM. The # trace handler is async, so give it a moment to land on disk before we # wait on the process. -kill -USR2 "$WSD_PID" 2>/dev/null && sleep 1 -kill "$WSD_PID" 2>/dev/null -wait "$WSD_PID" 2>/dev/null +kill -USR2 "$COMPUTERD_PID" 2>/dev/null && sleep 1 +kill "$COMPUTERD_PID" 2>/dev/null +wait "$COMPUTERD_PID" 2>/dev/null exit $status diff --git a/script/run-fs-tests.sh b/script/run-fs-tests.sh index 4aa12833..ef397160 100755 --- a/script/run-fs-tests.sh +++ b/script/run-fs-tests.sh @@ -1,35 +1,35 @@ #!/usr/bin/env bash -# Non-interactive harness for ./script/shell. Mount the wsd binary, fs-tests, +# Non-interactive harness for ./script/shell. Mount the computerd binary, fs-tests, # and this script into a debian:stable-slim container, then invoke this as -# the entrypoint. wsd runs in the background; fs-tests is executed against -# the FUSE mount; the wsd process is shut down before the script exits with +# the entrypoint. computerd runs in the background; fs-tests is executed against +# the FUSE mount; the computerd process is shut down before the script exits with # fs-tests' status code. set -u apt-get update >/dev/null 2>&1 apt-get install -y --no-install-recommends fuse3 libfuse2t64 attr util-linux coreutils findutils git ca-certificates curl >/dev/null 2>&1 mkdir -p /tmp/workspace -PORT=45678 MOUNT_POINT=/tmp/workspace /usr/local/bin/wsd >/tmp/wsd.log 2>&1 & -WSD_PID=$! +PORT=45678 MOUNT_POINT=/tmp/workspace /usr/local/bin/computerd >/tmp/computerd.log 2>&1 & +COMPUTERD_PID=$! # Wait for /health for i in $(seq 1 60); do if curl -fsS http://127.0.0.1:45678/health >/dev/null 2>&1; then - echo "wsd ready after ${i}s" + echo "computerd ready after ${i}s" break fi sleep 1 done -if ! kill -0 "$WSD_PID" 2>/dev/null; then - echo "wsd died:" - cat /tmp/wsd.log +if ! kill -0 "$COMPUTERD_PID" 2>/dev/null; then + echo "computerd died:" + cat /tmp/computerd.log exit 1 fi MOUNT=/tmp/workspace /usr/local/bin/fs-tests status=$? -kill "$WSD_PID" 2>/dev/null -wait "$WSD_PID" 2>/dev/null +kill "$COMPUTERD_PID" 2>/dev/null +wait "$COMPUTERD_PID" 2>/dev/null exit $status diff --git a/script/run-npm-bench-inner.sh b/script/run-npm-bench-inner.sh index 75cc2ffb..adc9ddba 100755 --- a/script/run-npm-bench-inner.sh +++ b/script/run-npm-bench-inner.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Inner entrypoint for the npm-bench Docker container; not called directly. Installs -# dependencies, starts wsd, and runs npm-bench.sh. Not meant to be +# dependencies, starts computerd, and runs npm-bench.sh. Not meant to be # called directly — run-npm-bench.sh is the user-facing entry point. set -u @@ -11,29 +11,29 @@ apt-get install -y --no-install-recommends \ mkdir -p /tmp/workspace /tmp/baseline -WSD_FUSE_TRACE="${WSD_FUSE_TRACE:-}" \ - WSD_FUSE_TRACE_FILE="${WSD_FUSE_TRACE_FILE:-}" \ - PORT=45678 MOUNT_POINT=/tmp/workspace /usr/local/bin/wsd >/tmp/wsd.log 2>&1 & -WSD_PID=$! +COMPUTERD_FUSE_TRACE="${COMPUTERD_FUSE_TRACE:-}" \ + COMPUTERD_FUSE_TRACE_FILE="${COMPUTERD_FUSE_TRACE_FILE:-}" \ + PORT=45678 MOUNT_POINT=/tmp/workspace /usr/local/bin/computerd >/tmp/computerd.log 2>&1 & +COMPUTERD_PID=$! for i in $(seq 1 60); do if curl -fsS http://127.0.0.1:45678/health >/dev/null 2>&1; then - echo "wsd ready after ${i}s" + echo "computerd ready after ${i}s" break fi sleep 1 done -if ! kill -0 "$WSD_PID" 2>/dev/null; then - echo "wsd died:" - cat /tmp/wsd.log +if ! kill -0 "$COMPUTERD_PID" 2>/dev/null; then + echo "computerd died:" + cat /tmp/computerd.log exit 1 fi MOUNT=/tmp/workspace BASE=/tmp/baseline /usr/local/bin/npm-bench status=$? -kill -USR2 "$WSD_PID" 2>/dev/null && sleep 1 -kill "$WSD_PID" 2>/dev/null -wait "$WSD_PID" 2>/dev/null +kill -USR2 "$COMPUTERD_PID" 2>/dev/null && sleep 1 +kill "$COMPUTERD_PID" 2>/dev/null +wait "$COMPUTERD_PID" 2>/dev/null exit $status diff --git a/script/run-npm-bench.sh b/script/run-npm-bench.sh index bcd8256f..70e079ef 100755 --- a/script/run-npm-bench.sh +++ b/script/run-npm-bench.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash -# Runs npm-bench.sh inside a privileged Docker container with wsd mounted. -# with wsd mounted, comparing npm install speed on native disk vs the -# FUSE mount. Boot wsd, run the bench, and drop the results on the host. +# Runs npm-bench.sh inside a privileged Docker container with computerd mounted. +# with computerd mounted, comparing npm install speed on native disk vs the +# FUSE mount. Boot computerd, run the bench, and drop the results on the host. # # Usage: # script/run-npm-bench.sh # # Knobs (environment variables): -# WSD_BINARY path to the wsd linux-x64 binary -# (default: artifacts/wsd/wsd-linux-x64) +# COMPUTERD_BINARY path to the computerd linux-x64 binary +# (default: artifacts/computerd/computerd-linux-x64) # REPS number of timed repetitions (default: 3) # WARMUP number of warm-up runs that are not counted (default: 1) # OUTPUT_JSON host path where the JSON result file is written @@ -19,25 +19,25 @@ # # Requirements: # docker available and able to run --privileged containers -# wsd linux-x64 binary at WSD_BINARY +# computerd linux-x64 binary at COMPUTERD_BINARY # # The binary is built with: -# npm run build:bin --workspace @cloudflare/workspace-wsd +# npm run build:bin --workspace @cloudflare/computerd set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -WSD_BINARY="${WSD_BINARY:-$REPO_ROOT/artifacts/wsd/wsd-linux-x64}" +COMPUTERD_BINARY="${COMPUTERD_BINARY:-$REPO_ROOT/artifacts/computerd/computerd-linux-x64}" OUTPUT_JSON="${OUTPUT_JSON:-$REPO_ROOT/bench-out/npm-bench.json}" REPS="${REPS:-3}" WARMUP="${WARMUP:-1}" SCENARIOS="${SCENARIOS:-express}" FUSE_TRACE="${FUSE_TRACE:-}" -if [ ! -f "$WSD_BINARY" ]; then - echo "wsd binary not found at $WSD_BINARY" - echo "Build it with: npm run build:bin --workspace @cloudflare/workspace-wsd" +if [ ! -f "$COMPUTERD_BINARY" ]; then + echo "computerd binary not found at $COMPUTERD_BINARY" + echo "Build it with: npm run build:bin --workspace @cloudflare/computerd" exit 1 fi @@ -45,7 +45,7 @@ mkdir -p "$(dirname "$OUTPUT_JSON")" docker run --rm --platform linux/amd64 --privileged \ --device /dev/fuse --cap-add SYS_ADMIN --cap-add MKNOD \ - -v "$WSD_BINARY:/usr/local/bin/wsd:ro" \ + -v "$COMPUTERD_BINARY:/usr/local/bin/computerd:ro" \ -v "$SCRIPT_DIR/npm-bench.sh:/usr/local/bin/npm-bench:ro" \ -v "$SCRIPT_DIR/run-npm-bench-inner.sh:/run-bench.sh:ro" \ -v "$(dirname "$OUTPUT_JSON"):/out" \ @@ -53,5 +53,5 @@ docker run --rm --platform linux/amd64 --privileged \ -e "WARMUP=$WARMUP" \ -e "SCENARIOS=$SCENARIOS" \ -e "OUTPUT_JSON=/out/$(basename "$OUTPUT_JSON")" \ - -e "WSD_FUSE_TRACE=${FUSE_TRACE}" \ + -e "COMPUTERD_FUSE_TRACE=${FUSE_TRACE}" \ debian:stable-slim bash /run-bench.sh diff --git a/script/set-versions.mjs b/script/set-versions.mjs index 7d1a9027..ae9a8f4c 100644 --- a/script/set-versions.mjs +++ b/script/set-versions.mjs @@ -4,7 +4,7 @@ // node script/set-versions.mjs 0.1.0-alpha.2 // node script/set-versions.mjs v0.1.0-alpha.2 # leading 'v' tolerated // -// Both @cloudflare/workspace and @cloudflare/workspace-wsd-linux-x64 +// Both @cloudflare/computer and @cloudflare/computer-computerd-linux-x64 // land at the same release tag. The release workflow runs this with // the pushed tag before publishing. @@ -12,26 +12,26 @@ import { readFile, writeFile } from "node:fs/promises"; import { argv } from "node:process"; const PACKAGES = [ - "packages/workspace/package.json", - "packages/wsd-linux-x64/package.json", - // wsd itself stays private, but its package.json version is what + "packages/computer/package.json", + "packages/computer-computerd-linux-x64/package.json", + // computerd itself stays private, but its package.json version is what // the build-docker.mjs script reads to tag the published image. // Keeping it in lockstep means the docker tag matches the npm // tag. - "packages/wsd/package.json", + "packages/computerd/package.json", ]; // Example Dockerfiles pin a specific -// ghcr.io/cloudflare/workspace-wsd-linux-x64: in their +// ghcr.io/cloudflare/computer-computerd-linux-x64: in their // first FROM line. Bump it in lockstep with the npm version so a // `git clone && wrangler dev` against any release tag pulls the -// matching wsd image. +// matching computerd image. const DOCKERFILES = [ "examples/container/Dockerfile", "examples/think/Dockerfile", "examples/think-compare-runtimes/Dockerfile.workspace", ]; -const WSD_IMAGE_TAG_RE = /(ghcr\.io\/cloudflare\/workspace-wsd-linux-x64:)[^\s]+/g; +const COMPUTERD_IMAGE_TAG_RE = /(ghcr\.io\/cloudflare\/computer-computerd-linux-x64:)[^\s]+/g; const raw = argv[2]; if (raw === undefined) { @@ -54,13 +54,15 @@ for (const pkg of PACKAGES) { for (const dockerfile of DOCKERFILES) { const before = await readFile(dockerfile, "utf8"); - if (!WSD_IMAGE_TAG_RE.test(before)) { - console.error(`${dockerfile}: no wsd-linux-x64 image tag matched ${WSD_IMAGE_TAG_RE}`); + if (!COMPUTERD_IMAGE_TAG_RE.test(before)) { + console.error( + `${dockerfile}: no computerd-linux-x64 image tag matched ${COMPUTERD_IMAGE_TAG_RE}`, + ); process.exit(2); } // RegExp with /g keeps lastIndex between calls; reset before replace(). - WSD_IMAGE_TAG_RE.lastIndex = 0; - const after = before.replace(WSD_IMAGE_TAG_RE, `$1${version}`); + COMPUTERD_IMAGE_TAG_RE.lastIndex = 0; + const after = before.replace(COMPUTERD_IMAGE_TAG_RE, `$1${version}`); if (after !== before) await writeFile(dockerfile, after); - console.log(`${dockerfile}: wsd image tag → ${version}`); + console.log(`${dockerfile}: computerd image tag → ${version}`); } diff --git a/script/shell b/script/shell index 285a0803..b514ece0 100755 --- a/script/shell +++ b/script/shell @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Launch a debian-slim container with the linux wsd binary mounted as /usr/local/bin/wsd. +# Launch a debian-slim container with the linux computerd binary mounted as /usr/local/bin/computerd. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -binary="${repo_root}/artifacts/wsd/wsd-linux-x64" +binary="${repo_root}/artifacts/computerd/computerd-linux-x64" if [[ ! -x "${binary}" ]]; then echo "missing or non-executable binary: ${binary}" >&2 @@ -18,7 +18,7 @@ exec docker run --rm -it \ --cap-add MKNOD \ --security-opt apparmor=unconfined \ --security-opt seccomp=unconfined \ - -v "${binary}:/usr/local/bin/wsd:ro" \ + -v "${binary}:/usr/local/bin/computerd:ro" \ -v "${repo_root}/script/fs-tests.sh:/usr/local/bin/fs-tests:ro" \ -v "${repo_root}/script/fs-bench.sh:/usr/local/bin/fs-bench:ro" \ -w /root \