WIP on NLPCRAFT-402
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ascii/NCAsciiTable.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ascii/NCAsciiTable.scala
index b538b92..68ad051 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ascii/NCAsciiTable.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ascii/NCAsciiTable.scala
@@ -111,10 +111,10 @@
     // Table drawing symbols.
     private val HDR_HOR = c("=")
     private val HDR_VER = c("|")
-    private val HDR_CRS = bo(c("+"))
+    private val HDR_CRS = r("+")
     private val ROW_HOR = c("-")
     private val ROW_VER = c("|")
-    private val ROW_CRS = bo(c("+"))
+    private val ROW_CRS = r("+")
 
     // Headers & rows.
     private var hdr = IndexedSeq.empty[Cell]
@@ -136,7 +136,7 @@
      * Global Flag indicating whether of not to automatically draw horizontal lines
      * for multiline rows.
      */
-    var autoBorder = true
+    var multiLineAutoBorder = true
 
     /**
      * If lines exceeds the style's maximum width it will be broken up
@@ -492,8 +492,8 @@
         // At this point all rows in the table have the
         // the same number of columns.
 
-        val colWs = new Array[Int](colsNum) // Column widths.
-        val rowHs = new Array[Int](rows.length) // Row heights.
+        val colWidths = new Array[Int](colsNum) // Column widths.
+        val rowHeights = new Array[Int](rows.length) // Row heights.
 
         // Header height.
         var hdrH = 0
@@ -502,7 +502,7 @@
         for (i <- hdr.indices) {
             val c = hdr(i)
 
-            colWs(i) = c.width
+            colWidths(i) = c.width
 
             hdrH = math.max(hdrH, c.height)
         }
@@ -511,12 +511,12 @@
         for (i <- rows.indices; j <- 0 until colsNum) {
             val c = rows(i)(j)
 
-            rowHs(i) = math.max(rowHs(i), c.height)
-            colWs(j) = math.max(colWs(j), c.width)
+            rowHeights(i) = math.max(rowHeights(i), c.height)
+            colWidths(j) = math.max(colWidths(j), c.width)
         }
 
         // Table width without the border.
-        val tableW = colWs.sum + colsNum - 1
+        val tableW = colWidths.sum + colsNum - 1
 
         val tbl = new StringBuilder
 
@@ -545,9 +545,9 @@
                     val c = hdr(j)
 
                     if (i >= 0 && i < c.height)
-                        tbl ++= aligned(c.lines(i), colWs(j), c.style)
+                        tbl ++= aligned(c.lines(i), colWidths(j), c.style)
                     else
-                        tbl ++= space(colWs(j))
+                        tbl ++= space(colWidths(j))
 
                     tbl ++= s"$HDR_VER" // '|'
                 }
@@ -561,34 +561,33 @@
         else
             tbl ++= mkAsciiLine(ROW_CRS, ROW_HOR)
 
+        val tblWidthLine = s"${space(margin.left)}$ROW_CRS${dash(ROW_HOR, tableW)}$ROW_CRS${space(margin.right)}\n"
+
         // Print rows, if any.
         if (rows.nonEmpty) {
-            val horLine = (i: Int) => {
+            val addHorLine = (i: Int) => {
                 // Left margin and '+'
                 tbl ++= s"${space(margin.left)}$ROW_CRS"
 
                 for (k <- rows(i).indices)
-                    tbl ++= s"${dash(ROW_HOR, colWs(k))}$ROW_CRS"
+                    tbl ++= s"${dash(ROW_HOR, colWidths(k))}$ROW_CRS"
 
                 // Right margin.
                 tbl ++= s"${space(margin.right)}\n"
             }
 
             for (i <- rows.indices) {
-                val r = rows(i)
+                val row = rows(i)
 
-                val rowH = rowHs(i)
-
-                if (i > 0 && ((rowH > 1 && autoBorder) || insideBorder) && rowHs(i - 1) == 1)
-                    horLine(i)
+                val rowH = rowHeights(i)
 
                 for (j <- 0 until rowH) {
                     // Left margin and '|'
                     tbl ++= s"${space(margin.left)}$ROW_VER"
 
-                    for (k <- r.indices) {
-                        val c = r(k)
-                        val w = colWs(k)
+                    for (k <- row.indices) {
+                        val c = row(k)
+                        val w = colWidths(k)
 
                         if (j < c.height)
                             tbl ++= aligned(c.lines(j), w, c.style)
@@ -602,11 +601,11 @@
                     tbl ++= s"${space(margin.right)}\n"
                 }
 
-                if (i < rows.size - 1 && ((rowH > 1 && autoBorder) || insideBorder))
-                    horLine(i)
+                if (i < rows.size - 1 && ((rowH > 1 && multiLineAutoBorder) || insideBorder))
+                    addHorLine(i)
             }
 
-            tbl ++= s"${space(margin.left)}$ROW_CRS${dash(ROW_HOR, tableW)}$ROW_CRS${space(margin.right)}\n"
+            tbl ++= tblWidthLine
         }
 
         // Bottom margin.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/impl/NCTestAutoModelValidatorImpl.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/impl/NCTestAutoModelValidatorImpl.scala
index 5f1cc62..780bbb4 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/impl/NCTestAutoModelValidatorImpl.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/impl/NCTestAutoModelValidatorImpl.scala
@@ -101,11 +101,11 @@
             }
 
             for ((intentId, seq) <- samples; txts <- seq)  yield ask(intentId, txts)
-        }.flatten.toList
+        }.flatMap(_.toSeq)
 
         val tbl = NCAsciiTable()
 
-        tbl #= ("Model ID", "Intent ID", "+/-", "Text", "Error", "Execution time, ms.")
+        tbl #= ("Model ID", "Intent ID", "+/-", "Text", "Error", "ms.")
 
         for (res <- results)
             tbl += (
@@ -116,7 +116,7 @@
                 res.error.getOrElse(""),
                 res.time
             )
-        
+
         val passCnt = results.count(_.pass)
         val failCnt = results.count(!_.pass)
         
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/NCProbeBoot.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/NCProbeBoot.scala
index 138b777..756481d 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/NCProbeBoot.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/NCProbeBoot.scala
@@ -460,7 +460,7 @@
         tbl += (s"${B}Down-Link$RST", cfg.downLinkString)
         tbl += (s"${B}Up-Link$RST", cfg.upLinkString)
         tbl += (s"${B}Lifecycle$RST", cfg.lifecycle)
-        tbl += (s"${B}Models (${cfg.modelsSeq.size})$RST", cfg.modelsSeq)
+        tbl += (s"${B}Models (${cfg.modelsSeq.size})$RST", cfg.modelsSeq.map(c(_)))
         tbl += (s"${B}JARs Folder$RST", cfg.jarsFolder.getOrElse(""))
 
         tbl.info(logger, Some("Probe Configuration:"))
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeModel.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeModel.scala
index 9084fb8..75ae18b 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeModel.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeModel.scala
@@ -21,11 +21,18 @@
 import org.apache.nlpcraft.model.intent.solver.NCIntentSolver
 import org.apache.nlpcraft.model.{NCElement, NCModel}
 
+case class NCProbeModelCallback(
+    origin: String,
+    className: String,
+    methodName: String
+)
+
 /**
   *
   * @param model
   * @param solver
   * @param intents
+  * @param callbacks
   * @param continuousSynonyms
   * @param sparseSynonyms
   * @param idlSynonyms
@@ -37,6 +44,7 @@
     model: NCModel,
     solver: NCIntentSolver,
     intents: Seq[NCIdlIntent],
+    callbacks: Map[String /* Intent ID */, NCProbeModelCallback],
     continuousSynonyms: Map[String /*Element ID*/ , Map[Int /*Synonym length*/ , NCProbeSynonymsWrapper]], // Fast access map.
     sparseSynonyms: Map[String /*Element ID*/, Seq[NCProbeSynonym]],
     idlSynonyms: Map[String /*Element ID*/ , Seq[NCProbeSynonym]], // Fast access map.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
index d518e62..f6ee6eb 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
@@ -37,7 +37,7 @@
 import org.apache.nlpcraft.model.intent.solver.NCIntentSolver
 import org.apache.nlpcraft.model.intent._
 import org.apache.nlpcraft.probe.mgrs.NCProbeSynonymChunkKind.{IDL, REGEX, TEXT}
-import org.apache.nlpcraft.probe.mgrs.{NCProbeModel, NCProbeSynonym, NCProbeSynonymChunk, NCProbeSynonymsWrapper}
+import org.apache.nlpcraft.probe.mgrs.{NCProbeModel, NCProbeModelCallback, NCProbeSynonym, NCProbeSynonymChunk, NCProbeSynonymsWrapper}
 
 import scala.util.Using
 import scala.compat.java8.OptionConverters._
@@ -76,7 +76,7 @@
         CLS_JAVA_OPT
     )
     
-    type Callback = (String /* ID */, Function[NCIntentMatch, NCResult])
+    case class Callback(id: String, clsName: String, funName: String, cbFun: Function[NCIntentMatch, NCResult])
     type Intent = (NCIdlIntent, Callback)
     type Sample = (String/* Intent ID */, Seq[Seq[String]] /* List of list of input samples for that intent. */)
     
@@ -497,7 +497,7 @@
             }
 
             solver = new NCIntentSolver(
-                intents.toList.map(x => (x._1, (z: NCIntentMatch) => x._2._2.apply(z)))
+                intents.toList.map(x => (x._1, (z: NCIntentMatch) => x._2.cbFun.apply(z)))
             )
         }
         else
@@ -512,6 +512,14 @@
             model = mdl,
             solver = solver,
             intents = intents.map(_._1).toSeq,
+            callbacks = intents.map(kv => (
+                kv._1.id,
+                NCProbeModelCallback(
+                    kv._1.origin,
+                    kv._2.clsName,
+                    kv._2.funName
+                )
+            )).toMap,
             continuousSynonyms = mkFastAccessMap(sparse(simple, sp = false), NCProbeSynonymsWrapper(_)),
             sparseSynonyms = toMap(sparse(simple, sp = true)),
             idlSynonyms = toMap(idl(syns.toSet, idl = true)),
@@ -1186,9 +1194,10 @@
 
         checkMinMax(mdl, mtd, tokParamTypes, termIds.map(allLimits), ctxFirstParam)
 
-        // Prepares invocation method.
-        (
+        Callback(
             mtd.toString,
+            mtd.getDeclaringClass.getName,
+            mtd.getName,
             (ctx: NCIntentMatch) => {
                 invoke(
                     mtd,
@@ -1544,7 +1553,7 @@
             val mtdStr = method2Str(m)
 
             def bindIntent(intent: NCIdlIntent, cb: Callback): Unit = {
-                if (intents.exists(i => i._1.id == intent.id && i._2._1 != cb._1))
+                if (intents.exists(i => i._1.id == intent.id && i._2.id != cb.id))
                     throw new NCE(s"The intent cannot be bound to more than one callback [" +
                         s"mdlId=$mdlId, " +
                         s"origin=${mdl.getOrigin}, " +
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/model/NCModelManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/model/NCModelManager.scala
index 1c5b6b0..da8c70b 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/model/NCModelManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/model/NCModelManager.scala
@@ -46,43 +46,52 @@
     override def start(parent: Span = null): NCService = startScopedSpan("start", parent) { span =>
         ackStarting()
 
-        val tbl = NCAsciiTable("Models")
-
         mux.synchronized {
-            data = NCDeployManager.getModels.map(w => {
-                w.model.onInit()
-                w.model.getId -> w
+            data = NCDeployManager.getModels.map(pm => {
+                pm.model.onInit()
+                pm.model.getId -> pm
             }).toMap
 
-            data.values.foreach(w => {
-                val mdl = w.model
+            logger.info(s"Models deployed: ${data.size}")
 
-                val contCnt = w.continuousSynonyms.flatMap(_._2.map(_._2.count)).sum
-                val sparseCnt = w.sparseSynonyms.map(_._2.size).sum
-                val allIdlSyns = w.idlSynonyms.values.flatten
+            data.values.foreach(pm => {
+                val mdl = pm.model
+
+                val contCnt = pm.continuousSynonyms.flatMap(_._2.map(_._2.count)).sum
+                val sparseCnt = pm.sparseSynonyms.map(_._2.size).sum
+                val allIdlSyns = pm.idlSynonyms.values.flatMap(_.toSeq)
                 val sparseIdlCnt = allIdlSyns.count(_.sparse)
                 val contIdlCnt = allIdlSyns.size - sparseIdlCnt
 
                 def withWarn(i: Int): String = if (i == 0) s"0 ${r("(!)")}" else i.toString
 
+                val tbl = NCAsciiTable()
+
                 tbl += Seq(
                     s"${B}Name:$RST                  ${bo(c(mdl.getName))}",
                     s"${B}ID:$RST                    ${bo(mdl.getId)}",
                     s"${B}Version:$RST               ${mdl.getVersion}",
                     s"${B}Origin:$RST                ${mdl.getOrigin}",
-                    s"${B}Elements:$RST              ${withWarn(w.elements.keySet.size)}",
+                    s"${B}Elements:$RST              ${withWarn(pm.elements.keySet.size)}"
+                )
+                tbl += Seq(
                     s"${B}Synonyms:$RST",
                     s"$B   Simple continuous:$RST  $contCnt",
                     s"$B   Simple sparse:$RST      $sparseCnt",
                     s"$B   IDL continuous:$RST     $contIdlCnt",
                     s"$B   IDL sparse:$RST         $sparseIdlCnt",
-                    s"${B}Intents:$RST               ${withWarn(w.intents.size)}"
+
                 )
+                tbl += Seq(s"${B}Intents:$RST ${withWarn(pm.intents.size)}") ++
+                    (
+                        for (cb <- pm.callbacks) yield
+                        s"   ${g(bo(cb._1))} from ${m(cb._2.origin)} -> ${c(cb._2.className)}${bo("#")}${c(cb._2.methodName)}(...)"
+                    ).toSeq
+
+                tbl.info(logger, Some("Model:"))
             })
         }
 
-        tbl.info(logger, Some(s"Models deployed: ${data.size}"))
-
         addTags(
             span,
             "deployedModels" -> data.values.map(_.model.getId).mkString(",")