comfyui-mcp 0.51.29 → 0.51.30
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.
|
@@ -65,7 +65,7 @@ function journalTabFor(ctx) {
|
|
|
65
65
|
function journalConversationFor(ctx) {
|
|
66
66
|
return conversationOfScopeAddress(ctx.tabId);
|
|
67
67
|
}
|
|
68
|
-
import { dispatchOutcomeOf, isCapabilityRefusal, isPanelCmdUnsupportedError, isReplyTimeoutTagged, isRoutingAmbiguity, } from "../services/ui-bridge.js";
|
|
68
|
+
import { BRIDGE_DEFAULT_TIMEOUT_MS, dispatchOutcomeOf, isCapabilityRefusal, isPanelCmdUnsupportedError, isReplyTimeoutTagged, isRoutingAmbiguity, } from "../services/ui-bridge.js";
|
|
69
69
|
import { withWorkflowTarget, } from "../services/workflow-target-store.js";
|
|
70
70
|
import { addUserMcpServer, readUserMcpServers, removeUserMcpServer, setUserMcpServerSecret, } from "../services/user-mcp-config.js";
|
|
71
71
|
import { setComfyuiSecret, setAgentSecret, isAllowedAgentSecretKey, receiptDisclosures, shadowedNote, storeDamageNote, } from "../services/panel-secrets.js";
|
|
@@ -1609,6 +1609,135 @@ function isAckTimeout(res) {
|
|
|
1609
1609
|
// prefixed acked error reach the receipt-recovery path.
|
|
1610
1610
|
return /^(?:Error: )?Panel tab .+? did not reply to "workflow_open" within \d+\s*ms/i.test(text);
|
|
1611
1611
|
}
|
|
1612
|
+
/**
|
|
1613
|
+
* #1468 — carry the bridge's AUTHORITATIVE reply-timeout marker across the
|
|
1614
|
+
* error → ToolResult conversion in `ctx.call`.
|
|
1615
|
+
*
|
|
1616
|
+
* Two codex rounds rejected deciding this from message TEXT, and both were right.
|
|
1617
|
+
* `isAckTimeout`'s looseness is safe for `workflow_open` because a match there
|
|
1618
|
+
* only opens a door — the panel's #514 receipt, correlated to the request's exact
|
|
1619
|
+
* rid, is what actually decides. A caller with no receipt has the predicate doing
|
|
1620
|
+
* all the work: match plus one root-looking read promotes an error to success. No
|
|
1621
|
+
* amount of regex tightening fixes that, because ACKED panel errors arrive as
|
|
1622
|
+
* ARBITRARY `msg.error` text (ui-bridge) and `ctx.call` flattens both kinds into
|
|
1623
|
+
* the same text-only result — so any sentence the bridge can write, a panel error
|
|
1624
|
+
* can also contain.
|
|
1625
|
+
*
|
|
1626
|
+
* `markReplyTimeout`/`isReplyTimeoutTagged` already answer the question exactly,
|
|
1627
|
+
* on the error object, at the only place that KNOWS: the bridge. The information
|
|
1628
|
+
* was simply being dropped in translation. This preserves it as a non-enumerable
|
|
1629
|
+
* symbol so the result's JSON payload is byte-identical and nothing downstream
|
|
1630
|
+
* can observe it by accident.
|
|
1631
|
+
*/
|
|
1632
|
+
const REPLY_TIMEOUT_RESULT = Symbol("panel.replyTimeoutResult");
|
|
1633
|
+
function carryReplyTimeoutMark(err, res) {
|
|
1634
|
+
if (!isReplyTimeoutTagged(err))
|
|
1635
|
+
return res;
|
|
1636
|
+
Object.defineProperty(res, REPLY_TIMEOUT_RESULT, {
|
|
1637
|
+
value: true,
|
|
1638
|
+
enumerable: false,
|
|
1639
|
+
configurable: true,
|
|
1640
|
+
});
|
|
1641
|
+
return res;
|
|
1642
|
+
}
|
|
1643
|
+
/** True only for a ToolResult that `ctx.call` produced from a BRIDGE-TAGGED
|
|
1644
|
+
* reply timeout — the tab was reached and never answered. An acked executor
|
|
1645
|
+
* error can never carry this, whatever its text says. */
|
|
1646
|
+
function isReplyTimeoutResult(res) {
|
|
1647
|
+
return res?.isError === true && res[REPLY_TIMEOUT_RESULT] === true;
|
|
1648
|
+
}
|
|
1649
|
+
// ---- panel_exit_subgraph settle-after-ack-timeout (#1468) ------------------
|
|
1650
|
+
// `graph_exit_subgraph` timed out at 15 s while a following `panel_graph_outline`
|
|
1651
|
+
// proved the view HAD returned to root — an applied navigation reported as a
|
|
1652
|
+
// failed one. The bound is not the cause: the panel's own receipt
|
|
1653
|
+
// (`confirmCanvasNavigation`) budgets 25 polls × 40 ms ≈ 1 s and returns early on
|
|
1654
|
+
// success, so 15 s already clears its worst case by 15×. What fails is the ANSWER
|
|
1655
|
+
// getting back from a busy tab. The effect, though, is locally observable — so on
|
|
1656
|
+
// a no-reply we ask, exactly as #1473 does for the fence, instead of handing the
|
|
1657
|
+
// caller "may have been applied" and the homework.
|
|
1658
|
+
/** The one decisive observation: the canvas is at the root graph. */
|
|
1659
|
+
function exitConfirmedAtRootNote() {
|
|
1660
|
+
return (`CHECKED FOR YOU: the tab did not ACKNOWLEDGE the exit within the window, but a graph read ` +
|
|
1661
|
+
`taken immediately afterwards, on that same tab, reports the canvas at the ROOT graph — the ` +
|
|
1662
|
+
`state this tool exists to reach. No recovery step is needed and a retry would be wasted ` +
|
|
1663
|
+
`work. A missing acknowledgement is not evidence the navigation failed; here it is evidence ` +
|
|
1664
|
+
`the tab was too busy to answer in time. Stated precisely: what is established is WHERE THE ` +
|
|
1665
|
+
`CANVAS IS, not that this command is what put it there — someone navigating out on the ` +
|
|
1666
|
+
`canvas while the tab was unresponsive would read identically. Both leave you where you ` +
|
|
1667
|
+
`asked to be, so the distinction changes nothing you would do next; it is drawn because only ` +
|
|
1668
|
+
`one of the two was actually observed.`);
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* The NON-decisive observation, stated as non-decisive. `graph_exit_subgraph`
|
|
1672
|
+
* pops to the IMMEDIATE PARENT, not to root (#412) — so "still inside a subgraph"
|
|
1673
|
+
* is equally consistent with the exit never landing and with the exit landing in
|
|
1674
|
+
* the parent of a NESTED subgraph. Recommending a retry on this reading would pop
|
|
1675
|
+
* a level the caller wanted to keep, which is the same class of harm as the false
|
|
1676
|
+
* failure being fixed, one step removed.
|
|
1677
|
+
*/
|
|
1678
|
+
function exitInconclusiveInSubgraphNote(title) {
|
|
1679
|
+
const where = title ? `subgraph “${title}”` : "a subgraph";
|
|
1680
|
+
return (`CHECKED FOR YOU — and the check does NOT settle it. A graph read taken immediately after the ` +
|
|
1681
|
+
`missing acknowledgement reports the canvas inside ${where}. panel_exit_subgraph pops to the ` +
|
|
1682
|
+
`IMMEDIATE PARENT, not to the root graph, so this single observation cannot separate two ` +
|
|
1683
|
+
`cases: the exit never landed and you are where you started, OR the exit DID land and this is ` +
|
|
1684
|
+
`the parent you popped into from a nested subgraph. Settle it with a scope read ` +
|
|
1685
|
+
`(panel_graph_outline, or the canvas breadcrumb) before acting. If you do re-issue, use the ` +
|
|
1686
|
+
`retry_of token above rather than a bare repeat: a token names the original mutation and is ` +
|
|
1687
|
+
`answered from its ledger entry WITHOUT running the executor again (#694), whereas identical ` +
|
|
1688
|
+
`args with no token execute fresh — and from the second case that pops another level you may ` +
|
|
1689
|
+
`have wanted to keep.`);
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* After an ack timeout on `graph_exit_subgraph`, take ONE scope read and report
|
|
1693
|
+
* what it found. Returns the untouched timeout when the read cannot answer —
|
|
1694
|
+
* #1473's rule: an unknown answer claims nothing in either direction, and reading
|
|
1695
|
+
* every probe failure as proof would invent a verdict out of a backgrounded tab.
|
|
1696
|
+
*/
|
|
1697
|
+
async function settleExitSubgraphAfterAckTimeout(ctx, timedOut) {
|
|
1698
|
+
// The observation is only evidence about the tab the navigation was DISPATCHED
|
|
1699
|
+
// to (codex P1). `ctx.call` runs `ensureReachable` first, which silently rebinds
|
|
1700
|
+
// an unpinned current-mode session onto the sole remaining interactive tab when
|
|
1701
|
+
// the bound one has gone — the exact situation an unanswered command makes
|
|
1702
|
+
// likely. Without this the probe could read a DIFFERENT tab's canvas, and that
|
|
1703
|
+
// tab sitting at root would be reported as this navigation having landed: a
|
|
1704
|
+
// wrong-target success, which is worse than the false failure being fixed.
|
|
1705
|
+
const dispatchTab = ctx.tabId;
|
|
1706
|
+
// `fields:"ids", limit:1` is the cheapest shape that still carries `viewing` —
|
|
1707
|
+
// the panel builds that field unconditionally on every graph_query return path.
|
|
1708
|
+
const probe = await ctx.call({ cmd: "graph_query", fields: "ids", limit: 1 }, 8000);
|
|
1709
|
+
// Checked AFTER the call, because the rebind happens inside it. A moved binding
|
|
1710
|
+
// makes the reading inconclusive, not false — so it takes the same "claim
|
|
1711
|
+
// nothing" exit as an unanswerable probe.
|
|
1712
|
+
if (ctx.tabId !== dispatchTab)
|
|
1713
|
+
return timedOut;
|
|
1714
|
+
const viewing = parseToolResultJson(probe)?.viewing;
|
|
1715
|
+
const scope = typeof viewing?.scope === "string" ? viewing.scope : null;
|
|
1716
|
+
if (scope === "root") {
|
|
1717
|
+
// `at_root`, NOT `exited`. The read proves WHERE THE CANVAS IS; it cannot
|
|
1718
|
+
// prove this command is what put it there (the user may have navigated out on
|
|
1719
|
+
// the canvas while the tab was too busy to answer us). Those happen to be the
|
|
1720
|
+
// same actionable answer — the caller's goal state holds either way — but only
|
|
1721
|
+
// one of them is something the observation actually establishes, and naming
|
|
1722
|
+
// the stronger claim would be this issue's own defect pointed the other way.
|
|
1723
|
+
return ok({
|
|
1724
|
+
viewing,
|
|
1725
|
+
at_root: true,
|
|
1726
|
+
acknowledged: false,
|
|
1727
|
+
confirmed_by: "graph read after ack timeout",
|
|
1728
|
+
note: exitConfirmedAtRootNote(),
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
if (scope === "subgraph") {
|
|
1732
|
+
const title = typeof viewing?.title === "string" && viewing.title ? viewing.title : null;
|
|
1733
|
+
const text = timedOut.content?.find((c) => c.type === "text")?.text ?? "";
|
|
1734
|
+
return {
|
|
1735
|
+
...timedOut,
|
|
1736
|
+
content: [{ type: "text", text: `${text}\n\n${exitInconclusiveInSubgraphNote(title)}` }],
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
return timedOut;
|
|
1740
|
+
}
|
|
1612
1741
|
/** Parse a ctx.call ToolResult's text payload as JSON, or null if not parseable. */
|
|
1613
1742
|
function parseToolResultJson(res) {
|
|
1614
1743
|
if (!res || res.isError)
|
|
@@ -5422,7 +5551,13 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
5422
5551
|
`restart/reload. Wait a moment and retry; if it persists, rebind with ` +
|
|
5423
5552
|
`panel_set_workflow_target({mode:"current"}). (${err2 instanceof Error ? err2.message : String(err2)})`);
|
|
5424
5553
|
}
|
|
5425
|
-
|
|
5554
|
+
// #1468 — the RETRY's own failure can be a reply timeout too: this branch
|
|
5555
|
+
// is only entered when the FIRST error was a reconnect flap or a switch
|
|
5556
|
+
// refusal, so a tagged no-reply lands here rather than on the outer path.
|
|
5557
|
+
// Leaving it unmarked fails closed (nothing is settled, no false success)
|
|
5558
|
+
// but silently switches the settle off for a real sequence, which is the
|
|
5559
|
+
// kind of gap that reads as "the fix does not work" much later.
|
|
5560
|
+
return carryReplyTimeoutMark(err2, fail(err2));
|
|
5426
5561
|
}
|
|
5427
5562
|
}
|
|
5428
5563
|
// #442 defect 4: a MUTATING command (deliberately excluded from RETRY_SAFE_CMDS)
|
|
@@ -5654,9 +5789,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
5654
5789
|
RETRY_TOKEN_CMDS.has(typeof cmd.cmd === "string" ? cmd.cmd : "") &&
|
|
5655
5790
|
(dispatchOutcomeOf(err) === true || isReplyTimeoutTagged(err))) {
|
|
5656
5791
|
const cause = err instanceof Error ? err.message : String(err);
|
|
5657
|
-
return fail(`${cause}\n\nTo retry this exact mutation, re-issue identical args plus retry_of:"${dispatchedRid}"; otherwise call normally.`);
|
|
5792
|
+
return carryReplyTimeoutMark(err, fail(`${cause}\n\nTo retry this exact mutation, re-issue identical args plus retry_of:"${dispatchedRid}"; otherwise call normally.`));
|
|
5658
5793
|
}
|
|
5659
|
-
return fail(err);
|
|
5794
|
+
return carryReplyTimeoutMark(err, fail(err));
|
|
5660
5795
|
}
|
|
5661
5796
|
};
|
|
5662
5797
|
// Human-in-the-loop confirmation for a DESTRUCTIVE op: render a yes/no card in
|
|
@@ -8740,7 +8875,27 @@ export function buildPanelToolDefs() {
|
|
|
8740
8875
|
// other direct-bridge call site) — without this an orphaned session
|
|
8741
8876
|
// wrongly returns "no connected tab" even when a live tab exists (#381).
|
|
8742
8877
|
ctx.ensureReachable?.();
|
|
8743
|
-
|
|
8878
|
+
// #1468 — was a hardcoded 10_000, HALF this codebase's own default, and
|
|
8879
|
+
// the tight bound is the entire bug: `civitai_search` kept timing out
|
|
8880
|
+
// while the search demonstrably applied (renderRev advanced,
|
|
8881
|
+
// civitai_results reported loading:true), so an applied mutation was
|
|
8882
|
+
// reported to the caller as a failure.
|
|
8883
|
+
//
|
|
8884
|
+
// The tight bound was there to fence a wait on CivitAI's HTTP. That wait
|
|
8885
|
+
// is GONE: panel #282 made `driveSearch` resolve on DISPATCH — it fires
|
|
8886
|
+
// `void reload(...)` and returns `{dispatched:true, renderRev}` with no
|
|
8887
|
+
// await anywhere in the handler. That shipped at panel 0.11.0; #1468 was
|
|
8888
|
+
// filed from 0.11.44, so the reporter already had it. Measured, not
|
|
8889
|
+
// assumed — my own first reading of this issue asserted the coupling was
|
|
8890
|
+
// still there and argued AGAINST raising the bound on that basis.
|
|
8891
|
+
//
|
|
8892
|
+
// With nothing external left to wait on, what remains between the panel's
|
|
8893
|
+
// return and the caller's error is only whether the reply gets back in
|
|
8894
|
+
// time — the #357/#694 shape, a busy-but-alive main thread missing a
|
|
8895
|
+
// tighter-than-default bound and succeeding moments later. So this takes
|
|
8896
|
+
// the shared default like every other panel command, rather than keeping
|
|
8897
|
+
// a fence around a wait that no longer exists.
|
|
8898
|
+
const reply = await ctx.bridge.send({ cmd: "civitai_search", query, filters: args.filters, browsingLevels }, { tabId: ctx.tabId, timeoutMs: BRIDGE_DEFAULT_TIMEOUT_MS });
|
|
8744
8899
|
// Do NOT let a supplied-but-unapplied creator filter masquerade as a
|
|
8745
8900
|
// legitimate empty result: if the panel echoes back a different (or null)
|
|
8746
8901
|
// creator, surface an explicit warning so the caller can tell "filter
|
|
@@ -9507,7 +9662,18 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
9507
9662
|
}
|
|
9508
9663
|
}),
|
|
9509
9664
|
def("panel_enter_subgraph", "Navigate INTO a subgraph node so you can read and EDIT its inner nodes — after this, panel_query_graph / panel_graph_outline and all panel_* edit tools target the subgraph's inner graph (the user sees the canvas drill in). This is how you edit inside a subgraph (e.g. tweak a widget on an inner node). Call panel_exit_subgraph when done. Returns the new viewing scope.", { node_id: nodeId().describe("Subgraph node id (is_subgraph=true).") }, async (args, ctx) => ctx.call({ cmd: "graph_enter_subgraph", node_id: args.node_id }, 15000)),
|
|
9510
|
-
def("panel_exit_subgraph", "Leave the current subgraph and return to the root graph (undo a panel_enter_subgraph). After this, panel_* tools target the root graph again.", {}, async (_args, ctx) =>
|
|
9665
|
+
def("panel_exit_subgraph", "Leave the current subgraph and return to the root graph (undo a panel_enter_subgraph). After this, panel_* tools target the root graph again.", {}, async (_args, ctx) => {
|
|
9666
|
+
const res = await ctx.call({ cmd: "graph_exit_subgraph" }, 15000);
|
|
9667
|
+
// #1468 — ONLY a no-reply is settled by a read. A genuine executor error
|
|
9668
|
+
// (the panel's own "could not confirm … no observation ever saw the canvas
|
|
9669
|
+
// there") is an ACKED reply the bridge received and relayed; it already
|
|
9670
|
+
// reasoned about this exact uncertainty and prescribes its own next step,
|
|
9671
|
+
// so re-deciding it from out here would overwrite a better-informed verdict
|
|
9672
|
+
// with a worse-informed one.
|
|
9673
|
+
if (!isReplyTimeoutResult(res))
|
|
9674
|
+
return res;
|
|
9675
|
+
return settleExitSubgraphAfterAckTimeout(ctx, res);
|
|
9676
|
+
}),
|
|
9511
9677
|
def("panel_move_rail", "Reposition a subgraph's input or output RAIL (the boundary I/O node that the inner wires connect to). You MUST be INSIDE the subgraph first (panel_enter_subgraph). Read current rail positions from panel_query_graph's `rails` field (present when viewing a subgraph). Use this to place the input rail just left of the first node column and the output rail just right of the last one, so a tidy interior layout doesn't leave the rails stranded. rail is 'input' or 'output'.", {
|
|
9512
9678
|
rail: z.enum(["input", "output"]).describe("Which boundary rail to move."),
|
|
9513
9679
|
pos: xy().describe("New top-left [x, y] (two numbers)."),
|