agent-relay 3.2.2 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +1358 -941
- package/dist/src/cli/commands/agent-management.d.ts +2 -2
- package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
- package/dist/src/cli/commands/agent-management.js +41 -240
- package/dist/src/cli/commands/agent-management.js.map +1 -1
- package/dist/src/cli/commands/messaging.d.ts +1 -1
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js +14 -5
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.js +27 -2
- package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
- package/package.json +11 -10
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/ADAPTER_REVIEW.md +109 -0
- package/packages/sdk/dist/client.d.ts +66 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +230 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.js +220 -0
- package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
- package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.js +209 -0
- package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.js +7 -0
- package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
- package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
- package/packages/sdk/dist/communicate/core.d.ts +58 -0
- package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/core.js +128 -0
- package/packages/sdk/dist/communicate/core.js.map +1 -0
- package/packages/sdk/dist/communicate/index.d.ts +4 -0
- package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/index.js +4 -0
- package/packages/sdk/dist/communicate/index.js.map +1 -0
- package/packages/sdk/dist/communicate/transport.d.ts +36 -0
- package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/transport.js +371 -0
- package/packages/sdk/dist/communicate/transport.js.map +1 -0
- package/packages/sdk/dist/communicate/types.d.ts +58 -0
- package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/types.js +66 -0
- package/packages/sdk/dist/communicate/types.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +35 -5
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +81 -7
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +14 -1
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +95 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +11 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
- package/packages/sdk/examples/communicate/pi_example.ts +8 -0
- package/packages/sdk/package.json +48 -2
- package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
- package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
- package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
- package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
- package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
- package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
- package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
- package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
- package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
- package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
- package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
- package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
- package/packages/sdk/src/client.ts +301 -0
- package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
- package/packages/sdk/src/communicate/a2a-server.ts +277 -0
- package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
- package/packages/sdk/src/communicate/a2a-types.ts +338 -0
- package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
- package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
- package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
- package/packages/sdk/src/communicate/adapters/index.ts +6 -0
- package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
- package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
- package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
- package/packages/sdk/src/communicate/core.ts +157 -0
- package/packages/sdk/src/communicate/index.ts +3 -0
- package/packages/sdk/src/communicate/transport.ts +489 -0
- package/packages/sdk/src/communicate/types.ts +106 -0
- package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
- package/packages/sdk/src/workflows/builder.ts +97 -9
- package/packages/sdk/src/workflows/cli.ts +16 -1
- package/packages/sdk/src/workflows/runner.ts +110 -1
- package/packages/sdk/src/workflows/types.ts +14 -0
- package/packages/sdk/tsconfig.build.json +1 -7
- package/packages/sdk/tsconfig.json +1 -7
- package/packages/sdk-py/README.md +67 -25
- package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
- package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
- package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
- package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
- package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
- package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
- package/packages/sdk-py/pyproject.toml +12 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
- package/packages/sdk-py/src/agent_relay/builder.py +65 -26
- package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
- package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
- package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
- package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
- package/packages/sdk-py/src/agent_relay/types.py +2 -1
- package/packages/sdk-py/tests/communicate/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
- package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
- package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
- package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
- package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
- package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
- package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
- package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
- package/packages/sdk-py/tests/communicate/conftest.py +555 -0
- package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
- package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
- package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
- package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
- package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
- package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
- package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
- package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
- package/packages/sdk-py/tests/communicate/test_core.py +331 -0
- package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
- package/packages/sdk-py/tests/communicate/test_types.py +285 -0
- package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.js +0 -305
- package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.js +0 -205
- package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.js +0 -20
- package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
- package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.js +0 -357
- package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
- package/packages/sdk-py/agent_relay/__init__.py +0 -21
- package/packages/sdk-py/agent_relay/models.py +0 -398
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
4
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
8
|
+
|
|
9
|
+
const coreModulePath = '../../../communicate/core.js';
|
|
10
|
+
const piAdapterModulePath = '../../../communicate/adapters/pi.js';
|
|
11
|
+
const claudeAdapterModulePath = '../../../communicate/adapters/claude-sdk.js';
|
|
12
|
+
|
|
13
|
+
async function loadCoreModule(): Promise<any> {
|
|
14
|
+
return import(coreModulePath);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function loadPiAdapterModule(): Promise<any> {
|
|
18
|
+
return import(piAdapterModulePath);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function loadClaudeAdapterModule(): Promise<any> {
|
|
22
|
+
return import(claudeAdapterModulePath);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function waitFor<T>(run: () => Promise<T | undefined>, timeoutMs = 1_000): Promise<T> {
|
|
26
|
+
const startedAt = Date.now();
|
|
27
|
+
let lastValue: T | undefined;
|
|
28
|
+
|
|
29
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
30
|
+
lastValue = await run();
|
|
31
|
+
if (lastValue !== undefined) {
|
|
32
|
+
return lastValue;
|
|
33
|
+
}
|
|
34
|
+
await sleep(10);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new Error(`Timed out waiting for async condition. Last value: ${String(lastValue)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readJson(request: IncomingMessage): Promise<any> {
|
|
41
|
+
const chunks: Buffer[] = [];
|
|
42
|
+
for await (const chunk of request) {
|
|
43
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
44
|
+
}
|
|
45
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
46
|
+
return raw.length > 0 ? JSON.parse(raw) : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sendJson(response: ServerResponse, statusCode: number, payload: unknown): void {
|
|
50
|
+
response.statusCode = statusCode;
|
|
51
|
+
response.setHeader('content-type', 'application/json');
|
|
52
|
+
response.end(JSON.stringify(payload));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class MockRelayServer {
|
|
56
|
+
readonly apiKey = 'test-key';
|
|
57
|
+
readonly workspace = 'test-workspace';
|
|
58
|
+
readonly inboxes = new Map<string, any[]>();
|
|
59
|
+
readonly registeredAgents = new Map<string, { name: string; token: string }>();
|
|
60
|
+
|
|
61
|
+
private server = createServer(this.handleRequest.bind(this));
|
|
62
|
+
private wsServer = new WebSocketServer({ noServer: true });
|
|
63
|
+
private nextAgentId = 1;
|
|
64
|
+
private nextMessageId = 1;
|
|
65
|
+
private readonly websockets = new Map<string, WebSocket>();
|
|
66
|
+
|
|
67
|
+
baseUrl = '';
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
this.server.on('upgrade', this.handleUpgrade.bind(this));
|
|
71
|
+
this.wsServer.on('connection', (socket, request) => {
|
|
72
|
+
const url = new URL(request.url ?? '/', 'http://127.0.0.1');
|
|
73
|
+
const agentId = url.pathname.split('/').at(-1);
|
|
74
|
+
if (!agentId) {
|
|
75
|
+
socket.close();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.websockets.set(agentId, socket);
|
|
80
|
+
socket.on('close', () => {
|
|
81
|
+
if (this.websockets.get(agentId) === socket) {
|
|
82
|
+
this.websockets.delete(agentId);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async start(): Promise<void> {
|
|
89
|
+
this.server.listen(0, '127.0.0.1');
|
|
90
|
+
await once(this.server, 'listening');
|
|
91
|
+
const address = this.server.address();
|
|
92
|
+
if (!address || typeof address === 'string') {
|
|
93
|
+
throw new Error('Failed to start mock Relaycast server.');
|
|
94
|
+
}
|
|
95
|
+
this.baseUrl = `http://127.0.0.1:${address.port}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async stop(): Promise<void> {
|
|
99
|
+
for (const socket of this.websockets.values()) {
|
|
100
|
+
socket.close();
|
|
101
|
+
}
|
|
102
|
+
this.wsServer.close();
|
|
103
|
+
this.server.close();
|
|
104
|
+
await once(this.server, 'close');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
makeConfig() {
|
|
108
|
+
return {
|
|
109
|
+
workspace: this.workspace,
|
|
110
|
+
apiKey: this.apiKey,
|
|
111
|
+
baseUrl: this.baseUrl,
|
|
112
|
+
autoCleanup: false,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
findAgentId(name: string): string | undefined {
|
|
117
|
+
for (const [agentId, registration] of this.registeredAgents.entries()) {
|
|
118
|
+
if (registration.name === name) {
|
|
119
|
+
return agentId;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async waitForAgent(name: string): Promise<string> {
|
|
126
|
+
return waitFor(async () => {
|
|
127
|
+
const agentId = this.findAgentId(name);
|
|
128
|
+
return agentId;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private queueInboxByName(name: string, payload: any): void {
|
|
133
|
+
const agentId = this.findAgentId(name);
|
|
134
|
+
if (!agentId) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const current = this.inboxes.get(agentId) ?? [];
|
|
139
|
+
current.push(payload);
|
|
140
|
+
this.inboxes.set(agentId, current);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async handleRequest(request: IncomingMessage, response: ServerResponse): Promise<void> {
|
|
144
|
+
const url = new URL(request.url ?? '/', this.baseUrl || 'http://127.0.0.1');
|
|
145
|
+
const pathname = url.pathname;
|
|
146
|
+
|
|
147
|
+
if (request.headers.authorization !== `Bearer ${this.apiKey}`) {
|
|
148
|
+
sendJson(response, 401, { message: 'Unauthorized' });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (request.method === 'POST' && pathname === '/v1/agents/register') {
|
|
153
|
+
const json = await readJson(request);
|
|
154
|
+
const agentId = `agent-${this.nextAgentId++}`;
|
|
155
|
+
const token = `token-${agentId}`;
|
|
156
|
+
this.registeredAgents.set(agentId, { name: json.name, token });
|
|
157
|
+
sendJson(response, 200, { agent_id: agentId, token });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (request.method === 'DELETE' && pathname.startsWith('/v1/agents/')) {
|
|
162
|
+
const agentId = pathname.split('/').at(-1)!;
|
|
163
|
+
this.registeredAgents.delete(agentId);
|
|
164
|
+
this.inboxes.delete(agentId);
|
|
165
|
+
this.websockets.get(agentId)?.close();
|
|
166
|
+
sendJson(response, 204, {});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (request.method === 'POST' && pathname === '/v1/messages/dm') {
|
|
171
|
+
const json = await readJson(request);
|
|
172
|
+
const messageId = `message-${this.nextMessageId++}`;
|
|
173
|
+
// Queue in HTTP inbox
|
|
174
|
+
this.queueInboxByName(json.to, {
|
|
175
|
+
sender: json.from,
|
|
176
|
+
text: json.text,
|
|
177
|
+
message_id: messageId,
|
|
178
|
+
});
|
|
179
|
+
// Also push via WebSocket to recipient (real server does this)
|
|
180
|
+
const recipientAgentId = this.findAgentId(json.to);
|
|
181
|
+
if (recipientAgentId) {
|
|
182
|
+
const ws = this.websockets.get(recipientAgentId);
|
|
183
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
184
|
+
ws.send(JSON.stringify({
|
|
185
|
+
type: 'message',
|
|
186
|
+
sender: json.from,
|
|
187
|
+
text: json.text,
|
|
188
|
+
message_id: messageId,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
sendJson(response, 200, { message_id: messageId });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (request.method === 'GET' && pathname.startsWith('/v1/inbox/')) {
|
|
197
|
+
const agentId = pathname.split('/').at(-1)!;
|
|
198
|
+
const messages = this.inboxes.get(agentId) ?? [];
|
|
199
|
+
this.inboxes.set(agentId, []);
|
|
200
|
+
sendJson(response, 200, { messages });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (request.method === 'GET' && pathname === '/v1/agents') {
|
|
205
|
+
sendJson(response, 200, { agents: [...this.registeredAgents.values()].map((entry) => entry.name) });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
sendJson(response, 404, { message: 'Not found' });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private handleUpgrade(request: IncomingMessage, socket: any, head: Buffer): void {
|
|
213
|
+
const url = new URL(request.url ?? '/', this.baseUrl || 'http://127.0.0.1');
|
|
214
|
+
const pathname = url.pathname;
|
|
215
|
+
if (!pathname.startsWith('/v1/ws/')) {
|
|
216
|
+
socket.destroy();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const agentId = pathname.split('/').at(-1)!;
|
|
221
|
+
const token = url.searchParams.get('token');
|
|
222
|
+
const registration = this.registeredAgents.get(agentId);
|
|
223
|
+
if (!registration || token !== registration.token) {
|
|
224
|
+
socket.destroy();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.wsServer.handleUpgrade(request, socket, head, (ws) => {
|
|
229
|
+
this.wsServer.emit('connection', ws, request);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function withServer(run: (server: MockRelayServer) => Promise<void>): Promise<void> {
|
|
235
|
+
const server = new MockRelayServer();
|
|
236
|
+
await server.start();
|
|
237
|
+
try {
|
|
238
|
+
await run(server);
|
|
239
|
+
} finally {
|
|
240
|
+
await server.stop();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getPiTool(config: any, name: string) {
|
|
245
|
+
const tool = config.customTools?.find((candidate: any) => candidate.name === name);
|
|
246
|
+
assert.ok(tool, `Expected Pi tool ${name} to be registered`);
|
|
247
|
+
return tool;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getClaudePostToolUseHook(options: any) {
|
|
251
|
+
const matcher = options.hooks?.PostToolUse?.at(-1);
|
|
252
|
+
assert.ok(matcher, 'Expected a Claude PostToolUse matcher');
|
|
253
|
+
assert.ok(Array.isArray(matcher.hooks), 'Expected PostToolUse matcher to include hooks');
|
|
254
|
+
return matcher.hooks[0];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function invokeClaudePostToolUse(hook: any): Promise<any> {
|
|
258
|
+
return hook(
|
|
259
|
+
{
|
|
260
|
+
hook_event_name: 'PostToolUse',
|
|
261
|
+
tool_name: 'Read',
|
|
262
|
+
tool_input: {},
|
|
263
|
+
tool_response: {},
|
|
264
|
+
tool_use_id: 'tool-1',
|
|
265
|
+
},
|
|
266
|
+
'tool-1',
|
|
267
|
+
{ signal: new AbortController().signal }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function waitForClaudeSystemMessage(hook: any, expected: string[]): Promise<string> {
|
|
272
|
+
return waitFor(async () => {
|
|
273
|
+
const result = await invokeClaudePostToolUse(hook);
|
|
274
|
+
const systemMessage = result?.systemMessage;
|
|
275
|
+
if (typeof systemMessage === 'string' && expected.every((fragment) => systemMessage.includes(fragment))) {
|
|
276
|
+
return systemMessage;
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function waitForPiInbox(tool: any, expected: string[]): Promise<string> {
|
|
283
|
+
return waitFor(async () => {
|
|
284
|
+
const result = await tool.execute('tool-2', {});
|
|
285
|
+
const content = result.content.map((entry: any) => entry.text).join('\n');
|
|
286
|
+
if (expected.every((fragment) => content.includes(fragment))) {
|
|
287
|
+
return content;
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
test('Pi relay_send reaches Claude PostToolUse hook over the shared relay server', async () => {
|
|
294
|
+
await withServer(async (server) => {
|
|
295
|
+
const { Relay } = await loadCoreModule();
|
|
296
|
+
const { onRelay: onPi } = await loadPiAdapterModule();
|
|
297
|
+
const { onRelay: onClaude } = await loadClaudeAdapterModule();
|
|
298
|
+
|
|
299
|
+
const piRelay = new Relay('PiSender', server.makeConfig());
|
|
300
|
+
const claudeRelay = new Relay('ClaudeReceiver', server.makeConfig());
|
|
301
|
+
|
|
302
|
+
const piConfig = onPi('PiSender', {}, piRelay);
|
|
303
|
+
const claudeOptions = onClaude('ClaudeReceiver', {}, claudeRelay);
|
|
304
|
+
const sendTool = getPiTool(piConfig, 'relay_send');
|
|
305
|
+
const postToolUseHook = getClaudePostToolUseHook(claudeOptions);
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
await invokeClaudePostToolUse(postToolUseHook);
|
|
309
|
+
await server.waitForAgent('ClaudeReceiver');
|
|
310
|
+
|
|
311
|
+
await sendTool.execute('tool-1', {
|
|
312
|
+
to: 'ClaudeReceiver',
|
|
313
|
+
text: 'ping from Pi',
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const systemMessage = await waitForClaudeSystemMessage(postToolUseHook, [
|
|
317
|
+
'Relay message from PiSender: ping from Pi',
|
|
318
|
+
]);
|
|
319
|
+
assert.match(systemMessage, /Relay message from PiSender: ping from Pi/);
|
|
320
|
+
} finally {
|
|
321
|
+
await Promise.all([piRelay.close(), claudeRelay.close()]);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('Claude relay send is visible through the Pi relay_inbox tool over the shared relay server', async () => {
|
|
327
|
+
await withServer(async (server) => {
|
|
328
|
+
const { Relay } = await loadCoreModule();
|
|
329
|
+
const { onRelay: onPi } = await loadPiAdapterModule();
|
|
330
|
+
const { onRelay: onClaude } = await loadClaudeAdapterModule();
|
|
331
|
+
|
|
332
|
+
const piRelay = new Relay('PiReceiver', server.makeConfig());
|
|
333
|
+
const claudeRelay = new Relay('ClaudeSender', server.makeConfig());
|
|
334
|
+
|
|
335
|
+
const piConfig = onPi('PiReceiver', {}, piRelay);
|
|
336
|
+
onClaude('ClaudeSender', {}, claudeRelay);
|
|
337
|
+
const inboxTool = getPiTool(piConfig, 'relay_inbox');
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
await inboxTool.execute('tool-0', {});
|
|
341
|
+
await server.waitForAgent('PiReceiver');
|
|
342
|
+
|
|
343
|
+
await claudeRelay.send('PiReceiver', 'reply from Claude');
|
|
344
|
+
|
|
345
|
+
const inboxText = await waitForPiInbox(inboxTool, [
|
|
346
|
+
'Relay message from ClaudeSender: reply from Claude',
|
|
347
|
+
]);
|
|
348
|
+
assert.match(inboxText, /Relay message from ClaudeSender: reply from Claude/);
|
|
349
|
+
} finally {
|
|
350
|
+
await Promise.all([piRelay.close(), claudeRelay.close()]);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
});
|