diff --git a/.plans/opencode-ai-cli-full-integration-plan.md b/.plans/archived/opencode-ai-cli-full-integration-plan.md similarity index 100% rename from .plans/opencode-ai-cli-full-integration-plan.md rename to .plans/archived/opencode-ai-cli-full-integration-plan.md diff --git a/.plans/opencode-fake-provider-direct-plan.md b/.plans/archived/opencode-fake-provider-direct-plan.md similarity index 100% rename from .plans/opencode-fake-provider-direct-plan.md rename to .plans/archived/opencode-fake-provider-direct-plan.md diff --git a/.plans/trap-sandbox-gateway-plan.md b/.plans/trap-sandbox-gateway-plan.md new file mode 100644 index 0000000..23b105e --- /dev/null +++ b/.plans/trap-sandbox-gateway-plan.md @@ -0,0 +1,217 @@ +# Plan: trap-sandbox gateway + trapos-sandbox client (first iteration) + +## Context + +`tools/trap-sandbox/` is a new TypeScript service that replaces the legacy `tools/mcp-bridge` +(kept only for inspiration). It delivers a **general-purpose Fastify API on port 4444**, organized +into "sandbox modules" (Fastify plugins). The only module in this iteration is the +**`trapos-cloud-gateway`**: a WebSocket gateway giving a CraftOS (trapos) computer and the +sandbox/opencode a generic, bi-directional request/reply protocol. + +On the Lua side, a new `trapos-sandbox` package installs `servers/sandbox.lua` (a boot daemon that +owns one persistent WS connection and bridges WS frames to in-OS events) plus a `sandbox` command. +The legacy `trapos-sandbox` package is renamed to `trapos-sandbox-legacy`. + +**Intended outcome:** after installing the new trapos and setting `sandbox.url`, running +`sandbox health` in-game prints `sandboxOk: true` / `opencodeOk: true|false`. + +This plan was hardened through a full design interview (see **Decision log**). Notably, auth moved +from Basic-Auth-at-upgrade to an **in-protocol `hello` handshake**, which also prepares a future +"one account owns many computers, account stored server-side" model without building it yet. + +## Decision log (grill outcomes) + +| Topic | Decision | +|---|---| +| Event shape | Explicit `event` field: `computer_request|computer_response|gateway_request|gateway_response`; `type` = redux action | +| Bidi scope | All 4 events now; reverse direction (`gateway_request`→`computer_response`) exercised by tests | +| Duplicate trapos id | **Replace existing (newest-wins)** — deliberate deviation from INIT's "reject the duplicate" | +| Identity | **Trust the frame** — gateway uses the client-declared `traposId`; no connection↔frame cross-validation | +| **Auth** | **In-protocol `hello` handshake** — secret in payload; **supersedes** Basic-Auth-at-upgrade + `?id=&label=` query and the "header support" risk | +| Auth reject | **Terminal**: daemon stops reconnecting on `unauthorized`, stores `lastError`; network drops still reconnect | +| Error model | `ok` + `error: { code, message }` with a stable code enum | +| Health disconnected | **Fail fast** with clear error; ~10s command timeout; ~5s opencode probe bound | +| Opencode wiring | `OPENCODE_SERVER_PASSWORD` + default `OPENCODE_URL=http://127.0.0.1:4242`, Basic Auth (opencode link only) | +| Health payload | `{ sandboxOk, opencodeOk, opencodeDetail? }` (detail shown only on failure) | +| Reverse scope (Lua) | Built-in `ping` only; no extensibility fan-out/response path in v1 | +| Future account seam | `accountId` constant `"local"` via `resolveAccount(secret)`; registry keyed by `(accountId, traposId)`; structured error codes | +| Host tooling | Full wiring: `just sandbox-serve`, `npm.just`/`install`/`ci`, `docs/public-ports.md` row for 4444 | +| Meta package | Add `trapos-sandbox` to `trapos` meta + bump version (daemon dormant until `sandbox.url` set) | +| Versions | Fastify `^5` + `@fastify/websocket` `^11`; `engines.node >=20` (Node 24 in use) | +| Tests | 3 layers (Lua-unit, TS-unit, TS-integration) + one full-boot e2e | + +> The "basic password mechanism" from INIT is preserved *in spirit* (a shared secret = +> `SANDBOX_PASSWORD`, mirroring `OPENCODE_SERVER_PASSWORD`) but delivered **in-band via the hello +> frame**, not as a Basic-Auth header. The opencode probe still uses Basic Auth to the opencode +> server (unchanged). + +## Protocol + +JSON frames. Computer-originated carry `traposSenderId`; gateway-originated carry `traposReceiverId`. +`traposId = ":