terminal-commands 0.1.3 → 0.2.1

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.
package/dist/agent.js CHANGED
@@ -4154,13 +4154,14 @@ var require_fast_uri = __commonJS({
4154
4154
  if (!malformedIPLiteral) {
4155
4155
  malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4156
4156
  }
4157
- if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4158
- if (uri.indexOf("%") !== -1) {
4159
- if (parsed.host !== void 0 && !malformedIPLiteral) {
4160
- const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4161
- parsed.host = reescapeHostDelimiters(host, isIP);
4162
- }
4157
+ if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
4158
+ let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4159
+ if (!isIP) {
4160
+ host = normalizePercentEncoding(host.toLowerCase());
4163
4161
  }
4162
+ parsed.host = reescapeHostDelimiters(host, isIP);
4163
+ }
4164
+ if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4164
4165
  if (parsed.path) {
4165
4166
  parsed.path = normalizePathEncoding(parsed.path);
4166
4167
  }
@@ -32302,6 +32303,9 @@ var Protocol = class {
32302
32303
  this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
32303
32304
  const handleTaskResult = async () => {
32304
32305
  const taskId = request.params.taskId;
32306
+ if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
32307
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
32308
+ }
32305
32309
  if (this._taskMessageQueue) {
32306
32310
  let queuedMessage;
32307
32311
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
@@ -32332,12 +32336,12 @@ var Protocol = class {
32332
32336
  throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
32333
32337
  }
32334
32338
  if (!isTerminal(task.status)) {
32335
- await this._waitForTaskUpdate(taskId, extra.signal);
32339
+ await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
32336
32340
  return await handleTaskResult();
32337
32341
  }
32338
32342
  if (isTerminal(task.status)) {
32339
32343
  const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
32340
- this._clearTaskQueue(taskId);
32344
+ this._clearTaskQueue(taskId, extra.sessionId);
32341
32345
  return {
32342
32346
  ...result,
32343
32347
  _meta: {
@@ -32374,7 +32378,7 @@ var Protocol = class {
32374
32378
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
32375
32379
  }
32376
32380
  await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
32377
- this._clearTaskQueue(request.params.taskId);
32381
+ this._clearTaskQueue(request.params.taskId, extra.sessionId);
32378
32382
  const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
32379
32383
  if (!cancelledTask) {
32380
32384
  throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
@@ -32502,6 +32506,19 @@ var Protocol = class {
32502
32506
  const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
32503
32507
  const capturedTransport = this._transport;
32504
32508
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
32509
+ const sessionId = capturedTransport?.sessionId;
32510
+ const store = this._taskStore;
32511
+ let relatedTaskFound = true;
32512
+ let relatedTaskLookup;
32513
+ if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
32514
+ relatedTaskFound = false;
32515
+ relatedTaskLookup = (async () => {
32516
+ if (!await store.getTask(relatedTaskId, sessionId)) {
32517
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
32518
+ }
32519
+ relatedTaskFound = true;
32520
+ })();
32521
+ }
32505
32522
  if (handler === void 0) {
32506
32523
  const errorResponse = {
32507
32524
  jsonrpc: "2.0",
@@ -32511,7 +32528,10 @@ var Protocol = class {
32511
32528
  message: "Method not found"
32512
32529
  }
32513
32530
  };
32514
- if (relatedTaskId && this._taskMessageQueue) {
32531
+ if (relatedTaskId && relatedTaskLookup) {
32532
+ const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
32533
+ relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error62) => this._onerror(new Error(`Failed to send an error response: ${error62}`)));
32534
+ } else if (relatedTaskId && this._taskMessageQueue) {
32515
32535
  this._enqueueTaskMessage(relatedTaskId, {
32516
32536
  type: "error",
32517
32537
  message: errorResponse,
@@ -32562,7 +32582,10 @@ var Protocol = class {
32562
32582
  closeSSEStream: extra?.closeSSEStream,
32563
32583
  closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
32564
32584
  };
32565
- Promise.resolve().then(() => {
32585
+ (relatedTaskLookup ?? Promise.resolve()).then(() => {
32586
+ if (relatedTaskLookup && abortController.signal.aborted) {
32587
+ throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
32588
+ }
32566
32589
  if (taskCreationParams) {
32567
32590
  this.assertTaskHandlerCapability(request.method);
32568
32591
  }
@@ -32597,7 +32620,7 @@ var Protocol = class {
32597
32620
  ...error62["data"] !== void 0 && { data: error62["data"] }
32598
32621
  }
32599
32622
  };
32600
- if (relatedTaskId && this._taskMessageQueue) {
32623
+ if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
32601
32624
  await this._enqueueTaskMessage(relatedTaskId, {
32602
32625
  type: "error",
32603
32626
  message: errorResponse,
@@ -33077,7 +33100,7 @@ var Protocol = class {
33077
33100
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
33078
33101
  }
33079
33102
  const maxQueueSize = this._options?.maxTaskQueueSize;
33080
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
33103
+ await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
33081
33104
  }
33082
33105
  /**
33083
33106
  * Clears the message queue for a task and rejects any pending request resolvers.
@@ -33106,12 +33129,13 @@ var Protocol = class {
33106
33129
  * Uses polling to check for updates at the task's configured poll interval.
33107
33130
  * @param taskId The task ID to wait for
33108
33131
  * @param signal Abort signal to cancel the wait
33132
+ * @param sessionId Session of the request that waits, passed to the task store
33109
33133
  * @returns Promise that resolves when an update occurs or rejects if aborted
33110
33134
  */
33111
- async _waitForTaskUpdate(taskId, signal) {
33135
+ async _waitForTaskUpdate(taskId, signal, sessionId) {
33112
33136
  let interval = this._options?.defaultTaskPollInterval ?? 1e3;
33113
33137
  try {
33114
- const task = await this._taskStore?.getTask(taskId);
33138
+ const task = await this._taskStore?.getTask(taskId, sessionId);
33115
33139
  if (task?.pollInterval) {
33116
33140
  interval = task.pollInterval;
33117
33141
  }
@@ -34026,6 +34050,68 @@ function createFetchWithInit(baseFetch = fetch, baseInit) {
34026
34050
  return baseFetch(url2, mergedInit);
34027
34051
  };
34028
34052
  }
34053
+ var MAX_REDIRECTS = 5;
34054
+ var REDIRECT_STATUSES = [301, 302, 303, 307, 308];
34055
+ function isWithinOrigin(from, to) {
34056
+ if (from.protocol === to.protocol && from.hostname === to.hostname && from.port === to.port) {
34057
+ return true;
34058
+ }
34059
+ return from.hostname === to.hostname && from.protocol === "http:" && from.port === "" && to.protocol === "https:" && to.port === "";
34060
+ }
34061
+ function redirectTarget(response, requestUrl) {
34062
+ const location = REDIRECT_STATUSES.includes(response.status) ? response.headers.get("location") : null;
34063
+ try {
34064
+ return location ? new URL(location, requestUrl) : void 0;
34065
+ } catch {
34066
+ return void 0;
34067
+ }
34068
+ }
34069
+ function followWithinOrigin(baseFetch, url2, init, response, followed = 0) {
34070
+ const target = redirectTarget(response, url2);
34071
+ const keepsMethod = response.status === 307 || response.status === 308 || (init?.method ?? "GET").toUpperCase() === "GET";
34072
+ if (!target || followed === MAX_REDIRECTS || !keepsMethod) {
34073
+ return void 0;
34074
+ }
34075
+ const from = new URL(url2);
34076
+ const addsUserinfo = (target.username || target.password) && (target.username !== from.username || target.password !== from.password);
34077
+ if (addsUserinfo || !isWithinOrigin(from, target)) {
34078
+ return void 0;
34079
+ }
34080
+ return Promise.resolve(response.body?.cancel()).then(() => baseFetch(target, { ...init, redirect: "manual" })).then((next) => followWithinOrigin(baseFetch, target, init, next, followed + 1) ?? next);
34081
+ }
34082
+ var followingFetches = /* @__PURE__ */ new WeakSet();
34083
+ function fetchLeavingRedirects(baseFetch) {
34084
+ const following = (url2, init) => (baseFetch ?? fetch)(url2, init);
34085
+ followingFetches.add(following);
34086
+ return following;
34087
+ }
34088
+ function fetchWithinOrigin(baseFetch = fetch) {
34089
+ if (followingFetches.has(baseFetch)) {
34090
+ return baseFetch;
34091
+ }
34092
+ return (url2, init) => {
34093
+ if (init?.redirect === "error" || init?.redirect === "manual") {
34094
+ return baseFetch(url2, init);
34095
+ }
34096
+ return Promise.resolve(baseFetch(url2, { ...init, redirect: "manual" })).then((response) => followWithinOrigin(baseFetch, url2, init, response) ?? response);
34097
+ };
34098
+ }
34099
+ function unfollowedRedirect(response, requestUrl) {
34100
+ if (response.type === "opaqueredirect") {
34101
+ return "Redirect not followed: this runtime does not expose the redirect target";
34102
+ }
34103
+ const from = response.url || requestUrl;
34104
+ const target = redirectTarget(response, from);
34105
+ if (!target) {
34106
+ return void 0;
34107
+ }
34108
+ target.username = target.password = target.search = target.hash = "";
34109
+ if (target.protocol === "http:" && new URL(from).protocol === "https:") {
34110
+ target.protocol = "https:";
34111
+ return `Redirect to plain http not followed; try ${target.href} instead`;
34112
+ }
34113
+ return `Redirect to ${target.href} not followed`;
34114
+ }
34029
34115
 
34030
34116
  // node_modules/pkce-challenge/dist/index.node.js
34031
34117
  var crypto;
@@ -34172,7 +34258,12 @@ var OAuthTokensSchema = object2({
34172
34258
  token_type: string2(),
34173
34259
  expires_in: coerce_exports.number().optional(),
34174
34260
  scope: string2().optional(),
34175
- refresh_token: string2().optional()
34261
+ refresh_token: string2().optional(),
34262
+ /**
34263
+ * Not part of the wire format: the authorization server this value was obtained from, added by
34264
+ * the client's `auth()` before it is stored and compared when it is read back.
34265
+ */
34266
+ issuer: string2().optional().catch(void 0)
34176
34267
  }).strip();
34177
34268
  var OAuthErrorResponseSchema = object2({
34178
34269
  error: string2(),
@@ -34202,7 +34293,12 @@ var OAuthClientInformationSchema = object2({
34202
34293
  client_id: string2(),
34203
34294
  client_secret: string2().optional(),
34204
34295
  client_id_issued_at: number2().optional(),
34205
- client_secret_expires_at: number2().optional()
34296
+ client_secret_expires_at: number2().optional(),
34297
+ /**
34298
+ * Not part of the wire format: the authorization server this value was obtained from, added by
34299
+ * the client's `auth()` before it is stored and compared when it is read back.
34300
+ */
34301
+ issuer: string2().optional().catch(void 0)
34206
34302
  }).strip();
34207
34303
  var OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
34208
34304
  var OAuthClientRegistrationErrorSchema = object2({
@@ -34400,10 +34496,31 @@ async function parseErrorResponse(input2) {
34400
34496
  const errorClass = OAUTH_ERRORS[error62] || ServerError;
34401
34497
  return new errorClass(error_description || "", error_uri);
34402
34498
  } catch (error62) {
34403
- const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error62}. Raw body: ${body}`;
34499
+ const redirect = input2 instanceof Response ? unfollowedRedirect(input2, input2.url) : void 0;
34500
+ const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}${redirect ?? `Invalid OAuth error response: ${error62}. Raw body: ${body}`}`;
34404
34501
  return new ServerError(errorMessage);
34405
34502
  }
34406
34503
  }
34504
+ function issuersMatch(a, b) {
34505
+ let [x, y] = [a, b];
34506
+ try {
34507
+ [x, y] = [new URL(a).href, new URL(b).href];
34508
+ } catch {
34509
+ }
34510
+ return x === y || x.endsWith("/") && x.slice(0, -1) === y || y.endsWith("/") && y.slice(0, -1) === x;
34511
+ }
34512
+ function discardIfIssuerMismatch(stored, issuer) {
34513
+ if (!stored)
34514
+ return void 0;
34515
+ if (typeof stored.issuer !== "string")
34516
+ return stored.issuer == null ? stored : { ...stored, issuer: void 0 };
34517
+ return issuersMatch(stored.issuer, issuer) ? stored : void 0;
34518
+ }
34519
+ var TokenResponseSchema = OAuthTokensSchema.omit({ issuer: true });
34520
+ var RegistrationResponseSchema = OAuthClientInformationFullSchema.omit({ issuer: true });
34521
+ function boundElsewhereError(stored, issuer) {
34522
+ return new Error(`OAuth client information is bound to authorization server ${stored.issuer} and is not presented to ${issuer}. Clear the stored client information, or correct \`expectedIssuer\`, if the authorization server has moved.`);
34523
+ }
34407
34524
  async function auth(provider, options) {
34408
34525
  try {
34409
34526
  return await authInternal(provider, options);
@@ -34457,9 +34574,24 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
34457
34574
  authorizationServerMetadata: metadata
34458
34575
  });
34459
34576
  }
34460
- const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
34577
+ const issuer = String(authorizationServerUrl);
34578
+ const selectedResource = await selectResourceURL(serverUrl, provider, resourceMetadata);
34579
+ const resource = selectedResource && resourceMetadata && !provider.validateResourceURL ? resourceMetadata.resource : selectedResource;
34461
34580
  const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
34462
- let clientInformation = await Promise.resolve(provider.clientInformation());
34581
+ const storedClientInformation = await Promise.resolve(provider.clientInformation());
34582
+ let clientInformation = discardIfIssuerMismatch(storedClientInformation, issuer);
34583
+ const canRegisterAgain = provider.saveClientInformation !== void 0 && !!provider.redirectUrl;
34584
+ if (storedClientInformation && !clientInformation && !canRegisterAgain) {
34585
+ throw boundElsewhereError(storedClientInformation, issuer);
34586
+ }
34587
+ const unstampedClientInformation = clientInformation?.issuer == null ? clientInformation : void 0;
34588
+ const bindClientInformation = async () => {
34589
+ try {
34590
+ if (unstampedClientInformation)
34591
+ await provider.saveClientInformation?.({ ...unstampedClientInformation, issuer });
34592
+ } catch {
34593
+ }
34594
+ };
34463
34595
  if (!clientInformation) {
34464
34596
  if (authorizationCode !== void 0) {
34465
34597
  throw new Error("Existing OAuth client information is required when exchanging an authorization code");
@@ -34471,9 +34603,7 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
34471
34603
  }
34472
34604
  const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
34473
34605
  if (shouldUseUrlBasedClientId) {
34474
- clientInformation = {
34475
- client_id: clientMetadataUrl
34476
- };
34606
+ clientInformation = { client_id: clientMetadataUrl, issuer };
34477
34607
  await provider.saveClientInformation?.(clientInformation);
34478
34608
  } else {
34479
34609
  if (!provider.saveClientInformation) {
@@ -34485,8 +34615,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
34485
34615
  scope: resolvedScope,
34486
34616
  fetchFn
34487
34617
  });
34488
- await provider.saveClientInformation(fullInformation);
34489
- clientInformation = fullInformation;
34618
+ clientInformation = { ...fullInformation, issuer };
34619
+ await provider.saveClientInformation(clientInformation);
34490
34620
  }
34491
34621
  }
34492
34622
  const nonInteractiveFlow = !provider.redirectUrl;
@@ -34497,10 +34627,14 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
34497
34627
  authorizationCode,
34498
34628
  fetchFn
34499
34629
  });
34500
- await provider.saveTokens(tokens2);
34630
+ await bindClientInformation();
34631
+ await provider.saveTokens({ ...tokens2, issuer });
34501
34632
  return "AUTHORIZED";
34502
34633
  }
34503
- const tokens = await provider.tokens();
34634
+ const tokens = discardIfIssuerMismatch(await provider.tokens(), issuer);
34635
+ if (tokens?.refresh_token && tokens.issuer == null) {
34636
+ console.warn("[mcp-sdk] stored OAuth tokens have no 'issuer' property (saved by an earlier version, or by a provider that does not keep it) and are used as-is; make sure your OAuthClientProvider stores what saveTokens() and saveClientInformation() receive unchanged.");
34637
+ }
34504
34638
  if (tokens?.refresh_token) {
34505
34639
  try {
34506
34640
  const newTokens = await refreshAuthorization(authorizationServerUrl, {
@@ -34511,7 +34645,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
34511
34645
  addClientAuthentication: provider.addClientAuthentication,
34512
34646
  fetchFn
34513
34647
  });
34514
- await provider.saveTokens(newTokens);
34648
+ await bindClientInformation();
34649
+ await provider.saveTokens({ ...newTokens, issuer });
34515
34650
  return "AUTHORIZED";
34516
34651
  } catch (error62) {
34517
34652
  if (!(error62 instanceof OAuthError) || error62 instanceof ServerError) {
@@ -34610,7 +34745,7 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
34610
34745
  }
34611
34746
  async function fetchWithCorsRetry(url2, headers, fetchFn = fetch) {
34612
34747
  try {
34613
- return await fetchFn(url2, { headers });
34748
+ return await fetchWithinOrigin(fetchFn)(url2, { headers });
34614
34749
  } catch (error62) {
34615
34750
  if (error62 instanceof TypeError) {
34616
34751
  if (headers) {
@@ -34635,7 +34770,7 @@ async function tryMetadataDiscovery(url2, protocolVersion, fetchFn = fetch) {
34635
34770
  return await fetchWithCorsRetry(url2, headers, fetchFn);
34636
34771
  }
34637
34772
  function shouldAttemptFallback(response, pathname) {
34638
- return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
34773
+ return !response || !response.ok && response.status < 500 && pathname !== "/";
34639
34774
  }
34640
34775
  async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
34641
34776
  const issuer = new URL(serverUrl);
@@ -34701,7 +34836,7 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fet
34701
34836
  }
34702
34837
  if (!response.ok) {
34703
34838
  await response.body?.cancel();
34704
- if (response.status >= 400 && response.status < 500) {
34839
+ if (response.status < 500) {
34705
34840
  continue;
34706
34841
  }
34707
34842
  throw new Error(`HTTP ${response.status} trying to load ${type === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
@@ -34734,6 +34869,9 @@ async function discoverOAuthServerInfo(serverUrl, opts) {
34734
34869
  resourceMetadata
34735
34870
  };
34736
34871
  }
34872
+ function resourceIndicatorToString(resource) {
34873
+ return typeof resource === "string" ? resource : resource.href;
34874
+ }
34737
34875
  async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
34738
34876
  let authorizationUrl;
34739
34877
  if (metadata) {
@@ -34765,7 +34903,7 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
34765
34903
  authorizationUrl.searchParams.append("prompt", "consent");
34766
34904
  }
34767
34905
  if (resource) {
34768
- authorizationUrl.searchParams.set("resource", resource.href);
34906
+ authorizationUrl.searchParams.set("resource", resourceIndicatorToString(resource));
34769
34907
  }
34770
34908
  return { authorizationUrl, codeVerifier };
34771
34909
  }
@@ -34784,7 +34922,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
34784
34922
  Accept: "application/json"
34785
34923
  });
34786
34924
  if (resource) {
34787
- tokenRequestParams.set("resource", resource.href);
34925
+ tokenRequestParams.set("resource", resourceIndicatorToString(resource));
34788
34926
  }
34789
34927
  if (addClientAuthentication) {
34790
34928
  await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
@@ -34793,7 +34931,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
34793
34931
  const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
34794
34932
  applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
34795
34933
  }
34796
- const response = await (fetchFn ?? fetch)(tokenUrl, {
34934
+ const response = await fetchWithinOrigin(fetchFn ?? fetch)(tokenUrl, {
34797
34935
  method: "POST",
34798
34936
  headers,
34799
34937
  body: tokenRequestParams
@@ -34801,7 +34939,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
34801
34939
  if (!response.ok) {
34802
34940
  throw await parseErrorResponse(response);
34803
34941
  }
34804
- return OAuthTokensSchema.parse(await response.json());
34942
+ return TokenResponseSchema.parse(await response.json());
34805
34943
  }
34806
34944
  async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
34807
34945
  const tokenRequestParams = new URLSearchParams({
@@ -34819,6 +34957,15 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
34819
34957
  return { refresh_token: refreshToken, ...tokens };
34820
34958
  }
34821
34959
  async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
34960
+ const readClientInformation = async () => {
34961
+ const storedClientInformation = await provider.clientInformation();
34962
+ const checked = discardIfIssuerMismatch(storedClientInformation, String(authorizationServerUrl));
34963
+ if (storedClientInformation && !checked) {
34964
+ throw boundElsewhereError(storedClientInformation, String(authorizationServerUrl));
34965
+ }
34966
+ return checked;
34967
+ };
34968
+ let clientInformation = await readClientInformation();
34822
34969
  const scope = provider.clientMetadata.scope;
34823
34970
  let tokenRequestParams;
34824
34971
  if (provider.prepareTokenRequest) {
@@ -34834,11 +34981,11 @@ async function fetchToken(provider, authorizationServerUrl, { metadata, resource
34834
34981
  const codeVerifier = await provider.codeVerifier();
34835
34982
  tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
34836
34983
  }
34837
- const clientInformation = await provider.clientInformation();
34984
+ clientInformation ?? (clientInformation = await readClientInformation());
34838
34985
  return executeTokenRequest(authorizationServerUrl, {
34839
34986
  metadata,
34840
34987
  tokenRequestParams,
34841
- clientInformation: clientInformation ?? void 0,
34988
+ clientInformation,
34842
34989
  addClientAuthentication: provider.addClientAuthentication,
34843
34990
  resource,
34844
34991
  fetchFn
@@ -34854,7 +35001,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
34854
35001
  } else {
34855
35002
  registrationUrl = new URL("/register", authorizationServerUrl);
34856
35003
  }
34857
- const response = await (fetchFn ?? fetch)(registrationUrl, {
35004
+ const response = await fetchWithinOrigin(fetchFn ?? fetch)(registrationUrl, {
34858
35005
  method: "POST",
34859
35006
  headers: {
34860
35007
  "Content-Type": "application/json"
@@ -34867,7 +35014,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
34867
35014
  if (!response.ok) {
34868
35015
  throw await parseErrorResponse(response);
34869
35016
  }
34870
- return OAuthClientInformationFullSchema.parse(await response.json());
35017
+ return RegistrationResponseSchema.parse(await response.json());
34871
35018
  }
34872
35019
 
34873
35020
  // node_modules/eventsource-parser/dist/index.js
@@ -35094,9 +35241,19 @@ var StreamableHTTPClientTransport = class {
35094
35241
  this._authProvider = opts?.authProvider;
35095
35242
  this._fetch = opts?.fetch;
35096
35243
  this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
35244
+ this._followRedirects = opts?.redirectPolicy === "follow";
35245
+ if (this._followRedirects) {
35246
+ this._fetch = fetchLeavingRedirects(this._fetch);
35247
+ this._fetchWithInit = fetchLeavingRedirects(this._fetchWithInit);
35248
+ }
35097
35249
  this._sessionId = opts?.sessionId;
35098
35250
  this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
35099
35251
  }
35252
+ /** Error text for a redirect `response` that was not followed, or `undefined` for any other response. */
35253
+ _unfollowedRedirect(response, url2) {
35254
+ const text = unfollowedRedirect(response, url2);
35255
+ return text && !this._followRedirects ? `${text} (redirectPolicy: 'same-origin')` : text;
35256
+ }
35100
35257
  async _authThenStart() {
35101
35258
  if (!this._authProvider) {
35102
35259
  throw new UnauthorizedError("No auth provider");
@@ -35146,11 +35303,13 @@ var StreamableHTTPClientTransport = class {
35146
35303
  if (resumptionToken) {
35147
35304
  headers.set("last-event-id", resumptionToken);
35148
35305
  }
35149
- const response = await (this._fetch ?? fetch)(this._url, {
35150
- method: "GET",
35151
- headers,
35152
- signal: this._abortController?.signal
35153
- });
35306
+ const fetchFn = this._fetch ?? fetch;
35307
+ const init = { method: "GET", headers, signal: this._abortController?.signal };
35308
+ let response = await fetchFn(this._url, this._followRedirects ? init : { ...init, redirect: "manual" });
35309
+ const followed = this._followRedirects ? void 0 : followWithinOrigin(fetchFn, this._url, init, response);
35310
+ if (followed) {
35311
+ response = await followed;
35312
+ }
35154
35313
  if (!response.ok) {
35155
35314
  await response.body?.cancel();
35156
35315
  if (response.status === 401 && this._authProvider) {
@@ -35159,7 +35318,7 @@ var StreamableHTTPClientTransport = class {
35159
35318
  if (response.status === 405) {
35160
35319
  return;
35161
35320
  }
35162
- throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
35321
+ throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
35163
35322
  }
35164
35323
  this._handleSseStream(response.body, options, true);
35165
35324
  } catch (error62) {
@@ -35322,7 +35481,7 @@ var StreamableHTTPClientTransport = class {
35322
35481
  body: JSON.stringify(message),
35323
35482
  signal: this._abortController?.signal
35324
35483
  };
35325
- const response = await (this._fetch ?? fetch)(this._url, init);
35484
+ const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
35326
35485
  const sessionId = response.headers.get("mcp-session-id");
35327
35486
  if (sessionId) {
35328
35487
  this._sessionId = sessionId;
@@ -35374,7 +35533,7 @@ var StreamableHTTPClientTransport = class {
35374
35533
  return this.send(message);
35375
35534
  }
35376
35535
  }
35377
- throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
35536
+ throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${this._unfollowedRedirect(response, this._url) ?? text}`);
35378
35537
  }
35379
35538
  this._hasCompletedAuthFlow = false;
35380
35539
  this._lastUpscopingHeader = void 0;
@@ -35436,10 +35595,10 @@ var StreamableHTTPClientTransport = class {
35436
35595
  headers,
35437
35596
  signal: this._abortController?.signal
35438
35597
  };
35439
- const response = await (this._fetch ?? fetch)(this._url, init);
35598
+ const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
35440
35599
  await response.body?.cancel();
35441
35600
  if (!response.ok && response.status !== 405) {
35442
- throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
35601
+ throw new StreamableHTTPError(response.status, `Failed to terminate session: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
35443
35602
  }
35444
35603
  this._sessionId = void 0;
35445
35604
  } catch (error62) {
@@ -35479,88 +35638,443 @@ var import_websocket = __toESM(require_websocket(), 1);
35479
35638
  var import_websocket_server = __toESM(require_websocket_server(), 1);
35480
35639
  var wrapper_default = import_websocket.default;
35481
35640
 
35641
+ // src/process-event-forwarder.ts
35642
+ var eventSchema = external_exports.object({
35643
+ sessionId: external_exports.string().uuid(),
35644
+ taskId: external_exports.string().uuid().optional(),
35645
+ sequence: external_exports.number().int().positive(),
35646
+ kind: external_exports.enum(["started", "stdout", "stderr", "finished"]),
35647
+ offset: external_exports.number().int().nonnegative().optional(),
35648
+ data: external_exports.string().max(4096).optional(),
35649
+ exitCode: external_exports.number().int().nullable().optional(),
35650
+ signal: external_exports.string().nullable().optional(),
35651
+ timedOut: external_exports.boolean().optional()
35652
+ });
35653
+ var batchSchema = external_exports.object({
35654
+ sessionId: external_exports.string().uuid(),
35655
+ events: external_exports.array(eventSchema).max(32),
35656
+ nextSequence: external_exports.number().int().nonnegative(),
35657
+ missedEvents: external_exports.boolean(),
35658
+ finished: external_exports.boolean()
35659
+ });
35660
+ var processListSchema = external_exports.object({
35661
+ processes: external_exports.array(
35662
+ external_exports.object({
35663
+ sessionId: external_exports.string().uuid(),
35664
+ taskId: external_exports.string().uuid().optional()
35665
+ })
35666
+ )
35667
+ });
35668
+ function structuredContent(response) {
35669
+ if (!response || typeof response !== "object") {
35670
+ throw new Error("Local process tool returned an invalid response.");
35671
+ }
35672
+ const result = response;
35673
+ if (result.isError || result.structuredContent === void 0) {
35674
+ throw new Error("Local process event tool failed or returned no content.");
35675
+ }
35676
+ return result.structuredContent;
35677
+ }
35678
+ var ProcessEventForwarder = class {
35679
+ constructor(callLocalTool, send, log, acknowledged = /* @__PURE__ */ new Map(), ackTimeoutMs = 3e3, onFatal) {
35680
+ this.callLocalTool = callLocalTool;
35681
+ this.send = send;
35682
+ this.log = log;
35683
+ this.acknowledged = acknowledged;
35684
+ this.ackTimeoutMs = ackTimeoutMs;
35685
+ this.onFatal = onFatal;
35686
+ }
35687
+ callLocalTool;
35688
+ send;
35689
+ log;
35690
+ acknowledged;
35691
+ ackTimeoutMs;
35692
+ onFatal;
35693
+ #watchers = /* @__PURE__ */ new Map();
35694
+ #ackWaiters = /* @__PURE__ */ new Map();
35695
+ #lastSent = /* @__PURE__ */ new Map();
35696
+ #closed = false;
35697
+ acknowledge(sessionId, sequence) {
35698
+ if (!external_exports.string().uuid().safeParse(sessionId).success || !Number.isSafeInteger(sequence) || sequence < 1)
35699
+ return;
35700
+ const maximum = this.#lastSent.get(sessionId) ?? 0;
35701
+ if (sequence > maximum) return;
35702
+ const current = this.acknowledged.get(sessionId) ?? 0;
35703
+ if (sequence <= current) return;
35704
+ this.acknowledged.set(sessionId, sequence);
35705
+ for (const wake of this.#ackWaiters.get(sessionId) ?? []) wake();
35706
+ }
35707
+ async #waitForAck(sessionId, target, signal) {
35708
+ if ((this.acknowledged.get(sessionId) ?? 0) >= target) return true;
35709
+ if (signal.aborted) return false;
35710
+ return new Promise((resolve) => {
35711
+ const listeners = this.#ackWaiters.get(sessionId) ?? /* @__PURE__ */ new Set();
35712
+ this.#ackWaiters.set(sessionId, listeners);
35713
+ let timer;
35714
+ const finish = () => {
35715
+ clearTimeout(timer);
35716
+ signal.removeEventListener("abort", finish);
35717
+ listeners.delete(wake);
35718
+ if (listeners.size === 0) this.#ackWaiters.delete(sessionId);
35719
+ resolve((this.acknowledged.get(sessionId) ?? 0) >= target);
35720
+ };
35721
+ const wake = () => {
35722
+ if ((this.acknowledged.get(sessionId) ?? 0) >= target) finish();
35723
+ };
35724
+ listeners.add(wake);
35725
+ signal.addEventListener("abort", finish, { once: true });
35726
+ timer = setTimeout(finish, this.ackTimeoutMs);
35727
+ wake();
35728
+ });
35729
+ }
35730
+ async recover() {
35731
+ if (this.#closed) return;
35732
+ const result = await this.callLocalTool("list_processes", {});
35733
+ const parsed = processListSchema.parse(structuredContent(result));
35734
+ for (const process2 of parsed.processes) {
35735
+ if (process2.taskId) this.watch(process2.sessionId);
35736
+ }
35737
+ }
35738
+ watch(sessionId) {
35739
+ if (this.#closed || this.#watchers.has(sessionId)) return;
35740
+ if (!external_exports.string().uuid().safeParse(sessionId).success) return;
35741
+ const controller2 = new AbortController();
35742
+ this.#watchers.set(sessionId, controller2);
35743
+ void this.#pump(sessionId, controller2.signal).catch((error62) => {
35744
+ const failure2 = error62 instanceof Error ? error62 : new Error("Unknown process event failure.");
35745
+ this.log?.("Process event watch failed: " + failure2.message);
35746
+ if (!controller2.signal.aborted) this.onFatal?.(failure2);
35747
+ }).finally(() => {
35748
+ if (this.#watchers.get(sessionId) === controller2) {
35749
+ this.#watchers.delete(sessionId);
35750
+ }
35751
+ });
35752
+ }
35753
+ async #pump(sessionId, signal) {
35754
+ let afterSequence = this.acknowledged.get(sessionId) ?? 0;
35755
+ while (!signal.aborted) {
35756
+ const raw = await this.callLocalTool("watch_process_events", {
35757
+ sessionId,
35758
+ afterSequence,
35759
+ timeoutMs: 1e4,
35760
+ maxEvents: 32
35761
+ });
35762
+ if (signal.aborted) break;
35763
+ const batch = batchSchema.parse(structuredContent(raw));
35764
+ if (batch.sessionId !== sessionId) {
35765
+ throw new Error("Process event watch returned a different session ID.");
35766
+ }
35767
+ if (batch.nextSequence < afterSequence) {
35768
+ throw new Error("Process event cursor moved backwards.");
35769
+ }
35770
+ if (batch.events.length === 0 && batch.finished) break;
35771
+ if (batch.events.length === 0) continue;
35772
+ let confirmed = false;
35773
+ for (let attempt = 0; attempt < 5 && !signal.aborted; attempt++) {
35774
+ for (const [index, event] of batch.events.entries()) {
35775
+ if (signal.aborted) break;
35776
+ if (event.sessionId !== sessionId) {
35777
+ throw new Error("Process event belongs to another session.");
35778
+ }
35779
+ this.#lastSent.set(
35780
+ sessionId,
35781
+ Math.max(this.#lastSent.get(sessionId) ?? 0, event.sequence)
35782
+ );
35783
+ this.send({
35784
+ type: "process_event",
35785
+ event,
35786
+ missedEvents: index === 0 && batch.missedEvents
35787
+ });
35788
+ }
35789
+ confirmed = await this.#waitForAck(
35790
+ sessionId,
35791
+ batch.nextSequence,
35792
+ signal
35793
+ );
35794
+ if (confirmed) break;
35795
+ }
35796
+ if (!confirmed) {
35797
+ if (signal.aborted) break;
35798
+ throw new Error(
35799
+ "PROCESS_EVENT_ACK_TIMEOUT: Gateway did not acknowledge event batch."
35800
+ );
35801
+ }
35802
+ afterSequence = batch.nextSequence;
35803
+ if (batch.finished && batch.events.length < 32) break;
35804
+ }
35805
+ }
35806
+ close() {
35807
+ this.#closed = true;
35808
+ for (const controller2 of this.#watchers.values()) controller2.abort();
35809
+ this.#watchers.clear();
35810
+ for (const wake of this.#ackWaiters.values()) {
35811
+ for (const notify of wake) notify();
35812
+ }
35813
+ this.#ackWaiters.clear();
35814
+ }
35815
+ };
35816
+
35482
35817
  // src/agent-core.ts
35818
+ var supportedAgentTools = [
35819
+ "get_system_info",
35820
+ "list_directory",
35821
+ "read_text_file",
35822
+ "start_process",
35823
+ "read_process_output",
35824
+ "watch_process_events",
35825
+ "terminate_process",
35826
+ "list_processes",
35827
+ "write_text_file"
35828
+ ];
35483
35829
  var destructiveTools = /* @__PURE__ */ new Set([
35484
35830
  "start_process",
35485
35831
  "terminate_process",
35486
35832
  "write_text_file"
35487
35833
  ]);
35488
- var requestSchema = external_exports.object({
35834
+ var requestEnvelope = external_exports.object({
35489
35835
  type: external_exports.literal("request"),
35490
- requestId: external_exports.string().uuid(),
35491
- tool: external_exports.enum([
35492
- "get_system_info",
35493
- "list_directory",
35494
- "read_text_file",
35495
- "start_process",
35496
- "read_process_output",
35497
- "terminate_process",
35498
- "list_processes",
35499
- "write_text_file"
35500
- ]),
35501
- arguments: external_exports.record(external_exports.string(), external_exports.unknown())
35502
- });
35503
- async function authorizeToolCall(tool, args, allowDestructive, approve) {
35836
+ requestId: external_exports.string().uuid()
35837
+ });
35838
+ var requestSchema = requestEnvelope.extend({
35839
+ tool: external_exports.enum(supportedAgentTools),
35840
+ arguments: external_exports.record(external_exports.string(), external_exports.unknown()),
35841
+ deadlineAt: external_exports.number().int().positive().optional()
35842
+ });
35843
+ var cancelSchema = external_exports.object({
35844
+ type: external_exports.literal("cancel"),
35845
+ requestId: external_exports.string().uuid()
35846
+ });
35847
+ var ackSchema = external_exports.object({
35848
+ type: external_exports.literal("process_event_ack"),
35849
+ sessionId: external_exports.string().uuid(),
35850
+ sequence: external_exports.number().int().positive()
35851
+ });
35852
+ var DEFAULT_REQUEST_LIFETIME_MS = 6e4;
35853
+ function assertActive(signal) {
35854
+ if (signal?.aborted) {
35855
+ throw new Error(
35856
+ "REQUEST_EXPIRED: The operation was cancelled or its approval deadline expired."
35857
+ );
35858
+ }
35859
+ }
35860
+ async function authorizeToolCall(tool, args, allowDestructive, approve, signal, progress) {
35861
+ const notify = (phase) => {
35862
+ try {
35863
+ progress?.(phase);
35864
+ } catch {
35865
+ }
35866
+ };
35867
+ assertActive(signal);
35504
35868
  if (!destructiveTools.has(tool)) return;
35505
35869
  if (!allowDestructive) {
35870
+ notify("denied");
35506
35871
  throw new Error(
35507
35872
  "This action requires local approval. Commands and writes are disabled unless explicitly enabled."
35508
35873
  );
35509
35874
  }
35510
- if (!approve || !await approve(tool, args)) {
35875
+ if (!approve) {
35876
+ notify("denied");
35877
+ throw new Error("The action was not approved on the connected machine.");
35878
+ }
35879
+ notify("awaiting_approval");
35880
+ if (!await approve(tool, args, signal)) {
35881
+ assertActive(signal);
35882
+ notify("denied");
35511
35883
  throw new Error("The action was not approved on the connected machine.");
35512
35884
  }
35885
+ assertActive(signal);
35886
+ notify("approved");
35513
35887
  }
35514
- async function connectOnce(options) {
35888
+ async function connectOnce(options, acknowledged) {
35515
35889
  const { config: config3, signal } = options;
35890
+ if (!config3.MACHINE_TERMINAL_DEVICE_SECRET) {
35891
+ throw new Error(
35892
+ "DEVICE_PAIRING_REQUIRED: Connect using the CLI or set MACHINE_TERMINAL_DEVICE_SECRET."
35893
+ );
35894
+ }
35516
35895
  const client = new Client({
35517
35896
  name: "terminal-commands-cli",
35518
- version: "0.1.0"
35519
- });
35520
- await client.connect(
35521
- new StreamableHTTPClientTransport(
35522
- new URL(config3.MACHINE_TERMINAL_LOCAL_MCP_URL)
35523
- )
35524
- );
35525
- const token = options.getToken ? await options.getToken() : config3.MACHINE_TERMINAL_DEVICE_TOKEN;
35526
- const socket = new wrapper_default(config3.MACHINE_TERMINAL_GATEWAY_URL, {
35527
- headers: { Authorization: `Bearer ${token}` }
35897
+ version: "0.2.1"
35528
35898
  });
35529
- const stop2 = () => socket.close(1e3, "Client stopping.");
35530
- signal?.addEventListener("abort", stop2, { once: true });
35531
- socket.on("open", () => {
35532
- socket.send(
35533
- JSON.stringify({
35534
- type: "hello",
35535
- deviceId: config3.MACHINE_TERMINAL_DEVICE_ID,
35536
- name: config3.MACHINE_TERMINAL_DEVICE_NAME,
35537
- platform: `${os.platform()}-${os.arch()}`
35538
- })
35899
+ let disposeSocket;
35900
+ try {
35901
+ await client.connect(
35902
+ new StreamableHTTPClientTransport(
35903
+ new URL(config3.MACHINE_TERMINAL_LOCAL_MCP_URL),
35904
+ {
35905
+ requestInit: {
35906
+ headers: config3.MACHINE_TERMINAL_LOCAL_AGENT_TOKEN ? {
35907
+ "X-Terminal-Commands-Agent-Token": config3.MACHINE_TERMINAL_LOCAL_AGENT_TOKEN
35908
+ } : {}
35909
+ }
35910
+ }
35911
+ )
35539
35912
  );
35540
- });
35541
- socket.on("message", (raw) => {
35542
- void handleMessage(options, client, socket, raw);
35543
- });
35544
- await new Promise((resolve, reject) => {
35545
- socket.once("close", resolve);
35546
- socket.once("error", reject);
35547
- }).finally(() => signal?.removeEventListener("abort", stop2));
35548
- await client.close();
35913
+ const token = options.getToken ? await options.getToken() : config3.MACHINE_TERMINAL_DEVICE_TOKEN;
35914
+ const socket = new wrapper_default(config3.MACHINE_TERMINAL_GATEWAY_URL, {
35915
+ headers: { Authorization: "Bearer " + token }
35916
+ });
35917
+ disposeSocket = () => socket.terminate();
35918
+ const pending = /* @__PURE__ */ new Map();
35919
+ let requestProgressEnabled = false;
35920
+ const forwarder = new ProcessEventForwarder(
35921
+ async (name, args) => client.callTool({ name, arguments: args }),
35922
+ (message) => {
35923
+ if (socket.readyState !== wrapper_default.OPEN) return;
35924
+ if (socket.bufferedAmount > 512e3) {
35925
+ socket.close(1013, "Process event backpressure");
35926
+ throw new Error(
35927
+ "Process event WebSocket backpressure limit reached."
35928
+ );
35929
+ }
35930
+ socket.send(JSON.stringify(message));
35931
+ },
35932
+ options.log,
35933
+ acknowledged,
35934
+ 3e3,
35935
+ () => {
35936
+ if (socket.readyState === wrapper_default.OPEN) {
35937
+ socket.close(1013, "Process event delivery stalled");
35938
+ }
35939
+ }
35940
+ );
35941
+ const stop2 = () => socket.close(1e3, "Client stopping.");
35942
+ signal?.addEventListener("abort", stop2, { once: true });
35943
+ socket.on("open", () => {
35944
+ socket.send(
35945
+ JSON.stringify({
35946
+ type: "hello",
35947
+ deviceId: config3.MACHINE_TERMINAL_DEVICE_ID,
35948
+ deviceSecret: config3.MACHINE_TERMINAL_DEVICE_SECRET,
35949
+ name: config3.MACHINE_TERMINAL_DEVICE_NAME,
35950
+ platform: os.platform() + "-" + os.arch(),
35951
+ protocolVersion: 2,
35952
+ capabilities: supportedAgentTools
35953
+ })
35954
+ );
35955
+ });
35956
+ socket.on("message", (raw) => {
35957
+ try {
35958
+ const message = JSON.parse(raw.toString());
35959
+ const cancelled = cancelSchema.safeParse(message);
35960
+ if (cancelled.success) {
35961
+ pending.get(cancelled.data.requestId)?.abort();
35962
+ return;
35963
+ }
35964
+ const ack = ackSchema.safeParse(message);
35965
+ if (ack.success) {
35966
+ forwarder.acknowledge(ack.data.sessionId, ack.data.sequence);
35967
+ return;
35968
+ }
35969
+ if (message && typeof message === "object" && "type" in message && message.type === "ready") {
35970
+ requestProgressEnabled = message.requestProgressVersion === 1;
35971
+ void forwarder.recover().catch(
35972
+ (error62) => options.log?.(
35973
+ "Could not recover local process streams: " + (error62 instanceof Error ? error62.message : "unknown error")
35974
+ )
35975
+ );
35976
+ return;
35977
+ }
35978
+ } catch {
35979
+ options.log?.("Ignoring a malformed gateway message.");
35980
+ return;
35981
+ }
35982
+ void handleMessage(
35983
+ options,
35984
+ client,
35985
+ socket,
35986
+ raw,
35987
+ pending,
35988
+ forwarder,
35989
+ requestProgressEnabled
35990
+ );
35991
+ });
35992
+ socket.on("close", () => {
35993
+ forwarder.close();
35994
+ for (const controller2 of pending.values()) controller2.abort();
35995
+ });
35996
+ await new Promise((resolve, reject) => {
35997
+ socket.once("close", resolve);
35998
+ socket.once("error", reject);
35999
+ }).finally(() => signal?.removeEventListener("abort", stop2));
36000
+ } finally {
36001
+ disposeSocket?.();
36002
+ await client.close().catch(() => void 0);
36003
+ }
35549
36004
  }
35550
- async function handleMessage(options, client, socket, raw) {
36005
+ async function handleMessage(options, client, socket, raw, pending, forwarder, requestProgressEnabled) {
35551
36006
  let requestId;
36007
+ let controller2;
36008
+ let deadlineTimer;
35552
36009
  try {
35553
- const parsed = requestSchema.parse(JSON.parse(raw.toString()));
35554
- requestId = parsed.requestId;
36010
+ const payload = JSON.parse(raw.toString());
36011
+ const envelope = requestEnvelope.safeParse(payload);
36012
+ if (!envelope.success) {
36013
+ options.log?.("Ignoring a request with an invalid envelope.");
36014
+ return;
36015
+ }
36016
+ requestId = envelope.data.requestId;
36017
+ const parsed = requestSchema.safeParse(payload);
36018
+ if (!parsed.success) {
36019
+ const tool = payload && typeof payload === "object" && "tool" in payload ? payload.tool : void 0;
36020
+ if (typeof tool === "string" && !supportedAgentTools.includes(tool)) {
36021
+ throw new Error(
36022
+ "UNSUPPORTED_TOOL: " + tool + ". Update the local companion."
36023
+ );
36024
+ }
36025
+ throw new Error("INVALID_REQUEST: Invalid tool arguments or deadline.");
36026
+ }
36027
+ if (pending.has(requestId)) {
36028
+ throw new Error(
36029
+ "DUPLICATE_REQUEST: This request is already in progress."
36030
+ );
36031
+ }
36032
+ const deadlineAt = parsed.data.deadlineAt ?? Date.now() + DEFAULT_REQUEST_LIFETIME_MS;
36033
+ if (deadlineAt <= Date.now()) {
36034
+ throw new Error("REQUEST_EXPIRED: The request deadline has passed.");
36035
+ }
36036
+ controller2 = new AbortController();
36037
+ pending.set(requestId, controller2);
36038
+ deadlineTimer = setTimeout(
36039
+ () => controller2?.abort(),
36040
+ Math.max(0, deadlineAt - Date.now())
36041
+ );
36042
+ let progressSequence = 0;
36043
+ const progress = (phase) => {
36044
+ if (!requestProgressEnabled || parsed.data.tool !== "start_process" || socket.readyState !== wrapper_default.OPEN)
36045
+ return;
36046
+ if (socket.bufferedAmount > 512e3) {
36047
+ socket.close(1013, "Request progress backpressure");
36048
+ return;
36049
+ }
36050
+ try {
36051
+ socket.send(
36052
+ JSON.stringify({
36053
+ type: "request_progress",
36054
+ requestId,
36055
+ sequence: ++progressSequence,
36056
+ phase,
36057
+ ...phase === "approved" ? {
36058
+ approvalMode: options.config.MACHINE_TERMINAL_AUTO_APPROVE === "1" ? "automatic" : "manual"
36059
+ } : {}
36060
+ })
36061
+ );
36062
+ } catch {
36063
+ }
36064
+ };
36065
+ progress("received");
35555
36066
  await authorizeToolCall(
35556
- parsed.tool,
35557
- parsed.arguments,
36067
+ parsed.data.tool,
36068
+ parsed.data.arguments,
35558
36069
  options.config.MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE === "1",
35559
- options.approve
36070
+ options.approve,
36071
+ controller2.signal,
36072
+ progress
35560
36073
  );
36074
+ assertActive(controller2.signal);
35561
36075
  const result = await client.callTool({
35562
- name: parsed.tool,
35563
- arguments: parsed.arguments
36076
+ name: parsed.data.tool,
36077
+ arguments: parsed.data.arguments
35564
36078
  });
35565
36079
  if (result.isError) {
35566
36080
  const content = Array.isArray(result.content) ? result.content : [];
@@ -35577,6 +36091,12 @@ async function handleMessage(options, client, socket, raw) {
35577
36091
  result: result.structuredContent ?? { content: result.content }
35578
36092
  })
35579
36093
  );
36094
+ if (parsed.data.tool === "start_process") {
36095
+ const snapshot = result.structuredContent;
36096
+ if (snapshot && typeof snapshot === "object" && "sessionId" in snapshot && typeof snapshot.sessionId === "string" && "taskId" in snapshot && typeof snapshot.taskId === "string") {
36097
+ forwarder.watch(snapshot.sessionId);
36098
+ }
36099
+ }
35580
36100
  }
35581
36101
  } catch (error62) {
35582
36102
  if (!requestId || socket.readyState !== wrapper_default.OPEN) return;
@@ -35587,18 +36107,24 @@ async function handleMessage(options, client, socket, raw) {
35587
36107
  error: error62 instanceof Error ? error62.message : "Local request failed."
35588
36108
  })
35589
36109
  );
36110
+ } finally {
36111
+ if (deadlineTimer) clearTimeout(deadlineTimer);
36112
+ if (requestId && controller2 && pending.get(requestId) === controller2) {
36113
+ pending.delete(requestId);
36114
+ }
35590
36115
  }
35591
36116
  }
35592
36117
  async function runAgent(options) {
35593
36118
  let reconnectDelayMs = 1e3;
36119
+ const acknowledged = /* @__PURE__ */ new Map();
35594
36120
  while (!options.signal?.aborted) {
35595
36121
  try {
35596
- await connectOnce(options);
36122
+ await connectOnce(options, acknowledged);
35597
36123
  reconnectDelayMs = 1e3;
35598
36124
  } catch (error62) {
35599
36125
  if (options.signal?.aborted) break;
35600
36126
  options.log?.(
35601
- `Connection failed: ${error62 instanceof Error ? error62.message : "unknown error"}`
36127
+ "Connection failed: " + (error62 instanceof Error ? error62.message : "unknown error")
35602
36128
  );
35603
36129
  }
35604
36130
  if (options.signal?.aborted) break;
@@ -35622,7 +36148,9 @@ import os2 from "node:os";
35622
36148
  var schema = external_exports.object({
35623
36149
  MACHINE_TERMINAL_GATEWAY_URL: external_exports.string().url(),
35624
36150
  MACHINE_TERMINAL_DEVICE_TOKEN: external_exports.string().min(1),
36151
+ MACHINE_TERMINAL_LOCAL_AGENT_TOKEN: external_exports.string().regex(/^[a-f0-9]{64}$/i).optional(),
35625
36152
  MACHINE_TERMINAL_DEVICE_ID: external_exports.string().min(1).max(128),
36153
+ MACHINE_TERMINAL_DEVICE_SECRET: external_exports.string().regex(/^[a-f0-9]{64}$/i).optional(),
35626
36154
  MACHINE_TERMINAL_DEVICE_NAME: external_exports.string().min(1).max(128).default(os2.hostname()),
35627
36155
  MACHINE_TERMINAL_LOCAL_MCP_URL: external_exports.string().url().default("http://127.0.0.1:3333/mcp"),
35628
36156
  MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE: external_exports.enum(["0", "1"]).default("0"),
@@ -35670,55 +36198,78 @@ var dim = (text) => paint("dim", text);
35670
36198
  var warn = (text) => paint("yellow", text);
35671
36199
 
35672
36200
  // src/approval.ts
36201
+ var DEFAULT_APPROVAL_TIMEOUT_MS = 3e4;
36202
+ function safeLabel(value) {
36203
+ return JSON.stringify(String(value));
36204
+ }
35673
36205
  function approvalSummary(tool, args) {
35674
36206
  if (tool === "write_text_file") {
35675
36207
  const bytes = Buffer.byteLength(String(args.contents ?? ""));
35676
- return `Write ${bytes} bytes to ${String(args.path ?? "an unknown path")}${args.overwrite ? " (overwrite)" : ""}`;
36208
+ return "Write " + bytes + " bytes to " + safeLabel(args.path ?? "an unknown path") + (args.overwrite ? " (overwrite)" : "");
35677
36209
  }
35678
36210
  if (tool === "start_process") {
35679
36211
  const command = [
35680
- String(args.program ?? ""),
35681
- ...Array.isArray(args.args) ? args.args.map(String) : []
36212
+ safeLabel(args.program ?? ""),
36213
+ ...Array.isArray(args.args) ? args.args.map(safeLabel) : []
35682
36214
  ];
35683
- return `Start: ${command.join(" ")}`;
36215
+ return "Start: " + command.join(" ") + " (cwd: " + safeLabel(args.cwd ?? "configured root") + ")";
35684
36216
  }
35685
36217
  if (tool === "terminate_process") {
35686
- return `Terminate process session ${String(args.sessionId ?? "")}`;
36218
+ return "Terminate process session " + safeLabel(args.sessionId ?? "");
35687
36219
  }
35688
- return `Run destructive tool: ${tool}`;
36220
+ return "Run destructive tool: " + safeLabel(tool);
35689
36221
  }
35690
36222
  function createAutoApprover() {
35691
- return (tool, args) => {
36223
+ return (tool, args, signal) => {
36224
+ if (signal?.aborted) return Promise.resolve(false);
35692
36225
  console.log(
35693
- `
35694
- ${warn("Approval requested:")} ${approvalSummary(tool, args)} ${dim("(auto-approved)")}`
36226
+ "\n" + warn("Approval requested:") + " " + approvalSummary(tool, args) + " " + dim("(auto-approved)")
35695
36227
  );
35696
36228
  return Promise.resolve(true);
35697
36229
  };
35698
36230
  }
35699
- function createTerminalApprover() {
35700
- let queue = Promise.resolve(false);
35701
- return (tool, args) => {
35702
- queue = queue.catch(() => false).then(async () => {
35703
- if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
36231
+ function createTerminalApprover(timeoutMs = DEFAULT_APPROVAL_TIMEOUT_MS) {
36232
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
36233
+ throw new Error("Approval timeout must be a positive integer.");
36234
+ }
36235
+ let queue = Promise.resolve();
36236
+ return (tool, args, signal) => {
36237
+ const next = queue.then(async () => {
36238
+ if (!process.stdin.isTTY || !process.stdout.isTTY || signal?.aborted) {
36239
+ return false;
36240
+ }
36241
+ const controller2 = new AbortController();
36242
+ const abort = () => controller2.abort();
36243
+ signal?.addEventListener("abort", abort, { once: true });
36244
+ const timer = setTimeout(abort, timeoutMs);
35704
36245
  const prompt = createInterface({
35705
36246
  input: process.stdin,
35706
36247
  output: process.stdout
35707
36248
  });
35708
36249
  try {
36250
+ if (signal?.aborted) return false;
35709
36251
  console.log(
35710
- `
35711
- ${warn("Approval requested:")} ${approvalSummary(tool, args)}`
36252
+ "\n" + warn("Approval requested:") + " " + approvalSummary(tool, args)
35712
36253
  );
35713
36254
  const answer = await prompt.question(
35714
- `${bold("Approve this action?")} [y/N] `
36255
+ bold("Approve this action?") + " [y/N] ",
36256
+ { signal: controller2.signal }
35715
36257
  );
35716
- return (/* @__PURE__ */ new Set(["y", "yes"])).has(answer.trim().toLowerCase());
36258
+ return !controller2.signal.aborted && (/* @__PURE__ */ new Set(["y", "yes"])).has(answer.trim().toLowerCase());
36259
+ } catch (error62) {
36260
+ if (controller2.signal.aborted) return false;
36261
+ throw error62;
35717
36262
  } finally {
36263
+ clearTimeout(timer);
36264
+ signal?.removeEventListener("abort", abort);
35718
36265
  prompt.close();
35719
36266
  }
35720
36267
  });
35721
- return queue;
36268
+ queue = next.then(
36269
+ () => void 0,
36270
+ () => void 0
36271
+ );
36272
+ return next;
35722
36273
  };
35723
36274
  }
35724
36275