agent-relay 3.0.2 → 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.
Files changed (266) hide show
  1. package/README.md +8 -0
  2. package/bin/agent-relay-broker-linux-x64 +0 -0
  3. package/dist/index.cjs +273 -56
  4. package/dist/src/cli/commands/core.d.ts +2 -0
  5. package/dist/src/cli/commands/core.d.ts.map +1 -1
  6. package/dist/src/cli/commands/core.js +9 -2
  7. package/dist/src/cli/commands/core.js.map +1 -1
  8. package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
  9. package/dist/src/cli/lib/broker-lifecycle.js +87 -28
  10. package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
  11. package/package.json +9 -9
  12. package/packages/acp-bridge/README.md +50 -67
  13. package/packages/acp-bridge/package.json +2 -2
  14. package/packages/config/package.json +1 -1
  15. package/packages/hooks/package.json +4 -4
  16. package/packages/memory/package.json +2 -2
  17. package/packages/openclaw/README.md +78 -0
  18. package/packages/openclaw/bin/relay-openclaw.mjs +2 -0
  19. package/packages/openclaw/bridge/bridge.mjs +305 -0
  20. package/packages/openclaw/dist/__tests__/gateway-threads.test.d.ts +2 -0
  21. package/packages/openclaw/dist/__tests__/gateway-threads.test.d.ts.map +1 -0
  22. package/packages/openclaw/dist/__tests__/gateway-threads.test.js +320 -0
  23. package/packages/openclaw/dist/__tests__/gateway-threads.test.js.map +1 -0
  24. package/packages/openclaw/dist/__tests__/naming.test.d.ts +2 -0
  25. package/packages/openclaw/dist/__tests__/naming.test.d.ts.map +1 -0
  26. package/packages/openclaw/dist/__tests__/naming.test.js +21 -0
  27. package/packages/openclaw/dist/__tests__/naming.test.js.map +1 -0
  28. package/packages/openclaw/dist/__tests__/spawn-manager.test.d.ts +2 -0
  29. package/packages/openclaw/dist/__tests__/spawn-manager.test.d.ts.map +1 -0
  30. package/packages/openclaw/dist/__tests__/spawn-manager.test.js +126 -0
  31. package/packages/openclaw/dist/__tests__/spawn-manager.test.js.map +1 -0
  32. package/packages/openclaw/dist/auth/converter.d.ts +28 -0
  33. package/packages/openclaw/dist/auth/converter.d.ts.map +1 -0
  34. package/packages/openclaw/dist/auth/converter.js +64 -0
  35. package/packages/openclaw/dist/auth/converter.js.map +1 -0
  36. package/packages/openclaw/dist/cli.d.ts +2 -0
  37. package/packages/openclaw/dist/cli.d.ts.map +1 -0
  38. package/packages/openclaw/dist/cli.js +230 -0
  39. package/packages/openclaw/dist/cli.js.map +1 -0
  40. package/packages/openclaw/dist/config.d.ts +27 -0
  41. package/packages/openclaw/dist/config.d.ts.map +1 -0
  42. package/packages/openclaw/dist/config.js +97 -0
  43. package/packages/openclaw/dist/config.js.map +1 -0
  44. package/packages/openclaw/dist/control.d.ts +22 -0
  45. package/packages/openclaw/dist/control.d.ts.map +1 -0
  46. package/packages/openclaw/dist/control.js +58 -0
  47. package/packages/openclaw/dist/control.js.map +1 -0
  48. package/packages/openclaw/dist/gateway.d.ts +71 -0
  49. package/packages/openclaw/dist/gateway.d.ts.map +1 -0
  50. package/packages/openclaw/dist/gateway.js +785 -0
  51. package/packages/openclaw/dist/gateway.js.map +1 -0
  52. package/packages/openclaw/dist/identity/contract.d.ts +11 -0
  53. package/packages/openclaw/dist/identity/contract.d.ts.map +1 -0
  54. package/packages/openclaw/dist/identity/contract.js +40 -0
  55. package/packages/openclaw/dist/identity/contract.js.map +1 -0
  56. package/packages/openclaw/dist/identity/files.d.ts +33 -0
  57. package/packages/openclaw/dist/identity/files.d.ts.map +1 -0
  58. package/packages/openclaw/dist/identity/files.js +145 -0
  59. package/packages/openclaw/dist/identity/files.js.map +1 -0
  60. package/packages/openclaw/dist/identity/model.d.ts +11 -0
  61. package/packages/openclaw/dist/identity/model.d.ts.map +1 -0
  62. package/packages/openclaw/dist/identity/model.js +28 -0
  63. package/packages/openclaw/dist/identity/model.js.map +1 -0
  64. package/packages/openclaw/dist/identity/naming.d.ts +5 -0
  65. package/packages/openclaw/dist/identity/naming.d.ts.map +1 -0
  66. package/packages/openclaw/dist/identity/naming.js +7 -0
  67. package/packages/openclaw/dist/identity/naming.js.map +1 -0
  68. package/packages/openclaw/dist/index.d.ts +20 -0
  69. package/packages/openclaw/dist/index.d.ts.map +1 -0
  70. package/packages/openclaw/dist/index.js +27 -0
  71. package/packages/openclaw/dist/index.js.map +1 -0
  72. package/packages/openclaw/dist/inject.d.ts +14 -0
  73. package/packages/openclaw/dist/inject.d.ts.map +1 -0
  74. package/packages/openclaw/dist/inject.js +66 -0
  75. package/packages/openclaw/dist/inject.js.map +1 -0
  76. package/packages/openclaw/dist/mcp/server.d.ts +8 -0
  77. package/packages/openclaw/dist/mcp/server.d.ts.map +1 -0
  78. package/packages/openclaw/dist/mcp/server.js +105 -0
  79. package/packages/openclaw/dist/mcp/server.js.map +1 -0
  80. package/packages/openclaw/dist/mcp/tools.d.ts +17 -0
  81. package/packages/openclaw/dist/mcp/tools.d.ts.map +1 -0
  82. package/packages/openclaw/dist/mcp/tools.js +145 -0
  83. package/packages/openclaw/dist/mcp/tools.js.map +1 -0
  84. package/packages/openclaw/dist/runtime/openclaw-config.d.ts +20 -0
  85. package/packages/openclaw/dist/runtime/openclaw-config.d.ts.map +1 -0
  86. package/packages/openclaw/dist/runtime/openclaw-config.js +50 -0
  87. package/packages/openclaw/dist/runtime/openclaw-config.js.map +1 -0
  88. package/packages/openclaw/dist/runtime/patch.d.ts +24 -0
  89. package/packages/openclaw/dist/runtime/patch.d.ts.map +1 -0
  90. package/packages/openclaw/dist/runtime/patch.js +92 -0
  91. package/packages/openclaw/dist/runtime/patch.js.map +1 -0
  92. package/packages/openclaw/dist/runtime/setup.d.ts +26 -0
  93. package/packages/openclaw/dist/runtime/setup.d.ts.map +1 -0
  94. package/packages/openclaw/dist/runtime/setup.js +58 -0
  95. package/packages/openclaw/dist/runtime/setup.js.map +1 -0
  96. package/packages/openclaw/dist/setup.d.ts +29 -0
  97. package/packages/openclaw/dist/setup.d.ts.map +1 -0
  98. package/packages/openclaw/dist/setup.js +300 -0
  99. package/packages/openclaw/dist/setup.js.map +1 -0
  100. package/packages/openclaw/dist/spawn/docker.d.ts +58 -0
  101. package/packages/openclaw/dist/spawn/docker.d.ts.map +1 -0
  102. package/packages/openclaw/dist/spawn/docker.js +222 -0
  103. package/packages/openclaw/dist/spawn/docker.js.map +1 -0
  104. package/packages/openclaw/dist/spawn/manager.d.ts +45 -0
  105. package/packages/openclaw/dist/spawn/manager.d.ts.map +1 -0
  106. package/packages/openclaw/dist/spawn/manager.js +140 -0
  107. package/packages/openclaw/dist/spawn/manager.js.map +1 -0
  108. package/packages/openclaw/dist/spawn/process.d.ts +16 -0
  109. package/packages/openclaw/dist/spawn/process.d.ts.map +1 -0
  110. package/packages/openclaw/dist/spawn/process.js +241 -0
  111. package/packages/openclaw/dist/spawn/process.js.map +1 -0
  112. package/packages/openclaw/dist/spawn/types.d.ts +42 -0
  113. package/packages/openclaw/dist/spawn/types.d.ts.map +1 -0
  114. package/packages/openclaw/dist/spawn/types.js +2 -0
  115. package/packages/openclaw/dist/spawn/types.js.map +1 -0
  116. package/packages/openclaw/dist/types.d.ts +37 -0
  117. package/packages/openclaw/dist/types.d.ts.map +1 -0
  118. package/packages/openclaw/dist/types.js +2 -0
  119. package/packages/openclaw/dist/types.js.map +1 -0
  120. package/packages/openclaw/package.json +63 -0
  121. package/packages/openclaw/skill/SKILL.md +194 -0
  122. package/packages/openclaw/src/__tests__/gateway-threads.test.ts +384 -0
  123. package/packages/openclaw/src/__tests__/naming.test.ts +24 -0
  124. package/packages/openclaw/src/__tests__/spawn-manager.test.ts +152 -0
  125. package/packages/openclaw/src/auth/converter.ts +90 -0
  126. package/packages/openclaw/src/cli.ts +269 -0
  127. package/packages/openclaw/src/config.ts +124 -0
  128. package/packages/openclaw/src/control.ts +100 -0
  129. package/packages/openclaw/src/gateway.ts +941 -0
  130. package/packages/openclaw/src/identity/contract.ts +44 -0
  131. package/packages/openclaw/src/identity/files.ts +198 -0
  132. package/packages/openclaw/src/identity/model.ts +27 -0
  133. package/packages/openclaw/src/identity/naming.ts +6 -0
  134. package/packages/openclaw/src/index.ts +59 -0
  135. package/packages/openclaw/src/inject.ts +77 -0
  136. package/packages/openclaw/src/mcp/server.ts +121 -0
  137. package/packages/openclaw/src/mcp/tools.ts +174 -0
  138. package/packages/openclaw/src/runtime/openclaw-config.ts +64 -0
  139. package/packages/openclaw/src/runtime/patch.ts +103 -0
  140. package/packages/openclaw/src/runtime/setup.ts +89 -0
  141. package/packages/openclaw/src/setup.ts +336 -0
  142. package/packages/openclaw/src/spawn/docker.ts +261 -0
  143. package/packages/openclaw/src/spawn/manager.ts +181 -0
  144. package/packages/openclaw/src/spawn/process.ts +272 -0
  145. package/packages/openclaw/src/spawn/types.ts +43 -0
  146. package/packages/openclaw/src/types.ts +38 -0
  147. package/packages/openclaw/templates/SOUL.md.template +34 -0
  148. package/packages/openclaw/tsconfig.json +12 -0
  149. package/packages/policy/package.json +2 -2
  150. package/packages/sdk/README.md +169 -64
  151. package/packages/sdk/dist/__tests__/contract-fixtures.test.js +76 -9
  152. package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +1 -1
  153. package/packages/sdk/dist/__tests__/integration.test.js +5 -4
  154. package/packages/sdk/dist/__tests__/integration.test.js.map +1 -1
  155. package/packages/sdk/dist/client.d.ts +34 -3
  156. package/packages/sdk/dist/client.d.ts.map +1 -1
  157. package/packages/sdk/dist/client.js +120 -10
  158. package/packages/sdk/dist/client.js.map +1 -1
  159. package/packages/sdk/dist/protocol.d.ts +7 -1
  160. package/packages/sdk/dist/protocol.d.ts.map +1 -1
  161. package/packages/sdk/dist/relay.d.ts +47 -11
  162. package/packages/sdk/dist/relay.d.ts.map +1 -1
  163. package/packages/sdk/dist/relay.js +114 -23
  164. package/packages/sdk/dist/relay.js.map +1 -1
  165. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  166. package/packages/sdk/dist/workflows/runner.js +71 -36
  167. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  168. package/packages/sdk/dist/workflows/types.d.ts +1 -1
  169. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  170. package/packages/sdk/package.json +2 -2
  171. package/packages/sdk/src/__tests__/contract-fixtures.test.ts +88 -9
  172. package/packages/sdk/src/__tests__/error-scenarios.test.ts +1 -1
  173. package/packages/sdk/src/__tests__/idle-nudge.test.ts +205 -257
  174. package/packages/sdk/src/__tests__/integration.test.ts +5 -4
  175. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +277 -13
  176. package/packages/sdk/src/__tests__/swarm-coordinator.test.ts +1 -0
  177. package/packages/sdk/src/__tests__/workflow-runner.test.ts +67 -7
  178. package/packages/sdk/src/__tests__/workflow-trajectory.test.ts +4 -5
  179. package/packages/sdk/src/client.ts +171 -14
  180. package/packages/sdk/src/examples/workflows/runner-idle-refactor.yaml +306 -0
  181. package/packages/sdk/src/protocol.ts +7 -2
  182. package/packages/sdk/src/relay.ts +196 -34
  183. package/packages/sdk/src/workflows/runner.ts +73 -42
  184. package/packages/sdk/src/workflows/schema.json +1 -1
  185. package/packages/sdk/src/workflows/types.ts +1 -1
  186. package/packages/sdk/vitest.config.ts +1 -0
  187. package/packages/sdk-py/README.md +89 -102
  188. package/packages/sdk-py/agent_relay/__init__.py +16 -19
  189. package/packages/sdk-py/pyproject.toml +5 -1
  190. package/packages/sdk-py/src/agent_relay/__init__.py +35 -1
  191. package/packages/sdk-py/src/agent_relay/client.py +776 -0
  192. package/packages/sdk-py/src/agent_relay/models.py +27 -0
  193. package/packages/sdk-py/src/agent_relay/protocol.py +114 -0
  194. package/packages/sdk-py/src/agent_relay/relay.py +860 -0
  195. package/packages/sdk-py/tests/test_relay_lifecycle_hooks.py +250 -0
  196. package/packages/telemetry/package.json +1 -1
  197. package/packages/trajectory/package.json +2 -2
  198. package/packages/user-directory/package.json +2 -2
  199. package/packages/utils/package.json +2 -2
  200. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  201. package/bin/agent-relay-broker-darwin-x64 +0 -0
  202. package/bin/agent-relay-broker-linux-arm64 +0 -0
  203. package/packages/sdk/.trajectories/active/traj_1771875803391_84ca57b2.json +0 -50
  204. package/packages/sdk/.trajectories/active/traj_1771891934534_06504121.json +0 -50
  205. package/packages/sdk/.trajectories/active/traj_1771891957929_211afc4e.json +0 -50
  206. package/packages/sdk/.trajectories/active/traj_1771891982509_38c84638.json +0 -50
  207. package/packages/sdk/.trajectories/completed/traj_1771875803188_cd6d181c.json +0 -80
  208. package/packages/sdk/.trajectories/completed/traj_1771875803204_f2aeb8c8.json +0 -80
  209. package/packages/sdk/.trajectories/completed/traj_1771875803210_d65f3f1a.json +0 -80
  210. package/packages/sdk/.trajectories/completed/traj_1771875803218_e454a25d.json +0 -80
  211. package/packages/sdk/.trajectories/completed/traj_1771875803223_d7a64815.json +0 -80
  212. package/packages/sdk/.trajectories/completed/traj_1771875803227_7e56da5b.json +0 -80
  213. package/packages/sdk/.trajectories/completed/traj_1771875803235_4fbf93b4.json +0 -80
  214. package/packages/sdk/.trajectories/completed/traj_1771875803243_47931c71.json +0 -80
  215. package/packages/sdk/.trajectories/completed/traj_1771875803258_3816f3fe.json +0 -80
  216. package/packages/sdk/.trajectories/completed/traj_1771875803268_8061140e.json +0 -80
  217. package/packages/sdk/.trajectories/completed/traj_1771875803326_ae6f9c78.json +0 -80
  218. package/packages/sdk/.trajectories/completed/traj_1771875808396_cbde0a6c.json +0 -91
  219. package/packages/sdk/.trajectories/completed/traj_1771875812026_aa2442bb.json +0 -91
  220. package/packages/sdk/.trajectories/completed/traj_1771875815431_c2c656c5.json +0 -91
  221. package/packages/sdk/.trajectories/completed/traj_1771875818645_3a4dbf02.json +0 -91
  222. package/packages/sdk/.trajectories/completed/traj_1771891934403_24923c03.json +0 -80
  223. package/packages/sdk/.trajectories/completed/traj_1771891934421_dca16e24.json +0 -80
  224. package/packages/sdk/.trajectories/completed/traj_1771891934430_057706f7.json +0 -80
  225. package/packages/sdk/.trajectories/completed/traj_1771891934442_faf97382.json +0 -80
  226. package/packages/sdk/.trajectories/completed/traj_1771891934454_5542ecd5.json +0 -80
  227. package/packages/sdk/.trajectories/completed/traj_1771891934464_12202a08.json +0 -80
  228. package/packages/sdk/.trajectories/completed/traj_1771891934487_94378275.json +0 -80
  229. package/packages/sdk/.trajectories/completed/traj_1771891934503_ca728c13.json +0 -80
  230. package/packages/sdk/.trajectories/completed/traj_1771891934519_100af69a.json +0 -80
  231. package/packages/sdk/.trajectories/completed/traj_1771891934536_62ad39d9.json +0 -80
  232. package/packages/sdk/.trajectories/completed/traj_1771891934553_d6798a52.json +0 -80
  233. package/packages/sdk/.trajectories/completed/traj_1771891939537_541c8096.json +0 -91
  234. package/packages/sdk/.trajectories/completed/traj_1771891942985_36ab9a4d.json +0 -91
  235. package/packages/sdk/.trajectories/completed/traj_1771891946453_e8a6e05f.json +0 -91
  236. package/packages/sdk/.trajectories/completed/traj_1771891949838_5de0de84.json +0 -91
  237. package/packages/sdk/.trajectories/completed/traj_1771891957807_0ecfb4f4.json +0 -80
  238. package/packages/sdk/.trajectories/completed/traj_1771891957827_c4539239.json +0 -80
  239. package/packages/sdk/.trajectories/completed/traj_1771891957836_91168b48.json +0 -80
  240. package/packages/sdk/.trajectories/completed/traj_1771891957848_8c5cad0b.json +0 -80
  241. package/packages/sdk/.trajectories/completed/traj_1771891957857_0986b293.json +0 -80
  242. package/packages/sdk/.trajectories/completed/traj_1771891957872_8a3113af.json +0 -80
  243. package/packages/sdk/.trajectories/completed/traj_1771891957884_0bb85208.json +0 -80
  244. package/packages/sdk/.trajectories/completed/traj_1771891957892_86c75e2e.json +0 -80
  245. package/packages/sdk/.trajectories/completed/traj_1771891957907_98ca0e6f.json +0 -80
  246. package/packages/sdk/.trajectories/completed/traj_1771891957918_d9091231.json +0 -80
  247. package/packages/sdk/.trajectories/completed/traj_1771891957931_dcaf77ed.json +0 -80
  248. package/packages/sdk/.trajectories/completed/traj_1771891962931_eb1fdee2.json +0 -91
  249. package/packages/sdk/.trajectories/completed/traj_1771891966262_9061a93f.json +0 -91
  250. package/packages/sdk/.trajectories/completed/traj_1771891969915_1adaba19.json +0 -91
  251. package/packages/sdk/.trajectories/completed/traj_1771891973588_f08b79e9.json +0 -91
  252. package/packages/sdk/.trajectories/completed/traj_1771891982421_f1985bce.json +0 -80
  253. package/packages/sdk/.trajectories/completed/traj_1771891982432_e7a84163.json +0 -80
  254. package/packages/sdk/.trajectories/completed/traj_1771891982447_369b842a.json +0 -80
  255. package/packages/sdk/.trajectories/completed/traj_1771891982469_5fc45199.json +0 -80
  256. package/packages/sdk/.trajectories/completed/traj_1771891982495_454c7cb3.json +0 -80
  257. package/packages/sdk/.trajectories/completed/traj_1771891982514_08098e03.json +0 -80
  258. package/packages/sdk/.trajectories/completed/traj_1771891982526_b351d778.json +0 -80
  259. package/packages/sdk/.trajectories/completed/traj_1771891982533_fa542d83.json +0 -80
  260. package/packages/sdk/.trajectories/completed/traj_1771891982540_18ab24dc.json +0 -80
  261. package/packages/sdk/.trajectories/completed/traj_1771891982544_5b4fa163.json +0 -80
  262. package/packages/sdk/.trajectories/completed/traj_1771891982548_c13f089a.json +0 -80
  263. package/packages/sdk/.trajectories/completed/traj_1771891987510_23f6da1f.json +0 -91
  264. package/packages/sdk/.trajectories/completed/traj_1771891991466_912c2e04.json +0 -91
  265. package/packages/sdk/.trajectories/completed/traj_1771891994891_60604be2.json +0 -91
  266. package/packages/sdk/.trajectories/completed/traj_1771891998370_cfaf9b8b.json +0 -91
@@ -0,0 +1,336 @@
1
+ import { mkdir, writeFile, readFile, copyFile } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { hostname } from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawn as spawnProcess, execFileSync } from 'node:child_process';
7
+
8
+ import { detectOpenClaw, saveGatewayConfig } from './config.js';
9
+ import type { GatewayConfig } from './types.js';
10
+
11
+ export interface SetupOptions {
12
+ /** If provided, join this workspace. Otherwise create a new one. */
13
+ apiKey?: string;
14
+ /** Name for this claw (default: hostname). */
15
+ clawName?: string;
16
+ /** Channels to auto-join (default: ['general']). */
17
+ channels?: string[];
18
+ /** Relaycast API base URL. */
19
+ baseUrl?: string;
20
+ }
21
+
22
+ export interface SetupResult {
23
+ ok: boolean;
24
+ apiKey: string;
25
+ clawName: string;
26
+ skillDir: string;
27
+ message: string;
28
+ }
29
+
30
+ /**
31
+ * Install the Relaycast bridge into an OpenClaw workspace.
32
+ *
33
+ * 1. Detect OpenClaw installation
34
+ * 2. Create/join workspace via Relaycast API (if no key provided)
35
+ * 3. Install SKILL.md
36
+ * 4. Write .env config
37
+ * 5. Configure MCP server in openclaw.json
38
+ * 6. Print success summary
39
+ */
40
+ export async function setup(options: SetupOptions): Promise<SetupResult> {
41
+ const detection = await detectOpenClaw();
42
+ const clawName = options.clawName ?? hostname() ?? 'my-claw';
43
+ const baseUrl = options.baseUrl ?? 'https://api.relaycast.dev';
44
+ const channels = options.channels ?? ['general'];
45
+
46
+ if (!detection.installed) {
47
+ // Auto-create ~/.openclaw/ if OpenClaw binary is available but the config dir
48
+ // doesn't exist yet (common in Docker images before onboarding).
49
+ try {
50
+ await mkdir(detection.homeDir, { recursive: true });
51
+ await mkdir(join(detection.homeDir, 'workspace'), { recursive: true });
52
+ // Write a minimal openclaw.json so MCP servers can be registered
53
+ const configPath = join(detection.homeDir, 'openclaw.json');
54
+ if (!existsSync(configPath)) {
55
+ await writeFile(configPath, JSON.stringify({ mcpServers: {} }, null, 2) + '\n', 'utf-8');
56
+ }
57
+ // Re-detect after creating
58
+ const redetection = await detectOpenClaw();
59
+ Object.assign(detection, redetection);
60
+ } catch {
61
+ return {
62
+ ok: false,
63
+ apiKey: '',
64
+ clawName,
65
+ skillDir: '',
66
+ message:
67
+ 'OpenClaw not found. Please install OpenClaw first (expected ~/.openclaw/ directory).',
68
+ };
69
+ }
70
+ }
71
+
72
+ // Enable the OpenResponses HTTP API so the inbound gateway can inject
73
+ // messages via POST /v1/responses on the local OpenClaw gateway.
74
+ try {
75
+ execFileSync('openclaw', [
76
+ 'config', 'set',
77
+ 'gateway.http.endpoints.responses.enabled', 'true',
78
+ ], { stdio: 'pipe' });
79
+ } catch {
80
+ console.warn('Could not enable OpenResponses API (non-fatal). Enable manually:');
81
+ console.warn(' openclaw config set gateway.http.endpoints.responses.enabled true');
82
+ }
83
+
84
+ // Resolve API key: use provided key or create a new workspace
85
+ let apiKey = options.apiKey;
86
+
87
+ if (!apiKey) {
88
+ try {
89
+ const res = await fetch(`${baseUrl}/v1/workspaces`, {
90
+ method: 'POST',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ body: JSON.stringify({ name: `${clawName}-workspace` }),
93
+ });
94
+
95
+ if (res.status === 409) {
96
+ // Workspace already exists — look up its API key
97
+ const lookupRes = await fetch(`${baseUrl}/v1/workspaces/by-name/${encodeURIComponent(`${clawName}-workspace`)}`, {
98
+ headers: { 'Content-Type': 'application/json' },
99
+ });
100
+ if (lookupRes.ok) {
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ const lookupBody = (await lookupRes.json()) as any;
103
+ apiKey = lookupBody.apiKey ?? lookupBody.api_key ?? lookupBody.data?.apiKey ?? lookupBody.data?.api_key;
104
+ }
105
+ if (!apiKey) {
106
+ return {
107
+ ok: false,
108
+ apiKey: '',
109
+ clawName,
110
+ skillDir: '',
111
+ message: `Workspace "${clawName}-workspace" already exists. Pass the workspace key: @agent-relay/openclaw setup <key> --name ${clawName}`,
112
+ };
113
+ }
114
+ } else if (!res.ok) {
115
+ const body = await res.text();
116
+ return {
117
+ ok: false,
118
+ apiKey: '',
119
+ clawName,
120
+ skillDir: '',
121
+ message: `Failed to create workspace: ${res.status} ${body}`,
122
+ };
123
+ } else {
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ const successBody = (await res.json()) as any;
126
+ apiKey = successBody.apiKey ?? successBody.api_key ?? successBody.data?.apiKey ?? successBody.data?.api_key;
127
+ }
128
+
129
+ if (!apiKey) {
130
+ return {
131
+ ok: false,
132
+ apiKey: '',
133
+ clawName,
134
+ skillDir: '',
135
+ message: 'Workspace created but no API key returned.',
136
+ };
137
+ }
138
+ } catch (err) {
139
+ return {
140
+ ok: false,
141
+ apiKey: '',
142
+ clawName,
143
+ skillDir: '',
144
+ message: `Failed to create workspace: ${err instanceof Error ? err.message : String(err)}`,
145
+ };
146
+ }
147
+ }
148
+
149
+ // Agent registration is done after mcporter is configured (see below),
150
+ // since the register tool is accessed via mcporter call relaycast.register.
151
+
152
+ // Install SKILL.md
153
+ const skillDir = join(detection.workspaceDir, 'relaycast');
154
+ await mkdir(skillDir, { recursive: true });
155
+
156
+ const skillSrc = resolveSkillPath();
157
+ if (existsSync(skillSrc)) {
158
+ await copyFile(skillSrc, join(skillDir, 'SKILL.md'));
159
+ } else {
160
+ // Write a minimal SKILL.md inline if the bundled one isn't found
161
+ await writeFile(
162
+ join(skillDir, 'SKILL.md'),
163
+ FALLBACK_SKILL_MD,
164
+ 'utf-8',
165
+ );
166
+ }
167
+
168
+ // Save gateway config (.env)
169
+ const gatewayConfig: GatewayConfig = {
170
+ apiKey,
171
+ clawName,
172
+ baseUrl,
173
+ channels,
174
+ };
175
+ await saveGatewayConfig(gatewayConfig);
176
+
177
+ // Register MCP servers via mcporter
178
+ let mcpConfigured = false;
179
+ {
180
+ const envArgs = [
181
+ '--env', `RELAY_API_KEY=${apiKey}`,
182
+ ...(baseUrl !== 'https://api.relaycast.dev'
183
+ ? ['--env', `RELAY_BASE_URL=${baseUrl}`]
184
+ : []),
185
+ ];
186
+
187
+ try {
188
+ // Register relaycast messaging MCP server
189
+ execFileSync('mcporter', [
190
+ 'config', 'add', 'relaycast',
191
+ '--command', 'npx',
192
+ '--arg', '@relaycast/mcp',
193
+ ...envArgs,
194
+ '--scope', 'home',
195
+ '--description', 'Relaycast messaging MCP server',
196
+ ], { stdio: 'pipe' });
197
+
198
+ // Register openclaw-spawner MCP server
199
+ execFileSync('mcporter', [
200
+ 'config', 'add', 'openclaw-spawner',
201
+ '--command', 'npx',
202
+ '--arg', '@agent-relay/openclaw',
203
+ '--arg', 'mcp-server',
204
+ ...envArgs,
205
+ '--scope', 'home',
206
+ '--description', 'OpenClaw spawner MCP server',
207
+ ], { stdio: 'pipe' });
208
+
209
+ mcpConfigured = true;
210
+
211
+ // Register this claw as an agent via mcporter and persist the agent token
212
+ try {
213
+ const registerOutput = execFileSync('mcporter', [
214
+ 'call', 'relaycast.register',
215
+ 'name=' + clawName,
216
+ 'type=agent',
217
+ ], { stdio: 'pipe', encoding: 'utf-8' });
218
+
219
+ // Parse the agent token from the register output
220
+ let agentToken: string | undefined;
221
+ try {
222
+ const parsed = JSON.parse(registerOutput);
223
+ agentToken = parsed.token ?? parsed.agentToken ?? parsed.agent_token;
224
+ } catch {
225
+ // Try to find token in raw output
226
+ const tokenMatch = registerOutput.match(/"token"\s*:\s*"([^"]+)"/);
227
+ if (tokenMatch) agentToken = tokenMatch[1];
228
+ }
229
+
230
+ if (agentToken) {
231
+ // Reconfigure mcporter with the agent token so subsequent calls are authenticated
232
+ try {
233
+ execFileSync('mcporter', ['config', 'remove', 'relaycast'], { stdio: 'pipe' });
234
+ } catch { /* may not exist */ }
235
+
236
+ execFileSync('mcporter', [
237
+ 'config', 'add', 'relaycast',
238
+ '--command', 'npx',
239
+ '--arg', '@relaycast/mcp',
240
+ ...envArgs,
241
+ '--env', `RELAY_AGENT_TOKEN=${agentToken}`,
242
+ '--scope', 'home',
243
+ '--description', 'Relaycast messaging MCP server',
244
+ ], { stdio: 'pipe' });
245
+
246
+ console.log(`Agent "${clawName}" registered with token.`);
247
+ } else {
248
+ console.warn('Agent registered but no token found in response.');
249
+ }
250
+ } catch (regErr) {
251
+ console.warn(`Agent registration via mcporter failed (non-fatal): ${regErr instanceof Error ? regErr.message : String(regErr)}`);
252
+ }
253
+ } catch (err) {
254
+ // mcporter not installed — non-fatal, print manual instructions
255
+ console.warn('mcporter not found. Install MCP servers manually:');
256
+ console.warn(` mcporter config add relaycast --command npx --arg @relaycast/mcp --env RELAY_API_KEY=${apiKey} --scope home`);
257
+ console.warn(` mcporter config add openclaw-spawner --command npx --arg @agent-relay/openclaw --arg mcp-server --env RELAY_API_KEY=${apiKey} --scope home`);
258
+ }
259
+ }
260
+
261
+ // Auto-start the inbound gateway in the background
262
+ let gatewayStarted = false;
263
+ try {
264
+ const child = spawnProcess('npx', ['@agent-relay/openclaw', 'gateway'], {
265
+ stdio: 'ignore',
266
+ detached: true,
267
+ env: { ...process.env, RELAY_API_KEY: apiKey, RELAY_CLAW_NAME: clawName, RELAY_BASE_URL: baseUrl },
268
+ });
269
+ child.unref();
270
+ gatewayStarted = true;
271
+ } catch {
272
+ // Non-fatal — user can start manually
273
+ }
274
+
275
+ const parts = [
276
+ `Relaycast bridge installed at ${skillDir}`,
277
+ mcpConfigured ? 'MCP server configured in openclaw.json.' : '',
278
+ `Claw name: ${clawName}`,
279
+ `Channels: ${channels.join(', ')}`,
280
+ gatewayStarted
281
+ ? 'Inbound gateway started in background.'
282
+ : 'Start the inbound gateway manually:\n relay-openclaw gateway',
283
+ ].filter(Boolean);
284
+
285
+ return {
286
+ ok: true,
287
+ apiKey,
288
+ clawName,
289
+ skillDir,
290
+ message: parts.join('\n'),
291
+ };
292
+ }
293
+
294
+ /** Resolve the path to the bundled SKILL.md. */
295
+ function resolveSkillPath(): string {
296
+ try {
297
+ const thisDir = dirname(fileURLToPath(import.meta.url));
298
+ return join(thisDir, '..', 'skill', 'SKILL.md');
299
+ } catch {
300
+ return join(process.cwd(), 'skill', 'SKILL.md');
301
+ }
302
+ }
303
+
304
+ const FALLBACK_SKILL_MD = `# Relaycast Bridge
305
+
306
+ Structured messaging for multi-claw communication. Provides channels, threads,
307
+ DMs, reactions, search, and persistent message history across OpenClaw instances.
308
+
309
+ ## Environment
310
+
311
+ - \`RELAY_API_KEY\` — Your Relaycast workspace key (required)
312
+ - \`RELAY_CLAW_NAME\` — This claw's agent name in Relaycast (required)
313
+ - \`RELAY_BASE_URL\` — API endpoint (default: https://api.relaycast.dev)
314
+
315
+ ## Setup
316
+
317
+ \`\`\`bash
318
+ relay-openclaw setup [YOUR_WORKSPACE_KEY]
319
+ \`\`\`
320
+
321
+ ## MCP Tools
322
+
323
+ Once installed, use the Relaycast MCP tools:
324
+ - \`post_message\` — Send to a channel
325
+ - \`send_dm\` — Direct message another agent
326
+ - \`reply_to_thread\` — Reply in a thread
327
+ - \`check_inbox\` — See unread messages
328
+
329
+ ## Commands
330
+
331
+ \`\`\`bash
332
+ relay-openclaw setup [key] # Install & configure
333
+ relay-openclaw gateway # Start inbound gateway
334
+ relay-openclaw status # Check connection
335
+ \`\`\`
336
+ `;
@@ -0,0 +1,261 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { randomUUID } from 'node:crypto';
5
+
6
+ import type { SpawnProvider, SpawnOptions, SpawnHandle } from './types.js';
7
+ import { normalizeModelRef } from '../identity/model.js';
8
+ import { buildIdentityTask } from '../identity/contract.js';
9
+ import { buildAgentName } from '../identity/naming.js';
10
+ import { convertCodexAuth } from '../auth/converter.js';
11
+
12
+ async function pathExists(targetPath: string): Promise<boolean> {
13
+ try {
14
+ await access(targetPath);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ function expandHomeDir(input: string): string {
22
+ if (input === '~') return homedir();
23
+ if (input.startsWith('~/')) return join(homedir(), input.slice(2));
24
+ return input;
25
+ }
26
+
27
+ function sanitizeContainerSegment(value: string): string {
28
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, '-');
29
+ return normalized.replace(/^-+|-+$/g, '') || 'claw';
30
+ }
31
+
32
+ export interface DockerSpawnProviderOptions {
33
+ /** Docker image to use. Default: 'openclaw:local'. */
34
+ image?: string;
35
+ /** Fallback image if primary not found. Default: 'clawrunner-sandbox:latest'. */
36
+ imageFallback?: string;
37
+ /** Docker network mode. Default: 'bridge'. */
38
+ networkMode?: string;
39
+ /** Docker socket path. Default: '/var/run/docker.sock'. */
40
+ socketPath?: string;
41
+ /** Path to host codex auth.json. Default: ~/.codex/auth.json. */
42
+ codexAuthFile?: string;
43
+ /** Path to host codex config.toml. Default: ~/.codex/config.toml. */
44
+ codexConfigFile?: string;
45
+ /** Container home dir. Default: '/home/node'. */
46
+ containerHome?: string;
47
+ /**
48
+ * Custom container command. If set, overrides the default entrypoint.
49
+ * Use this for ClawRunner-managed images that have /opt/clawrunner/start-claw.sh.
50
+ * Default: uses @agent-relay/openclaw runtime-setup + openclaw gateway + agent-relay broker-spawn.
51
+ */
52
+ containerCmd?: string[];
53
+ }
54
+
55
+ /**
56
+ * Spawn OpenClaw instances as Docker containers.
57
+ * Requires `dockerode` as an optional peer dependency — dynamically imported at runtime.
58
+ *
59
+ * By default, the container runs:
60
+ * 1. `npx @agent-relay/openclaw runtime-setup` — auth conversion, config, identity files, dist patching
61
+ * 2. `openclaw gateway` in background
62
+ * 3. `agent-relay broker-spawn --from-env` as PID 1
63
+ *
64
+ * For ClawRunner-managed images, set containerCmd to ['/opt/clawrunner/start-claw.sh'].
65
+ */
66
+ export class DockerSpawnProvider implements SpawnProvider {
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ private docker: any = null;
69
+ private readonly image: string;
70
+ private readonly imageFallback: string;
71
+ private readonly networkMode: string;
72
+ private readonly socketPath: string;
73
+ private readonly codexAuthFile: string;
74
+ private readonly codexConfigFile: string;
75
+ private readonly containerHome: string;
76
+ private readonly containerCmd: string[] | null;
77
+ private readonly handles = new Map<string, SpawnHandle>();
78
+
79
+ constructor(options: DockerSpawnProviderOptions = {}) {
80
+ this.image = options.image ?? process.env.CLAW_IMAGE ?? 'openclaw:local';
81
+ this.imageFallback = options.imageFallback ?? process.env.CLAW_IMAGE_FALLBACK ?? 'clawrunner-sandbox:latest';
82
+ this.networkMode = options.networkMode ?? process.env.CLAW_NETWORK ?? 'bridge';
83
+ this.socketPath = options.socketPath ?? process.env.DOCKER_SOCKET ?? '/var/run/docker.sock';
84
+ this.codexAuthFile = expandHomeDir(options.codexAuthFile ?? process.env.CLAW_CODEX_AUTH_FILE ?? '~/.codex/auth.json');
85
+ this.codexConfigFile = expandHomeDir(options.codexConfigFile ?? process.env.CLAW_CODEX_CONFIG_FILE ?? '~/.codex/config.toml');
86
+ this.containerHome = options.containerHome ?? process.env.CLAW_CONTAINER_HOME ?? '/home/node';
87
+ this.containerCmd = options.containerCmd ?? (process.env.CLAW_CONTAINER_CMD
88
+ ? process.env.CLAW_CONTAINER_CMD.split(' ')
89
+ : null);
90
+ }
91
+
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ private async getDocker(): Promise<any> {
94
+ if (this.docker) return this.docker;
95
+ try {
96
+ // @ts-expect-error dockerode is an optional dependency
97
+ const mod = await import('dockerode');
98
+ const Docker = mod.default ?? mod;
99
+ this.docker = new Docker({ socketPath: this.socketPath });
100
+ return this.docker;
101
+ } catch {
102
+ throw new Error(
103
+ 'dockerode is required for Docker spawning. Install it with: npm install dockerode',
104
+ );
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Build the default container entrypoint script.
110
+ * This script works with any vanilla OpenClaw image that has `openclaw` and `node` on PATH.
111
+ * It runs runtime-setup via the package CLI, starts the gateway, then hands off to broker-spawn.
112
+ */
113
+ private buildEntrypointScript(gatewayPort: number): string[] {
114
+ // Shell script that runs setup, starts gateway, waits for health, then execs broker-spawn.
115
+ // Uses sh -c so it works in minimal alpine images.
116
+ // Runtime setup via package CLI, then gateway, then SDK's spawnFromEnv()
117
+ // which handles broker + agent lifecycle without needing the agent-relay CLI.
118
+ const script = [
119
+ 'set -e',
120
+ // Runtime setup: auth conversion, openclaw.json, identity files, dist patching
121
+ 'npx @agent-relay/openclaw runtime-setup',
122
+ // Resolve bridge.mjs from the installed package and symlink to the known AGENT_ARGS path.
123
+ // This handles any npm install location (global, local, npx cache).
124
+ 'node -e "' +
125
+ "const p = require('path');" +
126
+ "const m = require('module');" +
127
+ "const r = m.createRequire(require.resolve('@agent-relay/openclaw/package.json'));" +
128
+ "const bp = p.join(p.dirname(require.resolve('@agent-relay/openclaw/package.json')), 'bridge', 'bridge.mjs');" +
129
+ "require('fs').symlinkSync(bp, '/tmp/openclaw-bridge.mjs');" +
130
+ "console.log('[entrypoint] Bridge resolved: ' + bp);" +
131
+ '"',
132
+ // Start gateway in background
133
+ `openclaw gateway --port ${gatewayPort} --bind loopback --allow-unconfigured --auth token &`,
134
+ // Wait for gateway health
135
+ `for i in $(seq 1 30); do`,
136
+ ` if openclaw health --port ${gatewayPort} 2>/dev/null; then break; fi`,
137
+ ` if [ "$i" -eq 30 ]; then echo "Gateway failed to start" >&2; exit 1; fi`,
138
+ ` sleep 1`,
139
+ `done`,
140
+ // Use SDK's spawnFromEnv() instead of shelling out to agent-relay CLI.
141
+ // This reads AGENT_NAME, AGENT_CLI, RELAY_API_KEY etc. from env,
142
+ // creates a broker internally, spawns the agent via PTY, and waits for exit.
143
+ `node -e "import('@agent-relay/sdk').then(m => m.spawnFromEnv())"`,
144
+ ].join('\n');
145
+
146
+ return ['sh', '-c', script];
147
+ }
148
+
149
+ async spawn(options: SpawnOptions): Promise<SpawnHandle> {
150
+ const docker = await this.getDocker();
151
+ const { preferredProvider } = await convertCodexAuth();
152
+ const modelRef = normalizeModelRef(options.model, preferredProvider);
153
+ const workspaceId = options.workspaceId ?? `local-${Date.now().toString(36)}`;
154
+ const agentName = buildAgentName(workspaceId, options.name);
155
+ const gatewayPort = 18789; // Internal to container — each container is isolated
156
+ const identityTask = buildIdentityTask(agentName, workspaceId, modelRef);
157
+ const channels = options.channels?.length ? options.channels : ['general'];
158
+ const gatewayToken = randomUUID().replace(/-/g, '').slice(0, 32);
159
+
160
+ const suffix = `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
161
+ const containerName = `openclaw-${sanitizeContainerSegment(agentName)}-${suffix}`.slice(0, 63);
162
+
163
+ const binds: string[] = [];
164
+ if (options.workspacePath) {
165
+ binds.push(`${options.workspacePath}:/workspace:rw`);
166
+ }
167
+ if (await pathExists(this.codexAuthFile)) {
168
+ binds.push(`${this.codexAuthFile}:${this.containerHome}/.codex/auth.json:rw`);
169
+ }
170
+ if (await pathExists(this.codexConfigFile)) {
171
+ binds.push(`${this.codexConfigFile}:${this.containerHome}/.codex/config.toml:ro`);
172
+ }
173
+
174
+ const envVars: Record<string, string> = {
175
+ AGENT_NAME: agentName,
176
+ AGENT_CLI: 'node',
177
+ // Bridge path: resolved dynamically inside the container via the entrypoint script.
178
+ // The entrypoint writes the resolved path to /tmp/bridge-path.txt after runtime-setup.
179
+ AGENT_ARGS: '/tmp/openclaw-bridge.mjs',
180
+ RELAY_API_KEY: options.relayApiKey,
181
+ RELAY_BASE_URL: options.relayBaseUrl ?? '',
182
+ AGENT_TASK: options.systemPrompt
183
+ ? `${options.systemPrompt}\n\n${identityTask}`
184
+ : identityTask,
185
+ AGENT_CWD: '/workspace',
186
+ AGENT_CHANNELS: channels.join(','),
187
+ GATEWAY_PORT: String(gatewayPort),
188
+ OPENCLAW_GATEWAY_TOKEN: gatewayToken,
189
+ OPENCLAW_WORKSPACE_ID: workspaceId,
190
+ OPENCLAW_NAME: options.name,
191
+ OPENCLAW_ROLE: options.role ?? 'general',
192
+ OPENCLAW_MODEL: modelRef,
193
+ BROKER_NO_REMOTE_SPAWN: '1',
194
+ };
195
+
196
+ // Try to remove stale container with same name
197
+ try {
198
+ const stale = docker.getContainer(containerName);
199
+ await stale.stop({ t: 5 }).catch(() => {});
200
+ await stale.remove({ force: true }).catch(() => {});
201
+ } catch {
202
+ // No stale container
203
+ }
204
+
205
+ let imageToUse = this.image;
206
+ try {
207
+ await docker.getImage(this.image).inspect();
208
+ } catch {
209
+ imageToUse = this.imageFallback;
210
+ }
211
+
212
+ // Use custom cmd if provided, otherwise generate a vanilla-compatible entrypoint
213
+ const cmd = this.containerCmd ?? this.buildEntrypointScript(gatewayPort);
214
+
215
+ const container = await docker.createContainer({
216
+ Image: imageToUse,
217
+ name: containerName,
218
+ Env: Object.entries(envVars).map(([k, v]: [string, string]) => `${k}=${v}`),
219
+ Cmd: cmd,
220
+ WorkingDir: '/workspace',
221
+ Labels: {
222
+ '@agent-relay/openclaw.spawn': 'true',
223
+ '@agent-relay/openclaw.agent': agentName,
224
+ },
225
+ HostConfig: {
226
+ NetworkMode: this.networkMode,
227
+ Binds: binds,
228
+ AutoRemove: false,
229
+ },
230
+ });
231
+
232
+ await container.start();
233
+
234
+ const handle: SpawnHandle = {
235
+ id: container.id,
236
+ displayName: options.name,
237
+ agentName,
238
+ gatewayPort,
239
+ destroy: () => this.destroy(container.id),
240
+ };
241
+
242
+ this.handles.set(container.id, handle);
243
+ return handle;
244
+ }
245
+
246
+ async destroy(id: string): Promise<void> {
247
+ this.handles.delete(id);
248
+ try {
249
+ const docker = await this.getDocker();
250
+ const container = docker.getContainer(id);
251
+ await container.stop({ t: 5 }).catch(() => {});
252
+ await container.remove({ force: true }).catch(() => {});
253
+ } catch {
254
+ // Already gone
255
+ }
256
+ }
257
+
258
+ async list(): Promise<SpawnHandle[]> {
259
+ return Array.from(this.handles.values());
260
+ }
261
+ }