AUDIT / src /lib /agentLoop /fileToolsAdvanced.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 3)
95eb75a verified
Raw
History Blame Contribute Delete
10.4 kB
// ─── fileToolsAdvanced.ts ─────────────────────────────────────────────────────
// S742: estratto da fileTools.ts — handler per 6 tool avanzati S706:
// copy_file · file_info · file_diff · append_to_file · clone_url_to_vfs · batch_file_op
// Ogni tool restituisce string | null.
// Importato da fileTools.ts tramite tryExecuteAdvanced().
// ─────────────────────────────────────────────────────────────────────────────
import { vfsAsync, fileBackupsDb } from "../vfsDb";
export type AdvancedToolArgs = Record<string, unknown>;
export async function tryExecuteAdvanced(
name: string,
args: AdvancedToolArgs,
signal?: AbortSignal
): Promise<string | null> {
// ── S706: copy_file ──────────────────────────────────────────────────────────
if (name === "copy_file") {
const { from, to } = args as { from: string; to: string };
const content = await vfsAsync.read(from);
if (content === null) {
const all = await vfsAsync.list() as Array<{ path: string }>;
return `❌ copy_file: sorgente "${from}" non trovata.\nDisponibili: ${all.map(f => f.path).join(", ") || "nessuno"}`;
}
const existing = await vfsAsync.read(to);
if (existing !== null) return `❌ copy_file: destinazione "${to}" esiste già. Cancellala con delete_file prima.`;
const files2 = await vfsAsync.list() as Array<{ path: string; type?: string }>;
const mime2 = (files2.find(f => f.path === from) as { type?: string } | undefined)?.type ?? "text/plain";
await vfsAsync.write(to, content, mime2);
return `✅ Copiato: \`${from}\` → \`${to}\` (${content.length} chars)`;
}
// ── S706: file_info ──────────────────────────────────────────────────────────
if (name === "file_info") {
const { path: fiPath } = args as { path: string };
const content = await vfsAsync.read(fiPath);
if (content === null) {
const all = await vfsAsync.list() as Array<{ path: string }>;
return `❌ file_info: "${fiPath}" non trovato.\nDisponibili: ${all.map(f => f.path).join(", ") || "nessuno"}`;
}
const allF = await vfsAsync.list() as Array<{ path: string; type?: string; size?: number }>;
const meta = allF.find(f => f.path === fiPath);
const isText = !content.startsWith("data:");
const lines = isText ? content.split("\n").length : null;
const words = isText ? content.split(/\s+/).filter(Boolean).length : null;
const ext = fiPath.split(".").pop()?.toLowerCase() ?? "";
const info: string[] = [
`📄 **Info: \`${fiPath}\`**`,
`- **Dimensione**: ${content.length.toLocaleString()} chars${meta?.size ? ` (${meta.size.toLocaleString()} bytes)` : ""}`,
`- **Tipo MIME**: ${meta?.type ?? "text/plain"}`,
`- **Estensione**: .${ext}`,
];
if (lines !== null) info.push(`- **Righe**: ${lines.toLocaleString()}`);
if (words !== null) info.push(`- **Parole**: ${words.toLocaleString()}`);
if (content.startsWith("data:")) {
const mimeFromUrl = content.match(/^data:([^;]+)/)?.[1] ?? "unknown";
info.push(`- **Contenuto**: file binario/media (${mimeFromUrl})`);
} else {
const hasNonAscii = /[^\x00-\x7F]/.test(content.slice(0, 1000));
info.push(`- **Encoding**: ${hasNonAscii ? "UTF-8 (con caratteri non-ASCII)" : "ASCII/UTF-8"}`);
}
const backups = await fileBackupsDb.list(fiPath);
if (backups.length) info.push(`- **Backup disponibili**: ${backups.length}`);
return info.join("\n");
}
// ── S706: file_diff ──────────────────────────────────────────────────────────
if (name === "file_diff") {
const { path_a, path_b } = args as { path_a: string; path_b: string };
const [ca, cb2] = await Promise.all([vfsAsync.read(path_a), vfsAsync.read(path_b)]);
if (ca === null) return `❌ file_diff: "${path_a}" non trovato nel VFS.`;
if (cb2 === null) return `❌ file_diff: "${path_b}" non trovato nel VFS.`;
const linesA = ca.split("\n"), linesB = cb2.split("\n");
const patch: string[] = [];
let i = 0, j = 0;
while (i < linesA.length || j < linesB.length) {
if (i < linesA.length && j < linesB.length && linesA[i] === linesB[j]) {
patch.push(` ${linesA[i]}`); i++; j++;
} else if (j >= linesB.length || (i < linesA.length && linesA[i] !== linesB[j])) {
patch.push(`-${linesA[i] ?? ""}`); i++;
} else {
patch.push(`+${linesB[j]}`); j++;
}
}
const added = patch.filter(l => l.startsWith("+")).length;
const removed = patch.filter(l => l.startsWith("-")).length;
if (added === 0 && removed === 0) return `✅ file_diff: **"${path_a}"** e **"${path_b}"** sono identici (${linesA.length} righe).`;
const preview = patch.slice(0, 100).join("\n");
return `📊 **Diff: \`${path_a}\` ↔ \`${path_b}\`**\n+${added} righe aggiunte / -${removed} rimosse\n\n\`\`\`diff\n${preview}${patch.length > 100 ? `\n…(+${patch.length - 100} righe)` : ""}\n\`\`\``;
}
// ── S706: append_to_file ─────────────────────────────────────────────────────
if (name === "append_to_file") {
const { path: apPath, content: apContent, separator = "\n" } = args as { path: string; content: string; separator?: string };
const existing = await vfsAsync.read(apPath);
const newContent = existing !== null ? existing + separator + apContent : apContent;
await vfsAsync.write(apPath, newContent);
const _wr = await vfsAsync.read(apPath);
if (_wr === null) return `⚠️ append_to_file: file non salvato (quota localStorage esaurita).`;
return `✅ Append a \`${apPath}\` — +${apContent.length} chars, totale ${newContent.length} chars`;
}
// ── S706: clone_url_to_vfs ───────────────────────────────────────────────────
if (name === "clone_url_to_vfs") {
const { url: cuUrl, save_path: cuPath, format: cuFmt = "auto" } = args as { url: string; save_path: string; format?: string };
if (!cuUrl || !cuPath) return "❌ clone_url_to_vfs: url e save_path sono obbligatori.";
try {
const PROXIES = [
(u: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(u)}`,
(u: string) => `https://corsproxy.io/?${encodeURIComponent(u)}`,
(u: string) => u,
];
let fetchedOk = false, mimeType = "text/plain", savedAs = "";
for (const proxy of PROXIES) {
try {
const resp = await fetch(proxy(cuUrl), { signal });
if (!resp.ok) continue;
const ct = resp.headers.get("content-type") ?? "";
const isBinary = cuFmt === "binary" || (!ct.includes("text") && !ct.includes("json") && !ct.includes("xml") && !ct.includes("svg") && cuFmt !== "text");
if (isBinary) {
const blob = await resp.blob();
const dr: string = await new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result as string); fr.onerror = rej; fr.readAsDataURL(blob); });
mimeType = ct.split(";")[0].trim() || "application/octet-stream";
await vfsAsync.write(cuPath, dr, mimeType);
savedAs = `binario (${blob.size} bytes, ${mimeType})`;
} else {
const text = await resp.text();
mimeType = ct.split(";")[0].trim() || "text/plain";
await vfsAsync.write(cuPath, text, mimeType);
savedAs = `testo (${text.length} chars)`;
}
fetchedOk = true; break;
} catch { continue; }
}
if (!fetchedOk) return `❌ clone_url_to_vfs: impossibile scaricare "${cuUrl}" (tutti i proxy CORS falliti).`;
return `✅ Download → \`${cuPath}\` ← ${cuUrl}\n${savedAs}`;
} catch (e) { return `❌ clone_url_to_vfs: ${e instanceof Error ? e.message : String(e)}`; }
}
// ── S706: batch_file_op ──────────────────────────────────────────────────────
if (name === "batch_file_op") {
const { operation: bOp, pattern: bPat, replacement: bRep, paths: bPaths, dry_run = false } = args as {
operation: "copy" | "delete" | "rename" | "list_matches";
pattern: string; replacement?: string; paths?: string[]; dry_run?: boolean;
};
const allBatch = await vfsAsync.list() as Array<{ path: string }>;
let bRx: RegExp;
try { bRx = new RegExp(bPat, "i"); } catch { return `❌ batch_file_op: pattern regex non valido: "${bPat}"`; }
const bTargets = (bPaths ? allBatch.filter(f => bPaths.includes(f.path)) : allBatch).filter(f => bRx.test(f.path));
if (!bTargets.length) return `📂 batch_file_op: nessun file corrisponde a \`${bPat}\`.`;
if (bOp === "list_matches") return `📂 **${bTargets.length} file corrispondono a \`${bPat}\`:**\n${bTargets.map(f => `• \`${f.path}\``).join("\n")}`;
const bResults: string[] = [];
for (const f of bTargets) {
if (bOp === "delete") {
if (!dry_run) await vfsAsync.delete(f.path);
bResults.push(`${dry_run ? "🔍" : "🗑️"} ${f.path}`);
} else if ((bOp === "copy" || bOp === "rename") && bRep !== undefined) {
const dest = f.path.replace(bRx, bRep);
if (!dry_run) {
const c = await vfsAsync.read(f.path);
if (c !== null) {
await vfsAsync.write(dest, c);
if (bOp === "rename") await vfsAsync.delete(f.path);
}
}
bResults.push(`${dry_run ? "🔍" : bOp === "copy" ? "📋" : "🔄"} ${f.path}${dest}`);
}
}
return `✅ **batch_file_op (${bOp})${dry_run ? " — DRY RUN" : ""}**\n${bResults.join("\n")}`;
}
return null;
}