comfyui-mcp 0.48.27 → 0.48.29
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/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/panel-tools.js +189 -18
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/graph-query.js +172 -13
- package/dist/services/graph-query.js.map +1 -1
- package/dist/services/panel-settings.js +11 -2
- package/dist/services/panel-settings.js.map +1 -1
- package/dist/services/ui-bridge.js +86 -7
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/tools/compact.js +30 -4
- package/dist/tools/compact.js.map +1 -1
- package/dist/tools/image-management.js +7 -1
- package/dist/tools/image-management.js.map +1 -1
- package/dist/tools/index.js +59 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
-
import {
|
|
6
|
+
import { collectToolCatalog, registerFullTools } from "./tools/index.js";
|
|
7
7
|
import { registerCompactTools } from "./tools/compact.js";
|
|
8
8
|
import { logger } from "./utils/logger.js";
|
|
9
9
|
import { JobWatcher } from "./services/job-watcher.js";
|
|
@@ -102,7 +102,11 @@ async function createConfiguredServer(toolMode = "full") {
|
|
|
102
102
|
logger.info(`Compact tool mode: ${catalog.tools.size} tools available via list_tools/describe_tool/call_tool`);
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
105
|
-
|
|
105
|
+
// Full direct surface PLUS the compact facade (list_tools/describe_tool/
|
|
106
|
+
// call_tool) as a stable reconnect escape hatch — see registerFullTools and
|
|
107
|
+
// issue #616. One atomic registration pass, completed before the transport
|
|
108
|
+
// connects below. Opt out of the facade with COMFYUI_MCP_NO_FACADE=1.
|
|
109
|
+
await registerFullTools(server);
|
|
106
110
|
}
|
|
107
111
|
server.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
108
112
|
resources: [],
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,kCAAkC,GACnC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,kCAAkC,GACnC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAiB,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D;;;;;GAKG;AACH,SAAS,iBAAiB;IACxB,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO;IAC3B,KAAK,oBAAoB,EAAE;SACxB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACnB,KAAK,WAAW;gBACd,MAAM,CAAC,IAAI,CACT,wFAAwF,EACxF,GAAG,CACJ,CAAC;gBACF,MAAM;YACR,KAAK,YAAY;gBACf,MAAM,CAAC,IAAI,CAAC,4CAA4C,EAAE,GAAG,CAAC,CAAC;gBAC/D,MAAM;YACR,KAAK,aAAa;gBAChB,MAAM,CAAC,IAAI,CACT,wEAAwE,EACxE,GAAG,CACJ,CAAC;gBACF,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,CAAC,KAAK,CAAC,iEAAiE,EAAE,GAAG,CAAC,CAAC;gBACrF,MAAM;YACR;gBACE,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB;IACvB,KAAK,kBAAkB,EAAE;SACtB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACnB,KAAK,SAAS;gBACZ,MAAM,CAAC,IAAI,CACT,oCAAoC,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,EAC3F,GAAG,CACJ,CAAC;gBACF,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,EAAE,aAAa,EAAE,EAAE,GAAG,CAAC,CAAC;gBACxE,MAAM;YACR,KAAK,YAAY;gBACf,MAAM,CAAC,KAAK,CAAC,6CAA6C,EAAE,GAAG,CAAC,CAAC;gBACjE,MAAM;YACR,KAAK,aAAa;gBAChB,MAAM,CAAC,IAAI,CAAC,2DAA2D,EAAE,GAAG,CAAC,CAAC;gBAC9E,MAAM;YACR,KAAK,kBAAkB;gBACrB,MAAM,CAAC,KAAK,CAAC,mDAAmD,EAAE,GAAG,CAAC,CAAC;gBACvE,MAAM;YACR;gBACE,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,WAAqB,MAAM;IAC/D,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,oEAAoE;QACpE,sEAAsE;QACtE,8DAA8D;QAC9D,sCAAsC;QACtC,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,EAAE;YACb,OAAO,EAAE,EAAE;SACZ;KACF,CACF,CAAC;IACF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,6EAA6E;QAC7E,oEAAoE;QACpE,sEAAsE;QACtE,0DAA0D;QAC1D,MAAM,OAAO,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAC3C,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CACT,sBAAsB,OAAO,CAAC,KAAK,CAAC,IAAI,yDAAyD,CAClG,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,yEAAyE;QACzE,4EAA4E;QAC5E,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACvE,SAAS,EAAE,EAAE;KACd,CAAC,CAAC,CAAC;IACJ,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAC7B,kCAAkC,EAClC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CACxC,CAAC;IACF,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACrE,OAAO,EAAE,EAAE;KACZ,CAAC,CAAC,CAAC;IAEJ,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,qBAAqB,CAClC,IAAY,EACZ,IAAY,EACZ,KAAa;IAEb,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAClE,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IAC3D,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC5C,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,CACV,yCAAyC,OAAO,IAAI;YAClD,4CAA4C;YAC5C,8GAA8G;YAC9G,iDAAiD,IAAI,IAAI,IAAI,2BAA2B,CAC3F,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,SAAS,MAAM,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAC5B;QACE,UAAU,EAAE;YACV,OAAO,EAAE;gBACP,GAAG,EAAE,MAAM;gBACX,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;aAChC;SACF;KACF,EACD,IAAI,EACJ,CAAC,CACF,CAAC;IAEF,4EAA4E;IAC5E,kCAAkC;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,EAAE;QACF,sEAAsE;QACtE,kDAAkD;QAClD,sEAAsE;QACtE,qBAAqB,MAAM,EAAE;QAC7B,qBAAqB,KAAK,EAAE;QAC5B,EAAE;QACF,iEAAiE;QACjE,qBAAqB;QACrB,eAAe,MAAM,EAAE;QACvB,2BAA2B,KAAK,gCAAgC,KAAK,GAAG;QACxE,EAAE;QACF,0CAA0C;QAC1C,OAAO;QACP,EAAE;QACF,sEAAsE;QACtE,sEAAsE;QACtE,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC;AAED;2EAC2E;AAC3E,SAAS,gBAAgB,CAAC,CAAS;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,QAAQ;YACzB,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC9D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,2EAA2E;IAC3E,yEAAyE;IACzE,gDAAgD;IAChD,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAC9E,MAAM,KAAK,GAAG,GAAG,CAAC,UAA0C,CAAC;QAC7D,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+BAA+B,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,0DAA0D;gBAC5G,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAoB,GAAG,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChE,2EAA2E,CAC9E,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;gBAC9B,KAAK;gBACL,OAAO,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS;gBACtE,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,MAAM,EAAE,GAAG,CAAC,WAAW;aACxB,CAAC,CAAC;YACH,MAAM,KAAK,GAAG;gBACZ,EAAE;gBACF,MAAM,CAAC,KAAK;oBACV,CAAC,CAAC,uCAAuC,MAAM,CAAC,UAAU,EAAE;oBAC5D,CAAC,CAAC,0BAA0B,MAAM,CAAC,UAAU,OAAO;gBACtD,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBACvD,EAAE;gBACF,aAAa;gBACb,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1C,EAAE;aACH,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAC5E,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,IAAI,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC1B,8EAA8E;QAC9E,0EAA0E;QAC1E,+EAA+E;QAC/E,6EAA6E;QAC7E,6EAA6E;QAC7E,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,GAAG,CAAC,cAAc;YAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,GAAG,GAAG,CAAC;QACtE,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnB,6EAA6E;YAC7E,8EAA8E;YAC9E,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACpD,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,QAAQ,MAAM,CAAC,CAAC;gBACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC,UAAU,CAAC;YACzC,wEAAwE;YACxE,qCAAqC;YACrC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,IAAI,CAAC;YACvE,gEAAgE;YAChE,qEAAqE;YACrE,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,cAAc,IAAI,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC7E,MAAM,UAAU,GAAG,YAAY;gBAC7B,CAAC,CAAC,0EAA0E;gBAC5E,CAAC,CAAC,kCAAkC,UAAU,EAAE,CAAC;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;gBACE,EAAE;gBACF,sEAAsE;gBACtE,+CAA+C;gBAC/C,sEAAsE;gBACtE,UAAU;gBACV,mBAAmB,GAAG,CAAC,UAAU,EAAE;gBACnC,YAAY;oBACV,CAAC,CAAC,iJAAiJ;oBACnJ,CAAC,CAAC,IAAI;gBACR,EAAE;gBACF,cAAc;gBACd,4CAA4C,GAAG,CAAC,UAAU,EAAE;gBAC5D,wDAAwD;gBACxD,qDAAqD;gBACrD,wEAAwE;gBACxE,EAAE;gBACF,yEAAyE;gBACzE,qEAAqE;gBACrE,yDAAyD;gBACzD,sEAAsE;gBACtE,EAAE;aACH;iBACE,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;iBACtC,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC;QACzE,MAAM,oBAAoB,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,MAAM,UAAU,CAAC,eAAe,EAAE,CAAC;IAEnC,IAAI,GAAG,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAC7B,4EAA4E;QAC5E,wEAAwE;QACxE,MAAM,KAAK,GACT,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAE1E,MAAM,eAAe,CAAC;YACpB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,KAAK;YACL,oBAAoB,EAAE,GAAG,CAAC,oBAAoB;YAC9C,YAAY,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC;SACzD,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,wCAAwC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC;QAChF,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CACT,uFAAuF,CACxF,CAAC;QACJ,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,MAAM,qBAAqB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAM,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1D,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IACrD,CAAC;IAED,wEAAwE;IACxE,iBAAiB,EAAE,CAAC;IACpB,mFAAmF;IACnF,gBAAgB,EAAE,CAAC;AACrB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,6EAA6E;IAC7E,4EAA4E;IAC5E,kDAAkD;IAClD,MAAM,CAAC,KAAK,CACV,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAClF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -30,7 +30,7 @@ import { fileURLToPath } from "node:url";
|
|
|
30
30
|
import { comfyuiFetch } from "../comfyui/fetch.js";
|
|
31
31
|
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
32
32
|
import { parse as parseYaml } from "yaml";
|
|
33
|
-
import { dispatchOutcomeOf, isPanelCmdUnsupportedError } from "../services/ui-bridge.js";
|
|
33
|
+
import { dispatchOutcomeOf, isPanelCmdUnsupportedError, isReplyTimeoutTagged, } from "../services/ui-bridge.js";
|
|
34
34
|
import { withWorkflowTarget, } from "../services/workflow-target-store.js";
|
|
35
35
|
import { addUserMcpServer, readUserMcpServers, removeUserMcpServer, setUserMcpServerSecret, } from "../services/user-mcp-config.js";
|
|
36
36
|
import { setComfyuiSecret, setAgentSecret, isAllowedAgentSecretKey } from "../services/panel-secrets.js";
|
|
@@ -43,12 +43,65 @@ import { restartComfyUI } from "../services/process-control.js";
|
|
|
43
43
|
import { isRemoteMode, isCloudMode, getBootLocalComfyUIBaseUrl, getComfyUIBaseUrl, } from "../config.js";
|
|
44
44
|
import { sliceWorkflow } from "../services/workflow-slicer.js";
|
|
45
45
|
import { validateA2UISpecServer } from "../services/a2ui-spec.js";
|
|
46
|
-
/** Treat these as an affirmative answer to
|
|
46
|
+
/** Treat these as an affirmative answer to a yes/no confirm card (destructive-op
|
|
47
|
+
* gate). Deliberately BROAD/lenient — a false "no" only SKIPS a destructive op, so
|
|
48
|
+
* erring toward "not yes" is safe. NEVER use this for the adult-consent gate: a
|
|
49
|
+
* false positive there would enable adult content without genuine consent. Use
|
|
50
|
+
* classifyConsentReply() for that. */
|
|
47
51
|
function isAffirmative(reply) {
|
|
48
52
|
if (typeof reply !== "string")
|
|
49
53
|
return false;
|
|
50
54
|
return /^(yes|allow|allowed|true|on|ok(ay)?|sure|agree|confirm|enable|i'?m? ?18|18\+?|adult)/i.test(reply.trim());
|
|
51
55
|
}
|
|
56
|
+
// The exact option labels the adult-consent card presents. Single source of truth
|
|
57
|
+
// shared by the card and its STRICT reply classifier so the gate matches on option
|
|
58
|
+
// IDENTITY, never on a loose heuristic.
|
|
59
|
+
const CONSENT_YES_LABEL = "Yes — I'm 18+ and it's legal in my region";
|
|
60
|
+
const CONSENT_NO_LABEL = "No — keep it SFW";
|
|
61
|
+
// STRICT, whole-string affirmatives/declines accepted from the card's free-text
|
|
62
|
+
// ("Other") box in addition to the exact option labels. Anchored ^…$ so ONLY an
|
|
63
|
+
// unambiguous token counts — unlike the broad isAffirmative() PREFIX match, a
|
|
64
|
+
// free-text reply like "adult content is illegal here", "On second thought, no",
|
|
65
|
+
// or "18 is the age but I decline" can NEVER be read as consent. Consent semantics
|
|
66
|
+
// (18+ AND legal) really live in the affirmative BUTTON; these bare tokens are the
|
|
67
|
+
// pragmatic fallback for a user who types instead of clicking.
|
|
68
|
+
const CONSENT_STRICT_YES_RE = /^(yes|y|yep|yeah|i agree|i consent|agreed?|confirm(ed)?|enable)$/i;
|
|
69
|
+
const CONSENT_STRICT_NO_RE = /^(no|n|nope|decline|declined|cancel|keep (it )?sfw|sfw)$/i;
|
|
70
|
+
/**
|
|
71
|
+
* STRICT classification of an adult-consent card reply — the ONLY gate that may
|
|
72
|
+
* turn adult mode ON. Returns:
|
|
73
|
+
* "grant" → the EXACT affirmative option, or a strict whole-string yes token
|
|
74
|
+
* → turn consent ON.
|
|
75
|
+
* "decline" → the EXACT decline option, or a strict whole-string no token
|
|
76
|
+
* → turn consent OFF.
|
|
77
|
+
* "unclear" → anything else (free text, ambiguous, non-string) → change NOTHING
|
|
78
|
+
* (never grants; never revokes a prior genuine grant).
|
|
79
|
+
* Never routes through isAffirmative() — a loose prefix match must not gate adult
|
|
80
|
+
* content.
|
|
81
|
+
*/
|
|
82
|
+
function classifyConsentReply(reply) {
|
|
83
|
+
if (typeof reply !== "string")
|
|
84
|
+
return "unclear";
|
|
85
|
+
// Exact OPTION IDENTITY: a button click echoes the card's label verbatim, so match
|
|
86
|
+
// it BYTE-FOR-BYTE — no .trim(). Trimming here would strip zero-width/BOM/line-
|
|
87
|
+
// separator characters (U+2028/U+2029/U+FEFF, \n) and let a near-label like
|
|
88
|
+
// " Yes — I'm 18+…" pass as the exact affirmative, defeating the exact-identity
|
|
89
|
+
// invariant this classifier exists to hold.
|
|
90
|
+
if (reply === CONSENT_YES_LABEL)
|
|
91
|
+
return "grant";
|
|
92
|
+
if (reply === CONSENT_NO_LABEL)
|
|
93
|
+
return "decline";
|
|
94
|
+
// Free-text ("Other" box) fallback: a small set of unambiguous whole-string tokens.
|
|
95
|
+
// This path is DELIBERATELY loose — a user who types " yes " is consenting — so a
|
|
96
|
+
// minimal .trim() of surrounding whitespace is applied ONLY here, never to the
|
|
97
|
+
// exact-label comparison above.
|
|
98
|
+
const t = reply.trim();
|
|
99
|
+
if (CONSENT_STRICT_YES_RE.test(t))
|
|
100
|
+
return "grant";
|
|
101
|
+
if (CONSENT_STRICT_NO_RE.test(t))
|
|
102
|
+
return "decline";
|
|
103
|
+
return "unclear";
|
|
104
|
+
}
|
|
52
105
|
function ok(value) {
|
|
53
106
|
return {
|
|
54
107
|
content: [
|
|
@@ -199,6 +252,10 @@ export const __panelToolsTestHooks = {
|
|
|
199
252
|
},
|
|
200
253
|
isRetrySafeCmd,
|
|
201
254
|
isTransientReconnectError,
|
|
255
|
+
// Adult-consent classifier + its exact card labels (#390 gate tests).
|
|
256
|
+
classifyConsentReply,
|
|
257
|
+
CONSENT_YES_LABEL,
|
|
258
|
+
CONSENT_NO_LABEL,
|
|
202
259
|
// #384 live-canvas capture fallback (defined later in the module).
|
|
203
260
|
reconstructUiFromState: (reply) => reconstructUiFromState(reply),
|
|
204
261
|
resolveWorkflowInput: (args, ctx, allowStateFallback = true) => resolveWorkflowInput(args, ctx, allowStateFallback),
|
|
@@ -222,6 +279,17 @@ function sleep(ms) {
|
|
|
222
279
|
// settle. Mutating graph edits (add_node/connect/set_widget/…) are deliberately
|
|
223
280
|
// EXCLUDED — re-issuing them could double-apply — so they keep surfacing the
|
|
224
281
|
// bridge's honest OUTCOME-UNKNOWN error.
|
|
282
|
+
// #599: commands whose FRONTEND handler intentionally awaits a fresh /object_info
|
|
283
|
+
// re-register before it can reply — the refresh-before-validate path in
|
|
284
|
+
// graph_set_widget (#338/#458) and graph_add_node (#289/#458), and the explicit
|
|
285
|
+
// forced node-def refresh in refresh_nodes (#608). On a large install with many
|
|
286
|
+
// custom-node packs a legitimate /object_info fetch can take longer than the
|
|
287
|
+
// bridge's 6000 ms DEFAULT ack window, so the panel replies late and the tool
|
|
288
|
+
// returns a FALSE "tab did not reply" timeout even though the write is valid and
|
|
289
|
+
// in progress. Give these a larger BOUNDED ack budget so a slow-but-valid refresh
|
|
290
|
+
// is not mistaken for a dead tab — still capped (never Infinity) so a genuinely
|
|
291
|
+
// frozen/backgrounded tab fails in bounded time instead of hanging forever.
|
|
292
|
+
const OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS = 30_000;
|
|
225
293
|
const RETRY_SAFE_CMDS = new Set([
|
|
226
294
|
// Idempotent reads (mirror UiBridge.READONLY_CMDS + list/status probes).
|
|
227
295
|
"graph_serialize",
|
|
@@ -237,6 +305,10 @@ const RETRY_SAFE_CMDS = new Set([
|
|
|
237
305
|
"node_queue_status",
|
|
238
306
|
// Idempotent full-replace UI state — re-sending the same list is a no-op (#481).
|
|
239
307
|
"set_todo",
|
|
308
|
+
// #608: a forced /object_info re-register + combo refresh. Non-destructive and
|
|
309
|
+
// idempotent (re-running just re-fetches the current defs), so a dropped
|
|
310
|
+
// transport can safely re-issue it once.
|
|
311
|
+
"refresh_nodes",
|
|
240
312
|
]);
|
|
241
313
|
/** A command whose result is unchanged by being re-issued after a reconnect —
|
|
242
314
|
* so it is safe to transparently retry once when the transport dropped. */
|
|
@@ -1991,10 +2063,37 @@ function getAskTiming() {
|
|
|
1991
2063
|
}
|
|
1992
2064
|
/** True when an error is the bridge's reply-TIMEOUT for a card (the tab never
|
|
1993
2065
|
* replied within the window), NOT a genuine transport/command error. Only a
|
|
1994
|
-
* timeout warrants polling the late-reply buffer for a slow-but-valid answer.
|
|
2066
|
+
* timeout warrants polling the late-reply buffer for a slow-but-valid answer.
|
|
2067
|
+
*
|
|
2068
|
+
* Anchored (`^…$`) on the bridge's CANONICAL no-reply message — the WHOLE message
|
|
2069
|
+
* ui-bridge `dispatch()` emits: `Panel tab <id> did not reply to "<cmd>" within <N>
|
|
2070
|
+
* ms — the ComfyUI tab may be backgrounded or frozen`. Whole-message anchoring is
|
|
2071
|
+
* deliberate: a genuine transport error that merely WRAPS or embeds the phrase —
|
|
2072
|
+
* e.g. `relay failed: Panel tab abcd did not reply to "ask_user" within 500 ms;
|
|
2073
|
+
* reconnecting`, or `upstream service did not reply to us within 500 ms` — must NOT
|
|
2074
|
+
* be mis-handled as a recoverable card-reply timeout. It mirrors the same canonical
|
|
2075
|
+
* distinction the panel_open_workflow ack-timeout path draws. Crucial for the
|
|
2076
|
+
* consent gate: a real transport failure must reach fail(), never be quietly
|
|
2077
|
+
* reported as an unchanged-state success. */
|
|
1995
2078
|
function isReplyTimeoutError(err) {
|
|
2079
|
+
// AUTHORITATIVE: the bridge tags every reply-timeout with a typed marker
|
|
2080
|
+
// (markReplyTimeout). Keying on it makes the recoverable-timeout decision robust to
|
|
2081
|
+
// ANY tab_id — including one containing spaces, which the text form below can't
|
|
2082
|
+
// segment (ui-bridge accepts an arbitrary-string tab_id).
|
|
2083
|
+
if (isReplyTimeoutTagged(err))
|
|
2084
|
+
return true;
|
|
2085
|
+
// FALLBACK (test-injected plain errors / any untagged error): an EXACT whole-message
|
|
2086
|
+
// match of the bridge's canonical no-reply. Literal single space before "ms" (the
|
|
2087
|
+
// bridge always emits "within <N> ms"), no `.trim()`, `^` at true start (no /m flag)
|
|
2088
|
+
// and a HARD end-of-input `(?![\s\S])` — NOT `$`, which in JS also matches just
|
|
2089
|
+
// before a final "\n" and would let `canonical + "\n"` slip through. The tab-id
|
|
2090
|
+
// segment is `.+?` (lazy, bounded by the fixed ` did not reply to "` that follows —
|
|
2091
|
+
// an ≤8-char id slice can't contain that 19-char delimiter) so a spaced tab_id still
|
|
2092
|
+
// matches here too. So a prefixed/suffixed wrapper, whitespace/newline-wrapped text,
|
|
2093
|
+
// or noncanonical spacing ("within 500ms") does NOT match and is surfaced as a real
|
|
2094
|
+
// error. Case-sensitive: the message is a fixed literal template.
|
|
1996
2095
|
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
1997
|
-
return
|
|
2096
|
+
return /^Panel tab .+? did not reply to "[^"]*" within \d+ ms — the ComfyUI tab may be backgrounded or frozen(?![\s\S])/.test(msg);
|
|
1998
2097
|
}
|
|
1999
2098
|
/**
|
|
2000
2099
|
* Actionable error string when THIS session has no interactive surface able to
|
|
@@ -2223,7 +2322,12 @@ export function buildPanelToolDefs() {
|
|
|
2223
2322
|
.optional()
|
|
2224
2323
|
.describe("Canvas [x, y] (two numbers). Auto-placed beside existing nodes when omitted."),
|
|
2225
2324
|
title: z.string().optional().describe("Optional custom node title."),
|
|
2226
|
-
}, async (args, ctx) =>
|
|
2325
|
+
}, async (args, ctx) =>
|
|
2326
|
+
// #599: the frontend gates the add on a FRESH /object_info (assertAddNode-
|
|
2327
|
+
// ResolvableRefreshing) so an uninstalled class can't be added as a
|
|
2328
|
+
// placeholder — that fetch can outlast the 6000 ms default on a large
|
|
2329
|
+
// install. Give it the bounded refresh ack budget.
|
|
2330
|
+
ctx.call({ cmd: "graph_add_node", class_type: args.class_type, pos: args.pos, title: args.title }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS)),
|
|
2227
2331
|
def("panel_remove_node", "Remove a node (and its connections) from the user's open graph by id. Undoable with Ctrl+Z.", { node_id: z.number().int().describe("Node id from panel_graph_outline / panel_query_graph.") }, async (args, ctx) => ctx.call({ cmd: "graph_remove_node", node_id: args.node_id })),
|
|
2228
2332
|
def("panel_clear", "Remove EVERY node from the user's open graph — only for an explicit 'clear/reset the canvas'. Just CALL THIS DIRECTLY when they ask to clear: the tool itself pops a confirm card and only wipes on a yes (don't ask separately first). The wipe is a single Ctrl+Z undo. NEVER use this for a 'new workflow' — that's panel_new_workflow (a new tab, leaves this graph intact).", {}, async (_args, ctx) => {
|
|
2229
2333
|
const decision = await ctx.confirm("Clear the canvas? This removes every node from the open workflow. (One Ctrl+Z undoes it.)", "Clear canvas");
|
|
@@ -2453,7 +2557,12 @@ export function buildPanelToolDefs() {
|
|
|
2453
2557
|
if (value === undefined) {
|
|
2454
2558
|
return fail("panel_set_widget needs a `value`. To set an empty string, pass `clear: true` (some clients drop an empty-string `value`).");
|
|
2455
2559
|
}
|
|
2456
|
-
|
|
2560
|
+
// #599: the frontend runs refresh-before-validate here (pulls a fresh
|
|
2561
|
+
// /object_info so a just-staged/-downloaded/-installed value is accepted on
|
|
2562
|
+
// a single revalidation, #338/#458) — that authoritative fetch can outlast
|
|
2563
|
+
// the 6000 ms default ack on a large install and return a FALSE timeout.
|
|
2564
|
+
// Give the guarded write the bounded refresh ack budget.
|
|
2565
|
+
return ctx.call({ cmd: "graph_set_widget", node_id: args.node_id, widget: args.widget, value }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS);
|
|
2457
2566
|
}),
|
|
2458
2567
|
def("panel_move_node", "Move a node to a new canvas position [x, y] in the user's open graph. Undoable.", {
|
|
2459
2568
|
node_id: z.number().int().describe("Node id from panel_graph_outline / panel_query_graph."),
|
|
@@ -2585,6 +2694,11 @@ export function buildPanelToolDefs() {
|
|
|
2585
2694
|
return res;
|
|
2586
2695
|
}),
|
|
2587
2696
|
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.", {}, async (_args, ctx) => ctx.call({ cmd: "graph_get_errors" })),
|
|
2697
|
+
def("panel_refresh_nodes", "Re-pull the live ComfyUI server's /object_info and rebuild every combo/loader option list in the user's open tab, so an asset that appeared server-side AFTER the tab loaded becomes SELECTABLE without a manual reload (the 'press R' step) or a restart. Use this right after stage_output_as_input (chaining a stage's output into a LoadImage / VHS_LoadVideo / LoadAudio loader — the returned filename won't be in the loader's dropdown until you refresh), after downloading a model / LoRA / VAE (a freshly downloaded file is otherwise 'not a valid option' in its loader), or after installing a node pack. Then panel_set_widget / panel_add_node will accept the new value. Non-destructive: it only re-registers node defs and refreshes combo option lists — it does NOT change your graph and is undo-neutral. Idempotent (safe to call repeatedly). Returns whether the refresh authoritatively fetched fresh defs.", {}, async (_args, ctx) =>
|
|
2698
|
+
// Same bounded ack budget as the refresh-before-validate writes (#599): a
|
|
2699
|
+
// fresh /object_info on a large install routinely exceeds the 6000 ms
|
|
2700
|
+
// default, and this command's WHOLE purpose is to await that fetch.
|
|
2701
|
+
ctx.call({ cmd: "refresh_nodes" }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS)),
|
|
2588
2702
|
def("panel_reload", "Soft-reload yourself to pick up code changes WITHOUT restarting ComfyUI — your chat session resumes automatically and you'll be nudged to continue. Use scope 'orchestrator' (default) after backend/orchestrator code changed (new tools, system prompt, services); use scope 'frontend' after the panel UI (web JS/CSS) changed. This ENDS the current turn — your tools/prompt are reloaded and you continue fresh. For custom-node or model changes that need a full ComfyUI restart, use panel_restart_comfyui instead. Only call this when code has actually changed and needs to take effect now.", {
|
|
2589
2703
|
scope: z
|
|
2590
2704
|
.enum(["orchestrator", "frontend"])
|
|
@@ -2607,9 +2721,14 @@ export function buildPanelToolDefs() {
|
|
|
2607
2721
|
// Count only INTERACTIVE (canvas-owning) tabs for the ambiguity guard: one
|
|
2608
2722
|
// desktop canvas alongside headless viewers is NOT ambiguous — rebindToActiveTab
|
|
2609
2723
|
// binds the sole desktop tab. Only 2+ real canvas tabs are unpickable here.
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2724
|
+
// Call isHeadless THROUGH the bridge (not an extracted reference): it is a
|
|
2725
|
+
// plain method that reads `this.conns` — invoking a detached `const headless
|
|
2726
|
+
// = ctx.bridge.isHeadless` loses `this`, so `this.conns` throws "Cannot read
|
|
2727
|
+
// properties of undefined (reading 'conns')" and panel_reload fails outright
|
|
2728
|
+
// (panel #478). Mirror the bound `isHeadlessTab` helper used elsewhere here.
|
|
2729
|
+
const isHeadlessTab = (id) => typeof ctx.bridge.isHeadless === "function" && ctx.bridge.isHeadless(id);
|
|
2730
|
+
const interactive = Array.isArray(live)
|
|
2731
|
+
? live.filter((t) => !isHeadlessTab(t.tab_id))
|
|
2613
2732
|
: live;
|
|
2614
2733
|
if (orphaned && Array.isArray(interactive) && interactive.length > 1) {
|
|
2615
2734
|
return fail("This session's ComfyUI tab was replaced and multiple tabs are now open — " +
|
|
@@ -2767,16 +2886,64 @@ export function buildPanelToolDefs() {
|
|
|
2767
2886
|
// that lost its live binding (reconnect/reload/workflow-switch) throws a
|
|
2768
2887
|
// false `no connected tab` here even though graph tools just worked.
|
|
2769
2888
|
ctx.ensureReachable?.();
|
|
2770
|
-
|
|
2889
|
+
// #390: the enclosing MCP `tools/call` is killed at ~300s. A hardcoded 300s
|
|
2890
|
+
// card wait raced that budget with ZERO margin, so an idle user who never
|
|
2891
|
+
// clicked blew the whole tool call as a transport timeout ("timed out
|
|
2892
|
+
// awaiting tools/call after 300s") instead of resolving cleanly. CLAMP the
|
|
2893
|
+
// card deadline under the budget (the same getAskTiming() ceiling panel_ask
|
|
2894
|
+
// and confirm use) with a stable ask_id, and on a reply-timeout poll the
|
|
2895
|
+
// bridge's late-reply buffer for a bounded grace so a slow-but-VALID pick is
|
|
2896
|
+
// still honored. If the user is simply away, resolve cleanly with
|
|
2897
|
+
// { nsfw_allowed:false, timed_out:true } WITHOUT touching consent — a timeout
|
|
2898
|
+
// NEVER grants the gate; the persistent decision is unchanged and can be read
|
|
2899
|
+
// back with panel_get_content_mode, or the user re-asked, on the next turn.
|
|
2900
|
+
const timing = getAskTiming();
|
|
2901
|
+
const askId = randomUUID();
|
|
2902
|
+
const askCard = {
|
|
2771
2903
|
cmd: "ask_user",
|
|
2904
|
+
ask_id: askId,
|
|
2772
2905
|
question,
|
|
2773
2906
|
header: "18+ consent",
|
|
2774
2907
|
options: [
|
|
2775
|
-
{ label:
|
|
2776
|
-
{ label:
|
|
2908
|
+
{ label: CONSENT_YES_LABEL, description: "Enable adult content for this session" },
|
|
2909
|
+
{ label: CONSENT_NO_LABEL, description: "Stay in safe-for-work mode" },
|
|
2777
2910
|
],
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2911
|
+
};
|
|
2912
|
+
let reply;
|
|
2913
|
+
let timedOut = false;
|
|
2914
|
+
try {
|
|
2915
|
+
reply = await ctx.bridge.send(askCard, { tabId: ctx.tabId, timeoutMs: timing.deadlineMs });
|
|
2916
|
+
}
|
|
2917
|
+
catch (err) {
|
|
2918
|
+
// Only a card-reply TIMEOUT is recoverable here: poll the late buffer for a
|
|
2919
|
+
// slow-but-valid answer. Any other error (no panel, transport failure)
|
|
2920
|
+
// propagates to the outer catch → fail(), exactly as before.
|
|
2921
|
+
if (!isReplyTimeoutError(err))
|
|
2922
|
+
throw err;
|
|
2923
|
+
timedOut = true;
|
|
2924
|
+
reply = await pollLateAskReply(ctx.bridge, askId, timing);
|
|
2925
|
+
}
|
|
2926
|
+
// STRICT gate: adult mode turns ON only on the EXACT affirmative option (or a
|
|
2927
|
+
// strict whole-string yes token) — never via the loose isAffirmative() prefix
|
|
2928
|
+
// match, so a free-text late reply like "adult content is illegal here" or "On
|
|
2929
|
+
// second thought, no" can't grant. An UNCLEAR reply (incl. a no-answer timeout,
|
|
2930
|
+
// where reply is undefined) changes NOTHING: it neither grants nor revokes a
|
|
2931
|
+
// prior genuine grant. Only an EXACT decline explicitly reverts to SFW.
|
|
2932
|
+
const decision = classifyConsentReply(reply);
|
|
2933
|
+
if (decision === "unclear") {
|
|
2934
|
+
const current = getNsfwConsent();
|
|
2935
|
+
return ok({
|
|
2936
|
+
nsfw_allowed: current.allowed,
|
|
2937
|
+
decided_at: current.decidedAt ?? null,
|
|
2938
|
+
...(timedOut ? { timed_out: true } : {}),
|
|
2939
|
+
note: timedOut
|
|
2940
|
+
? "The 18+ consent card wasn't answered in time, so nothing changed — adult content stays gated by the existing state. " +
|
|
2941
|
+
"Ask again when the user is back, or read the current state with panel_get_content_mode."
|
|
2942
|
+
: "The 18+ consent card wasn't answered with a clear yes/no, so nothing changed — the existing content-mode state is unchanged. " +
|
|
2943
|
+
"Ask again with a clear choice, or read the current state with panel_get_content_mode.",
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
const allowed = decision === "grant";
|
|
2780
2947
|
const state = setNsfwConsent(allowed);
|
|
2781
2948
|
return ok({
|
|
2782
2949
|
nsfw_allowed: state.allowed,
|
|
@@ -3077,9 +3244,11 @@ export function buildPanelToolDefs() {
|
|
|
3077
3244
|
if (Array.isArray(live)) {
|
|
3078
3245
|
// Count only INTERACTIVE (canvas-owning) tabs: a headless-only reconnect is
|
|
3079
3246
|
// NOT a usable graph binding, so it defers (binds once a real canvas tab
|
|
3080
|
-
// connects) rather than failing as if a tab were pickable.
|
|
3081
|
-
|
|
3082
|
-
|
|
3247
|
+
// connects) rather than failing as if a tab were pickable. Call isHeadless
|
|
3248
|
+
// THROUGH the bridge (it reads `this.conns`) — a detached reference would
|
|
3249
|
+
// lose `this` and throw "reading 'conns'" (the same #478 unbound-method bug).
|
|
3250
|
+
const isHeadlessTab = (id) => typeof ctx.bridge.isHeadless === "function" && ctx.bridge.isHeadless(id);
|
|
3251
|
+
const interactive = live.filter((t) => !isHeadlessTab(t.tab_id));
|
|
3083
3252
|
noTabsConnected = interactive.length === 0;
|
|
3084
3253
|
}
|
|
3085
3254
|
else {
|
|
@@ -3096,7 +3265,9 @@ export function buildPanelToolDefs() {
|
|
|
3096
3265
|
deferredBind = true;
|
|
3097
3266
|
rebindNote =
|
|
3098
3267
|
" No panel tab is connected yet — cleared the stale binding; this session will " +
|
|
3099
|
-
"follow (bind onto) the tab as soon as one reconnects. Retry your graph tool in a
|
|
3268
|
+
"follow (bind onto) the tab as soon as one reconnects. Retry your graph tool in a " +
|
|
3269
|
+
"moment; if nothing reconnects shortly, ask the user to refresh (reload) the ComfyUI " +
|
|
3270
|
+
"browser tab, which reconnects the Agent panel after a restart (not an install issue).";
|
|
3100
3271
|
}
|
|
3101
3272
|
// Detect the rebind regardless of whether awaitReachable or rebindToActiveTab
|
|
3102
3273
|
// performed it (either mutates ctx.tabId), so the note is never swallowed.
|