@agentorg/computer 0.0.14 → 0.0.16

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.
@@ -3751,7 +3751,6 @@ var require_websocket_server = __commonJS({
3751
3751
  });
3752
3752
 
3753
3753
  // src/main.ts
3754
- import { readFileSync as readFileSync6 } from "node:fs";
3755
3754
  import { homedir as homedir2 } from "node:os";
3756
3755
  import { isAbsolute as isAbsolute2, join as join11, resolve as resolve5 } from "node:path";
3757
3756
  import { parseArgs } from "node:util";
@@ -3805,44 +3804,73 @@ async function hostBrowser(options) {
3805
3804
  }
3806
3805
 
3807
3806
  // src/browser-mcp.ts
3807
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
3808
3808
  import { createRequire as createRequire2 } from "node:module";
3809
+ import { join as join2 } from "node:path";
3809
3810
  var require2 = createRequire2(import.meta.url);
3810
3811
  var mcpParts = () => require2("playwright-core/lib/coreBundle").tools;
3812
+ function listenOnSocket(socket) {
3813
+ const http = require2("node:http");
3814
+ const make = http.createServer;
3815
+ http.createServer = ((...args) => {
3816
+ http.createServer = make;
3817
+ const server = make(...args);
3818
+ const listen = server.listen.bind(server);
3819
+ server.listen = (() => listen(socket));
3820
+ server.address = () => ({ address: "127.0.0.1", family: "IPv4", port: 0 });
3821
+ return server;
3822
+ });
3823
+ }
3811
3824
  async function serveBrowserMcp(options) {
3812
3825
  const parts = mcpParts();
3813
3826
  parts.setupExitWatchdog();
3827
+ const folder = mkdtempSync2(join2("/tmp", "agentcomputer-"));
3828
+ process.once("exit", () => rmSync2(folder, { recursive: true, force: true }));
3829
+ const socket = join2(folder, "mcp.sock");
3814
3830
  const config3 = await parts.resolveCLIConfigForMCP({
3815
3831
  endpoint: options.endpoint,
3816
3832
  outputDir: options.outputDir,
3817
3833
  host: "127.0.0.1",
3818
3834
  port: 0,
3835
+ // What the proxy sends, which is all that can reach the socket.
3836
+ allowedHosts: ["localhost"],
3819
3837
  // The tool that closes the browser refuses, as it does for any browser its clients share.
3820
3838
  sharedBrowserContext: true
3821
3839
  });
3822
3840
  const tools = parts.filteredTools(config3);
3823
- await parts.start(
3824
- {
3825
- name: "Playwright",
3826
- nameInConfig: "playwright",
3827
- // playwright-core's, as Playwright MCP's program gives it.
3828
- version: require2("playwright-core/package.json").version,
3829
- toolSchemas: tools.map((tool) => tool.schema),
3830
- ignoreRoots: config3.ignoreRoots,
3831
- /** One for each client's session: a connection of its own to the one browser. */
3832
- create: async () => {
3833
- const browser = await playwright().chromium.connect(options.endpoint);
3834
- const context = browser.contexts()[0];
3835
- if (!context) {
3836
- await browser.close();
3837
- throw new Error("The browser has no context to work in.");
3838
- }
3839
- return new parts.BrowserBackend(config3, context, tools, {
3840
- dispose: () => browser.close()
3841
- });
3842
- }
3843
- },
3844
- config3.server
3845
- );
3841
+ listenOnSocket(socket);
3842
+ const say = console.error;
3843
+ console.error = () => {
3844
+ };
3845
+ try {
3846
+ await parts.start(
3847
+ {
3848
+ name: "Playwright",
3849
+ nameInConfig: "playwright",
3850
+ // playwright-core's, as Playwright MCP's program gives it.
3851
+ version: require2("playwright-core/package.json").version,
3852
+ toolSchemas: tools.map((tool) => tool.schema),
3853
+ ignoreRoots: config3.ignoreRoots,
3854
+ /** One for each client's session: a connection of its own to the one browser. */
3855
+ create: async () => {
3856
+ const browser = await playwright().chromium.connect(options.endpoint);
3857
+ const context = browser.contexts()[0];
3858
+ if (!context) {
3859
+ await browser.close();
3860
+ throw new Error("The browser has no context to work in.");
3861
+ }
3862
+ return new parts.BrowserBackend(config3, context, tools, {
3863
+ dispose: () => browser.close()
3864
+ });
3865
+ }
3866
+ },
3867
+ config3.server
3868
+ );
3869
+ } finally {
3870
+ console.error = say;
3871
+ }
3872
+ process.stdout.write(`${JSON.stringify({ socket })}
3873
+ `);
3846
3874
  }
3847
3875
 
3848
3876
  // src/container.ts
@@ -3857,7 +3885,9 @@ import { promisify as promisify2 } from "node:util";
3857
3885
 
3858
3886
  // src/browser.ts
3859
3887
  import { spawn } from "node:child_process";
3860
- import { join as join2 } from "node:path";
3888
+ import { rmSync as rmSync3 } from "node:fs";
3889
+ import { request as ask } from "node:http";
3890
+ import { dirname, join as join3 } from "node:path";
3861
3891
  import { createInterface } from "node:readline";
3862
3892
  import { pipeline, Readable } from "node:stream";
3863
3893
  import { fileURLToPath } from "node:url";
@@ -4829,10 +4859,10 @@ function $constructor(name, initializer4, proto, params) {
4829
4859
  if (initialized) {
4830
4860
  const own3 = Object.getPrototypeOf(inst);
4831
4861
  const ctorProto = inst._zod.constr.prototype;
4832
- let up = own3;
4833
- while (up && up !== ctorProto)
4834
- up = Object.getPrototypeOf(up);
4835
- const target = up ?? own3;
4862
+ let up2 = own3;
4863
+ while (up2 && up2 !== ctorProto)
4864
+ up2 = Object.getPrototypeOf(up2);
4865
+ const target = up2 ?? own3;
4836
4866
  if (!initialized.has(target)) {
4837
4867
  initialized.add(target);
4838
4868
  members(target, protoMembers);
@@ -18888,14 +18918,14 @@ var require_validate = /* @__PURE__ */ __commonJSMin(((exports) => {
18888
18918
  } else {
18889
18919
  const matches = RELATIVE_JSON_POINTER.exec($data);
18890
18920
  if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`);
18891
- const up = +matches[1];
18921
+ const up2 = +matches[1];
18892
18922
  jsonPointer = matches[2];
18893
18923
  if (jsonPointer === "#") {
18894
- if (up >= dataLevel) throw new Error(errorMsg("property/index", up));
18895
- return dataPathArr[dataLevel - up];
18924
+ if (up2 >= dataLevel) throw new Error(errorMsg("property/index", up2));
18925
+ return dataPathArr[dataLevel - up2];
18896
18926
  }
18897
- if (up > dataLevel) throw new Error(errorMsg("data", up));
18898
- data = dataNames[dataLevel - up];
18927
+ if (up2 > dataLevel) throw new Error(errorMsg("data", up2));
18928
+ data = dataNames[dataLevel - up2];
18899
18929
  if (!jsonPointer) return data;
18900
18930
  }
18901
18931
  let expr = data;
@@ -18905,8 +18935,8 @@ var require_validate = /* @__PURE__ */ __commonJSMin(((exports) => {
18905
18935
  expr = (0, codegen_1._)`${expr} && ${data}`;
18906
18936
  }
18907
18937
  return expr;
18908
- function errorMsg(pointerType, up) {
18909
- return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
18938
+ function errorMsg(pointerType, up2) {
18939
+ return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
18910
18940
  }
18911
18941
  }
18912
18942
  exports.getData = getData;
@@ -27958,7 +27988,10 @@ var StreamableHTTPClientTransport = class {
27958
27988
 
27959
27989
  // src/browser.ts
27960
27990
  var RUNS_CODE = "browser_run_code_unsafe";
27961
- var MAX_BODY = 8 * 1024 * 1024;
27991
+ var SCREENSHOT = "browser_take_screenshot";
27992
+ var MIB = 1024 * 1024;
27993
+ var MAX_BODY = 8 * MIB;
27994
+ var MAX_PICTURE = 3 * MIB;
27962
27995
  var START_MS = 3e4;
27963
27996
  var STOP_MS = 2e3;
27964
27997
  var ONE_REQUEST = "A body is one JSON-RPC request, as a JSON object: send each request in its own POST.";
@@ -27980,16 +28013,64 @@ function sendAnswer(upstream, response) {
27980
28013
  pipeline(Readable.fromWeb(upstream.body), response, () => {
27981
28014
  });
27982
28015
  }
28016
+ function socketFetch(socket) {
28017
+ return (url3, init = {}) => new Promise((done, failed) => {
28018
+ const { body } = init;
28019
+ if (body != null && typeof body !== "string" && !(body instanceof Uint8Array))
28020
+ return failed(new TypeError("A body to the browser's server is text or bytes."));
28021
+ const { pathname, search: search2 } = new URL(url3);
28022
+ const headers = Object.fromEntries(new Headers(init.headers));
28023
+ if (body != null) headers["content-length"] = String(Buffer.byteLength(body));
28024
+ const sent = ask(
28025
+ {
28026
+ socketPath: socket,
28027
+ path: pathname + search2,
28028
+ method: init.method ?? "GET",
28029
+ headers,
28030
+ signal: init.signal ?? void 0
28031
+ },
28032
+ (answer) => {
28033
+ const answered2 = new Headers();
28034
+ for (const [name, value] of Object.entries(answer.headers))
28035
+ for (const one of [value ?? []].flat()) answered2.append(name, one);
28036
+ const status = answer.statusCode ?? 502;
28037
+ const none2 = [204, 205, 304].includes(status);
28038
+ if (none2) answer.resume();
28039
+ done(
28040
+ new Response(none2 ? null : Readable.toWeb(answer), {
28041
+ status,
28042
+ headers: answered2
28043
+ })
28044
+ );
28045
+ }
28046
+ );
28047
+ sent.on("error", failed);
28048
+ sent.end(body ?? void 0);
28049
+ });
28050
+ }
28051
+ var MCP = "http://localhost/mcp";
27983
28052
  var self = fileURLToPath(import.meta.url);
27984
- function withoutHidden(text3, hidden) {
27985
- const clean = (json4) => {
28053
+ function changed(text3, change) {
28054
+ const one = (json4) => {
27986
28055
  const message = JSON.parse(json4);
27987
- if (message.result?.tools)
27988
- message.result.tools = message.result.tools.filter((tool) => !hidden.has(tool.name));
28056
+ change(message);
27989
28057
  return JSON.stringify(message);
27990
28058
  };
27991
- if (text3.trimStart().startsWith("{")) return clean(text3);
27992
- return text3.replace(/^data: (\{.*\})$/gm, (_, json4) => `data: ${clean(json4)}`);
28059
+ if (text3.trimStart().startsWith("{")) return one(text3);
28060
+ return text3.replace(/^data: (\{[^\n\r]*\})$/gm, (_, json4) => `data: ${one(json4)}`);
28061
+ }
28062
+ var withoutHidden = (hidden) => (message) => {
28063
+ if (message.result?.tools)
28064
+ message.result.tools = message.result.tools.filter((tool) => !hidden.has(tool.name));
28065
+ };
28066
+ function withoutLarge(message) {
28067
+ const sizes = (message.result?.content ?? []).map(
28068
+ (block) => block.type === "image" ? Buffer.byteLength(block.data ?? "", "base64") : 0
28069
+ );
28070
+ const size = Math.max(0, ...sizes);
28071
+ if (size <= MAX_PICTURE) return;
28072
+ const text3 = `The screenshot is ${(Math.ceil(size / MIB * 10) / 10).toFixed(1)} MiB, and the most a screenshot may be is ${MAX_PICTURE / MIB} MiB. Take it again without fullPage for what is on screen, or with element and target for one part of the page.`;
28073
+ message.result = { isError: true, content: [{ type: "text", text: text3 }] };
27993
28074
  }
27994
28075
  async function end(child) {
27995
28076
  if (child.exitCode !== null || child.signalCode !== null) return;
@@ -28002,7 +28083,10 @@ async function end(child) {
28002
28083
  var Browser = class {
28003
28084
  #host;
28004
28085
  #mcp;
28005
- #origin;
28086
+ /** Where its server answers, by way of its Unix socket. */
28087
+ #served;
28088
+ /** The folder its server made for its socket. */
28089
+ #socketFolder;
28006
28090
  /** The bound browser's socket, for the live viewer. */
28007
28091
  endpoint;
28008
28092
  #hidden;
@@ -28024,7 +28108,7 @@ var Browser = class {
28024
28108
  self,
28025
28109
  "browser-host",
28026
28110
  "--profile",
28027
- join2(folder, "profile"),
28111
+ join3(folder, "profile"),
28028
28112
  ...config3.path ? ["--executable", config3.path] : ["--channel", config3.browser ?? "chrome"],
28029
28113
  ...config3.sandbox === false ? ["--no-sandbox"] : []
28030
28114
  ],
@@ -28047,12 +28131,12 @@ var Browser = class {
28047
28131
  });
28048
28132
  this.endpoint.catch(() => {
28049
28133
  });
28050
- this.#origin = this.endpoint.then(
28134
+ this.#served = this.endpoint.then(
28051
28135
  (endpoint) => new Promise((done, failed) => {
28052
28136
  if (this.#stopping) return failed(new Error("It stopped."));
28053
28137
  const mcp = spawn(
28054
28138
  process.execPath,
28055
- [self, "browser-mcp", "--endpoint", endpoint, "--output", join2(folder, "output")],
28139
+ [self, "browser-mcp", "--endpoint", endpoint, "--output", join3(folder, "output")],
28056
28140
  // Its own limit on which files it touches is the folder it starts in.
28057
28141
  { cwd, stdio: ["ignore", "pipe", "pipe"] }
28058
28142
  );
@@ -28063,34 +28147,39 @@ var Browser = class {
28063
28147
  (code) => failed(new Error(`The browser's server exited with ${code}.`))
28064
28148
  );
28065
28149
  mcp.once("exit", ended("The browser's server"));
28066
- for (const stream of [mcp.stdout, mcp.stderr])
28067
- createInterface({ input: stream }).on("line", (line) => {
28150
+ createInterface({ input: mcp.stdout }).on("line", (line) => {
28151
+ try {
28152
+ const { socket } = JSON.parse(line);
28153
+ log(`The browser's server is on ${socket}.`);
28154
+ this.#socketFolder = dirname(socket);
28155
+ done(socketFetch(socket));
28156
+ } catch {
28068
28157
  log(line);
28069
- const listening = /^Listening on (http:\/\/[^/\s]+)/.exec(line);
28070
- if (listening) done(listening[1]);
28071
- });
28158
+ }
28159
+ });
28160
+ createInterface({ input: mcp.stderr }).on("line", log);
28072
28161
  })
28073
28162
  );
28074
- this.#origin.catch(() => {
28163
+ this.#served.catch(() => {
28075
28164
  });
28076
28165
  }
28077
28166
  /** Wait until it answers, and read its tools. */
28078
28167
  async ready() {
28079
- const origin = await Promise.race([
28080
- this.#origin,
28168
+ const served = await Promise.race([
28169
+ this.#served,
28081
28170
  new Promise(
28082
28171
  (_, failed) => setTimeout(() => failed(new Error("The browser's server didn't start.")), START_MS)
28083
28172
  )
28084
28173
  ]);
28085
28174
  const client = new Client({ name: "agentcomputer", version: "0" });
28086
- await client.connect(new StreamableHTTPClientTransport(new URL(`${origin}/mcp`)));
28175
+ await client.connect(new StreamableHTTPClientTransport(new URL(MCP), { fetch: served }));
28087
28176
  const { tools } = await client.listTools();
28088
28177
  await client.close();
28089
28178
  this.tools = tools.map((tool) => tool.name).filter((name) => !this.#hidden.has(name));
28090
28179
  }
28091
28180
  /** Answer one MCP request by passing it on, enforcing what this computer may not do. */
28092
28181
  async handle(request, response) {
28093
- const origin = await this.#origin;
28182
+ const served = await this.#served;
28094
28183
  const chunks = [];
28095
28184
  let size = 0;
28096
28185
  for await (const chunk of request) {
@@ -28134,7 +28223,7 @@ var Browser = class {
28134
28223
  const session = headers["mcp-session-id"] ?? "";
28135
28224
  if (request.method === "DELETE") this.gate?.sessionEnded(session);
28136
28225
  if (tool && this.gate?.takeAnsweredChooser(session) && tool !== "browser_file_upload")
28137
- await fetch(`${origin}/mcp`, {
28226
+ await served(MCP, {
28138
28227
  method: "POST",
28139
28228
  headers,
28140
28229
  body: JSON.stringify({
@@ -28147,20 +28236,22 @@ var Browser = class {
28147
28236
  });
28148
28237
  if (tool === "browser_file_upload") this.gate?.agentAnsweredChooser();
28149
28238
  if (message) headers["content-type"] = "application/json";
28150
- const upstream = await fetch(`${origin}/mcp`, {
28239
+ const upstream = await served(MCP, {
28151
28240
  method: request.method,
28152
28241
  headers,
28153
28242
  ...message ? { body: JSON.stringify(message) } : {}
28154
28243
  });
28155
- if (message?.method === "tools/list" && this.#hidden.size > 0) {
28156
- const text3 = withoutHidden(await upstream.text(), this.#hidden);
28157
- return sendAnswer(new Response(text3, upstream), response);
28158
- }
28159
- sendAnswer(upstream, response);
28244
+ let change;
28245
+ if (message?.method === "tools/list" && this.#hidden.size > 0)
28246
+ change = withoutHidden(this.#hidden);
28247
+ else if (tool === SCREENSHOT) change = withoutLarge;
28248
+ if (!change) return sendAnswer(upstream, response);
28249
+ sendAnswer(new Response(changed(await upstream.text(), change), upstream), response);
28160
28250
  }
28161
28251
  async stop() {
28162
28252
  this.#stopping = true;
28163
28253
  await Promise.all([this.#mcp, this.#host].map((child) => child ? end(child) : void 0));
28254
+ if (this.#socketFolder) rmSync3(this.#socketFolder, { recursive: true, force: true });
28164
28255
  }
28165
28256
  };
28166
28257
 
@@ -28173,7 +28264,7 @@ import { join as join7 } from "node:path";
28173
28264
  import { chmodSync as chmodSync2, existsSync, statSync as statSync2 } from "node:fs";
28174
28265
  import { createRequire as createRequire3 } from "node:module";
28175
28266
  import { hostname as hostname3 } from "node:os";
28176
- import { basename as basename2, dirname as dirname3, join as join6 } from "node:path";
28267
+ import { basename as basename2, dirname as dirname4, join as join6 } from "node:path";
28177
28268
 
28178
28269
  // node_modules/@agentorg/exec/dist/lib.js
28179
28270
  import { realpathSync } from "node:fs";
@@ -28183,7 +28274,7 @@ import { Http2ServerRequest, constants } from "node:http2";
28183
28274
  import { Readable as Readable2 } from "node:stream";
28184
28275
  import { randomBytes as randomBytes3 } from "node:crypto";
28185
28276
  import { lstat as lstat2, open as open3, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2 } from "node:fs/promises";
28186
- import { dirname as dirname2, join as join3 } from "node:path";
28277
+ import { dirname as dirname2, join as join32 } from "node:path";
28187
28278
  import { randomBytes as randomBytes2 } from "node:crypto";
28188
28279
  import {
28189
28280
  chmod,
@@ -28197,7 +28288,7 @@ import {
28197
28288
  stat,
28198
28289
  writeFile
28199
28290
  } from "node:fs/promises";
28200
- import { basename, dirname, join as join4, resolve, sep as sep2 } from "node:path";
28291
+ import { basename, dirname as dirname3, join as join4, resolve, sep as sep2 } from "node:path";
28201
28292
  import { spawn as spawn2 } from "node:child_process";
28202
28293
  import { randomBytes } from "node:crypto";
28203
28294
  import { StringDecoder } from "node:string_decoder";
@@ -30622,10 +30713,10 @@ function $constructor2(name, initializer32, proto, params) {
30622
30713
  if (initialized) {
30623
30714
  const own22 = Object.getPrototypeOf(inst);
30624
30715
  const ctorProto = inst._zod.constr.prototype;
30625
- let up = own22;
30626
- while (up && up !== ctorProto)
30627
- up = Object.getPrototypeOf(up);
30628
- const target = up ?? own22;
30716
+ let up2 = own22;
30717
+ while (up2 && up2 !== ctorProto)
30718
+ up2 = Object.getPrototypeOf(up2);
30719
+ const target = up2 ?? own22;
30629
30720
  if (!initialized.has(target)) {
30630
30721
  initialized.add(target);
30631
30722
  members2(target, protoMembers);
@@ -48482,21 +48573,21 @@ function visit(schema, fnOrHandlers) {
48482
48573
  case "object": {
48483
48574
  const oldShape = def.shape;
48484
48575
  const keys = Object.keys(oldShape);
48485
- let changed2 = false;
48576
+ let changed22 = false;
48486
48577
  const newShape = {};
48487
48578
  for (const k of keys) {
48488
48579
  const mapped = run3(oldShape[k]);
48489
48580
  if (mapped !== oldShape[k])
48490
- changed2 = true;
48581
+ changed22 = true;
48491
48582
  newShape[k] = mapped;
48492
48583
  }
48493
48584
  let newCatchall = def.catchall;
48494
48585
  if (def.catchall) {
48495
48586
  newCatchall = run3(def.catchall);
48496
48587
  if (newCatchall !== def.catchall)
48497
- changed2 = true;
48588
+ changed22 = true;
48498
48589
  }
48499
- return changed2 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48590
+ return changed22 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48500
48591
  }
48501
48592
  case "array": {
48502
48593
  const mapped = run3(def.element);
@@ -48504,21 +48595,21 @@ function visit(schema, fnOrHandlers) {
48504
48595
  }
48505
48596
  case "tuple": {
48506
48597
  const oldItems = def.items;
48507
- let changed2 = false;
48598
+ let changed22 = false;
48508
48599
  const newItems = [];
48509
48600
  for (const item of oldItems) {
48510
48601
  const mapped = run3(item);
48511
48602
  if (mapped !== item)
48512
- changed2 = true;
48603
+ changed22 = true;
48513
48604
  newItems.push(mapped);
48514
48605
  }
48515
48606
  let newRest = def.rest;
48516
48607
  if (def.rest) {
48517
48608
  newRest = run3(def.rest);
48518
48609
  if (newRest !== def.rest)
48519
- changed2 = true;
48610
+ changed22 = true;
48520
48611
  }
48521
- return changed2 ? clone2(s, { ...def, items: newItems, rest: newRest }) : s;
48612
+ return changed22 ? clone2(s, { ...def, items: newItems, rest: newRest }) : s;
48522
48613
  }
48523
48614
  case "record":
48524
48615
  case "map": {
@@ -48532,15 +48623,15 @@ function visit(schema, fnOrHandlers) {
48532
48623
  }
48533
48624
  case "union": {
48534
48625
  const oldOptions = def.options;
48535
- let changed2 = false;
48626
+ let changed22 = false;
48536
48627
  const newOptions = [];
48537
48628
  for (const opt of oldOptions) {
48538
48629
  const mapped = run3(opt);
48539
48630
  if (mapped !== opt)
48540
- changed2 = true;
48631
+ changed22 = true;
48541
48632
  newOptions.push(mapped);
48542
48633
  }
48543
- return changed2 ? clone2(s, { ...def, options: newOptions }) : s;
48634
+ return changed22 ? clone2(s, { ...def, options: newOptions }) : s;
48544
48635
  }
48545
48636
  case "intersection": {
48546
48637
  const newLeft = run3(def.left);
@@ -56621,14 +56712,14 @@ var require_validate2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56621
56712
  } else {
56622
56713
  const matches = RELATIVE_JSON_POINTER.exec($data);
56623
56714
  if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`);
56624
- const up = +matches[1];
56715
+ const up2 = +matches[1];
56625
56716
  jsonPointer = matches[2];
56626
56717
  if (jsonPointer === "#") {
56627
- if (up >= dataLevel) throw new Error(errorMsg("property/index", up));
56628
- return dataPathArr[dataLevel - up];
56718
+ if (up2 >= dataLevel) throw new Error(errorMsg("property/index", up2));
56719
+ return dataPathArr[dataLevel - up2];
56629
56720
  }
56630
- if (up > dataLevel) throw new Error(errorMsg("data", up));
56631
- data = dataNames[dataLevel - up];
56721
+ if (up2 > dataLevel) throw new Error(errorMsg("data", up2));
56722
+ data = dataNames[dataLevel - up2];
56632
56723
  if (!jsonPointer) return data;
56633
56724
  }
56634
56725
  let expr = data;
@@ -56638,8 +56729,8 @@ var require_validate2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56638
56729
  expr = (0, codegen_1._)`${expr} && ${data}`;
56639
56730
  }
56640
56731
  return expr;
56641
- function errorMsg(pointerType, up) {
56642
- return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
56732
+ function errorMsg(pointerType, up2) {
56733
+ return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
56643
56734
  }
56644
56735
  }
56645
56736
  exports.getData = getData;
@@ -65904,6 +65995,7 @@ var Sessions = class {
65904
65995
  this.#room();
65905
65996
  const started = Date.now();
65906
65997
  const session = this.#start(input2.cmd, input2.workdir ?? this.#root);
65998
+ if (input2.group) session.group = input2.group;
65907
65999
  const wait = clamp(input2.yield_time_ms ?? DEFAULT_YIELD_MS, MIN_YIELD_MS, MAX_YIELD_MS);
65908
66000
  return this.#after(session, wait, started, input2.max_output_tokens);
65909
66001
  }
@@ -65988,26 +66080,37 @@ var Sessions = class {
65988
66080
  if (!session) throw new ExecRefused(`No terminal ${id.slice(0, 64)} is here.`);
65989
66081
  await this.#end([session], "SIGINT");
65990
66082
  }
66083
+ /**
66084
+ * End the terminals still running of one group, as `stop` ends one, and say how many. A shell a
66085
+ * person opened is in no group.
66086
+ */
66087
+ async stopGroup(group) {
66088
+ const running2 = [...this.#sessions.values()].filter(
66089
+ (session) => session.group === group && session.exitCode === void 0
66090
+ );
66091
+ await this.#end(running2, "SIGINT");
66092
+ return running2.length;
66093
+ }
65991
66094
  /** Stop every terminal: a polite signal, then a certain one. */
65992
66095
  async stopAll() {
65993
66096
  await this.#end([...this.#sessions.values()], "SIGTERM");
65994
66097
  }
65995
66098
  async #end(sessions, polite) {
65996
- const running = sessions.filter((s) => s.exitCode === void 0);
65997
- for (const session of running) session.signal(session.tty ? "SIGHUP" : polite);
66099
+ const running2 = sessions.filter((s) => s.exitCode === void 0);
66100
+ for (const session of running2) session.signal(session.tty ? "SIGHUP" : polite);
65998
66101
  let timer;
65999
66102
  await Promise.race([
66000
- Promise.all(running.map((s) => s.ended)),
66103
+ Promise.all(running2.map((s) => s.ended)),
66001
66104
  new Promise((done) => {
66002
66105
  timer = setTimeout(done, KILL_AFTER_MS);
66003
66106
  })
66004
66107
  ]);
66005
66108
  clearTimeout(timer);
66006
- for (const session of running) if (session.exitCode === void 0) session.signal("SIGKILL");
66109
+ for (const session of running2) if (session.exitCode === void 0) session.signal("SIGKILL");
66007
66110
  }
66008
66111
  #room() {
66009
- const running = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
66010
- if (running.length >= MAX_RUNNING)
66112
+ const running2 = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
66113
+ if (running2.length >= MAX_RUNNING)
66011
66114
  throw new ExecRefused(`${MAX_RUNNING} commands are running. Wait for one, or stop one.`);
66012
66115
  }
66013
66116
  #running(id) {
@@ -66218,7 +66321,7 @@ async function inside(root, path) {
66218
66321
  () => false
66219
66322
  ))
66220
66323
  throw refused2();
66221
- const above = dirname(at);
66324
+ const above = dirname3(at);
66222
66325
  if (above === at) throw refused2();
66223
66326
  return join4(await real(above), basename(at));
66224
66327
  }
@@ -66306,7 +66409,7 @@ ${cut(shown.join("\n"), MAX_READ_CHARS)}`;
66306
66409
  after = before.split(input2.old_text).join(input2.new_text);
66307
66410
  }
66308
66411
  if (after === before) throw new ExecRefused("That edit changes nothing.");
66309
- const temporary = join4(dirname(path), `.agentexec-${randomBytes2(6).toString("hex")}`);
66412
+ const temporary = join4(dirname3(path), `.agentexec-${randomBytes2(6).toString("hex")}`);
66310
66413
  try {
66311
66414
  await writeFile(temporary, after, found ? { mode: found.mode } : {});
66312
66415
  if (found) await chmod(temporary, found.mode & 511);
@@ -66379,7 +66482,7 @@ function searched(job) {
66379
66482
  const more = out.length > limit;
66380
66483
  return (more ? [...out.slice(0, limit), `\u2026 more than ${limit} matches`] : out).join("\n");
66381
66484
  }
66382
- function changed(job) {
66485
+ function changed2(job) {
66383
66486
  const changes = [];
66384
66487
  let more = false;
66385
66488
  const newest = () => {
@@ -66432,7 +66535,7 @@ function changedSince(root, since) {
66432
66535
  }
66433
66536
  if (!isMainThread && workerData?.agentexecSearch) {
66434
66537
  const job = workerData;
66435
- parentPort?.postMessage("since" in job ? changed(job) : searched(job));
66538
+ parentPort?.postMessage("since" in job ? changed2(job) : searched(job));
66436
66539
  }
66437
66540
  var MAX_READ_BYTES = 1024 * 1024;
66438
66541
  function createHostFiles(root, level) {
@@ -66445,7 +66548,7 @@ function createHostFiles(root, level) {
66445
66548
  found.sort(foldersFirst);
66446
66549
  const entries = await Promise.all(
66447
66550
  found.slice(0, MAX_ENTRIES).map(async (entry) => {
66448
- const own22 = await lstat2(join3(folder, entry.name)).catch(() => void 0);
66551
+ const own22 = await lstat2(join32(folder, entry.name)).catch(() => void 0);
66449
66552
  return own22 && {
66450
66553
  name: entry.name,
66451
66554
  kind: entry.isDirectory() ? "folder" : entry.isSymbolicLink() ? "link" : "file",
@@ -66492,7 +66595,7 @@ function createHostFiles(root, level) {
66492
66595
  });
66493
66596
  if (before?.isDirectory()) throw new ExecRefused(`${path} is a folder.`);
66494
66597
  await stat2(dirname2(target));
66495
- const temporary = join3(dirname2(target), `.agentexec-${randomBytes3(6).toString("hex")}`);
66598
+ const temporary = join32(dirname2(target), `.agentexec-${randomBytes3(6).toString("hex")}`);
66496
66599
  const file2 = await open3(temporary, "wx");
66497
66600
  const discard = async () => {
66498
66601
  await file2.close().catch(() => {
@@ -66585,6 +66688,14 @@ var Answered = {
66585
66688
  session_id: external_exports.string().nullable().describe("For write_stdin, while it is still running."),
66586
66689
  text: external_exports.string().describe("What it wrote since the last call, cut in the middle if long.")
66587
66690
  };
66691
+ var GROUP = "agentexec/group";
66692
+ var STOP_GROUP = "agentexec/group/stop";
66693
+ var Group = external_exports.string().min(1).max(200);
66694
+ var groupOf = (meta3) => {
66695
+ const word = Group.optional().safeParse(meta3?.[GROUP]);
66696
+ if (!word.success) throw new ExecRefused(`${GROUP} in _meta is a string of 1 to 200 characters.`);
66697
+ return word.data;
66698
+ };
66588
66699
  var YieldTime = external_exports.number().int().positive().optional();
66589
66700
  var MaxTokens = external_exports.number().int().positive().optional();
66590
66701
  var READ_ONLY = { readOnlyHint: true };
@@ -66613,6 +66724,11 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66613
66724
  }
66614
66725
  );
66615
66726
  if (level === "terminal") {
66727
+ server.server.setRequestHandler(
66728
+ STOP_GROUP,
66729
+ { params: external_exports.object({ group: Group }), result: external_exports.object({ stopped: external_exports.number() }) },
66730
+ async ({ group }) => ({ stopped: await sessions.stopGroup(group) })
66731
+ );
66616
66732
  server.registerTool(
66617
66733
  "exec_command",
66618
66734
  {
@@ -66626,7 +66742,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66626
66742
  outputSchema: Answered,
66627
66743
  annotations: { destructiveHint: true }
66628
66744
  },
66629
- (input2) => answering(() => sessions.exec(input2))
66745
+ (input2, ctx) => answering(async () => sessions.exec({ ...input2, group: groupOf(ctx.mcpReq._meta) }))
66630
66746
  );
66631
66747
  server.registerTool(
66632
66748
  "write_stdin",
@@ -66729,7 +66845,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66729
66845
  );
66730
66846
  return server;
66731
66847
  }
66732
- var VERSION = "0.0.7";
66848
+ var VERSION = "0.0.8";
66733
66849
  function createAgentExec(options) {
66734
66850
  const root = realpathSync(resolve3(options.workspace));
66735
66851
  const level = options.level ?? "read";
@@ -67034,8 +67150,8 @@ var BrowserLive = class {
67034
67150
  }
67035
67151
  /** The cast of a page, started if no socket was shown it yet. */
67036
67152
  #cast(page) {
67037
- const running = this.#casts.get(page);
67038
- if (running) return running;
67153
+ const running2 = this.#casts.get(page);
67154
+ if (running2) return running2;
67039
67155
  const onFrame = (frame) => {
67040
67156
  const watching = this.#shownTo(page);
67041
67157
  if (frame.width !== cast.size.width || frame.height !== cast.size.height) {
@@ -67733,7 +67849,7 @@ var loadPty = () => {
67733
67849
  } catch {
67734
67850
  return fails("node-pty isn't installed with this service.");
67735
67851
  }
67736
- const root = dirname3(require3.resolve("node-pty/package.json"));
67852
+ const root = dirname4(require3.resolve("node-pty/package.json"));
67737
67853
  for (const helper of [
67738
67854
  join6(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
67739
67855
  join6(root, "build", "Release", "spawn-helper")
@@ -67748,7 +67864,6 @@ var loadPty = () => {
67748
67864
  return pty;
67749
67865
  };
67750
67866
  var ProcessRuntime = class {
67751
- isolation = "none";
67752
67867
  #config;
67753
67868
  #contained;
67754
67869
  /** `contained` is for inside a container, where the computer and its load are its own. */
@@ -67756,6 +67871,9 @@ var ProcessRuntime = class {
67756
67871
  this.#config = config3;
67757
67872
  this.#contained = contained;
67758
67873
  }
67874
+ async available() {
67875
+ return true;
67876
+ }
67759
67877
  async prepare() {
67760
67878
  }
67761
67879
  async stopLeftover() {
@@ -67850,7 +67968,7 @@ async function serveInside(options) {
67850
67968
  if (!token) throw new Error(`${INNER_TOKEN_VARIABLE} is not set.`);
67851
67969
  delete process.env[INNER_TOKEN_VARIABLE];
67852
67970
  const digest = digestOf(token);
67853
- const running = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67971
+ const running2 = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67854
67972
  {
67855
67973
  id: "inside",
67856
67974
  ...options,
@@ -67877,13 +67995,13 @@ async function serveInside(options) {
67877
67995
  if (!given || !sameToken(given, digest)) return void response.writeHead(401).end();
67878
67996
  const { pathname } = new URL(request.url ?? "/", "http://localhost");
67879
67997
  if (pathname === "/ready") {
67880
- const { browserTools, surfaces } = await running;
67998
+ const { browserTools, surfaces } = await running2;
67881
67999
  response.writeHead(200, { "Content-Type": "application/json" });
67882
68000
  return void response.end(JSON.stringify({ browserTools, surfaces }));
67883
68001
  }
67884
68002
  const endpoint = /^\/mcp\/(computer|browser)$/.exec(pathname)?.[1];
67885
68003
  if (!endpoint) return void response.writeHead(404).end();
67886
- return (await running).handle(endpoint, request, response);
68004
+ return (await running2).handle(endpoint, request, response);
67887
68005
  };
67888
68006
  const server = createServer3((request, response) => {
67889
68007
  route(request, response).catch((error62) => {
@@ -67899,14 +68017,14 @@ async function serveInside(options) {
67899
68017
  const mode = url3.searchParams.get("mode");
67900
68018
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
67901
68019
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
67902
- running.then((computer) => computer.openLive(surface, mode, request, socket, head)).catch(() => socket.destroy());
68020
+ running2.then((computer) => computer.openLive(surface, mode, request, socket, head)).catch(() => socket.destroy());
67903
68021
  });
67904
68022
  server.listen(INNER_PORT, "0.0.0.0");
67905
68023
  for (const signal of ["SIGINT", "SIGTERM"])
67906
68024
  process.once(signal, () => {
67907
- void running.then((computer) => computer.stop()).finally(() => process.exit(0));
68025
+ void running2.then((computer) => computer.stop()).finally(() => process.exit(0));
67908
68026
  });
67909
- await running;
68027
+ await running2;
67910
68028
  }
67911
68029
 
67912
68030
  // src/container.ts
@@ -67941,17 +68059,30 @@ var UPGRADE_HEADERS = [
67941
68059
  var nameOf = (id) => `agentcomputer-${id}`;
67942
68060
  var sleep5 = (ms) => new Promise((done) => setTimeout(done, ms));
67943
68061
  var ContainerRuntime = class {
67944
- isolation = "container";
67945
68062
  #image;
67946
68063
  constructor(image) {
67947
68064
  this.#image = image;
67948
68065
  }
67949
- async prepare() {
68066
+ /** Whether Docker answers, asked for no longer than this. */
68067
+ async #answers(ms) {
67950
68068
  try {
67951
- await docker(["version", "--format", "{{.Server.Version}}"]);
67952
- } catch (error62) {
67953
- throw new Error(`Container isolation needs Docker, which didn't answer: ${String(error62)}`);
68069
+ await run2("docker", ["version", "--format", "{{.Server.Version}}"], { timeout: ms });
68070
+ return true;
68071
+ } catch {
68072
+ return false;
67954
68073
  }
68074
+ }
68075
+ // Asked each time the service says what it can make, so it doesn't wait on a Docker that hangs.
68076
+ available() {
68077
+ return this.#answers(1e3);
68078
+ }
68079
+ /** Docker, or the refusal of what can't be done without it. */
68080
+ async #docker() {
68081
+ if (!await this.#answers(1e4))
68082
+ throw new ApiError(409, "A container needs Docker, which isn't answering on this machine.");
68083
+ }
68084
+ async prepare() {
68085
+ await this.#docker();
67955
68086
  try {
67956
68087
  await docker(["image", "inspect", "--format", "{{.Id}}", this.#image]);
67957
68088
  } catch {
@@ -68092,11 +68223,14 @@ var ContainerRuntime = class {
68092
68223
  }
68093
68224
  throw new Error("Its container didn't start. Its logs say why.");
68094
68225
  }
68226
+ // Neither is said to be done where Docker doesn't answer: the container would be left as it is.
68095
68227
  async stopLeftover(id) {
68228
+ await this.#docker();
68096
68229
  await docker(["stop", "--time", STOP_SECONDS, nameOf(id)]).catch(() => {
68097
68230
  });
68098
68231
  }
68099
68232
  async remove(id) {
68233
+ await this.#docker();
68100
68234
  await docker(["rm", "--force", nameOf(id)]).catch(() => {
68101
68235
  });
68102
68236
  }
@@ -68145,10 +68279,14 @@ async function forward(url3, authorization, request, response) {
68145
68279
  }
68146
68280
 
68147
68281
  // src/handlers.ts
68148
- var VERSION2 = "0.0.14";
68282
+ var VERSION2 = "0.0.16";
68149
68283
  function createHandlers(computers) {
68150
68284
  return {
68151
- getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
68285
+ getInfo: async () => ({
68286
+ version: VERSION2,
68287
+ isolation: computers.isolation,
68288
+ isolationsAvailable: await computers.available()
68289
+ }),
68152
68290
  listComputers: async ({ state }) => computers.list(state),
68153
68291
  createComputer: (input2) => computers.create(input2),
68154
68292
  getComputer: async ({ computerID }) => computers.get(computerID),
@@ -68167,6 +68305,81 @@ function createHandlers(computers) {
68167
68305
  };
68168
68306
  }
68169
68307
 
68308
+ // src/running.ts
68309
+ import { spawn as spawn4 } from "node:child_process";
68310
+ import { closeSync, openSync, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
68311
+ import { join as join9 } from "node:path";
68312
+ var MAX_LOG = 1024 * 1024;
68313
+ var STOP_MS2 = 3e4;
68314
+ var sleep6 = (ms) => new Promise((done) => setTimeout(done, ms));
68315
+ var recordOf = (data) => join9(data, "server.json");
68316
+ function recorded(data) {
68317
+ try {
68318
+ const { url: url3, pid } = JSON.parse(readFileSync4(recordOf(data), "utf8"));
68319
+ return { url: url3, pid, token: readFileSync4(join9(data, "control-token"), "utf8").trim() };
68320
+ } catch {
68321
+ return void 0;
68322
+ }
68323
+ }
68324
+ async function running(data, said = recorded(data)) {
68325
+ if (!said) return void 0;
68326
+ try {
68327
+ const answer = await fetch(`${said.url}/v1/info`, {
68328
+ headers: { Authorization: `Bearer ${said.token}` },
68329
+ signal: AbortSignal.timeout(3e3)
68330
+ });
68331
+ if (!answer.ok) return void 0;
68332
+ const { version: version3, isolation } = await answer.json();
68333
+ return { url: said.url, pid: said.pid, version: version3, isolation };
68334
+ } catch {
68335
+ return void 0;
68336
+ }
68337
+ }
68338
+ async function up(data, program, args) {
68339
+ const already = await running(data);
68340
+ if (already) return already;
68341
+ const log = join9(privateFolder(data), "service.log");
68342
+ try {
68343
+ const before = readFileSync4(log);
68344
+ if (before.length > MAX_LOG) writeFileSync2(log, before.subarray(before.length - MAX_LOG));
68345
+ } catch {
68346
+ }
68347
+ const from = statSync3(log, { throwIfNoEntry: false })?.size ?? 0;
68348
+ const out = openSync(log, "a", 384);
68349
+ const child = spawn4(process.execPath, [program, "serve", ...args], {
68350
+ detached: true,
68351
+ stdio: ["ignore", out, out]
68352
+ });
68353
+ closeSync(out);
68354
+ child.unref();
68355
+ let ended = false;
68356
+ child.once("exit", () => {
68357
+ ended = true;
68358
+ });
68359
+ for (; ; await sleep6(100)) {
68360
+ const said = recorded(data);
68361
+ const up2 = await running(data, said);
68362
+ const own3 = said?.pid === child.pid;
68363
+ if (up2 && (own3 || ended)) return up2;
68364
+ if (!ended && !own3) continue;
68365
+ if (!ended) {
68366
+ child.kill();
68367
+ throw new Error(`The service doesn't answer at ${said?.url} from here, so it was stopped.`);
68368
+ }
68369
+ const wrote = readFileSync4(log).subarray(from).toString().trim();
68370
+ throw new Error(`The service didn't start.${wrote ? ` It said:
68371
+ ${wrote}` : ""}`);
68372
+ }
68373
+ }
68374
+ async function down(data) {
68375
+ const found = await running(data);
68376
+ if (!found) return;
68377
+ process.kill(found.pid, "SIGTERM");
68378
+ for (const deadline = Date.now() + STOP_MS2; Date.now() < deadline; await sleep6(100))
68379
+ if (!await running(data)) return;
68380
+ throw new Error(`The service at ${found.url} didn't stop.`);
68381
+ }
68382
+
68170
68383
  // node_modules/openapi-fetch/dist/index.mjs
68171
68384
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
68172
68385
  var supportsRequestInitExt = () => {
@@ -68852,13 +69065,14 @@ var Computer = class _Computer {
68852
69065
  this.files = new Files(this.#computer);
68853
69066
  }
68854
69067
  static async create(opts) {
68855
- const { access, browser, workspace, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
69068
+ const { access, browser, isolation, workspace, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
68856
69069
  const connection = { ...apiKey ? { apiKey } : {}, ...apiUrl ? { apiUrl } : {} };
68857
69070
  const created = answered(
68858
69071
  await apiOf(connection).POST("/v1/computers", {
68859
69072
  body: {
68860
69073
  access,
68861
69074
  browser: browser ?? false,
69075
+ ...isolation === void 0 ? {} : { isolation },
68862
69076
  ...workspace === void 0 ? {} : { workspace },
68863
69077
  ...name === void 0 ? {} : { name },
68864
69078
  ...timeoutMs === void 0 ? {} : { timeout: seconds(timeoutMs) },
@@ -68963,13 +69177,12 @@ var Computer = class _Computer {
68963
69177
  };
68964
69178
 
68965
69179
  // src/service.ts
68966
- import { readFileSync as readFileSync5, rmSync as rmSync3 } from "node:fs";
69180
+ import { readFileSync as readFileSync6, rmSync as rmSync5 } from "node:fs";
68967
69181
  import {
68968
69182
  createServer as createServer5,
68969
69183
  STATUS_CODES as STATUS_CODES3
68970
69184
  } from "node:http";
68971
69185
  import { createServer as createTlsServer } from "node:https";
68972
- import { join as join10 } from "node:path";
68973
69186
 
68974
69187
  // node_modules/@modelcontextprotocol/server/dist/chunk-vYduTEsO.mjs
68975
69188
  var __create4 = Object.create;
@@ -72430,7 +72643,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS3 = 250;
72430
72643
  function inputRequiredRoundsExceededMessage3(method, maxRounds) {
72431
72644
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
72432
72645
  }
72433
- function sleep6(ms, signal) {
72646
+ function sleep7(ms, signal) {
72434
72647
  return new Promise((resolve6, reject) => {
72435
72648
  if (signal?.aborted) {
72436
72649
  reject(signal.reason instanceof SdkError3 ? signal.reason : new SdkError3(SdkErrorCode3.RequestTimeout, String(signal.reason)));
@@ -76061,14 +76274,14 @@ var require_validate3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76061
76274
  } else {
76062
76275
  const matches = RELATIVE_JSON_POINTER.exec($data);
76063
76276
  if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`);
76064
- const up = +matches[1];
76277
+ const up2 = +matches[1];
76065
76278
  jsonPointer = matches[2];
76066
76279
  if (jsonPointer === "#") {
76067
- if (up >= dataLevel) throw new Error(errorMsg("property/index", up));
76068
- return dataPathArr[dataLevel - up];
76280
+ if (up2 >= dataLevel) throw new Error(errorMsg("property/index", up2));
76281
+ return dataPathArr[dataLevel - up2];
76069
76282
  }
76070
- if (up > dataLevel) throw new Error(errorMsg("data", up));
76071
- data = dataNames[dataLevel - up];
76283
+ if (up2 > dataLevel) throw new Error(errorMsg("data", up2));
76284
+ data = dataNames[dataLevel - up2];
76072
76285
  if (!jsonPointer) return data;
76073
76286
  }
76074
76287
  let expr = data;
@@ -76078,8 +76291,8 @@ var require_validate3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76078
76291
  expr = (0, codegen_1._)`${expr} && ${data}`;
76079
76292
  }
76080
76293
  return expr;
76081
- function errorMsg(pointerType, up) {
76082
- return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
76294
+ function errorMsg(pointerType, up2) {
76295
+ return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
76083
76296
  }
76084
76297
  }
76085
76298
  exports.getData = getData;
@@ -81427,7 +81640,7 @@ var LegacyInputRequiredShim2 = class {
81427
81640
  } finally {
81428
81641
  roundAbort.dispose();
81429
81642
  }
81430
- } else await sleep6(REQUEST_STATE_ONLY_LEG_PACING_MS3, outerSignal);
81643
+ } else await sleep7(REQUEST_STATE_ONLY_LEG_PACING_MS3, outerSignal);
81431
81644
  let ctxNext = {
81432
81645
  ...ctx,
81433
81646
  mcpReq: {
@@ -84784,8 +84997,8 @@ var NodeStreamableHTTPServerTransport2 = class {
84784
84997
 
84785
84998
  // src/computers.ts
84786
84999
  import { randomBytes as randomBytes5 } from "node:crypto";
84787
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
84788
- import { isAbsolute, join as join9, resolve as resolve4 } from "node:path";
85000
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, statSync as statSync4 } from "node:fs";
85001
+ import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
84789
85002
  var READ = ["about", "read", "list", "search"];
84790
85003
  var FILES = [...READ, "edit"];
84791
85004
  var TOOLS2 = {
@@ -84793,29 +85006,37 @@ var TOOLS2 = {
84793
85006
  files: FILES,
84794
85007
  terminal: [...FILES, "exec_command", "write_stdin", "list_sessions"]
84795
85008
  };
84796
- var MAX_LOG = 256 * 1024;
85009
+ var MAX_LOG2 = 256 * 1024;
84797
85010
  var MAX_TIMER_S = 2e6;
84798
85011
  var TICKET_MS = 6e4;
84799
85012
  var expiry = (seconds2) => new Date(Date.now() + seconds2 * 1e3).toISOString();
85013
+ var messageOf = (error62) => error62 instanceof Error ? error62.message : String(error62);
85014
+ var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf(error62));
84800
85015
  var Computers = class {
84801
85016
  #data;
84802
85017
  #base;
84803
- #runtime;
85018
+ #runtimes;
85019
+ /** The isolation of a computer made without saying which. */
85020
+ isolation;
84804
85021
  #stored = /* @__PURE__ */ new Map();
84805
85022
  #live = /* @__PURE__ */ new Map();
84806
85023
  #tickets = /* @__PURE__ */ new Map();
84807
85024
  #file;
84808
- constructor(data, base, runtime) {
85025
+ constructor(data, base, runtimes, isolation) {
84809
85026
  this.#data = data;
84810
85027
  this.#base = base;
84811
- this.#runtime = runtime;
84812
- this.#file = join9(data, "computers.json");
85028
+ this.#runtimes = runtimes;
85029
+ this.isolation = isolation;
85030
+ this.#file = join10(data, "computers.json");
84813
85031
  if (existsSync2(this.#file))
84814
- for (const entry of JSON.parse(readFileSync4(this.#file, "utf8")))
84815
- this.#stored.set(entry.id, entry);
85032
+ for (const entry of JSON.parse(readFileSync5(this.#file, "utf8")))
85033
+ this.#stored.set(entry.id, { ...entry, isolation: entry.isolation ?? isolation });
84816
85034
  }
84817
- get isolation() {
84818
- return this.#runtime.isolation;
85035
+ /** The isolations a computer can be made in now. */
85036
+ async available() {
85037
+ const all = Object.keys(this.#runtimes);
85038
+ const can = await Promise.all(all.map((one) => this.#runtimes[one].available()));
85039
+ return all.filter((_, index) => can[index]);
84819
85040
  }
84820
85041
  /** Bring back what was running when the service last stopped. */
84821
85042
  async resume() {
@@ -84823,12 +85044,17 @@ var Computers = class {
84823
85044
  if (stored.state !== "running") continue;
84824
85045
  if (stored.endAt && Date.parse(stored.endAt) <= Date.now()) {
84825
85046
  stored.state = "stopped";
84826
- await this.#runtime.stopLeftover(stored.id);
85047
+ await this.#runtimes[stored.isolation].stopLeftover(stored.id).catch(
85048
+ (error62) => this.#log(stored.id, `What it left running wasn't stopped: ${messageOf(error62)}`)
85049
+ );
84827
85050
  continue;
84828
85051
  }
84829
- await this.#start(stored).catch(
84830
- (error62) => this.#log(stored.id, `It didn't start again: ${String(error62)}`)
84831
- );
85052
+ await this.#start(stored).catch((error62) => {
85053
+ const live = this.#live.get(stored.id);
85054
+ if (live) live.failed = true;
85055
+ this.#arm(stored);
85056
+ this.#log(stored.id, `It didn't start again: ${messageOf(error62)}`);
85057
+ });
84832
85058
  }
84833
85059
  this.#save();
84834
85060
  }
@@ -84841,7 +85067,7 @@ var Computers = class {
84841
85067
  if (input2.workspace !== void 0) {
84842
85068
  let folder = false;
84843
85069
  try {
84844
- folder = isAbsolute(input2.workspace) && statSync3(input2.workspace).isDirectory();
85070
+ folder = isAbsolute(input2.workspace) && statSync4(input2.workspace).isDirectory();
84845
85071
  } catch {
84846
85072
  }
84847
85073
  if (!folder) throw new ApiError(400, `There's no folder at ${input2.workspace}.`);
@@ -84854,6 +85080,7 @@ var Computers = class {
84854
85080
  id,
84855
85081
  name: name ?? id,
84856
85082
  access,
85083
+ isolation: input2.isolation ?? this.isolation,
84857
85084
  browser,
84858
85085
  ...workspace ? { workspace } : {},
84859
85086
  metadata: metadata ?? {},
@@ -84864,7 +85091,7 @@ var Computers = class {
84864
85091
  ...timeout === void 0 ? {} : { endAt: expiry(timeout) }
84865
85092
  };
84866
85093
  if (!workspace && access !== "none") {
84867
- stored.workspace = join9(this.#folder(id), "workspace");
85094
+ stored.workspace = join10(this.#folder(id), "workspace");
84868
85095
  mkdirSync2(stored.workspace, { recursive: true, mode: 448 });
84869
85096
  }
84870
85097
  this.#stored.set(id, stored);
@@ -84873,9 +85100,10 @@ var Computers = class {
84873
85100
  } catch (error62) {
84874
85101
  await this.#halt(id);
84875
85102
  this.#stored.delete(id);
84876
- await this.#runtime.remove(id);
84877
- rmSync2(this.#folder(id), { recursive: true, force: true });
84878
- throw new ApiError(500, error62 instanceof Error ? error62.message : String(error62));
85103
+ await this.#runtimes[stored.isolation].remove(id).catch(() => {
85104
+ });
85105
+ rmSync4(this.#folder(id), { recursive: true, force: true });
85106
+ throw refusalOf(error62);
84879
85107
  }
84880
85108
  this.#save();
84881
85109
  return { ...this.#describe(stored), accessToken: token };
@@ -84930,25 +85158,31 @@ var Computers = class {
84930
85158
  await this.#halt(id);
84931
85159
  stored.state = "stopped";
84932
85160
  this.#save();
84933
- throw new ApiError(500, error62 instanceof Error ? error62.message : String(error62));
85161
+ throw refusalOf(error62);
84934
85162
  }
84935
85163
  this.#save();
84936
85164
  return this.#describe(stored);
84937
85165
  }
84938
85166
  /** Stop everything and remove what the service made. A folder someone named stays. */
84939
85167
  async kill(id) {
84940
- this.#found(id);
85168
+ const stored = this.#found(id);
84941
85169
  await this.#halt(id);
84942
- await this.#runtime.remove(id);
85170
+ try {
85171
+ await this.#runtimes[stored.isolation].remove(id);
85172
+ } catch (error62) {
85173
+ stored.state = "stopped";
85174
+ this.#save();
85175
+ throw refusalOf(error62);
85176
+ }
84943
85177
  this.#stored.delete(id);
84944
85178
  this.#save();
84945
- rmSync2(this.#folder(id), { recursive: true, force: true });
85179
+ rmSync4(this.#folder(id), { recursive: true, force: true });
84946
85180
  }
84947
85181
  logs(id) {
84948
85182
  this.#found(id);
84949
85183
  let text3 = "";
84950
85184
  try {
84951
- text3 = readFileSync4(this.#logFile(id), "utf8");
85185
+ text3 = readFileSync5(this.#logFile(id), "utf8");
84952
85186
  } catch {
84953
85187
  }
84954
85188
  return text3.split("\n").filter(Boolean).map((entry) => ({
@@ -84965,17 +85199,17 @@ var Computers = class {
84965
85199
  throw new ApiError(404, "This computer has no files or terminal.");
84966
85200
  if (endpoint === "browser" && !stored.browser)
84967
85201
  throw new ApiError(404, "This computer has no browser.");
84968
- const running = this.#live.get(id)?.running;
84969
- if (!running) throw new ApiError(409, "It has stopped.");
85202
+ const running2 = this.#live.get(id)?.running;
85203
+ if (!running2) throw new ApiError(409, "It has stopped.");
84970
85204
  this.#log(id, `${request.method} mcp/${endpoint}`);
84971
- return running.handle(endpoint, request, response);
85205
+ return running2.handle(endpoint, request, response);
84972
85206
  }
84973
85207
  /** A one-time ticket to open one of its surfaces live. */
84974
85208
  liveSession(id, surface, mode) {
84975
85209
  this.#found(id);
84976
- const running = this.#live.get(id)?.running;
84977
- if (!running) throw new ApiError(409, "It has stopped.");
84978
- const info = running.surfaces.find((one) => one.surface === surface);
85210
+ const running2 = this.#live.get(id)?.running;
85211
+ if (!running2) throw new ApiError(409, "It has stopped.");
85212
+ const info = running2.surfaces.find((one) => one.surface === surface);
84979
85213
  if (!info) throw new ApiError(409, `This computer has no ${surface} to open.`);
84980
85214
  if (info.unavailable) throw new ApiError(409, info.unavailable);
84981
85215
  if (mode === "operate" && !info.operate)
@@ -85000,10 +85234,10 @@ var Computers = class {
85000
85234
  this.#tickets.delete(ticket);
85001
85235
  if (!found || found.expiresAt <= Date.now())
85002
85236
  throw new ApiError(404, "There's no such live session: it was used, or it expired.");
85003
- const running = this.#live.get(found.computerID)?.running;
85004
- if (!running) throw new ApiError(409, "It has stopped.");
85237
+ const running2 = this.#live.get(found.computerID)?.running;
85238
+ if (!running2) throw new ApiError(409, "It has stopped.");
85005
85239
  this.#log(found.computerID, `live ${found.surface} ${found.mode}`);
85006
- await running.openLive(found.surface, found.mode, request, socket, head);
85240
+ await running2.openLive(found.surface, found.mode, request, socket, head);
85007
85241
  }
85008
85242
  /** Stop every computer, leaving their records to resume at the next start. */
85009
85243
  async close() {
@@ -85015,17 +85249,17 @@ var Computers = class {
85015
85249
  return stored;
85016
85250
  }
85017
85251
  #folder(id) {
85018
- return privateFolder(join9(this.#data, "computers", id));
85252
+ return privateFolder(join10(this.#data, "computers", id));
85019
85253
  }
85020
85254
  #logFile(id) {
85021
- return join9(this.#folder(id), "log");
85255
+ return join10(this.#folder(id), "log");
85022
85256
  }
85023
85257
  #log(id, line) {
85024
85258
  if (!this.#stored.has(id)) return;
85025
85259
  try {
85026
85260
  const file2 = this.#logFile(id);
85027
- if (existsSync2(file2) && statSync3(file2).size > MAX_LOG)
85028
- writePrivate(file2, readFileSync4(file2, "utf8").slice(-MAX_LOG / 2));
85261
+ if (existsSync2(file2) && statSync4(file2).size > MAX_LOG2)
85262
+ writePrivate(file2, readFileSync5(file2, "utf8").slice(-MAX_LOG2 / 2));
85029
85263
  appendFileSync(file2, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
85030
85264
  `, { mode: 384 });
85031
85265
  } catch {
@@ -85036,12 +85270,13 @@ var Computers = class {
85036
85270
  }
85037
85271
  #describe(stored) {
85038
85272
  const path = `${this.#base()}/v1/computers/${stored.id}/mcp`;
85039
- const down = this.#live.get(stored.id)?.failed === true;
85273
+ const down2 = this.#live.get(stored.id)?.failed === true;
85040
85274
  return {
85041
85275
  computerID: stored.id,
85042
85276
  name: stored.name,
85043
- state: stored.state === "running" && down ? "failed" : stored.state,
85277
+ state: stored.state === "running" && down2 ? "failed" : stored.state,
85044
85278
  access: stored.access,
85279
+ isolation: stored.isolation,
85045
85280
  browser: stored.browser,
85046
85281
  workspace: stored.workspace ?? null,
85047
85282
  startedAt: stored.startedAt,
@@ -85057,7 +85292,9 @@ var Computers = class {
85057
85292
  const live = {};
85058
85293
  this.#live.set(stored.id, live);
85059
85294
  stored.startedAt = (/* @__PURE__ */ new Date()).toISOString();
85060
- live.running = await this.#runtime.start(
85295
+ const runtime = this.#runtimes[stored.isolation];
85296
+ await runtime.prepare();
85297
+ live.running = await runtime.start(
85061
85298
  {
85062
85299
  id: stored.id,
85063
85300
  access: stored.access,
@@ -85110,7 +85347,7 @@ var OPENAPI = {
85110
85347
  "openapi": "3.1.0",
85111
85348
  "info": {
85112
85349
  "title": "AgentComputer",
85113
- "version": "0.0.14",
85350
+ "version": "0.0.16",
85114
85351
  "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.",
85115
85352
  "license": {
85116
85353
  "name": "Apache-2.0",
@@ -85570,7 +85807,8 @@ var OPENAPI = {
85570
85807
  "running",
85571
85808
  "stopped",
85572
85809
  "failed"
85573
- ]
85810
+ ],
85811
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
85574
85812
  },
85575
85813
  "Timeout": {
85576
85814
  "type": "integer",
@@ -85611,6 +85849,10 @@ var OPENAPI = {
85611
85849
  "maxLength": 4096,
85612
85850
  "description": "The full path of a folder that exists, on the service's machine. With none, the service makes a folder, which goes when the computer is killed. A folder named here never does."
85613
85851
  },
85852
+ "isolation": {
85853
+ "$ref": "#/components/schemas/Isolation",
85854
+ "description": "How it is isolated, which it keeps for as long as it exists. Left out, it is the service's own, the `isolation` that `getInfo` answers."
85855
+ },
85614
85856
  "timeout": {
85615
85857
  "$ref": "#/components/schemas/Timeout"
85616
85858
  },
@@ -85619,6 +85861,14 @@ var OPENAPI = {
85619
85861
  }
85620
85862
  }
85621
85863
  },
85864
+ "Isolation": {
85865
+ "type": "string",
85866
+ "enum": [
85867
+ "none",
85868
+ "container"
85869
+ ],
85870
+ "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
85871
+ },
85622
85872
  "Endpoints": {
85623
85873
  "type": "object",
85624
85874
  "required": [
@@ -85650,6 +85900,7 @@ var OPENAPI = {
85650
85900
  "name",
85651
85901
  "state",
85652
85902
  "access",
85903
+ "isolation",
85653
85904
  "browser",
85654
85905
  "workspace",
85655
85906
  "startedAt",
@@ -85670,6 +85921,10 @@ var OPENAPI = {
85670
85921
  "access": {
85671
85922
  "$ref": "#/components/schemas/Access"
85672
85923
  },
85924
+ "isolation": {
85925
+ "$ref": "#/components/schemas/Isolation",
85926
+ "description": "The isolation it was made in. It is never run in another."
85927
+ },
85673
85928
  "browser": {
85674
85929
  "type": "boolean"
85675
85930
  },
@@ -86828,19 +87083,23 @@ var OPENAPI = {
86828
87083
  "type": "object",
86829
87084
  "required": [
86830
87085
  "version",
86831
- "isolation"
87086
+ "isolation",
87087
+ "isolationsAvailable"
86832
87088
  ],
86833
87089
  "properties": {
86834
87090
  "version": {
86835
87091
  "type": "string"
86836
87092
  },
86837
87093
  "isolation": {
86838
- "type": "string",
86839
- "enum": [
86840
- "none",
86841
- "container"
86842
- ],
86843
- "description": "How this service isolates its computers, chosen when it is started."
87094
+ "$ref": "#/components/schemas/Isolation",
87095
+ "description": "The isolation of a computer made without saying which, chosen when the service is started."
87096
+ },
87097
+ "isolationsAvailable": {
87098
+ "type": "array",
87099
+ "items": {
87100
+ "$ref": "#/components/schemas/Isolation"
87101
+ },
87102
+ "description": "The isolations a computer can be made in now, `none` first. `container` is among them while Docker answers on the service's machine. It is what to offer, not a promise: Docker is asked for a second here and for ten when a container is made, so one that is slow can be missing here and still be made. If `isolation` itself is missing, a computer made without saying which is refused with 409."
86844
87103
  }
86845
87104
  }
86846
87105
  }
@@ -86868,7 +87127,8 @@ var OPERATIONS = [
86868
87127
  "type": "object",
86869
87128
  "required": [
86870
87129
  "version",
86871
- "isolation"
87130
+ "isolation",
87131
+ "isolationsAvailable"
86872
87132
  ],
86873
87133
  "properties": {
86874
87134
  "version": {
@@ -86880,7 +87140,19 @@ var OPERATIONS = [
86880
87140
  "none",
86881
87141
  "container"
86882
87142
  ],
86883
- "description": "How this service isolates its computers, chosen when it is started."
87143
+ "description": "The isolation of a computer made without saying which, chosen when the service is started."
87144
+ },
87145
+ "isolationsAvailable": {
87146
+ "type": "array",
87147
+ "items": {
87148
+ "type": "string",
87149
+ "enum": [
87150
+ "none",
87151
+ "container"
87152
+ ],
87153
+ "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
87154
+ },
87155
+ "description": "The isolations a computer can be made in now, `none` first. `container` is among them while Docker answers on the service's machine. It is what to offer, not a promise: Docker is asked for a second here and for ten when a container is made, so one that is slow can be missing here and still be made. If `isolation` itself is missing, a computer made without saying which is refused with 409."
86884
87156
  }
86885
87157
  }
86886
87158
  },
@@ -86908,7 +87180,8 @@ var OPERATIONS = [
86908
87180
  "running",
86909
87181
  "stopped",
86910
87182
  "failed"
86911
- ]
87183
+ ],
87184
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
86912
87185
  }
86913
87186
  },
86914
87187
  "required": []
@@ -86922,6 +87195,7 @@ var OPERATIONS = [
86922
87195
  "name",
86923
87196
  "state",
86924
87197
  "access",
87198
+ "isolation",
86925
87199
  "browser",
86926
87200
  "workspace",
86927
87201
  "startedAt",
@@ -86942,7 +87216,8 @@ var OPERATIONS = [
86942
87216
  "running",
86943
87217
  "stopped",
86944
87218
  "failed"
86945
- ]
87219
+ ],
87220
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
86946
87221
  },
86947
87222
  "access": {
86948
87223
  "type": "string",
@@ -86954,6 +87229,14 @@ var OPERATIONS = [
86954
87229
  ],
86955
87230
  "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
86956
87231
  },
87232
+ "isolation": {
87233
+ "type": "string",
87234
+ "enum": [
87235
+ "none",
87236
+ "container"
87237
+ ],
87238
+ "description": "The isolation it was made in. It is never run in another."
87239
+ },
86957
87240
  "browser": {
86958
87241
  "type": "boolean"
86959
87242
  },
@@ -87053,6 +87336,14 @@ var OPERATIONS = [
87053
87336
  "maxLength": 4096,
87054
87337
  "description": "The full path of a folder that exists, on the service's machine. With none, the service makes a folder, which goes when the computer is killed. A folder named here never does."
87055
87338
  },
87339
+ "isolation": {
87340
+ "type": "string",
87341
+ "enum": [
87342
+ "none",
87343
+ "container"
87344
+ ],
87345
+ "description": "How it is isolated, which it keeps for as long as it exists. Left out, it is the service's own, the `isolation` that `getInfo` answers."
87346
+ },
87056
87347
  "timeout": {
87057
87348
  "type": "integer",
87058
87349
  "minimum": 1,
@@ -87081,6 +87372,7 @@ var OPERATIONS = [
87081
87372
  "name",
87082
87373
  "state",
87083
87374
  "access",
87375
+ "isolation",
87084
87376
  "browser",
87085
87377
  "workspace",
87086
87378
  "startedAt",
@@ -87101,7 +87393,8 @@ var OPERATIONS = [
87101
87393
  "running",
87102
87394
  "stopped",
87103
87395
  "failed"
87104
- ]
87396
+ ],
87397
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
87105
87398
  },
87106
87399
  "access": {
87107
87400
  "type": "string",
@@ -87113,6 +87406,14 @@ var OPERATIONS = [
87113
87406
  ],
87114
87407
  "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87115
87408
  },
87409
+ "isolation": {
87410
+ "type": "string",
87411
+ "enum": [
87412
+ "none",
87413
+ "container"
87414
+ ],
87415
+ "description": "The isolation it was made in. It is never run in another."
87416
+ },
87116
87417
  "browser": {
87117
87418
  "type": "boolean"
87118
87419
  },
@@ -87221,6 +87522,7 @@ var OPERATIONS = [
87221
87522
  "name",
87222
87523
  "state",
87223
87524
  "access",
87525
+ "isolation",
87224
87526
  "browser",
87225
87527
  "workspace",
87226
87528
  "startedAt",
@@ -87241,7 +87543,8 @@ var OPERATIONS = [
87241
87543
  "running",
87242
87544
  "stopped",
87243
87545
  "failed"
87244
- ]
87546
+ ],
87547
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
87245
87548
  },
87246
87549
  "access": {
87247
87550
  "type": "string",
@@ -87253,6 +87556,14 @@ var OPERATIONS = [
87253
87556
  ],
87254
87557
  "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87255
87558
  },
87559
+ "isolation": {
87560
+ "type": "string",
87561
+ "enum": [
87562
+ "none",
87563
+ "container"
87564
+ ],
87565
+ "description": "The isolation it was made in. It is never run in another."
87566
+ },
87256
87567
  "browser": {
87257
87568
  "type": "boolean"
87258
87569
  },
@@ -87452,6 +87763,7 @@ var OPERATIONS = [
87452
87763
  "name",
87453
87764
  "state",
87454
87765
  "access",
87766
+ "isolation",
87455
87767
  "browser",
87456
87768
  "workspace",
87457
87769
  "startedAt",
@@ -87472,7 +87784,8 @@ var OPERATIONS = [
87472
87784
  "running",
87473
87785
  "stopped",
87474
87786
  "failed"
87475
- ]
87787
+ ],
87788
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
87476
87789
  },
87477
87790
  "access": {
87478
87791
  "type": "string",
@@ -87484,6 +87797,14 @@ var OPERATIONS = [
87484
87797
  ],
87485
87798
  "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87486
87799
  },
87800
+ "isolation": {
87801
+ "type": "string",
87802
+ "enum": [
87803
+ "none",
87804
+ "container"
87805
+ ],
87806
+ "description": "The isolation it was made in. It is never run in another."
87807
+ },
87487
87808
  "browser": {
87488
87809
  "type": "boolean"
87489
87810
  },
@@ -87576,6 +87897,7 @@ var OPERATIONS = [
87576
87897
  "name",
87577
87898
  "state",
87578
87899
  "access",
87900
+ "isolation",
87579
87901
  "browser",
87580
87902
  "workspace",
87581
87903
  "startedAt",
@@ -87596,7 +87918,8 @@ var OPERATIONS = [
87596
87918
  "running",
87597
87919
  "stopped",
87598
87920
  "failed"
87599
- ]
87921
+ ],
87922
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
87600
87923
  },
87601
87924
  "access": {
87602
87925
  "type": "string",
@@ -87608,6 +87931,14 @@ var OPERATIONS = [
87608
87931
  ],
87609
87932
  "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87610
87933
  },
87934
+ "isolation": {
87935
+ "type": "string",
87936
+ "enum": [
87937
+ "none",
87938
+ "container"
87939
+ ],
87940
+ "description": "The isolation it was made in. It is never run in another."
87941
+ },
87611
87942
  "browser": {
87612
87943
  "type": "boolean"
87613
87944
  },
@@ -87705,6 +88036,7 @@ var OPERATIONS = [
87705
88036
  "name",
87706
88037
  "state",
87707
88038
  "access",
88039
+ "isolation",
87708
88040
  "browser",
87709
88041
  "workspace",
87710
88042
  "startedAt",
@@ -87725,7 +88057,8 @@ var OPERATIONS = [
87725
88057
  "running",
87726
88058
  "stopped",
87727
88059
  "failed"
87728
- ]
88060
+ ],
88061
+ "description": "failed: it was to be running and isn't, because its browser or container ended by itself, or because it couldn't be brought back when the service started. Its logs say why. Stop it and start it again."
87729
88062
  },
87730
88063
  "access": {
87731
88064
  "type": "string",
@@ -87737,6 +88070,14 @@ var OPERATIONS = [
87737
88070
  ],
87738
88071
  "description": "How far it reaches into files and commands, each level containing the one before: `read` has about, read, list and search; `files` adds edit; `terminal` adds exec_command, write_stdin and list_sessions. Below terminal, the browser's code-running tool is hidden."
87739
88072
  },
88073
+ "isolation": {
88074
+ "type": "string",
88075
+ "enum": [
88076
+ "none",
88077
+ "container"
88078
+ ],
88079
+ "description": "The isolation it was made in. It is never run in another."
88080
+ },
87740
88081
  "browser": {
87741
88082
  "type": "boolean"
87742
88083
  },
@@ -88024,9 +88365,12 @@ async function serve(options) {
88024
88365
  checkExposure(host, options);
88025
88366
  const data = privateFolder(options.data);
88026
88367
  const control = digestOf(controlToken(data));
88368
+ const live = await running(data);
88369
+ if (live)
88370
+ throw new Error(`A service is already running with its data in ${data}, at ${live.url}.`);
88027
88371
  let base = "";
88028
- await options.runtime.prepare();
88029
- const computers = new Computers(data, () => base, options.runtime);
88372
+ await options.runtimes[options.isolation].prepare();
88373
+ const computers = new Computers(data, () => base, options.runtimes, options.isolation);
88030
88374
  const handlers = createHandlers(computers);
88031
88375
  const routes = OPERATIONS.map((operation) => ({
88032
88376
  operation,
@@ -88114,7 +88458,7 @@ async function serve(options) {
88114
88458
  });
88115
88459
  };
88116
88460
  const server = options.tls ? createTlsServer(
88117
- { cert: readFileSync5(options.tls.cert), key: readFileSync5(options.tls.key) },
88461
+ { cert: readFileSync6(options.tls.cert), key: readFileSync6(options.tls.key) },
88118
88462
  listener
88119
88463
  ) : createServer5(listener);
88120
88464
  const allowed = new Set(options.allowOrigins ?? []);
@@ -88146,11 +88490,12 @@ ${JSON.stringify({ code: status, message })}
88146
88490
  const bound = server.address();
88147
88491
  const scheme = options.tls ? "https" : "http";
88148
88492
  base = options.publicUrl?.replace(/\/+$/, "") ?? `${scheme}://${bound.family === "IPv6" ? `[${bound.address}]` : bound.address}:${bound.port}`;
88493
+ const record3 = recordOf(data);
88494
+ rmSync5(record3, { force: true });
88149
88495
  await computers.resume();
88150
- const record3 = join10(data, "server.json");
88151
88496
  writePrivate(
88152
88497
  record3,
88153
- JSON.stringify({ url: base, pid: process.pid, isolation: options.runtime.isolation })
88498
+ JSON.stringify({ url: base, pid: process.pid, isolation: options.isolation })
88154
88499
  );
88155
88500
  process.stdout.write(`AgentComputer on ${base}, data in ${data}
88156
88501
  `);
@@ -88162,7 +88507,7 @@ ${JSON.stringify({ code: status, message })}
88162
88507
  process.stderr.write(`${String(error62)}
88163
88508
  `);
88164
88509
  });
88165
- rmSync3(record3, { force: true });
88510
+ rmSync5(record3, { force: true });
88166
88511
  process.exit(0);
88167
88512
  };
88168
88513
  for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void stop());
@@ -88176,9 +88521,14 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88176
88521
  [--browser-name NAME] [--browser-path PATH]
88177
88522
  run the service; 127.0.0.1:7878 by default, and
88178
88523
  anywhere else only with TLS
88179
- info the service's version and isolation
88524
+ up [the options of serve] run the service in the background and return once it
88525
+ answers, printing where; one running is left as it is
88526
+ down stop the service, and its computers with it
88527
+ status where the service is running, if it is
88528
+ info the service's version, the isolation it gives a computer
88529
+ made without saying, and the ones it can make now
88180
88530
  create --access none|read|files|terminal [--browser] [--workspace DIR] [--name NAME]
88181
- [--timeout SECONDS] [--metadata KEY=VALUE]...
88531
+ [--isolation none|container] [--timeout SECONDS] [--metadata KEY=VALUE]...
88182
88532
  make a computer; prints its endpoints and token, once
88183
88533
  list [--state running|stopped|failed]
88184
88534
  the computers
@@ -88191,6 +88541,8 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88191
88541
  a one-time ticket to open one live, as a WebSocket
88192
88542
  kill <id> stop it and remove what the service made
88193
88543
 
88544
+ --version prints this program's version, with no service.
88545
+
88194
88546
  The data folder is --data, else $AGENTCOMPUTER_DATA, else $XDG_DATA_HOME/agentcomputer, else
88195
88547
  ~/.local/share/agentcomputer. Commands find the running service there, with its control token.
88196
88548
  `;
@@ -88203,20 +88555,18 @@ function dataFolder(given) {
88203
88555
  given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join11(xdg, "agentcomputer") : join11(homedir2(), ".local", "share", "agentcomputer"))
88204
88556
  );
88205
88557
  }
88558
+ var none = (data) => `No service is running with its data in ${data}. Start one: agentcomputer up`;
88206
88559
  function connectionTo(data) {
88207
- try {
88208
- const { url: url3 } = JSON.parse(readFileSync6(join11(data, "server.json"), "utf8"));
88209
- return { apiUrl: url3, apiKey: readFileSync6(join11(data, "control-token"), "utf8").trim() };
88210
- } catch {
88211
- throw new Error(
88212
- `No service is running with its data in ${data}. Start one: agentcomputer serve`
88213
- );
88214
- }
88560
+ const said = recorded(data);
88561
+ if (!said) throw new Error(none(data));
88562
+ return { apiUrl: said.url, apiKey: said.token };
88215
88563
  }
88216
88564
  async function main() {
88217
- const { values, positionals } = parseArgs({
88565
+ const { values, positionals, tokens } = parseArgs({
88218
88566
  allowPositionals: true,
88567
+ tokens: true,
88219
88568
  options: {
88569
+ version: { type: "boolean" },
88220
88570
  data: { type: "string" },
88221
88571
  listen: { type: "string" },
88222
88572
  isolation: { type: "string" },
@@ -88247,19 +88597,38 @@ async function main() {
88247
88597
  }
88248
88598
  });
88249
88599
  const [command, id, extra] = positionals;
88600
+ if (values.version) return void process.stdout.write(`${VERSION2}
88601
+ `);
88250
88602
  if (values.help || !command) return void process.stdout.write(USAGE);
88251
88603
  const data = dataFolder(values.data);
88252
88604
  const out = (value) => process.stdout.write(`${JSON.stringify(value, null, 2)}
88253
88605
  `);
88606
+ if (command === "status") {
88607
+ const found = await running(data);
88608
+ if (!found) throw new Error(none(data));
88609
+ return out(found);
88610
+ }
88611
+ if (command === "up") {
88612
+ const given = process.argv.slice(2);
88613
+ given.splice(tokens.find((token) => token.kind === "positional")?.index, 1);
88614
+ return out(await up(data, process.argv[1], given));
88615
+ }
88616
+ if (command === "down") return down(data);
88617
+ if ((command === "start" || command === "stop") && !id) {
88618
+ const service = command === "start" ? "up" : "down";
88619
+ throw new UsageError(
88620
+ `${command} takes a computer's id. For the service itself: agentcomputer ${service}`
88621
+ );
88622
+ }
88254
88623
  const idOf = () => {
88255
88624
  if (!id) throw new UsageError(`${command} takes a computer's id.`);
88256
88625
  return id;
88257
88626
  };
88258
88627
  const milliseconds = (seconds2) => Number(seconds2) * 1e3;
88628
+ const { isolation } = values;
88629
+ if (isolation !== void 0 && isolation !== "none" && isolation !== "container")
88630
+ throw new UsageError("--isolation is none or container.");
88259
88631
  if (command === "serve") {
88260
- const isolation = values.isolation ?? "none";
88261
- if (isolation !== "none" && isolation !== "container")
88262
- throw new UsageError("--isolation is none or container.");
88263
88632
  const { "tls-cert": cert, "tls-key": key } = values;
88264
88633
  if (Boolean(cert) !== Boolean(key))
88265
88634
  throw new UsageError("--tls-cert and --tls-key go together.");
@@ -88270,10 +88639,11 @@ async function main() {
88270
88639
  tlsProxy: values["tls-proxy"],
88271
88640
  publicUrl: values["public-url"],
88272
88641
  allowOrigins: values["allow-origin"],
88273
- runtime: isolation === "container" ? new ContainerRuntime(values.image ?? `${IMAGE}:${VERSION2}`) : new ProcessRuntime({
88274
- browser: values["browser-name"],
88275
- path: values["browser-path"]
88276
- })
88642
+ isolation: isolation ?? "none",
88643
+ runtimes: {
88644
+ none: new ProcessRuntime({ browser: values["browser-name"], path: values["browser-path"] }),
88645
+ container: new ContainerRuntime(values.image ?? `${IMAGE}:${VERSION2}`)
88646
+ }
88277
88647
  });
88278
88648
  }
88279
88649
  if (command === "browser-host")
@@ -88303,6 +88673,7 @@ async function main() {
88303
88673
  ...connection,
88304
88674
  access: values.access,
88305
88675
  ...values.browser ? { browser: true } : {},
88676
+ ...isolation ? { isolation } : {},
88306
88677
  ...values.workspace ? { workspace: resolve5(values.workspace) } : {},
88307
88678
  ...values.name ? { name: values.name } : {},
88308
88679
  ...values.timeout ? { timeoutMs: milliseconds(values.timeout) } : {},