@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 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
- toOptionalTrimmed
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/runtime/turn.ts
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 = void 0;
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 = void 0;
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. Use concrete labels like Searching docs, Reviewing results, or Running checks. Skip trivial direct answers, generic filler, and minor substeps.",
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. The UI truncates it if needed."
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/credentials/unlink-provider.ts
8446
- async function unlinkProvider(userId, provider, userTokenStore) {
8447
- await Promise.all([
8448
- userTokenStore.delete(userId, provider),
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(`Plugin authorization started for ${provider}`);
8460
- this.name = "PluginAuthorizationPauseError";
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": sandbox.getActiveSkill()?.name,
8318
+ "app.skill.name": skillName,
8625
8319
  "app.credential.delivery": "header_transform",
8626
8320
  "app.credential.header_domains": headerDomains
8627
8321
  },
8628
- "Injecting scoped credential headers for sandbox command"
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 PluginAuthorizationPauseError) {
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 parseJsonCandidate2(text) {
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 = parseJsonCandidate2(text);
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 = 1200;
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 void 0;
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
- return trimmed.length <= MAX_ROUTER_CONTEXT_CHARS ? trimmed : trimmed.slice(-MAX_ROUTER_CONTEXT_CHARS);
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
- const context = trimContextForRouter(args.conversationContext);
9278
- if (context) {
9279
- sections.push("<thread-background>", context, "</thread-background>", "");
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
- "<turn-context>",
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 activeSkillNames = [...new Set(args.activeSkillNames ?? [])].sort();
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
- modelId: args.fastModelId,
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:${parsed.reason.trim()}`
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: parsed.reason.trim()
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/mcp-auth-orchestration.ts
9561
- var McpAuthorizationPauseError = class extends Error {
9562
- provider;
9563
- constructor(provider) {
9564
- super(`MCP authorization started for ${provider}`);
9565
- this.name = "McpAuthorizationPauseError";
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
- function createMcpAuthOrchestration(deps, abortAgent) {
9570
- let pendingPause;
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 delivery = await deliverPrivateMessage({
9612
- channelId: authSession.channelId,
9613
- threadTs: authSession.threadTs,
9614
- userId: authSession.userId,
9615
- text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
9427
+ const reusingPendingLink = canReusePendingAuthLink({
9428
+ pendingAuth: deps.currentPendingAuth,
9429
+ kind: "mcp",
9430
+ provider,
9431
+ requesterId: deps.requesterId
9616
9432
  });
9617
- if (!delivery) {
9618
- throw new Error(
9619
- `Unable to deliver MCP authorization link for plugin "${provider}"`
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
- pendingPause = new McpAuthorizationPauseError(provider);
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
- let completedAssistantTurn = false;
10199
+ beforeMessageCount = agent.state.messages.length;
10214
10200
  try {
10215
10201
  if (resumedFromCheckpoint) {
10216
- agent.replaceMessages(existingCheckpoint.piMessages);
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 ((error instanceof McpAuthorizationPauseError || error instanceof PluginAuthorizationPauseError) && timeoutResumeConversationId && timeoutResumeSessionId) {
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 instanceof PluginAuthorizationPauseError ? "plugin_auth_resume" : "mcp_auth_resume",
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
- const traceId = args.traceId?.trim();
10866
- if (traceId) {
10861
+ if (args.thinkingLevel) {
10867
10862
  items.push({
10868
- label: "Trace",
10869
- value: traceId
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
- traceId: getActiveTraceId(),
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 userMessage = getTurnUserMessage(conversation, authSession.sessionId);
11492
- if (conversation.processing.activeTurnId !== authSession.sessionId) {
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
- authSession.sessionId
11597
+ resolvedSessionId
11502
11598
  );
11503
11599
  await resumeAuthorizedRequest({
11504
- messageText: authSession.userMessage,
11600
+ messageText: userMessage.text,
11505
11601
  channelId: authSession.channelId,
11506
11602
  threadTs: authSession.threadTs,
11507
11603
  lockKey: authSession.conversationId,
11508
- connectedText: `Your ${provider} MCP access is now connected. Continuing the original request...`,
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: authSession.sessionId,
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
- authSession.sessionId,
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
- authSession.sessionId
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: authSession.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 || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "auth") {
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 userMessage = getTurnUserMessage(conversation, stored.resumeSessionId);
11895
- if (!userMessage?.author?.userId) {
11896
- return false;
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 (conversation.processing.activeTurnId !== stored.resumeSessionId) {
11899
- return true;
12047
+ if (!userMessage?.author?.userId || !resolvedSessionId) {
12048
+ return false;
11900
12049
  }
11901
12050
  const conversationContext = await buildCheckpointConversationContext(
11902
12051
  stored.resumeConversationId,
11903
- stored.resumeSessionId
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: `Your ${providerLabel} account is now connected. Processing your request...`,
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
- "Auto-resumed checkpointed turn after OAuth callback"
12104
+ "OAuth callback auto-resumed checkpoint finished replying"
11946
12105
  );
11947
12106
  await persistCompletedOAuthReplyState({
11948
12107
  conversationId: stored.resumeConversationId,
11949
- sessionId: stored.resumeSessionId,
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: stored.resumeSessionId
12122
+ sessionId: resolvedSessionId
11964
12123
  });
11965
12124
  },
11966
12125
  onAuthPause: async (error) => {
11967
- logException(
12126
+ await deliverAuthPauseReply({
12127
+ channelId: stored.channelId,
12128
+ conversationId: stored.resumeConversationId,
11968
12129
  error,
11969
- "oauth_callback_resume_reparked_for_auth",
11970
- {},
11971
- { "app.credential.provider": stored.provider },
11972
- "Resumed OAuth turn requested another authorization flow"
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: stored.resumeSessionId,
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: `Your ${providerLabel} account is now connected. Processing your request...`,
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
- "Auto-resumed pending message after OAuth callback"
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
- traceId: getActiveTraceId(),
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
- throw error;
14583
+ return;
14356
14584
  }
14357
14585
  if (isRetryableTurnError(error, "turn_timeout_resume")) {
14358
14586
  const conversationIdForResume = error.metadata?.conversationId;