agent-relay 3.0.1 → 3.1.0

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 (142) hide show
  1. package/README.md +37 -244
  2. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  3. package/bin/agent-relay-broker-darwin-x64 +0 -0
  4. package/bin/agent-relay-broker-linux-arm64 +0 -0
  5. package/bin/agent-relay-broker-linux-x64 +0 -0
  6. package/dist/index.cjs +342 -60
  7. package/dist/src/cli/commands/core.d.ts +2 -0
  8. package/dist/src/cli/commands/core.d.ts.map +1 -1
  9. package/dist/src/cli/commands/core.js +9 -2
  10. package/dist/src/cli/commands/core.js.map +1 -1
  11. package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
  12. package/dist/src/cli/lib/broker-lifecycle.js +87 -28
  13. package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
  14. package/package.json +9 -8
  15. package/packages/acp-bridge/README.md +50 -67
  16. package/packages/acp-bridge/package.json +2 -2
  17. package/packages/config/package.json +1 -1
  18. package/packages/hooks/package.json +4 -4
  19. package/packages/memory/package.json +2 -2
  20. package/packages/policy/package.json +2 -2
  21. package/packages/sdk/README.md +169 -64
  22. package/packages/sdk/dist/__tests__/contract-fixtures.test.js +76 -9
  23. package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +1 -1
  24. package/packages/sdk/dist/__tests__/facade.test.js +48 -0
  25. package/packages/sdk/dist/__tests__/facade.test.js.map +1 -1
  26. package/packages/sdk/dist/__tests__/integration.test.js +11 -5
  27. package/packages/sdk/dist/__tests__/integration.test.js.map +1 -1
  28. package/packages/sdk/dist/__tests__/unit.test.js +36 -0
  29. package/packages/sdk/dist/__tests__/unit.test.js.map +1 -1
  30. package/packages/sdk/dist/client.d.ts +36 -3
  31. package/packages/sdk/dist/client.d.ts.map +1 -1
  32. package/packages/sdk/dist/client.js +142 -9
  33. package/packages/sdk/dist/client.js.map +1 -1
  34. package/packages/sdk/dist/protocol.d.ts +7 -1
  35. package/packages/sdk/dist/protocol.d.ts.map +1 -1
  36. package/packages/sdk/dist/relay.d.ts +74 -11
  37. package/packages/sdk/dist/relay.d.ts.map +1 -1
  38. package/packages/sdk/dist/relay.js +175 -27
  39. package/packages/sdk/dist/relay.js.map +1 -1
  40. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  41. package/packages/sdk/dist/workflows/runner.js +71 -36
  42. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  43. package/packages/sdk/dist/workflows/types.d.ts +1 -1
  44. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  45. package/packages/sdk/package.json +2 -2
  46. package/packages/sdk/src/__tests__/contract-fixtures.test.ts +88 -9
  47. package/packages/sdk/src/__tests__/error-scenarios.test.ts +1 -1
  48. package/packages/sdk/src/__tests__/facade.test.ts +68 -0
  49. package/packages/sdk/src/__tests__/idle-nudge.test.ts +205 -257
  50. package/packages/sdk/src/__tests__/integration.test.ts +11 -5
  51. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +277 -13
  52. package/packages/sdk/src/__tests__/swarm-coordinator.test.ts +1 -0
  53. package/packages/sdk/src/__tests__/unit.test.ts +44 -0
  54. package/packages/sdk/src/__tests__/workflow-runner.test.ts +67 -7
  55. package/packages/sdk/src/__tests__/workflow-trajectory.test.ts +4 -5
  56. package/packages/sdk/src/client.ts +195 -14
  57. package/packages/sdk/src/examples/workflows/runner-idle-refactor.yaml +306 -0
  58. package/packages/sdk/src/protocol.ts +7 -2
  59. package/packages/sdk/src/relay.ts +271 -38
  60. package/packages/sdk/src/workflows/runner.ts +73 -42
  61. package/packages/sdk/src/workflows/schema.json +1 -1
  62. package/packages/sdk/src/workflows/types.ts +1 -1
  63. package/packages/sdk/vitest.config.ts +1 -0
  64. package/packages/sdk-py/README.md +89 -102
  65. package/packages/sdk-py/agent_relay/__init__.py +16 -19
  66. package/packages/sdk-py/pyproject.toml +6 -2
  67. package/packages/sdk-py/src/agent_relay/__init__.py +35 -1
  68. package/packages/sdk-py/src/agent_relay/client.py +776 -0
  69. package/packages/sdk-py/src/agent_relay/models.py +27 -0
  70. package/packages/sdk-py/src/agent_relay/protocol.py +114 -0
  71. package/packages/sdk-py/src/agent_relay/relay.py +860 -0
  72. package/packages/sdk-py/tests/test_relay_lifecycle_hooks.py +250 -0
  73. package/packages/telemetry/package.json +1 -1
  74. package/packages/trajectory/package.json +2 -2
  75. package/packages/user-directory/package.json +2 -2
  76. package/packages/utils/package.json +2 -2
  77. package/scripts/postinstall.js +35 -162
  78. package/packages/sdk/.trajectories/active/traj_1771875803391_84ca57b2.json +0 -50
  79. package/packages/sdk/.trajectories/active/traj_1771891934534_06504121.json +0 -50
  80. package/packages/sdk/.trajectories/active/traj_1771891957929_211afc4e.json +0 -50
  81. package/packages/sdk/.trajectories/active/traj_1771891982509_38c84638.json +0 -50
  82. package/packages/sdk/.trajectories/completed/traj_1771875803188_cd6d181c.json +0 -80
  83. package/packages/sdk/.trajectories/completed/traj_1771875803204_f2aeb8c8.json +0 -80
  84. package/packages/sdk/.trajectories/completed/traj_1771875803210_d65f3f1a.json +0 -80
  85. package/packages/sdk/.trajectories/completed/traj_1771875803218_e454a25d.json +0 -80
  86. package/packages/sdk/.trajectories/completed/traj_1771875803223_d7a64815.json +0 -80
  87. package/packages/sdk/.trajectories/completed/traj_1771875803227_7e56da5b.json +0 -80
  88. package/packages/sdk/.trajectories/completed/traj_1771875803235_4fbf93b4.json +0 -80
  89. package/packages/sdk/.trajectories/completed/traj_1771875803243_47931c71.json +0 -80
  90. package/packages/sdk/.trajectories/completed/traj_1771875803258_3816f3fe.json +0 -80
  91. package/packages/sdk/.trajectories/completed/traj_1771875803268_8061140e.json +0 -80
  92. package/packages/sdk/.trajectories/completed/traj_1771875803326_ae6f9c78.json +0 -80
  93. package/packages/sdk/.trajectories/completed/traj_1771875808396_cbde0a6c.json +0 -91
  94. package/packages/sdk/.trajectories/completed/traj_1771875812026_aa2442bb.json +0 -91
  95. package/packages/sdk/.trajectories/completed/traj_1771875815431_c2c656c5.json +0 -91
  96. package/packages/sdk/.trajectories/completed/traj_1771875818645_3a4dbf02.json +0 -91
  97. package/packages/sdk/.trajectories/completed/traj_1771891934403_24923c03.json +0 -80
  98. package/packages/sdk/.trajectories/completed/traj_1771891934421_dca16e24.json +0 -80
  99. package/packages/sdk/.trajectories/completed/traj_1771891934430_057706f7.json +0 -80
  100. package/packages/sdk/.trajectories/completed/traj_1771891934442_faf97382.json +0 -80
  101. package/packages/sdk/.trajectories/completed/traj_1771891934454_5542ecd5.json +0 -80
  102. package/packages/sdk/.trajectories/completed/traj_1771891934464_12202a08.json +0 -80
  103. package/packages/sdk/.trajectories/completed/traj_1771891934487_94378275.json +0 -80
  104. package/packages/sdk/.trajectories/completed/traj_1771891934503_ca728c13.json +0 -80
  105. package/packages/sdk/.trajectories/completed/traj_1771891934519_100af69a.json +0 -80
  106. package/packages/sdk/.trajectories/completed/traj_1771891934536_62ad39d9.json +0 -80
  107. package/packages/sdk/.trajectories/completed/traj_1771891934553_d6798a52.json +0 -80
  108. package/packages/sdk/.trajectories/completed/traj_1771891939537_541c8096.json +0 -91
  109. package/packages/sdk/.trajectories/completed/traj_1771891942985_36ab9a4d.json +0 -91
  110. package/packages/sdk/.trajectories/completed/traj_1771891946453_e8a6e05f.json +0 -91
  111. package/packages/sdk/.trajectories/completed/traj_1771891949838_5de0de84.json +0 -91
  112. package/packages/sdk/.trajectories/completed/traj_1771891957807_0ecfb4f4.json +0 -80
  113. package/packages/sdk/.trajectories/completed/traj_1771891957827_c4539239.json +0 -80
  114. package/packages/sdk/.trajectories/completed/traj_1771891957836_91168b48.json +0 -80
  115. package/packages/sdk/.trajectories/completed/traj_1771891957848_8c5cad0b.json +0 -80
  116. package/packages/sdk/.trajectories/completed/traj_1771891957857_0986b293.json +0 -80
  117. package/packages/sdk/.trajectories/completed/traj_1771891957872_8a3113af.json +0 -80
  118. package/packages/sdk/.trajectories/completed/traj_1771891957884_0bb85208.json +0 -80
  119. package/packages/sdk/.trajectories/completed/traj_1771891957892_86c75e2e.json +0 -80
  120. package/packages/sdk/.trajectories/completed/traj_1771891957907_98ca0e6f.json +0 -80
  121. package/packages/sdk/.trajectories/completed/traj_1771891957918_d9091231.json +0 -80
  122. package/packages/sdk/.trajectories/completed/traj_1771891957931_dcaf77ed.json +0 -80
  123. package/packages/sdk/.trajectories/completed/traj_1771891962931_eb1fdee2.json +0 -91
  124. package/packages/sdk/.trajectories/completed/traj_1771891966262_9061a93f.json +0 -91
  125. package/packages/sdk/.trajectories/completed/traj_1771891969915_1adaba19.json +0 -91
  126. package/packages/sdk/.trajectories/completed/traj_1771891973588_f08b79e9.json +0 -91
  127. package/packages/sdk/.trajectories/completed/traj_1771891982421_f1985bce.json +0 -80
  128. package/packages/sdk/.trajectories/completed/traj_1771891982432_e7a84163.json +0 -80
  129. package/packages/sdk/.trajectories/completed/traj_1771891982447_369b842a.json +0 -80
  130. package/packages/sdk/.trajectories/completed/traj_1771891982469_5fc45199.json +0 -80
  131. package/packages/sdk/.trajectories/completed/traj_1771891982495_454c7cb3.json +0 -80
  132. package/packages/sdk/.trajectories/completed/traj_1771891982514_08098e03.json +0 -80
  133. package/packages/sdk/.trajectories/completed/traj_1771891982526_b351d778.json +0 -80
  134. package/packages/sdk/.trajectories/completed/traj_1771891982533_fa542d83.json +0 -80
  135. package/packages/sdk/.trajectories/completed/traj_1771891982540_18ab24dc.json +0 -80
  136. package/packages/sdk/.trajectories/completed/traj_1771891982544_5b4fa163.json +0 -80
  137. package/packages/sdk/.trajectories/completed/traj_1771891982548_c13f089a.json +0 -80
  138. package/packages/sdk/.trajectories/completed/traj_1771891987510_23f6da1f.json +0 -91
  139. package/packages/sdk/.trajectories/completed/traj_1771891991466_912c2e04.json +0 -91
  140. package/packages/sdk/.trajectories/completed/traj_1771891994891_60604be2.json +0 -91
  141. package/packages/sdk/.trajectories/completed/traj_1771891998370_cfaf9b8b.json +0 -91
  142. package/packages/sdk/bin/agent-relay-broker +0 -0
@@ -0,0 +1,250 @@
1
+ """Tests for spawn/release lifecycle hooks in the high-level relay facade."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from unittest.mock import AsyncMock
7
+
8
+ import pytest
9
+
10
+ from agent_relay import AgentRelay, SpawnOptions
11
+
12
+
13
+ class _FakeRelayClient:
14
+ def __init__(self) -> None:
15
+ self.spawn_error: Exception | None = None
16
+ self.release_error: Exception | None = None
17
+ self.spawn_calls: list[dict] = []
18
+ self.release_calls: list[tuple[str, str | None]] = []
19
+
20
+ async def spawn_pty(self, **kwargs):
21
+ self.spawn_calls.append(kwargs)
22
+ if self.spawn_error:
23
+ raise self.spawn_error
24
+ return {"name": kwargs["name"], "runtime": "pty"}
25
+
26
+ async def release(self, name: str, reason: str | None = None):
27
+ self.release_calls.append((name, reason))
28
+ if self.release_error:
29
+ raise self.release_error
30
+ return {"name": name}
31
+
32
+
33
+ @pytest.mark.asyncio
34
+ async def test_spawn_lifecycle_hooks_success():
35
+ relay = AgentRelay()
36
+ client = _FakeRelayClient()
37
+ relay._ensure_started = AsyncMock(return_value=client)
38
+
39
+ events: list[tuple[str, dict]] = []
40
+ options = SpawnOptions(
41
+ channels=["general"],
42
+ on_start=lambda ctx: events.append(("start", dict(ctx))),
43
+ on_success=lambda ctx: events.append(("success", dict(ctx))),
44
+ on_error=lambda ctx: events.append(("error", dict(ctx))),
45
+ )
46
+
47
+ agent = await relay.spawn("HookWorker", "claude", "Do the work", options)
48
+
49
+ assert agent.name == "HookWorker"
50
+ assert events[0] == (
51
+ "start",
52
+ {
53
+ "name": "HookWorker",
54
+ "cli": "claude",
55
+ "channels": ["general"],
56
+ "task": "Do the work",
57
+ },
58
+ )
59
+ assert events[1] == (
60
+ "success",
61
+ {
62
+ "name": "HookWorker",
63
+ "cli": "claude",
64
+ "channels": ["general"],
65
+ "task": "Do the work",
66
+ "runtime": "pty",
67
+ },
68
+ )
69
+ assert len(events) == 2
70
+
71
+
72
+ @pytest.mark.asyncio
73
+ async def test_spawn_lifecycle_hooks_support_async_callbacks():
74
+ relay = AgentRelay()
75
+ client = _FakeRelayClient()
76
+ relay._ensure_started = AsyncMock(return_value=client)
77
+
78
+ start_done = False
79
+ success_done = False
80
+
81
+ async def on_start(_ctx):
82
+ nonlocal start_done
83
+ await asyncio.sleep(0)
84
+ start_done = True
85
+
86
+ async def on_success(_ctx):
87
+ nonlocal success_done
88
+ await asyncio.sleep(0)
89
+ success_done = True
90
+
91
+ options = SpawnOptions(
92
+ channels=["general"],
93
+ on_start=on_start,
94
+ on_success=on_success,
95
+ )
96
+
97
+ await relay.spawn("AsyncHookWorker", "claude", "Do the work", options)
98
+
99
+ assert start_done is True
100
+ assert success_done is True
101
+
102
+
103
+ @pytest.mark.asyncio
104
+ async def test_spawn_lifecycle_hooks_error():
105
+ relay = AgentRelay()
106
+ client = _FakeRelayClient()
107
+ client.spawn_error = RuntimeError("spawn failed")
108
+ relay._ensure_started = AsyncMock(return_value=client)
109
+
110
+ on_error_calls: list[dict] = []
111
+ options = SpawnOptions(
112
+ channels=["general"],
113
+ on_start=lambda _: None,
114
+ on_error=lambda ctx: on_error_calls.append(dict(ctx)),
115
+ )
116
+
117
+ with pytest.raises(RuntimeError, match="spawn failed"):
118
+ await relay.spawn("HookWorkerFail", "claude", "Do the work", options)
119
+
120
+ assert len(on_error_calls) == 1
121
+ error_ctx = on_error_calls[0]
122
+ assert error_ctx["name"] == "HookWorkerFail"
123
+ assert error_ctx["cli"] == "claude"
124
+ assert isinstance(error_ctx["error"], RuntimeError)
125
+
126
+
127
+ @pytest.mark.asyncio
128
+ async def test_shorthand_spawn_lifecycle_hooks_success():
129
+ relay = AgentRelay()
130
+ client = _FakeRelayClient()
131
+ relay._ensure_started = AsyncMock(return_value=client)
132
+
133
+ events: list[str] = []
134
+ agent = await relay.claude.spawn(
135
+ name="ShorthandWorker",
136
+ channels=["general"],
137
+ task="Run analysis",
138
+ on_start=lambda _: events.append("start"),
139
+ on_success=lambda _: events.append("success"),
140
+ on_error=lambda _: events.append("error"),
141
+ )
142
+
143
+ assert agent.name == "ShorthandWorker"
144
+ assert events == ["start", "success"]
145
+
146
+
147
+ @pytest.mark.asyncio
148
+ async def test_shorthand_spawn_does_not_fire_start_hook_if_broker_startup_fails():
149
+ relay = AgentRelay()
150
+ relay._ensure_started = AsyncMock(side_effect=RuntimeError("broker startup failed"))
151
+
152
+ start_called = False
153
+ error_called = False
154
+
155
+ def _mark_called(kind: str) -> None:
156
+ nonlocal start_called, error_called
157
+ if kind == "start":
158
+ start_called = True
159
+ else:
160
+ error_called = True
161
+
162
+ with pytest.raises(RuntimeError, match="broker startup failed"):
163
+ await relay.claude.spawn(
164
+ name="ShorthandWorkerStartupFail",
165
+ channels=["general"],
166
+ on_start=lambda _ctx: _mark_called("start"),
167
+ on_error=lambda _ctx: _mark_called("error"),
168
+ )
169
+
170
+ assert start_called is False
171
+ assert error_called is False
172
+
173
+
174
+ @pytest.mark.asyncio
175
+ async def test_release_lifecycle_hooks_success_and_error():
176
+ relay = AgentRelay()
177
+ client = _FakeRelayClient()
178
+ relay._ensure_started = AsyncMock(return_value=client)
179
+
180
+ agent = await relay.spawn("ReleaseWorker", "claude")
181
+
182
+ success_events: list[str] = []
183
+ await agent.release(
184
+ "cleanup",
185
+ on_start=lambda _: success_events.append("start"),
186
+ on_success=lambda _: success_events.append("success"),
187
+ on_error=lambda _: success_events.append("error"),
188
+ )
189
+
190
+ assert client.release_calls[-1] == ("ReleaseWorker", "cleanup")
191
+ assert success_events == ["start", "success"]
192
+
193
+ client.release_error = RuntimeError("release failed")
194
+ error_calls: list[dict] = []
195
+ with pytest.raises(RuntimeError, match="release failed"):
196
+ await agent.release(
197
+ "cleanup-again",
198
+ on_error=lambda ctx: error_calls.append(dict(ctx)),
199
+ )
200
+
201
+ assert len(error_calls) == 1
202
+ assert error_calls[0]["name"] == "ReleaseWorker"
203
+ assert isinstance(error_calls[0]["error"], RuntimeError)
204
+
205
+
206
+ @pytest.mark.asyncio
207
+ async def test_release_lifecycle_hooks_support_async_callbacks():
208
+ relay = AgentRelay()
209
+ client = _FakeRelayClient()
210
+ relay._ensure_started = AsyncMock(return_value=client)
211
+
212
+ agent = await relay.spawn("ReleaseAsyncWorker", "claude")
213
+
214
+ success_done = False
215
+
216
+ async def on_success(_ctx):
217
+ nonlocal success_done
218
+ await asyncio.sleep(0)
219
+ success_done = True
220
+
221
+ await agent.release("cleanup", on_success=on_success)
222
+
223
+ assert success_done is True
224
+
225
+
226
+ @pytest.mark.asyncio
227
+ async def test_release_does_not_fire_hooks_if_broker_startup_fails():
228
+ relay = AgentRelay()
229
+ client = _FakeRelayClient()
230
+ relay._ensure_started = AsyncMock(return_value=client)
231
+ agent = await relay.spawn("ReleaseStartupFailWorker", "claude")
232
+
233
+ relay._ensure_started = AsyncMock(side_effect=RuntimeError("broker startup failed"))
234
+
235
+ start_called = False
236
+ error_called = False
237
+
238
+ def mark_start(_ctx):
239
+ nonlocal start_called
240
+ start_called = True
241
+
242
+ def mark_error(_ctx):
243
+ nonlocal error_called
244
+ error_called = True
245
+
246
+ with pytest.raises(RuntimeError, match="broker startup failed"):
247
+ await agent.release("cleanup", on_start=mark_start, on_error=mark_error)
248
+
249
+ assert start_called is False
250
+ assert error_called is False
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/telemetry",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Anonymous telemetry for Agent Relay usage analytics",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/trajectory",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Trajectory integration utilities (trail/PDERO) for Relay",
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/config": "3.0.1"
25
+ "@agent-relay/config": "3.1.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/user-directory",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "User directory service for agent-relay (per-user credential storage)",
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/utils": "3.0.1"
25
+ "@agent-relay/utils": "3.1.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/utils",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Shared utilities for agent-relay: logging, name generation, command resolution, update checking",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.js",
@@ -112,7 +112,7 @@
112
112
  "vitest": "^3.2.4"
113
113
  },
114
114
  "dependencies": {
115
- "@agent-relay/config": "3.0.1",
115
+ "@agent-relay/config": "3.1.0",
116
116
  "compare-versions": "^6.1.1"
117
117
  },
118
118
  "publishConfig": {
@@ -3,7 +3,7 @@
3
3
  * Postinstall Script for agent-relay
4
4
  *
5
5
  * This script runs after npm install to:
6
- * 1. Install relay-pty binary for current platform
6
+ * 1. Install agent-relay-broker binary for current platform
7
7
  * 2. Install dashboard dependencies
8
8
  * 3. Patch agent-trajectories CLI
9
9
  * 4. Check for tmux availability (fallback)
@@ -148,36 +148,6 @@ function ensureSqliteDriver() {
148
148
  return { ok: false, driver: 'none', statusPath, error: detail };
149
149
  }
150
150
 
151
- /**
152
- * Get the platform-specific binary name for relay-pty
153
- * Returns null if platform is not supported
154
- */
155
- function getRelayPtyBinaryName() {
156
- const platform = os.platform();
157
- const arch = os.arch();
158
-
159
- // Map Node.js arch to Rust target arch
160
- const archMap = {
161
- 'arm64': 'arm64',
162
- 'x64': 'x64',
163
- };
164
-
165
- // Map Node.js platform to Rust target platform
166
- const platformMap = {
167
- 'darwin': 'darwin',
168
- 'linux': 'linux',
169
- };
170
-
171
- const targetPlatform = platformMap[platform];
172
- const targetArch = archMap[arch];
173
-
174
- if (!targetPlatform || !targetArch) {
175
- return null;
176
- }
177
-
178
- return `relay-pty-${targetPlatform}-${targetArch}`;
179
- }
180
-
181
151
  /** Read the package version from package.json */
182
152
  function getPackageVersion(pkgRoot) {
183
153
  const packageJsonPath = path.join(pkgRoot, 'package.json');
@@ -198,7 +168,7 @@ function getPackageVersion(pkgRoot) {
198
168
  * Download a file via HTTPS, following redirects
199
169
  * Uses only built-in https module (no deps)
200
170
  */
201
- function downloadRelayPtyBinary(url, destPath, maxRedirects = 5) {
171
+ function downloadBinary(url, destPath, maxRedirects = 5) {
202
172
  fs.mkdirSync(path.dirname(destPath), { recursive: true });
203
173
 
204
174
  const attemptDownload = (currentUrl, redirectsRemaining, resolve, reject) => {
@@ -210,7 +180,7 @@ function downloadRelayPtyBinary(url, destPath, maxRedirects = 5) {
210
180
  if (isRedirect) {
211
181
  if (redirectsRemaining <= 0) {
212
182
  res.resume();
213
- reject(new Error('Too many redirects while downloading relay-pty binary'));
183
+ reject(new Error('Too many redirects while downloading binary'));
214
184
  return;
215
185
  }
216
186
 
@@ -280,83 +250,6 @@ function resignBinaryForMacOS(binaryPath) {
280
250
  }
281
251
  }
282
252
 
283
- /**
284
- * Install the relay-pty binary for the current platform
285
- */
286
- async function installRelayPtyBinary() {
287
- const pkgRoot = getPackageRoot();
288
- const binaryName = getRelayPtyBinaryName();
289
-
290
- if (!binaryName) {
291
- warn(`Unsupported platform: ${os.platform()}-${os.arch()}`);
292
- warn('relay-pty binary not available, will fall back to tmux mode');
293
- return false;
294
- }
295
-
296
- const sourcePath = path.join(pkgRoot, 'bin', binaryName);
297
- const targetPath = path.join(pkgRoot, 'bin', 'relay-pty');
298
-
299
- // Check if platform-specific binary exists (bundled install)
300
- if (!fs.existsSync(sourcePath)) {
301
- const version = getPackageVersion(pkgRoot);
302
- if (!version) {
303
- warn('relay-pty binary not available and package version unknown');
304
- warn('Will fall back to tmux mode');
305
- return false;
306
- }
307
-
308
- const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version}/${binaryName}`;
309
- info(`relay-pty binary not bundled, downloading from ${downloadUrl} ...`);
310
-
311
- try {
312
- await downloadRelayPtyBinary(downloadUrl, sourcePath);
313
- fs.chmodSync(sourcePath, 0o755);
314
- success(`Downloaded relay-pty binary for ${os.platform()}-${os.arch()}`);
315
- } catch (err) {
316
- const message = err instanceof Error ? err.message : String(err);
317
- warn(`Failed to download relay-pty binary: ${message}`);
318
- warn('Will fall back to tmux mode');
319
- return false;
320
- }
321
- }
322
-
323
- // Check if already installed (and is a symlink or copy of correct binary)
324
- if (fs.existsSync(targetPath)) {
325
- try {
326
- // Check if it's already the right binary by comparing size
327
- const sourceStats = fs.statSync(sourcePath);
328
- const targetStats = fs.statSync(targetPath);
329
- if (sourceStats.size === targetStats.size) {
330
- // Re-sign even if already installed to ensure signature is valid
331
- // This fixes issues where previous installs have invalid signatures
332
- resignBinaryForMacOS(targetPath);
333
- info('relay-pty binary already installed');
334
- return true;
335
- }
336
- } catch {
337
- // Continue to reinstall
338
- }
339
- }
340
-
341
- // Copy the binary (symlinks don't work well across npm install)
342
- try {
343
- fs.copyFileSync(sourcePath, targetPath);
344
- fs.chmodSync(targetPath, 0o755);
345
-
346
- // Re-sign the binary on macOS to prevent code signature validation failures
347
- // Without this, macOS may SIGKILL the process immediately on execution
348
- if (resignBinaryForMacOS(targetPath)) {
349
- success(`Installed relay-pty binary for ${os.platform()}-${os.arch()}`);
350
- } else {
351
- warn(`Installed relay-pty binary but signing failed - may not work on macOS`);
352
- }
353
- return true;
354
- } catch (err) {
355
- warn(`Failed to install relay-pty binary: ${err.message}`);
356
- return false;
357
- }
358
- }
359
-
360
253
  /**
361
254
  * Get the platform-specific binary name for the broker binary.
362
255
  * The broker binary is the Rust-compiled broker (not the Bun-compiled CLI).
@@ -391,8 +284,9 @@ function getBrokerBinaryName() {
391
284
  *
392
285
  * Resolution order:
393
286
  * 1. Already bundled at packages/sdk/bin/agent-relay-broker (e.g. from prepack)
394
- * 2. Download platform-specific standalone binary from GitHub releases
395
- * 3. Fall back to the local Rust debug binary at target/debug/agent-relay-broker (dev only)
287
+ * 2. Platform-specific binary bundled in root bin/ (e.g. bin/agent-relay-broker-linux-x64)
288
+ * 3. Download platform-specific standalone binary from GitHub releases
289
+ * 4. Fall back to the local Rust debug binary at target/debug/agent-relay-broker (dev only)
396
290
  */
397
291
  async function installBrokerBinary() {
398
292
  const pkgRoot = getPackageRoot();
@@ -420,8 +314,25 @@ async function installBrokerBinary() {
420
314
 
421
315
  fs.mkdirSync(sdkBinDir, { recursive: true });
422
316
 
423
- // 2. Try downloading from GitHub releases
424
317
  const binaryName = getBrokerBinaryName();
318
+
319
+ // 2. Check for bundled platform-specific binary in root bin/
320
+ if (binaryName) {
321
+ const bundledBinary = path.join(pkgRoot, 'bin', binaryName);
322
+ if (fs.existsSync(bundledBinary)) {
323
+ try {
324
+ fs.copyFileSync(bundledBinary, targetPath);
325
+ fs.chmodSync(targetPath, 0o755);
326
+ resignBinaryForMacOS(targetPath);
327
+ success(`Installed broker binary from bundled package (${binaryName})`);
328
+ return true;
329
+ } catch (err) {
330
+ warn(`Failed to copy bundled broker binary: ${err.message}`);
331
+ }
332
+ }
333
+ }
334
+
335
+ // 3. Try downloading from GitHub releases
425
336
  if (binaryName) {
426
337
  const version = getPackageVersion(pkgRoot);
427
338
  if (version) {
@@ -429,7 +340,7 @@ async function installBrokerBinary() {
429
340
  info(`Downloading broker binary from ${downloadUrl} ...`);
430
341
 
431
342
  try {
432
- await downloadRelayPtyBinary(downloadUrl, targetPath);
343
+ await downloadBinary(downloadUrl, targetPath);
433
344
  fs.chmodSync(targetPath, 0o755);
434
345
  resignBinaryForMacOS(targetPath);
435
346
  success(`Downloaded broker binary for ${os.platform()}-${os.arch()}`);
@@ -441,7 +352,7 @@ async function installBrokerBinary() {
441
352
  }
442
353
  }
443
354
 
444
- // 3. Dev fallback — check for local Rust build (release first, then debug)
355
+ // 4. Dev fallback — check for local Rust build (release first, then debug)
445
356
  for (const profile of ['release', 'debug']) {
446
357
  const localBinary = path.join(pkgRoot, 'target', profile, binaryFilename);
447
358
  if (fs.existsSync(localBinary)) {
@@ -462,18 +373,6 @@ async function installBrokerBinary() {
462
373
  return false;
463
374
  }
464
375
 
465
- /**
466
- * Check if tmux is available on the system
467
- */
468
- function hasSystemTmux() {
469
- try {
470
- execSync('which tmux', { stdio: 'pipe' });
471
- return true;
472
- } catch {
473
- return false;
474
- }
475
- }
476
-
477
376
  /**
478
377
  * Setup workspace package symlinks for global/bundled installs.
479
378
  *
@@ -683,7 +582,7 @@ function patchAgentTrajectories() {
683
582
  success('Patched agent-trajectories to record agent on trail start');
684
583
  }
685
584
 
686
- function logPostinstallDiagnostics(hasRelayPty, hasBrokerBinary, sqliteStatus, linkResult) {
585
+ function logPostinstallDiagnostics(hasBrokerBinary, sqliteStatus, linkResult) {
687
586
  // Workspace packages status (for global installs)
688
587
  if (linkResult && linkResult.needed) {
689
588
  if (linkResult.success) {
@@ -693,16 +592,10 @@ function logPostinstallDiagnostics(hasRelayPty, hasBrokerBinary, sqliteStatus, l
693
592
  }
694
593
  }
695
594
 
696
- if (hasRelayPty) {
697
- console.log('✓ relay-pty binary installed');
698
- } else {
699
- console.log('⚠ relay-pty binary not installed - falling back to tmux mode if available');
700
- }
701
-
702
595
  if (hasBrokerBinary) {
703
- console.log('✓ broker binary installed (SDK programmatic usage ready)');
596
+ console.log('✓ agent-relay-broker binary installed');
704
597
  } else {
705
- console.log('⚠ broker binary not installed - AgentRelay programmatic API will not work');
598
+ console.log('⚠ agent-relay-broker binary not installed - AgentRelay will not work');
706
599
  }
707
600
 
708
601
  if (sqliteStatus.ok && sqliteStatus.driver === 'better-sqlite3') {
@@ -734,10 +627,7 @@ async function main() {
734
627
  }
735
628
  }
736
629
 
737
- // Install relay-pty binary for current platform (primary mode)
738
- const hasRelayPty = await installRelayPtyBinary();
739
-
740
- // Install broker binary for SDK programmatic usage (AgentRelay)
630
+ // Install broker binary for agent spawning and SDK programmatic usage
741
631
  const hasBrokerBinary = await installBrokerBinary();
742
632
 
743
633
  // Ensure SQLite driver is available (better-sqlite3 or node:sqlite)
@@ -750,30 +640,13 @@ async function main() {
750
640
  installDashboardDeps();
751
641
 
752
642
  // Always print diagnostics (even in CI)
753
- logPostinstallDiagnostics(hasRelayPty, hasBrokerBinary, sqliteStatus, linkResult);
754
-
755
- // Skip tmux check in CI environments
756
- if (process.env.CI === 'true') {
757
- return;
758
- }
643
+ logPostinstallDiagnostics(hasBrokerBinary, sqliteStatus, linkResult);
759
644
 
760
- // If relay-pty is installed, we're good
761
- if (hasRelayPty) {
762
- info('Using relay-pty for agent communication (fast mode)');
763
- return;
645
+ if (!hasBrokerBinary) {
646
+ warn('agent-relay-broker binary not available');
647
+ info('Agent spawning will not work without the broker binary.');
648
+ info('To fix: cargo build --release --bin agent-relay-broker (requires Rust toolchain)');
764
649
  }
765
-
766
- // Fall back to tmux check
767
- if (hasSystemTmux()) {
768
- info('System tmux found (fallback mode)');
769
- return;
770
- }
771
-
772
- // Neither relay-pty nor tmux available
773
- warn('Neither relay-pty nor tmux available');
774
- info('Agent spawning will not work without one of:');
775
- info(' 1. relay-pty binary (included for darwin-arm64, darwin-x64, linux-x64)');
776
- info(' 2. tmux: brew install tmux (macOS) or apt install tmux (Linux)');
777
650
  }
778
651
 
779
652
  main().catch((err) => {
@@ -1,50 +0,0 @@
1
- {
2
- "id": "traj_1771875803391_84ca57b2",
3
- "version": 1,
4
- "task": {
5
- "title": "default run #18889026",
6
- "source": {
7
- "system": "workflow-runner",
8
- "id": "188890267c8264bd0f5a7422"
9
- }
10
- },
11
- "status": "active",
12
- "startedAt": "2026-02-23T19:43:23.391Z",
13
- "agents": [
14
- {
15
- "name": "orchestrator",
16
- "role": "workflow-runner",
17
- "joinedAt": "2026-02-23T19:43:23.391Z"
18
- },
19
- {
20
- "name": "agent-a",
21
- "role": "agent-a",
22
- "joinedAt": "2026-02-23T19:43:28.180Z"
23
- }
24
- ],
25
- "chapters": [
26
- {
27
- "id": "ch_ed619507",
28
- "title": "Planning",
29
- "agentName": "orchestrator",
30
- "startedAt": "2026-02-23T19:43:23.391Z",
31
- "events": [
32
- {
33
- "ts": 1771875803391,
34
- "type": "note",
35
- "content": "Workflow \"default\" started with 2 steps"
36
- },
37
- {
38
- "ts": 1771875803391,
39
- "type": "note",
40
- "content": "Parsed 2 steps, 1 dependent steps, DAG validated, no cycles"
41
- },
42
- {
43
- "ts": 1771875808180,
44
- "type": "note",
45
- "content": "Step \"step-1\" assigned to agent \"agent-a\""
46
- }
47
- ]
48
- }
49
- ]
50
- }