@sentry/junior 0.30.0 → 0.31.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 +738 -543
- package/dist/{chunk-ICIRAL6Y.js → chunk-3SH2A7VQ.js} +52 -106
- package/dist/cli/check.js +8 -1
- package/package.json +1 -1
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-
|
|
6
|
+
} from "./chunk-3SH2A7VQ.js";
|
|
8
7
|
import {
|
|
9
8
|
GEN_AI_PROVIDER_NAME,
|
|
10
9
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
@@ -40,6 +39,8 @@ import {
|
|
|
40
39
|
createRequestContext,
|
|
41
40
|
extractGenAiUsageSummary,
|
|
42
41
|
getActiveTraceId,
|
|
42
|
+
getPluginCapabilityProviders,
|
|
43
|
+
getPluginCatalogSignature,
|
|
43
44
|
getPluginDefinition,
|
|
44
45
|
getPluginMcpProviders,
|
|
45
46
|
getPluginOAuthConfig,
|
|
@@ -2883,18 +2884,21 @@ function formatConfigurationValue(value) {
|
|
|
2883
2884
|
function renderIdentityBlock(tag, fields) {
|
|
2884
2885
|
const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
|
|
2885
2886
|
if (lines.length === 0) {
|
|
2886
|
-
return [`<${tag}>`, "none", `</${tag}>`]
|
|
2887
|
+
return [`<${tag}>`, "none", `</${tag}>`];
|
|
2887
2888
|
}
|
|
2888
|
-
return [`<${tag}>`, ...lines, `</${tag}>`]
|
|
2889
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
2889
2890
|
}
|
|
2890
|
-
function renderTag(tag,
|
|
2891
|
+
function renderTag(tag, lines) {
|
|
2892
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
2893
|
+
}
|
|
2894
|
+
function renderTagBlock(tag, content) {
|
|
2891
2895
|
return [`<${tag}>`, content, `</${tag}>`].join("\n");
|
|
2892
2896
|
}
|
|
2893
2897
|
function formatAvailableSkillsForPrompt(skills) {
|
|
2894
2898
|
if (skills.length === 0) {
|
|
2895
|
-
return "<
|
|
2899
|
+
return "<available-skills>\n</available-skills>";
|
|
2896
2900
|
}
|
|
2897
|
-
const lines = ["<
|
|
2901
|
+
const lines = ["<available-skills>"];
|
|
2898
2902
|
for (const skill of skills) {
|
|
2899
2903
|
const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
|
|
2900
2904
|
lines.push(" <skill>");
|
|
@@ -2906,45 +2910,37 @@ function formatAvailableSkillsForPrompt(skills) {
|
|
|
2906
2910
|
if (skill.pluginProvider) {
|
|
2907
2911
|
lines.push(` <provider>${escapeXml(skill.pluginProvider)}</provider>`);
|
|
2908
2912
|
}
|
|
2909
|
-
if (skill.usesConfig && skill.usesConfig.length > 0) {
|
|
2910
|
-
lines.push(
|
|
2911
|
-
` <uses_config>${escapeXml(skill.usesConfig.join(" "))}</uses_config>`
|
|
2912
|
-
);
|
|
2913
|
-
}
|
|
2914
2913
|
lines.push(" </skill>");
|
|
2915
2914
|
}
|
|
2916
|
-
lines.push("</
|
|
2915
|
+
lines.push("</available-skills>");
|
|
2917
2916
|
return lines.join("\n");
|
|
2918
2917
|
}
|
|
2919
2918
|
function formatLoadedSkillsForPrompt(skills) {
|
|
2920
2919
|
if (skills.length === 0) {
|
|
2921
|
-
return "<
|
|
2920
|
+
return "<loaded-skills>\n</loaded-skills>";
|
|
2922
2921
|
}
|
|
2923
|
-
const lines = ["<
|
|
2922
|
+
const lines = ["<loaded-skills>"];
|
|
2924
2923
|
for (const skill of skills) {
|
|
2925
2924
|
const skillDir = workspaceSkillDir(skill.name);
|
|
2926
2925
|
lines.push(
|
|
2927
2926
|
` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
|
|
2928
2927
|
);
|
|
2929
2928
|
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
|
-
}
|
|
2935
2929
|
lines.push("");
|
|
2936
2930
|
lines.push(skill.body);
|
|
2937
2931
|
lines.push(" </skill>");
|
|
2938
2932
|
}
|
|
2939
|
-
lines.push("</
|
|
2933
|
+
lines.push("</loaded-skills>");
|
|
2940
2934
|
return lines.join("\n");
|
|
2941
2935
|
}
|
|
2942
2936
|
function formatProviderCatalogForPrompt() {
|
|
2943
2937
|
const providers = getPluginProviders().map((plugin) => plugin.manifest);
|
|
2944
2938
|
if (providers.length === 0) {
|
|
2945
|
-
return
|
|
2939
|
+
return null;
|
|
2946
2940
|
}
|
|
2947
|
-
const lines = [
|
|
2941
|
+
const lines = [
|
|
2942
|
+
"Config keys and default targets per provider; use after a skill is loaded."
|
|
2943
|
+
];
|
|
2948
2944
|
for (const provider of providers) {
|
|
2949
2945
|
lines.push(`- provider: ${escapeXml(provider.name)}`);
|
|
2950
2946
|
lines.push(
|
|
@@ -2958,288 +2954,260 @@ function formatProviderCatalogForPrompt() {
|
|
|
2958
2954
|
}
|
|
2959
2955
|
return lines.join("\n");
|
|
2960
2956
|
}
|
|
2961
|
-
function
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
"
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
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");
|
|
2957
|
+
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
2958
|
+
if (catalogs.length === 0) {
|
|
2959
|
+
return null;
|
|
2960
|
+
}
|
|
2961
|
+
const lines = [
|
|
2962
|
+
"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`."
|
|
2963
|
+
];
|
|
2964
|
+
for (const catalog of catalogs) {
|
|
2965
|
+
lines.push(" <catalog>");
|
|
2966
|
+
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
2967
|
+
lines.push(
|
|
2968
|
+
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
2969
|
+
);
|
|
2970
|
+
lines.push(" </catalog>");
|
|
2971
|
+
}
|
|
2972
|
+
return lines.join("\n");
|
|
2984
2973
|
}
|
|
2985
|
-
function
|
|
2974
|
+
function formatReferenceFilesLines() {
|
|
2986
2975
|
const files = listReferenceFiles();
|
|
2987
2976
|
if (files.length === 0) {
|
|
2988
|
-
return
|
|
2977
|
+
return null;
|
|
2989
2978
|
}
|
|
2990
|
-
|
|
2979
|
+
return files.map((filePath) => {
|
|
2991
2980
|
const name = path2.basename(filePath);
|
|
2992
2981
|
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
2993
2982
|
});
|
|
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
2983
|
}
|
|
3004
|
-
function
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
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(
|
|
2984
|
+
function formatArtifactsLines(artifactState) {
|
|
2985
|
+
if (!artifactState) return null;
|
|
2986
|
+
const lines = [];
|
|
2987
|
+
if (artifactState.lastCanvasId) {
|
|
2988
|
+
lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
|
|
2989
|
+
}
|
|
2990
|
+
if (artifactState.lastCanvasUrl) {
|
|
2991
|
+
lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
|
|
2992
|
+
}
|
|
2993
|
+
if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
|
|
2994
|
+
lines.push("- recent_canvases:");
|
|
2995
|
+
for (const canvas of artifactState.recentCanvases) {
|
|
2996
|
+
lines.push(` - id: ${escapeXml(canvas.id)}`);
|
|
2997
|
+
if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
|
|
2998
|
+
if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
|
|
2999
|
+
if (canvas.createdAt) {
|
|
3000
|
+
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
if (artifactState.lastListId) {
|
|
3005
|
+
lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
|
|
3006
|
+
}
|
|
3007
|
+
if (artifactState.lastListUrl) {
|
|
3008
|
+
lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
|
|
3009
|
+
}
|
|
3010
|
+
return lines.length > 0 ? lines : null;
|
|
3011
|
+
}
|
|
3012
|
+
function formatConfigurationLines(configuration) {
|
|
3013
|
+
const keys = Object.keys(configuration ?? {}).sort(
|
|
3041
3014
|
(a, b) => a.localeCompare(b)
|
|
3042
3015
|
);
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3016
|
+
if (keys.length === 0) return null;
|
|
3017
|
+
return keys.map(
|
|
3018
|
+
(key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
function formatThreadParticipantsLines(participants) {
|
|
3022
|
+
if (!participants || participants.length === 0) return null;
|
|
3023
|
+
return participants.map((p) => {
|
|
3024
|
+
const parts = [];
|
|
3025
|
+
if (p.userId) {
|
|
3026
|
+
parts.push(`user_id: ${escapeXml(p.userId)}`);
|
|
3027
|
+
parts.push(`slack_mention: <@${p.userId}>`);
|
|
3028
|
+
}
|
|
3029
|
+
if (p.userName) parts.push(`user_name: ${escapeXml(p.userName)}`);
|
|
3030
|
+
if (p.fullName) parts.push(`full_name: ${escapeXml(p.fullName)}`);
|
|
3031
|
+
return `- ${parts.join(", ")}`;
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
var HEADER = "You are a Slack-based helper assistant. The behavior and output blocks below are authoritative; the personality block sets voice only.";
|
|
3035
|
+
var BEHAVIOR_RULES = [
|
|
3036
|
+
"- Load the best-matching skill/tool when relevant, then use it before answering; do not preload multiple skills or claim tool use that did not happen.",
|
|
3037
|
+
"- After `loadSkill`, resolve references under `skill_dir`; for active MCP catalogs, use `searchMcpTools` then `callMcpTool` with exact returned tool names.",
|
|
3038
|
+
"- Default to acting in-turn: use relevant available skills/tools to satisfy the request, continue until done or blocked, and only ask the user when access or required input is missing. If a fact cannot be verified, say so.",
|
|
3039
|
+
"- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
|
|
3040
|
+
"- Keep work silent and post one result-focused reply unless blocked or waiting on user input; do not use reactions as progress.",
|
|
3041
|
+
"- Do not claim an attachment, canvas, or channel post succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
|
|
3042
|
+
"- Run authenticated provider commands directly; resolve target defaults first and let the runtime handle auth pauses/resumes.",
|
|
3043
|
+
"- On resumed turns, post a brief continuation notice, then the resumed answer as a separate message.",
|
|
3044
|
+
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
3045
|
+
"- Run `jr-rpc config get|set|unset|list` as standalone bash commands for conversation-scoped provider defaults.",
|
|
3046
|
+
"- For explicit channel-post or emoji-reaction requests, skip the text reply."
|
|
3047
|
+
];
|
|
3048
|
+
function buildOutputSection() {
|
|
3049
|
+
const openTag = `<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
|
|
3050
|
+
return [
|
|
3051
|
+
openTag,
|
|
3052
|
+
"- Use Slack-friendly mrkdwn: bolded section labels instead of headings, no markdown tables or markdown links, and plain URLs.",
|
|
3053
|
+
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
3054
|
+
"- 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 a short summary plus the canvas link.",
|
|
3055
|
+
"- End every turn with a final user-facing markdown response.",
|
|
3056
|
+
"</output>"
|
|
3057
|
+
].join("\n");
|
|
3058
|
+
}
|
|
3059
|
+
function buildContextSection(params) {
|
|
3060
|
+
const blocks = [];
|
|
3061
|
+
if (JUNIOR_WORLD) {
|
|
3062
|
+
blocks.push(renderTag("world", [JUNIOR_WORLD.trim()]));
|
|
3063
|
+
}
|
|
3064
|
+
const referenceLines = formatReferenceFilesLines();
|
|
3065
|
+
if (referenceLines) {
|
|
3066
|
+
blocks.push(
|
|
3067
|
+
renderTag("reference-files", [
|
|
3068
|
+
"Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
|
|
3069
|
+
...referenceLines
|
|
3070
|
+
])
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
const runtimeVersion = getRuntimeMetadata().version;
|
|
3074
|
+
if (runtimeVersion) {
|
|
3075
|
+
blocks.push([`<runtime version="${escapeXml(runtimeVersion)}" />`]);
|
|
3076
|
+
}
|
|
3077
|
+
blocks.push(
|
|
3078
|
+
renderIdentityBlock("assistant", {
|
|
3079
|
+
user_name: params.assistant?.userName ?? botConfig.userName,
|
|
3080
|
+
user_id: params.assistant?.userId
|
|
3081
|
+
})
|
|
3047
3082
|
);
|
|
3048
|
-
|
|
3049
|
-
(
|
|
3083
|
+
blocks.push(
|
|
3084
|
+
renderIdentityBlock("requester", {
|
|
3085
|
+
full_name: params.requester?.fullName,
|
|
3086
|
+
user_name: params.requester?.userName,
|
|
3087
|
+
user_id: params.requester?.userId
|
|
3088
|
+
})
|
|
3050
3089
|
);
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3090
|
+
const participantLines = formatThreadParticipantsLines(
|
|
3091
|
+
params.threadParticipants
|
|
3053
3092
|
);
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3093
|
+
if (participantLines) {
|
|
3094
|
+
blocks.push(
|
|
3095
|
+
renderTag("thread-participants", [
|
|
3096
|
+
"Known participants. When you mention one of these people, use the provided `<@USERID>` token exactly; do not write a bare `@name`.",
|
|
3097
|
+
...participantLines
|
|
3098
|
+
])
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
3102
|
+
if (artifactLines) {
|
|
3103
|
+
blocks.push(renderTag("artifacts", artifactLines));
|
|
3104
|
+
}
|
|
3105
|
+
const configLines = formatConfigurationLines(params.configuration);
|
|
3106
|
+
if (configLines) {
|
|
3107
|
+
blocks.push(
|
|
3108
|
+
renderTag("configuration", [
|
|
3109
|
+
"Conversation-scoped defaults. Follow explicit user input when it conflicts.",
|
|
3110
|
+
...configLines
|
|
3111
|
+
])
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
if (params.turnState === "resumed") {
|
|
3115
|
+
blocks.push([
|
|
3116
|
+
"<turn-state>resumed</turn-state>",
|
|
3117
|
+
"This turn continues from a prior checkpoint. Prior tool results and assistant messages are already in the conversation history."
|
|
3118
|
+
]);
|
|
3119
|
+
}
|
|
3120
|
+
if (params.invocation) {
|
|
3121
|
+
blocks.push([
|
|
3122
|
+
`<explicit-skill-trigger>/${escapeXml(params.invocation.skillName)}</explicit-skill-trigger>`
|
|
3123
|
+
]);
|
|
3124
|
+
}
|
|
3125
|
+
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
3126
|
+
return renderTagBlock("context", body);
|
|
3127
|
+
}
|
|
3128
|
+
function buildCapabilitiesSection(params) {
|
|
3129
|
+
const blocks = [];
|
|
3130
|
+
blocks.push(formatAvailableSkillsForPrompt(params.availableSkills));
|
|
3131
|
+
blocks.push(formatLoadedSkillsForPrompt(params.activeSkills));
|
|
3132
|
+
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
3133
|
+
params.activeMcpCatalogs
|
|
3134
|
+
);
|
|
3135
|
+
if (activeCatalogs) {
|
|
3136
|
+
blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
|
|
3137
|
+
}
|
|
3138
|
+
const providerCatalog = formatProviderCatalogForPrompt();
|
|
3139
|
+
if (providerCatalog) {
|
|
3140
|
+
blocks.push(renderTagBlock("providers", providerCatalog));
|
|
3141
|
+
}
|
|
3142
|
+
return renderTagBlock("capabilities", blocks.join("\n\n"));
|
|
3143
|
+
}
|
|
3144
|
+
function buildSystemPrompt(params) {
|
|
3062
3145
|
const sections = [
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
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
|
-
)
|
|
3146
|
+
HEADER,
|
|
3147
|
+
renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
|
|
3148
|
+
buildContextSection({
|
|
3149
|
+
assistant: params.assistant,
|
|
3150
|
+
requester: params.requester,
|
|
3151
|
+
artifactState: params.artifactState,
|
|
3152
|
+
configuration: params.configuration,
|
|
3153
|
+
threadParticipants: params.threadParticipants,
|
|
3154
|
+
invocation: params.invocation,
|
|
3155
|
+
turnState: params.turnState
|
|
3156
|
+
}),
|
|
3157
|
+
buildCapabilitiesSection({
|
|
3158
|
+
availableSkills: params.availableSkills,
|
|
3159
|
+
activeSkills: params.activeSkills,
|
|
3160
|
+
activeMcpCatalogs: params.activeMcpCatalogs ?? []
|
|
3161
|
+
}),
|
|
3162
|
+
renderTagBlock("behavior", BEHAVIOR_RULES.join("\n")),
|
|
3163
|
+
buildOutputSection()
|
|
3239
3164
|
];
|
|
3240
3165
|
return sections.join("\n\n");
|
|
3241
3166
|
}
|
|
3242
3167
|
|
|
3168
|
+
// src/chat/capabilities/catalog.ts
|
|
3169
|
+
var cachedCatalog;
|
|
3170
|
+
function getCapabilityCatalog() {
|
|
3171
|
+
const signature = getPluginCatalogSignature();
|
|
3172
|
+
if (cachedCatalog?.signature === signature) return cachedCatalog;
|
|
3173
|
+
const providers = getPluginCapabilityProviders();
|
|
3174
|
+
const capabilityToProvider = /* @__PURE__ */ new Map();
|
|
3175
|
+
for (const provider of providers) {
|
|
3176
|
+
for (const capability of provider.capabilities) {
|
|
3177
|
+
if (capabilityToProvider.has(capability)) {
|
|
3178
|
+
throw new Error(
|
|
3179
|
+
`Duplicate capability registration for "${capability}"`
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
capabilityToProvider.set(capability, provider);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
cachedCatalog = { signature, providers, capabilityToProvider };
|
|
3186
|
+
return cachedCatalog;
|
|
3187
|
+
}
|
|
3188
|
+
var catalogLogged = false;
|
|
3189
|
+
function logCapabilityCatalogLoadedOnce() {
|
|
3190
|
+
if (catalogLogged) return;
|
|
3191
|
+
catalogLogged = true;
|
|
3192
|
+
const { providers } = getCapabilityCatalog();
|
|
3193
|
+
const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
|
|
3194
|
+
const configKeys = [
|
|
3195
|
+
...new Set(providers.flatMap((p) => p.configKeys))
|
|
3196
|
+
].sort();
|
|
3197
|
+
logInfo(
|
|
3198
|
+
"capability_catalog_loaded",
|
|
3199
|
+
{},
|
|
3200
|
+
{
|
|
3201
|
+
"app.capability.providers": providers.map((p) => p.provider),
|
|
3202
|
+
"app.capability.count": capabilityNames.length,
|
|
3203
|
+
"app.capability.names": capabilityNames,
|
|
3204
|
+
"app.config.key_count": configKeys.length,
|
|
3205
|
+
"app.config.keys": configKeys
|
|
3206
|
+
},
|
|
3207
|
+
"Loaded capability provider catalog"
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3243
3211
|
// src/chat/capabilities/router.ts
|
|
3244
3212
|
var ProviderCredentialRouter = class {
|
|
3245
3213
|
brokersByProvider;
|
|
@@ -3965,6 +3933,27 @@ var MCP_CLIENT_INFO = {
|
|
|
3965
3933
|
name: "junior-mcp-client",
|
|
3966
3934
|
version: "1.0.0"
|
|
3967
3935
|
};
|
|
3936
|
+
var MCP_TOOLS_CALL_METHOD = "tools/call";
|
|
3937
|
+
function getDefaultPort(url) {
|
|
3938
|
+
if (url.protocol === "https:") {
|
|
3939
|
+
return 443;
|
|
3940
|
+
}
|
|
3941
|
+
if (url.protocol === "http:") {
|
|
3942
|
+
return 80;
|
|
3943
|
+
}
|
|
3944
|
+
return void 0;
|
|
3945
|
+
}
|
|
3946
|
+
function getMcpNetworkAttributes(url) {
|
|
3947
|
+
const port = url.port ? Number(url.port) : getDefaultPort(url);
|
|
3948
|
+
return {
|
|
3949
|
+
"server.address": url.hostname,
|
|
3950
|
+
...port !== void 0 ? { "server.port": port } : {},
|
|
3951
|
+
...url.protocol === "http:" || url.protocol === "https:" ? {
|
|
3952
|
+
"network.protocol.name": "http",
|
|
3953
|
+
"network.transport": "tcp"
|
|
3954
|
+
} : {}
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3968
3957
|
var McpAuthorizationRequiredError = class extends Error {
|
|
3969
3958
|
provider;
|
|
3970
3959
|
constructor(provider, message) {
|
|
@@ -4014,10 +4003,22 @@ var PluginMcpClient = class {
|
|
|
4014
4003
|
async callTool(name, args) {
|
|
4015
4004
|
return await this.withSessionRecovery(async () => {
|
|
4016
4005
|
const client2 = await this.getClient();
|
|
4006
|
+
const mcp = this.plugin.manifest.mcp;
|
|
4007
|
+
if (mcp) {
|
|
4008
|
+
const url = new URL(mcp.url);
|
|
4009
|
+
setSpanAttributes({
|
|
4010
|
+
"mcp.method.name": MCP_TOOLS_CALL_METHOD,
|
|
4011
|
+
"gen_ai.operation.name": "execute_tool",
|
|
4012
|
+
"gen_ai.tool.name": name,
|
|
4013
|
+
...this.transport?.sessionId ? { "mcp.session.id": this.transport.sessionId } : {},
|
|
4014
|
+
...this.transport?.protocolVersion ? { "mcp.protocol.version": this.transport.protocolVersion } : {},
|
|
4015
|
+
...getMcpNetworkAttributes(url)
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4017
4018
|
const result = await this.wrapAuth(
|
|
4018
4019
|
client2.callTool({
|
|
4019
4020
|
name,
|
|
4020
|
-
|
|
4021
|
+
arguments: args ?? {}
|
|
4021
4022
|
})
|
|
4022
4023
|
);
|
|
4023
4024
|
await this.syncTransportSessionId();
|
|
@@ -4130,13 +4131,24 @@ var PluginMcpClient = class {
|
|
|
4130
4131
|
}
|
|
4131
4132
|
};
|
|
4132
4133
|
|
|
4133
|
-
// src/chat/mcp/
|
|
4134
|
+
// src/chat/mcp/errors.ts
|
|
4134
4135
|
var McpToolError = class extends Error {
|
|
4135
4136
|
constructor(message) {
|
|
4136
4137
|
super(message);
|
|
4137
4138
|
this.name = "McpToolError";
|
|
4138
4139
|
}
|
|
4139
4140
|
};
|
|
4141
|
+
function getMcpAwareErrorType(error, fallback) {
|
|
4142
|
+
if (error instanceof McpToolError) {
|
|
4143
|
+
return "tool_error";
|
|
4144
|
+
}
|
|
4145
|
+
return error instanceof Error ? error.name : fallback;
|
|
4146
|
+
}
|
|
4147
|
+
function getMcpAwareErrorMessage(error) {
|
|
4148
|
+
return error instanceof Error ? error.message : String(error);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
// src/chat/mcp/tool-manager.ts
|
|
4140
4152
|
function normalizeMcpToolName(provider, toolName) {
|
|
4141
4153
|
return `mcp__${provider}__${toolName}`;
|
|
4142
4154
|
}
|
|
@@ -4302,24 +4314,6 @@ var McpToolManager = class {
|
|
|
4302
4314
|
(tool2) => this.toToolDescriptor(tool2)
|
|
4303
4315
|
);
|
|
4304
4316
|
}
|
|
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
4317
|
filterListedTools(plugin, tools) {
|
|
4324
4318
|
const allowedTools = plugin.manifest.mcp?.allowedTools;
|
|
4325
4319
|
if (!allowedTools || allowedTools.length === 0) {
|
|
@@ -4351,6 +4345,8 @@ var McpToolManager = class {
|
|
|
4351
4345
|
return client2;
|
|
4352
4346
|
}
|
|
4353
4347
|
toManagedTool(plugin, client2, tool2) {
|
|
4348
|
+
const outputSchema = toOptionalRecord(tool2.outputSchema);
|
|
4349
|
+
const annotations = toOptionalRecord(tool2.annotations);
|
|
4354
4350
|
return {
|
|
4355
4351
|
name: normalizeMcpToolName(plugin.manifest.name, tool2.name),
|
|
4356
4352
|
description: describeMcpTool(plugin.manifest.name, tool2),
|
|
@@ -4358,8 +4354,15 @@ var McpToolManager = class {
|
|
|
4358
4354
|
provider: plugin.manifest.name,
|
|
4359
4355
|
rawName: tool2.name,
|
|
4360
4356
|
...tool2.title?.trim() ? { title: tool2.title.trim() } : {},
|
|
4357
|
+
...outputSchema ? { outputSchema } : {},
|
|
4358
|
+
...annotations ? { annotations } : {},
|
|
4361
4359
|
execute: async (args) => {
|
|
4362
4360
|
const resolvedArgs = typeof args === "object" && args !== null ? args : {};
|
|
4361
|
+
const baseAttributes = {
|
|
4362
|
+
"gen_ai.tool.name": tool2.name,
|
|
4363
|
+
"mcp.method.name": "tools/call"
|
|
4364
|
+
};
|
|
4365
|
+
setSpanAttributes(baseAttributes);
|
|
4363
4366
|
try {
|
|
4364
4367
|
const result = await client2.callTool(tool2.name, resolvedArgs);
|
|
4365
4368
|
if ("isError" in result && result.isError) {
|
|
@@ -4395,6 +4398,20 @@ var McpToolManager = class {
|
|
|
4395
4398
|
}
|
|
4396
4399
|
};
|
|
4397
4400
|
}
|
|
4401
|
+
const errorAttributes = {
|
|
4402
|
+
...baseAttributes,
|
|
4403
|
+
"error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
|
|
4404
|
+
"error.message": getMcpAwareErrorMessage(error)
|
|
4405
|
+
};
|
|
4406
|
+
setSpanAttributes(errorAttributes);
|
|
4407
|
+
if (error instanceof McpToolError) {
|
|
4408
|
+
logWarn(
|
|
4409
|
+
"mcp_tool_call_failed",
|
|
4410
|
+
{},
|
|
4411
|
+
errorAttributes,
|
|
4412
|
+
"MCP tool call failed"
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4398
4415
|
throw error;
|
|
4399
4416
|
}
|
|
4400
4417
|
}
|
|
@@ -4441,37 +4458,19 @@ var McpToolManager = class {
|
|
|
4441
4458
|
toToolDescriptor(tool2) {
|
|
4442
4459
|
return {
|
|
4443
4460
|
name: tool2.name,
|
|
4461
|
+
rawName: tool2.rawName,
|
|
4462
|
+
...tool2.title ? { title: tool2.title } : {},
|
|
4444
4463
|
description: tool2.description,
|
|
4445
4464
|
parameters: tool2.parameters,
|
|
4465
|
+
...tool2.outputSchema ? { outputSchema: tool2.outputSchema } : {},
|
|
4466
|
+
...tool2.annotations ? { annotations: tool2.annotations } : {},
|
|
4446
4467
|
provider: tool2.provider
|
|
4447
4468
|
};
|
|
4448
4469
|
}
|
|
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
4470
|
};
|
|
4471
|
+
function toOptionalRecord(value) {
|
|
4472
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4473
|
+
}
|
|
4475
4474
|
|
|
4476
4475
|
// src/chat/tools/definition.ts
|
|
4477
4476
|
function tool(definition) {
|
|
@@ -4784,8 +4783,61 @@ function createImageGenerateTool(hooks, deps = {}) {
|
|
|
4784
4783
|
});
|
|
4785
4784
|
}
|
|
4786
4785
|
|
|
4787
|
-
// src/chat/tools/skill/
|
|
4786
|
+
// src/chat/tools/skill/call-mcp-tool.ts
|
|
4788
4787
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
4788
|
+
function resolveMcpArguments(input) {
|
|
4789
|
+
const extraKeys = Object.keys(input).filter(
|
|
4790
|
+
(key) => key !== "tool_name" && key !== "arguments"
|
|
4791
|
+
);
|
|
4792
|
+
if (extraKeys.length > 0) {
|
|
4793
|
+
throw new Error(
|
|
4794
|
+
`callMcpTool MCP arguments must be nested under arguments, not top-level fields: ${extraKeys.join(", ")}`
|
|
4795
|
+
);
|
|
4796
|
+
}
|
|
4797
|
+
if ("arguments" in input) {
|
|
4798
|
+
const args = input.arguments;
|
|
4799
|
+
if (args === void 0) {
|
|
4800
|
+
return {};
|
|
4801
|
+
}
|
|
4802
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
4803
|
+
throw new Error("callMcpTool arguments must be an object when provided");
|
|
4804
|
+
}
|
|
4805
|
+
return args;
|
|
4806
|
+
}
|
|
4807
|
+
return {};
|
|
4808
|
+
}
|
|
4809
|
+
function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
|
|
4810
|
+
return tool({
|
|
4811
|
+
description: "Call an active MCP tool by exact tool_name. Use loadSkill to activate the provider, then searchMcpTools to discover tool names and schemas; authorization is handled by the runtime when required.",
|
|
4812
|
+
inputSchema: Type4.Object(
|
|
4813
|
+
{
|
|
4814
|
+
tool_name: Type4.String({
|
|
4815
|
+
minLength: 1,
|
|
4816
|
+
description: "Exact MCP tool_name from searchMcpTools."
|
|
4817
|
+
}),
|
|
4818
|
+
arguments: Type4.Optional(
|
|
4819
|
+
Type4.Record(Type4.String(), Type4.Unknown(), {
|
|
4820
|
+
description: "Arguments matching the disclosed MCP tool schema."
|
|
4821
|
+
})
|
|
4822
|
+
)
|
|
4823
|
+
},
|
|
4824
|
+
{ additionalProperties: false }
|
|
4825
|
+
),
|
|
4826
|
+
execute: async (input) => {
|
|
4827
|
+
const { tool_name } = input;
|
|
4828
|
+
const mcpTool = mcpToolManager.getResolvedActiveTools(getActiveSkills()).find((candidate) => candidate.name === tool_name);
|
|
4829
|
+
if (!mcpTool) {
|
|
4830
|
+
throw new Error(`MCP tool is not active for this turn: ${tool_name}`);
|
|
4831
|
+
}
|
|
4832
|
+
return await mcpTool.execute(
|
|
4833
|
+
resolveMcpArguments(input)
|
|
4834
|
+
);
|
|
4835
|
+
}
|
|
4836
|
+
});
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
// src/chat/tools/skill/load-skill.ts
|
|
4840
|
+
import { Type as Type5 } from "@sinclair/typebox";
|
|
4789
4841
|
function toLoadedSkill(result, availableSkills) {
|
|
4790
4842
|
if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
|
|
4791
4843
|
return null;
|
|
@@ -4797,7 +4849,6 @@ function toLoadedSkill(result, availableSkills) {
|
|
|
4797
4849
|
skillPath: metadata?.skillPath ?? result.skill_dir,
|
|
4798
4850
|
...metadata?.pluginProvider ? { pluginProvider: metadata.pluginProvider } : {},
|
|
4799
4851
|
...metadata?.allowedTools ? { allowedTools: metadata.allowedTools } : {},
|
|
4800
|
-
...metadata?.usesConfig ? { usesConfig: metadata.usesConfig } : {},
|
|
4801
4852
|
body: result.instructions
|
|
4802
4853
|
};
|
|
4803
4854
|
}
|
|
@@ -4830,9 +4881,9 @@ async function loadSkillFromHost(availableSkills, skillName) {
|
|
|
4830
4881
|
}
|
|
4831
4882
|
function createLoadSkillTool(availableSkills, options) {
|
|
4832
4883
|
return tool({
|
|
4833
|
-
description: "Load a skill by name so its instructions are available for this turn.
|
|
4834
|
-
inputSchema:
|
|
4835
|
-
skill_name:
|
|
4884
|
+
description: "Load a skill by name so its instructions and provider tool catalog are available for this turn. When the result includes mcp_provider and available_tool_count, use searchMcpTools to list or search descriptors before callMcpTool. Use when a request clearly matches a known skill.",
|
|
4885
|
+
inputSchema: Type5.Object({
|
|
4886
|
+
skill_name: Type5.String({
|
|
4836
4887
|
minLength: 1,
|
|
4837
4888
|
description: "Skill name to load, without the leading slash."
|
|
4838
4889
|
})
|
|
@@ -4851,53 +4902,87 @@ function createLoadSkillTool(availableSkills, options) {
|
|
|
4851
4902
|
});
|
|
4852
4903
|
}
|
|
4853
4904
|
|
|
4854
|
-
// src/chat/tools/
|
|
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
|
|
4905
|
+
// src/chat/tools/skill/search-mcp-tools.ts
|
|
4877
4906
|
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
4907
|
|
|
4893
4908
|
// src/chat/tools/skill/mcp-tool-summary.ts
|
|
4894
|
-
function
|
|
4895
|
-
|
|
4896
|
-
|
|
4909
|
+
function getSchemaProperties(schema) {
|
|
4910
|
+
return schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
4911
|
+
}
|
|
4912
|
+
function getRequiredFields(schema) {
|
|
4913
|
+
return Array.isArray(schema.required) ? new Set(
|
|
4897
4914
|
schema.required.filter(
|
|
4898
4915
|
(value) => typeof value === "string"
|
|
4899
4916
|
)
|
|
4900
4917
|
) : /* @__PURE__ */ new Set();
|
|
4918
|
+
}
|
|
4919
|
+
function formatSchemaType(schema) {
|
|
4920
|
+
if (!schema || typeof schema !== "object") {
|
|
4921
|
+
return "unknown";
|
|
4922
|
+
}
|
|
4923
|
+
const typed = schema;
|
|
4924
|
+
const type = typed.type;
|
|
4925
|
+
if (typeof type === "string") {
|
|
4926
|
+
if (type === "array") {
|
|
4927
|
+
return `${formatSchemaType(typed.items)}[]`;
|
|
4928
|
+
}
|
|
4929
|
+
return type;
|
|
4930
|
+
}
|
|
4931
|
+
if (Array.isArray(type)) {
|
|
4932
|
+
return type.filter((value) => typeof value === "string").join(" | ");
|
|
4933
|
+
}
|
|
4934
|
+
if (Array.isArray(typed.enum) && typed.enum.length > 0) {
|
|
4935
|
+
return typed.enum.map((value) => JSON.stringify(value)).join(" | ");
|
|
4936
|
+
}
|
|
4937
|
+
return "unknown";
|
|
4938
|
+
}
|
|
4939
|
+
function formatArgumentPlaceholder(name, schema) {
|
|
4940
|
+
const type = formatSchemaType(schema);
|
|
4941
|
+
if (type === "string") {
|
|
4942
|
+
return `<${name}>`;
|
|
4943
|
+
}
|
|
4944
|
+
if (type === "number" || type === "integer") {
|
|
4945
|
+
return "<number>";
|
|
4946
|
+
}
|
|
4947
|
+
if (type === "boolean") {
|
|
4948
|
+
return "<boolean>";
|
|
4949
|
+
}
|
|
4950
|
+
if (type.endsWith("[]")) {
|
|
4951
|
+
return "<array>";
|
|
4952
|
+
}
|
|
4953
|
+
if (type === "object") {
|
|
4954
|
+
return "<object>";
|
|
4955
|
+
}
|
|
4956
|
+
return `<${type}>`;
|
|
4957
|
+
}
|
|
4958
|
+
function formatMcpToolSignature(toolName, schema) {
|
|
4959
|
+
const properties = getSchemaProperties(schema);
|
|
4960
|
+
const required = getRequiredFields(schema);
|
|
4961
|
+
const fields = Object.entries(properties).map(([name, propertySchema]) => {
|
|
4962
|
+
const marker = required.has(name) ? "" : "?";
|
|
4963
|
+
return `${name}${marker}: ${formatSchemaType(propertySchema)}`;
|
|
4964
|
+
});
|
|
4965
|
+
if (fields.length === 0) {
|
|
4966
|
+
return `${toolName}()`;
|
|
4967
|
+
}
|
|
4968
|
+
return `${toolName}({ ${fields.join(", ")} })`;
|
|
4969
|
+
}
|
|
4970
|
+
function formatMcpToolCallExample(toolName, schema) {
|
|
4971
|
+
return {
|
|
4972
|
+
tool_name: toolName,
|
|
4973
|
+
arguments: Object.fromEntries(
|
|
4974
|
+
Object.entries(getSchemaProperties(schema)).map(
|
|
4975
|
+
([name, propertySchema]) => [
|
|
4976
|
+
name,
|
|
4977
|
+
formatArgumentPlaceholder(name, propertySchema)
|
|
4978
|
+
]
|
|
4979
|
+
)
|
|
4980
|
+
)
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
function summarizeInputSchema(schema) {
|
|
4984
|
+
const properties = getSchemaProperties(schema);
|
|
4985
|
+
const required = getRequiredFields(schema);
|
|
4901
4986
|
const propertyNames = Object.keys(properties);
|
|
4902
4987
|
if (propertyNames.length === 0) {
|
|
4903
4988
|
return "No arguments.";
|
|
@@ -4907,59 +4992,190 @@ function summarizeInputSchema(schema) {
|
|
|
4907
4992
|
function toExposedToolSummary(toolDef) {
|
|
4908
4993
|
return {
|
|
4909
4994
|
tool_name: toolDef.name,
|
|
4995
|
+
mcp_tool_name: toolDef.rawName,
|
|
4910
4996
|
provider: toolDef.provider,
|
|
4997
|
+
...toolDef.title ? { title: toolDef.title } : {},
|
|
4911
4998
|
description: toolDef.description,
|
|
4999
|
+
signature: formatMcpToolSignature(toolDef.name, toolDef.parameters),
|
|
5000
|
+
call: formatMcpToolCallExample(toolDef.name, toolDef.parameters),
|
|
4912
5001
|
input_schema: toolDef.parameters,
|
|
4913
|
-
input_schema_summary: summarizeInputSchema(toolDef.parameters)
|
|
5002
|
+
input_schema_summary: summarizeInputSchema(toolDef.parameters),
|
|
5003
|
+
...toolDef.outputSchema ? { output_schema: toolDef.outputSchema } : {},
|
|
5004
|
+
...toolDef.annotations ? { annotations: toolDef.annotations } : {}
|
|
4914
5005
|
};
|
|
4915
5006
|
}
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
5007
|
+
function toActiveMcpCatalogSummaries(toolDefs) {
|
|
5008
|
+
const countsByProvider = /* @__PURE__ */ new Map();
|
|
5009
|
+
for (const toolDef of toolDefs) {
|
|
5010
|
+
countsByProvider.set(
|
|
5011
|
+
toolDef.provider,
|
|
5012
|
+
(countsByProvider.get(toolDef.provider) ?? 0) + 1
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
return [...countsByProvider.entries()].map(([provider, availableToolCount]) => ({
|
|
5016
|
+
provider,
|
|
5017
|
+
available_tool_count: availableToolCount
|
|
5018
|
+
})).sort((left, right) => left.provider.localeCompare(right.provider));
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
// src/chat/tools/skill/search-mcp-tools.ts
|
|
5022
|
+
var DEFAULT_MAX_RESULTS = 5;
|
|
5023
|
+
var MAX_RESULTS = 20;
|
|
5024
|
+
function normalize(value) {
|
|
5025
|
+
return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
|
|
5026
|
+
}
|
|
5027
|
+
function searchableToolText(toolDef) {
|
|
5028
|
+
return normalize(
|
|
5029
|
+
[
|
|
5030
|
+
toolDef.name,
|
|
5031
|
+
toolDef.rawName,
|
|
5032
|
+
toolDef.title,
|
|
5033
|
+
toolDef.provider,
|
|
5034
|
+
toolDef.description,
|
|
5035
|
+
JSON.stringify(toolDef.parameters),
|
|
5036
|
+
JSON.stringify(toolDef.outputSchema),
|
|
5037
|
+
JSON.stringify(toolDef.annotations)
|
|
5038
|
+
].filter(Boolean).join(" ")
|
|
5039
|
+
);
|
|
5040
|
+
}
|
|
5041
|
+
function scoreTool(toolDef, query) {
|
|
5042
|
+
const normalizedQuery = normalize(query);
|
|
5043
|
+
if (!normalizedQuery) {
|
|
5044
|
+
return 0;
|
|
5045
|
+
}
|
|
5046
|
+
const normalizedName = normalize(toolDef.name);
|
|
5047
|
+
const normalizedRawName = normalize(toolDef.rawName);
|
|
5048
|
+
const text = searchableToolText(toolDef);
|
|
5049
|
+
let score = 0;
|
|
5050
|
+
if (normalizedName === normalizedQuery || normalizedRawName === normalizedQuery) {
|
|
5051
|
+
score += 100;
|
|
5052
|
+
}
|
|
5053
|
+
if (normalizedName.includes(normalizedQuery)) {
|
|
5054
|
+
score += 50;
|
|
5055
|
+
}
|
|
5056
|
+
if (normalizedRawName.includes(normalizedQuery)) {
|
|
5057
|
+
score += 45;
|
|
5058
|
+
}
|
|
5059
|
+
if (text.includes(normalizedQuery)) {
|
|
5060
|
+
score += 25;
|
|
5061
|
+
}
|
|
5062
|
+
for (const term of normalizedQuery.split(/\s+/).filter(Boolean)) {
|
|
5063
|
+
if (normalizedName.includes(term)) {
|
|
5064
|
+
score += 12;
|
|
5065
|
+
}
|
|
5066
|
+
if (normalizedRawName.includes(term)) {
|
|
5067
|
+
score += 10;
|
|
5068
|
+
}
|
|
5069
|
+
if (text.includes(term)) {
|
|
5070
|
+
score += 4;
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
return score;
|
|
5074
|
+
}
|
|
5075
|
+
function searchMcpCatalog(tools, query) {
|
|
5076
|
+
if (!normalize(query)) {
|
|
5077
|
+
return [...tools].sort(
|
|
5078
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
5079
|
+
);
|
|
5080
|
+
}
|
|
5081
|
+
return tools.map(
|
|
5082
|
+
(toolDef) => ({
|
|
5083
|
+
tool: toolDef,
|
|
5084
|
+
score: scoreTool(toolDef, query)
|
|
5085
|
+
})
|
|
5086
|
+
).filter((ranked) => ranked.score > 0).sort((left, right) => {
|
|
5087
|
+
if (right.score !== left.score) {
|
|
5088
|
+
return right.score - left.score;
|
|
5089
|
+
}
|
|
5090
|
+
return left.tool.name.localeCompare(right.tool.name);
|
|
5091
|
+
}).map((ranked) => ranked.tool);
|
|
5092
|
+
}
|
|
5093
|
+
function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
4921
5094
|
return tool({
|
|
4922
|
-
description: "
|
|
4923
|
-
inputSchema:
|
|
5095
|
+
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.",
|
|
5096
|
+
inputSchema: Type6.Object(
|
|
4924
5097
|
{
|
|
4925
|
-
query:
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
5098
|
+
query: Type6.Optional(
|
|
5099
|
+
Type6.String({
|
|
5100
|
+
minLength: 1,
|
|
5101
|
+
description: "Optional search terms describing the MCP tool or arguments needed."
|
|
5102
|
+
})
|
|
5103
|
+
),
|
|
5104
|
+
provider: Type6.Optional(
|
|
5105
|
+
Type6.String({
|
|
4931
5106
|
minLength: 1,
|
|
4932
|
-
description: "Optional
|
|
5107
|
+
description: "Optional provider name to list or search within."
|
|
4933
5108
|
})
|
|
4934
5109
|
),
|
|
4935
|
-
|
|
4936
|
-
|
|
5110
|
+
max_results: Type6.Optional(
|
|
5111
|
+
Type6.Integer({
|
|
4937
5112
|
minimum: 1,
|
|
4938
|
-
maximum:
|
|
4939
|
-
description: "Maximum
|
|
5113
|
+
maximum: MAX_RESULTS,
|
|
5114
|
+
description: "Maximum matching tool descriptors to return."
|
|
4940
5115
|
})
|
|
4941
5116
|
)
|
|
4942
5117
|
},
|
|
4943
5118
|
{ additionalProperties: false }
|
|
4944
5119
|
),
|
|
4945
|
-
execute: async ({ query, provider,
|
|
4946
|
-
const
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
5120
|
+
execute: async ({ query, provider, max_results }) => {
|
|
5121
|
+
const catalog = mcpToolManager.getActiveToolCatalog(
|
|
5122
|
+
getActiveSkills(),
|
|
5123
|
+
provider ? { provider } : {}
|
|
5124
|
+
);
|
|
5125
|
+
const maxResults = max_results ?? DEFAULT_MAX_RESULTS;
|
|
5126
|
+
const matches = searchMcpCatalog(catalog, query ?? "").slice(
|
|
5127
|
+
0,
|
|
5128
|
+
maxResults
|
|
5129
|
+
);
|
|
4950
5130
|
return {
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
5131
|
+
query: query ?? null,
|
|
5132
|
+
provider: provider ?? null,
|
|
5133
|
+
total_active_tools: catalog.length,
|
|
5134
|
+
returned_tools: matches.length,
|
|
5135
|
+
tools: matches.map(toExposedToolSummary)
|
|
4956
5136
|
};
|
|
4957
5137
|
}
|
|
4958
5138
|
});
|
|
4959
5139
|
}
|
|
4960
5140
|
|
|
4961
|
-
// src/chat/tools/
|
|
5141
|
+
// src/chat/tools/sandbox/read-file.ts
|
|
5142
|
+
import { Type as Type7 } from "@sinclair/typebox";
|
|
5143
|
+
function createReadFileTool() {
|
|
5144
|
+
return tool({
|
|
5145
|
+
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.",
|
|
5146
|
+
inputSchema: Type7.Object(
|
|
5147
|
+
{
|
|
5148
|
+
path: Type7.String({
|
|
5149
|
+
minLength: 1,
|
|
5150
|
+
description: "Path to the file in the sandbox workspace."
|
|
5151
|
+
})
|
|
5152
|
+
},
|
|
5153
|
+
{ additionalProperties: false }
|
|
5154
|
+
),
|
|
5155
|
+
execute: async () => {
|
|
5156
|
+
throw new Error(
|
|
5157
|
+
"readFile can only run when sandbox execution is enabled."
|
|
5158
|
+
);
|
|
5159
|
+
}
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
// src/chat/tools/runtime/report-progress.ts
|
|
4962
5164
|
import { Type as Type8 } from "@sinclair/typebox";
|
|
5165
|
+
function createReportProgressTool() {
|
|
5166
|
+
return tool({
|
|
5167
|
+
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.",
|
|
5168
|
+
inputSchema: Type8.Object({
|
|
5169
|
+
message: Type8.String({
|
|
5170
|
+
minLength: 1,
|
|
5171
|
+
description: "Short user-facing progress message."
|
|
5172
|
+
})
|
|
5173
|
+
})
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5177
|
+
// src/chat/tools/slack/channel-list-messages.ts
|
|
5178
|
+
import { Type as Type9 } from "@sinclair/typebox";
|
|
4963
5179
|
|
|
4964
5180
|
// src/chat/slack/channel.ts
|
|
4965
5181
|
async function listChannelMessages(input) {
|
|
@@ -5048,39 +5264,39 @@ async function listThreadReplies(input) {
|
|
|
5048
5264
|
function createSlackChannelListMessagesTool(context) {
|
|
5049
5265
|
return tool({
|
|
5050
5266
|
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:
|
|
5052
|
-
limit:
|
|
5053
|
-
|
|
5267
|
+
inputSchema: Type9.Object({
|
|
5268
|
+
limit: Type9.Optional(
|
|
5269
|
+
Type9.Integer({
|
|
5054
5270
|
minimum: 1,
|
|
5055
5271
|
maximum: 1e3,
|
|
5056
5272
|
description: "Maximum number of messages to return across pages."
|
|
5057
5273
|
})
|
|
5058
5274
|
),
|
|
5059
|
-
cursor:
|
|
5060
|
-
|
|
5275
|
+
cursor: Type9.Optional(
|
|
5276
|
+
Type9.String({
|
|
5061
5277
|
minLength: 1,
|
|
5062
5278
|
description: "Optional cursor to continue from a prior call."
|
|
5063
5279
|
})
|
|
5064
5280
|
),
|
|
5065
|
-
oldest:
|
|
5066
|
-
|
|
5281
|
+
oldest: Type9.Optional(
|
|
5282
|
+
Type9.String({
|
|
5067
5283
|
minLength: 1,
|
|
5068
5284
|
description: "Optional oldest message timestamp (Slack ts) for range filtering."
|
|
5069
5285
|
})
|
|
5070
5286
|
),
|
|
5071
|
-
latest:
|
|
5072
|
-
|
|
5287
|
+
latest: Type9.Optional(
|
|
5288
|
+
Type9.String({
|
|
5073
5289
|
minLength: 1,
|
|
5074
5290
|
description: "Optional latest message timestamp (Slack ts) for range filtering."
|
|
5075
5291
|
})
|
|
5076
5292
|
),
|
|
5077
|
-
inclusive:
|
|
5078
|
-
|
|
5293
|
+
inclusive: Type9.Optional(
|
|
5294
|
+
Type9.Boolean({
|
|
5079
5295
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
5080
5296
|
})
|
|
5081
5297
|
),
|
|
5082
|
-
max_pages:
|
|
5083
|
-
|
|
5298
|
+
max_pages: Type9.Optional(
|
|
5299
|
+
Type9.Integer({
|
|
5084
5300
|
minimum: 1,
|
|
5085
5301
|
maximum: 10,
|
|
5086
5302
|
description: "Maximum number of API pages to traverse in a single call."
|
|
@@ -5134,7 +5350,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5134
5350
|
}
|
|
5135
5351
|
|
|
5136
5352
|
// src/chat/tools/slack/channel-post-message.ts
|
|
5137
|
-
import { Type as
|
|
5353
|
+
import { Type as Type10 } from "@sinclair/typebox";
|
|
5138
5354
|
|
|
5139
5355
|
// src/chat/tools/idempotency.ts
|
|
5140
5356
|
function stableSerialize(value) {
|
|
@@ -5156,8 +5372,8 @@ function createOperationKey(toolName, input) {
|
|
|
5156
5372
|
function createSlackChannelPostMessageTool(context, state) {
|
|
5157
5373
|
return tool({
|
|
5158
5374
|
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:
|
|
5160
|
-
text:
|
|
5375
|
+
inputSchema: Type10.Object({
|
|
5376
|
+
text: Type10.String({
|
|
5161
5377
|
minLength: 1,
|
|
5162
5378
|
maxLength: 4e4,
|
|
5163
5379
|
description: "Slack mrkdwn text to post."
|
|
@@ -5200,12 +5416,12 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
5200
5416
|
}
|
|
5201
5417
|
|
|
5202
5418
|
// src/chat/tools/slack/message-add-reaction.ts
|
|
5203
|
-
import { Type as
|
|
5419
|
+
import { Type as Type11 } from "@sinclair/typebox";
|
|
5204
5420
|
function createSlackMessageAddReactionTool(context, state) {
|
|
5205
5421
|
return tool({
|
|
5206
5422
|
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:
|
|
5208
|
-
emoji:
|
|
5423
|
+
inputSchema: Type11.Object({
|
|
5424
|
+
emoji: Type11.String({
|
|
5209
5425
|
minLength: 1,
|
|
5210
5426
|
maxLength: 64,
|
|
5211
5427
|
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 +5479,7 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
5263
5479
|
}
|
|
5264
5480
|
|
|
5265
5481
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5266
|
-
import { Type as
|
|
5482
|
+
import { Type as Type12 } from "@sinclair/typebox";
|
|
5267
5483
|
|
|
5268
5484
|
// src/chat/tools/slack/canvases.ts
|
|
5269
5485
|
function normalizeCanvasMarkdown(markdown) {
|
|
@@ -5479,13 +5695,13 @@ function mergeRecentCanvases(existing, created) {
|
|
|
5479
5695
|
function createSlackCanvasCreateTool(context, state) {
|
|
5480
5696
|
return tool({
|
|
5481
5697
|
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:
|
|
5483
|
-
title:
|
|
5698
|
+
inputSchema: Type12.Object({
|
|
5699
|
+
title: Type12.String({
|
|
5484
5700
|
minLength: 1,
|
|
5485
5701
|
maxLength: 160,
|
|
5486
5702
|
description: "Canvas title."
|
|
5487
5703
|
}),
|
|
5488
|
-
markdown:
|
|
5704
|
+
markdown: Type12.String({
|
|
5489
5705
|
minLength: 1,
|
|
5490
5706
|
description: "Canvas markdown body content."
|
|
5491
5707
|
})
|
|
@@ -5551,29 +5767,29 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
5551
5767
|
function createSlackCanvasUpdateTool(state, _context) {
|
|
5552
5768
|
return tool({
|
|
5553
5769
|
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:
|
|
5555
|
-
markdown:
|
|
5770
|
+
inputSchema: Type12.Object({
|
|
5771
|
+
markdown: Type12.String({
|
|
5556
5772
|
minLength: 1,
|
|
5557
5773
|
description: "Markdown content to insert or use as replacement text."
|
|
5558
5774
|
}),
|
|
5559
|
-
operation:
|
|
5560
|
-
|
|
5775
|
+
operation: Type12.Optional(
|
|
5776
|
+
Type12.Union(
|
|
5561
5777
|
[
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5778
|
+
Type12.Literal("insert_at_end"),
|
|
5779
|
+
Type12.Literal("insert_at_start"),
|
|
5780
|
+
Type12.Literal("replace")
|
|
5565
5781
|
],
|
|
5566
5782
|
{ description: "Canvas update mode." }
|
|
5567
5783
|
)
|
|
5568
5784
|
),
|
|
5569
|
-
section_id:
|
|
5570
|
-
|
|
5785
|
+
section_id: Type12.Optional(
|
|
5786
|
+
Type12.String({
|
|
5571
5787
|
minLength: 1,
|
|
5572
5788
|
description: "Optional section ID required for targeted replace operations."
|
|
5573
5789
|
})
|
|
5574
5790
|
),
|
|
5575
|
-
section_contains_text:
|
|
5576
|
-
|
|
5791
|
+
section_contains_text: Type12.Optional(
|
|
5792
|
+
Type12.String({
|
|
5577
5793
|
minLength: 1,
|
|
5578
5794
|
description: "Optional helper text used to find the target section when section_id is not provided."
|
|
5579
5795
|
})
|
|
@@ -5639,8 +5855,8 @@ function createSlackCanvasUpdateTool(state, _context) {
|
|
|
5639
5855
|
function createSlackCanvasReadTool() {
|
|
5640
5856
|
return tool({
|
|
5641
5857
|
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:
|
|
5643
|
-
canvas:
|
|
5858
|
+
inputSchema: Type12.Object({
|
|
5859
|
+
canvas: Type12.String({
|
|
5644
5860
|
minLength: 1,
|
|
5645
5861
|
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
5646
5862
|
})
|
|
@@ -5690,7 +5906,7 @@ function createSlackCanvasReadTool() {
|
|
|
5690
5906
|
}
|
|
5691
5907
|
|
|
5692
5908
|
// src/chat/tools/slack/list-tools.ts
|
|
5693
|
-
import { Type as
|
|
5909
|
+
import { Type as Type13 } from "@sinclair/typebox";
|
|
5694
5910
|
|
|
5695
5911
|
// src/chat/tools/slack/lists.ts
|
|
5696
5912
|
function normalizeKey(value) {
|
|
@@ -5864,8 +6080,8 @@ async function updateListItem(input) {
|
|
|
5864
6080
|
function createSlackListCreateTool(state) {
|
|
5865
6081
|
return tool({
|
|
5866
6082
|
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:
|
|
5868
|
-
name:
|
|
6083
|
+
inputSchema: Type13.Object({
|
|
6084
|
+
name: Type13.String({
|
|
5869
6085
|
minLength: 1,
|
|
5870
6086
|
maxLength: 160,
|
|
5871
6087
|
description: "Name for the new Slack list."
|
|
@@ -5900,20 +6116,20 @@ function createSlackListCreateTool(state) {
|
|
|
5900
6116
|
function createSlackListAddItemsTool(state) {
|
|
5901
6117
|
return tool({
|
|
5902
6118
|
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:
|
|
5904
|
-
items:
|
|
6119
|
+
inputSchema: Type13.Object({
|
|
6120
|
+
items: Type13.Array(Type13.String({ minLength: 1 }), {
|
|
5905
6121
|
minItems: 1,
|
|
5906
6122
|
maxItems: 25,
|
|
5907
6123
|
description: "List item titles to create."
|
|
5908
6124
|
}),
|
|
5909
|
-
assignee_user_id:
|
|
5910
|
-
|
|
6125
|
+
assignee_user_id: Type13.Optional(
|
|
6126
|
+
Type13.String({
|
|
5911
6127
|
minLength: 1,
|
|
5912
6128
|
description: "Optional Slack user ID assigned to all created items."
|
|
5913
6129
|
})
|
|
5914
6130
|
),
|
|
5915
|
-
due_date:
|
|
5916
|
-
|
|
6131
|
+
due_date: Type13.Optional(
|
|
6132
|
+
Type13.String({
|
|
5917
6133
|
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
5918
6134
|
description: "Optional due date in YYYY-MM-DD format."
|
|
5919
6135
|
})
|
|
@@ -5962,9 +6178,9 @@ function createSlackListAddItemsTool(state) {
|
|
|
5962
6178
|
function createSlackListGetItemsTool(state) {
|
|
5963
6179
|
return tool({
|
|
5964
6180
|
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:
|
|
5966
|
-
limit:
|
|
5967
|
-
|
|
6181
|
+
inputSchema: Type13.Object({
|
|
6182
|
+
limit: Type13.Optional(
|
|
6183
|
+
Type13.Integer({
|
|
5968
6184
|
minimum: 1,
|
|
5969
6185
|
maximum: 200,
|
|
5970
6186
|
description: "Maximum number of list items to return."
|
|
@@ -5989,19 +6205,19 @@ function createSlackListGetItemsTool(state) {
|
|
|
5989
6205
|
function createSlackListUpdateItemTool(state) {
|
|
5990
6206
|
return tool({
|
|
5991
6207
|
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:
|
|
6208
|
+
inputSchema: Type13.Object(
|
|
5993
6209
|
{
|
|
5994
|
-
item_id:
|
|
6210
|
+
item_id: Type13.String({
|
|
5995
6211
|
minLength: 1,
|
|
5996
6212
|
description: "ID of the Slack list item to update."
|
|
5997
6213
|
}),
|
|
5998
|
-
completed:
|
|
5999
|
-
|
|
6214
|
+
completed: Type13.Optional(
|
|
6215
|
+
Type13.Boolean({
|
|
6000
6216
|
description: "Optional completion status update."
|
|
6001
6217
|
})
|
|
6002
6218
|
),
|
|
6003
|
-
title:
|
|
6004
|
-
|
|
6219
|
+
title: Type13.Optional(
|
|
6220
|
+
Type13.String({
|
|
6005
6221
|
minLength: 1,
|
|
6006
6222
|
description: "Optional new item title."
|
|
6007
6223
|
})
|
|
@@ -6051,11 +6267,11 @@ function createSlackListUpdateItemTool(state) {
|
|
|
6051
6267
|
}
|
|
6052
6268
|
|
|
6053
6269
|
// src/chat/tools/system-time.ts
|
|
6054
|
-
import { Type as
|
|
6270
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
6055
6271
|
function createSystemTimeTool() {
|
|
6056
6272
|
return tool({
|
|
6057
6273
|
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:
|
|
6274
|
+
inputSchema: Type14.Object({}),
|
|
6059
6275
|
execute: async () => {
|
|
6060
6276
|
const now = /* @__PURE__ */ new Date();
|
|
6061
6277
|
return {
|
|
@@ -6070,7 +6286,7 @@ function createSystemTimeTool() {
|
|
|
6070
6286
|
}
|
|
6071
6287
|
|
|
6072
6288
|
// src/chat/tools/web/fetch-tool.ts
|
|
6073
|
-
import { Type as
|
|
6289
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
6074
6290
|
|
|
6075
6291
|
// src/chat/tools/web/constants.ts
|
|
6076
6292
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -6418,13 +6634,13 @@ function extractHttpStatusFromMessage(message) {
|
|
|
6418
6634
|
function createWebFetchTool(hooks) {
|
|
6419
6635
|
return tool({
|
|
6420
6636
|
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:
|
|
6422
|
-
url:
|
|
6637
|
+
inputSchema: Type15.Object({
|
|
6638
|
+
url: Type15.String({
|
|
6423
6639
|
minLength: 1,
|
|
6424
6640
|
description: "HTTP(S) URL to fetch."
|
|
6425
6641
|
}),
|
|
6426
|
-
max_chars:
|
|
6427
|
-
|
|
6642
|
+
max_chars: Type15.Optional(
|
|
6643
|
+
Type15.Integer({
|
|
6428
6644
|
minimum: 500,
|
|
6429
6645
|
maximum: MAX_FETCH_CHARS,
|
|
6430
6646
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -6484,9 +6700,9 @@ function createWebFetchTool(hooks) {
|
|
|
6484
6700
|
// src/chat/tools/web/search.ts
|
|
6485
6701
|
import { generateText } from "ai";
|
|
6486
6702
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
6487
|
-
import { Type as
|
|
6703
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6488
6704
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6489
|
-
var
|
|
6705
|
+
var MAX_RESULTS2 = 5;
|
|
6490
6706
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
6491
6707
|
var SEARCH_TOOL_NAME = "parallelSearch";
|
|
6492
6708
|
function asString(value) {
|
|
@@ -6527,16 +6743,16 @@ function isAuthFailure(message) {
|
|
|
6527
6743
|
function createWebSearchTool() {
|
|
6528
6744
|
return tool({
|
|
6529
6745
|
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:
|
|
6531
|
-
query:
|
|
6746
|
+
inputSchema: Type16.Object({
|
|
6747
|
+
query: Type16.String({
|
|
6532
6748
|
minLength: 1,
|
|
6533
6749
|
maxLength: 500,
|
|
6534
6750
|
description: "Search query."
|
|
6535
6751
|
}),
|
|
6536
|
-
max_results:
|
|
6537
|
-
|
|
6752
|
+
max_results: Type16.Optional(
|
|
6753
|
+
Type16.Integer({
|
|
6538
6754
|
minimum: 1,
|
|
6539
|
-
maximum:
|
|
6755
|
+
maximum: MAX_RESULTS2,
|
|
6540
6756
|
description: "Max results to return."
|
|
6541
6757
|
})
|
|
6542
6758
|
)
|
|
@@ -6600,17 +6816,17 @@ function createWebSearchTool() {
|
|
|
6600
6816
|
}
|
|
6601
6817
|
|
|
6602
6818
|
// src/chat/tools/sandbox/write-file.ts
|
|
6603
|
-
import { Type as
|
|
6819
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
6604
6820
|
function createWriteFileTool() {
|
|
6605
6821
|
return tool({
|
|
6606
6822
|
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:
|
|
6823
|
+
inputSchema: Type17.Object(
|
|
6608
6824
|
{
|
|
6609
|
-
path:
|
|
6825
|
+
path: Type17.String({
|
|
6610
6826
|
minLength: 1,
|
|
6611
6827
|
description: "Path to write in the sandbox workspace."
|
|
6612
6828
|
}),
|
|
6613
|
-
content:
|
|
6829
|
+
content: Type17.String({
|
|
6614
6830
|
description: "UTF-8 file content to write."
|
|
6615
6831
|
})
|
|
6616
6832
|
},
|
|
@@ -6685,7 +6901,11 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6685
6901
|
slackListUpdateItem: createSlackListUpdateItemTool(state)
|
|
6686
6902
|
};
|
|
6687
6903
|
if (context.mcpToolManager && context.getActiveSkills) {
|
|
6688
|
-
tools.
|
|
6904
|
+
tools.searchMcpTools = createSearchMcpToolsTool(
|
|
6905
|
+
context.mcpToolManager,
|
|
6906
|
+
context.getActiveSkills
|
|
6907
|
+
);
|
|
6908
|
+
tools.callMcpTool = createCallMcpToolTool(
|
|
6689
6909
|
context.mcpToolManager,
|
|
6690
6910
|
context.getActiveSkills
|
|
6691
6911
|
);
|
|
@@ -8375,8 +8595,10 @@ function getToolErrorAttributes(error) {
|
|
|
8375
8595
|
};
|
|
8376
8596
|
}
|
|
8377
8597
|
function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
|
|
8598
|
+
const errorType = getMcpAwareErrorType(error, "tool_execution_error");
|
|
8599
|
+
const errorMessage = getMcpAwareErrorMessage(error);
|
|
8378
8600
|
setSpanAttributes({
|
|
8379
|
-
"error.type":
|
|
8601
|
+
"error.type": errorType
|
|
8380
8602
|
});
|
|
8381
8603
|
if (shouldTrace) {
|
|
8382
8604
|
logWarn(
|
|
@@ -8387,8 +8609,8 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
8387
8609
|
"gen_ai.operation.name": "execute_tool",
|
|
8388
8610
|
"gen_ai.tool.name": toolName,
|
|
8389
8611
|
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
8390
|
-
"error.type":
|
|
8391
|
-
"error.message":
|
|
8612
|
+
"error.type": errorType,
|
|
8613
|
+
"error.message": errorMessage
|
|
8392
8614
|
},
|
|
8393
8615
|
"Agent tool call failed"
|
|
8394
8616
|
);
|
|
@@ -8422,7 +8644,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8422
8644
|
execute: async (toolCallId, params) => {
|
|
8423
8645
|
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
8424
8646
|
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
8425
|
-
onToolCall?.(toolName);
|
|
8426
8647
|
const traceToolContext = {
|
|
8427
8648
|
...spanContext,
|
|
8428
8649
|
conversationId: spanContext.conversationId,
|
|
@@ -8441,6 +8662,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8441
8662
|
spanContext,
|
|
8442
8663
|
async () => {
|
|
8443
8664
|
const parsed = params;
|
|
8665
|
+
onToolCall?.(toolName, parsed);
|
|
8444
8666
|
try {
|
|
8445
8667
|
if (typeof toolDef.execute !== "function") {
|
|
8446
8668
|
const resultDetails = { ok: true };
|
|
@@ -8710,24 +8932,11 @@ function upsertActiveSkill(activeSkills, next) {
|
|
|
8710
8932
|
existing.description = next.description;
|
|
8711
8933
|
existing.skillPath = next.skillPath;
|
|
8712
8934
|
existing.allowedTools = next.allowedTools;
|
|
8713
|
-
existing.usesConfig = next.usesConfig;
|
|
8714
8935
|
existing.pluginProvider = next.pluginProvider;
|
|
8715
8936
|
return;
|
|
8716
8937
|
}
|
|
8717
8938
|
activeSkills.push(next);
|
|
8718
8939
|
}
|
|
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
8940
|
function trimTrailingAssistantMessages(messages) {
|
|
8732
8941
|
let end = messages.length;
|
|
8733
8942
|
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
|
|
@@ -9710,20 +9919,6 @@ function buildUserTurnInput(args) {
|
|
|
9710
9919
|
}
|
|
9711
9920
|
return { routerBlocks, userContentParts };
|
|
9712
9921
|
}
|
|
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
9922
|
async function generateAssistantReply(messageText, context = {}) {
|
|
9728
9923
|
const replyStartedAtMs = Date.now();
|
|
9729
9924
|
let timeoutResumeConversationId;
|
|
@@ -10057,11 +10252,18 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10057
10252
|
if (!effective.pluginProvider) {
|
|
10058
10253
|
return void 0;
|
|
10059
10254
|
}
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10255
|
+
if (!turnMcpToolManager.getActiveProviders().includes(effective.pluginProvider)) {
|
|
10256
|
+
return void 0;
|
|
10257
|
+
}
|
|
10258
|
+
const availableToolCount = turnMcpToolManager.getActiveToolCatalog(
|
|
10259
|
+
activeSkills,
|
|
10260
|
+
{
|
|
10063
10261
|
provider: effective.pluginProvider
|
|
10064
|
-
}
|
|
10262
|
+
}
|
|
10263
|
+
).length;
|
|
10264
|
+
return {
|
|
10265
|
+
mcp_provider: effective.pluginProvider,
|
|
10266
|
+
available_tool_count: availableToolCount
|
|
10065
10267
|
};
|
|
10066
10268
|
}
|
|
10067
10269
|
},
|
|
@@ -10091,22 +10293,20 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10091
10293
|
await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
|
|
10092
10294
|
}
|
|
10093
10295
|
syncResumeState();
|
|
10094
|
-
const
|
|
10296
|
+
const activeMcpCatalogs = toActiveMcpCatalogSummaries(
|
|
10297
|
+
turnMcpToolManager.getActiveToolCatalog(activeSkills)
|
|
10298
|
+
);
|
|
10095
10299
|
baseInstructions = buildSystemPrompt({
|
|
10096
10300
|
availableSkills,
|
|
10097
10301
|
activeSkills,
|
|
10098
|
-
|
|
10302
|
+
activeMcpCatalogs,
|
|
10099
10303
|
invocation: skillInvocation,
|
|
10100
10304
|
assistant: context.assistant,
|
|
10101
10305
|
requester: context.requester,
|
|
10102
10306
|
artifactState: context.artifactState,
|
|
10103
10307
|
configuration: configurationValues,
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
invokedSkill
|
|
10107
|
-
),
|
|
10108
|
-
runtimeMetadata: getRuntimeMetadata(),
|
|
10109
|
-
threadParticipants: context.threadParticipants
|
|
10308
|
+
threadParticipants: context.threadParticipants,
|
|
10309
|
+
turnState: resumedFromCheckpoint ? "resumed" : "fresh"
|
|
10110
10310
|
});
|
|
10111
10311
|
const inputMessagesAttribute = serializeGenAiAttribute([
|
|
10112
10312
|
{
|
|
@@ -10118,10 +10318,23 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10118
10318
|
content: userContentParts.map((part) => toObservablePromptPart(part))
|
|
10119
10319
|
}
|
|
10120
10320
|
]);
|
|
10121
|
-
const onToolCall = (toolName) => {
|
|
10321
|
+
const onToolCall = (toolName, params) => {
|
|
10122
10322
|
toolCalls.push(toolName);
|
|
10323
|
+
try {
|
|
10324
|
+
context.onToolInvocation?.({ toolName, params });
|
|
10325
|
+
} catch (error) {
|
|
10326
|
+
logWarn(
|
|
10327
|
+
"tool_invocation_observer_failed",
|
|
10328
|
+
spanContext,
|
|
10329
|
+
{
|
|
10330
|
+
"gen_ai.tool.name": toolName,
|
|
10331
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
10332
|
+
},
|
|
10333
|
+
"Tool invocation observer failed"
|
|
10334
|
+
);
|
|
10335
|
+
}
|
|
10123
10336
|
};
|
|
10124
|
-
const
|
|
10337
|
+
const agentTools = createAgentTools(
|
|
10125
10338
|
tools,
|
|
10126
10339
|
skillSandbox,
|
|
10127
10340
|
spanContext,
|
|
@@ -10131,24 +10344,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10131
10344
|
pluginAuth,
|
|
10132
10345
|
onToolCall
|
|
10133
10346
|
);
|
|
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
10347
|
agent = new Agent({
|
|
10153
10348
|
getApiKey: () => getPiGatewayApiKeyOverride(),
|
|
10154
10349
|
initialState: {
|