comfyui-mcp 0.51.28 → 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)
|
|
@@ -3726,6 +3855,7 @@ panelGapNote = "") {
|
|
|
3726
3855
|
if (fenceStillMatches) {
|
|
3727
3856
|
return {
|
|
3728
3857
|
binding: "not_recovered",
|
|
3858
|
+
settleByRead: true,
|
|
3729
3859
|
note: `${lead}. This session's fence was NOT re-derived, and whether that reply describes ` +
|
|
3730
3860
|
`the LIVE canvas is exactly what could not be confirmed — a stale or background ` +
|
|
3731
3861
|
`record can carry the right uuid and still not be the canvas in front of the user, ` +
|
|
@@ -5421,7 +5551,13 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
5421
5551
|
`restart/reload. Wait a moment and retry; if it persists, rebind with ` +
|
|
5422
5552
|
`panel_set_workflow_target({mode:"current"}). (${err2 instanceof Error ? err2.message : String(err2)})`);
|
|
5423
5553
|
}
|
|
5424
|
-
|
|
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));
|
|
5425
5561
|
}
|
|
5426
5562
|
}
|
|
5427
5563
|
// #442 defect 4: a MUTATING command (deliberately excluded from RETRY_SAFE_CMDS)
|
|
@@ -5653,9 +5789,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
5653
5789
|
RETRY_TOKEN_CMDS.has(typeof cmd.cmd === "string" ? cmd.cmd : "") &&
|
|
5654
5790
|
(dispatchOutcomeOf(err) === true || isReplyTimeoutTagged(err))) {
|
|
5655
5791
|
const cause = err instanceof Error ? err.message : String(err);
|
|
5656
|
-
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.`));
|
|
5657
5793
|
}
|
|
5658
|
-
return fail(err);
|
|
5794
|
+
return carryReplyTimeoutMark(err, fail(err));
|
|
5659
5795
|
}
|
|
5660
5796
|
};
|
|
5661
5797
|
// Human-in-the-loop confirmation for a DESTRUCTIVE op: render a yes/no card in
|
|
@@ -8739,7 +8875,27 @@ export function buildPanelToolDefs() {
|
|
|
8739
8875
|
// other direct-bridge call site) — without this an orphaned session
|
|
8740
8876
|
// wrongly returns "no connected tab" even when a live tab exists (#381).
|
|
8741
8877
|
ctx.ensureReachable?.();
|
|
8742
|
-
|
|
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 });
|
|
8743
8899
|
// Do NOT let a supplied-but-unapplied creator filter masquerade as a
|
|
8744
8900
|
// legitimate empty result: if the panel echoes back a different (or null)
|
|
8745
8901
|
// creator, surface an explicit warning so the caller can tell "filter
|
|
@@ -9197,10 +9353,95 @@ export function buildPanelToolDefs() {
|
|
|
9197
9353
|
const fence = fenceRebind
|
|
9198
9354
|
? describeFenceRebind(fenceRebind, canMutateNow, refusalCause)
|
|
9199
9355
|
: undefined;
|
|
9356
|
+
// #1473 — TAKE THE ADVICE THIS MESSAGE GIVES, instead of assigning it as homework.
|
|
9357
|
+
//
|
|
9358
|
+
// The reporter restarted ComfyUI, called this, was told the binding was NOT
|
|
9359
|
+
// restored — and their very next `panel_graph_outline` succeeded with the expected
|
|
9360
|
+
// graph. #1401 already made the message right for this shape: when the panel's
|
|
9361
|
+
// reported identity MATCHES the fence this session already holds, it says so, says
|
|
9362
|
+
// it is NOT a claim that graph tools work, and prescribes a cheap graph read to
|
|
9363
|
+
// settle it. All true. It just left the caller to run that read themselves.
|
|
9364
|
+
//
|
|
9365
|
+
// So the read is taken here, on the matching-uuid path ONLY, and its ANSWER is put
|
|
9366
|
+
// in the message.
|
|
9367
|
+
//
|
|
9368
|
+
// THE RESULT STILL REPORTS FAILURE, deliberately, and that is #1401's call not
|
|
9369
|
+
// mine: "softening the DIAGNOSIS must not soften the RESULT — the rebind genuinely
|
|
9370
|
+
// did not happen". It did not. What changes is that the caller no longer has to
|
|
9371
|
+
// discover, one call later, that nothing was wrong: the answer arrives with the
|
|
9372
|
+
// refusal instead of after it. Whether this should become a SUCCESS is a real
|
|
9373
|
+
// product question and a reversal of a reasoned decision, so it is raised on the
|
|
9374
|
+
// issue rather than decided here.
|
|
9375
|
+
let settledNote = "";
|
|
9376
|
+
if (fence && fence.binding === "not_recovered" && fence.settleByRead) {
|
|
9377
|
+
// WHAT THE PROBE PROVES, AND ONLY THAT (codex).
|
|
9378
|
+
//
|
|
9379
|
+
// Two ways to overclaim here, and the first version did both:
|
|
9380
|
+
//
|
|
9381
|
+
// • EVERY error read as "refused". `ctx.call` turns a transport failure into an
|
|
9382
|
+
// error result too, so a `graph_query` that merely TIMED OUT on a backgrounded
|
|
9383
|
+
// tab would have been reported as a confirmed fence refusal — inventing a wedge
|
|
9384
|
+
// out of a slow tab, and contradicting this block's own "unknown says nothing"
|
|
9385
|
+
// rule. Only an actual instance-mismatch refusal proves the fence rejected it.
|
|
9386
|
+
// • A passing READ read as "graph tools work". The write fence is a SEPARATE
|
|
9387
|
+
// capability: a panel can serve reads while refusing every mutation, and the
|
|
9388
|
+
// capability probe above already knows. So the claim is scoped to reads, and the
|
|
9389
|
+
// known-negative write case is stated rather than papered over.
|
|
9390
|
+
let probeRefused;
|
|
9391
|
+
let probeOk = false;
|
|
9392
|
+
try {
|
|
9393
|
+
// The cheapest fenced read there is: ids only, one row. It is refused by the
|
|
9394
|
+
// same instance fence every graph command carries, which is exactly the
|
|
9395
|
+
// question — a full outline would answer it no better and would cost the caller
|
|
9396
|
+
// a page of graph on a recovery path.
|
|
9397
|
+
const probe = await ctx.call({ cmd: "graph_query", fields: "ids", limit: 1 }, 8000);
|
|
9398
|
+
if (!probe.isError)
|
|
9399
|
+
probeOk = true;
|
|
9400
|
+
else
|
|
9401
|
+
probeRefused = isWorkflowInstanceMismatch(toolResultText(probe));
|
|
9402
|
+
}
|
|
9403
|
+
catch (err) {
|
|
9404
|
+
probeRefused = isWorkflowInstanceMismatch(err);
|
|
9405
|
+
}
|
|
9406
|
+
if (probeOk) {
|
|
9407
|
+
settledNote =
|
|
9408
|
+
// ONE OBSERVATION, STATED AS ONE (codex r2). A passing graph_query proves that
|
|
9409
|
+
// read passed this fence just now — not that every command will, and not that
|
|
9410
|
+
// the cause "was" a race. The earlier wording generalised to "READS work" and
|
|
9411
|
+
// asserted the cause outright, which is the same overclaim this reply exists to
|
|
9412
|
+
// stop making.
|
|
9413
|
+
`
|
|
9414
|
+
|
|
9415
|
+
CHECKED FOR YOU: the graph read this message prescribes was just run — a ` +
|
|
9416
|
+
`graph_query against this fence — and it SUCCEEDED. So the fence did not reject ` +
|
|
9417
|
+
`THAT command a moment ago, which is the single fact this settles: it is evidence ` +
|
|
9418
|
+
`of a reconciliation race after the reconnect rather than a broken binding, not a ` +
|
|
9419
|
+
`guarantee about the next command.` +
|
|
9420
|
+
(canMutateNow === false
|
|
9421
|
+
? ` MUTATIONS are a separate matter and remain refused on this tab — that is the ` +
|
|
9422
|
+
`write-fence capability above, not the binding, and re-running this will not ` +
|
|
9423
|
+
`change it.`
|
|
9424
|
+
: ` Nothing suggests a recovery step is needed for the binding; the next graph ` +
|
|
9425
|
+
`command is still the authority.`) +
|
|
9426
|
+
` (The rebind itself still did not happen, which is why this is reported as a ` +
|
|
9427
|
+
`failure.)`;
|
|
9428
|
+
}
|
|
9429
|
+
else if (probeRefused === true) {
|
|
9430
|
+
settledNote =
|
|
9431
|
+
`
|
|
9432
|
+
|
|
9433
|
+
CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
9434
|
+
`was REFUSED by the instance fence — so the reply was stale after all and graph ` +
|
|
9435
|
+
`commands really are being rejected. The remedy above applies.`;
|
|
9436
|
+
}
|
|
9437
|
+
// Anything else — a timeout, a dropped tab, an error that is not a fence refusal —
|
|
9438
|
+
// settles NOTHING and says nothing. An inconclusive probe must not become evidence
|
|
9439
|
+
// in either direction.
|
|
9440
|
+
}
|
|
9200
9441
|
if (fence && fence.binding === "not_recovered") {
|
|
9201
9442
|
return fail(`panel_set_workflow_target({mode:"current"}) did NOT restore this session's graph ` +
|
|
9202
9443
|
`binding.\n\nAPPLIED (do not repeat this part): the workflow target is now ` +
|
|
9203
|
-
`mode:"current"${rebindNote ? `.${rebindNote}` : "."}\n\nNOT APPLIED:${fence.note}`);
|
|
9444
|
+
`mode:"current"${rebindNote ? `.${rebindNote}` : "."}\n\nNOT APPLIED:${fence.note}${settledNote}`);
|
|
9204
9445
|
}
|
|
9205
9446
|
// #888 — SAY what the scope repin did. A silent success is as unhelpful
|
|
9206
9447
|
// here as the silent refusal #1077 fixed: the whole complaint is that
|
|
@@ -9421,7 +9662,18 @@ export function buildPanelToolDefs() {
|
|
|
9421
9662
|
}
|
|
9422
9663
|
}),
|
|
9423
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)),
|
|
9424
|
-
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
|
+
}),
|
|
9425
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'.", {
|
|
9426
9678
|
rail: z.enum(["input", "output"]).describe("Which boundary rail to move."),
|
|
9427
9679
|
pos: xy().describe("New top-left [x, y] (two numbers)."),
|