@agentorg/computer 0.0.11 → 0.0.13

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
@@ -22,7 +22,7 @@ That prints the computer's `computerID`, its endpoints and its `accessToken`, on
22
22
  `agentcomputer serve --isolation container` makes each computer a container of its own, which needs Docker on the same machine. The service follows your `DOCKER_HOST` or current context and never changes them.
23
23
 
24
24
  - The 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.
25
- - `--workspace` is mounted at `/workspace`, read-only at `--access read`, and nothing else of the machine is visible. Docker has to be able to share that folder. Without `--workspace`, the service makes one.
25
+ - `--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.
26
26
  - It runs as the user who started the service, with no capabilities.
27
27
  - `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.
28
28
 
@@ -30,11 +30,29 @@ That prints the computer's `computerID`, its endpoints and its `accessToken`, on
30
30
 
31
31
  | `--access` | Tools at the `computer` endpoint |
32
32
  | ---------- | --------------------------------------------------------- |
33
- | `read` | `read`, `list`, `search` |
33
+ | `read` | `about`, `read`, `list`, `search` |
34
34
  | `files` | Those, and `edit` |
35
35
  | `terminal` | Those, and `exec_command`, `write_stdin`, `list_sessions` |
36
36
  | `none` | No `computer` endpoint; needs `--browser` |
37
37
 
38
+ `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, of whatever size it comes to: a `fullPage` one of a long page can be many megabytes. A container made by an earlier version keeps the program it was made with, and refuses a picture as a binary file until it is killed and made again.
39
+
40
+ `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:
41
+
42
+ | `label` | `value` |
43
+ | ----------- | ----------------------------------------------------------------------------------------- |
44
+ | `Where` | The machine's own name |
45
+ | `Folder` | The workspace's path on that machine, as a path |
46
+ | `Reaches` | `Read-only files`, `Files`, or `Files and terminal`; with a browser, `, Browser` after it |
47
+ | `Isolation` | `None: it acts as you`, as a caution, or `A container of its own` |
48
+ | `CPU` | Used, of all the machine's CPUs, as a whole percentage: `37%` |
49
+ | `Memory` | Used of the machine's total, counted in 1024s and written GB: `9.2 of 16 GB` |
50
+ | `Disk` | Used of total, of the volume the workspace is on, counted in 1000s: `212 of 494 GB` |
51
+
52
+ - **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.
53
+ - **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.
54
+ - **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.
55
+
38
56
  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 service enforces all of this, whatever the client does.
39
57
 
40
58
  `--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.
@@ -169,7 +187,7 @@ Every running computer has one more, to watch only:
169
187
  - **`load`** (protocol `metrics@1`): how busy it is. The service sends a reading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later: `{"type":"reading","scope":"machine","cpuUsedPct":37.2,"memUsed":…,"memTotal":…,"diskUsed":…,"diskTotal":…}`, in bytes. `openapi.yaml` has each field, as `LoadReading`.
170
188
  - **`scope` says whose numbers they are.** A computer in a container reads its own (`computer`), against the machine's totals, since a container has no limit of its own. A computer with no isolation has no edge to measure at, so it reads the whole machine's (`machine`), as every other computer of that service does; so does a container that can't read its own.
171
189
  - **The disk is the volume its workspace is on,** not the workspace's own size.
172
- - **Nothing depends on it.** Nothing is read while nobody watches, a reading that can't be taken is left out, and a socket that hasn't read the one before is not sent the next.
190
+ - **Nothing depends on it.** Nothing is read while nobody watches or asks, a reading that can't be taken is left out, and a socket that hasn't read the one before is not sent the next.
173
191
 
174
192
  `POST /v1/computers/{computerID}/live` with `{ "surface": "terminal", "mode": "operate" }` gives a ticket: a `url` to open once within a minute, which needs no token. A request with an `Origin` header is refused unless `serve --allow-origin` names that origin, so a page can't open one. The SDK does the same with `computer.terminals.connect({ onEvent })`, or gives the ticket alone with `computer.liveSession("files")` or `computer.liveSession("browser", "operate")`, and the CLI with `agentcomputer live <id> --surface terminal --mode operate`.
175
193
 
@@ -1,4 +1,4 @@
1
- @agentorg/exec 0.0.5 (Apache-2.0)
1
+ @agentorg/exec 0.0.7 (Apache-2.0)
2
2
 
3
3
  Apache License
4
4
  Version 2.0, January 2004
@@ -3268,7 +3268,7 @@ var require_stream = __commonJS({
3268
3268
  };
3269
3269
  duplex._final = function(callback) {
3270
3270
  if (ws.readyState === ws.CONNECTING) {
3271
- ws.once("open", function open4() {
3271
+ ws.once("open", function open5() {
3272
3272
  duplex._final(callback);
3273
3273
  });
3274
3274
  return;
@@ -3289,7 +3289,7 @@ var require_stream = __commonJS({
3289
3289
  };
3290
3290
  duplex._write = function(chunk, encoding, callback) {
3291
3291
  if (ws.readyState === ws.CONNECTING) {
3292
- ws.once("open", function open4() {
3292
+ ws.once("open", function open5() {
3293
3293
  duplex._write(chunk, encoding, callback);
3294
3294
  });
3295
3295
  return;
@@ -3849,7 +3849,8 @@ async function serveBrowserMcp(options) {
3849
3849
  import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
3850
3850
  import { readFileSync as readFileSync3 } from "node:fs";
3851
3851
  import { connect } from "node:net";
3852
- import { basename as basename3, join as join8 } from "node:path";
3852
+ import { hostname as hostname4 } from "node:os";
3853
+ import { join as join8 } from "node:path";
3853
3854
  import { createInterface as createInterface2 } from "node:readline";
3854
3855
  import { Readable as Readable3 } from "node:stream";
3855
3856
  import { promisify as promisify2 } from "node:util";
@@ -5806,9 +5807,9 @@ var asciiTabOrNewline = /[\t\n\r]/g;
5806
5807
  function stripTabAndNewline(value) {
5807
5808
  return value.replace(asciiTabOrNewline, "");
5808
5809
  }
5809
- function urlHostnameOk(url3, hostname3) {
5810
- hostname3.lastIndex = 0;
5811
- return hostname3.test(url3.hostname);
5810
+ function urlHostnameOk(url3, hostname5) {
5811
+ hostname5.lastIndex = 0;
5812
+ return hostname5.test(url3.hostname);
5812
5813
  }
5813
5814
  function urlProtocolOk(url3, protocol) {
5814
5815
  protocol.lastIndex = 0;
@@ -24148,8 +24149,8 @@ function applyPostAuth(clientId, clientSecret, params) {
24148
24149
  function applyPublicAuth(clientId, params) {
24149
24150
  params.set("client_id", clientId);
24150
24151
  }
24151
- function isLoopbackHost(hostname3) {
24152
- return hostname3 === "localhost" || hostname3.endsWith(".localhost") || hostname3 === "127.0.0.1" || hostname3 === "[::1]" || hostname3 === "::1";
24152
+ function isLoopbackHost(hostname5) {
24153
+ return hostname5 === "localhost" || hostname5.endsWith(".localhost") || hostname5 === "127.0.0.1" || hostname5 === "[::1]" || hostname5 === "::1";
24153
24154
  }
24154
24155
  function assertSecureTokenEndpoint(tokenEndpoint) {
24155
24156
  const url3 = new URL(String(tokenEndpoint));
@@ -28158,6 +28159,7 @@ import { join as join7 } from "node:path";
28158
28159
  // src/runtime.ts
28159
28160
  import { chmodSync as chmodSync2, existsSync, statSync as statSync2 } from "node:fs";
28160
28161
  import { createRequire as createRequire3 } from "node:module";
28162
+ import { hostname as hostname3 } from "node:os";
28161
28163
  import { basename as basename2, dirname as dirname3, join as join6 } from "node:path";
28162
28164
 
28163
28165
  // node_modules/@agentorg/exec/dist/lib.js
@@ -28167,12 +28169,13 @@ import { STATUS_CODES, ServerResponse, createServer } from "node:http";
28167
28169
  import { Http2ServerRequest, constants } from "node:http2";
28168
28170
  import { Readable as Readable2 } from "node:stream";
28169
28171
  import { randomBytes as randomBytes3 } from "node:crypto";
28170
- import { lstat as lstat2, open as open2, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2 } from "node:fs/promises";
28172
+ import { lstat as lstat2, open as open3, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2 } from "node:fs/promises";
28171
28173
  import { dirname as dirname2, join as join3 } from "node:path";
28172
28174
  import { randomBytes as randomBytes2 } from "node:crypto";
28173
28175
  import {
28174
28176
  chmod,
28175
28177
  lstat,
28178
+ open as open2,
28176
28179
  readdir,
28177
28180
  readFile,
28178
28181
  realpath,
@@ -31928,9 +31931,9 @@ var asciiTabOrNewline2 = /[\t\n\r]/g;
31928
31931
  function stripTabAndNewline2(value) {
31929
31932
  return value.replace(asciiTabOrNewline2, "");
31930
31933
  }
31931
- function urlHostnameOk2(url22, hostname3) {
31932
- hostname3.lastIndex = 0;
31933
- return hostname3.test(url22.hostname);
31934
+ function urlHostnameOk2(url22, hostname32) {
31935
+ hostname32.lastIndex = 0;
31936
+ return hostname32.test(url22.hostname);
31934
31937
  }
31935
31938
  function urlProtocolOk2(url22, protocol) {
31936
31939
  protocol.lastIndex = 0;
@@ -34316,7 +34319,7 @@ function bucketFor2(state, inst) {
34316
34319
  return bucket;
34317
34320
  }
34318
34321
  var handoff2;
34319
- var open3 = [];
34322
+ var open4 = [];
34320
34323
  var memo3 = {
34321
34324
  alloc(_inst, payload, empty) {
34322
34325
  const bucket = handoff2;
@@ -34325,7 +34328,7 @@ var memo3 = {
34325
34328
  handoff2 = void 0;
34326
34329
  const entry = { value: empty, issues: null };
34327
34330
  bucket.set(payload.value, entry);
34328
- open3.push(entry);
34331
+ open4.push(entry);
34329
34332
  return empty;
34330
34333
  },
34331
34334
  guard(inst) {
@@ -34396,10 +34399,10 @@ var memo3 = {
34396
34399
  return payload;
34397
34400
  }
34398
34401
  handoff2 = bucket;
34399
- const depth = open3.length;
34402
+ const depth = open4.length;
34400
34403
  const result = base(payload, ctx);
34401
34404
  handoff2 = void 0;
34402
- const entry = open3.length > depth ? open3.pop() : void 0;
34405
+ const entry = open4.length > depth ? open4.pop() : void 0;
34403
34406
  if (result instanceof Promise) {
34404
34407
  return result.then((r) => {
34405
34408
  if (entry)
@@ -66156,6 +66159,21 @@ var MAX_READ_CHARS = 28e3;
66156
66159
  var MAX_LINE_CHARS = 2e3;
66157
66160
  var MAX_ENTRIES = 1e3;
66158
66161
  var MAX_EDIT_BYTES = 8 * 1024 * 1024;
66162
+ var MAX_PICTURE_BYTES = 3 * 1024 * 1024;
66163
+ async function pictureKind(path) {
66164
+ const file2 = await open2(path);
66165
+ try {
66166
+ const { buffer, bytesRead } = await file2.read(Buffer.alloc(12), 0, 12, 0);
66167
+ const start = buffer.toString("latin1", 0, bytesRead);
66168
+ if (start.startsWith("\x89PNG\r\n\n")) return "image/png";
66169
+ if (start.startsWith("\xFF\xD8\xFF")) return "image/jpeg";
66170
+ if (start.startsWith("GIF87a") || start.startsWith("GIF89a")) return "image/gif";
66171
+ if (start.startsWith("RIFF") && start.slice(8) === "WEBP") return "image/webp";
66172
+ return void 0;
66173
+ } finally {
66174
+ await file2.close();
66175
+ }
66176
+ }
66159
66177
  var reason = (error62) => {
66160
66178
  const { code, path } = error62;
66161
66179
  if (code === "ENOENT") return `No such file or folder: ${path}`;
@@ -66211,9 +66229,19 @@ function createFiles(root) {
66211
66229
  return bytes.toString("utf8");
66212
66230
  };
66213
66231
  return {
66214
- /** Numbered lines of a file. */
66232
+ /** Numbered lines of a file, or the file itself where it is a picture. */
66215
66233
  read: (input2) => refusing(async () => {
66216
- const lines = (await text22(await at(input2.path), input2.path)).split("\n");
66234
+ const path = await at(input2.path);
66235
+ const { size } = await aFile(path, input2.path);
66236
+ const mimeType = await pictureKind(path);
66237
+ if (mimeType) {
66238
+ if (size > MAX_PICTURE_BYTES)
66239
+ throw new ExecRefused(
66240
+ `${input2.path} is too large to read as a picture: ${size.toLocaleString("en-US")} bytes, and the most is ${MAX_PICTURE_BYTES.toLocaleString("en-US")}.`
66241
+ );
66242
+ return { mimeType, bytes: await readFile(path) };
66243
+ }
66244
+ const lines = (await text22(path, input2.path)).split("\n");
66217
66245
  const first2 = Math.max(input2.offset ?? 1, 1);
66218
66246
  const shown = lines.slice(first2 - 1, first2 - 1 + (input2.limit ?? DEFAULT_LINES)).map((line, i) => `${first2 + i} ${cut(line, MAX_LINE_CHARS)}`);
66219
66247
  const last = first2 + shown.length - 1;
@@ -66423,7 +66451,7 @@ function createHostFiles(root, level) {
66423
66451
  throw new ExecRefused("A read starts at 0 or later and takes 1 to 1,048,576 bytes.");
66424
66452
  const real = await inside(root, path);
66425
66453
  const found = await aFile(real, path);
66426
- const file2 = await open2(real, "r");
66454
+ const file2 = await open3(real, "r");
66427
66455
  try {
66428
66456
  const bytes = Buffer.alloc(Math.max(0, Math.min(length, found.size - offset)));
66429
66457
  const { bytesRead } = await file2.read(bytes, 0, bytes.length, offset);
@@ -66452,7 +66480,7 @@ function createHostFiles(root, level) {
66452
66480
  if (before?.isDirectory()) throw new ExecRefused(`${path} is a folder.`);
66453
66481
  await stat2(dirname2(target));
66454
66482
  const temporary = join3(dirname2(target), `.agentexec-${randomBytes3(6).toString("hex")}`);
66455
- const file2 = await open2(temporary, "wx");
66483
+ const file2 = await open3(temporary, "wx");
66456
66484
  const discard = async () => {
66457
66485
  await file2.close().catch(() => {
66458
66486
  });
@@ -66525,6 +66553,12 @@ var answering = async (work) => {
66525
66553
  return refused(error62);
66526
66554
  }
66527
66555
  };
66556
+ var pictured = (path, { mimeType, bytes }) => ({
66557
+ content: [
66558
+ { type: "text", text: `${path}: ${mimeType}` },
66559
+ { type: "image", mimeType, data: bytes.toString("base64") }
66560
+ ]
66561
+ });
66528
66562
  var saying = async (work) => {
66529
66563
  try {
66530
66564
  return text(await work());
@@ -66541,9 +66575,30 @@ var Answered = {
66541
66575
  var YieldTime = external_exports.number().int().positive().optional();
66542
66576
  var MaxTokens = external_exports.number().int().positive().optional();
66543
66577
  var READ_ONLY = { readOnlyHint: true };
66544
- function createServer2(root, sessions, version22, level) {
66578
+ var TOOLS = [
66579
+ "exec_command",
66580
+ "write_stdin",
66581
+ "list_sessions",
66582
+ "read",
66583
+ "list",
66584
+ "search",
66585
+ "edit"
66586
+ ];
66587
+ function createServer2(root, sessions, version22, level, extraTools = []) {
66545
66588
  const server = new McpServer({ name: "agentexec", version: version22 });
66546
66589
  const files = createFiles(root);
66590
+ for (const { name, description, readOnly, call } of extraTools)
66591
+ server.registerTool(
66592
+ name,
66593
+ { description, ...readOnly ? { annotations: READ_ONLY } : {} },
66594
+ async () => {
66595
+ try {
66596
+ return text(String(await call()));
66597
+ } catch (error62) {
66598
+ return { isError: true, ...text(error62 instanceof Error ? error62.message : String(error62)) };
66599
+ }
66600
+ }
66601
+ );
66547
66602
  if (level === "terminal") {
66548
66603
  server.registerTool(
66549
66604
  "exec_command",
@@ -66603,7 +66658,7 @@ function createServer2(root, sessions, version22, level) {
66603
66658
  server.registerTool(
66604
66659
  "read",
66605
66660
  {
66606
- description: "Reads a text file as numbered lines.",
66661
+ description: "Reads a text file as numbered lines, or shows a PNG, JPEG, GIF or WebP of up to 3 MiB as an image; offset and limit are for text.",
66607
66662
  inputSchema: {
66608
66663
  path: external_exports.string().min(1).describe("Inside the workspace; relative to it, or absolute."),
66609
66664
  offset: external_exports.number().int().positive().optional().describe("The first line, from 1."),
@@ -66611,7 +66666,14 @@ function createServer2(root, sessions, version22, level) {
66611
66666
  },
66612
66667
  annotations: READ_ONLY
66613
66668
  },
66614
- (input2) => saying(() => files.read(input2))
66669
+ async (input2) => {
66670
+ try {
66671
+ const read = await files.read(input2);
66672
+ return typeof read === "string" ? text(read) : pictured(input2.path, read);
66673
+ } catch (error62) {
66674
+ return refused(error62);
66675
+ }
66676
+ }
66615
66677
  );
66616
66678
  server.registerTool(
66617
66679
  "list",
@@ -66654,7 +66716,7 @@ function createServer2(root, sessions, version22, level) {
66654
66716
  );
66655
66717
  return server;
66656
66718
  }
66657
- var VERSION = "0.0.5";
66719
+ var VERSION = "0.0.7";
66658
66720
  function createAgentExec(options) {
66659
66721
  const root = realpathSync(resolve3(options.workspace));
66660
66722
  const level = options.level ?? "read";
@@ -66671,9 +66733,15 @@ function createAgentExec(options) {
66671
66733
  options.pty
66672
66734
  );
66673
66735
  const host = createHostFiles(root, level);
66736
+ const extraTools = [...options.extraTools ?? []];
66737
+ const names = new Set(TOOLS);
66738
+ for (const { name } of extraTools) {
66739
+ if (names.has(name)) throw new Error(`There is already a tool called ${name}.`);
66740
+ names.add(name);
66741
+ }
66674
66742
  return {
66675
66743
  async handle(request, response, body) {
66676
- const server = createServer2(root, sessions, VERSION, level);
66744
+ const server = createServer2(root, sessions, VERSION, level, extraTools);
66677
66745
  const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
66678
66746
  response.once("close", () => {
66679
66747
  void transport.close();
@@ -66703,6 +66771,48 @@ function createAgentExec(options) {
66703
66771
  };
66704
66772
  }
66705
66773
 
66774
+ // src/about.ts
66775
+ var REACHES = {
66776
+ read: "Read-only files",
66777
+ files: "Files",
66778
+ terminal: "Files and terminal"
66779
+ };
66780
+ var GIB = 1024 ** 3;
66781
+ var GB = 1e3 ** 3;
66782
+ var tenths = (count) => String(Math.round(count * 10) / 10);
66783
+ var whole = (count) => String(Math.round(count));
66784
+ function aboutTool(facts, load) {
66785
+ return {
66786
+ name: "about",
66787
+ description: 'What this computer is, in the words a person looking at it reads: where it is, its folder, what it reaches, how it is isolated and how loaded it is. Takes nothing. Answers JSON text: {"rows": [{"label": "Where", "value": "..."}]}, with "path": true on a value that is a path and "caution": true on one to be read as a caution.',
66788
+ readOnly: true,
66789
+ async call() {
66790
+ const { outside, inside: inside2 } = facts.workspace;
66791
+ const rows = [
66792
+ { label: "Where", value: facts.machine },
66793
+ // In a container its commands find the folder somewhere else, so the row says both.
66794
+ inside2 === outside ? { label: "Folder", value: outside, path: true } : { label: "Folder", value: `${outside} on the machine, mounted at ${inside2} in it` },
66795
+ { label: "Reaches", value: REACHES[facts.access] + (facts.browser ? ", Browser" : "") },
66796
+ facts.contained ? { label: "Isolation", value: "A container of its own" } : { label: "Isolation", value: "None: it acts as you", caution: true }
66797
+ ];
66798
+ const reading = await load.now();
66799
+ if (reading)
66800
+ rows.push(
66801
+ { label: "CPU", value: `${whole(reading.cpuUsedPct)}%` },
66802
+ {
66803
+ label: "Memory",
66804
+ value: `${tenths(reading.memUsed / GIB)} of ${tenths(reading.memTotal / GIB)} GB`
66805
+ },
66806
+ {
66807
+ label: "Disk",
66808
+ value: `${whole(reading.diskUsed / GB)} of ${whole(reading.diskTotal / GB)} GB`
66809
+ }
66810
+ );
66811
+ return JSON.stringify({ rows });
66812
+ }
66813
+ };
66814
+ }
66815
+
66706
66816
  // src/screencast.ts
66707
66817
  var FIRST_FRAME_MS = 500;
66708
66818
  var RESTARTS = 3;
@@ -67123,7 +67233,7 @@ var MAX_BUFFERED2 = 4 * 1024 * 1024;
67123
67233
  var MAX_UPLOADS = 64;
67124
67234
  var reason2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
67125
67235
  var text2 = (value) => typeof value === "string";
67126
- var whole = (value) => Number.isSafeInteger(value);
67236
+ var whole2 = (value) => Number.isSafeInteger(value);
67127
67237
  var sleep4 = (ms) => new Promise((done) => setTimeout(done, ms));
67128
67238
  var FilesLive = class {
67129
67239
  #files;
@@ -67242,7 +67352,7 @@ var FilesLive = class {
67242
67352
  awaited = "refused";
67243
67353
  if (mode !== "operate") throw new Error("This socket watches: it can't write.");
67244
67354
  const { path, offset, length, last } = request;
67245
- if (!text2(path) || !whole(offset) || offset < 0 || !whole(length) || length < 0 || length > MAX_PART || typeof last !== "boolean")
67355
+ if (!text2(path) || !whole2(offset) || offset < 0 || !whole2(length) || length < 0 || length > MAX_PART || typeof last !== "boolean")
67246
67356
  throw new Error(
67247
67357
  "A write names a path, where it starts, 0 to 1,048,576 bytes, and last."
67248
67358
  );
@@ -67439,6 +67549,7 @@ import { readFile as readFile2 } from "node:fs/promises";
67439
67549
  import { availableParallelism, cpus, freemem, totalmem } from "node:os";
67440
67550
  import { promisify } from "node:util";
67441
67551
  var EVERY_MS = 2e3;
67552
+ var ONCE_MS = 500;
67442
67553
  var run = promisify(execFile);
67443
67554
  var numberOf = (text3, name) => Number(new RegExp(`^${name}\\s+(\\d+)`, "m").exec(text3)?.[1]);
67444
67555
  async function disk(folder) {
@@ -67477,6 +67588,18 @@ async function cgroup() {
67477
67588
  memUsed: Number(current) - numberOf(memory, "inactive_file")
67478
67589
  };
67479
67590
  }
67591
+ function readingOf(before, sample, volume) {
67592
+ const share = (sample.cpu.used - before.used) / (sample.cpu.total - before.total) || 0;
67593
+ const reading = {
67594
+ type: "reading",
67595
+ scope: sample.scope,
67596
+ cpuUsedPct: Math.round(Math.min(Math.max(share, 0), 1) * 1e3) / 10,
67597
+ memUsed: sample.memUsed,
67598
+ memTotal: totalmem(),
67599
+ ...volume
67600
+ };
67601
+ return Number.isFinite(reading.cpuUsedPct + reading.memUsed) ? reading : void 0;
67602
+ }
67480
67603
  var LoadLive = class {
67481
67604
  #folder;
67482
67605
  #contained;
@@ -67510,28 +67633,33 @@ var LoadLive = class {
67510
67633
  this.#timer = this.#before = this.#last = void 0;
67511
67634
  for (const ws of this.#sockets) ws.close(1001, "It stopped.");
67512
67635
  }
67636
+ /** Its own, inside a container that can read it; else the machine's. */
67637
+ #sample() {
67638
+ return this.#contained ? cgroup().catch(machine) : machine();
67639
+ }
67640
+ /** One reading for whoever asks once, taken over half a second. None where it can't be. */
67641
+ async now() {
67642
+ try {
67643
+ const before = await this.#sample();
67644
+ await new Promise((done) => setTimeout(done, ONCE_MS));
67645
+ return readingOf(before.cpu, await this.#sample(), await disk(this.#folder));
67646
+ } catch {
67647
+ return void 0;
67648
+ }
67649
+ }
67513
67650
  /** One at a time, and for the watching it began under: a reading taken too late is left. */
67514
67651
  async #read() {
67515
67652
  const timer = this.#timer;
67516
67653
  if (this.#reading) return;
67517
67654
  this.#reading = true;
67518
67655
  try {
67519
- const sample = await (this.#contained ? cgroup().catch(machine) : machine());
67656
+ const sample = await this.#sample();
67520
67657
  const volume = await disk(this.#folder);
67521
67658
  if (this.#timer !== timer) return;
67522
67659
  const before = this.#before;
67523
67660
  this.#before = sample.cpu;
67524
- if (!before) return;
67525
- const share = (sample.cpu.used - before.used) / (sample.cpu.total - before.total) || 0;
67526
- const reading = {
67527
- type: "reading",
67528
- scope: sample.scope,
67529
- cpuUsedPct: Math.round(Math.min(Math.max(share, 0), 1) * 1e3) / 10,
67530
- memUsed: sample.memUsed,
67531
- memTotal: totalmem(),
67532
- ...volume
67533
- };
67534
- if (!Number.isFinite(reading.cpuUsedPct + reading.memUsed)) return;
67661
+ const reading = before && readingOf(before, sample, volume);
67662
+ if (!reading) return;
67535
67663
  this.#last = JSON.stringify(reading);
67536
67664
  for (const ws of this.#sockets) if (ws.bufferedAmount === 0) ws.send(this.#last);
67537
67665
  } catch {
@@ -67610,7 +67738,7 @@ var ProcessRuntime = class {
67610
67738
  isolation = "none";
67611
67739
  #config;
67612
67740
  #contained;
67613
- /** `contained` is for inside a container, where load is the computer's own. */
67741
+ /** `contained` is for inside a container, where the computer and its load are its own. */
67614
67742
  constructor(config3, contained = false) {
67615
67743
  this.#config = config3;
67616
67744
  this.#contained = contained;
@@ -67622,13 +67750,27 @@ var ProcessRuntime = class {
67622
67750
  async remove() {
67623
67751
  }
67624
67752
  async start(spec, folder, events) {
67753
+ const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67625
67754
  let exec;
67626
- if (spec.access !== "none")
67755
+ if (spec.access !== "none") {
67756
+ const workspace = spec.workspace;
67757
+ const about = aboutTool(
67758
+ {
67759
+ machine: spec.outside?.machine ?? hostname3(),
67760
+ workspace: { outside: spec.outside?.workspace ?? workspace, inside: workspace },
67761
+ access: spec.access,
67762
+ browser: spec.browser,
67763
+ contained: this.#contained
67764
+ },
67765
+ load
67766
+ );
67627
67767
  exec = createAgentExec({
67628
- workspace: spec.workspace,
67768
+ workspace,
67629
67769
  level: spec.access,
67630
- pty: await loadPty()
67770
+ pty: await loadPty(),
67771
+ extraTools: [about]
67631
67772
  });
67773
+ }
67632
67774
  let browser;
67633
67775
  let browserDown = false;
67634
67776
  if (spec.browser) {
@@ -67656,8 +67798,7 @@ var ProcessRuntime = class {
67656
67798
  browserLive = new BrowserLive(browser.endpoint);
67657
67799
  browser.gate = browserLive;
67658
67800
  }
67659
- const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67660
- const files = exec && new FilesLive(exec.files, spec.workspaceName ?? basename2(spec.workspace));
67801
+ const files = exec && new FilesLive(exec.files, basename2(spec.outside?.workspace ?? spec.workspace));
67661
67802
  const live = new Live(exec, spec.access, load, files, browserLive);
67662
67803
  const surfaces = live.surfaces();
67663
67804
  return {
@@ -67688,7 +67829,8 @@ var ProcessRuntime = class {
67688
67829
  // src/inside.ts
67689
67830
  var INNER_PORT = 8e3;
67690
67831
  var INNER_TOKEN_VARIABLE = "AGENTCOMPUTER_INNER_TOKEN";
67691
- var WORKSPACE_NAME_VARIABLE = "AGENTCOMPUTER_WORKSPACE_NAME";
67832
+ var OUTSIDE_WORKSPACE_VARIABLE = "AGENTCOMPUTER_OUTSIDE_WORKSPACE";
67833
+ var OUTSIDE_MACHINE_VARIABLE = "AGENTCOMPUTER_OUTSIDE_MACHINE";
67692
67834
  var BROWSER = "/opt/browsers/headless-shell";
67693
67835
  async function serveInside(options) {
67694
67836
  const token = process.env[INNER_TOKEN_VARIABLE];
@@ -67699,7 +67841,11 @@ async function serveInside(options) {
67699
67841
  {
67700
67842
  id: "inside",
67701
67843
  ...options,
67702
- ...options.access === "none" ? {} : { workspace: "/workspace", workspaceName: process.env[WORKSPACE_NAME_VARIABLE] }
67844
+ ...options.access === "none" ? {} : { workspace: "/workspace" },
67845
+ outside: {
67846
+ workspace: process.env[OUTSIDE_WORKSPACE_VARIABLE],
67847
+ machine: process.env[OUTSIDE_MACHINE_VARIABLE]
67848
+ }
67703
67849
  },
67704
67850
  privateFolder(join7(homedir(), ".agentcomputer")),
67705
67851
  {
@@ -67810,6 +67956,10 @@ var ContainerRuntime = class {
67810
67956
  }
67811
67957
  async #create(name, spec, token) {
67812
67958
  const uid = process.getuid?.();
67959
+ const told = {
67960
+ [INNER_TOKEN_VARIABLE]: token,
67961
+ ...spec.workspace ? { [OUTSIDE_WORKSPACE_VARIABLE]: spec.workspace, [OUTSIDE_MACHINE_VARIABLE]: hostname4() } : {}
67962
+ };
67813
67963
  await docker(
67814
67964
  [
67815
67965
  "create",
@@ -67824,12 +67974,8 @@ var ContainerRuntime = class {
67824
67974
  "no-new-privileges",
67825
67975
  "--publish",
67826
67976
  `127.0.0.1::${INNER_PORT}`,
67827
- // By name, so the value stays out of the command line other users can see.
67828
- "--env",
67829
- INNER_TOKEN_VARIABLE,
67830
- // What its workspace is called, which in there is only /workspace. An image from before
67831
- // this was told leaves it.
67832
- ...spec.workspace ? ["--env", WORKSPACE_NAME_VARIABLE] : [],
67977
+ // By name, so the values stay out of the command line other users can see.
67978
+ ...Object.keys(told).flatMap((name2) => ["--env", name2]),
67833
67979
  // Files it writes in the workspace belong to whoever runs the service.
67834
67980
  ...uid ? ["--user", `${uid}:${process.getgid?.()}`] : [],
67835
67981
  ...spec.workspace ? [
@@ -67842,11 +67988,7 @@ var ContainerRuntime = class {
67842
67988
  spec.access,
67843
67989
  ...spec.browser ? ["--browser"] : []
67844
67990
  ],
67845
- {
67846
- ...process.env,
67847
- [INNER_TOKEN_VARIABLE]: token,
67848
- ...spec.workspace ? { [WORKSPACE_NAME_VARIABLE]: basename3(spec.workspace) } : {}
67849
- }
67991
+ { ...process.env, ...told }
67850
67992
  );
67851
67993
  }
67852
67994
  /** The token between the service and this container, which a restart has to find again. */
@@ -67990,7 +68132,7 @@ async function forward(url3, authorization, request, response) {
67990
68132
  }
67991
68133
 
67992
68134
  // src/handlers.ts
67993
- var VERSION2 = "0.0.11";
68135
+ var VERSION2 = "0.0.13";
67994
68136
  function createHandlers(computers) {
67995
68137
  return {
67996
68138
  getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
@@ -68574,8 +68716,8 @@ async function openLive(session, onMessage) {
68574
68716
  }
68575
68717
  var Terminals = class {
68576
68718
  #open;
68577
- constructor(open4) {
68578
- this.#open = open4;
68719
+ constructor(open5) {
68720
+ this.#open = open5;
68579
68721
  }
68580
68722
  /**
68581
68723
  * Each terminal as it starts, writes and exits: those already there first, each with the end of
@@ -84631,10 +84773,12 @@ var NodeStreamableHTTPServerTransport2 = class {
84631
84773
  import { randomBytes as randomBytes5 } from "node:crypto";
84632
84774
  import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
84633
84775
  import { isAbsolute, join as join9, resolve as resolve4 } from "node:path";
84634
- var TOOLS = {
84635
- read: ["read", "list", "search"],
84636
- files: ["read", "list", "search", "edit"],
84637
- terminal: ["read", "list", "search", "edit", "exec_command", "write_stdin", "list_sessions"]
84776
+ var READ = ["about", "read", "list", "search"];
84777
+ var FILES = [...READ, "edit"];
84778
+ var TOOLS2 = {
84779
+ read: READ,
84780
+ files: FILES,
84781
+ terminal: [...FILES, "exec_command", "write_stdin", "list_sessions"]
84638
84782
  };
84639
84783
  var MAX_LOG = 256 * 1024;
84640
84784
  var MAX_TIMER_S = 2e6;
@@ -84733,7 +84877,7 @@ var Computers = class {
84733
84877
  ...this.#describe(stored),
84734
84878
  uptimeSeconds: stored.state === "running" ? Math.round((Date.now() - Date.parse(stored.startedAt)) / 1e3) : 0,
84735
84879
  tools: {
84736
- computer: stored.access === "none" ? [] : TOOLS[stored.access],
84880
+ computer: stored.access === "none" ? [] : TOOLS2[stored.access],
84737
84881
  browser: live?.running?.browserTools ?? []
84738
84882
  },
84739
84883
  surfaces: live?.running?.surfaces ?? []
@@ -84953,7 +85097,7 @@ var OPENAPI = {
84953
85097
  "openapi": "3.1.0",
84954
85098
  "info": {
84955
85099
  "title": "AgentComputer",
84956
- "version": "0.0.11",
85100
+ "version": "0.0.13",
84957
85101
  "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.",
84958
85102
  "license": {
84959
85103
  "name": "Apache-2.0",
@@ -85405,7 +85549,7 @@ var OPENAPI = {
85405
85549
  "files",
85406
85550
  "terminal"
85407
85551
  ],
85408
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
85552
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
85409
85553
  },
85410
85554
  "ComputerState": {
85411
85555
  "type": "string",
@@ -85618,7 +85762,7 @@ var OPENAPI = {
85618
85762
  "browser",
85619
85763
  "load"
85620
85764
  ],
85621
- "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
85765
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches or asks."
85622
85766
  },
85623
85767
  "LiveMode": {
85624
85768
  "type": "string",
@@ -86795,7 +86939,7 @@ var OPERATIONS = [
86795
86939
  "files",
86796
86940
  "terminal"
86797
86941
  ],
86798
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
86942
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
86799
86943
  },
86800
86944
  "browser": {
86801
86945
  "type": "boolean"
@@ -86884,7 +87028,7 @@ var OPERATIONS = [
86884
87028
  "files",
86885
87029
  "terminal"
86886
87030
  ],
86887
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87031
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
86888
87032
  },
86889
87033
  "browser": {
86890
87034
  "type": "boolean",
@@ -86954,7 +87098,7 @@ var OPERATIONS = [
86954
87098
  "files",
86955
87099
  "terminal"
86956
87100
  ],
86957
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87101
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
86958
87102
  },
86959
87103
  "browser": {
86960
87104
  "type": "boolean"
@@ -87094,7 +87238,7 @@ var OPERATIONS = [
87094
87238
  "files",
87095
87239
  "terminal"
87096
87240
  ],
87097
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87241
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87098
87242
  },
87099
87243
  "browser": {
87100
87244
  "type": "boolean"
@@ -87183,7 +87327,7 @@ var OPERATIONS = [
87183
87327
  "browser",
87184
87328
  "load"
87185
87329
  ],
87186
- "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
87330
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches or asks."
87187
87331
  },
87188
87332
  "protocol": {
87189
87333
  "type": "string"
@@ -87325,7 +87469,7 @@ var OPERATIONS = [
87325
87469
  "files",
87326
87470
  "terminal"
87327
87471
  ],
87328
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87472
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87329
87473
  },
87330
87474
  "browser": {
87331
87475
  "type": "boolean"
@@ -87449,7 +87593,7 @@ var OPERATIONS = [
87449
87593
  "files",
87450
87594
  "terminal"
87451
87595
  ],
87452
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87596
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87453
87597
  },
87454
87598
  "browser": {
87455
87599
  "type": "boolean"
@@ -87578,7 +87722,7 @@ var OPERATIONS = [
87578
87722
  "files",
87579
87723
  "terminal"
87580
87724
  ],
87581
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87725
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87582
87726
  },
87583
87727
  "browser": {
87584
87728
  "type": "boolean"
@@ -87723,7 +87867,7 @@ var OPERATIONS = [
87723
87867
  "browser",
87724
87868
  "load"
87725
87869
  ],
87726
- "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
87870
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches or asks."
87727
87871
  },
87728
87872
  "mode": {
87729
87873
  "allOf": [
@@ -87770,7 +87914,7 @@ var OPERATIONS = [
87770
87914
  "browser",
87771
87915
  "load"
87772
87916
  ],
87773
- "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
87917
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches or asks."
87774
87918
  },
87775
87919
  "mode": {
87776
87920
  "type": "string",
@@ -182,7 +182,7 @@ export interface components {
182
182
  message: string;
183
183
  };
184
184
  /**
185
- * @description How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden.
185
+ * @description How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden.
186
186
  * @enum {string}
187
187
  */
188
188
  Access: "none" | "read" | "files" | "terminal";
@@ -248,7 +248,7 @@ export interface components {
248
248
  };
249
249
  };
250
250
  /**
251
- * @description terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches.
251
+ * @description terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches or asks.
252
252
  * @enum {string}
253
253
  */
254
254
  Surface: "terminal" | "files" | "browser" | "load";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/computer",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
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": {
@@ -31,7 +31,7 @@
31
31
  "playwright-core": "1.65.0-alpha-2026-10-30"
32
32
  },
33
33
  "devDependencies": {
34
- "@agentorg/exec": "0.0.5",
34
+ "@agentorg/exec": "0.0.7",
35
35
  "@biomejs/biome": "2.6.5",
36
36
  "@modelcontextprotocol/client": "2.4.0",
37
37
  "@modelcontextprotocol/node": "2.1.2",