@agentorg/computer 0.0.9 → 0.0.11

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
@@ -148,7 +148,7 @@ A computer at `--access terminal` has a surface you can open live, as a WebSocke
148
148
  A computer at `--access read` or above has another, and at `read` it is the only one:
149
149
 
150
150
  - **`files`** (protocol `files@1`): its workspace's files, for a file window. Text messages are JSON, and each request has an `id` of your choosing, which its answer repeats. `openapi.yaml` has each one's fields.
151
- - **`list`** is answered with `entries`: a folder's entries with their kind, size and date, folders first, the first thousand.
151
+ - **`list`** is answered with `entries`: a folder's entries with their kind, size and date, folders first, the first thousand. With them come `path`, the folder as it was asked for, and `workspace`, what the workspace is called: its folder's own name, and nothing of where it is. The two together name the folder, `Research/src`; a workspace the service made is called `workspace`, and one at the top of a disk has an empty name. A computer in a container made before `workspace` was added sends none, and then `path` alone names the folder.
152
152
  - **`read`** is answered with `data` and then one binary message, a range of a file's bytes. A file larger than one range is read by asking again from where the last ended.
153
153
  - **`changed`** is answered with `changes`: the files modified after a time, the newest thousand, the newest first. It skips what `.gitignore` files ignore, `.git`, `node_modules` and links, and a file that was removed isn't one of them. Each one asked walks the workspace once, a computer walks for one at a time, and nothing is walked that wasn't asked for.
154
154
  - **`write`**, on an `operate` socket, puts a file there a part at a time, each followed at once by one binary message, its bytes. Nothing is at the path until the part marked `last`, which puts the file there whole and is answered with `written`; the parts before it are answered only if one is refused. It makes no folders, and nothing removes or renames a file. A file not finished when the socket closes is given up.
@@ -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
- import { join as join8 } from "node:path";
3852
+ import { basename as basename3, 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";
@@ -28158,7 +28158,7 @@ import { join as join7 } from "node:path";
28158
28158
  // src/runtime.ts
28159
28159
  import { chmodSync as chmodSync2, existsSync, statSync as statSync2 } from "node:fs";
28160
28160
  import { createRequire as createRequire3 } from "node:module";
28161
- import { dirname as dirname3, join as join6 } from "node:path";
28161
+ import { basename as basename2, dirname as dirname3, join as join6 } from "node:path";
28162
28162
 
28163
28163
  // node_modules/@agentorg/exec/dist/lib.js
28164
28164
  import { realpathSync } from "node:fs";
@@ -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();
@@ -28915,11 +28915,11 @@ var require_ignore = __commonJS2({
28915
28915
  const rules = this._rules;
28916
28916
  const { length } = rules;
28917
28917
  const shortcut = this._basenameCount * 2 >= length;
28918
- const basename2 = shortcut ? basenameOf(path) : path;
28918
+ const basename22 = shortcut ? basenameOf(path) : path;
28919
28919
  for (let index = length - 1; index >= 0; index--) {
28920
28920
  const rule = rules[index];
28921
28921
  if (rule[mode].test(
28922
- shortcut && rule._basenameOnly ? basename2 : path
28922
+ shortcut && rule._basenameOnly ? basename22 : path
28923
28923
  )) {
28924
28924
  const { negative } = rule;
28925
28925
  const ret = {
@@ -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, {
@@ -67117,16 +67117,6 @@ var BrowserLive = class {
67117
67117
  }
67118
67118
  };
67119
67119
 
67120
- // node_modules/ws/wrapper.mjs
67121
- var import_stream2 = __toESM(require_stream(), 1);
67122
- var import_extension = __toESM(require_extension(), 1);
67123
- var import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
67124
- var import_receiver = __toESM(require_receiver(), 1);
67125
- var import_sender = __toESM(require_sender(), 1);
67126
- var import_subprotocol = __toESM(require_subprotocol(), 1);
67127
- var import_websocket = __toESM(require_websocket(), 1);
67128
- var import_websocket_server = __toESM(require_websocket_server(), 1);
67129
-
67130
67120
  // src/files-live.ts
67131
67121
  var MAX_PART = 1024 * 1024;
67132
67122
  var MAX_BUFFERED2 = 4 * 1024 * 1024;
@@ -67137,13 +67127,16 @@ var whole = (value) => Number.isSafeInteger(value);
67137
67127
  var sleep4 = (ms) => new Promise((done) => setTimeout(done, ms));
67138
67128
  var FilesLive = class {
67139
67129
  #files;
67130
+ #workspace;
67140
67131
  #sockets = /* @__PURE__ */ new Set();
67141
67132
  /** One walk of the workspace at a time, however many ask. */
67142
67133
  #walking = Promise.resolve();
67143
67134
  /** How many files are part written, over every socket. */
67144
67135
  #uploads = 0;
67145
- constructor(files) {
67136
+ /** `workspace` is what the workspace is called: its folder's own name, not where it is. */
67137
+ constructor(files, workspace) {
67146
67138
  this.#files = files;
67139
+ this.#workspace = workspace;
67147
67140
  }
67148
67141
  close() {
67149
67142
  for (const ws of this.#sockets) ws.close(1001, "It stopped.");
@@ -67213,7 +67206,13 @@ var FilesLive = class {
67213
67206
  const path = request.path ?? ".";
67214
67207
  if (!text2(path)) throw new Error("A path is text.");
67215
67208
  return inTurn(
67216
- async () => send2({ type: "entries", id, path, ...await this.#files.list(path) })
67209
+ async () => send2({
67210
+ type: "entries",
67211
+ id,
67212
+ workspace: this.#workspace,
67213
+ path,
67214
+ ...await this.#files.list(path)
67215
+ })
67217
67216
  );
67218
67217
  }
67219
67218
  case "read": {
@@ -67294,6 +67293,16 @@ var FilesLive = class {
67294
67293
  }
67295
67294
  };
67296
67295
 
67296
+ // node_modules/ws/wrapper.mjs
67297
+ var import_stream2 = __toESM(require_stream(), 1);
67298
+ var import_extension = __toESM(require_extension(), 1);
67299
+ var import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
67300
+ var import_receiver = __toESM(require_receiver(), 1);
67301
+ var import_sender = __toESM(require_sender(), 1);
67302
+ var import_subprotocol = __toESM(require_subprotocol(), 1);
67303
+ var import_websocket = __toESM(require_websocket(), 1);
67304
+ var import_websocket_server = __toESM(require_websocket_server(), 1);
67305
+
67297
67306
  // src/text.ts
67298
67307
  var MAX_TEXT = 1e4;
67299
67308
  function* pieces(text3, most = MAX_TEXT) {
@@ -67310,7 +67319,8 @@ function* pieces(text3, most = MAX_TEXT) {
67310
67319
  var PROTOCOLS = {
67311
67320
  terminal: "terminal@1",
67312
67321
  files: "files@1",
67313
- browser: "screencast@1"
67322
+ browser: "screencast@1",
67323
+ load: "metrics@1"
67314
67324
  };
67315
67325
  var MAX_MESSAGE = 64 * 1024;
67316
67326
  var MAX_BUFFERED3 = 4 * 1024 * 1024;
@@ -67323,7 +67333,8 @@ var MAX_WORKDIR = 2e3;
67323
67333
  var SERVERS = {
67324
67334
  terminal: new import_websocket_server.default({ noServer: true, maxPayload: MAX_MESSAGE }),
67325
67335
  files: new import_websocket_server.default({ noServer: true, maxPayload: MAX_PART }),
67326
- browser: new import_websocket_server.default({ noServer: true })
67336
+ browser: new import_websocket_server.default({ noServer: true }),
67337
+ load: new import_websocket_server.default({ noServer: true, maxPayload: MAX_MESSAGE })
67327
67338
  };
67328
67339
  var MALFORMED = "That isn't a terminal@1 message.";
67329
67340
  var first = (text3, most) => {
@@ -67343,14 +67354,16 @@ var Live = class {
67343
67354
  #files;
67344
67355
  /** Whether a person may write files here: not where the agent can only read them. */
67345
67356
  #writes;
67346
- constructor(exec, access, browser) {
67357
+ #load;
67358
+ constructor(exec, access, load, files, browser) {
67359
+ this.#load = load;
67347
67360
  if (exec && access === "terminal") {
67348
67361
  this.#terminals = exec.terminals;
67349
67362
  exec.onCommand((event) => {
67350
67363
  for (const message of messages(event)) for (const ws of this.#viewers) deliver(ws, message);
67351
67364
  });
67352
67365
  }
67353
- this.#files = exec && new FilesLive(exec.files);
67366
+ this.#files = files;
67354
67367
  this.#writes = access !== "read";
67355
67368
  this.#browser = browser;
67356
67369
  }
@@ -67362,6 +67375,7 @@ var Live = class {
67362
67375
  surfaces.push({ surface: "files", protocol: PROTOCOLS.files, operate: this.#writes });
67363
67376
  if (this.#browser)
67364
67377
  surfaces.push({ surface: "browser", protocol: PROTOCOLS.browser, operate: true });
67378
+ surfaces.push({ surface: "load", protocol: PROTOCOLS.load, operate: false });
67365
67379
  return surfaces;
67366
67380
  }
67367
67381
  /** Take an upgraded request as a socket on one surface. */
@@ -67372,6 +67386,7 @@ var Live = class {
67372
67386
  if (surface === "terminal" && this.#terminals) this.#view(ws, mode, this.#terminals);
67373
67387
  else if (surface === "files" && this.#files) this.#files.open(ws, mode);
67374
67388
  else if (surface === "browser" && this.#browser) this.#browser.open(ws, mode);
67389
+ else if (surface === "load") this.#load.open(ws);
67375
67390
  else ws.close(1011, `There's no ${surface} here.`);
67376
67391
  });
67377
67392
  }
@@ -67379,6 +67394,7 @@ var Live = class {
67379
67394
  close() {
67380
67395
  void this.#browser?.close();
67381
67396
  this.#files?.close();
67397
+ this.#load.close();
67382
67398
  for (const ws of this.#viewers) ws.close(1001, "It stopped.");
67383
67399
  }
67384
67400
  /**
@@ -67417,6 +67433,114 @@ var Live = class {
67417
67433
  }
67418
67434
  };
67419
67435
 
67436
+ // src/load-live.ts
67437
+ import { execFile } from "node:child_process";
67438
+ import { readFile as readFile2 } from "node:fs/promises";
67439
+ import { availableParallelism, cpus, freemem, totalmem } from "node:os";
67440
+ import { promisify } from "node:util";
67441
+ var EVERY_MS = 2e3;
67442
+ var run = promisify(execFile);
67443
+ var numberOf = (text3, name) => Number(new RegExp(`^${name}\\s+(\\d+)`, "m").exec(text3)?.[1]);
67444
+ async function disk(folder) {
67445
+ const { stdout } = await run("df", ["-kP", folder], { timeout: EVERY_MS });
67446
+ const [, total, used] = /\s(\d+)\s+(\d+)\s+\d+\s+\d+%/.exec(stdout) ?? [];
67447
+ if (!total) throw new Error("No disk.");
67448
+ return { diskUsed: Number(used) * 1024, diskTotal: Number(total) * 1024 };
67449
+ }
67450
+ async function machine() {
67451
+ const cpu = { used: 0, total: 0 };
67452
+ for (const { times } of cpus()) {
67453
+ cpu.used += times.user + times.nice + times.sys + times.irq;
67454
+ cpu.total += times.user + times.nice + times.sys + times.irq + times.idle;
67455
+ }
67456
+ if (process.platform !== "darwin")
67457
+ return { scope: "machine", cpu, memUsed: totalmem() - freemem() };
67458
+ const { stdout } = await run("/usr/bin/vm_stat", { timeout: EVERY_MS });
67459
+ const pages = numberOf(stdout, "Pages active:") + numberOf(stdout, "Pages wired down:") + numberOf(stdout, "Pages occupied by compressor:");
67460
+ return { scope: "machine", cpu, memUsed: pages * Number(/page size of (\d+)/.exec(stdout)?.[1]) };
67461
+ }
67462
+ async function cgroup() {
67463
+ const read = (file2) => readFile2(`/sys/fs/cgroup/${file2}`, "utf8");
67464
+ const [cpu, current, memory] = await Promise.all([
67465
+ read("cpu.stat"),
67466
+ read("memory.current"),
67467
+ read("memory.stat")
67468
+ ]);
67469
+ return {
67470
+ scope: "computer",
67471
+ // No container has a limit, so the time there was is every CPU's.
67472
+ cpu: {
67473
+ used: numberOf(cpu, "usage_usec"),
67474
+ total: performance.now() * 1e3 * availableParallelism()
67475
+ },
67476
+ // Less the files it has read and no longer uses, as `docker stats` counts it.
67477
+ memUsed: Number(current) - numberOf(memory, "inactive_file")
67478
+ };
67479
+ }
67480
+ var LoadLive = class {
67481
+ #folder;
67482
+ #contained;
67483
+ #sockets = /* @__PURE__ */ new Set();
67484
+ #timer;
67485
+ #reading = false;
67486
+ #before;
67487
+ #last;
67488
+ /**
67489
+ * `folder` is on the disk that is reported. `contained` is whether the processes here are one
67490
+ * computer's and nothing else's, as they are inside a container.
67491
+ */
67492
+ constructor(folder, contained) {
67493
+ this.#folder = folder;
67494
+ this.#contained = contained;
67495
+ }
67496
+ /** metrics@1: a reading every two seconds, the last one at once. What it sends is dropped. */
67497
+ open(ws) {
67498
+ this.#sockets.add(ws);
67499
+ if (this.#last) ws.send(this.#last);
67500
+ ws.on("close", () => {
67501
+ this.#sockets.delete(ws);
67502
+ if (this.#sockets.size === 0) this.close();
67503
+ });
67504
+ if (this.#timer) return;
67505
+ this.#timer = setInterval(() => void this.#read(), EVERY_MS);
67506
+ void this.#read();
67507
+ }
67508
+ close() {
67509
+ clearInterval(this.#timer);
67510
+ this.#timer = this.#before = this.#last = void 0;
67511
+ for (const ws of this.#sockets) ws.close(1001, "It stopped.");
67512
+ }
67513
+ /** One at a time, and for the watching it began under: a reading taken too late is left. */
67514
+ async #read() {
67515
+ const timer = this.#timer;
67516
+ if (this.#reading) return;
67517
+ this.#reading = true;
67518
+ try {
67519
+ const sample = await (this.#contained ? cgroup().catch(machine) : machine());
67520
+ const volume = await disk(this.#folder);
67521
+ if (this.#timer !== timer) return;
67522
+ const before = this.#before;
67523
+ 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;
67535
+ this.#last = JSON.stringify(reading);
67536
+ for (const ws of this.#sockets) if (ws.bufferedAmount === 0) ws.send(this.#last);
67537
+ } catch {
67538
+ } finally {
67539
+ this.#reading = false;
67540
+ }
67541
+ }
67542
+ };
67543
+
67420
67544
  // src/store.ts
67421
67545
  import { createHash, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
67422
67546
  import { chmodSync, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
@@ -67485,8 +67609,11 @@ var loadPty = () => {
67485
67609
  var ProcessRuntime = class {
67486
67610
  isolation = "none";
67487
67611
  #config;
67488
- constructor(config3) {
67612
+ #contained;
67613
+ /** `contained` is for inside a container, where load is the computer's own. */
67614
+ constructor(config3, contained = false) {
67489
67615
  this.#config = config3;
67616
+ this.#contained = contained;
67490
67617
  }
67491
67618
  async prepare() {
67492
67619
  }
@@ -67529,7 +67656,9 @@ var ProcessRuntime = class {
67529
67656
  browserLive = new BrowserLive(browser.endpoint);
67530
67657
  browser.gate = browserLive;
67531
67658
  }
67532
- const live = new Live(exec, spec.access, browserLive);
67659
+ const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67660
+ const files = exec && new FilesLive(exec.files, spec.workspaceName ?? basename2(spec.workspace));
67661
+ const live = new Live(exec, spec.access, load, files, browserLive);
67533
67662
  const surfaces = live.surfaces();
67534
67663
  return {
67535
67664
  browserTools: browser?.tools ?? [],
@@ -67559,17 +67688,18 @@ var ProcessRuntime = class {
67559
67688
  // src/inside.ts
67560
67689
  var INNER_PORT = 8e3;
67561
67690
  var INNER_TOKEN_VARIABLE = "AGENTCOMPUTER_INNER_TOKEN";
67691
+ var WORKSPACE_NAME_VARIABLE = "AGENTCOMPUTER_WORKSPACE_NAME";
67562
67692
  var BROWSER = "/opt/browsers/headless-shell";
67563
67693
  async function serveInside(options) {
67564
67694
  const token = process.env[INNER_TOKEN_VARIABLE];
67565
67695
  if (!token) throw new Error(`${INNER_TOKEN_VARIABLE} is not set.`);
67566
67696
  delete process.env[INNER_TOKEN_VARIABLE];
67567
67697
  const digest = digestOf(token);
67568
- const running = new ProcessRuntime({ path: BROWSER, sandbox: false }).start(
67698
+ const running = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67569
67699
  {
67570
67700
  id: "inside",
67571
67701
  ...options,
67572
- ...options.access === "none" ? {} : { workspace: "/workspace" }
67702
+ ...options.access === "none" ? {} : { workspace: "/workspace", workspaceName: process.env[WORKSPACE_NAME_VARIABLE] }
67573
67703
  },
67574
67704
  privateFolder(join7(homedir(), ".agentcomputer")),
67575
67705
  {
@@ -67606,7 +67736,7 @@ async function serveInside(options) {
67606
67736
  socket.on("error", () => socket.destroy());
67607
67737
  const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
67608
67738
  const url3 = new URL(request.url ?? "/", "http://localhost");
67609
- const surface = /^\/live\/(terminal|files|browser)$/.exec(url3.pathname)?.[1];
67739
+ const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3.pathname)?.[1];
67610
67740
  const mode = url3.searchParams.get("mode");
67611
67741
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
67612
67742
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
@@ -67621,7 +67751,7 @@ async function serveInside(options) {
67621
67751
  }
67622
67752
 
67623
67753
  // src/container.ts
67624
- var run = promisify(execFile);
67754
+ var run2 = promisify2(execFile2);
67625
67755
  var START_MS2 = 6e4;
67626
67756
  var STOP_SECONDS = "5";
67627
67757
  var NOT_FORWARDED = /* @__PURE__ */ new Set([
@@ -67635,7 +67765,7 @@ var NOT_FORWARDED = /* @__PURE__ */ new Set([
67635
67765
  ]);
67636
67766
  async function docker(args, env = process.env) {
67637
67767
  try {
67638
- return (await run("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67768
+ return (await run2("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67639
67769
  } catch (error62) {
67640
67770
  const { stderr, message } = error62;
67641
67771
  throw new Error(stderr?.trim() || message);
@@ -67697,6 +67827,9 @@ var ContainerRuntime = class {
67697
67827
  // By name, so the value stays out of the command line other users can see.
67698
67828
  "--env",
67699
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] : [],
67700
67833
  // Files it writes in the workspace belong to whoever runs the service.
67701
67834
  ...uid ? ["--user", `${uid}:${process.getgid?.()}`] : [],
67702
67835
  ...spec.workspace ? [
@@ -67709,7 +67842,11 @@ var ContainerRuntime = class {
67709
67842
  spec.access,
67710
67843
  ...spec.browser ? ["--browser"] : []
67711
67844
  ],
67712
- { ...process.env, [INNER_TOKEN_VARIABLE]: token }
67845
+ {
67846
+ ...process.env,
67847
+ [INNER_TOKEN_VARIABLE]: token,
67848
+ ...spec.workspace ? { [WORKSPACE_NAME_VARIABLE]: basename3(spec.workspace) } : {}
67849
+ }
67713
67850
  );
67714
67851
  }
67715
67852
  /** The token between the service and this container, which a restart has to find again. */
@@ -67853,7 +67990,7 @@ async function forward(url3, authorization, request, response) {
67853
67990
  }
67854
67991
 
67855
67992
  // src/handlers.ts
67856
- var VERSION2 = "0.0.9";
67993
+ var VERSION2 = "0.0.11";
67857
67994
  function createHandlers(computers) {
67858
67995
  return {
67859
67996
  getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
@@ -84816,7 +84953,7 @@ var OPENAPI = {
84816
84953
  "openapi": "3.1.0",
84817
84954
  "info": {
84818
84955
  "title": "AgentComputer",
84819
- "version": "0.0.9",
84956
+ "version": "0.0.11",
84820
84957
  "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
84958
  "license": {
84822
84959
  "name": "Apache-2.0",
@@ -85137,7 +85274,7 @@ var OPENAPI = {
85137
85274
  "post": {
85138
85275
  "operationId": "createLiveSession",
85139
85276
  "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.",
85277
+ "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
85278
  "requestBody": {
85142
85279
  "required": true,
85143
85280
  "content": {
@@ -85478,9 +85615,10 @@ var OPENAPI = {
85478
85615
  "enum": [
85479
85616
  "terminal",
85480
85617
  "files",
85481
- "browser"
85618
+ "browser",
85619
+ "load"
85482
85620
  ],
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."
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."
85484
85622
  },
85485
85623
  "LiveMode": {
85486
85624
  "type": "string",
@@ -86135,6 +86273,54 @@ var OPENAPI = {
86135
86273
  }
86136
86274
  ]
86137
86275
  },
86276
+ "LoadReading": {
86277
+ "description": "What the service sends on a metrics@1 socket, as JSON text. What a socket sends is dropped.",
86278
+ "type": "object",
86279
+ "required": [
86280
+ "type",
86281
+ "scope",
86282
+ "cpuUsedPct",
86283
+ "memUsed",
86284
+ "memTotal",
86285
+ "diskUsed",
86286
+ "diskTotal"
86287
+ ],
86288
+ "properties": {
86289
+ "type": {
86290
+ "const": "reading"
86291
+ },
86292
+ "scope": {
86293
+ "type": "string",
86294
+ "enum": [
86295
+ "computer",
86296
+ "machine"
86297
+ ],
86298
+ "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."
86299
+ },
86300
+ "cpuUsedPct": {
86301
+ "type": "number",
86302
+ "minimum": 0,
86303
+ "maximum": 100,
86304
+ "description": "Of all the machine's CPUs, since the reading before. One decimal."
86305
+ },
86306
+ "memUsed": {
86307
+ "type": "integer",
86308
+ "description": "Bytes in use."
86309
+ },
86310
+ "memTotal": {
86311
+ "type": "integer",
86312
+ "description": "Bytes the machine has: no computer has a limit."
86313
+ },
86314
+ "diskUsed": {
86315
+ "type": "integer",
86316
+ "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."
86317
+ },
86318
+ "diskTotal": {
86319
+ "type": "integer",
86320
+ "description": "Bytes on that volume."
86321
+ }
86322
+ }
86323
+ },
86138
86324
  "FilesRequest": {
86139
86325
  "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
86326
  "oneOf": [
@@ -86274,8 +86460,13 @@ var OPENAPI = {
86274
86460
  "id": {
86275
86461
  "type": "string"
86276
86462
  },
86463
+ "workspace": {
86464
+ "type": "string",
86465
+ "description": "What the workspace is called: its folder's own name, and nothing of where it is. It comes with every entries answer, whichever folder was asked for, so that with path it names the folder. A computer in a container made before `workspace` was added sends none, and there path alone names the folder. It is `workspace` for a workspace the service made, and empty for one at the top of a disk."
86466
+ },
86277
86467
  "path": {
86278
- "type": "string"
86468
+ "type": "string",
86469
+ "description": "The folder as it was asked for: `.` for the workspace itself."
86279
86470
  },
86280
86471
  "entries": {
86281
86472
  "type": "array",
@@ -86989,9 +87180,10 @@ var OPERATIONS = [
86989
87180
  "enum": [
86990
87181
  "terminal",
86991
87182
  "files",
86992
- "browser"
87183
+ "browser",
87184
+ "load"
86993
87185
  ],
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."
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."
86995
87187
  },
86996
87188
  "protocol": {
86997
87189
  "type": "string"
@@ -87507,7 +87699,7 @@ var OPERATIONS = [
87507
87699
  "method": "POST",
87508
87700
  "path": "/v1/computers/{computerID}/live",
87509
87701
  "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.",
87702
+ "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
87703
  "status": 201,
87512
87704
  "hasBody": true,
87513
87705
  "pathParameters": [
@@ -87528,9 +87720,10 @@ var OPERATIONS = [
87528
87720
  "enum": [
87529
87721
  "terminal",
87530
87722
  "files",
87531
- "browser"
87723
+ "browser",
87724
+ "load"
87532
87725
  ],
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."
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."
87534
87727
  },
87535
87728
  "mode": {
87536
87729
  "allOf": [
@@ -87574,9 +87767,10 @@ var OPERATIONS = [
87574
87767
  "enum": [
87575
87768
  "terminal",
87576
87769
  "files",
87577
- "browser"
87770
+ "browser",
87771
+ "load"
87578
87772
  ],
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."
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."
87580
87774
  },
87581
87775
  "mode": {
87582
87776
  "type": "string",
@@ -87682,7 +87876,7 @@ async function serve(options) {
87682
87876
  pattern: new RegExp(`^${operation.path.replace(/\{[^}]+\}/g, "([^/]+)")}$`),
87683
87877
  check: fromJsonSchema3(operation.input)
87684
87878
  }));
87685
- const run2 = async (route2, input2) => {
87879
+ const run3 = async (route2, input2) => {
87686
87880
  const checked = await route2.check["~standard"].validate(input2);
87687
87881
  if (checked.issues)
87688
87882
  throw new ApiError(400, checked.issues.map((issue3) => issue3.message).join("; "));
@@ -87697,7 +87891,7 @@ async function serve(options) {
87697
87891
  { description: route2.operation.summary, inputSchema: route2.check },
87698
87892
  async (input2) => {
87699
87893
  try {
87700
- const output2 = await run2(route2, input2) ?? {};
87894
+ const output2 = await run3(route2, input2) ?? {};
87701
87895
  return { content: [{ type: "text", text: JSON.stringify(output2) }] };
87702
87896
  } catch (error62) {
87703
87897
  if (!(error62 instanceof ApiError)) throw error62;
@@ -87749,7 +87943,7 @@ async function serve(options) {
87749
87943
  const value = url3.searchParams.get(name);
87750
87944
  if (value !== null) input2[name] = value;
87751
87945
  }
87752
- return send(response, operation.status, await run2(candidate, input2));
87946
+ return send(response, operation.status, await run3(candidate, input2));
87753
87947
  }
87754
87948
  throw pathMatched ? new ApiError(405, "Not allowed.") : new ApiError(404, "Not found.");
87755
87949
  };
@@ -87836,7 +88030,7 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
87836
88030
  stop <id> stop it, keeping its folder and token
87837
88031
  start <id> [--timeout SECONDS] start a stopped computer again
87838
88032
  logs <id> what its servers wrote
87839
- live <id> --surface terminal|files|browser [--mode watch|operate]
88033
+ live <id> --surface terminal|files|browser|load [--mode watch|operate]
87840
88034
  a one-time ticket to open one live, as a WebSocket
87841
88035
  kill <id> stop it and remove what the service made
87842
88036
 
@@ -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 */
@@ -467,6 +487,9 @@ export interface components {
467
487
  /** @constant */
468
488
  type: "entries";
469
489
  id: string;
490
+ /** @description What the workspace is called: its folder's own name, and nothing of where it is. It comes with every entries answer, whichever folder was asked for, so that with path it names the folder. A computer in a container made before `workspace` was added sends none, and there path alone names the folder. It is `workspace` for a workspace the service made, and empty for one at the top of a disk. */
491
+ workspace?: string;
492
+ /** @description The folder as it was asked for: `.` for the workspace itself. */
470
493
  path: string;
471
494
  entries: {
472
495
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentorg/computer",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
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": {