@sentry/junior 0.37.0 → 0.39.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 +1508 -361
- package/dist/{chunk-LAD5O3RX.js → chunk-7QMPV6YJ.js} +1 -1
- package/dist/{chunk-ERH4OYNB.js → chunk-DVMGFG4W.js} +1 -1
- package/dist/{chunk-QZRPUFO6.js → chunk-EQPY4742.js} +4 -13
- package/dist/cli/check.js +2 -2
- package/dist/cli/snapshot-warmup.js +2 -2
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
findSkillByName,
|
|
4
4
|
loadSkillsByName,
|
|
5
5
|
parseSkillInvocation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7QMPV6YJ.js";
|
|
7
7
|
import {
|
|
8
8
|
GEN_AI_PROVIDER_NAME,
|
|
9
9
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
@@ -30,10 +30,11 @@ import {
|
|
|
30
30
|
runNonInteractiveCommand,
|
|
31
31
|
sandboxSkillDir,
|
|
32
32
|
sandboxSkillFile
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-DVMGFG4W.js";
|
|
34
34
|
import {
|
|
35
35
|
CredentialUnavailableError,
|
|
36
36
|
buildOAuthTokenRequest,
|
|
37
|
+
buildTurnFailureResponse,
|
|
37
38
|
createChatSdkLogger,
|
|
38
39
|
createPluginBroker,
|
|
39
40
|
createRequestContext,
|
|
@@ -57,7 +58,6 @@ import {
|
|
|
57
58
|
mergeHeaderTransforms,
|
|
58
59
|
parseOAuthTokenResponse,
|
|
59
60
|
resolveAuthTokenPlaceholder,
|
|
60
|
-
resolveErrorReference,
|
|
61
61
|
serializeGenAiAttribute,
|
|
62
62
|
setSpanAttributes,
|
|
63
63
|
setSpanStatus,
|
|
@@ -66,8 +66,10 @@ import {
|
|
|
66
66
|
toOptionalString,
|
|
67
67
|
withContext,
|
|
68
68
|
withSpan
|
|
69
|
-
} from "./chunk-
|
|
70
|
-
import
|
|
69
|
+
} from "./chunk-EQPY4742.js";
|
|
70
|
+
import {
|
|
71
|
+
sentry_exports
|
|
72
|
+
} from "./chunk-Z3YD6NHK.js";
|
|
71
73
|
import {
|
|
72
74
|
discoverInstalledPluginPackageContent,
|
|
73
75
|
homeDir,
|
|
@@ -2772,20 +2774,9 @@ function buildSlackOutputMessage(text, files) {
|
|
|
2772
2774
|
files
|
|
2773
2775
|
};
|
|
2774
2776
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
{},
|
|
2778
|
-
{
|
|
2779
|
-
"app.output.original_length": text.length,
|
|
2780
|
-
"app.output.parsed_length": normalized.length,
|
|
2781
|
-
"app.output.file_count": fileCount
|
|
2782
|
-
},
|
|
2783
|
-
"Slack output normalized to empty content"
|
|
2777
|
+
throw new Error(
|
|
2778
|
+
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
2784
2779
|
);
|
|
2785
|
-
return {
|
|
2786
|
-
markdown: "I couldn't produce a response.",
|
|
2787
|
-
files
|
|
2788
|
-
};
|
|
2789
2780
|
}
|
|
2790
2781
|
return {
|
|
2791
2782
|
markdown: normalized,
|
|
@@ -2983,6 +2974,28 @@ function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
|
2983
2974
|
}
|
|
2984
2975
|
return lines.join("\n");
|
|
2985
2976
|
}
|
|
2977
|
+
function formatToolGuidanceForPrompt(tools) {
|
|
2978
|
+
const guidedTools = tools.filter(
|
|
2979
|
+
(tool2) => Boolean(tool2.promptSnippet?.trim()) || (tool2.promptGuidelines?.length ?? 0) > 0
|
|
2980
|
+
);
|
|
2981
|
+
if (guidedTools.length === 0) {
|
|
2982
|
+
return null;
|
|
2983
|
+
}
|
|
2984
|
+
const lines = [];
|
|
2985
|
+
for (const tool2 of guidedTools) {
|
|
2986
|
+
lines.push(` <tool name="${escapeXml(tool2.name)}">`);
|
|
2987
|
+
if (tool2.promptSnippet?.trim()) {
|
|
2988
|
+
lines.push(` - ${escapeXml(tool2.promptSnippet.trim())}`);
|
|
2989
|
+
}
|
|
2990
|
+
if (tool2.promptGuidelines && tool2.promptGuidelines.length > 0) {
|
|
2991
|
+
for (const guideline of tool2.promptGuidelines) {
|
|
2992
|
+
lines.push(` - ${escapeXml(guideline)}`);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
lines.push(" </tool>");
|
|
2996
|
+
}
|
|
2997
|
+
return lines.join("\n");
|
|
2998
|
+
}
|
|
2986
2999
|
function formatReferenceFilesLines() {
|
|
2987
3000
|
const files = listReferenceFiles();
|
|
2988
3001
|
if (files.length === 0) {
|
|
@@ -3038,7 +3051,7 @@ function formatSlackCapabilityNames(capabilities) {
|
|
|
3038
3051
|
].filter(Boolean);
|
|
3039
3052
|
return names.length > 0 ? names.join(", ") : "none";
|
|
3040
3053
|
}
|
|
3041
|
-
var HEADER = "You are a Slack-based helper assistant. The behavior and output blocks
|
|
3054
|
+
var HEADER = "You are a Slack-based helper assistant. Follow the personality block for voice and tone in every reply. The behavior and output blocks define platform mechanics and override personality only when those mechanics conflict.";
|
|
3042
3055
|
var TURN_CONTEXT_HEADER = "Per-turn runtime context for this request. Treat these blocks as trusted runtime facts and skill/provider instructions for the current turn; the static system prompt remains authoritative.";
|
|
3043
3056
|
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
3044
3057
|
var TOOL_POLICY_RULES = [
|
|
@@ -3203,6 +3216,10 @@ function buildCapabilitiesSection(params) {
|
|
|
3203
3216
|
if (activeCatalogs) {
|
|
3204
3217
|
blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
|
|
3205
3218
|
}
|
|
3219
|
+
const toolGuidance = formatToolGuidanceForPrompt(params.toolGuidance ?? []);
|
|
3220
|
+
if (toolGuidance) {
|
|
3221
|
+
blocks.push(renderTagBlock("tool-guidance", toolGuidance));
|
|
3222
|
+
}
|
|
3206
3223
|
const providerCatalog = formatProviderCatalogForPrompt();
|
|
3207
3224
|
if (providerCatalog) {
|
|
3208
3225
|
blocks.push(renderTagBlock("providers", providerCatalog));
|
|
@@ -3227,7 +3244,8 @@ function buildTurnContextPrompt(params) {
|
|
|
3227
3244
|
buildCapabilitiesSection({
|
|
3228
3245
|
availableSkills: params.availableSkills,
|
|
3229
3246
|
activeSkills: params.activeSkills,
|
|
3230
|
-
activeMcpCatalogs: params.activeMcpCatalogs ?? []
|
|
3247
|
+
activeMcpCatalogs: params.activeMcpCatalogs ?? [],
|
|
3248
|
+
toolGuidance: params.toolGuidance ?? []
|
|
3231
3249
|
}),
|
|
3232
3250
|
buildContextSection({
|
|
3233
3251
|
requester: params.requester,
|
|
@@ -4591,7 +4609,13 @@ function createBashTool() {
|
|
|
4591
4609
|
command: Type.String({
|
|
4592
4610
|
minLength: 1,
|
|
4593
4611
|
description: "Bash command to run inside the sandbox."
|
|
4594
|
-
})
|
|
4612
|
+
}),
|
|
4613
|
+
timeoutMs: Type.Optional(
|
|
4614
|
+
Type.Integer({
|
|
4615
|
+
minimum: 1e3,
|
|
4616
|
+
description: "Optional command timeout in milliseconds. Use for commands that may hang."
|
|
4617
|
+
})
|
|
4618
|
+
)
|
|
4595
4619
|
},
|
|
4596
4620
|
{ additionalProperties: false }
|
|
4597
4621
|
),
|
|
@@ -4601,9 +4625,635 @@ function createBashTool() {
|
|
|
4601
4625
|
});
|
|
4602
4626
|
}
|
|
4603
4627
|
|
|
4604
|
-
// src/chat/tools/sandbox/
|
|
4628
|
+
// src/chat/tools/sandbox/file-utils.ts
|
|
4605
4629
|
import path4 from "path";
|
|
4630
|
+
var MAX_TEXT_CHARS = 6e4;
|
|
4631
|
+
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
4632
|
+
function positiveInteger(value) {
|
|
4633
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
4634
|
+
return void 0;
|
|
4635
|
+
}
|
|
4636
|
+
const integer = Math.floor(value);
|
|
4637
|
+
return integer > 0 ? integer : void 0;
|
|
4638
|
+
}
|
|
4639
|
+
function normalizeToLf(value) {
|
|
4640
|
+
return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4641
|
+
}
|
|
4642
|
+
function truncateText(value, maxChars = MAX_TEXT_CHARS) {
|
|
4643
|
+
if (value.length <= maxChars) {
|
|
4644
|
+
return { content: value, truncated: false };
|
|
4645
|
+
}
|
|
4646
|
+
const removed = value.length - maxChars;
|
|
4647
|
+
return {
|
|
4648
|
+
content: `${value.slice(0, maxChars)}
|
|
4649
|
+
|
|
4650
|
+
[output truncated: ${removed} characters removed]`,
|
|
4651
|
+
truncated: true
|
|
4652
|
+
};
|
|
4653
|
+
}
|
|
4654
|
+
function escapeRegExp(value) {
|
|
4655
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4656
|
+
}
|
|
4657
|
+
function globToRegExp(pattern) {
|
|
4658
|
+
let source = "";
|
|
4659
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
4660
|
+
const char = pattern[index];
|
|
4661
|
+
const next = pattern[index + 1];
|
|
4662
|
+
if (char === "*" && next === "*") {
|
|
4663
|
+
if (pattern[index + 2] === "/") {
|
|
4664
|
+
source += "(?:.*/)?";
|
|
4665
|
+
index += 2;
|
|
4666
|
+
continue;
|
|
4667
|
+
}
|
|
4668
|
+
source += ".*";
|
|
4669
|
+
index += 1;
|
|
4670
|
+
continue;
|
|
4671
|
+
}
|
|
4672
|
+
if (char === "*") {
|
|
4673
|
+
source += "[^/]*";
|
|
4674
|
+
continue;
|
|
4675
|
+
}
|
|
4676
|
+
if (char === "?") {
|
|
4677
|
+
source += "[^/]";
|
|
4678
|
+
continue;
|
|
4679
|
+
}
|
|
4680
|
+
source += escapeRegExp(char);
|
|
4681
|
+
}
|
|
4682
|
+
return new RegExp(`^${source}$`);
|
|
4683
|
+
}
|
|
4684
|
+
function matchesGlob(relativePath, pattern) {
|
|
4685
|
+
const matcher = globToRegExp(pattern);
|
|
4686
|
+
if (matcher.test(relativePath)) {
|
|
4687
|
+
return true;
|
|
4688
|
+
}
|
|
4689
|
+
if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
|
|
4690
|
+
return true;
|
|
4691
|
+
}
|
|
4692
|
+
return !pattern.includes("/") && matcher.test(path4.posix.basename(relativePath));
|
|
4693
|
+
}
|
|
4694
|
+
function resolveWorkspacePath(input, fallback = ".") {
|
|
4695
|
+
const requested = (input ?? "").trim() || fallback;
|
|
4696
|
+
const absolute = requested.startsWith("/") ? requested : path4.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
|
|
4697
|
+
const normalized = path4.posix.normalize(absolute);
|
|
4698
|
+
if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
4699
|
+
throw new Error(
|
|
4700
|
+
`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
|
|
4701
|
+
);
|
|
4702
|
+
}
|
|
4703
|
+
return normalized;
|
|
4704
|
+
}
|
|
4705
|
+
async function collectFiles(params) {
|
|
4706
|
+
const files = [];
|
|
4707
|
+
let limitReached = false;
|
|
4708
|
+
const visit = async (dirPath) => {
|
|
4709
|
+
const entries = (await params.fs.readdir(dirPath)).sort(
|
|
4710
|
+
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
4711
|
+
);
|
|
4712
|
+
for (const entry of entries) {
|
|
4713
|
+
const fullPath = path4.posix.join(dirPath, entry);
|
|
4714
|
+
const stat2 = await params.fs.stat(fullPath);
|
|
4715
|
+
if (stat2.isDirectory()) {
|
|
4716
|
+
if (!SKIPPED_DIRECTORIES.has(entry)) {
|
|
4717
|
+
await visit(fullPath);
|
|
4718
|
+
}
|
|
4719
|
+
if (limitReached) return;
|
|
4720
|
+
continue;
|
|
4721
|
+
}
|
|
4722
|
+
const relativePath = path4.posix.relative(params.root, fullPath);
|
|
4723
|
+
if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
|
|
4724
|
+
files.push(fullPath);
|
|
4725
|
+
if (params.limit && files.length >= params.limit) {
|
|
4726
|
+
limitReached = true;
|
|
4727
|
+
return;
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
};
|
|
4732
|
+
const stat = await params.fs.stat(params.root);
|
|
4733
|
+
if (!stat.isDirectory()) {
|
|
4734
|
+
const relativePath = path4.posix.basename(params.root);
|
|
4735
|
+
return {
|
|
4736
|
+
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
4737
|
+
limitReached: false
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4740
|
+
await visit(params.root);
|
|
4741
|
+
return { files, limitReached };
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
// src/chat/tools/sandbox/edit-file.ts
|
|
4606
4745
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
4746
|
+
function detectLineEnding(value) {
|
|
4747
|
+
return value.includes("\r\n") ? "\r\n" : "\n";
|
|
4748
|
+
}
|
|
4749
|
+
function restoreLineEndings(value, lineEnding) {
|
|
4750
|
+
return lineEnding === "\r\n" ? value.replace(/\n/g, "\r\n") : value;
|
|
4751
|
+
}
|
|
4752
|
+
function stripBom(value) {
|
|
4753
|
+
return value.startsWith("\uFEFF") ? { bom: "\uFEFF", text: value.slice(1) } : { bom: "", text: value };
|
|
4754
|
+
}
|
|
4755
|
+
function countOccurrences(content, target) {
|
|
4756
|
+
let count = 0;
|
|
4757
|
+
let start = 0;
|
|
4758
|
+
while (target.length > 0) {
|
|
4759
|
+
const index = content.indexOf(target, start);
|
|
4760
|
+
if (index === -1) break;
|
|
4761
|
+
count += 1;
|
|
4762
|
+
start = index + target.length;
|
|
4763
|
+
}
|
|
4764
|
+
return count;
|
|
4765
|
+
}
|
|
4766
|
+
function firstChangedLine(oldContent, newContent) {
|
|
4767
|
+
const oldLines = oldContent.split("\n");
|
|
4768
|
+
const newLines = newContent.split("\n");
|
|
4769
|
+
const count = Math.max(oldLines.length, newLines.length);
|
|
4770
|
+
for (let index = 0; index < count; index += 1) {
|
|
4771
|
+
if (oldLines[index] !== newLines[index]) {
|
|
4772
|
+
return index + 1;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
return void 0;
|
|
4776
|
+
}
|
|
4777
|
+
function buildCompactDiff(oldContent, newContent) {
|
|
4778
|
+
const oldLines = oldContent.split("\n");
|
|
4779
|
+
const newLines = newContent.split("\n");
|
|
4780
|
+
let prefix = 0;
|
|
4781
|
+
while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix]) {
|
|
4782
|
+
prefix += 1;
|
|
4783
|
+
}
|
|
4784
|
+
let oldSuffix = oldLines.length - 1;
|
|
4785
|
+
let newSuffix = newLines.length - 1;
|
|
4786
|
+
while (oldSuffix >= prefix && newSuffix >= prefix && oldLines[oldSuffix] === newLines[newSuffix]) {
|
|
4787
|
+
oldSuffix -= 1;
|
|
4788
|
+
newSuffix -= 1;
|
|
4789
|
+
}
|
|
4790
|
+
const contextStart = Math.max(0, prefix - 3);
|
|
4791
|
+
const newContextEnd = Math.min(newLines.length - 1, newSuffix + 3);
|
|
4792
|
+
const oldContextEnd = Math.min(oldLines.length - 1, oldSuffix + 3);
|
|
4793
|
+
const width = String(Math.max(oldLines.length, newLines.length)).length;
|
|
4794
|
+
const output = [];
|
|
4795
|
+
if (contextStart > 0) {
|
|
4796
|
+
output.push(` ${"".padStart(width)} ...`);
|
|
4797
|
+
}
|
|
4798
|
+
for (let index = contextStart; index < prefix; index += 1) {
|
|
4799
|
+
output.push(` ${String(index + 1).padStart(width)} ${oldLines[index]}`);
|
|
4800
|
+
}
|
|
4801
|
+
for (let index = prefix; index <= oldSuffix; index += 1) {
|
|
4802
|
+
output.push(`-${String(index + 1).padStart(width)} ${oldLines[index]}`);
|
|
4803
|
+
}
|
|
4804
|
+
for (let index = prefix; index <= newSuffix; index += 1) {
|
|
4805
|
+
output.push(`+${String(index + 1).padStart(width)} ${newLines[index]}`);
|
|
4806
|
+
}
|
|
4807
|
+
for (let index = newSuffix + 1; index <= newContextEnd; index += 1) {
|
|
4808
|
+
output.push(` ${String(index + 1).padStart(width)} ${newLines[index]}`);
|
|
4809
|
+
}
|
|
4810
|
+
if (newContextEnd < newLines.length - 1 || oldContextEnd < oldLines.length - 1) {
|
|
4811
|
+
output.push(` ${"".padStart(width)} ...`);
|
|
4812
|
+
}
|
|
4813
|
+
return {
|
|
4814
|
+
diff: output.join("\n"),
|
|
4815
|
+
firstChangedLine: firstChangedLine(oldContent, newContent)
|
|
4816
|
+
};
|
|
4817
|
+
}
|
|
4818
|
+
function validateAndApplyEdits(content, edits, filePath) {
|
|
4819
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
4820
|
+
throw new Error("editFile requires at least one edit.");
|
|
4821
|
+
}
|
|
4822
|
+
const normalizedEdits = edits.map((edit, index) => {
|
|
4823
|
+
if (typeof edit.oldText !== "string" || edit.oldText.length === 0) {
|
|
4824
|
+
throw new Error(
|
|
4825
|
+
`edits[${index}].oldText must not be empty in ${filePath}.`
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4828
|
+
if (typeof edit.newText !== "string") {
|
|
4829
|
+
throw new Error(
|
|
4830
|
+
`edits[${index}].newText must be a string in ${filePath}.`
|
|
4831
|
+
);
|
|
4832
|
+
}
|
|
4833
|
+
return {
|
|
4834
|
+
oldText: normalizeToLf(edit.oldText),
|
|
4835
|
+
newText: normalizeToLf(edit.newText)
|
|
4836
|
+
};
|
|
4837
|
+
});
|
|
4838
|
+
const matchedEdits = [];
|
|
4839
|
+
for (let index = 0; index < normalizedEdits.length; index += 1) {
|
|
4840
|
+
const edit = normalizedEdits[index];
|
|
4841
|
+
const matchIndex = content.indexOf(edit.oldText);
|
|
4842
|
+
if (matchIndex === -1) {
|
|
4843
|
+
throw new Error(
|
|
4844
|
+
`Could not find edits[${index}] in ${filePath}. oldText must match exactly including whitespace and newlines.`
|
|
4845
|
+
);
|
|
4846
|
+
}
|
|
4847
|
+
const occurrences = countOccurrences(content, edit.oldText);
|
|
4848
|
+
if (occurrences > 1) {
|
|
4849
|
+
throw new Error(
|
|
4850
|
+
`Found ${occurrences} occurrences of edits[${index}] in ${filePath}. Each oldText must be unique.`
|
|
4851
|
+
);
|
|
4852
|
+
}
|
|
4853
|
+
matchedEdits.push({
|
|
4854
|
+
editIndex: index,
|
|
4855
|
+
matchIndex,
|
|
4856
|
+
matchLength: edit.oldText.length,
|
|
4857
|
+
newText: edit.newText
|
|
4858
|
+
});
|
|
4859
|
+
}
|
|
4860
|
+
matchedEdits.sort((a, b) => a.matchIndex - b.matchIndex);
|
|
4861
|
+
for (let index = 1; index < matchedEdits.length; index += 1) {
|
|
4862
|
+
const previous = matchedEdits[index - 1];
|
|
4863
|
+
const current = matchedEdits[index];
|
|
4864
|
+
if (previous.matchIndex + previous.matchLength > current.matchIndex) {
|
|
4865
|
+
throw new Error(
|
|
4866
|
+
`edits[${previous.editIndex}] and edits[${current.editIndex}] overlap in ${filePath}. Merge overlapping replacements into one edit.`
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
let newContent = content;
|
|
4871
|
+
for (let index = matchedEdits.length - 1; index >= 0; index -= 1) {
|
|
4872
|
+
const edit = matchedEdits[index];
|
|
4873
|
+
newContent = newContent.slice(0, edit.matchIndex) + edit.newText + newContent.slice(edit.matchIndex + edit.matchLength);
|
|
4874
|
+
}
|
|
4875
|
+
if (newContent === content) {
|
|
4876
|
+
throw new Error(`No changes made to ${filePath}.`);
|
|
4877
|
+
}
|
|
4878
|
+
return { baseContent: content, newContent };
|
|
4879
|
+
}
|
|
4880
|
+
function prepareEditFileArguments(input) {
|
|
4881
|
+
if (!input || typeof input !== "object") {
|
|
4882
|
+
return input;
|
|
4883
|
+
}
|
|
4884
|
+
const raw = { ...input };
|
|
4885
|
+
if (typeof raw.edits === "string") {
|
|
4886
|
+
try {
|
|
4887
|
+
raw.edits = JSON.parse(raw.edits);
|
|
4888
|
+
} catch {
|
|
4889
|
+
return raw;
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
const edits = Array.isArray(raw.edits) ? [...raw.edits] : [];
|
|
4893
|
+
const oldText = raw.oldText ?? raw.old_text;
|
|
4894
|
+
const newText = raw.newText ?? raw.new_text;
|
|
4895
|
+
if (typeof oldText === "string" && typeof newText === "string") {
|
|
4896
|
+
edits.push({ oldText, newText });
|
|
4897
|
+
}
|
|
4898
|
+
if (edits.length > 0) {
|
|
4899
|
+
raw.edits = edits.map((edit) => {
|
|
4900
|
+
if (!edit || typeof edit !== "object") {
|
|
4901
|
+
return edit;
|
|
4902
|
+
}
|
|
4903
|
+
const record = edit;
|
|
4904
|
+
const { old_text, new_text, ...rest } = record;
|
|
4905
|
+
return {
|
|
4906
|
+
...rest,
|
|
4907
|
+
oldText: record.oldText ?? old_text,
|
|
4908
|
+
newText: record.newText ?? new_text
|
|
4909
|
+
};
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4912
|
+
delete raw.oldText;
|
|
4913
|
+
delete raw.old_text;
|
|
4914
|
+
delete raw.newText;
|
|
4915
|
+
delete raw.new_text;
|
|
4916
|
+
return raw;
|
|
4917
|
+
}
|
|
4918
|
+
async function editFile(params) {
|
|
4919
|
+
const filePath = resolveWorkspacePath(params.path);
|
|
4920
|
+
const rawContent = await params.fs.readFile(filePath, { encoding: "utf8" });
|
|
4921
|
+
const { bom, text } = stripBom(rawContent);
|
|
4922
|
+
const lineEnding = detectLineEnding(text);
|
|
4923
|
+
const normalizedContent = normalizeToLf(text);
|
|
4924
|
+
const { baseContent, newContent } = validateAndApplyEdits(
|
|
4925
|
+
normalizedContent,
|
|
4926
|
+
params.edits,
|
|
4927
|
+
params.path
|
|
4928
|
+
);
|
|
4929
|
+
await params.fs.writeFile(
|
|
4930
|
+
filePath,
|
|
4931
|
+
bom + restoreLineEndings(newContent, lineEnding),
|
|
4932
|
+
{ encoding: "utf8" }
|
|
4933
|
+
);
|
|
4934
|
+
const diff = buildCompactDiff(baseContent, newContent);
|
|
4935
|
+
return {
|
|
4936
|
+
content: [
|
|
4937
|
+
{
|
|
4938
|
+
type: "text",
|
|
4939
|
+
text: `Successfully replaced ${params.edits.length} block(s) in ${params.path}.`
|
|
4940
|
+
}
|
|
4941
|
+
],
|
|
4942
|
+
details: {
|
|
4943
|
+
diff: diff.diff,
|
|
4944
|
+
first_changed_line: diff.firstChangedLine,
|
|
4945
|
+
ok: true,
|
|
4946
|
+
path: params.path,
|
|
4947
|
+
replacements: params.edits.length
|
|
4948
|
+
}
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
var editReplacementSchema = Type2.Object(
|
|
4952
|
+
{
|
|
4953
|
+
oldText: Type2.String({
|
|
4954
|
+
minLength: 1,
|
|
4955
|
+
description: "Exact text to replace. It must be unique in the original file and must not overlap another edit."
|
|
4956
|
+
}),
|
|
4957
|
+
newText: Type2.String({
|
|
4958
|
+
description: "Replacement text for this edit."
|
|
4959
|
+
})
|
|
4960
|
+
},
|
|
4961
|
+
{ additionalProperties: false }
|
|
4962
|
+
);
|
|
4963
|
+
function createEditFileTool() {
|
|
4964
|
+
return tool({
|
|
4965
|
+
description: "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff.",
|
|
4966
|
+
promptSnippet: "existing-file exact edits; returns diff",
|
|
4967
|
+
promptGuidelines: [
|
|
4968
|
+
"prefer over writeFile for targeted changes",
|
|
4969
|
+
"oldText exact, unique, non-overlapping",
|
|
4970
|
+
"multiple same-file changes: one edits[] call"
|
|
4971
|
+
],
|
|
4972
|
+
prepareArguments: prepareEditFileArguments,
|
|
4973
|
+
executionMode: "sequential",
|
|
4974
|
+
inputSchema: Type2.Object(
|
|
4975
|
+
{
|
|
4976
|
+
path: Type2.String({
|
|
4977
|
+
minLength: 1,
|
|
4978
|
+
description: "Path to edit in the sandbox workspace."
|
|
4979
|
+
}),
|
|
4980
|
+
edits: Type2.Array(editReplacementSchema, {
|
|
4981
|
+
minItems: 1,
|
|
4982
|
+
description: "Exact replacements matched against the original file, not incrementally."
|
|
4983
|
+
})
|
|
4984
|
+
},
|
|
4985
|
+
{ additionalProperties: false }
|
|
4986
|
+
),
|
|
4987
|
+
execute: async () => {
|
|
4988
|
+
throw new Error(
|
|
4989
|
+
"editFile can only run when sandbox execution is enabled."
|
|
4990
|
+
);
|
|
4991
|
+
}
|
|
4992
|
+
});
|
|
4993
|
+
}
|
|
4994
|
+
|
|
4995
|
+
// src/chat/tools/sandbox/find-files.ts
|
|
4996
|
+
import path5 from "path";
|
|
4997
|
+
import { Type as Type3 } from "@sinclair/typebox";
|
|
4998
|
+
var DEFAULT_FIND_LIMIT = 1e3;
|
|
4999
|
+
async function findFiles(params) {
|
|
5000
|
+
if (!params.pattern.trim()) {
|
|
5001
|
+
throw new Error("pattern is required");
|
|
5002
|
+
}
|
|
5003
|
+
const root = resolveWorkspacePath(params.path);
|
|
5004
|
+
const limit = positiveInteger(params.limit) ?? DEFAULT_FIND_LIMIT;
|
|
5005
|
+
const { files, limitReached } = await collectFiles({
|
|
5006
|
+
fs: params.fs,
|
|
5007
|
+
root,
|
|
5008
|
+
pattern: params.pattern,
|
|
5009
|
+
limit
|
|
5010
|
+
});
|
|
5011
|
+
const relativePaths = files.map(
|
|
5012
|
+
(filePath) => path5.posix.relative(root, filePath)
|
|
5013
|
+
);
|
|
5014
|
+
const bounded = truncateText(
|
|
5015
|
+
relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
|
|
5016
|
+
);
|
|
5017
|
+
const notices = [];
|
|
5018
|
+
if (limitReached) {
|
|
5019
|
+
notices.push(
|
|
5020
|
+
`${limit} results limit reached. Refine pattern or raise limit.`
|
|
5021
|
+
);
|
|
5022
|
+
}
|
|
5023
|
+
if (bounded.truncated) {
|
|
5024
|
+
notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
|
|
5025
|
+
}
|
|
5026
|
+
return {
|
|
5027
|
+
content: [
|
|
5028
|
+
{
|
|
5029
|
+
type: "text",
|
|
5030
|
+
text: notices.length > 0 ? `${bounded.content}
|
|
5031
|
+
|
|
5032
|
+
[${notices.join(" ")}]` : bounded.content
|
|
5033
|
+
}
|
|
5034
|
+
],
|
|
5035
|
+
details: {
|
|
5036
|
+
ok: true,
|
|
5037
|
+
path: params.path ?? ".",
|
|
5038
|
+
truncated: limitReached || bounded.truncated,
|
|
5039
|
+
...limitReached ? { result_limit_reached: limit } : {}
|
|
5040
|
+
}
|
|
5041
|
+
};
|
|
5042
|
+
}
|
|
5043
|
+
function createFindFilesTool() {
|
|
5044
|
+
return tool({
|
|
5045
|
+
description: "Find sandbox workspace files by glob pattern. Returns bounded paths relative to the search root and skips dependency/cache directories.",
|
|
5046
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5047
|
+
inputSchema: Type3.Object(
|
|
5048
|
+
{
|
|
5049
|
+
pattern: Type3.String({
|
|
5050
|
+
minLength: 1,
|
|
5051
|
+
description: "Glob pattern to match, for example '*.ts', '**/*.json', or 'src/**/*.test.ts'."
|
|
5052
|
+
}),
|
|
5053
|
+
path: Type3.Optional(
|
|
5054
|
+
Type3.String({
|
|
5055
|
+
minLength: 1,
|
|
5056
|
+
description: "Directory or file path in the sandbox workspace. Defaults to the workspace root."
|
|
5057
|
+
})
|
|
5058
|
+
),
|
|
5059
|
+
limit: Type3.Optional(
|
|
5060
|
+
Type3.Integer({
|
|
5061
|
+
minimum: 1,
|
|
5062
|
+
description: "Maximum number of file paths to return. Defaults to 1000."
|
|
5063
|
+
})
|
|
5064
|
+
)
|
|
5065
|
+
},
|
|
5066
|
+
{ additionalProperties: false }
|
|
5067
|
+
),
|
|
5068
|
+
execute: async () => {
|
|
5069
|
+
throw new Error(
|
|
5070
|
+
"findFiles can only run when sandbox execution is enabled."
|
|
5071
|
+
);
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
// src/chat/tools/sandbox/grep.ts
|
|
5077
|
+
import path6 from "path";
|
|
5078
|
+
import { Type as Type4 } from "@sinclair/typebox";
|
|
5079
|
+
var DEFAULT_GREP_LIMIT = 100;
|
|
5080
|
+
var MAX_GREP_LINE_CHARS = 500;
|
|
5081
|
+
function truncateGrepLine(value) {
|
|
5082
|
+
if (value.length <= MAX_GREP_LINE_CHARS) {
|
|
5083
|
+
return { line: value, truncated: false };
|
|
5084
|
+
}
|
|
5085
|
+
return {
|
|
5086
|
+
line: `${value.slice(0, MAX_GREP_LINE_CHARS)}... [line truncated]`,
|
|
5087
|
+
truncated: true
|
|
5088
|
+
};
|
|
5089
|
+
}
|
|
5090
|
+
function lineMatches(params) {
|
|
5091
|
+
if (!params.literal) {
|
|
5092
|
+
return Boolean(params.regex?.test(params.line));
|
|
5093
|
+
}
|
|
5094
|
+
if (params.ignoreCase) {
|
|
5095
|
+
return params.line.toLowerCase().includes(params.pattern.toLowerCase());
|
|
5096
|
+
}
|
|
5097
|
+
return params.line.includes(params.pattern);
|
|
5098
|
+
}
|
|
5099
|
+
async function grepFiles(params) {
|
|
5100
|
+
if (!params.pattern) {
|
|
5101
|
+
throw new Error("pattern is required");
|
|
5102
|
+
}
|
|
5103
|
+
const root = resolveWorkspacePath(params.path);
|
|
5104
|
+
const limit = positiveInteger(params.limit) ?? DEFAULT_GREP_LIMIT;
|
|
5105
|
+
const context = positiveInteger(params.context) ?? 0;
|
|
5106
|
+
const regex = params.literal ? void 0 : new RegExp(params.pattern, params.ignoreCase ? "i" : "");
|
|
5107
|
+
const { files } = await collectFiles({
|
|
5108
|
+
fs: params.fs,
|
|
5109
|
+
root,
|
|
5110
|
+
pattern: params.glob
|
|
5111
|
+
});
|
|
5112
|
+
const output = [];
|
|
5113
|
+
let matchCount = 0;
|
|
5114
|
+
let matchLimitReached = false;
|
|
5115
|
+
let lineTruncated = false;
|
|
5116
|
+
for (const filePath of files) {
|
|
5117
|
+
if (matchLimitReached) break;
|
|
5118
|
+
let content;
|
|
5119
|
+
try {
|
|
5120
|
+
content = await params.fs.readFile(filePath, { encoding: "utf8" });
|
|
5121
|
+
} catch {
|
|
5122
|
+
continue;
|
|
5123
|
+
}
|
|
5124
|
+
if (content.includes("\0")) {
|
|
5125
|
+
continue;
|
|
5126
|
+
}
|
|
5127
|
+
const lines = normalizeToLf(content).split("\n");
|
|
5128
|
+
const relativePath = files.length === 1 && filePath === root ? path6.posix.basename(filePath) : path6.posix.relative(root, filePath);
|
|
5129
|
+
const matchedLines = [];
|
|
5130
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
5131
|
+
if (!lineMatches({
|
|
5132
|
+
ignoreCase: params.ignoreCase,
|
|
5133
|
+
line: lines[lineIndex],
|
|
5134
|
+
literal: params.literal,
|
|
5135
|
+
pattern: params.pattern,
|
|
5136
|
+
regex
|
|
5137
|
+
})) {
|
|
5138
|
+
continue;
|
|
5139
|
+
}
|
|
5140
|
+
if (matchCount >= limit) {
|
|
5141
|
+
matchLimitReached = true;
|
|
5142
|
+
break;
|
|
5143
|
+
}
|
|
5144
|
+
matchCount += 1;
|
|
5145
|
+
matchedLines.push(lineIndex);
|
|
5146
|
+
}
|
|
5147
|
+
const matchedLineSet = new Set(matchedLines);
|
|
5148
|
+
const emittedLines = /* @__PURE__ */ new Set();
|
|
5149
|
+
for (const lineIndex of matchedLines) {
|
|
5150
|
+
const start = Math.max(0, lineIndex - context);
|
|
5151
|
+
const end = Math.min(lines.length - 1, lineIndex + context);
|
|
5152
|
+
for (let current = start; current <= end; current += 1) {
|
|
5153
|
+
if (emittedLines.has(current)) {
|
|
5154
|
+
continue;
|
|
5155
|
+
}
|
|
5156
|
+
emittedLines.add(current);
|
|
5157
|
+
const truncated = truncateGrepLine(lines[current]);
|
|
5158
|
+
lineTruncated ||= truncated.truncated;
|
|
5159
|
+
const separator = matchedLineSet.has(current) ? ":" : "-";
|
|
5160
|
+
output.push(
|
|
5161
|
+
`${relativePath}${separator}${current + 1}${separator} ${truncated.line}`
|
|
5162
|
+
);
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
const bounded = truncateText(
|
|
5167
|
+
output.length > 0 ? output.join("\n") : "No matches found"
|
|
5168
|
+
);
|
|
5169
|
+
const notices = [];
|
|
5170
|
+
if (matchLimitReached) {
|
|
5171
|
+
notices.push(
|
|
5172
|
+
`${limit} matches limit reached. Refine pattern or raise limit.`
|
|
5173
|
+
);
|
|
5174
|
+
}
|
|
5175
|
+
if (lineTruncated) {
|
|
5176
|
+
notices.push(
|
|
5177
|
+
`Some lines were truncated to ${MAX_GREP_LINE_CHARS} characters.`
|
|
5178
|
+
);
|
|
5179
|
+
}
|
|
5180
|
+
if (bounded.truncated) {
|
|
5181
|
+
notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
|
|
5182
|
+
}
|
|
5183
|
+
return {
|
|
5184
|
+
content: [
|
|
5185
|
+
{
|
|
5186
|
+
type: "text",
|
|
5187
|
+
text: notices.length > 0 ? `${bounded.content}
|
|
5188
|
+
|
|
5189
|
+
[${notices.join(" ")}]` : bounded.content
|
|
5190
|
+
}
|
|
5191
|
+
],
|
|
5192
|
+
details: {
|
|
5193
|
+
ok: true,
|
|
5194
|
+
path: params.path ?? ".",
|
|
5195
|
+
truncated: matchLimitReached || lineTruncated || bounded.truncated,
|
|
5196
|
+
...matchLimitReached ? { match_limit_reached: limit } : {},
|
|
5197
|
+
...lineTruncated ? { line_truncated: true } : {}
|
|
5198
|
+
}
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
function createGrepTool() {
|
|
5202
|
+
return tool({
|
|
5203
|
+
description: "Search sandbox workspace file contents. Returns bounded matching lines with file paths and line numbers. Respects path/glob filters and includes truncation notices.",
|
|
5204
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5205
|
+
inputSchema: Type4.Object(
|
|
5206
|
+
{
|
|
5207
|
+
pattern: Type4.String({
|
|
5208
|
+
minLength: 1,
|
|
5209
|
+
description: "Regex pattern or literal text to search for."
|
|
5210
|
+
}),
|
|
5211
|
+
path: Type4.Optional(
|
|
5212
|
+
Type4.String({
|
|
5213
|
+
minLength: 1,
|
|
5214
|
+
description: "Directory or file path in the sandbox workspace. Defaults to the workspace root."
|
|
5215
|
+
})
|
|
5216
|
+
),
|
|
5217
|
+
glob: Type4.Optional(
|
|
5218
|
+
Type4.String({
|
|
5219
|
+
minLength: 1,
|
|
5220
|
+
description: "Optional glob filter such as '*.ts' or '**/*.test.ts'."
|
|
5221
|
+
})
|
|
5222
|
+
),
|
|
5223
|
+
ignoreCase: Type4.Optional(
|
|
5224
|
+
Type4.Boolean({
|
|
5225
|
+
description: "Whether matching should ignore case."
|
|
5226
|
+
})
|
|
5227
|
+
),
|
|
5228
|
+
literal: Type4.Optional(
|
|
5229
|
+
Type4.Boolean({
|
|
5230
|
+
description: "Treat pattern as literal text instead of regex."
|
|
5231
|
+
})
|
|
5232
|
+
),
|
|
5233
|
+
context: Type4.Optional(
|
|
5234
|
+
Type4.Integer({
|
|
5235
|
+
minimum: 0,
|
|
5236
|
+
description: "Number of surrounding context lines to include."
|
|
5237
|
+
})
|
|
5238
|
+
),
|
|
5239
|
+
limit: Type4.Optional(
|
|
5240
|
+
Type4.Integer({
|
|
5241
|
+
minimum: 1,
|
|
5242
|
+
description: "Maximum matches to return. Defaults to 100."
|
|
5243
|
+
})
|
|
5244
|
+
)
|
|
5245
|
+
},
|
|
5246
|
+
{ additionalProperties: false }
|
|
5247
|
+
),
|
|
5248
|
+
execute: async () => {
|
|
5249
|
+
throw new Error("grep can only run when sandbox execution is enabled.");
|
|
5250
|
+
}
|
|
5251
|
+
});
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5254
|
+
// src/chat/tools/sandbox/attach-file.ts
|
|
5255
|
+
import path7 from "path";
|
|
5256
|
+
import { Type as Type5 } from "@sinclair/typebox";
|
|
4607
5257
|
var MAX_ATTACH_FILE_BYTES = 10 * 1024 * 1024;
|
|
4608
5258
|
var MIME_BY_EXTENSION = {
|
|
4609
5259
|
".png": "image/png",
|
|
@@ -4624,20 +5274,20 @@ function normalizeSandboxPath(inputPath) {
|
|
|
4624
5274
|
if (!trimmed) {
|
|
4625
5275
|
throw new Error("path is required");
|
|
4626
5276
|
}
|
|
4627
|
-
if (
|
|
5277
|
+
if (path7.posix.isAbsolute(trimmed)) {
|
|
4628
5278
|
return trimmed;
|
|
4629
5279
|
}
|
|
4630
|
-
return
|
|
5280
|
+
return path7.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
|
|
4631
5281
|
}
|
|
4632
5282
|
function sanitizeFilename(value, fallbackPath) {
|
|
4633
5283
|
const candidate = (value ?? "").trim();
|
|
4634
5284
|
if (candidate) {
|
|
4635
|
-
const base =
|
|
5285
|
+
const base = path7.posix.basename(candidate);
|
|
4636
5286
|
if (base && base !== "." && base !== "..") {
|
|
4637
5287
|
return base;
|
|
4638
5288
|
}
|
|
4639
5289
|
}
|
|
4640
|
-
const derived =
|
|
5290
|
+
const derived = path7.posix.basename(fallbackPath);
|
|
4641
5291
|
if (derived && derived !== "." && derived !== "..") {
|
|
4642
5292
|
return derived;
|
|
4643
5293
|
}
|
|
@@ -4648,7 +5298,7 @@ function inferMimeType(filename, explicitMimeType) {
|
|
|
4648
5298
|
if (explicit) {
|
|
4649
5299
|
return explicit;
|
|
4650
5300
|
}
|
|
4651
|
-
const ext =
|
|
5301
|
+
const ext = path7.extname(filename).toLowerCase();
|
|
4652
5302
|
return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
4653
5303
|
}
|
|
4654
5304
|
async function detectMimeType(sandbox, targetPath) {
|
|
@@ -4669,20 +5319,20 @@ async function detectMimeType(sandbox, targetPath) {
|
|
|
4669
5319
|
function createAttachFileTool(sandbox, hooks = {}) {
|
|
4670
5320
|
return tool({
|
|
4671
5321
|
description: "Attach a file to the Slack reply. Use this for files that exist in the sandbox, such as screenshots, PDFs, or logs, or for generated image `attachment_path` values returned earlier in the turn.",
|
|
4672
|
-
inputSchema:
|
|
5322
|
+
inputSchema: Type5.Object(
|
|
4673
5323
|
{
|
|
4674
|
-
path:
|
|
5324
|
+
path: Type5.String({
|
|
4675
5325
|
minLength: 1,
|
|
4676
5326
|
description: "Absolute path (for example /tmp/screenshot.png) or workspace-relative path."
|
|
4677
5327
|
}),
|
|
4678
|
-
filename:
|
|
4679
|
-
|
|
5328
|
+
filename: Type5.Optional(
|
|
5329
|
+
Type5.String({
|
|
4680
5330
|
minLength: 1,
|
|
4681
5331
|
description: "Optional filename override shown in Slack."
|
|
4682
5332
|
})
|
|
4683
5333
|
),
|
|
4684
|
-
mimeType:
|
|
4685
|
-
|
|
5334
|
+
mimeType: Type5.Optional(
|
|
5335
|
+
Type5.String({
|
|
4686
5336
|
minLength: 1,
|
|
4687
5337
|
description: "Optional MIME type override (for example image/png)."
|
|
4688
5338
|
})
|
|
@@ -4695,7 +5345,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
4695
5345
|
const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
|
|
4696
5346
|
if (!fileBuffer) {
|
|
4697
5347
|
const generatedFile = hooks.getGeneratedFile?.(
|
|
4698
|
-
|
|
5348
|
+
path7.posix.basename(targetPath)
|
|
4699
5349
|
);
|
|
4700
5350
|
if (generatedFile) {
|
|
4701
5351
|
hooks.onGeneratedFiles?.([generatedFile]);
|
|
@@ -4742,8 +5392,95 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
4742
5392
|
});
|
|
4743
5393
|
}
|
|
4744
5394
|
|
|
5395
|
+
// src/chat/tools/sandbox/list-dir.ts
|
|
5396
|
+
import path8 from "path";
|
|
5397
|
+
import { Type as Type6 } from "@sinclair/typebox";
|
|
5398
|
+
var DEFAULT_LIST_LIMIT = 500;
|
|
5399
|
+
async function listDir(params) {
|
|
5400
|
+
const dirPath = resolveWorkspacePath(params.path);
|
|
5401
|
+
const limit = positiveInteger(params.limit) ?? DEFAULT_LIST_LIMIT;
|
|
5402
|
+
const stat = await params.fs.stat(dirPath);
|
|
5403
|
+
if (!stat.isDirectory()) {
|
|
5404
|
+
throw new Error(`Not a directory: ${params.path ?? "."}`);
|
|
5405
|
+
}
|
|
5406
|
+
const entries = (await params.fs.readdir(dirPath)).sort(
|
|
5407
|
+
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
5408
|
+
);
|
|
5409
|
+
const output = [];
|
|
5410
|
+
let entryLimitReached = false;
|
|
5411
|
+
for (const entry of entries) {
|
|
5412
|
+
if (output.length >= limit) {
|
|
5413
|
+
entryLimitReached = true;
|
|
5414
|
+
break;
|
|
5415
|
+
}
|
|
5416
|
+
const entryPath = path8.posix.join(dirPath, entry);
|
|
5417
|
+
try {
|
|
5418
|
+
const entryStat = await params.fs.stat(entryPath);
|
|
5419
|
+
output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
|
|
5420
|
+
} catch {
|
|
5421
|
+
continue;
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const bounded = truncateText(
|
|
5425
|
+
output.length > 0 ? output.join("\n") : "(empty directory)"
|
|
5426
|
+
);
|
|
5427
|
+
const notices = [];
|
|
5428
|
+
if (entryLimitReached) {
|
|
5429
|
+
notices.push(
|
|
5430
|
+
`${limit} entries limit reached. Use a higher limit to continue.`
|
|
5431
|
+
);
|
|
5432
|
+
}
|
|
5433
|
+
if (bounded.truncated) {
|
|
5434
|
+
notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
|
|
5435
|
+
}
|
|
5436
|
+
return {
|
|
5437
|
+
content: [
|
|
5438
|
+
{
|
|
5439
|
+
type: "text",
|
|
5440
|
+
text: notices.length > 0 ? `${bounded.content}
|
|
5441
|
+
|
|
5442
|
+
[${notices.join(" ")}]` : bounded.content
|
|
5443
|
+
}
|
|
5444
|
+
],
|
|
5445
|
+
details: {
|
|
5446
|
+
ok: true,
|
|
5447
|
+
path: params.path ?? ".",
|
|
5448
|
+
truncated: entryLimitReached || bounded.truncated,
|
|
5449
|
+
...entryLimitReached ? { entry_limit_reached: limit } : {}
|
|
5450
|
+
}
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
function createListDirTool() {
|
|
5454
|
+
return tool({
|
|
5455
|
+
description: "List a sandbox workspace directory. Returns sorted entries with '/' suffixes for directories and bounded truncation notices.",
|
|
5456
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5457
|
+
inputSchema: Type6.Object(
|
|
5458
|
+
{
|
|
5459
|
+
path: Type6.Optional(
|
|
5460
|
+
Type6.String({
|
|
5461
|
+
minLength: 1,
|
|
5462
|
+
description: "Directory path in the sandbox workspace. Defaults to the workspace root."
|
|
5463
|
+
})
|
|
5464
|
+
),
|
|
5465
|
+
limit: Type6.Optional(
|
|
5466
|
+
Type6.Integer({
|
|
5467
|
+
minimum: 1,
|
|
5468
|
+
description: "Maximum entries to return. Defaults to 500."
|
|
5469
|
+
})
|
|
5470
|
+
)
|
|
5471
|
+
},
|
|
5472
|
+
{ additionalProperties: false }
|
|
5473
|
+
),
|
|
5474
|
+
execute: async () => {
|
|
5475
|
+
throw new Error(
|
|
5476
|
+
"listDir can only run when sandbox execution is enabled."
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
|
|
4745
5482
|
// src/chat/tools/web/image-generate.ts
|
|
4746
|
-
import { Type as
|
|
5483
|
+
import { Type as Type7 } from "@sinclair/typebox";
|
|
4747
5484
|
var DEFAULT_IMAGE_MODEL = "google/gemini-3-pro-image";
|
|
4748
5485
|
var ENRICHMENT_SYSTEM_PROMPT = `You are an image prompt enrichment agent. Your job is to rewrite image generation requests to reflect a specific visual identity and mood.
|
|
4749
5486
|
|
|
@@ -4804,8 +5541,8 @@ function parseImageGenerationError(status, body, model) {
|
|
|
4804
5541
|
function createImageGenerateTool(hooks, deps = {}) {
|
|
4805
5542
|
return tool({
|
|
4806
5543
|
description: "Generate images from a prompt. Use when the user wants to visually show or represent something \u2014 feelings, concepts, art, humor, or any visual idea. Also use for explicit image creation requests.",
|
|
4807
|
-
inputSchema:
|
|
4808
|
-
prompt:
|
|
5544
|
+
inputSchema: Type7.Object({
|
|
5545
|
+
prompt: Type7.String({
|
|
4809
5546
|
minLength: 1,
|
|
4810
5547
|
maxLength: 4e3,
|
|
4811
5548
|
description: "Image generation prompt."
|
|
@@ -4888,7 +5625,7 @@ function createImageGenerateTool(hooks, deps = {}) {
|
|
|
4888
5625
|
}
|
|
4889
5626
|
|
|
4890
5627
|
// src/chat/tools/skill/call-mcp-tool.ts
|
|
4891
|
-
import { Type as
|
|
5628
|
+
import { Type as Type8 } from "@sinclair/typebox";
|
|
4892
5629
|
function resolveMcpArguments(input) {
|
|
4893
5630
|
const extraKeys = Object.keys(input).filter(
|
|
4894
5631
|
(key) => key !== "tool_name" && key !== "arguments"
|
|
@@ -4913,14 +5650,14 @@ function resolveMcpArguments(input) {
|
|
|
4913
5650
|
function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
|
|
4914
5651
|
return tool({
|
|
4915
5652
|
description: "Call an active MCP tool by exact tool_name. Use loadSkill to activate the provider, then searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
|
|
4916
|
-
inputSchema:
|
|
5653
|
+
inputSchema: Type8.Object(
|
|
4917
5654
|
{
|
|
4918
|
-
tool_name:
|
|
5655
|
+
tool_name: Type8.String({
|
|
4919
5656
|
minLength: 1,
|
|
4920
5657
|
description: "Exact MCP tool_name from searchMcpTools."
|
|
4921
5658
|
}),
|
|
4922
|
-
arguments:
|
|
4923
|
-
|
|
5659
|
+
arguments: Type8.Optional(
|
|
5660
|
+
Type8.Record(Type8.String(), Type8.Unknown(), {
|
|
4924
5661
|
description: 'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
|
|
4925
5662
|
})
|
|
4926
5663
|
)
|
|
@@ -4941,7 +5678,7 @@ function createCallMcpToolTool(mcpToolManager, getActiveSkills) {
|
|
|
4941
5678
|
}
|
|
4942
5679
|
|
|
4943
5680
|
// src/chat/tools/skill/load-skill.ts
|
|
4944
|
-
import { Type as
|
|
5681
|
+
import { Type as Type9 } from "@sinclair/typebox";
|
|
4945
5682
|
function toLoadedSkill(result, availableSkills) {
|
|
4946
5683
|
if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
|
|
4947
5684
|
return null;
|
|
@@ -4988,8 +5725,8 @@ async function loadSkillFromHost(availableSkills, skillName) {
|
|
|
4988
5725
|
function createLoadSkillTool(availableSkills, options) {
|
|
4989
5726
|
return tool({
|
|
4990
5727
|
description: "Load a skill by name for this turn. The result includes working_directory; resolve skill paths there and run skill-owned bash commands from there or with absolute paths. When the result includes mcp_provider, use searchMcpTools before callMcpTool. Use when a request clearly matches a known skill.",
|
|
4991
|
-
inputSchema:
|
|
4992
|
-
skill_name:
|
|
5728
|
+
inputSchema: Type9.Object({
|
|
5729
|
+
skill_name: Type9.String({
|
|
4993
5730
|
minLength: 1,
|
|
4994
5731
|
description: "Skill name to load, without the leading slash."
|
|
4995
5732
|
})
|
|
@@ -5009,7 +5746,7 @@ function createLoadSkillTool(availableSkills, options) {
|
|
|
5009
5746
|
}
|
|
5010
5747
|
|
|
5011
5748
|
// src/chat/tools/skill/search-mcp-tools.ts
|
|
5012
|
-
import { Type as
|
|
5749
|
+
import { Type as Type10 } from "@sinclair/typebox";
|
|
5013
5750
|
|
|
5014
5751
|
// src/chat/tools/skill/mcp-tool-summary.ts
|
|
5015
5752
|
function getSchemaProperties(schema) {
|
|
@@ -5199,22 +5936,22 @@ function searchMcpCatalog(tools, query) {
|
|
|
5199
5936
|
function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
5200
5937
|
return tool({
|
|
5201
5938
|
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.",
|
|
5202
|
-
inputSchema:
|
|
5939
|
+
inputSchema: Type10.Object(
|
|
5203
5940
|
{
|
|
5204
|
-
query:
|
|
5205
|
-
|
|
5941
|
+
query: Type10.Optional(
|
|
5942
|
+
Type10.String({
|
|
5206
5943
|
minLength: 1,
|
|
5207
5944
|
description: "Optional search terms describing the MCP tool or arguments needed."
|
|
5208
5945
|
})
|
|
5209
5946
|
),
|
|
5210
|
-
provider:
|
|
5211
|
-
|
|
5947
|
+
provider: Type10.Optional(
|
|
5948
|
+
Type10.String({
|
|
5212
5949
|
minLength: 1,
|
|
5213
5950
|
description: "Optional provider name to list or search within."
|
|
5214
5951
|
})
|
|
5215
5952
|
),
|
|
5216
|
-
max_results:
|
|
5217
|
-
|
|
5953
|
+
max_results: Type10.Optional(
|
|
5954
|
+
Type10.Integer({
|
|
5218
5955
|
minimum: 1,
|
|
5219
5956
|
maximum: MAX_RESULTS,
|
|
5220
5957
|
description: "Maximum matching tool descriptors to return."
|
|
@@ -5245,16 +5982,55 @@ function createSearchMcpToolsTool(mcpToolManager, getActiveSkills) {
|
|
|
5245
5982
|
}
|
|
5246
5983
|
|
|
5247
5984
|
// src/chat/tools/sandbox/read-file.ts
|
|
5248
|
-
import { Type as
|
|
5985
|
+
import { Type as Type11 } from "@sinclair/typebox";
|
|
5986
|
+
var DEFAULT_READ_LIMIT = 1e3;
|
|
5987
|
+
function sliceFileContent(params) {
|
|
5988
|
+
const normalized = normalizeToLf(params.content);
|
|
5989
|
+
const lines = normalized.length === 0 ? [] : normalized.split("\n");
|
|
5990
|
+
const requestedOffset = positiveInteger(params.offset);
|
|
5991
|
+
const requestedLimit = positiveInteger(params.limit);
|
|
5992
|
+
const startLine = requestedOffset ?? 1;
|
|
5993
|
+
const maxLines = requestedLimit ?? DEFAULT_READ_LIMIT;
|
|
5994
|
+
const startIndex = Math.min(lines.length, startLine - 1);
|
|
5995
|
+
const selected = lines.slice(startIndex, startIndex + maxLines);
|
|
5996
|
+
const endLine = selected.length > 0 ? startLine + selected.length - 1 : startLine - 1;
|
|
5997
|
+
const truncated = startIndex > 0 || endLine < lines.length;
|
|
5998
|
+
const rangeRequested = requestedOffset !== void 0 || requestedLimit !== void 0;
|
|
5999
|
+
return {
|
|
6000
|
+
content: !rangeRequested && !truncated ? params.content : selected.join("\n"),
|
|
6001
|
+
end_line: selected.length > 0 ? endLine : void 0,
|
|
6002
|
+
path: params.path,
|
|
6003
|
+
start_line: startLine,
|
|
6004
|
+
success: true,
|
|
6005
|
+
total_lines: lines.length,
|
|
6006
|
+
truncated,
|
|
6007
|
+
...endLine < lines.length ? {
|
|
6008
|
+
continuation: `Read more with offset=${endLine + 1} and limit=${maxLines}.`
|
|
6009
|
+
} : {}
|
|
6010
|
+
};
|
|
6011
|
+
}
|
|
5249
6012
|
function createReadFileTool() {
|
|
5250
6013
|
return tool({
|
|
5251
|
-
description: "Read a
|
|
5252
|
-
|
|
6014
|
+
description: "Read a bounded line range from a file in the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Prefer grep/findFiles/listDir for broad discovery.",
|
|
6015
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
6016
|
+
inputSchema: Type11.Object(
|
|
5253
6017
|
{
|
|
5254
|
-
path:
|
|
6018
|
+
path: Type11.String({
|
|
5255
6019
|
minLength: 1,
|
|
5256
6020
|
description: "Path to the file in the sandbox workspace."
|
|
5257
|
-
})
|
|
6021
|
+
}),
|
|
6022
|
+
offset: Type11.Optional(
|
|
6023
|
+
Type11.Integer({
|
|
6024
|
+
minimum: 1,
|
|
6025
|
+
description: "1-indexed line number to start reading from."
|
|
6026
|
+
})
|
|
6027
|
+
),
|
|
6028
|
+
limit: Type11.Optional(
|
|
6029
|
+
Type11.Integer({
|
|
6030
|
+
minimum: 1,
|
|
6031
|
+
description: "Maximum number of lines to read. Defaults to 1000."
|
|
6032
|
+
})
|
|
6033
|
+
)
|
|
5258
6034
|
},
|
|
5259
6035
|
{ additionalProperties: false }
|
|
5260
6036
|
),
|
|
@@ -5267,12 +6043,12 @@ function createReadFileTool() {
|
|
|
5267
6043
|
}
|
|
5268
6044
|
|
|
5269
6045
|
// src/chat/tools/runtime/report-progress.ts
|
|
5270
|
-
import { Type as
|
|
6046
|
+
import { Type as Type12 } from "@sinclair/typebox";
|
|
5271
6047
|
function createReportProgressTool() {
|
|
5272
6048
|
return tool({
|
|
5273
6049
|
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.",
|
|
5274
|
-
inputSchema:
|
|
5275
|
-
message:
|
|
6050
|
+
inputSchema: Type12.Object({
|
|
6051
|
+
message: Type12.String({
|
|
5276
6052
|
minLength: 1,
|
|
5277
6053
|
description: "Short user-facing progress message."
|
|
5278
6054
|
})
|
|
@@ -5281,7 +6057,7 @@ function createReportProgressTool() {
|
|
|
5281
6057
|
}
|
|
5282
6058
|
|
|
5283
6059
|
// src/chat/tools/slack/channel-list-messages.ts
|
|
5284
|
-
import { Type as
|
|
6060
|
+
import { Type as Type13 } from "@sinclair/typebox";
|
|
5285
6061
|
|
|
5286
6062
|
// src/chat/slack/channel.ts
|
|
5287
6063
|
async function listChannelMessages(input) {
|
|
@@ -5370,39 +6146,40 @@ async function listThreadReplies(input) {
|
|
|
5370
6146
|
function createSlackChannelListMessagesTool(context) {
|
|
5371
6147
|
return tool({
|
|
5372
6148
|
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.",
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
6149
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
6150
|
+
inputSchema: Type13.Object({
|
|
6151
|
+
limit: Type13.Optional(
|
|
6152
|
+
Type13.Integer({
|
|
5376
6153
|
minimum: 1,
|
|
5377
6154
|
maximum: 1e3,
|
|
5378
6155
|
description: "Maximum number of messages to return across pages."
|
|
5379
6156
|
})
|
|
5380
6157
|
),
|
|
5381
|
-
cursor:
|
|
5382
|
-
|
|
6158
|
+
cursor: Type13.Optional(
|
|
6159
|
+
Type13.String({
|
|
5383
6160
|
minLength: 1,
|
|
5384
6161
|
description: "Optional cursor to continue from a prior call."
|
|
5385
6162
|
})
|
|
5386
6163
|
),
|
|
5387
|
-
oldest:
|
|
5388
|
-
|
|
6164
|
+
oldest: Type13.Optional(
|
|
6165
|
+
Type13.String({
|
|
5389
6166
|
minLength: 1,
|
|
5390
6167
|
description: "Optional oldest message timestamp (Slack ts) for range filtering."
|
|
5391
6168
|
})
|
|
5392
6169
|
),
|
|
5393
|
-
latest:
|
|
5394
|
-
|
|
6170
|
+
latest: Type13.Optional(
|
|
6171
|
+
Type13.String({
|
|
5395
6172
|
minLength: 1,
|
|
5396
6173
|
description: "Optional latest message timestamp (Slack ts) for range filtering."
|
|
5397
6174
|
})
|
|
5398
6175
|
),
|
|
5399
|
-
inclusive:
|
|
5400
|
-
|
|
6176
|
+
inclusive: Type13.Optional(
|
|
6177
|
+
Type13.Boolean({
|
|
5401
6178
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
5402
6179
|
})
|
|
5403
6180
|
),
|
|
5404
|
-
max_pages:
|
|
5405
|
-
|
|
6181
|
+
max_pages: Type13.Optional(
|
|
6182
|
+
Type13.Integer({
|
|
5406
6183
|
minimum: 1,
|
|
5407
6184
|
maximum: 10,
|
|
5408
6185
|
description: "Maximum number of API pages to traverse in a single call."
|
|
@@ -5456,7 +6233,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5456
6233
|
}
|
|
5457
6234
|
|
|
5458
6235
|
// src/chat/tools/slack/channel-post-message.ts
|
|
5459
|
-
import { Type as
|
|
6236
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
5460
6237
|
|
|
5461
6238
|
// src/chat/tools/idempotency.ts
|
|
5462
6239
|
function stableSerialize(value) {
|
|
@@ -5478,8 +6255,8 @@ function createOperationKey(toolName, input) {
|
|
|
5478
6255
|
function createSlackChannelPostMessageTool(context, state) {
|
|
5479
6256
|
return tool({
|
|
5480
6257
|
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.",
|
|
5481
|
-
inputSchema:
|
|
5482
|
-
text:
|
|
6258
|
+
inputSchema: Type14.Object({
|
|
6259
|
+
text: Type14.String({
|
|
5483
6260
|
minLength: 1,
|
|
5484
6261
|
maxLength: 4e4,
|
|
5485
6262
|
description: "Slack mrkdwn text to post."
|
|
@@ -5522,12 +6299,12 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
5522
6299
|
}
|
|
5523
6300
|
|
|
5524
6301
|
// src/chat/tools/slack/message-add-reaction.ts
|
|
5525
|
-
import { Type as
|
|
6302
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
5526
6303
|
function createSlackMessageAddReactionTool(context, state) {
|
|
5527
6304
|
return tool({
|
|
5528
6305
|
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.",
|
|
5529
|
-
inputSchema:
|
|
5530
|
-
emoji:
|
|
6306
|
+
inputSchema: Type15.Object({
|
|
6307
|
+
emoji: Type15.String({
|
|
5531
6308
|
minLength: 1,
|
|
5532
6309
|
maxLength: 64,
|
|
5533
6310
|
description: "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
|
|
@@ -5585,7 +6362,7 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
5585
6362
|
}
|
|
5586
6363
|
|
|
5587
6364
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5588
|
-
import { Type as
|
|
6365
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
5589
6366
|
|
|
5590
6367
|
// src/chat/tools/slack/canvases.ts
|
|
5591
6368
|
function normalizeCanvasMarkdown(markdown) {
|
|
@@ -5801,13 +6578,13 @@ function mergeRecentCanvases(existing, created) {
|
|
|
5801
6578
|
function createSlackCanvasCreateTool(context, state) {
|
|
5802
6579
|
return tool({
|
|
5803
6580
|
description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
|
|
5804
|
-
inputSchema:
|
|
5805
|
-
title:
|
|
6581
|
+
inputSchema: Type16.Object({
|
|
6582
|
+
title: Type16.String({
|
|
5806
6583
|
minLength: 1,
|
|
5807
6584
|
maxLength: 160,
|
|
5808
6585
|
description: "Canvas title."
|
|
5809
6586
|
}),
|
|
5810
|
-
markdown:
|
|
6587
|
+
markdown: Type16.String({
|
|
5811
6588
|
minLength: 1,
|
|
5812
6589
|
description: "Canvas markdown body content."
|
|
5813
6590
|
})
|
|
@@ -5873,29 +6650,29 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
5873
6650
|
function createSlackCanvasUpdateTool(state, _context) {
|
|
5874
6651
|
return tool({
|
|
5875
6652
|
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.",
|
|
5876
|
-
inputSchema:
|
|
5877
|
-
markdown:
|
|
6653
|
+
inputSchema: Type16.Object({
|
|
6654
|
+
markdown: Type16.String({
|
|
5878
6655
|
minLength: 1,
|
|
5879
6656
|
description: "Markdown content to insert or use as replacement text."
|
|
5880
6657
|
}),
|
|
5881
|
-
operation:
|
|
5882
|
-
|
|
6658
|
+
operation: Type16.Optional(
|
|
6659
|
+
Type16.Union(
|
|
5883
6660
|
[
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
6661
|
+
Type16.Literal("insert_at_end"),
|
|
6662
|
+
Type16.Literal("insert_at_start"),
|
|
6663
|
+
Type16.Literal("replace")
|
|
5887
6664
|
],
|
|
5888
6665
|
{ description: "Canvas update mode." }
|
|
5889
6666
|
)
|
|
5890
6667
|
),
|
|
5891
|
-
section_id:
|
|
5892
|
-
|
|
6668
|
+
section_id: Type16.Optional(
|
|
6669
|
+
Type16.String({
|
|
5893
6670
|
minLength: 1,
|
|
5894
6671
|
description: "Optional section ID required for targeted replace operations."
|
|
5895
6672
|
})
|
|
5896
6673
|
),
|
|
5897
|
-
section_contains_text:
|
|
5898
|
-
|
|
6674
|
+
section_contains_text: Type16.Optional(
|
|
6675
|
+
Type16.String({
|
|
5899
6676
|
minLength: 1,
|
|
5900
6677
|
description: "Optional helper text used to find the target section when section_id is not provided."
|
|
5901
6678
|
})
|
|
@@ -5961,8 +6738,9 @@ function createSlackCanvasUpdateTool(state, _context) {
|
|
|
5961
6738
|
function createSlackCanvasReadTool() {
|
|
5962
6739
|
return tool({
|
|
5963
6740
|
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.",
|
|
5964
|
-
|
|
5965
|
-
|
|
6741
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
6742
|
+
inputSchema: Type16.Object({
|
|
6743
|
+
canvas: Type16.String({
|
|
5966
6744
|
minLength: 1,
|
|
5967
6745
|
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
5968
6746
|
})
|
|
@@ -6012,7 +6790,7 @@ function createSlackCanvasReadTool() {
|
|
|
6012
6790
|
}
|
|
6013
6791
|
|
|
6014
6792
|
// src/chat/tools/slack/list-tools.ts
|
|
6015
|
-
import { Type as
|
|
6793
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
6016
6794
|
|
|
6017
6795
|
// src/chat/tools/slack/lists.ts
|
|
6018
6796
|
function normalizeKey(value) {
|
|
@@ -6186,8 +6964,8 @@ async function updateListItem(input) {
|
|
|
6186
6964
|
function createSlackListCreateTool(state) {
|
|
6187
6965
|
return tool({
|
|
6188
6966
|
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.",
|
|
6189
|
-
inputSchema:
|
|
6190
|
-
name:
|
|
6967
|
+
inputSchema: Type17.Object({
|
|
6968
|
+
name: Type17.String({
|
|
6191
6969
|
minLength: 1,
|
|
6192
6970
|
maxLength: 160,
|
|
6193
6971
|
description: "Name for the new Slack list."
|
|
@@ -6222,20 +7000,20 @@ function createSlackListCreateTool(state) {
|
|
|
6222
7000
|
function createSlackListAddItemsTool(state) {
|
|
6223
7001
|
return tool({
|
|
6224
7002
|
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.",
|
|
6225
|
-
inputSchema:
|
|
6226
|
-
items:
|
|
7003
|
+
inputSchema: Type17.Object({
|
|
7004
|
+
items: Type17.Array(Type17.String({ minLength: 1 }), {
|
|
6227
7005
|
minItems: 1,
|
|
6228
7006
|
maxItems: 25,
|
|
6229
7007
|
description: "List item titles to create."
|
|
6230
7008
|
}),
|
|
6231
|
-
assignee_user_id:
|
|
6232
|
-
|
|
7009
|
+
assignee_user_id: Type17.Optional(
|
|
7010
|
+
Type17.String({
|
|
6233
7011
|
minLength: 1,
|
|
6234
7012
|
description: "Optional Slack user ID assigned to all created items."
|
|
6235
7013
|
})
|
|
6236
7014
|
),
|
|
6237
|
-
due_date:
|
|
6238
|
-
|
|
7015
|
+
due_date: Type17.Optional(
|
|
7016
|
+
Type17.String({
|
|
6239
7017
|
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
6240
7018
|
description: "Optional due date in YYYY-MM-DD format."
|
|
6241
7019
|
})
|
|
@@ -6284,9 +7062,10 @@ function createSlackListAddItemsTool(state) {
|
|
|
6284
7062
|
function createSlackListGetItemsTool(state) {
|
|
6285
7063
|
return tool({
|
|
6286
7064
|
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.",
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
7065
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7066
|
+
inputSchema: Type17.Object({
|
|
7067
|
+
limit: Type17.Optional(
|
|
7068
|
+
Type17.Integer({
|
|
6290
7069
|
minimum: 1,
|
|
6291
7070
|
maximum: 200,
|
|
6292
7071
|
description: "Maximum number of list items to return."
|
|
@@ -6311,19 +7090,19 @@ function createSlackListGetItemsTool(state) {
|
|
|
6311
7090
|
function createSlackListUpdateItemTool(state) {
|
|
6312
7091
|
return tool({
|
|
6313
7092
|
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.",
|
|
6314
|
-
inputSchema:
|
|
7093
|
+
inputSchema: Type17.Object(
|
|
6315
7094
|
{
|
|
6316
|
-
item_id:
|
|
7095
|
+
item_id: Type17.String({
|
|
6317
7096
|
minLength: 1,
|
|
6318
7097
|
description: "ID of the Slack list item to update."
|
|
6319
7098
|
}),
|
|
6320
|
-
completed:
|
|
6321
|
-
|
|
7099
|
+
completed: Type17.Optional(
|
|
7100
|
+
Type17.Boolean({
|
|
6322
7101
|
description: "Optional completion status update."
|
|
6323
7102
|
})
|
|
6324
7103
|
),
|
|
6325
|
-
title:
|
|
6326
|
-
|
|
7104
|
+
title: Type17.Optional(
|
|
7105
|
+
Type17.String({
|
|
6327
7106
|
minLength: 1,
|
|
6328
7107
|
description: "Optional new item title."
|
|
6329
7108
|
})
|
|
@@ -6373,11 +7152,12 @@ function createSlackListUpdateItemTool(state) {
|
|
|
6373
7152
|
}
|
|
6374
7153
|
|
|
6375
7154
|
// src/chat/tools/system-time.ts
|
|
6376
|
-
import { Type as
|
|
7155
|
+
import { Type as Type18 } from "@sinclair/typebox";
|
|
6377
7156
|
function createSystemTimeTool() {
|
|
6378
7157
|
return tool({
|
|
6379
7158
|
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.",
|
|
6380
|
-
|
|
7159
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7160
|
+
inputSchema: Type18.Object({}),
|
|
6381
7161
|
execute: async () => {
|
|
6382
7162
|
const now = /* @__PURE__ */ new Date();
|
|
6383
7163
|
return {
|
|
@@ -6395,7 +7175,7 @@ function createSystemTimeTool() {
|
|
|
6395
7175
|
import {
|
|
6396
7176
|
Agent
|
|
6397
7177
|
} from "@mariozechner/pi-agent-core";
|
|
6398
|
-
import { Type as
|
|
7178
|
+
import { Type as Type19 } from "@sinclair/typebox";
|
|
6399
7179
|
|
|
6400
7180
|
// src/chat/respond-helpers.ts
|
|
6401
7181
|
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
@@ -6533,12 +7313,6 @@ function encodeNonImageAttachmentForPrompt(attachment) {
|
|
|
6533
7313
|
"</attachment>"
|
|
6534
7314
|
].join("\n");
|
|
6535
7315
|
}
|
|
6536
|
-
function buildExecutionFailureMessage(toolErrorCount) {
|
|
6537
|
-
if (toolErrorCount > 0) {
|
|
6538
|
-
return "I couldn't complete this because one or more required tools failed in this turn. I've logged the failure details.";
|
|
6539
|
-
}
|
|
6540
|
-
return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
|
|
6541
|
-
}
|
|
6542
7316
|
function isToolResultMessage(value) {
|
|
6543
7317
|
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
6544
7318
|
}
|
|
@@ -6635,23 +7409,13 @@ function createStateAdvisorSessionStore() {
|
|
|
6635
7409
|
}
|
|
6636
7410
|
|
|
6637
7411
|
// src/chat/tools/advisor/tool.ts
|
|
6638
|
-
var
|
|
6639
|
-
"bash",
|
|
6640
|
-
"readFile",
|
|
6641
|
-
"searchMcpTools",
|
|
6642
|
-
"slackCanvasRead",
|
|
6643
|
-
"slackChannelListMessages",
|
|
6644
|
-
"slackListGetItems",
|
|
6645
|
-
"systemTime",
|
|
6646
|
-
"webFetch",
|
|
6647
|
-
"webSearch"
|
|
6648
|
-
]);
|
|
6649
|
-
var ADVISOR_TOOL_DESCRIPTION = "Ask a stronger advisor for deep technical guidance. Call this when the task has a hard reasoning core: algorithm design, architecture, concurrency, security-sensitive logic, data modeling, unclear requirements, repeated failures, difficult debugging, broad refactors, or final review of nontrivial work. Pass a focused question plus curated context containing the exact evidence, constraints, current plan, alternatives, command output, code snippets, or diffs the advisor should start from. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, can use inspection tools to verify evidence, can reason deeply, and returns guidance for you to apply and verify. Follow-up calls can build on prior advisor guidance but must include any new evidence or changed constraints. Use it after initial orientation reads when repository context matters, before committing to a non-obvious implementation plan, when changing approach, when stuck, and before declaring complex work complete. Do not use it for greetings, simple deterministic edits, routine formatting, or tasks where the next action is already obvious from fresh tool output.";
|
|
7412
|
+
var ADVISOR_TOOL_DESCRIPTION = "Ask a stronger advisor for deep technical guidance. Call this when the task has a hard reasoning core: algorithm design, architecture, concurrency, security-sensitive logic, data modeling, unclear requirements, repeated failures, difficult debugging, broad refactors, or final review of nontrivial work. Pass a focused question plus curated context containing the exact evidence, constraints, current plan, alternatives, command output, code snippets, or diffs the advisor should start from. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, can use read-only inspection tools to verify evidence, can reason deeply, and returns guidance for you to apply and verify. Follow-up calls can build on prior advisor guidance but must include any new evidence or changed constraints. Use it after initial orientation reads when repository context matters, before committing to a non-obvious implementation plan, when changing approach, when stuck, and before declaring complex work complete. Do not use it for greetings, simple deterministic edits, routine formatting, or tasks where the next action is already obvious from fresh tool output.";
|
|
6650
7413
|
var ADVISOR_SYSTEM_PROMPT = [
|
|
6651
7414
|
"You are a senior technical advisor for the executor.",
|
|
6652
|
-
"Analyze the executor-supplied context deeply. Use
|
|
7415
|
+
"Analyze the executor-supplied context deeply. Use read-only tools when direct inspection or verification would materially improve the advice.",
|
|
6653
7416
|
"Distinguish evidence from inference. Treat the advisor task as the focus for this call and the executor context as the starting evidence packet.",
|
|
6654
7417
|
"Do not assume access to parent transcript or tool output that was not included or gathered in this advisor call.",
|
|
7418
|
+
"Use only the read-only tools provided to you.",
|
|
6655
7419
|
"Do not make user-visible side effects, post Slack messages, or mutate files. If a mutating action is needed, recommend it to the executor instead.",
|
|
6656
7420
|
"Identify the hard part, recommend a concrete plan or correction, call out blocking risks, and propose focused verification.",
|
|
6657
7421
|
"If the supplied context is insufficient, say exactly what additional evidence the executor needs to gather before acting.",
|
|
@@ -6684,20 +7448,27 @@ function success(memo) {
|
|
|
6684
7448
|
}
|
|
6685
7449
|
};
|
|
6686
7450
|
}
|
|
6687
|
-
function
|
|
6688
|
-
return
|
|
7451
|
+
function hasReadOnlyToolAnnotations(annotations) {
|
|
7452
|
+
return annotations?.readOnlyHint === true && annotations.destructiveHint !== true;
|
|
7453
|
+
}
|
|
7454
|
+
function createAdvisorToolDefinitions(definitions) {
|
|
7455
|
+
return Object.fromEntries(
|
|
7456
|
+
Object.entries(definitions).filter(
|
|
7457
|
+
([name, definition]) => name !== "callMcpTool" && name !== "searchMcpTools" && hasReadOnlyToolAnnotations(definition.annotations)
|
|
7458
|
+
)
|
|
7459
|
+
);
|
|
6689
7460
|
}
|
|
6690
7461
|
function createAdvisorTool(context) {
|
|
6691
7462
|
const store = context.store ?? createStateAdvisorSessionStore();
|
|
6692
7463
|
const spanContext = context.logContext ?? {};
|
|
6693
7464
|
return tool({
|
|
6694
7465
|
description: ADVISOR_TOOL_DESCRIPTION,
|
|
6695
|
-
inputSchema:
|
|
6696
|
-
question:
|
|
7466
|
+
inputSchema: Type19.Object({
|
|
7467
|
+
question: Type19.String({
|
|
6697
7468
|
minLength: 1,
|
|
6698
7469
|
description: "Focused advisor question or decision point."
|
|
6699
7470
|
}),
|
|
6700
|
-
context:
|
|
7471
|
+
context: Type19.String({
|
|
6701
7472
|
minLength: 1,
|
|
6702
7473
|
description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
|
|
6703
7474
|
})
|
|
@@ -6809,7 +7580,7 @@ function createAdvisorTool(context) {
|
|
|
6809
7580
|
}
|
|
6810
7581
|
|
|
6811
7582
|
// src/chat/tools/web/fetch-tool.ts
|
|
6812
|
-
import { Type as
|
|
7583
|
+
import { Type as Type20 } from "@sinclair/typebox";
|
|
6813
7584
|
|
|
6814
7585
|
// src/chat/tools/web/constants.ts
|
|
6815
7586
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -7011,13 +7782,16 @@ async function assertPublicUrl(rawUrl) {
|
|
|
7011
7782
|
}
|
|
7012
7783
|
return parsed;
|
|
7013
7784
|
}
|
|
7014
|
-
async function withTimeout(task, timeoutMs, label) {
|
|
7785
|
+
async function withTimeout(task, timeoutMs, label, options) {
|
|
7015
7786
|
let timer;
|
|
7016
7787
|
const timeoutPromise = new Promise((_, reject) => {
|
|
7017
|
-
timer = setTimeout(
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7788
|
+
timer = setTimeout(() => {
|
|
7789
|
+
reject(new Error(`${label} timed out`));
|
|
7790
|
+
try {
|
|
7791
|
+
options?.onTimeout?.();
|
|
7792
|
+
} catch {
|
|
7793
|
+
}
|
|
7794
|
+
}, timeoutMs);
|
|
7021
7795
|
});
|
|
7022
7796
|
try {
|
|
7023
7797
|
return await Promise.race([task, timeoutPromise]);
|
|
@@ -7154,13 +7928,18 @@ function extractHttpStatusFromMessage(message) {
|
|
|
7154
7928
|
function createWebFetchTool(hooks) {
|
|
7155
7929
|
return tool({
|
|
7156
7930
|
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.",
|
|
7157
|
-
|
|
7158
|
-
|
|
7931
|
+
annotations: {
|
|
7932
|
+
readOnlyHint: true,
|
|
7933
|
+
destructiveHint: false,
|
|
7934
|
+
openWorldHint: true
|
|
7935
|
+
},
|
|
7936
|
+
inputSchema: Type20.Object({
|
|
7937
|
+
url: Type20.String({
|
|
7159
7938
|
minLength: 1,
|
|
7160
7939
|
description: "HTTP(S) URL to fetch."
|
|
7161
7940
|
}),
|
|
7162
|
-
max_chars:
|
|
7163
|
-
|
|
7941
|
+
max_chars: Type20.Optional(
|
|
7942
|
+
Type20.Integer({
|
|
7164
7943
|
minimum: 500,
|
|
7165
7944
|
maximum: MAX_FETCH_CHARS,
|
|
7166
7945
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -7220,7 +7999,7 @@ function createWebFetchTool(hooks) {
|
|
|
7220
7999
|
// src/chat/tools/web/search.ts
|
|
7221
8000
|
import { generateText } from "ai";
|
|
7222
8001
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
7223
|
-
import { Type as
|
|
8002
|
+
import { Type as Type21 } from "@sinclair/typebox";
|
|
7224
8003
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
7225
8004
|
var MAX_RESULTS2 = 5;
|
|
7226
8005
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -7263,14 +8042,19 @@ function isAuthFailure(message) {
|
|
|
7263
8042
|
function createWebSearchTool() {
|
|
7264
8043
|
return tool({
|
|
7265
8044
|
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.",
|
|
7266
|
-
|
|
7267
|
-
|
|
8045
|
+
annotations: {
|
|
8046
|
+
readOnlyHint: true,
|
|
8047
|
+
destructiveHint: false,
|
|
8048
|
+
openWorldHint: true
|
|
8049
|
+
},
|
|
8050
|
+
inputSchema: Type21.Object({
|
|
8051
|
+
query: Type21.String({
|
|
7268
8052
|
minLength: 1,
|
|
7269
8053
|
maxLength: 500,
|
|
7270
8054
|
description: "Search query."
|
|
7271
8055
|
}),
|
|
7272
|
-
max_results:
|
|
7273
|
-
|
|
8056
|
+
max_results: Type21.Optional(
|
|
8057
|
+
Type21.Integer({
|
|
7274
8058
|
minimum: 1,
|
|
7275
8059
|
maximum: MAX_RESULTS2,
|
|
7276
8060
|
description: "Max results to return."
|
|
@@ -7280,6 +8064,7 @@ function createWebSearchTool() {
|
|
|
7280
8064
|
execute: async ({ query, max_results }) => {
|
|
7281
8065
|
const maxResults = max_results ?? 3;
|
|
7282
8066
|
const model = process.env.AI_WEB_SEARCH_MODEL ?? DEFAULT_SEARCH_MODEL;
|
|
8067
|
+
const controller = new AbortController();
|
|
7283
8068
|
try {
|
|
7284
8069
|
const provider = createGatewayProvider();
|
|
7285
8070
|
const response = await withTimeout(
|
|
@@ -7292,10 +8077,12 @@ function createWebSearchTool() {
|
|
|
7292
8077
|
maxResults
|
|
7293
8078
|
})
|
|
7294
8079
|
},
|
|
7295
|
-
toolChoice: { type: "tool", toolName: SEARCH_TOOL_NAME }
|
|
8080
|
+
toolChoice: { type: "tool", toolName: SEARCH_TOOL_NAME },
|
|
8081
|
+
abortSignal: controller.signal
|
|
7296
8082
|
}),
|
|
7297
8083
|
SEARCH_TIMEOUT_MS,
|
|
7298
|
-
"webSearch"
|
|
8084
|
+
"webSearch",
|
|
8085
|
+
{ onTimeout: () => controller.abort() }
|
|
7299
8086
|
);
|
|
7300
8087
|
const results = parseSearchResults(response.toolResults, maxResults);
|
|
7301
8088
|
return {
|
|
@@ -7336,17 +8123,20 @@ function createWebSearchTool() {
|
|
|
7336
8123
|
}
|
|
7337
8124
|
|
|
7338
8125
|
// src/chat/tools/sandbox/write-file.ts
|
|
7339
|
-
import { Type as
|
|
8126
|
+
import { Type as Type22 } from "@sinclair/typebox";
|
|
7340
8127
|
function createWriteFileTool() {
|
|
7341
8128
|
return tool({
|
|
7342
8129
|
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.",
|
|
7343
|
-
|
|
8130
|
+
promptSnippet: "new file or deliberate full-file replacement",
|
|
8131
|
+
promptGuidelines: ["targeted existing-file changes: editFile"],
|
|
8132
|
+
executionMode: "sequential",
|
|
8133
|
+
inputSchema: Type22.Object(
|
|
7344
8134
|
{
|
|
7345
|
-
path:
|
|
8135
|
+
path: Type22.String({
|
|
7346
8136
|
minLength: 1,
|
|
7347
8137
|
description: "Path to write in the sandbox workspace."
|
|
7348
8138
|
}),
|
|
7349
|
-
content:
|
|
8139
|
+
content: Type22.String({
|
|
7350
8140
|
description: "UTF-8 file content to write."
|
|
7351
8141
|
})
|
|
7352
8142
|
},
|
|
@@ -7406,6 +8196,10 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
7406
8196
|
bash: createBashTool(),
|
|
7407
8197
|
attachFile: createAttachFileTool(context.sandbox, hooks),
|
|
7408
8198
|
readFile: createReadFileTool(),
|
|
8199
|
+
editFile: createEditFileTool(),
|
|
8200
|
+
grep: createGrepTool(),
|
|
8201
|
+
findFiles: createFindFilesTool(),
|
|
8202
|
+
listDir: createListDirTool(),
|
|
7409
8203
|
writeFile: createWriteFileTool(),
|
|
7410
8204
|
webSearch: createWebSearchTool(),
|
|
7411
8205
|
webFetch: createWebFetchTool(hooks),
|
|
@@ -7672,7 +8466,7 @@ import { createBashTool as createBashTool2 } from "bash-tool";
|
|
|
7672
8466
|
|
|
7673
8467
|
// src/chat/sandbox/skill-sync.ts
|
|
7674
8468
|
import fs3 from "fs/promises";
|
|
7675
|
-
import
|
|
8469
|
+
import path9 from "path";
|
|
7676
8470
|
|
|
7677
8471
|
// src/chat/sandbox/eval-gh-stub.ts
|
|
7678
8472
|
function buildEvalGitHubCliStub() {
|
|
@@ -8041,7 +8835,7 @@ fallbackToRealSentry();
|
|
|
8041
8835
|
|
|
8042
8836
|
// src/chat/sandbox/skill-sync.ts
|
|
8043
8837
|
function toPosixRelative(base, absolute) {
|
|
8044
|
-
return
|
|
8838
|
+
return path9.relative(base, absolute).split(path9.sep).join("/");
|
|
8045
8839
|
}
|
|
8046
8840
|
async function listFilesRecursive(root) {
|
|
8047
8841
|
const queue = [root];
|
|
@@ -8051,7 +8845,7 @@ async function listFilesRecursive(root) {
|
|
|
8051
8845
|
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
8052
8846
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
8053
8847
|
for (const entry of entries) {
|
|
8054
|
-
const absolute =
|
|
8848
|
+
const absolute = path9.join(dir, entry.name);
|
|
8055
8849
|
if (entry.isDirectory()) {
|
|
8056
8850
|
queue.push(absolute);
|
|
8057
8851
|
} else if (entry.isFile()) {
|
|
@@ -8090,7 +8884,7 @@ async function buildSkillSyncFiles(availableSkills, runtimeBinDir, referenceFile
|
|
|
8090
8884
|
});
|
|
8091
8885
|
if (referenceFiles && referenceFiles.length > 0) {
|
|
8092
8886
|
for (const absoluteFile of referenceFiles) {
|
|
8093
|
-
const fileName =
|
|
8887
|
+
const fileName = path9.basename(absoluteFile);
|
|
8094
8888
|
filesToWrite.push({
|
|
8095
8889
|
path: `${SANDBOX_DATA_ROOT}/${fileName}`,
|
|
8096
8890
|
content: await fs3.readFile(absoluteFile)
|
|
@@ -8118,7 +8912,7 @@ async function buildSkillSyncFiles(availableSkills, runtimeBinDir, referenceFile
|
|
|
8118
8912
|
function collectDirectories(filesToWrite, workspaceRoot) {
|
|
8119
8913
|
const directoriesToEnsure = /* @__PURE__ */ new Set();
|
|
8120
8914
|
for (const file of filesToWrite) {
|
|
8121
|
-
const normalizedPath =
|
|
8915
|
+
const normalizedPath = path9.posix.normalize(file.path);
|
|
8122
8916
|
const parts = normalizedPath.split("/").filter(Boolean);
|
|
8123
8917
|
let current = "";
|
|
8124
8918
|
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
@@ -8131,19 +8925,19 @@ function collectDirectories(filesToWrite, workspaceRoot) {
|
|
|
8131
8925
|
).sort((a, b) => a.length - b.length);
|
|
8132
8926
|
}
|
|
8133
8927
|
function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
8134
|
-
const normalizedPath =
|
|
8928
|
+
const normalizedPath = path9.posix.normalize(sandboxPath.trim());
|
|
8135
8929
|
for (const skill of availableSkills) {
|
|
8136
8930
|
const virtualRoot = sandboxSkillDir(skill.name);
|
|
8137
8931
|
if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
|
|
8138
8932
|
continue;
|
|
8139
8933
|
}
|
|
8140
|
-
const relativePath =
|
|
8934
|
+
const relativePath = path9.posix.relative(virtualRoot, normalizedPath);
|
|
8141
8935
|
if (!relativePath || relativePath.startsWith("../")) {
|
|
8142
8936
|
return null;
|
|
8143
8937
|
}
|
|
8144
|
-
const hostRoot =
|
|
8145
|
-
const hostPath =
|
|
8146
|
-
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${
|
|
8938
|
+
const hostRoot = path9.resolve(skill.skillPath);
|
|
8939
|
+
const hostPath = path9.resolve(hostRoot, ...relativePath.split("/"));
|
|
8940
|
+
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path9.sep}`)) {
|
|
8147
8941
|
return null;
|
|
8148
8942
|
}
|
|
8149
8943
|
return hostPath;
|
|
@@ -8151,16 +8945,16 @@ function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
|
8151
8945
|
return null;
|
|
8152
8946
|
}
|
|
8153
8947
|
function resolveHostDataPath(referenceFiles, sandboxPath) {
|
|
8154
|
-
const normalizedPath =
|
|
8948
|
+
const normalizedPath = path9.posix.normalize(sandboxPath.trim());
|
|
8155
8949
|
if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
|
|
8156
8950
|
return null;
|
|
8157
8951
|
}
|
|
8158
|
-
const relativePath =
|
|
8952
|
+
const relativePath = path9.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
|
|
8159
8953
|
if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
|
|
8160
8954
|
return null;
|
|
8161
8955
|
}
|
|
8162
8956
|
for (const hostFile of referenceFiles) {
|
|
8163
|
-
if (
|
|
8957
|
+
if (path9.basename(hostFile) === relativePath) {
|
|
8164
8958
|
return hostFile;
|
|
8165
8959
|
}
|
|
8166
8960
|
}
|
|
@@ -8658,16 +9452,41 @@ function createSandboxSessionManager(options) {
|
|
|
8658
9452
|
env: input.env,
|
|
8659
9453
|
pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
|
|
8660
9454
|
});
|
|
9455
|
+
const controller = input.timeoutMs && input.timeoutMs > 0 ? new AbortController() : void 0;
|
|
9456
|
+
let timedOut = false;
|
|
9457
|
+
const timeoutId = controller ? setTimeout(() => {
|
|
9458
|
+
timedOut = true;
|
|
9459
|
+
controller.abort();
|
|
9460
|
+
}, input.timeoutMs) : void 0;
|
|
8661
9461
|
return await withTemporaryHeaderTransforms(
|
|
8662
9462
|
sandboxInstance,
|
|
8663
9463
|
input.headerTransforms,
|
|
8664
9464
|
async () => {
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
9465
|
+
try {
|
|
9466
|
+
const commandResult2 = await sandboxInstance.runCommand({
|
|
9467
|
+
cmd: "bash",
|
|
9468
|
+
args: ["-c", script],
|
|
9469
|
+
cwd: SANDBOX_WORKSPACE_ROOT,
|
|
9470
|
+
...controller ? { signal: controller.signal } : {}
|
|
9471
|
+
});
|
|
9472
|
+
return await readCommandOutput(commandResult2);
|
|
9473
|
+
} catch (error) {
|
|
9474
|
+
if (timedOut) {
|
|
9475
|
+
return {
|
|
9476
|
+
stdout: "",
|
|
9477
|
+
stderr: `Command timed out after ${input.timeoutMs}ms`,
|
|
9478
|
+
exitCode: 124,
|
|
9479
|
+
stdoutTruncated: false,
|
|
9480
|
+
stderrTruncated: false,
|
|
9481
|
+
timedOut: true
|
|
9482
|
+
};
|
|
9483
|
+
}
|
|
9484
|
+
throw error;
|
|
9485
|
+
} finally {
|
|
9486
|
+
if (timeoutId) {
|
|
9487
|
+
clearTimeout(timeoutId);
|
|
9488
|
+
}
|
|
9489
|
+
}
|
|
8671
9490
|
}
|
|
8672
9491
|
);
|
|
8673
9492
|
},
|
|
@@ -8678,7 +9497,8 @@ function createSandboxSessionManager(options) {
|
|
|
8678
9497
|
writeFile: async (input) => await executeWriteFile(input, {
|
|
8679
9498
|
toolCallId: "sandbox-write-file",
|
|
8680
9499
|
messages: []
|
|
8681
|
-
})
|
|
9500
|
+
}),
|
|
9501
|
+
fs: sandboxInstance.fs
|
|
8682
9502
|
};
|
|
8683
9503
|
};
|
|
8684
9504
|
const ensureReadySandbox = async () => {
|
|
@@ -8734,7 +9554,15 @@ function createSandboxSessionManager(options) {
|
|
|
8734
9554
|
}
|
|
8735
9555
|
|
|
8736
9556
|
// src/chat/sandbox/sandbox.ts
|
|
8737
|
-
var SANDBOX_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
9557
|
+
var SANDBOX_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
9558
|
+
"bash",
|
|
9559
|
+
"readFile",
|
|
9560
|
+
"editFile",
|
|
9561
|
+
"grep",
|
|
9562
|
+
"findFiles",
|
|
9563
|
+
"listDir",
|
|
9564
|
+
"writeFile"
|
|
9565
|
+
]);
|
|
8738
9566
|
function parseHeaderTransforms(raw) {
|
|
8739
9567
|
if (!Array.isArray(raw)) {
|
|
8740
9568
|
return void 0;
|
|
@@ -8798,6 +9626,7 @@ function createSandboxExecutor(options) {
|
|
|
8798
9626
|
const executeBashTool = async (rawInput, command) => {
|
|
8799
9627
|
const headerTransforms = parseHeaderTransforms(rawInput.headerTransforms);
|
|
8800
9628
|
const env = parseEnv(rawInput.env);
|
|
9629
|
+
const timeoutMs = positiveInteger(rawInput.timeoutMs);
|
|
8801
9630
|
logSandboxBootRequest("tool.bash", {
|
|
8802
9631
|
"app.sandbox.command_length": command.length
|
|
8803
9632
|
});
|
|
@@ -8813,7 +9642,8 @@ function createSandboxExecutor(options) {
|
|
|
8813
9642
|
const response = await executeBash({
|
|
8814
9643
|
command,
|
|
8815
9644
|
...headerTransforms ? { headerTransforms } : {},
|
|
8816
|
-
...env ? { env } : {}
|
|
9645
|
+
...env ? { env } : {},
|
|
9646
|
+
...timeoutMs ? { timeoutMs } : {}
|
|
8817
9647
|
});
|
|
8818
9648
|
setSpanAttributes({
|
|
8819
9649
|
"process.exit.code": response.exitCode,
|
|
@@ -8845,7 +9675,7 @@ function createSandboxExecutor(options) {
|
|
|
8845
9675
|
cwd: SANDBOX_WORKSPACE_ROOT,
|
|
8846
9676
|
exit_code: result.exitCode,
|
|
8847
9677
|
signal: null,
|
|
8848
|
-
timed_out:
|
|
9678
|
+
timed_out: Boolean(result.timedOut),
|
|
8849
9679
|
stdout: result.stdout,
|
|
8850
9680
|
stderr: result.stderr,
|
|
8851
9681
|
stdout_truncated: result.stdoutTruncated,
|
|
@@ -8858,6 +9688,8 @@ function createSandboxExecutor(options) {
|
|
|
8858
9688
|
if (!filePath) {
|
|
8859
9689
|
throw new Error("path is required");
|
|
8860
9690
|
}
|
|
9691
|
+
const offset = positiveInteger(rawInput.offset);
|
|
9692
|
+
const limit = positiveInteger(rawInput.limit);
|
|
8861
9693
|
if (!sessionManager.getSandboxId()) {
|
|
8862
9694
|
const hostPath = resolveHostSkillPath(availableSkills, filePath) ?? resolveHostDataPath(referenceFiles, filePath);
|
|
8863
9695
|
if (hostPath) {
|
|
@@ -8871,11 +9703,12 @@ function createSandboxExecutor(options) {
|
|
|
8871
9703
|
});
|
|
8872
9704
|
setSpanStatus("ok");
|
|
8873
9705
|
return {
|
|
8874
|
-
result: {
|
|
9706
|
+
result: sliceFileContent({
|
|
8875
9707
|
content,
|
|
8876
9708
|
path: filePath,
|
|
8877
|
-
|
|
8878
|
-
|
|
9709
|
+
offset,
|
|
9710
|
+
limit
|
|
9711
|
+
})
|
|
8879
9712
|
};
|
|
8880
9713
|
} catch (error) {
|
|
8881
9714
|
if (!isHostFileMissingError(error)) {
|
|
@@ -8903,9 +9736,12 @@ function createSandboxExecutor(options) {
|
|
|
8903
9736
|
});
|
|
8904
9737
|
setSpanStatus("ok");
|
|
8905
9738
|
return {
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
9739
|
+
...sliceFileContent({
|
|
9740
|
+
content,
|
|
9741
|
+
path: filePath,
|
|
9742
|
+
offset,
|
|
9743
|
+
limit
|
|
9744
|
+
})
|
|
8909
9745
|
};
|
|
8910
9746
|
}
|
|
8911
9747
|
);
|
|
@@ -8945,6 +9781,116 @@ function createSandboxExecutor(options) {
|
|
|
8945
9781
|
}
|
|
8946
9782
|
};
|
|
8947
9783
|
};
|
|
9784
|
+
const executeEditFileTool = async (rawInput) => {
|
|
9785
|
+
const filePath = String(rawInput.path ?? "").trim();
|
|
9786
|
+
if (!filePath) {
|
|
9787
|
+
throw new Error("path is required");
|
|
9788
|
+
}
|
|
9789
|
+
if (!Array.isArray(rawInput.edits)) {
|
|
9790
|
+
throw new Error("edits is required");
|
|
9791
|
+
}
|
|
9792
|
+
logSandboxBootRequest("tool.editFile", {
|
|
9793
|
+
"file.path": filePath
|
|
9794
|
+
});
|
|
9795
|
+
const executors = await sessionManager.ensureToolExecutors();
|
|
9796
|
+
const result = await withSandboxSpan(
|
|
9797
|
+
"sandbox.editFile",
|
|
9798
|
+
"sandbox.fs.edit",
|
|
9799
|
+
{
|
|
9800
|
+
"app.sandbox.path.length": filePath.length,
|
|
9801
|
+
"app.sandbox.edit.count": rawInput.edits.length
|
|
9802
|
+
},
|
|
9803
|
+
async () => {
|
|
9804
|
+
const response = await editFile({
|
|
9805
|
+
fs: executors.fs,
|
|
9806
|
+
path: filePath,
|
|
9807
|
+
edits: rawInput.edits
|
|
9808
|
+
});
|
|
9809
|
+
setSpanStatus("ok");
|
|
9810
|
+
return response;
|
|
9811
|
+
}
|
|
9812
|
+
);
|
|
9813
|
+
return { result };
|
|
9814
|
+
};
|
|
9815
|
+
const executeGrepTool = async (rawInput) => {
|
|
9816
|
+
const pattern = String(rawInput.pattern ?? "");
|
|
9817
|
+
if (!pattern) {
|
|
9818
|
+
throw new Error("pattern is required");
|
|
9819
|
+
}
|
|
9820
|
+
logSandboxBootRequest("tool.grep");
|
|
9821
|
+
const contextLines = positiveInteger(rawInput.context);
|
|
9822
|
+
const limit = positiveInteger(rawInput.limit);
|
|
9823
|
+
const executors = await sessionManager.ensureToolExecutors();
|
|
9824
|
+
const result = await withSandboxSpan(
|
|
9825
|
+
"sandbox.grep",
|
|
9826
|
+
"sandbox.fs.search",
|
|
9827
|
+
{
|
|
9828
|
+
"app.sandbox.pattern.length": pattern.length
|
|
9829
|
+
},
|
|
9830
|
+
async () => {
|
|
9831
|
+
const response = await grepFiles({
|
|
9832
|
+
fs: executors.fs,
|
|
9833
|
+
pattern,
|
|
9834
|
+
...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
|
|
9835
|
+
...typeof rawInput.glob === "string" ? { glob: rawInput.glob } : {},
|
|
9836
|
+
...typeof rawInput.ignoreCase === "boolean" ? { ignoreCase: rawInput.ignoreCase } : {},
|
|
9837
|
+
...typeof rawInput.literal === "boolean" ? { literal: rawInput.literal } : {},
|
|
9838
|
+
...contextLines ? { context: contextLines } : {},
|
|
9839
|
+
...limit ? { limit } : {}
|
|
9840
|
+
});
|
|
9841
|
+
setSpanStatus("ok");
|
|
9842
|
+
return response;
|
|
9843
|
+
}
|
|
9844
|
+
);
|
|
9845
|
+
return { result };
|
|
9846
|
+
};
|
|
9847
|
+
const executeFindFilesTool = async (rawInput) => {
|
|
9848
|
+
const pattern = String(rawInput.pattern ?? "");
|
|
9849
|
+
if (!pattern) {
|
|
9850
|
+
throw new Error("pattern is required");
|
|
9851
|
+
}
|
|
9852
|
+
logSandboxBootRequest("tool.findFiles");
|
|
9853
|
+
const limit = positiveInteger(rawInput.limit);
|
|
9854
|
+
const executors = await sessionManager.ensureToolExecutors();
|
|
9855
|
+
const result = await withSandboxSpan(
|
|
9856
|
+
"sandbox.findFiles",
|
|
9857
|
+
"sandbox.fs.find",
|
|
9858
|
+
{
|
|
9859
|
+
"app.sandbox.pattern.length": pattern.length
|
|
9860
|
+
},
|
|
9861
|
+
async () => {
|
|
9862
|
+
const response = await findFiles({
|
|
9863
|
+
fs: executors.fs,
|
|
9864
|
+
pattern,
|
|
9865
|
+
...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
|
|
9866
|
+
...limit ? { limit } : {}
|
|
9867
|
+
});
|
|
9868
|
+
setSpanStatus("ok");
|
|
9869
|
+
return response;
|
|
9870
|
+
}
|
|
9871
|
+
);
|
|
9872
|
+
return { result };
|
|
9873
|
+
};
|
|
9874
|
+
const executeListDirTool = async (rawInput) => {
|
|
9875
|
+
logSandboxBootRequest("tool.listDir");
|
|
9876
|
+
const limit = positiveInteger(rawInput.limit);
|
|
9877
|
+
const executors = await sessionManager.ensureToolExecutors();
|
|
9878
|
+
const result = await withSandboxSpan(
|
|
9879
|
+
"sandbox.listDir",
|
|
9880
|
+
"sandbox.fs.list",
|
|
9881
|
+
{},
|
|
9882
|
+
async () => {
|
|
9883
|
+
const response = await listDir({
|
|
9884
|
+
fs: executors.fs,
|
|
9885
|
+
...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
|
|
9886
|
+
...limit ? { limit } : {}
|
|
9887
|
+
});
|
|
9888
|
+
setSpanStatus("ok");
|
|
9889
|
+
return response;
|
|
9890
|
+
}
|
|
9891
|
+
);
|
|
9892
|
+
return { result };
|
|
9893
|
+
};
|
|
8948
9894
|
const execute = async (params) => {
|
|
8949
9895
|
const rawInput = params.input ?? {};
|
|
8950
9896
|
const bashCommand = params.toolName === "bash" ? String(rawInput.command ?? "").trim() : void 0;
|
|
@@ -8963,6 +9909,18 @@ function createSandboxExecutor(options) {
|
|
|
8963
9909
|
if (params.toolName === "readFile") {
|
|
8964
9910
|
return await executeReadFileTool(rawInput);
|
|
8965
9911
|
}
|
|
9912
|
+
if (params.toolName === "editFile") {
|
|
9913
|
+
return await executeEditFileTool(rawInput);
|
|
9914
|
+
}
|
|
9915
|
+
if (params.toolName === "grep") {
|
|
9916
|
+
return await executeGrepTool(rawInput);
|
|
9917
|
+
}
|
|
9918
|
+
if (params.toolName === "findFiles") {
|
|
9919
|
+
return await executeFindFilesTool(rawInput);
|
|
9920
|
+
}
|
|
9921
|
+
if (params.toolName === "listDir") {
|
|
9922
|
+
return await executeListDirTool(rawInput);
|
|
9923
|
+
}
|
|
8966
9924
|
if (params.toolName === "writeFile") {
|
|
8967
9925
|
return await executeWriteFileTool(rawInput);
|
|
8968
9926
|
}
|
|
@@ -9035,11 +9993,49 @@ function buildReportedProgressStatus(input) {
|
|
|
9035
9993
|
|
|
9036
9994
|
// src/chat/tools/execution/build-sandbox-input.ts
|
|
9037
9995
|
function buildSandboxInput(toolName, params) {
|
|
9996
|
+
const optionalNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
9038
9997
|
if (toolName === "bash") {
|
|
9039
|
-
return {
|
|
9998
|
+
return {
|
|
9999
|
+
command: String(params.command ?? ""),
|
|
10000
|
+
...optionalNumber(params.timeoutMs) ? { timeoutMs: optionalNumber(params.timeoutMs) } : {}
|
|
10001
|
+
};
|
|
9040
10002
|
}
|
|
9041
10003
|
if (toolName === "readFile") {
|
|
9042
|
-
return {
|
|
10004
|
+
return {
|
|
10005
|
+
path: String(params.path ?? ""),
|
|
10006
|
+
...optionalNumber(params.offset) ? { offset: optionalNumber(params.offset) } : {},
|
|
10007
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
10008
|
+
};
|
|
10009
|
+
}
|
|
10010
|
+
if (toolName === "editFile") {
|
|
10011
|
+
return {
|
|
10012
|
+
path: String(params.path ?? ""),
|
|
10013
|
+
edits: Array.isArray(params.edits) ? params.edits : []
|
|
10014
|
+
};
|
|
10015
|
+
}
|
|
10016
|
+
if (toolName === "grep") {
|
|
10017
|
+
return {
|
|
10018
|
+
pattern: String(params.pattern ?? ""),
|
|
10019
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
10020
|
+
...typeof params.glob === "string" ? { glob: params.glob } : {},
|
|
10021
|
+
...typeof params.ignoreCase === "boolean" ? { ignoreCase: params.ignoreCase } : {},
|
|
10022
|
+
...typeof params.literal === "boolean" ? { literal: params.literal } : {},
|
|
10023
|
+
...optionalNumber(params.context) ? { context: optionalNumber(params.context) } : {},
|
|
10024
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
10025
|
+
};
|
|
10026
|
+
}
|
|
10027
|
+
if (toolName === "findFiles") {
|
|
10028
|
+
return {
|
|
10029
|
+
pattern: String(params.pattern ?? ""),
|
|
10030
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
10031
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
10032
|
+
};
|
|
10033
|
+
}
|
|
10034
|
+
if (toolName === "listDir") {
|
|
10035
|
+
return {
|
|
10036
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
10037
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
10038
|
+
};
|
|
9043
10039
|
}
|
|
9044
10040
|
if (toolName === "writeFile") {
|
|
9045
10041
|
return {
|
|
@@ -9174,6 +10170,8 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9174
10170
|
label: toolName,
|
|
9175
10171
|
description: toolDef.description,
|
|
9176
10172
|
parameters: toolDef.inputSchema,
|
|
10173
|
+
prepareArguments: toolDef.prepareArguments,
|
|
10174
|
+
executionMode: toolDef.executionMode,
|
|
9177
10175
|
execute: async (toolCallId, params) => {
|
|
9178
10176
|
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
9179
10177
|
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
@@ -9431,18 +10429,17 @@ function buildTurnResult(input) {
|
|
|
9431
10429
|
const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
9432
10430
|
const usedPrimaryText = Boolean(primaryText);
|
|
9433
10431
|
const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : sideEffectOnlySuccess ? "success" : "execution_failure";
|
|
9434
|
-
const fallbackText = buildExecutionFailureMessage(toolErrorCount);
|
|
9435
10432
|
const suppressReactionOnlyText = reactionPerformed && !channelPostPerformed && replyFiles.length === 0 && Boolean(primaryText) && isReactionOnlyIntent(userInput);
|
|
9436
|
-
const rawResponseText = suppressReactionOnlyText ? "" : primaryText
|
|
10433
|
+
const rawResponseText = suppressReactionOnlyText ? "" : primaryText;
|
|
9437
10434
|
const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
|
|
9438
10435
|
const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
|
|
9439
|
-
const resolvedText = escapedOrRawPayload ?
|
|
9440
|
-
const
|
|
10436
|
+
const resolvedText = escapedOrRawPayload ? "" : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
|
|
10437
|
+
const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
|
|
10438
|
+
const deliveryPlan = resolvedOutcome === "success" && !resolvedText && replyFiles.length === 0 && (reactionPerformed || channelPostPerformed) ? {
|
|
9441
10439
|
...baseDeliveryPlan,
|
|
9442
10440
|
postThreadText: false
|
|
9443
10441
|
} : baseDeliveryPlan;
|
|
9444
10442
|
const deliveryMode = deliveryPlan.mode;
|
|
9445
|
-
const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
|
|
9446
10443
|
if (shouldTrace) {
|
|
9447
10444
|
logInfo(
|
|
9448
10445
|
"agent_message_out",
|
|
@@ -9493,7 +10490,13 @@ var MAX_ROUTER_CONTEXT_CHARS = 8e3;
|
|
|
9493
10490
|
var ROUTER_CONTEXT_HEAD_CHARS = 3e3;
|
|
9494
10491
|
var ROUTER_CONTEXT_TAIL_CHARS = 5e3;
|
|
9495
10492
|
var TRUNCATION_MARKER = "\n\u2026[truncated]\u2026\n";
|
|
9496
|
-
var TURN_THINKING_LEVELS = [
|
|
10493
|
+
var TURN_THINKING_LEVELS = [
|
|
10494
|
+
"none",
|
|
10495
|
+
"low",
|
|
10496
|
+
"medium",
|
|
10497
|
+
"high",
|
|
10498
|
+
"xhigh"
|
|
10499
|
+
];
|
|
9497
10500
|
var turnExecutionProfileSchema = z.object({
|
|
9498
10501
|
thinking_level: z.enum(TURN_THINKING_LEVELS),
|
|
9499
10502
|
confidence: z.number().min(0).max(1),
|
|
@@ -9504,7 +10507,8 @@ var THINKING_LEVEL_RANK = {
|
|
|
9504
10507
|
none: 0,
|
|
9505
10508
|
low: 1,
|
|
9506
10509
|
medium: 2,
|
|
9507
|
-
high: 3
|
|
10510
|
+
high: 3,
|
|
10511
|
+
xhigh: 4
|
|
9508
10512
|
};
|
|
9509
10513
|
function trimContextForRouter(text) {
|
|
9510
10514
|
const trimmed = text?.trim();
|
|
@@ -9529,12 +10533,13 @@ function trimContextForRouter(text) {
|
|
|
9529
10533
|
function buildClassifierSystemPrompt() {
|
|
9530
10534
|
return [
|
|
9531
10535
|
"You route assistant turns to the thinking level most likely to produce a complete, source-grounded answer.",
|
|
9532
|
-
"Choose exactly one bucket: none, low, medium, or
|
|
10536
|
+
"Choose exactly one bucket: none, low, medium, high, or xhigh.",
|
|
9533
10537
|
"",
|
|
9534
10538
|
"Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
|
|
9535
10539
|
"Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no thread-background interpretation, and no source verification.",
|
|
9536
10540
|
"Use medium for normal assistant work: explanations, source-backed checks, thread follow-ups, tool choice, likely tool use, ambiguous asks, multi-step analysis, or anything where a confident but shallow answer would be risky.",
|
|
9537
|
-
"Use high for
|
|
10541
|
+
"Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
|
|
10542
|
+
"Use xhigh for the most complex tasks: code changes, debugging/root-cause analysis, broad refactors, architecture decisions, multi-file implementation, or any task where deep reasoning across multiple systems or files is required.",
|
|
9538
10543
|
"When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
|
|
9539
10544
|
"",
|
|
9540
10545
|
"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.",
|
|
@@ -9676,6 +10681,8 @@ function toAgentThinkingLevel(level) {
|
|
|
9676
10681
|
return "medium";
|
|
9677
10682
|
case "high":
|
|
9678
10683
|
return "high";
|
|
10684
|
+
case "xhigh":
|
|
10685
|
+
return "xhigh";
|
|
9679
10686
|
}
|
|
9680
10687
|
}
|
|
9681
10688
|
|
|
@@ -10724,6 +11731,13 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10724
11731
|
}
|
|
10725
11732
|
}
|
|
10726
11733
|
);
|
|
11734
|
+
const toolGuidance = Object.entries(
|
|
11735
|
+
tools
|
|
11736
|
+
).map(([name, definition]) => ({
|
|
11737
|
+
name,
|
|
11738
|
+
promptGuidelines: definition.promptGuidelines,
|
|
11739
|
+
promptSnippet: definition.promptSnippet
|
|
11740
|
+
}));
|
|
10727
11741
|
syncResumeState();
|
|
10728
11742
|
for (const skill of activeSkills) {
|
|
10729
11743
|
await turnMcpToolManager.activateForSkill(skill);
|
|
@@ -10743,6 +11757,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10743
11757
|
availableSkills,
|
|
10744
11758
|
activeSkills,
|
|
10745
11759
|
activeMcpCatalogs,
|
|
11760
|
+
toolGuidance,
|
|
10746
11761
|
runtime: {
|
|
10747
11762
|
channelId: toolChannelId,
|
|
10748
11763
|
fastModelId: botConfig.fastModelId,
|
|
@@ -10796,7 +11811,16 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10796
11811
|
pluginAuth,
|
|
10797
11812
|
onToolCall
|
|
10798
11813
|
);
|
|
10799
|
-
advisorTools =
|
|
11814
|
+
advisorTools = createAgentTools(
|
|
11815
|
+
createAdvisorToolDefinitions(tools),
|
|
11816
|
+
skillSandbox,
|
|
11817
|
+
spanContext,
|
|
11818
|
+
context.onStatus,
|
|
11819
|
+
sandboxExecutor,
|
|
11820
|
+
capabilityRuntime,
|
|
11821
|
+
pluginAuth,
|
|
11822
|
+
onToolCall
|
|
11823
|
+
);
|
|
10800
11824
|
agent = new Agent2({
|
|
10801
11825
|
getApiKey: () => getPiGatewayApiKeyOverride(),
|
|
10802
11826
|
initialState: {
|
|
@@ -11097,6 +12121,97 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11097
12121
|
}
|
|
11098
12122
|
}
|
|
11099
12123
|
|
|
12124
|
+
// src/chat/services/turn-failure-response.ts
|
|
12125
|
+
function requireTurnFailureEventId(eventId, eventName) {
|
|
12126
|
+
if (!eventId) {
|
|
12127
|
+
throw new Error(`Sentry did not return an event ID for ${eventName}`);
|
|
12128
|
+
}
|
|
12129
|
+
return eventId;
|
|
12130
|
+
}
|
|
12131
|
+
function getExecutionFailureReason(reply) {
|
|
12132
|
+
const errorMessage = reply.diagnostics.errorMessage?.trim();
|
|
12133
|
+
if (errorMessage) {
|
|
12134
|
+
return errorMessage;
|
|
12135
|
+
}
|
|
12136
|
+
if (reply.diagnostics.toolErrorCount > 0) {
|
|
12137
|
+
return `${reply.diagnostics.toolErrorCount} tool result error(s)`;
|
|
12138
|
+
}
|
|
12139
|
+
if (reply.diagnostics.assistantMessageCount > 0) {
|
|
12140
|
+
return "assistant returned no text";
|
|
12141
|
+
}
|
|
12142
|
+
return "empty assistant turn";
|
|
12143
|
+
}
|
|
12144
|
+
function getFailureCapture(reply) {
|
|
12145
|
+
if (reply.diagnostics.outcome === "provider_error") {
|
|
12146
|
+
return {
|
|
12147
|
+
eventName: "agent_turn_provider_error",
|
|
12148
|
+
error: reply.diagnostics.providerError ?? new Error(
|
|
12149
|
+
reply.diagnostics.errorMessage ?? "Provider error without explicit message"
|
|
12150
|
+
),
|
|
12151
|
+
attributes: {},
|
|
12152
|
+
body: "Agent turn failed with provider error"
|
|
12153
|
+
};
|
|
12154
|
+
}
|
|
12155
|
+
const failureReason = getExecutionFailureReason(reply);
|
|
12156
|
+
return {
|
|
12157
|
+
eventName: "agent_turn_execution_failure",
|
|
12158
|
+
error: new Error(`Agent turn execution failure: ${failureReason}`),
|
|
12159
|
+
attributes: {
|
|
12160
|
+
"app.ai.execution_failure_reason": failureReason
|
|
12161
|
+
},
|
|
12162
|
+
body: "Agent turn completed with execution failure"
|
|
12163
|
+
};
|
|
12164
|
+
}
|
|
12165
|
+
function getAgentTurnDiagnosticsAttributes(reply) {
|
|
12166
|
+
return {
|
|
12167
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
12168
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
12169
|
+
"app.ai.outcome": reply.diagnostics.outcome,
|
|
12170
|
+
"app.ai.assistant_messages": reply.diagnostics.assistantMessageCount,
|
|
12171
|
+
"app.ai.tool_results": reply.diagnostics.toolResultCount,
|
|
12172
|
+
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
|
|
12173
|
+
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
12174
|
+
"app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
|
|
12175
|
+
...reply.diagnostics.thinkingLevel ? {
|
|
12176
|
+
"app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
|
|
12177
|
+
} : {},
|
|
12178
|
+
...reply.diagnostics.stopReason ? {
|
|
12179
|
+
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
12180
|
+
} : {},
|
|
12181
|
+
...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
|
|
12182
|
+
};
|
|
12183
|
+
}
|
|
12184
|
+
function finalizeFailedTurnReply(args) {
|
|
12185
|
+
if (args.reply.diagnostics.outcome === "success") {
|
|
12186
|
+
return args.reply;
|
|
12187
|
+
}
|
|
12188
|
+
const capture = getFailureCapture(args.reply);
|
|
12189
|
+
const eventId = requireTurnFailureEventId(
|
|
12190
|
+
args.logException(
|
|
12191
|
+
capture.error,
|
|
12192
|
+
capture.eventName,
|
|
12193
|
+
args.context,
|
|
12194
|
+
{
|
|
12195
|
+
...getAgentTurnDiagnosticsAttributes(args.reply),
|
|
12196
|
+
...args.attributes,
|
|
12197
|
+
...capture.attributes
|
|
12198
|
+
},
|
|
12199
|
+
capture.body
|
|
12200
|
+
),
|
|
12201
|
+
capture.eventName
|
|
12202
|
+
);
|
|
12203
|
+
return {
|
|
12204
|
+
...args.reply,
|
|
12205
|
+
text: buildTurnFailureResponse(eventId),
|
|
12206
|
+
deliveryMode: "thread",
|
|
12207
|
+
deliveryPlan: {
|
|
12208
|
+
mode: "thread",
|
|
12209
|
+
postThreadText: true,
|
|
12210
|
+
attachFiles: args.reply.files && args.reply.files.length > 0 ? "inline" : "none"
|
|
12211
|
+
}
|
|
12212
|
+
};
|
|
12213
|
+
}
|
|
12214
|
+
|
|
11100
12215
|
// src/chat/slack/assistant-thread/status-render.ts
|
|
11101
12216
|
var DEFAULT_STATUS_CONTEXTS = {
|
|
11102
12217
|
thinking: "\u2026",
|
|
@@ -11366,7 +12481,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
11366
12481
|
};
|
|
11367
12482
|
}
|
|
11368
12483
|
function createSlackWebApiStatusSender(args) {
|
|
11369
|
-
const
|
|
12484
|
+
const getClient3 = args.getSlackClient ?? getSlackClient;
|
|
11370
12485
|
return async (text, loadingMessages) => {
|
|
11371
12486
|
const channelId = args.channelId;
|
|
11372
12487
|
const threadTs = args.threadTs;
|
|
@@ -11379,7 +12494,7 @@ function createSlackWebApiStatusSender(args) {
|
|
|
11379
12494
|
}
|
|
11380
12495
|
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
11381
12496
|
try {
|
|
11382
|
-
await
|
|
12497
|
+
await getClient3().assistant.threads.setStatus({
|
|
11383
12498
|
channel_id: normalizedChannelId,
|
|
11384
12499
|
thread_ts: threadTs,
|
|
11385
12500
|
status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
@@ -11456,21 +12571,85 @@ function createSlackWebApiAssistantStatusSession(args) {
|
|
|
11456
12571
|
}
|
|
11457
12572
|
|
|
11458
12573
|
// src/chat/slack/footer.ts
|
|
12574
|
+
var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
|
|
12575
|
+
var ORG_ID_HOST_RE = /^o(\d+)\./;
|
|
11459
12576
|
function escapeSlackMrkdwn(text) {
|
|
11460
12577
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
11461
12578
|
}
|
|
12579
|
+
function escapeSlackLinkUrl(url) {
|
|
12580
|
+
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
12581
|
+
}
|
|
12582
|
+
function toOptionalString2(value) {
|
|
12583
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12584
|
+
return String(value);
|
|
12585
|
+
}
|
|
12586
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
12587
|
+
}
|
|
12588
|
+
function quoteSentrySearchValue(value) {
|
|
12589
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
12590
|
+
}
|
|
12591
|
+
function getDsnOrgId(host) {
|
|
12592
|
+
return host?.match(ORG_ID_HOST_RE)?.[1];
|
|
12593
|
+
}
|
|
12594
|
+
function isSentrySaasDsnHost(host) {
|
|
12595
|
+
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
12596
|
+
}
|
|
12597
|
+
function buildSentryWebBaseUrl(dsn) {
|
|
12598
|
+
if (isSentrySaasDsnHost(dsn.host)) {
|
|
12599
|
+
return "https://sentry.io";
|
|
12600
|
+
}
|
|
12601
|
+
const port = dsn.port ? `:${dsn.port}` : "";
|
|
12602
|
+
const path11 = dsn.path ? `/${dsn.path}` : "";
|
|
12603
|
+
return `${dsn.protocol}://${dsn.host}${port}${path11}`;
|
|
12604
|
+
}
|
|
12605
|
+
function getSentryConversationSearchUrl(conversationId) {
|
|
12606
|
+
const client2 = sentry_exports.getClient();
|
|
12607
|
+
const dsn = client2?.getDsn();
|
|
12608
|
+
if (!dsn?.host || !dsn.projectId) {
|
|
12609
|
+
return void 0;
|
|
12610
|
+
}
|
|
12611
|
+
const orgId = toOptionalString2(client2?.getOptions().orgId) ?? getDsnOrgId(dsn.host);
|
|
12612
|
+
if (!orgId) {
|
|
12613
|
+
return void 0;
|
|
12614
|
+
}
|
|
12615
|
+
const params = new URLSearchParams();
|
|
12616
|
+
params.set(
|
|
12617
|
+
"query",
|
|
12618
|
+
`gen_ai.conversation.id:${quoteSentrySearchValue(conversationId)}`
|
|
12619
|
+
);
|
|
12620
|
+
params.set("project", dsn.projectId);
|
|
12621
|
+
params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
|
|
12622
|
+
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgId}/explore/traces/?${params.toString()}`;
|
|
12623
|
+
}
|
|
11462
12624
|
function formatSlackTokenCount(value) {
|
|
11463
|
-
|
|
12625
|
+
if (value >= 1e6) {
|
|
12626
|
+
const millions = value / 1e6;
|
|
12627
|
+
return `${parseFloat(millions.toFixed(2))}m`;
|
|
12628
|
+
}
|
|
12629
|
+
if (value >= 1e3) {
|
|
12630
|
+
const thousands = value / 1e3;
|
|
12631
|
+
return `${parseFloat(thousands.toFixed(1))}k`;
|
|
12632
|
+
}
|
|
12633
|
+
return `${value}`;
|
|
11464
12634
|
}
|
|
11465
12635
|
function formatSlackDuration(durationMs) {
|
|
11466
12636
|
if (durationMs < 1e3) {
|
|
11467
12637
|
return `${durationMs}ms`;
|
|
11468
12638
|
}
|
|
11469
|
-
const
|
|
11470
|
-
if (
|
|
11471
|
-
|
|
12639
|
+
const totalSeconds = Math.round(durationMs / 1e3);
|
|
12640
|
+
if (totalSeconds < 10) {
|
|
12641
|
+
const precise = durationMs / 1e3;
|
|
12642
|
+
return `${precise.toFixed(1).replace(/\.0$/, "")}s`;
|
|
12643
|
+
}
|
|
12644
|
+
if (totalSeconds < 60) {
|
|
12645
|
+
return `${totalSeconds}s`;
|
|
11472
12646
|
}
|
|
11473
|
-
|
|
12647
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
12648
|
+
const seconds = totalSeconds % 60;
|
|
12649
|
+
if (seconds === 0) {
|
|
12650
|
+
return `${minutes}m`;
|
|
12651
|
+
}
|
|
12652
|
+
return `${minutes}m${seconds}s`;
|
|
11474
12653
|
}
|
|
11475
12654
|
function resolveTotalTokens(usage) {
|
|
11476
12655
|
if (!usage) {
|
|
@@ -11491,10 +12670,15 @@ function buildSlackReplyFooter(args) {
|
|
|
11491
12670
|
const items = [];
|
|
11492
12671
|
const conversationId = args.conversationId?.trim();
|
|
11493
12672
|
if (conversationId) {
|
|
11494
|
-
|
|
12673
|
+
const idItem = {
|
|
11495
12674
|
label: "ID",
|
|
11496
12675
|
value: conversationId
|
|
11497
|
-
}
|
|
12676
|
+
};
|
|
12677
|
+
const conversationUrl = getSentryConversationSearchUrl(conversationId);
|
|
12678
|
+
if (conversationUrl) {
|
|
12679
|
+
idItem.url = conversationUrl;
|
|
12680
|
+
}
|
|
12681
|
+
items.push(idItem);
|
|
11498
12682
|
}
|
|
11499
12683
|
const totalTokens = resolveTotalTokens(args.usage);
|
|
11500
12684
|
if (totalTokens !== void 0) {
|
|
@@ -11533,7 +12717,7 @@ function buildSlackReplyBlocks(text, footer) {
|
|
|
11533
12717
|
type: "context",
|
|
11534
12718
|
elements: footer.items.map((item) => ({
|
|
11535
12719
|
type: "mrkdwn",
|
|
11536
|
-
text: `*${escapeSlackMrkdwn(item.label)}:* ${escapeSlackMrkdwn(item.value)}`
|
|
12720
|
+
text: item.url ? `*${escapeSlackMrkdwn(item.label)}:* <${escapeSlackLinkUrl(item.url)}|${escapeSlackMrkdwn(item.value)}>` : `*${escapeSlackMrkdwn(item.label)}:* ${escapeSlackMrkdwn(item.value)}`
|
|
11537
12721
|
}))
|
|
11538
12722
|
});
|
|
11539
12723
|
}
|
|
@@ -11542,9 +12726,6 @@ function buildSlackReplyBlocks(text, footer) {
|
|
|
11542
12726
|
|
|
11543
12727
|
// src/chat/slack/reply.ts
|
|
11544
12728
|
import { Buffer as Buffer2 } from "buffer";
|
|
11545
|
-
function isInterruptedVisibleReply(reply) {
|
|
11546
|
-
return reply.diagnostics.outcome === "provider_error";
|
|
11547
|
-
}
|
|
11548
12729
|
function resolveReplyDelivery(reply) {
|
|
11549
12730
|
const replyHasFiles = Boolean(reply.files && reply.files.length > 0);
|
|
11550
12731
|
const deliveryPlan = reply.deliveryPlan ?? {
|
|
@@ -11568,9 +12749,7 @@ function buildReplyText(text) {
|
|
|
11568
12749
|
return "";
|
|
11569
12750
|
}
|
|
11570
12751
|
function buildTextPosts(args) {
|
|
11571
|
-
const chunks = splitSlackReplyText(args.text
|
|
11572
|
-
interrupted: args.interrupted
|
|
11573
|
-
});
|
|
12752
|
+
const chunks = splitSlackReplyText(args.text);
|
|
11574
12753
|
return chunks.map((chunk, index) => ({
|
|
11575
12754
|
text: chunk,
|
|
11576
12755
|
...index === 0 && args.firstFiles ? { files: args.firstFiles } : {},
|
|
@@ -11621,11 +12800,9 @@ function planSlackReplyPosts(args) {
|
|
|
11621
12800
|
const { shouldPostThreadReply, attachFiles } = resolveReplyDelivery(
|
|
11622
12801
|
args.reply
|
|
11623
12802
|
);
|
|
11624
|
-
const interrupted = isInterruptedVisibleReply(args.reply);
|
|
11625
12803
|
const posts = [];
|
|
11626
12804
|
const textPosts = shouldPostThreadReply ? buildTextPosts({
|
|
11627
12805
|
text: args.reply.text,
|
|
11628
|
-
interrupted,
|
|
11629
12806
|
firstFiles: attachFiles === "inline" ? replyFiles : void 0
|
|
11630
12807
|
}) : [];
|
|
11631
12808
|
posts.push(...textPosts);
|
|
@@ -11750,6 +12927,64 @@ var ResumeTurnBusyError = class extends Error {
|
|
|
11750
12927
|
function getDefaultLockKey(channelId, threadTs) {
|
|
11751
12928
|
return `slack:${channelId}:${threadTs}`;
|
|
11752
12929
|
}
|
|
12930
|
+
function getResumeLogContext(args, lockKey) {
|
|
12931
|
+
return {
|
|
12932
|
+
conversationId: args.replyContext?.correlation?.conversationId ?? lockKey,
|
|
12933
|
+
slackThreadId: args.replyContext?.correlation?.threadId ?? lockKey,
|
|
12934
|
+
slackUserId: args.replyContext?.requester?.userId ?? args.replyContext?.correlation?.requesterId,
|
|
12935
|
+
slackUserName: args.replyContext?.requester?.userName,
|
|
12936
|
+
slackChannelId: args.channelId,
|
|
12937
|
+
runId: args.replyContext?.correlation?.runId,
|
|
12938
|
+
assistantUserName: botConfig.userName,
|
|
12939
|
+
modelId: botConfig.modelId
|
|
12940
|
+
};
|
|
12941
|
+
}
|
|
12942
|
+
async function postResumeFailureReply(args) {
|
|
12943
|
+
try {
|
|
12944
|
+
await postSlackMessage({
|
|
12945
|
+
channelId: args.channelId,
|
|
12946
|
+
threadTs: args.threadTs,
|
|
12947
|
+
text: buildTurnFailureResponse(args.eventId)
|
|
12948
|
+
});
|
|
12949
|
+
} catch (error) {
|
|
12950
|
+
logException(
|
|
12951
|
+
error,
|
|
12952
|
+
"slack_resume_failure_reply_post_failed",
|
|
12953
|
+
args.logContext,
|
|
12954
|
+
{
|
|
12955
|
+
"app.error.original_event_id": args.eventId
|
|
12956
|
+
},
|
|
12957
|
+
"Failed to post resumed turn failure reply"
|
|
12958
|
+
);
|
|
12959
|
+
throw error;
|
|
12960
|
+
}
|
|
12961
|
+
}
|
|
12962
|
+
async function handleResumeFailure(args) {
|
|
12963
|
+
const logContext = getResumeLogContext(args.resumeArgs, args.lockKey);
|
|
12964
|
+
const capturedEventId = logException(
|
|
12965
|
+
args.error,
|
|
12966
|
+
args.eventName,
|
|
12967
|
+
logContext,
|
|
12968
|
+
{},
|
|
12969
|
+
args.body
|
|
12970
|
+
);
|
|
12971
|
+
await args.resumeArgs.onFailure?.(args.error);
|
|
12972
|
+
const eventId = requireTurnFailureEventId(capturedEventId, args.eventName);
|
|
12973
|
+
let postError;
|
|
12974
|
+
try {
|
|
12975
|
+
await postResumeFailureReply({
|
|
12976
|
+
channelId: args.resumeArgs.channelId,
|
|
12977
|
+
threadTs: args.resumeArgs.threadTs,
|
|
12978
|
+
eventId,
|
|
12979
|
+
logContext
|
|
12980
|
+
});
|
|
12981
|
+
} catch (error) {
|
|
12982
|
+
postError = error;
|
|
12983
|
+
}
|
|
12984
|
+
if (postError) {
|
|
12985
|
+
throw postError;
|
|
12986
|
+
}
|
|
12987
|
+
}
|
|
11753
12988
|
function createResumeReplyContext(args, statusSession) {
|
|
11754
12989
|
const replyContext = args.replyContext ?? {};
|
|
11755
12990
|
const threadId = args.lockKey ?? getDefaultLockKey(args.channelId, args.threadTs);
|
|
@@ -11817,7 +13052,7 @@ async function resumeSlackTurn(args) {
|
|
|
11817
13052
|
...replyContext
|
|
11818
13053
|
});
|
|
11819
13054
|
const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
|
|
11820
|
-
|
|
13055
|
+
let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
|
|
11821
13056
|
replyPromise,
|
|
11822
13057
|
new Promise(
|
|
11823
13058
|
(_, reject) => setTimeout(
|
|
@@ -11830,6 +13065,11 @@ async function resumeSlackTurn(args) {
|
|
|
11830
13065
|
)
|
|
11831
13066
|
)
|
|
11832
13067
|
]) : await replyPromise;
|
|
13068
|
+
reply = finalizeFailedTurnReply({
|
|
13069
|
+
reply,
|
|
13070
|
+
logException,
|
|
13071
|
+
context: getResumeLogContext(args, lockKey)
|
|
13072
|
+
});
|
|
11833
13073
|
await status.stop();
|
|
11834
13074
|
const footer = buildSlackReplyFooter({
|
|
11835
13075
|
conversationId: args.replyContext?.correlation?.conversationId ?? lockKey,
|
|
@@ -11857,14 +13097,13 @@ async function resumeSlackTurn(args) {
|
|
|
11857
13097
|
};
|
|
11858
13098
|
} else {
|
|
11859
13099
|
deferredFailureHandler = async () => {
|
|
11860
|
-
await
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
}
|
|
13100
|
+
await handleResumeFailure({
|
|
13101
|
+
body: "Failed to resume Slack turn",
|
|
13102
|
+
error,
|
|
13103
|
+
eventName: "slack_resume_turn_failed",
|
|
13104
|
+
lockKey,
|
|
13105
|
+
resumeArgs: args
|
|
13106
|
+
});
|
|
11868
13107
|
};
|
|
11869
13108
|
}
|
|
11870
13109
|
} finally {
|
|
@@ -11875,14 +13114,13 @@ async function resumeSlackTurn(args) {
|
|
|
11875
13114
|
await deferredPauseHandler();
|
|
11876
13115
|
return;
|
|
11877
13116
|
} catch (pauseError) {
|
|
11878
|
-
await
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
}
|
|
13117
|
+
await handleResumeFailure({
|
|
13118
|
+
body: "Failed to handle resumed turn pause",
|
|
13119
|
+
error: pauseError,
|
|
13120
|
+
eventName: "slack_resume_pause_handler_failed",
|
|
13121
|
+
lockKey,
|
|
13122
|
+
resumeArgs: args
|
|
13123
|
+
});
|
|
11886
13124
|
return;
|
|
11887
13125
|
}
|
|
11888
13126
|
}
|
|
@@ -11898,7 +13136,6 @@ async function resumeAuthorizedRequest(args) {
|
|
|
11898
13136
|
replyContext: args.replyContext,
|
|
11899
13137
|
lockKey: args.lockKey,
|
|
11900
13138
|
initialText: args.connectedText,
|
|
11901
|
-
failureText: args.failureText,
|
|
11902
13139
|
generateReply: args.generateReply,
|
|
11903
13140
|
onSuccess: args.onSuccess,
|
|
11904
13141
|
onFailure: args.onFailure,
|
|
@@ -12206,7 +13443,6 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
12206
13443
|
threadTs: authSession.threadTs,
|
|
12207
13444
|
lockKey: authSession.conversationId,
|
|
12208
13445
|
connectedText: "",
|
|
12209
|
-
failureText: "MCP authorization completed, but resuming the request failed. Please retry the original command.",
|
|
12210
13446
|
replyContext: {
|
|
12211
13447
|
requester: {
|
|
12212
13448
|
userId: authSession.userId,
|
|
@@ -12256,14 +13492,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
12256
13492
|
);
|
|
12257
13493
|
}
|
|
12258
13494
|
},
|
|
12259
|
-
onFailure: async (
|
|
12260
|
-
logException(
|
|
12261
|
-
error,
|
|
12262
|
-
"mcp_oauth_callback_resume_failed",
|
|
12263
|
-
{},
|
|
12264
|
-
{ "app.credential.provider": provider },
|
|
12265
|
-
"Failed to resume MCP-authorized turn"
|
|
12266
|
-
);
|
|
13495
|
+
onFailure: async () => {
|
|
12267
13496
|
try {
|
|
12268
13497
|
await persistFailedReplyState(
|
|
12269
13498
|
authSession.channelId,
|
|
@@ -12376,7 +13605,7 @@ async function GET4(request, provider, waitUntil) {
|
|
|
12376
13605
|
|
|
12377
13606
|
// src/chat/slack/app-home.ts
|
|
12378
13607
|
import fs5 from "fs";
|
|
12379
|
-
import
|
|
13608
|
+
import path10 from "path";
|
|
12380
13609
|
var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
|
|
12381
13610
|
var MAX_HOME_SKILLS = 6;
|
|
12382
13611
|
var MAX_SECTION_TEXT_CHARS = 3e3;
|
|
@@ -12388,7 +13617,7 @@ function clampSectionText(text) {
|
|
|
12388
13617
|
return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
|
|
12389
13618
|
}
|
|
12390
13619
|
function loadDescriptionText() {
|
|
12391
|
-
const descriptionPath =
|
|
13620
|
+
const descriptionPath = path10.join(homeDir(), "DESCRIPTION.md");
|
|
12392
13621
|
try {
|
|
12393
13622
|
const raw = fs5.readFileSync(descriptionPath, "utf8").trim();
|
|
12394
13623
|
if (raw.length > 0) {
|
|
@@ -12655,7 +13884,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
12655
13884
|
threadTs: stored.threadTs,
|
|
12656
13885
|
lockKey: stored.resumeConversationId,
|
|
12657
13886
|
initialText: "",
|
|
12658
|
-
failureText: "I connected your account but hit an error processing your request. Please try the command again.",
|
|
12659
13887
|
replyContext: {
|
|
12660
13888
|
requester: {
|
|
12661
13889
|
userId: userMessage.author.userId,
|
|
@@ -12703,14 +13931,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
12703
13931
|
reply
|
|
12704
13932
|
});
|
|
12705
13933
|
},
|
|
12706
|
-
onFailure: async (
|
|
12707
|
-
logException(
|
|
12708
|
-
error,
|
|
12709
|
-
"oauth_callback_resume_failed",
|
|
12710
|
-
{},
|
|
12711
|
-
{ "app.credential.provider": stored.provider },
|
|
12712
|
-
"Failed to auto-resume checkpointed turn after OAuth callback"
|
|
12713
|
-
);
|
|
13934
|
+
onFailure: async () => {
|
|
12714
13935
|
await persistFailedOAuthReplyState({
|
|
12715
13936
|
conversationId: stored.resumeConversationId,
|
|
12716
13937
|
sessionId: resolvedSessionId
|
|
@@ -12762,7 +13983,6 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
12762
13983
|
channelId: stored.channelId,
|
|
12763
13984
|
threadTs: stored.threadTs,
|
|
12764
13985
|
connectedText: "",
|
|
12765
|
-
failureText: `I connected your account but hit an error processing your request. Please try \`${stored.pendingMessage}\` again.`,
|
|
12766
13986
|
replyContext: {
|
|
12767
13987
|
requester: { userId: stored.userId },
|
|
12768
13988
|
conversationContext,
|
|
@@ -12780,15 +14000,6 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
12780
14000
|
},
|
|
12781
14001
|
"OAuth callback auto-resumed pending message finished replying"
|
|
12782
14002
|
);
|
|
12783
|
-
},
|
|
12784
|
-
onFailure: async (error) => {
|
|
12785
|
-
logException(
|
|
12786
|
-
error,
|
|
12787
|
-
"oauth_callback_resume_failed",
|
|
12788
|
-
{},
|
|
12789
|
-
{ "app.credential.provider": stored.provider },
|
|
12790
|
-
"Failed to auto-resume pending message after OAuth callback"
|
|
12791
|
-
);
|
|
12792
14003
|
}
|
|
12793
14004
|
});
|
|
12794
14005
|
}
|
|
@@ -13115,7 +14326,6 @@ async function resumeTimedOutTurn(payload) {
|
|
|
13115
14326
|
channelId: thread.channelId,
|
|
13116
14327
|
threadTs: thread.threadTs,
|
|
13117
14328
|
lockKey: payload.conversationId,
|
|
13118
|
-
failureText: "I hit an error while resuming that request. Please try the command again.",
|
|
13119
14329
|
replyContext: {
|
|
13120
14330
|
requester: {
|
|
13121
14331
|
userId: userMessage.author.userId,
|
|
@@ -13164,18 +14374,7 @@ async function resumeTimedOutTurn(payload) {
|
|
|
13164
14374
|
);
|
|
13165
14375
|
}
|
|
13166
14376
|
},
|
|
13167
|
-
onFailure: async (
|
|
13168
|
-
logException(
|
|
13169
|
-
error,
|
|
13170
|
-
"timeout_resume_failed",
|
|
13171
|
-
{},
|
|
13172
|
-
{
|
|
13173
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
13174
|
-
"app.ai.session_id": payload.sessionId,
|
|
13175
|
-
...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
|
|
13176
|
-
},
|
|
13177
|
-
"Failed to resume timed-out turn"
|
|
13178
|
-
);
|
|
14377
|
+
onFailure: async () => {
|
|
13179
14378
|
await persistFailedReplyState2(checkpoint);
|
|
13180
14379
|
},
|
|
13181
14380
|
onAuthPause: async () => {
|
|
@@ -13287,11 +14486,11 @@ var DIRECTED_FOLLOW_UP_CUE_RE = /\b(?:you said|you just said|your last response|
|
|
|
13287
14486
|
var TERSE_CLARIFICATION_RE = /^(?:which one|which ones|why|how so|what do you mean|what did you mean|say more|explain that|clarify that|expand on that|elaborate on that)\??$/i;
|
|
13288
14487
|
var GENERIC_IMMEDIATE_SIDE_CONVERSATION_RE = /^(?:is that (?:the )?right (?:approach|call|move)|(?:can|could|would) you check on this)\??$/i;
|
|
13289
14488
|
var RECENT_THREAD_WINDOW = 6;
|
|
13290
|
-
function
|
|
14489
|
+
function escapeRegExp2(value) {
|
|
13291
14490
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13292
14491
|
}
|
|
13293
14492
|
function containsAssistantInvocation(text, botUserName) {
|
|
13294
|
-
const escapedUserName =
|
|
14493
|
+
const escapedUserName = escapeRegExp2(botUserName);
|
|
13295
14494
|
const plainNameMentionRe = new RegExp(`(^|\\s)@${escapedUserName}\\b`, "i");
|
|
13296
14495
|
const labeledEntityMentionRe = new RegExp(
|
|
13297
14496
|
`<@[^>|]+\\|${escapedUserName}>`,
|
|
@@ -13587,7 +14786,7 @@ async function decideSubscribedThreadReply(args) {
|
|
|
13587
14786
|
}
|
|
13588
14787
|
|
|
13589
14788
|
// src/chat/runtime/thread-context.ts
|
|
13590
|
-
function
|
|
14789
|
+
function escapeRegExp3(value) {
|
|
13591
14790
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13592
14791
|
}
|
|
13593
14792
|
function stripLeadingBotMention(text, options = {}) {
|
|
@@ -13597,12 +14796,12 @@ function stripLeadingBotMention(text, options = {}) {
|
|
|
13597
14796
|
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
13598
14797
|
}
|
|
13599
14798
|
const mentionByNameRe = new RegExp(
|
|
13600
|
-
`^\\s*@${
|
|
14799
|
+
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
13601
14800
|
"i"
|
|
13602
14801
|
);
|
|
13603
14802
|
next = next.replace(mentionByNameRe, "").trim();
|
|
13604
14803
|
const mentionByLabeledEntityRe = new RegExp(
|
|
13605
|
-
`^\\s*<@[^>|]+\\|${
|
|
14804
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
13606
14805
|
"i"
|
|
13607
14806
|
);
|
|
13608
14807
|
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
@@ -13703,15 +14902,6 @@ async function maybeHandleThreadOptOutDecision(args) {
|
|
|
13703
14902
|
await args.thread.post(THREAD_OPTOUT_ACK);
|
|
13704
14903
|
return true;
|
|
13705
14904
|
}
|
|
13706
|
-
function buildFailureMessage(reference) {
|
|
13707
|
-
if (!reference) {
|
|
13708
|
-
return "I ran into an internal error while processing that. Please try again.";
|
|
13709
|
-
}
|
|
13710
|
-
if (reference.eventId) {
|
|
13711
|
-
return `I ran into an internal error while processing that. Reference: \`event_id=${reference.eventId} trace_id=${reference.traceId}\`.`;
|
|
13712
|
-
}
|
|
13713
|
-
return `I ran into an internal error while processing that. Reference: \`trace_id=${reference.traceId}\`.`;
|
|
13714
|
-
}
|
|
13715
14905
|
function buildLogContext(deps, args) {
|
|
13716
14906
|
return {
|
|
13717
14907
|
conversationId: args.threadId ?? args.runId,
|
|
@@ -13728,7 +14918,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
13728
14918
|
const logContext = (args) => buildLogContext(deps, args);
|
|
13729
14919
|
const postFallbackErrorReplyWithLogging = async (args) => {
|
|
13730
14920
|
try {
|
|
13731
|
-
await args.thread.post(
|
|
14921
|
+
await args.thread.post(buildTurnFailureResponse(args.eventId));
|
|
13732
14922
|
} catch (postError) {
|
|
13733
14923
|
deps.logException(
|
|
13734
14924
|
postError,
|
|
@@ -13736,7 +14926,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
13736
14926
|
args.errorContext,
|
|
13737
14927
|
{
|
|
13738
14928
|
"app.slack.reply_stage": "error_fallback_post",
|
|
13739
|
-
|
|
14929
|
+
"app.error.original_event_id": args.eventId,
|
|
13740
14930
|
...getSlackErrorObservabilityAttributes(postError)
|
|
13741
14931
|
},
|
|
13742
14932
|
args.postFailureBody
|
|
@@ -13822,11 +15012,14 @@ function createSlackTurnRuntime(deps) {
|
|
|
13822
15012
|
{},
|
|
13823
15013
|
"onNewMention failed"
|
|
13824
15014
|
);
|
|
15015
|
+
if (!eventId) {
|
|
15016
|
+
throw new Error(
|
|
15017
|
+
"Sentry did not return an event ID for mention_handler_failed"
|
|
15018
|
+
);
|
|
15019
|
+
}
|
|
13825
15020
|
await hooks?.beforeFirstResponsePost?.();
|
|
13826
|
-
const reference = deps.getErrorReference(eventId);
|
|
13827
15021
|
await postFallbackErrorReplyWithLogging({
|
|
13828
15022
|
thread,
|
|
13829
|
-
reference,
|
|
13830
15023
|
errorContext,
|
|
13831
15024
|
eventId,
|
|
13832
15025
|
postFailureEventName: "mention_handler_failure_reply_post_failed",
|
|
@@ -13954,11 +15147,14 @@ function createSlackTurnRuntime(deps) {
|
|
|
13954
15147
|
{},
|
|
13955
15148
|
"onSubscribedMessage failed"
|
|
13956
15149
|
);
|
|
15150
|
+
if (!eventId) {
|
|
15151
|
+
throw new Error(
|
|
15152
|
+
"Sentry did not return an event ID for subscribed_message_handler_failed"
|
|
15153
|
+
);
|
|
15154
|
+
}
|
|
13957
15155
|
await hooks?.beforeFirstResponsePost?.();
|
|
13958
|
-
const reference = deps.getErrorReference(eventId);
|
|
13959
15156
|
await postFallbackErrorReplyWithLogging({
|
|
13960
15157
|
thread,
|
|
13961
|
-
reference,
|
|
13962
15158
|
errorContext,
|
|
13963
15159
|
eventId,
|
|
13964
15160
|
postFailureEventName: "subscribed_message_handler_failure_reply_post_failed",
|
|
@@ -14746,19 +15942,6 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
14746
15942
|
}
|
|
14747
15943
|
|
|
14748
15944
|
// src/chat/runtime/reply-executor.ts
|
|
14749
|
-
function getExecutionFailureReason(reply) {
|
|
14750
|
-
const errorMessage = reply.diagnostics.errorMessage?.trim();
|
|
14751
|
-
if (errorMessage) {
|
|
14752
|
-
return errorMessage;
|
|
14753
|
-
}
|
|
14754
|
-
if (reply.diagnostics.toolErrorCount > 0) {
|
|
14755
|
-
return `${reply.diagnostics.toolErrorCount} tool result error(s)`;
|
|
14756
|
-
}
|
|
14757
|
-
if (reply.diagnostics.assistantMessageCount > 0) {
|
|
14758
|
-
return "assistant returned no text";
|
|
14759
|
-
}
|
|
14760
|
-
return "empty assistant turn";
|
|
14761
|
-
}
|
|
14762
15945
|
function createReplyToThread(deps) {
|
|
14763
15946
|
return async function replyToThread(thread, message, options = {}) {
|
|
14764
15947
|
if (message.author.isMe) {
|
|
@@ -14908,7 +16091,7 @@ function createReplyToThread(deps) {
|
|
|
14908
16091
|
let shouldPersistFailureState = true;
|
|
14909
16092
|
try {
|
|
14910
16093
|
const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
|
|
14911
|
-
|
|
16094
|
+
let reply = await deps.services.generateAssistantReply(userText, {
|
|
14912
16095
|
requester: {
|
|
14913
16096
|
userId: message.author.userId,
|
|
14914
16097
|
userName: message.author.userName ?? fallbackIdentity?.userName,
|
|
@@ -14967,49 +16150,14 @@ function createReplyToThread(deps) {
|
|
|
14967
16150
|
assistantUserName: botConfig.userName,
|
|
14968
16151
|
modelId: reply.diagnostics.modelId
|
|
14969
16152
|
};
|
|
14970
|
-
const diagnosticsAttributes =
|
|
14971
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
14972
|
-
"gen_ai.operation.name": "invoke_agent",
|
|
14973
|
-
"app.ai.outcome": reply.diagnostics.outcome,
|
|
14974
|
-
"app.ai.assistant_messages": reply.diagnostics.assistantMessageCount,
|
|
14975
|
-
"app.ai.tool_results": reply.diagnostics.toolResultCount,
|
|
14976
|
-
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
|
|
14977
|
-
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
14978
|
-
"app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
|
|
14979
|
-
...reply.diagnostics.thinkingLevel ? {
|
|
14980
|
-
"app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
|
|
14981
|
-
} : {},
|
|
14982
|
-
...reply.diagnostics.stopReason ? {
|
|
14983
|
-
"gen_ai.response.finish_reasons": [
|
|
14984
|
-
reply.diagnostics.stopReason
|
|
14985
|
-
]
|
|
14986
|
-
} : {},
|
|
14987
|
-
...reply.diagnostics.errorMessage ? { "error.message": reply.diagnostics.errorMessage } : {}
|
|
14988
|
-
};
|
|
16153
|
+
const diagnosticsAttributes = getAgentTurnDiagnosticsAttributes(reply);
|
|
14989
16154
|
setSpanAttributes(diagnosticsAttributes);
|
|
14990
|
-
if (reply.diagnostics.outcome
|
|
14991
|
-
|
|
14992
|
-
reply
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
"agent_turn_provider_error",
|
|
14997
|
-
diagnosticsContext,
|
|
14998
|
-
diagnosticsAttributes,
|
|
14999
|
-
"Agent turn failed with provider error"
|
|
15000
|
-
);
|
|
15001
|
-
} else if (reply.diagnostics.outcome !== "success") {
|
|
15002
|
-
const failureReason = getExecutionFailureReason(reply);
|
|
15003
|
-
logException(
|
|
15004
|
-
new Error(`Agent turn execution failure: ${failureReason}`),
|
|
15005
|
-
"agent_turn_execution_failure",
|
|
15006
|
-
diagnosticsContext,
|
|
15007
|
-
{
|
|
15008
|
-
...diagnosticsAttributes,
|
|
15009
|
-
"app.ai.execution_failure_reason": failureReason
|
|
15010
|
-
},
|
|
15011
|
-
"Agent turn completed with execution failure"
|
|
15012
|
-
);
|
|
16155
|
+
if (reply.diagnostics.outcome !== "success") {
|
|
16156
|
+
reply = finalizeFailedTurnReply({
|
|
16157
|
+
reply,
|
|
16158
|
+
logException,
|
|
16159
|
+
context: diagnosticsContext
|
|
16160
|
+
});
|
|
15013
16161
|
}
|
|
15014
16162
|
markConversationMessage(
|
|
15015
16163
|
preparedState.conversation,
|
|
@@ -15381,7 +16529,6 @@ function createSlackRuntime(options) {
|
|
|
15381
16529
|
assistantUserName: botConfig.userName,
|
|
15382
16530
|
modelId: botConfig.modelId,
|
|
15383
16531
|
now: options.now ?? (() => Date.now()),
|
|
15384
|
-
getErrorReference: resolveErrorReference,
|
|
15385
16532
|
getThreadId,
|
|
15386
16533
|
getChannelId,
|
|
15387
16534
|
getRunId,
|