@agentorg/computer 0.0.3 → 0.0.5

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/dist/sdk.js CHANGED
@@ -24640,10 +24640,67 @@ var Endpoint = class {
24640
24640
  await client?.close();
24641
24641
  }
24642
24642
  };
24643
+ async function openLive(session, onMessage) {
24644
+ const ws = new WebSocket(session.url);
24645
+ ws.binaryType = "arraybuffer";
24646
+ const closed = new Promise((done) => {
24647
+ ws.addEventListener("close", (event) => done({ code: event.code, reason: event.reason }));
24648
+ });
24649
+ await new Promise((done, failed) => {
24650
+ ws.addEventListener("open", () => done(), { once: true });
24651
+ ws.addEventListener(
24652
+ "error",
24653
+ () => failed(new ComputerError(502, "The live session didn't open.")),
24654
+ { once: true }
24655
+ );
24656
+ });
24657
+ ws.addEventListener("message", (event) => onMessage(event.data));
24658
+ return { ws, closed };
24659
+ }
24660
+ var INPUT = 48;
24661
+ var RESIZE = 49;
24662
+ var Pty = class {
24663
+ #open;
24664
+ constructor(open2) {
24665
+ this.#open = open2;
24666
+ }
24667
+ /** Attach to its shell, starting one if none is running. Every attachment shares it. */
24668
+ async connect(opts) {
24669
+ const mode = opts.mode ?? "operate";
24670
+ const decoder = new TextDecoder();
24671
+ const { ws, closed } = await openLive(await this.#open("terminal", mode), (data) => {
24672
+ if (typeof data === "string") return;
24673
+ const frame = new Uint8Array(data);
24674
+ if (frame[0] === INPUT) opts.onData(decoder.decode(frame.subarray(1), { stream: true }));
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
+ };
24683
+ return {
24684
+ sendInput: (data) => send(INPUT, data),
24685
+ resize: (columns, rows) => send(RESIZE, JSON.stringify({ columns, rows })),
24686
+ close: () => ws.close(),
24687
+ closed
24688
+ };
24689
+ }
24690
+ };
24643
24691
  var Commands = class {
24644
24692
  #endpoint;
24645
- constructor(endpoint) {
24693
+ #open;
24694
+ constructor(endpoint, open2) {
24646
24695
  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 };
24647
24704
  }
24648
24705
  async run(cmd, opts = {}) {
24649
24706
  let answer = await this.#endpoint.command("exec_command", {
@@ -24720,6 +24777,7 @@ var Computer = class _Computer {
24720
24777
  computerID;
24721
24778
  commands;
24722
24779
  files;
24780
+ pty;
24723
24781
  #connection;
24724
24782
  #token;
24725
24783
  #computer;
@@ -24730,7 +24788,9 @@ var Computer = class _Computer {
24730
24788
  this.#token = token;
24731
24789
  this.#urls = info.endpoints;
24732
24790
  this.#computer = new Endpoint(info.endpoints.computer, token);
24733
- this.commands = new Commands(this.#computer);
24791
+ const open2 = (surface, mode) => this.liveSession(surface, mode);
24792
+ this.commands = new Commands(this.#computer, open2);
24793
+ this.pty = new Pty(open2);
24734
24794
  this.files = new Files(this.#computer);
24735
24795
  }
24736
24796
  static async create(opts) {
@@ -24790,6 +24850,15 @@ var Computer = class _Computer {
24790
24850
  static async getLogs(computerID, opts = {}) {
24791
24851
  return answered(await apiOf(opts).GET("/v1/computers/{computerID}/logs", pathOf(computerID))).logs;
24792
24852
  }
24853
+ /** A one-time ticket to open one of its surfaces live, within a minute. */
24854
+ static async createLiveSession(computerID, session, opts = {}) {
24855
+ return answered(
24856
+ await apiOf(opts).POST("/v1/computers/{computerID}/live", {
24857
+ ...pathOf(computerID),
24858
+ body: { surface: session.surface, mode: session.mode ?? "watch" }
24859
+ })
24860
+ );
24861
+ }
24793
24862
  static async kill(computerID, opts = {}) {
24794
24863
  answered(await apiOf(opts).DELETE("/v1/computers/{computerID}", pathOf(computerID)));
24795
24864
  }
@@ -24819,6 +24888,9 @@ var Computer = class _Computer {
24819
24888
  start(opts = {}) {
24820
24889
  return _Computer.start(this.computerID, { ...this.#connection, ...opts });
24821
24890
  }
24891
+ liveSession(surface, mode = "watch") {
24892
+ return _Computer.createLiveSession(this.computerID, { surface, mode }, this.#connection);
24893
+ }
24822
24894
  getLogs() {
24823
24895
  return _Computer.getLogs(this.computerID, this.#connection);
24824
24896
  }
@@ -135,6 +135,44 @@ export interface paths {
135
135
  patch?: never;
136
136
  trace?: never;
137
137
  };
138
+ "/v1/computers/{computerID}/live": {
139
+ parameters: {
140
+ query?: never;
141
+ header?: never;
142
+ path: {
143
+ computerID: components["parameters"]["computerID"];
144
+ };
145
+ cookie?: never;
146
+ };
147
+ get?: never;
148
+ put?: never;
149
+ /** A one-time ticket to watch or operate one of its surfaces live: the agent's commands as they run, or a shell of your own. Open its url as a WebSocket within 60 seconds. */
150
+ post: operations["createLiveSession"];
151
+ delete?: never;
152
+ options?: never;
153
+ head?: never;
154
+ patch?: never;
155
+ trace?: never;
156
+ };
157
+ "/v1/live/{liveID}": {
158
+ parameters: {
159
+ query?: never;
160
+ header?: never;
161
+ path: {
162
+ liveID: string;
163
+ };
164
+ cookie?: never;
165
+ };
166
+ /** Open a live session as a WebSocket. The ticket in its path is its authority and works once, so it takes no token. A request with an Origin header is refused unless the service was told to allow that origin. */
167
+ get: operations["openLiveSession"];
168
+ put?: never;
169
+ post?: never;
170
+ delete?: never;
171
+ options?: never;
172
+ head?: never;
173
+ patch?: never;
174
+ trace?: never;
175
+ };
138
176
  }
139
177
  export type webhooks = Record<string, never>;
140
178
  export interface components {
@@ -202,11 +240,146 @@ export interface components {
202
240
  };
203
241
  ComputerDetail: components["schemas"]["Computer"] & {
204
242
  uptimeSeconds: number;
243
+ /** @description What can be opened live while it runs. */
244
+ surfaces: components["schemas"]["SurfaceInfo"][];
205
245
  tools: {
206
246
  computer: string[];
207
247
  browser: string[];
208
248
  };
209
249
  };
250
+ /**
251
+ * @description commands: the agent's commands as they run, read-only, in commands@1. terminal: a shell of your own in its workspace, as its user, in pty@1. 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. Operating it makes you the driver: the agent's browser calls wait up to 25 seconds while you are, then answer that a person has or had it. You stop being the driver when you send release, close the socket, or send nothing for 60 seconds.
252
+ * @enum {string}
253
+ */
254
+ Surface: "commands" | "terminal" | "browser";
255
+ /**
256
+ * @description In watch, what you send is dropped.
257
+ * @enum {string}
258
+ */
259
+ LiveMode: "watch" | "operate";
260
+ SurfaceInfo: {
261
+ surface: components["schemas"]["Surface"];
262
+ protocol: string;
263
+ /** @description Whether it can be operated */
264
+ operate: boolean;
265
+ /** @description Why it can't be opened here */
266
+ unavailable?: string;
267
+ };
268
+ NewLiveSession: {
269
+ surface: components["schemas"]["Surface"];
270
+ /** @default watch */
271
+ mode: components["schemas"]["LiveMode"];
272
+ };
273
+ LiveSession: {
274
+ /** @description The WebSocket to open */
275
+ url: string;
276
+ protocol: string;
277
+ surface: components["schemas"]["Surface"];
278
+ mode: components["schemas"]["LiveMode"];
279
+ /** Format: date-time */
280
+ expiresAt: string;
281
+ };
282
+ /** @description What the service sends on a screencast@1 socket, as JSON text. */
283
+ ScreencastEvent: {
284
+ /** @constant */
285
+ type: "tabs";
286
+ tabs: {
287
+ id: number;
288
+ url: string;
289
+ title: string;
290
+ }[];
291
+ shown: number | null;
292
+ } | {
293
+ /** @constant */
294
+ type: "viewport";
295
+ width: number;
296
+ height: number;
297
+ } | {
298
+ /** @constant */
299
+ type: "dialog";
300
+ open: boolean;
301
+ /** @enum {string} */
302
+ kind?: "alert" | "confirm" | "prompt" | "beforeunload";
303
+ message?: string;
304
+ defaultValue?: string;
305
+ } | {
306
+ /** @constant */
307
+ type: "file-chooser";
308
+ open: boolean;
309
+ multiple?: boolean;
310
+ } | {
311
+ /** @constant */
312
+ type: "choice";
313
+ multiple: boolean;
314
+ options: {
315
+ value: string;
316
+ label: string;
317
+ selected: boolean;
318
+ }[];
319
+ } | {
320
+ /** @constant */
321
+ type: "driver";
322
+ you: boolean;
323
+ held: boolean;
324
+ agentWaiting: boolean;
325
+ };
326
+ /** @description What an operate socket sends on screencast@1, as JSON text. A watch socket's is dropped. */
327
+ ScreencastInput: {
328
+ /** @constant */
329
+ type: "mouse";
330
+ /** @enum {string} */
331
+ action: "move" | "down" | "up" | "wheel";
332
+ x: number;
333
+ y: number;
334
+ /** @enum {string} */
335
+ button?: "left" | "right" | "middle";
336
+ deltaX?: number;
337
+ deltaY?: number;
338
+ } | {
339
+ /** @constant */
340
+ type: "key";
341
+ /** @enum {string} */
342
+ action: "down" | "up";
343
+ /** @description A key name */
344
+ key: string;
345
+ } | {
346
+ /** @constant */
347
+ type: "text";
348
+ text: string;
349
+ } | {
350
+ /** @constant */
351
+ type: "dialog";
352
+ accept: boolean;
353
+ text?: string;
354
+ } | {
355
+ /** @constant */
356
+ type: "files";
357
+ files: {
358
+ name: string;
359
+ mimeType?: string;
360
+ /** @description Base64. */
361
+ data: string;
362
+ }[];
363
+ } | {
364
+ /** @constant */
365
+ type: "choice";
366
+ values: string[];
367
+ } | {
368
+ /** @constant */
369
+ type: "tab";
370
+ /** @enum {string} */
371
+ action: "show" | "open" | "close";
372
+ id?: number;
373
+ url?: string;
374
+ } | {
375
+ /** @constant */
376
+ type: "navigate";
377
+ /** @description A URL */
378
+ to: string;
379
+ } | {
380
+ /** @constant */
381
+ type: "release";
382
+ };
210
383
  ComputerLogs: {
211
384
  logs: {
212
385
  /** Format: date-time */
@@ -476,4 +649,57 @@ export interface operations {
476
649
  404: components["responses"]["Error"];
477
650
  };
478
651
  };
652
+ createLiveSession: {
653
+ parameters: {
654
+ query?: never;
655
+ header?: never;
656
+ path: {
657
+ computerID: components["parameters"]["computerID"];
658
+ };
659
+ cookie?: never;
660
+ };
661
+ requestBody: {
662
+ content: {
663
+ "application/json": components["schemas"]["NewLiveSession"];
664
+ };
665
+ };
666
+ responses: {
667
+ /** @description The ticket. */
668
+ 201: {
669
+ headers: {
670
+ [name: string]: unknown;
671
+ };
672
+ content: {
673
+ "application/json": components["schemas"]["LiveSession"];
674
+ };
675
+ };
676
+ 400: components["responses"]["Error"];
677
+ 401: components["responses"]["Unauthorized"];
678
+ 404: components["responses"]["Error"];
679
+ 409: components["responses"]["Error"];
680
+ };
681
+ };
682
+ openLiveSession: {
683
+ parameters: {
684
+ query?: never;
685
+ header?: never;
686
+ path: {
687
+ liveID: string;
688
+ };
689
+ cookie?: never;
690
+ };
691
+ requestBody?: never;
692
+ responses: {
693
+ /** @description The socket */
694
+ 101: {
695
+ headers: {
696
+ [name: string]: unknown;
697
+ };
698
+ content?: never;
699
+ };
700
+ 403: components["responses"]["Error"];
701
+ 404: components["responses"]["Error"];
702
+ 426: components["responses"]["Error"];
703
+ };
704
+ };
479
705
  }
@@ -4,6 +4,39 @@ export type ComputerInfo = Schemas["Computer"];
4
4
  export type ComputerDetail = Schemas["ComputerDetail"];
5
5
  export type ServiceInfo = Schemas["ServiceInfo"];
6
6
  export type ComputerLog = Schemas["ComputerLogs"]["logs"][number];
7
+ export type LiveSurface = Schemas["Surface"];
8
+ export type LiveMode = Schemas["LiveMode"];
9
+ export type LiveSession = Schemas["LiveSession"];
10
+ /** What happens to one of the agent's commands, as it happens. */
11
+ export type CommandEvent = {
12
+ type: "start";
13
+ id: string;
14
+ cmd: string;
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
+ };
28
+ export interface LiveHandle {
29
+ close(): void;
30
+ /** Resolves when the socket closes, with why. */
31
+ closed: Promise<{
32
+ code: number;
33
+ reason: string;
34
+ }>;
35
+ }
36
+ export interface PtyHandle extends LiveHandle {
37
+ sendInput(data: string): void;
38
+ resize(columns: number, rows: number): void;
39
+ }
7
40
  export interface ConnectionOpts {
8
41
  /** The control token; `AGENTCOMPUTER_API_KEY` by default. */
9
42
  apiKey?: string;
@@ -48,10 +81,23 @@ declare class Endpoint {
48
81
  command(name: string, args: Record<string, unknown>): Promise<CommandAnswer>;
49
82
  close(): Promise<void>;
50
83
  }
84
+ type Opener = (surface: LiveSurface, mode: LiveMode) => Promise<LiveSession>;
85
+ /** A shell of your own on the computer, beside the agent's commands. */
86
+ declare class Pty {
87
+ #private;
88
+ constructor(open: Opener);
89
+ /** Attach to its shell, starting one if none is running. Every attachment shares it. */
90
+ connect(opts: {
91
+ onData: (data: string) => void;
92
+ mode?: LiveMode;
93
+ }): Promise<PtyHandle>;
94
+ }
51
95
  /** Commands on a computer with the terminal level. */
52
96
  declare class Commands {
53
97
  #private;
54
- constructor(endpoint: Endpoint);
98
+ constructor(endpoint: Endpoint, open: Opener);
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>;
55
101
  /** Run a command to its end. */
56
102
  run(cmd: string, opts?: {
57
103
  cwd?: string;
@@ -98,6 +144,7 @@ export declare class Computer {
98
144
  readonly computerID: string;
99
145
  readonly commands: Commands;
100
146
  readonly files: Files;
147
+ readonly pty: Pty;
101
148
  private constructor();
102
149
  static create(opts: CreateOpts): Promise<Computer>;
103
150
  /** A computer made earlier. Its token was shown when it was made, and only then. */
@@ -116,6 +163,11 @@ export declare class Computer {
116
163
  timeoutMs?: number;
117
164
  }): Promise<ComputerInfo>;
118
165
  static getLogs(computerID: string, opts?: ConnectionOpts): Promise<ComputerLog[]>;
166
+ /** A one-time ticket to open one of its surfaces live, within a minute. */
167
+ static createLiveSession(computerID: string, session: {
168
+ surface: LiveSurface;
169
+ mode?: LiveMode;
170
+ }, opts?: ConnectionOpts): Promise<LiveSession>;
119
171
  static kill(computerID: string, opts?: ConnectionOpts): Promise<void>;
120
172
  /** The endpoint to give an MCP client. */
121
173
  getMcpUrl(endpoint?: "computer" | "browser"): string;
@@ -134,6 +186,7 @@ export declare class Computer {
134
186
  endpoints: components["schemas"]["Endpoints"];
135
187
  } & {
136
188
  uptimeSeconds: number;
189
+ surfaces: components["schemas"]["SurfaceInfo"][];
137
190
  tools: {
138
191
  computer: string[];
139
192
  browser: string[];
@@ -178,6 +231,13 @@ export declare class Computer {
178
231
  metadata: components["schemas"]["Metadata"];
179
232
  endpoints: components["schemas"]["Endpoints"];
180
233
  }>;
234
+ liveSession(surface: LiveSurface, mode?: LiveMode): Promise<{
235
+ url: string;
236
+ protocol: string;
237
+ surface: components["schemas"]["Surface"];
238
+ mode: components["schemas"]["LiveMode"];
239
+ expiresAt: string;
240
+ }>;
181
241
  getLogs(): Promise<{
182
242
  timestamp: string;
183
243
  line: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/computer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
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,19 +28,22 @@
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"
31
+ "@playwright/mcp": "0.0.84",
32
+ "playwright-core": "1.65.0-alpha-2026-10-30"
32
33
  },
33
34
  "devDependencies": {
34
- "@agentorg/exec": "0.0.2",
35
+ "@agentorg/exec": "0.0.3",
35
36
  "@biomejs/biome": "2.6.5",
36
37
  "@modelcontextprotocol/client": "2.4.0",
37
38
  "@modelcontextprotocol/node": "2.1.2",
38
39
  "@modelcontextprotocol/server": "2.4.0",
39
40
  "@types/node": "^22.20.5",
41
+ "@types/ws": "8.18.2",
40
42
  "ajv": "8.20.0",
41
43
  "esbuild": "0.28.2",
42
44
  "openapi-fetch": "0.17.0",
43
45
  "typescript": "^7.0.2",
46
+ "ws": "8.22.2",
44
47
  "yaml": "2.9.1",
45
48
  "zod": "^4.6.7"
46
49
  },
@@ -49,5 +52,11 @@
49
52
  "types": "./dist/types/sdk.d.ts",
50
53
  "default": "./dist/sdk.js"
51
54
  }
55
+ },
56
+ "optionalDependencies": {
57
+ "node-pty": "1.1.0"
58
+ },
59
+ "allowScripts": {
60
+ "node-pty@1.1.0": true
52
61
  }
53
62
  }