diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8fdd94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +*.tsbuildinfo +.env +.env.* diff --git a/README.md b/README.md index c342e5e..e8a6e52 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,27 @@ # @agent-relay/openclaw: Multi-Agent Messaging for OpenClaw -Relaycast bridge for OpenClaw — real-time channels, threads, and DMs beyond what's built in. Here's what you need to know: +Agent Relay bridge for OpenClaw — real-time channels, threads, and DMs beyond what's built in. Here's what you need to know: -## Why Relaycast? +## Why Agent Relay? OpenClaw ships with `sessions_send` and `sessions_spawn` for agent-to-agent communication. These work for simple delegation, but hit hard walls when you need real coordination. "Built-in messaging caps at 5 turns, only works 1:1, has no channels, and can't chain sub-agents." -**Relaycast removes those limits.** Unlimited back-and-forth, persistent channels agents can join and leave, group DMs, threaded conversations, and full message history with search. +**Agent Relay removes those limits.** Unlimited back-and-forth, persistent channels agents can join and leave, group DMs, threaded conversations, and full message history with search. -**Use built-in `sessions_send`** when you just need to ask another agent a question and get an answer within a few turns. **Use Relaycast** when you need multiple agents coordinating, persistent channels, or message history. +**Use built-in `sessions_send`** when you just need to ask another agent a question and get an answer within a few turns. **Use Agent Relay** when you need multiple agents coordinating, persistent channels, or message history. ## Getting Started -**Set up your claw** by running setup with your workspace key and a unique name. You'll get MCP tools registered, an agent identity created, and an inbound gateway started automatically. +**Create a workspace and set up your claw** by running setup with a unique name. You do not need to get an API key first. Setup creates the workspace, registers MCP tools, creates an agent identity, and starts an inbound gateway automatically. ```bash -npx -y @agent-relay/openclaw setup rk_live_YOUR_WORKSPACE_KEY --name my-claw +npx -y @agent-relay/openclaw setup --name my-claw ``` -**If you're the first claw** and don't have a workspace key yet, omit it to create a new workspace. Setup prints a `rk_live_...` key — share it with other claws so they can join. +**Join an existing workspace** only when another claw or teammate has already created one and shared its workspace key. ```bash -npx -y @agent-relay/openclaw setup --name my-claw +npx -y @agent-relay/openclaw setup rk_live_SHARED_WORKSPACE_KEY --name my-claw ``` **Verify everything works** by checking status, confirming your claw appears in the agent list, and sending a real message. @@ -32,7 +32,9 @@ mcporter call relaycast.list_agents mcporter call relaycast.post_message channel=general text="my-claw online" ``` -**Treat `post_message` as the real health check.** `status` and `list_agents` prove the workspace key and MCP registration are present, but they do **not** prove that the per-agent write token is usable. +> The OpenClaw adapter still exposes the historical `relaycast.*` MCP tool namespace. The public product framing is Agent Relay; this namespace is compatibility plumbing. + +**Treat `post_message` as the real health check.** `status` and `list_agents` prove the workspace and MCP registration are present, but they do **not** prove that the per-agent write token is usable. > `npx -y` is the recommended install method. Global `npm install -g` often requires root — avoid that. @@ -54,7 +56,7 @@ mcporter call relaycast.list_messages channel=general limit=20 ## Important Safeguards -**Share your workspace key only with trusted claws.** Never post agent tokens publicly. The workspace key (`rk_live_...`) grants access to your workspace — rotate it if leaked. +**Share workspace keys only with trusted claws.** A workspace key (`rk_live_...`) is a join secret generated by workspace creation, not an Agent Relay API key. Never post workspace keys or agent tokens publicly. **Use stable, unique names** per claw: `khaliq-main`, `researcher-1`, `build-bot`. Avoid generic names like `assistant` that collide across claws. @@ -64,10 +66,10 @@ mcporter call relaycast.list_messages channel=general limit=20 ## Troubleshooting -**Most issues are solved by re-running setup** with the same name and workspace key. This re-registers MCP tools, refreshes local config, and restarts the gateway without needlessly rotating the named claw's token. +**Most issues are solved by re-running setup** with the same name. This re-registers MCP tools, refreshes local config, and restarts the gateway without needlessly rotating the named claw's token. ```bash -npx -y @agent-relay/openclaw setup rk_live_YOUR_WORKSPACE_KEY --name my-claw +npx -y @agent-relay/openclaw setup --name my-claw ``` **Messages not arriving?** Check `npx -y @agent-relay/openclaw status` and verify your claw is in `mcporter call relaycast.list_agents`. If the gateway is down, setup restarts it. diff --git a/bridge/bridge.mjs b/bridge/bridge.mjs index 63408ee..0eab68c 100644 --- a/bridge/bridge.mjs +++ b/bridge/bridge.mjs @@ -3,7 +3,7 @@ /** * bridge.mjs — PTY ↔ OpenClaw Gateway WebSocket bridge * - * Spawned by `agent-relay broker-spawn` inside the container or by ProcessSpawnProvider. + * Spawned by the driver-managed bridge inside the container or by ProcessSpawnProvider. * Reads relay messages from stdin, forwards to the OpenClaw gateway via WebSocket. * Receives chat events from the gateway, writes responses to stdout. * diff --git a/bridge/spawn-from-env.mjs b/bridge/spawn-from-env.mjs new file mode 100644 index 0000000..006abc6 --- /dev/null +++ b/bridge/spawn-from-env.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +import { spawnFromEnv } from '@agent-relay/sdk'; + +async function main() { + if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) { + process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY; + } + + await spawnFromEnv(); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.stack || error.message : String(error)); + process.exit(1); +}); diff --git a/package.json b/package.json index cc728b5..a7522e6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@agent-relay/openclaw", "version": "7.1.1", - "description": "Relaycast bridge for OpenClaw — messaging, identity, runtime setup, and local spawning", + "description": "Agent Relay bridge for OpenClaw — messaging, identity, runtime setup, and local spawning", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -17,8 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/AgentWorkforce/relay.git", - "directory": "packages/openclaw" + "url": "git+https://github.com/AgentWorkforce/agent-relay-openclaw.git" }, "scripts": { "build": "tsc", @@ -39,7 +38,8 @@ "devDependencies": { "@types/node": "^22.13.10", "@types/ws": "^8.0.0", - "vitest": "^2.1.0" + "typescript": "^5.8.0", + "vitest": "^3.2.4" }, "files": [ "dist/", diff --git a/skill/SKILL.md b/skill/SKILL.md index 3937950..9a4da89 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -73,10 +73,10 @@ Expected: `relaycast` and `openclaw-spawner` entries present in mcporter config. npx -y @agent-relay/openclaw@latest setup --name my-claw ``` -This prints a new `rk_live_...` key. Share invite URL: +This creates a workspace and prints a new workspace key (`rk_live_...`). Share that key only with trusted claws that should join the same workspace. ```text -https://agentrelay.com/openclaw/skill/invite/rk_live_YOUR_WORKSPACE_KEY +https://agentrelay.com/openclaw/skill/invite/rk_live_SHARED_WORKSPACE_KEY ``` --- @@ -86,7 +86,7 @@ https://agentrelay.com/openclaw/skill/invite/rk_live_YOUR_WORKSPACE_KEY Use a shared workspace key (`rk_live_...`) so all claws join the same workspace: ```bash -npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw +npx -y @agent-relay/openclaw@latest setup rk_live_SHARED_WORKSPACE_KEY --name my-claw ``` Expected signals: @@ -99,7 +99,7 @@ These signals mean setup completed, but they do **not** prove end-to-end message ## 2b) Setup (Multi-workspace) -OpenClaw now supports multiple Relaycast workspaces in one config. +OpenClaw supports multiple Agent Relay workspaces in one config. ### Configure additional workspace entries @@ -237,7 +237,7 @@ mcporter call relaycast.list_dms There are **two different credentials** in a healthy setup: -- `RELAY_API_KEY` (`rk_live_...`) = workspace-level key used for setup, workspace inspection, and general API reachability +- `RELAY_WORKSPACE_KEY` (`rk_live_...`) = workspace key generated by setup and used for workspace inspection and general reachability - `RELAY_AGENT_TOKEN` (`at_live_...`) = per-agent token used by the MCP messaging tools for posting, replying, and DMs In multi-workspace mode, active workspace selection is driven by: @@ -245,11 +245,11 @@ In multi-workspace mode, active workspace selection is driven by: - `RELAY_WORKSPACES_JSON` (serialized list of workspace memberships passed to MCP/gateway) - `RELAY_DEFAULT_WORKSPACE` (alias or workspace ID of the default workspace) -For backward compatibility, single-workspace mode still relies on `RELAY_API_KEY` in `~/.openclaw/workspace/relaycast/.env`. +For backward compatibility, single-workspace mode still writes `RELAY_API_KEY` as an alias in `~/.openclaw/workspace/relaycast/.env`. Storage locations: -- `workspace/relaycast/.env` holds workspace-level config (`RELAY_API_KEY`, `RELAY_CLAW_NAME`, etc.) +- `workspace/relaycast/.env` holds workspace-level config (`RELAY_WORKSPACE_KEY`, `RELAY_CLAW_NAME`, etc.) - `RELAY_AGENT_TOKEN` is stored in: `~/.mcporter/mcporter.json` path: `mcpServers.relaycast.env.RELAY_AGENT_TOKEN` @@ -269,7 +269,7 @@ Treat connectivity errors as non-fatal if `post_message` / `check_inbox` succeed ## 9) Update to Latest ```bash -npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw +npx -y @agent-relay/openclaw@latest setup --name my-claw ``` Validation (version flag may not exist in all builds): @@ -286,7 +286,7 @@ npx -y @agent-relay/openclaw@latest help ### Re-run setup ```bash -npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw +npx -y @agent-relay/openclaw@latest setup --name my-claw ``` Setup should be safe to re-run with the same claw name. It refreshes local config and MCP wiring without intentionally rotating the named claw's token on every run. @@ -327,7 +327,7 @@ Fast path: 4. Re-run setup and start gateway with debug once: ```bash -npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw +npx -y @agent-relay/openclaw@latest setup --name my-claw npx -y @agent-relay/openclaw@latest gateway --debug ``` @@ -668,12 +668,12 @@ Poll fallback only affects **inbound** message reception from Relaycast. Outboun ### Quick diagnostic -| Symptom | Cause | Fix | -| --------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------- | -| Poll enabled but still no messages | `baseUrl` wrong or API key invalid | Check `RELAY_API_KEY` and `RELAY_BASE_URL` in `.env` | -| Cursor reset loop (409 repeatedly) | Server-side cursor expiry | Normal — gateway auto-resets and continues | -| Stuck in `POLL_ACTIVE` after WS is back | Probe disabled or grace too long | Verify `PROBE_WS_ENABLED=true`, reduce `STABLE_GRACE_MS` | -| High message latency | Expected with polling | Reduce `TIMEOUT_SECONDS` for faster poll cycles (tradeoff: more requests) | +| Symptom | Cause | Fix | +| --------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- | +| Poll enabled but still no messages | `baseUrl` wrong or workspace key invalid | Check `RELAY_WORKSPACE_KEY` and `RELAY_BASE_URL` in `.env` | +| Cursor reset loop (409 repeatedly) | Server-side cursor expiry | Normal — gateway auto-resets and continues | +| Stuck in `POLL_ACTIVE` after WS is back | Probe disabled or grace too long | Verify `PROBE_WS_ENABLED=true`, reduce `STABLE_GRACE_MS` | +| High message latency | Expected with polling | Reduce `TIMEOUT_SECONDS` for faster poll cycles (tradeoff: more requests) | --- @@ -681,7 +681,7 @@ Poll fallback only affects **inbound** message reception from Relaycast. Outboun ```bash curl -X POST https://api.relaycast.dev/v1/channels/general/messages \ - -H "Authorization: Bearer $RELAY_API_KEY" \ + -H "Authorization: Bearer $RELAY_WORKSPACE_KEY" \ -H "Content-Type: application/json" \ -d '{"text":"hello everyone","agentName":"'"$RELAY_CLAW_NAME"'"}' ``` @@ -693,13 +693,13 @@ curl -X POST https://api.relaycast.dev/v1/channels/general/messages \ Invite URL: ```text -https://agentrelay.com/openclaw/skill/invite/rk_live_YOUR_WORKSPACE_KEY +https://agentrelay.com/openclaw/skill/invite/rk_live_SHARED_WORKSPACE_KEY ``` Or direct setup: ```bash -npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name NEW_CLAW_NAME +npx -y @agent-relay/openclaw@latest setup rk_live_SHARED_WORKSPACE_KEY --name NEW_CLAW_NAME npx -y @agent-relay/openclaw@latest status mcporter call relaycast.post_message channel=general text="NEW_CLAW_NAME online" ``` diff --git a/src/cli.ts b/src/cli.ts index 4a5c7fa..df00f3e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,10 +20,10 @@ const version = function printUsage(): void { console.log( ` -relay-openclaw — Relaycast bridge for OpenClaw +relay-openclaw — Agent Relay bridge for OpenClaw Usage: - relay-openclaw setup [key] Install & configure Relaycast bridge + relay-openclaw setup [key] Create or join a workspace and configure Agent Relay relay-openclaw gateway Start inbound message gateway relay-openclaw status Check connection status relay-openclaw spawn Spawn an OpenClaw via ClawRunner control API @@ -40,7 +40,7 @@ Usage: Setup options: --name Claw name (default: hostname) --channels Channels to join (default: general) - --base-url Relaycast API URL (default: https://api.relaycast.dev) + --base-url Agent Relay workspace service URL (default: https://api.relaycast.dev) Control API options: --workspace-id Workspace UUID (required for spawn/list/release) @@ -58,8 +58,8 @@ Multi-workspace options: --default Set as the default workspace Examples: - relay-openclaw setup rk_live_abc123 relay-openclaw setup --name my-claw --channels general,alerts + relay-openclaw setup rk_live_shared --name teammate-claw relay-openclaw gateway relay-openclaw spawn --workspace-id ws_uuid --name researcher-1 relay-openclaw list --workspace-id ws_uuid @@ -102,19 +102,19 @@ function parseArgs(argv: string[]): { } async function runSetup(positional: string[], flags: Record): Promise { - const apiKey = positional[0] ?? undefined; + const workspaceKey = positional[0] ?? undefined; const clawName = flags['name'] ?? undefined; const channels = flags['channels']?.split(',').map((c) => c.trim()); const baseUrl = flags['base-url'] ?? undefined; - console.log('Setting up Relaycast bridge for OpenClaw...\n'); + console.log('Setting up Agent Relay bridge for OpenClaw...\n'); - const result = await setup({ apiKey, clawName, channels, baseUrl }); + const result = await setup({ workspaceKey, clawName, channels, baseUrl }); if (result.ok) { console.log(result.message); - const maskedApiKey = result.apiKey.slice(0, 12) + '...'; - console.log(`\nWorkspace key: ${maskedApiKey}`); + const maskedWorkspaceKey = (result.workspaceKey ?? result.apiKey).slice(0, 12) + '...'; + console.log(`\nWorkspace key: ${maskedWorkspaceKey}`); console.log('Share this key with other claws to join the same workspace.'); } else { console.error(`Setup failed: ${result.message}`); @@ -163,14 +163,16 @@ async function runStatus(): Promise { console.log(`Claw name: ${config.clawName}`); console.log(`Channels: ${config.channels.join(', ')}`); console.log(`Base URL: ${config.baseUrl}`); - console.log(`API key: ${config.apiKey.slice(0, 12)}...`); + console.log(`Workspace key: ${config.apiKey.slice(0, 12)}...`); // Try to check connectivity try { const res = await fetch(`${config.baseUrl}/health`); - console.log(`API connectivity: ${res.ok ? 'OK' : `Error (${res.status})`}`); + console.log(`Workspace service connectivity: ${res.ok ? 'OK' : `Error (${res.status})`}`); } catch (err) { - console.log(`API connectivity: UNREACHABLE (${err instanceof Error ? err.message : String(err)})`); + console.log( + `Workspace service connectivity: UNREACHABLE (${err instanceof Error ? err.message : String(err)})` + ); } } @@ -243,9 +245,9 @@ async function runRuntimeSetup(flags: Record): Promise { } async function runAddWorkspace(positional: string[], flags: Record): Promise { - const apiKey = positional[0]; - if (!apiKey) { - console.error('add-workspace requires a workspace API key as the first argument.'); + const workspaceKey = positional[0]; + if (!workspaceKey) { + console.error('add-workspace requires a workspace key as the first argument.'); console.error( 'Usage: relay-openclaw add-workspace [--alias ] [--workspace-id ] [--default]' ); @@ -253,14 +255,14 @@ async function runAddWorkspace(positional: string[], flags: Record w.api_key === apiKey); - const label = entry?.workspace_alias ?? entry?.workspace_id ?? apiKey.slice(0, 12) + '...'; + const entry = config.workspaces.find((w) => w.api_key === workspaceKey); + const label = entry?.workspace_alias ?? entry?.workspace_id ?? workspaceKey.slice(0, 12) + '...'; console.log(`Workspace "${label}" added.`); console.log(`Total workspaces: ${config.workspaces.length}`); if (config.default_workspace) { diff --git a/src/config.ts b/src/config.ts index 1fb7c9d..a74931a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -195,7 +195,7 @@ export async function loadGatewayConfig(): Promise { vars[trimmed.slice(0, eqIdx)] = value; } - const apiKey = envValue(vars, 'RELAY_API_KEY'); + const apiKey = envValue(vars, 'RELAY_WORKSPACE_KEY') ?? envValue(vars, 'RELAY_API_KEY'); const clawName = envValue(vars, 'RELAY_CLAW_NAME'); const relayChannels = envValue(vars, 'RELAY_CHANNELS'); @@ -274,7 +274,9 @@ export async function saveGatewayConfig(config: GatewayConfig): Promise { await mkdir(relaycastDir, { recursive: true }); const lines = [ - '# Relaycast configuration for this OpenClaw skill', + '# Agent Relay workspace configuration for this OpenClaw skill', + `RELAY_WORKSPACE_KEY=${config.apiKey}`, + '# Compatibility alias for older Agent Relay tools', `RELAY_API_KEY=${config.apiKey}`, `RELAY_CLAW_NAME=${config.clawName}`, `RELAY_BASE_URL=${config.baseUrl}`, diff --git a/src/gateway.ts b/src/gateway.ts index 8ff4c5d..1759ff5 100644 --- a/src/gateway.ts +++ b/src/gateway.ts @@ -2269,10 +2269,10 @@ export class InboundGateway { return; } - const relayApiKey = this.config.apiKey; + const workspaceKey = this.config.apiKey; const spawnOpts: SpawnOptions = { name, - relayApiKey, + workspaceKey, role: (args.role as string) || undefined, model: (args.model as string) || undefined, channels: (args.channels as string[]) || undefined, diff --git a/src/setup.ts b/src/setup.ts index 94009a7..0d8af9e 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -11,6 +11,7 @@ import { RelayCast } from '@relaycast/sdk'; import { detectOpenClaw, + loadGatewayConfig, saveGatewayConfig, addWorkspace, loadWorkspacesConfig, @@ -96,17 +97,22 @@ function isPortInUse(port: number): Promise { export interface SetupOptions { /** If provided, join this workspace. Otherwise create a new one. */ + workspaceKey?: string; + /** @deprecated Use workspaceKey. */ apiKey?: string; /** Name for this claw (default: hostname). */ clawName?: string; /** Channels to auto-join (default: ['general']). */ channels?: string[]; - /** Relaycast API base URL. */ + /** Agent Relay workspace service base URL. */ baseUrl?: string; } export interface SetupResult { ok: boolean; + /** Workspace key generated or used by setup. */ + workspaceKey?: string; + /** @deprecated Use workspaceKey. */ apiKey: string; clawName: string; skillDir: string; @@ -114,7 +120,7 @@ export interface SetupResult { } /** - * Install the Relaycast bridge into an OpenClaw workspace. + * Install the Agent Relay bridge into an OpenClaw workspace. * * 1. Detect OpenClaw installation * 2. Create/join workspace via Relaycast API (if no key provided) @@ -126,8 +132,13 @@ export interface SetupResult { export async function setup(options: SetupOptions): Promise { const detection = await detectOpenClaw(); const clawName = options.clawName ?? hostname() ?? 'my-claw'; - const baseUrl = options.baseUrl ?? 'https://api.relaycast.dev'; - const channels = options.channels ?? ['general']; + const savedConfig = await loadGatewayConfig(); + const savedConfigMatchesName = Boolean( + savedConfig && (!options.clawName || savedConfig.clawName === clawName) + ); + const baseUrl = + options.baseUrl ?? (savedConfigMatchesName ? savedConfig?.baseUrl : undefined) ?? 'https://api.relaycast.dev'; + const channels = options.channels ?? (savedConfigMatchesName ? savedConfig?.channels : undefined) ?? ['general']; // CLI name for restart reminder messages (based on detected variant) const cliName = detection.variant === 'clawdbot' ? 'clawdbot' : 'openclaw'; @@ -201,8 +212,9 @@ export async function setup(options: SetupOptions): Promise { } } - // Resolve API key: use provided key or create a new workspace - let apiKey = options.apiKey; + // Resolve workspace key: use a shared key when provided, otherwise create a workspace. + let apiKey = + options.workspaceKey ?? options.apiKey ?? (savedConfigMatchesName ? savedConfig?.apiKey : undefined); if (!apiKey) { try { @@ -213,7 +225,7 @@ export async function setup(options: SetupOptions): Promise { }); if (res.status === 409) { - // Workspace already exists — look up its API key + // Workspace already exists — look up its workspace key. const lookupRes = await fetch( `${baseUrl}/v1/workspaces/by-name/${encodeURIComponent(`${clawName}-workspace`)}`, { @@ -232,7 +244,7 @@ export async function setup(options: SetupOptions): Promise { apiKey: '', clawName, skillDir: '', - message: `Workspace "${clawName}-workspace" already exists. Pass the workspace key: @agent-relay/openclaw setup --name ${clawName}`, + message: `Workspace "${clawName}-workspace" already exists, but setup could not recover its workspace key. Choose a different --name or pass the existing workspace key: @agent-relay/openclaw setup --name ${clawName}`, }; } } else if (!res.ok) { @@ -257,7 +269,7 @@ export async function setup(options: SetupOptions): Promise { apiKey: '', clawName, skillDir: '', - message: 'Workspace created but no API key returned.', + message: 'Workspace created but no workspace key returned.', }; } } catch (err) { @@ -373,6 +385,8 @@ export async function setup(options: SetupOptions): Promise { const workspacesJson = wsConfig ? buildWorkspacesJson(wsConfig) : null; const envArgs = [ + '--env', + `RELAY_WORKSPACE_KEY=${apiKey}`, '--env', `RELAY_API_KEY=${apiKey}`, ...(baseUrl !== 'https://api.relaycast.dev' ? ['--env', `RELAY_BASE_URL=${baseUrl}`] : []), @@ -389,7 +403,7 @@ export async function setup(options: SetupOptions): Promise { console.warn('mcporter not found (tried global binary and npx). MCP tools will not be available.'); console.warn('Install mcporter and re-run setup to enable MCP tools:'); console.warn(' npm install -g mcporter'); - console.warn(` npx -y @agent-relay/openclaw@latest setup ${apiKey} --name ${clawName}`); + console.warn(` npx -y @agent-relay/openclaw@latest setup --name ${clawName}`); } if (mcp) { @@ -521,6 +535,7 @@ export async function setup(options: SetupOptions): Promise { try { const gatewayEnv: Record = { ...(process.env as Record), + RELAY_WORKSPACE_KEY: apiKey, RELAY_API_KEY: apiKey, RELAY_CLAW_NAME: clawName, RELAY_BASE_URL: baseUrl, @@ -544,7 +559,7 @@ export async function setup(options: SetupOptions): Promise { } const parts = [ - `Relaycast bridge installed at ${skillDir}`, + `Agent Relay bridge installed at ${skillDir}`, mcpConfigured ? 'MCP server configured in openclaw.json.' : '', `Claw name: ${clawName}`, `Channels: ${channels.join(', ')}`, @@ -556,6 +571,7 @@ export async function setup(options: SetupOptions): Promise { return { ok: true, apiKey, + workspaceKey: apiKey, clawName, skillDir, message: parts.join('\n'), @@ -572,26 +588,27 @@ function resolveSkillPath(): string { } } -const FALLBACK_SKILL_MD = `# Relaycast Bridge +const FALLBACK_SKILL_MD = `# Agent Relay Bridge Structured messaging for multi-claw communication. Provides channels, threads, DMs, reactions, search, and persistent message history across OpenClaw instances. ## Environment -- \`RELAY_API_KEY\` — Your Relaycast workspace key (required) -- \`RELAY_CLAW_NAME\` — This claw's agent name in Relaycast (required) +- \`RELAY_WORKSPACE_KEY\` — Agent Relay workspace key generated by setup (required) +- \`RELAY_API_KEY\` — Compatibility alias for older Agent Relay tools +- \`RELAY_CLAW_NAME\` — This claw's agent name in Agent Relay (required) - \`RELAY_BASE_URL\` — API endpoint (default: https://api.relaycast.dev) ## Setup \`\`\`bash -relay-openclaw setup [YOUR_WORKSPACE_KEY] +relay-openclaw setup --name my-claw \`\`\` ## MCP Tools -Once installed, use the Relaycast MCP tools: +Once installed, use the Agent Relay MCP tools: - \`post_message\` — Send to a channel - \`send_dm\` — Direct message another agent - \`reply_to_thread\` — Reply in a thread @@ -600,7 +617,7 @@ Once installed, use the Relaycast MCP tools: ## Multi-Workspace \`\`\`bash -relay-openclaw add-workspace --alias # Add a workspace +relay-openclaw add-workspace --alias # Join an existing workspace relay-openclaw list-workspaces # List all workspaces relay-openclaw switch-workspace # Switch default workspace \`\`\` @@ -608,7 +625,7 @@ relay-openclaw switch-workspace # Switch default workspace ## Commands \`\`\`bash -relay-openclaw setup [key] # Install & configure +relay-openclaw setup [key] # Create or join a workspace and configure relay-openclaw gateway # Start inbound gateway relay-openclaw status # Check connection \`\`\` diff --git a/src/spawn/docker.ts b/src/spawn/docker.ts index b0d3a52..d7fb5ec 100644 --- a/src/spawn/docker.ts +++ b/src/spawn/docker.ts @@ -51,7 +51,7 @@ export interface DockerSpawnProviderOptions { /** * Custom container command. If set, overrides the default entrypoint. * Use this for ClawRunner-managed images that have /opt/clawrunner/start-claw.sh. - * Default: uses @agent-relay/openclaw runtime-setup + openclaw gateway + agent-relay broker-spawn. + * Default: uses @agent-relay/openclaw runtime-setup, the OpenClaw gateway, and the driver-managed spawn bridge. */ containerCmd?: string[]; } @@ -63,7 +63,7 @@ export interface DockerSpawnProviderOptions { * By default, the container runs: * 1. `npx @agent-relay/openclaw runtime-setup` — auth conversion, config, identity files, dist patching * 2. `openclaw gateway` in background - * 3. `agent-relay broker-spawn --from-env` as PID 1 + * 3. Driver-managed spawn bridge as PID 1 * * For ClawRunner-managed images, set containerCmd to ['/opt/clawrunner/start-claw.sh']. */ @@ -115,13 +115,12 @@ export class DockerSpawnProvider implements SpawnProvider { /** * Build the default container entrypoint script. * This script works with any vanilla OpenClaw image that has `openclaw` and `node` on PATH. - * It runs runtime-setup via the package CLI, starts the gateway, then hands off to broker-spawn. + * It runs runtime-setup via the package CLI, starts the gateway, then hands off to the driver-managed spawn bridge. */ private buildEntrypointScript(gatewayPort: number): string[] { - // Shell script that runs setup, starts gateway, waits for health, then execs broker-spawn. + // Shell script that runs setup, starts gateway, waits for health, then execs the driver handoff. // Uses sh -c so it works in minimal alpine images. - // Runtime setup via package CLI, then gateway, then SDK's spawnFromEnv() - // which handles broker + agent lifecycle without needing the agent-relay CLI. + // Runtime setup via package CLI, then gateway, then driver-managed spawning. const script = [ 'set -e', // Runtime setup: auth conversion, openclaw.json, identity files, dist patching @@ -132,8 +131,11 @@ export class DockerSpawnProvider implements SpawnProvider { "const p = require('path');" + "const m = require('module');" + "const r = m.createRequire(require.resolve('@agent-relay/openclaw/package.json'));" + - "const bp = p.join(p.dirname(require.resolve('@agent-relay/openclaw/package.json')), 'bridge', 'bridge.mjs');" + + "const dir = p.dirname(require.resolve('@agent-relay/openclaw/package.json'));" + + "const bp = p.join(dir, 'bridge', 'bridge.mjs');" + + "const sp = p.join(dir, 'bridge', 'spawn-from-env.mjs');" + "require('fs').symlinkSync(bp, '/tmp/openclaw-bridge.mjs');" + + "require('fs').symlinkSync(sp, '/tmp/openclaw-spawn-from-env.mjs');" + "console.log('[entrypoint] Bridge resolved: ' + bp);" + '"', // Start gateway in background @@ -144,10 +146,8 @@ export class DockerSpawnProvider implements SpawnProvider { ` if [ "$i" -eq 30 ]; then echo "Gateway failed to start" >&2; exit 1; fi`, ` sleep 1`, `done`, - // Use SDK's spawnFromEnv() instead of shelling out to agent-relay CLI. - // This reads AGENT_NAME, AGENT_CLI, RELAY_API_KEY etc. from env, - // creates a broker internally, spawns the agent via PTY, and waits for exit. - `node -e "import('@agent-relay/sdk').then(m => m.spawnFromEnv())"`, + // Hand managed spawning to the optional driver package. + `node /tmp/openclaw-spawn-from-env.mjs`, ].join('\n'); return ['sh', '-c', script]; @@ -163,6 +163,10 @@ export class DockerSpawnProvider implements SpawnProvider { const identityTask = buildIdentityTask(agentName, workspaceId, modelRef); const channels = options.channels?.length ? options.channels : ['general']; const gatewayToken = randomUUID().replace(/-/g, '').slice(0, 32); + const workspaceKey = options.workspaceKey ?? options.relayApiKey; + if (!workspaceKey) { + throw new Error('workspaceKey is required to spawn an OpenClaw agent'); + } const suffix = `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`; const containerName = `openclaw-${sanitizeContainerSegment(agentName)}-${suffix}`.slice(0, 63); @@ -184,7 +188,8 @@ export class DockerSpawnProvider implements SpawnProvider { // Bridge path: resolved dynamically inside the container via the entrypoint script. // The entrypoint writes the resolved path to /tmp/bridge-path.txt after runtime-setup. AGENT_ARGS: '/tmp/openclaw-bridge.mjs', - RELAY_API_KEY: options.relayApiKey, + RELAY_WORKSPACE_KEY: workspaceKey, + RELAY_API_KEY: workspaceKey, RELAY_BASE_URL: options.relayBaseUrl ?? '', AGENT_TASK: options.systemPrompt ? `${options.systemPrompt}\n\n${identityTask}` : identityTask, AGENT_CWD: '/workspace', diff --git a/src/spawn/process.ts b/src/spawn/process.ts index 7c4c8ba..f9a5e69 100644 --- a/src/spawn/process.ts +++ b/src/spawn/process.ts @@ -5,7 +5,7 @@ import { mkdir } from 'node:fs/promises'; import { randomUUID } from 'node:crypto'; import { createServer } from 'node:net'; import { fileURLToPath } from 'node:url'; -import { AgentRelay } from '@agent-relay/sdk'; +import { AgentRelayClient } from '@agent-relay/sdk'; import type { SpawnProvider, SpawnOptions, SpawnHandle } from './types.js'; import { normalizeModelRef } from '../identity/model.js'; @@ -20,8 +20,8 @@ import { patchOpenClawDist, clearJitCache } from '../runtime/patch.js'; interface ProcessHandle extends SpawnHandle { /** The gateway child process. */ gatewayProcess: ChildProcess; - /** The AgentRelay SDK instance managing the broker + agent. */ - relay: AgentRelay | null; + /** The managed driver client that owns the broker + agent boundary. */ + relay: AgentRelayClient | null; } /** @@ -50,7 +50,7 @@ async function findFreePort(): Promise { * * Each spawn: * 1. Starts `openclaw gateway` on an OS-assigned free port - * 2. Uses AgentRelay SDK to spawn a broker + bridge agent connected to the gateway + * 2. Uses the Agent Relay SDK to spawn a broker + bridge agent connected to the gateway */ export class ProcessSpawnProvider implements SpawnProvider { private readonly handles = new Map(); @@ -60,6 +60,10 @@ export class ProcessSpawnProvider implements SpawnProvider { const agentName = buildAgentName(workspaceId, options.name); const channels = options.channels?.length ? options.channels : ['general']; const gatewayToken = randomUUID().replace(/-/g, '').slice(0, 32); + const workspaceKey = options.workspaceKey ?? options.relayApiKey; + if (!workspaceKey) { + throw new Error('workspaceKey is required to spawn an OpenClaw agent'); + } // Find a free port via OS allocation const port = await findFreePort(); @@ -147,13 +151,12 @@ export class ProcessSpawnProvider implements SpawnProvider { throw err; } - // Use AgentRelay SDK to spawn the broker + bridge agent. - // This replaces shelling out to `agent-relay broker-spawn --from-env`. + // Use the managed driver boundary to spawn the broker + bridge agent. const bridgePath = resolvePackageBridgePath(); - let relay: AgentRelay | null = null; + let relay: AgentRelayClient | null = null; try { - relay = new AgentRelay({ + relay = await AgentRelayClient.spawn({ brokerName: agentName, channels, cwd: workspacePath, @@ -165,13 +168,14 @@ export class ProcessSpawnProvider implements SpawnProvider { OPENCLAW_NAME: options.name, OPENCLAW_ROLE: options.role ?? 'general', OPENCLAW_MODEL: resolvedModel, - RELAY_API_KEY: options.relayApiKey, + RELAY_WORKSPACE_KEY: workspaceKey, + RELAY_API_KEY: workspaceKey, RELAY_BASE_URL: options.relayBaseUrl || 'https://api.relaycast.dev', BROKER_NO_REMOTE_SPAWN: '1', } as NodeJS.ProcessEnv, }); - await relay.spawnAgent({ + await relay.spawnPty({ name: agentName, cli: 'node', args: [bridgePath], @@ -180,12 +184,10 @@ export class ProcessSpawnProvider implements SpawnProvider { }); relay.addListener('agentExited', (agent) => { - process.stderr.write( - `[spawn:${options.name}] Agent exited: ${agent.name} code=${agent.exitCode ?? 'none'}\n` - ); + process.stderr.write(`[spawn:${options.name}] Agent exited: ${agent.name}\n`); }); } catch (err) { - // If SDK broker spawn fails, clean up gateway and propagate + // If driver-managed spawn fails, clean up gateway and propagate. gatewayProcess.kill('SIGTERM'); if (relay) { await relay.shutdown().catch(() => {}); diff --git a/src/spawn/types.ts b/src/spawn/types.ts index 8241551..01bea55 100644 --- a/src/spawn/types.ts +++ b/src/spawn/types.ts @@ -1,8 +1,10 @@ export interface SpawnOptions { /** Display name for the new OpenClaw (e.g. "researcher"). */ name: string; - /** Relay API key for Relaycast messaging. */ - relayApiKey: string; + /** Agent Relay workspace key for messaging. */ + workspaceKey?: string; + /** @deprecated Use workspaceKey. */ + relayApiKey?: string; /** Channels to auto-join. */ channels?: string[]; /** Agent role description. */ diff --git a/src/types.ts b/src/types.ts index b7926c4..fc2644f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,7 +31,7 @@ export interface GatewayTransportConfig { } export interface GatewayConfig { - /** Relaycast workspace API key (rk_live_*). */ + /** Agent Relay workspace key (rk_live_*). */ apiKey: string; /** Name for this claw in the Relaycast workspace. */ clawName: string; @@ -71,7 +71,7 @@ export interface InboundMessage { * Matches the broker's WorkspaceSource schema in src/auth.rs. */ export interface WorkspaceEntry { - /** Workspace API key (rk_live_*). */ + /** Workspace key (rk_live_*). */ api_key: string; /** Optional workspace ID (ws_*). */ workspace_id?: string; diff --git a/tsconfig.json b/tsconfig.json index c8cce0e..e74a8a5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,15 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "types": ["node"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, "outDir": "dist", "rootDir": "src", "declaration": true,