From 3dedcede34721b041de7cb33ba254a0ad1cecbb6 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Thu, 26 Mar 2026 14:07:46 -0700 Subject: [PATCH] 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 --- collector/index.js | 1 + .../processSingleFile/convert/asAudio.js | 6 +- collector/processSingleFile/convert/asDocx.js | 4 +- collector/processSingleFile/convert/asEPub.js | 4 +- .../processSingleFile/convert/asImage.js | 4 +- collector/processSingleFile/convert/asMbox.js | 4 +- .../processSingleFile/convert/asOfficeMime.js | 4 +- .../processSingleFile/convert/asPDF/index.js | 4 +- collector/processSingleFile/convert/asTxt.js | 4 +- collector/processSingleFile/convert/asXlsx.js | 2 +- collector/processSingleFile/index.js | 18 +- .../ToolsMenu/Tabs/AgentSkills/index.jsx | 11 +- frontend/src/locales/ar/common.js | 57 +- frontend/src/locales/cs/common.js | 58 +- frontend/src/locales/da/common.js | 56 +- frontend/src/locales/de/common.js | 60 +- frontend/src/locales/en/common.js | 54 ++ frontend/src/locales/es/common.js | 60 +- frontend/src/locales/et/common.js | 56 +- frontend/src/locales/fa/common.js | 55 +- frontend/src/locales/fr/common.js | 60 +- frontend/src/locales/he/common.js | 55 +- frontend/src/locales/it/common.js | 57 +- frontend/src/locales/ja/common.js | 57 +- frontend/src/locales/ko/common.js | 56 +- frontend/src/locales/lt/common.js | 56 +- frontend/src/locales/lv/common.js | 58 +- frontend/src/locales/nl/common.js | 59 +- frontend/src/locales/pl/common.js | 58 +- frontend/src/locales/pt_BR/common.js | 58 +- frontend/src/locales/ro/common.js | 58 +- frontend/src/locales/ru/common.js | 57 +- frontend/src/locales/tr/common.js | 59 +- frontend/src/locales/vn/common.js | 57 +- frontend/src/locales/zh/common.js | 55 +- frontend/src/locales/zh_TW/common.js | 55 +- frontend/src/media/agents/file-system.png | Bin 0 -> 163665 bytes frontend/src/models/system.js | 15 + .../Agents/FileSystemSkillPanel/index.jsx | 328 ++++++++ frontend/src/pages/Admin/Agents/index.jsx | 29 +- frontend/src/pages/Admin/Agents/skills.js | 18 +- server/.gitignore | 1 + server/endpoints/admin.js | 3 + server/endpoints/agentSkillWhitelist.js | 18 + server/models/systemSettings.js | 11 + server/package.json | 2 + server/utils/agents/aibitat/index.js | 94 ++- .../aibitat/plugins/filesystem/copy-file.js | 121 +++ .../plugins/filesystem/create-directory.js | 84 ++ .../aibitat/plugins/filesystem/edit-file.js | 133 +++ .../plugins/filesystem/get-file-info.js | 75 ++ .../aibitat/plugins/filesystem/index.js | 33 + .../agents/aibitat/plugins/filesystem/lib.js | 787 ++++++++++++++++++ .../plugins/filesystem/list-directory.js | 175 ++++ .../aibitat/plugins/filesystem/move-file.js | 95 +++ .../plugins/filesystem/read-multiple-files.js | 157 ++++ .../plugins/filesystem/read-text-file.js | 142 ++++ .../plugins/filesystem/search-files.js | 461 ++++++++++ .../aibitat/plugins/filesystem/write-file.js | 89 ++ server/utils/agents/aibitat/plugins/index.js | 3 + server/utils/agents/defaults.js | 19 + server/utils/collectorApi/index.js | 9 +- server/yarn.lock | 70 +- 63 files changed, 4322 insertions(+), 87 deletions(-) create mode 100644 frontend/src/media/agents/file-system.png create mode 100644 frontend/src/pages/Admin/Agents/FileSystemSkillPanel/index.jsx create mode 100644 server/utils/agents/aibitat/plugins/filesystem/copy-file.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/create-directory.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/edit-file.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/get-file-info.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/index.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/lib.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/list-directory.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/move-file.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/read-multiple-files.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/read-text-file.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/search-files.js create mode 100644 server/utils/agents/aibitat/plugins/filesystem/write-file.js diff --git a/collector/index.js b/collector/index.js index 7955ce99..69cb2eba 100644 --- a/collector/index.js +++ b/collector/index.js @@ -86,6 +86,7 @@ app.post( } = await processSingleFile(targetFilename, { ...options, parseOnly: true, + absolutePath: options.absolutePath || null, }); response .status(200) diff --git a/collector/processSingleFile/convert/asAudio.js b/collector/processSingleFile/convert/asAudio.js index b4ddc8eb..bf915855 100644 --- a/collector/processSingleFile/convert/asAudio.js +++ b/collector/processSingleFile/convert/asAudio.js @@ -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` ); diff --git a/collector/processSingleFile/convert/asDocx.js b/collector/processSingleFile/convert/asDocx.js index 1f77e772..20807563 100644 --- a/collector/processSingleFile/convert/asDocx.js +++ b/collector/processSingleFile/convert/asDocx.js @@ -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] }; } diff --git a/collector/processSingleFile/convert/asEPub.js b/collector/processSingleFile/convert/asEPub.js index 283eb1e2..49d39454 100644 --- a/collector/processSingleFile/convert/asEPub.js +++ b/collector/processSingleFile/convert/asEPub.js @@ -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] }; } diff --git a/collector/processSingleFile/convert/asImage.js b/collector/processSingleFile/convert/asImage.js index 77052f14..85e941c1 100644 --- a/collector/processSingleFile/convert/asImage.js +++ b/collector/processSingleFile/convert/asImage.js @@ -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] }; } diff --git a/collector/processSingleFile/convert/asMbox.js b/collector/processSingleFile/convert/asMbox.js index 8927616a..2954e683 100644 --- a/collector/processSingleFile/convert/asMbox.js +++ b/collector/processSingleFile/convert/asMbox.js @@ -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` ); diff --git a/collector/processSingleFile/convert/asOfficeMime.js b/collector/processSingleFile/convert/asOfficeMime.js index dcd08414..00895ba4 100644 --- a/collector/processSingleFile/convert/asOfficeMime.js +++ b/collector/processSingleFile/convert/asOfficeMime.js @@ -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] }; } diff --git a/collector/processSingleFile/convert/asPDF/index.js b/collector/processSingleFile/convert/asPDF/index.js index bacfdaf5..59d26f5d 100644 --- a/collector/processSingleFile/convert/asPDF/index.js +++ b/collector/processSingleFile/convert/asPDF/index.js @@ -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] }; } diff --git a/collector/processSingleFile/convert/asTxt.js b/collector/processSingleFile/convert/asTxt.js index d32cebce..62e8e401 100644 --- a/collector/processSingleFile/convert/asTxt.js +++ b/collector/processSingleFile/convert/asTxt.js @@ -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] }; } diff --git a/collector/processSingleFile/convert/asXlsx.js b/collector/processSingleFile/convert/asXlsx.js index 4601ceff..a316d1e6 100644 --- a/collector/processSingleFile/convert/asXlsx.js +++ b/collector/processSingleFile/convert/asXlsx.js @@ -145,7 +145,7 @@ async function asXlsx({ documents: [], }; } finally { - trashFile(fullFilePath); + if (!options.absolutePath) trashFile(fullFilePath); } if (documents.length === 0) { diff --git a/collector/processSingleFile/index.js b/collector/processSingleFile/index.js index 14601272..4f2defb9 100644 --- a/collector/processSingleFile/index.js +++ b/collector/processSingleFile/index.js @@ -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.`, diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/ToolsMenu/Tabs/AgentSkills/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/ToolsMenu/Tabs/AgentSkills/index.jsx index f6f7a388..5ed32c37 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/ToolsMenu/Tabs/AgentSkills/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/ToolsMenu/Tabs/AgentSkills/index.jsx @@ -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 { diff --git a/frontend/src/locales/ar/common.js b/frontend/src/locales/ar/common.js index 7661d562..f276c92a 100644 --- a/frontend/src/locales/ar/common.js +++ b/frontend/src/locales/ar/common.js @@ -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: + "الوصول إلى نظام الملفات يمكن أن يكون خطيرًا، لأنه يمكنه تعديل أو حذف الملفات. يرجى الرجوع إلى الوثائق قبل تمكينه.", + 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", diff --git a/frontend/src/locales/cs/common.js b/frontend/src/locales/cs/common.js index 545327bd..b5e96299 100644 --- a/frontend/src/locales/cs/common.js +++ b/frontend/src/locales/cs/common.js @@ -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 dokumentace.", + 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", diff --git a/frontend/src/locales/da/common.js b/frontend/src/locales/da/common.js index ae974aaf..a6b2e3d2 100644 --- a/frontend/src/locales/da/common.js +++ b/frontend/src/locales/da/common.js @@ -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 dokumentationen 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", diff --git a/frontend/src/locales/de/common.js b/frontend/src/locales/de/common.js index fb4f5e4c..7bd574c4 100644 --- a/frontend/src/locales/de/common.js +++ b/frontend/src/locales/de/common.js @@ -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 Dokumentation.", + 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.", diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js index cb87d425..0750f52c 100644 --- a/frontend/src/locales/en/common.js +++ b/frontend/src/locales/en/common.js @@ -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 documentation 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.", }, diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js index 0b17abcb..828eb331 100644 --- a/frontend/src/locales/es/common.js +++ b/frontend/src/locales/es/common.js @@ -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 documentación 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", diff --git a/frontend/src/locales/et/common.js b/frontend/src/locales/et/common.js index 6fdc01ba..c41c561c 100644 --- a/frontend/src/locales/et/common.js +++ b/frontend/src/locales/et/common.js @@ -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 dokumentatsiooni.", + 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", diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js index e261abe7..22e4cdd3 100644 --- a/frontend/src/locales/fa/common.js +++ b/frontend/src/locales/fa/common.js @@ -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", diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js index c82464de..2bf80082 100644 --- a/frontend/src/locales/fr/common.js +++ b/frontend/src/locales/fr/common.js @@ -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 documentation 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", diff --git a/frontend/src/locales/he/common.js b/frontend/src/locales/he/common.js index dcf8d1f5..41c68e9a 100644 --- a/frontend/src/locales/he/common.js +++ b/frontend/src/locales/he/common.js @@ -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: + "גישה למערכת הקבצים עלולה להיות מסוכנת, שכן היא עלולה לשנות או למחוק קבצים. אנא התייעצו עם התיעוד לפני הפעלתה.", + 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", diff --git a/frontend/src/locales/it/common.js b/frontend/src/locales/it/common.js index 7baae496..1c520332 100644 --- a/frontend/src/locales/it/common.js +++ b/frontend/src/locales/it/common.js @@ -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 documentazione 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", diff --git a/frontend/src/locales/ja/common.js b/frontend/src/locales/ja/common.js index c34aeae4..cdd5fea9 100644 --- a/frontend/src/locales/ja/common.js +++ b/frontend/src/locales/ja/common.js @@ -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: + "ファイルシステムへのアクセスは危険であり、ファイルの内容を変更または削除する可能性があります。設定する前に、必ずのドキュメントを参照してください。", + 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 サーバー", diff --git a/frontend/src/locales/ko/common.js b/frontend/src/locales/ko/common.js index 34a126fd..5aa3fb6e 100644 --- a/frontend/src/locales/ko/common.js +++ b/frontend/src/locales/ko/common.js @@ -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: + "파일 시스템 접근은 위험할 수 있습니다. 왜냐하면 파일 내용을 변경하거나 삭제할 수 있기 때문입니다. 사용하기 전에 반드시 문서를 참조하십시오.", + 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 서버", diff --git a/frontend/src/locales/lt/common.js b/frontend/src/locales/lt/common.js index eab62632..06a15e9c 100644 --- a/frontend/src/locales/lt/common.js +++ b/frontend/src/locales/lt/common.js @@ -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 dokumentacija.", + 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", diff --git a/frontend/src/locales/lv/common.js b/frontend/src/locales/lv/common.js index d3b6c48d..9288a597 100644 --- a/frontend/src/locales/lv/common.js +++ b/frontend/src/locales/lv/common.js @@ -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 dokumentāciju 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", diff --git a/frontend/src/locales/nl/common.js b/frontend/src/locales/nl/common.js index e02df956..d8b05f7b 100644 --- a/frontend/src/locales/nl/common.js +++ b/frontend/src/locales/nl/common.js @@ -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 documentatie 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", diff --git a/frontend/src/locales/pl/common.js b/frontend/src/locales/pl/common.js index b49660f2..fd81273d 100644 --- a/frontend/src/locales/pl/common.js +++ b/frontend/src/locales/pl/common.js @@ -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ą 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", diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js index e0ae3f9d..35054992 100644 --- a/frontend/src/locales/pt_BR/common.js +++ b/frontend/src/locales/pt_BR/common.js @@ -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 documentação 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", diff --git a/frontend/src/locales/ro/common.js b/frontend/src/locales/ro/common.js index aead258c..79fb3043 100644 --- a/frontend/src/locales/ro/common.js +++ b/frontend/src/locales/ro/common.js @@ -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 î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", diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js index 47e01a77..05f7a9f5 100644 --- a/frontend/src/locales/ru/common.js +++ b/frontend/src/locales/ru/common.js @@ -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: + "Доступ к файловой системе может быть опасным, так как он может изменять или удалять файлы. Пожалуйста, ознакомьтесь с документацией перед включением.", + 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", diff --git a/frontend/src/locales/tr/common.js b/frontend/src/locales/tr/common.js index 1452acb0..b3ab1c18 100644 --- a/frontend/src/locales/tr/common.js +++ b/frontend/src/locales/tr/common.js @@ -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 belgelendirme'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ı", diff --git a/frontend/src/locales/vn/common.js b/frontend/src/locales/vn/common.js index 948e6718..9d3d0c12 100644 --- a/frontend/src/locales/vn/common.js +++ b/frontend/src/locales/vn/common.js @@ -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 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", diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js index 1ad0d412..5b8859e3 100644 --- a/frontend/src/locales/zh/common.js +++ b/frontend/src/locales/zh/common.js @@ -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: + "访问文件系统可能存在风险,因为它可能修改或删除文件。在启用之前,请务必查阅文档。", + 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 服务器", diff --git a/frontend/src/locales/zh_TW/common.js b/frontend/src/locales/zh_TW/common.js index 6133f5cf..955ce2ae 100644 --- a/frontend/src/locales/zh_TW/common.js +++ b/frontend/src/locales/zh_TW/common.js @@ -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: + "訪問檔案系統可能存在風險,因為它可能會修改或刪除檔案。在啟用之前,請務必查閱相關文件。", + 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 伺服器", diff --git a/frontend/src/media/agents/file-system.png b/frontend/src/media/agents/file-system.png new file mode 100644 index 0000000000000000000000000000000000000000..b1705f00368c558134e7093784adc00373196881 GIT binary patch literal 163665 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 z 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 Y0jyo&g~!e8EdT%j07*qoM6N<$f;;R_j{pDw literal 0 HcmV?d00001 diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 8ca6d0dd..7ac0bc9f 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -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} + */ + 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, diff --git a/frontend/src/pages/Admin/Agents/FileSystemSkillPanel/index.jsx b/frontend/src/pages/Admin/Agents/FileSystemSkillPanel/index.jsx new file mode 100644 index 00000000..474846ba --- /dev/null +++ b/frontend/src/pages/Admin/Agents/FileSystemSkillPanel/index.jsx @@ -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 ( +
+
+
+
+ {icon && + React.createElement(icon, { + size: 24, + color: "var(--theme-text-primary)", + weight: "bold", + })} + +
+ toggleSkill(skill)} + /> +
+ + {title} + +
+

+ {t("agent.skill.filesystem.description")} +

+ + {t("agent.skill.filesystem.learnMore")} → + +
+ + {enabled && ( + <> + +
+
+

+ {t("agent.skill.filesystem.configuration")} +

+
+ {loading ? ( +
+ +
+ ) : ( + <> +
+

+ {t("agent.skill.filesystem.readActions")} +

+
+ {readSkills.map((subSkill) => ( + toggleSubSkill(subSkill.name)} + /> + ))} +
+
+ +
+

+ + {t("agent.skill.filesystem.writeActions")} +

+
+ {writeSkills.map((subSkill) => ( + toggleSubSkill(subSkill.name)} + isWriteOperation + /> + ))} +
+
+ + )} +
+ + )} +
+
+ ); +} + +function WarningBanner() { + return ( +
+ +

+ + ), + }} + /> +

+
+ ); +} + +function SubSkillRow({ subSkill, disabled, onToggle, isWriteOperation }) { + const Icon = subSkill.icon; + return ( +
+
+ +
+ + {subSkill.title} + + + {subSkill.description} + +
+
+ +
+ ); +} diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index 4cb6f29e..2bb8617d 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -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(); diff --git a/frontend/src/pages/Admin/Agents/skills.js b/frontend/src/pages/Admin/Agents/skills.js index 0b8fbb77..555c51dc 100644 --- a/frontend/src/pages/Admin/Agents/skills.js +++ b/frontend/src/pages/Admin/Agents/skills.js @@ -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"), diff --git a/server/.gitignore b/server/.gitignore index d264868e..c4147a0a 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -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/* diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index 3756831c..4315d03e 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -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; diff --git a/server/endpoints/agentSkillWhitelist.js b/server/endpoints/agentSkillWhitelist.js index 12a414d6..116fb9ec 100644 --- a/server/endpoints/agentSkillWhitelist.js +++ b/server/endpoints/agentSkillWhitelist.js @@ -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)], diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 4859dfbc..332fab23 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -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, diff --git a/server/package.json b/server/package.json index 611e9829..4c9a07bf 100644 --- a/server/package.json +++ b/server/package.json @@ -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", diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js index aab75872..37a285b3 100644 --- a/server/utils/agents/aibitat/index.js +++ b/server/utils/agents/aibitat/index.js @@ -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, diff --git a/server/utils/agents/aibitat/plugins/filesystem/copy-file.js b/server/utils/agents/aibitat/plugins/filesystem/copy-file.js new file mode 100644 index 00000000..fd22cb27 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/copy-file.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/create-directory.js b/server/utils/agents/aibitat/plugins/filesystem/create-directory.js new file mode 100644 index 00000000..f843085b --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/create-directory.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/edit-file.js b/server/utils/agents/aibitat/plugins/filesystem/edit-file.js new file mode 100644 index 00000000..a1965e7c --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/edit-file.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/get-file-info.js b/server/utils/agents/aibitat/plugins/filesystem/get-file-info.js new file mode 100644 index 00000000..085cd685 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/get-file-info.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/index.js b/server/utils/agents/aibitat/plugins/filesystem/index.js new file mode 100644 index 00000000..86b8304f --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/index.js @@ -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, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/lib.js b/server/utils/agents/aibitat/plugins/filesystem/lib.js new file mode 100644 index 00000000..faf1e5d0 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/lib.js @@ -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} 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(/(? { + 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} + */ + 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} 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} 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} 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} 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} + */ + 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} 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} 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} 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} + */ + 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} 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} 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} 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} 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(); diff --git a/server/utils/agents/aibitat/plugins/filesystem/list-directory.js b/server/utils/agents/aibitat/plugins/filesystem/list-directory.js new file mode 100644 index 00000000..752c5ef5 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/list-directory.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/move-file.js b/server/utils/agents/aibitat/plugins/filesystem/move-file.js new file mode 100644 index 00000000..6080726a --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/move-file.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/read-multiple-files.js b/server/utils/agents/aibitat/plugins/filesystem/read-multiple-files.js new file mode 100644 index 00000000..1088b180 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/read-multiple-files.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/read-text-file.js b/server/utils/agents/aibitat/plugins/filesystem/read-text-file.js new file mode 100644 index 00000000..a5758125 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/read-text-file.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/filesystem/search-files.js b/server/utils/agents/aibitat/plugins/filesystem/search-files.js new file mode 100644 index 00000000..d63c7c05 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/search-files.js @@ -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} 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; +} diff --git a/server/utils/agents/aibitat/plugins/filesystem/write-file.js b/server/utils/agents/aibitat/plugins/filesystem/write-file.js new file mode 100644 index 00000000..42fc9be5 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/filesystem/write-file.js @@ -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}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/index.js b/server/utils/agents/aibitat/plugins/index.js index 9a7ee7a0..13524bf4 100644 --- a/server/utils/agents/aibitat/plugins/index.js +++ b/server/utils/agents/aibitat/plugins/index.js @@ -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, }; diff --git a/server/utils/agents/defaults.js b/server/utils/agents/defaults.js index ba54cd92..41b58008 100644 --- a/server/utils/agents/defaults.js +++ b/server/utils/agents/defaults.js @@ -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}` ); diff --git a/server/utils/collectorApi/index.js b/server/utils/collectorApi/index.js index 3d5afd79..ef5b6b47 100644 --- a/server/utils/collectorApi/index.js +++ b/server/utils/collectorApi/index.js @@ -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} - 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`, { diff --git a/server/yarn.lock b/server/yarn.lock index 1ecbae4f..597387c1 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -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"