Forked from brdcastro/maestro
"use strict";
/**
* @file Spill-to-disk — saves large tool outputs to temp files.
*
* When a tool produces output larger than the truncation limit, instead of
* simply discarding the overflow, we save the full content to a temp file
* and tell the model where to find it. This way:
* - Context window stays lean (only preview in the response)
* - Full data is preserved and accessible via read_file
* - Model can decide whether it needs the rest
*
* Temp files live in /tmp/maestro-spill/ and are cleaned up periodically.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.spillIfNeeded = spillIfNeeded;
const promises_1 = require("fs/promises");
const path_1 = require("path");
const os_1 = require("os");
const crypto_1 = require("crypto");
const SPILL_DIR = (0, path_1.join)((0, os_1.tmpdir)(), "maestro-spill");
const MAX_AGE_MS = 30 * 60_000; // 30 minutes
/** Ensure spill directory exists. */
async function ensureSpillDir() {
await (0, promises_1.mkdir)(SPILL_DIR, { recursive: true });
}
/**
* Clean up old spill files (>30 min).
* Runs opportunistically — failures are silently ignored.
*/
async function cleanupOldSpills() {
try {
const files = await (0, promises_1.readdir)(SPILL_DIR);
const now = Date.now();
for (const f of files) {
try {
const fp = (0, path_1.join)(SPILL_DIR, f);
const s = await (0, promises_1.stat)(fp);
if (now - s.mtimeMs > MAX_AGE_MS) {
await (0, promises_1.rm)(fp, { force: true });
}
}
catch { /* ignore */ }
}
}
catch { /* dir may not exist yet */ }
}
/**
* If `content` exceeds `maxChars`, save the full content to a temp file
* and return a preview + file path. Otherwise return the content as-is.
*
* @param content The full output string
* @param maxChars The truncation limit (from OutputLimits)
* @param label A short label for the file name (e.g. "diff", "exec", "web")
*/
async function spillIfNeeded(content, maxChars, label = "output") {
if (content.length <= maxChars) {
return { spilled: false, preview: content, totalChars: content.length };
}
await ensureSpillDir();
// Opportunistic cleanup (don't await — fire and forget)
cleanupOldSpills().catch(() => { });
const id = (0, crypto_1.randomBytes)(4).toString("hex");
const fileName = `${label}-${id}.txt`;
const filePath = (0, path_1.join)(SPILL_DIR, fileName);
await (0, promises_1.writeFile)(filePath, content, "utf-8");
const preview = content.substring(0, maxChars);
const spillNote = `\n... (showing ${maxChars.toLocaleString()} of ${content.length.toLocaleString()} chars) ` +
`Full output saved to: ${filePath} — use read_file to see the rest.`;
return {
spilled: true,
preview: preview + spillNote,
spillPath: filePath,
totalChars: content.length,
spillNote,
};
}
//# sourceMappingURL=spillToDisk.js.map