@sentry/junior 0.16.0 → 0.18.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-ZU6X5ZGL.js";
10
+ } from "./chunk-4XWTSMRF.js";
11
11
  import {
12
12
  SANDBOX_SKILLS_ROOT,
13
13
  SANDBOX_WORKSPACE_ROOT,
@@ -26,7 +26,7 @@ import {
26
26
  runNonInteractiveCommand,
27
27
  sandboxSkillDir,
28
28
  toOptionalTrimmed
29
- } from "./chunk-3JKW7ISL.js";
29
+ } from "./chunk-XYOKYK6U.js";
30
30
  import {
31
31
  CredentialUnavailableError,
32
32
  buildOAuthTokenRequest,
@@ -55,7 +55,7 @@ import {
55
55
  toOptionalString,
56
56
  withContext,
57
57
  withSpan
58
- } from "./chunk-RUC2V7Q7.js";
58
+ } from "./chunk-DTOS5CG4.js";
59
59
  import "./chunk-Z3YD6NHK.js";
60
60
  import {
61
61
  aboutPathCandidates,
@@ -1937,9 +1937,7 @@ function createChannelConfigurationService(storage) {
1937
1937
  };
1938
1938
  const resolveValues = async (options = {}) => {
1939
1939
  const keys = Array.isArray(options.keys) ? options.keys.map((entry) => entry.trim()).filter((entry) => entry.length > 0) : void 0;
1940
- const entries = await list({
1941
- ...options.prefix ? { prefix: options.prefix } : {}
1942
- });
1940
+ const entries = options.prefix ? await list({ prefix: options.prefix }) : await list({});
1943
1941
  const filtered = keys ? entries.filter((entry) => keys.includes(entry.key)) : entries;
1944
1942
  const resolved = {};
1945
1943
  for (const entry of filtered) {
@@ -2176,14 +2174,15 @@ function resolveGatewayModel(modelId) {
2176
2174
  return matched;
2177
2175
  }
2178
2176
  async function completeText(params) {
2179
- const startedAt = Date.now();
2180
2177
  const model = resolveGatewayModel(params.modelId);
2181
2178
  const apiKey = getPiGatewayApiKeyOverride();
2182
2179
  const requestMessagesAttribute = serializeGenAiAttribute(params.messages);
2180
+ const systemInstructionsAttribute = params.system ? serializeGenAiAttribute([{ type: "text", content: params.system }]) : void 0;
2183
2181
  const startAttributes = {
2184
2182
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2185
2183
  "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2186
2184
  "gen_ai.request.model": params.modelId,
2185
+ ...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
2187
2186
  ...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
2188
2187
  "app.ai.auth_mode": apiKey ? "oidc" : "api_key"
2189
2188
  };
@@ -2216,8 +2215,7 @@ async function completeText(params) {
2216
2215
  "gen_ai.request.model": params.modelId,
2217
2216
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
2218
2217
  ...usageAttributes,
2219
- "app.ai.duration_ms": Date.now() - startedAt,
2220
- "app.ai.stop_reason": message.stopReason ?? "unknown"
2218
+ ...message.stopReason ? { "gen_ai.response.finish_reasons": [message.stopReason] } : {}
2221
2219
  };
2222
2220
  setSpanAttributes(endAttributes);
2223
2221
  if (message.stopReason === "error") {
@@ -2807,24 +2805,6 @@ function formatLoadedSkillsForPrompt(skills) {
2807
2805
  lines.push("</loaded_skills>");
2808
2806
  return lines.join("\n");
2809
2807
  }
2810
- function formatLoadedToolsForPrompt(tools) {
2811
- if (tools.length === 0) {
2812
- return "<loaded_tools>\n</loaded_tools>";
2813
- }
2814
- const lines = ["<loaded_tools>"];
2815
- for (const tool2 of tools) {
2816
- lines.push(
2817
- ` <tool name="${escapeXml(tool2.tool_name)}" provider="${escapeXml(tool2.provider)}">`
2818
- );
2819
- lines.push(` <description>${escapeXml(tool2.description)}</description>`);
2820
- lines.push(
2821
- ` <arguments_summary>${escapeXml(tool2.input_schema_summary)}</arguments_summary>`
2822
- );
2823
- lines.push(" </tool>");
2824
- }
2825
- lines.push("</loaded_tools>");
2826
- return lines.join("\n");
2827
- }
2828
2808
  function formatProviderCatalogForPrompt() {
2829
2809
  const providers = listCapabilityProviders();
2830
2810
  if (providers.length === 0) {
@@ -2858,7 +2838,7 @@ function baseSystemPrompt() {
2858
2838
  "- Never claim you cannot access tools in this turn. If prior results are empty, run tools now.",
2859
2839
  "- If critical input is missing and cannot be discovered with tools, ask one direct clarifying question.",
2860
2840
  "- Always gather evidence from available sources (tools or skills) before answering factual questions.",
2861
- "- 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.",
2862
2842
  "- Use `searchTools` only when you need to rediscover or filter active MCP tools.",
2863
2843
  "- Never guess. If you cannot verify with available sources, say it is unverified.",
2864
2844
  "- Never claim a lookup succeeded unless a tool result supports it.",
@@ -2899,10 +2879,8 @@ function buildSystemPrompt(params) {
2899
2879
  "Loaded skills for this turn:",
2900
2880
  formatLoadedSkillsForPrompt(activeSkills)
2901
2881
  ].join("\n");
2902
- const activeToolsSection = [
2903
- "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:",
2904
- formatLoadedToolsForPrompt(activeTools ?? [])
2905
- ].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.` : "";
2906
2884
  const configurationKeys = Object.keys(configuration ?? {}).sort(
2907
2885
  (a, b) => a.localeCompare(b)
2908
2886
  );
@@ -3028,9 +3006,8 @@ function buildSystemPrompt(params) {
3028
3006
  "- Do not use reaction-based progress signals; Assistants API status already covers in-progress UX.",
3029
3007
  "- Prefer `webSearch` before `webFetch` when the user gave no URL.",
3030
3008
  "- Never call side-effecting tools when the user only asked for analysis or options.",
3031
- "- `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`).",
3032
3010
  "- `searchTools` searches active MCP tools exposed by currently loaded skills when you need to rediscover or filter them.",
3033
- "- `useTool` executes a canonical MCP tool name from `loadSkill.available_tools`, `<loaded_tools>`, or `searchTools`.",
3034
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."
3035
3012
  ].join("\n")
3036
3013
  ),
@@ -3046,7 +3023,7 @@ function buildSystemPrompt(params) {
3046
3023
  "- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
3047
3024
  "- Load only the best matching skill first; do not load multiple skills upfront.",
3048
3025
  "- After `loadSkill`, use `skill_dir` as the root for any referenced files you read via `bash`.",
3049
- "- 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.",
3050
3027
  "- Use `searchTools` only when you need to rediscover or filter the currently exposed MCP tools.",
3051
3028
  "- If no skill is a clear fit, continue with normal tool usage."
3052
3029
  ].join("\n")
@@ -3067,7 +3044,7 @@ function buildSystemPrompt(params) {
3067
3044
  ),
3068
3045
  availableSkillsSection,
3069
3046
  activeSkillsSection,
3070
- activeToolsSection,
3047
+ ...activeToolsSection ? [activeToolsSection] : [],
3071
3048
  renderTag(
3072
3049
  "invocation-context",
3073
3050
  invocation ? `Explicit skill trigger detected: /${invocation.skillName}` : "No explicit skill trigger detected."
@@ -3760,9 +3737,7 @@ ${usage}
3760
3737
  exitCode: 2
3761
3738
  });
3762
3739
  }
3763
- const entries = await configuration.list({
3764
- ...prefixResult.prefix ? { prefix: prefixResult.prefix } : {}
3765
- });
3740
+ const entries = prefixResult.prefix ? await configuration.list({ prefix: prefixResult.prefix }) : await configuration.list({});
3766
3741
  return commandResult({
3767
3742
  stdout: {
3768
3743
  ok: true,
@@ -3952,65 +3927,6 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
3952
3927
  };
3953
3928
  }
3954
3929
 
3955
- // src/chat/services/channel-intent.ts
3956
- function isExplicitChannelPostIntent(text) {
3957
- if (!/\bchannel\b/i.test(text)) {
3958
- return false;
3959
- }
3960
- const directChannelVerb = /\b(show|post|send|share|say|announce|broadcast)\b[\s\S]{0,80}\b(?:the\s+)?channel\b/i;
3961
- if (directChannelVerb.test(text)) {
3962
- return true;
3963
- }
3964
- 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;
3965
- return scopedChannelVerb.test(text);
3966
- }
3967
-
3968
- // src/chat/services/reply-delivery-plan.ts
3969
- var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
3970
- var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
3971
- var REACTION_ALIAS_PREFIX_RE = /^:[a-z0-9_+-]*$/i;
3972
- function normalizeReactionAckText(text) {
3973
- return text.trim().toLowerCase().replace(/[!.]+$/g, "");
3974
- }
3975
- function isRedundantReactionAckText(text) {
3976
- const trimmed = text.trim();
3977
- if (!trimmed) {
3978
- return false;
3979
- }
3980
- if (REACTION_ONLY_ACK_RE.test(trimmed)) {
3981
- return true;
3982
- }
3983
- const normalized = normalizeReactionAckText(text);
3984
- return REDUNDANT_REACTION_ACK_TEXT.includes(
3985
- normalized
3986
- );
3987
- }
3988
- function isPotentialRedundantReactionAckText(text) {
3989
- const trimmed = text.trim();
3990
- if (!trimmed) {
3991
- return true;
3992
- }
3993
- if (REACTION_ONLY_ACK_RE.test(trimmed) || REACTION_ALIAS_PREFIX_RE.test(trimmed)) {
3994
- return true;
3995
- }
3996
- const normalized = normalizeReactionAckText(text);
3997
- return REDUNDANT_REACTION_ACK_TEXT.some(
3998
- (candidate) => candidate.startsWith(normalized)
3999
- );
4000
- }
4001
- function buildReplyDeliveryPlan(args) {
4002
- const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
4003
- let attachFiles = "none";
4004
- if (args.hasFiles && mode === "thread") {
4005
- attachFiles = args.streamingThreadReply ? "followup" : "inline";
4006
- }
4007
- return {
4008
- mode,
4009
- postThreadText: mode === "thread",
4010
- attachFiles
4011
- };
4012
- }
4013
-
4014
3930
  // src/chat/sandbox/skill-sandbox.ts
4015
3931
  import fs2 from "fs/promises";
4016
3932
  import path2 from "path";
@@ -4210,9 +4126,6 @@ var SkillSandbox = class {
4210
4126
  }
4211
4127
  };
4212
4128
 
4213
- // src/chat/mcp/tool-manager.ts
4214
- import { validateToolArguments } from "@mariozechner/pi-ai";
4215
-
4216
4129
  // src/chat/mcp/client.ts
4217
4130
  import { Client } from "@modelcontextprotocol/sdk/client";
4218
4131
  import {
@@ -4392,6 +4305,12 @@ var PluginMcpClient = class {
4392
4305
  };
4393
4306
 
4394
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
+ };
4395
4314
  function normalizeMcpToolName(provider, toolName) {
4396
4315
  return `mcp__${provider}__${toolName}`;
4397
4316
  }
@@ -4575,13 +4494,6 @@ var McpToolManager = class {
4575
4494
  return left.tool.name.localeCompare(right.tool.name);
4576
4495
  }).slice(0, Math.max(1, options.limit ?? 8)).map((entry) => this.toToolDescriptor(entry.tool));
4577
4496
  }
4578
- async executeTool(skills, canonicalToolName, args) {
4579
- const tool2 = this.resolveActiveTool(skills, canonicalToolName);
4580
- if (!tool2) {
4581
- throw new Error(`Unknown active MCP tool: ${canonicalToolName}`);
4582
- }
4583
- return await tool2.execute(this.validateExecutionArgs(tool2, args));
4584
- }
4585
4497
  filterListedTools(plugin, tools) {
4586
4498
  const allowedTools = plugin.manifest.mcp?.allowedTools;
4587
4499
  if (!allowedTools || allowedTools.length === 0) {
@@ -4625,7 +4537,7 @@ var McpToolManager = class {
4625
4537
  try {
4626
4538
  const result = await client2.callTool(tool2.name, resolvedArgs);
4627
4539
  if ("isError" in result && result.isError) {
4628
- throw new Error(extractMcpErrorMessage(result));
4540
+ throw new McpToolError(extractMcpErrorMessage(result));
4629
4541
  }
4630
4542
  return {
4631
4543
  content: toAgentToolContent(result),
@@ -4676,6 +4588,7 @@ var McpToolManager = class {
4676
4588
  this.activeProviders.delete(provider);
4677
4589
  return true;
4678
4590
  }
4591
+ /** Return all active ManagedMcpTool objects for the given skill scope. */
4679
4592
  getResolvedActiveTools(skills, options = {}) {
4680
4593
  const resolved = [];
4681
4594
  for (const provider of this.getActiveProviders()) {
@@ -4699,11 +4612,6 @@ var McpToolManager = class {
4699
4612
  }
4700
4613
  return providerTools;
4701
4614
  }
4702
- resolveActiveTool(skills, canonicalToolName) {
4703
- return this.getResolvedActiveTools(skills).find(
4704
- (tool2) => tool2.name === canonicalToolName
4705
- );
4706
- }
4707
4615
  toToolDescriptor(tool2) {
4708
4616
  return {
4709
4617
  name: tool2.name,
@@ -4712,15 +4620,6 @@ var McpToolManager = class {
4712
4620
  provider: tool2.provider
4713
4621
  };
4714
4622
  }
4715
- validateExecutionArgs(tool2, args) {
4716
- return validateToolArguments(
4717
- tool2,
4718
- {
4719
- name: tool2.name,
4720
- arguments: args
4721
- }
4722
- );
4723
- }
4724
4623
  scoreToolMatch(tool2, normalizedQuery, queryTokens) {
4725
4624
  const exactCandidates = [tool2.name, tool2.rawName, tool2.title].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
4726
4625
  if (exactCandidates.includes(normalizedQuery)) {
@@ -5186,7 +5085,7 @@ var DEFAULT_LIMIT = 5;
5186
5085
  var MAX_LIMIT = 20;
5187
5086
  function createSearchToolsTool(mcpToolManager, getActiveSkills) {
5188
5087
  return tool({
5189
- 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.",
5190
5089
  inputSchema: Type6.Object(
5191
5090
  {
5192
5091
  query: Type6.String({
@@ -6262,45 +6161,8 @@ function createSystemTimeTool() {
6262
6161
  });
6263
6162
  }
6264
6163
 
6265
- // src/chat/tools/skill/use-tool.ts
6266
- import { Type as Type13 } from "@sinclair/typebox";
6267
- function normalizeToolArguments(value) {
6268
- return value ?? {};
6269
- }
6270
- function createUseToolTool(mcpToolManager, getActiveSkills) {
6271
- return tool({
6272
- description: "Execute an active MCP tool by canonical tool_name. Use tool_name values disclosed by `loadSkill`, `<loaded_tools>`, or `searchTools`.",
6273
- inputSchema: Type13.Object(
6274
- {
6275
- tool_name: Type13.String({
6276
- minLength: 1,
6277
- description: "Canonical MCP tool name in the form mcp__<provider>__<tool>."
6278
- }),
6279
- arguments: Type13.Optional(
6280
- Type13.Object(
6281
- {},
6282
- {
6283
- additionalProperties: true,
6284
- description: "Arguments for the selected MCP tool."
6285
- }
6286
- )
6287
- )
6288
- },
6289
- { additionalProperties: false }
6290
- ),
6291
- execute: async ({ tool_name, arguments: rawArguments }) => {
6292
- const activeSkills = getActiveSkills();
6293
- return await mcpToolManager.executeTool(
6294
- activeSkills,
6295
- tool_name,
6296
- normalizeToolArguments(rawArguments)
6297
- );
6298
- }
6299
- });
6300
- }
6301
-
6302
6164
  // src/chat/tools/web/fetch-tool.ts
6303
- import { Type as Type14 } from "@sinclair/typebox";
6165
+ import { Type as Type13 } from "@sinclair/typebox";
6304
6166
 
6305
6167
  // src/chat/tools/web/constants.ts
6306
6168
  var USER_AGENT = "junior-bot/0.1";
@@ -6648,13 +6510,13 @@ function extractHttpStatusFromMessage(message) {
6648
6510
  function createWebFetchTool(hooks) {
6649
6511
  return tool({
6650
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.",
6651
- inputSchema: Type14.Object({
6652
- url: Type14.String({
6513
+ inputSchema: Type13.Object({
6514
+ url: Type13.String({
6653
6515
  minLength: 1,
6654
6516
  description: "HTTP(S) URL to fetch."
6655
6517
  }),
6656
- max_chars: Type14.Optional(
6657
- Type14.Integer({
6518
+ max_chars: Type13.Optional(
6519
+ Type13.Integer({
6658
6520
  minimum: 500,
6659
6521
  maximum: MAX_FETCH_CHARS,
6660
6522
  description: "Optional maximum number of extracted characters to return."
@@ -6714,7 +6576,7 @@ function createWebFetchTool(hooks) {
6714
6576
  // src/chat/tools/web/search.ts
6715
6577
  import { generateText } from "ai";
6716
6578
  import { createGatewayProvider } from "@ai-sdk/gateway";
6717
- import { Type as Type15 } from "@sinclair/typebox";
6579
+ import { Type as Type14 } from "@sinclair/typebox";
6718
6580
  var SEARCH_TIMEOUT_MS = 1e4;
6719
6581
  var MAX_RESULTS = 5;
6720
6582
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -6765,14 +6627,14 @@ function isTimeoutSearchFailure(message) {
6765
6627
  function createWebSearchTool() {
6766
6628
  return tool({
6767
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.",
6768
- inputSchema: Type15.Object({
6769
- query: Type15.String({
6630
+ inputSchema: Type14.Object({
6631
+ query: Type14.String({
6770
6632
  minLength: 1,
6771
6633
  maxLength: 500,
6772
6634
  description: "Search query."
6773
6635
  }),
6774
- max_results: Type15.Optional(
6775
- Type15.Integer({
6636
+ max_results: Type14.Optional(
6637
+ Type14.Integer({
6776
6638
  minimum: 1,
6777
6639
  maximum: MAX_RESULTS,
6778
6640
  description: "Max results to return."
@@ -6833,17 +6695,17 @@ function createWebSearchTool() {
6833
6695
  }
6834
6696
 
6835
6697
  // src/chat/tools/sandbox/write-file.ts
6836
- import { Type as Type16 } from "@sinclair/typebox";
6698
+ import { Type as Type15 } from "@sinclair/typebox";
6837
6699
  function createWriteFileTool() {
6838
6700
  return tool({
6839
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.",
6840
- inputSchema: Type16.Object(
6702
+ inputSchema: Type15.Object(
6841
6703
  {
6842
- path: Type16.String({
6704
+ path: Type15.String({
6843
6705
  minLength: 1,
6844
6706
  description: "Path to write in the sandbox workspace."
6845
6707
  }),
6846
- content: Type16.String({
6708
+ content: Type15.String({
6847
6709
  description: "UTF-8 file content to write."
6848
6710
  })
6849
6711
  },
@@ -6963,20 +6825,16 @@ function createTools(availableSkills, hooks = {}, context) {
6963
6825
  createSearchToolsTool(context.mcpToolManager, context.getActiveSkills),
6964
6826
  hooks
6965
6827
  );
6966
- tools.useTool = wrapToolExecution(
6967
- "useTool",
6968
- createUseToolTool(context.mcpToolManager, context.getActiveSkills),
6969
- hooks
6970
- );
6971
6828
  }
6972
- if (isConversationScopedChannel(context.channelId)) {
6829
+ const { channelCapabilities } = context;
6830
+ if (channelCapabilities.canCreateCanvas) {
6973
6831
  tools.slackCanvasCreate = wrapToolExecution(
6974
6832
  "slackCanvasCreate",
6975
6833
  createSlackCanvasCreateTool(context, state),
6976
6834
  hooks
6977
6835
  );
6978
6836
  }
6979
- if (isConversationChannel(context.channelId)) {
6837
+ if (channelCapabilities.canPostToChannel) {
6980
6838
  tools.slackChannelPostMessage = wrapToolExecution(
6981
6839
  "slackChannelPostMessage",
6982
6840
  createSlackChannelPostMessageTool(context, state),
@@ -6988,7 +6846,7 @@ function createTools(availableSkills, hooks = {}, context) {
6988
6846
  hooks
6989
6847
  );
6990
6848
  }
6991
- if (isConversationScopedChannel(context.channelId)) {
6849
+ if (channelCapabilities.canAddReactions) {
6992
6850
  tools.slackMessageAddReaction = wrapToolExecution(
6993
6851
  "slackMessageAddReaction",
6994
6852
  createSlackMessageAddReactionTool(context, state),
@@ -6998,6 +6856,15 @@ function createTools(availableSkills, hooks = {}, context) {
6998
6856
  return tools;
6999
6857
  }
7000
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
+
7001
6868
  // src/chat/sandbox/sandbox.ts
7002
6869
  import fs3 from "fs/promises";
7003
6870
  import path4 from "path";
@@ -7939,6 +7806,8 @@ function createSandboxExecutor(options) {
7939
7806
  pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
7940
7807
  });
7941
7808
  let commandError;
7809
+ let result;
7810
+ let restoreError;
7942
7811
  try {
7943
7812
  const commandResult2 = await activeSandbox.runCommand({
7944
7813
  cmd: "bash",
@@ -7954,7 +7823,7 @@ function createSandboxExecutor(options) {
7954
7823
  const stderrRaw = await commandResult2.stderr();
7955
7824
  const stdout = truncateOutput(stdoutRaw, boundedOutputLength);
7956
7825
  const stderr = truncateOutput(stderrRaw, boundedOutputLength);
7957
- return {
7826
+ result = {
7958
7827
  stdout: stdout.value,
7959
7828
  stderr: stderr.value,
7960
7829
  exitCode: commandResult2.exitCode,
@@ -7968,14 +7837,16 @@ function createSandboxExecutor(options) {
7968
7837
  if (headerTransforms && headerTransforms.length > 0) {
7969
7838
  try {
7970
7839
  await activeSandbox.updateNetworkPolicy(restoreNetworkPolicy);
7971
- } catch (restoreError) {
7972
- await invalidateSandboxInstance(activeSandbox, restoreError);
7973
- if (!commandError) {
7974
- throw restoreError;
7975
- }
7840
+ } catch (error) {
7841
+ restoreError = error;
7842
+ await invalidateSandboxInstance(activeSandbox, error);
7976
7843
  }
7977
7844
  }
7978
7845
  }
7846
+ if (restoreError && !commandError) {
7847
+ throw restoreError;
7848
+ }
7849
+ return result;
7979
7850
  },
7980
7851
  readFile: async (input) => await executeReadFile(input, {
7981
7852
  toolCallId: "sandbox-read-file",
@@ -8198,95 +8069,6 @@ function shouldEmitDevAgentTrace() {
8198
8069
  return process.env.NODE_ENV === "development";
8199
8070
  }
8200
8071
 
8201
- // src/chat/state/turn-session-store.ts
8202
- var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
8203
- var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
8204
- function agentTurnSessionKey(conversationId, sessionId) {
8205
- return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
8206
- }
8207
- function parseAgentTurnSessionCheckpoint(value) {
8208
- if (typeof value !== "string") {
8209
- return void 0;
8210
- }
8211
- try {
8212
- const parsed = JSON.parse(value);
8213
- if (!isRecord(parsed)) {
8214
- return void 0;
8215
- }
8216
- const status = parsed.state;
8217
- if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
8218
- return void 0;
8219
- }
8220
- const conversationId = parsed.conversationId;
8221
- const sessionId = parsed.sessionId;
8222
- const sliceId = parsed.sliceId;
8223
- const checkpointVersion = parsed.checkpointVersion;
8224
- const updatedAtMs = parsed.updatedAtMs;
8225
- if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
8226
- return void 0;
8227
- }
8228
- return {
8229
- checkpointVersion,
8230
- conversationId,
8231
- sessionId,
8232
- sliceId,
8233
- state: status,
8234
- updatedAtMs,
8235
- piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
8236
- ...Array.isArray(parsed.loadedSkillNames) ? {
8237
- loadedSkillNames: parsed.loadedSkillNames.filter(
8238
- (value2) => typeof value2 === "string"
8239
- )
8240
- } : {},
8241
- ...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
8242
- ...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
8243
- ...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
8244
- };
8245
- } catch {
8246
- return void 0;
8247
- }
8248
- }
8249
- async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
8250
- const stateAdapter = getStateAdapter();
8251
- await stateAdapter.connect();
8252
- const value = await stateAdapter.get(
8253
- agentTurnSessionKey(conversationId, sessionId)
8254
- );
8255
- return parseAgentTurnSessionCheckpoint(value);
8256
- }
8257
- async function upsertAgentTurnSessionCheckpoint(args) {
8258
- const stateAdapter = getStateAdapter();
8259
- await stateAdapter.connect();
8260
- const existing = await getAgentTurnSessionCheckpoint(
8261
- args.conversationId,
8262
- args.sessionId
8263
- );
8264
- const checkpoint = {
8265
- checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
8266
- conversationId: args.conversationId,
8267
- sessionId: args.sessionId,
8268
- sliceId: args.sliceId,
8269
- state: args.state,
8270
- updatedAtMs: Date.now(),
8271
- piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
8272
- ...Array.isArray(args.loadedSkillNames) ? {
8273
- loadedSkillNames: args.loadedSkillNames.filter(
8274
- (value) => typeof value === "string"
8275
- )
8276
- } : {},
8277
- ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
8278
- ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
8279
- ...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
8280
- };
8281
- const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
8282
- await stateAdapter.set(
8283
- agentTurnSessionKey(args.conversationId, args.sessionId),
8284
- JSON.stringify(checkpoint),
8285
- ttlMs
8286
- );
8287
- return checkpoint;
8288
- }
8289
-
8290
8072
  // src/chat/runtime/status-format.ts
8291
8073
  var SLACK_STATUS_MAX_LENGTH = 50;
8292
8074
  function truncateWithEllipsis(text, maxLength) {
@@ -8433,20 +8215,6 @@ function extractStatusUrlDomain(value) {
8433
8215
  }
8434
8216
 
8435
8217
  // src/chat/runtime/tool-status.ts
8436
- function formatCanonicalToolStatusName(value) {
8437
- if (typeof value !== "string") {
8438
- return void 0;
8439
- }
8440
- const trimmed = value.trim();
8441
- if (!trimmed) {
8442
- return void 0;
8443
- }
8444
- const mcpMatch = /^mcp__([^_]+)__(.+)$/.exec(trimmed);
8445
- if (mcpMatch) {
8446
- return compactStatusText(`${mcpMatch[1]}/${mcpMatch[2]}`, 40);
8447
- }
8448
- return compactStatusText(trimmed, 40);
8449
- }
8450
8218
  function formatToolStatus(toolName) {
8451
8219
  const known = {
8452
8220
  loadSkill: "Loading skill instructions",
@@ -8465,12 +8233,15 @@ function formatToolStatus(toolName) {
8465
8233
  slackListAddItems: "Updating tracking list",
8466
8234
  slackListUpdateItem: "Updating tracking list",
8467
8235
  imageGenerate: "Generating image",
8468
- searchTools: "Searching active tools",
8469
- useTool: "Running active tool"
8236
+ searchTools: "Searching active tools"
8470
8237
  };
8471
8238
  if (known[toolName]) {
8472
8239
  return known[toolName];
8473
8240
  }
8241
+ const mcpMatch = /^mcp__([^_]+)__(.+)$/.exec(toolName);
8242
+ if (mcpMatch) {
8243
+ return `Running ${mcpMatch[1]}/${mcpMatch[2]}`;
8244
+ }
8474
8245
  const readable = toolName.replaceAll("_", " ").trim();
8475
8246
  return readable.length > 0 ? `Running ${readable}` : "Running tool";
8476
8247
  }
@@ -8483,7 +8254,6 @@ function formatToolStatusWithInput(toolName, input) {
8483
8254
  const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
8484
8255
  const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
8485
8256
  const provider = obj ? compactStatusText(obj.provider, 20) : void 0;
8486
- const activeToolName = obj ? formatCanonicalToolStatusName(obj.tool_name ?? obj.toolName) : void 0;
8487
8257
  if (command && toolName === "bash") {
8488
8258
  return `Running ${command}`;
8489
8259
  }
@@ -8508,25 +8278,57 @@ function formatToolStatusWithInput(toolName, input) {
8508
8278
  if (query && toolName === "searchTools") {
8509
8279
  return `Searching tools for "${query}"`;
8510
8280
  }
8511
- if (activeToolName && toolName === "useTool") {
8512
- return `Running ${activeToolName}`;
8513
- }
8514
8281
  if (domain && toolName === "webFetch") {
8515
8282
  return `Fetching page from ${domain}`;
8516
8283
  }
8517
8284
  return formatToolStatus(toolName);
8518
8285
  }
8519
8286
 
8520
- // src/chat/tools/agent-tools.ts
8521
- import { Value } from "@sinclair/typebox/value";
8522
- function toToolContentText(value) {
8523
- if (typeof value === "string") return value;
8524
- try {
8525
- return JSON.stringify(value);
8526
- } catch {
8527
- 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
+ };
8300
+ }
8301
+ return params;
8302
+ }
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
+ );
8528
8327
  }
8328
+ return { headerTransforms, env };
8529
8329
  }
8330
+
8331
+ // src/chat/tools/execution/normalize-result.ts
8530
8332
  function isStructuredToolExecutionResult(value) {
8531
8333
  const content = value?.content;
8532
8334
  return typeof value === "object" && value !== null && Array.isArray(content) && content.every((part) => {
@@ -8543,9 +8345,29 @@ function isStructuredToolExecutionResult(value) {
8543
8345
  return false;
8544
8346
  }) && "details" in value;
8545
8347
  }
8546
- function getToolErrorAttributes(error) {
8547
- if (!(error instanceof SlackActionError)) {
8548
- return {};
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
8368
+ function getToolErrorAttributes(error) {
8369
+ if (!(error instanceof SlackActionError)) {
8370
+ return {};
8549
8371
  }
8550
8372
  return {
8551
8373
  "app.slack.error_code": error.code,
@@ -8555,6 +8377,44 @@ function getToolErrorAttributes(error) {
8555
8377
  ...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
8556
8378
  };
8557
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
8558
8418
  function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, hooks) {
8559
8419
  const shouldTrace = shouldEmitDevAgentTrace();
8560
8420
  return Object.entries(tools).map(([toolName, toolDef]) => ({
@@ -8566,7 +8426,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8566
8426
  const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
8567
8427
  const toolArgumentsAttribute = serializeGenAiAttribute(params);
8568
8428
  hooks?.onToolCall?.(toolName);
8569
- const toolStartedAt = Date.now();
8570
8429
  const traceToolContext = {
8571
8430
  ...spanContext,
8572
8431
  conversationId: spanContext.conversationId,
@@ -8579,163 +8438,65 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8579
8438
  "gen_ai.execute_tool",
8580
8439
  spanContext,
8581
8440
  async () => {
8582
- if (!Value.Check(toolDef.inputSchema, params)) {
8583
- const details = [...Value.Errors(toolDef.inputSchema, params)].slice(0, 3).map((entry) => `${entry.path || "/"}: ${entry.message}`).join("; ");
8584
- const validationMessage = details.length > 0 ? details : "Invalid tool input";
8585
- const durationMs = Date.now() - toolStartedAt;
8586
- setSpanAttributes({
8587
- "app.ai.tool_duration_ms": durationMs,
8588
- "error.type": "tool_input_validation_error"
8589
- });
8590
- setSpanStatus("error");
8591
- logWarn(
8592
- "agent_tool_call_invalid_input",
8593
- {},
8594
- {
8595
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8596
- "gen_ai.operation.name": "execute_tool",
8597
- "gen_ai.tool.name": toolName,
8598
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8599
- "app.ai.tool_duration_ms": durationMs
8600
- },
8601
- "Agent tool call input validation failed"
8602
- );
8603
- logException(
8604
- new Error(validationMessage),
8605
- "agent_tool_call_invalid_input_exception",
8606
- {},
8607
- {
8608
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8609
- "gen_ai.operation.name": "execute_tool",
8610
- "gen_ai.tool.name": toolName,
8611
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8612
- "app.ai.tool_duration_ms": durationMs
8613
- },
8614
- "Agent tool call input validation failed with exception"
8615
- );
8616
- throw new Error(validationMessage);
8617
- }
8618
8441
  const parsed = params;
8619
8442
  try {
8620
8443
  if (typeof toolDef.execute !== "function") {
8621
- const resultDetails2 = { ok: true };
8622
- const durationMs2 = Date.now() - toolStartedAt;
8623
- const toolResultAttribute2 = serializeGenAiAttribute(resultDetails2);
8624
- setSpanAttributes({
8625
- "app.ai.tool_duration_ms": durationMs2,
8626
- "app.ai.tool_outcome": "success",
8627
- ...toolResultAttribute2 ? { "gen_ai.tool.call.result": toolResultAttribute2 } : {}
8628
- });
8629
- 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
+ }
8630
8451
  return {
8631
8452
  content: [{ type: "text", text: "ok" }],
8632
- details: resultDetails2
8453
+ details: resultDetails
8633
8454
  };
8634
8455
  }
8635
- const injectedHeaders = toolName === "bash" ? capabilityRuntime?.getTurnHeaderTransforms() : void 0;
8636
- const injectedEnv = toolName === "bash" ? capabilityRuntime?.getTurnEnv() : void 0;
8637
8456
  const bashCommand = toolName === "bash" && typeof parsed.command === "string" ? parsed.command.trim() : "";
8638
- const isCustomBashCommand = toolName === "bash" && /^jr-rpc(?:\s|$)/.test(bashCommand);
8639
- const shouldLogCredentialInjection = toolName === "bash" && !isCustomBashCommand && Boolean(injectedHeaders && injectedHeaders.length > 0);
8640
- if (shouldLogCredentialInjection) {
8641
- const headerDomains = (injectedHeaders ?? []).map(
8642
- (transform) => transform.domain
8643
- );
8644
- logInfo(
8645
- "credential_inject_start",
8646
- {},
8647
- {
8648
- "app.skill.name": sandbox.getActiveSkill()?.name,
8649
- "app.credential.delivery": "header_transform",
8650
- "app.credential.header_domains": headerDomains
8651
- },
8652
- "Injecting scoped credential headers for sandbox command"
8653
- );
8654
- }
8655
- const hasBashCredentials = injectedHeaders || injectedEnv;
8656
- const sandboxInput = toolName === "bash" ? { command: String(parsed.command ?? "") } : toolName === "readFile" ? { path: String(parsed.path ?? "") } : toolName === "writeFile" ? {
8657
- path: String(parsed.path ?? ""),
8658
- content: String(parsed.content ?? "")
8659
- } : parsed;
8660
- const result = sandboxExecutor?.canExecute(toolName) ? await sandboxExecutor.execute({
8457
+ const injection = resolveCredentialInjection(
8661
8458
  toolName,
8662
- input: toolName === "bash" && hasBashCredentials ? {
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({
8466
+ toolName,
8467
+ input: toolName === "bash" && (injection.headerTransforms || injection.env) ? {
8663
8468
  ...sandboxInput,
8664
- ...injectedHeaders ? { headerTransforms: injectedHeaders } : {},
8665
- ...injectedEnv ? { env: injectedEnv } : {}
8469
+ ...injection.headerTransforms ? { headerTransforms: injection.headerTransforms } : {},
8470
+ ...injection.env ? { env: injection.env } : {}
8666
8471
  } : sandboxInput
8667
8472
  }) : await toolDef.execute(parsed, {
8668
8473
  experimental_context: sandbox
8669
8474
  });
8670
- const resultDetails = sandboxExecutor?.canExecute(toolName) && result && typeof result === "object" && "result" in result ? result.result : result;
8671
- const durationMs = Date.now() - toolStartedAt;
8672
- const structuredToolResult = isStructuredToolExecutionResult(
8673
- resultDetails
8674
- ) ? resultDetails : void 0;
8475
+ const normalized = normalizeToolResult(result, isSandbox);
8675
8476
  const toolResultAttribute = serializeGenAiAttribute(
8676
- structuredToolResult?.details ?? resultDetails
8477
+ normalized.details
8677
8478
  );
8678
- setSpanAttributes({
8679
- "app.ai.tool_duration_ms": durationMs,
8680
- "app.ai.tool_outcome": "success",
8681
- ...toolResultAttribute ? { "gen_ai.tool.call.result": toolResultAttribute } : {}
8682
- });
8683
- setSpanStatus("ok");
8684
- if (structuredToolResult) {
8685
- return structuredToolResult;
8479
+ if (toolResultAttribute) {
8480
+ setSpanAttributes({
8481
+ "gen_ai.tool.call.result": toolResultAttribute
8482
+ });
8686
8483
  }
8687
- return {
8688
- content: [
8689
- { type: "text", text: toToolContentText(resultDetails) }
8690
- ],
8691
- details: resultDetails
8692
- };
8484
+ return normalized;
8693
8485
  } catch (error) {
8694
- const durationMs = Date.now() - toolStartedAt;
8695
- setSpanAttributes({
8696
- "app.ai.tool_duration_ms": durationMs,
8697
- "app.ai.tool_outcome": "error",
8698
- "error.type": error instanceof Error ? error.name : "tool_execution_error"
8699
- });
8700
- setSpanStatus("error");
8701
- if (shouldTrace) {
8702
- logWarn(
8703
- "agent_tool_call_failed",
8704
- traceToolContext,
8705
- {
8706
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8707
- "gen_ai.operation.name": "execute_tool",
8708
- "gen_ai.tool.name": toolName,
8709
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8710
- "app.ai.tool_duration_ms": durationMs,
8711
- "app.ai.tool_outcome": "error",
8712
- "error.type": error instanceof Error ? error.name : "tool_execution_error",
8713
- "error.message": error instanceof Error ? error.message : String(error)
8714
- },
8715
- "Agent tool call failed"
8716
- );
8717
- }
8718
- logException(
8486
+ handleToolExecutionError(
8719
8487
  error,
8720
- "agent_tool_call_failed",
8721
- {},
8722
- {
8723
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8724
- "gen_ai.operation.name": "execute_tool",
8725
- "gen_ai.tool.name": toolName,
8726
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8727
- "app.ai.tool_duration_ms": durationMs,
8728
- ...getToolErrorAttributes(error)
8729
- },
8730
- "Agent tool call failed"
8488
+ toolName,
8489
+ normalizedToolCallId,
8490
+ shouldTrace,
8491
+ traceToolContext
8731
8492
  );
8732
- throw error;
8733
8493
  }
8734
8494
  },
8735
8495
  {
8736
8496
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8737
8497
  "gen_ai.operation.name": "execute_tool",
8738
8498
  "gen_ai.tool.name": toolName,
8499
+ "gen_ai.tool.description": toolDef.description,
8739
8500
  ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8740
8501
  ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
8741
8502
  }
@@ -8804,6 +8565,65 @@ function resolveReplyDelivery(args) {
8804
8565
  };
8805
8566
  }
8806
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
+
8807
8627
  // src/chat/services/attachment-claims.ts
8808
8628
  function splitSentences(text) {
8809
8629
  return text.split(/\n+/).flatMap((line) => line.split(/(?<=[.!?])\s+/)).map((part) => part.trim()).filter((part) => part.length > 0);
@@ -8833,8 +8653,268 @@ function enforceAttachmentClaimTruth(text, hasAttachedFiles) {
8833
8653
  Note: No file was attached in this turn. I need to attach the file before claiming it is shared.`;
8834
8654
  }
8835
8655
 
8836
- // src/chat/respond.ts
8837
- 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
8838
8918
  var McpAuthorizationPauseError = class extends Error {
8839
8919
  provider;
8840
8920
  constructor(provider) {
@@ -8843,6 +8923,86 @@ var McpAuthorizationPauseError = class extends Error {
8843
8923
  this.provider = provider;
8844
8924
  }
8845
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
+ }
8846
9006
  async function maybeReplaceAgentMessages(agent, messages) {
8847
9007
  const resumable = agent;
8848
9008
  if (typeof resumable.replaceMessages !== "function") {
@@ -8867,7 +9027,6 @@ async function generateAssistantReply(messageText, context = {}) {
8867
9027
  let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
8868
9028
  let loadedSkillNamesForResume = [];
8869
9029
  let mcpToolManager;
8870
- let pendingMcpAuthorizationPause;
8871
9030
  try {
8872
9031
  const shouldTrace = shouldEmitDevAgentTrace();
8873
9032
  const spanContext = {
@@ -8926,15 +9085,13 @@ async function generateAssistantReply(messageText, context = {}) {
8926
9085
  const activeSkills = [];
8927
9086
  const skillSandbox = new SkillSandbox(availableSkills, activeSkills);
8928
9087
  const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(context);
8929
- const canUseTurnSession = Boolean(sessionConversationId && sessionId);
9088
+ const checkpointState = await loadTurnCheckpoint({
9089
+ conversationId: sessionConversationId,
9090
+ sessionId
9091
+ });
9092
+ const { resumedFromCheckpoint, currentSliceId, existingCheckpoint } = checkpointState;
8930
9093
  timeoutResumeConversationId = sessionConversationId;
8931
9094
  timeoutResumeSessionId = sessionId;
8932
- const existingTurnCheckpoint = canUseTurnSession && sessionConversationId && sessionId ? await getAgentTurnSessionCheckpoint(sessionConversationId, sessionId) : void 0;
8933
- const hasAwaitingResumeCheckpoint = Boolean(
8934
- existingTurnCheckpoint && existingTurnCheckpoint.state === "awaiting_resume" && existingTurnCheckpoint.piMessages.length > 0
8935
- );
8936
- const resumedFromCheckpoint = hasAwaitingResumeCheckpoint;
8937
- const currentSliceId = hasAwaitingResumeCheckpoint ? existingTurnCheckpoint.sliceId : 1;
8938
9095
  timeoutResumeSliceId = currentSliceId;
8939
9096
  const capabilityRuntime = createSkillCapabilityRuntime({
8940
9097
  invocationArgs: skillInvocation?.args,
@@ -8971,7 +9128,7 @@ async function generateAssistantReply(messageText, context = {}) {
8971
9128
  lastKnownSandboxDependencyProfileHash = sandboxExecutor.getDependencyProfileHash();
8972
9129
  sandboxExecutor.configureSkills(availableSkills);
8973
9130
  const sandbox = await sandboxExecutor.createSandbox();
8974
- for (const skillName of existingTurnCheckpoint?.loadedSkillNames ?? []) {
9131
+ for (const skillName of existingCheckpoint?.loadedSkillNames ?? []) {
8975
9132
  const preloaded = await skillSandbox.loadSkill(skillName);
8976
9133
  if (preloaded) {
8977
9134
  upsertActiveSkill(activeSkills, preloaded);
@@ -8992,76 +9149,29 @@ async function generateAssistantReply(messageText, context = {}) {
8992
9149
  }
8993
9150
  );
8994
9151
  timeoutResumeMessages = [];
8995
- pendingMcpAuthorizationPause = void 0;
8996
9152
  const generatedFiles = [];
8997
9153
  const replyFiles = [];
8998
9154
  const artifactStatePatch = {};
8999
9155
  const toolCalls = [];
9000
- const mcpAuthSessionIdsByProvider = /* @__PURE__ */ new Map();
9001
9156
  let agent;
9002
- mcpToolManager = new McpToolManager(getPluginMcpProviders(), {
9003
- authProviderFactory: async (plugin) => {
9004
- if (!sessionConversationId || !sessionId || !context.requester?.userId) {
9005
- return void 0;
9006
- }
9007
- const provider = await createMcpOAuthClientProvider({
9008
- provider: plugin.manifest.name,
9009
- conversationId: sessionConversationId,
9010
- sessionId,
9011
- userId: context.requester.userId,
9012
- userMessage: userInput,
9013
- ...context.correlation?.channelId ? { channelId: context.correlation.channelId } : {},
9014
- ...context.correlation?.threadTs ? { threadTs: context.correlation.threadTs } : {},
9015
- ...context.toolChannelId ? { toolChannelId: context.toolChannelId } : {},
9016
- configuration: configurationValues,
9017
- artifactState: context.artifactState
9018
- });
9019
- mcpAuthSessionIdsByProvider.set(
9020
- plugin.manifest.name,
9021
- provider.authSessionId
9022
- );
9023
- 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)
9024
9169
  },
9025
- onAuthorizationRequired: async (provider) => {
9026
- if (pendingMcpAuthorizationPause) {
9027
- return true;
9028
- }
9029
- const authSessionId = mcpAuthSessionIdsByProvider.get(provider);
9030
- if (!authSessionId || !context.requester?.userId) {
9031
- throw new Error(
9032
- `Missing MCP auth session context for plugin "${provider}"`
9033
- );
9034
- }
9035
- const latestArtifactState = mergeArtifactsState(
9036
- context.artifactState ?? {},
9037
- artifactStatePatch
9038
- );
9039
- await patchMcpAuthSession(authSessionId, {
9040
- configuration: { ...configurationValues },
9041
- artifactState: latestArtifactState,
9042
- toolChannelId: context.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? context.correlation?.channelId
9043
- });
9044
- const authSession = await getMcpAuthSession(authSessionId);
9045
- if (!authSession?.authorizationUrl) {
9046
- throw new Error(
9047
- `Missing MCP authorization URL for plugin "${provider}"`
9048
- );
9049
- }
9050
- const delivery = await deliverPrivateMessage({
9051
- channelId: authSession.channelId,
9052
- threadTs: authSession.threadTs,
9053
- userId: authSession.userId,
9054
- text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
9055
- });
9056
- if (!delivery) {
9057
- throw new Error(
9058
- `Unable to deliver MCP authorization link for plugin "${provider}"`
9059
- );
9060
- }
9061
- pendingMcpAuthorizationPause = new McpAuthorizationPauseError(provider);
9062
- agent?.abort();
9063
- return true;
9064
- }
9170
+ () => agent?.abort()
9171
+ );
9172
+ mcpToolManager = new McpToolManager(getPluginMcpProviders(), {
9173
+ authProviderFactory: mcpAuth.authProviderFactory,
9174
+ onAuthorizationRequired: mcpAuth.onAuthorizationRequired
9065
9175
  });
9066
9176
  const turnMcpToolManager = mcpToolManager;
9067
9177
  const syncResumeState = () => {
@@ -9104,22 +9214,25 @@ async function generateAssistantReply(messageText, context = {}) {
9104
9214
  syncResumeState();
9105
9215
  await turnMcpToolManager.activateForSkill(effective);
9106
9216
  syncResumeState();
9107
- if (pendingMcpAuthorizationPause) {
9217
+ if (mcpAuth.getPendingPause()) {
9108
9218
  return void 0;
9109
9219
  }
9110
9220
  if (!effective.pluginProvider) {
9111
9221
  return void 0;
9112
9222
  }
9223
+ syncMcpAgentTools();
9113
9224
  return {
9114
9225
  available_tools: turnMcpToolManager.getActiveToolCatalog(activeSkills, {
9115
9226
  provider: effective.pluginProvider
9116
- }).map(toExposedToolSummary),
9117
- tool_search_available: true
9227
+ }).map(toExposedToolSummary)
9118
9228
  };
9119
9229
  }
9120
9230
  },
9121
9231
  {
9122
9232
  channelId: context.toolChannelId ?? context.correlation?.channelId,
9233
+ channelCapabilities: resolveChannelCapabilities(
9234
+ context.toolChannelId ?? context.correlation?.channelId
9235
+ ),
9123
9236
  messageTs: context.correlation?.messageTs,
9124
9237
  threadTs: context.correlation?.threadTs,
9125
9238
  userText: userInput,
@@ -9134,9 +9247,9 @@ async function generateAssistantReply(messageText, context = {}) {
9134
9247
  for (const skill of activeSkills) {
9135
9248
  await turnMcpToolManager.activateForSkill(skill);
9136
9249
  syncResumeState();
9137
- if (pendingMcpAuthorizationPause) {
9138
- timeoutResumeMessages = existingTurnCheckpoint?.piMessages ?? [];
9139
- throw pendingMcpAuthorizationPause;
9250
+ if (mcpAuth.getPendingPause()) {
9251
+ timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
9252
+ throw mcpAuth.getPendingPause();
9140
9253
  }
9141
9254
  }
9142
9255
  syncResumeState();
@@ -9181,6 +9294,11 @@ async function generateAssistantReply(messageText, context = {}) {
9181
9294
  content: userContentParts.map((part) => toObservablePromptPart(part))
9182
9295
  }
9183
9296
  ]);
9297
+ const agentToolHooks = {
9298
+ onToolCall: (toolName) => {
9299
+ toolCalls.push(toolName);
9300
+ }
9301
+ };
9184
9302
  const baseAgentTools = createAgentTools(
9185
9303
  tools,
9186
9304
  skillSandbox,
@@ -9188,18 +9306,31 @@ async function generateAssistantReply(messageText, context = {}) {
9188
9306
  context.onStatus,
9189
9307
  sandboxExecutor,
9190
9308
  capabilityRuntime,
9191
- {
9192
- onToolCall: (toolName) => {
9193
- toolCalls.push(toolName);
9194
- }
9195
- }
9309
+ agentToolHooks
9196
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();
9197
9328
  agent = new Agent({
9198
9329
  getApiKey: () => getPiGatewayApiKeyOverride(),
9199
9330
  initialState: {
9200
9331
  systemPrompt: baseInstructions,
9201
9332
  model: resolveGatewayModel(botConfig.modelId),
9202
- tools: baseAgentTools
9333
+ tools: agentTools
9203
9334
  }
9204
9335
  });
9205
9336
  let hasEmittedText = false;
@@ -9211,16 +9342,10 @@ async function generateAssistantReply(messageText, context = {}) {
9211
9342
  }
9212
9343
  return;
9213
9344
  }
9214
- if (event.type !== "message_update") {
9215
- return;
9216
- }
9217
- if (event.assistantMessageEvent.type !== "text_delta") {
9218
- return;
9219
- }
9345
+ if (event.type !== "message_update") return;
9346
+ if (event.assistantMessageEvent.type !== "text_delta") return;
9220
9347
  const deltaText = event.assistantMessageEvent.delta;
9221
- if (!deltaText) {
9222
- return;
9223
- }
9348
+ if (!deltaText) return;
9224
9349
  const text = needsSeparator ? "\n\n" + deltaText : deltaText;
9225
9350
  needsSeparator = false;
9226
9351
  hasEmittedText = true;
@@ -9242,7 +9367,7 @@ async function generateAssistantReply(messageText, context = {}) {
9242
9367
  if (resumedFromCheckpoint) {
9243
9368
  const didReplace = await maybeReplaceAgentMessages(
9244
9369
  agent,
9245
- existingTurnCheckpoint.piMessages
9370
+ existingCheckpoint.piMessages
9246
9371
  );
9247
9372
  if (!didReplace) {
9248
9373
  throw new Error(
@@ -9294,9 +9419,9 @@ async function generateAssistantReply(messageText, context = {}) {
9294
9419
  });
9295
9420
  timeoutResumeMessages = [...agent.state.messages];
9296
9421
  }
9297
- if (pendingMcpAuthorizationPause) {
9422
+ if (mcpAuth.getPendingPause()) {
9298
9423
  timeoutResumeMessages = [...agent.state.messages];
9299
- throw pendingMcpAuthorizationPause;
9424
+ throw mcpAuth.getPendingPause();
9300
9425
  }
9301
9426
  throw error;
9302
9427
  } finally {
@@ -9308,12 +9433,9 @@ async function generateAssistantReply(messageText, context = {}) {
9308
9433
  beforeMessageCount
9309
9434
  );
9310
9435
  completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
9311
- if (pendingMcpAuthorizationPause && !completedAssistantTurn) {
9436
+ if (mcpAuth.getPendingPause() && !completedAssistantTurn) {
9312
9437
  timeoutResumeMessages = [...agent.state.messages];
9313
- throw pendingMcpAuthorizationPause;
9314
- }
9315
- if (pendingMcpAuthorizationPause && completedAssistantTurn) {
9316
- pendingMcpAuthorizationPause = void 0;
9438
+ throw mcpAuth.getPendingPause();
9317
9439
  }
9318
9440
  const outputMessages = newMessages.filter(isAssistantMessage);
9319
9441
  const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
@@ -9337,171 +9459,51 @@ async function generateAssistantReply(messageText, context = {}) {
9337
9459
  } finally {
9338
9460
  unsubscribe();
9339
9461
  }
9340
- if (pendingMcpAuthorizationPause && !completedAssistantTurn) {
9341
- throw pendingMcpAuthorizationPause;
9462
+ if (mcpAuth.getPendingPause() && !completedAssistantTurn) {
9463
+ throw mcpAuth.getPendingPause();
9342
9464
  }
9343
- if (canUseTurnSession && sessionConversationId && sessionId) {
9344
- await upsertAgentTurnSessionCheckpoint({
9465
+ if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
9466
+ await persistCompletedCheckpoint({
9345
9467
  conversationId: sessionConversationId,
9346
9468
  sessionId,
9347
9469
  sliceId: currentSliceId,
9348
- state: "completed",
9349
- piMessages: agent.state.messages,
9470
+ allMessages: agent.state.messages,
9350
9471
  loadedSkillNames: activeSkills.map((skill) => skill.name)
9351
9472
  });
9352
9473
  }
9353
- const toolResults = newMessages.filter(isToolResultMessage);
9354
- const assistantMessages = newMessages.filter(isAssistantMessage);
9355
- const primaryText = assistantMessages.map((message) => extractAssistantText(message)).join("\n\n").trim();
9356
- const oauthStartedMessage = extractOAuthStartedMessageFromToolResults(toolResults);
9357
- const toolErrorCount = toolResults.filter(
9358
- (result) => result.isError
9359
- ).length;
9360
- const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
9361
- const successfulToolNames = new Set(
9362
- toolResults.filter((result) => !isToolResultError(result)).map((result) => normalizeToolNameFromResult(result)).filter((value) => Boolean(value))
9363
- );
9364
- const channelPostPerformed = successfulToolNames.has(
9365
- "slackChannelPostMessage"
9366
- );
9367
- const deliveryPlan = buildReplyDeliveryPlan({
9368
- explicitChannelPostIntent,
9369
- channelPostPerformed,
9370
- hasFiles: replyFiles.length > 0,
9371
- 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
9372
9488
  });
9373
- const deliveryMode = deliveryPlan.mode;
9374
- if (!primaryText && !oauthStartedMessage) {
9375
- logWarn(
9376
- "ai_model_response_empty",
9377
- {
9378
- slackThreadId: context.correlation?.threadId,
9379
- slackUserId: context.correlation?.requesterId,
9380
- 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,
9381
9502
  runId: context.correlation?.runId,
9382
9503
  assistantUserName: context.assistant?.userName,
9383
9504
  modelId: botConfig.modelId
9384
- },
9385
- {
9386
- "app.ai.tool_results": toolResults.length,
9387
- "app.ai.tool_error_results": toolErrorCount,
9388
- "app.ai.generated_files": generatedFiles.length
9389
- },
9390
- "Model returned empty text response"
9391
- );
9392
- }
9393
- const lastAssistant = assistantMessages.at(-1);
9394
- const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
9395
- const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
9396
- const usedPrimaryText = Boolean(primaryText);
9397
- const outcome = primaryText || oauthStartedMessage ? stopReason === "error" ? "provider_error" : "success" : "execution_failure";
9398
- const fallbackText = oauthStartedMessage ?? buildExecutionFailureMessage(toolErrorCount);
9399
- const responseText = primaryText || fallbackText;
9400
- const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
9401
- const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
9402
- const resolvedOutcome = escapedOrRawPayload ? oauthStartedMessage ? outcome : "execution_failure" : outcome;
9403
- if (shouldTrace) {
9404
- logInfo(
9405
- "agent_message_out",
9406
- spanContext,
9407
- {
9408
- "app.message.kind": "assistant_outbound",
9409
- "app.message.length": resolvedText.length,
9410
- "app.message.output": summarizeMessageText(resolvedText),
9411
- "app.ai.outcome": resolvedOutcome,
9412
- "app.ai.assistant_messages": assistantMessages.length,
9413
- ...stopReason ? { "app.ai.stop_reason": stopReason } : {}
9414
- },
9415
- "Agent message sent"
9416
- );
9417
- }
9418
- if (escapedOrRawPayload) {
9419
- return {
9420
- text: resolvedText,
9421
- files: replyFiles.length > 0 ? replyFiles : void 0,
9422
- artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
9423
- deliveryPlan,
9424
- deliveryMode,
9425
- sandboxId: sandboxExecutor.getSandboxId(),
9426
- sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
9427
- diagnostics: {
9428
- outcome: "execution_failure",
9429
- modelId: botConfig.modelId,
9430
- assistantMessageCount: assistantMessages.length,
9431
- toolCalls,
9432
- toolResultCount: toolResults.length,
9433
- toolErrorCount,
9434
- usedPrimaryText,
9435
- stopReason,
9436
- errorMessage,
9437
- providerError: void 0
9438
9505
  }
9439
- };
9440
- }
9441
- return {
9442
- text: resolvedText,
9443
- files: replyFiles.length > 0 ? replyFiles : void 0,
9444
- artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
9445
- deliveryPlan,
9446
- deliveryMode,
9447
- sandboxId: sandboxExecutor.getSandboxId(),
9448
- sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
9449
- diagnostics: {
9450
- outcome,
9451
- modelId: botConfig.modelId,
9452
- assistantMessageCount: assistantMessages.length,
9453
- toolCalls,
9454
- toolResultCount: toolResults.length,
9455
- toolErrorCount,
9456
- usedPrimaryText,
9457
- stopReason,
9458
- errorMessage,
9459
- providerError: void 0
9460
- }
9461
- };
9462
- } catch (error) {
9463
- if (error instanceof McpAuthorizationPauseError && timeoutResumeConversationId && timeoutResumeSessionId) {
9464
- const nextSliceId = timeoutResumeSliceId + 1;
9465
- try {
9466
- const latestCheckpoint = await getAgentTurnSessionCheckpoint(
9467
- timeoutResumeConversationId,
9468
- timeoutResumeSessionId
9469
- );
9470
- const piMessages = trimTrailingAssistantMessages(
9471
- timeoutResumeMessages.length > 0 ? timeoutResumeMessages : latestCheckpoint?.piMessages ?? []
9472
- );
9473
- await upsertAgentTurnSessionCheckpoint({
9474
- conversationId: timeoutResumeConversationId,
9475
- sessionId: timeoutResumeSessionId,
9476
- sliceId: nextSliceId,
9477
- state: "awaiting_resume",
9478
- piMessages,
9479
- loadedSkillNames: loadedSkillNamesForResume,
9480
- resumeReason: "auth",
9481
- resumedFromSliceId: timeoutResumeSliceId,
9482
- errorMessage: error.message
9483
- });
9484
- } catch (checkpointError) {
9485
- logException(
9486
- checkpointError,
9487
- "agent_turn_auth_resume_checkpoint_failed",
9488
- {
9489
- slackThreadId: context.correlation?.threadId,
9490
- slackUserId: context.correlation?.requesterId,
9491
- slackChannelId: context.correlation?.channelId,
9492
- runId: context.correlation?.runId,
9493
- assistantUserName: context.assistant?.userName,
9494
- modelId: botConfig.modelId
9495
- },
9496
- {
9497
- "app.ai.resume_conversation_id": timeoutResumeConversationId,
9498
- "app.ai.resume_session_id": timeoutResumeSessionId,
9499
- "app.ai.resume_from_slice_id": timeoutResumeSliceId,
9500
- "app.ai.resume_next_slice_id": nextSliceId
9501
- },
9502
- "Failed to persist auth checkpoint before retry"
9503
- );
9504
- }
9506
+ });
9505
9507
  throw new RetryableTurnError(
9506
9508
  "mcp_auth_resume",
9507
9509
  `conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`
@@ -12004,7 +12006,11 @@ function createReplyToThread(deps) {
12004
12006
  "app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
12005
12007
  "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
12006
12008
  "app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
12007
- ...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
+ } : {},
12008
12014
  ...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
12009
12015
  };
12010
12016
  setSpanAttributes(diagnosticsAttributes);