@agentorg/computer 0.0.9 → 0.0.10

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
@@ -164,6 +164,13 @@ A computer with `--browser` has another:
164
164
  - **A dialog, a file chooser and a list belong to a page.** A dialog is told to the sockets shown its page until someone answers it, you or the agent. Nothing says when the agent answers a file chooser, so when it answers one every open chooser is taken as closed: press again if yours was.
165
165
  - **A client written for the earlier `screencast@1` still connects.** Its `release` is ignored and no `driver` event comes: nobody holds the browser any more.
166
166
 
167
+ Every running computer has one more, to watch only:
168
+
169
+ - **`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
+ - **`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
+ - **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.
173
+
167
174
  `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`.
168
175
 
169
176
  A shell of your own needs [node-pty](https://github.com/microsoft/node-pty), an optional dependency installed beside the program. It comes prebuilt for macOS. On Linux it's compiled at install, which npm only does when told: `npm install -g @agentorg/computer --allow-scripts=node-pty`, with a C++ toolchain. Without it, the agent's commands run and show as ever, and only `open` is refused, saying why. The computer image has it.
@@ -3772,9 +3772,9 @@ var playwright = () => {
3772
3772
  // src/browser-host.ts
3773
3773
  var sandboxFor = (options) => options.sandbox ?? (process.platform !== "linux" || !options.executable && !["chromium", "chrome-for-testing"].includes(options.channel ?? "chrome"));
3774
3774
  async function hostBrowser(options) {
3775
- const run2 = mkdtempSync(join("/tmp", "agentcomputer-"));
3776
- process.env.XDG_RUNTIME_DIR = run2;
3777
- process.env.TMPDIR = run2;
3775
+ const run3 = mkdtempSync(join("/tmp", "agentcomputer-"));
3776
+ process.env.XDG_RUNTIME_DIR = run3;
3777
+ process.env.TMPDIR = run3;
3778
3778
  const context = await playwright().chromium.launchPersistentContext(options.profile, {
3779
3779
  headless: true,
3780
3780
  chromiumSandbox: sandboxFor(options),
@@ -3783,7 +3783,7 @@ async function hostBrowser(options) {
3783
3783
  const browser = context.browser();
3784
3784
  if (!browser) throw new Error("The browser can't be shared.");
3785
3785
  const { endpoint } = await browser.bind("agentcomputer");
3786
- if (!endpoint.startsWith(run2 + sep)) {
3786
+ if (!endpoint.startsWith(run3 + sep)) {
3787
3787
  await context.close();
3788
3788
  throw new Error(`The browser's socket isn't in its own folder: ${endpoint}`);
3789
3789
  }
@@ -3797,7 +3797,7 @@ async function hostBrowser(options) {
3797
3797
  });
3798
3798
  await context.close().catch(() => {
3799
3799
  });
3800
- rmSync(run2, { recursive: true, force: true });
3800
+ rmSync(run3, { recursive: true, force: true });
3801
3801
  process.exit(0);
3802
3802
  };
3803
3803
  for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void stop());
@@ -3846,13 +3846,13 @@ async function serveBrowserMcp(options) {
3846
3846
  }
3847
3847
 
3848
3848
  // src/container.ts
3849
- import { execFile, spawn as spawn3 } from "node:child_process";
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
3852
  import { join as join8 } from "node:path";
3853
3853
  import { createInterface as createInterface2 } from "node:readline";
3854
3854
  import { Readable as Readable3 } from "node:stream";
3855
- import { promisify } from "node:util";
3855
+ import { promisify as promisify2 } from "node:util";
3856
3856
 
3857
3857
  // src/browser.ts
3858
3858
  import { spawn } from "node:child_process";
@@ -28413,7 +28413,7 @@ var require_ignore = __commonJS2({
28413
28413
  // ever starts again inside it -- `(\\+?) ` did, which made a long run
28414
28414
  // quadratic.
28415
28415
  /(\\+)( ?)/g,
28416
- (_, run2, space) => space ? run2.slice(0, run2.length - run2.length % 2) + SPACE2 : run2,
28416
+ (_, run3, space) => space ? run3.slice(0, run3.length - run3.length % 2) + SPACE2 : run3,
28417
28417
  ESCAPE + SPACE2
28418
28418
  ],
28419
28419
  // git's wildmatch() reads a run of two or more asterisks as one globstar
@@ -28599,10 +28599,10 @@ var require_ignore = __commonJS2({
28599
28599
  var REGEX_REPLACE_TRAILING_DOUBLESTAR = /\uE001$/;
28600
28600
  var replaceTrailingDoublestar = () => ".+(?=$|\\/$)";
28601
28601
  var WILDCARD2 = "[^\\/]*";
28602
- var separatorAfter = (run2, at) => {
28602
+ var separatorAfter = (run3, at) => {
28603
28603
  let separator = EMPTY;
28604
- for (let index = at + 1; index < run2.length && !run2[index].wildcard; index++) {
28605
- separator += run2[index].single;
28604
+ for (let index = at + 1; index < run3.length && !run3[index].wildcard; index++) {
28605
+ separator += run3[index].single;
28606
28606
  }
28607
28607
  return separator;
28608
28608
  };
@@ -28662,26 +28662,26 @@ var require_ignore = __commonJS2({
28662
28662
  }
28663
28663
  }
28664
28664
  let out = EMPTY;
28665
- let run2 = [];
28665
+ let run3 = [];
28666
28666
  const flush = () => {
28667
28667
  let lastWildcard;
28668
- run2.forEach((token, at) => {
28668
+ run3.forEach((token, at) => {
28669
28669
  if (token.wildcard) {
28670
28670
  lastWildcard = at;
28671
28671
  }
28672
28672
  });
28673
- run2.forEach((token, at) => {
28673
+ run3.forEach((token, at) => {
28674
28674
  if (!token.wildcard) {
28675
28675
  out += token.single;
28676
28676
  return;
28677
28677
  }
28678
- out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run2, at)})[^\\/])*`;
28678
+ out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run3, at)})[^\\/])*`;
28679
28679
  });
28680
- run2 = [];
28680
+ run3 = [];
28681
28681
  };
28682
28682
  tokens.forEach((token) => {
28683
28683
  if (token.boundary === void 0) {
28684
- run2.push(token);
28684
+ run3.push(token);
28685
28685
  return;
28686
28686
  }
28687
28687
  flush();
@@ -48443,7 +48443,7 @@ function visit(schema, fnOrHandlers) {
48443
48443
  return h ? h(node22, rewritten) : node22;
48444
48444
  };
48445
48445
  const cache = /* @__PURE__ */ new Map();
48446
- function run2(s) {
48446
+ function run3(s) {
48447
48447
  const cached22 = cache.get(s);
48448
48448
  if (cached22 === RESOLVING) {
48449
48449
  return new $ZodLazy2({
@@ -48469,21 +48469,21 @@ function visit(schema, fnOrHandlers) {
48469
48469
  let changed2 = false;
48470
48470
  const newShape = {};
48471
48471
  for (const k of keys) {
48472
- const mapped = run2(oldShape[k]);
48472
+ const mapped = run3(oldShape[k]);
48473
48473
  if (mapped !== oldShape[k])
48474
48474
  changed2 = true;
48475
48475
  newShape[k] = mapped;
48476
48476
  }
48477
48477
  let newCatchall = def.catchall;
48478
48478
  if (def.catchall) {
48479
- newCatchall = run2(def.catchall);
48479
+ newCatchall = run3(def.catchall);
48480
48480
  if (newCatchall !== def.catchall)
48481
48481
  changed2 = true;
48482
48482
  }
48483
48483
  return changed2 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48484
48484
  }
48485
48485
  case "array": {
48486
- const mapped = run2(def.element);
48486
+ const mapped = run3(def.element);
48487
48487
  return mapped === def.element ? s : clone2(s, { ...def, element: mapped });
48488
48488
  }
48489
48489
  case "tuple": {
@@ -48491,14 +48491,14 @@ function visit(schema, fnOrHandlers) {
48491
48491
  let changed2 = false;
48492
48492
  const newItems = [];
48493
48493
  for (const item of oldItems) {
48494
- const mapped = run2(item);
48494
+ const mapped = run3(item);
48495
48495
  if (mapped !== item)
48496
48496
  changed2 = true;
48497
48497
  newItems.push(mapped);
48498
48498
  }
48499
48499
  let newRest = def.rest;
48500
48500
  if (def.rest) {
48501
- newRest = run2(def.rest);
48501
+ newRest = run3(def.rest);
48502
48502
  if (newRest !== def.rest)
48503
48503
  changed2 = true;
48504
48504
  }
@@ -48506,12 +48506,12 @@ function visit(schema, fnOrHandlers) {
48506
48506
  }
48507
48507
  case "record":
48508
48508
  case "map": {
48509
- const newKey = run2(def.keyType);
48510
- const newVal = run2(def.valueType);
48509
+ const newKey = run3(def.keyType);
48510
+ const newVal = run3(def.valueType);
48511
48511
  return newKey === def.keyType && newVal === def.valueType ? s : clone2(s, { ...def, keyType: newKey, valueType: newVal });
48512
48512
  }
48513
48513
  case "set": {
48514
- const newVal = run2(def.valueType);
48514
+ const newVal = run3(def.valueType);
48515
48515
  return newVal === def.valueType ? s : clone2(s, { ...def, valueType: newVal });
48516
48516
  }
48517
48517
  case "union": {
@@ -48519,7 +48519,7 @@ function visit(schema, fnOrHandlers) {
48519
48519
  let changed2 = false;
48520
48520
  const newOptions = [];
48521
48521
  for (const opt of oldOptions) {
48522
- const mapped = run2(opt);
48522
+ const mapped = run3(opt);
48523
48523
  if (mapped !== opt)
48524
48524
  changed2 = true;
48525
48525
  newOptions.push(mapped);
@@ -48527,8 +48527,8 @@ function visit(schema, fnOrHandlers) {
48527
48527
  return changed2 ? clone2(s, { ...def, options: newOptions }) : s;
48528
48528
  }
48529
48529
  case "intersection": {
48530
- const newLeft = run2(def.left);
48531
- const newRight = run2(def.right);
48530
+ const newLeft = run3(def.left);
48531
+ const newRight = run3(def.right);
48532
48532
  return newLeft === def.left && newRight === def.right ? s : clone2(s, { ...def, left: newLeft, right: newRight });
48533
48533
  }
48534
48534
  case "optional":
@@ -48540,23 +48540,23 @@ function visit(schema, fnOrHandlers) {
48540
48540
  case "nonoptional":
48541
48541
  case "promise":
48542
48542
  case "success": {
48543
- const newInner = run2(def.innerType);
48543
+ const newInner = run3(def.innerType);
48544
48544
  return newInner === def.innerType ? s : clone2(s, { ...def, innerType: newInner });
48545
48545
  }
48546
48546
  case "pipe": {
48547
- const newIn = run2(def.in);
48548
- const newOut = run2(def.out);
48547
+ const newIn = run3(def.in);
48548
+ const newOut = run3(def.out);
48549
48549
  return newIn === def.in && newOut === def.out ? s : clone2(s, { ...def, in: newIn, out: newOut });
48550
48550
  }
48551
48551
  case "function": {
48552
- const newInput = run2(def.input);
48553
- const newOutput = run2(def.output);
48552
+ const newInput = run3(def.input);
48553
+ const newOutput = run3(def.output);
48554
48554
  return newInput === def.input && newOutput === def.output ? s : clone2(s, { ...def, input: newInput, output: newOutput });
48555
48555
  }
48556
48556
  case "lazy": {
48557
48557
  const original = def.getter;
48558
48558
  const { _cachedInner, ...rest } = def;
48559
- return clone2(s, { ...rest, getter: () => run2(original()) });
48559
+ return clone2(s, { ...rest, getter: () => run3(original()) });
48560
48560
  }
48561
48561
  // A leaf by choice: `parts` are regex fragments, not data positions.
48562
48562
  case "template_literal":
@@ -48587,7 +48587,7 @@ function visit(schema, fnOrHandlers) {
48587
48587
  }
48588
48588
  }
48589
48589
  }
48590
- return run2(schema);
48590
+ return run3(schema);
48591
48591
  }
48592
48592
  function deepPartial(schema) {
48593
48593
  return visit(schema, {
@@ -67310,7 +67310,8 @@ function* pieces(text3, most = MAX_TEXT) {
67310
67310
  var PROTOCOLS = {
67311
67311
  terminal: "terminal@1",
67312
67312
  files: "files@1",
67313
- browser: "screencast@1"
67313
+ browser: "screencast@1",
67314
+ load: "metrics@1"
67314
67315
  };
67315
67316
  var MAX_MESSAGE = 64 * 1024;
67316
67317
  var MAX_BUFFERED3 = 4 * 1024 * 1024;
@@ -67323,7 +67324,8 @@ var MAX_WORKDIR = 2e3;
67323
67324
  var SERVERS = {
67324
67325
  terminal: new import_websocket_server.default({ noServer: true, maxPayload: MAX_MESSAGE }),
67325
67326
  files: new import_websocket_server.default({ noServer: true, maxPayload: MAX_PART }),
67326
- browser: new import_websocket_server.default({ noServer: true })
67327
+ browser: new import_websocket_server.default({ noServer: true }),
67328
+ load: new import_websocket_server.default({ noServer: true, maxPayload: MAX_MESSAGE })
67327
67329
  };
67328
67330
  var MALFORMED = "That isn't a terminal@1 message.";
67329
67331
  var first = (text3, most) => {
@@ -67343,7 +67345,9 @@ var Live = class {
67343
67345
  #files;
67344
67346
  /** Whether a person may write files here: not where the agent can only read them. */
67345
67347
  #writes;
67346
- constructor(exec, access, browser) {
67348
+ #load;
67349
+ constructor(exec, access, load, browser) {
67350
+ this.#load = load;
67347
67351
  if (exec && access === "terminal") {
67348
67352
  this.#terminals = exec.terminals;
67349
67353
  exec.onCommand((event) => {
@@ -67362,6 +67366,7 @@ var Live = class {
67362
67366
  surfaces.push({ surface: "files", protocol: PROTOCOLS.files, operate: this.#writes });
67363
67367
  if (this.#browser)
67364
67368
  surfaces.push({ surface: "browser", protocol: PROTOCOLS.browser, operate: true });
67369
+ surfaces.push({ surface: "load", protocol: PROTOCOLS.load, operate: false });
67365
67370
  return surfaces;
67366
67371
  }
67367
67372
  /** Take an upgraded request as a socket on one surface. */
@@ -67372,6 +67377,7 @@ var Live = class {
67372
67377
  if (surface === "terminal" && this.#terminals) this.#view(ws, mode, this.#terminals);
67373
67378
  else if (surface === "files" && this.#files) this.#files.open(ws, mode);
67374
67379
  else if (surface === "browser" && this.#browser) this.#browser.open(ws, mode);
67380
+ else if (surface === "load") this.#load.open(ws);
67375
67381
  else ws.close(1011, `There's no ${surface} here.`);
67376
67382
  });
67377
67383
  }
@@ -67379,6 +67385,7 @@ var Live = class {
67379
67385
  close() {
67380
67386
  void this.#browser?.close();
67381
67387
  this.#files?.close();
67388
+ this.#load.close();
67382
67389
  for (const ws of this.#viewers) ws.close(1001, "It stopped.");
67383
67390
  }
67384
67391
  /**
@@ -67417,6 +67424,114 @@ var Live = class {
67417
67424
  }
67418
67425
  };
67419
67426
 
67427
+ // src/load-live.ts
67428
+ import { execFile } from "node:child_process";
67429
+ import { readFile as readFile2 } from "node:fs/promises";
67430
+ import { availableParallelism, cpus, freemem, totalmem } from "node:os";
67431
+ import { promisify } from "node:util";
67432
+ var EVERY_MS = 2e3;
67433
+ var run = promisify(execFile);
67434
+ var numberOf = (text3, name) => Number(new RegExp(`^${name}\\s+(\\d+)`, "m").exec(text3)?.[1]);
67435
+ async function disk(folder) {
67436
+ const { stdout } = await run("df", ["-kP", folder], { timeout: EVERY_MS });
67437
+ const [, total, used] = /\s(\d+)\s+(\d+)\s+\d+\s+\d+%/.exec(stdout) ?? [];
67438
+ if (!total) throw new Error("No disk.");
67439
+ return { diskUsed: Number(used) * 1024, diskTotal: Number(total) * 1024 };
67440
+ }
67441
+ async function machine() {
67442
+ const cpu = { used: 0, total: 0 };
67443
+ for (const { times } of cpus()) {
67444
+ cpu.used += times.user + times.nice + times.sys + times.irq;
67445
+ cpu.total += times.user + times.nice + times.sys + times.irq + times.idle;
67446
+ }
67447
+ if (process.platform !== "darwin")
67448
+ return { scope: "machine", cpu, memUsed: totalmem() - freemem() };
67449
+ const { stdout } = await run("/usr/bin/vm_stat", { timeout: EVERY_MS });
67450
+ const pages = numberOf(stdout, "Pages active:") + numberOf(stdout, "Pages wired down:") + numberOf(stdout, "Pages occupied by compressor:");
67451
+ return { scope: "machine", cpu, memUsed: pages * Number(/page size of (\d+)/.exec(stdout)?.[1]) };
67452
+ }
67453
+ async function cgroup() {
67454
+ const read = (file2) => readFile2(`/sys/fs/cgroup/${file2}`, "utf8");
67455
+ const [cpu, current, memory] = await Promise.all([
67456
+ read("cpu.stat"),
67457
+ read("memory.current"),
67458
+ read("memory.stat")
67459
+ ]);
67460
+ return {
67461
+ scope: "computer",
67462
+ // No container has a limit, so the time there was is every CPU's.
67463
+ cpu: {
67464
+ used: numberOf(cpu, "usage_usec"),
67465
+ total: performance.now() * 1e3 * availableParallelism()
67466
+ },
67467
+ // Less the files it has read and no longer uses, as `docker stats` counts it.
67468
+ memUsed: Number(current) - numberOf(memory, "inactive_file")
67469
+ };
67470
+ }
67471
+ var LoadLive = class {
67472
+ #folder;
67473
+ #contained;
67474
+ #sockets = /* @__PURE__ */ new Set();
67475
+ #timer;
67476
+ #reading = false;
67477
+ #before;
67478
+ #last;
67479
+ /**
67480
+ * `folder` is on the disk that is reported. `contained` is whether the processes here are one
67481
+ * computer's and nothing else's, as they are inside a container.
67482
+ */
67483
+ constructor(folder, contained) {
67484
+ this.#folder = folder;
67485
+ this.#contained = contained;
67486
+ }
67487
+ /** metrics@1: a reading every two seconds, the last one at once. What it sends is dropped. */
67488
+ open(ws) {
67489
+ this.#sockets.add(ws);
67490
+ if (this.#last) ws.send(this.#last);
67491
+ ws.on("close", () => {
67492
+ this.#sockets.delete(ws);
67493
+ if (this.#sockets.size === 0) this.close();
67494
+ });
67495
+ if (this.#timer) return;
67496
+ this.#timer = setInterval(() => void this.#read(), EVERY_MS);
67497
+ void this.#read();
67498
+ }
67499
+ close() {
67500
+ clearInterval(this.#timer);
67501
+ this.#timer = this.#before = this.#last = void 0;
67502
+ for (const ws of this.#sockets) ws.close(1001, "It stopped.");
67503
+ }
67504
+ /** One at a time, and for the watching it began under: a reading taken too late is left. */
67505
+ async #read() {
67506
+ const timer = this.#timer;
67507
+ if (this.#reading) return;
67508
+ this.#reading = true;
67509
+ try {
67510
+ const sample = await (this.#contained ? cgroup().catch(machine) : machine());
67511
+ const volume = await disk(this.#folder);
67512
+ if (this.#timer !== timer) return;
67513
+ const before = this.#before;
67514
+ this.#before = sample.cpu;
67515
+ if (!before) return;
67516
+ const share = (sample.cpu.used - before.used) / (sample.cpu.total - before.total) || 0;
67517
+ const reading = {
67518
+ type: "reading",
67519
+ scope: sample.scope,
67520
+ cpuUsedPct: Math.round(Math.min(Math.max(share, 0), 1) * 1e3) / 10,
67521
+ memUsed: sample.memUsed,
67522
+ memTotal: totalmem(),
67523
+ ...volume
67524
+ };
67525
+ if (!Number.isFinite(reading.cpuUsedPct + reading.memUsed)) return;
67526
+ this.#last = JSON.stringify(reading);
67527
+ for (const ws of this.#sockets) if (ws.bufferedAmount === 0) ws.send(this.#last);
67528
+ } catch {
67529
+ } finally {
67530
+ this.#reading = false;
67531
+ }
67532
+ }
67533
+ };
67534
+
67420
67535
  // src/store.ts
67421
67536
  import { createHash, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
67422
67537
  import { chmodSync, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
@@ -67485,8 +67600,11 @@ var loadPty = () => {
67485
67600
  var ProcessRuntime = class {
67486
67601
  isolation = "none";
67487
67602
  #config;
67488
- constructor(config3) {
67603
+ #contained;
67604
+ /** `contained` is for inside a container, where load is the computer's own. */
67605
+ constructor(config3, contained = false) {
67489
67606
  this.#config = config3;
67607
+ this.#contained = contained;
67490
67608
  }
67491
67609
  async prepare() {
67492
67610
  }
@@ -67529,7 +67647,8 @@ var ProcessRuntime = class {
67529
67647
  browserLive = new BrowserLive(browser.endpoint);
67530
67648
  browser.gate = browserLive;
67531
67649
  }
67532
- const live = new Live(exec, spec.access, browserLive);
67650
+ const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67651
+ const live = new Live(exec, spec.access, load, browserLive);
67533
67652
  const surfaces = live.surfaces();
67534
67653
  return {
67535
67654
  browserTools: browser?.tools ?? [],
@@ -67565,7 +67684,7 @@ async function serveInside(options) {
67565
67684
  if (!token) throw new Error(`${INNER_TOKEN_VARIABLE} is not set.`);
67566
67685
  delete process.env[INNER_TOKEN_VARIABLE];
67567
67686
  const digest = digestOf(token);
67568
- const running = new ProcessRuntime({ path: BROWSER, sandbox: false }).start(
67687
+ const running = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67569
67688
  {
67570
67689
  id: "inside",
67571
67690
  ...options,
@@ -67606,7 +67725,7 @@ async function serveInside(options) {
67606
67725
  socket.on("error", () => socket.destroy());
67607
67726
  const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
67608
67727
  const url3 = new URL(request.url ?? "/", "http://localhost");
67609
- const surface = /^\/live\/(terminal|files|browser)$/.exec(url3.pathname)?.[1];
67728
+ const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3.pathname)?.[1];
67610
67729
  const mode = url3.searchParams.get("mode");
67611
67730
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
67612
67731
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
@@ -67621,7 +67740,7 @@ async function serveInside(options) {
67621
67740
  }
67622
67741
 
67623
67742
  // src/container.ts
67624
- var run = promisify(execFile);
67743
+ var run2 = promisify2(execFile2);
67625
67744
  var START_MS2 = 6e4;
67626
67745
  var STOP_SECONDS = "5";
67627
67746
  var NOT_FORWARDED = /* @__PURE__ */ new Set([
@@ -67635,7 +67754,7 @@ var NOT_FORWARDED = /* @__PURE__ */ new Set([
67635
67754
  ]);
67636
67755
  async function docker(args, env = process.env) {
67637
67756
  try {
67638
- return (await run("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67757
+ return (await run2("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67639
67758
  } catch (error62) {
67640
67759
  const { stderr, message } = error62;
67641
67760
  throw new Error(stderr?.trim() || message);
@@ -67853,7 +67972,7 @@ async function forward(url3, authorization, request, response) {
67853
67972
  }
67854
67973
 
67855
67974
  // src/handlers.ts
67856
- var VERSION2 = "0.0.9";
67975
+ var VERSION2 = "0.0.10";
67857
67976
  function createHandlers(computers) {
67858
67977
  return {
67859
67978
  getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
@@ -84816,7 +84935,7 @@ var OPENAPI = {
84816
84935
  "openapi": "3.1.0",
84817
84936
  "info": {
84818
84937
  "title": "AgentComputer",
84819
- "version": "0.0.9",
84938
+ "version": "0.0.10",
84820
84939
  "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.",
84821
84940
  "license": {
84822
84941
  "name": "Apache-2.0",
@@ -85137,7 +85256,7 @@ var OPENAPI = {
85137
85256
  "post": {
85138
85257
  "operationId": "createLiveSession",
85139
85258
  "x-mcp-tool": "computer_live",
85140
- "summary": "A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, or its browser. Open its url as a WebSocket within 60 seconds.",
85259
+ "summary": "A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, its browser, or its load. Open its url as a WebSocket within 60 seconds.",
85141
85260
  "requestBody": {
85142
85261
  "required": true,
85143
85262
  "content": {
@@ -85478,9 +85597,10 @@ var OPENAPI = {
85478
85597
  "enum": [
85479
85598
  "terminal",
85480
85599
  "files",
85481
- "browser"
85600
+ "browser",
85601
+ "load"
85482
85602
  ],
85483
- "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."
85603
+ "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."
85484
85604
  },
85485
85605
  "LiveMode": {
85486
85606
  "type": "string",
@@ -86135,6 +86255,54 @@ var OPENAPI = {
86135
86255
  }
86136
86256
  ]
86137
86257
  },
86258
+ "LoadReading": {
86259
+ "description": "What the service sends on a metrics@1 socket, as JSON text. What a socket sends is dropped.",
86260
+ "type": "object",
86261
+ "required": [
86262
+ "type",
86263
+ "scope",
86264
+ "cpuUsedPct",
86265
+ "memUsed",
86266
+ "memTotal",
86267
+ "diskUsed",
86268
+ "diskTotal"
86269
+ ],
86270
+ "properties": {
86271
+ "type": {
86272
+ "const": "reading"
86273
+ },
86274
+ "scope": {
86275
+ "type": "string",
86276
+ "enum": [
86277
+ "computer",
86278
+ "machine"
86279
+ ],
86280
+ "description": "Whose numbers these are. computer: this computer's own, which a container's are. machine: the whole machine's, which a computer with no isolation shares with whatever else runs there, so every computer of that service reads the same; and a container's where it can't read its own."
86281
+ },
86282
+ "cpuUsedPct": {
86283
+ "type": "number",
86284
+ "minimum": 0,
86285
+ "maximum": 100,
86286
+ "description": "Of all the machine's CPUs, since the reading before. One decimal."
86287
+ },
86288
+ "memUsed": {
86289
+ "type": "integer",
86290
+ "description": "Bytes in use."
86291
+ },
86292
+ "memTotal": {
86293
+ "type": "integer",
86294
+ "description": "Bytes the machine has: no computer has a limit."
86295
+ },
86296
+ "diskUsed": {
86297
+ "type": "integer",
86298
+ "description": "Bytes used on the volume its workspace is on, or its own folder where it has no workspace. The volume's, not the workspace's own size."
86299
+ },
86300
+ "diskTotal": {
86301
+ "type": "integer",
86302
+ "description": "Bytes on that volume."
86303
+ }
86304
+ }
86305
+ },
86138
86306
  "FilesRequest": {
86139
86307
  "description": "What a socket sends on files@1, as JSON text. Each has an id of its own choosing, which the answer repeats; answers come as they are ready, in any order, and ids aren't checked for repeats. A request still unanswered when the socket closes gets no answer. A path is from the workspace, with / between its parts, or absolute inside it, and nothing outside the workspace is reached. A binary message that follows no write closes the socket, and so does any message over 1,048,576 bytes.",
86140
86308
  "oneOf": [
@@ -86989,9 +87157,10 @@ var OPERATIONS = [
86989
87157
  "enum": [
86990
87158
  "terminal",
86991
87159
  "files",
86992
- "browser"
87160
+ "browser",
87161
+ "load"
86993
87162
  ],
86994
- "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."
87163
+ "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."
86995
87164
  },
86996
87165
  "protocol": {
86997
87166
  "type": "string"
@@ -87507,7 +87676,7 @@ var OPERATIONS = [
87507
87676
  "method": "POST",
87508
87677
  "path": "/v1/computers/{computerID}/live",
87509
87678
  "tool": "computer_live",
87510
- "summary": "A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, or its browser. Open its url as a WebSocket within 60 seconds.",
87679
+ "summary": "A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, its browser, or its load. Open its url as a WebSocket within 60 seconds.",
87511
87680
  "status": 201,
87512
87681
  "hasBody": true,
87513
87682
  "pathParameters": [
@@ -87528,9 +87697,10 @@ var OPERATIONS = [
87528
87697
  "enum": [
87529
87698
  "terminal",
87530
87699
  "files",
87531
- "browser"
87700
+ "browser",
87701
+ "load"
87532
87702
  ],
87533
- "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."
87703
+ "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."
87534
87704
  },
87535
87705
  "mode": {
87536
87706
  "allOf": [
@@ -87574,9 +87744,10 @@ var OPERATIONS = [
87574
87744
  "enum": [
87575
87745
  "terminal",
87576
87746
  "files",
87577
- "browser"
87747
+ "browser",
87748
+ "load"
87578
87749
  ],
87579
- "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."
87750
+ "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."
87580
87751
  },
87581
87752
  "mode": {
87582
87753
  "type": "string",
@@ -87682,7 +87853,7 @@ async function serve(options) {
87682
87853
  pattern: new RegExp(`^${operation.path.replace(/\{[^}]+\}/g, "([^/]+)")}$`),
87683
87854
  check: fromJsonSchema3(operation.input)
87684
87855
  }));
87685
- const run2 = async (route2, input2) => {
87856
+ const run3 = async (route2, input2) => {
87686
87857
  const checked = await route2.check["~standard"].validate(input2);
87687
87858
  if (checked.issues)
87688
87859
  throw new ApiError(400, checked.issues.map((issue3) => issue3.message).join("; "));
@@ -87697,7 +87868,7 @@ async function serve(options) {
87697
87868
  { description: route2.operation.summary, inputSchema: route2.check },
87698
87869
  async (input2) => {
87699
87870
  try {
87700
- const output2 = await run2(route2, input2) ?? {};
87871
+ const output2 = await run3(route2, input2) ?? {};
87701
87872
  return { content: [{ type: "text", text: JSON.stringify(output2) }] };
87702
87873
  } catch (error62) {
87703
87874
  if (!(error62 instanceof ApiError)) throw error62;
@@ -87749,7 +87920,7 @@ async function serve(options) {
87749
87920
  const value = url3.searchParams.get(name);
87750
87921
  if (value !== null) input2[name] = value;
87751
87922
  }
87752
- return send(response, operation.status, await run2(candidate, input2));
87923
+ return send(response, operation.status, await run3(candidate, input2));
87753
87924
  }
87754
87925
  throw pathMatched ? new ApiError(405, "Not allowed.") : new ApiError(404, "Not found.");
87755
87926
  };
@@ -87836,7 +88007,7 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
87836
88007
  stop <id> stop it, keeping its folder and token
87837
88008
  start <id> [--timeout SECONDS] start a stopped computer again
87838
88009
  logs <id> what its servers wrote
87839
- live <id> --surface terminal|files|browser [--mode watch|operate]
88010
+ live <id> --surface terminal|files|browser|load [--mode watch|operate]
87840
88011
  a one-time ticket to open one live, as a WebSocket
87841
88012
  kill <id> stop it and remove what the service made
87842
88013
 
@@ -146,7 +146,7 @@ export interface paths {
146
146
  };
147
147
  get?: never;
148
148
  put?: never;
149
- /** A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, or its browser. Open its url as a WebSocket within 60 seconds. */
149
+ /** A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, its browser, or its load. Open its url as a WebSocket within 60 seconds. */
150
150
  post: operations["createLiveSession"];
151
151
  delete?: never;
152
152
  options?: never;
@@ -248,10 +248,10 @@ 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.
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.
252
252
  * @enum {string}
253
253
  */
254
- Surface: "terminal" | "files" | "browser";
254
+ Surface: "terminal" | "files" | "browser" | "load";
255
255
  /**
256
256
  * @description In watch you see and don't act: what you send is dropped, except that a files socket may still ask to list, to read and for what changed.
257
257
  * @enum {string}
@@ -433,6 +433,26 @@ export interface components {
433
433
  type: "stop";
434
434
  id: string;
435
435
  };
436
+ /** @description What the service sends on a metrics@1 socket, as JSON text. What a socket sends is dropped. */
437
+ LoadReading: {
438
+ /** @constant */
439
+ type: "reading";
440
+ /**
441
+ * @description Whose numbers these are. computer: this computer's own, which a container's are. machine: the whole machine's, which a computer with no isolation shares with whatever else runs there, so every computer of that service reads the same; and a container's where it can't read its own.
442
+ * @enum {string}
443
+ */
444
+ scope: "computer" | "machine";
445
+ /** @description Of all the machine's CPUs, since the reading before. One decimal. */
446
+ cpuUsedPct: number;
447
+ /** @description Bytes in use. */
448
+ memUsed: number;
449
+ /** @description Bytes the machine has: no computer has a limit. */
450
+ memTotal: number;
451
+ /** @description Bytes used on the volume its workspace is on, or its own folder where it has no workspace. The volume's, not the workspace's own size. */
452
+ diskUsed: number;
453
+ /** @description Bytes on that volume. */
454
+ diskTotal: number;
455
+ };
436
456
  /** @description What a socket sends on files@1, as JSON text. Each has an id of its own choosing, which the answer repeats; answers come as they are ready, in any order, and ids aren't checked for repeats. A request still unanswered when the socket closes gets no answer. A path is from the workspace, with / between its parts, or absolute inside it, and nothing outside the workspace is reached. A binary message that follows no write closes the socket, and so does any message over 1,048,576 bytes. */
437
457
  FilesRequest: {
438
458
  /** @constant */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/computer",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
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": {