comfyui-mcp 0.52.53 → 0.52.55

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.
Files changed (41) hide show
  1. package/README.md +3 -2
  2. package/dist/orchestrator/download-done-guard.js +82 -48
  3. package/dist/orchestrator/download-done-guard.js.map +1 -1
  4. package/dist/orchestrator/index.js +15 -27
  5. package/dist/orchestrator/index.js.map +1 -1
  6. package/dist/orchestrator/panel-agent.js +21 -6
  7. package/dist/orchestrator/panel-agent.js.map +1 -1
  8. package/dist/orchestrator/panel-tools.js +199 -9
  9. package/dist/orchestrator/panel-tools.js.map +1 -1
  10. package/dist/orchestrator/queue-note.js +34 -0
  11. package/dist/orchestrator/queue-note.js.map +1 -0
  12. package/dist/orchestrator/queue-note.test.js +74 -0
  13. package/dist/orchestrator/queue-note.test.js.map +1 -0
  14. package/dist/orchestrator/turn-origins.js +24 -5
  15. package/dist/orchestrator/turn-origins.js.map +1 -1
  16. package/dist/services/env-capabilities.js +8 -0
  17. package/dist/services/env-capabilities.js.map +1 -1
  18. package/dist/services/kitchen.js +1012 -0
  19. package/dist/services/kitchen.js.map +1 -0
  20. package/dist/services/queue-monitor.js +29 -20
  21. package/dist/services/queue-monitor.js.map +1 -1
  22. package/dist/services/queue-monitor.self-attribution.test.js +18 -6
  23. package/dist/services/queue-monitor.self-attribution.test.js.map +1 -1
  24. package/dist/tools/index.js +3 -0
  25. package/dist/tools/index.js.map +1 -1
  26. package/dist/tools/kitchen.js +157 -0
  27. package/dist/tools/kitchen.js.map +1 -0
  28. package/dist/tools/tool-surface-filter.js +2 -0
  29. package/dist/tools/tool-surface-filter.js.map +1 -1
  30. package/dist/tools/vocabulary.js +4 -3
  31. package/dist/tools/vocabulary.js.map +1 -1
  32. package/docs/design/panel-surface.txt +1 -0
  33. package/docs/design/tool-surface.txt +1 -0
  34. package/package.json +1 -1
  35. package/plugin/skills/comfyui-launch-flags/SKILL.md +21 -11
  36. package/plugin/skills/model-compatibility/SKILL.md +2 -1
  37. package/plugin/skills/model-registry/SKILL.md +3 -1
  38. package/plugin/skills/triton-sageattention/SKILL.md +20 -6
  39. package/scripts/check-blog-boilerplate.mjs +1 -0
  40. package/scripts/gen-tool-docs.ts +1 -0
  41. package/scripts/tool-doc-examples.ts +46 -0
package/README.md CHANGED
@@ -29,7 +29,7 @@ Works on **macOS**, **Linux**, and **Windows**. Auto-detects your ComfyUI instal
29
29
 
30
30
  **Stuck or have a question? [Join the Discord](https://discord.gg/cW9arBhzCu)** — help, model tips, and release announcements.
31
31
 
32
- **37 MCP tools** | **41 AI skills** (Flux · WAN · LTX 2.3 video · MiniMax H3 · Qwen · Z-Image · Ideogram 4 · ERNIE · ANIMA · model registry · Civitai · node authoring · launch/perf flags) | **56 installer packs** | **11 slash commands** | **4 autonomous agents** | **3 hooks**
32
+ **38 MCP tools** | **41 AI skills** (Flux · WAN · LTX 2.3 video · MiniMax H3 · Qwen · Z-Image · Ideogram 4 · ERNIE · ANIMA · model registry · Civitai · node authoring · launch/perf flags) | **56 installer packs** | **11 slash commands** | **4 autonomous agents** | **3 hooks**
33
33
 
34
34
  The plugin ships **expert skills that grow with every release** — model-specific generation guides with curated download URLs, workflow recipes, troubleshooting, and custom-node authoring — so Claude knows the right sampler, CFG, resolution, and model files for each architecture without trial and error.
35
35
 
@@ -263,7 +263,7 @@ for the port, the capability matrix, and the per-provider "clink" points, and th
263
263
 
264
264
  ## MCP Tools
265
265
 
266
- 37 tools across workflow execution, generation, iteration, composition, models, and more:
266
+ 38 tools across workflow execution, generation, iteration, composition, models, and more:
267
267
 
268
268
  ### Image Generation (high-level)
269
269
 
@@ -370,6 +370,7 @@ Install [comfy-cli](https://docs.comfy.org/comfy-cli/getting-started#install-cli
370
370
  | Tool | Description |
371
371
  |------|-------------|
372
372
  | `get_system_stats (action:"logs")` | Get ComfyUI server logs with optional keyword filter (e.g., `error`, `warning`, a node name) |
373
+ | `kitchen` | See what comfy-kitchen can do on this GPU (`status`), find where a graph leaves it on the table (`assess`), and apply the faster path (`apply`) — a failed probe is unknown, never a no |
373
374
  | `get_history` `action: "list"` | Get execution history with full error details, Python tracebacks, timing, and cached node info |
374
375
  | `get_history` `action: "diagnose"` | Explain a FAILED run in one call — the failed node and traceback PLUS the missing models (file + widget) and missing node types |
375
376
 
@@ -1,34 +1,41 @@
1
1
  /**
2
- * #1574 — when the completion event and `download_model action:"status"` disagree, say so.
3
- *
4
- * The tray raised `transfer completed` for an 11.46GB download while
5
- * `download_model action:"status"` reported it still streaming, `list_local_models` showed the
6
- * file absent, and the category count only rose minutes later. The file landed AFTER the
7
- * event.
2
+ * #1574 / #2057 — when the tray event and `download_model action:"status"` disagree, say so.
8
3
  *
9
4
  * The completion event is built from a PROGRESS ROW read off disk each tick. `status` answers
10
5
  * from the JOB RECORD. Two stores, and the event consults only one — the same split #1545
11
6
  * documented from the other side.
12
7
  *
8
+ * #1574: the tray raised `transfer completed` for an 11.46GB download while status reported
9
+ * it still streaming. The file landed AFTER the event.
10
+ *
11
+ * #2057: the tray raised `Model download FAILED` (and claimed nothing transferred) for a
12
+ * 19.53GB HuggingFace fetch while status — same id — still said **downloading** with
13
+ * advancing bytes (0.14 → 0.22 GB). An agent that trusts FAILED re-issues into a live
14
+ * writer, which is the corruption hazard status itself warns about. #1150 only hedges
15
+ * when a LIVE TRAY ROW still shows that filename downloading; here the tray row itself
16
+ * was the error, so that hedge never fired. The job record is the same authority status
17
+ * reads, so a failure has to be checked against it the same way a completion is.
18
+ *
13
19
  * ## Why this ANNOTATES and never suppresses
14
20
  *
15
- * The first version of this dropped the contradicted event. Review killed it, correctly: a
16
- * terminal record may legitimately still read `downloading` until the ~15s persistence
17
- * heartbeat retries (see `persistDownloadJob`'s return value, #1545). The debounce bucket is
18
- * deleted before filtering and never requeued, so suppressing on a lagging record would
19
- * PERMANENTLY lose the completion notification for a download that genuinely finished.
21
+ * The first version of the completion guard dropped the contradicted event. Review killed
22
+ * it, correctly: a terminal record may legitimately still read `downloading` until the ~15s
23
+ * persistence heartbeat retries (see `persistDownloadJob`'s return value, #1545). The
24
+ * debounce bucket is deleted before filtering and never requeued, so suppressing on a
25
+ * lagging record would PERMANENTLY lose the notification.
20
26
  *
21
27
  * That trades a confusing message for a missing one, which is worse: the user is waiting on
22
- * that event. So the event always fires, and a disagreement is disclosed on it. The harm in
23
- * the report is a CONFIDENT false completion "the natural next action is to use the file" —
24
- * and a hedge is precisely what removes that.
28
+ * that event. So the event always fires, and a disagreement is disclosed on it. For a
29
+ * completion the harm is a CONFIDENT false success; for a failure it is a CONFIDENT false
30
+ * FAILED both removed by hedging, not by silence.
25
31
  *
26
32
  * ## Identity
27
33
  *
28
34
  * The two stores do not share an id. A progress row's `id` is the progress/tray identity;
29
- * the job's `id` is its public status handle (`6226e26ba97f8527` in the report, against tray
30
- * `93015fbfa0fa9933`). The row is matched against the job's `progressId ?? trayId`, which is
31
- * what writes those rows. An earlier version compared row id to job id and was therefore
35
+ * the job's `id` is its public status handle (`6226e26ba97f8527` in the #1574 report,
36
+ * against tray `93015fbfa0fa9933`; `07d14bb0c73bc007` against tray `31bba4a9cdb04e28` in
37
+ * #2057). The row is matched against the job's `progressId ?? trayId`, which is what
38
+ * writes those rows. An earlier version compared row id to job id and was therefore
32
39
  * INERT — it never matched anything, and its unit tests missed that because they fed
33
40
  * synthetic rows carrying whatever id the assertion wanted.
34
41
  */
@@ -38,6 +45,44 @@ function progressIdentityOf(job) {
38
45
  const tray = typeof job.trayId === "string" ? job.trayId : null;
39
46
  return progress ?? tray;
40
47
  }
48
+ /**
49
+ * The job record for this progress row, or null when nothing matches.
50
+ *
51
+ * (id, target) — the SAME key the supersession logic uses, and for the same reason: a
52
+ * concurrent LOCAL and POD transfer of one URL shares an id but is two transfers with two
53
+ * outcomes. Matching on id alone could annotate the wrong terminal, or miss a real
54
+ * disagreement by finding the other one first (review).
55
+ *
56
+ * A target is compared only when BOTH sides carry one. Rows and records that predate the
57
+ * field, or a route that never sets it, must not silently stop matching — that would make
58
+ * the check inert again, which is exactly how the first version shipped.
59
+ *
60
+ * PREFER THE EXACT (id, target) MATCH (review, round 3). Taking the first id match in
61
+ * array order let a TARGETLESS record shadow the exact one: a targetless "downloading"
62
+ * sitting before an exact-target terminal reported a disagreement that does not exist.
63
+ * The targetless record still stands in when nothing matches on target.
64
+ */
65
+ function matchingRecord(row, jobs) {
66
+ if (!row || typeof row !== "object")
67
+ return null;
68
+ const id = typeof row.id === "string" ? row.id : null;
69
+ if (!id)
70
+ return null;
71
+ if (!Array.isArray(jobs))
72
+ return null;
73
+ const target = typeof row.target === "string" ? row.target : null;
74
+ const sameId = jobs.filter((j) => j && typeof j === "object" && progressIdentityOf(j) === id);
75
+ if (!sameId.length)
76
+ return null;
77
+ return ((target ? sameId.find((j) => j.target === target) : undefined) ??
78
+ sameId.find((j) => typeof j.target !== "string") ??
79
+ (target ? undefined : sameId[0]) ??
80
+ null);
81
+ }
82
+ function recordStillDownloading(row, jobs) {
83
+ const record = matchingRecord(row, jobs);
84
+ return record != null && record.status === "downloading";
85
+ }
41
86
  /**
42
87
  * Does the job record disagree with announcing this row as completed?
43
88
  *
@@ -48,41 +93,26 @@ function progressIdentityOf(job) {
48
93
  export function completionDisagreesWithRecord(row, jobs) {
49
94
  if (!row || typeof row !== "object")
50
95
  return false;
51
- // Only COMPLETIONS. A failure event carries its own hedged wording (#1150) and must be
52
- // left entirely alone.
96
+ // Completions only. Failures have their own check (#2057); #1150's live-tray hedge
97
+ // stays a separate question (a different id writing the same filename).
53
98
  if (row.status !== "done")
54
99
  return false;
55
- const id = typeof row.id === "string" ? row.id : null;
56
- if (!id)
57
- return false;
58
- if (!Array.isArray(jobs))
59
- return false;
60
- // (id, target) the SAME key the supersession logic uses, and for the same reason: a
61
- // concurrent LOCAL and POD transfer of one URL shares an id but is two transfers with two
62
- // outcomes. Matching on id alone could annotate the wrong completion, or miss a real
63
- // disagreement by finding the other one first (review).
64
- //
65
- // A target is compared only when BOTH sides carry one. Rows and records that predate the
66
- // field, or a route that never sets it, must not silently stop matching — that would make
67
- // the check inert again, which is exactly how the first version shipped.
68
- const target = typeof row.target === "string" ? row.target : null;
69
- const sameId = jobs.filter((j) => j && typeof j === "object" && progressIdentityOf(j) === id);
70
- if (!sameId.length)
100
+ return recordStillDownloading(row, jobs);
101
+ }
102
+ /**
103
+ * Does the job record disagree with announcing this row as FAILED?
104
+ *
105
+ * Same identity and same positive-contradiction rule as {@link completionDisagreesWithRecord}.
106
+ * #1150 only looks at live TRAY rows of the same filename; this looks at the job RECORD
107
+ * `download_model action:"status"` reads. The reported case had no live tray row the tray
108
+ * itself was the error while status still showed the same id streaming and advancing.
109
+ */
110
+ export function failureDisagreesWithRecord(row, jobs) {
111
+ if (!row || typeof row !== "object")
71
112
  return false;
72
- // PREFER THE EXACT (id, target) MATCH (review, round 3). Taking the first id match in
73
- // array order let a TARGETLESS record shadow the exact one: a targetless "downloading"
74
- // sitting before an exact-target "done" reported a disagreement that does not exist, and
75
- // would have hedged a completion that was perfectly fine.
76
- //
77
- // The targetless record still stands in when nothing matches on target — that is what
78
- // keeps rows and records predating the field from silently going unmatched, which would
79
- // make the whole check inert again.
80
- const record = (target ? sameId.find((j) => j.target === target) : undefined) ??
81
- sameId.find((j) => typeof j.target !== "string") ??
82
- (target ? undefined : sameId[0]);
83
- if (!record)
113
+ if (row.status !== "error")
84
114
  return false;
85
- return record.status === "downloading";
115
+ return recordStillDownloading(row, jobs);
86
116
  }
87
117
  /** The disclosure appended to a completion the record disagrees with. Deliberately states
88
118
  * BOTH readings and what to do, rather than picking a winner this cannot establish. */
@@ -91,4 +121,8 @@ export const COMPLETION_DISAGREEMENT_NOTE = "CAVEAT: `download_model action:\"st
91
121
  "seconds, so this may simply be that — but a completion event has also been observed to " +
92
122
  "arrive minutes before the file existed (#1574). Check `download_model action:\"status\"` " +
93
123
  "and that the file is present before loading it.";
124
+ /** Instruction for a FAILED tray row whose job record is still downloading (#2057).
125
+ * Must not contain the word FAILED: that headline is what made the agent re-issue. */
126
+ export const FAILURE_DISAGREEMENT_NOTE = "`download_model action:\"status\"` still reports this transfer as downloading — " +
127
+ "do NOT report it as failed";
94
128
  //# sourceMappingURL=download-done-guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"download-done-guard.js","sourceRoot":"","sources":["../../src/orchestrator/download-done-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAmBH,oDAAoD;AACpD,SAAS,kBAAkB,CAAC,GAAY;IACtC,MAAM,QAAQ,GAAG,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,OAAO,QAAQ,IAAI,IAAI,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B,CAAC,GAAY,EAAE,IAAwB;IAClF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,uFAAuF;IACvF,uBAAuB;IACvB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACtB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,sFAAsF;IACtF,0FAA0F;IAC1F,qFAAqF;IACrF,wDAAwD;IACxD,EAAE;IACF,yFAAyF;IACzF,0FAA0F;IAC1F,yEAAyE;IACzE,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,kBAAkB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9F,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACjC,sFAAsF;IACtF,uFAAuF;IACvF,yFAAyF;IACzF,0DAA0D;IAC1D,EAAE;IACF,sFAAsF;IACtF,wFAAwF;IACxF,oCAAoC;IACpC,MAAM,MAAM,GACV,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;QAChD,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,OAAO,MAAM,CAAC,MAAM,KAAK,aAAa,CAAC;AACzC,CAAC;AAED;wFACwF;AACxF,MAAM,CAAC,MAAM,4BAA4B,GACvC,yFAAyF;IACzF,2FAA2F;IAC3F,yFAAyF;IACzF,2FAA2F;IAC3F,iDAAiD,CAAC"}
1
+ {"version":3,"file":"download-done-guard.js","sourceRoot":"","sources":["../../src/orchestrator/download-done-guard.ts"],"names":[],"mappings":"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"}
@@ -48,7 +48,7 @@ import { setConnectedPanelOrigins } from "../comfyui/fetch.js";
48
48
  import { publishConnectedPanelOrigins } from "../services/panel-origin-channel.js";
49
49
  import { logger } from "../utils/logger.js";
50
50
  import { listDownloadJobs } from "../services/download-jobs.js";
51
- import { completionDisagreesWithRecord } from "./download-done-guard.js";
51
+ import { completionDisagreesWithRecord, failureDisagreesWithRecord } from "./download-done-guard.js";
52
52
  import { assembleVocabularyHash, describeVocabularySkew } from "../tools/vocabulary.js";
53
53
  import { buildPanelToolDefs } from "./panel-tools.js";
54
54
  /** The panel vocabulary hashes whose MISMATCH has already been reported (#236).
@@ -129,6 +129,7 @@ import { dedupeAudioRefs, splitAudioAttachments } from "./audio-attachment.js";
129
129
  import { startPanelConsoleHttpServer } from "./panel-console-http.js";
130
130
  import { readComfyuiCrashLog, formatCrashNote } from "../services/crash-log.js";
131
131
  import { QueueMonitor } from "../services/queue-monitor.js";
132
+ import { formatQueueNote } from "./queue-note.js";
132
133
  import { RunCompletions, describe as describeCorrelation, } from "./run-completion-journal.js";
133
134
  import { createRunCompletionWatchdog, resolveHistoryCompletionImages, resolveHistoryCompletionStatus, } from "./run-completion-watchdog.js";
134
135
  import { AskAnswers, preview as previewQuestion } from "./ask-answer-journal.js";
@@ -170,7 +171,7 @@ const MCP_VERSION_RUNNING = (() => {
170
171
  * silently failed to interpolate would type-check, build, and ship the placeholder. */
171
172
  export const PANEL_SYSTEM_APPEND = `You are the autonomous assistant embedded directly in a ComfyUI sidebar panel. The person is working in ComfyUI and talks to you through that panel: their messages arrive as your prompts, and everything you write is shown to them in the panel chat. Write for that reader — lead with the result, keep replies short and concrete, and don't narrate routine internal steps.
172
173
 
173
- You can SEE and EDIT the workflow the user currently has open, via the panel_* tools (panel_graph_outline, panel_query_graph, panel_add_node, panel_connect, panel_set_widget, panel_run, panel_get_errors, panel_save_workflow, …). STRONGLY PREFER building on their live canvas: read it first (panel_graph_outline, then panel_query_graph for specifics), add/wire/configure nodes with the panel_* tools, then panel_run to queue it — so the user watches the work happen and the result loads in their own workflow with full Ctrl+Z undo. Only fall back to the headless generate_image/enqueue_workflow tools when the user explicitly wants a one-off they don't need on their canvas, or when no panel tab is connected (a panel_* call will error if so). On a LARGE graph (a loaded pack/template with dozens of nodes), do NOT dump the whole thing and scan it — and NEVER shell out to grep/jq/python over a saved workflow file. To UNDERSTAND the graph, call panel_graph_outline FIRST: a compact, dependency-ordered TEXT map (nodes topologically sorted source→sink, each with its key widgets and ← inputs / → outputs wiring, plus a groups index) made for you to read top-to-bottom. To PINPOINT and INSPECT specific nodes, use panel_query_graph: filter by types/title/widget predicates ('cfg>7'), traverse upstream_of/downstream_of a node, aggregate with group_by:'type', and read ONE node's exact slot/widget detail with {ids:[id], fields:'detail'} — output is token-bounded so it can never flood your context. panel_find_nodes remains for free-text search across all fields.
174
+ You can SEE and EDIT the workflow the user currently has open, via the panel_* tools (panel_graph_outline, panel_query_graph, panel_add_node, panel_connect, panel_set_widget, panel_run, panel_get_errors, panel_save_workflow, panel_kitchen, …). STRONGLY PREFER building on their live canvas: read it first (panel_graph_outline, then panel_query_graph for specifics), add/wire/configure nodes with the panel_* tools, then panel_run to queue it — so the user watches the work happen and the result loads in their own workflow with full Ctrl+Z undo. Only fall back to the headless generate_image/enqueue_workflow tools when the user explicitly wants a one-off they don't need on their canvas, or when no panel tab is connected (a panel_* call will error if so). On a LARGE graph (a loaded pack/template with dozens of nodes), do NOT dump the whole thing and scan it — and NEVER shell out to grep/jq/python over a saved workflow file. To UNDERSTAND the graph, call panel_graph_outline FIRST: a compact, dependency-ordered TEXT map (nodes topologically sorted source→sink, each with its key widgets and ← inputs / → outputs wiring, plus a groups index) made for you to read top-to-bottom. To PINPOINT and INSPECT specific nodes, use panel_query_graph: filter by types/title/widget predicates ('cfg>7'), traverse upstream_of/downstream_of a node, aggregate with group_by:'type', and read ONE node's exact slot/widget detail with {ids:[id], fields:'detail'} — output is token-bounded so it can never flood your context. panel_find_nodes remains for free-text search across all fields.
174
175
 
175
176
  ${DEFERRED_PANEL_TOOLS_STEERING}
176
177
 
@@ -397,31 +398,6 @@ function stallThresholdMs() {
397
398
  const s = Number(process.env.COMFYUI_MCP_STALL_S);
398
399
  return Number.isFinite(s) && s > 0 ? Math.round(s * 1000) : 180000;
399
400
  }
400
- /** Build a one-line agent note from a stall/backlog report, or null when the
401
- * queue is healthy. Stall takes priority over a plain backlog. */
402
- function formatQueueNote(rep) {
403
- if (rep.stalled) {
404
- const secs = Math.round(rep.stalledForMs / 1000);
405
- return (`⚠️ The current ComfyUI render appears STALLED: ` +
406
- `${rep.currentNode ? `node ${rep.currentNode} ` : ""}${rep.progress ? `(progress ${rep.progress}) ` : ""}` +
407
- `on prompt ${rep.runningPromptId ?? "?"} has not advanced for ~${secs}s. ComfyUI only checks interrupts ` +
408
- `BETWEEN steps, so a stuck step can ignore a cancel. If it's wedged: call queue (action:"cancel") with ` +
409
- `clear_pending:true; if it reports the job still wedged, restart_comfyui / panel_restart_comfyui. ` +
410
- `Do NOT queue another run on top.`);
411
- }
412
- // A plain backlog (depth > 1) is NOT evidence of a wedge — deliberately queuing
413
- // a batch is a normal workflow. Suppress the note entirely when the in-flight
414
- // work is this session's own recent jobs, and when it isn't, report it NEUTRALLY
415
- // (no false "you likely queued behind a stuck job" diagnosis, no destructive
416
- // clear_pending as the headline). A genuinely stalled job is handled above (#559).
417
- if (rep.backlog && !rep.selfAttributed) {
418
- const pending = Math.max(0, rep.queueDepth - 1);
419
- return (`ℹ️ ComfyUI queue: ${rep.queueDepth} tasks in flight (1 running + ${pending} pending) that this session ` +
420
- `didn't queue. This is only a problem if the running one is stuck — inspect with queue (action:"list"). ` +
421
- `queue (action:"cancel_queued") drops a single pending item; queue (action:"cancel") with clear_pending:true resets everything.`);
422
- }
423
- return null;
424
- }
425
401
  /**
426
402
  * Lockfile path for a given bridge port. The orchestrator self-registers its
427
403
  * REAL node pid here (not the npx shim's), plus the ComfyUI pid that launched
@@ -5574,6 +5550,18 @@ export async function runPanelOrchestrator() {
5574
5550
  if (disagreeing.length) {
5575
5551
  logger.warn("[panel-orchestrator] a download completion disagrees with the job record; disclosing rather than suppressing (#1574)", { ids: disagreeing.map((d) => String(d.id ?? "")) });
5576
5552
  }
5553
+ // #2057 — the SAME split in the other direction. A tray ERROR while the job record
5554
+ // still says downloading is how "Model download FAILED" + "NOTHING transferred"
5555
+ // fired for a 19.53 GB fetch whose status (same id) was still streaming and
5556
+ // advancing. #1150 only sees a live TRAY row of that filename; here the tray
5557
+ // row itself was the error, so that hedge never ran. Flag it so the formatter
5558
+ // will not say FAILED.
5559
+ const failedDisagreeing = settled.filter((d) => failureDisagreesWithRecord(d, records));
5560
+ for (const d of failedDisagreeing)
5561
+ d.recordDisagrees = true;
5562
+ if (failedDisagreeing.length) {
5563
+ logger.warn("[panel-orchestrator] a download failure disagrees with the job record; disclosing rather than announcing FAILED (#2057)", { ids: failedDisagreeing.map((d) => String(d.id ?? "")) });
5564
+ }
5577
5565
  // #884 — a download has no originating TAB (its row names the owning
5578
5566
  // conversation), so its turn INHERITS the conversation's LAST
5579
5567
  // ESTABLISHED origin — never the active tab (confirming gate 2, P0 rule: