Retry library pulls after Comfy finishes and fall back to disk.

Long jobs were succeeding on Beast then failing the save step when /view briefly dropped; retries plus host-agent file serving keep finished MP4s from being orphaned.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 21:06:15 -05:00
co-authored by Cursor
parent d260abb0f8
commit ec887a320a
3 changed files with 172 additions and 30 deletions
+46 -1
View File
@@ -2,7 +2,7 @@ import http from 'node:http'
import net from 'node:net'
import { execFile, spawn } from 'node:child_process'
import { promisify } from 'node:util'
import { readdirSync, existsSync, rmSync, readFileSync, openSync, mkdirSync, writeFileSync, unlinkSync, statSync } from 'node:fs'
import { readdirSync, existsSync, rmSync, readFileSync, openSync, mkdirSync, writeFileSync, unlinkSync, statSync, createReadStream } from 'node:fs'
import { basename, dirname, join, resolve, relative, isAbsolute } from 'node:path'
import { tmpdir } from 'node:os'
@@ -594,6 +594,42 @@ function safeFile(root, subfolder, filename) {
return target
}
function findOutputFile(filename, subfolder, type) {
const roots = rootsForType(type || 'output')
const subs = [...new Set([String(subfolder || ''), type === 'input' ? '' : 'video', type === 'input' ? '' : 'audio', ''])]
for (const root of roots) {
if (!existsSync(root)) continue
for (const sub of subs) {
const path = safeFile(root, sub, filename)
if (path && existsSync(path)) {
try {
if (statSync(path).isFile()) return path
} catch { /* ignore */ }
}
}
}
return null
}
function streamFile(res, path) {
const size = statSync(path).size
const lower = path.toLowerCase()
const type = lower.endsWith('.mp4') ? 'video/mp4'
: lower.endsWith('.webm') ? 'video/webm'
: lower.endsWith('.flac') ? 'audio/flac'
: lower.endsWith('.wav') ? 'audio/wav'
: lower.endsWith('.mp3') ? 'audio/mpeg'
: lower.endsWith('.png') ? 'image/png'
: lower.endsWith('.jpg') || lower.endsWith('.jpeg') ? 'image/jpeg'
: 'application/octet-stream'
res.writeHead(200, {
'Content-Type': type,
'Content-Length': size,
'Cache-Control': 'no-store'
})
createReadStream(path).pipe(res)
}
function removeFile(path) {
if (!path || !existsSync(path)) return false
try {
@@ -845,6 +881,15 @@ const server = http.createServer(async (req, res) => {
console.log(JSON.stringify({ src: 'comfy-host-agent', event: 'stopped', reason: 'stop', killed }))
return json(res, 200, { ok: true, stopped: true, asleep: true, killed })
}
if (req.method === 'GET' && url.pathname === '/view') {
const filename = String(url.searchParams.get('filename') || '')
const subfolder = String(url.searchParams.get('subfolder') || '')
const type = String(url.searchParams.get('type') || 'output')
const path = findOutputFile(filename, subfolder, type)
if (!path) return json(res, 404, { ok: false, error: 'not-found', filename, subfolder, type })
markWork()
return streamFile(res, path)
}
if (req.method === 'POST' && url.pathname === '/purge') {
const body = await readJson(req)
return json(res, 200, purgeDesktopFiles(body))