@sentry/junior 0.15.2 → 0.17.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
@@ -7,7 +7,7 @@ import {
7
7
  logCapabilityCatalogLoadedOnce,
8
8
  parseSkillInvocation,
9
9
  stripFrontmatter
10
- } from "./chunk-KTBQH6L5.js";
10
+ } from "./chunk-4XWTSMRF.js";
11
11
  import {
12
12
  SANDBOX_SKILLS_ROOT,
13
13
  SANDBOX_WORKSPACE_ROOT,
@@ -26,13 +26,14 @@ import {
26
26
  runNonInteractiveCommand,
27
27
  sandboxSkillDir,
28
28
  toOptionalTrimmed
29
- } from "./chunk-ESPIOJPM.js";
29
+ } from "./chunk-XYOKYK6U.js";
30
30
  import {
31
31
  CredentialUnavailableError,
32
32
  buildOAuthTokenRequest,
33
33
  createPluginBroker,
34
34
  createRequestContext,
35
35
  extractGenAiUsageAttributes,
36
+ getActiveTraceId,
36
37
  getPluginDefinition,
37
38
  getPluginMcpProviders,
38
39
  getPluginOAuthConfig,
@@ -54,7 +55,7 @@ import {
54
55
  toOptionalString,
55
56
  withContext,
56
57
  withSpan
57
- } from "./chunk-5JHLDXBN.js";
58
+ } from "./chunk-DTOS5CG4.js";
58
59
  import "./chunk-Z3YD6NHK.js";
59
60
  import {
60
61
  aboutPathCandidates,
@@ -224,8 +225,8 @@ async function GET3() {
224
225
  html += `
225
226
  </div>`;
226
227
  const endpoints = [
227
- { method: "GET", path: "/api/health" },
228
- { method: "GET", path: "/api/__junior/discovery" },
228
+ { method: "GET", path: "/health" },
229
+ { method: "GET", path: "/api/info" },
229
230
  { method: "GET", path: "/api/oauth/callback/mcp/:provider" },
230
231
  { method: "GET", path: "/api/oauth/callback/:provider" },
231
232
  { method: "POST", path: "/api/webhooks/:platform" }
@@ -1157,21 +1158,44 @@ function summarizeMessageText(text) {
1157
1158
  }
1158
1159
  return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
1159
1160
  }
1160
- function buildUserTurnText(userInput, conversationContext) {
1161
+ function buildUserTurnText(userInput, conversationContext, metadata) {
1161
1162
  const trimmedContext = conversationContext?.trim();
1162
- if (!trimmedContext) {
1163
+ const hasSessionContext = Boolean(metadata?.sessionContext?.conversationId);
1164
+ const hasTurnContext = Boolean(metadata?.turnContext?.traceId);
1165
+ if (!trimmedContext && !hasSessionContext && !hasTurnContext) {
1163
1166
  return userInput;
1164
1167
  }
1165
- return [
1168
+ const sections = [
1166
1169
  "<current-message>",
1167
1170
  userInput,
1168
- "</current-message>",
1169
- "",
1170
- "<thread-conversation-context>",
1171
- "Use this context for continuity across prior thread turns.",
1172
- trimmedContext,
1173
- "</thread-conversation-context>"
1174
- ].join("\n");
1171
+ "</current-message>"
1172
+ ];
1173
+ if (trimmedContext) {
1174
+ sections.push(
1175
+ "",
1176
+ "<thread-conversation-context>",
1177
+ "Use this context for continuity across prior thread turns.",
1178
+ trimmedContext,
1179
+ "</thread-conversation-context>"
1180
+ );
1181
+ }
1182
+ if (metadata?.sessionContext?.conversationId) {
1183
+ sections.push(
1184
+ "",
1185
+ "<session-context>",
1186
+ `- gen_ai.conversation.id: ${metadata.sessionContext.conversationId}`,
1187
+ "</session-context>"
1188
+ );
1189
+ }
1190
+ if (metadata?.turnContext?.traceId) {
1191
+ sections.push(
1192
+ "",
1193
+ "<turn-context>",
1194
+ `- trace_id: ${metadata.turnContext.traceId}`,
1195
+ "</turn-context>"
1196
+ );
1197
+ }
1198
+ return sections.join("\n");
1175
1199
  }
1176
1200
  function encodeNonImageAttachmentForPrompt(attachment) {
1177
1201
  const base64 = attachment.data.toString("base64");
@@ -1913,9 +1937,7 @@ function createChannelConfigurationService(storage) {
1913
1937
  };
1914
1938
  const resolveValues = async (options = {}) => {
1915
1939
  const keys = Array.isArray(options.keys) ? options.keys.map((entry) => entry.trim()).filter((entry) => entry.length > 0) : void 0;
1916
- const entries = await list({
1917
- ...options.prefix ? { prefix: options.prefix } : {}
1918
- });
1940
+ const entries = options.prefix ? await list({ prefix: options.prefix }) : await list({});
1919
1941
  const filtered = keys ? entries.filter((entry) => keys.includes(entry.key)) : entries;
1920
1942
  const resolved = {};
1921
1943
  for (const entry of filtered) {
@@ -2152,14 +2174,15 @@ function resolveGatewayModel(modelId) {
2152
2174
  return matched;
2153
2175
  }
2154
2176
  async function completeText(params) {
2155
- const startedAt = Date.now();
2156
2177
  const model = resolveGatewayModel(params.modelId);
2157
2178
  const apiKey = getPiGatewayApiKeyOverride();
2158
2179
  const requestMessagesAttribute = serializeGenAiAttribute(params.messages);
2180
+ const systemInstructionsAttribute = params.system ? serializeGenAiAttribute([{ type: "text", content: params.system }]) : void 0;
2159
2181
  const startAttributes = {
2160
2182
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2161
2183
  "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2162
2184
  "gen_ai.request.model": params.modelId,
2185
+ ...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
2163
2186
  ...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
2164
2187
  "app.ai.auth_mode": apiKey ? "oidc" : "api_key"
2165
2188
  };
@@ -2192,8 +2215,7 @@ async function completeText(params) {
2192
2215
  "gen_ai.request.model": params.modelId,
2193
2216
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
2194
2217
  ...usageAttributes,
2195
- "app.ai.duration_ms": Date.now() - startedAt,
2196
- "app.ai.stop_reason": message.stopReason ?? "unknown"
2218
+ ...message.stopReason ? { "gen_ai.response.finish_reasons": [message.stopReason] } : {}
2197
2219
  };
2198
2220
  setSpanAttributes(endAttributes);
2199
2221
  if (message.stopReason === "error") {
@@ -2783,24 +2805,6 @@ function formatLoadedSkillsForPrompt(skills) {
2783
2805
  lines.push("</loaded_skills>");
2784
2806
  return lines.join("\n");
2785
2807
  }
2786
- function formatLoadedToolsForPrompt(tools) {
2787
- if (tools.length === 0) {
2788
- return "<loaded_tools>\n</loaded_tools>";
2789
- }
2790
- const lines = ["<loaded_tools>"];
2791
- for (const tool2 of tools) {
2792
- lines.push(
2793
- ` <tool name="${escapeXml(tool2.tool_name)}" provider="${escapeXml(tool2.provider)}">`
2794
- );
2795
- lines.push(` <description>${escapeXml(tool2.description)}</description>`);
2796
- lines.push(
2797
- ` <arguments_summary>${escapeXml(tool2.input_schema_summary)}</arguments_summary>`
2798
- );
2799
- lines.push(" </tool>");
2800
- }
2801
- lines.push("</loaded_tools>");
2802
- return lines.join("\n");
2803
- }
2804
2808
  function formatProviderCatalogForPrompt() {
2805
2809
  const providers = listCapabilityProviders();
2806
2810
  if (providers.length === 0) {
@@ -2834,7 +2838,7 @@ function baseSystemPrompt() {
2834
2838
  "- Never claim you cannot access tools in this turn. If prior results are empty, run tools now.",
2835
2839
  "- If critical input is missing and cannot be discovered with tools, ask one direct clarifying question.",
2836
2840
  "- Always gather evidence from available sources (tools or skills) before answering factual questions.",
2837
- "- When a loaded skill exposes MCP capabilities, prefer the exact tool_name values disclosed by `loadSkill` or `<loaded_tools>`, then execute them with `useTool`.",
2841
+ "- When a loaded skill exposes MCP capabilities, those tools are registered as callable tools. Call them directly by name.",
2838
2842
  "- Use `searchTools` only when you need to rediscover or filter active MCP tools.",
2839
2843
  "- Never guess. If you cannot verify with available sources, say it is unverified.",
2840
2844
  "- Never claim a lookup succeeded unless a tool result supports it.",
@@ -2875,10 +2879,8 @@ function buildSystemPrompt(params) {
2875
2879
  "Loaded skills for this turn:",
2876
2880
  formatLoadedSkillsForPrompt(activeSkills)
2877
2881
  ].join("\n");
2878
- const activeToolsSection = [
2879
- "Loaded host-managed tools for this turn. Use the exact tool_name values from here or from `loadSkill.available_tools`, and use `searchTools` only when you need to rediscover or filter them:",
2880
- formatLoadedToolsForPrompt(activeTools ?? [])
2881
- ].join("\n");
2882
+ const activeToolNames = (activeTools ?? []).map((tool2) => tool2.tool_name);
2883
+ const activeToolsSection = activeToolNames.length > 0 ? `Active MCP tools registered for this turn: ${activeToolNames.join(", ")}. Call them directly by name.` : "";
2882
2884
  const configurationKeys = Object.keys(configuration ?? {}).sort(
2883
2885
  (a, b) => a.localeCompare(b)
2884
2886
  );
@@ -3004,9 +3006,9 @@ function buildSystemPrompt(params) {
3004
3006
  "- Do not use reaction-based progress signals; Assistants API status already covers in-progress UX.",
3005
3007
  "- Prefer `webSearch` before `webFetch` when the user gave no URL.",
3006
3008
  "- Never call side-effecting tools when the user only asked for analysis or options.",
3007
- "- `loadSkill` returns `available_tools` when the loaded skill exposes MCP tools. Use those exact tool_name values instead of guessing.",
3009
+ "- `loadSkill` activates MCP tools when the loaded skill exposes them. After loading, call them directly by name (for example `mcp__provider__tool_name`).",
3008
3010
  "- `searchTools` searches active MCP tools exposed by currently loaded skills when you need to rediscover or filter them.",
3009
- "- `useTool` executes a canonical MCP tool name from `loadSkill.available_tools`, `<loaded_tools>`, or `searchTools`."
3011
+ "- When the user asks for their conversation ID, trace ID, or a reference for Sentry lookup, use the IDs from `<session-context>` and `<turn-context>` in the user turn."
3010
3012
  ].join("\n")
3011
3013
  ),
3012
3014
  renderTag(
@@ -3021,7 +3023,7 @@ function buildSystemPrompt(params) {
3021
3023
  "- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
3022
3024
  "- Load only the best matching skill first; do not load multiple skills upfront.",
3023
3025
  "- After `loadSkill`, use `skill_dir` as the root for any referenced files you read via `bash`.",
3024
- "- If a loaded skill exposes MCP tools, prefer the exact tool_name values returned by `loadSkill`, then use `useTool` to execute them.",
3026
+ "- If a loaded skill exposes MCP tools, they are registered as callable tools after `loadSkill` returns. Call them directly by name.",
3025
3027
  "- Use `searchTools` only when you need to rediscover or filter the currently exposed MCP tools.",
3026
3028
  "- If no skill is a clear fit, continue with normal tool usage."
3027
3029
  ].join("\n")
@@ -3042,7 +3044,7 @@ function buildSystemPrompt(params) {
3042
3044
  ),
3043
3045
  availableSkillsSection,
3044
3046
  activeSkillsSection,
3045
- activeToolsSection,
3047
+ ...activeToolsSection ? [activeToolsSection] : [],
3046
3048
  renderTag(
3047
3049
  "invocation-context",
3048
3050
  invocation ? `Explicit skill trigger detected: /${invocation.skillName}` : "No explicit skill trigger detected."
@@ -3735,9 +3737,7 @@ ${usage}
3735
3737
  exitCode: 2
3736
3738
  });
3737
3739
  }
3738
- const entries = await configuration.list({
3739
- ...prefixResult.prefix ? { prefix: prefixResult.prefix } : {}
3740
- });
3740
+ const entries = prefixResult.prefix ? await configuration.list({ prefix: prefixResult.prefix }) : await configuration.list({});
3741
3741
  return commandResult({
3742
3742
  stdout: {
3743
3743
  ok: true,
@@ -3927,65 +3927,6 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
3927
3927
  };
3928
3928
  }
3929
3929
 
3930
- // src/chat/services/channel-intent.ts
3931
- function isExplicitChannelPostIntent(text) {
3932
- if (!/\bchannel\b/i.test(text)) {
3933
- return false;
3934
- }
3935
- const directChannelVerb = /\b(show|post|send|share|say|announce|broadcast)\b[\s\S]{0,80}\b(?:the\s+)?channel\b/i;
3936
- if (directChannelVerb.test(text)) {
3937
- return true;
3938
- }
3939
- const scopedChannelVerb = /\b(post|send|share|say|announce|broadcast)\b[\s\S]{0,80}\b(?:in|to)\b[\s\S]{0,40}\b(?:the\s+)?channel\b/i;
3940
- return scopedChannelVerb.test(text);
3941
- }
3942
-
3943
- // src/chat/services/reply-delivery-plan.ts
3944
- var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
3945
- var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
3946
- var REACTION_ALIAS_PREFIX_RE = /^:[a-z0-9_+-]*$/i;
3947
- function normalizeReactionAckText(text) {
3948
- return text.trim().toLowerCase().replace(/[!.]+$/g, "");
3949
- }
3950
- function isRedundantReactionAckText(text) {
3951
- const trimmed = text.trim();
3952
- if (!trimmed) {
3953
- return false;
3954
- }
3955
- if (REACTION_ONLY_ACK_RE.test(trimmed)) {
3956
- return true;
3957
- }
3958
- const normalized = normalizeReactionAckText(text);
3959
- return REDUNDANT_REACTION_ACK_TEXT.includes(
3960
- normalized
3961
- );
3962
- }
3963
- function isPotentialRedundantReactionAckText(text) {
3964
- const trimmed = text.trim();
3965
- if (!trimmed) {
3966
- return true;
3967
- }
3968
- if (REACTION_ONLY_ACK_RE.test(trimmed) || REACTION_ALIAS_PREFIX_RE.test(trimmed)) {
3969
- return true;
3970
- }
3971
- const normalized = normalizeReactionAckText(text);
3972
- return REDUNDANT_REACTION_ACK_TEXT.some(
3973
- (candidate) => candidate.startsWith(normalized)
3974
- );
3975
- }
3976
- function buildReplyDeliveryPlan(args) {
3977
- const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
3978
- let attachFiles = "none";
3979
- if (args.hasFiles && mode === "thread") {
3980
- attachFiles = args.streamingThreadReply ? "followup" : "inline";
3981
- }
3982
- return {
3983
- mode,
3984
- postThreadText: mode === "thread",
3985
- attachFiles
3986
- };
3987
- }
3988
-
3989
3930
  // src/chat/sandbox/skill-sandbox.ts
3990
3931
  import fs2 from "fs/promises";
3991
3932
  import path2 from "path";
@@ -4185,9 +4126,6 @@ var SkillSandbox = class {
4185
4126
  }
4186
4127
  };
4187
4128
 
4188
- // src/chat/mcp/tool-manager.ts
4189
- import { validateToolArguments } from "@mariozechner/pi-ai";
4190
-
4191
4129
  // src/chat/mcp/client.ts
4192
4130
  import { Client } from "@modelcontextprotocol/sdk/client";
4193
4131
  import {
@@ -4367,6 +4305,12 @@ var PluginMcpClient = class {
4367
4305
  };
4368
4306
 
4369
4307
  // src/chat/mcp/tool-manager.ts
4308
+ var McpToolError = class extends Error {
4309
+ constructor(message) {
4310
+ super(message);
4311
+ this.name = "McpToolError";
4312
+ }
4313
+ };
4370
4314
  function normalizeMcpToolName(provider, toolName) {
4371
4315
  return `mcp__${provider}__${toolName}`;
4372
4316
  }
@@ -4550,13 +4494,6 @@ var McpToolManager = class {
4550
4494
  return left.tool.name.localeCompare(right.tool.name);
4551
4495
  }).slice(0, Math.max(1, options.limit ?? 8)).map((entry) => this.toToolDescriptor(entry.tool));
4552
4496
  }
4553
- async executeTool(skills, canonicalToolName, args) {
4554
- const tool2 = this.resolveActiveTool(skills, canonicalToolName);
4555
- if (!tool2) {
4556
- throw new Error(`Unknown active MCP tool: ${canonicalToolName}`);
4557
- }
4558
- return await tool2.execute(this.validateExecutionArgs(tool2, args));
4559
- }
4560
4497
  filterListedTools(plugin, tools) {
4561
4498
  const allowedTools = plugin.manifest.mcp?.allowedTools;
4562
4499
  if (!allowedTools || allowedTools.length === 0) {
@@ -4600,7 +4537,7 @@ var McpToolManager = class {
4600
4537
  try {
4601
4538
  const result = await client2.callTool(tool2.name, resolvedArgs);
4602
4539
  if ("isError" in result && result.isError) {
4603
- throw new Error(extractMcpErrorMessage(result));
4540
+ throw new McpToolError(extractMcpErrorMessage(result));
4604
4541
  }
4605
4542
  return {
4606
4543
  content: toAgentToolContent(result),
@@ -4651,6 +4588,7 @@ var McpToolManager = class {
4651
4588
  this.activeProviders.delete(provider);
4652
4589
  return true;
4653
4590
  }
4591
+ /** Return all active ManagedMcpTool objects for the given skill scope. */
4654
4592
  getResolvedActiveTools(skills, options = {}) {
4655
4593
  const resolved = [];
4656
4594
  for (const provider of this.getActiveProviders()) {
@@ -4674,11 +4612,6 @@ var McpToolManager = class {
4674
4612
  }
4675
4613
  return providerTools;
4676
4614
  }
4677
- resolveActiveTool(skills, canonicalToolName) {
4678
- return this.getResolvedActiveTools(skills).find(
4679
- (tool2) => tool2.name === canonicalToolName
4680
- );
4681
- }
4682
4615
  toToolDescriptor(tool2) {
4683
4616
  return {
4684
4617
  name: tool2.name,
@@ -4687,15 +4620,6 @@ var McpToolManager = class {
4687
4620
  provider: tool2.provider
4688
4621
  };
4689
4622
  }
4690
- validateExecutionArgs(tool2, args) {
4691
- return validateToolArguments(
4692
- tool2,
4693
- {
4694
- name: tool2.name,
4695
- arguments: args
4696
- }
4697
- );
4698
- }
4699
4623
  scoreToolMatch(tool2, normalizedQuery, queryTokens) {
4700
4624
  const exactCandidates = [tool2.name, tool2.rawName, tool2.title].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
4701
4625
  if (exactCandidates.includes(normalizedQuery)) {
@@ -5161,7 +5085,7 @@ var DEFAULT_LIMIT = 5;
5161
5085
  var MAX_LIMIT = 20;
5162
5086
  function createSearchToolsTool(mcpToolManager, getActiveSkills) {
5163
5087
  return tool({
5164
- description: "Search active MCP tools exposed by the currently loaded skills. Use when you need to rediscover or filter tools beyond what `loadSkill` or `<loaded_tools>` already disclosed.",
5088
+ description: "Search active MCP tools exposed by the currently loaded skills. Use when you need to rediscover or filter active tools.",
5165
5089
  inputSchema: Type6.Object(
5166
5090
  {
5167
5091
  query: Type6.String({
@@ -6237,45 +6161,8 @@ function createSystemTimeTool() {
6237
6161
  });
6238
6162
  }
6239
6163
 
6240
- // src/chat/tools/skill/use-tool.ts
6241
- import { Type as Type13 } from "@sinclair/typebox";
6242
- function normalizeToolArguments(value) {
6243
- return value ?? {};
6244
- }
6245
- function createUseToolTool(mcpToolManager, getActiveSkills) {
6246
- return tool({
6247
- description: "Execute an active MCP tool by canonical tool_name. Use tool_name values disclosed by `loadSkill`, `<loaded_tools>`, or `searchTools`.",
6248
- inputSchema: Type13.Object(
6249
- {
6250
- tool_name: Type13.String({
6251
- minLength: 1,
6252
- description: "Canonical MCP tool name in the form mcp__<provider>__<tool>."
6253
- }),
6254
- arguments: Type13.Optional(
6255
- Type13.Object(
6256
- {},
6257
- {
6258
- additionalProperties: true,
6259
- description: "Arguments for the selected MCP tool."
6260
- }
6261
- )
6262
- )
6263
- },
6264
- { additionalProperties: false }
6265
- ),
6266
- execute: async ({ tool_name, arguments: rawArguments }) => {
6267
- const activeSkills = getActiveSkills();
6268
- return await mcpToolManager.executeTool(
6269
- activeSkills,
6270
- tool_name,
6271
- normalizeToolArguments(rawArguments)
6272
- );
6273
- }
6274
- });
6275
- }
6276
-
6277
6164
  // src/chat/tools/web/fetch-tool.ts
6278
- import { Type as Type14 } from "@sinclair/typebox";
6165
+ import { Type as Type13 } from "@sinclair/typebox";
6279
6166
 
6280
6167
  // src/chat/tools/web/constants.ts
6281
6168
  var USER_AGENT = "junior-bot/0.1";
@@ -6623,13 +6510,13 @@ function extractHttpStatusFromMessage(message) {
6623
6510
  function createWebFetchTool(hooks) {
6624
6511
  return tool({
6625
6512
  description: "Fetch and extract readable content from a specific URL. Use when you need details from a known page or document. Do not use for discovery when search is the first step.",
6626
- inputSchema: Type14.Object({
6627
- url: Type14.String({
6513
+ inputSchema: Type13.Object({
6514
+ url: Type13.String({
6628
6515
  minLength: 1,
6629
6516
  description: "HTTP(S) URL to fetch."
6630
6517
  }),
6631
- max_chars: Type14.Optional(
6632
- Type14.Integer({
6518
+ max_chars: Type13.Optional(
6519
+ Type13.Integer({
6633
6520
  minimum: 500,
6634
6521
  maximum: MAX_FETCH_CHARS,
6635
6522
  description: "Optional maximum number of extracted characters to return."
@@ -6689,7 +6576,7 @@ function createWebFetchTool(hooks) {
6689
6576
  // src/chat/tools/web/search.ts
6690
6577
  import { generateText } from "ai";
6691
6578
  import { createGatewayProvider } from "@ai-sdk/gateway";
6692
- import { Type as Type15 } from "@sinclair/typebox";
6579
+ import { Type as Type14 } from "@sinclair/typebox";
6693
6580
  var SEARCH_TIMEOUT_MS = 1e4;
6694
6581
  var MAX_RESULTS = 5;
6695
6582
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -6740,14 +6627,14 @@ function isTimeoutSearchFailure(message) {
6740
6627
  function createWebSearchTool() {
6741
6628
  return tool({
6742
6629
  description: "Search public web sources and return top snippets/URLs. Use when you need discovery or source candidates. Do not use when the user already provided a specific URL to inspect.",
6743
- inputSchema: Type15.Object({
6744
- query: Type15.String({
6630
+ inputSchema: Type14.Object({
6631
+ query: Type14.String({
6745
6632
  minLength: 1,
6746
6633
  maxLength: 500,
6747
6634
  description: "Search query."
6748
6635
  }),
6749
- max_results: Type15.Optional(
6750
- Type15.Integer({
6636
+ max_results: Type14.Optional(
6637
+ Type14.Integer({
6751
6638
  minimum: 1,
6752
6639
  maximum: MAX_RESULTS,
6753
6640
  description: "Max results to return."
@@ -6808,17 +6695,17 @@ function createWebSearchTool() {
6808
6695
  }
6809
6696
 
6810
6697
  // src/chat/tools/sandbox/write-file.ts
6811
- import { Type as Type16 } from "@sinclair/typebox";
6698
+ import { Type as Type15 } from "@sinclair/typebox";
6812
6699
  function createWriteFileTool() {
6813
6700
  return tool({
6814
6701
  description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.",
6815
- inputSchema: Type16.Object(
6702
+ inputSchema: Type15.Object(
6816
6703
  {
6817
- path: Type16.String({
6704
+ path: Type15.String({
6818
6705
  minLength: 1,
6819
6706
  description: "Path to write in the sandbox workspace."
6820
6707
  }),
6821
- content: Type16.String({
6708
+ content: Type15.String({
6822
6709
  description: "UTF-8 file content to write."
6823
6710
  })
6824
6711
  },
@@ -6938,20 +6825,16 @@ function createTools(availableSkills, hooks = {}, context) {
6938
6825
  createSearchToolsTool(context.mcpToolManager, context.getActiveSkills),
6939
6826
  hooks
6940
6827
  );
6941
- tools.useTool = wrapToolExecution(
6942
- "useTool",
6943
- createUseToolTool(context.mcpToolManager, context.getActiveSkills),
6944
- hooks
6945
- );
6946
6828
  }
6947
- if (isConversationScopedChannel(context.channelId)) {
6829
+ const { channelCapabilities } = context;
6830
+ if (channelCapabilities.canCreateCanvas) {
6948
6831
  tools.slackCanvasCreate = wrapToolExecution(
6949
6832
  "slackCanvasCreate",
6950
6833
  createSlackCanvasCreateTool(context, state),
6951
6834
  hooks
6952
6835
  );
6953
6836
  }
6954
- if (isConversationChannel(context.channelId)) {
6837
+ if (channelCapabilities.canPostToChannel) {
6955
6838
  tools.slackChannelPostMessage = wrapToolExecution(
6956
6839
  "slackChannelPostMessage",
6957
6840
  createSlackChannelPostMessageTool(context, state),
@@ -6963,7 +6846,7 @@ function createTools(availableSkills, hooks = {}, context) {
6963
6846
  hooks
6964
6847
  );
6965
6848
  }
6966
- if (isConversationScopedChannel(context.channelId)) {
6849
+ if (channelCapabilities.canAddReactions) {
6967
6850
  tools.slackMessageAddReaction = wrapToolExecution(
6968
6851
  "slackMessageAddReaction",
6969
6852
  createSlackMessageAddReactionTool(context, state),
@@ -6973,6 +6856,15 @@ function createTools(availableSkills, hooks = {}, context) {
6973
6856
  return tools;
6974
6857
  }
6975
6858
 
6859
+ // src/chat/tools/channel-capabilities.ts
6860
+ function resolveChannelCapabilities(channelId) {
6861
+ return {
6862
+ canCreateCanvas: isConversationScopedChannel(channelId),
6863
+ canPostToChannel: isConversationChannel(channelId),
6864
+ canAddReactions: isConversationScopedChannel(channelId)
6865
+ };
6866
+ }
6867
+
6976
6868
  // src/chat/sandbox/sandbox.ts
6977
6869
  import fs3 from "fs/promises";
6978
6870
  import path4 from "path";
@@ -7914,6 +7806,8 @@ function createSandboxExecutor(options) {
7914
7806
  pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
7915
7807
  });
7916
7808
  let commandError;
7809
+ let result;
7810
+ let restoreError;
7917
7811
  try {
7918
7812
  const commandResult2 = await activeSandbox.runCommand({
7919
7813
  cmd: "bash",
@@ -7929,7 +7823,7 @@ function createSandboxExecutor(options) {
7929
7823
  const stderrRaw = await commandResult2.stderr();
7930
7824
  const stdout = truncateOutput(stdoutRaw, boundedOutputLength);
7931
7825
  const stderr = truncateOutput(stderrRaw, boundedOutputLength);
7932
- return {
7826
+ result = {
7933
7827
  stdout: stdout.value,
7934
7828
  stderr: stderr.value,
7935
7829
  exitCode: commandResult2.exitCode,
@@ -7943,14 +7837,16 @@ function createSandboxExecutor(options) {
7943
7837
  if (headerTransforms && headerTransforms.length > 0) {
7944
7838
  try {
7945
7839
  await activeSandbox.updateNetworkPolicy(restoreNetworkPolicy);
7946
- } catch (restoreError) {
7947
- await invalidateSandboxInstance(activeSandbox, restoreError);
7948
- if (!commandError) {
7949
- throw restoreError;
7950
- }
7840
+ } catch (error) {
7841
+ restoreError = error;
7842
+ await invalidateSandboxInstance(activeSandbox, error);
7951
7843
  }
7952
7844
  }
7953
7845
  }
7846
+ if (restoreError && !commandError) {
7847
+ throw restoreError;
7848
+ }
7849
+ return result;
7954
7850
  },
7955
7851
  readFile: async (input) => await executeReadFile(input, {
7956
7852
  toolCallId: "sandbox-read-file",
@@ -8173,95 +8069,6 @@ function shouldEmitDevAgentTrace() {
8173
8069
  return process.env.NODE_ENV === "development";
8174
8070
  }
8175
8071
 
8176
- // src/chat/state/turn-session-store.ts
8177
- var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
8178
- var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
8179
- function agentTurnSessionKey(conversationId, sessionId) {
8180
- return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
8181
- }
8182
- function parseAgentTurnSessionCheckpoint(value) {
8183
- if (typeof value !== "string") {
8184
- return void 0;
8185
- }
8186
- try {
8187
- const parsed = JSON.parse(value);
8188
- if (!isRecord(parsed)) {
8189
- return void 0;
8190
- }
8191
- const status = parsed.state;
8192
- if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
8193
- return void 0;
8194
- }
8195
- const conversationId = parsed.conversationId;
8196
- const sessionId = parsed.sessionId;
8197
- const sliceId = parsed.sliceId;
8198
- const checkpointVersion = parsed.checkpointVersion;
8199
- const updatedAtMs = parsed.updatedAtMs;
8200
- if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
8201
- return void 0;
8202
- }
8203
- return {
8204
- checkpointVersion,
8205
- conversationId,
8206
- sessionId,
8207
- sliceId,
8208
- state: status,
8209
- updatedAtMs,
8210
- piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
8211
- ...Array.isArray(parsed.loadedSkillNames) ? {
8212
- loadedSkillNames: parsed.loadedSkillNames.filter(
8213
- (value2) => typeof value2 === "string"
8214
- )
8215
- } : {},
8216
- ...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
8217
- ...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
8218
- ...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
8219
- };
8220
- } catch {
8221
- return void 0;
8222
- }
8223
- }
8224
- async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
8225
- const stateAdapter = getStateAdapter();
8226
- await stateAdapter.connect();
8227
- const value = await stateAdapter.get(
8228
- agentTurnSessionKey(conversationId, sessionId)
8229
- );
8230
- return parseAgentTurnSessionCheckpoint(value);
8231
- }
8232
- async function upsertAgentTurnSessionCheckpoint(args) {
8233
- const stateAdapter = getStateAdapter();
8234
- await stateAdapter.connect();
8235
- const existing = await getAgentTurnSessionCheckpoint(
8236
- args.conversationId,
8237
- args.sessionId
8238
- );
8239
- const checkpoint = {
8240
- checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
8241
- conversationId: args.conversationId,
8242
- sessionId: args.sessionId,
8243
- sliceId: args.sliceId,
8244
- state: args.state,
8245
- updatedAtMs: Date.now(),
8246
- piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
8247
- ...Array.isArray(args.loadedSkillNames) ? {
8248
- loadedSkillNames: args.loadedSkillNames.filter(
8249
- (value) => typeof value === "string"
8250
- )
8251
- } : {},
8252
- ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
8253
- ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
8254
- ...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
8255
- };
8256
- const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
8257
- await stateAdapter.set(
8258
- agentTurnSessionKey(args.conversationId, args.sessionId),
8259
- JSON.stringify(checkpoint),
8260
- ttlMs
8261
- );
8262
- return checkpoint;
8263
- }
8264
-
8265
8072
  // src/chat/runtime/status-format.ts
8266
8073
  var SLACK_STATUS_MAX_LENGTH = 50;
8267
8074
  function truncateWithEllipsis(text, maxLength) {
@@ -8408,20 +8215,6 @@ function extractStatusUrlDomain(value) {
8408
8215
  }
8409
8216
 
8410
8217
  // src/chat/runtime/tool-status.ts
8411
- function formatCanonicalToolStatusName(value) {
8412
- if (typeof value !== "string") {
8413
- return void 0;
8414
- }
8415
- const trimmed = value.trim();
8416
- if (!trimmed) {
8417
- return void 0;
8418
- }
8419
- const mcpMatch = /^mcp__([^_]+)__(.+)$/.exec(trimmed);
8420
- if (mcpMatch) {
8421
- return compactStatusText(`${mcpMatch[1]}/${mcpMatch[2]}`, 40);
8422
- }
8423
- return compactStatusText(trimmed, 40);
8424
- }
8425
8218
  function formatToolStatus(toolName) {
8426
8219
  const known = {
8427
8220
  loadSkill: "Loading skill instructions",
@@ -8440,12 +8233,15 @@ function formatToolStatus(toolName) {
8440
8233
  slackListAddItems: "Updating tracking list",
8441
8234
  slackListUpdateItem: "Updating tracking list",
8442
8235
  imageGenerate: "Generating image",
8443
- searchTools: "Searching active tools",
8444
- useTool: "Running active tool"
8236
+ searchTools: "Searching active tools"
8445
8237
  };
8446
8238
  if (known[toolName]) {
8447
8239
  return known[toolName];
8448
8240
  }
8241
+ const mcpMatch = /^mcp__([^_]+)__(.+)$/.exec(toolName);
8242
+ if (mcpMatch) {
8243
+ return `Running ${mcpMatch[1]}/${mcpMatch[2]}`;
8244
+ }
8449
8245
  const readable = toolName.replaceAll("_", " ").trim();
8450
8246
  return readable.length > 0 ? `Running ${readable}` : "Running tool";
8451
8247
  }
@@ -8458,7 +8254,6 @@ function formatToolStatusWithInput(toolName, input) {
8458
8254
  const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
8459
8255
  const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
8460
8256
  const provider = obj ? compactStatusText(obj.provider, 20) : void 0;
8461
- const activeToolName = obj ? formatCanonicalToolStatusName(obj.tool_name ?? obj.toolName) : void 0;
8462
8257
  if (command && toolName === "bash") {
8463
8258
  return `Running ${command}`;
8464
8259
  }
@@ -8483,41 +8278,93 @@ function formatToolStatusWithInput(toolName, input) {
8483
8278
  if (query && toolName === "searchTools") {
8484
8279
  return `Searching tools for "${query}"`;
8485
8280
  }
8486
- if (activeToolName && toolName === "useTool") {
8487
- return `Running ${activeToolName}`;
8488
- }
8489
8281
  if (domain && toolName === "webFetch") {
8490
8282
  return `Fetching page from ${domain}`;
8491
8283
  }
8492
8284
  return formatToolStatus(toolName);
8493
8285
  }
8494
8286
 
8495
- // src/chat/tools/agent-tools.ts
8496
- import { Value } from "@sinclair/typebox/value";
8497
- function toToolContentText(value) {
8498
- if (typeof value === "string") return value;
8499
- try {
8500
- return JSON.stringify(value);
8501
- } catch {
8502
- return String(value);
8287
+ // src/chat/tools/execution/build-sandbox-input.ts
8288
+ function buildSandboxInput(toolName, params) {
8289
+ if (toolName === "bash") {
8290
+ return { command: String(params.command ?? "") };
8291
+ }
8292
+ if (toolName === "readFile") {
8293
+ return { path: String(params.path ?? "") };
8294
+ }
8295
+ if (toolName === "writeFile") {
8296
+ return {
8297
+ path: String(params.path ?? ""),
8298
+ content: String(params.content ?? "")
8299
+ };
8503
8300
  }
8301
+ return params;
8504
8302
  }
8505
- function isStructuredToolExecutionResult(value) {
8506
- const content = value?.content;
8507
- return typeof value === "object" && value !== null && Array.isArray(content) && content.every((part) => {
8508
- if (!part || typeof part !== "object") {
8509
- return false;
8510
- }
8511
- const record = part;
8512
- if (record.type === "text") {
8513
- return typeof record.text === "string";
8514
- }
8303
+
8304
+ // src/chat/tools/execution/inject-credentials.ts
8305
+ function resolveCredentialInjection(toolName, command, capabilityRuntime, sandbox) {
8306
+ if (toolName !== "bash" || !capabilityRuntime) {
8307
+ return {};
8308
+ }
8309
+ const headerTransforms = capabilityRuntime.getTurnHeaderTransforms();
8310
+ const env = capabilityRuntime.getTurnEnv();
8311
+ const isCustomCommand = /^jr-rpc(?:\s|$)/.test(command.trim());
8312
+ const shouldLog = !isCustomCommand && Boolean(headerTransforms && headerTransforms.length > 0);
8313
+ if (shouldLog) {
8314
+ const headerDomains = (headerTransforms ?? []).map(
8315
+ (transform) => transform.domain
8316
+ );
8317
+ logInfo(
8318
+ "credential_inject_start",
8319
+ {},
8320
+ {
8321
+ "app.skill.name": sandbox.getActiveSkill()?.name,
8322
+ "app.credential.delivery": "header_transform",
8323
+ "app.credential.header_domains": headerDomains
8324
+ },
8325
+ "Injecting scoped credential headers for sandbox command"
8326
+ );
8327
+ }
8328
+ return { headerTransforms, env };
8329
+ }
8330
+
8331
+ // src/chat/tools/execution/normalize-result.ts
8332
+ function isStructuredToolExecutionResult(value) {
8333
+ const content = value?.content;
8334
+ return typeof value === "object" && value !== null && Array.isArray(content) && content.every((part) => {
8335
+ if (!part || typeof part !== "object") {
8336
+ return false;
8337
+ }
8338
+ const record = part;
8339
+ if (record.type === "text") {
8340
+ return typeof record.text === "string";
8341
+ }
8515
8342
  if (record.type === "image") {
8516
8343
  return typeof record.data === "string" && typeof record.mimeType === "string";
8517
8344
  }
8518
8345
  return false;
8519
8346
  }) && "details" in value;
8520
8347
  }
8348
+ function toToolContentText(value) {
8349
+ if (typeof value === "string") return value;
8350
+ try {
8351
+ return JSON.stringify(value);
8352
+ } catch {
8353
+ return String(value);
8354
+ }
8355
+ }
8356
+ function normalizeToolResult(result, isSandboxResult) {
8357
+ const unwrapped = isSandboxResult && result && typeof result === "object" && "result" in result ? result.result : result;
8358
+ if (isStructuredToolExecutionResult(unwrapped)) {
8359
+ return unwrapped;
8360
+ }
8361
+ return {
8362
+ content: [{ type: "text", text: toToolContentText(unwrapped) }],
8363
+ details: unwrapped
8364
+ };
8365
+ }
8366
+
8367
+ // src/chat/tools/execution/tool-error-handler.ts
8521
8368
  function getToolErrorAttributes(error) {
8522
8369
  if (!(error instanceof SlackActionError)) {
8523
8370
  return {};
@@ -8530,6 +8377,44 @@ function getToolErrorAttributes(error) {
8530
8377
  ...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
8531
8378
  };
8532
8379
  }
8380
+ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
8381
+ setSpanAttributes({
8382
+ "error.type": error instanceof Error ? error.name : "tool_execution_error"
8383
+ });
8384
+ if (shouldTrace) {
8385
+ logWarn(
8386
+ "agent_tool_call_failed",
8387
+ traceContext,
8388
+ {
8389
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8390
+ "gen_ai.operation.name": "execute_tool",
8391
+ "gen_ai.tool.name": toolName,
8392
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
8393
+ "error.type": error instanceof Error ? error.name : "tool_execution_error",
8394
+ "error.message": error instanceof Error ? error.message : String(error)
8395
+ },
8396
+ "Agent tool call failed"
8397
+ );
8398
+ }
8399
+ if (!(error instanceof McpToolError)) {
8400
+ logException(
8401
+ error,
8402
+ "agent_tool_call_failed",
8403
+ {},
8404
+ {
8405
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8406
+ "gen_ai.operation.name": "execute_tool",
8407
+ "gen_ai.tool.name": toolName,
8408
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
8409
+ ...getToolErrorAttributes(error)
8410
+ },
8411
+ "Agent tool call failed"
8412
+ );
8413
+ }
8414
+ throw error;
8415
+ }
8416
+
8417
+ // src/chat/tools/agent-tools.ts
8533
8418
  function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, hooks) {
8534
8419
  const shouldTrace = shouldEmitDevAgentTrace();
8535
8420
  return Object.entries(tools).map(([toolName, toolDef]) => ({
@@ -8541,7 +8426,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8541
8426
  const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
8542
8427
  const toolArgumentsAttribute = serializeGenAiAttribute(params);
8543
8428
  hooks?.onToolCall?.(toolName);
8544
- const toolStartedAt = Date.now();
8545
8429
  const traceToolContext = {
8546
8430
  ...spanContext,
8547
8431
  conversationId: spanContext.conversationId,
@@ -8554,163 +8438,65 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8554
8438
  "gen_ai.execute_tool",
8555
8439
  spanContext,
8556
8440
  async () => {
8557
- if (!Value.Check(toolDef.inputSchema, params)) {
8558
- const details = [...Value.Errors(toolDef.inputSchema, params)].slice(0, 3).map((entry) => `${entry.path || "/"}: ${entry.message}`).join("; ");
8559
- const validationMessage = details.length > 0 ? details : "Invalid tool input";
8560
- const durationMs = Date.now() - toolStartedAt;
8561
- setSpanAttributes({
8562
- "app.ai.tool_duration_ms": durationMs,
8563
- "error.type": "tool_input_validation_error"
8564
- });
8565
- setSpanStatus("error");
8566
- logWarn(
8567
- "agent_tool_call_invalid_input",
8568
- {},
8569
- {
8570
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8571
- "gen_ai.operation.name": "execute_tool",
8572
- "gen_ai.tool.name": toolName,
8573
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8574
- "app.ai.tool_duration_ms": durationMs
8575
- },
8576
- "Agent tool call input validation failed"
8577
- );
8578
- logException(
8579
- new Error(validationMessage),
8580
- "agent_tool_call_invalid_input_exception",
8581
- {},
8582
- {
8583
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8584
- "gen_ai.operation.name": "execute_tool",
8585
- "gen_ai.tool.name": toolName,
8586
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8587
- "app.ai.tool_duration_ms": durationMs
8588
- },
8589
- "Agent tool call input validation failed with exception"
8590
- );
8591
- throw new Error(validationMessage);
8592
- }
8593
8441
  const parsed = params;
8594
8442
  try {
8595
8443
  if (typeof toolDef.execute !== "function") {
8596
- const resultDetails2 = { ok: true };
8597
- const durationMs2 = Date.now() - toolStartedAt;
8598
- const toolResultAttribute2 = serializeGenAiAttribute(resultDetails2);
8599
- setSpanAttributes({
8600
- "app.ai.tool_duration_ms": durationMs2,
8601
- "app.ai.tool_outcome": "success",
8602
- ...toolResultAttribute2 ? { "gen_ai.tool.call.result": toolResultAttribute2 } : {}
8603
- });
8604
- setSpanStatus("ok");
8444
+ const resultDetails = { ok: true };
8445
+ const toolResultAttribute2 = serializeGenAiAttribute(resultDetails);
8446
+ if (toolResultAttribute2) {
8447
+ setSpanAttributes({
8448
+ "gen_ai.tool.call.result": toolResultAttribute2
8449
+ });
8450
+ }
8605
8451
  return {
8606
8452
  content: [{ type: "text", text: "ok" }],
8607
- details: resultDetails2
8453
+ details: resultDetails
8608
8454
  };
8609
8455
  }
8610
- const injectedHeaders = toolName === "bash" ? capabilityRuntime?.getTurnHeaderTransforms() : void 0;
8611
- const injectedEnv = toolName === "bash" ? capabilityRuntime?.getTurnEnv() : void 0;
8612
8456
  const bashCommand = toolName === "bash" && typeof parsed.command === "string" ? parsed.command.trim() : "";
8613
- const isCustomBashCommand = toolName === "bash" && /^jr-rpc(?:\s|$)/.test(bashCommand);
8614
- const shouldLogCredentialInjection = toolName === "bash" && !isCustomBashCommand && Boolean(injectedHeaders && injectedHeaders.length > 0);
8615
- if (shouldLogCredentialInjection) {
8616
- const headerDomains = (injectedHeaders ?? []).map(
8617
- (transform) => transform.domain
8618
- );
8619
- logInfo(
8620
- "credential_inject_start",
8621
- {},
8622
- {
8623
- "app.skill.name": sandbox.getActiveSkill()?.name,
8624
- "app.credential.delivery": "header_transform",
8625
- "app.credential.header_domains": headerDomains
8626
- },
8627
- "Injecting scoped credential headers for sandbox command"
8628
- );
8629
- }
8630
- const hasBashCredentials = injectedHeaders || injectedEnv;
8631
- const sandboxInput = toolName === "bash" ? { command: String(parsed.command ?? "") } : toolName === "readFile" ? { path: String(parsed.path ?? "") } : toolName === "writeFile" ? {
8632
- path: String(parsed.path ?? ""),
8633
- content: String(parsed.content ?? "")
8634
- } : parsed;
8635
- const result = sandboxExecutor?.canExecute(toolName) ? await sandboxExecutor.execute({
8457
+ const injection = resolveCredentialInjection(
8458
+ toolName,
8459
+ bashCommand,
8460
+ capabilityRuntime,
8461
+ sandbox
8462
+ );
8463
+ const sandboxInput = buildSandboxInput(toolName, parsed);
8464
+ const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
8465
+ const result = isSandbox ? await sandboxExecutor.execute({
8636
8466
  toolName,
8637
- input: toolName === "bash" && hasBashCredentials ? {
8467
+ input: toolName === "bash" && (injection.headerTransforms || injection.env) ? {
8638
8468
  ...sandboxInput,
8639
- ...injectedHeaders ? { headerTransforms: injectedHeaders } : {},
8640
- ...injectedEnv ? { env: injectedEnv } : {}
8469
+ ...injection.headerTransforms ? { headerTransforms: injection.headerTransforms } : {},
8470
+ ...injection.env ? { env: injection.env } : {}
8641
8471
  } : sandboxInput
8642
8472
  }) : await toolDef.execute(parsed, {
8643
8473
  experimental_context: sandbox
8644
8474
  });
8645
- const resultDetails = sandboxExecutor?.canExecute(toolName) && result && typeof result === "object" && "result" in result ? result.result : result;
8646
- const durationMs = Date.now() - toolStartedAt;
8647
- const structuredToolResult = isStructuredToolExecutionResult(
8648
- resultDetails
8649
- ) ? resultDetails : void 0;
8475
+ const normalized = normalizeToolResult(result, isSandbox);
8650
8476
  const toolResultAttribute = serializeGenAiAttribute(
8651
- structuredToolResult?.details ?? resultDetails
8477
+ normalized.details
8652
8478
  );
8653
- setSpanAttributes({
8654
- "app.ai.tool_duration_ms": durationMs,
8655
- "app.ai.tool_outcome": "success",
8656
- ...toolResultAttribute ? { "gen_ai.tool.call.result": toolResultAttribute } : {}
8657
- });
8658
- setSpanStatus("ok");
8659
- if (structuredToolResult) {
8660
- return structuredToolResult;
8479
+ if (toolResultAttribute) {
8480
+ setSpanAttributes({
8481
+ "gen_ai.tool.call.result": toolResultAttribute
8482
+ });
8661
8483
  }
8662
- return {
8663
- content: [
8664
- { type: "text", text: toToolContentText(resultDetails) }
8665
- ],
8666
- details: resultDetails
8667
- };
8484
+ return normalized;
8668
8485
  } catch (error) {
8669
- const durationMs = Date.now() - toolStartedAt;
8670
- setSpanAttributes({
8671
- "app.ai.tool_duration_ms": durationMs,
8672
- "app.ai.tool_outcome": "error",
8673
- "error.type": error instanceof Error ? error.name : "tool_execution_error"
8674
- });
8675
- setSpanStatus("error");
8676
- if (shouldTrace) {
8677
- logWarn(
8678
- "agent_tool_call_failed",
8679
- traceToolContext,
8680
- {
8681
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8682
- "gen_ai.operation.name": "execute_tool",
8683
- "gen_ai.tool.name": toolName,
8684
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8685
- "app.ai.tool_duration_ms": durationMs,
8686
- "app.ai.tool_outcome": "error",
8687
- "error.type": error instanceof Error ? error.name : "tool_execution_error",
8688
- "error.message": error instanceof Error ? error.message : String(error)
8689
- },
8690
- "Agent tool call failed"
8691
- );
8692
- }
8693
- logException(
8486
+ handleToolExecutionError(
8694
8487
  error,
8695
- "agent_tool_call_failed",
8696
- {},
8697
- {
8698
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8699
- "gen_ai.operation.name": "execute_tool",
8700
- "gen_ai.tool.name": toolName,
8701
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8702
- "app.ai.tool_duration_ms": durationMs,
8703
- ...getToolErrorAttributes(error)
8704
- },
8705
- "Agent tool call failed"
8488
+ toolName,
8489
+ normalizedToolCallId,
8490
+ shouldTrace,
8491
+ traceToolContext
8706
8492
  );
8707
- throw error;
8708
8493
  }
8709
8494
  },
8710
8495
  {
8711
8496
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8712
8497
  "gen_ai.operation.name": "execute_tool",
8713
8498
  "gen_ai.tool.name": toolName,
8499
+ "gen_ai.tool.description": toolDef.description,
8714
8500
  ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8715
8501
  ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
8716
8502
  }
@@ -8779,6 +8565,65 @@ function resolveReplyDelivery(args) {
8779
8565
  };
8780
8566
  }
8781
8567
 
8568
+ // src/chat/services/reply-delivery-plan.ts
8569
+ var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
8570
+ var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
8571
+ var REACTION_ALIAS_PREFIX_RE = /^:[a-z0-9_+-]*$/i;
8572
+ function normalizeReactionAckText(text) {
8573
+ return text.trim().toLowerCase().replace(/[!.]+$/g, "");
8574
+ }
8575
+ function isRedundantReactionAckText(text) {
8576
+ const trimmed = text.trim();
8577
+ if (!trimmed) {
8578
+ return false;
8579
+ }
8580
+ if (REACTION_ONLY_ACK_RE.test(trimmed)) {
8581
+ return true;
8582
+ }
8583
+ const normalized = normalizeReactionAckText(text);
8584
+ return REDUNDANT_REACTION_ACK_TEXT.includes(
8585
+ normalized
8586
+ );
8587
+ }
8588
+ function isPotentialRedundantReactionAckText(text) {
8589
+ const trimmed = text.trim();
8590
+ if (!trimmed) {
8591
+ return true;
8592
+ }
8593
+ if (REACTION_ONLY_ACK_RE.test(trimmed) || REACTION_ALIAS_PREFIX_RE.test(trimmed)) {
8594
+ return true;
8595
+ }
8596
+ const normalized = normalizeReactionAckText(text);
8597
+ return REDUNDANT_REACTION_ACK_TEXT.some(
8598
+ (candidate) => candidate.startsWith(normalized)
8599
+ );
8600
+ }
8601
+ function buildReplyDeliveryPlan(args) {
8602
+ const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
8603
+ let attachFiles = "none";
8604
+ if (args.hasFiles && mode === "thread") {
8605
+ attachFiles = args.streamingThreadReply ? "followup" : "inline";
8606
+ }
8607
+ return {
8608
+ mode,
8609
+ postThreadText: mode === "thread",
8610
+ attachFiles
8611
+ };
8612
+ }
8613
+
8614
+ // src/chat/services/channel-intent.ts
8615
+ function isExplicitChannelPostIntent(text) {
8616
+ if (!/\bchannel\b/i.test(text)) {
8617
+ return false;
8618
+ }
8619
+ const directChannelVerb = /\b(show|post|send|share|say|announce|broadcast)\b[\s\S]{0,80}\b(?:the\s+)?channel\b/i;
8620
+ if (directChannelVerb.test(text)) {
8621
+ return true;
8622
+ }
8623
+ const scopedChannelVerb = /\b(post|send|share|say|announce|broadcast)\b[\s\S]{0,80}\b(?:in|to)\b[\s\S]{0,40}\b(?:the\s+)?channel\b/i;
8624
+ return scopedChannelVerb.test(text);
8625
+ }
8626
+
8782
8627
  // src/chat/services/attachment-claims.ts
8783
8628
  function splitSentences(text) {
8784
8629
  return text.split(/\n+/).flatMap((line) => line.split(/(?<=[.!?])\s+/)).map((part) => part.trim()).filter((part) => part.length > 0);
@@ -8808,8 +8653,268 @@ function enforceAttachmentClaimTruth(text, hasAttachedFiles) {
8808
8653
  Note: No file was attached in this turn. I need to attach the file before claiming it is shared.`;
8809
8654
  }
8810
8655
 
8811
- // src/chat/respond.ts
8812
- var startupDiscoveryLogged = false;
8656
+ // src/chat/services/turn-result.ts
8657
+ function buildTurnResult(input) {
8658
+ const {
8659
+ newMessages,
8660
+ userInput,
8661
+ replyFiles,
8662
+ artifactStatePatch,
8663
+ toolCalls,
8664
+ sandboxId,
8665
+ sandboxDependencyProfileHash,
8666
+ hasTextDeltaCallback,
8667
+ shouldTrace,
8668
+ spanContext,
8669
+ correlation,
8670
+ assistantUserName
8671
+ } = input;
8672
+ const toolResults = newMessages.filter(isToolResultMessage);
8673
+ const assistantMessages = newMessages.filter(isAssistantMessage);
8674
+ const primaryText = assistantMessages.map((message) => extractAssistantText(message)).join("\n\n").trim();
8675
+ const oauthStartedMessage = extractOAuthStartedMessageFromToolResults(toolResults);
8676
+ const toolErrorCount = toolResults.filter((result) => result.isError).length;
8677
+ const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
8678
+ const successfulToolNames = new Set(
8679
+ toolResults.filter((result) => !isToolResultError(result)).map((result) => normalizeToolNameFromResult(result)).filter((value) => Boolean(value))
8680
+ );
8681
+ const channelPostPerformed = successfulToolNames.has(
8682
+ "slackChannelPostMessage"
8683
+ );
8684
+ const deliveryPlan = buildReplyDeliveryPlan({
8685
+ explicitChannelPostIntent,
8686
+ channelPostPerformed,
8687
+ hasFiles: replyFiles.length > 0,
8688
+ streamingThreadReply: hasTextDeltaCallback
8689
+ });
8690
+ const deliveryMode = deliveryPlan.mode;
8691
+ if (!primaryText && !oauthStartedMessage) {
8692
+ logWarn(
8693
+ "ai_model_response_empty",
8694
+ {
8695
+ slackThreadId: correlation?.threadId,
8696
+ slackUserId: correlation?.requesterId,
8697
+ slackChannelId: correlation?.channelId,
8698
+ runId: correlation?.runId,
8699
+ assistantUserName,
8700
+ modelId: botConfig.modelId
8701
+ },
8702
+ {
8703
+ "app.ai.tool_results": toolResults.length,
8704
+ "app.ai.tool_error_results": toolErrorCount,
8705
+ "app.ai.generated_files": input.generatedFileCount
8706
+ },
8707
+ "Model returned empty text response"
8708
+ );
8709
+ }
8710
+ const lastAssistant = assistantMessages.at(-1);
8711
+ const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
8712
+ const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
8713
+ const usedPrimaryText = Boolean(primaryText);
8714
+ const outcome = primaryText || oauthStartedMessage ? stopReason === "error" ? "provider_error" : "success" : "execution_failure";
8715
+ const fallbackText = oauthStartedMessage ?? buildExecutionFailureMessage(toolErrorCount);
8716
+ const responseText = primaryText || fallbackText;
8717
+ const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
8718
+ const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
8719
+ const resolvedOutcome = escapedOrRawPayload ? oauthStartedMessage ? outcome : "execution_failure" : outcome;
8720
+ if (shouldTrace) {
8721
+ logInfo(
8722
+ "agent_message_out",
8723
+ spanContext,
8724
+ {
8725
+ "app.message.kind": "assistant_outbound",
8726
+ "app.message.length": resolvedText.length,
8727
+ "app.message.output": summarizeMessageText(resolvedText),
8728
+ "app.ai.outcome": resolvedOutcome,
8729
+ "app.ai.assistant_messages": assistantMessages.length,
8730
+ ...stopReason ? { "gen_ai.response.finish_reasons": [stopReason] } : {}
8731
+ },
8732
+ "Agent message sent"
8733
+ );
8734
+ }
8735
+ const resolvedDiagnostics = {
8736
+ outcome: resolvedOutcome,
8737
+ modelId: botConfig.modelId,
8738
+ assistantMessageCount: assistantMessages.length,
8739
+ toolCalls,
8740
+ toolResultCount: toolResults.length,
8741
+ toolErrorCount,
8742
+ usedPrimaryText,
8743
+ stopReason,
8744
+ errorMessage,
8745
+ providerError: void 0
8746
+ };
8747
+ return {
8748
+ text: resolvedText,
8749
+ files: replyFiles.length > 0 ? replyFiles : void 0,
8750
+ artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
8751
+ deliveryPlan,
8752
+ deliveryMode,
8753
+ sandboxId,
8754
+ sandboxDependencyProfileHash,
8755
+ diagnostics: resolvedDiagnostics
8756
+ };
8757
+ }
8758
+
8759
+ // src/chat/state/turn-session-store.ts
8760
+ var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
8761
+ var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
8762
+ function agentTurnSessionKey(conversationId, sessionId) {
8763
+ return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
8764
+ }
8765
+ function parseAgentTurnSessionCheckpoint(value) {
8766
+ if (typeof value !== "string") {
8767
+ return void 0;
8768
+ }
8769
+ try {
8770
+ const parsed = JSON.parse(value);
8771
+ if (!isRecord(parsed)) {
8772
+ return void 0;
8773
+ }
8774
+ const status = parsed.state;
8775
+ if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
8776
+ return void 0;
8777
+ }
8778
+ const conversationId = parsed.conversationId;
8779
+ const sessionId = parsed.sessionId;
8780
+ const sliceId = parsed.sliceId;
8781
+ const checkpointVersion = parsed.checkpointVersion;
8782
+ const updatedAtMs = parsed.updatedAtMs;
8783
+ if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
8784
+ return void 0;
8785
+ }
8786
+ return {
8787
+ checkpointVersion,
8788
+ conversationId,
8789
+ sessionId,
8790
+ sliceId,
8791
+ state: status,
8792
+ updatedAtMs,
8793
+ piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
8794
+ ...Array.isArray(parsed.loadedSkillNames) ? {
8795
+ loadedSkillNames: parsed.loadedSkillNames.filter(
8796
+ (value2) => typeof value2 === "string"
8797
+ )
8798
+ } : {},
8799
+ ...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
8800
+ ...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
8801
+ ...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
8802
+ };
8803
+ } catch {
8804
+ return void 0;
8805
+ }
8806
+ }
8807
+ async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
8808
+ const stateAdapter = getStateAdapter();
8809
+ await stateAdapter.connect();
8810
+ const value = await stateAdapter.get(
8811
+ agentTurnSessionKey(conversationId, sessionId)
8812
+ );
8813
+ return parseAgentTurnSessionCheckpoint(value);
8814
+ }
8815
+ async function upsertAgentTurnSessionCheckpoint(args) {
8816
+ const stateAdapter = getStateAdapter();
8817
+ await stateAdapter.connect();
8818
+ const existing = await getAgentTurnSessionCheckpoint(
8819
+ args.conversationId,
8820
+ args.sessionId
8821
+ );
8822
+ const checkpoint = {
8823
+ checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
8824
+ conversationId: args.conversationId,
8825
+ sessionId: args.sessionId,
8826
+ sliceId: args.sliceId,
8827
+ state: args.state,
8828
+ updatedAtMs: Date.now(),
8829
+ piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
8830
+ ...Array.isArray(args.loadedSkillNames) ? {
8831
+ loadedSkillNames: args.loadedSkillNames.filter(
8832
+ (value) => typeof value === "string"
8833
+ )
8834
+ } : {},
8835
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
8836
+ ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
8837
+ ...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
8838
+ };
8839
+ const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
8840
+ await stateAdapter.set(
8841
+ agentTurnSessionKey(args.conversationId, args.sessionId),
8842
+ JSON.stringify(checkpoint),
8843
+ ttlMs
8844
+ );
8845
+ return checkpoint;
8846
+ }
8847
+
8848
+ // src/chat/services/turn-checkpoint.ts
8849
+ async function loadTurnCheckpoint(ctx) {
8850
+ const canUseTurnSession = Boolean(ctx.conversationId && ctx.sessionId);
8851
+ const existingCheckpoint = canUseTurnSession && ctx.conversationId && ctx.sessionId ? await getAgentTurnSessionCheckpoint(ctx.conversationId, ctx.sessionId) : void 0;
8852
+ const hasAwaitingResumeCheckpoint = Boolean(
8853
+ existingCheckpoint && existingCheckpoint.state === "awaiting_resume" && existingCheckpoint.piMessages.length > 0
8854
+ );
8855
+ return {
8856
+ canUseTurnSession,
8857
+ resumedFromCheckpoint: hasAwaitingResumeCheckpoint,
8858
+ currentSliceId: hasAwaitingResumeCheckpoint ? existingCheckpoint.sliceId : 1,
8859
+ existingCheckpoint
8860
+ };
8861
+ }
8862
+ async function persistCompletedCheckpoint(args) {
8863
+ await upsertAgentTurnSessionCheckpoint({
8864
+ conversationId: args.conversationId,
8865
+ sessionId: args.sessionId,
8866
+ sliceId: args.sliceId,
8867
+ state: "completed",
8868
+ piMessages: args.allMessages,
8869
+ loadedSkillNames: args.loadedSkillNames
8870
+ });
8871
+ }
8872
+ async function persistAuthPauseCheckpoint(args) {
8873
+ const nextSliceId = args.currentSliceId + 1;
8874
+ try {
8875
+ const latestCheckpoint = await getAgentTurnSessionCheckpoint(
8876
+ args.conversationId,
8877
+ args.sessionId
8878
+ );
8879
+ const piMessages = trimTrailingAssistantMessages(
8880
+ args.messages.length > 0 ? args.messages : latestCheckpoint?.piMessages ?? []
8881
+ );
8882
+ await upsertAgentTurnSessionCheckpoint({
8883
+ conversationId: args.conversationId,
8884
+ sessionId: args.sessionId,
8885
+ sliceId: nextSliceId,
8886
+ state: "awaiting_resume",
8887
+ piMessages,
8888
+ loadedSkillNames: args.loadedSkillNames,
8889
+ resumeReason: "auth",
8890
+ resumedFromSliceId: args.currentSliceId,
8891
+ errorMessage: args.errorMessage
8892
+ });
8893
+ } catch (checkpointError) {
8894
+ logException(
8895
+ checkpointError,
8896
+ "agent_turn_auth_resume_checkpoint_failed",
8897
+ {
8898
+ slackThreadId: args.logContext.threadId,
8899
+ slackUserId: args.logContext.requesterId,
8900
+ slackChannelId: args.logContext.channelId,
8901
+ runId: args.logContext.runId,
8902
+ assistantUserName: args.logContext.assistantUserName,
8903
+ modelId: args.logContext.modelId
8904
+ },
8905
+ {
8906
+ "app.ai.resume_conversation_id": args.conversationId,
8907
+ "app.ai.resume_session_id": args.sessionId,
8908
+ "app.ai.resume_from_slice_id": args.currentSliceId,
8909
+ "app.ai.resume_next_slice_id": nextSliceId
8910
+ },
8911
+ "Failed to persist auth checkpoint before retry"
8912
+ );
8913
+ }
8914
+ return nextSliceId;
8915
+ }
8916
+
8917
+ // src/chat/services/mcp-auth-orchestration.ts
8813
8918
  var McpAuthorizationPauseError = class extends Error {
8814
8919
  provider;
8815
8920
  constructor(provider) {
@@ -8818,6 +8923,86 @@ var McpAuthorizationPauseError = class extends Error {
8818
8923
  this.provider = provider;
8819
8924
  }
8820
8925
  };
8926
+ function createMcpAuthOrchestration(deps, abortAgent) {
8927
+ let pendingPause;
8928
+ const authSessionIdsByProvider = /* @__PURE__ */ new Map();
8929
+ const authProviderFactory = async (plugin) => {
8930
+ if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
8931
+ return void 0;
8932
+ }
8933
+ const provider = await createMcpOAuthClientProvider({
8934
+ provider: plugin.manifest.name,
8935
+ conversationId: deps.conversationId,
8936
+ sessionId: deps.sessionId,
8937
+ userId: deps.requesterId,
8938
+ userMessage: deps.userMessage,
8939
+ ...deps.channelId ? { channelId: deps.channelId } : {},
8940
+ ...deps.threadTs ? { threadTs: deps.threadTs } : {},
8941
+ ...deps.toolChannelId ? { toolChannelId: deps.toolChannelId } : {},
8942
+ configuration: deps.getConfiguration(),
8943
+ artifactState: deps.getArtifactState()
8944
+ });
8945
+ authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
8946
+ return provider;
8947
+ };
8948
+ const onAuthorizationRequired = async (provider) => {
8949
+ if (pendingPause) {
8950
+ return true;
8951
+ }
8952
+ const authSessionId = authSessionIdsByProvider.get(provider);
8953
+ if (!authSessionId || !deps.requesterId) {
8954
+ throw new Error(
8955
+ `Missing MCP auth session context for plugin "${provider}"`
8956
+ );
8957
+ }
8958
+ const latestArtifactState = deps.getMergedArtifactState();
8959
+ await patchMcpAuthSession(authSessionId, {
8960
+ configuration: { ...deps.getConfiguration() },
8961
+ artifactState: latestArtifactState,
8962
+ toolChannelId: deps.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? deps.channelId
8963
+ });
8964
+ const authSession = await getMcpAuthSession(authSessionId);
8965
+ if (!authSession?.authorizationUrl) {
8966
+ throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
8967
+ }
8968
+ const delivery = await deliverPrivateMessage({
8969
+ channelId: authSession.channelId,
8970
+ threadTs: authSession.threadTs,
8971
+ userId: authSession.userId,
8972
+ text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
8973
+ });
8974
+ if (!delivery) {
8975
+ throw new Error(
8976
+ `Unable to deliver MCP authorization link for plugin "${provider}"`
8977
+ );
8978
+ }
8979
+ pendingPause = new McpAuthorizationPauseError(provider);
8980
+ abortAgent();
8981
+ return true;
8982
+ };
8983
+ return {
8984
+ authProviderFactory,
8985
+ onAuthorizationRequired,
8986
+ getPendingPause: () => pendingPause
8987
+ };
8988
+ }
8989
+
8990
+ // src/chat/respond.ts
8991
+ var startupDiscoveryLogged = false;
8992
+ function mcpToolsToDefinitions(mcpTools) {
8993
+ const defs = {};
8994
+ for (const tool2 of mcpTools) {
8995
+ defs[tool2.name] = {
8996
+ description: tool2.description,
8997
+ // Raw JSON Schema from MCP servers — not a TypeBox TSchema, but
8998
+ // pi-agent-core validates with AJV and the Anthropic provider reads
8999
+ // .properties/.required, so raw JSON Schema works at runtime.
9000
+ inputSchema: tool2.parameters,
9001
+ execute: async (args) => tool2.execute(args)
9002
+ };
9003
+ }
9004
+ return defs;
9005
+ }
8821
9006
  async function maybeReplaceAgentMessages(agent, messages) {
8822
9007
  const resumable = agent;
8823
9008
  if (typeof resumable.replaceMessages !== "function") {
@@ -8842,7 +9027,6 @@ async function generateAssistantReply(messageText, context = {}) {
8842
9027
  let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
8843
9028
  let loadedSkillNamesForResume = [];
8844
9029
  let mcpToolManager;
8845
- let pendingMcpAuthorizationPause;
8846
9030
  try {
8847
9031
  const shouldTrace = shouldEmitDevAgentTrace();
8848
9032
  const spanContext = {
@@ -8901,15 +9085,13 @@ async function generateAssistantReply(messageText, context = {}) {
8901
9085
  const activeSkills = [];
8902
9086
  const skillSandbox = new SkillSandbox(availableSkills, activeSkills);
8903
9087
  const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(context);
8904
- const canUseTurnSession = Boolean(sessionConversationId && sessionId);
9088
+ const checkpointState = await loadTurnCheckpoint({
9089
+ conversationId: sessionConversationId,
9090
+ sessionId
9091
+ });
9092
+ const { resumedFromCheckpoint, currentSliceId, existingCheckpoint } = checkpointState;
8905
9093
  timeoutResumeConversationId = sessionConversationId;
8906
9094
  timeoutResumeSessionId = sessionId;
8907
- const existingTurnCheckpoint = canUseTurnSession && sessionConversationId && sessionId ? await getAgentTurnSessionCheckpoint(sessionConversationId, sessionId) : void 0;
8908
- const hasAwaitingResumeCheckpoint = Boolean(
8909
- existingTurnCheckpoint && existingTurnCheckpoint.state === "awaiting_resume" && existingTurnCheckpoint.piMessages.length > 0
8910
- );
8911
- const resumedFromCheckpoint = hasAwaitingResumeCheckpoint;
8912
- const currentSliceId = hasAwaitingResumeCheckpoint ? existingTurnCheckpoint.sliceId : 1;
8913
9095
  timeoutResumeSliceId = currentSliceId;
8914
9096
  const capabilityRuntime = createSkillCapabilityRuntime({
8915
9097
  invocationArgs: skillInvocation?.args,
@@ -8946,7 +9128,7 @@ async function generateAssistantReply(messageText, context = {}) {
8946
9128
  lastKnownSandboxDependencyProfileHash = sandboxExecutor.getDependencyProfileHash();
8947
9129
  sandboxExecutor.configureSkills(availableSkills);
8948
9130
  const sandbox = await sandboxExecutor.createSandbox();
8949
- for (const skillName of existingTurnCheckpoint?.loadedSkillNames ?? []) {
9131
+ for (const skillName of existingCheckpoint?.loadedSkillNames ?? []) {
8950
9132
  const preloaded = await skillSandbox.loadSkill(skillName);
8951
9133
  if (preloaded) {
8952
9134
  upsertActiveSkill(activeSkills, preloaded);
@@ -8960,79 +9142,36 @@ async function generateAssistantReply(messageText, context = {}) {
8960
9142
  }
8961
9143
  const userTurnText = buildUserTurnText(
8962
9144
  userInput,
8963
- context.conversationContext
9145
+ context.conversationContext,
9146
+ {
9147
+ sessionContext: { conversationId: sessionConversationId },
9148
+ turnContext: { traceId: getActiveTraceId() }
9149
+ }
8964
9150
  );
8965
9151
  timeoutResumeMessages = [];
8966
- pendingMcpAuthorizationPause = void 0;
8967
9152
  const generatedFiles = [];
8968
9153
  const replyFiles = [];
8969
9154
  const artifactStatePatch = {};
8970
9155
  const toolCalls = [];
8971
- const mcpAuthSessionIdsByProvider = /* @__PURE__ */ new Map();
8972
9156
  let agent;
8973
- mcpToolManager = new McpToolManager(getPluginMcpProviders(), {
8974
- authProviderFactory: async (plugin) => {
8975
- if (!sessionConversationId || !sessionId || !context.requester?.userId) {
8976
- return void 0;
8977
- }
8978
- const provider = await createMcpOAuthClientProvider({
8979
- provider: plugin.manifest.name,
8980
- conversationId: sessionConversationId,
8981
- sessionId,
8982
- userId: context.requester.userId,
8983
- userMessage: userInput,
8984
- ...context.correlation?.channelId ? { channelId: context.correlation.channelId } : {},
8985
- ...context.correlation?.threadTs ? { threadTs: context.correlation.threadTs } : {},
8986
- ...context.toolChannelId ? { toolChannelId: context.toolChannelId } : {},
8987
- configuration: configurationValues,
8988
- artifactState: context.artifactState
8989
- });
8990
- mcpAuthSessionIdsByProvider.set(
8991
- plugin.manifest.name,
8992
- provider.authSessionId
8993
- );
8994
- return provider;
9157
+ const mcpAuth = createMcpAuthOrchestration(
9158
+ {
9159
+ conversationId: sessionConversationId,
9160
+ sessionId,
9161
+ requesterId: context.requester?.userId,
9162
+ channelId: context.correlation?.channelId,
9163
+ threadTs: context.correlation?.threadTs,
9164
+ toolChannelId: context.toolChannelId,
9165
+ userMessage: userInput,
9166
+ getConfiguration: () => configurationValues,
9167
+ getArtifactState: () => context.artifactState,
9168
+ getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch)
8995
9169
  },
8996
- onAuthorizationRequired: async (provider) => {
8997
- if (pendingMcpAuthorizationPause) {
8998
- return true;
8999
- }
9000
- const authSessionId = mcpAuthSessionIdsByProvider.get(provider);
9001
- if (!authSessionId || !context.requester?.userId) {
9002
- throw new Error(
9003
- `Missing MCP auth session context for plugin "${provider}"`
9004
- );
9005
- }
9006
- const latestArtifactState = mergeArtifactsState(
9007
- context.artifactState ?? {},
9008
- artifactStatePatch
9009
- );
9010
- await patchMcpAuthSession(authSessionId, {
9011
- configuration: { ...configurationValues },
9012
- artifactState: latestArtifactState,
9013
- toolChannelId: context.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? context.correlation?.channelId
9014
- });
9015
- const authSession = await getMcpAuthSession(authSessionId);
9016
- if (!authSession?.authorizationUrl) {
9017
- throw new Error(
9018
- `Missing MCP authorization URL for plugin "${provider}"`
9019
- );
9020
- }
9021
- const delivery = await deliverPrivateMessage({
9022
- channelId: authSession.channelId,
9023
- threadTs: authSession.threadTs,
9024
- userId: authSession.userId,
9025
- text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
9026
- });
9027
- if (!delivery) {
9028
- throw new Error(
9029
- `Unable to deliver MCP authorization link for plugin "${provider}"`
9030
- );
9031
- }
9032
- pendingMcpAuthorizationPause = new McpAuthorizationPauseError(provider);
9033
- agent?.abort();
9034
- return true;
9035
- }
9170
+ () => agent?.abort()
9171
+ );
9172
+ mcpToolManager = new McpToolManager(getPluginMcpProviders(), {
9173
+ authProviderFactory: mcpAuth.authProviderFactory,
9174
+ onAuthorizationRequired: mcpAuth.onAuthorizationRequired
9036
9175
  });
9037
9176
  const turnMcpToolManager = mcpToolManager;
9038
9177
  const syncResumeState = () => {
@@ -9075,22 +9214,25 @@ async function generateAssistantReply(messageText, context = {}) {
9075
9214
  syncResumeState();
9076
9215
  await turnMcpToolManager.activateForSkill(effective);
9077
9216
  syncResumeState();
9078
- if (pendingMcpAuthorizationPause) {
9217
+ if (mcpAuth.getPendingPause()) {
9079
9218
  return void 0;
9080
9219
  }
9081
9220
  if (!effective.pluginProvider) {
9082
9221
  return void 0;
9083
9222
  }
9223
+ syncMcpAgentTools();
9084
9224
  return {
9085
9225
  available_tools: turnMcpToolManager.getActiveToolCatalog(activeSkills, {
9086
9226
  provider: effective.pluginProvider
9087
- }).map(toExposedToolSummary),
9088
- tool_search_available: true
9227
+ }).map(toExposedToolSummary)
9089
9228
  };
9090
9229
  }
9091
9230
  },
9092
9231
  {
9093
9232
  channelId: context.toolChannelId ?? context.correlation?.channelId,
9233
+ channelCapabilities: resolveChannelCapabilities(
9234
+ context.toolChannelId ?? context.correlation?.channelId
9235
+ ),
9094
9236
  messageTs: context.correlation?.messageTs,
9095
9237
  threadTs: context.correlation?.threadTs,
9096
9238
  userText: userInput,
@@ -9105,9 +9247,9 @@ async function generateAssistantReply(messageText, context = {}) {
9105
9247
  for (const skill of activeSkills) {
9106
9248
  await turnMcpToolManager.activateForSkill(skill);
9107
9249
  syncResumeState();
9108
- if (pendingMcpAuthorizationPause) {
9109
- timeoutResumeMessages = existingTurnCheckpoint?.piMessages ?? [];
9110
- throw pendingMcpAuthorizationPause;
9250
+ if (mcpAuth.getPendingPause()) {
9251
+ timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
9252
+ throw mcpAuth.getPendingPause();
9111
9253
  }
9112
9254
  }
9113
9255
  syncResumeState();
@@ -9152,6 +9294,11 @@ async function generateAssistantReply(messageText, context = {}) {
9152
9294
  content: userContentParts.map((part) => toObservablePromptPart(part))
9153
9295
  }
9154
9296
  ]);
9297
+ const agentToolHooks = {
9298
+ onToolCall: (toolName) => {
9299
+ toolCalls.push(toolName);
9300
+ }
9301
+ };
9155
9302
  const baseAgentTools = createAgentTools(
9156
9303
  tools,
9157
9304
  skillSandbox,
@@ -9159,18 +9306,31 @@ async function generateAssistantReply(messageText, context = {}) {
9159
9306
  context.onStatus,
9160
9307
  sandboxExecutor,
9161
9308
  capabilityRuntime,
9162
- {
9163
- onToolCall: (toolName) => {
9164
- toolCalls.push(toolName);
9165
- }
9166
- }
9309
+ agentToolHooks
9167
9310
  );
9311
+ const agentTools = [...baseAgentTools];
9312
+ const syncMcpAgentTools = () => {
9313
+ const mcpTools = turnMcpToolManager.getResolvedActiveTools(activeSkills);
9314
+ const mcpDefs = mcpToolsToDefinitions(mcpTools);
9315
+ const mcpAgentTools = createAgentTools(
9316
+ mcpDefs,
9317
+ skillSandbox,
9318
+ spanContext,
9319
+ context.onStatus,
9320
+ sandboxExecutor,
9321
+ capabilityRuntime,
9322
+ agentToolHooks
9323
+ );
9324
+ agentTools.length = 0;
9325
+ agentTools.push(...baseAgentTools, ...mcpAgentTools);
9326
+ };
9327
+ syncMcpAgentTools();
9168
9328
  agent = new Agent({
9169
9329
  getApiKey: () => getPiGatewayApiKeyOverride(),
9170
9330
  initialState: {
9171
9331
  systemPrompt: baseInstructions,
9172
9332
  model: resolveGatewayModel(botConfig.modelId),
9173
- tools: baseAgentTools
9333
+ tools: agentTools
9174
9334
  }
9175
9335
  });
9176
9336
  let hasEmittedText = false;
@@ -9182,16 +9342,10 @@ async function generateAssistantReply(messageText, context = {}) {
9182
9342
  }
9183
9343
  return;
9184
9344
  }
9185
- if (event.type !== "message_update") {
9186
- return;
9187
- }
9188
- if (event.assistantMessageEvent.type !== "text_delta") {
9189
- return;
9190
- }
9345
+ if (event.type !== "message_update") return;
9346
+ if (event.assistantMessageEvent.type !== "text_delta") return;
9191
9347
  const deltaText = event.assistantMessageEvent.delta;
9192
- if (!deltaText) {
9193
- return;
9194
- }
9348
+ if (!deltaText) return;
9195
9349
  const text = needsSeparator ? "\n\n" + deltaText : deltaText;
9196
9350
  needsSeparator = false;
9197
9351
  hasEmittedText = true;
@@ -9213,7 +9367,7 @@ async function generateAssistantReply(messageText, context = {}) {
9213
9367
  if (resumedFromCheckpoint) {
9214
9368
  const didReplace = await maybeReplaceAgentMessages(
9215
9369
  agent,
9216
- existingTurnCheckpoint.piMessages
9370
+ existingCheckpoint.piMessages
9217
9371
  );
9218
9372
  if (!didReplace) {
9219
9373
  throw new Error(
@@ -9265,9 +9419,9 @@ async function generateAssistantReply(messageText, context = {}) {
9265
9419
  });
9266
9420
  timeoutResumeMessages = [...agent.state.messages];
9267
9421
  }
9268
- if (pendingMcpAuthorizationPause) {
9422
+ if (mcpAuth.getPendingPause()) {
9269
9423
  timeoutResumeMessages = [...agent.state.messages];
9270
- throw pendingMcpAuthorizationPause;
9424
+ throw mcpAuth.getPendingPause();
9271
9425
  }
9272
9426
  throw error;
9273
9427
  } finally {
@@ -9279,12 +9433,9 @@ async function generateAssistantReply(messageText, context = {}) {
9279
9433
  beforeMessageCount
9280
9434
  );
9281
9435
  completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
9282
- if (pendingMcpAuthorizationPause && !completedAssistantTurn) {
9436
+ if (mcpAuth.getPendingPause() && !completedAssistantTurn) {
9283
9437
  timeoutResumeMessages = [...agent.state.messages];
9284
- throw pendingMcpAuthorizationPause;
9285
- }
9286
- if (pendingMcpAuthorizationPause && completedAssistantTurn) {
9287
- pendingMcpAuthorizationPause = void 0;
9438
+ throw mcpAuth.getPendingPause();
9288
9439
  }
9289
9440
  const outputMessages = newMessages.filter(isAssistantMessage);
9290
9441
  const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
@@ -9308,171 +9459,51 @@ async function generateAssistantReply(messageText, context = {}) {
9308
9459
  } finally {
9309
9460
  unsubscribe();
9310
9461
  }
9311
- if (pendingMcpAuthorizationPause && !completedAssistantTurn) {
9312
- throw pendingMcpAuthorizationPause;
9462
+ if (mcpAuth.getPendingPause() && !completedAssistantTurn) {
9463
+ throw mcpAuth.getPendingPause();
9313
9464
  }
9314
- if (canUseTurnSession && sessionConversationId && sessionId) {
9315
- await upsertAgentTurnSessionCheckpoint({
9465
+ if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
9466
+ await persistCompletedCheckpoint({
9316
9467
  conversationId: sessionConversationId,
9317
9468
  sessionId,
9318
9469
  sliceId: currentSliceId,
9319
- state: "completed",
9320
- piMessages: agent.state.messages,
9470
+ allMessages: agent.state.messages,
9321
9471
  loadedSkillNames: activeSkills.map((skill) => skill.name)
9322
9472
  });
9323
9473
  }
9324
- const toolResults = newMessages.filter(isToolResultMessage);
9325
- const assistantMessages = newMessages.filter(isAssistantMessage);
9326
- const primaryText = assistantMessages.map((message) => extractAssistantText(message)).join("\n\n").trim();
9327
- const oauthStartedMessage = extractOAuthStartedMessageFromToolResults(toolResults);
9328
- const toolErrorCount = toolResults.filter(
9329
- (result) => result.isError
9330
- ).length;
9331
- const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
9332
- const successfulToolNames = new Set(
9333
- toolResults.filter((result) => !isToolResultError(result)).map((result) => normalizeToolNameFromResult(result)).filter((value) => Boolean(value))
9334
- );
9335
- const channelPostPerformed = successfulToolNames.has(
9336
- "slackChannelPostMessage"
9337
- );
9338
- const deliveryPlan = buildReplyDeliveryPlan({
9339
- explicitChannelPostIntent,
9340
- channelPostPerformed,
9341
- hasFiles: replyFiles.length > 0,
9342
- streamingThreadReply: Boolean(context.onTextDelta)
9474
+ return buildTurnResult({
9475
+ newMessages,
9476
+ userInput,
9477
+ replyFiles,
9478
+ artifactStatePatch,
9479
+ toolCalls,
9480
+ sandboxId: sandboxExecutor.getSandboxId(),
9481
+ sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
9482
+ generatedFileCount: generatedFiles.length,
9483
+ hasTextDeltaCallback: Boolean(context.onTextDelta),
9484
+ shouldTrace,
9485
+ spanContext,
9486
+ correlation: context.correlation,
9487
+ assistantUserName: context.assistant?.userName
9343
9488
  });
9344
- const deliveryMode = deliveryPlan.mode;
9345
- if (!primaryText && !oauthStartedMessage) {
9346
- logWarn(
9347
- "ai_model_response_empty",
9348
- {
9349
- slackThreadId: context.correlation?.threadId,
9350
- slackUserId: context.correlation?.requesterId,
9351
- slackChannelId: context.correlation?.channelId,
9489
+ } catch (error) {
9490
+ if (error instanceof McpAuthorizationPauseError && timeoutResumeConversationId && timeoutResumeSessionId) {
9491
+ const nextSliceId = await persistAuthPauseCheckpoint({
9492
+ conversationId: timeoutResumeConversationId,
9493
+ sessionId: timeoutResumeSessionId,
9494
+ currentSliceId: timeoutResumeSliceId,
9495
+ messages: timeoutResumeMessages,
9496
+ loadedSkillNames: loadedSkillNamesForResume,
9497
+ errorMessage: error.message,
9498
+ logContext: {
9499
+ threadId: context.correlation?.threadId,
9500
+ requesterId: context.correlation?.requesterId,
9501
+ channelId: context.correlation?.channelId,
9352
9502
  runId: context.correlation?.runId,
9353
9503
  assistantUserName: context.assistant?.userName,
9354
9504
  modelId: botConfig.modelId
9355
- },
9356
- {
9357
- "app.ai.tool_results": toolResults.length,
9358
- "app.ai.tool_error_results": toolErrorCount,
9359
- "app.ai.generated_files": generatedFiles.length
9360
- },
9361
- "Model returned empty text response"
9362
- );
9363
- }
9364
- const lastAssistant = assistantMessages.at(-1);
9365
- const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
9366
- const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
9367
- const usedPrimaryText = Boolean(primaryText);
9368
- const outcome = primaryText || oauthStartedMessage ? stopReason === "error" ? "provider_error" : "success" : "execution_failure";
9369
- const fallbackText = oauthStartedMessage ?? buildExecutionFailureMessage(toolErrorCount);
9370
- const responseText = primaryText || fallbackText;
9371
- const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
9372
- const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
9373
- const resolvedOutcome = escapedOrRawPayload ? oauthStartedMessage ? outcome : "execution_failure" : outcome;
9374
- if (shouldTrace) {
9375
- logInfo(
9376
- "agent_message_out",
9377
- spanContext,
9378
- {
9379
- "app.message.kind": "assistant_outbound",
9380
- "app.message.length": resolvedText.length,
9381
- "app.message.output": summarizeMessageText(resolvedText),
9382
- "app.ai.outcome": resolvedOutcome,
9383
- "app.ai.assistant_messages": assistantMessages.length,
9384
- ...stopReason ? { "app.ai.stop_reason": stopReason } : {}
9385
- },
9386
- "Agent message sent"
9387
- );
9388
- }
9389
- if (escapedOrRawPayload) {
9390
- return {
9391
- text: resolvedText,
9392
- files: replyFiles.length > 0 ? replyFiles : void 0,
9393
- artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
9394
- deliveryPlan,
9395
- deliveryMode,
9396
- sandboxId: sandboxExecutor.getSandboxId(),
9397
- sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
9398
- diagnostics: {
9399
- outcome: "execution_failure",
9400
- modelId: botConfig.modelId,
9401
- assistantMessageCount: assistantMessages.length,
9402
- toolCalls,
9403
- toolResultCount: toolResults.length,
9404
- toolErrorCount,
9405
- usedPrimaryText,
9406
- stopReason,
9407
- errorMessage,
9408
- providerError: void 0
9409
9505
  }
9410
- };
9411
- }
9412
- return {
9413
- text: resolvedText,
9414
- files: replyFiles.length > 0 ? replyFiles : void 0,
9415
- artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
9416
- deliveryPlan,
9417
- deliveryMode,
9418
- sandboxId: sandboxExecutor.getSandboxId(),
9419
- sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
9420
- diagnostics: {
9421
- outcome,
9422
- modelId: botConfig.modelId,
9423
- assistantMessageCount: assistantMessages.length,
9424
- toolCalls,
9425
- toolResultCount: toolResults.length,
9426
- toolErrorCount,
9427
- usedPrimaryText,
9428
- stopReason,
9429
- errorMessage,
9430
- providerError: void 0
9431
- }
9432
- };
9433
- } catch (error) {
9434
- if (error instanceof McpAuthorizationPauseError && timeoutResumeConversationId && timeoutResumeSessionId) {
9435
- const nextSliceId = timeoutResumeSliceId + 1;
9436
- try {
9437
- const latestCheckpoint = await getAgentTurnSessionCheckpoint(
9438
- timeoutResumeConversationId,
9439
- timeoutResumeSessionId
9440
- );
9441
- const piMessages = trimTrailingAssistantMessages(
9442
- timeoutResumeMessages.length > 0 ? timeoutResumeMessages : latestCheckpoint?.piMessages ?? []
9443
- );
9444
- await upsertAgentTurnSessionCheckpoint({
9445
- conversationId: timeoutResumeConversationId,
9446
- sessionId: timeoutResumeSessionId,
9447
- sliceId: nextSliceId,
9448
- state: "awaiting_resume",
9449
- piMessages,
9450
- loadedSkillNames: loadedSkillNamesForResume,
9451
- resumeReason: "auth",
9452
- resumedFromSliceId: timeoutResumeSliceId,
9453
- errorMessage: error.message
9454
- });
9455
- } catch (checkpointError) {
9456
- logException(
9457
- checkpointError,
9458
- "agent_turn_auth_resume_checkpoint_failed",
9459
- {
9460
- slackThreadId: context.correlation?.threadId,
9461
- slackUserId: context.correlation?.requesterId,
9462
- slackChannelId: context.correlation?.channelId,
9463
- runId: context.correlation?.runId,
9464
- assistantUserName: context.assistant?.userName,
9465
- modelId: botConfig.modelId
9466
- },
9467
- {
9468
- "app.ai.resume_conversation_id": timeoutResumeConversationId,
9469
- "app.ai.resume_session_id": timeoutResumeSessionId,
9470
- "app.ai.resume_from_slice_id": timeoutResumeSliceId,
9471
- "app.ai.resume_next_slice_id": nextSliceId
9472
- },
9473
- "Failed to persist auth checkpoint before retry"
9474
- );
9475
- }
9506
+ });
9476
9507
  throw new RetryableTurnError(
9477
9508
  "mcp_auth_resume",
9478
9509
  `conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`
@@ -11975,7 +12006,11 @@ function createReplyToThread(deps) {
11975
12006
  "app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
11976
12007
  "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
11977
12008
  "app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
11978
- ...reply.diagnostics.stopReason ? { "app.ai.stop_reason": reply.diagnostics.stopReason } : {},
12009
+ ...reply.diagnostics.stopReason ? {
12010
+ "gen_ai.response.finish_reasons": [
12011
+ reply.diagnostics.stopReason
12012
+ ]
12013
+ } : {},
11979
12014
  ...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
11980
12015
  };
11981
12016
  setSpanAttributes(diagnosticsAttributes);
@@ -12973,21 +13008,21 @@ async function createApp(options) {
12973
13008
  options?.pluginPackages ?? await resolveBuildPluginPackages()
12974
13009
  );
12975
13010
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
12976
- const app = new Hono().basePath("/api");
13011
+ const app = new Hono();
12977
13012
  app.onError((err, c) => {
12978
13013
  logException(err, "unhandled_route_error");
12979
13014
  return c.text("Internal Server Error", 500);
12980
13015
  });
13016
+ app.get("/", () => GET3());
12981
13017
  app.get("/health", () => GET2());
12982
- app.get("/__junior/discovery", () => GET());
12983
- app.get("/__junior/dashboard", () => GET3());
12984
- app.get("/oauth/callback/mcp/:provider", (c) => {
13018
+ app.get("/api/info", () => GET());
13019
+ app.get("/api/oauth/callback/mcp/:provider", (c) => {
12985
13020
  return GET4(c.req.raw, c.req.param("provider"), waitUntil);
12986
13021
  });
12987
- app.get("/oauth/callback/:provider", (c) => {
13022
+ app.get("/api/oauth/callback/:provider", (c) => {
12988
13023
  return GET5(c.req.raw, c.req.param("provider"), waitUntil);
12989
13024
  });
12990
- app.post("/webhooks/:platform", (c) => {
13025
+ app.post("/api/webhooks/:platform", (c) => {
12991
13026
  return POST(c.req.raw, c.req.param("platform"), waitUntil);
12992
13027
  });
12993
13028
  return app;