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', {