@agentorg/computer 0.0.18 → 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)
@@ -67864,16 +67880,44 @@ var loadPty = () => {
67864
67880
  })();
67865
67881
  return pty;
67866
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
+ }
67867
67898
  var ProcessRuntime = class {
67868
67899
  #config;
67869
67900
  #contained;
67870
- /** `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
+ */
67871
67906
  constructor(config3, contained = false) {
67872
67907
  this.#config = config3;
67873
67908
  this.#contained = contained;
67874
67909
  }
67875
- async available() {
67876
- 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
+ };
67877
67921
  }
67878
67922
  async prepare() {
67879
67923
  }
@@ -67899,6 +67943,7 @@ var ProcessRuntime = class {
67899
67943
  exec = createAgentExec({
67900
67944
  workspace,
67901
67945
  level: spec.access,
67946
+ reach: this.#contained ? "workspace" : "machine",
67902
67947
  pty: await loadPty(),
67903
67948
  extraTools: [about]
67904
67949
  });
@@ -67959,16 +68004,13 @@ var ProcessRuntime = class {
67959
68004
  };
67960
68005
 
67961
68006
  // src/handlers.ts
67962
- var VERSION2 = "0.0.18";
68007
+ var VERSION2 = "0.0.19";
67963
68008
  function createHandlers(computers) {
67964
68009
  return {
67965
- getInfo: async () => ({
67966
- version: VERSION2,
67967
- isolation: computers.isolation,
67968
- isolationsAvailable: await computers.available()
67969
- }),
68010
+ getInfo: async () => ({ version: VERSION2, ...await computers.offers() }),
67970
68011
  listComputers: async ({ state }) => computers.list(state),
67971
68012
  createComputer: (input2) => computers.create(input2),
68013
+ attachComputer: (input2) => computers.attach(input2),
67972
68014
  getComputer: async ({ computerID }) => computers.get(computerID),
67973
68015
  killComputer: async ({ computerID }) => {
67974
68016
  await computers.kill(computerID);
@@ -68059,6 +68101,7 @@ async function serveInside(options) {
68059
68101
  var run2 = promisify2(execFile2);
68060
68102
  var START_MS2 = 6e4;
68061
68103
  var STOP_SECONDS = "5";
68104
+ var NO_DOCKER = "A container needs Docker, which isn't answering on this machine.";
68062
68105
  var NOT_FORWARDED = /* @__PURE__ */ new Set([
68063
68106
  "authorization",
68064
68107
  "connection",
@@ -68105,14 +68148,22 @@ var ContainerRuntime = class {
68105
68148
  return false;
68106
68149
  }
68107
68150
  }
68108
- // Asked each time the service says what it can make, so it doesn't wait on a Docker that hangs.
68109
- available() {
68110
- 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
+ };
68111
68163
  }
68112
68164
  /** Docker, or the refusal of what can't be done without it. */
68113
68165
  async #docker() {
68114
- if (!await this.#answers(1e4))
68115
- 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);
68116
68167
  }
68117
68168
  async prepare() {
68118
68169
  await this.#docker();
@@ -68375,8 +68426,8 @@ async function running(data, said = recorded(data)) {
68375
68426
  signal: AbortSignal.timeout(3e3)
68376
68427
  });
68377
68428
  if (!answer.ok) return void 0;
68378
- const { version: version3, isolation } = await answer.json();
68379
- 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 };
68380
68431
  } catch {
68381
68432
  return void 0;
68382
68433
  }
@@ -68912,6 +68963,19 @@ var ComputerError = class extends Error {
68912
68963
  }
68913
68964
  };
68914
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
+ }
68915
68979
  function apiOf(opts = {}) {
68916
68980
  const apiKey = opts.apiKey ?? process.env.AGENTCOMPUTER_API_KEY;
68917
68981
  if (!apiKey) throw new ComputerError(401, "Pass apiKey, or set AGENTCOMPUTER_API_KEY.");
@@ -69111,23 +69175,30 @@ var Computer = class _Computer {
69111
69175
  this.files = new Files(this.#computer);
69112
69176
  }
69113
69177
  static async create(opts) {
69114
- const { access, browser, isolation, workspace, name, timeoutMs, metadata, apiKey, apiUrl } = opts;
69115
- const connection = { ...apiKey ? { apiKey } : {}, ...apiUrl ? { apiUrl } : {} };
69178
+ const { isolation, workspace, copyFrom } = opts;
69179
+ const { connection, body } = requestOf(opts);
69116
69180
  const created = answered(
69117
69181
  await apiOf(connection).POST("/v1/computers", {
69118
69182
  body: {
69119
- access,
69120
- browser: browser ?? false,
69183
+ ...body,
69121
69184
  ...isolation === void 0 ? {} : { isolation },
69122
69185
  ...workspace === void 0 ? {} : { workspace },
69123
- ...name === void 0 ? {} : { name },
69124
- ...timeoutMs === void 0 ? {} : { timeout: seconds(timeoutMs) },
69125
- ...metadata === void 0 ? {} : { metadata }
69186
+ ...copyFrom === void 0 ? {} : { copyFrom }
69126
69187
  }
69127
69188
  })
69128
69189
  );
69129
69190
  return new _Computer(connection, created, created.accessToken);
69130
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
+ }
69131
69202
  /** A computer made earlier. Its token was shown when it was made, and only then. */
69132
69203
  static async connect(computerID, opts) {
69133
69204
  const { accessToken, ...connection } = opts;
@@ -75860,13 +75931,13 @@ var require_resolve3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
75860
75931
  }
75861
75932
  return count;
75862
75933
  }
75863
- function getFullPath(resolver, id = "", normalize) {
75934
+ function getFullPath(resolver2, id = "", normalize) {
75864
75935
  if (normalize !== false) id = normalizeId(id);
75865
- return _getFullPath(resolver, resolver.parse(id));
75936
+ return _getFullPath(resolver2, resolver2.parse(id));
75866
75937
  }
75867
75938
  exports.getFullPath = getFullPath;
75868
- function _getFullPath(resolver, p) {
75869
- return resolver.serialize(p).split("#")[0] + "#";
75939
+ function _getFullPath(resolver2, p) {
75940
+ return resolver2.serialize(p).split("#")[0] + "#";
75870
75941
  }
75871
75942
  exports._getFullPath = _getFullPath;
75872
75943
  const TRAILING_SLASH_HASH = /#\/?$/;
@@ -75874,9 +75945,9 @@ var require_resolve3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
75874
75945
  return id ? id.replace(TRAILING_SLASH_HASH, "") : "";
75875
75946
  }
75876
75947
  exports.normalizeId = normalizeId;
75877
- function resolveUrl(resolver, baseId, id) {
75948
+ function resolveUrl(resolver2, baseId, id) {
75878
75949
  id = normalizeId(id);
75879
- return resolver.resolve(baseId, id);
75950
+ return resolver2.resolve(baseId, id);
75880
75951
  }
75881
75952
  exports.resolveUrl = resolveUrl;
75882
75953
  const ANCHOR = /^[a-z_][-a-z0-9._]*$/i;
@@ -76358,10 +76429,10 @@ var require_ref_error3 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76358
76429
  Object.defineProperty(exports, "__esModule", { value: true });
76359
76430
  const resolve_1 = require_resolve3();
76360
76431
  var MissingRefError = class extends Error {
76361
- constructor(resolver, baseId, ref, msg) {
76432
+ constructor(resolver2, baseId, ref, msg) {
76362
76433
  super(msg || `can't resolve reference ${ref} from id ${baseId}`);
76363
- this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref);
76364
- 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));
76365
76436
  }
76366
76437
  };
76367
76438
  exports.default = MissingRefError;
@@ -76605,10 +76676,10 @@ var require_utils3 = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
76605
76676
  const HEX_DIGITS = "0123456789ABCDEF";
76606
76677
  for (let i = 0; i < 256; i++) BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
76607
76678
  }
76608
- function percentEncodeNonAscii(cp) {
76609
- if (cp < 2048) return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
76610
- if (cp < 65536) return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
76611
- 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];
76612
76683
  }
76613
76684
  function stringArrayToHexStripped(input2) {
76614
76685
  let acc = "";
@@ -76948,8 +77019,8 @@ var require_utils3 = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
76948
77019
  function encodeFragment(input2) {
76949
77020
  return encodeComponent(input2, isQueryFragmentCharacter);
76950
77021
  }
76951
- function isEscapeSafe(cp) {
76952
- 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;
76953
77024
  }
76954
77025
  function normalizeQueryFragmentEncoding(input2) {
76955
77026
  let output2 = "";
@@ -83101,8 +83172,8 @@ var WebStandardStreamableHTTPServerTransport2 = class {
83101
83172
  return timer;
83102
83173
  }
83103
83174
  /** Sets the scope challenge resolver for parsed JSON-RPC requests. */
83104
- setScopeChallengeResolver(resolver) {
83105
- this._scopeChallengeResolver = resolver;
83175
+ setScopeChallengeResolver(resolver2) {
83176
+ this._scopeChallengeResolver = resolver2;
83106
83177
  }
83107
83178
  /**
83108
83179
  * Starts the transport. This is required by the {@linkcode Transport} interface but is a no-op
@@ -85002,8 +85073,8 @@ var NodeStreamableHTTPServerTransport2 = class {
85002
85073
  this._webStandardTransport.setSupportedProtocolVersions(versions);
85003
85074
  }
85004
85075
  /** Sets the scope challenge resolver used by the wrapped Web Standard transport. */
85005
- setScopeChallengeResolver(resolver) {
85006
- this._webStandardTransport.setScopeChallengeResolver(resolver);
85076
+ setScopeChallengeResolver(resolver2) {
85077
+ this._webStandardTransport.setScopeChallengeResolver(resolver2);
85007
85078
  }
85008
85079
  /**
85009
85080
  * Handles an incoming HTTP request, whether `GET` or `POST`.
@@ -85043,8 +85114,19 @@ var NodeStreamableHTTPServerTransport2 = class {
85043
85114
 
85044
85115
  // src/computers.ts
85045
85116
  import { randomBytes as randomBytes5 } from "node:crypto";
85046
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
85047
- 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";
85048
85130
  var READ = ["about", "read", "list", "search"];
85049
85131
  var FILES = [...READ, "edit"];
85050
85132
  var TOOLS2 = {
@@ -85057,32 +85139,59 @@ var MAX_TIMER_S = 2e6;
85057
85139
  var TICKET_MS = 6e4;
85058
85140
  var expiry = (seconds2) => new Date(Date.now() + seconds2 * 1e3).toISOString();
85059
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
+ }
85060
85165
  var refusalOf = (error62) => error62 instanceof ApiError ? error62 : new ApiError(500, messageOf2(error62));
85061
85166
  var Computers = class {
85062
85167
  #data;
85063
85168
  #base;
85169
+ /** This computer's way of running, as `none`, and each way of making one. */
85064
85170
  #runtimes;
85065
- /** The isolation of a computer made without saying which. */
85066
- isolation;
85171
+ /** Why this computer isn't offered here, where it isn't. */
85172
+ #notHere;
85067
85173
  #stored = /* @__PURE__ */ new Map();
85068
85174
  #live = /* @__PURE__ */ new Map();
85069
85175
  #tickets = /* @__PURE__ */ new Map();
85070
85176
  #file;
85071
- constructor(data, base, runtimes, isolation) {
85177
+ constructor(data, base, runtimes, notHere) {
85072
85178
  this.#data = data;
85073
85179
  this.#base = base;
85074
85180
  this.#runtimes = runtimes;
85075
- this.isolation = isolation;
85181
+ this.#notHere = notHere;
85076
85182
  this.#file = join10(data, "computers.json");
85077
85183
  if (existsSync2(this.#file))
85078
85184
  for (const entry of JSON.parse(readFileSync5(this.#file, "utf8")))
85079
- this.#stored.set(entry.id, { ...entry, isolation: entry.isolation ?? isolation });
85080
- }
85081
- /** The isolations a computer can be made in now. */
85082
- async available() {
85083
- const all = Object.keys(this.#runtimes);
85084
- const can = await Promise.all(all.map((one) => this.#runtimes[one].available()));
85085
- 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 };
85086
85195
  }
85087
85196
  /** Bring back what was running when the service last stopped. */
85088
85197
  async resume() {
@@ -85105,20 +85214,34 @@ var Computers = class {
85105
85214
  this.#save();
85106
85215
  }
85107
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) {
85108
85229
  const { access, name, timeout, metadata } = input2;
85109
85230
  const browser = input2.browser === true;
85110
85231
  if (access === "none" && !browser)
85111
85232
  throw new ApiError(400, "With no files, no commands and no browser, there's no computer.");
85112
- let workspace;
85113
- if (input2.workspace !== void 0) {
85114
- let folder = false;
85115
- try {
85116
- folder = isAbsolute(input2.workspace) && statSync4(input2.workspace).isDirectory();
85117
- } catch {
85118
- }
85119
- if (!folder) throw new ApiError(400, `There's no folder at ${input2.workspace}.`);
85120
- workspace = resolve4(input2.workspace);
85121
- }
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));
85122
85245
  const id = `c_${randomBytes5(9).toString("hex")}`;
85123
85246
  const token = newToken();
85124
85247
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -85126,7 +85249,8 @@ var Computers = class {
85126
85249
  id,
85127
85250
  name: name ?? id,
85128
85251
  access,
85129
- isolation: input2.isolation ?? this.isolation,
85252
+ isolation,
85253
+ ...isolation === "none" ? { attached: true } : {},
85130
85254
  browser,
85131
85255
  ...workspace ? { workspace } : {},
85132
85256
  metadata: metadata ?? {},
@@ -85137,8 +85261,21 @@ var Computers = class {
85137
85261
  ...timeout === void 0 ? {} : { endAt: expiry(timeout) }
85138
85262
  };
85139
85263
  if (!workspace && access !== "none") {
85140
- stored.workspace = join10(this.#folder(id), "workspace");
85141
- 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
+ });
85142
85279
  }
85143
85280
  this.#stored.set(id, stored);
85144
85281
  try {
@@ -85148,7 +85285,10 @@ var Computers = class {
85148
85285
  this.#stored.delete(id);
85149
85286
  await this.#runtimes[stored.isolation].remove(id).catch(() => {
85150
85287
  });
85151
- rmSync5(this.#folder(id), { recursive: true, force: true });
85288
+ try {
85289
+ removeFolder(this.#folder(id));
85290
+ } catch {
85291
+ }
85152
85292
  throw refusalOf(error62);
85153
85293
  }
85154
85294
  this.#save();
@@ -85222,7 +85362,12 @@ var Computers = class {
85222
85362
  }
85223
85363
  this.#stored.delete(id);
85224
85364
  this.#save();
85225
- 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);
85226
85371
  }
85227
85372
  logs(id) {
85228
85373
  this.#found(id);
@@ -85323,6 +85468,7 @@ var Computers = class {
85323
85468
  state: stored.state === "running" && down2 ? "failed" : stored.state,
85324
85469
  access: stored.access,
85325
85470
  isolation: stored.isolation,
85471
+ attached: stored.attached === true,
85326
85472
  version: stored.version ?? null,
85327
85473
  browser: stored.browser,
85328
85474
  workspace: stored.workspace ?? null,
@@ -85338,6 +85484,9 @@ var Computers = class {
85338
85484
  async #start(stored) {
85339
85485
  const live = {};
85340
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);
85341
85490
  stored.startedAt = (/* @__PURE__ */ new Date()).toISOString();
85342
85491
  const runtime = this.#runtimes[stored.isolation];
85343
85492
  await runtime.prepare();
@@ -85397,7 +85546,7 @@ var OPENAPI = {
85397
85546
  "openapi": "3.1.0",
85398
85547
  "info": {
85399
85548
  "title": "AgentComputer",
85400
- "version": "0.0.18",
85549
+ "version": "0.0.19",
85401
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.",
85402
85551
  "license": {
85403
85552
  "name": "Apache-2.0",
@@ -85420,7 +85569,7 @@ var OPENAPI = {
85420
85569
  "get": {
85421
85570
  "operationId": "getInfo",
85422
85571
  "x-mcp-tool": "service_info",
85423
- "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.",
85424
85573
  "responses": {
85425
85574
  "200": {
85426
85575
  "description": "The service.",
@@ -85502,6 +85651,47 @@ var OPENAPI = {
85502
85651
  },
85503
85652
  "401": {
85504
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"
85505
85695
  }
85506
85696
  }
85507
85697
  }
@@ -85675,6 +85865,9 @@ var OPENAPI = {
85675
85865
  },
85676
85866
  "404": {
85677
85867
  "$ref": "#/components/responses/Error"
85868
+ },
85869
+ "409": {
85870
+ "$ref": "#/components/responses/Error"
85678
85871
  }
85679
85872
  }
85680
85873
  }
@@ -85899,9 +86092,48 @@ var OPENAPI = {
85899
86092
  "maxLength": 4096,
85900
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."
85901
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
+ },
85902
86100
  "isolation": {
85903
86101
  "$ref": "#/components/schemas/Isolation",
85904
- "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."
85905
86137
  },
85906
86138
  "timeout": {
85907
86139
  "$ref": "#/components/schemas/Timeout"
@@ -85917,7 +86149,7 @@ var OPENAPI = {
85917
86149
  "none",
85918
86150
  "container"
85919
86151
  ],
85920
- "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."
85921
86153
  },
85922
86154
  "Endpoints": {
85923
86155
  "type": "object",
@@ -85951,6 +86183,7 @@ var OPENAPI = {
85951
86183
  "state",
85952
86184
  "access",
85953
86185
  "isolation",
86186
+ "attached",
85954
86187
  "version",
85955
86188
  "browser",
85956
86189
  "workspace",
@@ -85976,12 +86209,16 @@ var OPENAPI = {
85976
86209
  "$ref": "#/components/schemas/Isolation",
85977
86210
  "description": "The isolation it was made in. It is never run in another."
85978
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
+ },
85979
86216
  "version": {
85980
86217
  "type": [
85981
86218
  "string",
85982
86219
  "null"
85983
86220
  ],
85984
- "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."
85985
86222
  },
85986
86223
  "browser": {
85987
86224
  "type": "boolean"
@@ -86765,7 +87002,7 @@ var OPENAPI = {
86765
87002
  "computer",
86766
87003
  "machine"
86767
87004
  ],
86768
- "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."
86769
87006
  },
86770
87007
  "cpuUsedPct": {
86771
87008
  "type": "number",
@@ -87137,27 +87374,143 @@ var OPENAPI = {
87137
87374
  }
87138
87375
  }
87139
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
+ },
87140
87493
  "ServiceInfo": {
87141
87494
  "type": "object",
87142
87495
  "required": [
87143
87496
  "version",
87144
- "isolation",
87145
- "isolationsAvailable"
87497
+ "thisComputer",
87498
+ "made"
87146
87499
  ],
87147
87500
  "properties": {
87148
87501
  "version": {
87149
87502
  "type": "string"
87150
87503
  },
87151
- "isolation": {
87152
- "$ref": "#/components/schemas/Isolation",
87153
- "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."
87154
87507
  },
87155
- "isolationsAvailable": {
87508
+ "made": {
87156
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.",
87157
87511
  "items": {
87158
- "$ref": "#/components/schemas/Isolation"
87159
- },
87160
- "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
+ }
87161
87514
  }
87162
87515
  }
87163
87516
  }
@@ -87170,7 +87523,7 @@ var OPERATIONS = [
87170
87523
  "method": "GET",
87171
87524
  "path": "/v1/info",
87172
87525
  "tool": "service_info",
87173
- "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.",
87174
87527
  "status": 200,
87175
87528
  "hasBody": false,
87176
87529
  "pathParameters": [],
@@ -87185,32 +87538,251 @@ var OPERATIONS = [
87185
87538
  "type": "object",
87186
87539
  "required": [
87187
87540
  "version",
87188
- "isolation",
87189
- "isolationsAvailable"
87541
+ "thisComputer",
87542
+ "made"
87190
87543
  ],
87191
87544
  "properties": {
87192
87545
  "version": {
87193
87546
  "type": "string"
87194
87547
  },
87195
- "isolation": {
87196
- "type": "string",
87197
- "enum": [
87198
- "none",
87199
- "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
+ }
87200
87653
  ],
87201
- "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."
87202
87655
  },
87203
- "isolationsAvailable": {
87656
+ "made": {
87204
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.",
87205
87659
  "items": {
87206
- "type": "string",
87207
- "enum": [
87208
- "none",
87209
- "container"
87210
- ],
87211
- "description": "none: processes of whoever started the service. container: a container of its own, which needs Docker on the service's machine."
87212
- },
87213
- "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
+ }
87214
87786
  }
87215
87787
  }
87216
87788
  },
@@ -87254,6 +87826,7 @@ var OPERATIONS = [
87254
87826
  "state",
87255
87827
  "access",
87256
87828
  "isolation",
87829
+ "attached",
87257
87830
  "version",
87258
87831
  "browser",
87259
87832
  "workspace",
@@ -87296,12 +87869,16 @@ var OPERATIONS = [
87296
87869
  ],
87297
87870
  "description": "The isolation it was made in. It is never run in another."
87298
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
+ },
87299
87876
  "version": {
87300
87877
  "type": [
87301
87878
  "string",
87302
87879
  "null"
87303
87880
  ],
87304
- "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."
87305
87882
  },
87306
87883
  "browser": {
87307
87884
  "type": "boolean"
@@ -87402,13 +87979,18 @@ var OPERATIONS = [
87402
87979
  "maxLength": 4096,
87403
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."
87404
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
+ },
87405
87987
  "isolation": {
87406
87988
  "type": "string",
87407
87989
  "enum": [
87408
87990
  "none",
87409
87991
  "container"
87410
87992
  ],
87411
- "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."
87412
87994
  },
87413
87995
  "timeout": {
87414
87996
  "type": "integer",
@@ -87439,6 +88021,7 @@ var OPERATIONS = [
87439
88021
  "state",
87440
88022
  "access",
87441
88023
  "isolation",
88024
+ "attached",
87442
88025
  "version",
87443
88026
  "browser",
87444
88027
  "workspace",
@@ -87481,12 +88064,212 @@ var OPERATIONS = [
87481
88064
  ],
87482
88065
  "description": "The isolation it was made in. It is never run in another."
87483
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
+ },
87484
88071
  "version": {
87485
88072
  "type": [
87486
88073
  "string",
87487
88074
  "null"
87488
88075
  ],
87489
- "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."
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",
88221
+ "version",
88222
+ "browser",
88223
+ "workspace",
88224
+ "startedAt",
88225
+ "endAt",
88226
+ "metadata",
88227
+ "endpoints"
88228
+ ],
88229
+ "properties": {
88230
+ "computerID": {
88231
+ "type": "string"
88232
+ },
88233
+ "name": {
88234
+ "type": "string"
88235
+ },
88236
+ "state": {
88237
+ "type": "string",
88238
+ "enum": [
88239
+ "running",
88240
+ "stopped",
88241
+ "failed"
88242
+ ],
88243
+ "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."
88244
+ },
88245
+ "access": {
88246
+ "type": "string",
88247
+ "enum": [
88248
+ "none",
88249
+ "read",
88250
+ "files",
88251
+ "terminal"
88252
+ ],
88253
+ "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."
88254
+ },
88255
+ "isolation": {
88256
+ "type": "string",
88257
+ "enum": [
88258
+ "none",
88259
+ "container"
88260
+ ],
88261
+ "description": "The isolation it was made in. It is never run in another."
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
+ },
88267
+ "version": {
88268
+ "type": [
88269
+ "string",
88270
+ "null"
88271
+ ],
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."
87490
88273
  },
87491
88274
  "browser": {
87492
88275
  "type": "boolean"
@@ -87597,6 +88380,7 @@ var OPERATIONS = [
87597
88380
  "state",
87598
88381
  "access",
87599
88382
  "isolation",
88383
+ "attached",
87600
88384
  "version",
87601
88385
  "browser",
87602
88386
  "workspace",
@@ -87639,12 +88423,16 @@ var OPERATIONS = [
87639
88423
  ],
87640
88424
  "description": "The isolation it was made in. It is never run in another."
87641
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
+ },
87642
88430
  "version": {
87643
88431
  "type": [
87644
88432
  "string",
87645
88433
  "null"
87646
88434
  ],
87647
- "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."
87648
88436
  },
87649
88437
  "browser": {
87650
88438
  "type": "boolean"
@@ -87846,6 +88634,7 @@ var OPERATIONS = [
87846
88634
  "state",
87847
88635
  "access",
87848
88636
  "isolation",
88637
+ "attached",
87849
88638
  "version",
87850
88639
  "browser",
87851
88640
  "workspace",
@@ -87888,12 +88677,16 @@ var OPERATIONS = [
87888
88677
  ],
87889
88678
  "description": "The isolation it was made in. It is never run in another."
87890
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
+ },
87891
88684
  "version": {
87892
88685
  "type": [
87893
88686
  "string",
87894
88687
  "null"
87895
88688
  ],
87896
- "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."
87897
88690
  },
87898
88691
  "browser": {
87899
88692
  "type": "boolean"
@@ -87988,6 +88781,7 @@ var OPERATIONS = [
87988
88781
  "state",
87989
88782
  "access",
87990
88783
  "isolation",
88784
+ "attached",
87991
88785
  "version",
87992
88786
  "browser",
87993
88787
  "workspace",
@@ -88030,12 +88824,16 @@ var OPERATIONS = [
88030
88824
  ],
88031
88825
  "description": "The isolation it was made in. It is never run in another."
88032
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
+ },
88033
88831
  "version": {
88034
88832
  "type": [
88035
88833
  "string",
88036
88834
  "null"
88037
88835
  ],
88038
- "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."
88039
88837
  },
88040
88838
  "browser": {
88041
88839
  "type": "boolean"
@@ -88135,6 +88933,7 @@ var OPERATIONS = [
88135
88933
  "state",
88136
88934
  "access",
88137
88935
  "isolation",
88936
+ "attached",
88138
88937
  "version",
88139
88938
  "browser",
88140
88939
  "workspace",
@@ -88177,12 +88976,16 @@ var OPERATIONS = [
88177
88976
  ],
88178
88977
  "description": "The isolation it was made in. It is never run in another."
88179
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
+ },
88180
88983
  "version": {
88181
88984
  "type": [
88182
88985
  "string",
88183
88986
  "null"
88184
88987
  ],
88185
- "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."
88186
88989
  },
88187
88990
  "browser": {
88188
88991
  "type": "boolean"
@@ -88426,6 +89229,8 @@ var OPERATIONS = [
88426
89229
  var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
88427
89230
  var WILDCARD = /* @__PURE__ */ new Set(["0.0.0.0", "::"]);
88428
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;
88429
89234
  function checkExposure(host, options) {
88430
89235
  const { tls, tlsProxy, publicUrl } = options;
88431
89236
  if (tls && tlsProxy) throw new Error("--tls-cert and --tls-proxy are alternatives.");
@@ -88475,8 +89280,12 @@ async function serve(options) {
88475
89280
  if (live)
88476
89281
  throw new Error(`A service is already running with its data in ${data}, at ${live.url}.`);
88477
89282
  let base = "";
88478
- await options.runtimes[options.isolation].prepare();
88479
- 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
+ );
88480
89289
  const handlers = createHandlers(computers);
88481
89290
  const routes = OPERATIONS.map((operation) => ({
88482
89291
  operation,
@@ -88599,10 +89408,7 @@ ${JSON.stringify({ code: status, message })}
88599
89408
  const record3 = recordOf(data);
88600
89409
  rmSync6(record3, { force: true });
88601
89410
  await computers.resume();
88602
- writePrivate(
88603
- record3,
88604
- JSON.stringify({ url: base, pid: process.pid, isolation: options.isolation })
88605
- );
89411
+ writePrivate(record3, JSON.stringify({ url: base, pid: process.pid }));
88606
89412
  process.stdout.write(`AgentComputer on ${base}, data in ${data}
88607
89413
  `);
88608
89414
  let stopping = false;
@@ -88622,7 +89428,7 @@ ${JSON.stringify({ code: status, message })}
88622
89428
  // src/main.ts
88623
89429
  var USAGE = `Usage: agentcomputer <command> [--data DIR]
88624
89430
 
88625
- serve [--listen HOST:PORT] [--isolation none|container] [--image IMAGE]
89431
+ serve [--listen HOST:PORT] [--image IMAGE]
88626
89432
  [--tls-cert FILE --tls-key FILE | --tls-proxy] [--public-url URL] [--allow-origin ORIGIN]...
88627
89433
  [--browser-name NAME] [--browser-path PATH]
88628
89434
  run the service; 127.0.0.1:7878 by default, and
@@ -88631,11 +89437,16 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88631
89437
  answers, printing where; one running is left as it is
88632
89438
  down stop the service, and its computers with it
88633
89439
  status where the service is running, if it is
88634
- info the service's version, the isolation it gives a computer
88635
- made without saying, and the ones it can make now
88636
- create --access none|read|files|terminal [--browser] [--workspace DIR] [--name NAME]
88637
- [--isolation none|container] [--timeout SECONDS] [--metadata KEY=VALUE]...
88638
- 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
88639
89450
  list [--state running|stopped|failed]
88640
89451
  the computers
88641
89452
  get <id> one computer
@@ -88645,7 +89456,8 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
88645
89456
  logs <id> what its servers wrote
88646
89457
  live <id> --surface terminal|files|browser|load [--mode watch|operate]
88647
89458
  a one-time ticket to open one live, as a WebSocket
88648
- 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
88649
89461
 
88650
89462
  --version prints this program's version, with no service.
88651
89463
 
@@ -88658,7 +89470,7 @@ var UsageError = class extends Error {
88658
89470
  function dataFolder(given) {
88659
89471
  const xdg = process.env.XDG_DATA_HOME;
88660
89472
  return resolve5(
88661
- 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"))
88662
89474
  );
88663
89475
  }
88664
89476
  var none = (data) => `No service is running with its data in ${data}. Start one: agentcomputer up`;
@@ -88695,6 +89507,7 @@ async function main() {
88695
89507
  output: { type: "string" },
88696
89508
  access: { type: "string" },
88697
89509
  workspace: { type: "string" },
89510
+ "copy-from": { type: "string" },
88698
89511
  name: { type: "string" },
88699
89512
  timeout: { type: "string" },
88700
89513
  state: { type: "string" },
@@ -88732,8 +89545,9 @@ async function main() {
88732
89545
  };
88733
89546
  const milliseconds = (seconds2) => Number(seconds2) * 1e3;
88734
89547
  const { isolation } = values;
88735
- if (isolation !== void 0 && isolation !== "none" && isolation !== "container")
88736
- 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}.`);
88737
89551
  if (command === "serve") {
88738
89552
  const { "tls-cert": cert, "tls-key": key } = values;
88739
89553
  if (Boolean(cert) !== Boolean(key))
@@ -88745,7 +89559,6 @@ async function main() {
88745
89559
  tlsProxy: values["tls-proxy"],
88746
89560
  publicUrl: values["public-url"],
88747
89561
  allowOrigins: values["allow-origin"],
88748
- isolation: isolation ?? "none",
88749
89562
  runtimes: {
88750
89563
  none: new ProcessRuntime({ browser: values["browser-name"], path: values["browser-path"] }),
88751
89564
  container: new ContainerRuntime(values.image ?? `${IMAGE}:${VERSION2}`)
@@ -88770,31 +89583,43 @@ async function main() {
88770
89583
  browser: values.browser === true
88771
89584
  });
88772
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() });
88773
89606
  switch (command) {
88774
89607
  case "info":
88775
89608
  return out(await Computer.serviceInfo(connection));
88776
- case "create": {
88777
- if (!values.access) throw new UsageError("create takes --access.");
88778
- const computer = await Computer.create({
88779
- ...connection,
88780
- access: values.access,
88781
- ...values.browser ? { browser: true } : {},
88782
- ...isolation ? { isolation } : {},
88783
- ...values.workspace ? { workspace: resolve5(values.workspace) } : {},
88784
- ...values.name ? { name: values.name } : {},
88785
- ...values.timeout ? { timeoutMs: milliseconds(values.timeout) } : {},
88786
- ...values.metadata ? {
88787
- metadata: Object.fromEntries(
88788
- values.metadata.map((pair) => {
88789
- const at = pair.indexOf("=");
88790
- if (at < 1) throw new UsageError("--metadata takes KEY=VALUE.");
88791
- return [pair.slice(0, at), pair.slice(at + 1)];
88792
- })
88793
- )
88794
- } : {}
88795
- });
88796
- return out({ ...await computer.getInfo(), accessToken: computer.getMcpToken() });
88797
- }
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
+ );
88798
89623
  case "list":
88799
89624
  return out(
88800
89625
  await Computer.list({