comfyui-mcp 0.52.114 → 0.52.115
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/dist/orchestrator/download-done-guard.js +83 -16
- package/dist/orchestrator/download-done-guard.js.map +1 -1
- package/dist/orchestrator/download-done-loop.js +38 -0
- package/dist/orchestrator/download-done-loop.js.map +1 -0
- package/dist/orchestrator/index.js +52 -7
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/panel-agent.js +9 -3
- package/dist/orchestrator/panel-agent.js.map +1 -1
- package/dist/services/download-jobs.js +180 -49
- package/dist/services/download-jobs.js.map +1 -1
- package/dist/services/download-progress.js +29 -15
- package/dist/services/download-progress.js.map +1 -1
- package/dist/tools/model-management.js +8 -5
- package/dist/tools/model-management.js.map +1 -1
- package/package.json +1 -1
|
@@ -53,14 +53,9 @@ function progressIdentityOf(job) {
|
|
|
53
53
|
* outcomes. Matching on id alone could annotate the wrong terminal, or miss a real
|
|
54
54
|
* disagreement by finding the other one first (review).
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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.
|
|
56
|
+
* Target identity is production data, not a test-only discriminator. A row or record
|
|
57
|
+
* without it is ambiguous when local and pod transfers share an id, so legacy targetless
|
|
58
|
+
* records fail closed and never suppress or promote a terminal event.
|
|
64
59
|
*/
|
|
65
60
|
function matchingRecord(row, jobs) {
|
|
66
61
|
if (!row || typeof row !== "object")
|
|
@@ -70,18 +65,37 @@ function matchingRecord(row, jobs) {
|
|
|
70
65
|
return null;
|
|
71
66
|
if (!Array.isArray(jobs))
|
|
72
67
|
return null;
|
|
73
|
-
const target = typeof row.target === "string" ? row.target : null;
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
const target = typeof row.target === "string" && row.target.length > 0 ? row.target : null;
|
|
69
|
+
if (!target)
|
|
70
|
+
return null;
|
|
71
|
+
const candidates = jobs.filter((j) => j &&
|
|
72
|
+
typeof j === "object" &&
|
|
73
|
+
progressIdentityOf(j) === id &&
|
|
74
|
+
typeof j.target === "string" &&
|
|
75
|
+
j.target.length > 0 &&
|
|
76
|
+
j.target === target);
|
|
77
|
+
if (!candidates.length)
|
|
76
78
|
return null;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
// A reconnect can leave both an older terminal snapshot and the newer live record in
|
|
80
|
+
// the persisted store. `download_model action:"status"` resolves that ambiguity in
|
|
81
|
+
// favour of the live transfer; the tray guard must make the same choice or it can
|
|
82
|
+
// announce FAILED while status is still advancing.
|
|
83
|
+
// `download_model action:"status"` treats stale persisted rows as historical
|
|
84
|
+
// diagnostics and resolves the terminal record instead. Prefer a fresh live record
|
|
85
|
+
// when both snapshots are present, but keep a stale one available for error-detail
|
|
86
|
+
// lookup and explicit non-live handling below.
|
|
87
|
+
return (candidates.find((j) => j.status === "downloading" && j.staleInflight !== true) ??
|
|
88
|
+
candidates.find((j) => j.status !== "downloading") ??
|
|
89
|
+
candidates.find((j) => j.status === "downloading") ??
|
|
90
|
+
candidates[0] ??
|
|
80
91
|
null);
|
|
81
92
|
}
|
|
82
93
|
function recordStillDownloading(row, jobs) {
|
|
83
94
|
const record = matchingRecord(row, jobs);
|
|
84
|
-
|
|
95
|
+
// This is deliberately the same freshness boundary as the status action. A stale
|
|
96
|
+
// persisted heartbeat can remain on disk for six hours, but status resolves it as a
|
|
97
|
+
// terminal/no-live answer; it must not suppress a real FAILED tray notification.
|
|
98
|
+
return record != null && record.status === "downloading" && record.staleInflight !== true;
|
|
85
99
|
}
|
|
86
100
|
/**
|
|
87
101
|
* Does the job record disagree with announcing this row as completed?
|
|
@@ -99,6 +113,19 @@ export function completionDisagreesWithRecord(row, jobs) {
|
|
|
99
113
|
return false;
|
|
100
114
|
return recordStillDownloading(row, jobs);
|
|
101
115
|
}
|
|
116
|
+
/** Classify a terminal tray row against the exact production job identity. */
|
|
117
|
+
export function failureRecordDisposition(row, jobs) {
|
|
118
|
+
if (!row || typeof row !== "object" || row.status !== "error") {
|
|
119
|
+
return { disposition: "none", record: null };
|
|
120
|
+
}
|
|
121
|
+
const record = matchingRecord(row, jobs);
|
|
122
|
+
if (!record || record.status !== "downloading") {
|
|
123
|
+
return { disposition: "none", record: null };
|
|
124
|
+
}
|
|
125
|
+
if (record.staleInflight === true)
|
|
126
|
+
return { disposition: "stale", record };
|
|
127
|
+
return { disposition: row.progressAdvanced === true ? "advancing" : "stalled", record };
|
|
128
|
+
}
|
|
102
129
|
/**
|
|
103
130
|
* Does the job record disagree with announcing this row as FAILED?
|
|
104
131
|
*
|
|
@@ -112,7 +139,47 @@ export function failureDisagreesWithRecord(row, jobs) {
|
|
|
112
139
|
return false;
|
|
113
140
|
if (row.status !== "error")
|
|
114
141
|
return false;
|
|
115
|
-
|
|
142
|
+
// A fresh heartbeat says only that persistence happened. The failure hedge requires
|
|
143
|
+
// an actual byte increase observed by the poller, or a stalled stream could suppress
|
|
144
|
+
// a genuine FAILED notification indefinitely. Stalled/stale records are reconciled to
|
|
145
|
+
// terminal state by the production poll seam instead of being treated as live.
|
|
146
|
+
return failureRecordDisposition(row, jobs).disposition === "advancing";
|
|
147
|
+
}
|
|
148
|
+
/** Bound one error string before it reaches the tray/agent event. */
|
|
149
|
+
export function boundedDownloadError(value) {
|
|
150
|
+
if (typeof value !== "string")
|
|
151
|
+
return undefined;
|
|
152
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
153
|
+
return compact ? compact.slice(0, 400) : undefined;
|
|
154
|
+
}
|
|
155
|
+
/** Return the bounded error detail from the authoritative terminal record, when present. */
|
|
156
|
+
export function failureErrorDetail(row, jobs) {
|
|
157
|
+
if (!row || typeof row !== "object" || row.status !== "error")
|
|
158
|
+
return undefined;
|
|
159
|
+
const rowError = boundedDownloadError(row.error);
|
|
160
|
+
const record = matchingRecord(row, jobs);
|
|
161
|
+
return (rowError ??
|
|
162
|
+
(record?.status !== "downloading" ? boundedDownloadError(record?.error) : undefined));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Reconcile the failure half of one production download_done batch.
|
|
166
|
+
*
|
|
167
|
+
* The poll loop calls this after it has read the tray rows and authoritative job records,
|
|
168
|
+
* before injecting the batch into the panel agent. Keeping the mutation here makes that
|
|
169
|
+
* production seam directly testable without treating PanelAgentManager injection as the
|
|
170
|
+
* producer-side proof.
|
|
171
|
+
*/
|
|
172
|
+
export function reconcileDownloadDoneFailures(rows, jobs) {
|
|
173
|
+
const disagreeing = [];
|
|
174
|
+
for (const row of rows) {
|
|
175
|
+
if (failureDisagreesWithRecord(row, jobs)) {
|
|
176
|
+
row.recordDisagrees = true;
|
|
177
|
+
disagreeing.push(row);
|
|
178
|
+
}
|
|
179
|
+
if (row.status === "error")
|
|
180
|
+
row.error = failureErrorDetail(row, jobs);
|
|
181
|
+
}
|
|
182
|
+
return disagreeing;
|
|
116
183
|
}
|
|
117
184
|
/** The disclosure appended to a completion the record disagrees with. Deliberately states
|
|
118
185
|
* BOTH readings and what to do, rather than picking a winner this cannot establish. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download-done-guard.js","sourceRoot":"","sources":["../../src/orchestrator/download-done-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;
|
|
1
|
+
{"version":3,"file":"download-done-guard.js","sourceRoot":"","sources":["../../src/orchestrator/download-done-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { listDownloadJobs, reconcileStalledDownloadRecord, } from "../services/download-jobs.js";
|
|
2
|
+
import { failureErrorDetail, failureRecordDisposition, reconcileDownloadDoneFailures, } from "./download-done-guard.js";
|
|
3
|
+
/**
|
|
4
|
+
* The failure-reconciliation stage used by the download poll loop.
|
|
5
|
+
*
|
|
6
|
+
* The live loop already has the authoritative records because the completion guard reads
|
|
7
|
+
* them once per flush; callers pass those records to avoid a second filesystem scan. Tests
|
|
8
|
+
* may omit them to exercise the same production lookup against the persisted job store.
|
|
9
|
+
*/
|
|
10
|
+
export function reconcileDownloadDoneBatch(rows, records, progress, onInject) {
|
|
11
|
+
let authoritative = records;
|
|
12
|
+
if (!authoritative) {
|
|
13
|
+
try {
|
|
14
|
+
authoritative = listDownloadJobs();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
authoritative = [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const row of rows)
|
|
21
|
+
row.progressAdvanced = progress?.hasAdvanced(row) === true;
|
|
22
|
+
for (const row of rows) {
|
|
23
|
+
const match = failureRecordDisposition(row, authoritative);
|
|
24
|
+
if (match.disposition !== "stalled" && match.disposition !== "stale")
|
|
25
|
+
continue;
|
|
26
|
+
const durable = reconcileStalledDownloadRecord(match.record, failureErrorDetail(row, authoritative));
|
|
27
|
+
if (!durable)
|
|
28
|
+
row.recordDisagrees = true;
|
|
29
|
+
}
|
|
30
|
+
const disagreeing = reconcileDownloadDoneFailures(rows, authoritative);
|
|
31
|
+
// The advancement proof is producer-internal; recordDisagrees is the only reconciliation
|
|
32
|
+
// result the consumer needs. Do not add the polling detail to the agent event payload.
|
|
33
|
+
for (const row of rows)
|
|
34
|
+
delete row.progressAdvanced;
|
|
35
|
+
onInject?.({ kind: "download_done", downloads: rows });
|
|
36
|
+
return disagreeing;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=download-done-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-done-loop.js","sourceRoot":"","sources":["../../src/orchestrator/download-done-loop.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,8BAA8B,GAE/B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,6BAA6B,GAE9B,MAAM,0BAA0B,CAAC;AAOlC;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,IAAS,EACT,OAAgC,EAChC,QAAmG,EACnG,QAAoD;IAEpD,IAAI,aAAa,GAAG,OAAO,CAAC;IAC5B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,aAAa,GAAG,gBAAgB,EAAE,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,aAAa,GAAG,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,GAAG,CAAC,gBAAgB,GAAG,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;IACnF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,wBAAwB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAC3D,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO;YAAE,SAAS;QAC/E,MAAM,OAAO,GAAG,8BAA8B,CAC5C,KAAK,CAAC,MAAqB,EAC3B,kBAAkB,CAAC,GAAG,EAAE,aAAa,CAAC,CACvC,CAAC;QACF,IAAI,CAAC,OAAO;YAAE,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC;IAC3C,CAAC;IACD,MAAM,WAAW,GAAG,6BAA6B,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACvE,yFAAyF;IACzF,uFAAuF;IACvF,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,OAAO,GAAG,CAAC,gBAAgB,CAAC;IACpD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -50,7 +50,8 @@ import { startPanelImageRelayServer, verifyPanelImageRelayCapability, } from "..
|
|
|
50
50
|
import { startPanelTemplateRelayServer, verifyPanelTemplateRelayCapability, } from "../services/panel-template-relay.js";
|
|
51
51
|
import { logger } from "../utils/logger.js";
|
|
52
52
|
import { listDownloadJobs } from "../services/download-jobs.js";
|
|
53
|
-
import { completionDisagreesWithRecord,
|
|
53
|
+
import { boundedDownloadError, completionDisagreesWithRecord, } from "./download-done-guard.js";
|
|
54
|
+
import { reconcileDownloadDoneBatch } from "./download-done-loop.js";
|
|
54
55
|
import { assembleVocabularyHash, describeVocabularySkew } from "../tools/vocabulary.js";
|
|
55
56
|
import { buildPanelToolDefs } from "./panel-tools.js";
|
|
56
57
|
/** The panel vocabulary hashes whose MISMATCH has already been reported (#236).
|
|
@@ -573,9 +574,39 @@ export function pushModelsFrame(bridge, panelTabId, models, current, backend) {
|
|
|
573
574
|
* hello/re-hello so a reconnect cannot retain rows from an old process.
|
|
574
575
|
*/
|
|
575
576
|
export class DownloadProgressSnapshots {
|
|
577
|
+
/** A recent byte increase is required; heartbeat-only rows must not keep this hedge live. */
|
|
578
|
+
static ADVANCEMENT_MAX_AGE_MS = 5_000;
|
|
576
579
|
lastSnapshot = null;
|
|
577
580
|
rows = [];
|
|
581
|
+
progressByKey = new Map();
|
|
582
|
+
lastAdvancedAt = new Map();
|
|
583
|
+
progressKey(row) {
|
|
584
|
+
if (typeof row.id !== "string" || !row.id)
|
|
585
|
+
return null;
|
|
586
|
+
const target = typeof row.target === "string" ? row.target : "";
|
|
587
|
+
const attempt = typeof row.attempt === "number" ? String(row.attempt) : "";
|
|
588
|
+
return `${row.id}\n${target}\n${attempt}`;
|
|
589
|
+
}
|
|
578
590
|
record(rows) {
|
|
591
|
+
for (const row of rows) {
|
|
592
|
+
if (row.status !== "downloading")
|
|
593
|
+
continue;
|
|
594
|
+
const key = this.progressKey(row);
|
|
595
|
+
const downloaded = typeof row.downloaded === "number" ? row.downloaded : undefined;
|
|
596
|
+
const total = typeof row.total === "number" ? row.total : undefined;
|
|
597
|
+
if (!key ||
|
|
598
|
+
downloaded === undefined ||
|
|
599
|
+
!Number.isFinite(downloaded) ||
|
|
600
|
+
downloaded < 0 ||
|
|
601
|
+
total === undefined ||
|
|
602
|
+
!Number.isFinite(total) ||
|
|
603
|
+
total < 0)
|
|
604
|
+
continue;
|
|
605
|
+
const previous = this.progressByKey.get(key);
|
|
606
|
+
if (previous && downloaded > previous.downloaded)
|
|
607
|
+
this.lastAdvancedAt.set(key, Date.now());
|
|
608
|
+
this.progressByKey.set(key, { downloaded, total });
|
|
609
|
+
}
|
|
579
610
|
const snapshot = JSON.stringify(rows);
|
|
580
611
|
if (snapshot === this.lastSnapshot)
|
|
581
612
|
return false;
|
|
@@ -583,6 +614,15 @@ export class DownloadProgressSnapshots {
|
|
|
583
614
|
this.rows = rows;
|
|
584
615
|
return true;
|
|
585
616
|
}
|
|
617
|
+
/** True only after a recent later progress row proves downloaded bytes increased. */
|
|
618
|
+
hasAdvanced(row, now = Date.now()) {
|
|
619
|
+
if (typeof row.id !== "string" || !row.id)
|
|
620
|
+
return false;
|
|
621
|
+
const target = typeof row.target === "string" ? row.target : "";
|
|
622
|
+
const attempt = typeof row.attempt === "number" ? String(row.attempt) : "";
|
|
623
|
+
const advancedAt = this.lastAdvancedAt.get(`${row.id}\n${target}\n${attempt}`);
|
|
624
|
+
return advancedAt !== undefined && now - advancedAt <= DownloadProgressSnapshots.ADVANCEMENT_MAX_AGE_MS;
|
|
625
|
+
}
|
|
586
626
|
forPanel() {
|
|
587
627
|
return this.rows;
|
|
588
628
|
}
|
|
@@ -5472,6 +5512,13 @@ export async function runPanelOrchestrator() {
|
|
|
5472
5512
|
}
|
|
5473
5513
|
if (!row || typeof row !== "object")
|
|
5474
5514
|
continue;
|
|
5515
|
+
if ("error" in row) {
|
|
5516
|
+
const error = boundedDownloadError(row.error);
|
|
5517
|
+
if (error)
|
|
5518
|
+
row.error = error;
|
|
5519
|
+
else
|
|
5520
|
+
delete row.error;
|
|
5521
|
+
}
|
|
5475
5522
|
const updated = typeof row.updated === "number" ? row.updated : now;
|
|
5476
5523
|
parsed.push({ full, row, status: row.status, updated });
|
|
5477
5524
|
}
|
|
@@ -5553,6 +5600,7 @@ export async function runPanelOrchestrator() {
|
|
|
5553
5600
|
target: typeof row.target === "string" ? row.target : undefined,
|
|
5554
5601
|
name: String(row.name ?? row.id ?? "model"),
|
|
5555
5602
|
status: String(status),
|
|
5603
|
+
error: boundedDownloadError(row.error),
|
|
5556
5604
|
attempt: typeof row.attempt === "number" ? row.attempt : undefined,
|
|
5557
5605
|
supKey,
|
|
5558
5606
|
});
|
|
@@ -5645,9 +5693,9 @@ export async function runPanelOrchestrator() {
|
|
|
5645
5693
|
// advancing. #1150 only sees a live TRAY row of that filename; here the tray
|
|
5646
5694
|
// row itself was the error, so that hedge never ran. Flag it so the formatter
|
|
5647
5695
|
// will not say FAILED.
|
|
5648
|
-
const failedDisagreeing = settled
|
|
5649
|
-
|
|
5650
|
-
|
|
5696
|
+
const failedDisagreeing = reconcileDownloadDoneBatch(settled, records, downloadSnapshots, (event) => manager.injectEvent(key, event, {
|
|
5697
|
+
mid: turnOrigins.mintInheritedOrigin(),
|
|
5698
|
+
}));
|
|
5651
5699
|
if (failedDisagreeing.length) {
|
|
5652
5700
|
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 ?? "")) });
|
|
5653
5701
|
}
|
|
@@ -5660,9 +5708,6 @@ export async function runPanelOrchestrator() {
|
|
|
5660
5708
|
// ran — the turn routed to whatever tab was active (confirming gate 3,
|
|
5661
5709
|
// P1). The minted mid contributes nothing and the batch close inherits
|
|
5662
5710
|
// (or refuses, when no origin was ever established).
|
|
5663
|
-
manager.injectEvent(key, { kind: "download_done", downloads: settled }, {
|
|
5664
|
-
mid: turnOrigins.mintInheritedOrigin(),
|
|
5665
|
-
});
|
|
5666
5711
|
}
|
|
5667
5712
|
// MCP-child control channel (#269): runpod_* tools that ran in spawned
|
|
5668
5713
|
// agent children ask the orchestrator to retarget / watch / unwatch /
|