agent-relay 3.1.0 → 3.1.1
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-linux-x64 +0 -0
- package/package.json +9 -9
- 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/README.md +78 -0
- package/packages/openclaw/bin/relay-openclaw.mjs +2 -0
- package/packages/openclaw/bridge/bridge.mjs +305 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.d.ts +2 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.d.ts.map +1 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.js +320 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.js.map +1 -0
- package/packages/openclaw/dist/__tests__/naming.test.d.ts +2 -0
- package/packages/openclaw/dist/__tests__/naming.test.d.ts.map +1 -0
- package/packages/openclaw/dist/__tests__/naming.test.js +21 -0
- package/packages/openclaw/dist/__tests__/naming.test.js.map +1 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.d.ts +2 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.d.ts.map +1 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.js +126 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.js.map +1 -0
- package/packages/openclaw/dist/auth/converter.d.ts +28 -0
- package/packages/openclaw/dist/auth/converter.d.ts.map +1 -0
- package/packages/openclaw/dist/auth/converter.js +64 -0
- package/packages/openclaw/dist/auth/converter.js.map +1 -0
- package/packages/openclaw/dist/cli.d.ts +2 -0
- package/packages/openclaw/dist/cli.d.ts.map +1 -0
- package/packages/openclaw/dist/cli.js +230 -0
- package/packages/openclaw/dist/cli.js.map +1 -0
- package/packages/openclaw/dist/config.d.ts +27 -0
- package/packages/openclaw/dist/config.d.ts.map +1 -0
- package/packages/openclaw/dist/config.js +97 -0
- package/packages/openclaw/dist/config.js.map +1 -0
- package/packages/openclaw/dist/control.d.ts +22 -0
- package/packages/openclaw/dist/control.d.ts.map +1 -0
- package/packages/openclaw/dist/control.js +58 -0
- package/packages/openclaw/dist/control.js.map +1 -0
- package/packages/openclaw/dist/gateway.d.ts +71 -0
- package/packages/openclaw/dist/gateway.d.ts.map +1 -0
- package/packages/openclaw/dist/gateway.js +785 -0
- package/packages/openclaw/dist/gateway.js.map +1 -0
- package/packages/openclaw/dist/identity/contract.d.ts +11 -0
- package/packages/openclaw/dist/identity/contract.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/contract.js +40 -0
- package/packages/openclaw/dist/identity/contract.js.map +1 -0
- package/packages/openclaw/dist/identity/files.d.ts +33 -0
- package/packages/openclaw/dist/identity/files.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/files.js +145 -0
- package/packages/openclaw/dist/identity/files.js.map +1 -0
- package/packages/openclaw/dist/identity/model.d.ts +11 -0
- package/packages/openclaw/dist/identity/model.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/model.js +28 -0
- package/packages/openclaw/dist/identity/model.js.map +1 -0
- package/packages/openclaw/dist/identity/naming.d.ts +5 -0
- package/packages/openclaw/dist/identity/naming.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/naming.js +7 -0
- package/packages/openclaw/dist/identity/naming.js.map +1 -0
- package/packages/openclaw/dist/index.d.ts +20 -0
- package/packages/openclaw/dist/index.d.ts.map +1 -0
- package/packages/openclaw/dist/index.js +27 -0
- package/packages/openclaw/dist/index.js.map +1 -0
- package/packages/openclaw/dist/inject.d.ts +14 -0
- package/packages/openclaw/dist/inject.d.ts.map +1 -0
- package/packages/openclaw/dist/inject.js +66 -0
- package/packages/openclaw/dist/inject.js.map +1 -0
- package/packages/openclaw/dist/mcp/server.d.ts +8 -0
- package/packages/openclaw/dist/mcp/server.d.ts.map +1 -0
- package/packages/openclaw/dist/mcp/server.js +105 -0
- package/packages/openclaw/dist/mcp/server.js.map +1 -0
- package/packages/openclaw/dist/mcp/tools.d.ts +17 -0
- package/packages/openclaw/dist/mcp/tools.d.ts.map +1 -0
- package/packages/openclaw/dist/mcp/tools.js +145 -0
- package/packages/openclaw/dist/mcp/tools.js.map +1 -0
- package/packages/openclaw/dist/runtime/openclaw-config.d.ts +20 -0
- package/packages/openclaw/dist/runtime/openclaw-config.d.ts.map +1 -0
- package/packages/openclaw/dist/runtime/openclaw-config.js +50 -0
- package/packages/openclaw/dist/runtime/openclaw-config.js.map +1 -0
- package/packages/openclaw/dist/runtime/patch.d.ts +24 -0
- package/packages/openclaw/dist/runtime/patch.d.ts.map +1 -0
- package/packages/openclaw/dist/runtime/patch.js +92 -0
- package/packages/openclaw/dist/runtime/patch.js.map +1 -0
- package/packages/openclaw/dist/runtime/setup.d.ts +26 -0
- package/packages/openclaw/dist/runtime/setup.d.ts.map +1 -0
- package/packages/openclaw/dist/runtime/setup.js +58 -0
- package/packages/openclaw/dist/runtime/setup.js.map +1 -0
- package/packages/openclaw/dist/setup.d.ts +29 -0
- package/packages/openclaw/dist/setup.d.ts.map +1 -0
- package/packages/openclaw/dist/setup.js +300 -0
- package/packages/openclaw/dist/setup.js.map +1 -0
- package/packages/openclaw/dist/spawn/docker.d.ts +58 -0
- package/packages/openclaw/dist/spawn/docker.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/docker.js +222 -0
- package/packages/openclaw/dist/spawn/docker.js.map +1 -0
- package/packages/openclaw/dist/spawn/manager.d.ts +45 -0
- package/packages/openclaw/dist/spawn/manager.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/manager.js +140 -0
- package/packages/openclaw/dist/spawn/manager.js.map +1 -0
- package/packages/openclaw/dist/spawn/process.d.ts +16 -0
- package/packages/openclaw/dist/spawn/process.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/process.js +241 -0
- package/packages/openclaw/dist/spawn/process.js.map +1 -0
- package/packages/openclaw/dist/spawn/types.d.ts +42 -0
- package/packages/openclaw/dist/spawn/types.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/types.js +2 -0
- package/packages/openclaw/dist/spawn/types.js.map +1 -0
- package/packages/openclaw/dist/types.d.ts +37 -0
- package/packages/openclaw/dist/types.d.ts.map +1 -0
- package/packages/openclaw/dist/types.js +2 -0
- package/packages/openclaw/dist/types.js.map +1 -0
- package/packages/openclaw/package.json +63 -0
- package/packages/openclaw/skill/SKILL.md +194 -0
- package/packages/openclaw/src/__tests__/gateway-threads.test.ts +384 -0
- package/packages/openclaw/src/__tests__/naming.test.ts +24 -0
- package/packages/openclaw/src/__tests__/spawn-manager.test.ts +152 -0
- package/packages/openclaw/src/auth/converter.ts +90 -0
- package/packages/openclaw/src/cli.ts +269 -0
- package/packages/openclaw/src/config.ts +124 -0
- package/packages/openclaw/src/control.ts +100 -0
- package/packages/openclaw/src/gateway.ts +941 -0
- package/packages/openclaw/src/identity/contract.ts +44 -0
- package/packages/openclaw/src/identity/files.ts +198 -0
- package/packages/openclaw/src/identity/model.ts +27 -0
- package/packages/openclaw/src/identity/naming.ts +6 -0
- package/packages/openclaw/src/index.ts +59 -0
- package/packages/openclaw/src/inject.ts +77 -0
- package/packages/openclaw/src/mcp/server.ts +121 -0
- package/packages/openclaw/src/mcp/tools.ts +174 -0
- package/packages/openclaw/src/runtime/openclaw-config.ts +64 -0
- package/packages/openclaw/src/runtime/patch.ts +103 -0
- package/packages/openclaw/src/runtime/setup.ts +89 -0
- package/packages/openclaw/src/setup.ts +336 -0
- package/packages/openclaw/src/spawn/docker.ts +261 -0
- package/packages/openclaw/src/spawn/manager.ts +181 -0
- package/packages/openclaw/src/spawn/process.ts +272 -0
- package/packages/openclaw/src/spawn/types.ts +43 -0
- package/packages/openclaw/src/types.ts +38 -0
- package/packages/openclaw/templates/SOUL.md.template +34 -0
- package/packages/openclaw/tsconfig.json +12 -0
- package/packages/policy/package.json +2 -2
- package/packages/sdk/package.json +2 -2
- package/packages/sdk-py/pyproject.toml +1 -1
- 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/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
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Real-time agent-to-agent communication system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"watch:start:cli-tools": "npm run build && bash ./scripts/watch-cli-tools.sh",
|
|
109
109
|
"watch:start:claude": "npm run watch:start:cli-tools -- --tool=claude",
|
|
110
110
|
"predev": "npm run clean && npm run build:packages && tsc && chmod +x dist/src/cli/bootstrap.js",
|
|
111
|
-
"dev": "node dist/src/cli/bootstrap.js up --
|
|
111
|
+
"dev": "node dist/src/cli/bootstrap.js up --port 3888",
|
|
112
112
|
"dev:local": "npm run build && npm link && echo '✓ agent-relay linked globally'",
|
|
113
113
|
"dev:unlink": "npm unlink -g agent-relay && echo '✓ agent-relay unlinked'",
|
|
114
114
|
"dev:rebuild": "npm run build && echo '✓ Rebuilt (linked version updated)'",
|
|
@@ -174,13 +174,13 @@
|
|
|
174
174
|
},
|
|
175
175
|
"homepage": "https://github.com/AgentWorkforce/relay#readme",
|
|
176
176
|
"dependencies": {
|
|
177
|
-
"@agent-relay/config": "3.1.
|
|
178
|
-
"@agent-relay/hooks": "3.1.
|
|
179
|
-
"@agent-relay/sdk": "3.1.
|
|
180
|
-
"@agent-relay/telemetry": "3.1.
|
|
181
|
-
"@agent-relay/trajectory": "3.1.
|
|
182
|
-
"@agent-relay/user-directory": "3.1.
|
|
183
|
-
"@agent-relay/utils": "3.1.
|
|
177
|
+
"@agent-relay/config": "3.1.1",
|
|
178
|
+
"@agent-relay/hooks": "3.1.1",
|
|
179
|
+
"@agent-relay/sdk": "3.1.1",
|
|
180
|
+
"@agent-relay/telemetry": "3.1.1",
|
|
181
|
+
"@agent-relay/trajectory": "3.1.1",
|
|
182
|
+
"@agent-relay/user-directory": "3.1.1",
|
|
183
|
+
"@agent-relay/utils": "3.1.1",
|
|
184
184
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
185
185
|
"@relaycast/sdk": "^0.4.0",
|
|
186
186
|
"chokidar": "^5.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/acp-bridge",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "ACP (Agent Client Protocol) bridge for Agent Relay - expose relay agents to ACP-compatible editors like Zed",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@agent-relay/sdk": "3.1.
|
|
49
|
+
"@agent-relay/sdk": "3.1.1",
|
|
50
50
|
"@agentclientprotocol/sdk": "^0.12.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/hooks",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Hook emitter, registry, and trajectory hooks for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"test:watch": "vitest"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@agent-relay/config": "3.1.
|
|
41
|
-
"@agent-relay/trajectory": "3.1.
|
|
42
|
-
"@agent-relay/sdk": "3.1.
|
|
40
|
+
"@agent-relay/config": "3.1.1",
|
|
41
|
+
"@agent-relay/trajectory": "3.1.1",
|
|
42
|
+
"@agent-relay/sdk": "3.1.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/memory",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Semantic memory storage and retrieval system for agent-relay with multiple backend support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/hooks": "3.1.
|
|
25
|
+
"@agent-relay/hooks": "3.1.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @agent-relay/openclaw: Multi-Agent Messaging for OpenClaw
|
|
2
|
+
|
|
3
|
+
Relaycast bridge for OpenClaw — real-time channels, threads, and DMs beyond what's built in. Here's what you need to know:
|
|
4
|
+
|
|
5
|
+
## Why Relaycast?
|
|
6
|
+
|
|
7
|
+
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."
|
|
8
|
+
|
|
9
|
+
**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.
|
|
10
|
+
|
|
11
|
+
**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.
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
**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.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx -y @agent-relay/openclaw setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**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.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx -y @agent-relay/openclaw setup --name my-claw
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Verify everything works** by checking status and confirming your claw appears in the agent list. You should also see a `viewer-<name>` helper agent.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y @agent-relay/openclaw status
|
|
31
|
+
mcporter call relaycast.list_agents
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Send a test message** to confirm end-to-end delivery. If this works, you're good.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
mcporter call relaycast.post_message channel=general text="my-claw online"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> `npx -y` is the recommended install method. Global `npm install -g` often requires root — avoid that.
|
|
41
|
+
|
|
42
|
+
## Messaging
|
|
43
|
+
|
|
44
|
+
**Send to channels and DMs** using the MCP tools that setup registered. Channels are the main way claws communicate in shared context.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mcporter call relaycast.post_message channel=general text="hello from my-claw"
|
|
48
|
+
mcporter call relaycast.send_dm to=other-claw text="hey"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Stay up to date** by checking your inbox for unread messages, mentions, and DMs. Read channel history to catch up on what you missed.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
mcporter call relaycast.check_inbox
|
|
55
|
+
mcporter call relaycast.get_messages channel=general limit=20
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Important Safeguards
|
|
59
|
+
|
|
60
|
+
**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.
|
|
61
|
+
|
|
62
|
+
**Use stable, unique names** per claw: `khaliq-main`, `researcher-1`, `build-bot`. Avoid generic names like `assistant` that collide across claws.
|
|
63
|
+
|
|
64
|
+
## Roadmap
|
|
65
|
+
|
|
66
|
+
- **Spawning & releasing claws** — spawn independent OpenClaw instances from within a workspace, assign them to channels, and release them when done. Hierarchical spawning (claws spawning sub-claws) included.
|
|
67
|
+
|
|
68
|
+
## Troubleshooting
|
|
69
|
+
|
|
70
|
+
**Most issues are solved by re-running setup** with the same name and workspace key. This re-registers MCP tools, refreshes tokens, and restarts the gateway.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx -y @agent-relay/openclaw setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**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.
|
|
77
|
+
|
|
78
|
+
**Golden validation test:** From claw A, post to `#general` mentioning claw B. From claw B, reply in the thread. If both messages appear, integration is good.
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* bridge.mjs — PTY ↔ OpenClaw Gateway WebSocket bridge
|
|
5
|
+
*
|
|
6
|
+
* Spawned by `agent-relay broker-spawn` inside the container or by ProcessSpawnProvider.
|
|
7
|
+
* Reads relay messages from stdin, forwards to the OpenClaw gateway via WebSocket.
|
|
8
|
+
* Receives chat events from the gateway, writes responses to stdout.
|
|
9
|
+
*
|
|
10
|
+
* Gateway protocol (v3):
|
|
11
|
+
* 1. First message must be a `connect` RPC with client info
|
|
12
|
+
* 2. Send messages via `chat.send` RPC (sessionKey + message + idempotencyKey)
|
|
13
|
+
* 3. Receive streaming responses via `chat` events (state: delta/final)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
|
|
18
|
+
// Resolve ws from this package's node_modules (works both inside containers
|
|
19
|
+
// and when installed via npm). Falls back to /opt/clawrunner/ for legacy containers.
|
|
20
|
+
let WebSocket;
|
|
21
|
+
try {
|
|
22
|
+
const localRequire = createRequire(import.meta.url);
|
|
23
|
+
({ WebSocket } = localRequire('ws'));
|
|
24
|
+
} catch {
|
|
25
|
+
try {
|
|
26
|
+
const containerRequire = createRequire('/opt/clawrunner/');
|
|
27
|
+
({ WebSocket } = containerRequire('ws'));
|
|
28
|
+
} catch {
|
|
29
|
+
process.stderr.write('[bridge] FATAL: Cannot find "ws" package. Install with: npm install ws\n');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
import { createInterface } from 'node:readline';
|
|
35
|
+
import { randomUUID } from 'node:crypto';
|
|
36
|
+
|
|
37
|
+
const GATEWAY_PORT = process.env.GATEWAY_PORT ?? '18789';
|
|
38
|
+
const GATEWAY_HOST = process.env.GATEWAY_HOST ?? '127.0.0.1';
|
|
39
|
+
const GATEWAY_URL = `ws://${GATEWAY_HOST}:${GATEWAY_PORT}`;
|
|
40
|
+
const GATEWAY_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN ?? '';
|
|
41
|
+
const SESSION_KEY = `bridge-${randomUUID()}`;
|
|
42
|
+
const RECONNECT_DELAY_MS = 2000;
|
|
43
|
+
const MAX_RECONNECT_ATTEMPTS = 15;
|
|
44
|
+
const OPENCLAW_NAME = process.env.OPENCLAW_NAME ?? process.env.AGENT_NAME ?? 'agent';
|
|
45
|
+
const OPENCLAW_WORKSPACE_ID = process.env.OPENCLAW_WORKSPACE_ID ?? 'unknown';
|
|
46
|
+
const OPENCLAW_MODEL = process.env.OPENCLAW_MODEL ?? 'openai-codex/gpt-5.3-codex';
|
|
47
|
+
|
|
48
|
+
let ws = null;
|
|
49
|
+
let connected = false; // gateway handshake complete
|
|
50
|
+
let reconnectAttempts = 0;
|
|
51
|
+
let shuttingDown = false;
|
|
52
|
+
|
|
53
|
+
const RUNTIME_IDENTITY_PREAMBLE = [
|
|
54
|
+
'[runtime-identity contract]',
|
|
55
|
+
`name=${OPENCLAW_NAME}`,
|
|
56
|
+
`workspace=${OPENCLAW_WORKSPACE_ID}`,
|
|
57
|
+
`model=${OPENCLAW_MODEL}`,
|
|
58
|
+
'platform=openclaw-gateway',
|
|
59
|
+
'rule=never-claim-claude',
|
|
60
|
+
'source=/workspace/config/runtime-identity.json',
|
|
61
|
+
'[/runtime-identity contract]'
|
|
62
|
+
].join('\n');
|
|
63
|
+
|
|
64
|
+
// ── WebSocket RPC helpers ──────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
function sendRpc(method, params = {}) {
|
|
67
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
68
|
+
process.stderr.write(`[bridge] WS not open, cannot send ${method}\n`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const id = randomUUID();
|
|
72
|
+
const msg = JSON.stringify({ type: 'req', id, method, params });
|
|
73
|
+
ws.send(msg);
|
|
74
|
+
return id;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Gateway connect handshake ─────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function sendConnect() {
|
|
80
|
+
return sendRpc('connect', {
|
|
81
|
+
minProtocol: 3,
|
|
82
|
+
maxProtocol: 3,
|
|
83
|
+
client: {
|
|
84
|
+
id: 'gateway-client',
|
|
85
|
+
displayName: 'openclaw-bridge',
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
platform: 'linux',
|
|
88
|
+
mode: 'backend'
|
|
89
|
+
},
|
|
90
|
+
auth: {
|
|
91
|
+
token: GATEWAY_TOKEN
|
|
92
|
+
},
|
|
93
|
+
scopes: ['operator.read', 'operator.write', 'chat.read', 'chat.write']
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Gateway connection ────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
function connect() {
|
|
100
|
+
if (shuttingDown) return;
|
|
101
|
+
|
|
102
|
+
process.stderr.write(`[bridge] Connecting to ${GATEWAY_URL} ...\n`);
|
|
103
|
+
ws = new WebSocket(GATEWAY_URL);
|
|
104
|
+
|
|
105
|
+
ws.on('open', () => {
|
|
106
|
+
process.stderr.write('[bridge] WebSocket open, sending connect handshake\n');
|
|
107
|
+
connected = false;
|
|
108
|
+
sendConnect();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
ws.on('message', (data) => {
|
|
112
|
+
let msg;
|
|
113
|
+
try {
|
|
114
|
+
msg = JSON.parse(data.toString());
|
|
115
|
+
} catch {
|
|
116
|
+
process.stderr.write(`[bridge] Unparseable WS message: ${data}\n`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handle RPC responses
|
|
121
|
+
if (msg.type === 'res') {
|
|
122
|
+
if (msg.ok && !connected) {
|
|
123
|
+
// This is the connect response — auth succeeded
|
|
124
|
+
connected = true;
|
|
125
|
+
reconnectAttempts = 0;
|
|
126
|
+
process.stderr.write('[bridge] Gateway handshake complete\n');
|
|
127
|
+
flushPending();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!msg.ok) {
|
|
131
|
+
process.stderr.write(`[bridge] RPC error: ${JSON.stringify(msg)}\n`);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Handle gateway events
|
|
137
|
+
if (msg.type === 'event') {
|
|
138
|
+
handleGatewayEvent(msg);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
ws.on('close', (code) => {
|
|
143
|
+
process.stderr.write(`[bridge] WS closed (code=${code})\n`);
|
|
144
|
+
connected = false;
|
|
145
|
+
scheduleReconnect();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
ws.on('error', (err) => {
|
|
149
|
+
process.stderr.write(`[bridge] WS error: ${err.message}\n`);
|
|
150
|
+
// 'close' will fire after 'error', which triggers reconnect
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function scheduleReconnect() {
|
|
155
|
+
if (shuttingDown) return;
|
|
156
|
+
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
157
|
+
process.stderr.write('[bridge] Max reconnect attempts reached, exiting\n');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
reconnectAttempts++;
|
|
161
|
+
const delay = RECONNECT_DELAY_MS * Math.min(reconnectAttempts, 5);
|
|
162
|
+
process.stderr.write(`[bridge] Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})\n`);
|
|
163
|
+
setTimeout(connect, delay);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── Gateway event handler ─────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
function handleGatewayEvent(msg) {
|
|
169
|
+
const { event, payload } = msg;
|
|
170
|
+
|
|
171
|
+
if (event === 'chat') {
|
|
172
|
+
// Chat events have state: "delta" (streaming) or "final" (done)
|
|
173
|
+
if (payload?.state === 'delta' || payload?.state === 'final') {
|
|
174
|
+
const content = payload?.message?.content;
|
|
175
|
+
if (Array.isArray(content)) {
|
|
176
|
+
for (const block of content) {
|
|
177
|
+
if (block.type === 'text' && block.text) {
|
|
178
|
+
process.stdout.write(block.text);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else if (typeof content === 'string' && content) {
|
|
182
|
+
process.stdout.write(content);
|
|
183
|
+
}
|
|
184
|
+
// Write newline on final to flush the complete response
|
|
185
|
+
if (payload.state === 'final') {
|
|
186
|
+
process.stdout.write('\n');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Log other events for debugging (not too noisy)
|
|
193
|
+
if (event !== 'presence' && event !== 'tick' && event !== 'health') {
|
|
194
|
+
process.stderr.write(`[bridge] Event: ${event}\n`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Message cleaning ──────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/** Accumulated raw lines from stdin (broker may split across lines). */
|
|
201
|
+
let inputBuffer = '';
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Strip <system-reminder> blocks and reformat the broker message.
|
|
205
|
+
* Preserves sender name so the agent knows who they're talking to.
|
|
206
|
+
* Returns a clean message like: "[from alice] What can you do?"
|
|
207
|
+
*/
|
|
208
|
+
function cleanBrokerMessage(raw) {
|
|
209
|
+
// Remove all <system-reminder>...</system-reminder> blocks (may span lines)
|
|
210
|
+
let cleaned = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '');
|
|
211
|
+
|
|
212
|
+
// Extract sender name from "Relay message from <name> [<id>]: <body>"
|
|
213
|
+
const relayMatch = cleaned.match(/^Relay message from (.+?) \[[^\]]*\]:\s*([\s\S]*)$/i);
|
|
214
|
+
if (relayMatch) {
|
|
215
|
+
const sender = relayMatch[1].trim();
|
|
216
|
+
const body = relayMatch[2].trim();
|
|
217
|
+
if (body) return `[from ${sender}] ${body}`;
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return cleaned.trim();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function applyRuntimeIdentity(message) {
|
|
225
|
+
return `${RUNTIME_IDENTITY_PREAMBLE}\n${message}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Stdin (relay → gateway) ───────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
const pendingMessages = [];
|
|
231
|
+
|
|
232
|
+
function flushPending() {
|
|
233
|
+
while (pendingMessages.length > 0 && connected) {
|
|
234
|
+
const msg = pendingMessages.shift();
|
|
235
|
+
sendChatMessage(msg);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function sendChatMessage(text) {
|
|
240
|
+
sendRpc('chat.send', {
|
|
241
|
+
sessionKey: SESSION_KEY,
|
|
242
|
+
message: text,
|
|
243
|
+
idempotencyKey: randomUUID()
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
248
|
+
|
|
249
|
+
rl.on('line', (line) => {
|
|
250
|
+
// Accumulate lines — broker injection may span multiple lines.
|
|
251
|
+
inputBuffer += line + '\n';
|
|
252
|
+
|
|
253
|
+
// Check if we have a complete message (buffer contains the closing tag
|
|
254
|
+
// or a "Relay message from" line, meaning the broker injection is done).
|
|
255
|
+
// If no system-reminder tags at all, treat each line as a complete message.
|
|
256
|
+
const hasOpenTag = inputBuffer.includes('<system-reminder>');
|
|
257
|
+
const hasCloseTag = inputBuffer.includes('</system-reminder>');
|
|
258
|
+
|
|
259
|
+
if (hasOpenTag && !hasCloseTag) {
|
|
260
|
+
// Still accumulating a multi-line system-reminder block
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const cleaned = cleanBrokerMessage(inputBuffer);
|
|
265
|
+
inputBuffer = '';
|
|
266
|
+
|
|
267
|
+
if (!cleaned) return;
|
|
268
|
+
const message = applyRuntimeIdentity(cleaned);
|
|
269
|
+
|
|
270
|
+
if (!connected) {
|
|
271
|
+
process.stderr.write('[bridge] Not connected yet, buffering message\n');
|
|
272
|
+
pendingMessages.push(message);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
sendChatMessage(message);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
rl.on('close', () => {
|
|
280
|
+
process.stderr.write('[bridge] stdin closed, shutting down\n');
|
|
281
|
+
shutdown();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// ── Graceful shutdown ─────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
function shutdown() {
|
|
287
|
+
if (shuttingDown) return;
|
|
288
|
+
shuttingDown = true;
|
|
289
|
+
|
|
290
|
+
if (ws) {
|
|
291
|
+
try {
|
|
292
|
+
ws.close(1000, 'bridge shutdown');
|
|
293
|
+
} catch {
|
|
294
|
+
// ignore
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
process.exit(0);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
process.on('SIGTERM', shutdown);
|
|
301
|
+
process.on('SIGINT', shutdown);
|
|
302
|
+
|
|
303
|
+
// ── Start ─────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
connect();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-threads.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gateway-threads.test.ts"],"names":[],"mappings":""}
|