feat(probe): resolve the workspace the agent-relay CLI selects (phase 2)

Merged
KH
6d ago
+2314-348

Phase 2 of moving the probe core into relay (stacked on #2012). The probe now finds its Relaycast workspace the way the agent-relay CLI does, so relay init's local server and self-hosted servers work with no Cloud sign-in.

What changed

  • relay::workspace::cli: a read-only CLI source. It never writes the CLI's files. It follows the CLI's ladder (resolveWorkspaceSelection + resolveWorkspaceTransport):
    1. RELAY_WORKSPACE_KEY > AGENT_RELAY_WORKSPACE_KEY > RELAY_API_KEY
    2. the project pin <project>/.agentworkforce/relay/workspace-key.json when AGENT_RELAY_PROJECT names the project. A malformed pin falls through. Persisted routes and their route credential (relaycastApiKeyRef → relaycast-credentials.json) get the CLI's checks.
    3. the active entry of $AGENT_RELAY_HOME/workspaces.json. If the file exists but can't be read, resolution fails.
  • relay::workspace::origin_binding: reads relaycast-origins.json using the CLI's PBKDF2-HMAC-SHA256 fingerprint (via the ring already linked; no new dependency). It is tested against packages/contracts/fixtures/relaycast-origin-binding.json, so TS, the broker and the probe all agree. The origin is chosen in this order: persisted route (Agent Relay gateways only; a key bound to another server is refused) → RELAY_BASE_URL → the binding → https://cast.agentrelay.com. http://127.0.0.1:8787-style origins and self-hosted ports and path prefixes are accepted. If the bindings file or RELAY_BASE_URL is malformed, resolution fails closed.
  • How the sources combine: the CLI source wins whenever it names a workspace. The Cloud sign-in is used only when the CLI names nothing. If the CLI's selection can't be used, the daemon has no workspace and logs a warning; it does not fall back to the account. Neither source's key is ever sent to the other's origin (RELAYCAST_URL re-origins only the account's key). Workspace.source records which source the key came from.
  • Refresh/verify/recovery: a key rotated in workspaces.json is picked up by the next 30 s rediscovery and verified. Cloud recovery runs only for Cloud-sourced keys.
  • One workspace per daemon (documented). About 40 call sites treat Core.workspace as the workspace, so per-project workspaces that differ from the machine's active one are a follow-up.
  • Prettier fix: the auto-format bot on #2012 reformatted assets that the core embeds verbatim (the OpenCode and Pi plugins, the Claude hook, the sign-in page, fixtures), which broke 2 OpenCode tests. This PR restores them and adds crates/probe/{src,resources,tests/fixtures} to .prettierignore.
  • Docs: docs/probe-move.md (phase 2 done) and crates/probe/docs/relay-core.md (Workspace discovery).

Behaviour change to note

A desktop that is signed in to Cloud and has an active CLI workspace now uses the CLI's workspace for messaging. Uploads still go to the account. This reaches desktop users only in phase 4.

Verification

  • HOME=<empty> cargo test --locked --no-fail-fast in crates/probe: everything passes except the two known ones: cloud::tests::cloud_bearer_reuses_only_a_matching_unexpired_official_cli_session (env-dependent) and live::selected_remote::tests::selected_claude_worker_refreshes_shallow_catalog_and_selected_evidence (flake). 41 new or updated workspace tests cover precedence, origin binding with the shared fixture, malformed files, localhost, persisted routes, the combination rule, rotation, and recovery gating.
  • Live run, fully isolated (scratch HOME, AGENT_RELAY_HOME, app-data dir and socket, env -i; no Cloud sign-in): npx -y agent-relay@next init --yes --tools none --port 8799, then agent-relay-probe relay serve --headless. relay call status.get shows base_url http://127.0.0.1:8799, verification: verified, workspace_name: local. With a corrupted relaycast-origins.json the probe reports state: missing and does not fall back.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Agent Relay sessions

  • claude session ad9499a6 · opened via gh pr create · last active 2026-10-28
  • claude session a7931a00 · contributor · ran gh pr commands · last active 2026-10-29