diff --git a/package-lock.json b/package-lock.json index 9ab2a56..fb18576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -684,26 +684,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.45.tgz", - "integrity": "sha512-2QADgQcw/d0GFqTq2+nHwX152ZRvZxW0CHONG5d1RCs6YJtdr/GdbnMYYeRH2BiBIhnfkcvF50ImCRvsS5Tnwg==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.39.tgz", + "integrity": "sha512-AY0VPYf6QQm88wUcOav2B36iedWKBUaMegKRxxY2uIHESiU6HueEuQR/n7D3U2UdD0zLox3jFRjYbZAsr2CgkQ==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.45", - "@github/copilot-darwin-x64": "1.0.45", - "@github/copilot-linux-arm64": "1.0.45", - "@github/copilot-linux-x64": "1.0.45", - "@github/copilot-win32-arm64": "1.0.45", - "@github/copilot-win32-x64": "1.0.45" + "@github/copilot-darwin-arm64": "1.0.39", + "@github/copilot-darwin-x64": "1.0.39", + "@github/copilot-linux-arm64": "1.0.39", + "@github/copilot-linux-x64": "1.0.39", + "@github/copilot-win32-arm64": "1.0.39", + "@github/copilot-win32-x64": "1.0.39" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.45.tgz", - "integrity": "sha512-gCJy1nOIWL5lpLFJTRk2Kz7bS30emkA4p4gM+PJ5/dOwNRBOyUO0/2f03/m5vYL4DNd/T47cFIN6s82gISAIYQ==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.39.tgz", + "integrity": "sha512-E8WfNL43NMzMTDDpCiYikaEmYCMAr6mz8LHrJtkaFuVXVkBr/q2NI3hAtwHFy8M11Fac/MeIe3/VEymWwwh3kw==", "cpu": [ "arm64" ], @@ -717,9 +717,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.45.tgz", - "integrity": "sha512-nLzC7C0i/WAY+4FukHuONBDNeKUAqBBab3n36aEdpqxVDP5h2Tbzg2yShqav2blR7KDJL7YMcYTVFxmwfQj+yQ==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.39.tgz", + "integrity": "sha512-0zbC4lDVX7l8Wvq+JSCMjO0xTN69nWLejTBCl3Ev5bP6P+/7wPURcUvZKoHEaXxOULQ3AGj0DwZNAsvvQkA/6Q==", "cpu": [ "x64" ], @@ -733,9 +733,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.45.tgz", - "integrity": "sha512-MdRNZUNMrI0dpQ+DiDoZQ7AbitQp9eN7ir176Za2Kf7dkUxPwmio32yhRbBS81McU6vBw8cCzEZviwv/jc8buQ==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.39.tgz", + "integrity": "sha512-x88FuByweJlHlAmUZXjq4JlmtqgoM57Fe7nXzQkGr2Y5wnc2EDydBzFYEOlYDSWozQreimaJIm0KEMAA5T8/Fg==", "cpu": [ "arm64" ], @@ -749,9 +749,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.45.tgz", - "integrity": "sha512-xSRUjWA+wrSSjktJSjNtiS/47Cy0PviPejj7RUmtChsPfDJB8wW2iZ6NfpdiAomtxAz5xx4AjbjT1I4b1FqnwA==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.39.tgz", + "integrity": "sha512-ssahg8r7a0VCsHVXPRmFFXx70xNAxaTM2SZfG7qPRfFB2OM8gHrW26F2oikTklDF6D+A2MfSAMpzJLBUZbPnhw==", "cpu": [ "x64" ], @@ -786,9 +786,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.45.tgz", - "integrity": "sha512-lhcTlKs7MWMzIXv21hUSpL4aFW49jqVhNrQKaB8sYk2nzvGRJvNwTcBS1Tn5ndXlPzQ9P/p9B6B5uwwmZ1vHHw==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.39.tgz", + "integrity": "sha512-hhBWGZQIywbp6MBxlqMX2GSmHqtUAOGwpo9b0igscecL4i0kz89QNasC+mKiN+zFEHP6I8gggOu87XPI17Io8Q==", "cpu": [ "arm64" ], @@ -802,9 +802,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.45", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.45.tgz", - "integrity": "sha512-XYZ983NQmooVr/n+pCnHIorBmf1hd3o1rMlSAodwG/VFlQaydGoOs1F1NntxWBoFAND+eM6N4PZfw8M8sRayfA==", + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.39.tgz", + "integrity": "sha512-0ehlMtBiwKjmfEY3hVZggdn7qrmPMC8ueBQv/b+6UY3SMRS/M/1Y7xkOCwG84NvJsktdSsk3SlQnE2LbkTVpSA==", "cpu": [ "x64" ], diff --git a/scripts/generate-native-dts.ts b/scripts/generate-native-dts.ts index b357984..1fadd7c 100644 --- a/scripts/generate-native-dts.ts +++ b/scripts/generate-native-dts.ts @@ -231,7 +231,14 @@ function main() { const functions = parseRustModule(source); if (functions.length === 0) { - console.warn(` ⚠️ No #[rquickjs::function] found in ${moduleDir}`); + if (moduleDir === "native-globals") { + // native-globals registers globals (TextEncoder, crypto, etc.) on + // globalThis — it doesn't export module functions. Skip silently. + continue; + } + console.warn( + ` ⚠️ No #[rquickjs::function] exports found for native module '${moduleDir}'; skipping .d.ts generation.`, + ); continue; } diff --git a/src/agent/index.ts b/src/agent/index.ts index 005e82f..f7583b8 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -529,14 +529,16 @@ async function drainAndWarn(rl: readline.Interface): Promise { const discarded = await drainBufferedLines(rl); if (discarded.length > 0) { console.log( - C.warn(`⚠️ Discarded ${discarded.length} buffered line(s) from paste:`), + C.warn( + "⚠️ Discarded " + discarded.length + " buffered line(s) from paste:", + ), ); for (const line of discarded.slice(0, 2)) { const truncated = line.length > 50 ? line.slice(0, 50) + "..." : line; - console.log(C.dim(` "${truncated}"`)); + console.log(C.dim(' "' + truncated + '"')); } if (discarded.length > 2) { - console.log(C.dim(` ...and ${discarded.length - 2} more`)); + console.log(C.dim(" ...and " + (discarded.length - 2) + " more")); } } } @@ -2000,7 +2002,7 @@ const executeJavascriptTool = defineTool("execute_javascript", { if (consoleOutput?.length) { if (state.verboseOutput) { for (const line of consoleOutput) { - console.error(` ${C.dim(`[console] ${line.trimEnd()}`)}`); + console.error(` ${C.dim("[console] " + line.trimEnd())}`); } } debugLog( @@ -2016,9 +2018,9 @@ const executeJavascriptTool = defineTool("execute_javascript", { if (state.showTimingEnabled && timing) { console.error( - ` ${C.dim(`⏱️ ${timing.totalMs}ms wall-clock`)} ` + - `${C.dim(`(init: ${timing.initMs}ms, compile: ${timing.compileMs}ms, exec: ${timing.executeMs}ms)`)} ` + - `${C.dim(`· limits: cpu ${effectiveCpuLimit}ms, wall ${effectiveWallLimit}ms`)}`, + ` ${C.dim("⏱️ " + timing.totalMs + "ms wall-clock")} ` + + `${C.dim("(init: " + timing.initMs + "ms, compile: " + timing.compileMs + "ms, exec: " + timing.executeMs + "ms)")} ` + + `${C.dim("· limits: cpu " + effectiveCpuLimit + "ms, wall " + effectiveWallLimit + "ms")}`, ); } @@ -2273,134 +2275,91 @@ function getBashRunnerHandler(): string { // Build the IFileSystem adapter that delegates to host plugins. // Falls back to InMemoryFs for paths/operations the plugins can't handle. - const fsAdapter = ` -// ── Hybrid filesystem: InMemoryFs + host plugins ────────────────── -const memFs = new InMemoryFs(); - -const hostFs = { - async readFile(path, opts) { - ${ - hasFsRead - ? `try { - const r = fsRead.readFile(path, typeof opts === 'string' ? opts : opts?.encoding || 'utf-8'); - if (r.error) throw new Error(r.error); - return r.content; - } catch (e) { - // Fall back to in-memory - return memFs.readFile(path, opts); - }` - : `return memFs.readFile(path, opts);` - } - }, - async readFileBuffer(path) { - ${ - hasFsRead - ? `try { - const r = fsRead.readFileBinary(path); - return r; - } catch { - return memFs.readFileBuffer(path); - }` - : `return memFs.readFileBuffer(path);` - } - }, - async writeFile(path, content, opts) { - ${ - hasFsWrite - ? `try { - const text = typeof content === 'string' ? content : new TextDecoder().decode(content); - const r = fsWrite.writeFile(path, text); - if (r.error) throw new Error(r.error); - } catch (e) { - // Fall back to in-memory if plugin rejects (path jail etc.) - await memFs.writeFile(path, content, opts); - }` - : `await memFs.writeFile(path, content, opts);` - } - }, - async appendFile(path, content, opts) { - ${ - hasFsWrite - ? `try { - const text = typeof content === 'string' ? content : new TextDecoder().decode(content); - const r = fsWrite.appendFile(path, text); - if (r.error) throw new Error(r.error); - } catch { - await memFs.appendFile(path, content, opts); - }` - : `await memFs.appendFile(path, content, opts);` - } - }, - async exists(path) { - ${ - hasFsRead - ? `try { - const r = fsRead.stat(path); - return !r.error; - } catch { - return memFs.exists(path); - }` - : `return memFs.exists(path);` - } - }, - async stat(path) { - ${ - hasFsRead - ? `try { - const r = fsRead.stat(path); - if (r.error) throw new Error(r.error); - return { - isFile: r.type === 'file', - isDirectory: r.type === 'directory', - isSymbolicLink: false, - mode: 0o644, - size: r.size || 0, - mtime: r.modified ? new Date(r.modified) : new Date(), - }; - } catch { - return memFs.stat(path); - }` - : `return memFs.stat(path);` - } - }, - async mkdir(path, opts) { - ${ - hasFsWrite - ? `try { - fsWrite.mkdir(path); - } catch { - await memFs.mkdir(path, opts); - }` - : `await memFs.mkdir(path, opts);` - } - }, - async readdir(path) { - ${ - hasFsRead - ? `try { - const r = fsRead.listDir(path); - if (r.error) throw new Error(r.error); - return r.entries.map(e => e.name); - } catch { - return memFs.readdir(path); - }` - : `return memFs.readdir(path);` - } - }, - // Delegate remaining operations to in-memory FS - async rm(p, o) { return memFs.rm(p, o); }, - async cp(s, d, o) { return memFs.cp(s, d, o); }, - async mv(s, d) { return memFs.mv(s, d); }, - resolvePath(b, p) { return memFs.resolvePath(b, p); }, - getAllPaths() { return memFs.getAllPaths(); }, - async chmod(p, m) { return memFs.chmod(p, m); }, - async symlink(t, l) { return memFs.symlink(t, l); }, - async link(e, n) { return memFs.link(e, n); }, - async readlink(p) { return memFs.readlink(p); }, - async lstat(p) { return memFs.lstat(p); }, - async realpath(p) { return memFs.realpath(p); }, - async utimes(p, a, m) { return memFs.utimes(p, a, m); }, -};`; + // Built with string concatenation (not template literals) to avoid + // confusing Prettier's parser with nested backtick/class syntax. + const fsAdapterParts: string[] = [ + "// ── Hybrid filesystem: extends InMemoryFs + delegates to host plugins ─", + "// normPath: maps sandbox paths to plugin baseDir-relative paths.", + "// /home/user/foo.txt → foo.txt (sandbox home prefix stripped)", + "// /tmp/foo.txt → tmp/foo.txt (preserved under tmp/ subdir to avoid collisions)", + "// /absolute/path → absolute/path (leading slashes stripped)", + "function normPath(p) {", + ' let n = String(p).replace(/\\\\/g, "/");', + ' n = n.replace(/^\\/home\\/user\\//, "").replace(/^\\/tmp\\//, "tmp/").replace(/^\\/+/, "");', + " const parts = n.split('/');", + " const out = [];", + " for (const s of parts) {", + ' if (!s || s === ".") continue;', + ' if (s === "..") { if (out.length === 0) throw new Error("Path escapes virtual root"); out.pop(); continue; }', + " out.push(s);", + " }", + ' return out.length === 0 ? "." : out.join("/");', + "}", + "class HybridFs extends InMemoryFs {", + ]; + + if (hasFsRead) { + fsAdapterParts.push( + " async readFile(path, opts) {", + " try {", + ' const r = fsRead.readFile(normPath(path), typeof opts === "string" ? opts : opts?.encoding || "utf-8");', + " if (r.error) throw new Error(r.error);", + " return r.content;", + " } catch { return super.readFile(path, opts); }", + " }", + " async readFileBuffer(path) {", + " try { return fsRead.readFileBinary(normPath(path)); }", + " catch { return super.readFileBuffer(path); }", + " }", + " async exists(path) {", + " try { const r = fsRead.stat(normPath(path)); if (!r.error) return true; } catch {}", + " return super.exists(path);", + " }", + " async stat(path) {", + " try {", + " const r = fsRead.stat(normPath(path));", + " if (r.error) throw new Error(r.error);", + " return { isFile: !!r.isFile, isDirectory: !!r.isDirectory, isSymbolicLink: false, mode: 0o644, size: r.size ?? 0 };", + " } catch { return super.stat(path); }", + " }", + " async readdir(path) {", + " try {", + " const r = fsRead.listDir(normPath(path));", + " if (Array.isArray(r)) return r.map(e => e.name);", + " if (r.error) throw new Error(r.error);", + " throw new Error('unexpected listDir result');", + " } catch { return super.readdir(path); }", + " }", + ); + } + + if (hasFsWrite) { + fsAdapterParts.push( + " async writeFile(path, content, opts) {", + " try {", + ' const text = typeof content === "string" ? content : new TextDecoder().decode(content);', + " const r = fsWrite.writeFile(normPath(path), text);", + " if (r.error) throw new Error(r.error);", + " } catch { await super.writeFile(path, content, opts); }", + " }", + " async appendFile(path, content, opts) {", + " try {", + ' const text = typeof content === "string" ? content : new TextDecoder().decode(content);', + " const r = fsWrite.appendFile(normPath(path), text);", + " if (r.error) throw new Error(r.error);", + " } catch { await super.appendFile(path, content, opts); }", + " }", + " async mkdir(path, opts) {", + " try {", + " const r = fsWrite.mkdir(normPath(path));", + " if (r.error) throw new Error(r.error);", + " } catch { await super.mkdir(path, opts); }", + " }", + ); + } + + fsAdapterParts.push("}", "const hostFs = new HybridFs();"); + const fsAdapter = fsAdapterParts.join("\n"); // Build custom curl command if fetch is available const curlCommand = hasFetch @@ -2424,7 +2383,7 @@ const curlCmd = defineCommand("curl", async (args) => { i++; } if (!url) return { stdout: "", stderr: "curl: no URL specified\\n", exitCode: 2 }; - if (!url.match(/^https?:\\/\\//)) url = "https://" + url; + if (url.indexOf("://") === -1) url = "https://" + url; try { if (method === "GET" || method === "HEAD") { const result = fetchMod.fetchText(url, { method, headers: headersMap, includeMeta: true }); @@ -2451,6 +2410,7 @@ const curlCmd = defineCommand("curl", async (args) => { const allCmds: string[] = [...BASH_SUPPORTED_COMMANDS]; if (hasFetch) allCmds.push("curl"); const helpCmdList = allCmds.sort().join(", "); + // prettier-ignore const helpCommand = ` // 'commands' — lists what's actually available (builtin 'help' is misleading) const commandsCmd = defineCommand("commands", async () => ({ @@ -2462,27 +2422,32 @@ const commandsCmd = defineCommand("commands", async () => ({ const customCmds = ["commandsCmd"]; if (hasFetch) customCmds.push("curlCmd"); - const bashOpts = [ - `commands: ${cmdsJson}`, - `fs: hostFs`, - `customCommands: [${customCmds.join(", ")}]`, - ].join(",\n "); - - return ` -${imports.join("\n")} - -${fsAdapter} -${curlCommand} -${helpCommand} + const bashOptParts = [ + "commands: " + cmdsJson, + "fs: hostFs", + "customCommands: [" + customCmds.join(", ") + "]", + ]; + const bashOpts = bashOptParts.join(",\n "); -export async function handler(event) { - const e = typeof event === 'string' ? JSON.parse(event) : event; - const bash = new Bash({ - ${bashOpts} - }); - const result = await bash.exec(e.command || "echo 'No command provided'"); - return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode }; -}`; + // Build final handler code via string concatenation to avoid + // confusing Prettier with nested template literals containing code. + const handlerBody = [ + imports.join("\n"), + "", + fsAdapter, + curlCommand, + helpCommand, + "", + "export async function handler(event) {", + " const e = typeof event === 'string' ? JSON.parse(event) : event;", + " const bash = new Bash({", + " " + bashOpts, + " });", + " const result = await bash.exec(e.command || \"echo 'No command provided'\");", + " return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode };", + "}", + ].join("\n"); + return handlerBody; } /** Whether the bash runner handler has been registered this session. */ @@ -2530,8 +2495,14 @@ async function getBashSandbox() { const fetchEnabled = pluginManager .getEnabledPlugins() .some((p) => p.manifest.name === "fetch"); + const fetchLabel = fetchEnabled + ? "enabled → curl available" + : "disabled → no curl"; console.error( - ` ${C.dim(`🐚 Initialising bash sandbox (fetch plugin: ${fetchEnabled ? "enabled → curl available" : "disabled → no curl"})...`)}`, + " " + + C.dim( + "🐚 Initialising bash sandbox (fetch plugin: " + fetchLabel + ")...", + ), ); const handlerCode = getBashRunnerHandler(); const reg = await bashSandbox.registerHandler("sys-bash-runner", handlerCode); @@ -2573,6 +2544,7 @@ const executeBashTool = defineTool("execute_bash", { "NOT AVAILABLE: rm, mv, ln, wget, python, node, sleep, ssh, git, apt", " - No file deletion or rename (security: fs-write is create/append only)", " - For complex logic, use register_handler with JavaScript instead", + " - Host paths like /tmp are NOT accessible — only plugin baseDir files", "", "EXAMPLES:", ' execute_bash({ command: "ls -la" })', @@ -2613,16 +2585,19 @@ const executeBashTool = defineTool("execute_bash", { // Log bash command to code log (same as show-code for JS handlers) if (state.showCodeEnabled) { - console.log(` ${C.dim("$ " + command)}`); + console.log(" " + C.dim("$ " + command)); } - sandbox.writeCode(`// ── bash ──\n$ ${command}\n`); + sandbox.writeCode("// ── bash ──\n$ " + command + "\n"); const { success, result, error, consoleOutput, stats, timing } = await bs.executeJavaScript("sys-bash-runner", { command }); if (state.showTimingEnabled && timing) { console.error( - ` ${C.dim(`⏱️ ${timing.totalMs}ms (exec: ${timing.executeMs}ms)`)}`, + " " + + C.dim( + "⏱️ " + timing.totalMs + "ms (exec: " + timing.executeMs + "ms)", + ), ); } @@ -2631,12 +2606,75 @@ const executeBashTool = defineTool("execute_bash", { const stderr = String(result.stderr ?? ""); const exitCode = Number(result.exitCode ?? 0); - // Show output to user + // ── Large output interception ────────────────────────────── + // Check threshold BEFORE printing to avoid flooding the + // console with huge output. Same pattern as execute_javascript: + // save to disk and return a small summary before the SDK's + // VB() truncation (which writes to /tmp — inaccessible from + // the sandbox). + const outputThreshold = parseInt( + process.env.HYPERAGENT_OUTPUT_THRESHOLD_BYTES || "20480", + 10, + ); + const fullResultBytes = Buffer.byteLength(stdout, "utf-8"); + const isLargeOutput = fullResultBytes > outputThreshold; + + // Show output to user (preview only if large) if (stdout) { - console.log(stdout.endsWith("\n") ? stdout.slice(0, -1) : stdout); + if (isLargeOutput) { + // Print just a short preview so the terminal isn't flooded + const previewLines = stdout.slice(0, 1024); + console.log(previewLines + (stdout.length > 1024 ? "\n..." : "")); + } else { + console.log(stdout.endsWith("\n") ? stdout.slice(0, -1) : stdout); + } } if (stderr) { - console.error(` ${C.dim(stderr.trimEnd())}`); + console.error(" " + C.dim(stderr.trimEnd())); + } + + if (isLargeOutput) { + const fsWriteBaseDir = getPluginBaseDir("fs-write"); + if (fsWriteBaseDir) { + const resultsDir = resolve(fsWriteBaseDir, "results"); + if (!existsSync(resultsDir)) { + mkdirSync(resultsDir, { recursive: true }); + } + const filename = `bash-${Date.now()}.txt`; + const outputPath = join(resultsDir, filename); + writeFileSync(outputPath, stdout, "utf-8"); + const relativePath = `results/${filename}`; + + console.error( + ` ${C.ok("📦")} stdout too large (${(fullResultBytes / 1024).toFixed(1)} KB) → saved to ${relativePath}`, + ); + + const preview = stdout.slice(0, 500); + return { + stdout: + `Output saved to ${relativePath} (${(fullResultBytes / 1024).toFixed(1)} KB).\n` + + `Preview (first 500 chars):\n${preview}\n\n` + + `Read the full output with: execute_bash({ command: "cat ${relativePath}" })\n` + + `Or process it: execute_bash({ command: "cat ${relativePath} | grep ... | wc -l" })`, + stderr, + exitCode, + ...(consoleOutput?.length ? { consoleOutput } : {}), + }; + } + // fs-write not enabled — truncate in-place with guidance + console.error( + ` ${C.warn("⚠️")} stdout too large (${(fullResultBytes / 1024).toFixed(1)} KB) and fs-write not enabled`, + ); + const preview = stdout.slice(0, 2048); + return { + stdout: + `Output truncated (${(fullResultBytes / 1024).toFixed(1)} KB). ` + + `Enable fs-write to save large output: manage_plugin("fs-write", "enable")\n\n` + + `Preview (first 2KB):\n${preview}`, + stderr, + exitCode, + ...(consoleOutput?.length ? { consoleOutput } : {}), + }; } return { @@ -2646,7 +2684,7 @@ const executeBashTool = defineTool("execute_bash", { ...(consoleOutput?.length ? { consoleOutput } : {}), }; } else { - console.error(` ${C.err("❌ " + (error ?? "Unknown bash error"))}`); + console.error(" " + C.err("❌ " + (error ?? "Unknown bash error"))); return { error: error ?? "Bash execution failed", ...(consoleOutput?.length ? { consoleOutput } : {}), @@ -2680,10 +2718,14 @@ const sandboxResetTool = defineTool("reset_sandbox", { bashRunnerRegistered = false; if (result.success) { console.error( - ` ${C.ok("🔄 Sandbox state reset")} (${result.handlers?.length ?? 0} handlers preserved, ha:shared-state preserved)`, + " " + + C.ok("🔄 Sandbox state reset") + + " (" + + (result.handlers?.length ?? 0) + + " handlers preserved, ha:shared-state preserved)", ); } else { - console.error(` ${C.err("❌ " + result.error)}`); + console.error(" " + C.err("❌ " + (result.error ?? "Unknown error"))); } return result; }, @@ -2778,17 +2820,17 @@ async function configureSandboxImpl(params: { }) { // Build a human-readable summary of what's being changed. const changes: string[] = []; - if (params.heap !== undefined) changes.push(`heap → ${params.heap}MB`); + if (params.heap !== undefined) changes.push("heap → " + params.heap + "MB"); if (params.scratch !== undefined) - changes.push(`scratch → ${params.scratch}MB`); + changes.push("scratch → " + params.scratch + "MB"); if (params.cpuTimeout !== undefined) - changes.push(`CPU timeout → ${params.cpuTimeout}ms`); + changes.push("CPU timeout → " + params.cpuTimeout + "ms"); if (params.wallTimeout !== undefined) - changes.push(`wall timeout → ${params.wallTimeout}ms`); + changes.push("wall timeout → " + params.wallTimeout + "ms"); if (params.inputBuffer !== undefined) - changes.push(`input buffer → ${params.inputBuffer}KB`); + changes.push("input buffer → " + params.inputBuffer + "KB"); if (params.outputBuffer !== undefined) - changes.push(`output buffer → ${params.outputBuffer}KB`); + changes.push("output buffer → " + params.outputBuffer + "KB"); if (changes.length === 0) { return { success: false, error: "No changes specified" }; @@ -4492,7 +4534,7 @@ const manageMCPTool = defineTool("manage_mcp", { await syncPluginsToSandbox(); console.log( - ` ${C.ok(`✓ "${params.name}" connected`)} — ${connected.tools.length} tool(s) available as ${C.dim(`host:mcp-${params.name}`)}`, + ` ${C.ok('✓ "' + params.name + '" connected')} — ${connected.tools.length} tool(s) available as ${C.dim("host:mcp-" + params.name)}`, ); return { @@ -6238,7 +6280,7 @@ async function main(): Promise { const skillNames = cli.skill.split(/\s+/).filter(Boolean); for (const skillName of skillNames) { console.log( - `${ANSI.bold}${ANSI.cyan}You: ${ANSI.reset}${C.dim(`(invoking skill: ${skillName})`)}`, + `${ANSI.bold}${ANSI.cyan}You: ${ANSI.reset}${C.dim("(invoking skill: " + skillName + ")")}`, ); const handled = await handleSlashCommand(`/${skillName}`, rl); if (!handled) { @@ -6254,7 +6296,7 @@ async function main(): Promise { const suggestions = extractSuggestedCommands(response); for (const cmd of suggestions) { console.log( - `${ANSI.bold}${ANSI.cyan}You: ${ANSI.reset}${C.dim(`(auto-applying: ${cmd})`)}`, + `${ANSI.bold}${ANSI.cyan}You: ${ANSI.reset}${C.dim("(auto-applying: " + cmd + ")")}`, ); await handleSlashCommand(cmd, rl); if (pluginManager.consumeSandboxDirty()) { @@ -6423,12 +6465,12 @@ async function main(): Promise { console.log(` ${C.warn("💡 Suggested commands:")}`); for (let i = 0; i < suggestions.length; i++) { console.log( - ` ${C.info(`[${i + 1}]`)} ${C.val(suggestions[i])}`, + ` ${C.info("[" + (i + 1) + "]")} ${C.val(suggestions[i])}`, ); } await drainAndWarn(rl); const answer = await rl.question( - ` ${C.dim(`Pick [1-${suggestions.length}], [a]ll, or [n]one: `)}`, + ` ${C.dim("Pick [1-" + suggestions.length + "], [a]ll, or [n]one: ")}`, ); const normalised = answer.trim().toLowerCase(); if ( diff --git a/src/agent/system-message.ts b/src/agent/system-message.ts index 46708fd..76e0710 100644 --- a/src/agent/system-message.ts +++ b/src/agent/system-message.ts @@ -159,6 +159,13 @@ MCP HANDLER-ONLY EXECUTION: URLS: Do NOT guess URLs — they will 404. Discover via APIs or verify first. +HOST FILESYSTEM: /tmp and other host paths are NOT accessible from the sandbox. + If you see a path like /tmp/copilot-tool-output-*.txt in a truncation message, + do NOT try to cat or read it via execute_bash — it will fail. + When fs-write is enabled, large output is automatically saved to the results/ + directory (readable via execute_bash "cat results/..." or read_output). + Use those paths instead. + UNAVAILABLE: setTimeout, fetch(), Buffer, fs, process. AVAILABLE GLOBALS: TextEncoder, TextDecoder, atob, btoa, queueMicrotask, crypto. crypto provides: crypto.getRandomValues, crypto.randomUUID, crypto.subtle.digest (SHA-1, SHA-256). diff --git a/src/code-validator/guest/Cargo.lock b/src/code-validator/guest/Cargo.lock index 433eba3..2ddeccc 100644 --- a/src/code-validator/guest/Cargo.lock +++ b/src/code-validator/guest/Cargo.lock @@ -1600,9 +1600,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "pin-project-lite", ]