cc-libs/.plans/trap-sandbox/archived/02-lua-client.md

3.9 KiB

02 — Lua client (trapos-sandbox)

Sub-plan of trap-sandbox-gateway-spec. Implements the Protocol, Handshake, and os-event contract from the spec — reference them, don't redefine. M2; depends on the protocol contract (not on M1 code existing).

Scope

The Lua side: apis/libsandbox.lua, servers/sandbox.lua, programs/sandbox.lua, and Lua unit tests. Package/meta changes are 03-packages; integration/e2e is 04-integration-tooling.

Task checklist

  • apis/libsandbox.lua (template: apis/libmcpcomputer.lua).
  • servers/sandbox.lua (template: servers/mcp-computer-server.lua).
  • programs/sandbox.lua (template: programs/ai.lua).
  • Lua unit tests (tests/sandbox.lua).
  • Gate: just _craftos-test runs tests/sandbox.lua green.

apis/libsandbox.lua — template: apis/libmcpcomputer.lua

createSandbox() returns:

  • traposId(osLike)id .. ":" .. (label or "").
  • Envelope builders for the 4 events + hello. Every computer_request (including hello) must carry a non-empty traposSenderId = the computer's traposId — the TS gateway's zod schema requires it (decided in the 01-ts-gateway grill). The hello identity used for registration is still payload.traposId, but the envelope traposSenderId must also be present.
  • onMessage(content, …): decode (pcall); gateway_request→ built-in ping (else ok:false unknown_type) replied as computer_response via pcall-send; gateway_response→ if type=="hello" drive the handshake state, else queueEvent('trapos_sandbox_reply', messageId, ok, payload, error).
  • startSession(opts) (daemon; injectable http/eventloop/encode/decode/os): builds the connect URL from base opts.url, computes traposId; websocketAsync; on websocket_success send hello; on hello ok:true set state=ready + queueEvent('trapos_sandbox_connected', traposId); on hello ok:false set state=unauthorized (terminal, store lastError, close, no reconnect); on websocket_failure/_closed reconnect via el.setTimeout(connect, 5) (unless unauthorized); hello with no ack in ~10s → close + reconnect; trapos_sandbox_send handler builds computer_request, pcall-send only when ready, else fast trapos_sandbox_reply(messageId, false, nil, {code:"not_connected"}). Returns { isReady, lastError }.
  • request(type, payload, opts) (injectable queueEvent/pullEvent/startTimer): mint messageId, queue trapos_sandbox_send, wait for matching trapos_sandbox_reply vs a timer (default 10s, overridable). Returns (ok, payloadOrError).

servers/sandbox.lua — template: servers/mcp-computer-server.lua

Read sandbox.url (unset/http.websocket missing → "daemon inactive", return) and sandbox.password; startSession{ eventloop=_G.bootEventLoop, url, password(secret), os }; print version.

programs/sandbox.lua — template: programs/ai.lua

  • Reads sandbox.url first; unset → immediate set sandbox.url first (no 10s wait).
  • health command → request('probe_gateway') (wire type renamed from health during the 01-ts-gateway grill; the in-game command name stays health); print sandboxOk/opencodeOk (+ opencode: <detail> when false); on not ok print sandbox health failed: <code> <message> (e.g. unauthorized, not_connected, timeout).
  • --version / --help.

Lua unit tests (tests/sandbox.lua, libtest + fakes)

Envelope builders, traposId, onMessage dispatch (ping ok / unknown_type / hello), empty-payload coercion, startSession wiring (fake eventloop+http: hello sent on open, ready on ack, terminal on unauthorized, reconnect on drop), request() resolve + timeout via injected event primitives.

Verification gate

just _craftos-test runs the new tests/sandbox.lua green.