@sentry/junior 0.29.0 → 0.30.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 +684 -234
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -392,6 +392,26 @@ function defaultConversationState() {
|
|
|
392
392
|
}
|
|
393
393
|
};
|
|
394
394
|
}
|
|
395
|
+
function coercePendingAuthState(value) {
|
|
396
|
+
if (!isRecord(value)) {
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
const kind = value.kind;
|
|
400
|
+
const provider = toOptionalString(value.provider);
|
|
401
|
+
const requesterId = toOptionalString(value.requesterId);
|
|
402
|
+
const sessionId = toOptionalString(value.sessionId);
|
|
403
|
+
const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
|
|
404
|
+
if (kind !== "mcp" && kind !== "plugin" || !provider || !requesterId || !sessionId || typeof linkSentAtMs !== "number") {
|
|
405
|
+
return void 0;
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
kind,
|
|
409
|
+
provider,
|
|
410
|
+
requesterId,
|
|
411
|
+
sessionId,
|
|
412
|
+
linkSentAtMs
|
|
413
|
+
};
|
|
414
|
+
}
|
|
395
415
|
function coerceThreadConversationState(value) {
|
|
396
416
|
if (!isRecord(value)) {
|
|
397
417
|
return defaultConversationState();
|
|
@@ -442,7 +462,8 @@ function coerceThreadConversationState(value) {
|
|
|
442
462
|
const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
|
|
443
463
|
const processing = {
|
|
444
464
|
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
445
|
-
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs)
|
|
465
|
+
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
466
|
+
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
446
467
|
};
|
|
447
468
|
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
448
469
|
const stats = {
|
|
@@ -2001,11 +2022,13 @@ function buildThreadParticipants(messages) {
|
|
|
2001
2022
|
return participants;
|
|
2002
2023
|
}
|
|
2003
2024
|
|
|
2004
|
-
// src/chat/
|
|
2025
|
+
// src/chat/state/turn-id.ts
|
|
2005
2026
|
function buildDeterministicTurnId(messageId) {
|
|
2006
2027
|
const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2007
2028
|
return `turn_${sanitized}`;
|
|
2008
2029
|
}
|
|
2030
|
+
|
|
2031
|
+
// src/chat/runtime/turn.ts
|
|
2009
2032
|
var RetryableTurnError = class extends Error {
|
|
2010
2033
|
code = "retryable_turn";
|
|
2011
2034
|
metadata;
|
|
@@ -2031,12 +2054,16 @@ function startActiveTurn(args) {
|
|
|
2031
2054
|
args.updateConversationStats(args.conversation);
|
|
2032
2055
|
}
|
|
2033
2056
|
function markTurnCompleted(args) {
|
|
2034
|
-
args.conversation.processing.activeTurnId
|
|
2057
|
+
if (!args.sessionId || args.conversation.processing.activeTurnId === args.sessionId) {
|
|
2058
|
+
args.conversation.processing.activeTurnId = void 0;
|
|
2059
|
+
}
|
|
2035
2060
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2036
2061
|
args.updateConversationStats(args.conversation);
|
|
2037
2062
|
}
|
|
2038
2063
|
function markTurnFailed(args) {
|
|
2039
|
-
args.conversation.processing.activeTurnId
|
|
2064
|
+
if (!args.sessionId || args.conversation.processing.activeTurnId === args.sessionId) {
|
|
2065
|
+
args.conversation.processing.activeTurnId = void 0;
|
|
2066
|
+
}
|
|
2040
2067
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2041
2068
|
args.markConversationMessage(args.conversation, args.userMessageId, {
|
|
2042
2069
|
replied: false,
|
|
@@ -8221,134 +8248,21 @@ function shouldEmitDevAgentTrace() {
|
|
|
8221
8248
|
return process.env.NODE_ENV === "development";
|
|
8222
8249
|
}
|
|
8223
8250
|
|
|
8224
|
-
// src/chat/
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
8229
|
-
deleteMcpServerSessionId(userId, provider),
|
|
8230
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
8231
|
-
]);
|
|
8232
|
-
}
|
|
8233
|
-
|
|
8234
|
-
// src/chat/services/plugin-auth-orchestration.ts
|
|
8235
|
-
var PluginAuthorizationPauseError = class extends Error {
|
|
8251
|
+
// src/chat/services/auth-pause.ts
|
|
8252
|
+
var AuthorizationPauseError = class extends Error {
|
|
8253
|
+
disposition;
|
|
8254
|
+
kind;
|
|
8236
8255
|
provider;
|
|
8237
|
-
constructor(provider) {
|
|
8238
|
-
super(
|
|
8239
|
-
|
|
8256
|
+
constructor(kind, provider, disposition) {
|
|
8257
|
+
super(
|
|
8258
|
+
kind === "mcp" ? `MCP authorization started for ${provider}` : `Plugin authorization started for ${provider}`
|
|
8259
|
+
);
|
|
8260
|
+
this.name = kind === "mcp" ? "McpAuthorizationPauseError" : "PluginAuthorizationPauseError";
|
|
8261
|
+
this.disposition = disposition;
|
|
8262
|
+
this.kind = kind;
|
|
8240
8263
|
this.provider = provider;
|
|
8241
8264
|
}
|
|
8242
8265
|
};
|
|
8243
|
-
function isCommandAuthFailure(details) {
|
|
8244
|
-
if (!details || typeof details !== "object") {
|
|
8245
|
-
return false;
|
|
8246
|
-
}
|
|
8247
|
-
const result = details;
|
|
8248
|
-
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
8249
|
-
return false;
|
|
8250
|
-
}
|
|
8251
|
-
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
8252
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
8253
|
-
if (!text.trim()) {
|
|
8254
|
-
return false;
|
|
8255
|
-
}
|
|
8256
|
-
return [
|
|
8257
|
-
/\b401\b/,
|
|
8258
|
-
/\bunauthorized\b/,
|
|
8259
|
-
/\bbad credentials\b/,
|
|
8260
|
-
/\binvalid token\b/,
|
|
8261
|
-
/\btoken (?:expired|revoked)\b/,
|
|
8262
|
-
/\bexpired token\b/,
|
|
8263
|
-
/\bmissing scopes?\b/,
|
|
8264
|
-
/\binsufficient scope\b/,
|
|
8265
|
-
/\binvalid grant\b/,
|
|
8266
|
-
/\breauthoriz/
|
|
8267
|
-
].some((pattern) => pattern.test(text));
|
|
8268
|
-
}
|
|
8269
|
-
function commandTargetsProvider(provider, command, details) {
|
|
8270
|
-
const normalizedCommand = command.trim().toLowerCase();
|
|
8271
|
-
if (!normalizedCommand) {
|
|
8272
|
-
return false;
|
|
8273
|
-
}
|
|
8274
|
-
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
8275
|
-
return true;
|
|
8276
|
-
}
|
|
8277
|
-
const plugin = getPluginDefinition(provider);
|
|
8278
|
-
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
8279
|
-
const credentials = plugin?.manifest.credentials;
|
|
8280
|
-
if (credentials) {
|
|
8281
|
-
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
8282
|
-
for (const domain of credentials.apiDomains) {
|
|
8283
|
-
candidates.add(domain.toLowerCase());
|
|
8284
|
-
}
|
|
8285
|
-
}
|
|
8286
|
-
const combinedText = `${normalizedCommand}
|
|
8287
|
-
${details.stdout?.toLowerCase() ?? ""}
|
|
8288
|
-
${details.stderr?.toLowerCase() ?? ""}`;
|
|
8289
|
-
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
8290
|
-
}
|
|
8291
|
-
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
8292
|
-
let pendingPause;
|
|
8293
|
-
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
8294
|
-
if (pendingPause) {
|
|
8295
|
-
throw pendingPause;
|
|
8296
|
-
}
|
|
8297
|
-
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
8298
|
-
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
8299
|
-
}
|
|
8300
|
-
const providerLabel = formatProviderLabel(provider);
|
|
8301
|
-
const oauthResult = await startOAuthFlow(provider, {
|
|
8302
|
-
requesterId: deps.requesterId,
|
|
8303
|
-
channelId: deps.channelId,
|
|
8304
|
-
threadTs: deps.threadTs,
|
|
8305
|
-
userMessage: deps.userMessage,
|
|
8306
|
-
channelConfiguration: deps.channelConfiguration,
|
|
8307
|
-
activeSkillName: activeSkill?.name ?? void 0,
|
|
8308
|
-
resumeConversationId: deps.conversationId,
|
|
8309
|
-
resumeSessionId: deps.sessionId
|
|
8310
|
-
});
|
|
8311
|
-
if (!oauthResult.ok) {
|
|
8312
|
-
throw new Error(oauthResult.error);
|
|
8313
|
-
}
|
|
8314
|
-
if (!oauthResult.delivery) {
|
|
8315
|
-
throw new Error(
|
|
8316
|
-
`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.`
|
|
8317
|
-
);
|
|
8318
|
-
}
|
|
8319
|
-
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
8320
|
-
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
8321
|
-
}
|
|
8322
|
-
pendingPause = new PluginAuthorizationPauseError(provider);
|
|
8323
|
-
abortAgent();
|
|
8324
|
-
throw pendingPause;
|
|
8325
|
-
};
|
|
8326
|
-
const handleCredentialUnavailable = async (input) => {
|
|
8327
|
-
if (pendingPause) {
|
|
8328
|
-
throw pendingPause;
|
|
8329
|
-
}
|
|
8330
|
-
if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
|
|
8331
|
-
throw input.error;
|
|
8332
|
-
}
|
|
8333
|
-
return await startAuthorizationPause(
|
|
8334
|
-
input.error.provider,
|
|
8335
|
-
input.activeSkill
|
|
8336
|
-
);
|
|
8337
|
-
};
|
|
8338
|
-
return {
|
|
8339
|
-
handleCredentialUnavailable,
|
|
8340
|
-
handleCommandFailure: async (input) => {
|
|
8341
|
-
const provider = input.activeSkill?.pluginProvider;
|
|
8342
|
-
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
|
|
8343
|
-
return;
|
|
8344
|
-
}
|
|
8345
|
-
await startAuthorizationPause(provider, input.activeSkill, {
|
|
8346
|
-
unlinkExistingProvider: true
|
|
8347
|
-
});
|
|
8348
|
-
},
|
|
8349
|
-
getPendingPause: () => pendingPause
|
|
8350
|
-
};
|
|
8351
|
-
}
|
|
8352
8266
|
|
|
8353
8267
|
// src/chat/runtime/report-progress.ts
|
|
8354
8268
|
function buildReportedProgressStatus(input) {
|
|
@@ -8396,15 +8310,16 @@ function resolveCredentialInjection(toolName, command, capabilityRuntime, sandbo
|
|
|
8396
8310
|
const headerDomains = (headerTransforms ?? []).map(
|
|
8397
8311
|
(transform) => transform.domain
|
|
8398
8312
|
);
|
|
8313
|
+
const skillName = sandbox.getActiveSkill()?.name;
|
|
8399
8314
|
logInfo(
|
|
8400
8315
|
"credential_inject_start",
|
|
8401
8316
|
{},
|
|
8402
8317
|
{
|
|
8403
|
-
"app.skill.name":
|
|
8318
|
+
"app.skill.name": skillName,
|
|
8404
8319
|
"app.credential.delivery": "header_transform",
|
|
8405
8320
|
"app.credential.header_domains": headerDomains
|
|
8406
8321
|
},
|
|
8407
|
-
|
|
8322
|
+
`Injecting scoped credential headers for sandbox command (${skillName ?? "unknown skill"} \u2192 ${headerDomains.join(", ")})`
|
|
8408
8323
|
);
|
|
8409
8324
|
}
|
|
8410
8325
|
return { headerTransforms, env };
|
|
@@ -8577,7 +8492,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8577
8492
|
}
|
|
8578
8493
|
return normalized;
|
|
8579
8494
|
} catch (error) {
|
|
8580
|
-
if (error instanceof
|
|
8495
|
+
if (error instanceof AuthorizationPauseError) {
|
|
8581
8496
|
throw error;
|
|
8582
8497
|
}
|
|
8583
8498
|
handleToolExecutionError(
|
|
@@ -8788,14 +8703,6 @@ function getTerminalAssistantMessages(messages) {
|
|
|
8788
8703
|
}
|
|
8789
8704
|
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
8790
8705
|
}
|
|
8791
|
-
function hasCompletedAssistantTurn(messages) {
|
|
8792
|
-
const message = getTerminalAssistantMessages(messages).at(-1);
|
|
8793
|
-
if (!message) {
|
|
8794
|
-
return false;
|
|
8795
|
-
}
|
|
8796
|
-
const stopReason = message.stopReason;
|
|
8797
|
-
return typeof stopReason === "string" && stopReason !== "error" && extractAssistantText(message).trim().length > 0;
|
|
8798
|
-
}
|
|
8799
8706
|
function upsertActiveSkill(activeSkills, next) {
|
|
8800
8707
|
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
8801
8708
|
if (existing) {
|
|
@@ -9023,7 +8930,10 @@ function buildTurnResult(input) {
|
|
|
9023
8930
|
// src/chat/services/turn-thinking-level.ts
|
|
9024
8931
|
import { z } from "zod";
|
|
9025
8932
|
var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
|
|
9026
|
-
var MAX_ROUTER_CONTEXT_CHARS =
|
|
8933
|
+
var MAX_ROUTER_CONTEXT_CHARS = 8e3;
|
|
8934
|
+
var ROUTER_CONTEXT_HEAD_CHARS = 3e3;
|
|
8935
|
+
var ROUTER_CONTEXT_TAIL_CHARS = 5e3;
|
|
8936
|
+
var TRUNCATION_MARKER = "\n\u2026[truncated]\u2026\n";
|
|
9027
8937
|
var TURN_THINKING_LEVELS = ["none", "low", "medium", "high"];
|
|
9028
8938
|
var turnExecutionProfileSchema = z.object({
|
|
9029
8939
|
thinking_level: z.enum(TURN_THINKING_LEVELS),
|
|
@@ -9034,9 +8944,22 @@ var DEFAULT_THINKING_LEVEL = "low";
|
|
|
9034
8944
|
function trimContextForRouter(text) {
|
|
9035
8945
|
const trimmed = text?.trim();
|
|
9036
8946
|
if (!trimmed) {
|
|
9037
|
-
return
|
|
8947
|
+
return null;
|
|
8948
|
+
}
|
|
8949
|
+
if (trimmed.length <= MAX_ROUTER_CONTEXT_CHARS) {
|
|
8950
|
+
return {
|
|
8951
|
+
text: trimmed,
|
|
8952
|
+
truncated: false,
|
|
8953
|
+
originalCharCount: trimmed.length
|
|
8954
|
+
};
|
|
9038
8955
|
}
|
|
9039
|
-
|
|
8956
|
+
const head = trimmed.slice(0, ROUTER_CONTEXT_HEAD_CHARS).trimEnd();
|
|
8957
|
+
const tail = trimmed.slice(-ROUTER_CONTEXT_TAIL_CHARS).trimStart();
|
|
8958
|
+
return {
|
|
8959
|
+
text: `${head}${TRUNCATION_MARKER}${tail}`,
|
|
8960
|
+
truncated: true,
|
|
8961
|
+
originalCharCount: trimmed.length
|
|
8962
|
+
};
|
|
9040
8963
|
}
|
|
9041
8964
|
function buildClassifierSystemPrompt() {
|
|
9042
8965
|
return [
|
|
@@ -9048,22 +8971,23 @@ function buildClassifierSystemPrompt() {
|
|
|
9048
8971
|
"Use medium for investigations, ambiguous asks, multi-step analysis, or likely multi-tool work.",
|
|
9049
8972
|
"Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
|
|
9050
8973
|
"",
|
|
8974
|
+
"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.",
|
|
8975
|
+
"",
|
|
9051
8976
|
"Return JSON only with thinking_level, confidence, and reason."
|
|
9052
8977
|
].join("\n");
|
|
9053
8978
|
}
|
|
9054
8979
|
function buildClassifierPrompt(args) {
|
|
9055
8980
|
const sections = [];
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
8981
|
+
if (args.conversationContext) {
|
|
8982
|
+
sections.push(
|
|
8983
|
+
"<thread-background>",
|
|
8984
|
+
args.conversationContext.text,
|
|
8985
|
+
"</thread-background>",
|
|
8986
|
+
""
|
|
8987
|
+
);
|
|
9059
8988
|
}
|
|
9060
8989
|
sections.push(
|
|
9061
|
-
"<
|
|
9062
|
-
`- active_skills: ${args.activeSkillNames.join(", ") || "none"}`,
|
|
9063
|
-
`- attachment_count: ${args.attachmentCount}`,
|
|
9064
|
-
"</turn-context>",
|
|
9065
|
-
"",
|
|
9066
|
-
'<current-instruction priority="highest">',
|
|
8990
|
+
"<current-instruction>",
|
|
9067
8991
|
args.messageText.trim() || "[empty]",
|
|
9068
8992
|
"</current-instruction>"
|
|
9069
8993
|
);
|
|
@@ -9077,42 +9001,81 @@ function buildClassifierPrompt(args) {
|
|
|
9077
9001
|
return sections.join("\n");
|
|
9078
9002
|
}
|
|
9079
9003
|
async function selectTurnThinkingLevel(args) {
|
|
9080
|
-
const
|
|
9004
|
+
const trimmedContext = trimContextForRouter(args.conversationContext);
|
|
9005
|
+
const instructionLength = args.messageText.trim().length;
|
|
9006
|
+
const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
|
|
9007
|
+
(block) => block.trim().length > 0
|
|
9008
|
+
).length;
|
|
9009
|
+
const prompt = buildClassifierPrompt({
|
|
9010
|
+
conversationContext: trimmedContext,
|
|
9011
|
+
currentTurnBlocks: args.currentTurnBlocks,
|
|
9012
|
+
messageText: args.messageText
|
|
9013
|
+
});
|
|
9014
|
+
const logContext = {
|
|
9015
|
+
slackThreadId: args.context?.threadId,
|
|
9016
|
+
slackChannelId: args.context?.channelId,
|
|
9017
|
+
slackUserId: args.context?.requesterId,
|
|
9018
|
+
runId: args.context?.runId,
|
|
9019
|
+
modelId: args.fastModelId
|
|
9020
|
+
};
|
|
9021
|
+
return withSpan(
|
|
9022
|
+
"chat.route_thinking",
|
|
9023
|
+
"chat.route_thinking",
|
|
9024
|
+
logContext,
|
|
9025
|
+
async () => {
|
|
9026
|
+
setSpanAttributes({
|
|
9027
|
+
"app.ai.router.prompt_char_count": prompt.length,
|
|
9028
|
+
"app.ai.router.instruction_char_count": instructionLength,
|
|
9029
|
+
"app.ai.router.context_char_count": trimmedContext?.originalCharCount ?? 0,
|
|
9030
|
+
"app.ai.router.context_trimmed": trimmedContext?.truncated ?? false,
|
|
9031
|
+
"app.ai.router.turn_block_count": turnBlockCount
|
|
9032
|
+
});
|
|
9033
|
+
const selection = await classifyTurn({
|
|
9034
|
+
completeObject: args.completeObject,
|
|
9035
|
+
fastModelId: args.fastModelId,
|
|
9036
|
+
metadata: {
|
|
9037
|
+
modelId: args.fastModelId,
|
|
9038
|
+
threadId: args.context?.threadId ?? "",
|
|
9039
|
+
channelId: args.context?.channelId ?? "",
|
|
9040
|
+
requesterId: args.context?.requesterId ?? "",
|
|
9041
|
+
runId: args.context?.runId ?? ""
|
|
9042
|
+
},
|
|
9043
|
+
prompt
|
|
9044
|
+
});
|
|
9045
|
+
setSpanAttributes({
|
|
9046
|
+
"app.ai.thinking_level": selection.thinkingLevel,
|
|
9047
|
+
"app.ai.thinking_level_reason": selection.reason,
|
|
9048
|
+
...selection.confidence !== void 0 ? { "app.ai.thinking_level_confidence": selection.confidence } : {}
|
|
9049
|
+
});
|
|
9050
|
+
return selection;
|
|
9051
|
+
}
|
|
9052
|
+
);
|
|
9053
|
+
}
|
|
9054
|
+
async function classifyTurn(args) {
|
|
9081
9055
|
try {
|
|
9082
9056
|
const result = await args.completeObject({
|
|
9083
9057
|
modelId: args.fastModelId,
|
|
9084
9058
|
schema: turnExecutionProfileSchema,
|
|
9085
9059
|
maxTokens: 120,
|
|
9086
|
-
metadata:
|
|
9087
|
-
|
|
9088
|
-
threadId: args.context?.threadId ?? "",
|
|
9089
|
-
channelId: args.context?.channelId ?? "",
|
|
9090
|
-
requesterId: args.context?.requesterId ?? "",
|
|
9091
|
-
runId: args.context?.runId ?? ""
|
|
9092
|
-
},
|
|
9093
|
-
prompt: buildClassifierPrompt({
|
|
9094
|
-
activeSkillNames,
|
|
9095
|
-
attachmentCount: args.attachmentCount ?? 0,
|
|
9096
|
-
conversationContext: args.conversationContext,
|
|
9097
|
-
currentTurnBlocks: args.currentTurnBlocks,
|
|
9098
|
-
messageText: args.messageText
|
|
9099
|
-
}),
|
|
9060
|
+
metadata: args.metadata,
|
|
9061
|
+
prompt: args.prompt,
|
|
9100
9062
|
thinkingLevel: "low",
|
|
9101
9063
|
system: buildClassifierSystemPrompt(),
|
|
9102
9064
|
temperature: 0
|
|
9103
9065
|
});
|
|
9104
9066
|
const parsed = turnExecutionProfileSchema.parse(result.object);
|
|
9067
|
+
const reason = parsed.reason.trim();
|
|
9105
9068
|
if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
|
|
9106
9069
|
return {
|
|
9107
9070
|
confidence: parsed.confidence,
|
|
9108
9071
|
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
9109
|
-
reason: `low_confidence_default:${
|
|
9072
|
+
reason: `low_confidence_default:${reason}`
|
|
9110
9073
|
};
|
|
9111
9074
|
}
|
|
9112
9075
|
return {
|
|
9113
9076
|
confidence: parsed.confidence,
|
|
9114
9077
|
thinkingLevel: parsed.thinking_level,
|
|
9115
|
-
reason
|
|
9078
|
+
reason
|
|
9116
9079
|
};
|
|
9117
9080
|
} catch {
|
|
9118
9081
|
return {
|
|
@@ -9150,7 +9113,7 @@ function parseAgentTurnSessionCheckpoint(value) {
|
|
|
9150
9113
|
return void 0;
|
|
9151
9114
|
}
|
|
9152
9115
|
const status = parsed.state;
|
|
9153
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
|
|
9116
|
+
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
9154
9117
|
return void 0;
|
|
9155
9118
|
}
|
|
9156
9119
|
const conversationId = parsed.conversationId;
|
|
@@ -9222,6 +9185,26 @@ async function upsertAgentTurnSessionCheckpoint(args) {
|
|
|
9222
9185
|
);
|
|
9223
9186
|
return checkpoint;
|
|
9224
9187
|
}
|
|
9188
|
+
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
9189
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
9190
|
+
args.conversationId,
|
|
9191
|
+
args.sessionId
|
|
9192
|
+
);
|
|
9193
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
9194
|
+
return void 0;
|
|
9195
|
+
}
|
|
9196
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
9197
|
+
conversationId: existing.conversationId,
|
|
9198
|
+
sessionId: existing.sessionId,
|
|
9199
|
+
sliceId: existing.sliceId,
|
|
9200
|
+
state: "superseded",
|
|
9201
|
+
piMessages: existing.piMessages,
|
|
9202
|
+
loadedSkillNames: existing.loadedSkillNames,
|
|
9203
|
+
resumeReason: existing.resumeReason,
|
|
9204
|
+
resumedFromSliceId: existing.resumedFromSliceId,
|
|
9205
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9225
9208
|
|
|
9226
9209
|
// src/chat/services/turn-checkpoint.ts
|
|
9227
9210
|
async function loadTurnCheckpoint(ctx) {
|
|
@@ -9336,13 +9319,67 @@ async function persistTimeoutCheckpoint(args) {
|
|
|
9336
9319
|
}
|
|
9337
9320
|
}
|
|
9338
9321
|
|
|
9322
|
+
// src/chat/services/pending-auth.ts
|
|
9323
|
+
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
9324
|
+
function canReusePendingAuthLink(args) {
|
|
9325
|
+
const { pendingAuth } = args;
|
|
9326
|
+
if (!pendingAuth) {
|
|
9327
|
+
return false;
|
|
9328
|
+
}
|
|
9329
|
+
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
9330
|
+
}
|
|
9331
|
+
function buildAuthPauseReplyText(args) {
|
|
9332
|
+
const providerLabel = args.provider ? formatProviderLabel(args.provider) : "";
|
|
9333
|
+
if (args.disposition === "link_already_sent") {
|
|
9334
|
+
return providerLabel ? `I still need your ${providerLabel} access to continue. I already sent you a private link.` : "I still need additional access to continue. I already sent you a private link.";
|
|
9335
|
+
}
|
|
9336
|
+
return providerLabel ? `I need your ${providerLabel} access to continue. I sent you a private link.` : "I need additional access to continue. I sent you a private link.";
|
|
9337
|
+
}
|
|
9338
|
+
function getConversationPendingAuth(args) {
|
|
9339
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
9340
|
+
if (!pendingAuth) {
|
|
9341
|
+
return void 0;
|
|
9342
|
+
}
|
|
9343
|
+
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
9344
|
+
return void 0;
|
|
9345
|
+
}
|
|
9346
|
+
return pendingAuth;
|
|
9347
|
+
}
|
|
9348
|
+
function clearPendingAuth(conversation, sessionId) {
|
|
9349
|
+
if (!conversation.processing.pendingAuth) {
|
|
9350
|
+
return;
|
|
9351
|
+
}
|
|
9352
|
+
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
9353
|
+
return;
|
|
9354
|
+
}
|
|
9355
|
+
conversation.processing.pendingAuth = void 0;
|
|
9356
|
+
}
|
|
9357
|
+
async function applyPendingAuthUpdate(args) {
|
|
9358
|
+
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
9359
|
+
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
9360
|
+
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
9361
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
9362
|
+
conversationId: args.conversationId,
|
|
9363
|
+
sessionId: previousPendingAuth.sessionId,
|
|
9364
|
+
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
9365
|
+
});
|
|
9366
|
+
}
|
|
9367
|
+
}
|
|
9368
|
+
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
9369
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
9370
|
+
const message = conversation.messages[index];
|
|
9371
|
+
if (message?.role !== "user") {
|
|
9372
|
+
continue;
|
|
9373
|
+
}
|
|
9374
|
+
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
9375
|
+
}
|
|
9376
|
+
return false;
|
|
9377
|
+
}
|
|
9378
|
+
|
|
9339
9379
|
// src/chat/services/mcp-auth-orchestration.ts
|
|
9340
|
-
var McpAuthorizationPauseError = class extends
|
|
9341
|
-
provider
|
|
9342
|
-
|
|
9343
|
-
super(`MCP authorization started for ${provider}`);
|
|
9344
|
-
this.name = "McpAuthorizationPauseError";
|
|
9345
|
-
this.provider = provider;
|
|
9380
|
+
var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
9381
|
+
constructor(provider, disposition) {
|
|
9382
|
+
super("mcp", provider, disposition);
|
|
9346
9383
|
}
|
|
9347
9384
|
};
|
|
9348
9385
|
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
@@ -9387,18 +9424,40 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
9387
9424
|
if (!authSession?.authorizationUrl) {
|
|
9388
9425
|
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
9389
9426
|
}
|
|
9390
|
-
const
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9427
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
9428
|
+
pendingAuth: deps.currentPendingAuth,
|
|
9429
|
+
kind: "mcp",
|
|
9430
|
+
provider,
|
|
9431
|
+
requesterId: deps.requesterId
|
|
9395
9432
|
});
|
|
9396
|
-
if (!
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9433
|
+
if (!reusingPendingLink) {
|
|
9434
|
+
const delivery = await deliverPrivateMessage({
|
|
9435
|
+
channelId: authSession.channelId,
|
|
9436
|
+
threadTs: authSession.threadTs,
|
|
9437
|
+
userId: authSession.userId,
|
|
9438
|
+
text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
|
|
9439
|
+
});
|
|
9440
|
+
if (!delivery) {
|
|
9441
|
+
throw new Error(
|
|
9442
|
+
`Unable to deliver MCP authorization link for plugin "${provider}"`
|
|
9443
|
+
);
|
|
9444
|
+
}
|
|
9445
|
+
} else {
|
|
9446
|
+
await deleteMcpAuthSession(authSessionId);
|
|
9447
|
+
}
|
|
9448
|
+
if (deps.sessionId && deps.requesterId) {
|
|
9449
|
+
await deps.onPendingAuth?.({
|
|
9450
|
+
kind: "mcp",
|
|
9451
|
+
provider,
|
|
9452
|
+
requesterId: deps.requesterId,
|
|
9453
|
+
sessionId: deps.sessionId,
|
|
9454
|
+
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
9455
|
+
});
|
|
9400
9456
|
}
|
|
9401
|
-
pendingPause = new McpAuthorizationPauseError(
|
|
9457
|
+
pendingPause = new McpAuthorizationPauseError(
|
|
9458
|
+
provider,
|
|
9459
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
9460
|
+
);
|
|
9402
9461
|
abortAgent();
|
|
9403
9462
|
return true;
|
|
9404
9463
|
};
|
|
@@ -9409,6 +9468,152 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
9409
9468
|
};
|
|
9410
9469
|
}
|
|
9411
9470
|
|
|
9471
|
+
// src/chat/credentials/unlink-provider.ts
|
|
9472
|
+
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
9473
|
+
await Promise.all([
|
|
9474
|
+
userTokenStore.delete(userId, provider),
|
|
9475
|
+
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
9476
|
+
deleteMcpServerSessionId(userId, provider),
|
|
9477
|
+
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
9478
|
+
]);
|
|
9479
|
+
}
|
|
9480
|
+
|
|
9481
|
+
// src/chat/services/plugin-auth-orchestration.ts
|
|
9482
|
+
var PluginAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
9483
|
+
constructor(provider, disposition) {
|
|
9484
|
+
super("plugin", provider, disposition);
|
|
9485
|
+
}
|
|
9486
|
+
};
|
|
9487
|
+
function isCommandAuthFailure(details) {
|
|
9488
|
+
if (!details || typeof details !== "object") {
|
|
9489
|
+
return false;
|
|
9490
|
+
}
|
|
9491
|
+
const result = details;
|
|
9492
|
+
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
9493
|
+
return false;
|
|
9494
|
+
}
|
|
9495
|
+
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
9496
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
9497
|
+
if (!text.trim()) {
|
|
9498
|
+
return false;
|
|
9499
|
+
}
|
|
9500
|
+
return [
|
|
9501
|
+
/\b401\b/,
|
|
9502
|
+
/\bunauthorized\b/,
|
|
9503
|
+
/\bbad credentials\b/,
|
|
9504
|
+
/\binvalid token\b/,
|
|
9505
|
+
/\btoken (?:expired|revoked)\b/,
|
|
9506
|
+
/\bexpired token\b/,
|
|
9507
|
+
/\bmissing scopes?\b/,
|
|
9508
|
+
/\binsufficient scope\b/,
|
|
9509
|
+
/\binvalid grant\b/,
|
|
9510
|
+
/\breauthoriz/
|
|
9511
|
+
].some((pattern) => pattern.test(text));
|
|
9512
|
+
}
|
|
9513
|
+
function commandTargetsProvider(provider, command, details) {
|
|
9514
|
+
const normalizedCommand = command.trim().toLowerCase();
|
|
9515
|
+
if (!normalizedCommand) {
|
|
9516
|
+
return false;
|
|
9517
|
+
}
|
|
9518
|
+
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
9519
|
+
return true;
|
|
9520
|
+
}
|
|
9521
|
+
const plugin = getPluginDefinition(provider);
|
|
9522
|
+
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
9523
|
+
const credentials = plugin?.manifest.credentials;
|
|
9524
|
+
if (credentials) {
|
|
9525
|
+
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
9526
|
+
for (const domain of credentials.apiDomains) {
|
|
9527
|
+
candidates.add(domain.toLowerCase());
|
|
9528
|
+
}
|
|
9529
|
+
}
|
|
9530
|
+
const combinedText = `${normalizedCommand}
|
|
9531
|
+
${details.stdout?.toLowerCase() ?? ""}
|
|
9532
|
+
${details.stderr?.toLowerCase() ?? ""}`;
|
|
9533
|
+
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
9534
|
+
}
|
|
9535
|
+
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
9536
|
+
let pendingPause;
|
|
9537
|
+
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
9538
|
+
if (pendingPause) {
|
|
9539
|
+
throw pendingPause;
|
|
9540
|
+
}
|
|
9541
|
+
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
9542
|
+
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
9543
|
+
}
|
|
9544
|
+
const providerLabel = formatProviderLabel(provider);
|
|
9545
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
9546
|
+
pendingAuth: deps.currentPendingAuth,
|
|
9547
|
+
kind: "plugin",
|
|
9548
|
+
provider,
|
|
9549
|
+
requesterId: deps.requesterId
|
|
9550
|
+
});
|
|
9551
|
+
if (!reusingPendingLink) {
|
|
9552
|
+
const oauthResult = await startOAuthFlow(provider, {
|
|
9553
|
+
requesterId: deps.requesterId,
|
|
9554
|
+
channelId: deps.channelId,
|
|
9555
|
+
threadTs: deps.threadTs,
|
|
9556
|
+
userMessage: deps.userMessage,
|
|
9557
|
+
channelConfiguration: deps.channelConfiguration,
|
|
9558
|
+
activeSkillName: activeSkill?.name ?? void 0,
|
|
9559
|
+
resumeConversationId: deps.conversationId,
|
|
9560
|
+
resumeSessionId: deps.sessionId
|
|
9561
|
+
});
|
|
9562
|
+
if (!oauthResult.ok) {
|
|
9563
|
+
throw new Error(oauthResult.error);
|
|
9564
|
+
}
|
|
9565
|
+
if (!oauthResult.delivery) {
|
|
9566
|
+
throw new Error(
|
|
9567
|
+
`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.`
|
|
9568
|
+
);
|
|
9569
|
+
}
|
|
9570
|
+
}
|
|
9571
|
+
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
9572
|
+
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
9573
|
+
}
|
|
9574
|
+
if (deps.sessionId) {
|
|
9575
|
+
await deps.onPendingAuth?.({
|
|
9576
|
+
kind: "plugin",
|
|
9577
|
+
provider,
|
|
9578
|
+
requesterId: deps.requesterId,
|
|
9579
|
+
sessionId: deps.sessionId,
|
|
9580
|
+
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
9581
|
+
});
|
|
9582
|
+
}
|
|
9583
|
+
pendingPause = new PluginAuthorizationPauseError(
|
|
9584
|
+
provider,
|
|
9585
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
9586
|
+
);
|
|
9587
|
+
abortAgent();
|
|
9588
|
+
throw pendingPause;
|
|
9589
|
+
};
|
|
9590
|
+
const handleCredentialUnavailable = async (input) => {
|
|
9591
|
+
if (pendingPause) {
|
|
9592
|
+
throw pendingPause;
|
|
9593
|
+
}
|
|
9594
|
+
if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
|
|
9595
|
+
throw input.error;
|
|
9596
|
+
}
|
|
9597
|
+
return await startAuthorizationPause(
|
|
9598
|
+
input.error.provider,
|
|
9599
|
+
input.activeSkill
|
|
9600
|
+
);
|
|
9601
|
+
};
|
|
9602
|
+
return {
|
|
9603
|
+
handleCredentialUnavailable,
|
|
9604
|
+
handleCommandFailure: async (input) => {
|
|
9605
|
+
const provider = input.activeSkill?.pluginProvider;
|
|
9606
|
+
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
|
|
9607
|
+
return;
|
|
9608
|
+
}
|
|
9609
|
+
await startAuthorizationPause(provider, input.activeSkill, {
|
|
9610
|
+
unlinkExistingProvider: true
|
|
9611
|
+
});
|
|
9612
|
+
},
|
|
9613
|
+
getPendingPause: () => pendingPause
|
|
9614
|
+
};
|
|
9615
|
+
}
|
|
9616
|
+
|
|
9412
9617
|
// src/chat/respond.ts
|
|
9413
9618
|
var startupDiscoveryLogged = false;
|
|
9414
9619
|
var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
|
|
@@ -9525,6 +9730,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9525
9730
|
let timeoutResumeSessionId;
|
|
9526
9731
|
let timeoutResumeSliceId = 1;
|
|
9527
9732
|
let timeoutResumeMessages = [];
|
|
9733
|
+
let beforeMessageCount = 0;
|
|
9528
9734
|
let lastKnownSandboxId = context.sandbox?.sandboxId;
|
|
9529
9735
|
let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
|
|
9530
9736
|
let loadedSkillNamesForResume = [];
|
|
@@ -9717,8 +9923,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9717
9923
|
userTurnText
|
|
9718
9924
|
});
|
|
9719
9925
|
thinkingSelection = await selectTurnThinkingLevel({
|
|
9720
|
-
activeSkillNames: activeSkills.map((skill) => skill.name),
|
|
9721
|
-
attachmentCount: context.userAttachments?.length,
|
|
9722
9926
|
completeObject,
|
|
9723
9927
|
conversationContext: context.conversationContext,
|
|
9724
9928
|
context: {
|
|
@@ -9754,9 +9958,11 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9754
9958
|
threadTs: context.correlation?.threadTs,
|
|
9755
9959
|
toolChannelId: context.toolChannelId,
|
|
9756
9960
|
userMessage: userInput,
|
|
9961
|
+
currentPendingAuth: context.pendingAuth,
|
|
9757
9962
|
getConfiguration: () => configurationValues,
|
|
9758
9963
|
getArtifactState: () => context.artifactState,
|
|
9759
|
-
getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch)
|
|
9964
|
+
getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch),
|
|
9965
|
+
onPendingAuth: context.onAuthPending
|
|
9760
9966
|
},
|
|
9761
9967
|
() => agent?.abort()
|
|
9762
9968
|
);
|
|
@@ -9769,6 +9975,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9769
9975
|
threadTs: context.correlation?.threadTs,
|
|
9770
9976
|
userMessage: userInput,
|
|
9771
9977
|
channelConfiguration: context.channelConfiguration,
|
|
9978
|
+
currentPendingAuth: context.pendingAuth,
|
|
9979
|
+
onPendingAuth: context.onAuthPending,
|
|
9772
9980
|
userTokenStore
|
|
9773
9981
|
},
|
|
9774
9982
|
() => agent?.abort()
|
|
@@ -9987,9 +10195,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9987
10195
|
);
|
|
9988
10196
|
});
|
|
9989
10197
|
});
|
|
9990
|
-
let beforeMessageCount = agent.state.messages.length;
|
|
9991
10198
|
let newMessages = [];
|
|
9992
|
-
|
|
10199
|
+
beforeMessageCount = agent.state.messages.length;
|
|
9993
10200
|
try {
|
|
9994
10201
|
if (resumedFromCheckpoint) {
|
|
9995
10202
|
agent.state.messages = existingCheckpoint.piMessages;
|
|
@@ -10056,11 +10263,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10056
10263
|
}
|
|
10057
10264
|
}
|
|
10058
10265
|
newMessages = agent.state.messages.slice(beforeMessageCount);
|
|
10059
|
-
completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
|
|
10060
|
-
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10061
|
-
timeoutResumeMessages = [...agent.state.messages];
|
|
10062
|
-
throw getPendingAuthPause();
|
|
10063
|
-
}
|
|
10064
10266
|
const outputMessages = newMessages.filter(isAssistantMessage);
|
|
10065
10267
|
const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
|
|
10066
10268
|
const usageSummary = extractGenAiUsageSummary(
|
|
@@ -10076,6 +10278,10 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10076
10278
|
...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
|
|
10077
10279
|
...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
|
|
10078
10280
|
});
|
|
10281
|
+
if (getPendingAuthPause()) {
|
|
10282
|
+
timeoutResumeMessages = [...agent.state.messages];
|
|
10283
|
+
throw getPendingAuthPause();
|
|
10284
|
+
}
|
|
10079
10285
|
},
|
|
10080
10286
|
{
|
|
10081
10287
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
@@ -10088,9 +10294,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10088
10294
|
} finally {
|
|
10089
10295
|
unsubscribe();
|
|
10090
10296
|
}
|
|
10091
|
-
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10092
|
-
throw getPendingAuthPause();
|
|
10093
|
-
}
|
|
10094
10297
|
if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
10095
10298
|
await persistCompletedCheckpoint({
|
|
10096
10299
|
conversationId: sessionConversationId,
|
|
@@ -10148,7 +10351,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10148
10351
|
);
|
|
10149
10352
|
}
|
|
10150
10353
|
}
|
|
10151
|
-
if (
|
|
10354
|
+
if (error instanceof AuthorizationPauseError && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
10355
|
+
if (!turnUsage && timeoutResumeMessages.length > 0) {
|
|
10356
|
+
const fallbackUsage = extractGenAiUsageSummary(
|
|
10357
|
+
...timeoutResumeMessages.slice(beforeMessageCount).filter(isAssistantMessage)
|
|
10358
|
+
);
|
|
10359
|
+
turnUsage = Object.values(fallbackUsage).some(
|
|
10360
|
+
(value) => value !== void 0
|
|
10361
|
+
) ? fallbackUsage : void 0;
|
|
10362
|
+
}
|
|
10152
10363
|
const nextSliceId = await persistAuthPauseCheckpoint({
|
|
10153
10364
|
conversationId: timeoutResumeConversationId,
|
|
10154
10365
|
sessionId: timeoutResumeSessionId,
|
|
@@ -10166,9 +10377,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10166
10377
|
}
|
|
10167
10378
|
});
|
|
10168
10379
|
throw new RetryableTurnError(
|
|
10169
|
-
error
|
|
10380
|
+
error.kind === "plugin" ? "plugin_auth_resume" : "mcp_auth_resume",
|
|
10170
10381
|
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`,
|
|
10171
10382
|
{
|
|
10383
|
+
authDisposition: error.disposition,
|
|
10384
|
+
authDurationMs: Date.now() - replyStartedAtMs,
|
|
10385
|
+
authKind: error.kind,
|
|
10386
|
+
authProvider: error.provider,
|
|
10387
|
+
authThinkingLevel: thinkingSelection?.thinkingLevel,
|
|
10388
|
+
authUsage: turnUsage,
|
|
10172
10389
|
conversationId: timeoutResumeConversationId,
|
|
10173
10390
|
sessionId: timeoutResumeSessionId,
|
|
10174
10391
|
sliceId: nextSliceId
|
|
@@ -11041,6 +11258,82 @@ async function resumeAuthorizedRequest(args) {
|
|
|
11041
11258
|
});
|
|
11042
11259
|
}
|
|
11043
11260
|
|
|
11261
|
+
// src/chat/runtime/auth-pause-reply.ts
|
|
11262
|
+
function buildAuthPauseSlackMessage(args) {
|
|
11263
|
+
const footer = buildSlackReplyFooter({
|
|
11264
|
+
conversationId: args.conversationId,
|
|
11265
|
+
durationMs: args.durationMs,
|
|
11266
|
+
thinkingLevel: args.thinkingLevel,
|
|
11267
|
+
usage: args.usage
|
|
11268
|
+
});
|
|
11269
|
+
const blocks = buildSlackReplyBlocks(args.text, footer);
|
|
11270
|
+
return blocks ? { text: args.text, blocks } : { text: args.text };
|
|
11271
|
+
}
|
|
11272
|
+
function completeAuthPauseTurn(args) {
|
|
11273
|
+
markConversationMessage(
|
|
11274
|
+
args.conversation,
|
|
11275
|
+
getTurnUserMessageId(args.conversation, args.sessionId),
|
|
11276
|
+
{
|
|
11277
|
+
replied: true,
|
|
11278
|
+
skippedReason: void 0
|
|
11279
|
+
}
|
|
11280
|
+
);
|
|
11281
|
+
upsertConversationMessage(args.conversation, {
|
|
11282
|
+
id: generateConversationId("assistant"),
|
|
11283
|
+
role: "assistant",
|
|
11284
|
+
text: normalizeConversationText(args.text) || "[empty response]",
|
|
11285
|
+
createdAtMs: Date.now(),
|
|
11286
|
+
author: {
|
|
11287
|
+
userName: botConfig.userName,
|
|
11288
|
+
isBot: true
|
|
11289
|
+
},
|
|
11290
|
+
meta: {
|
|
11291
|
+
replied: true
|
|
11292
|
+
}
|
|
11293
|
+
});
|
|
11294
|
+
markTurnCompleted({
|
|
11295
|
+
conversation: args.conversation,
|
|
11296
|
+
nowMs: Date.now(),
|
|
11297
|
+
sessionId: args.sessionId,
|
|
11298
|
+
updateConversationStats
|
|
11299
|
+
});
|
|
11300
|
+
}
|
|
11301
|
+
async function persistAuthPauseReplyState(args) {
|
|
11302
|
+
const currentState = await getPersistedThreadState(args.threadStateId);
|
|
11303
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11304
|
+
completeAuthPauseTurn({
|
|
11305
|
+
conversation,
|
|
11306
|
+
sessionId: args.sessionId,
|
|
11307
|
+
text: args.text
|
|
11308
|
+
});
|
|
11309
|
+
await persistThreadStateById(args.threadStateId, { conversation });
|
|
11310
|
+
}
|
|
11311
|
+
async function deliverAuthPauseReply(args) {
|
|
11312
|
+
const retryable = isRetryableTurnError(args.error) ? args.error : void 0;
|
|
11313
|
+
const text = retryable ? buildAuthPauseReplyText({
|
|
11314
|
+
disposition: retryable.metadata?.authDisposition,
|
|
11315
|
+
provider: retryable.metadata?.authProvider
|
|
11316
|
+
}) : buildAuthPauseReplyText({ provider: args.fallbackProvider });
|
|
11317
|
+
const message = buildAuthPauseSlackMessage({
|
|
11318
|
+
conversationId: args.conversationId,
|
|
11319
|
+
durationMs: retryable?.metadata?.authDurationMs,
|
|
11320
|
+
text,
|
|
11321
|
+
thinkingLevel: retryable?.metadata?.authThinkingLevel,
|
|
11322
|
+
usage: retryable?.metadata?.authUsage
|
|
11323
|
+
});
|
|
11324
|
+
await postSlackMessage({
|
|
11325
|
+
channelId: args.channelId,
|
|
11326
|
+
threadTs: args.threadTs,
|
|
11327
|
+
text: message.text,
|
|
11328
|
+
...message.blocks ? { blocks: message.blocks } : {}
|
|
11329
|
+
});
|
|
11330
|
+
await persistAuthPauseReplyState({
|
|
11331
|
+
sessionId: args.sessionId,
|
|
11332
|
+
text,
|
|
11333
|
+
threadStateId: args.threadStateId
|
|
11334
|
+
});
|
|
11335
|
+
}
|
|
11336
|
+
|
|
11044
11337
|
// src/chat/services/timeout-resume.ts
|
|
11045
11338
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
11046
11339
|
var TURN_TIMEOUT_RESUME_PATH = "/api/internal/turn-resume";
|
|
@@ -11213,6 +11506,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11213
11506
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11214
11507
|
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : void 0;
|
|
11215
11508
|
const userMessageId = getTurnUserMessageId(conversation, sessionId);
|
|
11509
|
+
clearPendingAuth(conversation, sessionId);
|
|
11216
11510
|
markConversationMessage(conversation, userMessageId, {
|
|
11217
11511
|
replied: true,
|
|
11218
11512
|
skippedReason: void 0
|
|
@@ -11233,6 +11527,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11233
11527
|
markTurnCompleted({
|
|
11234
11528
|
conversation,
|
|
11235
11529
|
nowMs: Date.now(),
|
|
11530
|
+
sessionId,
|
|
11236
11531
|
updateConversationStats
|
|
11237
11532
|
});
|
|
11238
11533
|
await persistThreadStateById(threadId, {
|
|
@@ -11246,9 +11541,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
11246
11541
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
11247
11542
|
const currentState = await getPersistedThreadState(threadId);
|
|
11248
11543
|
const conversation = coerceThreadConversationState(currentState);
|
|
11544
|
+
clearPendingAuth(conversation, sessionId);
|
|
11249
11545
|
markTurnFailed({
|
|
11250
11546
|
conversation,
|
|
11251
11547
|
nowMs: Date.now(),
|
|
11548
|
+
sessionId,
|
|
11252
11549
|
userMessageId: getTurnUserMessageId(conversation, sessionId),
|
|
11253
11550
|
markConversationMessage,
|
|
11254
11551
|
updateConversationStats
|
|
@@ -11266,8 +11563,29 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11266
11563
|
const currentState = await getPersistedThreadState(threadId);
|
|
11267
11564
|
const conversation = coerceThreadConversationState(currentState);
|
|
11268
11565
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11269
|
-
const
|
|
11270
|
-
|
|
11566
|
+
const pendingAuth = getConversationPendingAuth({
|
|
11567
|
+
conversation,
|
|
11568
|
+
kind: "mcp",
|
|
11569
|
+
provider,
|
|
11570
|
+
requesterId: authSession.userId
|
|
11571
|
+
});
|
|
11572
|
+
const resolvedSessionId = pendingAuth?.sessionId ?? authSession.sessionId;
|
|
11573
|
+
const userMessage = getTurnUserMessage(conversation, resolvedSessionId);
|
|
11574
|
+
if (pendingAuth) {
|
|
11575
|
+
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
11576
|
+
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
11577
|
+
await persistThreadStateById(threadId, { conversation });
|
|
11578
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
11579
|
+
conversationId: authSession.conversationId,
|
|
11580
|
+
sessionId: pendingAuth.sessionId,
|
|
11581
|
+
errorMessage: "Auth completed after a newer thread message superseded this blocked request."
|
|
11582
|
+
});
|
|
11583
|
+
return;
|
|
11584
|
+
}
|
|
11585
|
+
} else if (conversation.processing.activeTurnId !== authSession.sessionId) {
|
|
11586
|
+
return;
|
|
11587
|
+
}
|
|
11588
|
+
if (!userMessage) {
|
|
11271
11589
|
return;
|
|
11272
11590
|
}
|
|
11273
11591
|
const channelConfiguration = getChannelConfigurationServiceById(
|
|
@@ -11276,14 +11594,14 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11276
11594
|
const conversationContext = await buildResumeConversationContext(
|
|
11277
11595
|
authSession.channelId,
|
|
11278
11596
|
authSession.threadTs,
|
|
11279
|
-
|
|
11597
|
+
resolvedSessionId
|
|
11280
11598
|
);
|
|
11281
11599
|
await resumeAuthorizedRequest({
|
|
11282
|
-
messageText:
|
|
11600
|
+
messageText: userMessage.text,
|
|
11283
11601
|
channelId: authSession.channelId,
|
|
11284
11602
|
threadTs: authSession.threadTs,
|
|
11285
11603
|
lockKey: authSession.conversationId,
|
|
11286
|
-
connectedText:
|
|
11604
|
+
connectedText: "",
|
|
11287
11605
|
failureText: "MCP authorization completed, but resuming the request failed. Please retry the original command.",
|
|
11288
11606
|
replyContext: {
|
|
11289
11607
|
assistant: { userName: botConfig.userName },
|
|
@@ -11294,7 +11612,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11294
11612
|
},
|
|
11295
11613
|
correlation: {
|
|
11296
11614
|
conversationId: authSession.conversationId,
|
|
11297
|
-
turnId:
|
|
11615
|
+
turnId: resolvedSessionId,
|
|
11298
11616
|
channelId: authSession.channelId,
|
|
11299
11617
|
threadTs: authSession.threadTs,
|
|
11300
11618
|
requesterId: authSession.userId
|
|
@@ -11303,9 +11621,18 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11303
11621
|
conversationContext,
|
|
11304
11622
|
artifactState: artifacts,
|
|
11305
11623
|
configuration: authSession.configuration,
|
|
11624
|
+
pendingAuth,
|
|
11306
11625
|
channelConfiguration,
|
|
11307
11626
|
sandbox: getPersistedSandboxState(currentState),
|
|
11308
11627
|
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
11628
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
11629
|
+
await applyPendingAuthUpdate({
|
|
11630
|
+
conversation,
|
|
11631
|
+
conversationId: authSession.conversationId,
|
|
11632
|
+
nextPendingAuth
|
|
11633
|
+
});
|
|
11634
|
+
await persistThreadStateById(threadId, { conversation });
|
|
11635
|
+
},
|
|
11309
11636
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11310
11637
|
},
|
|
11311
11638
|
onSuccess: async (reply) => {
|
|
@@ -11313,7 +11640,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11313
11640
|
await persistCompletedReplyState(
|
|
11314
11641
|
authSession.channelId,
|
|
11315
11642
|
authSession.threadTs,
|
|
11316
|
-
|
|
11643
|
+
resolvedSessionId,
|
|
11317
11644
|
reply
|
|
11318
11645
|
);
|
|
11319
11646
|
} catch (persistError) {
|
|
@@ -11338,7 +11665,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11338
11665
|
await persistFailedReplyState(
|
|
11339
11666
|
authSession.channelId,
|
|
11340
11667
|
authSession.threadTs,
|
|
11341
|
-
|
|
11668
|
+
resolvedSessionId
|
|
11342
11669
|
);
|
|
11343
11670
|
} catch (persistError) {
|
|
11344
11671
|
logException(
|
|
@@ -11350,7 +11677,16 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11350
11677
|
);
|
|
11351
11678
|
}
|
|
11352
11679
|
},
|
|
11353
|
-
onAuthPause: async () => {
|
|
11680
|
+
onAuthPause: async (error) => {
|
|
11681
|
+
await deliverAuthPauseReply({
|
|
11682
|
+
channelId: authSession.channelId,
|
|
11683
|
+
conversationId: authSession.conversationId,
|
|
11684
|
+
error,
|
|
11685
|
+
fallbackProvider: provider,
|
|
11686
|
+
sessionId: resolvedSessionId,
|
|
11687
|
+
threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`,
|
|
11688
|
+
threadTs: authSession.threadTs
|
|
11689
|
+
});
|
|
11354
11690
|
logWarn(
|
|
11355
11691
|
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
11356
11692
|
{},
|
|
@@ -11385,7 +11721,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11385
11721
|
}
|
|
11386
11722
|
await scheduleTurnTimeoutResume({
|
|
11387
11723
|
conversationId: authSession.conversationId,
|
|
11388
|
-
sessionId:
|
|
11724
|
+
sessionId: resolvedSessionId,
|
|
11389
11725
|
expectedCheckpointVersion: checkpointVersion
|
|
11390
11726
|
});
|
|
11391
11727
|
}
|
|
@@ -11610,6 +11946,7 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11610
11946
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11611
11947
|
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
11612
11948
|
const userMessage = getTurnUserMessage(conversation, args.sessionId);
|
|
11949
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
11613
11950
|
markConversationMessage(conversation, userMessage?.id, {
|
|
11614
11951
|
replied: true,
|
|
11615
11952
|
skippedReason: void 0
|
|
@@ -11630,6 +11967,7 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11630
11967
|
markTurnCompleted({
|
|
11631
11968
|
conversation,
|
|
11632
11969
|
nowMs: Date.now(),
|
|
11970
|
+
sessionId: args.sessionId,
|
|
11633
11971
|
updateConversationStats
|
|
11634
11972
|
});
|
|
11635
11973
|
await persistThreadStateById(args.conversationId, {
|
|
@@ -11642,9 +11980,11 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11642
11980
|
async function persistFailedOAuthReplyState(args) {
|
|
11643
11981
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11644
11982
|
const conversation = coerceThreadConversationState(currentState);
|
|
11983
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
11645
11984
|
markTurnFailed({
|
|
11646
11985
|
conversation,
|
|
11647
11986
|
nowMs: Date.now(),
|
|
11987
|
+
sessionId: args.sessionId,
|
|
11648
11988
|
userMessageId: getTurnUserMessage(conversation, args.sessionId)?.id,
|
|
11649
11989
|
markConversationMessage,
|
|
11650
11990
|
updateConversationStats
|
|
@@ -11661,35 +12001,65 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11661
12001
|
stored.resumeConversationId,
|
|
11662
12002
|
stored.resumeSessionId
|
|
11663
12003
|
);
|
|
11664
|
-
if (!checkpoint
|
|
12004
|
+
if (!checkpoint) {
|
|
11665
12005
|
return false;
|
|
11666
12006
|
}
|
|
12007
|
+
if (checkpoint.state === "completed" || checkpoint.state === "failed" || checkpoint.state === "superseded") {
|
|
12008
|
+
return true;
|
|
12009
|
+
}
|
|
12010
|
+
if (checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "auth") {
|
|
12011
|
+
return true;
|
|
12012
|
+
}
|
|
11667
12013
|
const currentState = await getPersistedThreadState(
|
|
11668
12014
|
stored.resumeConversationId
|
|
11669
12015
|
);
|
|
11670
12016
|
const conversation = coerceThreadConversationState(currentState);
|
|
11671
12017
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11672
|
-
const
|
|
11673
|
-
|
|
11674
|
-
|
|
12018
|
+
const pendingAuth = getConversationPendingAuth({
|
|
12019
|
+
conversation,
|
|
12020
|
+
kind: "plugin",
|
|
12021
|
+
provider: stored.provider,
|
|
12022
|
+
requesterId: stored.userId
|
|
12023
|
+
});
|
|
12024
|
+
const resolvedSessionId = pendingAuth?.sessionId ?? stored.resumeSessionId;
|
|
12025
|
+
const userMessage = resolvedSessionId ? getTurnUserMessage(conversation, resolvedSessionId) : void 0;
|
|
12026
|
+
if (pendingAuth) {
|
|
12027
|
+
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
12028
|
+
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
12029
|
+
await persistThreadStateById(stored.resumeConversationId, {
|
|
12030
|
+
conversation
|
|
12031
|
+
});
|
|
12032
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
12033
|
+
conversationId: stored.resumeConversationId,
|
|
12034
|
+
sessionId: pendingAuth.sessionId,
|
|
12035
|
+
errorMessage: "Auth completed after a newer thread message superseded this blocked request."
|
|
12036
|
+
});
|
|
12037
|
+
return true;
|
|
12038
|
+
}
|
|
12039
|
+
} else {
|
|
12040
|
+
if (!userMessage?.author?.userId) {
|
|
12041
|
+
return false;
|
|
12042
|
+
}
|
|
12043
|
+
if (conversation.processing.activeTurnId !== stored.resumeSessionId) {
|
|
12044
|
+
return true;
|
|
12045
|
+
}
|
|
11675
12046
|
}
|
|
11676
|
-
if (
|
|
11677
|
-
return
|
|
12047
|
+
if (!userMessage?.author?.userId || !resolvedSessionId) {
|
|
12048
|
+
return false;
|
|
11678
12049
|
}
|
|
11679
12050
|
const conversationContext = await buildCheckpointConversationContext(
|
|
11680
12051
|
stored.resumeConversationId,
|
|
11681
|
-
|
|
12052
|
+
resolvedSessionId
|
|
11682
12053
|
);
|
|
11683
12054
|
const channelConfiguration = getChannelConfigurationServiceById(
|
|
11684
12055
|
stored.channelId
|
|
11685
12056
|
);
|
|
11686
|
-
const providerLabel = formatProviderLabel(stored.provider);
|
|
11687
12057
|
await resumeSlackTurn({
|
|
11688
12058
|
messageText: stored.pendingMessage ?? userMessage.text,
|
|
11689
12059
|
channelId: stored.channelId,
|
|
11690
12060
|
threadTs: stored.threadTs,
|
|
11691
12061
|
lockKey: stored.resumeConversationId,
|
|
11692
|
-
initialText:
|
|
12062
|
+
initialText: "",
|
|
11693
12063
|
failureText: "I connected your account but hit an error processing your request. Please try the command again.",
|
|
11694
12064
|
replyContext: {
|
|
11695
12065
|
assistant: { userName: botConfig.userName },
|
|
@@ -11705,10 +12075,21 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11705
12075
|
},
|
|
11706
12076
|
toolChannelId: artifacts.assistantContextChannelId ?? stored.channelId,
|
|
11707
12077
|
artifactState: artifacts,
|
|
12078
|
+
pendingAuth,
|
|
11708
12079
|
conversationContext,
|
|
11709
12080
|
channelConfiguration,
|
|
11710
12081
|
sandbox: getPersistedSandboxState(currentState),
|
|
11711
12082
|
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
12083
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
12084
|
+
await applyPendingAuthUpdate({
|
|
12085
|
+
conversation,
|
|
12086
|
+
conversationId: stored.resumeConversationId,
|
|
12087
|
+
nextPendingAuth
|
|
12088
|
+
});
|
|
12089
|
+
await persistThreadStateById(stored.resumeConversationId, {
|
|
12090
|
+
conversation
|
|
12091
|
+
});
|
|
12092
|
+
},
|
|
11712
12093
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11713
12094
|
},
|
|
11714
12095
|
onSuccess: async (reply) => {
|
|
@@ -11720,11 +12101,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11720
12101
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
11721
12102
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
11722
12103
|
},
|
|
11723
|
-
"
|
|
12104
|
+
"OAuth callback auto-resumed checkpoint finished replying"
|
|
11724
12105
|
);
|
|
11725
12106
|
await persistCompletedOAuthReplyState({
|
|
11726
12107
|
conversationId: stored.resumeConversationId,
|
|
11727
|
-
sessionId:
|
|
12108
|
+
sessionId: resolvedSessionId,
|
|
11728
12109
|
reply
|
|
11729
12110
|
});
|
|
11730
12111
|
},
|
|
@@ -11738,17 +12119,19 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11738
12119
|
);
|
|
11739
12120
|
await persistFailedOAuthReplyState({
|
|
11740
12121
|
conversationId: stored.resumeConversationId,
|
|
11741
|
-
sessionId:
|
|
12122
|
+
sessionId: resolvedSessionId
|
|
11742
12123
|
});
|
|
11743
12124
|
},
|
|
11744
12125
|
onAuthPause: async (error) => {
|
|
11745
|
-
|
|
12126
|
+
await deliverAuthPauseReply({
|
|
12127
|
+
channelId: stored.channelId,
|
|
12128
|
+
conversationId: stored.resumeConversationId,
|
|
11746
12129
|
error,
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
);
|
|
12130
|
+
fallbackProvider: stored.provider,
|
|
12131
|
+
sessionId: resolvedSessionId,
|
|
12132
|
+
threadStateId: stored.resumeConversationId,
|
|
12133
|
+
threadTs: stored.threadTs
|
|
12134
|
+
});
|
|
11752
12135
|
},
|
|
11753
12136
|
onTimeoutPause: async (error) => {
|
|
11754
12137
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
@@ -11768,7 +12151,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11768
12151
|
}
|
|
11769
12152
|
await scheduleTurnTimeoutResume({
|
|
11770
12153
|
conversationId: stored.resumeConversationId,
|
|
11771
|
-
sessionId:
|
|
12154
|
+
sessionId: resolvedSessionId,
|
|
11772
12155
|
expectedCheckpointVersion: checkpointVersion
|
|
11773
12156
|
});
|
|
11774
12157
|
}
|
|
@@ -11777,7 +12160,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11777
12160
|
}
|
|
11778
12161
|
async function resumePendingOAuthMessage(stored) {
|
|
11779
12162
|
if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
|
|
11780
|
-
const providerLabel = formatProviderLabel(stored.provider);
|
|
11781
12163
|
const conversationContext = await buildResumeConversationContext2(
|
|
11782
12164
|
stored.channelId,
|
|
11783
12165
|
stored.threadTs
|
|
@@ -11786,7 +12168,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
11786
12168
|
messageText: stored.pendingMessage,
|
|
11787
12169
|
channelId: stored.channelId,
|
|
11788
12170
|
threadTs: stored.threadTs,
|
|
11789
|
-
connectedText:
|
|
12171
|
+
connectedText: "",
|
|
11790
12172
|
failureText: `I connected your account but hit an error processing your request. Please try \`${stored.pendingMessage}\` again.`,
|
|
11791
12173
|
replyContext: {
|
|
11792
12174
|
requester: { userId: stored.userId },
|
|
@@ -11802,7 +12184,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
11802
12184
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
11803
12185
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
11804
12186
|
},
|
|
11805
|
-
"
|
|
12187
|
+
"OAuth callback auto-resumed pending message finished replying"
|
|
11806
12188
|
);
|
|
11807
12189
|
},
|
|
11808
12190
|
onFailure: async (error) => {
|
|
@@ -12051,6 +12433,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
12051
12433
|
conversation,
|
|
12052
12434
|
args.checkpoint.sessionId
|
|
12053
12435
|
);
|
|
12436
|
+
clearPendingAuth(conversation, args.checkpoint.sessionId);
|
|
12054
12437
|
markConversationMessage(conversation, userMessage?.id, {
|
|
12055
12438
|
replied: true,
|
|
12056
12439
|
skippedReason: void 0
|
|
@@ -12071,6 +12454,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
12071
12454
|
markTurnCompleted({
|
|
12072
12455
|
conversation,
|
|
12073
12456
|
nowMs: Date.now(),
|
|
12457
|
+
sessionId: args.checkpoint.sessionId,
|
|
12074
12458
|
updateConversationStats
|
|
12075
12459
|
});
|
|
12076
12460
|
await persistThreadStateById(args.checkpoint.conversationId, {
|
|
@@ -12083,9 +12467,11 @@ async function persistCompletedReplyState2(args) {
|
|
|
12083
12467
|
async function persistFailedReplyState2(checkpoint) {
|
|
12084
12468
|
const currentState = await getPersistedThreadState(checkpoint.conversationId);
|
|
12085
12469
|
const conversation = coerceThreadConversationState(currentState);
|
|
12470
|
+
clearPendingAuth(conversation, checkpoint.sessionId);
|
|
12086
12471
|
markTurnFailed({
|
|
12087
12472
|
conversation,
|
|
12088
12473
|
nowMs: Date.now(),
|
|
12474
|
+
sessionId: checkpoint.sessionId,
|
|
12089
12475
|
userMessageId: getTurnUserMessage(conversation, checkpoint.sessionId)?.id,
|
|
12090
12476
|
markConversationMessage,
|
|
12091
12477
|
updateConversationStats
|
|
@@ -12149,10 +12535,21 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12149
12535
|
},
|
|
12150
12536
|
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
12151
12537
|
artifactState: artifacts,
|
|
12538
|
+
pendingAuth: conversation.processing.pendingAuth,
|
|
12152
12539
|
conversationContext,
|
|
12153
12540
|
channelConfiguration,
|
|
12154
12541
|
sandbox,
|
|
12155
12542
|
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
12543
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
12544
|
+
await applyPendingAuthUpdate({
|
|
12545
|
+
conversation,
|
|
12546
|
+
conversationId: payload.conversationId,
|
|
12547
|
+
nextPendingAuth
|
|
12548
|
+
});
|
|
12549
|
+
await persistThreadStateById(payload.conversationId, {
|
|
12550
|
+
conversation
|
|
12551
|
+
});
|
|
12552
|
+
},
|
|
12156
12553
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
12157
12554
|
},
|
|
12158
12555
|
onSuccess: async (reply) => {
|
|
@@ -12184,7 +12581,15 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12184
12581
|
);
|
|
12185
12582
|
await persistFailedReplyState2(checkpoint);
|
|
12186
12583
|
},
|
|
12187
|
-
onAuthPause: async () => {
|
|
12584
|
+
onAuthPause: async (error) => {
|
|
12585
|
+
await deliverAuthPauseReply({
|
|
12586
|
+
channelId: thread.channelId,
|
|
12587
|
+
conversationId: payload.conversationId,
|
|
12588
|
+
error,
|
|
12589
|
+
sessionId: payload.sessionId,
|
|
12590
|
+
threadStateId: payload.conversationId,
|
|
12591
|
+
threadTs: thread.threadTs
|
|
12592
|
+
});
|
|
12188
12593
|
logWarn(
|
|
12189
12594
|
"timeout_resume_reparked_for_auth",
|
|
12190
12595
|
{},
|
|
@@ -13923,6 +14328,7 @@ function createReplyToThread(deps) {
|
|
|
13923
14328
|
},
|
|
13924
14329
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
13925
14330
|
artifactState: preparedState.artifacts,
|
|
14331
|
+
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
13926
14332
|
configuration: preparedState.configuration,
|
|
13927
14333
|
channelConfiguration: preparedState.channelConfiguration,
|
|
13928
14334
|
inboundAttachmentCount: message.attachments.length,
|
|
@@ -13952,6 +14358,16 @@ function createReplyToThread(deps) {
|
|
|
13952
14358
|
onArtifactStateUpdated: async (artifacts) => {
|
|
13953
14359
|
await persistThreadState(thread, { artifacts });
|
|
13954
14360
|
},
|
|
14361
|
+
onAuthPending: async (pendingAuth) => {
|
|
14362
|
+
await applyPendingAuthUpdate({
|
|
14363
|
+
conversation: preparedState.conversation,
|
|
14364
|
+
conversationId,
|
|
14365
|
+
nextPendingAuth: pendingAuth
|
|
14366
|
+
});
|
|
14367
|
+
await persistThreadState(thread, {
|
|
14368
|
+
conversation: preparedState.conversation
|
|
14369
|
+
});
|
|
14370
|
+
},
|
|
13955
14371
|
threadParticipants,
|
|
13956
14372
|
onStatus: (nextStatus) => status.update(nextStatus)
|
|
13957
14373
|
});
|
|
@@ -14129,8 +14545,42 @@ function createReplyToThread(deps) {
|
|
|
14129
14545
|
}
|
|
14130
14546
|
} catch (error) {
|
|
14131
14547
|
if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
|
|
14548
|
+
const authPauseText = buildAuthPauseReplyText({
|
|
14549
|
+
disposition: error.metadata?.authDisposition,
|
|
14550
|
+
provider: error.metadata?.authProvider
|
|
14551
|
+
});
|
|
14552
|
+
const authPauseFooter = buildSlackReplyFooter({
|
|
14553
|
+
conversationId,
|
|
14554
|
+
durationMs: error.metadata?.authDurationMs,
|
|
14555
|
+
thinkingLevel: error.metadata?.authThinkingLevel,
|
|
14556
|
+
usage: error.metadata?.authUsage
|
|
14557
|
+
});
|
|
14558
|
+
const useSlackFooterForAuthPause = Boolean(authPauseFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
14559
|
+
if (useSlackFooterForAuthPause && channelId && threadTs) {
|
|
14560
|
+
await beforeFirstResponsePost();
|
|
14561
|
+
await postSlackApiReplyPosts({
|
|
14562
|
+
channelId,
|
|
14563
|
+
threadTs,
|
|
14564
|
+
footer: authPauseFooter,
|
|
14565
|
+
posts: [{ stage: "thread_reply", text: authPauseText }]
|
|
14566
|
+
});
|
|
14567
|
+
} else {
|
|
14568
|
+
await postThreadReply(
|
|
14569
|
+
buildSlackOutputMessage(authPauseText),
|
|
14570
|
+
"thread_reply"
|
|
14571
|
+
);
|
|
14572
|
+
}
|
|
14573
|
+
completeAuthPauseTurn({
|
|
14574
|
+
conversation: preparedState.conversation,
|
|
14575
|
+
sessionId: error.metadata?.sessionId ?? turnId,
|
|
14576
|
+
text: authPauseText
|
|
14577
|
+
});
|
|
14578
|
+
await persistThreadState(thread, {
|
|
14579
|
+
conversation: preparedState.conversation
|
|
14580
|
+
});
|
|
14581
|
+
persistedAtLeastOnce = true;
|
|
14132
14582
|
shouldPersistFailureState = false;
|
|
14133
|
-
|
|
14583
|
+
return;
|
|
14134
14584
|
}
|
|
14135
14585
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
14136
14586
|
const conversationIdForResume = error.metadata?.conversationId;
|