@sentry/junior 0.46.0 → 0.47.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 +486 -226
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -4466,6 +4466,29 @@ function truncateText(value, maxChars = MAX_TEXT_CHARS) {
|
|
|
4466
4466
|
truncated: true
|
|
4467
4467
|
};
|
|
4468
4468
|
}
|
|
4469
|
+
function isMissingPathError(error) {
|
|
4470
|
+
if (!error || typeof error !== "object") {
|
|
4471
|
+
return false;
|
|
4472
|
+
}
|
|
4473
|
+
const candidate = error;
|
|
4474
|
+
if (candidate.code === "ENOENT") {
|
|
4475
|
+
return true;
|
|
4476
|
+
}
|
|
4477
|
+
return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
|
|
4478
|
+
}
|
|
4479
|
+
function missingPathSearchResult(params) {
|
|
4480
|
+
const textPath = params.displayPath ?? params.missingPath ?? params.path;
|
|
4481
|
+
return {
|
|
4482
|
+
content: [{ type: "text", text: `Path not found: ${textPath}` }],
|
|
4483
|
+
details: {
|
|
4484
|
+
ok: false,
|
|
4485
|
+
error: "not_found",
|
|
4486
|
+
path: params.path,
|
|
4487
|
+
...params.missingPath ? { missing_path: params.missingPath } : {},
|
|
4488
|
+
truncated: false
|
|
4489
|
+
}
|
|
4490
|
+
};
|
|
4491
|
+
}
|
|
4469
4492
|
function escapeRegExp(value) {
|
|
4470
4493
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4471
4494
|
}
|
|
@@ -4520,13 +4543,35 @@ function resolveWorkspacePath(input, fallback = ".") {
|
|
|
4520
4543
|
async function collectFiles(params) {
|
|
4521
4544
|
const files = [];
|
|
4522
4545
|
let limitReached = false;
|
|
4546
|
+
let missingPath;
|
|
4523
4547
|
const visit = async (dirPath) => {
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4548
|
+
if (missingPath) {
|
|
4549
|
+
return;
|
|
4550
|
+
}
|
|
4551
|
+
let entries;
|
|
4552
|
+
try {
|
|
4553
|
+
entries = (await params.fs.readdir(dirPath)).sort(
|
|
4554
|
+
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
4555
|
+
);
|
|
4556
|
+
} catch (error) {
|
|
4557
|
+
if (isMissingPathError(error)) {
|
|
4558
|
+
missingPath = dirPath;
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4561
|
+
throw error;
|
|
4562
|
+
}
|
|
4527
4563
|
for (const entry of entries) {
|
|
4528
4564
|
const fullPath = path4.posix.join(dirPath, entry);
|
|
4529
|
-
|
|
4565
|
+
let stat2;
|
|
4566
|
+
try {
|
|
4567
|
+
stat2 = await params.fs.stat(fullPath);
|
|
4568
|
+
} catch (error) {
|
|
4569
|
+
if (isMissingPathError(error)) {
|
|
4570
|
+
missingPath = fullPath;
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
4573
|
+
throw error;
|
|
4574
|
+
}
|
|
4530
4575
|
if (stat2.isDirectory()) {
|
|
4531
4576
|
if (!SKIPPED_DIRECTORIES.has(entry)) {
|
|
4532
4577
|
await visit(fullPath);
|
|
@@ -4544,20 +4589,38 @@ async function collectFiles(params) {
|
|
|
4544
4589
|
}
|
|
4545
4590
|
}
|
|
4546
4591
|
};
|
|
4547
|
-
|
|
4592
|
+
let stat;
|
|
4593
|
+
try {
|
|
4594
|
+
stat = await params.fs.stat(params.root);
|
|
4595
|
+
} catch (error) {
|
|
4596
|
+
if (isMissingPathError(error)) {
|
|
4597
|
+
return {
|
|
4598
|
+
files,
|
|
4599
|
+
limitReached: false,
|
|
4600
|
+
missingPath: params.root,
|
|
4601
|
+
missingRoot: true
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
throw error;
|
|
4605
|
+
}
|
|
4548
4606
|
if (!stat.isDirectory()) {
|
|
4549
4607
|
const relativePath = path4.posix.basename(params.root);
|
|
4550
4608
|
return {
|
|
4551
4609
|
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
4552
|
-
limitReached: false
|
|
4610
|
+
limitReached: false,
|
|
4611
|
+
missingRoot: false
|
|
4553
4612
|
};
|
|
4554
4613
|
}
|
|
4555
4614
|
await visit(params.root);
|
|
4556
|
-
return {
|
|
4615
|
+
return {
|
|
4616
|
+
files,
|
|
4617
|
+
limitReached,
|
|
4618
|
+
missingPath,
|
|
4619
|
+
missingRoot: missingPath === params.root
|
|
4620
|
+
};
|
|
4557
4621
|
}
|
|
4558
4622
|
|
|
4559
|
-
// src/chat/tools/sandbox/
|
|
4560
|
-
import { Type as Type2 } from "@sinclair/typebox";
|
|
4623
|
+
// src/chat/tools/sandbox/text-edits.ts
|
|
4561
4624
|
function detectLineEnding(value) {
|
|
4562
4625
|
return value.includes("\r\n") ? "\r\n" : "\n";
|
|
4563
4626
|
}
|
|
@@ -4589,6 +4652,44 @@ function firstChangedLine(oldContent, newContent) {
|
|
|
4589
4652
|
}
|
|
4590
4653
|
return void 0;
|
|
4591
4654
|
}
|
|
4655
|
+
function prepareTextReplacementArguments(input) {
|
|
4656
|
+
if (!input || typeof input !== "object") {
|
|
4657
|
+
return input;
|
|
4658
|
+
}
|
|
4659
|
+
const raw = { ...input };
|
|
4660
|
+
if (typeof raw.edits === "string") {
|
|
4661
|
+
try {
|
|
4662
|
+
raw.edits = JSON.parse(raw.edits);
|
|
4663
|
+
} catch {
|
|
4664
|
+
return raw;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
const edits = Array.isArray(raw.edits) ? [...raw.edits] : [];
|
|
4668
|
+
const oldText = raw.oldText ?? raw.old_text;
|
|
4669
|
+
const newText = raw.newText ?? raw.new_text;
|
|
4670
|
+
if (typeof oldText === "string" && typeof newText === "string") {
|
|
4671
|
+
edits.push({ oldText, newText });
|
|
4672
|
+
}
|
|
4673
|
+
if (edits.length > 0) {
|
|
4674
|
+
raw.edits = edits.map((edit) => {
|
|
4675
|
+
if (!edit || typeof edit !== "object") {
|
|
4676
|
+
return edit;
|
|
4677
|
+
}
|
|
4678
|
+
const record = edit;
|
|
4679
|
+
const { old_text, new_text, ...rest } = record;
|
|
4680
|
+
return {
|
|
4681
|
+
...rest,
|
|
4682
|
+
oldText: record.oldText ?? old_text,
|
|
4683
|
+
newText: record.newText ?? new_text
|
|
4684
|
+
};
|
|
4685
|
+
});
|
|
4686
|
+
}
|
|
4687
|
+
delete raw.oldText;
|
|
4688
|
+
delete raw.old_text;
|
|
4689
|
+
delete raw.newText;
|
|
4690
|
+
delete raw.new_text;
|
|
4691
|
+
return raw;
|
|
4692
|
+
}
|
|
4592
4693
|
function buildCompactDiff(oldContent, newContent) {
|
|
4593
4694
|
const oldLines = oldContent.split("\n");
|
|
4594
4695
|
const newLines = newContent.split("\n");
|
|
@@ -4630,19 +4731,19 @@ function buildCompactDiff(oldContent, newContent) {
|
|
|
4630
4731
|
firstChangedLine: firstChangedLine(oldContent, newContent)
|
|
4631
4732
|
};
|
|
4632
4733
|
}
|
|
4633
|
-
function
|
|
4734
|
+
function validateAndApplyTextEdits(content, edits, targetName) {
|
|
4634
4735
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
4635
|
-
throw new Error(
|
|
4736
|
+
throw new Error(`${targetName} requires at least one edit.`);
|
|
4636
4737
|
}
|
|
4637
4738
|
const normalizedEdits = edits.map((edit, index) => {
|
|
4638
4739
|
if (typeof edit.oldText !== "string" || edit.oldText.length === 0) {
|
|
4639
4740
|
throw new Error(
|
|
4640
|
-
`edits[${index}].oldText must not be empty in ${
|
|
4741
|
+
`edits[${index}].oldText must not be empty in ${targetName}.`
|
|
4641
4742
|
);
|
|
4642
4743
|
}
|
|
4643
4744
|
if (typeof edit.newText !== "string") {
|
|
4644
4745
|
throw new Error(
|
|
4645
|
-
`edits[${index}].newText must be a string in ${
|
|
4746
|
+
`edits[${index}].newText must be a string in ${targetName}.`
|
|
4646
4747
|
);
|
|
4647
4748
|
}
|
|
4648
4749
|
return {
|
|
@@ -4656,13 +4757,13 @@ function validateAndApplyEdits(content, edits, filePath) {
|
|
|
4656
4757
|
const matchIndex = content.indexOf(edit.oldText);
|
|
4657
4758
|
if (matchIndex === -1) {
|
|
4658
4759
|
throw new Error(
|
|
4659
|
-
`Could not find edits[${index}] in ${
|
|
4760
|
+
`Could not find edits[${index}] in ${targetName}. oldText must match exactly including whitespace and newlines.`
|
|
4660
4761
|
);
|
|
4661
4762
|
}
|
|
4662
4763
|
const occurrences = countOccurrences(content, edit.oldText);
|
|
4663
4764
|
if (occurrences > 1) {
|
|
4664
4765
|
throw new Error(
|
|
4665
|
-
`Found ${occurrences} occurrences of edits[${index}] in ${
|
|
4766
|
+
`Found ${occurrences} occurrences of edits[${index}] in ${targetName}. Each oldText must be unique.`
|
|
4666
4767
|
);
|
|
4667
4768
|
}
|
|
4668
4769
|
matchedEdits.push({
|
|
@@ -4678,7 +4779,7 @@ function validateAndApplyEdits(content, edits, filePath) {
|
|
|
4678
4779
|
const current = matchedEdits[index];
|
|
4679
4780
|
if (previous.matchIndex + previous.matchLength > current.matchIndex) {
|
|
4680
4781
|
throw new Error(
|
|
4681
|
-
`edits[${previous.editIndex}] and edits[${current.editIndex}] overlap in ${
|
|
4782
|
+
`edits[${previous.editIndex}] and edits[${current.editIndex}] overlap in ${targetName}. Merge overlapping replacements into one edit.`
|
|
4682
4783
|
);
|
|
4683
4784
|
}
|
|
4684
4785
|
}
|
|
@@ -4688,47 +4789,15 @@ function validateAndApplyEdits(content, edits, filePath) {
|
|
|
4688
4789
|
newContent = newContent.slice(0, edit.matchIndex) + edit.newText + newContent.slice(edit.matchIndex + edit.matchLength);
|
|
4689
4790
|
}
|
|
4690
4791
|
if (newContent === content) {
|
|
4691
|
-
throw new Error(`No changes made to ${
|
|
4792
|
+
throw new Error(`No changes made to ${targetName}.`);
|
|
4692
4793
|
}
|
|
4693
4794
|
return { baseContent: content, newContent };
|
|
4694
4795
|
}
|
|
4796
|
+
|
|
4797
|
+
// src/chat/tools/sandbox/edit-file.ts
|
|
4798
|
+
import { Type as Type2 } from "@sinclair/typebox";
|
|
4695
4799
|
function prepareEditFileArguments(input) {
|
|
4696
|
-
|
|
4697
|
-
return input;
|
|
4698
|
-
}
|
|
4699
|
-
const raw = { ...input };
|
|
4700
|
-
if (typeof raw.edits === "string") {
|
|
4701
|
-
try {
|
|
4702
|
-
raw.edits = JSON.parse(raw.edits);
|
|
4703
|
-
} catch {
|
|
4704
|
-
return raw;
|
|
4705
|
-
}
|
|
4706
|
-
}
|
|
4707
|
-
const edits = Array.isArray(raw.edits) ? [...raw.edits] : [];
|
|
4708
|
-
const oldText = raw.oldText ?? raw.old_text;
|
|
4709
|
-
const newText = raw.newText ?? raw.new_text;
|
|
4710
|
-
if (typeof oldText === "string" && typeof newText === "string") {
|
|
4711
|
-
edits.push({ oldText, newText });
|
|
4712
|
-
}
|
|
4713
|
-
if (edits.length > 0) {
|
|
4714
|
-
raw.edits = edits.map((edit) => {
|
|
4715
|
-
if (!edit || typeof edit !== "object") {
|
|
4716
|
-
return edit;
|
|
4717
|
-
}
|
|
4718
|
-
const record = edit;
|
|
4719
|
-
const { old_text, new_text, ...rest } = record;
|
|
4720
|
-
return {
|
|
4721
|
-
...rest,
|
|
4722
|
-
oldText: record.oldText ?? old_text,
|
|
4723
|
-
newText: record.newText ?? new_text
|
|
4724
|
-
};
|
|
4725
|
-
});
|
|
4726
|
-
}
|
|
4727
|
-
delete raw.oldText;
|
|
4728
|
-
delete raw.old_text;
|
|
4729
|
-
delete raw.newText;
|
|
4730
|
-
delete raw.new_text;
|
|
4731
|
-
return raw;
|
|
4800
|
+
return prepareTextReplacementArguments(input);
|
|
4732
4801
|
}
|
|
4733
4802
|
async function editFile(params) {
|
|
4734
4803
|
const filePath = resolveWorkspacePath(params.path);
|
|
@@ -4736,7 +4805,7 @@ async function editFile(params) {
|
|
|
4736
4805
|
const { bom, text } = stripBom(rawContent);
|
|
4737
4806
|
const lineEnding = detectLineEnding(text);
|
|
4738
4807
|
const normalizedContent = normalizeToLf(text);
|
|
4739
|
-
const { baseContent, newContent } =
|
|
4808
|
+
const { baseContent, newContent } = validateAndApplyTextEdits(
|
|
4740
4809
|
normalizedContent,
|
|
4741
4810
|
params.edits,
|
|
4742
4811
|
params.path
|
|
@@ -4817,12 +4886,18 @@ async function findFiles(params) {
|
|
|
4817
4886
|
}
|
|
4818
4887
|
const root = resolveWorkspacePath(params.path);
|
|
4819
4888
|
const limit = positiveInteger(params.limit) ?? DEFAULT_FIND_LIMIT;
|
|
4820
|
-
const { files, limitReached } = await collectFiles({
|
|
4889
|
+
const { files, limitReached, missingPath, missingRoot } = await collectFiles({
|
|
4821
4890
|
fs: params.fs,
|
|
4822
4891
|
root,
|
|
4823
4892
|
pattern: params.pattern,
|
|
4824
4893
|
limit
|
|
4825
4894
|
});
|
|
4895
|
+
if (missingPath) {
|
|
4896
|
+
return missingPathSearchResult({
|
|
4897
|
+
path: params.path ?? ".",
|
|
4898
|
+
...missingRoot ? { displayPath: params.path ?? "." } : { missingPath }
|
|
4899
|
+
});
|
|
4900
|
+
}
|
|
4826
4901
|
const relativePaths = files.map(
|
|
4827
4902
|
(filePath) => path5.posix.relative(root, filePath)
|
|
4828
4903
|
);
|
|
@@ -4919,11 +4994,17 @@ async function grepFiles(params) {
|
|
|
4919
4994
|
const limit = positiveInteger(params.limit) ?? DEFAULT_GREP_LIMIT;
|
|
4920
4995
|
const context = positiveInteger(params.context) ?? 0;
|
|
4921
4996
|
const regex = params.literal ? void 0 : new RegExp(params.pattern, params.ignoreCase ? "i" : "");
|
|
4922
|
-
const { files } = await collectFiles({
|
|
4997
|
+
const { files, missingPath, missingRoot } = await collectFiles({
|
|
4923
4998
|
fs: params.fs,
|
|
4924
4999
|
root,
|
|
4925
5000
|
pattern: params.glob
|
|
4926
5001
|
});
|
|
5002
|
+
if (missingPath) {
|
|
5003
|
+
return missingPathSearchResult({
|
|
5004
|
+
path: params.path ?? ".",
|
|
5005
|
+
...missingRoot ? { displayPath: params.path ?? "." } : { missingPath }
|
|
5006
|
+
});
|
|
5007
|
+
}
|
|
4927
5008
|
const output = [];
|
|
4928
5009
|
let matchCount = 0;
|
|
4929
5010
|
let matchLimitReached = false;
|
|
@@ -4933,8 +5014,14 @@ async function grepFiles(params) {
|
|
|
4933
5014
|
let content;
|
|
4934
5015
|
try {
|
|
4935
5016
|
content = await params.fs.readFile(filePath, { encoding: "utf8" });
|
|
4936
|
-
} catch {
|
|
4937
|
-
|
|
5017
|
+
} catch (error) {
|
|
5018
|
+
if (isMissingPathError(error)) {
|
|
5019
|
+
return missingPathSearchResult({
|
|
5020
|
+
path: params.path ?? ".",
|
|
5021
|
+
missingPath: filePath
|
|
5022
|
+
});
|
|
5023
|
+
}
|
|
5024
|
+
throw error;
|
|
4938
5025
|
}
|
|
4939
5026
|
if (content.includes("\0")) {
|
|
4940
5027
|
continue;
|
|
@@ -5214,13 +5301,29 @@ var DEFAULT_LIST_LIMIT = 500;
|
|
|
5214
5301
|
async function listDir(params) {
|
|
5215
5302
|
const dirPath = resolveWorkspacePath(params.path);
|
|
5216
5303
|
const limit = positiveInteger(params.limit) ?? DEFAULT_LIST_LIMIT;
|
|
5217
|
-
|
|
5304
|
+
let stat;
|
|
5305
|
+
try {
|
|
5306
|
+
stat = await params.fs.stat(dirPath);
|
|
5307
|
+
} catch (error) {
|
|
5308
|
+
if (isMissingPathError(error)) {
|
|
5309
|
+
return missingPathSearchResult({ path: params.path ?? "." });
|
|
5310
|
+
}
|
|
5311
|
+
throw error;
|
|
5312
|
+
}
|
|
5218
5313
|
if (!stat.isDirectory()) {
|
|
5219
5314
|
throw new Error(`Not a directory: ${params.path ?? "."}`);
|
|
5220
5315
|
}
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5316
|
+
let entries;
|
|
5317
|
+
try {
|
|
5318
|
+
entries = (await params.fs.readdir(dirPath)).sort(
|
|
5319
|
+
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
5320
|
+
);
|
|
5321
|
+
} catch (error) {
|
|
5322
|
+
if (isMissingPathError(error)) {
|
|
5323
|
+
return missingPathSearchResult({ path: params.path ?? "." });
|
|
5324
|
+
}
|
|
5325
|
+
throw error;
|
|
5326
|
+
}
|
|
5224
5327
|
const output = [];
|
|
5225
5328
|
let entryLimitReached = false;
|
|
5226
5329
|
for (const entry of entries) {
|
|
@@ -5232,8 +5335,14 @@ async function listDir(params) {
|
|
|
5232
5335
|
try {
|
|
5233
5336
|
const entryStat = await params.fs.stat(entryPath);
|
|
5234
5337
|
output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
|
|
5235
|
-
} catch {
|
|
5236
|
-
|
|
5338
|
+
} catch (error) {
|
|
5339
|
+
if (isMissingPathError(error)) {
|
|
5340
|
+
return missingPathSearchResult({
|
|
5341
|
+
path: params.path ?? ".",
|
|
5342
|
+
missingPath: entryPath
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
throw error;
|
|
5237
5346
|
}
|
|
5238
5347
|
}
|
|
5239
5348
|
const bounded = truncateText(
|
|
@@ -5824,6 +5933,14 @@ function sliceFileContent(params) {
|
|
|
5824
5933
|
} : {}
|
|
5825
5934
|
};
|
|
5826
5935
|
}
|
|
5936
|
+
function missingFileResult(path11) {
|
|
5937
|
+
return {
|
|
5938
|
+
content: "",
|
|
5939
|
+
error: "not_found",
|
|
5940
|
+
path: path11,
|
|
5941
|
+
success: false
|
|
5942
|
+
};
|
|
5943
|
+
}
|
|
5827
5944
|
function createReadFileTool() {
|
|
5828
5945
|
return tool({
|
|
5829
5946
|
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.",
|
|
@@ -6180,6 +6297,37 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
6180
6297
|
// src/chat/tools/slack/canvas-tools.ts
|
|
6181
6298
|
import { Type as Type16 } from "@sinclair/typebox";
|
|
6182
6299
|
|
|
6300
|
+
// src/chat/slack/canvas-references.ts
|
|
6301
|
+
var SLACK_FILE_ID_PATTERN = /^F[A-Z0-9]{4,}$/i;
|
|
6302
|
+
var SLACK_CANVAS_PATH_PATTERN = /^\/(?:docs|canvas|files)\/(?:T[A-Z0-9]+\/)?(?:U[A-Z0-9]+\/)?(F[A-Z0-9]{4,})(?:\/|$)/i;
|
|
6303
|
+
function isSlackHost(hostname) {
|
|
6304
|
+
return hostname === "slack.com" || hostname.endsWith(".slack.com");
|
|
6305
|
+
}
|
|
6306
|
+
function normalizeReferenceInput(input) {
|
|
6307
|
+
let value = input.trim();
|
|
6308
|
+
if (value.startsWith("<") && value.endsWith(">")) {
|
|
6309
|
+
value = value.slice(1, -1);
|
|
6310
|
+
}
|
|
6311
|
+
return value.split("|", 1)[0]?.trim() ?? "";
|
|
6312
|
+
}
|
|
6313
|
+
function extractCanvasId(input) {
|
|
6314
|
+
const trimmed = normalizeReferenceInput(input);
|
|
6315
|
+
if (!trimmed) return void 0;
|
|
6316
|
+
if (SLACK_FILE_ID_PATTERN.test(trimmed)) {
|
|
6317
|
+
return trimmed.toUpperCase();
|
|
6318
|
+
}
|
|
6319
|
+
try {
|
|
6320
|
+
const url = new URL(trimmed);
|
|
6321
|
+
if (!isSlackHost(url.hostname)) {
|
|
6322
|
+
return void 0;
|
|
6323
|
+
}
|
|
6324
|
+
const urlMatch = url.pathname.match(SLACK_CANVAS_PATH_PATTERN);
|
|
6325
|
+
return urlMatch?.[1]?.toUpperCase();
|
|
6326
|
+
} catch {
|
|
6327
|
+
return void 0;
|
|
6328
|
+
}
|
|
6329
|
+
}
|
|
6330
|
+
|
|
6183
6331
|
// src/chat/tools/slack/canvases.ts
|
|
6184
6332
|
function normalizeCanvasMarkdown(markdown) {
|
|
6185
6333
|
let normalizedHeadingCount = 0;
|
|
@@ -6271,27 +6419,7 @@ async function grantChannelCanvasAccess(canvasId, channelId) {
|
|
|
6271
6419
|
);
|
|
6272
6420
|
}
|
|
6273
6421
|
}
|
|
6274
|
-
async function
|
|
6275
|
-
const client2 = getSlackClient();
|
|
6276
|
-
const response = await withSlackRetries(
|
|
6277
|
-
() => client2.canvases.sections.lookup({
|
|
6278
|
-
canvas_id: canvasId,
|
|
6279
|
-
criteria: {
|
|
6280
|
-
contains_text: containsText
|
|
6281
|
-
}
|
|
6282
|
-
}),
|
|
6283
|
-
3,
|
|
6284
|
-
{
|
|
6285
|
-
action: "canvases.sections.lookup",
|
|
6286
|
-
attributes: {
|
|
6287
|
-
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
|
|
6288
|
-
"app.slack.canvas.contains_text_length": containsText.length
|
|
6289
|
-
}
|
|
6290
|
-
}
|
|
6291
|
-
);
|
|
6292
|
-
return response.sections?.[0]?.id;
|
|
6293
|
-
}
|
|
6294
|
-
async function updateCanvas(input) {
|
|
6422
|
+
async function writeCanvasMarkdown(input) {
|
|
6295
6423
|
const client2 = getSlackClient();
|
|
6296
6424
|
const normalizedContent = normalizeCanvasMarkdown(input.markdown);
|
|
6297
6425
|
await withSlackRetries(
|
|
@@ -6299,8 +6427,7 @@ async function updateCanvas(input) {
|
|
|
6299
6427
|
canvas_id: input.canvasId,
|
|
6300
6428
|
changes: [
|
|
6301
6429
|
{
|
|
6302
|
-
operation:
|
|
6303
|
-
section_id: input.sectionId,
|
|
6430
|
+
operation: "replace",
|
|
6304
6431
|
document_content: {
|
|
6305
6432
|
type: "markdown",
|
|
6306
6433
|
markdown: normalizedContent.markdown
|
|
@@ -6313,27 +6440,19 @@ async function updateCanvas(input) {
|
|
|
6313
6440
|
action: "canvases.edit",
|
|
6314
6441
|
attributes: {
|
|
6315
6442
|
"app.slack.canvas.canvas_id_prefix": input.canvasId.slice(0, 1),
|
|
6316
|
-
"app.slack.canvas.operation":
|
|
6443
|
+
"app.slack.canvas.operation": "replace",
|
|
6317
6444
|
"app.slack.canvas.markdown_length": normalizedContent.markdown.length,
|
|
6318
6445
|
"app.slack.canvas.markdown_normalized": normalizedContent.normalizedHeadingCount > 0,
|
|
6319
6446
|
"app.slack.canvas.normalized_heading_count": normalizedContent.normalizedHeadingCount
|
|
6320
6447
|
}
|
|
6321
6448
|
}
|
|
6322
6449
|
);
|
|
6450
|
+
return normalizedContent;
|
|
6323
6451
|
}
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
if (!trimmed) return void 0;
|
|
6329
|
-
if (CANVAS_ID_PATTERN.test(trimmed)) {
|
|
6330
|
-
return trimmed.toUpperCase();
|
|
6331
|
-
}
|
|
6332
|
-
const urlMatch = trimmed.match(CANVAS_URL_FILE_ID_PATTERN);
|
|
6333
|
-
if (urlMatch?.[1]) {
|
|
6334
|
-
return urlMatch[1].toUpperCase();
|
|
6335
|
-
}
|
|
6336
|
-
return void 0;
|
|
6452
|
+
function isCanvasFile(file) {
|
|
6453
|
+
const filetype = file.filetype?.toLowerCase() ?? "";
|
|
6454
|
+
const mimetype = file.mimetype?.toLowerCase() ?? "";
|
|
6455
|
+
return filetype === "quip" || filetype === "canvas" || mimetype.includes("quip") || mimetype.includes("canvas");
|
|
6337
6456
|
}
|
|
6338
6457
|
async function readCanvas(canvasIdOrUrl) {
|
|
6339
6458
|
const canvasId = extractCanvasId(canvasIdOrUrl);
|
|
@@ -6359,6 +6478,9 @@ async function readCanvas(canvasIdOrUrl) {
|
|
|
6359
6478
|
if (!file) {
|
|
6360
6479
|
throw new Error("Slack returned no file metadata for canvas.");
|
|
6361
6480
|
}
|
|
6481
|
+
if (!isCanvasFile(file)) {
|
|
6482
|
+
throw new Error("Slack file metadata did not describe a Canvas document.");
|
|
6483
|
+
}
|
|
6362
6484
|
const downloadUrl = file.url_private_download ?? file.url_private;
|
|
6363
6485
|
if (!downloadUrl) {
|
|
6364
6486
|
throw new Error(
|
|
@@ -6378,7 +6500,6 @@ async function readCanvas(canvasIdOrUrl) {
|
|
|
6378
6500
|
}
|
|
6379
6501
|
|
|
6380
6502
|
// src/chat/tools/slack/canvas-tools.ts
|
|
6381
|
-
var MAX_CANVAS_READ_CHARS = 4e4;
|
|
6382
6503
|
var MAX_RECENT_CANVASES = 5;
|
|
6383
6504
|
function mergeRecentCanvases(existing, created) {
|
|
6384
6505
|
const nextEntry = {
|
|
@@ -6391,6 +6512,46 @@ function mergeRecentCanvases(existing, created) {
|
|
|
6391
6512
|
const deduped = prior.filter((entry) => entry.id !== created.id);
|
|
6392
6513
|
return [nextEntry, ...deduped].slice(0, MAX_RECENT_CANVASES);
|
|
6393
6514
|
}
|
|
6515
|
+
function prepareCanvasEditArguments(input) {
|
|
6516
|
+
return prepareTextReplacementArguments(input);
|
|
6517
|
+
}
|
|
6518
|
+
function storedCanvasUrl(state, canvasId) {
|
|
6519
|
+
const lastCanvasUrl = state.artifactState.lastCanvasUrl;
|
|
6520
|
+
if (lastCanvasUrl && extractCanvasId(lastCanvasUrl) === canvasId) {
|
|
6521
|
+
return lastCanvasUrl;
|
|
6522
|
+
}
|
|
6523
|
+
for (const canvas of state.artifactState.recentCanvases ?? []) {
|
|
6524
|
+
if (extractCanvasId(canvas.id) === canvasId) {
|
|
6525
|
+
return canvas.url;
|
|
6526
|
+
}
|
|
6527
|
+
if (canvas.url && extractCanvasId(canvas.url) === canvasId) {
|
|
6528
|
+
return canvas.url;
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
return void 0;
|
|
6532
|
+
}
|
|
6533
|
+
function resolveCanvasTarget(canvas) {
|
|
6534
|
+
const canvasId = extractCanvasId(canvas);
|
|
6535
|
+
if (!canvasId) {
|
|
6536
|
+
return {
|
|
6537
|
+
ok: false,
|
|
6538
|
+
error: "Could not parse a Slack canvas/file ID from input. Provide an F-prefixed ID or a Slack canvas/docs URL."
|
|
6539
|
+
};
|
|
6540
|
+
}
|
|
6541
|
+
return { ok: true, canvasId };
|
|
6542
|
+
}
|
|
6543
|
+
var editReplacementSchema2 = Type16.Object(
|
|
6544
|
+
{
|
|
6545
|
+
oldText: Type16.String({
|
|
6546
|
+
minLength: 1,
|
|
6547
|
+
description: "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
|
|
6548
|
+
}),
|
|
6549
|
+
newText: Type16.String({
|
|
6550
|
+
description: "Replacement Canvas markdown for this edit."
|
|
6551
|
+
})
|
|
6552
|
+
},
|
|
6553
|
+
{ additionalProperties: false }
|
|
6554
|
+
);
|
|
6394
6555
|
function createSlackCanvasCreateTool(context, state) {
|
|
6395
6556
|
return tool({
|
|
6396
6557
|
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.",
|
|
@@ -6439,7 +6600,6 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
6439
6600
|
markdown,
|
|
6440
6601
|
channelId: targetChannelId
|
|
6441
6602
|
});
|
|
6442
|
-
state.setTurnCreatedCanvasId(created.canvasId);
|
|
6443
6603
|
await state.patchArtifactState({
|
|
6444
6604
|
lastCanvasId: created.canvasId,
|
|
6445
6605
|
lastCanvasUrl: created.permalink,
|
|
@@ -6463,67 +6623,111 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
6463
6623
|
}
|
|
6464
6624
|
});
|
|
6465
6625
|
}
|
|
6466
|
-
function
|
|
6626
|
+
function createSlackCanvasReadTool() {
|
|
6467
6627
|
return tool({
|
|
6468
|
-
description: "
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
}),
|
|
6474
|
-
operation: Type16.Optional(
|
|
6475
|
-
Type16.Union(
|
|
6476
|
-
[
|
|
6477
|
-
Type16.Literal("insert_at_end"),
|
|
6478
|
-
Type16.Literal("insert_at_start"),
|
|
6479
|
-
Type16.Literal("replace")
|
|
6480
|
-
],
|
|
6481
|
-
{ description: "Canvas update mode." }
|
|
6482
|
-
)
|
|
6483
|
-
),
|
|
6484
|
-
section_id: Type16.Optional(
|
|
6485
|
-
Type16.String({
|
|
6486
|
-
minLength: 1,
|
|
6487
|
-
description: "Optional section ID required for targeted replace operations."
|
|
6488
|
-
})
|
|
6489
|
-
),
|
|
6490
|
-
section_contains_text: Type16.Optional(
|
|
6491
|
-
Type16.String({
|
|
6628
|
+
description: "Read a bounded line range from a Slack canvas as markdown. Use when you need exact Canvas contents to verify facts or make edits safely. Do not use for generic web pages \u2014 use webFetch for those.",
|
|
6629
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
6630
|
+
inputSchema: Type16.Object(
|
|
6631
|
+
{
|
|
6632
|
+
canvas: Type16.String({
|
|
6492
6633
|
minLength: 1,
|
|
6493
|
-
description: "
|
|
6494
|
-
})
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6634
|
+
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
6635
|
+
}),
|
|
6636
|
+
offset: Type16.Optional(
|
|
6637
|
+
Type16.Integer({
|
|
6638
|
+
minimum: 1,
|
|
6639
|
+
description: "1-indexed line number to start reading from."
|
|
6640
|
+
})
|
|
6641
|
+
),
|
|
6642
|
+
limit: Type16.Optional(
|
|
6643
|
+
Type16.Integer({
|
|
6644
|
+
minimum: 1,
|
|
6645
|
+
description: "Maximum number of lines to read. Defaults to 1000."
|
|
6646
|
+
})
|
|
6647
|
+
)
|
|
6648
|
+
},
|
|
6649
|
+
{ additionalProperties: false }
|
|
6650
|
+
),
|
|
6651
|
+
execute: async ({ canvas, offset, limit }) => {
|
|
6652
|
+
const target = resolveCanvasTarget(canvas);
|
|
6653
|
+
if (!target.ok) {
|
|
6654
|
+
return target;
|
|
6655
|
+
}
|
|
6656
|
+
try {
|
|
6657
|
+
const result = await readCanvas(target.canvasId);
|
|
6658
|
+
const range = sliceFileContent({
|
|
6659
|
+
content: normalizeToLf(result.content),
|
|
6660
|
+
limit,
|
|
6661
|
+
offset,
|
|
6662
|
+
path: result.canvasId
|
|
6663
|
+
});
|
|
6664
|
+
return {
|
|
6665
|
+
ok: true,
|
|
6666
|
+
canvas_id: result.canvasId,
|
|
6667
|
+
title: result.title,
|
|
6668
|
+
permalink: result.permalink,
|
|
6669
|
+
mimetype: result.mimetype,
|
|
6670
|
+
filetype: result.filetype,
|
|
6671
|
+
original_byte_length: result.byteLength,
|
|
6672
|
+
content: range.content,
|
|
6673
|
+
start_line: range.start_line,
|
|
6674
|
+
end_line: range.end_line,
|
|
6675
|
+
total_lines: range.total_lines,
|
|
6676
|
+
truncated: range.truncated,
|
|
6677
|
+
continuation: range.continuation
|
|
6678
|
+
};
|
|
6679
|
+
} catch (error) {
|
|
6680
|
+
const message = error instanceof Error ? error.message : "canvas read failed";
|
|
6506
6681
|
logWarn(
|
|
6507
|
-
"
|
|
6682
|
+
"slack_canvas_read_failed",
|
|
6508
6683
|
{},
|
|
6509
6684
|
{
|
|
6510
|
-
"gen_ai.tool.name": "
|
|
6511
|
-
"app.
|
|
6512
|
-
"app.artifacts.turn_created_canvas_id": state.getTurnCreatedCanvasId() ?? "none"
|
|
6685
|
+
"gen_ai.tool.name": "slackCanvasRead",
|
|
6686
|
+
"app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
|
|
6513
6687
|
},
|
|
6514
|
-
|
|
6688
|
+
message
|
|
6515
6689
|
);
|
|
6516
6690
|
return {
|
|
6517
6691
|
ok: false,
|
|
6518
|
-
|
|
6692
|
+
canvas_id: target.canvasId,
|
|
6693
|
+
error: message
|
|
6519
6694
|
};
|
|
6520
6695
|
}
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6696
|
+
}
|
|
6697
|
+
});
|
|
6698
|
+
}
|
|
6699
|
+
function createSlackCanvasEditTool(state) {
|
|
6700
|
+
return tool({
|
|
6701
|
+
description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff.",
|
|
6702
|
+
promptSnippet: "existing-canvas exact edits; returns diff",
|
|
6703
|
+
promptGuidelines: [
|
|
6704
|
+
"prefer over slackCanvasWrite for targeted changes",
|
|
6705
|
+
"oldText exact, unique, non-overlapping",
|
|
6706
|
+
"multiple same-canvas changes: one edits[] call"
|
|
6707
|
+
],
|
|
6708
|
+
prepareArguments: prepareCanvasEditArguments,
|
|
6709
|
+
executionMode: "sequential",
|
|
6710
|
+
inputSchema: Type16.Object(
|
|
6711
|
+
{
|
|
6712
|
+
canvas: Type16.String({
|
|
6713
|
+
minLength: 1,
|
|
6714
|
+
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."
|
|
6715
|
+
}),
|
|
6716
|
+
edits: Type16.Array(editReplacementSchema2, {
|
|
6717
|
+
minItems: 1,
|
|
6718
|
+
description: "Exact replacements matched against the current Canvas body, not incrementally."
|
|
6719
|
+
})
|
|
6720
|
+
},
|
|
6721
|
+
{ additionalProperties: false }
|
|
6722
|
+
),
|
|
6723
|
+
execute: async ({ canvas, edits }) => {
|
|
6724
|
+
const target = resolveCanvasTarget(canvas);
|
|
6725
|
+
if (!target.ok) {
|
|
6726
|
+
return target;
|
|
6727
|
+
}
|
|
6728
|
+
const operationKey = createOperationKey("slackCanvasEdit", {
|
|
6729
|
+
canvas_id: target.canvasId,
|
|
6730
|
+
edits
|
|
6527
6731
|
});
|
|
6528
6732
|
const cached = state.getOperationResult(operationKey);
|
|
6529
6733
|
if (cached) {
|
|
@@ -6532,72 +6736,124 @@ function createSlackCanvasUpdateTool(state, _context) {
|
|
|
6532
6736
|
deduplicated: true
|
|
6533
6737
|
};
|
|
6534
6738
|
}
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6739
|
+
try {
|
|
6740
|
+
const current = await readCanvas(target.canvasId);
|
|
6741
|
+
const normalizedContent = normalizeToLf(current.content);
|
|
6742
|
+
const { baseContent, newContent } = validateAndApplyTextEdits(
|
|
6743
|
+
normalizedContent,
|
|
6744
|
+
edits,
|
|
6745
|
+
target.canvasId
|
|
6746
|
+
);
|
|
6747
|
+
const written = await writeCanvasMarkdown({
|
|
6748
|
+
canvasId: target.canvasId,
|
|
6749
|
+
markdown: newContent
|
|
6750
|
+
});
|
|
6751
|
+
await state.patchArtifactState({
|
|
6752
|
+
lastCanvasId: target.canvasId,
|
|
6753
|
+
lastCanvasUrl: current.permalink ?? state.artifactState.lastCanvasUrl
|
|
6754
|
+
});
|
|
6755
|
+
const diff = buildCompactDiff(
|
|
6756
|
+
normalizeCanvasMarkdown(baseContent).markdown,
|
|
6757
|
+
written.markdown
|
|
6758
|
+
);
|
|
6759
|
+
const response = {
|
|
6760
|
+
ok: true,
|
|
6761
|
+
canvas_id: target.canvasId,
|
|
6762
|
+
title: current.title,
|
|
6763
|
+
permalink: current.permalink,
|
|
6764
|
+
diff: diff.diff,
|
|
6765
|
+
first_changed_line: diff.firstChangedLine,
|
|
6766
|
+
replacements: edits.length,
|
|
6767
|
+
normalized_heading_count: written.normalizedHeadingCount,
|
|
6768
|
+
summary: `Edited canvas ${target.canvasId}`
|
|
6769
|
+
};
|
|
6770
|
+
state.setOperationResult(operationKey, response);
|
|
6771
|
+
return response;
|
|
6772
|
+
} catch (error) {
|
|
6773
|
+
const message = error instanceof Error ? error.message : "canvas edit failed";
|
|
6774
|
+
logWarn(
|
|
6775
|
+
"slack_canvas_edit_failed",
|
|
6776
|
+
{},
|
|
6777
|
+
{
|
|
6778
|
+
"gen_ai.tool.name": "slackCanvasEdit",
|
|
6779
|
+
"app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
|
|
6780
|
+
},
|
|
6781
|
+
message
|
|
6782
|
+
);
|
|
6783
|
+
return {
|
|
6784
|
+
ok: false,
|
|
6785
|
+
canvas_id: target.canvasId,
|
|
6786
|
+
error: message
|
|
6787
|
+
};
|
|
6788
|
+
}
|
|
6551
6789
|
}
|
|
6552
6790
|
});
|
|
6553
6791
|
}
|
|
6554
|
-
function
|
|
6792
|
+
function createSlackCanvasWriteTool(state) {
|
|
6555
6793
|
return tool({
|
|
6556
|
-
description: "
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6794
|
+
description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation. Do not use for targeted edits.",
|
|
6795
|
+
promptSnippet: "deliberate full-canvas replacement",
|
|
6796
|
+
promptGuidelines: ["targeted existing-canvas changes: slackCanvasEdit"],
|
|
6797
|
+
executionMode: "sequential",
|
|
6798
|
+
inputSchema: Type16.Object(
|
|
6799
|
+
{
|
|
6800
|
+
canvas: Type16.String({
|
|
6801
|
+
minLength: 1,
|
|
6802
|
+
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."
|
|
6803
|
+
}),
|
|
6804
|
+
content: Type16.String({
|
|
6805
|
+
description: "UTF-8 markdown content to write."
|
|
6806
|
+
})
|
|
6807
|
+
},
|
|
6808
|
+
{ additionalProperties: false }
|
|
6809
|
+
),
|
|
6810
|
+
execute: async ({ canvas, content }) => {
|
|
6811
|
+
const target = resolveCanvasTarget(canvas);
|
|
6812
|
+
if (!target.ok) {
|
|
6813
|
+
return target;
|
|
6814
|
+
}
|
|
6815
|
+
const operationKey = createOperationKey("slackCanvasWrite", {
|
|
6816
|
+
canvas_id: target.canvasId,
|
|
6817
|
+
content
|
|
6818
|
+
});
|
|
6819
|
+
const cached = state.getOperationResult(operationKey);
|
|
6820
|
+
if (cached) {
|
|
6567
6821
|
return {
|
|
6568
|
-
|
|
6569
|
-
|
|
6822
|
+
...cached,
|
|
6823
|
+
deduplicated: true
|
|
6570
6824
|
};
|
|
6571
6825
|
}
|
|
6572
6826
|
try {
|
|
6573
|
-
const
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6827
|
+
const written = await writeCanvasMarkdown({
|
|
6828
|
+
canvasId: target.canvasId,
|
|
6829
|
+
markdown: content
|
|
6830
|
+
});
|
|
6831
|
+
await state.patchArtifactState({
|
|
6832
|
+
lastCanvasId: target.canvasId,
|
|
6833
|
+
lastCanvasUrl: storedCanvasUrl(state, target.canvasId)
|
|
6834
|
+
});
|
|
6835
|
+
const response = {
|
|
6577
6836
|
ok: true,
|
|
6578
|
-
canvas_id:
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
mimetype: result.mimetype,
|
|
6582
|
-
filetype: result.filetype,
|
|
6583
|
-
original_byte_length: result.byteLength,
|
|
6584
|
-
truncated,
|
|
6585
|
-
content
|
|
6837
|
+
canvas_id: target.canvasId,
|
|
6838
|
+
normalized_heading_count: written.normalizedHeadingCount,
|
|
6839
|
+
summary: `Wrote canvas ${target.canvasId}`
|
|
6586
6840
|
};
|
|
6841
|
+
state.setOperationResult(operationKey, response);
|
|
6842
|
+
return response;
|
|
6587
6843
|
} catch (error) {
|
|
6588
|
-
const message = error instanceof Error ? error.message : "canvas
|
|
6844
|
+
const message = error instanceof Error ? error.message : "canvas write failed";
|
|
6589
6845
|
logWarn(
|
|
6590
|
-
"
|
|
6846
|
+
"slack_canvas_write_failed",
|
|
6591
6847
|
{},
|
|
6592
6848
|
{
|
|
6593
|
-
"gen_ai.tool.name": "
|
|
6594
|
-
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1)
|
|
6849
|
+
"gen_ai.tool.name": "slackCanvasWrite",
|
|
6850
|
+
"app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
|
|
6595
6851
|
},
|
|
6596
6852
|
message
|
|
6597
6853
|
);
|
|
6598
6854
|
return {
|
|
6599
6855
|
ok: false,
|
|
6600
|
-
canvas_id: canvasId,
|
|
6856
|
+
canvas_id: target.canvasId,
|
|
6601
6857
|
error: message
|
|
6602
6858
|
};
|
|
6603
6859
|
}
|
|
@@ -8529,7 +8785,6 @@ function createWriteFileTool() {
|
|
|
8529
8785
|
// src/chat/tools/index.ts
|
|
8530
8786
|
function createToolState(hooks, context) {
|
|
8531
8787
|
const operationResultCache = /* @__PURE__ */ new Map();
|
|
8532
|
-
let turnCreatedCanvasId;
|
|
8533
8788
|
const artifactState = {
|
|
8534
8789
|
...context.artifactState ?? {},
|
|
8535
8790
|
listColumnMap: {
|
|
@@ -8549,11 +8804,6 @@ function createToolState(hooks, context) {
|
|
|
8549
8804
|
return {
|
|
8550
8805
|
artifactState,
|
|
8551
8806
|
patchArtifactState,
|
|
8552
|
-
getCurrentCanvasId: () => artifactState.lastCanvasId,
|
|
8553
|
-
getTurnCreatedCanvasId: () => turnCreatedCanvasId,
|
|
8554
|
-
setTurnCreatedCanvasId: (canvasId) => {
|
|
8555
|
-
turnCreatedCanvasId = canvasId;
|
|
8556
|
-
},
|
|
8557
8807
|
getCurrentListId: () => artifactState.lastListId,
|
|
8558
8808
|
getOperationResult: (operationKey) => operationResultCache.get(operationKey),
|
|
8559
8809
|
setOperationResult: (operationKey, result) => {
|
|
@@ -8584,7 +8834,8 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
8584
8834
|
hooks.toolOverrides?.imageGenerate
|
|
8585
8835
|
),
|
|
8586
8836
|
slackCanvasRead: createSlackCanvasReadTool(),
|
|
8587
|
-
|
|
8837
|
+
slackCanvasEdit: createSlackCanvasEditTool(state),
|
|
8838
|
+
slackCanvasWrite: createSlackCanvasWriteTool(state),
|
|
8588
8839
|
slackThreadRead: createSlackThreadReadTool(context),
|
|
8589
8840
|
slackUserLookup: createSlackUserLookupTool(),
|
|
8590
8841
|
slackListCreate: createSlackListCreateTool(state),
|
|
@@ -10304,7 +10555,16 @@ function createSandboxExecutor(options) {
|
|
|
10304
10555
|
"app.sandbox.path.length": filePath.length
|
|
10305
10556
|
},
|
|
10306
10557
|
async () => {
|
|
10307
|
-
|
|
10558
|
+
let response;
|
|
10559
|
+
try {
|
|
10560
|
+
response = await executeReadFile({ path: filePath });
|
|
10561
|
+
} catch (error) {
|
|
10562
|
+
if (isMissingPathError(error)) {
|
|
10563
|
+
setSpanStatus("ok");
|
|
10564
|
+
return missingFileResult(filePath);
|
|
10565
|
+
}
|
|
10566
|
+
throw error;
|
|
10567
|
+
}
|
|
10308
10568
|
const content = String(response.content ?? "");
|
|
10309
10569
|
setSpanAttributes({
|
|
10310
10570
|
"app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
|
|
@@ -16363,24 +16623,14 @@ function createSlackTurnRuntime(deps) {
|
|
|
16363
16623
|
const threadId = deps.getThreadId(thread, message);
|
|
16364
16624
|
const channelId = deps.getChannelId(thread, message);
|
|
16365
16625
|
const runId = deps.getRunId(thread, message);
|
|
16366
|
-
const
|
|
16626
|
+
const turnContext = logContext({
|
|
16367
16627
|
threadId,
|
|
16368
16628
|
requesterId: message.author.userId,
|
|
16369
16629
|
requesterUserName: message.author.userName,
|
|
16370
16630
|
channelId,
|
|
16371
16631
|
runId
|
|
16372
16632
|
});
|
|
16373
|
-
|
|
16374
|
-
thread,
|
|
16375
|
-
message,
|
|
16376
|
-
logException: deps.logException,
|
|
16377
|
-
logContext: context
|
|
16378
|
-
});
|
|
16379
|
-
const toolInvocationHook = createToolInvocationHook(
|
|
16380
|
-
processingReaction,
|
|
16381
|
-
hooks
|
|
16382
|
-
);
|
|
16383
|
-
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
16633
|
+
await deps.withSpan("chat.turn", "chat.turn", turnContext, async () => {
|
|
16384
16634
|
const legacyAttachmentText = renderSlackLegacyAttachmentText(
|
|
16385
16635
|
message.raw
|
|
16386
16636
|
);
|
|
@@ -16395,7 +16645,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
16395
16645
|
strippedUserText,
|
|
16396
16646
|
message.raw
|
|
16397
16647
|
);
|
|
16398
|
-
const
|
|
16648
|
+
const threadContext = {
|
|
16399
16649
|
threadId,
|
|
16400
16650
|
requesterId: message.author.userId,
|
|
16401
16651
|
channelId,
|
|
@@ -16413,7 +16663,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
16413
16663
|
thread,
|
|
16414
16664
|
message,
|
|
16415
16665
|
decision: { shouldReply: false, reason },
|
|
16416
|
-
context:
|
|
16666
|
+
context: threadContext,
|
|
16417
16667
|
userText
|
|
16418
16668
|
});
|
|
16419
16669
|
return;
|
|
@@ -16423,7 +16673,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
16423
16673
|
message,
|
|
16424
16674
|
userText,
|
|
16425
16675
|
explicitMention: Boolean(message.isMention),
|
|
16426
|
-
context:
|
|
16676
|
+
context: threadContext
|
|
16427
16677
|
});
|
|
16428
16678
|
await deps.persistPreparedState({
|
|
16429
16679
|
thread,
|
|
@@ -16435,7 +16685,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
16435
16685
|
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
16436
16686
|
hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
|
|
16437
16687
|
isExplicitMention: Boolean(message.isMention),
|
|
16438
|
-
context:
|
|
16688
|
+
context: threadContext
|
|
16439
16689
|
});
|
|
16440
16690
|
if (await maybeHandleThreadOptOutDecision({
|
|
16441
16691
|
thread,
|
|
@@ -16446,7 +16696,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
16446
16696
|
thread,
|
|
16447
16697
|
message,
|
|
16448
16698
|
decision,
|
|
16449
|
-
context:
|
|
16699
|
+
context: threadContext,
|
|
16450
16700
|
preparedState,
|
|
16451
16701
|
userText
|
|
16452
16702
|
});
|
|
@@ -16457,12 +16707,22 @@ function createSlackTurnRuntime(deps) {
|
|
|
16457
16707
|
thread,
|
|
16458
16708
|
message,
|
|
16459
16709
|
decision,
|
|
16460
|
-
context:
|
|
16710
|
+
context: threadContext,
|
|
16461
16711
|
preparedState,
|
|
16462
16712
|
userText
|
|
16463
16713
|
});
|
|
16464
16714
|
return;
|
|
16465
16715
|
}
|
|
16716
|
+
processingReaction = await startSlackProcessingReaction({
|
|
16717
|
+
thread,
|
|
16718
|
+
message,
|
|
16719
|
+
logException: deps.logException,
|
|
16720
|
+
logContext: turnContext
|
|
16721
|
+
});
|
|
16722
|
+
const toolInvocationHook = createToolInvocationHook(
|
|
16723
|
+
processingReaction,
|
|
16724
|
+
hooks
|
|
16725
|
+
);
|
|
16466
16726
|
await deps.replyToThread(thread, message, {
|
|
16467
16727
|
explicitMention: Boolean(message.isMention),
|
|
16468
16728
|
preparedState,
|