@sentry/junior 0.67.0 → 0.67.2

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.
package/dist/app.js CHANGED
@@ -47,13 +47,13 @@ import {
47
47
  validateAgentPlugins,
48
48
  verifySlackDirectCredentialSubject,
49
49
  withSlackRetries
50
- } from "./chunk-HFMZE67J.js";
50
+ } from "./chunk-PLJTW7IB.js";
51
51
  import {
52
52
  discoverSkills,
53
53
  findSkillByName,
54
54
  loadSkillsByName,
55
55
  parseSkillInvocation
56
- } from "./chunk-YL5G5YC4.js";
56
+ } from "./chunk-V47RLIO2.js";
57
57
  import {
58
58
  buildNonInteractiveShellScript,
59
59
  createSandboxInstance,
@@ -62,7 +62,7 @@ import {
62
62
  isSnapshotMissingError,
63
63
  resolveRuntimeDependencySnapshot,
64
64
  runNonInteractiveCommand
65
- } from "./chunk-KWEE2436.js";
65
+ } from "./chunk-PIVOJIUD.js";
66
66
  import {
67
67
  ACTIVE_LOCK_TTL_MS,
68
68
  FUNCTION_TIMEOUT_BUFFER_SECONDS,
@@ -83,8 +83,10 @@ import {
83
83
  getSlackBotToken,
84
84
  getSlackClientId,
85
85
  getSlackClientSecret,
86
+ getSlackReactionConfig,
86
87
  getSlackSigningSecret,
87
88
  getStateAdapter,
89
+ normalizeSlackEmojiName,
88
90
  parseSlackThreadId,
89
91
  resolveConversationPrivacy,
90
92
  resolveGatewayModel,
@@ -92,14 +94,16 @@ import {
92
94
  resolveSlackChannelIdFromThreadId,
93
95
  sandboxSkillDir,
94
96
  sandboxSkillFile,
97
+ setSlackReactionConfig,
95
98
  toGenAiMessageMetadata,
96
99
  toGenAiMessagesTraceAttributes,
97
100
  toGenAiPayloadMetadata,
98
101
  toGenAiPayloadTraceAttributes,
99
102
  toGenAiTextMetadata
100
- } from "./chunk-NWU2Z6SM.js";
103
+ } from "./chunk-EBVQXCD2.js";
101
104
  import {
102
105
  CredentialUnavailableError,
106
+ buildActorIdentity,
103
107
  buildOAuthTokenRequest,
104
108
  buildTurnFailureResponse,
105
109
  createChatSdkLogger,
@@ -116,6 +120,7 @@ import {
116
120
  getPluginOAuthConfig,
117
121
  getPluginProviders,
118
122
  hasRequiredOAuthScope,
123
+ isActorUserId,
119
124
  isPluginConfigKey,
120
125
  isPluginProvider,
121
126
  isRecord,
@@ -124,6 +129,7 @@ import {
124
129
  logInfo,
125
130
  logWarn,
126
131
  normalizeGenAiFinishReason,
132
+ parseActorUserId,
127
133
  parseCredentialContext,
128
134
  parseOAuthTokenResponse,
129
135
  resolveAuthTokenPlaceholder,
@@ -134,11 +140,12 @@ import {
134
140
  setSpanAttributes,
135
141
  setSpanStatus,
136
142
  setTags,
143
+ slackActorIdentity,
137
144
  toOptionalNumber,
138
145
  toOptionalString,
139
146
  withContext,
140
147
  withSpan
141
- } from "./chunk-6QWWMZCK.js";
148
+ } from "./chunk-OIIXZOOC.js";
142
149
  import {
143
150
  sentry_exports
144
151
  } from "./chunk-Z3YD6NHK.js";
@@ -3335,17 +3342,6 @@ function createSlackChannelListMessagesTool(context) {
3335
3342
  // src/chat/tools/slack/channel-post-message.ts
3336
3343
  import { Type as Type14 } from "@sinclair/typebox";
3337
3344
 
3338
- // src/chat/slack/emoji.ts
3339
- var SLACK_EMOJI_NAME_RE = /^(?:[a-z0-9_+-]+)(?:::(?:skin-tone-[2-6]))?$/;
3340
- function normalizeSlackEmojiName(value) {
3341
- const trimmed = value.trim().toLowerCase();
3342
- if (!trimmed) {
3343
- return null;
3344
- }
3345
- const normalized = trimmed.startsWith(":") && trimmed.endsWith(":") ? trimmed.slice(1, -1) : trimmed;
3346
- return SLACK_EMOJI_NAME_RE.test(normalized) ? normalized : null;
3347
- }
3348
-
3349
3345
  // src/chat/slack/outbound.ts
3350
3346
  var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
3351
3347
  function requireSlackConversationId(channelId, action) {
@@ -3453,7 +3449,7 @@ async function postSlackEphemeralMessage(input) {
3453
3449
  input.channelId,
3454
3450
  "Slack ephemeral message posting"
3455
3451
  );
3456
- const userId = input.userId.trim();
3452
+ const userId = parseActorUserId(input.userId);
3457
3453
  if (!userId) {
3458
3454
  throw new Error("Slack ephemeral message posting requires a user ID");
3459
3455
  }
@@ -11224,13 +11220,17 @@ function extractSliceUsage(messages, beforeMessageCount) {
11224
11220
  return hasAgentTurnUsage(usage) ? usage : void 0;
11225
11221
  }
11226
11222
  function requesterFromContext(requester, requesterId) {
11227
- const identity = {
11228
- ...requester?.email ? { email: requester.email } : {},
11229
- ...requester?.fullName ? { fullName: requester.fullName } : {},
11230
- ...requesterId ?? requester?.userId ? { slackUserId: requesterId ?? requester?.userId } : {},
11231
- ...requester?.userName ? { slackUserName: requester.userName } : {}
11223
+ const identity = actorRequesterFromContext(requester, requesterId);
11224
+ const agentRequester = {
11225
+ ...identity?.email ? { email: identity.email } : {},
11226
+ ...identity?.fullName ? { fullName: identity.fullName } : {},
11227
+ ...identity?.userId ? { slackUserId: identity.userId } : {},
11228
+ ...identity?.userName ? { slackUserName: identity.userName } : {}
11232
11229
  };
11233
- return Object.keys(identity).length > 0 ? identity : void 0;
11230
+ return Object.keys(agentRequester).length > 0 ? agentRequester : void 0;
11231
+ }
11232
+ function actorRequesterFromContext(requester, requesterId) {
11233
+ return buildActorIdentity(requester, requesterId);
11234
11234
  }
11235
11235
  function surfaceFromContext(context) {
11236
11236
  if (context.surface) {
@@ -11364,6 +11364,10 @@ async function generateAssistantReply(messageText2, context = {}) {
11364
11364
  context.requester,
11365
11365
  context.correlation?.requesterId
11366
11366
  );
11367
+ const actorRequester = actorRequesterFromContext(
11368
+ context.requester,
11369
+ context.correlation?.requesterId
11370
+ );
11367
11371
  const surface = surfaceFromContext(context);
11368
11372
  const credentialActor = context.credentialContext?.actor;
11369
11373
  const credentialActorLogContext = credentialActor ? {
@@ -11493,7 +11497,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11493
11497
  const authRequesterId = context.credentialContext?.actor.type === "user" ? context.credentialContext.actor.userId : void 0;
11494
11498
  const userTokenStore = createUserTokenStore();
11495
11499
  const agentPluginHooks = createAgentPluginHookRunner({
11496
- requester: context.requester
11500
+ requester: actorRequester
11497
11501
  });
11498
11502
  sandboxExecutor = createSandboxExecutor({
11499
11503
  sandboxId: context.sandbox?.sandboxId,
@@ -11510,7 +11514,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11510
11514
  const result = await maybeExecuteJrRpcCustomCommand(command, {
11511
11515
  activeSkill: skillSandbox.getActiveSkill(),
11512
11516
  channelConfiguration: context.channelConfiguration,
11513
- requesterId: context.requester?.userId,
11517
+ requesterId: actorRequester?.userId,
11514
11518
  onConfigurationValueChanged: (key, value) => {
11515
11519
  if (value === void 0) {
11516
11520
  delete configurationValues[key];
@@ -11746,7 +11750,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11746
11750
  {
11747
11751
  channelId: toolChannelId,
11748
11752
  channelCapabilities,
11749
- requester: context.requester,
11753
+ requester: actorRequester,
11750
11754
  teamId: context.correlation?.teamId,
11751
11755
  messageTs: context.correlation?.messageTs,
11752
11756
  threadTs: context.correlation?.threadTs,
@@ -11809,7 +11813,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11809
11813
  slackConversation: context.slackConversation
11810
11814
  },
11811
11815
  invocation: skillInvocation,
11812
- requester: context.requester,
11816
+ requester: actorRequester,
11813
11817
  artifactState: context.artifactState,
11814
11818
  configuration: configurationValues
11815
11819
  }) : null;
@@ -12445,6 +12449,7 @@ var CONTEXT_MIN_LIVE_MESSAGES = 12;
12445
12449
  var CONTEXT_COMPACTION_BATCH_SIZE = 24;
12446
12450
  var CONTEXT_MAX_COMPACTIONS = 16;
12447
12451
  var CONTEXT_MAX_MESSAGE_CHARS = 3200;
12452
+ var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
12448
12453
  function generateConversationId(prefix) {
12449
12454
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
12450
12455
  }
@@ -12464,7 +12469,7 @@ function buildImageContextSuffix(message, conversation) {
12464
12469
  return ` [image context: ${summaries.join(" | ")}]`;
12465
12470
  }
12466
12471
  function renderConversationMessageLine(message, conversation) {
12467
- const displayName = message.author?.fullName || message.author?.userName || (message.role === "assistant" ? botConfig.userName : message.role);
12472
+ const displayName = conversationAuthorDisplayName(message);
12468
12473
  const markers = [];
12469
12474
  if (message.meta?.replied === false) {
12470
12475
  markers.push(
@@ -12478,6 +12483,18 @@ function renderConversationMessageLine(message, conversation) {
12478
12483
  const imageContext = buildImageContextSuffix(message, conversation);
12479
12484
  return `[${message.role}] ${displayName}: ${message.text}${imageContext}${markerSuffix}`;
12480
12485
  }
12486
+ function conversationAuthorDisplayName(message) {
12487
+ const author = message.author;
12488
+ const fullName = authorDisplayField(author?.fullName, author?.userId);
12489
+ const userName = authorDisplayField(author?.userName, author?.userId);
12490
+ return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
12491
+ }
12492
+ function authorDisplayField(value, userId) {
12493
+ if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
12494
+ return void 0;
12495
+ }
12496
+ return value;
12497
+ }
12481
12498
  function updateConversationStats(conversation) {
12482
12499
  const contextText = buildConversationContext(conversation);
12483
12500
  conversation.stats.estimatedContextTokens = estimateTextTokens(
@@ -12547,11 +12564,12 @@ function buildConversationContext(conversation, options = {}) {
12547
12564
  }
12548
12565
  lines.push("<thread-transcript>");
12549
12566
  for (const [index, message] of messages.entries()) {
12550
- const author = escapeXml(message.author?.userName ?? message.role);
12567
+ const author = escapeXml(conversationAuthorDisplayName(message));
12568
+ const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
12551
12569
  const ts = new Date(message.createdAtMs).toISOString();
12552
12570
  const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
12553
12571
  lines.push(
12554
- ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
12572
+ ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
12555
12573
  renderConversationMessageLine(message, conversation),
12556
12574
  " </message>"
12557
12575
  );
@@ -14511,7 +14529,7 @@ function validateDispatchOptions(options) {
14511
14529
  if (options.credentialSubject.type !== "user") {
14512
14530
  throw new Error("Dispatch credentialSubject type must be user");
14513
14531
  }
14514
- if (!options.credentialSubject.userId.trim()) {
14532
+ if (!isActorUserId(options.credentialSubject.userId)) {
14515
14533
  throw new Error("Dispatch credentialSubject userId is required");
14516
14534
  }
14517
14535
  if (options.credentialSubject.allowedWhen !== "private-direct-conversation") {
@@ -15464,15 +15482,13 @@ function getTeamId(message) {
15464
15482
  }
15465
15483
 
15466
15484
  // src/chat/runtime/processing-reaction.ts
15467
- var PROCESSING_REACTION_EMOJI = "eyes";
15468
- var COMPLETED_REACTION_EMOJI = "white_check_mark";
15469
15485
  var noProcessingReaction = {
15470
15486
  complete: async () => void 0,
15471
15487
  keep: () => void 0,
15472
15488
  stop: async () => void 0
15473
15489
  };
15474
15490
  function isProcessingReactionEmoji(value) {
15475
- return typeof value === "string" && normalizeSlackEmojiName(value) === PROCESSING_REACTION_EMOJI;
15491
+ return typeof value === "string" && normalizeSlackEmojiName(value) === getChatConfig().slack.processingReactionEmoji;
15476
15492
  }
15477
15493
  function shouldKeepProcessingReactionForToolInvocation(input) {
15478
15494
  return input.toolName === "slackMessageAddReaction" && isProcessingReactionEmoji(input.params.emoji);
@@ -15498,7 +15514,7 @@ async function startSlackProcessingReactionForMessage(args) {
15498
15514
  await addReactionToMessage({
15499
15515
  channelId: args.channelId,
15500
15516
  timestamp: args.timestamp,
15501
- emoji: PROCESSING_REACTION_EMOJI
15517
+ emoji: getChatConfig().slack.processingReactionEmoji
15502
15518
  });
15503
15519
  } catch (error) {
15504
15520
  args.logException(
@@ -15523,7 +15539,7 @@ async function startSlackProcessingReactionForMessage(args) {
15523
15539
  await removeReactionFromMessage({
15524
15540
  channelId: args.channelId,
15525
15541
  timestamp: args.timestamp,
15526
- emoji: PROCESSING_REACTION_EMOJI
15542
+ emoji: getChatConfig().slack.processingReactionEmoji
15527
15543
  });
15528
15544
  return true;
15529
15545
  } catch (error) {
@@ -15550,7 +15566,7 @@ async function startSlackProcessingReactionForMessage(args) {
15550
15566
  await addReactionToMessage({
15551
15567
  channelId: args.channelId,
15552
15568
  timestamp: args.timestamp,
15553
- emoji: COMPLETED_REACTION_EMOJI
15569
+ emoji: getChatConfig().slack.completedReactionEmoji
15554
15570
  });
15555
15571
  } catch (error) {
15556
15572
  args.logException(
@@ -15997,6 +16013,87 @@ function htmlCallbackResponse(title, message, status) {
15997
16013
  });
15998
16014
  }
15999
16015
 
16016
+ // src/chat/slack/user.ts
16017
+ var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
16018
+ var userCache = /* @__PURE__ */ new Map();
16019
+ function readFromCache(userId) {
16020
+ const hit = userCache.get(userId);
16021
+ if (!hit) return null;
16022
+ if (hit.expiresAt < Date.now()) {
16023
+ userCache.delete(userId);
16024
+ return null;
16025
+ }
16026
+ return hit.value;
16027
+ }
16028
+ function writeToCache(userId, value) {
16029
+ userCache.set(userId, {
16030
+ value,
16031
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
16032
+ });
16033
+ }
16034
+ async function lookupSlackUser(userId) {
16035
+ if (!userId) {
16036
+ return null;
16037
+ }
16038
+ const cached = readFromCache(userId);
16039
+ if (cached) {
16040
+ return cached;
16041
+ }
16042
+ const token = getSlackBotToken();
16043
+ if (!token) {
16044
+ return null;
16045
+ }
16046
+ try {
16047
+ const response = await fetch(
16048
+ `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
16049
+ {
16050
+ headers: {
16051
+ authorization: `Bearer ${token}`
16052
+ }
16053
+ }
16054
+ );
16055
+ if (!response.ok) {
16056
+ logWarn(
16057
+ "slack_user_lookup_failed",
16058
+ {},
16059
+ {
16060
+ "enduser.id": userId,
16061
+ "http.response.status_code": response.status
16062
+ },
16063
+ "Slack user lookup request failed"
16064
+ );
16065
+ return null;
16066
+ }
16067
+ const payload = await response.json();
16068
+ if (!payload.ok || !payload.user) {
16069
+ return null;
16070
+ }
16071
+ const userName = payload.user.name?.trim() || void 0;
16072
+ const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
16073
+ const result = {
16074
+ userName,
16075
+ fullName,
16076
+ email: payload.user.profile?.email?.trim() || void 0
16077
+ };
16078
+ writeToCache(userId, result);
16079
+ return result;
16080
+ } catch (error) {
16081
+ logWarn(
16082
+ "slack_user_lookup_failed",
16083
+ {},
16084
+ {
16085
+ "enduser.id": userId,
16086
+ "exception.message": error instanceof Error ? error.message : String(error)
16087
+ },
16088
+ "Slack user lookup failed with exception"
16089
+ );
16090
+ return null;
16091
+ }
16092
+ }
16093
+ async function lookupSlackActorIdentity(userId) {
16094
+ return slackActorIdentity(userId, await lookupSlackUser(userId));
16095
+ }
16096
+
16000
16097
  // src/handlers/mcp-oauth-callback.ts
16001
16098
  var CALLBACK_PAGES = {
16002
16099
  missing_state: {
@@ -16197,6 +16294,7 @@ async function resumeAuthorizedMcpTurn(args) {
16197
16294
  }),
16198
16295
  ttlMs: THREAD_STATE_TTL_MS4
16199
16296
  });
16297
+ const requester = await lookupSlackActorIdentity(authSession.userId);
16200
16298
  return {
16201
16299
  messageText: lockedUserMessage.text,
16202
16300
  messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
@@ -16204,11 +16302,7 @@ async function resumeAuthorizedMcpTurn(args) {
16204
16302
  credentialContext: {
16205
16303
  actor: { type: "user", userId: authSession.userId }
16206
16304
  },
16207
- requester: {
16208
- userId: authSession.userId,
16209
- userName: lockedUserMessage.author?.userName,
16210
- fullName: lockedUserMessage.author?.fullName
16211
- },
16305
+ requester,
16212
16306
  correlation: {
16213
16307
  conversationId: authSession.conversationId,
16214
16308
  turnId: lockedSessionId,
@@ -16691,6 +16785,9 @@ async function resumeOAuthSessionRecordTurn(stored) {
16691
16785
  }),
16692
16786
  ttlMs: THREAD_STATE_TTL_MS5
16693
16787
  });
16788
+ const requester = await lookupSlackActorIdentity(
16789
+ lockedUserMessage.author.userId
16790
+ );
16694
16791
  return {
16695
16792
  messageText: stored.pendingMessage ?? lockedUserMessage.text,
16696
16793
  messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
@@ -16701,11 +16798,7 @@ async function resumeOAuthSessionRecordTurn(stored) {
16701
16798
  userId: lockedUserMessage.author.userId
16702
16799
  }
16703
16800
  },
16704
- requester: {
16705
- userId: lockedUserMessage.author.userId,
16706
- userName: lockedUserMessage.author.userName,
16707
- fullName: lockedUserMessage.author.fullName
16708
- },
16801
+ requester,
16709
16802
  correlation: {
16710
16803
  conversationId: stored.resumeConversationId,
16711
16804
  turnId: lockedSessionId,
@@ -16801,6 +16894,7 @@ async function resumePendingOAuthMessage(stored) {
16801
16894
  const conversationContext = buildConversationContext(conversation, {
16802
16895
  excludeMessageId: latestUserMessage?.id
16803
16896
  });
16897
+ const requester = await lookupSlackActorIdentity(stored.userId);
16804
16898
  await resumeAuthorizedRequest({
16805
16899
  messageText: stored.pendingMessage,
16806
16900
  channelId: stored.channelId,
@@ -16811,7 +16905,7 @@ async function resumePendingOAuthMessage(stored) {
16811
16905
  credentialContext: {
16812
16906
  actor: { type: "user", userId: stored.userId }
16813
16907
  },
16814
- requester: { userId: stored.userId },
16908
+ requester,
16815
16909
  conversationContext,
16816
16910
  piMessages: conversation.piMessages,
16817
16911
  configuration: stored.configuration
@@ -17690,6 +17784,9 @@ async function resumeTimedOutTurn(payload, options = {}) {
17690
17784
  excludeMessageId: userMessage2.id
17691
17785
  });
17692
17786
  const sandbox = getPersistedSandboxState(currentState);
17787
+ const requester = await lookupSlackActorIdentity(
17788
+ userMessage2.author.userId
17789
+ );
17693
17790
  return {
17694
17791
  messageText: userMessage2.text,
17695
17792
  messageTs: getTurnUserSlackMessageTs(userMessage2),
@@ -17700,11 +17797,7 @@ async function resumeTimedOutTurn(payload, options = {}) {
17700
17797
  userId: userMessage2.author.userId
17701
17798
  }
17702
17799
  },
17703
- requester: {
17704
- userId: userMessage2.author.userId,
17705
- userName: userMessage2.author.userName,
17706
- fullName: userMessage2.author.fullName
17707
- },
17800
+ requester,
17708
17801
  correlation: {
17709
17802
  conversationId: payload.conversationId,
17710
17803
  turnId: payload.sessionId,
@@ -18194,6 +18287,60 @@ function combineTurnText(queuedMessages, latestText) {
18194
18287
  };
18195
18288
  }
18196
18289
 
18290
+ // src/chat/services/message-actor-identity.ts
18291
+ var messageActors = /* @__PURE__ */ new WeakMap();
18292
+ function canonicalUserId(author, identity) {
18293
+ const authorUserId = parseActorUserId(author.userId);
18294
+ const identityUserId = parseActorUserId(identity.userId);
18295
+ if (authorUserId && identityUserId && authorUserId !== identityUserId) {
18296
+ throw new Error("Message actor identity user id mismatch");
18297
+ }
18298
+ const userId = authorUserId ?? identityUserId;
18299
+ if (!userId) {
18300
+ throw new Error("Message actor identity requires a user id");
18301
+ }
18302
+ return userId;
18303
+ }
18304
+ function actorIdentityFromAuthor(author) {
18305
+ const userId = parseActorUserId(author.userId);
18306
+ return userId ? { userId } : void 0;
18307
+ }
18308
+ function applyIdentityToAuthor(author, identity) {
18309
+ if (!isActorUserId(identity.userId)) {
18310
+ throw new Error("Message actor identity requires a user id");
18311
+ }
18312
+ author.userId = identity.userId;
18313
+ author.userName = identity.userName ?? "";
18314
+ author.fullName = identity.fullName ?? "";
18315
+ }
18316
+ function bindMessageActorIdentity(message, identity) {
18317
+ const userId = canonicalUserId(message.author, identity);
18318
+ const actorIdentity = buildActorIdentity(identity, userId);
18319
+ if (!actorIdentity?.userId) {
18320
+ throw new Error("Message actor identity requires a user id");
18321
+ }
18322
+ messageActors.set(message, actorIdentity);
18323
+ applyIdentityToAuthor(message.author, actorIdentity);
18324
+ return actorIdentity;
18325
+ }
18326
+ function getMessageActorIdentity(message) {
18327
+ return messageActors.get(message) ?? actorIdentityFromAuthor(message.author);
18328
+ }
18329
+ async function ensureSlackMessageActorIdentity(message, lookupSlackUser2) {
18330
+ const existing = messageActors.get(message);
18331
+ if (existing) {
18332
+ return existing;
18333
+ }
18334
+ const userId = parseActorUserId(message.author.userId);
18335
+ if (!userId) {
18336
+ throw new Error("Slack message actor identity requires a user id");
18337
+ }
18338
+ return bindMessageActorIdentity(
18339
+ message,
18340
+ slackActorIdentity(userId, await lookupSlackUser2(userId))
18341
+ );
18342
+ }
18343
+
18197
18344
  // src/chat/runtime/slack-runtime.ts
18198
18345
  var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
18199
18346
  async function maybeHandleThreadOptOutDecision(args) {
@@ -18253,6 +18400,9 @@ function buildLogContext(deps, args) {
18253
18400
  modelId: deps.modelId
18254
18401
  };
18255
18402
  }
18403
+ function requesterUserName(message) {
18404
+ return getMessageActorIdentity(message)?.userName;
18405
+ }
18256
18406
  function createSlackTurnRuntime(deps) {
18257
18407
  const logContext = (args) => buildLogContext(deps, args);
18258
18408
  const createToolInvocationHook = (processingReaction, hooks) => {
@@ -18326,7 +18476,7 @@ function createSlackTurnRuntime(deps) {
18326
18476
  logContext({
18327
18477
  threadId: args.context.threadId,
18328
18478
  requesterId: args.context.requesterId,
18329
- requesterUserName: args.message.author.userName,
18479
+ requesterUserName: requesterUserName(args.message),
18330
18480
  channelId: args.context.channelId,
18331
18481
  runId: args.context.runId
18332
18482
  }),
@@ -18368,7 +18518,7 @@ function createSlackTurnRuntime(deps) {
18368
18518
  threadId,
18369
18519
  channelId,
18370
18520
  requesterId: message.author.userId,
18371
- requesterUserName: message.author.userName,
18521
+ requesterUserName: requesterUserName(message),
18372
18522
  runId
18373
18523
  });
18374
18524
  processingReaction = await processingReactions.start(context, message);
@@ -18428,7 +18578,7 @@ function createSlackTurnRuntime(deps) {
18428
18578
  const errorContext = logContext({
18429
18579
  threadId: deps.getThreadId(thread, message),
18430
18580
  requesterId: message.author.userId,
18431
- requesterUserName: message.author.userName,
18581
+ requesterUserName: requesterUserName(message),
18432
18582
  channelId: deps.getChannelId(thread, message),
18433
18583
  runId: deps.getRunId(thread, message)
18434
18584
  });
@@ -18484,7 +18634,7 @@ function createSlackTurnRuntime(deps) {
18484
18634
  const turnContext = logContext({
18485
18635
  threadId,
18486
18636
  requesterId: message.author.userId,
18487
- requesterUserName: message.author.userName,
18637
+ requesterUserName: requesterUserName(message),
18488
18638
  channelId,
18489
18639
  runId
18490
18640
  });
@@ -18634,7 +18784,7 @@ function createSlackTurnRuntime(deps) {
18634
18784
  const errorContext = logContext({
18635
18785
  threadId: deps.getThreadId(thread, message),
18636
18786
  requesterId: message.author.userId,
18637
- requesterUserName: message.author.userName,
18787
+ requesterUserName: requesterUserName(message),
18638
18788
  channelId: deps.getChannelId(thread, message),
18639
18789
  runId: deps.getRunId(thread, message)
18640
18790
  });
@@ -18992,84 +19142,6 @@ function createContextCompactor(deps) {
18992
19142
  };
18993
19143
  }
18994
19144
 
18995
- // src/chat/slack/user.ts
18996
- var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
18997
- var userCache = /* @__PURE__ */ new Map();
18998
- function readFromCache(userId) {
18999
- const hit = userCache.get(userId);
19000
- if (!hit) return null;
19001
- if (hit.expiresAt < Date.now()) {
19002
- userCache.delete(userId);
19003
- return null;
19004
- }
19005
- return hit.value;
19006
- }
19007
- function writeToCache(userId, value) {
19008
- userCache.set(userId, {
19009
- value,
19010
- expiresAt: Date.now() + USER_CACHE_TTL_MS
19011
- });
19012
- }
19013
- async function lookupSlackUser(userId) {
19014
- if (!userId) {
19015
- return null;
19016
- }
19017
- const cached = readFromCache(userId);
19018
- if (cached) {
19019
- return cached;
19020
- }
19021
- const token = getSlackBotToken();
19022
- if (!token) {
19023
- return null;
19024
- }
19025
- try {
19026
- const response = await fetch(
19027
- `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
19028
- {
19029
- headers: {
19030
- authorization: `Bearer ${token}`
19031
- }
19032
- }
19033
- );
19034
- if (!response.ok) {
19035
- logWarn(
19036
- "slack_user_lookup_failed",
19037
- {},
19038
- {
19039
- "enduser.id": userId,
19040
- "http.response.status_code": response.status
19041
- },
19042
- "Slack user lookup request failed"
19043
- );
19044
- return null;
19045
- }
19046
- const payload = await response.json();
19047
- if (!payload.ok || !payload.user) {
19048
- return null;
19049
- }
19050
- const userName = payload.user.name?.trim() || void 0;
19051
- const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
19052
- const result = {
19053
- userName,
19054
- fullName,
19055
- email: payload.user.profile?.email?.trim() || void 0
19056
- };
19057
- writeToCache(userId, result);
19058
- return result;
19059
- } catch (error) {
19060
- logWarn(
19061
- "slack_user_lookup_failed",
19062
- {},
19063
- {
19064
- "enduser.id": userId,
19065
- "exception.message": error instanceof Error ? error.message : String(error)
19066
- },
19067
- "Slack user lookup failed with exception"
19068
- );
19069
- return null;
19070
- }
19071
- }
19072
-
19073
19145
  // src/chat/services/subscribed-reply-policy.ts
19074
19146
  function createSubscribedReplyPolicy(deps) {
19075
19147
  return async (args) => {
@@ -19793,14 +19865,14 @@ function collectCanvasUrls(artifacts) {
19793
19865
  ].filter((url) => typeof url === "string" && url !== "")
19794
19866
  );
19795
19867
  }
19796
- function turnRequester(args) {
19868
+ function turnRequester(identity) {
19797
19869
  const requester = {
19798
- ...args.email ? { email: args.email } : {},
19799
- ...args.fullName ? { fullName: args.fullName } : {},
19800
- ...args.userId ? { slackUserId: args.userId } : {},
19801
- ...args.userName ? { slackUserName: args.userName } : {}
19870
+ ...identity.email ? { email: identity.email } : {},
19871
+ ...identity.fullName ? { fullName: identity.fullName } : {},
19872
+ ...identity.userId ? { slackUserId: identity.userId } : {},
19873
+ ...identity.userName ? { slackUserName: identity.userName } : {}
19802
19874
  };
19803
- return Object.keys(requester).length > 0 ? requester : void 0;
19875
+ return requester;
19804
19876
  }
19805
19877
  async function resolveChannelName(thread) {
19806
19878
  const existingName = thread.channel.name?.trim();
@@ -19909,6 +19981,19 @@ function createReplyToThread(deps) {
19909
19981
  options.queuedMessages ?? [],
19910
19982
  currentText
19911
19983
  ).userText;
19984
+ await Promise.all(
19985
+ (options.queuedMessages ?? []).map(
19986
+ (queued) => ensureSlackMessageActorIdentity(
19987
+ queued.message,
19988
+ deps.services.lookupSlackUser
19989
+ )
19990
+ )
19991
+ );
19992
+ const requesterIdentity = await ensureSlackMessageActorIdentity(
19993
+ message,
19994
+ deps.services.lookupSlackUser
19995
+ );
19996
+ const requester = turnRequester(requesterIdentity);
19912
19997
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
19913
19998
  thread,
19914
19999
  message,
@@ -19926,15 +20011,6 @@ function createReplyToThread(deps) {
19926
20011
  });
19927
20012
  const slackMessageTs = getSlackMessageTs(message);
19928
20013
  const turnId = buildDeterministicTurnId(message.id);
19929
- const fallbackIdentity = await deps.services.lookupSlackUser(
19930
- message.author.userId
19931
- );
19932
- const requester = turnRequester({
19933
- email: fallbackIdentity?.email,
19934
- fullName: message.author.fullName ?? fallbackIdentity?.fullName,
19935
- userId: message.author.userId,
19936
- userName: message.author.userName ?? fallbackIdentity?.userName
19937
- });
19938
20014
  const turnTraceContext = {
19939
20015
  conversationId,
19940
20016
  slackThreadId: threadId,
@@ -20122,16 +20198,15 @@ function createReplyToThread(deps) {
20122
20198
  conversation: preparedState.conversation
20123
20199
  });
20124
20200
  await options.onTurnStatePersisted?.();
20125
- const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
20126
20201
  if (message.author.userId) {
20127
20202
  setSentryUser({
20128
20203
  id: message.author.userId,
20129
- ...resolvedUserName ? { username: resolvedUserName } : {},
20130
- ...fallbackIdentity?.email ? { email: fallbackIdentity.email } : {}
20204
+ ...requesterIdentity.userName ? { username: requesterIdentity.userName } : {},
20205
+ ...requesterIdentity.email ? { email: requesterIdentity.email } : {}
20131
20206
  });
20132
20207
  }
20133
- if (resolvedUserName) {
20134
- setTags({ slackUserName: resolvedUserName });
20208
+ if (requesterIdentity.userName) {
20209
+ setTags({ slackUserName: requesterIdentity.userName });
20135
20210
  }
20136
20211
  const turnAttachments = collectTurnAttachments(
20137
20212
  message,
@@ -20261,12 +20336,7 @@ function createReplyToThread(deps) {
20261
20336
  credentialContext: {
20262
20337
  actor: { type: "user", userId: message.author.userId }
20263
20338
  },
20264
- requester: {
20265
- userId: message.author.userId,
20266
- userName: message.author.userName ?? fallbackIdentity?.userName,
20267
- fullName: message.author.fullName ?? fallbackIdentity?.fullName,
20268
- email: fallbackIdentity?.email
20269
- },
20339
+ requester: requesterIdentity,
20270
20340
  conversationContext: preparedState.conversationContext,
20271
20341
  artifactState: preparedState.artifacts,
20272
20342
  piMessages,
@@ -20673,6 +20743,7 @@ function resolveMessageText(args) {
20673
20743
  return text || NON_TEXT_MESSAGE_TEXT;
20674
20744
  }
20675
20745
  function toConversationMessage(args) {
20746
+ const actor = getMessageActorIdentity(args.entry);
20676
20747
  const messageHasPotentialImageAttachment = hasPotentialImageAttachment(
20677
20748
  args.entry.attachments
20678
20749
  );
@@ -20683,9 +20754,9 @@ function toConversationMessage(args) {
20683
20754
  text: resolveMessageText(args),
20684
20755
  createdAtMs: args.entry.metadata.dateSent.getTime(),
20685
20756
  author: {
20686
- userId: args.entry.author.userId,
20687
- userName: args.entry.author.userName,
20688
- fullName: args.entry.author.fullName,
20757
+ ...actor?.userId ? { userId: actor.userId } : {},
20758
+ ...actor?.userName ? { userName: actor.userName } : {},
20759
+ ...actor?.fullName ? { fullName: actor.fullName } : {},
20689
20760
  isBot: typeof args.entry.author.isBot === "boolean" ? args.entry.author.isBot : void 0
20690
20761
  },
20691
20762
  meta: {
@@ -21102,6 +21173,13 @@ async function runWithSlackInstallation(args) {
21102
21173
  }
21103
21174
 
21104
21175
  // src/chat/task-execution/slack-work.ts
21176
+ function requireSlackAuthorId(message) {
21177
+ const authorId = parseActorUserId(message.author.userId);
21178
+ if (!authorId) {
21179
+ throw new Error("Slack message requires an actor user id");
21180
+ }
21181
+ return authorId;
21182
+ }
21105
21183
  function getConnectedState3(stateAdapter) {
21106
21184
  return stateAdapter ?? getStateAdapter();
21107
21185
  }
@@ -21126,6 +21204,23 @@ function restoreMessage(args) {
21126
21204
  rehydrateAttachmentFetchers(message);
21127
21205
  return message;
21128
21206
  }
21207
+ async function bindSlackActorIdentities(args) {
21208
+ const byAuthorId = /* @__PURE__ */ new Map();
21209
+ for (const message of args.messages) {
21210
+ const authorId = requireSlackAuthorId(message);
21211
+ byAuthorId.set(authorId, [...byAuthorId.get(authorId) ?? [], message]);
21212
+ }
21213
+ await Promise.all(
21214
+ [...byAuthorId].map(async ([authorId, messages]) => {
21215
+ const profile = await args.lookupSlackUser(authorId);
21216
+ await Promise.all(
21217
+ messages.map(
21218
+ (message) => ensureSlackMessageActorIdentity(message, async () => profile)
21219
+ )
21220
+ );
21221
+ })
21222
+ );
21223
+ }
21129
21224
  function restoreThread(args) {
21130
21225
  const threadId = normalizeIncomingSlackThreadId(
21131
21226
  args.threadJson.id,
@@ -21205,6 +21300,7 @@ function getPendingRecords(work) {
21205
21300
  function createSlackConversationWorker(options) {
21206
21301
  return async (context) => {
21207
21302
  const adapter = options.getSlackAdapter();
21303
+ const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
21208
21304
  const state = getConnectedState3(options.state);
21209
21305
  await state.connect();
21210
21306
  const resumeContinuation = options.resumeAwaitingContinuation ?? resumeAwaitingContinuation;
@@ -21242,6 +21338,10 @@ function createSlackConversationWorker(options) {
21242
21338
  const messages = records.map(
21243
21339
  (record) => restoreMessage({ adapter, record })
21244
21340
  );
21341
+ await bindSlackActorIdentities({
21342
+ lookupSlackUser: actorLookup,
21343
+ messages
21344
+ });
21245
21345
  const latestMessage = messages[messages.length - 1];
21246
21346
  if (!latestMessage) {
21247
21347
  return;
@@ -21328,6 +21428,7 @@ function createSlackConversationWorker(options) {
21328
21428
  };
21329
21429
  }
21330
21430
  function buildSlackInboundMessage(args) {
21431
+ const authorId = requireSlackAuthorId(args.message);
21331
21432
  return {
21332
21433
  conversationId: args.conversationId,
21333
21434
  inboundMessageId: [
@@ -21341,7 +21442,7 @@ function buildSlackInboundMessage(args) {
21341
21442
  receivedAtMs: args.receivedAtMs,
21342
21443
  input: {
21343
21444
  text: args.message.text || " ",
21344
- authorId: args.message.author.userId,
21445
+ authorId,
21345
21446
  attachments: args.message.attachments,
21346
21447
  metadata: {
21347
21448
  platform: "slack",
@@ -21460,7 +21561,8 @@ function extractMessageChangedMention(body, botUserId, adapter) {
21460
21561
  const channelId = event.channel;
21461
21562
  const messageTs = event.message.ts;
21462
21563
  const threadTs = event.message.thread_ts ?? messageTs;
21463
- const userId = event.message.user ?? "unknown";
21564
+ const userId = parseActorUserId(event.message.user);
21565
+ if (!userId) return null;
21464
21566
  const threadId = `slack:${channelId}:${threadTs}`;
21465
21567
  const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
21466
21568
  const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
@@ -21485,8 +21587,9 @@ function extractMessageChangedMention(body, botUserId, adapter) {
21485
21587
  raw,
21486
21588
  author: {
21487
21589
  userId,
21488
- userName: userId,
21489
- fullName: userId,
21590
+ // Raw message_changed payloads do not include profile fields.
21591
+ userName: "",
21592
+ fullName: "",
21490
21593
  isBot: false,
21491
21594
  isMe: false
21492
21595
  }
@@ -21516,10 +21619,17 @@ function isExternalSlackUser(raw) {
21516
21619
  async function postEphemeral(event, text) {
21517
21620
  await event.channel.postEphemeral(event.user, text, { fallbackToDM: false });
21518
21621
  }
21622
+ function requireRequesterId(event) {
21623
+ const userId = parseActorUserId(event.user.userId);
21624
+ if (!userId) {
21625
+ throw new Error("Slack slash command requires a requester user id");
21626
+ }
21627
+ return userId;
21628
+ }
21519
21629
  function getCommandName() {
21520
21630
  return getChatConfig().slack.slashCommand;
21521
21631
  }
21522
- async function handleLink(event, provider) {
21632
+ async function handleLink(event, requesterId, provider) {
21523
21633
  if (!isPluginProvider(provider)) {
21524
21634
  await postEphemeral(event, `Unknown provider: \`${provider}\``);
21525
21635
  return;
@@ -21533,7 +21643,7 @@ async function handleLink(event, provider) {
21533
21643
  }
21534
21644
  const raw = event.raw;
21535
21645
  const result = await startOAuthFlow(provider, {
21536
- requesterId: event.user.userId,
21646
+ requesterId,
21537
21647
  channelId: raw.channel_id
21538
21648
  });
21539
21649
  if (!result.ok) {
@@ -21552,7 +21662,7 @@ async function handleLink(event, provider) {
21552
21662
  );
21553
21663
  }
21554
21664
  }
21555
- async function handleUnlink(event, provider) {
21665
+ async function handleUnlink(event, requesterId, provider) {
21556
21666
  if (!isPluginProvider(provider)) {
21557
21667
  await postEphemeral(event, `Unknown provider: \`${provider}\``);
21558
21668
  return;
@@ -21565,10 +21675,10 @@ async function handleUnlink(event, provider) {
21565
21675
  return;
21566
21676
  }
21567
21677
  const tokenStore = createUserTokenStore();
21568
- await tokenStore.delete(event.user.userId, provider);
21678
+ await tokenStore.delete(requesterId, provider);
21569
21679
  logInfo(
21570
21680
  "slash_command_unlink",
21571
- { slackUserId: event.user.userId },
21681
+ { slackUserId: requesterId },
21572
21682
  { "app.credential.provider": provider },
21573
21683
  `Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
21574
21684
  );
@@ -21594,10 +21704,11 @@ async function handleSlashCommand(event) {
21594
21704
  return;
21595
21705
  }
21596
21706
  const normalized = provider.toLowerCase();
21707
+ const requesterId = requireRequesterId(event);
21597
21708
  if (subcommand === "link") {
21598
- await handleLink(event, normalized);
21709
+ await handleLink(event, requesterId, normalized);
21599
21710
  } else {
21600
- await handleUnlink(event, normalized);
21711
+ await handleUnlink(event, requesterId, normalized);
21601
21712
  }
21602
21713
  }
21603
21714
 
@@ -21837,15 +21948,12 @@ async function handleSlackEvent(args) {
21837
21948
  })
21838
21949
  );
21839
21950
  }
21840
- function buildAuthorFromInteractive(user) {
21841
- const userId = user?.id ?? "unknown";
21842
- return {
21843
- userId,
21844
- userName: user?.username ?? user?.name ?? userId,
21845
- fullName: user?.name ?? user?.username ?? userId,
21846
- isBot: false,
21847
- isMe: false
21848
- };
21951
+ function requireSlackPayloadUserId(value, source) {
21952
+ const userId = parseActorUserId(value);
21953
+ if (!userId) {
21954
+ throw new Error(`${source} is missing a Slack user id`);
21955
+ }
21956
+ return userId;
21849
21957
  }
21850
21958
  async function handleSlashCommandForm(args) {
21851
21959
  const raw = Object.fromEntries(args.params);
@@ -21855,7 +21963,21 @@ async function handleSlashCommandForm(args) {
21855
21963
  adapter: args.adapter,
21856
21964
  stateAdapter: args.state
21857
21965
  });
21858
- const userId = args.params.get("user_id") || "unknown";
21966
+ const userId = requireSlackPayloadUserId(
21967
+ args.params.get("user_id"),
21968
+ "Slack slash command payload"
21969
+ );
21970
+ const userIdentity = buildActorIdentity(
21971
+ {
21972
+ userId,
21973
+ userName: args.params.get("user_name") ?? void 0,
21974
+ fullName: args.params.get("user_name") ?? void 0
21975
+ },
21976
+ userId
21977
+ );
21978
+ if (!userIdentity?.userId) {
21979
+ throw new Error("Slack slash command payload actor identity is invalid");
21980
+ }
21859
21981
  await withSpan(
21860
21982
  "chat.slash_command",
21861
21983
  "chat.slash_command",
@@ -21870,8 +21992,8 @@ async function handleSlashCommandForm(args) {
21870
21992
  raw,
21871
21993
  user: {
21872
21994
  userId,
21873
- userName: args.params.get("user_name") || userId,
21874
- fullName: args.params.get("user_name") || userId,
21995
+ userName: userIdentity.userName ?? "",
21996
+ fullName: userIdentity.fullName ?? "",
21875
21997
  isBot: false,
21876
21998
  isMe: false
21877
21999
  },
@@ -21888,10 +22010,13 @@ async function handleInteractivePayload(args) {
21888
22010
  (candidate) => candidate.action_id === "app_home_disconnect"
21889
22011
  );
21890
22012
  const provider = action?.selected_option?.value ?? action?.value;
21891
- const userId = args.payload.user?.id;
21892
- if (!provider || !userId) {
22013
+ if (!provider) {
21893
22014
  return;
21894
22015
  }
22016
+ const userId = requireSlackPayloadUserId(
22017
+ args.payload.user?.id,
22018
+ "Slack app home disconnect payload"
22019
+ );
21895
22020
  await withSpan(
21896
22021
  "chat.app_home_disconnect",
21897
22022
  "chat.app_home_disconnect",
@@ -21986,7 +22111,7 @@ async function handleSlackForm(args) {
21986
22111
  })
21987
22112
  ).catch((error) => {
21988
22113
  logException(error, "slack_interactive_payload_failed", {
21989
- slackUserId: buildAuthorFromInteractive(payload.user).userId
22114
+ slackUserId: payload.user?.id?.trim() || void 0
21990
22115
  });
21991
22116
  })
21992
22117
  );
@@ -22720,9 +22845,13 @@ async function createApp(options) {
22720
22845
  const previousPluginCatalogConfig = setPluginCatalogConfig(pluginConfig);
22721
22846
  const previousAgentPlugins = setAgentPlugins(agentPlugins);
22722
22847
  const previousConfigDefaults = getConfigDefaults();
22848
+ const previousSlackReactionConfig = getSlackReactionConfig();
22723
22849
  let agentPluginRoutes = [];
22724
22850
  try {
22725
22851
  setConfigDefaults(options?.configDefaults);
22852
+ if (options?.slack) {
22853
+ setSlackReactionConfig(options.slack);
22854
+ }
22726
22855
  if (shouldValidatePluginCatalog) {
22727
22856
  getPluginCatalogSignature();
22728
22857
  validatePluginRegistrations(configuredPlugins?.registrations ?? []);
@@ -22732,6 +22861,7 @@ async function createApp(options) {
22732
22861
  setPluginCatalogConfig(previousPluginCatalogConfig);
22733
22862
  setAgentPlugins(previousAgentPlugins);
22734
22863
  setConfigDefaults(previousConfigDefaults);
22864
+ setSlackReactionConfig(previousSlackReactionConfig);
22735
22865
  throw error;
22736
22866
  }
22737
22867
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();