comfyui-mcp 0.52.7 → 0.52.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/boot.js +1 -1
- package/dist/boot.js.map +1 -1
- package/dist/comfyui/fetch.js +2 -2
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/orchestrator/index.js +46 -2
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/panel-tools.js +218 -18
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/orchestrator/run-completion-journal.js +38 -0
- package/dist/orchestrator/run-completion-journal.js.map +1 -1
- package/dist/orchestrator/run-completion-watchdog.js +189 -0
- package/dist/orchestrator/run-completion-watchdog.js.map +1 -0
- package/dist/services/comfy-cli.js +23 -10
- package/dist/services/comfy-cli.js.map +1 -1
- package/dist/services/env-capabilities.js +69 -11
- package/dist/services/env-capabilities.js.map +1 -1
- package/dist/services/extra-paths.js +211 -14
- package/dist/services/extra-paths.js.map +1 -1
- package/dist/services/live-interpreter.js +197 -11
- package/dist/services/live-interpreter.js.map +1 -1
- package/dist/services/manifest.js +50 -71
- package/dist/services/manifest.js.map +1 -1
- package/dist/services/node-management.js +30 -24
- package/dist/services/node-management.js.map +1 -1
- package/dist/services/node-snapshots.js +22 -8
- package/dist/services/node-snapshots.js.map +1 -1
- package/dist/services/panel-launcher.js +198 -33
- package/dist/services/panel-launcher.js.map +1 -1
- package/dist/services/panel-secrets.js +8 -0
- package/dist/services/panel-secrets.js.map +1 -1
- package/dist/services/skill-generator.js +3 -2
- package/dist/services/skill-generator.js.map +1 -1
- package/dist/services/ui-bridge.js +5 -4
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/services/update-comfyui.js +9 -10
- package/dist/services/update-comfyui.js.map +1 -1
- package/dist/services/workflow-lock.js +24 -21
- package/dist/services/workflow-lock.js.map +1 -1
- package/dist/services/workspace-env.js +158 -10
- package/dist/services/workspace-env.js.map +1 -1
- package/dist/tools/comfy-cli.js +1 -1
- package/dist/tools/comfy-cli.js.map +1 -1
- package/dist/tools/install-comfyui.js +2 -2
- package/dist/tools/install-comfyui.js.map +1 -1
- package/dist/tools/manifest.js +1 -1
- package/dist/tools/manifest.js.map +1 -1
- package/dist/tools/skills-access.js +5 -3
- package/dist/tools/skills-access.js.map +1 -1
- package/dist/tools/workflow-library.js +2 -2
- package/dist/tools/workflow-library.js.map +1 -1
- package/package.json +1 -1
|
@@ -74,7 +74,7 @@ function journalTabFor(ctx) {
|
|
|
74
74
|
function journalConversationFor(ctx) {
|
|
75
75
|
return conversationOfScopeAddress(ctx.tabId);
|
|
76
76
|
}
|
|
77
|
-
import { BRIDGE_DEFAULT_TIMEOUT_MS, BRIDGE_READ_DEFAULT_TIMEOUT_MS, dispatchOutcomeOf, isCapabilityRefusal, isPanelCmdUnsupportedError, isReplyTimeoutTagged, isRoutingAmbiguity, requiresWorkflowStampEnforcement, } from "../services/ui-bridge.js";
|
|
77
|
+
import { BRIDGE_DEFAULT_TIMEOUT_MS, BRIDGE_READ_DEFAULT_TIMEOUT_MS, dispatchOutcomeOf, GRAPH_CMD_EFFECT, isCapabilityRefusal, isPanelCmdUnsupportedError, isReplyTimeoutTagged, isRoutingAmbiguity, requiresWorkflowStampEnforcement, } from "../services/ui-bridge.js";
|
|
78
78
|
import { withWorkflowTarget, } from "../services/workflow-target-store.js";
|
|
79
79
|
import { addUserMcpServer, readUserMcpServers, removeUserMcpServer, setUserMcpServerSecret, } from "../services/user-mcp-config.js";
|
|
80
80
|
import { setComfyuiSecret, setAgentSecret, isAllowedAgentSecretKey, receiptDisclosures, shadowedNote, storeDamageNote, } from "../services/panel-secrets.js";
|
|
@@ -2545,6 +2545,109 @@ function summarizeSetWidgetEcho(res, full) {
|
|
|
2545
2545
|
content: res.content.map((c, i) => i === idx && c.type === "text" ? { ...c, text: JSON.stringify(fitted, null, 2) } : c),
|
|
2546
2546
|
};
|
|
2547
2547
|
}
|
|
2548
|
+
// ---- #1658: Anima/Krea2 regional canvas prompt textareas --------------------------
|
|
2549
|
+
//
|
|
2550
|
+
// ComfyUI_LC123_nodes `web/inline_regional_canvas.js` hides the prompt widgets
|
|
2551
|
+
// and keeps the authoritative string in `node.properties.animaPrompts[name]`
|
|
2552
|
+
// plus a custom <textarea>. `promptValue()` prefers the map; APPLY /
|
|
2553
|
+
// `savePrompts()` copies the textarea back over `widget.value`. The widgets
|
|
2554
|
+
// do NOT declare `options.property`, so the panel#1363 bound-property verify
|
|
2555
|
+
// never fires and `applyWidgetWrite`'s `w.value` read-back is a tautology.
|
|
2556
|
+
//
|
|
2557
|
+
// `savePrompts` / `setPromptValue` are closures, not methods on the node —
|
|
2558
|
+
// there is no route (the LTXDirector / PromptRelay shape). This is the
|
|
2559
|
+
// node-keyed refusal (#983 rgthree Fast Groups, #1569 Ideogram4). quality /
|
|
2560
|
+
// scene / negative have a real Python-side escape: execute() prefers the
|
|
2561
|
+
// `*_prompt_in` sockets. red / green / blue have none.
|
|
2562
|
+
const ANIMA_REGIONAL_CANVAS_TYPES = new Set([
|
|
2563
|
+
"AnimaRegionalCanvasInline",
|
|
2564
|
+
"Krea2RegionalCanvasInline",
|
|
2565
|
+
"AnimaRegionalCanvas",
|
|
2566
|
+
"AnimaRegionalInpaintCanvas",
|
|
2567
|
+
]);
|
|
2568
|
+
const ANIMA_REGIONAL_PROMPT_WIDGETS = new Set([
|
|
2569
|
+
"quality_prompt",
|
|
2570
|
+
"scene_prompt",
|
|
2571
|
+
"red_prompt",
|
|
2572
|
+
"green_prompt",
|
|
2573
|
+
"blue_prompt",
|
|
2574
|
+
"negative_prompt",
|
|
2575
|
+
]);
|
|
2576
|
+
const ANIMA_REGIONAL_WIRED_PROMPT_INPUT = {
|
|
2577
|
+
quality_prompt: "quality_prompt_in",
|
|
2578
|
+
scene_prompt: "scene_prompt_in",
|
|
2579
|
+
negative_prompt: "negative_prompt_in",
|
|
2580
|
+
};
|
|
2581
|
+
function isAnimaRegionalPromptWidget(widget) {
|
|
2582
|
+
return ANIMA_REGIONAL_PROMPT_WIDGETS.has(widget);
|
|
2583
|
+
}
|
|
2584
|
+
/** Type of the single node a compact/detail `graph_query` named, or null when
|
|
2585
|
+
* the reply does not identify one. Fail-open on an unreadable probe. */
|
|
2586
|
+
function parseQueriedNodeType(payload) {
|
|
2587
|
+
if (!payload)
|
|
2588
|
+
return null;
|
|
2589
|
+
const nodes = payload.nodes;
|
|
2590
|
+
if (Array.isArray(nodes)) {
|
|
2591
|
+
for (const n of nodes) {
|
|
2592
|
+
if (!n || typeof n !== "object" || Array.isArray(n))
|
|
2593
|
+
continue;
|
|
2594
|
+
const rec = n;
|
|
2595
|
+
const t = rec.type ?? rec.class_type;
|
|
2596
|
+
if (typeof t === "string" && t)
|
|
2597
|
+
return t;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
const text = typeof payload.text === "string" ? payload.text : "";
|
|
2601
|
+
for (const line of text.split("\n")) {
|
|
2602
|
+
const trimmed = line.trim();
|
|
2603
|
+
// Compact: `#2768 AnimaRegionalCanvasInline "title" · …`
|
|
2604
|
+
const compact = trimmed.match(/^#\S+\s+(\S+)/);
|
|
2605
|
+
if (compact)
|
|
2606
|
+
return compact[1];
|
|
2607
|
+
// Detail JSON line: `{"id":2768,"type":"AnimaRegionalCanvasInline",…}`
|
|
2608
|
+
if (trimmed.startsWith("{")) {
|
|
2609
|
+
try {
|
|
2610
|
+
const row = JSON.parse(trimmed);
|
|
2611
|
+
const t = row.type ?? row.class_type;
|
|
2612
|
+
if (typeof t === "string" && t)
|
|
2613
|
+
return t;
|
|
2614
|
+
}
|
|
2615
|
+
catch {
|
|
2616
|
+
/* not a detail row */
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return null;
|
|
2621
|
+
}
|
|
2622
|
+
function animaRegionalPromptRefusal(type, widget) {
|
|
2623
|
+
const wired = ANIMA_REGIONAL_WIRED_PROMPT_INPUT[widget];
|
|
2624
|
+
const route = wired
|
|
2625
|
+
? `Wire a PrimitiveStringMultiline into this node's \`${wired}\` socket — execute() prefers that input over the widget.`
|
|
2626
|
+
: `There is no input socket for "${widget}"; the custom textarea is the only durable store.`;
|
|
2627
|
+
return fail(`panel_set_widget cannot set "${widget}" on ${type}. ` +
|
|
2628
|
+
`The node's custom JS stores the prompt in node.properties.animaPrompts["${widget}"] and a hidden textarea; ` +
|
|
2629
|
+
`APPLY / savePrompts() copies the textarea back over widget.value, so a success here would be a lie. ` +
|
|
2630
|
+
`${route} Do not retry panel_set_widget on this widget.`);
|
|
2631
|
+
}
|
|
2632
|
+
/** Refuse the known LC123 regional-canvas prompt widgets. Identity is read
|
|
2633
|
+
* from a one-node `graph_query`; anything else (query failed, type unreadable,
|
|
2634
|
+
* some other node that happens to own `negative_prompt`) is fail-open. */
|
|
2635
|
+
async function refuseAnimaRegionalPromptWrite(ctx, nodeId, widget) {
|
|
2636
|
+
if (!isAnimaRegionalPromptWidget(widget))
|
|
2637
|
+
return null;
|
|
2638
|
+
const probe = await ctx.call({
|
|
2639
|
+
cmd: "graph_query",
|
|
2640
|
+
ids: [nodeId],
|
|
2641
|
+
fields: "compact",
|
|
2642
|
+
limit: 1,
|
|
2643
|
+
});
|
|
2644
|
+
if (probe.isError)
|
|
2645
|
+
return null;
|
|
2646
|
+
const type = parseQueriedNodeType(parseToolResultJson(probe));
|
|
2647
|
+
if (!type || !ANIMA_REGIONAL_CANVAS_TYPES.has(type))
|
|
2648
|
+
return null;
|
|
2649
|
+
return animaRegionalPromptRefusal(type, widget);
|
|
2650
|
+
}
|
|
2548
2651
|
// ---- #809: turn the panel's silent `truncated: true` booleans into a remedy --------
|
|
2549
2652
|
//
|
|
2550
2653
|
// A bare boolean is the WORST truncation signal there is: it is a field, not prose, so
|
|
@@ -7179,21 +7282,22 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
7179
7282
|
// downstream of here, so that field is undefined at this point for BOTH states
|
|
7180
7283
|
// (the trap that silently disabled #1330's transient branch one block up).
|
|
7181
7284
|
//
|
|
7182
|
-
//
|
|
7183
|
-
//
|
|
7184
|
-
//
|
|
7285
|
+
// AN UNSTAMPED INERT READ IS ADMITTED by minting a first fence from the
|
|
7286
|
+
// live canvas and retrying once. That is not the retarget #1646 removed:
|
|
7287
|
+
// there is no named workflow to abandon, and a pinned session is left
|
|
7288
|
+
// alone. The panel fence is unchanged (#718). A WRONG stamp still fails.
|
|
7289
|
+
// The probe stays read-only until this branch decides to mint.
|
|
7185
7290
|
if (isWorkflowInstanceMismatch(err) && isFencedGraphRead(cmd)) {
|
|
7186
7291
|
const name = typeof cmd.cmd === "string" ? cmd.cmd : "panel command";
|
|
7187
7292
|
const raw = err instanceof Error ? err.message : String(err);
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
: "unstated";
|
|
7293
|
+
// Anchored to the panel's own clause so a message that quotes BOTH
|
|
7294
|
+
// wordings cannot collapse into the missing-stamp state.
|
|
7295
|
+
const stamped = /this command was issued for workflow instance ([0-9a-f-]{36})/i.exec(raw)?.[1] ?? null;
|
|
7296
|
+
const unstamped = /this command carries no workflow-instance stamp/i.test(raw);
|
|
7297
|
+
// Three-valued on purpose. A panel whose wording matches neither — or
|
|
7298
|
+
// both — is not evidence for either state, and this branch must not
|
|
7299
|
+
// manufacture one.
|
|
7300
|
+
const shape = unstamped === Boolean(stamped) ? "unstated" : unstamped ? "unstamped" : "stamped";
|
|
7197
7301
|
// Naming `mode:"current"` to a PINNED session is naming something that also
|
|
7198
7302
|
// RELEASES the pin. Say so where it applies rather than letting the caller
|
|
7199
7303
|
// discover it by losing their target.
|
|
@@ -7203,12 +7307,59 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
7203
7307
|
`RELEASES that pin. To keep it, bring that workflow back to the canvas with ` +
|
|
7204
7308
|
`panel_open_workflow(${JSON.stringify(pin.path)}) and retry instead.`
|
|
7205
7309
|
: "";
|
|
7206
|
-
|
|
7207
|
-
|
|
7310
|
+
// graph_run is fenced like a read (the panel refuses it before it queues)
|
|
7311
|
+
// but it is not one: GRAPH_CMD_EFFECT lists it as targeted, and calling
|
|
7312
|
+
// it a read would tell the caller a retry cannot double-apply a prompt.
|
|
7313
|
+
const inertRead = GRAPH_CMD_EFFECT[name] === "inert";
|
|
7314
|
+
const RETRY_IS_FREE = inertRead
|
|
7315
|
+
? `RETRY THIS EXACT CALL ONCE — this is a read, so re-issuing it cannot double-apply ` +
|
|
7316
|
+
`anything.`
|
|
7317
|
+
: `RETRY THIS EXACT CALL ONCE — the panel refused it before it ran, so re-issuing it ` +
|
|
7318
|
+
`cannot double-apply anything.`;
|
|
7319
|
+
const refusedLead = inertRead
|
|
7320
|
+
? `${name} was refused before it ran — no graph data was read.`
|
|
7321
|
+
: `${name} was refused before it ran — nothing was applied.`;
|
|
7208
7322
|
let verdict;
|
|
7209
7323
|
try {
|
|
7210
7324
|
const probe = await rebindWorkflowFence(ctx, { adopt: false });
|
|
7211
7325
|
const live = "uuid" in probe ? probe.uuid : null;
|
|
7326
|
+
const priorAbsent = "before" in probe && probe.before.known === true && !probe.before.uuid;
|
|
7327
|
+
const admitUnstampedRead = inertRead && shape === "unstamped" && pin?.mode !== "pinned";
|
|
7328
|
+
// Mint a first stamp only when this session has none. A fence naming
|
|
7329
|
+
// some other workflow is the retarget #1646 removed; we do not move it.
|
|
7330
|
+
if (admitUnstampedRead &&
|
|
7331
|
+
probe.status === "diverged" &&
|
|
7332
|
+
priorAbsent &&
|
|
7333
|
+
typeof live === "string" &&
|
|
7334
|
+
refreshWorkflowUuid(ctx, { workflow_uuid: live })) {
|
|
7335
|
+
try {
|
|
7336
|
+
const retried = ok(await sendRouted(cmd, timeoutMs, observeRid));
|
|
7337
|
+
if (successProvesSwitchCleared(cmd.cmd))
|
|
7338
|
+
clearSwitchHold(ctx.tabId);
|
|
7339
|
+
return retried;
|
|
7340
|
+
}
|
|
7341
|
+
catch (retryErr) {
|
|
7342
|
+
onFailure?.(retryErr);
|
|
7343
|
+
return fail(retryErr instanceof Error ? retryErr : new Error(String(retryErr)));
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7346
|
+
// The fence appeared between dispatch and this probe. Retrying the
|
|
7347
|
+
// inert read now carries that stamp; nothing is adopted here.
|
|
7348
|
+
if (admitUnstampedRead &&
|
|
7349
|
+
(probe.status === "already_current" || probe.status === "healed_by_panel")) {
|
|
7350
|
+
try {
|
|
7351
|
+
const retried = ok(await sendRouted(cmd, timeoutMs, observeRid));
|
|
7352
|
+
if (successProvesSwitchCleared(cmd.cmd))
|
|
7353
|
+
clearSwitchHold(ctx.tabId);
|
|
7354
|
+
return retried;
|
|
7355
|
+
}
|
|
7356
|
+
catch (retryErr) {
|
|
7357
|
+
onFailure?.(retryErr);
|
|
7358
|
+
if (!isWorkflowInstanceMismatch(retryErr)) {
|
|
7359
|
+
return fail(retryErr instanceof Error ? retryErr : new Error(String(retryErr)));
|
|
7360
|
+
}
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7212
7363
|
verdict =
|
|
7213
7364
|
probe.status === "no_identity"
|
|
7214
7365
|
// Worded without reference to the session's own side, because this
|
|
@@ -7289,7 +7440,7 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
7289
7440
|
`refusal stands on its own terms and the fence is unchanged. ` +
|
|
7290
7441
|
`(${probeErr instanceof Error ? probeErr.message : String(probeErr)})`;
|
|
7291
7442
|
}
|
|
7292
|
-
return fail(`${
|
|
7443
|
+
return fail(`${refusedLead} ${raw}${verdict}`);
|
|
7293
7444
|
}
|
|
7294
7445
|
// #1480 — NAME A REMEDY THE TAB CAN ACTUALLY ACCEPT.
|
|
7295
7446
|
//
|
|
@@ -9534,7 +9685,7 @@ export function buildPanelToolDefs() {
|
|
|
9534
9685
|
node_id: nodeId().describe("Node id whose input to disconnect."),
|
|
9535
9686
|
input: slotRef.optional().describe("Input slot name or index (default 0)."),
|
|
9536
9687
|
}, async (args, ctx) => ctx.call({ cmd: "graph_disconnect", node_id: args.node_id, input: args.input })),
|
|
9537
|
-
def("panel_set_widget", "Set a widget value on a node in the user's open graph (steps, cfg, seed, ckpt_name, text prompts, …). Returns the previous and new value (a string longer than 1000 chars is echoed as {chars, sha256, preview} so batched calls stay inside the outer tool-result budget — pass `echo: \"full\"` for the verbatim string). Undoable with Ctrl+Z. To CLEAR a text widget to an empty string, pass `clear: true` (some MCP clients drop an empty-string `value` from the serialized payload, so `value: \"\"` may not arrive — `clear: true` always works). For the LTXDirector timeline node (WhatDreamsCost CSGlide), set `timeline_data` with the FULL timeline JSON (segments + global_prompt) to drive its custom timeline UI — this re-syncs the editor and regenerates its derived `local_prompts`/`segment_lengths`/`guide_strength` widgets; setting those derived widgets directly is refused (they are silently reverted).", {
|
|
9688
|
+
def("panel_set_widget", "Set a widget value on a node in the user's open graph (steps, cfg, seed, ckpt_name, text prompts, …). Returns the previous and new value (a string longer than 1000 chars is echoed as {chars, sha256, preview} so batched calls stay inside the outer tool-result budget — pass `echo: \"full\"` for the verbatim string). Undoable with Ctrl+Z. To CLEAR a text widget to an empty string, pass `clear: true` (some MCP clients drop an empty-string `value` from the serialized payload, so `value: \"\"` may not arrive — `clear: true` always works). For the LTXDirector timeline node (WhatDreamsCost CSGlide), set `timeline_data` with the FULL timeline JSON (segments + global_prompt) to drive its custom timeline UI — this re-syncs the editor and regenerates its derived `local_prompts`/`segment_lengths`/`guide_strength` widgets; setting those derived widgets directly is refused (they are silently reverted). For AnimaRegionalCanvasInline / Krea2RegionalCanvasInline (LC123), quality/scene/red/green/blue/negative prompt writes are refused: the custom textarea and node.properties.animaPrompts overwrite widget.value on APPLY. Drive quality/scene/negative via a PrimitiveStringMultiline wired into quality_prompt_in / scene_prompt_in / negative_prompt_in; red/green/blue have no socket.", {
|
|
9538
9689
|
node_id: nodeId().describe("Node id from panel_graph_outline / panel_query_graph."),
|
|
9539
9690
|
widget: z.string().describe("Widget name (e.g. 'steps', 'cfg', 'text')."),
|
|
9540
9691
|
value: z
|
|
@@ -9557,6 +9708,14 @@ export function buildPanelToolDefs() {
|
|
|
9557
9708
|
if (value === undefined) {
|
|
9558
9709
|
return fail("panel_set_widget needs a `value`. To set an empty string, pass `clear: true` (some clients drop an empty-string `value`).");
|
|
9559
9710
|
}
|
|
9711
|
+
// #1658: LC123 regional-canvas prompt widgets store the text in a
|
|
9712
|
+
// custom textarea + node.properties.animaPrompts; writing widget.value
|
|
9713
|
+
// reports success and is overwritten on APPLY. Refuse before the write
|
|
9714
|
+
// so the reply is not a lie. Identity is a one-node graph_query; an
|
|
9715
|
+
// unreadable probe is fail-open (some other node may own negative_prompt).
|
|
9716
|
+
const blocked = await refuseAnimaRegionalPromptWrite(ctx, args.node_id, args.widget);
|
|
9717
|
+
if (blocked)
|
|
9718
|
+
return blocked;
|
|
9560
9719
|
// #599: the frontend runs refresh-before-validate here (pulls a fresh
|
|
9561
9720
|
// /object_info so a just-staged/-downloaded/-installed value is accepted on
|
|
9562
9721
|
// a single revalidation, #338/#458) — that authoritative fetch can outlast
|
|
@@ -10032,8 +10191,49 @@ export function buildPanelToolDefs() {
|
|
|
10032
10191
|
// panel forwarded no `prompt_id` — a later completion can only be reported as
|
|
10033
10192
|
// UNDETERMINED, so telling the agent to idle and wait would park it on a
|
|
10034
10193
|
// promise we can't keep. Say so and point at the verification path instead.
|
|
10194
|
+
// #1789 — …AND HONEST ABOUT WHAT "AUTOMATICALLY" RESTS ON. The old text
|
|
10195
|
+
// forbade polling outright on the strength of `correlatable`, which
|
|
10196
|
+
// proves only that a completion COULD be recognised — never that one
|
|
10197
|
+
// will arrive. The producer is the panel's `executed` frame, and a
|
|
10198
|
+
// session that lost it sat idle through a clean 28.77s render until a
|
|
10199
|
+
// human intervened, because the one behaviour that would have caught it
|
|
10200
|
+
// in seconds is the one this rider had banned.
|
|
10201
|
+
//
|
|
10202
|
+
// Delivery is now genuinely stronger (the orchestrator fills in from its
|
|
10203
|
+
// own /history observation — run-completion-watchdog.ts), but it is
|
|
10204
|
+
// still not a guarantee this tool can prove at queue time: the watchdog
|
|
10205
|
+
// sees nothing when the monitored ComfyUI is not the one this run went
|
|
10206
|
+
// to, or cannot be polled. So the anti-poll instruction stays PRIMARY —
|
|
10207
|
+
// it is what keeps an agent from burning a turn a second — and it is
|
|
10208
|
+
// paired with a bounded escape hatch: ONE check, long after the render
|
|
10209
|
+
// should have landed, naming the prompt id so it costs a single call.
|
|
10210
|
+
// A weaker promise with a stated fallback beats a strong one that fails
|
|
10211
|
+
// into an infinite idle.
|
|
10212
|
+
// THE REMEDY MUST BE A CALL THAT ACTUALLY RUNS (gate NO-SHIP on the first
|
|
10213
|
+
// cut of this fix). Naming no id for a batch was right — inventing one
|
|
10214
|
+
// would point the agent at a single arbitrary member — but the remedy it
|
|
10215
|
+
// left behind, `queue (action:"status")` with no id, is REFUSED by the
|
|
10216
|
+
// tool: queue-management.ts's requirePromptId throws on an absent
|
|
10217
|
+
// prompt_id, so the agent that obeyed this fallback got an error instead
|
|
10218
|
+
// of a status. That is the reported bug's own shape, one level in: a
|
|
10219
|
+
// rider promising something the code declines to do. Verified live
|
|
10220
|
+
// against the real handler — `status` + id and bare `list` both return
|
|
10221
|
+
// JSON; bare `status` returns isError with "requires `prompt_id`".
|
|
10222
|
+
//
|
|
10223
|
+
// So the two branches get the call that fits what they can name:
|
|
10224
|
+
// • exactly one id → status on THAT run, the precise answer;
|
|
10225
|
+
// • a batch → list, which takes no id and shows whether any of
|
|
10226
|
+
// them are still in flight (get_history settles the
|
|
10227
|
+
// outcomes once they are not).
|
|
10228
|
+
const fallbackCheck = queuedIds.length === 1
|
|
10229
|
+
? `queue (action:"status", prompt_id:"${queuedIds[0]}")`
|
|
10230
|
+
: `queue (action:"list")`;
|
|
10231
|
+
const fallbackTail = queuedIds.length === 1
|
|
10232
|
+
? ""
|
|
10233
|
+
: ` (this run queued ${queuedIds.length} prompts, so the check is the queue as a whole; get_history settles the outcome of any that are no longer in flight)`;
|
|
10035
10234
|
const note = correlatable
|
|
10036
|
-
? "\n\n[IMPORTANT] You will be notified automatically with the output image(s)/video when the render finishes — do NOT poll queue (action:\"list\"), get_history, or get_image (action:\"list_outputs\"). Just end your turn now and wait for the result to be delivered to you."
|
|
10235
|
+
? "\n\n[IMPORTANT] You will be notified automatically with the output image(s)/video when the render finishes — do NOT poll queue (action:\"list\"), get_history, or get_image (action:\"list_outputs\"). Just end your turn now and wait for the result to be delivered to you." +
|
|
10236
|
+
`\n[FALLBACK] That notification is journaled and replayed if an agent is not there to take it, and the orchestrator will fill one in from ComfyUI itself — its execution event or its history record — if the panel never reports the finish. But it cannot be GUARANTEED, so do not wait forever on it. If nothing has arrived LONG after this render should have finished (roughly twice the time you expect it to take), make ONE check with ${fallbackCheck} instead of idling indefinitely${fallbackTail}. One check, not a loop — and not before then.`
|
|
10037
10237
|
: "\n\n[IMPORTANT] The run was queued, but the panel reported NO prompt id for it, so a completion event CANNOT be correlated back to this run — its outcome will be reported to you as UNDETERMINED. Do NOT simply idle and wait indefinitely: end your turn, and if nothing arrives, confirm the outcome with get_history (action:\"list\") before acting on it.";
|
|
10038
10238
|
// Backpressure note. A backlog is only alarming when it's a job we did NOT
|
|
10039
10239
|
// queue (possibly foreign/stuck). Deliberately batching renders — a sweep,
|