@agent-native/dispatch 0.27.9 → 0.27.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-gateway.d.ts","sourceRoot":"","sources":["../../../src/server/lib/mcp-gateway.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-gateway.d.ts","sourceRoot":"","sources":["../../../src/server/lib/mcp-gateway.ts"],"names":[],"mappings":"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"}
@@ -462,18 +462,6 @@ function workspaceSsoOriginForApp(appId) {
462
462
  const custom = parseWorkspaceSsoAppRegistrations(process.env.IDENTITY_SSO_APP_REGISTRY_JSON).find((registration) => registration.appId === appId);
463
463
  return custom?.origin ?? null;
464
464
  }
465
- function workspaceSsoGatewayOrigin() {
466
- const configured = process.env.WORKSPACE_GATEWAY_URL?.trim();
467
- if (!configured)
468
- return null;
469
- try {
470
- return new URL(configured).origin;
471
- }
472
- catch {
473
- // coercion-ok: malformed deployment configuration is not a trusted origin.
474
- return null;
475
- }
476
- }
477
465
  function isWorkspaceSsoCanonicalApp(app) {
478
466
  const origin = safeAppOrigin(app);
479
467
  if (!origin)
@@ -488,12 +476,7 @@ function isWorkspaceSsoCanonicalApp(app) {
488
476
  ? Object.prototype.hasOwnProperty.call(CANONICAL_WORKSPACE_SSO_APP_ORIGINS, app.id)
489
477
  : false;
490
478
  }
491
- function sameWorkspaceApp(candidate, mounted) {
492
- if (candidate.id !== mounted.id || !mounted.url)
493
- return false;
494
- return candidate.url.replace(/\/+$/, "") === mounted.url.replace(/\/+$/, "");
495
- }
496
- async function isEligibleWorkspaceSsoApp(candidate, mountedApps) {
479
+ async function isEligibleWorkspaceSsoApp(candidate) {
497
480
  const origin = safeAppOrigin(candidate);
498
481
  if (!origin)
499
482
  return false;
@@ -502,18 +485,7 @@ async function isEligibleWorkspaceSsoApp(candidate, mountedApps) {
502
485
  const customOrigin = workspaceSsoOriginForApp(candidate.id);
503
486
  if (customOrigin === origin)
504
487
  return true;
505
- const dispatchOrigin = safeAppOrigin({
506
- id: DISPATCH_APP_ID,
507
- name: DISPATCH_NAME,
508
- description: DISPATCH_DESCRIPTION,
509
- url: dispatchSelfBaseUrl(),
510
- color: DISPATCH_COLOR,
511
- granted: true,
512
- });
513
- const trustedMountedOrigin = workspaceSsoGatewayOrigin() ?? dispatchOrigin;
514
- if (origin !== trustedMountedOrigin)
515
- return false;
516
- return mountedApps.some((mounted) => sameWorkspaceApp(candidate, mounted));
488
+ return false;
517
489
  }
518
490
  async function listWorkspaceSsoApps() {
519
491
  const [agents, mountedApps] = await Promise.all([
@@ -533,8 +505,8 @@ async function listWorkspaceSsoApps() {
533
505
  });
534
506
  }
535
507
  // The hosted Dispatch database is separate from the shared Workspace
536
- // database. Overlay the live mounted registry so custom apps are eligible
537
- // without copying a stale app list into Dispatch configuration.
508
+ // database. Overlay the live mounted registry so custom apps remain
509
+ // discoverable without copying a stale app list into Dispatch configuration.
538
510
  for (const app of mountedApps) {
539
511
  if (app.isDispatch || !app.url)
540
512
  continue;
@@ -550,7 +522,7 @@ async function listWorkspaceSsoApps() {
550
522
  const candidates = [...candidatesById.values()];
551
523
  const eligible = [];
552
524
  for (const candidate of candidates) {
553
- if (await isEligibleWorkspaceSsoApp(candidate, mountedApps)) {
525
+ if (await isEligibleWorkspaceSsoApp(candidate)) {
554
526
  eligible.push(candidate);
555
527
  }
556
528
  }
@@ -750,6 +722,31 @@ function isRetryableTargetMcpError(error) {
750
722
  }
751
723
  return /streamable http|handshake|failed to fetch|fetch failed|networkerror|econnrefused|enotfound|timed out|timeout|502|503|504/i.test(message);
752
724
  }
725
+ function isTargetMcpAuthError(error) {
726
+ const message = error instanceof Error
727
+ ? error.message
728
+ : typeof error === "string"
729
+ ? error
730
+ : String(error ?? "");
731
+ return /\b401\b|\b403\b|unauthorized|forbidden|invalid(?: or expired)? (?:a2a )?token|authentication required/i.test(message);
732
+ }
733
+ function targetMcpErrorStatus(error) {
734
+ const message = error instanceof Error
735
+ ? error.message
736
+ : typeof error === "string"
737
+ ? error
738
+ : String(error ?? "");
739
+ const status = message.match(/\b([45]\d{2})\b/)?.[1];
740
+ return status ? Number(status) : undefined;
741
+ }
742
+ function targetMcpRequestDetails(input) {
743
+ const targetUrl = new URL(input.url);
744
+ return {
745
+ app: input.app.id,
746
+ targetOrigin: targetUrl.origin,
747
+ targetPath: targetUrl.pathname,
748
+ };
749
+ }
753
750
  function targetMcpRetryDelay(attempt) {
754
751
  const base = TARGET_EMBED_SESSION_RETRY_BASE_MS * Math.pow(2, Math.max(0, attempt - 1));
755
752
  return base + Math.floor(Math.random() * 100);
@@ -789,6 +786,42 @@ async function callTargetCreateEmbedSession(input) {
789
786
  }
790
787
  }
791
788
  }
789
+ async function createTargetMcpTokenAttempts(input) {
790
+ const attempts = [];
791
+ const addAttempt = async (tokenInput) => {
792
+ const token = await signA2AToken(input.ownerEmail, input.orgDomain, tokenInput.secret, {
793
+ expiresIn: "5m",
794
+ audience: canonicalA2AAudience(appBaseUrl(input.target)),
795
+ preferGlobalSecret: tokenInput.preferGlobalSecret,
796
+ });
797
+ if (!attempts.some((attempt) => attempt.token === token)) {
798
+ attempts.push({ token, strategy: tokenInput.strategy });
799
+ }
800
+ };
801
+ if (input.orgDomain && input.orgSecret) {
802
+ await addAttempt({
803
+ strategy: "org",
804
+ secret: input.orgSecret,
805
+ preferGlobalSecret: false,
806
+ });
807
+ // A target app may not have the org secret synced yet. The shared secret
808
+ // is a bounded compatibility fallback, used only after the target rejects
809
+ // the org-signed request and never after a non-authentication failure.
810
+ if (process.env.A2A_SECRET?.trim()) {
811
+ await addAttempt({
812
+ strategy: "global",
813
+ preferGlobalSecret: true,
814
+ });
815
+ }
816
+ }
817
+ else {
818
+ await addAttempt({
819
+ strategy: "global",
820
+ preferGlobalSecret: true,
821
+ });
822
+ }
823
+ return attempts;
824
+ }
792
825
  async function resolveEmbedTarget(input, options) {
793
826
  const explicitApp = input.app?.trim()
794
827
  ? await options.resolveApp(input.app)
@@ -897,28 +930,75 @@ async function createEmbedSessionForResolvedApp(input) {
897
930
  : [null, null];
898
931
  const usableOrgSecret = typeof orgSecret === "string" && orgSecret.trim().length > 0;
899
932
  const usableOrgDomain = typeof orgDomain === "string" && orgDomain.trim().length > 0;
900
- const useOrgSigning = usableOrgDomain && usableOrgSecret;
901
933
  const signedOrgDomain = usableOrgDomain ? orgDomain.trim() : undefined;
902
- const token = await signA2AToken(ownerEmail, signedOrgDomain, useOrgSigning ? orgSecret.trim() : undefined, {
903
- expiresIn: "5m",
904
- audience: canonicalA2AAudience(appBaseUrl(target.app)),
905
- // Prefer the synced org A2A secret when present because first-party
906
- // production apps do not have to share the same deployment env secret.
907
- // Fall back to the global A2A_SECRET for orgs that have not synced yet.
908
- preferGlobalSecret: !useOrgSigning,
934
+ const tokenAttempts = await createTargetMcpTokenAttempts({
935
+ ownerEmail,
936
+ orgDomain: signedOrgDomain,
937
+ orgSecret: usableOrgSecret ? orgSecret.trim() : undefined,
938
+ target: target.app,
909
939
  });
910
- const result = await callTargetCreateEmbedSession({
940
+ const targetDetails = targetMcpRequestDetails({
911
941
  app: target.app,
912
- token,
913
942
  url: target.url,
914
- chrome,
915
943
  });
916
- const parsed = parseMcpToolTextResult(result);
944
+ let parsed = null;
945
+ let lastError;
946
+ for (let attemptIndex = 0; attemptIndex < tokenAttempts.length; attemptIndex++) {
947
+ const tokenAttempt = tokenAttempts[attemptIndex];
948
+ console.info("[dispatch] workspace embed target request", {
949
+ ...targetDetails,
950
+ authStrategy: tokenAttempt.strategy,
951
+ attempt: attemptIndex + 1,
952
+ attempts: tokenAttempts.length,
953
+ });
954
+ try {
955
+ const result = await callTargetCreateEmbedSession({
956
+ app: target.app,
957
+ token: tokenAttempt.token,
958
+ url: target.url,
959
+ chrome,
960
+ });
961
+ parsed = parseMcpToolTextResult(result);
962
+ break;
963
+ }
964
+ catch (error) {
965
+ lastError = error;
966
+ console.warn("[dispatch] workspace embed target response", {
967
+ ...targetDetails,
968
+ authStrategy: tokenAttempt.strategy,
969
+ attempt: attemptIndex + 1,
970
+ status: targetMcpErrorStatus(error),
971
+ category: isTargetMcpAuthError(error)
972
+ ? "authentication"
973
+ : isRetryableTargetMcpError(error)
974
+ ? "transient"
975
+ : "permanent",
976
+ });
977
+ if (!isTargetMcpAuthError(error) ||
978
+ attemptIndex >= tokenAttempts.length - 1) {
979
+ throw error;
980
+ }
981
+ }
982
+ }
983
+ if (!parsed) {
984
+ throw lastError instanceof Error
985
+ ? lastError
986
+ : new Error("Target app did not return an embed session.");
987
+ }
917
988
  if (!parsed.startUrl) {
918
989
  throw new Error("Target app did not return an embed start URL.");
919
990
  }
991
+ const startUrl = resolveGrantedAppEmbedStartUrl(target.app, parsed.startUrl);
992
+ const startUrlDetails = new URL(startUrl);
993
+ console.info("[dispatch] workspace embed target minted session", {
994
+ ...targetDetails,
995
+ startPath: startUrlDetails.pathname,
996
+ returnedTicket: startUrlDetails.searchParams.has("ticket"),
997
+ returnedTargetPath: typeof parsed.targetPath === "string",
998
+ returnedExpiry: typeof parsed.expiresAt === "number",
999
+ });
920
1000
  const output = {
921
- startUrl: resolveGrantedAppEmbedStartUrl(target.app, parsed.startUrl),
1001
+ startUrl,
922
1002
  app: target.app.id,
923
1003
  };
924
1004
  if (parsed.targetPath)
@@ -929,14 +1009,31 @@ async function createEmbedSessionForResolvedApp(input) {
929
1009
  }
930
1010
  export async function createWorkspaceSsoEmbedSession(input) {
931
1011
  const ownerEmail = getRequestUserEmail();
932
- if (!ownerEmail)
1012
+ if (!ownerEmail) {
1013
+ console.warn("[dispatch] workspace embed mint rejected", {
1014
+ phase: "dispatch-auth",
1015
+ requestedApp: input.app ?? null,
1016
+ hasPath: typeof input.path === "string",
1017
+ hasUrl: typeof input.url === "string",
1018
+ ownerResolved: false,
1019
+ orgResolved: Boolean(getRequestOrgId()),
1020
+ });
933
1021
  throw new Error("no authenticated user");
1022
+ }
934
1023
  const enabled = await isFeatureFlagEnabled(DISPATCH_WORKSPACE_SSO_FLAG, {
935
1024
  userEmail: ownerEmail,
936
1025
  userKey: ownerEmail,
937
1026
  orgId: getRequestOrgId(),
938
1027
  });
939
1028
  if (!enabled) {
1029
+ console.warn("[dispatch] workspace embed mint rejected", {
1030
+ phase: "feature-flag",
1031
+ requestedApp: input.app ?? null,
1032
+ hasPath: typeof input.path === "string",
1033
+ hasUrl: typeof input.url === "string",
1034
+ ownerResolved: true,
1035
+ orgResolved: Boolean(getRequestOrgId()),
1036
+ });
940
1037
  throw new Error("Dispatch workspace sign-in is not enabled.");
941
1038
  }
942
1039
  const target = await resolveWorkspaceSsoEmbedTarget(input);