comfyui-mcp 0.52.138 → 0.52.139
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.
|
@@ -5181,11 +5181,41 @@ function promotedEnvelopeCarriesEvidence(payload) {
|
|
|
5181
5181
|
/** The shipped panel's only definitive non-promoted result is its own
|
|
5182
5182
|
* `Node <id> (<type>) is not a subgraph` refusal. Every other graph_get_subgraph
|
|
5183
5183
|
* error is an indeterminate read: a disconnect, stale route, old panel, or
|
|
5184
|
-
* transport failure can all leave a promoted container unresolved.
|
|
5184
|
+
* transport failure can all leave a promoted container unresolved.
|
|
5185
|
+
*
|
|
5186
|
+
* #2394 — the parenthesised segment is the node TYPE, and a node type may itself
|
|
5187
|
+
* contain parentheses: rgthree names every node that way (`Power Lora Loader
|
|
5188
|
+
* (rgthree)`), as do several other packs (`KSampler (Efficient)`). A `[^)]*`
|
|
5189
|
+
* body stops at the FIRST `)`, so `Node 82 (Power Lora Loader (rgthree)) is not
|
|
5190
|
+
* a subgraph` did not match its own definitive message. The read was then
|
|
5191
|
+
* treated as indeterminate and the write refused with "graph_get_subgraph could
|
|
5192
|
+
* not determine whether the addressed node is a promoted container" — the exact
|
|
5193
|
+
* refusal #2394 reported, on a node that is provably ordinary.
|
|
5194
|
+
*
|
|
5195
|
+
* The type body is matched as a BALANCED group (plain text, or text containing
|
|
5196
|
+
* one parenthesised group) UNIONED with the original `[^)]*` branch. Both halves
|
|
5197
|
+
* of that union are load-bearing, and each was added because a simpler edit
|
|
5198
|
+
* regressed a case that already worked (both caught by the codex gate):
|
|
5199
|
+
*
|
|
5200
|
+
* - Widening to `.*` looks equivalent but is not: `.` does not cross a newline
|
|
5201
|
+
* and `[^)]*` did, so a node type carrying a newline stopped matching.
|
|
5202
|
+
* - The balanced branch alone rejects an UNMATCHED `(` — a type like
|
|
5203
|
+
* `Power (rgthree` yields `Node 82 (Power (rgthree) is not a subgraph`, which
|
|
5204
|
+
* the original matched by stopping at the first `)`.
|
|
5205
|
+
*
|
|
5206
|
+
* Keeping the original branch in the union makes the accept set a strict
|
|
5207
|
+
* superset of the original's, so no message that was definitive before can
|
|
5208
|
+
* become indeterminate now. The reject set is unchanged: still anchored at
|
|
5209
|
+
* `^Error: Node `, still requiring the literal ` is not a subgraph` tail. A type
|
|
5210
|
+
* nesting parentheses more than one level deep still does not match and is
|
|
5211
|
+
* refused as indeterminate — the fail-closed direction, which is the correct
|
|
5212
|
+
* default for a predicate whose `true` authorizes an ordinary write. The
|
|
5213
|
+
* `[canvas-root-divergence]` diagnosis never begins with `Error: Node `, so it
|
|
5214
|
+
* cannot collide. */
|
|
5185
5215
|
function isDefinitiveNonPromotedSubgraphRead(res) {
|
|
5186
5216
|
if (!res.isError)
|
|
5187
5217
|
return false;
|
|
5188
|
-
return /^Error:\s*Node\s+\S+(?:\s+\([^)]*\))?\s+is not a subgraph\b/i.test(textOfToolResult(res));
|
|
5218
|
+
return /^Error:\s*Node\s+\S+(?:\s+\((?:(?:[^()]|\([^()]*\))*|[^)]*)\))?\s+is not a subgraph\b/i.test(textOfToolResult(res));
|
|
5189
5219
|
}
|
|
5190
5220
|
/**
|
|
5191
5221
|
* panel#1869 — the panel's `[canvas-root-divergence]` refusal is a DIAGNOSIS,
|
|
@@ -5564,6 +5594,41 @@ function promotedPanelBuildRefusal(ctx, nodeId, widget, reason, capability) {
|
|
|
5564
5594
|
* indeterminate read fails closed because the panel can resolve a promoted
|
|
5565
5595
|
* container to its unsafe inner node.
|
|
5566
5596
|
*/
|
|
5597
|
+
/**
|
|
5598
|
+
* Why the panel binding is no longer the one we captured, or undefined when it is.
|
|
5599
|
+
*
|
|
5600
|
+
* WHY THIS IS A FUNCTION AND NOT A THIRD COPY. An early `return null` out of the
|
|
5601
|
+
* promoted pre-dispatch check does not mean "refuse" — it means "this is not a promoted
|
|
5602
|
+
* write, proceed on the ordinary path". So every exit that crosses an `await` is a WRITE
|
|
5603
|
+
* AUTHORIZATION issued against a binding that may have moved underneath it, and each one
|
|
5604
|
+
* needs the same re-read. There were two hand-written copies of these five checks; a third
|
|
5605
|
+
* exit was then added between them without one, which is #2401, and fixing that exit alone
|
|
5606
|
+
* left the next one bare, which is #2409. One copy, called by each exit, so the next early
|
|
5607
|
+
* return has one obvious thing to call.
|
|
5608
|
+
*
|
|
5609
|
+
* `where` is appended to each message so the caller keeps naming its own await.
|
|
5610
|
+
*/
|
|
5611
|
+
function panelBindingDriftReason(ctx, where, hasIdentityApi, identityBefore, tabBefore) {
|
|
5612
|
+
if (!hasIdentityApi)
|
|
5613
|
+
return `the receiver identity was unavailable ${where}`;
|
|
5614
|
+
if (!isUsablePanelConnectionIdentity(identityBefore)) {
|
|
5615
|
+
return `the panel connection identity was unavailable ${where}`;
|
|
5616
|
+
}
|
|
5617
|
+
let identityAfter;
|
|
5618
|
+
try {
|
|
5619
|
+
identityAfter = ctx.panelConnectionIdentity?.();
|
|
5620
|
+
}
|
|
5621
|
+
catch {
|
|
5622
|
+
return `the panel connection identity became unreadable ${where}`;
|
|
5623
|
+
}
|
|
5624
|
+
if (ctx.tabId !== tabBefore || !isUsablePanelConnectionIdentity(identityAfter)) {
|
|
5625
|
+
return `the panel tab or connection changed ${where}`;
|
|
5626
|
+
}
|
|
5627
|
+
if (!samePanelConnectionIdentity(identityBefore, identityAfter)) {
|
|
5628
|
+
return `the panel session or connection changed ${where}`;
|
|
5629
|
+
}
|
|
5630
|
+
return undefined;
|
|
5631
|
+
}
|
|
5567
5632
|
async function preparePromotedWidgetWrite(ctx, nodeId, widget) {
|
|
5568
5633
|
// Production PanelToolCtx is always backed by UiBridge, which exposes this
|
|
5569
5634
|
// per-hello capability getter. A few transport-only forwarding contexts (and
|
|
@@ -5600,12 +5665,23 @@ async function preparePromotedWidgetWrite(ctx, nodeId, widget) {
|
|
|
5600
5665
|
// absent. An unreadable scope probe is not permission for an outer write;
|
|
5601
5666
|
// it falls through to the existing conservative graph_get_subgraph path.
|
|
5602
5667
|
const targetScope = await readPromotedTargetScope(ctx, nodeId);
|
|
5603
|
-
if (targetScope?.activeView === "root" && targetScope.node === "ordinary")
|
|
5668
|
+
if (targetScope?.activeView === "root" && targetScope.node === "ordinary") {
|
|
5669
|
+
// The scope probe crossed an await. Re-read the binding before taking the
|
|
5670
|
+
// ordinary fast path; otherwise a tab/connection rebind can make this
|
|
5671
|
+
// probe's ordinary classification authorize a write on the new receiver.
|
|
5672
|
+
const drift = panelBindingDriftReason(ctx, "after the scope probe", hasIdentityApi, identityBefore, tabBefore);
|
|
5673
|
+
if (drift)
|
|
5674
|
+
return promotedWriteRefusal(widget, drift);
|
|
5604
5675
|
return null;
|
|
5676
|
+
}
|
|
5605
5677
|
const sub = await ctx.call({ cmd: "graph_get_subgraph", node_id: nodeId });
|
|
5606
5678
|
if (sub.isError) {
|
|
5607
|
-
if (isDefinitiveNonPromotedSubgraphRead(sub))
|
|
5679
|
+
if (isDefinitiveNonPromotedSubgraphRead(sub)) {
|
|
5680
|
+
const drift2 = panelBindingDriftReason(ctx, "after the subgraph read", hasIdentityApi, identityBefore, tabBefore);
|
|
5681
|
+
if (drift2)
|
|
5682
|
+
return promotedWriteRefusal(widget, drift2);
|
|
5608
5683
|
return null;
|
|
5684
|
+
}
|
|
5609
5685
|
return promotedSubgraphReadRefusal(widget, sub, "graph_get_subgraph could not determine whether the addressed node is a promoted container");
|
|
5610
5686
|
}
|
|
5611
5687
|
let payload = parseToolResultJson(sub);
|
|
@@ -8522,6 +8598,7 @@ async function rebindWorkflowFence(ctx, opts) {
|
|
|
8522
8598
|
before,
|
|
8523
8599
|
kind: "no_uuid",
|
|
8524
8600
|
why: "the active workflow record carries no usable workflow_uuid",
|
|
8601
|
+
active,
|
|
8525
8602
|
};
|
|
8526
8603
|
}
|
|
8527
8604
|
// "already current" requires a KNOWN prior fence equal to the live uuid. An
|
|
@@ -9161,8 +9238,19 @@ WHAT TO DO: settle it with a graph read — call panel_graph_outline. That ` +
|
|
|
9161
9238
|
* active object and accept the returned UUID only while it still names this
|
|
9162
9239
|
* open's canonical target; otherwise leave the old stamp to fail closed.
|
|
9163
9240
|
*/
|
|
9164
|
-
async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult) {
|
|
9241
|
+
async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult, legacyRebind) {
|
|
9165
9242
|
const parsedOpen = parseToolResultJson(openResult);
|
|
9243
|
+
// #971 compatibility: older/lightweight bridges may return only an explicit
|
|
9244
|
+
// transport route for a successful open. The proof is supplied by the one
|
|
9245
|
+
// immediately following open only after the caller consumed it before any
|
|
9246
|
+
// await; it is never a substitute for the modern bare-alias `opened.path` +
|
|
9247
|
+
// confirmed active workflow contract.
|
|
9248
|
+
const legacyReboundRoute = legacyRebind?.tabId === ctx.tabId &&
|
|
9249
|
+
legacyRebind.savedIdentity === canonicalBareSavedIdentity(requestedPath) &&
|
|
9250
|
+
!openResult.isError &&
|
|
9251
|
+
parsedOpen?.ok === true &&
|
|
9252
|
+
parsedOpen.routedTo === ctx.tabId &&
|
|
9253
|
+
(typeof ctx.bridge.canReach !== "function" || ctx.bridge.canReach(ctx.tabId));
|
|
9166
9254
|
const opened = parsedOpen?.opened;
|
|
9167
9255
|
const openedPath = opened && typeof opened === "object" && typeof opened.path === "string"
|
|
9168
9256
|
? opened.path
|
|
@@ -9171,11 +9259,118 @@ async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult) {
|
|
|
9171
9259
|
// alias/basename to a path, but that reply must never retroactively turn the
|
|
9172
9260
|
// alias into a UUID-refresh authorization. Require the reply to corroborate
|
|
9173
9261
|
// the original exact saved identity before consulting the live active record.
|
|
9262
|
+
const requestedSavedPath = canonicalSavedWorkflowPath(requestedPath);
|
|
9263
|
+
const requestedIsBareAlias = !!requestedSavedPath && !requestedSavedPath.includes("/");
|
|
9174
9264
|
const requestedIdentity = canonicalRequestedSavedIdentity(requestedPath);
|
|
9175
9265
|
const openedIdentity = openedPath
|
|
9176
9266
|
? canonicalSavedRecordIdentity({ path: openedPath, routing_key: parsedOpen?.routing_key })
|
|
9177
9267
|
: null;
|
|
9268
|
+
if (requestedIsBareAlias) {
|
|
9269
|
+
// A bare filename is only a selector. It is not safe to let a native
|
|
9270
|
+
// success through unless the panel both resolved it to a saved path and
|
|
9271
|
+
// gave us a fresh, explicitly confirmed active observation. In particular,
|
|
9272
|
+
// an omitted `opened.path` is not a resolution, and an omitted
|
|
9273
|
+
// `active_confirmed` is not confirmation (#1639).
|
|
9274
|
+
if (!openedPath) {
|
|
9275
|
+
if (legacyReboundRoute)
|
|
9276
|
+
return null;
|
|
9277
|
+
return {
|
|
9278
|
+
drifted: true,
|
|
9279
|
+
unverified: true,
|
|
9280
|
+
activeLabel: "the panel did not return a resolved path for this filename",
|
|
9281
|
+
};
|
|
9282
|
+
}
|
|
9283
|
+
if (!canonicalRequestedSavedIdentity(openedPath)) {
|
|
9284
|
+
return {
|
|
9285
|
+
drifted: true,
|
|
9286
|
+
unverified: true,
|
|
9287
|
+
activeLabel: "the panel did not return a resolved path for this filename",
|
|
9288
|
+
};
|
|
9289
|
+
}
|
|
9290
|
+
let list = null;
|
|
9291
|
+
try {
|
|
9292
|
+
const res = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
9293
|
+
if (!res?.isError)
|
|
9294
|
+
list = parseToolResultJson(res);
|
|
9295
|
+
}
|
|
9296
|
+
catch {
|
|
9297
|
+
list = null;
|
|
9298
|
+
}
|
|
9299
|
+
if (!list || list.active_confirmed !== true) {
|
|
9300
|
+
return {
|
|
9301
|
+
drifted: true,
|
|
9302
|
+
unverified: true,
|
|
9303
|
+
activeLabel: "the panel did not return a freshly confirmed active workflow after resolving this filename",
|
|
9304
|
+
};
|
|
9305
|
+
}
|
|
9306
|
+
const resolvedIdentity = canonicalSavedRecordIdentity({
|
|
9307
|
+
path: openedPath,
|
|
9308
|
+
routing_key: parsedOpen?.routing_key,
|
|
9309
|
+
});
|
|
9310
|
+
const activeIdentity = canonicalSavedRecordIdentity(list.active);
|
|
9311
|
+
if (resolvedIdentity && activeIdentity === resolvedIdentity) {
|
|
9312
|
+
// The re-read proves which resolved path is active, but the caller only
|
|
9313
|
+
// supplied an alias. Preserve #716's no-adoption rule.
|
|
9314
|
+
return null;
|
|
9315
|
+
}
|
|
9316
|
+
if (activeIdentity) {
|
|
9317
|
+
return { drifted: true, activeLabel: describeActiveRecord(list.active) };
|
|
9318
|
+
}
|
|
9319
|
+
return {
|
|
9320
|
+
drifted: true,
|
|
9321
|
+
unverified: true,
|
|
9322
|
+
activeLabel: "the panel returned an unconfirmed active workflow after resolving this filename",
|
|
9323
|
+
};
|
|
9324
|
+
}
|
|
9178
9325
|
if (!requestedIdentity || requestedIdentity !== openedIdentity) {
|
|
9326
|
+
// A bare filename is an alias, not a saved identity. The panel's `opened.path`
|
|
9327
|
+
// is the resolution of that alias; once it is available, corroborate THAT exact
|
|
9328
|
+
// path against a fresh active-list read before allowing the caller to treat the
|
|
9329
|
+
// success as an active-canvas success. Without this branch, the alias exits here
|
|
9330
|
+
// before any live observation and a stale previous tab can be reported as active
|
|
9331
|
+
// and bound (#1639).
|
|
9332
|
+
const resolvedIdentity = openedPath ? canonicalRequestedSavedIdentity(openedPath) : null;
|
|
9333
|
+
if (!requestedIdentity && resolvedIdentity) {
|
|
9334
|
+
let list = null;
|
|
9335
|
+
try {
|
|
9336
|
+
const res = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
9337
|
+
if (!res?.isError)
|
|
9338
|
+
list = parseToolResultJson(res);
|
|
9339
|
+
}
|
|
9340
|
+
catch {
|
|
9341
|
+
list = null;
|
|
9342
|
+
}
|
|
9343
|
+
if (!list) {
|
|
9344
|
+
return {
|
|
9345
|
+
drifted: true,
|
|
9346
|
+
unverified: true,
|
|
9347
|
+
activeLabel: "the panel did not return a confirmed active workflow after resolving this filename",
|
|
9348
|
+
};
|
|
9349
|
+
}
|
|
9350
|
+
if (list.active_confirmed !== true) {
|
|
9351
|
+
return {
|
|
9352
|
+
drifted: true,
|
|
9353
|
+
unverified: true,
|
|
9354
|
+
activeLabel: "the panel returned an unconfirmed active workflow after resolving this filename",
|
|
9355
|
+
};
|
|
9356
|
+
}
|
|
9357
|
+
const activeIdentity = canonicalSavedRecordIdentity(list.active);
|
|
9358
|
+
if (activeIdentity === resolvedIdentity) {
|
|
9359
|
+
// The re-read proves which resolved path is active, but the caller only
|
|
9360
|
+
// supplied an alias. Preserve #716's no-adoption rule: an alias may be
|
|
9361
|
+
// observed for the #1639 wrong-canvas guard, never promoted into a new
|
|
9362
|
+
// command-fence UUID by the reply-resolved path.
|
|
9363
|
+
return null;
|
|
9364
|
+
}
|
|
9365
|
+
if (activeIdentity) {
|
|
9366
|
+
return { drifted: true, activeLabel: describeActiveRecord(list.active) };
|
|
9367
|
+
}
|
|
9368
|
+
return {
|
|
9369
|
+
drifted: true,
|
|
9370
|
+
unverified: true,
|
|
9371
|
+
activeLabel: "the panel returned an unconfirmed active workflow after resolving this filename",
|
|
9372
|
+
};
|
|
9373
|
+
}
|
|
9179
9374
|
// #812 — the SAVED corroboration above can never succeed for an unsaved
|
|
9180
9375
|
// target (there is no path), so try the parallel UNSAVED identity: the
|
|
9181
9376
|
// caller's literal token against the panel's own proven routing_key for
|
|
@@ -9413,6 +9608,11 @@ function noReachableTabFail(cmd, ctx) {
|
|
|
9413
9608
|
`sent. Retry in a moment, or rebind with panel_set_workflow_target({mode:"current"}).`);
|
|
9414
9609
|
}
|
|
9415
9610
|
async function openWorkflowWithVerify(path, ctx) {
|
|
9611
|
+
// #971 — consume the explicit-current proof BEFORE the first await. This
|
|
9612
|
+
// makes it one-shot even when this open fails, and prevents a concurrent or
|
|
9613
|
+
// later open from inheriting a proof belonging to an earlier operation.
|
|
9614
|
+
const legacyRebind = ctx.lastExplicitCurrentRebind;
|
|
9615
|
+
ctx.lastExplicitCurrentRebind = undefined;
|
|
9416
9616
|
// #402: after a full ComfyUI restart the browser tab re-registers a few seconds
|
|
9417
9617
|
// later. Awaiting a stable binding BEFORE dispatching a mutating workflow_open
|
|
9418
9618
|
// (nothing is sent yet — no double-apply risk) means the command reaches a live
|
|
@@ -9476,7 +9676,7 @@ async function openWorkflowWithVerify(path, ctx) {
|
|
|
9476
9676
|
`save-as, or edit expecting ${path}. Call panel_list_workflows to see the current state.`);
|
|
9477
9677
|
}
|
|
9478
9678
|
{
|
|
9479
|
-
const drift = await refreshOpenWorkflowUuid(ctx, path, res);
|
|
9679
|
+
const drift = await refreshOpenWorkflowUuid(ctx, path, res, legacyRebind);
|
|
9480
9680
|
// #887 — the read above is the ONLY observation in this whole path taken
|
|
9481
9681
|
// after a real round trip, so it is the only thing that can catch the active
|
|
9482
9682
|
// pointer having settled elsewhere. It already declined to adopt the uuid;
|
|
@@ -9489,6 +9689,12 @@ async function openWorkflowWithVerify(path, ctx) {
|
|
|
9489
9689
|
// act is typically a write. The session's fence is untouched (nothing was
|
|
9490
9690
|
// adopted), so the tab that IS active keeps its own protection.
|
|
9491
9691
|
if (drift) {
|
|
9692
|
+
if (drift.unverified) {
|
|
9693
|
+
return fail(`workflow_open: ${path} was reported applied, but the panel could not prove that ` +
|
|
9694
|
+
`${drift.activeLabel}. The active canvas is UNKNOWN, so this open is not a ` +
|
|
9695
|
+
`success and this session's workflow identity was NOT re-pointed. Inspect ` +
|
|
9696
|
+
`panel_list_workflows before reading or writing the graph.`);
|
|
9697
|
+
}
|
|
9492
9698
|
return fail(`workflow_open: ${path} was opened, but ${drift.activeLabel} is the ACTIVE workflow now — ` +
|
|
9493
9699
|
`the panel confirmed this on a re-read after the open completed. This session's ` +
|
|
9494
9700
|
`workflow identity was NOT re-pointed at ${path}, so the canvas you would read or ` +
|
|
@@ -9829,6 +10035,11 @@ function canonicalRequestedSavedIdentity(path) {
|
|
|
9829
10035
|
// authorize replacing its existing UUID stamp.
|
|
9830
10036
|
return canonicalPath && canonicalPath.includes("/") ? `wf:${canonicalPath}` : null;
|
|
9831
10037
|
}
|
|
10038
|
+
/** Canonical identity for a bare saved-workflow selector, for one-shot #971 proof matching. */
|
|
10039
|
+
function canonicalBareSavedIdentity(path) {
|
|
10040
|
+
const canonicalPath = canonicalSavedWorkflowPath(path);
|
|
10041
|
+
return canonicalPath && !canonicalPath.includes("/") ? `wf:${canonicalPath}` : null;
|
|
10042
|
+
}
|
|
9832
10043
|
/**
|
|
9833
10044
|
* Canonical `wf:<path>` identity, but only for a complete corroborating record.
|
|
9834
10045
|
* Command-fence replacement is stricter than pin routing: a same-path record
|
|
@@ -17087,6 +17298,10 @@ export function buildPanelToolDefs() {
|
|
|
17087
17298
|
if (mode === "pinned" && !(path ?? "").trim()) {
|
|
17088
17299
|
return fail("Provide path when pinning — use panel_list_workflows to list open workflows.");
|
|
17089
17300
|
}
|
|
17301
|
+
// A prior explicit recovery proof must not survive another targeting
|
|
17302
|
+
// call. It is valid only for the open immediately following the rebind
|
|
17303
|
+
// that produced it.
|
|
17304
|
+
ctx.lastExplicitCurrentRebind = undefined;
|
|
17090
17305
|
// mode:'current' is the explicit, user/agent-initiated "rebind me to the
|
|
17091
17306
|
// tab that's live now" consent signal. Self-heal a session whose captured
|
|
17092
17307
|
// tab id was orphaned (reconnect/reload/workflow-switch) BEFORE writing the
|
|
@@ -17099,8 +17314,10 @@ export function buildPanelToolDefs() {
|
|
|
17099
17314
|
// on a successful turn-pin recovery. Track that separately from the
|
|
17100
17315
|
// real-tab rebind note below.
|
|
17101
17316
|
let currentModeTurnRepinned = false;
|
|
17317
|
+
let explicitCurrentRebindBefore;
|
|
17102
17318
|
if (mode === "current" && ctx.rebindToActiveTab) {
|
|
17103
17319
|
const before = ctx.tabId;
|
|
17320
|
+
explicitCurrentRebindBefore = before;
|
|
17104
17321
|
const recoveringScope = isScopeAddress(before);
|
|
17105
17322
|
// Hold the send() wait BEFORE the first await so a same-batch sibling
|
|
17106
17323
|
// that already hit the null pin waits instead of minting #884.
|
|
@@ -17360,6 +17577,21 @@ export function buildPanelToolDefs() {
|
|
|
17360
17577
|
rebindNote += ` The panel dropped mid-call: this session moved from tab ${tabBeforeProbe} onto tab ${ctx.tabId}, and mode:"current" was applied there too.`;
|
|
17361
17578
|
}
|
|
17362
17579
|
}
|
|
17580
|
+
// #971 — preserve the old-panel recovery only when the current-mode
|
|
17581
|
+
// operation itself corroborated the saved identity now selected. The
|
|
17582
|
+
// proof is consumed by the next open before its first await, so a failed
|
|
17583
|
+
// open, a different alias, or a later open cannot inherit it.
|
|
17584
|
+
if (mode === "current" &&
|
|
17585
|
+
!deferredBind &&
|
|
17586
|
+
explicitCurrentRebindBefore !== undefined &&
|
|
17587
|
+
ctx.tabId !== explicitCurrentRebindBefore &&
|
|
17588
|
+
fenceRebind &&
|
|
17589
|
+
"active" in fenceRebind) {
|
|
17590
|
+
const savedIdentity = canonicalSavedRecordIdentity(fenceRebind.active);
|
|
17591
|
+
if (savedIdentity) {
|
|
17592
|
+
ctx.lastExplicitCurrentRebind = { tabId: ctx.tabId, savedIdentity };
|
|
17593
|
+
}
|
|
17594
|
+
}
|
|
17363
17595
|
// panel#1529 — "Graph tools will target that workflow" is a CLAIM ABOUT
|
|
17364
17596
|
// ROUTING, and this call only ever wrote the workflow-target store. It is
|
|
17365
17597
|
// true when the panel confirmed the target IS the live canvas, because a
|
|
@@ -19535,7 +19767,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
19535
19767
|
// and refuse to report freed:true when a device is still pinned.
|
|
19536
19768
|
return annotateFreeVramAck(ctx, res);
|
|
19537
19769
|
}),
|
|
19538
|
-
def("panel_show_media", "Display one or more images, videos or AUDIO files directly in the panel chat. Use this whenever the user asks to SEE, SHOW, PLAY or HEAR a file — a disk path you composited/downloaded/generated (absolute path on the orchestrator host) OR a ComfyUI output ref ({ filename, subfolder?, type? }). Items are rendered as media cards in the agent chat area — audio gets a real player (.wav/.mp3/.flac/.ogg/.oga/.opus/.m4a/.aac), so a generated TTS or voice take is played back with this tool, not described; supply optional captions. You are never sent the audio yourself and cannot hear it. Max 8 items per call. A path item OVER the 20 MB inline cap that is not under any directory ComfyUI serves can still be shown by passing stage:true on that item — the orchestrator COPIES it into <output>/_panel_staged (an opt-in, persistent disk write; 512 MB per-file and 2 GB total caps) and displays the copy by reference. NEVER describe an image with emoji or text placeholders — call this tool instead.", {
|
|
19770
|
+
def("panel_show_media", "Display one or more images, videos or AUDIO files directly in the panel chat. Use this whenever the user asks to SEE, SHOW, PLAY or HEAR a file — a disk path you composited/downloaded/generated (absolute path on the orchestrator host) OR a ComfyUI output ref ({ filename, subfolder?, type? }). Items are rendered as media cards in the agent chat area — audio gets a real player (.wav/.mp3/.flac/.ogg/.oga/.opus/.m4a/.aac), so a generated TTS or voice take is played back with this tool, not described; supply optional captions. You are never sent the audio yourself and cannot hear it. Max 8 items per call. A path item OVER the 20 MB inline cap that is not under any directory ComfyUI serves can still be shown by passing stage:true on that item — the orchestrator COPIES it into <output>/_panel_staged (an opt-in, persistent disk write; 512 MB per-file and 2 GB total caps) and displays the copy by reference. Staging needs a LOCAL ComfyUI: it is a filesystem copy into ComfyUI's own output directory, so it cannot run against a remote target (--comfyui-url on another host) and the call reports that instead of staging. NEVER describe an image with emoji or text placeholders — call this tool instead.", {
|
|
19539
19771
|
items: z
|
|
19540
19772
|
.array(z.object({
|
|
19541
19773
|
// #1968 — OPTIONAL in the shape, required by normalizeShowMediaItem
|