@agentorg/computer 0.0.17 → 0.0.19

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.
@@ -3752,7 +3752,7 @@ var require_websocket_server = __commonJS({
3752
3752
 
3753
3753
  // src/main.ts
3754
3754
  import { homedir as homedir2 } from "node:os";
3755
- import { isAbsolute as isAbsolute2, join as join11, resolve as resolve5 } from "node:path";
3755
+ import { isAbsolute as isAbsolute3, join as join11, resolve as resolve5 } from "node:path";
3756
3756
  import { parseArgs } from "node:util";
3757
3757
  import { isMainThread as isMainThread2 } from "node:worker_threads";
3758
3758
 
@@ -3767,9 +3767,11 @@ var playwright = () => {
3767
3767
  loaded ??= createRequire(import.meta.url)("playwright-core");
3768
3768
  return loaded;
3769
3769
  };
3770
+ var DEFAULT_CHANNEL = "chrome";
3771
+ var channelProgram = (channel) => createRequire(import.meta.url)("playwright-core/lib/coreBundle").registry.registry.findExecutable(channel)?.executablePath("javascript");
3770
3772
 
3771
3773
  // src/browser-host.ts
3772
- var sandboxFor = (options) => options.sandbox ?? (process.platform !== "linux" || !options.executable && !["chromium", "chrome-for-testing"].includes(options.channel ?? "chrome"));
3774
+ var sandboxFor = (options) => options.sandbox ?? (process.platform !== "linux" || !options.executable && !["chromium", "chrome-for-testing"].includes(options.channel ?? DEFAULT_CHANNEL));
3773
3775
  async function hostBrowser(options) {
3774
3776
  const run3 = mkdtempSync(join("/tmp", "agentcomputer-"));
3775
3777
  process.env.XDG_RUNTIME_DIR = run3;
@@ -3777,7 +3779,7 @@ async function hostBrowser(options) {
3777
3779
  const context = await playwright().chromium.launchPersistentContext(options.profile, {
3778
3780
  headless: true,
3779
3781
  chromiumSandbox: sandboxFor(options),
3780
- ...options.executable ? { executablePath: options.executable } : { channel: options.channel ?? "chrome" }
3782
+ ...options.executable ? { executablePath: options.executable } : { channel: options.channel ?? DEFAULT_CHANNEL }
3781
3783
  });
3782
3784
  const browser = context.browser();
3783
3785
  if (!browser) throw new Error("The browser can't be shared.");
@@ -18458,13 +18460,13 @@ var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
18458
18460
  }
18459
18461
  return count;
18460
18462
  }
18461
- function getFullPath(resolver, id = "", normalize) {
18463
+ function getFullPath(resolver2, id = "", normalize) {
18462
18464
  if (normalize !== false) id = normalizeId(id);
18463
- return _getFullPath(resolver, resolver.parse(id));
18465
+ return _getFullPath(resolver2, resolver2.parse(id));
18464
18466
  }
18465
18467
  exports.getFullPath = getFullPath;
18466
- function _getFullPath(resolver, p) {
18467
- return resolver.serialize(p).split("#")[0] + "#";
18468
+ function _getFullPath(resolver2, p) {
18469
+ return resolver2.serialize(p).split("#")[0] + "#";
18468
18470
  }
18469
18471
  exports._getFullPath = _getFullPath;
18470
18472
  const TRAILING_SLASH_HASH = /#\/?$/;
@@ -18472,9 +18474,9 @@ var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
18472
18474
  return id ? id.replace(TRAILING_SLASH_HASH, "") : "";
18473
18475
  }
18474
18476
  exports.normalizeId = normalizeId;
18475
- function resolveUrl(resolver, baseId, id) {
18477
+ function resolveUrl(resolver2, baseId, id) {
18476
18478
  id = normalizeId(id);
18477
- return resolver.resolve(baseId, id);
18479
+ return resolver2.resolve(baseId, id);
18478
18480
  }
18479
18481
  exports.resolveUrl = resolveUrl;
18480
18482
  const ANCHOR = /^[a-z_][-a-z0-9._]*$/i;
@@ -18956,10 +18958,10 @@ var require_ref_error = /* @__PURE__ */ __commonJSMin(((exports) => {
18956
18958
  Object.defineProperty(exports, "__esModule", { value: true });
18957
18959
  const resolve_1 = require_resolve();
18958
18960
  var MissingRefError = class extends Error {
18959
- constructor(resolver, baseId, ref, msg) {
18961
+ constructor(resolver2, baseId, ref, msg) {
18960
18962
  super(msg || `can't resolve reference ${ref} from id ${baseId}`);
18961
- this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref);
18962
- this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver, this.missingRef));
18963
+ this.missingRef = (0, resolve_1.resolveUrl)(resolver2, baseId, ref);
18964
+ this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver2, this.missingRef));
18963
18965
  }
18964
18966
  };
18965
18967
  exports.default = MissingRefError;
@@ -19203,10 +19205,10 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19203
19205
  const HEX_DIGITS = "0123456789ABCDEF";
19204
19206
  for (let i = 0; i < 256; i++) BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
19205
19207
  }
19206
- function percentEncodeNonAscii(cp) {
19207
- if (cp < 2048) return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
19208
- if (cp < 65536) return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
19209
- return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
19208
+ function percentEncodeNonAscii(cp2) {
19209
+ if (cp2 < 2048) return BYTE_HEX[192 | cp2 >> 6] + BYTE_HEX[128 | cp2 & 63];
19210
+ if (cp2 < 65536) return BYTE_HEX[224 | cp2 >> 12] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
19211
+ return BYTE_HEX[240 | cp2 >> 18] + BYTE_HEX[128 | cp2 >> 12 & 63] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
19210
19212
  }
19211
19213
  function stringArrayToHexStripped(input2) {
19212
19214
  let acc = "";
@@ -19546,8 +19548,8 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19546
19548
  function encodeFragment(input2) {
19547
19549
  return encodeComponent(input2, isQueryFragmentCharacter);
19548
19550
  }
19549
- function isEscapeSafe(cp) {
19550
- return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
19551
+ function isEscapeSafe(cp2) {
19552
+ return cp2 >= 48 && cp2 <= 57 || cp2 >= 65 && cp2 <= 90 || cp2 >= 97 && cp2 <= 122 || cp2 === 42 || cp2 === 43 || cp2 === 45 || cp2 === 46 || cp2 === 47 || cp2 === 64 || cp2 === 95;
19551
19553
  }
19552
19554
  function normalizeQueryFragmentEncoding(input2) {
19553
19555
  let output2 = "";
@@ -28109,7 +28111,7 @@ var Browser = class {
28109
28111
  "browser-host",
28110
28112
  "--profile",
28111
28113
  join3(folder, "profile"),
28112
- ...config3.path ? ["--executable", config3.path] : ["--channel", config3.browser ?? "chrome"],
28114
+ ...config3.path ? ["--executable", config3.path] : ["--channel", config3.browser ?? DEFAULT_CHANNEL],
28113
28115
  ...config3.sandbox === false ? ["--no-sandbox"] : []
28114
28116
  ],
28115
28117
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -28293,7 +28295,7 @@ import { spawn as spawn2 } from "node:child_process";
28293
28295
  import { randomBytes } from "node:crypto";
28294
28296
  import { StringDecoder } from "node:string_decoder";
28295
28297
  import { readdirSync, readFileSync, statSync } from "node:fs";
28296
- import { join as join22, relative, resolve as resolve2, sep as sep22 } from "node:path";
28298
+ import { isAbsolute, join as join22, relative, resolve as resolve2, sep as sep22 } from "node:path";
28297
28299
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28298
28300
  import { isMainThread, parentPort, Worker, workerData } from "node:worker_threads";
28299
28301
  var __create3 = Object.create;
@@ -56252,13 +56254,13 @@ var require_resolve2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56252
56254
  }
56253
56255
  return count;
56254
56256
  }
56255
- function getFullPath(resolver, id = "", normalize) {
56257
+ function getFullPath(resolver2, id = "", normalize) {
56256
56258
  if (normalize !== false) id = normalizeId(id);
56257
- return _getFullPath(resolver, resolver.parse(id));
56259
+ return _getFullPath(resolver2, resolver2.parse(id));
56258
56260
  }
56259
56261
  exports.getFullPath = getFullPath;
56260
- function _getFullPath(resolver, p) {
56261
- return resolver.serialize(p).split("#")[0] + "#";
56262
+ function _getFullPath(resolver2, p) {
56263
+ return resolver2.serialize(p).split("#")[0] + "#";
56262
56264
  }
56263
56265
  exports._getFullPath = _getFullPath;
56264
56266
  const TRAILING_SLASH_HASH = /#\/?$/;
@@ -56266,9 +56268,9 @@ var require_resolve2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56266
56268
  return id ? id.replace(TRAILING_SLASH_HASH, "") : "";
56267
56269
  }
56268
56270
  exports.normalizeId = normalizeId;
56269
- function resolveUrl(resolver, baseId, id) {
56271
+ function resolveUrl(resolver2, baseId, id) {
56270
56272
  id = normalizeId(id);
56271
- return resolver.resolve(baseId, id);
56273
+ return resolver2.resolve(baseId, id);
56272
56274
  }
56273
56275
  exports.resolveUrl = resolveUrl;
56274
56276
  const ANCHOR = /^[a-z_][-a-z0-9._]*$/i;
@@ -56750,10 +56752,10 @@ var require_ref_error2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
56750
56752
  Object.defineProperty(exports, "__esModule", { value: true });
56751
56753
  const resolve_1 = require_resolve2();
56752
56754
  var MissingRefError = class extends Error {
56753
- constructor(resolver, baseId, ref, msg) {
56755
+ constructor(resolver2, baseId, ref, msg) {
56754
56756
  super(msg || `can't resolve reference ${ref} from id ${baseId}`);
56755
- this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref);
56756
- this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver, this.missingRef));
56757
+ this.missingRef = (0, resolve_1.resolveUrl)(resolver2, baseId, ref);
56758
+ this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver2, this.missingRef));
56757
56759
  }
56758
56760
  };
56759
56761
  exports.default = MissingRefError;
@@ -56997,10 +56999,10 @@ var require_utils2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
56997
56999
  const HEX_DIGITS = "0123456789ABCDEF";
56998
57000
  for (let i = 0; i < 256; i++) BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
56999
57001
  }
57000
- function percentEncodeNonAscii(cp) {
57001
- if (cp < 2048) return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
57002
- if (cp < 65536) return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
57003
- return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
57002
+ function percentEncodeNonAscii(cp2) {
57003
+ if (cp2 < 2048) return BYTE_HEX[192 | cp2 >> 6] + BYTE_HEX[128 | cp2 & 63];
57004
+ if (cp2 < 65536) return BYTE_HEX[224 | cp2 >> 12] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
57005
+ return BYTE_HEX[240 | cp2 >> 18] + BYTE_HEX[128 | cp2 >> 12 & 63] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
57004
57006
  }
57005
57007
  function stringArrayToHexStripped(input2) {
57006
57008
  let acc = "";
@@ -57340,8 +57342,8 @@ var require_utils2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
57340
57342
  function encodeFragment(input2) {
57341
57343
  return encodeComponent(input2, isQueryFragmentCharacter);
57342
57344
  }
57343
- function isEscapeSafe(cp) {
57344
- return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
57345
+ function isEscapeSafe(cp2) {
57346
+ return cp2 >= 48 && cp2 <= 57 || cp2 >= 65 && cp2 <= 90 || cp2 >= 97 && cp2 <= 122 || cp2 === 42 || cp2 === 43 || cp2 === 45 || cp2 === 46 || cp2 === 47 || cp2 === 64 || cp2 === 95;
57345
57347
  }
57346
57348
  function normalizeQueryFragmentEncoding(input2) {
57347
57349
  let output2 = "";
@@ -63489,8 +63491,8 @@ var WebStandardStreamableHTTPServerTransport = class {
63489
63491
  return timer;
63490
63492
  }
63491
63493
  /** Sets the scope challenge resolver for parsed JSON-RPC requests. */
63492
- setScopeChallengeResolver(resolver) {
63493
- this._scopeChallengeResolver = resolver;
63494
+ setScopeChallengeResolver(resolver2) {
63495
+ this._scopeChallengeResolver = resolver2;
63494
63496
  }
63495
63497
  /**
63496
63498
  * Starts the transport. This is required by the {@linkcode Transport} interface but is a no-op
@@ -65373,8 +65375,8 @@ var NodeStreamableHTTPServerTransport = class {
65373
65375
  this._webStandardTransport.setSupportedProtocolVersions(versions);
65374
65376
  }
65375
65377
  /** Sets the scope challenge resolver used by the wrapped Web Standard transport. */
65376
- setScopeChallengeResolver(resolver) {
65377
- this._webStandardTransport.setScopeChallengeResolver(resolver);
65378
+ setScopeChallengeResolver(resolver2) {
65379
+ this._webStandardTransport.setScopeChallengeResolver(resolver2);
65378
65380
  }
65379
65381
  /**
65380
65382
  * Handles an incoming HTTP request, whether `GET` or `POST`.
@@ -66307,28 +66309,33 @@ var refusing = async (work) => {
66307
66309
  }
66308
66310
  };
66309
66311
  var foldersFirst = (a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name);
66310
- async function inside(root, path) {
66311
- const refused2 = () => new ExecRefused(`${path} is outside the workspace.`);
66312
- const wanted = resolve(root, path);
66313
- const real = async (at) => {
66314
- try {
66315
- return await realpath(at);
66316
- } catch (error62) {
66317
- const { code } = error62;
66318
- if (code !== "ENOENT" && code !== "ENOTDIR") throw error62;
66319
- if (await lstat(at).then(
66320
- () => true,
66321
- () => false
66322
- ))
66323
- throw refused2();
66324
- const above = dirname3(at);
66325
- if (above === at) throw refused2();
66326
- return join4(await real(above), basename(at));
66327
- }
66328
- };
66329
- const found = await real(wanted);
66330
- if (found !== root && !found.startsWith(root + sep2)) throw refused2();
66312
+ var resolver = (root, reach) => async (path) => {
66313
+ const found = await real(resolve(root, path));
66314
+ if (reach === "machine") {
66315
+ if (found === void 0) throw new ExecRefused(`${path} is a link to nothing.`);
66316
+ return found;
66317
+ }
66318
+ if (found === void 0 || found !== root && !found.startsWith(root + sep2))
66319
+ throw new ExecRefused(`${path} is outside the workspace.`);
66331
66320
  return found;
66321
+ };
66322
+ var inside = (root, path) => resolver(root, "workspace")(path);
66323
+ async function real(at) {
66324
+ try {
66325
+ return await realpath(at);
66326
+ } catch (error62) {
66327
+ const { code } = error62;
66328
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error62;
66329
+ if (await lstat(at).then(
66330
+ () => true,
66331
+ () => false
66332
+ ))
66333
+ return void 0;
66334
+ const above = dirname3(at);
66335
+ if (above === at) return void 0;
66336
+ const placed = await real(above);
66337
+ return placed === void 0 ? void 0 : join4(placed, basename(at));
66338
+ }
66332
66339
  }
66333
66340
  async function aFile(path, given) {
66334
66341
  const found = await stat(path);
@@ -66336,8 +66343,7 @@ async function aFile(path, given) {
66336
66343
  if (!found.isFile()) throw new ExecRefused(`${given} isn't a file that can be read.`);
66337
66344
  return found;
66338
66345
  }
66339
- function createFiles(root) {
66340
- const at = (path) => inside(root, path);
66346
+ function createFiles(at) {
66341
66347
  const text22 = async (path, given) => {
66342
66348
  await aFile(path, given);
66343
66349
  const bytes = await readFile(path);
@@ -66462,7 +66468,10 @@ function searched(job) {
66462
66468
  const matcher = new RegExp(pattern, job.ignore_case ? "i" : "");
66463
66469
  const limit = job.limit ?? DEFAULT_MATCHES;
66464
66470
  const out = [];
66465
- const name = (path) => relative(job.root, path) || path;
66471
+ const name = (path) => {
66472
+ const from = relative(job.root, path);
66473
+ return from === ".." || from.startsWith(`..${sep22}`) || isAbsolute(from) ? path : from || path;
66474
+ };
66466
66475
  const scan = (file2) => {
66467
66476
  const found = statSync(file2);
66468
66477
  if (!found.isFile() || found.size > MAX_FILE_BYTES) return;
@@ -66526,8 +66535,8 @@ function onThread(job, what) {
66526
66535
  });
66527
66536
  });
66528
66537
  }
66529
- async function search(root, input2) {
66530
- const path = await inside(root, input2.path ?? ".");
66538
+ async function search(root, at, input2) {
66539
+ const path = await at(input2.path ?? ".");
66531
66540
  return onThread({ ...input2, path, root, agentexecSearch: true }, "That search");
66532
66541
  }
66533
66542
  function changedSince(root, since) {
@@ -66565,9 +66574,9 @@ function createHostFiles(root, level) {
66565
66574
  read: (path, offset, length) => refusing(async () => {
66566
66575
  if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 1 || length > MAX_READ_BYTES)
66567
66576
  throw new ExecRefused("A read starts at 0 or later and takes 1 to 1,048,576 bytes.");
66568
- const real = await inside(root, path);
66569
- const found = await aFile(real, path);
66570
- const file2 = await open3(real, "r");
66577
+ const real2 = await inside(root, path);
66578
+ const found = await aFile(real2, path);
66579
+ const file2 = await open3(real2, "r");
66571
66580
  try {
66572
66581
  const bytes = Buffer.alloc(Math.max(0, Math.min(length, found.size - offset)));
66573
66582
  const { bytesRead } = await file2.read(bytes, 0, bytes.length, offset);
@@ -66708,9 +66717,12 @@ var TOOLS = [
66708
66717
  "search",
66709
66718
  "edit"
66710
66719
  ];
66711
- function createServer2(root, sessions, version22, level, extraTools = []) {
66720
+ function createServer2(root, sessions, version22, level, extraTools = [], reach = "workspace") {
66712
66721
  const server = new McpServer({ name: "agentexec", version: version22 });
66713
- const files = createFiles(root);
66722
+ const at = resolver(root, reach);
66723
+ const files = createFiles(at);
66724
+ const where = reach === "machine" ? "Relative to the workspace, or absolute, anywhere on the machine." : "Inside the workspace; relative to it, or absolute.";
66725
+ const orWorkspace = `${where} The workspace by default.`;
66714
66726
  for (const { name, description, readOnly, call } of extraTools)
66715
66727
  server.registerTool(
66716
66728
  name,
@@ -66789,7 +66801,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66789
66801
  {
66790
66802
  description: "Reads a text file as numbered lines, or shows a PNG, JPEG, GIF or WebP of up to 3 MiB as an image; offset and limit are for text.",
66791
66803
  inputSchema: {
66792
- path: external_exports.string().min(1).describe("Inside the workspace; relative to it, or absolute."),
66804
+ path: external_exports.string().min(1).describe(where),
66793
66805
  offset: external_exports.number().int().positive().optional().describe("The first line, from 1."),
66794
66806
  limit: external_exports.number().int().positive().optional().describe("How many lines; 2000 by default.")
66795
66807
  },
@@ -66808,7 +66820,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66808
66820
  "list",
66809
66821
  {
66810
66822
  description: "Lists a folder: folders end in /, and files give their size in bytes.",
66811
- inputSchema: { path: external_exports.string().optional().describe("The workspace by default.") },
66823
+ inputSchema: { path: external_exports.string().optional().describe(orWorkspace) },
66812
66824
  annotations: READ_ONLY
66813
66825
  },
66814
66826
  (input2) => saying(() => files.list(input2))
@@ -66816,17 +66828,17 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66816
66828
  server.registerTool(
66817
66829
  "search",
66818
66830
  {
66819
- description: "Searches text files under a folder, as path:line:text. Skips what .gitignore ignores, .git and node_modules.",
66831
+ description: `Searches text files under a folder, as path:line:text, the path from the workspace${reach === "machine" ? ", or whole for a file outside it" : ""}. Skips what .gitignore ignores, .git and node_modules.`,
66820
66832
  inputSchema: {
66821
66833
  pattern: external_exports.string().min(1).describe("Exact text, or a regular expression with regex."),
66822
- path: external_exports.string().optional().describe("A folder or file; the workspace by default."),
66834
+ path: external_exports.string().optional().describe(`A folder or file. ${orWorkspace}`),
66823
66835
  regex: external_exports.boolean().optional(),
66824
66836
  ignore_case: external_exports.boolean().optional(),
66825
66837
  limit: external_exports.number().int().positive().optional().describe("Most matches; 200 by default.")
66826
66838
  },
66827
66839
  annotations: READ_ONLY
66828
66840
  },
66829
- (input2) => saying(() => search(root, input2))
66841
+ (input2) => saying(() => search(root, at, input2))
66830
66842
  );
66831
66843
  if (level !== "read")
66832
66844
  server.registerTool(
@@ -66834,7 +66846,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66834
66846
  {
66835
66847
  description: "Replaces exact text in a file and answers with a unified diff. old_text must appear exactly once unless replace_all. An empty old_text creates a new file.",
66836
66848
  inputSchema: {
66837
- path: external_exports.string().min(1).describe("Inside the workspace; relative to it, or absolute."),
66849
+ path: external_exports.string().min(1).describe(where),
66838
66850
  old_text: external_exports.string().describe("The exact text to replace, whitespace included."),
66839
66851
  new_text: external_exports.string().describe("What to put in its place."),
66840
66852
  replace_all: external_exports.boolean().optional()
@@ -66845,7 +66857,7 @@ function createServer2(root, sessions, version22, level, extraTools = []) {
66845
66857
  );
66846
66858
  return server;
66847
66859
  }
66848
- var VERSION = "0.0.8";
66860
+ var VERSION = "0.0.9";
66849
66861
  function createAgentExec(options) {
66850
66862
  const root = realpathSync(resolve3(options.workspace));
66851
66863
  const level = options.level ?? "read";
@@ -66870,7 +66882,7 @@ function createAgentExec(options) {
66870
66882
  }
66871
66883
  return {
66872
66884
  async handle(request, response, body) {
66873
- const server = createServer2(root, sessions, VERSION, level, extraTools);
66885
+ const server = createServer2(root, sessions, VERSION, level, extraTools, options.reach);
66874
66886
  const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
66875
66887
  response.once("close", () => {
66876
66888
  void transport.close();
@@ -66922,7 +66934,11 @@ function aboutTool(facts, load) {
66922
66934
  // In a container its commands find the folder somewhere else, so the row says both.
66923
66935
  inside2 === outside ? { label: "Folder", value: outside, path: true } : { label: "Folder", value: `${outside} on the machine, mounted at ${inside2} in it` },
66924
66936
  { label: "Reaches", value: REACHES[facts.access] + (facts.browser ? ", Browser" : "") },
66925
- facts.contained ? { label: "Isolation", value: "A container of its own" } : { label: "Isolation", value: "None: it acts as you", caution: true }
66937
+ facts.contained ? { label: "Isolation", value: "A container of its own" } : {
66938
+ label: "Isolation",
66939
+ value: "None: it acts as you, on the whole machine",
66940
+ caution: true
66941
+ }
66926
66942
  ];
66927
66943
  const reading = await load.now();
66928
66944
  if (reading)
@@ -67031,6 +67047,7 @@ var BrowserLive = class {
67031
67047
  } catch {
67032
67048
  return;
67033
67049
  }
67050
+ if (typeof message !== "object" || message === null) return;
67034
67051
  this.#input(ws, viewer, message);
67035
67052
  });
67036
67053
  this.#begin().then(
@@ -67863,16 +67880,44 @@ var loadPty = () => {
67863
67880
  })();
67864
67881
  return pty;
67865
67882
  };
67883
+ function browserProgram(config3) {
67884
+ const channel = config3.browser ?? DEFAULT_CHANNEL;
67885
+ let path;
67886
+ try {
67887
+ playwright();
67888
+ path = config3.path ?? channelProgram(channel);
67889
+ } catch {
67890
+ return { ready: false, reason: "Playwright isn't installed with this service." };
67891
+ }
67892
+ if (path && existsSync(path)) return { ready: true };
67893
+ return {
67894
+ ready: false,
67895
+ reason: config3.path ? `There's no browser at ${config3.path}. Start the service with a --browser-path that exists.` : `The browser ${channel} isn't installed on this machine. Install it, or start the service with --browser-name or --browser-path.`
67896
+ };
67897
+ }
67866
67898
  var ProcessRuntime = class {
67867
67899
  #config;
67868
67900
  #contained;
67869
- /** `contained` is for inside a container, where the computer and its load are its own. */
67901
+ /**
67902
+ * Outside a container it is this computer, its files reaching the whole machine. `contained` is
67903
+ * for inside one, where the computer and its load are its own and its files stay in its
67904
+ * workspace.
67905
+ */
67870
67906
  constructor(config3, contained = false) {
67871
67907
  this.#config = config3;
67872
67908
  this.#contained = contained;
67873
67909
  }
67874
- async available() {
67875
- return true;
67910
+ async offer() {
67911
+ return {
67912
+ ready: true,
67913
+ capabilities: [
67914
+ { capability: "about", ready: true },
67915
+ { capability: "terminal", ready: true },
67916
+ { capability: "files", ready: true },
67917
+ { capability: "screen", ready: false, reason: "This service can't reach the screen yet." }
67918
+ ],
67919
+ browsers: [{ kind: "headless", ...browserProgram(this.#config) }]
67920
+ };
67876
67921
  }
67877
67922
  async prepare() {
67878
67923
  }
@@ -67898,6 +67943,7 @@ var ProcessRuntime = class {
67898
67943
  exec = createAgentExec({
67899
67944
  workspace,
67900
67945
  level: spec.access,
67946
+ reach: this.#contained ? "workspace" : "machine",
67901
67947
  pty: await loadPty(),
67902
67948
  extraTools: [about]
67903
67949
  });
@@ -67958,16 +68004,13 @@ var ProcessRuntime = class {
67958
68004
  };
67959
68005
 
67960
68006
  // src/handlers.ts
67961
- var VERSION2 = "0.0.17";
68007
+ var VERSION2 = "0.0.19";
67962
68008
  function createHandlers(computers) {
67963
68009
  return {
67964
- getInfo: async () => ({
67965
- version: VERSION2,
67966
- isolation: computers.isolation,
67967
- isolationsAvailable: await computers.available()
67968
- }),
68010
+ getInfo: async () => ({ version: VERSION2, ...await computers.offers() }),
67969
68011
  listComputers: async ({ state }) => computers.list(state),
67970
68012
  createComputer: (input2) => computers.create(input2),
68013
+ attachComputer: (input2) => computers.attach(input2),
67971
68014
  getComputer: async ({ computerID }) => computers.get(computerID),
67972
68015
  killComputer: async ({ computerID }) => {
67973
68016
  await computers.kill(computerID);
@@ -68039,9 +68082,9 @@ async function serveInside(options) {
68039
68082
  server.on("upgrade", (request, socket, head) => {
68040
68083
  socket.on("error", () => socket.destroy());
68041
68084
  const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
68042
- const url3 = new URL(request.url ?? "/", "http://localhost");
68043
- const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3.pathname)?.[1];
68044
- const mode = url3.searchParams.get("mode");
68085
+ const url3 = URL.parse(request.url ?? "/", "http://localhost");
68086
+ const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3?.pathname ?? "")?.[1];
68087
+ const mode = url3?.searchParams.get("mode");
68045
68088
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
68046
68089
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
68047
68090
  running2.then((computer) => computer.openLive(surface, mode, request, socket, head)).catch(() => socket.destroy());
@@ -68058,6 +68101,7 @@ async function serveInside(options) {
68058
68101
  var run2 = promisify2(execFile2);
68059
68102
  var START_MS2 = 6e4;
68060
68103
  var STOP_SECONDS = "5";
68104
+ var NO_DOCKER = "A container needs Docker, which isn't answering on this machine.";
68061
68105
  var NOT_FORWARDED = /* @__PURE__ */ new Set([
68062
68106
  "authorization",
68063
68107
  "connection",
@@ -68104,14 +68148,22 @@ var ContainerRuntime = class {
68104
68148
  return false;
68105
68149
  }
68106
68150
  }
68107
- // Asked each time the service says what it can make, so it doesn't wait on a Docker that hangs.
68108
- available() {
68109
- return this.#answers(1e3);
68151
+ // Docker is asked for a second each time the service says what it offers, so that doesn't wait
68152
+ // on a Docker that hangs. Its image has its own browser.
68153
+ async offer() {
68154
+ return {
68155
+ ...await this.#answers(1e3) ? { ready: true } : { ready: false, reason: NO_DOCKER },
68156
+ capabilities: [
68157
+ { capability: "about", ready: true },
68158
+ { capability: "terminal", ready: true },
68159
+ { capability: "files", ready: true }
68160
+ ],
68161
+ browsers: [{ kind: "headless", ready: true }]
68162
+ };
68110
68163
  }
68111
68164
  /** Docker, or the refusal of what can't be done without it. */
68112
68165
  async #docker() {
68113
- if (!await this.#answers(1e4))
68114
- throw new ApiError(409, "A container needs Docker, which isn't answering on this machine.");
68166
+ if (!await this.#answers(1e4)) throw new ApiError(409, NO_DOCKER);
68115
68167
  }
68116
68168
  async prepare() {
68117
68169
  await this.#docker();
@@ -68374,8 +68426,8 @@ async function running(data, said = recorded(data)) {
68374
68426
  signal: AbortSignal.timeout(3e3)
68375
68427
  });
68376
68428
  if (!answer.ok) return void 0;
68377
- const { version: version3, isolation } = await answer.json();
68378
- return { url: said.url, pid: said.pid, version: version3, isolation };
68429
+ const { version: version3 } = await answer.json();
68430
+ return { url: said.url, pid: said.pid, version: version3 };
68379
68431
  } catch {
68380
68432
  return void 0;
68381
68433
  }
@@ -68911,6 +68963,19 @@ var ComputerError = class extends Error {
68911
68963
  }
68912
68964
  };
68913
68965
  var seconds = (ms) => Math.max(1, Math.ceil(ms / 1e3));
68966
+ function requestOf(opts) {
68967
+ const { access, browser, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
68968
+ return {
68969
+ connection: { ...apiKey ? { apiKey } : {}, ...apiUrl ? { apiUrl } : {} },
68970
+ body: {
68971
+ access,
68972
+ browser: browser ?? false,
68973
+ ...name === void 0 ? {} : { name },
68974
+ ...timeoutMs === void 0 ? {} : { timeout: seconds(timeoutMs) },
68975
+ ...metadata === void 0 ? {} : { metadata }
68976
+ }
68977
+ };
68978
+ }
68914
68979
  function apiOf(opts = {}) {
68915
68980
  const apiKey = opts.apiKey ?? process.env.AGENTCOMPUTER_API_KEY;
68916
68981
  if (!apiKey) throw new ComputerError(401, "Pass apiKey, or set AGENTCOMPUTER_API_KEY.");
@@ -69110,23 +69175,30 @@ var Computer = class _Computer {
69110
69175
  this.files = new Files(this.#computer);
69111
69176
  }
69112
69177
  static async create(opts) {
69113
- const { access, browser, isolation, workspace, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
69114
- const connection = { ...apiKey ? { apiKey } : {}, ...apiUrl ? { apiUrl } : {} };
69178
+ const { isolation, workspace, copyFrom } = opts;
69179
+ const { connection, body } = requestOf(opts);
69115
69180
  const created = answered(
69116
69181
  await apiOf(connection).POST("/v1/computers", {
69117
69182
  body: {
69118
- access,
69119
- browser: browser ?? false,
69183
+ ...body,
69120
69184
  ...isolation === void 0 ? {} : { isolation },
69121
69185
  ...workspace === void 0 ? {} : { workspace },
69122
- ...name === void 0 ? {} : { name },
69123
- ...timeoutMs === void 0 ? {} : { timeout: seconds(timeoutMs) },
69124
- ...metadata === void 0 ? {} : { metadata }
69186
+ ...copyFrom === void 0 ? {} : { copyFrom }
69125
69187
  }
69126
69188
  })
69127
69189
  );
69128
69190
  return new _Computer(connection, created, created.accessToken);
69129
69191
  }
69192
+ /** This computer, attached whole. Killing it detaches it and removes nothing of the machine's. */
69193
+ static async attach(opts) {
69194
+ const { connection, body } = requestOf(opts);
69195
+ const attached = answered(
69196
+ await apiOf(connection).POST("/v1/computers/attach", {
69197
+ body: { ...body, workspace: opts.workspace }
69198
+ })
69199
+ );
69200
+ return new _Computer(connection, attached, attached.accessToken);
69201
+ }
69130
69202
  /** A computer made earlier. Its token was shown when it was made, and only then. */
69131
69203
  static async connect(computerID, opts) {
69132
69204
  const { accessToken, ...connection } = opts;
@@ -75859,13 +75931,13 @@ var require_resolve3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
75859
75931
  }
75860
75932
  return count;
75861
75933
  }
75862
- function getFullPath(resolver, id = "", normalize) {
75934
+ function getFullPath(resolver2, id = "", normalize) {
75863
75935
  if (normalize !== false) id = normalizeId(id);
75864
- return _getFullPath(resolver, resolver.parse(id));
75936
+ return _getFullPath(resolver2, resolver2.parse(id));
75865
75937
  }
75866
75938
  exports.getFullPath = getFullPath;
75867
- function _getFullPath(resolver, p) {
75868
- return resolver.serialize(p).split("#")[0] + "#";
75939
+ function _getFullPath(resolver2, p) {
75940
+ return resolver2.serialize(p).split("#")[0] + "#";
75869
75941
  }
75870
75942
  exports._getFullPath = _getFullPath;
75871
75943
  const TRAILING_SLASH_HASH = /#\/?$/;
@@ -75873,9 +75945,9 @@ var require_resolve3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
75873
75945
  return id ? id.replace(TRAILING_SLASH_HASH, "") : "";
75874
75946
  }
75875
75947
  exports.normalizeId = normalizeId;
75876
- function resolveUrl(resolver, baseId, id) {
75948
+ function resolveUrl(resolver2, baseId, id) {
75877
75949
  id = normalizeId(id);
75878
- return resolver.resolve(baseId, id);
75950
+ return resolver2.resolve(baseId, id);
75879
75951
  }
75880
75952
  exports.resolveUrl = resolveUrl;
75881
75953
  const ANCHOR = /^[a-z_][-a-z0-9._]*$/i;
@@ -76357,10 +76429,10 @@ var require_ref_error3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76357
76429
  Object.defineProperty(exports, "__esModule", { value: true });
76358
76430
  const resolve_1 = require_resolve3();
76359
76431
  var MissingRefError = class extends Error {
76360
- constructor(resolver, baseId, ref, msg) {
76432
+ constructor(resolver2, baseId, ref, msg) {
76361
76433
  super(msg || `can't resolve reference ${ref} from id ${baseId}`);
76362
- this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref);
76363
- this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver, this.missingRef));
76434
+ this.missingRef = (0, resolve_1.resolveUrl)(resolver2, baseId, ref);
76435
+ this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver2, this.missingRef));
76364
76436
  }
76365
76437
  };
76366
76438
  exports.default = MissingRefError;
@@ -76604,10 +76676,10 @@ var require_utils3 = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
76604
76676
  const HEX_DIGITS = "0123456789ABCDEF";
76605
76677
  for (let i = 0; i < 256; i++) BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
76606
76678
  }
76607
- function percentEncodeNonAscii(cp) {
76608
- if (cp < 2048) return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
76609
- if (cp < 65536) return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
76610
- return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
76679
+ function percentEncodeNonAscii(cp2) {
76680
+ if (cp2 < 2048) return BYTE_HEX[192 | cp2 >> 6] + BYTE_HEX[128 | cp2 & 63];
76681
+ if (cp2 < 65536) return BYTE_HEX[224 | cp2 >> 12] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
76682
+ return BYTE_HEX[240 | cp2 >> 18] + BYTE_HEX[128 | cp2 >> 12 & 63] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
76611
76683
  }
76612
76684
  function stringArrayToHexStripped(input2) {
76613
76685
  let acc = "";
@@ -76947,8 +77019,8 @@ var require_utils3 = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
76947
77019
  function encodeFragment(input2) {
76948
77020
  return encodeComponent(input2, isQueryFragmentCharacter);
76949
77021
  }
76950
- function isEscapeSafe(cp) {
76951
- return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
77022
+ function isEscapeSafe(cp2) {
77023
+ return cp2 >= 48 && cp2 <= 57 || cp2 >= 65 && cp2 <= 90 || cp2 >= 97 && cp2 <= 122 || cp2 === 42 || cp2 === 43 || cp2 === 45 || cp2 === 46 || cp2 === 47 || cp2 === 64 || cp2 === 95;
76952
77024
  }
76953
77025
  function normalizeQueryFragmentEncoding(input2) {
76954
77026
  let output2 = "";
@@ -83100,8 +83172,8 @@ var WebStandardStreamableHTTPServerTransport2 = class {
83100
83172
  return timer;
83101
83173
  }
83102
83174
  /** Sets the scope challenge resolver for parsed JSON-RPC requests. */
83103
- setScopeChallengeResolver(resolver) {
83104
- this._scopeChallengeResolver = resolver;
83175
+ setScopeChallengeResolver(resolver2) {
83176
+ this._scopeChallengeResolver = resolver2;
83105
83177
  }
83106
83178
  /**
83107
83179
  * Starts the transport. This is required by the {@linkcode Transport} interface but is a no-op
@@ -85001,8 +85073,8 @@ var NodeStreamableHTTPServerTransport2 = class {
85001
85073
  this._webStandardTransport.setSupportedProtocolVersions(versions);
85002
85074
  }
85003
85075
  /** Sets the scope challenge resolver used by the wrapped Web Standard transport. */
85004
- setScopeChallengeResolver(resolver) {
85005
- this._webStandardTransport.setScopeChallengeResolver(resolver);
85076
+ setScopeChallengeResolver(resolver2) {
85077
+ this._webStandardTransport.setScopeChallengeResolver(resolver2);
85006
85078
  }
85007
85079
  /**
85008
85080
  * Handles an incoming HTTP request, whether `GET` or `POST`.
@@ -85042,8 +85114,19 @@ var NodeStreamableHTTPServerTransport2 = class {
85042
85114
 
85043
85115
  // src/computers.ts
85044
85116
  import { randomBytes as randomBytes5 } from "node:crypto";
85045
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
85046
- import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
85117
+ import {
85118
+ appendFileSync,
85119
+ chmodSync as chmodSync4,
85120
+ existsSync as existsSync2,
85121
+ mkdirSync as mkdirSync3,
85122
+ readdirSync as readdirSync2,
85123
+ readFileSync as readFileSync5,
85124
+ realpathSync as realpathSync2,
85125
+ rmSync as rmSync5,
85126
+ statSync as statSync4
85127
+ } from "node:fs";
85128
+ import { cp, lstat as lstat3 } from "node:fs/promises";
85129
+ import { isAbsolute as isAbsolute2, join as join10, resolve as resolve4 } from "node:path";
85047
85130
  var READ = ["about", "read", "list", "search"];
85048
85131
  var FILES = [...READ, "edit"];
85049
85132
  var TOOLS2 = {
@@ -85056,32 +85139,59 @@ var MAX_TIMER_S = 2e6;
85056
85139
  var TICKET_MS = 6e4;
85057
85140
  var expiry = (seconds2) => new Date(Date.now() + seconds2 * 1e3).toISOString();
85058
85141
  var messageOf2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
85142
+ var noLongerMade = (stored) => "A computer with no isolation is no longer made. " + (stored.workspace ? `Its folder is kept at ${stored.workspace}. Attach this computer with that folder as its work folder, or make a container whose workspace starts as a copy of it.` : "Attach this computer, or make a container.");
85143
+ function removeFolder(path) {
85144
+ try {
85145
+ rmSync5(path, { recursive: true, force: true });
85146
+ } catch {
85147
+ const open5 = (at) => {
85148
+ chmodSync4(at, 448);
85149
+ for (const entry of readdirSync2(at, { withFileTypes: true }))
85150
+ if (entry.isDirectory()) open5(join10(at, entry.name));
85151
+ };
85152
+ open5(path);
85153
+ rmSync5(path, { recursive: true, force: true });
85154
+ }
85155
+ }
85156
+ function folderAt(path) {
85157
+ let folder = false;
85158
+ try {
85159
+ folder = isAbsolute2(path) && statSync4(path).isDirectory();
85160
+ } catch {
85161
+ }
85162
+ if (!folder) throw new ApiError(400, `There's no folder at ${path}.`);
85163
+ return resolve4(path);
85164
+ }
85059
85165
  var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf2(error62));
85060
85166
  var Computers = class {
85061
85167
  #data;
85062
85168
  #base;
85169
+ /** This computer's way of running, as `none`, and each way of making one. */
85063
85170
  #runtimes;
85064
- /** The isolation of a computer made without saying which. */
85065
- isolation;
85171
+ /** Why this computer isn't offered here, where it isn't. */
85172
+ #notHere;
85066
85173
  #stored = /* @__PURE__ */ new Map();
85067
85174
  #live = /* @__PURE__ */ new Map();
85068
85175
  #tickets = /* @__PURE__ */ new Map();
85069
85176
  #file;
85070
- constructor(data, base, runtimes, isolation) {
85177
+ constructor(data, base, runtimes, notHere) {
85071
85178
  this.#data = data;
85072
85179
  this.#base = base;
85073
85180
  this.#runtimes = runtimes;
85074
- this.isolation = isolation;
85181
+ this.#notHere = notHere;
85075
85182
  this.#file = join10(data, "computers.json");
85076
85183
  if (existsSync2(this.#file))
85077
85184
  for (const entry of JSON.parse(readFileSync5(this.#file, "utf8")))
85078
- this.#stored.set(entry.id, { ...entry, isolation: entry.isolation ?? isolation });
85079
- }
85080
- /** The isolations a computer can be made in now. */
85081
- async available() {
85082
- const all = Object.keys(this.#runtimes);
85083
- const can = await Promise.all(all.map((one) => this.#runtimes[one].available()));
85084
- return all.filter((_, index) => can[index]);
85185
+ this.#stored.set(entry.id, { ...entry, isolation: entry.isolation ?? "none" });
85186
+ }
85187
+ /** What it offers: this computer, and each kind it makes, with whether it can be now. */
85188
+ async offers() {
85189
+ const offered = await this.#runtimes.none.offer();
85190
+ const thisComputer = this.#notHere ? { ...offered, ready: false, reason: this.#notHere } : offered;
85191
+ const made = await Promise.all(
85192
+ Object.keys(this.#runtimes).filter((isolation) => isolation !== "none").map(async (isolation) => ({ isolation, ...await this.#runtimes[isolation].offer() }))
85193
+ );
85194
+ return { thisComputer, made };
85085
85195
  }
85086
85196
  /** Bring back what was running when the service last stopped. */
85087
85197
  async resume() {
@@ -85104,20 +85214,34 @@ var Computers = class {
85104
85214
  this.#save();
85105
85215
  }
85106
85216
  async create(input2) {
85217
+ if (input2.isolation === "none")
85218
+ throw new ApiError(
85219
+ 400,
85220
+ "A computer with no isolation is no longer made. Attach this computer, or make a container."
85221
+ );
85222
+ return this.#make(input2, input2.isolation ?? "container");
85223
+ }
85224
+ /** This computer, attached with its work folder. Nothing is made for it. */
85225
+ attach(input2) {
85226
+ return this.#make(input2, "none");
85227
+ }
85228
+ async #make(input2, isolation) {
85107
85229
  const { access, name, timeout, metadata } = input2;
85108
85230
  const browser = input2.browser === true;
85109
85231
  if (access === "none" && !browser)
85110
85232
  throw new ApiError(400, "With no files, no commands and no browser, there's no computer.");
85111
- let workspace;
85112
- if (input2.workspace !== void 0) {
85113
- let folder = false;
85114
- try {
85115
- folder = isAbsolute(input2.workspace) && statSync4(input2.workspace).isDirectory();
85116
- } catch {
85117
- }
85118
- if (!folder) throw new ApiError(400, `There's no folder at ${input2.workspace}.`);
85119
- workspace = resolve4(input2.workspace);
85120
- }
85233
+ if (input2.copyFrom !== void 0 && input2.workspace !== void 0)
85234
+ throw new ApiError(
85235
+ 400,
85236
+ "Give workspace or copyFrom, not both: copyFrom fills a workspace the service makes."
85237
+ );
85238
+ if (input2.copyFrom !== void 0 && access === "none")
85239
+ throw new ApiError(
85240
+ 400,
85241
+ "With access none there is no workspace. Give access read, files or terminal, or leave out copyFrom."
85242
+ );
85243
+ const workspace = input2.workspace === void 0 ? void 0 : folderAt(input2.workspace);
85244
+ const from = input2.copyFrom === void 0 ? void 0 : realpathSync2(folderAt(input2.copyFrom));
85121
85245
  const id = `c_${randomBytes5(9).toString("hex")}`;
85122
85246
  const token = newToken();
85123
85247
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -85125,7 +85249,8 @@ var Computers = class {
85125
85249
  id,
85126
85250
  name: name ?? id,
85127
85251
  access,
85128
- isolation: input2.isolation ?? this.isolation,
85252
+ isolation,
85253
+ ...isolation === "none" ? { attached: true } : {},
85129
85254
  browser,
85130
85255
  ...workspace ? { workspace } : {},
85131
85256
  metadata: metadata ?? {},
@@ -85136,8 +85261,21 @@ var Computers = class {
85136
85261
  ...timeout === void 0 ? {} : { endAt: expiry(timeout) }
85137
85262
  };
85138
85263
  if (!workspace && access !== "none") {
85139
- stored.workspace = join10(this.#folder(id), "workspace");
85140
- mkdirSync3(stored.workspace, { recursive: true, mode: 448 });
85264
+ const own3 = join10(this.#folder(id), "workspace");
85265
+ mkdirSync3(own3, { recursive: true, mode: 448 });
85266
+ stored.workspace = own3;
85267
+ if (from)
85268
+ await cp(from, own3, {
85269
+ recursive: true,
85270
+ verbatimSymlinks: true,
85271
+ filter: async (path) => {
85272
+ const found = await lstat3(path);
85273
+ return found.isFile() || found.isDirectory() || found.isSymbolicLink();
85274
+ }
85275
+ }).catch((error62) => {
85276
+ removeFolder(this.#folder(id));
85277
+ throw refusalOf(error62);
85278
+ });
85141
85279
  }
85142
85280
  this.#stored.set(id, stored);
85143
85281
  try {
@@ -85147,7 +85285,10 @@ var Computers = class {
85147
85285
  this.#stored.delete(id);
85148
85286
  await this.#runtimes[stored.isolation].remove(id).catch(() => {
85149
85287
  });
85150
- rmSync5(this.#folder(id), { recursive: true, force: true });
85288
+ try {
85289
+ removeFolder(this.#folder(id));
85290
+ } catch {
85291
+ }
85151
85292
  throw refusalOf(error62);
85152
85293
  }
85153
85294
  this.#save();
@@ -85221,7 +85362,12 @@ var Computers = class {
85221
85362
  }
85222
85363
  this.#stored.delete(id);
85223
85364
  this.#save();
85224
- rmSync5(this.#folder(id), { recursive: true, force: true });
85365
+ const folder = this.#folder(id);
85366
+ if (stored.isolation === "none" && !stored.attached && existsSync2(join10(folder, "workspace")))
85367
+ for (const entry of readdirSync2(folder)) {
85368
+ if (entry !== "workspace") removeFolder(join10(folder, entry));
85369
+ }
85370
+ else removeFolder(folder);
85225
85371
  }
85226
85372
  logs(id) {
85227
85373
  this.#found(id);
@@ -85322,6 +85468,7 @@ var Computers = class {
85322
85468
  state: stored.state === "running" && down2 ? "failed" : stored.state,
85323
85469
  access: stored.access,
85324
85470
  isolation: stored.isolation,
85471
+ attached: stored.attached === true,
85325
85472
  version: stored.version ?? null,
85326
85473
  browser: stored.browser,
85327
85474
  workspace: stored.workspace ?? null,
@@ -85337,6 +85484,9 @@ var Computers = class {
85337
85484
  async #start(stored) {
85338
85485
  const live = {};
85339
85486
  this.#live.set(stored.id, live);
85487
+ if (stored.isolation === "none" && !stored.attached)
85488
+ throw new ApiError(409, noLongerMade(stored));
85489
+ if (stored.attached && this.#notHere) throw new ApiError(409, this.#notHere);
85340
85490
  stored.startedAt = (/* @__PURE__ */ new Date()).toISOString();
85341
85491
  const runtime = this.#runtimes[stored.isolation];
85342
85492
  await runtime.prepare();
@@ -85396,7 +85546,7 @@ var OPENAPI = {
85396
85546
  "openapi": "3.1.0",
85397
85547
  "info": {
85398
85548
  "title": "AgentComputer",
85399
- "version": "0.0.17",
85549
+ "version": "0.0.19",
85400
85550
  "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.",
85401
85551
  "license": {
85402
85552
  "name": "Apache-2.0",
@@ -85419,7 +85569,7 @@ var OPENAPI = {
85419
85569
  "get": {
85420
85570
  "operationId": "getInfo",
85421
85571
  "x-mcp-tool": "service_info",
85422
- "summary": "What this service is and how it isolates its computers.",
85572
+ "summary": "What this service offers: this computer, the kinds of computer it can make, and the browsers that can go with each, every one ready or not and why.",
85423
85573
  "responses": {
85424
85574
  "200": {
85425
85575
  "description": "The service.",
@@ -85501,6 +85651,47 @@ var OPENAPI = {
85501
85651
  },
85502
85652
  "401": {
85503
85653
  "$ref": "#/components/responses/Unauthorized"
85654
+ },
85655
+ "409": {
85656
+ "$ref": "#/components/responses/Error"
85657
+ }
85658
+ }
85659
+ }
85660
+ },
85661
+ "/v1/computers/attach": {
85662
+ "post": {
85663
+ "operationId": "attachComputer",
85664
+ "x-mcp-tool": "computer_attach",
85665
+ "summary": "Attach this computer, the machine the service runs on, whole: its files and commands reach all of it, as whoever started the service. Each call attaches it as a computer of its own, with its own id and token. Killing one detaches it and removes nothing of the machine's. Refused with 409 by a service that other machines can reach, which doesn't offer it.",
85666
+ "requestBody": {
85667
+ "required": true,
85668
+ "content": {
85669
+ "application/json": {
85670
+ "schema": {
85671
+ "$ref": "#/components/schemas/AttachComputer"
85672
+ }
85673
+ }
85674
+ }
85675
+ },
85676
+ "responses": {
85677
+ "201": {
85678
+ "description": "It, with its access token, shown only now.",
85679
+ "content": {
85680
+ "application/json": {
85681
+ "schema": {
85682
+ "$ref": "#/components/schemas/CreatedComputer"
85683
+ }
85684
+ }
85685
+ }
85686
+ },
85687
+ "400": {
85688
+ "$ref": "#/components/responses/Error"
85689
+ },
85690
+ "401": {
85691
+ "$ref": "#/components/responses/Unauthorized"
85692
+ },
85693
+ "409": {
85694
+ "$ref": "#/components/responses/Error"
85504
85695
  }
85505
85696
  }
85506
85697
  }
@@ -85674,6 +85865,9 @@ var OPENAPI = {
85674
85865
  },
85675
85866
  "404": {
85676
85867
  "$ref": "#/components/responses/Error"
85868
+ },
85869
+ "409": {
85870
+ "$ref": "#/components/responses/Error"
85677
85871
  }
85678
85872
  }
85679
85873
  }
@@ -85898,9 +86092,48 @@ var OPENAPI = {
85898
86092
  "maxLength": 4096,
85899
86093
  "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."
85900
86094
  },
86095
+ "copyFrom": {
86096
+ "type": "string",
86097
+ "maxLength": 4096,
86098
+ "description": "The full path of a folder on the service's machine. The workspace the service makes starts with a copy of what is in it, taken once when the computer is made; the folder itself is never mounted or changed. Links are copied as they are, and sockets, pipes and devices are left out. Use it instead of workspace; it needs access other than none."
86099
+ },
85901
86100
  "isolation": {
85902
86101
  "$ref": "#/components/schemas/Isolation",
85903
- "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."
86102
+ "description": "How it is isolated, which it keeps for as long as it exists: container, the default. One with no isolation is no longer made, and asking for one is refused: attach this computer instead."
86103
+ },
86104
+ "timeout": {
86105
+ "$ref": "#/components/schemas/Timeout"
86106
+ },
86107
+ "metadata": {
86108
+ "$ref": "#/components/schemas/Metadata"
86109
+ }
86110
+ }
86111
+ },
86112
+ "AttachComputer": {
86113
+ "type": "object",
86114
+ "additionalProperties": false,
86115
+ "required": [
86116
+ "access",
86117
+ "workspace"
86118
+ ],
86119
+ "properties": {
86120
+ "name": {
86121
+ "type": "string",
86122
+ "minLength": 1,
86123
+ "maxLength": 128
86124
+ },
86125
+ "access": {
86126
+ "$ref": "#/components/schemas/Access"
86127
+ },
86128
+ "browser": {
86129
+ "type": "boolean",
86130
+ "default": false,
86131
+ "description": "Whether it has a headless browser of its own. With access none, it must."
86132
+ },
86133
+ "workspace": {
86134
+ "type": "string",
86135
+ "maxLength": 4096,
86136
+ "description": "Its work folder: the full path of a folder that exists. Commands and relative paths start there and its agent is told to work there, but nothing keeps its files inside it. Detaching never removes it."
85904
86137
  },
85905
86138
  "timeout": {
85906
86139
  "$ref": "#/components/schemas/Timeout"
@@ -85916,7 +86149,7 @@ var OPENAPI = {
85916
86149
  "none",
85917
86150
  "container"
85918
86151
  ],
85919
- "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
86152
+ "description": "none: processes of whoever started the service, which only this computer has now; a computer made with no isolation before can't start again. container: a container of its own, which needs Docker on the service's machine."
85920
86153
  },
85921
86154
  "Endpoints": {
85922
86155
  "type": "object",
@@ -85950,6 +86183,7 @@ var OPENAPI = {
85950
86183
  "state",
85951
86184
  "access",
85952
86185
  "isolation",
86186
+ "attached",
85953
86187
  "version",
85954
86188
  "browser",
85955
86189
  "workspace",
@@ -85975,12 +86209,16 @@ var OPENAPI = {
85975
86209
  "$ref": "#/components/schemas/Isolation",
85976
86210
  "description": "The isolation it was made in. It is never run in another."
85977
86211
  },
86212
+ "attached": {
86213
+ "type": "boolean",
86214
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
86215
+ },
85978
86216
  "version": {
85979
86217
  "type": [
85980
86218
  "string",
85981
86219
  "null"
85982
86220
  ],
85983
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
86221
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
85984
86222
  },
85985
86223
  "browser": {
85986
86224
  "type": "boolean"
@@ -86764,7 +87002,7 @@ var OPENAPI = {
86764
87002
  "computer",
86765
87003
  "machine"
86766
87004
  ],
86767
- "description": "Whose numbers these are. computer: this computer's own, which a container's are. machine: the whole machine's, which a computer with no isolation shares with whatever else runs there, so every computer of that service reads the same; and a container's where it can't read its own."
87005
+ "description": "Whose numbers these are. computer: the computer's own, as a container's are. machine: the whole machine's, as this computer's are, which it shares with whatever else runs there; and a container's where it can't read its own."
86768
87006
  },
86769
87007
  "cpuUsedPct": {
86770
87008
  "type": "number",
@@ -87136,27 +87374,143 @@ var OPENAPI = {
87136
87374
  }
87137
87375
  }
87138
87376
  },
87377
+ "Readiness": {
87378
+ "type": "object",
87379
+ "required": [
87380
+ "ready"
87381
+ ],
87382
+ "properties": {
87383
+ "ready": {
87384
+ "type": "boolean"
87385
+ },
87386
+ "reason": {
87387
+ "type": "string",
87388
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87389
+ }
87390
+ }
87391
+ },
87392
+ "Capability": {
87393
+ "type": "string",
87394
+ "enum": [
87395
+ "about",
87396
+ "terminal",
87397
+ "files",
87398
+ "screen"
87399
+ ],
87400
+ "description": "What a computer offers its agent and the person: what it is, its terminals, its files and its screen."
87401
+ },
87402
+ "CapabilityState": {
87403
+ "allOf": [
87404
+ {
87405
+ "$ref": "#/components/schemas/Readiness"
87406
+ },
87407
+ {
87408
+ "type": "object",
87409
+ "required": [
87410
+ "capability"
87411
+ ],
87412
+ "properties": {
87413
+ "capability": {
87414
+ "$ref": "#/components/schemas/Capability"
87415
+ }
87416
+ }
87417
+ }
87418
+ ]
87419
+ },
87420
+ "BrowserKind": {
87421
+ "type": "string",
87422
+ "enum": [
87423
+ "headless"
87424
+ ],
87425
+ "description": "headless: a headless browser with a profile of its own, beside the computer, which a person watches and controls through its live view."
87426
+ },
87427
+ "BrowserState": {
87428
+ "allOf": [
87429
+ {
87430
+ "$ref": "#/components/schemas/Readiness"
87431
+ },
87432
+ {
87433
+ "type": "object",
87434
+ "required": [
87435
+ "kind"
87436
+ ],
87437
+ "properties": {
87438
+ "kind": {
87439
+ "$ref": "#/components/schemas/BrowserKind"
87440
+ }
87441
+ }
87442
+ }
87443
+ ]
87444
+ },
87445
+ "Offer": {
87446
+ "allOf": [
87447
+ {
87448
+ "$ref": "#/components/schemas/Readiness"
87449
+ },
87450
+ {
87451
+ "type": "object",
87452
+ "required": [
87453
+ "capabilities",
87454
+ "browsers"
87455
+ ],
87456
+ "properties": {
87457
+ "capabilities": {
87458
+ "type": "array",
87459
+ "description": "What such a computer has, each ready or not.",
87460
+ "items": {
87461
+ "$ref": "#/components/schemas/CapabilityState"
87462
+ }
87463
+ },
87464
+ "browsers": {
87465
+ "type": "array",
87466
+ "description": "The browsers that can go with it, each ready or not.",
87467
+ "items": {
87468
+ "$ref": "#/components/schemas/BrowserState"
87469
+ }
87470
+ }
87471
+ }
87472
+ }
87473
+ ]
87474
+ },
87475
+ "MadeKind": {
87476
+ "allOf": [
87477
+ {
87478
+ "$ref": "#/components/schemas/Offer"
87479
+ },
87480
+ {
87481
+ "type": "object",
87482
+ "required": [
87483
+ "isolation"
87484
+ ],
87485
+ "properties": {
87486
+ "isolation": {
87487
+ "$ref": "#/components/schemas/Isolation"
87488
+ }
87489
+ }
87490
+ }
87491
+ ]
87492
+ },
87139
87493
  "ServiceInfo": {
87140
87494
  "type": "object",
87141
87495
  "required": [
87142
87496
  "version",
87143
- "isolation",
87144
- "isolationsAvailable"
87497
+ "thisComputer",
87498
+ "made"
87145
87499
  ],
87146
87500
  "properties": {
87147
87501
  "version": {
87148
87502
  "type": "string"
87149
87503
  },
87150
- "isolation": {
87151
- "$ref": "#/components/schemas/Isolation",
87152
- "description": "The isolation of a computer made without saying which, chosen when the service is started."
87504
+ "thisComputer": {
87505
+ "$ref": "#/components/schemas/Offer",
87506
+ "description": "This computer, the machine the service runs on, attached whole, as whoever started the service. It can be attached while ready: only where the service listens on loopback, with no proxy in front and no public address. Elsewhere it isn't offered, and one attached before is refused at start."
87153
87507
  },
87154
- "isolationsAvailable": {
87508
+ "made": {
87155
87509
  "type": "array",
87510
+ "description": "Each kind of computer it makes, ready while it can be made now. A container is ready while Docker answers on the service's machine: asked for a second here and for ten when one is made, so one that is slow can be not ready here and still be made.",
87156
87511
  "items": {
87157
- "$ref": "#/components/schemas/Isolation"
87158
- },
87159
- "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."
87512
+ "$ref": "#/components/schemas/MadeKind"
87513
+ }
87160
87514
  }
87161
87515
  }
87162
87516
  }
@@ -87169,7 +87523,7 @@ var OPERATIONS = [
87169
87523
  "method": "GET",
87170
87524
  "path": "/v1/info",
87171
87525
  "tool": "service_info",
87172
- "summary": "What this service is and how it isolates its computers.",
87526
+ "summary": "What this service offers: this computer, the kinds of computer it can make, and the browsers that can go with each, every one ready or not and why.",
87173
87527
  "status": 200,
87174
87528
  "hasBody": false,
87175
87529
  "pathParameters": [],
@@ -87184,32 +87538,251 @@ var OPERATIONS = [
87184
87538
  "type": "object",
87185
87539
  "required": [
87186
87540
  "version",
87187
- "isolation",
87188
- "isolationsAvailable"
87541
+ "thisComputer",
87542
+ "made"
87189
87543
  ],
87190
87544
  "properties": {
87191
87545
  "version": {
87192
87546
  "type": "string"
87193
87547
  },
87194
- "isolation": {
87195
- "type": "string",
87196
- "enum": [
87197
- "none",
87198
- "container"
87548
+ "thisComputer": {
87549
+ "allOf": [
87550
+ {
87551
+ "type": "object",
87552
+ "required": [
87553
+ "ready"
87554
+ ],
87555
+ "properties": {
87556
+ "ready": {
87557
+ "type": "boolean"
87558
+ },
87559
+ "reason": {
87560
+ "type": "string",
87561
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87562
+ }
87563
+ }
87564
+ },
87565
+ {
87566
+ "type": "object",
87567
+ "required": [
87568
+ "capabilities",
87569
+ "browsers"
87570
+ ],
87571
+ "properties": {
87572
+ "capabilities": {
87573
+ "type": "array",
87574
+ "description": "What such a computer has, each ready or not.",
87575
+ "items": {
87576
+ "allOf": [
87577
+ {
87578
+ "type": "object",
87579
+ "required": [
87580
+ "ready"
87581
+ ],
87582
+ "properties": {
87583
+ "ready": {
87584
+ "type": "boolean"
87585
+ },
87586
+ "reason": {
87587
+ "type": "string",
87588
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87589
+ }
87590
+ }
87591
+ },
87592
+ {
87593
+ "type": "object",
87594
+ "required": [
87595
+ "capability"
87596
+ ],
87597
+ "properties": {
87598
+ "capability": {
87599
+ "type": "string",
87600
+ "enum": [
87601
+ "about",
87602
+ "terminal",
87603
+ "files",
87604
+ "screen"
87605
+ ],
87606
+ "description": "What a computer offers its agent and the person: what it is, its terminals, its files and its screen."
87607
+ }
87608
+ }
87609
+ }
87610
+ ]
87611
+ }
87612
+ },
87613
+ "browsers": {
87614
+ "type": "array",
87615
+ "description": "The browsers that can go with it, each ready or not.",
87616
+ "items": {
87617
+ "allOf": [
87618
+ {
87619
+ "type": "object",
87620
+ "required": [
87621
+ "ready"
87622
+ ],
87623
+ "properties": {
87624
+ "ready": {
87625
+ "type": "boolean"
87626
+ },
87627
+ "reason": {
87628
+ "type": "string",
87629
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87630
+ }
87631
+ }
87632
+ },
87633
+ {
87634
+ "type": "object",
87635
+ "required": [
87636
+ "kind"
87637
+ ],
87638
+ "properties": {
87639
+ "kind": {
87640
+ "type": "string",
87641
+ "enum": [
87642
+ "headless"
87643
+ ],
87644
+ "description": "headless: a headless browser with a profile of its own, beside the computer, which a person watches and controls through its live view."
87645
+ }
87646
+ }
87647
+ }
87648
+ ]
87649
+ }
87650
+ }
87651
+ }
87652
+ }
87199
87653
  ],
87200
- "description": "The isolation of a computer made without saying which, chosen when the service is started."
87654
+ "description": "This computer, the machine the service runs on, attached whole, as whoever started the service. It can be attached while ready: only where the service listens on loopback, with no proxy in front and no public address. Elsewhere it isn't offered, and one attached before is refused at start."
87201
87655
  },
87202
- "isolationsAvailable": {
87656
+ "made": {
87203
87657
  "type": "array",
87658
+ "description": "Each kind of computer it makes, ready while it can be made now. A container is ready while Docker answers on the service's machine: asked for a second here and for ten when one is made, so one that is slow can be not ready here and still be made.",
87204
87659
  "items": {
87205
- "type": "string",
87206
- "enum": [
87207
- "none",
87208
- "container"
87209
- ],
87210
- "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
87211
- },
87212
- "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."
87660
+ "allOf": [
87661
+ {
87662
+ "allOf": [
87663
+ {
87664
+ "type": "object",
87665
+ "required": [
87666
+ "ready"
87667
+ ],
87668
+ "properties": {
87669
+ "ready": {
87670
+ "type": "boolean"
87671
+ },
87672
+ "reason": {
87673
+ "type": "string",
87674
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87675
+ }
87676
+ }
87677
+ },
87678
+ {
87679
+ "type": "object",
87680
+ "required": [
87681
+ "capabilities",
87682
+ "browsers"
87683
+ ],
87684
+ "properties": {
87685
+ "capabilities": {
87686
+ "type": "array",
87687
+ "description": "What such a computer has, each ready or not.",
87688
+ "items": {
87689
+ "allOf": [
87690
+ {
87691
+ "type": "object",
87692
+ "required": [
87693
+ "ready"
87694
+ ],
87695
+ "properties": {
87696
+ "ready": {
87697
+ "type": "boolean"
87698
+ },
87699
+ "reason": {
87700
+ "type": "string",
87701
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87702
+ }
87703
+ }
87704
+ },
87705
+ {
87706
+ "type": "object",
87707
+ "required": [
87708
+ "capability"
87709
+ ],
87710
+ "properties": {
87711
+ "capability": {
87712
+ "type": "string",
87713
+ "enum": [
87714
+ "about",
87715
+ "terminal",
87716
+ "files",
87717
+ "screen"
87718
+ ],
87719
+ "description": "What a computer offers its agent and the person: what it is, its terminals, its files and its screen."
87720
+ }
87721
+ }
87722
+ }
87723
+ ]
87724
+ }
87725
+ },
87726
+ "browsers": {
87727
+ "type": "array",
87728
+ "description": "The browsers that can go with it, each ready or not.",
87729
+ "items": {
87730
+ "allOf": [
87731
+ {
87732
+ "type": "object",
87733
+ "required": [
87734
+ "ready"
87735
+ ],
87736
+ "properties": {
87737
+ "ready": {
87738
+ "type": "boolean"
87739
+ },
87740
+ "reason": {
87741
+ "type": "string",
87742
+ "description": "Why it isn't ready, in a line a person reads. Only when it isn't."
87743
+ }
87744
+ }
87745
+ },
87746
+ {
87747
+ "type": "object",
87748
+ "required": [
87749
+ "kind"
87750
+ ],
87751
+ "properties": {
87752
+ "kind": {
87753
+ "type": "string",
87754
+ "enum": [
87755
+ "headless"
87756
+ ],
87757
+ "description": "headless: a headless browser with a profile of its own, beside the computer, which a person watches and controls through its live view."
87758
+ }
87759
+ }
87760
+ }
87761
+ ]
87762
+ }
87763
+ }
87764
+ }
87765
+ }
87766
+ ]
87767
+ },
87768
+ {
87769
+ "type": "object",
87770
+ "required": [
87771
+ "isolation"
87772
+ ],
87773
+ "properties": {
87774
+ "isolation": {
87775
+ "type": "string",
87776
+ "enum": [
87777
+ "none",
87778
+ "container"
87779
+ ],
87780
+ "description": "none: processes of whoever started the service, which only this computer has now; a computer made with no isolation before can't start again. container: a container of its own, which needs Docker on the service's machine."
87781
+ }
87782
+ }
87783
+ }
87784
+ ]
87785
+ }
87213
87786
  }
87214
87787
  }
87215
87788
  },
@@ -87253,6 +87826,7 @@ var OPERATIONS = [
87253
87826
  "state",
87254
87827
  "access",
87255
87828
  "isolation",
87829
+ "attached",
87256
87830
  "version",
87257
87831
  "browser",
87258
87832
  "workspace",
@@ -87295,12 +87869,16 @@ var OPERATIONS = [
87295
87869
  ],
87296
87870
  "description": "The isolation it was made in. It is never run in another."
87297
87871
  },
87872
+ "attached": {
87873
+ "type": "boolean",
87874
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
87875
+ },
87298
87876
  "version": {
87299
87877
  "type": [
87300
87878
  "string",
87301
87879
  "null"
87302
87880
  ],
87303
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87881
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87304
87882
  },
87305
87883
  "browser": {
87306
87884
  "type": "boolean"
@@ -87401,13 +87979,18 @@ var OPERATIONS = [
87401
87979
  "maxLength": 4096,
87402
87980
  "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."
87403
87981
  },
87982
+ "copyFrom": {
87983
+ "type": "string",
87984
+ "maxLength": 4096,
87985
+ "description": "The full path of a folder on the service's machine. The workspace the service makes starts with a copy of what is in it, taken once when the computer is made; the folder itself is never mounted or changed. Links are copied as they are, and sockets, pipes and devices are left out. Use it instead of workspace; it needs access other than none."
87986
+ },
87404
87987
  "isolation": {
87405
87988
  "type": "string",
87406
87989
  "enum": [
87407
87990
  "none",
87408
87991
  "container"
87409
87992
  ],
87410
- "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."
87993
+ "description": "How it is isolated, which it keeps for as long as it exists: container, the default. One with no isolation is no longer made, and asking for one is refused: attach this computer instead."
87411
87994
  },
87412
87995
  "timeout": {
87413
87996
  "type": "integer",
@@ -87438,6 +88021,203 @@ var OPERATIONS = [
87438
88021
  "state",
87439
88022
  "access",
87440
88023
  "isolation",
88024
+ "attached",
88025
+ "version",
88026
+ "browser",
88027
+ "workspace",
88028
+ "startedAt",
88029
+ "endAt",
88030
+ "metadata",
88031
+ "endpoints"
88032
+ ],
88033
+ "properties": {
88034
+ "computerID": {
88035
+ "type": "string"
88036
+ },
88037
+ "name": {
88038
+ "type": "string"
88039
+ },
88040
+ "state": {
88041
+ "type": "string",
88042
+ "enum": [
88043
+ "running",
88044
+ "stopped",
88045
+ "failed"
88046
+ ],
88047
+ "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."
88048
+ },
88049
+ "access": {
88050
+ "type": "string",
88051
+ "enum": [
88052
+ "none",
88053
+ "read",
88054
+ "files",
88055
+ "terminal"
88056
+ ],
88057
+ "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."
88058
+ },
88059
+ "isolation": {
88060
+ "type": "string",
88061
+ "enum": [
88062
+ "none",
88063
+ "container"
88064
+ ],
88065
+ "description": "The isolation it was made in. It is never run in another."
88066
+ },
88067
+ "attached": {
88068
+ "type": "boolean",
88069
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
88070
+ },
88071
+ "version": {
88072
+ "type": [
88073
+ "string",
88074
+ "null"
88075
+ ],
88076
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88077
+ },
88078
+ "browser": {
88079
+ "type": "boolean"
88080
+ },
88081
+ "workspace": {
88082
+ "type": [
88083
+ "string",
88084
+ "null"
88085
+ ]
88086
+ },
88087
+ "startedAt": {
88088
+ "type": "string",
88089
+ "format": "date-time",
88090
+ "description": "When it last started."
88091
+ },
88092
+ "endAt": {
88093
+ "type": [
88094
+ "string",
88095
+ "null"
88096
+ ],
88097
+ "format": "date-time",
88098
+ "description": "When it will stop; null if it has no timeout."
88099
+ },
88100
+ "metadata": {
88101
+ "type": "object",
88102
+ "additionalProperties": {
88103
+ "type": "string",
88104
+ "maxLength": 1024
88105
+ },
88106
+ "maxProperties": 64,
88107
+ "description": "Your own labels, kept and returned as given."
88108
+ },
88109
+ "endpoints": {
88110
+ "type": "object",
88111
+ "required": [
88112
+ "computer",
88113
+ "browser"
88114
+ ],
88115
+ "description": "MCP over Streamable HTTP, each taking the computer's access token as a bearer.",
88116
+ "properties": {
88117
+ "computer": {
88118
+ "type": [
88119
+ "string",
88120
+ "null"
88121
+ ],
88122
+ "description": "The terminal and files."
88123
+ },
88124
+ "browser": {
88125
+ "type": [
88126
+ "string",
88127
+ "null"
88128
+ ],
88129
+ "description": "The browser."
88130
+ }
88131
+ }
88132
+ }
88133
+ }
88134
+ },
88135
+ {
88136
+ "type": "object",
88137
+ "required": [
88138
+ "accessToken"
88139
+ ],
88140
+ "properties": {
88141
+ "accessToken": {
88142
+ "type": "string",
88143
+ "description": "Good only for this computer's endpoints. Shown only now."
88144
+ }
88145
+ }
88146
+ }
88147
+ ]
88148
+ },
88149
+ "websocket": false
88150
+ },
88151
+ {
88152
+ "operationId": "attachComputer",
88153
+ "method": "POST",
88154
+ "path": "/v1/computers/attach",
88155
+ "tool": "computer_attach",
88156
+ "summary": "Attach this computer, the machine the service runs on, whole: its files and commands reach all of it, as whoever started the service. Each call attaches it as a computer of its own, with its own id and token. Killing one detaches it and removes nothing of the machine's. Refused with 409 by a service that other machines can reach, which doesn't offer it.",
88157
+ "status": 201,
88158
+ "hasBody": true,
88159
+ "pathParameters": [],
88160
+ "queryParameters": [],
88161
+ "input": {
88162
+ "type": "object",
88163
+ "additionalProperties": false,
88164
+ "properties": {
88165
+ "name": {
88166
+ "type": "string",
88167
+ "minLength": 1,
88168
+ "maxLength": 128
88169
+ },
88170
+ "access": {
88171
+ "type": "string",
88172
+ "enum": [
88173
+ "none",
88174
+ "read",
88175
+ "files",
88176
+ "terminal"
88177
+ ],
88178
+ "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."
88179
+ },
88180
+ "browser": {
88181
+ "type": "boolean",
88182
+ "default": false,
88183
+ "description": "Whether it has a headless browser of its own. With access none, it must."
88184
+ },
88185
+ "workspace": {
88186
+ "type": "string",
88187
+ "maxLength": 4096,
88188
+ "description": "Its work folder: the full path of a folder that exists. Commands and relative paths start there and its agent is told to work there, but nothing keeps its files inside it. Detaching never removes it."
88189
+ },
88190
+ "timeout": {
88191
+ "type": "integer",
88192
+ "minimum": 1,
88193
+ "description": "Seconds from now until the computer stops."
88194
+ },
88195
+ "metadata": {
88196
+ "type": "object",
88197
+ "additionalProperties": {
88198
+ "type": "string",
88199
+ "maxLength": 1024
88200
+ },
88201
+ "maxProperties": 64,
88202
+ "description": "Your own labels, kept and returned as given."
88203
+ }
88204
+ },
88205
+ "required": [
88206
+ "access",
88207
+ "workspace"
88208
+ ]
88209
+ },
88210
+ "output": {
88211
+ "allOf": [
88212
+ {
88213
+ "type": "object",
88214
+ "required": [
88215
+ "computerID",
88216
+ "name",
88217
+ "state",
88218
+ "access",
88219
+ "isolation",
88220
+ "attached",
87441
88221
  "version",
87442
88222
  "browser",
87443
88223
  "workspace",
@@ -87480,12 +88260,16 @@ var OPERATIONS = [
87480
88260
  ],
87481
88261
  "description": "The isolation it was made in. It is never run in another."
87482
88262
  },
88263
+ "attached": {
88264
+ "type": "boolean",
88265
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
88266
+ },
87483
88267
  "version": {
87484
88268
  "type": [
87485
88269
  "string",
87486
88270
  "null"
87487
88271
  ],
87488
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88272
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87489
88273
  },
87490
88274
  "browser": {
87491
88275
  "type": "boolean"
@@ -87596,6 +88380,7 @@ var OPERATIONS = [
87596
88380
  "state",
87597
88381
  "access",
87598
88382
  "isolation",
88383
+ "attached",
87599
88384
  "version",
87600
88385
  "browser",
87601
88386
  "workspace",
@@ -87638,12 +88423,16 @@ var OPERATIONS = [
87638
88423
  ],
87639
88424
  "description": "The isolation it was made in. It is never run in another."
87640
88425
  },
88426
+ "attached": {
88427
+ "type": "boolean",
88428
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
88429
+ },
87641
88430
  "version": {
87642
88431
  "type": [
87643
88432
  "string",
87644
88433
  "null"
87645
88434
  ],
87646
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88435
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87647
88436
  },
87648
88437
  "browser": {
87649
88438
  "type": "boolean"
@@ -87845,6 +88634,7 @@ var OPERATIONS = [
87845
88634
  "state",
87846
88635
  "access",
87847
88636
  "isolation",
88637
+ "attached",
87848
88638
  "version",
87849
88639
  "browser",
87850
88640
  "workspace",
@@ -87887,12 +88677,16 @@ var OPERATIONS = [
87887
88677
  ],
87888
88678
  "description": "The isolation it was made in. It is never run in another."
87889
88679
  },
88680
+ "attached": {
88681
+ "type": "boolean",
88682
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
88683
+ },
87890
88684
  "version": {
87891
88685
  "type": [
87892
88686
  "string",
87893
88687
  "null"
87894
88688
  ],
87895
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88689
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
87896
88690
  },
87897
88691
  "browser": {
87898
88692
  "type": "boolean"
@@ -87987,6 +88781,7 @@ var OPERATIONS = [
87987
88781
  "state",
87988
88782
  "access",
87989
88783
  "isolation",
88784
+ "attached",
87990
88785
  "version",
87991
88786
  "browser",
87992
88787
  "workspace",
@@ -88029,12 +88824,16 @@ var OPERATIONS = [
88029
88824
  ],
88030
88825
  "description": "The isolation it was made in. It is never run in another."
88031
88826
  },
88827
+ "attached": {
88828
+ "type": "boolean",
88829
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
88830
+ },
88032
88831
  "version": {
88033
88832
  "type": [
88034
88833
  "string",
88035
88834
  "null"
88036
88835
  ],
88037
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88836
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88038
88837
  },
88039
88838
  "browser": {
88040
88839
  "type": "boolean"
@@ -88134,6 +88933,7 @@ var OPERATIONS = [
88134
88933
  "state",
88135
88934
  "access",
88136
88935
  "isolation",
88936
+ "attached",
88137
88937
  "version",
88138
88938
  "browser",
88139
88939
  "workspace",
@@ -88176,12 +88976,16 @@ var OPERATIONS = [
88176
88976
  ],
88177
88977
  "description": "The isolation it was made in. It is never run in another."
88178
88978
  },
88979
+ "attached": {
88980
+ "type": "boolean",
88981
+ "description": "Whether it is this computer, attached rather than made: the service's machine, whole, acting as whoever started the service, with no isolation. False with isolation none marks a computer made with no isolation by an earlier version, which can't start again; its workspace is kept."
88982
+ },
88179
88983
  "version": {
88180
88984
  "type": [
88181
88985
  "string",
88182
88986
  "null"
88183
88987
  ],
88184
- "description": "The version of the program it ran at its last start. With no isolation it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88988
+ "description": "The version of the program it ran at its last start. For this computer it is the service's `version` (see `getInfo`). In a container it is the version inside its image, and null for a container made before 0.0.17, which doesn't say, until it is made again. It is kept from its last start, so a stopped computer's can be older than the service's until it starts. Compare it with the service's to show that a computer runs an older program."
88185
88989
  },
88186
88990
  "browser": {
88187
88991
  "type": "boolean"
@@ -88425,6 +89229,8 @@ var OPERATIONS = [
88425
89229
  var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
88426
89230
  var WILDCARD = /* @__PURE__ */ new Set(["0.0.0.0", "::"]);
88427
89231
  var MAX_BODY2 = 64 * 1024;
89232
+ var NOT_HERE = "This service can be reached from other machines, so it doesn't offer the machine it runs on. Make a computer in a container, or attach this computer through a service that only this machine reaches.";
89233
+ var onlyThisMachine = (host, options) => LOOPBACK.has(host) && !options.tlsProxy && !options.publicUrl;
88428
89234
  function checkExposure(host, options) {
88429
89235
  const { tls, tlsProxy, publicUrl } = options;
88430
89236
  if (tls && tlsProxy) throw new Error("--tls-cert and --tls-proxy are alternatives.");
@@ -88474,8 +89280,12 @@ async function serve(options) {
88474
89280
  if (live)
88475
89281
  throw new Error(`A service is already running with its data in ${data}, at ${live.url}.`);
88476
89282
  let base = "";
88477
- await options.runtimes[options.isolation].prepare();
88478
- const computers = new Computers(data, () => base, options.runtimes, options.isolation);
89283
+ const computers = new Computers(
89284
+ data,
89285
+ () => base,
89286
+ options.runtimes,
89287
+ onlyThisMachine(host, options) ? void 0 : NOT_HERE
89288
+ );
88479
89289
  const handlers = createHandlers(computers);
88480
89290
  const routes = OPERATIONS.map((operation) => ({
88481
89291
  operation,
@@ -88577,13 +89387,13 @@ Connection: close\r
88577
89387
  ${JSON.stringify({ code: status, message })}
88578
89388
  `
88579
89389
  );
88580
- const { pathname } = new URL(request.url ?? "/", "http://localhost");
89390
+ const pathname = URL.parse(request.url ?? "/", "http://localhost")?.pathname ?? "";
88581
89391
  const ticket = /^\/v1\/live\/([^/]+)$/.exec(pathname)?.[1];
88582
89392
  if (!ticket) return refuse(404, "Not found.");
88583
89393
  const origin = request.headers.origin;
88584
89394
  if (origin !== void 0 && !allowed.has(origin))
88585
89395
  return refuse(403, `A live session isn't opened from ${origin.slice(0, 200)}.`);
88586
- computers.openLive(decodeURIComponent(ticket), request, socket, head).catch((error62) => {
89396
+ computers.openLive(ticket, request, socket, head).catch((error62) => {
88587
89397
  if (error62 instanceof ApiError) refuse(error62.status, error62.message);
88588
89398
  else socket.destroy();
88589
89399
  });
@@ -88598,10 +89408,7 @@ ${JSON.stringify({ code: status, message })}
88598
89408
  const record3 = recordOf(data);
88599
89409
  rmSync6(record3, { force: true });
88600
89410
  await computers.resume();
88601
- writePrivate(
88602
- record3,
88603
- JSON.stringify({ url: base, pid: process.pid, isolation: options.isolation })
88604
- );
89411
+ writePrivate(record3, JSON.stringify({ url: base, pid: process.pid }));
88605
89412
  process.stdout.write(`AgentComputer on ${base}, data in ${data}
88606
89413
  `);
88607
89414
  let stopping = false;
@@ -88621,7 +89428,7 @@ ${JSON.stringify({ code: status, message })}
88621
89428
  // src/main.ts
88622
89429
  var USAGE = `Usage: agentcomputer <command> [--data DIR]
88623
89430
 
88624
- serve [--listen HOST:PORT] [--isolation none|container] [--image IMAGE]
89431
+ serve [--listen HOST:PORT] [--image IMAGE]
88625
89432
  [--tls-cert FILE --tls-key FILE | --tls-proxy] [--public-url URL] [--allow-origin ORIGIN]...
88626
89433
  [--browser-name NAME] [--browser-path PATH]
88627
89434
  run the service; 127.0.0.1:7878 by default, and
@@ -88630,11 +89437,16 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88630
89437
  answers, printing where; one running is left as it is
88631
89438
  down stop the service, and its computers with it
88632
89439
  status where the service is running, if it is
88633
- info the service's version, the isolation it gives a computer
88634
- made without saying, and the ones it can make now
88635
- create --access none|read|files|terminal [--browser] [--workspace DIR] [--name NAME]
88636
- [--isolation none|container] [--timeout SECONDS] [--metadata KEY=VALUE]...
88637
- make a computer; prints its endpoints and token, once
89440
+ info the service's version and what it offers: this computer,
89441
+ the kinds it makes, and their browsers, each ready or not
89442
+ create --access none|read|files|terminal [--browser] [--workspace DIR | --copy-from DIR]
89443
+ [--name NAME] [--isolation container] [--timeout SECONDS] [--metadata KEY=VALUE]...
89444
+ make a computer, in a container; prints its endpoints and
89445
+ token, once
89446
+ attach --access none|read|files|terminal --workspace DIR [--browser] [--name NAME]
89447
+ [--timeout SECONDS] [--metadata KEY=VALUE]...
89448
+ attach this computer, whole and acting as you, with DIR its
89449
+ work folder; prints its endpoints and token, once
88638
89450
  list [--state running|stopped|failed]
88639
89451
  the computers
88640
89452
  get <id> one computer
@@ -88644,7 +89456,8 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88644
89456
  logs <id> what its servers wrote
88645
89457
  live <id> --surface terminal|files|browser|load [--mode watch|operate]
88646
89458
  a one-time ticket to open one live, as a WebSocket
88647
- kill <id> stop it and remove what the service made
89459
+ kill <id> stop it and remove what the service made, or detach this
89460
+ computer
88648
89461
 
88649
89462
  --version prints this program's version, with no service.
88650
89463
 
@@ -88657,7 +89470,7 @@ var UsageError = class extends Error {
88657
89470
  function dataFolder(given) {
88658
89471
  const xdg = process.env.XDG_DATA_HOME;
88659
89472
  return resolve5(
88660
- given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join11(xdg, "agentcomputer") : join11(homedir2(), ".local", "share", "agentcomputer"))
89473
+ given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute3(xdg) ? join11(xdg, "agentcomputer") : join11(homedir2(), ".local", "share", "agentcomputer"))
88661
89474
  );
88662
89475
  }
88663
89476
  var none = (data) => `No service is running with its data in ${data}. Start one: agentcomputer up`;
@@ -88694,6 +89507,7 @@ async function main() {
88694
89507
  output: { type: "string" },
88695
89508
  access: { type: "string" },
88696
89509
  workspace: { type: "string" },
89510
+ "copy-from": { type: "string" },
88697
89511
  name: { type: "string" },
88698
89512
  timeout: { type: "string" },
88699
89513
  state: { type: "string" },
@@ -88731,8 +89545,9 @@ async function main() {
88731
89545
  };
88732
89546
  const milliseconds = (seconds2) => Number(seconds2) * 1e3;
88733
89547
  const { isolation } = values;
88734
- if (isolation !== void 0 && isolation !== "none" && isolation !== "container")
88735
- throw new UsageError("--isolation is none or container.");
89548
+ for (const flag of ["isolation", "copy-from"])
89549
+ if (values[flag] !== void 0 && command !== "create")
89550
+ throw new UsageError(`Only create takes --${flag}.`);
88736
89551
  if (command === "serve") {
88737
89552
  const { "tls-cert": cert, "tls-key": key } = values;
88738
89553
  if (Boolean(cert) !== Boolean(key))
@@ -88744,7 +89559,6 @@ async function main() {
88744
89559
  tlsProxy: values["tls-proxy"],
88745
89560
  publicUrl: values["public-url"],
88746
89561
  allowOrigins: values["allow-origin"],
88747
- isolation: isolation ?? "none",
88748
89562
  runtimes: {
88749
89563
  none: new ProcessRuntime({ browser: values["browser-name"], path: values["browser-path"] }),
88750
89564
  container: new ContainerRuntime(values.image ?? `${IMAGE}:${VERSION2}`)
@@ -88769,31 +89583,43 @@ async function main() {
88769
89583
  browser: values.browser === true
88770
89584
  });
88771
89585
  const connection = connectionTo(data);
89586
+ const making = () => {
89587
+ if (!values.access) throw new UsageError(`${command} takes --access.`);
89588
+ return {
89589
+ ...connection,
89590
+ access: values.access,
89591
+ ...values.browser ? { browser: true } : {},
89592
+ ...values.name ? { name: values.name } : {},
89593
+ ...values.timeout ? { timeoutMs: milliseconds(values.timeout) } : {},
89594
+ ...values.metadata ? {
89595
+ metadata: Object.fromEntries(
89596
+ values.metadata.map((pair) => {
89597
+ const at = pair.indexOf("=");
89598
+ if (at < 1) throw new UsageError("--metadata takes KEY=VALUE.");
89599
+ return [pair.slice(0, at), pair.slice(at + 1)];
89600
+ })
89601
+ )
89602
+ } : {}
89603
+ };
89604
+ };
89605
+ const printMade = async (computer) => out({ ...await computer.getInfo(), accessToken: computer.getMcpToken() });
88772
89606
  switch (command) {
88773
89607
  case "info":
88774
89608
  return out(await Computer.serviceInfo(connection));
88775
- case "create": {
88776
- if (!values.access) throw new UsageError("create takes --access.");
88777
- const computer = await Computer.create({
88778
- ...connection,
88779
- access: values.access,
88780
- ...values.browser ? { browser: true } : {},
88781
- ...isolation ? { isolation } : {},
88782
- ...values.workspace ? { workspace: resolve5(values.workspace) } : {},
88783
- ...values.name ? { name: values.name } : {},
88784
- ...values.timeout ? { timeoutMs: milliseconds(values.timeout) } : {},
88785
- ...values.metadata ? {
88786
- metadata: Object.fromEntries(
88787
- values.metadata.map((pair) => {
88788
- const at = pair.indexOf("=");
88789
- if (at < 1) throw new UsageError("--metadata takes KEY=VALUE.");
88790
- return [pair.slice(0, at), pair.slice(at + 1)];
88791
- })
88792
- )
88793
- } : {}
88794
- });
88795
- return out({ ...await computer.getInfo(), accessToken: computer.getMcpToken() });
88796
- }
89609
+ case "create":
89610
+ return printMade(
89611
+ await Computer.create({
89612
+ ...making(),
89613
+ ...isolation ? { isolation } : {},
89614
+ ...values.workspace ? { workspace: resolve5(values.workspace) } : {},
89615
+ ...values["copy-from"] ? { copyFrom: resolve5(values["copy-from"]) } : {}
89616
+ })
89617
+ );
89618
+ case "attach":
89619
+ if (!values.workspace) throw new UsageError("attach takes --workspace.");
89620
+ return printMade(
89621
+ await Computer.attach({ ...making(), workspace: resolve5(values.workspace) })
89622
+ );
88797
89623
  case "list":
88798
89624
  return out(
88799
89625
  await Computer.list({