comfyui-mcp 0.48.28 → 0.48.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.
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 { registerAllTools, collectToolCatalog } from "./tools/index.js";
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
- await registerAllTools(server);
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,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxE,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,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACjC,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -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 the adult-content consent card. */
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),
@@ -2006,10 +2063,37 @@ function getAskTiming() {
2006
2063
  }
2007
2064
  /** True when an error is the bridge's reply-TIMEOUT for a card (the tab never
2008
2065
  * replied within the window), NOT a genuine transport/command error. Only a
2009
- * 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. */
2010
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.
2011
2095
  const msg = err instanceof Error ? err.message : String(err ?? "");
2012
- return /did not reply to .* within \d+\s*ms|backgrounded or frozen/i.test(msg);
2096
+ return /^Panel tab .+? did not reply to "[^"]*" within \d+ ms — the ComfyUI tab may be backgrounded or frozen(?![\s\S])/.test(msg);
2013
2097
  }
2014
2098
  /**
2015
2099
  * Actionable error string when THIS session has no interactive surface able to
@@ -2042,6 +2126,109 @@ function askSurfaceError(ctx) {
2042
2126
  "the user directly in plain chat text, or invoke panel_ask from an interactive " +
2043
2127
  "ComfyUI browser tab (not nested inside an exec/headless call).");
2044
2128
  }
2129
+ /**
2130
+ * Route a canvas-requiring panel UI command (set_todo, open_civitai) onto the live
2131
+ * DESKTOP canvas tab when THIS session's bound tab is a headless (mobile/remote)
2132
+ * client (#624). The mobile / remote pseudo-panel accepts ONLY show_media and
2133
+ * rejects every other ServerCommand with the (client-authored) string "mobile
2134
+ * client has no open canvas" — a misleading, mobile-specific error even when a real
2135
+ * desktop ComfyUI canvas is open in the same session. These commands render on the
2136
+ * desktop panel surface (the footer TODO tray, the in-panel CivitAI browser), so
2137
+ * when the bound tab is canvas-less we resolve the SAME interactive desktop tab the
2138
+ * graph/workflow tools bind to instead of blasting the command at the headless
2139
+ * client. This is the desktop fall-through; genuine tab-mirror sessions are bound to
2140
+ * the desktop tab already (non-headless) and are therefore untouched.
2141
+ *
2142
+ * Returns:
2143
+ * - `{ tabId }` → REDIRECT the dispatch onto the interactive (canvas-owning)
2144
+ * desktop tab (the sole one, else the last-active one when it is itself a canvas);
2145
+ * - `{ error }` → the ONLY connected client is canvas-less, so fail with the REAL
2146
+ * reason (named honestly) rather than surfacing the raw mobile "no open canvas";
2147
+ * - `null` → the bound tab is already a reachable canvas, the choice among 2+
2148
+ * desktop tabs is ambiguous, or the bridge can't enumerate headlessness — in every
2149
+ * such case the normal `ctx.call` path (unchanged) runs.
2150
+ */
2151
+ function desktopCanvasRedirect(ctx, label) {
2152
+ const b = ctx.bridge;
2153
+ // Older / lightweight bridges can't classify tabs — leave routing exactly as-is.
2154
+ if (typeof b.isHeadless !== "function" || typeof b.tabs !== "function")
2155
+ return null;
2156
+ // Only intervene when THIS session is bound to a canvas-less (mobile/remote) client.
2157
+ // A desktop-bound session — healthy, or merely orphaned by a reconnect — is left to
2158
+ // the normal ctx.call path and its reconnect/rebind machinery untouched.
2159
+ if (!b.isHeadless(ctx.tabId))
2160
+ return null;
2161
+ // Bound to a headless client: find the interactive (canvas-owning) DESKTOP tabs, the
2162
+ // SAME filter rebindToActiveTab/ensureReachable use for graph/workflow bindings.
2163
+ const live = b.tabs();
2164
+ const interactive = Array.isArray(live)
2165
+ ? live.filter((t) => !b.isHeadless(t.tab_id))
2166
+ : [];
2167
+ if (interactive.length === 1)
2168
+ return { tabId: interactive[0].tab_id };
2169
+ if (interactive.length === 0) {
2170
+ return {
2171
+ error: `${label} needs an open ComfyUI desktop panel canvas to render, but this session ` +
2172
+ `is attached to a canvas-less client (a mobile/remote viewer or a headless run) ` +
2173
+ `and no desktop ComfyUI panel tab is connected. Open the ComfyUI browser tab with ` +
2174
+ `the comfyui-mcp-panel pack installed, then retry.`,
2175
+ };
2176
+ }
2177
+ // 2+ interactive desktop tabs: prefer the last-active one when it is itself a canvas.
2178
+ // The bound tab is headless, so we must NOT fall through to ctx.call here — that would
2179
+ // dispatch right back at the canvas-less client and reproduce "mobile client has no
2180
+ // open canvas". When we can't single out one canvas tab, fail with the REAL reason.
2181
+ if (typeof b.resolveActiveTabId === "function") {
2182
+ try {
2183
+ const active = b.resolveActiveTabId();
2184
+ if (active && !b.isHeadless(active))
2185
+ return { tabId: active };
2186
+ }
2187
+ catch {
2188
+ /* ambiguous active-tab resolution — fall through to the ambiguity error below */
2189
+ }
2190
+ }
2191
+ return {
2192
+ error: `${label} needs an open ComfyUI desktop panel canvas, but this session is bound to ` +
2193
+ `a canvas-less (mobile/remote) client and multiple desktop tabs are open — it can't ` +
2194
+ `pick one automatically. Switch to the ComfyUI tab you want, rebind with ` +
2195
+ `panel_set_workflow_target({mode:"current"}), then retry.`,
2196
+ };
2197
+ }
2198
+ /** Dispatch a command to a SPECIFIC tab id (the #624 desktop-canvas redirect target)
2199
+ * and wrap the reply in the SAME ok()/fail() envelope ctx.call uses. The redirect
2200
+ * target came straight from a live `bridge.tabs()` enumeration, so it is already
2201
+ * reachable — this is a thin send that does NOT mutate ctx.tabId (the session's own
2202
+ * binding is left intact). For a retry-safe (idempotent full-replace) command it
2203
+ * mirrors ctx.call's single post-drop retry: on a transient reconnect error it settles
2204
+ * briefly, RE-RESOLVES the desktop tab (it may have reconnected under a new id), and
2205
+ * re-sends once before surfacing the error. `reResolve` returns the fresh desktop tab
2206
+ * id (or undefined to reuse `tabId`). */
2207
+ async function dispatchToTab(ctx, tabId, cmd, timeoutMs, reResolve) {
2208
+ try {
2209
+ return ok(await ctx.bridge.send(cmd, { tabId, timeoutMs }));
2210
+ }
2211
+ catch (err) {
2212
+ if (isRetrySafeCmd(cmd) && isTransientReconnectError(err)) {
2213
+ try {
2214
+ await sleep(retrySettleMs());
2215
+ const fresh = reResolve?.() ?? tabId;
2216
+ return ok(await ctx.bridge.send(cmd, { tabId: fresh, timeoutMs }));
2217
+ }
2218
+ catch (err2) {
2219
+ return fail(err2);
2220
+ }
2221
+ }
2222
+ return fail(err);
2223
+ }
2224
+ }
2225
+ /** Re-resolve the desktop redirect target after a transient drop — returns the fresh
2226
+ * interactive desktop tab id, or undefined when it can't be re-picked (so the caller
2227
+ * falls back to the original id for its single retry). */
2228
+ function reResolveDesktopTab(ctx, label) {
2229
+ const r = desktopCanvasRedirect(ctx, label);
2230
+ return r?.tabId;
2231
+ }
2045
2232
  /** Poll the bridge's late-reply buffer for a validated ask answer that arrived
2046
2233
  * after the card-reply timeout, up to the grace budget. undefined if none. */
2047
2234
  async function pollLateAskReply(bridge, askId, timing) {
@@ -2802,16 +2989,64 @@ export function buildPanelToolDefs() {
2802
2989
  // that lost its live binding (reconnect/reload/workflow-switch) throws a
2803
2990
  // false `no connected tab` here even though graph tools just worked.
2804
2991
  ctx.ensureReachable?.();
2805
- const reply = await ctx.bridge.send({
2992
+ // #390: the enclosing MCP `tools/call` is killed at ~300s. A hardcoded 300s
2993
+ // card wait raced that budget with ZERO margin, so an idle user who never
2994
+ // clicked blew the whole tool call as a transport timeout ("timed out
2995
+ // awaiting tools/call after 300s") instead of resolving cleanly. CLAMP the
2996
+ // card deadline under the budget (the same getAskTiming() ceiling panel_ask
2997
+ // and confirm use) with a stable ask_id, and on a reply-timeout poll the
2998
+ // bridge's late-reply buffer for a bounded grace so a slow-but-VALID pick is
2999
+ // still honored. If the user is simply away, resolve cleanly with
3000
+ // { nsfw_allowed:false, timed_out:true } WITHOUT touching consent — a timeout
3001
+ // NEVER grants the gate; the persistent decision is unchanged and can be read
3002
+ // back with panel_get_content_mode, or the user re-asked, on the next turn.
3003
+ const timing = getAskTiming();
3004
+ const askId = randomUUID();
3005
+ const askCard = {
2806
3006
  cmd: "ask_user",
3007
+ ask_id: askId,
2807
3008
  question,
2808
3009
  header: "18+ consent",
2809
3010
  options: [
2810
- { label: "Yes — I'm 18+ and it's legal in my region", description: "Enable adult content for this session" },
2811
- { label: "No — keep it SFW", description: "Stay in safe-for-work mode" },
3011
+ { label: CONSENT_YES_LABEL, description: "Enable adult content for this session" },
3012
+ { label: CONSENT_NO_LABEL, description: "Stay in safe-for-work mode" },
2812
3013
  ],
2813
- }, { tabId: ctx.tabId, timeoutMs: 300000 });
2814
- const allowed = isAffirmative(reply);
3014
+ };
3015
+ let reply;
3016
+ let timedOut = false;
3017
+ try {
3018
+ reply = await ctx.bridge.send(askCard, { tabId: ctx.tabId, timeoutMs: timing.deadlineMs });
3019
+ }
3020
+ catch (err) {
3021
+ // Only a card-reply TIMEOUT is recoverable here: poll the late buffer for a
3022
+ // slow-but-valid answer. Any other error (no panel, transport failure)
3023
+ // propagates to the outer catch → fail(), exactly as before.
3024
+ if (!isReplyTimeoutError(err))
3025
+ throw err;
3026
+ timedOut = true;
3027
+ reply = await pollLateAskReply(ctx.bridge, askId, timing);
3028
+ }
3029
+ // STRICT gate: adult mode turns ON only on the EXACT affirmative option (or a
3030
+ // strict whole-string yes token) — never via the loose isAffirmative() prefix
3031
+ // match, so a free-text late reply like "adult content is illegal here" or "On
3032
+ // second thought, no" can't grant. An UNCLEAR reply (incl. a no-answer timeout,
3033
+ // where reply is undefined) changes NOTHING: it neither grants nor revokes a
3034
+ // prior genuine grant. Only an EXACT decline explicitly reverts to SFW.
3035
+ const decision = classifyConsentReply(reply);
3036
+ if (decision === "unclear") {
3037
+ const current = getNsfwConsent();
3038
+ return ok({
3039
+ nsfw_allowed: current.allowed,
3040
+ decided_at: current.decidedAt ?? null,
3041
+ ...(timedOut ? { timed_out: true } : {}),
3042
+ note: timedOut
3043
+ ? "The 18+ consent card wasn't answered in time, so nothing changed — adult content stays gated by the existing state. " +
3044
+ "Ask again when the user is back, or read the current state with panel_get_content_mode."
3045
+ : "The 18+ consent card wasn't answered with a clear yes/no, so nothing changed — the existing content-mode state is unchanged. " +
3046
+ "Ask again with a clear choice, or read the current state with panel_get_content_mode.",
3047
+ });
3048
+ }
3049
+ const allowed = decision === "grant";
2815
3050
  const state = setNsfwConsent(allowed);
2816
3051
  return ok({
2817
3052
  nsfw_allowed: state.allowed,
@@ -2849,7 +3084,19 @@ export function buildPanelToolDefs() {
2849
3084
  // momentarily backgrounded. set_todo is a non-destructive, idempotent full-
2850
3085
  // replace UI write (already in RETRY_SAFE_CMDS), so give it the same sane 15s
2851
3086
  // bound as the other UI-state writes (workflow_save) instead of a tight 5s.
2852
- async (args, ctx) => ctx.call({ cmd: "set_todo", items: args.items }, 15000)),
3087
+ async (args, ctx) => {
3088
+ // #624: the footer TODO tray is a DESKTOP panel surface. When this session's
3089
+ // bound tab is a headless (mobile/remote) client, resolve the live desktop
3090
+ // canvas tab instead of dispatching at the headless client — which would
3091
+ // reject with the misleading "mobile client has no open canvas".
3092
+ const redirect = desktopCanvasRedirect(ctx, "panel_set_todo");
3093
+ if (redirect?.error)
3094
+ return fail(redirect.error);
3095
+ if (redirect?.tabId) {
3096
+ return dispatchToTab(ctx, redirect.tabId, { cmd: "set_todo", items: args.items }, 15000, () => reResolveDesktopTab(ctx, "panel_set_todo"));
3097
+ }
3098
+ return ctx.call({ cmd: "set_todo", items: args.items }, 15000);
3099
+ }),
2853
3100
  def("panel_open_civitai", "Open the in-panel CivitAI browser for the user, pre-seeded with a search term and suggested filters, so they can VISUALLY browse and pick a model / LoRA / checkpoint / workflow / image. When the user asks about — or you're recommending — specific CivitAI models/LoRAs/checkpoints (e.g. 'what's a good relight LoRA?'), PREFER opening this docked browser and highlighting your picks over a text-only answer: it docks beside the chat (dock defaults true) so chat and results stay visible together, and it lets the user SEE the actual cards instead of reading a table. Typical show-don't-tell flow: panel_open_civitai (docked) → panel_civitai_search to refine → panel_civitai_results to READ the metadata + URLs → panel_civitai_highlight the one(s) you recommend, with a brief text summary of why. Set a helpful query + filters matched to their goal (including the browsing level). Their selection comes back to you as a normal chat message — UNLESS the panel is muted, in which case they download it directly themselves. Prefer this over guessing a specific model or asking them to paste a URL.", {
2854
3101
  query: z
2855
3102
  .string()
@@ -2888,14 +3135,25 @@ export function buildPanelToolDefs() {
2888
3135
  const query = creator
2889
3136
  ? `@${creator}${rawQuery ? " " + rawQuery : " "}`
2890
3137
  : args.query;
2891
- return await ctx.call({
3138
+ const cmd = {
2892
3139
  cmd: "open_civitai",
2893
3140
  query,
2894
3141
  tab: args.tab,
2895
3142
  browsingLevels,
2896
3143
  filters: args.filters,
2897
3144
  dock: args.dock,
2898
- }, 10000);
3145
+ };
3146
+ // #624: the in-panel CivitAI browser is a DESKTOP panel surface. When this
3147
+ // session's bound tab is a headless (mobile/remote) client, resolve the live
3148
+ // desktop canvas tab instead of dispatching at the headless client — which
3149
+ // would reject with the misleading "mobile client has no open canvas".
3150
+ const redirect = desktopCanvasRedirect(ctx, "panel_open_civitai");
3151
+ if (redirect?.error)
3152
+ return fail(redirect.error);
3153
+ if (redirect?.tabId) {
3154
+ return await dispatchToTab(ctx, redirect.tabId, cmd, 10000, () => reResolveDesktopTab(ctx, "panel_open_civitai"));
3155
+ }
3156
+ return await ctx.call(cmd, 10000);
2899
3157
  }
2900
3158
  catch (err) {
2901
3159
  return fail(err);
@@ -3133,7 +3391,9 @@ export function buildPanelToolDefs() {
3133
3391
  deferredBind = true;
3134
3392
  rebindNote =
3135
3393
  " No panel tab is connected yet — cleared the stale binding; this session will " +
3136
- "follow (bind onto) the tab as soon as one reconnects. Retry your graph tool in a moment.";
3394
+ "follow (bind onto) the tab as soon as one reconnects. Retry your graph tool in a " +
3395
+ "moment; if nothing reconnects shortly, ask the user to refresh (reload) the ComfyUI " +
3396
+ "browser tab, which reconnects the Agent panel after a restart (not an install issue).";
3137
3397
  }
3138
3398
  // Detect the rebind regardless of whether awaitReachable or rebindToActiveTab
3139
3399
  // performed it (either mutates ctx.tabId), so the note is never swallowed.
@@ -3167,7 +3427,7 @@ export function buildPanelToolDefs() {
3167
3427
  return ok({ ...target, ...(deferredBind ? { deferred: true } : {}), note: hint + rebindNote });
3168
3428
  }),
3169
3429
  def("panel_new_workflow", "Open a brand-new BLANK workflow in a NEW TAB. Use this whenever the user wants a 'new workflow' / 'fresh canvas' / 'start over for a new project'. This does NOT touch their current workflow — it opens a separate tab. NEVER use panel_clear for a new workflow (panel_clear wipes the CURRENT graph and is only for 'clear/reset this canvas').", {}, async (_args, ctx) => ctx.call({ cmd: "workflow_new" }, 15000)),
3170
- def("panel_open_workflow", "Open / switch to a workflow by path or filename (from panel_list_workflows). Switches the active tab to it.", { path: z.string().describe("Workflow path, filename, or key from panel_list_workflows.") },
3430
+ def("panel_open_workflow", "Open / switch to a workflow by path or filename (from panel_list_workflows). Switches the active tab to it. If the workflow was ALREADY open and its .json changed on disk out-of-band, the result carries stale:true (or stale:\"unknown\" when staleness couldn't be verified) with a stale_hint — the canvas still shows the version this tab loaded (it is NOT auto-reloaded); call panel_load_workflow to load the on-disk version.", { path: z.string().describe("Workflow path, filename, or key from panel_list_workflows.") },
3171
3431
  // Verify-after-timeout (#215/#319/#496): a backgrounded/frozen or already-open
3172
3432
  // tab can be slow to ack workflow_open even though the switch succeeded. On an
3173
3433
  // ack-timeout, confirm via the authoritative workflow_list active identity