@agentorg/computer 0.0.15 → 0.0.17

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,15 +60,15 @@ 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
- `--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. 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.
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
 
71
73
  ## Commands
72
74
 
@@ -214,7 +216,6 @@ A shell of your own needs [node-pty](https://github.com/microsoft/node-pty), an
214
216
 
215
217
  ## Known limits
216
218
 
217
- - With no isolation, each browser's Playwright MCP listens on a loopback port with no token of its own, so another user on the same machine could reach it directly. Don't run that on a machine you share.
218
219
  - A container has no CPU or memory limit, and its network is Docker's default, so it reaches the internet and anything else Docker's network does.
219
220
  - In a container the browser runs without its own sandbox, which needs privileges a computer doesn't have. The container is the boundary.
220
221
  - A browser that stops, or a container that ends by itself, shows the computer as `failed`, and what can no longer be opened live says so in `surfaces`. So does a computer that couldn't be brought back when the service started, for one in a container because Docker didn't answer; its `logs` say why. Stop the computer and start it again.
@@ -3804,49 +3804,78 @@ async function hostBrowser(options) {
3804
3804
  }
3805
3805
 
3806
3806
  // src/browser-mcp.ts
3807
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
3807
3808
  import { createRequire as createRequire2 } from "node:module";
3809
+ import { join as join2 } from "node:path";
3808
3810
  var require2 = createRequire2(import.meta.url);
3809
3811
  var mcpParts = () => require2("playwright-core/lib/coreBundle").tools;
3812
+ function listenOnSocket(socket) {
3813
+ const http = require2("node:http");
3814
+ const make = http.createServer;
3815
+ http.createServer = ((...args) => {
3816
+ http.createServer = make;
3817
+ const server = make(...args);
3818
+ const listen = server.listen.bind(server);
3819
+ server.listen = (() => listen(socket));
3820
+ server.address = () => ({ address: "127.0.0.1", family: "IPv4", port: 0 });
3821
+ return server;
3822
+ });
3823
+ }
3810
3824
  async function serveBrowserMcp(options) {
3811
3825
  const parts = mcpParts();
3812
3826
  parts.setupExitWatchdog();
3827
+ const folder = mkdtempSync2(join2("/tmp", "agentcomputer-"));
3828
+ process.once("exit", () => rmSync2(folder, { recursive: true, force: true }));
3829
+ const socket = join2(folder, "mcp.sock");
3813
3830
  const config3 = await parts.resolveCLIConfigForMCP({
3814
3831
  endpoint: options.endpoint,
3815
3832
  outputDir: options.outputDir,
3816
3833
  host: "127.0.0.1",
3817
3834
  port: 0,
3835
+ // What the proxy sends, which is all that can reach the socket.
3836
+ allowedHosts: ["localhost"],
3818
3837
  // The tool that closes the browser refuses, as it does for any browser its clients share.
3819
3838
  sharedBrowserContext: true
3820
3839
  });
3821
3840
  const tools = parts.filteredTools(config3);
3822
- await parts.start(
3823
- {
3824
- name: "Playwright",
3825
- nameInConfig: "playwright",
3826
- // playwright-core's, as Playwright MCP's program gives it.
3827
- version: require2("playwright-core/package.json").version,
3828
- toolSchemas: tools.map((tool) => tool.schema),
3829
- ignoreRoots: config3.ignoreRoots,
3830
- /** One for each client's session: a connection of its own to the one browser. */
3831
- create: async () => {
3832
- const browser = await playwright().chromium.connect(options.endpoint);
3833
- const context = browser.contexts()[0];
3834
- if (!context) {
3835
- await browser.close();
3836
- throw new Error("The browser has no context to work in.");
3837
- }
3838
- return new parts.BrowserBackend(config3, context, tools, {
3839
- dispose: () => browser.close()
3840
- });
3841
- }
3842
- },
3843
- config3.server
3844
- );
3841
+ listenOnSocket(socket);
3842
+ const say = console.error;
3843
+ console.error = () => {
3844
+ };
3845
+ try {
3846
+ await parts.start(
3847
+ {
3848
+ name: "Playwright",
3849
+ nameInConfig: "playwright",
3850
+ // playwright-core's, as Playwright MCP's program gives it.
3851
+ version: require2("playwright-core/package.json").version,
3852
+ toolSchemas: tools.map((tool) => tool.schema),
3853
+ ignoreRoots: config3.ignoreRoots,
3854
+ /** One for each client's session: a connection of its own to the one browser. */
3855
+ create: async () => {
3856
+ const browser = await playwright().chromium.connect(options.endpoint);
3857
+ const context = browser.contexts()[0];
3858
+ if (!context) {
3859
+ await browser.close();
3860
+ throw new Error("The browser has no context to work in.");
3861
+ }
3862
+ return new parts.BrowserBackend(config3, context, tools, {
3863
+ dispose: () => browser.close()
3864
+ });
3865
+ }
3866
+ },
3867
+ config3.server
3868
+ );
3869
+ } finally {
3870
+ console.error = say;
3871
+ }
3872
+ process.stdout.write(`${JSON.stringify({ socket })}
3873
+ `);
3845
3874
  }
3846
3875
 
3847
3876
  // src/container.ts
3848
3877
  import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
3849
- 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";
3850
3879
  import { connect } from "node:net";
3851
3880
  import { hostname as hostname4 } from "node:os";
3852
3881
  import { join as join8 } from "node:path";
@@ -3856,7 +3885,9 @@ import { promisify as promisify2 } from "node:util";
3856
3885
 
3857
3886
  // src/browser.ts
3858
3887
  import { spawn } from "node:child_process";
3859
- import { join as join2 } from "node:path";
3888
+ import { rmSync as rmSync3 } from "node:fs";
3889
+ import { request as ask } from "node:http";
3890
+ import { dirname, join as join3 } from "node:path";
3860
3891
  import { createInterface } from "node:readline";
3861
3892
  import { pipeline, Readable } from "node:stream";
3862
3893
  import { fileURLToPath } from "node:url";
@@ -27982,6 +28013,42 @@ function sendAnswer(upstream, response) {
27982
28013
  pipeline(Readable.fromWeb(upstream.body), response, () => {
27983
28014
  });
27984
28015
  }
28016
+ function socketFetch(socket) {
28017
+ return (url3, init = {}) => new Promise((done, failed) => {
28018
+ const { body } = init;
28019
+ if (body != null && typeof body !== "string" && !(body instanceof Uint8Array))
28020
+ return failed(new TypeError("A body to the browser's server is text or bytes."));
28021
+ const { pathname, search: search2 } = new URL(url3);
28022
+ const headers = Object.fromEntries(new Headers(init.headers));
28023
+ if (body != null) headers["content-length"] = String(Buffer.byteLength(body));
28024
+ const sent = ask(
28025
+ {
28026
+ socketPath: socket,
28027
+ path: pathname + search2,
28028
+ method: init.method ?? "GET",
28029
+ headers,
28030
+ signal: init.signal ?? void 0
28031
+ },
28032
+ (answer) => {
28033
+ const answered2 = new Headers();
28034
+ for (const [name, value] of Object.entries(answer.headers))
28035
+ for (const one of [value ?? []].flat()) answered2.append(name, one);
28036
+ const status = answer.statusCode ?? 502;
28037
+ const none2 = [204, 205, 304].includes(status);
28038
+ if (none2) answer.resume();
28039
+ done(
28040
+ new Response(none2 ? null : Readable.toWeb(answer), {
28041
+ status,
28042
+ headers: answered2
28043
+ })
28044
+ );
28045
+ }
28046
+ );
28047
+ sent.on("error", failed);
28048
+ sent.end(body ?? void 0);
28049
+ });
28050
+ }
28051
+ var MCP = "http://localhost/mcp";
27985
28052
  var self = fileURLToPath(import.meta.url);
27986
28053
  function changed(text3, change) {
27987
28054
  const one = (json4) => {
@@ -28016,7 +28083,10 @@ async function end(child) {
28016
28083
  var Browser = class {
28017
28084
  #host;
28018
28085
  #mcp;
28019
- #origin;
28086
+ /** Where its server answers, by way of its Unix socket. */
28087
+ #served;
28088
+ /** The folder its server made for its socket. */
28089
+ #socketFolder;
28020
28090
  /** The bound browser's socket, for the live viewer. */
28021
28091
  endpoint;
28022
28092
  #hidden;
@@ -28038,7 +28108,7 @@ var Browser = class {
28038
28108
  self,
28039
28109
  "browser-host",
28040
28110
  "--profile",
28041
- join2(folder, "profile"),
28111
+ join3(folder, "profile"),
28042
28112
  ...config3.path ? ["--executable", config3.path] : ["--channel", config3.browser ?? "chrome"],
28043
28113
  ...config3.sandbox === false ? ["--no-sandbox"] : []
28044
28114
  ],
@@ -28061,12 +28131,12 @@ var Browser = class {
28061
28131
  });
28062
28132
  this.endpoint.catch(() => {
28063
28133
  });
28064
- this.#origin = this.endpoint.then(
28134
+ this.#served = this.endpoint.then(
28065
28135
  (endpoint) => new Promise((done, failed) => {
28066
28136
  if (this.#stopping) return failed(new Error("It stopped."));
28067
28137
  const mcp = spawn(
28068
28138
  process.execPath,
28069
- [self, "browser-mcp", "--endpoint", endpoint, "--output", join2(folder, "output")],
28139
+ [self, "browser-mcp", "--endpoint", endpoint, "--output", join3(folder, "output")],
28070
28140
  // Its own limit on which files it touches is the folder it starts in.
28071
28141
  { cwd, stdio: ["ignore", "pipe", "pipe"] }
28072
28142
  );
@@ -28077,34 +28147,39 @@ var Browser = class {
28077
28147
  (code) => failed(new Error(`The browser's server exited with ${code}.`))
28078
28148
  );
28079
28149
  mcp.once("exit", ended("The browser's server"));
28080
- for (const stream of [mcp.stdout, mcp.stderr])
28081
- createInterface({ input: stream }).on("line", (line) => {
28150
+ createInterface({ input: mcp.stdout }).on("line", (line) => {
28151
+ try {
28152
+ const { socket } = JSON.parse(line);
28153
+ log(`The browser's server is on ${socket}.`);
28154
+ this.#socketFolder = dirname(socket);
28155
+ done(socketFetch(socket));
28156
+ } catch {
28082
28157
  log(line);
28083
- const listening = /^Listening on (http:\/\/[^/\s]+)/.exec(line);
28084
- if (listening) done(listening[1]);
28085
- });
28158
+ }
28159
+ });
28160
+ createInterface({ input: mcp.stderr }).on("line", log);
28086
28161
  })
28087
28162
  );
28088
- this.#origin.catch(() => {
28163
+ this.#served.catch(() => {
28089
28164
  });
28090
28165
  }
28091
28166
  /** Wait until it answers, and read its tools. */
28092
28167
  async ready() {
28093
- const origin = await Promise.race([
28094
- this.#origin,
28168
+ const served = await Promise.race([
28169
+ this.#served,
28095
28170
  new Promise(
28096
28171
  (_, failed) => setTimeout(() => failed(new Error("The browser's server didn't start.")), START_MS)
28097
28172
  )
28098
28173
  ]);
28099
28174
  const client = new Client({ name: "agentcomputer", version: "0" });
28100
- await client.connect(new StreamableHTTPClientTransport(new URL(`${origin}/mcp`)));
28175
+ await client.connect(new StreamableHTTPClientTransport(new URL(MCP), { fetch: served }));
28101
28176
  const { tools } = await client.listTools();
28102
28177
  await client.close();
28103
28178
  this.tools = tools.map((tool) => tool.name).filter((name) => !this.#hidden.has(name));
28104
28179
  }
28105
28180
  /** Answer one MCP request by passing it on, enforcing what this computer may not do. */
28106
28181
  async handle(request, response) {
28107
- const origin = await this.#origin;
28182
+ const served = await this.#served;
28108
28183
  const chunks = [];
28109
28184
  let size = 0;
28110
28185
  for await (const chunk of request) {
@@ -28148,7 +28223,7 @@ var Browser = class {
28148
28223
  const session = headers["mcp-session-id"] ?? "";
28149
28224
  if (request.method === "DELETE") this.gate?.sessionEnded(session);
28150
28225
  if (tool && this.gate?.takeAnsweredChooser(session) && tool !== "browser_file_upload")
28151
- await fetch(`${origin}/mcp`, {
28226
+ await served(MCP, {
28152
28227
  method: "POST",
28153
28228
  headers,
28154
28229
  body: JSON.stringify({
@@ -28161,7 +28236,7 @@ var Browser = class {
28161
28236
  });
28162
28237
  if (tool === "browser_file_upload") this.gate?.agentAnsweredChooser();
28163
28238
  if (message) headers["content-type"] = "application/json";
28164
- const upstream = await fetch(`${origin}/mcp`, {
28239
+ const upstream = await served(MCP, {
28165
28240
  method: request.method,
28166
28241
  headers,
28167
28242
  ...message ? { body: JSON.stringify(message) } : {}
@@ -28176,6 +28251,7 @@ var Browser = class {
28176
28251
  async stop() {
28177
28252
  this.#stopping = true;
28178
28253
  await Promise.all([this.#mcp, this.#host].map((child) => child ? end(child) : void 0));
28254
+ if (this.#socketFolder) rmSync3(this.#socketFolder, { recursive: true, force: true });
28179
28255
  }
28180
28256
  };
28181
28257
 
@@ -28188,7 +28264,7 @@ import { join as join7 } from "node:path";
28188
28264
  import { chmodSync as chmodSync2, existsSync, statSync as statSync2 } from "node:fs";
28189
28265
  import { createRequire as createRequire3 } from "node:module";
28190
28266
  import { hostname as hostname3 } from "node:os";
28191
- import { basename as basename2, dirname as dirname3, join as join6 } from "node:path";
28267
+ import { basename as basename2, dirname as dirname4, join as join6 } from "node:path";
28192
28268
 
28193
28269
  // node_modules/@agentorg/exec/dist/lib.js
28194
28270
  import { realpathSync } from "node:fs";
@@ -28198,7 +28274,7 @@ import { Http2ServerRequest, constants } from "node:http2";
28198
28274
  import { Readable as Readable2 } from "node:stream";
28199
28275
  import { randomBytes as randomBytes3 } from "node:crypto";
28200
28276
  import { lstat as lstat2, open as open3, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2 } from "node:fs/promises";
28201
- import { dirname as dirname2, join as join3 } from "node:path";
28277
+ import { dirname as dirname2, join as join32 } from "node:path";
28202
28278
  import { randomBytes as randomBytes2 } from "node:crypto";
28203
28279
  import {
28204
28280
  chmod,
@@ -28212,7 +28288,7 @@ import {
28212
28288
  stat,
28213
28289
  writeFile
28214
28290
  } from "node:fs/promises";
28215
- import { basename, dirname, join as join4, resolve, sep as sep2 } from "node:path";
28291
+ import { basename, dirname as dirname3, join as join4, resolve, sep as sep2 } from "node:path";
28216
28292
  import { spawn as spawn2 } from "node:child_process";
28217
28293
  import { randomBytes } from "node:crypto";
28218
28294
  import { StringDecoder } from "node:string_decoder";
@@ -66245,7 +66321,7 @@ async function inside(root, path) {
66245
66321
  () => false
66246
66322
  ))
66247
66323
  throw refused2();
66248
- const above = dirname(at);
66324
+ const above = dirname3(at);
66249
66325
  if (above === at) throw refused2();
66250
66326
  return join4(await real(above), basename(at));
66251
66327
  }
@@ -66333,7 +66409,7 @@ ${cut(shown.join("\n"), MAX_READ_CHARS)}`;
66333
66409
  after = before.split(input2.old_text).join(input2.new_text);
66334
66410
  }
66335
66411
  if (after === before) throw new ExecRefused("That edit changes nothing.");
66336
- const temporary = join4(dirname(path), `.agentexec-${randomBytes2(6).toString("hex")}`);
66412
+ const temporary = join4(dirname3(path), `.agentexec-${randomBytes2(6).toString("hex")}`);
66337
66413
  try {
66338
66414
  await writeFile(temporary, after, found ? { mode: found.mode } : {});
66339
66415
  if (found) await chmod(temporary, found.mode & 511);
@@ -66472,7 +66548,7 @@ function createHostFiles(root, level) {
66472
66548
  found.sort(foldersFirst);
66473
66549
  const entries = await Promise.all(
66474
66550
  found.slice(0, MAX_ENTRIES).map(async (entry) => {
66475
- const own22 = await lstat2(join3(folder, entry.name)).catch(() => void 0);
66551
+ const own22 = await lstat2(join32(folder, entry.name)).catch(() => void 0);
66476
66552
  return own22 && {
66477
66553
  name: entry.name,
66478
66554
  kind: entry.isDirectory() ? "folder" : entry.isSymbolicLink() ? "link" : "file",
@@ -66519,7 +66595,7 @@ function createHostFiles(root, level) {
66519
66595
  });
66520
66596
  if (before?.isDirectory()) throw new ExecRefused(`${path} is a folder.`);
66521
66597
  await stat2(dirname2(target));
66522
- const temporary = join3(dirname2(target), `.agentexec-${randomBytes3(6).toString("hex")}`);
66598
+ const temporary = join32(dirname2(target), `.agentexec-${randomBytes3(6).toString("hex")}`);
66523
66599
  const file2 = await open3(temporary, "wx");
66524
66600
  const discard = async () => {
66525
66601
  await file2.close().catch(() => {
@@ -67773,7 +67849,7 @@ var loadPty = () => {
67773
67849
  } catch {
67774
67850
  return fails("node-pty isn't installed with this service.");
67775
67851
  }
67776
- const root = dirname3(require3.resolve("node-pty/package.json"));
67852
+ const root = dirname4(require3.resolve("node-pty/package.json"));
67777
67853
  for (const helper of [
67778
67854
  join6(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
67779
67855
  join6(root, "build", "Release", "spawn-helper")
@@ -67881,6 +67957,33 @@ var ProcessRuntime = class {
67881
67957
  }
67882
67958
  };
67883
67959
 
67960
+ // src/handlers.ts
67961
+ var VERSION2 = "0.0.17";
67962
+ function createHandlers(computers) {
67963
+ return {
67964
+ getInfo: async () => ({
67965
+ version: VERSION2,
67966
+ isolation: computers.isolation,
67967
+ isolationsAvailable: await computers.available()
67968
+ }),
67969
+ listComputers: async ({ state }) => computers.list(state),
67970
+ createComputer: (input2) => computers.create(input2),
67971
+ getComputer: async ({ computerID }) => computers.get(computerID),
67972
+ killComputer: async ({ computerID }) => {
67973
+ await computers.kill(computerID);
67974
+ return void 0;
67975
+ },
67976
+ setComputerTimeout: async ({ computerID, timeout }) => computers.setTimeout(computerID, timeout),
67977
+ stopComputer: ({ computerID }) => computers.stop(computerID),
67978
+ startComputer: ({ computerID, timeout }) => computers.start(computerID, timeout),
67979
+ getComputerLogs: async ({ computerID }) => ({ logs: computers.logs(computerID) }),
67980
+ createLiveSession: async ({ computerID, surface, mode }) => computers.liveSession(computerID, surface, mode ?? "watch"),
67981
+ openLiveSession: async () => {
67982
+ throw new ApiError(426, "Open it as a WebSocket.");
67983
+ }
67984
+ };
67985
+ }
67986
+
67884
67987
  // src/inside.ts
67885
67988
  var INNER_PORT = 8e3;
67886
67989
  var INNER_TOKEN_VARIABLE = "AGENTCOMPUTER_INNER_TOKEN";
@@ -67921,7 +68024,7 @@ async function serveInside(options) {
67921
68024
  if (pathname === "/ready") {
67922
68025
  const { browserTools, surfaces } = await running2;
67923
68026
  response.writeHead(200, { "Content-Type": "application/json" });
67924
- return void response.end(JSON.stringify({ browserTools, surfaces }));
68027
+ return void response.end(JSON.stringify({ browserTools, surfaces, version: VERSION2 }));
67925
68028
  }
67926
68029
  const endpoint = /^\/mcp\/(computer|browser)$/.exec(pathname)?.[1];
67927
68030
  if (!endpoint) return void response.writeHead(404).end();
@@ -67980,10 +68083,15 @@ var UPGRADE_HEADERS = [
67980
68083
  "sec-websocket-extensions",
67981
68084
  "sec-websocket-protocol"
67982
68085
  ];
68086
+ var messageOf = (error62) => error62 instanceof Error ? error62.message : String(error62);
67983
68087
  var nameOf = (id) => `agentcomputer-${id}`;
68088
+ var HOME = "/home/computer";
68089
+ var homeOf = (folder) => join8(folder, "home");
67984
68090
  var sleep5 = (ms) => new Promise((done) => setTimeout(done, ms));
67985
68091
  var ContainerRuntime = class {
67986
68092
  #image;
68093
+ /** The id of that image, as it is now on this machine. */
68094
+ #imageId;
67987
68095
  constructor(image) {
67988
68096
  this.#image = image;
67989
68097
  }
@@ -68007,12 +68115,14 @@ var ContainerRuntime = class {
68007
68115
  }
68008
68116
  async prepare() {
68009
68117
  await this.#docker();
68118
+ const id = () => docker(["image", "inspect", "--format", "{{.Id}}", this.#image]);
68010
68119
  try {
68011
- await docker(["image", "inspect", "--format", "{{.Id}}", this.#image]);
68120
+ this.#imageId = await id();
68012
68121
  } catch {
68013
68122
  process.stdout.write(`Pulling ${this.#image}
68014
68123
  `);
68015
68124
  await docker(["pull", this.#image]);
68125
+ this.#imageId = await id();
68016
68126
  }
68017
68127
  }
68018
68128
  async #inspect(name) {
@@ -68022,7 +68132,39 @@ var ContainerRuntime = class {
68022
68132
  return void 0;
68023
68133
  }
68024
68134
  }
68025
- async #create(name, spec, token) {
68135
+ /**
68136
+ * Remove a container made from another image, for it to be made again. What it keeps is outside
68137
+ * it: its workspace, and its home. One made before its home was outside has its home copied out
68138
+ * first, and if that can't be done it is kept as it is, to try again at its next start.
68139
+ */
68140
+ async #remake(name, made, folder, events) {
68141
+ if (!made.Mounts.some((mount) => mount.Destination === HOME)) {
68142
+ const home = homeOf(folder);
68143
+ const copying = `${home}.copying`;
68144
+ rmSync4(copying, { recursive: true, force: true });
68145
+ try {
68146
+ const owners = process.getuid?.() ? [] : ["--archive"];
68147
+ await docker(["cp", ...owners, `${name}:${HOME}`, copying]);
68148
+ rmSync4(home, { recursive: true, force: true });
68149
+ renameSync2(copying, home);
68150
+ } catch (error62) {
68151
+ rmSync4(copying, { recursive: true, force: true });
68152
+ events.log(
68153
+ `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.`
68154
+ );
68155
+ return;
68156
+ }
68157
+ events.log("Its home was copied out of its container, to keep.");
68158
+ }
68159
+ await docker(["rm", "--force", name]);
68160
+ events.log(
68161
+ `Its container was made from another image, so it is made again from ${this.#image}.`
68162
+ );
68163
+ }
68164
+ async #create(name, spec, token, folder) {
68165
+ const home = homeOf(folder);
68166
+ mkdirSync2(home, { recursive: true });
68167
+ chmodSync3(home, 1023);
68026
68168
  const uid = process.getuid?.();
68027
68169
  const told = {
68028
68170
  [INNER_TOKEN_VARIABLE]: token,
@@ -68046,6 +68188,8 @@ var ContainerRuntime = class {
68046
68188
  ...Object.keys(told).flatMap((name2) => ["--env", name2]),
68047
68189
  // Files it writes in the workspace belong to whoever runs the service.
68048
68190
  ...uid ? ["--user", `${uid}:${process.getgid?.()}`] : [],
68191
+ "--mount",
68192
+ `type=bind,source=${home},target=${HOME}`,
68049
68193
  ...spec.workspace ? [
68050
68194
  "--mount",
68051
68195
  `type=bind,source=${spec.workspace},target=/workspace${spec.access === "read" ? ",readonly" : ""}`
@@ -68073,7 +68217,10 @@ var ContainerRuntime = class {
68073
68217
  async start(spec, folder, events) {
68074
68218
  const name = nameOf(spec.id);
68075
68219
  const token = this.#tokenIn(folder);
68076
- if (!await this.#inspect(name)) await this.#create(name, spec, token);
68220
+ const made = await this.#inspect(name);
68221
+ if (made && this.#imageId && made.Image !== this.#imageId)
68222
+ await this.#remake(name, made, folder, events);
68223
+ if (!await this.#inspect(name)) await this.#create(name, spec, token, folder);
68077
68224
  await docker(["start", name]);
68078
68225
  const inspected = await this.#inspect(name);
68079
68226
  const port = inspected?.NetworkSettings.Ports[`${INNER_PORT}/tcp`]?.[0]?.HostPort;
@@ -68110,6 +68257,7 @@ var ContainerRuntime = class {
68110
68257
  }
68111
68258
  return {
68112
68259
  browserTools: ready.browserTools,
68260
+ version: ready.version,
68113
68261
  /** As they are now: nothing of a container that has stopped can be opened. */
68114
68262
  get surfaces() {
68115
68263
  return ready.surfaces.map(
@@ -68202,33 +68350,6 @@ async function forward(url3, authorization, request, response) {
68202
68350
  sendAnswer(upstream, response);
68203
68351
  }
68204
68352
 
68205
- // src/handlers.ts
68206
- var VERSION2 = "0.0.15";
68207
- function createHandlers(computers) {
68208
- return {
68209
- getInfo: async () => ({
68210
- version: VERSION2,
68211
- isolation: computers.isolation,
68212
- isolationsAvailable: await computers.available()
68213
- }),
68214
- listComputers: async ({ state }) => computers.list(state),
68215
- createComputer: (input2) => computers.create(input2),
68216
- getComputer: async ({ computerID }) => computers.get(computerID),
68217
- killComputer: async ({ computerID }) => {
68218
- await computers.kill(computerID);
68219
- return void 0;
68220
- },
68221
- setComputerTimeout: async ({ computerID, timeout }) => computers.setTimeout(computerID, timeout),
68222
- stopComputer: ({ computerID }) => computers.stop(computerID),
68223
- startComputer: ({ computerID, timeout }) => computers.start(computerID, timeout),
68224
- getComputerLogs: async ({ computerID }) => ({ logs: computers.logs(computerID) }),
68225
- createLiveSession: async ({ computerID, surface, mode }) => computers.liveSession(computerID, surface, mode ?? "watch"),
68226
- openLiveSession: async () => {
68227
- throw new ApiError(426, "Open it as a WebSocket.");
68228
- }
68229
- };
68230
- }
68231
-
68232
68353
  // src/running.ts
68233
68354
  import { spawn as spawn4 } from "node:child_process";
68234
68355
  import { closeSync, openSync, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
@@ -69101,7 +69222,7 @@ var Computer = class _Computer {
69101
69222
  };
69102
69223
 
69103
69224
  // src/service.ts
69104
- import { readFileSync as readFileSync6, rmSync as rmSync3 } from "node:fs";
69225
+ import { readFileSync as readFileSync6, rmSync as rmSync6 } from "node:fs";
69105
69226
  import {
69106
69227
  createServer as createServer5,
69107
69228
  STATUS_CODES as STATUS_CODES3
@@ -84921,7 +85042,7 @@ var NodeStreamableHTTPServerTransport2 = class {
84921
85042
 
84922
85043
  // src/computers.ts
84923
85044
  import { randomBytes as randomBytes5 } from "node:crypto";
84924
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
85045
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
84925
85046
  import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
84926
85047
  var READ = ["about", "read", "list", "search"];
84927
85048
  var FILES = [...READ, "edit"];
@@ -84934,8 +85055,8 @@ var MAX_LOG2 = 256 * 1024;
84934
85055
  var MAX_TIMER_S = 2e6;
84935
85056
  var TICKET_MS = 6e4;
84936
85057
  var expiry = (seconds2) => new Date(Date.now() + seconds2 * 1e3).toISOString();
84937
- var messageOf = (error62) => error62 instanceof Error ? error62.message : String(error62);
84938
- var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf(error62));
85058
+ var messageOf2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
85059
+ var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf2(error62));
84939
85060
  var Computers = class {
84940
85061
  #data;
84941
85062
  #base;
@@ -84969,7 +85090,7 @@ var Computers = class {
84969
85090
  if (stored.endAt && Date.parse(stored.endAt) <= Date.now()) {
84970
85091
  stored.state = "stopped";
84971
85092
  await this.#runtimes[stored.isolation].stopLeftover(stored.id).catch(
84972
- (error62) => this.#log(stored.id, `What it left running wasn't stopped: ${messageOf(error62)}`)
85093
+ (error62) => this.#log(stored.id, `What it left running wasn't stopped: ${messageOf2(error62)}`)
84973
85094
  );
84974
85095
  continue;
84975
85096
  }
@@ -84977,7 +85098,7 @@ var Computers = class {
84977
85098
  const live = this.#live.get(stored.id);
84978
85099
  if (live) live.failed = true;
84979
85100
  this.#arm(stored);
84980
- this.#log(stored.id, `It didn't start again: ${messageOf(error62)}`);
85101
+ this.#log(stored.id, `It didn't start again: ${messageOf2(error62)}`);
84981
85102
  });
84982
85103
  }
84983
85104
  this.#save();
@@ -85016,7 +85137,7 @@ var Computers = class {
85016
85137
  };
85017
85138
  if (!workspace && access !== "none") {
85018
85139
  stored.workspace = join10(this.#folder(id), "workspace");
85019
- mkdirSync2(stored.workspace, { recursive: true, mode: 448 });
85140
+ mkdirSync3(stored.workspace, { recursive: true, mode: 448 });
85020
85141
  }
85021
85142
  this.#stored.set(id, stored);
85022
85143
  try {
@@ -85026,7 +85147,7 @@ var Computers = class {
85026
85147
  this.#stored.delete(id);
85027
85148
  await this.#runtimes[stored.isolation].remove(id).catch(() => {
85028
85149
  });
85029
- rmSync2(this.#folder(id), { recursive: true, force: true });
85150
+ rmSync5(this.#folder(id), { recursive: true, force: true });
85030
85151
  throw refusalOf(error62);
85031
85152
  }
85032
85153
  this.#save();
@@ -85100,7 +85221,7 @@ var Computers = class {
85100
85221
  }
85101
85222
  this.#stored.delete(id);
85102
85223
  this.#save();
85103
- rmSync2(this.#folder(id), { recursive: true, force: true });
85224
+ rmSync5(this.#folder(id), { recursive: true, force: true });
85104
85225
  }
85105
85226
  logs(id) {
85106
85227
  this.#found(id);
@@ -85201,6 +85322,7 @@ var Computers = class {
85201
85322
  state: stored.state === "running" && down2 ? "failed" : stored.state,
85202
85323
  access: stored.access,
85203
85324
  isolation: stored.isolation,
85325
+ version: stored.version ?? null,
85204
85326
  browser: stored.browser,
85205
85327
  workspace: stored.workspace ?? null,
85206
85328
  startedAt: stored.startedAt,
@@ -85234,6 +85356,9 @@ var Computers = class {
85234
85356
  }
85235
85357
  }
85236
85358
  );
85359
+ const version3 = stored.isolation === "none" ? VERSION2 : live.running.version;
85360
+ if (version3) stored.version = version3;
85361
+ else delete stored.version;
85237
85362
  this.#arm(stored);
85238
85363
  }
85239
85364
  #arm(stored) {
@@ -85271,7 +85396,7 @@ var OPENAPI = {
85271
85396
  "openapi": "3.1.0",
85272
85397
  "info": {
85273
85398
  "title": "AgentComputer",
85274
- "version": "0.0.15",
85399
+ "version": "0.0.17",
85275
85400
  "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.",
85276
85401
  "license": {
85277
85402
  "name": "Apache-2.0",
@@ -85825,6 +85950,7 @@ var OPENAPI = {
85825
85950
  "state",
85826
85951
  "access",
85827
85952
  "isolation",
85953
+ "version",
85828
85954
  "browser",
85829
85955
  "workspace",
85830
85956
  "startedAt",
@@ -85849,6 +85975,13 @@ var OPENAPI = {
85849
85975
  "$ref": "#/components/schemas/Isolation",
85850
85976
  "description": "The isolation it was made in. It is never run in another."
85851
85977
  },
85978
+ "version": {
85979
+ "type": [
85980
+ "string",
85981
+ "null"
85982
+ ],
85983
+ "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."
85984
+ },
85852
85985
  "browser": {
85853
85986
  "type": "boolean"
85854
85987
  },
@@ -87120,6 +87253,7 @@ var OPERATIONS = [
87120
87253
  "state",
87121
87254
  "access",
87122
87255
  "isolation",
87256
+ "version",
87123
87257
  "browser",
87124
87258
  "workspace",
87125
87259
  "startedAt",
@@ -87161,6 +87295,13 @@ var OPERATIONS = [
87161
87295
  ],
87162
87296
  "description": "The isolation it was made in. It is never run in another."
87163
87297
  },
87298
+ "version": {
87299
+ "type": [
87300
+ "string",
87301
+ "null"
87302
+ ],
87303
+ "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."
87304
+ },
87164
87305
  "browser": {
87165
87306
  "type": "boolean"
87166
87307
  },
@@ -87297,6 +87438,7 @@ var OPERATIONS = [
87297
87438
  "state",
87298
87439
  "access",
87299
87440
  "isolation",
87441
+ "version",
87300
87442
  "browser",
87301
87443
  "workspace",
87302
87444
  "startedAt",
@@ -87338,6 +87480,13 @@ var OPERATIONS = [
87338
87480
  ],
87339
87481
  "description": "The isolation it was made in. It is never run in another."
87340
87482
  },
87483
+ "version": {
87484
+ "type": [
87485
+ "string",
87486
+ "null"
87487
+ ],
87488
+ "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."
87489
+ },
87341
87490
  "browser": {
87342
87491
  "type": "boolean"
87343
87492
  },
@@ -87447,6 +87596,7 @@ var OPERATIONS = [
87447
87596
  "state",
87448
87597
  "access",
87449
87598
  "isolation",
87599
+ "version",
87450
87600
  "browser",
87451
87601
  "workspace",
87452
87602
  "startedAt",
@@ -87488,6 +87638,13 @@ var OPERATIONS = [
87488
87638
  ],
87489
87639
  "description": "The isolation it was made in. It is never run in another."
87490
87640
  },
87641
+ "version": {
87642
+ "type": [
87643
+ "string",
87644
+ "null"
87645
+ ],
87646
+ "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."
87647
+ },
87491
87648
  "browser": {
87492
87649
  "type": "boolean"
87493
87650
  },
@@ -87688,6 +87845,7 @@ var OPERATIONS = [
87688
87845
  "state",
87689
87846
  "access",
87690
87847
  "isolation",
87848
+ "version",
87691
87849
  "browser",
87692
87850
  "workspace",
87693
87851
  "startedAt",
@@ -87729,6 +87887,13 @@ var OPERATIONS = [
87729
87887
  ],
87730
87888
  "description": "The isolation it was made in. It is never run in another."
87731
87889
  },
87890
+ "version": {
87891
+ "type": [
87892
+ "string",
87893
+ "null"
87894
+ ],
87895
+ "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."
87896
+ },
87732
87897
  "browser": {
87733
87898
  "type": "boolean"
87734
87899
  },
@@ -87822,6 +87987,7 @@ var OPERATIONS = [
87822
87987
  "state",
87823
87988
  "access",
87824
87989
  "isolation",
87990
+ "version",
87825
87991
  "browser",
87826
87992
  "workspace",
87827
87993
  "startedAt",
@@ -87863,6 +88029,13 @@ var OPERATIONS = [
87863
88029
  ],
87864
88030
  "description": "The isolation it was made in. It is never run in another."
87865
88031
  },
88032
+ "version": {
88033
+ "type": [
88034
+ "string",
88035
+ "null"
88036
+ ],
88037
+ "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."
88038
+ },
87866
88039
  "browser": {
87867
88040
  "type": "boolean"
87868
88041
  },
@@ -87961,6 +88134,7 @@ var OPERATIONS = [
87961
88134
  "state",
87962
88135
  "access",
87963
88136
  "isolation",
88137
+ "version",
87964
88138
  "browser",
87965
88139
  "workspace",
87966
88140
  "startedAt",
@@ -88002,6 +88176,13 @@ var OPERATIONS = [
88002
88176
  ],
88003
88177
  "description": "The isolation it was made in. It is never run in another."
88004
88178
  },
88179
+ "version": {
88180
+ "type": [
88181
+ "string",
88182
+ "null"
88183
+ ],
88184
+ "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."
88185
+ },
88005
88186
  "browser": {
88006
88187
  "type": "boolean"
88007
88188
  },
@@ -88415,7 +88596,7 @@ ${JSON.stringify({ code: status, message })}
88415
88596
  const scheme = options.tls ? "https" : "http";
88416
88597
  base = options.publicUrl?.replace(/\/+$/, "") ?? `${scheme}://${bound.family === "IPv6" ? `[${bound.address}]` : bound.address}:${bound.port}`;
88417
88598
  const record3 = recordOf(data);
88418
- rmSync3(record3, { force: true });
88599
+ rmSync6(record3, { force: true });
88419
88600
  await computers.resume();
88420
88601
  writePrivate(
88421
88602
  record3,
@@ -88431,7 +88612,7 @@ ${JSON.stringify({ code: status, message })}
88431
88612
  process.stderr.write(`${String(error62)}
88432
88613
  `);
88433
88614
  });
88434
- rmSync3(record3, { force: true });
88615
+ rmSync6(record3, { force: true });
88435
88616
  process.exit(0);
88436
88617
  };
88437
88618
  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.15",
3
+ "version": "0.0.17",
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": {