@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-
|
|
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
|
|
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
|
|
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
|
|
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 () =>
|
|
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:
|
|
10177
|
-
afterCommand:
|
|
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
|
|
14889
|
+
const latestUserMessage = [...conversation.messages].reverse().find((message) => message.role === "user");
|
|
14621
14890
|
const conversationContext = buildConversationContext(conversation, {
|
|
14622
|
-
excludeMessageId:
|
|
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
|
|
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
|
|
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
|
|
14934
|
-
const
|
|
14935
|
-
|
|
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
|
|
14941
|
-
return
|
|
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
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
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
|
-
|
|
15282
|
+
const url = new URL(request.url);
|
|
15283
|
+
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
14964
15284
|
}
|
|
14965
|
-
function buildUpstreamUrl(request
|
|
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
|
|
14988
|
-
if (!path11) {
|
|
14989
|
-
return { ok: false, error:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
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(
|
|
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
|
|
15149
|
-
return await proxySandboxEgressRequest(request
|
|
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
|
|
15174
|
-
return
|
|
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
|
|
15903
|
+
function escapeRegExp3(value) {
|
|
15491
15904
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15492
15905
|
}
|
|
15493
15906
|
function containsAssistantInvocation(text, botUserName) {
|
|
15494
|
-
const escapedUserName =
|
|
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
|
|
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
|
|
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",
|