@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 +8 -1
- package/dist/agentcomputer.js +275 -81
- package/dist/types/generated/schema.d.ts +26 -3
- package/package.json +1 -1
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.
|
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
|
-
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
|
-
(_,
|
|
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();
|
|
@@ -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
|
|
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 ?
|
|
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
|
|
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, {
|
|
@@ -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
|
-
|
|
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({
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|