// Shared guard for generated layout artifacts. The global layout.json and the // custom-header/custom-footer module folders are regenerated from the layout // pipeline (see set_layout_field). Editing them directly leaves the JSON source // out of sync and the visual builder overwrites the agent changes on next save. const PROTECTED_LAYOUT_PATHS = [ "cms/lib/plugins/builder_saas/layout.json", "template/estandar/modulos/custom-header-twig/", "template/estandar/modulos/custom-footer-twig/", "template/estandar/modulos/custom-header/", "template/estandar/modulos/custom-footer/", ]; // Table schemas live here. They are the .ini.php mirror of the real MySQL // structure: editing the file by hand does not run any DDL, so the schema and // the database drift apart (and the CMS keeps serving stale cached metadata). const SCHEMA_DIR_PREFIX = "cms/data/schema/"; // Normalizes a relative path: drops leading slashes and "./" segments so // "/cms/...", "./cms/..." and "cms/..." all compare equal. function normalizeRelPath(relPath) { let norm = String(relPath).replace(/^\/+/, ""); while (norm.startsWith("./")) { norm = norm.slice(2).replace(/^\/+/, ""); } return norm; } // Returns true when `relPath` points at the layout.json or any of the // generated custom-{header,footer}[-twig] module folders. export function isProtectedLayoutPath(relPath) { if (!relPath) return false; const norm = normalizeRelPath(relPath); return PROTECTED_LAYOUT_PATHS.some(p => { // Folder entries end with "/" -> prefix match on the normalized path. // File entries (no trailing slash) -> exact match only. if (p.endsWith("/")) return norm === p.slice(0, -1) || norm.startsWith(p); return norm === p; }); } // Builds a consistent MCP error response pointing the agent to set_layout_field. export function buildProtectedLayoutPathError(relPath) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Forbidden path: ${relPath} is a generated artifact of the global layout. Use set_layout_field instead with field='header' (for custom-header-twig) or field='footer' (for custom-footer-twig).`, }, null, 2), }], isError: true, }; } // Returns true when `relPath` points inside cms/data/schema/ (the table schema // directory), so file tools can bail out before hitting the Python endpoint. export function isProtectedSchemaPath(relPath) { if (!relPath) return false; const norm = normalizeRelPath(relPath); if (!norm) return false; return norm === SCHEMA_DIR_PREFIX.slice(0, -1) || norm.startsWith(SCHEMA_DIR_PREFIX); } // Builds a consistent MCP error response pointing the agent to the table tools. export function buildProtectedSchemaPathError(relPath) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Forbidden path: ${relPath} lives in ${SCHEMA_DIR_PREFIX} and table schemas are read-only through the file tools (acai-write, acai-line-replace, acai-delete). To change the structure use the table tools instead - create_table, create_field, update_field, delete_field, update_table_metadata, delete_table - which run the real DDL in MySQL and refresh the caches. To read a schema use get_table_schema or acai-view.`, }, null, 2), }], isError: true, }; }