From a9c2c01b3516dbe156a14b4cd0ee2e7342744dc2 Mon Sep 17 00:00:00 2001 From: Towsty Date: Thu, 3 Sep 2026 23:27:14 -0500 Subject: [PATCH] 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 --- scripts/comfy-host-agent.mjs | 33 +++++++++++++++++++++++++++++++-- server/utils/comfy.ts | 22 +++++++++++++++++----- server/utils/imageComfy.ts | 26 ++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/scripts/comfy-host-agent.mjs b/scripts/comfy-host-agent.mjs index 9fd8314..02495ba 100644 --- a/scripts/comfy-host-agent.mjs +++ b/scripts/comfy-host-agent.mjs @@ -683,7 +683,10 @@ function defaultSweepPrefixes() { } 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__… + 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) { @@ -697,6 +700,28 @@ function isStudioOutputName(file, sub, prefixes) { 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) { const deleted = [] const names = [...new Set([...defaultSweepPrefixes(), ...(prefixes || [])])].map(item => String(item || '').trim()).filter(Boolean) @@ -784,6 +809,7 @@ function purgeDesktopFiles(body) { )) } if (allowSweep) { + deleted.push(...sweepStaleStudioInputs(body?.sweepMaxAgeMs)) deleted.push(...sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs)) } const unique = [...new Set(deleted)] @@ -911,7 +937,10 @@ const server = http.createServer(async (req, res) => { } if (req.method === 'POST' && url.pathname === '/sweep') { 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({ src: 'comfy-host-agent', event: 'sweep', diff --git a/server/utils/comfy.ts b/server/utils/comfy.ts index 9a51688..38f371b 100644 --- a/server/utils/comfy.ts +++ b/server/utils/comfy.ts @@ -604,9 +604,13 @@ async function purgeOnDesktop(opts: { const controlUrl = String(config.comfyControlUrl || process.env.COMFY_CONTROL_URL || '').replace(/\/$/, '') const token = String(config.comfyControlToken || process.env.COMFY_CONTROL_TOKEN || '') if (!controlUrl) return + const imageNames = [ + opts.imageName, + ...(Array.isArray(opts.extraImageNames) ? opts.extraImageNames : []) + ].map(name => String(name || '').trim()).filter(Boolean) try { - // Exact files only. Do not sweep other MiniMax/LTX outputs or inputs — - // those are still part of the desktop workflow and recover path. + // Exact temps only: the input still(s) we uploaded + the output we already copied + // into the library. Library on the web server is source of truth — Beast must not keep copies. await fetch(`${controlUrl}/purge`, { method: 'POST', headers: { @@ -615,6 +619,9 @@ async function purgeOnDesktop(opts: { ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify({ + imageName: imageNames[0] || '', + imageNames, + imageSubfolder: opts.imageSubfolder || '', video: opts.video || null, audio: opts.audio || 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 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[] = [] if (opts.video?.filename) files.push(opts.video) if (opts.audio?.filename) files.push(opts.audio) 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) { await deleteComfyHttpFile(file) } diff --git a/server/utils/imageComfy.ts b/server/utils/imageComfy.ts index 4fc1a02..a672a52 100644 --- a/server/utils/imageComfy.ts +++ b/server/utils/imageComfy.ts @@ -376,6 +376,10 @@ async function purgeImageOnDesktop(opts: { config.imageComfyControlToken || process.env.IMAGE_COMFY_CONTROL_TOKEN || config.comfyControlToken || process.env.COMFY_CONTROL_TOKEN || '' ) if (!controlUrl) return + const imageNames = [ + opts.inputName, + ...(Array.isArray(opts.extraInputNames) ? opts.extraInputNames : []) + ].map(name => String(name || '').trim()).filter(Boolean) try { await fetch(`${controlUrl}/purge`, { method: 'POST', @@ -385,13 +389,16 @@ async function purgeImageOnDesktop(opts: { ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify({ + imageName: imageNames[0] || '', + imageNames, + imageSubfolder: opts.inputSubfolder || '', output: opts.output || null, sweep: false }), signal: AbortSignal.timeout(8000) }) } 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 }) { 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 }[] = [] if (opts.output?.filename) { files.push({ @@ -411,12 +424,17 @@ export async function purgeImageComfyArtifacts(opts: { 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) { await deleteSidecarFile(file) } - await purgeImageOnDesktop({ - output: opts.output - }) if (opts.promptId) { try { await imageComfyFetch('/history', {