@cydm/pie 2.5.2 → 2.6.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.
@@ -16019,6 +16019,9 @@ var Protocol = class {
16019
16019
  this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
16020
16020
  const handleTaskResult = async () => {
16021
16021
  const taskId = request.params.taskId;
16022
+ if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
16023
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
16024
+ }
16022
16025
  if (this._taskMessageQueue) {
16023
16026
  let queuedMessage;
16024
16027
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
@@ -16049,12 +16052,12 @@ var Protocol = class {
16049
16052
  throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
16050
16053
  }
16051
16054
  if (!isTerminal(task.status)) {
16052
- await this._waitForTaskUpdate(taskId, extra.signal);
16055
+ await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
16053
16056
  return await handleTaskResult();
16054
16057
  }
16055
16058
  if (isTerminal(task.status)) {
16056
16059
  const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
16057
- this._clearTaskQueue(taskId);
16060
+ this._clearTaskQueue(taskId, extra.sessionId);
16058
16061
  return {
16059
16062
  ...result,
16060
16063
  _meta: {
@@ -16091,7 +16094,7 @@ var Protocol = class {
16091
16094
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
16092
16095
  }
16093
16096
  await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
16094
- this._clearTaskQueue(request.params.taskId);
16097
+ this._clearTaskQueue(request.params.taskId, extra.sessionId);
16095
16098
  const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
16096
16099
  if (!cancelledTask) {
16097
16100
  throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
@@ -16219,6 +16222,19 @@ var Protocol = class {
16219
16222
  const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
16220
16223
  const capturedTransport = this._transport;
16221
16224
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
16225
+ const sessionId = capturedTransport?.sessionId;
16226
+ const store = this._taskStore;
16227
+ let relatedTaskFound = true;
16228
+ let relatedTaskLookup;
16229
+ if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
16230
+ relatedTaskFound = false;
16231
+ relatedTaskLookup = (async () => {
16232
+ if (!await store.getTask(relatedTaskId, sessionId)) {
16233
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
16234
+ }
16235
+ relatedTaskFound = true;
16236
+ })();
16237
+ }
16222
16238
  if (handler === void 0) {
16223
16239
  const errorResponse = {
16224
16240
  jsonrpc: "2.0",
@@ -16228,7 +16244,10 @@ var Protocol = class {
16228
16244
  message: "Method not found"
16229
16245
  }
16230
16246
  };
16231
- if (relatedTaskId && this._taskMessageQueue) {
16247
+ if (relatedTaskId && relatedTaskLookup) {
16248
+ const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
16249
+ relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error2) => this._onerror(new Error(`Failed to send an error response: ${error2}`)));
16250
+ } else if (relatedTaskId && this._taskMessageQueue) {
16232
16251
  this._enqueueTaskMessage(relatedTaskId, {
16233
16252
  type: "error",
16234
16253
  message: errorResponse,
@@ -16279,7 +16298,10 @@ var Protocol = class {
16279
16298
  closeSSEStream: extra?.closeSSEStream,
16280
16299
  closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
16281
16300
  };
16282
- Promise.resolve().then(() => {
16301
+ (relatedTaskLookup ?? Promise.resolve()).then(() => {
16302
+ if (relatedTaskLookup && abortController.signal.aborted) {
16303
+ throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
16304
+ }
16283
16305
  if (taskCreationParams) {
16284
16306
  this.assertTaskHandlerCapability(request.method);
16285
16307
  }
@@ -16314,7 +16336,7 @@ var Protocol = class {
16314
16336
  ...error2["data"] !== void 0 && { data: error2["data"] }
16315
16337
  }
16316
16338
  };
16317
- if (relatedTaskId && this._taskMessageQueue) {
16339
+ if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
16318
16340
  await this._enqueueTaskMessage(relatedTaskId, {
16319
16341
  type: "error",
16320
16342
  message: errorResponse,
@@ -16794,7 +16816,7 @@ var Protocol = class {
16794
16816
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
16795
16817
  }
16796
16818
  const maxQueueSize = this._options?.maxTaskQueueSize;
16797
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
16819
+ await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
16798
16820
  }
16799
16821
  /**
16800
16822
  * Clears the message queue for a task and rejects any pending request resolvers.
@@ -16823,12 +16845,13 @@ var Protocol = class {
16823
16845
  * Uses polling to check for updates at the task's configured poll interval.
16824
16846
  * @param taskId The task ID to wait for
16825
16847
  * @param signal Abort signal to cancel the wait
16848
+ * @param sessionId Session of the request that waits, passed to the task store
16826
16849
  * @returns Promise that resolves when an update occurs or rejects if aborted
16827
16850
  */
16828
- async _waitForTaskUpdate(taskId, signal) {
16851
+ async _waitForTaskUpdate(taskId, signal, sessionId) {
16829
16852
  let interval = this._options?.defaultTaskPollInterval ?? 1e3;
16830
16853
  try {
16831
- const task = await this._taskStore?.getTask(taskId);
16854
+ const task = await this._taskStore?.getTask(taskId, sessionId);
16832
16855
  if (task?.pollInterval) {
16833
16856
  interval = task.pollInterval;
16834
16857
  }
@@ -17956,6 +17979,68 @@ function createFetchWithInit(baseFetch = fetch, baseInit) {
17956
17979
  return baseFetch(url2, mergedInit);
17957
17980
  };
17958
17981
  }
17982
+ var MAX_REDIRECTS = 5;
17983
+ var REDIRECT_STATUSES = [301, 302, 303, 307, 308];
17984
+ function isWithinOrigin(from, to) {
17985
+ if (from.protocol === to.protocol && from.hostname === to.hostname && from.port === to.port) {
17986
+ return true;
17987
+ }
17988
+ return from.hostname === to.hostname && from.protocol === "http:" && from.port === "" && to.protocol === "https:" && to.port === "";
17989
+ }
17990
+ function redirectTarget(response, requestUrl) {
17991
+ const location = REDIRECT_STATUSES.includes(response.status) ? response.headers.get("location") : null;
17992
+ try {
17993
+ return location ? new URL(location, requestUrl) : void 0;
17994
+ } catch {
17995
+ return void 0;
17996
+ }
17997
+ }
17998
+ function followWithinOrigin(baseFetch, url2, init, response, followed = 0) {
17999
+ const target = redirectTarget(response, url2);
18000
+ const keepsMethod = response.status === 307 || response.status === 308 || (init?.method ?? "GET").toUpperCase() === "GET";
18001
+ if (!target || followed === MAX_REDIRECTS || !keepsMethod) {
18002
+ return void 0;
18003
+ }
18004
+ const from = new URL(url2);
18005
+ const addsUserinfo = (target.username || target.password) && (target.username !== from.username || target.password !== from.password);
18006
+ if (addsUserinfo || !isWithinOrigin(from, target)) {
18007
+ return void 0;
18008
+ }
18009
+ return Promise.resolve(response.body?.cancel()).then(() => baseFetch(target, { ...init, redirect: "manual" })).then((next) => followWithinOrigin(baseFetch, target, init, next, followed + 1) ?? next);
18010
+ }
18011
+ var followingFetches = /* @__PURE__ */ new WeakSet();
18012
+ function fetchLeavingRedirects(baseFetch) {
18013
+ const following = (url2, init) => (baseFetch ?? fetch)(url2, init);
18014
+ followingFetches.add(following);
18015
+ return following;
18016
+ }
18017
+ function fetchWithinOrigin(baseFetch = fetch) {
18018
+ if (followingFetches.has(baseFetch)) {
18019
+ return baseFetch;
18020
+ }
18021
+ return (url2, init) => {
18022
+ if (init?.redirect === "error" || init?.redirect === "manual") {
18023
+ return baseFetch(url2, init);
18024
+ }
18025
+ return Promise.resolve(baseFetch(url2, { ...init, redirect: "manual" })).then((response) => followWithinOrigin(baseFetch, url2, init, response) ?? response);
18026
+ };
18027
+ }
18028
+ function unfollowedRedirect(response, requestUrl) {
18029
+ if (response.type === "opaqueredirect") {
18030
+ return "Redirect not followed: this runtime does not expose the redirect target";
18031
+ }
18032
+ const from = response.url || requestUrl;
18033
+ const target = redirectTarget(response, from);
18034
+ if (!target) {
18035
+ return void 0;
18036
+ }
18037
+ target.username = target.password = target.search = target.hash = "";
18038
+ if (target.protocol === "http:" && new URL(from).protocol === "https:") {
18039
+ target.protocol = "https:";
18040
+ return `Redirect to plain http not followed; try ${target.href} instead`;
18041
+ }
18042
+ return `Redirect to ${target.href} not followed`;
18043
+ }
17959
18044
 
17960
18045
  // ../../node_modules/pkce-challenge/dist/index.node.js
17961
18046
  var crypto;
@@ -18102,7 +18187,12 @@ var OAuthTokensSchema = object2({
18102
18187
  token_type: string2(),
18103
18188
  expires_in: coerce_exports.number().optional(),
18104
18189
  scope: string2().optional(),
18105
- refresh_token: string2().optional()
18190
+ refresh_token: string2().optional(),
18191
+ /**
18192
+ * Not part of the wire format: the authorization server this value was obtained from, added by
18193
+ * the client's `auth()` before it is stored and compared when it is read back.
18194
+ */
18195
+ issuer: string2().optional().catch(void 0)
18106
18196
  }).strip();
18107
18197
  var OAuthErrorResponseSchema = object2({
18108
18198
  error: string2(),
@@ -18132,7 +18222,12 @@ var OAuthClientInformationSchema = object2({
18132
18222
  client_id: string2(),
18133
18223
  client_secret: string2().optional(),
18134
18224
  client_id_issued_at: number2().optional(),
18135
- client_secret_expires_at: number2().optional()
18225
+ client_secret_expires_at: number2().optional(),
18226
+ /**
18227
+ * Not part of the wire format: the authorization server this value was obtained from, added by
18228
+ * the client's `auth()` before it is stored and compared when it is read back.
18229
+ */
18230
+ issuer: string2().optional().catch(void 0)
18136
18231
  }).strip();
18137
18232
  var OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
18138
18233
  var OAuthClientRegistrationErrorSchema = object2({
@@ -18330,10 +18425,31 @@ async function parseErrorResponse(input) {
18330
18425
  const errorClass = OAUTH_ERRORS[error2] || ServerError;
18331
18426
  return new errorClass(error_description || "", error_uri);
18332
18427
  } catch (error2) {
18333
- const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error2}. Raw body: ${body}`;
18428
+ const redirect = input instanceof Response ? unfollowedRedirect(input, input.url) : void 0;
18429
+ const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}${redirect ?? `Invalid OAuth error response: ${error2}. Raw body: ${body}`}`;
18334
18430
  return new ServerError(errorMessage);
18335
18431
  }
18336
18432
  }
18433
+ function issuersMatch(a, b) {
18434
+ let [x, y] = [a, b];
18435
+ try {
18436
+ [x, y] = [new URL(a).href, new URL(b).href];
18437
+ } catch {
18438
+ }
18439
+ return x === y || x.endsWith("/") && x.slice(0, -1) === y || y.endsWith("/") && y.slice(0, -1) === x;
18440
+ }
18441
+ function discardIfIssuerMismatch(stored, issuer) {
18442
+ if (!stored)
18443
+ return void 0;
18444
+ if (typeof stored.issuer !== "string")
18445
+ return stored.issuer == null ? stored : { ...stored, issuer: void 0 };
18446
+ return issuersMatch(stored.issuer, issuer) ? stored : void 0;
18447
+ }
18448
+ var TokenResponseSchema = OAuthTokensSchema.omit({ issuer: true });
18449
+ var RegistrationResponseSchema = OAuthClientInformationFullSchema.omit({ issuer: true });
18450
+ function boundElsewhereError(stored, issuer) {
18451
+ 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.`);
18452
+ }
18337
18453
  async function auth(provider, options) {
18338
18454
  try {
18339
18455
  return await authInternal(provider, options);
@@ -18387,10 +18503,24 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
18387
18503
  authorizationServerMetadata: metadata
18388
18504
  });
18389
18505
  }
18506
+ const issuer = String(authorizationServerUrl);
18390
18507
  const selectedResource = await selectResourceURL(serverUrl, provider, resourceMetadata);
18391
18508
  const resource = selectedResource && resourceMetadata && !provider.validateResourceURL ? resourceMetadata.resource : selectedResource;
18392
18509
  const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
18393
- let clientInformation = await Promise.resolve(provider.clientInformation());
18510
+ const storedClientInformation = await Promise.resolve(provider.clientInformation());
18511
+ let clientInformation = discardIfIssuerMismatch(storedClientInformation, issuer);
18512
+ const canRegisterAgain = provider.saveClientInformation !== void 0 && !!provider.redirectUrl;
18513
+ if (storedClientInformation && !clientInformation && !canRegisterAgain) {
18514
+ throw boundElsewhereError(storedClientInformation, issuer);
18515
+ }
18516
+ const unstampedClientInformation = clientInformation?.issuer == null ? clientInformation : void 0;
18517
+ const bindClientInformation = async () => {
18518
+ try {
18519
+ if (unstampedClientInformation)
18520
+ await provider.saveClientInformation?.({ ...unstampedClientInformation, issuer });
18521
+ } catch {
18522
+ }
18523
+ };
18394
18524
  if (!clientInformation) {
18395
18525
  if (authorizationCode !== void 0) {
18396
18526
  throw new Error("Existing OAuth client information is required when exchanging an authorization code");
@@ -18402,9 +18532,7 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
18402
18532
  }
18403
18533
  const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
18404
18534
  if (shouldUseUrlBasedClientId) {
18405
- clientInformation = {
18406
- client_id: clientMetadataUrl
18407
- };
18535
+ clientInformation = { client_id: clientMetadataUrl, issuer };
18408
18536
  await provider.saveClientInformation?.(clientInformation);
18409
18537
  } else {
18410
18538
  if (!provider.saveClientInformation) {
@@ -18416,8 +18544,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
18416
18544
  scope: resolvedScope,
18417
18545
  fetchFn
18418
18546
  });
18419
- await provider.saveClientInformation(fullInformation);
18420
- clientInformation = fullInformation;
18547
+ clientInformation = { ...fullInformation, issuer };
18548
+ await provider.saveClientInformation(clientInformation);
18421
18549
  }
18422
18550
  }
18423
18551
  const nonInteractiveFlow = !provider.redirectUrl;
@@ -18428,10 +18556,14 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
18428
18556
  authorizationCode,
18429
18557
  fetchFn
18430
18558
  });
18431
- await provider.saveTokens(tokens2);
18559
+ await bindClientInformation();
18560
+ await provider.saveTokens({ ...tokens2, issuer });
18432
18561
  return "AUTHORIZED";
18433
18562
  }
18434
- const tokens = await provider.tokens();
18563
+ const tokens = discardIfIssuerMismatch(await provider.tokens(), issuer);
18564
+ if (tokens?.refresh_token && tokens.issuer == null) {
18565
+ 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.");
18566
+ }
18435
18567
  if (tokens?.refresh_token) {
18436
18568
  try {
18437
18569
  const newTokens = await refreshAuthorization(authorizationServerUrl, {
@@ -18442,7 +18574,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
18442
18574
  addClientAuthentication: provider.addClientAuthentication,
18443
18575
  fetchFn
18444
18576
  });
18445
- await provider.saveTokens(newTokens);
18577
+ await bindClientInformation();
18578
+ await provider.saveTokens({ ...newTokens, issuer });
18446
18579
  return "AUTHORIZED";
18447
18580
  } catch (error2) {
18448
18581
  if (!(error2 instanceof OAuthError) || error2 instanceof ServerError) {
@@ -18541,7 +18674,7 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
18541
18674
  }
18542
18675
  async function fetchWithCorsRetry(url2, headers, fetchFn = fetch) {
18543
18676
  try {
18544
- return await fetchFn(url2, { headers });
18677
+ return await fetchWithinOrigin(fetchFn)(url2, { headers });
18545
18678
  } catch (error2) {
18546
18679
  if (error2 instanceof TypeError) {
18547
18680
  if (headers) {
@@ -18566,7 +18699,7 @@ async function tryMetadataDiscovery(url2, protocolVersion, fetchFn = fetch) {
18566
18699
  return await fetchWithCorsRetry(url2, headers, fetchFn);
18567
18700
  }
18568
18701
  function shouldAttemptFallback(response, pathname) {
18569
- return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
18702
+ return !response || !response.ok && response.status < 500 && pathname !== "/";
18570
18703
  }
18571
18704
  async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
18572
18705
  const issuer = new URL(serverUrl);
@@ -18632,7 +18765,7 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fet
18632
18765
  }
18633
18766
  if (!response.ok) {
18634
18767
  await response.body?.cancel();
18635
- if (response.status >= 400 && response.status < 500) {
18768
+ if (response.status < 500) {
18636
18769
  continue;
18637
18770
  }
18638
18771
  throw new Error(`HTTP ${response.status} trying to load ${type === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
@@ -18727,7 +18860,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
18727
18860
  const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
18728
18861
  applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
18729
18862
  }
18730
- const response = await (fetchFn ?? fetch)(tokenUrl, {
18863
+ const response = await fetchWithinOrigin(fetchFn ?? fetch)(tokenUrl, {
18731
18864
  method: "POST",
18732
18865
  headers,
18733
18866
  body: tokenRequestParams
@@ -18735,7 +18868,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
18735
18868
  if (!response.ok) {
18736
18869
  throw await parseErrorResponse(response);
18737
18870
  }
18738
- return OAuthTokensSchema.parse(await response.json());
18871
+ return TokenResponseSchema.parse(await response.json());
18739
18872
  }
18740
18873
  async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
18741
18874
  const tokenRequestParams = new URLSearchParams({
@@ -18753,6 +18886,15 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
18753
18886
  return { refresh_token: refreshToken, ...tokens };
18754
18887
  }
18755
18888
  async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
18889
+ const readClientInformation = async () => {
18890
+ const storedClientInformation = await provider.clientInformation();
18891
+ const checked = discardIfIssuerMismatch(storedClientInformation, String(authorizationServerUrl));
18892
+ if (storedClientInformation && !checked) {
18893
+ throw boundElsewhereError(storedClientInformation, String(authorizationServerUrl));
18894
+ }
18895
+ return checked;
18896
+ };
18897
+ let clientInformation = await readClientInformation();
18756
18898
  const scope = provider.clientMetadata.scope;
18757
18899
  let tokenRequestParams;
18758
18900
  if (provider.prepareTokenRequest) {
@@ -18768,11 +18910,11 @@ async function fetchToken(provider, authorizationServerUrl, { metadata, resource
18768
18910
  const codeVerifier = await provider.codeVerifier();
18769
18911
  tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
18770
18912
  }
18771
- const clientInformation = await provider.clientInformation();
18913
+ clientInformation ?? (clientInformation = await readClientInformation());
18772
18914
  return executeTokenRequest(authorizationServerUrl, {
18773
18915
  metadata,
18774
18916
  tokenRequestParams,
18775
- clientInformation: clientInformation ?? void 0,
18917
+ clientInformation,
18776
18918
  addClientAuthentication: provider.addClientAuthentication,
18777
18919
  resource,
18778
18920
  fetchFn
@@ -18788,7 +18930,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
18788
18930
  } else {
18789
18931
  registrationUrl = new URL("/register", authorizationServerUrl);
18790
18932
  }
18791
- const response = await (fetchFn ?? fetch)(registrationUrl, {
18933
+ const response = await fetchWithinOrigin(fetchFn ?? fetch)(registrationUrl, {
18792
18934
  method: "POST",
18793
18935
  headers: {
18794
18936
  "Content-Type": "application/json"
@@ -18801,7 +18943,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
18801
18943
  if (!response.ok) {
18802
18944
  throw await parseErrorResponse(response);
18803
18945
  }
18804
- return OAuthClientInformationFullSchema.parse(await response.json());
18946
+ return RegistrationResponseSchema.parse(await response.json());
18805
18947
  }
18806
18948
 
18807
18949
  // ../../node_modules/eventsource-parser/dist/index.js
@@ -19028,9 +19170,19 @@ var StreamableHTTPClientTransport = class {
19028
19170
  this._authProvider = opts?.authProvider;
19029
19171
  this._fetch = opts?.fetch;
19030
19172
  this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
19173
+ this._followRedirects = opts?.redirectPolicy === "follow";
19174
+ if (this._followRedirects) {
19175
+ this._fetch = fetchLeavingRedirects(this._fetch);
19176
+ this._fetchWithInit = fetchLeavingRedirects(this._fetchWithInit);
19177
+ }
19031
19178
  this._sessionId = opts?.sessionId;
19032
19179
  this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
19033
19180
  }
19181
+ /** Error text for a redirect `response` that was not followed, or `undefined` for any other response. */
19182
+ _unfollowedRedirect(response, url2) {
19183
+ const text = unfollowedRedirect(response, url2);
19184
+ return text && !this._followRedirects ? `${text} (redirectPolicy: 'same-origin')` : text;
19185
+ }
19034
19186
  async _authThenStart() {
19035
19187
  if (!this._authProvider) {
19036
19188
  throw new UnauthorizedError("No auth provider");
@@ -19080,11 +19232,13 @@ var StreamableHTTPClientTransport = class {
19080
19232
  if (resumptionToken) {
19081
19233
  headers.set("last-event-id", resumptionToken);
19082
19234
  }
19083
- const response = await (this._fetch ?? fetch)(this._url, {
19084
- method: "GET",
19085
- headers,
19086
- signal: this._abortController?.signal
19087
- });
19235
+ const fetchFn = this._fetch ?? fetch;
19236
+ const init = { method: "GET", headers, signal: this._abortController?.signal };
19237
+ let response = await fetchFn(this._url, this._followRedirects ? init : { ...init, redirect: "manual" });
19238
+ const followed = this._followRedirects ? void 0 : followWithinOrigin(fetchFn, this._url, init, response);
19239
+ if (followed) {
19240
+ response = await followed;
19241
+ }
19088
19242
  if (!response.ok) {
19089
19243
  await response.body?.cancel();
19090
19244
  if (response.status === 401 && this._authProvider) {
@@ -19093,7 +19247,7 @@ var StreamableHTTPClientTransport = class {
19093
19247
  if (response.status === 405) {
19094
19248
  return;
19095
19249
  }
19096
- throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
19250
+ throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
19097
19251
  }
19098
19252
  this._handleSseStream(response.body, options, true);
19099
19253
  } catch (error2) {
@@ -19256,7 +19410,7 @@ var StreamableHTTPClientTransport = class {
19256
19410
  body: JSON.stringify(message),
19257
19411
  signal: this._abortController?.signal
19258
19412
  };
19259
- const response = await (this._fetch ?? fetch)(this._url, init);
19413
+ const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
19260
19414
  const sessionId = response.headers.get("mcp-session-id");
19261
19415
  if (sessionId) {
19262
19416
  this._sessionId = sessionId;
@@ -19308,7 +19462,7 @@ var StreamableHTTPClientTransport = class {
19308
19462
  return this.send(message);
19309
19463
  }
19310
19464
  }
19311
- throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
19465
+ throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${this._unfollowedRedirect(response, this._url) ?? text}`);
19312
19466
  }
19313
19467
  this._hasCompletedAuthFlow = false;
19314
19468
  this._lastUpscopingHeader = void 0;
@@ -19370,10 +19524,10 @@ var StreamableHTTPClientTransport = class {
19370
19524
  headers,
19371
19525
  signal: this._abortController?.signal
19372
19526
  };
19373
- const response = await (this._fetch ?? fetch)(this._url, init);
19527
+ const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
19374
19528
  await response.body?.cancel();
19375
19529
  if (!response.ok && response.status !== 405) {
19376
- throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
19530
+ throw new StreamableHTTPError(response.status, `Failed to terminate session: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
19377
19531
  }
19378
19532
  this._sessionId = void 0;
19379
19533
  } catch (error2) {
@@ -21,6 +21,6 @@ function tryFastPath(argv, options) {
21
21
 
22
22
  // vendor/chrome-devtools-axi/bin/chrome-devtools-axi.ts
23
23
  if (!tryFastPath(process.argv.slice(2), { version: VERSION })) {
24
- const { main } = await import("./cli-EJ5MQZNA.js");
24
+ const { main } = await import("./cli-GB37NZHQ.js");
25
25
  await main(process.argv.slice(2));
26
26
  }
@@ -1845,8 +1845,8 @@ function isProcessAlive(pid) {
1845
1845
  try {
1846
1846
  process.kill(pid, 0);
1847
1847
  return true;
1848
- } catch {
1849
- return false;
1848
+ } catch (error) {
1849
+ return error?.code !== "ESRCH";
1850
1850
  }
1851
1851
  }
1852
1852
  function httpGet(port, path, timeoutMs = 2e3) {
@@ -50,10 +50,54 @@ tui-use daemon stop
50
50
  - Send an explicit `press enter` after typing when reliable submission matters.
51
51
  - `wait --text` exits non-zero if the requested pattern is absent at the deadline.
52
52
  Read the returned screen and fix the input instead of assuming the wait succeeded.
53
+ Do not rerun the identical failing wait; poll with `--poll` (below) or change the input.
53
54
  - Use `wait --exit` to confirm process termination. Seeing the last output or
54
55
  leaving fullscreen does not by itself mean the process has exited.
55
56
  - Kill only sessions you created. Do not restart or stop a daemon that owns another task's live sessions.
56
57
 
58
+ ## Bounded Polling For Long Tasks
59
+
60
+ For work that legitimately takes longer than one wait, opt in to poll mode:
61
+
62
+ ```text
63
+ tui-use --session <id> wait 20000 --text "DONE_MARKER" --poll --format json
64
+ ```
65
+
66
+ - `wait_state: "satisfied"` (exit 0): the marker appeared, or `--exit` saw the exit.
67
+ With `--exit` this means termination was observed, not that the program
68
+ succeeded: read `exit_code`, which can be nonzero.
69
+ - `wait_state: "pending"` (exit 0): the deadline passed while the process is still
70
+ running. This only means the PTY is alive; it is not evidence of progress.
71
+ - `wait_state: "failed"` (exit 1): the session exited without the `--text` marker.
72
+ Daemon, session and argument errors also exit 1. Never treat these as pending.
73
+
74
+ Set a total budget before polling (for example at most 6 polls or 10 minutes).
75
+ After each pending poll compare the `screen` with the previous poll. If the
76
+ screen is unchanged across two or three polls, or the budget is spent, stop and
77
+ report the task as not progressing, with the last screen, instead of polling on.
78
+ For programs that can emit structured output, prefer that over viewport text.
79
+
80
+ ## Waiting On Pie Tasks
81
+
82
+ To run or supervise a Pie task, use the JSONL protocol over pipes instead of
83
+ driving the Pie TUI through this skill and reading its viewport:
84
+
85
+ ```text
86
+ pie chat --json-events # stdin: one JSON request per line; stdout: one JSON event per line
87
+ ```
88
+
89
+ - Wait for `agent.ready`, then send
90
+ `{"type":"turn.request","turnId":"t1","prompt":"..."}`.
91
+ - The turn outcome is exactly one of `turn.completed`, `turn.failed`,
92
+ `turn.cancelled` or `turn.stalled`, followed by `turn.settled`. Decide from
93
+ these events, not from screen text or the process still running.
94
+ - `tool.started`/`tool.completed` and `turn.retry.*` events are progress
95
+ evidence. Pie emits `turn.stalled` after `PIE_JSON_EVENTS_NO_PROGRESS_MS`
96
+ (default 10 minutes) without activity.
97
+ - `turn.failed` after `turn.retry.exhausted`, or an exhausted-quota/balance
98
+ error, is terminal: report it instead of resubmitting the same prompt.
99
+ - Use `{"type":"turn.interrupt"}` to cancel and `{"type":"shutdown"}` to exit.
100
+
57
101
  ## Host Behavior
58
102
 
59
103
  Windows defaults to native PowerShell with matching invocation arguments;
@@ -10361,8 +10361,8 @@ program2.command("snapshot").description("Return the current rendered screen sna
10361
10361
  handleResponse(res, (r) => printScreen(r, opts.format));
10362
10362
  });
10363
10363
  program2.command("wait [duration]").description(
10364
- 'Wait for screen to change or timeout.\n wait \u2192 wait for screen to change (default 3000ms)\n wait 5000 \u2192 wait for 5000ms\n wait --text "pattern" \u2192 wait until screen contains text\n wait --debounce 300 \u2192 wait until screen is stable for 300ms (default: 100ms)'
10365
- ).option("--text <text>", "Wait until screen contains literal text").option("--regex", "Interpret --text as a regex in a bounded worker").option("--exit", "Wait for process exit instead of just a screen change").option("--debounce <ms>", "Idle time after last change before resolving, in ms (default: 100)", "100").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (duration, opts) => {
10364
+ 'Wait for screen to change or timeout.\n wait \u2192 wait for screen to change (default 3000ms)\n wait 5000 \u2192 wait for 5000ms\n wait --text "pattern" \u2192 wait until screen contains text\n wait --debounce 300 \u2192 wait until screen is stable for 300ms (default: 100ms)\n wait --text "pattern" --poll \u2192 exit 0 with wait_state "pending" if the deadline passes while the session is still running'
10365
+ ).option("--text <text>", "Wait until screen contains literal text").option("--regex", "Interpret --text as a regex in a bounded worker").option("--exit", "Wait for process exit instead of just a screen change").option("--poll", 'Bounded polling: a deadline on a still-running session reports wait_state "pending" and exits 0; an exited session that never matched --text, and errors, exit 1. With --exit, "satisfied" means termination was observed, not success: check exit_code').option("--debounce <ms>", "Idle time after last change before resolving, in ms (default: 100)", "100").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (duration, opts) => {
10366
10366
  let timeoutMs = 3e3;
10367
10367
  if (duration) {
10368
10368
  timeoutMs = Number(duration);
@@ -10380,8 +10380,15 @@ program2.command("wait [duration]").description(
10380
10380
  });
10381
10381
  handleResponse(res, (r) => {
10382
10382
  const screen = r;
10383
- printScreen(screen, opts.format);
10384
- if ((opts.text !== void 0 || opts.exit) && screen.wait_satisfied === false) {
10383
+ const conditional = opts.text !== void 0 || opts.exit;
10384
+ const unmet = conditional && screen.wait_satisfied === false;
10385
+ const pending = !!opts.poll && screen.timed_out === true && screen.status === "running" && (unmet || !conditional);
10386
+ const waitState = opts.poll ? pending ? "pending" : unmet ? "failed" : "satisfied" : void 0;
10387
+ printScreen(screen, opts.format, waitState);
10388
+ if (pending) {
10389
+ process.stderr.write(`Wait pending: ${opts.text ?? (opts.exit ? "process exit" : "screen change")} not seen before timeout; session is still running. Liveness is not evidence of progress: compare screens across polls and stop after a bounded number of unchanged polls.
10390
+ `);
10391
+ } else if (unmet) {
10385
10392
  process.stderr.write(`Wait condition was not met${screen.timed_out ? " before timeout" : " before the session exited"}: ${opts.text ?? "process exit"}
10386
10393
  `);
10387
10394
  process.exitCode = 1;
@@ -10586,7 +10593,7 @@ function inputText(parts, fromStdin) {
10586
10593
  if (parts.length === 0) throw new Error("Provide text arguments or --stdin.");
10587
10594
  return parts.join(" ");
10588
10595
  }
10589
- function printScreen(r, format = "pretty") {
10596
+ function printScreen(r, format = "pretty", waitState) {
10590
10597
  if (format === "json") {
10591
10598
  console.log(
10592
10599
  JSON.stringify(
@@ -10602,7 +10609,8 @@ function printScreen(r, format = "pretty") {
10602
10609
  cols: r.cols,
10603
10610
  rows: r.rows,
10604
10611
  highlights: r.highlights,
10605
- ...r.type === "wait" ? { wait_satisfied: r.wait_satisfied, timed_out: r.timed_out } : {}
10612
+ ...r.type === "wait" ? { wait_satisfied: r.wait_satisfied, timed_out: r.timed_out } : {},
10613
+ ...waitState ? { wait_state: waitState } : {}
10606
10614
  },
10607
10615
  null,
10608
10616
  2
@@ -10612,7 +10620,7 @@ function printScreen(r, format = "pretty") {
10612
10620
  }
10613
10621
  const width = r.cols;
10614
10622
  const header = `\u2500\u2500\u2500 ${r.session_id} ${"\u2500".repeat(Math.max(0, width - r.session_id.length - 5))}`;
10615
- const info = `${r.status} | cursor(${r.cursor.x},${r.cursor.y}) | fullscreen:${r.is_fullscreen} | title:"${r.title || ""}"`;
10623
+ const info = `${r.status} | cursor(${r.cursor.x},${r.cursor.y}) | fullscreen:${r.is_fullscreen} | title:"${r.title || ""}"${waitState ? ` | wait:${waitState}` : ""}`;
10616
10624
  const footer = `\u2500\u2500\u2500 ${info} ${"\u2500".repeat(Math.max(0, width - info.length - 5))}`;
10617
10625
  process.stdout.write(header + "\n");
10618
10626
  for (const line of r.lines) process.stdout.write(line + "\n");
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-LO2V5DZZ.js";
11
+ } from "./chunk-IKLQHWY2.js";
12
12
  import {
13
13
  getBuiltinDir,
14
14
  getConfigDir