diff --git a/package-lock.json b/package-lock.json index f00a8f87a..775cb0c05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@aws-sdk/client-s3": "^3.1004.0", "@modelcontextprotocol/sdk": "^1.0.0", "@relaycast/mcp": "1.0.0", - "@relaycast/sdk": "1.0.0", + "@relaycast/sdk": "1.1.0", "@sinclair/typebox": "^0.34.14", "chalk": "^4.1.2", "chokidar": "^5.0.0", @@ -1308,7 +1308,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -3495,6 +3495,15 @@ "relaycast-mcp": "dist/stdio.js" } }, + "node_modules/@relaycast/mcp/node_modules/@relaycast/sdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-1.0.0.tgz", + "integrity": "sha512-s01xslec5xyDXxxkVDTJyHpRhzqlXC2gVoglvhu+HK1h5JeOKq13AFlhe2MszkxjJAQ0HJ36MItWXuGogbRdOg==", + "dependencies": { + "@relaycast/types": "1.0.0", + "zod": "^4.3.6" + } + }, "node_modules/@relaycast/mcp/node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", @@ -3505,11 +3514,19 @@ } }, "node_modules/@relaycast/sdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-1.0.0.tgz", - "integrity": "sha512-s01xslec5xyDXxxkVDTJyHpRhzqlXC2gVoglvhu+HK1h5JeOKq13AFlhe2MszkxjJAQ0HJ36MItWXuGogbRdOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-1.1.0.tgz", + "integrity": "sha512-9mCpcinrwNxA5BJjWtv3mrI8onior88bHtUZaSCaEaSlXwhhY1Q4Rw2JggOFuDYpBM7MumVHzuc6ZajVSwTYHw==", + "dependencies": { + "@relaycast/types": "1.1.0", + "zod": "^4.3.6" + } + }, + "node_modules/@relaycast/sdk/node_modules/@relaycast/types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@relaycast/types/-/types-1.1.0.tgz", + "integrity": "sha512-d0zByxvWK2PeZRnrhzbmDkE8Yar84/XH1H+89qGJj3lA82kTf/7Z76a2cqOIoxFXLgIjK959w9hTctXLem+lhA==", "dependencies": { - "@relaycast/types": "1.0.0", "zod": "^4.3.6" } }, @@ -15043,8 +15060,8 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@agent-relay/sdk": "3.2.18", - "@relaycast/sdk": "^1.0.0", + "@agent-relay/sdk": "^3.2.18", + "@relaycast/sdk": "^1.1.0", "ws": "^8.0.0" }, "bin": { @@ -15937,32 +15954,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "packages/sdk/node_modules/@relaycast/sdk": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-1.1.0.tgz", - "integrity": "sha512-9mCpcinrwNxA5BJjWtv3mrI8onior88bHtUZaSCaEaSlXwhhY1Q4Rw2JggOFuDYpBM7MumVHzuc6ZajVSwTYHw==", - "dependencies": { - "@relaycast/types": "1.1.0", - "zod": "^4.3.6" - } - }, - "packages/sdk/node_modules/@relaycast/types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@relaycast/types/-/types-1.1.0.tgz", - "integrity": "sha512-d0zByxvWK2PeZRnrhzbmDkE8Yar84/XH1H+89qGJj3lA82kTf/7Z76a2cqOIoxFXLgIjK959w9hTctXLem+lhA==", - "dependencies": { - "zod": "^4.3.6" - } - }, - "packages/sdk/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "packages/telemetry": { "name": "@agent-relay/telemetry", "version": "3.2.18", diff --git a/package.json b/package.json index c6c3ab8d1..1fc803ca3 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,7 @@ "@agent-relay/utils": "3.2.18", "@modelcontextprotocol/sdk": "^1.0.0", "@relaycast/mcp": "1.0.0", - "@relaycast/sdk": "1.0.0", + "@relaycast/sdk": "1.1.0", "@sinclair/typebox": "^0.34.14", "chalk": "^4.1.2", "chokidar": "^5.0.0", diff --git a/packages/openclaw/package.json b/packages/openclaw/package.json index 3c6185113..800d5de71 100644 --- a/packages/openclaw/package.json +++ b/packages/openclaw/package.json @@ -29,8 +29,8 @@ "postinstall": "node -e \"try{require('child_process').execSync('ldd --version 2>&1',{stdio:'pipe'})}catch{try{require('child_process').execSync('apk info gcompat 2>/dev/null',{stdio:'pipe'})}catch{console.warn('\\n\\u26a0\\ufe0f @agent-relay/openclaw: Alpine detected without gcompat. Spawning requires glibc.\\n Install with: apk add gcompat libstdc++\\n')}}\"" }, "dependencies": { - "@agent-relay/sdk": "3.2.18", - "@relaycast/sdk": "^1.0.0", + "@agent-relay/sdk": "^3.2.18", + "@relaycast/sdk": "^1.1.0", "ws": "^8.0.0" }, "optionalDependencies": { diff --git a/packages/openclaw/src/setup.ts b/packages/openclaw/src/setup.ts index 5a1277fe8..e9ef72858 100644 --- a/packages/openclaw/src/setup.ts +++ b/packages/openclaw/src/setup.ts @@ -203,46 +203,11 @@ export async function setup(options: SetupOptions): Promise { let apiKey = options.apiKey; if (!apiKey) { - try { - const res = await fetch(`${baseUrl}/v1/workspaces`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: `${clawName}-workspace` }), - }); + const workspaceName = `${clawName}-workspace`; - if (res.status === 409) { - // Workspace already exists — look up its API key - const lookupRes = await fetch(`${baseUrl}/v1/workspaces/by-name/${encodeURIComponent(`${clawName}-workspace`)}`, { - headers: { 'Content-Type': 'application/json' }, - }); - if (lookupRes.ok) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const lookupBody = (await lookupRes.json()) as any; - apiKey = lookupBody.apiKey ?? lookupBody.api_key ?? lookupBody.data?.apiKey ?? lookupBody.data?.api_key; - } - if (!apiKey) { - return { - ok: false, - apiKey: '', - clawName, - skillDir: '', - message: `Workspace "${clawName}-workspace" already exists. Pass the workspace key: @agent-relay/openclaw setup --name ${clawName}`, - }; - } - } else if (!res.ok) { - const body = await res.text(); - return { - ok: false, - apiKey: '', - clawName, - skillDir: '', - message: `Failed to create workspace: ${res.status} ${body}`, - }; - } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const successBody = (await res.json()) as any; - apiKey = successBody.apiKey ?? successBody.api_key ?? successBody.data?.apiKey ?? successBody.data?.api_key; - } + try { + const workspace = await RelayCast.ensureWorkspace(workspaceName, baseUrl); + apiKey = workspace.apiKey; if (!apiKey) { return { @@ -250,7 +215,9 @@ export async function setup(options: SetupOptions): Promise { apiKey: '', clawName, skillDir: '', - message: 'Workspace created but no API key returned.', + message: workspace.existed + ? `Workspace "${workspaceName}" already exists. Pass the workspace key: @agent-relay/openclaw setup --name ${clawName}` + : 'Workspace created but no API key returned.', }; } } catch (err) { diff --git a/src/cli/relaycast-mcp.startup.test.ts b/src/cli/relaycast-mcp.startup.test.ts index 9374a3b87..8242efe12 100644 --- a/src/cli/relaycast-mcp.startup.test.ts +++ b/src/cli/relaycast-mcp.startup.test.ts @@ -116,6 +116,11 @@ async function loadRelaycastMcpModule(options: LoadOptions = {}) { } } + const createWorkspace = vi.fn(async (name: string) => ({ + apiKey: 'rk_live_created', + workspaceName: name, + })); + const createInternalRelayCast = vi.fn((config: Record, origin: Record) => { const inbox = vi.fn(async (token?: string) => behavior.inboxImpl(String(token ?? ''))); const registerOrRotate = vi.fn(async (input: { name: string; type?: string }) => behavior.registerImpl(input)); @@ -160,6 +165,11 @@ async function loadRelaycastMcpModule(options: LoadOptions = {}) { vi.doMock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ McpServer: FakeMcpServer })); vi.doMock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: FakeTransport })); vi.doMock('@modelcontextprotocol/sdk/types.js', () => ({ ListToolsRequestSchema: { type: 'tools/list' } })); + vi.doMock('@relaycast/sdk', () => ({ + RelayCast: { + createWorkspace, + }, + })); vi.doMock('@relaycast/sdk/internal', () => ({ createInternalRelayCast, createInternalWsClient })); vi.doMock('@relaycast/mcp', () => ({ MCP_VERSION: 'test-mcp-version' })); vi.doMock('@relaycast/mcp/dist/piggyback.js', () => ({ enablePiggyback })); @@ -189,6 +199,7 @@ async function loadRelaycastMcpModule(options: LoadOptions = {}) { channelToolGetters, resourceGetters, telemetry, + createWorkspace, createInternalRelayCast, createInternalWsClient, enablePiggyback, @@ -239,16 +250,6 @@ describe('relaycast-mcp startup helpers', () => { describe('createPatchedRelayMcpServer', () => { it('registers startup tools, prompt text, and strips execution metadata from tools/list', async () => { const { mod, mocks } = await loadRelaycastMcpModule(); - vi.stubGlobal( - 'fetch', - vi.fn(async () => ({ - json: async () => ({ - ok: true, - data: { api_key: 'rk_live_created', workspace_name: 'Test Workspace' }, - }), - })) - ); - mod.createPatchedRelayMcpServer({ baseUrl: 'https://api.relaycast.dev/' }); const server = mocks.serverInstances[0]; const registerTool = server.tools.get('register'); @@ -275,13 +276,10 @@ describe('createPatchedRelayMcpServer', () => { ); const workspaceResult = await createWorkspaceTool?.handler({ name: 'Coverage Workspace' }); - expect(fetch).toHaveBeenCalledWith( - 'https://api.relaycast.dev/v1/workspaces', - expect.objectContaining({ method: 'POST' }) - ); + expect(mocks.createWorkspace).toHaveBeenCalledWith('Coverage Workspace', 'https://api.relaycast.dev'); expect(workspaceResult.structuredContent).toEqual({ - api_key: 'rk_live_created', - workspace_name: 'Test Workspace', + apiKey: 'rk_live_created', + workspaceName: 'Coverage Workspace', }); const registerResult = await registerTool?.handler({ diff --git a/src/cli/relaycast-mcp.ts b/src/cli/relaycast-mcp.ts index 1bcd341c8..2b091d7b6 100644 --- a/src/cli/relaycast-mcp.ts +++ b/src/cli/relaycast-mcp.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { RelayCast } from '@relaycast/sdk'; import { createInternalRelayCast, createInternalWsClient } from '@relaycast/sdk/internal'; import { MCP_VERSION } from '@relaycast/mcp'; import { enablePiggyback } from '@relaycast/mcp/dist/piggyback.js'; @@ -115,25 +116,7 @@ export function normalizeAgentType(value: string | undefined): AgentType | undef } async function createWorkspace(name: string, baseUrl?: string): Promise> { - const response = await fetch(`${normalizeBaseUrl(baseUrl)}/v1/workspaces`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name }), - }); - const payload = (await response.json()) as { - ok?: boolean; - data?: Record; - error?: { message?: string }; - } | null; - - if (!payload || typeof payload !== 'object' || typeof payload.ok !== 'boolean') { - throw new Error('Invalid response while creating workspace'); - } - if (!payload.ok) { - throw new Error(payload.error?.message ?? 'Failed to create workspace'); - } - - return payload.data ?? {}; + return await RelayCast.createWorkspace(name, normalizeBaseUrl(baseUrl)); } function requireWorkspaceKey(session: RegistrationSession): void {