agent-relay 3.2.13 → 3.2.15
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/README.md +1 -0
- package/dist/index.cjs +27 -9
- package/package.json +12 -11
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/cli-registry.d.ts.map +1 -1
- package/packages/sdk/dist/cli-registry.js +6 -1
- package/packages/sdk/dist/cli-registry.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +1 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/__tests__/workflow-runner.test.ts +2 -2
- package/packages/sdk/src/cli-registry.ts +6 -1
- package/packages/sdk/src/workflows/runner.ts +1 -1
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/sdk-swift/Package.swift +26 -0
- package/packages/sdk-swift/README.md +39 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift +405 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayObserver.swift +323 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayObserverTypes.swift +143 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift +220 -0
- package/packages/sdk-swift/Sources/AgentRelaySDK/RelayTypes.swift +435 -0
- package/packages/sdk-swift/Tests/AgentRelaySDKTests/AgentRelaySDKTests.swift +15 -0
- package/packages/sdk-swift/Tests/AgentRelaySDKTests/RelayObserverTests.swift +526 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import AgentRelaySDK
|
|
3
|
+
|
|
4
|
+
// MARK: - Helpers
|
|
5
|
+
|
|
6
|
+
private func jsonData(_ jsonString: String) -> Data {
|
|
7
|
+
jsonString.data(using: .utf8)!
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private func decodeEvent(_ json: String) throws -> RelayObserverEvent {
|
|
11
|
+
let decoder = JSONDecoder()
|
|
12
|
+
return try decoder.decode(RelayObserverEvent.self, from: jsonData(json))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private func encodeToDict<T: Encodable>(_ value: T) throws -> [String: Any] {
|
|
16
|
+
let encoder = JSONEncoder()
|
|
17
|
+
encoder.keyEncodingStrategy = .convertToSnakeCase
|
|
18
|
+
let data = try encoder.encode(value)
|
|
19
|
+
return try JSONSerialization.jsonObject(with: data) as! [String: Any]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// MARK: - Event Type Raw Values
|
|
23
|
+
|
|
24
|
+
final class RelayObserverEventTypeTests: XCTestCase {
|
|
25
|
+
|
|
26
|
+
func testAllRawValues() {
|
|
27
|
+
let expected: [(RelayObserverEventType, String)] = [
|
|
28
|
+
(.agentSpawned, "agent_spawned"),
|
|
29
|
+
(.agentReleased, "agent_released"),
|
|
30
|
+
(.agentIdle, "agent_idle"),
|
|
31
|
+
(.agentStatus, "agent_status"),
|
|
32
|
+
(.workerStream, "worker_stream"),
|
|
33
|
+
(.delivery, "delivery"),
|
|
34
|
+
(.channelMessage, "channel_message"),
|
|
35
|
+
(.stepStarted, "step_started"),
|
|
36
|
+
(.stepCompleted, "step_completed"),
|
|
37
|
+
(.runCompleted, "run_completed"),
|
|
38
|
+
(.relayConfig, "relay_config"),
|
|
39
|
+
(.relayWorkspace, "relay_workspace"),
|
|
40
|
+
(.commentPollTick, "comment_poll_tick"),
|
|
41
|
+
(.commentDetected, "comment_detected"),
|
|
42
|
+
(.error, "error"),
|
|
43
|
+
(.ack, "ack"),
|
|
44
|
+
(.connected, "connected"),
|
|
45
|
+
(.subscribed, "subscribed"),
|
|
46
|
+
(.pong, "pong"),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
XCTAssertEqual(expected.count, 19, "Should cover all 19 event types")
|
|
50
|
+
|
|
51
|
+
for (eventType, rawValue) in expected {
|
|
52
|
+
XCTAssertEqual(eventType.rawValue, rawValue, "Raw value mismatch for \(eventType)")
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func testRoundTripCodable() throws {
|
|
57
|
+
let encoder = JSONEncoder()
|
|
58
|
+
let decoder = JSONDecoder()
|
|
59
|
+
for eventType in [RelayObserverEventType.agentSpawned, .delivery, .pong, .error] {
|
|
60
|
+
let data = try encoder.encode(eventType)
|
|
61
|
+
let decoded = try decoder.decode(RelayObserverEventType.self, from: data)
|
|
62
|
+
XCTAssertEqual(decoded, eventType)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// MARK: - Event Decoding Tests
|
|
68
|
+
|
|
69
|
+
final class RelayObserverEventDecodingTests: XCTestCase {
|
|
70
|
+
|
|
71
|
+
// MARK: agent_spawned
|
|
72
|
+
|
|
73
|
+
func testAgentSpawnedWithName() throws {
|
|
74
|
+
let event = try decodeEvent("""
|
|
75
|
+
{
|
|
76
|
+
"type": "agent_spawned",
|
|
77
|
+
"name": "worker-1",
|
|
78
|
+
"cli": "claude",
|
|
79
|
+
"channels": ["ch-1", "ch-2"]
|
|
80
|
+
}
|
|
81
|
+
""")
|
|
82
|
+
XCTAssertEqual(event.type, .agentSpawned)
|
|
83
|
+
XCTAssertEqual(event.name, "worker-1")
|
|
84
|
+
XCTAssertNil(event.agentName)
|
|
85
|
+
XCTAssertEqual(event.cli, "claude")
|
|
86
|
+
XCTAssertEqual(event.channels, ["ch-1", "ch-2"])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func testAgentSpawnedWithAgentName() throws {
|
|
90
|
+
let event = try decodeEvent("""
|
|
91
|
+
{
|
|
92
|
+
"type": "agent_spawned",
|
|
93
|
+
"agent_name": "worker-2",
|
|
94
|
+
"cli": "codex",
|
|
95
|
+
"channels": ["ch-3"]
|
|
96
|
+
}
|
|
97
|
+
""")
|
|
98
|
+
XCTAssertEqual(event.type, .agentSpawned)
|
|
99
|
+
XCTAssertNil(event.name)
|
|
100
|
+
XCTAssertEqual(event.agentName, "worker-2")
|
|
101
|
+
XCTAssertEqual(event.cli, "codex")
|
|
102
|
+
XCTAssertEqual(event.channels, ["ch-3"])
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func testAgentSpawnedWithBothNames() throws {
|
|
106
|
+
let event = try decodeEvent("""
|
|
107
|
+
{
|
|
108
|
+
"type": "agent_spawned",
|
|
109
|
+
"name": "worker-a",
|
|
110
|
+
"agent_name": "worker-b",
|
|
111
|
+
"cli": "claude",
|
|
112
|
+
"channels": []
|
|
113
|
+
}
|
|
114
|
+
""")
|
|
115
|
+
XCTAssertEqual(event.name, "worker-a")
|
|
116
|
+
XCTAssertEqual(event.agentName, "worker-b")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// MARK: agent_released
|
|
120
|
+
|
|
121
|
+
func testAgentReleased() throws {
|
|
122
|
+
let event = try decodeEvent("""
|
|
123
|
+
{"type": "agent_released", "name": "worker-1", "reason": "task_complete"}
|
|
124
|
+
""")
|
|
125
|
+
XCTAssertEqual(event.type, .agentReleased)
|
|
126
|
+
XCTAssertEqual(event.name, "worker-1")
|
|
127
|
+
XCTAssertEqual(event.reason, "task_complete")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// MARK: agent_idle
|
|
131
|
+
|
|
132
|
+
func testAgentIdle() throws {
|
|
133
|
+
let event = try decodeEvent("""
|
|
134
|
+
{"type": "agent_idle", "name": "worker-1", "idle_secs": 120}
|
|
135
|
+
""")
|
|
136
|
+
XCTAssertEqual(event.type, .agentIdle)
|
|
137
|
+
XCTAssertEqual(event.name, "worker-1")
|
|
138
|
+
XCTAssertEqual(event.idleSecs, 120)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// MARK: agent_status
|
|
142
|
+
|
|
143
|
+
func testAgentStatus() throws {
|
|
144
|
+
let event = try decodeEvent("""
|
|
145
|
+
{"type": "agent_status", "name": "worker-1", "status": "busy"}
|
|
146
|
+
""")
|
|
147
|
+
XCTAssertEqual(event.type, .agentStatus)
|
|
148
|
+
XCTAssertEqual(event.name, "worker-1")
|
|
149
|
+
XCTAssertEqual(event.status, "busy")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// MARK: worker_stream
|
|
153
|
+
|
|
154
|
+
func testWorkerStream() throws {
|
|
155
|
+
let event = try decodeEvent("""
|
|
156
|
+
{"type": "worker_stream", "agent": "worker-1", "data": "hello world", "stream": "stdout"}
|
|
157
|
+
""")
|
|
158
|
+
XCTAssertEqual(event.type, .workerStream)
|
|
159
|
+
XCTAssertEqual(event.agent, "worker-1")
|
|
160
|
+
XCTAssertEqual(event.data, "hello world")
|
|
161
|
+
XCTAssertEqual(event.stream, "stdout")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MARK: delivery
|
|
165
|
+
|
|
166
|
+
func testDeliveryCompleted() throws {
|
|
167
|
+
let event = try decodeEvent("""
|
|
168
|
+
{
|
|
169
|
+
"type": "delivery",
|
|
170
|
+
"id": "msg-1",
|
|
171
|
+
"from": "lead",
|
|
172
|
+
"to": "worker-1",
|
|
173
|
+
"text": "do this task",
|
|
174
|
+
"state": "completed"
|
|
175
|
+
}
|
|
176
|
+
""")
|
|
177
|
+
XCTAssertEqual(event.type, .delivery)
|
|
178
|
+
XCTAssertEqual(event.id, "msg-1")
|
|
179
|
+
XCTAssertEqual(event.from, "lead")
|
|
180
|
+
XCTAssertEqual(event.to, "worker-1")
|
|
181
|
+
XCTAssertEqual(event.text, "do this task")
|
|
182
|
+
XCTAssertEqual(event.state, "completed")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func testDeliveryFailed() throws {
|
|
186
|
+
let event = try decodeEvent("""
|
|
187
|
+
{
|
|
188
|
+
"type": "delivery",
|
|
189
|
+
"id": "msg-2",
|
|
190
|
+
"from": "lead",
|
|
191
|
+
"to": "worker-2",
|
|
192
|
+
"text": "another task",
|
|
193
|
+
"state": "failed"
|
|
194
|
+
}
|
|
195
|
+
""")
|
|
196
|
+
XCTAssertEqual(event.type, .delivery)
|
|
197
|
+
XCTAssertEqual(event.state, "failed")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// MARK: channel_message
|
|
201
|
+
|
|
202
|
+
func testChannelMessage() throws {
|
|
203
|
+
let event = try decodeEvent("""
|
|
204
|
+
{
|
|
205
|
+
"type": "channel_message",
|
|
206
|
+
"channel": "general",
|
|
207
|
+
"from": "user-1",
|
|
208
|
+
"text": "hey team",
|
|
209
|
+
"timestamp": "2026-03-23T10:00:00Z"
|
|
210
|
+
}
|
|
211
|
+
""")
|
|
212
|
+
XCTAssertEqual(event.type, .channelMessage)
|
|
213
|
+
XCTAssertEqual(event.channel, "general")
|
|
214
|
+
XCTAssertEqual(event.from, "user-1")
|
|
215
|
+
XCTAssertEqual(event.text, "hey team")
|
|
216
|
+
XCTAssertEqual(event.timestamp, "2026-03-23T10:00:00Z")
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// MARK: step_started
|
|
220
|
+
|
|
221
|
+
func testStepStartedWithStep() throws {
|
|
222
|
+
let event = try decodeEvent("""
|
|
223
|
+
{"type": "step_started", "step": "build"}
|
|
224
|
+
""")
|
|
225
|
+
XCTAssertEqual(event.type, .stepStarted)
|
|
226
|
+
XCTAssertEqual(event.step, "build")
|
|
227
|
+
XCTAssertNil(event.stepName)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
func testStepStartedWithStepName() throws {
|
|
231
|
+
let event = try decodeEvent("""
|
|
232
|
+
{"type": "step_started", "step_name": "compile"}
|
|
233
|
+
""")
|
|
234
|
+
XCTAssertEqual(event.type, .stepStarted)
|
|
235
|
+
XCTAssertNil(event.step)
|
|
236
|
+
XCTAssertEqual(event.stepName, "compile")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func testStepStartedWithBothStepFields() throws {
|
|
240
|
+
let event = try decodeEvent("""
|
|
241
|
+
{"type": "step_started", "step": "step-1", "step_name": "Build App"}
|
|
242
|
+
""")
|
|
243
|
+
XCTAssertEqual(event.step, "step-1")
|
|
244
|
+
XCTAssertEqual(event.stepName, "Build App")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// MARK: step_completed
|
|
248
|
+
|
|
249
|
+
func testStepCompleted() throws {
|
|
250
|
+
let event = try decodeEvent("""
|
|
251
|
+
{"type": "step_completed", "step": "build", "step_name": "Build App", "output": "success"}
|
|
252
|
+
""")
|
|
253
|
+
XCTAssertEqual(event.type, .stepCompleted)
|
|
254
|
+
XCTAssertEqual(event.step, "build")
|
|
255
|
+
XCTAssertEqual(event.stepName, "Build App")
|
|
256
|
+
XCTAssertEqual(event.output, "success")
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// MARK: run_completed
|
|
260
|
+
|
|
261
|
+
func testRunCompleted() throws {
|
|
262
|
+
let event = try decodeEvent("""
|
|
263
|
+
{"type": "run_completed", "run_id": "run-abc-123"}
|
|
264
|
+
""")
|
|
265
|
+
XCTAssertEqual(event.type, .runCompleted)
|
|
266
|
+
XCTAssertEqual(event.runId, "run-abc-123")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// MARK: relay_config
|
|
270
|
+
|
|
271
|
+
func testRelayConfig() throws {
|
|
272
|
+
let event = try decodeEvent("""
|
|
273
|
+
{"type": "relay_config", "observer_url": "wss://relay.example.com/observe"}
|
|
274
|
+
""")
|
|
275
|
+
XCTAssertEqual(event.type, .relayConfig)
|
|
276
|
+
XCTAssertEqual(event.observerUrl, "wss://relay.example.com/observe")
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// MARK: relay_workspace
|
|
280
|
+
|
|
281
|
+
func testRelayWorkspace() throws {
|
|
282
|
+
let event = try decodeEvent("""
|
|
283
|
+
{"type": "relay_workspace", "workspace_id": "ws-42"}
|
|
284
|
+
""")
|
|
285
|
+
XCTAssertEqual(event.type, .relayWorkspace)
|
|
286
|
+
XCTAssertEqual(event.workspaceId, "ws-42")
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// MARK: comment_poll_tick
|
|
290
|
+
|
|
291
|
+
func testCommentPollTick() throws {
|
|
292
|
+
let event = try decodeEvent("""
|
|
293
|
+
{"type": "comment_poll_tick", "checked_at": "2026-03-23T12:00:00Z", "interval_seconds": 30}
|
|
294
|
+
""")
|
|
295
|
+
XCTAssertEqual(event.type, .commentPollTick)
|
|
296
|
+
XCTAssertEqual(event.checkedAt, "2026-03-23T12:00:00Z")
|
|
297
|
+
XCTAssertEqual(event.intervalSeconds, 30)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// MARK: comment_detected
|
|
301
|
+
|
|
302
|
+
func testCommentDetected() throws {
|
|
303
|
+
let event = try decodeEvent("""
|
|
304
|
+
{"type": "comment_detected", "message": "New comment on PR #42"}
|
|
305
|
+
""")
|
|
306
|
+
XCTAssertEqual(event.type, .commentDetected)
|
|
307
|
+
XCTAssertEqual(event.message, "New comment on PR #42")
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// MARK: error
|
|
311
|
+
|
|
312
|
+
func testError() throws {
|
|
313
|
+
let event = try decodeEvent("""
|
|
314
|
+
{"type": "error", "message": "something went wrong"}
|
|
315
|
+
""")
|
|
316
|
+
XCTAssertEqual(event.type, .error)
|
|
317
|
+
XCTAssertEqual(event.message, "something went wrong")
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// MARK: ack
|
|
321
|
+
|
|
322
|
+
func testAck() throws {
|
|
323
|
+
let event = try decodeEvent("""
|
|
324
|
+
{"type": "ack"}
|
|
325
|
+
""")
|
|
326
|
+
XCTAssertEqual(event.type, .ack)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// MARK: connected
|
|
330
|
+
|
|
331
|
+
func testConnected() throws {
|
|
332
|
+
let event = try decodeEvent("""
|
|
333
|
+
{"type": "connected"}
|
|
334
|
+
""")
|
|
335
|
+
XCTAssertEqual(event.type, .connected)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// MARK: subscribed
|
|
339
|
+
|
|
340
|
+
func testSubscribed() throws {
|
|
341
|
+
let event = try decodeEvent("""
|
|
342
|
+
{"type": "subscribed", "channel": "ops"}
|
|
343
|
+
""")
|
|
344
|
+
XCTAssertEqual(event.type, .subscribed)
|
|
345
|
+
XCTAssertEqual(event.channel, "ops")
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// MARK: pong
|
|
349
|
+
|
|
350
|
+
func testPong() throws {
|
|
351
|
+
let event = try decodeEvent("""
|
|
352
|
+
{"type": "pong"}
|
|
353
|
+
""")
|
|
354
|
+
XCTAssertEqual(event.type, .pong)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// MARK: - Optional Fields (minimal event)
|
|
359
|
+
|
|
360
|
+
final class RelayObserverEventOptionalFieldsTests: XCTestCase {
|
|
361
|
+
|
|
362
|
+
func testMinimalAckHasAllOptionalsNil() throws {
|
|
363
|
+
let event = try decodeEvent("""
|
|
364
|
+
{"type": "ack"}
|
|
365
|
+
""")
|
|
366
|
+
XCTAssertEqual(event.type, .ack)
|
|
367
|
+
XCTAssertNil(event.name)
|
|
368
|
+
XCTAssertNil(event.agentName)
|
|
369
|
+
XCTAssertNil(event.cli)
|
|
370
|
+
XCTAssertNil(event.channels)
|
|
371
|
+
XCTAssertNil(event.reason)
|
|
372
|
+
XCTAssertNil(event.idleSecs)
|
|
373
|
+
XCTAssertNil(event.status)
|
|
374
|
+
XCTAssertNil(event.agent)
|
|
375
|
+
XCTAssertNil(event.data)
|
|
376
|
+
XCTAssertNil(event.stream)
|
|
377
|
+
XCTAssertNil(event.id)
|
|
378
|
+
XCTAssertNil(event.from)
|
|
379
|
+
XCTAssertNil(event.to)
|
|
380
|
+
XCTAssertNil(event.text)
|
|
381
|
+
XCTAssertNil(event.state)
|
|
382
|
+
XCTAssertNil(event.channel)
|
|
383
|
+
XCTAssertNil(event.timestamp)
|
|
384
|
+
XCTAssertNil(event.step)
|
|
385
|
+
XCTAssertNil(event.stepName)
|
|
386
|
+
XCTAssertNil(event.output)
|
|
387
|
+
XCTAssertNil(event.runId)
|
|
388
|
+
XCTAssertNil(event.observerUrl)
|
|
389
|
+
XCTAssertNil(event.workspaceId)
|
|
390
|
+
XCTAssertNil(event.checkedAt)
|
|
391
|
+
XCTAssertNil(event.intervalSeconds)
|
|
392
|
+
XCTAssertNil(event.message)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// MARK: - Outbound Message Encoding
|
|
397
|
+
|
|
398
|
+
final class RelayObserverOutboundEncodingTests: XCTestCase {
|
|
399
|
+
|
|
400
|
+
func testSubscribeMessage() throws {
|
|
401
|
+
let msg = ObserverSubscribeMessage(channel: "ops")
|
|
402
|
+
let dict = try encodeToDict(msg)
|
|
403
|
+
XCTAssertEqual(dict["type"] as? String, "subscribe")
|
|
404
|
+
XCTAssertEqual(dict["channel"] as? String, "ops")
|
|
405
|
+
XCTAssertEqual(dict.count, 2)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
func testChannelSendMessageFull() throws {
|
|
409
|
+
let msg = ObserverChannelSendMessage(
|
|
410
|
+
channel: "general",
|
|
411
|
+
text: "hello",
|
|
412
|
+
personas: ["lead", "worker"],
|
|
413
|
+
cliPreferences: ["model": "opus"]
|
|
414
|
+
)
|
|
415
|
+
let dict = try encodeToDict(msg)
|
|
416
|
+
XCTAssertEqual(dict["type"] as? String, "channel_send")
|
|
417
|
+
XCTAssertEqual(dict["channel"] as? String, "general")
|
|
418
|
+
XCTAssertEqual(dict["text"] as? String, "hello")
|
|
419
|
+
XCTAssertEqual(dict["personas"] as? [String], ["lead", "worker"])
|
|
420
|
+
let prefs = dict["cli_preferences"] as? [String: String]
|
|
421
|
+
XCTAssertEqual(prefs?["model"], "opus")
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
func testChannelSendMessageMinimal() throws {
|
|
425
|
+
let msg = ObserverChannelSendMessage(
|
|
426
|
+
channel: "ops",
|
|
427
|
+
text: "ping",
|
|
428
|
+
personas: nil,
|
|
429
|
+
cliPreferences: nil
|
|
430
|
+
)
|
|
431
|
+
let dict = try encodeToDict(msg)
|
|
432
|
+
XCTAssertEqual(dict["type"] as? String, "channel_send")
|
|
433
|
+
XCTAssertEqual(dict["channel"] as? String, "ops")
|
|
434
|
+
XCTAssertEqual(dict["text"] as? String, "ping")
|
|
435
|
+
// nil optionals should not be present
|
|
436
|
+
XCTAssertNil(dict["personas"])
|
|
437
|
+
XCTAssertNil(dict["cli_preferences"])
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
func testDirectSendMessage() throws {
|
|
441
|
+
let msg = ObserverDirectSendMessage(to: "worker-1", text: "do it")
|
|
442
|
+
let dict = try encodeToDict(msg)
|
|
443
|
+
XCTAssertEqual(dict["type"] as? String, "send")
|
|
444
|
+
XCTAssertEqual(dict["to"] as? String, "worker-1")
|
|
445
|
+
XCTAssertEqual(dict["text"] as? String, "do it")
|
|
446
|
+
XCTAssertEqual(dict.count, 3)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// MARK: - RelayObserverError Tests
|
|
451
|
+
|
|
452
|
+
final class RelayObserverErrorTests: XCTestCase {
|
|
453
|
+
|
|
454
|
+
func testNotConnectedDescription() {
|
|
455
|
+
let error = RelayObserverError.notConnected
|
|
456
|
+
XCTAssertEqual(error.errorDescription, "Relay not connected")
|
|
457
|
+
XCTAssertEqual(error.localizedDescription, "Relay not connected")
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
func testEncodingFailedDescription() {
|
|
461
|
+
let error = RelayObserverError.encodingFailed
|
|
462
|
+
XCTAssertEqual(error.errorDescription, "Message encoding failed")
|
|
463
|
+
XCTAssertEqual(error.localizedDescription, "Message encoding failed")
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// MARK: - ConnectionState Tests
|
|
468
|
+
|
|
469
|
+
final class RelayObserverConnectionStateTests: XCTestCase {
|
|
470
|
+
|
|
471
|
+
func testEquatable() {
|
|
472
|
+
XCTAssertEqual(RelayObserver.ConnectionState.disconnected, .disconnected)
|
|
473
|
+
XCTAssertEqual(RelayObserver.ConnectionState.connecting, .connecting)
|
|
474
|
+
XCTAssertEqual(RelayObserver.ConnectionState.connected, .connected)
|
|
475
|
+
XCTAssertEqual(
|
|
476
|
+
RelayObserver.ConnectionState.reconnecting(attempt: 3),
|
|
477
|
+
.reconnecting(attempt: 3)
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
func testNotEqual() {
|
|
482
|
+
XCTAssertNotEqual(RelayObserver.ConnectionState.disconnected, .connecting)
|
|
483
|
+
XCTAssertNotEqual(RelayObserver.ConnectionState.connected, .disconnected)
|
|
484
|
+
XCTAssertNotEqual(
|
|
485
|
+
RelayObserver.ConnectionState.reconnecting(attempt: 1),
|
|
486
|
+
.reconnecting(attempt: 2)
|
|
487
|
+
)
|
|
488
|
+
XCTAssertNotEqual(
|
|
489
|
+
RelayObserver.ConnectionState.reconnecting(attempt: 1),
|
|
490
|
+
.connected
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// MARK: - RelayObserver Init Tests
|
|
496
|
+
|
|
497
|
+
final class RelayObserverInitTests: XCTestCase {
|
|
498
|
+
|
|
499
|
+
func testDefaultState() {
|
|
500
|
+
let observer = RelayObserver()
|
|
501
|
+
XCTAssertEqual(observer.connectionState, .disconnected)
|
|
502
|
+
XCTAssertEqual(observer.eventCounter, 0)
|
|
503
|
+
XCTAssertNil(observer.lastEvent)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
func testCustomInit() {
|
|
507
|
+
let observer = RelayObserver(maxReconnectAttempts: 3, baseReconnectDelay: 2.0)
|
|
508
|
+
XCTAssertEqual(observer.connectionState, .disconnected)
|
|
509
|
+
XCTAssertEqual(observer.eventCounter, 0)
|
|
510
|
+
XCTAssertNil(observer.lastEvent)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
func testSendChannelThrowsWhenDisconnected() {
|
|
514
|
+
let observer = RelayObserver()
|
|
515
|
+
XCTAssertThrowsError(try observer.sendChannel(channel: "ops", text: "hi")) { error in
|
|
516
|
+
XCTAssertEqual(error as? RelayObserverError, .notConnected)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
func testSendDirectThrowsWhenDisconnected() {
|
|
521
|
+
let observer = RelayObserver()
|
|
522
|
+
XCTAssertThrowsError(try observer.sendDirect(to: "agent", text: "hi")) { error in
|
|
523
|
+
XCTAssertEqual(error as? RelayObserverError, .notConnected)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/trajectory",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.15",
|
|
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.2.
|
|
25
|
+
"@agent-relay/config": "3.2.15"
|
|
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.2.
|
|
3
|
+
"version": "3.2.15",
|
|
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.2.
|
|
25
|
+
"@agent-relay/utils": "3.2.15"
|
|
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.2.
|
|
3
|
+
"version": "3.2.15",
|
|
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.2.
|
|
115
|
+
"@agent-relay/config": "3.2.15",
|
|
116
116
|
"compare-versions": "^6.1.1"
|
|
117
117
|
},
|
|
118
118
|
"publishConfig": {
|