@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 +7 -0
- package/dist/agentcomputer.js +232 -61
- package/dist/types/generated/schema.d.ts +23 -3
- package/package.json +1 -1
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.
|
package/dist/agentcomputer.js
CHANGED
|
@@ -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
|
|
3776
|
-
process.env.XDG_RUNTIME_DIR =
|
|
3777
|
-
process.env.TMPDIR =
|
|
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(
|
|
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(
|
|
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
|
-
(_,
|
|
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 = (
|
|
28602
|
+
var separatorAfter = (run3, at) => {
|
|
28603
28603
|
let separator = EMPTY;
|
|
28604
|
-
for (let index = at + 1; index <
|
|
28605
|
-
separator +=
|
|
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
|
|
28665
|
+
let run3 = [];
|
|
28666
28666
|
const flush = () => {
|
|
28667
28667
|
let lastWildcard;
|
|
28668
|
-
|
|
28668
|
+
run3.forEach((token, at) => {
|
|
28669
28669
|
if (token.wildcard) {
|
|
28670
28670
|
lastWildcard = at;
|
|
28671
28671
|
}
|
|
28672
28672
|
});
|
|
28673
|
-
|
|
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(
|
|
28678
|
+
out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run3, at)})[^\\/])*`;
|
|
28679
28679
|
});
|
|
28680
|
-
|
|
28680
|
+
run3 = [];
|
|
28681
28681
|
};
|
|
28682
28682
|
tokens.forEach((token) => {
|
|
28683
28683
|
if (token.boundary === void 0) {
|
|
28684
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
48510
|
-
const newVal =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
48531
|
-
const newRight =
|
|
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 =
|
|
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 =
|
|
48548
|
-
const newOut =
|
|
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 =
|
|
48553
|
-
const newOutput =
|
|
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: () =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 */
|