@backendkit-labs/agent-web 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +82 -193
  2. package/package.json +51 -51
package/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # @backendkit-labs/agent-web
2
2
 
3
- > **Status: `stable`** — Maintained and production-ready. `TriggerBus` (cron/webhook/BullMQ) is `experimental` and frozen during the pilot phase.
4
-
5
3
  HTTP/WebSocket server layer for [@backendkit-labs/agent-core](https://www.npmjs.com/package/@backendkit-labs/agent-core).
6
4
 
7
5
  Wraps `AgentEngine` in a Fastify 5 server with streaming WebSocket sessions, JWT authentication, Prometheus metrics, rate limiting, a real-time monitor bus, and a flexible trigger system (cron jobs, webhooks, BullMQ queues).
@@ -222,53 +220,49 @@ const engineFactory: EngineFactory = (sessionId, transport) => {
222
220
  };
223
221
  ```
224
222
 
225
- ### Factory with userId from JWT
223
+ ### Factory with per-user context
226
224
 
227
- When JWT auth is enabled, the `sessionId` carries the authenticated user's context. You can also inject user-specific data:
225
+ Each WebSocket connection to `/chat` gets its own session with a random `sessionId`, and the factory builds a fresh engine for it. When `auth` is set, the authenticated user (the JWT `sub`) becomes the **owner** of the session: other users cannot see it in `/sessions` or `/monitor`. To load per-user settings in the engine, keep your own map from session to user, or read them inside your tools.
228
226
 
229
227
  ```typescript
230
- const engineFactory: EngineFactory = (sessionId, transport) => {
231
- // sessionId is set to the JWT 'sub' claim when JwtAuth is used
232
- const userConfig = getUserConfig(sessionId); // look up per-user settings
233
-
234
- return new AgentEngine({
235
- transport,
236
- sessionId,
237
- projectContext: `User: ${userConfig.name}\nRole: ${userConfig.role}`,
228
+ const engineFactory: EngineFactory = (sessionId, transport) =>
229
+ new AgentEngine({
238
230
  ...baseConfig,
231
+ transport,
232
+ sessionId, // also the session's id in metrics, monitor and audit
239
233
  });
240
- };
241
234
  ```
242
235
 
243
236
  ---
244
237
 
245
238
  ## SessionManager
246
239
 
247
- `SessionManager` creates, tracks, and cleans up sessions. It is created internally by `AgentServer`, but can also be used standalone.
240
+ `SessionManager` creates, tracks and cleans up sessions. `AgentServer` creates one internally; you can also use it on your own WebSocket server.
248
241
 
249
242
  ```typescript
250
243
  import { SessionManager } from '@backendkit-labs/agent-web';
251
244
 
252
245
  const sessions = new SessionManager({
253
246
  engineFactory,
254
- maxIdleMs: 30 * 60 * 1000, // close after 30 min of inactivity
255
- maxSessions: 100,
247
+ maxIdleMs: 30 * 60 * 1000, // close after 30 min of inactivity (default)
248
+ maxSessions: 100, // reject new sessions beyond this (default)
256
249
  monitorBus: myMonitorBus,
257
250
  metrics: myMetricsCollector,
258
251
  store: myMetaStore,
259
252
  });
260
253
 
261
- // Lifecycle
262
- const session = await sessions.getOrCreate('session-id', myTransport);
263
- await sessions.close('session-id');
264
- const all = sessions.list(); // SessionMeta[]
254
+ const session = sessions.create(socket, userId); // one per WebSocket connection; userId is the owner
255
+ sessions.get(session.id);
256
+ sessions.list(); // [{ id, createdAt, lastActivityAt, userId }]
257
+ sessions.destroy(session.id);
258
+ sessions.close(); // all sessions, on shutdown
265
259
  ```
266
260
 
267
261
  ---
268
262
 
269
263
  ## JWT authentication
270
264
 
271
- `JwtAuth` validates Bearer tokens in the `Authorization` header or `?token=` query parameter (useful for browser WebSocket connections that can't set custom headers).
265
+ `JwtAuth` validates Bearer tokens on HTTP routes and on the WebSocket handshake. Browsers cannot set headers on a WebSocket, so the token can also travel as the subprotocol `bearer, <jwt>`. A `?token=` query parameter still works unless `allowQueryToken: false`.
272
266
 
273
267
  ### Strict mode
274
268
 
@@ -291,47 +285,26 @@ new AgentServer({ engineFactory, host: '0.0.0.0', auth, strict: true, cors: ['ht
291
285
  import { AgentServer, JwtAuth } from '@backendkit-labs/agent-web';
292
286
 
293
287
  const jwt = new JwtAuth({
294
- secret: process.env.JWT_SECRET!, // or use publicKey for RS256
295
- algorithm: 'HS256', // default
296
- sessionIdClaim:'sub', // JWT claim to use as sessionId
297
- expiresIn: '24h',
288
+ secret: process.env.JWT_SECRET!, // or publicKey for RS256 / ES256
289
+ algorithms: ['HS256'], // accepted algorithms
290
+ audience: 'agents', // optional checks
291
+ issuer: 'https://login.company.com',
298
292
  });
299
293
 
300
- const server = new AgentServer({
301
- port: 3000,
302
- engineFactory,
303
- auth: jwt.hook(), // AuthHook applied to all routes
304
- });
294
+ const server = new AgentServer({ port: 3000, engineFactory, auth: jwt.hook() });
305
295
  ```
306
296
 
307
- ### Issuing tokens (your login endpoint)
297
+ The token must carry `sub` (the user id, owner of the sessions it opens); `email` and `roles` are read when present. `adminRole` in `AgentServerOptions` names the role that may see every session.
308
298
 
309
- ```typescript
310
- import { JwtAuth } from '@backendkit-labs/agent-web';
299
+ ### Issuing tokens
311
300
 
312
- const jwt = new JwtAuth({ secret: process.env.JWT_SECRET! });
313
-
314
- // Issue a token for an authenticated user
315
- const token = jwt.sign({
316
- sub: 'user-123',
317
- role: 'admin',
318
- name: 'Alice',
319
- });
320
-
321
- // Return to client — they send it as:
322
- // Authorization: Bearer <token>
323
- // or ws://host/chat?token=<token>
324
- ```
325
-
326
- ### Per-user session isolation
327
-
328
- When `sessionIdClaim` is `'sub'`, every user gets their own session automatically. Two requests with the same token always hit the same session:
301
+ `JwtAuth` only verifies. Issue tokens in your login endpoint with any JWT library and the same secret or key pair:
329
302
 
330
303
  ```typescript
331
- const jwt = new JwtAuth({
332
- secret: process.env.JWT_SECRET!,
333
- sessionIdClaim: 'sub', // user ID as session key
334
- });
304
+ import jwt from 'jsonwebtoken';
305
+
306
+ const token = jwt.sign({ sub: 'user-123', roles: ['admin'] }, process.env.JWT_SECRET!, { expiresIn: '24h' });
307
+ // The client sends it as Authorization: Bearer <token>, or as the WebSocket subprotocol `bearer, <token>`.
335
308
  ```
336
309
 
337
310
  ### Custom auth hook
@@ -385,13 +358,15 @@ import { MetricsCollector } from '@backendkit-labs/agent-web';
385
358
 
386
359
  const collector = new MetricsCollector({ prefix: 'my_agent_' });
387
360
 
388
- // Record a completed run
389
- const endRun = collector.startRun('session-abc');
390
- // ... after run completes:
361
+ // Record a run
362
+ const endRun = collector.runStarted();
363
+ // ... after it completes:
391
364
  endRun('success'); // or endRun('error')
392
365
 
393
- // Get Prometheus text format
394
- const text = await collector.getMetrics();
366
+ collector.sessionOpened(); // and sessionClosed(), messageReceived()
367
+
368
+ // Prometheus text format (serve it with collector.contentType)
369
+ const text = await collector.collect();
395
370
  // # HELP my_agent_runs_total ...
396
371
  // my_agent_runs_total{status="success"} 42
397
372
  ```
@@ -484,7 +459,13 @@ unsub();
484
459
 
485
460
  ## TriggerBus — cron, webhooks, queues
486
461
 
487
- `TriggerBus` lets autonomous agent runs happen without a WebSocket client. Runs are visible in the Kanban monitor.
462
+ `TriggerBus` runs agents without a WebSocket client: on a schedule, on an incoming webhook, or for each job of a BullMQ queue. Each run gets a fresh engine from the factory and a `HeadlessTransport`, and shows up in the monitor.
463
+
464
+ Every trigger has the same three callbacks:
465
+
466
+ - `buildPrompt(payload)` returns the prompt. `payload` is `undefined` for cron, the parsed body for a webhook, and `job.data` for a queue job.
467
+ - `onComplete(result)` receives `{ sessionId, triggerName, firedAt, durationMs, output, events }`.
468
+ - `onError(err, triggerName)` is called when the run throws.
488
469
 
489
470
  ### Cron trigger (requires `node-cron`)
490
471
 
@@ -496,20 +477,19 @@ const triggers = new TriggerBus(engineFactory)
496
477
  name: 'daily-report',
497
478
  schedule: '0 8 * * 1-5', // weekdays at 8 AM
498
479
  buildPrompt: () => 'Generate the daily engineering standup summary from recent commits.',
499
- runOnStart: false, // set true to fire immediately on server start
500
- onResult: (out) => slack.send('#daily', out),
501
- onError: (err) => logger.error('[daily-report]', err),
480
+ runOnStart: false, // true fires once when the bus starts
481
+ onComplete: ({ output }) => slack.send('#daily', output),
482
+ onError: (err, name) => logger.error(`[${name}]`, err),
502
483
  })
503
484
  .addCron({
504
485
  name: 'health-check',
505
- schedule: '*/15 * * * *', // every 15 minutes
486
+ schedule: '*/15 * * * *',
506
487
  buildPrompt: () => 'Check all API endpoints and report any that are down.',
507
- onResult: (out) => { if (out.includes('DOWN')) pagerduty.alert(out); },
488
+ onComplete: ({ output }) => { if (output.includes('DOWN')) pagerduty.alert(output); },
508
489
  });
509
490
 
510
- const server = new AgentServer({ triggers, ...opts });
511
- await server.start();
512
- // Cron jobs start automatically with the server
491
+ const server = new AgentServer({ ...opts, triggers });
492
+ await server.start(); // cron jobs start with the server
513
493
  ```
514
494
 
515
495
  ### Webhook trigger
@@ -518,207 +498,116 @@ await server.start();
518
498
  const triggers = new TriggerBus(engineFactory)
519
499
  .addWebhook({
520
500
  name: 'github-pr',
521
- path: '/webhooks/github', // registered as POST /webhooks/github
522
- secret: process.env.GITHUB_SECRET, // HMAC X-Hub-Signature-256 validation (optional)
501
+ path: '/webhooks/github', // POST /webhooks/github (method: 'GET' | 'POST' | 'PUT')
502
+ secret: process.env.GITHUB_SECRET, // HMAC X-Hub-Signature-256 over the raw body
523
503
  buildPrompt: (body) => {
524
- const pr = body.pull_request;
504
+ const pr = (body as { pull_request: { number: number; title: string; diff_url: string } }).pull_request;
525
505
  return `Review PR #${pr.number}: "${pr.title}"\n${pr.diff_url}`;
526
506
  },
527
- onResult: (out, body) => github.postReview(body.pull_request.number, out),
528
- onError: (err) => logger.error('[github-pr]', err),
529
- })
530
- .addWebhook({
531
- name: 'jira-issue',
532
- path: '/webhooks/jira',
533
- buildPrompt: (body) => `Analyze Jira issue ${body.issue.key}: ${body.issue.fields.summary}`,
534
- onResult: (out) => jira.addComment(body.issue.key, out),
507
+ onComplete: ({ output }) => github.comment(output),
535
508
  });
536
509
  ```
537
510
 
538
- **Testing a webhook:**
511
+ The webhook answers immediately and the run continues in the background; use `onComplete` to act on the result. If the payload is needed there, capture it in `buildPrompt`.
539
512
 
540
513
  ```bash
541
514
  curl -X POST http://localhost:3000/webhooks/github \
542
515
  -H "Content-Type: application/json" \
543
- -H "X-Hub-Signature-256: sha256=<hmac>" \
544
- -d '{ "pull_request": { "number": 42, "title": "Add auth module" } }'
516
+ -H "X-Hub-Signature-256: sha256=<hmac of the exact body>" \
517
+ -d '{ "pull_request": { "number": 42, "title": "Add auth module", "diff_url": "..." } }'
545
518
  ```
546
519
 
547
520
  ### Queue trigger (BullMQ — requires `bullmq`)
548
521
 
549
522
  ```typescript
550
- import { TriggerBus } from '@backendkit-labs/agent-web';
551
-
552
523
  const triggers = new TriggerBus(engineFactory)
553
524
  .addQueue({
554
525
  name: 'document-processor',
555
- queueName: 'documents', // BullMQ queue name
556
- connection: process.env.REDIS_URL!, // Redis URL or connection object
557
- concurrency: 3, // process 3 jobs in parallel
558
- buildPrompt: (job) => {
559
- return `Summarize and extract key points from this document:\n\n${job.data.content}`;
560
- },
561
- onResult: (out, job) => {
562
- db.documents.update(job.data.id, { summary: out });
563
- },
564
- onError: (err, job) => {
565
- logger.error(`[queue] Failed to process document ${job?.data?.id}:`, err);
566
- // The error is re-thrown so BullMQ marks the job as failed and applies retry policy
567
- },
526
+ queueName: 'documents',
527
+ connection: process.env.REDIS_URL!, // URL, or { host, port, password, username, db, tls }
528
+ concurrency: 3, // default 1
529
+ buildPrompt: (data) => `Summarize and extract key points:\n\n${(data as { content: string }).content}`,
530
+ onComplete: ({ output }) => saveSummary(output),
568
531
  });
569
532
  ```
570
533
 
571
- **Publishing a job (from anywhere in your system):**
572
-
573
- ```typescript
574
- import { Queue } from 'bullmq';
575
-
576
- const queue = new Queue('documents', { connection: process.env.REDIS_URL! });
577
-
578
- await queue.add('process-doc', {
579
- id: 'doc-456',
580
- content: 'Full document text...',
581
- }, {
582
- attempts: 3,
583
- backoff: { type: 'exponential', delay: 2000 },
584
- removeOnComplete: 100,
585
- removeOnFail: 50,
586
- });
587
- ```
588
-
589
- ### Queue connection options
590
-
591
- ```typescript
592
- // URL string
593
- connection: 'redis://localhost:6379'
594
- connection: 'redis://:password@redis-host:6380/1'
595
-
596
- // Connection object
597
- connection: {
598
- host: 'redis-host',
599
- port: 6380,
600
- password: 'mypassword',
601
- username: 'default',
602
- db: 1,
603
- tls: {}, // enable TLS
604
- }
605
- ```
606
-
607
- ### CronTriggerDef with all options
608
-
609
- ```typescript
610
- interface CronTriggerDef {
611
- name: string; // unique name for the trigger
612
- schedule: string; // standard cron expression
613
- buildPrompt: (ts: Date) => string; // returns the prompt to run
614
- agentId?: string; // override default agent
615
- runOnStart?: boolean; // fire immediately on server start
616
- onResult?: (output: string) => void;
617
- onError?: (err: Error) => void;
618
- }
619
- ```
534
+ A failed run is rethrown to BullMQ, so the job is marked failed and its retry policy applies. Jobs are published with BullMQ as usual (`new Queue('documents', { connection }).add('process', { id, content }, { attempts: 3 })`).
620
535
 
621
536
  ---
622
537
 
623
538
  ## HeadlessTransport
624
539
 
625
- `HeadlessTransport` is a `Transport` implementation that buffers all `AgentEvent` objects in memory instead of streaming them over a socket. Used internally by `TriggerBus` for autonomous runs.
540
+ A `Transport` with no socket: it buffers every `AgentEvent` in memory. `TriggerBus` uses it for autonomous runs; use it to run an engine from a script.
626
541
 
627
542
  ```typescript
628
543
  import { HeadlessTransport } from '@backendkit-labs/agent-web';
629
544
 
630
545
  const transport = new HeadlessTransport();
631
-
632
546
  const engine = new AgentEngine({ ...opts, transport });
633
547
  await engine.run('Generate a daily report');
634
548
 
635
- // All events are buffered
636
- const events = transport.getEvents();
637
- const tokens = events
638
- .filter(e => e.type === 'chunk')
639
- .map(e => e.content)
640
- .join('');
641
-
642
- console.log('Final output:', tokens);
549
+ transport.getOutput(); // concatenated text of the 'token' events
550
+ transport.getEvents(); // every event, in order
643
551
  ```
644
552
 
645
553
  ---
646
554
 
647
555
  ## Full production example
648
556
 
649
- A complete production server with JWT auth, Redis sessions, Prometheus metrics, cron jobs, webhook triggers, and BullMQ queue processing:
557
+ JWT auth, Redis sessions, Prometheus metrics, a cron job, a webhook and a queue:
650
558
 
651
559
  ```typescript
652
560
  import { AgentServer, JwtAuth, TriggerBus, RedisSessionMetaStore } from '@backendkit-labs/agent-web';
653
561
  import { createEnterpriseSetup } from '@backendkit-labs/agent-enterprise';
654
- import { ProviderRegistry, DeepSeekProvider } from '@backendkit-labs/agent-core';
562
+ import { ProviderRegistry } from '@backendkit-labs/agent-core';
563
+ import { DeepSeekProvider } from '@backendkit-labs/agent-providers';
655
564
  import Redis from 'ioredis';
656
565
 
657
- // LLM providers
658
566
  const providers = new ProviderRegistry();
659
- providers.register('deepseek', new DeepSeekProvider({
660
- apiKey: process.env.DEEPSEEK_API_KEY!,
661
- model: 'deepseek-chat',
662
- }));
663
-
664
- // Enterprise setup (RAG + 6 agent profiles)
665
- const enterprise = createEnterpriseSetup({
666
- vaultPath: process.env.VAULT_PATH!,
667
- providers,
668
- defaultProvider: 'deepseek',
669
- });
670
- await enterprise.indexAll();
567
+ providers.register('deepseek', new DeepSeekProvider({ apiKey: process.env.DEEPSEEK_API_KEY!, model: 'deepseek-chat' }));
671
568
 
672
- // Redis
673
- const redis = new Redis(process.env.REDIS_URL!);
674
-
675
- // JWT auth
676
- const jwt = new JwtAuth({
677
- secret: process.env.JWT_SECRET!,
678
- sessionIdClaim: 'sub',
679
- });
569
+ const enterprise = createEnterpriseSetup({ vaultPath: process.env.VAULT_PATH!, providers, defaultProvider: 'deepseek' });
570
+ await enterprise.indexAll();
680
571
 
681
- // Autonomous triggers
682
572
  const triggers = new TriggerBus(enterprise.engineFactory)
683
573
  .addCron({
684
574
  name: 'weekly-digest',
685
- schedule: '0 9 * * 1', // Mondays at 9 AM
575
+ schedule: '0 9 * * 1',
686
576
  buildPrompt: () => 'Generate a weekly digest of all knowledge base changes.',
687
- onResult: (out) => slack.send('#general', out),
577
+ onComplete: ({ output }) => slack.send('#general', output),
688
578
  })
689
579
  .addWebhook({
690
580
  name: 'support-ticket',
691
581
  path: '/webhooks/support',
692
582
  secret: process.env.WEBHOOK_SECRET,
693
- buildPrompt: (body) => `Handle support ticket #${body.id}: ${body.subject}\n${body.description}`,
694
- onResult: (out, body) => helpdesk.reply(body.id, out),
583
+ buildPrompt: (body) => {
584
+ const t = body as { id: string; subject: string; description: string };
585
+ return `Handle support ticket #${t.id}: ${t.subject}\n${t.description}`;
586
+ },
695
587
  })
696
588
  .addQueue({
697
589
  name: 'email-processor',
698
590
  queueName: 'emails',
699
591
  connection: process.env.REDIS_URL!,
700
592
  concurrency: 5,
701
- buildPrompt: (job) => `Draft a professional reply to this email:\n\n${job.data.body}`,
702
- onResult: (out, job) => emailService.send({ to: job.data.from, body: out }),
593
+ buildPrompt: (data) => `Draft a professional reply to this email:\n\n${(data as { body: string }).body}`,
703
594
  });
704
595
 
705
- // Server
706
596
  const server = new AgentServer({
707
597
  port: 3000,
598
+ host: '0.0.0.0',
708
599
  engineFactory: enterprise.engineFactory,
709
- auth: jwt.hook(),
710
- store: new RedisSessionMetaStore(redis),
600
+ auth: new JwtAuth({ secret: process.env.JWT_SECRET!, allowQueryToken: false }).hook(),
601
+ strict: true,
602
+ store: new RedisSessionMetaStore(new Redis(process.env.REDIS_URL!)),
711
603
  cors: ['https://app.company.com'],
712
604
  logger: { level: 'info' },
713
605
  rateLimiting: { max: 60, timeWindow: '1 minute' },
714
- metrics: { enabled: true, prefix: 'bk_' },
606
+ metrics: { enabled: true },
715
607
  maxIdleMs: 30 * 60 * 1000,
716
608
  triggers,
717
609
  });
718
610
 
719
611
  await server.start();
720
- console.log('Agent server running on :3000');
721
-
722
- // Graceful shutdown
723
612
  process.on('SIGTERM', () => server.stop());
724
613
  ```
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@backendkit-labs/agent-web",
3
- "version": "0.6.4",
4
- "description": "HTTP/WebSocket server layer for agent-core — browser-ready streaming agent sessions",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist/",
9
- "client/",
10
- "README.md"
11
- ],
12
- "scripts": {
13
- "build": "tsc",
14
- "dev": "tsc --watch",
15
- "typecheck": "tsc --noEmit",
16
- "test": "jest",
17
- "prepublishOnly": "npm run build"
18
- },
19
- "peerDependencies": {
20
- "@backendkit-labs/agent-core": ">=0.24.0 <0.38.0"
21
- },
22
- "dependencies": {
23
- "@fastify/websocket": "^11.0.0",
24
- "fastify": "^5.0.0",
25
- "jsonwebtoken": "^9.0.0",
26
- "prom-client": "^15.0.0"
27
- },
28
- "optionalDependencies": {
29
- "@fastify/rate-limit": "^9.0.0",
30
- "bullmq": "^5.0.0",
31
- "node-cron": "^4.2.1"
32
- },
33
- "devDependencies": {
34
- "@backendkit-labs/agent-core": "^0.37.0",
35
- "@types/jest": "^29.5.14",
36
- "@types/jsonwebtoken": "^9.0.0",
37
- "@types/node": "^20.0.0",
38
- "@types/ws": "^8.0.0",
39
- "bullmq": "^5.0.0",
40
- "jest": "^29.7.0",
41
- "ts-jest": "^29.4.11",
42
- "typescript": "^5.4.0",
43
- "ws": "^8.21.0"
44
- },
45
- "engines": {
46
- "node": ">=20.0.0"
47
- },
48
- "publishConfig": {
49
- "access": "public"
50
- }
51
- }
1
+ {
2
+ "name": "@backendkit-labs/agent-web",
3
+ "version": "0.6.6",
4
+ "description": "HTTP/WebSocket server layer for agent-core — browser-ready streaming agent sessions",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "client/",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "typecheck": "tsc --noEmit",
16
+ "test": "jest",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "peerDependencies": {
20
+ "@backendkit-labs/agent-core": ">=0.24.0 <0.41.0"
21
+ },
22
+ "dependencies": {
23
+ "@fastify/websocket": "^11.0.0",
24
+ "fastify": "^5.0.0",
25
+ "jsonwebtoken": "^9.0.0",
26
+ "prom-client": "^15.0.0"
27
+ },
28
+ "optionalDependencies": {
29
+ "@fastify/rate-limit": "^9.0.0",
30
+ "bullmq": "^5.0.0",
31
+ "node-cron": "^4.2.1"
32
+ },
33
+ "devDependencies": {
34
+ "@backendkit-labs/agent-core": "^0.39.0",
35
+ "@types/jest": "^29.5.14",
36
+ "@types/jsonwebtoken": "^9.0.0",
37
+ "@types/node": "^20.0.0",
38
+ "@types/ws": "^8.0.0",
39
+ "bullmq": "^5.0.0",
40
+ "jest": "^29.7.0",
41
+ "ts-jest": "^29.4.11",
42
+ "typescript": "^5.4.0",
43
+ "ws": "^8.21.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.0.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }