@agentorg/computer 0.0.11 → 0.0.12

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,27 @@ 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
+ `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:
39
+
40
+ | `label` | `value` |
41
+ | ----------- | ----------------------------------------------------------------------------------------- |
42
+ | `Where` | The machine's own name |
43
+ | `Folder` | The workspace's path on that machine, as a path |
44
+ | `Reaches` | `Read-only files`, `Files`, or `Files and terminal`; with a browser, `, Browser` after it |
45
+ | `Isolation` | `None: it acts as you`, as a caution, or `A container of its own` |
46
+ | `CPU` | Used, of all the machine's CPUs, as a whole percentage: `37%` |
47
+ | `Memory` | Used of the machine's total, counted in 1024s and written GB: `9.2 of 16 GB` |
48
+ | `Disk` | Used of total, of the volume the workspace is on, counted in 1000s: `212 of 494 GB` |
49
+
50
+ - **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.
51
+ - **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.
52
+ - **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.
53
+
38
54
  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
55
 
40
56
  `--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 +185,7 @@ Every running computer has one more, to watch only:
169
185
  - **`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
186
  - **`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
187
  - **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.
188
+ - **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
189
 
174
190
  `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
191
 
@@ -1,4 +1,4 @@
1
- @agentorg/exec 0.0.5 (Apache-2.0)
1
+ @agentorg/exec 0.0.6 (Apache-2.0)
2
2
 
3
3
  Apache License
4
4
  Version 2.0, January 2004
@@ -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
@@ -31928,9 +31930,9 @@ var asciiTabOrNewline2 = /[\t\n\r]/g;
31928
31930
  function stripTabAndNewline2(value) {
31929
31931
  return value.replace(asciiTabOrNewline2, "");
31930
31932
  }
31931
- function urlHostnameOk2(url22, hostname3) {
31932
- hostname3.lastIndex = 0;
31933
- return hostname3.test(url22.hostname);
31933
+ function urlHostnameOk2(url22, hostname32) {
31934
+ hostname32.lastIndex = 0;
31935
+ return hostname32.test(url22.hostname);
31934
31936
  }
31935
31937
  function urlProtocolOk2(url22, protocol) {
31936
31938
  protocol.lastIndex = 0;
@@ -66541,9 +66543,30 @@ var Answered = {
66541
66543
  var YieldTime = external_exports.number().int().positive().optional();
66542
66544
  var MaxTokens = external_exports.number().int().positive().optional();
66543
66545
  var READ_ONLY = { readOnlyHint: true };
66544
- function createServer2(root, sessions, version22, level) {
66546
+ var TOOLS = [
66547
+ "exec_command",
66548
+ "write_stdin",
66549
+ "list_sessions",
66550
+ "read",
66551
+ "list",
66552
+ "search",
66553
+ "edit"
66554
+ ];
66555
+ function createServer2(root, sessions, version22, level, extraTools = []) {
66545
66556
  const server = new McpServer({ name: "agentexec", version: version22 });
66546
66557
  const files = createFiles(root);
66558
+ for (const { name, description, readOnly, call } of extraTools)
66559
+ server.registerTool(
66560
+ name,
66561
+ { description, ...readOnly ? { annotations: READ_ONLY } : {} },
66562
+ async () => {
66563
+ try {
66564
+ return text(String(await call()));
66565
+ } catch (error62) {
66566
+ return { isError: true, ...text(error62 instanceof Error ? error62.message : String(error62)) };
66567
+ }
66568
+ }
66569
+ );
66547
66570
  if (level === "terminal") {
66548
66571
  server.registerTool(
66549
66572
  "exec_command",
@@ -66654,7 +66677,7 @@ function createServer2(root, sessions, version22, level) {
66654
66677
  );
66655
66678
  return server;
66656
66679
  }
66657
- var VERSION = "0.0.5";
66680
+ var VERSION = "0.0.6";
66658
66681
  function createAgentExec(options) {
66659
66682
  const root = realpathSync(resolve3(options.workspace));
66660
66683
  const level = options.level ?? "read";
@@ -66671,9 +66694,15 @@ function createAgentExec(options) {
66671
66694
  options.pty
66672
66695
  );
66673
66696
  const host = createHostFiles(root, level);
66697
+ const extraTools = [...options.extraTools ?? []];
66698
+ const names = new Set(TOOLS);
66699
+ for (const { name } of extraTools) {
66700
+ if (names.has(name)) throw new Error(`There is already a tool called ${name}.`);
66701
+ names.add(name);
66702
+ }
66674
66703
  return {
66675
66704
  async handle(request, response, body) {
66676
- const server = createServer2(root, sessions, VERSION, level);
66705
+ const server = createServer2(root, sessions, VERSION, level, extraTools);
66677
66706
  const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
66678
66707
  response.once("close", () => {
66679
66708
  void transport.close();
@@ -66703,6 +66732,48 @@ function createAgentExec(options) {
66703
66732
  };
66704
66733
  }
66705
66734
 
66735
+ // src/about.ts
66736
+ var REACHES = {
66737
+ read: "Read-only files",
66738
+ files: "Files",
66739
+ terminal: "Files and terminal"
66740
+ };
66741
+ var GIB = 1024 ** 3;
66742
+ var GB = 1e3 ** 3;
66743
+ var tenths = (count) => String(Math.round(count * 10) / 10);
66744
+ var whole = (count) => String(Math.round(count));
66745
+ function aboutTool(facts, load) {
66746
+ return {
66747
+ name: "about",
66748
+ 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.',
66749
+ readOnly: true,
66750
+ async call() {
66751
+ const { outside, inside: inside2 } = facts.workspace;
66752
+ const rows = [
66753
+ { label: "Where", value: facts.machine },
66754
+ // In a container its commands find the folder somewhere else, so the row says both.
66755
+ inside2 === outside ? { label: "Folder", value: outside, path: true } : { label: "Folder", value: `${outside} on the machine, mounted at ${inside2} in it` },
66756
+ { label: "Reaches", value: REACHES[facts.access] + (facts.browser ? ", Browser" : "") },
66757
+ facts.contained ? { label: "Isolation", value: "A container of its own" } : { label: "Isolation", value: "None: it acts as you", caution: true }
66758
+ ];
66759
+ const reading = await load.now();
66760
+ if (reading)
66761
+ rows.push(
66762
+ { label: "CPU", value: `${whole(reading.cpuUsedPct)}%` },
66763
+ {
66764
+ label: "Memory",
66765
+ value: `${tenths(reading.memUsed / GIB)} of ${tenths(reading.memTotal / GIB)} GB`
66766
+ },
66767
+ {
66768
+ label: "Disk",
66769
+ value: `${whole(reading.diskUsed / GB)} of ${whole(reading.diskTotal / GB)} GB`
66770
+ }
66771
+ );
66772
+ return JSON.stringify({ rows });
66773
+ }
66774
+ };
66775
+ }
66776
+
66706
66777
  // src/screencast.ts
66707
66778
  var FIRST_FRAME_MS = 500;
66708
66779
  var RESTARTS = 3;
@@ -67123,7 +67194,7 @@ var MAX_BUFFERED2 = 4 * 1024 * 1024;
67123
67194
  var MAX_UPLOADS = 64;
67124
67195
  var reason2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
67125
67196
  var text2 = (value) => typeof value === "string";
67126
- var whole = (value) => Number.isSafeInteger(value);
67197
+ var whole2 = (value) => Number.isSafeInteger(value);
67127
67198
  var sleep4 = (ms) => new Promise((done) => setTimeout(done, ms));
67128
67199
  var FilesLive = class {
67129
67200
  #files;
@@ -67242,7 +67313,7 @@ var FilesLive = class {
67242
67313
  awaited = "refused";
67243
67314
  if (mode !== "operate") throw new Error("This socket watches: it can't write.");
67244
67315
  const { path, offset, length, last } = request;
67245
- if (!text2(path) || !whole(offset) || offset < 0 || !whole(length) || length < 0 || length > MAX_PART || typeof last !== "boolean")
67316
+ if (!text2(path) || !whole2(offset) || offset < 0 || !whole2(length) || length < 0 || length > MAX_PART || typeof last !== "boolean")
67246
67317
  throw new Error(
67247
67318
  "A write names a path, where it starts, 0 to 1,048,576 bytes, and last."
67248
67319
  );
@@ -67439,6 +67510,7 @@ import { readFile as readFile2 } from "node:fs/promises";
67439
67510
  import { availableParallelism, cpus, freemem, totalmem } from "node:os";
67440
67511
  import { promisify } from "node:util";
67441
67512
  var EVERY_MS = 2e3;
67513
+ var ONCE_MS = 500;
67442
67514
  var run = promisify(execFile);
67443
67515
  var numberOf = (text3, name) => Number(new RegExp(`^${name}\\s+(\\d+)`, "m").exec(text3)?.[1]);
67444
67516
  async function disk(folder) {
@@ -67477,6 +67549,18 @@ async function cgroup() {
67477
67549
  memUsed: Number(current) - numberOf(memory, "inactive_file")
67478
67550
  };
67479
67551
  }
67552
+ function readingOf(before, sample, volume) {
67553
+ const share = (sample.cpu.used - before.used) / (sample.cpu.total - before.total) || 0;
67554
+ const reading = {
67555
+ type: "reading",
67556
+ scope: sample.scope,
67557
+ cpuUsedPct: Math.round(Math.min(Math.max(share, 0), 1) * 1e3) / 10,
67558
+ memUsed: sample.memUsed,
67559
+ memTotal: totalmem(),
67560
+ ...volume
67561
+ };
67562
+ return Number.isFinite(reading.cpuUsedPct + reading.memUsed) ? reading : void 0;
67563
+ }
67480
67564
  var LoadLive = class {
67481
67565
  #folder;
67482
67566
  #contained;
@@ -67510,28 +67594,33 @@ var LoadLive = class {
67510
67594
  this.#timer = this.#before = this.#last = void 0;
67511
67595
  for (const ws of this.#sockets) ws.close(1001, "It stopped.");
67512
67596
  }
67597
+ /** Its own, inside a container that can read it; else the machine's. */
67598
+ #sample() {
67599
+ return this.#contained ? cgroup().catch(machine) : machine();
67600
+ }
67601
+ /** One reading for whoever asks once, taken over half a second. None where it can't be. */
67602
+ async now() {
67603
+ try {
67604
+ const before = await this.#sample();
67605
+ await new Promise((done) => setTimeout(done, ONCE_MS));
67606
+ return readingOf(before.cpu, await this.#sample(), await disk(this.#folder));
67607
+ } catch {
67608
+ return void 0;
67609
+ }
67610
+ }
67513
67611
  /** One at a time, and for the watching it began under: a reading taken too late is left. */
67514
67612
  async #read() {
67515
67613
  const timer = this.#timer;
67516
67614
  if (this.#reading) return;
67517
67615
  this.#reading = true;
67518
67616
  try {
67519
- const sample = await (this.#contained ? cgroup().catch(machine) : machine());
67617
+ const sample = await this.#sample();
67520
67618
  const volume = await disk(this.#folder);
67521
67619
  if (this.#timer !== timer) return;
67522
67620
  const before = this.#before;
67523
67621
  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;
67622
+ const reading = before && readingOf(before, sample, volume);
67623
+ if (!reading) return;
67535
67624
  this.#last = JSON.stringify(reading);
67536
67625
  for (const ws of this.#sockets) if (ws.bufferedAmount === 0) ws.send(this.#last);
67537
67626
  } catch {
@@ -67610,7 +67699,7 @@ var ProcessRuntime = class {
67610
67699
  isolation = "none";
67611
67700
  #config;
67612
67701
  #contained;
67613
- /** `contained` is for inside a container, where load is the computer's own. */
67702
+ /** `contained` is for inside a container, where the computer and its load are its own. */
67614
67703
  constructor(config3, contained = false) {
67615
67704
  this.#config = config3;
67616
67705
  this.#contained = contained;
@@ -67622,13 +67711,27 @@ var ProcessRuntime = class {
67622
67711
  async remove() {
67623
67712
  }
67624
67713
  async start(spec, folder, events) {
67714
+ const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67625
67715
  let exec;
67626
- if (spec.access !== "none")
67716
+ if (spec.access !== "none") {
67717
+ const workspace = spec.workspace;
67718
+ const about = aboutTool(
67719
+ {
67720
+ machine: spec.outside?.machine ?? hostname3(),
67721
+ workspace: { outside: spec.outside?.workspace ?? workspace, inside: workspace },
67722
+ access: spec.access,
67723
+ browser: spec.browser,
67724
+ contained: this.#contained
67725
+ },
67726
+ load
67727
+ );
67627
67728
  exec = createAgentExec({
67628
- workspace: spec.workspace,
67729
+ workspace,
67629
67730
  level: spec.access,
67630
- pty: await loadPty()
67731
+ pty: await loadPty(),
67732
+ extraTools: [about]
67631
67733
  });
67734
+ }
67632
67735
  let browser;
67633
67736
  let browserDown = false;
67634
67737
  if (spec.browser) {
@@ -67656,8 +67759,7 @@ var ProcessRuntime = class {
67656
67759
  browserLive = new BrowserLive(browser.endpoint);
67657
67760
  browser.gate = browserLive;
67658
67761
  }
67659
- const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67660
- const files = exec && new FilesLive(exec.files, spec.workspaceName ?? basename2(spec.workspace));
67762
+ const files = exec && new FilesLive(exec.files, basename2(spec.outside?.workspace ?? spec.workspace));
67661
67763
  const live = new Live(exec, spec.access, load, files, browserLive);
67662
67764
  const surfaces = live.surfaces();
67663
67765
  return {
@@ -67688,7 +67790,8 @@ var ProcessRuntime = class {
67688
67790
  // src/inside.ts
67689
67791
  var INNER_PORT = 8e3;
67690
67792
  var INNER_TOKEN_VARIABLE = "AGENTCOMPUTER_INNER_TOKEN";
67691
- var WORKSPACE_NAME_VARIABLE = "AGENTCOMPUTER_WORKSPACE_NAME";
67793
+ var OUTSIDE_WORKSPACE_VARIABLE = "AGENTCOMPUTER_OUTSIDE_WORKSPACE";
67794
+ var OUTSIDE_MACHINE_VARIABLE = "AGENTCOMPUTER_OUTSIDE_MACHINE";
67692
67795
  var BROWSER = "/opt/browsers/headless-shell";
67693
67796
  async function serveInside(options) {
67694
67797
  const token = process.env[INNER_TOKEN_VARIABLE];
@@ -67699,7 +67802,11 @@ async function serveInside(options) {
67699
67802
  {
67700
67803
  id: "inside",
67701
67804
  ...options,
67702
- ...options.access === "none" ? {} : { workspace: "/workspace", workspaceName: process.env[WORKSPACE_NAME_VARIABLE] }
67805
+ ...options.access === "none" ? {} : { workspace: "/workspace" },
67806
+ outside: {
67807
+ workspace: process.env[OUTSIDE_WORKSPACE_VARIABLE],
67808
+ machine: process.env[OUTSIDE_MACHINE_VARIABLE]
67809
+ }
67703
67810
  },
67704
67811
  privateFolder(join7(homedir(), ".agentcomputer")),
67705
67812
  {
@@ -67810,6 +67917,10 @@ var ContainerRuntime = class {
67810
67917
  }
67811
67918
  async #create(name, spec, token) {
67812
67919
  const uid = process.getuid?.();
67920
+ const told = {
67921
+ [INNER_TOKEN_VARIABLE]: token,
67922
+ ...spec.workspace ? { [OUTSIDE_WORKSPACE_VARIABLE]: spec.workspace, [OUTSIDE_MACHINE_VARIABLE]: hostname4() } : {}
67923
+ };
67813
67924
  await docker(
67814
67925
  [
67815
67926
  "create",
@@ -67824,12 +67935,8 @@ var ContainerRuntime = class {
67824
67935
  "no-new-privileges",
67825
67936
  "--publish",
67826
67937
  `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] : [],
67938
+ // By name, so the values stay out of the command line other users can see.
67939
+ ...Object.keys(told).flatMap((name2) => ["--env", name2]),
67833
67940
  // Files it writes in the workspace belong to whoever runs the service.
67834
67941
  ...uid ? ["--user", `${uid}:${process.getgid?.()}`] : [],
67835
67942
  ...spec.workspace ? [
@@ -67842,11 +67949,7 @@ var ContainerRuntime = class {
67842
67949
  spec.access,
67843
67950
  ...spec.browser ? ["--browser"] : []
67844
67951
  ],
67845
- {
67846
- ...process.env,
67847
- [INNER_TOKEN_VARIABLE]: token,
67848
- ...spec.workspace ? { [WORKSPACE_NAME_VARIABLE]: basename3(spec.workspace) } : {}
67849
- }
67952
+ { ...process.env, ...told }
67850
67953
  );
67851
67954
  }
67852
67955
  /** The token between the service and this container, which a restart has to find again. */
@@ -67990,7 +68093,7 @@ async function forward(url3, authorization, request, response) {
67990
68093
  }
67991
68094
 
67992
68095
  // src/handlers.ts
67993
- var VERSION2 = "0.0.11";
68096
+ var VERSION2 = "0.0.12";
67994
68097
  function createHandlers(computers) {
67995
68098
  return {
67996
68099
  getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
@@ -84631,10 +84734,12 @@ var NodeStreamableHTTPServerTransport2 = class {
84631
84734
  import { randomBytes as randomBytes5 } from "node:crypto";
84632
84735
  import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
84633
84736
  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"]
84737
+ var READ = ["about", "read", "list", "search"];
84738
+ var FILES = [...READ, "edit"];
84739
+ var TOOLS2 = {
84740
+ read: READ,
84741
+ files: FILES,
84742
+ terminal: [...FILES, "exec_command", "write_stdin", "list_sessions"]
84638
84743
  };
84639
84744
  var MAX_LOG = 256 * 1024;
84640
84745
  var MAX_TIMER_S = 2e6;
@@ -84733,7 +84838,7 @@ var Computers = class {
84733
84838
  ...this.#describe(stored),
84734
84839
  uptimeSeconds: stored.state === "running" ? Math.round((Date.now() - Date.parse(stored.startedAt)) / 1e3) : 0,
84735
84840
  tools: {
84736
- computer: stored.access === "none" ? [] : TOOLS[stored.access],
84841
+ computer: stored.access === "none" ? [] : TOOLS2[stored.access],
84737
84842
  browser: live?.running?.browserTools ?? []
84738
84843
  },
84739
84844
  surfaces: live?.running?.surfaces ?? []
@@ -84953,7 +85058,7 @@ var OPENAPI = {
84953
85058
  "openapi": "3.1.0",
84954
85059
  "info": {
84955
85060
  "title": "AgentComputer",
84956
- "version": "0.0.11",
85061
+ "version": "0.0.12",
84957
85062
  "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
85063
  "license": {
84959
85064
  "name": "Apache-2.0",
@@ -85405,7 +85510,7 @@ var OPENAPI = {
85405
85510
  "files",
85406
85511
  "terminal"
85407
85512
  ],
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."
85513
+ "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
85514
  },
85410
85515
  "ComputerState": {
85411
85516
  "type": "string",
@@ -85618,7 +85723,7 @@ var OPENAPI = {
85618
85723
  "browser",
85619
85724
  "load"
85620
85725
  ],
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."
85726
+ "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
85727
  },
85623
85728
  "LiveMode": {
85624
85729
  "type": "string",
@@ -86795,7 +86900,7 @@ var OPERATIONS = [
86795
86900
  "files",
86796
86901
  "terminal"
86797
86902
  ],
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."
86903
+ "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
86904
  },
86800
86905
  "browser": {
86801
86906
  "type": "boolean"
@@ -86884,7 +86989,7 @@ var OPERATIONS = [
86884
86989
  "files",
86885
86990
  "terminal"
86886
86991
  ],
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."
86992
+ "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
86993
  },
86889
86994
  "browser": {
86890
86995
  "type": "boolean",
@@ -86954,7 +87059,7 @@ var OPERATIONS = [
86954
87059
  "files",
86955
87060
  "terminal"
86956
87061
  ],
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."
87062
+ "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
87063
  },
86959
87064
  "browser": {
86960
87065
  "type": "boolean"
@@ -87094,7 +87199,7 @@ var OPERATIONS = [
87094
87199
  "files",
87095
87200
  "terminal"
87096
87201
  ],
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."
87202
+ "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
87203
  },
87099
87204
  "browser": {
87100
87205
  "type": "boolean"
@@ -87183,7 +87288,7 @@ var OPERATIONS = [
87183
87288
  "browser",
87184
87289
  "load"
87185
87290
  ],
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."
87291
+ "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
87292
  },
87188
87293
  "protocol": {
87189
87294
  "type": "string"
@@ -87325,7 +87430,7 @@ var OPERATIONS = [
87325
87430
  "files",
87326
87431
  "terminal"
87327
87432
  ],
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."
87433
+ "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
87434
  },
87330
87435
  "browser": {
87331
87436
  "type": "boolean"
@@ -87449,7 +87554,7 @@ var OPERATIONS = [
87449
87554
  "files",
87450
87555
  "terminal"
87451
87556
  ],
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."
87557
+ "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
87558
  },
87454
87559
  "browser": {
87455
87560
  "type": "boolean"
@@ -87578,7 +87683,7 @@ var OPERATIONS = [
87578
87683
  "files",
87579
87684
  "terminal"
87580
87685
  ],
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."
87686
+ "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
87687
  },
87583
87688
  "browser": {
87584
87689
  "type": "boolean"
@@ -87723,7 +87828,7 @@ var OPERATIONS = [
87723
87828
  "browser",
87724
87829
  "load"
87725
87830
  ],
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."
87831
+ "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
87832
  },
87728
87833
  "mode": {
87729
87834
  "allOf": [
@@ -87770,7 +87875,7 @@ var OPERATIONS = [
87770
87875
  "browser",
87771
87876
  "load"
87772
87877
  ],
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."
87878
+ "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
87879
  },
87775
87880
  "mode": {
87776
87881
  "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.12",
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.6",
35
35
  "@biomejs/biome": "2.6.5",
36
36
  "@modelcontextprotocol/client": "2.4.0",
37
37
  "@modelcontextprotocol/node": "2.1.2",