@agentorg/computer 0.0.7 → 0.0.9
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 +40 -16
- package/dist/THIRD_PARTY_LICENSES.txt +1 -1
- package/dist/agentcomputer.js +1905 -781
- package/dist/sdk.js +34 -34
- package/dist/types/generated/schema.d.ts +149 -14
- package/dist/types/sdk.d.ts +23 -30
- package/package.json +3 -3
package/dist/sdk.js
CHANGED
|
@@ -24573,6 +24573,18 @@ function removeTrailingSlash(url2) {
|
|
|
24573
24573
|
return url2;
|
|
24574
24574
|
}
|
|
24575
24575
|
|
|
24576
|
+
// src/text.ts
|
|
24577
|
+
var MAX_TEXT = 1e4;
|
|
24578
|
+
function* pieces(text, most = MAX_TEXT) {
|
|
24579
|
+
for (let from = 0; from < text.length; ) {
|
|
24580
|
+
let to = Math.min(from + most, text.length);
|
|
24581
|
+
const last = text.charCodeAt(to - 1);
|
|
24582
|
+
if (to < text.length && last >= 55296 && last <= 56319) to -= 1;
|
|
24583
|
+
yield text.slice(from, to);
|
|
24584
|
+
from = to;
|
|
24585
|
+
}
|
|
24586
|
+
}
|
|
24587
|
+
|
|
24576
24588
|
// src/sdk.ts
|
|
24577
24589
|
var ComputerError = class extends Error {
|
|
24578
24590
|
code;
|
|
@@ -24657,32 +24669,30 @@ async function openLive(session, onMessage) {
|
|
|
24657
24669
|
ws.addEventListener("message", (event) => onMessage(event.data));
|
|
24658
24670
|
return { ws, closed };
|
|
24659
24671
|
}
|
|
24660
|
-
var
|
|
24661
|
-
var RESIZE = 49;
|
|
24662
|
-
var Pty = class {
|
|
24672
|
+
var Terminals = class {
|
|
24663
24673
|
#open;
|
|
24664
24674
|
constructor(open2) {
|
|
24665
24675
|
this.#open = open2;
|
|
24666
24676
|
}
|
|
24667
|
-
/**
|
|
24677
|
+
/**
|
|
24678
|
+
* Each terminal as it starts, writes and exits: those already there first, each with the end of
|
|
24679
|
+
* what it wrote. Operating, the default, can also open a shell and act on any of them.
|
|
24680
|
+
*/
|
|
24668
24681
|
async connect(opts) {
|
|
24669
|
-
const
|
|
24670
|
-
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
const send = (type, text) => {
|
|
24677
|
-
const body = new TextEncoder().encode(text);
|
|
24678
|
-
const frame = new Uint8Array(body.length + 1);
|
|
24679
|
-
frame[0] = type;
|
|
24680
|
-
frame.set(body, 1);
|
|
24681
|
-
ws.send(frame);
|
|
24682
|
-
};
|
|
24682
|
+
const { ws, closed } = await openLive(
|
|
24683
|
+
await this.#open("terminal", opts.mode ?? "operate"),
|
|
24684
|
+
(data) => {
|
|
24685
|
+
if (typeof data === "string") opts.onEvent(JSON.parse(data));
|
|
24686
|
+
}
|
|
24687
|
+
);
|
|
24688
|
+
const send = (input) => ws.send(JSON.stringify(input));
|
|
24683
24689
|
return {
|
|
24684
|
-
|
|
24685
|
-
|
|
24690
|
+
openShell: (columns, rows) => send({ type: "open", columns, rows }),
|
|
24691
|
+
sendInput: (id, text) => {
|
|
24692
|
+
for (const piece of pieces(text)) send({ type: "input", id, text: piece });
|
|
24693
|
+
},
|
|
24694
|
+
resize: (id, columns, rows) => send({ type: "resize", id, columns, rows }),
|
|
24695
|
+
stop: (id) => send({ type: "stop", id }),
|
|
24686
24696
|
close: () => ws.close(),
|
|
24687
24697
|
closed
|
|
24688
24698
|
};
|
|
@@ -24690,17 +24700,8 @@ var Pty = class {
|
|
|
24690
24700
|
};
|
|
24691
24701
|
var Commands = class {
|
|
24692
24702
|
#endpoint;
|
|
24693
|
-
|
|
24694
|
-
constructor(endpoint, open2) {
|
|
24703
|
+
constructor(endpoint) {
|
|
24695
24704
|
this.#endpoint = endpoint;
|
|
24696
|
-
this.#open = open2;
|
|
24697
|
-
}
|
|
24698
|
-
/** Each of the agent's commands as it runs: the ones already heard first, then as they come. */
|
|
24699
|
-
async watch(onEvent) {
|
|
24700
|
-
const { ws, closed } = await openLive(await this.#open("commands", "watch"), (data) => {
|
|
24701
|
-
if (typeof data === "string") onEvent(JSON.parse(data));
|
|
24702
|
-
});
|
|
24703
|
-
return { close: () => ws.close(), closed };
|
|
24704
24705
|
}
|
|
24705
24706
|
async run(cmd, opts = {}) {
|
|
24706
24707
|
let answer = await this.#endpoint.command("exec_command", {
|
|
@@ -24777,7 +24778,7 @@ var Computer = class _Computer {
|
|
|
24777
24778
|
computerID;
|
|
24778
24779
|
commands;
|
|
24779
24780
|
files;
|
|
24780
|
-
|
|
24781
|
+
terminals;
|
|
24781
24782
|
#connection;
|
|
24782
24783
|
#token;
|
|
24783
24784
|
#computer;
|
|
@@ -24788,9 +24789,8 @@ var Computer = class _Computer {
|
|
|
24788
24789
|
this.#token = token;
|
|
24789
24790
|
this.#urls = info.endpoints;
|
|
24790
24791
|
this.#computer = new Endpoint(info.endpoints.computer, token);
|
|
24791
|
-
|
|
24792
|
-
this.
|
|
24793
|
-
this.pty = new Pty(open2);
|
|
24792
|
+
this.commands = new Commands(this.#computer);
|
|
24793
|
+
this.terminals = new Terminals((surface, mode) => this.liveSession(surface, mode));
|
|
24794
24794
|
this.files = new Files(this.#computer);
|
|
24795
24795
|
}
|
|
24796
24796
|
static async create(opts) {
|
|
@@ -146,7 +146,7 @@ export interface paths {
|
|
|
146
146
|
};
|
|
147
147
|
get?: never;
|
|
148
148
|
put?: never;
|
|
149
|
-
/** A one-time ticket to watch or operate one of its surfaces live:
|
|
149
|
+
/** A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, or its browser. Open its url as a WebSocket within 60 seconds. */
|
|
150
150
|
post: operations["createLiveSession"];
|
|
151
151
|
delete?: never;
|
|
152
152
|
options?: never;
|
|
@@ -182,7 +182,7 @@ export interface components {
|
|
|
182
182
|
message: string;
|
|
183
183
|
};
|
|
184
184
|
/**
|
|
185
|
-
* @description How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and
|
|
185
|
+
* @description How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden.
|
|
186
186
|
* @enum {string}
|
|
187
187
|
*/
|
|
188
188
|
Access: "none" | "read" | "files" | "terminal";
|
|
@@ -248,12 +248,12 @@ export interface components {
|
|
|
248
248
|
};
|
|
249
249
|
};
|
|
250
250
|
/**
|
|
251
|
-
* @description
|
|
251
|
+
* @description terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes.
|
|
252
252
|
* @enum {string}
|
|
253
253
|
*/
|
|
254
|
-
Surface: "
|
|
254
|
+
Surface: "terminal" | "files" | "browser";
|
|
255
255
|
/**
|
|
256
|
-
* @description In watch
|
|
256
|
+
* @description In watch you see and don't act: what you send is dropped, except that a files socket may still ask to list, to read and for what changed.
|
|
257
257
|
* @enum {string}
|
|
258
258
|
*/
|
|
259
259
|
LiveMode: "watch" | "operate";
|
|
@@ -262,7 +262,7 @@ export interface components {
|
|
|
262
262
|
protocol: string;
|
|
263
263
|
/** @description Whether it can be operated */
|
|
264
264
|
operate: boolean;
|
|
265
|
-
/** @description Why it can't be opened
|
|
265
|
+
/** @description Why it can't be opened now, if it can't. */
|
|
266
266
|
unavailable?: string;
|
|
267
267
|
};
|
|
268
268
|
NewLiveSession: {
|
|
@@ -316,12 +316,6 @@ export interface components {
|
|
|
316
316
|
label: string;
|
|
317
317
|
selected: boolean;
|
|
318
318
|
}[];
|
|
319
|
-
} | {
|
|
320
|
-
/** @constant */
|
|
321
|
-
type: "driver";
|
|
322
|
-
you: boolean;
|
|
323
|
-
held: boolean;
|
|
324
|
-
agentWaiting: boolean;
|
|
325
319
|
};
|
|
326
320
|
/** @description What an operate socket sends on screencast@1, as JSON text. A watch socket's is dropped. */
|
|
327
321
|
ScreencastInput: {
|
|
@@ -374,11 +368,152 @@ export interface components {
|
|
|
374
368
|
} | {
|
|
375
369
|
/** @constant */
|
|
376
370
|
type: "navigate";
|
|
377
|
-
/** @description A URL */
|
|
371
|
+
/** @description A URL, or back, forward or reload. */
|
|
378
372
|
to: string;
|
|
373
|
+
};
|
|
374
|
+
/** @description What the service sends on a terminal@1 socket, as JSON text, no message over 65,536 bytes. A terminal is a command the agent started or a shell a person opened; its id is the session id the agent's tools use. A new socket is first sent, for each terminal already there, the oldest first, its start, the end of what it wrote (its last 65,536 characters), and its exit if it has ended; then each event as it happens, with nothing to mark where the first ends. The last 64 that ended are among those already there, and one that leaves that list is not told. A character here is a UTF-16 unit. A socket with more than 4 MiB still to be sent to it is closed, with no close frame (1006): one that has stopped reading, or one that is reading while a terminal writes tens of megabytes at once. It isn't an error. Opened again, it is sent every terminal afresh, with the last 65,536 characters each wrote, in place of whatever it held. */
|
|
375
|
+
TerminalEvent: {
|
|
376
|
+
/** @constant */
|
|
377
|
+
type: "start";
|
|
378
|
+
id: string;
|
|
379
|
+
/**
|
|
380
|
+
* @description The way in it came: agent through a tool, person through this surface.
|
|
381
|
+
* @enum {string}
|
|
382
|
+
*/
|
|
383
|
+
by: "agent" | "person";
|
|
384
|
+
/** @description Its command, the first 8,000 characters of it. */
|
|
385
|
+
cmd: string;
|
|
386
|
+
/** @description Its folder, the first 2,000 characters of it. */
|
|
387
|
+
workdir: string;
|
|
388
|
+
/** @description Whether it is on a terminal device, as a person's shell is. One that isn't, as the agent's commands aren't, echoes nothing typed into it and wants a line feed for Enter. Nothing tells the size of one that is. */
|
|
389
|
+
tty: boolean;
|
|
390
|
+
/** Format: date-time */
|
|
391
|
+
at: string;
|
|
392
|
+
} | {
|
|
393
|
+
/** @constant */
|
|
394
|
+
type: "output";
|
|
395
|
+
id: string;
|
|
396
|
+
/**
|
|
397
|
+
* @description Left out for one on a terminal device, which has one stream, and in what a new socket is first sent, where the two are one text.
|
|
398
|
+
* @enum {string}
|
|
399
|
+
*/
|
|
400
|
+
stream?: "stdout" | "stderr";
|
|
401
|
+
text: string;
|
|
402
|
+
} | {
|
|
403
|
+
/** @constant */
|
|
404
|
+
type: "exit";
|
|
405
|
+
id: string;
|
|
406
|
+
exitCode: number;
|
|
407
|
+
/** Format: date-time */
|
|
408
|
+
at: string;
|
|
409
|
+
} | {
|
|
410
|
+
/** @constant */
|
|
411
|
+
type: "refused";
|
|
412
|
+
message: string;
|
|
413
|
+
};
|
|
414
|
+
/** @description What an operate socket sends on terminal@1, as JSON text. One that can't be done, or that is none of these, is answered with refused. What a watch socket sends is dropped. A message over 65,536 bytes closes the socket, so a long text goes as several inputs. */
|
|
415
|
+
TerminalInput: {
|
|
416
|
+
/** @constant */
|
|
417
|
+
type: "open";
|
|
418
|
+
columns: number;
|
|
419
|
+
rows: number;
|
|
420
|
+
} | {
|
|
421
|
+
/** @constant */
|
|
422
|
+
type: "input";
|
|
423
|
+
id: string;
|
|
424
|
+
text: string;
|
|
425
|
+
} | {
|
|
426
|
+
/** @constant */
|
|
427
|
+
type: "resize";
|
|
428
|
+
id: string;
|
|
429
|
+
columns: number;
|
|
430
|
+
rows: number;
|
|
379
431
|
} | {
|
|
380
432
|
/** @constant */
|
|
381
|
-
type: "
|
|
433
|
+
type: "stop";
|
|
434
|
+
id: string;
|
|
435
|
+
};
|
|
436
|
+
/** @description What a socket sends on files@1, as JSON text. Each has an id of its own choosing, which the answer repeats; answers come as they are ready, in any order, and ids aren't checked for repeats. A request still unanswered when the socket closes gets no answer. A path is from the workspace, with / between its parts, or absolute inside it, and nothing outside the workspace is reached. A binary message that follows no write closes the socket, and so does any message over 1,048,576 bytes. */
|
|
437
|
+
FilesRequest: {
|
|
438
|
+
/** @constant */
|
|
439
|
+
type: "list";
|
|
440
|
+
id: string;
|
|
441
|
+
/** @description The workspace itself, if left out. */
|
|
442
|
+
path?: string;
|
|
443
|
+
} | {
|
|
444
|
+
/** @constant */
|
|
445
|
+
type: "read";
|
|
446
|
+
id: string;
|
|
447
|
+
path: string;
|
|
448
|
+
offset: number;
|
|
449
|
+
length: number;
|
|
450
|
+
} | {
|
|
451
|
+
/** @constant */
|
|
452
|
+
type: "changed";
|
|
453
|
+
id: string;
|
|
454
|
+
/** Format: date-time */
|
|
455
|
+
since: string;
|
|
456
|
+
} | {
|
|
457
|
+
/** @constant */
|
|
458
|
+
type: "write";
|
|
459
|
+
id: string;
|
|
460
|
+
path: string;
|
|
461
|
+
offset: number;
|
|
462
|
+
length: number;
|
|
463
|
+
last: boolean;
|
|
464
|
+
};
|
|
465
|
+
/** @description What the service sends on files@1, as JSON text, each with the id it answers. Nothing comes between a data message and its bytes. */
|
|
466
|
+
FilesAnswer: {
|
|
467
|
+
/** @constant */
|
|
468
|
+
type: "entries";
|
|
469
|
+
id: string;
|
|
470
|
+
path: string;
|
|
471
|
+
entries: {
|
|
472
|
+
name: string;
|
|
473
|
+
/** @enum {string} */
|
|
474
|
+
kind: "file" | "folder" | "link";
|
|
475
|
+
size: number;
|
|
476
|
+
/** Format: date-time */
|
|
477
|
+
modifiedAt: string;
|
|
478
|
+
}[];
|
|
479
|
+
/** @description Whether the folder has more than these. */
|
|
480
|
+
more: boolean;
|
|
481
|
+
} | {
|
|
482
|
+
/** @constant */
|
|
483
|
+
type: "data";
|
|
484
|
+
id: string;
|
|
485
|
+
path: string;
|
|
486
|
+
offset: number;
|
|
487
|
+
/** @description How many bytes follow. */
|
|
488
|
+
length: number;
|
|
489
|
+
size: number;
|
|
490
|
+
/** Format: date-time */
|
|
491
|
+
modifiedAt: string;
|
|
492
|
+
} | {
|
|
493
|
+
/** @constant */
|
|
494
|
+
type: "changes";
|
|
495
|
+
id: string;
|
|
496
|
+
/** Format: date-time */
|
|
497
|
+
since: string;
|
|
498
|
+
changes: {
|
|
499
|
+
path: string;
|
|
500
|
+
size: number;
|
|
501
|
+
/** Format: date-time */
|
|
502
|
+
modifiedAt: string;
|
|
503
|
+
}[];
|
|
504
|
+
/** @description Whether more than these changed. */
|
|
505
|
+
more: boolean;
|
|
506
|
+
} | {
|
|
507
|
+
/** @constant */
|
|
508
|
+
type: "written";
|
|
509
|
+
id: string;
|
|
510
|
+
path: string;
|
|
511
|
+
size: number;
|
|
512
|
+
} | {
|
|
513
|
+
/** @constant */
|
|
514
|
+
type: "refused";
|
|
515
|
+
id: string;
|
|
516
|
+
message: string;
|
|
382
517
|
};
|
|
383
518
|
ComputerLogs: {
|
|
384
519
|
logs: {
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -7,24 +7,11 @@ export type ComputerLog = Schemas["ComputerLogs"]["logs"][number];
|
|
|
7
7
|
export type LiveSurface = Schemas["Surface"];
|
|
8
8
|
export type LiveMode = Schemas["LiveMode"];
|
|
9
9
|
export type LiveSession = Schemas["LiveSession"];
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
workdir: string;
|
|
16
|
-
at: string;
|
|
17
|
-
} | {
|
|
18
|
-
type: "output";
|
|
19
|
-
id: string;
|
|
20
|
-
stream: "stdout" | "stderr";
|
|
21
|
-
text: string;
|
|
22
|
-
} | {
|
|
23
|
-
type: "exit";
|
|
24
|
-
id: string;
|
|
25
|
-
exitCode: number;
|
|
26
|
-
at: string;
|
|
27
|
-
};
|
|
10
|
+
/**
|
|
11
|
+
* What happens to a terminal, as it happens: the agent's command or a person's shell. And
|
|
12
|
+
* `refused`, with why, when what this connection asked wasn't done.
|
|
13
|
+
*/
|
|
14
|
+
export type TerminalEvent = Schemas["TerminalEvent"];
|
|
28
15
|
export interface LiveHandle {
|
|
29
16
|
close(): void;
|
|
30
17
|
/** Resolves when the socket closes, with why. */
|
|
@@ -33,9 +20,14 @@ export interface LiveHandle {
|
|
|
33
20
|
reason: string;
|
|
34
21
|
}>;
|
|
35
22
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
/** What an operating connection can do. What can't be done comes back as a `refused` event. */
|
|
24
|
+
export interface TerminalsHandle extends LiveHandle {
|
|
25
|
+
/** Start a shell of your own. It arrives as a `start` event, which carries its id. */
|
|
26
|
+
openShell(columns: number, rows: number): void;
|
|
27
|
+
/** Type into a running terminal, the agent's included: a long text as several messages. */
|
|
28
|
+
sendInput(id: string, text: string): void;
|
|
29
|
+
resize(id: string, columns: number, rows: number): void;
|
|
30
|
+
stop(id: string): void;
|
|
39
31
|
}
|
|
40
32
|
export interface ConnectionOpts {
|
|
41
33
|
/** The control token; `AGENTCOMPUTER_API_KEY` by default. */
|
|
@@ -82,22 +74,23 @@ declare class Endpoint {
|
|
|
82
74
|
close(): Promise<void>;
|
|
83
75
|
}
|
|
84
76
|
type Opener = (surface: LiveSurface, mode: LiveMode) => Promise<LiveSession>;
|
|
85
|
-
/**
|
|
86
|
-
declare class
|
|
77
|
+
/** Every terminal on the computer: the agent's commands and a person's shells alike. */
|
|
78
|
+
declare class Terminals {
|
|
87
79
|
#private;
|
|
88
80
|
constructor(open: Opener);
|
|
89
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Each terminal as it starts, writes and exits: those already there first, each with the end of
|
|
83
|
+
* what it wrote. Operating, the default, can also open a shell and act on any of them.
|
|
84
|
+
*/
|
|
90
85
|
connect(opts: {
|
|
91
|
-
|
|
86
|
+
onEvent: (event: TerminalEvent) => void;
|
|
92
87
|
mode?: LiveMode;
|
|
93
|
-
}): Promise<
|
|
88
|
+
}): Promise<TerminalsHandle>;
|
|
94
89
|
}
|
|
95
90
|
/** Commands on a computer with the terminal level. */
|
|
96
91
|
declare class Commands {
|
|
97
92
|
#private;
|
|
98
|
-
constructor(endpoint: Endpoint
|
|
99
|
-
/** Each of the agent's commands as it runs: the ones already heard first, then as they come. */
|
|
100
|
-
watch(onEvent: (event: CommandEvent) => void): Promise<LiveHandle>;
|
|
93
|
+
constructor(endpoint: Endpoint);
|
|
101
94
|
/** Run a command to its end. */
|
|
102
95
|
run(cmd: string, opts?: {
|
|
103
96
|
cwd?: string;
|
|
@@ -144,7 +137,7 @@ export declare class Computer {
|
|
|
144
137
|
readonly computerID: string;
|
|
145
138
|
readonly commands: Commands;
|
|
146
139
|
readonly files: Files;
|
|
147
|
-
readonly
|
|
140
|
+
readonly terminals: Terminals;
|
|
148
141
|
private constructor();
|
|
149
142
|
static create(opts: CreateOpts): Promise<Computer>;
|
|
150
143
|
/** A computer made earlier. Its token was shown when it was made, and only then. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentorg/computer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Computers for agents, as a service: a terminal, files and a browser over MCP",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
"test": "npm run build && node --test --test-timeout=60000 \"out/test/**/*.test.js\""
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@playwright/mcp": "0.0.84",
|
|
32
31
|
"playwright-core": "1.65.0-alpha-2026-10-30"
|
|
33
32
|
},
|
|
34
33
|
"devDependencies": {
|
|
35
|
-
"@agentorg/exec": "0.0.
|
|
34
|
+
"@agentorg/exec": "0.0.5",
|
|
36
35
|
"@biomejs/biome": "2.6.5",
|
|
37
36
|
"@modelcontextprotocol/client": "2.4.0",
|
|
38
37
|
"@modelcontextprotocol/node": "2.1.2",
|
|
39
38
|
"@modelcontextprotocol/server": "2.4.0",
|
|
39
|
+
"@playwright/mcp": "0.0.84",
|
|
40
40
|
"@types/node": "^22.20.5",
|
|
41
41
|
"@types/ws": "8.18.2",
|
|
42
42
|
"ajv": "8.20.0",
|