mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Filesystem Agent Skill overhaul (#5260)
* wip * collector parse fixes * refactor for class and also operation for reading * add skill management panel * management panel + lint * management panel + lint * Hide skill in non-docker context * add ask-prompt for edit tool calls * fix dep * fix execa pkg (unused in codebase) * simplify search with ripgrep only and build deps * Fs skill i18n (#5264) i18n * add copy file support * fix translations
This commit is contained in:
@@ -86,6 +86,7 @@ app.post(
|
||||
} = await processSingleFile(targetFilename, {
|
||||
...options,
|
||||
parseOnly: true,
|
||||
absolutePath: options.absolutePath || null,
|
||||
});
|
||||
response
|
||||
.status(200)
|
||||
|
||||
@@ -32,7 +32,7 @@ async function asAudio({
|
||||
|
||||
if (!!error) {
|
||||
console.error(`Error encountered for parsing of ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: error,
|
||||
@@ -42,7 +42,7 @@ async function asAudio({
|
||||
|
||||
if (!content?.length) {
|
||||
console.error(`Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -69,7 +69,7 @@ async function asAudio({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(
|
||||
`[SUCCESS]: ${filename} transcribed, converted & ready for embedding.\n`
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ async function asDocX({
|
||||
|
||||
if (!pageContent.length) {
|
||||
console.error(`Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -55,7 +55,7 @@ async function asDocX({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.\n`);
|
||||
return { success: true, reason: null, documents: [document] };
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ async function asEPub({
|
||||
|
||||
if (!content?.length) {
|
||||
console.error(`Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -53,7 +53,7 @@ async function asEPub({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.\n`);
|
||||
return { success: true, reason: null, documents: [document] };
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ async function asImage({
|
||||
|
||||
if (!content?.length) {
|
||||
console.error(`Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -48,7 +48,7 @@ async function asImage({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.\n`);
|
||||
return { success: true, reason: null, documents: [document] };
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ async function asMbox({
|
||||
|
||||
if (!mails.length) {
|
||||
console.error(`Resulting mail items was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No mail items found in ${filename}.`,
|
||||
@@ -73,7 +73,7 @@ async function asMbox({
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(
|
||||
`[SUCCESS]: ${filename} messages converted & ready for embedding.\n`
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ async function asOfficeMime({
|
||||
|
||||
if (!content.length) {
|
||||
console.error(`Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -51,7 +51,7 @@ async function asOfficeMime({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.\n`);
|
||||
return { success: true, reason: null, documents: [document] };
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ async function asPdf({
|
||||
|
||||
if (!pageContent.length) {
|
||||
console.error(`[asPDF] Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -78,7 +78,7 @@ async function asPdf({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.\n`);
|
||||
return { success: true, reason: null, documents: [document] };
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ async function asTxt({
|
||||
|
||||
if (!content?.length) {
|
||||
console.error(`Resulting text content was empty for ${filename}.`);
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No text content found in ${filename}.`,
|
||||
@@ -51,7 +51,7 @@ async function asTxt({
|
||||
filename: `${slugify(filename)}-${data.id}`,
|
||||
options: { parseOnly: options.parseOnly },
|
||||
});
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.\n`);
|
||||
return { success: true, reason: null, documents: [document] };
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ async function asXlsx({
|
||||
documents: [],
|
||||
};
|
||||
} finally {
|
||||
trashFile(fullFilePath);
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
}
|
||||
|
||||
if (documents.length === 0) {
|
||||
|
||||
@@ -17,15 +17,21 @@ const RESERVED_FILES = ["__HOTDIR__.md"];
|
||||
* @param {string} targetFilename - The filename to process
|
||||
* @param {Object} options - The options for the file processing
|
||||
* @param {boolean} options.parseOnly - If true, the file will not be saved as a document even when `writeToServerDocuments` is called in the handler. Must be explicitly set to true to use.
|
||||
* @param {string} options.absolutePath - If provided, use this absolute path instead of resolving relative to WATCH_DIRECTORY. For internal use only.
|
||||
* @param {Object} metadata - The metadata for the file processing
|
||||
* @returns {Promise<{success: boolean, reason: string, documents: Object[]}>} - The documents from the file processing
|
||||
*/
|
||||
async function processSingleFile(targetFilename, options = {}, metadata = {}) {
|
||||
const fullFilePath = path.resolve(
|
||||
WATCH_DIRECTORY,
|
||||
normalizePath(targetFilename)
|
||||
const fullFilePath = normalizePath(
|
||||
options.absolutePath || path.resolve(WATCH_DIRECTORY, targetFilename)
|
||||
);
|
||||
if (!isWithin(path.resolve(WATCH_DIRECTORY), fullFilePath))
|
||||
|
||||
// If absolute path is not provided, check if the file is within the watch directory
|
||||
// to prevent unauthorized paths from being processed.
|
||||
if (
|
||||
!options.absolutePath &&
|
||||
!isWithin(path.resolve(WATCH_DIRECTORY), fullFilePath)
|
||||
)
|
||||
return {
|
||||
success: false,
|
||||
reason: "Filename is a not a valid path to process.",
|
||||
@@ -38,6 +44,7 @@ async function processSingleFile(targetFilename, options = {}, metadata = {}) {
|
||||
reason: "Filename is a reserved filename and cannot be processed.",
|
||||
documents: [],
|
||||
};
|
||||
|
||||
if (!fs.existsSync(fullFilePath))
|
||||
return {
|
||||
success: false,
|
||||
@@ -62,7 +69,8 @@ async function processSingleFile(targetFilename, options = {}, metadata = {}) {
|
||||
);
|
||||
processFileAs = ".txt";
|
||||
} else {
|
||||
trashFile(fullFilePath);
|
||||
// If absolute path is provided, do NOT trash the file since it is a user provided path.
|
||||
if (!options.absolutePath) trashFile(fullFilePath);
|
||||
return {
|
||||
success: false,
|
||||
reason: `File extension ${fileExtension} not supported for parsing and cannot be assumed as text file type.`,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import Admin from "@/models/admin";
|
||||
import System from "@/models/system";
|
||||
import AgentPlugins from "@/models/experimental/agentPlugins";
|
||||
import AgentFlows from "@/models/agentFlows";
|
||||
import {
|
||||
@@ -23,7 +24,11 @@ export default function AgentSkillsTab({
|
||||
const { showAgentCommand = true } = workspace ?? {};
|
||||
const agentSessionActive = useIsAgentSessionActive();
|
||||
const defaultSkills = getDefaultSkills(t);
|
||||
const configurableSkills = getConfigurableSkills(t);
|
||||
const [fileSystemAgentAvailable, setFileSystemAgentAvailable] =
|
||||
useState(false);
|
||||
const configurableSkills = getConfigurableSkills(t, {
|
||||
fileSystemAgentAvailable,
|
||||
});
|
||||
const [disabledDefaults, setDisabledDefaults] = useState([]);
|
||||
const [enabledConfigurable, setEnabledConfigurable] = useState([]);
|
||||
const [importedSkills, setImportedSkills] = useState([]);
|
||||
@@ -37,13 +42,14 @@ export default function AgentSkillsTab({
|
||||
|
||||
async function fetchSkillSettings() {
|
||||
try {
|
||||
const [prefs, flowsRes] = await Promise.all([
|
||||
const [prefs, flowsRes, fsAgentAvailable] = await Promise.all([
|
||||
Admin.systemPreferencesByFields([
|
||||
"disabled_agent_skills",
|
||||
"default_agent_skills",
|
||||
"imported_agent_skills",
|
||||
]),
|
||||
AgentFlows.listFlows(),
|
||||
System.isFileSystemAgentAvailable(),
|
||||
]);
|
||||
|
||||
if (prefs?.settings) {
|
||||
@@ -52,6 +58,7 @@ export default function AgentSkillsTab({
|
||||
setImportedSkills(prefs.settings.imported_agent_skills ?? []);
|
||||
}
|
||||
if (flowsRes?.flows) setFlows(flowsRes.flows);
|
||||
setFileSystemAgentAvailable(fsAgentAvailable);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "مرحبا في",
|
||||
getStarted: "بسم الله",
|
||||
welcome: null,
|
||||
welcome: "أهلاً وسهلاً",
|
||||
},
|
||||
llm: {
|
||||
title: "إعدادات نموذج التعلم العميق المفضّلة",
|
||||
@@ -325,6 +325,61 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"افتراضيًا، يتم تفعيل هذه الميزة، ولكن يمكنك تعطيلها إذا لم ترغب في أن تكون متاحة للممثل.",
|
||||
filesystem: {
|
||||
title: "الوصول إلى نظام الملفات",
|
||||
description:
|
||||
"السماح لمسؤولك بقراءة وكتابة الملفات والبحث عنها وإدارتها داخل مجلد محدد. يدعم تعديل الملفات وتصفح المجلدات والبحث عن المحتوى.",
|
||||
learnMore: "تعرف على المزيد حول كيفية استخدام هذه المهارة.",
|
||||
configuration: "التكوين",
|
||||
readActions: "اقرأ الإجراءات",
|
||||
writeActions: "الإجراءات",
|
||||
warning:
|
||||
"الوصول إلى نظام الملفات يمكن أن يكون خطيرًا، لأنه يمكنه تعديل أو حذف الملفات. يرجى الرجوع إلى الوثائق <link> قبل تمكينه.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "اقرأ الملف",
|
||||
description:
|
||||
"قراءة محتويات الملفات (النصوص، الشيفرة، ملفات PDF، الصور، إلخ).",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "قراءة ملفات متعددة",
|
||||
description: "اقرأ ملفات متعددة في وقت واحد.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "قائمة الاتجاهات",
|
||||
description: "اعرض قائمة الملفات والمجلدات الموجودة في مجلد معين.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "البحث عن الملفات",
|
||||
description: "ابحث عن الملفات حسب الاسم أو محتواها.",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "الحصول على معلومات الملف",
|
||||
description: "احصل على بيانات وصف تفصيلية حول الملفات.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "إنشاء ملف",
|
||||
description: "إنشاء ملفات جديدة أو استبدال الملفات الموجودة.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "تحرير الملف",
|
||||
description:
|
||||
"قم بإجراء التعديلات على ملفات النصوص بناءً على الأسطر.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "إنشاء مجلد",
|
||||
description: "إنشاء مجلدات جديدة",
|
||||
},
|
||||
"move-file": {
|
||||
title: "تحريك/إعادة تسمية الملف",
|
||||
description: "انقل أو غير أسماء الملفات والمجلدات.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "نسخ الملف",
|
||||
description: "نسخ الملفات والمجلدات",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "خوادم نظام MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Vítejte v",
|
||||
getStarted: "Začít",
|
||||
welcome: null,
|
||||
welcome: "Vítejte",
|
||||
},
|
||||
llm: {
|
||||
title: "Preferovaný LLM",
|
||||
@@ -341,6 +341,62 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Výchozí nastavení je, že tato schopnost je aktivní, ale můžete ji vypnout, pokud nechcete, aby ji mohl využít zástupce.",
|
||||
filesystem: {
|
||||
title: "Přístup k souborovému systému",
|
||||
description:
|
||||
"Umožněte svému zástupci, aby četl, zapisoval, vyhledával a spravoval soubory v určeném adresáři. Podporuje úpravu souborů, navigaci v adresářích a vyhledávání obsahu.",
|
||||
learnMore: "Zjistěte více o tom, jak tuto dovednost používat.",
|
||||
configuration: "Konfigurace",
|
||||
readActions: "Činnosti",
|
||||
writeActions: "Akce",
|
||||
warning:
|
||||
"Přístup k souborovému systému může být nebezpečný, protože může upravovat nebo mazat soubory. Před zapnutím funkce prosím nahlédněte do dokumentace <link>dokumentace</link>.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Otevřít soubor",
|
||||
description:
|
||||
"Přečtěte obsah souborů (text, kód, PDF, obrázky atd.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Přečtěte více souborů",
|
||||
description: "Přečtěte více souborů najednou",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Seznam adres",
|
||||
description: "Zobraz seznam souborů a adresářů v daném adresáři.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Hledat soubory",
|
||||
description: "Vyhledejte soubory podle názvu nebo obsahu",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Získejte informace o souboru",
|
||||
description: "Získejte podrobné metadatumy o souborech.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Vytvoř soubor",
|
||||
description:
|
||||
"Vytvořte nové soubory nebo přepsat stávající soubory.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Upravit soubor",
|
||||
description:
|
||||
"Proveďte úpravy v textových souborech na základě řádků.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Vytvořit adresář",
|
||||
description: "Vytvořte nové adresáře",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Přejmenovat/přesunout soubor",
|
||||
description: "Přesun nebo přejmenování souborů a adresářů",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Zkopírovat soubor",
|
||||
description: "Zkopírujte soubory a adresáře",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Servery společnosti MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Velkommen til",
|
||||
getStarted: "Kom godt i gang",
|
||||
welcome: null,
|
||||
welcome: "Velkommen",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM-præference",
|
||||
@@ -329,6 +329,60 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Som standard er denne funktion aktiveret, men du kan deaktivere den, hvis du ikke ønsker, at den skal være tilgængelig for agenten.",
|
||||
filesystem: {
|
||||
title: "Adgang til filsystem",
|
||||
description:
|
||||
"Giv din agent mulighed for at læse, skrive, søge og administrere filer inden for en bestemt mappe. Understøtter filredigering, mappe navigation og indholds søgning.",
|
||||
learnMore: "Lær mere om, hvordan du kan bruge denne færdighed",
|
||||
configuration: "Konfiguration",
|
||||
readActions: "Læs handlinger",
|
||||
writeActions: "Skriv handlinger",
|
||||
warning:
|
||||
"Adgang til filsystemet kan være farligt, da det kan ændre eller slette filer. Se venligst <link>dokumentationen</link> før du aktiverer denne funktion.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Åbn fil",
|
||||
description:
|
||||
"Læs indholdet af filer (tekst, kode, PDF-filer, billeder osv.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Læs flere filer",
|
||||
description: "Læs flere filer samtidigt",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Telefonkatalog",
|
||||
description: "Vis filer og mapper i en mappe",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Søg efter filer",
|
||||
description: "Søg efter filer efter navn eller indhold",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Få filinformation",
|
||||
description: "Få detaljerede metadata om filer",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Opret fil",
|
||||
description: "Opret nye filer eller skriv over eksisterende filer.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Rediger fil",
|
||||
description: "Rediger tekstfiler baseret på linjer",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Opret mappe",
|
||||
description: "Opret nye mapper",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Flyt/Omdøb fil",
|
||||
description: "Flyt eller omdøb filer og mapper",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Kopier fil",
|
||||
description: "Kopier filer og mapper",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP-servere",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Willkommen bei",
|
||||
getStarted: "Jetzt starten",
|
||||
welcome: null,
|
||||
welcome: "Herzlich willkommen",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM-Einstellung",
|
||||
@@ -332,6 +332,64 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Standardmäßig ist diese Funktion aktiviert, aber Sie können sie deaktivieren, wenn Sie nicht möchten, dass sie für den Agenten verfügbar ist.",
|
||||
filesystem: {
|
||||
title: "Zugriff auf das Dateisystem",
|
||||
description:
|
||||
"Ermöglichen Sie Ihrem Agenten, Dateien innerhalb eines bestimmten Verzeichnisses zu lesen, zu schreiben, zu suchen und zu verwalten. Unterstützt die Bearbeitung von Dateien, die Navigation durch Verzeichnisse und die Suche nach Inhalten.",
|
||||
learnMore:
|
||||
"Erfahren Sie mehr darüber, wie Sie diese Fähigkeit effektiv einsetzen können.",
|
||||
configuration: "Konfiguration",
|
||||
readActions: "Lesen von Aktionen",
|
||||
writeActions: "Aktionen",
|
||||
warning:
|
||||
"Der Zugriff auf das Dateisystem kann gefährlich sein, da er Dateien ändern oder löschen kann. Bitte konsultieren Sie vor der Aktivierung die <link>Dokumentation</link>.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Datei öffnen/lesen",
|
||||
description:
|
||||
"Inhalte von Dateien (Text, Code, PDF, Bilder usw.) lesen",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Mehrere Dateien lesen",
|
||||
description: "Mehrere Dateien gleichzeitig lesen",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Verzeichnis",
|
||||
description: "Dateien und Verzeichnisse in einem Ordner auflisten",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Dateien suchen",
|
||||
description: "Dateien nach Name oder Inhalt suchen",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Dateieninformationen abrufen",
|
||||
description: "Erhalten Sie detaillierte Metadaten über Dateien.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Datei erstellen",
|
||||
description:
|
||||
"Neue Dateien erstellen oder vorhandene Dateien überschreiben.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Datei bearbeiten",
|
||||
description:
|
||||
"Führen Sie Änderungen in Textdateien zeilenweise durch.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Ordner erstellen",
|
||||
description: "Neue Verzeichnisse erstellen",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Datei verschieben/umbenennen",
|
||||
description:
|
||||
"Dateien und Verzeichnisse verschieben oder umbenennen.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Datei kopieren",
|
||||
description: "Dateien und Verzeichnisse kopieren",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"performance-warning":
|
||||
"Die Leistung von LLMs, die keine explizite Unterstützung für das Aufrufen von Tools bieten, hängt stark von den Fähigkeiten und der Genauigkeit des Modells ab. Einige Fähigkeiten können eingeschränkt oder nicht funktionsfähig sein.",
|
||||
|
||||
@@ -338,6 +338,60 @@ const TRANSLATIONS = {
|
||||
description:
|
||||
"Enable your agent to be able to leverage SQL to answer you questions by connecting to various SQL database providers.",
|
||||
},
|
||||
filesystem: {
|
||||
title: "File System Access",
|
||||
description:
|
||||
"Enable your agent to read, write, search, and manage files within a designated directory. Supports file editing, directory navigation, and content search.",
|
||||
learnMore: "Learn more about this how to use this skill",
|
||||
configuration: "Configuration",
|
||||
readActions: "Read Actions",
|
||||
writeActions: "Write Actions",
|
||||
warning:
|
||||
"Filesystem access can be dangerous as it can modify or delete files. Please consult the <link>documentation</link> before enabling.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Read File",
|
||||
description:
|
||||
"Read contents of files (text, code, PDF, images, etc.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Read Multiple Files",
|
||||
description: "Read multiple files at once",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "List Directory",
|
||||
description: "List files and directories in a folder",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Search Files",
|
||||
description: "Search for files by name or content",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Get File Info",
|
||||
description: "Get detailed metadata about files",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Write File",
|
||||
description: "Create new files or overwrite existing files",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Edit File",
|
||||
description: "Make line-based edits to text files",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Create Directory",
|
||||
description: "Create new directories",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Copy File",
|
||||
description: "Copy files and directories",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Move/Rename File",
|
||||
description: "Move or rename files and directories",
|
||||
},
|
||||
},
|
||||
},
|
||||
default_skill:
|
||||
"By default, this skill is enabled, but you can disable it if you don't want it to be available to the agent.",
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Bienvenido a",
|
||||
getStarted: "Comenzar",
|
||||
welcome: null,
|
||||
welcome: "Bienvenido/a",
|
||||
},
|
||||
llm: {
|
||||
title: "Preferencia de LLM",
|
||||
@@ -338,6 +338,64 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Por defecto, esta función está activada, pero puede desactivarla si no desea que esté disponible para el agente.",
|
||||
filesystem: {
|
||||
title: "Acceso al sistema de archivos",
|
||||
description:
|
||||
"Permita que su agente pueda leer, escribir, buscar y administrar archivos dentro de un directorio específico. Soporta la edición de archivos, la navegación por directorios y la búsqueda de contenido.",
|
||||
learnMore: "Aprenda más sobre cómo utilizar esta habilidad.",
|
||||
configuration: "Configuración",
|
||||
readActions: "Leer acciones",
|
||||
writeActions: "Acciones a realizar",
|
||||
warning:
|
||||
"El acceso al sistema de archivos puede ser peligroso, ya que puede modificar o eliminar archivos. Consulte la <link>documentación</link> antes de habilitarlo.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Abrir archivo",
|
||||
description:
|
||||
"Leer el contenido de archivos (texto, código, archivos PDF, imágenes, etc.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Leer varios archivos",
|
||||
description: "Leer varios archivos a la vez.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Directorio",
|
||||
description:
|
||||
"Enumera los archivos y directorios dentro de una carpeta.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Buscar archivos",
|
||||
description: "Busque archivos por nombre o contenido.",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Obtener información del archivo",
|
||||
description:
|
||||
"Obtenga información detallada sobre los metadatos de los archivos.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Crear archivo",
|
||||
description:
|
||||
"Crear nuevos archivos o sobrescribir archivos existentes.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Editar archivo",
|
||||
description:
|
||||
"Realiza modificaciones basadas en líneas en archivos de texto.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Crear directorio",
|
||||
description: "Crear nuevas carpetas",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Mover/Cambiar el nombre del archivo",
|
||||
description: "Mover o renombrar archivos y directorios.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Copiar archivo",
|
||||
description: "Copiar archivos y directorios",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Servidores MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Tere tulemast",
|
||||
getStarted: "Alusta",
|
||||
welcome: null,
|
||||
welcome: "Tere tulemast",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM-i eelistus",
|
||||
@@ -324,6 +324,60 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Vaikimisi on see funktsioon lubatud, kuid saate seda välja lülitada, kui ei soovi, et see oleks saadaval kaagentile.",
|
||||
filesystem: {
|
||||
title: "Failisüsteemi juurdepääs",
|
||||
description:
|
||||
"Lisage oma agentile võimalus lugeda, kirjutada, otsida ja hallata faili, mis asub kindlalt määratud kaustas. Toetab failide redakteerimist, kaustade navigeerimist ja sisu otsimist.",
|
||||
learnMore: "Lisateabe saamiseks, kuidas seda oskust kasutada",
|
||||
configuration: "Konfiguratsioon",
|
||||
readActions: "Leia toimingud",
|
||||
writeActions: "Toimingud",
|
||||
warning:
|
||||
"Failisüsteemi juurimine võib olla ohtlik, kuna see võib muuta või kustutada faile. Enne selle aktiveerimist, palun vaadake <link>dokumentatsiooni</link>.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Ava fail",
|
||||
description:
|
||||
"Leia failide sisu (tekst, kood, PDF-failid, pildid jne)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Lugege mitut faili",
|
||||
description: "Lugege mitut faili üheaegselt",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Loend",
|
||||
description: "Looge failide ja kaustade loend ühes kaustas",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Failide otsimine",
|
||||
description: "Leidke failid nime või sisu järgi",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Hankige faili teave",
|
||||
description: "Hankige üksikasjalik teavet failide kohta",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Faili loomine",
|
||||
description: "Loo uusi faili või asenda olemasoleva faili",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Faili redigeerimine",
|
||||
description: "Muuda teksti failide sisu rida- järgselt.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Loo kaust",
|
||||
description: "Loo uusi kahteid",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Faili liiguta/nime muuda",
|
||||
description: "Liigu või nime muuta failid ja kaardid",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Kopeeri fail",
|
||||
description: "Kopeeri failid ja kaardi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP-serverid",
|
||||
|
||||
@@ -19,7 +19,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "به",
|
||||
getStarted: "شروع کنید",
|
||||
welcome: null,
|
||||
welcome: "به شما خوش آمد میگوییم",
|
||||
},
|
||||
llm: {
|
||||
title: "ترجیحات مدلهای زبان بزرگ",
|
||||
@@ -327,6 +327,59 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"به طور پیشفرض، این قابلیت فعال است، اما میتوانید آن را غیرفعال کنید اگر نمیخواهید این قابلیت برای نمایندگی در دسترس باشد.",
|
||||
filesystem: {
|
||||
title: "دسترسی به سیستم فایل",
|
||||
description:
|
||||
"به نماینده خود اجازه دهید تا فایلها را در یک پوشه مشخص، خوانده، نوشته، جستجو و مدیریت کند. این قابلیت شامل ویرایش فایلها، پیمایش در پوشهها و جستجوی محتوا است.",
|
||||
learnMore: "در مورد نحوه استفاده از این مهارت بیشتر بدانید.",
|
||||
configuration: "تنظیمات",
|
||||
readActions: "خواندن اقدامات",
|
||||
writeActions: "اقدامات",
|
||||
warning:
|
||||
"دسترسی به سیستم فایل میتواند خطرناک باشد، زیرا میتواند فایلها را تغییر دهد یا حذف کند. لطفاً قبل از فعالسازی، مستندات مربوطه را مطالعه کنید.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "خواندن فایل",
|
||||
description: "خواندن محتوای فایلها (متن، کد، PDF، تصاویر و غیره)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "خواندن چندین فایل",
|
||||
description: "خواندن چندین فایل به صورت همزمان",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "فهرست مخاطبین",
|
||||
description: "فایلها و پوشهها را در یک پوشه فهرست کنید.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "جستجو در فایلها",
|
||||
description: "جستجو بر اساس نام یا محتوای فایلها",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "اطلاعات فایل را دریافت کنید",
|
||||
description: "اطلاعات دقیق در مورد فایلها را دریافت کنید.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "ایجاد فایل",
|
||||
description: "ایجاد فایلهای جدید یا جایگزینی فایلهای موجود",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "ویرایش فایل",
|
||||
description: "امکان ویرایش متون از طریق ویرایش خط به خط",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "ایجاد پوشه",
|
||||
description: "ایجاد دایرکتوریهای جدید",
|
||||
},
|
||||
"move-file": {
|
||||
title: "انتقال/تغییر نام فایل",
|
||||
description: "فایلها و پوشهها را جابجا یا تغییر نام دهید.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "کپی فایل",
|
||||
description: "فایلها و دایرکتوریها را کپی کنید.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "سرورهای MCP",
|
||||
|
||||
@@ -18,7 +18,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Bienvenue",
|
||||
getStarted: "Commencer",
|
||||
welcome: null,
|
||||
welcome: "Bienvenue",
|
||||
},
|
||||
llm: {
|
||||
title: "Préférence LLM",
|
||||
@@ -328,6 +328,64 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Par défaut, cette fonctionnalité est activée, mais vous pouvez la désactiver si vous ne souhaitez pas qu'elle soit disponible pour l'agent.",
|
||||
filesystem: {
|
||||
title: "Accès au système de fichiers",
|
||||
description:
|
||||
"Permettez à votre agent de lire, écrire, rechercher et gérer des fichiers dans un répertoire spécifié. Prend en charge la modification de fichiers, la navigation dans les répertoires et la recherche de contenu.",
|
||||
learnMore: "En savoir plus sur la manière d'utiliser cette compétence.",
|
||||
configuration: "Configuration",
|
||||
readActions: "Lire les actions",
|
||||
writeActions: "Actions à effectuer",
|
||||
warning:
|
||||
"L'accès au système de fichiers peut être dangereux, car il peut modifier ou supprimer des fichiers. Veuillez consulter la <link>documentation</link> avant de l'activer.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Ouvrir le fichier",
|
||||
description:
|
||||
"Lire le contenu des fichiers (texte, code, PDF, images, etc.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Lire plusieurs fichiers",
|
||||
description: "Lire plusieurs fichiers simultanément.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Annuaire",
|
||||
description:
|
||||
"Énumérer les fichiers et les répertoires d'un dossier.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Rechercher des fichiers",
|
||||
description: "Rechercher des fichiers par nom ou par contenu",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Obtenir des informations sur le fichier",
|
||||
description: "Obtenez des métadonnées détaillées sur les fichiers.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Créer un fichier",
|
||||
description:
|
||||
"Créer de nouveaux fichiers ou remplacer des fichiers existants.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Modifier le fichier",
|
||||
description:
|
||||
"Effectuez des modifications basées sur des lignes dans les fichiers de texte.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Créer un répertoire",
|
||||
description: "Créer de nouveaux répertoires",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Déplacer/Renommer le fichier",
|
||||
description:
|
||||
"Déplacez ou renommez des fichiers et des répertoires.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Copier le fichier",
|
||||
description: "Copier des fichiers et des répertoires",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Serveurs MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "ברוכים הבאים ל",
|
||||
getStarted: "להתחלה",
|
||||
welcome: null,
|
||||
welcome: "ברוכים הבאים",
|
||||
},
|
||||
llm: {
|
||||
title: "העדפות מודל שפה (LLM)",
|
||||
@@ -326,6 +326,59 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"כברירת מחדל, הכישורים הזה מופעל, אך ניתן להשבית אותו אם אינכם רוצים שהוא יהיה זמין עבור הסוכן.",
|
||||
filesystem: {
|
||||
title: "גישה למערכת הקבצים",
|
||||
description:
|
||||
"אפשרו למתווך שלכם לקרוא, לכתוב, לחפש ולנהל קבצים בספריית מסוימת. תומך בעריכת קבצים, ניווט בספריות וחיפוש תוכן.",
|
||||
learnMore: "למידע נוסף על השימוש בכישרון זה",
|
||||
configuration: "הגדרות",
|
||||
readActions: "קריאת פעולות",
|
||||
writeActions: "פעולות",
|
||||
warning:
|
||||
"גישה למערכת הקבצים עלולה להיות מסוכנת, שכן היא עלולה לשנות או למחוק קבצים. אנא התייעצו עם ה<link>תיעוד</link> לפני הפעלתה.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "קרא קובץ",
|
||||
description: "קריאת תוכן קבצים (טקסט, קוד, PDF, תמונות וכו')",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "קריאת מספר קבצים",
|
||||
description: "קרא מספר קבצים בו זמנית.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "רשימת אנשי קשר",
|
||||
description: "רשימת קבצים וספריות בתיקייה",
|
||||
},
|
||||
"search-files": {
|
||||
title: "חיפוש קבצים",
|
||||
description: "חיפוש קבצים לפי שם או תוכן",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "קבל מידע על הקובץ",
|
||||
description: "קבל מידע מפורט על קבצים",
|
||||
},
|
||||
"write-file": {
|
||||
title: "יצירת קובץ",
|
||||
description: "יצירת קבצים חדשים או החלפת קבצים קיימים",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "ערוך קובץ",
|
||||
description: "בצעו עריכה של קבצי טקסט על בסיס שורות.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "יצירת תיקייה",
|
||||
description: "ליצור תיקיות חדשות",
|
||||
},
|
||||
"move-file": {
|
||||
title: "העתקה/שינוי שם של קובץ",
|
||||
description: "הזיזו או שנו את שמות הקבצים והתיקיות.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "העתק קובץ",
|
||||
description: "העתקת קבצים וספריות",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "שרתי MCP",
|
||||
|
||||
@@ -19,7 +19,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Benvenuti a",
|
||||
getStarted: "Inizia",
|
||||
welcome: null,
|
||||
welcome: "Benvenuti",
|
||||
},
|
||||
llm: {
|
||||
title: "Preferenza per i modelli LLM",
|
||||
@@ -331,6 +331,61 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Per impostazione predefinita, questa funzionalità è attiva, ma è possibile disabilitarla se non si desidera che sia disponibile per l'agente.",
|
||||
filesystem: {
|
||||
title: "Accesso al file system",
|
||||
description:
|
||||
"Permetti al tuo agente di leggere, scrivere, cercare e gestire file all'interno di una directory designata. Supporta la modifica dei file, la navigazione nelle directory e la ricerca di contenuti.",
|
||||
learnMore: "Scopri di più su come utilizzare questa competenza.",
|
||||
configuration: "Configurazione",
|
||||
readActions: "Leggi le azioni",
|
||||
writeActions: "Azioni da eseguire",
|
||||
warning:
|
||||
"L'accesso al file system può essere pericoloso, in quanto può modificare o eliminare file. Si prega di consultare la <link>documentazione</link> prima di abilitarlo.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Apri file",
|
||||
description:
|
||||
"Leggi il contenuto dei file (testo, codice, PDF, immagini, ecc.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Leggi più file",
|
||||
description: "Apri e leggi più file contemporaneamente.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Elenco di contatti",
|
||||
description:
|
||||
"Elenca i file e le directory all'interno di una cartella.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Cerca file",
|
||||
description: "Cerca file per nome o contenuto",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Ottieni informazioni sul file",
|
||||
description: "Ottenere metadati dettagliati sui file.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Creare file",
|
||||
description: "Creare nuovi file o sovrascrivere i file esistenti.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Modifica file",
|
||||
description: "Applica modifiche basate su righe ai file di testo.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Creare una directory",
|
||||
description: "Creare nuove directory",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Sposta/Rinomina file",
|
||||
description: "Spostare o rinominare file e directory.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Copia file",
|
||||
description: "Copia file e directory",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Server MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "ようこそ",
|
||||
getStarted: "はじめる",
|
||||
welcome: null,
|
||||
welcome: "ようこそ",
|
||||
},
|
||||
llm: {
|
||||
title: "LLMの設定",
|
||||
@@ -323,6 +323,61 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"デフォルトでは、この機能は有効になっていますが、エージェントに利用させたくない場合は、無効にすることができます。",
|
||||
filesystem: {
|
||||
title: "ファイルシステムのアクセス",
|
||||
description:
|
||||
"エージェントが、指定されたディレクトリ内のファイルを読む、書き、検索、および管理できるようにします。ファイル編集、ディレクトリのナビゲーション、およびコンテンツ検索をサポートします。",
|
||||
learnMore: "このスキルの使い方について、さらに詳しく知る",
|
||||
configuration: "設定",
|
||||
readActions: "行動",
|
||||
writeActions: "行動",
|
||||
warning:
|
||||
"ファイルシステムへのアクセスは危険であり、ファイルの内容を変更または削除する可能性があります。設定する前に、必ず<link>のドキュメント</link>を参照してください。",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "ファイルを開く",
|
||||
description:
|
||||
"ファイル(テキスト、コード、PDF、画像など)の内容を読み込む。",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "複数のファイルを読み込む",
|
||||
description: "複数のファイルを同時に読み込む",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "ディレクトリ一覧",
|
||||
description: "フォルダ内のファイルとディレクトリの一覧を表示する",
|
||||
},
|
||||
"search-files": {
|
||||
title: "ファイル検索",
|
||||
description: "ファイル名または内容で検索する",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "ファイルの情報を取得する",
|
||||
description: "ファイルに関する詳細なメタデータを取得する",
|
||||
},
|
||||
"write-file": {
|
||||
title: "ファイルを作成",
|
||||
description:
|
||||
"新しいファイルを作成するか、既存のファイルを上書きする",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "ファイル編集",
|
||||
description: "テキストファイルの行単位での編集を行う",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "ディレクトリを作成する",
|
||||
description: "新しいディレクトリを作成する",
|
||||
},
|
||||
"move-file": {
|
||||
title: "ファイル/ファイル名の変更",
|
||||
description: "ファイルやディレクトリを移動または名前を変更する",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "ファイルのコピー",
|
||||
description: "ファイルとディレクトリをコピーする",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP サーバー",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "방문을 환영합니다",
|
||||
getStarted: "시작하기",
|
||||
welcome: null,
|
||||
welcome: "환영합니다",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM 기본 설정",
|
||||
@@ -327,6 +327,60 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"기본적으로 이 기능은 활성화되어 있지만, 에이전트에게 이 기능을 사용하지 않도록 설정할 수도 있습니다.",
|
||||
filesystem: {
|
||||
title: "파일 시스템 접근",
|
||||
description:
|
||||
"제 에이전트가 지정된 디렉토리 내에서 파일을 읽고, 쓰고, 검색하고, 관리할 수 있도록 합니다. 파일 편집, 디렉토리 탐색, 콘텐츠 검색을 지원합니다.",
|
||||
learnMore: "이 기술을 사용하는 방법에 대해 자세히 알아보세요.",
|
||||
configuration: "구성",
|
||||
readActions: "실행 내용 보기",
|
||||
writeActions: "실행 내용",
|
||||
warning:
|
||||
"파일 시스템 접근은 위험할 수 있습니다. 왜냐하면 파일 내용을 변경하거나 삭제할 수 있기 때문입니다. 사용하기 전에 반드시 <link>문서</link>를 참조하십시오.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "파일 읽기",
|
||||
description:
|
||||
"파일(텍스트, 코드, PDF, 이미지 등)의 내용을 읽습니다.",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "여러 파일을 읽기",
|
||||
description: "여러 파일을 한 번에 읽기",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "디렉토리 목록",
|
||||
description: "폴더 내의 파일 및 디렉터리 목록 보기",
|
||||
},
|
||||
"search-files": {
|
||||
title: "파일 검색",
|
||||
description: "이름 또는 내용으로 파일을 검색",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "파일 정보 확인",
|
||||
description: "파일에 대한 자세한 메타데이터를 얻으세요.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "파일 작성",
|
||||
description: "새로운 파일을 생성하거나 기존 파일을 덮어쓰기",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "파일 편집",
|
||||
description: "텍스트 파일에 줄 단위로 편집",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "디렉토리 생성",
|
||||
description: "새로운 디렉토리를 생성합니다.",
|
||||
},
|
||||
"move-file": {
|
||||
title: "파일 이동/이름 변경",
|
||||
description: "파일 및 폴더를 이동하거나 이름을 변경합니다.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "파일 복사",
|
||||
description: "파일 및 디렉터리를 복사",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP 서버",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Sveiki atvykę į",
|
||||
getStarted: "Pradėti",
|
||||
welcome: null,
|
||||
welcome: "Sveiki",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM pasirinkimas",
|
||||
@@ -340,6 +340,60 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Pagal numatytuosius nustatymus šis įgūdis yra įjungtas, bet galite jį išjungti, jei nenorite, kad jis būtų prieinamas agentui.",
|
||||
filesystem: {
|
||||
title: "Failų sistemos prieigos teisės",
|
||||
description:
|
||||
"Leiskite savo agentui skaityti, rašyti, ieškoti ir valdomi failus nustatytame kataloge. Paremiama failų redagavimas, katalogų navigacija ir turinio paieška.",
|
||||
learnMore: "Sužinokite daugiau apie tai, kaip naudoti šią įgūdį.",
|
||||
configuration: "Konfigūracija",
|
||||
readActions: "Veikimas",
|
||||
writeActions: "Veikimas",
|
||||
warning:
|
||||
"Failų sistemos prieigos vartymas gali būti pavojus, nes gali modifikuoti arba ištrinti failus. Prašome, prieš įgalindami, pasikonsultuoti su <link>dokumentacija</link>.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Atidaryti failą",
|
||||
description:
|
||||
"Peržiūrėti failų turinį (tekstą, kodą, PDF, vaizdus ir kt.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Atidarykite kelis failus",
|
||||
description: "Galia, skaitykite kelis failus vienu metu.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Pašalinis katalogas",
|
||||
description: "Parodykite failus ir katalogus, esančius sąvade",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Paieškos failus",
|
||||
description: "Paieškokite failus pagal pavadinimą arba turinį",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Gaukite failo informaciją",
|
||||
description: "Gaukite išsamią informaciją apie failus.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Sukurti failą",
|
||||
description: "Sukurti naujus failus arba pakeisti esamus",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Redaguoti failą",
|
||||
description: "Atlikite teksto failų redakciją, remdamiesi eilėmis.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Sukurti katalogą",
|
||||
description: "Sukurti naujas katalogus",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Perkelti/Pavadinimą failą",
|
||||
description: "Perkelti arba pervardinti failus ir katalogus",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Kopijuoti failą",
|
||||
description: "Kopijuoti failus ir katalogus",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP serveriai",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Laipni lūgti",
|
||||
getStarted: "Sākt darbu",
|
||||
welcome: null,
|
||||
welcome: "Laipni lūdzam",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM preferences",
|
||||
@@ -332,6 +332,62 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Par iestatījumu, šī spēja ir aktivizēta, taču jūs varat to izslēgt, ja nevēlaties, lai tā būtu pieejama aģentam.",
|
||||
filesystem: {
|
||||
title: "Failu sistēmas piekļuves tiesības",
|
||||
description:
|
||||
"Iespējiet, lai jūsu pārstāvis varētu lasīt, rakstīt, meklēt un pārvaldīt failus noteiktā direktorijā. Atbalsta failu rediģēšanu, direktoriju navigāciju un satura meklēšanu.",
|
||||
learnMore: "Uzziniet vairāk par to, kā izmantot šo prasmi",
|
||||
configuration: "Konfigurācija",
|
||||
readActions: "Lasīt",
|
||||
writeActions: "Rīcības",
|
||||
warning:
|
||||
"Pieejums failu sistēmai var būt bīstams, jo tas var mainīt vai dzēst failus. Lūdzu, konsultējieties ar <link>dokumentāciju</link> pirms aktivizēšanas.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Atvērt failu",
|
||||
description:
|
||||
"Izlasiet failu saturu (tekstus, kodu, PDF failus, attēlus utt.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Izlasīt vairākus failus",
|
||||
description: "Lasi vairākus failus vienlaikus.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Saraksta direktorijs",
|
||||
description:
|
||||
"Izveidot failu un direktoru sarakstu ievietotajā mapē",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Meklēt failus",
|
||||
description: "Meklē failus pēc nosaukuma vai satura",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Iegūst faila informāciju",
|
||||
description: "Iesaļojiet detalizētus failu metadatus",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Izveidot failu",
|
||||
description: "Izveidot jaunas failus vai pārrakstīt esošus failus",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Rediģēt failu",
|
||||
description:
|
||||
"Veiciet teksta failu rediģēšanu, izmantojot rindu bāzes metodi.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Izveidot direktoriju",
|
||||
description: "Izveidot jaunas direktorijas",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Pārvietot/Vārdēt failu",
|
||||
description: "Vāc vai pārdzen failus un direktorijus",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Kopēt failu",
|
||||
description: "Kopēt failus un direktorus",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP serveri",
|
||||
|
||||
@@ -19,7 +19,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Welkom bij",
|
||||
getStarted: "Aan de slag",
|
||||
welcome: null,
|
||||
welcome: "Welkom",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM-voorkeuren",
|
||||
@@ -327,6 +327,63 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Standaard is deze functie ingeschakeld, maar u kunt deze uitschakelen als u niet wilt dat de agent er gebruik van kan maken.",
|
||||
filesystem: {
|
||||
title: "Toegang tot het bestandssysteem",
|
||||
description:
|
||||
"Geef uw agent de mogelijkheid om bestanden te lezen, te schrijven, te zoeken en te beheren binnen een aangewezen map. Ondersteunt het bewerken van bestanden, het navigeren door mappen en het zoeken naar inhoud.",
|
||||
learnMore:
|
||||
"Meer informatie over hoe u deze vaardigheid kunt toepassen.",
|
||||
configuration: "Configuratie",
|
||||
readActions: "Lees acties",
|
||||
writeActions: "Schrijf acties",
|
||||
warning:
|
||||
"Toegang tot het bestandssysteem kan gevaarlijk zijn, omdat het bestanden kan wijzigen of verwijderen. Raadpleeg de <link>documentatie</link> voordat u dit activeert.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Bestand openen",
|
||||
description:
|
||||
"Lees de inhoud van bestanden (tekst, code, PDF, afbeeldingen, enz.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Lees meerdere bestanden",
|
||||
description: "Lees meerdere bestanden tegelijkertijd.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Lijst met contactgegevens",
|
||||
description:
|
||||
"Maak een lijst van bestanden en mappen binnen een map.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Bestanden zoeken",
|
||||
description: "Zoek naar bestanden op naam of inhoud",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Fijlsinformatie bekijken",
|
||||
description: "Verkrijg gedetailleerde metadata over bestanden.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Schrijf bestand",
|
||||
description:
|
||||
"Maak nieuwe bestanden aan of vervang bestaande bestanden.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Bestand bewerken",
|
||||
description: "Voer wijzigingen uit op tekstbestanden, per regel.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Maak een directory",
|
||||
description: "Maak nieuwe mappen aan",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Verplaats/Hernoem bestand",
|
||||
description: "Verplaats of wijzig de naam van bestanden en mappen.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Kopieer bestand",
|
||||
description: "Kopieer bestanden en mappen",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP-servers",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Witamy w",
|
||||
getStarted: "Rozpocznij",
|
||||
welcome: null,
|
||||
welcome: "Witaj",
|
||||
},
|
||||
llm: {
|
||||
title: "Preferencje modeli językowych",
|
||||
@@ -333,6 +333,62 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Domyślnie, ta umiejętność jest włączona, ale można ją wyłączyć, jeśli nie chcemy, aby była dostępna dla agenta.",
|
||||
filesystem: {
|
||||
title: "Dostęp do systemu plików",
|
||||
description:
|
||||
"Pozwól swoim agentom na odczytywanie, zapisywanie, wyszukiwanie i zarządzanie plikami w określonym katalogu. Obsługuje edycję plików, nawigację po katalogach oraz wyszukiwanie zawartości.",
|
||||
learnMore:
|
||||
"Dowiedz się więcej na temat tego, jak wykorzystać tę umiejętność.",
|
||||
configuration: "Konfiguracja",
|
||||
readActions: "Czytać akcje",
|
||||
writeActions: "Działania",
|
||||
warning:
|
||||
"Dostęp do systemu plików może być niebezpieczny, ponieważ może modyfikować lub usuwać pliki. Prosimy o zapoznanie się z dokumentacją <link> przed włączeniem tej funkcji.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Otwórz plik",
|
||||
description:
|
||||
"Otwórz i przeczytaj zawartość plików (tekst, kod, pliki PDF, obrazy itp.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Odczytaj wiele plików",
|
||||
description: "Otwórz i przetwórz wiele plików jednocześnie.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Lista kontaktów",
|
||||
description: "Wyświetl pliki i katalogi w określonym folderze.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Wyszukaj pliki",
|
||||
description: "Wyszukaj pliki według nazwy lub zawartości",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Pobierz informacje o pliku",
|
||||
description: "Uzyskaj szczegółowe metadane dotyczące plików.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Utwórz plik",
|
||||
description: "Utwórz nowe pliki lub nadpisz istniejące",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Edytuj plik",
|
||||
description:
|
||||
"Wprowadzaj zmiany w plikach tekstowych, działając w oparciu o linie.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Utwórz katalog",
|
||||
description: "Utwórz nowe katalogi",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Przenieś/Przekształć nazwę pliku",
|
||||
description: "Przenieś lub zmień nazwę plików i katalogów",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Skopiuj plik",
|
||||
description: "Kopiuj pliki i katalogi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Serwery MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Bem-vindo ao",
|
||||
getStarted: "Começar",
|
||||
welcome: null,
|
||||
welcome: "Bem-vindo",
|
||||
},
|
||||
llm: {
|
||||
title: "Preferência de LLM",
|
||||
@@ -331,6 +331,62 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Por padrão, essa habilidade está ativada, mas você pode desativá-la se não quiser que ela esteja disponível para o agente.",
|
||||
filesystem: {
|
||||
title: "Acesso ao Sistema de Arquivos",
|
||||
description:
|
||||
"Permita que seu agente leia, grave, procure e gerencie arquivos dentro de um diretório específico. Suporta a edição de arquivos, a navegação em diretórios e a pesquisa de conteúdo.",
|
||||
learnMore: "Saiba mais sobre como utilizar esta habilidade.",
|
||||
configuration: "Configuração",
|
||||
readActions: "Ler ações",
|
||||
writeActions: "Ações a serem executadas",
|
||||
warning:
|
||||
"O acesso ao sistema de arquivos pode ser perigoso, pois pode modificar ou excluir arquivos. Por favor, consulte a <link>documentação</link> antes de habilitar.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Abrir arquivo",
|
||||
description:
|
||||
"Ler o conteúdo de arquivos (texto, código, PDF, imagens, etc.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Ler Vários Arquivos",
|
||||
description: "Leia vários arquivos simultaneamente.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Lista de diretórios",
|
||||
description: "Liste os arquivos e diretórios em uma pasta.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Pesquisar arquivos",
|
||||
description: "Pesquise arquivos por nome ou conteúdo.",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Obter informações do arquivo",
|
||||
description: "Obtenha metadados detalhados sobre os arquivos.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Criar arquivo",
|
||||
description:
|
||||
"Criar novos arquivos ou substituir arquivos existentes.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Editar arquivo",
|
||||
description:
|
||||
"Realize edições baseadas em linhas em arquivos de texto.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Criar Diretório",
|
||||
description: "Criar novas pastas/diretórios",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Mover/Renomear arquivo",
|
||||
description: "Mova ou renomeie arquivos e diretórios.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Copiar arquivo",
|
||||
description: "Copie arquivos e diretórios",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Servidores MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Bine ai venit la",
|
||||
getStarted: "Începe",
|
||||
welcome: null,
|
||||
welcome: "Bine ați venit",
|
||||
},
|
||||
llm: {
|
||||
title: "Preferința LLM",
|
||||
@@ -784,6 +784,62 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Implicit, această funcție este activată, dar puteți dezactiva-o dacă nu doriți ca agentul să o utilizeze.",
|
||||
filesystem: {
|
||||
title: "Acces la sistemul de fișiere",
|
||||
description:
|
||||
"Permite reprezentantului dumneavoastră să citească, să scrie, să caute și să gestioneze fișiere într-un director specific. Suportă editarea fișierelor, navigarea în director și căutarea conținutului.",
|
||||
learnMore:
|
||||
"Aflați mai multe despre cum să utilizați această abilitate.",
|
||||
configuration: "Configurare",
|
||||
readActions: "Cite",
|
||||
writeActions: "Acțiuni",
|
||||
warning:
|
||||
"Accesul la sistemul de fișiere poate fi periculos, deoarece poate modifica sau șterge fișiere. Vă rugăm să consultați documentația <link>înainte de a-l activa.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Citește fișierul",
|
||||
description:
|
||||
"Citează conținutul fișierelor (text, cod, PDF, imagini, etc.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Citește mai multe fișiere",
|
||||
description: "Citiți mai multe fișiere simultan",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Lista de contacte",
|
||||
description: "Enumeră fișierele și directoarele dintr-un folder",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Caută fișiere",
|
||||
description: "Căutați fișiere după nume sau conținut",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Obține informații despre fișier",
|
||||
description: "Obțineți metadate detaliate despre fișiere.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Creați fișier",
|
||||
description:
|
||||
"Creați fișiere noi sau suprascrieți fișierele existente.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Modifică fișierul",
|
||||
description: "Realizați modificări pe linii în fișierele de text.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Creați o directoare",
|
||||
description: "Creați noi directoare",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Mută/Redenumirea fișierului",
|
||||
description: "Mută sau redenumește fișierele și directoarele.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Copiază fișier",
|
||||
description: "Copiați fișiere și directoare",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Servere MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Добро пожаловать в",
|
||||
getStarted: "Начать",
|
||||
welcome: null,
|
||||
welcome: "Добро пожаловать",
|
||||
},
|
||||
llm: {
|
||||
title: "Предпочитаемые LLM",
|
||||
@@ -326,6 +326,61 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"По умолчанию, эта функция включена, но вы можете отключить ее, если не хотите, чтобы она была доступна для агента.",
|
||||
filesystem: {
|
||||
title: "Доступ к файловой системе",
|
||||
description:
|
||||
"Предоставьте вашему агенту возможность читать, создавать, искать и управлять файлами в определенной директории. Поддерживает редактирование файлов, навигацию по директориям и поиск содержимого.",
|
||||
learnMore: "Узнайте больше о том, как использовать этот навык.",
|
||||
configuration: "Настройка",
|
||||
readActions: "Прочитать действия",
|
||||
writeActions: "Определить действия",
|
||||
warning:
|
||||
"Доступ к файловой системе может быть опасным, так как он может изменять или удалять файлы. Пожалуйста, ознакомьтесь с <link>документацией</link> перед включением.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Открыть файл",
|
||||
description:
|
||||
"Прочитать содержимое файлов (текст, код, PDF, изображения и т.д.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Открыть несколько файлов",
|
||||
description: "Одновременно читать несколько файлов.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Список каталогов",
|
||||
description: "Перечислите файлы и каталоги в папке.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Поиск файлов",
|
||||
description: "Поиск файлов по имени или содержимому",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Получить информацию о файле",
|
||||
description: "Получите подробную информацию о метаданных файлов.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Создать файл",
|
||||
description: "Создать новые файлы или перезаписать существующие",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Редактировать файл",
|
||||
description:
|
||||
"Внесите изменения в текстовые файлы, работая построчно.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Создать папку",
|
||||
description: "Создать новые каталоги",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Переместить/Переименовать файл",
|
||||
description: "Переместите или переименуйте файлы и каталоги.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Скопировать файл",
|
||||
description: "Копировать файлы и каталоги",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Серверы MCP",
|
||||
|
||||
@@ -19,7 +19,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Hoş Geldiniz",
|
||||
getStarted: "Başla",
|
||||
welcome: null,
|
||||
welcome: "Hoş geldiniz",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM Tercihi",
|
||||
@@ -326,6 +326,63 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Varsayılan olarak bu özellik etkinleştirilmiştir, ancak ajanın kullanmasına izin vermek istemiyorsanız, bu özelliği devre dışı bırakabilirsiniz.",
|
||||
filesystem: {
|
||||
title: "Dosya Sistemi Erişimi",
|
||||
description:
|
||||
"Temsilcinizin, belirli bir klasör içindeki dosyaları okuma, yazma, arama ve yönetme yeteneğini etkinleştirin. Dosya düzenleme, klasör gezinme ve içerik arama özelliklerini destekler.",
|
||||
learnMore:
|
||||
"Bu beceriye nasıl başlanacağını ve nasıl kullanılacağını daha detaylı bir şekilde öğrenin.",
|
||||
configuration: "Yapılandırma",
|
||||
readActions: "Okunmuş Eylemler",
|
||||
writeActions: "Yapılacak İşler",
|
||||
warning:
|
||||
"Dosya sistemine erişim tehlikeli olabilir, çünkü dosyaları değiştirebilir veya silebilir. Bu özelliği etkinleştirmeden önce lütfen <link>belgelendirme</link>'i inceleyin.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Dosyayı aç",
|
||||
description:
|
||||
"Dosyalardaki içeriği okuyun (metin, kod, PDF, resimler vb.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Birden fazla dosyayı okuyun",
|
||||
description: "Birden fazla dosyayı aynı anda okuyun",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Yönerge Listesi",
|
||||
description: "Bir klasördeki dosyaları ve dizinleri listeleyin.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Dosyaları Arayın",
|
||||
description: "Dosyaları adlarına veya içeriğine göre arayın",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Dosya Hakkında Bilgi Al",
|
||||
description: "Dosyalara ilişkin ayrıntılı meta verileri elde edin.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Dosya Oluştur",
|
||||
description:
|
||||
"Yeni dosyalar oluşturun veya mevcut dosyaları üzerine yazın.",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Dosya Düzenle",
|
||||
description: "Metin dosyalarında satır bazlı değişiklikler yapın.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Klasör Oluştur",
|
||||
description: "Yeni klasörler oluşturun",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Dosya taşı/yeniden adlandır",
|
||||
description:
|
||||
"Dosyaları ve dizinleri taşıyın veya yeniden adlandırın.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Dosyayı Kopyala",
|
||||
description: "Dosyaları ve dizinleri kopyala",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP Sunucuları",
|
||||
|
||||
@@ -19,7 +19,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "Chào mừng đến",
|
||||
getStarted: "Bắt đầu",
|
||||
welcome: null,
|
||||
welcome: "Chào mừng",
|
||||
},
|
||||
llm: {
|
||||
title: "Tùy chọn LLM",
|
||||
@@ -325,6 +325,61 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"Theo mặc định, kỹ năng này được kích hoạt, nhưng bạn có thể tắt nó nếu không muốn nó được sử dụng bởi người đại diện.",
|
||||
filesystem: {
|
||||
title: "Quyền truy cập hệ thống tệp",
|
||||
description:
|
||||
"Cho phép đại lý của bạn đọc, ghi, tìm kiếm và quản lý các tệp tin trong một thư mục được chỉ định. Hỗ trợ chỉnh sửa tệp, điều hướng thư mục và tìm kiếm nội dung.",
|
||||
learnMore: "Tìm hiểu thêm về cách sử dụng kỹ năng này.",
|
||||
configuration: "Cấu hình",
|
||||
readActions: "Đọc hành động",
|
||||
writeActions: "Các hành động",
|
||||
warning:
|
||||
"Việc truy cập hệ thống tệp có thể gây nguy hiểm vì nó có thể sửa đổi hoặc xóa các tệp. Vui lòng tham khảo tài liệu <link> trước khi kích hoạt.",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "Đọc tệp",
|
||||
description:
|
||||
"Đọc nội dung của các tệp (văn bản, mã, PDF, hình ảnh, v.v.)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "Đọc nhiều tệp",
|
||||
description: "Đọc nhiều tệp tin cùng lúc.",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "Danh sách",
|
||||
description: "Liệt kê các tệp tin và thư mục trong một thư mục.",
|
||||
},
|
||||
"search-files": {
|
||||
title: "Tìm kiếm tệp",
|
||||
description: "Tìm kiếm các tệp theo tên hoặc nội dung",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "Lấy thông tin tệp",
|
||||
description: "Lấy thông tin chi tiết về các tệp tin.",
|
||||
},
|
||||
"write-file": {
|
||||
title: "Tạo tệp",
|
||||
description: "Tạo các tệp mới hoặc ghi đè các tệp hiện có",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "Chỉnh sửa tệp",
|
||||
description:
|
||||
"Thực hiện chỉnh sửa dựa trên dòng trong các tệp văn bản.",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "Tạo thư mục",
|
||||
description: "Tạo thư mục mới",
|
||||
},
|
||||
"move-file": {
|
||||
title: "Di chuyển/Đổi tên tệp",
|
||||
description: "Di chuyển hoặc đổi tên các tệp và thư mục.",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "Sao chép tệp",
|
||||
description: "Sao chép các tệp tin và thư mục",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "Máy chủ MCP",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "欢迎使用",
|
||||
getStarted: "开始",
|
||||
welcome: null,
|
||||
welcome: "欢迎",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM 偏好",
|
||||
@@ -317,6 +317,59 @@ const TRANSLATIONS = {
|
||||
},
|
||||
default_skill:
|
||||
"默认情况下,这项技能已启用。但是,如果您不想让该技能被代理使用,您可以将其禁用。",
|
||||
filesystem: {
|
||||
title: "文件系统访问",
|
||||
description:
|
||||
"允许您的代理能够读取、写入、搜索和管理指定目录中的文件。 支持文件编辑、目录导航和内容搜索功能。",
|
||||
learnMore: "了解更多关于如何使用这项技能的信息。",
|
||||
configuration: "配置",
|
||||
readActions: "阅读操作",
|
||||
writeActions: "编写操作",
|
||||
warning:
|
||||
"访问文件系统可能存在风险,因为它可能修改或删除文件。在启用之前,请务必查阅<link>文档</link>。",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "读取文件",
|
||||
description: "读取文件内容(包括文本、代码、PDF、图像等)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "读取多个文件",
|
||||
description: "同时读取多个文件",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "目录",
|
||||
description: "列出文件夹中的文件和目录",
|
||||
},
|
||||
"search-files": {
|
||||
title: "搜索文件",
|
||||
description: "按文件名或内容搜索文件",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "获取文件信息",
|
||||
description: "获取有关文件的详细元数据",
|
||||
},
|
||||
"write-file": {
|
||||
title: "创建文件",
|
||||
description: "创建新的文件或覆盖现有文件",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "编辑文件",
|
||||
description: "对文本文件进行基于行的编辑。",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "创建目录",
|
||||
description: "创建新的目录",
|
||||
},
|
||||
"move-file": {
|
||||
title: "移动/重命名文件",
|
||||
description: "移动或重命名文件和目录",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "复制文件",
|
||||
description: "复制文件和目录",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP 服务器",
|
||||
|
||||
@@ -4,7 +4,7 @@ const TRANSLATIONS = {
|
||||
home: {
|
||||
title: "歡迎使用",
|
||||
getStarted: "開始使用",
|
||||
welcome: null,
|
||||
welcome: "歡迎",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM 偏好",
|
||||
@@ -306,6 +306,59 @@ const TRANSLATIONS = {
|
||||
"讓您的智慧代理人能夠利用 SQL 查詢來回答您的問題,只需連接到不同的 SQL 資料庫提供者即可。",
|
||||
},
|
||||
default_skill: "這項技能預設為啟用;若不希望智慧代理人使用,也可以停用。",
|
||||
filesystem: {
|
||||
title: "檔案系統存取",
|
||||
description:
|
||||
"允許您的代理程式在指定目錄中讀取、寫入、搜尋和管理檔案。支援檔案編輯、目錄導航和內容搜尋功能。",
|
||||
learnMore: "了解更多關於如何運用這項技能的資訊",
|
||||
configuration: "設定",
|
||||
readActions: "閱讀行動",
|
||||
writeActions: "撰寫動作",
|
||||
warning:
|
||||
"訪問檔案系統可能存在風險,因為它可能會修改或刪除檔案。在啟用之前,請務必查閱相關<link>文件</link>。",
|
||||
skills: {
|
||||
"read-text-file": {
|
||||
title: "開啟檔案",
|
||||
description: "閱讀檔案內容(包括文字、程式碼、PDF 文件、圖片等)",
|
||||
},
|
||||
"read-multiple-files": {
|
||||
title: "閱讀多個檔案",
|
||||
description: "同時讀取多個檔案",
|
||||
},
|
||||
"list-directory": {
|
||||
title: "名錄索引",
|
||||
description: "列出指定資料夾中的檔案和目錄",
|
||||
},
|
||||
"search-files": {
|
||||
title: "搜尋檔案",
|
||||
description: "按檔案名稱或內容來搜尋",
|
||||
},
|
||||
"get-file-info": {
|
||||
title: "取得檔案資訊",
|
||||
description: "獲取關於檔案的詳細元數據",
|
||||
},
|
||||
"write-file": {
|
||||
title: "儲存檔案",
|
||||
description: "建立新的檔案或覆蓋現有檔案",
|
||||
},
|
||||
"edit-file": {
|
||||
title: "編輯檔案",
|
||||
description: "能夠對文字檔案進行行別編輯。",
|
||||
},
|
||||
"create-directory": {
|
||||
title: "建立資料夾",
|
||||
description: "建立新的資料夾",
|
||||
},
|
||||
"move-file": {
|
||||
title: "移動/更名檔案",
|
||||
description: "移動或更名檔案和資料夾",
|
||||
},
|
||||
"copy-file": {
|
||||
title: "複製檔案",
|
||||
description: "複製檔案和目錄",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
title: "MCP 伺服器",
|
||||
|
||||
BIN
frontend/src/media/agents/file-system.png
Normal file
BIN
frontend/src/media/agents/file-system.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
@@ -833,6 +833,21 @@ const System = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the filesystem-agent skill is available.
|
||||
* The filesystem-agent skill is only available when running in a Docker container.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
isFileSystemAgentAvailable: async function () {
|
||||
return fetch(`${API_BASE}/agent-skills/filesystem-agent/is-available`, {
|
||||
method: "GET",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => res?.available ?? false)
|
||||
.catch(() => false);
|
||||
},
|
||||
|
||||
experimentalFeatures: {
|
||||
liveSync: LiveDocumentSync,
|
||||
agentPlugins: AgentPlugins,
|
||||
|
||||
328
frontend/src/pages/Admin/Agents/FileSystemSkillPanel/index.jsx
Normal file
328
frontend/src/pages/Admin/Agents/FileSystemSkillPanel/index.jsx
Normal file
@@ -0,0 +1,328 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Toggle, { SimpleToggleSwitch } from "@/components/lib/Toggle";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import {
|
||||
Warning,
|
||||
File,
|
||||
Files,
|
||||
PencilSimple,
|
||||
FloppyDisk,
|
||||
FolderPlus,
|
||||
FolderOpen,
|
||||
ArrowsLeftRight,
|
||||
MagnifyingGlass,
|
||||
Info,
|
||||
CircleNotch,
|
||||
Copy,
|
||||
} from "@phosphor-icons/react";
|
||||
import Admin from "@/models/admin";
|
||||
|
||||
const getFileSystemSubSkills = (t) => {
|
||||
return [
|
||||
{
|
||||
name: "filesystem-read-text-file",
|
||||
title: t("agent.skill.filesystem.skills.read-text-file.title"),
|
||||
description: t(
|
||||
"agent.skill.filesystem.skills.read-text-file.description"
|
||||
),
|
||||
icon: File,
|
||||
category: "read",
|
||||
},
|
||||
{
|
||||
name: "filesystem-read-multiple-files",
|
||||
title: t("agent.skill.filesystem.skills.read-multiple-files.title"),
|
||||
description: t(
|
||||
"agent.skill.filesystem.skills.read-multiple-files.description"
|
||||
),
|
||||
icon: Files,
|
||||
category: "read",
|
||||
},
|
||||
{
|
||||
name: "filesystem-list-directory",
|
||||
title: t("agent.skill.filesystem.skills.list-directory.title"),
|
||||
description: t(
|
||||
"agent.skill.filesystem.skills.list-directory.description"
|
||||
),
|
||||
icon: FolderOpen,
|
||||
category: "read",
|
||||
},
|
||||
{
|
||||
name: "filesystem-search-files",
|
||||
title: t("agent.skill.filesystem.skills.search-files.title"),
|
||||
description: t("agent.skill.filesystem.skills.search-files.description"),
|
||||
icon: MagnifyingGlass,
|
||||
category: "read",
|
||||
},
|
||||
{
|
||||
name: "filesystem-get-file-info",
|
||||
title: t("agent.skill.filesystem.skills.get-file-info.title"),
|
||||
description: t("agent.skill.filesystem.skills.get-file-info.description"),
|
||||
icon: Info,
|
||||
category: "read",
|
||||
},
|
||||
{
|
||||
name: "filesystem-write-file",
|
||||
title: t("agent.skill.filesystem.skills.write-file.title"),
|
||||
description: t("agent.skill.filesystem.skills.write-file.description"),
|
||||
icon: FloppyDisk,
|
||||
category: "write",
|
||||
},
|
||||
{
|
||||
name: "filesystem-edit-file",
|
||||
title: t("agent.skill.filesystem.skills.edit-file.title"),
|
||||
description: t("agent.skill.filesystem.skills.edit-file.description"),
|
||||
icon: PencilSimple,
|
||||
category: "write",
|
||||
},
|
||||
{
|
||||
name: "filesystem-create-directory",
|
||||
title: t("agent.skill.filesystem.skills.create-directory.title"),
|
||||
description: t(
|
||||
"agent.skill.filesystem.skills.create-directory.description"
|
||||
),
|
||||
icon: FolderPlus,
|
||||
category: "write",
|
||||
},
|
||||
{
|
||||
name: "filesystem-copy-file",
|
||||
title: t("agent.skill.filesystem.skills.copy-file.title"),
|
||||
description: t("agent.skill.filesystem.skills.copy-file.description"),
|
||||
icon: Copy,
|
||||
category: "write",
|
||||
},
|
||||
{
|
||||
name: "filesystem-move-file",
|
||||
title: t("agent.skill.filesystem.skills.move-file.title"),
|
||||
description: t("agent.skill.filesystem.skills.move-file.description"),
|
||||
icon: ArrowsLeftRight,
|
||||
category: "write",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default function FileSystemSkillPanel({
|
||||
title,
|
||||
skill,
|
||||
toggleSkill,
|
||||
enabled = false,
|
||||
disabled = false,
|
||||
image,
|
||||
icon,
|
||||
setHasChanges,
|
||||
hasChanges = false,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [disabledSubSkills, setDisabledSubSkills] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const prevHasChanges = useRef(hasChanges);
|
||||
const FILESYSTEM_SUB_SKILLS = getFileSystemSubSkills(t);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Admin.systemPreferencesByFields(["disabled_filesystem_skills"])
|
||||
.then((res) =>
|
||||
setDisabledSubSkills(res?.settings?.disabled_filesystem_skills ?? [])
|
||||
)
|
||||
.catch(() => setDisabledSubSkills([]))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevHasChanges.current === true && hasChanges === false) {
|
||||
Admin.systemPreferencesByFields(["disabled_filesystem_skills"])
|
||||
.then((res) =>
|
||||
setDisabledSubSkills(res?.settings?.disabled_filesystem_skills ?? [])
|
||||
)
|
||||
.catch(() => {});
|
||||
}
|
||||
prevHasChanges.current = hasChanges;
|
||||
}, [hasChanges]);
|
||||
|
||||
function toggleSubSkill(subSkillName) {
|
||||
setHasChanges(true);
|
||||
setDisabledSubSkills((prev) => {
|
||||
if (prev.includes(subSkillName)) {
|
||||
return prev.filter((s) => s !== subSkillName);
|
||||
}
|
||||
return [...prev, subSkillName];
|
||||
});
|
||||
}
|
||||
const readSkills = FILESYSTEM_SUB_SKILLS.filter((s) => s.category === "read");
|
||||
const writeSkills = FILESYSTEM_SUB_SKILLS.filter(
|
||||
(s) => s.category === "write"
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<div className="flex items-center gap-x-2">
|
||||
{icon &&
|
||||
React.createElement(icon, {
|
||||
size: 24,
|
||||
color: "var(--theme-text-primary)",
|
||||
weight: "bold",
|
||||
})}
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="text-theme-text-primary text-md font-bold"
|
||||
>
|
||||
{title}
|
||||
</label>
|
||||
</div>
|
||||
<Toggle
|
||||
size="lg"
|
||||
enabled={enabled}
|
||||
disabled={disabled}
|
||||
onChange={() => toggleSkill(skill)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img src={image} alt={title} className="w-full rounded-md" />
|
||||
<WarningBanner />
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-theme-text-secondary text-opacity-60 text-xs font-medium">
|
||||
{t("agent.skill.filesystem.description")}
|
||||
</p>
|
||||
<Link
|
||||
to="/docs/guides/agent-skills/filesystem-agent"
|
||||
target="_blank"
|
||||
className="text-sky-400 hover:text-sky-500 text-xs font-medium underline"
|
||||
>
|
||||
{t("agent.skill.filesystem.learnMore")} →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{enabled && (
|
||||
<>
|
||||
<input
|
||||
name="system::disabled_filesystem_skills"
|
||||
type="hidden"
|
||||
value={disabledSubSkills.join(",")}
|
||||
/>
|
||||
<div className="flex flex-col mt-2 gap-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-theme-text-primary font-semibold text-sm">
|
||||
{t("agent.skill.filesystem.configuration")}
|
||||
</p>
|
||||
</div>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<CircleNotch
|
||||
size={24}
|
||||
className="animate-spin text-theme-text-primary"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<p className="text-theme-text-secondary text-xs font-medium uppercase tracking-wide">
|
||||
{t("agent.skill.filesystem.readActions")}
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{readSkills.map((subSkill) => (
|
||||
<SubSkillRow
|
||||
key={subSkill.name}
|
||||
subSkill={subSkill}
|
||||
disabled={disabledSubSkills.includes(subSkill.name)}
|
||||
onToggle={() => toggleSubSkill(subSkill.name)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<p className="text-theme-text-secondary text-xs font-medium uppercase tracking-wide flex items-center gap-x-1">
|
||||
<Warning
|
||||
size={12}
|
||||
className="text-orange-400"
|
||||
weight="fill"
|
||||
/>
|
||||
{t("agent.skill.filesystem.writeActions")}
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{writeSkills.map((subSkill) => (
|
||||
<SubSkillRow
|
||||
key={subSkill.name}
|
||||
subSkill={subSkill}
|
||||
disabled={disabledSubSkills.includes(subSkill.name)}
|
||||
onToggle={() => toggleSubSkill(subSkill.name)}
|
||||
isWriteOperation
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WarningBanner() {
|
||||
return (
|
||||
<div className="flex items-start gap-x-2.5 p-2.5 bg-orange-800/20 light:bg-orange-800/10 text-orange-400 light:text-orange-600 border border-orange-400/30 rounded-lg items-center">
|
||||
<Warning size={20} className="flex-shrink-0 mt-0.5" weight="fill" />
|
||||
<p className="text-xs font-medium">
|
||||
<Trans
|
||||
i18nKey="agent.skill.filesystem.warning"
|
||||
components={{
|
||||
link: (
|
||||
<Link
|
||||
to="/docs/guides/agent-skills/filesystem-agent"
|
||||
target="_blank"
|
||||
className="underline hover:text-orange-300 light:hover:text-orange-700"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SubSkillRow({ subSkill, disabled, onToggle, isWriteOperation }) {
|
||||
const Icon = subSkill.icon;
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between p-2 rounded-lg border ${
|
||||
disabled
|
||||
? "bg-theme-bg-secondary/30 border-theme-sidebar-border/30"
|
||||
: isWriteOperation
|
||||
? "bg-orange-900/10 border-orange-400/20 light:bg-orange-800/10 text-orange-400 light:text-orange-600"
|
||||
: "bg-theme-bg-secondary/50 border-theme-sidebar-border/50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Icon
|
||||
size={16}
|
||||
className={
|
||||
disabled
|
||||
? "text-theme-text-secondary/50"
|
||||
: isWriteOperation
|
||||
? "text-orange-400"
|
||||
: "text-theme-text-primary"
|
||||
}
|
||||
weight="bold"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`text-sm font-medium ${disabled ? "text-theme-text-secondary/50" : "text-theme-text-primary"}`}
|
||||
>
|
||||
{subSkill.title}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs ${disabled ? "text-theme-text-secondary/40" : "text-theme-text-secondary"}`}
|
||||
>
|
||||
{subSkill.description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<SimpleToggleSwitch enabled={!disabled} onChange={onToggle} size="md" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -58,8 +58,13 @@ export default function AdminAgents() {
|
||||
const [mcpServers, setMcpServers] = useState([]);
|
||||
const [selectedMcpServer, setSelectedMcpServer] = useState(null);
|
||||
|
||||
const [fileSystemAgentAvailable, setFileSystemAgentAvailable] =
|
||||
useState(false);
|
||||
|
||||
const defaultSkills = getDefaultSkills(t);
|
||||
const configurableSkills = getConfigurableSkills(t);
|
||||
const configurableSkills = getConfigurableSkills(t, {
|
||||
fileSystemAgentAvailable,
|
||||
});
|
||||
|
||||
// Alert user if they try to leave the page with unsaved changes
|
||||
useEffect(() => {
|
||||
@@ -77,15 +82,20 @@ export default function AdminAgents() {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchSettings() {
|
||||
const _settings = await System.keys();
|
||||
const _preferences = await Admin.systemPreferencesByFields([
|
||||
"disabled_agent_skills",
|
||||
"default_agent_skills",
|
||||
"imported_agent_skills",
|
||||
"active_agent_flows",
|
||||
]);
|
||||
const { flows = [] } = await AgentFlows.listFlows();
|
||||
const [_settings, _preferences, flowsRes, fsAgentAvailable] =
|
||||
await Promise.all([
|
||||
System.keys(),
|
||||
Admin.systemPreferencesByFields([
|
||||
"disabled_agent_skills",
|
||||
"default_agent_skills",
|
||||
"imported_agent_skills",
|
||||
"active_agent_flows",
|
||||
]),
|
||||
AgentFlows.listFlows(),
|
||||
System.isFileSystemAgentAvailable(),
|
||||
]);
|
||||
|
||||
const { flows = [] } = flowsRes;
|
||||
setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
|
||||
setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
|
||||
setDisabledAgentSkills(
|
||||
@@ -94,6 +104,7 @@ export default function AdminAgents() {
|
||||
setImportedSkills(_preferences.settings?.imported_agent_skills ?? []);
|
||||
setActiveFlowIds(_preferences.settings?.active_agent_flows ?? []);
|
||||
setAgentFlows(flows);
|
||||
setFileSystemAgentAvailable(fsAgentAvailable);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchSettings();
|
||||
|
||||
@@ -2,18 +2,21 @@ import AgentWebSearchSelection from "./WebSearchSelection";
|
||||
import AgentSQLConnectorSelection from "./SQLConnectorSelection";
|
||||
import GenericSkillPanel from "./GenericSkillPanel";
|
||||
import DefaultSkillPanel from "./DefaultSkillPanel";
|
||||
import FileSystemSkillPanel from "./FileSystemSkillPanel";
|
||||
import {
|
||||
Brain,
|
||||
File,
|
||||
Browser,
|
||||
ChartBar,
|
||||
FileMagnifyingGlass,
|
||||
FolderOpen,
|
||||
} from "@phosphor-icons/react";
|
||||
import RAGImage from "@/media/agents/rag-memory.png";
|
||||
import SummarizeImage from "@/media/agents/view-summarize.png";
|
||||
import ScrapeWebsitesImage from "@/media/agents/scrape-websites.png";
|
||||
import GenerateChartsImage from "@/media/agents/generate-charts.png";
|
||||
import GenerateSaveImages from "@/media/agents/generate-save-files.png";
|
||||
import FileSystemImage from "@/media/agents/file-system.png";
|
||||
|
||||
export const getDefaultSkills = (t) => ({
|
||||
"rag-memory": {
|
||||
@@ -42,7 +45,20 @@ export const getDefaultSkills = (t) => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const getConfigurableSkills = (t) => ({
|
||||
export const getConfigurableSkills = (
|
||||
t,
|
||||
{ fileSystemAgentAvailable = true } = {}
|
||||
) => ({
|
||||
...(fileSystemAgentAvailable && {
|
||||
"filesystem-agent": {
|
||||
title: t("agent.skill.filesystem.title"),
|
||||
description: t("agent.skill.filesystem.description"),
|
||||
component: FileSystemSkillPanel,
|
||||
skill: "filesystem-agent",
|
||||
icon: FolderOpen,
|
||||
image: FileSystemImage,
|
||||
},
|
||||
}),
|
||||
"save-file-to-browser": {
|
||||
title: t("agent.skill.save.title"),
|
||||
description: t("agent.skill.save.description"),
|
||||
|
||||
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@@ -10,6 +10,7 @@ storage/tmp/*
|
||||
storage/vector-cache/*.json
|
||||
storage/exports
|
||||
storage/imports
|
||||
storage/anythingllm-fs/*
|
||||
storage/plugins/agent-skills/*
|
||||
storage/plugins/agent-flows/*
|
||||
storage/plugins/office-extensions/*
|
||||
|
||||
@@ -403,6 +403,9 @@ function adminEndpoints(app) {
|
||||
case "disabled_agent_skills":
|
||||
requestedSettings[label] = safeJsonParse(setting?.value, []);
|
||||
break;
|
||||
case "disabled_filesystem_skills":
|
||||
requestedSettings[label] = safeJsonParse(setting?.value, []);
|
||||
break;
|
||||
case "imported_agent_skills":
|
||||
requestedSettings[label] = ImportedPlugin.listImportedPlugins();
|
||||
break;
|
||||
|
||||
@@ -9,6 +9,24 @@ const {
|
||||
function agentSkillWhitelistEndpoints(app) {
|
||||
if (!app) return;
|
||||
|
||||
app.get(
|
||||
"/agent-skills/filesystem-agent/is-available",
|
||||
[validatedRequest],
|
||||
async (_request, response) => {
|
||||
try {
|
||||
const filesystemTool = require("../utils/agents/aibitat/plugins/filesystem/lib");
|
||||
return response
|
||||
.status(200)
|
||||
.json({ available: filesystemTool.isToolAvailable() });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return response
|
||||
.status(500)
|
||||
.json({ available: false, error: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/agent-skills/whitelist/add",
|
||||
[validatedRequest, flexUserRoleValid(ROLES.all)],
|
||||
|
||||
@@ -33,6 +33,7 @@ const SystemSettings = {
|
||||
"agent_sql_connections",
|
||||
"default_agent_skills",
|
||||
"disabled_agent_skills",
|
||||
"disabled_filesystem_skills",
|
||||
"imported_agent_skills",
|
||||
"custom_app_name",
|
||||
"feature_flags",
|
||||
@@ -50,6 +51,7 @@ const SystemSettings = {
|
||||
"agent_search_provider",
|
||||
"default_agent_skills",
|
||||
"disabled_agent_skills",
|
||||
"disabled_filesystem_skills",
|
||||
"agent_sql_connections",
|
||||
"custom_app_name",
|
||||
"default_system_prompt",
|
||||
@@ -152,6 +154,15 @@ const SystemSettings = {
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
},
|
||||
disabled_filesystem_skills: (updates) => {
|
||||
try {
|
||||
const skills = updates.split(",").filter((skill) => !!skill);
|
||||
return JSON.stringify(skills);
|
||||
} catch {
|
||||
console.error(`Could not validate disabled filesystem skills.`);
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
},
|
||||
agent_sql_connections: async (updates) => {
|
||||
const existingConnections = safeJsonParse(
|
||||
(await SystemSettings.get({ label: "agent_sql_connections" }))?.value,
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@pinecone-database/pinecone": "^2.0.1",
|
||||
"@prisma/client": "5.3.1",
|
||||
"@qdrant/js-client-rest": "^1.9.0",
|
||||
"@vscode/ripgrep": "1.17.1",
|
||||
"@xenova/transformers": "^2.14.0",
|
||||
"@zilliz/milvus2-sdk-node": "^2.3.5",
|
||||
"adm-zip": "^0.5.16",
|
||||
@@ -52,6 +53,7 @@
|
||||
"chromadb": "^2.0.1",
|
||||
"cohere-ai": "^7.19.0",
|
||||
"cors": "^2.8.5",
|
||||
"diff": "7.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"elevenlabs": "^0.5.0",
|
||||
"express": "^4.21.2",
|
||||
|
||||
@@ -45,6 +45,14 @@ class AIbitat {
|
||||
*/
|
||||
_pendingCitations = [];
|
||||
|
||||
/**
|
||||
* Buffer for attachments (images) collected during tool execution.
|
||||
* Tools can call addToolAttachment() to queue images for injection into the conversation.
|
||||
* These are injected as a user message so all providers' existing attachment handling works.
|
||||
* @type {Array<{name: string, mime: string, contentString: string}>}
|
||||
*/
|
||||
_toolAttachments = [];
|
||||
|
||||
/**
|
||||
* Get the default maximum number of tools an agent can chain for a single response.
|
||||
* @returns {number}
|
||||
@@ -141,6 +149,28 @@ class AIbitat {
|
||||
this._pendingCitations = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attachment (image) from a tool to be injected into the conversation.
|
||||
* The attachment will be added as a user message so the model can "see" it.
|
||||
* This leverages existing provider attachment handling for user messages.
|
||||
* @param {{name: string, mime: string, contentString: string}} attachment - The attachment object with name, mime type, and base64 data URL
|
||||
*/
|
||||
addToolAttachment(attachment) {
|
||||
if (!attachment || !attachment.contentString) return;
|
||||
this._toolAttachments.push(attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect and clear any pending tool attachments.
|
||||
* @returns {Array<{name: string, mime: string, contentString: string}>} The collected attachments
|
||||
*/
|
||||
collectToolAttachments() {
|
||||
if (this._toolAttachments.length === 0) return [];
|
||||
const attachments = [...this._toolAttachments];
|
||||
this._toolAttachments = [];
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new agent to the AIbitat.
|
||||
*
|
||||
@@ -896,17 +926,31 @@ https://docs.anythingllm.com/agent/intelligent-tool-selection
|
||||
return result;
|
||||
}
|
||||
|
||||
const toolAttachments = this.collectToolAttachments();
|
||||
const newMessages = [
|
||||
...messages,
|
||||
{
|
||||
name,
|
||||
role: "function",
|
||||
content: result,
|
||||
originalFunctionCall: completionStream.functionCall,
|
||||
},
|
||||
];
|
||||
|
||||
if (toolAttachments.length > 0) {
|
||||
this.handlerProps?.log?.(
|
||||
`[debug]: Injecting ${toolAttachments.length} image attachment(s) from tool result`
|
||||
);
|
||||
newMessages.push({
|
||||
role: "user",
|
||||
content: "[Attached image(s) from tool result]",
|
||||
attachments: toolAttachments,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.handleAsyncExecution(
|
||||
provider,
|
||||
[
|
||||
...messages,
|
||||
{
|
||||
name,
|
||||
role: "function",
|
||||
content: result,
|
||||
originalFunctionCall: completionStream.functionCall,
|
||||
},
|
||||
],
|
||||
newMessages,
|
||||
functions,
|
||||
byAgent,
|
||||
depth + 1
|
||||
@@ -1034,17 +1078,31 @@ https://docs.anythingllm.com/agent/intelligent-tool-selection
|
||||
return result;
|
||||
}
|
||||
|
||||
const toolAttachments = this.collectToolAttachments();
|
||||
const newMessages = [
|
||||
...messages,
|
||||
{
|
||||
name,
|
||||
role: "function",
|
||||
content: result,
|
||||
originalFunctionCall: completion.functionCall,
|
||||
},
|
||||
];
|
||||
|
||||
if (toolAttachments.length > 0) {
|
||||
this.handlerProps?.log?.(
|
||||
`[debug]: Injecting ${toolAttachments.length} image attachment(s) from tool result`
|
||||
);
|
||||
newMessages.push({
|
||||
role: "user",
|
||||
content: "[Attached image(s) from tool result]",
|
||||
attachments: toolAttachments,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.handleExecution(
|
||||
provider,
|
||||
[
|
||||
...messages,
|
||||
{
|
||||
name,
|
||||
role: "function",
|
||||
content: result,
|
||||
originalFunctionCall: completion.functionCall,
|
||||
},
|
||||
],
|
||||
newMessages,
|
||||
functions,
|
||||
byAgent,
|
||||
depth + 1,
|
||||
|
||||
121
server/utils/agents/aibitat/plugins/filesystem/copy-file.js
Normal file
121
server/utils/agents/aibitat/plugins/filesystem/copy-file.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
async function copyRecursive(source, destination) {
|
||||
const stats = await fs.stat(source);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await fs.mkdir(destination, { recursive: true });
|
||||
const entries = await fs.readdir(source);
|
||||
for (const entry of entries) {
|
||||
await copyRecursive(
|
||||
path.join(source, entry),
|
||||
path.join(destination, entry)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await fs.copyFile(source, destination);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.FilesystemCopyFile = {
|
||||
name: "filesystem-copy-file",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-copy-file",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Copy files and directories. Creates a duplicate of the source at the destination path. " +
|
||||
"For directories, performs a recursive copy of all contents. If the destination exists, " +
|
||||
"the operation will fail. Both source and destination must be within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Make a backup copy of config.json",
|
||||
call: JSON.stringify({
|
||||
source: "config.json",
|
||||
destination: "config.backup.json",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Copy the templates folder to a new location",
|
||||
call: JSON.stringify({
|
||||
source: "templates",
|
||||
destination: "templates-backup",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
source: {
|
||||
type: "string",
|
||||
description: "The path of the file or directory to copy.",
|
||||
},
|
||||
destination: {
|
||||
type: "string",
|
||||
description:
|
||||
"The destination path where the copy should be created.",
|
||||
},
|
||||
},
|
||||
required: ["source", "destination"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ source = "", destination = "" }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-copy-file tool.`
|
||||
);
|
||||
|
||||
const validSourcePath = await filesystem.validatePath(source);
|
||||
const validDestPath = await filesystem.validatePath(destination);
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Copying ${source} to ${destination}`
|
||||
);
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { source, destination },
|
||||
description: "Copy a file or directory to a new location",
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
const destExists = await fs
|
||||
.access(validDestPath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (destExists) {
|
||||
return `Error: Destination "${destination}" already exists. Please choose a different destination.`;
|
||||
}
|
||||
|
||||
await copyRecursive(validSourcePath, validDestPath);
|
||||
this.super.introspect(
|
||||
`Successfully copied ${source} to ${destination}`
|
||||
);
|
||||
return `Successfully copied ${source} to ${destination}`;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-copy-file error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error copying file: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
const fs = require("fs/promises");
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemCreateDirectory = {
|
||||
name: "filesystem-create-directory",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-create-directory",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Create a new directory or ensure a directory exists. Can create multiple " +
|
||||
"nested directories in one operation. If the directory already exists, " +
|
||||
"this operation will succeed silently. Perfect for setting up directory " +
|
||||
"structures for projects or ensuring required paths exist. Only works within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Create a new folder called 'reports'",
|
||||
call: JSON.stringify({ path: "reports" }),
|
||||
},
|
||||
{
|
||||
prompt: "Create nested directories for the new module",
|
||||
call: JSON.stringify({ path: "src/modules/auth/utils" }),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path of the directory to create. Can include nested paths.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ path: dirPath = "" }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-create-directory tool.`
|
||||
);
|
||||
|
||||
const validPath = await filesystem.validatePath(dirPath);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Creating directory ${dirPath}`
|
||||
);
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { path: dirPath },
|
||||
description: "Create a new directory",
|
||||
});
|
||||
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
await fs.mkdir(validPath, { recursive: true });
|
||||
this.super.introspect(
|
||||
`Successfully created directory ${dirPath}`
|
||||
);
|
||||
return `Successfully created directory ${dirPath}`;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-create-directory error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error creating directory: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
133
server/utils/agents/aibitat/plugins/filesystem/edit-file.js
Normal file
133
server/utils/agents/aibitat/plugins/filesystem/edit-file.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemEditFile = {
|
||||
name: "filesystem-edit-file",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-edit-file",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Make line-based edits to a text file. Each edit replaces exact line sequences " +
|
||||
"with new content. Returns a git-style diff showing the changes made. " +
|
||||
"Use dryRun=true to preview changes without applying them. " +
|
||||
"Only works within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Change the port number from 3000 to 8080 in config.js",
|
||||
call: JSON.stringify({
|
||||
path: "config.js",
|
||||
edits: [{ oldText: "port: 3000", newText: "port: 8080" }],
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Preview what would happen if I renamed the function",
|
||||
call: JSON.stringify({
|
||||
path: "utils.js",
|
||||
edits: [
|
||||
{
|
||||
oldText: "function oldName()",
|
||||
newText: "function newName()",
|
||||
},
|
||||
],
|
||||
dryRun: true,
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "The path to the file to edit.",
|
||||
},
|
||||
edits: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
oldText: {
|
||||
type: "string",
|
||||
description: "Text to search for - must match exactly.",
|
||||
},
|
||||
newText: {
|
||||
type: "string",
|
||||
description: "Text to replace with.",
|
||||
},
|
||||
},
|
||||
required: ["oldText", "newText"],
|
||||
},
|
||||
description: "Array of edit operations to apply.",
|
||||
},
|
||||
dryRun: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description:
|
||||
"If true, preview changes using git-style diff format without applying them.",
|
||||
},
|
||||
},
|
||||
required: ["path", "edits"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({
|
||||
path: filePath = "",
|
||||
edits = [],
|
||||
dryRun = false,
|
||||
}) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-edit-file tool.`
|
||||
);
|
||||
|
||||
if (!Array.isArray(edits) || edits.length === 0) {
|
||||
return "Error: At least one edit operation must be provided.";
|
||||
}
|
||||
|
||||
const validPath = await filesystem.validatePath(filePath);
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: ${dryRun ? "Previewing" : "Applying"} ${edits.length} edit(s) to ${filePath}`
|
||||
);
|
||||
|
||||
if (this.super.requestToolApproval && !dryRun) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { path: filePath, edits, dryRun },
|
||||
description: "Edit a file",
|
||||
});
|
||||
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await filesystem.applyFileEdits(
|
||||
validPath,
|
||||
edits,
|
||||
dryRun
|
||||
);
|
||||
|
||||
if (dryRun)
|
||||
this.super.introspect(`Preview of changes to ${filePath}:`);
|
||||
else this.super.introspect(`Successfully edited ${filePath}`);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-edit-file error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error editing file: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemGetFileInfo = {
|
||||
name: "filesystem-get-file-info",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-get-file-info",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Retrieve detailed metadata about a file or directory. Returns comprehensive " +
|
||||
"information including size, creation time, last modified time, permissions, " +
|
||||
"and type. This tool is perfect for understanding file characteristics " +
|
||||
"without reading the actual content. Only works within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "What's the size of the database file?",
|
||||
call: JSON.stringify({ path: "data/app.db" }),
|
||||
},
|
||||
{
|
||||
prompt: "When was the config file last modified?",
|
||||
call: JSON.stringify({ path: "config.json" }),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path to the file or directory to get information about.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ path: filePath = "" }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-get-file-info tool.`
|
||||
);
|
||||
|
||||
const validPath = await filesystem.validatePath(filePath);
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Getting info for ${filePath}`
|
||||
);
|
||||
|
||||
const info = await filesystem.getFileStats(validPath);
|
||||
|
||||
const formatted = Object.entries(info)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join("\n");
|
||||
|
||||
this.super.introspect(
|
||||
`Successfully retrieved info for ${filePath}`
|
||||
);
|
||||
|
||||
return formatted;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-get-file-info error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error getting file info: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
33
server/utils/agents/aibitat/plugins/filesystem/index.js
Normal file
33
server/utils/agents/aibitat/plugins/filesystem/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { FilesystemReadTextFile } = require("./read-text-file.js");
|
||||
const { FilesystemReadMultipleFiles } = require("./read-multiple-files.js");
|
||||
const { FilesystemWriteFile } = require("./write-file.js");
|
||||
const { FilesystemEditFile } = require("./edit-file.js");
|
||||
const { FilesystemCreateDirectory } = require("./create-directory.js");
|
||||
const { FilesystemListDirectory } = require("./list-directory.js");
|
||||
const { FilesystemMoveFile } = require("./move-file.js");
|
||||
const { FilesystemCopyFile } = require("./copy-file.js");
|
||||
const { FilesystemSearchFiles } = require("./search-files.js");
|
||||
const { FilesystemGetFileInfo } = require("./get-file-info.js");
|
||||
|
||||
const filesystemAgent = {
|
||||
name: "filesystem-agent",
|
||||
startupConfig: {
|
||||
params: {},
|
||||
},
|
||||
plugin: [
|
||||
FilesystemReadTextFile,
|
||||
FilesystemReadMultipleFiles,
|
||||
FilesystemWriteFile,
|
||||
FilesystemEditFile,
|
||||
FilesystemCreateDirectory,
|
||||
FilesystemListDirectory,
|
||||
FilesystemMoveFile,
|
||||
FilesystemCopyFile,
|
||||
FilesystemSearchFiles,
|
||||
FilesystemGetFileInfo,
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
filesystemAgent,
|
||||
};
|
||||
787
server/utils/agents/aibitat/plugins/filesystem/lib.js
Normal file
787
server/utils/agents/aibitat/plugins/filesystem/lib.js
Normal file
@@ -0,0 +1,787 @@
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const { randomBytes } = require("crypto");
|
||||
const { createTwoFilesPatch } = require("diff");
|
||||
const { humanFileSize } = require("../../../../helpers");
|
||||
|
||||
/**
|
||||
* Manages filesystem operations with security constraints.
|
||||
* Ensures all file operations stay within allowed directories.
|
||||
*/
|
||||
class FilesystemManager {
|
||||
static FILE_READ_CHUNK_SIZE = 1024;
|
||||
static CONTEXT_RESERVE_RATIO = 0.25;
|
||||
static IMAGE_EXTENSIONS = [
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".webp",
|
||||
".svg",
|
||||
".bmp",
|
||||
];
|
||||
static IMAGE_MIME_TYPES = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".svg": "image/svg+xml",
|
||||
".bmp": "image/bmp",
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the filesystem tool is available.
|
||||
* The filesystem tool is only available when running in a docker container
|
||||
* or in development mode.
|
||||
* @returns {boolean} True if the tool is available
|
||||
*/
|
||||
isToolAvailable() {
|
||||
if (process.env.NODE_ENV === "development") return true;
|
||||
return process.env.ANYTHING_LLM_RUNTIME === "docker";
|
||||
}
|
||||
|
||||
#allowedDirectories = [];
|
||||
#isInitialized = false;
|
||||
|
||||
/**
|
||||
* Gets the default filesystem root path.
|
||||
* @returns {string} The default filesystem root path
|
||||
*/
|
||||
#getDefaultFilesystemRoot() {
|
||||
const storageRoot =
|
||||
process.env.STORAGE_DIR ||
|
||||
path.resolve(__dirname, "../../../../../storage");
|
||||
return path.join(storageRoot, "anythingllm-fs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the filesystem with default or configured directories.
|
||||
* @param {string[]} [directories] - Optional array of directories to allow
|
||||
* @returns {Promise<string[]>} The initialized allowed directories
|
||||
*/
|
||||
async #initializeFilesystem(directories = null) {
|
||||
if (directories && directories.length > 0) {
|
||||
this.#allowedDirectories = directories.map((dir) =>
|
||||
path.resolve(this.#expandHome(dir))
|
||||
);
|
||||
} else {
|
||||
const defaultRoot = this.#getDefaultFilesystemRoot();
|
||||
this.#allowedDirectories = [defaultRoot];
|
||||
}
|
||||
|
||||
for (const dir of this.#allowedDirectories) {
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Warning: Could not create directory ${dir}: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.#isInitialized = true;
|
||||
return this.#allowedDirectories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands home directory tildes in paths.
|
||||
* @param {string} filepath - The path to expand
|
||||
* @returns {string} Expanded path
|
||||
*/
|
||||
#expandHome(filepath) {
|
||||
if (filepath.startsWith("~/") || filepath === "~") {
|
||||
return path.join(os.homedir(), filepath.slice(1));
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a path by standardizing format.
|
||||
* @param {string} p - The path to normalize
|
||||
* @returns {string} Normalized path
|
||||
*/
|
||||
#normalizePath(p) {
|
||||
p = p.trim().replace(/^["']|["']$/g, "");
|
||||
if (p.startsWith("/"))
|
||||
return p.replace(/\/+/g, "/").replace(/(?<!^)\/$/, "");
|
||||
return path.normalize(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes a path string for security checks.
|
||||
* @param {string} p - The path to validate
|
||||
* @returns {string|null} Normalized absolute path or null if invalid
|
||||
*/
|
||||
#normalizeAndValidatePath(p) {
|
||||
if (typeof p !== "string" || !p || p.includes("\x00")) return null;
|
||||
try {
|
||||
const normalized = path.resolve(path.normalize(p));
|
||||
return path.isAbsolute(normalized) ? normalized : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an absolute path is within any of the allowed directories.
|
||||
* @param {string} absolutePath - The absolute path to check
|
||||
* @param {string[]} allowedDirs - Array of allowed directory paths
|
||||
* @returns {boolean} True if path is within allowed directories
|
||||
*/
|
||||
#isPathWithinAllowedDirectories(absolutePath, allowedDirs) {
|
||||
if (!Array.isArray(allowedDirs) || allowedDirs.length === 0) return false;
|
||||
|
||||
const normalizedPath = this.#normalizeAndValidatePath(absolutePath);
|
||||
if (!normalizedPath) return false;
|
||||
|
||||
return allowedDirs.some((dir) => {
|
||||
const normalizedDir = this.#normalizeAndValidatePath(dir);
|
||||
if (!normalizedDir) return false;
|
||||
|
||||
if (normalizedPath === normalizedDir) return true;
|
||||
return normalizedPath.startsWith(normalizedDir + path.sep);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a relative path against allowed directories.
|
||||
* @param {string} relativePath - The relative path to resolve
|
||||
* @returns {string} The resolved absolute path
|
||||
*/
|
||||
#resolveRelativePathAgainstAllowedDirectories(relativePath) {
|
||||
if (this.#allowedDirectories.length === 0) {
|
||||
return path.resolve(process.cwd(), relativePath);
|
||||
}
|
||||
|
||||
for (const allowedDir of this.#allowedDirectories) {
|
||||
const candidate = path.resolve(allowedDir, relativePath);
|
||||
const normalizedCandidate = this.#normalizePath(candidate);
|
||||
|
||||
if (
|
||||
this.#isPathWithinAllowedDirectories(
|
||||
normalizedCandidate,
|
||||
this.#allowedDirectories
|
||||
)
|
||||
) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return path.resolve(this.#allowedDirectories[0], relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes line endings to Unix-style.
|
||||
* @param {string} text - Text to normalize
|
||||
* @returns {string} Text with normalized line endings
|
||||
*/
|
||||
#normalizeLineEndings(text) {
|
||||
return text.replace(/\r\n/g, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes content to a file atomically using a temp file and rename.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {string} content - Content to write
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async #atomicWrite(filePath, content) {
|
||||
const tempPath = `${filePath}.${randomBytes(16).toString("hex")}.tmp`;
|
||||
try {
|
||||
await fs.writeFile(tempPath, content, "utf-8");
|
||||
await fs.rename(tempPath, filePath);
|
||||
} catch (error) {
|
||||
try {
|
||||
await fs.unlink(tempPath);
|
||||
} catch {}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads file content with collector API fallback.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {Function} processContent - Function to process parsed content
|
||||
* @param {Function} rawFallback - Function to call if collector fails
|
||||
* @returns {Promise<string>} Processed content
|
||||
*/
|
||||
async #withCollectorFallback(filePath, processContent, rawFallback) {
|
||||
const parseResult = await this.#parseFileWithCollector(filePath);
|
||||
if (parseResult.parsed && parseResult.content) {
|
||||
return processContent(parseResult.content);
|
||||
}
|
||||
return rawFallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unified diff between two strings.
|
||||
* @param {string} originalContent - Original content
|
||||
* @param {string} newContent - New content
|
||||
* @param {string} filepath - File path for diff header
|
||||
* @returns {string} Unified diff string
|
||||
*/
|
||||
#createUnifiedDiff(originalContent, newContent, filepath = "file") {
|
||||
const normalizedOriginal = this.#normalizeLineEndings(originalContent);
|
||||
const normalizedNew = this.#normalizeLineEndings(newContent);
|
||||
|
||||
return createTwoFilesPatch(
|
||||
filepath,
|
||||
filepath,
|
||||
normalizedOriginal,
|
||||
normalizedNew,
|
||||
"original",
|
||||
"modified"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads file content as text using direct file read.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {string} encoding - File encoding
|
||||
* @returns {Promise<string>} File content
|
||||
*/
|
||||
async #readFileContentRaw(filePath, encoding = "utf-8") {
|
||||
return await fs.readFile(filePath, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a file using the collector API to extract text content.
|
||||
* @param {string} filePath - Absolute path to the file
|
||||
* @returns {Promise<{content: string, parsed: boolean}>} Parsed content
|
||||
*/
|
||||
async #parseFileWithCollector(filePath) {
|
||||
try {
|
||||
const { CollectorApi } = require("../../../../collectorApi");
|
||||
const collectorApi = new CollectorApi();
|
||||
|
||||
const isOnline = await collectorApi.online();
|
||||
if (!isOnline) {
|
||||
return {
|
||||
content: null,
|
||||
parsed: false,
|
||||
error: "Collector service offline",
|
||||
};
|
||||
}
|
||||
|
||||
const filename = path.basename(filePath);
|
||||
const result = await collectorApi.parseDocument(filename, {
|
||||
absolutePath: filePath,
|
||||
});
|
||||
|
||||
if (!result || !result.success) {
|
||||
return {
|
||||
content: null,
|
||||
parsed: false,
|
||||
error: result?.reason || "Failed to parse document",
|
||||
};
|
||||
}
|
||||
|
||||
if (result.content) return { content: result.content, parsed: true };
|
||||
if (result.documents && result.documents.length > 0) {
|
||||
const content = result.documents
|
||||
.map((doc) => doc.pageContent || doc.content || "")
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
if (content) return { content, parsed: true };
|
||||
}
|
||||
|
||||
return { content: null, parsed: false, error: "No content in response" };
|
||||
} catch (error) {
|
||||
return { content: null, parsed: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last N lines of a file using raw file operations.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {number} numLines - Number of lines to return
|
||||
* @returns {Promise<string>} Last N lines of the file
|
||||
*/
|
||||
async #tailFileRaw(filePath, numLines) {
|
||||
const stats = await fs.stat(filePath);
|
||||
const fileSize = stats.size;
|
||||
|
||||
if (fileSize === 0) return "";
|
||||
|
||||
const fileHandle = await fs.open(filePath, "r");
|
||||
try {
|
||||
const lines = [];
|
||||
let position = fileSize;
|
||||
const chunk = Buffer.alloc(FilesystemManager.FILE_READ_CHUNK_SIZE);
|
||||
let linesFound = 0;
|
||||
let remainingText = "";
|
||||
|
||||
while (position > 0 && linesFound < numLines) {
|
||||
const size = Math.min(FilesystemManager.FILE_READ_CHUNK_SIZE, position);
|
||||
position -= size;
|
||||
|
||||
const { bytesRead } = await fileHandle.read(chunk, 0, size, position);
|
||||
if (!bytesRead) break;
|
||||
|
||||
const readData = chunk.slice(0, bytesRead).toString("utf-8");
|
||||
const chunkText = readData + remainingText;
|
||||
|
||||
const chunkLines = this.#normalizeLineEndings(chunkText).split("\n");
|
||||
|
||||
if (position > 0) {
|
||||
remainingText = chunkLines[0];
|
||||
chunkLines.shift();
|
||||
}
|
||||
|
||||
for (
|
||||
let i = chunkLines.length - 1;
|
||||
i >= 0 && linesFound < numLines;
|
||||
i--
|
||||
) {
|
||||
lines.unshift(chunkLines[i]);
|
||||
linesFound++;
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
} finally {
|
||||
await fileHandle.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first N lines of a file using raw file operations.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {number} numLines - Number of lines to return
|
||||
* @returns {Promise<string>} First N lines of the file
|
||||
*/
|
||||
async #headFileRaw(filePath, numLines) {
|
||||
const fileHandle = await fs.open(filePath, "r");
|
||||
try {
|
||||
const lines = [];
|
||||
let buffer = "";
|
||||
let bytesRead = 0;
|
||||
const chunk = Buffer.alloc(FilesystemManager.FILE_READ_CHUNK_SIZE);
|
||||
|
||||
while (lines.length < numLines) {
|
||||
const result = await fileHandle.read(chunk, 0, chunk.length, bytesRead);
|
||||
if (result.bytesRead === 0) break;
|
||||
bytesRead += result.bytesRead;
|
||||
buffer += chunk.slice(0, result.bytesRead).toString("utf-8");
|
||||
|
||||
const newLineIndex = buffer.lastIndexOf("\n");
|
||||
if (newLineIndex !== -1) {
|
||||
const completeLines = buffer.slice(0, newLineIndex).split("\n");
|
||||
buffer = buffer.slice(newLineIndex + 1);
|
||||
for (const line of completeLines) {
|
||||
lines.push(line);
|
||||
if (lines.length >= numLines) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.length > 0 && lines.length < numLines) {
|
||||
lines.push(buffer);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
} finally {
|
||||
await fileHandle.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current allowed directories.
|
||||
* @returns {string[]} Array of allowed directory paths
|
||||
*/
|
||||
getAllowedDirectories() {
|
||||
return [...this.#allowedDirectories];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the filesystem is initialized before use.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async ensureInitialized() {
|
||||
if (!this.#isInitialized) await this.#initializeFilesystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a path for security, ensuring it's within allowed directories.
|
||||
* @param {string} requestedPath - The path to validate
|
||||
* @returns {Promise<string>} The validated absolute path
|
||||
* @throws {Error} If path is outside allowed directories
|
||||
*/
|
||||
async validatePath(requestedPath) {
|
||||
await this.ensureInitialized();
|
||||
const expandedPath = this.#expandHome(requestedPath);
|
||||
const absolute = path.isAbsolute(expandedPath)
|
||||
? path.resolve(expandedPath)
|
||||
: this.#resolveRelativePathAgainstAllowedDirectories(expandedPath);
|
||||
|
||||
const normalizedRequested = this.#normalizePath(absolute);
|
||||
|
||||
const isAllowed = this.#isPathWithinAllowedDirectories(
|
||||
normalizedRequested,
|
||||
this.#allowedDirectories
|
||||
);
|
||||
if (!isAllowed) {
|
||||
console.log(
|
||||
`[validatePath] Access denied - path outside allowed directories: ${absolute} not in ${this.#allowedDirectories.join(", ")}`
|
||||
);
|
||||
throw new Error(`Access denied - path outside allowed directories.`);
|
||||
}
|
||||
|
||||
try {
|
||||
const realPath = await fs.realpath(absolute);
|
||||
const normalizedReal = this.#normalizePath(realPath);
|
||||
if (
|
||||
!this.#isPathWithinAllowedDirectories(
|
||||
normalizedReal,
|
||||
this.#allowedDirectories
|
||||
)
|
||||
) {
|
||||
console.log(
|
||||
`[validatePath] Access denied - symlink target outside allowed directories: ${realPath} not in ${this.#allowedDirectories.join(", ")}`
|
||||
);
|
||||
throw new Error(
|
||||
`Access denied - symlink target outside allowed directories.`
|
||||
);
|
||||
}
|
||||
return realPath;
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT") {
|
||||
const parentDir = path.dirname(absolute);
|
||||
try {
|
||||
const realParentPath = await fs.realpath(parentDir);
|
||||
const normalizedParent = this.#normalizePath(realParentPath);
|
||||
if (
|
||||
!this.#isPathWithinAllowedDirectories(
|
||||
normalizedParent,
|
||||
this.#allowedDirectories
|
||||
)
|
||||
) {
|
||||
console.log(
|
||||
`[validatePath] Access denied - parent directory outside allowed directories: ${realParentPath} not in ${this.#allowedDirectories.join(", ")}`
|
||||
);
|
||||
throw new Error(
|
||||
`Access denied - parent directory outside allowed directories.`
|
||||
);
|
||||
}
|
||||
return absolute;
|
||||
} catch {
|
||||
throw new Error(`Parent directory does not exist: ${parentDir}`);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets detailed file statistics.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @returns {Promise<Object>} File statistics
|
||||
*/
|
||||
async getFileStats(filePath) {
|
||||
const stats = await fs.stat(filePath);
|
||||
return {
|
||||
size: stats.size,
|
||||
sizeFormatted: humanFileSize(stats.size, true, 2),
|
||||
created: stats.birthtime.toISOString(),
|
||||
modified: stats.mtime.toISOString(),
|
||||
accessed: stats.atime.toISOString(),
|
||||
isDirectory: stats.isDirectory(),
|
||||
isFile: stats.isFile(),
|
||||
permissions: stats.mode.toString(8).slice(-3),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads file content, using the collector API to parse binary files.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {string} encoding - File encoding (default: utf-8)
|
||||
* @returns {Promise<string>} File content
|
||||
*/
|
||||
async readFileContent(filePath, encoding = "utf-8") {
|
||||
return this.#withCollectorFallback(
|
||||
filePath,
|
||||
(content) => content,
|
||||
() => this.#readFileContentRaw(filePath, encoding)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes content to a file securely.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {string} content - Content to write
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async writeFileContent(filePath, content) {
|
||||
try {
|
||||
await fs.writeFile(filePath, content, { encoding: "utf-8", flag: "wx" });
|
||||
} catch (error) {
|
||||
if (error.code === "EEXIST") {
|
||||
await this.#atomicWrite(filePath, content);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies edits to a file.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {Array<{oldText: string, newText: string}>} edits - Array of edits
|
||||
* @param {boolean} dryRun - If true, only preview changes
|
||||
* @returns {Promise<string>} Diff of changes
|
||||
*/
|
||||
async applyFileEdits(filePath, edits, dryRun = false) {
|
||||
const content = this.#normalizeLineEndings(
|
||||
await fs.readFile(filePath, "utf-8")
|
||||
);
|
||||
|
||||
let modifiedContent = content;
|
||||
for (const edit of edits) {
|
||||
const normalizedOld = this.#normalizeLineEndings(edit.oldText);
|
||||
const normalizedNew = this.#normalizeLineEndings(edit.newText);
|
||||
|
||||
if (modifiedContent.includes(normalizedOld)) {
|
||||
modifiedContent = modifiedContent.replace(normalizedOld, normalizedNew);
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldLines = normalizedOld.split("\n");
|
||||
const contentLines = modifiedContent.split("\n");
|
||||
let matchFound = false;
|
||||
|
||||
for (let i = 0; i <= contentLines.length - oldLines.length; i++) {
|
||||
const potentialMatch = contentLines.slice(i, i + oldLines.length);
|
||||
|
||||
const isMatch = oldLines.every((oldLine, j) => {
|
||||
const contentLine = potentialMatch[j];
|
||||
return oldLine.trim() === contentLine.trim();
|
||||
});
|
||||
|
||||
if (isMatch) {
|
||||
const originalIndent = contentLines[i].match(/^\s*/)?.[0] || "";
|
||||
const newLines = normalizedNew.split("\n").map((line, j) => {
|
||||
if (j === 0) return originalIndent + line.trimStart();
|
||||
const oldIndent = oldLines[j]?.match(/^\s*/)?.[0] || "";
|
||||
const newIndent = line.match(/^\s*/)?.[0] || "";
|
||||
if (oldIndent && newIndent) {
|
||||
const relativeIndent = newIndent.length - oldIndent.length;
|
||||
return (
|
||||
originalIndent +
|
||||
" ".repeat(Math.max(0, relativeIndent)) +
|
||||
line.trimStart()
|
||||
);
|
||||
}
|
||||
return line;
|
||||
});
|
||||
|
||||
contentLines.splice(i, oldLines.length, ...newLines);
|
||||
modifiedContent = contentLines.join("\n");
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchFound) {
|
||||
throw new Error(
|
||||
`Could not find exact match for edit:\n${edit.oldText}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const diffResult = this.#createUnifiedDiff(
|
||||
content,
|
||||
modifiedContent,
|
||||
filePath
|
||||
);
|
||||
|
||||
let numBackticks = 3;
|
||||
while (diffResult.includes("`".repeat(numBackticks))) {
|
||||
numBackticks++;
|
||||
}
|
||||
const formattedDiff = `${"`".repeat(numBackticks)}diff\n${diffResult}${"`".repeat(numBackticks)}\n\n`;
|
||||
|
||||
if (!dryRun) {
|
||||
await this.#atomicWrite(filePath, modifiedContent);
|
||||
}
|
||||
|
||||
return formattedDiff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last N lines of a file.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {number} numLines - Number of lines to return
|
||||
* @returns {Promise<string>} Last N lines of the file
|
||||
*/
|
||||
async tailFile(filePath, numLines) {
|
||||
return this.#withCollectorFallback(
|
||||
filePath,
|
||||
(content) => {
|
||||
const lines = this.#normalizeLineEndings(content).split("\n");
|
||||
return lines.slice(-numLines).join("\n");
|
||||
},
|
||||
() => this.#tailFileRaw(filePath, numLines)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first N lines of a file.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @param {number} numLines - Number of lines to return
|
||||
* @returns {Promise<string>} First N lines of the file
|
||||
*/
|
||||
async headFile(filePath, numLines) {
|
||||
return this.#withCollectorFallback(
|
||||
filePath,
|
||||
(content) => {
|
||||
const lines = this.#normalizeLineEndings(content).split("\n");
|
||||
return lines.slice(0, numLines).join("\n");
|
||||
},
|
||||
() => this.#headFileRaw(filePath, numLines)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for files matching a glob pattern.
|
||||
* @param {string} rootPath - Root path to search from
|
||||
* @param {string} pattern - Glob pattern to match
|
||||
* @param {Object} options - Search options
|
||||
* @param {string[]} options.excludePatterns - Patterns to exclude
|
||||
* @returns {Promise<string[]>} Array of matching file paths
|
||||
*/
|
||||
async searchFilesWithGlob(rootPath, pattern, options = {}) {
|
||||
const minimatch = require("minimatch");
|
||||
const { excludePatterns = [] } = options;
|
||||
const results = [];
|
||||
const matchOptions = { dot: true, nocase: true };
|
||||
|
||||
const search = async (currentPath) => {
|
||||
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentPath, entry.name);
|
||||
|
||||
try {
|
||||
await this.validatePath(fullPath);
|
||||
|
||||
const relativePath = path.relative(rootPath, fullPath);
|
||||
const shouldExclude = excludePatterns.some(
|
||||
(excludePattern) =>
|
||||
minimatch(relativePath, excludePattern, matchOptions) ||
|
||||
minimatch(entry.name, excludePattern, matchOptions)
|
||||
);
|
||||
|
||||
if (shouldExclude) continue;
|
||||
|
||||
const matchesPath = minimatch(relativePath, pattern, matchOptions);
|
||||
const matchesName = minimatch(entry.name, pattern, matchOptions);
|
||||
|
||||
if (matchesPath || matchesName) {
|
||||
results.push(fullPath);
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await search(fullPath);
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await search(rootPath);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates content if it exceeds the model's context limit.
|
||||
* @param {string} content - The content to potentially truncate
|
||||
* @param {object} aibitat - The aibitat instance with model/provider info
|
||||
* @param {string} [truncationMessage] - Optional custom message
|
||||
* @returns {{content: string, wasTruncated: boolean}} The content and truncation status
|
||||
*/
|
||||
truncateContentForContext(content, aibitat, truncationMessage = null) {
|
||||
const { TokenManager } = require("../../../../helpers/tiktoken");
|
||||
const Provider = require("../../providers/ai-provider");
|
||||
|
||||
const contextLimit = Provider.contextLimit(aibitat.provider, aibitat.model);
|
||||
const reserveForResponse = Math.floor(
|
||||
contextLimit * FilesystemManager.CONTEXT_RESERVE_RATIO
|
||||
);
|
||||
const maxTokens = contextLimit - reserveForResponse;
|
||||
|
||||
const tokenManager = new TokenManager(aibitat.model);
|
||||
const tokenCount = tokenManager.countFromString(content);
|
||||
|
||||
if (tokenCount <= maxTokens) {
|
||||
return { content, wasTruncated: false };
|
||||
}
|
||||
|
||||
const avgCharsPerToken = content.length / tokenCount;
|
||||
let targetChars = Math.floor(maxTokens * avgCharsPerToken);
|
||||
let truncated = content.slice(0, targetChars);
|
||||
|
||||
const lastNewline = truncated.lastIndexOf("\n");
|
||||
if (lastNewline > targetChars * 0.8) {
|
||||
truncated = truncated.slice(0, lastNewline);
|
||||
}
|
||||
|
||||
const defaultMessage =
|
||||
"[Content truncated - exceeds context limit. Consider reading smaller portions.]";
|
||||
const message = truncationMessage || defaultMessage;
|
||||
|
||||
return {
|
||||
content: truncated + "\n\n" + message,
|
||||
wasTruncated: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file path points to an image file.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @returns {boolean} True if the file is an image
|
||||
*/
|
||||
isImageFile(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return FilesystemManager.IMAGE_EXTENSIONS.includes(ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type for an image file.
|
||||
* @param {string} filePath - Path to the file
|
||||
* @returns {string|null} MIME type or null if not an image
|
||||
*/
|
||||
getImageMimeType(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return FilesystemManager.IMAGE_MIME_TYPES[ext] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an image file and return it as an attachment object.
|
||||
* @param {string} filePath - Validated absolute path to the image file
|
||||
* @returns {Promise<{name: string, mime: string, contentString: string}|null>} Attachment object or null on error
|
||||
*/
|
||||
async readImageAsAttachment(filePath) {
|
||||
try {
|
||||
const mime = this.getImageMimeType(filePath);
|
||||
if (!mime) return null;
|
||||
|
||||
const buffer = await fs.readFile(filePath);
|
||||
const base64 = buffer.toString("base64");
|
||||
const filename = path.basename(filePath);
|
||||
|
||||
return {
|
||||
name: filename,
|
||||
mime,
|
||||
contentString: `data:${mime};base64,${base64}`,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error reading image file ${filePath}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FilesystemManager();
|
||||
175
server/utils/agents/aibitat/plugins/filesystem/list-directory.js
Normal file
175
server/utils/agents/aibitat/plugins/filesystem/list-directory.js
Normal file
@@ -0,0 +1,175 @@
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const filesystem = require("./lib.js");
|
||||
const { humanFileSize } = require("../../../../helpers");
|
||||
|
||||
module.exports.FilesystemListDirectory = {
|
||||
name: "filesystem-list-directory",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-list-directory",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Get a detailed listing of all files and directories in a specified path. " +
|
||||
"Results clearly distinguish between files and directories with [FILE] and [DIR] " +
|
||||
"prefixes. Optionally includes file sizes and can sort by name or size. " +
|
||||
"This tool is essential for understanding directory structure and " +
|
||||
"finding specific files within a directory. Only works within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "List all files in the current folder",
|
||||
call: JSON.stringify({ path: "." }),
|
||||
},
|
||||
{
|
||||
prompt: "Show me the contents of the src directory with sizes",
|
||||
call: JSON.stringify({ path: "src", includeSizes: true }),
|
||||
},
|
||||
{
|
||||
prompt: "List files in downloads sorted by size",
|
||||
call: JSON.stringify({
|
||||
path: "downloads",
|
||||
includeSizes: true,
|
||||
sortBy: "size",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "The path of the directory to list.",
|
||||
},
|
||||
includeSizes: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description: "If true, include file sizes in the listing.",
|
||||
},
|
||||
sortBy: {
|
||||
type: "string",
|
||||
enum: ["name", "size"],
|
||||
default: "name",
|
||||
description: "Sort entries by name or size.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({
|
||||
path: dirPath = "",
|
||||
includeSizes = false,
|
||||
sortBy = "name",
|
||||
}) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-list-directory tool.`
|
||||
);
|
||||
|
||||
const validPath = await filesystem.validatePath(dirPath);
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Listing directory ${dirPath}`
|
||||
);
|
||||
|
||||
const entries = await fs.readdir(validPath, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
|
||||
if (!includeSizes) {
|
||||
// Simple listing without sizes
|
||||
const formatted = entries
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(
|
||||
(entry) =>
|
||||
`${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
this.super.introspect(
|
||||
`Found ${entries.length} items in ${dirPath}`
|
||||
);
|
||||
|
||||
return formatted || "Directory is empty";
|
||||
}
|
||||
|
||||
// Detailed listing with sizes
|
||||
const detailedEntries = await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const entryPath = path.join(validPath, entry.name);
|
||||
try {
|
||||
const stats = await fs.stat(entryPath);
|
||||
return {
|
||||
name: entry.name,
|
||||
isDirectory: entry.isDirectory(),
|
||||
size: stats.size,
|
||||
mtime: stats.mtime,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
name: entry.name,
|
||||
isDirectory: entry.isDirectory(),
|
||||
size: 0,
|
||||
mtime: new Date(0),
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Sort entries
|
||||
const sortedEntries = [...detailedEntries].sort((a, b) => {
|
||||
if (sortBy === "size") {
|
||||
return b.size - a.size;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
// Format output
|
||||
const formattedEntries = sortedEntries.map(
|
||||
(entry) =>
|
||||
`${entry.isDirectory ? "[DIR]" : "[FILE]"} ${entry.name.padEnd(30)} ${
|
||||
entry.isDirectory
|
||||
? ""
|
||||
: humanFileSize(entry.size, true, 2).padStart(10)
|
||||
}`
|
||||
);
|
||||
|
||||
// Add summary
|
||||
const totalFiles = detailedEntries.filter(
|
||||
(e) => !e.isDirectory
|
||||
).length;
|
||||
const totalDirs = detailedEntries.filter(
|
||||
(e) => e.isDirectory
|
||||
).length;
|
||||
const totalSize = detailedEntries.reduce(
|
||||
(sum, entry) => sum + (entry.isDirectory ? 0 : entry.size),
|
||||
0
|
||||
);
|
||||
|
||||
const summary = [
|
||||
"",
|
||||
`Total: ${totalFiles} files, ${totalDirs} directories`,
|
||||
`Combined size: ${humanFileSize(totalSize, true, 2)}`,
|
||||
];
|
||||
|
||||
this.super.introspect(
|
||||
`Found ${entries.length} items (${totalFiles} files, ${totalDirs} directories) in ${dirPath}`
|
||||
);
|
||||
|
||||
return [...formattedEntries, ...summary].join("\n");
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-list-directory error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error listing directory: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
95
server/utils/agents/aibitat/plugins/filesystem/move-file.js
Normal file
95
server/utils/agents/aibitat/plugins/filesystem/move-file.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const fs = require("fs/promises");
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemMoveFile = {
|
||||
name: "filesystem-move-file",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-move-file",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Move or rename files and directories. Can move files between directories " +
|
||||
"and rename them in a single operation. If the destination exists, the " +
|
||||
"operation will fail. Works across different directories and can be used " +
|
||||
"for simple renaming within the same directory. Both source and destination must be within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Rename config.json to config.backup.json",
|
||||
call: JSON.stringify({
|
||||
source: "config.json",
|
||||
destination: "config.backup.json",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Move the report file to the archive folder",
|
||||
call: JSON.stringify({
|
||||
source: "reports/monthly.pdf",
|
||||
destination: "archive/monthly-march.pdf",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
source: {
|
||||
type: "string",
|
||||
description: "The path of the file or directory to move.",
|
||||
},
|
||||
destination: {
|
||||
type: "string",
|
||||
description:
|
||||
"The destination path where the file or directory should be moved to.",
|
||||
},
|
||||
},
|
||||
required: ["source", "destination"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ source = "", destination = "" }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-move-file tool.`
|
||||
);
|
||||
|
||||
const validSourcePath = await filesystem.validatePath(source);
|
||||
const validDestPath = await filesystem.validatePath(destination);
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Moving ${source} to ${destination}`
|
||||
);
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { source, destination },
|
||||
description: "Move a file or directory to a new location",
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
await fs.rename(validSourcePath, validDestPath);
|
||||
this.super.introspect(
|
||||
`Successfully moved ${source} to ${destination}`
|
||||
);
|
||||
return `Successfully moved ${source} to ${destination}`;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-move-file error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error moving file: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
const path = require("path");
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemReadMultipleFiles = {
|
||||
name: "filesystem-read-multiple-files",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-read-multiple-files",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Read multiple files at once when you know their exact paths. " +
|
||||
"Supports many file types: text, code, PDFs, Word docs, audio/video (transcribed to text), and more. " +
|
||||
"Image files (png, jpg, jpeg, gif, webp, svg, bmp) are automatically attached for you to view and analyze visually. " +
|
||||
"IMPORTANT: If you don't know the file paths, use 'filesystem-search-files' first " +
|
||||
"with 'includeFileContents: true' to find and read files in one step. " +
|
||||
"Each file's content is returned with its path. Failed reads won't stop the operation.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Read both the package.json and README.md files",
|
||||
call: JSON.stringify({ paths: ["package.json", "README.md"] }),
|
||||
},
|
||||
{
|
||||
prompt: "Compare the config files in dev and prod folders",
|
||||
call: JSON.stringify({
|
||||
paths: ["dev/config.json", "prod/config.json"],
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Show me all the screenshots",
|
||||
call: JSON.stringify({
|
||||
paths: ["screenshot1.png", "screenshot2.png"],
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
paths: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
minItems: 1,
|
||||
description:
|
||||
"Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories.",
|
||||
},
|
||||
},
|
||||
required: ["paths"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ paths = [] }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-read-multiple-files tool.`
|
||||
);
|
||||
|
||||
if (!Array.isArray(paths) || paths.length === 0) {
|
||||
return "Error: At least one file path must be provided.";
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Reading ${paths.length} files`
|
||||
);
|
||||
|
||||
const results = await Promise.all(
|
||||
paths.map(async (filePath) => {
|
||||
try {
|
||||
const validPath = await filesystem.validatePath(filePath);
|
||||
const filename = path.basename(validPath);
|
||||
|
||||
if (filesystem.isImageFile(validPath)) {
|
||||
const attachment =
|
||||
await filesystem.readImageAsAttachment(validPath);
|
||||
if (attachment) {
|
||||
this.super.addToolAttachment?.(attachment);
|
||||
return {
|
||||
filePath,
|
||||
content: `[Image "${filename}" attached for viewing]`,
|
||||
success: true,
|
||||
isImage: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
filePath,
|
||||
content: `Error - Could not read image file`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const content = await filesystem.readFileContent(validPath);
|
||||
|
||||
this.super.addCitation?.({
|
||||
id: `fs-${Buffer.from(validPath).toString("base64url").slice(0, 32)}`,
|
||||
title: filename,
|
||||
text: content,
|
||||
chunkSource: validPath,
|
||||
score: null,
|
||||
});
|
||||
|
||||
return { filePath, content, success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
filePath,
|
||||
content: `Error - ${error.message}`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const combinedContent = results
|
||||
.map((r) =>
|
||||
r.success
|
||||
? `${r.filePath}:\n${r.content}\n`
|
||||
: `${r.filePath}: ${r.content}`
|
||||
)
|
||||
.join("\n---\n");
|
||||
|
||||
const { content: finalContent, wasTruncated } =
|
||||
filesystem.truncateContentForContext(
|
||||
combinedContent,
|
||||
this.super,
|
||||
"[Content truncated - combined files exceed context limit. Consider reading fewer files at once.]"
|
||||
);
|
||||
|
||||
if (wasTruncated) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Combined content was truncated to fit context limit`
|
||||
);
|
||||
}
|
||||
|
||||
const imageCount = results.filter((r) => r.isImage).length;
|
||||
const textCount = results.filter(
|
||||
(r) => r.success && !r.isImage
|
||||
).length;
|
||||
let introspectMsg = `Successfully processed ${paths.length} files`;
|
||||
if (imageCount > 0) {
|
||||
introspectMsg += ` (${imageCount} image${imageCount > 1 ? "s" : ""} attached, ${textCount} text file${textCount !== 1 ? "s" : ""} read)`;
|
||||
}
|
||||
this.super.introspect(introspectMsg);
|
||||
|
||||
return finalContent;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-read-multiple-files error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error reading files: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
142
server/utils/agents/aibitat/plugins/filesystem/read-text-file.js
Normal file
142
server/utils/agents/aibitat/plugins/filesystem/read-text-file.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const path = require("path");
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemReadTextFile = {
|
||||
name: "filesystem-read-text-file",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-read-text-file",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Read the contents of a file from the file system. " +
|
||||
"Supports many file types: text, code, PDFs, Word docs, audio/video (transcribed to text), and more. " +
|
||||
"Image files (png, jpg, jpeg, gif, webp, svg, bmp) are automatically attached for you to view and analyze visually. " +
|
||||
"IMPORTANT: Only use this tool when you know the exact file path. " +
|
||||
"If you don't know where a file is located, use 'filesystem-search-files' first " +
|
||||
"to find it (e.g., search for '*.csv' or the filename). " +
|
||||
"Use the 'head' parameter to read only the first N lines, or 'tail' for the last N lines (text files only). " +
|
||||
"Only works within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Read the contents of config.json",
|
||||
call: JSON.stringify({ path: "config.json" }),
|
||||
},
|
||||
{
|
||||
prompt: "Show me the last 50 lines of the log file",
|
||||
call: JSON.stringify({ path: "logs/app.log", tail: 50 }),
|
||||
},
|
||||
{
|
||||
prompt: "Read just the first 10 lines of README.md",
|
||||
call: JSON.stringify({ path: "README.md", head: 10 }),
|
||||
},
|
||||
{
|
||||
prompt: "Show me the screenshot.png image",
|
||||
call: JSON.stringify({ path: "screenshot.png" }),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path to the file to read. Can be relative to the allowed directory or absolute within allowed directories.",
|
||||
},
|
||||
head: {
|
||||
type: "number",
|
||||
description:
|
||||
"If provided, returns only the first N lines of the file.",
|
||||
},
|
||||
tail: {
|
||||
type: "number",
|
||||
description:
|
||||
"If provided, returns only the last N lines of the file.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ path: filePath = "", head, tail }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-read-text-file tool.`
|
||||
);
|
||||
|
||||
if (head && tail) {
|
||||
return "Error: Cannot specify both head and tail parameters simultaneously.";
|
||||
}
|
||||
|
||||
const validPath = await filesystem.validatePath(filePath);
|
||||
|
||||
if (filesystem.isImageFile(validPath)) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Detected image file ${filePath}, attaching for viewing`
|
||||
);
|
||||
const attachment =
|
||||
await filesystem.readImageAsAttachment(validPath);
|
||||
if (attachment) {
|
||||
this.super.addToolAttachment?.(attachment);
|
||||
const filename = path.basename(validPath);
|
||||
return `Image file "${filename}" has been attached and is now visible in the conversation. You can describe what you see in the image.`;
|
||||
}
|
||||
return `Error: Could not read image file "${path.basename(validPath)}"`;
|
||||
}
|
||||
|
||||
this.super.introspect(`${this.caller}: Reading file ${filePath}`);
|
||||
|
||||
let content;
|
||||
if (tail) {
|
||||
content = await filesystem.tailFile(validPath, tail);
|
||||
this.super.introspect(
|
||||
`Retrieved last ${tail} lines of ${filePath}`
|
||||
);
|
||||
} else if (head) {
|
||||
content = await filesystem.headFile(validPath, head);
|
||||
this.super.introspect(
|
||||
`Retrieved first ${head} lines of ${filePath}`
|
||||
);
|
||||
} else {
|
||||
content = await filesystem.readFileContent(validPath);
|
||||
this.super.introspect(`Successfully read ${filePath}`);
|
||||
}
|
||||
|
||||
const { content: finalContent, wasTruncated } =
|
||||
filesystem.truncateContentForContext(
|
||||
content,
|
||||
this.super,
|
||||
"[Content truncated - file exceeds context limit. Use head/tail parameters to read specific portions.]"
|
||||
);
|
||||
|
||||
if (wasTruncated) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: File content was truncated to fit context limit`
|
||||
);
|
||||
}
|
||||
|
||||
const filename = path.basename(validPath);
|
||||
this.super.addCitation?.({
|
||||
id: `fs-${Buffer.from(validPath).toString("base64url").slice(0, 32)}`,
|
||||
title: filename,
|
||||
text: finalContent,
|
||||
chunkSource: validPath,
|
||||
score: null,
|
||||
});
|
||||
|
||||
return finalContent;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-read-text-file error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error reading file: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
461
server/utils/agents/aibitat/plugins/filesystem/search-files.js
Normal file
461
server/utils/agents/aibitat/plugins/filesystem/search-files.js
Normal file
@@ -0,0 +1,461 @@
|
||||
const path = require("path");
|
||||
const filesystem = require("./lib.js");
|
||||
const { safeJsonParse } = require("../../../../http/index.js");
|
||||
|
||||
module.exports.FilesystemSearchFiles = {
|
||||
name: "filesystem-search-files",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-search-files",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Search for files by name or content. USE THIS FIRST when you need to find a file " +
|
||||
"but don't know its exact location. " +
|
||||
"Two modes: 'glob' matches file paths/names (e.g., '*.csv', 'config'), " +
|
||||
"'content' searches inside files using regex (like grep). " +
|
||||
"Set 'includeFileContents: true' to also read and return the full contents of matching files " +
|
||||
"in a single operation (useful when you need to find AND read files). " +
|
||||
"Simple patterns like 'sales.csv' automatically match files containing that string anywhere.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Find all JavaScript files",
|
||||
call: JSON.stringify({
|
||||
pattern: "**/*.js",
|
||||
mode: "glob",
|
||||
includeFileContents: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Find all CSV files",
|
||||
call: JSON.stringify({
|
||||
pattern: "*.csv",
|
||||
mode: "glob",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Search for error handling code",
|
||||
call: JSON.stringify({
|
||||
pattern: "catch.*error",
|
||||
mode: "content",
|
||||
filePattern: "*.js",
|
||||
includeFileContents: true,
|
||||
maxFilesToRead: 3,
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Find the config file and show its contents",
|
||||
call: JSON.stringify({
|
||||
pattern: "config",
|
||||
mode: "glob",
|
||||
includeFileContents: true,
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
pattern: {
|
||||
type: "string",
|
||||
description:
|
||||
"For glob mode: a glob pattern to match file paths. " +
|
||||
"For content mode: the text or regex pattern to search for in file contents.",
|
||||
},
|
||||
mode: {
|
||||
type: "string",
|
||||
enum: ["glob", "content"],
|
||||
default: "glob",
|
||||
description:
|
||||
"Search mode: 'glob' for matching file paths, 'content' for searching file contents.",
|
||||
},
|
||||
filePattern: {
|
||||
type: "string",
|
||||
description:
|
||||
"For content mode only: glob pattern to filter which files to search (e.g., '*.js', '*.{ts,tsx}').",
|
||||
},
|
||||
excludePatterns: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
default: [],
|
||||
description:
|
||||
"Patterns to exclude from search (e.g., 'node_modules', '*.log').",
|
||||
},
|
||||
caseSensitive: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description:
|
||||
"For content mode: whether the search should be case-sensitive.",
|
||||
},
|
||||
maxResults: {
|
||||
type: "number",
|
||||
default: 100,
|
||||
description: "Maximum number of results to return.",
|
||||
},
|
||||
includeFileContents: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description:
|
||||
"If true, read and return the full contents of matching files (limited by maxFilesToRead). " +
|
||||
"Useful when you need to analyze files, not just find them.",
|
||||
},
|
||||
maxFilesToRead: {
|
||||
type: "number",
|
||||
default: 5,
|
||||
description:
|
||||
"When includeFileContents is true, maximum number of files to read contents from.",
|
||||
},
|
||||
},
|
||||
required: ["pattern"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({
|
||||
pattern = "",
|
||||
mode = "glob",
|
||||
filePattern = "",
|
||||
excludePatterns = [],
|
||||
caseSensitive = true,
|
||||
maxResults = 100,
|
||||
includeFileContents = false,
|
||||
maxFilesToRead = 5,
|
||||
}) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-search-files tool.`
|
||||
);
|
||||
|
||||
await filesystem.ensureInitialized();
|
||||
const allowedDirs = filesystem.getAllowedDirectories();
|
||||
|
||||
if (allowedDirs.length === 0) {
|
||||
return "Error: No allowed directories configured";
|
||||
}
|
||||
|
||||
if (mode === "glob") {
|
||||
const allResults = [];
|
||||
const seenPaths = new Set();
|
||||
|
||||
// If pattern has no glob characters, convert to wildcard patterns
|
||||
// e.g., "sales" matches files containing "sales" anywhere in the name
|
||||
const hasGlobChars = /[*?[\]{}]/.test(pattern);
|
||||
const effectivePatterns = hasGlobChars
|
||||
? [pattern]
|
||||
: [`*${pattern}*`, `**/*${pattern}*`];
|
||||
|
||||
const patternNote =
|
||||
effectivePatterns.length > 1 ||
|
||||
effectivePatterns[0] !== pattern
|
||||
? ` (using pattern: ${effectivePatterns.join(" or ")})`
|
||||
: "";
|
||||
this.super.introspect(
|
||||
`${this.caller}: Searching for "${pattern}"${patternNote} in ${allowedDirs.length} allowed director${allowedDirs.length === 1 ? "y" : "ies"}`
|
||||
);
|
||||
|
||||
for (const dir of allowedDirs) {
|
||||
try {
|
||||
const { files } = searchFilesWithRipgrepGlob({
|
||||
searchPath: dir,
|
||||
patterns: effectivePatterns,
|
||||
excludePatterns,
|
||||
maxResults: maxResults - allResults.length,
|
||||
});
|
||||
|
||||
for (const filePath of files) {
|
||||
if (!seenPaths.has(filePath)) {
|
||||
seenPaths.add(filePath);
|
||||
allResults.push(filePath);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip directories that fail (e.g., don't exist)
|
||||
}
|
||||
}
|
||||
|
||||
const limitedResults = allResults.slice(0, maxResults);
|
||||
this.super.introspect(
|
||||
`Found ${allResults.length} matching files${allResults.length > maxResults ? ` (showing first ${maxResults})` : ""}`
|
||||
);
|
||||
|
||||
if (limitedResults.length === 0) return "No matches found";
|
||||
|
||||
if (includeFileContents) {
|
||||
return await readMatchingFileContents.call(
|
||||
this,
|
||||
limitedResults,
|
||||
maxFilesToRead
|
||||
);
|
||||
}
|
||||
|
||||
return limitedResults.join("\n");
|
||||
}
|
||||
|
||||
// Content search mode using ripgrep across all allowed directories
|
||||
this.super.introspect(
|
||||
`${this.caller}: Searching for "${pattern}" in file contents across ${allowedDirs.length} allowed director${allowedDirs.length === 1 ? "y" : "ies"}`
|
||||
);
|
||||
|
||||
const allResults = [];
|
||||
const seenKeys = new Set();
|
||||
|
||||
for (const dir of allowedDirs) {
|
||||
try {
|
||||
const results = searchWithRipgrep({
|
||||
searchPath: dir,
|
||||
pattern,
|
||||
filePattern,
|
||||
excludePatterns,
|
||||
caseSensitive,
|
||||
maxResults: maxResults - allResults.length,
|
||||
});
|
||||
|
||||
for (const result of results) {
|
||||
const key = `${result.file}:${result.line}`;
|
||||
if (!seenKeys.has(key)) {
|
||||
seenKeys.add(key);
|
||||
allResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (allResults.length >= maxResults) break;
|
||||
} catch {
|
||||
// Skip directories that fail
|
||||
}
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`Found ${allResults.length} matches${allResults.length > maxResults ? ` (showing first ${maxResults})` : ""}`
|
||||
);
|
||||
|
||||
if (includeFileContents) {
|
||||
const uniqueFiles = [...new Set(allResults.map((r) => r.file))];
|
||||
return await readMatchingFileContents.call(
|
||||
this,
|
||||
uniqueFiles,
|
||||
maxFilesToRead
|
||||
);
|
||||
}
|
||||
|
||||
return formatSearchResults(allResults, maxResults);
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-search-files error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error searching files: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Search for files by glob pattern using ripgrep (fast file listing).
|
||||
* @returns {{ files: string[], method: string }}
|
||||
*/
|
||||
function searchFilesWithRipgrepGlob({
|
||||
searchPath,
|
||||
patterns,
|
||||
excludePatterns = [],
|
||||
maxResults = 100,
|
||||
}) {
|
||||
const { spawnSync } = require("child_process");
|
||||
let rgPath;
|
||||
try {
|
||||
({ rgPath } = require("@vscode/ripgrep"));
|
||||
} catch {
|
||||
throw new Error("@vscode/ripgrep not installed");
|
||||
}
|
||||
|
||||
// Build ripgrep arguments for file listing
|
||||
const args = [
|
||||
"--files", // List files instead of searching content
|
||||
"--no-ignore", // Search all files, even those in .gitignore
|
||||
];
|
||||
|
||||
// Add glob patterns (ripgrep uses --glob for filtering --files output)
|
||||
for (const pattern of patterns) args.push("--glob", pattern);
|
||||
for (const exclude of excludePatterns) args.push("--glob", `!${exclude}`);
|
||||
|
||||
args.push(searchPath);
|
||||
const result = spawnSync(rgPath, args, {
|
||||
encoding: "utf-8",
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
});
|
||||
|
||||
if (result.status > 1) {
|
||||
throw new Error(
|
||||
result.stderr || `ripgrep exited with code ${result.status}`
|
||||
);
|
||||
}
|
||||
|
||||
// unique files
|
||||
const files = new Set();
|
||||
if (!result.stdout) return { files: Array.from(files), method: "ripgrep" };
|
||||
|
||||
const lines = result.stdout.trim().split("\n").filter(Boolean);
|
||||
for (const line of lines) {
|
||||
files.add(line);
|
||||
if (files.size >= maxResults) break;
|
||||
}
|
||||
|
||||
return { files: Array.from(files), method: "ripgrep" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Search file contents using @vscode/ripgrep binary directly via spawnSync.
|
||||
*/
|
||||
function searchWithRipgrep({
|
||||
searchPath,
|
||||
pattern,
|
||||
filePattern,
|
||||
excludePatterns,
|
||||
caseSensitive,
|
||||
maxResults,
|
||||
}) {
|
||||
const { spawnSync } = require("child_process");
|
||||
let rgPath;
|
||||
try {
|
||||
({ rgPath } = require("@vscode/ripgrep"));
|
||||
} catch {
|
||||
throw new Error("@vscode/ripgrep not installed");
|
||||
}
|
||||
|
||||
// Build ripgrep arguments
|
||||
const args = [
|
||||
"--json", // JSON output for structured parsing
|
||||
"--line-number", // Include line numbers
|
||||
"--no-ignore", // Search all files, even those in .gitignore
|
||||
"--max-count",
|
||||
String(maxResults),
|
||||
];
|
||||
|
||||
if (!caseSensitive) args.push("--ignore-case");
|
||||
if (filePattern) args.push("--glob", filePattern);
|
||||
for (const exclude of excludePatterns) args.push("--glob", `!${exclude}`);
|
||||
|
||||
// Pattern and path come last
|
||||
args.push(pattern, searchPath);
|
||||
const result = spawnSync(rgPath, args, {
|
||||
encoding: "utf-8",
|
||||
maxBuffer: 10 * 1024 * 1024, // 10MB
|
||||
});
|
||||
|
||||
// Exit code 1 means no matches (not an error)
|
||||
if (result.status > 1) {
|
||||
throw new Error(
|
||||
result.stderr || `ripgrep exited with code ${result.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
if (!result.stdout) return results;
|
||||
const matches = safeJsonParse(result.stdout, []).filter(
|
||||
(m) => m.type === "match" && m.data
|
||||
);
|
||||
|
||||
for (const match of matches) {
|
||||
results.push({
|
||||
file: match.data.path?.text || match.data.path,
|
||||
line: match.data.line_number,
|
||||
content: (match.data.lines?.text || "").trim(),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format search results for display.
|
||||
*/
|
||||
function formatSearchResults(results, maxResults) {
|
||||
if (results.length === 0) {
|
||||
return "No matches found";
|
||||
}
|
||||
|
||||
const formatted = results
|
||||
.slice(0, maxResults)
|
||||
.map((r) => `${r.file}:${r.line}: ${r.content}`)
|
||||
.join("\n");
|
||||
|
||||
const suffix =
|
||||
results.length > maxResults
|
||||
? `\n\n... and ${results.length - maxResults} more matches`
|
||||
: "";
|
||||
|
||||
return formatted + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read contents of matching files and add citations.
|
||||
* @param {string[]} filePaths - Array of file paths to read
|
||||
* @param {number} maxFiles - Maximum number of files to read
|
||||
* @returns {Promise<string>} Combined file contents
|
||||
*/
|
||||
async function readMatchingFileContents(filePaths, maxFiles) {
|
||||
const filesToRead = filePaths.slice(0, maxFiles);
|
||||
const skippedCount = filePaths.length - filesToRead.length;
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Reading contents of ${filesToRead.length} file${filesToRead.length === 1 ? "" : "s"}${skippedCount > 0 ? ` (${skippedCount} more files not read)` : ""}`
|
||||
);
|
||||
|
||||
const results = [];
|
||||
for (const filePath of filesToRead) {
|
||||
try {
|
||||
const content = await filesystem.readFileContent(filePath);
|
||||
const filename = path.basename(filePath);
|
||||
|
||||
this.super.addCitation?.({
|
||||
id: `fs-${Buffer.from(filePath).toString("base64url").slice(0, 32)}`,
|
||||
title: filename,
|
||||
text: content,
|
||||
chunkSource: filePath,
|
||||
score: null,
|
||||
});
|
||||
|
||||
results.push({
|
||||
path: filePath,
|
||||
content,
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
path: filePath,
|
||||
content: `Error reading file: ${error.message}`,
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const combinedContent = results
|
||||
.map((r) =>
|
||||
r.success
|
||||
? `=== ${r.path} ===\n${r.content}`
|
||||
: `=== ${r.path} ===\n${r.content}`
|
||||
)
|
||||
.join("\n\n---\n\n");
|
||||
|
||||
const { content: finalContent, wasTruncated } =
|
||||
filesystem.truncateContentForContext(
|
||||
combinedContent,
|
||||
this.super,
|
||||
`[Content truncated - file contents exceed context limit. Try reducing maxFilesToRead or searching more specifically.]`
|
||||
);
|
||||
|
||||
if (wasTruncated) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: File contents were truncated to fit context limit`
|
||||
);
|
||||
}
|
||||
|
||||
const header =
|
||||
skippedCount > 0
|
||||
? `Found ${filePaths.length} matching files. Showing contents of first ${filesToRead.length}:\n\n`
|
||||
: "";
|
||||
|
||||
return header + finalContent;
|
||||
}
|
||||
89
server/utils/agents/aibitat/plugins/filesystem/write-file.js
Normal file
89
server/utils/agents/aibitat/plugins/filesystem/write-file.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const filesystem = require("./lib.js");
|
||||
|
||||
module.exports.FilesystemWriteFile = {
|
||||
name: "filesystem-write-file",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "filesystem-write-file",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Create a new file or completely overwrite an existing file with new content. " +
|
||||
"Use with caution as it will overwrite existing files without warning. " +
|
||||
"Handles text content with proper encoding. Only works within allowed directories.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Create a new config file with these settings",
|
||||
call: JSON.stringify({
|
||||
path: "config.json",
|
||||
content: '{"debug": true, "port": 3000}',
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Write a hello world Python script",
|
||||
call: JSON.stringify({
|
||||
path: "hello.py",
|
||||
content: 'print("Hello, World!")',
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path where the file should be created or overwritten.",
|
||||
},
|
||||
content: {
|
||||
type: "string",
|
||||
description: "The content to write to the file.",
|
||||
},
|
||||
},
|
||||
required: ["path", "content"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ path: filePath = "", content = "" }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the filesystem-write-file tool.`
|
||||
);
|
||||
|
||||
const validPath = await filesystem.validatePath(filePath);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Writing to file ${filePath}`
|
||||
);
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { path: filePath, content },
|
||||
description: "Write content to a file",
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
await filesystem.writeFileContent(validPath, content);
|
||||
this.super.introspect(`Successfully wrote to ${filePath}`);
|
||||
return `Successfully wrote to ${filePath}`;
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`filesystem-write-file error: ${e.message}`
|
||||
);
|
||||
this.super.introspect(`Error: ${e.message}`);
|
||||
return `Error writing file: ${e.message}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -7,6 +7,7 @@ const { chatHistory } = require("./chat-history.js");
|
||||
const { memory } = require("./memory.js");
|
||||
const { rechart } = require("./rechart.js");
|
||||
const { sqlAgent } = require("./sql-agent/index.js");
|
||||
const { filesystemAgent } = require("./filesystem/index.js");
|
||||
|
||||
module.exports = {
|
||||
webScraping,
|
||||
@@ -18,6 +19,7 @@ module.exports = {
|
||||
memory,
|
||||
rechart,
|
||||
sqlAgent,
|
||||
filesystemAgent,
|
||||
|
||||
// Plugin name aliases so they can be pulled by slug as well.
|
||||
[webScraping.name]: webScraping,
|
||||
@@ -29,4 +31,5 @@ module.exports = {
|
||||
[memory.name]: memory,
|
||||
[rechart.name]: rechart,
|
||||
[sqlAgent.name]: sqlAgent,
|
||||
[filesystemAgent.name]: filesystemAgent,
|
||||
};
|
||||
|
||||
@@ -72,6 +72,15 @@ async function agentSkillsFromSystemSettings() {
|
||||
systemFunctions.push(AgentPlugins[skill].name);
|
||||
});
|
||||
|
||||
// Load disabled filesystem sub-skills
|
||||
const _disabledFilesystemSkills = safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
{ label: "disabled_filesystem_skills" },
|
||||
"[]"
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// Load non-imported built-in skills that are configurable.
|
||||
const _setting = safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
@@ -87,6 +96,16 @@ async function agentSkillsFromSystemSettings() {
|
||||
// need to be named via `${parent}#${child}` naming convention
|
||||
if (Array.isArray(AgentPlugins[skillName].plugin)) {
|
||||
for (const subPlugin of AgentPlugins[skillName].plugin) {
|
||||
/**
|
||||
* If the filesystem tool is not available, or the sub-skill is explicitly disabled, skip it
|
||||
* This is a docker specific skill so it cannot be used in other environments.
|
||||
*/
|
||||
if (skillName === "filesystem-agent") {
|
||||
const filesystemTool = require("./aibitat/plugins/filesystem/lib");
|
||||
if (!filesystemTool.isToolAvailable()) continue;
|
||||
if (_disabledFilesystemSkills.includes(subPlugin.name)) continue;
|
||||
}
|
||||
|
||||
systemFunctions.push(
|
||||
`${AgentPlugins[skillName].name}#${subPlugin.name}`
|
||||
);
|
||||
|
||||
@@ -256,14 +256,19 @@ class CollectorApi {
|
||||
* Parse a document without processing it
|
||||
* - Will append the options to the request body
|
||||
* @param {string} filename - The filename of the document to parse
|
||||
* @param {Object} parseOptions - Additional options for parsing
|
||||
* @param {string} parseOptions.absolutePath - If provided, use this absolute path instead of looking in the hotdir
|
||||
* @returns {Promise<Object>} - The response from the collector API
|
||||
*/
|
||||
async parseDocument(filename = "") {
|
||||
async parseDocument(filename = "", parseOptions = {}) {
|
||||
if (!filename) return false;
|
||||
|
||||
const data = JSON.stringify({
|
||||
filename,
|
||||
options: this.#attachOptions(),
|
||||
options: {
|
||||
...this.#attachOptions(),
|
||||
absolutePath: parseOptions.absolutePath || null,
|
||||
},
|
||||
});
|
||||
|
||||
return await fetch(`${this.endpoint}/parse`, {
|
||||
|
||||
@@ -3649,6 +3649,15 @@
|
||||
resolved "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz"
|
||||
integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==
|
||||
|
||||
"@vscode/ripgrep@1.17.1":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/ripgrep/-/ripgrep-1.17.1.tgz#7d29cdd80e343da8d973927ac91fcc9788aea484"
|
||||
integrity sha512-xTs7DGyAO3IsJYOCTBP8LnTvPiYVKEuyv8s0xyJDBXfs8rhBfqnZPvb6xDT+RnwWzcXqW27xLS/aGrkjX7lNWw==
|
||||
dependencies:
|
||||
https-proxy-agent "^7.0.2"
|
||||
proxy-from-env "^1.1.0"
|
||||
yauzl "^2.9.2"
|
||||
|
||||
"@xenova/transformers@^2.14.0", "@xenova/transformers@^2.17.2":
|
||||
version "2.17.2"
|
||||
resolved "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz"
|
||||
@@ -3729,6 +3738,11 @@ agent-base@^7.0.2, agent-base@^7.1.0:
|
||||
dependencies:
|
||||
debug "^4.3.4"
|
||||
|
||||
agent-base@^7.1.2:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
|
||||
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
|
||||
|
||||
agentkeepalive@^4.2.1:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz"
|
||||
@@ -4213,6 +4227,11 @@ bson@^6.2.0:
|
||||
resolved "https://registry.npmjs.org/bson/-/bson-6.6.0.tgz"
|
||||
integrity sha512-BVINv2SgcMjL4oYbBuCQTpE3/VKOSxrOA8Cj/wQP7izSzlBGVomdm+TcUd0Pzy0ytLSSDweCKQ6X3f5veM5LQA==
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz"
|
||||
@@ -4935,6 +4954,11 @@ detect-libc@^2.0.0, detect-libc@^2.0.2:
|
||||
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz"
|
||||
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
|
||||
|
||||
diff@7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a"
|
||||
integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==
|
||||
|
||||
digest-fetch@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz"
|
||||
@@ -5499,7 +5523,7 @@ eventsource@^3.0.2:
|
||||
|
||||
execa@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.3"
|
||||
@@ -5702,6 +5726,13 @@ fastest-levenshtein@^1.0.7:
|
||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
||||
integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fecha@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz"
|
||||
@@ -6010,7 +6041,7 @@ get-proto@^1.0.1:
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||
|
||||
get-symbol-description@^1.0.2:
|
||||
@@ -6270,6 +6301,14 @@ https-proxy-agent@^7.0.0:
|
||||
agent-base "^7.0.2"
|
||||
debug "4"
|
||||
|
||||
https-proxy-agent@^7.0.2:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
|
||||
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
|
||||
dependencies:
|
||||
agent-base "^7.1.2"
|
||||
debug "4"
|
||||
|
||||
human-interval@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz"
|
||||
@@ -6279,7 +6318,7 @@ human-interval@^2.0.1:
|
||||
|
||||
human-signals@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
humanize-ms@^1.2.1:
|
||||
@@ -7285,7 +7324,7 @@ merge-descriptors@^2.0.0:
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||
|
||||
methods@~1.1.2:
|
||||
@@ -7329,7 +7368,7 @@ mime@^3.0.0:
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
mimic-response@^3.1.0:
|
||||
@@ -7620,7 +7659,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
|
||||
npm-run-path@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
@@ -7753,7 +7792,7 @@ one-time@^1.0.0:
|
||||
|
||||
onetime@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
@@ -7988,6 +8027,11 @@ path-to-regexp@~0.1.12:
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
|
||||
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
@@ -8837,7 +8881,7 @@ side-channel@^1.0.6, side-channel@^1.1.0:
|
||||
|
||||
signal-exit@^3.0.3:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
signal-exit@^4.1.0:
|
||||
@@ -9074,7 +9118,7 @@ strip-ansi@^7.0.1, strip-ansi@^7.1.2:
|
||||
|
||||
strip-final-newline@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||
|
||||
strip-json-comments@^3.1.1:
|
||||
@@ -9886,6 +9930,14 @@ yargs@^17.7.2:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yauzl@^2.9.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user