@agentorg/computer 0.0.8 → 0.0.10

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.
@@ -3753,7 +3753,7 @@ var require_websocket_server = __commonJS({
3753
3753
  // src/main.ts
3754
3754
  import { readFileSync as readFileSync6 } from "node:fs";
3755
3755
  import { homedir as homedir2 } from "node:os";
3756
- import { isAbsolute as isAbsolute2, join as join12, resolve as resolve5 } from "node:path";
3756
+ import { isAbsolute as isAbsolute2, join as join11, resolve as resolve5 } from "node:path";
3757
3757
  import { parseArgs } from "node:util";
3758
3758
  import { isMainThread as isMainThread2 } from "node:worker_threads";
3759
3759
 
@@ -3772,9 +3772,9 @@ var playwright = () => {
3772
3772
  // src/browser-host.ts
3773
3773
  var sandboxFor = (options) => options.sandbox ?? (process.platform !== "linux" || !options.executable && !["chromium", "chrome-for-testing"].includes(options.channel ?? "chrome"));
3774
3774
  async function hostBrowser(options) {
3775
- const run2 = mkdtempSync(join("/tmp", "agentcomputer-"));
3776
- process.env.XDG_RUNTIME_DIR = run2;
3777
- process.env.TMPDIR = run2;
3775
+ const run3 = mkdtempSync(join("/tmp", "agentcomputer-"));
3776
+ process.env.XDG_RUNTIME_DIR = run3;
3777
+ process.env.TMPDIR = run3;
3778
3778
  const context = await playwright().chromium.launchPersistentContext(options.profile, {
3779
3779
  headless: true,
3780
3780
  chromiumSandbox: sandboxFor(options),
@@ -3783,7 +3783,7 @@ async function hostBrowser(options) {
3783
3783
  const browser = context.browser();
3784
3784
  if (!browser) throw new Error("The browser can't be shared.");
3785
3785
  const { endpoint } = await browser.bind("agentcomputer");
3786
- if (!endpoint.startsWith(run2 + sep)) {
3786
+ if (!endpoint.startsWith(run3 + sep)) {
3787
3787
  await context.close();
3788
3788
  throw new Error(`The browser's socket isn't in its own folder: ${endpoint}`);
3789
3789
  }
@@ -3797,7 +3797,7 @@ async function hostBrowser(options) {
3797
3797
  });
3798
3798
  await context.close().catch(() => {
3799
3799
  });
3800
- rmSync(run2, { recursive: true, force: true });
3800
+ rmSync(run3, { recursive: true, force: true });
3801
3801
  process.exit(0);
3802
3802
  };
3803
3803
  for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void stop());
@@ -3846,13 +3846,13 @@ async function serveBrowserMcp(options) {
3846
3846
  }
3847
3847
 
3848
3848
  // src/container.ts
3849
- import { execFile, spawn as spawn3 } from "node:child_process";
3849
+ import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
3850
3850
  import { readFileSync as readFileSync3 } from "node:fs";
3851
3851
  import { connect } from "node:net";
3852
- import { join as join9 } from "node:path";
3852
+ import { join as join8 } from "node:path";
3853
3853
  import { createInterface as createInterface2 } from "node:readline";
3854
3854
  import { Readable as Readable3 } from "node:stream";
3855
- import { promisify } from "node:util";
3855
+ import { promisify as promisify2 } from "node:util";
3856
3856
 
3857
3857
  // src/browser.ts
3858
3858
  import { spawn } from "node:child_process";
@@ -6616,10 +6616,10 @@ var $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
6616
6616
  }
6617
6617
  return void 0;
6618
6618
  });
6619
- const first = def.options.length === 1 ? def.options[0]._zod.run : null;
6619
+ const first2 = def.options.length === 1 ? def.options[0]._zod.run : null;
6620
6620
  inst._zod.parse = (payload, ctx) => {
6621
- if (first) {
6622
- return first(payload, ctx);
6621
+ if (first2) {
6622
+ return first2(payload, ctx);
6623
6623
  }
6624
6624
  let async = false;
6625
6625
  const results = [];
@@ -9017,9 +9017,9 @@ var numberProcessor = (schema, ctx, _json, params) => {
9017
9017
  else
9018
9018
  handleUnrepresentable(schema, ctx, json4, params, `A multipleOf divisor of ${divisor} cannot be represented in JSON Schema`);
9019
9019
  }
9020
- const [first, ...rest] = divisors;
9021
- if (first !== void 0)
9022
- json4.multipleOf = first;
9020
+ const [first2, ...rest] = divisors;
9021
+ if (first2 !== void 0)
9022
+ json4.multipleOf = first2;
9023
9023
  if (rest.length)
9024
9024
  json4.allOf = [...json4.allOf ?? [], ...rest.map((m) => ({ multipleOf: m }))];
9025
9025
  }
@@ -19229,8 +19229,8 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19229
19229
  }
19230
19230
  if (bestLength < 2) return hextets.join(":");
19231
19231
  const head = hextets.slice(0, bestStart).join(":");
19232
- const tail = hextets.slice(bestStart + bestLength).join(":");
19233
- return head + "::" + tail;
19232
+ const tail2 = hextets.slice(bestStart + bestLength).join(":");
19233
+ return head + "::" + tail2;
19234
19234
  }
19235
19235
  function normalizeIPv6Address(input2) {
19236
19236
  const compression = input2.indexOf("::");
@@ -28153,10 +28153,12 @@ var Browser = class {
28153
28153
  // src/inside.ts
28154
28154
  import { createServer as createServer3 } from "node:http";
28155
28155
  import { homedir } from "node:os";
28156
- import { join as join8 } from "node:path";
28156
+ import { join as join7 } from "node:path";
28157
28157
 
28158
28158
  // src/runtime.ts
28159
- import { join as join7 } from "node:path";
28159
+ import { chmodSync as chmodSync2, existsSync, statSync as statSync2 } from "node:fs";
28160
+ import { createRequire as createRequire3 } from "node:module";
28161
+ import { dirname as dirname3, join as join6 } from "node:path";
28160
28162
 
28161
28163
  // node_modules/@agentorg/exec/dist/lib.js
28162
28164
  import { realpathSync } from "node:fs";
@@ -28411,7 +28413,7 @@ var require_ignore = __commonJS2({
28411
28413
  // ever starts again inside it -- `(\\+?) ` did, which made a long run
28412
28414
  // quadratic.
28413
28415
  /(\\+)( ?)/g,
28414
- (_, run2, space) => space ? run2.slice(0, run2.length - run2.length % 2) + SPACE2 : run2,
28416
+ (_, run3, space) => space ? run3.slice(0, run3.length - run3.length % 2) + SPACE2 : run3,
28415
28417
  ESCAPE + SPACE2
28416
28418
  ],
28417
28419
  // git's wildmatch() reads a run of two or more asterisks as one globstar
@@ -28597,10 +28599,10 @@ var require_ignore = __commonJS2({
28597
28599
  var REGEX_REPLACE_TRAILING_DOUBLESTAR = /\uE001$/;
28598
28600
  var replaceTrailingDoublestar = () => ".+(?=$|\\/$)";
28599
28601
  var WILDCARD2 = "[^\\/]*";
28600
- var separatorAfter = (run2, at) => {
28602
+ var separatorAfter = (run3, at) => {
28601
28603
  let separator = EMPTY;
28602
- for (let index = at + 1; index < run2.length && !run2[index].wildcard; index++) {
28603
- separator += run2[index].single;
28604
+ for (let index = at + 1; index < run3.length && !run3[index].wildcard; index++) {
28605
+ separator += run3[index].single;
28604
28606
  }
28605
28607
  return separator;
28606
28608
  };
@@ -28660,26 +28662,26 @@ var require_ignore = __commonJS2({
28660
28662
  }
28661
28663
  }
28662
28664
  let out = EMPTY;
28663
- let run2 = [];
28665
+ let run3 = [];
28664
28666
  const flush = () => {
28665
28667
  let lastWildcard;
28666
- run2.forEach((token, at) => {
28668
+ run3.forEach((token, at) => {
28667
28669
  if (token.wildcard) {
28668
28670
  lastWildcard = at;
28669
28671
  }
28670
28672
  });
28671
- run2.forEach((token, at) => {
28673
+ run3.forEach((token, at) => {
28672
28674
  if (!token.wildcard) {
28673
28675
  out += token.single;
28674
28676
  return;
28675
28677
  }
28676
- out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run2, at)})[^\\/])*`;
28678
+ out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run3, at)})[^\\/])*`;
28677
28679
  });
28678
- run2 = [];
28680
+ run3 = [];
28679
28681
  };
28680
28682
  tokens.forEach((token) => {
28681
28683
  if (token.boundary === void 0) {
28682
- run2.push(token);
28684
+ run3.push(token);
28683
28685
  return;
28684
28686
  }
28685
28687
  flush();
@@ -28959,11 +28961,11 @@ var require_ignore = __commonJS2({
28959
28961
  return true;
28960
28962
  };
28961
28963
  var isNotRelative = (path) => {
28962
- const first = path.charCodeAt(0);
28963
- if (first === SLASH_CODE) {
28964
+ const first2 = path.charCodeAt(0);
28965
+ if (first2 === SLASH_CODE) {
28964
28966
  return true;
28965
28967
  }
28966
- if (first !== DOT_CODE) {
28968
+ if (first2 !== DOT_CODE) {
28967
28969
  return false;
28968
28970
  }
28969
28971
  if (path.length === 1) {
@@ -32872,10 +32874,10 @@ var $ZodUnion2 = /* @__PURE__ */ $constructor2("$ZodUnion", (inst, def) => {
32872
32874
  }
32873
32875
  return void 0;
32874
32876
  });
32875
- const first = def.options.length === 1 ? def.options[0]._zod.run : null;
32877
+ const first2 = def.options.length === 1 ? def.options[0]._zod.run : null;
32876
32878
  inst._zod.parse = (payload, ctx) => {
32877
- if (first) {
32878
- return first(payload, ctx);
32879
+ if (first2) {
32880
+ return first2(payload, ctx);
32879
32881
  }
32880
32882
  let async = false;
32881
32883
  const results = [];
@@ -32932,10 +32934,10 @@ function handleExclusiveUnionResults(results, final, inst, ctx) {
32932
32934
  var $ZodXor2 = /* @__PURE__ */ $constructor2("$ZodXor", (inst, def) => {
32933
32935
  $ZodUnion2.init(inst, def);
32934
32936
  def.inclusive = false;
32935
- const first = def.options.length === 1 ? def.options[0]._zod.run : null;
32937
+ const first2 = def.options.length === 1 ? def.options[0]._zod.run : null;
32936
32938
  inst._zod.parse = (payload, ctx) => {
32937
- if (first) {
32938
- return first(payload, ctx);
32939
+ if (first2) {
32940
+ return first2(payload, ctx);
32939
32941
  }
32940
32942
  let async = false;
32941
32943
  const results = [];
@@ -45270,9 +45272,9 @@ var numberProcessor2 = (schema, ctx, _json, params) => {
45270
45272
  else
45271
45273
  handleUnrepresentable2(schema, ctx, json32, params, `A multipleOf divisor of ${divisor} cannot be represented in JSON Schema`);
45272
45274
  }
45273
- const [first, ...rest] = divisors;
45274
- if (first !== void 0)
45275
- json32.multipleOf = first;
45275
+ const [first2, ...rest] = divisors;
45276
+ if (first2 !== void 0)
45277
+ json32.multipleOf = first2;
45276
45278
  if (rest.length)
45277
45279
  json32.allOf = [...json32.allOf ?? [], ...rest.map((m) => ({ multipleOf: m }))];
45278
45280
  }
@@ -47956,14 +47958,14 @@ function checkArrayGuards(arraySchema, guards) {
47956
47958
  const key = canonicalKey(items[i], /* @__PURE__ */ new Set());
47957
47959
  if (key === null)
47958
47960
  continue;
47959
- const first = firstSeen.get(key);
47960
- if (first === void 0) {
47961
+ const first2 = firstSeen.get(key);
47962
+ if (first2 === void 0) {
47961
47963
  firstSeen.set(key, i);
47962
47964
  continue;
47963
47965
  }
47964
47966
  payload.issues.push({
47965
47967
  code: "custom",
47966
- message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
47968
+ message: `Array items must be unique: element at index ${i} duplicates the one at index ${first2}`,
47967
47969
  input: items,
47968
47970
  path: [i],
47969
47971
  continue: true
@@ -48441,7 +48443,7 @@ function visit(schema, fnOrHandlers) {
48441
48443
  return h ? h(node22, rewritten) : node22;
48442
48444
  };
48443
48445
  const cache = /* @__PURE__ */ new Map();
48444
- function run2(s) {
48446
+ function run3(s) {
48445
48447
  const cached22 = cache.get(s);
48446
48448
  if (cached22 === RESOLVING) {
48447
48449
  return new $ZodLazy2({
@@ -48467,21 +48469,21 @@ function visit(schema, fnOrHandlers) {
48467
48469
  let changed2 = false;
48468
48470
  const newShape = {};
48469
48471
  for (const k of keys) {
48470
- const mapped = run2(oldShape[k]);
48472
+ const mapped = run3(oldShape[k]);
48471
48473
  if (mapped !== oldShape[k])
48472
48474
  changed2 = true;
48473
48475
  newShape[k] = mapped;
48474
48476
  }
48475
48477
  let newCatchall = def.catchall;
48476
48478
  if (def.catchall) {
48477
- newCatchall = run2(def.catchall);
48479
+ newCatchall = run3(def.catchall);
48478
48480
  if (newCatchall !== def.catchall)
48479
48481
  changed2 = true;
48480
48482
  }
48481
48483
  return changed2 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48482
48484
  }
48483
48485
  case "array": {
48484
- const mapped = run2(def.element);
48486
+ const mapped = run3(def.element);
48485
48487
  return mapped === def.element ? s : clone2(s, { ...def, element: mapped });
48486
48488
  }
48487
48489
  case "tuple": {
@@ -48489,14 +48491,14 @@ function visit(schema, fnOrHandlers) {
48489
48491
  let changed2 = false;
48490
48492
  const newItems = [];
48491
48493
  for (const item of oldItems) {
48492
- const mapped = run2(item);
48494
+ const mapped = run3(item);
48493
48495
  if (mapped !== item)
48494
48496
  changed2 = true;
48495
48497
  newItems.push(mapped);
48496
48498
  }
48497
48499
  let newRest = def.rest;
48498
48500
  if (def.rest) {
48499
- newRest = run2(def.rest);
48501
+ newRest = run3(def.rest);
48500
48502
  if (newRest !== def.rest)
48501
48503
  changed2 = true;
48502
48504
  }
@@ -48504,12 +48506,12 @@ function visit(schema, fnOrHandlers) {
48504
48506
  }
48505
48507
  case "record":
48506
48508
  case "map": {
48507
- const newKey = run2(def.keyType);
48508
- const newVal = run2(def.valueType);
48509
+ const newKey = run3(def.keyType);
48510
+ const newVal = run3(def.valueType);
48509
48511
  return newKey === def.keyType && newVal === def.valueType ? s : clone2(s, { ...def, keyType: newKey, valueType: newVal });
48510
48512
  }
48511
48513
  case "set": {
48512
- const newVal = run2(def.valueType);
48514
+ const newVal = run3(def.valueType);
48513
48515
  return newVal === def.valueType ? s : clone2(s, { ...def, valueType: newVal });
48514
48516
  }
48515
48517
  case "union": {
@@ -48517,7 +48519,7 @@ function visit(schema, fnOrHandlers) {
48517
48519
  let changed2 = false;
48518
48520
  const newOptions = [];
48519
48521
  for (const opt of oldOptions) {
48520
- const mapped = run2(opt);
48522
+ const mapped = run3(opt);
48521
48523
  if (mapped !== opt)
48522
48524
  changed2 = true;
48523
48525
  newOptions.push(mapped);
@@ -48525,8 +48527,8 @@ function visit(schema, fnOrHandlers) {
48525
48527
  return changed2 ? clone2(s, { ...def, options: newOptions }) : s;
48526
48528
  }
48527
48529
  case "intersection": {
48528
- const newLeft = run2(def.left);
48529
- const newRight = run2(def.right);
48530
+ const newLeft = run3(def.left);
48531
+ const newRight = run3(def.right);
48530
48532
  return newLeft === def.left && newRight === def.right ? s : clone2(s, { ...def, left: newLeft, right: newRight });
48531
48533
  }
48532
48534
  case "optional":
@@ -48538,23 +48540,23 @@ function visit(schema, fnOrHandlers) {
48538
48540
  case "nonoptional":
48539
48541
  case "promise":
48540
48542
  case "success": {
48541
- const newInner = run2(def.innerType);
48543
+ const newInner = run3(def.innerType);
48542
48544
  return newInner === def.innerType ? s : clone2(s, { ...def, innerType: newInner });
48543
48545
  }
48544
48546
  case "pipe": {
48545
- const newIn = run2(def.in);
48546
- const newOut = run2(def.out);
48547
+ const newIn = run3(def.in);
48548
+ const newOut = run3(def.out);
48547
48549
  return newIn === def.in && newOut === def.out ? s : clone2(s, { ...def, in: newIn, out: newOut });
48548
48550
  }
48549
48551
  case "function": {
48550
- const newInput = run2(def.input);
48551
- const newOutput = run2(def.output);
48552
+ const newInput = run3(def.input);
48553
+ const newOutput = run3(def.output);
48552
48554
  return newInput === def.input && newOutput === def.output ? s : clone2(s, { ...def, input: newInput, output: newOutput });
48553
48555
  }
48554
48556
  case "lazy": {
48555
48557
  const original = def.getter;
48556
48558
  const { _cachedInner, ...rest } = def;
48557
- return clone2(s, { ...rest, getter: () => run2(original()) });
48559
+ return clone2(s, { ...rest, getter: () => run3(original()) });
48558
48560
  }
48559
48561
  // A leaf by choice: `parts` are regex fragments, not data positions.
48560
48562
  case "template_literal":
@@ -48585,7 +48587,7 @@ function visit(schema, fnOrHandlers) {
48585
48587
  }
48586
48588
  }
48587
48589
  }
48588
- return run2(schema);
48590
+ return run3(schema);
48589
48591
  }
48590
48592
  function deepPartial(schema) {
48591
48593
  return visit(schema, {
@@ -56945,8 +56947,8 @@ var require_utils2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
56945
56947
  }
56946
56948
  if (bestLength < 2) return hextets.join(":");
56947
56949
  const head = hextets.slice(0, bestStart).join(":");
56948
- const tail = hextets.slice(bestStart + bestLength).join(":");
56949
- return head + "::" + tail;
56950
+ const tail2 = hextets.slice(bestStart + bestLength).join(":");
56951
+ return head + "::" + tail2;
56950
56952
  }
56951
56953
  function normalizeIPv6Address(input2) {
56952
56954
  const compression = input2.indexOf("::");
@@ -63419,9 +63421,9 @@ var WebStandardStreamableHTTPServerTransport = class {
63419
63421
  }
63420
63422
  });
63421
63423
  }
63422
- async _checkScopeChallenge(messages, authInfo) {
63424
+ async _checkScopeChallenge(messages2, authInfo) {
63423
63425
  if (!this._scopeChallengeResolver) return;
63424
- const challenge = await findScopeChallenge(messages.filter((message) => isJSONRPCRequest2(message)), authInfo, this._scopeChallengeResolver);
63426
+ const challenge = await findScopeChallenge(messages2.filter((message) => isJSONRPCRequest2(message)), authInfo, this._scopeChallengeResolver);
63425
63427
  return challenge === void 0 ? void 0 : createScopeChallengeResponse(challenge, scopeChallengeResourceMetadataUrl(authInfo));
63426
63428
  }
63427
63429
  /**
@@ -63719,21 +63721,21 @@ data:
63719
63721
  this.onerror?.(/* @__PURE__ */ new Error(`Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE} messages`));
63720
63722
  return this.createJsonErrorResponse(400, -32600, `Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE} messages`);
63721
63723
  }
63722
- let messages;
63724
+ let messages2;
63723
63725
  try {
63724
- messages = Array.isArray(rawMessage) ? rawMessage.map((msg) => JSONRPCMessageSchema2.parse(msg)) : [JSONRPCMessageSchema2.parse(rawMessage)];
63726
+ messages2 = Array.isArray(rawMessage) ? rawMessage.map((msg) => JSONRPCMessageSchema2.parse(msg)) : [JSONRPCMessageSchema2.parse(rawMessage)];
63725
63727
  } catch (error62) {
63726
63728
  this.onerror?.(error62);
63727
63729
  return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
63728
63730
  }
63729
63731
  if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
63730
- const isInitializationRequest = messages.some((element) => isInitializeRequest2(element));
63732
+ const isInitializationRequest = messages2.some((element) => isInitializeRequest2(element));
63731
63733
  if (isInitializationRequest) {
63732
63734
  if (this._initialized && this.sessionId !== void 0) {
63733
63735
  this.onerror?.(/* @__PURE__ */ new Error("Invalid Request: Server already initialized"));
63734
63736
  return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
63735
63737
  }
63736
- if (messages.length > 1) {
63738
+ if (messages2.length > 1) {
63737
63739
  this.onerror?.(/* @__PURE__ */ new Error("Invalid Request: Only one initialization request is allowed"));
63738
63740
  return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
63739
63741
  }
@@ -63750,21 +63752,21 @@ data:
63750
63752
  if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
63751
63753
  let scopeChallengeResponse;
63752
63754
  try {
63753
- scopeChallengeResponse = await this._checkScopeChallenge(messages, options?.authInfo);
63755
+ scopeChallengeResponse = await this._checkScopeChallenge(messages2, options?.authInfo);
63754
63756
  } catch (error62) {
63755
63757
  this.onerror?.(error62);
63756
63758
  return this.createJsonErrorResponse(500, -32603, "Internal server error");
63757
63759
  }
63758
63760
  if (scopeChallengeResponse) return scopeChallengeResponse;
63759
- if (!messages.some((element) => isJSONRPCRequest2(element))) {
63760
- for (const message of messages) this.onmessage?.(message, {
63761
+ if (!messages2.some((element) => isJSONRPCRequest2(element))) {
63762
+ for (const message of messages2) this.onmessage?.(message, {
63761
63763
  authInfo: options?.authInfo,
63762
63764
  request
63763
63765
  });
63764
63766
  return new Response(null, { status: 202 });
63765
63767
  }
63766
63768
  const streamId = crypto.randomUUID();
63767
- const initRequest = messages.find((m) => isInitializeRequest2(m));
63769
+ const initRequest = messages2.find((m) => isInitializeRequest2(m));
63768
63770
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION2;
63769
63771
  if (this._enableJsonResponse) return new Promise((resolve42) => {
63770
63772
  this._streamMapping.set(streamId, {
@@ -63773,8 +63775,8 @@ data:
63773
63775
  this._streamMapping.delete(streamId);
63774
63776
  }
63775
63777
  });
63776
- for (const message of messages) if (isJSONRPCRequest2(message)) this._requestToStreamMapping.set(message.id, streamId);
63777
- for (const message of messages) this.onmessage?.(message, {
63778
+ for (const message of messages2) if (isJSONRPCRequest2(message)) this._requestToStreamMapping.set(message.id, streamId);
63779
+ for (const message of messages2) this.onmessage?.(message, {
63778
63780
  authInfo: options?.authInfo,
63779
63781
  request
63780
63782
  });
@@ -63798,7 +63800,7 @@ data:
63798
63800
  "X-Accel-Buffering": "no"
63799
63801
  };
63800
63802
  if (this.sessionId !== void 0) headers["mcp-session-id"] = this.sessionId;
63801
- for (const message of messages) if (isJSONRPCRequest2(message)) {
63803
+ for (const message of messages2) if (isJSONRPCRequest2(message)) {
63802
63804
  this._streamMapping.set(streamId, {
63803
63805
  controller: streamController,
63804
63806
  encoder,
@@ -63814,7 +63816,7 @@ data:
63814
63816
  this._requestToStreamMapping.set(message.id, streamId);
63815
63817
  }
63816
63818
  await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
63817
- for (const message of messages) {
63819
+ for (const message of messages2) {
63818
63820
  let closeSSEStream;
63819
63821
  let closeStandaloneSSEStream;
63820
63822
  if (isJSONRPCRequest2(message) && this._eventStore && this.supportsEmptySSEData(clientProtocolVersion)) {
@@ -65811,10 +65813,10 @@ function cut(text22, max) {
65811
65813
  if (text22.length <= max) return text22;
65812
65814
  const half = Math.max(1, Math.floor(max / 2));
65813
65815
  const head = text22.slice(0, half).replace(/[\uD800-\uDBFF]$/, "");
65814
- const tail = text22.slice(-half).replace(/^[\uDC00-\uDFFF]/, "");
65816
+ const tail2 = text22.slice(-half).replace(/^[\uDC00-\uDFFF]/, "");
65815
65817
  return `${head}
65816
- \u2026 ${text22.length - head.length - tail.length} characters cut \u2026
65817
- ${tail}`;
65818
+ \u2026 ${text22.length - head.length - tail2.length} characters cut \u2026
65819
+ ${tail2}`;
65818
65820
  }
65819
65821
  var Pending = class {
65820
65822
  #head = "";
@@ -65850,6 +65852,10 @@ var DEFAULT_YIELD_MS = 1e4;
65850
65852
  var MIN_POLL_MS = 5e3;
65851
65853
  var WRITE_YIELD_MS = 250;
65852
65854
  var MAX_RUNNING = 64;
65855
+ var MAX_ENDED = 64;
65856
+ var KEPT = 64 * 1024;
65857
+ var CMD_SHOWN = 200;
65858
+ var MAX_SIZE = 1e3;
65853
65859
  var DEFAULT_MAX_TOKENS = 7e3;
65854
65860
  var DRAIN_MS = 200;
65855
65861
  var KILL_AFTER_MS = 1e3;
@@ -65863,48 +65869,183 @@ var SIGNALS = {
65863
65869
  SIGTERM: 15
65864
65870
  };
65865
65871
  var clamp = (value, low, high) => Math.min(Math.max(value, low), high);
65872
+ var tail = (text22) => text22.slice(-KEPT).replace(/^[\uDC00-\uDFFF]/, "");
65866
65873
  var Sessions = class {
65867
65874
  #sessions = /* @__PURE__ */ new Map();
65875
+ /** Those that have ended and are still listed, in the order they ended. */
65876
+ #ended = [];
65868
65877
  #root;
65869
65878
  #emit;
65879
+ #pty;
65870
65880
  constructor(root, emit = () => {
65871
- }) {
65881
+ }, pty2) {
65872
65882
  this.#root = root;
65873
65883
  this.#emit = emit;
65884
+ this.#pty = pty2;
65874
65885
  }
65875
65886
  /** Run `cmd` in a login shell, in `workdir` or the workspace. */
65876
65887
  async exec(input2) {
65877
- const running = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
65878
- if (running.length >= MAX_RUNNING)
65879
- throw new ExecRefused(`${MAX_RUNNING} commands are running. Wait for one, or stop one.`);
65888
+ this.#room();
65880
65889
  const started = Date.now();
65881
65890
  const session = this.#start(input2.cmd, input2.workdir ?? this.#root);
65882
65891
  const wait = clamp(input2.yield_time_ms ?? DEFAULT_YIELD_MS, MIN_YIELD_MS, MAX_YIELD_MS);
65883
65892
  return this.#after(session, wait, started, input2.max_output_tokens);
65884
65893
  }
65885
65894
  /**
65886
- * Type into a running command, or with no `chars` wait and see what it has written. Ctrl-C
65887
- * (`\u0003`) interrupts it and Ctrl-D (`\u0004`) ends its input.
65895
+ * Type into a running terminal, or with no `chars` wait and see what it has written. In a
65896
+ * command, Ctrl-C (`\u0003`) interrupts it and Ctrl-D (`\u0004`) ends its input; a shell on a
65897
+ * terminal device is typed into as it is.
65888
65898
  */
65889
65899
  async write(input2) {
65890
65900
  const session = this.#sessions.get(input2.session_id);
65891
- if (!session) throw new ExecRefused(`No session ${input2.session_id.slice(0, 64)} is running.`);
65901
+ if (!session || session.reported)
65902
+ throw new ExecRefused(`No session ${input2.session_id.slice(0, 64)} is running.`);
65892
65903
  const started = Date.now();
65893
65904
  const chars = input2.chars ?? "";
65894
- if (session.exitCode === void 0) this.#type(session, chars);
65905
+ if (session.exitCode === void 0) session.type(chars);
65895
65906
  const wait = chars ? clamp(input2.yield_time_ms ?? WRITE_YIELD_MS, MIN_YIELD_MS, MAX_YIELD_MS) : clamp(input2.yield_time_ms ?? MIN_POLL_MS, MIN_POLL_MS, MAX_YIELD_MS);
65896
65907
  return this.#after(session, wait, started, input2.max_output_tokens);
65897
65908
  }
65898
- /** Stop every command: a polite signal, then a certain one. */
65909
+ /** Every terminal, the oldest first: those running, and the last that ended. */
65910
+ list() {
65911
+ return [...this.#sessions.values()].map((session) => ({
65912
+ id: session.id,
65913
+ by: session.by,
65914
+ cmd: session.cmd,
65915
+ workdir: session.workdir,
65916
+ tty: session.tty,
65917
+ at: session.at,
65918
+ output: tail(session.last),
65919
+ ...session.exitCode === void 0 ? {} : { exitCode: session.exitCode, endedAt: session.endedAt }
65920
+ }));
65921
+ }
65922
+ /** Start a person's login shell in the workspace, on a terminal device of this size. */
65923
+ open(columns, rows) {
65924
+ if (!this.#pty)
65925
+ throw new ExecRefused("A shell needs a terminal device, and this host has none to give.");
65926
+ this.#room();
65927
+ const size = sized(columns, rows);
65928
+ const file2 = process.env.SHELL || "/bin/bash";
65929
+ let shell;
65930
+ try {
65931
+ shell = this.#pty(file2, ["-l"], {
65932
+ name: "xterm-256color",
65933
+ ...size,
65934
+ cwd: this.#root,
65935
+ env: process.env
65936
+ });
65937
+ } catch (error62) {
65938
+ const why = error62 instanceof Error ? error62.message : String(error62);
65939
+ throw new ExecRefused(`The shell didn't start: ${why}`);
65940
+ }
65941
+ const { session, wrote, finish } = this.#begin("person", file2, this.#root, true, {
65942
+ type: (chars) => shell.write(chars),
65943
+ signal(signal) {
65944
+ try {
65945
+ shell.kill(signal);
65946
+ } catch {
65947
+ }
65948
+ },
65949
+ resize: (cols, lines) => shell.resize(cols, lines)
65950
+ });
65951
+ shell.onData((text22) => wrote(text22));
65952
+ shell.onExit(({ exitCode, signal }) => finish(signal ? 128 + signal : exitCode));
65953
+ return session.id;
65954
+ }
65955
+ /** Type into a running terminal, as `write` does and with no answer. */
65956
+ type(id, chars) {
65957
+ this.#running(id).type(chars);
65958
+ }
65959
+ /** Give a shell's terminal device a new size. */
65960
+ resize(id, columns, rows) {
65961
+ const session = this.#running(id);
65962
+ if (!session.resize) throw new ExecRefused("Only a shell on a terminal device has a size.");
65963
+ const size = sized(columns, rows);
65964
+ session.resize(size.cols, size.rows);
65965
+ }
65966
+ /**
65967
+ * End one terminal: an interrupt, then a certain signal. It resolves once the terminal has
65968
+ * ended or that signal is sent. One that has ended is left alone.
65969
+ */
65970
+ async stop(id) {
65971
+ const session = this.#sessions.get(id);
65972
+ if (!session) throw new ExecRefused(`No terminal ${id.slice(0, 64)} is here.`);
65973
+ await this.#end([session], "SIGINT");
65974
+ }
65975
+ /** Stop every terminal: a polite signal, then a certain one. */
65899
65976
  async stopAll() {
65900
- const running = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
65901
- for (const session of running) this.#signal(session, "SIGTERM");
65977
+ await this.#end([...this.#sessions.values()], "SIGTERM");
65978
+ }
65979
+ async #end(sessions, polite) {
65980
+ const running = sessions.filter((s) => s.exitCode === void 0);
65981
+ for (const session of running) session.signal(session.tty ? "SIGHUP" : polite);
65982
+ let timer;
65902
65983
  await Promise.race([
65903
65984
  Promise.all(running.map((s) => s.ended)),
65904
- new Promise((done) => setTimeout(done, KILL_AFTER_MS))
65985
+ new Promise((done) => {
65986
+ timer = setTimeout(done, KILL_AFTER_MS);
65987
+ })
65905
65988
  ]);
65906
- for (const session of running)
65907
- if (session.exitCode === void 0) this.#signal(session, "SIGKILL");
65989
+ clearTimeout(timer);
65990
+ for (const session of running) if (session.exitCode === void 0) session.signal("SIGKILL");
65991
+ }
65992
+ #room() {
65993
+ const running = [...this.#sessions.values()].filter((s) => s.exitCode === void 0);
65994
+ if (running.length >= MAX_RUNNING)
65995
+ throw new ExecRefused(`${MAX_RUNNING} commands are running. Wait for one, or stop one.`);
65996
+ }
65997
+ #running(id) {
65998
+ const session = this.#sessions.get(id);
65999
+ if (!session) throw new ExecRefused(`No terminal ${id.slice(0, 64)} is here.`);
66000
+ if (session.exitCode !== void 0) throw new ExecRefused("That terminal has ended.");
66001
+ return session;
66002
+ }
66003
+ /**
66004
+ * List a terminal that has just started, with the ways to act on its process, and give the two
66005
+ * ways that process reports back.
66006
+ */
66007
+ #begin(by, cmd, workdir, tty, acts) {
66008
+ const id = `s_${randomBytes(9).toString("hex")}`;
66009
+ const at = (/* @__PURE__ */ new Date()).toISOString();
66010
+ let done = () => {
66011
+ };
66012
+ const session = {
66013
+ id,
66014
+ by,
66015
+ cmd,
66016
+ workdir,
66017
+ tty,
66018
+ at,
66019
+ pending: new Pending(),
66020
+ last: "",
66021
+ ended: new Promise((resolve42) => {
66022
+ done = resolve42;
66023
+ }),
66024
+ ...acts
66025
+ };
66026
+ this.#sessions.set(id, session);
66027
+ this.#emit({ type: "start", id, by, cmd, workdir, tty, at });
66028
+ const wrote = (text22, stream) => {
66029
+ if (!text22) return;
66030
+ session.pending.add(text22);
66031
+ session.last += text22;
66032
+ if (session.last.length > 2 * KEPT) session.last = tail(session.last);
66033
+ this.#emit({ type: "output", id, ...stream ? { stream } : {}, text: text22 });
66034
+ };
66035
+ const finish = (exitCode) => {
66036
+ if (session.exitCode !== void 0) return;
66037
+ session.exitCode = exitCode;
66038
+ session.endedAt = (/* @__PURE__ */ new Date()).toISOString();
66039
+ this.#emit({ type: "exit", id, exitCode, at: session.endedAt });
66040
+ this.#ended.push(session);
66041
+ if (this.#ended.length > MAX_ENDED) {
66042
+ const read = this.#ended.findIndex((one) => one.reported);
66043
+ const [old] = this.#ended.splice(Math.max(read, 0), 1);
66044
+ this.#sessions.delete(old.id);
66045
+ }
66046
+ done();
66047
+ };
66048
+ return { session, wrote, finish };
65908
66049
  }
65909
66050
  #start(cmd, cwd) {
65910
66051
  const child = spawn2(process.env.SHELL || "/bin/bash", ["-lc", cmd], {
@@ -65913,62 +66054,44 @@ var Sessions = class {
65913
66054
  // Its own process group, so a signal reaches what it started.
65914
66055
  detached: true
65915
66056
  });
65916
- const pending = new Pending();
65917
- const id = `s_${randomBytes(9).toString("hex")}`;
65918
- const session = { id, child, pending };
65919
- this.#emit({ type: "start", id, cmd, workdir: cwd, at: (/* @__PURE__ */ new Date()).toISOString() });
66057
+ const signal = (name) => {
66058
+ try {
66059
+ if (child.pid) process.kill(-child.pid, name);
66060
+ } catch {
66061
+ }
66062
+ };
66063
+ const type = (chars) => {
66064
+ for (const part of chars.split(new RegExp("(\\p{Cc})", "u"))) {
66065
+ if (part === "") signal("SIGINT");
66066
+ else if (part === "") child.stdin.end();
66067
+ else if (part && child.stdin.writable) child.stdin.write(part);
66068
+ }
66069
+ };
66070
+ const { session, wrote, finish } = this.#begin("agent", cmd, cwd, false, { type, signal });
65920
66071
  for (const [name, stream] of [
65921
66072
  ["stdout", child.stdout],
65922
66073
  ["stderr", child.stderr]
65923
66074
  ]) {
65924
66075
  const decoder = new StringDecoder("utf8");
65925
- stream.on("data", (chunk) => {
65926
- const text22 = decoder.write(chunk);
65927
- pending.add(text22);
65928
- if (text22) this.#emit({ type: "output", id, stream: name, text: text22 });
65929
- });
66076
+ stream.on("data", (chunk) => wrote(decoder.write(chunk), name));
65930
66077
  }
65931
66078
  child.stdin.on("error", () => {
65932
66079
  });
65933
- session.ended = new Promise((done) => {
65934
- const finish = (code) => {
65935
- if (session.exitCode !== void 0) return;
65936
- session.exitCode = code;
65937
- this.#emit({ type: "exit", id, exitCode: code, at: (/* @__PURE__ */ new Date()).toISOString() });
65938
- done();
65939
- };
65940
- child.once("error", (error62) => {
65941
- pending.add(`${error62.message}
65942
- `);
65943
- this.#emit({ type: "output", id, stream: "stderr", text: `${error62.message}
65944
- ` });
65945
- finish(127);
65946
- });
65947
- child.once("exit", (code, signal) => {
65948
- const exitCode = code ?? 128 + (signal ? SIGNALS[signal] ?? 1 : 1);
65949
- const late = setTimeout(() => finish(exitCode), DRAIN_MS);
65950
- child.once("close", () => {
65951
- clearTimeout(late);
65952
- finish(exitCode);
65953
- });
66080
+ child.once("error", (error62) => {
66081
+ wrote(`${error62.message}
66082
+ `, "stderr");
66083
+ finish(127);
66084
+ });
66085
+ child.once("exit", (code, signal2) => {
66086
+ const exitCode = code ?? 128 + (signal2 ? SIGNALS[signal2] ?? 1 : 1);
66087
+ const late = setTimeout(() => finish(exitCode), DRAIN_MS);
66088
+ child.once("close", () => {
66089
+ clearTimeout(late);
66090
+ finish(exitCode);
65954
66091
  });
65955
66092
  });
65956
- this.#sessions.set(session.id, session);
65957
66093
  return session;
65958
66094
  }
65959
- #type(session, chars) {
65960
- for (const part of chars.split(new RegExp("(\\p{Cc})", "u"))) {
65961
- if (part === "") this.#signal(session, "SIGINT");
65962
- else if (part === "") session.child.stdin.end();
65963
- else if (part && session.child.stdin.writable) session.child.stdin.write(part);
65964
- }
65965
- }
65966
- #signal(session, signal) {
65967
- try {
65968
- if (session.child.pid) process.kill(-session.child.pid, signal);
65969
- } catch {
65970
- }
65971
- }
65972
66095
  async #after(session, waitMs, started, maxTokens) {
65973
66096
  let timer;
65974
66097
  await Promise.race([
@@ -65979,7 +66102,7 @@ var Sessions = class {
65979
66102
  ]);
65980
66103
  clearTimeout(timer);
65981
66104
  const exited = session.exitCode !== void 0;
65982
- if (exited) this.#sessions.delete(session.id);
66105
+ if (exited) session.reported = true;
65983
66106
  return {
65984
66107
  exit_code: exited ? session.exitCode : null,
65985
66108
  seconds: Math.round(Date.now() - started) / 1e3,
@@ -65988,6 +66111,12 @@ var Sessions = class {
65988
66111
  };
65989
66112
  }
65990
66113
  };
66114
+ function sized(columns, rows) {
66115
+ for (const value of [columns, rows])
66116
+ if (!Number.isInteger(value) || value < 1 || value > MAX_SIZE)
66117
+ throw new ExecRefused(`A terminal has 1 to ${MAX_SIZE} columns and rows.`);
66118
+ return { cols: columns, rows };
66119
+ }
65991
66120
  function formatAnswer(answer) {
65992
66121
  return [
65993
66122
  `exit_code: ${answer.exit_code ?? "running"}`,
@@ -65997,6 +66126,31 @@ function formatAnswer(answer) {
65997
66126
  answer.text
65998
66127
  ].join("\n");
65999
66128
  }
66129
+ function listing(terminals) {
66130
+ return terminals.map((one) => {
66131
+ const line = one.cmd.split("\n", 1)[0].slice(0, CMD_SHOWN);
66132
+ const shown = line.replace(/[\uD800-\uDBFF]$/, "");
66133
+ return {
66134
+ session_id: one.id,
66135
+ started_by: one.by,
66136
+ since: one.at,
66137
+ exit_code: one.exitCode ?? null,
66138
+ cmd: shown === one.cmd ? shown : `${shown}\u2026`
66139
+ };
66140
+ });
66141
+ }
66142
+ function formatSessions(listed) {
66143
+ if (listed.length === 0) return "No sessions.";
66144
+ return listed.map(
66145
+ (one) => [
66146
+ `session_id: ${one.session_id}`,
66147
+ `started_by: ${one.started_by}`,
66148
+ `since: ${one.since}`,
66149
+ `exit_code: ${one.exit_code ?? "running"}`,
66150
+ `cmd: ${one.cmd}`
66151
+ ].join("\n")
66152
+ ).join("\n\n");
66153
+ }
66000
66154
  var DEFAULT_LINES = 2e3;
66001
66155
  var MAX_READ_CHARS = 28e3;
66002
66156
  var MAX_LINE_CHARS = 2e3;
@@ -66060,10 +66214,10 @@ function createFiles(root) {
66060
66214
  /** Numbered lines of a file. */
66061
66215
  read: (input2) => refusing(async () => {
66062
66216
  const lines = (await text22(await at(input2.path), input2.path)).split("\n");
66063
- const first = Math.max(input2.offset ?? 1, 1);
66064
- const shown = lines.slice(first - 1, first - 1 + (input2.limit ?? DEFAULT_LINES)).map((line, i) => `${first + i} ${cut(line, MAX_LINE_CHARS)}`);
66065
- const last = first + shown.length - 1;
66066
- return `lines ${first}-${last} of ${lines.length}
66217
+ const first2 = Math.max(input2.offset ?? 1, 1);
66218
+ const shown = lines.slice(first2 - 1, first2 - 1 + (input2.limit ?? DEFAULT_LINES)).map((line, i) => `${first2 + i} ${cut(line, MAX_LINE_CHARS)}`);
66219
+ const last = first2 + shown.length - 1;
66220
+ return `lines ${first2}-${last} of ${lines.length}
66067
66221
  ${cut(shown.join("\n"), MAX_READ_CHARS)}`;
66068
66222
  }),
66069
66223
  /** A folder's entries, folders first, each folder ending in `/`. */
@@ -66409,9 +66563,9 @@ function createServer2(root, sessions, version22, level) {
66409
66563
  server.registerTool(
66410
66564
  "write_stdin",
66411
66565
  {
66412
- description: "Types into a running command and returns what it wrote since. With no chars, waits and returns what it has written. A Ctrl-C character (\\u0003) interrupts it; Ctrl-D (\\u0004) ends its input.",
66566
+ description: "Types into a running command and returns what it wrote since. With no chars, waits and returns what it has written. A Ctrl-C character (\\u0003) interrupts it; Ctrl-D (\\u0004) ends its input. It also reaches a shell a person opened, by its id from list_sessions: there these are keys, what you type joins anything already typed, and Ctrl-D at its prompt closes it. Once an answer has given a session's exit code, that session is refused.",
66413
66567
  inputSchema: {
66414
- session_id: external_exports.string().min(1).describe("From exec_command's answer."),
66568
+ session_id: external_exports.string().min(1).describe("From exec_command's answer, or list_sessions."),
66415
66569
  chars: external_exports.string().optional().describe("What to type; include \\n to press Enter."),
66416
66570
  yield_time_ms: YieldTime.describe(
66417
66571
  "How long to wait: with chars 250 to 30000, by default 250; with none 5000 to 30000."
@@ -66423,6 +66577,28 @@ function createServer2(root, sessions, version22, level) {
66423
66577
  },
66424
66578
  (input2) => answering(() => sessions.write(input2))
66425
66579
  );
66580
+ server.registerTool(
66581
+ "list_sessions",
66582
+ {
66583
+ description: "Lists the sessions here, running or lately ended: each command exec_command started, and each shell a person opened. Gives each one's session_id for write_stdin, who started it, since when, its exit code and the start of its command.",
66584
+ outputSchema: {
66585
+ sessions: external_exports.array(
66586
+ external_exports.object({
66587
+ session_id: external_exports.string(),
66588
+ started_by: external_exports.enum(["agent", "person"]).describe("A person's is their own shell."),
66589
+ since: external_exports.string().describe("When it started."),
66590
+ exit_code: external_exports.number().nullable().describe("Null while it is still running."),
66591
+ cmd: external_exports.string().describe("Its first line, or the start of it.")
66592
+ })
66593
+ )
66594
+ },
66595
+ annotations: READ_ONLY
66596
+ },
66597
+ () => {
66598
+ const listed = listing(sessions.list());
66599
+ return { ...text(formatSessions(listed)), structuredContent: { sessions: listed } };
66600
+ }
66601
+ );
66426
66602
  }
66427
66603
  server.registerTool(
66428
66604
  "read",
@@ -66478,18 +66654,22 @@ function createServer2(root, sessions, version22, level) {
66478
66654
  );
66479
66655
  return server;
66480
66656
  }
66481
- var VERSION = "0.0.4";
66657
+ var VERSION = "0.0.5";
66482
66658
  function createAgentExec(options) {
66483
66659
  const root = realpathSync(resolve3(options.workspace));
66484
66660
  const level = options.level ?? "read";
66485
66661
  const listeners = /* @__PURE__ */ new Set();
66486
- const sessions = new Sessions(root, (event) => {
66487
- for (const listener of listeners)
66488
- try {
66489
- listener(event);
66490
- } catch {
66491
- }
66492
- });
66662
+ const sessions = new Sessions(
66663
+ root,
66664
+ (event) => {
66665
+ for (const listener of listeners)
66666
+ try {
66667
+ listener(event);
66668
+ } catch {
66669
+ }
66670
+ },
66671
+ options.pty
66672
+ );
66493
66673
  const host = createHostFiles(root, level);
66494
66674
  return {
66495
66675
  async handle(request, response, body) {
@@ -66507,6 +66687,16 @@ function createAgentExec(options) {
66507
66687
  return () => void listeners.delete(listener);
66508
66688
  },
66509
66689
  files: host.files,
66690
+ terminals: {
66691
+ list: () => sessions.list(),
66692
+ open({ columns, rows }) {
66693
+ if (level !== "terminal") throw new Error("A shell needs the terminal level.");
66694
+ return sessions.open(columns, rows);
66695
+ },
66696
+ write: (id, text22) => sessions.type(id, text22),
66697
+ resize: (id, columns, rows) => sessions.resize(id, columns, rows),
66698
+ stop: (id) => sessions.stop(id)
66699
+ },
66510
66700
  async close() {
66511
66701
  await Promise.all([sessions.stopAll(), host.close()]);
66512
66702
  }
@@ -66927,11 +67117,6 @@ var BrowserLive = class {
66927
67117
  }
66928
67118
  };
66929
67119
 
66930
- // src/live.ts
66931
- import { chmodSync, existsSync, statSync as statSync2 } from "node:fs";
66932
- import { createRequire as createRequire3 } from "node:module";
66933
- import { dirname as dirname3, join as join5 } from "node:path";
66934
-
66935
67120
  // node_modules/ws/wrapper.mjs
66936
67121
  var import_stream2 = __toESM(require_stream(), 1);
66937
67122
  var import_extension = __toESM(require_extension(), 1);
@@ -67109,183 +67294,263 @@ var FilesLive = class {
67109
67294
  }
67110
67295
  };
67111
67296
 
67297
+ // src/text.ts
67298
+ var MAX_TEXT = 1e4;
67299
+ function* pieces(text3, most = MAX_TEXT) {
67300
+ for (let from = 0; from < text3.length; ) {
67301
+ let to = Math.min(from + most, text3.length);
67302
+ const last = text3.charCodeAt(to - 1);
67303
+ if (to < text3.length && last >= 55296 && last <= 56319) to -= 1;
67304
+ yield text3.slice(from, to);
67305
+ from = to;
67306
+ }
67307
+ }
67308
+
67112
67309
  // src/live.ts
67113
67310
  var PROTOCOLS = {
67114
- commands: "commands@1",
67115
- terminal: "pty@1",
67311
+ terminal: "terminal@1",
67116
67312
  files: "files@1",
67117
- browser: "screencast@1"
67313
+ browser: "screencast@1",
67314
+ load: "metrics@1"
67118
67315
  };
67119
- var MAX_EVENTS = 2e3;
67120
- var MAX_SCROLLBACK = 64 * 1024;
67121
- var OUTPUT = 48;
67122
- var INPUT = 48;
67123
- var RESIZE = 49;
67124
- var sockets = new import_websocket_server.default({ noServer: true });
67125
- var fileSockets = new import_websocket_server.default({ noServer: true, maxPayload: MAX_PART });
67126
- var pty;
67127
- var loadPty = () => {
67128
- pty ??= (async () => {
67129
- const require3 = createRequire3(import.meta.url);
67130
- let loaded2;
67131
- try {
67132
- loaded2 = require3("node-pty");
67133
- } catch {
67134
- return "The terminal needs node-pty, which isn't installed with this service.";
67135
- }
67136
- const root = dirname3(require3.resolve("node-pty/package.json"));
67137
- for (const helper of [
67138
- join5(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
67139
- join5(root, "build", "Release", "spawn-helper")
67140
- ])
67141
- try {
67142
- if (existsSync(helper) && (statSync2(helper).mode & 73) === 0) chmodSync(helper, 493);
67143
- } catch {
67144
- return `The terminal needs ${helper} to be executable, and it isn't.`;
67145
- }
67146
- return loaded2;
67147
- })();
67148
- return pty;
67316
+ var MAX_MESSAGE = 64 * 1024;
67317
+ var MAX_BUFFERED3 = 4 * 1024 * 1024;
67318
+ var deliver = (ws, message) => {
67319
+ if (ws.bufferedAmount > MAX_BUFFERED3) ws.terminate();
67320
+ else ws.send(JSON.stringify(message));
67321
+ };
67322
+ var MAX_CMD = 8e3;
67323
+ var MAX_WORKDIR = 2e3;
67324
+ var SERVERS = {
67325
+ terminal: new import_websocket_server.default({ noServer: true, maxPayload: MAX_MESSAGE }),
67326
+ files: new import_websocket_server.default({ noServer: true, maxPayload: MAX_PART }),
67327
+ browser: new import_websocket_server.default({ noServer: true }),
67328
+ load: new import_websocket_server.default({ noServer: true, maxPayload: MAX_MESSAGE })
67329
+ };
67330
+ var MALFORMED = "That isn't a terminal@1 message.";
67331
+ var first = (text3, most) => {
67332
+ const [head = ""] = pieces(text3, most);
67333
+ return head;
67149
67334
  };
67335
+ function* messages(event) {
67336
+ if (event.type === "start")
67337
+ yield { ...event, cmd: first(event.cmd, MAX_CMD), workdir: first(event.workdir, MAX_WORKDIR) };
67338
+ else if (event.type === "exit") yield event;
67339
+ else for (const text3 of pieces(event.text)) yield { ...event, text: text3 };
67340
+ }
67150
67341
  var Live = class {
67151
- #workspace;
67152
- #terminal;
67153
- #events = [];
67154
- #watchers = /* @__PURE__ */ new Set();
67155
- #shells = /* @__PURE__ */ new Map();
67156
- #shell;
67157
- #scrollback = "";
67342
+ #terminals;
67343
+ #viewers = /* @__PURE__ */ new Set();
67158
67344
  #browser;
67159
67345
  #files;
67160
67346
  /** Whether a person may write files here: not where the agent can only read them. */
67161
67347
  #writes;
67162
- constructor(exec, workspace, access, browser) {
67163
- this.#workspace = workspace;
67164
- this.#terminal = access === "terminal" && exec !== void 0;
67348
+ #load;
67349
+ constructor(exec, access, load, browser) {
67350
+ this.#load = load;
67351
+ if (exec && access === "terminal") {
67352
+ this.#terminals = exec.terminals;
67353
+ exec.onCommand((event) => {
67354
+ for (const message of messages(event)) for (const ws of this.#viewers) deliver(ws, message);
67355
+ });
67356
+ }
67165
67357
  this.#files = exec && new FilesLive(exec.files);
67166
67358
  this.#writes = access !== "read";
67167
67359
  this.#browser = browser;
67168
- exec?.onCommand((event) => this.#heard(event));
67169
67360
  }
67170
- async surfaces() {
67361
+ surfaces() {
67171
67362
  const surfaces = [];
67172
- if (this.#terminal) {
67173
- const loaded2 = await loadPty();
67174
- surfaces.push(
67175
- { surface: "commands", protocol: PROTOCOLS.commands, operate: false },
67176
- {
67177
- surface: "terminal",
67178
- protocol: PROTOCOLS.terminal,
67179
- operate: true,
67180
- ...typeof loaded2 === "string" ? { unavailable: loaded2 } : {}
67181
- }
67182
- );
67183
- }
67363
+ if (this.#terminals)
67364
+ surfaces.push({ surface: "terminal", protocol: PROTOCOLS.terminal, operate: true });
67184
67365
  if (this.#files)
67185
67366
  surfaces.push({ surface: "files", protocol: PROTOCOLS.files, operate: this.#writes });
67186
67367
  if (this.#browser)
67187
67368
  surfaces.push({ surface: "browser", protocol: PROTOCOLS.browser, operate: true });
67369
+ surfaces.push({ surface: "load", protocol: PROTOCOLS.load, operate: false });
67188
67370
  return surfaces;
67189
67371
  }
67190
67372
  /** Take an upgraded request as a socket on one surface. */
67191
67373
  open(surface, mode, request, socket, head) {
67192
67374
  socket.on("error", () => socket.destroy());
67193
- const server = surface === "files" ? fileSockets : sockets;
67194
- server.handleUpgrade(request, socket, head, (ws) => {
67375
+ SERVERS[surface].handleUpgrade(request, socket, head, (ws) => {
67195
67376
  ws.on("error", () => ws.terminate());
67196
- if (surface === "commands") this.#watch(ws);
67377
+ if (surface === "terminal" && this.#terminals) this.#view(ws, mode, this.#terminals);
67197
67378
  else if (surface === "files" && this.#files) this.#files.open(ws, mode);
67198
67379
  else if (surface === "browser" && this.#browser) this.#browser.open(ws, mode);
67199
- else if (surface === "terminal") this.#attach(ws, mode).catch(() => ws.terminate());
67380
+ else if (surface === "load") this.#load.open(ws);
67200
67381
  else ws.close(1011, `There's no ${surface} here.`);
67201
67382
  });
67202
67383
  }
67384
+ /** The terminals themselves end with the AgentExec that runs them. */
67203
67385
  close() {
67204
67386
  void this.#browser?.close();
67205
67387
  this.#files?.close();
67206
- for (const ws of [...this.#watchers, ...this.#shells.keys()]) ws.close(1001, "It stopped.");
67207
- this.#shell?.kill();
67208
- this.#shell = void 0;
67209
- }
67210
- #heard(event) {
67211
- this.#events.push(event);
67212
- if (this.#events.length > MAX_EVENTS) this.#events.splice(0, this.#events.length - MAX_EVENTS);
67213
- const message = JSON.stringify(event);
67214
- for (const ws of this.#watchers) ws.send(message);
67215
- }
67216
- /** commands@1: each event as a JSON message, those before it first. Nothing comes back. */
67217
- #watch(ws) {
67218
- for (const event of this.#events) ws.send(JSON.stringify(event));
67219
- this.#watchers.add(ws);
67220
- ws.on("close", () => this.#watchers.delete(ws));
67221
- }
67222
- /** pty@1: one shell for the computer, shared by every socket on it. */
67223
- async #attach(ws, mode) {
67224
- const loaded2 = await loadPty();
67225
- if (typeof loaded2 === "string") return ws.close(1011, loaded2);
67226
- if (!this.#shell) {
67227
- this.#scrollback = "";
67228
- let shell;
67388
+ this.#load.close();
67389
+ for (const ws of this.#viewers) ws.close(1001, "It stopped.");
67390
+ }
67391
+ /**
67392
+ * terminal@1: every terminal on one socket. Those already here come first, each with the end of
67393
+ * what it wrote, and then each event as it happens. Listing and joining are one step, so nothing
67394
+ * falls between them.
67395
+ */
67396
+ #view(ws, mode, terminals) {
67397
+ for (const { id, by, cmd, workdir, tty, at, output: output2, exitCode, endedAt } of terminals.list()) {
67398
+ const events = [{ type: "start", id, by, cmd, workdir, tty, at }];
67399
+ if (output2) events.push({ type: "output", id, text: output2 });
67400
+ if (exitCode !== void 0 && endedAt)
67401
+ events.push({ type: "exit", id, exitCode, at: endedAt });
67402
+ for (const event of events)
67403
+ for (const message of messages(event)) ws.send(JSON.stringify(message));
67404
+ }
67405
+ this.#viewers.add(ws);
67406
+ ws.on("close", () => this.#viewers.delete(ws));
67407
+ if (mode !== "operate") return;
67408
+ const refuse = (why) => deliver(ws, { type: "refused", message: reason2(why) });
67409
+ ws.on("message", (data) => {
67229
67410
  try {
67230
- shell = loaded2.spawn(process.env.SHELL || "/bin/bash", ["-l"], {
67231
- name: "xterm-256color",
67232
- cols: 80,
67233
- rows: 24,
67234
- cwd: this.#workspace,
67235
- env: process.env
67236
- });
67411
+ const sent = JSON.parse(data.toString()) ?? {};
67412
+ const { type, id, text: text3 } = sent;
67413
+ const [columns, rows] = [sent.columns, sent.rows];
67414
+ if (type === "open") terminals.open({ columns, rows });
67415
+ else if (typeof id !== "string") throw new Error(MALFORMED);
67416
+ else if (type === "input" && typeof text3 === "string") terminals.write(id, text3);
67417
+ else if (type === "resize") terminals.resize(id, columns, rows);
67418
+ else if (type === "stop") terminals.stop(id).catch(refuse);
67419
+ else throw new Error(MALFORMED);
67237
67420
  } catch (error62) {
67238
- return ws.close(1011, `The shell didn't start: ${String(error62)}`.slice(0, 120));
67421
+ refuse(error62 instanceof SyntaxError ? MALFORMED : error62);
67239
67422
  }
67240
- shell.onData((data) => {
67241
- this.#scrollback = (this.#scrollback + data).slice(-MAX_SCROLLBACK);
67242
- const frame = Buffer.concat([Buffer.from([OUTPUT]), Buffer.from(data)]);
67243
- for (const client of this.#shells.keys()) client.send(frame);
67244
- });
67245
- shell.onExit(() => {
67246
- if (this.#shell !== shell) return;
67247
- this.#shell = void 0;
67248
- for (const client of this.#shells.keys()) client.close(1e3, "The shell exited.");
67249
- });
67250
- this.#shell = shell;
67251
- }
67252
- if (this.#scrollback)
67253
- ws.send(Buffer.concat([Buffer.from([OUTPUT]), Buffer.from(this.#scrollback)]));
67254
- this.#shells.set(ws, mode);
67255
- ws.on("close", () => this.#shells.delete(ws));
67256
- ws.on("message", (data, binary) => {
67257
- const frame = binary ? data : Buffer.from(data);
67258
- if (mode !== "operate" || frame.length === 0 || !this.#shell) return;
67259
- if (frame[0] === INPUT) this.#shell.write(frame.subarray(1).toString());
67260
- else if (frame[0] === RESIZE)
67261
- try {
67262
- const { columns, rows } = JSON.parse(frame.subarray(1).toString());
67263
- if (columns > 0 && rows > 0) this.#shell.resize(Math.floor(columns), Math.floor(rows));
67264
- } catch {
67265
- }
67266
67423
  });
67267
67424
  }
67268
67425
  };
67269
67426
 
67427
+ // src/load-live.ts
67428
+ import { execFile } from "node:child_process";
67429
+ import { readFile as readFile2 } from "node:fs/promises";
67430
+ import { availableParallelism, cpus, freemem, totalmem } from "node:os";
67431
+ import { promisify } from "node:util";
67432
+ var EVERY_MS = 2e3;
67433
+ var run = promisify(execFile);
67434
+ var numberOf = (text3, name) => Number(new RegExp(`^${name}\\s+(\\d+)`, "m").exec(text3)?.[1]);
67435
+ async function disk(folder) {
67436
+ const { stdout } = await run("df", ["-kP", folder], { timeout: EVERY_MS });
67437
+ const [, total, used] = /\s(\d+)\s+(\d+)\s+\d+\s+\d+%/.exec(stdout) ?? [];
67438
+ if (!total) throw new Error("No disk.");
67439
+ return { diskUsed: Number(used) * 1024, diskTotal: Number(total) * 1024 };
67440
+ }
67441
+ async function machine() {
67442
+ const cpu = { used: 0, total: 0 };
67443
+ for (const { times } of cpus()) {
67444
+ cpu.used += times.user + times.nice + times.sys + times.irq;
67445
+ cpu.total += times.user + times.nice + times.sys + times.irq + times.idle;
67446
+ }
67447
+ if (process.platform !== "darwin")
67448
+ return { scope: "machine", cpu, memUsed: totalmem() - freemem() };
67449
+ const { stdout } = await run("/usr/bin/vm_stat", { timeout: EVERY_MS });
67450
+ const pages = numberOf(stdout, "Pages active:") + numberOf(stdout, "Pages wired down:") + numberOf(stdout, "Pages occupied by compressor:");
67451
+ return { scope: "machine", cpu, memUsed: pages * Number(/page size of (\d+)/.exec(stdout)?.[1]) };
67452
+ }
67453
+ async function cgroup() {
67454
+ const read = (file2) => readFile2(`/sys/fs/cgroup/${file2}`, "utf8");
67455
+ const [cpu, current, memory] = await Promise.all([
67456
+ read("cpu.stat"),
67457
+ read("memory.current"),
67458
+ read("memory.stat")
67459
+ ]);
67460
+ return {
67461
+ scope: "computer",
67462
+ // No container has a limit, so the time there was is every CPU's.
67463
+ cpu: {
67464
+ used: numberOf(cpu, "usage_usec"),
67465
+ total: performance.now() * 1e3 * availableParallelism()
67466
+ },
67467
+ // Less the files it has read and no longer uses, as `docker stats` counts it.
67468
+ memUsed: Number(current) - numberOf(memory, "inactive_file")
67469
+ };
67470
+ }
67471
+ var LoadLive = class {
67472
+ #folder;
67473
+ #contained;
67474
+ #sockets = /* @__PURE__ */ new Set();
67475
+ #timer;
67476
+ #reading = false;
67477
+ #before;
67478
+ #last;
67479
+ /**
67480
+ * `folder` is on the disk that is reported. `contained` is whether the processes here are one
67481
+ * computer's and nothing else's, as they are inside a container.
67482
+ */
67483
+ constructor(folder, contained) {
67484
+ this.#folder = folder;
67485
+ this.#contained = contained;
67486
+ }
67487
+ /** metrics@1: a reading every two seconds, the last one at once. What it sends is dropped. */
67488
+ open(ws) {
67489
+ this.#sockets.add(ws);
67490
+ if (this.#last) ws.send(this.#last);
67491
+ ws.on("close", () => {
67492
+ this.#sockets.delete(ws);
67493
+ if (this.#sockets.size === 0) this.close();
67494
+ });
67495
+ if (this.#timer) return;
67496
+ this.#timer = setInterval(() => void this.#read(), EVERY_MS);
67497
+ void this.#read();
67498
+ }
67499
+ close() {
67500
+ clearInterval(this.#timer);
67501
+ this.#timer = this.#before = this.#last = void 0;
67502
+ for (const ws of this.#sockets) ws.close(1001, "It stopped.");
67503
+ }
67504
+ /** One at a time, and for the watching it began under: a reading taken too late is left. */
67505
+ async #read() {
67506
+ const timer = this.#timer;
67507
+ if (this.#reading) return;
67508
+ this.#reading = true;
67509
+ try {
67510
+ const sample = await (this.#contained ? cgroup().catch(machine) : machine());
67511
+ const volume = await disk(this.#folder);
67512
+ if (this.#timer !== timer) return;
67513
+ const before = this.#before;
67514
+ this.#before = sample.cpu;
67515
+ if (!before) return;
67516
+ const share = (sample.cpu.used - before.used) / (sample.cpu.total - before.total) || 0;
67517
+ const reading = {
67518
+ type: "reading",
67519
+ scope: sample.scope,
67520
+ cpuUsedPct: Math.round(Math.min(Math.max(share, 0), 1) * 1e3) / 10,
67521
+ memUsed: sample.memUsed,
67522
+ memTotal: totalmem(),
67523
+ ...volume
67524
+ };
67525
+ if (!Number.isFinite(reading.cpuUsedPct + reading.memUsed)) return;
67526
+ this.#last = JSON.stringify(reading);
67527
+ for (const ws of this.#sockets) if (ws.bufferedAmount === 0) ws.send(this.#last);
67528
+ } catch {
67529
+ } finally {
67530
+ this.#reading = false;
67531
+ }
67532
+ }
67533
+ };
67534
+
67270
67535
  // src/store.ts
67271
67536
  import { createHash, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
67272
- import { chmodSync as chmodSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
67273
- import { join as join6 } from "node:path";
67537
+ import { chmodSync, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
67538
+ import { join as join5 } from "node:path";
67274
67539
  var newToken = () => randomBytes4(32).toString("base64url");
67275
67540
  var digestOf = (token) => createHash("sha256").update(token).digest("hex");
67276
67541
  var sameToken = (token, digest) => timingSafeEqual(Buffer.from(digestOf(token), "hex"), Buffer.from(digest, "hex"));
67277
67542
  function writePrivate(file2, text3) {
67278
67543
  writeFileSync(`${file2}.tmp`, text3, { mode: 384, flush: true });
67279
67544
  renameSync(`${file2}.tmp`, file2);
67280
- chmodSync2(file2, 384);
67545
+ chmodSync(file2, 384);
67281
67546
  }
67282
67547
  var privateFolder = (dir) => {
67283
67548
  mkdirSync(dir, { recursive: true, mode: 448 });
67284
- chmodSync2(dir, 448);
67549
+ chmodSync(dir, 448);
67285
67550
  return dir;
67286
67551
  };
67287
67552
  function controlToken(data) {
67288
- const file2 = join6(data, "control-token");
67553
+ const file2 = join5(data, "control-token");
67289
67554
  try {
67290
67555
  return readFileSync2(file2, "utf8").trim();
67291
67556
  } catch (error62) {
@@ -67305,11 +67570,41 @@ var ApiError = class extends Error {
67305
67570
  this.status = status;
67306
67571
  }
67307
67572
  };
67573
+ var pty;
67574
+ var loadPty = () => {
67575
+ pty ??= (async () => {
67576
+ const fails = (why) => () => {
67577
+ throw new Error(why);
67578
+ };
67579
+ const require3 = createRequire3(import.meta.url);
67580
+ let loaded2;
67581
+ try {
67582
+ loaded2 = require3("node-pty");
67583
+ } catch {
67584
+ return fails("node-pty isn't installed with this service.");
67585
+ }
67586
+ const root = dirname3(require3.resolve("node-pty/package.json"));
67587
+ for (const helper of [
67588
+ join6(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
67589
+ join6(root, "build", "Release", "spawn-helper")
67590
+ ])
67591
+ try {
67592
+ if (existsSync(helper) && (statSync2(helper).mode & 73) === 0) chmodSync2(helper, 493);
67593
+ } catch {
67594
+ return fails(`${helper} has to be executable, and it isn't.`);
67595
+ }
67596
+ return loaded2.spawn;
67597
+ })();
67598
+ return pty;
67599
+ };
67308
67600
  var ProcessRuntime = class {
67309
67601
  isolation = "none";
67310
67602
  #config;
67311
- constructor(config3) {
67603
+ #contained;
67604
+ /** `contained` is for inside a container, where load is the computer's own. */
67605
+ constructor(config3, contained = false) {
67312
67606
  this.#config = config3;
67607
+ this.#contained = contained;
67313
67608
  }
67314
67609
  async prepare() {
67315
67610
  }
@@ -67320,11 +67615,15 @@ var ProcessRuntime = class {
67320
67615
  async start(spec, folder, events) {
67321
67616
  let exec;
67322
67617
  if (spec.access !== "none")
67323
- exec = createAgentExec({ workspace: spec.workspace, level: spec.access });
67618
+ exec = createAgentExec({
67619
+ workspace: spec.workspace,
67620
+ level: spec.access,
67621
+ pty: await loadPty()
67622
+ });
67324
67623
  let browser;
67325
67624
  let browserDown = false;
67326
67625
  if (spec.browser) {
67327
- const own3 = privateFolder(join7(folder, "browser"));
67626
+ const own3 = privateFolder(join6(folder, "browser"));
67328
67627
  browser = new Browser(
67329
67628
  this.#config,
67330
67629
  own3,
@@ -67348,8 +67647,9 @@ var ProcessRuntime = class {
67348
67647
  browserLive = new BrowserLive(browser.endpoint);
67349
67648
  browser.gate = browserLive;
67350
67649
  }
67351
- const live = new Live(exec, spec.workspace, spec.access, browserLive);
67352
- const surfaces = await live.surfaces();
67650
+ const load = new LoadLive(spec.workspace ?? folder, this.#contained);
67651
+ const live = new Live(exec, spec.access, load, browserLive);
67652
+ const surfaces = live.surfaces();
67353
67653
  return {
67354
67654
  browserTools: browser?.tools ?? [],
67355
67655
  /** As they are now: a browser that has stopped can't be opened, and says so. */
@@ -67384,13 +67684,13 @@ async function serveInside(options) {
67384
67684
  if (!token) throw new Error(`${INNER_TOKEN_VARIABLE} is not set.`);
67385
67685
  delete process.env[INNER_TOKEN_VARIABLE];
67386
67686
  const digest = digestOf(token);
67387
- const running = new ProcessRuntime({ path: BROWSER, sandbox: false }).start(
67687
+ const running = new ProcessRuntime({ path: BROWSER, sandbox: false }, true).start(
67388
67688
  {
67389
67689
  id: "inside",
67390
67690
  ...options,
67391
67691
  ...options.access === "none" ? {} : { workspace: "/workspace" }
67392
67692
  },
67393
- privateFolder(join8(homedir(), ".agentcomputer")),
67693
+ privateFolder(join7(homedir(), ".agentcomputer")),
67394
67694
  {
67395
67695
  log: (line) => process.stdout.write(`${line}
67396
67696
  `),
@@ -67425,7 +67725,7 @@ async function serveInside(options) {
67425
67725
  socket.on("error", () => socket.destroy());
67426
67726
  const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
67427
67727
  const url3 = new URL(request.url ?? "/", "http://localhost");
67428
- const surface = /^\/live\/(commands|terminal|files|browser)$/.exec(url3.pathname)?.[1];
67728
+ const surface = /^\/live\/(terminal|files|browser|load)$/.exec(url3.pathname)?.[1];
67429
67729
  const mode = url3.searchParams.get("mode");
67430
67730
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
67431
67731
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
@@ -67440,7 +67740,7 @@ async function serveInside(options) {
67440
67740
  }
67441
67741
 
67442
67742
  // src/container.ts
67443
- var run = promisify(execFile);
67743
+ var run2 = promisify2(execFile2);
67444
67744
  var START_MS2 = 6e4;
67445
67745
  var STOP_SECONDS = "5";
67446
67746
  var NOT_FORWARDED = /* @__PURE__ */ new Set([
@@ -67454,7 +67754,7 @@ var NOT_FORWARDED = /* @__PURE__ */ new Set([
67454
67754
  ]);
67455
67755
  async function docker(args, env = process.env) {
67456
67756
  try {
67457
- return (await run("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67757
+ return (await run2("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67458
67758
  } catch (error62) {
67459
67759
  const { stderr, message } = error62;
67460
67760
  throw new Error(stderr?.trim() || message);
@@ -67533,7 +67833,7 @@ var ContainerRuntime = class {
67533
67833
  }
67534
67834
  /** The token between the service and this container, which a restart has to find again. */
67535
67835
  #tokenIn(folder) {
67536
- const file2 = join9(folder, "inner-token");
67836
+ const file2 = join8(folder, "inner-token");
67537
67837
  try {
67538
67838
  return readFileSync3(file2, "utf8");
67539
67839
  } catch {
@@ -67672,7 +67972,7 @@ async function forward(url3, authorization, request, response) {
67672
67972
  }
67673
67973
 
67674
67974
  // src/handlers.ts
67675
- var VERSION2 = "0.0.8";
67975
+ var VERSION2 = "0.0.10";
67676
67976
  function createHandlers(computers) {
67677
67977
  return {
67678
67978
  getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
@@ -68254,32 +68554,30 @@ async function openLive(session, onMessage) {
68254
68554
  ws.addEventListener("message", (event) => onMessage(event.data));
68255
68555
  return { ws, closed };
68256
68556
  }
68257
- var INPUT2 = 48;
68258
- var RESIZE2 = 49;
68259
- var Pty = class {
68557
+ var Terminals = class {
68260
68558
  #open;
68261
68559
  constructor(open4) {
68262
68560
  this.#open = open4;
68263
68561
  }
68264
- /** Attach to its shell, starting one if none is running. Every attachment shares it. */
68562
+ /**
68563
+ * Each terminal as it starts, writes and exits: those already there first, each with the end of
68564
+ * what it wrote. Operating, the default, can also open a shell and act on any of them.
68565
+ */
68265
68566
  async connect(opts) {
68266
- const mode = opts.mode ?? "operate";
68267
- const decoder = new TextDecoder();
68268
- const { ws, closed } = await openLive(await this.#open("terminal", mode), (data) => {
68269
- if (typeof data === "string") return;
68270
- const frame = new Uint8Array(data);
68271
- if (frame[0] === INPUT2) opts.onData(decoder.decode(frame.subarray(1), { stream: true }));
68272
- });
68273
- const send2 = (type, text3) => {
68274
- const body = new TextEncoder().encode(text3);
68275
- const frame = new Uint8Array(body.length + 1);
68276
- frame[0] = type;
68277
- frame.set(body, 1);
68278
- ws.send(frame);
68279
- };
68567
+ const { ws, closed } = await openLive(
68568
+ await this.#open("terminal", opts.mode ?? "operate"),
68569
+ (data) => {
68570
+ if (typeof data === "string") opts.onEvent(JSON.parse(data));
68571
+ }
68572
+ );
68573
+ const send2 = (input2) => ws.send(JSON.stringify(input2));
68280
68574
  return {
68281
- sendInput: (data) => send2(INPUT2, data),
68282
- resize: (columns, rows) => send2(RESIZE2, JSON.stringify({ columns, rows })),
68575
+ openShell: (columns, rows) => send2({ type: "open", columns, rows }),
68576
+ sendInput: (id, text3) => {
68577
+ for (const piece of pieces(text3)) send2({ type: "input", id, text: piece });
68578
+ },
68579
+ resize: (id, columns, rows) => send2({ type: "resize", id, columns, rows }),
68580
+ stop: (id) => send2({ type: "stop", id }),
68283
68581
  close: () => ws.close(),
68284
68582
  closed
68285
68583
  };
@@ -68287,17 +68585,8 @@ var Pty = class {
68287
68585
  };
68288
68586
  var Commands = class {
68289
68587
  #endpoint;
68290
- #open;
68291
- constructor(endpoint, open4) {
68588
+ constructor(endpoint) {
68292
68589
  this.#endpoint = endpoint;
68293
- this.#open = open4;
68294
- }
68295
- /** Each of the agent's commands as it runs: the ones already heard first, then as they come. */
68296
- async watch(onEvent) {
68297
- const { ws, closed } = await openLive(await this.#open("commands", "watch"), (data) => {
68298
- if (typeof data === "string") onEvent(JSON.parse(data));
68299
- });
68300
- return { close: () => ws.close(), closed };
68301
68590
  }
68302
68591
  async run(cmd, opts = {}) {
68303
68592
  let answer = await this.#endpoint.command("exec_command", {
@@ -68374,7 +68663,7 @@ var Computer = class _Computer {
68374
68663
  computerID;
68375
68664
  commands;
68376
68665
  files;
68377
- pty;
68666
+ terminals;
68378
68667
  #connection;
68379
68668
  #token;
68380
68669
  #computer;
@@ -68385,9 +68674,8 @@ var Computer = class _Computer {
68385
68674
  this.#token = token;
68386
68675
  this.#urls = info.endpoints;
68387
68676
  this.#computer = new Endpoint(info.endpoints.computer, token);
68388
- const open4 = (surface, mode) => this.liveSession(surface, mode);
68389
- this.commands = new Commands(this.#computer, open4);
68390
- this.pty = new Pty(open4);
68677
+ this.commands = new Commands(this.#computer);
68678
+ this.terminals = new Terminals((surface, mode) => this.liveSession(surface, mode));
68391
68679
  this.files = new Files(this.#computer);
68392
68680
  }
68393
68681
  static async create(opts) {
@@ -68508,7 +68796,7 @@ import {
68508
68796
  STATUS_CODES as STATUS_CODES3
68509
68797
  } from "node:http";
68510
68798
  import { createServer as createTlsServer } from "node:https";
68511
- import { join as join11 } from "node:path";
68799
+ import { join as join10 } from "node:path";
68512
68800
 
68513
68801
  // node_modules/@modelcontextprotocol/server/dist/chunk-vYduTEsO.mjs
68514
68802
  var __create4 = Object.create;
@@ -75942,8 +76230,8 @@ var require_utils3 = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
75942
76230
  }
75943
76231
  if (bestLength < 2) return hextets.join(":");
75944
76232
  const head = hextets.slice(0, bestStart).join(":");
75945
- const tail = hextets.slice(bestStart + bestLength).join(":");
75946
- return head + "::" + tail;
76233
+ const tail2 = hextets.slice(bestStart + bestLength).join(":");
76234
+ return head + "::" + tail2;
75947
76235
  }
75948
76236
  function normalizeIPv6Address(input2) {
75949
76237
  const compression = input2.indexOf("::");
@@ -82420,9 +82708,9 @@ var WebStandardStreamableHTTPServerTransport2 = class {
82420
82708
  }
82421
82709
  });
82422
82710
  }
82423
- async _checkScopeChallenge(messages, authInfo) {
82711
+ async _checkScopeChallenge(messages2, authInfo) {
82424
82712
  if (!this._scopeChallengeResolver) return;
82425
- const challenge = await findScopeChallenge2(messages.filter((message) => isJSONRPCRequest3(message)), authInfo, this._scopeChallengeResolver);
82713
+ const challenge = await findScopeChallenge2(messages2.filter((message) => isJSONRPCRequest3(message)), authInfo, this._scopeChallengeResolver);
82426
82714
  return challenge === void 0 ? void 0 : createScopeChallengeResponse2(challenge, scopeChallengeResourceMetadataUrl2(authInfo));
82427
82715
  }
82428
82716
  /**
@@ -82720,21 +83008,21 @@ data:
82720
83008
  this.onerror?.(/* @__PURE__ */ new Error(`Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE2} messages`));
82721
83009
  return this.createJsonErrorResponse(400, -32600, `Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE2} messages`);
82722
83010
  }
82723
- let messages;
83011
+ let messages2;
82724
83012
  try {
82725
- messages = Array.isArray(rawMessage) ? rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(rawMessage)];
83013
+ messages2 = Array.isArray(rawMessage) ? rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(rawMessage)];
82726
83014
  } catch (error62) {
82727
83015
  this.onerror?.(error62);
82728
83016
  return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
82729
83017
  }
82730
83018
  if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
82731
- const isInitializationRequest = messages.some((element) => isInitializeRequest3(element));
83019
+ const isInitializationRequest = messages2.some((element) => isInitializeRequest3(element));
82732
83020
  if (isInitializationRequest) {
82733
83021
  if (this._initialized && this.sessionId !== void 0) {
82734
83022
  this.onerror?.(/* @__PURE__ */ new Error("Invalid Request: Server already initialized"));
82735
83023
  return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
82736
83024
  }
82737
- if (messages.length > 1) {
83025
+ if (messages2.length > 1) {
82738
83026
  this.onerror?.(/* @__PURE__ */ new Error("Invalid Request: Only one initialization request is allowed"));
82739
83027
  return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
82740
83028
  }
@@ -82751,21 +83039,21 @@ data:
82751
83039
  if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
82752
83040
  let scopeChallengeResponse;
82753
83041
  try {
82754
- scopeChallengeResponse = await this._checkScopeChallenge(messages, options?.authInfo);
83042
+ scopeChallengeResponse = await this._checkScopeChallenge(messages2, options?.authInfo);
82755
83043
  } catch (error62) {
82756
83044
  this.onerror?.(error62);
82757
83045
  return this.createJsonErrorResponse(500, -32603, "Internal server error");
82758
83046
  }
82759
83047
  if (scopeChallengeResponse) return scopeChallengeResponse;
82760
- if (!messages.some((element) => isJSONRPCRequest3(element))) {
82761
- for (const message of messages) this.onmessage?.(message, {
83048
+ if (!messages2.some((element) => isJSONRPCRequest3(element))) {
83049
+ for (const message of messages2) this.onmessage?.(message, {
82762
83050
  authInfo: options?.authInfo,
82763
83051
  request
82764
83052
  });
82765
83053
  return new Response(null, { status: 202 });
82766
83054
  }
82767
83055
  const streamId = crypto.randomUUID();
82768
- const initRequest = messages.find((m) => isInitializeRequest3(m));
83056
+ const initRequest = messages2.find((m) => isInitializeRequest3(m));
82769
83057
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
82770
83058
  if (this._enableJsonResponse) return new Promise((resolve6) => {
82771
83059
  this._streamMapping.set(streamId, {
@@ -82774,8 +83062,8 @@ data:
82774
83062
  this._streamMapping.delete(streamId);
82775
83063
  }
82776
83064
  });
82777
- for (const message of messages) if (isJSONRPCRequest3(message)) this._requestToStreamMapping.set(message.id, streamId);
82778
- for (const message of messages) this.onmessage?.(message, {
83065
+ for (const message of messages2) if (isJSONRPCRequest3(message)) this._requestToStreamMapping.set(message.id, streamId);
83066
+ for (const message of messages2) this.onmessage?.(message, {
82779
83067
  authInfo: options?.authInfo,
82780
83068
  request
82781
83069
  });
@@ -82799,7 +83087,7 @@ data:
82799
83087
  "X-Accel-Buffering": "no"
82800
83088
  };
82801
83089
  if (this.sessionId !== void 0) headers["mcp-session-id"] = this.sessionId;
82802
- for (const message of messages) if (isJSONRPCRequest3(message)) {
83090
+ for (const message of messages2) if (isJSONRPCRequest3(message)) {
82803
83091
  this._streamMapping.set(streamId, {
82804
83092
  controller: streamController,
82805
83093
  encoder,
@@ -82815,7 +83103,7 @@ data:
82815
83103
  this._requestToStreamMapping.set(message.id, streamId);
82816
83104
  }
82817
83105
  await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
82818
- for (const message of messages) {
83106
+ for (const message of messages2) {
82819
83107
  let closeSSEStream;
82820
83108
  let closeStandaloneSSEStream;
82821
83109
  if (isJSONRPCRequest3(message) && this._eventStore && this.supportsEmptySSEData(clientProtocolVersion)) {
@@ -84324,11 +84612,11 @@ var NodeStreamableHTTPServerTransport2 = class {
84324
84612
  // src/computers.ts
84325
84613
  import { randomBytes as randomBytes5 } from "node:crypto";
84326
84614
  import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
84327
- import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
84615
+ import { isAbsolute, join as join9, resolve as resolve4 } from "node:path";
84328
84616
  var TOOLS = {
84329
84617
  read: ["read", "list", "search"],
84330
84618
  files: ["read", "list", "search", "edit"],
84331
- terminal: ["read", "list", "search", "edit", "exec_command", "write_stdin"]
84619
+ terminal: ["read", "list", "search", "edit", "exec_command", "write_stdin", "list_sessions"]
84332
84620
  };
84333
84621
  var MAX_LOG = 256 * 1024;
84334
84622
  var MAX_TIMER_S = 2e6;
@@ -84346,7 +84634,7 @@ var Computers = class {
84346
84634
  this.#data = data;
84347
84635
  this.#base = base;
84348
84636
  this.#runtime = runtime;
84349
- this.#file = join10(data, "computers.json");
84637
+ this.#file = join9(data, "computers.json");
84350
84638
  if (existsSync2(this.#file))
84351
84639
  for (const entry of JSON.parse(readFileSync4(this.#file, "utf8")))
84352
84640
  this.#stored.set(entry.id, entry);
@@ -84401,7 +84689,7 @@ var Computers = class {
84401
84689
  ...timeout === void 0 ? {} : { endAt: expiry(timeout) }
84402
84690
  };
84403
84691
  if (!workspace && access !== "none") {
84404
- stored.workspace = join10(this.#folder(id), "workspace");
84692
+ stored.workspace = join9(this.#folder(id), "workspace");
84405
84693
  mkdirSync2(stored.workspace, { recursive: true, mode: 448 });
84406
84694
  }
84407
84695
  this.#stored.set(id, stored);
@@ -84552,10 +84840,10 @@ var Computers = class {
84552
84840
  return stored;
84553
84841
  }
84554
84842
  #folder(id) {
84555
- return privateFolder(join10(this.#data, "computers", id));
84843
+ return privateFolder(join9(this.#data, "computers", id));
84556
84844
  }
84557
84845
  #logFile(id) {
84558
- return join10(this.#folder(id), "log");
84846
+ return join9(this.#folder(id), "log");
84559
84847
  }
84560
84848
  #log(id, line) {
84561
84849
  if (!this.#stored.has(id)) return;
@@ -84647,7 +84935,7 @@ var OPENAPI = {
84647
84935
  "openapi": "3.1.0",
84648
84936
  "info": {
84649
84937
  "title": "AgentComputer",
84650
- "version": "0.0.8",
84938
+ "version": "0.0.10",
84651
84939
  "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.",
84652
84940
  "license": {
84653
84941
  "name": "Apache-2.0",
@@ -84968,7 +85256,7 @@ var OPENAPI = {
84968
85256
  "post": {
84969
85257
  "operationId": "createLiveSession",
84970
85258
  "x-mcp-tool": "computer_live",
84971
- "summary": "A one-time ticket to watch or operate one of its surfaces live: the agent's commands as they run, a shell of your own, its files, or its browser. Open its url as a WebSocket within 60 seconds.",
85259
+ "summary": "A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, its browser, or its load. Open its url as a WebSocket within 60 seconds.",
84972
85260
  "requestBody": {
84973
85261
  "required": true,
84974
85262
  "content": {
@@ -85099,7 +85387,7 @@ var OPENAPI = {
85099
85387
  "files",
85100
85388
  "terminal"
85101
85389
  ],
85102
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
85390
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
85103
85391
  },
85104
85392
  "ComputerState": {
85105
85393
  "type": "string",
@@ -85307,12 +85595,12 @@ var OPENAPI = {
85307
85595
  "Surface": {
85308
85596
  "type": "string",
85309
85597
  "enum": [
85310
- "commands",
85311
85598
  "terminal",
85312
85599
  "files",
85313
- "browser"
85600
+ "browser",
85601
+ "load"
85314
85602
  ],
85315
- "description": "commands: the agent's commands as they run, read-only, in commands@1. terminal: a shell of your own in its workspace, as its user, in pty@1. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes."
85603
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
85316
85604
  },
85317
85605
  "LiveMode": {
85318
85606
  "type": "string",
@@ -85755,6 +86043,266 @@ var OPENAPI = {
85755
86043
  }
85756
86044
  ]
85757
86045
  },
86046
+ "TerminalEvent": {
86047
+ "description": "What the service sends on a terminal@1 socket, as JSON text, no message over 65,536 bytes. A terminal is a command the agent started or a shell a person opened; its id is the session id the agent's tools use. A new socket is first sent, for each terminal already there, the oldest first, its start, the end of what it wrote (its last 65,536 characters), and its exit if it has ended; then each event as it happens, with nothing to mark where the first ends. The last 64 that ended are among those already there, and one that leaves that list is not told. A character here is a UTF-16 unit. A socket with more than 4 MiB still to be sent to it is closed, with no close frame (1006): one that has stopped reading, or one that is reading while a terminal writes tens of megabytes at once. It isn't an error. Opened again, it is sent every terminal afresh, with the last 65,536 characters each wrote, in place of whatever it held.",
86048
+ "oneOf": [
86049
+ {
86050
+ "type": "object",
86051
+ "description": "A terminal began.",
86052
+ "required": [
86053
+ "type",
86054
+ "id",
86055
+ "by",
86056
+ "cmd",
86057
+ "workdir",
86058
+ "tty",
86059
+ "at"
86060
+ ],
86061
+ "properties": {
86062
+ "type": {
86063
+ "const": "start"
86064
+ },
86065
+ "id": {
86066
+ "type": "string"
86067
+ },
86068
+ "by": {
86069
+ "type": "string",
86070
+ "enum": [
86071
+ "agent",
86072
+ "person"
86073
+ ],
86074
+ "description": "The way in it came: agent through a tool, person through this surface."
86075
+ },
86076
+ "cmd": {
86077
+ "type": "string",
86078
+ "description": "Its command, the first 8,000 characters of it."
86079
+ },
86080
+ "workdir": {
86081
+ "type": "string",
86082
+ "description": "Its folder, the first 2,000 characters of it."
86083
+ },
86084
+ "tty": {
86085
+ "type": "boolean",
86086
+ "description": "Whether it is on a terminal device, as a person's shell is. One that isn't, as the agent's commands aren't, echoes nothing typed into it and wants a line feed for Enter. Nothing tells the size of one that is."
86087
+ },
86088
+ "at": {
86089
+ "type": "string",
86090
+ "format": "date-time"
86091
+ }
86092
+ }
86093
+ },
86094
+ {
86095
+ "type": "object",
86096
+ "description": "What it wrote, as it writes: 10,000 characters a message at most, a longer piece as several. A command that left something running can still write after its exit.",
86097
+ "required": [
86098
+ "type",
86099
+ "id",
86100
+ "text"
86101
+ ],
86102
+ "properties": {
86103
+ "type": {
86104
+ "const": "output"
86105
+ },
86106
+ "id": {
86107
+ "type": "string"
86108
+ },
86109
+ "stream": {
86110
+ "type": "string",
86111
+ "enum": [
86112
+ "stdout",
86113
+ "stderr"
86114
+ ],
86115
+ "description": "Left out for one on a terminal device, which has one stream, and in what a new socket is first sent, where the two are one text."
86116
+ },
86117
+ "text": {
86118
+ "type": "string"
86119
+ }
86120
+ }
86121
+ },
86122
+ {
86123
+ "type": "object",
86124
+ "description": "It ended.",
86125
+ "required": [
86126
+ "type",
86127
+ "id",
86128
+ "exitCode",
86129
+ "at"
86130
+ ],
86131
+ "properties": {
86132
+ "type": {
86133
+ "const": "exit"
86134
+ },
86135
+ "id": {
86136
+ "type": "string"
86137
+ },
86138
+ "exitCode": {
86139
+ "type": "integer"
86140
+ },
86141
+ "at": {
86142
+ "type": "string",
86143
+ "format": "date-time"
86144
+ }
86145
+ }
86146
+ },
86147
+ {
86148
+ "type": "object",
86149
+ "description": "What this socket asked wasn't done, and why, in words for a person. Sent only to the operate socket that asked. What was done is answered by its event, or by nothing.",
86150
+ "required": [
86151
+ "type",
86152
+ "message"
86153
+ ],
86154
+ "properties": {
86155
+ "type": {
86156
+ "const": "refused"
86157
+ },
86158
+ "message": {
86159
+ "type": "string"
86160
+ }
86161
+ }
86162
+ }
86163
+ ]
86164
+ },
86165
+ "TerminalInput": {
86166
+ "description": "What an operate socket sends on terminal@1, as JSON text. One that can't be done, or that is none of these, is answered with refused. What a watch socket sends is dropped. A message over 65,536 bytes closes the socket, so a long text goes as several inputs.",
86167
+ "oneOf": [
86168
+ {
86169
+ "type": "object",
86170
+ "description": "Start a shell of your own in the workspace, as its user, on a terminal device of this size. It arrives as a start like any other. Refused past the 64 terminals that may run at once, and where the service has no node-pty.",
86171
+ "required": [
86172
+ "type",
86173
+ "columns",
86174
+ "rows"
86175
+ ],
86176
+ "properties": {
86177
+ "type": {
86178
+ "const": "open"
86179
+ },
86180
+ "columns": {
86181
+ "type": "integer",
86182
+ "minimum": 1,
86183
+ "maximum": 1e3
86184
+ },
86185
+ "rows": {
86186
+ "type": "integer",
86187
+ "minimum": 1,
86188
+ "maximum": 1e3
86189
+ }
86190
+ }
86191
+ },
86192
+ {
86193
+ "type": "object",
86194
+ "description": "Type into a running terminal, the agent's included. In a command, Ctrl-C (U+0003) interrupts it and Ctrl-D (U+0004) ends its input. Refused for one that has ended or isn't there.",
86195
+ "required": [
86196
+ "type",
86197
+ "id",
86198
+ "text"
86199
+ ],
86200
+ "properties": {
86201
+ "type": {
86202
+ "const": "input"
86203
+ },
86204
+ "id": {
86205
+ "type": "string"
86206
+ },
86207
+ "text": {
86208
+ "type": "string"
86209
+ }
86210
+ }
86211
+ },
86212
+ {
86213
+ "type": "object",
86214
+ "description": "Give a terminal on a terminal device a new size, which is the size for everyone looking at it. Refused for a command, which has none, and for one that has ended or isn't there.",
86215
+ "required": [
86216
+ "type",
86217
+ "id",
86218
+ "columns",
86219
+ "rows"
86220
+ ],
86221
+ "properties": {
86222
+ "type": {
86223
+ "const": "resize"
86224
+ },
86225
+ "id": {
86226
+ "type": "string"
86227
+ },
86228
+ "columns": {
86229
+ "type": "integer",
86230
+ "minimum": 1,
86231
+ "maximum": 1e3
86232
+ },
86233
+ "rows": {
86234
+ "type": "integer",
86235
+ "minimum": 1,
86236
+ "maximum": 1e3
86237
+ }
86238
+ }
86239
+ },
86240
+ {
86241
+ "type": "object",
86242
+ "description": "End a terminal: an interrupt for a command, a hangup for a shell, then a kill a second later. Its exit is the answer. One that has ended is left as it is, and one that isn't there is refused.",
86243
+ "required": [
86244
+ "type",
86245
+ "id"
86246
+ ],
86247
+ "properties": {
86248
+ "type": {
86249
+ "const": "stop"
86250
+ },
86251
+ "id": {
86252
+ "type": "string"
86253
+ }
86254
+ }
86255
+ }
86256
+ ]
86257
+ },
86258
+ "LoadReading": {
86259
+ "description": "What the service sends on a metrics@1 socket, as JSON text. What a socket sends is dropped.",
86260
+ "type": "object",
86261
+ "required": [
86262
+ "type",
86263
+ "scope",
86264
+ "cpuUsedPct",
86265
+ "memUsed",
86266
+ "memTotal",
86267
+ "diskUsed",
86268
+ "diskTotal"
86269
+ ],
86270
+ "properties": {
86271
+ "type": {
86272
+ "const": "reading"
86273
+ },
86274
+ "scope": {
86275
+ "type": "string",
86276
+ "enum": [
86277
+ "computer",
86278
+ "machine"
86279
+ ],
86280
+ "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."
86281
+ },
86282
+ "cpuUsedPct": {
86283
+ "type": "number",
86284
+ "minimum": 0,
86285
+ "maximum": 100,
86286
+ "description": "Of all the machine's CPUs, since the reading before. One decimal."
86287
+ },
86288
+ "memUsed": {
86289
+ "type": "integer",
86290
+ "description": "Bytes in use."
86291
+ },
86292
+ "memTotal": {
86293
+ "type": "integer",
86294
+ "description": "Bytes the machine has: no computer has a limit."
86295
+ },
86296
+ "diskUsed": {
86297
+ "type": "integer",
86298
+ "description": "Bytes used on the volume its workspace is on, or its own folder where it has no workspace. The volume's, not the workspace's own size."
86299
+ },
86300
+ "diskTotal": {
86301
+ "type": "integer",
86302
+ "description": "Bytes on that volume."
86303
+ }
86304
+ }
86305
+ },
85758
86306
  "FilesRequest": {
85759
86307
  "description": "What a socket sends on files@1, as JSON text. Each has an id of its own choosing, which the answer repeats; answers come as they are ready, in any order, and ids aren't checked for repeats. A request still unanswered when the socket closes gets no answer. A path is from the workspace, with / between its parts, or absolute inside it, and nothing outside the workspace is reached. A binary message that follows no write closes the socket, and so does any message over 1,048,576 bytes.",
85760
86308
  "oneOf": [
@@ -86224,7 +86772,7 @@ var OPERATIONS = [
86224
86772
  "files",
86225
86773
  "terminal"
86226
86774
  ],
86227
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
86775
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
86228
86776
  },
86229
86777
  "browser": {
86230
86778
  "type": "boolean"
@@ -86313,7 +86861,7 @@ var OPERATIONS = [
86313
86861
  "files",
86314
86862
  "terminal"
86315
86863
  ],
86316
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
86864
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
86317
86865
  },
86318
86866
  "browser": {
86319
86867
  "type": "boolean",
@@ -86383,7 +86931,7 @@ var OPERATIONS = [
86383
86931
  "files",
86384
86932
  "terminal"
86385
86933
  ],
86386
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
86934
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
86387
86935
  },
86388
86936
  "browser": {
86389
86937
  "type": "boolean"
@@ -86523,7 +87071,7 @@ var OPERATIONS = [
86523
87071
  "files",
86524
87072
  "terminal"
86525
87073
  ],
86526
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
87074
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
86527
87075
  },
86528
87076
  "browser": {
86529
87077
  "type": "boolean"
@@ -86607,12 +87155,12 @@ var OPERATIONS = [
86607
87155
  "surface": {
86608
87156
  "type": "string",
86609
87157
  "enum": [
86610
- "commands",
86611
87158
  "terminal",
86612
87159
  "files",
86613
- "browser"
87160
+ "browser",
87161
+ "load"
86614
87162
  ],
86615
- "description": "commands: the agent's commands as they run, read-only, in commands@1. terminal: a shell of your own in its workspace, as its user, in pty@1. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes."
87163
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
86616
87164
  },
86617
87165
  "protocol": {
86618
87166
  "type": "string"
@@ -86754,7 +87302,7 @@ var OPERATIONS = [
86754
87302
  "files",
86755
87303
  "terminal"
86756
87304
  ],
86757
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
87305
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
86758
87306
  },
86759
87307
  "browser": {
86760
87308
  "type": "boolean"
@@ -86878,7 +87426,7 @@ var OPERATIONS = [
86878
87426
  "files",
86879
87427
  "terminal"
86880
87428
  ],
86881
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
87429
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
86882
87430
  },
86883
87431
  "browser": {
86884
87432
  "type": "boolean"
@@ -87007,7 +87555,7 @@ var OPERATIONS = [
87007
87555
  "files",
87008
87556
  "terminal"
87009
87557
  ],
87010
- "description": "How far it reaches into files and commands, each level containing the one before: `read` has read, list and search; `files` adds edit; `terminal` adds exec_command and write_stdin. Below terminal, the browser's code-running tool is hidden."
87558
+ "description": "How far it reaches into files and commands, each level containing the one before: `read` has 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."
87011
87559
  },
87012
87560
  "browser": {
87013
87561
  "type": "boolean"
@@ -87128,7 +87676,7 @@ var OPERATIONS = [
87128
87676
  "method": "POST",
87129
87677
  "path": "/v1/computers/{computerID}/live",
87130
87678
  "tool": "computer_live",
87131
- "summary": "A one-time ticket to watch or operate one of its surfaces live: the agent's commands as they run, a shell of your own, its files, or its browser. Open its url as a WebSocket within 60 seconds.",
87679
+ "summary": "A one-time ticket to watch or operate one of its surfaces live: its terminals, its files, its browser, or its load. Open its url as a WebSocket within 60 seconds.",
87132
87680
  "status": 201,
87133
87681
  "hasBody": true,
87134
87682
  "pathParameters": [
@@ -87147,12 +87695,12 @@ var OPERATIONS = [
87147
87695
  "surface": {
87148
87696
  "type": "string",
87149
87697
  "enum": [
87150
- "commands",
87151
87698
  "terminal",
87152
87699
  "files",
87153
- "browser"
87700
+ "browser",
87701
+ "load"
87154
87702
  ],
87155
- "description": "commands: the agent's commands as they run, read-only, in commands@1. terminal: a shell of your own in its workspace, as its user, in pty@1. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes."
87703
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
87156
87704
  },
87157
87705
  "mode": {
87158
87706
  "allOf": [
@@ -87194,12 +87742,12 @@ var OPERATIONS = [
87194
87742
  "surface": {
87195
87743
  "type": "string",
87196
87744
  "enum": [
87197
- "commands",
87198
87745
  "terminal",
87199
87746
  "files",
87200
- "browser"
87747
+ "browser",
87748
+ "load"
87201
87749
  ],
87202
- "description": "commands: the agent's commands as they run, read-only, in commands@1. terminal: a shell of your own in its workspace, as its user, in pty@1. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes."
87750
+ "description": "terminal: every terminal on it, the agent's commands and a person's shells alike, on one socket in terminal@1: text messages are TerminalEvent one way and TerminalInput the other. Watching reads them all; operating also opens a shell, types into any of them, the agent's included, sizes and stops them. files: its workspace's files, in files@1: text messages are FilesRequest one way and FilesAnswer the other, and a file's bytes travel as binary messages of at most 1,048,576 bytes. Watching lists and reads; operating also writes, which a computer with read access doesn't offer. browser: its browser, in screencast@1: binary messages are JPEG frames of the page shown, text messages are ScreencastEvent one way and ScreencastInput the other. Nobody waits for anybody: the agent's tools and a person's input act on the same browser at once. The page shown is each socket's own. A socket is shown each new tab as it opens, until it sends a tab input to show or open one; from then its page changes only when that page closes. load: how busy it is, in metrics@1, to watch only: a LoadReading every two seconds while a socket is open, the first two seconds after the first socket opens, and the last one at once to a socket that opens later. One that can't be taken is left out, and a socket that hasn't read the one before is not sent the next. Nothing is read while nobody watches."
87203
87751
  },
87204
87752
  "mode": {
87205
87753
  "type": "string",
@@ -87305,7 +87853,7 @@ async function serve(options) {
87305
87853
  pattern: new RegExp(`^${operation.path.replace(/\{[^}]+\}/g, "([^/]+)")}$`),
87306
87854
  check: fromJsonSchema3(operation.input)
87307
87855
  }));
87308
- const run2 = async (route2, input2) => {
87856
+ const run3 = async (route2, input2) => {
87309
87857
  const checked = await route2.check["~standard"].validate(input2);
87310
87858
  if (checked.issues)
87311
87859
  throw new ApiError(400, checked.issues.map((issue3) => issue3.message).join("; "));
@@ -87320,7 +87868,7 @@ async function serve(options) {
87320
87868
  { description: route2.operation.summary, inputSchema: route2.check },
87321
87869
  async (input2) => {
87322
87870
  try {
87323
- const output2 = await run2(route2, input2) ?? {};
87871
+ const output2 = await run3(route2, input2) ?? {};
87324
87872
  return { content: [{ type: "text", text: JSON.stringify(output2) }] };
87325
87873
  } catch (error62) {
87326
87874
  if (!(error62 instanceof ApiError)) throw error62;
@@ -87372,7 +87920,7 @@ async function serve(options) {
87372
87920
  const value = url3.searchParams.get(name);
87373
87921
  if (value !== null) input2[name] = value;
87374
87922
  }
87375
- return send(response, operation.status, await run2(candidate, input2));
87923
+ return send(response, operation.status, await run3(candidate, input2));
87376
87924
  }
87377
87925
  throw pathMatched ? new ApiError(405, "Not allowed.") : new ApiError(404, "Not found.");
87378
87926
  };
@@ -87419,7 +87967,7 @@ ${JSON.stringify({ code: status, message })}
87419
87967
  const scheme = options.tls ? "https" : "http";
87420
87968
  base = options.publicUrl?.replace(/\/+$/, "") ?? `${scheme}://${bound.family === "IPv6" ? `[${bound.address}]` : bound.address}:${bound.port}`;
87421
87969
  await computers.resume();
87422
- const record3 = join11(data, "server.json");
87970
+ const record3 = join10(data, "server.json");
87423
87971
  writePrivate(
87424
87972
  record3,
87425
87973
  JSON.stringify({ url: base, pid: process.pid, isolation: options.runtime.isolation })
@@ -87459,7 +88007,7 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
87459
88007
  stop <id> stop it, keeping its folder and token
87460
88008
  start <id> [--timeout SECONDS] start a stopped computer again
87461
88009
  logs <id> what its servers wrote
87462
- live <id> --surface commands|terminal|files|browser [--mode watch|operate]
88010
+ live <id> --surface terminal|files|browser|load [--mode watch|operate]
87463
88011
  a one-time ticket to open one live, as a WebSocket
87464
88012
  kill <id> stop it and remove what the service made
87465
88013
 
@@ -87472,13 +88020,13 @@ var UsageError = class extends Error {
87472
88020
  function dataFolder(given) {
87473
88021
  const xdg = process.env.XDG_DATA_HOME;
87474
88022
  return resolve5(
87475
- given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join12(xdg, "agentcomputer") : join12(homedir2(), ".local", "share", "agentcomputer"))
88023
+ given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join11(xdg, "agentcomputer") : join11(homedir2(), ".local", "share", "agentcomputer"))
87476
88024
  );
87477
88025
  }
87478
88026
  function connectionTo(data) {
87479
88027
  try {
87480
- const { url: url3 } = JSON.parse(readFileSync6(join12(data, "server.json"), "utf8"));
87481
- return { apiUrl: url3, apiKey: readFileSync6(join12(data, "control-token"), "utf8").trim() };
88028
+ const { url: url3 } = JSON.parse(readFileSync6(join11(data, "server.json"), "utf8"));
88029
+ return { apiUrl: url3, apiKey: readFileSync6(join11(data, "control-token"), "utf8").trim() };
87482
88030
  } catch {
87483
88031
  throw new Error(
87484
88032
  `No service is running with its data in ${data}. Start one: agentcomputer serve`