@executor-js/sdk 1.5.16 → 1.5.18

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.
@@ -24,7 +24,7 @@ import {
24
24
  isValidPattern,
25
25
  resolveEffectivePolicy,
26
26
  rowToToolPolicy
27
- } from "./chunk-6EED5LAL.js";
27
+ } from "./chunk-PCSRC6WP.js";
28
28
  import {
29
29
  OAUTH2_DEFAULT_TIMEOUT_MS,
30
30
  assertSupportedOAuthEndpointUrl,
@@ -35,9 +35,10 @@ import {
35
35
  exchangeAuthorizationCode,
36
36
  exchangeClientCredentials,
37
37
  providerAuthorizeExtras,
38
+ rebindTokenEndpointHostToCallbackDomain,
38
39
  refreshAccessToken,
39
40
  shouldRefreshToken
40
- } from "./chunk-EIHWBY6T.js";
41
+ } from "./chunk-4XPVLX62.js";
41
42
  import {
42
43
  AuthTemplateSlug,
43
44
  ConnectionAddress,
@@ -549,6 +550,11 @@ var coreTables = defineTables({
549
550
  refresh_item_id: nullableTextColumn("refresh_item_id"),
550
551
  expires_at: nullableBigintColumn("expires_at"),
551
552
  oauth_scope: nullableTextColumn("oauth_scope"),
553
+ // Per-connection token endpoint override. Set only when the code was
554
+ // redeemed at a region other than the oauth_client's configured token host
555
+ // (multi-site providers like Datadog signal the org's region on the
556
+ // callback). Null means refresh uses the oauth_client's `token_url`.
557
+ oauth_token_url: nullableTextColumn("oauth_token_url"),
552
558
  provider_state: nullableJsonColumn("provider_state"),
553
559
  created_at: dateColumn("created_at"),
554
560
  updated_at: dateColumn("updated_at")
@@ -3114,10 +3120,26 @@ var ConnectionOutput = Schema2.Struct({
3114
3120
  });
3115
3121
  var ConnectionsListInput = Schema2.Struct({
3116
3122
  integration: Schema2.optional(Schema2.String),
3117
- owner: Schema2.optional(OwnerSchema)
3123
+ owner: Schema2.optional(OwnerSchema),
3124
+ verbose: Schema2.optional(Schema2.Boolean)
3125
+ });
3126
+ var ConnectionListItem = Schema2.Struct({
3127
+ owner: OwnerSchema,
3128
+ name: Schema2.String,
3129
+ integration: Schema2.String,
3130
+ template: Schema2.String,
3131
+ provider: Schema2.String,
3132
+ address: Schema2.String,
3133
+ identityLabel: Schema2.optional(Schema2.NullOr(Schema2.String)),
3134
+ description: Schema2.optional(Schema2.NullOr(Schema2.String)),
3135
+ expiresAt: Schema2.NullOr(Schema2.Number),
3136
+ oauthClient: Schema2.NullOr(Schema2.String),
3137
+ oauthClientOwner: Schema2.NullOr(OwnerSchema),
3138
+ oauthScopeCount: Schema2.NullOr(Schema2.Number),
3139
+ oauthScope: Schema2.optional(Schema2.NullOr(Schema2.String))
3118
3140
  });
3119
3141
  var ConnectionsListOutput = Schema2.Struct({
3120
- connections: Schema2.Array(ConnectionOutput)
3142
+ connections: Schema2.Array(ConnectionListItem)
3121
3143
  });
3122
3144
  var ConnectionCreateHandoffInput = Schema2.Struct({
3123
3145
  integration: Schema2.String,
@@ -3145,7 +3167,14 @@ var ConnectionCreateInput = Schema2.Struct({
3145
3167
  }).check(
3146
3168
  Schema2.makeFilter((payload) => {
3147
3169
  const originCount = (payload.from === void 0 ? 0 : 1) + (payload.inputs === void 0 ? 0 : 1);
3148
- if (originCount !== 1) return "Expected exactly one provider credential origin";
3170
+ const isNoAuth = String(payload.template) === String(NO_AUTH_TEMPLATE);
3171
+ if (isNoAuth) {
3172
+ if (originCount > 0) {
3173
+ return 'A no-auth connection (template "none") takes no provider credential origin';
3174
+ }
3175
+ } else if (originCount !== 1) {
3176
+ return "Expected exactly one provider credential origin";
3177
+ }
3149
3178
  if (payload.inputs !== void 0 && Object.keys(payload.inputs).length === 0) {
3150
3179
  return "Expected at least one provider credential input";
3151
3180
  }
@@ -3229,9 +3258,23 @@ var OAuthCreateClientInput = Schema2.Struct({
3229
3258
  tokenUrl: Schema2.String,
3230
3259
  grant: OAuthGrantSchema,
3231
3260
  clientId: Schema2.String,
3232
- clientSecret: Schema2.String,
3233
3261
  resource: Schema2.optional(Schema2.NullOr(Schema2.String))
3234
3262
  });
3263
+ var OAuthCreateClientHandoffInput = Schema2.Struct({
3264
+ integration: Schema2.String,
3265
+ owner: Schema2.optional(OwnerSchema),
3266
+ slug: Schema2.optional(Schema2.String),
3267
+ grant: Schema2.optional(OAuthGrantSchema),
3268
+ clientId: Schema2.optional(Schema2.String),
3269
+ authorizationUrl: Schema2.optional(Schema2.String),
3270
+ tokenUrl: Schema2.optional(Schema2.String),
3271
+ resource: Schema2.optional(Schema2.NullOr(Schema2.String)),
3272
+ label: Schema2.optional(Schema2.String)
3273
+ });
3274
+ var OAuthCreateClientHandoffOutput = Schema2.Struct({
3275
+ url: Schema2.String,
3276
+ instructions: Schema2.String
3277
+ });
3235
3278
  var OAuthClientOutputRef = Schema2.Struct({
3236
3279
  client: Schema2.String
3237
3280
  });
@@ -3310,6 +3353,8 @@ var PolicyUpdateInputStd = schemaToStandard(PolicyUpdateInput);
3310
3353
  var PolicyRemoveInputStd = schemaToStandard(PolicyRemoveInput);
3311
3354
  var OAuthClientsListOutputStd = schemaToStandard(OAuthClientsListOutput);
3312
3355
  var OAuthCreateClientInputStd = schemaToStandard(OAuthCreateClientInput);
3356
+ var OAuthCreateClientHandoffInputStd = schemaToStandard(OAuthCreateClientHandoffInput);
3357
+ var OAuthCreateClientHandoffOutputStd = schemaToStandard(OAuthCreateClientHandoffOutput);
3313
3358
  var OAuthClientOutputRefStd = schemaToStandard(OAuthClientOutputRef);
3314
3359
  var OAuthRegisterDynamicInputStd = schemaToStandard(OAuthRegisterDynamicInput);
3315
3360
  var OAuthRemoveClientInputStd = schemaToStandard(OAuthRemoveClientInput);
@@ -3332,6 +3377,22 @@ var connectionToOutput = (connection) => ({
3332
3377
  oauthClientOwner: connection.oauthClientOwner ?? null,
3333
3378
  oauthScope: connection.oauthScope ?? null
3334
3379
  });
3380
+ var oauthScopeCount = (scope) => scope == null ? null : scope.split(/\s+/).filter(Boolean).length;
3381
+ var connectionToListItem = (connection, verbose) => ({
3382
+ owner: connection.owner,
3383
+ name: String(connection.name),
3384
+ integration: String(connection.integration),
3385
+ template: String(connection.template),
3386
+ provider: String(connection.provider),
3387
+ address: String(connection.address),
3388
+ identityLabel: connection.identityLabel ?? null,
3389
+ description: connection.description ?? null,
3390
+ expiresAt: connection.expiresAt ?? null,
3391
+ oauthClient: connection.oauthClient == null ? null : String(connection.oauthClient),
3392
+ oauthClientOwner: connection.oauthClientOwner ?? null,
3393
+ oauthScopeCount: oauthScopeCount(connection.oauthScope),
3394
+ ...verbose ? { oauthScope: connection.oauthScope ?? null } : {}
3395
+ });
3335
3396
  var toolToOutput = (toolRow) => ({
3336
3397
  address: String(toolRow.address),
3337
3398
  owner: toolRow.owner,
@@ -3379,12 +3440,28 @@ var createConnectionInputFromTool = (input) => {
3379
3440
  )
3380
3441
  };
3381
3442
  };
3382
- var connectionCreateHandoffUrl = (webBaseUrl, input) => {
3443
+ var connectionCreateHandoffUrl = (webBaseUrl, orgSlug, input) => {
3383
3444
  const search = new URLSearchParams({ addAccount: "1" });
3384
3445
  if (input.owner !== void 0) search.set("owner", input.owner);
3385
3446
  if (input.template !== void 0) search.set("template", input.template);
3386
3447
  if (input.label !== void 0) search.set("label", input.label);
3387
- const path = `/integrations/${encodeURIComponent(input.integration)}?${search.toString()}`;
3448
+ const orgPrefix = orgSlug !== void 0 && orgSlug.length > 0 ? `/${orgSlug}` : "";
3449
+ const path = `${orgPrefix}/integrations/${encodeURIComponent(input.integration)}?${search.toString()}`;
3450
+ if (webBaseUrl === void 0 || webBaseUrl.length === 0) return path;
3451
+ return new URL(path, webBaseUrl.endsWith("/") ? webBaseUrl : `${webBaseUrl}/`).toString();
3452
+ };
3453
+ var oauthClientCreateHandoffUrl = (webBaseUrl, orgSlug, input) => {
3454
+ const search = new URLSearchParams({ addAccount: "1", oauthClient: "1" });
3455
+ if (input.owner !== void 0) search.set("owner", input.owner);
3456
+ if (input.slug !== void 0) search.set("clientSlug", input.slug);
3457
+ if (input.grant !== void 0) search.set("grant", input.grant);
3458
+ if (input.clientId !== void 0) search.set("clientId", input.clientId);
3459
+ if (input.authorizationUrl !== void 0) search.set("authorizationUrl", input.authorizationUrl);
3460
+ if (input.tokenUrl !== void 0) search.set("tokenUrl", input.tokenUrl);
3461
+ if (input.resource != null && input.resource.length > 0) search.set("resource", input.resource);
3462
+ if (input.label !== void 0) search.set("label", input.label);
3463
+ const orgPrefix = orgSlug !== void 0 && orgSlug.length > 0 ? `/${orgSlug}` : "";
3464
+ const path = `${orgPrefix}/integrations/${encodeURIComponent(input.integration)}?${search.toString()}`;
3388
3465
  if (webBaseUrl === void 0 || webBaseUrl.length === 0) return path;
3389
3466
  return new URL(path, webBaseUrl.endsWith("/") ? webBaseUrl : `${webBaseUrl}/`).toString();
3390
3467
  };
@@ -3430,7 +3507,7 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3430
3507
  }),
3431
3508
  tool({
3432
3509
  name: "connections.list",
3433
- description: "List saved connections (the credential for one integration). Never returns the credential value. Optionally filter by integration or owner.",
3510
+ description: "List saved connections (the credential for one integration). Never returns the credential value. Optionally filter by integration or owner. OAuth scopes are summarized as `oauthScopeCount` by default; pass `verbose: true` to include the full `oauthScope` grant string per connection.",
3434
3511
  inputSchema: ConnectionsListInputStd,
3435
3512
  outputSchema: ConnectionsListOutputStd,
3436
3513
  execute: (input, { ctx }) => Effect5.map(
@@ -3439,15 +3516,24 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3439
3516
  owner: input.owner === void 0 ? void 0 : input.owner
3440
3517
  }),
3441
3518
  (connections) => ({
3442
- connections: connections.map(connectionToOutput)
3519
+ connections: connections.map(
3520
+ (connection) => connectionToListItem(connection, input.verbose === true)
3521
+ )
3443
3522
  })
3444
3523
  )
3445
3524
  }),
3446
3525
  tool({
3447
3526
  name: "connections.create",
3448
- description: "Low-level create or replace for a saved connection from provider item references. For normal API keys/tokens, use `connections.createHandoff` so the user enters the credential in the web UI. OAuth credentials should use `oauth.start`.",
3527
+ description: 'Low-level create or replace for a saved connection from provider item references. For a no-auth integration (public MCP server, public REST API), pass `template: "none"` with no `from`/`inputs` to wire it up directly. For normal API keys/tokens, use `connections.createHandoff` so the user enters the credential in the web UI. OAuth credentials should use `oauth.start`.',
3449
3528
  inputSchema: ConnectionCreateInputStd,
3450
3529
  outputSchema: ConnectionOutputStd,
3530
+ // Creating a connection binds a credential reference and roots a new
3531
+ // tool catalog: every tool that connection produces then becomes
3532
+ // callable. Even the no-auth (`template: "none"`) path pulls tools
3533
+ // from an arbitrary endpoint. Prompt-injected code could silently
3534
+ // wire an attacker-chosen integration or credential, so this is
3535
+ // approval-gated (the v1 `sources.configure` carried the same guard).
3536
+ annotations: { requiresApproval: true },
3451
3537
  execute: (input, { ctx }) => Effect5.map(
3452
3538
  ctx.connections.create(createConnectionInputFromTool(input)),
3453
3539
  connectionToOutput
@@ -3459,7 +3545,7 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3459
3545
  inputSchema: ConnectionCreateHandoffInputStd,
3460
3546
  outputSchema: ConnectionCreateHandoffOutputStd,
3461
3547
  execute: (input) => {
3462
- const url = connectionCreateHandoffUrl(options.webBaseUrl, input);
3548
+ const url = connectionCreateHandoffUrl(options.webBaseUrl, options.orgSlug, input);
3463
3549
  return Effect5.succeed({
3464
3550
  url,
3465
3551
  instructions: "Ask the user to open this URL and add the account in the Executor web UI. Do not ask them to paste the credential value into chat. After they finish, call connections.list for the integration to discover the created connection."
@@ -3471,6 +3557,10 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3471
3557
  description: "Remove a saved connection and its produced tools by owner, integration, and connection name.",
3472
3558
  inputSchema: ConnectionRefInputStd,
3473
3559
  outputSchema: RemovedOutputStd,
3560
+ // Deleting a connection drops it and every tool it produced, which
3561
+ // prompt-injected code could use to disrupt an integration or force a
3562
+ // re-add flow. Approval-gated, matching v1 `sources.remove`.
3563
+ annotations: { requiresApproval: true },
3474
3564
  execute: (input, { ctx }) => Effect5.map(ctx.connections.remove(connectionRefFromInput(input)), () => ({
3475
3565
  removed: true
3476
3566
  }))
@@ -3480,6 +3570,11 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3480
3570
  description: "Re-run an integration's tool production for a saved connection, replacing that connection's persisted tools.",
3481
3571
  inputSchema: ConnectionRefInputStd,
3482
3572
  outputSchema: ConnectionsRefreshOutputStd,
3573
+ // Refresh replaces a connection's persisted tool set; for a mutable
3574
+ // upstream (an MCP server whose catalog can change) this can swap in
3575
+ // different tools without confirmation. Approval-gated, matching v1
3576
+ // `sources.refresh`.
3577
+ annotations: { requiresApproval: true },
3483
3578
  execute: (input, { ctx }) => Effect5.map(ctx.connections.refresh(connectionRefFromInput(input)), (tools) => ({
3484
3579
  tools: tools.map(toolToOutput)
3485
3580
  }))
@@ -3523,9 +3618,20 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3523
3618
  }),
3524
3619
  tool({
3525
3620
  name: "oauth.clients.create",
3526
- description: "Register or replace an owner-scoped OAuth client from explicit client credentials. Use grant `client_credentials` for machine OAuth or `authorization_code` for browser consent flows.",
3621
+ description: "Register or replace an owner-scoped OAuth client WITHOUT a client secret: a PUBLIC client (PKCE / authorization_code) or a discovery-prefill placeholder. To register a CONFIDENTIAL client that has a secret, call `oauth.clients.createHandoff` instead so the human enters the secret in the web UI; never pass a client secret through this tool.",
3527
3622
  inputSchema: OAuthCreateClientInputStd,
3528
3623
  outputSchema: OAuthClientOutputRefStd,
3624
+ // This persists an OAuth client and REPLACES on slug collision. It
3625
+ // takes NO client secret: a secret would have to travel through the
3626
+ // agent's context window, so a confidential app is registered by the
3627
+ // human via `oauth.clients.createHandoff`. An empty secret registers a
3628
+ // PUBLIC client. The remaining risk is the write itself: prompt-injected
3629
+ // code could register a client with an attacker-controlled
3630
+ // authorizationUrl/tokenUrl, then drive `oauth.start` to mint a
3631
+ // connection and route the user's tokens to the attacker. The
3632
+ // highest-value gate here; matches v1 `sources.bindings.set`, which
3633
+ // guarded credential writes.
3634
+ annotations: { requiresApproval: true },
3529
3635
  execute: (input, { ctx }) => Effect5.map(
3530
3636
  ctx.oauth.createClient({
3531
3637
  owner: input.owner,
@@ -3534,17 +3640,40 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3534
3640
  tokenUrl: input.tokenUrl,
3535
3641
  grant: input.grant,
3536
3642
  clientId: input.clientId,
3537
- clientSecret: input.clientSecret,
3643
+ // No secret crosses the agent boundary; an empty secret registers
3644
+ // a public client. Confidential clients go through
3645
+ // `oauth.clients.createHandoff`.
3646
+ clientSecret: "",
3538
3647
  resource: input.resource ?? null
3539
3648
  }),
3540
3649
  (client) => ({ client: String(client) })
3541
3650
  )
3542
3651
  }),
3652
+ tool({
3653
+ name: "oauth.clients.createHandoff",
3654
+ description: "Return a browser URL that opens the Register-OAuth-app form for one integration, pre-filled with the non-secret fields (client id, endpoints, grant). Use this for any CONFIDENTIAL OAuth app: the user types the client secret directly in the web UI instead of sending it through the agent. After they register the app, call `oauth.clients.list` to discover its owner and slug, then `oauth.start`.",
3655
+ inputSchema: OAuthCreateClientHandoffInputStd,
3656
+ outputSchema: OAuthCreateClientHandoffOutputStd,
3657
+ // Pure URL builder: no DB write, no token, no secret. This is the SAFE
3658
+ // path (it routes the secret to the human in the browser), so it is
3659
+ // deliberately NOT approval-gated, mirroring `connections.createHandoff`.
3660
+ execute: (input) => {
3661
+ const url = oauthClientCreateHandoffUrl(options.webBaseUrl, options.orgSlug, input);
3662
+ return Effect5.succeed({
3663
+ url,
3664
+ instructions: "Ask the user to open this URL and register the OAuth app in the Executor web UI, entering the client secret there. Do not ask them to paste the client secret into chat. After they finish, call oauth.clients.list to find the registered client (owner + slug), then oauth.start."
3665
+ });
3666
+ }
3667
+ }),
3543
3668
  tool({
3544
3669
  name: "oauth.clients.registerDynamic",
3545
3670
  description: "Register an OAuth client through RFC 7591 Dynamic Client Registration and save the minted client for later `oauth.start` calls.",
3546
3671
  inputSchema: OAuthRegisterDynamicInputStd,
3547
3672
  outputSchema: OAuthClientOutputRefStd,
3673
+ // Same risk class as `oauth.clients.create`: registers a client at a
3674
+ // caller-supplied endpoint and persists the minted credentials for
3675
+ // later `oauth.start` abuse. Approval-gated. See `oauth.clients.create`.
3676
+ annotations: { requiresApproval: true },
3548
3677
  execute: (input, { ctx }) => Effect5.map(
3549
3678
  ctx.oauth.registerDynamicClient({
3550
3679
  owner: input.owner,
@@ -3567,6 +3696,11 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3567
3696
  description: "Remove an owner-scoped OAuth client by owner and slug. Existing connections are not cascaded.",
3568
3697
  inputSchema: OAuthRemoveClientInputStd,
3569
3698
  outputSchema: RemovedOutputStd,
3699
+ // Removing a client breaks token refresh for every connection that
3700
+ // depends on it (a silent DoS) and can force re-auth through an
3701
+ // attacker-supplied replacement. Approval-gated, matching v1
3702
+ // `sources.bindings.remove`.
3703
+ annotations: { requiresApproval: true },
3570
3704
  execute: (input, { ctx }) => Effect5.map(
3571
3705
  ctx.oauth.removeClient(input.owner, OAuthClientSlug.make(input.slug)),
3572
3706
  () => ({ removed: true })
@@ -3591,6 +3725,14 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3591
3725
  description: "Start OAuth through a registered client to mint a connection for an integration. `client_credentials` clients return `connected`; authorization-code clients return an authorization URL and state.",
3592
3726
  inputSchema: OAuthStartInputStd,
3593
3727
  outputSchema: OAuthStartOutputStd,
3728
+ // This is the materialization step that turns a registered client
3729
+ // into a live connection. For `client_credentials` it completes
3730
+ // synchronously (status `connected`) with no browser step, so a
3731
+ // prompt-injected call against an attacker-registered client mints a
3732
+ // credentialed connection with no human in the loop. The
3733
+ // authorization-code path already returns a URL the user must visit,
3734
+ // but one gate on the whole tool covers the silent path cleanly.
3735
+ annotations: { requiresApproval: true },
3594
3736
  execute: (input, { ctx }) => Effect5.map(
3595
3737
  ctx.oauth.start({
3596
3738
  client: OAuthClientSlug.make(input.client),
@@ -3638,6 +3780,11 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3638
3780
  description: "Create a tool policy. `pattern` matches a tool address tail (`integration.connection.tool`, `integration.*`, `*`); `action` is approve/require_approval/block. `owner` is org (workspace guardrail) or user (personal).",
3639
3781
  inputSchema: PolicyCreateInputStd,
3640
3782
  outputSchema: PolicyOutputStd,
3783
+ // A policy decides which tools run without confirmation, so creating
3784
+ // one can silence every other approval gate (e.g. `approve *`). It
3785
+ // must itself require approval, otherwise prompt-injected code could
3786
+ // disable approvals by writing its own bypass policy.
3787
+ annotations: { requiresApproval: true },
3641
3788
  execute: (input, { ctx }) => Effect5.map(
3642
3789
  ctx.core.policies.create({
3643
3790
  owner: input.owner,
@@ -3658,6 +3805,10 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3658
3805
  description: "Update a tool policy's pattern and/or action by id + owner.",
3659
3806
  inputSchema: PolicyUpdateInputStd,
3660
3807
  outputSchema: PolicyOutputStd,
3808
+ // Editing a policy can broaden a pattern or flip an action to
3809
+ // `approve`, weakening an approval gate just as creation can, so it
3810
+ // requires approval too. See `policies.create`.
3811
+ annotations: { requiresApproval: true },
3661
3812
  execute: (input, { ctx }) => Effect5.map(
3662
3813
  ctx.core.policies.update({
3663
3814
  id: input.id,
@@ -3679,6 +3830,10 @@ var coreToolsPlugin = definePlugin((options = {}) => ({
3679
3830
  description: "Remove a tool policy by id + owner.",
3680
3831
  inputSchema: PolicyRemoveInputStd,
3681
3832
  outputSchema: RemovedOutputStd,
3833
+ // Removing a policy can drop a `block` or `require_approval`
3834
+ // guardrail, so deletion is also approval-gated. See
3835
+ // `policies.create`.
3836
+ annotations: { requiresApproval: true },
3682
3837
  execute: (input, { ctx }) => Effect5.map(
3683
3838
  ctx.core.policies.remove({
3684
3839
  id: input.id,
@@ -4339,7 +4494,9 @@ var makeOAuthService = (deps) => {
4339
4494
  client,
4340
4495
  token,
4341
4496
  requestedScopes,
4342
- input.clientOwner
4497
+ input.clientOwner,
4498
+ // client_credentials has no callback, so no regional rebind applies.
4499
+ null
4343
4500
  ).pipe(
4344
4501
  Effect7.mapError(
4345
4502
  (cause) => new OAuthStartError({
@@ -4452,8 +4609,12 @@ var makeOAuthService = (deps) => {
4452
4609
  restartRequired: true
4453
4610
  });
4454
4611
  }
4612
+ const tokenUrl = rebindTokenEndpointHostToCallbackDomain(
4613
+ client.tokenUrl,
4614
+ input.callbackDomain
4615
+ );
4455
4616
  const token = yield* exchangeAuthorizationCode({
4456
- tokenUrl: client.tokenUrl,
4617
+ tokenUrl,
4457
4618
  clientId: client.clientId,
4458
4619
  clientSecret: client.clientSecret,
4459
4620
  redirectUrl: session.redirectUrl,
@@ -4483,7 +4644,10 @@ var makeOAuthService = (deps) => {
4483
4644
  // The scopes `start` requested (the integration's declared set), persisted
4484
4645
  // on the session. Empty only for a corrupt/legacy session with no payload.
4485
4646
  session.requestedScopes ?? [],
4486
- session.clientOwner
4647
+ session.clientOwner,
4648
+ // Persist the regional token endpoint ONLY when it differs from the
4649
+ // client's configured one, so refresh redeems against the same region.
4650
+ tokenUrl === client.tokenUrl ? null : tokenUrl
4487
4651
  ).pipe(
4488
4652
  Effect7.mapError(
4489
4653
  (cause) => new OAuthCompleteError({
@@ -4496,7 +4660,7 @@ var makeOAuthService = (deps) => {
4496
4660
  yield* deleteSession(input.state);
4497
4661
  return connection;
4498
4662
  });
4499
- const mintFromToken = (target, client, token, requestedScopes, clientOwner) => Effect7.gen(function* () {
4663
+ const mintFromToken = (target, client, token, requestedScopes, clientOwner, oauthTokenUrl) => Effect7.gen(function* () {
4500
4664
  const provider = deps.defaultWritableProvider();
4501
4665
  if (!provider || !provider.set) {
4502
4666
  return yield* new StorageError({
@@ -4527,7 +4691,8 @@ var makeOAuthService = (deps) => {
4527
4691
  // Microsoft, issue a refresh token for `offline_access` but omit that
4528
4692
  // non-resource scope from the token `scope` string, so preserve it when
4529
4693
  // the refresh token proves it was granted.
4530
- oauthScope: recordedOAuthScope(token, requestedScopes)
4694
+ oauthScope: recordedOAuthScope(token, requestedScopes),
4695
+ oauthTokenUrl
4531
4696
  });
4532
4697
  });
4533
4698
  const deleteSession = (state) => deps.fuma.use(
@@ -5128,6 +5293,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
5128
5293
  const plugins = config.coreTools ? [
5129
5294
  coreToolsPlugin({
5130
5295
  webBaseUrl: config.coreTools.webBaseUrl,
5296
+ orgSlug: config.coreTools.orgSlug,
5131
5297
  includeProviders: config.coreTools.includeProviders
5132
5298
  }),
5133
5299
  ...userPlugins
@@ -5249,8 +5415,9 @@ var createExecutor = (config) => Effect8.gen(function* () {
5249
5415
  }
5250
5416
  const clientSecret = clientRow.client_secret_item_id ? (yield* provider.get(ProviderItemId.make(String(clientRow.client_secret_item_id)))) ?? "" : "";
5251
5417
  const grantedScopes = row.oauth_scope ? String(row.oauth_scope).split(/\s+/).filter(Boolean) : [];
5418
+ const tokenUrl = row.oauth_token_url ? String(row.oauth_token_url) : String(clientRow.token_url);
5252
5419
  const token = String(clientRow.grant) === "client_credentials" ? yield* exchangeClientCredentials({
5253
- tokenUrl: String(clientRow.token_url),
5420
+ tokenUrl,
5254
5421
  clientId: String(clientRow.client_id),
5255
5422
  clientSecret,
5256
5423
  scopes: grantedScopes,
@@ -5277,7 +5444,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
5277
5444
  return yield* reauth("Stored refresh token could not be resolved.");
5278
5445
  }
5279
5446
  return yield* refreshAccessToken({
5280
- tokenUrl: String(clientRow.token_url),
5447
+ tokenUrl,
5281
5448
  clientId: String(clientRow.client_id),
5282
5449
  clientSecret,
5283
5450
  refreshToken,
@@ -5817,6 +5984,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
5817
5984
  refresh_item_id: input.refreshItemId,
5818
5985
  expires_at: input.expiresAt,
5819
5986
  oauth_scope: input.oauthScope,
5987
+ oauth_token_url: input.oauthTokenUrl ?? null,
5820
5988
  updated_at: now
5821
5989
  };
5822
5990
  if (existing) {
@@ -5847,6 +6015,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
5847
6015
  refresh_item_id: input.refreshItemId,
5848
6016
  expires_at: input.expiresAt,
5849
6017
  oauth_scope: input.oauthScope,
6018
+ oauth_token_url: input.oauthTokenUrl ?? null,
5850
6019
  provider_state: null,
5851
6020
  created_at: now,
5852
6021
  updated_at: now
@@ -5874,6 +6043,7 @@ var createExecutor = (config) => Effect8.gen(function* () {
5874
6043
  refresh_item_id: input.refreshItemId,
5875
6044
  expires_at: input.expiresAt,
5876
6045
  oauth_scope: input.oauthScope,
6046
+ oauth_token_url: input.oauthTokenUrl ?? null,
5877
6047
  provider_state: null,
5878
6048
  created_at: now,
5879
6049
  updated_at: now
@@ -6685,4 +6855,4 @@ export {
6685
6855
  collectTables,
6686
6856
  createExecutor
6687
6857
  };
6688
- //# sourceMappingURL=chunk-VICUTMT6.js.map
6858
+ //# sourceMappingURL=chunk-UWBP7WLB.js.map