@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
package/README.md CHANGED
@@ -50,4 +50,3 @@ Canonical docs: **https://junior.sentry.dev/**
50
50
  - Slack app setup: https://junior.sentry.dev/start-here/slack-app-setup/
51
51
  - Deployment: https://junior.sentry.dev/start-here/deploy-to-vercel/
52
52
  - Plugin setup: https://junior.sentry.dev/extend/
53
- - API reference: https://junior.sentry.dev/reference/api/
@@ -310,7 +310,7 @@ export declare const conversationContextEventSchema: z.ZodDiscriminatedUnion<[z.
310
310
  type: z.ZodLiteral<"model_handoff">;
311
311
  createdAt: z.ZodString;
312
312
  fromModelId: z.ZodOptional<z.ZodString>;
313
- summary: z.ZodOptional<z.ZodString>;
313
+ message: z.ZodOptional<z.ZodString>;
314
314
  toModelId: z.ZodString;
315
315
  transcriptIndex: z.ZodNumber;
316
316
  }, z.core.$strict>], "type">;
@@ -436,7 +436,7 @@ export declare const conversationDetailReportSchema: z.ZodObject<{
436
436
  type: z.ZodLiteral<"model_handoff">;
437
437
  createdAt: z.ZodString;
438
438
  fromModelId: z.ZodOptional<z.ZodString>;
439
- summary: z.ZodOptional<z.ZodString>;
439
+ message: z.ZodOptional<z.ZodString>;
440
440
  toModelId: z.ZodString;
441
441
  transcriptIndex: z.ZodNumber;
442
442
  }, z.core.$strict>], "type">>>;
@@ -9,17 +9,17 @@ import {
9
9
  dailyConversationActivitySchema,
10
10
  locationDetailReportSchema,
11
11
  locationDirectoryReportSchema
12
- } from "../chunk-4AQUWWEM.js";
12
+ } from "../chunk-HNIT4HXU.js";
13
13
  import {
14
14
  actorDirectoryReportSchema,
15
15
  actorProfileReportSchema
16
- } from "../chunk-IDZBM3NW.js";
16
+ } from "../chunk-DBPLXSBK.js";
17
17
  import {
18
18
  conversationDetailReportSchema,
19
19
  conversationFeedSchema,
20
20
  conversationStatsReportSchema,
21
21
  conversationSubagentTranscriptReportSchema
22
- } from "../chunk-MM5UFDAC.js";
22
+ } from "../chunk-HYDQULR2.js";
23
23
  import {
24
24
  healthReportSchema,
25
25
  pluginOperationalReportFeedSchema,
package/dist/api.js CHANGED
@@ -5,9 +5,9 @@ import {
5
5
  personParamsSchema,
6
6
  subagentParamsSchema
7
7
  } from "./chunk-2RNOB32R.js";
8
- import "./chunk-4AQUWWEM.js";
9
- import "./chunk-IDZBM3NW.js";
10
- import "./chunk-MM5UFDAC.js";
8
+ import "./chunk-HNIT4HXU.js";
9
+ import "./chunk-DBPLXSBK.js";
10
+ import "./chunk-HYDQULR2.js";
11
11
  import {
12
12
  readHealthReport
13
13
  } from "./chunk-QNVHH5PU.js";
@@ -126,7 +126,7 @@ function createJuniorApi() {
126
126
  return Response.json(await readPluginOperationalReportFeed());
127
127
  });
128
128
  app.get("/api/conversations", async (c) => {
129
- const { readConversationFeed } = await import("./list-KHRAWZWU.js");
129
+ const { readConversationFeed } = await import("./list-J6ROVH56.js");
130
130
  const { actorEmail } = parseQuery(
131
131
  conversationFeedQuerySchema,
132
132
  c.req.query()
@@ -134,11 +134,11 @@ function createJuniorApi() {
134
134
  return Response.json(await readConversationFeed({ actorEmail }));
135
135
  });
136
136
  app.get("/api/conversations/stats", async () => {
137
- const { readConversationStats } = await import("./stats-J3YGCYDF.js");
137
+ const { readConversationStats } = await import("./stats-Q5FRUXLY.js");
138
138
  return Response.json(await readConversationStats());
139
139
  });
140
140
  app.get("/api/conversations/:conversationId", async (c) => {
141
- const { readConversationDetail } = await import("./detail-LU6COZO6.js");
141
+ const { readConversationDetail } = await import("./detail-4EPUMSDU.js");
142
142
  const { conversationId } = parseParams(
143
143
  conversationParamsSchema,
144
144
  c.req.param()
@@ -149,7 +149,7 @@ function createJuniorApi() {
149
149
  app.get(
150
150
  "/api/conversations/:conversationId/subagents/:subagentId",
151
151
  async (c) => {
152
- const { readConversationSubagent } = await import("./subagent-P7HOSMVC.js");
152
+ const { readConversationSubagent } = await import("./subagent-YIINCFAS.js");
153
153
  const { conversationId, subagentId } = parseParams(
154
154
  subagentParamsSchema,
155
155
  c.req.param()
@@ -159,21 +159,21 @@ function createJuniorApi() {
159
159
  }
160
160
  );
161
161
  app.get("/api/people", async () => {
162
- const { readPeopleList } = await import("./list-2FI5SFV7.js");
162
+ const { readPeopleList } = await import("./list-DA2ATOM4.js");
163
163
  return Response.json(await readPeopleList());
164
164
  });
165
165
  app.get("/api/people/:email", async (c) => {
166
166
  const { email } = parseParams(personParamsSchema, c.req.param());
167
- const { readPeopleProfile } = await import("./profile-3O3OVENT.js");
167
+ const { readPeopleProfile } = await import("./profile-6NRWEKMZ.js");
168
168
  return Response.json(await readPeopleProfile(email));
169
169
  });
170
170
  app.get("/api/locations", async () => {
171
- const { readLocationDirectory } = await import("./list-4ALSU6ZW.js");
171
+ const { readLocationDirectory } = await import("./list-7L5LY2WU.js");
172
172
  return Response.json(await readLocationDirectory());
173
173
  });
174
174
  app.get("/api/locations/:locationId", async (c) => {
175
175
  const { locationId } = parseParams(locationParamsSchema, c.req.param());
176
- const { readLocationDetail } = await import("./detail-5SLCGT6J.js");
176
+ const { readLocationDetail } = await import("./detail-CD4SPJ4K.js");
177
177
  const report = await readLocationDetail(locationId);
178
178
  return report ? Response.json(report) : Response.json({ error: "Location not found." }, { status: 404 });
179
179
  });
package/dist/app.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  processPluginTask,
6
6
  requireTurnFailureEventId,
7
7
  scheduleSessionCompletedPluginTasks
8
- } from "./chunk-XSBIISXS.js";
8
+ } from "./chunk-L2JIOAA2.js";
9
9
  import {
10
10
  buildSentryConversationUrl
11
11
  } from "./chunk-LVUKF5CE.js";
@@ -37,7 +37,7 @@ import {
37
37
  requestConversationContinuation,
38
38
  requestConversationWork,
39
39
  startConversationWork
40
- } from "./chunk-2T7DCRAW.js";
40
+ } from "./chunk-EBFJOOUE.js";
41
41
  import {
42
42
  GET
43
43
  } from "./chunk-QNVHH5PU.js";
@@ -86,6 +86,7 @@ import {
86
86
  formatProviderLabel,
87
87
  formatSlackLink,
88
88
  getConfigDefaults,
89
+ getMcpAuthSession,
89
90
  getMcpStoredOAuthCredentials,
90
91
  latestReportedProgress,
91
92
  listThreadReplies,
@@ -105,12 +106,11 @@ import {
105
106
  splitSlackReplyText,
106
107
  startOAuthFlow,
107
108
  truncateStatusText
108
- } from "./chunk-45DMFMY2.js";
109
+ } from "./chunk-UWGTSF5O.js";
109
110
  import {
110
111
  CooperativeTurnYieldError,
111
112
  TurnInputCommitLostError,
112
113
  TurnInputDeferredError,
113
- applyPendingAuthUpdate,
114
114
  buildDeterministicTurnId,
115
115
  clearPendingAuth,
116
116
  coerceThreadArtifactsState,
@@ -134,11 +134,11 @@ import {
134
134
  persistWithRetry,
135
135
  persistYieldSessionRecord,
136
136
  startActiveTurn
137
- } from "./chunk-NSOCZGOH.js";
137
+ } from "./chunk-XVR4O24H.js";
138
138
  import {
139
139
  JUNIOR_THREAD_STATE_TTL_MS,
140
140
  coerceThreadConversationState
141
- } from "./chunk-6DTVCPLO.js";
141
+ } from "./chunk-XIMUQ4IU.js";
142
142
  import {
143
143
  abandonAgentTurnSessionRecord,
144
144
  failAgentTurnSessionRecord,
@@ -4497,6 +4497,11 @@ var CALLBACK_PAGES = {
4497
4497
  message: "Missing code parameter.",
4498
4498
  status: 400
4499
4499
  },
4500
+ expired: {
4501
+ title: "Authorization expired",
4502
+ message: "This authorization link is no longer active. Return to Slack and retry the original request.",
4503
+ status: 400
4504
+ },
4500
4505
  success: {
4501
4506
  title: "Authorization complete",
4502
4507
  message: "Your MCP access is connected. Junior will continue the paused request in Slack.",
@@ -4508,6 +4513,12 @@ var CALLBACK_PAGES = {
4508
4513
  status: 500
4509
4514
  }
4510
4515
  };
4516
+ var McpOAuthAttemptExpiredError = class extends Error {
4517
+ constructor() {
4518
+ super("MCP OAuth authorization attempt is no longer current");
4519
+ this.name = "McpOAuthAttemptExpiredError";
4520
+ }
4521
+ };
4511
4522
  function mcpAuthorizationId(args) {
4512
4523
  return `${args.sessionId}:mcp:${args.provider}`;
4513
4524
  }
@@ -4597,20 +4608,19 @@ async function resumeAuthorizedMcpTurn(args) {
4597
4608
  provider,
4598
4609
  actorId: authSession.userId
4599
4610
  });
4600
- const resolvedSessionId = pendingAuth?.sessionId ?? authSession.sessionId;
4611
+ if (pendingAuth?.authSessionId !== authSession.authSessionId) {
4612
+ return;
4613
+ }
4614
+ const resolvedSessionId = pendingAuth.sessionId;
4601
4615
  const userMessage = getTurnUserMessage(conversation, resolvedSessionId);
4602
- if (pendingAuth) {
4603
- if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
4604
- clearPendingAuth(conversation, pendingAuth.sessionId);
4605
- await persistThreadStateById(threadId, { conversation });
4606
- await abandonAgentTurnSessionRecord({
4607
- conversationId: authSession.conversationId,
4608
- sessionId: pendingAuth.sessionId,
4609
- errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
4610
- });
4611
- return;
4612
- }
4613
- } else if (conversation.processing.activeTurnId !== authSession.sessionId) {
4616
+ if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
4617
+ clearPendingAuth(conversation, pendingAuth.sessionId);
4618
+ await persistThreadStateById(threadId, { conversation });
4619
+ await abandonAgentTurnSessionRecord({
4620
+ conversationId: authSession.conversationId,
4621
+ sessionId: pendingAuth.sessionId,
4622
+ errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
4623
+ });
4614
4624
  return;
4615
4625
  }
4616
4626
  if (!userMessage) {
@@ -4638,24 +4648,23 @@ async function resumeAuthorizedMcpTurn(args) {
4638
4648
  provider,
4639
4649
  actorId: authSession.userId
4640
4650
  });
4641
- const lockedSessionId = lockedPendingAuth?.sessionId ?? authSession.sessionId;
4651
+ if (lockedPendingAuth?.authSessionId !== authSession.authSessionId) {
4652
+ return false;
4653
+ }
4654
+ const lockedSessionId = lockedPendingAuth.sessionId;
4642
4655
  if (lockedSessionId !== resolvedSessionId) {
4643
4656
  return false;
4644
4657
  }
4645
- if (lockedPendingAuth) {
4646
- if (!isPendingAuthLatestRequest(lockedConversation, lockedPendingAuth)) {
4647
- clearPendingAuth(lockedConversation, lockedPendingAuth.sessionId);
4648
- await persistThreadStateById(threadId, {
4649
- conversation: lockedConversation
4650
- });
4651
- await abandonAgentTurnSessionRecord({
4652
- conversationId: authSession.conversationId,
4653
- sessionId: lockedPendingAuth.sessionId,
4654
- errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
4655
- });
4656
- return false;
4657
- }
4658
- } else if (lockedConversation.processing.activeTurnId !== authSession.sessionId) {
4658
+ if (!isPendingAuthLatestRequest(lockedConversation, lockedPendingAuth)) {
4659
+ clearPendingAuth(lockedConversation, lockedPendingAuth.sessionId);
4660
+ await persistThreadStateById(threadId, {
4661
+ conversation: lockedConversation
4662
+ });
4663
+ await abandonAgentTurnSessionRecord({
4664
+ conversationId: authSession.conversationId,
4665
+ sessionId: lockedPendingAuth.sessionId,
4666
+ errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
4667
+ });
4659
4668
  return false;
4660
4669
  }
4661
4670
  const lockedUserMessage = getTurnUserMessage(
@@ -4755,11 +4764,7 @@ async function resumeAuthorizedMcpTurn(args) {
4755
4764
  },
4756
4765
  durability: {
4757
4766
  recordPendingAuth: async (nextPendingAuth) => {
4758
- await applyPendingAuthUpdate({
4759
- conversation: lockedConversation,
4760
- conversationId: authSession.conversationId,
4761
- nextPendingAuth
4762
- });
4767
+ lockedConversation.processing.pendingAuth = nextPendingAuth;
4763
4768
  await persistThreadStateById(threadId, {
4764
4769
  conversation: lockedConversation
4765
4770
  });
@@ -4824,6 +4829,43 @@ async function resumeAuthorizedMcpTurn(args) {
4824
4829
  }
4825
4830
  });
4826
4831
  }
4832
+ async function isCurrentMcpAuthorizationAttempt(authSession, provider) {
4833
+ if (!authSession.channelId || !authSession.threadTs) {
4834
+ return false;
4835
+ }
4836
+ const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4837
+ const currentState = await getPersistedThreadState(threadId);
4838
+ const conversation = coerceThreadConversationState(currentState);
4839
+ const pendingAuth = getConversationPendingAuth({
4840
+ conversation,
4841
+ kind: "mcp",
4842
+ provider,
4843
+ actorId: authSession.userId
4844
+ });
4845
+ return pendingAuth?.authSessionId === authSession.authSessionId && pendingAuth.sessionId === authSession.sessionId;
4846
+ }
4847
+ async function runCurrentMcpCredentialMutation(authSession, provider, mutation) {
4848
+ if (!authSession.channelId || !authSession.threadTs) {
4849
+ throw new McpOAuthAttemptExpiredError();
4850
+ }
4851
+ const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4852
+ const stateAdapter = getStateAdapter();
4853
+ await stateAdapter.connect();
4854
+ const lock = await acquireActiveLock(stateAdapter, threadId);
4855
+ if (!lock) {
4856
+ throw new Error(
4857
+ `Could not acquire MCP OAuth callback lock for ${threadId}`
4858
+ );
4859
+ }
4860
+ try {
4861
+ if (!await isCurrentMcpAuthorizationAttempt(authSession, provider)) {
4862
+ throw new McpOAuthAttemptExpiredError();
4863
+ }
4864
+ return await mutation();
4865
+ } finally {
4866
+ await stateAdapter.releaseLock(lock);
4867
+ }
4868
+ }
4827
4869
  async function GET4(request, provider, waitUntil, options) {
4828
4870
  const url = new URL(request.url);
4829
4871
  const state = url.searchParams.get("state")?.trim();
@@ -4839,7 +4881,23 @@ async function GET4(request, provider, waitUntil, options) {
4839
4881
  return htmlResponse("missing_code");
4840
4882
  }
4841
4883
  try {
4842
- const authSession = await finalizeMcpAuthorization(provider, state, code);
4884
+ const pendingSession = await getMcpAuthSession(state);
4885
+ if (!pendingSession || pendingSession.provider !== provider || !await isCurrentMcpAuthorizationAttempt(pendingSession, provider)) {
4886
+ if (pendingSession) {
4887
+ await deleteMcpAuthSession(pendingSession.authSessionId);
4888
+ }
4889
+ return htmlResponse("expired");
4890
+ }
4891
+ const authSession = await finalizeMcpAuthorization(
4892
+ provider,
4893
+ state,
4894
+ code,
4895
+ async (mutation) => await runCurrentMcpCredentialMutation(
4896
+ pendingSession,
4897
+ provider,
4898
+ mutation
4899
+ )
4900
+ );
4843
4901
  try {
4844
4902
  await deleteMcpAuthSession(authSession.authSessionId);
4845
4903
  } catch (cleanupError) {
@@ -4860,6 +4918,10 @@ async function GET4(request, provider, waitUntil, options) {
4860
4918
  );
4861
4919
  return htmlResponse("success");
4862
4920
  } catch (callbackError) {
4921
+ if (callbackError instanceof McpOAuthAttemptExpiredError) {
4922
+ await deleteMcpAuthSession(state);
4923
+ return htmlResponse("expired");
4924
+ }
4863
4925
  logException(
4864
4926
  callbackError,
4865
4927
  "mcp_oauth_callback_failed",
@@ -5304,11 +5366,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5304
5366
  },
5305
5367
  durability: {
5306
5368
  recordPendingAuth: async (nextPendingAuth) => {
5307
- await applyPendingAuthUpdate({
5308
- conversation: lockedConversation,
5309
- conversationId: stored.resumeConversationId,
5310
- nextPendingAuth
5311
- });
5369
+ lockedConversation.processing.pendingAuth = nextPendingAuth;
5312
5370
  await persistThreadStateById(stored.resumeConversationId, {
5313
5371
  conversation: lockedConversation
5314
5372
  });
@@ -10108,11 +10166,7 @@ function createReplyToThread(deps) {
10108
10166
  });
10109
10167
  },
10110
10168
  recordPendingAuth: async (pendingAuth) => {
10111
- await applyPendingAuthUpdate({
10112
- conversation: preparedState.conversation,
10113
- conversationId,
10114
- nextPendingAuth: pendingAuth
10115
- });
10169
+ preparedState.conversation.processing.pendingAuth = pendingAuth;
10116
10170
  await persistThreadState(thread, {
10117
10171
  conversation: preparedState.conversation
10118
10172
  });
@@ -11200,11 +11254,7 @@ async function continueSlackAgentRun(payload, options) {
11200
11254
  },
11201
11255
  durability: {
11202
11256
  recordPendingAuth: async (nextPendingAuth) => {
11203
- await applyPendingAuthUpdate({
11204
- conversation,
11205
- conversationId: payload.conversationId,
11206
- nextPendingAuth
11207
- });
11257
+ conversation.processing.pendingAuth = nextPendingAuth;
11208
11258
  await persistThreadStateById(payload.conversationId, {
11209
11259
  conversation
11210
11260
  });
@@ -133,7 +133,7 @@ export interface AgentRunDurability {
133
133
  /** Return true when the durable worker should pause at the next Pi boundary. */
134
134
  shouldYield?: () => boolean;
135
135
  drainSteeringMessages?: (accept: (messages: AgentRunSteeringMessage[]) => Promise<void>) => Promise<AgentRunSteeringMessage[]>;
136
- recordPendingAuth?: (pendingAuth: ConversationPendingAuthState) => void | Promise<void>;
136
+ recordPendingAuth?: (pendingAuth: ConversationPendingAuthState | undefined) => void | Promise<void>;
137
137
  onSandboxAcquired?: (sandbox: SandboxAcquiredState) => void | Promise<void>;
138
138
  onArtifactStateUpdated?: (artifactState: ThreadArtifactsState) => void | Promise<void>;
139
139
  }
@@ -26,7 +26,7 @@ export interface Conversation {
26
26
  /**
27
27
  * When retention purged this conversation's content. Set means messages and
28
28
  * steps were deleted wholesale; reporting presents the transcript as expired
29
- * rather than privacy-redacted (`../../../specs/data-redaction-policy.md`).
29
+ * rather than privacy-redacted (`../../../../../policies/data-redaction.md`).
30
30
  */
31
31
  transcriptPurgedAtMs?: number;
32
32
  /** Persisted destination visibility. Undefined means no destination row exists. */
@@ -3,6 +3,7 @@ import type { OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.
3
3
  import { type Destination, type Source } from "@sentry/junior-plugin-api";
4
4
  import type { ThreadArtifactsState } from "@/chat/state/artifacts";
5
5
  export interface McpAuthSessionState {
6
+ schemaVersion: 2;
6
7
  authSessionId: string;
7
8
  provider: string;
8
9
  userId: string;
@@ -15,6 +15,7 @@ export declare class PluginMcpClient {
15
15
  private readonly plugin;
16
16
  private readonly options;
17
17
  private client?;
18
+ private clientPromise?;
18
19
  private lastAttemptedTransportSessionId?;
19
20
  private transport?;
20
21
  private listedTools?;
@@ -24,6 +25,7 @@ export declare class PluginMcpClient {
24
25
  close(): Promise<void>;
25
26
  private withSessionRecovery;
26
27
  private getClient;
28
+ private connectClient;
27
29
  private wrapAuth;
28
30
  private shouldResetMissingSession;
29
31
  private disposeClient;
@@ -5,8 +5,9 @@ export declare class StateBackedMcpOAuthClientProvider implements OAuthClientPro
5
5
  readonly authSessionId: string;
6
6
  private readonly callbackUrl;
7
7
  private readonly sessionContext?;
8
+ private readonly runCredentialMutation?;
8
9
  readonly clientMetadata: OAuthClientMetadata;
9
- constructor(authSessionId: string, callbackUrl: string, sessionContext?: Omit<McpAuthSessionState, "authSessionId" | "authorizationUrl" | "codeVerifier" | "createdAtMs" | "updatedAtMs"> | undefined);
10
+ constructor(authSessionId: string, callbackUrl: string, sessionContext?: Omit<McpAuthSessionState, "schemaVersion" | "authSessionId" | "authorizationUrl" | "codeVerifier" | "createdAtMs" | "updatedAtMs"> | undefined, runCredentialMutation?: (<T>(mutation: () => Promise<T>) => Promise<T>) | undefined);
10
11
  get redirectUrl(): string;
11
12
  state(): Promise<string>;
12
13
  clientInformation(): Promise<OAuthClientInformationMixed | undefined>;
@@ -21,6 +22,8 @@ export declare class StateBackedMcpOAuthClientProvider implements OAuthClientPro
21
22
  invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise<void>;
22
23
  getMcpServerSessionId(): Promise<string | undefined>;
23
24
  saveMcpServerSessionId(sessionId: string | undefined): Promise<void>;
25
+ /** Route shared credential writes through callback-owned freshness control. */
26
+ private mutateCredentials;
24
27
  private getCredentialContext;
25
28
  private ensureSession;
26
29
  private requireSession;
@@ -3,6 +3,7 @@ import type { ThreadArtifactsState } from "@/chat/state/artifacts";
3
3
  import { type McpAuthSessionState } from "./auth-store";
4
4
  import { StateBackedMcpOAuthClientProvider } from "./oauth-provider";
5
5
  export declare function getMcpOAuthCallbackPath(provider: string): string;
6
+ /** Create one immutable provider authorization attempt for the active turn. */
6
7
  export declare function createMcpOAuthClientProvider(input: {
7
8
  provider: string;
8
9
  conversationId: string;
@@ -17,4 +18,5 @@ export declare function createMcpOAuthClientProvider(input: {
17
18
  configuration?: Record<string, unknown>;
18
19
  artifactState?: ThreadArtifactsState;
19
20
  }): Promise<StateBackedMcpOAuthClientProvider>;
20
- export declare function finalizeMcpAuthorization(provider: string, authSessionId: string, authorizationCode: string): Promise<McpAuthSessionState>;
21
+ /** Exchange a callback code while guarding every shared credential mutation. */
22
+ export declare function finalizeMcpAuthorization(provider: string, authSessionId: string, authorizationCode: string, runCredentialMutation?: <T>(mutation: () => Promise<T>) => Promise<T>): Promise<McpAuthSessionState>;
@@ -43,6 +43,7 @@ export declare class McpToolManager {
43
43
  private readonly activeProviders;
44
44
  private readonly authorizationPendingProviders;
45
45
  private readonly clientsByProvider;
46
+ private readonly clientPromisesByProvider;
46
47
  private readonly toolsByProvider;
47
48
  constructor(plugins: PluginDefinition[], options?: McpToolManagerOptions);
48
49
  getActiveProviders(): string[];
@@ -61,6 +62,7 @@ export declare class McpToolManager {
61
62
  }): ManagedMcpToolDescriptor[];
62
63
  private filterListedTools;
63
64
  private getClient;
65
+ private createClient;
64
66
  private toManagedTool;
65
67
  private handleAuthorizationRequired;
66
68
  /** Return all active ManagedMcpTool objects, optionally filtered by provider. */
@@ -30,7 +30,7 @@ export interface McpAuthOrchestrationInput {
30
30
  getConfiguration: () => Record<string, unknown>;
31
31
  getArtifactState: () => ThreadArtifactsState | undefined;
32
32
  getMergedArtifactState: () => ThreadArtifactsState;
33
- recordPendingAuth?: (pendingAuth: ConversationPendingAuthState) => void | Promise<void>;
33
+ recordPendingAuth?: (pendingAuth: ConversationPendingAuthState | undefined) => void | Promise<void>;
34
34
  authorizationFlowMode?: AuthorizationFlowMode;
35
35
  }
36
36
  export interface McpAuthOrchestration {
@@ -10,22 +10,30 @@ export declare function canReusePendingAuthLink(args: {
10
10
  scope?: string;
11
11
  sessionId: string;
12
12
  }): boolean;
13
+ /** Return the exact pending authorization target for one actor and provider. */
13
14
  export declare function getConversationPendingAuth(args: {
14
15
  conversation: ThreadConversationState;
15
- kind: AuthorizationPauseKind;
16
+ kind: "mcp";
16
17
  provider: string;
17
18
  actorId: string;
18
19
  scope?: string;
19
- }): ConversationPendingAuthState | undefined;
20
- export declare function clearPendingAuth(conversation: ThreadConversationState, sessionId?: string): void;
21
- /**
22
- * Apply a new pending-auth record to the conversation and, when replacing a
23
- * different session's pending-auth, mark the prior session record as abandoned.
24
- * Callers are responsible for persisting the mutated conversation afterwards.
25
- */
26
- export declare function applyPendingAuthUpdate(args: {
20
+ }): Extract<ConversationPendingAuthState, {
21
+ kind: "mcp";
22
+ }> | undefined;
23
+ export declare function getConversationPendingAuth(args: {
27
24
  conversation: ThreadConversationState;
25
+ kind: "plugin";
26
+ provider: string;
27
+ actorId: string;
28
+ scope?: string;
29
+ }): Extract<ConversationPendingAuthState, {
30
+ kind: "plugin";
31
+ }> | undefined;
32
+ export declare function clearPendingAuth(conversation: ThreadConversationState, sessionId?: string): void;
33
+ /** Mark the prior blocked turn abandoned after a new auth attempt replaces it. */
34
+ export declare function abandonReplacedPendingAuth(args: {
28
35
  conversationId: string | undefined;
36
+ previousPendingAuth: ConversationPendingAuthState | undefined;
29
37
  nextPendingAuth: ConversationPendingAuthState;
30
38
  }): Promise<void>;
31
39
  /**
@@ -1,4 +1,3 @@
1
- import type { AuthorizationPauseKind } from "@/chat/services/auth-pause";
2
1
  type ConversationRole = "assistant" | "system" | "user";
3
2
  export interface ConversationAuthor {
4
3
  fullName?: string;
@@ -39,14 +38,19 @@ export interface ConversationProcessingState {
39
38
  lastCompletedAtMs?: number;
40
39
  pendingAuth?: ConversationPendingAuthState;
41
40
  }
42
- export interface ConversationPendingAuthState {
43
- kind: AuthorizationPauseKind;
41
+ interface ConversationPendingAuthBase {
44
42
  linkSentAtMs: number;
45
43
  provider: string;
46
44
  actorId: string;
47
45
  scope?: string;
48
46
  sessionId: string;
49
47
  }
48
+ export type ConversationPendingAuthState = (ConversationPendingAuthBase & {
49
+ authSessionId: string;
50
+ kind: "mcp";
51
+ }) | (ConversationPendingAuthBase & {
52
+ kind: "plugin";
53
+ });
50
54
  export interface ConversationStats {
51
55
  compactedMessageCount: number;
52
56
  estimatedContextTokens: number;
@@ -465,10 +465,11 @@ function historyContent(args) {
465
465
  ) : marker?.entry.reason === "handoff" ? summaryIndex(projected, projectedProvenance, [
466
466
  MODEL_HANDOFF_SUMMARY_PREFIX
467
467
  ]) : -1;
468
- const summary = replacementSummaryIndex >= 0 ? summaryAfterPrefix(
468
+ const summary = marker?.entry.reason === "compaction" && replacementSummaryIndex >= 0 ? summaryAfterPrefix(
469
469
  projected[replacementSummaryIndex],
470
- marker?.entry.reason === "handoff" ? [MODEL_HANDOFF_SUMMARY_PREFIX] : COMPACTION_SUMMARY_PREFIXES
470
+ COMPACTION_SUMMARY_PREFIXES
471
471
  ) : void 0;
472
+ const handoffMessage = marker?.entry.reason === "handoff" && replacementSummaryIndex >= 0 ? messageText(projected[replacementSummaryIndex]) || void 0 : void 0;
472
473
  if (marker?.entry.reason === "compaction") {
473
474
  contextEvents.push({
474
475
  type: "context_compacted",
@@ -483,7 +484,7 @@ function historyContent(args) {
483
484
  createdAt: new Date(marker.createdAtMs).toISOString(),
484
485
  ...previousModelId ? { fromModelId: previousModelId } : {},
485
486
  toModelId: marker.entry.modelId,
486
- ...args.canExposePayload && summary ? { summary } : {},
487
+ ...args.canExposePayload && handoffMessage ? { message: handoffMessage } : {},
487
488
  transcriptIndex: messages.length
488
489
  });
489
490
  }
@@ -2,7 +2,7 @@ import {
2
2
  actorIdentitySchema,
3
3
  conversationStatsItemSchema,
4
4
  conversationSummaryReportSchema
5
- } from "./chunk-MM5UFDAC.js";
5
+ } from "./chunk-HYDQULR2.js";
6
6
 
7
7
  // src/api/people/schema.ts
8
8
  import { z } from "zod";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  JUNIOR_THREAD_STATE_TTL_MS
3
- } from "./chunk-6DTVCPLO.js";
3
+ } from "./chunk-XIMUQ4IU.js";
4
4
  import {
5
5
  getChatConfig,
6
6
  getDefaultRedisStateAdapterFor,
@@ -2,7 +2,7 @@ import {
2
2
  actorIdentitySchema,
3
3
  conversationStatsItemSchema,
4
4
  conversationSummaryReportSchema
5
- } from "./chunk-MM5UFDAC.js";
5
+ } from "./chunk-HYDQULR2.js";
6
6
  import {
7
7
  juniorDestinationKindSchema
8
8
  } from "./chunk-FXFQNHRU.js";
@@ -131,7 +131,7 @@ var conversationContextEventSchema = z.discriminatedUnion("type", [
131
131
  type: z.literal("model_handoff"),
132
132
  createdAt: z.string(),
133
133
  fromModelId: z.string().optional(),
134
- summary: z.string().optional(),
134
+ message: z.string().optional(),
135
135
  toModelId: z.string(),
136
136
  transcriptIndex: z.number().int().nonnegative()
137
137
  }).strict()
@@ -9,10 +9,10 @@ import {
9
9
  getPersistedThreadState,
10
10
  markTurnCompleted,
11
11
  mergeArtifactsState
12
- } from "./chunk-NSOCZGOH.js";
12
+ } from "./chunk-XVR4O24H.js";
13
13
  import {
14
14
  coerceThreadConversationState
15
- } from "./chunk-6DTVCPLO.js";
15
+ } from "./chunk-XIMUQ4IU.js";
16
16
  import {
17
17
  getAgentTurnSessionRecord
18
18
  } from "./chunk-IVXL57YF.js";