@generacy-ai/generacy 0.0.0-preview-20260304013206

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 (244) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +207 -0
  3. package/bin/generacy.js +11 -0
  4. package/dist/agency/index.d.ts +68 -0
  5. package/dist/agency/index.d.ts.map +1 -0
  6. package/dist/agency/index.js +28 -0
  7. package/dist/agency/index.js.map +1 -0
  8. package/dist/agency/network.d.ts +41 -0
  9. package/dist/agency/network.d.ts.map +1 -0
  10. package/dist/agency/network.js +133 -0
  11. package/dist/agency/network.js.map +1 -0
  12. package/dist/agency/subprocess.d.ts +58 -0
  13. package/dist/agency/subprocess.d.ts.map +1 -0
  14. package/dist/agency/subprocess.js +216 -0
  15. package/dist/agency/subprocess.js.map +1 -0
  16. package/dist/cli/commands/agent.d.ts +10 -0
  17. package/dist/cli/commands/agent.d.ts.map +1 -0
  18. package/dist/cli/commands/agent.js +216 -0
  19. package/dist/cli/commands/agent.js.map +1 -0
  20. package/dist/cli/commands/doctor/checks/agency-mcp.d.ts +3 -0
  21. package/dist/cli/commands/doctor/checks/agency-mcp.d.ts.map +1 -0
  22. package/dist/cli/commands/doctor/checks/agency-mcp.js +51 -0
  23. package/dist/cli/commands/doctor/checks/agency-mcp.js.map +1 -0
  24. package/dist/cli/commands/doctor/checks/anthropic-key.d.ts +3 -0
  25. package/dist/cli/commands/doctor/checks/anthropic-key.d.ts.map +1 -0
  26. package/dist/cli/commands/doctor/checks/anthropic-key.js +68 -0
  27. package/dist/cli/commands/doctor/checks/anthropic-key.js.map +1 -0
  28. package/dist/cli/commands/doctor/checks/config.d.ts +3 -0
  29. package/dist/cli/commands/doctor/checks/config.d.ts.map +1 -0
  30. package/dist/cli/commands/doctor/checks/config.js +81 -0
  31. package/dist/cli/commands/doctor/checks/config.js.map +1 -0
  32. package/dist/cli/commands/doctor/checks/devcontainer.d.ts +3 -0
  33. package/dist/cli/commands/doctor/checks/devcontainer.d.ts.map +1 -0
  34. package/dist/cli/commands/doctor/checks/devcontainer.js +58 -0
  35. package/dist/cli/commands/doctor/checks/devcontainer.js.map +1 -0
  36. package/dist/cli/commands/doctor/checks/docker.d.ts +3 -0
  37. package/dist/cli/commands/doctor/checks/docker.d.ts.map +1 -0
  38. package/dist/cli/commands/doctor/checks/docker.js +71 -0
  39. package/dist/cli/commands/doctor/checks/docker.js.map +1 -0
  40. package/dist/cli/commands/doctor/checks/env-file.d.ts +3 -0
  41. package/dist/cli/commands/doctor/checks/env-file.d.ts.map +1 -0
  42. package/dist/cli/commands/doctor/checks/env-file.js +56 -0
  43. package/dist/cli/commands/doctor/checks/env-file.js.map +1 -0
  44. package/dist/cli/commands/doctor/checks/github-token.d.ts +3 -0
  45. package/dist/cli/commands/doctor/checks/github-token.d.ts.map +1 -0
  46. package/dist/cli/commands/doctor/checks/github-token.js +99 -0
  47. package/dist/cli/commands/doctor/checks/github-token.js.map +1 -0
  48. package/dist/cli/commands/doctor/checks/npm-packages.d.ts +3 -0
  49. package/dist/cli/commands/doctor/checks/npm-packages.d.ts.map +1 -0
  50. package/dist/cli/commands/doctor/checks/npm-packages.js +117 -0
  51. package/dist/cli/commands/doctor/checks/npm-packages.js.map +1 -0
  52. package/dist/cli/commands/doctor/formatter.d.ts +27 -0
  53. package/dist/cli/commands/doctor/formatter.d.ts.map +1 -0
  54. package/dist/cli/commands/doctor/formatter.js +162 -0
  55. package/dist/cli/commands/doctor/formatter.js.map +1 -0
  56. package/dist/cli/commands/doctor/index.d.ts +5 -0
  57. package/dist/cli/commands/doctor/index.d.ts.map +1 -0
  58. package/dist/cli/commands/doctor/index.js +8 -0
  59. package/dist/cli/commands/doctor/index.js.map +1 -0
  60. package/dist/cli/commands/doctor/registry.d.ts +48 -0
  61. package/dist/cli/commands/doctor/registry.d.ts.map +1 -0
  62. package/dist/cli/commands/doctor/registry.js +166 -0
  63. package/dist/cli/commands/doctor/registry.js.map +1 -0
  64. package/dist/cli/commands/doctor/runner.d.ts +14 -0
  65. package/dist/cli/commands/doctor/runner.d.ts.map +1 -0
  66. package/dist/cli/commands/doctor/runner.js +257 -0
  67. package/dist/cli/commands/doctor/runner.js.map +1 -0
  68. package/dist/cli/commands/doctor/types.d.ts +87 -0
  69. package/dist/cli/commands/doctor/types.d.ts.map +1 -0
  70. package/dist/cli/commands/doctor/types.js +2 -0
  71. package/dist/cli/commands/doctor/types.js.map +1 -0
  72. package/dist/cli/commands/doctor.d.ts +12 -0
  73. package/dist/cli/commands/doctor.d.ts.map +1 -0
  74. package/dist/cli/commands/doctor.js +97 -0
  75. package/dist/cli/commands/doctor.js.map +1 -0
  76. package/dist/cli/commands/init/conflicts.d.ts +36 -0
  77. package/dist/cli/commands/init/conflicts.d.ts.map +1 -0
  78. package/dist/cli/commands/init/conflicts.js +165 -0
  79. package/dist/cli/commands/init/conflicts.js.map +1 -0
  80. package/dist/cli/commands/init/github.d.ts +32 -0
  81. package/dist/cli/commands/init/github.d.ts.map +1 -0
  82. package/dist/cli/commands/init/github.js +161 -0
  83. package/dist/cli/commands/init/github.js.map +1 -0
  84. package/dist/cli/commands/init/index.d.ts +21 -0
  85. package/dist/cli/commands/init/index.d.ts.map +1 -0
  86. package/dist/cli/commands/init/index.js +175 -0
  87. package/dist/cli/commands/init/index.js.map +1 -0
  88. package/dist/cli/commands/init/prompts.d.ts +15 -0
  89. package/dist/cli/commands/init/prompts.d.ts.map +1 -0
  90. package/dist/cli/commands/init/prompts.js +281 -0
  91. package/dist/cli/commands/init/prompts.js.map +1 -0
  92. package/dist/cli/commands/init/repo-utils.d.ts +32 -0
  93. package/dist/cli/commands/init/repo-utils.d.ts.map +1 -0
  94. package/dist/cli/commands/init/repo-utils.js +112 -0
  95. package/dist/cli/commands/init/repo-utils.js.map +1 -0
  96. package/dist/cli/commands/init/resolver.d.ts +20 -0
  97. package/dist/cli/commands/init/resolver.d.ts.map +1 -0
  98. package/dist/cli/commands/init/resolver.js +273 -0
  99. package/dist/cli/commands/init/resolver.js.map +1 -0
  100. package/dist/cli/commands/init/summary.d.ts +21 -0
  101. package/dist/cli/commands/init/summary.d.ts.map +1 -0
  102. package/dist/cli/commands/init/summary.js +100 -0
  103. package/dist/cli/commands/init/summary.js.map +1 -0
  104. package/dist/cli/commands/init/types.d.ts +53 -0
  105. package/dist/cli/commands/init/types.d.ts.map +1 -0
  106. package/dist/cli/commands/init/types.js +2 -0
  107. package/dist/cli/commands/init/types.js.map +1 -0
  108. package/dist/cli/commands/init/writer.d.ts +22 -0
  109. package/dist/cli/commands/init/writer.d.ts.map +1 -0
  110. package/dist/cli/commands/init/writer.js +96 -0
  111. package/dist/cli/commands/init/writer.js.map +1 -0
  112. package/dist/cli/commands/orchestrator.d.ts +11 -0
  113. package/dist/cli/commands/orchestrator.d.ts.map +1 -0
  114. package/dist/cli/commands/orchestrator.js +291 -0
  115. package/dist/cli/commands/orchestrator.js.map +1 -0
  116. package/dist/cli/commands/run.d.ts +10 -0
  117. package/dist/cli/commands/run.d.ts.map +1 -0
  118. package/dist/cli/commands/run.js +167 -0
  119. package/dist/cli/commands/run.js.map +1 -0
  120. package/dist/cli/commands/setup/auth.d.ts +11 -0
  121. package/dist/cli/commands/setup/auth.d.ts.map +1 -0
  122. package/dist/cli/commands/setup/auth.js +108 -0
  123. package/dist/cli/commands/setup/auth.js.map +1 -0
  124. package/dist/cli/commands/setup/build.d.ts +11 -0
  125. package/dist/cli/commands/setup/build.d.ts.map +1 -0
  126. package/dist/cli/commands/setup/build.js +212 -0
  127. package/dist/cli/commands/setup/build.js.map +1 -0
  128. package/dist/cli/commands/setup/services.d.ts +11 -0
  129. package/dist/cli/commands/setup/services.d.ts.map +1 -0
  130. package/dist/cli/commands/setup/services.js +294 -0
  131. package/dist/cli/commands/setup/services.js.map +1 -0
  132. package/dist/cli/commands/setup/workspace.d.ts +11 -0
  133. package/dist/cli/commands/setup/workspace.d.ts.map +1 -0
  134. package/dist/cli/commands/setup/workspace.js +215 -0
  135. package/dist/cli/commands/setup/workspace.js.map +1 -0
  136. package/dist/cli/commands/setup.d.ts +7 -0
  137. package/dist/cli/commands/setup.d.ts.map +1 -0
  138. package/dist/cli/commands/setup.js +19 -0
  139. package/dist/cli/commands/setup.js.map +1 -0
  140. package/dist/cli/commands/validate.d.ts +10 -0
  141. package/dist/cli/commands/validate.d.ts.map +1 -0
  142. package/dist/cli/commands/validate.js +164 -0
  143. package/dist/cli/commands/validate.js.map +1 -0
  144. package/dist/cli/commands/worker.d.ts +10 -0
  145. package/dist/cli/commands/worker.d.ts.map +1 -0
  146. package/dist/cli/commands/worker.js +224 -0
  147. package/dist/cli/commands/worker.js.map +1 -0
  148. package/dist/cli/index.d.ts +14 -0
  149. package/dist/cli/index.d.ts.map +1 -0
  150. package/dist/cli/index.js +68 -0
  151. package/dist/cli/index.js.map +1 -0
  152. package/dist/cli/utils/config.d.ts +49 -0
  153. package/dist/cli/utils/config.d.ts.map +1 -0
  154. package/dist/cli/utils/config.js +110 -0
  155. package/dist/cli/utils/config.js.map +1 -0
  156. package/dist/cli/utils/exec.d.ts +39 -0
  157. package/dist/cli/utils/exec.d.ts.map +1 -0
  158. package/dist/cli/utils/exec.js +68 -0
  159. package/dist/cli/utils/exec.js.map +1 -0
  160. package/dist/cli/utils/logger.d.ts +47 -0
  161. package/dist/cli/utils/logger.d.ts.map +1 -0
  162. package/dist/cli/utils/logger.js +97 -0
  163. package/dist/cli/utils/logger.js.map +1 -0
  164. package/dist/config/index.d.ts +10 -0
  165. package/dist/config/index.d.ts.map +1 -0
  166. package/dist/config/index.js +13 -0
  167. package/dist/config/index.js.map +1 -0
  168. package/dist/config/loader.d.ts +104 -0
  169. package/dist/config/loader.d.ts.map +1 -0
  170. package/dist/config/loader.js +266 -0
  171. package/dist/config/loader.js.map +1 -0
  172. package/dist/config/schema.d.ts +304 -0
  173. package/dist/config/schema.d.ts.map +1 -0
  174. package/dist/config/schema.js +160 -0
  175. package/dist/config/schema.js.map +1 -0
  176. package/dist/config/validator.d.ts +60 -0
  177. package/dist/config/validator.d.ts.map +1 -0
  178. package/dist/config/validator.js +112 -0
  179. package/dist/config/validator.js.map +1 -0
  180. package/dist/health/server.d.ts +47 -0
  181. package/dist/health/server.d.ts.map +1 -0
  182. package/dist/health/server.js +92 -0
  183. package/dist/health/server.js.map +1 -0
  184. package/dist/index.d.ts +21 -0
  185. package/dist/index.d.ts.map +1 -0
  186. package/dist/index.js +22 -0
  187. package/dist/index.js.map +1 -0
  188. package/dist/orchestrator/async-event-queue.d.ts +28 -0
  189. package/dist/orchestrator/async-event-queue.d.ts.map +1 -0
  190. package/dist/orchestrator/async-event-queue.js +57 -0
  191. package/dist/orchestrator/async-event-queue.js.map +1 -0
  192. package/dist/orchestrator/client.d.ts +110 -0
  193. package/dist/orchestrator/client.d.ts.map +1 -0
  194. package/dist/orchestrator/client.js +288 -0
  195. package/dist/orchestrator/client.js.map +1 -0
  196. package/dist/orchestrator/event-bus.d.ts +195 -0
  197. package/dist/orchestrator/event-bus.d.ts.map +1 -0
  198. package/dist/orchestrator/event-bus.js +557 -0
  199. package/dist/orchestrator/event-bus.js.map +1 -0
  200. package/dist/orchestrator/heartbeat.d.ts +71 -0
  201. package/dist/orchestrator/heartbeat.d.ts.map +1 -0
  202. package/dist/orchestrator/heartbeat.js +116 -0
  203. package/dist/orchestrator/heartbeat.js.map +1 -0
  204. package/dist/orchestrator/index.d.ts +25 -0
  205. package/dist/orchestrator/index.d.ts.map +1 -0
  206. package/dist/orchestrator/index.js +15 -0
  207. package/dist/orchestrator/index.js.map +1 -0
  208. package/dist/orchestrator/job-handler.d.ts +109 -0
  209. package/dist/orchestrator/job-handler.d.ts.map +1 -0
  210. package/dist/orchestrator/job-handler.js +612 -0
  211. package/dist/orchestrator/job-handler.js.map +1 -0
  212. package/dist/orchestrator/job-queue.d.ts +81 -0
  213. package/dist/orchestrator/job-queue.d.ts.map +1 -0
  214. package/dist/orchestrator/job-queue.js +206 -0
  215. package/dist/orchestrator/job-queue.js.map +1 -0
  216. package/dist/orchestrator/label-monitor-bridge.d.ts +25 -0
  217. package/dist/orchestrator/label-monitor-bridge.d.ts.map +1 -0
  218. package/dist/orchestrator/label-monitor-bridge.js +57 -0
  219. package/dist/orchestrator/label-monitor-bridge.js.map +1 -0
  220. package/dist/orchestrator/log-buffer.d.ts +74 -0
  221. package/dist/orchestrator/log-buffer.d.ts.map +1 -0
  222. package/dist/orchestrator/log-buffer.js +104 -0
  223. package/dist/orchestrator/log-buffer.js.map +1 -0
  224. package/dist/orchestrator/redis-job-queue.d.ts +44 -0
  225. package/dist/orchestrator/redis-job-queue.d.ts.map +1 -0
  226. package/dist/orchestrator/redis-job-queue.js +300 -0
  227. package/dist/orchestrator/redis-job-queue.js.map +1 -0
  228. package/dist/orchestrator/router.d.ts +125 -0
  229. package/dist/orchestrator/router.d.ts.map +1 -0
  230. package/dist/orchestrator/router.js +143 -0
  231. package/dist/orchestrator/router.js.map +1 -0
  232. package/dist/orchestrator/server.d.ts +62 -0
  233. package/dist/orchestrator/server.d.ts.map +1 -0
  234. package/dist/orchestrator/server.js +711 -0
  235. package/dist/orchestrator/server.js.map +1 -0
  236. package/dist/orchestrator/types.d.ts +184 -0
  237. package/dist/orchestrator/types.d.ts.map +1 -0
  238. package/dist/orchestrator/types.js +6 -0
  239. package/dist/orchestrator/types.js.map +1 -0
  240. package/dist/orchestrator/worker-registry.d.ts +110 -0
  241. package/dist/orchestrator/worker-registry.d.ts.map +1 -0
  242. package/dist/orchestrator/worker-registry.js +191 -0
  243. package/dist/orchestrator/worker-registry.js.map +1 -0
  244. package/package.json +80 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Redis-backed job queue implementation for the orchestrator server.
3
+ * Provides persistent job storage with priority-based ordering using Redis sorted sets.
4
+ */
5
+ import IORedis from 'ioredis';
6
+ const Redis = IORedis.default ?? IORedis;
7
+ /**
8
+ * Priority ordering: urgent > high > normal > low
9
+ * Score = priority * 1e13 + (1e13 - timestamp_ms) to get priority-first, FIFO within same priority
10
+ */
11
+ const PRIORITY_WEIGHT = {
12
+ urgent: 4,
13
+ high: 3,
14
+ normal: 2,
15
+ low: 1,
16
+ };
17
+ const KEY_PREFIX = 'orchestrator';
18
+ const JOB_KEY = (id) => `${KEY_PREFIX}:job:${id}`;
19
+ const PENDING_QUEUE = `${KEY_PREFIX}:queue:pending`;
20
+ /**
21
+ * Lua script for atomic poll operation.
22
+ * Finds the highest-priority pending job matching worker capabilities,
23
+ * removes it from the pending set, and updates its status to 'assigned'.
24
+ *
25
+ * KEYS[1] = pending queue sorted set
26
+ * ARGV[1] = worker ID
27
+ * ARGV[2] = comma-separated capabilities (empty string = match all)
28
+ * ARGV[3] = current ISO timestamp
29
+ * ARGV[4] = job key prefix
30
+ *
31
+ * Returns: job JSON string or nil
32
+ */
33
+ const POLL_SCRIPT = `
34
+ local pending_key = KEYS[1]
35
+ local worker_id = ARGV[1]
36
+ local caps_str = ARGV[2]
37
+ local now = ARGV[3]
38
+ local prefix = ARGV[4]
39
+
40
+ -- Get all pending job IDs sorted by score (highest priority first)
41
+ local members = redis.call('ZREVRANGE', pending_key, 0, -1)
42
+ if #members == 0 then
43
+ return nil
44
+ end
45
+
46
+ -- Parse capabilities
47
+ local caps = {}
48
+ local match_all = (caps_str == '' or caps_str == '*')
49
+ if not match_all then
50
+ for cap in string.gmatch(caps_str, '([^,]+)') do
51
+ caps[cap] = true
52
+ end
53
+ end
54
+
55
+ for _, job_id in ipairs(members) do
56
+ local job_key = prefix .. ':job:' .. job_id
57
+ local job_json = redis.call('GET', job_key)
58
+
59
+ if job_json then
60
+ local job = cjson.decode(job_json)
61
+
62
+ -- Verify still pending
63
+ if job.status == 'pending' then
64
+ -- Check capability match
65
+ local matched = match_all
66
+ if not matched then
67
+ local tags = job.tags
68
+ if tags == nil or #tags == 0 then
69
+ matched = true
70
+ else
71
+ for _, tag in ipairs(tags) do
72
+ if caps[tag] then
73
+ matched = true
74
+ break
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ if matched then
81
+ -- Remove from pending queue
82
+ redis.call('ZREM', pending_key, job_id)
83
+
84
+ -- Update job status
85
+ job.status = 'assigned'
86
+ job.assignedAt = now
87
+ job.workerId = worker_id
88
+
89
+ local updated_json = cjson.encode(job)
90
+ redis.call('SET', job_key, updated_json)
91
+
92
+ return updated_json
93
+ end
94
+ else
95
+ -- Stale entry, clean up
96
+ redis.call('ZREM', pending_key, job_id)
97
+ end
98
+ else
99
+ -- Job key missing, clean up
100
+ redis.call('ZREM', pending_key, job_id)
101
+ end
102
+ end
103
+
104
+ return nil
105
+ `;
106
+ /**
107
+ * Redis-backed job queue implementation.
108
+ * Jobs survive orchestrator restarts as long as Redis persists.
109
+ */
110
+ export class RedisJobQueue {
111
+ redis;
112
+ isConnected = false;
113
+ constructor(redisUrl) {
114
+ this.redis = new Redis(redisUrl, {
115
+ maxRetriesPerRequest: 3,
116
+ retryStrategy(times) {
117
+ if (times > 10)
118
+ return null;
119
+ return Math.min(times * 200, 2000);
120
+ },
121
+ lazyConnect: true,
122
+ });
123
+ this.redis.on('connect', () => {
124
+ this.isConnected = true;
125
+ });
126
+ this.redis.on('close', () => {
127
+ this.isConnected = false;
128
+ });
129
+ }
130
+ /**
131
+ * Connect to Redis. Must be called before using the queue.
132
+ * Throws if connection fails.
133
+ */
134
+ async connect() {
135
+ await this.redis.connect();
136
+ // Verify connection with a ping
137
+ await this.redis.ping();
138
+ this.isConnected = true;
139
+ }
140
+ /**
141
+ * Disconnect from Redis.
142
+ */
143
+ async close() {
144
+ if (this.isConnected) {
145
+ this.redis.disconnect();
146
+ this.isConnected = false;
147
+ }
148
+ }
149
+ /**
150
+ * Compute sorted set score: priority-first, FIFO within same priority.
151
+ */
152
+ computeScore(priority) {
153
+ const weight = PRIORITY_WEIGHT[priority];
154
+ // Use priority as the integer part, and inverse timestamp fraction for FIFO
155
+ // score = priority * 1e13 + (1e13 - Date.now())
156
+ // This ensures higher priority always wins, and within same priority, earlier jobs score higher
157
+ return weight * 1e13 + (1e13 - Date.now());
158
+ }
159
+ async enqueue(job) {
160
+ const key = JOB_KEY(job.id);
161
+ await this.redis.set(key, JSON.stringify(job));
162
+ if (job.status === 'pending') {
163
+ const score = this.computeScore(job.priority);
164
+ await this.redis.zadd(PENDING_QUEUE, score.toString(), job.id);
165
+ }
166
+ }
167
+ async poll(workerId, capabilities) {
168
+ const capsStr = capabilities.length === 0 ? '' : capabilities.join(',');
169
+ const now = new Date().toISOString();
170
+ const result = await this.redis.eval(POLL_SCRIPT, 1, PENDING_QUEUE, workerId, capsStr, now, KEY_PREFIX);
171
+ if (!result)
172
+ return null;
173
+ return JSON.parse(result);
174
+ }
175
+ async updateStatus(jobId, status, metadata) {
176
+ const key = JOB_KEY(jobId);
177
+ const raw = await this.redis.get(key);
178
+ if (!raw) {
179
+ throw new Error(`Job not found: ${jobId}`);
180
+ }
181
+ const job = JSON.parse(raw);
182
+ const now = new Date().toISOString();
183
+ job.status = status;
184
+ switch (status) {
185
+ case 'running':
186
+ job.startedAt = now;
187
+ break;
188
+ case 'completed':
189
+ case 'failed':
190
+ case 'cancelled':
191
+ job.completedAt = now;
192
+ break;
193
+ case 'assigned':
194
+ job.assignedAt = now;
195
+ break;
196
+ }
197
+ if (metadata) {
198
+ job.metadata = { ...job.metadata, ...metadata };
199
+ }
200
+ await this.redis.set(key, JSON.stringify(job));
201
+ // If job goes back to pending, re-add to sorted set
202
+ if (status === 'pending') {
203
+ const score = this.computeScore(job.priority);
204
+ await this.redis.zadd(PENDING_QUEUE, score.toString(), jobId);
205
+ }
206
+ else {
207
+ // Remove from pending if it was there
208
+ await this.redis.zrem(PENDING_QUEUE, jobId);
209
+ }
210
+ }
211
+ async reportResult(jobId, result) {
212
+ const key = JOB_KEY(jobId);
213
+ const raw = await this.redis.get(key);
214
+ if (!raw) {
215
+ throw new Error(`Job not found: ${jobId}`);
216
+ }
217
+ const job = JSON.parse(raw);
218
+ const now = new Date().toISOString();
219
+ job.status = result.status;
220
+ job.completedAt = now;
221
+ job.metadata = {
222
+ ...job.metadata,
223
+ result: {
224
+ outputs: result.outputs,
225
+ error: result.error,
226
+ errorStack: result.errorStack,
227
+ duration: result.duration,
228
+ phases: result.phases,
229
+ steps: result.steps,
230
+ },
231
+ };
232
+ await this.redis.set(key, JSON.stringify(job));
233
+ await this.redis.zrem(PENDING_QUEUE, jobId);
234
+ }
235
+ async getJob(jobId) {
236
+ const raw = await this.redis.get(JOB_KEY(jobId));
237
+ if (!raw)
238
+ return null;
239
+ return JSON.parse(raw);
240
+ }
241
+ async cancelJob(jobId, reason) {
242
+ const key = JOB_KEY(jobId);
243
+ const raw = await this.redis.get(key);
244
+ if (!raw) {
245
+ throw new Error(`Job not found: ${jobId}`);
246
+ }
247
+ const job = JSON.parse(raw);
248
+ const terminalStates = ['completed', 'failed', 'cancelled'];
249
+ if (terminalStates.includes(job.status)) {
250
+ return;
251
+ }
252
+ const now = new Date().toISOString();
253
+ job.status = 'cancelled';
254
+ job.completedAt = now;
255
+ if (reason) {
256
+ job.metadata = { ...job.metadata, cancellationReason: reason };
257
+ }
258
+ await this.redis.set(key, JSON.stringify(job));
259
+ await this.redis.zrem(PENDING_QUEUE, jobId);
260
+ }
261
+ async requeue(jobId) {
262
+ const key = JOB_KEY(jobId);
263
+ const raw = await this.redis.get(key);
264
+ if (!raw) {
265
+ throw new Error(`Job not found: ${jobId}`);
266
+ }
267
+ const job = JSON.parse(raw);
268
+ if (job.status !== 'assigned') {
269
+ throw new Error(`Cannot requeue job ${jobId}: expected status 'assigned', got '${job.status}'`);
270
+ }
271
+ // Reset to clean pending state
272
+ job.status = 'pending';
273
+ job.workerId = undefined;
274
+ job.assignedAt = undefined;
275
+ await this.redis.set(key, JSON.stringify(job));
276
+ // Re-insert into pending queue at correct priority position
277
+ const score = this.computeScore(job.priority);
278
+ await this.redis.zadd(PENDING_QUEUE, score.toString(), jobId);
279
+ }
280
+ }
281
+ /**
282
+ * Create a job queue, preferring Redis if a URL is provided.
283
+ * Falls back to InMemoryJobQueue if Redis connection fails.
284
+ */
285
+ export async function createJobQueue(redisUrl, logger) {
286
+ if (redisUrl) {
287
+ try {
288
+ const queue = new RedisJobQueue(redisUrl);
289
+ await queue.connect();
290
+ (logger?.info ?? console.log)('[JobQueue] Connected to Redis', { url: redisUrl });
291
+ return queue;
292
+ }
293
+ catch (error) {
294
+ (logger?.warn ?? console.warn)(`[JobQueue] Failed to connect to Redis (${error instanceof Error ? error.message : String(error)}), falling back to in-memory queue`);
295
+ }
296
+ }
297
+ const { InMemoryJobQueue } = await import('./job-queue.js');
298
+ return new InMemoryJobQueue();
299
+ }
300
+ //# sourceMappingURL=redis-job-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-job-queue.js","sourceRoot":"","sources":["../../src/orchestrator/redis-job-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * HTTP router utility for the orchestrator server.
3
+ * Provides simple path-matching routing without external dependencies.
4
+ */
5
+ import type { IncomingMessage, ServerResponse } from 'node:http';
6
+ /**
7
+ * Route match result containing the handler name and extracted parameters
8
+ */
9
+ export interface RouteMatch {
10
+ /** Handler name to invoke */
11
+ handler: string;
12
+ /** Extracted path parameters */
13
+ params: Record<string, string>;
14
+ }
15
+ /**
16
+ * Route definition with method, pattern, handler, and optional parameter names
17
+ */
18
+ export interface Route {
19
+ /** HTTP method (GET, POST, PUT, DELETE, etc.) */
20
+ method: string;
21
+ /** Regular expression pattern to match the path */
22
+ pattern: RegExp;
23
+ /** Handler name to invoke when matched */
24
+ handler: string;
25
+ /** Names of path parameters in order of capture groups */
26
+ paramNames?: string[];
27
+ }
28
+ /**
29
+ * Result of converting a path pattern to regex
30
+ */
31
+ export interface PathToRegexResult {
32
+ /** Regular expression for matching paths */
33
+ regex: RegExp;
34
+ /** Parameter names extracted from the pattern */
35
+ paramNames: string[];
36
+ }
37
+ /**
38
+ * Convert a path pattern like `/api/workers/:id` to a regex with parameter names.
39
+ *
40
+ * @param pattern - Path pattern with `:paramName` placeholders
41
+ * @returns Object containing regex and parameter names
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const result = pathToRegex('/api/workers/:id');
46
+ * // result.regex = /^\/api\/workers\/([^/]+)$/
47
+ * // result.paramNames = ['id']
48
+ *
49
+ * const result2 = pathToRegex('/api/jobs/:id/result');
50
+ * // result2.regex = /^\/api\/jobs\/([^/]+)\/result$/
51
+ * // result2.paramNames = ['id']
52
+ * ```
53
+ */
54
+ export declare function pathToRegex(pattern: string): PathToRegexResult;
55
+ /**
56
+ * Router function type returned by createRouter
57
+ */
58
+ export type Router = (method: string, path: string) => RouteMatch | null;
59
+ /**
60
+ * Create a router function from a list of routes.
61
+ *
62
+ * @param routes - Array of route definitions
63
+ * @returns Function that matches a method and path to a route
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const router = createRouter([
68
+ * { method: 'POST', pattern: /^\/api\/workers\/register$/, handler: 'registerWorker' },
69
+ * { method: 'DELETE', pattern: /^\/api\/workers\/([^/]+)$/, handler: 'unregisterWorker', paramNames: ['id'] },
70
+ * ]);
71
+ *
72
+ * const match = router('DELETE', '/api/workers/abc123');
73
+ * // match = { handler: 'unregisterWorker', params: { id: 'abc123' } }
74
+ * ```
75
+ */
76
+ export declare function createRouter(routes: Route[]): Router;
77
+ /**
78
+ * Parse JSON body from an incoming HTTP request.
79
+ *
80
+ * @param req - Incoming HTTP request
81
+ * @returns Parsed JSON body
82
+ * @throws Error if body is not valid JSON
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * interface RegisterRequest {
87
+ * name: string;
88
+ * capabilities: string[];
89
+ * }
90
+ *
91
+ * const body = await parseJsonBody<RegisterRequest>(req);
92
+ * console.log(body.name, body.capabilities);
93
+ * ```
94
+ */
95
+ export declare function parseJsonBody<T>(req: IncomingMessage): Promise<T>;
96
+ /**
97
+ * Send a JSON response.
98
+ *
99
+ * @param res - Server response object
100
+ * @param status - HTTP status code
101
+ * @param data - Data to serialize as JSON
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * sendJson(res, 200, { workerId: 'abc123' });
106
+ * sendJson(res, 201, { job: { id: 'job-1', status: 'pending' } });
107
+ * ```
108
+ */
109
+ export declare function sendJson(res: ServerResponse, status: number, data: unknown): void;
110
+ /**
111
+ * Send an error response in the standard orchestrator error format.
112
+ *
113
+ * @param res - Server response object
114
+ * @param status - HTTP status code
115
+ * @param code - Error code (e.g., 'WORKER_NOT_FOUND')
116
+ * @param message - Human-readable error message
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * sendError(res, 404, 'WORKER_NOT_FOUND', 'Worker with ID abc123 not found');
121
+ * sendError(res, 400, 'INVALID_REQUEST', 'Missing required field: name');
122
+ * ```
123
+ */
124
+ export declare function sendError(res: ServerResponse, status: number, code: string, message: string): void;
125
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/orchestrator/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAgB9D;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;AAEzE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAmBpD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAGjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,IAAI,CAEN"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Convert a path pattern like `/api/workers/:id` to a regex with parameter names.
3
+ *
4
+ * @param pattern - Path pattern with `:paramName` placeholders
5
+ * @returns Object containing regex and parameter names
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const result = pathToRegex('/api/workers/:id');
10
+ * // result.regex = /^\/api\/workers\/([^/]+)$/
11
+ * // result.paramNames = ['id']
12
+ *
13
+ * const result2 = pathToRegex('/api/jobs/:id/result');
14
+ * // result2.regex = /^\/api\/jobs\/([^/]+)\/result$/
15
+ * // result2.paramNames = ['id']
16
+ * ```
17
+ */
18
+ export function pathToRegex(pattern) {
19
+ const paramNames = [];
20
+ // Escape special regex characters except for colons (which mark params)
21
+ // and forward slashes (which we keep as literal)
22
+ const regexPattern = pattern
23
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
24
+ .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, paramName) => {
25
+ paramNames.push(paramName);
26
+ return '([^/]+)';
27
+ });
28
+ return {
29
+ regex: new RegExp(`^${regexPattern}$`),
30
+ paramNames,
31
+ };
32
+ }
33
+ /**
34
+ * Create a router function from a list of routes.
35
+ *
36
+ * @param routes - Array of route definitions
37
+ * @returns Function that matches a method and path to a route
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const router = createRouter([
42
+ * { method: 'POST', pattern: /^\/api\/workers\/register$/, handler: 'registerWorker' },
43
+ * { method: 'DELETE', pattern: /^\/api\/workers\/([^/]+)$/, handler: 'unregisterWorker', paramNames: ['id'] },
44
+ * ]);
45
+ *
46
+ * const match = router('DELETE', '/api/workers/abc123');
47
+ * // match = { handler: 'unregisterWorker', params: { id: 'abc123' } }
48
+ * ```
49
+ */
50
+ export function createRouter(routes) {
51
+ return (method, path) => {
52
+ for (const route of routes) {
53
+ if (method !== route.method)
54
+ continue;
55
+ const match = path.match(route.pattern);
56
+ if (match) {
57
+ const params = {};
58
+ route.paramNames?.forEach((name, i) => {
59
+ const value = match[i + 1];
60
+ if (value !== undefined) {
61
+ params[name] = value;
62
+ }
63
+ });
64
+ return { handler: route.handler, params };
65
+ }
66
+ }
67
+ return null;
68
+ };
69
+ }
70
+ /**
71
+ * Parse JSON body from an incoming HTTP request.
72
+ *
73
+ * @param req - Incoming HTTP request
74
+ * @returns Parsed JSON body
75
+ * @throws Error if body is not valid JSON
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * interface RegisterRequest {
80
+ * name: string;
81
+ * capabilities: string[];
82
+ * }
83
+ *
84
+ * const body = await parseJsonBody<RegisterRequest>(req);
85
+ * console.log(body.name, body.capabilities);
86
+ * ```
87
+ */
88
+ export async function parseJsonBody(req) {
89
+ return new Promise((resolve, reject) => {
90
+ let body = '';
91
+ req.on('data', (chunk) => {
92
+ body += chunk.toString();
93
+ });
94
+ req.on('end', () => {
95
+ try {
96
+ if (!body) {
97
+ resolve({});
98
+ return;
99
+ }
100
+ resolve(JSON.parse(body));
101
+ }
102
+ catch {
103
+ reject(new Error('Invalid JSON'));
104
+ }
105
+ });
106
+ req.on('error', reject);
107
+ });
108
+ }
109
+ /**
110
+ * Send a JSON response.
111
+ *
112
+ * @param res - Server response object
113
+ * @param status - HTTP status code
114
+ * @param data - Data to serialize as JSON
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * sendJson(res, 200, { workerId: 'abc123' });
119
+ * sendJson(res, 201, { job: { id: 'job-1', status: 'pending' } });
120
+ * ```
121
+ */
122
+ export function sendJson(res, status, data) {
123
+ res.writeHead(status, { 'Content-Type': 'application/json' });
124
+ res.end(JSON.stringify(data));
125
+ }
126
+ /**
127
+ * Send an error response in the standard orchestrator error format.
128
+ *
129
+ * @param res - Server response object
130
+ * @param status - HTTP status code
131
+ * @param code - Error code (e.g., 'WORKER_NOT_FOUND')
132
+ * @param message - Human-readable error message
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * sendError(res, 404, 'WORKER_NOT_FOUND', 'Worker with ID abc123 not found');
137
+ * sendError(res, 400, 'INVALID_REQUEST', 'Missing required field: name');
138
+ * ```
139
+ */
140
+ export function sendError(res, status, code, message) {
141
+ sendJson(res, status, { error: { code, message } });
142
+ }
143
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/orchestrator/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Orchestrator HTTP server.
3
+ * Provides REST API for worker registration, job distribution, and health monitoring.
4
+ */
5
+ import { type Server } from 'node:http';
6
+ import type { Job } from './types.js';
7
+ import { WorkerRegistry } from './worker-registry.js';
8
+ import { type JobQueue } from './job-queue.js';
9
+ import { EventBus } from './event-bus.js';
10
+ /**
11
+ * Orchestrator server options
12
+ */
13
+ export interface OrchestratorServerOptions {
14
+ /** Port to listen on (default: 3100) */
15
+ port?: number;
16
+ /** Host to bind to (default: '0.0.0.0') */
17
+ host?: string;
18
+ /** Worker heartbeat timeout in milliseconds (default: 60000) */
19
+ workerTimeout?: number;
20
+ /** Job queue instance (defaults to InMemoryJobQueue) */
21
+ jobQueue?: JobQueue;
22
+ /** Authentication token (if not set, auth is disabled) */
23
+ authToken?: string;
24
+ /** SSE event buffer size per job (default: 1000) */
25
+ eventBufferSize?: number;
26
+ /** Grace period in ms before cleaning up terminal job buffers (default: 300000) */
27
+ eventGracePeriod?: number;
28
+ /** SSE heartbeat interval in ms (default: 30000) */
29
+ sseHeartbeatInterval?: number;
30
+ /** Logger instance */
31
+ logger?: {
32
+ info: (message: string, data?: Record<string, unknown>) => void;
33
+ warn: (message: string, data?: Record<string, unknown>) => void;
34
+ error: (message: string, data?: Record<string, unknown>) => void;
35
+ };
36
+ }
37
+ /**
38
+ * Orchestrator server interface
39
+ */
40
+ export interface OrchestratorServer {
41
+ /** Start the server */
42
+ listen(): Promise<void>;
43
+ /** Stop the server */
44
+ close(): Promise<void>;
45
+ /** Get the port the server is listening on */
46
+ getPort(): number;
47
+ /** Submit a job to the queue (for programmatic use) */
48
+ submitJob(job: Omit<Job, 'id' | 'status' | 'createdAt'>): Promise<string>;
49
+ /** Get the underlying HTTP server */
50
+ getHttpServer(): Server;
51
+ /** Get the worker registry */
52
+ getWorkerRegistry(): WorkerRegistry;
53
+ /** Get the job queue */
54
+ getJobQueue(): JobQueue;
55
+ /** Get the event bus */
56
+ getEventBus(): EventBus;
57
+ }
58
+ /**
59
+ * Create an orchestrator server
60
+ */
61
+ export declare function createOrchestratorServer(options?: OrchestratorServerOptions): OrchestratorServer;
62
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/orchestrator/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAEjG,OAAO,KAAK,EACV,GAAG,EAUJ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,sBAAsB;IACtB,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAChE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAChE,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAClE,CAAC;CACH;AAiBD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,uBAAuB;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB,sBAAsB;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,8CAA8C;IAC9C,OAAO,IAAI,MAAM,CAAC;IAElB,uDAAuD;IACvD,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1E,qCAAqC;IACrC,aAAa,IAAI,MAAM,CAAC;IAExB,8BAA8B;IAC9B,iBAAiB,IAAI,cAAc,CAAC;IAEpC,wBAAwB;IACxB,WAAW,IAAI,QAAQ,CAAC;IAExB,wBAAwB;IACxB,WAAW,IAAI,QAAQ,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,yBAA8B,GAAG,kBAAkB,CAmyBpG"}