@sentry/junior 0.44.0 → 0.45.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 +1061 -720
- package/dist/cli/check.js +216 -11
- package/dist/cli/init.js +1 -0
- package/package.json +5 -5
package/dist/app.js
CHANGED
|
@@ -1274,6 +1274,37 @@ async function addReactionToMessage(input) {
|
|
|
1274
1274
|
}
|
|
1275
1275
|
return { ok: true };
|
|
1276
1276
|
}
|
|
1277
|
+
async function removeReactionFromMessage(input) {
|
|
1278
|
+
const channelId = requireSlackConversationId(
|
|
1279
|
+
input.channelId,
|
|
1280
|
+
"Slack reaction removal"
|
|
1281
|
+
);
|
|
1282
|
+
const timestamp = requireSlackMessageTimestamp(
|
|
1283
|
+
input.timestamp,
|
|
1284
|
+
"Slack reaction removal"
|
|
1285
|
+
);
|
|
1286
|
+
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
1287
|
+
if (!emoji) {
|
|
1288
|
+
throw new Error("Slack reaction removal requires a valid emoji alias name");
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
await withSlackRetries(
|
|
1292
|
+
() => getSlackClient().reactions.remove({
|
|
1293
|
+
channel: channelId,
|
|
1294
|
+
timestamp,
|
|
1295
|
+
name: emoji
|
|
1296
|
+
}),
|
|
1297
|
+
3,
|
|
1298
|
+
{ action: "reactions.remove" }
|
|
1299
|
+
);
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
if (error instanceof SlackActionError && error.code === "no_reaction") {
|
|
1302
|
+
return { ok: true };
|
|
1303
|
+
}
|
|
1304
|
+
throw error;
|
|
1305
|
+
}
|
|
1306
|
+
return { ok: true };
|
|
1307
|
+
}
|
|
1277
1308
|
|
|
1278
1309
|
// src/chat/oauth-flow.ts
|
|
1279
1310
|
var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -10620,86 +10651,477 @@ function normalizeToolResult(result, isSandboxResult) {
|
|
|
10620
10651
|
};
|
|
10621
10652
|
}
|
|
10622
10653
|
|
|
10623
|
-
// src/chat/
|
|
10624
|
-
function
|
|
10625
|
-
|
|
10626
|
-
|
|
10654
|
+
// src/chat/credentials/unlink-provider.ts
|
|
10655
|
+
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
10656
|
+
await Promise.all([
|
|
10657
|
+
userTokenStore.delete(userId, provider),
|
|
10658
|
+
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
10659
|
+
deleteMcpServerSessionId(userId, provider),
|
|
10660
|
+
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
10661
|
+
]);
|
|
10662
|
+
}
|
|
10663
|
+
|
|
10664
|
+
// src/chat/state/turn-session-store.ts
|
|
10665
|
+
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
10666
|
+
var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10667
|
+
function agentTurnSessionKey(conversationId, sessionId) {
|
|
10668
|
+
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
10669
|
+
}
|
|
10670
|
+
function parseAgentTurnSessionCheckpoint(value) {
|
|
10671
|
+
if (typeof value !== "string") {
|
|
10672
|
+
return void 0;
|
|
10627
10673
|
}
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10674
|
+
try {
|
|
10675
|
+
const parsed = JSON.parse(value);
|
|
10676
|
+
if (!isRecord(parsed)) {
|
|
10677
|
+
return void 0;
|
|
10678
|
+
}
|
|
10679
|
+
const status = parsed.state;
|
|
10680
|
+
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
10681
|
+
return void 0;
|
|
10682
|
+
}
|
|
10683
|
+
const conversationId = parsed.conversationId;
|
|
10684
|
+
const sessionId = parsed.sessionId;
|
|
10685
|
+
const sliceId = parsed.sliceId;
|
|
10686
|
+
const checkpointVersion = parsed.checkpointVersion;
|
|
10687
|
+
const updatedAtMs = parsed.updatedAtMs;
|
|
10688
|
+
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
10689
|
+
return void 0;
|
|
10690
|
+
}
|
|
10691
|
+
return {
|
|
10692
|
+
checkpointVersion,
|
|
10693
|
+
conversationId,
|
|
10694
|
+
sessionId,
|
|
10695
|
+
sliceId,
|
|
10696
|
+
state: status,
|
|
10697
|
+
updatedAtMs,
|
|
10698
|
+
piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
|
|
10699
|
+
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
10700
|
+
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
10701
|
+
(value2) => typeof value2 === "string"
|
|
10702
|
+
)
|
|
10703
|
+
} : {},
|
|
10704
|
+
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
10705
|
+
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
10706
|
+
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
10707
|
+
};
|
|
10708
|
+
} catch {
|
|
10709
|
+
return void 0;
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
10713
|
+
const stateAdapter = getStateAdapter();
|
|
10714
|
+
await stateAdapter.connect();
|
|
10715
|
+
const value = await stateAdapter.get(
|
|
10716
|
+
agentTurnSessionKey(conversationId, sessionId)
|
|
10717
|
+
);
|
|
10718
|
+
return parseAgentTurnSessionCheckpoint(value);
|
|
10719
|
+
}
|
|
10720
|
+
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
10721
|
+
const stateAdapter = getStateAdapter();
|
|
10722
|
+
await stateAdapter.connect();
|
|
10723
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
10724
|
+
args.conversationId,
|
|
10725
|
+
args.sessionId
|
|
10726
|
+
);
|
|
10727
|
+
const checkpoint = {
|
|
10728
|
+
checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
|
|
10729
|
+
conversationId: args.conversationId,
|
|
10730
|
+
sessionId: args.sessionId,
|
|
10731
|
+
sliceId: args.sliceId,
|
|
10732
|
+
state: args.state,
|
|
10733
|
+
updatedAtMs: Date.now(),
|
|
10734
|
+
piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
|
|
10735
|
+
...Array.isArray(args.loadedSkillNames) ? {
|
|
10736
|
+
loadedSkillNames: args.loadedSkillNames.filter(
|
|
10737
|
+
(value) => typeof value === "string"
|
|
10738
|
+
)
|
|
10739
|
+
} : {},
|
|
10740
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
10741
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
10742
|
+
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
10634
10743
|
};
|
|
10744
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
10745
|
+
await stateAdapter.set(
|
|
10746
|
+
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
10747
|
+
JSON.stringify(checkpoint),
|
|
10748
|
+
ttlMs
|
|
10749
|
+
);
|
|
10750
|
+
return checkpoint;
|
|
10635
10751
|
}
|
|
10636
|
-
function
|
|
10637
|
-
const
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10752
|
+
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
10753
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
10754
|
+
args.conversationId,
|
|
10755
|
+
args.sessionId
|
|
10756
|
+
);
|
|
10757
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
10758
|
+
return void 0;
|
|
10759
|
+
}
|
|
10760
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
10761
|
+
conversationId: existing.conversationId,
|
|
10762
|
+
sessionId: existing.sessionId,
|
|
10763
|
+
sliceId: existing.sliceId,
|
|
10764
|
+
state: "superseded",
|
|
10765
|
+
piMessages: existing.piMessages,
|
|
10766
|
+
loadedSkillNames: existing.loadedSkillNames,
|
|
10767
|
+
resumeReason: existing.resumeReason,
|
|
10768
|
+
resumedFromSliceId: existing.resumedFromSliceId,
|
|
10769
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
10641
10770
|
});
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10651
|
-
"error.type": errorType,
|
|
10652
|
-
"exception.message": errorMessage
|
|
10653
|
-
},
|
|
10654
|
-
"Agent tool call failed"
|
|
10655
|
-
);
|
|
10771
|
+
}
|
|
10772
|
+
|
|
10773
|
+
// src/chat/services/pending-auth.ts
|
|
10774
|
+
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
10775
|
+
function canReusePendingAuthLink(args) {
|
|
10776
|
+
const { pendingAuth } = args;
|
|
10777
|
+
if (!pendingAuth) {
|
|
10778
|
+
return false;
|
|
10656
10779
|
}
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
10664
|
-
"gen_ai.operation.name": "execute_tool",
|
|
10665
|
-
"gen_ai.tool.name": toolName,
|
|
10666
|
-
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10667
|
-
...getToolErrorAttributes(error)
|
|
10668
|
-
},
|
|
10669
|
-
"Agent tool call failed"
|
|
10670
|
-
);
|
|
10780
|
+
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
10781
|
+
}
|
|
10782
|
+
function getConversationPendingAuth(args) {
|
|
10783
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
10784
|
+
if (!pendingAuth) {
|
|
10785
|
+
return void 0;
|
|
10671
10786
|
}
|
|
10672
|
-
|
|
10787
|
+
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
10788
|
+
return void 0;
|
|
10789
|
+
}
|
|
10790
|
+
return pendingAuth;
|
|
10791
|
+
}
|
|
10792
|
+
function clearPendingAuth(conversation, sessionId) {
|
|
10793
|
+
if (!conversation.processing.pendingAuth) {
|
|
10794
|
+
return;
|
|
10795
|
+
}
|
|
10796
|
+
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
10797
|
+
return;
|
|
10798
|
+
}
|
|
10799
|
+
conversation.processing.pendingAuth = void 0;
|
|
10800
|
+
}
|
|
10801
|
+
async function applyPendingAuthUpdate(args) {
|
|
10802
|
+
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
10803
|
+
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
10804
|
+
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
10805
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
10806
|
+
conversationId: args.conversationId,
|
|
10807
|
+
sessionId: previousPendingAuth.sessionId,
|
|
10808
|
+
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
10809
|
+
});
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
10813
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
10814
|
+
const message = conversation.messages[index];
|
|
10815
|
+
if (message?.role !== "user") {
|
|
10816
|
+
continue;
|
|
10817
|
+
}
|
|
10818
|
+
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
10819
|
+
}
|
|
10820
|
+
return false;
|
|
10673
10821
|
}
|
|
10674
10822
|
|
|
10675
|
-
// src/chat/
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10823
|
+
// src/chat/services/plugin-auth-orchestration.ts
|
|
10824
|
+
var PluginAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
10825
|
+
constructor(provider, disposition) {
|
|
10826
|
+
super("plugin", provider, disposition);
|
|
10827
|
+
}
|
|
10828
|
+
};
|
|
10829
|
+
var PluginCredentialFailureError = class extends Error {
|
|
10830
|
+
provider;
|
|
10831
|
+
constructor(provider, message) {
|
|
10832
|
+
super(message);
|
|
10833
|
+
this.name = "PluginCredentialFailureError";
|
|
10834
|
+
this.provider = provider;
|
|
10835
|
+
}
|
|
10836
|
+
};
|
|
10837
|
+
function isCommandAuthFailure(details) {
|
|
10838
|
+
if (!details || typeof details !== "object") {
|
|
10839
|
+
return false;
|
|
10840
|
+
}
|
|
10841
|
+
const result = details;
|
|
10842
|
+
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
10843
|
+
return false;
|
|
10844
|
+
}
|
|
10845
|
+
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
10846
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
10847
|
+
if (!text.trim()) {
|
|
10848
|
+
return false;
|
|
10849
|
+
}
|
|
10850
|
+
return [
|
|
10851
|
+
/\bjunior-auth-required\b/,
|
|
10852
|
+
/\b401\b/,
|
|
10853
|
+
/\bunauthorized\b/,
|
|
10854
|
+
/\bbad credentials\b/,
|
|
10855
|
+
/\binvalid token\b/,
|
|
10856
|
+
/\bgithub_token\b.*\binvalid\b/,
|
|
10857
|
+
/\btoken (?:expired|revoked)\b/,
|
|
10858
|
+
/\bexpired token\b/,
|
|
10859
|
+
/\bmissing scopes?\b/,
|
|
10860
|
+
/\binsufficient scope\b/,
|
|
10861
|
+
/\binvalid grant\b/,
|
|
10862
|
+
/\breauthoriz/
|
|
10863
|
+
].some((pattern) => pattern.test(text));
|
|
10864
|
+
}
|
|
10865
|
+
function commandText(details) {
|
|
10866
|
+
if (!details || typeof details !== "object") {
|
|
10867
|
+
return "";
|
|
10868
|
+
}
|
|
10869
|
+
const result = details;
|
|
10870
|
+
return `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
10871
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`;
|
|
10872
|
+
}
|
|
10873
|
+
function isGitHubSmartHttpAuthFailure(provider, command, details) {
|
|
10874
|
+
if (provider !== "github" || !/^\s*(?:gh|git)\b/i.test(command)) {
|
|
10875
|
+
return false;
|
|
10876
|
+
}
|
|
10877
|
+
const text = commandText(details).toLowerCase();
|
|
10878
|
+
return /\bgzip:\s*invalid header\b/.test(text);
|
|
10879
|
+
}
|
|
10880
|
+
function explicitAuthRequiredProvider(details) {
|
|
10881
|
+
const match = /\bjunior-auth-required\s+provider=([a-z0-9-]+)\b/.exec(
|
|
10882
|
+
commandText(details).toLowerCase()
|
|
10883
|
+
);
|
|
10884
|
+
return match?.[1];
|
|
10885
|
+
}
|
|
10886
|
+
function registeredProviderNames() {
|
|
10887
|
+
const providers = /* @__PURE__ */ new Set();
|
|
10888
|
+
for (const plugin of getPluginProviders()) {
|
|
10889
|
+
const domains = [
|
|
10890
|
+
...plugin.manifest.credentials?.domains ?? [],
|
|
10891
|
+
...plugin.manifest.domains ?? []
|
|
10892
|
+
];
|
|
10893
|
+
if (domains.length > 0) {
|
|
10894
|
+
providers.add(plugin.manifest.name);
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10897
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
10898
|
+
}
|
|
10899
|
+
function commandTargetsProvider(provider, command, details) {
|
|
10900
|
+
const normalizedCommand = command.trim().toLowerCase();
|
|
10901
|
+
if (!normalizedCommand) {
|
|
10902
|
+
return false;
|
|
10903
|
+
}
|
|
10904
|
+
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
10905
|
+
return true;
|
|
10906
|
+
}
|
|
10907
|
+
const plugin = getPluginDefinition(provider);
|
|
10908
|
+
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
10909
|
+
const manifest = plugin?.manifest;
|
|
10910
|
+
const credentials = manifest?.credentials;
|
|
10911
|
+
if (credentials) {
|
|
10912
|
+
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
10913
|
+
for (const domain of credentials.domains) {
|
|
10914
|
+
candidates.add(domain.toLowerCase());
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
for (const domain of manifest?.domains ?? []) {
|
|
10918
|
+
candidates.add(domain.toLowerCase());
|
|
10919
|
+
}
|
|
10920
|
+
const combinedText = `${normalizedCommand}
|
|
10921
|
+
${commandText(details).toLowerCase()}`;
|
|
10922
|
+
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
10923
|
+
}
|
|
10924
|
+
function formatCommand(command) {
|
|
10925
|
+
const collapsed = command.replace(/\s+/g, " ").trim();
|
|
10926
|
+
return collapsed.length > 160 ? `${collapsed.slice(0, 157)}...` : collapsed;
|
|
10927
|
+
}
|
|
10928
|
+
function buildCredentialFailureError(provider, command) {
|
|
10929
|
+
const providerLabel = provider === "github" ? "GitHub" : formatProviderLabel(provider);
|
|
10930
|
+
const plugin = getPluginDefinition(provider);
|
|
10931
|
+
const credentialType = plugin?.manifest.credentials?.type;
|
|
10932
|
+
const commandSummary = formatCommand(command);
|
|
10933
|
+
const remediation = provider === "github" && credentialType === "github-app" ? "Verify the GitHub App installation covers the target repository and the host GitHub App environment variables are current." : `Verify the ${providerLabel} provider credentials before retrying.`;
|
|
10934
|
+
return new PluginCredentialFailureError(
|
|
10935
|
+
provider,
|
|
10936
|
+
`${providerLabel} credentials were rejected while running \`${commandSummary}\`. ${remediation}`
|
|
10937
|
+
);
|
|
10938
|
+
}
|
|
10939
|
+
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
10940
|
+
let pendingPause;
|
|
10941
|
+
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
10942
|
+
if (pendingPause) {
|
|
10943
|
+
throw pendingPause;
|
|
10944
|
+
}
|
|
10945
|
+
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
10946
|
+
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
10947
|
+
}
|
|
10948
|
+
const providerLabel = formatProviderLabel(provider);
|
|
10949
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
10950
|
+
pendingAuth: deps.currentPendingAuth,
|
|
10951
|
+
kind: "plugin",
|
|
10952
|
+
provider,
|
|
10953
|
+
requesterId: deps.requesterId
|
|
10954
|
+
});
|
|
10955
|
+
if (!reusingPendingLink) {
|
|
10956
|
+
const oauthResult = await startOAuthFlow(provider, {
|
|
10957
|
+
requesterId: deps.requesterId,
|
|
10958
|
+
channelId: deps.channelId,
|
|
10959
|
+
threadTs: deps.threadTs,
|
|
10960
|
+
userMessage: deps.userMessage,
|
|
10961
|
+
channelConfiguration: deps.channelConfiguration,
|
|
10962
|
+
activeSkillName: activeSkill?.name ?? void 0,
|
|
10963
|
+
resumeConversationId: deps.conversationId,
|
|
10964
|
+
resumeSessionId: deps.sessionId
|
|
10965
|
+
});
|
|
10966
|
+
if (!oauthResult.ok) {
|
|
10967
|
+
throw new Error(oauthResult.error);
|
|
10968
|
+
}
|
|
10969
|
+
if (!oauthResult.delivery) {
|
|
10970
|
+
throw new Error(
|
|
10971
|
+
`I need to connect your ${providerLabel} account first, but I wasn't able to send you a private authorization link. Please send me a direct message and try again.`
|
|
10972
|
+
);
|
|
10973
|
+
}
|
|
10974
|
+
}
|
|
10975
|
+
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
10976
|
+
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
10977
|
+
}
|
|
10978
|
+
if (deps.sessionId) {
|
|
10979
|
+
await deps.onPendingAuth?.({
|
|
10980
|
+
kind: "plugin",
|
|
10981
|
+
provider,
|
|
10982
|
+
requesterId: deps.requesterId,
|
|
10983
|
+
sessionId: deps.sessionId,
|
|
10984
|
+
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
10985
|
+
});
|
|
10986
|
+
}
|
|
10987
|
+
pendingPause = new PluginAuthorizationPauseError(
|
|
10988
|
+
provider,
|
|
10989
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
10990
|
+
);
|
|
10991
|
+
abortAgent();
|
|
10992
|
+
throw pendingPause;
|
|
10993
|
+
};
|
|
10994
|
+
return {
|
|
10995
|
+
handleCommandFailure: async (input) => {
|
|
10996
|
+
const providers = registeredProviderNames();
|
|
10997
|
+
const explicitProvider = explicitAuthRequiredProvider(input.details);
|
|
10998
|
+
const provider = explicitProvider && providers.includes(explicitProvider) ? explicitProvider : providers.find(
|
|
10999
|
+
(availableProvider) => commandTargetsProvider(
|
|
11000
|
+
availableProvider,
|
|
11001
|
+
input.command,
|
|
11002
|
+
input.details
|
|
11003
|
+
)
|
|
11004
|
+
);
|
|
11005
|
+
if (!provider) {
|
|
11006
|
+
return;
|
|
11007
|
+
}
|
|
11008
|
+
const authFailure = isCommandAuthFailure(input.details) || isGitHubSmartHttpAuthFailure(provider, input.command, input.details);
|
|
11009
|
+
if (!authFailure) {
|
|
11010
|
+
return;
|
|
11011
|
+
}
|
|
11012
|
+
if (!deps.requesterId || !deps.userTokenStore) {
|
|
11013
|
+
throw buildCredentialFailureError(provider, input.command);
|
|
11014
|
+
}
|
|
11015
|
+
if (!getPluginOAuthConfig(provider)) {
|
|
11016
|
+
throw buildCredentialFailureError(provider, input.command);
|
|
11017
|
+
}
|
|
11018
|
+
await startAuthorizationPause(provider, input.activeSkill, {
|
|
11019
|
+
unlinkExistingProvider: true
|
|
11020
|
+
});
|
|
11021
|
+
},
|
|
11022
|
+
getPendingPause: () => pendingPause
|
|
11023
|
+
};
|
|
11024
|
+
}
|
|
11025
|
+
|
|
11026
|
+
// src/chat/tools/execution/tool-error-handler.ts
|
|
11027
|
+
function getToolErrorAttributes(error) {
|
|
11028
|
+
if (!(error instanceof SlackActionError)) {
|
|
11029
|
+
return {};
|
|
11030
|
+
}
|
|
11031
|
+
return {
|
|
11032
|
+
"app.slack.error_code": error.code,
|
|
11033
|
+
...error.apiError ? { "app.slack.api_error": error.apiError } : {},
|
|
11034
|
+
...error.detail ? { "app.slack.detail": error.detail } : {},
|
|
11035
|
+
...error.detailLine !== void 0 ? { "app.slack.detail_line": error.detailLine } : {},
|
|
11036
|
+
...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
|
|
11037
|
+
};
|
|
11038
|
+
}
|
|
11039
|
+
function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
|
|
11040
|
+
const errorType = getMcpAwareErrorType(error, "tool_execution_error");
|
|
11041
|
+
const errorMessage = getMcpAwareErrorMessage(error);
|
|
11042
|
+
setSpanAttributes({
|
|
11043
|
+
"error.type": errorType,
|
|
11044
|
+
...error instanceof PluginCredentialFailureError ? { "app.credential.provider": error.provider } : {}
|
|
11045
|
+
});
|
|
11046
|
+
if (error instanceof PluginCredentialFailureError) {
|
|
11047
|
+
if (shouldTrace) {
|
|
11048
|
+
logInfo(
|
|
11049
|
+
"plugin_credential_rejected",
|
|
11050
|
+
traceContext,
|
|
11051
|
+
{
|
|
11052
|
+
"app.credential.provider": error.provider,
|
|
11053
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
11054
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11055
|
+
"gen_ai.tool.name": toolName,
|
|
11056
|
+
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
11057
|
+
"error.type": errorType
|
|
11058
|
+
},
|
|
11059
|
+
"Plugin credentials were rejected during tool execution"
|
|
11060
|
+
);
|
|
11061
|
+
}
|
|
11062
|
+
throw error;
|
|
11063
|
+
}
|
|
11064
|
+
if (shouldTrace) {
|
|
11065
|
+
logWarn(
|
|
11066
|
+
"agent_tool_call_failed",
|
|
11067
|
+
traceContext,
|
|
11068
|
+
{
|
|
11069
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
11070
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11071
|
+
"gen_ai.tool.name": toolName,
|
|
11072
|
+
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
11073
|
+
"error.type": errorType,
|
|
11074
|
+
"exception.message": errorMessage
|
|
11075
|
+
},
|
|
11076
|
+
"Agent tool call failed"
|
|
11077
|
+
);
|
|
11078
|
+
}
|
|
11079
|
+
if (!(error instanceof McpToolError)) {
|
|
11080
|
+
logException(
|
|
11081
|
+
error,
|
|
11082
|
+
"agent_tool_call_failed",
|
|
11083
|
+
{},
|
|
11084
|
+
{
|
|
11085
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
11086
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11087
|
+
"gen_ai.tool.name": toolName,
|
|
11088
|
+
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
11089
|
+
...getToolErrorAttributes(error)
|
|
11090
|
+
},
|
|
11091
|
+
"Agent tool call failed"
|
|
11092
|
+
);
|
|
11093
|
+
}
|
|
11094
|
+
throw error;
|
|
11095
|
+
}
|
|
11096
|
+
|
|
11097
|
+
// src/chat/tools/agent-tools.ts
|
|
11098
|
+
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, pluginAuthOrchestration, onToolCall) {
|
|
11099
|
+
const shouldTrace = shouldEmitDevAgentTrace();
|
|
11100
|
+
return Object.entries(tools).map(([toolName, toolDef]) => ({
|
|
11101
|
+
name: toolName,
|
|
11102
|
+
label: toolName,
|
|
11103
|
+
description: toolDef.description,
|
|
11104
|
+
parameters: toolDef.inputSchema,
|
|
11105
|
+
prepareArguments: toolDef.prepareArguments,
|
|
11106
|
+
executionMode: toolDef.executionMode,
|
|
11107
|
+
execute: async (toolCallId, params) => {
|
|
11108
|
+
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
11109
|
+
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
11110
|
+
if (toolName === "reportProgress") {
|
|
11111
|
+
const status = buildReportedProgressStatus(params);
|
|
11112
|
+
if (status) {
|
|
11113
|
+
await onStatus?.(status);
|
|
11114
|
+
}
|
|
11115
|
+
}
|
|
11116
|
+
return withSpan(
|
|
11117
|
+
`execute_tool ${toolName}`,
|
|
11118
|
+
"gen_ai.execute_tool",
|
|
11119
|
+
spanContext,
|
|
11120
|
+
async () => {
|
|
11121
|
+
const parsed = params;
|
|
11122
|
+
onToolCall?.(toolName, parsed);
|
|
11123
|
+
try {
|
|
11124
|
+
if (typeof toolDef.execute !== "function") {
|
|
10703
11125
|
const resultDetails = { ok: true };
|
|
10704
11126
|
const toolResultAttribute2 = serializeGenAiAttribute(resultDetails);
|
|
10705
11127
|
if (toolResultAttribute2) {
|
|
@@ -10997,9 +11419,31 @@ var TURN_THINKING_LEVELS = [
|
|
|
10997
11419
|
"high",
|
|
10998
11420
|
"xhigh"
|
|
10999
11421
|
];
|
|
11422
|
+
var CONFIDENCE_LABELS = {
|
|
11423
|
+
low: 0.5,
|
|
11424
|
+
medium: CLASSIFIER_CONFIDENCE_THRESHOLD,
|
|
11425
|
+
high: 0.9
|
|
11426
|
+
};
|
|
11427
|
+
function coerceClassifierConfidence(value) {
|
|
11428
|
+
if (typeof value !== "string") {
|
|
11429
|
+
return value;
|
|
11430
|
+
}
|
|
11431
|
+
const trimmed = value.trim().toLowerCase();
|
|
11432
|
+
if (!trimmed) {
|
|
11433
|
+
return value;
|
|
11434
|
+
}
|
|
11435
|
+
const numeric = Number.parseFloat(trimmed);
|
|
11436
|
+
if (Number.isFinite(numeric)) {
|
|
11437
|
+
return numeric;
|
|
11438
|
+
}
|
|
11439
|
+
return CONFIDENCE_LABELS[trimmed] ?? value;
|
|
11440
|
+
}
|
|
11000
11441
|
var turnExecutionProfileSchema = z.object({
|
|
11001
11442
|
thinking_level: z.enum(TURN_THINKING_LEVELS),
|
|
11002
|
-
confidence: z.
|
|
11443
|
+
confidence: z.preprocess(
|
|
11444
|
+
coerceClassifierConfidence,
|
|
11445
|
+
z.number().min(0).max(1)
|
|
11446
|
+
),
|
|
11003
11447
|
reason: z.string().min(1)
|
|
11004
11448
|
});
|
|
11005
11449
|
var DEFAULT_THINKING_LEVEL = "medium";
|
|
@@ -11044,7 +11488,8 @@ function buildClassifierSystemPrompt() {
|
|
|
11044
11488
|
"",
|
|
11045
11489
|
"Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and the thread-background contains a pending task, classify the pending task \u2014 not the affirmation.",
|
|
11046
11490
|
"",
|
|
11047
|
-
"Return JSON only with thinking_level, confidence, and reason."
|
|
11491
|
+
"Return JSON only with thinking_level, confidence, and reason.",
|
|
11492
|
+
"confidence must be a number from 0 to 1, not a word label."
|
|
11048
11493
|
].join("\n");
|
|
11049
11494
|
}
|
|
11050
11495
|
function buildClassifierPrompt(args) {
|
|
@@ -11179,120 +11624,11 @@ function toAgentThinkingLevel(level) {
|
|
|
11179
11624
|
return "low";
|
|
11180
11625
|
case "medium":
|
|
11181
11626
|
return "medium";
|
|
11182
|
-
case "high":
|
|
11183
|
-
return "high";
|
|
11184
|
-
case "xhigh":
|
|
11185
|
-
return "xhigh";
|
|
11186
|
-
}
|
|
11187
|
-
}
|
|
11188
|
-
|
|
11189
|
-
// src/chat/state/turn-session-store.ts
|
|
11190
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
11191
|
-
var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11192
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
11193
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
11194
|
-
}
|
|
11195
|
-
function parseAgentTurnSessionCheckpoint(value) {
|
|
11196
|
-
if (typeof value !== "string") {
|
|
11197
|
-
return void 0;
|
|
11198
|
-
}
|
|
11199
|
-
try {
|
|
11200
|
-
const parsed = JSON.parse(value);
|
|
11201
|
-
if (!isRecord(parsed)) {
|
|
11202
|
-
return void 0;
|
|
11203
|
-
}
|
|
11204
|
-
const status = parsed.state;
|
|
11205
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
11206
|
-
return void 0;
|
|
11207
|
-
}
|
|
11208
|
-
const conversationId = parsed.conversationId;
|
|
11209
|
-
const sessionId = parsed.sessionId;
|
|
11210
|
-
const sliceId = parsed.sliceId;
|
|
11211
|
-
const checkpointVersion = parsed.checkpointVersion;
|
|
11212
|
-
const updatedAtMs = parsed.updatedAtMs;
|
|
11213
|
-
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
11214
|
-
return void 0;
|
|
11215
|
-
}
|
|
11216
|
-
return {
|
|
11217
|
-
checkpointVersion,
|
|
11218
|
-
conversationId,
|
|
11219
|
-
sessionId,
|
|
11220
|
-
sliceId,
|
|
11221
|
-
state: status,
|
|
11222
|
-
updatedAtMs,
|
|
11223
|
-
piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
|
|
11224
|
-
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
11225
|
-
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
11226
|
-
(value2) => typeof value2 === "string"
|
|
11227
|
-
)
|
|
11228
|
-
} : {},
|
|
11229
|
-
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
11230
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
11231
|
-
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
11232
|
-
};
|
|
11233
|
-
} catch {
|
|
11234
|
-
return void 0;
|
|
11235
|
-
}
|
|
11236
|
-
}
|
|
11237
|
-
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
11238
|
-
const stateAdapter = getStateAdapter();
|
|
11239
|
-
await stateAdapter.connect();
|
|
11240
|
-
const value = await stateAdapter.get(
|
|
11241
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
11242
|
-
);
|
|
11243
|
-
return parseAgentTurnSessionCheckpoint(value);
|
|
11244
|
-
}
|
|
11245
|
-
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
11246
|
-
const stateAdapter = getStateAdapter();
|
|
11247
|
-
await stateAdapter.connect();
|
|
11248
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
11249
|
-
args.conversationId,
|
|
11250
|
-
args.sessionId
|
|
11251
|
-
);
|
|
11252
|
-
const checkpoint = {
|
|
11253
|
-
checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
|
|
11254
|
-
conversationId: args.conversationId,
|
|
11255
|
-
sessionId: args.sessionId,
|
|
11256
|
-
sliceId: args.sliceId,
|
|
11257
|
-
state: args.state,
|
|
11258
|
-
updatedAtMs: Date.now(),
|
|
11259
|
-
piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
|
|
11260
|
-
...Array.isArray(args.loadedSkillNames) ? {
|
|
11261
|
-
loadedSkillNames: args.loadedSkillNames.filter(
|
|
11262
|
-
(value) => typeof value === "string"
|
|
11263
|
-
)
|
|
11264
|
-
} : {},
|
|
11265
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
11266
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
11267
|
-
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
11268
|
-
};
|
|
11269
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
11270
|
-
await stateAdapter.set(
|
|
11271
|
-
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
11272
|
-
JSON.stringify(checkpoint),
|
|
11273
|
-
ttlMs
|
|
11274
|
-
);
|
|
11275
|
-
return checkpoint;
|
|
11276
|
-
}
|
|
11277
|
-
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
11278
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
11279
|
-
args.conversationId,
|
|
11280
|
-
args.sessionId
|
|
11281
|
-
);
|
|
11282
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
11283
|
-
return void 0;
|
|
11627
|
+
case "high":
|
|
11628
|
+
return "high";
|
|
11629
|
+
case "xhigh":
|
|
11630
|
+
return "xhigh";
|
|
11284
11631
|
}
|
|
11285
|
-
return await upsertAgentTurnSessionCheckpoint({
|
|
11286
|
-
conversationId: existing.conversationId,
|
|
11287
|
-
sessionId: existing.sessionId,
|
|
11288
|
-
sliceId: existing.sliceId,
|
|
11289
|
-
state: "superseded",
|
|
11290
|
-
piMessages: existing.piMessages,
|
|
11291
|
-
loadedSkillNames: existing.loadedSkillNames,
|
|
11292
|
-
resumeReason: existing.resumeReason,
|
|
11293
|
-
resumedFromSliceId: existing.resumedFromSliceId,
|
|
11294
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
11295
|
-
});
|
|
11296
11632
|
}
|
|
11297
11633
|
|
|
11298
11634
|
// src/chat/services/turn-checkpoint.ts
|
|
@@ -11399,329 +11735,103 @@ async function persistTimeoutCheckpoint(args) {
|
|
|
11399
11735
|
{
|
|
11400
11736
|
"app.ai.resume_conversation_id": args.conversationId,
|
|
11401
11737
|
"app.ai.resume_session_id": args.sessionId,
|
|
11402
|
-
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
11403
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
11404
|
-
},
|
|
11405
|
-
"Failed to persist timeout checkpoint before scheduling resume"
|
|
11406
|
-
);
|
|
11407
|
-
return void 0;
|
|
11408
|
-
}
|
|
11409
|
-
}
|
|
11410
|
-
|
|
11411
|
-
// src/chat/services/pending-auth.ts
|
|
11412
|
-
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
11413
|
-
function canReusePendingAuthLink(args) {
|
|
11414
|
-
const { pendingAuth } = args;
|
|
11415
|
-
if (!pendingAuth) {
|
|
11416
|
-
return false;
|
|
11417
|
-
}
|
|
11418
|
-
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
11419
|
-
}
|
|
11420
|
-
function getConversationPendingAuth(args) {
|
|
11421
|
-
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
11422
|
-
if (!pendingAuth) {
|
|
11423
|
-
return void 0;
|
|
11424
|
-
}
|
|
11425
|
-
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
11426
|
-
return void 0;
|
|
11427
|
-
}
|
|
11428
|
-
return pendingAuth;
|
|
11429
|
-
}
|
|
11430
|
-
function clearPendingAuth(conversation, sessionId) {
|
|
11431
|
-
if (!conversation.processing.pendingAuth) {
|
|
11432
|
-
return;
|
|
11433
|
-
}
|
|
11434
|
-
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
11435
|
-
return;
|
|
11436
|
-
}
|
|
11437
|
-
conversation.processing.pendingAuth = void 0;
|
|
11438
|
-
}
|
|
11439
|
-
async function applyPendingAuthUpdate(args) {
|
|
11440
|
-
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
11441
|
-
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
11442
|
-
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
11443
|
-
await supersedeAgentTurnSessionCheckpoint({
|
|
11444
|
-
conversationId: args.conversationId,
|
|
11445
|
-
sessionId: previousPendingAuth.sessionId,
|
|
11446
|
-
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
11447
|
-
});
|
|
11448
|
-
}
|
|
11449
|
-
}
|
|
11450
|
-
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
11451
|
-
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
11452
|
-
const message = conversation.messages[index];
|
|
11453
|
-
if (message?.role !== "user") {
|
|
11454
|
-
continue;
|
|
11455
|
-
}
|
|
11456
|
-
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
11457
|
-
}
|
|
11458
|
-
return false;
|
|
11459
|
-
}
|
|
11460
|
-
|
|
11461
|
-
// src/chat/services/mcp-auth-orchestration.ts
|
|
11462
|
-
var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
11463
|
-
constructor(provider, disposition) {
|
|
11464
|
-
super("mcp", provider, disposition);
|
|
11465
|
-
}
|
|
11466
|
-
};
|
|
11467
|
-
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
11468
|
-
let pendingPause;
|
|
11469
|
-
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
11470
|
-
const authProviderFactory = async (plugin) => {
|
|
11471
|
-
if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
|
|
11472
|
-
return void 0;
|
|
11473
|
-
}
|
|
11474
|
-
const provider = await createMcpOAuthClientProvider({
|
|
11475
|
-
provider: plugin.manifest.name,
|
|
11476
|
-
conversationId: deps.conversationId,
|
|
11477
|
-
sessionId: deps.sessionId,
|
|
11478
|
-
userId: deps.requesterId,
|
|
11479
|
-
userMessage: deps.userMessage,
|
|
11480
|
-
...deps.channelId ? { channelId: deps.channelId } : {},
|
|
11481
|
-
...deps.threadTs ? { threadTs: deps.threadTs } : {},
|
|
11482
|
-
...deps.toolChannelId ? { toolChannelId: deps.toolChannelId } : {},
|
|
11483
|
-
configuration: deps.getConfiguration(),
|
|
11484
|
-
artifactState: deps.getArtifactState()
|
|
11485
|
-
});
|
|
11486
|
-
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
11487
|
-
return provider;
|
|
11488
|
-
};
|
|
11489
|
-
const onAuthorizationRequired = async (provider) => {
|
|
11490
|
-
if (pendingPause) {
|
|
11491
|
-
return true;
|
|
11492
|
-
}
|
|
11493
|
-
const authSessionId = authSessionIdsByProvider.get(provider);
|
|
11494
|
-
if (!authSessionId || !deps.requesterId) {
|
|
11495
|
-
throw new Error(
|
|
11496
|
-
`Missing MCP auth session context for plugin "${provider}"`
|
|
11497
|
-
);
|
|
11498
|
-
}
|
|
11499
|
-
const latestArtifactState = deps.getMergedArtifactState();
|
|
11500
|
-
await patchMcpAuthSession(authSessionId, {
|
|
11501
|
-
configuration: { ...deps.getConfiguration() },
|
|
11502
|
-
artifactState: latestArtifactState,
|
|
11503
|
-
toolChannelId: deps.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? deps.channelId
|
|
11504
|
-
});
|
|
11505
|
-
const authSession = await getMcpAuthSession(authSessionId);
|
|
11506
|
-
if (!authSession?.authorizationUrl) {
|
|
11507
|
-
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
11508
|
-
}
|
|
11509
|
-
const reusingPendingLink = canReusePendingAuthLink({
|
|
11510
|
-
pendingAuth: deps.currentPendingAuth,
|
|
11511
|
-
kind: "mcp",
|
|
11512
|
-
provider,
|
|
11513
|
-
requesterId: deps.requesterId
|
|
11514
|
-
});
|
|
11515
|
-
if (!reusingPendingLink) {
|
|
11516
|
-
const delivery = await deliverPrivateMessage({
|
|
11517
|
-
channelId: authSession.channelId,
|
|
11518
|
-
threadTs: authSession.threadTs,
|
|
11519
|
-
userId: authSession.userId,
|
|
11520
|
-
text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
|
|
11521
|
-
});
|
|
11522
|
-
if (!delivery) {
|
|
11523
|
-
throw new Error(
|
|
11524
|
-
`Unable to deliver MCP authorization link for plugin "${provider}"`
|
|
11525
|
-
);
|
|
11526
|
-
}
|
|
11527
|
-
} else {
|
|
11528
|
-
await deleteMcpAuthSession(authSessionId);
|
|
11529
|
-
}
|
|
11530
|
-
if (deps.sessionId && deps.requesterId) {
|
|
11531
|
-
await deps.onPendingAuth?.({
|
|
11532
|
-
kind: "mcp",
|
|
11533
|
-
provider,
|
|
11534
|
-
requesterId: deps.requesterId,
|
|
11535
|
-
sessionId: deps.sessionId,
|
|
11536
|
-
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
11537
|
-
});
|
|
11538
|
-
}
|
|
11539
|
-
pendingPause = new McpAuthorizationPauseError(
|
|
11540
|
-
provider,
|
|
11541
|
-
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
11542
|
-
);
|
|
11543
|
-
abortAgent();
|
|
11544
|
-
return true;
|
|
11545
|
-
};
|
|
11546
|
-
return {
|
|
11547
|
-
authProviderFactory,
|
|
11548
|
-
onAuthorizationRequired,
|
|
11549
|
-
getPendingPause: () => pendingPause
|
|
11550
|
-
};
|
|
11551
|
-
}
|
|
11552
|
-
|
|
11553
|
-
// src/chat/credentials/unlink-provider.ts
|
|
11554
|
-
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
11555
|
-
await Promise.all([
|
|
11556
|
-
userTokenStore.delete(userId, provider),
|
|
11557
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
11558
|
-
deleteMcpServerSessionId(userId, provider),
|
|
11559
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
11560
|
-
]);
|
|
11561
|
-
}
|
|
11562
|
-
|
|
11563
|
-
// src/chat/services/plugin-auth-orchestration.ts
|
|
11564
|
-
var PluginAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
11565
|
-
constructor(provider, disposition) {
|
|
11566
|
-
super("plugin", provider, disposition);
|
|
11567
|
-
}
|
|
11568
|
-
};
|
|
11569
|
-
function isCommandAuthFailure(details) {
|
|
11570
|
-
if (!details || typeof details !== "object") {
|
|
11571
|
-
return false;
|
|
11572
|
-
}
|
|
11573
|
-
const result = details;
|
|
11574
|
-
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
11575
|
-
return false;
|
|
11576
|
-
}
|
|
11577
|
-
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
11578
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
11579
|
-
if (!text.trim()) {
|
|
11580
|
-
return false;
|
|
11581
|
-
}
|
|
11582
|
-
return [
|
|
11583
|
-
/\bjunior-auth-required\b/,
|
|
11584
|
-
/\b401\b/,
|
|
11585
|
-
/\bunauthorized\b/,
|
|
11586
|
-
/\bbad credentials\b/,
|
|
11587
|
-
/\binvalid token\b/,
|
|
11588
|
-
/\btoken (?:expired|revoked)\b/,
|
|
11589
|
-
/\bexpired token\b/,
|
|
11590
|
-
/\bmissing scopes?\b/,
|
|
11591
|
-
/\binsufficient scope\b/,
|
|
11592
|
-
/\binvalid grant\b/,
|
|
11593
|
-
/\breauthoriz/
|
|
11594
|
-
].some((pattern) => pattern.test(text));
|
|
11595
|
-
}
|
|
11596
|
-
function commandText(details) {
|
|
11597
|
-
if (!details || typeof details !== "object") {
|
|
11598
|
-
return "";
|
|
11599
|
-
}
|
|
11600
|
-
const result = details;
|
|
11601
|
-
return `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
11602
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`;
|
|
11603
|
-
}
|
|
11604
|
-
function explicitAuthRequiredProvider(details) {
|
|
11605
|
-
const match = /\bjunior-auth-required\s+provider=([a-z0-9-]+)\b/.exec(
|
|
11606
|
-
commandText(details).toLowerCase()
|
|
11607
|
-
);
|
|
11608
|
-
return match?.[1];
|
|
11609
|
-
}
|
|
11610
|
-
function registeredProviderNames() {
|
|
11611
|
-
const providers = /* @__PURE__ */ new Set();
|
|
11612
|
-
for (const plugin of getPluginProviders()) {
|
|
11613
|
-
const domains = [
|
|
11614
|
-
...plugin.manifest.credentials?.domains ?? [],
|
|
11615
|
-
...plugin.manifest.domains ?? []
|
|
11616
|
-
];
|
|
11617
|
-
if (domains.length > 0) {
|
|
11618
|
-
providers.add(plugin.manifest.name);
|
|
11619
|
-
}
|
|
11620
|
-
}
|
|
11621
|
-
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
11622
|
-
}
|
|
11623
|
-
function commandTargetsProvider(provider, command, details) {
|
|
11624
|
-
const normalizedCommand = command.trim().toLowerCase();
|
|
11625
|
-
if (!normalizedCommand) {
|
|
11626
|
-
return false;
|
|
11627
|
-
}
|
|
11628
|
-
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
11629
|
-
return true;
|
|
11630
|
-
}
|
|
11631
|
-
const plugin = getPluginDefinition(provider);
|
|
11632
|
-
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
11633
|
-
const manifest = plugin?.manifest;
|
|
11634
|
-
const credentials = manifest?.credentials;
|
|
11635
|
-
if (credentials) {
|
|
11636
|
-
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
11637
|
-
for (const domain of credentials.domains) {
|
|
11638
|
-
candidates.add(domain.toLowerCase());
|
|
11639
|
-
}
|
|
11640
|
-
}
|
|
11641
|
-
for (const domain of manifest?.domains ?? []) {
|
|
11642
|
-
candidates.add(domain.toLowerCase());
|
|
11738
|
+
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
11739
|
+
"app.ai.resume_next_slice_id": nextSliceId
|
|
11740
|
+
},
|
|
11741
|
+
"Failed to persist timeout checkpoint before scheduling resume"
|
|
11742
|
+
);
|
|
11743
|
+
return void 0;
|
|
11643
11744
|
}
|
|
11644
|
-
const combinedText = `${normalizedCommand}
|
|
11645
|
-
${commandText(details).toLowerCase()}`;
|
|
11646
|
-
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
11647
11745
|
}
|
|
11648
|
-
|
|
11746
|
+
|
|
11747
|
+
// src/chat/services/mcp-auth-orchestration.ts
|
|
11748
|
+
var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
11749
|
+
constructor(provider, disposition) {
|
|
11750
|
+
super("mcp", provider, disposition);
|
|
11751
|
+
}
|
|
11752
|
+
};
|
|
11753
|
+
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
11649
11754
|
let pendingPause;
|
|
11650
|
-
const
|
|
11755
|
+
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
11756
|
+
const authProviderFactory = async (plugin) => {
|
|
11757
|
+
if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
|
|
11758
|
+
return void 0;
|
|
11759
|
+
}
|
|
11760
|
+
const provider = await createMcpOAuthClientProvider({
|
|
11761
|
+
provider: plugin.manifest.name,
|
|
11762
|
+
conversationId: deps.conversationId,
|
|
11763
|
+
sessionId: deps.sessionId,
|
|
11764
|
+
userId: deps.requesterId,
|
|
11765
|
+
userMessage: deps.userMessage,
|
|
11766
|
+
...deps.channelId ? { channelId: deps.channelId } : {},
|
|
11767
|
+
...deps.threadTs ? { threadTs: deps.threadTs } : {},
|
|
11768
|
+
...deps.toolChannelId ? { toolChannelId: deps.toolChannelId } : {},
|
|
11769
|
+
configuration: deps.getConfiguration(),
|
|
11770
|
+
artifactState: deps.getArtifactState()
|
|
11771
|
+
});
|
|
11772
|
+
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
11773
|
+
return provider;
|
|
11774
|
+
};
|
|
11775
|
+
const onAuthorizationRequired = async (provider) => {
|
|
11651
11776
|
if (pendingPause) {
|
|
11652
|
-
|
|
11777
|
+
return true;
|
|
11653
11778
|
}
|
|
11654
|
-
|
|
11655
|
-
|
|
11779
|
+
const authSessionId = authSessionIdsByProvider.get(provider);
|
|
11780
|
+
if (!authSessionId || !deps.requesterId) {
|
|
11781
|
+
throw new Error(
|
|
11782
|
+
`Missing MCP auth session context for plugin "${provider}"`
|
|
11783
|
+
);
|
|
11784
|
+
}
|
|
11785
|
+
const latestArtifactState = deps.getMergedArtifactState();
|
|
11786
|
+
await patchMcpAuthSession(authSessionId, {
|
|
11787
|
+
configuration: { ...deps.getConfiguration() },
|
|
11788
|
+
artifactState: latestArtifactState,
|
|
11789
|
+
toolChannelId: deps.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? deps.channelId
|
|
11790
|
+
});
|
|
11791
|
+
const authSession = await getMcpAuthSession(authSessionId);
|
|
11792
|
+
if (!authSession?.authorizationUrl) {
|
|
11793
|
+
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
11656
11794
|
}
|
|
11657
|
-
const providerLabel = formatProviderLabel(provider);
|
|
11658
11795
|
const reusingPendingLink = canReusePendingAuthLink({
|
|
11659
11796
|
pendingAuth: deps.currentPendingAuth,
|
|
11660
|
-
kind: "
|
|
11797
|
+
kind: "mcp",
|
|
11661
11798
|
provider,
|
|
11662
11799
|
requesterId: deps.requesterId
|
|
11663
11800
|
});
|
|
11664
11801
|
if (!reusingPendingLink) {
|
|
11665
|
-
const
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
channelConfiguration: deps.channelConfiguration,
|
|
11671
|
-
activeSkillName: activeSkill?.name ?? void 0,
|
|
11672
|
-
resumeConversationId: deps.conversationId,
|
|
11673
|
-
resumeSessionId: deps.sessionId
|
|
11802
|
+
const delivery = await deliverPrivateMessage({
|
|
11803
|
+
channelId: authSession.channelId,
|
|
11804
|
+
threadTs: authSession.threadTs,
|
|
11805
|
+
userId: authSession.userId,
|
|
11806
|
+
text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
|
|
11674
11807
|
});
|
|
11675
|
-
if (!
|
|
11676
|
-
throw new Error(oauthResult.error);
|
|
11677
|
-
}
|
|
11678
|
-
if (!oauthResult.delivery) {
|
|
11808
|
+
if (!delivery) {
|
|
11679
11809
|
throw new Error(
|
|
11680
|
-
`
|
|
11810
|
+
`Unable to deliver MCP authorization link for plugin "${provider}"`
|
|
11681
11811
|
);
|
|
11682
11812
|
}
|
|
11813
|
+
} else {
|
|
11814
|
+
await deleteMcpAuthSession(authSessionId);
|
|
11683
11815
|
}
|
|
11684
|
-
if (
|
|
11685
|
-
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
11686
|
-
}
|
|
11687
|
-
if (deps.sessionId) {
|
|
11816
|
+
if (deps.sessionId && deps.requesterId) {
|
|
11688
11817
|
await deps.onPendingAuth?.({
|
|
11689
|
-
kind: "
|
|
11818
|
+
kind: "mcp",
|
|
11690
11819
|
provider,
|
|
11691
11820
|
requesterId: deps.requesterId,
|
|
11692
11821
|
sessionId: deps.sessionId,
|
|
11693
11822
|
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
11694
11823
|
});
|
|
11695
11824
|
}
|
|
11696
|
-
pendingPause = new
|
|
11825
|
+
pendingPause = new McpAuthorizationPauseError(
|
|
11697
11826
|
provider,
|
|
11698
11827
|
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
11699
11828
|
);
|
|
11700
11829
|
abortAgent();
|
|
11701
|
-
|
|
11830
|
+
return true;
|
|
11702
11831
|
};
|
|
11703
11832
|
return {
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
const authFailure = isCommandAuthFailure(input.details);
|
|
11707
|
-
if (!authFailure) {
|
|
11708
|
-
return;
|
|
11709
|
-
}
|
|
11710
|
-
const explicitProvider = explicitAuthRequiredProvider(input.details);
|
|
11711
|
-
const provider = explicitProvider && providers.includes(explicitProvider) ? explicitProvider : providers.find(
|
|
11712
|
-
(availableProvider) => commandTargetsProvider(
|
|
11713
|
-
availableProvider,
|
|
11714
|
-
input.command,
|
|
11715
|
-
input.details
|
|
11716
|
-
)
|
|
11717
|
-
);
|
|
11718
|
-
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider)) {
|
|
11719
|
-
return;
|
|
11720
|
-
}
|
|
11721
|
-
await startAuthorizationPause(provider, input.activeSkill, {
|
|
11722
|
-
unlinkExistingProvider: true
|
|
11723
|
-
});
|
|
11724
|
-
},
|
|
11833
|
+
authProviderFactory,
|
|
11834
|
+
onAuthorizationRequired,
|
|
11725
11835
|
getPendingPause: () => pendingPause
|
|
11726
11836
|
};
|
|
11727
11837
|
}
|
|
@@ -12708,6 +12818,12 @@ function finalizeFailedTurnReply(args) {
|
|
|
12708
12818
|
};
|
|
12709
12819
|
}
|
|
12710
12820
|
|
|
12821
|
+
// src/chat/services/turn-continuation-response.ts
|
|
12822
|
+
var TURN_CONTINUATION_RESPONSE = "I'm still working on this in the background. I'll post the final response here when it finishes.";
|
|
12823
|
+
function buildTurnContinuationResponse() {
|
|
12824
|
+
return TURN_CONTINUATION_RESPONSE;
|
|
12825
|
+
}
|
|
12826
|
+
|
|
12711
12827
|
// src/chat/slack/assistant-thread/status-render.ts
|
|
12712
12828
|
var DEFAULT_STATUS_CONTEXTS = {
|
|
12713
12829
|
thinking: "\u2026",
|
|
@@ -13453,6 +13569,25 @@ async function postResumeFailureReply(args) {
|
|
|
13453
13569
|
throw error;
|
|
13454
13570
|
}
|
|
13455
13571
|
}
|
|
13572
|
+
async function postTurnContinuationNoticeBestEffort(args) {
|
|
13573
|
+
try {
|
|
13574
|
+
await postSlackMessage({
|
|
13575
|
+
channelId: args.resumeArgs.channelId,
|
|
13576
|
+
threadTs: args.resumeArgs.threadTs,
|
|
13577
|
+
text: buildTurnContinuationResponse()
|
|
13578
|
+
});
|
|
13579
|
+
} catch (error) {
|
|
13580
|
+
logException(
|
|
13581
|
+
error,
|
|
13582
|
+
"slack_turn_continuation_notice_post_failed",
|
|
13583
|
+
getResumeLogContext(args.resumeArgs, args.lockKey),
|
|
13584
|
+
{
|
|
13585
|
+
"app.slack.reply_stage": "thread_reply_turn_continuation_notice"
|
|
13586
|
+
},
|
|
13587
|
+
"Failed to post turn continuation notice"
|
|
13588
|
+
);
|
|
13589
|
+
}
|
|
13590
|
+
}
|
|
13456
13591
|
async function handleResumeFailure(args) {
|
|
13457
13592
|
const logContext = getResumeLogContext(args.resumeArgs, args.lockKey);
|
|
13458
13593
|
const capturedEventId = logException(
|
|
@@ -13520,6 +13655,7 @@ async function resumeSlackTurn(args) {
|
|
|
13520
13655
|
channelId: args.channelId,
|
|
13521
13656
|
threadTs: args.threadTs
|
|
13522
13657
|
});
|
|
13658
|
+
let deferredPauseKind;
|
|
13523
13659
|
let deferredPauseHandler;
|
|
13524
13660
|
let deferredFailureHandler;
|
|
13525
13661
|
try {
|
|
@@ -13573,10 +13709,12 @@ async function resumeSlackTurn(args) {
|
|
|
13573
13709
|
const onAuthPause = args.onAuthPause;
|
|
13574
13710
|
const onTimeoutPause = args.onTimeoutPause;
|
|
13575
13711
|
if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
|
|
13712
|
+
deferredPauseKind = "auth";
|
|
13576
13713
|
deferredPauseHandler = async () => {
|
|
13577
13714
|
await onAuthPause(error);
|
|
13578
13715
|
};
|
|
13579
13716
|
} else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
|
|
13717
|
+
deferredPauseKind = "timeout";
|
|
13580
13718
|
deferredPauseHandler = async () => {
|
|
13581
13719
|
await onTimeoutPause(error);
|
|
13582
13720
|
};
|
|
@@ -13597,6 +13735,12 @@ async function resumeSlackTurn(args) {
|
|
|
13597
13735
|
if (deferredPauseHandler) {
|
|
13598
13736
|
try {
|
|
13599
13737
|
await deferredPauseHandler();
|
|
13738
|
+
if (deferredPauseKind === "timeout") {
|
|
13739
|
+
await postTurnContinuationNoticeBestEffort({
|
|
13740
|
+
lockKey,
|
|
13741
|
+
resumeArgs: args
|
|
13742
|
+
});
|
|
13743
|
+
}
|
|
13600
13744
|
return;
|
|
13601
13745
|
} catch (pauseError) {
|
|
13602
13746
|
await handleResumeFailure({
|
|
@@ -13668,6 +13812,20 @@ var MAX_TURN_TIMEOUT_RESUME_SLICE_ID = 5;
|
|
|
13668
13812
|
function canScheduleTurnTimeoutResume(nextSliceId) {
|
|
13669
13813
|
return typeof nextSliceId === "number" && nextSliceId > 1 && nextSliceId <= MAX_TURN_TIMEOUT_RESUME_SLICE_ID;
|
|
13670
13814
|
}
|
|
13815
|
+
async function getAwaitingTurnContinuationRequest(args) {
|
|
13816
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
13817
|
+
args.conversationId,
|
|
13818
|
+
args.sessionId
|
|
13819
|
+
);
|
|
13820
|
+
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "timeout" || !canScheduleTurnTimeoutResume(checkpoint.sliceId)) {
|
|
13821
|
+
return void 0;
|
|
13822
|
+
}
|
|
13823
|
+
return {
|
|
13824
|
+
conversationId: args.conversationId,
|
|
13825
|
+
sessionId: args.sessionId,
|
|
13826
|
+
expectedCheckpointVersion: checkpoint.checkpointVersion
|
|
13827
|
+
};
|
|
13828
|
+
}
|
|
13671
13829
|
function getTurnTimeoutResumeSecret() {
|
|
13672
13830
|
const explicit = process.env.JUNIOR_INTERNAL_RESUME_SECRET?.trim();
|
|
13673
13831
|
if (explicit) {
|
|
@@ -14764,6 +14922,10 @@ var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
|
|
|
14764
14922
|
FORWARDED_SCHEME_HEADER,
|
|
14765
14923
|
FORWARDED_PORT_HEADER
|
|
14766
14924
|
]);
|
|
14925
|
+
var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
14926
|
+
"content-encoding",
|
|
14927
|
+
"content-length"
|
|
14928
|
+
]);
|
|
14767
14929
|
var AUTH_REJECTION_STATUS = /* @__PURE__ */ new Set([401, 403]);
|
|
14768
14930
|
function jsonError(message, status) {
|
|
14769
14931
|
return Response.json({ error: message }, { status });
|
|
@@ -14862,7 +15024,7 @@ function responseHeaders(upstream) {
|
|
|
14862
15024
|
const headers = new Headers();
|
|
14863
15025
|
upstream.headers.forEach((value, key) => {
|
|
14864
15026
|
const normalized = key.toLowerCase();
|
|
14865
|
-
if (!HOP_BY_HOP_HEADERS.has(normalized)) {
|
|
15027
|
+
if (!HOP_BY_HOP_HEADERS.has(normalized) && !DECODED_RESPONSE_HEADERS.has(normalized)) {
|
|
14866
15028
|
headers.append(key, value);
|
|
14867
15029
|
}
|
|
14868
15030
|
});
|
|
@@ -15034,6 +15196,10 @@ function resolveSlackChannelIdFromMessage(message) {
|
|
|
15034
15196
|
}
|
|
15035
15197
|
|
|
15036
15198
|
// src/handlers/turn-resume.ts
|
|
15199
|
+
var TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
15200
|
+
function sleep3(ms) {
|
|
15201
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15202
|
+
}
|
|
15037
15203
|
async function persistCompletedReplyState2(args) {
|
|
15038
15204
|
const currentState = await getPersistedThreadState(
|
|
15039
15205
|
args.checkpoint.conversationId
|
|
@@ -15233,25 +15399,53 @@ async function resumeTimedOutTurn(payload) {
|
|
|
15233
15399
|
}
|
|
15234
15400
|
});
|
|
15235
15401
|
}
|
|
15236
|
-
async function
|
|
15237
|
-
const
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15243
|
-
|
|
15402
|
+
async function resumeTimedOutTurnWithLockRetry(payload) {
|
|
15403
|
+
for (const [attempt, delayMs] of [
|
|
15404
|
+
...TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS,
|
|
15405
|
+
void 0
|
|
15406
|
+
].entries()) {
|
|
15407
|
+
try {
|
|
15408
|
+
await resumeTimedOutTurn(payload);
|
|
15409
|
+
return;
|
|
15410
|
+
} catch (error) {
|
|
15411
|
+
if (!(error instanceof ResumeTurnBusyError)) {
|
|
15412
|
+
throw error;
|
|
15413
|
+
}
|
|
15414
|
+
if (typeof delayMs !== "number") {
|
|
15244
15415
|
logWarn(
|
|
15245
15416
|
"timeout_resume_lock_busy",
|
|
15246
15417
|
{},
|
|
15247
15418
|
{
|
|
15248
15419
|
"app.ai.conversation_id": payload.conversationId,
|
|
15249
|
-
"app.ai.session_id": payload.sessionId
|
|
15420
|
+
"app.ai.session_id": payload.sessionId,
|
|
15421
|
+
"app.ai.resume_lock_retry_count": attempt
|
|
15250
15422
|
},
|
|
15251
|
-
"Skipped timeout resume because another turn owns the thread lock"
|
|
15423
|
+
"Skipped timeout resume because another turn still owns the thread lock"
|
|
15252
15424
|
);
|
|
15253
15425
|
return;
|
|
15254
15426
|
}
|
|
15427
|
+
logWarn(
|
|
15428
|
+
"timeout_resume_lock_busy_retrying",
|
|
15429
|
+
{},
|
|
15430
|
+
{
|
|
15431
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
15432
|
+
"app.ai.session_id": payload.sessionId,
|
|
15433
|
+
"app.ai.resume_lock_retry_attempt": attempt + 1,
|
|
15434
|
+
"app.ai.resume_lock_retry_delay_ms": delayMs
|
|
15435
|
+
},
|
|
15436
|
+
"Timeout resume lock was busy; retrying"
|
|
15437
|
+
);
|
|
15438
|
+
await sleep3(delayMs);
|
|
15439
|
+
}
|
|
15440
|
+
}
|
|
15441
|
+
}
|
|
15442
|
+
async function POST(request, waitUntil) {
|
|
15443
|
+
const payload = await verifyTurnTimeoutResumeRequest(request);
|
|
15444
|
+
if (!payload) {
|
|
15445
|
+
return new Response("Unauthorized", { status: 401 });
|
|
15446
|
+
}
|
|
15447
|
+
waitUntil(
|
|
15448
|
+
() => resumeTimedOutTurnWithLockRetry(payload).catch((error) => {
|
|
15255
15449
|
logException(
|
|
15256
15450
|
error,
|
|
15257
15451
|
"timeout_resume_handler_failed",
|
|
@@ -15615,21 +15809,162 @@ function getSlackErrorObservabilityAttributes(error) {
|
|
|
15615
15809
|
if (typeof candidate.code === "string" && candidate.code.trim().length > 0) {
|
|
15616
15810
|
attributes["app.slack.error_code"] = candidate.code;
|
|
15617
15811
|
}
|
|
15618
|
-
if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
|
|
15619
|
-
attributes["app.slack.api_error"] = candidate.data.error;
|
|
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;
|
|
15620
15912
|
}
|
|
15621
|
-
const
|
|
15622
|
-
|
|
15623
|
-
|
|
15913
|
+
const channelId = getChannelId(args.thread, args.message);
|
|
15914
|
+
const messageTs = getMessageTs(args.message);
|
|
15915
|
+
if (!channelId || !messageTs) {
|
|
15916
|
+
return noProcessingReaction;
|
|
15624
15917
|
}
|
|
15625
|
-
|
|
15626
|
-
|
|
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;
|
|
15627
15937
|
}
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
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
|
+
};
|
|
15633
15968
|
}
|
|
15634
15969
|
|
|
15635
15970
|
// src/chat/runtime/slack-runtime.ts
|
|
@@ -15657,6 +15992,14 @@ function buildLogContext(deps, args) {
|
|
|
15657
15992
|
}
|
|
15658
15993
|
function createSlackTurnRuntime(deps) {
|
|
15659
15994
|
const logContext = (args) => buildLogContext(deps, args);
|
|
15995
|
+
const createToolInvocationHook = (processingReaction, hooks) => {
|
|
15996
|
+
return (invocation) => {
|
|
15997
|
+
if (shouldKeepProcessingReactionForToolInvocation(invocation)) {
|
|
15998
|
+
processingReaction.keep();
|
|
15999
|
+
}
|
|
16000
|
+
hooks?.onToolInvocation?.(invocation);
|
|
16001
|
+
};
|
|
16002
|
+
};
|
|
15660
16003
|
const postFallbackErrorReplyWithLogging = async (args) => {
|
|
15661
16004
|
try {
|
|
15662
16005
|
await args.thread.post(buildTurnFailureResponse(args.eventId));
|
|
@@ -15710,6 +16053,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15710
16053
|
};
|
|
15711
16054
|
return {
|
|
15712
16055
|
async handleNewMention(thread, message, hooks) {
|
|
16056
|
+
let processingReaction;
|
|
15713
16057
|
try {
|
|
15714
16058
|
const threadId = deps.getThreadId(thread, message);
|
|
15715
16059
|
const channelId = deps.getChannelId(thread, message);
|
|
@@ -15721,11 +16065,22 @@ function createSlackTurnRuntime(deps) {
|
|
|
15721
16065
|
requesterUserName: message.author.userName,
|
|
15722
16066
|
runId
|
|
15723
16067
|
});
|
|
16068
|
+
processingReaction = await startSlackProcessingReaction({
|
|
16069
|
+
thread,
|
|
16070
|
+
message,
|
|
16071
|
+
logException: deps.logException,
|
|
16072
|
+
logContext: context
|
|
16073
|
+
});
|
|
16074
|
+
const toolInvocationHook = createToolInvocationHook(
|
|
16075
|
+
processingReaction,
|
|
16076
|
+
hooks
|
|
16077
|
+
);
|
|
15724
16078
|
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
15725
16079
|
await thread.subscribe();
|
|
15726
16080
|
await deps.replyToThread(thread, message, {
|
|
15727
16081
|
explicitMention: true,
|
|
15728
|
-
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
16082
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
16083
|
+
onToolInvocation: toolInvocationHook
|
|
15729
16084
|
});
|
|
15730
16085
|
});
|
|
15731
16086
|
} catch (error) {
|
|
@@ -15766,113 +16121,123 @@ function createSlackTurnRuntime(deps) {
|
|
|
15766
16121
|
postFailureEventName: "mention_handler_failure_reply_post_failed",
|
|
15767
16122
|
postFailureBody: "Failed to post fallback error reply for mention handler"
|
|
15768
16123
|
});
|
|
16124
|
+
} finally {
|
|
16125
|
+
await processingReaction?.stop();
|
|
15769
16126
|
}
|
|
15770
16127
|
},
|
|
15771
16128
|
async handleSubscribedMessage(thread, message, hooks) {
|
|
16129
|
+
let processingReaction;
|
|
15772
16130
|
try {
|
|
15773
16131
|
const threadId = deps.getThreadId(thread, message);
|
|
15774
16132
|
const channelId = deps.getChannelId(thread, message);
|
|
15775
16133
|
const runId = deps.getRunId(thread, message);
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
16134
|
+
const context = logContext({
|
|
16135
|
+
threadId,
|
|
16136
|
+
requesterId: message.author.userId,
|
|
16137
|
+
requesterUserName: message.author.userName,
|
|
16138
|
+
channelId,
|
|
16139
|
+
runId
|
|
16140
|
+
});
|
|
16141
|
+
processingReaction = await startSlackProcessingReaction({
|
|
16142
|
+
thread,
|
|
16143
|
+
message,
|
|
16144
|
+
logException: deps.logException,
|
|
16145
|
+
logContext: context
|
|
16146
|
+
});
|
|
16147
|
+
const toolInvocationHook = createToolInvocationHook(
|
|
16148
|
+
processingReaction,
|
|
16149
|
+
hooks
|
|
16150
|
+
);
|
|
16151
|
+
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
16152
|
+
const legacyAttachmentText = renderSlackLegacyAttachmentText(
|
|
16153
|
+
message.raw
|
|
16154
|
+
);
|
|
16155
|
+
const rawUserText = appendSlackLegacyAttachmentText(
|
|
16156
|
+
message.text,
|
|
16157
|
+
message.raw
|
|
16158
|
+
);
|
|
16159
|
+
const strippedUserText = deps.stripLeadingBotMention(message.text, {
|
|
16160
|
+
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
16161
|
+
});
|
|
16162
|
+
const userText = appendSlackLegacyAttachmentText(
|
|
16163
|
+
strippedUserText,
|
|
16164
|
+
message.raw
|
|
16165
|
+
);
|
|
16166
|
+
const context2 = {
|
|
15780
16167
|
threadId,
|
|
15781
16168
|
requesterId: message.author.userId,
|
|
15782
|
-
requesterUserName: message.author.userName,
|
|
15783
16169
|
channelId,
|
|
15784
16170
|
runId
|
|
15785
|
-
}
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
15796
|
-
});
|
|
15797
|
-
const userText = appendSlackLegacyAttachmentText(
|
|
15798
|
-
strippedUserText,
|
|
15799
|
-
message.raw
|
|
15800
|
-
);
|
|
15801
|
-
const context = {
|
|
15802
|
-
threadId,
|
|
15803
|
-
requesterId: message.author.userId,
|
|
15804
|
-
channelId,
|
|
15805
|
-
runId
|
|
15806
|
-
};
|
|
15807
|
-
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
15808
|
-
botUserName: deps.assistantUserName,
|
|
15809
|
-
rawText: rawUserText,
|
|
15810
|
-
text: userText,
|
|
15811
|
-
isExplicitMention: Boolean(message.isMention)
|
|
15812
|
-
});
|
|
15813
|
-
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
15814
|
-
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
15815
|
-
await skipSubscribedMessage({
|
|
15816
|
-
thread,
|
|
15817
|
-
message,
|
|
15818
|
-
decision: { shouldReply: false, reason },
|
|
15819
|
-
context,
|
|
15820
|
-
userText
|
|
15821
|
-
});
|
|
15822
|
-
return;
|
|
15823
|
-
}
|
|
15824
|
-
const preparedState = await deps.prepareTurnState({
|
|
16171
|
+
};
|
|
16172
|
+
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
16173
|
+
botUserName: deps.assistantUserName,
|
|
16174
|
+
rawText: rawUserText,
|
|
16175
|
+
text: userText,
|
|
16176
|
+
isExplicitMention: Boolean(message.isMention)
|
|
16177
|
+
});
|
|
16178
|
+
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
16179
|
+
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
16180
|
+
await skipSubscribedMessage({
|
|
15825
16181
|
thread,
|
|
15826
16182
|
message,
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
16183
|
+
decision: { shouldReply: false, reason },
|
|
16184
|
+
context: context2,
|
|
16185
|
+
userText
|
|
15830
16186
|
});
|
|
15831
|
-
|
|
16187
|
+
return;
|
|
16188
|
+
}
|
|
16189
|
+
const preparedState = await deps.prepareTurnState({
|
|
16190
|
+
thread,
|
|
16191
|
+
message,
|
|
16192
|
+
userText,
|
|
16193
|
+
explicitMention: Boolean(message.isMention),
|
|
16194
|
+
context: context2
|
|
16195
|
+
});
|
|
16196
|
+
await deps.persistPreparedState({
|
|
16197
|
+
thread,
|
|
16198
|
+
preparedState
|
|
16199
|
+
});
|
|
16200
|
+
const decision = await deps.decideSubscribedReply({
|
|
16201
|
+
rawText: rawUserText,
|
|
16202
|
+
text: userText,
|
|
16203
|
+
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
16204
|
+
hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
|
|
16205
|
+
isExplicitMention: Boolean(message.isMention),
|
|
16206
|
+
context: context2
|
|
16207
|
+
});
|
|
16208
|
+
if (await maybeHandleThreadOptOutDecision({
|
|
16209
|
+
thread,
|
|
16210
|
+
decision,
|
|
16211
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
16212
|
+
})) {
|
|
16213
|
+
await skipSubscribedMessage({
|
|
15832
16214
|
thread,
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
15839
|
-
hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
|
|
15840
|
-
isExplicitMention: Boolean(message.isMention),
|
|
15841
|
-
context
|
|
16215
|
+
message,
|
|
16216
|
+
decision,
|
|
16217
|
+
context: context2,
|
|
16218
|
+
preparedState,
|
|
16219
|
+
userText
|
|
15842
16220
|
});
|
|
15843
|
-
|
|
16221
|
+
return;
|
|
16222
|
+
}
|
|
16223
|
+
if (!decision.shouldReply) {
|
|
16224
|
+
await skipSubscribedMessage({
|
|
15844
16225
|
thread,
|
|
16226
|
+
message,
|
|
15845
16227
|
decision,
|
|
15846
|
-
|
|
15847
|
-
})) {
|
|
15848
|
-
await skipSubscribedMessage({
|
|
15849
|
-
thread,
|
|
15850
|
-
message,
|
|
15851
|
-
decision,
|
|
15852
|
-
context,
|
|
15853
|
-
preparedState,
|
|
15854
|
-
userText
|
|
15855
|
-
});
|
|
15856
|
-
return;
|
|
15857
|
-
}
|
|
15858
|
-
if (!decision.shouldReply) {
|
|
15859
|
-
await skipSubscribedMessage({
|
|
15860
|
-
thread,
|
|
15861
|
-
message,
|
|
15862
|
-
decision,
|
|
15863
|
-
context,
|
|
15864
|
-
preparedState,
|
|
15865
|
-
userText
|
|
15866
|
-
});
|
|
15867
|
-
return;
|
|
15868
|
-
}
|
|
15869
|
-
await deps.replyToThread(thread, message, {
|
|
15870
|
-
explicitMention: Boolean(message.isMention),
|
|
16228
|
+
context: context2,
|
|
15871
16229
|
preparedState,
|
|
15872
|
-
|
|
16230
|
+
userText
|
|
15873
16231
|
});
|
|
16232
|
+
return;
|
|
15874
16233
|
}
|
|
15875
|
-
|
|
16234
|
+
await deps.replyToThread(thread, message, {
|
|
16235
|
+
explicitMention: Boolean(message.isMention),
|
|
16236
|
+
preparedState,
|
|
16237
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
16238
|
+
onToolInvocation: toolInvocationHook
|
|
16239
|
+
});
|
|
16240
|
+
});
|
|
15876
16241
|
} catch (error) {
|
|
15877
16242
|
const errorContext = logContext({
|
|
15878
16243
|
threadId: deps.getThreadId(thread, message),
|
|
@@ -15911,6 +16276,8 @@ function createSlackTurnRuntime(deps) {
|
|
|
15911
16276
|
postFailureEventName: "subscribed_message_handler_failure_reply_post_failed",
|
|
15912
16277
|
postFailureBody: "Failed to post fallback error reply for subscribed message handler"
|
|
15913
16278
|
});
|
|
16279
|
+
} finally {
|
|
16280
|
+
await processingReaction?.stop();
|
|
15914
16281
|
}
|
|
15915
16282
|
},
|
|
15916
16283
|
async handleAssistantThreadStarted(event) {
|
|
@@ -16608,6 +16975,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
16608
16975
|
conversationMemory,
|
|
16609
16976
|
replyExecutor: {
|
|
16610
16977
|
generateAssistantReply: overrides.replyExecutor?.generateAssistantReply ?? generateAssistantReply,
|
|
16978
|
+
getAwaitingTurnContinuationRequest: overrides.replyExecutor?.getAwaitingTurnContinuationRequest ?? getAwaitingTurnContinuationRequest,
|
|
16611
16979
|
lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
|
|
16612
16980
|
scheduleTurnTimeoutResume: overrides.replyExecutor?.scheduleTurnTimeoutResume ?? scheduleTurnTimeoutResume,
|
|
16613
16981
|
generateThreadTitle: conversationMemory.generateThreadTitle
|
|
@@ -16630,74 +16998,6 @@ function getSlackMessageTs(message) {
|
|
|
16630
16998
|
return message.id;
|
|
16631
16999
|
}
|
|
16632
17000
|
|
|
16633
|
-
// src/chat/runtime/thread-context.ts
|
|
16634
|
-
function escapeRegExp3(value) {
|
|
16635
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16636
|
-
}
|
|
16637
|
-
function stripLeadingBotMention(text, options = {}) {
|
|
16638
|
-
if (!text.trim()) return text;
|
|
16639
|
-
let next = text;
|
|
16640
|
-
if (options.stripLeadingSlackMentionToken) {
|
|
16641
|
-
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
16642
|
-
}
|
|
16643
|
-
const mentionByNameRe = new RegExp(
|
|
16644
|
-
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
16645
|
-
"i"
|
|
16646
|
-
);
|
|
16647
|
-
next = next.replace(mentionByNameRe, "").trim();
|
|
16648
|
-
const mentionByLabeledEntityRe = new RegExp(
|
|
16649
|
-
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
16650
|
-
"i"
|
|
16651
|
-
);
|
|
16652
|
-
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
16653
|
-
return next;
|
|
16654
|
-
}
|
|
16655
|
-
function getThreadId(thread, _message) {
|
|
16656
|
-
return toOptionalString(thread.id);
|
|
16657
|
-
}
|
|
16658
|
-
function getRunId(thread, message) {
|
|
16659
|
-
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
16660
|
-
}
|
|
16661
|
-
function getChannelId(thread, message) {
|
|
16662
|
-
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
16663
|
-
}
|
|
16664
|
-
function getThreadTs(threadId) {
|
|
16665
|
-
return parseSlackThreadId(threadId)?.threadTs;
|
|
16666
|
-
}
|
|
16667
|
-
function getAssistantThreadContext(message) {
|
|
16668
|
-
const raw = message.raw;
|
|
16669
|
-
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
16670
|
-
const channelId = toOptionalString(rawRecord?.channel);
|
|
16671
|
-
if (channelId) {
|
|
16672
|
-
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
16673
|
-
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
16674
|
-
if (threadTs) {
|
|
16675
|
-
return { channelId, threadTs };
|
|
16676
|
-
}
|
|
16677
|
-
}
|
|
16678
|
-
const parsedThreadId = parseSlackThreadId(
|
|
16679
|
-
toOptionalString(message.threadId)
|
|
16680
|
-
);
|
|
16681
|
-
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
16682
|
-
return void 0;
|
|
16683
|
-
}
|
|
16684
|
-
return parsedThreadId;
|
|
16685
|
-
}
|
|
16686
|
-
function getMessageTs(message) {
|
|
16687
|
-
const directTs = toOptionalString(
|
|
16688
|
-
message.ts
|
|
16689
|
-
);
|
|
16690
|
-
if (directTs) {
|
|
16691
|
-
return directTs;
|
|
16692
|
-
}
|
|
16693
|
-
const raw = message.raw;
|
|
16694
|
-
if (!raw || typeof raw !== "object") {
|
|
16695
|
-
return void 0;
|
|
16696
|
-
}
|
|
16697
|
-
const rawRecord = raw;
|
|
16698
|
-
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
16699
|
-
}
|
|
16700
|
-
|
|
16701
17001
|
// src/chat/slack/assistant-thread/title.ts
|
|
16702
17002
|
function maybeUpdateAssistantTitle(args) {
|
|
16703
17003
|
const assistantThreadContext = args.assistantThreadContext;
|
|
@@ -16814,11 +17114,6 @@ function createReplyToThread(deps) {
|
|
|
16814
17114
|
});
|
|
16815
17115
|
const slackMessageTs = getSlackMessageTs(message);
|
|
16816
17116
|
const turnId = buildDeterministicTurnId(message.id);
|
|
16817
|
-
startActiveTurn({
|
|
16818
|
-
conversation: preparedState.conversation,
|
|
16819
|
-
nextTurnId: turnId,
|
|
16820
|
-
updateConversationStats
|
|
16821
|
-
});
|
|
16822
17117
|
const turnTraceContext = {
|
|
16823
17118
|
conversationId,
|
|
16824
17119
|
slackThreadId: threadId,
|
|
@@ -16828,6 +17123,78 @@ function createReplyToThread(deps) {
|
|
|
16828
17123
|
assistantUserName: botConfig.userName,
|
|
16829
17124
|
modelId: botConfig.modelId
|
|
16830
17125
|
};
|
|
17126
|
+
let beforeFirstResponsePostCalled = false;
|
|
17127
|
+
const beforeFirstResponsePost = async () => {
|
|
17128
|
+
if (beforeFirstResponsePostCalled) {
|
|
17129
|
+
return;
|
|
17130
|
+
}
|
|
17131
|
+
beforeFirstResponsePostCalled = true;
|
|
17132
|
+
await options.beforeFirstResponsePost?.();
|
|
17133
|
+
};
|
|
17134
|
+
const postTurnContinuationNotice = async () => {
|
|
17135
|
+
try {
|
|
17136
|
+
await beforeFirstResponsePost();
|
|
17137
|
+
await thread.post(
|
|
17138
|
+
buildSlackOutputMessage(buildTurnContinuationResponse())
|
|
17139
|
+
);
|
|
17140
|
+
} catch (error) {
|
|
17141
|
+
logException(
|
|
17142
|
+
error,
|
|
17143
|
+
"slack_turn_continuation_notice_post_failed",
|
|
17144
|
+
turnTraceContext,
|
|
17145
|
+
{
|
|
17146
|
+
"app.slack.reply_stage": "thread_reply_turn_continuation_notice",
|
|
17147
|
+
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
17148
|
+
...getSlackErrorObservabilityAttributes(error)
|
|
17149
|
+
},
|
|
17150
|
+
"Failed to post turn continuation notice"
|
|
17151
|
+
);
|
|
17152
|
+
throw error;
|
|
17153
|
+
}
|
|
17154
|
+
};
|
|
17155
|
+
const activeTurnId = preparedState.conversation.processing.activeTurnId;
|
|
17156
|
+
if (conversationId && activeTurnId) {
|
|
17157
|
+
const resumeRequest = await deps.services.getAwaitingTurnContinuationRequest({
|
|
17158
|
+
conversationId,
|
|
17159
|
+
sessionId: activeTurnId
|
|
17160
|
+
});
|
|
17161
|
+
if (resumeRequest) {
|
|
17162
|
+
try {
|
|
17163
|
+
await deps.services.scheduleTurnTimeoutResume(resumeRequest);
|
|
17164
|
+
} catch (error) {
|
|
17165
|
+
logException(
|
|
17166
|
+
error,
|
|
17167
|
+
"agent_turn_continuation_retry_schedule_failed",
|
|
17168
|
+
turnTraceContext,
|
|
17169
|
+
{
|
|
17170
|
+
"app.ai.resume_checkpoint_version": resumeRequest.expectedCheckpointVersion,
|
|
17171
|
+
"app.ai.resume_session_id": resumeRequest.sessionId,
|
|
17172
|
+
...messageTs ? { "messaging.message.id": messageTs } : {}
|
|
17173
|
+
},
|
|
17174
|
+
"Failed to reschedule active turn continuation"
|
|
17175
|
+
);
|
|
17176
|
+
throw error;
|
|
17177
|
+
}
|
|
17178
|
+
await postTurnContinuationNotice();
|
|
17179
|
+
markConversationMessage(
|
|
17180
|
+
preparedState.conversation,
|
|
17181
|
+
preparedState.userMessageId,
|
|
17182
|
+
{
|
|
17183
|
+
replied: true,
|
|
17184
|
+
skippedReason: void 0
|
|
17185
|
+
}
|
|
17186
|
+
);
|
|
17187
|
+
await persistThreadState(thread, {
|
|
17188
|
+
conversation: preparedState.conversation
|
|
17189
|
+
});
|
|
17190
|
+
return;
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
startActiveTurn({
|
|
17194
|
+
conversation: preparedState.conversation,
|
|
17195
|
+
nextTurnId: turnId,
|
|
17196
|
+
updateConversationStats
|
|
17197
|
+
});
|
|
16831
17198
|
setTags({
|
|
16832
17199
|
conversationId
|
|
16833
17200
|
});
|
|
@@ -16869,14 +17236,6 @@ function createReplyToThread(deps) {
|
|
|
16869
17236
|
threadTs: assistantThreadContext?.threadTs,
|
|
16870
17237
|
getSlackAdapter: deps.getSlackAdapter
|
|
16871
17238
|
});
|
|
16872
|
-
let beforeFirstResponsePostCalled = false;
|
|
16873
|
-
const beforeFirstResponsePost = async () => {
|
|
16874
|
-
if (beforeFirstResponsePostCalled) {
|
|
16875
|
-
return;
|
|
16876
|
-
}
|
|
16877
|
-
beforeFirstResponsePostCalled = true;
|
|
16878
|
-
await options.beforeFirstResponsePost?.();
|
|
16879
|
-
};
|
|
16880
17239
|
const postThreadReply = async (payload, stage) => {
|
|
16881
17240
|
await beforeFirstResponsePost();
|
|
16882
17241
|
try {
|
|
@@ -16963,7 +17322,8 @@ function createReplyToThread(deps) {
|
|
|
16963
17322
|
conversation: preparedState.conversation
|
|
16964
17323
|
});
|
|
16965
17324
|
},
|
|
16966
|
-
onStatus: (nextStatus) => status.update(nextStatus)
|
|
17325
|
+
onStatus: (nextStatus) => status.update(nextStatus),
|
|
17326
|
+
onToolInvocation: options.onToolInvocation
|
|
16967
17327
|
});
|
|
16968
17328
|
const diagnosticsContext = {
|
|
16969
17329
|
slackThreadId: threadId,
|
|
@@ -17130,7 +17490,6 @@ function createReplyToThread(deps) {
|
|
|
17130
17490
|
expectedCheckpointVersion: checkpointVersion
|
|
17131
17491
|
});
|
|
17132
17492
|
shouldPersistFailureState = false;
|
|
17133
|
-
return;
|
|
17134
17493
|
} catch (scheduleError) {
|
|
17135
17494
|
logException(
|
|
17136
17495
|
scheduleError,
|
|
@@ -17142,7 +17501,11 @@ function createReplyToThread(deps) {
|
|
|
17142
17501
|
},
|
|
17143
17502
|
"Failed to schedule timeout resume callback"
|
|
17144
17503
|
);
|
|
17504
|
+
shouldPersistFailureState = true;
|
|
17505
|
+
throw scheduleError;
|
|
17145
17506
|
}
|
|
17507
|
+
await postTurnContinuationNotice();
|
|
17508
|
+
return;
|
|
17146
17509
|
} else if (conversationIdForResume && sessionIdForResume && typeof checkpointVersion === "number") {
|
|
17147
17510
|
logWarn(
|
|
17148
17511
|
"agent_turn_timeout_resume_slice_limit_reached",
|
|
@@ -17599,75 +17962,53 @@ function enqueueBackgroundTask(options, task) {
|
|
|
17599
17962
|
throw new Error("Chat background processing requires waitUntil");
|
|
17600
17963
|
}
|
|
17601
17964
|
options.waitUntil(task);
|
|
17965
|
+
return task;
|
|
17602
17966
|
}
|
|
17603
17967
|
var JuniorChat = class extends Chat {
|
|
17604
17968
|
/**
|
|
17605
17969
|
* Normalize Slack thread IDs before the SDK's concurrency queue.
|
|
17606
17970
|
*
|
|
17607
|
-
*
|
|
17608
|
-
*
|
|
17609
|
-
*
|
|
17610
|
-
*
|
|
17611
|
-
* `event.thread_ts || ""` instead of falling back to `event.ts`.
|
|
17612
|
-
* See @chat-adapter/slack/dist/index.js:1466.
|
|
17613
|
-
*
|
|
17614
|
-
* A DM root event arrives as `slack:D123:` while a reply in the same
|
|
17615
|
-
* thread carries `slack:D123:<ts>`, splitting the lock/state/subscription
|
|
17616
|
-
* keys and breaking conversation continuity.
|
|
17617
|
-
*
|
|
17618
|
-
* We fix this by resolving the message eagerly (even when the adapter
|
|
17619
|
-
* provides a factory), deriving the canonical thread ID from
|
|
17620
|
-
* `raw.channel` + `raw.thread_ts ?? raw.ts`, and passing both the
|
|
17621
|
-
* normalized threadId and concrete message to super.processMessage.
|
|
17622
|
-
*
|
|
17623
|
-
* Remove this override when @chat-adapter/slack uses `event.ts` as
|
|
17624
|
-
* the DM thread_ts fallback.
|
|
17971
|
+
* Slack DM roots can arrive with an empty thread timestamp, while
|
|
17972
|
+
* later replies include the root timestamp. Resolve factories before
|
|
17973
|
+
* delegating so the lock/state/subscription key is canonicalized before
|
|
17974
|
+
* the SDK computes its per-thread queue key.
|
|
17625
17975
|
*/
|
|
17626
17976
|
processMessage(adapter, threadId, messageOrFactory, options) {
|
|
17627
17977
|
if (typeof messageOrFactory === "function") {
|
|
17628
17978
|
const runtime = this;
|
|
17629
|
-
enqueueBackgroundTask(
|
|
17979
|
+
return enqueueBackgroundTask(
|
|
17630
17980
|
options,
|
|
17631
17981
|
(async () => {
|
|
17982
|
+
let message2;
|
|
17632
17983
|
try {
|
|
17633
|
-
|
|
17634
|
-
if (isExternalSlackUser(message.raw)) {
|
|
17635
|
-
return;
|
|
17636
|
-
}
|
|
17637
|
-
const normalized = normalizeIncomingSlackThreadId(
|
|
17638
|
-
threadId,
|
|
17639
|
-
message
|
|
17640
|
-
);
|
|
17641
|
-
if (normalized !== threadId && "threadId" in message) {
|
|
17642
|
-
message.threadId = normalized;
|
|
17643
|
-
}
|
|
17644
|
-
super.processMessage(adapter, normalized, message, options);
|
|
17984
|
+
message2 = await messageOrFactory();
|
|
17645
17985
|
} catch (error) {
|
|
17646
17986
|
runtime.logger?.error?.("Message factory resolution error", {
|
|
17647
17987
|
error,
|
|
17648
17988
|
threadId
|
|
17649
17989
|
});
|
|
17990
|
+
return;
|
|
17991
|
+
}
|
|
17992
|
+
if (isExternalSlackUser(message2.raw)) {
|
|
17993
|
+
return;
|
|
17650
17994
|
}
|
|
17995
|
+
const normalized2 = normalizeIncomingSlackThreadId(threadId, message2);
|
|
17996
|
+
if (normalized2 !== threadId && "threadId" in message2) {
|
|
17997
|
+
message2.threadId = normalized2;
|
|
17998
|
+
}
|
|
17999
|
+
await super.processMessage(adapter, normalized2, message2, options);
|
|
17651
18000
|
})()
|
|
17652
18001
|
);
|
|
17653
|
-
return;
|
|
17654
18002
|
}
|
|
17655
|
-
|
|
17656
|
-
|
|
18003
|
+
const message = messageOrFactory;
|
|
18004
|
+
if (isExternalSlackUser(message.raw)) {
|
|
18005
|
+
return Promise.resolve();
|
|
17657
18006
|
}
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
messageOrFactory
|
|
17664
|
-
);
|
|
17665
|
-
if (normalized !== threadId && "threadId" in messageOrFactory) {
|
|
17666
|
-
messageOrFactory.threadId = normalized;
|
|
17667
|
-
}
|
|
17668
|
-
super.processMessage(adapter, normalized, messageOrFactory, options);
|
|
17669
|
-
})()
|
|
17670
|
-
);
|
|
18007
|
+
const normalized = normalizeIncomingSlackThreadId(threadId, message);
|
|
18008
|
+
if (normalized !== threadId && "threadId" in message) {
|
|
18009
|
+
message.threadId = normalized;
|
|
18010
|
+
}
|
|
18011
|
+
return super.processMessage(adapter, normalized, message, options);
|
|
17671
18012
|
}
|
|
17672
18013
|
processReaction(event, options) {
|
|
17673
18014
|
const runtime = this;
|