@agentorg/computer 0.0.16 → 0.0.18

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 CHANGED
@@ -28,10 +28,12 @@ Isolation is chosen for each computer when it is made, and it keeps it until it
28
28
  A computer in a container:
29
29
 
30
30
  - Its image holds AgentExec, Playwright MCP and a headless Chromium. It is `ghcr.io/uniac-ai/agentcomputer/computer` at the service's version; `--image` names another.
31
- - `--workspace` is mounted at `/workspace`, read-only at `--access read`, and nothing else of the machine is visible. The computer is told that folder's path on the machine and the machine's name, which `about` answers. Docker has to be able to share that folder. Without `--workspace`, the service makes one.
31
+ - `--workspace` is mounted at `/workspace`, read-only at `--access read`, and nothing else of the machine is visible. The computer is told that folder's path on the machine and the machine's name, which `about` answers. Docker has to be able to share that folder, and the data folder, where its home is. Without `--workspace`, the service makes one.
32
32
  - The image is fetched the first time it is needed, which can take minutes: when the service starts if its own isolation is `container`, and otherwise when the first computer in a container is made, a call that then waits for it. `docker pull` it beforehand to spare that wait.
33
33
  - It runs as the user who started the service, with no capabilities.
34
- - `stop` stops the container and keeps it, so what was installed in it is there at `start`. `kill` removes it. Starting a stopped computer uses the image it was made from.
34
+ - Its home, `/home/computer`, is a folder of the machine's, in the computer's own folder in the data folder, mounted as the workspace is. What the agent keeps there stays, the browser's profile with its sign-ins among it. `stop` stops the container and keeps it, and `kill` removes it with the computer's folder.
35
+ - **After an update,** a computer in a container is made again at its first start under a new version of the service, or under another `--image`: its container is removed and made again from the service's image. It keeps its workspace, its home, and its id, token, endpoints and settings. It loses its running commands, as at any restart, and everything else in the container: software installed there, and files outside `/workspace` and `/home/computer`, such as `/tmp`. Keep what must last in those two folders, or in an image of your own. Every computer's answer says the `version` of the program it runs.
36
+ - A container made before 0.0.17 keeps its home inside it. At its first remake the home is copied out. If that fails, the container is kept and started as it was, its `logs` say why, and the copy is tried again at its next start. Until it is made again, its `version` is `null`.
35
37
 
36
38
  ## Access
37
39
 
@@ -42,7 +44,7 @@ A computer in a container:
42
44
  | `terminal` | Those, and `exec_command`, `write_stdin`, `list_sessions` |
43
45
  | `none` | No `computer` endpoint; needs `--browser` |
44
46
 
45
- `read` answers a text file as numbered lines, and a PNG, JPEG, GIF or WebP of up to 3 MiB as the picture itself: an image an MCP client can pass on to a model. The browser's `browser_take_screenshot` answers an image too, but not one of more than 3 MiB, which a `fullPage` screenshot of a long page can be many times over: it answers an error that gives the size and says to take it again without `fullPage`, or of one element. The most counts bytes only: a model may still refuse a smaller picture for its height or width. A container made by an earlier version keeps the program it was made with: it refuses a picture as a binary file, and passes on a screenshot of any size, until it is killed and made again.
47
+ `read` answers a text file as numbered lines, and a PNG, JPEG, GIF or WebP of up to 3 MiB as the picture itself: an image an MCP client can pass on to a model. The browser's `browser_take_screenshot` answers an image too, but not one of more than 3 MiB, which a `fullPage` screenshot of a long page can be many times over: it answers an error that gives the size and says to take it again without `fullPage`, or of one element. The most counts bytes only: a model may still refuse a smaller picture for its height or width.
46
48
 
47
49
  `about` tells the agent what a person looking at the computer reads. It takes nothing and answers JSON text, `{"rows": [...]}`, each row a `label` and a `value`, with `"path": true` on a value that is a path and `"caution": true` on one to be read as a caution; a row that is neither has neither. The rows come in this order:
48
50
 
@@ -58,13 +60,13 @@ A computer in a container:
58
60
 
59
61
  - **In a container** the agent's commands find the folder somewhere else, so `Folder` says both and is not marked as a path: `/home/me/Research on the machine, mounted at /workspace in it`. CPU and Memory there are the computer's own; with no isolation they are the whole machine's.
60
62
  - **The last three** are a reading of its load taken over half a second when it is asked, in the same way the `load` surface reads. They come together, or not at all where a reading can't be taken.
61
- - **Not every computer has it.** One with no `computer` endpoint has no `about`. One in a container made by an earlier version keeps the program it was made with: `about` is listed among its tools and isn't there until it is killed and made again.
63
+ - **Not every computer has it.** One with no `computer` endpoint has no `about`.
62
64
 
63
- A client that calls a computer's tools can put the commands it starts in a group, and stop them together. At `--access terminal` it sends a word as `agentexec/group` in the `_meta` of an `exec_command` call, and the request `agentexec/group/stop` with `{"group": "<word>"}` on the `computer` endpoint, which ends every running command of that group and answers `{"stopped": <how many>}`. Giving up a call does not stop its command; only that request does. A shell a person opened is in no group. This is AgentExec's, described in [its README](https://github.com/uniac-ai/AgentExec#sessions), and the service passes both through, with no isolation and in a container. A container made by an earlier version has neither until it is killed and made again.
65
+ A client that calls a computer's tools can put the commands it starts in a group, and stop them together. At `--access terminal` it sends a word as `agentexec/group` in the `_meta` of an `exec_command` call, and the request `agentexec/group/stop` with `{"group": "<word>"}` on the `computer` endpoint, which ends every running command of that group and answers `{"stopped": <how many>}`. Giving up a call does not stop its command; only that request does. A shell a person opened is in no group. This is AgentExec's, described in [its README](https://github.com/uniac-ai/AgentExec#sessions), and the service passes both through, with no isolation and in a container.
64
66
 
65
67
  The file tools stay inside the workspace. That's a check, not a boundary. Below `terminal`, the browser's `browser_run_code_unsafe` tool is hidden and refused, because it would otherwise be a terminal. The browser's endpoint takes one JSON-RPC request in each POST, as a JSON object, and refuses anything else with 400, a batch among them: it checks each request before passing it on, and passes on only what it read. The service enforces all of this, whatever the client does.
66
68
 
67
- Before 0.0.14 the browser's endpoint passed on a body it couldn't read as one request, unchecked. So on a computer made with `--access none`, `read` or `files` and `--browser`, a caller holding that computer's token could run code in its browser by sending the call in a batch or behind a byte order mark. It needed the computer's own token and a client that sends such a body, which MCP clients don't. 0.0.14 fixes it; a computer in a container made by an earlier version keeps the fault until it is killed and made again.
69
+ Before 0.0.14 the browser's endpoint passed on a body it couldn't read as one request, unchecked. So on a computer made with `--access none`, `read` or `files` and `--browser`, a caller holding that computer's token could run code in its browser by sending the call in a batch or behind a byte order mark. It needed the computer's own token and a client that sends such a body, which MCP clients don't. 0.0.14 fixes it; a computer in a container made by an earlier version keeps the fault until its container is made again.
68
70
 
69
71
  `--browser` drives the Google Chrome installed on the machine, in a profile of the computer's own. `serve --browser-name` and `--browser-path` choose another. The browser has no debug port: a small host process starts it and shares it on a Unix socket in a folder only its user can enter, which the agent's tools and the live view both use. Playwright MCP, which serves the tools, listens on a Unix socket in a second such folder, and no port either. Only the service's user can reach them; callers go through the computer's `browser` endpoint and its token. It lives as long as its computer does: a client that ends its session or goes away leaves the browser and its pages as they are, and the agent's `browser_close` is refused. Only an agent with `--access terminal` can stop it, with `browser_run_code_unsafe` or from its terminal.
70
72
 
@@ -3875,7 +3875,7 @@ async function serveBrowserMcp(options) {
3875
3875
 
3876
3876
  // src/container.ts
3877
3877
  import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
3878
- import { readFileSync as readFileSync3 } from "node:fs";
3878
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync4 } from "node:fs";
3879
3879
  import { connect } from "node:net";
3880
3880
  import { hostname as hostname4 } from "node:os";
3881
3881
  import { join as join8 } from "node:path";
@@ -67031,6 +67031,7 @@ var BrowserLive = class {
67031
67031
  } catch {
67032
67032
  return;
67033
67033
  }
67034
+ if (typeof message !== "object" || message === null) return;
67034
67035
  this.#input(ws, viewer, message);
67035
67036
  });
67036
67037
  this.#begin().then(
@@ -67957,6 +67958,33 @@ var ProcessRuntime = class {
67957
67958
  }
67958
67959
  };
67959
67960
 
67961
+ // src/handlers.ts
67962
+ var VERSION2 = "0.0.18";
67963
+ function createHandlers(computers) {
67964
+ return {
67965
+ getInfo: async () => ({
67966
+ version: VERSION2,
67967
+ isolation: computers.isolation,
67968
+ isolationsAvailable: await computers.available()
67969
+ }),
67970
+ listComputers: async ({ state }) => computers.list(state),
67971
+ createComputer: (input2) => computers.create(input2),
67972
+ getComputer: async ({ computerID }) => computers.get(computerID),
67973
+ killComputer: async ({ computerID }) => {
67974
+ await computers.kill(computerID);
67975
+ return void 0;
67976
+ },
67977
+ setComputerTimeout: async ({ computerID, timeout }) => computers.setTimeout(computerID, timeout),
67978
+ stopComputer: ({ computerID }) => computers.stop(computerID),
67979
+ startComputer: ({ computerID, timeout }) => computers.start(computerID, timeout),
67980
+ getComputerLogs: async ({ computerID }) => ({ logs: computers.logs(computerID) }),
67981
+ createLiveSession: async ({ computerID, surface, mode }) => computers.liveSession(computerID, surface, mode ?? "watch"),
67982
+ openLiveSession: async () => {
67983
+ throw new ApiError(426, "Open it as a WebSocket.");
67984
+ }
67985
+ };
67986
+ }
67987
+
67960
67988
  // src/inside.ts
67961
67989
  var INNER_PORT = 8e3;
67962
67990
  var INNER_TOKEN_VARIABLE = "AGENTCOMPUTER_INNER_TOKEN";
@@ -67997,7 +68025,7 @@ async function serveInside(options) {
67997
68025
  if (pathname === "/ready") {
67998
68026
  const { browserTools, surfaces } = await running2;
67999
68027
  response.writeHead(200, { "Content-Type": "application/json" });
68000
- return void response.end(JSON.stringify({ browserTools, surfaces }));
68028
+ return void response.end(JSON.stringify({ browserTools, surfaces, version: VERSION2 }));
68001
68029
  }
68002
68030
  const endpoint = /^\/mcp\/(computer|browser)$/.exec(pathname)?.[1];
68003
68031
  if (!endpoint) return void response.writeHead(404).end();
@@ -68012,9 +68040,9 @@ async function serveInside(options) {
68012
68040
  server.on("upgrade", (request, socket, head) => {
68013
68041
  socket.on("error", () => socket.destroy());
68014
68042
  const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
68015
- const url3 = new URL(request.url ?? "/", "http://localhost");
68016
- const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3.pathname)?.[1];
68017
- const mode = url3.searchParams.get("mode");
68043
+ const url3 = URL.parse(request.url ?? "/", "http://localhost");
68044
+ const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3?.pathname ?? "")?.[1];
68045
+ const mode = url3?.searchParams.get("mode");
68018
68046
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
68019
68047
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
68020
68048
  running2.then((computer) => computer.openLive(surface, mode, request, socket, head)).catch(() => socket.destroy());
@@ -68056,10 +68084,15 @@ var UPGRADE_HEADERS = [
68056
68084
  "sec-websocket-extensions",
68057
68085
  "sec-websocket-protocol"
68058
68086
  ];
68087
+ var messageOf = (error62) => error62 instanceof Error ? error62.message : String(error62);
68059
68088
  var nameOf = (id) => `agentcomputer-${id}`;
68089
+ var HOME = "/home/computer";
68090
+ var homeOf = (folder) => join8(folder, "home");
68060
68091
  var sleep5 = (ms) => new Promise((done) => setTimeout(done, ms));
68061
68092
  var ContainerRuntime = class {
68062
68093
  #image;
68094
+ /** The id of that image, as it is now on this machine. */
68095
+ #imageId;
68063
68096
  constructor(image) {
68064
68097
  this.#image = image;
68065
68098
  }
@@ -68083,12 +68116,14 @@ var ContainerRuntime = class {
68083
68116
  }
68084
68117
  async prepare() {
68085
68118
  await this.#docker();
68119
+ const id = () => docker(["image", "inspect", "--format", "{{.Id}}", this.#image]);
68086
68120
  try {
68087
- await docker(["image", "inspect", "--format", "{{.Id}}", this.#image]);
68121
+ this.#imageId = await id();
68088
68122
  } catch {
68089
68123
  process.stdout.write(`Pulling ${this.#image}
68090
68124
  `);
68091
68125
  await docker(["pull", this.#image]);
68126
+ this.#imageId = await id();
68092
68127
  }
68093
68128
  }
68094
68129
  async #inspect(name) {
@@ -68098,7 +68133,39 @@ var ContainerRuntime = class {
68098
68133
  return void 0;
68099
68134
  }
68100
68135
  }
68101
- async #create(name, spec, token) {
68136
+ /**
68137
+ * Remove a container made from another image, for it to be made again. What it keeps is outside
68138
+ * it: its workspace, and its home. One made before its home was outside has its home copied out
68139
+ * first, and if that can't be done it is kept as it is, to try again at its next start.
68140
+ */
68141
+ async #remake(name, made, folder, events) {
68142
+ if (!made.Mounts.some((mount) => mount.Destination === HOME)) {
68143
+ const home = homeOf(folder);
68144
+ const copying = `${home}.copying`;
68145
+ rmSync4(copying, { recursive: true, force: true });
68146
+ try {
68147
+ const owners = process.getuid?.() ? [] : ["--archive"];
68148
+ await docker(["cp", ...owners, `${name}:${HOME}`, copying]);
68149
+ rmSync4(home, { recursive: true, force: true });
68150
+ renameSync2(copying, home);
68151
+ } catch (error62) {
68152
+ rmSync4(copying, { recursive: true, force: true });
68153
+ events.log(
68154
+ `Its home couldn't be copied out of its container (${messageOf(error62)}), so the container is kept and started as it was made. The copy is tried again at its next start.`
68155
+ );
68156
+ return;
68157
+ }
68158
+ events.log("Its home was copied out of its container, to keep.");
68159
+ }
68160
+ await docker(["rm", "--force", name]);
68161
+ events.log(
68162
+ `Its container was made from another image, so it is made again from ${this.#image}.`
68163
+ );
68164
+ }
68165
+ async #create(name, spec, token, folder) {
68166
+ const home = homeOf(folder);
68167
+ mkdirSync2(home, { recursive: true });
68168
+ chmodSync3(home, 1023);
68102
68169
  const uid = process.getuid?.();
68103
68170
  const told = {
68104
68171
  [INNER_TOKEN_VARIABLE]: token,
@@ -68122,6 +68189,8 @@ var ContainerRuntime = class {
68122
68189
  ...Object.keys(told).flatMap((name2) => ["--env", name2]),
68123
68190
  // Files it writes in the workspace belong to whoever runs the service.
68124
68191
  ...uid ? ["--user", `${uid}:${process.getgid?.()}`] : [],
68192
+ "--mount",
68193
+ `type=bind,source=${home},target=${HOME}`,
68125
68194
  ...spec.workspace ? [
68126
68195
  "--mount",
68127
68196
  `type=bind,source=${spec.workspace},target=/workspace${spec.access === "read" ? ",readonly" : ""}`
@@ -68149,7 +68218,10 @@ var ContainerRuntime = class {
68149
68218
  async start(spec, folder, events) {
68150
68219
  const name = nameOf(spec.id);
68151
68220
  const token = this.#tokenIn(folder);
68152
- if (!await this.#inspect(name)) await this.#create(name, spec, token);
68221
+ const made = await this.#inspect(name);
68222
+ if (made && this.#imageId && made.Image !== this.#imageId)
68223
+ await this.#remake(name, made, folder, events);
68224
+ if (!await this.#inspect(name)) await this.#create(name, spec, token, folder);
68153
68225
  await docker(["start", name]);
68154
68226
  const inspected = await this.#inspect(name);
68155
68227
  const port = inspected?.NetworkSettings.Ports[`${INNER_PORT}/tcp`]?.[0]?.HostPort;
@@ -68186,6 +68258,7 @@ var ContainerRuntime = class {
68186
68258
  }
68187
68259
  return {
68188
68260
  browserTools: ready.browserTools,
68261
+ version: ready.version,
68189
68262
  /** As they are now: nothing of a container that has stopped can be opened. */
68190
68263
  get surfaces() {
68191
68264
  return ready.surfaces.map(
@@ -68278,33 +68351,6 @@ async function forward(url3, authorization, request, response) {
68278
68351
  sendAnswer(upstream, response);
68279
68352
  }
68280
68353
 
68281
- // src/handlers.ts
68282
- var VERSION2 = "0.0.16";
68283
- function createHandlers(computers) {
68284
- return {
68285
- getInfo: async () => ({
68286
- version: VERSION2,
68287
- isolation: computers.isolation,
68288
- isolationsAvailable: await computers.available()
68289
- }),
68290
- listComputers: async ({ state }) => computers.list(state),
68291
- createComputer: (input2) => computers.create(input2),
68292
- getComputer: async ({ computerID }) => computers.get(computerID),
68293
- killComputer: async ({ computerID }) => {
68294
- await computers.kill(computerID);
68295
- return void 0;
68296
- },
68297
- setComputerTimeout: async ({ computerID, timeout }) => computers.setTimeout(computerID, timeout),
68298
- stopComputer: ({ computerID }) => computers.stop(computerID),
68299
- startComputer: ({ computerID, timeout }) => computers.start(computerID, timeout),
68300
- getComputerLogs: async ({ computerID }) => ({ logs: computers.logs(computerID) }),
68301
- createLiveSession: async ({ computerID, surface, mode }) => computers.liveSession(computerID, surface, mode ?? "watch"),
68302
- openLiveSession: async () => {
68303
- throw new ApiError(426, "Open it as a WebSocket.");
68304
- }
68305
- };
68306
- }
68307
-
68308
68354
  // src/running.ts
68309
68355
  import { spawn as spawn4 } from "node:child_process";
68310
68356
  import { closeSync, openSync, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
@@ -69177,7 +69223,7 @@ var Computer = class _Computer {
69177
69223
  };
69178
69224
 
69179
69225
  // src/service.ts
69180
- import { readFileSync as readFileSync6, rmSync as rmSync5 } from "node:fs";
69226
+ import { readFileSync as readFileSync6, rmSync as rmSync6 } from "node:fs";
69181
69227
  import {
69182
69228
  createServer as createServer5,
69183
69229
  STATUS_CODES as STATUS_CODES3
@@ -84997,7 +85043,7 @@ var NodeStreamableHTTPServerTransport2 = class {
84997
85043
 
84998
85044
  // src/computers.ts
84999
85045
  import { randomBytes as randomBytes5 } from "node:crypto";
85000
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, statSync as statSync4 } from "node:fs";
85046
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
85001
85047
  import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
85002
85048
  var READ = ["about", "read", "list", "search"];
85003
85049
  var FILES = [...READ, "edit"];
@@ -85010,8 +85056,8 @@ var MAX_LOG2 = 256 * 1024;
85010
85056
  var MAX_TIMER_S = 2e6;
85011
85057
  var TICKET_MS = 6e4;
85012
85058
  var expiry = (seconds2) => new Date(Date.now() + seconds2 * 1e3).toISOString();
85013
- var messageOf = (error62) => error62 instanceof Error ? error62.message : String(error62);
85014
- var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf(error62));
85059
+ var messageOf2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
85060
+ var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf2(error62));
85015
85061
  var Computers = class {
85016
85062
  #data;
85017
85063
  #base;
@@ -85045,7 +85091,7 @@ var Computers = class {
85045
85091
  if (stored.endAt && Date.parse(stored.endAt) <= Date.now()) {
85046
85092
  stored.state = "stopped";
85047
85093
  await this.#runtimes[stored.isolation].stopLeftover(stored.id).catch(
85048
- (error62) => this.#log(stored.id, `What it left running wasn't stopped: ${messageOf(error62)}`)
85094
+ (error62) => this.#log(stored.id, `What it left running wasn't stopped: ${messageOf2(error62)}`)
85049
85095
  );
85050
85096
  continue;
85051
85097
  }
@@ -85053,7 +85099,7 @@ var Computers = class {
85053
85099
  const live = this.#live.get(stored.id);
85054
85100
  if (live) live.failed = true;
85055
85101
  this.#arm(stored);
85056
- this.#log(stored.id, `It didn't start again: ${messageOf(error62)}`);
85102
+ this.#log(stored.id, `It didn't start again: ${messageOf2(error62)}`);
85057
85103
  });
85058
85104
  }
85059
85105
  this.#save();
@@ -85092,7 +85138,7 @@ var Computers = class {
85092
85138
  };
85093
85139
  if (!workspace && access !== "none") {
85094
85140
  stored.workspace = join10(this.#folder(id), "workspace");
85095
- mkdirSync2(stored.workspace, { recursive: true, mode: 448 });
85141
+ mkdirSync3(stored.workspace, { recursive: true, mode: 448 });
85096
85142
  }
85097
85143
  this.#stored.set(id, stored);
85098
85144
  try {
@@ -85102,7 +85148,7 @@ var Computers = class {
85102
85148
  this.#stored.delete(id);
85103
85149
  await this.#runtimes[stored.isolation].remove(id).catch(() => {
85104
85150
  });
85105
- rmSync4(this.#folder(id), { recursive: true, force: true });
85151
+ rmSync5(this.#folder(id), { recursive: true, force: true });
85106
85152
  throw refusalOf(error62);
85107
85153
  }
85108
85154
  this.#save();
@@ -85176,7 +85222,7 @@ var Computers = class {
85176
85222
  }
85177
85223
  this.#stored.delete(id);
85178
85224
  this.#save();
85179
- rmSync4(this.#folder(id), { recursive: true, force: true });
85225
+ rmSync5(this.#folder(id), { recursive: true, force: true });
85180
85226
  }
85181
85227
  logs(id) {
85182
85228
  this.#found(id);
@@ -85277,6 +85323,7 @@ var Computers = class {
85277
85323
  state: stored.state === "running" && down2 ? "failed" : stored.state,
85278
85324
  access: stored.access,
85279
85325
  isolation: stored.isolation,
85326
+ version: stored.version ?? null,
85280
85327
  browser: stored.browser,
85281
85328
  workspace: stored.workspace ?? null,
85282
85329
  startedAt: stored.startedAt,
@@ -85310,6 +85357,9 @@ var Computers = class {
85310
85357
  }
85311
85358
  }
85312
85359
  );
85360
+ const version3 = stored.isolation === "none" ? VERSION2 : live.running.version;
85361
+ if (version3) stored.version = version3;
85362
+ else delete stored.version;
85313
85363
  this.#arm(stored);
85314
85364
  }
85315
85365
  #arm(stored) {
@@ -85347,7 +85397,7 @@ var OPENAPI = {
85347
85397
  "openapi": "3.1.0",
85348
85398
  "info": {
85349
85399
  "title": "AgentComputer",
85350
- "version": "0.0.16",
85400
+ "version": "0.0.18",
85351
85401
  "description": "Computers for agents. This file is the source of truth: the HTTP API, the MCP control tools and the SDK are all generated from it. An operation with `x-mcp-tool` is also an MCP tool of that name.",
85352
85402
  "license": {
85353
85403
  "name": "Apache-2.0",
@@ -85901,6 +85951,7 @@ var OPENAPI = {
85901
85951
  "state",
85902
85952
  "access",
85903
85953
  "isolation",
85954
+ "version",
85904
85955
  "browser",
85905
85956
  "workspace",
85906
85957
  "startedAt",
@@ -85925,6 +85976,13 @@ var OPENAPI = {
85925
85976
  "$ref": "#/components/schemas/Isolation",
85926
85977
  "description": "The isolation it was made in. It is never run in another."
85927
85978
  },
85979
+ "version": {
85980
+ "type": [
85981
+ "string",
85982
+ "null"
85983
+ ],
85984
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
85985
+ },
85928
85986
  "browser": {
85929
85987
  "type": "boolean"
85930
85988
  },
@@ -87196,6 +87254,7 @@ var OPERATIONS = [
87196
87254
  "state",
87197
87255
  "access",
87198
87256
  "isolation",
87257
+ "version",
87199
87258
  "browser",
87200
87259
  "workspace",
87201
87260
  "startedAt",
@@ -87237,6 +87296,13 @@ var OPERATIONS = [
87237
87296
  ],
87238
87297
  "description": "The isolation it was made in. It is never run in another."
87239
87298
  },
87299
+ "version": {
87300
+ "type": [
87301
+ "string",
87302
+ "null"
87303
+ ],
87304
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87305
+ },
87240
87306
  "browser": {
87241
87307
  "type": "boolean"
87242
87308
  },
@@ -87373,6 +87439,7 @@ var OPERATIONS = [
87373
87439
  "state",
87374
87440
  "access",
87375
87441
  "isolation",
87442
+ "version",
87376
87443
  "browser",
87377
87444
  "workspace",
87378
87445
  "startedAt",
@@ -87414,6 +87481,13 @@ var OPERATIONS = [
87414
87481
  ],
87415
87482
  "description": "The isolation it was made in. It is never run in another."
87416
87483
  },
87484
+ "version": {
87485
+ "type": [
87486
+ "string",
87487
+ "null"
87488
+ ],
87489
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87490
+ },
87417
87491
  "browser": {
87418
87492
  "type": "boolean"
87419
87493
  },
@@ -87523,6 +87597,7 @@ var OPERATIONS = [
87523
87597
  "state",
87524
87598
  "access",
87525
87599
  "isolation",
87600
+ "version",
87526
87601
  "browser",
87527
87602
  "workspace",
87528
87603
  "startedAt",
@@ -87564,6 +87639,13 @@ var OPERATIONS = [
87564
87639
  ],
87565
87640
  "description": "The isolation it was made in. It is never run in another."
87566
87641
  },
87642
+ "version": {
87643
+ "type": [
87644
+ "string",
87645
+ "null"
87646
+ ],
87647
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87648
+ },
87567
87649
  "browser": {
87568
87650
  "type": "boolean"
87569
87651
  },
@@ -87764,6 +87846,7 @@ var OPERATIONS = [
87764
87846
  "state",
87765
87847
  "access",
87766
87848
  "isolation",
87849
+ "version",
87767
87850
  "browser",
87768
87851
  "workspace",
87769
87852
  "startedAt",
@@ -87805,6 +87888,13 @@ var OPERATIONS = [
87805
87888
  ],
87806
87889
  "description": "The isolation it was made in. It is never run in another."
87807
87890
  },
87891
+ "version": {
87892
+ "type": [
87893
+ "string",
87894
+ "null"
87895
+ ],
87896
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87897
+ },
87808
87898
  "browser": {
87809
87899
  "type": "boolean"
87810
87900
  },
@@ -87898,6 +87988,7 @@ var OPERATIONS = [
87898
87988
  "state",
87899
87989
  "access",
87900
87990
  "isolation",
87991
+ "version",
87901
87992
  "browser",
87902
87993
  "workspace",
87903
87994
  "startedAt",
@@ -87939,6 +88030,13 @@ var OPERATIONS = [
87939
88030
  ],
87940
88031
  "description": "The isolation it was made in. It is never run in another."
87941
88032
  },
88033
+ "version": {
88034
+ "type": [
88035
+ "string",
88036
+ "null"
88037
+ ],
88038
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88039
+ },
87942
88040
  "browser": {
87943
88041
  "type": "boolean"
87944
88042
  },
@@ -88037,6 +88135,7 @@ var OPERATIONS = [
88037
88135
  "state",
88038
88136
  "access",
88039
88137
  "isolation",
88138
+ "version",
88040
88139
  "browser",
88041
88140
  "workspace",
88042
88141
  "startedAt",
@@ -88078,6 +88177,13 @@ var OPERATIONS = [
88078
88177
  ],
88079
88178
  "description": "The isolation it was made in. It is never run in another."
88080
88179
  },
88180
+ "version": {
88181
+ "type": [
88182
+ "string",
88183
+ "null"
88184
+ ],
88185
+ "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88186
+ },
88081
88187
  "browser": {
88082
88188
  "type": "boolean"
88083
88189
  },
@@ -88472,13 +88578,13 @@ Connection: close\r
88472
88578
  ${JSON.stringify({ code: status, message })}
88473
88579
  `
88474
88580
  );
88475
- const { pathname } = new URL(request.url ?? "/", "http://localhost");
88581
+ const pathname = URL.parse(request.url ?? "/", "http://localhost")?.pathname ?? "";
88476
88582
  const ticket = /^\/v1\/live\/([^/]+)$/.exec(pathname)?.[1];
88477
88583
  if (!ticket) return refuse(404, "Not found.");
88478
88584
  const origin = request.headers.origin;
88479
88585
  if (origin !== void 0 && !allowed.has(origin))
88480
88586
  return refuse(403, `A live session isn't opened from ${origin.slice(0, 200)}.`);
88481
- computers.openLive(decodeURIComponent(ticket), request, socket, head).catch((error62) => {
88587
+ computers.openLive(ticket, request, socket, head).catch((error62) => {
88482
88588
  if (error62 instanceof ApiError) refuse(error62.status, error62.message);
88483
88589
  else socket.destroy();
88484
88590
  });
@@ -88491,7 +88597,7 @@ ${JSON.stringify({ code: status, message })}
88491
88597
  const scheme = options.tls ? "https" : "http";
88492
88598
  base = options.publicUrl?.replace(/\/+$/, "") ?? `${scheme}://${bound.family === "IPv6" ? `[${bound.address}]` : bound.address}:${bound.port}`;
88493
88599
  const record3 = recordOf(data);
88494
- rmSync5(record3, { force: true });
88600
+ rmSync6(record3, { force: true });
88495
88601
  await computers.resume();
88496
88602
  writePrivate(
88497
88603
  record3,
@@ -88507,7 +88613,7 @@ ${JSON.stringify({ code: status, message })}
88507
88613
  process.stderr.write(`${String(error62)}
88508
88614
  `);
88509
88615
  });
88510
- rmSync5(record3, { force: true });
88616
+ rmSync6(record3, { force: true });
88511
88617
  process.exit(0);
88512
88618
  };
88513
88619
  for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void stop());
@@ -231,6 +231,8 @@ export interface components {
231
231
  access: components["schemas"]["Access"];
232
232
  /** @description The isolation it was made in. It is never run in another. */
233
233
  isolation: components["schemas"]["Isolation"];
234
+ /** @description The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program. */
235
+ version: string | null;
234
236
  browser: boolean;
235
237
  workspace: string | null;
236
238
  /**
@@ -174,6 +174,7 @@ export declare class Computer {
174
174
  state: components["schemas"]["ComputerState"];
175
175
  access: components["schemas"]["Access"];
176
176
  isolation: components["schemas"]["Isolation"];
177
+ version: string | null;
177
178
  browser: boolean;
178
179
  workspace: string | null;
179
180
  startedAt: string;
@@ -195,6 +196,7 @@ export declare class Computer {
195
196
  state: components["schemas"]["ComputerState"];
196
197
  access: components["schemas"]["Access"];
197
198
  isolation: components["schemas"]["Isolation"];
199
+ version: string | null;
198
200
  browser: boolean;
199
201
  workspace: string | null;
200
202
  startedAt: string;
@@ -208,6 +210,7 @@ export declare class Computer {
208
210
  state: components["schemas"]["ComputerState"];
209
211
  access: components["schemas"]["Access"];
210
212
  isolation: components["schemas"]["Isolation"];
213
+ version: string | null;
211
214
  browser: boolean;
212
215
  workspace: string | null;
213
216
  startedAt: string;
@@ -223,6 +226,7 @@ export declare class Computer {
223
226
  state: components["schemas"]["ComputerState"];
224
227
  access: components["schemas"]["Access"];
225
228
  isolation: components["schemas"]["Isolation"];
229
+ version: string | null;
226
230
  browser: boolean;
227
231
  workspace: string | null;
228
232
  startedAt: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/computer",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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": {