find_nodes looks up this ComfyUI's node types (inputs, outputs, choices)
from its own /object_info. new_workflow opens a new tab, blank or built and
laid out from a workflow in API form, never touching the open one;
add_node, connect_nodes, set_values and remove_nodes edit it. Everything is
checked against the nodes before the canvas changes (types, loops, settings,
choices with close matches); each is one Ctrl+Z step, logged as the AI's
and shown under its workbench step. Receipts now show rewiring ("wired":
what feeds an input, before and after). A run ComfyUI refuses reports
ComfyUI's own reason instead of "Prompt execution failed".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
55 lines
3.0 KiB
JavaScript
55 lines
3.0 KiB
JavaScript
// ComfyUI's own words when it refuses a prompt (which node, what is wrong), not just "failed".
|
|
const reason = failure => String((failure?.response && failure.toString?.()) || failure?.message || failure).slice(0, 800);
|
|
|
|
/** Capture submission receipts from one native ComfyUI queue request, preserving its seed/widget hooks. */
|
|
export async function queueTracked(app, api, batch = 1) {
|
|
if (app.processingQueue) throw new Error('ComfyUI is submitting another batch. Wait for submission to finish, then try again.');
|
|
if (!Number.isSafeInteger(app.nextQueueRequestId) || typeof api.queuePrompt !== 'function') {
|
|
throw new Error('This ComfyUI frontend cannot identify queue requests. Update the frontend before running from AI.');
|
|
}
|
|
const requestId = app.nextQueueRequestId;
|
|
const original = api.queuePrompt;
|
|
const ids = [];
|
|
let capturing = false, error = '', attempts = 0;
|
|
let complete;
|
|
const ownDone = new Promise(resolve => { complete = resolve; });
|
|
const started = event => { if (event.detail?.requestId === requestId) capturing = true; };
|
|
const finished = event => { if (event.detail?.requestId === requestId) { capturing = false; complete(); } };
|
|
const wrapped = async function (...args) {
|
|
// Calls queued by the user behind this batch run after its promptQueued event.
|
|
const ours = capturing && attempts < batch;
|
|
if (ours) attempts++;
|
|
try {
|
|
const result = await original.apply(this, args);
|
|
if (ours) {
|
|
if (typeof result?.prompt_id === 'string' && result.prompt_id) ids.push(result.prompt_id);
|
|
else { error = 'ComfyUI did not return a prompt ID for this submission.'; capturing = false; complete(); }
|
|
}
|
|
return result;
|
|
} catch (failure) {
|
|
if (ours) { error = reason(failure); capturing = false; complete(); }
|
|
throw failure; // the native error display and partial-batch handling still run
|
|
}
|
|
};
|
|
api.addEventListener('promptQueueing', started);
|
|
api.addEventListener('promptQueued', finished);
|
|
api.queuePrompt = wrapped;
|
|
try {
|
|
// Native queuePrompt may also drain later manual requests. Return our receipt as soon as
|
|
// our batch ends, but still handle a later rejection from the native queue promise.
|
|
const nativeDone = Promise.resolve(app.queuePrompt(0, batch)).catch(failure => {
|
|
if (ids.length < batch) error ||= reason(failure);
|
|
});
|
|
await Promise.race([nativeDone, ownDone]);
|
|
} catch (failure) {
|
|
error ||= reason(failure);
|
|
} finally {
|
|
capturing = false;
|
|
if (api.queuePrompt === wrapped) api.queuePrompt = original;
|
|
api.removeEventListener('promptQueueing', started);
|
|
api.removeEventListener('promptQueued', finished);
|
|
}
|
|
if (ids.length < batch) error ||= `Only ${ids.length} of ${batch} prompts were accepted. Check ComfyUI before retrying.`;
|
|
return { prompt_ids: ids, queued: ids.length, requested: batch, ...(error ? { submission_error: error } : {}) };
|
|
}
|