@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.d.ts +2 -0
- package/dist/app.js +948 -584
- package/dist/{chunk-LEYD42MR.js → chunk-3M7ZD6FF.js} +1 -1
- package/dist/{chunk-ICIRAL6Y.js → chunk-EHXMTKBA.js} +53 -107
- package/dist/{chunk-RZJDO55D.js → chunk-XARRBRQV.js} +4 -0
- package/dist/cli/check.js +9 -2
- package/dist/cli/snapshot-warmup.js +2 -2
- 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-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-
|
|
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-
|
|
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}>`]
|
|
2908
|
+
return [`<${tag}>`, "none", `</${tag}>`];
|
|
2887
2909
|
}
|
|
2888
|
-
return [`<${tag}>`, ...lines, `</${tag}>`]
|
|
2910
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
2889
2911
|
}
|
|
2890
|
-
function renderTag(tag,
|
|
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 "<
|
|
2920
|
+
return "<available-skills>\n</available-skills>";
|
|
2896
2921
|
}
|
|
2897
|
-
const lines = ["<
|
|
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("</
|
|
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 "<
|
|
2941
|
+
return "<loaded-skills>\n</loaded-skills>";
|
|
2922
2942
|
}
|
|
2923
|
-
const lines = ["<
|
|
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(
|
|
2930
|
-
|
|
2931
|
-
|
|
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("</
|
|
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
|
|
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
|
|
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");
|
|
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
|
|
2997
|
+
function formatReferenceFilesLines() {
|
|
2986
2998
|
const files = listReferenceFiles();
|
|
2987
2999
|
if (files.length === 0) {
|
|
2988
|
-
return
|
|
3000
|
+
return null;
|
|
2989
3001
|
}
|
|
2990
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
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
|
-
|
|
3049
|
-
(
|
|
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
|
|
3052
|
-
|
|
3189
|
+
const participantLines = formatThreadParticipantsLines(
|
|
3190
|
+
params.threadParticipants
|
|
3053
3191
|
);
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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
|
|
4834
|
-
inputSchema:
|
|
4835
|
-
skill_name:
|
|
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/
|
|
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
|
|
4895
|
-
|
|
4896
|
-
|
|
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
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
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: "
|
|
4923
|
-
inputSchema:
|
|
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:
|
|
4926
|
-
|
|
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
|
|
5203
|
+
description: "Optional search terms describing the MCP tool or arguments needed."
|
|
4933
5204
|
})
|
|
4934
5205
|
),
|
|
4935
|
-
|
|
4936
|
-
|
|
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:
|
|
4939
|
-
description: "Maximum
|
|
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,
|
|
4946
|
-
const
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
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
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
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/
|
|
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:
|
|
5052
|
-
limit:
|
|
5053
|
-
|
|
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:
|
|
5060
|
-
|
|
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:
|
|
5066
|
-
|
|
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:
|
|
5072
|
-
|
|
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:
|
|
5078
|
-
|
|
5395
|
+
inclusive: Type9.Optional(
|
|
5396
|
+
Type9.Boolean({
|
|
5079
5397
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
5080
5398
|
})
|
|
5081
5399
|
),
|
|
5082
|
-
max_pages:
|
|
5083
|
-
|
|
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
|
|
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:
|
|
5160
|
-
text:
|
|
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
|
|
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:
|
|
5208
|
-
emoji:
|
|
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
|
|
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,
|
|
5482
|
-
inputSchema:
|
|
5483
|
-
title:
|
|
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:
|
|
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:
|
|
5555
|
-
markdown:
|
|
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:
|
|
5560
|
-
|
|
5877
|
+
operation: Type12.Optional(
|
|
5878
|
+
Type12.Union(
|
|
5561
5879
|
[
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
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:
|
|
5570
|
-
|
|
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:
|
|
5576
|
-
|
|
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:
|
|
5643
|
-
canvas:
|
|
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
|
|
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:
|
|
5868
|
-
name:
|
|
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:
|
|
5904
|
-
items:
|
|
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:
|
|
5910
|
-
|
|
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:
|
|
5916
|
-
|
|
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:
|
|
5966
|
-
limit:
|
|
5967
|
-
|
|
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:
|
|
6310
|
+
inputSchema: Type13.Object(
|
|
5993
6311
|
{
|
|
5994
|
-
item_id:
|
|
6312
|
+
item_id: Type13.String({
|
|
5995
6313
|
minLength: 1,
|
|
5996
6314
|
description: "ID of the Slack list item to update."
|
|
5997
6315
|
}),
|
|
5998
|
-
completed:
|
|
5999
|
-
|
|
6316
|
+
completed: Type13.Optional(
|
|
6317
|
+
Type13.Boolean({
|
|
6000
6318
|
description: "Optional completion status update."
|
|
6001
6319
|
})
|
|
6002
6320
|
),
|
|
6003
|
-
title:
|
|
6004
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
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:
|
|
6422
|
-
url:
|
|
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:
|
|
6427
|
-
|
|
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
|
|
6802
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6488
6803
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6489
|
-
var
|
|
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:
|
|
6531
|
-
query:
|
|
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:
|
|
6537
|
-
|
|
6851
|
+
max_results: Type16.Optional(
|
|
6852
|
+
Type16.Integer({
|
|
6538
6853
|
minimum: 1,
|
|
6539
|
-
maximum:
|
|
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
|
|
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:
|
|
6922
|
+
inputSchema: Type17.Object(
|
|
6608
6923
|
{
|
|
6609
|
-
path:
|
|
6924
|
+
path: Type17.String({
|
|
6610
6925
|
minLength: 1,
|
|
6611
6926
|
description: "Path to write in the sandbox workspace."
|
|
6612
6927
|
}),
|
|
6613
|
-
content:
|
|
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.
|
|
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] === "
|
|
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] === "
|
|
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":
|
|
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":
|
|
8391
|
-
"error.message":
|
|
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
|
|
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 = "
|
|
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
|
|
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
|
|
8970
|
-
"Use low for
|
|
8971
|
-
"Use medium for
|
|
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":
|
|
9047
|
-
"app.ai.thinking_level_reason":
|
|
9048
|
-
...
|
|
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
|
|
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: `
|
|
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
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
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
|
-
}
|
|
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:
|
|
10070
|
-
channelCapabilities
|
|
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
|
|
10457
|
+
const activeMcpCatalogs = toActiveMcpCatalogSummaries(
|
|
10458
|
+
turnMcpToolManager.getActiveToolCatalog(activeSkills)
|
|
10459
|
+
);
|
|
10095
10460
|
baseInstructions = buildSystemPrompt({
|
|
10096
10461
|
availableSkills,
|
|
10097
10462
|
activeSkills,
|
|
10098
|
-
|
|
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
|
-
|
|
10105
|
-
|
|
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
|
|
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) => {
|