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