@sentry/junior 0.30.0 → 0.32.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
@@ -2,9 +2,8 @@ import {
2
2
  discoverSkills,
3
3
  findSkillByName,
4
4
  loadSkillsByName,
5
- logCapabilityCatalogLoadedOnce,
6
5
  parseSkillInvocation
7
- } from "./chunk-ICIRAL6Y.js";
6
+ } from "./chunk-EHXMTKBA.js";
8
7
  import {
9
8
  GEN_AI_PROVIDER_NAME,
10
9
  MISSING_GATEWAY_CREDENTIALS_ERROR,
@@ -31,7 +30,7 @@ import {
31
30
  runNonInteractiveCommand,
32
31
  sandboxSkillDir,
33
32
  sandboxSkillFile
34
- } from "./chunk-LEYD42MR.js";
33
+ } from "./chunk-3M7ZD6FF.js";
35
34
  import {
36
35
  CredentialUnavailableError,
37
36
  buildOAuthTokenRequest,
@@ -40,11 +39,14 @@ import {
40
39
  createRequestContext,
41
40
  extractGenAiUsageSummary,
42
41
  getActiveTraceId,
42
+ getPluginCapabilityProviders,
43
+ getPluginCatalogSignature,
43
44
  getPluginDefinition,
44
45
  getPluginMcpProviders,
45
46
  getPluginOAuthConfig,
46
47
  getPluginProviders,
47
48
  hasRequiredOAuthScope,
49
+ isPluginConfigKey,
48
50
  isPluginProvider,
49
51
  isRecord,
50
52
  logError,
@@ -62,7 +64,7 @@ import {
62
64
  toOptionalString,
63
65
  withContext,
64
66
  withSpan
65
- } from "./chunk-RZJDO55D.js";
67
+ } from "./chunk-XARRBRQV.js";
66
68
  import "./chunk-Z3YD6NHK.js";
67
69
  import {
68
70
  discoverInstalledPluginPackageContent,
@@ -77,6 +79,26 @@ import "./chunk-2KG3PWR4.js";
77
79
  // src/app.ts
78
80
  import { Hono } from "hono";
79
81
 
82
+ // src/chat/configuration/defaults.ts
83
+ var installDefaults = {};
84
+ function setConfigDefaults(defaults) {
85
+ if (!defaults) {
86
+ installDefaults = {};
87
+ return;
88
+ }
89
+ for (const key of Object.keys(defaults)) {
90
+ if (!isPluginConfigKey(key)) {
91
+ throw new Error(
92
+ `configDefaults: "${key}" is not a registered plugin config key`
93
+ );
94
+ }
95
+ }
96
+ installDefaults = { ...defaults };
97
+ }
98
+ function getConfigDefaults() {
99
+ return installDefaults;
100
+ }
101
+
80
102
  // src/handlers/diagnostics.ts
81
103
  import { readFileSync } from "fs";
82
104
  import path from "path";
@@ -2883,18 +2905,21 @@ function formatConfigurationValue(value) {
2883
2905
  function renderIdentityBlock(tag, fields) {
2884
2906
  const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
2885
2907
  if (lines.length === 0) {
2886
- return [`<${tag}>`, "none", `</${tag}>`].join("\n");
2908
+ return [`<${tag}>`, "none", `</${tag}>`];
2887
2909
  }
2888
- return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
2910
+ return [`<${tag}>`, ...lines, `</${tag}>`];
2889
2911
  }
2890
- function renderTag(tag, content) {
2912
+ function renderTag(tag, lines) {
2913
+ return [`<${tag}>`, ...lines, `</${tag}>`];
2914
+ }
2915
+ function renderTagBlock(tag, content) {
2891
2916
  return [`<${tag}>`, content, `</${tag}>`].join("\n");
2892
2917
  }
2893
2918
  function formatAvailableSkillsForPrompt(skills) {
2894
2919
  if (skills.length === 0) {
2895
- return "<available_skills>\n</available_skills>";
2920
+ return "<available-skills>\n</available-skills>";
2896
2921
  }
2897
- const lines = ["<available_skills>"];
2922
+ const lines = ["<available-skills>"];
2898
2923
  for (const skill of skills) {
2899
2924
  const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
2900
2925
  lines.push(" <skill>");
@@ -2906,45 +2931,39 @@ function formatAvailableSkillsForPrompt(skills) {
2906
2931
  if (skill.pluginProvider) {
2907
2932
  lines.push(` <provider>${escapeXml(skill.pluginProvider)}</provider>`);
2908
2933
  }
2909
- if (skill.usesConfig && skill.usesConfig.length > 0) {
2910
- lines.push(
2911
- ` <uses_config>${escapeXml(skill.usesConfig.join(" "))}</uses_config>`
2912
- );
2913
- }
2914
2934
  lines.push(" </skill>");
2915
2935
  }
2916
- lines.push("</available_skills>");
2936
+ lines.push("</available-skills>");
2917
2937
  return lines.join("\n");
2918
2938
  }
2919
2939
  function formatLoadedSkillsForPrompt(skills) {
2920
2940
  if (skills.length === 0) {
2921
- return "<loaded_skills>\n</loaded_skills>";
2941
+ return "<loaded-skills>\n</loaded-skills>";
2922
2942
  }
2923
- const lines = ["<loaded_skills>"];
2943
+ const lines = ["<loaded-skills>"];
2924
2944
  for (const skill of skills) {
2925
2945
  const skillDir = workspaceSkillDir(skill.name);
2926
2946
  lines.push(
2927
2947
  ` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
2928
2948
  );
2929
- lines.push(`References are relative to ${escapeXml(skillDir)}.`);
2930
- if (skill.usesConfig && skill.usesConfig.length > 0) {
2931
- lines.push(
2932
- `Uses config keys: ${escapeXml(skill.usesConfig.join(", "))}.`
2933
- );
2934
- }
2949
+ lines.push(
2950
+ `Skill directory: ${escapeXml(skillDir)}. Resolve relative paths there; for skill-owned bash commands, cd there first or use absolute paths.`
2951
+ );
2935
2952
  lines.push("");
2936
2953
  lines.push(skill.body);
2937
2954
  lines.push(" </skill>");
2938
2955
  }
2939
- lines.push("</loaded_skills>");
2956
+ lines.push("</loaded-skills>");
2940
2957
  return lines.join("\n");
2941
2958
  }
2942
2959
  function formatProviderCatalogForPrompt() {
2943
2960
  const providers = getPluginProviders().map((plugin) => plugin.manifest);
2944
2961
  if (providers.length === 0) {
2945
- return "- none";
2962
+ return null;
2946
2963
  }
2947
- const lines = [];
2964
+ const lines = [
2965
+ "Config keys and default targets per provider; use after a skill is loaded."
2966
+ ];
2948
2967
  for (const provider of providers) {
2949
2968
  lines.push(`- provider: ${escapeXml(provider.name)}`);
2950
2969
  lines.push(
@@ -2958,288 +2977,337 @@ function formatProviderCatalogForPrompt() {
2958
2977
  }
2959
2978
  return lines.join("\n");
2960
2979
  }
2961
- function baseSystemPrompt() {
2962
- return [
2963
- "You are a Slack-based helper assistant.",
2964
- "Identity, tone, and domain defaults are defined in the personality block.",
2965
- "",
2966
- "- Be concise, practical, and specific.",
2967
- "- Prefer actionable next steps over generic explanations.",
2968
- "- When the user gives a clear task, execute it immediately in this turn.",
2969
- "- Do not ask for permission to proceed when the request is already clear.",
2970
- "- Keep user-visible progress communication concise and useful.",
2971
- "- In thread follow-ups, answer using prior thread context directly; do not repeat unresolved clarifying questions unless the user asks to refine.",
2972
- "- If the user asks what you just said or means by the previous answer, summarize your prior assistant reply plainly.",
2973
- "- Never ask the user to re-tag or re-invoke for a clear task; continue execution in this turn.",
2974
- "- Never claim you cannot access tools in this turn. If prior results are empty, run tools now.",
2975
- "- If critical input is missing and cannot be discovered with tools, ask one direct clarifying question.",
2976
- "- Always gather evidence from available sources (tools or skills) before answering factual questions.",
2977
- "- When a loaded skill exposes MCP capabilities, those tools are registered as callable tools. Call them directly by name.",
2978
- "- Use `searchTools` only when you need to rediscover or filter active MCP tools.",
2979
- "- Never guess. If you cannot verify with available sources, say it is unverified.",
2980
- "- Never claim a lookup succeeded unless a tool result supports it.",
2981
- "- Do not give up when unsure how to do something; find a viable path, gather evidence, and provide the best actionable way forward.",
2982
- "- When active skills are present, follow their instructions before default behavior."
2983
- ].join("\n");
2980
+ function formatActiveMcpCatalogsForPrompt(catalogs) {
2981
+ if (catalogs.length === 0) {
2982
+ return null;
2983
+ }
2984
+ const lines = [
2985
+ "Active MCP provider catalogs are available through `searchMcpTools`. Call it with provider to list descriptors or with query to narrow results, then pass the exact returned `tool_name` to `callMcpTool`."
2986
+ ];
2987
+ for (const catalog of catalogs) {
2988
+ lines.push(" <catalog>");
2989
+ lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
2990
+ lines.push(
2991
+ ` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
2992
+ );
2993
+ lines.push(" </catalog>");
2994
+ }
2995
+ return lines.join("\n");
2984
2996
  }
2985
- function formatReferenceFilesSection() {
2997
+ function formatReferenceFilesLines() {
2986
2998
  const files = listReferenceFiles();
2987
2999
  if (files.length === 0) {
2988
- return [];
3000
+ return null;
2989
3001
  }
2990
- const fileNames = files.map((filePath) => {
3002
+ return files.map((filePath) => {
2991
3003
  const name = path2.basename(filePath);
2992
3004
  return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
2993
3005
  });
2994
- return [
2995
- renderTag(
2996
- "reference-files",
2997
- [
2998
- "Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
2999
- ...fileNames
3000
- ].join("\n")
3001
- )
3002
- ];
3003
3006
  }
3004
- function buildSystemPrompt(params) {
3005
- const {
3006
- availableSkills,
3007
- activeSkills,
3008
- activeTools,
3009
- invocation,
3010
- requester,
3011
- assistant,
3012
- artifactState,
3013
- configuration,
3014
- relevantConfigurationKeys,
3015
- runtimeMetadata,
3016
- threadParticipants
3017
- } = params;
3018
- const assistantSection = renderIdentityBlock("assistant", {
3019
- user_name: assistant?.userName ?? botConfig.userName,
3020
- user_id: assistant?.userId
3021
- });
3022
- const requesterSection = renderIdentityBlock("requester", {
3023
- full_name: requester?.fullName,
3024
- user_name: requester?.userName,
3025
- user_id: requester?.userId
3026
- });
3027
- const availableSkillsSection = [
3028
- "The following skills provide specialized instructions for specific tasks.",
3029
- "Call `loadSkill` when the task matches a skill description.",
3030
- "When a skill references a relative path, resolve it against `skill_dir` and use that path with `bash`.",
3031
- "",
3032
- formatAvailableSkillsForPrompt(availableSkills)
3033
- ].join("\n");
3034
- const activeSkillsSection = [
3035
- "Loaded skills for this turn:",
3036
- formatLoadedSkillsForPrompt(activeSkills)
3037
- ].join("\n");
3038
- const activeToolNames = (activeTools ?? []).map((tool2) => tool2.tool_name);
3039
- const activeToolsSection = activeToolNames.length > 0 ? `Active MCP tools registered for this turn: ${activeToolNames.join(", ")}. Call them directly by name.` : "";
3040
- const configurationKeys = Object.keys(configuration ?? {}).sort(
3007
+ function formatArtifactsLines(artifactState) {
3008
+ if (!artifactState) return null;
3009
+ const lines = [];
3010
+ if (artifactState.lastCanvasId) {
3011
+ lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
3012
+ }
3013
+ if (artifactState.lastCanvasUrl) {
3014
+ lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
3015
+ }
3016
+ if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
3017
+ lines.push("- recent_canvases:");
3018
+ for (const canvas of artifactState.recentCanvases) {
3019
+ lines.push(` - id: ${escapeXml(canvas.id)}`);
3020
+ if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
3021
+ if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
3022
+ if (canvas.createdAt) {
3023
+ lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
3024
+ }
3025
+ }
3026
+ }
3027
+ if (artifactState.lastListId) {
3028
+ lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
3029
+ }
3030
+ if (artifactState.lastListUrl) {
3031
+ lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
3032
+ }
3033
+ return lines.length > 0 ? lines : null;
3034
+ }
3035
+ function formatConfigurationLines(configuration) {
3036
+ const keys = Object.keys(configuration ?? {}).sort(
3041
3037
  (a, b) => a.localeCompare(b)
3042
3038
  );
3043
- const relevantConfigSet = new Set(
3044
- (relevantConfigurationKeys ?? []).filter(
3045
- (key) => Object.prototype.hasOwnProperty.call(configuration ?? {}, key)
3046
- )
3039
+ if (keys.length === 0) return null;
3040
+ return keys.map(
3041
+ (key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
3042
+ );
3043
+ }
3044
+ function formatThreadParticipantsLines(participants) {
3045
+ if (!participants || participants.length === 0) return null;
3046
+ return participants.map((p) => {
3047
+ const parts = [];
3048
+ if (p.userId) {
3049
+ parts.push(`user_id: ${escapeXml(p.userId)}`);
3050
+ parts.push(`slack_mention: <@${p.userId}>`);
3051
+ }
3052
+ if (p.userName) parts.push(`user_name: ${escapeXml(p.userName)}`);
3053
+ if (p.fullName) parts.push(`full_name: ${escapeXml(p.fullName)}`);
3054
+ return `- ${parts.join(", ")}`;
3055
+ });
3056
+ }
3057
+ function formatSlackCapabilityNames(capabilities) {
3058
+ const names = [
3059
+ capabilities?.canCreateCanvas ? "canvas_create" : "",
3060
+ capabilities?.canPostToChannel ? "channel_post" : "",
3061
+ capabilities?.canAddReactions ? "reaction_add" : ""
3062
+ ].filter(Boolean);
3063
+ return names.length > 0 ? names.join(", ") : "none";
3064
+ }
3065
+ var HEADER = "You are a Slack-based helper assistant. The behavior and output blocks below are authoritative; the personality block sets voice only.";
3066
+ var TOOL_POLICY_RULES = [
3067
+ "- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
3068
+ "- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
3069
+ "- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
3070
+ "- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
3071
+ `- Sandbox-backed file and shell tools operate in an isolated workspace rooted at ${SANDBOX_WORKSPACE_ROOT}; readFile/writeFile paths are sandbox-workspace paths, bash runs inside that workspace, and attachFile accepts absolute or workspace-relative sandbox paths.`,
3072
+ "- If a sandbox-backed tool reports that sandbox execution is unavailable, treat that as a blocker for local file/shell inspection; do not pretend host files were inspected.",
3073
+ "- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
3074
+ "- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
3075
+ ];
3076
+ var TOOL_CALL_STYLE_RULES = [
3077
+ "- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
3078
+ "- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
3079
+ "- When a first-class tool exists for an action, use it directly instead of asking the user to run an equivalent command, slash command, or manual lookup.",
3080
+ "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
3081
+ ];
3082
+ var SKILL_POLICY_RULES = [
3083
+ "- Before answering, scan `<available-skills>`. For matching operational or conceptual provider/repository workflow questions, load the most specific skill; do not answer from memory first. If none fits, do not load a skill.",
3084
+ "- Never load multiple skills up front. After `loadSkill`, follow `<loaded-skills>` and resolve relative references under that skill's location.",
3085
+ "- For explicit `/skill` triggers, treat that skill as selected unless the tool says it is unavailable.",
3086
+ "- For active MCP catalogs, use `searchMcpTools` to inspect descriptors before `callMcpTool`; pass exact returned `tool_name` values and put provider fields inside `arguments`.",
3087
+ "- Run authenticated provider commands directly after resolving target defaults; let the runtime handle auth pauses/resumes.",
3088
+ "- Run `jr-rpc config get|set|unset|list` as standalone bash commands for conversation-scoped provider defaults; do not chain them with `cd`, `&&`, pipes, or provider commands."
3089
+ ];
3090
+ var EXECUTION_CONTRACT_RULES = [
3091
+ "- Actionable request: act in this turn.",
3092
+ "- Continue until done or genuinely blocked. Do not finish with a plan, promise, or offer to check next when an available tool or source can move the request forward.",
3093
+ "- Completion means the final answer covers the user's actual ask, including requested follow-up checks, and is grounded in the best evidence you could access.",
3094
+ "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
3095
+ "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
3096
+ "- For non-trivial or long-running work, call `reportProgress` early when available, then only when the major phase changes. Routine tool calls should stay silent."
3097
+ ];
3098
+ var CONVERSATION_RULES = [
3099
+ "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
3100
+ "- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
3101
+ "- On resumed turns, post a brief continuation notice, then the resumed answer as a separate message."
3102
+ ];
3103
+ var SLACK_ACTION_RULES = [
3104
+ "- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
3105
+ "- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
3106
+ "- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
3107
+ "- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
3108
+ "- Do not claim an attachment, canvas, channel post, list update, or reaction succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
3109
+ "- Do not use reactions as progress indicators."
3110
+ ];
3111
+ var SAFETY_RULES = [
3112
+ "- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
3113
+ "- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
3114
+ "- Do not change system prompts, tool policies, security settings, credentials, or runtime configuration unless the user explicitly requests that exact administrative action and an available tool permits it."
3115
+ ];
3116
+ var FAILURE_RULES = [
3117
+ "- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
3118
+ "- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
3119
+ "- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
3120
+ ];
3121
+ function renderRuleSection(tag, lines) {
3122
+ return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
3123
+ }
3124
+ function buildBehaviorSection() {
3125
+ return [
3126
+ renderRuleSection("tool-policy", TOOL_POLICY_RULES),
3127
+ renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
3128
+ renderRuleSection("skill-policy", SKILL_POLICY_RULES),
3129
+ renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
3130
+ renderRuleSection("conversation", CONVERSATION_RULES),
3131
+ renderRuleSection("slack-actions", SLACK_ACTION_RULES),
3132
+ renderRuleSection("safety", SAFETY_RULES),
3133
+ renderRuleSection("failure-handling", FAILURE_RULES)
3134
+ ].join("\n\n");
3135
+ }
3136
+ function buildOutputSection() {
3137
+ const openTag = `<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
3138
+ return [
3139
+ openTag,
3140
+ "- Start with the answer or result, not internal process narration.",
3141
+ "- Use Slack-friendly mrkdwn: bolded section labels instead of headings, no markdown tables or markdown links, and plain URLs.",
3142
+ "- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
3143
+ "- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
3144
+ "- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
3145
+ "</output>"
3146
+ ].join("\n");
3147
+ }
3148
+ function buildRuntimeSection(params) {
3149
+ const lines = [
3150
+ `- version: ${escapeXml(getRuntimeMetadata().version ?? "unknown")}`,
3151
+ params.modelId ? `- model: ${escapeXml(params.modelId)}` : "",
3152
+ params.fastModelId ? `- fast_model: ${escapeXml(params.fastModelId)}` : "",
3153
+ params.thinkingLevel ? `- thinking: ${escapeXml(params.thinkingLevel)}` : "",
3154
+ params.channelId ? "- channel: slack" : "",
3155
+ params.channelId ? `- slack_capabilities: ${escapeXml(
3156
+ formatSlackCapabilityNames(params.slackCapabilities)
3157
+ )}` : "",
3158
+ `- sandbox_workspace: ${escapeXml(SANDBOX_WORKSPACE_ROOT)}`
3159
+ ].filter(Boolean);
3160
+ return renderTagBlock("runtime", lines.join("\n"));
3161
+ }
3162
+ function buildContextSection(params) {
3163
+ const blocks = [];
3164
+ if (JUNIOR_WORLD) {
3165
+ blocks.push(renderTag("world", [JUNIOR_WORLD.trim()]));
3166
+ }
3167
+ const referenceLines = formatReferenceFilesLines();
3168
+ if (referenceLines) {
3169
+ blocks.push(
3170
+ renderTag("reference-files", [
3171
+ "Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
3172
+ ...referenceLines
3173
+ ])
3174
+ );
3175
+ }
3176
+ blocks.push(
3177
+ renderIdentityBlock("assistant", {
3178
+ user_name: params.assistant?.userName ?? botConfig.userName,
3179
+ user_id: params.assistant?.userId
3180
+ })
3047
3181
  );
3048
- const relevantConfigLines = configurationKeys.filter((key) => relevantConfigSet.has(key)).map(
3049
- (key) => ` - ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
3182
+ blocks.push(
3183
+ renderIdentityBlock("requester", {
3184
+ full_name: params.requester?.fullName,
3185
+ user_name: params.requester?.userName,
3186
+ user_id: params.requester?.userId
3187
+ })
3050
3188
  );
3051
- const otherConfigLines = configurationKeys.filter((key) => !relevantConfigSet.has(key)).map(
3052
- (key) => ` - ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
3189
+ const participantLines = formatThreadParticipantsLines(
3190
+ params.threadParticipants
3053
3191
  );
3054
- const configurationSection = [
3055
- "Use these conversation-scoped defaults when the user has not provided explicit values in this turn.",
3056
- "If explicit user input conflicts with configuration, follow explicit user input.",
3057
- configurationKeys.length === 0 ? "- none" : [
3058
- ...relevantConfigLines.length > 0 ? ["- relevant_for_active_skills:", ...relevantConfigLines] : [],
3059
- ...otherConfigLines.length > 0 ? ["- other_available_keys:", ...otherConfigLines] : []
3060
- ].join("\n")
3061
- ].join("\n");
3192
+ if (participantLines) {
3193
+ blocks.push(
3194
+ renderTag("thread-participants", [
3195
+ "Known participants. When you mention one of these people, use the provided `<@USERID>` token exactly; do not write a bare `@name`.",
3196
+ ...participantLines
3197
+ ])
3198
+ );
3199
+ }
3200
+ const artifactLines = formatArtifactsLines(params.artifactState);
3201
+ if (artifactLines) {
3202
+ blocks.push(renderTag("artifacts", artifactLines));
3203
+ }
3204
+ const configLines = formatConfigurationLines(params.configuration);
3205
+ if (configLines) {
3206
+ blocks.push(
3207
+ renderTag("configuration", [
3208
+ "Install and conversation-scoped defaults. Channel overrides take precedence; follow explicit user input when it conflicts.",
3209
+ ...configLines
3210
+ ])
3211
+ );
3212
+ }
3213
+ if (params.turnState === "resumed") {
3214
+ blocks.push([
3215
+ "<turn-state>resumed</turn-state>",
3216
+ "This turn continues from a prior checkpoint. Prior tool results and assistant messages are already in the conversation history."
3217
+ ]);
3218
+ }
3219
+ if (params.invocation) {
3220
+ blocks.push([
3221
+ `<explicit-skill-trigger>/${escapeXml(params.invocation.skillName)}</explicit-skill-trigger>`
3222
+ ]);
3223
+ }
3224
+ const body = blocks.map((block) => block.join("\n")).join("\n\n");
3225
+ return renderTagBlock("context", body);
3226
+ }
3227
+ function buildCapabilitiesSection(params) {
3228
+ const blocks = [];
3229
+ blocks.push(formatAvailableSkillsForPrompt(params.availableSkills));
3230
+ blocks.push(formatLoadedSkillsForPrompt(params.activeSkills));
3231
+ const activeCatalogs = formatActiveMcpCatalogsForPrompt(
3232
+ params.activeMcpCatalogs
3233
+ );
3234
+ if (activeCatalogs) {
3235
+ blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
3236
+ }
3237
+ const providerCatalog = formatProviderCatalogForPrompt();
3238
+ if (providerCatalog) {
3239
+ blocks.push(renderTagBlock("providers", providerCatalog));
3240
+ }
3241
+ return renderTagBlock("capabilities", blocks.join("\n\n"));
3242
+ }
3243
+ function buildSystemPrompt(params) {
3062
3244
  const sections = [
3063
- baseSystemPrompt(),
3064
- renderTag(
3065
- "personality",
3066
- [
3067
- "Always follow the personality guidance for tone/style unless safety or policy constraints require otherwise.",
3068
- "",
3069
- JUNIOR_PERSONALITY.trim()
3070
- ].join("\n")
3071
- ),
3072
- ...JUNIOR_WORLD ? [
3073
- renderTag(
3074
- "world",
3075
- [
3076
- "Use this as the assistant's operational/domain context.",
3077
- "",
3078
- JUNIOR_WORLD.trim()
3079
- ].join("\n")
3080
- )
3081
- ] : [],
3082
- ...formatReferenceFilesSection(),
3083
- renderTag(
3084
- "identity-context",
3085
- [
3086
- "Use these blocks as authoritative metadata for identity questions.",
3087
- assistantSection,
3088
- requesterSection,
3089
- ...threadParticipants && threadParticipants.length > 0 ? [
3090
- renderTag(
3091
- "thread-participants",
3092
- [
3093
- "Known participants in this thread. When you mention one of these people, use the provided Slack mention token exactly as `<@USERID>` and do not write a bare `@name` form.",
3094
- ...threadParticipants.map((p) => {
3095
- const parts = [];
3096
- if (p.userId) {
3097
- parts.push(`user_id: ${escapeXml(p.userId)}`);
3098
- parts.push(`slack_mention: <@${p.userId}>`);
3099
- }
3100
- if (p.userName)
3101
- parts.push(`user_name: ${escapeXml(p.userName)}`);
3102
- if (p.fullName)
3103
- parts.push(`full_name: ${escapeXml(p.fullName)}`);
3104
- return `- ${parts.join(", ")}`;
3105
- })
3106
- ].join("\n")
3107
- )
3108
- ] : []
3109
- ].join("\n")
3110
- ),
3111
- renderTag(
3112
- "artifact-context",
3113
- [
3114
- "Use this thread-scoped memory for follow-up updates to existing Slack artifacts.",
3115
- artifactState ? [
3116
- artifactState.lastCanvasId ? `- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}` : "- last_canvas_id: none",
3117
- artifactState.lastCanvasUrl ? `- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}` : "- last_canvas_url: none",
3118
- artifactState.recentCanvases && artifactState.recentCanvases.length > 0 ? [
3119
- "- recent_canvases:",
3120
- ...artifactState.recentCanvases.map(
3121
- (canvas) => [
3122
- ` - id: ${escapeXml(canvas.id)}`,
3123
- canvas.title ? ` title: ${escapeXml(canvas.title)}` : " title: [unknown]",
3124
- canvas.url ? ` url: ${escapeXml(canvas.url)}` : " url: [unknown]",
3125
- canvas.createdAt ? ` created_at: ${escapeXml(canvas.createdAt)}` : " created_at: [unknown]"
3126
- ].join("\n")
3127
- )
3128
- ].join("\n") : "- recent_canvases: none",
3129
- artifactState.lastListId ? `- last_list_id: ${escapeXml(artifactState.lastListId)}` : "- last_list_id: none",
3130
- artifactState.lastListUrl ? `- last_list_url: ${escapeXml(artifactState.lastListUrl)}` : "- last_list_url: none"
3131
- ].join("\n") : "- none"
3132
- ].join("\n")
3133
- ),
3134
- renderTag("configuration-context", configurationSection),
3135
- renderTag(
3136
- "runtime-metadata",
3137
- [
3138
- "Use this for runtime version questions about the deployed assistant.",
3139
- `- version: ${escapeXml(runtimeMetadata?.version ?? "unknown")}`
3140
- ].join("\n")
3141
- ),
3142
- renderTag(
3143
- "provider-config",
3144
- [
3145
- "Use this catalog to map already-chosen provider work to valid config keys and provider defaults.",
3146
- "Do not use this catalog by itself to decide which domain skill matches an operational task.",
3147
- "When user intent is to set a provider default, choose a config key from this catalog and use jr-rpc config set.",
3148
- "The `jr-rpc` config command is a built-in bash custom command when conversation config is available; do not claim it is missing just because no `jr-rpc` skill is loaded.",
3149
- formatProviderCatalogForPrompt()
3150
- ].join("\n")
3151
- ),
3152
- renderTag(
3153
- "skill-routing",
3154
- [
3155
- "- Choose the skill that matches the requested operation, not incidental nouns, product names, organization names, or channel context.",
3156
- "- When multiple skills seem adjacent, prefer the one whose description matches the user's requested action most directly.",
3157
- "- If the task needs evidence from a specialized external system or workflow, load the matching skill before drawing conclusions.",
3158
- "- The provider-config catalog is for exact config keys and provider defaults after skill selection. It is not a shortcut for choosing a domain.",
3159
- "- Never start provider auth speculatively. First load the skill that owns the operation, then let runtime-managed credential injection handle authenticated provider commands for that skill."
3160
- ].join("\n")
3161
- ),
3162
- renderTag(
3163
- "tool-usage",
3164
- [
3165
- "- For factual or external questions, run tools/skills first, then answer from evidence.",
3166
- "- Use tool descriptions as the source of truth for when each tool should or should not be called.",
3167
- "- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
3168
- "- Keep routine setup and research steps silent in user-facing replies. Do not narrate duplicate checks, credential issuance, file writes, or similar internal progress unless the result is user-relevant.",
3169
- "- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
3170
- "- Prefer a single result-focused reply after tool work completes. Only send an interim reply when you need user input or have a concrete blocking problem to report.",
3171
- "- For external/factual research requests that require tools, do not send any preliminary conclusion, 'let me check', or progress narration before the evidence-gathering work is done. Use assistant status for in-progress work and make the first visible reply the researched answer.",
3172
- "- For evidence-gathering tasks, never state a factual conclusion before you have actually gathered and checked the sources.",
3173
- "- When the user provides multiple sources, synthesize them explicitly as one combined answer instead of anchoring the reply on a single page or API call.",
3174
- "- When the user provides explicit URLs or named sources, briefly anchor the answer to those provided sources (for example, 'Across the provided Slack docs...') so the summary reads as researched rather than generic memory.",
3175
- "- Do not include internal process chatter such as 'let me find', 'fetching now', 'good, I have sources', 'trying smaller limits', or 'I now have sufficient context' in the final user-facing reply.",
3176
- "- Never claim a screenshot/file is attached unless `attachFile` succeeded in this turn.",
3177
- "- If `attachFile` fails, explain the failure and do not say the file was shared.",
3178
- "- When you create or update a Slack artifact in this turn (for example a canvas, list, posted message, or attached file), mention it explicitly in the final reply and include its link when the tool returned one.",
3179
- "- For explicit in-channel post requests, prefer no thread text reply after a successful channel post. A reaction-only acknowledgment is acceptable when useful.",
3180
- "- When the user explicitly asks for an emoji reaction instead of text, react and skip the text reply.",
3181
- "- After the matching plugin-owned skill is loaded, authenticated bash commands for that skill get provider credentials injected automatically for the current turn.",
3182
- "- Resolve repo/project/org defaults before authenticated provider commands so the runtime can narrow injected credentials correctly.",
3183
- "- If no loaded skill clearly owns the authenticated command, load the matching skill first instead of guessing from the provider catalog.",
3184
- "- If provider authorization is required, the runtime sends the private authorization link itself and resumes the paused turn after authorization.",
3185
- "- Do not try to manage provider auth directly. Run the real provider command and let the runtime handle authorization, reconnect, and resume behavior.",
3186
- "- Provider-targeted commands may need `--target <value>` or a provider-specific configured default target key when the provider catalog shows one.",
3187
- "- To persist or read conversation defaults, choose a config key from the provider catalog or active skill metadata and run `jr-rpc config get|set|unset|list ...` as a bash command.",
3188
- "- `jr-rpc` config commands are built into the bash runtime for conversation-scoped config work; they do not require a separate helper binary in the sandbox.",
3189
- "- When your work is complete, provide the exact user-facing markdown response.",
3190
- "- Do not use reaction-based progress signals; Assistants API status already covers in-progress UX.",
3191
- "- Never call side-effecting tools when the user only asked for analysis or options.",
3192
- "- 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."
3193
- ].join("\n")
3194
- ),
3195
- renderTag(
3196
- "skills",
3197
- [
3198
- "- Explicit skill triggers may appear as `/skillname`.",
3199
- "- If explicitly invoked skill instructions are already present in <loaded_skills>, apply them immediately.",
3200
- "- If an explicitly invoked skill is present in <loaded_skills>, never say the skill is unavailable, missing, or unsupported in this environment.",
3201
- "- Otherwise, for an explicitly invoked skill, call `loadSkill` for that exact skill before applying skill-specific behavior.",
3202
- "- For requests without an explicit trigger where a skill clearly matches, call `loadSkill` before applying skill-specific behavior.",
3203
- "- When multiple skills appear relevant, prefer the skill whose description best matches the requested action rather than incidental domain nouns in the prompt.",
3204
- "- For explicit config tasks, you may load the helper skill that owns config commands, but do not use helper skills to choose the provider for unrelated operational work.",
3205
- "- Do not claim to have used a skill unless it is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
3206
- "- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
3207
- "- Load only the best matching skill first; do not load multiple skills upfront.",
3208
- "- After `loadSkill`, use `skill_dir` as the root for any referenced files you read via `bash`.",
3209
- "- If a loaded skill exposes MCP tools, they are registered as callable tools after `loadSkill` returns. Call them directly by name (for example `mcp__provider__tool_name`).",
3210
- "- If no skill is a clear fit, continue with normal tool usage."
3211
- ].join("\n")
3212
- ),
3213
- renderTag(
3214
- "output-contract",
3215
- [
3216
- "Always produce output that follows this contract:",
3217
- `<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`,
3218
- "- Use Slack-friendly markdown, not full CommonMark. Prefer bold section labels over markdown headings, and use bullets and short code blocks when helpful.",
3219
- "- Keep normal responses brief and scannable.",
3220
- "- If depth is needed, start with a concise summary and then provide fuller detail.",
3221
- "- Prefer a single compact thread reply when the full answer comfortably fits within this inline budget.",
3222
- "- When canvas creation is available and a research or document-style answer would likely need continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to a short summary plus the canvas link.",
3223
- "- Typical canvas-first cases include long-form research summaries, timelines, bios or profiles, structured notes, plans, comparisons, and other reusable reference documents.",
3224
- "- Do not create a canvas for short factual answers that fit cleanly in one normal thread reply.",
3225
- "- For tool-heavy research, discovery, or source-checking requests, do not send an initial acknowledgment. Start the visible reply only once you can present the actual answer.",
3226
- "- Do not narrate tool execution or repeated status updates in the visible reply.",
3227
- "- Avoid tables and markdown links like `[label](url)` unless explicitly requested. Prefer plain URLs or Slack-native entities when exact rendering matters.",
3228
- "- End every turn with a final user-facing markdown response.",
3229
- "</output>"
3230
- ].join("\n")
3231
- ),
3232
- availableSkillsSection,
3233
- activeSkillsSection,
3234
- ...activeToolsSection ? [activeToolsSection] : [],
3235
- renderTag(
3236
- "invocation-context",
3237
- invocation ? `Explicit skill trigger detected: /${invocation.skillName}` : "No explicit skill trigger detected."
3238
- )
3245
+ HEADER,
3246
+ renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
3247
+ renderTagBlock("behavior", buildBehaviorSection()),
3248
+ buildOutputSection(),
3249
+ buildCapabilitiesSection({
3250
+ availableSkills: params.availableSkills,
3251
+ activeSkills: params.activeSkills,
3252
+ activeMcpCatalogs: params.activeMcpCatalogs ?? []
3253
+ }),
3254
+ buildContextSection({
3255
+ assistant: params.assistant,
3256
+ requester: params.requester,
3257
+ artifactState: params.artifactState,
3258
+ configuration: params.configuration,
3259
+ threadParticipants: params.threadParticipants,
3260
+ invocation: params.invocation,
3261
+ turnState: params.turnState
3262
+ }),
3263
+ buildRuntimeSection(params.runtime ?? {})
3239
3264
  ];
3240
3265
  return sections.join("\n\n");
3241
3266
  }
3242
3267
 
3268
+ // src/chat/capabilities/catalog.ts
3269
+ var cachedCatalog;
3270
+ function getCapabilityCatalog() {
3271
+ const signature = getPluginCatalogSignature();
3272
+ if (cachedCatalog?.signature === signature) return cachedCatalog;
3273
+ const providers = getPluginCapabilityProviders();
3274
+ const capabilityToProvider = /* @__PURE__ */ new Map();
3275
+ for (const provider of providers) {
3276
+ for (const capability of provider.capabilities) {
3277
+ if (capabilityToProvider.has(capability)) {
3278
+ throw new Error(
3279
+ `Duplicate capability registration for "${capability}"`
3280
+ );
3281
+ }
3282
+ capabilityToProvider.set(capability, provider);
3283
+ }
3284
+ }
3285
+ cachedCatalog = { signature, providers, capabilityToProvider };
3286
+ return cachedCatalog;
3287
+ }
3288
+ var catalogLogged = false;
3289
+ function logCapabilityCatalogLoadedOnce() {
3290
+ if (catalogLogged) return;
3291
+ catalogLogged = true;
3292
+ const { providers } = getCapabilityCatalog();
3293
+ const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
3294
+ const configKeys = [
3295
+ ...new Set(providers.flatMap((p) => p.configKeys))
3296
+ ].sort();
3297
+ logInfo(
3298
+ "capability_catalog_loaded",
3299
+ {},
3300
+ {
3301
+ "app.capability.providers": providers.map((p) => p.provider),
3302
+ "app.capability.count": capabilityNames.length,
3303
+ "app.capability.names": capabilityNames,
3304
+ "app.config.key_count": configKeys.length,
3305
+ "app.config.keys": configKeys
3306
+ },
3307
+ "Loaded capability provider catalog"
3308
+ );
3309
+ }
3310
+
3243
3311
  // src/chat/capabilities/router.ts
3244
3312
  var ProviderCredentialRouter = class {
3245
3313
  brokersByProvider;
@@ -3965,6 +4033,27 @@ var MCP_CLIENT_INFO = {
3965
4033
  name: "junior-mcp-client",
3966
4034
  version: "1.0.0"
3967
4035
  };
4036
+ var MCP_TOOLS_CALL_METHOD = "tools/call";
4037
+ function getDefaultPort(url) {
4038
+ if (url.protocol === "https:") {
4039
+ return 443;
4040
+ }
4041
+ if (url.protocol === "http:") {
4042
+ return 80;
4043
+ }
4044
+ return void 0;
4045
+ }
4046
+ function getMcpNetworkAttributes(url) {
4047
+ const port = url.port ? Number(url.port) : getDefaultPort(url);
4048
+ return {
4049
+ "server.address": url.hostname,
4050
+ ...port !== void 0 ? { "server.port": port } : {},
4051
+ ...url.protocol === "http:" || url.protocol === "https:" ? {
4052
+ "network.protocol.name": "http",
4053
+ "network.transport": "tcp"
4054
+ } : {}
4055
+ };
4056
+ }
3968
4057
  var McpAuthorizationRequiredError = class extends Error {
3969
4058
  provider;
3970
4059
  constructor(provider, message) {
@@ -4014,10 +4103,22 @@ var PluginMcpClient = class {
4014
4103
  async callTool(name, args) {
4015
4104
  return await this.withSessionRecovery(async () => {
4016
4105
  const client2 = await this.getClient();
4106
+ const mcp = this.plugin.manifest.mcp;
4107
+ if (mcp) {
4108
+ const url = new URL(mcp.url);
4109
+ setSpanAttributes({
4110
+ "mcp.method.name": MCP_TOOLS_CALL_METHOD,
4111
+ "gen_ai.operation.name": "execute_tool",
4112
+ "gen_ai.tool.name": name,
4113
+ ...this.transport?.sessionId ? { "mcp.session.id": this.transport.sessionId } : {},
4114
+ ...this.transport?.protocolVersion ? { "mcp.protocol.version": this.transport.protocolVersion } : {},
4115
+ ...getMcpNetworkAttributes(url)
4116
+ });
4117
+ }
4017
4118
  const result = await this.wrapAuth(
4018
4119
  client2.callTool({
4019
4120
  name,
4020
- ...args && Object.keys(args).length > 0 ? { arguments: args } : {}
4121
+ arguments: args ?? {}
4021
4122
  })
4022
4123
  );
4023
4124
  await this.syncTransportSessionId();
@@ -4130,13 +4231,24 @@ var PluginMcpClient = class {
4130
4231
  }
4131
4232
  };
4132
4233
 
4133
- // src/chat/mcp/tool-manager.ts
4234
+ // src/chat/mcp/errors.ts
4134
4235
  var McpToolError = class extends Error {
4135
4236
  constructor(message) {
4136
4237
  super(message);
4137
4238
  this.name = "McpToolError";
4138
4239
  }
4139
4240
  };
4241
+ function getMcpAwareErrorType(error, fallback) {
4242
+ if (error instanceof McpToolError) {
4243
+ return "tool_error";
4244
+ }
4245
+ return error instanceof Error ? error.name : fallback;
4246
+ }
4247
+ function getMcpAwareErrorMessage(error) {
4248
+ return error instanceof Error ? error.message : String(error);
4249
+ }
4250
+
4251
+ // src/chat/mcp/tool-manager.ts
4140
4252
  function normalizeMcpToolName(provider, toolName) {
4141
4253
  return `mcp__${provider}__${toolName}`;
4142
4254
  }
@@ -4302,24 +4414,6 @@ var McpToolManager = class {
4302
4414
  (tool2) => this.toToolDescriptor(tool2)
4303
4415
  );
4304
4416
  }
4305
- searchTools(skills, query, options = {}) {
4306
- const resolved = this.getResolvedActiveTools(skills, options);
4307
- const trimmedQuery = query.trim();
4308
- if (!trimmedQuery || trimmedQuery === "*") {
4309
- return resolved.slice(0, Math.max(1, options.limit ?? 8)).map((tool2) => this.toToolDescriptor(tool2));
4310
- }
4311
- const normalizedQuery = trimmedQuery.toLowerCase();
4312
- const queryTokens = normalizedQuery.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
4313
- return resolved.map((tool2) => ({
4314
- tool: tool2,
4315
- score: this.scoreToolMatch(tool2, normalizedQuery, queryTokens)
4316
- })).filter((entry) => entry.score > 0).sort((left, right) => {
4317
- if (right.score !== left.score) {
4318
- return right.score - left.score;
4319
- }
4320
- return left.tool.name.localeCompare(right.tool.name);
4321
- }).slice(0, Math.max(1, options.limit ?? 8)).map((entry) => this.toToolDescriptor(entry.tool));
4322
- }
4323
4417
  filterListedTools(plugin, tools) {
4324
4418
  const allowedTools = plugin.manifest.mcp?.allowedTools;
4325
4419
  if (!allowedTools || allowedTools.length === 0) {
@@ -4351,6 +4445,8 @@ var McpToolManager = class {
4351
4445
  return client2;
4352
4446
  }
4353
4447
  toManagedTool(plugin, client2, tool2) {
4448
+ const outputSchema = toOptionalRecord(tool2.outputSchema);
4449
+ const annotations = toOptionalRecord(tool2.annotations);
4354
4450
  return {
4355
4451
  name: normalizeMcpToolName(plugin.manifest.name, tool2.name),
4356
4452
  description: describeMcpTool(plugin.manifest.name, tool2),
@@ -4358,8 +4454,15 @@ var McpToolManager = class {
4358
4454
  provider: plugin.manifest.name,
4359
4455
  rawName: tool2.name,
4360
4456
  ...tool2.title?.trim() ? { title: tool2.title.trim() } : {},
4457
+ ...outputSchema ? { outputSchema } : {},
4458
+ ...annotations ? { annotations } : {},
4361
4459
  execute: async (args) => {
4362
4460
  const resolvedArgs = typeof args === "object" && args !== null ? args : {};
4461
+ const baseAttributes = {
4462
+ "gen_ai.tool.name": tool2.name,
4463
+ "mcp.method.name": "tools/call"
4464
+ };
4465
+ setSpanAttributes(baseAttributes);
4363
4466
  try {
4364
4467
  const result = await client2.callTool(tool2.name, resolvedArgs);
4365
4468
  if ("isError" in result && result.isError) {
@@ -4395,6 +4498,20 @@ var McpToolManager = class {
4395
4498
  }
4396
4499
  };
4397
4500
  }
4501
+ const errorAttributes = {
4502
+ ...baseAttributes,
4503
+ "error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
4504
+ "error.message": getMcpAwareErrorMessage(error)
4505
+ };
4506
+ setSpanAttributes(errorAttributes);
4507
+ if (error instanceof McpToolError) {
4508
+ logWarn(
4509
+ "mcp_tool_call_failed",
4510
+ {},
4511
+ errorAttributes,
4512
+ "MCP tool call failed"
4513
+ );
4514
+ }
4398
4515
  throw error;
4399
4516
  }
4400
4517
  }
@@ -4441,37 +4558,19 @@ var McpToolManager = class {
4441
4558
  toToolDescriptor(tool2) {
4442
4559
  return {
4443
4560
  name: tool2.name,
4561
+ rawName: tool2.rawName,
4562
+ ...tool2.title ? { title: tool2.title } : {},
4444
4563
  description: tool2.description,
4445
4564
  parameters: tool2.parameters,
4565
+ ...tool2.outputSchema ? { outputSchema: tool2.outputSchema } : {},
4566
+ ...tool2.annotations ? { annotations: tool2.annotations } : {},
4446
4567
  provider: tool2.provider
4447
4568
  };
4448
4569
  }
4449
- scoreToolMatch(tool2, normalizedQuery, queryTokens) {
4450
- const exactCandidates = [tool2.name, tool2.rawName, tool2.title].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
4451
- if (exactCandidates.includes(normalizedQuery)) {
4452
- return 100;
4453
- }
4454
- let score = 0;
4455
- const searchableText = [
4456
- tool2.name,
4457
- tool2.rawName,
4458
- tool2.title,
4459
- tool2.description,
4460
- tool2.provider
4461
- ].filter((value) => Boolean(value)).join(" ").toLowerCase();
4462
- for (const candidate of exactCandidates) {
4463
- if (candidate.startsWith(normalizedQuery)) {
4464
- score = Math.max(score, 60);
4465
- }
4466
- }
4467
- for (const token of queryTokens) {
4468
- if (searchableText.includes(token)) {
4469
- score += 10;
4470
- }
4471
- }
4472
- return score;
4473
- }
4474
4570
  };
4571
+ function toOptionalRecord(value) {
4572
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4573
+ }
4475
4574
 
4476
4575
  // src/chat/tools/definition.ts
4477
4576
  function tool(definition) {
@@ -4784,8 +4883,61 @@ function createImageGenerateTool(hooks, deps = {}) {
4784
4883
  });
4785
4884
  }
4786
4885
 
4787
- // src/chat/tools/skill/load-skill.ts
4886
+ // src/chat/tools/skill/call-mcp-tool.ts
4788
4887
  import { Type as Type4 } from "@sinclair/typebox";
4888
+ function resolveMcpArguments(input) {
4889
+ const extraKeys = Object.keys(input).filter(
4890
+ (key) => key !== "tool_name" && key !== "arguments"
4891
+ );
4892
+ if (extraKeys.length > 0) {
4893
+ throw new Error(
4894
+ `callMcpTool MCP arguments must be nested under arguments, not top-level fields: ${extraKeys.join(", ")}`
4895
+ );
4896
+ }
4897
+ if ("arguments" in input) {
4898
+ const args = input.arguments;
4899
+ if (args === void 0) {
4900
+ return {};
4901
+ }
4902
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
4903
+ throw new Error("callMcpTool arguments must be an object when provided");
4904
+ }
4905
+ return args;
4906
+ }
4907
+ return {};
4908
+ }
4909
+ function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
4910
+ return tool({
4911
+ description: "Call an active MCP tool by exact tool_name. Use loadSkill to activate the provider, then searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
4912
+ inputSchema: Type4.Object(
4913
+ {
4914
+ tool_name: Type4.String({
4915
+ minLength: 1,
4916
+ description: "Exact MCP tool_name from searchMcpTools."
4917
+ }),
4918
+ arguments: Type4.Optional(
4919
+ Type4.Record(Type4.String(), Type4.Unknown(), {
4920
+ description: 'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
4921
+ })
4922
+ )
4923
+ },
4924
+ { additionalProperties: false }
4925
+ ),
4926
+ execute: async (input) => {
4927
+ const { tool_name } = input;
4928
+ const mcpTool = mcpToolManager.getResolvedActiveTools(getActiveSkills()).find((candidate) => candidate.name === tool_name);
4929
+ if (!mcpTool) {
4930
+ throw new Error(`MCP tool is not active for this turn: ${tool_name}`);
4931
+ }
4932
+ return await mcpTool.execute(
4933
+ resolveMcpArguments(input)
4934
+ );
4935
+ }
4936
+ });
4937
+ }
4938
+
4939
+ // src/chat/tools/skill/load-skill.ts
4940
+ import { Type as Type5 } from "@sinclair/typebox";
4789
4941
  function toLoadedSkill(result, availableSkills) {
4790
4942
  if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
4791
4943
  return null;
@@ -4797,7 +4949,6 @@ function toLoadedSkill(result, availableSkills) {
4797
4949
  skillPath: metadata?.skillPath ?? result.skill_dir,
4798
4950
  ...metadata?.pluginProvider ? { pluginProvider: metadata.pluginProvider } : {},
4799
4951
  ...metadata?.allowedTools ? { allowedTools: metadata.allowedTools } : {},
4800
- ...metadata?.usesConfig ? { usesConfig: metadata.usesConfig } : {},
4801
4952
  body: result.instructions
4802
4953
  };
4803
4954
  }
@@ -4824,15 +4975,17 @@ async function loadSkillFromHost(availableSkills, skillName) {
4824
4975
  skill_name: skill.name,
4825
4976
  description: skill.description,
4826
4977
  skill_dir: skillDir,
4978
+ working_directory: skillDir,
4827
4979
  location: skillFilePath,
4980
+ path_resolution: `Resolve relative paths in this skill against ${skillDir}. For bash commands from this skill, cd to ${skillDir} first or use absolute paths.`,
4828
4981
  instructions: loaded.body
4829
4982
  };
4830
4983
  }
4831
4984
  function createLoadSkillTool(availableSkills, options) {
4832
4985
  return tool({
4833
- description: "Load a skill by name so its instructions are available for this turn. The result includes `available_tools` when the skill exposes MCP tools for this turn. Use when a request clearly matches a known skill. Do not use when no skill is relevant.",
4834
- inputSchema: Type4.Object({
4835
- skill_name: Type4.String({
4986
+ description: "Load a skill by name for this turn. The result includes working_directory; resolve skill paths there and run skill-owned bash commands from there or with absolute paths. When the result includes mcp_provider, use searchMcpTools before callMcpTool. Use when a request clearly matches a known skill.",
4987
+ inputSchema: Type5.Object({
4988
+ skill_name: Type5.String({
4836
4989
  minLength: 1,
4837
4990
  description: "Skill name to load, without the leading slash."
4838
4991
  })
@@ -4851,53 +5004,87 @@ function createLoadSkillTool(availableSkills, options) {
4851
5004
  });
4852
5005
  }
4853
5006
 
4854
- // src/chat/tools/sandbox/read-file.ts
4855
- import { Type as Type5 } from "@sinclair/typebox";
4856
- function createReadFileTool() {
4857
- return tool({
4858
- description: "Read a file from the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Do not use for broad discovery when search tools are better.",
4859
- inputSchema: Type5.Object(
4860
- {
4861
- path: Type5.String({
4862
- minLength: 1,
4863
- description: "Path to the file in the sandbox workspace."
4864
- })
4865
- },
4866
- { additionalProperties: false }
4867
- ),
4868
- execute: async () => {
4869
- throw new Error(
4870
- "readFile can only run when sandbox execution is enabled."
4871
- );
4872
- }
4873
- });
4874
- }
4875
-
4876
- // src/chat/tools/runtime/report-progress.ts
5007
+ // src/chat/tools/skill/search-mcp-tools.ts
4877
5008
  import { Type as Type6 } from "@sinclair/typebox";
4878
- function createReportProgressTool() {
4879
- return tool({
4880
- description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
4881
- inputSchema: Type6.Object({
4882
- message: Type6.String({
4883
- minLength: 1,
4884
- description: "Short user-facing progress message."
4885
- })
4886
- })
4887
- });
4888
- }
4889
-
4890
- // src/chat/tools/skill/search-tools.ts
4891
- import { Type as Type7 } from "@sinclair/typebox";
4892
5009
 
4893
5010
  // src/chat/tools/skill/mcp-tool-summary.ts
4894
- function summarizeInputSchema(schema) {
4895
- const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
4896
- const required = Array.isArray(schema.required) ? new Set(
5011
+ function getSchemaProperties(schema) {
5012
+ return schema.properties && typeof schema.properties === "object" ? schema.properties : {};
5013
+ }
5014
+ function getRequiredFields(schema) {
5015
+ return Array.isArray(schema.required) ? new Set(
4897
5016
  schema.required.filter(
4898
5017
  (value) => typeof value === "string"
4899
5018
  )
4900
5019
  ) : /* @__PURE__ */ new Set();
5020
+ }
5021
+ function formatSchemaType(schema) {
5022
+ if (!schema || typeof schema !== "object") {
5023
+ return "unknown";
5024
+ }
5025
+ const typed = schema;
5026
+ const type = typed.type;
5027
+ if (typeof type === "string") {
5028
+ if (type === "array") {
5029
+ return `${formatSchemaType(typed.items)}[]`;
5030
+ }
5031
+ return type;
5032
+ }
5033
+ if (Array.isArray(type)) {
5034
+ return type.filter((value) => typeof value === "string").join(" | ");
5035
+ }
5036
+ if (Array.isArray(typed.enum) && typed.enum.length > 0) {
5037
+ return typed.enum.map((value) => JSON.stringify(value)).join(" | ");
5038
+ }
5039
+ return "unknown";
5040
+ }
5041
+ function formatArgumentPlaceholder(name, schema) {
5042
+ const type = formatSchemaType(schema);
5043
+ if (type === "string") {
5044
+ return `<${name}>`;
5045
+ }
5046
+ if (type === "number" || type === "integer") {
5047
+ return "<number>";
5048
+ }
5049
+ if (type === "boolean") {
5050
+ return "<boolean>";
5051
+ }
5052
+ if (type.endsWith("[]")) {
5053
+ return "<array>";
5054
+ }
5055
+ if (type === "object") {
5056
+ return "<object>";
5057
+ }
5058
+ return `<${type}>`;
5059
+ }
5060
+ function formatMcpToolSignature(toolName, schema) {
5061
+ const properties = getSchemaProperties(schema);
5062
+ const required = getRequiredFields(schema);
5063
+ const fields = Object.entries(properties).map(([name, propertySchema]) => {
5064
+ const marker = required.has(name) ? "" : "?";
5065
+ return `${name}${marker}: ${formatSchemaType(propertySchema)}`;
5066
+ });
5067
+ if (fields.length === 0) {
5068
+ return `${toolName}()`;
5069
+ }
5070
+ return `${toolName}({ ${fields.join(", ")} })`;
5071
+ }
5072
+ function formatMcpToolCallExample(toolName, schema) {
5073
+ return {
5074
+ tool_name: toolName,
5075
+ arguments: Object.fromEntries(
5076
+ Object.entries(getSchemaProperties(schema)).map(
5077
+ ([name, propertySchema]) => [
5078
+ name,
5079
+ formatArgumentPlaceholder(name, propertySchema)
5080
+ ]
5081
+ )
5082
+ )
5083
+ };
5084
+ }
5085
+ function summarizeInputSchema(schema) {
5086
+ const properties = getSchemaProperties(schema);
5087
+ const required = getRequiredFields(schema);
4901
5088
  const propertyNames = Object.keys(properties);
4902
5089
  if (propertyNames.length === 0) {
4903
5090
  return "No arguments.";
@@ -4907,59 +5094,190 @@ function summarizeInputSchema(schema) {
4907
5094
  function toExposedToolSummary(toolDef) {
4908
5095
  return {
4909
5096
  tool_name: toolDef.name,
5097
+ mcp_tool_name: toolDef.rawName,
4910
5098
  provider: toolDef.provider,
5099
+ ...toolDef.title ? { title: toolDef.title } : {},
4911
5100
  description: toolDef.description,
5101
+ signature: formatMcpToolSignature(toolDef.name, toolDef.parameters),
5102
+ call: formatMcpToolCallExample(toolDef.name, toolDef.parameters),
4912
5103
  input_schema: toolDef.parameters,
4913
- input_schema_summary: summarizeInputSchema(toolDef.parameters)
5104
+ input_schema_summary: summarizeInputSchema(toolDef.parameters),
5105
+ ...toolDef.outputSchema ? { output_schema: toolDef.outputSchema } : {},
5106
+ ...toolDef.annotations ? { annotations: toolDef.annotations } : {}
4914
5107
  };
4915
5108
  }
4916
-
4917
- // src/chat/tools/skill/search-tools.ts
4918
- var DEFAULT_LIMIT = 5;
4919
- var MAX_LIMIT = 20;
4920
- function createSearchToolsTool(mcpToolManager, getActiveSkills) {
5109
+ function toActiveMcpCatalogSummaries(toolDefs) {
5110
+ const countsByProvider = /* @__PURE__ */ new Map();
5111
+ for (const toolDef of toolDefs) {
5112
+ countsByProvider.set(
5113
+ toolDef.provider,
5114
+ (countsByProvider.get(toolDef.provider) ?? 0) + 1
5115
+ );
5116
+ }
5117
+ return [...countsByProvider.entries()].map(([provider, availableToolCount]) => ({
5118
+ provider,
5119
+ available_tool_count: availableToolCount
5120
+ })).sort((left, right) => left.provider.localeCompare(right.provider));
5121
+ }
5122
+
5123
+ // src/chat/tools/skill/search-mcp-tools.ts
5124
+ var DEFAULT_MAX_RESULTS = 5;
5125
+ var MAX_RESULTS = 20;
5126
+ function normalize(value) {
5127
+ return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
5128
+ }
5129
+ function searchableToolText(toolDef) {
5130
+ return normalize(
5131
+ [
5132
+ toolDef.name,
5133
+ toolDef.rawName,
5134
+ toolDef.title,
5135
+ toolDef.provider,
5136
+ toolDef.description,
5137
+ JSON.stringify(toolDef.parameters),
5138
+ JSON.stringify(toolDef.outputSchema),
5139
+ JSON.stringify(toolDef.annotations)
5140
+ ].filter(Boolean).join(" ")
5141
+ );
5142
+ }
5143
+ function scoreTool(toolDef, query) {
5144
+ const normalizedQuery = normalize(query);
5145
+ if (!normalizedQuery) {
5146
+ return 0;
5147
+ }
5148
+ const normalizedName = normalize(toolDef.name);
5149
+ const normalizedRawName = normalize(toolDef.rawName);
5150
+ const text = searchableToolText(toolDef);
5151
+ let score = 0;
5152
+ if (normalizedName === normalizedQuery || normalizedRawName === normalizedQuery) {
5153
+ score += 100;
5154
+ }
5155
+ if (normalizedName.includes(normalizedQuery)) {
5156
+ score += 50;
5157
+ }
5158
+ if (normalizedRawName.includes(normalizedQuery)) {
5159
+ score += 45;
5160
+ }
5161
+ if (text.includes(normalizedQuery)) {
5162
+ score += 25;
5163
+ }
5164
+ for (const term of normalizedQuery.split(/\s+/).filter(Boolean)) {
5165
+ if (normalizedName.includes(term)) {
5166
+ score += 12;
5167
+ }
5168
+ if (normalizedRawName.includes(term)) {
5169
+ score += 10;
5170
+ }
5171
+ if (text.includes(term)) {
5172
+ score += 4;
5173
+ }
5174
+ }
5175
+ return score;
5176
+ }
5177
+ function searchMcpCatalog(tools, query) {
5178
+ if (!normalize(query)) {
5179
+ return [...tools].sort(
5180
+ (left, right) => left.name.localeCompare(right.name)
5181
+ );
5182
+ }
5183
+ return tools.map(
5184
+ (toolDef) => ({
5185
+ tool: toolDef,
5186
+ score: scoreTool(toolDef, query)
5187
+ })
5188
+ ).filter((ranked) => ranked.score > 0).sort((left, right) => {
5189
+ if (right.score !== left.score) {
5190
+ return right.score - left.score;
5191
+ }
5192
+ return left.tool.name.localeCompare(right.tool.name);
5193
+ }).map((ranked) => ranked.tool);
5194
+ }
5195
+ function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
4921
5196
  return tool({
4922
- description: "Search active MCP tools exposed by the currently loaded skills. Use when you need to rediscover or filter active tools.",
4923
- inputSchema: Type7.Object(
5197
+ description: "List or search active MCP tools and return full descriptors, including input/output schemas and annotations. Use after loadSkill when choosing a provider tool or when callMcpTool arguments are unclear.",
5198
+ inputSchema: Type6.Object(
4924
5199
  {
4925
- query: Type7.String({
4926
- minLength: 1,
4927
- description: "Search query for matching MCP tool names or descriptions."
4928
- }),
4929
- provider: Type7.Optional(
4930
- Type7.String({
5200
+ query: Type6.Optional(
5201
+ Type6.String({
4931
5202
  minLength: 1,
4932
- description: "Optional MCP provider filter, for example notion or sentry."
5203
+ description: "Optional search terms describing the MCP tool or arguments needed."
4933
5204
  })
4934
5205
  ),
4935
- limit: Type7.Optional(
4936
- Type7.Integer({
5206
+ provider: Type6.Optional(
5207
+ Type6.String({
5208
+ minLength: 1,
5209
+ description: "Optional provider name to list or search within."
5210
+ })
5211
+ ),
5212
+ max_results: Type6.Optional(
5213
+ Type6.Integer({
4937
5214
  minimum: 1,
4938
- maximum: MAX_LIMIT,
4939
- description: "Maximum number of matching tools to return."
5215
+ maximum: MAX_RESULTS,
5216
+ description: "Maximum matching tool descriptors to return."
4940
5217
  })
4941
5218
  )
4942
5219
  },
4943
5220
  { additionalProperties: false }
4944
5221
  ),
4945
- execute: async ({ query, provider, limit }) => {
4946
- const results = mcpToolManager.searchTools(getActiveSkills(), query, {
4947
- ...provider ? { provider } : {},
4948
- limit: limit ?? DEFAULT_LIMIT
4949
- }).map(toExposedToolSummary);
5222
+ execute: async ({ query, provider, max_results }) => {
5223
+ const catalog = mcpToolManager.getActiveToolCatalog(
5224
+ getActiveSkills(),
5225
+ provider ? { provider } : {}
5226
+ );
5227
+ const maxResults = max_results ?? DEFAULT_MAX_RESULTS;
5228
+ const matches = searchMcpCatalog(catalog, query ?? "").slice(
5229
+ 0,
5230
+ maxResults
5231
+ );
4950
5232
  return {
4951
- ok: true,
4952
- query,
4953
- ...provider ? { provider } : {},
4954
- result_count: results.length,
4955
- results
5233
+ query: query ?? null,
5234
+ provider: provider ?? null,
5235
+ total_active_tools: catalog.length,
5236
+ returned_tools: matches.length,
5237
+ tools: matches.map(toExposedToolSummary)
4956
5238
  };
4957
5239
  }
4958
5240
  });
4959
5241
  }
4960
5242
 
4961
- // src/chat/tools/slack/channel-list-messages.ts
5243
+ // src/chat/tools/sandbox/read-file.ts
5244
+ import { Type as Type7 } from "@sinclair/typebox";
5245
+ function createReadFileTool() {
5246
+ return tool({
5247
+ description: "Read a file from the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Do not use for broad discovery when search tools are better.",
5248
+ inputSchema: Type7.Object(
5249
+ {
5250
+ path: Type7.String({
5251
+ minLength: 1,
5252
+ description: "Path to the file in the sandbox workspace."
5253
+ })
5254
+ },
5255
+ { additionalProperties: false }
5256
+ ),
5257
+ execute: async () => {
5258
+ throw new Error(
5259
+ "readFile can only run when sandbox execution is enabled."
5260
+ );
5261
+ }
5262
+ });
5263
+ }
5264
+
5265
+ // src/chat/tools/runtime/report-progress.ts
4962
5266
  import { Type as Type8 } from "@sinclair/typebox";
5267
+ function createReportProgressTool() {
5268
+ return tool({
5269
+ description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
5270
+ inputSchema: Type8.Object({
5271
+ message: Type8.String({
5272
+ minLength: 1,
5273
+ description: "Short user-facing progress message."
5274
+ })
5275
+ })
5276
+ });
5277
+ }
5278
+
5279
+ // src/chat/tools/slack/channel-list-messages.ts
5280
+ import { Type as Type9 } from "@sinclair/typebox";
4963
5281
 
4964
5282
  // src/chat/slack/channel.ts
4965
5283
  async function listChannelMessages(input) {
@@ -5048,39 +5366,39 @@ async function listThreadReplies(input) {
5048
5366
  function createSlackChannelListMessagesTool(context) {
5049
5367
  return tool({
5050
5368
  description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
5051
- inputSchema: Type8.Object({
5052
- limit: Type8.Optional(
5053
- Type8.Integer({
5369
+ inputSchema: Type9.Object({
5370
+ limit: Type9.Optional(
5371
+ Type9.Integer({
5054
5372
  minimum: 1,
5055
5373
  maximum: 1e3,
5056
5374
  description: "Maximum number of messages to return across pages."
5057
5375
  })
5058
5376
  ),
5059
- cursor: Type8.Optional(
5060
- Type8.String({
5377
+ cursor: Type9.Optional(
5378
+ Type9.String({
5061
5379
  minLength: 1,
5062
5380
  description: "Optional cursor to continue from a prior call."
5063
5381
  })
5064
5382
  ),
5065
- oldest: Type8.Optional(
5066
- Type8.String({
5383
+ oldest: Type9.Optional(
5384
+ Type9.String({
5067
5385
  minLength: 1,
5068
5386
  description: "Optional oldest message timestamp (Slack ts) for range filtering."
5069
5387
  })
5070
5388
  ),
5071
- latest: Type8.Optional(
5072
- Type8.String({
5389
+ latest: Type9.Optional(
5390
+ Type9.String({
5073
5391
  minLength: 1,
5074
5392
  description: "Optional latest message timestamp (Slack ts) for range filtering."
5075
5393
  })
5076
5394
  ),
5077
- inclusive: Type8.Optional(
5078
- Type8.Boolean({
5395
+ inclusive: Type9.Optional(
5396
+ Type9.Boolean({
5079
5397
  description: "Whether oldest/latest bounds should be inclusive."
5080
5398
  })
5081
5399
  ),
5082
- max_pages: Type8.Optional(
5083
- Type8.Integer({
5400
+ max_pages: Type9.Optional(
5401
+ Type9.Integer({
5084
5402
  minimum: 1,
5085
5403
  maximum: 10,
5086
5404
  description: "Maximum number of API pages to traverse in a single call."
@@ -5134,7 +5452,7 @@ function createSlackChannelListMessagesTool(context) {
5134
5452
  }
5135
5453
 
5136
5454
  // src/chat/tools/slack/channel-post-message.ts
5137
- import { Type as Type9 } from "@sinclair/typebox";
5455
+ import { Type as Type10 } from "@sinclair/typebox";
5138
5456
 
5139
5457
  // src/chat/tools/idempotency.ts
5140
5458
  function stableSerialize(value) {
@@ -5156,8 +5474,8 @@ function createOperationKey(toolName, input) {
5156
5474
  function createSlackChannelPostMessageTool(context, state) {
5157
5475
  return tool({
5158
5476
  description: "Post a message in the active Slack channel context (outside the thread). Use this only when the user explicitly asks to post/send/share/say something in the current channel. Do not use it for normal thread replies, speculative broadcasts, or requests targeting another named channel; explain that limitation instead. Do not claim a channel message was posted unless this tool succeeds in this turn.",
5159
- inputSchema: Type9.Object({
5160
- text: Type9.String({
5477
+ inputSchema: Type10.Object({
5478
+ text: Type10.String({
5161
5479
  minLength: 1,
5162
5480
  maxLength: 4e4,
5163
5481
  description: "Slack mrkdwn text to post."
@@ -5200,12 +5518,12 @@ function createSlackChannelPostMessageTool(context, state) {
5200
5518
  }
5201
5519
 
5202
5520
  // src/chat/tools/slack/message-add-reaction.ts
5203
- import { Type as Type10 } from "@sinclair/typebox";
5521
+ import { Type as Type11 } from "@sinclair/typebox";
5204
5522
  function createSlackMessageAddReactionTool(context, state) {
5205
5523
  return tool({
5206
5524
  description: "Add an emoji reaction to the current inbound Slack message. Use sparingly for lightweight acknowledgements. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
5207
- inputSchema: Type10.Object({
5208
- emoji: Type10.String({
5525
+ inputSchema: Type11.Object({
5526
+ emoji: Type11.String({
5209
5527
  minLength: 1,
5210
5528
  maxLength: 64,
5211
5529
  description: "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
@@ -5263,7 +5581,7 @@ function createSlackMessageAddReactionTool(context, state) {
5263
5581
  }
5264
5582
 
5265
5583
  // src/chat/tools/slack/canvas-tools.ts
5266
- import { Type as Type11 } from "@sinclair/typebox";
5584
+ import { Type as Type12 } from "@sinclair/typebox";
5267
5585
 
5268
5586
  // src/chat/tools/slack/canvases.ts
5269
5587
  function normalizeCanvasMarkdown(markdown) {
@@ -5478,14 +5796,14 @@ function mergeRecentCanvases(existing, created) {
5478
5796
  }
5479
5797
  function createSlackCanvasCreateTool(context, state) {
5480
5798
  return tool({
5481
- description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, keep the thread reply brief and include the canvas link. Do not use for short answers that fit cleanly in one normal thread reply.",
5482
- inputSchema: Type11.Object({
5483
- title: Type11.String({
5799
+ description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
5800
+ inputSchema: Type12.Object({
5801
+ title: Type12.String({
5484
5802
  minLength: 1,
5485
5803
  maxLength: 160,
5486
5804
  description: "Canvas title."
5487
5805
  }),
5488
- markdown: Type11.String({
5806
+ markdown: Type12.String({
5489
5807
  minLength: 1,
5490
5808
  description: "Canvas markdown body content."
5491
5809
  })
@@ -5551,29 +5869,29 @@ function createSlackCanvasCreateTool(context, state) {
5551
5869
  function createSlackCanvasUpdateTool(state, _context) {
5552
5870
  return tool({
5553
5871
  description: "Update the active Slack canvas tracked in artifact context. Use when continuing or correcting a document already tracked in this thread. Do not use to create a brand-new long-form artifact.",
5554
- inputSchema: Type11.Object({
5555
- markdown: Type11.String({
5872
+ inputSchema: Type12.Object({
5873
+ markdown: Type12.String({
5556
5874
  minLength: 1,
5557
5875
  description: "Markdown content to insert or use as replacement text."
5558
5876
  }),
5559
- operation: Type11.Optional(
5560
- Type11.Union(
5877
+ operation: Type12.Optional(
5878
+ Type12.Union(
5561
5879
  [
5562
- Type11.Literal("insert_at_end"),
5563
- Type11.Literal("insert_at_start"),
5564
- Type11.Literal("replace")
5880
+ Type12.Literal("insert_at_end"),
5881
+ Type12.Literal("insert_at_start"),
5882
+ Type12.Literal("replace")
5565
5883
  ],
5566
5884
  { description: "Canvas update mode." }
5567
5885
  )
5568
5886
  ),
5569
- section_id: Type11.Optional(
5570
- Type11.String({
5887
+ section_id: Type12.Optional(
5888
+ Type12.String({
5571
5889
  minLength: 1,
5572
5890
  description: "Optional section ID required for targeted replace operations."
5573
5891
  })
5574
5892
  ),
5575
- section_contains_text: Type11.Optional(
5576
- Type11.String({
5893
+ section_contains_text: Type12.Optional(
5894
+ Type12.String({
5577
5895
  minLength: 1,
5578
5896
  description: "Optional helper text used to find the target section when section_id is not provided."
5579
5897
  })
@@ -5639,8 +5957,8 @@ function createSlackCanvasUpdateTool(state, _context) {
5639
5957
  function createSlackCanvasReadTool() {
5640
5958
  return tool({
5641
5959
  description: "Read a Slack canvas the bot has access to (including canvases the bot created) by canvas ID or Slack canvas/docs URL. Use when the user shares a Slack canvas link (https://*.slack.com/docs/... or /canvas/...) or references a canvas ID and you need its contents. Do not use for generic web pages \u2014 use webFetch for those.",
5642
- inputSchema: Type11.Object({
5643
- canvas: Type11.String({
5960
+ inputSchema: Type12.Object({
5961
+ canvas: Type12.String({
5644
5962
  minLength: 1,
5645
5963
  description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
5646
5964
  })
@@ -5690,7 +6008,7 @@ function createSlackCanvasReadTool() {
5690
6008
  }
5691
6009
 
5692
6010
  // src/chat/tools/slack/list-tools.ts
5693
- import { Type as Type12 } from "@sinclair/typebox";
6011
+ import { Type as Type13 } from "@sinclair/typebox";
5694
6012
 
5695
6013
  // src/chat/tools/slack/lists.ts
5696
6014
  function normalizeKey(value) {
@@ -5864,8 +6182,8 @@ async function updateListItem(input) {
5864
6182
  function createSlackListCreateTool(state) {
5865
6183
  return tool({
5866
6184
  description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
5867
- inputSchema: Type12.Object({
5868
- name: Type12.String({
6185
+ inputSchema: Type13.Object({
6186
+ name: Type13.String({
5869
6187
  minLength: 1,
5870
6188
  maxLength: 160,
5871
6189
  description: "Name for the new Slack list."
@@ -5900,20 +6218,20 @@ function createSlackListCreateTool(state) {
5900
6218
  function createSlackListAddItemsTool(state) {
5901
6219
  return tool({
5902
6220
  description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
5903
- inputSchema: Type12.Object({
5904
- items: Type12.Array(Type12.String({ minLength: 1 }), {
6221
+ inputSchema: Type13.Object({
6222
+ items: Type13.Array(Type13.String({ minLength: 1 }), {
5905
6223
  minItems: 1,
5906
6224
  maxItems: 25,
5907
6225
  description: "List item titles to create."
5908
6226
  }),
5909
- assignee_user_id: Type12.Optional(
5910
- Type12.String({
6227
+ assignee_user_id: Type13.Optional(
6228
+ Type13.String({
5911
6229
  minLength: 1,
5912
6230
  description: "Optional Slack user ID assigned to all created items."
5913
6231
  })
5914
6232
  ),
5915
- due_date: Type12.Optional(
5916
- Type12.String({
6233
+ due_date: Type13.Optional(
6234
+ Type13.String({
5917
6235
  pattern: "^\\d{4}-\\d{2}-\\d{2}$",
5918
6236
  description: "Optional due date in YYYY-MM-DD format."
5919
6237
  })
@@ -5962,9 +6280,9 @@ function createSlackListAddItemsTool(state) {
5962
6280
  function createSlackListGetItemsTool(state) {
5963
6281
  return tool({
5964
6282
  description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
5965
- inputSchema: Type12.Object({
5966
- limit: Type12.Optional(
5967
- Type12.Integer({
6283
+ inputSchema: Type13.Object({
6284
+ limit: Type13.Optional(
6285
+ Type13.Integer({
5968
6286
  minimum: 1,
5969
6287
  maximum: 200,
5970
6288
  description: "Maximum number of list items to return."
@@ -5989,19 +6307,19 @@ function createSlackListGetItemsTool(state) {
5989
6307
  function createSlackListUpdateItemTool(state) {
5990
6308
  return tool({
5991
6309
  description: "Update an item in the active Slack list tracked in artifact context (title/completion). Use when the user asks to mark progress or rename a tracked task. Do not use to add new tasks.",
5992
- inputSchema: Type12.Object(
6310
+ inputSchema: Type13.Object(
5993
6311
  {
5994
- item_id: Type12.String({
6312
+ item_id: Type13.String({
5995
6313
  minLength: 1,
5996
6314
  description: "ID of the Slack list item to update."
5997
6315
  }),
5998
- completed: Type12.Optional(
5999
- Type12.Boolean({
6316
+ completed: Type13.Optional(
6317
+ Type13.Boolean({
6000
6318
  description: "Optional completion status update."
6001
6319
  })
6002
6320
  ),
6003
- title: Type12.Optional(
6004
- Type12.String({
6321
+ title: Type13.Optional(
6322
+ Type13.String({
6005
6323
  minLength: 1,
6006
6324
  description: "Optional new item title."
6007
6325
  })
@@ -6051,11 +6369,11 @@ function createSlackListUpdateItemTool(state) {
6051
6369
  }
6052
6370
 
6053
6371
  // src/chat/tools/system-time.ts
6054
- import { Type as Type13 } from "@sinclair/typebox";
6372
+ import { Type as Type14 } from "@sinclair/typebox";
6055
6373
  function createSystemTimeTool() {
6056
6374
  return tool({
6057
6375
  description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
6058
- inputSchema: Type13.Object({}),
6376
+ inputSchema: Type14.Object({}),
6059
6377
  execute: async () => {
6060
6378
  const now = /* @__PURE__ */ new Date();
6061
6379
  return {
@@ -6070,7 +6388,7 @@ function createSystemTimeTool() {
6070
6388
  }
6071
6389
 
6072
6390
  // src/chat/tools/web/fetch-tool.ts
6073
- import { Type as Type14 } from "@sinclair/typebox";
6391
+ import { Type as Type15 } from "@sinclair/typebox";
6074
6392
 
6075
6393
  // src/chat/tools/web/constants.ts
6076
6394
  var USER_AGENT = "junior-bot/0.1";
@@ -6219,25 +6537,24 @@ async function fetchWithPinnedLookup(url, resolved, signal) {
6219
6537
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
6220
6538
  });
6221
6539
  response.on("end", () => {
6222
- signal.removeEventListener("abort", onAbort);
6223
- const headers = new Headers();
6224
- for (const [name, value] of Object.entries(response.headers)) {
6225
- if (Array.isArray(value)) {
6226
- for (const item of value) {
6227
- headers.append(name, item);
6228
- }
6229
- continue;
6230
- }
6231
- if (typeof value === "string") {
6232
- headers.set(name, value);
6540
+ try {
6541
+ signal.removeEventListener("abort", onAbort);
6542
+ const headers = new Headers();
6543
+ const rawHeaders = response.rawHeaders;
6544
+ for (let i = 0; i < rawHeaders.length; i += 2) {
6545
+ headers.append(rawHeaders[i], rawHeaders[i + 1]);
6233
6546
  }
6547
+ const rawStatus = response.statusCode ?? 500;
6548
+ const status = rawStatus >= 200 && rawStatus <= 599 ? rawStatus : 502;
6549
+ resolve(
6550
+ new Response(Buffer.concat(chunks), {
6551
+ status,
6552
+ headers
6553
+ })
6554
+ );
6555
+ } catch (error) {
6556
+ reject(error);
6234
6557
  }
6235
- resolve(
6236
- new Response(Buffer.concat(chunks), {
6237
- status: response.statusCode ?? 500,
6238
- headers
6239
- })
6240
- );
6241
6558
  });
6242
6559
  }
6243
6560
  );
@@ -6328,9 +6645,7 @@ async function readResponseBody(response, maxBytes) {
6328
6645
  }
6329
6646
  chunks.push(value);
6330
6647
  }
6331
- return new TextDecoder().decode(
6332
- Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))
6333
- );
6648
+ return new TextDecoder().decode(Buffer.concat(chunks));
6334
6649
  }
6335
6650
 
6336
6651
  // src/chat/tools/web/fetch-content.ts
@@ -6418,13 +6733,13 @@ function extractHttpStatusFromMessage(message) {
6418
6733
  function createWebFetchTool(hooks) {
6419
6734
  return tool({
6420
6735
  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.",
6421
- inputSchema: Type14.Object({
6422
- url: Type14.String({
6736
+ inputSchema: Type15.Object({
6737
+ url: Type15.String({
6423
6738
  minLength: 1,
6424
6739
  description: "HTTP(S) URL to fetch."
6425
6740
  }),
6426
- max_chars: Type14.Optional(
6427
- Type14.Integer({
6741
+ max_chars: Type15.Optional(
6742
+ Type15.Integer({
6428
6743
  minimum: 500,
6429
6744
  maximum: MAX_FETCH_CHARS,
6430
6745
  description: "Optional maximum number of extracted characters to return."
@@ -6484,9 +6799,9 @@ function createWebFetchTool(hooks) {
6484
6799
  // src/chat/tools/web/search.ts
6485
6800
  import { generateText } from "ai";
6486
6801
  import { createGatewayProvider } from "@ai-sdk/gateway";
6487
- import { Type as Type15 } from "@sinclair/typebox";
6802
+ import { Type as Type16 } from "@sinclair/typebox";
6488
6803
  var SEARCH_TIMEOUT_MS = 6e4;
6489
- var MAX_RESULTS = 5;
6804
+ var MAX_RESULTS2 = 5;
6490
6805
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
6491
6806
  var SEARCH_TOOL_NAME = "parallelSearch";
6492
6807
  function asString(value) {
@@ -6527,16 +6842,16 @@ function isAuthFailure(message) {
6527
6842
  function createWebSearchTool() {
6528
6843
  return tool({
6529
6844
  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.",
6530
- inputSchema: Type15.Object({
6531
- query: Type15.String({
6845
+ inputSchema: Type16.Object({
6846
+ query: Type16.String({
6532
6847
  minLength: 1,
6533
6848
  maxLength: 500,
6534
6849
  description: "Search query."
6535
6850
  }),
6536
- max_results: Type15.Optional(
6537
- Type15.Integer({
6851
+ max_results: Type16.Optional(
6852
+ Type16.Integer({
6538
6853
  minimum: 1,
6539
- maximum: MAX_RESULTS,
6854
+ maximum: MAX_RESULTS2,
6540
6855
  description: "Max results to return."
6541
6856
  })
6542
6857
  )
@@ -6600,17 +6915,17 @@ function createWebSearchTool() {
6600
6915
  }
6601
6916
 
6602
6917
  // src/chat/tools/sandbox/write-file.ts
6603
- import { Type as Type16 } from "@sinclair/typebox";
6918
+ import { Type as Type17 } from "@sinclair/typebox";
6604
6919
  function createWriteFileTool() {
6605
6920
  return tool({
6606
6921
  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.",
6607
- inputSchema: Type16.Object(
6922
+ inputSchema: Type17.Object(
6608
6923
  {
6609
- path: Type16.String({
6924
+ path: Type17.String({
6610
6925
  minLength: 1,
6611
6926
  description: "Path to write in the sandbox workspace."
6612
6927
  }),
6613
- content: Type16.String({
6928
+ content: Type17.String({
6614
6929
  description: "UTF-8 file content to write."
6615
6930
  })
6616
6931
  },
@@ -6685,7 +7000,11 @@ function createTools(availableSkills, hooks = {}, context) {
6685
7000
  slackListUpdateItem: createSlackListUpdateItemTool(state)
6686
7001
  };
6687
7002
  if (context.mcpToolManager && context.getActiveSkills) {
6688
- tools.searchTools = createSearchToolsTool(
7003
+ tools.searchMcpTools = createSearchMcpToolsTool(
7004
+ context.mcpToolManager,
7005
+ context.getActiveSkills
7006
+ );
7007
+ tools.callMcpTool = createCallMcpToolTool(
6689
7008
  context.mcpToolManager,
6690
7009
  context.getActiveSkills
6691
7010
  );
@@ -7264,7 +7583,17 @@ if (args.length === 0 || args[0] === "--version" || args[0] === "version") {
7264
7583
  process.exit(0);
7265
7584
  }
7266
7585
 
7267
- if (args[0] === "issues" && args[1] === "list") {
7586
+ if (args[0] === "--help" || args[0] === "help") {
7587
+ outputText("USAGE\\n sentry issue list|view|events ...\\n sentry org list|view ...\\n sentry log list|view ...\\n sentry trace list|view|logs ...\\n sentry api ...\\n");
7588
+ process.exit(0);
7589
+ }
7590
+
7591
+ if (args.includes("--help")) {
7592
+ outputText("sentry eval stub help\\n");
7593
+ process.exit(0);
7594
+ }
7595
+
7596
+ if (args[0] === "issue" && args[1] === "list") {
7268
7597
  if (hasFlag("--json")) {
7269
7598
  outputJson([]);
7270
7599
  } else {
@@ -7273,7 +7602,7 @@ if (args[0] === "issues" && args[1] === "list") {
7273
7602
  process.exit(0);
7274
7603
  }
7275
7604
 
7276
- if (args[0] === "organizations" && args[1] === "list") {
7605
+ if (args[0] === "org" && args[1] === "list") {
7277
7606
  if (hasFlag("--json")) {
7278
7607
  outputJson([{ slug: "getsentry", name: "Sentry" }]);
7279
7608
  } else {
@@ -8375,8 +8704,10 @@ function getToolErrorAttributes(error) {
8375
8704
  };
8376
8705
  }
8377
8706
  function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
8707
+ const errorType = getMcpAwareErrorType(error, "tool_execution_error");
8708
+ const errorMessage = getMcpAwareErrorMessage(error);
8378
8709
  setSpanAttributes({
8379
- "error.type": error instanceof Error ? error.name : "tool_execution_error"
8710
+ "error.type": errorType
8380
8711
  });
8381
8712
  if (shouldTrace) {
8382
8713
  logWarn(
@@ -8387,8 +8718,8 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
8387
8718
  "gen_ai.operation.name": "execute_tool",
8388
8719
  "gen_ai.tool.name": toolName,
8389
8720
  ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
8390
- "error.type": error instanceof Error ? error.name : "tool_execution_error",
8391
- "error.message": error instanceof Error ? error.message : String(error)
8721
+ "error.type": errorType,
8722
+ "error.message": errorMessage
8392
8723
  },
8393
8724
  "Agent tool call failed"
8394
8725
  );
@@ -8422,7 +8753,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8422
8753
  execute: async (toolCallId, params) => {
8423
8754
  const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
8424
8755
  const toolArgumentsAttribute = serializeGenAiAttribute(params);
8425
- onToolCall?.(toolName);
8426
8756
  const traceToolContext = {
8427
8757
  ...spanContext,
8428
8758
  conversationId: spanContext.conversationId,
@@ -8441,6 +8771,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8441
8771
  spanContext,
8442
8772
  async () => {
8443
8773
  const parsed = params;
8774
+ onToolCall?.(toolName, parsed);
8444
8775
  try {
8445
8776
  if (typeof toolDef.execute !== "function") {
8446
8777
  const resultDetails = { ok: true };
@@ -8710,24 +9041,11 @@ function upsertActiveSkill(activeSkills, next) {
8710
9041
  existing.description = next.description;
8711
9042
  existing.skillPath = next.skillPath;
8712
9043
  existing.allowedTools = next.allowedTools;
8713
- existing.usesConfig = next.usesConfig;
8714
9044
  existing.pluginProvider = next.pluginProvider;
8715
9045
  return;
8716
9046
  }
8717
9047
  activeSkills.push(next);
8718
9048
  }
8719
- function collectRelevantConfigurationKeys(activeSkills, explicitSkill) {
8720
- const keys = /* @__PURE__ */ new Set();
8721
- for (const skill of [
8722
- ...activeSkills,
8723
- ...explicitSkill ? [explicitSkill] : []
8724
- ]) {
8725
- for (const key of skill.usesConfig ?? []) {
8726
- keys.add(key);
8727
- }
8728
- }
8729
- return [...keys].sort((a, b) => a.localeCompare(b));
8730
- }
8731
9049
  function trimTrailingAssistantMessages(messages) {
8732
9050
  let end = messages.length;
8733
9051
  while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
@@ -8739,6 +9057,8 @@ function trimTrailingAssistantMessages(messages) {
8739
9057
  // src/chat/services/reply-delivery-plan.ts
8740
9058
  var REACTION_ONLY_ACK_RE = /^(?::[a-z0-9_+-]+:|[\p{Extended_Pictographic}\uFE0F\u200D]+)$/u;
8741
9059
  var REDUNDANT_REACTION_ACK_TEXT = ["done", "got it", "ok", "okay"];
9060
+ var REACTION_INTENT_RE = /\b(react|reaction|emoji|thumbs?\s*up|acknowledge)\b/i;
9061
+ var REACTION_WITH_REPLY_INTENT_RE = /\b(confirm|explain|reply|respond|say|tell|summari[sz]e|why)\b/i;
8742
9062
  function normalizeReactionAckText(text) {
8743
9063
  return text.trim().toLowerCase().replace(/[!.]+$/g, "");
8744
9064
  }
@@ -8755,6 +9075,13 @@ function isRedundantReactionAckText(text) {
8755
9075
  normalized
8756
9076
  );
8757
9077
  }
9078
+ function isReactionOnlyIntent(text) {
9079
+ const normalized = text.trim();
9080
+ if (!normalized) {
9081
+ return false;
9082
+ }
9083
+ return REACTION_INTENT_RE.test(normalized) && !REACTION_WITH_REPLY_INTENT_RE.test(normalized);
9084
+ }
8758
9085
  function buildReplyDeliveryPlan(args) {
8759
9086
  const mode = args.explicitChannelPostIntent && args.channelPostPerformed ? "channel_only" : "thread";
8760
9087
  let attachFiles = "none";
@@ -8815,6 +9142,22 @@ Note: No file was attached in this turn. I need to attach the file before claimi
8815
9142
  }
8816
9143
 
8817
9144
  // src/chat/services/turn-result.ts
9145
+ var POST_CANVAS_REPLY_MAX_CHARS = 700;
9146
+ var POST_CANVAS_REPLY_MAX_LINES = 8;
9147
+ function isVerbosePostCanvasReply(text) {
9148
+ const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
9149
+ return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
9150
+ }
9151
+ function getCreatedCanvasUrl(artifactStatePatch) {
9152
+ if (artifactStatePatch.lastCanvasUrl) {
9153
+ return artifactStatePatch.lastCanvasUrl;
9154
+ }
9155
+ return artifactStatePatch.recentCanvases?.find((canvas) => canvas.url)?.url;
9156
+ }
9157
+ function buildBriefPostCanvasReply(artifactStatePatch) {
9158
+ const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
9159
+ return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
9160
+ }
8818
9161
  function buildTurnResult(input) {
8819
9162
  const {
8820
9163
  newMessages,
@@ -8844,6 +9187,7 @@ function buildTurnResult(input) {
8844
9187
  const channelPostPerformed = successfulToolNames.has(
8845
9188
  "slackChannelPostMessage"
8846
9189
  );
9190
+ const canvasCreated = successfulToolNames.has("slackCanvasCreate");
8847
9191
  const reactionPerformed = successfulToolNames.has("slackMessageAddReaction");
8848
9192
  const baseDeliveryPlan = buildReplyDeliveryPlan({
8849
9193
  explicitChannelPostIntent,
@@ -8876,7 +9220,9 @@ function buildTurnResult(input) {
8876
9220
  const usedPrimaryText = Boolean(primaryText);
8877
9221
  const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : sideEffectOnlySuccess ? "success" : "execution_failure";
8878
9222
  const fallbackText = buildExecutionFailureMessage(toolErrorCount);
8879
- const responseText = primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
9223
+ const suppressReactionOnlyText = reactionPerformed && !channelPostPerformed && replyFiles.length === 0 && Boolean(primaryText) && isReactionOnlyIntent(userInput);
9224
+ const rawResponseText = suppressReactionOnlyText ? "" : primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
9225
+ const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
8880
9226
  const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
8881
9227
  const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
8882
9228
  const deliveryPlan = reactionPerformed && !resolvedText && replyFiles.length === 0 && !channelPostPerformed ? {
@@ -8940,7 +9286,13 @@ var turnExecutionProfileSchema = z.object({
8940
9286
  confidence: z.number().min(0).max(1),
8941
9287
  reason: z.string().min(1)
8942
9288
  });
8943
- var DEFAULT_THINKING_LEVEL = "low";
9289
+ var DEFAULT_THINKING_LEVEL = "medium";
9290
+ var THINKING_LEVEL_RANK = {
9291
+ none: 0,
9292
+ low: 1,
9293
+ medium: 2,
9294
+ high: 3
9295
+ };
8944
9296
  function trimContextForRouter(text) {
8945
9297
  const trimmed = text?.trim();
8946
9298
  if (!trimmed) {
@@ -8963,13 +9315,14 @@ function trimContextForRouter(text) {
8963
9315
  }
8964
9316
  function buildClassifierSystemPrompt() {
8965
9317
  return [
8966
- "You route assistant turns to the cheapest thinking level that is still likely to succeed.",
9318
+ "You route assistant turns to the thinking level most likely to produce a complete, source-grounded answer.",
8967
9319
  "Choose exactly one bucket: none, low, medium, or high.",
8968
9320
  "",
8969
- "Use none for greetings, acknowledgments, and trivial single-step asks.",
8970
- "Use low for straightforward explanations or simple one-step work.",
8971
- "Use medium for investigations, ambiguous asks, multi-step analysis, or likely multi-tool work.",
9321
+ "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
9322
+ "Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no thread-background interpretation, and no source verification.",
9323
+ "Use medium for normal assistant work: explanations, source-backed checks, thread follow-ups, tool choice, likely tool use, ambiguous asks, multi-step analysis, or anything where a confident but shallow answer would be risky.",
8972
9324
  "Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
9325
+ "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
8973
9326
  "",
8974
9327
  "Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and the thread-background contains a pending task, classify the pending task \u2014 not the affirmation.",
8975
9328
  "",
@@ -9042,15 +9395,31 @@ async function selectTurnThinkingLevel(args) {
9042
9395
  },
9043
9396
  prompt
9044
9397
  });
9398
+ const normalizedSelection = applyThinkingFloor(selection, {
9399
+ minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
9400
+ });
9045
9401
  setSpanAttributes({
9046
- "app.ai.thinking_level": selection.thinkingLevel,
9047
- "app.ai.thinking_level_reason": selection.reason,
9048
- ...selection.confidence !== void 0 ? { "app.ai.thinking_level_confidence": selection.confidence } : {}
9402
+ "app.ai.thinking_level": normalizedSelection.thinkingLevel,
9403
+ "app.ai.thinking_level_reason": normalizedSelection.reason,
9404
+ ...normalizedSelection.confidence !== void 0 ? {
9405
+ "app.ai.thinking_level_confidence": normalizedSelection.confidence
9406
+ } : {}
9049
9407
  });
9050
- return selection;
9408
+ return normalizedSelection;
9051
9409
  }
9052
9410
  );
9053
9411
  }
9412
+ function applyThinkingFloor(selection, args) {
9413
+ const minimum = args.minimum;
9414
+ if (!minimum || selection.thinkingLevel === "none" || THINKING_LEVEL_RANK[selection.thinkingLevel] >= THINKING_LEVEL_RANK[minimum]) {
9415
+ return selection;
9416
+ }
9417
+ return {
9418
+ ...selection,
9419
+ thinkingLevel: minimum,
9420
+ reason: `thinking_floor:${minimum}:${selection.reason}`
9421
+ };
9422
+ }
9054
9423
  async function classifyTurn(args) {
9055
9424
  try {
9056
9425
  const result = await args.completeObject({
@@ -9069,7 +9438,7 @@ async function classifyTurn(args) {
9069
9438
  return {
9070
9439
  confidence: parsed.confidence,
9071
9440
  thinkingLevel: DEFAULT_THINKING_LEVEL,
9072
- reason: `low_confidence_default:${reason}`
9441
+ reason: `low_confidence_medium_default:${reason}`
9073
9442
  };
9074
9443
  }
9075
9444
  return {
@@ -9710,20 +10079,6 @@ function buildUserTurnInput(args) {
9710
10079
  }
9711
10080
  return { routerBlocks, userContentParts };
9712
10081
  }
9713
- function mcpToolsToDefinitions(mcpTools) {
9714
- const defs = {};
9715
- for (const tool2 of mcpTools) {
9716
- defs[tool2.name] = {
9717
- description: tool2.description,
9718
- // Raw JSON Schema from MCP servers — not a TypeBox TSchema, but
9719
- // pi-agent-core validates with AJV and the Anthropic provider reads
9720
- // .properties/.required, so raw JSON Schema works at runtime.
9721
- inputSchema: tool2.parameters,
9722
- execute: async (args) => tool2.execute(args)
9723
- };
9724
- }
9725
- return defs;
9726
- }
9727
10082
  async function generateAssistantReply(messageText, context = {}) {
9728
10083
  const replyStartedAtMs = Date.now();
9729
10084
  let timeoutResumeConversationId;
@@ -9818,6 +10173,7 @@ async function generateAssistantReply(messageText, context = {}) {
9818
10173
  timeoutResumeSliceId = currentSliceId;
9819
10174
  const persistedConfigurationValues = context.channelConfiguration ? await context.channelConfiguration.resolveValues() : {};
9820
10175
  configurationValues = {
10176
+ ...getConfigDefaults(),
9821
10177
  ...context.configuration ?? {},
9822
10178
  ...persistedConfigurationValues
9823
10179
  };
@@ -10020,6 +10376,8 @@ async function generateAssistantReply(messageText, context = {}) {
10020
10376
  assistantUserName: context.assistant?.userName,
10021
10377
  modelId: botConfig.modelId
10022
10378
  });
10379
+ const toolChannelId = context.toolChannelId ?? context.correlation?.channelId;
10380
+ const channelCapabilities = resolveChannelCapabilities(toolChannelId);
10023
10381
  const tools = createTools(
10024
10382
  availableSkills,
10025
10383
  {
@@ -10057,19 +10415,24 @@ async function generateAssistantReply(messageText, context = {}) {
10057
10415
  if (!effective.pluginProvider) {
10058
10416
  return void 0;
10059
10417
  }
10060
- syncMcpAgentTools();
10061
- return {
10062
- available_tools: turnMcpToolManager.getActiveToolCatalog(activeSkills, {
10418
+ if (!turnMcpToolManager.getActiveProviders().includes(effective.pluginProvider)) {
10419
+ return void 0;
10420
+ }
10421
+ const availableToolCount = turnMcpToolManager.getActiveToolCatalog(
10422
+ activeSkills,
10423
+ {
10063
10424
  provider: effective.pluginProvider
10064
- }).map(toExposedToolSummary)
10425
+ }
10426
+ ).length;
10427
+ return {
10428
+ mcp_provider: effective.pluginProvider,
10429
+ available_tool_count: availableToolCount
10065
10430
  };
10066
10431
  }
10067
10432
  },
10068
10433
  {
10069
- channelId: context.toolChannelId ?? context.correlation?.channelId,
10070
- channelCapabilities: resolveChannelCapabilities(
10071
- context.toolChannelId ?? context.correlation?.channelId
10072
- ),
10434
+ channelId: toolChannelId,
10435
+ channelCapabilities,
10073
10436
  messageTs: context.correlation?.messageTs,
10074
10437
  threadTs: context.correlation?.threadTs,
10075
10438
  userText: userInput,
@@ -10091,22 +10454,27 @@ async function generateAssistantReply(messageText, context = {}) {
10091
10454
  await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
10092
10455
  }
10093
10456
  syncResumeState();
10094
- const activeToolSummaries = turnMcpToolManager.getActiveToolCatalog(activeSkills).map(toExposedToolSummary);
10457
+ const activeMcpCatalogs = toActiveMcpCatalogSummaries(
10458
+ turnMcpToolManager.getActiveToolCatalog(activeSkills)
10459
+ );
10095
10460
  baseInstructions = buildSystemPrompt({
10096
10461
  availableSkills,
10097
10462
  activeSkills,
10098
- activeTools: activeToolSummaries,
10463
+ activeMcpCatalogs,
10464
+ runtime: {
10465
+ channelId: toolChannelId,
10466
+ fastModelId: botConfig.fastModelId,
10467
+ modelId: botConfig.modelId,
10468
+ slackCapabilities: channelCapabilities,
10469
+ thinkingLevel: thinkingSelection.thinkingLevel
10470
+ },
10099
10471
  invocation: skillInvocation,
10100
10472
  assistant: context.assistant,
10101
10473
  requester: context.requester,
10102
10474
  artifactState: context.artifactState,
10103
10475
  configuration: configurationValues,
10104
- relevantConfigurationKeys: collectRelevantConfigurationKeys(
10105
- activeSkills,
10106
- invokedSkill
10107
- ),
10108
- runtimeMetadata: getRuntimeMetadata(),
10109
- threadParticipants: context.threadParticipants
10476
+ threadParticipants: context.threadParticipants,
10477
+ turnState: resumedFromCheckpoint ? "resumed" : "fresh"
10110
10478
  });
10111
10479
  const inputMessagesAttribute = serializeGenAiAttribute([
10112
10480
  {
@@ -10118,10 +10486,23 @@ async function generateAssistantReply(messageText, context = {}) {
10118
10486
  content: userContentParts.map((part) => toObservablePromptPart(part))
10119
10487
  }
10120
10488
  ]);
10121
- const onToolCall = (toolName) => {
10489
+ const onToolCall = (toolName, params) => {
10122
10490
  toolCalls.push(toolName);
10491
+ try {
10492
+ context.onToolInvocation?.({ toolName, params });
10493
+ } catch (error) {
10494
+ logWarn(
10495
+ "tool_invocation_observer_failed",
10496
+ spanContext,
10497
+ {
10498
+ "gen_ai.tool.name": toolName,
10499
+ "error.message": error instanceof Error ? error.message : String(error)
10500
+ },
10501
+ "Tool invocation observer failed"
10502
+ );
10503
+ }
10123
10504
  };
10124
- const baseAgentTools = createAgentTools(
10505
+ const agentTools = createAgentTools(
10125
10506
  tools,
10126
10507
  skillSandbox,
10127
10508
  spanContext,
@@ -10131,24 +10512,6 @@ async function generateAssistantReply(messageText, context = {}) {
10131
10512
  pluginAuth,
10132
10513
  onToolCall
10133
10514
  );
10134
- const agentTools = [...baseAgentTools];
10135
- const syncMcpAgentTools = () => {
10136
- const mcpTools = turnMcpToolManager.getResolvedActiveTools(activeSkills);
10137
- const mcpDefs = mcpToolsToDefinitions(mcpTools);
10138
- const mcpAgentTools = createAgentTools(
10139
- mcpDefs,
10140
- skillSandbox,
10141
- spanContext,
10142
- context.onStatus,
10143
- sandboxExecutor,
10144
- capabilityRuntime,
10145
- pluginAuth,
10146
- onToolCall
10147
- );
10148
- agentTools.length = 0;
10149
- agentTools.push(...baseAgentTools, ...mcpAgentTools);
10150
- };
10151
- syncMcpAgentTools();
10152
10515
  agent = new Agent({
10153
10516
  getApiKey: () => getPiGatewayApiKeyOverride(),
10154
10517
  initialState: {
@@ -15686,6 +16049,7 @@ async function createApp(options) {
15686
16049
  setPluginPackages(
15687
16050
  options?.pluginPackages ?? await resolveBuildPluginPackages()
15688
16051
  );
16052
+ setConfigDefaults(options?.configDefaults);
15689
16053
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
15690
16054
  const app = new Hono();
15691
16055
  app.onError((err, c) => {