@sentry/junior 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1467 -822
- package/dist/{chunk-ICIRAL6Y.js → chunk-3SH2A7VQ.js} +52 -106
- package/dist/cli/check.js +8 -1
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -2,9 +2,8 @@ import {
|
|
|
2
2
|
discoverSkills,
|
|
3
3
|
findSkillByName,
|
|
4
4
|
loadSkillsByName,
|
|
5
|
-
logCapabilityCatalogLoadedOnce,
|
|
6
5
|
parseSkillInvocation
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3SH2A7VQ.js";
|
|
8
7
|
import {
|
|
9
8
|
GEN_AI_PROVIDER_NAME,
|
|
10
9
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
@@ -40,6 +39,8 @@ import {
|
|
|
40
39
|
createRequestContext,
|
|
41
40
|
extractGenAiUsageSummary,
|
|
42
41
|
getActiveTraceId,
|
|
42
|
+
getPluginCapabilityProviders,
|
|
43
|
+
getPluginCatalogSignature,
|
|
43
44
|
getPluginDefinition,
|
|
44
45
|
getPluginMcpProviders,
|
|
45
46
|
getPluginOAuthConfig,
|
|
@@ -392,6 +393,26 @@ function defaultConversationState() {
|
|
|
392
393
|
}
|
|
393
394
|
};
|
|
394
395
|
}
|
|
396
|
+
function coercePendingAuthState(value) {
|
|
397
|
+
if (!isRecord(value)) {
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
const kind = value.kind;
|
|
401
|
+
const provider = toOptionalString(value.provider);
|
|
402
|
+
const requesterId = toOptionalString(value.requesterId);
|
|
403
|
+
const sessionId = toOptionalString(value.sessionId);
|
|
404
|
+
const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
|
|
405
|
+
if (kind !== "mcp" && kind !== "plugin" || !provider || !requesterId || !sessionId || typeof linkSentAtMs !== "number") {
|
|
406
|
+
return void 0;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
kind,
|
|
410
|
+
provider,
|
|
411
|
+
requesterId,
|
|
412
|
+
sessionId,
|
|
413
|
+
linkSentAtMs
|
|
414
|
+
};
|
|
415
|
+
}
|
|
395
416
|
function coerceThreadConversationState(value) {
|
|
396
417
|
if (!isRecord(value)) {
|
|
397
418
|
return defaultConversationState();
|
|
@@ -442,7 +463,8 @@ function coerceThreadConversationState(value) {
|
|
|
442
463
|
const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
|
|
443
464
|
const processing = {
|
|
444
465
|
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
445
|
-
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs)
|
|
466
|
+
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
467
|
+
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
446
468
|
};
|
|
447
469
|
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
448
470
|
const stats = {
|
|
@@ -2001,11 +2023,13 @@ function buildThreadParticipants(messages) {
|
|
|
2001
2023
|
return participants;
|
|
2002
2024
|
}
|
|
2003
2025
|
|
|
2004
|
-
// src/chat/
|
|
2026
|
+
// src/chat/state/turn-id.ts
|
|
2005
2027
|
function buildDeterministicTurnId(messageId) {
|
|
2006
2028
|
const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2007
2029
|
return `turn_${sanitized}`;
|
|
2008
2030
|
}
|
|
2031
|
+
|
|
2032
|
+
// src/chat/runtime/turn.ts
|
|
2009
2033
|
var RetryableTurnError = class extends Error {
|
|
2010
2034
|
code = "retryable_turn";
|
|
2011
2035
|
metadata;
|
|
@@ -2031,12 +2055,16 @@ function startActiveTurn(args) {
|
|
|
2031
2055
|
args.updateConversationStats(args.conversation);
|
|
2032
2056
|
}
|
|
2033
2057
|
function markTurnCompleted(args) {
|
|
2034
|
-
args.conversation.processing.activeTurnId
|
|
2058
|
+
if (!args.sessionId || args.conversation.processing.activeTurnId === args.sessionId) {
|
|
2059
|
+
args.conversation.processing.activeTurnId = void 0;
|
|
2060
|
+
}
|
|
2035
2061
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2036
2062
|
args.updateConversationStats(args.conversation);
|
|
2037
2063
|
}
|
|
2038
2064
|
function markTurnFailed(args) {
|
|
2039
|
-
args.conversation.processing.activeTurnId
|
|
2065
|
+
if (!args.sessionId || args.conversation.processing.activeTurnId === args.sessionId) {
|
|
2066
|
+
args.conversation.processing.activeTurnId = void 0;
|
|
2067
|
+
}
|
|
2040
2068
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2041
2069
|
args.markConversationMessage(args.conversation, args.userMessageId, {
|
|
2042
2070
|
replied: false,
|
|
@@ -2856,18 +2884,21 @@ function formatConfigurationValue(value) {
|
|
|
2856
2884
|
function renderIdentityBlock(tag, fields) {
|
|
2857
2885
|
const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
|
|
2858
2886
|
if (lines.length === 0) {
|
|
2859
|
-
return [`<${tag}>`, "none", `</${tag}>`]
|
|
2887
|
+
return [`<${tag}>`, "none", `</${tag}>`];
|
|
2860
2888
|
}
|
|
2861
|
-
return [`<${tag}>`, ...lines, `</${tag}>`]
|
|
2889
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
2890
|
+
}
|
|
2891
|
+
function renderTag(tag, lines) {
|
|
2892
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
2862
2893
|
}
|
|
2863
|
-
function
|
|
2894
|
+
function renderTagBlock(tag, content) {
|
|
2864
2895
|
return [`<${tag}>`, content, `</${tag}>`].join("\n");
|
|
2865
2896
|
}
|
|
2866
2897
|
function formatAvailableSkillsForPrompt(skills) {
|
|
2867
2898
|
if (skills.length === 0) {
|
|
2868
|
-
return "<
|
|
2899
|
+
return "<available-skills>\n</available-skills>";
|
|
2869
2900
|
}
|
|
2870
|
-
const lines = ["<
|
|
2901
|
+
const lines = ["<available-skills>"];
|
|
2871
2902
|
for (const skill of skills) {
|
|
2872
2903
|
const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
|
|
2873
2904
|
lines.push(" <skill>");
|
|
@@ -2879,45 +2910,37 @@ function formatAvailableSkillsForPrompt(skills) {
|
|
|
2879
2910
|
if (skill.pluginProvider) {
|
|
2880
2911
|
lines.push(` <provider>${escapeXml(skill.pluginProvider)}</provider>`);
|
|
2881
2912
|
}
|
|
2882
|
-
if (skill.usesConfig && skill.usesConfig.length > 0) {
|
|
2883
|
-
lines.push(
|
|
2884
|
-
` <uses_config>${escapeXml(skill.usesConfig.join(" "))}</uses_config>`
|
|
2885
|
-
);
|
|
2886
|
-
}
|
|
2887
2913
|
lines.push(" </skill>");
|
|
2888
2914
|
}
|
|
2889
|
-
lines.push("</
|
|
2915
|
+
lines.push("</available-skills>");
|
|
2890
2916
|
return lines.join("\n");
|
|
2891
2917
|
}
|
|
2892
2918
|
function formatLoadedSkillsForPrompt(skills) {
|
|
2893
2919
|
if (skills.length === 0) {
|
|
2894
|
-
return "<
|
|
2920
|
+
return "<loaded-skills>\n</loaded-skills>";
|
|
2895
2921
|
}
|
|
2896
|
-
const lines = ["<
|
|
2922
|
+
const lines = ["<loaded-skills>"];
|
|
2897
2923
|
for (const skill of skills) {
|
|
2898
2924
|
const skillDir = workspaceSkillDir(skill.name);
|
|
2899
2925
|
lines.push(
|
|
2900
2926
|
` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
|
|
2901
2927
|
);
|
|
2902
2928
|
lines.push(`References are relative to ${escapeXml(skillDir)}.`);
|
|
2903
|
-
if (skill.usesConfig && skill.usesConfig.length > 0) {
|
|
2904
|
-
lines.push(
|
|
2905
|
-
`Uses config keys: ${escapeXml(skill.usesConfig.join(", "))}.`
|
|
2906
|
-
);
|
|
2907
|
-
}
|
|
2908
2929
|
lines.push("");
|
|
2909
2930
|
lines.push(skill.body);
|
|
2910
2931
|
lines.push(" </skill>");
|
|
2911
2932
|
}
|
|
2912
|
-
lines.push("</
|
|
2933
|
+
lines.push("</loaded-skills>");
|
|
2913
2934
|
return lines.join("\n");
|
|
2914
2935
|
}
|
|
2915
2936
|
function formatProviderCatalogForPrompt() {
|
|
2916
2937
|
const providers = getPluginProviders().map((plugin) => plugin.manifest);
|
|
2917
2938
|
if (providers.length === 0) {
|
|
2918
|
-
return
|
|
2939
|
+
return null;
|
|
2919
2940
|
}
|
|
2920
|
-
const lines = [
|
|
2941
|
+
const lines = [
|
|
2942
|
+
"Config keys and default targets per provider; use after a skill is loaded."
|
|
2943
|
+
];
|
|
2921
2944
|
for (const provider of providers) {
|
|
2922
2945
|
lines.push(`- provider: ${escapeXml(provider.name)}`);
|
|
2923
2946
|
lines.push(
|
|
@@ -2931,288 +2954,260 @@ function formatProviderCatalogForPrompt() {
|
|
|
2931
2954
|
}
|
|
2932
2955
|
return lines.join("\n");
|
|
2933
2956
|
}
|
|
2934
|
-
function
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
"
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
"- When a loaded skill exposes MCP capabilities, those tools are registered as callable tools. Call them directly by name.",
|
|
2951
|
-
"- Use `searchTools` only when you need to rediscover or filter active MCP tools.",
|
|
2952
|
-
"- Never guess. If you cannot verify with available sources, say it is unverified.",
|
|
2953
|
-
"- Never claim a lookup succeeded unless a tool result supports it.",
|
|
2954
|
-
"- Do not give up when unsure how to do something; find a viable path, gather evidence, and provide the best actionable way forward.",
|
|
2955
|
-
"- When active skills are present, follow their instructions before default behavior."
|
|
2956
|
-
].join("\n");
|
|
2957
|
+
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
2958
|
+
if (catalogs.length === 0) {
|
|
2959
|
+
return null;
|
|
2960
|
+
}
|
|
2961
|
+
const lines = [
|
|
2962
|
+
"Active MCP provider catalogs are available through `searchMcpTools`. Call it with provider to list descriptors or with query to narrow results, then pass the exact returned `tool_name` to `callMcpTool`."
|
|
2963
|
+
];
|
|
2964
|
+
for (const catalog of catalogs) {
|
|
2965
|
+
lines.push(" <catalog>");
|
|
2966
|
+
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
2967
|
+
lines.push(
|
|
2968
|
+
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
2969
|
+
);
|
|
2970
|
+
lines.push(" </catalog>");
|
|
2971
|
+
}
|
|
2972
|
+
return lines.join("\n");
|
|
2957
2973
|
}
|
|
2958
|
-
function
|
|
2974
|
+
function formatReferenceFilesLines() {
|
|
2959
2975
|
const files = listReferenceFiles();
|
|
2960
2976
|
if (files.length === 0) {
|
|
2961
|
-
return
|
|
2977
|
+
return null;
|
|
2962
2978
|
}
|
|
2963
|
-
|
|
2979
|
+
return files.map((filePath) => {
|
|
2964
2980
|
const name = path2.basename(filePath);
|
|
2965
2981
|
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
2966
2982
|
});
|
|
2967
|
-
return [
|
|
2968
|
-
renderTag(
|
|
2969
|
-
"reference-files",
|
|
2970
|
-
[
|
|
2971
|
-
"Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
|
|
2972
|
-
...fileNames
|
|
2973
|
-
].join("\n")
|
|
2974
|
-
)
|
|
2975
|
-
];
|
|
2976
2983
|
}
|
|
2977
|
-
function
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
}
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
const activeSkillsSection = [
|
|
3008
|
-
"Loaded skills for this turn:",
|
|
3009
|
-
formatLoadedSkillsForPrompt(activeSkills)
|
|
3010
|
-
].join("\n");
|
|
3011
|
-
const activeToolNames = (activeTools ?? []).map((tool2) => tool2.tool_name);
|
|
3012
|
-
const activeToolsSection = activeToolNames.length > 0 ? `Active MCP tools registered for this turn: ${activeToolNames.join(", ")}. Call them directly by name.` : "";
|
|
3013
|
-
const configurationKeys = Object.keys(configuration ?? {}).sort(
|
|
2984
|
+
function formatArtifactsLines(artifactState) {
|
|
2985
|
+
if (!artifactState) return null;
|
|
2986
|
+
const lines = [];
|
|
2987
|
+
if (artifactState.lastCanvasId) {
|
|
2988
|
+
lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
|
|
2989
|
+
}
|
|
2990
|
+
if (artifactState.lastCanvasUrl) {
|
|
2991
|
+
lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
|
|
2992
|
+
}
|
|
2993
|
+
if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
|
|
2994
|
+
lines.push("- recent_canvases:");
|
|
2995
|
+
for (const canvas of artifactState.recentCanvases) {
|
|
2996
|
+
lines.push(` - id: ${escapeXml(canvas.id)}`);
|
|
2997
|
+
if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
|
|
2998
|
+
if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
|
|
2999
|
+
if (canvas.createdAt) {
|
|
3000
|
+
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
if (artifactState.lastListId) {
|
|
3005
|
+
lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
|
|
3006
|
+
}
|
|
3007
|
+
if (artifactState.lastListUrl) {
|
|
3008
|
+
lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
|
|
3009
|
+
}
|
|
3010
|
+
return lines.length > 0 ? lines : null;
|
|
3011
|
+
}
|
|
3012
|
+
function formatConfigurationLines(configuration) {
|
|
3013
|
+
const keys = Object.keys(configuration ?? {}).sort(
|
|
3014
3014
|
(a, b) => a.localeCompare(b)
|
|
3015
3015
|
);
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3016
|
+
if (keys.length === 0) return null;
|
|
3017
|
+
return keys.map(
|
|
3018
|
+
(key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
function formatThreadParticipantsLines(participants) {
|
|
3022
|
+
if (!participants || participants.length === 0) return null;
|
|
3023
|
+
return participants.map((p) => {
|
|
3024
|
+
const parts = [];
|
|
3025
|
+
if (p.userId) {
|
|
3026
|
+
parts.push(`user_id: ${escapeXml(p.userId)}`);
|
|
3027
|
+
parts.push(`slack_mention: <@${p.userId}>`);
|
|
3028
|
+
}
|
|
3029
|
+
if (p.userName) parts.push(`user_name: ${escapeXml(p.userName)}`);
|
|
3030
|
+
if (p.fullName) parts.push(`full_name: ${escapeXml(p.fullName)}`);
|
|
3031
|
+
return `- ${parts.join(", ")}`;
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
var HEADER = "You are a Slack-based helper assistant. The behavior and output blocks below are authoritative; the personality block sets voice only.";
|
|
3035
|
+
var BEHAVIOR_RULES = [
|
|
3036
|
+
"- Load the best-matching skill/tool when relevant, then use it before answering; do not preload multiple skills or claim tool use that did not happen.",
|
|
3037
|
+
"- After `loadSkill`, resolve references under `skill_dir`; for active MCP catalogs, use `searchMcpTools` then `callMcpTool` with exact returned tool names.",
|
|
3038
|
+
"- Default to acting in-turn: use relevant available skills/tools to satisfy the request, continue until done or blocked, and only ask the user when access or required input is missing. If a fact cannot be verified, say so.",
|
|
3039
|
+
"- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
|
|
3040
|
+
"- Keep work silent and post one result-focused reply unless blocked or waiting on user input; do not use reactions as progress.",
|
|
3041
|
+
"- Do not claim an attachment, canvas, or channel post succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
|
|
3042
|
+
"- Run authenticated provider commands directly; resolve target defaults first and let the runtime handle auth pauses/resumes.",
|
|
3043
|
+
"- On resumed turns, post a brief continuation notice, then the resumed answer as a separate message.",
|
|
3044
|
+
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
3045
|
+
"- Run `jr-rpc config get|set|unset|list` as standalone bash commands for conversation-scoped provider defaults.",
|
|
3046
|
+
"- For explicit channel-post or emoji-reaction requests, skip the text reply."
|
|
3047
|
+
];
|
|
3048
|
+
function buildOutputSection() {
|
|
3049
|
+
const openTag = `<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
|
|
3050
|
+
return [
|
|
3051
|
+
openTag,
|
|
3052
|
+
"- Use Slack-friendly mrkdwn: bolded section labels instead of headings, no markdown tables or markdown links, and plain URLs.",
|
|
3053
|
+
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
3054
|
+
"- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to a short summary plus the canvas link.",
|
|
3055
|
+
"- End every turn with a final user-facing markdown response.",
|
|
3056
|
+
"</output>"
|
|
3057
|
+
].join("\n");
|
|
3058
|
+
}
|
|
3059
|
+
function buildContextSection(params) {
|
|
3060
|
+
const blocks = [];
|
|
3061
|
+
if (JUNIOR_WORLD) {
|
|
3062
|
+
blocks.push(renderTag("world", [JUNIOR_WORLD.trim()]));
|
|
3063
|
+
}
|
|
3064
|
+
const referenceLines = formatReferenceFilesLines();
|
|
3065
|
+
if (referenceLines) {
|
|
3066
|
+
blocks.push(
|
|
3067
|
+
renderTag("reference-files", [
|
|
3068
|
+
"Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
|
|
3069
|
+
...referenceLines
|
|
3070
|
+
])
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
const runtimeVersion = getRuntimeMetadata().version;
|
|
3074
|
+
if (runtimeVersion) {
|
|
3075
|
+
blocks.push([`<runtime version="${escapeXml(runtimeVersion)}" />`]);
|
|
3076
|
+
}
|
|
3077
|
+
blocks.push(
|
|
3078
|
+
renderIdentityBlock("assistant", {
|
|
3079
|
+
user_name: params.assistant?.userName ?? botConfig.userName,
|
|
3080
|
+
user_id: params.assistant?.userId
|
|
3081
|
+
})
|
|
3020
3082
|
);
|
|
3021
|
-
|
|
3022
|
-
(
|
|
3083
|
+
blocks.push(
|
|
3084
|
+
renderIdentityBlock("requester", {
|
|
3085
|
+
full_name: params.requester?.fullName,
|
|
3086
|
+
user_name: params.requester?.userName,
|
|
3087
|
+
user_id: params.requester?.userId
|
|
3088
|
+
})
|
|
3023
3089
|
);
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3090
|
+
const participantLines = formatThreadParticipantsLines(
|
|
3091
|
+
params.threadParticipants
|
|
3026
3092
|
);
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3093
|
+
if (participantLines) {
|
|
3094
|
+
blocks.push(
|
|
3095
|
+
renderTag("thread-participants", [
|
|
3096
|
+
"Known participants. When you mention one of these people, use the provided `<@USERID>` token exactly; do not write a bare `@name`.",
|
|
3097
|
+
...participantLines
|
|
3098
|
+
])
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
3102
|
+
if (artifactLines) {
|
|
3103
|
+
blocks.push(renderTag("artifacts", artifactLines));
|
|
3104
|
+
}
|
|
3105
|
+
const configLines = formatConfigurationLines(params.configuration);
|
|
3106
|
+
if (configLines) {
|
|
3107
|
+
blocks.push(
|
|
3108
|
+
renderTag("configuration", [
|
|
3109
|
+
"Conversation-scoped defaults. Follow explicit user input when it conflicts.",
|
|
3110
|
+
...configLines
|
|
3111
|
+
])
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
if (params.turnState === "resumed") {
|
|
3115
|
+
blocks.push([
|
|
3116
|
+
"<turn-state>resumed</turn-state>",
|
|
3117
|
+
"This turn continues from a prior checkpoint. Prior tool results and assistant messages are already in the conversation history."
|
|
3118
|
+
]);
|
|
3119
|
+
}
|
|
3120
|
+
if (params.invocation) {
|
|
3121
|
+
blocks.push([
|
|
3122
|
+
`<explicit-skill-trigger>/${escapeXml(params.invocation.skillName)}</explicit-skill-trigger>`
|
|
3123
|
+
]);
|
|
3124
|
+
}
|
|
3125
|
+
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
3126
|
+
return renderTagBlock("context", body);
|
|
3127
|
+
}
|
|
3128
|
+
function buildCapabilitiesSection(params) {
|
|
3129
|
+
const blocks = [];
|
|
3130
|
+
blocks.push(formatAvailableSkillsForPrompt(params.availableSkills));
|
|
3131
|
+
blocks.push(formatLoadedSkillsForPrompt(params.activeSkills));
|
|
3132
|
+
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
3133
|
+
params.activeMcpCatalogs
|
|
3134
|
+
);
|
|
3135
|
+
if (activeCatalogs) {
|
|
3136
|
+
blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
|
|
3137
|
+
}
|
|
3138
|
+
const providerCatalog = formatProviderCatalogForPrompt();
|
|
3139
|
+
if (providerCatalog) {
|
|
3140
|
+
blocks.push(renderTagBlock("providers", providerCatalog));
|
|
3141
|
+
}
|
|
3142
|
+
return renderTagBlock("capabilities", blocks.join("\n\n"));
|
|
3143
|
+
}
|
|
3144
|
+
function buildSystemPrompt(params) {
|
|
3035
3145
|
const sections = [
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
] : [],
|
|
3055
|
-
...formatReferenceFilesSection(),
|
|
3056
|
-
renderTag(
|
|
3057
|
-
"identity-context",
|
|
3058
|
-
[
|
|
3059
|
-
"Use these blocks as authoritative metadata for identity questions.",
|
|
3060
|
-
assistantSection,
|
|
3061
|
-
requesterSection,
|
|
3062
|
-
...threadParticipants && threadParticipants.length > 0 ? [
|
|
3063
|
-
renderTag(
|
|
3064
|
-
"thread-participants",
|
|
3065
|
-
[
|
|
3066
|
-
"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.",
|
|
3067
|
-
...threadParticipants.map((p) => {
|
|
3068
|
-
const parts = [];
|
|
3069
|
-
if (p.userId) {
|
|
3070
|
-
parts.push(`user_id: ${escapeXml(p.userId)}`);
|
|
3071
|
-
parts.push(`slack_mention: <@${p.userId}>`);
|
|
3072
|
-
}
|
|
3073
|
-
if (p.userName)
|
|
3074
|
-
parts.push(`user_name: ${escapeXml(p.userName)}`);
|
|
3075
|
-
if (p.fullName)
|
|
3076
|
-
parts.push(`full_name: ${escapeXml(p.fullName)}`);
|
|
3077
|
-
return `- ${parts.join(", ")}`;
|
|
3078
|
-
})
|
|
3079
|
-
].join("\n")
|
|
3080
|
-
)
|
|
3081
|
-
] : []
|
|
3082
|
-
].join("\n")
|
|
3083
|
-
),
|
|
3084
|
-
renderTag(
|
|
3085
|
-
"artifact-context",
|
|
3086
|
-
[
|
|
3087
|
-
"Use this thread-scoped memory for follow-up updates to existing Slack artifacts.",
|
|
3088
|
-
artifactState ? [
|
|
3089
|
-
artifactState.lastCanvasId ? `- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}` : "- last_canvas_id: none",
|
|
3090
|
-
artifactState.lastCanvasUrl ? `- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}` : "- last_canvas_url: none",
|
|
3091
|
-
artifactState.recentCanvases && artifactState.recentCanvases.length > 0 ? [
|
|
3092
|
-
"- recent_canvases:",
|
|
3093
|
-
...artifactState.recentCanvases.map(
|
|
3094
|
-
(canvas) => [
|
|
3095
|
-
` - id: ${escapeXml(canvas.id)}`,
|
|
3096
|
-
canvas.title ? ` title: ${escapeXml(canvas.title)}` : " title: [unknown]",
|
|
3097
|
-
canvas.url ? ` url: ${escapeXml(canvas.url)}` : " url: [unknown]",
|
|
3098
|
-
canvas.createdAt ? ` created_at: ${escapeXml(canvas.createdAt)}` : " created_at: [unknown]"
|
|
3099
|
-
].join("\n")
|
|
3100
|
-
)
|
|
3101
|
-
].join("\n") : "- recent_canvases: none",
|
|
3102
|
-
artifactState.lastListId ? `- last_list_id: ${escapeXml(artifactState.lastListId)}` : "- last_list_id: none",
|
|
3103
|
-
artifactState.lastListUrl ? `- last_list_url: ${escapeXml(artifactState.lastListUrl)}` : "- last_list_url: none"
|
|
3104
|
-
].join("\n") : "- none"
|
|
3105
|
-
].join("\n")
|
|
3106
|
-
),
|
|
3107
|
-
renderTag("configuration-context", configurationSection),
|
|
3108
|
-
renderTag(
|
|
3109
|
-
"runtime-metadata",
|
|
3110
|
-
[
|
|
3111
|
-
"Use this for runtime version questions about the deployed assistant.",
|
|
3112
|
-
`- version: ${escapeXml(runtimeMetadata?.version ?? "unknown")}`
|
|
3113
|
-
].join("\n")
|
|
3114
|
-
),
|
|
3115
|
-
renderTag(
|
|
3116
|
-
"provider-config",
|
|
3117
|
-
[
|
|
3118
|
-
"Use this catalog to map already-chosen provider work to valid config keys and provider defaults.",
|
|
3119
|
-
"Do not use this catalog by itself to decide which domain skill matches an operational task.",
|
|
3120
|
-
"When user intent is to set a provider default, choose a config key from this catalog and use jr-rpc config set.",
|
|
3121
|
-
"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.",
|
|
3122
|
-
formatProviderCatalogForPrompt()
|
|
3123
|
-
].join("\n")
|
|
3124
|
-
),
|
|
3125
|
-
renderTag(
|
|
3126
|
-
"skill-routing",
|
|
3127
|
-
[
|
|
3128
|
-
"- Choose the skill that matches the requested operation, not incidental nouns, product names, organization names, or channel context.",
|
|
3129
|
-
"- When multiple skills seem adjacent, prefer the one whose description matches the user's requested action most directly.",
|
|
3130
|
-
"- If the task needs evidence from a specialized external system or workflow, load the matching skill before drawing conclusions.",
|
|
3131
|
-
"- The provider-config catalog is for exact config keys and provider defaults after skill selection. It is not a shortcut for choosing a domain.",
|
|
3132
|
-
"- 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."
|
|
3133
|
-
].join("\n")
|
|
3134
|
-
),
|
|
3135
|
-
renderTag(
|
|
3136
|
-
"tool-usage",
|
|
3137
|
-
[
|
|
3138
|
-
"- For factual or external questions, run tools/skills first, then answer from evidence.",
|
|
3139
|
-
"- Use tool descriptions as the source of truth for when each tool should or should not be called.",
|
|
3140
|
-
"- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
|
|
3141
|
-
"- 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.",
|
|
3142
|
-
"- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
|
|
3143
|
-
"- 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.",
|
|
3144
|
-
"- 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.",
|
|
3145
|
-
"- For evidence-gathering tasks, never state a factual conclusion before you have actually gathered and checked the sources.",
|
|
3146
|
-
"- 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.",
|
|
3147
|
-
"- 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.",
|
|
3148
|
-
"- 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.",
|
|
3149
|
-
"- Never claim a screenshot/file is attached unless `attachFile` succeeded in this turn.",
|
|
3150
|
-
"- If `attachFile` fails, explain the failure and do not say the file was shared.",
|
|
3151
|
-
"- 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.",
|
|
3152
|
-
"- For explicit in-channel post requests, prefer no thread text reply after a successful channel post. A reaction-only acknowledgment is acceptable when useful.",
|
|
3153
|
-
"- When the user explicitly asks for an emoji reaction instead of text, react and skip the text reply.",
|
|
3154
|
-
"- After the matching plugin-owned skill is loaded, authenticated bash commands for that skill get provider credentials injected automatically for the current turn.",
|
|
3155
|
-
"- Resolve repo/project/org defaults before authenticated provider commands so the runtime can narrow injected credentials correctly.",
|
|
3156
|
-
"- If no loaded skill clearly owns the authenticated command, load the matching skill first instead of guessing from the provider catalog.",
|
|
3157
|
-
"- If provider authorization is required, the runtime sends the private authorization link itself and resumes the paused turn after authorization.",
|
|
3158
|
-
"- Do not try to manage provider auth directly. Run the real provider command and let the runtime handle authorization, reconnect, and resume behavior.",
|
|
3159
|
-
"- Provider-targeted commands may need `--target <value>` or a provider-specific configured default target key when the provider catalog shows one.",
|
|
3160
|
-
"- 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.",
|
|
3161
|
-
"- `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.",
|
|
3162
|
-
"- When your work is complete, provide the exact user-facing markdown response.",
|
|
3163
|
-
"- Do not use reaction-based progress signals; Assistants API status already covers in-progress UX.",
|
|
3164
|
-
"- Never call side-effecting tools when the user only asked for analysis or options.",
|
|
3165
|
-
"- 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."
|
|
3166
|
-
].join("\n")
|
|
3167
|
-
),
|
|
3168
|
-
renderTag(
|
|
3169
|
-
"skills",
|
|
3170
|
-
[
|
|
3171
|
-
"- Explicit skill triggers may appear as `/skillname`.",
|
|
3172
|
-
"- If explicitly invoked skill instructions are already present in <loaded_skills>, apply them immediately.",
|
|
3173
|
-
"- If an explicitly invoked skill is present in <loaded_skills>, never say the skill is unavailable, missing, or unsupported in this environment.",
|
|
3174
|
-
"- Otherwise, for an explicitly invoked skill, call `loadSkill` for that exact skill before applying skill-specific behavior.",
|
|
3175
|
-
"- For requests without an explicit trigger where a skill clearly matches, call `loadSkill` before applying skill-specific behavior.",
|
|
3176
|
-
"- When multiple skills appear relevant, prefer the skill whose description best matches the requested action rather than incidental domain nouns in the prompt.",
|
|
3177
|
-
"- 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.",
|
|
3178
|
-
"- Do not claim to have used a skill unless it is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
|
|
3179
|
-
"- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
|
|
3180
|
-
"- Load only the best matching skill first; do not load multiple skills upfront.",
|
|
3181
|
-
"- After `loadSkill`, use `skill_dir` as the root for any referenced files you read via `bash`.",
|
|
3182
|
-
"- 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`).",
|
|
3183
|
-
"- If no skill is a clear fit, continue with normal tool usage."
|
|
3184
|
-
].join("\n")
|
|
3185
|
-
),
|
|
3186
|
-
renderTag(
|
|
3187
|
-
"output-contract",
|
|
3188
|
-
[
|
|
3189
|
-
"Always produce output that follows this contract:",
|
|
3190
|
-
`<output format="slack-mrkdwn" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`,
|
|
3191
|
-
"- Use Slack-friendly markdown, not full CommonMark. Prefer bold section labels over markdown headings, and use bullets and short code blocks when helpful.",
|
|
3192
|
-
"- Keep normal responses brief and scannable.",
|
|
3193
|
-
"- If depth is needed, start with a concise summary and then provide fuller detail.",
|
|
3194
|
-
"- Prefer a single compact thread reply when the full answer comfortably fits within this inline budget.",
|
|
3195
|
-
"- 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.",
|
|
3196
|
-
"- Typical canvas-first cases include long-form research summaries, timelines, bios or profiles, structured notes, plans, comparisons, and other reusable reference documents.",
|
|
3197
|
-
"- Do not create a canvas for short factual answers that fit cleanly in one normal thread reply.",
|
|
3198
|
-
"- 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.",
|
|
3199
|
-
"- Do not narrate tool execution or repeated status updates in the visible reply.",
|
|
3200
|
-
"- Avoid tables and markdown links like `[label](url)` unless explicitly requested. Prefer plain URLs or Slack-native entities when exact rendering matters.",
|
|
3201
|
-
"- End every turn with a final user-facing markdown response.",
|
|
3202
|
-
"</output>"
|
|
3203
|
-
].join("\n")
|
|
3204
|
-
),
|
|
3205
|
-
availableSkillsSection,
|
|
3206
|
-
activeSkillsSection,
|
|
3207
|
-
...activeToolsSection ? [activeToolsSection] : [],
|
|
3208
|
-
renderTag(
|
|
3209
|
-
"invocation-context",
|
|
3210
|
-
invocation ? `Explicit skill trigger detected: /${invocation.skillName}` : "No explicit skill trigger detected."
|
|
3211
|
-
)
|
|
3146
|
+
HEADER,
|
|
3147
|
+
renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
|
|
3148
|
+
buildContextSection({
|
|
3149
|
+
assistant: params.assistant,
|
|
3150
|
+
requester: params.requester,
|
|
3151
|
+
artifactState: params.artifactState,
|
|
3152
|
+
configuration: params.configuration,
|
|
3153
|
+
threadParticipants: params.threadParticipants,
|
|
3154
|
+
invocation: params.invocation,
|
|
3155
|
+
turnState: params.turnState
|
|
3156
|
+
}),
|
|
3157
|
+
buildCapabilitiesSection({
|
|
3158
|
+
availableSkills: params.availableSkills,
|
|
3159
|
+
activeSkills: params.activeSkills,
|
|
3160
|
+
activeMcpCatalogs: params.activeMcpCatalogs ?? []
|
|
3161
|
+
}),
|
|
3162
|
+
renderTagBlock("behavior", BEHAVIOR_RULES.join("\n")),
|
|
3163
|
+
buildOutputSection()
|
|
3212
3164
|
];
|
|
3213
3165
|
return sections.join("\n\n");
|
|
3214
3166
|
}
|
|
3215
3167
|
|
|
3168
|
+
// src/chat/capabilities/catalog.ts
|
|
3169
|
+
var cachedCatalog;
|
|
3170
|
+
function getCapabilityCatalog() {
|
|
3171
|
+
const signature = getPluginCatalogSignature();
|
|
3172
|
+
if (cachedCatalog?.signature === signature) return cachedCatalog;
|
|
3173
|
+
const providers = getPluginCapabilityProviders();
|
|
3174
|
+
const capabilityToProvider = /* @__PURE__ */ new Map();
|
|
3175
|
+
for (const provider of providers) {
|
|
3176
|
+
for (const capability of provider.capabilities) {
|
|
3177
|
+
if (capabilityToProvider.has(capability)) {
|
|
3178
|
+
throw new Error(
|
|
3179
|
+
`Duplicate capability registration for "${capability}"`
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
capabilityToProvider.set(capability, provider);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
cachedCatalog = { signature, providers, capabilityToProvider };
|
|
3186
|
+
return cachedCatalog;
|
|
3187
|
+
}
|
|
3188
|
+
var catalogLogged = false;
|
|
3189
|
+
function logCapabilityCatalogLoadedOnce() {
|
|
3190
|
+
if (catalogLogged) return;
|
|
3191
|
+
catalogLogged = true;
|
|
3192
|
+
const { providers } = getCapabilityCatalog();
|
|
3193
|
+
const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
|
|
3194
|
+
const configKeys = [
|
|
3195
|
+
...new Set(providers.flatMap((p) => p.configKeys))
|
|
3196
|
+
].sort();
|
|
3197
|
+
logInfo(
|
|
3198
|
+
"capability_catalog_loaded",
|
|
3199
|
+
{},
|
|
3200
|
+
{
|
|
3201
|
+
"app.capability.providers": providers.map((p) => p.provider),
|
|
3202
|
+
"app.capability.count": capabilityNames.length,
|
|
3203
|
+
"app.capability.names": capabilityNames,
|
|
3204
|
+
"app.config.key_count": configKeys.length,
|
|
3205
|
+
"app.config.keys": configKeys
|
|
3206
|
+
},
|
|
3207
|
+
"Loaded capability provider catalog"
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3216
3211
|
// src/chat/capabilities/router.ts
|
|
3217
3212
|
var ProviderCredentialRouter = class {
|
|
3218
3213
|
brokersByProvider;
|
|
@@ -3938,6 +3933,27 @@ var MCP_CLIENT_INFO = {
|
|
|
3938
3933
|
name: "junior-mcp-client",
|
|
3939
3934
|
version: "1.0.0"
|
|
3940
3935
|
};
|
|
3936
|
+
var MCP_TOOLS_CALL_METHOD = "tools/call";
|
|
3937
|
+
function getDefaultPort(url) {
|
|
3938
|
+
if (url.protocol === "https:") {
|
|
3939
|
+
return 443;
|
|
3940
|
+
}
|
|
3941
|
+
if (url.protocol === "http:") {
|
|
3942
|
+
return 80;
|
|
3943
|
+
}
|
|
3944
|
+
return void 0;
|
|
3945
|
+
}
|
|
3946
|
+
function getMcpNetworkAttributes(url) {
|
|
3947
|
+
const port = url.port ? Number(url.port) : getDefaultPort(url);
|
|
3948
|
+
return {
|
|
3949
|
+
"server.address": url.hostname,
|
|
3950
|
+
...port !== void 0 ? { "server.port": port } : {},
|
|
3951
|
+
...url.protocol === "http:" || url.protocol === "https:" ? {
|
|
3952
|
+
"network.protocol.name": "http",
|
|
3953
|
+
"network.transport": "tcp"
|
|
3954
|
+
} : {}
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3941
3957
|
var McpAuthorizationRequiredError = class extends Error {
|
|
3942
3958
|
provider;
|
|
3943
3959
|
constructor(provider, message) {
|
|
@@ -3987,10 +4003,22 @@ var PluginMcpClient = class {
|
|
|
3987
4003
|
async callTool(name, args) {
|
|
3988
4004
|
return await this.withSessionRecovery(async () => {
|
|
3989
4005
|
const client2 = await this.getClient();
|
|
4006
|
+
const mcp = this.plugin.manifest.mcp;
|
|
4007
|
+
if (mcp) {
|
|
4008
|
+
const url = new URL(mcp.url);
|
|
4009
|
+
setSpanAttributes({
|
|
4010
|
+
"mcp.method.name": MCP_TOOLS_CALL_METHOD,
|
|
4011
|
+
"gen_ai.operation.name": "execute_tool",
|
|
4012
|
+
"gen_ai.tool.name": name,
|
|
4013
|
+
...this.transport?.sessionId ? { "mcp.session.id": this.transport.sessionId } : {},
|
|
4014
|
+
...this.transport?.protocolVersion ? { "mcp.protocol.version": this.transport.protocolVersion } : {},
|
|
4015
|
+
...getMcpNetworkAttributes(url)
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
3990
4018
|
const result = await this.wrapAuth(
|
|
3991
4019
|
client2.callTool({
|
|
3992
4020
|
name,
|
|
3993
|
-
|
|
4021
|
+
arguments: args ?? {}
|
|
3994
4022
|
})
|
|
3995
4023
|
);
|
|
3996
4024
|
await this.syncTransportSessionId();
|
|
@@ -4103,13 +4131,24 @@ var PluginMcpClient = class {
|
|
|
4103
4131
|
}
|
|
4104
4132
|
};
|
|
4105
4133
|
|
|
4106
|
-
// src/chat/mcp/
|
|
4134
|
+
// src/chat/mcp/errors.ts
|
|
4107
4135
|
var McpToolError = class extends Error {
|
|
4108
4136
|
constructor(message) {
|
|
4109
4137
|
super(message);
|
|
4110
4138
|
this.name = "McpToolError";
|
|
4111
4139
|
}
|
|
4112
4140
|
};
|
|
4141
|
+
function getMcpAwareErrorType(error, fallback) {
|
|
4142
|
+
if (error instanceof McpToolError) {
|
|
4143
|
+
return "tool_error";
|
|
4144
|
+
}
|
|
4145
|
+
return error instanceof Error ? error.name : fallback;
|
|
4146
|
+
}
|
|
4147
|
+
function getMcpAwareErrorMessage(error) {
|
|
4148
|
+
return error instanceof Error ? error.message : String(error);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
// src/chat/mcp/tool-manager.ts
|
|
4113
4152
|
function normalizeMcpToolName(provider, toolName) {
|
|
4114
4153
|
return `mcp__${provider}__${toolName}`;
|
|
4115
4154
|
}
|
|
@@ -4275,24 +4314,6 @@ var McpToolManager = class {
|
|
|
4275
4314
|
(tool2) => this.toToolDescriptor(tool2)
|
|
4276
4315
|
);
|
|
4277
4316
|
}
|
|
4278
|
-
searchTools(skills, query, options = {}) {
|
|
4279
|
-
const resolved = this.getResolvedActiveTools(skills, options);
|
|
4280
|
-
const trimmedQuery = query.trim();
|
|
4281
|
-
if (!trimmedQuery || trimmedQuery === "*") {
|
|
4282
|
-
return resolved.slice(0, Math.max(1, options.limit ?? 8)).map((tool2) => this.toToolDescriptor(tool2));
|
|
4283
|
-
}
|
|
4284
|
-
const normalizedQuery = trimmedQuery.toLowerCase();
|
|
4285
|
-
const queryTokens = normalizedQuery.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
4286
|
-
return resolved.map((tool2) => ({
|
|
4287
|
-
tool: tool2,
|
|
4288
|
-
score: this.scoreToolMatch(tool2, normalizedQuery, queryTokens)
|
|
4289
|
-
})).filter((entry) => entry.score > 0).sort((left, right) => {
|
|
4290
|
-
if (right.score !== left.score) {
|
|
4291
|
-
return right.score - left.score;
|
|
4292
|
-
}
|
|
4293
|
-
return left.tool.name.localeCompare(right.tool.name);
|
|
4294
|
-
}).slice(0, Math.max(1, options.limit ?? 8)).map((entry) => this.toToolDescriptor(entry.tool));
|
|
4295
|
-
}
|
|
4296
4317
|
filterListedTools(plugin, tools) {
|
|
4297
4318
|
const allowedTools = plugin.manifest.mcp?.allowedTools;
|
|
4298
4319
|
if (!allowedTools || allowedTools.length === 0) {
|
|
@@ -4324,6 +4345,8 @@ var McpToolManager = class {
|
|
|
4324
4345
|
return client2;
|
|
4325
4346
|
}
|
|
4326
4347
|
toManagedTool(plugin, client2, tool2) {
|
|
4348
|
+
const outputSchema = toOptionalRecord(tool2.outputSchema);
|
|
4349
|
+
const annotations = toOptionalRecord(tool2.annotations);
|
|
4327
4350
|
return {
|
|
4328
4351
|
name: normalizeMcpToolName(plugin.manifest.name, tool2.name),
|
|
4329
4352
|
description: describeMcpTool(plugin.manifest.name, tool2),
|
|
@@ -4331,8 +4354,15 @@ var McpToolManager = class {
|
|
|
4331
4354
|
provider: plugin.manifest.name,
|
|
4332
4355
|
rawName: tool2.name,
|
|
4333
4356
|
...tool2.title?.trim() ? { title: tool2.title.trim() } : {},
|
|
4357
|
+
...outputSchema ? { outputSchema } : {},
|
|
4358
|
+
...annotations ? { annotations } : {},
|
|
4334
4359
|
execute: async (args) => {
|
|
4335
4360
|
const resolvedArgs = typeof args === "object" && args !== null ? args : {};
|
|
4361
|
+
const baseAttributes = {
|
|
4362
|
+
"gen_ai.tool.name": tool2.name,
|
|
4363
|
+
"mcp.method.name": "tools/call"
|
|
4364
|
+
};
|
|
4365
|
+
setSpanAttributes(baseAttributes);
|
|
4336
4366
|
try {
|
|
4337
4367
|
const result = await client2.callTool(tool2.name, resolvedArgs);
|
|
4338
4368
|
if ("isError" in result && result.isError) {
|
|
@@ -4368,6 +4398,20 @@ var McpToolManager = class {
|
|
|
4368
4398
|
}
|
|
4369
4399
|
};
|
|
4370
4400
|
}
|
|
4401
|
+
const errorAttributes = {
|
|
4402
|
+
...baseAttributes,
|
|
4403
|
+
"error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
|
|
4404
|
+
"error.message": getMcpAwareErrorMessage(error)
|
|
4405
|
+
};
|
|
4406
|
+
setSpanAttributes(errorAttributes);
|
|
4407
|
+
if (error instanceof McpToolError) {
|
|
4408
|
+
logWarn(
|
|
4409
|
+
"mcp_tool_call_failed",
|
|
4410
|
+
{},
|
|
4411
|
+
errorAttributes,
|
|
4412
|
+
"MCP tool call failed"
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4371
4415
|
throw error;
|
|
4372
4416
|
}
|
|
4373
4417
|
}
|
|
@@ -4414,37 +4458,19 @@ var McpToolManager = class {
|
|
|
4414
4458
|
toToolDescriptor(tool2) {
|
|
4415
4459
|
return {
|
|
4416
4460
|
name: tool2.name,
|
|
4461
|
+
rawName: tool2.rawName,
|
|
4462
|
+
...tool2.title ? { title: tool2.title } : {},
|
|
4417
4463
|
description: tool2.description,
|
|
4418
4464
|
parameters: tool2.parameters,
|
|
4465
|
+
...tool2.outputSchema ? { outputSchema: tool2.outputSchema } : {},
|
|
4466
|
+
...tool2.annotations ? { annotations: tool2.annotations } : {},
|
|
4419
4467
|
provider: tool2.provider
|
|
4420
4468
|
};
|
|
4421
4469
|
}
|
|
4422
|
-
scoreToolMatch(tool2, normalizedQuery, queryTokens) {
|
|
4423
|
-
const exactCandidates = [tool2.name, tool2.rawName, tool2.title].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
|
|
4424
|
-
if (exactCandidates.includes(normalizedQuery)) {
|
|
4425
|
-
return 100;
|
|
4426
|
-
}
|
|
4427
|
-
let score = 0;
|
|
4428
|
-
const searchableText = [
|
|
4429
|
-
tool2.name,
|
|
4430
|
-
tool2.rawName,
|
|
4431
|
-
tool2.title,
|
|
4432
|
-
tool2.description,
|
|
4433
|
-
tool2.provider
|
|
4434
|
-
].filter((value) => Boolean(value)).join(" ").toLowerCase();
|
|
4435
|
-
for (const candidate of exactCandidates) {
|
|
4436
|
-
if (candidate.startsWith(normalizedQuery)) {
|
|
4437
|
-
score = Math.max(score, 60);
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
for (const token of queryTokens) {
|
|
4441
|
-
if (searchableText.includes(token)) {
|
|
4442
|
-
score += 10;
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
return score;
|
|
4446
|
-
}
|
|
4447
4470
|
};
|
|
4471
|
+
function toOptionalRecord(value) {
|
|
4472
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4473
|
+
}
|
|
4448
4474
|
|
|
4449
4475
|
// src/chat/tools/definition.ts
|
|
4450
4476
|
function tool(definition) {
|
|
@@ -4757,8 +4783,61 @@ function createImageGenerateTool(hooks, deps = {}) {
|
|
|
4757
4783
|
});
|
|
4758
4784
|
}
|
|
4759
4785
|
|
|
4760
|
-
// src/chat/tools/skill/
|
|
4786
|
+
// src/chat/tools/skill/call-mcp-tool.ts
|
|
4761
4787
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
4788
|
+
function resolveMcpArguments(input) {
|
|
4789
|
+
const extraKeys = Object.keys(input).filter(
|
|
4790
|
+
(key) => key !== "tool_name" && key !== "arguments"
|
|
4791
|
+
);
|
|
4792
|
+
if (extraKeys.length > 0) {
|
|
4793
|
+
throw new Error(
|
|
4794
|
+
`callMcpTool MCP arguments must be nested under arguments, not top-level fields: ${extraKeys.join(", ")}`
|
|
4795
|
+
);
|
|
4796
|
+
}
|
|
4797
|
+
if ("arguments" in input) {
|
|
4798
|
+
const args = input.arguments;
|
|
4799
|
+
if (args === void 0) {
|
|
4800
|
+
return {};
|
|
4801
|
+
}
|
|
4802
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
4803
|
+
throw new Error("callMcpTool arguments must be an object when provided");
|
|
4804
|
+
}
|
|
4805
|
+
return args;
|
|
4806
|
+
}
|
|
4807
|
+
return {};
|
|
4808
|
+
}
|
|
4809
|
+
function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
|
|
4810
|
+
return tool({
|
|
4811
|
+
description: "Call an active MCP tool by exact tool_name. Use loadSkill to activate the provider, then searchMcpTools to discover tool names and schemas; authorization is handled by the runtime when required.",
|
|
4812
|
+
inputSchema: Type4.Object(
|
|
4813
|
+
{
|
|
4814
|
+
tool_name: Type4.String({
|
|
4815
|
+
minLength: 1,
|
|
4816
|
+
description: "Exact MCP tool_name from searchMcpTools."
|
|
4817
|
+
}),
|
|
4818
|
+
arguments: Type4.Optional(
|
|
4819
|
+
Type4.Record(Type4.String(), Type4.Unknown(), {
|
|
4820
|
+
description: "Arguments matching the disclosed MCP tool schema."
|
|
4821
|
+
})
|
|
4822
|
+
)
|
|
4823
|
+
},
|
|
4824
|
+
{ additionalProperties: false }
|
|
4825
|
+
),
|
|
4826
|
+
execute: async (input) => {
|
|
4827
|
+
const { tool_name } = input;
|
|
4828
|
+
const mcpTool = mcpToolManager.getResolvedActiveTools(getActiveSkills()).find((candidate) => candidate.name === tool_name);
|
|
4829
|
+
if (!mcpTool) {
|
|
4830
|
+
throw new Error(`MCP tool is not active for this turn: ${tool_name}`);
|
|
4831
|
+
}
|
|
4832
|
+
return await mcpTool.execute(
|
|
4833
|
+
resolveMcpArguments(input)
|
|
4834
|
+
);
|
|
4835
|
+
}
|
|
4836
|
+
});
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
// src/chat/tools/skill/load-skill.ts
|
|
4840
|
+
import { Type as Type5 } from "@sinclair/typebox";
|
|
4762
4841
|
function toLoadedSkill(result, availableSkills) {
|
|
4763
4842
|
if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
|
|
4764
4843
|
return null;
|
|
@@ -4770,7 +4849,6 @@ function toLoadedSkill(result, availableSkills) {
|
|
|
4770
4849
|
skillPath: metadata?.skillPath ?? result.skill_dir,
|
|
4771
4850
|
...metadata?.pluginProvider ? { pluginProvider: metadata.pluginProvider } : {},
|
|
4772
4851
|
...metadata?.allowedTools ? { allowedTools: metadata.allowedTools } : {},
|
|
4773
|
-
...metadata?.usesConfig ? { usesConfig: metadata.usesConfig } : {},
|
|
4774
4852
|
body: result.instructions
|
|
4775
4853
|
};
|
|
4776
4854
|
}
|
|
@@ -4803,9 +4881,9 @@ async function loadSkillFromHost(availableSkills, skillName) {
|
|
|
4803
4881
|
}
|
|
4804
4882
|
function createLoadSkillTool(availableSkills, options) {
|
|
4805
4883
|
return tool({
|
|
4806
|
-
description: "Load a skill by name so its instructions are available for this turn.
|
|
4807
|
-
inputSchema:
|
|
4808
|
-
skill_name:
|
|
4884
|
+
description: "Load a skill by name so its instructions and provider tool catalog are available for this turn. When the result includes mcp_provider and available_tool_count, use searchMcpTools to list or search descriptors before callMcpTool. Use when a request clearly matches a known skill.",
|
|
4885
|
+
inputSchema: Type5.Object({
|
|
4886
|
+
skill_name: Type5.String({
|
|
4809
4887
|
minLength: 1,
|
|
4810
4888
|
description: "Skill name to load, without the leading slash."
|
|
4811
4889
|
})
|
|
@@ -4824,53 +4902,87 @@ function createLoadSkillTool(availableSkills, options) {
|
|
|
4824
4902
|
});
|
|
4825
4903
|
}
|
|
4826
4904
|
|
|
4827
|
-
// src/chat/tools/
|
|
4828
|
-
import { Type as Type5 } from "@sinclair/typebox";
|
|
4829
|
-
function createReadFileTool() {
|
|
4830
|
-
return tool({
|
|
4831
|
-
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.",
|
|
4832
|
-
inputSchema: Type5.Object(
|
|
4833
|
-
{
|
|
4834
|
-
path: Type5.String({
|
|
4835
|
-
minLength: 1,
|
|
4836
|
-
description: "Path to the file in the sandbox workspace."
|
|
4837
|
-
})
|
|
4838
|
-
},
|
|
4839
|
-
{ additionalProperties: false }
|
|
4840
|
-
),
|
|
4841
|
-
execute: async () => {
|
|
4842
|
-
throw new Error(
|
|
4843
|
-
"readFile can only run when sandbox execution is enabled."
|
|
4844
|
-
);
|
|
4845
|
-
}
|
|
4846
|
-
});
|
|
4847
|
-
}
|
|
4848
|
-
|
|
4849
|
-
// src/chat/tools/runtime/report-progress.ts
|
|
4905
|
+
// src/chat/tools/skill/search-mcp-tools.ts
|
|
4850
4906
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
4851
|
-
function createReportProgressTool() {
|
|
4852
|
-
return tool({
|
|
4853
|
-
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.",
|
|
4854
|
-
inputSchema: Type6.Object({
|
|
4855
|
-
message: Type6.String({
|
|
4856
|
-
minLength: 1,
|
|
4857
|
-
description: "Short user-facing progress message."
|
|
4858
|
-
})
|
|
4859
|
-
})
|
|
4860
|
-
});
|
|
4861
|
-
}
|
|
4862
|
-
|
|
4863
|
-
// src/chat/tools/skill/search-tools.ts
|
|
4864
|
-
import { Type as Type7 } from "@sinclair/typebox";
|
|
4865
4907
|
|
|
4866
4908
|
// src/chat/tools/skill/mcp-tool-summary.ts
|
|
4867
|
-
function
|
|
4868
|
-
|
|
4869
|
-
|
|
4909
|
+
function getSchemaProperties(schema) {
|
|
4910
|
+
return schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
4911
|
+
}
|
|
4912
|
+
function getRequiredFields(schema) {
|
|
4913
|
+
return Array.isArray(schema.required) ? new Set(
|
|
4870
4914
|
schema.required.filter(
|
|
4871
4915
|
(value) => typeof value === "string"
|
|
4872
4916
|
)
|
|
4873
4917
|
) : /* @__PURE__ */ new Set();
|
|
4918
|
+
}
|
|
4919
|
+
function formatSchemaType(schema) {
|
|
4920
|
+
if (!schema || typeof schema !== "object") {
|
|
4921
|
+
return "unknown";
|
|
4922
|
+
}
|
|
4923
|
+
const typed = schema;
|
|
4924
|
+
const type = typed.type;
|
|
4925
|
+
if (typeof type === "string") {
|
|
4926
|
+
if (type === "array") {
|
|
4927
|
+
return `${formatSchemaType(typed.items)}[]`;
|
|
4928
|
+
}
|
|
4929
|
+
return type;
|
|
4930
|
+
}
|
|
4931
|
+
if (Array.isArray(type)) {
|
|
4932
|
+
return type.filter((value) => typeof value === "string").join(" | ");
|
|
4933
|
+
}
|
|
4934
|
+
if (Array.isArray(typed.enum) && typed.enum.length > 0) {
|
|
4935
|
+
return typed.enum.map((value) => JSON.stringify(value)).join(" | ");
|
|
4936
|
+
}
|
|
4937
|
+
return "unknown";
|
|
4938
|
+
}
|
|
4939
|
+
function formatArgumentPlaceholder(name, schema) {
|
|
4940
|
+
const type = formatSchemaType(schema);
|
|
4941
|
+
if (type === "string") {
|
|
4942
|
+
return `<${name}>`;
|
|
4943
|
+
}
|
|
4944
|
+
if (type === "number" || type === "integer") {
|
|
4945
|
+
return "<number>";
|
|
4946
|
+
}
|
|
4947
|
+
if (type === "boolean") {
|
|
4948
|
+
return "<boolean>";
|
|
4949
|
+
}
|
|
4950
|
+
if (type.endsWith("[]")) {
|
|
4951
|
+
return "<array>";
|
|
4952
|
+
}
|
|
4953
|
+
if (type === "object") {
|
|
4954
|
+
return "<object>";
|
|
4955
|
+
}
|
|
4956
|
+
return `<${type}>`;
|
|
4957
|
+
}
|
|
4958
|
+
function formatMcpToolSignature(toolName, schema) {
|
|
4959
|
+
const properties = getSchemaProperties(schema);
|
|
4960
|
+
const required = getRequiredFields(schema);
|
|
4961
|
+
const fields = Object.entries(properties).map(([name, propertySchema]) => {
|
|
4962
|
+
const marker = required.has(name) ? "" : "?";
|
|
4963
|
+
return `${name}${marker}: ${formatSchemaType(propertySchema)}`;
|
|
4964
|
+
});
|
|
4965
|
+
if (fields.length === 0) {
|
|
4966
|
+
return `${toolName}()`;
|
|
4967
|
+
}
|
|
4968
|
+
return `${toolName}({ ${fields.join(", ")} })`;
|
|
4969
|
+
}
|
|
4970
|
+
function formatMcpToolCallExample(toolName, schema) {
|
|
4971
|
+
return {
|
|
4972
|
+
tool_name: toolName,
|
|
4973
|
+
arguments: Object.fromEntries(
|
|
4974
|
+
Object.entries(getSchemaProperties(schema)).map(
|
|
4975
|
+
([name, propertySchema]) => [
|
|
4976
|
+
name,
|
|
4977
|
+
formatArgumentPlaceholder(name, propertySchema)
|
|
4978
|
+
]
|
|
4979
|
+
)
|
|
4980
|
+
)
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
function summarizeInputSchema(schema) {
|
|
4984
|
+
const properties = getSchemaProperties(schema);
|
|
4985
|
+
const required = getRequiredFields(schema);
|
|
4874
4986
|
const propertyNames = Object.keys(properties);
|
|
4875
4987
|
if (propertyNames.length === 0) {
|
|
4876
4988
|
return "No arguments.";
|
|
@@ -4880,59 +4992,190 @@ function summarizeInputSchema(schema) {
|
|
|
4880
4992
|
function toExposedToolSummary(toolDef) {
|
|
4881
4993
|
return {
|
|
4882
4994
|
tool_name: toolDef.name,
|
|
4995
|
+
mcp_tool_name: toolDef.rawName,
|
|
4883
4996
|
provider: toolDef.provider,
|
|
4997
|
+
...toolDef.title ? { title: toolDef.title } : {},
|
|
4884
4998
|
description: toolDef.description,
|
|
4999
|
+
signature: formatMcpToolSignature(toolDef.name, toolDef.parameters),
|
|
5000
|
+
call: formatMcpToolCallExample(toolDef.name, toolDef.parameters),
|
|
4885
5001
|
input_schema: toolDef.parameters,
|
|
4886
|
-
input_schema_summary: summarizeInputSchema(toolDef.parameters)
|
|
4887
|
-
|
|
5002
|
+
input_schema_summary: summarizeInputSchema(toolDef.parameters),
|
|
5003
|
+
...toolDef.outputSchema ? { output_schema: toolDef.outputSchema } : {},
|
|
5004
|
+
...toolDef.annotations ? { annotations: toolDef.annotations } : {}
|
|
5005
|
+
};
|
|
5006
|
+
}
|
|
5007
|
+
function toActiveMcpCatalogSummaries(toolDefs) {
|
|
5008
|
+
const countsByProvider = /* @__PURE__ */ new Map();
|
|
5009
|
+
for (const toolDef of toolDefs) {
|
|
5010
|
+
countsByProvider.set(
|
|
5011
|
+
toolDef.provider,
|
|
5012
|
+
(countsByProvider.get(toolDef.provider) ?? 0) + 1
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
return [...countsByProvider.entries()].map(([provider, availableToolCount]) => ({
|
|
5016
|
+
provider,
|
|
5017
|
+
available_tool_count: availableToolCount
|
|
5018
|
+
})).sort((left, right) => left.provider.localeCompare(right.provider));
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
// src/chat/tools/skill/search-mcp-tools.ts
|
|
5022
|
+
var DEFAULT_MAX_RESULTS = 5;
|
|
5023
|
+
var MAX_RESULTS = 20;
|
|
5024
|
+
function normalize(value) {
|
|
5025
|
+
return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
|
|
5026
|
+
}
|
|
5027
|
+
function searchableToolText(toolDef) {
|
|
5028
|
+
return normalize(
|
|
5029
|
+
[
|
|
5030
|
+
toolDef.name,
|
|
5031
|
+
toolDef.rawName,
|
|
5032
|
+
toolDef.title,
|
|
5033
|
+
toolDef.provider,
|
|
5034
|
+
toolDef.description,
|
|
5035
|
+
JSON.stringify(toolDef.parameters),
|
|
5036
|
+
JSON.stringify(toolDef.outputSchema),
|
|
5037
|
+
JSON.stringify(toolDef.annotations)
|
|
5038
|
+
].filter(Boolean).join(" ")
|
|
5039
|
+
);
|
|
4888
5040
|
}
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
5041
|
+
function scoreTool(toolDef, query) {
|
|
5042
|
+
const normalizedQuery = normalize(query);
|
|
5043
|
+
if (!normalizedQuery) {
|
|
5044
|
+
return 0;
|
|
5045
|
+
}
|
|
5046
|
+
const normalizedName = normalize(toolDef.name);
|
|
5047
|
+
const normalizedRawName = normalize(toolDef.rawName);
|
|
5048
|
+
const text = searchableToolText(toolDef);
|
|
5049
|
+
let score = 0;
|
|
5050
|
+
if (normalizedName === normalizedQuery || normalizedRawName === normalizedQuery) {
|
|
5051
|
+
score += 100;
|
|
5052
|
+
}
|
|
5053
|
+
if (normalizedName.includes(normalizedQuery)) {
|
|
5054
|
+
score += 50;
|
|
5055
|
+
}
|
|
5056
|
+
if (normalizedRawName.includes(normalizedQuery)) {
|
|
5057
|
+
score += 45;
|
|
5058
|
+
}
|
|
5059
|
+
if (text.includes(normalizedQuery)) {
|
|
5060
|
+
score += 25;
|
|
5061
|
+
}
|
|
5062
|
+
for (const term of normalizedQuery.split(/\s+/).filter(Boolean)) {
|
|
5063
|
+
if (normalizedName.includes(term)) {
|
|
5064
|
+
score += 12;
|
|
5065
|
+
}
|
|
5066
|
+
if (normalizedRawName.includes(term)) {
|
|
5067
|
+
score += 10;
|
|
5068
|
+
}
|
|
5069
|
+
if (text.includes(term)) {
|
|
5070
|
+
score += 4;
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
return score;
|
|
5074
|
+
}
|
|
5075
|
+
function searchMcpCatalog(tools, query) {
|
|
5076
|
+
if (!normalize(query)) {
|
|
5077
|
+
return [...tools].sort(
|
|
5078
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
5079
|
+
);
|
|
5080
|
+
}
|
|
5081
|
+
return tools.map(
|
|
5082
|
+
(toolDef) => ({
|
|
5083
|
+
tool: toolDef,
|
|
5084
|
+
score: scoreTool(toolDef, query)
|
|
5085
|
+
})
|
|
5086
|
+
).filter((ranked) => ranked.score > 0).sort((left, right) => {
|
|
5087
|
+
if (right.score !== left.score) {
|
|
5088
|
+
return right.score - left.score;
|
|
5089
|
+
}
|
|
5090
|
+
return left.tool.name.localeCompare(right.tool.name);
|
|
5091
|
+
}).map((ranked) => ranked.tool);
|
|
5092
|
+
}
|
|
5093
|
+
function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
4894
5094
|
return tool({
|
|
4895
|
-
description: "
|
|
4896
|
-
inputSchema:
|
|
5095
|
+
description: "List or search active MCP tools and return full descriptors, including input/output schemas and annotations. Use after loadSkill when choosing a provider tool or when callMcpTool arguments are unclear.",
|
|
5096
|
+
inputSchema: Type6.Object(
|
|
4897
5097
|
{
|
|
4898
|
-
query:
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
5098
|
+
query: Type6.Optional(
|
|
5099
|
+
Type6.String({
|
|
5100
|
+
minLength: 1,
|
|
5101
|
+
description: "Optional search terms describing the MCP tool or arguments needed."
|
|
5102
|
+
})
|
|
5103
|
+
),
|
|
5104
|
+
provider: Type6.Optional(
|
|
5105
|
+
Type6.String({
|
|
4904
5106
|
minLength: 1,
|
|
4905
|
-
description: "Optional
|
|
5107
|
+
description: "Optional provider name to list or search within."
|
|
4906
5108
|
})
|
|
4907
5109
|
),
|
|
4908
|
-
|
|
4909
|
-
|
|
5110
|
+
max_results: Type6.Optional(
|
|
5111
|
+
Type6.Integer({
|
|
4910
5112
|
minimum: 1,
|
|
4911
|
-
maximum:
|
|
4912
|
-
description: "Maximum
|
|
5113
|
+
maximum: MAX_RESULTS,
|
|
5114
|
+
description: "Maximum matching tool descriptors to return."
|
|
4913
5115
|
})
|
|
4914
5116
|
)
|
|
4915
5117
|
},
|
|
4916
5118
|
{ additionalProperties: false }
|
|
4917
5119
|
),
|
|
4918
|
-
execute: async ({ query, provider,
|
|
4919
|
-
const
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
5120
|
+
execute: async ({ query, provider, max_results }) => {
|
|
5121
|
+
const catalog = mcpToolManager.getActiveToolCatalog(
|
|
5122
|
+
getActiveSkills(),
|
|
5123
|
+
provider ? { provider } : {}
|
|
5124
|
+
);
|
|
5125
|
+
const maxResults = max_results ?? DEFAULT_MAX_RESULTS;
|
|
5126
|
+
const matches = searchMcpCatalog(catalog, query ?? "").slice(
|
|
5127
|
+
0,
|
|
5128
|
+
maxResults
|
|
5129
|
+
);
|
|
4923
5130
|
return {
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
5131
|
+
query: query ?? null,
|
|
5132
|
+
provider: provider ?? null,
|
|
5133
|
+
total_active_tools: catalog.length,
|
|
5134
|
+
returned_tools: matches.length,
|
|
5135
|
+
tools: matches.map(toExposedToolSummary)
|
|
4929
5136
|
};
|
|
4930
5137
|
}
|
|
4931
5138
|
});
|
|
4932
5139
|
}
|
|
4933
5140
|
|
|
4934
|
-
// src/chat/tools/
|
|
5141
|
+
// src/chat/tools/sandbox/read-file.ts
|
|
5142
|
+
import { Type as Type7 } from "@sinclair/typebox";
|
|
5143
|
+
function createReadFileTool() {
|
|
5144
|
+
return tool({
|
|
5145
|
+
description: "Read a file from the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Do not use for broad discovery when search tools are better.",
|
|
5146
|
+
inputSchema: Type7.Object(
|
|
5147
|
+
{
|
|
5148
|
+
path: Type7.String({
|
|
5149
|
+
minLength: 1,
|
|
5150
|
+
description: "Path to the file in the sandbox workspace."
|
|
5151
|
+
})
|
|
5152
|
+
},
|
|
5153
|
+
{ additionalProperties: false }
|
|
5154
|
+
),
|
|
5155
|
+
execute: async () => {
|
|
5156
|
+
throw new Error(
|
|
5157
|
+
"readFile can only run when sandbox execution is enabled."
|
|
5158
|
+
);
|
|
5159
|
+
}
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
// src/chat/tools/runtime/report-progress.ts
|
|
4935
5164
|
import { Type as Type8 } from "@sinclair/typebox";
|
|
5165
|
+
function createReportProgressTool() {
|
|
5166
|
+
return tool({
|
|
5167
|
+
description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
|
|
5168
|
+
inputSchema: Type8.Object({
|
|
5169
|
+
message: Type8.String({
|
|
5170
|
+
minLength: 1,
|
|
5171
|
+
description: "Short user-facing progress message."
|
|
5172
|
+
})
|
|
5173
|
+
})
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5177
|
+
// src/chat/tools/slack/channel-list-messages.ts
|
|
5178
|
+
import { Type as Type9 } from "@sinclair/typebox";
|
|
4936
5179
|
|
|
4937
5180
|
// src/chat/slack/channel.ts
|
|
4938
5181
|
async function listChannelMessages(input) {
|
|
@@ -5021,39 +5264,39 @@ async function listThreadReplies(input) {
|
|
|
5021
5264
|
function createSlackChannelListMessagesTool(context) {
|
|
5022
5265
|
return tool({
|
|
5023
5266
|
description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
|
|
5024
|
-
inputSchema:
|
|
5025
|
-
limit:
|
|
5026
|
-
|
|
5267
|
+
inputSchema: Type9.Object({
|
|
5268
|
+
limit: Type9.Optional(
|
|
5269
|
+
Type9.Integer({
|
|
5027
5270
|
minimum: 1,
|
|
5028
5271
|
maximum: 1e3,
|
|
5029
5272
|
description: "Maximum number of messages to return across pages."
|
|
5030
5273
|
})
|
|
5031
5274
|
),
|
|
5032
|
-
cursor:
|
|
5033
|
-
|
|
5275
|
+
cursor: Type9.Optional(
|
|
5276
|
+
Type9.String({
|
|
5034
5277
|
minLength: 1,
|
|
5035
5278
|
description: "Optional cursor to continue from a prior call."
|
|
5036
5279
|
})
|
|
5037
5280
|
),
|
|
5038
|
-
oldest:
|
|
5039
|
-
|
|
5281
|
+
oldest: Type9.Optional(
|
|
5282
|
+
Type9.String({
|
|
5040
5283
|
minLength: 1,
|
|
5041
5284
|
description: "Optional oldest message timestamp (Slack ts) for range filtering."
|
|
5042
5285
|
})
|
|
5043
5286
|
),
|
|
5044
|
-
latest:
|
|
5045
|
-
|
|
5287
|
+
latest: Type9.Optional(
|
|
5288
|
+
Type9.String({
|
|
5046
5289
|
minLength: 1,
|
|
5047
5290
|
description: "Optional latest message timestamp (Slack ts) for range filtering."
|
|
5048
5291
|
})
|
|
5049
5292
|
),
|
|
5050
|
-
inclusive:
|
|
5051
|
-
|
|
5293
|
+
inclusive: Type9.Optional(
|
|
5294
|
+
Type9.Boolean({
|
|
5052
5295
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
5053
5296
|
})
|
|
5054
5297
|
),
|
|
5055
|
-
max_pages:
|
|
5056
|
-
|
|
5298
|
+
max_pages: Type9.Optional(
|
|
5299
|
+
Type9.Integer({
|
|
5057
5300
|
minimum: 1,
|
|
5058
5301
|
maximum: 10,
|
|
5059
5302
|
description: "Maximum number of API pages to traverse in a single call."
|
|
@@ -5107,7 +5350,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5107
5350
|
}
|
|
5108
5351
|
|
|
5109
5352
|
// src/chat/tools/slack/channel-post-message.ts
|
|
5110
|
-
import { Type as
|
|
5353
|
+
import { Type as Type10 } from "@sinclair/typebox";
|
|
5111
5354
|
|
|
5112
5355
|
// src/chat/tools/idempotency.ts
|
|
5113
5356
|
function stableSerialize(value) {
|
|
@@ -5129,8 +5372,8 @@ function createOperationKey(toolName, input) {
|
|
|
5129
5372
|
function createSlackChannelPostMessageTool(context, state) {
|
|
5130
5373
|
return tool({
|
|
5131
5374
|
description: "Post a message in the active Slack channel context (outside the thread). Use this only when the user explicitly asks to post/send/share/say something in the current channel. Do not use it for normal thread replies, speculative broadcasts, or requests targeting another named channel; explain that limitation instead. Do not claim a channel message was posted unless this tool succeeds in this turn.",
|
|
5132
|
-
inputSchema:
|
|
5133
|
-
text:
|
|
5375
|
+
inputSchema: Type10.Object({
|
|
5376
|
+
text: Type10.String({
|
|
5134
5377
|
minLength: 1,
|
|
5135
5378
|
maxLength: 4e4,
|
|
5136
5379
|
description: "Slack mrkdwn text to post."
|
|
@@ -5173,12 +5416,12 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
5173
5416
|
}
|
|
5174
5417
|
|
|
5175
5418
|
// src/chat/tools/slack/message-add-reaction.ts
|
|
5176
|
-
import { Type as
|
|
5419
|
+
import { Type as Type11 } from "@sinclair/typebox";
|
|
5177
5420
|
function createSlackMessageAddReactionTool(context, state) {
|
|
5178
5421
|
return tool({
|
|
5179
5422
|
description: "Add an emoji reaction to the current inbound Slack message. Use sparingly for lightweight acknowledgements. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
|
|
5180
|
-
inputSchema:
|
|
5181
|
-
emoji:
|
|
5423
|
+
inputSchema: Type11.Object({
|
|
5424
|
+
emoji: Type11.String({
|
|
5182
5425
|
minLength: 1,
|
|
5183
5426
|
maxLength: 64,
|
|
5184
5427
|
description: "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
|
|
@@ -5236,7 +5479,7 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
5236
5479
|
}
|
|
5237
5480
|
|
|
5238
5481
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5239
|
-
import { Type as
|
|
5482
|
+
import { Type as Type12 } from "@sinclair/typebox";
|
|
5240
5483
|
|
|
5241
5484
|
// src/chat/tools/slack/canvases.ts
|
|
5242
5485
|
function normalizeCanvasMarkdown(markdown) {
|
|
@@ -5452,13 +5695,13 @@ function mergeRecentCanvases(existing, created) {
|
|
|
5452
5695
|
function createSlackCanvasCreateTool(context, state) {
|
|
5453
5696
|
return tool({
|
|
5454
5697
|
description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, keep the thread reply brief and include the canvas link. Do not use for short answers that fit cleanly in one normal thread reply.",
|
|
5455
|
-
inputSchema:
|
|
5456
|
-
title:
|
|
5698
|
+
inputSchema: Type12.Object({
|
|
5699
|
+
title: Type12.String({
|
|
5457
5700
|
minLength: 1,
|
|
5458
5701
|
maxLength: 160,
|
|
5459
5702
|
description: "Canvas title."
|
|
5460
5703
|
}),
|
|
5461
|
-
markdown:
|
|
5704
|
+
markdown: Type12.String({
|
|
5462
5705
|
minLength: 1,
|
|
5463
5706
|
description: "Canvas markdown body content."
|
|
5464
5707
|
})
|
|
@@ -5524,29 +5767,29 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
5524
5767
|
function createSlackCanvasUpdateTool(state, _context) {
|
|
5525
5768
|
return tool({
|
|
5526
5769
|
description: "Update the active Slack canvas tracked in artifact context. Use when continuing or correcting a document already tracked in this thread. Do not use to create a brand-new long-form artifact.",
|
|
5527
|
-
inputSchema:
|
|
5528
|
-
markdown:
|
|
5770
|
+
inputSchema: Type12.Object({
|
|
5771
|
+
markdown: Type12.String({
|
|
5529
5772
|
minLength: 1,
|
|
5530
5773
|
description: "Markdown content to insert or use as replacement text."
|
|
5531
5774
|
}),
|
|
5532
|
-
operation:
|
|
5533
|
-
|
|
5775
|
+
operation: Type12.Optional(
|
|
5776
|
+
Type12.Union(
|
|
5534
5777
|
[
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5778
|
+
Type12.Literal("insert_at_end"),
|
|
5779
|
+
Type12.Literal("insert_at_start"),
|
|
5780
|
+
Type12.Literal("replace")
|
|
5538
5781
|
],
|
|
5539
5782
|
{ description: "Canvas update mode." }
|
|
5540
5783
|
)
|
|
5541
5784
|
),
|
|
5542
|
-
section_id:
|
|
5543
|
-
|
|
5785
|
+
section_id: Type12.Optional(
|
|
5786
|
+
Type12.String({
|
|
5544
5787
|
minLength: 1,
|
|
5545
5788
|
description: "Optional section ID required for targeted replace operations."
|
|
5546
5789
|
})
|
|
5547
5790
|
),
|
|
5548
|
-
section_contains_text:
|
|
5549
|
-
|
|
5791
|
+
section_contains_text: Type12.Optional(
|
|
5792
|
+
Type12.String({
|
|
5550
5793
|
minLength: 1,
|
|
5551
5794
|
description: "Optional helper text used to find the target section when section_id is not provided."
|
|
5552
5795
|
})
|
|
@@ -5612,8 +5855,8 @@ function createSlackCanvasUpdateTool(state, _context) {
|
|
|
5612
5855
|
function createSlackCanvasReadTool() {
|
|
5613
5856
|
return tool({
|
|
5614
5857
|
description: "Read a Slack canvas the bot has access to (including canvases the bot created) by canvas ID or Slack canvas/docs URL. Use when the user shares a Slack canvas link (https://*.slack.com/docs/... or /canvas/...) or references a canvas ID and you need its contents. Do not use for generic web pages \u2014 use webFetch for those.",
|
|
5615
|
-
inputSchema:
|
|
5616
|
-
canvas:
|
|
5858
|
+
inputSchema: Type12.Object({
|
|
5859
|
+
canvas: Type12.String({
|
|
5617
5860
|
minLength: 1,
|
|
5618
5861
|
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
5619
5862
|
})
|
|
@@ -5663,7 +5906,7 @@ function createSlackCanvasReadTool() {
|
|
|
5663
5906
|
}
|
|
5664
5907
|
|
|
5665
5908
|
// src/chat/tools/slack/list-tools.ts
|
|
5666
|
-
import { Type as
|
|
5909
|
+
import { Type as Type13 } from "@sinclair/typebox";
|
|
5667
5910
|
|
|
5668
5911
|
// src/chat/tools/slack/lists.ts
|
|
5669
5912
|
function normalizeKey(value) {
|
|
@@ -5837,8 +6080,8 @@ async function updateListItem(input) {
|
|
|
5837
6080
|
function createSlackListCreateTool(state) {
|
|
5838
6081
|
return tool({
|
|
5839
6082
|
description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
|
|
5840
|
-
inputSchema:
|
|
5841
|
-
name:
|
|
6083
|
+
inputSchema: Type13.Object({
|
|
6084
|
+
name: Type13.String({
|
|
5842
6085
|
minLength: 1,
|
|
5843
6086
|
maxLength: 160,
|
|
5844
6087
|
description: "Name for the new Slack list."
|
|
@@ -5873,20 +6116,20 @@ function createSlackListCreateTool(state) {
|
|
|
5873
6116
|
function createSlackListAddItemsTool(state) {
|
|
5874
6117
|
return tool({
|
|
5875
6118
|
description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
|
|
5876
|
-
inputSchema:
|
|
5877
|
-
items:
|
|
6119
|
+
inputSchema: Type13.Object({
|
|
6120
|
+
items: Type13.Array(Type13.String({ minLength: 1 }), {
|
|
5878
6121
|
minItems: 1,
|
|
5879
6122
|
maxItems: 25,
|
|
5880
6123
|
description: "List item titles to create."
|
|
5881
6124
|
}),
|
|
5882
|
-
assignee_user_id:
|
|
5883
|
-
|
|
6125
|
+
assignee_user_id: Type13.Optional(
|
|
6126
|
+
Type13.String({
|
|
5884
6127
|
minLength: 1,
|
|
5885
6128
|
description: "Optional Slack user ID assigned to all created items."
|
|
5886
6129
|
})
|
|
5887
6130
|
),
|
|
5888
|
-
due_date:
|
|
5889
|
-
|
|
6131
|
+
due_date: Type13.Optional(
|
|
6132
|
+
Type13.String({
|
|
5890
6133
|
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
5891
6134
|
description: "Optional due date in YYYY-MM-DD format."
|
|
5892
6135
|
})
|
|
@@ -5935,9 +6178,9 @@ function createSlackListAddItemsTool(state) {
|
|
|
5935
6178
|
function createSlackListGetItemsTool(state) {
|
|
5936
6179
|
return tool({
|
|
5937
6180
|
description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
|
|
5938
|
-
inputSchema:
|
|
5939
|
-
limit:
|
|
5940
|
-
|
|
6181
|
+
inputSchema: Type13.Object({
|
|
6182
|
+
limit: Type13.Optional(
|
|
6183
|
+
Type13.Integer({
|
|
5941
6184
|
minimum: 1,
|
|
5942
6185
|
maximum: 200,
|
|
5943
6186
|
description: "Maximum number of list items to return."
|
|
@@ -5962,19 +6205,19 @@ function createSlackListGetItemsTool(state) {
|
|
|
5962
6205
|
function createSlackListUpdateItemTool(state) {
|
|
5963
6206
|
return tool({
|
|
5964
6207
|
description: "Update an item in the active Slack list tracked in artifact context (title/completion). Use when the user asks to mark progress or rename a tracked task. Do not use to add new tasks.",
|
|
5965
|
-
inputSchema:
|
|
6208
|
+
inputSchema: Type13.Object(
|
|
5966
6209
|
{
|
|
5967
|
-
item_id:
|
|
6210
|
+
item_id: Type13.String({
|
|
5968
6211
|
minLength: 1,
|
|
5969
6212
|
description: "ID of the Slack list item to update."
|
|
5970
6213
|
}),
|
|
5971
|
-
completed:
|
|
5972
|
-
|
|
6214
|
+
completed: Type13.Optional(
|
|
6215
|
+
Type13.Boolean({
|
|
5973
6216
|
description: "Optional completion status update."
|
|
5974
6217
|
})
|
|
5975
6218
|
),
|
|
5976
|
-
title:
|
|
5977
|
-
|
|
6219
|
+
title: Type13.Optional(
|
|
6220
|
+
Type13.String({
|
|
5978
6221
|
minLength: 1,
|
|
5979
6222
|
description: "Optional new item title."
|
|
5980
6223
|
})
|
|
@@ -6024,11 +6267,11 @@ function createSlackListUpdateItemTool(state) {
|
|
|
6024
6267
|
}
|
|
6025
6268
|
|
|
6026
6269
|
// src/chat/tools/system-time.ts
|
|
6027
|
-
import { Type as
|
|
6270
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
6028
6271
|
function createSystemTimeTool() {
|
|
6029
6272
|
return tool({
|
|
6030
6273
|
description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
|
|
6031
|
-
inputSchema:
|
|
6274
|
+
inputSchema: Type14.Object({}),
|
|
6032
6275
|
execute: async () => {
|
|
6033
6276
|
const now = /* @__PURE__ */ new Date();
|
|
6034
6277
|
return {
|
|
@@ -6043,7 +6286,7 @@ function createSystemTimeTool() {
|
|
|
6043
6286
|
}
|
|
6044
6287
|
|
|
6045
6288
|
// src/chat/tools/web/fetch-tool.ts
|
|
6046
|
-
import { Type as
|
|
6289
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
6047
6290
|
|
|
6048
6291
|
// src/chat/tools/web/constants.ts
|
|
6049
6292
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -6391,13 +6634,13 @@ function extractHttpStatusFromMessage(message) {
|
|
|
6391
6634
|
function createWebFetchTool(hooks) {
|
|
6392
6635
|
return tool({
|
|
6393
6636
|
description: "Fetch and extract readable content from a specific URL. Use when you need details from a known page or document. Do not use for discovery when search is the first step.",
|
|
6394
|
-
inputSchema:
|
|
6395
|
-
url:
|
|
6637
|
+
inputSchema: Type15.Object({
|
|
6638
|
+
url: Type15.String({
|
|
6396
6639
|
minLength: 1,
|
|
6397
6640
|
description: "HTTP(S) URL to fetch."
|
|
6398
6641
|
}),
|
|
6399
|
-
max_chars:
|
|
6400
|
-
|
|
6642
|
+
max_chars: Type15.Optional(
|
|
6643
|
+
Type15.Integer({
|
|
6401
6644
|
minimum: 500,
|
|
6402
6645
|
maximum: MAX_FETCH_CHARS,
|
|
6403
6646
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -6457,9 +6700,9 @@ function createWebFetchTool(hooks) {
|
|
|
6457
6700
|
// src/chat/tools/web/search.ts
|
|
6458
6701
|
import { generateText } from "ai";
|
|
6459
6702
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
6460
|
-
import { Type as
|
|
6703
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6461
6704
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6462
|
-
var
|
|
6705
|
+
var MAX_RESULTS2 = 5;
|
|
6463
6706
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
6464
6707
|
var SEARCH_TOOL_NAME = "parallelSearch";
|
|
6465
6708
|
function asString(value) {
|
|
@@ -6500,16 +6743,16 @@ function isAuthFailure(message) {
|
|
|
6500
6743
|
function createWebSearchTool() {
|
|
6501
6744
|
return tool({
|
|
6502
6745
|
description: "Search public web sources and return top snippets/URLs. Use when you need discovery or source candidates. Do not use when the user already provided a specific URL to inspect.",
|
|
6503
|
-
inputSchema:
|
|
6504
|
-
query:
|
|
6746
|
+
inputSchema: Type16.Object({
|
|
6747
|
+
query: Type16.String({
|
|
6505
6748
|
minLength: 1,
|
|
6506
6749
|
maxLength: 500,
|
|
6507
6750
|
description: "Search query."
|
|
6508
6751
|
}),
|
|
6509
|
-
max_results:
|
|
6510
|
-
|
|
6752
|
+
max_results: Type16.Optional(
|
|
6753
|
+
Type16.Integer({
|
|
6511
6754
|
minimum: 1,
|
|
6512
|
-
maximum:
|
|
6755
|
+
maximum: MAX_RESULTS2,
|
|
6513
6756
|
description: "Max results to return."
|
|
6514
6757
|
})
|
|
6515
6758
|
)
|
|
@@ -6573,17 +6816,17 @@ function createWebSearchTool() {
|
|
|
6573
6816
|
}
|
|
6574
6817
|
|
|
6575
6818
|
// src/chat/tools/sandbox/write-file.ts
|
|
6576
|
-
import { Type as
|
|
6819
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
6577
6820
|
function createWriteFileTool() {
|
|
6578
6821
|
return tool({
|
|
6579
6822
|
description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.",
|
|
6580
|
-
inputSchema:
|
|
6823
|
+
inputSchema: Type17.Object(
|
|
6581
6824
|
{
|
|
6582
|
-
path:
|
|
6825
|
+
path: Type17.String({
|
|
6583
6826
|
minLength: 1,
|
|
6584
6827
|
description: "Path to write in the sandbox workspace."
|
|
6585
6828
|
}),
|
|
6586
|
-
content:
|
|
6829
|
+
content: Type17.String({
|
|
6587
6830
|
description: "UTF-8 file content to write."
|
|
6588
6831
|
})
|
|
6589
6832
|
},
|
|
@@ -6658,7 +6901,11 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6658
6901
|
slackListUpdateItem: createSlackListUpdateItemTool(state)
|
|
6659
6902
|
};
|
|
6660
6903
|
if (context.mcpToolManager && context.getActiveSkills) {
|
|
6661
|
-
tools.
|
|
6904
|
+
tools.searchMcpTools = createSearchMcpToolsTool(
|
|
6905
|
+
context.mcpToolManager,
|
|
6906
|
+
context.getActiveSkills
|
|
6907
|
+
);
|
|
6908
|
+
tools.callMcpTool = createCallMcpToolTool(
|
|
6662
6909
|
context.mcpToolManager,
|
|
6663
6910
|
context.getActiveSkills
|
|
6664
6911
|
);
|
|
@@ -8221,134 +8468,21 @@ function shouldEmitDevAgentTrace() {
|
|
|
8221
8468
|
return process.env.NODE_ENV === "development";
|
|
8222
8469
|
}
|
|
8223
8470
|
|
|
8224
|
-
// src/chat/
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
8229
|
-
deleteMcpServerSessionId(userId, provider),
|
|
8230
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
8231
|
-
]);
|
|
8232
|
-
}
|
|
8233
|
-
|
|
8234
|
-
// src/chat/services/plugin-auth-orchestration.ts
|
|
8235
|
-
var PluginAuthorizationPauseError = class extends Error {
|
|
8471
|
+
// src/chat/services/auth-pause.ts
|
|
8472
|
+
var AuthorizationPauseError = class extends Error {
|
|
8473
|
+
disposition;
|
|
8474
|
+
kind;
|
|
8236
8475
|
provider;
|
|
8237
|
-
constructor(provider) {
|
|
8238
|
-
super(
|
|
8239
|
-
|
|
8476
|
+
constructor(kind, provider, disposition) {
|
|
8477
|
+
super(
|
|
8478
|
+
kind === "mcp" ? `MCP authorization started for ${provider}` : `Plugin authorization started for ${provider}`
|
|
8479
|
+
);
|
|
8480
|
+
this.name = kind === "mcp" ? "McpAuthorizationPauseError" : "PluginAuthorizationPauseError";
|
|
8481
|
+
this.disposition = disposition;
|
|
8482
|
+
this.kind = kind;
|
|
8240
8483
|
this.provider = provider;
|
|
8241
8484
|
}
|
|
8242
8485
|
};
|
|
8243
|
-
function isCommandAuthFailure(details) {
|
|
8244
|
-
if (!details || typeof details !== "object") {
|
|
8245
|
-
return false;
|
|
8246
|
-
}
|
|
8247
|
-
const result = details;
|
|
8248
|
-
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
8249
|
-
return false;
|
|
8250
|
-
}
|
|
8251
|
-
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
8252
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
8253
|
-
if (!text.trim()) {
|
|
8254
|
-
return false;
|
|
8255
|
-
}
|
|
8256
|
-
return [
|
|
8257
|
-
/\b401\b/,
|
|
8258
|
-
/\bunauthorized\b/,
|
|
8259
|
-
/\bbad credentials\b/,
|
|
8260
|
-
/\binvalid token\b/,
|
|
8261
|
-
/\btoken (?:expired|revoked)\b/,
|
|
8262
|
-
/\bexpired token\b/,
|
|
8263
|
-
/\bmissing scopes?\b/,
|
|
8264
|
-
/\binsufficient scope\b/,
|
|
8265
|
-
/\binvalid grant\b/,
|
|
8266
|
-
/\breauthoriz/
|
|
8267
|
-
].some((pattern) => pattern.test(text));
|
|
8268
|
-
}
|
|
8269
|
-
function commandTargetsProvider(provider, command, details) {
|
|
8270
|
-
const normalizedCommand = command.trim().toLowerCase();
|
|
8271
|
-
if (!normalizedCommand) {
|
|
8272
|
-
return false;
|
|
8273
|
-
}
|
|
8274
|
-
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
8275
|
-
return true;
|
|
8276
|
-
}
|
|
8277
|
-
const plugin = getPluginDefinition(provider);
|
|
8278
|
-
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
8279
|
-
const credentials = plugin?.manifest.credentials;
|
|
8280
|
-
if (credentials) {
|
|
8281
|
-
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
8282
|
-
for (const domain of credentials.apiDomains) {
|
|
8283
|
-
candidates.add(domain.toLowerCase());
|
|
8284
|
-
}
|
|
8285
|
-
}
|
|
8286
|
-
const combinedText = `${normalizedCommand}
|
|
8287
|
-
${details.stdout?.toLowerCase() ?? ""}
|
|
8288
|
-
${details.stderr?.toLowerCase() ?? ""}`;
|
|
8289
|
-
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
8290
|
-
}
|
|
8291
|
-
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
8292
|
-
let pendingPause;
|
|
8293
|
-
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
8294
|
-
if (pendingPause) {
|
|
8295
|
-
throw pendingPause;
|
|
8296
|
-
}
|
|
8297
|
-
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
8298
|
-
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
8299
|
-
}
|
|
8300
|
-
const providerLabel = formatProviderLabel(provider);
|
|
8301
|
-
const oauthResult = await startOAuthFlow(provider, {
|
|
8302
|
-
requesterId: deps.requesterId,
|
|
8303
|
-
channelId: deps.channelId,
|
|
8304
|
-
threadTs: deps.threadTs,
|
|
8305
|
-
userMessage: deps.userMessage,
|
|
8306
|
-
channelConfiguration: deps.channelConfiguration,
|
|
8307
|
-
activeSkillName: activeSkill?.name ?? void 0,
|
|
8308
|
-
resumeConversationId: deps.conversationId,
|
|
8309
|
-
resumeSessionId: deps.sessionId
|
|
8310
|
-
});
|
|
8311
|
-
if (!oauthResult.ok) {
|
|
8312
|
-
throw new Error(oauthResult.error);
|
|
8313
|
-
}
|
|
8314
|
-
if (!oauthResult.delivery) {
|
|
8315
|
-
throw new Error(
|
|
8316
|
-
`I need to connect your ${providerLabel} account first, but I wasn't able to send you a private authorization link. Please send me a direct message and try again.`
|
|
8317
|
-
);
|
|
8318
|
-
}
|
|
8319
|
-
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
8320
|
-
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
8321
|
-
}
|
|
8322
|
-
pendingPause = new PluginAuthorizationPauseError(provider);
|
|
8323
|
-
abortAgent();
|
|
8324
|
-
throw pendingPause;
|
|
8325
|
-
};
|
|
8326
|
-
const handleCredentialUnavailable = async (input) => {
|
|
8327
|
-
if (pendingPause) {
|
|
8328
|
-
throw pendingPause;
|
|
8329
|
-
}
|
|
8330
|
-
if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
|
|
8331
|
-
throw input.error;
|
|
8332
|
-
}
|
|
8333
|
-
return await startAuthorizationPause(
|
|
8334
|
-
input.error.provider,
|
|
8335
|
-
input.activeSkill
|
|
8336
|
-
);
|
|
8337
|
-
};
|
|
8338
|
-
return {
|
|
8339
|
-
handleCredentialUnavailable,
|
|
8340
|
-
handleCommandFailure: async (input) => {
|
|
8341
|
-
const provider = input.activeSkill?.pluginProvider;
|
|
8342
|
-
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
|
|
8343
|
-
return;
|
|
8344
|
-
}
|
|
8345
|
-
await startAuthorizationPause(provider, input.activeSkill, {
|
|
8346
|
-
unlinkExistingProvider: true
|
|
8347
|
-
});
|
|
8348
|
-
},
|
|
8349
|
-
getPendingPause: () => pendingPause
|
|
8350
|
-
};
|
|
8351
|
-
}
|
|
8352
8486
|
|
|
8353
8487
|
// src/chat/runtime/report-progress.ts
|
|
8354
8488
|
function buildReportedProgressStatus(input) {
|
|
@@ -8396,15 +8530,16 @@ function resolveCredentialInjection(toolName, command, capabilityRuntime, sandbo
|
|
|
8396
8530
|
const headerDomains = (headerTransforms ?? []).map(
|
|
8397
8531
|
(transform) => transform.domain
|
|
8398
8532
|
);
|
|
8533
|
+
const skillName = sandbox.getActiveSkill()?.name;
|
|
8399
8534
|
logInfo(
|
|
8400
8535
|
"credential_inject_start",
|
|
8401
8536
|
{},
|
|
8402
8537
|
{
|
|
8403
|
-
"app.skill.name":
|
|
8538
|
+
"app.skill.name": skillName,
|
|
8404
8539
|
"app.credential.delivery": "header_transform",
|
|
8405
8540
|
"app.credential.header_domains": headerDomains
|
|
8406
8541
|
},
|
|
8407
|
-
|
|
8542
|
+
`Injecting scoped credential headers for sandbox command (${skillName ?? "unknown skill"} \u2192 ${headerDomains.join(", ")})`
|
|
8408
8543
|
);
|
|
8409
8544
|
}
|
|
8410
8545
|
return { headerTransforms, env };
|
|
@@ -8460,8 +8595,10 @@ function getToolErrorAttributes(error) {
|
|
|
8460
8595
|
};
|
|
8461
8596
|
}
|
|
8462
8597
|
function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
|
|
8598
|
+
const errorType = getMcpAwareErrorType(error, "tool_execution_error");
|
|
8599
|
+
const errorMessage = getMcpAwareErrorMessage(error);
|
|
8463
8600
|
setSpanAttributes({
|
|
8464
|
-
"error.type":
|
|
8601
|
+
"error.type": errorType
|
|
8465
8602
|
});
|
|
8466
8603
|
if (shouldTrace) {
|
|
8467
8604
|
logWarn(
|
|
@@ -8472,8 +8609,8 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
8472
8609
|
"gen_ai.operation.name": "execute_tool",
|
|
8473
8610
|
"gen_ai.tool.name": toolName,
|
|
8474
8611
|
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
8475
|
-
"error.type":
|
|
8476
|
-
"error.message":
|
|
8612
|
+
"error.type": errorType,
|
|
8613
|
+
"error.message": errorMessage
|
|
8477
8614
|
},
|
|
8478
8615
|
"Agent tool call failed"
|
|
8479
8616
|
);
|
|
@@ -8507,7 +8644,6 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8507
8644
|
execute: async (toolCallId, params) => {
|
|
8508
8645
|
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
8509
8646
|
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
8510
|
-
onToolCall?.(toolName);
|
|
8511
8647
|
const traceToolContext = {
|
|
8512
8648
|
...spanContext,
|
|
8513
8649
|
conversationId: spanContext.conversationId,
|
|
@@ -8526,6 +8662,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8526
8662
|
spanContext,
|
|
8527
8663
|
async () => {
|
|
8528
8664
|
const parsed = params;
|
|
8665
|
+
onToolCall?.(toolName, parsed);
|
|
8529
8666
|
try {
|
|
8530
8667
|
if (typeof toolDef.execute !== "function") {
|
|
8531
8668
|
const resultDetails = { ok: true };
|
|
@@ -8577,7 +8714,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
8577
8714
|
}
|
|
8578
8715
|
return normalized;
|
|
8579
8716
|
} catch (error) {
|
|
8580
|
-
if (error instanceof
|
|
8717
|
+
if (error instanceof AuthorizationPauseError) {
|
|
8581
8718
|
throw error;
|
|
8582
8719
|
}
|
|
8583
8720
|
handleToolExecutionError(
|
|
@@ -8788,14 +8925,6 @@ function getTerminalAssistantMessages(messages) {
|
|
|
8788
8925
|
}
|
|
8789
8926
|
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
8790
8927
|
}
|
|
8791
|
-
function hasCompletedAssistantTurn(messages) {
|
|
8792
|
-
const message = getTerminalAssistantMessages(messages).at(-1);
|
|
8793
|
-
if (!message) {
|
|
8794
|
-
return false;
|
|
8795
|
-
}
|
|
8796
|
-
const stopReason = message.stopReason;
|
|
8797
|
-
return typeof stopReason === "string" && stopReason !== "error" && extractAssistantText(message).trim().length > 0;
|
|
8798
|
-
}
|
|
8799
8928
|
function upsertActiveSkill(activeSkills, next) {
|
|
8800
8929
|
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
8801
8930
|
if (existing) {
|
|
@@ -8803,24 +8932,11 @@ function upsertActiveSkill(activeSkills, next) {
|
|
|
8803
8932
|
existing.description = next.description;
|
|
8804
8933
|
existing.skillPath = next.skillPath;
|
|
8805
8934
|
existing.allowedTools = next.allowedTools;
|
|
8806
|
-
existing.usesConfig = next.usesConfig;
|
|
8807
8935
|
existing.pluginProvider = next.pluginProvider;
|
|
8808
8936
|
return;
|
|
8809
8937
|
}
|
|
8810
8938
|
activeSkills.push(next);
|
|
8811
8939
|
}
|
|
8812
|
-
function collectRelevantConfigurationKeys(activeSkills, explicitSkill) {
|
|
8813
|
-
const keys = /* @__PURE__ */ new Set();
|
|
8814
|
-
for (const skill of [
|
|
8815
|
-
...activeSkills,
|
|
8816
|
-
...explicitSkill ? [explicitSkill] : []
|
|
8817
|
-
]) {
|
|
8818
|
-
for (const key of skill.usesConfig ?? []) {
|
|
8819
|
-
keys.add(key);
|
|
8820
|
-
}
|
|
8821
|
-
}
|
|
8822
|
-
return [...keys].sort((a, b) => a.localeCompare(b));
|
|
8823
|
-
}
|
|
8824
8940
|
function trimTrailingAssistantMessages(messages) {
|
|
8825
8941
|
let end = messages.length;
|
|
8826
8942
|
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
|
|
@@ -9023,7 +9139,10 @@ function buildTurnResult(input) {
|
|
|
9023
9139
|
// src/chat/services/turn-thinking-level.ts
|
|
9024
9140
|
import { z } from "zod";
|
|
9025
9141
|
var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
|
|
9026
|
-
var MAX_ROUTER_CONTEXT_CHARS =
|
|
9142
|
+
var MAX_ROUTER_CONTEXT_CHARS = 8e3;
|
|
9143
|
+
var ROUTER_CONTEXT_HEAD_CHARS = 3e3;
|
|
9144
|
+
var ROUTER_CONTEXT_TAIL_CHARS = 5e3;
|
|
9145
|
+
var TRUNCATION_MARKER = "\n\u2026[truncated]\u2026\n";
|
|
9027
9146
|
var TURN_THINKING_LEVELS = ["none", "low", "medium", "high"];
|
|
9028
9147
|
var turnExecutionProfileSchema = z.object({
|
|
9029
9148
|
thinking_level: z.enum(TURN_THINKING_LEVELS),
|
|
@@ -9034,9 +9153,22 @@ var DEFAULT_THINKING_LEVEL = "low";
|
|
|
9034
9153
|
function trimContextForRouter(text) {
|
|
9035
9154
|
const trimmed = text?.trim();
|
|
9036
9155
|
if (!trimmed) {
|
|
9037
|
-
return
|
|
9156
|
+
return null;
|
|
9157
|
+
}
|
|
9158
|
+
if (trimmed.length <= MAX_ROUTER_CONTEXT_CHARS) {
|
|
9159
|
+
return {
|
|
9160
|
+
text: trimmed,
|
|
9161
|
+
truncated: false,
|
|
9162
|
+
originalCharCount: trimmed.length
|
|
9163
|
+
};
|
|
9038
9164
|
}
|
|
9039
|
-
|
|
9165
|
+
const head = trimmed.slice(0, ROUTER_CONTEXT_HEAD_CHARS).trimEnd();
|
|
9166
|
+
const tail = trimmed.slice(-ROUTER_CONTEXT_TAIL_CHARS).trimStart();
|
|
9167
|
+
return {
|
|
9168
|
+
text: `${head}${TRUNCATION_MARKER}${tail}`,
|
|
9169
|
+
truncated: true,
|
|
9170
|
+
originalCharCount: trimmed.length
|
|
9171
|
+
};
|
|
9040
9172
|
}
|
|
9041
9173
|
function buildClassifierSystemPrompt() {
|
|
9042
9174
|
return [
|
|
@@ -9048,22 +9180,23 @@ function buildClassifierSystemPrompt() {
|
|
|
9048
9180
|
"Use medium for investigations, ambiguous asks, multi-step analysis, or likely multi-tool work.",
|
|
9049
9181
|
"Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
|
|
9050
9182
|
"",
|
|
9183
|
+
"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.",
|
|
9184
|
+
"",
|
|
9051
9185
|
"Return JSON only with thinking_level, confidence, and reason."
|
|
9052
9186
|
].join("\n");
|
|
9053
9187
|
}
|
|
9054
9188
|
function buildClassifierPrompt(args) {
|
|
9055
9189
|
const sections = [];
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9190
|
+
if (args.conversationContext) {
|
|
9191
|
+
sections.push(
|
|
9192
|
+
"<thread-background>",
|
|
9193
|
+
args.conversationContext.text,
|
|
9194
|
+
"</thread-background>",
|
|
9195
|
+
""
|
|
9196
|
+
);
|
|
9059
9197
|
}
|
|
9060
9198
|
sections.push(
|
|
9061
|
-
"<
|
|
9062
|
-
`- active_skills: ${args.activeSkillNames.join(", ") || "none"}`,
|
|
9063
|
-
`- attachment_count: ${args.attachmentCount}`,
|
|
9064
|
-
"</turn-context>",
|
|
9065
|
-
"",
|
|
9066
|
-
'<current-instruction priority="highest">',
|
|
9199
|
+
"<current-instruction>",
|
|
9067
9200
|
args.messageText.trim() || "[empty]",
|
|
9068
9201
|
"</current-instruction>"
|
|
9069
9202
|
);
|
|
@@ -9077,42 +9210,81 @@ function buildClassifierPrompt(args) {
|
|
|
9077
9210
|
return sections.join("\n");
|
|
9078
9211
|
}
|
|
9079
9212
|
async function selectTurnThinkingLevel(args) {
|
|
9080
|
-
const
|
|
9213
|
+
const trimmedContext = trimContextForRouter(args.conversationContext);
|
|
9214
|
+
const instructionLength = args.messageText.trim().length;
|
|
9215
|
+
const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
|
|
9216
|
+
(block) => block.trim().length > 0
|
|
9217
|
+
).length;
|
|
9218
|
+
const prompt = buildClassifierPrompt({
|
|
9219
|
+
conversationContext: trimmedContext,
|
|
9220
|
+
currentTurnBlocks: args.currentTurnBlocks,
|
|
9221
|
+
messageText: args.messageText
|
|
9222
|
+
});
|
|
9223
|
+
const logContext = {
|
|
9224
|
+
slackThreadId: args.context?.threadId,
|
|
9225
|
+
slackChannelId: args.context?.channelId,
|
|
9226
|
+
slackUserId: args.context?.requesterId,
|
|
9227
|
+
runId: args.context?.runId,
|
|
9228
|
+
modelId: args.fastModelId
|
|
9229
|
+
};
|
|
9230
|
+
return withSpan(
|
|
9231
|
+
"chat.route_thinking",
|
|
9232
|
+
"chat.route_thinking",
|
|
9233
|
+
logContext,
|
|
9234
|
+
async () => {
|
|
9235
|
+
setSpanAttributes({
|
|
9236
|
+
"app.ai.router.prompt_char_count": prompt.length,
|
|
9237
|
+
"app.ai.router.instruction_char_count": instructionLength,
|
|
9238
|
+
"app.ai.router.context_char_count": trimmedContext?.originalCharCount ?? 0,
|
|
9239
|
+
"app.ai.router.context_trimmed": trimmedContext?.truncated ?? false,
|
|
9240
|
+
"app.ai.router.turn_block_count": turnBlockCount
|
|
9241
|
+
});
|
|
9242
|
+
const selection = await classifyTurn({
|
|
9243
|
+
completeObject: args.completeObject,
|
|
9244
|
+
fastModelId: args.fastModelId,
|
|
9245
|
+
metadata: {
|
|
9246
|
+
modelId: args.fastModelId,
|
|
9247
|
+
threadId: args.context?.threadId ?? "",
|
|
9248
|
+
channelId: args.context?.channelId ?? "",
|
|
9249
|
+
requesterId: args.context?.requesterId ?? "",
|
|
9250
|
+
runId: args.context?.runId ?? ""
|
|
9251
|
+
},
|
|
9252
|
+
prompt
|
|
9253
|
+
});
|
|
9254
|
+
setSpanAttributes({
|
|
9255
|
+
"app.ai.thinking_level": selection.thinkingLevel,
|
|
9256
|
+
"app.ai.thinking_level_reason": selection.reason,
|
|
9257
|
+
...selection.confidence !== void 0 ? { "app.ai.thinking_level_confidence": selection.confidence } : {}
|
|
9258
|
+
});
|
|
9259
|
+
return selection;
|
|
9260
|
+
}
|
|
9261
|
+
);
|
|
9262
|
+
}
|
|
9263
|
+
async function classifyTurn(args) {
|
|
9081
9264
|
try {
|
|
9082
9265
|
const result = await args.completeObject({
|
|
9083
9266
|
modelId: args.fastModelId,
|
|
9084
9267
|
schema: turnExecutionProfileSchema,
|
|
9085
9268
|
maxTokens: 120,
|
|
9086
|
-
metadata:
|
|
9087
|
-
|
|
9088
|
-
threadId: args.context?.threadId ?? "",
|
|
9089
|
-
channelId: args.context?.channelId ?? "",
|
|
9090
|
-
requesterId: args.context?.requesterId ?? "",
|
|
9091
|
-
runId: args.context?.runId ?? ""
|
|
9092
|
-
},
|
|
9093
|
-
prompt: buildClassifierPrompt({
|
|
9094
|
-
activeSkillNames,
|
|
9095
|
-
attachmentCount: args.attachmentCount ?? 0,
|
|
9096
|
-
conversationContext: args.conversationContext,
|
|
9097
|
-
currentTurnBlocks: args.currentTurnBlocks,
|
|
9098
|
-
messageText: args.messageText
|
|
9099
|
-
}),
|
|
9269
|
+
metadata: args.metadata,
|
|
9270
|
+
prompt: args.prompt,
|
|
9100
9271
|
thinkingLevel: "low",
|
|
9101
9272
|
system: buildClassifierSystemPrompt(),
|
|
9102
9273
|
temperature: 0
|
|
9103
9274
|
});
|
|
9104
9275
|
const parsed = turnExecutionProfileSchema.parse(result.object);
|
|
9276
|
+
const reason = parsed.reason.trim();
|
|
9105
9277
|
if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
|
|
9106
9278
|
return {
|
|
9107
9279
|
confidence: parsed.confidence,
|
|
9108
9280
|
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
9109
|
-
reason: `low_confidence_default:${
|
|
9281
|
+
reason: `low_confidence_default:${reason}`
|
|
9110
9282
|
};
|
|
9111
9283
|
}
|
|
9112
9284
|
return {
|
|
9113
9285
|
confidence: parsed.confidence,
|
|
9114
9286
|
thinkingLevel: parsed.thinking_level,
|
|
9115
|
-
reason
|
|
9287
|
+
reason
|
|
9116
9288
|
};
|
|
9117
9289
|
} catch {
|
|
9118
9290
|
return {
|
|
@@ -9150,7 +9322,7 @@ function parseAgentTurnSessionCheckpoint(value) {
|
|
|
9150
9322
|
return void 0;
|
|
9151
9323
|
}
|
|
9152
9324
|
const status = parsed.state;
|
|
9153
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
|
|
9325
|
+
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
9154
9326
|
return void 0;
|
|
9155
9327
|
}
|
|
9156
9328
|
const conversationId = parsed.conversationId;
|
|
@@ -9222,6 +9394,26 @@ async function upsertAgentTurnSessionCheckpoint(args) {
|
|
|
9222
9394
|
);
|
|
9223
9395
|
return checkpoint;
|
|
9224
9396
|
}
|
|
9397
|
+
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
9398
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
9399
|
+
args.conversationId,
|
|
9400
|
+
args.sessionId
|
|
9401
|
+
);
|
|
9402
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
9403
|
+
return void 0;
|
|
9404
|
+
}
|
|
9405
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
9406
|
+
conversationId: existing.conversationId,
|
|
9407
|
+
sessionId: existing.sessionId,
|
|
9408
|
+
sliceId: existing.sliceId,
|
|
9409
|
+
state: "superseded",
|
|
9410
|
+
piMessages: existing.piMessages,
|
|
9411
|
+
loadedSkillNames: existing.loadedSkillNames,
|
|
9412
|
+
resumeReason: existing.resumeReason,
|
|
9413
|
+
resumedFromSliceId: existing.resumedFromSliceId,
|
|
9414
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
9415
|
+
});
|
|
9416
|
+
}
|
|
9225
9417
|
|
|
9226
9418
|
// src/chat/services/turn-checkpoint.ts
|
|
9227
9419
|
async function loadTurnCheckpoint(ctx) {
|
|
@@ -9334,77 +9526,299 @@ async function persistTimeoutCheckpoint(args) {
|
|
|
9334
9526
|
);
|
|
9335
9527
|
return void 0;
|
|
9336
9528
|
}
|
|
9337
|
-
}
|
|
9338
|
-
|
|
9339
|
-
// src/chat/services/
|
|
9340
|
-
var
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9529
|
+
}
|
|
9530
|
+
|
|
9531
|
+
// src/chat/services/pending-auth.ts
|
|
9532
|
+
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
9533
|
+
function canReusePendingAuthLink(args) {
|
|
9534
|
+
const { pendingAuth } = args;
|
|
9535
|
+
if (!pendingAuth) {
|
|
9536
|
+
return false;
|
|
9537
|
+
}
|
|
9538
|
+
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
9539
|
+
}
|
|
9540
|
+
function buildAuthPauseReplyText(args) {
|
|
9541
|
+
const providerLabel = args.provider ? formatProviderLabel(args.provider) : "";
|
|
9542
|
+
if (args.disposition === "link_already_sent") {
|
|
9543
|
+
return providerLabel ? `I still need your ${providerLabel} access to continue. I already sent you a private link.` : "I still need additional access to continue. I already sent you a private link.";
|
|
9544
|
+
}
|
|
9545
|
+
return providerLabel ? `I need your ${providerLabel} access to continue. I sent you a private link.` : "I need additional access to continue. I sent you a private link.";
|
|
9546
|
+
}
|
|
9547
|
+
function getConversationPendingAuth(args) {
|
|
9548
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
9549
|
+
if (!pendingAuth) {
|
|
9550
|
+
return void 0;
|
|
9551
|
+
}
|
|
9552
|
+
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
9553
|
+
return void 0;
|
|
9554
|
+
}
|
|
9555
|
+
return pendingAuth;
|
|
9556
|
+
}
|
|
9557
|
+
function clearPendingAuth(conversation, sessionId) {
|
|
9558
|
+
if (!conversation.processing.pendingAuth) {
|
|
9559
|
+
return;
|
|
9560
|
+
}
|
|
9561
|
+
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
9562
|
+
return;
|
|
9563
|
+
}
|
|
9564
|
+
conversation.processing.pendingAuth = void 0;
|
|
9565
|
+
}
|
|
9566
|
+
async function applyPendingAuthUpdate(args) {
|
|
9567
|
+
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
9568
|
+
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
9569
|
+
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
9570
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
9571
|
+
conversationId: args.conversationId,
|
|
9572
|
+
sessionId: previousPendingAuth.sessionId,
|
|
9573
|
+
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
9574
|
+
});
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
9578
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
9579
|
+
const message = conversation.messages[index];
|
|
9580
|
+
if (message?.role !== "user") {
|
|
9581
|
+
continue;
|
|
9582
|
+
}
|
|
9583
|
+
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
9584
|
+
}
|
|
9585
|
+
return false;
|
|
9586
|
+
}
|
|
9587
|
+
|
|
9588
|
+
// src/chat/services/mcp-auth-orchestration.ts
|
|
9589
|
+
var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
9590
|
+
constructor(provider, disposition) {
|
|
9591
|
+
super("mcp", provider, disposition);
|
|
9592
|
+
}
|
|
9593
|
+
};
|
|
9594
|
+
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
9595
|
+
let pendingPause;
|
|
9596
|
+
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
9597
|
+
const authProviderFactory = async (plugin) => {
|
|
9598
|
+
if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
|
|
9599
|
+
return void 0;
|
|
9600
|
+
}
|
|
9601
|
+
const provider = await createMcpOAuthClientProvider({
|
|
9602
|
+
provider: plugin.manifest.name,
|
|
9603
|
+
conversationId: deps.conversationId,
|
|
9604
|
+
sessionId: deps.sessionId,
|
|
9605
|
+
userId: deps.requesterId,
|
|
9606
|
+
userMessage: deps.userMessage,
|
|
9607
|
+
...deps.channelId ? { channelId: deps.channelId } : {},
|
|
9608
|
+
...deps.threadTs ? { threadTs: deps.threadTs } : {},
|
|
9609
|
+
...deps.toolChannelId ? { toolChannelId: deps.toolChannelId } : {},
|
|
9610
|
+
configuration: deps.getConfiguration(),
|
|
9611
|
+
artifactState: deps.getArtifactState()
|
|
9612
|
+
});
|
|
9613
|
+
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
9614
|
+
return provider;
|
|
9615
|
+
};
|
|
9616
|
+
const onAuthorizationRequired = async (provider) => {
|
|
9617
|
+
if (pendingPause) {
|
|
9618
|
+
return true;
|
|
9619
|
+
}
|
|
9620
|
+
const authSessionId = authSessionIdsByProvider.get(provider);
|
|
9621
|
+
if (!authSessionId || !deps.requesterId) {
|
|
9622
|
+
throw new Error(
|
|
9623
|
+
`Missing MCP auth session context for plugin "${provider}"`
|
|
9624
|
+
);
|
|
9625
|
+
}
|
|
9626
|
+
const latestArtifactState = deps.getMergedArtifactState();
|
|
9627
|
+
await patchMcpAuthSession(authSessionId, {
|
|
9628
|
+
configuration: { ...deps.getConfiguration() },
|
|
9629
|
+
artifactState: latestArtifactState,
|
|
9630
|
+
toolChannelId: deps.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? deps.channelId
|
|
9631
|
+
});
|
|
9632
|
+
const authSession = await getMcpAuthSession(authSessionId);
|
|
9633
|
+
if (!authSession?.authorizationUrl) {
|
|
9634
|
+
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
9635
|
+
}
|
|
9636
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
9637
|
+
pendingAuth: deps.currentPendingAuth,
|
|
9638
|
+
kind: "mcp",
|
|
9639
|
+
provider,
|
|
9640
|
+
requesterId: deps.requesterId
|
|
9641
|
+
});
|
|
9642
|
+
if (!reusingPendingLink) {
|
|
9643
|
+
const delivery = await deliverPrivateMessage({
|
|
9644
|
+
channelId: authSession.channelId,
|
|
9645
|
+
threadTs: authSession.threadTs,
|
|
9646
|
+
userId: authSession.userId,
|
|
9647
|
+
text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
|
|
9648
|
+
});
|
|
9649
|
+
if (!delivery) {
|
|
9650
|
+
throw new Error(
|
|
9651
|
+
`Unable to deliver MCP authorization link for plugin "${provider}"`
|
|
9652
|
+
);
|
|
9653
|
+
}
|
|
9654
|
+
} else {
|
|
9655
|
+
await deleteMcpAuthSession(authSessionId);
|
|
9656
|
+
}
|
|
9657
|
+
if (deps.sessionId && deps.requesterId) {
|
|
9658
|
+
await deps.onPendingAuth?.({
|
|
9659
|
+
kind: "mcp",
|
|
9660
|
+
provider,
|
|
9661
|
+
requesterId: deps.requesterId,
|
|
9662
|
+
sessionId: deps.sessionId,
|
|
9663
|
+
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9666
|
+
pendingPause = new McpAuthorizationPauseError(
|
|
9667
|
+
provider,
|
|
9668
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
9669
|
+
);
|
|
9670
|
+
abortAgent();
|
|
9671
|
+
return true;
|
|
9672
|
+
};
|
|
9673
|
+
return {
|
|
9674
|
+
authProviderFactory,
|
|
9675
|
+
onAuthorizationRequired,
|
|
9676
|
+
getPendingPause: () => pendingPause
|
|
9677
|
+
};
|
|
9678
|
+
}
|
|
9679
|
+
|
|
9680
|
+
// src/chat/credentials/unlink-provider.ts
|
|
9681
|
+
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
9682
|
+
await Promise.all([
|
|
9683
|
+
userTokenStore.delete(userId, provider),
|
|
9684
|
+
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
9685
|
+
deleteMcpServerSessionId(userId, provider),
|
|
9686
|
+
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
9687
|
+
]);
|
|
9688
|
+
}
|
|
9689
|
+
|
|
9690
|
+
// src/chat/services/plugin-auth-orchestration.ts
|
|
9691
|
+
var PluginAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
9692
|
+
constructor(provider, disposition) {
|
|
9693
|
+
super("plugin", provider, disposition);
|
|
9694
|
+
}
|
|
9695
|
+
};
|
|
9696
|
+
function isCommandAuthFailure(details) {
|
|
9697
|
+
if (!details || typeof details !== "object") {
|
|
9698
|
+
return false;
|
|
9699
|
+
}
|
|
9700
|
+
const result = details;
|
|
9701
|
+
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
9702
|
+
return false;
|
|
9703
|
+
}
|
|
9704
|
+
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
9705
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
9706
|
+
if (!text.trim()) {
|
|
9707
|
+
return false;
|
|
9708
|
+
}
|
|
9709
|
+
return [
|
|
9710
|
+
/\b401\b/,
|
|
9711
|
+
/\bunauthorized\b/,
|
|
9712
|
+
/\bbad credentials\b/,
|
|
9713
|
+
/\binvalid token\b/,
|
|
9714
|
+
/\btoken (?:expired|revoked)\b/,
|
|
9715
|
+
/\bexpired token\b/,
|
|
9716
|
+
/\bmissing scopes?\b/,
|
|
9717
|
+
/\binsufficient scope\b/,
|
|
9718
|
+
/\binvalid grant\b/,
|
|
9719
|
+
/\breauthoriz/
|
|
9720
|
+
].some((pattern) => pattern.test(text));
|
|
9721
|
+
}
|
|
9722
|
+
function commandTargetsProvider(provider, command, details) {
|
|
9723
|
+
const normalizedCommand = command.trim().toLowerCase();
|
|
9724
|
+
if (!normalizedCommand) {
|
|
9725
|
+
return false;
|
|
9726
|
+
}
|
|
9727
|
+
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
9728
|
+
return true;
|
|
9346
9729
|
}
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9730
|
+
const plugin = getPluginDefinition(provider);
|
|
9731
|
+
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
9732
|
+
const credentials = plugin?.manifest.credentials;
|
|
9733
|
+
if (credentials) {
|
|
9734
|
+
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
9735
|
+
for (const domain of credentials.apiDomains) {
|
|
9736
|
+
candidates.add(domain.toLowerCase());
|
|
9354
9737
|
}
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
configuration: deps.getConfiguration(),
|
|
9365
|
-
artifactState: deps.getArtifactState()
|
|
9366
|
-
});
|
|
9367
|
-
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
9368
|
-
return provider;
|
|
9369
|
-
};
|
|
9370
|
-
const onAuthorizationRequired = async (provider) => {
|
|
9738
|
+
}
|
|
9739
|
+
const combinedText = `${normalizedCommand}
|
|
9740
|
+
${details.stdout?.toLowerCase() ?? ""}
|
|
9741
|
+
${details.stderr?.toLowerCase() ?? ""}`;
|
|
9742
|
+
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
9743
|
+
}
|
|
9744
|
+
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
9745
|
+
let pendingPause;
|
|
9746
|
+
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
9371
9747
|
if (pendingPause) {
|
|
9372
|
-
|
|
9748
|
+
throw pendingPause;
|
|
9373
9749
|
}
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
throw new Error(
|
|
9377
|
-
`Missing MCP auth session context for plugin "${provider}"`
|
|
9378
|
-
);
|
|
9750
|
+
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
9751
|
+
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
9379
9752
|
}
|
|
9380
|
-
const
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9753
|
+
const providerLabel = formatProviderLabel(provider);
|
|
9754
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
9755
|
+
pendingAuth: deps.currentPendingAuth,
|
|
9756
|
+
kind: "plugin",
|
|
9757
|
+
provider,
|
|
9758
|
+
requesterId: deps.requesterId
|
|
9385
9759
|
});
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9760
|
+
if (!reusingPendingLink) {
|
|
9761
|
+
const oauthResult = await startOAuthFlow(provider, {
|
|
9762
|
+
requesterId: deps.requesterId,
|
|
9763
|
+
channelId: deps.channelId,
|
|
9764
|
+
threadTs: deps.threadTs,
|
|
9765
|
+
userMessage: deps.userMessage,
|
|
9766
|
+
channelConfiguration: deps.channelConfiguration,
|
|
9767
|
+
activeSkillName: activeSkill?.name ?? void 0,
|
|
9768
|
+
resumeConversationId: deps.conversationId,
|
|
9769
|
+
resumeSessionId: deps.sessionId
|
|
9770
|
+
});
|
|
9771
|
+
if (!oauthResult.ok) {
|
|
9772
|
+
throw new Error(oauthResult.error);
|
|
9773
|
+
}
|
|
9774
|
+
if (!oauthResult.delivery) {
|
|
9775
|
+
throw new Error(
|
|
9776
|
+
`I need to connect your ${providerLabel} account first, but I wasn't able to send you a private authorization link. Please send me a direct message and try again.`
|
|
9777
|
+
);
|
|
9778
|
+
}
|
|
9389
9779
|
}
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9780
|
+
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
9781
|
+
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
9782
|
+
}
|
|
9783
|
+
if (deps.sessionId) {
|
|
9784
|
+
await deps.onPendingAuth?.({
|
|
9785
|
+
kind: "plugin",
|
|
9786
|
+
provider,
|
|
9787
|
+
requesterId: deps.requesterId,
|
|
9788
|
+
sessionId: deps.sessionId,
|
|
9789
|
+
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
9790
|
+
});
|
|
9400
9791
|
}
|
|
9401
|
-
pendingPause = new
|
|
9792
|
+
pendingPause = new PluginAuthorizationPauseError(
|
|
9793
|
+
provider,
|
|
9794
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
9795
|
+
);
|
|
9402
9796
|
abortAgent();
|
|
9403
|
-
|
|
9797
|
+
throw pendingPause;
|
|
9798
|
+
};
|
|
9799
|
+
const handleCredentialUnavailable = async (input) => {
|
|
9800
|
+
if (pendingPause) {
|
|
9801
|
+
throw pendingPause;
|
|
9802
|
+
}
|
|
9803
|
+
if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
|
|
9804
|
+
throw input.error;
|
|
9805
|
+
}
|
|
9806
|
+
return await startAuthorizationPause(
|
|
9807
|
+
input.error.provider,
|
|
9808
|
+
input.activeSkill
|
|
9809
|
+
);
|
|
9404
9810
|
};
|
|
9405
9811
|
return {
|
|
9406
|
-
|
|
9407
|
-
|
|
9812
|
+
handleCredentialUnavailable,
|
|
9813
|
+
handleCommandFailure: async (input) => {
|
|
9814
|
+
const provider = input.activeSkill?.pluginProvider;
|
|
9815
|
+
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
|
|
9816
|
+
return;
|
|
9817
|
+
}
|
|
9818
|
+
await startAuthorizationPause(provider, input.activeSkill, {
|
|
9819
|
+
unlinkExistingProvider: true
|
|
9820
|
+
});
|
|
9821
|
+
},
|
|
9408
9822
|
getPendingPause: () => pendingPause
|
|
9409
9823
|
};
|
|
9410
9824
|
}
|
|
@@ -9505,26 +9919,13 @@ function buildUserTurnInput(args) {
|
|
|
9505
9919
|
}
|
|
9506
9920
|
return { routerBlocks, userContentParts };
|
|
9507
9921
|
}
|
|
9508
|
-
function mcpToolsToDefinitions(mcpTools) {
|
|
9509
|
-
const defs = {};
|
|
9510
|
-
for (const tool2 of mcpTools) {
|
|
9511
|
-
defs[tool2.name] = {
|
|
9512
|
-
description: tool2.description,
|
|
9513
|
-
// Raw JSON Schema from MCP servers — not a TypeBox TSchema, but
|
|
9514
|
-
// pi-agent-core validates with AJV and the Anthropic provider reads
|
|
9515
|
-
// .properties/.required, so raw JSON Schema works at runtime.
|
|
9516
|
-
inputSchema: tool2.parameters,
|
|
9517
|
-
execute: async (args) => tool2.execute(args)
|
|
9518
|
-
};
|
|
9519
|
-
}
|
|
9520
|
-
return defs;
|
|
9521
|
-
}
|
|
9522
9922
|
async function generateAssistantReply(messageText, context = {}) {
|
|
9523
9923
|
const replyStartedAtMs = Date.now();
|
|
9524
9924
|
let timeoutResumeConversationId;
|
|
9525
9925
|
let timeoutResumeSessionId;
|
|
9526
9926
|
let timeoutResumeSliceId = 1;
|
|
9527
9927
|
let timeoutResumeMessages = [];
|
|
9928
|
+
let beforeMessageCount = 0;
|
|
9528
9929
|
let lastKnownSandboxId = context.sandbox?.sandboxId;
|
|
9529
9930
|
let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
|
|
9530
9931
|
let loadedSkillNamesForResume = [];
|
|
@@ -9717,8 +10118,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9717
10118
|
userTurnText
|
|
9718
10119
|
});
|
|
9719
10120
|
thinkingSelection = await selectTurnThinkingLevel({
|
|
9720
|
-
activeSkillNames: activeSkills.map((skill) => skill.name),
|
|
9721
|
-
attachmentCount: context.userAttachments?.length,
|
|
9722
10121
|
completeObject,
|
|
9723
10122
|
conversationContext: context.conversationContext,
|
|
9724
10123
|
context: {
|
|
@@ -9754,9 +10153,11 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9754
10153
|
threadTs: context.correlation?.threadTs,
|
|
9755
10154
|
toolChannelId: context.toolChannelId,
|
|
9756
10155
|
userMessage: userInput,
|
|
10156
|
+
currentPendingAuth: context.pendingAuth,
|
|
9757
10157
|
getConfiguration: () => configurationValues,
|
|
9758
10158
|
getArtifactState: () => context.artifactState,
|
|
9759
|
-
getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch)
|
|
10159
|
+
getMergedArtifactState: () => mergeArtifactsState(context.artifactState ?? {}, artifactStatePatch),
|
|
10160
|
+
onPendingAuth: context.onAuthPending
|
|
9760
10161
|
},
|
|
9761
10162
|
() => agent?.abort()
|
|
9762
10163
|
);
|
|
@@ -9769,6 +10170,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9769
10170
|
threadTs: context.correlation?.threadTs,
|
|
9770
10171
|
userMessage: userInput,
|
|
9771
10172
|
channelConfiguration: context.channelConfiguration,
|
|
10173
|
+
currentPendingAuth: context.pendingAuth,
|
|
10174
|
+
onPendingAuth: context.onAuthPending,
|
|
9772
10175
|
userTokenStore
|
|
9773
10176
|
},
|
|
9774
10177
|
() => agent?.abort()
|
|
@@ -9849,11 +10252,18 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9849
10252
|
if (!effective.pluginProvider) {
|
|
9850
10253
|
return void 0;
|
|
9851
10254
|
}
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
10255
|
+
if (!turnMcpToolManager.getActiveProviders().includes(effective.pluginProvider)) {
|
|
10256
|
+
return void 0;
|
|
10257
|
+
}
|
|
10258
|
+
const availableToolCount = turnMcpToolManager.getActiveToolCatalog(
|
|
10259
|
+
activeSkills,
|
|
10260
|
+
{
|
|
9855
10261
|
provider: effective.pluginProvider
|
|
9856
|
-
}
|
|
10262
|
+
}
|
|
10263
|
+
).length;
|
|
10264
|
+
return {
|
|
10265
|
+
mcp_provider: effective.pluginProvider,
|
|
10266
|
+
available_tool_count: availableToolCount
|
|
9857
10267
|
};
|
|
9858
10268
|
}
|
|
9859
10269
|
},
|
|
@@ -9883,22 +10293,20 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9883
10293
|
await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
|
|
9884
10294
|
}
|
|
9885
10295
|
syncResumeState();
|
|
9886
|
-
const
|
|
10296
|
+
const activeMcpCatalogs = toActiveMcpCatalogSummaries(
|
|
10297
|
+
turnMcpToolManager.getActiveToolCatalog(activeSkills)
|
|
10298
|
+
);
|
|
9887
10299
|
baseInstructions = buildSystemPrompt({
|
|
9888
10300
|
availableSkills,
|
|
9889
10301
|
activeSkills,
|
|
9890
|
-
|
|
10302
|
+
activeMcpCatalogs,
|
|
9891
10303
|
invocation: skillInvocation,
|
|
9892
10304
|
assistant: context.assistant,
|
|
9893
10305
|
requester: context.requester,
|
|
9894
10306
|
artifactState: context.artifactState,
|
|
9895
10307
|
configuration: configurationValues,
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
invokedSkill
|
|
9899
|
-
),
|
|
9900
|
-
runtimeMetadata: getRuntimeMetadata(),
|
|
9901
|
-
threadParticipants: context.threadParticipants
|
|
10308
|
+
threadParticipants: context.threadParticipants,
|
|
10309
|
+
turnState: resumedFromCheckpoint ? "resumed" : "fresh"
|
|
9902
10310
|
});
|
|
9903
10311
|
const inputMessagesAttribute = serializeGenAiAttribute([
|
|
9904
10312
|
{
|
|
@@ -9910,10 +10318,23 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9910
10318
|
content: userContentParts.map((part) => toObservablePromptPart(part))
|
|
9911
10319
|
}
|
|
9912
10320
|
]);
|
|
9913
|
-
const onToolCall = (toolName) => {
|
|
10321
|
+
const onToolCall = (toolName, params) => {
|
|
9914
10322
|
toolCalls.push(toolName);
|
|
10323
|
+
try {
|
|
10324
|
+
context.onToolInvocation?.({ toolName, params });
|
|
10325
|
+
} catch (error) {
|
|
10326
|
+
logWarn(
|
|
10327
|
+
"tool_invocation_observer_failed",
|
|
10328
|
+
spanContext,
|
|
10329
|
+
{
|
|
10330
|
+
"gen_ai.tool.name": toolName,
|
|
10331
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
10332
|
+
},
|
|
10333
|
+
"Tool invocation observer failed"
|
|
10334
|
+
);
|
|
10335
|
+
}
|
|
9915
10336
|
};
|
|
9916
|
-
const
|
|
10337
|
+
const agentTools = createAgentTools(
|
|
9917
10338
|
tools,
|
|
9918
10339
|
skillSandbox,
|
|
9919
10340
|
spanContext,
|
|
@@ -9923,24 +10344,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9923
10344
|
pluginAuth,
|
|
9924
10345
|
onToolCall
|
|
9925
10346
|
);
|
|
9926
|
-
const agentTools = [...baseAgentTools];
|
|
9927
|
-
const syncMcpAgentTools = () => {
|
|
9928
|
-
const mcpTools = turnMcpToolManager.getResolvedActiveTools(activeSkills);
|
|
9929
|
-
const mcpDefs = mcpToolsToDefinitions(mcpTools);
|
|
9930
|
-
const mcpAgentTools = createAgentTools(
|
|
9931
|
-
mcpDefs,
|
|
9932
|
-
skillSandbox,
|
|
9933
|
-
spanContext,
|
|
9934
|
-
context.onStatus,
|
|
9935
|
-
sandboxExecutor,
|
|
9936
|
-
capabilityRuntime,
|
|
9937
|
-
pluginAuth,
|
|
9938
|
-
onToolCall
|
|
9939
|
-
);
|
|
9940
|
-
agentTools.length = 0;
|
|
9941
|
-
agentTools.push(...baseAgentTools, ...mcpAgentTools);
|
|
9942
|
-
};
|
|
9943
|
-
syncMcpAgentTools();
|
|
9944
10347
|
agent = new Agent({
|
|
9945
10348
|
getApiKey: () => getPiGatewayApiKeyOverride(),
|
|
9946
10349
|
initialState: {
|
|
@@ -9987,9 +10390,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9987
10390
|
);
|
|
9988
10391
|
});
|
|
9989
10392
|
});
|
|
9990
|
-
let beforeMessageCount = agent.state.messages.length;
|
|
9991
10393
|
let newMessages = [];
|
|
9992
|
-
|
|
10394
|
+
beforeMessageCount = agent.state.messages.length;
|
|
9993
10395
|
try {
|
|
9994
10396
|
if (resumedFromCheckpoint) {
|
|
9995
10397
|
agent.state.messages = existingCheckpoint.piMessages;
|
|
@@ -10056,11 +10458,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10056
10458
|
}
|
|
10057
10459
|
}
|
|
10058
10460
|
newMessages = agent.state.messages.slice(beforeMessageCount);
|
|
10059
|
-
completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
|
|
10060
|
-
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10061
|
-
timeoutResumeMessages = [...agent.state.messages];
|
|
10062
|
-
throw getPendingAuthPause();
|
|
10063
|
-
}
|
|
10064
10461
|
const outputMessages = newMessages.filter(isAssistantMessage);
|
|
10065
10462
|
const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
|
|
10066
10463
|
const usageSummary = extractGenAiUsageSummary(
|
|
@@ -10076,6 +10473,10 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10076
10473
|
...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
|
|
10077
10474
|
...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
|
|
10078
10475
|
});
|
|
10476
|
+
if (getPendingAuthPause()) {
|
|
10477
|
+
timeoutResumeMessages = [...agent.state.messages];
|
|
10478
|
+
throw getPendingAuthPause();
|
|
10479
|
+
}
|
|
10079
10480
|
},
|
|
10080
10481
|
{
|
|
10081
10482
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
@@ -10088,9 +10489,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10088
10489
|
} finally {
|
|
10089
10490
|
unsubscribe();
|
|
10090
10491
|
}
|
|
10091
|
-
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10092
|
-
throw getPendingAuthPause();
|
|
10093
|
-
}
|
|
10094
10492
|
if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
10095
10493
|
await persistCompletedCheckpoint({
|
|
10096
10494
|
conversationId: sessionConversationId,
|
|
@@ -10148,7 +10546,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10148
10546
|
);
|
|
10149
10547
|
}
|
|
10150
10548
|
}
|
|
10151
|
-
if (
|
|
10549
|
+
if (error instanceof AuthorizationPauseError && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
10550
|
+
if (!turnUsage && timeoutResumeMessages.length > 0) {
|
|
10551
|
+
const fallbackUsage = extractGenAiUsageSummary(
|
|
10552
|
+
...timeoutResumeMessages.slice(beforeMessageCount).filter(isAssistantMessage)
|
|
10553
|
+
);
|
|
10554
|
+
turnUsage = Object.values(fallbackUsage).some(
|
|
10555
|
+
(value) => value !== void 0
|
|
10556
|
+
) ? fallbackUsage : void 0;
|
|
10557
|
+
}
|
|
10152
10558
|
const nextSliceId = await persistAuthPauseCheckpoint({
|
|
10153
10559
|
conversationId: timeoutResumeConversationId,
|
|
10154
10560
|
sessionId: timeoutResumeSessionId,
|
|
@@ -10166,9 +10572,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10166
10572
|
}
|
|
10167
10573
|
});
|
|
10168
10574
|
throw new RetryableTurnError(
|
|
10169
|
-
error
|
|
10575
|
+
error.kind === "plugin" ? "plugin_auth_resume" : "mcp_auth_resume",
|
|
10170
10576
|
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`,
|
|
10171
10577
|
{
|
|
10578
|
+
authDisposition: error.disposition,
|
|
10579
|
+
authDurationMs: Date.now() - replyStartedAtMs,
|
|
10580
|
+
authKind: error.kind,
|
|
10581
|
+
authProvider: error.provider,
|
|
10582
|
+
authThinkingLevel: thinkingSelection?.thinkingLevel,
|
|
10583
|
+
authUsage: turnUsage,
|
|
10172
10584
|
conversationId: timeoutResumeConversationId,
|
|
10173
10585
|
sessionId: timeoutResumeSessionId,
|
|
10174
10586
|
sliceId: nextSliceId
|
|
@@ -11041,6 +11453,82 @@ async function resumeAuthorizedRequest(args) {
|
|
|
11041
11453
|
});
|
|
11042
11454
|
}
|
|
11043
11455
|
|
|
11456
|
+
// src/chat/runtime/auth-pause-reply.ts
|
|
11457
|
+
function buildAuthPauseSlackMessage(args) {
|
|
11458
|
+
const footer = buildSlackReplyFooter({
|
|
11459
|
+
conversationId: args.conversationId,
|
|
11460
|
+
durationMs: args.durationMs,
|
|
11461
|
+
thinkingLevel: args.thinkingLevel,
|
|
11462
|
+
usage: args.usage
|
|
11463
|
+
});
|
|
11464
|
+
const blocks = buildSlackReplyBlocks(args.text, footer);
|
|
11465
|
+
return blocks ? { text: args.text, blocks } : { text: args.text };
|
|
11466
|
+
}
|
|
11467
|
+
function completeAuthPauseTurn(args) {
|
|
11468
|
+
markConversationMessage(
|
|
11469
|
+
args.conversation,
|
|
11470
|
+
getTurnUserMessageId(args.conversation, args.sessionId),
|
|
11471
|
+
{
|
|
11472
|
+
replied: true,
|
|
11473
|
+
skippedReason: void 0
|
|
11474
|
+
}
|
|
11475
|
+
);
|
|
11476
|
+
upsertConversationMessage(args.conversation, {
|
|
11477
|
+
id: generateConversationId("assistant"),
|
|
11478
|
+
role: "assistant",
|
|
11479
|
+
text: normalizeConversationText(args.text) || "[empty response]",
|
|
11480
|
+
createdAtMs: Date.now(),
|
|
11481
|
+
author: {
|
|
11482
|
+
userName: botConfig.userName,
|
|
11483
|
+
isBot: true
|
|
11484
|
+
},
|
|
11485
|
+
meta: {
|
|
11486
|
+
replied: true
|
|
11487
|
+
}
|
|
11488
|
+
});
|
|
11489
|
+
markTurnCompleted({
|
|
11490
|
+
conversation: args.conversation,
|
|
11491
|
+
nowMs: Date.now(),
|
|
11492
|
+
sessionId: args.sessionId,
|
|
11493
|
+
updateConversationStats
|
|
11494
|
+
});
|
|
11495
|
+
}
|
|
11496
|
+
async function persistAuthPauseReplyState(args) {
|
|
11497
|
+
const currentState = await getPersistedThreadState(args.threadStateId);
|
|
11498
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11499
|
+
completeAuthPauseTurn({
|
|
11500
|
+
conversation,
|
|
11501
|
+
sessionId: args.sessionId,
|
|
11502
|
+
text: args.text
|
|
11503
|
+
});
|
|
11504
|
+
await persistThreadStateById(args.threadStateId, { conversation });
|
|
11505
|
+
}
|
|
11506
|
+
async function deliverAuthPauseReply(args) {
|
|
11507
|
+
const retryable = isRetryableTurnError(args.error) ? args.error : void 0;
|
|
11508
|
+
const text = retryable ? buildAuthPauseReplyText({
|
|
11509
|
+
disposition: retryable.metadata?.authDisposition,
|
|
11510
|
+
provider: retryable.metadata?.authProvider
|
|
11511
|
+
}) : buildAuthPauseReplyText({ provider: args.fallbackProvider });
|
|
11512
|
+
const message = buildAuthPauseSlackMessage({
|
|
11513
|
+
conversationId: args.conversationId,
|
|
11514
|
+
durationMs: retryable?.metadata?.authDurationMs,
|
|
11515
|
+
text,
|
|
11516
|
+
thinkingLevel: retryable?.metadata?.authThinkingLevel,
|
|
11517
|
+
usage: retryable?.metadata?.authUsage
|
|
11518
|
+
});
|
|
11519
|
+
await postSlackMessage({
|
|
11520
|
+
channelId: args.channelId,
|
|
11521
|
+
threadTs: args.threadTs,
|
|
11522
|
+
text: message.text,
|
|
11523
|
+
...message.blocks ? { blocks: message.blocks } : {}
|
|
11524
|
+
});
|
|
11525
|
+
await persistAuthPauseReplyState({
|
|
11526
|
+
sessionId: args.sessionId,
|
|
11527
|
+
text,
|
|
11528
|
+
threadStateId: args.threadStateId
|
|
11529
|
+
});
|
|
11530
|
+
}
|
|
11531
|
+
|
|
11044
11532
|
// src/chat/services/timeout-resume.ts
|
|
11045
11533
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
11046
11534
|
var TURN_TIMEOUT_RESUME_PATH = "/api/internal/turn-resume";
|
|
@@ -11213,6 +11701,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11213
11701
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11214
11702
|
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : void 0;
|
|
11215
11703
|
const userMessageId = getTurnUserMessageId(conversation, sessionId);
|
|
11704
|
+
clearPendingAuth(conversation, sessionId);
|
|
11216
11705
|
markConversationMessage(conversation, userMessageId, {
|
|
11217
11706
|
replied: true,
|
|
11218
11707
|
skippedReason: void 0
|
|
@@ -11233,6 +11722,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11233
11722
|
markTurnCompleted({
|
|
11234
11723
|
conversation,
|
|
11235
11724
|
nowMs: Date.now(),
|
|
11725
|
+
sessionId,
|
|
11236
11726
|
updateConversationStats
|
|
11237
11727
|
});
|
|
11238
11728
|
await persistThreadStateById(threadId, {
|
|
@@ -11246,9 +11736,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
11246
11736
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
11247
11737
|
const currentState = await getPersistedThreadState(threadId);
|
|
11248
11738
|
const conversation = coerceThreadConversationState(currentState);
|
|
11739
|
+
clearPendingAuth(conversation, sessionId);
|
|
11249
11740
|
markTurnFailed({
|
|
11250
11741
|
conversation,
|
|
11251
11742
|
nowMs: Date.now(),
|
|
11743
|
+
sessionId,
|
|
11252
11744
|
userMessageId: getTurnUserMessageId(conversation, sessionId),
|
|
11253
11745
|
markConversationMessage,
|
|
11254
11746
|
updateConversationStats
|
|
@@ -11266,8 +11758,29 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11266
11758
|
const currentState = await getPersistedThreadState(threadId);
|
|
11267
11759
|
const conversation = coerceThreadConversationState(currentState);
|
|
11268
11760
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11269
|
-
const
|
|
11270
|
-
|
|
11761
|
+
const pendingAuth = getConversationPendingAuth({
|
|
11762
|
+
conversation,
|
|
11763
|
+
kind: "mcp",
|
|
11764
|
+
provider,
|
|
11765
|
+
requesterId: authSession.userId
|
|
11766
|
+
});
|
|
11767
|
+
const resolvedSessionId = pendingAuth?.sessionId ?? authSession.sessionId;
|
|
11768
|
+
const userMessage = getTurnUserMessage(conversation, resolvedSessionId);
|
|
11769
|
+
if (pendingAuth) {
|
|
11770
|
+
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
11771
|
+
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
11772
|
+
await persistThreadStateById(threadId, { conversation });
|
|
11773
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
11774
|
+
conversationId: authSession.conversationId,
|
|
11775
|
+
sessionId: pendingAuth.sessionId,
|
|
11776
|
+
errorMessage: "Auth completed after a newer thread message superseded this blocked request."
|
|
11777
|
+
});
|
|
11778
|
+
return;
|
|
11779
|
+
}
|
|
11780
|
+
} else if (conversation.processing.activeTurnId !== authSession.sessionId) {
|
|
11781
|
+
return;
|
|
11782
|
+
}
|
|
11783
|
+
if (!userMessage) {
|
|
11271
11784
|
return;
|
|
11272
11785
|
}
|
|
11273
11786
|
const channelConfiguration = getChannelConfigurationServiceById(
|
|
@@ -11276,14 +11789,14 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11276
11789
|
const conversationContext = await buildResumeConversationContext(
|
|
11277
11790
|
authSession.channelId,
|
|
11278
11791
|
authSession.threadTs,
|
|
11279
|
-
|
|
11792
|
+
resolvedSessionId
|
|
11280
11793
|
);
|
|
11281
11794
|
await resumeAuthorizedRequest({
|
|
11282
|
-
messageText:
|
|
11795
|
+
messageText: userMessage.text,
|
|
11283
11796
|
channelId: authSession.channelId,
|
|
11284
11797
|
threadTs: authSession.threadTs,
|
|
11285
11798
|
lockKey: authSession.conversationId,
|
|
11286
|
-
connectedText:
|
|
11799
|
+
connectedText: "",
|
|
11287
11800
|
failureText: "MCP authorization completed, but resuming the request failed. Please retry the original command.",
|
|
11288
11801
|
replyContext: {
|
|
11289
11802
|
assistant: { userName: botConfig.userName },
|
|
@@ -11294,7 +11807,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11294
11807
|
},
|
|
11295
11808
|
correlation: {
|
|
11296
11809
|
conversationId: authSession.conversationId,
|
|
11297
|
-
turnId:
|
|
11810
|
+
turnId: resolvedSessionId,
|
|
11298
11811
|
channelId: authSession.channelId,
|
|
11299
11812
|
threadTs: authSession.threadTs,
|
|
11300
11813
|
requesterId: authSession.userId
|
|
@@ -11303,9 +11816,18 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11303
11816
|
conversationContext,
|
|
11304
11817
|
artifactState: artifacts,
|
|
11305
11818
|
configuration: authSession.configuration,
|
|
11819
|
+
pendingAuth,
|
|
11306
11820
|
channelConfiguration,
|
|
11307
11821
|
sandbox: getPersistedSandboxState(currentState),
|
|
11308
11822
|
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
11823
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
11824
|
+
await applyPendingAuthUpdate({
|
|
11825
|
+
conversation,
|
|
11826
|
+
conversationId: authSession.conversationId,
|
|
11827
|
+
nextPendingAuth
|
|
11828
|
+
});
|
|
11829
|
+
await persistThreadStateById(threadId, { conversation });
|
|
11830
|
+
},
|
|
11309
11831
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11310
11832
|
},
|
|
11311
11833
|
onSuccess: async (reply) => {
|
|
@@ -11313,7 +11835,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11313
11835
|
await persistCompletedReplyState(
|
|
11314
11836
|
authSession.channelId,
|
|
11315
11837
|
authSession.threadTs,
|
|
11316
|
-
|
|
11838
|
+
resolvedSessionId,
|
|
11317
11839
|
reply
|
|
11318
11840
|
);
|
|
11319
11841
|
} catch (persistError) {
|
|
@@ -11338,7 +11860,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11338
11860
|
await persistFailedReplyState(
|
|
11339
11861
|
authSession.channelId,
|
|
11340
11862
|
authSession.threadTs,
|
|
11341
|
-
|
|
11863
|
+
resolvedSessionId
|
|
11342
11864
|
);
|
|
11343
11865
|
} catch (persistError) {
|
|
11344
11866
|
logException(
|
|
@@ -11350,7 +11872,16 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11350
11872
|
);
|
|
11351
11873
|
}
|
|
11352
11874
|
},
|
|
11353
|
-
onAuthPause: async () => {
|
|
11875
|
+
onAuthPause: async (error) => {
|
|
11876
|
+
await deliverAuthPauseReply({
|
|
11877
|
+
channelId: authSession.channelId,
|
|
11878
|
+
conversationId: authSession.conversationId,
|
|
11879
|
+
error,
|
|
11880
|
+
fallbackProvider: provider,
|
|
11881
|
+
sessionId: resolvedSessionId,
|
|
11882
|
+
threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`,
|
|
11883
|
+
threadTs: authSession.threadTs
|
|
11884
|
+
});
|
|
11354
11885
|
logWarn(
|
|
11355
11886
|
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
11356
11887
|
{},
|
|
@@ -11385,7 +11916,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11385
11916
|
}
|
|
11386
11917
|
await scheduleTurnTimeoutResume({
|
|
11387
11918
|
conversationId: authSession.conversationId,
|
|
11388
|
-
sessionId:
|
|
11919
|
+
sessionId: resolvedSessionId,
|
|
11389
11920
|
expectedCheckpointVersion: checkpointVersion
|
|
11390
11921
|
});
|
|
11391
11922
|
}
|
|
@@ -11610,6 +12141,7 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11610
12141
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11611
12142
|
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
11612
12143
|
const userMessage = getTurnUserMessage(conversation, args.sessionId);
|
|
12144
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
11613
12145
|
markConversationMessage(conversation, userMessage?.id, {
|
|
11614
12146
|
replied: true,
|
|
11615
12147
|
skippedReason: void 0
|
|
@@ -11630,6 +12162,7 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11630
12162
|
markTurnCompleted({
|
|
11631
12163
|
conversation,
|
|
11632
12164
|
nowMs: Date.now(),
|
|
12165
|
+
sessionId: args.sessionId,
|
|
11633
12166
|
updateConversationStats
|
|
11634
12167
|
});
|
|
11635
12168
|
await persistThreadStateById(args.conversationId, {
|
|
@@ -11642,9 +12175,11 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
11642
12175
|
async function persistFailedOAuthReplyState(args) {
|
|
11643
12176
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11644
12177
|
const conversation = coerceThreadConversationState(currentState);
|
|
12178
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
11645
12179
|
markTurnFailed({
|
|
11646
12180
|
conversation,
|
|
11647
12181
|
nowMs: Date.now(),
|
|
12182
|
+
sessionId: args.sessionId,
|
|
11648
12183
|
userMessageId: getTurnUserMessage(conversation, args.sessionId)?.id,
|
|
11649
12184
|
markConversationMessage,
|
|
11650
12185
|
updateConversationStats
|
|
@@ -11661,35 +12196,65 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11661
12196
|
stored.resumeConversationId,
|
|
11662
12197
|
stored.resumeSessionId
|
|
11663
12198
|
);
|
|
11664
|
-
if (!checkpoint
|
|
12199
|
+
if (!checkpoint) {
|
|
11665
12200
|
return false;
|
|
11666
12201
|
}
|
|
12202
|
+
if (checkpoint.state === "completed" || checkpoint.state === "failed" || checkpoint.state === "superseded") {
|
|
12203
|
+
return true;
|
|
12204
|
+
}
|
|
12205
|
+
if (checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "auth") {
|
|
12206
|
+
return true;
|
|
12207
|
+
}
|
|
11667
12208
|
const currentState = await getPersistedThreadState(
|
|
11668
12209
|
stored.resumeConversationId
|
|
11669
12210
|
);
|
|
11670
12211
|
const conversation = coerceThreadConversationState(currentState);
|
|
11671
12212
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11672
|
-
const
|
|
11673
|
-
|
|
11674
|
-
|
|
12213
|
+
const pendingAuth = getConversationPendingAuth({
|
|
12214
|
+
conversation,
|
|
12215
|
+
kind: "plugin",
|
|
12216
|
+
provider: stored.provider,
|
|
12217
|
+
requesterId: stored.userId
|
|
12218
|
+
});
|
|
12219
|
+
const resolvedSessionId = pendingAuth?.sessionId ?? stored.resumeSessionId;
|
|
12220
|
+
const userMessage = resolvedSessionId ? getTurnUserMessage(conversation, resolvedSessionId) : void 0;
|
|
12221
|
+
if (pendingAuth) {
|
|
12222
|
+
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
12223
|
+
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
12224
|
+
await persistThreadStateById(stored.resumeConversationId, {
|
|
12225
|
+
conversation
|
|
12226
|
+
});
|
|
12227
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
12228
|
+
conversationId: stored.resumeConversationId,
|
|
12229
|
+
sessionId: pendingAuth.sessionId,
|
|
12230
|
+
errorMessage: "Auth completed after a newer thread message superseded this blocked request."
|
|
12231
|
+
});
|
|
12232
|
+
return true;
|
|
12233
|
+
}
|
|
12234
|
+
} else {
|
|
12235
|
+
if (!userMessage?.author?.userId) {
|
|
12236
|
+
return false;
|
|
12237
|
+
}
|
|
12238
|
+
if (conversation.processing.activeTurnId !== stored.resumeSessionId) {
|
|
12239
|
+
return true;
|
|
12240
|
+
}
|
|
11675
12241
|
}
|
|
11676
|
-
if (
|
|
11677
|
-
return
|
|
12242
|
+
if (!userMessage?.author?.userId || !resolvedSessionId) {
|
|
12243
|
+
return false;
|
|
11678
12244
|
}
|
|
11679
12245
|
const conversationContext = await buildCheckpointConversationContext(
|
|
11680
12246
|
stored.resumeConversationId,
|
|
11681
|
-
|
|
12247
|
+
resolvedSessionId
|
|
11682
12248
|
);
|
|
11683
12249
|
const channelConfiguration = getChannelConfigurationServiceById(
|
|
11684
12250
|
stored.channelId
|
|
11685
12251
|
);
|
|
11686
|
-
const providerLabel = formatProviderLabel(stored.provider);
|
|
11687
12252
|
await resumeSlackTurn({
|
|
11688
12253
|
messageText: stored.pendingMessage ?? userMessage.text,
|
|
11689
12254
|
channelId: stored.channelId,
|
|
11690
12255
|
threadTs: stored.threadTs,
|
|
11691
12256
|
lockKey: stored.resumeConversationId,
|
|
11692
|
-
initialText:
|
|
12257
|
+
initialText: "",
|
|
11693
12258
|
failureText: "I connected your account but hit an error processing your request. Please try the command again.",
|
|
11694
12259
|
replyContext: {
|
|
11695
12260
|
assistant: { userName: botConfig.userName },
|
|
@@ -11705,10 +12270,21 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11705
12270
|
},
|
|
11706
12271
|
toolChannelId: artifacts.assistantContextChannelId ?? stored.channelId,
|
|
11707
12272
|
artifactState: artifacts,
|
|
12273
|
+
pendingAuth,
|
|
11708
12274
|
conversationContext,
|
|
11709
12275
|
channelConfiguration,
|
|
11710
12276
|
sandbox: getPersistedSandboxState(currentState),
|
|
11711
12277
|
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
12278
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
12279
|
+
await applyPendingAuthUpdate({
|
|
12280
|
+
conversation,
|
|
12281
|
+
conversationId: stored.resumeConversationId,
|
|
12282
|
+
nextPendingAuth
|
|
12283
|
+
});
|
|
12284
|
+
await persistThreadStateById(stored.resumeConversationId, {
|
|
12285
|
+
conversation
|
|
12286
|
+
});
|
|
12287
|
+
},
|
|
11712
12288
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11713
12289
|
},
|
|
11714
12290
|
onSuccess: async (reply) => {
|
|
@@ -11720,11 +12296,11 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11720
12296
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
11721
12297
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
11722
12298
|
},
|
|
11723
|
-
"
|
|
12299
|
+
"OAuth callback auto-resumed checkpoint finished replying"
|
|
11724
12300
|
);
|
|
11725
12301
|
await persistCompletedOAuthReplyState({
|
|
11726
12302
|
conversationId: stored.resumeConversationId,
|
|
11727
|
-
sessionId:
|
|
12303
|
+
sessionId: resolvedSessionId,
|
|
11728
12304
|
reply
|
|
11729
12305
|
});
|
|
11730
12306
|
},
|
|
@@ -11738,17 +12314,19 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11738
12314
|
);
|
|
11739
12315
|
await persistFailedOAuthReplyState({
|
|
11740
12316
|
conversationId: stored.resumeConversationId,
|
|
11741
|
-
sessionId:
|
|
12317
|
+
sessionId: resolvedSessionId
|
|
11742
12318
|
});
|
|
11743
12319
|
},
|
|
11744
12320
|
onAuthPause: async (error) => {
|
|
11745
|
-
|
|
12321
|
+
await deliverAuthPauseReply({
|
|
12322
|
+
channelId: stored.channelId,
|
|
12323
|
+
conversationId: stored.resumeConversationId,
|
|
11746
12324
|
error,
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
);
|
|
12325
|
+
fallbackProvider: stored.provider,
|
|
12326
|
+
sessionId: resolvedSessionId,
|
|
12327
|
+
threadStateId: stored.resumeConversationId,
|
|
12328
|
+
threadTs: stored.threadTs
|
|
12329
|
+
});
|
|
11752
12330
|
},
|
|
11753
12331
|
onTimeoutPause: async (error) => {
|
|
11754
12332
|
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
@@ -11768,7 +12346,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11768
12346
|
}
|
|
11769
12347
|
await scheduleTurnTimeoutResume({
|
|
11770
12348
|
conversationId: stored.resumeConversationId,
|
|
11771
|
-
sessionId:
|
|
12349
|
+
sessionId: resolvedSessionId,
|
|
11772
12350
|
expectedCheckpointVersion: checkpointVersion
|
|
11773
12351
|
});
|
|
11774
12352
|
}
|
|
@@ -11777,7 +12355,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
11777
12355
|
}
|
|
11778
12356
|
async function resumePendingOAuthMessage(stored) {
|
|
11779
12357
|
if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
|
|
11780
|
-
const providerLabel = formatProviderLabel(stored.provider);
|
|
11781
12358
|
const conversationContext = await buildResumeConversationContext2(
|
|
11782
12359
|
stored.channelId,
|
|
11783
12360
|
stored.threadTs
|
|
@@ -11786,7 +12363,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
11786
12363
|
messageText: stored.pendingMessage,
|
|
11787
12364
|
channelId: stored.channelId,
|
|
11788
12365
|
threadTs: stored.threadTs,
|
|
11789
|
-
connectedText:
|
|
12366
|
+
connectedText: "",
|
|
11790
12367
|
failureText: `I connected your account but hit an error processing your request. Please try \`${stored.pendingMessage}\` again.`,
|
|
11791
12368
|
replyContext: {
|
|
11792
12369
|
requester: { userId: stored.userId },
|
|
@@ -11802,7 +12379,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
11802
12379
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
11803
12380
|
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
11804
12381
|
},
|
|
11805
|
-
"
|
|
12382
|
+
"OAuth callback auto-resumed pending message finished replying"
|
|
11806
12383
|
);
|
|
11807
12384
|
},
|
|
11808
12385
|
onFailure: async (error) => {
|
|
@@ -12051,6 +12628,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
12051
12628
|
conversation,
|
|
12052
12629
|
args.checkpoint.sessionId
|
|
12053
12630
|
);
|
|
12631
|
+
clearPendingAuth(conversation, args.checkpoint.sessionId);
|
|
12054
12632
|
markConversationMessage(conversation, userMessage?.id, {
|
|
12055
12633
|
replied: true,
|
|
12056
12634
|
skippedReason: void 0
|
|
@@ -12071,6 +12649,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
12071
12649
|
markTurnCompleted({
|
|
12072
12650
|
conversation,
|
|
12073
12651
|
nowMs: Date.now(),
|
|
12652
|
+
sessionId: args.checkpoint.sessionId,
|
|
12074
12653
|
updateConversationStats
|
|
12075
12654
|
});
|
|
12076
12655
|
await persistThreadStateById(args.checkpoint.conversationId, {
|
|
@@ -12083,9 +12662,11 @@ async function persistCompletedReplyState2(args) {
|
|
|
12083
12662
|
async function persistFailedReplyState2(checkpoint) {
|
|
12084
12663
|
const currentState = await getPersistedThreadState(checkpoint.conversationId);
|
|
12085
12664
|
const conversation = coerceThreadConversationState(currentState);
|
|
12665
|
+
clearPendingAuth(conversation, checkpoint.sessionId);
|
|
12086
12666
|
markTurnFailed({
|
|
12087
12667
|
conversation,
|
|
12088
12668
|
nowMs: Date.now(),
|
|
12669
|
+
sessionId: checkpoint.sessionId,
|
|
12089
12670
|
userMessageId: getTurnUserMessage(conversation, checkpoint.sessionId)?.id,
|
|
12090
12671
|
markConversationMessage,
|
|
12091
12672
|
updateConversationStats
|
|
@@ -12149,10 +12730,21 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12149
12730
|
},
|
|
12150
12731
|
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
12151
12732
|
artifactState: artifacts,
|
|
12733
|
+
pendingAuth: conversation.processing.pendingAuth,
|
|
12152
12734
|
conversationContext,
|
|
12153
12735
|
channelConfiguration,
|
|
12154
12736
|
sandbox,
|
|
12155
12737
|
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
12738
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
12739
|
+
await applyPendingAuthUpdate({
|
|
12740
|
+
conversation,
|
|
12741
|
+
conversationId: payload.conversationId,
|
|
12742
|
+
nextPendingAuth
|
|
12743
|
+
});
|
|
12744
|
+
await persistThreadStateById(payload.conversationId, {
|
|
12745
|
+
conversation
|
|
12746
|
+
});
|
|
12747
|
+
},
|
|
12156
12748
|
...getTurnUserReplyAttachmentContext(userMessage)
|
|
12157
12749
|
},
|
|
12158
12750
|
onSuccess: async (reply) => {
|
|
@@ -12184,7 +12776,15 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12184
12776
|
);
|
|
12185
12777
|
await persistFailedReplyState2(checkpoint);
|
|
12186
12778
|
},
|
|
12187
|
-
onAuthPause: async () => {
|
|
12779
|
+
onAuthPause: async (error) => {
|
|
12780
|
+
await deliverAuthPauseReply({
|
|
12781
|
+
channelId: thread.channelId,
|
|
12782
|
+
conversationId: payload.conversationId,
|
|
12783
|
+
error,
|
|
12784
|
+
sessionId: payload.sessionId,
|
|
12785
|
+
threadStateId: payload.conversationId,
|
|
12786
|
+
threadTs: thread.threadTs
|
|
12787
|
+
});
|
|
12188
12788
|
logWarn(
|
|
12189
12789
|
"timeout_resume_reparked_for_auth",
|
|
12190
12790
|
{},
|
|
@@ -13923,6 +14523,7 @@ function createReplyToThread(deps) {
|
|
|
13923
14523
|
},
|
|
13924
14524
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
13925
14525
|
artifactState: preparedState.artifacts,
|
|
14526
|
+
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
13926
14527
|
configuration: preparedState.configuration,
|
|
13927
14528
|
channelConfiguration: preparedState.channelConfiguration,
|
|
13928
14529
|
inboundAttachmentCount: message.attachments.length,
|
|
@@ -13952,6 +14553,16 @@ function createReplyToThread(deps) {
|
|
|
13952
14553
|
onArtifactStateUpdated: async (artifacts) => {
|
|
13953
14554
|
await persistThreadState(thread, { artifacts });
|
|
13954
14555
|
},
|
|
14556
|
+
onAuthPending: async (pendingAuth) => {
|
|
14557
|
+
await applyPendingAuthUpdate({
|
|
14558
|
+
conversation: preparedState.conversation,
|
|
14559
|
+
conversationId,
|
|
14560
|
+
nextPendingAuth: pendingAuth
|
|
14561
|
+
});
|
|
14562
|
+
await persistThreadState(thread, {
|
|
14563
|
+
conversation: preparedState.conversation
|
|
14564
|
+
});
|
|
14565
|
+
},
|
|
13955
14566
|
threadParticipants,
|
|
13956
14567
|
onStatus: (nextStatus) => status.update(nextStatus)
|
|
13957
14568
|
});
|
|
@@ -14129,8 +14740,42 @@ function createReplyToThread(deps) {
|
|
|
14129
14740
|
}
|
|
14130
14741
|
} catch (error) {
|
|
14131
14742
|
if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
|
|
14743
|
+
const authPauseText = buildAuthPauseReplyText({
|
|
14744
|
+
disposition: error.metadata?.authDisposition,
|
|
14745
|
+
provider: error.metadata?.authProvider
|
|
14746
|
+
});
|
|
14747
|
+
const authPauseFooter = buildSlackReplyFooter({
|
|
14748
|
+
conversationId,
|
|
14749
|
+
durationMs: error.metadata?.authDurationMs,
|
|
14750
|
+
thinkingLevel: error.metadata?.authThinkingLevel,
|
|
14751
|
+
usage: error.metadata?.authUsage
|
|
14752
|
+
});
|
|
14753
|
+
const useSlackFooterForAuthPause = Boolean(authPauseFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
14754
|
+
if (useSlackFooterForAuthPause && channelId && threadTs) {
|
|
14755
|
+
await beforeFirstResponsePost();
|
|
14756
|
+
await postSlackApiReplyPosts({
|
|
14757
|
+
channelId,
|
|
14758
|
+
threadTs,
|
|
14759
|
+
footer: authPauseFooter,
|
|
14760
|
+
posts: [{ stage: "thread_reply", text: authPauseText }]
|
|
14761
|
+
});
|
|
14762
|
+
} else {
|
|
14763
|
+
await postThreadReply(
|
|
14764
|
+
buildSlackOutputMessage(authPauseText),
|
|
14765
|
+
"thread_reply"
|
|
14766
|
+
);
|
|
14767
|
+
}
|
|
14768
|
+
completeAuthPauseTurn({
|
|
14769
|
+
conversation: preparedState.conversation,
|
|
14770
|
+
sessionId: error.metadata?.sessionId ?? turnId,
|
|
14771
|
+
text: authPauseText
|
|
14772
|
+
});
|
|
14773
|
+
await persistThreadState(thread, {
|
|
14774
|
+
conversation: preparedState.conversation
|
|
14775
|
+
});
|
|
14776
|
+
persistedAtLeastOnce = true;
|
|
14132
14777
|
shouldPersistFailureState = false;
|
|
14133
|
-
|
|
14778
|
+
return;
|
|
14134
14779
|
}
|
|
14135
14780
|
if (isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
14136
14781
|
const conversationIdForResume = error.metadata?.conversationId;
|