Start MiniMax Comfy headlessly so wake does not wait on the instance picker.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 } from 'node:fs'
|
||||
import { readdirSync, existsSync, rmSync, readFileSync, openSync } from 'node:fs'
|
||||
import { basename, join, resolve, relative, isAbsolute } from 'node:path'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
@@ -157,20 +157,166 @@ async function processUp() {
|
||||
}
|
||||
}
|
||||
|
||||
function launchCommand() {
|
||||
if (process.env.COMFY_LAUNCH_CMD) return process.env.COMFY_LAUNCH_CMD
|
||||
function readJsonFile(path) {
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, 'utf8'))
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function desktopListenPort() {
|
||||
try {
|
||||
const url = new URL(defaultHttp.includes('://') ? defaultHttp : `http://${defaultHttp}`)
|
||||
return Number(url.port || 8188) || 8188
|
||||
} catch {
|
||||
return 8188
|
||||
}
|
||||
}
|
||||
|
||||
function pickDesktopInstall() {
|
||||
const wanted = String(process.env.COMFY_INSTANCE_NAME || 'ComfyUI (1)').trim()
|
||||
const parsed = readJsonFile(join(process.env.APPDATA || '', 'Comfy Desktop', 'installations.json'))
|
||||
const installs = (Array.isArray(parsed) ? parsed : [])
|
||||
.filter(item => item && item.status === 'installed' && item.installPath && item.sourceId !== 'cloud')
|
||||
if (!installs.length) return null
|
||||
return installs.find(item => item.name === wanted)
|
||||
|| installs.slice().sort((a, b) => Number(b.lastLaunchedAt || 0) - Number(a.lastLaunchedAt || 0))[0]
|
||||
}
|
||||
|
||||
function resolveHeadlessLaunch() {
|
||||
if (process.env.COMFY_LAUNCH_CMD) {
|
||||
return {
|
||||
command: process.env.COMFY_LAUNCH_CMD,
|
||||
cwd: process.env.COMFY_LAUNCH_CWD || undefined,
|
||||
mode: 'env'
|
||||
}
|
||||
}
|
||||
const inst = pickDesktopInstall()
|
||||
if (!inst) return null
|
||||
const venvPython = join(inst.installPath, 'ComfyUI', '.venv', 'Scripts', 'python.exe')
|
||||
const standalonePython = join(inst.installPath, 'standalone-env', 'python.exe')
|
||||
const python = existsSync(venvPython) ? venvPython : standalonePython
|
||||
const cwd = inst.installPath
|
||||
const main = join(inst.installPath, 'ComfyUI', 'main.py')
|
||||
if (!existsSync(python) || !existsSync(main)) return null
|
||||
const extra = join(process.env.APPDATA || '', 'Comfy Desktop', 'instance-model-paths', `${inst.id}.yaml`)
|
||||
const shared = join(process.env.APPDATA || '', 'Comfy Desktop', 'shared_model_paths.yaml')
|
||||
const extraConfig = existsSync(extra) ? extra : (existsSync(shared) ? shared : '')
|
||||
const roots = sharedRoots()
|
||||
const args = [
|
||||
'-s',
|
||||
'-u',
|
||||
join('ComfyUI', 'main.py'),
|
||||
'--listen', '0.0.0.0',
|
||||
'--port', String(desktopListenPort()),
|
||||
'--disable-auto-launch'
|
||||
]
|
||||
const extraArgs = String(inst.launchArgs || '').trim()
|
||||
if (extraArgs) args.push(...extraArgs.split(/\s+/).filter(Boolean))
|
||||
if (extraConfig) args.push('--extra-model-paths-config', extraConfig)
|
||||
if (existsSync(roots.input)) args.push('--input-directory', roots.input)
|
||||
if (existsSync(roots.output)) args.push('--output-directory', roots.output)
|
||||
return {
|
||||
file: python,
|
||||
args,
|
||||
cwd,
|
||||
mode: 'headless',
|
||||
instance: inst.name,
|
||||
log: join(inst.installPath, 'logs', 'aigen-headless.log')
|
||||
}
|
||||
}
|
||||
|
||||
function desktopShellCommand() {
|
||||
const local = process.env.LOCALAPPDATA || ''
|
||||
const desktop = `${local}\\Programs\\Comfy Desktop\\Comfy Desktop.exe`
|
||||
return `start "" "${desktop}"`
|
||||
}
|
||||
|
||||
function startComfy() {
|
||||
async function pythonMainUp() {
|
||||
try {
|
||||
const { stdout } = await execFileAsync('wmic', [
|
||||
'process', 'where', "name='python.exe'", 'get', 'commandline'
|
||||
], { windowsHide: true, timeout: 6000 })
|
||||
return /main\.py/i.test(String(stdout || ''))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function portInUse(portNum) {
|
||||
return new Promise((resolve) => {
|
||||
const probe = net.createServer()
|
||||
probe.once('error', () => resolve(true))
|
||||
probe.once('listening', () => {
|
||||
probe.close(() => resolve(false))
|
||||
})
|
||||
probe.listen(portNum, '0.0.0.0')
|
||||
})
|
||||
}
|
||||
|
||||
async function pickListenPort() {
|
||||
const preferred = desktopListenPort()
|
||||
const ordered = [preferred, ...candidatePorts()]
|
||||
const seen = new Set()
|
||||
for (const next of ordered) {
|
||||
if (!next || seen.has(next)) continue
|
||||
seen.add(next)
|
||||
if (await probeStats(next)) return next
|
||||
if (!(await portInUse(next))) return next
|
||||
}
|
||||
return preferred
|
||||
}
|
||||
|
||||
async function startComfy() {
|
||||
markWork()
|
||||
const command = launchCommand()
|
||||
const cwd = process.env.COMFY_LAUNCH_CWD || undefined
|
||||
const child = spawn(command, { shell: true, detached: true, stdio: 'ignore', windowsHide: true, cwd })
|
||||
const launch = resolveHeadlessLaunch()
|
||||
if (launch?.file) {
|
||||
const port = await pickListenPort()
|
||||
const portIdx = launch.args.indexOf('--port')
|
||||
if (portIdx >= 0) launch.args[portIdx + 1] = String(port)
|
||||
let stdio = 'ignore'
|
||||
try {
|
||||
stdio = ['ignore', openSync(launch.log, 'a'), openSync(launch.log, 'a')]
|
||||
} catch { /* ignore */ }
|
||||
const child = spawn(launch.file, launch.args, {
|
||||
detached: true,
|
||||
stdio,
|
||||
windowsHide: true,
|
||||
cwd: launch.cwd
|
||||
})
|
||||
child.unref()
|
||||
return { started: true, command, cwd: cwd || null }
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'start',
|
||||
mode: launch.mode,
|
||||
instance: launch.instance,
|
||||
cwd: launch.cwd,
|
||||
port,
|
||||
pid: child.pid || null
|
||||
}))
|
||||
return { started: true, mode: launch.mode, instance: launch.instance, cwd: launch.cwd, port, pid: child.pid || null }
|
||||
}
|
||||
const fallback = {
|
||||
command: launch?.command || desktopShellCommand(),
|
||||
cwd: launch?.cwd || process.env.COMFY_LAUNCH_CWD || undefined,
|
||||
mode: launch?.mode || 'desktop-shell'
|
||||
}
|
||||
const child = spawn(fallback.command, {
|
||||
shell: true,
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
windowsHide: true,
|
||||
cwd: fallback.cwd
|
||||
})
|
||||
child.unref()
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'start',
|
||||
mode: fallback.mode,
|
||||
cwd: fallback.cwd || null
|
||||
}))
|
||||
return { started: true, mode: fallback.mode, instance: null, cwd: fallback.cwd || null }
|
||||
}
|
||||
|
||||
async function fetchLocalQueue(portNum) {
|
||||
@@ -399,7 +545,11 @@ const server = http.createServer(async (req, res) => {
|
||||
markWork()
|
||||
return json(res, 200, { ok: true, started: false, already: true, asleep: false, port: healthyPort, proxyPort })
|
||||
}
|
||||
const launched = startComfy()
|
||||
if (await pythonMainUp()) {
|
||||
return json(res, 200, { ok: true, started: false, already: true, booting: true, asleep: false })
|
||||
}
|
||||
await taskkillImage('Comfy Desktop.exe')
|
||||
const launched = await startComfy()
|
||||
return json(res, 200, { ok: true, asleep: false, ...launched })
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/stop') {
|
||||
|
||||
@@ -299,7 +299,7 @@ async function ensureUnlocked(onStatus: StatusFn, skipBusyWait = false) {
|
||||
const localPort = remote.port ? ` Port ${remote.port} is healthy on the desktop.` : ''
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
statusMessage: `Comfy Desktop is open and the local API is up, but this server cannot reach ${host}.${localPort} Open the MiniMax instance “ComfyUI (1)” and wait until the graph editor loads — the instance picker is not enough.`
|
||||
statusMessage: `Comfy is running on the desktop but this server cannot reach ${host}.${localPort} Check COMFY_HOST / the LAN proxy.`
|
||||
})
|
||||
}
|
||||
const running = await processRunning()
|
||||
@@ -361,7 +361,7 @@ async function ensureUnlocked(onStatus: StatusFn, skipBusyWait = false) {
|
||||
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
statusMessage: 'Started Comfy Desktop but the API never answered. If the instance picker is showing, click “ComfyUI (1)” (MiniMax) and wait until the graph editor is loaded. Cold start can take a couple of minutes.'
|
||||
statusMessage: 'Started ComfyUI but the API never answered. A cold start can take a couple of minutes. Confirm the host agent launched the MiniMax install headlessly.'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user