comfyui-mcp 0.52.26 → 0.52.27
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/node-id.js +12 -2
- package/dist/orchestrator/node-id.js.map +1 -1
- package/dist/orchestrator/panel-tools.js +148 -30
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/ui-bridge.js +59 -9
- package/dist/services/ui-bridge.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,8 +21,18 @@
|
|
|
21
21
|
* refused to risk. So a qualified id is never converted: it stays the string the
|
|
22
22
|
* reader printed, all the way to the panel.
|
|
23
23
|
*/
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* A plain integer id, the form the wire has always carried.
|
|
26
|
+
*
|
|
27
|
+
* Exported because one argument needs this shape ON ITS OWN: panel_run's
|
|
28
|
+
* `to_node_id` (#1497). Everything else takes NODE_ID_PATTERN, which admits the
|
|
29
|
+
* qualified form too — but a run-to-node target is resolved by NUMBER all the way
|
|
30
|
+
* down (the panel's findNodeInScopes does `Number(id)`, and its reply reports
|
|
31
|
+
* `ran_to_node: Number(to_node_id)`), so admitting `"120:104"` there would trade a
|
|
32
|
+
* clear schema refusal for a panel-side "node not found" about a node that exists.
|
|
33
|
+
* The shape lives here so the two patterns cannot drift apart.
|
|
34
|
+
*/
|
|
35
|
+
export const PLAIN_NODE_ID_PATTERN = /^-?\d+$/;
|
|
26
36
|
/**
|
|
27
37
|
* A subgraph-qualified id: integer segments joined by colons (`120:104`,
|
|
28
38
|
* `120:113:78`). No depth limit — nesting is arbitrary, and a limit would fail the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-id.js","sourceRoot":"","sources":["../../src/orchestrator/node-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH
|
|
1
|
+
{"version":3,"file":"node-id.js","sourceRoot":"","sources":["../../src/orchestrator/node-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,CAAS;IACzC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,CAAkB;IAChD,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;oFACoF;AACpF,MAAM,CAAC,MAAM,eAAe,GAC1B,kFAAkF,CAAC"}
|
|
@@ -45,7 +45,7 @@ import { compareSemver } from "../services/self-update.js";
|
|
|
45
45
|
import { describeInstallPanelAction } from "../services/panel-recovery.js";
|
|
46
46
|
import { peekResolvedPanelBase, primePanelBase, verifiedPanelDiskVersion, } from "../services/panel-workspace.js";
|
|
47
47
|
import { conversationOfScopeAddress, isScopeAddress, shortTabId } from "../services/session-scope.js";
|
|
48
|
-
import { NODE_ID_MESSAGE, NODE_ID_PATTERN, normalizeNodeId } from "./node-id.js";
|
|
48
|
+
import { NODE_ID_MESSAGE, NODE_ID_PATTERN, normalizeNodeId, PLAIN_NODE_ID_PATTERN, } from "./node-id.js";
|
|
49
49
|
import { parseContradictoryPromotedWidgetRefusal, resolveInnerPromotedTarget, } from "./promoted-widget.js";
|
|
50
50
|
import { fastGroupsFilterPropertyNote, isFastGroupsFilterProperty, } from "./rgthree-fast-groups-property.js";
|
|
51
51
|
import { isPlainObject, isStampMismatchSaveRefusal, openLiveMatchesDestContent, patchOpenIdentity, shouldRebindOpenIdentity, workflowFromSerializeReply, } from "./open-identity-normalization.js";
|
|
@@ -4973,8 +4973,18 @@ panelGapNote = "") {
|
|
|
4973
4973
|
// A panel that cannot fence a WRITE gives reads only, however good the stamp is.
|
|
4974
4974
|
const mutationsRefused = canMutate === false;
|
|
4975
4975
|
const mutationsUnknown = canMutate === undefined;
|
|
4976
|
+
// #1494 — `reads_only` would be a FALSE consolation for this one cause. The
|
|
4977
|
+
// dispatch-time agreement gate covers `graph_*` READS as well as mutations
|
|
4978
|
+
// (requiresStampTargetAgreement), so a session in that state has neither working
|
|
4979
|
+
// graph reads NOR writes — only the canvas-INDEPENDENT ops that predicate exempts
|
|
4980
|
+
// (graph_update_node, the virtual-type reads) still dispatch, which is not what a
|
|
4981
|
+
// caller reads "reads_only" as. And the whole report is that this call answered
|
|
4982
|
+
// "bound" while everything the gate covers was being refused. It is
|
|
4983
|
+
// `not_recovered`: the caller must not read success out of it.
|
|
4976
4984
|
const okBinding = mutationsRefused
|
|
4977
|
-
? "
|
|
4985
|
+
? refusalCause === "target_disagreement"
|
|
4986
|
+
? "not_recovered"
|
|
4987
|
+
: "reads_only"
|
|
4978
4988
|
: mutationsUnknown
|
|
4979
4989
|
? "unverified"
|
|
4980
4990
|
: "bound";
|
|
@@ -5000,23 +5010,37 @@ panelGapNote = "") {
|
|
|
5000
5010
|
`\n\nWHAT TO DO: call this tool again to bind an identity. If it keeps coming back ` +
|
|
5001
5011
|
`without one, have the user click into the workflow tab so the panel reports an active ` +
|
|
5002
5012
|
`canvas, then call this again.`
|
|
5003
|
-
: mutationsRefused && refusalCause === "
|
|
5004
|
-
? `\n\nBUT
|
|
5005
|
-
`
|
|
5006
|
-
`
|
|
5007
|
-
`
|
|
5008
|
-
|
|
5009
|
-
`
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5013
|
+
: mutationsRefused && refusalCause === "target_disagreement"
|
|
5014
|
+
? `\n\nBUT THIS DID NOT CLEAR THE REFUSAL, and saying otherwise is the bug this ` +
|
|
5015
|
+
`wording exists to stop (#1494). Graph commands are refused BEFORE they are ` +
|
|
5016
|
+
`dispatched, by a comparison this call does not perform: the session's stamp against ` +
|
|
5017
|
+
`the identity the ROUTED TAB last advertised in its handshake. This call compared the ` +
|
|
5018
|
+
`stamp against the LIVE canvas instead, and those are different facts — the live ` +
|
|
5019
|
+
`canvas agreeing is exactly why nothing above says "rebound". Graph READS are refused ` +
|
|
5020
|
+
`by the same gate, so this is not a reads-only session.` +
|
|
5021
|
+
`\n\nWHAT TO DO: re-open the workflow you mean with panel_open_workflow(<path>) — an ` +
|
|
5022
|
+
`open proves an identity for the tab under its current id, which is what the gate is ` +
|
|
5023
|
+
`missing; for a never-saved canvas use its routing_key from panel_list_workflows. The ` +
|
|
5024
|
+
`panel also re-advertises on its own when it notices the drift, so a command that ` +
|
|
5025
|
+
`keeps failing here may start working without you doing anything — do NOT read that as ` +
|
|
5026
|
+
`this call having repaired it.`
|
|
5027
|
+
: mutationsRefused && refusalCause === "unroutable"
|
|
5028
|
+
? `\n\nBUT this tab is NOT REACHABLE from the orchestrator right now, so graph mutations ` +
|
|
5029
|
+
`are refused — and reads are not working either, whatever this note says about them. ` +
|
|
5030
|
+
`This is NOT a panel-version problem: updating the pack and hard-refreshing cannot help, ` +
|
|
5031
|
+
`because there is nothing to refresh until the tab is connected again.` +
|
|
5032
|
+
`\n\nWHAT TO DO: check that the ComfyUI browser tab is still open and its panel is ` +
|
|
5033
|
+
`connected, then call this again.`
|
|
5034
|
+
: mutationsRefused
|
|
5035
|
+
? `\n\nBUT graph MUTATIONS are still refused for this tab: its panel build does not ` +
|
|
5036
|
+
`advertise the write-boundary workflow fence a graph edit requires, and no rebind — ` +
|
|
5037
|
+
`including calling this tool again — can add it. Reads work now (this call just read the ` +
|
|
5038
|
+
`live canvas).\n\nWHAT TO DO FOR EDITS: update the comfyui-mcp-panel pack, then have the ` +
|
|
5039
|
+
`user HARD-refresh (Ctrl+Shift+R) the ComfyUI browser tab — an open tab keeps running its ` +
|
|
5040
|
+
`cached bundle, so the capability lags the version on disk until it does. Do NOT use ` +
|
|
5041
|
+
`panel_reload for this: it has been observed to leave the tab permanently unresponsive ` +
|
|
5042
|
+
`(#803).`
|
|
5043
|
+
: "";
|
|
5020
5044
|
switch (r.status) {
|
|
5021
5045
|
case "diverged":
|
|
5022
5046
|
// #1646 — produced ONLY by a read-only probe (`adopt:false`), which the
|
|
@@ -7427,14 +7451,53 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
7427
7451
|
let verdict;
|
|
7428
7452
|
try {
|
|
7429
7453
|
const probe = await rebindWorkflowFence(ctx, { adopt: false });
|
|
7454
|
+
// #1494 — WHICH COMPARISON IS REFUSING, asked of the gate itself rather than
|
|
7455
|
+
// inferred from the probe.
|
|
7456
|
+
//
|
|
7457
|
+
// The probe reads the LIVE canvas. The dispatch-time agreement gate refuses on
|
|
7458
|
+
// the identity the ROUTED TAB last advertised. When the advertisement is the
|
|
7459
|
+
// stale side, those two disagree — the probe reports `already_current` on the
|
|
7460
|
+
// very uuid the command carried, which reads as "transient, retry" while every
|
|
7461
|
+
// retry is compared against the same unchanged advertisement and refused
|
|
7462
|
+
// identically. That is the reported contradiction: an immediate rebind
|
|
7463
|
+
// confirming the original instance, next to a refusal naming another one.
|
|
7464
|
+
//
|
|
7465
|
+
// Typed, from `tabGraphMutationCapability` (the bridge's own verdict, sharing
|
|
7466
|
+
// one predicate with `send()`), never a second reading of the refusal's prose.
|
|
7467
|
+
let gateStillRefuses = false;
|
|
7468
|
+
try {
|
|
7469
|
+
const cap = ctx.tabGraphMutationCapability?.();
|
|
7470
|
+
gateStillRefuses =
|
|
7471
|
+
cap?.known === true &&
|
|
7472
|
+
cap.canMutate === false &&
|
|
7473
|
+
cap.because === "target_disagreement";
|
|
7474
|
+
}
|
|
7475
|
+
catch {
|
|
7476
|
+
gateStillRefuses = false; // a diagnostic must never replace the outcome
|
|
7477
|
+
}
|
|
7430
7478
|
verdict =
|
|
7431
7479
|
probe.status === "already_current" && stamped && probe.uuid === stamped
|
|
7432
|
-
?
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7480
|
+
? gateStillRefuses
|
|
7481
|
+
? `\n\nCHECKED, and the two claims are BOTH true of different things: the live ` +
|
|
7482
|
+
`canvas does report the SAME workflow instance this command carried ` +
|
|
7483
|
+
`(${stamped}), and the refusal above is not about the live canvas — it is ` +
|
|
7484
|
+
`the pre-dispatch check on the identity the ROUTED TAB last advertised, ` +
|
|
7485
|
+
`which is the stale side here. So a bare retry is compared against that ` +
|
|
7486
|
+
`same unchanged value and is refused the same way; ` +
|
|
7487
|
+
`panel_set_workflow_target({mode:"current"}) reports "already current" for ` +
|
|
7488
|
+
`the same reason and does not clear it either. Nothing was applied. WHAT TO ` +
|
|
7489
|
+
`DO: re-open the workflow you mean with panel_open_workflow(<path>) — that ` +
|
|
7490
|
+
`proves an identity for the tab under its CURRENT id, which is what is ` +
|
|
7491
|
+
`missing; for a never-saved canvas use its routing_key from ` +
|
|
7492
|
+
`panel_list_workflows. The panel also re-advertises on its own once it ` +
|
|
7493
|
+
`notices the drift, so a retry may start working shortly — that is the ` +
|
|
7494
|
+
`panel repairing it, not this call.`
|
|
7495
|
+
: `\n\nCHECKED: the live canvas now reports the SAME workflow instance this ` +
|
|
7496
|
+
`command carried (${stamped}), so the mismatch was TRANSIENT: the identity ` +
|
|
7497
|
+
`flipped and settled back, which happens while a new unsaved workflow is still ` +
|
|
7498
|
+
`materialising. RETRY THIS EXACT CALL ONCE. Nothing was applied, so a retry ` +
|
|
7499
|
+
`cannot double-apply, and re-issuing the whole build would duplicate the work ` +
|
|
7500
|
+
`that already succeeded.`
|
|
7438
7501
|
: probe.status === "already_current"
|
|
7439
7502
|
? `\n\nCHECKED: the session's fence already names the live canvas ` +
|
|
7440
7503
|
`(${probe.uuid}), so it was not the stale side. Retry once — if it refuses ` +
|
|
@@ -8966,6 +9029,38 @@ const nodeId = () => z
|
|
|
8966
9029
|
z.string().regex(NODE_ID_PATTERN, NODE_ID_MESSAGE),
|
|
8967
9030
|
])
|
|
8968
9031
|
.transform(normalizeNodeId);
|
|
9032
|
+
/**
|
|
9033
|
+
* #1497 — the ONE node-id argument #845 never reached: panel_run's `to_node_id`.
|
|
9034
|
+
*
|
|
9035
|
+
* #845 made every panel tool accept back an id it had PRINTED, because the graph
|
|
9036
|
+
* readers hand out ids as strings (`summarizeNode` returns `id: node.id`, and on
|
|
9037
|
+
* the modern ComfyUI frontend that is `"11"`, not `11`). Twenty-seven node-id
|
|
9038
|
+
* arguments adopted `nodeId()`; this one kept `z.number().int()` and was missed,
|
|
9039
|
+
* so the documented round trip — take the id `panel_add_node` just returned, hand
|
|
9040
|
+
* it to panel_run — failed with a raw `expected number, received string`, exactly
|
|
9041
|
+
* as #845 described and #427 hit for panel_create_group. A canonical validator is
|
|
9042
|
+
* adopted per CALL SITE, and this call site never adopted it.
|
|
9043
|
+
*
|
|
9044
|
+
* NOT `nodeId()`, deliberately. That helper also admits the subgraph-QUALIFIED
|
|
9045
|
+
* shape (`"120:104"`, #1425) and passes it through as a string, which is right for
|
|
9046
|
+
* a write that addresses a node by key. A run-to-node target is not addressed by
|
|
9047
|
+
* key — it is an EXECUTION ROOT, resolved numerically the whole way down:
|
|
9048
|
+
* the panel's `findNodeInScopes` matches on `Number(id)`, its reply reports
|
|
9049
|
+
* `ran_to_node: Number(to_node_id)`, and two branches here (#772's stamp-race
|
|
9050
|
+
* re-issue and #468's run ticket) gate on `typeof args.to_node_id === "number"`.
|
|
9051
|
+
* Widening this argument to the qualified form would therefore turn a clear schema
|
|
9052
|
+
* refusal into a panel-side "node 120:104 was not found" about a node that plainly
|
|
9053
|
+
* exists, and silently drop the run ticket that correlates the completion. So the
|
|
9054
|
+
* spelling widens and the SHAPE does not: `11` and `"11"` both mean node 11, and
|
|
9055
|
+
* a qualified id is still refused — now by name, with a reason.
|
|
9056
|
+
*/
|
|
9057
|
+
const RUN_TO_NODE_ID_MESSAGE = 'a run-to-node target must be a plain integer node id — 42 or "42" both work. A subgraph-qualified id (e.g. "120:104") is not an execution root: pass the output node\'s own plain id, which is what panel_query_graph prints for it even when it is nested inside a subgraph';
|
|
9058
|
+
const runToNodeId = () => z
|
|
9059
|
+
.union([
|
|
9060
|
+
z.number().int(),
|
|
9061
|
+
z.string().regex(PLAIN_NODE_ID_PATTERN, RUN_TO_NODE_ID_MESSAGE),
|
|
9062
|
+
])
|
|
9063
|
+
.transform((v) => (typeof v === "number" ? v : Number.parseInt(v, 10)));
|
|
8969
9064
|
/**
|
|
8970
9065
|
* #845 — which `panel_canvas` arguments the chosen action actually consumes.
|
|
8971
9066
|
*
|
|
@@ -10238,11 +10333,9 @@ export function buildPanelToolDefs() {
|
|
|
10238
10333
|
.max(100)
|
|
10239
10334
|
.optional()
|
|
10240
10335
|
.describe("Times to queue (default 1)."),
|
|
10241
|
-
to_node_id:
|
|
10242
|
-
.number()
|
|
10243
|
-
.int()
|
|
10336
|
+
to_node_id: runToNodeId()
|
|
10244
10337
|
.optional()
|
|
10245
|
-
.describe("Output node id to render UP TO (partial execution). Omit to run the whole graph. Must be an OUTPUT node — one with is_output:true in panel_query_graph's detail rows. May be nested inside a subgraph (pass the node's own id)."),
|
|
10338
|
+
.describe("Output node id to render UP TO (partial execution). Omit to run the whole graph. Must be an OUTPUT node — one with is_output:true in panel_query_graph's detail rows. May be nested inside a subgraph (pass the node's own id). Takes the id EXACTLY as the readers print it: 11 and \"11\" both mean node 11 (#1497). A subgraph-qualified id (\"120:104\") is not an execution root and is refused."),
|
|
10246
10339
|
allow_duplicate: z
|
|
10247
10340
|
.boolean()
|
|
10248
10341
|
.optional()
|
|
@@ -10623,7 +10716,32 @@ export function buildPanelToolDefs() {
|
|
|
10623
10716
|
return appendToolResultText(res, lateAckNote);
|
|
10624
10717
|
return res;
|
|
10625
10718
|
}),
|
|
10626
|
-
def("panel_get_errors", "WHY IS THAT NODE RED / WHY DID THE RUN FAIL? The single error surface for the user's open tab: every errored node JOINED TO ITS CAUSE, which ComfyUI itself does not show — LiteGraph only paints a red outline and stores no reason, which is why users report \"red node, no error message\". Call this whenever the user mentions a red/highlighted/erroring node, a failed run, or \"required models are missing\" — instead of guessing from widget values. Each entry in `nodes[]` is the node's full detail summary plus `red_outline` and `reasons[]`, drawn from every source: `missing_model` (exact file, its models directory, the widget holding it, and a download URL when known), `missing_media` (a referenced input image/video that isn't on disk — the usual cause of a red LoadImage), `validation` (per-input errors from the last queue attempt: message, details, offending input), and `execution` (runtime failure with `exception_type`, e.g. PIL.UnidentifiedImageError). TWO THINGS THAT MAKE THIS ESSENTIAL: (1) missing model/media assets paint nodes red AS SOON AS THE WORKFLOW LOADS, long before any queue attempt — so the raw validation map is still EMPTY while the user is staring at red nodes; (2) a node that throws AT RUNTIME is never painted red at all, so it can't be spotted on the canvas — it appears here with red_outline:false. Also returns graph-level `missing_models`, `missing_media`, `missing_node_types` (or `missing_node_count`), plus the raw `node_errors` map and `last_execution_error` for reference. A ⚠️ GRAPH VALIDATION block is auto-injected at your turn start when this state changes; call this to re-check on demand (e.g. after you edit widgets/links). Read-only.", {},
|
|
10719
|
+
def("panel_get_errors", "WHY IS THAT NODE RED / WHY DID THE RUN FAIL? The single error surface for the user's open tab: every errored node JOINED TO ITS CAUSE, which ComfyUI itself does not show — LiteGraph only paints a red outline and stores no reason, which is why users report \"red node, no error message\". Call this whenever the user mentions a red/highlighted/erroring node, a failed run, or \"required models are missing\" — instead of guessing from widget values. Each entry in `nodes[]` is the node's full detail summary plus `red_outline` and `reasons[]`, drawn from every source: `missing_model` (exact file, its models directory, the widget holding it, and a download URL when known), `missing_media` (a referenced input image/video that isn't on disk — the usual cause of a red LoadImage), `validation` (per-input errors from the last queue attempt: message, details, offending input), and `execution` (runtime failure with `exception_type`, e.g. PIL.UnidentifiedImageError). TWO THINGS THAT MAKE THIS ESSENTIAL: (1) missing model/media assets paint nodes red AS SOON AS THE WORKFLOW LOADS, long before any queue attempt — so the raw validation map is still EMPTY while the user is staring at red nodes; (2) a node that throws AT RUNTIME is never painted red at all, so it can't be spotted on the canvas — it appears here with red_outline:false. Also returns graph-level `missing_models`, `missing_media`, `missing_node_types` (or `missing_node_count`), plus the raw `node_errors` map and `last_execution_error` for reference. A ⚠️ GRAPH VALIDATION block is auto-injected at your turn start when this state changes; call this to re-check on demand (e.g. after you edit widgets/links). Read-only.", {},
|
|
10720
|
+
// #1493 — graph_get_errors belongs to the #599 refresh-ack cohort above, and was the
|
|
10721
|
+
// one member of it left on the generic read default.
|
|
10722
|
+
//
|
|
10723
|
+
// Its frontend handler deliberately awaits a FORCED /object_info re-register before it
|
|
10724
|
+
// will trust a combo to clear a resolved missing-model candidate (#610), and it spends a
|
|
10725
|
+
// shared 18 000 ms elective budget across that refresh race, the /system_stats probe and the
|
|
10726
|
+
// /view media probes (GET_ERRORS_TOTAL_BUDGET_MS, panel web/js/lib/get-errors-budget.js).
|
|
10727
|
+
// That budget was sized to sit under BRIDGE_READ_DEFAULT_TIMEOUT_MS — which leaves 2 000 ms
|
|
10728
|
+
// for everything the call still has to do AFTER the waits: collectMissingAssets over the
|
|
10729
|
+
// whole graph, the per-node live combo scan, the subgraph walk, and serializing a reply big
|
|
10730
|
+
// enough that this tool ships two truncation riders for it.
|
|
10731
|
+
//
|
|
10732
|
+
// The reported sequence spends that budget exactly as designed — panel_refresh_nodes, three
|
|
10733
|
+
// promoted-widget writes on a model-loader subgraph, a save — and panel_get_errors was then
|
|
10734
|
+
// declared "backgrounded or frozen" at 20 000 ms by a tab that had just answered four
|
|
10735
|
+
// commands and did reply, late. That is the precise failure the shared budget exists to
|
|
10736
|
+
// prevent (#589): an agent left with NO error surface for a workflow whose nodes are red.
|
|
10737
|
+
//
|
|
10738
|
+
// The margin has to come from THIS side. The panel's 18 s bound must stay where it is — it
|
|
10739
|
+
// is what keeps a current panel safe in front of an OLDER orchestrator still using the 20 s
|
|
10740
|
+
// default — so the orchestrator widens its own wait to the same bounded budget its
|
|
10741
|
+
// refresh-before-validate siblings already use. This read is idempotent, so waiting longer
|
|
10742
|
+
// costs a slow reply and never a double-applied write; it stays BOUNDED (never Infinity), so
|
|
10743
|
+
// a genuinely frozen tab still fails, just at 30 s instead of 20 s.
|
|
10744
|
+
async (_args, ctx) => withTruncationHints(await ctx.call({ cmd: "graph_get_errors" }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS), [
|
|
10627
10745
|
{
|
|
10628
10746
|
flag: "truncated",
|
|
10629
10747
|
key: "truncation_hint",
|