@sentry/junior 0.45.0 → 0.46.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.
package/dist/app.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  runNonInteractiveCommand,
32
32
  sandboxSkillDir,
33
33
  sandboxSkillFile
34
- } from "./chunk-QAMTCT2R.js";
34
+ } from "./chunk-ELM6HJ6S.js";
35
35
  import {
36
36
  CredentialUnavailableError,
37
37
  buildOAuthTokenRequest,
@@ -2119,6 +2119,10 @@ function markTurnFailed(args) {
2119
2119
  }
2120
2120
 
2121
2121
  // src/chat/runtime/turn-user-message.ts
2122
+ function normalizeSlackMessageTs(value) {
2123
+ const trimmed = value?.trim();
2124
+ return trimmed && /^\d+(?:\.\d+)?$/.test(trimmed) ? trimmed : void 0;
2125
+ }
2122
2126
  function getTurnUserMessage(conversation, sessionId) {
2123
2127
  for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
2124
2128
  const message = conversation.messages[index];
@@ -2134,6 +2138,9 @@ function getTurnUserMessage(conversation, sessionId) {
2134
2138
  function getTurnUserMessageId(conversation, sessionId) {
2135
2139
  return getTurnUserMessage(conversation, sessionId)?.id;
2136
2140
  }
2141
+ function getTurnUserSlackMessageTs(message) {
2142
+ return normalizeSlackMessageTs(message?.meta?.slackTs) ?? normalizeSlackMessageTs(message?.id);
2143
+ }
2137
2144
  function getTurnUserReplyAttachmentContext(message) {
2138
2145
  const inboundAttachmentCount = message?.meta?.attachmentCount ?? 0;
2139
2146
  const imageAttachmentCount = message?.meta?.imageAttachmentCount ?? 0;
@@ -8631,7 +8638,6 @@ function resolveChannelCapabilities(channelId) {
8631
8638
  import fs4 from "fs/promises";
8632
8639
 
8633
8640
  // src/chat/sandbox/egress-policy.ts
8634
- var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
8635
8641
  function matchesSandboxEgressDomain(host, domain) {
8636
8642
  return host.toLowerCase() === domain.toLowerCase();
8637
8643
  }
@@ -8653,31 +8659,24 @@ function resolveSandboxEgressProviderForHost(host) {
8653
8659
  (entry) => entry.domains.some((domain) => matchesSandboxEgressDomain(host, domain))
8654
8660
  )?.provider;
8655
8661
  }
8656
- function proxyUrl(egressId) {
8662
+ function sandboxProxyUrl() {
8657
8663
  const baseUrl = resolveBaseUrl();
8658
8664
  if (!baseUrl) {
8659
- return void 0;
8660
- }
8661
- const url = new URL(
8662
- `${SANDBOX_EGRESS_PROXY_PATH}/${encodeURIComponent(egressId)}`,
8663
- baseUrl
8664
- );
8665
- return url.toString();
8666
- }
8667
- function buildSandboxEgressNetworkPolicy(egressId) {
8668
- const entries = providerEntries();
8669
- if (entries.length === 0) {
8670
- return void 0;
8671
- }
8672
- const forwardURL = proxyUrl(egressId);
8673
- if (!forwardURL) {
8674
8665
  throw new Error(
8675
8666
  "Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
8676
8667
  );
8677
8668
  }
8669
+ return new URL("/", baseUrl).toString();
8670
+ }
8671
+ function buildSandboxEgressNetworkPolicy() {
8678
8672
  const allow = {
8679
8673
  "*": []
8680
8674
  };
8675
+ const entries = providerEntries();
8676
+ if (entries.length === 0) {
8677
+ return { allow };
8678
+ }
8679
+ const forwardURL = sandboxProxyUrl();
8681
8680
  for (const entry of entries) {
8682
8681
  for (const domain of entry.domains) {
8683
8682
  allow[domain] = [{ forwardURL }];
@@ -8685,11 +8684,14 @@ function buildSandboxEgressNetworkPolicy(egressId) {
8685
8684
  }
8686
8685
  return { allow };
8687
8686
  }
8688
- async function resolveSandboxCommandEnvironment() {
8687
+ async function resolveSandboxCommandEnvironment(provider) {
8689
8688
  const env = {};
8690
8689
  for (const plugin of getPluginProviders().sort(
8691
8690
  (left, right) => left.manifest.name.localeCompare(right.manifest.name)
8692
8691
  )) {
8692
+ if (provider && plugin.manifest.name !== provider) {
8693
+ continue;
8694
+ }
8693
8695
  Object.assign(env, resolvePluginCommandEnv(plugin.manifest));
8694
8696
  const credentials = plugin.manifest.credentials;
8695
8697
  if (credentials) {
@@ -10006,7 +10008,6 @@ function createSandboxSessionManager(options) {
10006
10008
  return {
10007
10009
  bash: async (input) => {
10008
10010
  const commandEgressId = sandboxInstance.sandboxEgressId;
10009
- await options?.beforeCommand?.(commandEgressId);
10010
10011
  let timedOut = false;
10011
10012
  let timeoutId;
10012
10013
  let commandFinished = false;
@@ -10016,6 +10017,7 @@ function createSandboxSessionManager(options) {
10016
10017
  }
10017
10018
  commandFinished = true;
10018
10019
  await options?.afterCommand?.(commandEgressId);
10020
+ await refreshNetworkPolicy(sandboxInstance);
10019
10021
  };
10020
10022
  const finishCommandBestEffort = async () => {
10021
10023
  try {
@@ -10033,6 +10035,8 @@ function createSandboxSessionManager(options) {
10033
10035
  }
10034
10036
  };
10035
10037
  try {
10038
+ await options?.beforeCommand?.(commandEgressId);
10039
+ await refreshNetworkPolicy(sandboxInstance);
10036
10040
  const sandboxCommandEnv = await resolveCommandEnv();
10037
10041
  const script = buildNonInteractiveShellScript(input.command, {
10038
10042
  env: { ...sandboxCommandEnv, ...input.env ?? {} },
@@ -10156,14 +10160,14 @@ function createSandboxExecutor(options) {
10156
10160
  let referenceFiles = [];
10157
10161
  const traceContext = options?.traceContext ?? {};
10158
10162
  const credentialEgress = options?.credentialEgress;
10159
- const syncSandboxEgressSession = credentialEgress ? async (egressId) => {
10163
+ const authorizeSandboxEgressForCommand = credentialEgress ? async (egressId) => {
10160
10164
  await upsertSandboxEgressSession({
10161
10165
  egressId,
10162
10166
  requesterId: credentialEgress.requesterId,
10163
10167
  ttlMs: options?.timeoutMs
10164
10168
  });
10165
10169
  } : void 0;
10166
- const clearSandboxEgressSessionForCommand = credentialEgress ? async (egressId) => {
10170
+ const clearSandboxEgressForCommand = credentialEgress ? async (egressId) => {
10167
10171
  await clearSandboxEgressSession(egressId);
10168
10172
  } : void 0;
10169
10173
  const sessionManager = createSandboxSessionManager({
@@ -10171,10 +10175,13 @@ function createSandboxExecutor(options) {
10171
10175
  sandboxDependencyProfileHash: options?.sandboxDependencyProfileHash,
10172
10176
  timeoutMs: options?.timeoutMs,
10173
10177
  traceContext,
10174
- commandEnv: credentialEgress ? async () => await resolveSandboxCommandEnvironment() : void 0,
10178
+ commandEnv: credentialEgress ? async () => {
10179
+ const provider = credentialEgress.activeProvider?.();
10180
+ return provider ? await resolveSandboxCommandEnvironment(provider) : {};
10181
+ } : void 0,
10175
10182
  createNetworkPolicy: credentialEgress ? buildSandboxEgressNetworkPolicy : void 0,
10176
- beforeCommand: syncSandboxEgressSession,
10177
- afterCommand: clearSandboxEgressSessionForCommand,
10183
+ beforeCommand: authorizeSandboxEgressForCommand,
10184
+ afterCommand: clearSandboxEgressForCommand,
10178
10185
  onSandboxAcquired: async (sandbox) => {
10179
10186
  await options?.onSandboxAcquired?.(sandbox);
10180
10187
  }
@@ -12098,7 +12105,8 @@ async function generateAssistantReply(messageText, context = {}) {
12098
12105
  sandboxDependencyProfileHash: context.sandbox?.sandboxDependencyProfileHash,
12099
12106
  traceContext: spanContext,
12100
12107
  credentialEgress: requesterId ? {
12101
- requesterId
12108
+ requesterId,
12109
+ activeProvider: () => skillSandbox.getActiveSkill()?.pluginProvider
12102
12110
  } : void 0,
12103
12111
  onSandboxAcquired: async (sandbox2) => {
12104
12112
  lastKnownSandboxId = sandbox2.sandboxId;
@@ -13480,6 +13488,247 @@ async function postSlackApiReplyPosts(args) {
13480
13488
  return lastPostedMessageTs;
13481
13489
  }
13482
13490
 
13491
+ // src/chat/slack/errors.ts
13492
+ function getSlackApiErrorCode(error) {
13493
+ if (!error || typeof error !== "object") {
13494
+ return void 0;
13495
+ }
13496
+ const candidate = error;
13497
+ if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
13498
+ return candidate.data.error;
13499
+ }
13500
+ if (typeof candidate.code === "string" && candidate.code.trim().length > 0) {
13501
+ return candidate.code;
13502
+ }
13503
+ return void 0;
13504
+ }
13505
+ function getSlackErrorObservabilityAttributes(error) {
13506
+ if (!error || typeof error !== "object") {
13507
+ return {};
13508
+ }
13509
+ const candidate = error;
13510
+ const attributes = {};
13511
+ if (typeof candidate.code === "string" && candidate.code.trim().length > 0) {
13512
+ attributes["app.slack.error_code"] = candidate.code;
13513
+ }
13514
+ if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
13515
+ attributes["app.slack.api_error"] = candidate.data.error;
13516
+ }
13517
+ const requestId = getHeaderString(candidate.headers, "x-slack-req-id");
13518
+ if (requestId) {
13519
+ attributes["app.slack.request_id"] = requestId;
13520
+ }
13521
+ if (typeof candidate.statusCode === "number" && Number.isFinite(candidate.statusCode)) {
13522
+ attributes["http.response.status_code"] = candidate.statusCode;
13523
+ }
13524
+ return attributes;
13525
+ }
13526
+ function isSlackTitlePermissionError(error) {
13527
+ const code = getSlackApiErrorCode(error);
13528
+ return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
13529
+ }
13530
+
13531
+ // src/chat/slack/context.ts
13532
+ function toTrimmedSlackString(value) {
13533
+ const normalized = toOptionalString(value);
13534
+ return normalized?.trim() || void 0;
13535
+ }
13536
+ function parseSlackThreadId(threadId) {
13537
+ const normalizedThreadId = toTrimmedSlackString(threadId);
13538
+ if (!normalizedThreadId) {
13539
+ return void 0;
13540
+ }
13541
+ const parts = normalizedThreadId.split(":");
13542
+ if (parts.length !== 3 || parts[0] !== "slack") {
13543
+ return void 0;
13544
+ }
13545
+ const channelId = toTrimmedSlackString(parts[1]);
13546
+ const threadTs = toTrimmedSlackString(parts[2]);
13547
+ if (!channelId || !threadTs) {
13548
+ return void 0;
13549
+ }
13550
+ return { channelId, threadTs };
13551
+ }
13552
+ function resolveSlackChannelIdFromThreadId(threadId) {
13553
+ return parseSlackThreadId(threadId)?.channelId;
13554
+ }
13555
+ function resolveSlackChannelIdFromMessage(message) {
13556
+ const messageChannelId = toTrimmedSlackString(
13557
+ message.channelId
13558
+ );
13559
+ if (messageChannelId) {
13560
+ return messageChannelId;
13561
+ }
13562
+ const raw = message.raw;
13563
+ if (raw && typeof raw === "object") {
13564
+ const rawChannel = toTrimmedSlackString(
13565
+ raw.channel
13566
+ );
13567
+ if (rawChannel) {
13568
+ return rawChannel;
13569
+ }
13570
+ }
13571
+ const threadId = toTrimmedSlackString(
13572
+ message.threadId
13573
+ );
13574
+ return resolveSlackChannelIdFromThreadId(threadId);
13575
+ }
13576
+
13577
+ // src/chat/runtime/thread-context.ts
13578
+ function escapeRegExp2(value) {
13579
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13580
+ }
13581
+ function stripLeadingBotMention(text, options = {}) {
13582
+ if (!text.trim()) return text;
13583
+ let next = text;
13584
+ if (options.stripLeadingSlackMentionToken) {
13585
+ next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
13586
+ }
13587
+ const mentionByNameRe = new RegExp(
13588
+ `^\\s*@${escapeRegExp2(botConfig.userName)}\\b[\\s,:-]*`,
13589
+ "i"
13590
+ );
13591
+ next = next.replace(mentionByNameRe, "").trim();
13592
+ const mentionByLabeledEntityRe = new RegExp(
13593
+ `^\\s*<@[^>|]+\\|${escapeRegExp2(botConfig.userName)}>[\\s,:-]*`,
13594
+ "i"
13595
+ );
13596
+ next = next.replace(mentionByLabeledEntityRe, "").trim();
13597
+ return next;
13598
+ }
13599
+ function getThreadId(thread, _message) {
13600
+ return toOptionalString(thread.id);
13601
+ }
13602
+ function getRunId(thread, message) {
13603
+ return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
13604
+ }
13605
+ function getChannelId(thread, message) {
13606
+ return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
13607
+ }
13608
+ function getThreadTs(threadId) {
13609
+ return parseSlackThreadId(threadId)?.threadTs;
13610
+ }
13611
+ function getAssistantThreadContext(message) {
13612
+ const raw = message.raw;
13613
+ const rawRecord = raw && typeof raw === "object" ? raw : void 0;
13614
+ const channelId = toOptionalString(rawRecord?.channel);
13615
+ if (channelId) {
13616
+ const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
13617
+ const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
13618
+ if (threadTs) {
13619
+ return { channelId, threadTs };
13620
+ }
13621
+ }
13622
+ const parsedThreadId = parseSlackThreadId(
13623
+ toOptionalString(message.threadId)
13624
+ );
13625
+ if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
13626
+ return void 0;
13627
+ }
13628
+ return parsedThreadId;
13629
+ }
13630
+ function getMessageTs(message) {
13631
+ const directTs = toOptionalString(
13632
+ message.ts
13633
+ );
13634
+ if (directTs) {
13635
+ return directTs;
13636
+ }
13637
+ const raw = message.raw;
13638
+ if (!raw || typeof raw !== "object") {
13639
+ return void 0;
13640
+ }
13641
+ const rawRecord = raw;
13642
+ return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
13643
+ }
13644
+
13645
+ // src/chat/runtime/processing-reaction.ts
13646
+ var PROCESSING_REACTION_EMOJI = "eyes";
13647
+ var noProcessingReaction = {
13648
+ keep: () => void 0,
13649
+ stop: async () => void 0
13650
+ };
13651
+ function isProcessingReactionEmoji(value) {
13652
+ return typeof value === "string" && normalizeSlackEmojiName(value) === PROCESSING_REACTION_EMOJI;
13653
+ }
13654
+ function shouldKeepProcessingReactionForToolInvocation(input) {
13655
+ return input.toolName === "slackMessageAddReaction" && isProcessingReactionEmoji(input.params.emoji);
13656
+ }
13657
+ async function startSlackProcessingReaction(args) {
13658
+ if (args.message.author.isMe) {
13659
+ return noProcessingReaction;
13660
+ }
13661
+ const channelId = getChannelId(args.thread, args.message);
13662
+ const messageTs = getMessageTs(args.message);
13663
+ if (!channelId || !messageTs) {
13664
+ return noProcessingReaction;
13665
+ }
13666
+ return startSlackProcessingReactionForMessage({
13667
+ channelId,
13668
+ timestamp: messageTs,
13669
+ logException: args.logException,
13670
+ logContext: args.logContext
13671
+ });
13672
+ }
13673
+ async function startSlackProcessingReactionForMessage(args) {
13674
+ try {
13675
+ await addReactionToMessage({
13676
+ channelId: args.channelId,
13677
+ timestamp: args.timestamp,
13678
+ emoji: PROCESSING_REACTION_EMOJI
13679
+ });
13680
+ } catch (error) {
13681
+ args.logException(
13682
+ error,
13683
+ "slack_processing_reaction_add_failed",
13684
+ args.logContext,
13685
+ {
13686
+ "app.slack.action": "reactions.add",
13687
+ "messaging.message.id": args.timestamp,
13688
+ ...getSlackErrorObservabilityAttributes(error)
13689
+ },
13690
+ "Failed to add Slack processing reaction"
13691
+ );
13692
+ return noProcessingReaction;
13693
+ }
13694
+ let shouldRemove = true;
13695
+ return {
13696
+ keep: () => {
13697
+ shouldRemove = false;
13698
+ },
13699
+ stop: async () => {
13700
+ if (!shouldRemove) {
13701
+ return;
13702
+ }
13703
+ try {
13704
+ await removeReactionFromMessage({
13705
+ channelId: args.channelId,
13706
+ timestamp: args.timestamp,
13707
+ emoji: PROCESSING_REACTION_EMOJI
13708
+ });
13709
+ } catch (error) {
13710
+ args.logException(
13711
+ error,
13712
+ "slack_processing_reaction_remove_failed",
13713
+ args.logContext,
13714
+ {
13715
+ "app.slack.action": "reactions.remove",
13716
+ "messaging.message.id": args.timestamp,
13717
+ ...getSlackErrorObservabilityAttributes(error)
13718
+ },
13719
+ "Failed to remove Slack processing reaction"
13720
+ );
13721
+ }
13722
+ }
13723
+ };
13724
+ }
13725
+
13726
+ // src/chat/services/auth-pause-response.ts
13727
+ var AUTH_PAUSE_RESPONSE = "I need authorization to continue. Check your private link to connect.";
13728
+ function buildAuthPauseResponse() {
13729
+ return AUTH_PAUSE_RESPONSE;
13730
+ }
13731
+
13483
13732
  // src/chat/runtime/slack-resume.ts
13484
13733
  function resolveReplyTimeoutMs(explicitTimeoutMs) {
13485
13734
  if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
@@ -13655,10 +13904,19 @@ async function resumeSlackTurn(args) {
13655
13904
  channelId: args.channelId,
13656
13905
  threadTs: args.threadTs
13657
13906
  });
13907
+ let processingReaction;
13658
13908
  let deferredPauseKind;
13659
13909
  let deferredPauseHandler;
13660
13910
  let deferredFailureHandler;
13661
13911
  try {
13912
+ if (args.messageTs) {
13913
+ processingReaction = await startSlackProcessingReactionForMessage({
13914
+ channelId: args.channelId,
13915
+ timestamp: args.messageTs,
13916
+ logException,
13917
+ logContext: { ...getResumeLogContext(args, lockKey) }
13918
+ });
13919
+ }
13662
13920
  if (args.initialText) {
13663
13921
  await postSlackMessageBestEffort(
13664
13922
  args.channelId,
@@ -13730,11 +13988,19 @@ async function resumeSlackTurn(args) {
13730
13988
  };
13731
13989
  }
13732
13990
  } finally {
13991
+ await processingReaction?.stop();
13733
13992
  await stateAdapter.releaseLock(lock);
13734
13993
  }
13735
13994
  if (deferredPauseHandler) {
13736
13995
  try {
13737
13996
  await deferredPauseHandler();
13997
+ if (deferredPauseKind === "auth") {
13998
+ await postSlackMessageBestEffort(
13999
+ args.channelId,
14000
+ args.threadTs,
14001
+ buildAuthPauseResponse()
14002
+ );
14003
+ }
13738
14004
  if (deferredPauseKind === "timeout") {
13739
14005
  await postTurnContinuationNoticeBestEffort({
13740
14006
  lockKey,
@@ -13762,6 +14028,7 @@ async function resumeAuthorizedRequest(args) {
13762
14028
  messageText: args.messageText,
13763
14029
  channelId: args.channelId,
13764
14030
  threadTs: args.threadTs,
14031
+ messageTs: args.messageTs,
13765
14032
  replyContext: args.replyContext,
13766
14033
  lockKey: args.lockKey,
13767
14034
  initialText: args.connectedText,
@@ -14084,6 +14351,7 @@ async function resumeAuthorizedMcpTurn(args) {
14084
14351
  messageText: userMessage.text,
14085
14352
  channelId: authSession.channelId,
14086
14353
  threadTs: authSession.threadTs,
14354
+ messageTs: getTurnUserSlackMessageTs(userMessage),
14087
14355
  lockKey: authSession.conversationId,
14088
14356
  connectedText: "",
14089
14357
  replyContext: {
@@ -14525,6 +14793,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
14525
14793
  messageText: stored.pendingMessage ?? userMessage.text,
14526
14794
  channelId: stored.channelId,
14527
14795
  threadTs: stored.threadTs,
14796
+ messageTs: getTurnUserSlackMessageTs(userMessage),
14528
14797
  lockKey: stored.resumeConversationId,
14529
14798
  initialText: "",
14530
14799
  replyContext: {
@@ -14617,14 +14886,15 @@ async function resumePendingOAuthMessage(stored) {
14617
14886
  const conversation = coerceThreadConversationState(
14618
14887
  await getPersistedThreadState(threadId)
14619
14888
  );
14620
- const latestUserMessageId = [...conversation.messages].reverse().find((message) => message.role === "user")?.id;
14889
+ const latestUserMessage = [...conversation.messages].reverse().find((message) => message.role === "user");
14621
14890
  const conversationContext = buildConversationContext(conversation, {
14622
- excludeMessageId: latestUserMessageId
14891
+ excludeMessageId: latestUserMessage?.id
14623
14892
  });
14624
14893
  await resumeAuthorizedRequest({
14625
14894
  messageText: stored.pendingMessage,
14626
14895
  channelId: stored.channelId,
14627
14896
  threadTs: stored.threadTs,
14897
+ messageTs: getTurnUserSlackMessageTs(latestUserMessage),
14628
14898
  connectedText: "",
14629
14899
  replyContext: {
14630
14900
  requester: { userId: stored.userId },
@@ -14881,12 +15151,7 @@ async function getJwks(issuer) {
14881
15151
  });
14882
15152
  return jwks;
14883
15153
  }
14884
- function validateSandboxClaim(payload, egressId) {
14885
- if (payload.sandbox_id !== egressId) {
14886
- throw new Error("Vercel OIDC token belongs to a different sandbox");
14887
- }
14888
- }
14889
- async function verifyVercelSandboxOidcToken(token, egressId) {
15154
+ async function verifyVercelSandboxOidcToken(token) {
14890
15155
  const unverified = decodeJwt(token);
14891
15156
  if (typeof unverified.iss !== "string") {
14892
15157
  throw new Error("Vercel OIDC token did not include an issuer");
@@ -14895,7 +15160,6 @@ async function verifyVercelSandboxOidcToken(token, egressId) {
14895
15160
  const verified = await jwtVerify(token, jwks, {
14896
15161
  issuer: unverified.iss
14897
15162
  });
14898
- validateSandboxClaim(verified.payload, egressId);
14899
15163
  return verified.payload;
14900
15164
  }
14901
15165
 
@@ -14904,7 +15168,7 @@ var OIDC_TOKEN_HEADER = "vercel-sandbox-oidc-token";
14904
15168
  var FORWARDED_HOST_HEADER = "vercel-forwarded-host";
14905
15169
  var FORWARDED_SCHEME_HEADER = "vercel-forwarded-scheme";
14906
15170
  var FORWARDED_PORT_HEADER = "vercel-forwarded-port";
14907
- var ROUTE_PREFIX = "/api/internal/sandbox-egress";
15171
+ var FORWARDED_PATH_HEADER = "vercel-forwarded-path";
14908
15172
  var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
14909
15173
  "connection",
14910
15174
  "host",
@@ -14920,7 +15184,8 @@ var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
14920
15184
  OIDC_TOKEN_HEADER,
14921
15185
  FORWARDED_HOST_HEADER,
14922
15186
  FORWARDED_SCHEME_HEADER,
14923
- FORWARDED_PORT_HEADER
15187
+ FORWARDED_PORT_HEADER,
15188
+ FORWARDED_PATH_HEADER
14924
15189
  ]);
14925
15190
  var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
14926
15191
  "content-encoding",
@@ -14930,15 +15195,61 @@ var AUTH_REJECTION_STATUS = /* @__PURE__ */ new Set([401, 403]);
14930
15195
  function jsonError(message, status) {
14931
15196
  return Response.json({ error: message }, { status });
14932
15197
  }
14933
- function normalizeHost(value) {
14934
- const trimmed = value.trim().toLowerCase();
14935
- if (!trimmed || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes(":")) {
14936
- return void 0;
14937
- }
14938
- return trimmed.replace(/\.$/, "");
15198
+ function shouldLogSandboxEgressInfo() {
15199
+ const environment = (process.env.SENTRY_ENVIRONMENT ?? process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "").trim().toLowerCase();
15200
+ return environment !== "production";
14939
15201
  }
14940
- function normalizeScheme(value) {
14941
- return value.trim().toLowerCase() === "https" ? "https" : void 0;
15202
+ function egressAttributes(input) {
15203
+ return {
15204
+ ...input.egressId ? { "app.sandbox.egress_id": input.egressId } : {},
15205
+ ...input.provider ? { "app.credential.provider": input.provider } : {},
15206
+ ...input.host ? { "server.address": input.host } : {},
15207
+ ...input.method ? { "http.request.method": input.method } : {},
15208
+ ...input.path ? { "url.path": input.path } : {},
15209
+ ...input.status ? { "http.response.status_code": input.status } : {}
15210
+ };
15211
+ }
15212
+ function routingAttributes(request, upstreamUrl) {
15213
+ const proxyUrl = new URL(request.url);
15214
+ const attributes = {
15215
+ "app.sandbox.egress.proxy_path": proxyUrl.pathname
15216
+ };
15217
+ if (upstreamUrl) {
15218
+ attributes["app.sandbox.egress.upstream_path"] = upstreamUrl.pathname;
15219
+ }
15220
+ return attributes;
15221
+ }
15222
+ function logSandboxEgressUpstreamRequest(input) {
15223
+ if (!shouldLogSandboxEgressInfo()) {
15224
+ return;
15225
+ }
15226
+ logInfo(
15227
+ "sandbox_egress_upstream_request",
15228
+ {},
15229
+ {
15230
+ ...egressAttributes({
15231
+ egressId: input.egressId,
15232
+ host: input.upstreamUrl.hostname,
15233
+ method: input.request.method,
15234
+ path: input.upstreamUrl.pathname,
15235
+ provider: input.provider,
15236
+ status: input.upstream.status
15237
+ }),
15238
+ ...routingAttributes(input.request, input.upstreamUrl),
15239
+ "app.sandbox.egress.upstream_ok": input.upstream.ok
15240
+ },
15241
+ `Sandbox egress ${input.request.method} ${input.upstreamUrl.hostname}${input.upstreamUrl.pathname} -> ${input.upstream.status}`
15242
+ );
15243
+ }
15244
+ function normalizeHost(value) {
15245
+ const trimmed = value.trim().toLowerCase();
15246
+ if (!trimmed || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes(":")) {
15247
+ return void 0;
15248
+ }
15249
+ return trimmed.replace(/\.$/, "");
15250
+ }
15251
+ function normalizeScheme(value) {
15252
+ return value.trim().toLowerCase() === "https" ? "https" : void 0;
14942
15253
  }
14943
15254
  function normalizePort(value) {
14944
15255
  if (!value) {
@@ -14951,18 +15262,27 @@ function normalizePort(value) {
14951
15262
  const port = Number.parseInt(trimmed, 10);
14952
15263
  return port >= 1 && port <= 65535 ? trimmed : void 0;
14953
15264
  }
14954
- function upstreamPath(request, egressId) {
14955
- const url = new URL(request.url);
14956
- const prefix = `${ROUTE_PREFIX}/${encodeURIComponent(egressId)}`;
14957
- if (url.pathname === prefix) {
14958
- return `/${url.search}`;
14959
- }
14960
- if (url.pathname.startsWith(`${prefix}/`)) {
14961
- return `${url.pathname.slice(prefix.length)}${url.search}`;
15265
+ function sandboxIdFromPayload(payload) {
15266
+ return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
15267
+ }
15268
+ function upstreamPath(request) {
15269
+ const forwardedPath = request.headers.get(FORWARDED_PATH_HEADER);
15270
+ if (forwardedPath?.trim()) {
15271
+ const path11 = forwardedPath.trim();
15272
+ if (!path11.startsWith("/") || path11.startsWith("//") || path11.includes("#") || /[\r\n]/.test(path11)) {
15273
+ return { ok: false, error: "Invalid forwarded path" };
15274
+ }
15275
+ try {
15276
+ const url2 = new URL(path11, "https://sandbox-forwarded.local");
15277
+ return { ok: true, path: `${url2.pathname}${url2.search}` };
15278
+ } catch {
15279
+ return { ok: false, error: "Invalid forwarded path" };
15280
+ }
14962
15281
  }
14963
- return void 0;
15282
+ const url = new URL(request.url);
15283
+ return { ok: true, path: `${url.pathname}${url.search}` };
14964
15284
  }
14965
- function buildUpstreamUrl(request, egressId) {
15285
+ function buildUpstreamUrl(request) {
14966
15286
  const forwardedHost = request.headers.get(FORWARDED_HOST_HEADER);
14967
15287
  if (!forwardedHost?.trim()) {
14968
15288
  return { ok: false, error: "Missing forwarded host" };
@@ -14984,12 +15304,14 @@ function buildUpstreamUrl(request, egressId) {
14984
15304
  if (forwardedPort && !port) {
14985
15305
  return { ok: false, error: "Invalid forwarded port" };
14986
15306
  }
14987
- const path11 = upstreamPath(request, egressId);
14988
- if (!path11) {
14989
- return { ok: false, error: "Invalid egress route" };
15307
+ const path11 = upstreamPath(request);
15308
+ if (!path11.ok) {
15309
+ return { ok: false, error: path11.error };
14990
15310
  }
14991
15311
  try {
14992
- const url = new URL(`${scheme}://${host}${port ? `:${port}` : ""}${path11}`);
15312
+ const url = new URL(
15313
+ `${scheme}://${host}${port ? `:${port}` : ""}${path11.path}`
15314
+ );
14993
15315
  return { ok: true, url };
14994
15316
  } catch {
14995
15317
  return { ok: false, error: "Invalid forwarded URL" };
@@ -15063,15 +15385,20 @@ function hasTransformForHost(lease, host) {
15063
15385
  (transform) => matchesSandboxEgressDomain(host, transform.domain)
15064
15386
  );
15065
15387
  }
15066
- async function proxySandboxEgressRequest(request, egressId, deps = {}) {
15388
+ function isSandboxEgressForwardedRequest(request) {
15389
+ return Boolean(
15390
+ request.headers.get(OIDC_TOKEN_HEADER)?.trim() && request.headers.get(FORWARDED_HOST_HEADER)?.trim() && request.headers.get(FORWARDED_SCHEME_HEADER)?.trim()
15391
+ );
15392
+ }
15393
+ async function proxySandboxEgressRequest(request, deps = {}) {
15067
15394
  const oidcToken = request.headers.get(OIDC_TOKEN_HEADER)?.trim();
15068
15395
  if (!oidcToken) {
15069
15396
  return jsonError("Missing Vercel Sandbox OIDC token", 401);
15070
15397
  }
15398
+ let oidcPayload;
15071
15399
  try {
15072
- await (deps.verifyOidc ?? verifyVercelSandboxOidcToken)(
15073
- oidcToken,
15074
- egressId
15400
+ oidcPayload = await (deps.verifyOidc ?? verifyVercelSandboxOidcToken)(
15401
+ oidcToken
15075
15402
  );
15076
15403
  } catch (error) {
15077
15404
  logWarn(
@@ -15084,24 +15411,101 @@ async function proxySandboxEgressRequest(request, egressId, deps = {}) {
15084
15411
  );
15085
15412
  return jsonError("Invalid Vercel Sandbox OIDC token", 401);
15086
15413
  }
15087
- const upstreamResult = buildUpstreamUrl(request, egressId);
15414
+ const activeEgressId = sandboxIdFromPayload(oidcPayload);
15415
+ if (!activeEgressId) {
15416
+ logWarn(
15417
+ "sandbox_egress_oidc_session_missing",
15418
+ {},
15419
+ {
15420
+ "http.request.method": request.method,
15421
+ "url.path": new URL(request.url).pathname
15422
+ },
15423
+ "Sandbox egress OIDC payload did not include a VM session id"
15424
+ );
15425
+ return jsonError(
15426
+ "Vercel Sandbox OIDC token did not include sandbox_id",
15427
+ 401
15428
+ );
15429
+ }
15430
+ const upstreamResult = buildUpstreamUrl(request);
15088
15431
  if (!upstreamResult.ok) {
15432
+ logWarn(
15433
+ "sandbox_egress_upstream_url_invalid",
15434
+ {},
15435
+ {
15436
+ ...egressAttributes({
15437
+ egressId: activeEgressId,
15438
+ method: request.method,
15439
+ path: new URL(request.url).pathname,
15440
+ status: 400
15441
+ }),
15442
+ ...routingAttributes(request)
15443
+ },
15444
+ "Sandbox egress forwarded request had invalid upstream routing headers"
15445
+ );
15089
15446
  return jsonError(upstreamResult.error, 400);
15090
15447
  }
15091
15448
  const upstreamUrl = upstreamResult.url;
15092
15449
  const provider = resolveSandboxEgressProviderForHost(upstreamUrl.hostname);
15093
15450
  if (!provider) {
15451
+ logWarn(
15452
+ "sandbox_egress_provider_unresolved",
15453
+ {},
15454
+ {
15455
+ ...egressAttributes({
15456
+ egressId: activeEgressId,
15457
+ host: upstreamUrl.hostname,
15458
+ method: request.method,
15459
+ path: upstreamUrl.pathname,
15460
+ status: 403
15461
+ }),
15462
+ ...routingAttributes(request, upstreamUrl)
15463
+ },
15464
+ "Sandbox egress forwarded host is not owned by any credential provider"
15465
+ );
15094
15466
  return jsonError("No provider owns forwarded host", 403);
15095
15467
  }
15096
- const session = await getSandboxEgressSession(egressId);
15468
+ const session = await getSandboxEgressSession(activeEgressId);
15097
15469
  if (!session) {
15470
+ logWarn(
15471
+ "sandbox_egress_session_unauthorized",
15472
+ {},
15473
+ {
15474
+ ...egressAttributes({
15475
+ egressId: activeEgressId,
15476
+ host: upstreamUrl.hostname,
15477
+ method: request.method,
15478
+ path: upstreamUrl.pathname,
15479
+ provider,
15480
+ status: 403
15481
+ }),
15482
+ ...routingAttributes(request, upstreamUrl)
15483
+ },
15484
+ "Sandbox egress VM session is not authorized for requester credentials"
15485
+ );
15098
15486
  return jsonError("Sandbox egress session is not authorized", 403);
15099
15487
  }
15100
15488
  let lease;
15101
15489
  try {
15102
- lease = await credentialLease(egressId, provider, session);
15490
+ lease = await credentialLease(activeEgressId, provider, session);
15103
15491
  } catch (error) {
15104
15492
  if (error instanceof CredentialUnavailableError) {
15493
+ logWarn(
15494
+ "sandbox_egress_credential_unavailable",
15495
+ {},
15496
+ {
15497
+ ...egressAttributes({
15498
+ egressId: activeEgressId,
15499
+ host: upstreamUrl.hostname,
15500
+ method: request.method,
15501
+ path: upstreamUrl.pathname,
15502
+ provider,
15503
+ status: 401
15504
+ }),
15505
+ ...routingAttributes(request, upstreamUrl)
15506
+ },
15507
+ "Sandbox egress provider credential is unavailable"
15508
+ );
15105
15509
  return new Response(
15106
15510
  `junior-auth-required provider=${error.provider} 401 unauthorized
15107
15511
  ${error.message}`,
@@ -15114,28 +15518,80 @@ ${error.message}`,
15114
15518
  throw error;
15115
15519
  }
15116
15520
  if (!hasTransformForHost(lease, upstreamUrl.hostname)) {
15521
+ logWarn(
15522
+ "sandbox_egress_transform_missing",
15523
+ {},
15524
+ {
15525
+ ...egressAttributes({
15526
+ egressId: activeEgressId,
15527
+ host: upstreamUrl.hostname,
15528
+ method: request.method,
15529
+ path: upstreamUrl.pathname,
15530
+ provider,
15531
+ status: 403
15532
+ }),
15533
+ "app.sandbox.egress.transform_domains": lease.headerTransforms.map(
15534
+ (transform) => transform.domain
15535
+ ),
15536
+ ...routingAttributes(request, upstreamUrl)
15537
+ },
15538
+ "Sandbox egress credential lease does not cover forwarded host"
15539
+ );
15117
15540
  return jsonError("Credential lease does not cover forwarded host", 403);
15118
15541
  }
15119
15542
  const body = await requestBodyBytes(request);
15120
- const upstream = await (deps.fetch ?? fetch)(upstreamUrl, {
15543
+ const fetchImpl = deps.fetch ?? fetch;
15544
+ const headers = requestHeaders(request, lease, upstreamUrl.hostname);
15545
+ const upstream = await fetchImpl(upstreamUrl, {
15121
15546
  method: request.method,
15122
- headers: requestHeaders(request, lease, upstreamUrl.hostname),
15123
- ...body ? { body } : {},
15547
+ headers,
15548
+ ...body !== void 0 ? { body } : {},
15124
15549
  redirect: "manual"
15125
15550
  });
15551
+ logSandboxEgressUpstreamRequest({
15552
+ egressId: activeEgressId,
15553
+ provider,
15554
+ request,
15555
+ upstream,
15556
+ upstreamUrl
15557
+ });
15558
+ if (upstream.status >= 400) {
15559
+ logWarn(
15560
+ "sandbox_egress_upstream_error_response",
15561
+ {},
15562
+ {
15563
+ ...egressAttributes({
15564
+ egressId: activeEgressId,
15565
+ host: upstreamUrl.hostname,
15566
+ method: request.method,
15567
+ path: upstreamUrl.pathname,
15568
+ provider,
15569
+ status: upstream.status
15570
+ }),
15571
+ ...routingAttributes(request, upstreamUrl),
15572
+ "error.type": `http_${upstream.status}`
15573
+ },
15574
+ `Sandbox egress upstream returned HTTP ${upstream.status}`
15575
+ );
15576
+ }
15126
15577
  if (AUTH_REJECTION_STATUS.has(upstream.status)) {
15127
15578
  logWarn(
15128
15579
  "sandbox_egress_upstream_auth_rejected",
15129
15580
  {},
15130
15581
  {
15131
- "app.credential.provider": provider,
15132
- "http.request.method": request.method,
15133
- "http.response.status_code": upstream.status,
15134
- "server.address": upstreamUrl.hostname
15582
+ ...egressAttributes({
15583
+ egressId: activeEgressId,
15584
+ host: upstreamUrl.hostname,
15585
+ method: request.method,
15586
+ path: upstreamUrl.pathname,
15587
+ provider,
15588
+ status: upstream.status
15589
+ }),
15590
+ ...routingAttributes(request, upstreamUrl)
15135
15591
  },
15136
15592
  "Sandbox egress upstream auth rejected"
15137
15593
  );
15138
- await clearSandboxEgressCredentialLease(egressId, provider, session);
15594
+ await clearSandboxEgressCredentialLease(activeEgressId, provider, session);
15139
15595
  }
15140
15596
  return new Response(upstream.body, {
15141
15597
  status: upstream.status,
@@ -15145,54 +15601,11 @@ ${error.message}`,
15145
15601
  }
15146
15602
 
15147
15603
  // src/handlers/sandbox-egress-proxy.ts
15148
- async function ALL(request, egressId) {
15149
- return await proxySandboxEgressRequest(request, egressId);
15150
- }
15151
-
15152
- // src/chat/slack/context.ts
15153
- function toTrimmedSlackString(value) {
15154
- const normalized = toOptionalString(value);
15155
- return normalized?.trim() || void 0;
15156
- }
15157
- function parseSlackThreadId(threadId) {
15158
- const normalizedThreadId = toTrimmedSlackString(threadId);
15159
- if (!normalizedThreadId) {
15160
- return void 0;
15161
- }
15162
- const parts = normalizedThreadId.split(":");
15163
- if (parts.length !== 3 || parts[0] !== "slack") {
15164
- return void 0;
15165
- }
15166
- const channelId = toTrimmedSlackString(parts[1]);
15167
- const threadTs = toTrimmedSlackString(parts[2]);
15168
- if (!channelId || !threadTs) {
15169
- return void 0;
15170
- }
15171
- return { channelId, threadTs };
15604
+ async function ALL(request) {
15605
+ return await proxySandboxEgressRequest(request);
15172
15606
  }
15173
- function resolveSlackChannelIdFromThreadId(threadId) {
15174
- return parseSlackThreadId(threadId)?.channelId;
15175
- }
15176
- function resolveSlackChannelIdFromMessage(message) {
15177
- const messageChannelId = toTrimmedSlackString(
15178
- message.channelId
15179
- );
15180
- if (messageChannelId) {
15181
- return messageChannelId;
15182
- }
15183
- const raw = message.raw;
15184
- if (raw && typeof raw === "object") {
15185
- const rawChannel = toTrimmedSlackString(
15186
- raw.channel
15187
- );
15188
- if (rawChannel) {
15189
- return rawChannel;
15190
- }
15191
- }
15192
- const threadId = toTrimmedSlackString(
15193
- message.threadId
15194
- );
15195
- return resolveSlackChannelIdFromThreadId(threadId);
15607
+ function isSandboxEgressRequest(request) {
15608
+ return isSandboxEgressForwardedRequest(request);
15196
15609
  }
15197
15610
 
15198
15611
  // src/handlers/turn-resume.ts
@@ -15487,11 +15900,11 @@ var DIRECTED_FOLLOW_UP_CUE_RE = /\b(?:you said|you just said|your last response|
15487
15900
  var TERSE_CLARIFICATION_RE = /^(?:which one|which ones|why|how so|what do you mean|what did you mean|say more|explain that|clarify that|expand on that|elaborate on that)\??$/i;
15488
15901
  var GENERIC_IMMEDIATE_SIDE_CONVERSATION_RE = /^(?:is that (?:the )?right (?:approach|call|move)|(?:can|could|would) you check on this)\??$/i;
15489
15902
  var RECENT_THREAD_WINDOW = 6;
15490
- function escapeRegExp2(value) {
15903
+ function escapeRegExp3(value) {
15491
15904
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15492
15905
  }
15493
15906
  function containsAssistantInvocation(text, botUserName) {
15494
- const escapedUserName = escapeRegExp2(botUserName);
15907
+ const escapedUserName = escapeRegExp3(botUserName);
15495
15908
  const plainNameMentionRe = new RegExp(`(^|\\s)@${escapedUserName}\\b`, "i");
15496
15909
  const labeledEntityMentionRe = new RegExp(
15497
15910
  `<@[^>|]+\\|${escapedUserName}>`,
@@ -15786,187 +16199,6 @@ async function decideSubscribedThreadReply(args) {
15786
16199
  }
15787
16200
  }
15788
16201
 
15789
- // src/chat/slack/errors.ts
15790
- function getSlackApiErrorCode(error) {
15791
- if (!error || typeof error !== "object") {
15792
- return void 0;
15793
- }
15794
- const candidate = error;
15795
- if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
15796
- return candidate.data.error;
15797
- }
15798
- if (typeof candidate.code === "string" && candidate.code.trim().length > 0) {
15799
- return candidate.code;
15800
- }
15801
- return void 0;
15802
- }
15803
- function getSlackErrorObservabilityAttributes(error) {
15804
- if (!error || typeof error !== "object") {
15805
- return {};
15806
- }
15807
- const candidate = error;
15808
- const attributes = {};
15809
- if (typeof candidate.code === "string" && candidate.code.trim().length > 0) {
15810
- attributes["app.slack.error_code"] = candidate.code;
15811
- }
15812
- if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
15813
- attributes["app.slack.api_error"] = candidate.data.error;
15814
- }
15815
- const requestId = getHeaderString(candidate.headers, "x-slack-req-id");
15816
- if (requestId) {
15817
- attributes["app.slack.request_id"] = requestId;
15818
- }
15819
- if (typeof candidate.statusCode === "number" && Number.isFinite(candidate.statusCode)) {
15820
- attributes["http.response.status_code"] = candidate.statusCode;
15821
- }
15822
- return attributes;
15823
- }
15824
- function isSlackTitlePermissionError(error) {
15825
- const code = getSlackApiErrorCode(error);
15826
- return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
15827
- }
15828
-
15829
- // src/chat/runtime/thread-context.ts
15830
- function escapeRegExp3(value) {
15831
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15832
- }
15833
- function stripLeadingBotMention(text, options = {}) {
15834
- if (!text.trim()) return text;
15835
- let next = text;
15836
- if (options.stripLeadingSlackMentionToken) {
15837
- next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
15838
- }
15839
- const mentionByNameRe = new RegExp(
15840
- `^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
15841
- "i"
15842
- );
15843
- next = next.replace(mentionByNameRe, "").trim();
15844
- const mentionByLabeledEntityRe = new RegExp(
15845
- `^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
15846
- "i"
15847
- );
15848
- next = next.replace(mentionByLabeledEntityRe, "").trim();
15849
- return next;
15850
- }
15851
- function getThreadId(thread, _message) {
15852
- return toOptionalString(thread.id);
15853
- }
15854
- function getRunId(thread, message) {
15855
- return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
15856
- }
15857
- function getChannelId(thread, message) {
15858
- return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
15859
- }
15860
- function getThreadTs(threadId) {
15861
- return parseSlackThreadId(threadId)?.threadTs;
15862
- }
15863
- function getAssistantThreadContext(message) {
15864
- const raw = message.raw;
15865
- const rawRecord = raw && typeof raw === "object" ? raw : void 0;
15866
- const channelId = toOptionalString(rawRecord?.channel);
15867
- if (channelId) {
15868
- const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
15869
- const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
15870
- if (threadTs) {
15871
- return { channelId, threadTs };
15872
- }
15873
- }
15874
- const parsedThreadId = parseSlackThreadId(
15875
- toOptionalString(message.threadId)
15876
- );
15877
- if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
15878
- return void 0;
15879
- }
15880
- return parsedThreadId;
15881
- }
15882
- function getMessageTs(message) {
15883
- const directTs = toOptionalString(
15884
- message.ts
15885
- );
15886
- if (directTs) {
15887
- return directTs;
15888
- }
15889
- const raw = message.raw;
15890
- if (!raw || typeof raw !== "object") {
15891
- return void 0;
15892
- }
15893
- const rawRecord = raw;
15894
- return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
15895
- }
15896
-
15897
- // src/chat/runtime/processing-reaction.ts
15898
- var PROCESSING_REACTION_EMOJI = "eyes";
15899
- var noProcessingReaction = {
15900
- keep: () => void 0,
15901
- stop: async () => void 0
15902
- };
15903
- function isProcessingReactionEmoji(value) {
15904
- return typeof value === "string" && normalizeSlackEmojiName(value) === PROCESSING_REACTION_EMOJI;
15905
- }
15906
- function shouldKeepProcessingReactionForToolInvocation(input) {
15907
- return input.toolName === "slackMessageAddReaction" && isProcessingReactionEmoji(input.params.emoji);
15908
- }
15909
- async function startSlackProcessingReaction(args) {
15910
- if (args.message.author.isMe) {
15911
- return noProcessingReaction;
15912
- }
15913
- const channelId = getChannelId(args.thread, args.message);
15914
- const messageTs = getMessageTs(args.message);
15915
- if (!channelId || !messageTs) {
15916
- return noProcessingReaction;
15917
- }
15918
- try {
15919
- await addReactionToMessage({
15920
- channelId,
15921
- timestamp: messageTs,
15922
- emoji: PROCESSING_REACTION_EMOJI
15923
- });
15924
- } catch (error) {
15925
- args.logException(
15926
- error,
15927
- "slack_processing_reaction_add_failed",
15928
- args.logContext,
15929
- {
15930
- "app.slack.action": "reactions.add",
15931
- "messaging.message.id": messageTs,
15932
- ...getSlackErrorObservabilityAttributes(error)
15933
- },
15934
- "Failed to add Slack processing reaction"
15935
- );
15936
- return noProcessingReaction;
15937
- }
15938
- let shouldRemove = true;
15939
- return {
15940
- keep: () => {
15941
- shouldRemove = false;
15942
- },
15943
- stop: async () => {
15944
- if (!shouldRemove) {
15945
- return;
15946
- }
15947
- try {
15948
- await removeReactionFromMessage({
15949
- channelId,
15950
- timestamp: messageTs,
15951
- emoji: PROCESSING_REACTION_EMOJI
15952
- });
15953
- } catch (error) {
15954
- args.logException(
15955
- error,
15956
- "slack_processing_reaction_remove_failed",
15957
- args.logContext,
15958
- {
15959
- "app.slack.action": "reactions.remove",
15960
- "messaging.message.id": messageTs,
15961
- ...getSlackErrorObservabilityAttributes(error)
15962
- },
15963
- "Failed to remove Slack processing reaction"
15964
- );
15965
- }
15966
- }
15967
- };
15968
- }
15969
-
15970
16202
  // src/chat/runtime/slack-runtime.ts
15971
16203
  var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
15972
16204
  async function maybeHandleThreadOptOutDecision(args) {
@@ -16988,8 +17220,14 @@ function createJuniorRuntimeServices(overrides = {}) {
16988
17220
  }
16989
17221
 
16990
17222
  // src/chat/slack/message.ts
17223
+ function isSlackMessageTs(value) {
17224
+ return /^\d+(?:\.\d+)?$/.test(value.trim());
17225
+ }
16991
17226
  function getSlackMessageTs(message) {
16992
- if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
17227
+ if (isSlackMessageTs(message.id)) {
17228
+ return message.id;
17229
+ }
17230
+ if (message.raw && typeof message.raw === "object") {
16993
17231
  const ts = message.raw.ts;
16994
17232
  if (typeof ts === "string" && ts.length > 0) {
16995
17233
  return ts;
@@ -17152,6 +17390,26 @@ function createReplyToThread(deps) {
17152
17390
  throw error;
17153
17391
  }
17154
17392
  };
17393
+ const postAuthPauseNotice = async () => {
17394
+ try {
17395
+ await beforeFirstResponsePost();
17396
+ await thread.post(
17397
+ buildSlackOutputMessage(buildAuthPauseResponse())
17398
+ );
17399
+ } catch (error) {
17400
+ logException(
17401
+ error,
17402
+ "slack_auth_pause_notice_post_failed",
17403
+ turnTraceContext,
17404
+ {
17405
+ "app.slack.reply_stage": "thread_reply_auth_pause_notice",
17406
+ ...messageTs ? { "messaging.message.id": messageTs } : {},
17407
+ ...getSlackErrorObservabilityAttributes(error)
17408
+ },
17409
+ "Failed to post auth pause notice"
17410
+ );
17411
+ }
17412
+ };
17155
17413
  const activeTurnId = preparedState.conversation.processing.activeTurnId;
17156
17414
  if (conversationId && activeTurnId) {
17157
17415
  const resumeRequest = await deps.services.getAwaitingTurnContinuationRequest({
@@ -17466,6 +17724,7 @@ function createReplyToThread(deps) {
17466
17724
  }
17467
17725
  } catch (error) {
17468
17726
  if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
17727
+ await postAuthPauseNotice();
17469
17728
  completeAuthPauseTurn({
17470
17729
  conversation: preparedState.conversation,
17471
17730
  sessionId: error.metadata?.sessionId ?? turnId
@@ -18692,6 +18951,12 @@ async function createApp(options) {
18692
18951
  logException(err, "unhandled_route_error");
18693
18952
  return c.text("Internal Server Error", 500);
18694
18953
  });
18954
+ app.use("*", async (c, next) => {
18955
+ if (isSandboxEgressRequest(c.req.raw)) {
18956
+ return await ALL(c.req.raw);
18957
+ }
18958
+ await next();
18959
+ });
18695
18960
  app.get("/", () => GET3());
18696
18961
  app.get("/health", () => GET2());
18697
18962
  app.get("/api/info", () => GET());
@@ -18704,12 +18969,6 @@ async function createApp(options) {
18704
18969
  app.post("/api/internal/turn-resume", (c) => {
18705
18970
  return POST(c.req.raw, waitUntil);
18706
18971
  });
18707
- app.all("/api/internal/sandbox-egress/:egressId", (c) => {
18708
- return ALL(c.req.raw, c.req.param("egressId"));
18709
- });
18710
- app.all("/api/internal/sandbox-egress/:egressId/*", (c) => {
18711
- return ALL(c.req.raw, c.req.param("egressId"));
18712
- });
18713
18972
  app.post("/api/webhooks/:platform", (c) => {
18714
18973
  return POST2(c.req.raw, c.req.param("platform"), waitUntil);
18715
18974
  });
@@ -494,7 +494,7 @@ var NON_INTERACTIVE_ENV = {
494
494
  GCM_INTERACTIVE: "never",
495
495
  DEBIAN_FRONTEND: "noninteractive",
496
496
  // Git credential isolation: prevent git from sending its own auth so the
497
- // sandbox network proxy's header transforms are the sole credential source.
497
+ // sandbox egress proxy's header transforms are the sole credential source.
498
498
  GIT_ASKPASS: "/bin/true",
499
499
  GIT_CONFIG_NOSYSTEM: "1",
500
500
  GIT_CONFIG_COUNT: "2",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
3
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-QAMTCT2R.js";
4
+ } from "../chunk-ELM6HJ6S.js";
5
5
  import {
6
6
  getPluginProviders,
7
7
  getPluginRuntimeDependencies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"