@sentry/junior 0.67.0 → 0.67.1

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-5UIDU7XR.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-YGGH2742.js";
66
66
  import {
67
67
  ACTIVE_LOCK_TTL_MS,
68
68
  FUNCTION_TIMEOUT_BUFFER_SECONDS,
@@ -85,6 +85,7 @@ import {
85
85
  getSlackClientSecret,
86
86
  getSlackSigningSecret,
87
87
  getStateAdapter,
88
+ normalizeSlackEmojiName,
88
89
  parseSlackThreadId,
89
90
  resolveConversationPrivacy,
90
91
  resolveGatewayModel,
@@ -97,9 +98,10 @@ import {
97
98
  toGenAiPayloadMetadata,
98
99
  toGenAiPayloadTraceAttributes,
99
100
  toGenAiTextMetadata
100
- } from "./chunk-NWU2Z6SM.js";
101
+ } from "./chunk-MT23VNOH.js";
101
102
  import {
102
103
  CredentialUnavailableError,
104
+ buildActorIdentity,
103
105
  buildOAuthTokenRequest,
104
106
  buildTurnFailureResponse,
105
107
  createChatSdkLogger,
@@ -116,6 +118,7 @@ import {
116
118
  getPluginOAuthConfig,
117
119
  getPluginProviders,
118
120
  hasRequiredOAuthScope,
121
+ isActorUserId,
119
122
  isPluginConfigKey,
120
123
  isPluginProvider,
121
124
  isRecord,
@@ -124,6 +127,7 @@ import {
124
127
  logInfo,
125
128
  logWarn,
126
129
  normalizeGenAiFinishReason,
130
+ parseActorUserId,
127
131
  parseCredentialContext,
128
132
  parseOAuthTokenResponse,
129
133
  resolveAuthTokenPlaceholder,
@@ -134,11 +138,12 @@ import {
134
138
  setSpanAttributes,
135
139
  setSpanStatus,
136
140
  setTags,
141
+ slackActorIdentity,
137
142
  toOptionalNumber,
138
143
  toOptionalString,
139
144
  withContext,
140
145
  withSpan
141
- } from "./chunk-6QWWMZCK.js";
146
+ } from "./chunk-OIIXZOOC.js";
142
147
  import {
143
148
  sentry_exports
144
149
  } from "./chunk-Z3YD6NHK.js";
@@ -3335,17 +3340,6 @@ function createSlackChannelListMessagesTool(context) {
3335
3340
  // src/chat/tools/slack/channel-post-message.ts
3336
3341
  import { Type as Type14 } from "@sinclair/typebox";
3337
3342
 
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
3343
  // src/chat/slack/outbound.ts
3350
3344
  var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
3351
3345
  function requireSlackConversationId(channelId, action) {
@@ -3453,7 +3447,7 @@ async function postSlackEphemeralMessage(input) {
3453
3447
  input.channelId,
3454
3448
  "Slack ephemeral message posting"
3455
3449
  );
3456
- const userId = input.userId.trim();
3450
+ const userId = parseActorUserId(input.userId);
3457
3451
  if (!userId) {
3458
3452
  throw new Error("Slack ephemeral message posting requires a user ID");
3459
3453
  }
@@ -11224,13 +11218,17 @@ function extractSliceUsage(messages, beforeMessageCount) {
11224
11218
  return hasAgentTurnUsage(usage) ? usage : void 0;
11225
11219
  }
11226
11220
  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 } : {}
11221
+ const identity = actorRequesterFromContext(requester, requesterId);
11222
+ const agentRequester = {
11223
+ ...identity?.email ? { email: identity.email } : {},
11224
+ ...identity?.fullName ? { fullName: identity.fullName } : {},
11225
+ ...identity?.userId ? { slackUserId: identity.userId } : {},
11226
+ ...identity?.userName ? { slackUserName: identity.userName } : {}
11232
11227
  };
11233
- return Object.keys(identity).length > 0 ? identity : void 0;
11228
+ return Object.keys(agentRequester).length > 0 ? agentRequester : void 0;
11229
+ }
11230
+ function actorRequesterFromContext(requester, requesterId) {
11231
+ return buildActorIdentity(requester, requesterId);
11234
11232
  }
11235
11233
  function surfaceFromContext(context) {
11236
11234
  if (context.surface) {
@@ -11364,6 +11362,10 @@ async function generateAssistantReply(messageText2, context = {}) {
11364
11362
  context.requester,
11365
11363
  context.correlation?.requesterId
11366
11364
  );
11365
+ const actorRequester = actorRequesterFromContext(
11366
+ context.requester,
11367
+ context.correlation?.requesterId
11368
+ );
11367
11369
  const surface = surfaceFromContext(context);
11368
11370
  const credentialActor = context.credentialContext?.actor;
11369
11371
  const credentialActorLogContext = credentialActor ? {
@@ -11493,7 +11495,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11493
11495
  const authRequesterId = context.credentialContext?.actor.type === "user" ? context.credentialContext.actor.userId : void 0;
11494
11496
  const userTokenStore = createUserTokenStore();
11495
11497
  const agentPluginHooks = createAgentPluginHookRunner({
11496
- requester: context.requester
11498
+ requester: actorRequester
11497
11499
  });
11498
11500
  sandboxExecutor = createSandboxExecutor({
11499
11501
  sandboxId: context.sandbox?.sandboxId,
@@ -11510,7 +11512,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11510
11512
  const result = await maybeExecuteJrRpcCustomCommand(command, {
11511
11513
  activeSkill: skillSandbox.getActiveSkill(),
11512
11514
  channelConfiguration: context.channelConfiguration,
11513
- requesterId: context.requester?.userId,
11515
+ requesterId: actorRequester?.userId,
11514
11516
  onConfigurationValueChanged: (key, value) => {
11515
11517
  if (value === void 0) {
11516
11518
  delete configurationValues[key];
@@ -11746,7 +11748,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11746
11748
  {
11747
11749
  channelId: toolChannelId,
11748
11750
  channelCapabilities,
11749
- requester: context.requester,
11751
+ requester: actorRequester,
11750
11752
  teamId: context.correlation?.teamId,
11751
11753
  messageTs: context.correlation?.messageTs,
11752
11754
  threadTs: context.correlation?.threadTs,
@@ -11809,7 +11811,7 @@ async function generateAssistantReply(messageText2, context = {}) {
11809
11811
  slackConversation: context.slackConversation
11810
11812
  },
11811
11813
  invocation: skillInvocation,
11812
- requester: context.requester,
11814
+ requester: actorRequester,
11813
11815
  artifactState: context.artifactState,
11814
11816
  configuration: configurationValues
11815
11817
  }) : null;
@@ -12445,6 +12447,7 @@ var CONTEXT_MIN_LIVE_MESSAGES = 12;
12445
12447
  var CONTEXT_COMPACTION_BATCH_SIZE = 24;
12446
12448
  var CONTEXT_MAX_COMPACTIONS = 16;
12447
12449
  var CONTEXT_MAX_MESSAGE_CHARS = 3200;
12450
+ var SLACK_USER_ID_DISPLAY_PATTERN = /^[UW][A-Z0-9]{5,}$/;
12448
12451
  function generateConversationId(prefix) {
12449
12452
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
12450
12453
  }
@@ -12464,7 +12467,7 @@ function buildImageContextSuffix(message, conversation) {
12464
12467
  return ` [image context: ${summaries.join(" | ")}]`;
12465
12468
  }
12466
12469
  function renderConversationMessageLine(message, conversation) {
12467
- const displayName = message.author?.fullName || message.author?.userName || (message.role === "assistant" ? botConfig.userName : message.role);
12470
+ const displayName = conversationAuthorDisplayName(message);
12468
12471
  const markers = [];
12469
12472
  if (message.meta?.replied === false) {
12470
12473
  markers.push(
@@ -12478,6 +12481,18 @@ function renderConversationMessageLine(message, conversation) {
12478
12481
  const imageContext = buildImageContextSuffix(message, conversation);
12479
12482
  return `[${message.role}] ${displayName}: ${message.text}${imageContext}${markerSuffix}`;
12480
12483
  }
12484
+ function conversationAuthorDisplayName(message) {
12485
+ const author = message.author;
12486
+ const fullName = authorDisplayField(author?.fullName, author?.userId);
12487
+ const userName = authorDisplayField(author?.userName, author?.userId);
12488
+ return fullName ?? userName ?? (message.role === "assistant" ? botConfig.userName : message.role);
12489
+ }
12490
+ function authorDisplayField(value, userId) {
12491
+ if (!value || value === userId || SLACK_USER_ID_DISPLAY_PATTERN.test(value)) {
12492
+ return void 0;
12493
+ }
12494
+ return value;
12495
+ }
12481
12496
  function updateConversationStats(conversation) {
12482
12497
  const contextText = buildConversationContext(conversation);
12483
12498
  conversation.stats.estimatedContextTokens = estimateTextTokens(
@@ -12547,11 +12562,12 @@ function buildConversationContext(conversation, options = {}) {
12547
12562
  }
12548
12563
  lines.push("<thread-transcript>");
12549
12564
  for (const [index, message] of messages.entries()) {
12550
- const author = escapeXml(message.author?.userName ?? message.role);
12565
+ const author = escapeXml(conversationAuthorDisplayName(message));
12566
+ const actorIdAttr = message.author?.userId ? ` actor_id="${escapeXml(message.author.userId)}"` : "";
12551
12567
  const ts = new Date(message.createdAtMs).toISOString();
12552
12568
  const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
12553
12569
  lines.push(
12554
- ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
12570
+ ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${actorIdAttr}${slackTsAttr}>`,
12555
12571
  renderConversationMessageLine(message, conversation),
12556
12572
  " </message>"
12557
12573
  );
@@ -14511,7 +14527,7 @@ function validateDispatchOptions(options) {
14511
14527
  if (options.credentialSubject.type !== "user") {
14512
14528
  throw new Error("Dispatch credentialSubject type must be user");
14513
14529
  }
14514
- if (!options.credentialSubject.userId.trim()) {
14530
+ if (!isActorUserId(options.credentialSubject.userId)) {
14515
14531
  throw new Error("Dispatch credentialSubject userId is required");
14516
14532
  }
14517
14533
  if (options.credentialSubject.allowedWhen !== "private-direct-conversation") {
@@ -15464,15 +15480,13 @@ function getTeamId(message) {
15464
15480
  }
15465
15481
 
15466
15482
  // src/chat/runtime/processing-reaction.ts
15467
- var PROCESSING_REACTION_EMOJI = "eyes";
15468
- var COMPLETED_REACTION_EMOJI = "white_check_mark";
15469
15483
  var noProcessingReaction = {
15470
15484
  complete: async () => void 0,
15471
15485
  keep: () => void 0,
15472
15486
  stop: async () => void 0
15473
15487
  };
15474
15488
  function isProcessingReactionEmoji(value) {
15475
- return typeof value === "string" && normalizeSlackEmojiName(value) === PROCESSING_REACTION_EMOJI;
15489
+ return typeof value === "string" && normalizeSlackEmojiName(value) === botConfig.processingReactionEmoji;
15476
15490
  }
15477
15491
  function shouldKeepProcessingReactionForToolInvocation(input) {
15478
15492
  return input.toolName === "slackMessageAddReaction" && isProcessingReactionEmoji(input.params.emoji);
@@ -15498,7 +15512,7 @@ async function startSlackProcessingReactionForMessage(args) {
15498
15512
  await addReactionToMessage({
15499
15513
  channelId: args.channelId,
15500
15514
  timestamp: args.timestamp,
15501
- emoji: PROCESSING_REACTION_EMOJI
15515
+ emoji: botConfig.processingReactionEmoji
15502
15516
  });
15503
15517
  } catch (error) {
15504
15518
  args.logException(
@@ -15523,7 +15537,7 @@ async function startSlackProcessingReactionForMessage(args) {
15523
15537
  await removeReactionFromMessage({
15524
15538
  channelId: args.channelId,
15525
15539
  timestamp: args.timestamp,
15526
- emoji: PROCESSING_REACTION_EMOJI
15540
+ emoji: botConfig.processingReactionEmoji
15527
15541
  });
15528
15542
  return true;
15529
15543
  } catch (error) {
@@ -15550,7 +15564,7 @@ async function startSlackProcessingReactionForMessage(args) {
15550
15564
  await addReactionToMessage({
15551
15565
  channelId: args.channelId,
15552
15566
  timestamp: args.timestamp,
15553
- emoji: COMPLETED_REACTION_EMOJI
15567
+ emoji: botConfig.completedReactionEmoji
15554
15568
  });
15555
15569
  } catch (error) {
15556
15570
  args.logException(
@@ -15997,6 +16011,87 @@ function htmlCallbackResponse(title, message, status) {
15997
16011
  });
15998
16012
  }
15999
16013
 
16014
+ // src/chat/slack/user.ts
16015
+ var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
16016
+ var userCache = /* @__PURE__ */ new Map();
16017
+ function readFromCache(userId) {
16018
+ const hit = userCache.get(userId);
16019
+ if (!hit) return null;
16020
+ if (hit.expiresAt < Date.now()) {
16021
+ userCache.delete(userId);
16022
+ return null;
16023
+ }
16024
+ return hit.value;
16025
+ }
16026
+ function writeToCache(userId, value) {
16027
+ userCache.set(userId, {
16028
+ value,
16029
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
16030
+ });
16031
+ }
16032
+ async function lookupSlackUser(userId) {
16033
+ if (!userId) {
16034
+ return null;
16035
+ }
16036
+ const cached = readFromCache(userId);
16037
+ if (cached) {
16038
+ return cached;
16039
+ }
16040
+ const token = getSlackBotToken();
16041
+ if (!token) {
16042
+ return null;
16043
+ }
16044
+ try {
16045
+ const response = await fetch(
16046
+ `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
16047
+ {
16048
+ headers: {
16049
+ authorization: `Bearer ${token}`
16050
+ }
16051
+ }
16052
+ );
16053
+ if (!response.ok) {
16054
+ logWarn(
16055
+ "slack_user_lookup_failed",
16056
+ {},
16057
+ {
16058
+ "enduser.id": userId,
16059
+ "http.response.status_code": response.status
16060
+ },
16061
+ "Slack user lookup request failed"
16062
+ );
16063
+ return null;
16064
+ }
16065
+ const payload = await response.json();
16066
+ if (!payload.ok || !payload.user) {
16067
+ return null;
16068
+ }
16069
+ const userName = payload.user.name?.trim() || void 0;
16070
+ const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
16071
+ const result = {
16072
+ userName,
16073
+ fullName,
16074
+ email: payload.user.profile?.email?.trim() || void 0
16075
+ };
16076
+ writeToCache(userId, result);
16077
+ return result;
16078
+ } catch (error) {
16079
+ logWarn(
16080
+ "slack_user_lookup_failed",
16081
+ {},
16082
+ {
16083
+ "enduser.id": userId,
16084
+ "exception.message": error instanceof Error ? error.message : String(error)
16085
+ },
16086
+ "Slack user lookup failed with exception"
16087
+ );
16088
+ return null;
16089
+ }
16090
+ }
16091
+ async function lookupSlackActorIdentity(userId) {
16092
+ return slackActorIdentity(userId, await lookupSlackUser(userId));
16093
+ }
16094
+
16000
16095
  // src/handlers/mcp-oauth-callback.ts
16001
16096
  var CALLBACK_PAGES = {
16002
16097
  missing_state: {
@@ -16197,6 +16292,7 @@ async function resumeAuthorizedMcpTurn(args) {
16197
16292
  }),
16198
16293
  ttlMs: THREAD_STATE_TTL_MS4
16199
16294
  });
16295
+ const requester = await lookupSlackActorIdentity(authSession.userId);
16200
16296
  return {
16201
16297
  messageText: lockedUserMessage.text,
16202
16298
  messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
@@ -16204,11 +16300,7 @@ async function resumeAuthorizedMcpTurn(args) {
16204
16300
  credentialContext: {
16205
16301
  actor: { type: "user", userId: authSession.userId }
16206
16302
  },
16207
- requester: {
16208
- userId: authSession.userId,
16209
- userName: lockedUserMessage.author?.userName,
16210
- fullName: lockedUserMessage.author?.fullName
16211
- },
16303
+ requester,
16212
16304
  correlation: {
16213
16305
  conversationId: authSession.conversationId,
16214
16306
  turnId: lockedSessionId,
@@ -16691,6 +16783,9 @@ async function resumeOAuthSessionRecordTurn(stored) {
16691
16783
  }),
16692
16784
  ttlMs: THREAD_STATE_TTL_MS5
16693
16785
  });
16786
+ const requester = await lookupSlackActorIdentity(
16787
+ lockedUserMessage.author.userId
16788
+ );
16694
16789
  return {
16695
16790
  messageText: stored.pendingMessage ?? lockedUserMessage.text,
16696
16791
  messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
@@ -16701,11 +16796,7 @@ async function resumeOAuthSessionRecordTurn(stored) {
16701
16796
  userId: lockedUserMessage.author.userId
16702
16797
  }
16703
16798
  },
16704
- requester: {
16705
- userId: lockedUserMessage.author.userId,
16706
- userName: lockedUserMessage.author.userName,
16707
- fullName: lockedUserMessage.author.fullName
16708
- },
16799
+ requester,
16709
16800
  correlation: {
16710
16801
  conversationId: stored.resumeConversationId,
16711
16802
  turnId: lockedSessionId,
@@ -16801,6 +16892,7 @@ async function resumePendingOAuthMessage(stored) {
16801
16892
  const conversationContext = buildConversationContext(conversation, {
16802
16893
  excludeMessageId: latestUserMessage?.id
16803
16894
  });
16895
+ const requester = await lookupSlackActorIdentity(stored.userId);
16804
16896
  await resumeAuthorizedRequest({
16805
16897
  messageText: stored.pendingMessage,
16806
16898
  channelId: stored.channelId,
@@ -16811,7 +16903,7 @@ async function resumePendingOAuthMessage(stored) {
16811
16903
  credentialContext: {
16812
16904
  actor: { type: "user", userId: stored.userId }
16813
16905
  },
16814
- requester: { userId: stored.userId },
16906
+ requester,
16815
16907
  conversationContext,
16816
16908
  piMessages: conversation.piMessages,
16817
16909
  configuration: stored.configuration
@@ -17690,6 +17782,9 @@ async function resumeTimedOutTurn(payload, options = {}) {
17690
17782
  excludeMessageId: userMessage2.id
17691
17783
  });
17692
17784
  const sandbox = getPersistedSandboxState(currentState);
17785
+ const requester = await lookupSlackActorIdentity(
17786
+ userMessage2.author.userId
17787
+ );
17693
17788
  return {
17694
17789
  messageText: userMessage2.text,
17695
17790
  messageTs: getTurnUserSlackMessageTs(userMessage2),
@@ -17700,11 +17795,7 @@ async function resumeTimedOutTurn(payload, options = {}) {
17700
17795
  userId: userMessage2.author.userId
17701
17796
  }
17702
17797
  },
17703
- requester: {
17704
- userId: userMessage2.author.userId,
17705
- userName: userMessage2.author.userName,
17706
- fullName: userMessage2.author.fullName
17707
- },
17798
+ requester,
17708
17799
  correlation: {
17709
17800
  conversationId: payload.conversationId,
17710
17801
  turnId: payload.sessionId,
@@ -18194,6 +18285,60 @@ function combineTurnText(queuedMessages, latestText) {
18194
18285
  };
18195
18286
  }
18196
18287
 
18288
+ // src/chat/services/message-actor-identity.ts
18289
+ var messageActors = /* @__PURE__ */ new WeakMap();
18290
+ function canonicalUserId(author, identity) {
18291
+ const authorUserId = parseActorUserId(author.userId);
18292
+ const identityUserId = parseActorUserId(identity.userId);
18293
+ if (authorUserId && identityUserId && authorUserId !== identityUserId) {
18294
+ throw new Error("Message actor identity user id mismatch");
18295
+ }
18296
+ const userId = authorUserId ?? identityUserId;
18297
+ if (!userId) {
18298
+ throw new Error("Message actor identity requires a user id");
18299
+ }
18300
+ return userId;
18301
+ }
18302
+ function actorIdentityFromAuthor(author) {
18303
+ const userId = parseActorUserId(author.userId);
18304
+ return userId ? { userId } : void 0;
18305
+ }
18306
+ function applyIdentityToAuthor(author, identity) {
18307
+ if (!isActorUserId(identity.userId)) {
18308
+ throw new Error("Message actor identity requires a user id");
18309
+ }
18310
+ author.userId = identity.userId;
18311
+ author.userName = identity.userName ?? "";
18312
+ author.fullName = identity.fullName ?? "";
18313
+ }
18314
+ function bindMessageActorIdentity(message, identity) {
18315
+ const userId = canonicalUserId(message.author, identity);
18316
+ const actorIdentity = buildActorIdentity(identity, userId);
18317
+ if (!actorIdentity?.userId) {
18318
+ throw new Error("Message actor identity requires a user id");
18319
+ }
18320
+ messageActors.set(message, actorIdentity);
18321
+ applyIdentityToAuthor(message.author, actorIdentity);
18322
+ return actorIdentity;
18323
+ }
18324
+ function getMessageActorIdentity(message) {
18325
+ return messageActors.get(message) ?? actorIdentityFromAuthor(message.author);
18326
+ }
18327
+ async function ensureSlackMessageActorIdentity(message, lookupSlackUser2) {
18328
+ const existing = messageActors.get(message);
18329
+ if (existing) {
18330
+ return existing;
18331
+ }
18332
+ const userId = parseActorUserId(message.author.userId);
18333
+ if (!userId) {
18334
+ throw new Error("Slack message actor identity requires a user id");
18335
+ }
18336
+ return bindMessageActorIdentity(
18337
+ message,
18338
+ slackActorIdentity(userId, await lookupSlackUser2(userId))
18339
+ );
18340
+ }
18341
+
18197
18342
  // src/chat/runtime/slack-runtime.ts
18198
18343
  var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
18199
18344
  async function maybeHandleThreadOptOutDecision(args) {
@@ -18253,6 +18398,9 @@ function buildLogContext(deps, args) {
18253
18398
  modelId: deps.modelId
18254
18399
  };
18255
18400
  }
18401
+ function requesterUserName(message) {
18402
+ return getMessageActorIdentity(message)?.userName;
18403
+ }
18256
18404
  function createSlackTurnRuntime(deps) {
18257
18405
  const logContext = (args) => buildLogContext(deps, args);
18258
18406
  const createToolInvocationHook = (processingReaction, hooks) => {
@@ -18326,7 +18474,7 @@ function createSlackTurnRuntime(deps) {
18326
18474
  logContext({
18327
18475
  threadId: args.context.threadId,
18328
18476
  requesterId: args.context.requesterId,
18329
- requesterUserName: args.message.author.userName,
18477
+ requesterUserName: requesterUserName(args.message),
18330
18478
  channelId: args.context.channelId,
18331
18479
  runId: args.context.runId
18332
18480
  }),
@@ -18368,7 +18516,7 @@ function createSlackTurnRuntime(deps) {
18368
18516
  threadId,
18369
18517
  channelId,
18370
18518
  requesterId: message.author.userId,
18371
- requesterUserName: message.author.userName,
18519
+ requesterUserName: requesterUserName(message),
18372
18520
  runId
18373
18521
  });
18374
18522
  processingReaction = await processingReactions.start(context, message);
@@ -18428,7 +18576,7 @@ function createSlackTurnRuntime(deps) {
18428
18576
  const errorContext = logContext({
18429
18577
  threadId: deps.getThreadId(thread, message),
18430
18578
  requesterId: message.author.userId,
18431
- requesterUserName: message.author.userName,
18579
+ requesterUserName: requesterUserName(message),
18432
18580
  channelId: deps.getChannelId(thread, message),
18433
18581
  runId: deps.getRunId(thread, message)
18434
18582
  });
@@ -18484,7 +18632,7 @@ function createSlackTurnRuntime(deps) {
18484
18632
  const turnContext = logContext({
18485
18633
  threadId,
18486
18634
  requesterId: message.author.userId,
18487
- requesterUserName: message.author.userName,
18635
+ requesterUserName: requesterUserName(message),
18488
18636
  channelId,
18489
18637
  runId
18490
18638
  });
@@ -18634,7 +18782,7 @@ function createSlackTurnRuntime(deps) {
18634
18782
  const errorContext = logContext({
18635
18783
  threadId: deps.getThreadId(thread, message),
18636
18784
  requesterId: message.author.userId,
18637
- requesterUserName: message.author.userName,
18785
+ requesterUserName: requesterUserName(message),
18638
18786
  channelId: deps.getChannelId(thread, message),
18639
18787
  runId: deps.getRunId(thread, message)
18640
18788
  });
@@ -18992,84 +19140,6 @@ function createContextCompactor(deps) {
18992
19140
  };
18993
19141
  }
18994
19142
 
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
19143
  // src/chat/services/subscribed-reply-policy.ts
19074
19144
  function createSubscribedReplyPolicy(deps) {
19075
19145
  return async (args) => {
@@ -19793,14 +19863,14 @@ function collectCanvasUrls(artifacts) {
19793
19863
  ].filter((url) => typeof url === "string" && url !== "")
19794
19864
  );
19795
19865
  }
19796
- function turnRequester(args) {
19866
+ function turnRequester(identity) {
19797
19867
  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 } : {}
19868
+ ...identity.email ? { email: identity.email } : {},
19869
+ ...identity.fullName ? { fullName: identity.fullName } : {},
19870
+ ...identity.userId ? { slackUserId: identity.userId } : {},
19871
+ ...identity.userName ? { slackUserName: identity.userName } : {}
19802
19872
  };
19803
- return Object.keys(requester).length > 0 ? requester : void 0;
19873
+ return requester;
19804
19874
  }
19805
19875
  async function resolveChannelName(thread) {
19806
19876
  const existingName = thread.channel.name?.trim();
@@ -19909,6 +19979,19 @@ function createReplyToThread(deps) {
19909
19979
  options.queuedMessages ?? [],
19910
19980
  currentText
19911
19981
  ).userText;
19982
+ await Promise.all(
19983
+ (options.queuedMessages ?? []).map(
19984
+ (queued) => ensureSlackMessageActorIdentity(
19985
+ queued.message,
19986
+ deps.services.lookupSlackUser
19987
+ )
19988
+ )
19989
+ );
19990
+ const requesterIdentity = await ensureSlackMessageActorIdentity(
19991
+ message,
19992
+ deps.services.lookupSlackUser
19993
+ );
19994
+ const requester = turnRequester(requesterIdentity);
19912
19995
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
19913
19996
  thread,
19914
19997
  message,
@@ -19926,15 +20009,6 @@ function createReplyToThread(deps) {
19926
20009
  });
19927
20010
  const slackMessageTs = getSlackMessageTs(message);
19928
20011
  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
20012
  const turnTraceContext = {
19939
20013
  conversationId,
19940
20014
  slackThreadId: threadId,
@@ -20122,16 +20196,15 @@ function createReplyToThread(deps) {
20122
20196
  conversation: preparedState.conversation
20123
20197
  });
20124
20198
  await options.onTurnStatePersisted?.();
20125
- const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
20126
20199
  if (message.author.userId) {
20127
20200
  setSentryUser({
20128
20201
  id: message.author.userId,
20129
- ...resolvedUserName ? { username: resolvedUserName } : {},
20130
- ...fallbackIdentity?.email ? { email: fallbackIdentity.email } : {}
20202
+ ...requesterIdentity.userName ? { username: requesterIdentity.userName } : {},
20203
+ ...requesterIdentity.email ? { email: requesterIdentity.email } : {}
20131
20204
  });
20132
20205
  }
20133
- if (resolvedUserName) {
20134
- setTags({ slackUserName: resolvedUserName });
20206
+ if (requesterIdentity.userName) {
20207
+ setTags({ slackUserName: requesterIdentity.userName });
20135
20208
  }
20136
20209
  const turnAttachments = collectTurnAttachments(
20137
20210
  message,
@@ -20261,12 +20334,7 @@ function createReplyToThread(deps) {
20261
20334
  credentialContext: {
20262
20335
  actor: { type: "user", userId: message.author.userId }
20263
20336
  },
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
- },
20337
+ requester: requesterIdentity,
20270
20338
  conversationContext: preparedState.conversationContext,
20271
20339
  artifactState: preparedState.artifacts,
20272
20340
  piMessages,
@@ -20673,6 +20741,7 @@ function resolveMessageText(args) {
20673
20741
  return text || NON_TEXT_MESSAGE_TEXT;
20674
20742
  }
20675
20743
  function toConversationMessage(args) {
20744
+ const actor = getMessageActorIdentity(args.entry);
20676
20745
  const messageHasPotentialImageAttachment = hasPotentialImageAttachment(
20677
20746
  args.entry.attachments
20678
20747
  );
@@ -20683,9 +20752,9 @@ function toConversationMessage(args) {
20683
20752
  text: resolveMessageText(args),
20684
20753
  createdAtMs: args.entry.metadata.dateSent.getTime(),
20685
20754
  author: {
20686
- userId: args.entry.author.userId,
20687
- userName: args.entry.author.userName,
20688
- fullName: args.entry.author.fullName,
20755
+ ...actor?.userId ? { userId: actor.userId } : {},
20756
+ ...actor?.userName ? { userName: actor.userName } : {},
20757
+ ...actor?.fullName ? { fullName: actor.fullName } : {},
20689
20758
  isBot: typeof args.entry.author.isBot === "boolean" ? args.entry.author.isBot : void 0
20690
20759
  },
20691
20760
  meta: {
@@ -21102,6 +21171,13 @@ async function runWithSlackInstallation(args) {
21102
21171
  }
21103
21172
 
21104
21173
  // src/chat/task-execution/slack-work.ts
21174
+ function requireSlackAuthorId(message) {
21175
+ const authorId = parseActorUserId(message.author.userId);
21176
+ if (!authorId) {
21177
+ throw new Error("Slack message requires an actor user id");
21178
+ }
21179
+ return authorId;
21180
+ }
21105
21181
  function getConnectedState3(stateAdapter) {
21106
21182
  return stateAdapter ?? getStateAdapter();
21107
21183
  }
@@ -21126,6 +21202,23 @@ function restoreMessage(args) {
21126
21202
  rehydrateAttachmentFetchers(message);
21127
21203
  return message;
21128
21204
  }
21205
+ async function bindSlackActorIdentities(args) {
21206
+ const byAuthorId = /* @__PURE__ */ new Map();
21207
+ for (const message of args.messages) {
21208
+ const authorId = requireSlackAuthorId(message);
21209
+ byAuthorId.set(authorId, [...byAuthorId.get(authorId) ?? [], message]);
21210
+ }
21211
+ await Promise.all(
21212
+ [...byAuthorId].map(async ([authorId, messages]) => {
21213
+ const profile = await args.lookupSlackUser(authorId);
21214
+ await Promise.all(
21215
+ messages.map(
21216
+ (message) => ensureSlackMessageActorIdentity(message, async () => profile)
21217
+ )
21218
+ );
21219
+ })
21220
+ );
21221
+ }
21129
21222
  function restoreThread(args) {
21130
21223
  const threadId = normalizeIncomingSlackThreadId(
21131
21224
  args.threadJson.id,
@@ -21205,6 +21298,7 @@ function getPendingRecords(work) {
21205
21298
  function createSlackConversationWorker(options) {
21206
21299
  return async (context) => {
21207
21300
  const adapter = options.getSlackAdapter();
21301
+ const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
21208
21302
  const state = getConnectedState3(options.state);
21209
21303
  await state.connect();
21210
21304
  const resumeContinuation = options.resumeAwaitingContinuation ?? resumeAwaitingContinuation;
@@ -21242,6 +21336,10 @@ function createSlackConversationWorker(options) {
21242
21336
  const messages = records.map(
21243
21337
  (record) => restoreMessage({ adapter, record })
21244
21338
  );
21339
+ await bindSlackActorIdentities({
21340
+ lookupSlackUser: actorLookup,
21341
+ messages
21342
+ });
21245
21343
  const latestMessage = messages[messages.length - 1];
21246
21344
  if (!latestMessage) {
21247
21345
  return;
@@ -21328,6 +21426,7 @@ function createSlackConversationWorker(options) {
21328
21426
  };
21329
21427
  }
21330
21428
  function buildSlackInboundMessage(args) {
21429
+ const authorId = requireSlackAuthorId(args.message);
21331
21430
  return {
21332
21431
  conversationId: args.conversationId,
21333
21432
  inboundMessageId: [
@@ -21341,7 +21440,7 @@ function buildSlackInboundMessage(args) {
21341
21440
  receivedAtMs: args.receivedAtMs,
21342
21441
  input: {
21343
21442
  text: args.message.text || " ",
21344
- authorId: args.message.author.userId,
21443
+ authorId,
21345
21444
  attachments: args.message.attachments,
21346
21445
  metadata: {
21347
21446
  platform: "slack",
@@ -21460,7 +21559,8 @@ function extractMessageChangedMention(body, botUserId, adapter) {
21460
21559
  const channelId = event.channel;
21461
21560
  const messageTs = event.message.ts;
21462
21561
  const threadTs = event.message.thread_ts ?? messageTs;
21463
- const userId = event.message.user ?? "unknown";
21562
+ const userId = parseActorUserId(event.message.user);
21563
+ if (!userId) return null;
21464
21564
  const threadId = `slack:${channelId}:${threadTs}`;
21465
21565
  const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
21466
21566
  const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
@@ -21485,8 +21585,9 @@ function extractMessageChangedMention(body, botUserId, adapter) {
21485
21585
  raw,
21486
21586
  author: {
21487
21587
  userId,
21488
- userName: userId,
21489
- fullName: userId,
21588
+ // Raw message_changed payloads do not include profile fields.
21589
+ userName: "",
21590
+ fullName: "",
21490
21591
  isBot: false,
21491
21592
  isMe: false
21492
21593
  }
@@ -21516,10 +21617,17 @@ function isExternalSlackUser(raw) {
21516
21617
  async function postEphemeral(event, text) {
21517
21618
  await event.channel.postEphemeral(event.user, text, { fallbackToDM: false });
21518
21619
  }
21620
+ function requireRequesterId(event) {
21621
+ const userId = parseActorUserId(event.user.userId);
21622
+ if (!userId) {
21623
+ throw new Error("Slack slash command requires a requester user id");
21624
+ }
21625
+ return userId;
21626
+ }
21519
21627
  function getCommandName() {
21520
21628
  return getChatConfig().slack.slashCommand;
21521
21629
  }
21522
- async function handleLink(event, provider) {
21630
+ async function handleLink(event, requesterId, provider) {
21523
21631
  if (!isPluginProvider(provider)) {
21524
21632
  await postEphemeral(event, `Unknown provider: \`${provider}\``);
21525
21633
  return;
@@ -21533,7 +21641,7 @@ async function handleLink(event, provider) {
21533
21641
  }
21534
21642
  const raw = event.raw;
21535
21643
  const result = await startOAuthFlow(provider, {
21536
- requesterId: event.user.userId,
21644
+ requesterId,
21537
21645
  channelId: raw.channel_id
21538
21646
  });
21539
21647
  if (!result.ok) {
@@ -21552,7 +21660,7 @@ async function handleLink(event, provider) {
21552
21660
  );
21553
21661
  }
21554
21662
  }
21555
- async function handleUnlink(event, provider) {
21663
+ async function handleUnlink(event, requesterId, provider) {
21556
21664
  if (!isPluginProvider(provider)) {
21557
21665
  await postEphemeral(event, `Unknown provider: \`${provider}\``);
21558
21666
  return;
@@ -21565,10 +21673,10 @@ async function handleUnlink(event, provider) {
21565
21673
  return;
21566
21674
  }
21567
21675
  const tokenStore = createUserTokenStore();
21568
- await tokenStore.delete(event.user.userId, provider);
21676
+ await tokenStore.delete(requesterId, provider);
21569
21677
  logInfo(
21570
21678
  "slash_command_unlink",
21571
- { slackUserId: event.user.userId },
21679
+ { slackUserId: requesterId },
21572
21680
  { "app.credential.provider": provider },
21573
21681
  `Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
21574
21682
  );
@@ -21594,10 +21702,11 @@ async function handleSlashCommand(event) {
21594
21702
  return;
21595
21703
  }
21596
21704
  const normalized = provider.toLowerCase();
21705
+ const requesterId = requireRequesterId(event);
21597
21706
  if (subcommand === "link") {
21598
- await handleLink(event, normalized);
21707
+ await handleLink(event, requesterId, normalized);
21599
21708
  } else {
21600
- await handleUnlink(event, normalized);
21709
+ await handleUnlink(event, requesterId, normalized);
21601
21710
  }
21602
21711
  }
21603
21712
 
@@ -21837,15 +21946,12 @@ async function handleSlackEvent(args) {
21837
21946
  })
21838
21947
  );
21839
21948
  }
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
- };
21949
+ function requireSlackPayloadUserId(value, source) {
21950
+ const userId = parseActorUserId(value);
21951
+ if (!userId) {
21952
+ throw new Error(`${source} is missing a Slack user id`);
21953
+ }
21954
+ return userId;
21849
21955
  }
21850
21956
  async function handleSlashCommandForm(args) {
21851
21957
  const raw = Object.fromEntries(args.params);
@@ -21855,7 +21961,21 @@ async function handleSlashCommandForm(args) {
21855
21961
  adapter: args.adapter,
21856
21962
  stateAdapter: args.state
21857
21963
  });
21858
- const userId = args.params.get("user_id") || "unknown";
21964
+ const userId = requireSlackPayloadUserId(
21965
+ args.params.get("user_id"),
21966
+ "Slack slash command payload"
21967
+ );
21968
+ const userIdentity = buildActorIdentity(
21969
+ {
21970
+ userId,
21971
+ userName: args.params.get("user_name") ?? void 0,
21972
+ fullName: args.params.get("user_name") ?? void 0
21973
+ },
21974
+ userId
21975
+ );
21976
+ if (!userIdentity?.userId) {
21977
+ throw new Error("Slack slash command payload actor identity is invalid");
21978
+ }
21859
21979
  await withSpan(
21860
21980
  "chat.slash_command",
21861
21981
  "chat.slash_command",
@@ -21870,8 +21990,8 @@ async function handleSlashCommandForm(args) {
21870
21990
  raw,
21871
21991
  user: {
21872
21992
  userId,
21873
- userName: args.params.get("user_name") || userId,
21874
- fullName: args.params.get("user_name") || userId,
21993
+ userName: userIdentity.userName ?? "",
21994
+ fullName: userIdentity.fullName ?? "",
21875
21995
  isBot: false,
21876
21996
  isMe: false
21877
21997
  },
@@ -21888,10 +22008,13 @@ async function handleInteractivePayload(args) {
21888
22008
  (candidate) => candidate.action_id === "app_home_disconnect"
21889
22009
  );
21890
22010
  const provider = action?.selected_option?.value ?? action?.value;
21891
- const userId = args.payload.user?.id;
21892
- if (!provider || !userId) {
22011
+ if (!provider) {
21893
22012
  return;
21894
22013
  }
22014
+ const userId = requireSlackPayloadUserId(
22015
+ args.payload.user?.id,
22016
+ "Slack app home disconnect payload"
22017
+ );
21895
22018
  await withSpan(
21896
22019
  "chat.app_home_disconnect",
21897
22020
  "chat.app_home_disconnect",
@@ -21986,7 +22109,7 @@ async function handleSlackForm(args) {
21986
22109
  })
21987
22110
  ).catch((error) => {
21988
22111
  logException(error, "slack_interactive_payload_failed", {
21989
- slackUserId: buildAuthorFromInteractive(payload.user).userId
22112
+ slackUserId: payload.user?.id?.trim() || void 0
21990
22113
  });
21991
22114
  })
21992
22115
  );