terminal-commands 0.1.2 → 0.2.0

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,81 +35638,394 @@ 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
35483
- var destructiveTools = /* @__PURE__ */ new Set(["run_program", "write_text_file"]);
35484
- var requestSchema = external_exports.object({
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
+ ];
35829
+ var destructiveTools = /* @__PURE__ */ new Set([
35830
+ "start_process",
35831
+ "terminate_process",
35832
+ "write_text_file"
35833
+ ]);
35834
+ var requestEnvelope = external_exports.object({
35485
35835
  type: external_exports.literal("request"),
35486
- requestId: external_exports.string().uuid(),
35487
- tool: external_exports.enum([
35488
- "get_system_info",
35489
- "list_directory",
35490
- "read_text_file",
35491
- "run_program",
35492
- "write_text_file"
35493
- ]),
35494
- arguments: external_exports.record(external_exports.string(), external_exports.unknown())
35495
- });
35496
- 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) {
35861
+ assertActive(signal);
35497
35862
  if (!destructiveTools.has(tool)) return;
35498
35863
  if (!allowDestructive) {
35499
35864
  throw new Error(
35500
35865
  "This action requires local approval. Commands and writes are disabled unless explicitly enabled."
35501
35866
  );
35502
35867
  }
35503
- if (!approve || !await approve(tool, args)) {
35868
+ if (!approve || !await approve(tool, args, signal)) {
35869
+ assertActive(signal);
35504
35870
  throw new Error("The action was not approved on the connected machine.");
35505
35871
  }
35872
+ assertActive(signal);
35506
35873
  }
35507
- async function connectOnce(options) {
35874
+ async function connectOnce(options, acknowledged) {
35508
35875
  const { config: config3, signal } = options;
35876
+ if (!config3.MACHINE_TERMINAL_DEVICE_SECRET) {
35877
+ throw new Error(
35878
+ "DEVICE_PAIRING_REQUIRED: Connect using the CLI or set MACHINE_TERMINAL_DEVICE_SECRET."
35879
+ );
35880
+ }
35509
35881
  const client = new Client({
35510
35882
  name: "terminal-commands-cli",
35511
- version: "0.1.0"
35512
- });
35513
- await client.connect(
35514
- new StreamableHTTPClientTransport(
35515
- new URL(config3.MACHINE_TERMINAL_LOCAL_MCP_URL)
35516
- )
35517
- );
35518
- const token = options.getToken ? await options.getToken() : config3.MACHINE_TERMINAL_DEVICE_TOKEN;
35519
- const socket = new wrapper_default(config3.MACHINE_TERMINAL_GATEWAY_URL, {
35520
- headers: { Authorization: `Bearer ${token}` }
35883
+ version: "0.2.0"
35521
35884
  });
35522
- const stop2 = () => socket.close(1e3, "Client stopping.");
35523
- signal?.addEventListener("abort", stop2, { once: true });
35524
- socket.on("open", () => {
35525
- socket.send(
35526
- JSON.stringify({
35527
- type: "hello",
35528
- deviceId: config3.MACHINE_TERMINAL_DEVICE_ID,
35529
- name: config3.MACHINE_TERMINAL_DEVICE_NAME,
35530
- platform: `${os.platform()}-${os.arch()}`
35531
- })
35885
+ let disposeSocket;
35886
+ try {
35887
+ await client.connect(
35888
+ new StreamableHTTPClientTransport(
35889
+ new URL(config3.MACHINE_TERMINAL_LOCAL_MCP_URL),
35890
+ {
35891
+ requestInit: {
35892
+ headers: config3.MACHINE_TERMINAL_LOCAL_AGENT_TOKEN ? {
35893
+ "X-Terminal-Commands-Agent-Token": config3.MACHINE_TERMINAL_LOCAL_AGENT_TOKEN
35894
+ } : {}
35895
+ }
35896
+ }
35897
+ )
35532
35898
  );
35533
- });
35534
- socket.on("message", (raw) => {
35535
- void handleMessage(options, client, socket, raw);
35536
- });
35537
- await new Promise((resolve, reject) => {
35538
- socket.once("close", resolve);
35539
- socket.once("error", reject);
35540
- }).finally(() => signal?.removeEventListener("abort", stop2));
35541
- await client.close();
35899
+ const token = options.getToken ? await options.getToken() : config3.MACHINE_TERMINAL_DEVICE_TOKEN;
35900
+ const socket = new wrapper_default(config3.MACHINE_TERMINAL_GATEWAY_URL, {
35901
+ headers: { Authorization: "Bearer " + token }
35902
+ });
35903
+ disposeSocket = () => socket.terminate();
35904
+ const pending = /* @__PURE__ */ new Map();
35905
+ const forwarder = new ProcessEventForwarder(
35906
+ async (name, args) => client.callTool({ name, arguments: args }),
35907
+ (message) => {
35908
+ if (socket.readyState !== wrapper_default.OPEN) return;
35909
+ if (socket.bufferedAmount > 512e3) {
35910
+ socket.close(1013, "Process event backpressure");
35911
+ throw new Error(
35912
+ "Process event WebSocket backpressure limit reached."
35913
+ );
35914
+ }
35915
+ socket.send(JSON.stringify(message));
35916
+ },
35917
+ options.log,
35918
+ acknowledged,
35919
+ 3e3,
35920
+ () => {
35921
+ if (socket.readyState === wrapper_default.OPEN) {
35922
+ socket.close(1013, "Process event delivery stalled");
35923
+ }
35924
+ }
35925
+ );
35926
+ const stop2 = () => socket.close(1e3, "Client stopping.");
35927
+ signal?.addEventListener("abort", stop2, { once: true });
35928
+ socket.on("open", () => {
35929
+ socket.send(
35930
+ JSON.stringify({
35931
+ type: "hello",
35932
+ deviceId: config3.MACHINE_TERMINAL_DEVICE_ID,
35933
+ deviceSecret: config3.MACHINE_TERMINAL_DEVICE_SECRET,
35934
+ name: config3.MACHINE_TERMINAL_DEVICE_NAME,
35935
+ platform: os.platform() + "-" + os.arch(),
35936
+ protocolVersion: 2,
35937
+ capabilities: supportedAgentTools
35938
+ })
35939
+ );
35940
+ });
35941
+ socket.on("message", (raw) => {
35942
+ try {
35943
+ const message = JSON.parse(raw.toString());
35944
+ const cancelled = cancelSchema.safeParse(message);
35945
+ if (cancelled.success) {
35946
+ pending.get(cancelled.data.requestId)?.abort();
35947
+ return;
35948
+ }
35949
+ const ack = ackSchema.safeParse(message);
35950
+ if (ack.success) {
35951
+ forwarder.acknowledge(ack.data.sessionId, ack.data.sequence);
35952
+ return;
35953
+ }
35954
+ if (message && typeof message === "object" && "type" in message && message.type === "ready") {
35955
+ void forwarder.recover().catch(
35956
+ (error62) => options.log?.(
35957
+ "Could not recover local process streams: " + (error62 instanceof Error ? error62.message : "unknown error")
35958
+ )
35959
+ );
35960
+ return;
35961
+ }
35962
+ } catch {
35963
+ options.log?.("Ignoring a malformed gateway message.");
35964
+ return;
35965
+ }
35966
+ void handleMessage(options, client, socket, raw, pending, forwarder);
35967
+ });
35968
+ socket.on("close", () => {
35969
+ forwarder.close();
35970
+ for (const controller2 of pending.values()) controller2.abort();
35971
+ });
35972
+ await new Promise((resolve, reject) => {
35973
+ socket.once("close", resolve);
35974
+ socket.once("error", reject);
35975
+ }).finally(() => signal?.removeEventListener("abort", stop2));
35976
+ } finally {
35977
+ disposeSocket?.();
35978
+ await client.close().catch(() => void 0);
35979
+ }
35542
35980
  }
35543
- async function handleMessage(options, client, socket, raw) {
35981
+ async function handleMessage(options, client, socket, raw, pending, forwarder) {
35544
35982
  let requestId;
35983
+ let controller2;
35984
+ let deadlineTimer;
35545
35985
  try {
35546
- const parsed = requestSchema.parse(JSON.parse(raw.toString()));
35547
- requestId = parsed.requestId;
35986
+ const payload = JSON.parse(raw.toString());
35987
+ const envelope = requestEnvelope.safeParse(payload);
35988
+ if (!envelope.success) {
35989
+ options.log?.("Ignoring a request with an invalid envelope.");
35990
+ return;
35991
+ }
35992
+ requestId = envelope.data.requestId;
35993
+ const parsed = requestSchema.safeParse(payload);
35994
+ if (!parsed.success) {
35995
+ const tool = payload && typeof payload === "object" && "tool" in payload ? payload.tool : void 0;
35996
+ if (typeof tool === "string" && !supportedAgentTools.includes(tool)) {
35997
+ throw new Error(
35998
+ "UNSUPPORTED_TOOL: " + tool + ". Update the local companion."
35999
+ );
36000
+ }
36001
+ throw new Error("INVALID_REQUEST: Invalid tool arguments or deadline.");
36002
+ }
36003
+ if (pending.has(requestId)) {
36004
+ throw new Error(
36005
+ "DUPLICATE_REQUEST: This request is already in progress."
36006
+ );
36007
+ }
36008
+ const deadlineAt = parsed.data.deadlineAt ?? Date.now() + DEFAULT_REQUEST_LIFETIME_MS;
36009
+ if (deadlineAt <= Date.now()) {
36010
+ throw new Error("REQUEST_EXPIRED: The request deadline has passed.");
36011
+ }
36012
+ controller2 = new AbortController();
36013
+ pending.set(requestId, controller2);
36014
+ deadlineTimer = setTimeout(
36015
+ () => controller2?.abort(),
36016
+ Math.max(0, deadlineAt - Date.now())
36017
+ );
35548
36018
  await authorizeToolCall(
35549
- parsed.tool,
35550
- parsed.arguments,
36019
+ parsed.data.tool,
36020
+ parsed.data.arguments,
35551
36021
  options.config.MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE === "1",
35552
- options.approve
36022
+ options.approve,
36023
+ controller2.signal
35553
36024
  );
36025
+ assertActive(controller2.signal);
35554
36026
  const result = await client.callTool({
35555
- name: parsed.tool,
35556
- arguments: parsed.arguments
36027
+ name: parsed.data.tool,
36028
+ arguments: parsed.data.arguments
35557
36029
  });
35558
36030
  if (result.isError) {
35559
36031
  const content = Array.isArray(result.content) ? result.content : [];
@@ -35570,6 +36042,12 @@ async function handleMessage(options, client, socket, raw) {
35570
36042
  result: result.structuredContent ?? { content: result.content }
35571
36043
  })
35572
36044
  );
36045
+ if (parsed.data.tool === "start_process") {
36046
+ const snapshot = result.structuredContent;
36047
+ if (snapshot && typeof snapshot === "object" && "sessionId" in snapshot && typeof snapshot.sessionId === "string" && "taskId" in snapshot && typeof snapshot.taskId === "string") {
36048
+ forwarder.watch(snapshot.sessionId);
36049
+ }
36050
+ }
35573
36051
  }
35574
36052
  } catch (error62) {
35575
36053
  if (!requestId || socket.readyState !== wrapper_default.OPEN) return;
@@ -35580,18 +36058,24 @@ async function handleMessage(options, client, socket, raw) {
35580
36058
  error: error62 instanceof Error ? error62.message : "Local request failed."
35581
36059
  })
35582
36060
  );
36061
+ } finally {
36062
+ if (deadlineTimer) clearTimeout(deadlineTimer);
36063
+ if (requestId && controller2 && pending.get(requestId) === controller2) {
36064
+ pending.delete(requestId);
36065
+ }
35583
36066
  }
35584
36067
  }
35585
36068
  async function runAgent(options) {
35586
36069
  let reconnectDelayMs = 1e3;
36070
+ const acknowledged = /* @__PURE__ */ new Map();
35587
36071
  while (!options.signal?.aborted) {
35588
36072
  try {
35589
- await connectOnce(options);
36073
+ await connectOnce(options, acknowledged);
35590
36074
  reconnectDelayMs = 1e3;
35591
36075
  } catch (error62) {
35592
36076
  if (options.signal?.aborted) break;
35593
36077
  options.log?.(
35594
- `Connection failed: ${error62 instanceof Error ? error62.message : "unknown error"}`
36078
+ "Connection failed: " + (error62 instanceof Error ? error62.message : "unknown error")
35595
36079
  );
35596
36080
  }
35597
36081
  if (options.signal?.aborted) break;
@@ -35615,7 +36099,9 @@ import os2 from "node:os";
35615
36099
  var schema = external_exports.object({
35616
36100
  MACHINE_TERMINAL_GATEWAY_URL: external_exports.string().url(),
35617
36101
  MACHINE_TERMINAL_DEVICE_TOKEN: external_exports.string().min(1),
36102
+ MACHINE_TERMINAL_LOCAL_AGENT_TOKEN: external_exports.string().regex(/^[a-f0-9]{64}$/i).optional(),
35618
36103
  MACHINE_TERMINAL_DEVICE_ID: external_exports.string().min(1).max(128),
36104
+ MACHINE_TERMINAL_DEVICE_SECRET: external_exports.string().regex(/^[a-f0-9]{64}$/i).optional(),
35619
36105
  MACHINE_TERMINAL_DEVICE_NAME: external_exports.string().min(1).max(128).default(os2.hostname()),
35620
36106
  MACHINE_TERMINAL_LOCAL_MCP_URL: external_exports.string().url().default("http://127.0.0.1:3333/mcp"),
35621
36107
  MACHINE_TERMINAL_AGENT_ALLOW_DESTRUCTIVE: external_exports.enum(["0", "1"]).default("0"),
@@ -35663,52 +36149,78 @@ var dim = (text) => paint("dim", text);
35663
36149
  var warn = (text) => paint("yellow", text);
35664
36150
 
35665
36151
  // src/approval.ts
36152
+ var DEFAULT_APPROVAL_TIMEOUT_MS = 3e4;
36153
+ function safeLabel(value) {
36154
+ return JSON.stringify(String(value));
36155
+ }
35666
36156
  function approvalSummary(tool, args) {
35667
36157
  if (tool === "write_text_file") {
35668
36158
  const bytes = Buffer.byteLength(String(args.contents ?? ""));
35669
- return `Write ${bytes} bytes to ${String(args.path ?? "an unknown path")}${args.overwrite ? " (overwrite)" : ""}`;
36159
+ return "Write " + bytes + " bytes to " + safeLabel(args.path ?? "an unknown path") + (args.overwrite ? " (overwrite)" : "");
35670
36160
  }
35671
- if (tool === "run_program") {
36161
+ if (tool === "start_process") {
35672
36162
  const command = [
35673
- String(args.program ?? ""),
35674
- ...Array.isArray(args.args) ? args.args.map(String) : []
36163
+ safeLabel(args.program ?? ""),
36164
+ ...Array.isArray(args.args) ? args.args.map(safeLabel) : []
35675
36165
  ];
35676
- return `Run: ${command.join(" ")}`;
36166
+ return "Start: " + command.join(" ") + " (cwd: " + safeLabel(args.cwd ?? "configured root") + ")";
36167
+ }
36168
+ if (tool === "terminate_process") {
36169
+ return "Terminate process session " + safeLabel(args.sessionId ?? "");
35677
36170
  }
35678
- return `Run destructive tool: ${tool}`;
36171
+ return "Run destructive tool: " + safeLabel(tool);
35679
36172
  }
35680
36173
  function createAutoApprover() {
35681
- return (tool, args) => {
36174
+ return (tool, args, signal) => {
36175
+ if (signal?.aborted) return Promise.resolve(false);
35682
36176
  console.log(
35683
- `
35684
- ${warn("Approval requested:")} ${approvalSummary(tool, args)} ${dim("(auto-approved)")}`
36177
+ "\n" + warn("Approval requested:") + " " + approvalSummary(tool, args) + " " + dim("(auto-approved)")
35685
36178
  );
35686
36179
  return Promise.resolve(true);
35687
36180
  };
35688
36181
  }
35689
- function createTerminalApprover() {
35690
- let queue = Promise.resolve(false);
35691
- return (tool, args) => {
35692
- queue = queue.catch(() => false).then(async () => {
35693
- if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
36182
+ function createTerminalApprover(timeoutMs = DEFAULT_APPROVAL_TIMEOUT_MS) {
36183
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
36184
+ throw new Error("Approval timeout must be a positive integer.");
36185
+ }
36186
+ let queue = Promise.resolve();
36187
+ return (tool, args, signal) => {
36188
+ const next = queue.then(async () => {
36189
+ if (!process.stdin.isTTY || !process.stdout.isTTY || signal?.aborted) {
36190
+ return false;
36191
+ }
36192
+ const controller2 = new AbortController();
36193
+ const abort = () => controller2.abort();
36194
+ signal?.addEventListener("abort", abort, { once: true });
36195
+ const timer = setTimeout(abort, timeoutMs);
35694
36196
  const prompt = createInterface({
35695
36197
  input: process.stdin,
35696
36198
  output: process.stdout
35697
36199
  });
35698
36200
  try {
36201
+ if (signal?.aborted) return false;
35699
36202
  console.log(
35700
- `
35701
- ${warn("Approval requested:")} ${approvalSummary(tool, args)}`
36203
+ "\n" + warn("Approval requested:") + " " + approvalSummary(tool, args)
35702
36204
  );
35703
36205
  const answer = await prompt.question(
35704
- `${bold("Approve this action?")} [y/N] `
36206
+ bold("Approve this action?") + " [y/N] ",
36207
+ { signal: controller2.signal }
35705
36208
  );
35706
- return (/* @__PURE__ */ new Set(["y", "yes"])).has(answer.trim().toLowerCase());
36209
+ return !controller2.signal.aborted && (/* @__PURE__ */ new Set(["y", "yes"])).has(answer.trim().toLowerCase());
36210
+ } catch (error62) {
36211
+ if (controller2.signal.aborted) return false;
36212
+ throw error62;
35707
36213
  } finally {
36214
+ clearTimeout(timer);
36215
+ signal?.removeEventListener("abort", abort);
35708
36216
  prompt.close();
35709
36217
  }
35710
36218
  });
35711
- return queue;
36219
+ queue = next.then(
36220
+ () => void 0,
36221
+ () => void 0
36222
+ );
36223
+ return next;
35712
36224
  };
35713
36225
  }
35714
36226