@agentorg/computer 0.0.6 → 0.0.8

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.
@@ -2295,7 +2295,7 @@ var require_websocket = __commonJS({
2295
2295
  var net = __require("net");
2296
2296
  var querystring = __require("querystring");
2297
2297
  var tls = __require("tls");
2298
- var { randomBytes: randomBytes5, createHash: createHash2 } = __require("crypto");
2298
+ var { randomBytes: randomBytes6, createHash: createHash2 } = __require("crypto");
2299
2299
  var { Duplex, Readable: Readable5 } = __require("stream");
2300
2300
  var { URL: URL2 } = __require("url");
2301
2301
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2845,7 +2845,7 @@ var require_websocket = __commonJS({
2845
2845
  }
2846
2846
  }
2847
2847
  const defaultPort = isSecure ? 443 : 80;
2848
- const key = randomBytes5(16).toString("base64");
2848
+ const key = randomBytes6(16).toString("base64");
2849
2849
  const request = isSecure ? https.request : http.request;
2850
2850
  const protocolSet = /* @__PURE__ */ new Set();
2851
2851
  let perMessageDeflate;
@@ -3095,21 +3095,21 @@ var require_websocket = __commonJS({
3095
3095
  process.nextTick(cb, err);
3096
3096
  }
3097
3097
  }
3098
- function receiverOnConclude(code, reason2) {
3098
+ function receiverOnConclude(code, reason3) {
3099
3099
  const websocket = this[kWebSocket];
3100
3100
  websocket._closeFrameReceived = true;
3101
3101
  websocket._closeCode = code;
3102
- if (reason2.length === 0) {
3102
+ if (reason3.length === 0) {
3103
3103
  websocket._closeMessage = EMPTY_BUFFER;
3104
3104
  } else {
3105
- websocket._closeMessage = Buffer.allocUnsafeSlow(reason2.length);
3106
- websocket._closeMessage.set(reason2);
3105
+ websocket._closeMessage = Buffer.allocUnsafeSlow(reason3.length);
3106
+ websocket._closeMessage.set(reason3);
3107
3107
  }
3108
3108
  if (websocket._socket[kWebSocket] === void 0) return;
3109
3109
  websocket._socket.removeListener("data", socketOnData);
3110
3110
  process.nextTick(resume, websocket._socket);
3111
3111
  if (code === 1005) websocket.close();
3112
- else websocket.close(code, reason2);
3112
+ else websocket.close(code, reason3);
3113
3113
  }
3114
3114
  function receiverOnDrain() {
3115
3115
  const websocket = this[kWebSocket];
@@ -3268,7 +3268,7 @@ var require_stream = __commonJS({
3268
3268
  };
3269
3269
  duplex._final = function(callback) {
3270
3270
  if (ws.readyState === ws.CONNECTING) {
3271
- ws.once("open", function open3() {
3271
+ ws.once("open", function open4() {
3272
3272
  duplex._final(callback);
3273
3273
  });
3274
3274
  return;
@@ -3289,7 +3289,7 @@ var require_stream = __commonJS({
3289
3289
  };
3290
3290
  duplex._write = function(chunk, encoding, callback) {
3291
3291
  if (ws.readyState === ws.CONNECTING) {
3292
- ws.once("open", function open3() {
3292
+ ws.once("open", function open4() {
3293
3293
  duplex._write(chunk, encoding, callback);
3294
3294
  });
3295
3295
  return;
@@ -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 join11, resolve as resolve5 } from "node:path";
3756
+ import { isAbsolute as isAbsolute2, join as join12, 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 run3 = mkdtempSync(join("/tmp", "agentcomputer-"));
3776
- process.env.XDG_RUNTIME_DIR = run3;
3777
- process.env.TMPDIR = run3;
3775
+ const run2 = mkdtempSync(join("/tmp", "agentcomputer-"));
3776
+ process.env.XDG_RUNTIME_DIR = run2;
3777
+ process.env.TMPDIR = run2;
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(run3 + sep)) {
3786
+ if (!endpoint.startsWith(run2 + 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,26 +3797,66 @@ async function hostBrowser(options) {
3797
3797
  });
3798
3798
  await context.close().catch(() => {
3799
3799
  });
3800
- rmSync(run3, { recursive: true, force: true });
3800
+ rmSync(run2, { recursive: true, force: true });
3801
3801
  process.exit(0);
3802
3802
  };
3803
3803
  for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void stop());
3804
3804
  context.on("close", () => void stop());
3805
3805
  }
3806
3806
 
3807
+ // src/browser-mcp.ts
3808
+ import { createRequire as createRequire2 } from "node:module";
3809
+ var require2 = createRequire2(import.meta.url);
3810
+ var mcpParts = () => require2("playwright-core/lib/coreBundle").tools;
3811
+ async function serveBrowserMcp(options) {
3812
+ const parts = mcpParts();
3813
+ parts.setupExitWatchdog();
3814
+ const config3 = await parts.resolveCLIConfigForMCP({
3815
+ endpoint: options.endpoint,
3816
+ outputDir: options.outputDir,
3817
+ host: "127.0.0.1",
3818
+ port: 0,
3819
+ // The tool that closes the browser refuses, as it does for any browser its clients share.
3820
+ sharedBrowserContext: true
3821
+ });
3822
+ const tools = parts.filteredTools(config3);
3823
+ await parts.start(
3824
+ {
3825
+ name: "Playwright",
3826
+ nameInConfig: "playwright",
3827
+ // playwright-core's, as Playwright MCP's program gives it.
3828
+ version: require2("playwright-core/package.json").version,
3829
+ toolSchemas: tools.map((tool) => tool.schema),
3830
+ ignoreRoots: config3.ignoreRoots,
3831
+ /** One for each client's session: a connection of its own to the one browser. */
3832
+ create: async () => {
3833
+ const browser = await playwright().chromium.connect(options.endpoint);
3834
+ const context = browser.contexts()[0];
3835
+ if (!context) {
3836
+ await browser.close();
3837
+ throw new Error("The browser has no context to work in.");
3838
+ }
3839
+ return new parts.BrowserBackend(config3, context, tools, {
3840
+ dispose: () => browser.close()
3841
+ });
3842
+ }
3843
+ },
3844
+ config3.server
3845
+ );
3846
+ }
3847
+
3807
3848
  // src/container.ts
3808
3849
  import { execFile, spawn as spawn3 } from "node:child_process";
3809
3850
  import { readFileSync as readFileSync3 } from "node:fs";
3810
3851
  import { connect } from "node:net";
3811
- import { join as join8 } from "node:path";
3852
+ import { join as join9 } from "node:path";
3812
3853
  import { createInterface as createInterface2 } from "node:readline";
3813
3854
  import { Readable as Readable3 } from "node:stream";
3814
3855
  import { promisify } from "node:util";
3815
3856
 
3816
3857
  // src/browser.ts
3817
3858
  import { spawn } from "node:child_process";
3818
- import { createRequire as createRequire2 } from "node:module";
3819
- import { dirname, join as join2 } from "node:path";
3859
+ import { join as join2 } from "node:path";
3820
3860
  import { createInterface } from "node:readline";
3821
3861
  import { pipeline, Readable } from "node:stream";
3822
3862
  import { fileURLToPath } from "node:url";
@@ -15120,7 +15160,7 @@ function linkedRoundAbort(outer) {
15120
15160
  if (outer?.aborted) controller.abort(outer.reason);
15121
15161
  return {
15122
15162
  signal: controller.signal,
15123
- abort: (reason2) => controller.abort(reason2),
15163
+ abort: (reason3) => controller.abort(reason3),
15124
15164
  dispose: () => outer?.removeEventListener("abort", onOuterAbort)
15125
15165
  };
15126
15166
  }
@@ -15963,8 +16003,8 @@ var Protocol = class {
15963
16003
  return;
15964
16004
  }
15965
16005
  if (options?.signal?.aborted) {
15966
- const reason2 = options.signal.reason;
15967
- throw reason2 instanceof SdkError ? reason2 : new SdkError(SdkErrorCode.RequestTimeout, String(reason2));
16006
+ const reason3 = options.signal.reason;
16007
+ throw reason3 instanceof SdkError ? reason3 : new SdkError(SdkErrorCode.RequestTimeout, String(reason3));
15968
16008
  }
15969
16009
  const requestAbort = codec2.era === MODERN_WIRE_REVISION && this._transport.hasPerRequestStream === true ? new AbortController() : void 0;
15970
16010
  const messageId = this._requestMessageId++;
@@ -15986,7 +16026,7 @@ var Protocol = class {
15986
16026
  }
15987
16027
  const outbound = this._envelopeOutbound(jsonrpcRequest);
15988
16028
  let responseReceived = false;
15989
- const cancel = (reason2) => {
16029
+ const cancel = (reason3) => {
15990
16030
  if (responseReceived) return;
15991
16031
  this._progressHandlers.delete(messageId);
15992
16032
  if (requestAbort === void 0) {
@@ -15995,7 +16035,7 @@ var Protocol = class {
15995
16035
  method: "notifications/cancelled",
15996
16036
  params: {
15997
16037
  requestId: messageId,
15998
- reason: String(reason2)
16038
+ reason: String(reason3)
15999
16039
  }
16000
16040
  }), {
16001
16041
  relatedRequestId,
@@ -16003,7 +16043,7 @@ var Protocol = class {
16003
16043
  onresumptiontoken
16004
16044
  }).catch((error62) => this._onerror(/* @__PURE__ */ new Error(`Failed to send cancellation: ${error62}`)));
16005
16045
  } else requestAbort.abort();
16006
- reject(reason2 instanceof SdkError ? reason2 : new SdkError(SdkErrorCode.RequestTimeout, String(reason2)));
16046
+ reject(reason3 instanceof SdkError ? reason3 : new SdkError(SdkErrorCode.RequestTimeout, String(reason3)));
16007
16047
  };
16008
16048
  this._responseHandlers.set(messageId, (response) => {
16009
16049
  if (options?.signal?.aborted) return;
@@ -20413,7 +20453,7 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
20413
20453
  }
20414
20454
  errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
20415
20455
  if (!errors || errors.length === 0) return "No errors";
20416
- return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
20456
+ return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text3, msg) => text3 + separator + msg);
20417
20457
  }
20418
20458
  $dataMetaSchema(metaSchema, keywordsJsonPointers) {
20419
20459
  const rules = this.RULES.all;
@@ -23818,8 +23858,8 @@ async function random(size) {
23818
23858
  const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
23819
23859
  let result = "";
23820
23860
  while (result.length < size) {
23821
- const randomBytes5 = await getRandomValues(size - result.length);
23822
- for (const randomByte of randomBytes5) {
23861
+ const randomBytes6 = await getRandomValues(size - result.length);
23862
+ for (const randomByte of randomBytes6) {
23823
23863
  if (randomByte < evenDistCutoff) {
23824
23864
  result += mask[randomByte % mask.length];
23825
23865
  }
@@ -25485,11 +25525,11 @@ function normalizeReply(reply, timeoutMs) {
25485
25525
  error: error62
25486
25526
  };
25487
25527
  if (error62 instanceof SdkHttpError) {
25488
- const text2 = error62.data?.text;
25528
+ const text3 = error62.data?.text;
25489
25529
  return {
25490
25530
  kind: "http-error",
25491
25531
  status: error62.data.status,
25492
- body: typeof text2 === "string" ? text2 : void 0,
25532
+ body: typeof text3 === "string" ? text3 : void 0,
25493
25533
  statusText: error62.data.statusText
25494
25534
  };
25495
25535
  }
@@ -25731,10 +25771,10 @@ var Client = class extends Protocol {
25731
25771
  this._discoverResult = void 0;
25732
25772
  this._autoOpenedSubscription = void 0;
25733
25773
  if (this._listenState.size > 0) {
25734
- const reason2 = new SdkError(SdkErrorCode.ConnectionClosed, "subscriptions/listen: client reconnected or closed; subscription state from the previous connection was reset");
25774
+ const reason3 = new SdkError(SdkErrorCode.ConnectionClosed, "subscriptions/listen: client reconnected or closed; subscription state from the previous connection was reset");
25735
25775
  for (const entry of this._listenState.values()) entry.settle({
25736
25776
  cause: "remote",
25737
- error: reason2
25777
+ error: reason3
25738
25778
  });
25739
25779
  }
25740
25780
  this._listenState.clear();
@@ -26478,8 +26518,8 @@ var Client = class extends Protocol {
26478
26518
  const hit = await this._cache.read(method, params).catch((error62) => void this._reportStoreError(error62));
26479
26519
  if (hit !== void 0) {
26480
26520
  if (options?.signal?.aborted) {
26481
- const reason2 = options.signal.reason;
26482
- throw reason2 instanceof SdkError ? reason2 : new SdkError(SdkErrorCode.RequestTimeout, String(reason2));
26521
+ const reason3 = options.signal.reason;
26522
+ throw reason3 instanceof SdkError ? reason3 : new SdkError(SdkErrorCode.RequestTimeout, String(reason3));
26483
26523
  }
26484
26524
  return hit.value;
26485
26525
  }
@@ -26620,8 +26660,8 @@ var Client = class extends Protocol {
26620
26660
  protocolVersion: negotiated
26621
26661
  });
26622
26662
  if (options?.signal?.aborted) {
26623
- const reason2 = options.signal.reason;
26624
- throw reason2 instanceof SdkError ? reason2 : new SdkError(SdkErrorCode.RequestTimeout, String(reason2));
26663
+ const reason3 = options.signal.reason;
26664
+ throw reason3 instanceof SdkError ? reason3 : new SdkError(SdkErrorCode.RequestTimeout, String(reason3));
26625
26665
  }
26626
26666
  const requestAbort = new AbortController();
26627
26667
  const listenId = `listen:${this._nextListenId++}`;
@@ -26684,10 +26724,10 @@ var Client = class extends Protocol {
26684
26724
  const callerSignal = options.signal;
26685
26725
  onCallerAbort = () => {
26686
26726
  if (state === "closed") return;
26687
- const reason2 = callerSignal.reason;
26727
+ const reason3 = callerSignal.reason;
26688
26728
  settle3({
26689
26729
  cause: "local",
26690
- error: reason2 instanceof Error ? reason2 : new Error(String(reason2 ?? "Aborted"))
26730
+ error: reason3 instanceof Error ? reason3 : new Error(String(reason3 ?? "Aborted"))
26691
26731
  });
26692
26732
  wireTeardown().catch(() => {
26693
26733
  });
@@ -26814,10 +26854,10 @@ var Client = class extends Protocol {
26814
26854
  */
26815
26855
  _onclose() {
26816
26856
  if (this._listenState.size > 0) {
26817
- const reason2 = new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed");
26857
+ const reason3 = new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed");
26818
26858
  for (const entry of this._listenState.values()) entry.settle({
26819
26859
  cause: "remote",
26820
- error: reason2
26860
+ error: reason3
26821
26861
  });
26822
26862
  this._listenState.clear();
26823
26863
  }
@@ -27467,8 +27507,8 @@ var StreamableHTTPClientTransport = class {
27467
27507
  }
27468
27508
  /** Error text for a redirect `response` that was not followed, or `undefined` for any other response. */
27469
27509
  _unfollowedRedirect(url3, response) {
27470
- const text2 = this._redirectPolicy === "follow" ? void 0 : unfollowedRedirect(url3, response);
27471
- return text2 && `${text2} (redirectPolicy: 'same-origin')`;
27510
+ const text3 = this._redirectPolicy === "follow" ? void 0 : unfollowedRedirect(url3, response);
27511
+ return text3 && `${text3} (redirectPolicy: 'same-origin')`;
27472
27512
  }
27473
27513
  async _commonHeaders() {
27474
27514
  const headers = new Headers(this._requestInit?.headers || void 0);
@@ -27559,13 +27599,13 @@ var StreamableHTTPClientTransport = class {
27559
27599
  if (response.status === 403) {
27560
27600
  const { resourceMetadataUrl, scope, error: error62, errorDescription } = extractWWWAuthenticateParams(response);
27561
27601
  if (error62 === "insufficient_scope") {
27562
- const text2 = await response.text?.().catch(() => null);
27602
+ const text3 = await response.text?.().catch(() => null);
27563
27603
  if (await this._stepUpAuthorize({
27564
27604
  scope,
27565
27605
  resourceMetadataUrl,
27566
27606
  errorDescription,
27567
27607
  statusText: response.statusText,
27568
- text: text2
27608
+ text: text3
27569
27609
  }, stepUpRetries) !== "AUTHORIZED") throw markAuthSeamEscape(new UnauthorizedError());
27570
27610
  return this._startOrAuthSse(options, isAuthRetry, stepUpRetries + 1);
27571
27611
  }
@@ -27576,8 +27616,8 @@ var StreamableHTTPClientTransport = class {
27576
27616
  options.onRequestStreamEnd?.();
27577
27617
  return;
27578
27618
  }
27579
- const reason2 = this._unfollowedRedirect(this._url, response) ?? response.statusText;
27580
- throw new SdkHttpError(SdkErrorCode.ClientHttpFailedToOpenStream, `Failed to open SSE stream: ${reason2}`, {
27619
+ const reason3 = this._unfollowedRedirect(this._url, response) ?? response.statusText;
27620
+ throw new SdkHttpError(SdkErrorCode.ClientHttpFailedToOpenStream, `Failed to open SSE stream: ${reason3}`, {
27581
27621
  status: response.status,
27582
27622
  statusText: response.statusText
27583
27623
  });
@@ -27797,7 +27837,7 @@ var StreamableHTTPClientTransport = class {
27797
27837
  }));
27798
27838
  throw markAuthSeamEscape(new UnauthorizedError());
27799
27839
  }
27800
- const text2 = await response.text?.().catch(() => null);
27840
+ const text3 = await response.text?.().catch(() => null);
27801
27841
  if (response.status === 403) {
27802
27842
  const { resourceMetadataUrl, scope, error: error62, errorDescription } = extractWWWAuthenticateParams(response);
27803
27843
  if (error62 === "insufficient_scope") {
@@ -27806,13 +27846,13 @@ var StreamableHTTPClientTransport = class {
27806
27846
  resourceMetadataUrl,
27807
27847
  errorDescription,
27808
27848
  statusText: response.statusText,
27809
- text: text2
27849
+ text: text3
27810
27850
  }, stepUpRetries) !== "AUTHORIZED") throw markAuthSeamEscape(new UnauthorizedError());
27811
27851
  return this._send(message, options, isAuthRetry, stepUpRetries + 1);
27812
27852
  }
27813
27853
  }
27814
- if (response.status === 400 && typeof text2 === "string" && this._isModernEnvelopedRequest(message)) try {
27815
- const parsed = JSONRPCMessageSchema.parse(JSON.parse(text2));
27854
+ if (response.status === 400 && typeof text3 === "string" && this._isModernEnvelopedRequest(message)) try {
27855
+ const parsed = JSONRPCMessageSchema.parse(JSON.parse(text3));
27816
27856
  const requests = (Array.isArray(message) ? message : [message]).filter((m) => isJSONRPCRequest(m));
27817
27857
  if (isJSONRPCErrorResponse(parsed) && requests.some((r) => r.id === parsed.id)) {
27818
27858
  this.onmessage?.(parsed);
@@ -27820,11 +27860,11 @@ var StreamableHTTPClientTransport = class {
27820
27860
  }
27821
27861
  } catch {
27822
27862
  }
27823
- const reason2 = this._unfollowedRedirect(this._url, response) ?? text2;
27824
- throw new SdkHttpError(SdkErrorCode.ClientHttpNotImplemented, `Error POSTing to endpoint: ${reason2}`, {
27863
+ const reason3 = this._unfollowedRedirect(this._url, response) ?? text3;
27864
+ throw new SdkHttpError(SdkErrorCode.ClientHttpNotImplemented, `Error POSTing to endpoint: ${reason3}`, {
27825
27865
  status: response.status,
27826
27866
  statusText: response.statusText,
27827
- text: text2
27867
+ text: text3
27828
27868
  });
27829
27869
  }
27830
27870
  if (response.status === 202) {
@@ -27938,18 +27978,16 @@ function sendAnswer(upstream, response) {
27938
27978
  pipeline(Readable.fromWeb(upstream.body), response, () => {
27939
27979
  });
27940
27980
  }
27941
- var mcpCli = () => join2(dirname(createRequire2(import.meta.url).resolve("@playwright/mcp/package.json")), "cli.js");
27942
27981
  var self = fileURLToPath(import.meta.url);
27943
- var ANSWERS = /* @__PURE__ */ new Set(["browser_handle_dialog", "browser_file_upload"]);
27944
- function withoutHidden(text2, hidden) {
27982
+ function withoutHidden(text3, hidden) {
27945
27983
  const clean = (json4) => {
27946
27984
  const message = JSON.parse(json4);
27947
27985
  if (message.result?.tools)
27948
27986
  message.result.tools = message.result.tools.filter((tool) => !hidden.has(tool.name));
27949
27987
  return JSON.stringify(message);
27950
27988
  };
27951
- if (text2.trimStart().startsWith("{")) return clean(text2);
27952
- return text2.replace(/^data: (\{.*\})$/gm, (_, json4) => `data: ${clean(json4)}`);
27989
+ if (text3.trimStart().startsWith("{")) return clean(text3);
27990
+ return text3.replace(/^data: (\{.*\})$/gm, (_, json4) => `data: ${clean(json4)}`);
27953
27991
  }
27954
27992
  async function end(child) {
27955
27993
  if (child.exitCode !== null || child.signalCode !== null) return;
@@ -28012,17 +28050,7 @@ var Browser = class {
28012
28050
  if (this.#stopping) return failed(new Error("It stopped."));
28013
28051
  const mcp = spawn(
28014
28052
  process.execPath,
28015
- [
28016
- mcpCli(),
28017
- "--host",
28018
- "127.0.0.1",
28019
- "--port",
28020
- "0",
28021
- "--endpoint",
28022
- endpoint,
28023
- "--output-dir",
28024
- join2(folder, "output")
28025
- ],
28053
+ [self, "browser-mcp", "--endpoint", endpoint, "--output", join2(folder, "output")],
28026
28054
  // Its own limit on which files it touches is the folder it starts in.
28027
28055
  { cwd, stdio: ["ignore", "pipe", "pipe"] }
28028
28056
  );
@@ -28075,27 +28103,24 @@ var Browser = class {
28075
28103
  } catch {
28076
28104
  }
28077
28105
  const tool = message.method === "tools/call" ? String(message.params?.name) : void 0;
28078
- const refuse = (text2, code = -32e3) => {
28106
+ if (tool && this.#hidden.has(tool)) {
28079
28107
  response.writeHead(200, { "Content-Type": "application/json" });
28080
- response.end(
28108
+ return void response.end(
28081
28109
  JSON.stringify({
28082
28110
  jsonrpc: "2.0",
28083
28111
  id: message.id ?? null,
28084
- ...code === -32602 ? { error: { code, message: text2 } } : { result: { content: [{ type: "text", text: text2 }], isError: true } }
28112
+ error: { code: -32602, message: `Unknown tool: ${tool}` }
28085
28113
  })
28086
28114
  );
28087
- };
28088
- if (tool && this.#hidden.has(tool)) return refuse(`Unknown tool: ${tool}`, -32602);
28089
- if (tool && this.gate) {
28090
- const answer = await this.gate.wait();
28091
- if (answer) return refuse(answer);
28092
28115
  }
28093
28116
  const headers = {};
28094
28117
  for (const name of FORWARDED) {
28095
28118
  const value = request.headers[name];
28096
28119
  if (typeof value === "string") headers[name] = value;
28097
28120
  }
28098
- if (tool && this.gate?.takeAnsweredChooser())
28121
+ const session = headers["mcp-session-id"] ?? "";
28122
+ if (request.method === "DELETE") this.gate?.sessionEnded(session);
28123
+ if (tool && this.gate?.takeAnsweredChooser(session) && tool !== "browser_file_upload")
28099
28124
  await fetch(`${origin}/mcp`, {
28100
28125
  method: "POST",
28101
28126
  headers,
@@ -28107,15 +28132,15 @@ var Browser = class {
28107
28132
  })
28108
28133
  }).then((answer) => answer.text()).catch(() => {
28109
28134
  });
28110
- if (tool && ANSWERS.has(tool)) this.gate?.agentAnswered();
28135
+ if (tool === "browser_file_upload") this.gate?.agentAnsweredChooser();
28111
28136
  const upstream = await fetch(`${origin}/mcp`, {
28112
28137
  method: request.method,
28113
28138
  headers,
28114
28139
  ...body.length > 0 ? { body } : {}
28115
28140
  });
28116
28141
  if (message.method === "tools/list" && this.#hidden.size > 0) {
28117
- const text2 = withoutHidden(await upstream.text(), this.#hidden);
28118
- return sendAnswer(new Response(text2, upstream), response);
28142
+ const text3 = withoutHidden(await upstream.text(), this.#hidden);
28143
+ return sendAnswer(new Response(text3, upstream), response);
28119
28144
  }
28120
28145
  sendAnswer(upstream, response);
28121
28146
  }
@@ -28128,10 +28153,10 @@ var Browser = class {
28128
28153
  // src/inside.ts
28129
28154
  import { createServer as createServer3 } from "node:http";
28130
28155
  import { homedir } from "node:os";
28131
- import { join as join7 } from "node:path";
28156
+ import { join as join8 } from "node:path";
28132
28157
 
28133
28158
  // src/runtime.ts
28134
- import { join as join6 } from "node:path";
28159
+ import { join as join7 } from "node:path";
28135
28160
 
28136
28161
  // node_modules/@agentorg/exec/dist/lib.js
28137
28162
  import { realpathSync } from "node:fs";
@@ -28139,9 +28164,22 @@ import { resolve as resolve3 } from "node:path";
28139
28164
  import { STATUS_CODES, ServerResponse, createServer } from "node:http";
28140
28165
  import { Http2ServerRequest, constants } from "node:http2";
28141
28166
  import { Readable as Readable2 } from "node:stream";
28167
+ import { randomBytes as randomBytes3 } from "node:crypto";
28168
+ import { lstat as lstat2, open as open2, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2 } from "node:fs/promises";
28169
+ import { dirname as dirname2, join as join3 } from "node:path";
28142
28170
  import { randomBytes as randomBytes2 } from "node:crypto";
28143
- import { lstat, readdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
28144
- import { basename, dirname as dirname2, join as join3, resolve, sep as sep2 } from "node:path";
28171
+ import {
28172
+ chmod,
28173
+ lstat,
28174
+ readdir,
28175
+ readFile,
28176
+ realpath,
28177
+ rename,
28178
+ rm,
28179
+ stat,
28180
+ writeFile
28181
+ } from "node:fs/promises";
28182
+ import { basename, dirname, join as join4, resolve, sep as sep2 } from "node:path";
28145
28183
  import { spawn as spawn2 } from "node:child_process";
28146
28184
  import { randomBytes } from "node:crypto";
28147
28185
  import { StringDecoder } from "node:string_decoder";
@@ -28373,7 +28411,7 @@ var require_ignore = __commonJS2({
28373
28411
  // ever starts again inside it -- `(\\+?) ` did, which made a long run
28374
28412
  // quadratic.
28375
28413
  /(\\+)( ?)/g,
28376
- (_, run22, space) => space ? run22.slice(0, run22.length - run22.length % 2) + SPACE2 : run22,
28414
+ (_, run2, space) => space ? run2.slice(0, run2.length - run2.length % 2) + SPACE2 : run2,
28377
28415
  ESCAPE + SPACE2
28378
28416
  ],
28379
28417
  // git's wildmatch() reads a run of two or more asterisks as one globstar
@@ -28559,10 +28597,10 @@ var require_ignore = __commonJS2({
28559
28597
  var REGEX_REPLACE_TRAILING_DOUBLESTAR = /\uE001$/;
28560
28598
  var replaceTrailingDoublestar = () => ".+(?=$|\\/$)";
28561
28599
  var WILDCARD2 = "[^\\/]*";
28562
- var separatorAfter = (run22, at) => {
28600
+ var separatorAfter = (run2, at) => {
28563
28601
  let separator = EMPTY;
28564
- for (let index = at + 1; index < run22.length && !run22[index].wildcard; index++) {
28565
- separator += run22[index].single;
28602
+ for (let index = at + 1; index < run2.length && !run2[index].wildcard; index++) {
28603
+ separator += run2[index].single;
28566
28604
  }
28567
28605
  return separator;
28568
28606
  };
@@ -28622,26 +28660,26 @@ var require_ignore = __commonJS2({
28622
28660
  }
28623
28661
  }
28624
28662
  let out = EMPTY;
28625
- let run22 = [];
28663
+ let run2 = [];
28626
28664
  const flush = () => {
28627
28665
  let lastWildcard;
28628
- run22.forEach((token, at) => {
28666
+ run2.forEach((token, at) => {
28629
28667
  if (token.wildcard) {
28630
28668
  lastWildcard = at;
28631
28669
  }
28632
28670
  });
28633
- run22.forEach((token, at) => {
28671
+ run2.forEach((token, at) => {
28634
28672
  if (!token.wildcard) {
28635
28673
  out += token.single;
28636
28674
  return;
28637
28675
  }
28638
- out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run22, at)})[^\\/])*`;
28676
+ out += at === lastWildcard ? WILDCARD2 : `(?:(?!${separatorAfter(run2, at)})[^\\/])*`;
28639
28677
  });
28640
- run22 = [];
28678
+ run2 = [];
28641
28679
  };
28642
28680
  tokens.forEach((token) => {
28643
28681
  if (token.boundary === void 0) {
28644
- run22.push(token);
28682
+ run2.push(token);
28645
28683
  return;
28646
28684
  }
28647
28685
  flush();
@@ -34276,7 +34314,7 @@ function bucketFor2(state, inst) {
34276
34314
  return bucket;
34277
34315
  }
34278
34316
  var handoff2;
34279
- var open2 = [];
34317
+ var open3 = [];
34280
34318
  var memo3 = {
34281
34319
  alloc(_inst, payload, empty) {
34282
34320
  const bucket = handoff2;
@@ -34285,7 +34323,7 @@ var memo3 = {
34285
34323
  handoff2 = void 0;
34286
34324
  const entry = { value: empty, issues: null };
34287
34325
  bucket.set(payload.value, entry);
34288
- open2.push(entry);
34326
+ open3.push(entry);
34289
34327
  return empty;
34290
34328
  },
34291
34329
  guard(inst) {
@@ -34356,10 +34394,10 @@ var memo3 = {
34356
34394
  return payload;
34357
34395
  }
34358
34396
  handoff2 = bucket;
34359
- const depth = open2.length;
34397
+ const depth = open3.length;
34360
34398
  const result = base(payload, ctx);
34361
34399
  handoff2 = void 0;
34362
- const entry = open2.length > depth ? open2.pop() : void 0;
34400
+ const entry = open3.length > depth ? open3.pop() : void 0;
34363
34401
  if (result instanceof Promise) {
34364
34402
  return result.then((r) => {
34365
34403
  if (entry)
@@ -38340,8 +38378,8 @@ function ko_default() {
38340
38378
  localeError: error32()
38341
38379
  };
38342
38380
  }
38343
- var capitalizeFirstCharacter = (text2) => {
38344
- return text2.charAt(0).toUpperCase() + text2.slice(1);
38381
+ var capitalizeFirstCharacter = (text22) => {
38382
+ return text22.charAt(0).toUpperCase() + text22.slice(1);
38345
38383
  };
38346
38384
  function getUnitTypeFromNumber(number42) {
38347
38385
  const abs = Math.abs(number42);
@@ -48403,7 +48441,7 @@ function visit(schema, fnOrHandlers) {
48403
48441
  return h ? h(node22, rewritten) : node22;
48404
48442
  };
48405
48443
  const cache = /* @__PURE__ */ new Map();
48406
- function run22(s) {
48444
+ function run2(s) {
48407
48445
  const cached22 = cache.get(s);
48408
48446
  if (cached22 === RESOLVING) {
48409
48447
  return new $ZodLazy2({
@@ -48426,69 +48464,69 @@ function visit(schema, fnOrHandlers) {
48426
48464
  case "object": {
48427
48465
  const oldShape = def.shape;
48428
48466
  const keys = Object.keys(oldShape);
48429
- let changed = false;
48467
+ let changed2 = false;
48430
48468
  const newShape = {};
48431
48469
  for (const k of keys) {
48432
- const mapped = run22(oldShape[k]);
48470
+ const mapped = run2(oldShape[k]);
48433
48471
  if (mapped !== oldShape[k])
48434
- changed = true;
48472
+ changed2 = true;
48435
48473
  newShape[k] = mapped;
48436
48474
  }
48437
48475
  let newCatchall = def.catchall;
48438
48476
  if (def.catchall) {
48439
- newCatchall = run22(def.catchall);
48477
+ newCatchall = run2(def.catchall);
48440
48478
  if (newCatchall !== def.catchall)
48441
- changed = true;
48479
+ changed2 = true;
48442
48480
  }
48443
- return changed ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48481
+ return changed2 ? clone2(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
48444
48482
  }
48445
48483
  case "array": {
48446
- const mapped = run22(def.element);
48484
+ const mapped = run2(def.element);
48447
48485
  return mapped === def.element ? s : clone2(s, { ...def, element: mapped });
48448
48486
  }
48449
48487
  case "tuple": {
48450
48488
  const oldItems = def.items;
48451
- let changed = false;
48489
+ let changed2 = false;
48452
48490
  const newItems = [];
48453
48491
  for (const item of oldItems) {
48454
- const mapped = run22(item);
48492
+ const mapped = run2(item);
48455
48493
  if (mapped !== item)
48456
- changed = true;
48494
+ changed2 = true;
48457
48495
  newItems.push(mapped);
48458
48496
  }
48459
48497
  let newRest = def.rest;
48460
48498
  if (def.rest) {
48461
- newRest = run22(def.rest);
48499
+ newRest = run2(def.rest);
48462
48500
  if (newRest !== def.rest)
48463
- changed = true;
48501
+ changed2 = true;
48464
48502
  }
48465
- return changed ? clone2(s, { ...def, items: newItems, rest: newRest }) : s;
48503
+ return changed2 ? clone2(s, { ...def, items: newItems, rest: newRest }) : s;
48466
48504
  }
48467
48505
  case "record":
48468
48506
  case "map": {
48469
- const newKey = run22(def.keyType);
48470
- const newVal = run22(def.valueType);
48507
+ const newKey = run2(def.keyType);
48508
+ const newVal = run2(def.valueType);
48471
48509
  return newKey === def.keyType && newVal === def.valueType ? s : clone2(s, { ...def, keyType: newKey, valueType: newVal });
48472
48510
  }
48473
48511
  case "set": {
48474
- const newVal = run22(def.valueType);
48512
+ const newVal = run2(def.valueType);
48475
48513
  return newVal === def.valueType ? s : clone2(s, { ...def, valueType: newVal });
48476
48514
  }
48477
48515
  case "union": {
48478
48516
  const oldOptions = def.options;
48479
- let changed = false;
48517
+ let changed2 = false;
48480
48518
  const newOptions = [];
48481
48519
  for (const opt of oldOptions) {
48482
- const mapped = run22(opt);
48520
+ const mapped = run2(opt);
48483
48521
  if (mapped !== opt)
48484
- changed = true;
48522
+ changed2 = true;
48485
48523
  newOptions.push(mapped);
48486
48524
  }
48487
- return changed ? clone2(s, { ...def, options: newOptions }) : s;
48525
+ return changed2 ? clone2(s, { ...def, options: newOptions }) : s;
48488
48526
  }
48489
48527
  case "intersection": {
48490
- const newLeft = run22(def.left);
48491
- const newRight = run22(def.right);
48528
+ const newLeft = run2(def.left);
48529
+ const newRight = run2(def.right);
48492
48530
  return newLeft === def.left && newRight === def.right ? s : clone2(s, { ...def, left: newLeft, right: newRight });
48493
48531
  }
48494
48532
  case "optional":
@@ -48500,23 +48538,23 @@ function visit(schema, fnOrHandlers) {
48500
48538
  case "nonoptional":
48501
48539
  case "promise":
48502
48540
  case "success": {
48503
- const newInner = run22(def.innerType);
48541
+ const newInner = run2(def.innerType);
48504
48542
  return newInner === def.innerType ? s : clone2(s, { ...def, innerType: newInner });
48505
48543
  }
48506
48544
  case "pipe": {
48507
- const newIn = run22(def.in);
48508
- const newOut = run22(def.out);
48545
+ const newIn = run2(def.in);
48546
+ const newOut = run2(def.out);
48509
48547
  return newIn === def.in && newOut === def.out ? s : clone2(s, { ...def, in: newIn, out: newOut });
48510
48548
  }
48511
48549
  case "function": {
48512
- const newInput = run22(def.input);
48513
- const newOutput = run22(def.output);
48550
+ const newInput = run2(def.input);
48551
+ const newOutput = run2(def.output);
48514
48552
  return newInput === def.input && newOutput === def.output ? s : clone2(s, { ...def, input: newInput, output: newOutput });
48515
48553
  }
48516
48554
  case "lazy": {
48517
48555
  const original = def.getter;
48518
48556
  const { _cachedInner, ...rest } = def;
48519
- return clone2(s, { ...rest, getter: () => run22(original()) });
48557
+ return clone2(s, { ...rest, getter: () => run2(original()) });
48520
48558
  }
48521
48559
  // A leaf by choice: `parts` are regex fragments, not data positions.
48522
48560
  case "template_literal":
@@ -48547,7 +48585,7 @@ function visit(schema, fnOrHandlers) {
48547
48585
  }
48548
48586
  }
48549
48587
  }
48550
- return run22(schema);
48588
+ return run2(schema);
48551
48589
  }
48552
48590
  function deepPartial(schema) {
48553
48591
  return visit(schema, {
@@ -52975,7 +53013,7 @@ function linkedRoundAbort2(outer) {
52975
53013
  if (outer?.aborted) controller.abort(outer.reason);
52976
53014
  return {
52977
53015
  signal: controller.signal,
52978
- abort: (reason2) => controller.abort(reason2),
53016
+ abort: (reason22) => controller.abort(reason22),
52979
53017
  dispose: () => outer?.removeEventListener("abort", onOuterAbort)
52980
53018
  };
52981
53019
  }
@@ -53770,8 +53808,8 @@ var Protocol2 = class {
53770
53808
  return;
53771
53809
  }
53772
53810
  if (options?.signal?.aborted) {
53773
- const reason2 = options.signal.reason;
53774
- throw reason2 instanceof SdkError2 ? reason2 : new SdkError2(SdkErrorCode2.RequestTimeout, String(reason2));
53811
+ const reason22 = options.signal.reason;
53812
+ throw reason22 instanceof SdkError2 ? reason22 : new SdkError2(SdkErrorCode2.RequestTimeout, String(reason22));
53775
53813
  }
53776
53814
  const requestAbort = codec2.era === MODERN_WIRE_REVISION2 && this._transport.hasPerRequestStream === true ? new AbortController() : void 0;
53777
53815
  const messageId = this._requestMessageId++;
@@ -53793,7 +53831,7 @@ var Protocol2 = class {
53793
53831
  }
53794
53832
  const outbound = this._envelopeOutbound(jsonrpcRequest);
53795
53833
  let responseReceived = false;
53796
- const cancel = (reason2) => {
53834
+ const cancel = (reason22) => {
53797
53835
  if (responseReceived) return;
53798
53836
  this._progressHandlers.delete(messageId);
53799
53837
  if (requestAbort === void 0) {
@@ -53802,7 +53840,7 @@ var Protocol2 = class {
53802
53840
  method: "notifications/cancelled",
53803
53841
  params: {
53804
53842
  requestId: messageId,
53805
- reason: String(reason2)
53843
+ reason: String(reason22)
53806
53844
  }
53807
53845
  }), {
53808
53846
  relatedRequestId,
@@ -53810,7 +53848,7 @@ var Protocol2 = class {
53810
53848
  onresumptiontoken
53811
53849
  }).catch((error62) => this._onerror(/* @__PURE__ */ new Error(`Failed to send cancellation: ${error62}`)));
53812
53850
  } else requestAbort.abort();
53813
- reject(reason2 instanceof SdkError2 ? reason2 : new SdkError2(SdkErrorCode2.RequestTimeout, String(reason2)));
53851
+ reject(reason22 instanceof SdkError2 ? reason22 : new SdkError2(SdkErrorCode2.RequestTimeout, String(reason22)));
53814
53852
  };
53815
53853
  this._responseHandlers.set(messageId, (response) => {
53816
53854
  if (options?.signal?.aborted) return;
@@ -58131,7 +58169,7 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
58131
58169
  }
58132
58170
  errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
58133
58171
  if (!errors || errors.length === 0) return "No errors";
58134
- return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
58172
+ return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text22, msg) => text22 + separator + msg);
58135
58173
  }
58136
58174
  $dataMetaSchema(metaSchema, keywordsJsonPointers) {
58137
58175
  const rules = this.RULES.all;
@@ -63273,21 +63311,21 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
63273
63311
  const reader = request.body.getReader();
63274
63312
  const decoder = new TextDecoder();
63275
63313
  let received = 0;
63276
- let text2 = "";
63314
+ let text22 = "";
63277
63315
  try {
63278
63316
  for (; ; ) {
63279
63317
  const { done, value } = await reader.read();
63280
63318
  if (done) break;
63281
63319
  received += value.byteLength;
63282
63320
  if (received > maxBytes) return { tooLarge: true };
63283
- text2 += decoder.decode(value, { stream: true });
63321
+ text22 += decoder.decode(value, { stream: true });
63284
63322
  }
63285
63323
  } finally {
63286
63324
  reader.releaseLock();
63287
63325
  }
63288
63326
  return {
63289
63327
  tooLarge: false,
63290
- text: text2 + decoder.decode()
63328
+ text: text22 + decoder.decode()
63291
63329
  };
63292
63330
  }
63293
63331
  var WebStandardStreamableHTTPServerTransport = class {
@@ -64356,8 +64394,8 @@ var readRawBodyIfAvailable = (request) => {
64356
64394
  };
64357
64395
  var normalizeAbortError = (request, incoming) => {
64358
64396
  if (incoming.errored) return incoming.errored;
64359
- const reason2 = request[abortReasonKey];
64360
- if (reason2 !== void 0) return reason2 instanceof Error ? reason2 : new Error(String(reason2));
64397
+ const reason22 = request[abortReasonKey];
64398
+ if (reason22 !== void 0) return reason22 instanceof Error ? reason22 : new Error(String(reason22));
64361
64399
  return /* @__PURE__ */ new Error("Client connection prematurely closed.");
64362
64400
  };
64363
64401
  var readBodyDirect = (request) => {
@@ -64451,10 +64489,10 @@ var requestPrototype = {
64451
64489
  get headers() {
64452
64490
  return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
64453
64491
  },
64454
- [abortRequest](reason2) {
64455
- if (this[abortReasonKey] === void 0) this[abortReasonKey] = reason2;
64492
+ [abortRequest](reason22) {
64493
+ if (this[abortReasonKey] === void 0) this[abortReasonKey] = reason22;
64456
64494
  const abortController = this[abortControllerKey];
64457
- if (abortController && !abortController.signal.aborted) abortController.abort(reason2);
64495
+ if (abortController && !abortController.signal.aborted) abortController.abort(reason22);
64458
64496
  },
64459
64497
  [getAbortController]() {
64460
64498
  this[abortControllerKey] ||= new AbortController();
@@ -65098,8 +65136,8 @@ var upgradeWebSocket = defineWebSocketHelper(async (c, events, options) => {
65098
65136
  ws.on("message", bufferMessage);
65099
65137
  const ctx = {
65100
65138
  binaryType: "arraybuffer",
65101
- close(code, reason2) {
65102
- ws.close(code, reason2);
65139
+ close(code, reason22) {
65140
+ ws.close(code, reason22);
65103
65141
  },
65104
65142
  protocol: ws.protocol,
65105
65143
  raw: ws,
@@ -65129,11 +65167,11 @@ var upgradeWebSocket = defineWebSocketHelper(async (c, events, options) => {
65129
65167
  ws.on("message", (data, isBinary) => {
65130
65168
  handleMessage(data, isBinary);
65131
65169
  });
65132
- ws.on("close", (code, reason2) => {
65170
+ ws.on("close", (code, reason22) => {
65133
65171
  try {
65134
65172
  events?.onClose?.(new CloseEvent("close", {
65135
65173
  code,
65136
- reason: reason2.toString()
65174
+ reason: reason22.toString()
65137
65175
  }), ctx);
65138
65176
  } catch (e) {
65139
65177
  (options?.onError ?? console.error)(e);
@@ -65757,9 +65795,9 @@ function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader
65757
65795
  } }));
65758
65796
  }
65759
65797
  }
65760
- function splitLines(text2) {
65761
- const hasTrailingNl = text2.endsWith("\n");
65762
- const result = text2.split("\n").map((line) => line + "\n");
65798
+ function splitLines(text22) {
65799
+ const hasTrailingNl = text22.endsWith("\n");
65800
+ const result = text22.split("\n").map((line) => line + "\n");
65763
65801
  if (hasTrailingNl) {
65764
65802
  result.pop();
65765
65803
  } else {
@@ -65769,26 +65807,26 @@ function splitLines(text2) {
65769
65807
  }
65770
65808
  var CHARS_PER_TOKEN = 4;
65771
65809
  var HELD = 1024 * 1024;
65772
- function cut(text2, max) {
65773
- if (text2.length <= max) return text2;
65810
+ function cut(text22, max) {
65811
+ if (text22.length <= max) return text22;
65774
65812
  const half = Math.max(1, Math.floor(max / 2));
65775
- const head = text2.slice(0, half).replace(/[\uD800-\uDBFF]$/, "");
65776
- const tail = text2.slice(-half).replace(/^[\uDC00-\uDFFF]/, "");
65813
+ const head = text22.slice(0, half).replace(/[\uD800-\uDBFF]$/, "");
65814
+ const tail = text22.slice(-half).replace(/^[\uDC00-\uDFFF]/, "");
65777
65815
  return `${head}
65778
- \u2026 ${text2.length - head.length - tail.length} characters cut \u2026
65816
+ \u2026 ${text22.length - head.length - tail.length} characters cut \u2026
65779
65817
  ${tail}`;
65780
65818
  }
65781
65819
  var Pending = class {
65782
65820
  #head = "";
65783
65821
  #tail = "";
65784
65822
  #dropped = 0;
65785
- add(text2) {
65823
+ add(text22) {
65786
65824
  const room = HELD / 2 - this.#head.length;
65787
65825
  if (room > 0) {
65788
- this.#head += text2.slice(0, room);
65789
- text2 = text2.slice(room);
65826
+ this.#head += text22.slice(0, room);
65827
+ text22 = text22.slice(room);
65790
65828
  }
65791
- this.#tail += text2;
65829
+ this.#tail += text22;
65792
65830
  const over = this.#tail.length - HELD / 2;
65793
65831
  if (over > 0) {
65794
65832
  this.#tail = this.#tail.slice(over);
@@ -65797,13 +65835,13 @@ var Pending = class {
65797
65835
  }
65798
65836
  /** What is pending, leaving nothing. */
65799
65837
  take() {
65800
- const text2 = this.#dropped ? `${this.#head}
65838
+ const text22 = this.#dropped ? `${this.#head}
65801
65839
  \u2026 ${this.#dropped} characters cut \u2026
65802
65840
  ${this.#tail}` : this.#head + this.#tail;
65803
65841
  this.#head = "";
65804
65842
  this.#tail = "";
65805
65843
  this.#dropped = 0;
65806
- return text2;
65844
+ return text22;
65807
65845
  }
65808
65846
  };
65809
65847
  var MIN_YIELD_MS = 250;
@@ -65885,9 +65923,9 @@ var Sessions = class {
65885
65923
  ]) {
65886
65924
  const decoder = new StringDecoder("utf8");
65887
65925
  stream.on("data", (chunk) => {
65888
- const text2 = decoder.write(chunk);
65889
- pending.add(text2);
65890
- if (text2) this.#emit({ type: "output", id, stream: name, text: text2 });
65926
+ const text22 = decoder.write(chunk);
65927
+ pending.add(text22);
65928
+ if (text22) this.#emit({ type: "output", id, stream: name, text: text22 });
65891
65929
  });
65892
65930
  }
65893
65931
  child.stdin.on("error", () => {
@@ -65980,6 +66018,7 @@ var refusing = async (work) => {
65980
66018
  throw new ExecRefused(reason(error62));
65981
66019
  }
65982
66020
  };
66021
+ var foldersFirst = (a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name);
65983
66022
  async function inside(root, path) {
65984
66023
  const refused2 = () => new ExecRefused(`${path} is outside the workspace.`);
65985
66024
  const wanted = resolve(root, path);
@@ -65994,26 +66033,33 @@ async function inside(root, path) {
65994
66033
  () => false
65995
66034
  ))
65996
66035
  throw refused2();
65997
- const above = dirname2(at);
66036
+ const above = dirname(at);
65998
66037
  if (above === at) throw refused2();
65999
- return join3(await real(above), basename(at));
66038
+ return join4(await real(above), basename(at));
66000
66039
  }
66001
66040
  };
66002
66041
  const found = await real(wanted);
66003
66042
  if (found !== root && !found.startsWith(root + sep2)) throw refused2();
66004
66043
  return found;
66005
66044
  }
66045
+ async function aFile(path, given) {
66046
+ const found = await stat(path);
66047
+ if (found.isDirectory()) throw new ExecRefused(`${given} is a folder.`);
66048
+ if (!found.isFile()) throw new ExecRefused(`${given} isn't a file that can be read.`);
66049
+ return found;
66050
+ }
66006
66051
  function createFiles(root) {
66007
66052
  const at = (path) => inside(root, path);
66008
- const text2 = async (path) => {
66053
+ const text22 = async (path, given) => {
66054
+ await aFile(path, given);
66009
66055
  const bytes = await readFile(path);
66010
- if (bytes.includes(0)) throw new ExecRefused(`${path} is a binary file.`);
66056
+ if (bytes.includes(0)) throw new ExecRefused(`${given} is a binary file.`);
66011
66057
  return bytes.toString("utf8");
66012
66058
  };
66013
66059
  return {
66014
66060
  /** Numbered lines of a file. */
66015
66061
  read: (input2) => refusing(async () => {
66016
- const lines = (await text2(await at(input2.path))).split("\n");
66062
+ const lines = (await text22(await at(input2.path), input2.path)).split("\n");
66017
66063
  const first = Math.max(input2.offset ?? 1, 1);
66018
66064
  const shown = lines.slice(first - 1, first - 1 + (input2.limit ?? DEFAULT_LINES)).map((line, i) => `${first + i} ${cut(line, MAX_LINE_CHARS)}`);
66019
66065
  const last = first + shown.length - 1;
@@ -66024,13 +66070,11 @@ ${cut(shown.join("\n"), MAX_READ_CHARS)}`;
66024
66070
  list: (input2) => refusing(async () => {
66025
66071
  const folder = await at(input2.path ?? ".");
66026
66072
  const entries = await readdir(folder, { withFileTypes: true });
66027
- entries.sort(
66028
- (a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name)
66029
- );
66073
+ entries.sort(foldersFirst);
66030
66074
  const lines = await Promise.all(
66031
66075
  entries.slice(0, MAX_ENTRIES).map(async (entry) => {
66032
66076
  if (entry.isDirectory()) return `${entry.name}/`;
66033
- const { size } = await lstat(join3(folder, entry.name));
66077
+ const { size } = await lstat(join4(folder, entry.name));
66034
66078
  return `${entry.name} ${size}`;
66035
66079
  })
66036
66080
  );
@@ -66050,7 +66094,7 @@ ${cut(shown.join("\n"), MAX_READ_CHARS)}`;
66050
66094
  });
66051
66095
  if (found && found.size > MAX_EDIT_BYTES)
66052
66096
  throw new ExecRefused(`${input2.path} is too large to edit here.`);
66053
- const before = found ? await text2(path) : "";
66097
+ const before = found ? await text22(path, input2.path) : "";
66054
66098
  let after;
66055
66099
  if (input2.old_text === "") {
66056
66100
  if (before !== "")
@@ -66067,9 +66111,10 @@ ${cut(shown.join("\n"), MAX_READ_CHARS)}`;
66067
66111
  after = before.split(input2.old_text).join(input2.new_text);
66068
66112
  }
66069
66113
  if (after === before) throw new ExecRefused("That edit changes nothing.");
66070
- const temporary = join3(dirname2(path), `.agentexec-${randomBytes2(6).toString("hex")}`);
66114
+ const temporary = join4(dirname(path), `.agentexec-${randomBytes2(6).toString("hex")}`);
66071
66115
  try {
66072
66116
  await writeFile(temporary, after, found ? { mode: found.mode } : {});
66117
+ if (found) await chmod(temporary, found.mode & 511);
66073
66118
  await rename(temporary, path);
66074
66119
  } catch (error62) {
66075
66120
  await rm(temporary, { force: true });
@@ -66084,32 +66129,18 @@ var DEADLINE_MS = 2e4;
66084
66129
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
66085
66130
  var MAX_LINE_CHARS2 = 500;
66086
66131
  var DEFAULT_MATCHES = 200;
66132
+ var MAX_CHANGES = 1e3;
66087
66133
  var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules"]);
66088
- function run(job) {
66089
- const start = resolve2(job.root, job.path ?? ".");
66090
- const pattern = job.regex ? job.pattern : job.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
66091
- const matcher = new RegExp(pattern, job.ignore_case ? "i" : "");
66092
- const limit = job.limit ?? DEFAULT_MATCHES;
66093
- const out = [];
66094
- const name = (path) => relative(job.root, path) || path;
66095
- const scan = (file2) => {
66096
- if (statSync(file2).size > MAX_FILE_BYTES) return;
66097
- const bytes = readFileSync(file2);
66098
- if (bytes.includes(0)) return;
66099
- bytes.toString("utf8").split("\n").forEach((line, i) => {
66100
- if (out.length <= limit && matcher.test(line))
66101
- out.push(`${name(file2)}:${i + 1}:${line.slice(0, MAX_LINE_CHARS2)}`);
66102
- });
66103
- };
66134
+ function eachFile(start, visit2) {
66104
66135
  const walk = (folder, rules) => {
66105
66136
  let here = rules;
66106
66137
  try {
66107
- const own22 = (0, import_ignore.default)().add(readFileSync(join22(folder, ".gitignore"), "utf8"));
66108
- here = [...rules, { base: folder, ignore: own22 }];
66138
+ const own22 = join22(folder, ".gitignore");
66139
+ if (statSync(own22).isFile())
66140
+ here = [...rules, { base: folder, ignore: (0, import_ignore.default)().add(readFileSync(own22, "utf8")) }];
66109
66141
  } catch {
66110
66142
  }
66111
66143
  for (const entry of readdirSync(folder, { withFileTypes: true })) {
66112
- if (out.length > limit) return;
66113
66144
  if (SKIPPED.has(entry.name) || entry.isSymbolicLink()) continue;
66114
66145
  const path = join22(folder, entry.name);
66115
66146
  const suffix = entry.isDirectory() ? "/" : "";
@@ -66118,30 +66149,78 @@ function run(job) {
66118
66149
  );
66119
66150
  if (ignored) continue;
66120
66151
  try {
66121
- if (entry.isDirectory()) walk(path, here);
66122
- else if (entry.isFile()) scan(path);
66152
+ const carryOn = entry.isDirectory() ? walk(path, here) : !entry.isFile() || visit2(path);
66153
+ if (!carryOn) return false;
66123
66154
  } catch {
66124
66155
  }
66125
66156
  }
66157
+ return true;
66126
66158
  };
66127
- if (statSync(start).isDirectory()) walk(start, []);
66159
+ walk(start, []);
66160
+ }
66161
+ function searched(job) {
66162
+ const start = resolve2(job.root, job.path ?? ".");
66163
+ const pattern = job.regex ? job.pattern : job.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
66164
+ const matcher = new RegExp(pattern, job.ignore_case ? "i" : "");
66165
+ const limit = job.limit ?? DEFAULT_MATCHES;
66166
+ const out = [];
66167
+ const name = (path) => relative(job.root, path) || path;
66168
+ const scan = (file2) => {
66169
+ const found = statSync(file2);
66170
+ if (!found.isFile() || found.size > MAX_FILE_BYTES) return;
66171
+ const bytes = readFileSync(file2);
66172
+ if (bytes.includes(0)) return;
66173
+ bytes.toString("utf8").split("\n").forEach((line, i) => {
66174
+ if (out.length <= limit && matcher.test(line))
66175
+ out.push(`${name(file2)}:${i + 1}:${line.slice(0, MAX_LINE_CHARS2)}`);
66176
+ });
66177
+ };
66178
+ if (statSync(start).isDirectory())
66179
+ eachFile(start, (file2) => {
66180
+ scan(file2);
66181
+ return out.length <= limit;
66182
+ });
66128
66183
  else scan(start);
66129
66184
  const more = out.length > limit;
66130
66185
  return (more ? [...out.slice(0, limit), `\u2026 more than ${limit} matches`] : out).join("\n");
66131
66186
  }
66132
- async function search(root, input2) {
66133
- const path = await inside(root, input2.path ?? ".");
66187
+ function changed(job) {
66188
+ const changes = [];
66189
+ let more = false;
66190
+ const newest = () => {
66191
+ changes.sort(
66192
+ (a, b) => b.modifiedAt.localeCompare(a.modifiedAt) || a.path.localeCompare(b.path)
66193
+ );
66194
+ if (changes.length <= MAX_CHANGES) return;
66195
+ more = true;
66196
+ changes.length = MAX_CHANGES;
66197
+ };
66198
+ eachFile(job.root, (file2) => {
66199
+ const { mtime, size } = statSync(file2);
66200
+ if (mtime.getTime() > job.since) {
66201
+ changes.push({
66202
+ path: relative(job.root, file2).split(sep22).join("/"),
66203
+ size,
66204
+ modifiedAt: mtime.toISOString()
66205
+ });
66206
+ if (changes.length >= 2 * MAX_CHANGES) newest();
66207
+ }
66208
+ return true;
66209
+ });
66210
+ newest();
66211
+ return { changes, more };
66212
+ }
66213
+ function onThread(job, what) {
66134
66214
  return new Promise((done, failed) => {
66135
- const workerData2 = { ...input2, path, root, agentexecSearch: true };
66136
- const worker = new Worker(fileURLToPath2(import.meta.url), { workerData: workerData2 });
66215
+ const worker = new Worker(fileURLToPath2(import.meta.url), { workerData: job });
66137
66216
  const timer = setTimeout(() => {
66138
66217
  void worker.terminate();
66139
- failed(new ExecRefused(`That search took over ${DEADLINE_MS / 1e3} s, so it was stopped.`));
66218
+ failed(new ExecRefused(`${what} took over ${DEADLINE_MS / 1e3} s, so it was stopped.`));
66140
66219
  }, DEADLINE_MS);
66141
- worker.once("message", (text2) => {
66220
+ worker.once("message", (answer) => {
66142
66221
  clearTimeout(timer);
66143
66222
  void worker.terminate();
66144
- done(text2);
66223
+ done(answer);
66145
66224
  });
66146
66225
  worker.once("error", (error62) => {
66147
66226
  clearTimeout(timer);
@@ -66149,8 +66228,136 @@ async function search(root, input2) {
66149
66228
  });
66150
66229
  });
66151
66230
  }
66152
- if (!isMainThread && workerData?.agentexecSearch)
66153
- parentPort?.postMessage(run(workerData));
66231
+ async function search(root, input2) {
66232
+ const path = await inside(root, input2.path ?? ".");
66233
+ return onThread({ ...input2, path, root, agentexecSearch: true }, "That search");
66234
+ }
66235
+ function changedSince(root, since) {
66236
+ return onThread({ root, since, agentexecSearch: true }, "Looking for what changed");
66237
+ }
66238
+ if (!isMainThread && workerData?.agentexecSearch) {
66239
+ const job = workerData;
66240
+ parentPort?.postMessage("since" in job ? changed(job) : searched(job));
66241
+ }
66242
+ var MAX_READ_BYTES = 1024 * 1024;
66243
+ function createHostFiles(root, level) {
66244
+ const uploads = /* @__PURE__ */ new Set();
66245
+ let closes = 0;
66246
+ const files = {
66247
+ list: (path = ".") => refusing(async () => {
66248
+ const folder = await inside(root, path);
66249
+ const found = await readdir2(folder, { withFileTypes: true });
66250
+ found.sort(foldersFirst);
66251
+ const entries = await Promise.all(
66252
+ found.slice(0, MAX_ENTRIES).map(async (entry) => {
66253
+ const own22 = await lstat2(join3(folder, entry.name)).catch(() => void 0);
66254
+ return own22 && {
66255
+ name: entry.name,
66256
+ kind: entry.isDirectory() ? "folder" : entry.isSymbolicLink() ? "link" : "file",
66257
+ size: own22.size,
66258
+ modifiedAt: own22.mtime.toISOString()
66259
+ };
66260
+ })
66261
+ );
66262
+ return {
66263
+ entries: entries.filter((entry) => entry !== void 0),
66264
+ more: found.length > MAX_ENTRIES
66265
+ };
66266
+ }),
66267
+ read: (path, offset, length) => refusing(async () => {
66268
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 1 || length > MAX_READ_BYTES)
66269
+ throw new ExecRefused("A read starts at 0 or later and takes 1 to 1,048,576 bytes.");
66270
+ const real = await inside(root, path);
66271
+ const found = await aFile(real, path);
66272
+ const file2 = await open2(real, "r");
66273
+ try {
66274
+ const bytes = Buffer.alloc(Math.max(0, Math.min(length, found.size - offset)));
66275
+ const { bytesRead } = await file2.read(bytes, 0, bytes.length, offset);
66276
+ return {
66277
+ bytes: bytes.subarray(0, bytesRead),
66278
+ size: found.size,
66279
+ modifiedAt: found.mtime.toISOString()
66280
+ };
66281
+ } finally {
66282
+ await file2.close();
66283
+ }
66284
+ }),
66285
+ changed: (since) => refusing(async () => {
66286
+ const from = since.getTime();
66287
+ if (Number.isNaN(from)) throw new ExecRefused("That isn't a time.");
66288
+ return changedSince(root, from);
66289
+ }),
66290
+ upload: (path) => refusing(async () => {
66291
+ if (level === "read") throw new ExecRefused("Files here can be read, not written.");
66292
+ const opening = closes;
66293
+ const target = await inside(root, path);
66294
+ const before = await stat2(target).catch((error62) => {
66295
+ if (error62.code === "ENOENT") return void 0;
66296
+ throw error62;
66297
+ });
66298
+ if (before?.isDirectory()) throw new ExecRefused(`${path} is a folder.`);
66299
+ await stat2(dirname2(target));
66300
+ const temporary = join3(dirname2(target), `.agentexec-${randomBytes3(6).toString("hex")}`);
66301
+ const file2 = await open2(temporary, "wx");
66302
+ const discard = async () => {
66303
+ await file2.close().catch(() => {
66304
+ });
66305
+ await rm2(temporary, { force: true });
66306
+ };
66307
+ let ended = false;
66308
+ let written = Promise.resolve();
66309
+ const end2 = () => {
66310
+ if (ended) throw new ExecRefused("That upload has ended.");
66311
+ ended = true;
66312
+ uploads.delete(upload);
66313
+ };
66314
+ const upload = {
66315
+ write: (bytes) => refusing(async () => {
66316
+ if (ended) throw new ExecRefused("That upload has ended.");
66317
+ written = written.then(() => file2.appendFile(bytes));
66318
+ await written;
66319
+ }),
66320
+ finish: () => refusing(async () => {
66321
+ end2();
66322
+ try {
66323
+ await written;
66324
+ await file2.close();
66325
+ await rename2(temporary, target);
66326
+ } catch (error62) {
66327
+ await discard();
66328
+ throw error62;
66329
+ }
66330
+ const after = await stat2(target);
66331
+ return { size: after.size, modifiedAt: after.mtime.toISOString() };
66332
+ }),
66333
+ abort: () => refusing(async () => {
66334
+ if (ended) return;
66335
+ end2();
66336
+ await written.catch(() => {
66337
+ });
66338
+ await discard();
66339
+ })
66340
+ };
66341
+ try {
66342
+ if (before) await file2.chmod(before.mode & 511);
66343
+ if (closes !== opening) throw new ExecRefused("It closed before that upload began.");
66344
+ } catch (error62) {
66345
+ await discard();
66346
+ throw error62;
66347
+ }
66348
+ uploads.add(upload);
66349
+ return upload;
66350
+ })
66351
+ };
66352
+ return {
66353
+ files,
66354
+ /** Give up every upload still open, and any that is opening. */
66355
+ close: async () => {
66356
+ closes++;
66357
+ await Promise.all([...uploads].map((upload) => upload.abort()));
66358
+ }
66359
+ };
66360
+ }
66154
66361
  var text = (value) => ({ content: [{ type: "text", text: value }] });
66155
66362
  var refused = (error62) => {
66156
66363
  if (!(error62 instanceof ExecRefused)) throw error62;
@@ -66271,7 +66478,7 @@ function createServer2(root, sessions, version22, level) {
66271
66478
  );
66272
66479
  return server;
66273
66480
  }
66274
- var VERSION = "0.0.3";
66481
+ var VERSION = "0.0.4";
66275
66482
  function createAgentExec(options) {
66276
66483
  const root = realpathSync(resolve3(options.workspace));
66277
66484
  const level = options.level ?? "read";
@@ -66283,6 +66490,7 @@ function createAgentExec(options) {
66283
66490
  } catch {
66284
66491
  }
66285
66492
  });
66493
+ const host = createHostFiles(root, level);
66286
66494
  return {
66287
66495
  async handle(request, response, body) {
66288
66496
  const server = createServer2(root, sessions, VERSION, level);
@@ -66298,266 +66506,339 @@ function createAgentExec(options) {
66298
66506
  listeners.add(listener);
66299
66507
  return () => void listeners.delete(listener);
66300
66508
  },
66301
- close: () => sessions.stopAll()
66509
+ files: host.files,
66510
+ async close() {
66511
+ await Promise.all([sessions.stopAll(), host.close()]);
66512
+ }
66302
66513
  };
66303
66514
  }
66304
66515
 
66305
66516
  // src/screencast.ts
66517
+ var FIRST_FRAME_MS = 500;
66518
+ var RESTARTS = 3;
66306
66519
  async function startScreencast(page, onFrame) {
66307
- await page.screencast.start({
66520
+ let seen = false;
66521
+ let stopped = false;
66522
+ const start = () => page.screencast.start({
66308
66523
  quality: 70,
66309
- onFrame: ({ data, viewportWidth, viewportHeight }) => onFrame({ data, width: viewportWidth, height: viewportHeight })
66310
- });
66311
- return () => page.screencast.stop().catch(() => {
66524
+ onFrame: ({ data, viewportWidth, viewportHeight }) => {
66525
+ seen = true;
66526
+ onFrame({ data, width: viewportWidth, height: viewportHeight });
66527
+ }
66312
66528
  });
66529
+ await start();
66530
+ void (async () => {
66531
+ for (let left = RESTARTS; left > 0; left--) {
66532
+ await new Promise((done) => setTimeout(done, FIRST_FRAME_MS).unref());
66533
+ if (seen || stopped) return;
66534
+ await page.screencast.stop().catch(() => {
66535
+ });
66536
+ if (stopped) return;
66537
+ if (!await start().then(() => true).catch(() => false))
66538
+ return;
66539
+ }
66540
+ })();
66541
+ return async () => {
66542
+ stopped = true;
66543
+ await page.screencast.stop().catch(() => {
66544
+ });
66545
+ };
66313
66546
  }
66314
66547
 
66315
66548
  // src/browser-live.ts
66316
- var WAIT_MS = 25e3;
66317
- var IDLE_MS = 6e4;
66549
+ var TITLE_CHECK_MS = 1e3;
66318
66550
  var STEP_MS = 2e3;
66319
66551
  var MAX_BUFFERED = 4 * 1024 * 1024;
66320
66552
  var sleep3 = (ms) => new Promise((done) => setTimeout(done, ms));
66321
66553
  var within = (work, ms) => Promise.race([work, sleep3(ms).then(() => void 0)]);
66322
66554
  var BrowserLive = class {
66323
66555
  #endpoint;
66324
- #sockets = /* @__PURE__ */ new Map();
66325
- #viewer;
66556
+ #viewers = /* @__PURE__ */ new Map();
66557
+ #client;
66326
66558
  #context;
66327
66559
  #ids = /* @__PURE__ */ new Map();
66328
66560
  #nextId = 1;
66329
- #shown;
66330
- #stopCast;
66331
- #size = { width: 0, height: 0 };
66332
- #dialog;
66333
- #chooser;
66334
- #choice;
66335
- #swallowUp = false;
66336
- #driver;
66337
- #lastInput = 0;
66338
- #agentWaiting = 0;
66339
- #answeredChooser = false;
66340
- #steps = Promise.resolve();
66561
+ #casts = /* @__PURE__ */ new Map();
66562
+ /** A page's cast that is still stopping, which its next one waits for. */
66563
+ #stopping = /* @__PURE__ */ new Map();
66564
+ #dialogs = /* @__PURE__ */ new Map();
66565
+ #choosers = /* @__PURE__ */ new Map();
66566
+ /** The agent's MCP sessions seen so far. */
66567
+ #sessions = /* @__PURE__ */ new Set();
66568
+ /** Those still to be told that a person answered a file chooser. */
66569
+ #owed = /* @__PURE__ */ new Set();
66570
+ /** The tabs last sent, to send them again only when something about them changed. */
66571
+ #lastTabs = "";
66572
+ #titles;
66573
+ #checking = false;
66574
+ /** Counts each time everyone has left, so a connection begun before that knows it is stale. */
66575
+ #ended = 0;
66341
66576
  constructor(endpoint) {
66342
66577
  this.#endpoint = endpoint;
66343
66578
  }
66344
66579
  // ---- The agent's side
66345
- async wait() {
66346
- if (!this.#held()) return void 0;
66347
- this.#agentWaiting++;
66348
- this.#announceDriver();
66349
- try {
66350
- const deadline = Date.now() + WAIT_MS;
66351
- while (this.#held() && Date.now() < deadline) await sleep3(200);
66352
- return this.#held() ? "A person has the browser now, so this didn't run. Try again later." : "A person used the browser, so this didn't run and the page may have changed. Take a new snapshot before you act.";
66353
- } finally {
66354
- this.#agentWaiting--;
66355
- this.#announceDriver();
66356
- }
66580
+ takeAnsweredChooser(session) {
66581
+ this.#sessions.add(session);
66582
+ return this.#owed.delete(session);
66357
66583
  }
66358
- takeAnsweredChooser() {
66359
- const answered2 = this.#answeredChooser;
66360
- this.#answeredChooser = false;
66361
- return answered2;
66584
+ sessionEnded(session) {
66585
+ this.#sessions.delete(session);
66586
+ this.#owed.delete(session);
66362
66587
  }
66363
- agentAnswered() {
66364
- this.#dialog = void 0;
66365
- this.#chooser = void 0;
66366
- this.#broadcast({ type: "dialog", open: false });
66367
- this.#broadcast({ type: "file-chooser", open: false });
66588
+ agentAnsweredChooser() {
66589
+ this.#choosers.clear();
66590
+ for (const ws of this.#viewers.keys()) this.#send(ws, { type: "file-chooser", open: false });
66368
66591
  }
66369
66592
  // ---- The person's side
66370
66593
  open(ws, mode) {
66371
- this.#sockets.set(ws, mode);
66594
+ const viewer = { mode, chose: false, swallowUp: false, steps: Promise.resolve() };
66595
+ this.#viewers.set(ws, viewer);
66372
66596
  ws.on("close", () => this.#left(ws));
66373
66597
  ws.on("message", (data, binary) => {
66374
- if (binary || this.#sockets.get(ws) !== "operate") return;
66598
+ if (binary || viewer.mode !== "operate") return;
66375
66599
  let message;
66376
66600
  try {
66377
66601
  message = JSON.parse(data.toString());
66378
66602
  } catch {
66379
66603
  return;
66380
66604
  }
66381
- this.#input(ws, message);
66605
+ this.#input(ws, viewer, message);
66382
66606
  });
66383
66607
  this.#begin().then(
66384
- () => this.#greet(ws),
66608
+ // Unless it chose a tab while this was starting, it is shown the newest.
66609
+ () => viewer.chose ? void 0 : this.#show(ws, this.#newest()),
66385
66610
  () => ws.close(1011, "The browser can't be reached.")
66386
66611
  );
66387
66612
  }
66388
66613
  async close() {
66389
- for (const ws of this.#sockets.keys()) ws.close(1001, "It stopped.");
66614
+ for (const ws of this.#viewers.keys()) ws.close(1001, "It stopped.");
66390
66615
  await this.#end();
66391
66616
  }
66392
- #held() {
66393
- return this.#driver !== void 0 && Date.now() - this.#lastInput < IDLE_MS;
66394
- }
66395
66617
  #send(ws, message) {
66396
66618
  if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(message));
66397
66619
  }
66398
- #broadcast(message) {
66399
- for (const ws of this.#sockets.keys()) this.#send(ws, message);
66620
+ /** The sockets shown a page. */
66621
+ #shownTo(page) {
66622
+ return [...this.#viewers].filter(([, viewer]) => viewer.shown === page).map(([ws]) => ws);
66400
66623
  }
66401
- #announceDriver() {
66402
- for (const ws of this.#sockets.keys())
66403
- this.#send(ws, {
66404
- type: "driver",
66405
- you: ws === this.#driver && this.#held(),
66406
- held: this.#held(),
66407
- agentWaiting: this.#agentWaiting > 0
66408
- });
66624
+ #newest() {
66625
+ return this.#context?.pages().at(-1);
66409
66626
  }
66410
66627
  async #begin() {
66411
- this.#viewer ??= (async () => {
66412
- const viewer = await playwright().chromium.connect(await this.#endpoint);
66413
- const context = viewer.contexts()[0] ?? await viewer.newContext();
66628
+ this.#client ??= (async () => {
66629
+ const ended = this.#ended;
66630
+ const client = await playwright().chromium.connect(await this.#endpoint);
66631
+ const context = client.contexts()[0] ?? await client.newContext();
66632
+ if (ended !== this.#ended) return client;
66414
66633
  this.#context = context;
66415
66634
  for (const page of context.pages()) this.#track(page);
66416
66635
  context.on("page", (page) => {
66417
66636
  this.#track(page);
66418
- void this.#show(page);
66637
+ for (const [ws, viewer] of this.#viewers)
66638
+ if (!viewer.chose || !viewer.shown) void this.#show(ws, page);
66639
+ void this.#tabs();
66419
66640
  });
66420
- await this.#show(context.pages().at(-1) ?? await context.newPage());
66421
- return viewer;
66641
+ if (context.pages().length === 0) await context.newPage();
66642
+ if (ended === this.#ended)
66643
+ this.#titles = setInterval(() => void this.#checkTabs(), TITLE_CHECK_MS);
66644
+ return client;
66422
66645
  })();
66423
- await this.#viewer;
66646
+ await this.#client;
66424
66647
  }
66648
+ /** Everyone has left. All of it is forgotten at once, so a socket that opens next starts anew. */
66425
66649
  async #end() {
66426
- const viewer = this.#viewer;
66427
- this.#viewer = void 0;
66428
- await this.#stopCast?.();
66429
- this.#stopCast = void 0;
66430
- this.#shown = void 0;
66650
+ const client = this.#client;
66651
+ const casts = [...this.#casts.values()];
66652
+ this.#ended++;
66653
+ this.#client = void 0;
66654
+ this.#context = void 0;
66655
+ clearInterval(this.#titles);
66656
+ this.#titles = void 0;
66657
+ this.#lastTabs = "";
66658
+ this.#casts.clear();
66659
+ this.#stopping.clear();
66431
66660
  this.#ids.clear();
66432
- await (await viewer?.catch(() => void 0))?.close().catch(() => {
66661
+ this.#dialogs.clear();
66662
+ this.#choosers.clear();
66663
+ await Promise.all(casts.map((cast) => cast.stop.then((stop) => stop?.()).catch(() => {
66664
+ })));
66665
+ await (await client?.catch(() => void 0))?.close().catch(() => {
66433
66666
  });
66434
66667
  }
66435
66668
  #left(ws) {
66436
- this.#sockets.delete(ws);
66437
- if (this.#driver === ws) {
66438
- this.#driver = void 0;
66439
- this.#announceDriver();
66440
- }
66441
- if (this.#sockets.size === 0) void this.#end();
66442
- }
66443
- async #greet(ws) {
66444
- await this.#tabs(ws);
66445
- if (this.#size.width) this.#send(ws, { type: "viewport", ...this.#size });
66446
- if (this.#dialog)
66447
- this.#send(ws, {
66448
- type: "dialog",
66449
- open: true,
66450
- kind: this.#dialog.type(),
66451
- message: this.#dialog.message()
66452
- });
66453
- if (this.#chooser)
66454
- this.#send(ws, { type: "file-chooser", open: true, multiple: this.#chooser.isMultiple() });
66455
- this.#announceDriver();
66669
+ const shown = this.#viewers.get(ws)?.shown;
66670
+ this.#viewers.delete(ws);
66671
+ if (this.#viewers.size === 0) return void this.#end();
66672
+ if (shown) void this.#uncast(shown);
66456
66673
  }
66457
66674
  #track(page) {
66458
66675
  if (this.#ids.has(page)) return;
66459
66676
  this.#ids.set(page, this.#nextId++);
66460
66677
  page.on("dialog", (dialog) => {
66461
- this.#dialog = dialog;
66462
- this.#broadcast({
66463
- type: "dialog",
66464
- open: true,
66465
- kind: dialog.type(),
66466
- message: dialog.message(),
66467
- ...dialog.defaultValue() ? { defaultValue: dialog.defaultValue() } : {}
66468
- });
66678
+ this.#dialogs.set(page, dialog);
66679
+ for (const ws of this.#shownTo(page)) this.#tellDialog(ws, page);
66680
+ });
66681
+ page.on("dialogclosed", () => {
66682
+ this.#dialogs.delete(page);
66683
+ for (const ws of this.#shownTo(page)) this.#tellDialog(ws, page);
66469
66684
  });
66470
66685
  page.on("filechooser", (chooser) => {
66471
- this.#chooser = chooser;
66472
- this.#broadcast({ type: "file-chooser", open: true, multiple: chooser.isMultiple() });
66686
+ this.#choosers.set(page, chooser);
66687
+ for (const ws of this.#shownTo(page)) this.#tellChooser(ws, page);
66473
66688
  });
66474
66689
  page.on("framenavigated", (frame) => {
66475
66690
  if (frame === page.mainFrame()) void this.#tabs();
66476
66691
  });
66477
66692
  page.on("close", () => {
66478
66693
  this.#ids.delete(page);
66479
- if (this.#shown === page) void this.#show(this.#context?.pages().at(-1));
66480
- else void this.#tabs();
66694
+ this.#dialogs.delete(page);
66695
+ this.#choosers.delete(page);
66696
+ this.#casts.delete(page);
66697
+ this.#stopping.delete(page);
66698
+ for (const ws of this.#shownTo(page)) void this.#show(ws, this.#newest());
66699
+ void this.#tabs();
66481
66700
  });
66482
66701
  }
66483
- /** Which page the frames are of: the newest by default, or the one the person picked. */
66484
- async #show(page) {
66485
- if (page === this.#shown) return;
66486
- await this.#stopCast?.();
66487
- this.#stopCast = void 0;
66488
- this.#shown = page;
66489
- this.#choice = void 0;
66702
+ #tellDialog(ws, page) {
66703
+ const dialog = this.#dialogs.get(page);
66704
+ this.#send(
66705
+ ws,
66706
+ dialog ? {
66707
+ type: "dialog",
66708
+ open: true,
66709
+ kind: dialog.type(),
66710
+ message: dialog.message(),
66711
+ ...dialog.defaultValue() ? { defaultValue: dialog.defaultValue() } : {}
66712
+ } : { type: "dialog", open: false }
66713
+ );
66714
+ }
66715
+ #tellChooser(ws, page) {
66716
+ const chooser = this.#choosers.get(page);
66717
+ this.#send(
66718
+ ws,
66719
+ chooser ? { type: "file-chooser", open: true, multiple: chooser.isMultiple() } : { type: "file-chooser", open: false }
66720
+ );
66721
+ }
66722
+ /** The cast of a page, started if no socket was shown it yet. */
66723
+ #cast(page) {
66724
+ const running = this.#casts.get(page);
66725
+ if (running) return running;
66726
+ const onFrame = (frame) => {
66727
+ const watching = this.#shownTo(page);
66728
+ if (frame.width !== cast.size.width || frame.height !== cast.size.height) {
66729
+ cast.size = { width: frame.width, height: frame.height };
66730
+ for (const ws of watching) this.#send(ws, { type: "viewport", ...cast.size });
66731
+ }
66732
+ cast.last = frame.data;
66733
+ for (const ws of watching)
66734
+ if (ws.readyState === ws.OPEN && ws.bufferedAmount < MAX_BUFFERED) ws.send(frame.data);
66735
+ };
66736
+ const cast = {
66737
+ size: { width: 0, height: 0 },
66738
+ // A page has one screencast at a time, so this one starts once the last has stopped.
66739
+ stop: (this.#stopping.get(page) ?? Promise.resolve()).then(() => startScreencast(page, onFrame)).catch(() => void 0)
66740
+ };
66741
+ this.#casts.set(page, cast);
66742
+ return cast;
66743
+ }
66744
+ /** Stop casting a page no socket is shown any more. */
66745
+ async #uncast(page) {
66746
+ const cast = this.#casts.get(page);
66747
+ if (!cast || this.#shownTo(page).length > 0) return;
66748
+ this.#casts.delete(page);
66749
+ const stopped = cast.stop.then((stop) => stop?.()).catch(() => {
66750
+ });
66751
+ this.#stopping.set(page, stopped);
66752
+ await stopped;
66753
+ if (this.#stopping.get(page) === stopped) this.#stopping.delete(page);
66754
+ }
66755
+ /** Change the page a socket is shown, and tell it what that page is waiting on. */
66756
+ async #show(ws, page) {
66757
+ const viewer = this.#viewers.get(ws);
66758
+ if (!viewer || page && viewer.shown === page) return;
66759
+ const before = viewer.shown;
66760
+ viewer.shown = page;
66761
+ viewer.choice = void 0;
66762
+ viewer.swallowUp = false;
66763
+ if (before) await this.#uncast(before);
66764
+ if (this.#viewers.get(ws) !== viewer || viewer.shown !== page) return;
66490
66765
  if (page) {
66491
- this.#stopCast = await startScreencast(page, (frame) => {
66492
- if (frame.width !== this.#size.width || frame.height !== this.#size.height) {
66493
- this.#size = { width: frame.width, height: frame.height };
66494
- this.#broadcast({ type: "viewport", ...this.#size });
66495
- }
66496
- for (const ws of this.#sockets.keys())
66497
- if (ws.readyState === ws.OPEN && ws.bufferedAmount < MAX_BUFFERED) ws.send(frame.data);
66498
- }).catch(() => void 0);
66766
+ const cast = this.#cast(page);
66767
+ if (cast.size.width) this.#send(ws, { type: "viewport", ...cast.size });
66768
+ if (cast.last && ws.readyState === ws.OPEN) ws.send(cast.last);
66769
+ this.#tellDialog(ws, page);
66770
+ this.#tellChooser(ws, page);
66499
66771
  }
66500
- await this.#tabs();
66772
+ await this.#tabs(ws);
66501
66773
  }
66502
- async #tabs(only) {
66503
- const tabs = await Promise.all(
66774
+ async #tabList() {
66775
+ return Promise.all(
66504
66776
  [...this.#ids].map(async ([page, id]) => ({
66505
66777
  id,
66506
66778
  url: page.url(),
66507
66779
  title: await within(page.title(), STEP_MS).catch(() => void 0) ?? ""
66508
66780
  }))
66509
66781
  );
66510
- const message = {
66511
- type: "tabs",
66512
- tabs,
66513
- shown: (this.#shown && this.#ids.get(this.#shown)) ?? null
66514
- };
66515
- if (only) this.#send(only, message);
66516
- else this.#broadcast(message);
66782
+ }
66783
+ /** The tabs, to one socket or to all, each told which one it is shown. */
66784
+ async #tabs(only, listed) {
66785
+ const tabs = listed ?? await this.#tabList();
66786
+ if (!only) this.#lastTabs = JSON.stringify(tabs);
66787
+ for (const [ws, viewer] of this.#viewers)
66788
+ if (!only || ws === only)
66789
+ this.#send(ws, {
66790
+ type: "tabs",
66791
+ tabs,
66792
+ shown: (viewer.shown && this.#ids.get(viewer.shown)) ?? null
66793
+ });
66794
+ }
66795
+ /** Send the tabs again if anything about them changed, such as a title. */
66796
+ async #checkTabs() {
66797
+ if (this.#checking) return;
66798
+ this.#checking = true;
66799
+ try {
66800
+ const tabs = await this.#tabList();
66801
+ if (JSON.stringify(tabs) !== this.#lastTabs) await this.#tabs(void 0, tabs);
66802
+ } finally {
66803
+ this.#checking = false;
66804
+ }
66517
66805
  }
66518
66806
  /** Each step in order, none waiting on another for long: a dialog blocks the page's input. */
66519
- #step(work) {
66520
- this.#steps = this.#steps.then(() => within(work(), STEP_MS)).catch(() => {
66807
+ #step(viewer, work) {
66808
+ viewer.steps = viewer.steps.then(() => within(work(), STEP_MS)).catch(() => {
66521
66809
  });
66522
66810
  }
66523
- #input(ws, message) {
66524
- if (message.type === "release") {
66525
- if (this.#driver === ws) this.#driver = void 0;
66526
- return this.#announceDriver();
66527
- }
66528
- const changed = this.#driver !== ws || !this.#held();
66529
- this.#driver = ws;
66530
- this.#lastInput = Date.now();
66531
- if (changed) this.#announceDriver();
66532
- const page = this.#shown;
66811
+ #input(ws, viewer, message) {
66812
+ const page = viewer.shown;
66533
66813
  switch (message.type) {
66534
66814
  case "mouse":
66535
66815
  if (!page) return;
66536
- return this.#step(() => this.#mouse(page, message));
66816
+ return this.#step(viewer, () => this.#mouse(ws, viewer, page, message));
66537
66817
  case "key":
66538
66818
  if (!page) return;
66539
66819
  return this.#step(
66820
+ viewer,
66540
66821
  () => message.action === "down" ? page.keyboard.down(message.key) : page.keyboard.up(message.key)
66541
66822
  );
66542
66823
  case "text":
66543
66824
  if (!page) return;
66544
- return this.#step(() => page.keyboard.insertText(message.text));
66825
+ return this.#step(viewer, () => page.keyboard.insertText(message.text));
66545
66826
  case "dialog": {
66546
- const dialog = this.#dialog;
66547
- this.#dialog = void 0;
66548
- this.#broadcast({ type: "dialog", open: false });
66827
+ const dialog = page && this.#dialogs.get(page);
66549
66828
  if (!dialog) return;
66550
66829
  return this.#step(
66830
+ viewer,
66551
66831
  () => message.accept ? dialog.accept(message.text ?? void 0) : dialog.dismiss()
66552
66832
  );
66553
66833
  }
66554
66834
  case "files": {
66555
- const chooser = this.#chooser;
66556
- this.#chooser = void 0;
66557
- this.#broadcast({ type: "file-chooser", open: false });
66558
- if (!chooser) return;
66559
- this.#answeredChooser = true;
66835
+ const chooser = page && this.#choosers.get(page);
66836
+ if (!page || !chooser) return;
66837
+ this.#choosers.delete(page);
66838
+ for (const socket of this.#shownTo(page)) this.#tellChooser(socket, page);
66839
+ for (const session of this.#sessions) this.#owed.add(session);
66560
66840
  return this.#step(
66841
+ viewer,
66561
66842
  () => chooser.setFiles(
66562
66843
  message.files.map((file2) => ({
66563
66844
  name: file2.name,
@@ -66568,16 +66849,16 @@ var BrowserLive = class {
66568
66849
  );
66569
66850
  }
66570
66851
  case "choice": {
66571
- const choice = this.#choice;
66572
- this.#choice = void 0;
66852
+ const choice = viewer.choice;
66853
+ viewer.choice = void 0;
66573
66854
  if (!choice) return;
66574
- return this.#step(() => choice.selectOption(message.values));
66855
+ return this.#step(viewer, () => choice.selectOption(message.values));
66575
66856
  }
66576
66857
  case "tab":
66577
- return this.#step(() => this.#tab(message));
66858
+ return this.#step(viewer, () => this.#tab(ws, viewer, message));
66578
66859
  case "navigate":
66579
66860
  if (!page) return;
66580
- return this.#step(async () => {
66861
+ return this.#step(viewer, async () => {
66581
66862
  if (message.to === "back") await page.goBack();
66582
66863
  else if (message.to === "forward") await page.goForward();
66583
66864
  else if (message.to === "reload") await page.reload();
@@ -66585,15 +66866,15 @@ var BrowserLive = class {
66585
66866
  });
66586
66867
  }
66587
66868
  }
66588
- async #mouse(page, message) {
66869
+ async #mouse(ws, viewer, page, message) {
66589
66870
  const { x, y } = message;
66590
66871
  if (message.action === "move") return page.mouse.move(x, y);
66591
66872
  if (message.action === "wheel")
66592
66873
  return page.mouse.wheel(message.deltaX ?? 0, message.deltaY ?? 0);
66593
66874
  const button = message.button ?? "left";
66594
66875
  if (message.action === "up") {
66595
- if (this.#swallowUp) {
66596
- this.#swallowUp = false;
66876
+ if (viewer.swallowUp) {
66877
+ viewer.swallowUp = false;
66597
66878
  return;
66598
66879
  }
66599
66880
  return page.mouse.up({ button });
@@ -66619,20 +66900,29 @@ var BrowserLive = class {
66619
66900
  ([px, py]) => document.elementFromPoint(px, py),
66620
66901
  [x, y]
66621
66902
  );
66622
- this.#choice = handle.asElement() ?? void 0;
66623
- this.#swallowUp = true;
66624
- return this.#broadcast({ type: "choice", ...choice });
66903
+ viewer.choice = handle.asElement() ?? void 0;
66904
+ viewer.swallowUp = true;
66905
+ return this.#send(ws, { type: "choice", ...choice });
66625
66906
  }
66626
66907
  }
66627
66908
  await page.mouse.move(x, y);
66628
66909
  return page.mouse.down({ button });
66629
66910
  }
66630
- async #tab(message) {
66911
+ /** Showing or opening a tab makes the socket's page its own from then on. */
66912
+ async #tab(ws, viewer, message) {
66631
66913
  const byId = (id) => [...this.#ids].find(([, known]) => known === id)?.[0];
66632
- if (message.action === "show") return this.#show(byId(message.id));
66633
66914
  if (message.action === "close") return byId(message.id)?.close();
66915
+ if (message.action === "show") {
66916
+ const page2 = byId(message.id);
66917
+ if (!page2) return;
66918
+ viewer.chose = true;
66919
+ return this.#show(ws, page2);
66920
+ }
66634
66921
  const page = await this.#context?.newPage();
66635
- if (page && message.url) await page.goto(message.url).catch(() => {
66922
+ if (!page) return;
66923
+ viewer.chose = true;
66924
+ await this.#show(ws, page);
66925
+ if (message.url) await page.goto(message.url).catch(() => {
66636
66926
  });
66637
66927
  }
66638
66928
  };
@@ -66640,7 +66930,7 @@ var BrowserLive = class {
66640
66930
  // src/live.ts
66641
66931
  import { chmodSync, existsSync, statSync as statSync2 } from "node:fs";
66642
66932
  import { createRequire as createRequire3 } from "node:module";
66643
- import { dirname as dirname3, join as join4 } from "node:path";
66933
+ import { dirname as dirname3, join as join5 } from "node:path";
66644
66934
 
66645
66935
  // node_modules/ws/wrapper.mjs
66646
66936
  var import_stream2 = __toESM(require_stream(), 1);
@@ -66652,10 +66942,178 @@ var import_subprotocol = __toESM(require_subprotocol(), 1);
66652
66942
  var import_websocket = __toESM(require_websocket(), 1);
66653
66943
  var import_websocket_server = __toESM(require_websocket_server(), 1);
66654
66944
 
66945
+ // src/files-live.ts
66946
+ var MAX_PART = 1024 * 1024;
66947
+ var MAX_BUFFERED2 = 4 * 1024 * 1024;
66948
+ var MAX_UPLOADS = 64;
66949
+ var reason2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
66950
+ var text2 = (value) => typeof value === "string";
66951
+ var whole = (value) => Number.isSafeInteger(value);
66952
+ var sleep4 = (ms) => new Promise((done) => setTimeout(done, ms));
66953
+ var FilesLive = class {
66954
+ #files;
66955
+ #sockets = /* @__PURE__ */ new Set();
66956
+ /** One walk of the workspace at a time, however many ask. */
66957
+ #walking = Promise.resolve();
66958
+ /** How many files are part written, over every socket. */
66959
+ #uploads = 0;
66960
+ constructor(files) {
66961
+ this.#files = files;
66962
+ }
66963
+ close() {
66964
+ for (const ws of this.#sockets) ws.close(1001, "It stopped.");
66965
+ }
66966
+ open(ws, mode) {
66967
+ this.#sockets.add(ws);
66968
+ const uploads = /* @__PURE__ */ new Map();
66969
+ let awaited;
66970
+ let writing = Promise.resolve();
66971
+ let waiting = 0;
66972
+ let reading = Promise.resolve();
66973
+ let closed = false;
66974
+ const send2 = (answer) => {
66975
+ if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(answer));
66976
+ };
66977
+ const refuse = (id, message) => send2({ type: "refused", id, message });
66978
+ const giveUp = async (path) => {
66979
+ const had = uploads.get(path);
66980
+ if (!had) return;
66981
+ uploads.delete(path);
66982
+ this.#uploads--;
66983
+ await had.upload.abort().catch(() => {
66984
+ });
66985
+ };
66986
+ const drained = async () => {
66987
+ while (!closed && ws.bufferedAmount > MAX_BUFFERED2) await sleep4(50);
66988
+ };
66989
+ const writePart = async (write, bytes) => {
66990
+ const { id, path, offset, last } = write;
66991
+ if (bytes.length !== write.length)
66992
+ throw new Error(`It said ${write.length} bytes, and ${bytes.length} came.`);
66993
+ if (offset === 0) {
66994
+ await giveUp(path);
66995
+ if (this.#uploads >= MAX_UPLOADS)
66996
+ throw new Error(`${MAX_UPLOADS} files are part written here. Finish one first.`);
66997
+ this.#uploads++;
66998
+ try {
66999
+ uploads.set(path, { upload: await this.#files.upload(path), size: 0 });
67000
+ } catch (error62) {
67001
+ this.#uploads--;
67002
+ throw error62;
67003
+ }
67004
+ }
67005
+ const file2 = uploads.get(path);
67006
+ if (!file2 || file2.size !== offset)
67007
+ throw new Error(`The next part of ${path} starts at ${file2?.size ?? 0}.`);
67008
+ await file2.upload.write(bytes);
67009
+ file2.size += bytes.length;
67010
+ if (!last) return void 0;
67011
+ uploads.delete(path);
67012
+ this.#uploads--;
67013
+ return { type: "written", id, path, size: (await file2.upload.finish()).size };
67014
+ };
67015
+ const inTurn = (work) => {
67016
+ const done = reading.then(async () => {
67017
+ await drained();
67018
+ if (!closed) await work();
67019
+ });
67020
+ reading = done.catch(() => {
67021
+ });
67022
+ return done;
67023
+ };
67024
+ const handle = async (request) => {
67025
+ const { id } = request;
67026
+ switch (request.type) {
67027
+ case "list": {
67028
+ const path = request.path ?? ".";
67029
+ if (!text2(path)) throw new Error("A path is text.");
67030
+ return inTurn(
67031
+ async () => send2({ type: "entries", id, path, ...await this.#files.list(path) })
67032
+ );
67033
+ }
67034
+ case "read": {
67035
+ const { path, offset, length } = request;
67036
+ if (!text2(path)) throw new Error("A path is text.");
67037
+ return inTurn(async () => {
67038
+ const { bytes, size, modifiedAt } = await this.#files.read(path, offset, length);
67039
+ if (ws.readyState !== ws.OPEN) return;
67040
+ send2({ type: "data", id, path, offset, length: bytes.length, size, modifiedAt });
67041
+ ws.send(bytes);
67042
+ });
67043
+ }
67044
+ case "changed": {
67045
+ const { since } = request;
67046
+ if (!text2(since)) throw new Error("That isn't a time.");
67047
+ const walked = this.#walking.then(
67048
+ () => ws.readyState === ws.OPEN ? this.#files.changed(new Date(since)) : void 0
67049
+ );
67050
+ this.#walking = walked.catch(() => {
67051
+ });
67052
+ const found = await walked;
67053
+ await drained();
67054
+ if (found) send2({ type: "changes", id, since, ...found });
67055
+ return;
67056
+ }
67057
+ case "write": {
67058
+ awaited = "refused";
67059
+ if (mode !== "operate") throw new Error("This socket watches: it can't write.");
67060
+ const { path, offset, length, last } = request;
67061
+ if (!text2(path) || !whole(offset) || offset < 0 || !whole(length) || length < 0 || length > MAX_PART || typeof last !== "boolean")
67062
+ throw new Error(
67063
+ "A write names a path, where it starts, 0 to 1,048,576 bytes, and last."
67064
+ );
67065
+ awaited = request;
67066
+ return;
67067
+ }
67068
+ default:
67069
+ throw new Error("That isn't something files@1 does.");
67070
+ }
67071
+ };
67072
+ ws.on("close", () => {
67073
+ closed = true;
67074
+ this.#sockets.delete(ws);
67075
+ void writing.finally(() => Promise.all([...uploads.keys()].map(giveUp)));
67076
+ });
67077
+ ws.on("message", (data, binary) => {
67078
+ const write = awaited;
67079
+ awaited = void 0;
67080
+ if (binary) {
67081
+ if (write === "refused") return;
67082
+ if (!write) return ws.close(1008, "Those bytes belong to no write.");
67083
+ waiting++;
67084
+ ws.pause();
67085
+ writing = writing.then(() => closed ? void 0 : writePart(write, data)).then(
67086
+ (done) => done && send2(done),
67087
+ async (error62) => {
67088
+ await giveUp(write.path);
67089
+ refuse(write.id, reason2(error62));
67090
+ }
67091
+ ).finally(() => {
67092
+ if (--waiting === 0) ws.resume();
67093
+ });
67094
+ return;
67095
+ }
67096
+ if (write && write !== "refused") refuse(write.id, "Its bytes didn't follow it.");
67097
+ let request;
67098
+ try {
67099
+ request = JSON.parse(data.toString());
67100
+ } catch {
67101
+ return refuse("", "That isn't JSON.");
67102
+ }
67103
+ const { id, type } = request ?? {};
67104
+ if (type === "write") awaited = "refused";
67105
+ if (!text2(id) || id.length < 1 || id.length > 64)
67106
+ return refuse("", "Every request has an id of 1 to 64 characters.");
67107
+ handle(request).catch((error62) => refuse(id, reason2(error62)));
67108
+ });
67109
+ }
67110
+ };
67111
+
66655
67112
  // src/live.ts
66656
67113
  var PROTOCOLS = {
66657
67114
  commands: "commands@1",
66658
67115
  terminal: "pty@1",
67116
+ files: "files@1",
66659
67117
  browser: "screencast@1"
66660
67118
  };
66661
67119
  var MAX_EVENTS = 2e3;
@@ -66664,20 +67122,21 @@ var OUTPUT = 48;
66664
67122
  var INPUT = 48;
66665
67123
  var RESIZE = 49;
66666
67124
  var sockets = new import_websocket_server.default({ noServer: true });
67125
+ var fileSockets = new import_websocket_server.default({ noServer: true, maxPayload: MAX_PART });
66667
67126
  var pty;
66668
67127
  var loadPty = () => {
66669
67128
  pty ??= (async () => {
66670
- const require2 = createRequire3(import.meta.url);
67129
+ const require3 = createRequire3(import.meta.url);
66671
67130
  let loaded2;
66672
67131
  try {
66673
- loaded2 = require2("node-pty");
67132
+ loaded2 = require3("node-pty");
66674
67133
  } catch {
66675
67134
  return "The terminal needs node-pty, which isn't installed with this service.";
66676
67135
  }
66677
- const root = dirname3(require2.resolve("node-pty/package.json"));
67136
+ const root = dirname3(require3.resolve("node-pty/package.json"));
66678
67137
  for (const helper of [
66679
- join4(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
66680
- join4(root, "build", "Release", "spawn-helper")
67138
+ join5(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
67139
+ join5(root, "build", "Release", "spawn-helper")
66681
67140
  ])
66682
67141
  try {
66683
67142
  if (existsSync(helper) && (statSync2(helper).mode & 73) === 0) chmodSync(helper, 493);
@@ -66697,9 +67156,14 @@ var Live = class {
66697
67156
  #shell;
66698
67157
  #scrollback = "";
66699
67158
  #browser;
66700
- constructor(exec, workspace, terminal, browser) {
67159
+ #files;
67160
+ /** Whether a person may write files here: not where the agent can only read them. */
67161
+ #writes;
67162
+ constructor(exec, workspace, access, browser) {
66701
67163
  this.#workspace = workspace;
66702
- this.#terminal = terminal && exec !== void 0;
67164
+ this.#terminal = access === "terminal" && exec !== void 0;
67165
+ this.#files = exec && new FilesLive(exec.files);
67166
+ this.#writes = access !== "read";
66703
67167
  this.#browser = browser;
66704
67168
  exec?.onCommand((event) => this.#heard(event));
66705
67169
  }
@@ -66717,6 +67181,8 @@ var Live = class {
66717
67181
  }
66718
67182
  );
66719
67183
  }
67184
+ if (this.#files)
67185
+ surfaces.push({ surface: "files", protocol: PROTOCOLS.files, operate: this.#writes });
66720
67186
  if (this.#browser)
66721
67187
  surfaces.push({ surface: "browser", protocol: PROTOCOLS.browser, operate: true });
66722
67188
  return surfaces;
@@ -66724,9 +67190,11 @@ var Live = class {
66724
67190
  /** Take an upgraded request as a socket on one surface. */
66725
67191
  open(surface, mode, request, socket, head) {
66726
67192
  socket.on("error", () => socket.destroy());
66727
- sockets.handleUpgrade(request, socket, head, (ws) => {
67193
+ const server = surface === "files" ? fileSockets : sockets;
67194
+ server.handleUpgrade(request, socket, head, (ws) => {
66728
67195
  ws.on("error", () => ws.terminate());
66729
67196
  if (surface === "commands") this.#watch(ws);
67197
+ else if (surface === "files" && this.#files) this.#files.open(ws, mode);
66730
67198
  else if (surface === "browser" && this.#browser) this.#browser.open(ws, mode);
66731
67199
  else if (surface === "terminal") this.#attach(ws, mode).catch(() => ws.terminate());
66732
67200
  else ws.close(1011, `There's no ${surface} here.`);
@@ -66734,6 +67202,7 @@ var Live = class {
66734
67202
  }
66735
67203
  close() {
66736
67204
  void this.#browser?.close();
67205
+ this.#files?.close();
66737
67206
  for (const ws of [...this.#watchers, ...this.#shells.keys()]) ws.close(1001, "It stopped.");
66738
67207
  this.#shell?.kill();
66739
67208
  this.#shell = void 0;
@@ -66799,14 +67268,14 @@ var Live = class {
66799
67268
  };
66800
67269
 
66801
67270
  // src/store.ts
66802
- import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
67271
+ import { createHash, randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
66803
67272
  import { chmodSync as chmodSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
66804
- import { join as join5 } from "node:path";
66805
- var newToken = () => randomBytes3(32).toString("base64url");
67273
+ import { join as join6 } from "node:path";
67274
+ var newToken = () => randomBytes4(32).toString("base64url");
66806
67275
  var digestOf = (token) => createHash("sha256").update(token).digest("hex");
66807
67276
  var sameToken = (token, digest) => timingSafeEqual(Buffer.from(digestOf(token), "hex"), Buffer.from(digest, "hex"));
66808
- function writePrivate(file2, text2) {
66809
- writeFileSync(`${file2}.tmp`, text2, { mode: 384, flush: true });
67277
+ function writePrivate(file2, text3) {
67278
+ writeFileSync(`${file2}.tmp`, text3, { mode: 384, flush: true });
66810
67279
  renameSync(`${file2}.tmp`, file2);
66811
67280
  chmodSync2(file2, 384);
66812
67281
  }
@@ -66816,7 +67285,7 @@ var privateFolder = (dir) => {
66816
67285
  return dir;
66817
67286
  };
66818
67287
  function controlToken(data) {
66819
- const file2 = join5(data, "control-token");
67288
+ const file2 = join6(data, "control-token");
66820
67289
  try {
66821
67290
  return readFileSync2(file2, "utf8").trim();
66822
67291
  } catch (error62) {
@@ -66855,7 +67324,7 @@ var ProcessRuntime = class {
66855
67324
  let browser;
66856
67325
  let browserDown = false;
66857
67326
  if (spec.browser) {
66858
- const own3 = privateFolder(join6(folder, "browser"));
67327
+ const own3 = privateFolder(join7(folder, "browser"));
66859
67328
  browser = new Browser(
66860
67329
  this.#config,
66861
67330
  own3,
@@ -66879,10 +67348,19 @@ var ProcessRuntime = class {
66879
67348
  browserLive = new BrowserLive(browser.endpoint);
66880
67349
  browser.gate = browserLive;
66881
67350
  }
66882
- const live = new Live(exec, spec.workspace, spec.access === "terminal", browserLive);
67351
+ const live = new Live(exec, spec.workspace, spec.access, browserLive);
67352
+ const surfaces = await live.surfaces();
66883
67353
  return {
66884
67354
  browserTools: browser?.tools ?? [],
66885
- surfaces: await live.surfaces(),
67355
+ /** As they are now: a browser that has stopped can't be opened, and says so. */
67356
+ get surfaces() {
67357
+ return surfaces.map(
67358
+ (one) => one.surface === "browser" && browserDown ? {
67359
+ ...one,
67360
+ unavailable: "Its browser has stopped. Stop the computer and start it again."
67361
+ } : one
67362
+ );
67363
+ },
66886
67364
  openLive: async (surface, mode, request, socket, head) => live.open(surface, mode, request, socket, head),
66887
67365
  handle: async (endpoint, request, response) => {
66888
67366
  if (endpoint === "computer") return exec.handle(request, response);
@@ -66912,13 +67390,13 @@ async function serveInside(options) {
66912
67390
  ...options,
66913
67391
  ...options.access === "none" ? {} : { workspace: "/workspace" }
66914
67392
  },
66915
- privateFolder(join7(homedir(), ".agentcomputer")),
67393
+ privateFolder(join8(homedir(), ".agentcomputer")),
66916
67394
  {
66917
67395
  log: (line) => process.stdout.write(`${line}
66918
67396
  `),
66919
67397
  // The service sees the container end, and shows the computer as failed.
66920
- failed: (reason2) => {
66921
- process.stderr.write(`${reason2}
67398
+ failed: (reason3) => {
67399
+ process.stderr.write(`${reason3}
66922
67400
  `);
66923
67401
  process.exit(1);
66924
67402
  }
@@ -66947,7 +67425,7 @@ async function serveInside(options) {
66947
67425
  socket.on("error", () => socket.destroy());
66948
67426
  const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
66949
67427
  const url3 = new URL(request.url ?? "/", "http://localhost");
66950
- const surface = /^\/live\/(commands|terminal|browser)$/.exec(url3.pathname)?.[1];
67428
+ const surface = /^\/live\/(commands|terminal|files|browser)$/.exec(url3.pathname)?.[1];
66951
67429
  const mode = url3.searchParams.get("mode");
66952
67430
  if (!given || !sameToken(given, digest) || !surface || mode !== "watch" && mode !== "operate")
66953
67431
  return void socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
@@ -66962,7 +67440,7 @@ async function serveInside(options) {
66962
67440
  }
66963
67441
 
66964
67442
  // src/container.ts
66965
- var run2 = promisify(execFile);
67443
+ var run = promisify(execFile);
66966
67444
  var START_MS2 = 6e4;
66967
67445
  var STOP_SECONDS = "5";
66968
67446
  var NOT_FORWARDED = /* @__PURE__ */ new Set([
@@ -66976,7 +67454,7 @@ var NOT_FORWARDED = /* @__PURE__ */ new Set([
66976
67454
  ]);
66977
67455
  async function docker(args, env = process.env) {
66978
67456
  try {
66979
- return (await run2("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
67457
+ return (await run("docker", args, { env, maxBuffer: 16 * 1024 * 1024 })).stdout.trim();
66980
67458
  } catch (error62) {
66981
67459
  const { stderr, message } = error62;
66982
67460
  throw new Error(stderr?.trim() || message);
@@ -66991,7 +67469,7 @@ var UPGRADE_HEADERS = [
66991
67469
  "sec-websocket-protocol"
66992
67470
  ];
66993
67471
  var nameOf = (id) => `agentcomputer-${id}`;
66994
- var sleep4 = (ms) => new Promise((done) => setTimeout(done, ms));
67472
+ var sleep5 = (ms) => new Promise((done) => setTimeout(done, ms));
66995
67473
  var ContainerRuntime = class {
66996
67474
  isolation = "container";
66997
67475
  #image;
@@ -67055,7 +67533,7 @@ var ContainerRuntime = class {
67055
67533
  }
67056
67534
  /** The token between the service and this container, which a restart has to find again. */
67057
67535
  #tokenIn(folder) {
67058
- const file2 = join8(folder, "inner-token");
67536
+ const file2 = join9(folder, "inner-token");
67059
67537
  try {
67060
67538
  return readFileSync3(file2, "utf8");
67061
67539
  } catch {
@@ -67103,7 +67581,16 @@ var ContainerRuntime = class {
67103
67581
  throw error62;
67104
67582
  }
67105
67583
  return {
67106
- ...ready,
67584
+ browserTools: ready.browserTools,
67585
+ /** As they are now: nothing of a container that has stopped can be opened. */
67586
+ get surfaces() {
67587
+ return ready.surfaces.map(
67588
+ (one) => ended ? {
67589
+ ...one,
67590
+ unavailable: "Its container has stopped. Stop the computer and start it again."
67591
+ } : one
67592
+ );
67593
+ },
67107
67594
  stop,
67108
67595
  openLive: async (surface, mode, request, socket, head) => pipeUpgrade(
67109
67596
  Number(port),
@@ -67128,7 +67615,7 @@ var ContainerRuntime = class {
67128
67615
  if (answer.ok) return await answer.json();
67129
67616
  } catch {
67130
67617
  }
67131
- await sleep4(100);
67618
+ await sleep5(100);
67132
67619
  }
67133
67620
  throw new Error("Its container didn't start. Its logs say why.");
67134
67621
  }
@@ -67185,7 +67672,7 @@ async function forward(url3, authorization, request, response) {
67185
67672
  }
67186
67673
 
67187
67674
  // src/handlers.ts
67188
- var VERSION2 = "0.0.6";
67675
+ var VERSION2 = "0.0.8";
67189
67676
  function createHandlers(computers) {
67190
67677
  return {
67191
67678
  getInfo: async () => ({ version: VERSION2, isolation: computers.isolation }),
@@ -67734,9 +68221,9 @@ var Endpoint = class {
67734
68221
  async #call(name, args) {
67735
68222
  const client = await this.#connected();
67736
68223
  const result = await client.callTool({ name, arguments: args });
67737
- const text2 = result.content.map((block) => block.text ?? "").join("");
67738
- if (result.isError) throw new ComputerError(400, text2);
67739
- return { text: text2, structured: result.structuredContent };
68224
+ const text3 = result.content.map((block) => block.text ?? "").join("");
68225
+ if (result.isError) throw new ComputerError(400, text3);
68226
+ return { text: text3, structured: result.structuredContent };
67740
68227
  }
67741
68228
  async text(name, args) {
67742
68229
  return (await this.#call(name, args)).text;
@@ -67771,8 +68258,8 @@ var INPUT2 = 48;
67771
68258
  var RESIZE2 = 49;
67772
68259
  var Pty = class {
67773
68260
  #open;
67774
- constructor(open3) {
67775
- this.#open = open3;
68261
+ constructor(open4) {
68262
+ this.#open = open4;
67776
68263
  }
67777
68264
  /** Attach to its shell, starting one if none is running. Every attachment shares it. */
67778
68265
  async connect(opts) {
@@ -67783,8 +68270,8 @@ var Pty = class {
67783
68270
  const frame = new Uint8Array(data);
67784
68271
  if (frame[0] === INPUT2) opts.onData(decoder.decode(frame.subarray(1), { stream: true }));
67785
68272
  });
67786
- const send2 = (type, text2) => {
67787
- const body = new TextEncoder().encode(text2);
68273
+ const send2 = (type, text3) => {
68274
+ const body = new TextEncoder().encode(text3);
67788
68275
  const frame = new Uint8Array(body.length + 1);
67789
68276
  frame[0] = type;
67790
68277
  frame.set(body, 1);
@@ -67801,9 +68288,9 @@ var Pty = class {
67801
68288
  var Commands = class {
67802
68289
  #endpoint;
67803
68290
  #open;
67804
- constructor(endpoint, open3) {
68291
+ constructor(endpoint, open4) {
67805
68292
  this.#endpoint = endpoint;
67806
- this.#open = open3;
68293
+ this.#open = open4;
67807
68294
  }
67808
68295
  /** Each of the agent's commands as it runs: the ones already heard first, then as they come. */
67809
68296
  async watch(onEvent) {
@@ -67870,16 +68357,16 @@ var Files = class {
67870
68357
  }
67871
68358
  /** A folder's entries, the first thousand. */
67872
68359
  async list(path = ".") {
67873
- const text2 = await this.#endpoint.text("list", { path });
67874
- return text2.split("\n").filter((line) => line.endsWith("/") || line.includes(" ")).map((line) => {
68360
+ const text3 = await this.#endpoint.text("list", { path });
68361
+ return text3.split("\n").filter((line) => line.endsWith("/") || line.includes(" ")).map((line) => {
67875
68362
  const [name = "", size] = line.split(" ");
67876
68363
  return name.endsWith("/") ? { name: name.slice(0, -1), type: "dir" } : { name, type: "file", size: Number(size) };
67877
68364
  });
67878
68365
  }
67879
68366
  /** Lines matching text under a folder, as `path:line:text`. */
67880
68367
  async search(pattern, opts = {}) {
67881
- const text2 = await this.#endpoint.text("search", { pattern, ...opts });
67882
- return text2 ? text2.split("\n") : [];
68368
+ const text3 = await this.#endpoint.text("search", { pattern, ...opts });
68369
+ return text3 ? text3.split("\n") : [];
67883
68370
  }
67884
68371
  };
67885
68372
  var pathOf = (computerID) => ({ params: { path: { computerID } } });
@@ -67898,9 +68385,9 @@ var Computer = class _Computer {
67898
68385
  this.#token = token;
67899
68386
  this.#urls = info.endpoints;
67900
68387
  this.#computer = new Endpoint(info.endpoints.computer, token);
67901
- const open3 = (surface, mode) => this.liveSession(surface, mode);
67902
- this.commands = new Commands(this.#computer, open3);
67903
- this.pty = new Pty(open3);
68388
+ const open4 = (surface, mode) => this.liveSession(surface, mode);
68389
+ this.commands = new Commands(this.#computer, open4);
68390
+ this.pty = new Pty(open4);
67904
68391
  this.files = new Files(this.#computer);
67905
68392
  }
67906
68393
  static async create(opts) {
@@ -68021,7 +68508,7 @@ import {
68021
68508
  STATUS_CODES as STATUS_CODES3
68022
68509
  } from "node:http";
68023
68510
  import { createServer as createTlsServer } from "node:https";
68024
- import { join as join10 } from "node:path";
68511
+ import { join as join11 } from "node:path";
68025
68512
 
68026
68513
  // node_modules/@modelcontextprotocol/server/dist/chunk-vYduTEsO.mjs
68027
68514
  var __create4 = Object.create;
@@ -71482,7 +71969,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS3 = 250;
71482
71969
  function inputRequiredRoundsExceededMessage3(method, maxRounds) {
71483
71970
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
71484
71971
  }
71485
- function sleep5(ms, signal) {
71972
+ function sleep6(ms, signal) {
71486
71973
  return new Promise((resolve6, reject) => {
71487
71974
  if (signal?.aborted) {
71488
71975
  reject(signal.reason instanceof SdkError3 ? signal.reason : new SdkError3(SdkErrorCode3.RequestTimeout, String(signal.reason)));
@@ -71506,7 +71993,7 @@ function linkedRoundAbort3(outer) {
71506
71993
  if (outer?.aborted) controller.abort(outer.reason);
71507
71994
  return {
71508
71995
  signal: controller.signal,
71509
- abort: (reason2) => controller.abort(reason2),
71996
+ abort: (reason3) => controller.abort(reason3),
71510
71997
  dispose: () => outer?.removeEventListener("abort", onOuterAbort)
71511
71998
  };
71512
71999
  }
@@ -72301,8 +72788,8 @@ var Protocol3 = class {
72301
72788
  return;
72302
72789
  }
72303
72790
  if (options?.signal?.aborted) {
72304
- const reason2 = options.signal.reason;
72305
- throw reason2 instanceof SdkError3 ? reason2 : new SdkError3(SdkErrorCode3.RequestTimeout, String(reason2));
72791
+ const reason3 = options.signal.reason;
72792
+ throw reason3 instanceof SdkError3 ? reason3 : new SdkError3(SdkErrorCode3.RequestTimeout, String(reason3));
72306
72793
  }
72307
72794
  const requestAbort = codec2.era === MODERN_WIRE_REVISION3 && this._transport.hasPerRequestStream === true ? new AbortController() : void 0;
72308
72795
  const messageId = this._requestMessageId++;
@@ -72324,7 +72811,7 @@ var Protocol3 = class {
72324
72811
  }
72325
72812
  const outbound = this._envelopeOutbound(jsonrpcRequest);
72326
72813
  let responseReceived = false;
72327
- const cancel = (reason2) => {
72814
+ const cancel = (reason3) => {
72328
72815
  if (responseReceived) return;
72329
72816
  this._progressHandlers.delete(messageId);
72330
72817
  if (requestAbort === void 0) {
@@ -72333,7 +72820,7 @@ var Protocol3 = class {
72333
72820
  method: "notifications/cancelled",
72334
72821
  params: {
72335
72822
  requestId: messageId,
72336
- reason: String(reason2)
72823
+ reason: String(reason3)
72337
72824
  }
72338
72825
  }), {
72339
72826
  relatedRequestId,
@@ -72341,7 +72828,7 @@ var Protocol3 = class {
72341
72828
  onresumptiontoken
72342
72829
  }).catch((error62) => this._onerror(/* @__PURE__ */ new Error(`Failed to send cancellation: ${error62}`)));
72343
72830
  } else requestAbort.abort();
72344
- reject(reason2 instanceof SdkError3 ? reason2 : new SdkError3(SdkErrorCode3.RequestTimeout, String(reason2)));
72831
+ reject(reason3 instanceof SdkError3 ? reason3 : new SdkError3(SdkErrorCode3.RequestTimeout, String(reason3)));
72345
72832
  };
72346
72833
  this._responseHandlers.set(messageId, (response) => {
72347
72834
  if (options?.signal?.aborted) return;
@@ -76679,7 +77166,7 @@ var require_core$33 = /* @__PURE__ */ __commonJSMin3(((exports) => {
76679
77166
  }
76680
77167
  errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
76681
77168
  if (!errors || errors.length === 0) return "No errors";
76682
- return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
77169
+ return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text3, msg) => text3 + separator + msg);
76683
77170
  }
76684
77171
  $dataMetaSchema(metaSchema, keywordsJsonPointers) {
76685
77172
  const rules = this.RULES.all;
@@ -80479,7 +80966,7 @@ var LegacyInputRequiredShim2 = class {
80479
80966
  } finally {
80480
80967
  roundAbort.dispose();
80481
80968
  }
80482
- } else await sleep5(REQUEST_STATE_ONLY_LEG_PACING_MS3, outerSignal);
80969
+ } else await sleep6(REQUEST_STATE_ONLY_LEG_PACING_MS3, outerSignal);
80483
80970
  let ctxNext = {
80484
80971
  ...ctx,
80485
80972
  mcpReq: {
@@ -81825,21 +82312,21 @@ async function readRequestBody2(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZ
81825
82312
  const reader = request.body.getReader();
81826
82313
  const decoder = new TextDecoder();
81827
82314
  let received = 0;
81828
- let text2 = "";
82315
+ let text3 = "";
81829
82316
  try {
81830
82317
  for (; ; ) {
81831
82318
  const { done, value } = await reader.read();
81832
82319
  if (done) break;
81833
82320
  received += value.byteLength;
81834
82321
  if (received > maxBytes) return { tooLarge: true };
81835
- text2 += decoder.decode(value, { stream: true });
82322
+ text3 += decoder.decode(value, { stream: true });
81836
82323
  }
81837
82324
  } finally {
81838
82325
  reader.releaseLock();
81839
82326
  }
81840
82327
  return {
81841
82328
  tooLarge: false,
81842
- text: text2 + decoder.decode()
82329
+ text: text3 + decoder.decode()
81843
82330
  };
81844
82331
  }
81845
82332
  var WebStandardStreamableHTTPServerTransport2 = class {
@@ -82923,8 +83410,8 @@ var readRawBodyIfAvailable2 = (request) => {
82923
83410
  };
82924
83411
  var normalizeAbortError2 = (request, incoming) => {
82925
83412
  if (incoming.errored) return incoming.errored;
82926
- const reason2 = request[abortReasonKey2];
82927
- if (reason2 !== void 0) return reason2 instanceof Error ? reason2 : new Error(String(reason2));
83413
+ const reason3 = request[abortReasonKey2];
83414
+ if (reason3 !== void 0) return reason3 instanceof Error ? reason3 : new Error(String(reason3));
82928
83415
  return /* @__PURE__ */ new Error("Client connection prematurely closed.");
82929
83416
  };
82930
83417
  var readBodyDirect2 = (request) => {
@@ -83018,10 +83505,10 @@ var requestPrototype2 = {
83018
83505
  get headers() {
83019
83506
  return this[headersKey2] ||= newHeadersFromIncoming2(this[incomingKey2]);
83020
83507
  },
83021
- [abortRequest2](reason2) {
83022
- if (this[abortReasonKey2] === void 0) this[abortReasonKey2] = reason2;
83508
+ [abortRequest2](reason3) {
83509
+ if (this[abortReasonKey2] === void 0) this[abortReasonKey2] = reason3;
83023
83510
  const abortController = this[abortControllerKey2];
83024
- if (abortController && !abortController.signal.aborted) abortController.abort(reason2);
83511
+ if (abortController && !abortController.signal.aborted) abortController.abort(reason3);
83025
83512
  },
83026
83513
  [getAbortController2]() {
83027
83514
  this[abortControllerKey2] ||= new AbortController();
@@ -83665,8 +84152,8 @@ var upgradeWebSocket2 = defineWebSocketHelper2(async (c, events, options) => {
83665
84152
  ws.on("message", bufferMessage);
83666
84153
  const ctx = {
83667
84154
  binaryType: "arraybuffer",
83668
- close(code, reason2) {
83669
- ws.close(code, reason2);
84155
+ close(code, reason3) {
84156
+ ws.close(code, reason3);
83670
84157
  },
83671
84158
  protocol: ws.protocol,
83672
84159
  raw: ws,
@@ -83696,11 +84183,11 @@ var upgradeWebSocket2 = defineWebSocketHelper2(async (c, events, options) => {
83696
84183
  ws.on("message", (data, isBinary) => {
83697
84184
  handleMessage(data, isBinary);
83698
84185
  });
83699
- ws.on("close", (code, reason2) => {
84186
+ ws.on("close", (code, reason3) => {
83700
84187
  try {
83701
84188
  events?.onClose?.(new CloseEvent2("close", {
83702
84189
  code,
83703
- reason: reason2.toString()
84190
+ reason: reason3.toString()
83704
84191
  }), ctx);
83705
84192
  } catch (e) {
83706
84193
  (options?.onError ?? console.error)(e);
@@ -83835,9 +84322,9 @@ var NodeStreamableHTTPServerTransport2 = class {
83835
84322
  };
83836
84323
 
83837
84324
  // src/computers.ts
83838
- import { randomBytes as randomBytes4 } from "node:crypto";
84325
+ import { randomBytes as randomBytes5 } from "node:crypto";
83839
84326
  import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
83840
- import { isAbsolute, join as join9, resolve as resolve4 } from "node:path";
84327
+ import { isAbsolute, join as join10, resolve as resolve4 } from "node:path";
83841
84328
  var TOOLS = {
83842
84329
  read: ["read", "list", "search"],
83843
84330
  files: ["read", "list", "search", "edit"],
@@ -83859,7 +84346,7 @@ var Computers = class {
83859
84346
  this.#data = data;
83860
84347
  this.#base = base;
83861
84348
  this.#runtime = runtime;
83862
- this.#file = join9(data, "computers.json");
84349
+ this.#file = join10(data, "computers.json");
83863
84350
  if (existsSync2(this.#file))
83864
84351
  for (const entry of JSON.parse(readFileSync4(this.#file, "utf8")))
83865
84352
  this.#stored.set(entry.id, entry);
@@ -83897,7 +84384,7 @@ var Computers = class {
83897
84384
  if (!folder) throw new ApiError(400, `There's no folder at ${input2.workspace}.`);
83898
84385
  workspace = resolve4(input2.workspace);
83899
84386
  }
83900
- const id = `c_${randomBytes4(9).toString("hex")}`;
84387
+ const id = `c_${randomBytes5(9).toString("hex")}`;
83901
84388
  const token = newToken();
83902
84389
  const now = (/* @__PURE__ */ new Date()).toISOString();
83903
84390
  const stored = {
@@ -83914,7 +84401,7 @@ var Computers = class {
83914
84401
  ...timeout === void 0 ? {} : { endAt: expiry(timeout) }
83915
84402
  };
83916
84403
  if (!workspace && access !== "none") {
83917
- stored.workspace = join9(this.#folder(id), "workspace");
84404
+ stored.workspace = join10(this.#folder(id), "workspace");
83918
84405
  mkdirSync2(stored.workspace, { recursive: true, mode: 448 });
83919
84406
  }
83920
84407
  this.#stored.set(id, stored);
@@ -83996,12 +84483,12 @@ var Computers = class {
83996
84483
  }
83997
84484
  logs(id) {
83998
84485
  this.#found(id);
83999
- let text2 = "";
84486
+ let text3 = "";
84000
84487
  try {
84001
- text2 = readFileSync4(this.#logFile(id), "utf8");
84488
+ text3 = readFileSync4(this.#logFile(id), "utf8");
84002
84489
  } catch {
84003
84490
  }
84004
- return text2.split("\n").filter(Boolean).map((entry) => ({
84491
+ return text3.split("\n").filter(Boolean).map((entry) => ({
84005
84492
  timestamp: entry.slice(0, entry.indexOf(" ")),
84006
84493
  line: entry.slice(entry.indexOf(" ") + 1)
84007
84494
  }));
@@ -84033,7 +84520,7 @@ var Computers = class {
84033
84520
  const now = Date.now();
84034
84521
  for (const [key, ticket2] of this.#tickets)
84035
84522
  if (ticket2.expiresAt <= now) this.#tickets.delete(key);
84036
- const ticket = `l_${randomBytes4(24).toString("base64url")}`;
84523
+ const ticket = `l_${randomBytes5(24).toString("base64url")}`;
84037
84524
  const expiresAt = now + TICKET_MS;
84038
84525
  this.#tickets.set(ticket, { computerID: id, surface, mode, expiresAt });
84039
84526
  return {
@@ -84065,10 +84552,10 @@ var Computers = class {
84065
84552
  return stored;
84066
84553
  }
84067
84554
  #folder(id) {
84068
- return privateFolder(join9(this.#data, "computers", id));
84555
+ return privateFolder(join10(this.#data, "computers", id));
84069
84556
  }
84070
84557
  #logFile(id) {
84071
- return join9(this.#folder(id), "log");
84558
+ return join10(this.#folder(id), "log");
84072
84559
  }
84073
84560
  #log(id, line) {
84074
84561
  if (!this.#stored.has(id)) return;
@@ -84117,9 +84604,9 @@ var Computers = class {
84117
84604
  this.#folder(stored.id),
84118
84605
  {
84119
84606
  log: (line) => this.#log(stored.id, line),
84120
- failed: (reason2) => {
84607
+ failed: (reason3) => {
84121
84608
  live.failed = true;
84122
- this.#log(stored.id, reason2);
84609
+ this.#log(stored.id, reason3);
84123
84610
  }
84124
84611
  }
84125
84612
  );
@@ -84160,7 +84647,7 @@ var OPENAPI = {
84160
84647
  "openapi": "3.1.0",
84161
84648
  "info": {
84162
84649
  "title": "AgentComputer",
84163
- "version": "0.0.6",
84650
+ "version": "0.0.8",
84164
84651
  "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.",
84165
84652
  "license": {
84166
84653
  "name": "Apache-2.0",
@@ -84481,7 +84968,7 @@ var OPENAPI = {
84481
84968
  "post": {
84482
84969
  "operationId": "createLiveSession",
84483
84970
  "x-mcp-tool": "computer_live",
84484
- "summary": "A one-time ticket to watch or operate one of its surfaces live: the agent's commands as they run, or a shell of your own. Open its url as a WebSocket within 60 seconds.",
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.",
84485
84972
  "requestBody": {
84486
84973
  "required": true,
84487
84974
  "content": {
@@ -84822,9 +85309,10 @@ var OPENAPI = {
84822
85309
  "enum": [
84823
85310
  "commands",
84824
85311
  "terminal",
85312
+ "files",
84825
85313
  "browser"
84826
85314
  ],
84827
- "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. 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. Operating it makes you the driver: the agent's browser calls wait up to 25 seconds while you are, then answer that a person has or had it. You stop being the driver when you send release, close the socket, or send nothing for 60 seconds."
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."
84828
85316
  },
84829
85317
  "LiveMode": {
84830
85318
  "type": "string",
@@ -84832,7 +85320,7 @@ var OPENAPI = {
84832
85320
  "watch",
84833
85321
  "operate"
84834
85322
  ],
84835
- "description": "In watch, what you send is dropped."
85323
+ "description": "In watch you see and don't act: what you send is dropped, except that a files socket may still ask to list, to read and for what changed."
84836
85324
  },
84837
85325
  "SurfaceInfo": {
84838
85326
  "type": "object",
@@ -84855,8 +85343,7 @@ var OPENAPI = {
84855
85343
  },
84856
85344
  "unavailable": {
84857
85345
  "type": "string",
84858
- "description": "Why it can't be opened here",
84859
- "if it can't.": null
85346
+ "description": "Why it can't be opened now, if it can't."
84860
85347
  }
84861
85348
  }
84862
85349
  },
@@ -84915,7 +85402,7 @@ var OPENAPI = {
84915
85402
  "oneOf": [
84916
85403
  {
84917
85404
  "type": "object",
84918
- "description": "The open tabs, and which one the frames are of.",
85405
+ "description": "The open tabs, and which one this socket's frames are of, sent again whenever a tab opens, closes, navigates or changes its title, or the socket is shown another.",
84919
85406
  "required": [
84920
85407
  "type",
84921
85408
  "tabs",
@@ -85063,30 +85550,6 @@ var OPENAPI = {
85063
85550
  }
85064
85551
  }
85065
85552
  }
85066
- },
85067
- {
85068
- "type": "object",
85069
- "description": "Who holds the browser, and whether the agent is waiting for it.",
85070
- "required": [
85071
- "type",
85072
- "you",
85073
- "held",
85074
- "agentWaiting"
85075
- ],
85076
- "properties": {
85077
- "type": {
85078
- "const": "driver"
85079
- },
85080
- "you": {
85081
- "type": "boolean"
85082
- },
85083
- "held": {
85084
- "type": "boolean"
85085
- },
85086
- "agentWaiting": {
85087
- "type": "boolean"
85088
- }
85089
- }
85090
85553
  }
85091
85554
  ]
85092
85555
  },
@@ -85249,6 +85712,7 @@ var OPENAPI = {
85249
85712
  },
85250
85713
  {
85251
85714
  "type": "object",
85715
+ "description": "Show this socket another tab, open one and show it, or close one. After show or open, the socket's page is its own and no longer follows new tabs.",
85252
85716
  "required": [
85253
85717
  "type",
85254
85718
  "action"
@@ -85285,20 +85749,321 @@ var OPENAPI = {
85285
85749
  },
85286
85750
  "to": {
85287
85751
  "type": "string",
85288
- "description": "A URL",
85289
- "or back": null,
85290
- "forward or reload.": null
85752
+ "description": "A URL, or back, forward or reload."
85753
+ }
85754
+ }
85755
+ }
85756
+ ]
85757
+ },
85758
+ "FilesRequest": {
85759
+ "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
+ "oneOf": [
85761
+ {
85762
+ "type": "object",
85763
+ "description": "A folder's entries, folders first, the first thousand. There is no asking for the rest.",
85764
+ "required": [
85765
+ "type",
85766
+ "id"
85767
+ ],
85768
+ "properties": {
85769
+ "type": {
85770
+ "const": "list"
85771
+ },
85772
+ "id": {
85773
+ "type": "string",
85774
+ "minLength": 1,
85775
+ "maxLength": 64
85776
+ },
85777
+ "path": {
85778
+ "type": "string",
85779
+ "description": "The workspace itself, if left out."
85780
+ }
85781
+ }
85782
+ },
85783
+ {
85784
+ "type": "object",
85785
+ "description": "Bytes of a file from an offset. The answer is a data message and then one binary message, the bytes: fewer than asked for at the file's end, and none past it.",
85786
+ "required": [
85787
+ "type",
85788
+ "id",
85789
+ "path",
85790
+ "offset",
85791
+ "length"
85792
+ ],
85793
+ "properties": {
85794
+ "type": {
85795
+ "const": "read"
85796
+ },
85797
+ "id": {
85798
+ "type": "string",
85799
+ "minLength": 1,
85800
+ "maxLength": 64
85801
+ },
85802
+ "path": {
85803
+ "type": "string"
85804
+ },
85805
+ "offset": {
85806
+ "type": "integer",
85807
+ "minimum": 0
85808
+ },
85809
+ "length": {
85810
+ "type": "integer",
85811
+ "minimum": 1,
85812
+ "maximum": 1048576
85813
+ }
85814
+ }
85815
+ },
85816
+ {
85817
+ "type": "object",
85818
+ "description": "The files modified after a time, in whole milliseconds: the newest thousand, the newest first. It skips what .gitignore files ignore, .git, node_modules and links, and a file that was removed isn't one of them. Each one asked walks the workspace once, and a computer walks for one at a time. Nothing is walked that wasn't asked for.",
85819
+ "required": [
85820
+ "type",
85821
+ "id",
85822
+ "since"
85823
+ ],
85824
+ "properties": {
85825
+ "type": {
85826
+ "const": "changed"
85827
+ },
85828
+ "id": {
85829
+ "type": "string",
85830
+ "minLength": 1,
85831
+ "maxLength": 64
85832
+ },
85833
+ "since": {
85834
+ "type": "string",
85835
+ "format": "date-time"
85836
+ }
85837
+ }
85838
+ },
85839
+ {
85840
+ "type": "object",
85841
+ "description": "The next part of a file, on an operate socket, followed by one binary message of exactly length bytes, with no message between them. The part at offset 0 starts the file, or starts it again, and each one after starts where the last ended. Nothing is at the path until the part marked last, which puts the file there whole, over any that was there, and is answered with written. Only that part, and a part that is refused, is answered. It makes no folders. A file not finished when the socket closes is given up.",
85842
+ "required": [
85843
+ "type",
85844
+ "id",
85845
+ "path",
85846
+ "offset",
85847
+ "length",
85848
+ "last"
85849
+ ],
85850
+ "properties": {
85851
+ "type": {
85852
+ "const": "write"
85853
+ },
85854
+ "id": {
85855
+ "type": "string",
85856
+ "minLength": 1,
85857
+ "maxLength": 64
85858
+ },
85859
+ "path": {
85860
+ "type": "string"
85861
+ },
85862
+ "offset": {
85863
+ "type": "integer",
85864
+ "minimum": 0
85865
+ },
85866
+ "length": {
85867
+ "type": "integer",
85868
+ "minimum": 0,
85869
+ "maximum": 1048576
85870
+ },
85871
+ "last": {
85872
+ "type": "boolean"
85873
+ }
85874
+ }
85875
+ }
85876
+ ]
85877
+ },
85878
+ "FilesAnswer": {
85879
+ "description": "What the service sends on files@1, as JSON text, each with the id it answers. Nothing comes between a data message and its bytes.",
85880
+ "oneOf": [
85881
+ {
85882
+ "type": "object",
85883
+ "required": [
85884
+ "type",
85885
+ "id",
85886
+ "path",
85887
+ "entries",
85888
+ "more"
85889
+ ],
85890
+ "properties": {
85891
+ "type": {
85892
+ "const": "entries"
85893
+ },
85894
+ "id": {
85895
+ "type": "string"
85896
+ },
85897
+ "path": {
85898
+ "type": "string"
85899
+ },
85900
+ "entries": {
85901
+ "type": "array",
85902
+ "items": {
85903
+ "type": "object",
85904
+ "description": "A link is not followed, and its size and date are its own.",
85905
+ "required": [
85906
+ "name",
85907
+ "kind",
85908
+ "size",
85909
+ "modifiedAt"
85910
+ ],
85911
+ "properties": {
85912
+ "name": {
85913
+ "type": "string"
85914
+ },
85915
+ "kind": {
85916
+ "type": "string",
85917
+ "enum": [
85918
+ "file",
85919
+ "folder",
85920
+ "link"
85921
+ ]
85922
+ },
85923
+ "size": {
85924
+ "type": "integer"
85925
+ },
85926
+ "modifiedAt": {
85927
+ "type": "string",
85928
+ "format": "date-time"
85929
+ }
85930
+ }
85931
+ }
85932
+ },
85933
+ "more": {
85934
+ "type": "boolean",
85935
+ "description": "Whether the folder has more than these."
85936
+ }
85937
+ }
85938
+ },
85939
+ {
85940
+ "type": "object",
85941
+ "description": "What the binary message after it is, and the file's whole size and date.",
85942
+ "required": [
85943
+ "type",
85944
+ "id",
85945
+ "path",
85946
+ "offset",
85947
+ "length",
85948
+ "size",
85949
+ "modifiedAt"
85950
+ ],
85951
+ "properties": {
85952
+ "type": {
85953
+ "const": "data"
85954
+ },
85955
+ "id": {
85956
+ "type": "string"
85957
+ },
85958
+ "path": {
85959
+ "type": "string"
85960
+ },
85961
+ "offset": {
85962
+ "type": "integer"
85963
+ },
85964
+ "length": {
85965
+ "type": "integer",
85966
+ "description": "How many bytes follow."
85967
+ },
85968
+ "size": {
85969
+ "type": "integer"
85970
+ },
85971
+ "modifiedAt": {
85972
+ "type": "string",
85973
+ "format": "date-time"
85974
+ }
85975
+ }
85976
+ },
85977
+ {
85978
+ "type": "object",
85979
+ "required": [
85980
+ "type",
85981
+ "id",
85982
+ "since",
85983
+ "changes",
85984
+ "more"
85985
+ ],
85986
+ "properties": {
85987
+ "type": {
85988
+ "const": "changes"
85989
+ },
85990
+ "id": {
85991
+ "type": "string"
85992
+ },
85993
+ "since": {
85994
+ "type": "string",
85995
+ "format": "date-time"
85996
+ },
85997
+ "changes": {
85998
+ "type": "array",
85999
+ "items": {
86000
+ "type": "object",
86001
+ "required": [
86002
+ "path",
86003
+ "size",
86004
+ "modifiedAt"
86005
+ ],
86006
+ "properties": {
86007
+ "path": {
86008
+ "type": "string"
86009
+ },
86010
+ "size": {
86011
+ "type": "integer"
86012
+ },
86013
+ "modifiedAt": {
86014
+ "type": "string",
86015
+ "format": "date-time"
86016
+ }
86017
+ }
86018
+ }
86019
+ },
86020
+ "more": {
86021
+ "type": "boolean",
86022
+ "description": "Whether more than these changed."
85291
86023
  }
85292
86024
  }
85293
86025
  },
85294
86026
  {
85295
86027
  "type": "object",
86028
+ "description": "The file is in place.",
85296
86029
  "required": [
85297
- "type"
86030
+ "type",
86031
+ "id",
86032
+ "path",
86033
+ "size"
85298
86034
  ],
85299
86035
  "properties": {
85300
86036
  "type": {
85301
- "const": "release"
86037
+ "const": "written"
86038
+ },
86039
+ "id": {
86040
+ "type": "string"
86041
+ },
86042
+ "path": {
86043
+ "type": "string"
86044
+ },
86045
+ "size": {
86046
+ "type": "integer"
86047
+ }
86048
+ }
86049
+ },
86050
+ {
86051
+ "type": "object",
86052
+ "description": "It wasn't done, and why. The id is empty when what was sent had none to repeat.",
86053
+ "required": [
86054
+ "type",
86055
+ "id",
86056
+ "message"
86057
+ ],
86058
+ "properties": {
86059
+ "type": {
86060
+ "const": "refused"
86061
+ },
86062
+ "id": {
86063
+ "type": "string"
86064
+ },
86065
+ "message": {
86066
+ "type": "string"
85302
86067
  }
85303
86068
  }
85304
86069
  }
@@ -85844,9 +86609,10 @@ var OPERATIONS = [
85844
86609
  "enum": [
85845
86610
  "commands",
85846
86611
  "terminal",
86612
+ "files",
85847
86613
  "browser"
85848
86614
  ],
85849
- "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. 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. Operating it makes you the driver: the agent's browser calls wait up to 25 seconds while you are, then answer that a person has or had it. You stop being the driver when you send release, close the socket, or send nothing for 60 seconds."
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."
85850
86616
  },
85851
86617
  "protocol": {
85852
86618
  "type": "string"
@@ -85858,8 +86624,7 @@ var OPERATIONS = [
85858
86624
  },
85859
86625
  "unavailable": {
85860
86626
  "type": "string",
85861
- "description": "Why it can't be opened here",
85862
- "if it can't.": null
86627
+ "description": "Why it can't be opened now, if it can't."
85863
86628
  }
85864
86629
  }
85865
86630
  }
@@ -86363,7 +87128,7 @@ var OPERATIONS = [
86363
87128
  "method": "POST",
86364
87129
  "path": "/v1/computers/{computerID}/live",
86365
87130
  "tool": "computer_live",
86366
- "summary": "A one-time ticket to watch or operate one of its surfaces live: the agent's commands as they run, or a shell of your own. Open its url as a WebSocket within 60 seconds.",
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.",
86367
87132
  "status": 201,
86368
87133
  "hasBody": true,
86369
87134
  "pathParameters": [
@@ -86384,9 +87149,10 @@ var OPERATIONS = [
86384
87149
  "enum": [
86385
87150
  "commands",
86386
87151
  "terminal",
87152
+ "files",
86387
87153
  "browser"
86388
87154
  ],
86389
- "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. 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. Operating it makes you the driver: the agent's browser calls wait up to 25 seconds while you are, then answer that a person has or had it. You stop being the driver when you send release, close the socket, or send nothing for 60 seconds."
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."
86390
87156
  },
86391
87157
  "mode": {
86392
87158
  "allOf": [
@@ -86396,7 +87162,7 @@ var OPERATIONS = [
86396
87162
  "watch",
86397
87163
  "operate"
86398
87164
  ],
86399
- "description": "In watch, what you send is dropped."
87165
+ "description": "In watch you see and don't act: what you send is dropped, except that a files socket may still ask to list, to read and for what changed."
86400
87166
  }
86401
87167
  ],
86402
87168
  "default": "watch"
@@ -86430,9 +87196,10 @@ var OPERATIONS = [
86430
87196
  "enum": [
86431
87197
  "commands",
86432
87198
  "terminal",
87199
+ "files",
86433
87200
  "browser"
86434
87201
  ],
86435
- "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. 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. Operating it makes you the driver: the agent's browser calls wait up to 25 seconds while you are, then answer that a person has or had it. You stop being the driver when you send release, close the socket, or send nothing for 60 seconds."
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."
86436
87203
  },
86437
87204
  "mode": {
86438
87205
  "type": "string",
@@ -86440,7 +87207,7 @@ var OPERATIONS = [
86440
87207
  "watch",
86441
87208
  "operate"
86442
87209
  ],
86443
- "description": "In watch, what you send is dropped."
87210
+ "description": "In watch you see and don't act: what you send is dropped, except that a files socket may still ask to list, to read and for what changed."
86444
87211
  },
86445
87212
  "expiresAt": {
86446
87213
  "type": "string",
@@ -86504,13 +87271,13 @@ function parseListen(listen) {
86504
87271
  }
86505
87272
  var bearerOf = (request) => /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
86506
87273
  async function jsonBody(request) {
86507
- let text2 = "";
87274
+ let text3 = "";
86508
87275
  for await (const chunk of request) {
86509
- text2 += String(chunk);
86510
- if (text2.length > MAX_BODY2) throw new ApiError(413, "That request is too large.");
87276
+ text3 += String(chunk);
87277
+ if (text3.length > MAX_BODY2) throw new ApiError(413, "That request is too large.");
86511
87278
  }
86512
87279
  try {
86513
- const parsed = JSON.parse(text2 || "{}");
87280
+ const parsed = JSON.parse(text3 || "{}");
86514
87281
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
86515
87282
  return parsed;
86516
87283
  } catch {
@@ -86538,7 +87305,7 @@ async function serve(options) {
86538
87305
  pattern: new RegExp(`^${operation.path.replace(/\{[^}]+\}/g, "([^/]+)")}$`),
86539
87306
  check: fromJsonSchema3(operation.input)
86540
87307
  }));
86541
- const run3 = async (route2, input2) => {
87308
+ const run2 = async (route2, input2) => {
86542
87309
  const checked = await route2.check["~standard"].validate(input2);
86543
87310
  if (checked.issues)
86544
87311
  throw new ApiError(400, checked.issues.map((issue3) => issue3.message).join("; "));
@@ -86553,7 +87320,7 @@ async function serve(options) {
86553
87320
  { description: route2.operation.summary, inputSchema: route2.check },
86554
87321
  async (input2) => {
86555
87322
  try {
86556
- const output2 = await run3(route2, input2) ?? {};
87323
+ const output2 = await run2(route2, input2) ?? {};
86557
87324
  return { content: [{ type: "text", text: JSON.stringify(output2) }] };
86558
87325
  } catch (error62) {
86559
87326
  if (!(error62 instanceof ApiError)) throw error62;
@@ -86605,7 +87372,7 @@ async function serve(options) {
86605
87372
  const value = url3.searchParams.get(name);
86606
87373
  if (value !== null) input2[name] = value;
86607
87374
  }
86608
- return send(response, operation.status, await run3(candidate, input2));
87375
+ return send(response, operation.status, await run2(candidate, input2));
86609
87376
  }
86610
87377
  throw pathMatched ? new ApiError(405, "Not allowed.") : new ApiError(404, "Not found.");
86611
87378
  };
@@ -86652,7 +87419,7 @@ ${JSON.stringify({ code: status, message })}
86652
87419
  const scheme = options.tls ? "https" : "http";
86653
87420
  base = options.publicUrl?.replace(/\/+$/, "") ?? `${scheme}://${bound.family === "IPv6" ? `[${bound.address}]` : bound.address}:${bound.port}`;
86654
87421
  await computers.resume();
86655
- const record3 = join10(data, "server.json");
87422
+ const record3 = join11(data, "server.json");
86656
87423
  writePrivate(
86657
87424
  record3,
86658
87425
  JSON.stringify({ url: base, pid: process.pid, isolation: options.runtime.isolation })
@@ -86692,7 +87459,7 @@ var USAGE = `Usage: agentcomputer <command> [--data DIR]
86692
87459
  stop <id> stop it, keeping its folder and token
86693
87460
  start <id> [--timeout SECONDS] start a stopped computer again
86694
87461
  logs <id> what its servers wrote
86695
- live <id> --surface commands|terminal [--mode watch|operate]
87462
+ live <id> --surface commands|terminal|files|browser [--mode watch|operate]
86696
87463
  a one-time ticket to open one live, as a WebSocket
86697
87464
  kill <id> stop it and remove what the service made
86698
87465
 
@@ -86705,13 +87472,13 @@ var UsageError = class extends Error {
86705
87472
  function dataFolder(given) {
86706
87473
  const xdg = process.env.XDG_DATA_HOME;
86707
87474
  return resolve5(
86708
- given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join11(xdg, "agentcomputer") : join11(homedir2(), ".local", "share", "agentcomputer"))
87475
+ given ?? process.env.AGENTCOMPUTER_DATA ?? (xdg && isAbsolute2(xdg) ? join12(xdg, "agentcomputer") : join12(homedir2(), ".local", "share", "agentcomputer"))
86709
87476
  );
86710
87477
  }
86711
87478
  function connectionTo(data) {
86712
87479
  try {
86713
- const { url: url3 } = JSON.parse(readFileSync6(join11(data, "server.json"), "utf8"));
86714
- return { apiUrl: url3, apiKey: readFileSync6(join11(data, "control-token"), "utf8").trim() };
87480
+ const { url: url3 } = JSON.parse(readFileSync6(join12(data, "server.json"), "utf8"));
87481
+ return { apiUrl: url3, apiKey: readFileSync6(join12(data, "control-token"), "utf8").trim() };
86715
87482
  } catch {
86716
87483
  throw new Error(
86717
87484
  `No service is running with its data in ${data}. Start one: agentcomputer serve`
@@ -86740,6 +87507,8 @@ async function main() {
86740
87507
  channel: { type: "string" },
86741
87508
  executable: { type: "string" },
86742
87509
  "no-sandbox": { type: "boolean" },
87510
+ endpoint: { type: "string" },
87511
+ output: { type: "string" },
86743
87512
  access: { type: "string" },
86744
87513
  workspace: { type: "string" },
86745
87514
  name: { type: "string" },
@@ -86786,6 +87555,11 @@ async function main() {
86786
87555
  executable: values.executable,
86787
87556
  sandbox: values["no-sandbox"] ? false : void 0
86788
87557
  });
87558
+ if (command === "browser-mcp")
87559
+ return serveBrowserMcp({
87560
+ endpoint: values.endpoint ?? "",
87561
+ outputDir: values.output ?? ""
87562
+ });
86789
87563
  if (command === "inside")
86790
87564
  return serveInside({
86791
87565
  access: values.access,