agent-relay 2.3.4 → 2.3.6

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 (297) hide show
  1. package/README.md +1 -1
  2. package/dist/src/cli/index.js +124 -7
  3. package/dist/src/cli/index.js.map +1 -1
  4. package/package.json +23 -26
  5. package/packages/acp-bridge/package.json +2 -2
  6. package/packages/bridge/package.json +7 -7
  7. package/packages/config/package.json +2 -2
  8. package/packages/continuity/package.json +2 -2
  9. package/packages/daemon/package.json +12 -12
  10. package/packages/hooks/package.json +4 -4
  11. package/packages/mcp/package.json +5 -5
  12. package/packages/memory/package.json +2 -2
  13. package/packages/policy/package.json +2 -2
  14. package/packages/protocol/package.json +1 -1
  15. package/packages/resiliency/package.json +1 -1
  16. package/packages/sdk/dist/index.d.ts +1 -29
  17. package/packages/sdk/dist/index.d.ts.map +1 -1
  18. package/packages/sdk/dist/index.js +1 -38
  19. package/packages/sdk/dist/index.js.map +1 -1
  20. package/packages/sdk/package.json +4 -25
  21. package/packages/sdk/src/index.ts +1 -69
  22. package/packages/sdk-py/README.md +56 -0
  23. package/packages/sdk-py/pyproject.toml +23 -0
  24. package/packages/sdk-py/src/agent_relay/__init__.py +27 -0
  25. package/packages/sdk-py/src/agent_relay/builder.py +367 -0
  26. package/packages/sdk-py/src/agent_relay/types.py +92 -0
  27. package/packages/sdk-py/tests/__init__.py +0 -0
  28. package/packages/sdk-py/tests/test_builder.py +101 -0
  29. package/packages/sdk-ts/dist/index.d.ts +1 -0
  30. package/packages/sdk-ts/dist/index.d.ts.map +1 -1
  31. package/packages/sdk-ts/dist/index.js +1 -0
  32. package/packages/sdk-ts/dist/index.js.map +1 -1
  33. package/packages/sdk-ts/dist/workflows/barrier.d.ts +72 -0
  34. package/packages/sdk-ts/dist/workflows/barrier.d.ts.map +1 -0
  35. package/packages/sdk-ts/dist/workflows/barrier.js +162 -0
  36. package/packages/sdk-ts/dist/workflows/barrier.js.map +1 -0
  37. package/packages/sdk-ts/dist/workflows/builder.d.ts +101 -0
  38. package/packages/sdk-ts/dist/workflows/builder.d.ts.map +1 -0
  39. package/packages/sdk-ts/dist/workflows/builder.js +179 -0
  40. package/packages/sdk-ts/dist/workflows/builder.js.map +1 -0
  41. package/packages/sdk-ts/dist/workflows/cli.d.ts +10 -0
  42. package/packages/sdk-ts/dist/workflows/cli.d.ts.map +1 -0
  43. package/packages/sdk-ts/dist/workflows/cli.js +82 -0
  44. package/packages/sdk-ts/dist/workflows/cli.js.map +1 -0
  45. package/packages/sdk-ts/dist/workflows/coordinator.d.ts +68 -0
  46. package/packages/sdk-ts/dist/workflows/coordinator.d.ts.map +1 -0
  47. package/packages/sdk-ts/dist/workflows/coordinator.js +353 -0
  48. package/packages/sdk-ts/dist/workflows/coordinator.js.map +1 -0
  49. package/packages/sdk-ts/dist/workflows/index.d.ts +10 -0
  50. package/packages/sdk-ts/dist/workflows/index.d.ts.map +1 -0
  51. package/packages/sdk-ts/dist/workflows/index.js +10 -0
  52. package/packages/sdk-ts/dist/workflows/index.js.map +1 -0
  53. package/packages/sdk-ts/dist/workflows/memory-db.d.ts +17 -0
  54. package/packages/sdk-ts/dist/workflows/memory-db.d.ts.map +1 -0
  55. package/packages/sdk-ts/dist/workflows/memory-db.js +33 -0
  56. package/packages/sdk-ts/dist/workflows/memory-db.js.map +1 -0
  57. package/packages/sdk-ts/dist/workflows/run.d.ts +31 -0
  58. package/packages/sdk-ts/dist/workflows/run.d.ts.map +1 -0
  59. package/packages/sdk-ts/dist/workflows/run.js +24 -0
  60. package/packages/sdk-ts/dist/workflows/run.js.map +1 -0
  61. package/packages/sdk-ts/dist/workflows/runner.d.ts +119 -0
  62. package/packages/sdk-ts/dist/workflows/runner.d.ts.map +1 -0
  63. package/packages/sdk-ts/dist/workflows/runner.js +650 -0
  64. package/packages/sdk-ts/dist/workflows/runner.js.map +1 -0
  65. package/packages/sdk-ts/dist/workflows/state.d.ts +77 -0
  66. package/packages/sdk-ts/dist/workflows/state.d.ts.map +1 -0
  67. package/packages/sdk-ts/dist/workflows/state.js +140 -0
  68. package/packages/sdk-ts/dist/workflows/state.js.map +1 -0
  69. package/packages/sdk-ts/dist/workflows/templates.d.ts +47 -0
  70. package/packages/sdk-ts/dist/workflows/templates.d.ts.map +1 -0
  71. package/packages/sdk-ts/dist/workflows/templates.js +395 -0
  72. package/packages/sdk-ts/dist/workflows/templates.js.map +1 -0
  73. package/packages/sdk-ts/dist/workflows/types.d.ts +126 -0
  74. package/packages/sdk-ts/dist/workflows/types.d.ts.map +1 -0
  75. package/packages/sdk-ts/dist/workflows/types.js +8 -0
  76. package/packages/sdk-ts/dist/workflows/types.js.map +1 -0
  77. package/packages/sdk-ts/package.json +8 -2
  78. package/packages/sdk-ts/src/__tests__/error-scenarios.test.ts +682 -0
  79. package/packages/sdk-ts/src/__tests__/swarm-coordinator.test.ts +416 -0
  80. package/packages/sdk-ts/src/__tests__/workflow-runner.test.ts +333 -0
  81. package/packages/sdk-ts/src/index.ts +1 -0
  82. package/packages/sdk-ts/src/workflows/README.md +450 -0
  83. package/packages/sdk-ts/src/workflows/barrier.ts +254 -0
  84. package/packages/sdk-ts/src/workflows/builder.ts +241 -0
  85. package/packages/sdk-ts/src/workflows/builtin-templates/bug-fix.yaml +75 -0
  86. package/packages/sdk-ts/src/workflows/builtin-templates/code-review.yaml +82 -0
  87. package/packages/sdk-ts/src/workflows/builtin-templates/documentation.yaml +70 -0
  88. package/packages/sdk-ts/src/workflows/builtin-templates/feature-dev.yaml +76 -0
  89. package/packages/sdk-ts/src/workflows/builtin-templates/refactor.yaml +82 -0
  90. package/packages/sdk-ts/src/workflows/builtin-templates/security-audit.yaml +84 -0
  91. package/packages/sdk-ts/src/workflows/cli.ts +93 -0
  92. package/packages/sdk-ts/src/workflows/coordinator.ts +520 -0
  93. package/packages/sdk-ts/src/workflows/index.ts +9 -0
  94. package/packages/sdk-ts/src/workflows/memory-db.ts +39 -0
  95. package/packages/sdk-ts/src/workflows/run.ts +47 -0
  96. package/packages/sdk-ts/src/workflows/runner.ts +873 -0
  97. package/packages/sdk-ts/src/workflows/schema.json +321 -0
  98. package/packages/sdk-ts/src/workflows/state.ts +279 -0
  99. package/packages/sdk-ts/src/workflows/templates.ts +544 -0
  100. package/packages/sdk-ts/src/workflows/types.ts +178 -0
  101. package/packages/sdk-ts/tsconfig.json +6 -1
  102. package/packages/spawner/package.json +1 -1
  103. package/packages/state/package.json +1 -1
  104. package/packages/storage/package.json +2 -2
  105. package/packages/telemetry/package.json +1 -1
  106. package/packages/trajectory/package.json +2 -2
  107. package/packages/user-directory/package.json +2 -2
  108. package/packages/utils/package.json +3 -3
  109. package/packages/wrapper/package.json +5 -6
  110. package/packages/api-types/.trajectories/active/traj_xbsvuzogscey.json +0 -15
  111. package/packages/api-types/.trajectories/index.json +0 -12
  112. package/packages/api-types/dist/index.d.ts +0 -21
  113. package/packages/api-types/dist/index.d.ts.map +0 -1
  114. package/packages/api-types/dist/index.js +0 -22
  115. package/packages/api-types/dist/index.js.map +0 -1
  116. package/packages/api-types/dist/schemas/agent.d.ts +0 -259
  117. package/packages/api-types/dist/schemas/agent.d.ts.map +0 -1
  118. package/packages/api-types/dist/schemas/agent.js +0 -102
  119. package/packages/api-types/dist/schemas/agent.js.map +0 -1
  120. package/packages/api-types/dist/schemas/api.d.ts +0 -290
  121. package/packages/api-types/dist/schemas/api.d.ts.map +0 -1
  122. package/packages/api-types/dist/schemas/api.js +0 -162
  123. package/packages/api-types/dist/schemas/api.js.map +0 -1
  124. package/packages/api-types/dist/schemas/decision.d.ts +0 -230
  125. package/packages/api-types/dist/schemas/decision.d.ts.map +0 -1
  126. package/packages/api-types/dist/schemas/decision.js +0 -104
  127. package/packages/api-types/dist/schemas/decision.js.map +0 -1
  128. package/packages/api-types/dist/schemas/fleet.d.ts +0 -615
  129. package/packages/api-types/dist/schemas/fleet.d.ts.map +0 -1
  130. package/packages/api-types/dist/schemas/fleet.js +0 -71
  131. package/packages/api-types/dist/schemas/fleet.js.map +0 -1
  132. package/packages/api-types/dist/schemas/history.d.ts +0 -180
  133. package/packages/api-types/dist/schemas/history.d.ts.map +0 -1
  134. package/packages/api-types/dist/schemas/history.js +0 -72
  135. package/packages/api-types/dist/schemas/history.js.map +0 -1
  136. package/packages/api-types/dist/schemas/index.d.ts +0 -14
  137. package/packages/api-types/dist/schemas/index.d.ts.map +0 -1
  138. package/packages/api-types/dist/schemas/index.js +0 -22
  139. package/packages/api-types/dist/schemas/index.js.map +0 -1
  140. package/packages/api-types/dist/schemas/message.d.ts +0 -456
  141. package/packages/api-types/dist/schemas/message.d.ts.map +0 -1
  142. package/packages/api-types/dist/schemas/message.js +0 -88
  143. package/packages/api-types/dist/schemas/message.js.map +0 -1
  144. package/packages/api-types/dist/schemas/session.d.ts +0 -60
  145. package/packages/api-types/dist/schemas/session.d.ts.map +0 -1
  146. package/packages/api-types/dist/schemas/session.js +0 -36
  147. package/packages/api-types/dist/schemas/session.js.map +0 -1
  148. package/packages/api-types/dist/schemas/task.d.ts +0 -111
  149. package/packages/api-types/dist/schemas/task.d.ts.map +0 -1
  150. package/packages/api-types/dist/schemas/task.js +0 -64
  151. package/packages/api-types/dist/schemas/task.js.map +0 -1
  152. package/packages/api-types/package.json +0 -61
  153. package/packages/api-types/scripts/generate-openapi.ts +0 -106
  154. package/packages/api-types/src/index.ts +0 -22
  155. package/packages/api-types/src/schemas/agent.test.ts +0 -164
  156. package/packages/api-types/src/schemas/agent.ts +0 -110
  157. package/packages/api-types/src/schemas/api.test.ts +0 -372
  158. package/packages/api-types/src/schemas/api.ts +0 -194
  159. package/packages/api-types/src/schemas/decision.test.ts +0 -324
  160. package/packages/api-types/src/schemas/decision.ts +0 -136
  161. package/packages/api-types/src/schemas/fleet.test.ts +0 -212
  162. package/packages/api-types/src/schemas/fleet.ts +0 -83
  163. package/packages/api-types/src/schemas/history.test.ts +0 -242
  164. package/packages/api-types/src/schemas/history.ts +0 -84
  165. package/packages/api-types/src/schemas/index.ts +0 -148
  166. package/packages/api-types/src/schemas/message.test.ts +0 -192
  167. package/packages/api-types/src/schemas/message.ts +0 -98
  168. package/packages/api-types/src/schemas/session.test.ts +0 -104
  169. package/packages/api-types/src/schemas/session.ts +0 -40
  170. package/packages/api-types/src/schemas/task.test.ts +0 -192
  171. package/packages/api-types/src/schemas/task.ts +0 -78
  172. package/packages/api-types/tsconfig.json +0 -19
  173. package/packages/api-types/vitest.config.ts +0 -9
  174. package/packages/benchmark/README.md +0 -200
  175. package/packages/benchmark/datasets/coding-tasks.yaml +0 -127
  176. package/packages/benchmark/datasets/coordination-tasks.yaml +0 -122
  177. package/packages/benchmark/datasets/quick-test.yaml +0 -20
  178. package/packages/benchmark/dist/benchmark.d.ts +0 -47
  179. package/packages/benchmark/dist/benchmark.d.ts.map +0 -1
  180. package/packages/benchmark/dist/benchmark.js +0 -224
  181. package/packages/benchmark/dist/benchmark.js.map +0 -1
  182. package/packages/benchmark/dist/cli.d.ts +0 -8
  183. package/packages/benchmark/dist/cli.d.ts.map +0 -1
  184. package/packages/benchmark/dist/cli.js +0 -185
  185. package/packages/benchmark/dist/cli.js.map +0 -1
  186. package/packages/benchmark/dist/harbor.d.ts +0 -53
  187. package/packages/benchmark/dist/harbor.d.ts.map +0 -1
  188. package/packages/benchmark/dist/harbor.js +0 -127
  189. package/packages/benchmark/dist/harbor.js.map +0 -1
  190. package/packages/benchmark/dist/index.d.ts +0 -48
  191. package/packages/benchmark/dist/index.d.ts.map +0 -1
  192. package/packages/benchmark/dist/index.js +0 -50
  193. package/packages/benchmark/dist/index.js.map +0 -1
  194. package/packages/benchmark/dist/runners/base.d.ts +0 -63
  195. package/packages/benchmark/dist/runners/base.d.ts.map +0 -1
  196. package/packages/benchmark/dist/runners/base.js +0 -156
  197. package/packages/benchmark/dist/runners/base.js.map +0 -1
  198. package/packages/benchmark/dist/runners/index.d.ts +0 -10
  199. package/packages/benchmark/dist/runners/index.d.ts.map +0 -1
  200. package/packages/benchmark/dist/runners/index.js +0 -10
  201. package/packages/benchmark/dist/runners/index.js.map +0 -1
  202. package/packages/benchmark/dist/runners/single.d.ts +0 -19
  203. package/packages/benchmark/dist/runners/single.d.ts.map +0 -1
  204. package/packages/benchmark/dist/runners/single.js +0 -111
  205. package/packages/benchmark/dist/runners/single.js.map +0 -1
  206. package/packages/benchmark/dist/runners/subagent.d.ts +0 -32
  207. package/packages/benchmark/dist/runners/subagent.d.ts.map +0 -1
  208. package/packages/benchmark/dist/runners/subagent.js +0 -212
  209. package/packages/benchmark/dist/runners/subagent.js.map +0 -1
  210. package/packages/benchmark/dist/runners/swarm.d.ts +0 -36
  211. package/packages/benchmark/dist/runners/swarm.d.ts.map +0 -1
  212. package/packages/benchmark/dist/runners/swarm.js +0 -273
  213. package/packages/benchmark/dist/runners/swarm.js.map +0 -1
  214. package/packages/benchmark/dist/types.d.ts +0 -178
  215. package/packages/benchmark/dist/types.d.ts.map +0 -1
  216. package/packages/benchmark/dist/types.js +0 -16
  217. package/packages/benchmark/dist/types.js.map +0 -1
  218. package/packages/benchmark/package.json +0 -80
  219. package/packages/benchmark/src/benchmark.ts +0 -298
  220. package/packages/benchmark/src/cli.ts +0 -240
  221. package/packages/benchmark/src/harbor.ts +0 -170
  222. package/packages/benchmark/src/index.ts +0 -73
  223. package/packages/benchmark/src/runners/base.ts +0 -205
  224. package/packages/benchmark/src/runners/index.ts +0 -10
  225. package/packages/benchmark/src/runners/single.ts +0 -121
  226. package/packages/benchmark/src/runners/subagent.ts +0 -240
  227. package/packages/benchmark/src/runners/swarm.ts +0 -326
  228. package/packages/benchmark/src/types.ts +0 -205
  229. package/packages/benchmark/tsconfig.json +0 -20
  230. package/packages/cli-tester/README.md +0 -277
  231. package/packages/cli-tester/dist/index.d.ts +0 -21
  232. package/packages/cli-tester/dist/index.d.ts.map +0 -1
  233. package/packages/cli-tester/dist/index.js +0 -21
  234. package/packages/cli-tester/dist/index.js.map +0 -1
  235. package/packages/cli-tester/dist/utils/credential-check.d.ts +0 -56
  236. package/packages/cli-tester/dist/utils/credential-check.d.ts.map +0 -1
  237. package/packages/cli-tester/dist/utils/credential-check.js +0 -230
  238. package/packages/cli-tester/dist/utils/credential-check.js.map +0 -1
  239. package/packages/cli-tester/dist/utils/socket-client.d.ts +0 -76
  240. package/packages/cli-tester/dist/utils/socket-client.d.ts.map +0 -1
  241. package/packages/cli-tester/dist/utils/socket-client.js +0 -153
  242. package/packages/cli-tester/dist/utils/socket-client.js.map +0 -1
  243. package/packages/cli-tester/docker/Dockerfile +0 -61
  244. package/packages/cli-tester/docker/docker-compose.yml +0 -71
  245. package/packages/cli-tester/docker/entrypoint.sh +0 -58
  246. package/packages/cli-tester/package.json +0 -32
  247. package/packages/cli-tester/scripts/clear-auth.sh +0 -101
  248. package/packages/cli-tester/scripts/inject-message.sh +0 -42
  249. package/packages/cli-tester/scripts/start.sh +0 -71
  250. package/packages/cli-tester/scripts/test-cli.sh +0 -56
  251. package/packages/cli-tester/scripts/test-full-spawn.sh +0 -238
  252. package/packages/cli-tester/scripts/test-registration.sh +0 -182
  253. package/packages/cli-tester/scripts/test-setup-flow.sh +0 -202
  254. package/packages/cli-tester/scripts/test-spawn.sh +0 -140
  255. package/packages/cli-tester/scripts/test-with-daemon.sh +0 -247
  256. package/packages/cli-tester/scripts/verify-auth.sh +0 -112
  257. package/packages/cli-tester/src/index.ts +0 -40
  258. package/packages/cli-tester/src/utils/credential-check.ts +0 -284
  259. package/packages/cli-tester/src/utils/socket-client.ts +0 -211
  260. package/packages/cli-tester/tests/credential-check.test.ts +0 -56
  261. package/packages/cli-tester/tsconfig.json +0 -11
  262. package/packages/sdk/dist/browser-client.d.ts +0 -212
  263. package/packages/sdk/dist/browser-client.d.ts.map +0 -1
  264. package/packages/sdk/dist/browser-client.js +0 -750
  265. package/packages/sdk/dist/browser-client.js.map +0 -1
  266. package/packages/sdk/dist/browser-framing.d.ts +0 -46
  267. package/packages/sdk/dist/browser-framing.d.ts.map +0 -1
  268. package/packages/sdk/dist/browser-framing.js +0 -122
  269. package/packages/sdk/dist/browser-framing.js.map +0 -1
  270. package/packages/sdk/dist/standalone.d.ts +0 -89
  271. package/packages/sdk/dist/standalone.d.ts.map +0 -1
  272. package/packages/sdk/dist/standalone.js +0 -131
  273. package/packages/sdk/dist/standalone.js.map +0 -1
  274. package/packages/sdk/dist/transports/index.d.ts +0 -92
  275. package/packages/sdk/dist/transports/index.d.ts.map +0 -1
  276. package/packages/sdk/dist/transports/index.js +0 -129
  277. package/packages/sdk/dist/transports/index.js.map +0 -1
  278. package/packages/sdk/dist/transports/socket-transport.d.ts +0 -30
  279. package/packages/sdk/dist/transports/socket-transport.d.ts.map +0 -1
  280. package/packages/sdk/dist/transports/socket-transport.js +0 -94
  281. package/packages/sdk/dist/transports/socket-transport.js.map +0 -1
  282. package/packages/sdk/dist/transports/types.d.ts +0 -69
  283. package/packages/sdk/dist/transports/types.d.ts.map +0 -1
  284. package/packages/sdk/dist/transports/types.js +0 -10
  285. package/packages/sdk/dist/transports/types.js.map +0 -1
  286. package/packages/sdk/dist/transports/websocket-transport.d.ts +0 -55
  287. package/packages/sdk/dist/transports/websocket-transport.d.ts.map +0 -1
  288. package/packages/sdk/dist/transports/websocket-transport.js +0 -180
  289. package/packages/sdk/dist/transports/websocket-transport.js.map +0 -1
  290. package/packages/sdk/src/browser-client.ts +0 -985
  291. package/packages/sdk/src/browser-framing.test.ts +0 -115
  292. package/packages/sdk/src/browser-framing.ts +0 -150
  293. package/packages/sdk/src/standalone.ts +0 -183
  294. package/packages/sdk/src/transports/index.ts +0 -197
  295. package/packages/sdk/src/transports/socket-transport.ts +0 -115
  296. package/packages/sdk/src/transports/types.ts +0 -77
  297. package/packages/sdk/src/transports/websocket-transport.ts +0 -245
@@ -0,0 +1,416 @@
1
+ /**
2
+ * SwarmCoordinator integration tests.
3
+ *
4
+ * Tests pattern selection, topology resolution, run lifecycle,
5
+ * and step management with a mocked DbClient.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
9
+ import { SwarmCoordinator } from '../workflows/coordinator.js';
10
+ import type { DbClient } from '../workflows/coordinator.js';
11
+ import type { RelayYamlConfig, WorkflowRunRow, WorkflowStepRow } from '../workflows/types.js';
12
+
13
+ // ── Helpers ──────────────────────────────────────────────────────────────────
14
+
15
+ function makeDb(): DbClient {
16
+ return {
17
+ query: vi.fn().mockResolvedValue({ rows: [] }),
18
+ };
19
+ }
20
+
21
+ function makeConfig(overrides: Partial<RelayYamlConfig> = {}): RelayYamlConfig {
22
+ return {
23
+ version: '1',
24
+ name: 'test-workflow',
25
+ swarm: { pattern: 'fan-out' },
26
+ agents: [
27
+ { name: 'leader', cli: 'claude', role: 'lead' },
28
+ { name: 'worker-1', cli: 'claude' },
29
+ { name: 'worker-2', cli: 'codex' },
30
+ ],
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ function makeRunRow(overrides: Partial<WorkflowRunRow> = {}): WorkflowRunRow {
36
+ const now = new Date().toISOString();
37
+ return {
38
+ id: 'run_test_1',
39
+ workspaceId: 'ws-1',
40
+ workflowName: 'test-workflow',
41
+ pattern: 'fan-out',
42
+ status: 'pending',
43
+ config: makeConfig(),
44
+ startedAt: now,
45
+ createdAt: now,
46
+ updatedAt: now,
47
+ ...overrides,
48
+ };
49
+ }
50
+
51
+ function makeStepRow(overrides: Partial<WorkflowStepRow> = {}): WorkflowStepRow {
52
+ const now = new Date().toISOString();
53
+ return {
54
+ id: 'step_test_1',
55
+ runId: 'run_test_1',
56
+ stepName: 'step-1',
57
+ agentName: 'worker-1',
58
+ status: 'pending',
59
+ task: 'Do something',
60
+ dependsOn: [],
61
+ retryCount: 0,
62
+ createdAt: now,
63
+ updatedAt: now,
64
+ ...overrides,
65
+ };
66
+ }
67
+
68
+ // ── Tests ────────────────────────────────────────────────────────────────────
69
+
70
+ describe('SwarmCoordinator', () => {
71
+ let db: DbClient;
72
+ let coordinator: SwarmCoordinator;
73
+
74
+ beforeEach(() => {
75
+ vi.clearAllMocks();
76
+ db = makeDb();
77
+ coordinator = new SwarmCoordinator(db);
78
+ });
79
+
80
+ // ── Pattern selection ──────────────────────────────────────────────────
81
+
82
+ describe('selectPattern', () => {
83
+ it('should return explicit pattern from config', () => {
84
+ expect(coordinator.selectPattern(makeConfig({ swarm: { pattern: 'pipeline' } }))).toBe('pipeline');
85
+ });
86
+
87
+ it('should auto-select dag when steps have dependencies', () => {
88
+ const config = makeConfig({
89
+ swarm: { pattern: undefined as unknown as string } as any,
90
+ workflows: [
91
+ {
92
+ name: 'wf',
93
+ steps: [
94
+ { name: 's1', agent: 'worker-1', task: 'x' },
95
+ { name: 's2', agent: 'worker-2', task: 'y', dependsOn: ['s1'] },
96
+ ],
97
+ },
98
+ ],
99
+ });
100
+ // With pattern set explicitly, it returns it; with undefined it falls through heuristics
101
+ // Since config.swarm.pattern is undefined (truthy check fails), heuristics kick in
102
+ config.swarm.pattern = '' as any;
103
+ const pattern = coordinator.selectPattern(config);
104
+ expect(pattern).toBe('dag');
105
+ });
106
+
107
+ it('should auto-select consensus when consensusStrategy is set', () => {
108
+ const config = makeConfig({
109
+ swarm: { pattern: '' as any },
110
+ coordination: { consensusStrategy: 'majority' },
111
+ });
112
+ expect(coordinator.selectPattern(config)).toBe('consensus');
113
+ });
114
+ });
115
+
116
+ // ── Topology resolution ────────────────────────────────────────────────
117
+
118
+ describe('resolveTopology', () => {
119
+ it('should build fan-out topology with hub', () => {
120
+ const topology = coordinator.resolveTopology(makeConfig());
121
+ expect(topology.pattern).toBe('fan-out');
122
+ expect(topology.hub).toBe('leader');
123
+ expect(topology.edges.get('leader')).toEqual(['worker-1', 'worker-2']);
124
+ expect(topology.edges.get('worker-1')).toEqual(['leader']);
125
+ });
126
+
127
+ it('should build pipeline topology in step order', () => {
128
+ const config = makeConfig({
129
+ swarm: { pattern: 'pipeline' },
130
+ workflows: [
131
+ {
132
+ name: 'wf',
133
+ steps: [
134
+ { name: 's1', agent: 'worker-1', task: 'step 1' },
135
+ { name: 's2', agent: 'worker-2', task: 'step 2' },
136
+ { name: 's3', agent: 'leader', task: 'step 3' },
137
+ ],
138
+ },
139
+ ],
140
+ });
141
+ const topology = coordinator.resolveTopology(config);
142
+ expect(topology.pattern).toBe('pipeline');
143
+ expect(topology.pipelineOrder).toEqual(['worker-1', 'worker-2', 'leader']);
144
+ expect(topology.edges.get('worker-1')).toEqual(['worker-2']);
145
+ expect(topology.edges.get('leader')).toEqual([]);
146
+ });
147
+
148
+ it('should build hub-spoke topology', () => {
149
+ const config = makeConfig({ swarm: { pattern: 'hub-spoke' } });
150
+ const topology = coordinator.resolveTopology(config);
151
+ expect(topology.hub).toBe('leader');
152
+ expect(topology.edges.get('leader')).toContain('worker-1');
153
+ expect(topology.edges.get('worker-1')).toEqual(['leader']);
154
+ });
155
+
156
+ it('should build mesh topology for consensus', () => {
157
+ const config = makeConfig({ swarm: { pattern: 'consensus' } });
158
+ const topology = coordinator.resolveTopology(config);
159
+ expect(topology.edges.get('leader')).toContain('worker-1');
160
+ expect(topology.edges.get('leader')).toContain('worker-2');
161
+ expect(topology.edges.get('worker-1')).toContain('leader');
162
+ });
163
+
164
+ it('should build DAG topology from step dependencies', () => {
165
+ const config = makeConfig({
166
+ swarm: { pattern: 'dag' },
167
+ workflows: [
168
+ {
169
+ name: 'wf',
170
+ steps: [
171
+ { name: 's1', agent: 'worker-1', task: 'x' },
172
+ { name: 's2', agent: 'worker-2', task: 'y', dependsOn: ['s1'] },
173
+ ],
174
+ },
175
+ ],
176
+ });
177
+ const topology = coordinator.resolveTopology(config);
178
+ expect(topology.pattern).toBe('dag');
179
+ expect(topology.edges.get('worker-1')).toContain('worker-2');
180
+ });
181
+
182
+ it('should build hierarchical topology', () => {
183
+ const config = makeConfig({ swarm: { pattern: 'hierarchical' } });
184
+ const topology = coordinator.resolveTopology(config);
185
+ expect(topology.hub).toBe('leader');
186
+ expect(topology.edges.get('leader')).toContain('worker-1');
187
+ });
188
+
189
+ it('should build cascade topology', () => {
190
+ const config = makeConfig({ swarm: { pattern: 'cascade' } });
191
+ const topology = coordinator.resolveTopology(config);
192
+ expect(topology.pipelineOrder).toEqual(['leader', 'worker-1', 'worker-2']);
193
+ });
194
+ });
195
+
196
+ // ── Run lifecycle ──────────────────────────────────────────────────────
197
+
198
+ describe('createRun', () => {
199
+ it('should insert a run and emit run:created', async () => {
200
+ const run = makeRunRow();
201
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [run] });
202
+
203
+ const spy = vi.fn();
204
+ coordinator.on('run:created', spy);
205
+
206
+ const result = await coordinator.createRun('ws-1', makeConfig());
207
+ expect(result).toEqual(run);
208
+ expect(spy).toHaveBeenCalledWith(run);
209
+ expect(db.query).toHaveBeenCalledOnce();
210
+ });
211
+ });
212
+
213
+ describe('startRun', () => {
214
+ it('should transition pending run to running', async () => {
215
+ const run = makeRunRow({ status: 'running' });
216
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [run] });
217
+
218
+ const spy = vi.fn();
219
+ coordinator.on('run:started', spy);
220
+
221
+ const result = await coordinator.startRun('run_test_1');
222
+ expect(result.status).toBe('running');
223
+ expect(spy).toHaveBeenCalledWith(run);
224
+ });
225
+
226
+ it('should throw when run not found', async () => {
227
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [] });
228
+ await expect(coordinator.startRun('nonexistent')).rejects.toThrow('not found or not in pending state');
229
+ });
230
+ });
231
+
232
+ describe('completeRun', () => {
233
+ it('should transition run to completed and emit event', async () => {
234
+ const run = makeRunRow({ status: 'completed' });
235
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [run] });
236
+
237
+ const spy = vi.fn();
238
+ coordinator.on('run:completed', spy);
239
+
240
+ const result = await coordinator.completeRun('run_test_1', { result: 'ok' });
241
+ expect(result.status).toBe('completed');
242
+ expect(spy).toHaveBeenCalledWith(run);
243
+ });
244
+
245
+ it('should throw when run not found', async () => {
246
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [] });
247
+ await expect(coordinator.completeRun('nonexistent')).rejects.toThrow('not found');
248
+ });
249
+ });
250
+
251
+ describe('failRun', () => {
252
+ it('should transition run to failed with error', async () => {
253
+ const run = makeRunRow({ status: 'failed', error: 'boom' });
254
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [run] });
255
+
256
+ const spy = vi.fn();
257
+ coordinator.on('run:failed', spy);
258
+
259
+ await coordinator.failRun('run_test_1', 'boom');
260
+ expect(spy).toHaveBeenCalledWith(run);
261
+ });
262
+ });
263
+
264
+ describe('cancelRun', () => {
265
+ it('should transition run to cancelled', async () => {
266
+ const run = makeRunRow({ status: 'cancelled' });
267
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [run] });
268
+
269
+ const spy = vi.fn();
270
+ coordinator.on('run:cancelled', spy);
271
+
272
+ const result = await coordinator.cancelRun('run_test_1');
273
+ expect(result.status).toBe('cancelled');
274
+ });
275
+ });
276
+
277
+ // ── Step management ────────────────────────────────────────────────────
278
+
279
+ describe('createSteps', () => {
280
+ it('should create steps from workflow config', async () => {
281
+ const step = makeStepRow();
282
+ vi.mocked(db.query).mockResolvedValue({ rows: [step] });
283
+
284
+ const config = makeConfig({
285
+ workflows: [
286
+ {
287
+ name: 'wf',
288
+ steps: [
289
+ { name: 's1', agent: 'worker-1', task: 'x' },
290
+ { name: 's2', agent: 'worker-2', task: 'y' },
291
+ ],
292
+ },
293
+ ],
294
+ });
295
+
296
+ const steps = await coordinator.createSteps('run_1', config);
297
+ expect(steps).toHaveLength(2);
298
+ expect(db.query).toHaveBeenCalledTimes(2);
299
+ });
300
+ });
301
+
302
+ describe('startStep', () => {
303
+ it('should transition step to running and emit event', async () => {
304
+ const step = makeStepRow({ status: 'running' });
305
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [step] });
306
+
307
+ const spy = vi.fn();
308
+ coordinator.on('step:started', spy);
309
+
310
+ const result = await coordinator.startStep('step_1');
311
+ expect(result.status).toBe('running');
312
+ expect(spy).toHaveBeenCalledWith(step);
313
+ });
314
+
315
+ it('should throw for non-pending step', async () => {
316
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [] });
317
+ await expect(coordinator.startStep('bad')).rejects.toThrow('not found or not in pending state');
318
+ });
319
+ });
320
+
321
+ describe('completeStep', () => {
322
+ it('should transition step to completed with output', async () => {
323
+ const step = makeStepRow({ status: 'completed', output: 'result data' });
324
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [step] });
325
+
326
+ const spy = vi.fn();
327
+ coordinator.on('step:completed', spy);
328
+
329
+ const result = await coordinator.completeStep('step_1', 'result data');
330
+ expect(result.output).toBe('result data');
331
+ expect(spy).toHaveBeenCalledWith(step);
332
+ });
333
+ });
334
+
335
+ describe('failStep', () => {
336
+ it('should transition step to failed with error', async () => {
337
+ const step = makeStepRow({ status: 'failed', error: 'timeout' });
338
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [step] });
339
+
340
+ const spy = vi.fn();
341
+ coordinator.on('step:failed', spy);
342
+
343
+ const result = await coordinator.failStep('step_1', 'timeout');
344
+ expect(result.error).toBe('timeout');
345
+ });
346
+ });
347
+
348
+ describe('skipStep', () => {
349
+ it('should mark step as skipped', async () => {
350
+ const step = makeStepRow({ status: 'skipped' });
351
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [step] });
352
+
353
+ const result = await coordinator.skipStep('step_1');
354
+ expect(result.status).toBe('skipped');
355
+ });
356
+ });
357
+
358
+ // ── Queries ────────────────────────────────────────────────────────────
359
+
360
+ describe('getRun', () => {
361
+ it('should return run or null', async () => {
362
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [] });
363
+ expect(await coordinator.getRun('nonexistent')).toBeNull();
364
+
365
+ const run = makeRunRow();
366
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [run] });
367
+ expect(await coordinator.getRun('run_test_1')).toEqual(run);
368
+ });
369
+ });
370
+
371
+ describe('getReadySteps', () => {
372
+ it('should return pending steps with all dependencies completed', async () => {
373
+ const steps: WorkflowStepRow[] = [
374
+ makeStepRow({ id: 's1', stepName: 'step-1', status: 'completed', dependsOn: [] }),
375
+ makeStepRow({ id: 's2', stepName: 'step-2', status: 'pending', dependsOn: ['step-1'] }),
376
+ makeStepRow({ id: 's3', stepName: 'step-3', status: 'pending', dependsOn: ['step-2'] }),
377
+ ];
378
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: steps });
379
+
380
+ const ready = await coordinator.getReadySteps('run_test_1');
381
+ expect(ready).toHaveLength(1);
382
+ expect(ready[0].stepName).toBe('step-2');
383
+ });
384
+
385
+ it('should return all pending steps with no dependencies', async () => {
386
+ const steps: WorkflowStepRow[] = [
387
+ makeStepRow({ id: 's1', stepName: 'a', status: 'pending', dependsOn: [] }),
388
+ makeStepRow({ id: 's2', stepName: 'b', status: 'pending', dependsOn: [] }),
389
+ ];
390
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: steps });
391
+
392
+ const ready = await coordinator.getReadySteps('run_test_1');
393
+ expect(ready).toHaveLength(2);
394
+ });
395
+ });
396
+
397
+ describe('getRunsByWorkspace', () => {
398
+ it('should query by workspace with optional status filter', async () => {
399
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [] });
400
+ await coordinator.getRunsByWorkspace('ws-1', 'running');
401
+ expect(db.query).toHaveBeenCalledWith(
402
+ expect.stringContaining('status = $2'),
403
+ ['ws-1', 'running'],
404
+ );
405
+ });
406
+
407
+ it('should query without status filter', async () => {
408
+ vi.mocked(db.query).mockResolvedValueOnce({ rows: [] });
409
+ await coordinator.getRunsByWorkspace('ws-1');
410
+ expect(db.query).toHaveBeenCalledWith(
411
+ expect.not.stringContaining('status ='),
412
+ ['ws-1'],
413
+ );
414
+ });
415
+ });
416
+ });