@renseiai/agentfactory-server 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/src/a2a-server.d.ts +88 -0
  4. package/dist/src/a2a-server.d.ts.map +1 -0
  5. package/dist/src/a2a-server.integration.test.d.ts +9 -0
  6. package/dist/src/a2a-server.integration.test.d.ts.map +1 -0
  7. package/dist/src/a2a-server.integration.test.js +397 -0
  8. package/dist/src/a2a-server.js +235 -0
  9. package/dist/src/a2a-server.test.d.ts +2 -0
  10. package/dist/src/a2a-server.test.d.ts.map +1 -0
  11. package/dist/src/a2a-server.test.js +311 -0
  12. package/dist/src/a2a-types.d.ts +125 -0
  13. package/dist/src/a2a-types.d.ts.map +1 -0
  14. package/dist/src/a2a-types.js +8 -0
  15. package/dist/src/agent-tracking.d.ts +201 -0
  16. package/dist/src/agent-tracking.d.ts.map +1 -0
  17. package/dist/src/agent-tracking.js +349 -0
  18. package/dist/src/env-validation.d.ts +65 -0
  19. package/dist/src/env-validation.d.ts.map +1 -0
  20. package/dist/src/env-validation.js +134 -0
  21. package/dist/src/governor-dedup.d.ts +15 -0
  22. package/dist/src/governor-dedup.d.ts.map +1 -0
  23. package/dist/src/governor-dedup.js +31 -0
  24. package/dist/src/governor-event-bus.d.ts +54 -0
  25. package/dist/src/governor-event-bus.d.ts.map +1 -0
  26. package/dist/src/governor-event-bus.js +152 -0
  27. package/dist/src/governor-storage.d.ts +28 -0
  28. package/dist/src/governor-storage.d.ts.map +1 -0
  29. package/dist/src/governor-storage.js +52 -0
  30. package/dist/src/index.d.ts +26 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +50 -0
  33. package/dist/src/issue-lock.d.ts +129 -0
  34. package/dist/src/issue-lock.d.ts.map +1 -0
  35. package/dist/src/issue-lock.js +508 -0
  36. package/dist/src/logger.d.ts +76 -0
  37. package/dist/src/logger.d.ts.map +1 -0
  38. package/dist/src/logger.js +218 -0
  39. package/dist/src/orphan-cleanup.d.ts +64 -0
  40. package/dist/src/orphan-cleanup.d.ts.map +1 -0
  41. package/dist/src/orphan-cleanup.js +369 -0
  42. package/dist/src/pending-prompts.d.ts +67 -0
  43. package/dist/src/pending-prompts.d.ts.map +1 -0
  44. package/dist/src/pending-prompts.js +176 -0
  45. package/dist/src/processing-state-storage.d.ts +38 -0
  46. package/dist/src/processing-state-storage.d.ts.map +1 -0
  47. package/dist/src/processing-state-storage.js +61 -0
  48. package/dist/src/quota-tracker.d.ts +62 -0
  49. package/dist/src/quota-tracker.d.ts.map +1 -0
  50. package/dist/src/quota-tracker.js +155 -0
  51. package/dist/src/rate-limit.d.ts +111 -0
  52. package/dist/src/rate-limit.d.ts.map +1 -0
  53. package/dist/src/rate-limit.js +171 -0
  54. package/dist/src/redis-circuit-breaker.d.ts +67 -0
  55. package/dist/src/redis-circuit-breaker.d.ts.map +1 -0
  56. package/dist/src/redis-circuit-breaker.js +290 -0
  57. package/dist/src/redis-rate-limiter.d.ts +51 -0
  58. package/dist/src/redis-rate-limiter.d.ts.map +1 -0
  59. package/dist/src/redis-rate-limiter.js +168 -0
  60. package/dist/src/redis.d.ts +146 -0
  61. package/dist/src/redis.d.ts.map +1 -0
  62. package/dist/src/redis.js +343 -0
  63. package/dist/src/session-hash.d.ts +48 -0
  64. package/dist/src/session-hash.d.ts.map +1 -0
  65. package/dist/src/session-hash.js +80 -0
  66. package/dist/src/session-storage.d.ts +166 -0
  67. package/dist/src/session-storage.d.ts.map +1 -0
  68. package/dist/src/session-storage.js +397 -0
  69. package/dist/src/token-storage.d.ts +118 -0
  70. package/dist/src/token-storage.d.ts.map +1 -0
  71. package/dist/src/token-storage.js +263 -0
  72. package/dist/src/types.d.ts +11 -0
  73. package/dist/src/types.d.ts.map +1 -0
  74. package/dist/src/types.js +7 -0
  75. package/dist/src/webhook-idempotency.d.ts +44 -0
  76. package/dist/src/webhook-idempotency.d.ts.map +1 -0
  77. package/dist/src/webhook-idempotency.js +148 -0
  78. package/dist/src/work-queue.d.ts +120 -0
  79. package/dist/src/work-queue.d.ts.map +1 -0
  80. package/dist/src/work-queue.js +384 -0
  81. package/dist/src/worker-auth.d.ts +29 -0
  82. package/dist/src/worker-auth.d.ts.map +1 -0
  83. package/dist/src/worker-auth.js +49 -0
  84. package/dist/src/worker-storage.d.ts +108 -0
  85. package/dist/src/worker-storage.d.ts.map +1 -0
  86. package/dist/src/worker-storage.js +295 -0
  87. package/dist/src/workflow-state-integration.test.d.ts +2 -0
  88. package/dist/src/workflow-state-integration.test.d.ts.map +1 -0
  89. package/dist/src/workflow-state-integration.test.js +342 -0
  90. package/dist/src/workflow-state.test.d.ts +2 -0
  91. package/dist/src/workflow-state.test.d.ts.map +1 -0
  92. package/dist/src/workflow-state.test.js +113 -0
  93. package/package.json +72 -0
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Work Queue Module (Optimized)
3
+ *
4
+ * Manages the queue of pending agent work items in Redis.
5
+ * Workers poll this queue to claim and process work.
6
+ *
7
+ * Data Structures (optimized for high concurrency):
8
+ * - work:items (Hash): sessionId -> JSON work item - O(1) lookup
9
+ * - work:queue (Sorted Set): score = priority, member = sessionId - O(log n) operations
10
+ * - work:claim:{sessionId} (String): workerId with TTL - atomic claims
11
+ *
12
+ * Performance:
13
+ * - queueWork: O(log n) - HSET + ZADD
14
+ * - claimWork: O(log n) - SETNX + HGET + ZREM
15
+ * - peekWork: O(log n + k) - ZRANGEBYSCORE + HMGET where k = limit
16
+ * - getQueueLength: O(1) - ZCARD
17
+ */
18
+ import { redisSetNX, redisDel, redisGet, redisZAdd, redisZRem, redisZRangeByScore, redisZCard, redisHSet, redisHGet, redisHDel, redisHMGet, isRedisConfigured,
19
+ // Legacy list operations for migration
20
+ redisLRange, redisLRem, } from './redis.js';
21
+ const log = {
22
+ info: (msg, data) => console.log(`[work-queue] ${msg}`, data ? JSON.stringify(data) : ''),
23
+ warn: (msg, data) => console.warn(`[work-queue] ${msg}`, data ? JSON.stringify(data) : ''),
24
+ error: (msg, data) => console.error(`[work-queue] ${msg}`, data ? JSON.stringify(data) : ''),
25
+ debug: (_msg, _data) => { },
26
+ };
27
+ // Redis key constants
28
+ const WORK_QUEUE_KEY = 'work:queue'; // Sorted set: priority queue
29
+ const WORK_ITEMS_KEY = 'work:items'; // Hash: sessionId -> work item
30
+ const WORK_CLAIM_PREFIX = 'work:claim:';
31
+ // Legacy key for migration
32
+ const LEGACY_QUEUE_KEY = 'work:queue:legacy';
33
+ // Default TTL for work claims (1 hour)
34
+ const WORK_CLAIM_TTL = parseInt(process.env.WORK_CLAIM_TTL ?? '3600', 10);
35
+ /**
36
+ * Calculate priority score for sorted set
37
+ * Lower scores = higher priority (processed first)
38
+ * Score = (priority * 1e13) + timestamp
39
+ * This ensures priority is the primary sort key, timestamp is secondary
40
+ */
41
+ function calculateScore(priority, queuedAt) {
42
+ // Clamp priority to 1-9 to ensure score calculation works correctly
43
+ const clampedPriority = Math.max(1, Math.min(9, priority));
44
+ // Use 1e13 multiplier to leave room for timestamps up to year ~2286
45
+ return clampedPriority * 1e13 + queuedAt;
46
+ }
47
+ /**
48
+ * Add work to the queue
49
+ *
50
+ * @param work - Work item to queue
51
+ * @returns true if queued successfully
52
+ */
53
+ export async function queueWork(work) {
54
+ if (!isRedisConfigured()) {
55
+ log.warn('Redis not configured, cannot queue work');
56
+ return false;
57
+ }
58
+ try {
59
+ const score = calculateScore(work.priority, work.queuedAt);
60
+ const serialized = JSON.stringify(work);
61
+ // Store work item in hash (O(1) lookup)
62
+ await redisHSet(WORK_ITEMS_KEY, work.sessionId, serialized);
63
+ // Add to priority queue (O(log n))
64
+ await redisZAdd(WORK_QUEUE_KEY, score, work.sessionId);
65
+ log.info('Work queued', {
66
+ sessionId: work.sessionId,
67
+ issueIdentifier: work.issueIdentifier,
68
+ priority: work.priority,
69
+ score,
70
+ });
71
+ return true;
72
+ }
73
+ catch (error) {
74
+ log.error('Failed to queue work', { error, sessionId: work.sessionId });
75
+ return false;
76
+ }
77
+ }
78
+ /**
79
+ * Peek at pending work without removing from queue
80
+ * Returns items sorted by priority (lowest number = highest priority)
81
+ *
82
+ * @param limit - Maximum number of items to return
83
+ * @returns Array of work items sorted by priority
84
+ */
85
+ export async function peekWork(limit = 10) {
86
+ if (!isRedisConfigured()) {
87
+ return [];
88
+ }
89
+ try {
90
+ // Get session IDs from priority queue (lowest scores first)
91
+ const sessionIds = await redisZRangeByScore(WORK_QUEUE_KEY, '-inf', '+inf', limit);
92
+ if (sessionIds.length === 0) {
93
+ return [];
94
+ }
95
+ // Batch fetch work items from hash
96
+ const items = await redisHMGet(WORK_ITEMS_KEY, sessionIds);
97
+ // Parse and filter out any missing items
98
+ const result = [];
99
+ for (let i = 0; i < items.length; i++) {
100
+ const item = items[i];
101
+ if (item) {
102
+ try {
103
+ result.push(JSON.parse(item));
104
+ }
105
+ catch {
106
+ log.warn('Failed to parse work item', { sessionId: sessionIds[i] });
107
+ }
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ catch (error) {
113
+ log.error('Failed to peek work queue', { error });
114
+ return [];
115
+ }
116
+ }
117
+ /**
118
+ * Get the number of items in the queue
119
+ */
120
+ export async function getQueueLength() {
121
+ if (!isRedisConfigured()) {
122
+ return 0;
123
+ }
124
+ try {
125
+ return await redisZCard(WORK_QUEUE_KEY);
126
+ }
127
+ catch (error) {
128
+ log.error('Failed to get queue length', { error });
129
+ return 0;
130
+ }
131
+ }
132
+ /**
133
+ * Claim a work item for processing
134
+ *
135
+ * Uses SETNX for atomic claim to prevent race conditions.
136
+ * O(log n) complexity for claim + remove operations.
137
+ *
138
+ * @param sessionId - Session ID to claim
139
+ * @param workerId - Worker claiming the work
140
+ * @returns The work item if claimed successfully, null otherwise
141
+ */
142
+ export async function claimWork(sessionId, workerId) {
143
+ if (!isRedisConfigured()) {
144
+ log.warn('Redis not configured, cannot claim work');
145
+ return null;
146
+ }
147
+ const claimKey = `${WORK_CLAIM_PREFIX}${sessionId}`;
148
+ let claimAcquired = false;
149
+ try {
150
+ // Try to atomically set the claim
151
+ const claimed = await redisSetNX(claimKey, workerId, WORK_CLAIM_TTL);
152
+ if (!claimed) {
153
+ log.debug('Work already claimed', { sessionId, workerId });
154
+ return null;
155
+ }
156
+ claimAcquired = true;
157
+ // Get work item from hash (O(1))
158
+ const itemJson = await redisHGet(WORK_ITEMS_KEY, sessionId);
159
+ if (!itemJson) {
160
+ // Work item not found - release the claim
161
+ await redisDel(claimKey);
162
+ claimAcquired = false;
163
+ log.warn('Work item not found in hash after claim', { sessionId });
164
+ return null;
165
+ }
166
+ const work = JSON.parse(itemJson);
167
+ // Remove from priority queue (O(log n))
168
+ await redisZRem(WORK_QUEUE_KEY, sessionId);
169
+ // Remove from items hash (O(1))
170
+ await redisHDel(WORK_ITEMS_KEY, sessionId);
171
+ log.info('Work claimed', {
172
+ sessionId,
173
+ workerId,
174
+ issueIdentifier: work.issueIdentifier,
175
+ });
176
+ return work;
177
+ }
178
+ catch (error) {
179
+ // Release the claim key if we acquired it to prevent deadlock.
180
+ // Without this, a transient Redis error after SETNX would leave the
181
+ // claim key stuck for its full TTL (1 hour), blocking all workers
182
+ // from claiming this work item while it remains in the queue.
183
+ if (claimAcquired) {
184
+ try {
185
+ await redisDel(claimKey);
186
+ }
187
+ catch (cleanupError) {
188
+ log.error('Failed to release claim during error cleanup', {
189
+ cleanupError,
190
+ sessionId,
191
+ });
192
+ }
193
+ }
194
+ log.error('Failed to claim work', { error, sessionId, workerId });
195
+ return null;
196
+ }
197
+ }
198
+ /**
199
+ * Release a work claim (e.g., on failure or cancellation)
200
+ *
201
+ * @param sessionId - Session ID to release
202
+ * @returns true if released successfully
203
+ */
204
+ export async function releaseClaim(sessionId) {
205
+ if (!isRedisConfigured()) {
206
+ return false;
207
+ }
208
+ try {
209
+ const claimKey = `${WORK_CLAIM_PREFIX}${sessionId}`;
210
+ const deleted = await redisDel(claimKey);
211
+ return deleted > 0;
212
+ }
213
+ catch (error) {
214
+ log.error('Failed to release claim', { error, sessionId });
215
+ return false;
216
+ }
217
+ }
218
+ /**
219
+ * Check which worker has claimed a session
220
+ *
221
+ * @param sessionId - Session ID to check
222
+ * @returns Worker ID if claimed, null otherwise
223
+ */
224
+ export async function getClaimOwner(sessionId) {
225
+ if (!isRedisConfigured()) {
226
+ return null;
227
+ }
228
+ try {
229
+ const claimKey = `${WORK_CLAIM_PREFIX}${sessionId}`;
230
+ return await redisGet(claimKey);
231
+ }
232
+ catch (error) {
233
+ log.error('Failed to get claim owner', { error, sessionId });
234
+ return null;
235
+ }
236
+ }
237
+ /**
238
+ * Check if a session has an entry in the work queue.
239
+ * O(1) check via the work items hash.
240
+ *
241
+ * @param sessionId - Session ID to check
242
+ * @returns true if the session is present in the work queue
243
+ */
244
+ export async function isSessionInQueue(sessionId) {
245
+ if (!isRedisConfigured()) {
246
+ return false;
247
+ }
248
+ try {
249
+ const item = await redisHGet(WORK_ITEMS_KEY, sessionId);
250
+ return item !== null;
251
+ }
252
+ catch (error) {
253
+ log.error('Failed to check if session is in queue', { error, sessionId });
254
+ return false;
255
+ }
256
+ }
257
+ /**
258
+ * Re-queue work that failed or was abandoned
259
+ *
260
+ * @param work - Work item to re-queue
261
+ * @param priorityBoost - Decrease priority number (higher priority) by this amount
262
+ * @returns true if re-queued successfully
263
+ */
264
+ export async function requeueWork(work, priorityBoost = 1) {
265
+ if (!isRedisConfigured()) {
266
+ return false;
267
+ }
268
+ try {
269
+ // Release any existing claim
270
+ await releaseClaim(work.sessionId);
271
+ // Boost priority (lower number = higher priority)
272
+ const newPriority = Math.max(1, work.priority - priorityBoost);
273
+ // Re-queue with updated priority and timestamp
274
+ const updatedWork = {
275
+ ...work,
276
+ priority: newPriority,
277
+ queuedAt: Date.now(),
278
+ };
279
+ return await queueWork(updatedWork);
280
+ }
281
+ catch (error) {
282
+ log.error('Failed to requeue work', { error, sessionId: work.sessionId });
283
+ return false;
284
+ }
285
+ }
286
+ /**
287
+ * Get all pending work items (for dashboard/monitoring)
288
+ * Returns items sorted by priority
289
+ */
290
+ export async function getAllPendingWork() {
291
+ if (!isRedisConfigured()) {
292
+ return [];
293
+ }
294
+ try {
295
+ // Get all session IDs from priority queue
296
+ const sessionIds = await redisZRangeByScore(WORK_QUEUE_KEY, '-inf', '+inf');
297
+ if (sessionIds.length === 0) {
298
+ return [];
299
+ }
300
+ // Batch fetch all work items
301
+ const items = await redisHMGet(WORK_ITEMS_KEY, sessionIds);
302
+ const result = [];
303
+ for (const item of items) {
304
+ if (item) {
305
+ try {
306
+ result.push(JSON.parse(item));
307
+ }
308
+ catch {
309
+ // Skip invalid items
310
+ }
311
+ }
312
+ }
313
+ return result;
314
+ }
315
+ catch (error) {
316
+ log.error('Failed to get all pending work', { error });
317
+ return [];
318
+ }
319
+ }
320
+ /**
321
+ * Remove a work item from queue (without claiming)
322
+ * Used for cleanup operations
323
+ *
324
+ * @param sessionId - Session ID to remove
325
+ * @returns true if removed
326
+ */
327
+ export async function removeFromQueue(sessionId) {
328
+ if (!isRedisConfigured()) {
329
+ return false;
330
+ }
331
+ try {
332
+ // Remove from both data structures
333
+ await redisZRem(WORK_QUEUE_KEY, sessionId);
334
+ await redisHDel(WORK_ITEMS_KEY, sessionId);
335
+ return true;
336
+ }
337
+ catch (error) {
338
+ log.error('Failed to remove from queue', { error, sessionId });
339
+ return false;
340
+ }
341
+ }
342
+ /**
343
+ * Migrate data from legacy list-based queue to new sorted set/hash structure
344
+ * Run this once after deployment to migrate existing data
345
+ */
346
+ export async function migrateFromLegacyQueue() {
347
+ if (!isRedisConfigured()) {
348
+ return { migrated: 0, failed: 0 };
349
+ }
350
+ let migrated = 0;
351
+ let failed = 0;
352
+ try {
353
+ // Check if there's data in the legacy queue (same key, but was a list)
354
+ // Try to read as list first
355
+ const legacyItems = await redisLRange(WORK_QUEUE_KEY, 0, -1);
356
+ if (legacyItems.length === 0) {
357
+ log.info('No legacy queue data to migrate');
358
+ return { migrated: 0, failed: 0 };
359
+ }
360
+ log.info('Migrating legacy queue data', { itemCount: legacyItems.length });
361
+ for (const itemJson of legacyItems) {
362
+ try {
363
+ const work = JSON.parse(itemJson);
364
+ // Add to new data structures
365
+ const score = calculateScore(work.priority, work.queuedAt);
366
+ await redisHSet(WORK_ITEMS_KEY, work.sessionId, itemJson);
367
+ await redisZAdd(WORK_QUEUE_KEY, score, work.sessionId);
368
+ // Remove from legacy list
369
+ await redisLRem(WORK_QUEUE_KEY, 1, itemJson);
370
+ migrated++;
371
+ }
372
+ catch (err) {
373
+ log.warn('Failed to migrate work item', { error: err, itemJson });
374
+ failed++;
375
+ }
376
+ }
377
+ log.info('Legacy queue migration complete', { migrated, failed });
378
+ }
379
+ catch (error) {
380
+ // This might fail if the key doesn't exist as a list (already migrated)
381
+ log.debug('No legacy queue to migrate or already migrated', { error });
382
+ }
383
+ return { migrated, failed };
384
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Worker Authentication Module
3
+ *
4
+ * Framework-agnostic API key verification for worker endpoints.
5
+ * Workers must include a valid API key in the Authorization header.
6
+ */
7
+ /**
8
+ * Extract a Bearer token from an Authorization header value
9
+ *
10
+ * @param authHeader - The Authorization header value
11
+ * @returns The bearer token or null if not a valid Bearer header
12
+ */
13
+ export declare function extractBearerToken(authHeader: string | null | undefined): string | null;
14
+ /**
15
+ * Verify an API key against the expected key using timing-safe comparison
16
+ *
17
+ * @param providedKey - The API key from the request
18
+ * @param expectedKey - The expected API key (defaults to WORKER_API_KEY env var)
19
+ * @returns true if the key is valid
20
+ */
21
+ export declare function verifyApiKey(providedKey: string, expectedKey?: string): boolean;
22
+ /**
23
+ * Check if worker auth is configured
24
+ * (useful for development/testing where auth might be disabled)
25
+ *
26
+ * @param envVar - Environment variable name to check (default: WORKER_API_KEY)
27
+ */
28
+ export declare function isWorkerAuthConfigured(envVar?: string): boolean;
29
+ //# sourceMappingURL=worker-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-auth.d.ts","sourceRoot":"","sources":["../../src/worker-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAKvF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAiBT;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,SAAmB,GAAG,OAAO,CAEzE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Worker Authentication Module
3
+ *
4
+ * Framework-agnostic API key verification for worker endpoints.
5
+ * Workers must include a valid API key in the Authorization header.
6
+ */
7
+ import crypto from 'crypto';
8
+ /**
9
+ * Extract a Bearer token from an Authorization header value
10
+ *
11
+ * @param authHeader - The Authorization header value
12
+ * @returns The bearer token or null if not a valid Bearer header
13
+ */
14
+ export function extractBearerToken(authHeader) {
15
+ if (!authHeader?.startsWith('Bearer ')) {
16
+ return null;
17
+ }
18
+ return authHeader.slice(7);
19
+ }
20
+ /**
21
+ * Verify an API key against the expected key using timing-safe comparison
22
+ *
23
+ * @param providedKey - The API key from the request
24
+ * @param expectedKey - The expected API key (defaults to WORKER_API_KEY env var)
25
+ * @returns true if the key is valid
26
+ */
27
+ export function verifyApiKey(providedKey, expectedKey) {
28
+ const expected = expectedKey ?? process.env.WORKER_API_KEY;
29
+ if (!expected) {
30
+ return false;
31
+ }
32
+ // Use timing-safe comparison to prevent timing attacks
33
+ try {
34
+ return crypto.timingSafeEqual(Buffer.from(providedKey), Buffer.from(expected));
35
+ }
36
+ catch {
37
+ // Buffers have different lengths
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * Check if worker auth is configured
43
+ * (useful for development/testing where auth might be disabled)
44
+ *
45
+ * @param envVar - Environment variable name to check (default: WORKER_API_KEY)
46
+ */
47
+ export function isWorkerAuthConfigured(envVar = 'WORKER_API_KEY') {
48
+ return !!process.env[envVar];
49
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Worker Storage Module
3
+ *
4
+ * Manages worker registration and tracking in Redis.
5
+ * Workers register on startup, send periodic heartbeats,
6
+ * and deregister on shutdown.
7
+ */
8
+ /**
9
+ * Worker registration data stored in Redis
10
+ */
11
+ export interface WorkerData {
12
+ id: string;
13
+ hostname: string;
14
+ capacity: number;
15
+ activeCount: number;
16
+ registeredAt: number;
17
+ lastHeartbeat: number;
18
+ status: 'active' | 'draining' | 'offline';
19
+ version?: string;
20
+ projects?: string[];
21
+ }
22
+ /**
23
+ * Worker info returned to API consumers
24
+ */
25
+ export interface WorkerInfo extends WorkerData {
26
+ activeSessions: string[];
27
+ }
28
+ /**
29
+ * Register a new worker
30
+ *
31
+ * @param hostname - Worker's hostname
32
+ * @param capacity - Maximum concurrent agents the worker can handle
33
+ * @param version - Optional worker software version
34
+ * @returns Worker ID and configuration
35
+ */
36
+ export declare function registerWorker(hostname: string, capacity: number, version?: string, projects?: string[]): Promise<{
37
+ workerId: string;
38
+ heartbeatInterval: number;
39
+ pollInterval: number;
40
+ } | null>;
41
+ /**
42
+ * Update worker heartbeat
43
+ *
44
+ * @param workerId - Worker ID
45
+ * @param activeCount - Current number of active agents
46
+ * @param load - Optional system load metrics
47
+ * @returns Heartbeat acknowledgment or null on failure
48
+ */
49
+ export declare function updateHeartbeat(workerId: string, activeCount: number, load?: {
50
+ cpu: number;
51
+ memory: number;
52
+ }): Promise<{
53
+ acknowledged: boolean;
54
+ serverTime: string;
55
+ pendingWorkCount: number;
56
+ } | null>;
57
+ /**
58
+ * Get worker by ID
59
+ */
60
+ export declare function getWorker(workerId: string): Promise<WorkerInfo | null>;
61
+ /**
62
+ * Deregister a worker
63
+ *
64
+ * @param workerId - Worker ID to deregister
65
+ * @returns List of session IDs that need to be re-queued
66
+ */
67
+ export declare function deregisterWorker(workerId: string): Promise<{
68
+ deregistered: boolean;
69
+ unclaimedSessions: string[];
70
+ }>;
71
+ /**
72
+ * List all registered workers
73
+ */
74
+ export declare function listWorkers(): Promise<WorkerInfo[]>;
75
+ /**
76
+ * Get workers that have missed heartbeats (stale workers)
77
+ */
78
+ export declare function getStaleWorkers(): Promise<WorkerInfo[]>;
79
+ /**
80
+ * Add a session to a worker's active sessions
81
+ *
82
+ * @param workerId - Worker ID
83
+ * @param sessionId - Session ID being processed
84
+ */
85
+ export declare function addWorkerSession(workerId: string, sessionId: string): Promise<boolean>;
86
+ /**
87
+ * Remove a session from a worker's active sessions
88
+ *
89
+ * @param workerId - Worker ID
90
+ * @param sessionId - Session ID to remove
91
+ */
92
+ export declare function removeWorkerSession(workerId: string, sessionId: string): Promise<boolean>;
93
+ /**
94
+ * Get total capacity across all active workers.
95
+ *
96
+ * Accepts an optional pre-fetched workers list to avoid redundant Redis scans
97
+ * (callers like the stats handler already call listWorkers()).
98
+ *
99
+ * Uses activeSessions.length (authoritative Redis set) instead of the
100
+ * heartbeat-reported activeCount, which can be stale after re-registration
101
+ * or between heartbeat intervals.
102
+ */
103
+ export declare function getTotalCapacity(prefetchedWorkers?: WorkerInfo[]): Promise<{
104
+ totalCapacity: number;
105
+ totalActive: number;
106
+ availableCapacity: number;
107
+ }>;
108
+ //# sourceMappingURL=worker-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-storage.d.ts","sourceRoot":"","sources":["../../src/worker-storage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8CH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAA;IACzC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAyCvF;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrC,OAAO,CAAC;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsCzF;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyB5E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA6BjE;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAoCzD;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAY7D;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,iBAAiB,CAAC,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAChF,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;CAC1B,CAAC,CAqBD"}