comfyui-mcp 0.52.156 → 0.52.157
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/panel-tools.js +258 -60
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/ui-bridge.js +168 -22
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/services/update-comfyui.js +128 -1
- package/dist/services/update-comfyui.js.map +1 -1
- package/dist/services/workflow-converter.js +7 -3
- package/dist/services/workflow-converter.js.map +1 -1
- package/package.json +1 -1
- package/packs/krea2-identity-edit/manifest.yaml +1 -1
- package/packs/krea2-identity-edit/pack.yaml +1 -1
- package/packs/krea2-identity-edit/workflow.json +1 -1
|
@@ -184,8 +184,8 @@ function journalConversationFor(ctx) {
|
|
|
184
184
|
return conversationOfScopeAddress(ctx.tabId);
|
|
185
185
|
}
|
|
186
186
|
import { BRIDGE_DEFAULT_TIMEOUT_MS, BRIDGE_READ_DEFAULT_TIMEOUT_MS, dispatchOutcomeOf, GRAPH_CMD_EFFECT, isCapabilityRefusal, isPanelCmdUnsupportedError, isMidCommandDisconnectTagged, isReplyTimeoutTagged, isRoutingAmbiguity, requiresWorkflowStampEnforcement, } from "../services/ui-bridge.js";
|
|
187
|
-
import { PANEL_TOOL_BY_GRAPH_CMD, QUEUE_BUSY_READ_TOOLS, RETRY_TOKEN_CMD_BY_TOOL, panelToolForGraphCmd, } from "../services/panel-graph-cmd-tools.js";
|
|
188
|
-
export { PANEL_TOOL_BY_GRAPH_CMD, QUEUE_BUSY_READ_TOOLS, RETRY_TOKEN_CMD_BY_TOOL, panelToolForGraphCmd, };
|
|
187
|
+
import { GRAPH_READ_CMD_BY_TOOL, PANEL_TOOL_BY_GRAPH_CMD, QUEUE_BUSY_READ_TOOLS, RETRY_TOKEN_CMD_BY_TOOL, panelToolForGraphCmd, } from "../services/panel-graph-cmd-tools.js";
|
|
188
|
+
export { GRAPH_READ_CMD_BY_TOOL, PANEL_TOOL_BY_GRAPH_CMD, QUEUE_BUSY_READ_TOOLS, RETRY_TOKEN_CMD_BY_TOOL, panelToolForGraphCmd, };
|
|
189
189
|
import { withWorkflowTarget, } from "../services/workflow-target-store.js";
|
|
190
190
|
import { addUserMcpServer, backendInheritsUserMcpServers, readUserMcpServers, removeUserMcpServer, setUserMcpServerSecret, } from "../services/user-mcp-config.js";
|
|
191
191
|
import { setComfyuiSecret, setAgentSecret, isAllowedAgentSecretKey, receiptDisclosures, shadowedNote, storeDamageNote, } from "../services/panel-secrets.js";
|
|
@@ -853,6 +853,10 @@ export const __panelToolsTestHooks = {
|
|
|
853
853
|
setRunLateAckGraceMs(ms) {
|
|
854
854
|
runLateAckGraceMsOverride = ms;
|
|
855
855
|
},
|
|
856
|
+
/** Shrink the #2527 graph-read wait for an outstanding timed-out mutation. */
|
|
857
|
+
setOutstandingMutationReadSettleMs(ms) {
|
|
858
|
+
outstandingMutationReadSettleMsOverride = ms;
|
|
859
|
+
},
|
|
856
860
|
/** Mark a synthetic graph_run result as the bridge's authoritative reply timeout. */
|
|
857
861
|
markRunReplyTimeout(res) {
|
|
858
862
|
Object.defineProperty(res, REPLY_TIMEOUT_RESULT, {
|
|
@@ -949,6 +953,65 @@ const OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS = 90_000;
|
|
|
949
953
|
* rather than "wait longer for a chance at completeness". 8 s is the same elective
|
|
950
954
|
* probe budget the graph_query / graph_serialize probes in this file already use. */
|
|
951
955
|
const GET_ERRORS_COMPLETION_BUDGET_MS = 8_000;
|
|
956
|
+
// #2527 — after a mutating graph command times out, the frontend may still be
|
|
957
|
+
// applying it. An immediate graph read can then echo the pre-write value, and a
|
|
958
|
+
// later write reports the timed-out value as `previous`. Graph reads wait this
|
|
959
|
+
// long for that settlement, then disclose any still-outstanding receipt.
|
|
960
|
+
const OUTSTANDING_MUTATION_READ_SETTLE_MS = 8_000;
|
|
961
|
+
let outstandingMutationReadSettleMsOverride = null;
|
|
962
|
+
function outstandingMutationReadSettleMs() {
|
|
963
|
+
return outstandingMutationReadSettleMsOverride ?? OUTSTANDING_MUTATION_READ_SETTLE_MS;
|
|
964
|
+
}
|
|
965
|
+
const GRAPH_READ_SETTLE_CMDS = new Set([
|
|
966
|
+
...Object.values(GRAPH_READ_CMD_BY_TOOL),
|
|
967
|
+
"graph_get_subgraph",
|
|
968
|
+
"graph_get_state",
|
|
969
|
+
"graph_serialize",
|
|
970
|
+
]);
|
|
971
|
+
function isGraphReadSettleCmd(cmd) {
|
|
972
|
+
return typeof cmd.cmd === "string" && GRAPH_READ_SETTLE_CMDS.has(cmd.cmd);
|
|
973
|
+
}
|
|
974
|
+
function sameMutationScalar(a, b) {
|
|
975
|
+
if (a === b)
|
|
976
|
+
return true;
|
|
977
|
+
if (a == null || b == null)
|
|
978
|
+
return a == null && b == null;
|
|
979
|
+
return String(a) === String(b);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Semantic identity of a delivered mutation vs a retry, ignoring bridge-owned
|
|
983
|
+
* stamps (`rid`, `retry_of`, `timeout_ms`, `workflow_uuid`) and write-fence
|
|
984
|
+
* witnesses that a later probe may reconstruct (`expected_*`).
|
|
985
|
+
*/
|
|
986
|
+
function mutationIdentityMatches(retryCmd, delivered) {
|
|
987
|
+
if (retryCmd.cmd !== delivered.cmd)
|
|
988
|
+
return false;
|
|
989
|
+
if (retryCmd.cmd === "graph_set_widget") {
|
|
990
|
+
return (sameMutationScalar(retryCmd.node_id, delivered.node_id) &&
|
|
991
|
+
sameMutationScalar(retryCmd.widget, delivered.widget) &&
|
|
992
|
+
sameMutationScalar(retryCmd.value, delivered.value));
|
|
993
|
+
}
|
|
994
|
+
if ("node_id" in delivered)
|
|
995
|
+
return sameMutationScalar(retryCmd.node_id, delivered.node_id);
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
async function awaitOutstandingMutationSettle(bridge, tabId) {
|
|
999
|
+
const wait = bridge.waitForOutstandingMutations;
|
|
1000
|
+
if (typeof wait !== "function")
|
|
1001
|
+
return;
|
|
1002
|
+
await wait.call(bridge, tabId, outstandingMutationReadSettleMs());
|
|
1003
|
+
}
|
|
1004
|
+
function discloseOutstandingMutations(bridge, tabId, res) {
|
|
1005
|
+
const list = bridge.listOutstandingMutations?.(tabId);
|
|
1006
|
+
if (!list?.length)
|
|
1007
|
+
return res;
|
|
1008
|
+
const named = list
|
|
1009
|
+
.map((m) => `"${m.cmd}" retry_of:"${m.rid}"`)
|
|
1010
|
+
.join(", ");
|
|
1011
|
+
return appendReplyNote(res, `OUTCOME UNKNOWN: ${list.length} delivered mutation(s) still unsettled on this tab (${named}). ` +
|
|
1012
|
+
`This graph read may show values from before those writes applied. Do not treat this snapshot ` +
|
|
1013
|
+
`as proof the write failed; wait, or retry the named mutation with its retry_of token.`);
|
|
1014
|
+
}
|
|
952
1015
|
// #1639 — while a ComfyUI prompt is running the frontend main thread often
|
|
953
1016
|
// cannot service graph_* at all. Waiting out the 20/90 s ack bound only
|
|
954
1017
|
// surfaces "tab may be backgrounded or frozen" with an unknown mutation
|
|
@@ -9800,6 +9863,20 @@ function pinHonorsLiveCanvas(pin, liveActive) {
|
|
|
9800
9863
|
return false;
|
|
9801
9864
|
return readOpenActiveAgainstTarget(liveActive, pin.path) === "same";
|
|
9802
9865
|
}
|
|
9866
|
+
/**
|
|
9867
|
+
* Automatic fence repair needs stronger identity than legacy pin resolution.
|
|
9868
|
+
* `activeMatchesTarget` intentionally accepts filename/basename aliases for
|
|
9869
|
+
* compatibility, but a basename cannot prove which saved workflow is live
|
|
9870
|
+
* when two directories contain the same filename. Use only a canonical path
|
|
9871
|
+
* or a canonical key here; otherwise leave the dispatch fence refusing.
|
|
9872
|
+
*/
|
|
9873
|
+
function exactPinnedSavedPathMatchesLive(pinPath, liveActive) {
|
|
9874
|
+
const expected = canonicalSavedWorkflowPath(pinPath);
|
|
9875
|
+
if (!expected || !expected.includes("/") || !liveActive)
|
|
9876
|
+
return false;
|
|
9877
|
+
const observed = liveActive;
|
|
9878
|
+
return [observed.path, observed.key].some((value) => canonicalSavedWorkflowPath(value) === expected);
|
|
9879
|
+
}
|
|
9803
9880
|
/**
|
|
9804
9881
|
* The ONE remedy that is actually reachable from every wedged state below, and
|
|
9805
9882
|
* the one the reporters found to be the only thing that worked.
|
|
@@ -12363,6 +12440,12 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
12363
12440
|
const blocked = graphCmdBlockedByRunningPrompt(cmd);
|
|
12364
12441
|
if (blocked)
|
|
12365
12442
|
return fail(blocked);
|
|
12443
|
+
// #2527 — a timed-out mutation may still be applying. Graph reads wait for
|
|
12444
|
+
// that settlement (or disclose the outstanding receipt) so they cannot
|
|
12445
|
+
// certify a stale widget value as current.
|
|
12446
|
+
if (isGraphReadSettleCmd(cmd)) {
|
|
12447
|
+
await awaitOutstandingMutationSettle(bridge, ctx.tabId);
|
|
12448
|
+
}
|
|
12366
12449
|
// The bridge owns the graph lane and re-resolves the live connection after
|
|
12367
12450
|
// waiting. Pass the fence through so it runs at the actual socket dispatch,
|
|
12368
12451
|
// not before that retarget window.
|
|
@@ -12373,7 +12456,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
12373
12456
|
// switch inherited its age and was announced as stuck at once (codex r4).
|
|
12374
12457
|
if (successProvesSwitchCleared(cmd.cmd))
|
|
12375
12458
|
clearSwitchHold(ctx.tabId);
|
|
12376
|
-
return
|
|
12459
|
+
return isGraphReadSettleCmd(cmd)
|
|
12460
|
+
? discloseOutstandingMutations(bridge, ctx.tabId, firstTry)
|
|
12461
|
+
: firstTry;
|
|
12377
12462
|
}
|
|
12378
12463
|
catch (err) {
|
|
12379
12464
|
onFailure?.(err); // #1560 — the error this attempt failed on (see the wrapper).
|
|
@@ -12450,6 +12535,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
12450
12535
|
await awaitReachable();
|
|
12451
12536
|
ensureReachable(); // rebinds a current-mode session onto the reconnected tab
|
|
12452
12537
|
holdTab = ctx.tabId;
|
|
12538
|
+
if (isGraphReadSettleCmd(cmd)) {
|
|
12539
|
+
await awaitOutstandingMutationSettle(bridge, ctx.tabId);
|
|
12540
|
+
}
|
|
12453
12541
|
const retried = ok(await sendRouted(cmd, timeoutMs, observeRid, beforeDispatch, options));
|
|
12454
12542
|
// Cleared HERE and nowhere else, and only when the failure this retried
|
|
12455
12543
|
// was a SWITCH refusal. Clearing on every routed success let an unguarded
|
|
@@ -12463,7 +12551,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
12463
12551
|
if (isWorkflowSwitchGuardRefusal(err) && successProvesSwitchCleared(cmd.cmd)) {
|
|
12464
12552
|
clearSwitchHold(holdTab);
|
|
12465
12553
|
}
|
|
12466
|
-
return
|
|
12554
|
+
return isGraphReadSettleCmd(cmd)
|
|
12555
|
+
? discloseOutstandingMutations(bridge, ctx.tabId, retried)
|
|
12556
|
+
: retried;
|
|
12467
12557
|
}
|
|
12468
12558
|
catch (err2) {
|
|
12469
12559
|
// #1560 — the RETRY's failure is the one every exit below describes, so it
|
|
@@ -13017,7 +13107,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
13017
13107
|
const retried = ok(await sendRouted(cmd, timeoutMs, observeRid));
|
|
13018
13108
|
if (successProvesSwitchCleared(cmd.cmd))
|
|
13019
13109
|
clearSwitchHold(ctx.tabId);
|
|
13020
|
-
return
|
|
13110
|
+
return isGraphReadSettleCmd(cmd)
|
|
13111
|
+
? discloseOutstandingMutations(bridge, ctx.tabId, retried)
|
|
13112
|
+
: retried;
|
|
13021
13113
|
}
|
|
13022
13114
|
}
|
|
13023
13115
|
catch {
|
|
@@ -13046,7 +13138,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
13046
13138
|
const retried = ok(await sendRouted(cmd, timeoutMs, observeRid));
|
|
13047
13139
|
if (successProvesSwitchCleared(cmd.cmd))
|
|
13048
13140
|
clearSwitchHold(ctx.tabId);
|
|
13049
|
-
return
|
|
13141
|
+
return isGraphReadSettleCmd(cmd)
|
|
13142
|
+
? discloseOutstandingMutations(bridge, ctx.tabId, retried)
|
|
13143
|
+
: retried;
|
|
13050
13144
|
}
|
|
13051
13145
|
catch (retryErr) {
|
|
13052
13146
|
onFailure?.(retryErr);
|
|
@@ -13061,7 +13155,9 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
13061
13155
|
const retried = ok(await sendRouted(cmd, timeoutMs, observeRid));
|
|
13062
13156
|
if (successProvesSwitchCleared(cmd.cmd))
|
|
13063
13157
|
clearSwitchHold(ctx.tabId);
|
|
13064
|
-
return
|
|
13158
|
+
return isGraphReadSettleCmd(cmd)
|
|
13159
|
+
? discloseOutstandingMutations(bridge, ctx.tabId, retried)
|
|
13160
|
+
: retried;
|
|
13065
13161
|
}
|
|
13066
13162
|
catch (retryErr) {
|
|
13067
13163
|
onFailure?.(retryErr);
|
|
@@ -13494,6 +13590,95 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
13494
13590
|
* what I have open" is the common ask, and requiring a save-to-disk round trip
|
|
13495
13591
|
* first derailed real sessions (deleted placeholder files, 404 tabs).
|
|
13496
13592
|
*/
|
|
13593
|
+
/**
|
|
13594
|
+
* #2487 — whether the dispatch-time stamp-target gate would refuse a fenced
|
|
13595
|
+
* graph command from this session.
|
|
13596
|
+
*
|
|
13597
|
+
* TRI-STATE on purpose. The gate compares the session stamp against the routed
|
|
13598
|
+
* tab's LAST ADVERTISED identity, which is not the pin and is not the live
|
|
13599
|
+
* canvas. A missing or throwing probe is an unmade observation, not "no
|
|
13600
|
+
* disagreement".
|
|
13601
|
+
*/
|
|
13602
|
+
function stampTargetDisagrees(ctx) {
|
|
13603
|
+
try {
|
|
13604
|
+
const cap = ctx.tabGraphMutationCapability?.();
|
|
13605
|
+
if (!cap)
|
|
13606
|
+
return undefined;
|
|
13607
|
+
if (cap.known !== true)
|
|
13608
|
+
return undefined;
|
|
13609
|
+
return cap.canMutate === false && cap.because === "target_disagreement";
|
|
13610
|
+
}
|
|
13611
|
+
catch {
|
|
13612
|
+
return undefined;
|
|
13613
|
+
}
|
|
13614
|
+
}
|
|
13615
|
+
/**
|
|
13616
|
+
* #2487 — a pin is a PATH, not the last-active tab's advertised instance.
|
|
13617
|
+
*
|
|
13618
|
+
* After a verified pin, subgraph enter/exit can succeed (they go through
|
|
13619
|
+
* `ctx.call`) while the next live-canvas capture — `panel_strip_workflow({})`
|
|
13620
|
+
* via `bridge.send` — is refused because the routed tab still advertises a
|
|
13621
|
+
* different workflow instance. The refusal names `mode:"current"`, which
|
|
13622
|
+
* RELEASES the pin.
|
|
13623
|
+
*
|
|
13624
|
+
* Re-resolve the pin against the live canvas. If it still NAMES that canvas,
|
|
13625
|
+
* reconcile a stale advertisement (#2209) or refresh a reminted instance
|
|
13626
|
+
* stamp (#1913: honoring the path is not abandoning it). A pin to a
|
|
13627
|
+
* DIFFERENT canvas is left alone — that refusal is the fence working.
|
|
13628
|
+
*
|
|
13629
|
+
* Returns whether a repair actually happened, so a caller can retry a
|
|
13630
|
+
* capture that was refused for this reason and not for any other.
|
|
13631
|
+
*/
|
|
13632
|
+
async function repairPinnedStampForLiveCanvas(ctx) {
|
|
13633
|
+
let pin;
|
|
13634
|
+
try {
|
|
13635
|
+
pin = ctx.workflowTarget?.get(ctx.tabId);
|
|
13636
|
+
}
|
|
13637
|
+
catch {
|
|
13638
|
+
return false;
|
|
13639
|
+
}
|
|
13640
|
+
if (pin?.mode !== "pinned" || typeof pin.path !== "string" || pin.path.length === 0) {
|
|
13641
|
+
return false;
|
|
13642
|
+
}
|
|
13643
|
+
if (typeof ctx.call !== "function")
|
|
13644
|
+
return false;
|
|
13645
|
+
let probe;
|
|
13646
|
+
try {
|
|
13647
|
+
probe = await rebindWorkflowFence(ctx, { adopt: false });
|
|
13648
|
+
}
|
|
13649
|
+
catch {
|
|
13650
|
+
return false;
|
|
13651
|
+
}
|
|
13652
|
+
// pinHonorsLiveCanvas is the UUID-adoption gate: it needs routing_key ===
|
|
13653
|
+
// wf:<path>. Current panels advertise wf:<route>:<path> (#640), which that
|
|
13654
|
+
// gate reads as indeterminate. Path/filename/key equality still proves the
|
|
13655
|
+
// pin names this canvas. A path/key match is required when the panel's
|
|
13656
|
+
// routing shape is indeterminate; a bare filename is never enough.
|
|
13657
|
+
const live = liveActiveFromProbe(probe);
|
|
13658
|
+
const namesLive = pinHonorsLiveCanvas(pin, live) ||
|
|
13659
|
+
exactPinnedSavedPathMatchesLive(pin.path, live);
|
|
13660
|
+
if (!namesLive)
|
|
13661
|
+
return false;
|
|
13662
|
+
if (probe.status === "already_current") {
|
|
13663
|
+
return reconcileStampTarget(ctx, probe.uuid).ok;
|
|
13664
|
+
}
|
|
13665
|
+
if (probe.status === "diverged") {
|
|
13666
|
+
const identity = probe.active ?? { workflow_uuid: probe.uuid };
|
|
13667
|
+
if (!refreshWorkflowUuid(ctx, identity))
|
|
13668
|
+
return false;
|
|
13669
|
+
const rec = reconcileStampTarget(ctx, probe.uuid);
|
|
13670
|
+
return rec.ok || rec.why === "no_disagreement";
|
|
13671
|
+
}
|
|
13672
|
+
return false;
|
|
13673
|
+
}
|
|
13674
|
+
/** Live-canvas capture: the same pin injection `ctx.call` applies, over the
|
|
13675
|
+
* direct `bridge.send` this path has to use (it needs the raw graph, not a
|
|
13676
|
+
* ToolResult wrapper). */
|
|
13677
|
+
async function sendLiveCanvasGraphCmd(ctx, cmd, timeoutMs) {
|
|
13678
|
+
const target = ctx.workflowTarget?.get(ctx.tabId);
|
|
13679
|
+
const routed = target ? withWorkflowTarget({ cmd }, target) : { cmd };
|
|
13680
|
+
return ctx.bridge.send(routed, { tabId: ctx.tabId, timeoutMs });
|
|
13681
|
+
}
|
|
13497
13682
|
/**
|
|
13498
13683
|
* Rebuild a UI-format workflow ({ nodes, links }) from the panel's back-compat
|
|
13499
13684
|
* `graph_get_state` reply (the #384 fallback). Each summarized node carries its
|
|
@@ -13672,18 +13857,27 @@ notes) {
|
|
|
13672
13857
|
let reply;
|
|
13673
13858
|
try {
|
|
13674
13859
|
ctx.ensureReachable?.();
|
|
13675
|
-
//
|
|
13676
|
-
//
|
|
13677
|
-
//
|
|
13678
|
-
//
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
}
|
|
13860
|
+
// #2487 — honor a verified pin BEFORE the capture is compared against the
|
|
13861
|
+
// routed tab's last-advertised identity. That advertisement is unrelated
|
|
13862
|
+
// active-tab state: subgraph enter/exit can leave it stale while the pin
|
|
13863
|
+
// still names the live canvas. Re-resolve here rather than telling the
|
|
13864
|
+
// caller to `mode:"current"`, which would release the pin. Cheap when the
|
|
13865
|
+
// gate is not refusing; a missing probe is not treated as disagreement.
|
|
13866
|
+
if (stampTargetDisagrees(ctx) === true) {
|
|
13867
|
+
await repairPinnedStampForLiveCanvas(ctx);
|
|
13868
|
+
}
|
|
13869
|
+
try {
|
|
13870
|
+
reply = await sendLiveCanvasGraphCmd(ctx, "graph_serialize", 30000);
|
|
13871
|
+
}
|
|
13872
|
+
catch (err) {
|
|
13873
|
+
// The capability probe is optional (lightweight ctxs omit it) and can
|
|
13874
|
+
// miss a disagreement the send() gate still sees. One repair+retry, and
|
|
13875
|
+
// only for this refusal — any other capture error keeps its own path.
|
|
13876
|
+
if (!isWorkflowInstanceMismatch(err) || !(await repairPinnedStampForLiveCanvas(ctx))) {
|
|
13877
|
+
throw err;
|
|
13878
|
+
}
|
|
13879
|
+
reply = await sendLiveCanvasGraphCmd(ctx, "graph_serialize", 30000);
|
|
13880
|
+
}
|
|
13687
13881
|
}
|
|
13688
13882
|
catch (err) {
|
|
13689
13883
|
// #384: a panel too old to register graph_serialize (added at 0.8.2) still
|
|
@@ -13700,14 +13894,7 @@ notes) {
|
|
|
13700
13894
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13701
13895
|
if (allowStateFallback && isPanelCmdUnsupportedError(err, "graph_serialize")) {
|
|
13702
13896
|
try {
|
|
13703
|
-
const
|
|
13704
|
-
const stateCmd = target
|
|
13705
|
-
? withWorkflowTarget({ cmd: "graph_get_state" }, target)
|
|
13706
|
-
: { cmd: "graph_get_state" };
|
|
13707
|
-
const stateReply = await ctx.bridge.send(stateCmd, {
|
|
13708
|
-
tabId: ctx.tabId,
|
|
13709
|
-
timeoutMs: 30000,
|
|
13710
|
-
});
|
|
13897
|
+
const stateReply = await sendLiveCanvasGraphCmd(ctx, "graph_get_state", 30000);
|
|
13711
13898
|
const rebuilt = reconstructUiFromState(stateReply);
|
|
13712
13899
|
if (rebuilt)
|
|
13713
13900
|
return rebuilt;
|
|
@@ -13741,14 +13928,7 @@ notes) {
|
|
|
13741
13928
|
if (allowStateFallback && notes) {
|
|
13742
13929
|
let stateReply;
|
|
13743
13930
|
try {
|
|
13744
|
-
|
|
13745
|
-
const stateCmd = target
|
|
13746
|
-
? withWorkflowTarget({ cmd: "graph_get_state" }, target)
|
|
13747
|
-
: { cmd: "graph_get_state" };
|
|
13748
|
-
stateReply = await ctx.bridge.send(stateCmd, {
|
|
13749
|
-
tabId: ctx.tabId,
|
|
13750
|
-
timeoutMs: 30000,
|
|
13751
|
-
});
|
|
13931
|
+
stateReply = await sendLiveCanvasGraphCmd(ctx, "graph_get_state", 30000);
|
|
13752
13932
|
}
|
|
13753
13933
|
catch (err) {
|
|
13754
13934
|
// Never fatal: strip already HAS a usable graph. Losing the cross-check
|
|
@@ -15093,32 +15273,50 @@ function withRetryToken(d) {
|
|
|
15093
15273
|
// they just named it. Drained here (once) and reported alongside the
|
|
15094
15274
|
// retry's own outcome.
|
|
15095
15275
|
//
|
|
15096
|
-
//
|
|
15097
|
-
//
|
|
15098
|
-
//
|
|
15099
|
-
// this rid landed" does not prove "the write you are asking for now is
|
|
15100
|
-
// that same write". Suppressing a real mutation on that inference is the
|
|
15101
|
-
// #683 mistake with the arrow reversed, and #687 reverted it for cause.
|
|
15102
|
-
// Double-apply is already the #521 panel ledger's job; this only makes the
|
|
15103
|
-
// outcome VISIBLE, which is the half of #694 nothing else does.
|
|
15104
|
-
const wrapped = Object.create(ctx);
|
|
15105
|
-
wrapped.call = (cmd, timeoutMs, onDispatchedRid, beforeDispatch) => ctx.call(
|
|
15106
|
-
// Ask the RETRY MAP's question, not the workflow fence's (codex gate).
|
|
15107
|
-
// These were the same answer only while isMutatingGraphCommand
|
|
15108
|
-
// over-classified — the #778 defect. Once the fence got its own effect
|
|
15109
|
-
// ledger they diverged, and gating on the fence would have SILENTLY
|
|
15110
|
-
// DROPPED a caller-supplied retry_of for the four UI-state commands the
|
|
15111
|
-
// map admits on purpose: the schema accepts the token, the description
|
|
15112
|
-
// promises dedupe, and nothing would reach the wire. A caller who
|
|
15113
|
-
// believes their retry is deduped and is wrong is exactly the failure
|
|
15114
|
-
// the token exists to prevent.
|
|
15276
|
+
// #683/#687 — do NOT short-circuit on rid alone. The token names an
|
|
15277
|
+
// attempt, not a fingerprint of its args, so "this rid landed" does not
|
|
15278
|
+
// prove "the write you are asking for now is that same write".
|
|
15115
15279
|
//
|
|
15116
|
-
//
|
|
15117
|
-
//
|
|
15118
|
-
//
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15280
|
+
// #2527 — when the receipt for that rid is the SAME command (semantic
|
|
15281
|
+
// identity) and the frontend has already settled it, re-dispatching is
|
|
15282
|
+
// what gets rejected as "retry_of refers to a different command or
|
|
15283
|
+
// workflow". Answer from the receipt instead. A sibling mutation inside
|
|
15284
|
+
// the same handler (enter_subgraph during a set_widget retry) must not
|
|
15285
|
+
// carry this token at all.
|
|
15286
|
+
const wrapped = Object.create(ctx);
|
|
15287
|
+
wrapped.call = (cmd, timeoutMs, onDispatchedRid, beforeDispatch) => {
|
|
15288
|
+
const cmdName = typeof cmd.cmd === "string" ? cmd.cmd : "";
|
|
15289
|
+
const original = ctx.bridge?.peekDeliveredMutation?.(retryOf);
|
|
15290
|
+
if (original) {
|
|
15291
|
+
if (original.cmd !== cmdName) {
|
|
15292
|
+
return ctx.call(cmd, timeoutMs, onDispatchedRid, beforeDispatch);
|
|
15293
|
+
}
|
|
15294
|
+
const settled = ctx.bridge.peekLateMutation?.(retryOf);
|
|
15295
|
+
if (settled && mutationIdentityMatches(cmd, original.frame)) {
|
|
15296
|
+
return Promise.resolve(ok(settled.result !== undefined ? settled.result : { ok: true, retry_of: retryOf }));
|
|
15297
|
+
}
|
|
15298
|
+
if (mutationIdentityMatches(cmd, original.frame)) {
|
|
15299
|
+
const replay = { ...original.frame, retry_of: retryOf };
|
|
15300
|
+
delete replay.rid;
|
|
15301
|
+
return ctx.call(replay, timeoutMs, onDispatchedRid, beforeDispatch);
|
|
15302
|
+
}
|
|
15303
|
+
}
|
|
15304
|
+
return ctx.call(
|
|
15305
|
+
// Ask the RETRY MAP's question, not the workflow fence's (codex gate).
|
|
15306
|
+
// These were the same answer only while isMutatingGraphCommand
|
|
15307
|
+
// over-classified — the #778 defect. Once the fence got its own effect
|
|
15308
|
+
// ledger they diverged, and gating on the fence would have SILENTLY
|
|
15309
|
+
// DROPPED a caller-supplied retry_of for the four UI-state commands the
|
|
15310
|
+
// map admits on purpose: the schema accepts the token, the description
|
|
15311
|
+
// promises dedupe, and nothing would reach the wire. A caller who
|
|
15312
|
+
// believes their retry is deduped and is wrong is exactly the failure
|
|
15313
|
+
// the token exists to prevent.
|
|
15314
|
+
//
|
|
15315
|
+
// A read probe inside a mutating handler (panel_flatten_workflow's
|
|
15316
|
+
// graph_serialize) is still excluded — RETRY_TOKEN_CMDS contains no
|
|
15317
|
+
// reads, which is asserted in panel-retry-identity.test.ts.
|
|
15318
|
+
RETRY_TOKEN_CMDS.has(cmdName) ? { ...cmd, retry_of: retryOf } : cmd, timeoutMs, onDispatchedRid, beforeDispatch);
|
|
15319
|
+
};
|
|
15122
15320
|
const out = await d.handler(args, wrapped);
|
|
15123
15321
|
// Drained AFTER the handler, deliberately, for two measured reasons.
|
|
15124
15322
|
//
|