@sentry/junior 0.100.0 → 0.102.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 (80) hide show
  1. package/README.md +0 -1
  2. package/dist/{agent-hooks-V6KU7PQN.js → agent-hooks-I4I2DHSW.js} +7 -7
  3. package/dist/api/conversations/schema.d.ts +2 -2
  4. package/dist/api/people/schema.d.ts +13 -0
  5. package/dist/api/schema.d.ts +1 -1
  6. package/dist/api/schema.js +3 -3
  7. package/dist/api.js +16 -16
  8. package/dist/app.js +165 -104
  9. package/dist/{catalog-runtime-PFHVMHDR.js → catalog-runtime-5CVM2DOF.js} +2 -2
  10. package/dist/chat/agent/prompt.d.ts +1 -1
  11. package/dist/chat/agent/request.d.ts +1 -1
  12. package/dist/chat/agent-dispatch/validation.d.ts +1 -1
  13. package/dist/chat/conversation-privacy.d.ts +2 -6
  14. package/dist/chat/conversations/store.d.ts +1 -1
  15. package/dist/chat/credentials/context.d.ts +33 -6
  16. package/dist/chat/credentials/subject.d.ts +15 -1
  17. package/dist/chat/logging.d.ts +4 -2
  18. package/dist/chat/mcp/auth-store.d.ts +1 -0
  19. package/dist/chat/mcp/client.d.ts +2 -0
  20. package/dist/chat/mcp/oauth-provider.d.ts +4 -1
  21. package/dist/chat/mcp/oauth.d.ts +3 -1
  22. package/dist/chat/mcp/tool-manager.d.ts +3 -1
  23. package/dist/chat/sandbox/egress/schemas.d.ts +13 -2
  24. package/dist/chat/services/context-compaction-marker.d.ts +8 -0
  25. package/dist/chat/services/mcp-auth-orchestration.d.ts +1 -1
  26. package/dist/chat/services/pending-auth.d.ts +17 -9
  27. package/dist/chat/state/conversation.d.ts +7 -3
  28. package/dist/{chunk-XJA7BPHS.js → chunk-3OABPOHR.js} +3 -3
  29. package/dist/{chunk-2T7DCRAW.js → chunk-3UK5DFKW.js} +3 -3
  30. package/dist/{chunk-IDZBM3NW.js → chunk-4YN2ZXKB.js} +10 -2
  31. package/dist/{chunk-NSOCZGOH.js → chunk-5K4N73TP.js} +9 -11
  32. package/dist/{chunk-LMJPRYHP.js → chunk-77LHNQK4.js} +32 -8
  33. package/dist/{chunk-I35CTGPO.js → chunk-AEWZ7F65.js} +41 -17
  34. package/dist/{chunk-IQIBZXGB.js → chunk-B5QS4AVO.js} +2 -2
  35. package/dist/{chunk-XSBIISXS.js → chunk-DTUSCR2Y.js} +9 -12
  36. package/dist/{chunk-GEUUJRQF.js → chunk-E4EMFKGB.js} +1 -1
  37. package/dist/{chunk-VVIXK2BX.js → chunk-EMUZSROX.js} +2 -2
  38. package/dist/{chunk-IVXL57YF.js → chunk-EOB77KME.js} +4 -4
  39. package/dist/{chunk-PGGEOCQY.js → chunk-F4CQEUTP.js} +1 -1
  40. package/dist/{chunk-4AQUWWEM.js → chunk-HNIT4HXU.js} +1 -1
  41. package/dist/{chunk-MM5UFDAC.js → chunk-HYDQULR2.js} +1 -1
  42. package/dist/{chunk-FP5N5OWZ.js → chunk-IQ36I3DF.js} +2 -2
  43. package/dist/{chunk-JSAANELJ.js → chunk-LJB2TLVT.js} +3 -3
  44. package/dist/{chunk-D5KZYS4T.js → chunk-MJSJAHBW.js} +1 -1
  45. package/dist/{chunk-QSBQJ3PA.js → chunk-MR6H67P2.js} +3 -3
  46. package/dist/{chunk-VY7BT4SM.js → chunk-NSP6OGL2.js} +9 -8
  47. package/dist/{chunk-VHRM6GNH.js → chunk-QZGVPPIJ.js} +27 -3
  48. package/dist/{chunk-LVA4I4H7.js → chunk-RT5BUHR5.js} +15 -44
  49. package/dist/{chunk-A7X2FDUO.js → chunk-SHBPSE7A.js} +88 -66
  50. package/dist/{chunk-CM5EZD5F.js → chunk-UIE3R5XU.js} +1 -1
  51. package/dist/{chunk-JPQISMMJ.js → chunk-VSAFV2GI.js} +7 -6
  52. package/dist/{chunk-3ECW2K2O.js → chunk-WR7H7VLJ.js} +2 -2
  53. package/dist/{chunk-TPTJEHUE.js → chunk-X2IPNCB5.js} +61 -25
  54. package/dist/{chunk-6DTVCPLO.js → chunk-XIMUQ4IU.js} +4 -3
  55. package/dist/{chunk-45DMFMY2.js → chunk-ZIX4PNBI.js} +348 -200
  56. package/dist/{chunk-PWC24W5F.js → chunk-ZSHL44IQ.js} +2 -2
  57. package/dist/cli/chat.js +22 -22
  58. package/dist/cli/check.js +3 -3
  59. package/dist/cli/plugins.js +9 -9
  60. package/dist/cli/snapshot-warmup.js +5 -5
  61. package/dist/cli/upgrade.js +203 -14
  62. package/dist/db/postgres-json.d.ts +5 -0
  63. package/dist/{db-RCZB7KNM.js → db-6QP6HY7U.js} +4 -4
  64. package/dist/{detail-LU6COZO6.js → detail-OEIVZYXS.js} +12 -12
  65. package/dist/{detail-5SLCGT6J.js → detail-U6IP2Z4N.js} +8 -8
  66. package/dist/instrumentation.js +2 -2
  67. package/dist/{legacy-import-VCYLWT2V.js → legacy-import-R3BMM2GH.js} +5 -5
  68. package/dist/{list-KHRAWZWU.js → list-AJ6RWBOG.js} +8 -8
  69. package/dist/list-UMZKA5AI.js +121 -0
  70. package/dist/{list-4ALSU6ZW.js → list-Z3CC52CZ.js} +8 -8
  71. package/dist/{plugin-conversations-JANXC4GA.js → plugin-conversations-F3IBVNFX.js} +7 -7
  72. package/dist/{profile-3O3OVENT.js → profile-CQMPFKED.js} +8 -8
  73. package/dist/{runner-BM7D35YW.js → runner-C2ZTIOUD.js} +13 -13
  74. package/dist/{skills-MAZXZ2KE.js → skills-722AXRV5.js} +3 -3
  75. package/dist/{stats-J3YGCYDF.js → stats-ZYFQFCTL.js} +6 -6
  76. package/dist/{subagent-P7HOSMVC.js → subagent-X3ZWJHR4.js} +11 -11
  77. package/dist/{validation-FMEAEKCY.js → validation-EUI2UFRM.js} +3 -3
  78. package/package.json +6 -6
  79. package/dist/api-reference.d.ts +0 -14
  80. package/dist/list-2FI5SFV7.js +0 -80
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-DTUSCR2Y.js";
9
9
  import {
10
10
  buildSentryConversationUrl
11
11
  } from "./chunk-LVUKF5CE.js";
@@ -13,7 +13,7 @@ import {
13
13
  conversationVisibilityFromSlackChannelType,
14
14
  resolveSlackChannelTypeFromMessage,
15
15
  resolveSlackConversationContext
16
- } from "./chunk-VVIXK2BX.js";
16
+ } from "./chunk-EMUZSROX.js";
17
17
  import {
18
18
  CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
19
19
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
@@ -37,7 +37,7 @@ import {
37
37
  requestConversationContinuation,
38
38
  requestConversationWork,
39
39
  startConversationWork
40
- } from "./chunk-2T7DCRAW.js";
40
+ } from "./chunk-3UK5DFKW.js";
41
41
  import {
42
42
  GET
43
43
  } from "./chunk-QNVHH5PU.js";
@@ -55,7 +55,7 @@ import {
55
55
  import {
56
56
  validatePluginEgressCredentialHooks,
57
57
  validatePluginRegistrations
58
- } from "./chunk-D5KZYS4T.js";
58
+ } from "./chunk-MJSJAHBW.js";
59
59
  import {
60
60
  defineJuniorPlugins,
61
61
  pluginCatalogConfigFromEnv,
@@ -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-ZIX4PNBI.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,24 +134,24 @@ import {
134
134
  persistWithRetry,
135
135
  persistYieldSessionRecord,
136
136
  startActiveTurn
137
- } from "./chunk-NSOCZGOH.js";
137
+ } from "./chunk-5K4N73TP.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,
145
145
  getAgentTurnSessionRecord,
146
146
  listAgentTurnSessionSummariesForConversation,
147
147
  recordAgentTurnSessionSummary
148
- } from "./chunk-IVXL57YF.js";
148
+ } from "./chunk-EOB77KME.js";
149
149
  import {
150
150
  commitMessages,
151
151
  loadConversationProjection,
152
152
  loadProjection,
153
153
  recordAuthorizationCompleted
154
- } from "./chunk-IQIBZXGB.js";
154
+ } from "./chunk-B5QS4AVO.js";
155
155
  import {
156
156
  buildConversationContext,
157
157
  createConversationMemoryService,
@@ -166,11 +166,12 @@ import {
166
166
  persistConversationMessages,
167
167
  updateConversationStats,
168
168
  upsertConversationMessage
169
- } from "./chunk-JPQISMMJ.js";
170
- import "./chunk-XJA7BPHS.js";
169
+ } from "./chunk-VSAFV2GI.js";
170
+ import "./chunk-3OABPOHR.js";
171
171
  import "./chunk-PDO5BLNM.js";
172
- import "./chunk-CM5EZD5F.js";
172
+ import "./chunk-UIE3R5XU.js";
173
173
  import {
174
+ bindScheduledTaskCredentialSubject,
174
175
  bindSlackDirectCredentialSubject,
175
176
  getPluginApiRoutes,
176
177
  getPluginRoutes,
@@ -178,8 +179,9 @@ import {
178
179
  getPlugins,
179
180
  setPlugins,
180
181
  validatePlugins,
182
+ verifyScheduledTaskCredentialSubject,
181
183
  verifySlackDirectCredentialSubject
182
- } from "./chunk-TPTJEHUE.js";
184
+ } from "./chunk-X2IPNCB5.js";
183
185
  import {
184
186
  SlackActionError,
185
187
  downloadPrivateSlackFile,
@@ -190,11 +192,11 @@ import {
190
192
  normalizeSlackConversationId,
191
193
  runWithSlackInstallationToken,
192
194
  runWithWorkspaceTeamId
193
- } from "./chunk-FP5N5OWZ.js";
195
+ } from "./chunk-IQ36I3DF.js";
194
196
  import {
195
197
  createPluginLogger,
196
198
  createPluginState
197
- } from "./chunk-PWC24W5F.js";
199
+ } from "./chunk-ZSHL44IQ.js";
198
200
  import {
199
201
  contextProvenance,
200
202
  getConversationStore,
@@ -204,7 +206,7 @@ import {
204
206
  juniorAgentSteps,
205
207
  juniorConversationMessages,
206
208
  juniorConversations
207
- } from "./chunk-VHRM6GNH.js";
209
+ } from "./chunk-QZGVPPIJ.js";
208
210
  import "./chunk-G3E7SCME.js";
209
211
  import {
210
212
  sleep
@@ -232,7 +234,7 @@ import {
232
234
  setSlackReactionConfig,
233
235
  stripRuntimeTurnContext,
234
236
  trimTrailingAssistantMessages
235
- } from "./chunk-A7X2FDUO.js";
237
+ } from "./chunk-SHBPSE7A.js";
236
238
  import {
237
239
  juniorDestinations
238
240
  } from "./chunk-FXFQNHRU.js";
@@ -241,7 +243,7 @@ import {
241
243
  readSlackRawMessageContext,
242
244
  resolveSlackChannelIdFromMessage,
243
245
  resolveSlackChannelIdFromThreadId
244
- } from "./chunk-LVA4I4H7.js";
246
+ } from "./chunk-RT5BUHR5.js";
245
247
  import {
246
248
  createSlackDestination,
247
249
  destinationKey,
@@ -251,13 +253,14 @@ import {
251
253
  } from "./chunk-B7PUBQQM.js";
252
254
  import {
253
255
  discoverSkills
254
- } from "./chunk-3ECW2K2O.js";
256
+ } from "./chunk-WR7H7VLJ.js";
255
257
  import {
256
258
  buildOAuthTokenRequest,
259
+ credentialSubjectSchema,
257
260
  hasRequiredOAuthScope,
258
261
  parseOAuthTokenResponse,
259
262
  pluginCatalogRuntime
260
- } from "./chunk-LMJPRYHP.js";
263
+ } from "./chunk-77LHNQK4.js";
261
264
  import {
262
265
  buildTurnFailureResponse,
263
266
  createActor,
@@ -279,7 +282,7 @@ import {
279
282
  setTags,
280
283
  withContext,
281
284
  withSpan
282
- } from "./chunk-I35CTGPO.js";
285
+ } from "./chunk-AEWZ7F65.js";
283
286
  import {
284
287
  toOptionalString
285
288
  } from "./chunk-OB42YVAE.js";
@@ -570,7 +573,6 @@ async function verifyDispatchCallbackRequest(request) {
570
573
  // src/chat/agent-dispatch/store.ts
571
574
  import { createHash } from "crypto";
572
575
  import {
573
- pluginCredentialSubjectSchema,
574
576
  destinationSchema,
575
577
  isSlackDestination,
576
578
  sourceSchema
@@ -596,20 +598,11 @@ var dispatchActorSchema = z.object({
596
598
  platform: z.literal("system"),
597
599
  name: nonEmptyExactStringSchema
598
600
  }).strict();
599
- var credentialSubjectBindingSchema = z.object({
600
- type: z.literal("slack-direct-conversation"),
601
- teamId: z.string().min(1),
602
- channelId: z.string().min(1),
603
- signature: z.string().min(1)
604
- }).strict();
605
- var boundCredentialSubjectSchema = pluginCredentialSubjectSchema.extend({
606
- binding: credentialSubjectBindingSchema
607
- }).strict();
608
601
  var dispatchRecordSchema = z.object({
609
602
  actor: dispatchActorSchema,
610
603
  attempt: z.number().int().nonnegative(),
611
604
  createdAtMs: z.number().finite(),
612
- credentialSubject: boundCredentialSubjectSchema.optional(),
605
+ credentialSubject: credentialSubjectSchema.optional(),
613
606
  destination: destinationSchema,
614
607
  errorMessage: z.string().optional(),
615
608
  id: nonEmptyExactStringSchema,
@@ -638,18 +631,28 @@ var dispatchRecordSchema = z.object({
638
631
  if (!subject) {
639
632
  return;
640
633
  }
641
- if (!record.destination.channelId.startsWith("D")) {
642
- ctx.addIssue({
643
- code: z.ZodIssueCode.custom,
644
- message: "Dispatch credentialSubject requires a private direct Slack destination",
645
- path: ["credentialSubject"]
646
- });
634
+ if (subject.allowedWhen === "private-direct-conversation") {
635
+ if (!record.destination.channelId.startsWith("D")) {
636
+ ctx.addIssue({
637
+ code: z.ZodIssueCode.custom,
638
+ message: "Dispatch credentialSubject requires a private direct Slack destination",
639
+ path: ["credentialSubject"]
640
+ });
641
+ return;
642
+ }
643
+ if (subject.binding.type !== "slack-direct-conversation" || subject.binding.teamId !== record.destination.teamId || subject.binding.channelId !== record.destination.channelId) {
644
+ ctx.addIssue({
645
+ code: z.ZodIssueCode.custom,
646
+ message: "Dispatch credentialSubject binding must match destination",
647
+ path: ["credentialSubject", "binding"]
648
+ });
649
+ }
647
650
  return;
648
651
  }
649
- if (subject.binding.teamId !== record.destination.teamId || subject.binding.channelId !== record.destination.channelId) {
652
+ if (subject.binding.type !== "scheduled-task" || subject.binding.plugin !== record.plugin || subject.binding.taskId !== subject.taskId) {
650
653
  ctx.addIssue({
651
654
  code: z.ZodIssueCode.custom,
652
- message: "Dispatch credentialSubject binding must match destination",
655
+ message: "Dispatch credentialSubject binding must match task",
653
656
  path: ["credentialSubject", "binding"]
654
657
  });
655
658
  }
@@ -2745,7 +2748,7 @@ function dispatchOptionsErrorMessage(issues) {
2745
2748
  return "Dispatch credentialSubject userId is required";
2746
2749
  }
2747
2750
  if (hasIssueUnderPath(issues, ["credentialSubject", "allowedWhen"])) {
2748
- return "Dispatch credentialSubject allowedWhen must be private-direct-conversation";
2751
+ return "Dispatch credentialSubject allowedWhen is invalid";
2749
2752
  }
2750
2753
  if (hasIssueUnderPath(issues, ["credentialSubject"])) {
2751
2754
  return "Dispatch credentialSubject type must be user";
@@ -2772,26 +2775,27 @@ function validateDispatchOptions(options) {
2772
2775
  throw new Error("Dispatch destination platform must be slack");
2773
2776
  }
2774
2777
  if (credentialSubject !== void 0) {
2775
- if (!isDmChannel(destination.channelId)) {
2778
+ if (credentialSubject.allowedWhen === "private-direct-conversation" && !isDmChannel(destination.channelId)) {
2776
2779
  throw new Error(
2777
2780
  "Dispatch credentialSubject requires a private direct Slack destination"
2778
2781
  );
2779
2782
  }
2780
2783
  }
2781
2784
  }
2782
- async function verifyDispatchCredentialSubjectAccess(options) {
2785
+ async function verifyDispatchCredentialSubjectAccess(options, plugin) {
2783
2786
  if (!options.credentialSubject) {
2784
2787
  return;
2785
2788
  }
2786
- const verified = verifySlackDirectCredentialSubject({
2789
+ const verified = options.credentialSubject.allowedWhen === "scheduled-task" ? verifyScheduledTaskCredentialSubject({
2790
+ plugin,
2791
+ subject: options.credentialSubject
2792
+ }) : verifySlackDirectCredentialSubject({
2787
2793
  channelId: options.destination.channelId,
2788
2794
  teamId: options.destination.teamId,
2789
2795
  subject: options.credentialSubject
2790
2796
  });
2791
2797
  if (!verified) {
2792
- throw new Error(
2793
- "Dispatch credentialSubject must match the private direct Slack destination"
2794
- );
2798
+ throw new Error("Dispatch credentialSubject is not valid for this action");
2795
2799
  }
2796
2800
  }
2797
2801
 
@@ -2803,7 +2807,7 @@ function shouldScheduleDispatch(record, nowMs) {
2803
2807
  }
2804
2808
  return record.status !== "running" || typeof record.leaseExpiresAtMs !== "number" || record.leaseExpiresAtMs <= nowMs;
2805
2809
  }
2806
- function bindDispatchCredentialSubject(options) {
2810
+ function bindDispatchCredentialSubject(options, plugin) {
2807
2811
  const { credentialSubject, ...baseOptions } = options;
2808
2812
  if (!credentialSubject) {
2809
2813
  return baseOptions;
@@ -2811,15 +2815,16 @@ function bindDispatchCredentialSubject(options) {
2811
2815
  if ("binding" in credentialSubject) {
2812
2816
  throw new Error("Dispatch credentialSubject binding is runtime-owned");
2813
2817
  }
2814
- const boundSubject = bindSlackDirectCredentialSubject({
2818
+ const boundSubject = credentialSubject.allowedWhen === "scheduled-task" ? bindScheduledTaskCredentialSubject({
2819
+ plugin,
2820
+ subject: credentialSubject
2821
+ }) : bindSlackDirectCredentialSubject({
2815
2822
  channelId: options.destination.channelId,
2816
2823
  teamId: options.destination.teamId,
2817
2824
  subject: credentialSubject
2818
2825
  });
2819
2826
  if (!boundSubject) {
2820
- throw new Error(
2821
- "Dispatch credentialSubject must match the private direct Slack destination"
2822
- );
2827
+ throw new Error("Dispatch credentialSubject is not valid for this action");
2823
2828
  }
2824
2829
  return {
2825
2830
  ...baseOptions,
@@ -2838,11 +2843,17 @@ function createHeartbeatContext(args) {
2838
2843
  agent: {
2839
2844
  async dispatch(options) {
2840
2845
  validateDispatchOptions(options);
2841
- const dispatchOptions = bindDispatchCredentialSubject(options);
2846
+ const dispatchOptions = bindDispatchCredentialSubject(
2847
+ options,
2848
+ pluginName
2849
+ );
2842
2850
  if (dispatchCount >= MAX_DISPATCHES_PER_HEARTBEAT) {
2843
2851
  throw new Error("Plugin heartbeat exceeded the dispatch limit");
2844
2852
  }
2845
- await verifyDispatchCredentialSubjectAccess(dispatchOptions);
2853
+ await verifyDispatchCredentialSubjectAccess(
2854
+ dispatchOptions,
2855
+ pluginName
2856
+ );
2846
2857
  const result = await createOrGetDispatch({
2847
2858
  plugin: pluginName,
2848
2859
  options: dispatchOptions,
@@ -4497,6 +4508,11 @@ var CALLBACK_PAGES = {
4497
4508
  message: "Missing code parameter.",
4498
4509
  status: 400
4499
4510
  },
4511
+ expired: {
4512
+ title: "Authorization expired",
4513
+ message: "This authorization link is no longer active. Return to Slack and retry the original request.",
4514
+ status: 400
4515
+ },
4500
4516
  success: {
4501
4517
  title: "Authorization complete",
4502
4518
  message: "Your MCP access is connected. Junior will continue the paused request in Slack.",
@@ -4508,6 +4524,12 @@ var CALLBACK_PAGES = {
4508
4524
  status: 500
4509
4525
  }
4510
4526
  };
4527
+ var McpOAuthAttemptExpiredError = class extends Error {
4528
+ constructor() {
4529
+ super("MCP OAuth authorization attempt is no longer current");
4530
+ this.name = "McpOAuthAttemptExpiredError";
4531
+ }
4532
+ };
4511
4533
  function mcpAuthorizationId(args) {
4512
4534
  return `${args.sessionId}:mcp:${args.provider}`;
4513
4535
  }
@@ -4597,20 +4619,19 @@ async function resumeAuthorizedMcpTurn(args) {
4597
4619
  provider,
4598
4620
  actorId: authSession.userId
4599
4621
  });
4600
- const resolvedSessionId = pendingAuth?.sessionId ?? authSession.sessionId;
4622
+ if (pendingAuth?.authSessionId !== authSession.authSessionId) {
4623
+ return;
4624
+ }
4625
+ const resolvedSessionId = pendingAuth.sessionId;
4601
4626
  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) {
4627
+ if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
4628
+ clearPendingAuth(conversation, pendingAuth.sessionId);
4629
+ await persistThreadStateById(threadId, { conversation });
4630
+ await abandonAgentTurnSessionRecord({
4631
+ conversationId: authSession.conversationId,
4632
+ sessionId: pendingAuth.sessionId,
4633
+ errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
4634
+ });
4614
4635
  return;
4615
4636
  }
4616
4637
  if (!userMessage) {
@@ -4638,24 +4659,23 @@ async function resumeAuthorizedMcpTurn(args) {
4638
4659
  provider,
4639
4660
  actorId: authSession.userId
4640
4661
  });
4641
- const lockedSessionId = lockedPendingAuth?.sessionId ?? authSession.sessionId;
4662
+ if (lockedPendingAuth?.authSessionId !== authSession.authSessionId) {
4663
+ return false;
4664
+ }
4665
+ const lockedSessionId = lockedPendingAuth.sessionId;
4642
4666
  if (lockedSessionId !== resolvedSessionId) {
4643
4667
  return false;
4644
4668
  }
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) {
4669
+ if (!isPendingAuthLatestRequest(lockedConversation, lockedPendingAuth)) {
4670
+ clearPendingAuth(lockedConversation, lockedPendingAuth.sessionId);
4671
+ await persistThreadStateById(threadId, {
4672
+ conversation: lockedConversation
4673
+ });
4674
+ await abandonAgentTurnSessionRecord({
4675
+ conversationId: authSession.conversationId,
4676
+ sessionId: lockedPendingAuth.sessionId,
4677
+ errorMessage: "Auth completed after a newer thread message abandoned this blocked request."
4678
+ });
4659
4679
  return false;
4660
4680
  }
4661
4681
  const lockedUserMessage = getTurnUserMessage(
@@ -4755,11 +4775,7 @@ async function resumeAuthorizedMcpTurn(args) {
4755
4775
  },
4756
4776
  durability: {
4757
4777
  recordPendingAuth: async (nextPendingAuth) => {
4758
- await applyPendingAuthUpdate({
4759
- conversation: lockedConversation,
4760
- conversationId: authSession.conversationId,
4761
- nextPendingAuth
4762
- });
4778
+ lockedConversation.processing.pendingAuth = nextPendingAuth;
4763
4779
  await persistThreadStateById(threadId, {
4764
4780
  conversation: lockedConversation
4765
4781
  });
@@ -4824,6 +4840,43 @@ async function resumeAuthorizedMcpTurn(args) {
4824
4840
  }
4825
4841
  });
4826
4842
  }
4843
+ async function isCurrentMcpAuthorizationAttempt(authSession, provider) {
4844
+ if (!authSession.channelId || !authSession.threadTs) {
4845
+ return false;
4846
+ }
4847
+ const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4848
+ const currentState = await getPersistedThreadState(threadId);
4849
+ const conversation = coerceThreadConversationState(currentState);
4850
+ const pendingAuth = getConversationPendingAuth({
4851
+ conversation,
4852
+ kind: "mcp",
4853
+ provider,
4854
+ actorId: authSession.userId
4855
+ });
4856
+ return pendingAuth?.authSessionId === authSession.authSessionId && pendingAuth.sessionId === authSession.sessionId;
4857
+ }
4858
+ async function runCurrentMcpCredentialMutation(authSession, provider, mutation) {
4859
+ if (!authSession.channelId || !authSession.threadTs) {
4860
+ throw new McpOAuthAttemptExpiredError();
4861
+ }
4862
+ const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4863
+ const stateAdapter = getStateAdapter();
4864
+ await stateAdapter.connect();
4865
+ const lock = await acquireActiveLock(stateAdapter, threadId);
4866
+ if (!lock) {
4867
+ throw new Error(
4868
+ `Could not acquire MCP OAuth callback lock for ${threadId}`
4869
+ );
4870
+ }
4871
+ try {
4872
+ if (!await isCurrentMcpAuthorizationAttempt(authSession, provider)) {
4873
+ throw new McpOAuthAttemptExpiredError();
4874
+ }
4875
+ return await mutation();
4876
+ } finally {
4877
+ await stateAdapter.releaseLock(lock);
4878
+ }
4879
+ }
4827
4880
  async function GET4(request, provider, waitUntil, options) {
4828
4881
  const url = new URL(request.url);
4829
4882
  const state = url.searchParams.get("state")?.trim();
@@ -4839,7 +4892,23 @@ async function GET4(request, provider, waitUntil, options) {
4839
4892
  return htmlResponse("missing_code");
4840
4893
  }
4841
4894
  try {
4842
- const authSession = await finalizeMcpAuthorization(provider, state, code);
4895
+ const pendingSession = await getMcpAuthSession(state);
4896
+ if (!pendingSession || pendingSession.provider !== provider || !await isCurrentMcpAuthorizationAttempt(pendingSession, provider)) {
4897
+ if (pendingSession) {
4898
+ await deleteMcpAuthSession(pendingSession.authSessionId);
4899
+ }
4900
+ return htmlResponse("expired");
4901
+ }
4902
+ const authSession = await finalizeMcpAuthorization(
4903
+ provider,
4904
+ state,
4905
+ code,
4906
+ async (mutation) => await runCurrentMcpCredentialMutation(
4907
+ pendingSession,
4908
+ provider,
4909
+ mutation
4910
+ )
4911
+ );
4843
4912
  try {
4844
4913
  await deleteMcpAuthSession(authSession.authSessionId);
4845
4914
  } catch (cleanupError) {
@@ -4860,6 +4929,10 @@ async function GET4(request, provider, waitUntil, options) {
4860
4929
  );
4861
4930
  return htmlResponse("success");
4862
4931
  } catch (callbackError) {
4932
+ if (callbackError instanceof McpOAuthAttemptExpiredError) {
4933
+ await deleteMcpAuthSession(state);
4934
+ return htmlResponse("expired");
4935
+ }
4863
4936
  logException(
4864
4937
  callbackError,
4865
4938
  "mcp_oauth_callback_failed",
@@ -5304,11 +5377,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5304
5377
  },
5305
5378
  durability: {
5306
5379
  recordPendingAuth: async (nextPendingAuth) => {
5307
- await applyPendingAuthUpdate({
5308
- conversation: lockedConversation,
5309
- conversationId: stored.resumeConversationId,
5310
- nextPendingAuth
5311
- });
5380
+ lockedConversation.processing.pendingAuth = nextPendingAuth;
5312
5381
  await persistThreadStateById(stored.resumeConversationId, {
5313
5382
  conversation: lockedConversation
5314
5383
  });
@@ -10108,11 +10177,7 @@ function createReplyToThread(deps) {
10108
10177
  });
10109
10178
  },
10110
10179
  recordPendingAuth: async (pendingAuth) => {
10111
- await applyPendingAuthUpdate({
10112
- conversation: preparedState.conversation,
10113
- conversationId,
10114
- nextPendingAuth: pendingAuth
10115
- });
10180
+ preparedState.conversation.processing.pendingAuth = pendingAuth;
10116
10181
  await persistThreadState(thread, {
10117
10182
  conversation: preparedState.conversation
10118
10183
  });
@@ -11200,11 +11265,7 @@ async function continueSlackAgentRun(payload, options) {
11200
11265
  },
11201
11266
  durability: {
11202
11267
  recordPendingAuth: async (nextPendingAuth) => {
11203
- await applyPendingAuthUpdate({
11204
- conversation,
11205
- conversationId: payload.conversationId,
11206
- nextPendingAuth
11207
- });
11268
+ conversation.processing.pendingAuth = nextPendingAuth;
11208
11269
  await persistThreadStateById(payload.conversationId, {
11209
11270
  conversation
11210
11271
  });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  pluginCatalogRuntime
3
- } from "./chunk-LMJPRYHP.js";
4
- import "./chunk-I35CTGPO.js";
3
+ } from "./chunk-77LHNQK4.js";
4
+ import "./chunk-AEWZ7F65.js";
5
5
  import "./chunk-OB42YVAE.js";
6
6
  import "./chunk-6GWA276C.js";
7
7
  import "./chunk-B3IT5END.js";
@@ -1,6 +1,6 @@
1
1
  import type { PiMessage } from "@/chat/pi/messages";
2
2
  import { type LogContext } from "@/chat/logging";
3
- import { type ConversationPrivacy } from "@/chat/conversation-privacy";
3
+ import type { ConversationPrivacy } from "@/chat/conversation-privacy";
4
4
  import type { SkillInvocation, SkillMetadata } from "@/chat/skills";
5
5
  import type { ActiveMcpCatalogSummary } from "@/chat/tool-support/skill/mcp-tool-summary";
6
6
  import type { ToolRuntimeContext } from "@/chat/tools/types";
@@ -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
  }
@@ -2,4 +2,4 @@ import type { BoundDispatchOptions, SlackDispatchOptions } from "./types";
2
2
  /** Validate plugin-provided dispatch options before core persists them. */
3
3
  export declare function validateDispatchOptions(options: unknown): asserts options is SlackDispatchOptions;
4
4
  /** Verify runtime-owned access requirements for delegated dispatch credentials. */
5
- export declare function verifyDispatchCredentialSubjectAccess(options: BoundDispatchOptions): Promise<void>;
5
+ export declare function verifyDispatchCredentialSubjectAccess(options: BoundDispatchOptions, plugin: string): Promise<void>;
@@ -25,14 +25,10 @@ export declare function canExposeConversationPayload(input: {
25
25
  export declare function getCurrentConversationPrivacy(): ConversationPrivacy | undefined;
26
26
  /** Bind one conversation privacy mode to all async work in a turn. */
27
27
  export declare function runWithConversationPrivacy<T>(privacy: ConversationPrivacy, callback: () => T): T;
28
- /** Convert a GenAI message into safe metadata for private trace contexts. */
29
- export declare function toGenAiMessageMetadata(message: unknown): Record<string, unknown>;
30
- /** Convert raw text into size-only metadata for private trace contexts. */
31
- export declare function toGenAiTextMetadata(text: string): Record<string, unknown>;
32
28
  /** Convert an arbitrary payload into safe structured metadata for trace data fields. */
33
29
  export declare function toGenAiPayloadMetadata(payload: unknown): Record<string, unknown>;
34
30
  /** Convert an arbitrary payload into safe flattened trace attributes. */
35
- export declare function toGenAiPayloadTraceAttributes(prefix: string, payload: unknown): Record<string, TraceAttributeValue>;
31
+ export declare function toGenAiPayloadTraceAttributes(prefix: "gen_ai.tool.call.arguments" | "gen_ai.tool.call.result", payload: unknown): Record<string, TraceAttributeValue>;
36
32
  /**
37
33
  * Map a pi-ai AssistantMessage to the canonical gen_ai.output.messages shape:
38
34
  * `{ role, parts: [...], finish_reason }` — drops provider noise fields.
@@ -44,5 +40,5 @@ export declare function toCanonicalOutputMessage(message: AssistantMessage): Rec
44
40
  */
45
41
  export declare function toCanonicalInputMessage(message: Message): Record<string, unknown>;
46
42
  /** Summarize a message list without exposing raw message content. */
47
- export declare function toGenAiMessagesTraceAttributes(prefix: string, messages: unknown[]): Record<string, TraceAttributeValue>;
43
+ export declare function toGenAiMessagesTraceAttributes(prefix: "gen_ai.input" | "gen_ai.output", messages: unknown[]): Record<string, TraceAttributeValue>;
48
44
  export {};
@@ -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. */