@cogitator-ai/worker 0.3.20 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -55
- package/dist/connection.d.ts +29 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +63 -0
- package/dist/connection.js.map +1 -0
- package/dist/distributed-swarm-worker.d.ts +56 -0
- package/dist/distributed-swarm-worker.d.ts.map +1 -0
- package/dist/distributed-swarm-worker.js +151 -0
- package/dist/distributed-swarm-worker.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +22 -15
- package/dist/metrics.js.map +1 -1
- package/dist/processors/agent.d.ts +12 -2
- package/dist/processors/agent.d.ts.map +1 -1
- package/dist/processors/agent.js +25 -23
- package/dist/processors/agent.js.map +1 -1
- package/dist/processors/index.d.ts +3 -3
- package/dist/processors/index.d.ts.map +1 -1
- package/dist/processors/index.js +3 -3
- package/dist/processors/index.js.map +1 -1
- package/dist/processors/shared.d.ts +26 -3
- package/dist/processors/shared.d.ts.map +1 -1
- package/dist/processors/shared.js +38 -25
- package/dist/processors/shared.js.map +1 -1
- package/dist/processors/swarm-agent.d.ts +25 -4
- package/dist/processors/swarm-agent.d.ts.map +1 -1
- package/dist/processors/swarm-agent.js +33 -47
- package/dist/processors/swarm-agent.js.map +1 -1
- package/dist/processors/swarm.d.ts +7 -2
- package/dist/processors/swarm.d.ts.map +1 -1
- package/dist/processors/swarm.js +75 -50
- package/dist/processors/swarm.js.map +1 -1
- package/dist/processors/workflow.d.ts +21 -5
- package/dist/processors/workflow.d.ts.map +1 -1
- package/dist/processors/workflow.js +304 -8
- package/dist/processors/workflow.js.map +1 -1
- package/dist/queue.d.ts +6 -5
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +16 -19
- package/dist/queue.js.map +1 -1
- package/dist/types.d.ts +58 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +10 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +62 -24
- package/dist/worker.js.map +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -5,13 +5,15 @@ Distributed job queue for Cogitator agent execution. Built on BullMQ for reliabl
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm add @cogitator-ai/worker ioredis
|
|
8
|
+
pnpm add @cogitator-ai/worker @cogitator-ai/core ioredis
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **BullMQ-Based** - Reliable job processing with Redis
|
|
14
|
-
- **Job Types** - Agents,
|
|
14
|
+
- **Job Types** - Agents, workflow graphs, swarms and distributed swarm turns
|
|
15
|
+
- **Worker Runtime** - Run jobs with your own `Cogitator` (provider keys, memory) and tool implementations
|
|
16
|
+
- **Distributed Swarms** - `DistributedSwarmWorker` executes agent turns for `@cogitator-ai/swarms`
|
|
15
17
|
- **Auto-Retry** - Exponential backoff for failed jobs
|
|
16
18
|
- **Priority Queue** - Process important jobs first
|
|
17
19
|
- **Delayed Jobs** - Schedule jobs for later execution
|
|
@@ -35,7 +37,7 @@ const queue = new JobQueue({
|
|
|
35
37
|
const agentConfig = {
|
|
36
38
|
name: 'Assistant',
|
|
37
39
|
instructions: 'You are a helpful assistant.',
|
|
38
|
-
model: 'openai/gpt-
|
|
40
|
+
model: 'openai/gpt-4o',
|
|
39
41
|
provider: 'openai' as const,
|
|
40
42
|
tools: [],
|
|
41
43
|
};
|
|
@@ -51,17 +53,24 @@ console.log(`Job added: ${job.id}`);
|
|
|
51
53
|
### Consumer: Process Jobs
|
|
52
54
|
|
|
53
55
|
```typescript
|
|
56
|
+
import { Cogitator } from '@cogitator-ai/core';
|
|
54
57
|
import { WorkerPool } from '@cogitator-ai/worker';
|
|
55
58
|
|
|
56
59
|
const pool = new WorkerPool({
|
|
57
60
|
redis: { host: 'localhost', port: 6379 },
|
|
58
61
|
concurrency: 5,
|
|
59
62
|
workerCount: 2,
|
|
63
|
+
cogitator: new Cogitator({
|
|
64
|
+
llm: { providers: { openai: { apiKey: process.env.OPENAI_API_KEY } } },
|
|
65
|
+
}),
|
|
66
|
+
tools: [searchTool], // implementations for tools referenced by serialized agents
|
|
60
67
|
});
|
|
61
68
|
|
|
62
69
|
await pool.start();
|
|
63
70
|
```
|
|
64
71
|
|
|
72
|
+
Serialized agents reference tools by name. The worker resolves them from its `tools`; a job whose agent needs a tool the worker does not provide fails with `Tools not registered on this worker: <names>`.
|
|
73
|
+
|
|
65
74
|
---
|
|
66
75
|
|
|
67
76
|
## Job Queue
|
|
@@ -122,10 +131,11 @@ interface QueueConfig {
|
|
|
122
131
|
const agentConfig: SerializedAgent = {
|
|
123
132
|
name: 'Researcher',
|
|
124
133
|
instructions: 'Research and summarize topics.',
|
|
125
|
-
model: 'openai/gpt-
|
|
134
|
+
model: 'openai/gpt-4o', // or 'gpt-4o' — the provider is prepended when missing
|
|
126
135
|
provider: 'openai',
|
|
127
136
|
temperature: 0.7,
|
|
128
137
|
maxTokens: 2048,
|
|
138
|
+
maxIterations: 5,
|
|
129
139
|
tools: [
|
|
130
140
|
{
|
|
131
141
|
name: 'search',
|
|
@@ -145,38 +155,60 @@ const job = await queue.addAgentJob(agentConfig, 'Research quantum computing', {
|
|
|
145
155
|
|
|
146
156
|
**Workflow Jobs:**
|
|
147
157
|
|
|
158
|
+
Workflow jobs run a DAG over a shared state object initialised from the job input. Nodes run as soon as their predecessors settle; independent branches run concurrently.
|
|
159
|
+
|
|
148
160
|
```typescript
|
|
149
161
|
const workflowConfig: SerializedWorkflow = {
|
|
150
|
-
id: '
|
|
151
|
-
name: '
|
|
162
|
+
id: 'triage',
|
|
163
|
+
name: 'Ticket triage',
|
|
152
164
|
nodes: [
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
{
|
|
166
|
+
id: 'classify',
|
|
167
|
+
type: 'agent',
|
|
168
|
+
config: {
|
|
169
|
+
agentConfig: classifierAgent, // SerializedAgent
|
|
170
|
+
prompt: 'Classify this ticket as BUG or QUESTION: {{ticket}}',
|
|
171
|
+
outputKey: 'category',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{ id: 'normalize', type: 'transform', config: { transform: 'trim', inputKey: 'category' } },
|
|
175
|
+
{
|
|
176
|
+
id: 'is-bug',
|
|
177
|
+
type: 'condition',
|
|
178
|
+
config: { key: 'normalize', operator: 'contains', value: 'BUG' },
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'summary',
|
|
182
|
+
type: 'transform',
|
|
183
|
+
config: { transform: 'template', template: 'Bug report: {{ticket}}' },
|
|
184
|
+
},
|
|
156
185
|
],
|
|
157
186
|
edges: [
|
|
158
|
-
{ from: '
|
|
159
|
-
{ from: '
|
|
187
|
+
{ from: 'classify', to: 'normalize' },
|
|
188
|
+
{ from: 'normalize', to: 'is-bug' },
|
|
189
|
+
{ from: 'is-bug', to: 'summary', condition: 'true' },
|
|
160
190
|
],
|
|
161
191
|
};
|
|
162
192
|
|
|
163
|
-
await queue.addWorkflowJob(
|
|
164
|
-
workflowConfig,
|
|
165
|
-
{ source: 'api' },
|
|
166
|
-
{
|
|
167
|
-
runId: 'run-789',
|
|
168
|
-
priority: 2,
|
|
169
|
-
}
|
|
170
|
-
);
|
|
193
|
+
await queue.addWorkflowJob(workflowConfig, { ticket: 'App crashes on login' });
|
|
171
194
|
```
|
|
172
195
|
|
|
196
|
+
| Node type | Config |
|
|
197
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| `agent` | `{ agentConfig, prompt?, outputKey? }` — `prompt` supports `{{path}}` placeholders; default prompt is the state as JSON |
|
|
199
|
+
| `transform` | `{ transform, inputKey?, outputKey?, template? }` — `uppercase`, `lowercase`, `trim`, `json-parse`, `json-stringify`, `template` |
|
|
200
|
+
| `condition` | `{ key, operator, value? }` — `equals`, `not-equals`, `contains`, `exists`, `gt`, `lt`; outgoing edges use `condition: 'true' \| 'false'` |
|
|
201
|
+
| `parallel` | `{}` — fan-out marker; successors run concurrently |
|
|
202
|
+
|
|
203
|
+
Node outputs are stored in the state under `outputKey` (default: node id). Nodes reachable only through untaken condition branches are skipped (`{ skipped: true }` in `nodeResults`). Graphs are validated before execution (unknown nodes, invalid configs, cycles).
|
|
204
|
+
|
|
173
205
|
**Swarm Jobs:**
|
|
174
206
|
|
|
175
207
|
```typescript
|
|
176
208
|
const swarmConfig: SerializedSwarm = {
|
|
177
|
-
topology: '
|
|
209
|
+
topology: 'voting',
|
|
178
210
|
agents: [researcherConfig, writerConfig, editorConfig],
|
|
179
|
-
coordinator: coordinatorConfig,
|
|
211
|
+
coordinator: coordinatorConfig, // decides when no consensus is reached
|
|
180
212
|
maxRounds: 3,
|
|
181
213
|
consensusThreshold: 0.8,
|
|
182
214
|
};
|
|
@@ -187,6 +219,14 @@ await queue.addSwarmJob(swarmConfig, 'Write an article about AI', {
|
|
|
187
219
|
});
|
|
188
220
|
```
|
|
189
221
|
|
|
222
|
+
| Topology | Swarm strategy | Notes |
|
|
223
|
+
| --------------- | -------------- | ------------------------------------------------------------ |
|
|
224
|
+
| `sequential` | `pipeline` | One stage per agent, in order |
|
|
225
|
+
| `hierarchical` | `hierarchical` | `coordinator` is required and becomes the supervisor |
|
|
226
|
+
| `collaborative` | `round-robin` | |
|
|
227
|
+
| `debate` | `debate` | `maxRounds` rounds, `coordinator` moderates |
|
|
228
|
+
| `voting` | `consensus` | `consensusThreshold`, `maxRounds`; `coordinator` breaks ties |
|
|
229
|
+
|
|
190
230
|
### Queue Methods
|
|
191
231
|
|
|
192
232
|
```typescript
|
|
@@ -254,6 +294,8 @@ interface WorkerConfig extends QueueConfig {
|
|
|
254
294
|
concurrency?: number; // Default: 5
|
|
255
295
|
lockDuration?: number; // Default: 30000ms
|
|
256
296
|
stalledInterval?: number; // Default: 30000ms
|
|
297
|
+
cogitator?: Cogitator; // Default: new Cogitator()
|
|
298
|
+
tools?: Tool[]; // Tool implementations, resolved by name
|
|
257
299
|
}
|
|
258
300
|
```
|
|
259
301
|
|
|
@@ -268,7 +310,7 @@ interface WorkerConfig extends QueueConfig {
|
|
|
268
310
|
|
|
269
311
|
```typescript
|
|
270
312
|
interface WorkerPoolEvents {
|
|
271
|
-
onJobStarted?: (jobId: string, type: 'agent' | 'workflow' | 'swarm') => void;
|
|
313
|
+
onJobStarted?: (jobId: string, type: 'agent' | 'workflow' | 'swarm' | 'swarm-agent') => void;
|
|
272
314
|
onJobCompleted?: (jobId: string, result: JobResult) => void;
|
|
273
315
|
onJobFailed?: (jobId: string, error: Error) => void;
|
|
274
316
|
onWorkerError?: (error: Error) => void;
|
|
@@ -286,7 +328,10 @@ pool.getWorkerCount();
|
|
|
286
328
|
|
|
287
329
|
const metrics = await pool.getMetrics(await queue.getMetrics());
|
|
288
330
|
|
|
289
|
-
//
|
|
331
|
+
// Job duration histogram and per-type counters
|
|
332
|
+
pool.metrics.format(await pool.getMetrics(await queue.getMetrics()));
|
|
333
|
+
|
|
334
|
+
// Graceful shutdown (waits up to 30s for active jobs, then force-closes)
|
|
290
335
|
await pool.stop(30000);
|
|
291
336
|
|
|
292
337
|
// Force shutdown
|
|
@@ -301,26 +346,62 @@ Built-in processors handle each job type.
|
|
|
301
346
|
|
|
302
347
|
### Using Processors Directly
|
|
303
348
|
|
|
349
|
+
Processors take the job payload and an optional runtime (`{ cogitator, tools }`):
|
|
350
|
+
|
|
304
351
|
```typescript
|
|
305
|
-
import { processAgentJob,
|
|
306
|
-
|
|
307
|
-
const agentResult = await processAgentJob({
|
|
308
|
-
type: 'agent',
|
|
309
|
-
jobId: 'job-1',
|
|
310
|
-
agentConfig: myAgentConfig,
|
|
311
|
-
input: 'Hello!',
|
|
312
|
-
threadId: 'thread-1',
|
|
313
|
-
});
|
|
352
|
+
import { processAgentJob, processWorkflowJob, processSwarmJob } from '@cogitator-ai/worker';
|
|
314
353
|
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
354
|
+
const runtime = { cogitator, tools: [searchTool] };
|
|
355
|
+
|
|
356
|
+
const agentResult = await processAgentJob(
|
|
357
|
+
{ type: 'agent', jobId: 'job-1', agentConfig: myAgentConfig, input: 'Hello!', threadId: 't-1' },
|
|
358
|
+
runtime
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const workflowResult = await processWorkflowJob(
|
|
362
|
+
{ type: 'workflow', jobId: 'job-2', runId: 'run-1', workflowConfig, input: { ticket: '...' } },
|
|
363
|
+
runtime
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const swarmResult = await processSwarmJob(
|
|
367
|
+
{ type: 'swarm', jobId: 'job-3', swarmConfig: mySwarmConfig, input: 'Solve this problem' },
|
|
368
|
+
runtime
|
|
369
|
+
);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
`processSwarmAgentJob(payload, { publisher, isFinalAttempt, ...runtime })` executes one distributed swarm turn and publishes the result (tagged with the job id) to `payload.stateKeys.results`; `executeSwarmAgentJob` returns the result without publishing.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Distributed Swarm Workers
|
|
377
|
+
|
|
378
|
+
Swarms created with `distributed.enabled` (see `@cogitator-ai/swarms`) dispatch every agent turn to a Redis queue. `DistributedSwarmWorker` consumes those turns:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { Cogitator } from '@cogitator-ai/core';
|
|
382
|
+
import { DistributedSwarmWorker } from '@cogitator-ai/worker';
|
|
383
|
+
|
|
384
|
+
const worker = new DistributedSwarmWorker(
|
|
385
|
+
{
|
|
386
|
+
redis: { host: 'localhost', port: 6379 },
|
|
387
|
+
keyPrefix: 'swarm', // must match the swarm's distributed.redis.keyPrefix
|
|
388
|
+
queue: 'swarm-agent-jobs', // must match distributed.queue
|
|
389
|
+
concurrency: 4,
|
|
390
|
+
cogitator: new Cogitator({ llm: { defaultModel: 'ollama/llama3.2' } }),
|
|
391
|
+
tools: [searchTool],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
onJobCompleted: (job) => console.log('done', job.agentName),
|
|
395
|
+
onJobFailed: (job, error) => console.error(job.agentName, error.message),
|
|
396
|
+
onError: (error) => console.error(error),
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
await worker.start();
|
|
401
|
+
process.on('SIGTERM', () => void worker.stop()); // waits for in-flight turns
|
|
321
402
|
```
|
|
322
403
|
|
|
323
|
-
|
|
404
|
+
Failed turns are reported back to the swarm as errors, so the swarm's own `errorHandling` (retry, failover, skip) applies.
|
|
324
405
|
|
|
325
406
|
---
|
|
326
407
|
|
|
@@ -381,24 +462,18 @@ Built-in metrics for monitoring and Kubernetes HPA.
|
|
|
381
462
|
### Exposing Metrics
|
|
382
463
|
|
|
383
464
|
```typescript
|
|
384
|
-
import {
|
|
385
|
-
JobQueue,
|
|
386
|
-
WorkerPool,
|
|
387
|
-
MetricsCollector,
|
|
388
|
-
formatPrometheusMetrics,
|
|
389
|
-
} from '@cogitator-ai/worker';
|
|
465
|
+
import { JobQueue, WorkerPool } from '@cogitator-ai/worker';
|
|
390
466
|
import express from 'express';
|
|
391
467
|
|
|
392
468
|
const queue = new JobQueue({ redis: { host: 'localhost', port: 6379 } });
|
|
393
469
|
const pool = new WorkerPool({ redis: { host: 'localhost', port: 6379 } });
|
|
394
|
-
|
|
470
|
+
await pool.start();
|
|
395
471
|
|
|
396
472
|
const app = express();
|
|
397
473
|
|
|
398
474
|
app.get('/metrics', async (req, res) => {
|
|
399
|
-
const queueMetrics = await queue.getMetrics();
|
|
400
|
-
|
|
401
|
-
res.type('text/plain').send(metrics.format(fullMetrics));
|
|
475
|
+
const queueMetrics = await queue.getMetrics(); // workerCount = workers connected to the queue
|
|
476
|
+
res.type('text/plain').send(pool.metrics.format(queueMetrics));
|
|
402
477
|
});
|
|
403
478
|
|
|
404
479
|
app.listen(9090);
|
|
@@ -414,7 +489,7 @@ app.listen(9090);
|
|
|
414
489
|
| `cogitator_queue_completed_total` | counter | Total completed jobs |
|
|
415
490
|
| `cogitator_queue_failed_total` | counter | Total failed jobs |
|
|
416
491
|
| `cogitator_queue_delayed` | gauge | Scheduled/delayed jobs |
|
|
417
|
-
| `cogitator_workers_total` | gauge |
|
|
492
|
+
| `cogitator_workers_total` | gauge | Workers connected to the queue |
|
|
418
493
|
| `cogitator_job_duration_seconds` | histogram | Job processing time |
|
|
419
494
|
| `cogitator_jobs_by_type_total` | counter | Jobs by type |
|
|
420
495
|
|
|
@@ -489,6 +564,8 @@ const queue = new JobQueue({
|
|
|
489
564
|
|
|
490
565
|
### Redis Cluster
|
|
491
566
|
|
|
567
|
+
Queues and workers connect to all cluster nodes (keys use the `{cogitator}` hash tag so they live in one slot):
|
|
568
|
+
|
|
492
569
|
```typescript
|
|
493
570
|
const queue = new JobQueue({
|
|
494
571
|
redis: {
|
|
@@ -516,11 +593,12 @@ Jobs use serialized configurations that can be stored in Redis.
|
|
|
516
593
|
interface SerializedAgent {
|
|
517
594
|
name: string;
|
|
518
595
|
instructions: string;
|
|
519
|
-
model: string;
|
|
520
|
-
provider:
|
|
596
|
+
model: string; // may include the provider prefix
|
|
597
|
+
provider: LLMProvider; // used when model has no prefix
|
|
521
598
|
temperature?: number;
|
|
522
599
|
maxTokens?: number;
|
|
523
|
-
|
|
600
|
+
maxIterations?: number;
|
|
601
|
+
tools: ToolSchema[]; // resolved by name against the worker's tools
|
|
524
602
|
}
|
|
525
603
|
```
|
|
526
604
|
|
|
@@ -543,10 +621,12 @@ interface SerializedWorkflowNode {
|
|
|
543
621
|
interface SerializedWorkflowEdge {
|
|
544
622
|
from: string;
|
|
545
623
|
to: string;
|
|
546
|
-
condition?: string;
|
|
624
|
+
condition?: string; // 'true' | 'false' for edges leaving condition nodes
|
|
547
625
|
}
|
|
548
626
|
```
|
|
549
627
|
|
|
628
|
+
Node configs are typed as `AgentNodeConfig`, `TransformNodeConfig` and `ConditionNodeConfig`.
|
|
629
|
+
|
|
550
630
|
### SerializedSwarm
|
|
551
631
|
|
|
552
632
|
```typescript
|
|
@@ -578,7 +658,7 @@ async function main() {
|
|
|
578
658
|
const agentConfig = {
|
|
579
659
|
name: 'Summarizer',
|
|
580
660
|
instructions: 'Summarize the given text concisely.',
|
|
581
|
-
model: 'openai/gpt-
|
|
661
|
+
model: 'openai/gpt-4o',
|
|
582
662
|
provider: 'openai' as const,
|
|
583
663
|
tools: [],
|
|
584
664
|
};
|
|
@@ -694,6 +774,9 @@ import type {
|
|
|
694
774
|
SerializedWorkflow,
|
|
695
775
|
SerializedWorkflowNode,
|
|
696
776
|
SerializedWorkflowEdge,
|
|
777
|
+
AgentNodeConfig,
|
|
778
|
+
TransformNodeConfig,
|
|
779
|
+
ConditionNodeConfig,
|
|
697
780
|
SerializedSwarm,
|
|
698
781
|
|
|
699
782
|
// Job payloads
|
|
@@ -713,7 +796,10 @@ import type {
|
|
|
713
796
|
// Configuration
|
|
714
797
|
QueueConfig,
|
|
715
798
|
WorkerConfig,
|
|
799
|
+
WorkerRuntime,
|
|
716
800
|
QueueMetrics,
|
|
801
|
+
DistributedSwarmWorkerConfig,
|
|
802
|
+
DistributedSwarmWorkerEvents,
|
|
717
803
|
} from '@cogitator-ai/worker';
|
|
718
804
|
```
|
|
719
805
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Cluster, Redis } from 'ioredis';
|
|
2
|
+
import type { ConnectionOptions } from 'bullmq';
|
|
3
|
+
import type { QueueConfig } from './types.js';
|
|
4
|
+
export declare const DEFAULT_QUEUE_NAME = "cogitator-jobs";
|
|
5
|
+
export type RedisClient = Redis | Cluster;
|
|
6
|
+
type RedisConnectionConfig = QueueConfig['redis'];
|
|
7
|
+
/**
|
|
8
|
+
* BullMQ key prefix; cluster mode needs a hash tag so all queue keys share a slot.
|
|
9
|
+
*/
|
|
10
|
+
export declare function queuePrefix(redis: RedisConnectionConfig): string;
|
|
11
|
+
export interface BullConnection {
|
|
12
|
+
connection: ConnectionOptions;
|
|
13
|
+
/** Close connections that BullMQ treats as shared (cluster instances) */
|
|
14
|
+
dispose(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Connection for BullMQ queues and workers. Blocking (worker) connections must not limit
|
|
18
|
+
* retries per request, as BullMQ requires. Cluster instances are shared with BullMQ, which
|
|
19
|
+
* does not close them, so callers must `dispose()` them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createBullConnection(redis: RedisConnectionConfig, options?: {
|
|
22
|
+
blocking?: boolean;
|
|
23
|
+
}): BullConnection;
|
|
24
|
+
/**
|
|
25
|
+
* Plain client (e.g. for publishing swarm results) using the same Redis configuration.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createRedisClient(redis: RedisConnectionConfig): RedisClient;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,OAAO,CAAC;AAE1C,KAAK,qBAAqB,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;AAElD;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAEhE;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,yEAAyE;IACzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GACnC,cAAc,CAiChB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,GAAG,WAAW,CAa3E"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Cluster, Redis } from 'ioredis';
|
|
2
|
+
export const DEFAULT_QUEUE_NAME = 'cogitator-jobs';
|
|
3
|
+
/**
|
|
4
|
+
* BullMQ key prefix; cluster mode needs a hash tag so all queue keys share a slot.
|
|
5
|
+
*/
|
|
6
|
+
export function queuePrefix(redis) {
|
|
7
|
+
return redis.cluster ? '{cogitator}' : 'cogitator';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Connection for BullMQ queues and workers. Blocking (worker) connections must not limit
|
|
11
|
+
* retries per request, as BullMQ requires. Cluster instances are shared with BullMQ, which
|
|
12
|
+
* does not close them, so callers must `dispose()` them.
|
|
13
|
+
*/
|
|
14
|
+
export function createBullConnection(redis, options = {}) {
|
|
15
|
+
const maxRetriesPerRequest = options.blocking ? null : undefined;
|
|
16
|
+
if (redis.cluster) {
|
|
17
|
+
if (redis.cluster.nodes.length === 0) {
|
|
18
|
+
throw new Error('Redis cluster configuration requires at least one node');
|
|
19
|
+
}
|
|
20
|
+
const cluster = new Cluster(redis.cluster.nodes, {
|
|
21
|
+
lazyConnect: true,
|
|
22
|
+
redisOptions: { password: redis.password, maxRetriesPerRequest },
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
connection: cluster,
|
|
26
|
+
dispose: async () => {
|
|
27
|
+
if (cluster.status === 'end')
|
|
28
|
+
return;
|
|
29
|
+
if (cluster.status === 'wait') {
|
|
30
|
+
cluster.disconnect();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await cluster.quit();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
connection: {
|
|
39
|
+
host: redis.host ?? 'localhost',
|
|
40
|
+
port: redis.port ?? 6379,
|
|
41
|
+
password: redis.password,
|
|
42
|
+
maxRetriesPerRequest,
|
|
43
|
+
},
|
|
44
|
+
dispose: async () => { },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Plain client (e.g. for publishing swarm results) using the same Redis configuration.
|
|
49
|
+
*/
|
|
50
|
+
export function createRedisClient(redis) {
|
|
51
|
+
if (redis.cluster) {
|
|
52
|
+
if (redis.cluster.nodes.length === 0) {
|
|
53
|
+
throw new Error('Redis cluster configuration requires at least one node');
|
|
54
|
+
}
|
|
55
|
+
return new Cluster(redis.cluster.nodes, { redisOptions: { password: redis.password } });
|
|
56
|
+
}
|
|
57
|
+
return new Redis({
|
|
58
|
+
host: redis.host ?? 'localhost',
|
|
59
|
+
port: redis.port ?? 6379,
|
|
60
|
+
password: redis.password,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAMnD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,KAA4B;IACtD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC;AACrD,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAA4B,EAC5B,UAAkC,EAAE;IAEpC,MAAM,oBAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAEjE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE;YAC/C,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,oBAAoB,EAAE;SACjE,CAAC,CAAC;QACH,OAAO;YACL,UAAU,EAAE,OAAO;YACnB,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;oBAAE,OAAO;gBACrC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;oBAC9B,OAAO,CAAC,UAAU,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBACD,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACvB,CAAC;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU,EAAE;YACV,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,WAAW;YAC/B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,oBAAoB;SACrB;QACD,OAAO,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;KACxB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAA4B;IAC5D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,IAAI,KAAK,CAAC;QACf,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,WAAW;QAC/B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker node for distributed swarms.
|
|
3
|
+
*
|
|
4
|
+
* Consumes agent turns dispatched by `DistributedSwarmCoordinator` (from
|
|
5
|
+
* `@cogitator-ai/swarms`), runs them with the worker's Cogitator and tools, and publishes
|
|
6
|
+
* each result back to the coordinator's results channel.
|
|
7
|
+
*/
|
|
8
|
+
import type { SwarmAgentJobPayload, SwarmAgentJobResult, WorkerRuntime } from './types.js';
|
|
9
|
+
export interface DistributedSwarmWorkerConfig extends WorkerRuntime {
|
|
10
|
+
/** Redis holding the swarm job queue (must match the swarm's `distributed.redis`) */
|
|
11
|
+
redis?: {
|
|
12
|
+
host?: string;
|
|
13
|
+
port?: number;
|
|
14
|
+
password?: string;
|
|
15
|
+
db?: number;
|
|
16
|
+
};
|
|
17
|
+
/** Swarm key prefix (must match `distributed.redis.keyPrefix`, default: 'swarm') */
|
|
18
|
+
keyPrefix?: string;
|
|
19
|
+
/** Job queue name (must match `distributed.queue`, default: 'swarm-agent-jobs') */
|
|
20
|
+
queue?: string;
|
|
21
|
+
/** Agent turns processed concurrently by this worker (default: 4) */
|
|
22
|
+
concurrency?: number;
|
|
23
|
+
/** Seconds a blocking poll waits for new jobs before re-checking state (default: 1) */
|
|
24
|
+
pollTimeout?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface DistributedSwarmWorkerEvents {
|
|
27
|
+
onJobStarted?: (job: SwarmAgentJobPayload) => void;
|
|
28
|
+
onJobCompleted?: (job: SwarmAgentJobPayload, result: SwarmAgentJobResult) => void;
|
|
29
|
+
onJobFailed?: (job: SwarmAgentJobPayload, error: Error) => void;
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
}
|
|
32
|
+
export declare class DistributedSwarmWorker {
|
|
33
|
+
private readonly config;
|
|
34
|
+
private readonly events;
|
|
35
|
+
private readonly runtime;
|
|
36
|
+
private readonly queueKey;
|
|
37
|
+
private consumer?;
|
|
38
|
+
private publisher?;
|
|
39
|
+
private loop?;
|
|
40
|
+
private running;
|
|
41
|
+
private readonly active;
|
|
42
|
+
constructor(config?: DistributedSwarmWorkerConfig, events?: DistributedSwarmWorkerEvents);
|
|
43
|
+
/**
|
|
44
|
+
* Start consuming jobs. Resolves once the worker is connected.
|
|
45
|
+
*/
|
|
46
|
+
start(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Stop taking new jobs, wait for in-flight jobs to finish and close connections.
|
|
49
|
+
*/
|
|
50
|
+
stop(): Promise<void>;
|
|
51
|
+
isRunning(): boolean;
|
|
52
|
+
getActiveJobCount(): number;
|
|
53
|
+
private consume;
|
|
54
|
+
private handle;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=distributed-swarm-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distributed-swarm-worker.d.ts","sourceRoot":"","sources":["../src/distributed-swarm-worker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAKxF,MAAM,WAAW,4BAA6B,SAAQ,aAAa;IACjE,qFAAqF;IACrF,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;IACF,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,4BAA4B;IAC3C,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACnD,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAChE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAgBD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,IAAI,CAAC,CAAgB;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;gBAGjD,MAAM,GAAE,4BAAiC,EACzC,MAAM,GAAE,4BAAiC;IAW3C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B,SAAS,IAAI,OAAO;IAIpB,iBAAiB,IAAI,MAAM;YAIb,OAAO;YA6BP,MAAM;CAgCrB"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker node for distributed swarms.
|
|
3
|
+
*
|
|
4
|
+
* Consumes agent turns dispatched by `DistributedSwarmCoordinator` (from
|
|
5
|
+
* `@cogitator-ai/swarms`), runs them with the worker's Cogitator and tools, and publishes
|
|
6
|
+
* each result back to the coordinator's results channel.
|
|
7
|
+
*/
|
|
8
|
+
import { Redis } from 'ioredis';
|
|
9
|
+
import { swarmJobQueueKey } from '@cogitator-ai/swarms';
|
|
10
|
+
import { executeSwarmAgentJob } from './processors/swarm-agent.js';
|
|
11
|
+
import { toErrorMessage } from './processors/shared.js';
|
|
12
|
+
import { Cogitator } from '@cogitator-ai/core';
|
|
13
|
+
function isSwarmAgentJob(value) {
|
|
14
|
+
if (typeof value !== 'object' || value === null)
|
|
15
|
+
return false;
|
|
16
|
+
const job = value;
|
|
17
|
+
return (job.type === 'swarm-agent' &&
|
|
18
|
+
typeof job.jobId === 'string' &&
|
|
19
|
+
typeof job.agentName === 'string' &&
|
|
20
|
+
typeof job.input === 'string' &&
|
|
21
|
+
typeof job.agentConfig === 'object' &&
|
|
22
|
+
job.agentConfig !== null &&
|
|
23
|
+
typeof job.stateKeys?.results === 'string');
|
|
24
|
+
}
|
|
25
|
+
export class DistributedSwarmWorker {
|
|
26
|
+
config;
|
|
27
|
+
events;
|
|
28
|
+
runtime;
|
|
29
|
+
queueKey;
|
|
30
|
+
consumer;
|
|
31
|
+
publisher;
|
|
32
|
+
loop;
|
|
33
|
+
running = false;
|
|
34
|
+
active = new Set();
|
|
35
|
+
constructor(config = {}, events = {}) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.events = events;
|
|
38
|
+
this.runtime = {
|
|
39
|
+
cogitator: config.cogitator ?? new Cogitator(),
|
|
40
|
+
tools: config.tools ?? [],
|
|
41
|
+
};
|
|
42
|
+
this.queueKey = swarmJobQueueKey(config.keyPrefix, config.queue);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Start consuming jobs. Resolves once the worker is connected.
|
|
46
|
+
*/
|
|
47
|
+
async start() {
|
|
48
|
+
if (this.running)
|
|
49
|
+
return;
|
|
50
|
+
const options = {
|
|
51
|
+
host: this.config.redis?.host ?? 'localhost',
|
|
52
|
+
port: this.config.redis?.port ?? 6379,
|
|
53
|
+
password: this.config.redis?.password,
|
|
54
|
+
db: this.config.redis?.db ?? 0,
|
|
55
|
+
lazyConnect: true,
|
|
56
|
+
};
|
|
57
|
+
const consumer = new Redis({ ...options, maxRetriesPerRequest: null });
|
|
58
|
+
const publisher = new Redis(options);
|
|
59
|
+
try {
|
|
60
|
+
await Promise.all([consumer.connect(), publisher.connect()]);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
consumer.disconnect();
|
|
64
|
+
publisher.disconnect();
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
this.consumer = consumer;
|
|
68
|
+
this.publisher = publisher;
|
|
69
|
+
this.running = true;
|
|
70
|
+
this.loop = this.consume();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Stop taking new jobs, wait for in-flight jobs to finish and close connections.
|
|
74
|
+
*/
|
|
75
|
+
async stop() {
|
|
76
|
+
if (!this.running)
|
|
77
|
+
return;
|
|
78
|
+
this.running = false;
|
|
79
|
+
this.consumer?.disconnect();
|
|
80
|
+
await this.loop;
|
|
81
|
+
await Promise.allSettled([...this.active]);
|
|
82
|
+
await this.publisher?.quit();
|
|
83
|
+
this.consumer = undefined;
|
|
84
|
+
this.publisher = undefined;
|
|
85
|
+
this.loop = undefined;
|
|
86
|
+
}
|
|
87
|
+
isRunning() {
|
|
88
|
+
return this.running;
|
|
89
|
+
}
|
|
90
|
+
getActiveJobCount() {
|
|
91
|
+
return this.active.size;
|
|
92
|
+
}
|
|
93
|
+
async consume() {
|
|
94
|
+
const concurrency = Math.max(1, this.config.concurrency ?? 4);
|
|
95
|
+
const pollTimeout = Math.max(0.1, this.config.pollTimeout ?? 1);
|
|
96
|
+
while (this.running) {
|
|
97
|
+
if (this.active.size >= concurrency) {
|
|
98
|
+
await Promise.race(this.active);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
let entry;
|
|
102
|
+
try {
|
|
103
|
+
entry = await this.consumer.blpop(this.queueKey, pollTimeout);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!this.running)
|
|
107
|
+
return;
|
|
108
|
+
this.events.onError?.(error instanceof Error ? error : new Error(toErrorMessage(error)));
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!entry)
|
|
113
|
+
continue;
|
|
114
|
+
const task = this.handle(entry[1]).finally(() => {
|
|
115
|
+
this.active.delete(task);
|
|
116
|
+
});
|
|
117
|
+
this.active.add(task);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async handle(raw) {
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = JSON.parse(raw);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
this.events.onError?.(new Error('Discarded swarm job with invalid JSON'));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!isSwarmAgentJob(parsed)) {
|
|
130
|
+
this.events.onError?.(new Error('Discarded malformed swarm job payload'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const job = parsed;
|
|
134
|
+
this.events.onJobStarted?.(job);
|
|
135
|
+
const result = await executeSwarmAgentJob(job, this.runtime);
|
|
136
|
+
try {
|
|
137
|
+
await this.publisher.publish(job.stateKeys.results, JSON.stringify(result));
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.events.onError?.(error instanceof Error ? error : new Error(toErrorMessage(error)));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (result.error !== undefined) {
|
|
144
|
+
this.events.onJobFailed?.(job, new Error(result.error));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
this.events.onJobCompleted?.(job, result);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=distributed-swarm-worker.js.map
|