Files
agentic-dev/apps/runner/AGENTS.md
Danijel Martinek b090e26701 feat(runner): WS protocol server with handshake
apps/runner scaffold (app-tier, walking-skeleton story 04): WS server
speaking @repo/core-runner-protocol. Every inbound/outbound frame is
envelope-wrapped and zod-parsed; hello/ready handshake gates on the
workspace-scoped token (constant-time compare, redacted token on
rejection replies); named error events for version/schema/auth
rejections. Config via env only (token never argv); port announced on
stdout for the story-06 provisioner. Runtime deps: ws (the standard
Node WS server; ADR-022 traces do not apply to app-tier) and zod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
2026-07-12 22:50:41 +02:00

2.4 KiB

apps/runner — workspace runner (cloud-runner process)

The runner process of ADR-027: executes a workspace's repo code (clone, install, scan, preview adapter) on behalf of the control plane, speaking @repo/core-runner-protocol over plain WS/JSON. App-tier (tags: ["app"]): imperative code is expected here; there is no feature manifest, no use-case layer, no DI container.

Process contract (what the provisioner — story 06 — relies on)

  • Config is env-only (see src/config.ts): RUNNER_TOKEN (required, secret — never argv), RUNNER_WORKSPACE_DIR (required), RUNNER_PORT (default 0 = ephemeral), RUNNER_HOST (default 127.0.0.1), RUNNER_HEARTBEAT_MS (default 1000).
  • Port discovery: on boot the runner writes a JSON line to stdout: {"level":"info","event":"listening","host":...,"port":...}.
  • Logs are JSON lines on stdout. Log event names + safe fields only — never protocol payloads, never credentials. Integration tests grep the entire child output for the PAT and the workspace token.
  • SIGTERM/SIGINT shut down gracefully.

Protocol session semantics (documented in src/server.ts)

  • Every inbound/outbound frame is envelope-wrapped and zod-parsed (envelopeSchema); rejections emit named errors (unsupported-protocol-version / invalid-message / unauthorized). Pre-handshake rejections and token mismatches close the socket (1008).
  • First message must be hello; the runner answers ready.
  • Commands run one at a time in arrival order. A running stage streams status (stage + elapsedMs: start, heartbeats, final). Success ends with ready (the idle marker the story-07 orchestrator drives on); failure ends with a named error and the session stays open.
  • Zod issues are summarized as path + code only — payload values (which may include a PAT) are never echoed into errors or logs.

Testing

  • Unit suites live next to sources in src/; protocol/WS suites live in tests/ (protocol-server.test.ts runs the real server in-process so v8 coverage sees it; runner-process.integration.test.ts spawns the real child process and discovers the port from stdout).
  • tests/protocol-client.ts envelope-parses every inbound frame, so every test doubles as an outbound-conformance assertion.
  • Coverage: app-tier vitest thresholds inherited from vitest.base.node; only src/main.ts (bootstrap glue, exercised by the spawn suite in a child process) is excluded.