Delete Comfy input stills after library save, not only outputs.

Purge was skipping Beast input copies and never sending image names to the host agent, so studio temps piled up on the desktop. Also sweep stale hex-/aigen-prefixed inputs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 23:27:14 -05:00
co-authored by Cursor
parent 780b407b3c
commit a9c2c01b35
3 changed files with 70 additions and 11 deletions
+31 -2
View File
@@ -683,7 +683,10 @@ function defaultSweepPrefixes() {
} }
function looksLikeStudioStill(name) { function looksLikeStudioStill(name) {
return /^[0-9a-f]{12}(_p\d+)?(_\d{5})?_?\.(png|jpe?g|webp)$/i.test(String(name || '')) // Studio uploads: 12-hex job prefix, or legacy aigen_<hex>_…
const n = String(name || '')
return /^[0-9a-f]{12}_.+\.(png|jpe?g|webp|gif)$/i.test(n)
|| /^aigen_[0-9a-f]+_.+\.(png|jpe?g|webp|gif)$/i.test(n)
} }
function isStudioOutputName(file, sub, prefixes) { function isStudioOutputName(file, sub, prefixes) {
@@ -697,6 +700,28 @@ function isStudioOutputName(file, sub, prefixes) {
return prefixes.some(prefix => file.startsWith(prefix)) return prefixes.some(prefix => file.startsWith(prefix))
} }
function sweepStaleStudioInputs(maxAgeMs = 15 * 60 * 1000) {
const deleted = []
const cutoff = Date.now() - Math.max(60_000, Number(maxAgeMs) || 900_000)
for (const root of inputRoots()) {
if (!existsSync(root)) continue
let files = []
try { files = readdirSync(root) } catch { continue }
for (const file of files) {
if (!looksLikeStudioStill(file)) continue
const path = join(root, file)
try {
const st = statSync(path)
if (st.isDirectory() || st.mtimeMs > cutoff) continue
} catch {
continue
}
if (removeFile(path)) deleted.push(path)
}
}
return deleted
}
function sweepStaleStudioOutputs(prefixes, subfolders, maxAgeMs = 15 * 60 * 1000) { function sweepStaleStudioOutputs(prefixes, subfolders, maxAgeMs = 15 * 60 * 1000) {
const deleted = [] const deleted = []
const names = [...new Set([...defaultSweepPrefixes(), ...(prefixes || [])])].map(item => String(item || '').trim()).filter(Boolean) const names = [...new Set([...defaultSweepPrefixes(), ...(prefixes || [])])].map(item => String(item || '').trim()).filter(Boolean)
@@ -784,6 +809,7 @@ function purgeDesktopFiles(body) {
)) ))
} }
if (allowSweep) { if (allowSweep) {
deleted.push(...sweepStaleStudioInputs(body?.sweepMaxAgeMs))
deleted.push(...sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs)) deleted.push(...sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs))
} }
const unique = [...new Set(deleted)] const unique = [...new Set(deleted)]
@@ -911,7 +937,10 @@ const server = http.createServer(async (req, res) => {
} }
if (req.method === 'POST' && url.pathname === '/sweep') { if (req.method === 'POST' && url.pathname === '/sweep') {
const body = await readJson(req) const body = await readJson(req)
const deleted = sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs) const deleted = [
...sweepStaleStudioInputs(body?.sweepMaxAgeMs),
...sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs)
]
console.log(JSON.stringify({ console.log(JSON.stringify({
src: 'comfy-host-agent', src: 'comfy-host-agent',
event: 'sweep', event: 'sweep',
+17 -5
View File
@@ -604,9 +604,13 @@ async function purgeOnDesktop(opts: {
const controlUrl = String(config.comfyControlUrl || process.env.COMFY_CONTROL_URL || '').replace(/\/$/, '') const controlUrl = String(config.comfyControlUrl || process.env.COMFY_CONTROL_URL || '').replace(/\/$/, '')
const token = String(config.comfyControlToken || process.env.COMFY_CONTROL_TOKEN || '') const token = String(config.comfyControlToken || process.env.COMFY_CONTROL_TOKEN || '')
if (!controlUrl) return if (!controlUrl) return
const imageNames = [
opts.imageName,
...(Array.isArray(opts.extraImageNames) ? opts.extraImageNames : [])
].map(name => String(name || '').trim()).filter(Boolean)
try { try {
// Exact files only. Do not sweep other MiniMax/LTX outputs or inputs — // Exact temps only: the input still(s) we uploaded + the output we already copied
// those are still part of the desktop workflow and recover path. // into the library. Library on the web server is source of truth — Beast must not keep copies.
await fetch(`${controlUrl}/purge`, { await fetch(`${controlUrl}/purge`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -615,6 +619,9 @@ async function purgeOnDesktop(opts: {
...(token ? { Authorization: `Bearer ${token}` } : {}) ...(token ? { Authorization: `Bearer ${token}` } : {})
}, },
body: JSON.stringify({ body: JSON.stringify({
imageName: imageNames[0] || '',
imageNames,
imageSubfolder: opts.imageSubfolder || '',
video: opts.video || null, video: opts.video || null,
audio: opts.audio || null, audio: opts.audio || null,
output: opts.output || (opts.video && /\.(png|jpe?g|webp|gif)$/i.test(opts.video.filename) ? opts.video : null), output: opts.output || (opts.video && /\.(png|jpe?g|webp|gif)$/i.test(opts.video.filename) ? opts.video : null),
@@ -638,13 +645,18 @@ export async function purgeComfyArtifacts(opts: {
}) { }) {
if (!purgeComfyEnabled()) return if (!purgeComfyEnabled()) return
await purgeOnDesktop(opts) await purgeOnDesktop(opts)
// Only remove the output we already copied into the library.
// Leave Comfy input stills / identity refs on disk — shot chains and
// desktop recover still need them.
const files: ComfyDiskFile[] = [] const files: ComfyDiskFile[] = []
if (opts.video?.filename) files.push(opts.video) if (opts.video?.filename) files.push(opts.video)
if (opts.audio?.filename) files.push(opts.audio) if (opts.audio?.filename) files.push(opts.audio)
if (opts.output?.filename) files.push(opts.output) if (opts.output?.filename) files.push(opts.output)
const inputSub = opts.imageSubfolder || ''
if (opts.imageName) {
files.push({ filename: opts.imageName, subfolder: inputSub, type: 'input' })
}
for (const name of opts.extraImageNames || []) {
if (!name) continue
files.push({ filename: name, subfolder: inputSub, type: 'input' })
}
for (const file of files) { for (const file of files) {
await deleteComfyHttpFile(file) await deleteComfyHttpFile(file)
} }
+22 -4
View File
@@ -376,6 +376,10 @@ async function purgeImageOnDesktop(opts: {
config.imageComfyControlToken || process.env.IMAGE_COMFY_CONTROL_TOKEN || config.comfyControlToken || process.env.COMFY_CONTROL_TOKEN || '' config.imageComfyControlToken || process.env.IMAGE_COMFY_CONTROL_TOKEN || config.comfyControlToken || process.env.COMFY_CONTROL_TOKEN || ''
) )
if (!controlUrl) return if (!controlUrl) return
const imageNames = [
opts.inputName,
...(Array.isArray(opts.extraInputNames) ? opts.extraInputNames : [])
].map(name => String(name || '').trim()).filter(Boolean)
try { try {
await fetch(`${controlUrl}/purge`, { await fetch(`${controlUrl}/purge`, {
method: 'POST', method: 'POST',
@@ -385,13 +389,16 @@ async function purgeImageOnDesktop(opts: {
...(token ? { Authorization: `Bearer ${token}` } : {}) ...(token ? { Authorization: `Bearer ${token}` } : {})
}, },
body: JSON.stringify({ body: JSON.stringify({
imageName: imageNames[0] || '',
imageNames,
imageSubfolder: opts.inputSubfolder || '',
output: opts.output || null, output: opts.output || null,
sweep: false sweep: false
}), }),
signal: AbortSignal.timeout(8000) signal: AbortSignal.timeout(8000)
}) })
} catch { } catch {
/* Host agent purge is optional; Comfy HTTP delete is the primary path. */ /* Host agent purge is optional; Comfy HTTP delete is the fallback. */
} }
} }
@@ -403,6 +410,12 @@ export async function purgeImageComfyArtifacts(opts: {
promptId?: string promptId?: string
}) { }) {
if (!purgeImageEnabled()) return if (!purgeImageEnabled()) return
await purgeImageOnDesktop({
inputName: opts.inputName,
inputSubfolder: opts.inputSubfolder,
extraInputNames: opts.extraInputNames,
output: opts.output
})
const files: { filename: string; subfolder: string; type: string }[] = [] const files: { filename: string; subfolder: string; type: string }[] = []
if (opts.output?.filename) { if (opts.output?.filename) {
files.push({ files.push({
@@ -411,12 +424,17 @@ export async function purgeImageComfyArtifacts(opts: {
type: opts.output.type || 'output' type: opts.output.type || 'output'
}) })
} }
const inputSub = opts.inputSubfolder || ''
if (opts.inputName) {
files.push({ filename: opts.inputName, subfolder: inputSub, type: 'input' })
}
for (const name of opts.extraInputNames || []) {
if (!name) continue
files.push({ filename: name, subfolder: inputSub, type: 'input' })
}
for (const file of files) { for (const file of files) {
await deleteSidecarFile(file) await deleteSidecarFile(file)
} }
await purgeImageOnDesktop({
output: opts.output
})
if (opts.promptId) { if (opts.promptId) {
try { try {
await imageComfyFetch('/history', { await imageComfyFetch('/history', {