@sentry/junior 0.100.0 → 0.101.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.
Files changed (38) hide show
  1. package/README.md +0 -1
  2. package/dist/api/conversations/schema.d.ts +2 -2
  3. package/dist/api/schema.js +3 -3
  4. package/dist/api.js +11 -11
  5. package/dist/app.js +105 -55
  6. package/dist/chat/agent/request.d.ts +1 -1
  7. package/dist/chat/conversations/store.d.ts +1 -1
  8. package/dist/chat/mcp/auth-store.d.ts +1 -0
  9. package/dist/chat/mcp/client.d.ts +2 -0
  10. package/dist/chat/mcp/oauth-provider.d.ts +4 -1
  11. package/dist/chat/mcp/oauth.d.ts +3 -1
  12. package/dist/chat/mcp/tool-manager.d.ts +2 -0
  13. package/dist/chat/services/mcp-auth-orchestration.d.ts +1 -1
  14. package/dist/chat/services/pending-auth.d.ts +17 -9
  15. package/dist/chat/state/conversation.d.ts +7 -3
  16. package/dist/{chunk-VY7BT4SM.js → chunk-5VGJJSSV.js} +4 -3
  17. package/dist/{chunk-IDZBM3NW.js → chunk-DBPLXSBK.js} +1 -1
  18. package/dist/{chunk-2T7DCRAW.js → chunk-EBFJOOUE.js} +1 -1
  19. package/dist/{chunk-4AQUWWEM.js → chunk-HNIT4HXU.js} +1 -1
  20. package/dist/{chunk-MM5UFDAC.js → chunk-HYDQULR2.js} +1 -1
  21. package/dist/{chunk-XSBIISXS.js → chunk-L2JIOAA2.js} +2 -2
  22. package/dist/{chunk-45DMFMY2.js → chunk-UWGTSF5O.js} +220 -110
  23. package/dist/{chunk-QSBQJ3PA.js → chunk-VFBFK6EY.js} +1 -1
  24. package/dist/{chunk-6DTVCPLO.js → chunk-XIMUQ4IU.js} +4 -3
  25. package/dist/{chunk-NSOCZGOH.js → chunk-XVR4O24H.js} +5 -7
  26. package/dist/cli/chat.js +4 -4
  27. package/dist/cli/upgrade.js +119 -2
  28. package/dist/{detail-LU6COZO6.js → detail-4EPUMSDU.js} +2 -2
  29. package/dist/{detail-5SLCGT6J.js → detail-CD4SPJ4K.js} +3 -3
  30. package/dist/{list-4ALSU6ZW.js → list-7L5LY2WU.js} +3 -3
  31. package/dist/{list-2FI5SFV7.js → list-DA2ATOM4.js} +2 -2
  32. package/dist/{list-KHRAWZWU.js → list-J6ROVH56.js} +1 -1
  33. package/dist/{profile-3O3OVENT.js → profile-6NRWEKMZ.js} +2 -2
  34. package/dist/{runner-BM7D35YW.js → runner-CZIKSWGC.js} +3 -3
  35. package/dist/{stats-J3YGCYDF.js → stats-Q5FRUXLY.js} +1 -1
  36. package/dist/{subagent-P7HOSMVC.js → subagent-YIINCFAS.js} +2 -2
  37. package/package.json +6 -6
  38. package/dist/api-reference.d.ts +0 -14
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  CooperativeTurnYieldError,
3
3
  TurnInputCommitLostError,
4
+ abandonReplacedPendingAuth,
4
5
  canReusePendingAuthLink,
5
6
  getInterruptionMarker,
6
7
  isTurnInputCommitLostError,
@@ -10,10 +11,10 @@ import {
10
11
  persistRunningSessionRecord,
11
12
  persistTimeoutSessionRecord,
12
13
  persistYieldSessionRecord
13
- } from "./chunk-NSOCZGOH.js";
14
+ } from "./chunk-XVR4O24H.js";
14
15
  import {
15
16
  JUNIOR_THREAD_STATE_TTL_MS
16
- } from "./chunk-6DTVCPLO.js";
17
+ } from "./chunk-XIMUQ4IU.js";
17
18
  import {
18
19
  addAgentTurnUsage,
19
20
  hasAgentTurnUsage
@@ -3151,6 +3152,7 @@ var PluginMcpClient = class {
3151
3152
  plugin;
3152
3153
  options;
3153
3154
  client;
3155
+ clientPromise;
3154
3156
  lastAttemptedTransportSessionId;
3155
3157
  transport;
3156
3158
  listedTools;
@@ -3227,6 +3229,20 @@ var PluginMcpClient = class {
3227
3229
  if (this.client) {
3228
3230
  return this.client;
3229
3231
  }
3232
+ if (this.clientPromise) {
3233
+ return await this.clientPromise;
3234
+ }
3235
+ const clientPromise = this.connectClient();
3236
+ this.clientPromise = clientPromise;
3237
+ try {
3238
+ return await clientPromise;
3239
+ } finally {
3240
+ if (this.clientPromise === clientPromise) {
3241
+ this.clientPromise = void 0;
3242
+ }
3243
+ }
3244
+ }
3245
+ async connectClient() {
3230
3246
  const mcp = this.plugin.manifest.mcp;
3231
3247
  if (!mcp) {
3232
3248
  throw new Error(
@@ -3289,6 +3305,7 @@ var PluginMcpClient = class {
3289
3305
  this.listedTools = void 0;
3290
3306
  this.transport = void 0;
3291
3307
  this.client = void 0;
3308
+ this.clientPromise = void 0;
3292
3309
  if (transport) {
3293
3310
  await transport.close();
3294
3311
  }
@@ -3337,6 +3354,9 @@ function getMcpAwareTelemetryMessage(error, privacy) {
3337
3354
  }
3338
3355
 
3339
3356
  // src/chat/mcp/tool-manager.ts
3357
+ var MAX_TOOL_RESULT_TOKENS = 1e4;
3358
+ var MAX_TOOL_RESULT_BYTES = 32 * 1024;
3359
+ var CHARS_PER_ESTIMATED_TOKEN = 4;
3340
3360
  function normalizeMcpToolName(provider, toolName) {
3341
3361
  return `mcp__${provider}__${toolName}`;
3342
3362
  }
@@ -3407,16 +3427,80 @@ function toAgentToolContent(result) {
3407
3427
  }
3408
3428
  return [{ type: "text", text: "ok" }];
3409
3429
  }
3430
+ function utf8Bytes(value) {
3431
+ return Buffer.byteLength(value, "utf8");
3432
+ }
3433
+ function prefixWithin(value, maxChars, maxBytes) {
3434
+ let low = 0;
3435
+ let high = Math.min(value.length, maxChars);
3436
+ while (low < high) {
3437
+ const middle = Math.ceil((low + high) / 2);
3438
+ if (utf8Bytes(value.slice(0, middle)) <= maxBytes) {
3439
+ low = middle;
3440
+ } else {
3441
+ high = middle - 1;
3442
+ }
3443
+ }
3444
+ if (low > 0 && /[\uD800-\uDBFF]/.test(value[low - 1])) {
3445
+ low -= 1;
3446
+ }
3447
+ return value.slice(0, low);
3448
+ }
3449
+ function suffixWithin(value, maxChars, maxBytes) {
3450
+ let low = Math.max(0, value.length - maxChars);
3451
+ let high = value.length;
3452
+ while (low < high) {
3453
+ const middle = Math.floor((low + high) / 2);
3454
+ if (utf8Bytes(value.slice(middle)) <= maxBytes) {
3455
+ high = middle;
3456
+ } else {
3457
+ low = middle + 1;
3458
+ }
3459
+ }
3460
+ if (low < value.length && /[\uDC00-\uDFFF]/.test(value[low])) {
3461
+ low += 1;
3462
+ }
3463
+ return value.slice(low);
3464
+ }
3465
+ function boundMcpContent(content) {
3466
+ const text = content.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
3467
+ const originalBytes = utf8Bytes(text);
3468
+ const originalTokens = Math.ceil(text.length / CHARS_PER_ESTIMATED_TOKEN);
3469
+ if (originalTokens <= MAX_TOOL_RESULT_TOKENS && originalBytes <= MAX_TOOL_RESULT_BYTES) {
3470
+ return content;
3471
+ }
3472
+ const warning = `
3473
+
3474
+ Warning: truncated output (original token count: ${originalTokens}; original bytes: ${originalBytes})
3475
+
3476
+ `;
3477
+ const remainingChars = MAX_TOOL_RESULT_TOKENS * CHARS_PER_ESTIMATED_TOKEN - warning.length;
3478
+ const remainingBytes = MAX_TOOL_RESULT_BYTES - utf8Bytes(warning);
3479
+ const head = prefixWithin(
3480
+ text,
3481
+ Math.ceil(remainingChars / 2),
3482
+ Math.ceil(remainingBytes / 2)
3483
+ );
3484
+ const tail = suffixWithin(
3485
+ text.slice(head.length),
3486
+ remainingChars - head.length,
3487
+ remainingBytes - utf8Bytes(head)
3488
+ );
3489
+ return [
3490
+ { type: "text", text: `${head}${warning}${tail}` },
3491
+ ...content.filter((part) => part.type === "image")
3492
+ ];
3493
+ }
3410
3494
  function toModelVisibleMcpContent(result) {
3411
3495
  const content = toAgentToolContent(result);
3412
3496
  if (content.some((part) => part.type === "image")) {
3413
- return content;
3497
+ return boundMcpContent(content);
3414
3498
  }
3415
3499
  const structured = summarizeStructuredContent(result.structuredContent);
3416
3500
  if (structured) {
3417
- return [{ type: "text", text: structured }];
3501
+ return boundMcpContent([{ type: "text", text: structured }]);
3418
3502
  }
3419
- return content;
3503
+ return boundMcpContent(content);
3420
3504
  }
3421
3505
  function describeMcpTool(provider, tool) {
3422
3506
  const prefix = `[${provider}]`;
@@ -3453,6 +3537,7 @@ var McpToolManager = class {
3453
3537
  activeProviders = /* @__PURE__ */ new Set();
3454
3538
  authorizationPendingProviders = /* @__PURE__ */ new Set();
3455
3539
  clientsByProvider = /* @__PURE__ */ new Map();
3540
+ clientPromisesByProvider = /* @__PURE__ */ new Map();
3456
3541
  toolsByProvider = /* @__PURE__ */ new Map();
3457
3542
  getActiveProviders() {
3458
3543
  return [...this.activeProviders].sort(
@@ -3545,6 +3630,21 @@ var McpToolManager = class {
3545
3630
  if (existing) {
3546
3631
  return existing;
3547
3632
  }
3633
+ const pending = this.clientPromisesByProvider.get(plugin.manifest.name);
3634
+ if (pending) {
3635
+ return await pending;
3636
+ }
3637
+ const clientPromise = this.createClient(plugin);
3638
+ this.clientPromisesByProvider.set(plugin.manifest.name, clientPromise);
3639
+ try {
3640
+ return await clientPromise;
3641
+ } finally {
3642
+ if (this.clientPromisesByProvider.get(plugin.manifest.name) === clientPromise) {
3643
+ this.clientPromisesByProvider.delete(plugin.manifest.name);
3644
+ }
3645
+ }
3646
+ }
3647
+ async createClient(plugin) {
3548
3648
  const authProvider = this.options.authProviderFactory ? await this.options.authProviderFactory(plugin) : void 0;
3549
3649
  const client = new PluginMcpClient(plugin, {
3550
3650
  ...authProvider ? { authProvider } : {},
@@ -10006,13 +10106,19 @@ function createPluginAuthOrchestration(input) {
10006
10106
  }
10007
10107
  }
10008
10108
  if (input.sessionId && recordPendingAuth) {
10009
- await recordPendingAuth({
10109
+ const nextPendingAuth = {
10010
10110
  kind: "plugin",
10011
10111
  provider,
10012
10112
  actorId: input.actorId,
10013
10113
  ...options?.scope ? { scope: options.scope } : {},
10014
10114
  sessionId: input.sessionId,
10015
10115
  linkSentAtMs: reusingPendingLink ? input.pendingAuth.linkSentAtMs : Date.now()
10116
+ };
10117
+ await recordPendingAuth(nextPendingAuth);
10118
+ await abandonReplacedPendingAuth({
10119
+ conversationId: input.conversationId,
10120
+ previousPendingAuth: input.pendingAuth,
10121
+ nextPendingAuth
10016
10122
  });
10017
10123
  }
10018
10124
  if (input.conversationId && input.sessionId) {
@@ -12580,9 +12686,9 @@ import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from
12580
12686
  import {
12581
12687
  sourceSchema as sourceSchema2
12582
12688
  } from "@sentry/junior-plugin-api";
12583
- var MCP_AUTH_SESSION_PREFIX = "junior:mcp_auth_session";
12689
+ var MCP_AUTH_SESSION_PREFIX = "junior:mcp_oauth_attempt:v2";
12584
12690
  var MCP_AUTH_CREDENTIALS_PREFIX = "junior:mcp_auth_credentials";
12585
- var MCP_AUTH_SESSION_INDEX_PREFIX = "junior:mcp_auth_session_index";
12691
+ var MCP_AUTH_SESSION_INDEX_PREFIX = "junior:mcp_oauth_attempt_index:v2";
12586
12692
  var MCP_SERVER_SESSION_PREFIX = "junior:mcp_server_session";
12587
12693
  var MCP_AUTH_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
12588
12694
  var MCP_AUTH_CREDENTIALS_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -12624,7 +12730,7 @@ function parseMcpAuthSession(value) {
12624
12730
  if (!isRecord(parsed)) {
12625
12731
  return void 0;
12626
12732
  }
12627
- if (typeof parsed.authSessionId !== "string" || typeof parsed.provider !== "string" || typeof parsed.userId !== "string" || typeof parsed.conversationId !== "string" || typeof parsed.sessionId !== "string" || typeof parsed.userMessage !== "string" || typeof parsed.createdAtMs !== "number" || typeof parsed.updatedAtMs !== "number") {
12733
+ if (parsed.schemaVersion !== 2 || typeof parsed.authSessionId !== "string" || typeof parsed.provider !== "string" || typeof parsed.userId !== "string" || typeof parsed.conversationId !== "string" || typeof parsed.sessionId !== "string" || typeof parsed.userMessage !== "string" || typeof parsed.createdAtMs !== "number" || typeof parsed.updatedAtMs !== "number") {
12628
12734
  return void 0;
12629
12735
  }
12630
12736
  const destination = parsed.destination === void 0 ? void 0 : parseDestination(parsed.destination);
@@ -12636,6 +12742,7 @@ function parseMcpAuthSession(value) {
12636
12742
  return void 0;
12637
12743
  }
12638
12744
  return {
12745
+ schemaVersion: 2,
12639
12746
  authSessionId: parsed.authSessionId,
12640
12747
  provider: parsed.provider,
12641
12748
  userId: parsed.userId,
@@ -12733,6 +12840,7 @@ async function patchMcpAuthSession(authSessionId, patch) {
12733
12840
  const next = {
12734
12841
  ...current,
12735
12842
  ...patch,
12843
+ schemaVersion: 2,
12736
12844
  authSessionId: current.authSessionId,
12737
12845
  provider: current.provider,
12738
12846
  userId: current.userId,
@@ -12777,25 +12885,6 @@ async function deleteMcpAuthSessionsForUserProvider(userId, provider) {
12777
12885
  }
12778
12886
  await stateAdapter.delete(indexKey);
12779
12887
  }
12780
- async function getLatestMcpAuthSessionForUserProvider(userId, provider) {
12781
- const stateAdapter = await getConnectedStateAdapter();
12782
- const authSessionIds = parseSessionIndex(
12783
- await stateAdapter.get(sessionIndexKey(userId, provider))
12784
- );
12785
- let latestSession;
12786
- for (const authSessionId of authSessionIds) {
12787
- const session = parseMcpAuthSession(
12788
- await stateAdapter.get(sessionKey(authSessionId))
12789
- );
12790
- if (!session) {
12791
- continue;
12792
- }
12793
- if (!latestSession || session.updatedAtMs > latestSession.updatedAtMs) {
12794
- latestSession = session;
12795
- }
12796
- }
12797
- return latestSession;
12798
- }
12799
12888
  async function getMcpStoredOAuthCredentials(userId, provider) {
12800
12889
  const stateAdapter = await getConnectedStateAdapter();
12801
12890
  return parseStoredCredentials(
@@ -12854,15 +12943,17 @@ function clientAllowsRedirectUri(clientInformation, callbackUrl) {
12854
12943
  return redirectUris.includes(callbackUrl);
12855
12944
  }
12856
12945
  var StateBackedMcpOAuthClientProvider = class {
12857
- constructor(authSessionId, callbackUrl, sessionContext) {
12946
+ constructor(authSessionId, callbackUrl, sessionContext, runCredentialMutation) {
12858
12947
  this.authSessionId = authSessionId;
12859
12948
  this.callbackUrl = callbackUrl;
12860
12949
  this.sessionContext = sessionContext;
12950
+ this.runCredentialMutation = runCredentialMutation;
12861
12951
  this.clientMetadata = createClientMetadata(callbackUrl);
12862
12952
  }
12863
12953
  authSessionId;
12864
12954
  callbackUrl;
12865
12955
  sessionContext;
12956
+ runCredentialMutation;
12866
12957
  clientMetadata;
12867
12958
  get redirectUrl() {
12868
12959
  return this.callbackUrl;
@@ -12886,11 +12977,16 @@ var StateBackedMcpOAuthClientProvider = class {
12886
12977
  return void 0;
12887
12978
  }
12888
12979
  async saveClientInformation(clientInformation) {
12889
- const session = await this.getCredentialContext();
12890
- const credentials = await getMcpStoredOAuthCredentials(session.userId, session.provider) ?? {};
12891
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
12892
- ...credentials,
12893
- clientInformation
12980
+ await this.mutateCredentials(async () => {
12981
+ const session = await this.getCredentialContext();
12982
+ const credentials = await getMcpStoredOAuthCredentials(
12983
+ session.userId,
12984
+ session.provider
12985
+ ) ?? {};
12986
+ await putMcpStoredOAuthCredentials(session.userId, session.provider, {
12987
+ ...credentials,
12988
+ clientInformation
12989
+ });
12894
12990
  });
12895
12991
  }
12896
12992
  async tokens() {
@@ -12902,19 +12998,32 @@ var StateBackedMcpOAuthClientProvider = class {
12902
12998
  return credentials?.tokens;
12903
12999
  }
12904
13000
  async saveTokens(tokens) {
12905
- const session = await this.getCredentialContext();
12906
- const credentials = await getMcpStoredOAuthCredentials(session.userId, session.provider) ?? {};
12907
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
12908
- ...credentials,
12909
- tokens
13001
+ await this.mutateCredentials(async () => {
13002
+ const session = await this.getCredentialContext();
13003
+ const credentials = await getMcpStoredOAuthCredentials(
13004
+ session.userId,
13005
+ session.provider
13006
+ ) ?? {};
13007
+ await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13008
+ ...credentials,
13009
+ tokens
13010
+ });
12910
13011
  });
12911
13012
  }
12912
13013
  async redirectToAuthorization(authorizationUrl) {
13014
+ const existing = await getMcpAuthSession(this.authSessionId);
13015
+ if (existing?.authorizationUrl && existing.authorizationUrl !== authorizationUrl.toString()) {
13016
+ throw new Error("MCP OAuth authorization attempt is already initialized");
13017
+ }
12913
13018
  await this.ensureSession({
12914
13019
  authorizationUrl: authorizationUrl.toString()
12915
13020
  });
12916
13021
  }
12917
13022
  async saveCodeVerifier(codeVerifier) {
13023
+ const existing = await getMcpAuthSession(this.authSessionId);
13024
+ if (existing?.codeVerifier && existing.codeVerifier !== codeVerifier) {
13025
+ throw new Error("MCP OAuth authorization attempt is already initialized");
13026
+ }
12918
13027
  await this.ensureSession({ codeVerifier });
12919
13028
  }
12920
13029
  async codeVerifier() {
@@ -12925,11 +13034,16 @@ var StateBackedMcpOAuthClientProvider = class {
12925
13034
  return session.codeVerifier;
12926
13035
  }
12927
13036
  async saveDiscoveryState(state) {
12928
- const session = await this.getCredentialContext();
12929
- const credentials = await getMcpStoredOAuthCredentials(session.userId, session.provider) ?? {};
12930
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
12931
- ...credentials,
12932
- discoveryState: state
13037
+ await this.mutateCredentials(async () => {
13038
+ const session = await this.getCredentialContext();
13039
+ const credentials = await getMcpStoredOAuthCredentials(
13040
+ session.userId,
13041
+ session.provider
13042
+ ) ?? {};
13043
+ await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13044
+ ...credentials,
13045
+ discoveryState: state
13046
+ });
12933
13047
  });
12934
13048
  }
12935
13049
  async discoveryState() {
@@ -12941,34 +13055,36 @@ var StateBackedMcpOAuthClientProvider = class {
12941
13055
  return credentials?.discoveryState;
12942
13056
  }
12943
13057
  async invalidateCredentials(scope) {
12944
- const session = await this.getCredentialContext();
12945
- const credentials = await getMcpStoredOAuthCredentials(session.userId, session.provider) ?? {};
12946
- await putMcpStoredOAuthCredentials(session.userId, session.provider, {
12947
- ...scope === "tokens" || scope === "all" ? {} : credentials.tokens ? { tokens: credentials.tokens } : {},
12948
- ...scope === "client" || scope === "all" ? {} : credentials.clientInformation ? { clientInformation: credentials.clientInformation } : {},
12949
- ...scope === "discovery" || scope === "all" ? {} : credentials.discoveryState ? { discoveryState: credentials.discoveryState } : {}
13058
+ await this.mutateCredentials(async () => {
13059
+ const session = await this.getCredentialContext();
13060
+ const credentials = await getMcpStoredOAuthCredentials(
13061
+ session.userId,
13062
+ session.provider
13063
+ ) ?? {};
13064
+ await putMcpStoredOAuthCredentials(session.userId, session.provider, {
13065
+ ...scope === "tokens" || scope === "all" ? {} : credentials.tokens ? { tokens: credentials.tokens } : {},
13066
+ ...scope === "client" || scope === "all" ? {} : credentials.clientInformation ? { clientInformation: credentials.clientInformation } : {},
13067
+ ...scope === "discovery" || scope === "all" ? {} : credentials.discoveryState ? { discoveryState: credentials.discoveryState } : {}
13068
+ });
12950
13069
  });
12951
- if (scope === "verifier" || scope === "all") {
12952
- const authSession = await getMcpAuthSession(this.authSessionId);
12953
- if (authSession) {
12954
- await patchMcpAuthSession(this.authSessionId, {
12955
- codeVerifier: void 0,
12956
- ...scope === "all" ? { authorizationUrl: void 0 } : {}
12957
- });
12958
- }
12959
- }
12960
13070
  }
12961
13071
  async getMcpServerSessionId() {
12962
13072
  const session = await this.getCredentialContext();
12963
13073
  return await getMcpServerSessionId(session.userId, session.provider);
12964
13074
  }
12965
13075
  async saveMcpServerSessionId(sessionId) {
12966
- const session = await this.getCredentialContext();
12967
- if (!sessionId) {
12968
- await deleteMcpServerSessionId(session.userId, session.provider);
12969
- return;
12970
- }
12971
- await putMcpServerSessionId(session.userId, session.provider, sessionId);
13076
+ await this.mutateCredentials(async () => {
13077
+ const session = await this.getCredentialContext();
13078
+ if (!sessionId) {
13079
+ await deleteMcpServerSessionId(session.userId, session.provider);
13080
+ return;
13081
+ }
13082
+ await putMcpServerSessionId(session.userId, session.provider, sessionId);
13083
+ });
13084
+ }
13085
+ /** Route shared credential writes through callback-owned freshness control. */
13086
+ async mutateCredentials(mutation) {
13087
+ return this.runCredentialMutation ? await this.runCredentialMutation(mutation) : await mutation();
12972
13088
  }
12973
13089
  async getCredentialContext() {
12974
13090
  return this.sessionContext ?? await this.requireSession();
@@ -12983,6 +13099,7 @@ var StateBackedMcpOAuthClientProvider = class {
12983
13099
  }
12984
13100
  const now = Date.now();
12985
13101
  const nextSession = {
13102
+ schemaVersion: 2,
12986
13103
  authSessionId: this.authSessionId,
12987
13104
  ...this.sessionContext,
12988
13105
  ...patch,
@@ -13020,32 +13137,7 @@ async function createMcpOAuthClientProvider(input) {
13020
13137
  "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
13021
13138
  );
13022
13139
  }
13023
- const existingSession = await getLatestMcpAuthSessionForUserProvider(
13024
- input.userId,
13025
- input.provider
13026
- );
13027
- const reusableSession = existingSession && existingSession.conversationId === input.conversationId && existingSession.sessionId === input.sessionId ? existingSession : void 0;
13028
- const now = Date.now();
13029
- const authSessionId = reusableSession?.authSessionId ?? randomUUID3();
13030
- await putMcpAuthSession({
13031
- authSessionId,
13032
- provider: input.provider,
13033
- userId: input.userId,
13034
- conversationId: input.conversationId,
13035
- ...input.destination ? { destination: input.destination } : {},
13036
- ...input.source ? { source: input.source } : {},
13037
- sessionId: input.sessionId,
13038
- userMessage: input.userMessage,
13039
- ...input.channelId ? { channelId: input.channelId } : {},
13040
- ...input.threadTs ? { threadTs: input.threadTs } : {},
13041
- ...input.toolChannelId ? { toolChannelId: input.toolChannelId } : {},
13042
- ...input.configuration ? { configuration: input.configuration } : {},
13043
- ...input.artifactState ? { artifactState: input.artifactState } : {},
13044
- ...reusableSession?.authorizationUrl ? { authorizationUrl: reusableSession.authorizationUrl } : {},
13045
- ...reusableSession?.codeVerifier ? { codeVerifier: reusableSession.codeVerifier } : {},
13046
- createdAtMs: reusableSession?.createdAtMs ?? now,
13047
- updatedAtMs: now
13048
- });
13140
+ const authSessionId = randomUUID3();
13049
13141
  return new StateBackedMcpOAuthClientProvider(
13050
13142
  authSessionId,
13051
13143
  `${baseUrl}${getMcpOAuthCallbackPath(input.provider)}`,
@@ -13054,6 +13146,7 @@ async function createMcpOAuthClientProvider(input) {
13054
13146
  userId: input.userId,
13055
13147
  conversationId: input.conversationId,
13056
13148
  ...input.destination ? { destination: input.destination } : {},
13149
+ ...input.source ? { source: input.source } : {},
13057
13150
  sessionId: input.sessionId,
13058
13151
  userMessage: input.userMessage,
13059
13152
  ...input.channelId ? { channelId: input.channelId } : {},
@@ -13064,7 +13157,7 @@ async function createMcpOAuthClientProvider(input) {
13064
13157
  }
13065
13158
  );
13066
13159
  }
13067
- async function finalizeMcpAuthorization(provider, authSessionId, authorizationCode) {
13160
+ async function finalizeMcpAuthorization(provider, authSessionId, authorizationCode, runCredentialMutation) {
13068
13161
  const plugin = requirePluginWithMcp(provider);
13069
13162
  const mcp = plugin.manifest.mcp;
13070
13163
  if (!mcp) {
@@ -13088,7 +13181,9 @@ async function finalizeMcpAuthorization(provider, authSessionId, authorizationCo
13088
13181
  const callbackUrl = `${baseUrl}${getMcpOAuthCallbackPath(provider)}`;
13089
13182
  const authProvider = new StateBackedMcpOAuthClientProvider(
13090
13183
  authSessionId,
13091
- callbackUrl
13184
+ callbackUrl,
13185
+ void 0,
13186
+ runCredentialMutation
13092
13187
  );
13093
13188
  const requestInit = {};
13094
13189
  if (mcp.headers && Object.keys(mcp.headers).length > 0) {
@@ -13171,16 +13266,6 @@ function createMcpAuthOrchestration(input) {
13171
13266
  `Missing pending auth recorder for MCP authorization pause "${provider}"`
13172
13267
  );
13173
13268
  }
13174
- const latestArtifactState = input.getMergedArtifactState();
13175
- await patchMcpAuthSession(authSessionId, {
13176
- configuration: { ...input.getConfiguration() },
13177
- artifactState: latestArtifactState,
13178
- toolChannelId: input.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? input.channelId
13179
- });
13180
- const authSession = await getMcpAuthSession(authSessionId);
13181
- if (!authSession?.authorizationUrl) {
13182
- throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
13183
- }
13184
13269
  const reusingPendingLink = canReusePendingAuthLink({
13185
13270
  pendingAuth: input.pendingAuth,
13186
13271
  kind: "mcp",
@@ -13188,8 +13273,31 @@ function createMcpAuthOrchestration(input) {
13188
13273
  actorId,
13189
13274
  sessionId
13190
13275
  });
13276
+ const reusedAuthSessionId = reusingPendingLink && input.pendingAuth?.kind === "mcp" ? input.pendingAuth.authSessionId : void 0;
13277
+ const activeAuthSessionId = reusedAuthSessionId ?? authSessionId;
13278
+ const latestArtifactState = input.getMergedArtifactState();
13279
+ await patchMcpAuthSession(activeAuthSessionId, {
13280
+ configuration: { ...input.getConfiguration() },
13281
+ artifactState: latestArtifactState,
13282
+ toolChannelId: input.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? input.channelId
13283
+ });
13191
13284
  const providerLabel = formatProviderLabel(provider);
13285
+ const nextPendingAuth = {
13286
+ authSessionId: activeAuthSessionId,
13287
+ kind: "mcp",
13288
+ provider,
13289
+ actorId,
13290
+ sessionId,
13291
+ linkSentAtMs: reusingPendingLink ? input.pendingAuth.linkSentAtMs : Date.now()
13292
+ };
13192
13293
  if (!reusingPendingLink) {
13294
+ const authSession = await getMcpAuthSession(authSessionId);
13295
+ if (!authSession?.authorizationUrl) {
13296
+ throw new Error(
13297
+ `Missing MCP authorization URL for plugin "${provider}"`
13298
+ );
13299
+ }
13300
+ await recordPendingAuth(nextPendingAuth);
13193
13301
  const delivery = await deliverPrivateMessage({
13194
13302
  channelId: authSession.channelId,
13195
13303
  threadTs: authSession.threadTs,
@@ -13201,20 +13309,21 @@ function createMcpAuthOrchestration(input) {
13201
13309
  })
13202
13310
  });
13203
13311
  if (!delivery) {
13312
+ await deleteMcpAuthSession(authSessionId);
13313
+ await recordPendingAuth(input.pendingAuth);
13204
13314
  throw new Error(
13205
13315
  `Unable to deliver MCP authorization link for plugin "${provider}"`
13206
13316
  );
13207
13317
  }
13318
+ await abandonReplacedPendingAuth({
13319
+ conversationId,
13320
+ previousPendingAuth: input.pendingAuth,
13321
+ nextPendingAuth
13322
+ });
13208
13323
  } else {
13209
13324
  await deleteMcpAuthSession(authSessionId);
13325
+ await recordPendingAuth(nextPendingAuth);
13210
13326
  }
13211
- await recordPendingAuth({
13212
- kind: "mcp",
13213
- provider,
13214
- actorId,
13215
- sessionId,
13216
- linkSentAtMs: reusingPendingLink ? input.pendingAuth.linkSentAtMs : Date.now()
13217
- });
13218
13327
  await recordAuthorizationRequested({
13219
13328
  conversationId,
13220
13329
  kind: "mcp",
@@ -16102,6 +16211,7 @@ export {
16102
16211
  normalizeSandboxEgressTracePropagationDomains,
16103
16212
  shouldPropagateSandboxEgressTrace,
16104
16213
  resolveSandboxEgressProviderForHost,
16214
+ getMcpAuthSession,
16105
16215
  deleteMcpAuthSession,
16106
16216
  deleteMcpAuthSessionsForUserProvider,
16107
16217
  getMcpStoredOAuthCredentials,
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-PGGEOCQY.js";
8
8
  import {
9
9
  activityDays
10
- } from "./chunk-4AQUWWEM.js";
10
+ } from "./chunk-HNIT4HXU.js";
11
11
  import {
12
12
  getDb,
13
13
  juniorConversations,
@@ -31,20 +31,21 @@ function coercePendingAuthState(value) {
31
31
  const kind = value.kind;
32
32
  const provider = toOptionalString(value.provider);
33
33
  const actorId = toOptionalString(value.actorId);
34
+ const authSessionId = toOptionalString(value.authSessionId);
34
35
  const scope = toOptionalString(value.scope);
35
36
  const sessionId = toOptionalString(value.sessionId);
36
37
  const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
37
- if (kind !== "mcp" && kind !== "plugin" || !provider || !actorId || !sessionId || typeof linkSentAtMs !== "number") {
38
+ if (kind !== "mcp" && kind !== "plugin" || !provider || !actorId || kind === "mcp" && !authSessionId || !sessionId || typeof linkSentAtMs !== "number") {
38
39
  return void 0;
39
40
  }
40
- return {
41
- kind,
41
+ const base = {
42
42
  provider,
43
43
  actorId,
44
44
  ...scope ? { scope } : {},
45
45
  sessionId,
46
46
  linkSentAtMs
47
47
  };
48
+ return kind === "mcp" ? { ...base, authSessionId, kind } : { ...base, kind };
48
49
  }
49
50
  function coerceThreadConversationState(value) {
50
51
  if (!isRecord(value)) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  JUNIOR_THREAD_STATE_TTL_MS,
3
3
  buildConversationStatePatch
4
- } from "./chunk-6DTVCPLO.js";
4
+ } from "./chunk-XIMUQ4IU.js";
5
5
  import {
6
6
  abandonAgentTurnSessionRecord,
7
7
  addAgentTurnUsage,
@@ -899,13 +899,11 @@ function clearPendingAuth(conversation, sessionId) {
899
899
  }
900
900
  conversation.processing.pendingAuth = void 0;
901
901
  }
902
- async function applyPendingAuthUpdate(args) {
903
- const previousPendingAuth = args.conversation.processing.pendingAuth;
904
- args.conversation.processing.pendingAuth = args.nextPendingAuth;
905
- if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
902
+ async function abandonReplacedPendingAuth(args) {
903
+ if (args.previousPendingAuth && args.previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
906
904
  await abandonAgentTurnSessionRecord({
907
905
  conversationId: args.conversationId,
908
- sessionId: previousPendingAuth.sessionId,
906
+ sessionId: args.previousPendingAuth.sessionId,
909
907
  errorMessage: "Abandoned by a newer auth-blocked request in the same conversation."
910
908
  });
911
909
  }
@@ -951,7 +949,7 @@ export {
951
949
  canReusePendingAuthLink,
952
950
  getConversationPendingAuth,
953
951
  clearPendingAuth,
954
- applyPendingAuthUpdate,
952
+ abandonReplacedPendingAuth,
955
953
  isPendingAuthLatestRequest,
956
954
  coerceThreadArtifactsState,
957
955
  mergeArtifactsState,
package/dist/cli/chat.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createAgentRunner,
3
3
  executeAgentRun
4
- } from "../chunk-45DMFMY2.js";
5
- import "../chunk-NSOCZGOH.js";
6
- import "../chunk-6DTVCPLO.js";
4
+ } from "../chunk-UWGTSF5O.js";
5
+ import "../chunk-XVR4O24H.js";
6
+ import "../chunk-XIMUQ4IU.js";
7
7
  import "../chunk-IVXL57YF.js";
8
8
  import "../chunk-IQIBZXGB.js";
9
9
  import "../chunk-JPQISMMJ.js";
@@ -217,7 +217,7 @@ function newRunConversationId() {
217
217
  async function prepareLocalChatRun(io, pluginSet) {
218
218
  defaultStateAdapterForLocalChat();
219
219
  await configureLocalChatPlugins(pluginSet);
220
- const { runLocalAgentTurn } = await import("../runner-BM7D35YW.js");
220
+ const { runLocalAgentTurn } = await import("../runner-CZIKSWGC.js");
221
221
  const deps = {
222
222
  agentRunner: createAgentRunner(executeAgentRun),
223
223
  deliverReply: async (reply) => {