@agentorg/computer 0.0.13 → 0.0.15

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";
@@ -4829,10 +4828,10 @@ function $constructor(name, initializer4, proto, params) {
4829
4828
  if (initialized) {
4830
4829
  const own3 = Object.getPrototypeOf(inst);
4831
4830
  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;
4831
+ let up2 = own3;
4832
+ while (up2 && up2 !== ctorProto)
4833
+ up2 = Object.getPrototypeOf(up2);
4834
+ const target = up2 ?? own3;
4836
4835
  if (!initialized.has(target)) {
4837
4836
  initialized.add(target);
4838
4837
  members(target, protoMembers);
@@ -18888,14 +18887,14 @@ var require_validate = /* @__PURE__ */ __commonJSMin(((exports) => {
18888
18887
  } else {
18889
18888
  const matches = RELATIVE_JSON_POINTER.exec($data);
18890
18889
  if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`);
18891
- const up = +matches[1];
18890
+ const up2 = +matches[1];
18892
18891
  jsonPointer = matches[2];
18893
18892
  if (jsonPointer === "#") {
18894
- if (up >= dataLevel) throw new Error(errorMsg("property/index", up));
18895
- return dataPathArr[dataLevel - up];
18893
+ if (up2 >= dataLevel) throw new Error(errorMsg("property/index", up2));
18894
+ return dataPathArr[dataLevel - up2];
18896
18895
  }
18897
- if (up > dataLevel) throw new Error(errorMsg("data", up));
18898
- data = dataNames[dataLevel - up];
18896
+ if (up2 > dataLevel) throw new Error(errorMsg("data", up2));
18897
+ data = dataNames[dataLevel - up2];
18899
18898
  if (!jsonPointer) return data;
18900
18899
  }
18901
18900
  let expr = data;
@@ -18905,8 +18904,8 @@ var require_validate = /* @__PURE__ */ __commonJSMin(((exports) => {
18905
18904
  expr = (0, codegen_1._)`${expr} && ${data}`;
18906
18905
  }
18907
18906
  return expr;
18908
- function errorMsg(pointerType, up) {
18909
- return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
18907
+ function errorMsg(pointerType, up2) {
18908
+ return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
18910
18909
  }
18911
18910
  }
18912
18911
  exports.getData = getData;
@@ -27958,9 +27957,13 @@ var StreamableHTTPClientTransport = class {
27958
27957
 
27959
27958
  // src/browser.ts
27960
27959
  var RUNS_CODE = "browser_run_code_unsafe";
27961
- var MAX_BODY = 8 * 1024 * 1024;
27960
+ var SCREENSHOT = "browser_take_screenshot";
27961
+ var MIB = 1024 * 1024;
27962
+ var MAX_BODY = 8 * MIB;
27963
+ var MAX_PICTURE = 3 * MIB;
27962
27964
  var START_MS = 3e4;
27963
27965
  var STOP_MS = 2e3;
27966
+ var ONE_REQUEST = "A body is one JSON-RPC request, as a JSON object: send each request in its own POST.";
27964
27967
  var FORWARDED = [
27965
27968
  "accept",
27966
27969
  "content-type",
@@ -27980,15 +27983,27 @@ function sendAnswer(upstream, response) {
27980
27983
  });
27981
27984
  }
27982
27985
  var self = fileURLToPath(import.meta.url);
27983
- function withoutHidden(text3, hidden) {
27984
- const clean = (json4) => {
27986
+ function changed(text3, change) {
27987
+ const one = (json4) => {
27985
27988
  const message = JSON.parse(json4);
27986
- if (message.result?.tools)
27987
- message.result.tools = message.result.tools.filter((tool) => !hidden.has(tool.name));
27989
+ change(message);
27988
27990
  return JSON.stringify(message);
27989
27991
  };
27990
- if (text3.trimStart().startsWith("{")) return clean(text3);
27991
- return text3.replace(/^data: (\{.*\})$/gm, (_, json4) => `data: ${clean(json4)}`);
27992
+ if (text3.trimStart().startsWith("{")) return one(text3);
27993
+ return text3.replace(/^data: (\{[^\n\r]*\})$/gm, (_, json4) => `data: ${one(json4)}`);
27994
+ }
27995
+ var withoutHidden = (hidden) => (message) => {
27996
+ if (message.result?.tools)
27997
+ message.result.tools = message.result.tools.filter((tool) => !hidden.has(tool.name));
27998
+ };
27999
+ function withoutLarge(message) {
28000
+ const sizes = (message.result?.content ?? []).map(
28001
+ (block) => block.type === "image" ? Buffer.byteLength(block.data ?? "", "base64") : 0
28002
+ );
28003
+ const size = Math.max(0, ...sizes);
28004
+ if (size <= MAX_PICTURE) return;
28005
+ 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.`;
28006
+ message.result = { isError: true, content: [{ type: "text", text: text3 }] };
27992
28007
  }
27993
28008
  async function end(child) {
27994
28009
  if (child.exitCode !== null || child.signalCode !== null) return;
@@ -28098,18 +28113,29 @@ var Browser = class {
28098
28113
  chunks.push(chunk);
28099
28114
  }
28100
28115
  const body = Buffer.concat(chunks);
28101
- let message = {};
28116
+ let message;
28102
28117
  try {
28103
- if (body.length > 0) message = JSON.parse(body.toString("utf8"));
28118
+ const read = body.length > 0 ? JSON.parse(body.toString("utf8")) : void 0;
28119
+ if (typeof read === "object" && read !== null && !Array.isArray(read)) message = read;
28104
28120
  } catch {
28105
28121
  }
28106
- const tool = message.method === "tools/call" ? String(message.params?.name) : void 0;
28122
+ if (body.length > 0 && !message) {
28123
+ response.writeHead(400, { "Content-Type": "application/json" });
28124
+ return void response.end(
28125
+ JSON.stringify({
28126
+ jsonrpc: "2.0",
28127
+ id: null,
28128
+ error: { code: -32600, message: ONE_REQUEST }
28129
+ })
28130
+ );
28131
+ }
28132
+ const tool = message?.method === "tools/call" ? String(message.params?.name) : void 0;
28107
28133
  if (tool && this.#hidden.has(tool)) {
28108
28134
  response.writeHead(200, { "Content-Type": "application/json" });
28109
28135
  return void response.end(
28110
28136
  JSON.stringify({
28111
28137
  jsonrpc: "2.0",
28112
- id: message.id ?? null,
28138
+ id: message?.id ?? null,
28113
28139
  error: { code: -32602, message: `Unknown tool: ${tool}` }
28114
28140
  })
28115
28141
  );
@@ -28134,16 +28160,18 @@ var Browser = class {
28134
28160
  }).then((answer) => answer.text()).catch(() => {
28135
28161
  });
28136
28162
  if (tool === "browser_file_upload") this.gate?.agentAnsweredChooser();
28163
+ if (message) headers["content-type"] = "application/json";
28137
28164
  const upstream = await fetch(`${origin}/mcp`, {
28138
28165
  method: request.method,
28139
28166
  headers,
28140
- ...body.length > 0 ? { body } : {}
28167
+ ...message ? { body: JSON.stringify(message) } : {}
28141
28168
  });
28142
- if (message.method === "tools/list" && this.#hidden.size > 0) {
28143
- const text3 = withoutHidden(await upstream.text(), this.#hidden);
28144
- return sendAnswer(new Response(text3, upstream), response);
28145
- }
28146
- sendAnswer(upstream, response);
28169
+ let change;
28170
+ if (message?.method === "tools/list" && this.#hidden.size > 0)
28171
+ change = withoutHidden(this.#hidden);
28172
+ else if (tool === SCREENSHOT) change = withoutLarge;
28173
+ if (!change) return sendAnswer(upstream, response);
28174
+ sendAnswer(new Response(changed(await upstream.text(), change), upstream), response);
28147
28175
  }
28148
28176
  async stop() {
28149
28177
  this.#stopping = true;
@@ -30609,10 +30637,10 @@ function $constructor2(name, initializer32, proto, params) {
30609
30637
  if (initialized) {
30610
30638
  const own22 = Object.getPrototypeOf(inst);
30611
30639
  const ctorProto = inst._zod.constr.prototype;
30612
- let up = own22;
30613
- while (up && up !== ctorProto)
30614
- up = Object.getPrototypeOf(up);
30615
- const target = up ?? own22;
30640
+ let up2 = own22;
30641
+ while (up2 && up2 !== ctorProto)
30642
+ up2 = Object.getPrototypeOf(up2);
30643
+ const target = up2 ?? own22;
30616
30644
  if (!initialized.has(target)) {
30617
30645
  initialized.add(target);
30618
30646
  members2(target, protoMembers);
@@ -48469,21 +48497,21 @@ function visit(schema, fnOrHandlers) {
48469
48497
  case "object": {
48470
48498
  const oldShape = def.shape;
48471
48499
  const keys = Object.keys(oldShape);
48472
- let changed2 = false;
48500
+ let changed22 = false;
48473
48501
  const newShape = {};
48474
48502
  for (const k of keys) {
48475
48503
  const mapped = run3(oldShape[k]);
48476
48504
  if (mapped !== oldShape[k])
48477
- changed2 = true;
48505
+ changed22 = true;
48478
48506
  newShape[k] = mapped;
48479
48507
  }
48480
48508
  let newCatchall = def.catchall;
48481
48509
  if (def.catchall) {
48482
48510
  newCatchall = run3(def.catchall);
48483
48511
  if (newCatchall !== def.catchall)
48484
- changed2 = true;
48512
+ changed22 = true;
48485
48513
  }
48486
- return changed2 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48514
+ return changed22 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48487
48515
  }
48488
48516
  case "array": {
48489
48517
  const mapped = run3(def.element);
@@ -48491,21 +48519,21 @@ function visit(schema, fnOrHandlers) {
48491
48519
  }
48492
48520
  case "tuple": {
48493
48521
  const oldItems = def.items;
48494
- let changed2 = false;
48522
+ let changed22 = false;
48495
48523
  const newItems = [];
48496
48524
  for (const item of oldItems) {
48497
48525
  const mapped = run3(item);
48498
48526
  if (mapped !== item)
48499
- changed2 = true;
48527
+ changed22 = true;
48500
48528
  newItems.push(mapped);
48501
48529
  }
48502
48530
  let newRest = def.rest;
48503
48531
  if (def.rest) {
48504
48532
  newRest = run3(def.rest);
48505
48533
  if (newRest !== def.rest)
48506
- changed2 = true;
48534
+ changed22 = true;
48507
48535
  }
48508
- return changed2 ? clone2(s, { ...def, items: newItems, rest: newRest }) : s;
48536
+ return changed22 ? clone2(s, { ...def, items: newItems, rest: newRest }) : s;
48509
48537
  }
48510
48538
  case "record":
48511
48539
  case "map": {
@@ -48519,15 +48547,15 @@ function visit(schema, fnOrHandlers) {
48519
48547
  }
48520
48548
  case "union": {
48521
48549
  const oldOptions = def.options;
48522
- let changed2 = false;
48550
+ let changed22 = false;
48523
48551
  const newOptions = [];
48524
48552
  for (const opt of oldOptions) {
48525
48553
  const mapped = run3(opt);
48526
48554
  if (mapped !== opt)
48527
- changed2 = true;
48555
+ changed22 = true;
48528
48556
  newOptions.push(mapped);
48529
48557
  }
48530
- return changed2 ? clone2(s, { ...def, options: newOptions }) : s;
48558
+ return changed22 ? clone2(s, { ...def, options: newOptions }) : s;
48531
48559
  }
48532
48560
  case "intersection": {
48533
48561
  const newLeft = run3(def.left);
@@ -56608,14 +56636,14 @@ var require_validate2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56608
56636
  } else {
56609
56637
  const matches = RELATIVE_JSON_POINTER.exec($data);
56610
56638
  if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`);
56611
- const up = +matches[1];
56639
+ const up2 = +matches[1];
56612
56640
  jsonPointer = matches[2];
56613
56641
  if (jsonPointer === "#") {
56614
- if (up >= dataLevel) throw new Error(errorMsg("property/index", up));
56615
- return dataPathArr[dataLevel - up];
56642
+ if (up2 >= dataLevel) throw new Error(errorMsg("property/index", up2));
56643
+ return dataPathArr[dataLevel - up2];
56616
56644
  }
56617
- if (up > dataLevel) throw new Error(errorMsg("data", up));
56618
- data = dataNames[dataLevel - up];
56645
+ if (up2 > dataLevel) throw new Error(errorMsg("data", up2));
56646
+ data = dataNames[dataLevel - up2];
56619
56647
  if (!jsonPointer) return data;
56620
56648
  }
56621
56649
  let expr = data;
@@ -56625,8 +56653,8 @@ var require_validate2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56625
56653
  expr = (0, codegen_1._)`${expr} && ${data}`;
56626
56654
  }
56627
56655
  return expr;
56628
- function errorMsg(pointerType, up) {
56629
- return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
56656
+ function errorMsg(pointerType, up2) {
56657
+ return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
56630
56658
  }
56631
56659
  }
56632
56660
  exports.getData = getData;
@@ -65891,6 +65919,7 @@ var Sessions = class {
65891
65919
  this.#room();
65892
65920
  const started = Date.now();
65893
65921
  const session = this.#start(input2.cmd, input2.workdir ?? this.#root);
65922
+ if (input2.group) session.group = input2.group;
65894
65923
  const wait = clamp(input2.yield_time_ms ?? DEFAULT_YIELD_MS, MIN_YIELD_MS, MAX_YIELD_MS);
65895
65924
  return this.#after(session, wait, started, input2.max_output_tokens);
65896
65925
  }
@@ -65975,26 +66004,37 @@ var Sessions = class {
65975
66004
  if (!session) throw new ExecRefused(`No terminal ${id.slice(0, 64)} is here.`);
65976
66005
  await this.#end([session], "SIGINT");
65977
66006
  }
66007
+ /**
66008
+ * End the terminals still running of one group, as `stop` ends one, and say how many. A shell a
66009
+ * person opened is in no group.
66010
+ */
66011
+ async stopGroup(group) {
66012
+ const running2 = [...this.#sessions.values()].filter(
66013
+ (session) => session.group === group && session.exitCode === void 0
66014
+ );
66015
+ await this.#end(running2, "SIGINT");
66016
+ return running2.length;
66017
+ }
65978
66018
  /** Stop every terminal: a polite signal, then a certain one. */
65979
66019
  async stopAll() {
65980
66020
  await this.#end([...this.#sessions.values()], "SIGTERM");
65981
66021
  }
65982
66022
  async #end(sessions, polite) {
65983
- const running = sessions.filter((s) => s.exitCode === void 0);
65984
- for (const session of running) session.signal(session.tty ? "SIGHUP" : polite);
66023
+ const running2 = sessions.filter((s) => s.exitCode === void 0);
66024
+ for (const session of running2) session.signal(session.tty ? "SIGHUP" : polite);
65985
66025
  let timer;
65986
66026
  await Promise.race([
65987
- Promise.all(running.map((s) => s.ended)),
66027
+ Promise.all(running2.map((s) => s.ended)),
65988
66028
  new Promise((done) => {
65989
66029
  timer = setTimeout(done, KILL_AFTER_MS);
65990
66030
  })
65991
66031
  ]);
65992
66032
  clearTimeout(timer);
65993
- for (const session of running) if (session.exitCode === void 0) session.signal("SIGKILL");
66033
+ for (const session of running2) if (session.exitCode === void 0) session.signal("SIGKILL");
65994
66034
  }
65995
66035
  #room() {
65996
- const running = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
65997
- if (running.length >= MAX_RUNNING)
66036
+ const running2 = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
66037
+ if (running2.length >= MAX_RUNNING)
65998
66038
  throw new ExecRefused(`${MAX_RUNNING} commands are running. Wait for one, or stop one.`);
65999
66039
  }
66000
66040
  #running(id) {
@@ -66366,7 +66406,7 @@ function searched(job) {
66366
66406
  const more = out.length > limit;
66367
66407
  return (more ? [...out.slice(0, limit), `\u2026 more than ${limit} matches`] : out).join("\n");
66368
66408
  }
66369
- function changed(job) {
66409
+ function changed2(job) {
66370
66410
  const changes = [];
66371
66411
  let more = false;
66372
66412
  const newest = () => {
@@ -66419,7 +66459,7 @@ function changedSince(root, since) {
66419
66459
  }
66420
66460
  if (!isMainThread && workerData?.agentexecSearch) {
66421
66461
  const job = workerData;
66422
- parentPort?.postMessage("since" in job ? changed(job) : searched(job));
66462
+ parentPort?.postMessage("since" in job ? changed2(job) : searched(job));
66423
66463
  }
66424
66464
  var MAX_READ_BYTES = 1024 * 1024;
66425
66465
  function createHostFiles(root, level) {
@@ -66572,6 +66612,14 @@ var Answered = {
66572
66612
  session_id: external_exports.string().nullable().describe("For write_stdin, while it is still running."),
66573
66613
  text: external_exports.string().describe("What it wrote since the last call, cut in the middle if long.")
66574
66614
  };
66615
+ var GROUP = "agentexec/group";
66616
+ var STOP_GROUP = "agentexec/group/stop";
66617
+ var Group = external_exports.string().min(1).max(200);
66618
+ var groupOf = (meta3) => {
66619
+ const word = Group.optional().safeParse(meta3?.[GROUP]);
66620
+ if (!word.success) throw new ExecRefused(`${GROUP} in _meta is a string of 1 to 200 characters.`);
66621
+ return word.data;
66622
+ };
66575
66623
  var YieldTime = external_exports.number().int().positive().optional();
66576
66624
  var MaxTokens = external_exports.number().int().positive().optional();
66577
66625
  var READ_ONLY = { readOnlyHint: true };
@@ -66600,6 +66648,11 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66600
66648
  }
66601
66649
  );
66602
66650
  if (level === "terminal") {
66651
+ server.server.setRequestHandler(
66652
+ STOP_GROUP,
66653
+ { params: external_exports.object({ group: Group }), result: external_exports.object({ stopped: external_exports.number() }) },
66654
+ async ({ group }) => ({ stopped: await sessions.stopGroup(group) })
66655
+ );
66603
66656
  server.registerTool(
66604
66657
  "exec_command",
66605
66658
  {
@@ -66613,7 +66666,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66613
66666
  outputSchema: Answered,
66614
66667
  annotations: { destructiveHint: true }
66615
66668
  },
66616
- (input2) => answering(() => sessions.exec(input2))
66669
+ (input2, ctx) => answering(async () => sessions.exec({ ...input2, group: groupOf(ctx.mcpReq._meta) }))
66617
66670
  );
66618
66671
  server.registerTool(
66619
66672
  "write_stdin",
@@ -66716,7 +66769,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66716
66769
  );
66717
66770
  return server;
66718
66771
  }
66719
- var VERSION = "0.0.7";
66772
+ var VERSION = "0.0.8";
66720
66773
  function createAgentExec(options) {
66721
66774
  const root = realpathSync(resolve3(options.workspace));
66722
66775
  const level = options.level ?? "read";
@@ -67021,8 +67074,8 @@ var BrowserLive = class {
67021
67074
  }
67022
67075
  /** The cast of a page, started if no socket was shown it yet. */
67023
67076
  #cast(page) {
67024
- const running = this.#casts.get(page);
67025
- if (running) return running;
67077
+ const running2 = this.#casts.get(page);
67078
+ if (running2) return running2;
67026
67079
  const onFrame = (frame) => {
67027
67080
  const watching = this.#shownTo(page);
67028
67081
  if (frame.width !== cast.size.width || frame.height !== cast.size.height) {
@@ -67735,7 +67788,6 @@ var loadPty = () => {
67735
67788
  return pty;
67736
67789
  };
67737
67790
  var ProcessRuntime = class {
67738
- isolation = "none";
67739
67791
  #config;
67740
67792
  #contained;
67741
67793
  /** `contained` is for inside a container, where the computer and its load are its own. */
@@ -67743,6 +67795,9 @@ var ProcessRuntime = class {
67743
67795
  this.#config = config3;
67744
67796
  this.#contained = contained;
67745
67797
  }
67798
+ async available() {
67799
+ return true;
67800
+ }
67746
67801
  async prepare() {
67747
67802
  }
67748
67803
  async stopLeftover() {
@@ -67837,7 +67892,7 @@ async function serveInside(options) {
67837
67892
  if (!token) throw new Error(`${INNER_TOKEN_VARIABLE} is not set.`);
67838
67893
  delete process.env[INNER_TOKEN_VARIABLE];
67839
67894
  const digest = digestOf(token);
67840
- const running = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67895
+ const running2 = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67841
67896
  {
67842
67897
  id: "inside",
67843
67898
  ...options,
@@ -67864,13 +67919,13 @@ async function serveInside(options) {
67864
67919
  if (!given || !sameToken(given, digest)) return void response.writeHead(401).end();
67865
67920
  const { pathname } = new URL(request.url ?? "/", "http://localhost");
67866
67921
  if (pathname === "/ready") {
67867
- const { browserTools, surfaces } = await running;
67922
+ const { browserTools, surfaces } = await running2;
67868
67923
  response.writeHead(200, { "Content-Type": "application/json" });
67869
67924
  return void response.end(JSON.stringify({ browserTools, surfaces }));
67870
67925
  }
67871
67926
  const endpoint = /^\/mcp\/(computer|browser)$/.exec(pathname)?.[1];
67872
67927
  if (!endpoint) return void response.writeHead(404).end();
67873
- return (await running).handle(endpoint, request, response);
67928
+ return (await running2).handle(endpoint, request, response);
67874
67929
  };
67875
67930
  const server = createServer3((request, response) => {
67876
67931
  route(request, response).catch((error62) => {
@@ -67886,14 +67941,14 @@ async function serveInside(options) {
67886
67941
  const mode = url3.searchParams.get("mode");
67887
67942
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
67888
67943
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
67889
- running.then((computer) => computer.openLive(surface, mode, request, socket, head)).catch(() => socket.destroy());
67944
+ running2.then((computer) => computer.openLive(surface, mode, request, socket, head)).catch(() => socket.destroy());
67890
67945
  });
67891
67946
  server.listen(INNER_PORT, "0.0.0.0");
67892
67947
  for (const signal of ["SIGINT", "SIGTERM"])
67893
67948
  process.once(signal, () => {
67894
- void running.then((computer) => computer.stop()).finally(() => process.exit(0));
67949
+ void running2.then((computer) => computer.stop()).finally(() => process.exit(0));
67895
67950
  });
67896
- await running;
67951
+ await running2;
67897
67952
  }
67898
67953
 
67899
67954
  // src/container.ts
@@ -67928,17 +67983,30 @@ var UPGRADE_HEADERS = [
67928
67983
  var nameOf = (id) => `agentcomputer-${id}`;
67929
67984
  var sleep5 = (ms) => new Promise((done) => setTimeout(done, ms));
67930
67985
  var ContainerRuntime = class {
67931
- isolation = "container";
67932
67986
  #image;
67933
67987
  constructor(image) {
67934
67988
  this.#image = image;
67935
67989
  }
67936
- async prepare() {
67990
+ /** Whether Docker answers, asked for no longer than this. */
67991
+ async #answers(ms) {
67937
67992
  try {
67938
- await docker(["version", "--format", "{{.Server.Version}}"]);
67939
- } catch (error62) {
67940
- throw new Error(`Container isolation needs Docker, which didn't answer: ${String(error62)}`);
67993
+ await run2("docker", ["version", "--format", "{{.Server.Version}}"], { timeout: ms });
67994
+ return true;
67995
+ } catch {
67996
+ return false;
67941
67997
  }
67998
+ }
67999
+ // Asked each time the service says what it can make, so it doesn't wait on a Docker that hangs.
68000
+ available() {
68001
+ return this.#answers(1e3);
68002
+ }
68003
+ /** Docker, or the refusal of what can't be done without it. */
68004
+ async #docker() {
68005
+ if (!await this.#answers(1e4))
68006
+ throw new ApiError(409, "A container needs Docker, which isn't answering on this machine.");
68007
+ }
68008
+ async prepare() {
68009
+ await this.#docker();
67942
68010
  try {
67943
68011
  await docker(["image", "inspect", "--format", "{{.Id}}", this.#image]);
67944
68012
  } catch {
@@ -68079,11 +68147,14 @@ var ContainerRuntime = class {
68079
68147
  }
68080
68148
  throw new Error("Its container didn't start. Its logs say why.");
68081
68149
  }
68150
+ // Neither is said to be done where Docker doesn't answer: the container would be left as it is.
68082
68151
  async stopLeftover(id) {
68152
+ await this.#docker();
68083
68153
  await docker(["stop", "--time", STOP_SECONDS, nameOf(id)]).catch(() => {
68084
68154
  });
68085
68155
  }
68086
68156
  async remove(id) {
68157
+ await this.#docker();
68087
68158
  await docker(["rm", "--force", nameOf(id)]).catch(() => {
68088
68159
  });
68089
68160
  }
@@ -68132,10 +68203,14 @@ async function forward(url3, authorization, request, response) {
68132
68203
  }
68133
68204
 
68134
68205
  // src/handlers.ts
68135
- var VERSION2 = "0.0.13";
68206
+ var VERSION2 = "0.0.15";
68136
68207
  function createHandlers(computers) {
68137
68208
  return {
68138
- getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
68209
+ getInfo: async () => ({
68210
+ version: VERSION2,
68211
+ isolation: computers.isolation,
68212
+ isolationsAvailable: await computers.available()
68213
+ }),
68139
68214
  listComputers: async ({ state }) => computers.list(state),
68140
68215
  createComputer: (input2) => computers.create(input2),
68141
68216
  getComputer: async ({ computerID }) => computers.get(computerID),
@@ -68154,6 +68229,81 @@ function createHandlers(computers) {
68154
68229
  };
68155
68230
  }
68156
68231
 
68232
+ // src/running.ts
68233
+ import { spawn as spawn4 } from "node:child_process";
68234
+ import { closeSync, openSync, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
68235
+ import { join as join9 } from "node:path";
68236
+ var MAX_LOG = 1024 * 1024;
68237
+ var STOP_MS2 = 3e4;
68238
+ var sleep6 = (ms) => new Promise((done) => setTimeout(done, ms));
68239
+ var recordOf = (data) => join9(data, "server.json");
68240
+ function recorded(data) {
68241
+ try {
68242
+ const { url: url3, pid } = JSON.parse(readFileSync4(recordOf(data), "utf8"));
68243
+ return { url: url3, pid, token: readFileSync4(join9(data, "control-token"), "utf8").trim() };
68244
+ } catch {
68245
+ return void 0;
68246
+ }
68247
+ }
68248
+ async function running(data, said = recorded(data)) {
68249
+ if (!said) return void 0;
68250
+ try {
68251
+ const answer = await fetch(`${said.url}/v1/info`, {
68252
+ headers: { Authorization: `Bearer ${said.token}` },
68253
+ signal: AbortSignal.timeout(3e3)
68254
+ });
68255
+ if (!answer.ok) return void 0;
68256
+ const { version: version3, isolation } = await answer.json();
68257
+ return { url: said.url, pid: said.pid, version: version3, isolation };
68258
+ } catch {
68259
+ return void 0;
68260
+ }
68261
+ }
68262
+ async function up(data, program, args) {
68263
+ const already = await running(data);
68264
+ if (already) return already;
68265
+ const log = join9(privateFolder(data), "service.log");
68266
+ try {
68267
+ const before = readFileSync4(log);
68268
+ if (before.length > MAX_LOG) writeFileSync2(log, before.subarray(before.length - MAX_LOG));
68269
+ } catch {
68270
+ }
68271
+ const from = statSync3(log, { throwIfNoEntry: false })?.size ?? 0;
68272
+ const out = openSync(log, "a", 384);
68273
+ const child = spawn4(process.execPath, [program, "serve", ...args], {
68274
+ detached: true,
68275
+ stdio: ["ignore", out, out]
68276
+ });
68277
+ closeSync(out);
68278
+ child.unref();
68279
+ let ended = false;
68280
+ child.once("exit", () => {
68281
+ ended = true;
68282
+ });
68283
+ for (; ; await sleep6(100)) {
68284
+ const said = recorded(data);
68285
+ const up2 = await running(data, said);
68286
+ const own3 = said?.pid === child.pid;
68287
+ if (up2 && (own3 || ended)) return up2;
68288
+ if (!ended && !own3) continue;
68289
+ if (!ended) {
68290
+ child.kill();
68291
+ throw new Error(`The service doesn't answer at ${said?.url} from here, so it was stopped.`);
68292
+ }
68293
+ const wrote = readFileSync4(log).subarray(from).toString().trim();
68294
+ throw new Error(`The service didn't start.${wrote ? ` It said:
68295
+ ${wrote}` : ""}`);
68296
+ }
68297
+ }
68298
+ async function down(data) {
68299
+ const found = await running(data);
68300
+ if (!found) return;
68301
+ process.kill(found.pid, "SIGTERM");
68302
+ for (const deadline = Date.now() + STOP_MS2; Date.now() < deadline; await sleep6(100))
68303
+ if (!await running(data)) return;
68304
+ throw new Error(`The service at ${found.url} didn't stop.`);
68305
+ }
68306
+
68157
68307
  // node_modules/openapi-fetch/dist/index.mjs
68158
68308
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
68159
68309
  var supportsRequestInitExt = () => {
@@ -68839,13 +68989,14 @@ var Computer = class _Computer {
68839
68989
  this.files = new Files(this.#computer);
68840
68990
  }
68841
68991
  static async create(opts) {
68842
- const { access, browser, workspace, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
68992
+ const { access, browser, isolation, workspace, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
68843
68993
  const connection = { ...apiKey ? { apiKey } : {}, ...apiUrl ? { apiUrl } : {} };
68844
68994
  const created = answered(
68845
68995
  await apiOf(connection).POST("/v1/computers", {
68846
68996
  body: {
68847
68997
  access,
68848
68998
  browser: browser ?? false,
68999
+ ...isolation === void 0 ? {} : { isolation },
68849
69000
  ...workspace === void 0 ? {} : { workspace },
68850
69001
  ...name === void 0 ? {} : { name },
68851
69002
  ...timeoutMs === void 0 ? {} : { timeout: seconds(timeoutMs) },
@@ -68950,13 +69101,12 @@ var Computer = class _Computer {
68950
69101
  };
68951
69102
 
68952
69103
  // src/service.ts
68953
- import { readFileSync as readFileSync5, rmSync as rmSync3 } from "node:fs";
69104
+ import { readFileSync as readFileSync6, rmSync as rmSync3 } from "node:fs";
68954
69105
  import {
68955
69106
  createServer as createServer5,
68956
69107
  STATUS_CODES as STATUS_CODES3
68957
69108
  } from "node:http";
68958
69109
  import { createServer as createTlsServer } from "node:https";
68959
- import { join as join10 } from "node:path";
68960
69110
 
68961
69111
  // node_modules/@modelcontextprotocol/server/dist/chunk-vYduTEsO.mjs
68962
69112
  var __create4 = Object.create;
@@ -72417,7 +72567,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS3 = 250;
72417
72567
  function inputRequiredRoundsExceededMessage3(method, maxRounds) {
72418
72568
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
72419
72569
  }
72420
- function sleep6(ms, signal) {
72570
+ function sleep7(ms, signal) {
72421
72571
  return new Promise((resolve6, reject) => {
72422
72572
  if (signal?.aborted) {
72423
72573
  reject(signal.reason instanceof SdkError3 ? signal.reason : new SdkError3(SdkErrorCode3.RequestTimeout, String(signal.reason)));
@@ -76048,14 +76198,14 @@ var require_validate3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76048
76198
  } else {
76049
76199
  const matches = RELATIVE_JSON_POINTER.exec($data);
76050
76200
  if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`);
76051
- const up = +matches[1];
76201
+ const up2 = +matches[1];
76052
76202
  jsonPointer = matches[2];
76053
76203
  if (jsonPointer === "#") {
76054
- if (up >= dataLevel) throw new Error(errorMsg("property/index", up));
76055
- return dataPathArr[dataLevel - up];
76204
+ if (up2 >= dataLevel) throw new Error(errorMsg("property/index", up2));
76205
+ return dataPathArr[dataLevel - up2];
76056
76206
  }
76057
- if (up > dataLevel) throw new Error(errorMsg("data", up));
76058
- data = dataNames[dataLevel - up];
76207
+ if (up2 > dataLevel) throw new Error(errorMsg("data", up2));
76208
+ data = dataNames[dataLevel - up2];
76059
76209
  if (!jsonPointer) return data;
76060
76210
  }
76061
76211
  let expr = data;
@@ -76065,8 +76215,8 @@ var require_validate3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76065
76215
  expr = (0, codegen_1._)`${expr} && ${data}`;
76066
76216
  }
76067
76217
  return expr;
76068
- function errorMsg(pointerType, up) {
76069
- return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
76218
+ function errorMsg(pointerType, up2) {
76219
+ return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
76070
76220
  }
76071
76221
  }
76072
76222
  exports.getData = getData;
@@ -81414,7 +81564,7 @@ var LegacyInputRequiredShim2 = class {
81414
81564
  } finally {
81415
81565
  roundAbort.dispose();
81416
81566
  }
81417
- } else await sleep6(REQUEST_STATE_ONLY_LEG_PACING_MS3, outerSignal);
81567
+ } else await sleep7(REQUEST_STATE_ONLY_LEG_PACING_MS3, outerSignal);
81418
81568
  let ctxNext = {
81419
81569
  ...ctx,
81420
81570
  mcpReq: {
@@ -84771,8 +84921,8 @@ var NodeStreamableHTTPServerTransport2 = class {
84771
84921
 
84772
84922
  // src/computers.ts
84773
84923
  import { randomBytes as randomBytes5 } from "node:crypto";
84774
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
84775
- import { isAbsolute, join as join9, resolve as resolve4 } from "node:path";
84924
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
84925
+ import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
84776
84926
  var READ = ["about", "read", "list", "search"];
84777
84927
  var FILES = [...READ, "edit"];
84778
84928
  var TOOLS2 = {
@@ -84780,29 +84930,37 @@ var TOOLS2 = {
84780
84930
  files: FILES,
84781
84931
  terminal: [...FILES, "exec_command", "write_stdin", "list_sessions"]
84782
84932
  };
84783
- var MAX_LOG = 256 * 1024;
84933
+ var MAX_LOG2 = 256 * 1024;
84784
84934
  var MAX_TIMER_S = 2e6;
84785
84935
  var TICKET_MS = 6e4;
84786
84936
  var expiry = (seconds2) => new Date(Date.now() + seconds2 * 1e3).toISOString();
84937
+ var messageOf = (error62) => error62 instanceof Error ? error62.message : String(error62);
84938
+ var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf(error62));
84787
84939
  var Computers = class {
84788
84940
  #data;
84789
84941
  #base;
84790
- #runtime;
84942
+ #runtimes;
84943
+ /** The isolation of a computer made without saying which. */
84944
+ isolation;
84791
84945
  #stored = /* @__PURE__ */ new Map();
84792
84946
  #live = /* @__PURE__ */ new Map();
84793
84947
  #tickets = /* @__PURE__ */ new Map();
84794
84948
  #file;
84795
- constructor(data, base, runtime) {
84949
+ constructor(data, base, runtimes, isolation) {
84796
84950
  this.#data = data;
84797
84951
  this.#base = base;
84798
- this.#runtime = runtime;
84799
- this.#file = join9(data, "computers.json");
84952
+ this.#runtimes = runtimes;
84953
+ this.isolation = isolation;
84954
+ this.#file = join10(data, "computers.json");
84800
84955
  if (existsSync2(this.#file))
84801
- for (const entry of JSON.parse(readFileSync4(this.#file, "utf8")))
84802
- this.#stored.set(entry.id, entry);
84956
+ for (const entry of JSON.parse(readFileSync5(this.#file, "utf8")))
84957
+ this.#stored.set(entry.id, { ...entry, isolation: entry.isolation ?? isolation });
84803
84958
  }
84804
- get isolation() {
84805
- return this.#runtime.isolation;
84959
+ /** The isolations a computer can be made in now. */
84960
+ async available() {
84961
+ const all = Object.keys(this.#runtimes);
84962
+ const can = await Promise.all(all.map((one) => this.#runtimes[one].available()));
84963
+ return all.filter((_, index) => can[index]);
84806
84964
  }
84807
84965
  /** Bring back what was running when the service last stopped. */
84808
84966
  async resume() {
@@ -84810,12 +84968,17 @@ var Computers = class {
84810
84968
  if (stored.state !== "running") continue;
84811
84969
  if (stored.endAt && Date.parse(stored.endAt) <= Date.now()) {
84812
84970
  stored.state = "stopped";
84813
- await this.#runtime.stopLeftover(stored.id);
84971
+ await this.#runtimes[stored.isolation].stopLeftover(stored.id).catch(
84972
+ (error62) => this.#log(stored.id, `What it left running wasn't stopped: ${messageOf(error62)}`)
84973
+ );
84814
84974
  continue;
84815
84975
  }
84816
- await this.#start(stored).catch(
84817
- (error62) => this.#log(stored.id, `It didn't start again: ${String(error62)}`)
84818
- );
84976
+ await this.#start(stored).catch((error62) => {
84977
+ const live = this.#live.get(stored.id);
84978
+ if (live) live.failed = true;
84979
+ this.#arm(stored);
84980
+ this.#log(stored.id, `It didn't start again: ${messageOf(error62)}`);
84981
+ });
84819
84982
  }
84820
84983
  this.#save();
84821
84984
  }
@@ -84828,7 +84991,7 @@ var Computers = class {
84828
84991
  if (input2.workspace !== void 0) {
84829
84992
  let folder = false;
84830
84993
  try {
84831
- folder = isAbsolute(input2.workspace) && statSync3(input2.workspace).isDirectory();
84994
+ folder = isAbsolute(input2.workspace) && statSync4(input2.workspace).isDirectory();
84832
84995
  } catch {
84833
84996
  }
84834
84997
  if (!folder) throw new ApiError(400, `There's no folder at ${input2.workspace}.`);
@@ -84841,6 +85004,7 @@ var Computers = class {
84841
85004
  id,
84842
85005
  name: name ?? id,
84843
85006
  access,
85007
+ isolation: input2.isolation ?? this.isolation,
84844
85008
  browser,
84845
85009
  ...workspace ? { workspace } : {},
84846
85010
  metadata: metadata ?? {},
@@ -84851,7 +85015,7 @@ var Computers = class {
84851
85015
  ...timeout === void 0 ? {} : { endAt: expiry(timeout) }
84852
85016
  };
84853
85017
  if (!workspace && access !== "none") {
84854
- stored.workspace = join9(this.#folder(id), "workspace");
85018
+ stored.workspace = join10(this.#folder(id), "workspace");
84855
85019
  mkdirSync2(stored.workspace, { recursive: true, mode: 448 });
84856
85020
  }
84857
85021
  this.#stored.set(id, stored);
@@ -84860,9 +85024,10 @@ var Computers = class {
84860
85024
  } catch (error62) {
84861
85025
  await this.#halt(id);
84862
85026
  this.#stored.delete(id);
84863
- await this.#runtime.remove(id);
85027
+ await this.#runtimes[stored.isolation].remove(id).catch(() => {
85028
+ });
84864
85029
  rmSync2(this.#folder(id), { recursive: true, force: true });
84865
- throw new ApiError(500, error62 instanceof Error ? error62.message : String(error62));
85030
+ throw refusalOf(error62);
84866
85031
  }
84867
85032
  this.#save();
84868
85033
  return { ...this.#describe(stored), accessToken: token };
@@ -84917,16 +85082,22 @@ var Computers = class {
84917
85082
  await this.#halt(id);
84918
85083
  stored.state = "stopped";
84919
85084
  this.#save();
84920
- throw new ApiError(500, error62 instanceof Error ? error62.message : String(error62));
85085
+ throw refusalOf(error62);
84921
85086
  }
84922
85087
  this.#save();
84923
85088
  return this.#describe(stored);
84924
85089
  }
84925
85090
  /** Stop everything and remove what the service made. A folder someone named stays. */
84926
85091
  async kill(id) {
84927
- this.#found(id);
85092
+ const stored = this.#found(id);
84928
85093
  await this.#halt(id);
84929
- await this.#runtime.remove(id);
85094
+ try {
85095
+ await this.#runtimes[stored.isolation].remove(id);
85096
+ } catch (error62) {
85097
+ stored.state = "stopped";
85098
+ this.#save();
85099
+ throw refusalOf(error62);
85100
+ }
84930
85101
  this.#stored.delete(id);
84931
85102
  this.#save();
84932
85103
  rmSync2(this.#folder(id), { recursive: true, force: true });
@@ -84935,7 +85106,7 @@ var Computers = class {
84935
85106
  this.#found(id);
84936
85107
  let text3 = "";
84937
85108
  try {
84938
- text3 = readFileSync4(this.#logFile(id), "utf8");
85109
+ text3 = readFileSync5(this.#logFile(id), "utf8");
84939
85110
  } catch {
84940
85111
  }
84941
85112
  return text3.split("\n").filter(Boolean).map((entry) => ({
@@ -84952,17 +85123,17 @@ var Computers = class {
84952
85123
  throw new ApiError(404, "This computer has no files or terminal.");
84953
85124
  if (endpoint === "browser" && !stored.browser)
84954
85125
  throw new ApiError(404, "This computer has no browser.");
84955
- const running = this.#live.get(id)?.running;
84956
- if (!running) throw new ApiError(409, "It has stopped.");
85126
+ const running2 = this.#live.get(id)?.running;
85127
+ if (!running2) throw new ApiError(409, "It has stopped.");
84957
85128
  this.#log(id, `${request.method} mcp/${endpoint}`);
84958
- return running.handle(endpoint, request, response);
85129
+ return running2.handle(endpoint, request, response);
84959
85130
  }
84960
85131
  /** A one-time ticket to open one of its surfaces live. */
84961
85132
  liveSession(id, surface, mode) {
84962
85133
  this.#found(id);
84963
- const running = this.#live.get(id)?.running;
84964
- if (!running) throw new ApiError(409, "It has stopped.");
84965
- const info = running.surfaces.find((one) => one.surface === surface);
85134
+ const running2 = this.#live.get(id)?.running;
85135
+ if (!running2) throw new ApiError(409, "It has stopped.");
85136
+ const info = running2.surfaces.find((one) => one.surface === surface);
84966
85137
  if (!info) throw new ApiError(409, `This computer has no ${surface} to open.`);
84967
85138
  if (info.unavailable) throw new ApiError(409, info.unavailable);
84968
85139
  if (mode === "operate" && !info.operate)
@@ -84987,10 +85158,10 @@ var Computers = class {
84987
85158
  this.#tickets.delete(ticket);
84988
85159
  if (!found || found.expiresAt <= Date.now())
84989
85160
  throw new ApiError(404, "There's no such live session: it was used, or it expired.");
84990
- const running = this.#live.get(found.computerID)?.running;
84991
- if (!running) throw new ApiError(409, "It has stopped.");
85161
+ const running2 = this.#live.get(found.computerID)?.running;
85162
+ if (!running2) throw new ApiError(409, "It has stopped.");
84992
85163
  this.#log(found.computerID, `live ${found.surface} ${found.mode}`);
84993
- await running.openLive(found.surface, found.mode, request, socket, head);
85164
+ await running2.openLive(found.surface, found.mode, request, socket, head);
84994
85165
  }
84995
85166
  /** Stop every computer, leaving their records to resume at the next start. */
84996
85167
  async close() {
@@ -85002,17 +85173,17 @@ var Computers = class {
85002
85173
  return stored;
85003
85174
  }
85004
85175
  #folder(id) {
85005
- return privateFolder(join9(this.#data, "computers", id));
85176
+ return privateFolder(join10(this.#data, "computers", id));
85006
85177
  }
85007
85178
  #logFile(id) {
85008
- return join9(this.#folder(id), "log");
85179
+ return join10(this.#folder(id), "log");
85009
85180
  }
85010
85181
  #log(id, line) {
85011
85182
  if (!this.#stored.has(id)) return;
85012
85183
  try {
85013
85184
  const file2 = this.#logFile(id);
85014
- if (existsSync2(file2) && statSync3(file2).size > MAX_LOG)
85015
- writePrivate(file2, readFileSync4(file2, "utf8").slice(-MAX_LOG / 2));
85185
+ if (existsSync2(file2) && statSync4(file2).size > MAX_LOG2)
85186
+ writePrivate(file2, readFileSync5(file2, "utf8").slice(-MAX_LOG2 / 2));
85016
85187
  appendFileSync(file2, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
85017
85188
  `, { mode: 384 });
85018
85189
  } catch {
@@ -85023,12 +85194,13 @@ var Computers = class {
85023
85194
  }
85024
85195
  #describe(stored) {
85025
85196
  const path = `${this.#base()}/v1/computers/${stored.id}/mcp`;
85026
- const down = this.#live.get(stored.id)?.failed === true;
85197
+ const down2 = this.#live.get(stored.id)?.failed === true;
85027
85198
  return {
85028
85199
  computerID: stored.id,
85029
85200
  name: stored.name,
85030
- state: stored.state === "running" && down ? "failed" : stored.state,
85201
+ state: stored.state === "running" && down2 ? "failed" : stored.state,
85031
85202
  access: stored.access,
85203
+ isolation: stored.isolation,
85032
85204
  browser: stored.browser,
85033
85205
  workspace: stored.workspace ?? null,
85034
85206
  startedAt: stored.startedAt,
@@ -85044,7 +85216,9 @@ var Computers = class {
85044
85216
  const live = {};
85045
85217
  this.#live.set(stored.id, live);
85046
85218
  stored.startedAt = (/* @__PURE__ */ new Date()).toISOString();
85047
- live.running = await this.#runtime.start(
85219
+ const runtime = this.#runtimes[stored.isolation];
85220
+ await runtime.prepare();
85221
+ live.running = await runtime.start(
85048
85222
  {
85049
85223
  id: stored.id,
85050
85224
  access: stored.access,
@@ -85097,7 +85271,7 @@ var OPENAPI = {
85097
85271
  "openapi": "3.1.0",
85098
85272
  "info": {
85099
85273
  "title": "AgentComputer",
85100
- "version": "0.0.13",
85274
+ "version": "0.0.15",
85101
85275
  "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.",
85102
85276
  "license": {
85103
85277
  "name": "Apache-2.0",
@@ -85557,7 +85731,8 @@ var OPENAPI = {
85557
85731
  "running",
85558
85732
  "stopped",
85559
85733
  "failed"
85560
- ]
85734
+ ],
85735
+ "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."
85561
85736
  },
85562
85737
  "Timeout": {
85563
85738
  "type": "integer",
@@ -85598,6 +85773,10 @@ var OPENAPI = {
85598
85773
  "maxLength": 4096,
85599
85774
  "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."
85600
85775
  },
85776
+ "isolation": {
85777
+ "$ref": "#/components/schemas/Isolation",
85778
+ "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."
85779
+ },
85601
85780
  "timeout": {
85602
85781
  "$ref": "#/components/schemas/Timeout"
85603
85782
  },
@@ -85606,6 +85785,14 @@ var OPENAPI = {
85606
85785
  }
85607
85786
  }
85608
85787
  },
85788
+ "Isolation": {
85789
+ "type": "string",
85790
+ "enum": [
85791
+ "none",
85792
+ "container"
85793
+ ],
85794
+ "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
85795
+ },
85609
85796
  "Endpoints": {
85610
85797
  "type": "object",
85611
85798
  "required": [
@@ -85637,6 +85824,7 @@ var OPENAPI = {
85637
85824
  "name",
85638
85825
  "state",
85639
85826
  "access",
85827
+ "isolation",
85640
85828
  "browser",
85641
85829
  "workspace",
85642
85830
  "startedAt",
@@ -85657,6 +85845,10 @@ var OPENAPI = {
85657
85845
  "access": {
85658
85846
  "$ref": "#/components/schemas/Access"
85659
85847
  },
85848
+ "isolation": {
85849
+ "$ref": "#/components/schemas/Isolation",
85850
+ "description": "The isolation it was made in. It is never run in another."
85851
+ },
85660
85852
  "browser": {
85661
85853
  "type": "boolean"
85662
85854
  },
@@ -86815,19 +87007,23 @@ var OPENAPI = {
86815
87007
  "type": "object",
86816
87008
  "required": [
86817
87009
  "version",
86818
- "isolation"
87010
+ "isolation",
87011
+ "isolationsAvailable"
86819
87012
  ],
86820
87013
  "properties": {
86821
87014
  "version": {
86822
87015
  "type": "string"
86823
87016
  },
86824
87017
  "isolation": {
86825
- "type": "string",
86826
- "enum": [
86827
- "none",
86828
- "container"
86829
- ],
86830
- "description": "How this service isolates its computers, chosen when it is started."
87018
+ "$ref": "#/components/schemas/Isolation",
87019
+ "description": "The isolation of a computer made without saying which, chosen when the service is started."
87020
+ },
87021
+ "isolationsAvailable": {
87022
+ "type": "array",
87023
+ "items": {
87024
+ "$ref": "#/components/schemas/Isolation"
87025
+ },
87026
+ "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."
86831
87027
  }
86832
87028
  }
86833
87029
  }
@@ -86855,7 +87051,8 @@ var OPERATIONS = [
86855
87051
  "type": "object",
86856
87052
  "required": [
86857
87053
  "version",
86858
- "isolation"
87054
+ "isolation",
87055
+ "isolationsAvailable"
86859
87056
  ],
86860
87057
  "properties": {
86861
87058
  "version": {
@@ -86867,7 +87064,19 @@ var OPERATIONS = [
86867
87064
  "none",
86868
87065
  "container"
86869
87066
  ],
86870
- "description": "How this service isolates its computers, chosen when it is started."
87067
+ "description": "The isolation of a computer made without saying which, chosen when the service is started."
87068
+ },
87069
+ "isolationsAvailable": {
87070
+ "type": "array",
87071
+ "items": {
87072
+ "type": "string",
87073
+ "enum": [
87074
+ "none",
87075
+ "container"
87076
+ ],
87077
+ "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
87078
+ },
87079
+ "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."
86871
87080
  }
86872
87081
  }
86873
87082
  },
@@ -86895,7 +87104,8 @@ var OPERATIONS = [
86895
87104
  "running",
86896
87105
  "stopped",
86897
87106
  "failed"
86898
- ]
87107
+ ],
87108
+ "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."
86899
87109
  }
86900
87110
  },
86901
87111
  "required": []
@@ -86909,6 +87119,7 @@ var OPERATIONS = [
86909
87119
  "name",
86910
87120
  "state",
86911
87121
  "access",
87122
+ "isolation",
86912
87123
  "browser",
86913
87124
  "workspace",
86914
87125
  "startedAt",
@@ -86929,7 +87140,8 @@ var OPERATIONS = [
86929
87140
  "running",
86930
87141
  "stopped",
86931
87142
  "failed"
86932
- ]
87143
+ ],
87144
+ "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."
86933
87145
  },
86934
87146
  "access": {
86935
87147
  "type": "string",
@@ -86941,6 +87153,14 @@ var OPERATIONS = [
86941
87153
  ],
86942
87154
  "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."
86943
87155
  },
87156
+ "isolation": {
87157
+ "type": "string",
87158
+ "enum": [
87159
+ "none",
87160
+ "container"
87161
+ ],
87162
+ "description": "The isolation it was made in. It is never run in another."
87163
+ },
86944
87164
  "browser": {
86945
87165
  "type": "boolean"
86946
87166
  },
@@ -87040,6 +87260,14 @@ var OPERATIONS = [
87040
87260
  "maxLength": 4096,
87041
87261
  "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."
87042
87262
  },
87263
+ "isolation": {
87264
+ "type": "string",
87265
+ "enum": [
87266
+ "none",
87267
+ "container"
87268
+ ],
87269
+ "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."
87270
+ },
87043
87271
  "timeout": {
87044
87272
  "type": "integer",
87045
87273
  "minimum": 1,
@@ -87068,6 +87296,7 @@ var OPERATIONS = [
87068
87296
  "name",
87069
87297
  "state",
87070
87298
  "access",
87299
+ "isolation",
87071
87300
  "browser",
87072
87301
  "workspace",
87073
87302
  "startedAt",
@@ -87088,7 +87317,8 @@ var OPERATIONS = [
87088
87317
  "running",
87089
87318
  "stopped",
87090
87319
  "failed"
87091
- ]
87320
+ ],
87321
+ "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."
87092
87322
  },
87093
87323
  "access": {
87094
87324
  "type": "string",
@@ -87100,6 +87330,14 @@ var OPERATIONS = [
87100
87330
  ],
87101
87331
  "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."
87102
87332
  },
87333
+ "isolation": {
87334
+ "type": "string",
87335
+ "enum": [
87336
+ "none",
87337
+ "container"
87338
+ ],
87339
+ "description": "The isolation it was made in. It is never run in another."
87340
+ },
87103
87341
  "browser": {
87104
87342
  "type": "boolean"
87105
87343
  },
@@ -87208,6 +87446,7 @@ var OPERATIONS = [
87208
87446
  "name",
87209
87447
  "state",
87210
87448
  "access",
87449
+ "isolation",
87211
87450
  "browser",
87212
87451
  "workspace",
87213
87452
  "startedAt",
@@ -87228,7 +87467,8 @@ var OPERATIONS = [
87228
87467
  "running",
87229
87468
  "stopped",
87230
87469
  "failed"
87231
- ]
87470
+ ],
87471
+ "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."
87232
87472
  },
87233
87473
  "access": {
87234
87474
  "type": "string",
@@ -87240,6 +87480,14 @@ var OPERATIONS = [
87240
87480
  ],
87241
87481
  "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."
87242
87482
  },
87483
+ "isolation": {
87484
+ "type": "string",
87485
+ "enum": [
87486
+ "none",
87487
+ "container"
87488
+ ],
87489
+ "description": "The isolation it was made in. It is never run in another."
87490
+ },
87243
87491
  "browser": {
87244
87492
  "type": "boolean"
87245
87493
  },
@@ -87439,6 +87687,7 @@ var OPERATIONS = [
87439
87687
  "name",
87440
87688
  "state",
87441
87689
  "access",
87690
+ "isolation",
87442
87691
  "browser",
87443
87692
  "workspace",
87444
87693
  "startedAt",
@@ -87459,7 +87708,8 @@ var OPERATIONS = [
87459
87708
  "running",
87460
87709
  "stopped",
87461
87710
  "failed"
87462
- ]
87711
+ ],
87712
+ "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."
87463
87713
  },
87464
87714
  "access": {
87465
87715
  "type": "string",
@@ -87471,6 +87721,14 @@ var OPERATIONS = [
87471
87721
  ],
87472
87722
  "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."
87473
87723
  },
87724
+ "isolation": {
87725
+ "type": "string",
87726
+ "enum": [
87727
+ "none",
87728
+ "container"
87729
+ ],
87730
+ "description": "The isolation it was made in. It is never run in another."
87731
+ },
87474
87732
  "browser": {
87475
87733
  "type": "boolean"
87476
87734
  },
@@ -87563,6 +87821,7 @@ var OPERATIONS = [
87563
87821
  "name",
87564
87822
  "state",
87565
87823
  "access",
87824
+ "isolation",
87566
87825
  "browser",
87567
87826
  "workspace",
87568
87827
  "startedAt",
@@ -87583,7 +87842,8 @@ var OPERATIONS = [
87583
87842
  "running",
87584
87843
  "stopped",
87585
87844
  "failed"
87586
- ]
87845
+ ],
87846
+ "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."
87587
87847
  },
87588
87848
  "access": {
87589
87849
  "type": "string",
@@ -87595,6 +87855,14 @@ var OPERATIONS = [
87595
87855
  ],
87596
87856
  "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."
87597
87857
  },
87858
+ "isolation": {
87859
+ "type": "string",
87860
+ "enum": [
87861
+ "none",
87862
+ "container"
87863
+ ],
87864
+ "description": "The isolation it was made in. It is never run in another."
87865
+ },
87598
87866
  "browser": {
87599
87867
  "type": "boolean"
87600
87868
  },
@@ -87692,6 +87960,7 @@ var OPERATIONS = [
87692
87960
  "name",
87693
87961
  "state",
87694
87962
  "access",
87963
+ "isolation",
87695
87964
  "browser",
87696
87965
  "workspace",
87697
87966
  "startedAt",
@@ -87712,7 +87981,8 @@ var OPERATIONS = [
87712
87981
  "running",
87713
87982
  "stopped",
87714
87983
  "failed"
87715
- ]
87984
+ ],
87985
+ "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."
87716
87986
  },
87717
87987
  "access": {
87718
87988
  "type": "string",
@@ -87724,6 +87994,14 @@ var OPERATIONS = [
87724
87994
  ],
87725
87995
  "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."
87726
87996
  },
87997
+ "isolation": {
87998
+ "type": "string",
87999
+ "enum": [
88000
+ "none",
88001
+ "container"
88002
+ ],
88003
+ "description": "The isolation it was made in. It is never run in another."
88004
+ },
87727
88005
  "browser": {
87728
88006
  "type": "boolean"
87729
88007
  },
@@ -88011,9 +88289,12 @@ async function serve(options) {
88011
88289
  checkExposure(host, options);
88012
88290
  const data = privateFolder(options.data);
88013
88291
  const control = digestOf(controlToken(data));
88292
+ const live = await running(data);
88293
+ if (live)
88294
+ throw new Error(`A service is already running with its data in ${data}, at ${live.url}.`);
88014
88295
  let base = "";
88015
- await options.runtime.prepare();
88016
- const computers = new Computers(data, () => base, options.runtime);
88296
+ await options.runtimes[options.isolation].prepare();
88297
+ const computers = new Computers(data, () => base, options.runtimes, options.isolation);
88017
88298
  const handlers = createHandlers(computers);
88018
88299
  const routes = OPERATIONS.map((operation) => ({
88019
88300
  operation,
@@ -88101,7 +88382,7 @@ async function serve(options) {
88101
88382
  });
88102
88383
  };
88103
88384
  const server = options.tls ? createTlsServer(
88104
- { cert: readFileSync5(options.tls.cert), key: readFileSync5(options.tls.key) },
88385
+ { cert: readFileSync6(options.tls.cert), key: readFileSync6(options.tls.key) },
88105
88386
  listener
88106
88387
  ) : createServer5(listener);
88107
88388
  const allowed = new Set(options.allowOrigins ?? []);
@@ -88133,11 +88414,12 @@ ${JSON.stringify({ code: status, message })}
88133
88414
  const bound = server.address();
88134
88415
  const scheme = options.tls ? "https" : "http";
88135
88416
  base = options.publicUrl?.replace(/\/+$/, "") ?? `${scheme}://${bound.family === "IPv6" ? `[${bound.address}]` : bound.address}:${bound.port}`;
88417
+ const record3 = recordOf(data);
88418
+ rmSync3(record3, { force: true });
88136
88419
  await computers.resume();
88137
- const record3 = join10(data, "server.json");
88138
88420
  writePrivate(
88139
88421
  record3,
88140
- JSON.stringify({ url: base, pid: process.pid, isolation: options.runtime.isolation })
88422
+ JSON.stringify({ url: base, pid: process.pid, isolation: options.isolation })
88141
88423
  );
88142
88424
  process.stdout.write(`AgentComputer on ${base}, data in ${data}
88143
88425
  `);
@@ -88163,9 +88445,14 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88163
88445
  [--browser-name NAME] [--browser-path PATH]
88164
88446
  run the service; 127.0.0.1:7878 by default, and
88165
88447
  anywhere else only with TLS
88166
- info the service's version and isolation
88448
+ up [the options of serve] run the service in the background and return once it
88449
+ answers, printing where; one running is left as it is
88450
+ down stop the service, and its computers with it
88451
+ status where the service is running, if it is
88452
+ info the service's version, the isolation it gives a computer
88453
+ made without saying, and the ones it can make now
88167
88454
  create --access none|read|files|terminal [--browser] [--workspace DIR] [--name NAME]
88168
- [--timeout SECONDS] [--metadata KEY=VALUE]...
88455
+ [--isolation none|container] [--timeout SECONDS] [--metadata KEY=VALUE]...
88169
88456
  make a computer; prints its endpoints and token, once
88170
88457
  list [--state running|stopped|failed]
88171
88458
  the computers
@@ -88178,6 +88465,8 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88178
88465
  a one-time ticket to open one live, as a WebSocket
88179
88466
  kill <id> stop it and remove what the service made
88180
88467
 
88468
+ --version prints this program's version, with no service.
88469
+
88181
88470
  The data folder is --data, else $AGENTCOMPUTER_DATA, else $XDG_DATA_HOME/agentcomputer, else
88182
88471
  ~/.local/share/agentcomputer. Commands find the running service there, with its control token.
88183
88472
  `;
@@ -88190,20 +88479,18 @@ function dataFolder(given) {
88190
88479
  given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join11(xdg, "agentcomputer") : join11(homedir2(), ".local", "share", "agentcomputer"))
88191
88480
  );
88192
88481
  }
88482
+ var none = (data) => `No service is running with its data in ${data}. Start one: agentcomputer up`;
88193
88483
  function connectionTo(data) {
88194
- try {
88195
- const { url: url3 } = JSON.parse(readFileSync6(join11(data, "server.json"), "utf8"));
88196
- return { apiUrl: url3, apiKey: readFileSync6(join11(data, "control-token"), "utf8").trim() };
88197
- } catch {
88198
- throw new Error(
88199
- `No service is running with its data in ${data}. Start one: agentcomputer serve`
88200
- );
88201
- }
88484
+ const said = recorded(data);
88485
+ if (!said) throw new Error(none(data));
88486
+ return { apiUrl: said.url, apiKey: said.token };
88202
88487
  }
88203
88488
  async function main() {
88204
- const { values, positionals } = parseArgs({
88489
+ const { values, positionals, tokens } = parseArgs({
88205
88490
  allowPositionals: true,
88491
+ tokens: true,
88206
88492
  options: {
88493
+ version: { type: "boolean" },
88207
88494
  data: { type: "string" },
88208
88495
  listen: { type: "string" },
88209
88496
  isolation: { type: "string" },
@@ -88234,19 +88521,38 @@ async function main() {
88234
88521
  }
88235
88522
  });
88236
88523
  const [command, id, extra] = positionals;
88524
+ if (values.version) return void process.stdout.write(`${VERSION2}
88525
+ `);
88237
88526
  if (values.help || !command) return void process.stdout.write(USAGE);
88238
88527
  const data = dataFolder(values.data);
88239
88528
  const out = (value) => process.stdout.write(`${JSON.stringify(value, null, 2)}
88240
88529
  `);
88530
+ if (command === "status") {
88531
+ const found = await running(data);
88532
+ if (!found) throw new Error(none(data));
88533
+ return out(found);
88534
+ }
88535
+ if (command === "up") {
88536
+ const given = process.argv.slice(2);
88537
+ given.splice(tokens.find((token) => token.kind === "positional")?.index, 1);
88538
+ return out(await up(data, process.argv[1], given));
88539
+ }
88540
+ if (command === "down") return down(data);
88541
+ if ((command === "start" || command === "stop") && !id) {
88542
+ const service = command === "start" ? "up" : "down";
88543
+ throw new UsageError(
88544
+ `${command} takes a computer's id. For the service itself: agentcomputer ${service}`
88545
+ );
88546
+ }
88241
88547
  const idOf = () => {
88242
88548
  if (!id) throw new UsageError(`${command} takes a computer's id.`);
88243
88549
  return id;
88244
88550
  };
88245
88551
  const milliseconds = (seconds2) => Number(seconds2) * 1e3;
88552
+ const { isolation } = values;
88553
+ if (isolation !== void 0 && isolation !== "none" && isolation !== "container")
88554
+ throw new UsageError("--isolation is none or container.");
88246
88555
  if (command === "serve") {
88247
- const isolation = values.isolation ?? "none";
88248
- if (isolation !== "none" && isolation !== "container")
88249
- throw new UsageError("--isolation is none or container.");
88250
88556
  const { "tls-cert": cert, "tls-key": key } = values;
88251
88557
  if (Boolean(cert) !== Boolean(key))
88252
88558
  throw new UsageError("--tls-cert and --tls-key go together.");
@@ -88257,10 +88563,11 @@ async function main() {
88257
88563
  tlsProxy: values["tls-proxy"],
88258
88564
  publicUrl: values["public-url"],
88259
88565
  allowOrigins: values["allow-origin"],
88260
- runtime: isolation === "container" ? new ContainerRuntime(values.image ?? `${IMAGE}:${VERSION2}`) : new ProcessRuntime({
88261
- browser: values["browser-name"],
88262
- path: values["browser-path"]
88263
- })
88566
+ isolation: isolation ?? "none",
88567
+ runtimes: {
88568
+ none: new ProcessRuntime({ browser: values["browser-name"], path: values["browser-path"] }),
88569
+ container: new ContainerRuntime(values.image ?? `${IMAGE}:${VERSION2}`)
88570
+ }
88264
88571
  });
88265
88572
  }
88266
88573
  if (command === "browser-host")
@@ -88290,6 +88597,7 @@ async function main() {
88290
88597
  ...connection,
88291
88598
  access: values.access,
88292
88599
  ...values.browser ? { browser: true } : {},
88600
+ ...isolation ? { isolation } : {},
88293
88601
  ...values.workspace ? { workspace: resolve5(values.workspace) } : {},
88294
88602
  ...values.name ? { name: values.name } : {},
88295
88603
  ...values.timeout ? { timeoutMs: milliseconds(values.timeout) } : {},