@sentry/junior 0.28.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 +707 -479
- package/dist/{chunk-375D5V4U.js → chunk-LEYD42MR.js} +256 -4
- package/dist/cli/snapshot-warmup.js +1 -1
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -6,11 +6,17 @@ import {
|
|
|
6
6
|
parseSkillInvocation
|
|
7
7
|
} from "./chunk-ICIRAL6Y.js";
|
|
8
8
|
import {
|
|
9
|
+
GEN_AI_PROVIDER_NAME,
|
|
10
|
+
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
9
11
|
SANDBOX_DATA_ROOT,
|
|
10
12
|
SANDBOX_SKILLS_ROOT,
|
|
11
13
|
SANDBOX_WORKSPACE_ROOT,
|
|
12
14
|
botConfig,
|
|
13
15
|
buildNonInteractiveShellScript,
|
|
16
|
+
completeObject,
|
|
17
|
+
completeText,
|
|
18
|
+
getGatewayApiKey,
|
|
19
|
+
getPiGatewayApiKeyOverride,
|
|
14
20
|
getRuntimeDependencyProfileHash,
|
|
15
21
|
getRuntimeMetadata,
|
|
16
22
|
getSlackBotToken,
|
|
@@ -20,19 +26,18 @@ import {
|
|
|
20
26
|
getStateAdapter,
|
|
21
27
|
getVercelSandboxCredentials,
|
|
22
28
|
isSnapshotMissingError,
|
|
29
|
+
resolveGatewayModel,
|
|
23
30
|
resolveRuntimeDependencySnapshot,
|
|
24
31
|
runNonInteractiveCommand,
|
|
25
32
|
sandboxSkillDir,
|
|
26
|
-
sandboxSkillFile
|
|
27
|
-
|
|
28
|
-
} from "./chunk-375D5V4U.js";
|
|
33
|
+
sandboxSkillFile
|
|
34
|
+
} from "./chunk-LEYD42MR.js";
|
|
29
35
|
import {
|
|
30
36
|
CredentialUnavailableError,
|
|
31
37
|
buildOAuthTokenRequest,
|
|
32
38
|
createChatSdkLogger,
|
|
33
39
|
createPluginBroker,
|
|
34
40
|
createRequestContext,
|
|
35
|
-
extractGenAiUsageAttributes,
|
|
36
41
|
extractGenAiUsageSummary,
|
|
37
42
|
getActiveTraceId,
|
|
38
43
|
getPluginDefinition,
|
|
@@ -387,6 +392,26 @@ function defaultConversationState() {
|
|
|
387
392
|
}
|
|
388
393
|
};
|
|
389
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
|
+
}
|
|
390
415
|
function coerceThreadConversationState(value) {
|
|
391
416
|
if (!isRecord(value)) {
|
|
392
417
|
return defaultConversationState();
|
|
@@ -437,7 +462,8 @@ function coerceThreadConversationState(value) {
|
|
|
437
462
|
const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
|
|
438
463
|
const processing = {
|
|
439
464
|
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
440
|
-
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs)
|
|
465
|
+
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
466
|
+
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
441
467
|
};
|
|
442
468
|
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
443
469
|
const stats = {
|
|
@@ -1996,11 +2022,13 @@ function buildThreadParticipants(messages) {
|
|
|
1996
2022
|
return participants;
|
|
1997
2023
|
}
|
|
1998
2024
|
|
|
1999
|
-
// src/chat/
|
|
2025
|
+
// src/chat/state/turn-id.ts
|
|
2000
2026
|
function buildDeterministicTurnId(messageId) {
|
|
2001
2027
|
const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2002
2028
|
return `turn_${sanitized}`;
|
|
2003
2029
|
}
|
|
2030
|
+
|
|
2031
|
+
// src/chat/runtime/turn.ts
|
|
2004
2032
|
var RetryableTurnError = class extends Error {
|
|
2005
2033
|
code = "retryable_turn";
|
|
2006
2034
|
metadata;
|
|
@@ -2026,12 +2054,16 @@ function startActiveTurn(args) {
|
|
|
2026
2054
|
args.updateConversationStats(args.conversation);
|
|
2027
2055
|
}
|
|
2028
2056
|
function markTurnCompleted(args) {
|
|
2029
|
-
args.conversation.processing.activeTurnId
|
|
2057
|
+
if (!args.sessionId || args.conversation.processing.activeTurnId === args.sessionId) {
|
|
2058
|
+
args.conversation.processing.activeTurnId = void 0;
|
|
2059
|
+
}
|
|
2030
2060
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2031
2061
|
args.updateConversationStats(args.conversation);
|
|
2032
2062
|
}
|
|
2033
2063
|
function markTurnFailed(args) {
|
|
2034
|
-
args.conversation.processing.activeTurnId
|
|
2064
|
+
if (!args.sessionId || args.conversation.processing.activeTurnId === args.sessionId) {
|
|
2065
|
+
args.conversation.processing.activeTurnId = void 0;
|
|
2066
|
+
}
|
|
2035
2067
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2036
2068
|
args.markConversationMessage(args.conversation, args.userMessageId, {
|
|
2037
2069
|
replied: false,
|
|
@@ -2066,232 +2098,6 @@ function getTurnUserReplyAttachmentContext(message) {
|
|
|
2066
2098
|
};
|
|
2067
2099
|
}
|
|
2068
2100
|
|
|
2069
|
-
// src/chat/pi/client.ts
|
|
2070
|
-
import {
|
|
2071
|
-
completeSimple,
|
|
2072
|
-
getEnvApiKey,
|
|
2073
|
-
getModels,
|
|
2074
|
-
registerApiProvider
|
|
2075
|
-
} from "@mariozechner/pi-ai";
|
|
2076
|
-
import {
|
|
2077
|
-
streamAnthropic,
|
|
2078
|
-
streamSimpleAnthropic
|
|
2079
|
-
} from "@mariozechner/pi-ai/anthropic";
|
|
2080
|
-
registerApiProvider({
|
|
2081
|
-
api: "anthropic-messages",
|
|
2082
|
-
stream: streamAnthropic,
|
|
2083
|
-
streamSimple: streamSimpleAnthropic
|
|
2084
|
-
});
|
|
2085
|
-
var GATEWAY_PROVIDER = "vercel-ai-gateway";
|
|
2086
|
-
var GEN_AI_PROVIDER_NAME = GATEWAY_PROVIDER;
|
|
2087
|
-
var GEN_AI_OPERATION_CHAT = "chat";
|
|
2088
|
-
var MISSING_GATEWAY_CREDENTIALS_ERROR = "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)";
|
|
2089
|
-
function getGatewayApiKey() {
|
|
2090
|
-
return toOptionalTrimmed(getEnvApiKey("vercel-ai-gateway")) ?? toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
|
|
2091
|
-
}
|
|
2092
|
-
function getPiGatewayApiKeyOverride() {
|
|
2093
|
-
return toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
|
|
2094
|
-
}
|
|
2095
|
-
function extractText(message) {
|
|
2096
|
-
return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
|
|
2097
|
-
}
|
|
2098
|
-
function parseJsonCandidate(text) {
|
|
2099
|
-
const trimmed = text.trim();
|
|
2100
|
-
if (!trimmed) return void 0;
|
|
2101
|
-
try {
|
|
2102
|
-
return JSON.parse(trimmed);
|
|
2103
|
-
} catch {
|
|
2104
|
-
const fencedBlocks = [
|
|
2105
|
-
...trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)
|
|
2106
|
-
];
|
|
2107
|
-
for (const block of fencedBlocks) {
|
|
2108
|
-
try {
|
|
2109
|
-
return JSON.parse(block[1]);
|
|
2110
|
-
} catch {
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
const openBraceIndex = trimmed.indexOf("{");
|
|
2114
|
-
if (openBraceIndex >= 0) {
|
|
2115
|
-
let depth = 0;
|
|
2116
|
-
let inString = false;
|
|
2117
|
-
let escaped = false;
|
|
2118
|
-
for (let index = openBraceIndex; index < trimmed.length; index += 1) {
|
|
2119
|
-
const char = trimmed[index];
|
|
2120
|
-
if (inString) {
|
|
2121
|
-
if (escaped) {
|
|
2122
|
-
escaped = false;
|
|
2123
|
-
continue;
|
|
2124
|
-
}
|
|
2125
|
-
if (char === "\\") {
|
|
2126
|
-
escaped = true;
|
|
2127
|
-
continue;
|
|
2128
|
-
}
|
|
2129
|
-
if (char === '"') {
|
|
2130
|
-
inString = false;
|
|
2131
|
-
}
|
|
2132
|
-
continue;
|
|
2133
|
-
}
|
|
2134
|
-
if (char === '"') {
|
|
2135
|
-
inString = true;
|
|
2136
|
-
continue;
|
|
2137
|
-
}
|
|
2138
|
-
if (char === "{") {
|
|
2139
|
-
depth += 1;
|
|
2140
|
-
continue;
|
|
2141
|
-
}
|
|
2142
|
-
if (char === "}") {
|
|
2143
|
-
depth -= 1;
|
|
2144
|
-
if (depth === 0) {
|
|
2145
|
-
const slice = trimmed.slice(openBraceIndex, index + 1);
|
|
2146
|
-
try {
|
|
2147
|
-
return JSON.parse(slice);
|
|
2148
|
-
} catch {
|
|
2149
|
-
break;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
return void 0;
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
function resolveGatewayModel(modelId) {
|
|
2159
|
-
const models = getModels(GATEWAY_PROVIDER);
|
|
2160
|
-
const matched = models.find((model) => model.id === modelId);
|
|
2161
|
-
if (!matched) {
|
|
2162
|
-
throw new Error(`Unknown AI Gateway model id: ${modelId}`);
|
|
2163
|
-
}
|
|
2164
|
-
return matched;
|
|
2165
|
-
}
|
|
2166
|
-
async function completeText(params) {
|
|
2167
|
-
const model = resolveGatewayModel(params.modelId);
|
|
2168
|
-
const apiKey = getPiGatewayApiKeyOverride();
|
|
2169
|
-
const requestMessagesAttribute = serializeGenAiAttribute(params.messages);
|
|
2170
|
-
const systemInstructionsAttribute = params.system ? serializeGenAiAttribute([{ type: "text", content: params.system }]) : void 0;
|
|
2171
|
-
const startAttributes = {
|
|
2172
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
2173
|
-
"gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
|
|
2174
|
-
"gen_ai.request.model": params.modelId,
|
|
2175
|
-
...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
|
|
2176
|
-
...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
|
|
2177
|
-
"app.ai.auth_mode": apiKey ? "oidc" : "api_key",
|
|
2178
|
-
...params.thinkingLevel ? { "app.ai.reasoning_effort": params.thinkingLevel } : {}
|
|
2179
|
-
};
|
|
2180
|
-
setSpanAttributes(startAttributes);
|
|
2181
|
-
const message = await completeSimple(
|
|
2182
|
-
model,
|
|
2183
|
-
{
|
|
2184
|
-
systemPrompt: params.system,
|
|
2185
|
-
messages: params.messages
|
|
2186
|
-
},
|
|
2187
|
-
{
|
|
2188
|
-
...apiKey ? { apiKey } : {},
|
|
2189
|
-
temperature: params.temperature,
|
|
2190
|
-
maxTokens: params.maxTokens,
|
|
2191
|
-
reasoning: params.thinkingLevel,
|
|
2192
|
-
signal: params.signal,
|
|
2193
|
-
metadata: params.metadata
|
|
2194
|
-
}
|
|
2195
|
-
);
|
|
2196
|
-
const outputText = extractText(message);
|
|
2197
|
-
const outputMessagesAttribute = serializeGenAiAttribute([
|
|
2198
|
-
{
|
|
2199
|
-
role: "assistant",
|
|
2200
|
-
content: outputText ? [{ type: "text", text: outputText }] : []
|
|
2201
|
-
}
|
|
2202
|
-
]);
|
|
2203
|
-
const usageAttributes = extractGenAiUsageAttributes(message);
|
|
2204
|
-
const endAttributes = {
|
|
2205
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
2206
|
-
"gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
|
|
2207
|
-
"gen_ai.request.model": params.modelId,
|
|
2208
|
-
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
2209
|
-
...usageAttributes,
|
|
2210
|
-
...message.stopReason ? { "gen_ai.response.finish_reasons": [message.stopReason] } : {},
|
|
2211
|
-
...params.thinkingLevel ? { "app.ai.reasoning_effort": params.thinkingLevel } : {}
|
|
2212
|
-
};
|
|
2213
|
-
setSpanAttributes(endAttributes);
|
|
2214
|
-
if (message.stopReason === "error") {
|
|
2215
|
-
const providerMessage = message.errorMessage?.trim() || "Unknown provider error";
|
|
2216
|
-
logWarn(
|
|
2217
|
-
"ai_completion_provider_error",
|
|
2218
|
-
{},
|
|
2219
|
-
{
|
|
2220
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
2221
|
-
"gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
|
|
2222
|
-
"gen_ai.request.model": params.modelId,
|
|
2223
|
-
"error.message": providerMessage
|
|
2224
|
-
},
|
|
2225
|
-
"AI completion returned provider error"
|
|
2226
|
-
);
|
|
2227
|
-
throw new Error(`AI provider error: ${providerMessage}`);
|
|
2228
|
-
}
|
|
2229
|
-
return {
|
|
2230
|
-
message,
|
|
2231
|
-
text: outputText
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
async function completeObject(params) {
|
|
2235
|
-
const startedAt = Date.now();
|
|
2236
|
-
let text = "";
|
|
2237
|
-
try {
|
|
2238
|
-
({ text } = await completeText({
|
|
2239
|
-
modelId: params.modelId,
|
|
2240
|
-
system: params.system,
|
|
2241
|
-
thinkingLevel: params.thinkingLevel,
|
|
2242
|
-
temperature: params.temperature,
|
|
2243
|
-
maxTokens: params.maxTokens,
|
|
2244
|
-
signal: params.signal,
|
|
2245
|
-
metadata: params.metadata,
|
|
2246
|
-
messages: [
|
|
2247
|
-
{
|
|
2248
|
-
role: "user",
|
|
2249
|
-
content: params.prompt,
|
|
2250
|
-
timestamp: Date.now()
|
|
2251
|
-
}
|
|
2252
|
-
]
|
|
2253
|
-
}));
|
|
2254
|
-
} catch (error) {
|
|
2255
|
-
logException(
|
|
2256
|
-
error,
|
|
2257
|
-
"ai_completion_failed",
|
|
2258
|
-
{},
|
|
2259
|
-
{
|
|
2260
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
2261
|
-
"gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
|
|
2262
|
-
"gen_ai.request.model": params.modelId,
|
|
2263
|
-
"app.ai.duration_ms": Date.now() - startedAt
|
|
2264
|
-
},
|
|
2265
|
-
"AI object completion failed"
|
|
2266
|
-
);
|
|
2267
|
-
throw error;
|
|
2268
|
-
}
|
|
2269
|
-
const candidate = parseJsonCandidate(text);
|
|
2270
|
-
const parsed = params.schema.safeParse(candidate);
|
|
2271
|
-
if (!parsed.success) {
|
|
2272
|
-
const preview = text.length > 400 ? `${text.slice(0, 400)}...` : text;
|
|
2273
|
-
logWarn(
|
|
2274
|
-
"ai_completion_schema_parse_failed",
|
|
2275
|
-
{},
|
|
2276
|
-
{
|
|
2277
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
2278
|
-
"gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
|
|
2279
|
-
"gen_ai.request.model": params.modelId,
|
|
2280
|
-
"app.ai.duration_ms": Date.now() - startedAt,
|
|
2281
|
-
"app.ai.response_preview": preview
|
|
2282
|
-
},
|
|
2283
|
-
"AI object completion schema parse failed"
|
|
2284
|
-
);
|
|
2285
|
-
throw new Error(
|
|
2286
|
-
`Model did not return valid JSON for schema: ${parsed.error.message}. Raw response: ${preview}`
|
|
2287
|
-
);
|
|
2288
|
-
}
|
|
2289
|
-
return {
|
|
2290
|
-
object: parsed.data,
|
|
2291
|
-
text
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
2101
|
// src/chat/slack/message.ts
|
|
2296
2102
|
function getSlackMessageTs(message) {
|
|
2297
2103
|
if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
|
|
@@ -5071,11 +4877,11 @@ function createReadFileTool() {
|
|
|
5071
4877
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
5072
4878
|
function createReportProgressTool() {
|
|
5073
4879
|
return tool({
|
|
5074
|
-
description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes.
|
|
4880
|
+
description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
|
|
5075
4881
|
inputSchema: Type6.Object({
|
|
5076
4882
|
message: Type6.String({
|
|
5077
4883
|
minLength: 1,
|
|
5078
|
-
description: "Short user-facing progress message.
|
|
4884
|
+
description: "Short user-facing progress message."
|
|
5079
4885
|
})
|
|
5080
4886
|
})
|
|
5081
4887
|
});
|
|
@@ -8442,134 +8248,21 @@ function shouldEmitDevAgentTrace() {
|
|
|
8442
8248
|
return process.env.NODE_ENV === "development";
|
|
8443
8249
|
}
|
|
8444
8250
|
|
|
8445
|
-
// src/chat/
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
8450
|
-
deleteMcpServerSessionId(userId, provider),
|
|
8451
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
8452
|
-
]);
|
|
8453
|
-
}
|
|
8454
|
-
|
|
8455
|
-
// src/chat/services/plugin-auth-orchestration.ts
|
|
8456
|
-
var PluginAuthorizationPauseError = class extends Error {
|
|
8251
|
+
// src/chat/services/auth-pause.ts
|
|
8252
|
+
var AuthorizationPauseError = class extends Error {
|
|
8253
|
+
disposition;
|
|
8254
|
+
kind;
|
|
8457
8255
|
provider;
|
|
8458
|
-
constructor(provider) {
|
|
8459
|
-
super(
|
|
8460
|
-
|
|
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;
|
|
8461
8263
|
this.provider = provider;
|
|
8462
8264
|
}
|
|
8463
8265
|
};
|
|
8464
|
-
function isCommandAuthFailure(details) {
|
|
8465
|
-
if (!details || typeof details !== "object") {
|
|
8466
|
-
return false;
|
|
8467
|
-
}
|
|
8468
|
-
const result = details;
|
|
8469
|
-
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
8470
|
-
return false;
|
|
8471
|
-
}
|
|
8472
|
-
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
8473
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
8474
|
-
if (!text.trim()) {
|
|
8475
|
-
return false;
|
|
8476
|
-
}
|
|
8477
|
-
return [
|
|
8478
|
-
/\b401\b/,
|
|
8479
|
-
/\bunauthorized\b/,
|
|
8480
|
-
/\bbad credentials\b/,
|
|
8481
|
-
/\binvalid token\b/,
|
|
8482
|
-
/\btoken (?:expired|revoked)\b/,
|
|
8483
|
-
/\bexpired token\b/,
|
|
8484
|
-
/\bmissing scopes?\b/,
|
|
8485
|
-
/\binsufficient scope\b/,
|
|
8486
|
-
/\binvalid grant\b/,
|
|
8487
|
-
/\breauthoriz/
|
|
8488
|
-
].some((pattern) => pattern.test(text));
|
|
8489
|
-
}
|
|
8490
|
-
function commandTargetsProvider(provider, command, details) {
|
|
8491
|
-
const normalizedCommand = command.trim().toLowerCase();
|
|
8492
|
-
if (!normalizedCommand) {
|
|
8493
|
-
return false;
|
|
8494
|
-
}
|
|
8495
|
-
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
8496
|
-
return true;
|
|
8497
|
-
}
|
|
8498
|
-
const plugin = getPluginDefinition(provider);
|
|
8499
|
-
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
8500
|
-
const credentials = plugin?.manifest.credentials;
|
|
8501
|
-
if (credentials) {
|
|
8502
|
-
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
8503
|
-
for (const domain of credentials.apiDomains) {
|
|
8504
|
-
candidates.add(domain.toLowerCase());
|
|
8505
|
-
}
|
|
8506
|
-
}
|
|
8507
|
-
const combinedText = `${normalizedCommand}
|
|
8508
|
-
${details.stdout?.toLowerCase() ?? ""}
|
|
8509
|
-
${details.stderr?.toLowerCase() ?? ""}`;
|
|
8510
|
-
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
8511
|
-
}
|
|
8512
|
-
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
8513
|
-
let pendingPause;
|
|
8514
|
-
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
8515
|
-
if (pendingPause) {
|
|
8516
|
-
throw pendingPause;
|
|
8517
|
-
}
|
|
8518
|
-
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
8519
|
-
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
8520
|
-
}
|
|
8521
|
-
const providerLabel = formatProviderLabel(provider);
|
|
8522
|
-
const oauthResult = await startOAuthFlow(provider, {
|
|
8523
|
-
requesterId: deps.requesterId,
|
|
8524
|
-
channelId: deps.channelId,
|
|
8525
|
-
threadTs: deps.threadTs,
|
|
8526
|
-
userMessage: deps.userMessage,
|
|
8527
|
-
channelConfiguration: deps.channelConfiguration,
|
|
8528
|
-
activeSkillName: activeSkill?.name ?? void 0,
|
|
8529
|
-
resumeConversationId: deps.conversationId,
|
|
8530
|
-
resumeSessionId: deps.sessionId
|
|
8531
|
-
});
|
|
8532
|
-
if (!oauthResult.ok) {
|
|
8533
|
-
throw new Error(oauthResult.error);
|
|
8534
|
-
}
|
|
8535
|
-
if (!oauthResult.delivery) {
|
|
8536
|
-
throw new Error(
|
|
8537
|
-
`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.`
|
|
8538
|
-
);
|
|
8539
|
-
}
|
|
8540
|
-
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
8541
|
-
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
8542
|
-
}
|
|
8543
|
-
pendingPause = new PluginAuthorizationPauseError(provider);
|
|
8544
|
-
abortAgent();
|
|
8545
|
-
throw pendingPause;
|
|
8546
|
-
};
|
|
8547
|
-
const handleCredentialUnavailable = async (input) => {
|
|
8548
|
-
if (pendingPause) {
|
|
8549
|
-
throw pendingPause;
|
|
8550
|
-
}
|
|
8551
|
-
if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
|
|
8552
|
-
throw input.error;
|
|
8553
|
-
}
|
|
8554
|
-
return await startAuthorizationPause(
|
|
8555
|
-
input.error.provider,
|
|
8556
|
-
input.activeSkill
|
|
8557
|
-
);
|
|
8558
|
-
};
|
|
8559
|
-
return {
|
|
8560
|
-
handleCredentialUnavailable,
|
|
8561
|
-
handleCommandFailure: async (input) => {
|
|
8562
|
-
const provider = input.activeSkill?.pluginProvider;
|
|
8563
|
-
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
|
|
8564
|
-
return;
|
|
8565
|
-
}
|
|
8566
|
-
await startAuthorizationPause(provider, input.activeSkill, {
|
|
8567
|
-
unlinkExistingProvider: true
|
|
8568
|
-
});
|
|
8569
|
-
},
|
|
8570
|
-
getPendingPause: () => pendingPause
|
|
8571
|
-
};
|
|
8572
|
-
}
|
|
8573
8266
|
|
|
8574
8267
|
// src/chat/runtime/report-progress.ts
|
|
8575
8268
|
function buildReportedProgressStatus(input) {
|
|
@@ -8617,15 +8310,16 @@ function resolveCredentialInjection(toolName, command, capabilityRuntime, sandbo
|
|
|
8617
8310
|
const headerDomains = (headerTransforms ?? []).map(
|
|
8618
8311
|
(transform) => transform.domain
|
|
8619
8312
|
);
|
|
8313
|
+
const skillName = sandbox.getActiveSkill()?.name;
|
|
8620
8314
|
logInfo(
|
|
8621
8315
|
"credential_inject_start",
|
|
8622
8316
|
{},
|
|
8623
8317
|
{
|
|
8624
|
-
"app.skill.name":
|
|
8318
|
+
"app.skill.name": skillName,
|
|
8625
8319
|
"app.credential.delivery": "header_transform",
|
|
8626
8320
|
"app.credential.header_domains": headerDomains
|
|
8627
8321
|
},
|
|
8628
|
-
|
|
8322
|
+
`Injecting scoped credential headers for sandbox command (${skillName ?? "unknown skill"} \u2192 ${headerDomains.join(", ")})`
|
|
8629
8323
|
);
|
|
8630
8324
|
}
|
|
8631
8325
|
return { headerTransforms, env };
|
|
@@ -8798,7 +8492,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8798
8492
|
}
|
|
8799
8493
|
return normalized;
|
|
8800
8494
|
} catch (error) {
|
|
8801
|
-
if (error instanceof
|
|
8495
|
+
if (error instanceof AuthorizationPauseError) {
|
|
8802
8496
|
throw error;
|
|
8803
8497
|
}
|
|
8804
8498
|
handleToolExecutionError(
|
|
@@ -8846,7 +8540,7 @@ function isExecutionEscapeResponse(text) {
|
|
|
8846
8540
|
if (!trimmed) return false;
|
|
8847
8541
|
return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
|
|
8848
8542
|
}
|
|
8849
|
-
function
|
|
8543
|
+
function parseJsonCandidate(text) {
|
|
8850
8544
|
const trimmed = text.trim();
|
|
8851
8545
|
if (!trimmed) return void 0;
|
|
8852
8546
|
try {
|
|
@@ -8874,7 +8568,7 @@ function isToolPayloadShape(payload) {
|
|
|
8874
8568
|
return false;
|
|
8875
8569
|
}
|
|
8876
8570
|
function isRawToolPayloadResponse(text) {
|
|
8877
|
-
const parsed =
|
|
8571
|
+
const parsed = parseJsonCandidate(text);
|
|
8878
8572
|
if (Array.isArray(parsed)) {
|
|
8879
8573
|
return parsed.some((entry) => isToolPayloadShape(entry));
|
|
8880
8574
|
}
|
|
@@ -9009,14 +8703,6 @@ function getTerminalAssistantMessages(messages) {
|
|
|
9009
8703
|
}
|
|
9010
8704
|
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
9011
8705
|
}
|
|
9012
|
-
function hasCompletedAssistantTurn(messages) {
|
|
9013
|
-
const message = getTerminalAssistantMessages(messages).at(-1);
|
|
9014
|
-
if (!message) {
|
|
9015
|
-
return false;
|
|
9016
|
-
}
|
|
9017
|
-
const stopReason = message.stopReason;
|
|
9018
|
-
return typeof stopReason === "string" && stopReason !== "error" && extractAssistantText(message).trim().length > 0;
|
|
9019
|
-
}
|
|
9020
8706
|
function upsertActiveSkill(activeSkills, next) {
|
|
9021
8707
|
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
9022
8708
|
if (existing) {
|
|
@@ -9244,7 +8930,10 @@ function buildTurnResult(input) {
|
|
|
9244
8930
|
// src/chat/services/turn-thinking-level.ts
|
|
9245
8931
|
import { z } from "zod";
|
|
9246
8932
|
var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
|
|
9247
|
-
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";
|
|
9248
8937
|
var TURN_THINKING_LEVELS = ["none", "low", "medium", "high"];
|
|
9249
8938
|
var turnExecutionProfileSchema = z.object({
|
|
9250
8939
|
thinking_level: z.enum(TURN_THINKING_LEVELS),
|
|
@@ -9255,9 +8944,22 @@ var DEFAULT_THINKING_LEVEL = "low";
|
|
|
9255
8944
|
function trimContextForRouter(text) {
|
|
9256
8945
|
const trimmed = text?.trim();
|
|
9257
8946
|
if (!trimmed) {
|
|
9258
|
-
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
|
+
};
|
|
9259
8955
|
}
|
|
9260
|
-
|
|
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
|
+
};
|
|
9261
8963
|
}
|
|
9262
8964
|
function buildClassifierSystemPrompt() {
|
|
9263
8965
|
return [
|
|
@@ -9269,22 +8971,23 @@ function buildClassifierSystemPrompt() {
|
|
|
9269
8971
|
"Use medium for investigations, ambiguous asks, multi-step analysis, or likely multi-tool work.",
|
|
9270
8972
|
"Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
|
|
9271
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
|
+
"",
|
|
9272
8976
|
"Return JSON only with thinking_level, confidence, and reason."
|
|
9273
8977
|
].join("\n");
|
|
9274
8978
|
}
|
|
9275
8979
|
function buildClassifierPrompt(args) {
|
|
9276
8980
|
const sections = [];
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
8981
|
+
if (args.conversationContext) {
|
|
8982
|
+
sections.push(
|
|
8983
|
+
"<thread-background>",
|
|
8984
|
+
args.conversationContext.text,
|
|
8985
|
+
"</thread-background>",
|
|
8986
|
+
""
|
|
8987
|
+
);
|
|
9280
8988
|
}
|
|
9281
8989
|
sections.push(
|
|
9282
|
-
"<
|
|
9283
|
-
`- active_skills: ${args.activeSkillNames.join(", ") || "none"}`,
|
|
9284
|
-
`- attachment_count: ${args.attachmentCount}`,
|
|
9285
|
-
"</turn-context>",
|
|
9286
|
-
"",
|
|
9287
|
-
'<current-instruction priority="highest">',
|
|
8990
|
+
"<current-instruction>",
|
|
9288
8991
|
args.messageText.trim() || "[empty]",
|
|
9289
8992
|
"</current-instruction>"
|
|
9290
8993
|
);
|
|
@@ -9298,42 +9001,81 @@ function buildClassifierPrompt(args) {
|
|
|
9298
9001
|
return sections.join("\n");
|
|
9299
9002
|
}
|
|
9300
9003
|
async function selectTurnThinkingLevel(args) {
|
|
9301
|
-
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) {
|
|
9302
9055
|
try {
|
|
9303
9056
|
const result = await args.completeObject({
|
|
9304
9057
|
modelId: args.fastModelId,
|
|
9305
9058
|
schema: turnExecutionProfileSchema,
|
|
9306
9059
|
maxTokens: 120,
|
|
9307
|
-
metadata:
|
|
9308
|
-
|
|
9309
|
-
threadId: args.context?.threadId ?? "",
|
|
9310
|
-
channelId: args.context?.channelId ?? "",
|
|
9311
|
-
requesterId: args.context?.requesterId ?? "",
|
|
9312
|
-
runId: args.context?.runId ?? ""
|
|
9313
|
-
},
|
|
9314
|
-
prompt: buildClassifierPrompt({
|
|
9315
|
-
activeSkillNames,
|
|
9316
|
-
attachmentCount: args.attachmentCount ?? 0,
|
|
9317
|
-
conversationContext: args.conversationContext,
|
|
9318
|
-
currentTurnBlocks: args.currentTurnBlocks,
|
|
9319
|
-
messageText: args.messageText
|
|
9320
|
-
}),
|
|
9060
|
+
metadata: args.metadata,
|
|
9061
|
+
prompt: args.prompt,
|
|
9321
9062
|
thinkingLevel: "low",
|
|
9322
9063
|
system: buildClassifierSystemPrompt(),
|
|
9323
9064
|
temperature: 0
|
|
9324
9065
|
});
|
|
9325
9066
|
const parsed = turnExecutionProfileSchema.parse(result.object);
|
|
9067
|
+
const reason = parsed.reason.trim();
|
|
9326
9068
|
if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
|
|
9327
9069
|
return {
|
|
9328
9070
|
confidence: parsed.confidence,
|
|
9329
9071
|
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
9330
|
-
reason: `low_confidence_default:${
|
|
9072
|
+
reason: `low_confidence_default:${reason}`
|
|
9331
9073
|
};
|
|
9332
9074
|
}
|
|
9333
9075
|
return {
|
|
9334
9076
|
confidence: parsed.confidence,
|
|
9335
9077
|
thinkingLevel: parsed.thinking_level,
|
|
9336
|
-
reason
|
|
9078
|
+
reason
|
|
9337
9079
|
};
|
|
9338
9080
|
} catch {
|
|
9339
9081
|
return {
|
|
@@ -9371,7 +9113,7 @@ function parseAgentTurnSessionCheckpoint(value) {
|
|
|
9371
9113
|
return void 0;
|
|
9372
9114
|
}
|
|
9373
9115
|
const status = parsed.state;
|
|
9374
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
|
|
9116
|
+
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
9375
9117
|
return void 0;
|
|
9376
9118
|
}
|
|
9377
9119
|
const conversationId = parsed.conversationId;
|
|
@@ -9443,6 +9185,26 @@ async function upsertAgentTurnSessionCheckpoint(args) {
|
|
|
9443
9185
|
);
|
|
9444
9186
|
return checkpoint;
|
|
9445
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
|
+
}
|
|
9446
9208
|
|
|
9447
9209
|
// src/chat/services/turn-checkpoint.ts
|
|
9448
9210
|
async function loadTurnCheckpoint(ctx) {
|
|
@@ -9557,17 +9319,71 @@ async function persistTimeoutCheckpoint(args) {
|
|
|
9557
9319
|
}
|
|
9558
9320
|
}
|
|
9559
9321
|
|
|
9560
|
-
// src/chat/services/
|
|
9561
|
-
var
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
this.provider = provider;
|
|
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;
|
|
9567
9328
|
}
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
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
|
+
|
|
9379
|
+
// src/chat/services/mcp-auth-orchestration.ts
|
|
9380
|
+
var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
9381
|
+
constructor(provider, disposition) {
|
|
9382
|
+
super("mcp", provider, disposition);
|
|
9383
|
+
}
|
|
9384
|
+
};
|
|
9385
|
+
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
9386
|
+
let pendingPause;
|
|
9571
9387
|
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
9572
9388
|
const authProviderFactory = async (plugin) => {
|
|
9573
9389
|
if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
|
|
@@ -9608,18 +9424,40 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
9608
9424
|
if (!authSession?.authorizationUrl) {
|
|
9609
9425
|
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
9610
9426
|
}
|
|
9611
|
-
const
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9427
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
9428
|
+
pendingAuth: deps.currentPendingAuth,
|
|
9429
|
+
kind: "mcp",
|
|
9430
|
+
provider,
|
|
9431
|
+
requesterId: deps.requesterId
|
|
9616
9432
|
});
|
|
9617
|
-
if (!
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
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);
|
|
9621
9447
|
}
|
|
9622
|
-
|
|
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
|
+
});
|
|
9456
|
+
}
|
|
9457
|
+
pendingPause = new McpAuthorizationPauseError(
|
|
9458
|
+
provider,
|
|
9459
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
9460
|
+
);
|
|
9623
9461
|
abortAgent();
|
|
9624
9462
|
return true;
|
|
9625
9463
|
};
|
|
@@ -9630,6 +9468,152 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
9630
9468
|
};
|
|
9631
9469
|
}
|
|
9632
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
|
+
|
|
9633
9617
|
// src/chat/respond.ts
|
|
9634
9618
|
var startupDiscoveryLogged = false;
|
|
9635
9619
|
var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
|
|
@@ -9746,6 +9730,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9746
9730
|
let timeoutResumeSessionId;
|
|
9747
9731
|
let timeoutResumeSliceId = 1;
|
|
9748
9732
|
let timeoutResumeMessages = [];
|
|
9733
|
+
let beforeMessageCount = 0;
|
|
9749
9734
|
let lastKnownSandboxId = context.sandbox?.sandboxId;
|
|
9750
9735
|
let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
|
|
9751
9736
|
let loadedSkillNamesForResume = [];
|
|
@@ -9938,8 +9923,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9938
9923
|
userTurnText
|
|
9939
9924
|
});
|
|
9940
9925
|
thinkingSelection = await selectTurnThinkingLevel({
|
|
9941
|
-
activeSkillNames: activeSkills.map((skill) => skill.name),
|
|
9942
|
-
attachmentCount: context.userAttachments?.length,
|
|
9943
9926
|
completeObject,
|
|
9944
9927
|
conversationContext: context.conversationContext,
|
|
9945
9928
|
context: {
|
|
@@ -9975,9 +9958,11 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9975
9958
|
threadTs: context.correlation?.threadTs,
|
|
9976
9959
|
toolChannelId: context.toolChannelId,
|
|
9977
9960
|
userMessage: userInput,
|
|
9961
|
+
currentPendingAuth: context.pendingAuth,
|
|
9978
9962
|
getConfiguration: () => configurationValues,
|
|
9979
9963
|
getArtifactState: () => context.artifactState,
|
|
9980
|
-
getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch)
|
|
9964
|
+
getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch),
|
|
9965
|
+
onPendingAuth: context.onAuthPending
|
|
9981
9966
|
},
|
|
9982
9967
|
() => agent?.abort()
|
|
9983
9968
|
);
|
|
@@ -9990,6 +9975,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9990
9975
|
threadTs: context.correlation?.threadTs,
|
|
9991
9976
|
userMessage: userInput,
|
|
9992
9977
|
channelConfiguration: context.channelConfiguration,
|
|
9978
|
+
currentPendingAuth: context.pendingAuth,
|
|
9979
|
+
onPendingAuth: context.onAuthPending,
|
|
9993
9980
|
userTokenStore
|
|
9994
9981
|
},
|
|
9995
9982
|
() => agent?.abort()
|
|
@@ -10208,12 +10195,11 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10208
10195
|
);
|
|
10209
10196
|
});
|
|
10210
10197
|
});
|
|
10211
|
-
let beforeMessageCount = agent.state.messages.length;
|
|
10212
10198
|
let newMessages = [];
|
|
10213
|
-
|
|
10199
|
+
beforeMessageCount = agent.state.messages.length;
|
|
10214
10200
|
try {
|
|
10215
10201
|
if (resumedFromCheckpoint) {
|
|
10216
|
-
agent.
|
|
10202
|
+
agent.state.messages = existingCheckpoint.piMessages;
|
|
10217
10203
|
}
|
|
10218
10204
|
beforeMessageCount = agent.state.messages.length;
|
|
10219
10205
|
await withSpan(
|
|
@@ -10277,11 +10263,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10277
10263
|
}
|
|
10278
10264
|
}
|
|
10279
10265
|
newMessages = agent.state.messages.slice(beforeMessageCount);
|
|
10280
|
-
completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
|
|
10281
|
-
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10282
|
-
timeoutResumeMessages = [...agent.state.messages];
|
|
10283
|
-
throw getPendingAuthPause();
|
|
10284
|
-
}
|
|
10285
10266
|
const outputMessages = newMessages.filter(isAssistantMessage);
|
|
10286
10267
|
const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
|
|
10287
10268
|
const usageSummary = extractGenAiUsageSummary(
|
|
@@ -10297,6 +10278,10 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10297
10278
|
...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
|
|
10298
10279
|
...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
|
|
10299
10280
|
});
|
|
10281
|
+
if (getPendingAuthPause()) {
|
|
10282
|
+
timeoutResumeMessages = [...agent.state.messages];
|
|
10283
|
+
throw getPendingAuthPause();
|
|
10284
|
+
}
|
|
10300
10285
|
},
|
|
10301
10286
|
{
|
|
10302
10287
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
@@ -10309,9 +10294,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10309
10294
|
} finally {
|
|
10310
10295
|
unsubscribe();
|
|
10311
10296
|
}
|
|
10312
|
-
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10313
|
-
throw getPendingAuthPause();
|
|
10314
|
-
}
|
|
10315
10297
|
if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
10316
10298
|
await persistCompletedCheckpoint({
|
|
10317
10299
|
conversationId: sessionConversationId,
|
|
@@ -10369,7 +10351,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10369
10351
|
);
|
|
10370
10352
|
}
|
|
10371
10353
|
}
|
|
10372
|
-
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
|
+
}
|
|
10373
10363
|
const nextSliceId = await persistAuthPauseCheckpoint({
|
|
10374
10364
|
conversationId: timeoutResumeConversationId,
|
|
10375
10365
|
sessionId: timeoutResumeSessionId,
|
|
@@ -10387,9 +10377,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10387
10377
|
}
|
|
10388
10378
|
});
|
|
10389
10379
|
throw new RetryableTurnError(
|
|
10390
|
-
error
|
|
10380
|
+
error.kind === "plugin" ? "plugin_auth_resume" : "mcp_auth_resume",
|
|
10391
10381
|
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`,
|
|
10392
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,
|
|
10393
10389
|
conversationId: timeoutResumeConversationId,
|
|
10394
10390
|
sessionId: timeoutResumeSessionId,
|
|
10395
10391
|
sliceId: nextSliceId
|
|
@@ -10862,11 +10858,10 @@ function buildSlackReplyFooter(args) {
|
|
|
10862
10858
|
value: formatSlackDuration(durationMs)
|
|
10863
10859
|
});
|
|
10864
10860
|
}
|
|
10865
|
-
|
|
10866
|
-
if (traceId) {
|
|
10861
|
+
if (args.thinkingLevel) {
|
|
10867
10862
|
items.push({
|
|
10868
|
-
label: "
|
|
10869
|
-
value:
|
|
10863
|
+
label: "Thinking",
|
|
10864
|
+
value: args.thinkingLevel
|
|
10870
10865
|
});
|
|
10871
10866
|
}
|
|
10872
10867
|
return items.length > 0 ? { items } : void 0;
|
|
@@ -11189,7 +11184,7 @@ async function resumeSlackTurn(args) {
|
|
|
11189
11184
|
const footer = buildSlackReplyFooter({
|
|
11190
11185
|
conversationId: args.replyContext?.correlation?.conversationId ?? lockKey,
|
|
11191
11186
|
durationMs: reply.diagnostics.durationMs,
|
|
11192
|
-
|
|
11187
|
+
thinkingLevel: reply.diagnostics.thinkingLevel,
|
|
11193
11188
|
usage: reply.diagnostics.usage
|
|
11194
11189
|
});
|
|
11195
11190
|
await postSlackApiReplyPosts({
|
|
@@ -11263,6 +11258,82 @@ async function resumeAuthorizedRequest(args) {
|
|
|
11263
11258
|
});
|
|
11264
11259
|
}
|
|
11265
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
|
+
|
|
11266
11337
|
// src/chat/services/timeout-resume.ts
|
|
11267
11338
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
11268
11339
|
var TURN_TIMEOUT_RESUME_PATH = "/api/internal/turn-resume";
|
|
@@ -11435,6 +11506,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11435
11506
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11436
11507
|
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : void 0;
|
|
11437
11508
|
const userMessageId = getTurnUserMessageId(conversation, sessionId);
|
|
11509
|
+
clearPendingAuth(conversation, sessionId);
|
|
11438
11510
|
markConversationMessage(conversation, userMessageId, {
|
|
11439
11511
|
replied: true,
|
|
11440
11512
|
skippedReason: void 0
|
|
@@ -11455,6 +11527,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11455
11527
|
markTurnCompleted({
|
|
11456
11528
|
conversation,
|
|
11457
11529
|
nowMs: Date.now(),
|
|
11530
|
+
sessionId,
|
|
11458
11531
|
updateConversationStats
|
|
11459
11532
|
});
|
|
11460
11533
|
await persistThreadStateById(threadId, {
|
|
@@ -11468,9 +11541,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
11468
11541
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
11469
11542
|
const currentState = await getPersistedThreadState(threadId);
|
|
11470
11543
|
const conversation = coerceThreadConversationState(currentState);
|
|
11544
|
+
clearPendingAuth(conversation, sessionId);
|
|
11471
11545
|
markTurnFailed({
|
|
11472
11546
|
conversation,
|
|
11473
11547
|
nowMs: Date.now(),
|
|
11548
|
+
sessionId,
|
|
11474
11549
|
userMessageId: getTurnUserMessageId(conversation, sessionId),
|
|
11475
11550
|
markConversationMessage,
|
|
11476
11551
|
updateConversationStats
|
|
@@ -11488,8 +11563,29 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11488
11563
|
const currentState = await getPersistedThreadState(threadId);
|
|
11489
11564
|
const conversation = coerceThreadConversationState(currentState);
|
|
11490
11565
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11491
|
-
const
|
|
11492
|
-
|
|
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) {
|
|
11493
11589
|
return;
|
|
11494
11590
|
}
|
|
11495
11591
|
const channelConfiguration = getChannelConfigurationServiceById(
|
|
@@ -11498,14 +11594,14 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11498
11594
|
const conversationContext = await buildResumeConversationContext(
|
|
11499
11595
|
authSession.channelId,
|
|
11500
11596
|
authSession.threadTs,
|
|
11501
|
-
|
|
11597
|
+
resolvedSessionId
|
|
11502
11598
|
);
|
|
11503
11599
|
await resumeAuthorizedRequest({
|
|
11504
|
-
messageText:
|
|
11600
|
+
messageText: userMessage.text,
|
|
11505
11601
|
channelId: authSession.channelId,
|
|
11506
11602
|
threadTs: authSession.threadTs,
|
|
11507
11603
|
lockKey: authSession.conversationId,
|
|
11508
|
-
connectedText:
|
|
11604
|
+
connectedText: "",
|
|
11509
11605
|
failureText: "MCP authorization completed, but resuming the request failed. Please retry the original command.",
|
|
11510
11606
|
replyContext: {
|
|
11511
11607
|
assistant: { userName: botConfig.userName },
|
|
@@ -11516,7 +11612,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11516
11612
|
},
|
|
11517
11613
|
correlation: {
|
|
11518
11614
|
conversationId: authSession.conversationId,
|
|
11519
|
-
turnId:
|
|
11615
|
+
turnId: resolvedSessionId,
|
|
11520
11616
|
channelId: authSession.channelId,
|
|
11521
11617
|
threadTs: authSession.threadTs,
|
|
11522
11618
|
requesterId: authSession.userId
|
|
@@ -11525,9 +11621,18 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11525
11621
|
conversationContext,
|
|
11526
11622
|
artifactState: artifacts,
|
|
11527
11623
|
configuration: authSession.configuration,
|
|
11624
|
+
pendingAuth,
|
|
11528
11625
|
channelConfiguration,
|
|
11529
11626
|
sandbox: getPersistedSandboxState(currentState),
|
|
11530
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
|
+
},
|
|
11531
11636
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11532
11637
|
},
|
|
11533
11638
|
onSuccess: async (reply) => {
|
|
@@ -11535,7 +11640,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11535
11640
|
await persistCompletedReplyState(
|
|
11536
11641
|
authSession.channelId,
|
|
11537
11642
|
authSession.threadTs,
|
|
11538
|
-
|
|
11643
|
+
resolvedSessionId,
|
|
11539
11644
|
reply
|
|
11540
11645
|
);
|
|
11541
11646
|
} catch (persistError) {
|
|
@@ -11560,7 +11665,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11560
11665
|
await persistFailedReplyState(
|
|
11561
11666
|
authSession.channelId,
|
|
11562
11667
|
authSession.threadTs,
|
|
11563
|
-
|
|
11668
|
+
resolvedSessionId
|
|
11564
11669
|
);
|
|
11565
11670
|
} catch (persistError) {
|
|
11566
11671
|
logException(
|
|
@@ -11572,7 +11677,16 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11572
11677
|
);
|
|
11573
11678
|
}
|
|
11574
11679
|
},
|
|
11575
|
-
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
|
+
});
|
|
11576
11690
|
logWarn(
|
|
11577
11691
|
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
11578
11692
|
{},
|
|
@@ -11607,7 +11721,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11607
11721
|
}
|
|
11608
11722
|
await scheduleTurnTimeoutResume({
|
|
11609
11723
|
conversationId: authSession.conversationId,
|
|
11610
|
-
sessionId:
|
|
11724
|
+
sessionId: resolvedSessionId,
|
|
11611
11725
|
expectedCheckpointVersion: checkpointVersion
|
|
11612
11726
|
});
|
|
11613
11727
|
}
|
|
@@ -11832,6 +11946,7 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11832
11946
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11833
11947
|
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
11834
11948
|
const userMessage = getTurnUserMessage(conversation, args.sessionId);
|
|
11949
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
11835
11950
|
markConversationMessage(conversation, userMessage?.id, {
|
|
11836
11951
|
replied: true,
|
|
11837
11952
|
skippedReason: void 0
|
|
@@ -11852,6 +11967,7 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11852
11967
|
markTurnCompleted({
|
|
11853
11968
|
conversation,
|
|
11854
11969
|
nowMs: Date.now(),
|
|
11970
|
+
sessionId: args.sessionId,
|
|
11855
11971
|
updateConversationStats
|
|
11856
11972
|
});
|
|
11857
11973
|
await persistThreadStateById(args.conversationId, {
|
|
@@ -11864,9 +11980,11 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11864
11980
|
async function persistFailedOAuthReplyState(args) {
|
|
11865
11981
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11866
11982
|
const conversation = coerceThreadConversationState(currentState);
|
|
11983
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
11867
11984
|
markTurnFailed({
|
|
11868
11985
|
conversation,
|
|
11869
11986
|
nowMs: Date.now(),
|
|
11987
|
+
sessionId: args.sessionId,
|
|
11870
11988
|
userMessageId: getTurnUserMessage(conversation, args.sessionId)?.id,
|
|
11871
11989
|
markConversationMessage,
|
|
11872
11990
|
updateConversationStats
|
|
@@ -11883,35 +12001,65 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11883
12001
|
stored.resumeConversationId,
|
|
11884
12002
|
stored.resumeSessionId
|
|
11885
12003
|
);
|
|
11886
|
-
if (!checkpoint
|
|
12004
|
+
if (!checkpoint) {
|
|
11887
12005
|
return false;
|
|
11888
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
|
+
}
|
|
11889
12013
|
const currentState = await getPersistedThreadState(
|
|
11890
12014
|
stored.resumeConversationId
|
|
11891
12015
|
);
|
|
11892
12016
|
const conversation = coerceThreadConversationState(currentState);
|
|
11893
12017
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11894
|
-
const
|
|
11895
|
-
|
|
11896
|
-
|
|
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
|
+
}
|
|
11897
12046
|
}
|
|
11898
|
-
if (
|
|
11899
|
-
return
|
|
12047
|
+
if (!userMessage?.author?.userId || !resolvedSessionId) {
|
|
12048
|
+
return false;
|
|
11900
12049
|
}
|
|
11901
12050
|
const conversationContext = await buildCheckpointConversationContext(
|
|
11902
12051
|
stored.resumeConversationId,
|
|
11903
|
-
|
|
12052
|
+
resolvedSessionId
|
|
11904
12053
|
);
|
|
11905
12054
|
const channelConfiguration = getChannelConfigurationServiceById(
|
|
11906
12055
|
stored.channelId
|
|
11907
12056
|
);
|
|
11908
|
-
const providerLabel = formatProviderLabel(stored.provider);
|
|
11909
12057
|
await resumeSlackTurn({
|
|
11910
12058
|
messageText: stored.pendingMessage ?? userMessage.text,
|
|
11911
12059
|
channelId: stored.channelId,
|
|
11912
12060
|
threadTs: stored.threadTs,
|
|
11913
12061
|
lockKey: stored.resumeConversationId,
|
|
11914
|
-
initialText:
|
|
12062
|
+
initialText: "",
|
|
11915
12063
|
failureText: "I connected your account but hit an error processing your request. Please try the command again.",
|
|
11916
12064
|
replyContext: {
|
|
11917
12065
|
assistant: { userName: botConfig.userName },
|
|
@@ -11927,10 +12075,21 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11927
12075
|
},
|
|
11928
12076
|
toolChannelId: artifacts.assistantContextChannelId ?? stored.channelId,
|
|
11929
12077
|
artifactState: artifacts,
|
|
12078
|
+
pendingAuth,
|
|
11930
12079
|
conversationContext,
|
|
11931
12080
|
channelConfiguration,
|
|
11932
12081
|
sandbox: getPersistedSandboxState(currentState),
|
|
11933
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
|
+
},
|
|
11934
12093
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11935
12094
|
},
|
|
11936
12095
|
onSuccess: async (reply) => {
|
|
@@ -11942,11 +12101,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11942
12101
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
11943
12102
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
11944
12103
|
},
|
|
11945
|
-
"
|
|
12104
|
+
"OAuth callback auto-resumed checkpoint finished replying"
|
|
11946
12105
|
);
|
|
11947
12106
|
await persistCompletedOAuthReplyState({
|
|
11948
12107
|
conversationId: stored.resumeConversationId,
|
|
11949
|
-
sessionId:
|
|
12108
|
+
sessionId: resolvedSessionId,
|
|
11950
12109
|
reply
|
|
11951
12110
|
});
|
|
11952
12111
|
},
|
|
@@ -11960,17 +12119,19 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11960
12119
|
);
|
|
11961
12120
|
await persistFailedOAuthReplyState({
|
|
11962
12121
|
conversationId: stored.resumeConversationId,
|
|
11963
|
-
sessionId:
|
|
12122
|
+
sessionId: resolvedSessionId
|
|
11964
12123
|
});
|
|
11965
12124
|
},
|
|
11966
12125
|
onAuthPause: async (error) => {
|
|
11967
|
-
|
|
12126
|
+
await deliverAuthPauseReply({
|
|
12127
|
+
channelId: stored.channelId,
|
|
12128
|
+
conversationId: stored.resumeConversationId,
|
|
11968
12129
|
error,
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
);
|
|
12130
|
+
fallbackProvider: stored.provider,
|
|
12131
|
+
sessionId: resolvedSessionId,
|
|
12132
|
+
threadStateId: stored.resumeConversationId,
|
|
12133
|
+
threadTs: stored.threadTs
|
|
12134
|
+
});
|
|
11974
12135
|
},
|
|
11975
12136
|
onTimeoutPause: async (error) => {
|
|
11976
12137
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
@@ -11990,7 +12151,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11990
12151
|
}
|
|
11991
12152
|
await scheduleTurnTimeoutResume({
|
|
11992
12153
|
conversationId: stored.resumeConversationId,
|
|
11993
|
-
sessionId:
|
|
12154
|
+
sessionId: resolvedSessionId,
|
|
11994
12155
|
expectedCheckpointVersion: checkpointVersion
|
|
11995
12156
|
});
|
|
11996
12157
|
}
|
|
@@ -11999,7 +12160,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11999
12160
|
}
|
|
12000
12161
|
async function resumePendingOAuthMessage(stored) {
|
|
12001
12162
|
if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
|
|
12002
|
-
const providerLabel = formatProviderLabel(stored.provider);
|
|
12003
12163
|
const conversationContext = await buildResumeConversationContext2(
|
|
12004
12164
|
stored.channelId,
|
|
12005
12165
|
stored.threadTs
|
|
@@ -12008,7 +12168,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
12008
12168
|
messageText: stored.pendingMessage,
|
|
12009
12169
|
channelId: stored.channelId,
|
|
12010
12170
|
threadTs: stored.threadTs,
|
|
12011
|
-
connectedText:
|
|
12171
|
+
connectedText: "",
|
|
12012
12172
|
failureText: `I connected your account but hit an error processing your request. Please try \`${stored.pendingMessage}\` again.`,
|
|
12013
12173
|
replyContext: {
|
|
12014
12174
|
requester: { userId: stored.userId },
|
|
@@ -12024,7 +12184,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
12024
12184
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
12025
12185
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
12026
12186
|
},
|
|
12027
|
-
"
|
|
12187
|
+
"OAuth callback auto-resumed pending message finished replying"
|
|
12028
12188
|
);
|
|
12029
12189
|
},
|
|
12030
12190
|
onFailure: async (error) => {
|
|
@@ -12273,6 +12433,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
12273
12433
|
conversation,
|
|
12274
12434
|
args.checkpoint.sessionId
|
|
12275
12435
|
);
|
|
12436
|
+
clearPendingAuth(conversation, args.checkpoint.sessionId);
|
|
12276
12437
|
markConversationMessage(conversation, userMessage?.id, {
|
|
12277
12438
|
replied: true,
|
|
12278
12439
|
skippedReason: void 0
|
|
@@ -12293,6 +12454,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
12293
12454
|
markTurnCompleted({
|
|
12294
12455
|
conversation,
|
|
12295
12456
|
nowMs: Date.now(),
|
|
12457
|
+
sessionId: args.checkpoint.sessionId,
|
|
12296
12458
|
updateConversationStats
|
|
12297
12459
|
});
|
|
12298
12460
|
await persistThreadStateById(args.checkpoint.conversationId, {
|
|
@@ -12305,9 +12467,11 @@ async function persistCompletedReplyState2(args) {
|
|
|
12305
12467
|
async function persistFailedReplyState2(checkpoint) {
|
|
12306
12468
|
const currentState = await getPersistedThreadState(checkpoint.conversationId);
|
|
12307
12469
|
const conversation = coerceThreadConversationState(currentState);
|
|
12470
|
+
clearPendingAuth(conversation, checkpoint.sessionId);
|
|
12308
12471
|
markTurnFailed({
|
|
12309
12472
|
conversation,
|
|
12310
12473
|
nowMs: Date.now(),
|
|
12474
|
+
sessionId: checkpoint.sessionId,
|
|
12311
12475
|
userMessageId: getTurnUserMessage(conversation, checkpoint.sessionId)?.id,
|
|
12312
12476
|
markConversationMessage,
|
|
12313
12477
|
updateConversationStats
|
|
@@ -12371,10 +12535,21 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12371
12535
|
},
|
|
12372
12536
|
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
12373
12537
|
artifactState: artifacts,
|
|
12538
|
+
pendingAuth: conversation.processing.pendingAuth,
|
|
12374
12539
|
conversationContext,
|
|
12375
12540
|
channelConfiguration,
|
|
12376
12541
|
sandbox,
|
|
12377
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
|
+
},
|
|
12378
12553
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
12379
12554
|
},
|
|
12380
12555
|
onSuccess: async (reply) => {
|
|
@@ -12406,7 +12581,15 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12406
12581
|
);
|
|
12407
12582
|
await persistFailedReplyState2(checkpoint);
|
|
12408
12583
|
},
|
|
12409
|
-
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
|
+
});
|
|
12410
12593
|
logWarn(
|
|
12411
12594
|
"timeout_resume_reparked_for_auth",
|
|
12412
12595
|
{},
|
|
@@ -14145,6 +14328,7 @@ function createReplyToThread(deps) {
|
|
|
14145
14328
|
},
|
|
14146
14329
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
14147
14330
|
artifactState: preparedState.artifacts,
|
|
14331
|
+
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
14148
14332
|
configuration: preparedState.configuration,
|
|
14149
14333
|
channelConfiguration: preparedState.channelConfiguration,
|
|
14150
14334
|
inboundAttachmentCount: message.attachments.length,
|
|
@@ -14174,6 +14358,16 @@ function createReplyToThread(deps) {
|
|
|
14174
14358
|
onArtifactStateUpdated: async (artifacts) => {
|
|
14175
14359
|
await persistThreadState(thread, { artifacts });
|
|
14176
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
|
+
},
|
|
14177
14371
|
threadParticipants,
|
|
14178
14372
|
onStatus: (nextStatus) => status.update(nextStatus)
|
|
14179
14373
|
});
|
|
@@ -14258,7 +14452,7 @@ function createReplyToThread(deps) {
|
|
|
14258
14452
|
const replyFooter = buildSlackReplyFooter({
|
|
14259
14453
|
conversationId,
|
|
14260
14454
|
durationMs: reply.diagnostics.durationMs,
|
|
14261
|
-
|
|
14455
|
+
thinkingLevel: reply.diagnostics.thinkingLevel,
|
|
14262
14456
|
usage: reply.diagnostics.usage
|
|
14263
14457
|
});
|
|
14264
14458
|
const shouldUseSlackFooter = Boolean(replyFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
@@ -14351,8 +14545,42 @@ function createReplyToThread(deps) {
|
|
|
14351
14545
|
}
|
|
14352
14546
|
} catch (error) {
|
|
14353
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;
|
|
14354
14582
|
shouldPersistFailureState = false;
|
|
14355
|
-
|
|
14583
|
+
return;
|
|
14356
14584
|
}
|
|
14357
14585
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
14358
14586
|
const conversationIdForResume = error.metadata?.conversationId;
|