answer42 0.4.75__tar.gz → 0.4.76__tar.gz

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.
Files changed (64) hide show
  1. {answer42-0.4.75 → answer42-0.4.76}/PKG-INFO +1 -1
  2. {answer42-0.4.75 → answer42-0.4.76}/pyproject.toml +1 -1
  3. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/service.py +8 -4
  4. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/summary_backend.py +86 -1
  5. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/server.py +94 -16
  6. {answer42-0.4.75 → answer42-0.4.76}/.gitignore +0 -0
  7. {answer42-0.4.75 → answer42-0.4.76}/LICENSE +0 -0
  8. {answer42-0.4.75 → answer42-0.4.76}/README.md +0 -0
  9. {answer42-0.4.75 → answer42-0.4.76}/credentials.example.json +0 -0
  10. {answer42-0.4.75 → answer42-0.4.76}/docs/agent-installation.md +0 -0
  11. {answer42-0.4.75 → answer42-0.4.76}/docs/architecture.md +0 -0
  12. {answer42-0.4.75 → answer42-0.4.76}/docs/assets/answer42-logo.png +0 -0
  13. {answer42-0.4.75 → answer42-0.4.76}/docs/assets/platform42-logo.svg +0 -0
  14. {answer42-0.4.75 → answer42-0.4.76}/docs/installation.md +0 -0
  15. {answer42-0.4.75 → answer42-0.4.76}/scripts/build_cf.py +0 -0
  16. {answer42-0.4.75 → answer42-0.4.76}/scripts/build_pages.py +0 -0
  17. {answer42-0.4.75 → answer42-0.4.76}/scripts/e2e_http_smoke.py +0 -0
  18. {answer42-0.4.75 → answer42-0.4.76}/scripts/e2e_stable.py +0 -0
  19. {answer42-0.4.75 → answer42-0.4.76}/scripts/openclaw_mcp_autoreload.py +0 -0
  20. {answer42-0.4.75 → answer42-0.4.76}/scripts/rag_cli.py +0 -0
  21. {answer42-0.4.75 → answer42-0.4.76}/src/cf/ConfigDumpInfo.xml +0 -0
  22. {answer42-0.4.75 → answer42-0.4.76}/src/cf/Configuration.xml +0 -0
  23. {answer42-0.4.75 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  24. {answer42-0.4.75 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl" +0 -0
  25. {answer42-0.4.75 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  26. {answer42-0.4.75 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  27. {answer42-0.4.75 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  28. {answer42-0.4.75 → answer42-0.4.76}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  29. {answer42-0.4.75 → answer42-0.4.76}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  30. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Catalogs//320/237/320/241_/320/222/320/273/320/260/320/264/320/265/320/273/320/265/321/206.xml" +0 -0
  31. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Catalogs//320/237/320/241_/320/241/320/277/321/200/320/260/320/262/320/276/321/207/320/275/320/270/320/2721.xml" +0 -0
  32. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/ConfigDumpInfo.xml +0 -0
  33. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Configuration.xml +0 -0
  34. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  35. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ManagerModule.bsl" +0 -0
  36. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ObjectModule.bsl" +0 -0
  37. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205/Ext/Template.xml" +0 -0
  38. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205.xml" +0 -0
  39. {answer42-0.4.75 → answer42-0.4.76}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262.xml" +0 -0
  40. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/__init__.py +0 -0
  41. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  42. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  43. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/assets/__init__.py +0 -0
  44. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  45. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  46. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/assets/skills/answer42-universal-report/SKILL.md +0 -0
  47. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/auth.py +0 -0
  48. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/bridge.py +0 -0
  49. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/credentials.py +0 -0
  50. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/os_support.py +0 -0
  51. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/platform.py +0 -0
  52. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/protocol.py +0 -0
  53. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/__init__.py +0 -0
  54. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/detect.py +0 -0
  55. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/dump.py +0 -0
  56. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/model.py +0 -0
  57. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/parsers.py +0 -0
  58. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/store.py +0 -0
  59. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/rag/summary_vector.py +0 -0
  60. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/recorder.py +0 -0
  61. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/release_helper.py +0 -0
  62. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/runtime.py +0 -0
  63. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/skill_installer.py +0 -0
  64. {answer42-0.4.75 → answer42-0.4.76}/src/mcp_1c/window_control.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: answer42
3
- Version: 0.4.75
3
+ Version: 0.4.76
4
4
  Summary: Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API
5
5
  Author: Marvin (AI Assistant), 42Clouds, and contributors
6
6
  Author-email: "Kosolapov Stanislav (proDOOMman)" <prodoomman@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "answer42"
3
- version = "0.4.75"
3
+ version = "0.4.76"
4
4
  description = "Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -417,9 +417,9 @@ class RagService:
417
417
  row["data_composition_fields"] = [dict(r) for r in con.execute("SELECT schema_name,data_path,field,title,expression,dataset_name,field_type,source_path FROM data_composition_fields WHERE object_id=? ORDER BY id", (oid,))]
418
418
  return {"query": object, "snapshot": snapshot, "matches": rows}
419
419
 
420
- def lookup_form_element_types(self, snapshot: str | int | None = None) -> dict[str, Any]:
420
+ def lookup_form_element_types(self, snapshot: str | int | None = None, object_full_name: str | None = None) -> dict[str, Any]:
421
421
  if self.summary_backend.is_summary_snapshot(snapshot):
422
- return self.summary_backend.lookup_form_element_types(snapshot)
422
+ return self.summary_backend.lookup_form_element_types(snapshot, object_full_name=object_full_name)
423
423
  """Return a compact name/title/data-path index for UI-tree enrichment.
424
424
 
425
425
  The 1C test-client API exposes runtime UI element names and captions, but
@@ -435,14 +435,18 @@ class RagService:
435
435
  auto-generated forms using metadata alone.
436
436
  """
437
437
  with self.store.connect() as con:
438
- source_filter = ""
438
+ filters = []
439
439
  params: list[Any] = []
440
440
  if snapshot is not None:
441
441
  snap = _snapshot_row(con, snapshot)
442
442
  if snap is None:
443
443
  raise ValueError(f"snapshot not found: {snapshot}")
444
- source_filter = "AND mo.source_id IN (SELECT source_id FROM snapshot_layers WHERE snapshot_id=?)"
444
+ filters.append("mo.source_id IN (SELECT source_id FROM snapshot_layers WHERE snapshot_id=?)")
445
445
  params.append(snap["id"])
446
+ if object_full_name:
447
+ filters.append("mo.full_name=?")
448
+ params.append(str(object_full_name))
449
+ source_filter = " AND " + " AND ".join(filters) if filters else ""
446
450
  rows = [
447
451
  dict(r)
448
452
  for r in con.execute(
@@ -397,10 +397,12 @@ class ConfigurationSummaryBackend:
397
397
  seen.add(obj["full_name"])
398
398
  return {"query": object, "snapshot": snapshot, "backend": "configuration-summary", "summary": self._summary_info(info, meta), "matches": matches}
399
399
 
400
- def lookup_form_element_types(self, snapshot: str | int | None = None, limit: int = 10_000) -> dict[str, Any]:
400
+ def lookup_form_element_types(self, snapshot: str | int | None = None, limit: int = 10_000, object_full_name: str | None = None) -> dict[str, Any]:
401
401
  info = self.resolve_snapshot(snapshot)
402
402
  if not info:
403
403
  raise ValueError(f"summary snapshot not found: {snapshot}")
404
+ if object_full_name:
405
+ return self._lookup_object_form_element_types(info, snapshot, object_full_name, limit=limit)
404
406
  hits, meta = self.search(str(info["config_code"]), self._snapshot_layer_names(info), "форма", limit=limit, kinds="form")
405
407
  items = []
406
408
  for hit in hits:
@@ -411,6 +413,89 @@ class ConfigurationSummaryBackend:
411
413
  # what dynamic_list_metadata_available_fields uses for a concrete object.
412
414
  return {"snapshot": snapshot, "backend": "configuration-summary", "summary": self._summary_info(info, meta), "count": len(items), "items": items}
413
415
 
416
+ def _lookup_object_form_element_types(self, info: dict[str, Any], snapshot: str | int | None, object_full_name: str, *, limit: int) -> dict[str, Any]:
417
+ metadata_type, metadata_name = _split_object_name(object_full_name)
418
+ query = " ".join(part for part in (metadata_type, metadata_name, "форма") if part)
419
+ hits, meta = self.search(str(info["config_code"]), self._snapshot_layer_names(info), query, limit=min(max(limit, 20), 120), kinds="form")
420
+ items = []
421
+ seen = set()
422
+ for hit in hits:
423
+ row = hit.metadata
424
+ row_type = str(row.get("metadataType") or "")
425
+ row_name = str(row.get("metadataName") or "")
426
+ if metadata_type and row_type and normalize_query(row_type) != normalize_query(metadata_type):
427
+ continue
428
+ if metadata_name and row_name and normalize_query(row_name) != normalize_query(metadata_name):
429
+ continue
430
+ for item in self._form_element_rows(row, self._info_for_hit_layer(info, row)):
431
+ key = (
432
+ item.get("object_full_name"),
433
+ item.get("form_name"),
434
+ item.get("name"),
435
+ item.get("data_path"),
436
+ item.get("command_name"),
437
+ )
438
+ if key not in seen:
439
+ items.append(item)
440
+ seen.add(key)
441
+ obj_lookup = self.lookup_object(object_full_name, snapshot)
442
+ for obj in obj_lookup.get("matches") or []:
443
+ if normalize_query(str(obj.get("full_name") or "")) != normalize_query(object_full_name):
444
+ continue
445
+ source_path = str(obj.get("source_path") or "")
446
+ object_kind = str(obj.get("kind") or metadata_type)
447
+ for attr in obj.get("attributes") or []:
448
+ name = str(attr.get("name") or "")
449
+ if not name:
450
+ continue
451
+ key = (object_full_name, "<auto>", name, name, None)
452
+ if key in seen:
453
+ continue
454
+ items.append({
455
+ "object_full_name": object_full_name,
456
+ "object_kind": object_kind,
457
+ "form_name": "<auto>",
458
+ "name": name,
459
+ "element_type": "AutoGeneratedField",
460
+ "title": attr.get("synonym") or name,
461
+ "data_path": name,
462
+ "command_name": None,
463
+ "parent_name": None,
464
+ "source_path": attr.get("source_path") or source_path,
465
+ "attribute_type": attr.get("type_name"),
466
+ "attribute_role": attr.get("role") or "attribute",
467
+ "dcs_field_type": None,
468
+ })
469
+ seen.add(key)
470
+ for table_part in obj.get("table_parts") or []:
471
+ table_name = str(table_part.get("name") or "")
472
+ for col in table_part.get("columns") or []:
473
+ col_name = str(col.get("name") or "")
474
+ if not table_name or not col_name:
475
+ continue
476
+ data_path = f"{table_name}.{col_name}"
477
+ key = (object_full_name, "<auto>", data_path, data_path, None)
478
+ if key in seen:
479
+ continue
480
+ items.append({
481
+ "object_full_name": object_full_name,
482
+ "object_kind": object_kind,
483
+ "form_name": "<auto>",
484
+ "name": data_path,
485
+ "element_type": "AutoGeneratedTablePartField",
486
+ "title": col.get("synonym") or col_name,
487
+ "data_path": data_path,
488
+ "command_name": None,
489
+ "parent_name": table_name,
490
+ "source_path": col.get("source_path") or source_path,
491
+ "attribute_type": col.get("type_name"),
492
+ "attribute_role": "table_part_column",
493
+ "dcs_field_type": None,
494
+ })
495
+ seen.add(key)
496
+ break
497
+ return {"snapshot": snapshot, "backend": "configuration-summary", "summary": self._summary_info(info, meta), "object_full_name": object_full_name, "count": len(items), "items": items}
498
+
414
499
 
415
500
  def _snapshot_layer_names(self, info: dict[str, Any]) -> list[str]:
416
501
  layers = [str(item.get("layer") or "") for item in info.get("summary_layers") or [] if str(item.get("layer") or "")]
@@ -1969,6 +1969,8 @@ def _walk_ui_tree(node: Any):
1969
1969
  yield node
1970
1970
  for child in node.get("children") or []:
1971
1971
  yield from _walk_ui_tree(child)
1972
+ for child in node.get("objects") or []:
1973
+ yield from _walk_ui_tree(child)
1972
1974
 
1973
1975
 
1974
1976
  def _walk_ui_snapshot(node: Any):
@@ -2108,7 +2110,8 @@ _UI_TREE_FIELD_PRESETS: dict[str, set[str] | None] = {
2108
2110
  "diagnostic": {
2109
2111
  "type", "name", "title", "form_name", "navigation_url", "visible", "available", "read_only", "enabled",
2110
2112
  "main", "home", "current", "selected", "data_path", "current_page_name", "current_page", "pages",
2111
- "hidden_children_count", "_tree_ms", "_nodes_serialized", "_nodes_visited", "metrics", "flat", "source",
2113
+ "hidden_children_count", "rag_type", "rag", "_tree_ms", "_nodes_serialized", "_nodes_visited",
2114
+ "metrics", "flat", "source",
2112
2115
  },
2113
2116
  "data": {
2114
2117
  "type", "name", "title", "form_name", "navigation_url", "text", "value", "presentation", "data",
@@ -2133,6 +2136,19 @@ def _ui_tree_field_names(fields: str, profile: str) -> set[str] | None:
2133
2136
  raise ValueError(f"Unsupported ui_tree fields: {fields!r}. Expected one of: {allowed}.")
2134
2137
 
2135
2138
 
2139
+ def _ui_tree_rag_enabled(value: bool | str | None, profile: str) -> bool:
2140
+ if isinstance(value, bool):
2141
+ return value
2142
+ raw = str(value if value is not None else "auto").strip().lower()
2143
+ if raw in {"", "auto", "default", "profile"}:
2144
+ return profile in _UI_TREE_RAG_AUTO_PROFILES
2145
+ if raw in {"1", "true", "yes", "on", "enable", "enabled"}:
2146
+ return True
2147
+ if raw in {"0", "false", "no", "off", "disable", "disabled", "none"}:
2148
+ return False
2149
+ raise ValueError("Unsupported ui_tree include_rag_types: " f"{value!r}. Expected bool or one of: auto, true, false.")
2150
+
2151
+
2136
2152
  def _ui_tree_state_options(field_names: set[str] | None, *, include_hidden: bool) -> dict[str, bool]:
2137
2153
  if field_names is None:
2138
2154
  return {
@@ -2339,6 +2355,7 @@ _UI_TREE_PROFILES = {"navigation", "diagnostic", "data", "full", "command_panels
2339
2355
  _UI_TREE_COMMAND_PANEL_MODES = {"auto", "include", "full", "with", "all", "exclude", "without", "none", "off", "no", "only"}
2340
2356
  _UI_TREE_GROUP_MODES = {"include", "flatten", "flatten_layout", "exclude", "exclude_empty", "pages_only"}
2341
2357
  _UI_TREE_FORMATS = {"json", "outline", "yaml", "yml"}
2358
+ _UI_TREE_RAG_AUTO_PROFILES = {"diagnostic", "data", "full"}
2342
2359
 
2343
2360
 
2344
2361
  def _validate_ui_tree_choice(kind: str, value: str, allowed: set[str]) -> str:
@@ -3007,9 +3024,15 @@ def _convert_1c_guid_navigation_link(
3007
3024
  }
3008
3025
 
3009
3026
 
3010
- def _rag_form_element_index(snapshot: str | int | None = None) -> dict[str, list[dict[str, Any]]]:
3027
+ def _rag_form_element_index(snapshot: str | int | None = None, *, active_object: str = "") -> dict[str, list[dict[str, Any]]]:
3028
+ lookup_object = _metadata_name_to_rag_name(active_object) if active_object else ""
3011
3029
  try:
3012
- items = _RAG.lookup_form_element_types(snapshot=snapshot).get("items", [])
3030
+ try:
3031
+ result = _RAG.lookup_form_element_types(snapshot=snapshot, object_full_name=lookup_object or None)
3032
+ except TypeError:
3033
+ # Backward compatibility for tests or external RagService-like stubs.
3034
+ result = _RAG.lookup_form_element_types(snapshot=snapshot)
3035
+ items = result.get("items", [])
3013
3036
  except Exception as exc:
3014
3037
  logging.debug("RAG form element lookup failed: %s", exc)
3015
3038
  return {}
@@ -3022,21 +3045,72 @@ def _rag_form_element_index(snapshot: str | int | None = None) -> dict[str, list
3022
3045
  return index
3023
3046
 
3024
3047
 
3048
+ def _ui_tree_form_short_name(value: Any) -> str:
3049
+ text = str(value or "")
3050
+ marker = ".Форма."
3051
+ if marker in text:
3052
+ return text.rsplit(marker, 1)[1]
3053
+ marker = ".Form."
3054
+ if marker in text:
3055
+ return text.rsplit(marker, 1)[1]
3056
+ return text
3057
+
3058
+
3059
+ def _metadata_name_from_ui_tree(tree: dict[str, Any]) -> str:
3060
+ value = _metadata_name_from_navigation_url(str(tree.get("navigation_url") or ""))
3061
+ if value:
3062
+ return value
3063
+ diagnostics = tree.get("client_diagnostics")
3064
+ if isinstance(diagnostics, dict):
3065
+ form_name = str(diagnostics.get("active_form_name") or diagnostics.get("modal_form_name") or "")
3066
+ for marker in (".Форма.", ".Form."):
3067
+ if marker in form_name:
3068
+ return form_name.split(marker, 1)[0]
3069
+ for node in _walk_ui_tree(tree):
3070
+ form_name = str(node.get("form_name") or "")
3071
+ for marker in (".Форма.", ".Form."):
3072
+ if marker in form_name:
3073
+ return form_name.split(marker, 1)[0]
3074
+ return ""
3075
+
3076
+
3077
+ def _ui_node_rag_lookup_keys(node: dict[str, Any]) -> list[str]:
3078
+ values = [node.get("name"), node.get("title"), node.get("data_path")]
3079
+ keys: list[str] = []
3080
+ for value in values:
3081
+ text = str(value or "").strip()
3082
+ if not text:
3083
+ continue
3084
+ keys.append(_norm_key(text))
3085
+ if "." in text:
3086
+ keys.append(_norm_key(text.rsplit(".", 1)[1]))
3087
+ return list(dict.fromkeys(key for key in keys if key))
3088
+
3089
+
3025
3090
  def _pick_rag_element(ui_node: dict[str, Any], candidates: list[dict[str, Any]], active_object: str = "") -> dict[str, Any] | None:
3026
3091
  if not candidates:
3027
3092
  return None
3028
- active_norm = _norm_key(active_object)
3029
- form_name = _norm_key(ui_node.get("form_name"))
3093
+ active_names = {_norm_key(active_object), _norm_key(_metadata_name_to_rag_name(active_object))}
3094
+ active_names.discard("")
3095
+ form_name = _norm_key(_ui_tree_form_short_name(ui_node.get("form_name")))
3030
3096
 
3031
3097
  def score(item: dict[str, Any]) -> int:
3032
3098
  value = 0
3033
- if active_norm and active_norm in _norm_key(item.get("object_full_name")):
3099
+ item_object = _norm_key(item.get("object_full_name"))
3100
+ if active_names and item_object in active_names:
3034
3101
  value += 20
3035
- if form_name and form_name == _norm_key(item.get("form_name")):
3102
+ item_form = _norm_key(_ui_tree_form_short_name(item.get("form_name")))
3103
+ if form_name and form_name == item_form:
3036
3104
  value += 5
3037
- if _norm_key(ui_node.get("name")) == _norm_key(item.get("name")):
3105
+ elif item_form == "<auto>":
3106
+ value += 1
3107
+ node_name = _norm_key(ui_node.get("name"))
3108
+ item_name = _norm_key(item.get("name"))
3109
+ item_data_path = _norm_key(item.get("data_path"))
3110
+ if node_name and (node_name == item_name or node_name == item_data_path):
3038
3111
  value += 3
3039
- if _norm_key(ui_node.get("title")) == _norm_key(item.get("title")):
3112
+ node_title = _norm_key(ui_node.get("title"))
3113
+ if node_title and node_title == _norm_key(item.get("title")):
3040
3114
  value += 2
3041
3115
  return value
3042
3116
 
@@ -3046,12 +3120,12 @@ def _pick_rag_element(ui_node: dict[str, Any], candidates: list[dict[str, Any]],
3046
3120
  def _enrich_ui_tree_with_rag_types(tree: Any, snapshot: str | int | None = None) -> Any:
3047
3121
  if not isinstance(tree, dict):
3048
3122
  return tree
3049
- index = _rag_form_element_index(snapshot=snapshot)
3123
+ active_object = _metadata_name_from_ui_tree(tree)
3124
+ index = _rag_form_element_index(snapshot=snapshot, active_object=active_object)
3050
3125
  if not index:
3051
3126
  return tree
3052
- active_object = _metadata_name_from_navigation_url(str(tree.get("navigation_url") or ""))
3053
3127
  for node in _walk_ui_tree(tree):
3054
- keys = [_norm_key(node.get("name")), _norm_key(node.get("title")), _norm_key(node.get("data_path"))]
3128
+ keys = _ui_node_rag_lookup_keys(node)
3055
3129
  candidates: list[dict[str, Any]] = []
3056
3130
  seen: set[int] = set()
3057
3131
  for key in keys:
@@ -6118,7 +6192,7 @@ async def decoration_click(name: Annotated[str, "Decoration technical name"] = "
6118
6192
 
6119
6193
  @mcp.tool()
6120
6194
  async def ui_tree(
6121
- include_rag_types: Annotated[bool, "Enrich UI elements with best-effort types from the local RAG index; disabled by default because it may add lookup cost and verbose metadata"] = False,
6195
+ include_rag_types: Annotated[bool | str, "Enrich UI elements with best-effort types from the RAG index. Use true/false to force; auto/default enables it for diagnostic, data and full profiles when session RAG is enabled"] = "auto",
6122
6196
  include_hidden: Annotated[bool, "Include invisible form elements/pages; visible/available/read_only flags are returned for serialized nodes"] = False,
6123
6197
  compact: Annotated[bool | None, "Return compact node fields; default follows ONEC_MCP_RESULT_VERBOSITY"] = None,
6124
6198
  include_data: Annotated[bool, "Include data presentations from UI nodes; expensive on large forms, prefer field_value_text/table_rows/tabular_document_text for values"] = False,
@@ -6143,6 +6217,8 @@ async def ui_tree(
6143
6217
 
6144
6218
  Default profile is `navigation`: visible elements, compact fields, no data
6145
6219
  presentations, no RAG enrichment, and command panels excluded unless requested.
6220
+ `include_rag_types` defaults to `auto`: RAG enrichment is enabled for
6221
+ `diagnostic`, `data` and `full` profiles and disabled for navigation profiles.
6146
6222
  The tree has no depth limit. Use `include_hidden=true` for hidden pages/elements.
6147
6223
  Pass `parent_name` to serialize only the subtree rooted at a specific form element.
6148
6224
  Use `fields=minimal|navigation|diagnostic|data|full` (or a comma-separated
@@ -6181,6 +6257,7 @@ async def ui_tree(
6181
6257
  command_mode = _validate_ui_tree_choice("command_panels", command_panels, _UI_TREE_COMMAND_PANEL_MODES)
6182
6258
  group_mode_key = _validate_ui_tree_choice("group_mode", group_mode, _UI_TREE_GROUP_MODES)
6183
6259
  fmt = _validate_ui_tree_choice("format", format, _UI_TREE_FORMATS)
6260
+ include_rag_types_effective = _ui_tree_rag_enabled(include_rag_types, profile_key)
6184
6261
  field_names = _ui_tree_field_names(fields, profile_key)
6185
6262
  state_options = _ui_tree_state_options(field_names, include_hidden=include_hidden)
6186
6263
  if profile_key in {"full", "diagnostic", "data"}:
@@ -6236,13 +6313,14 @@ async def ui_tree(
6236
6313
  if isinstance(tree, dict):
6237
6314
  tree["tree_summary"] = _summarize_ui_tree(tree, include_hidden=include_hidden)
6238
6315
  timed_stage("python_summary_ms", summary_start)
6239
- if include_rag_types:
6316
+ if include_rag_types_effective:
6240
6317
  rag_start = time.monotonic()
6241
6318
  sess = _session(session_id)
6242
6319
  if _RAG_ENABLED and sess.rag_enabled:
6243
- tree = _enrich_ui_tree_with_rag_types(tree, snapshot=rag_snapshot or sess.rag_snapshot or None)
6320
+ effective_rag_snapshot = rag_snapshot or sess.rag_snapshot or str(sess.rag_notice.get("snapshot") or "") or None
6321
+ tree = _enrich_ui_tree_with_rag_types(tree, snapshot=effective_rag_snapshot)
6244
6322
  if isinstance(tree, dict):
6245
- tree["rag_diagnostic"] = _rag_diagnostic(session_id, rag_snapshot)
6323
+ tree["rag_diagnostic"] = _rag_diagnostic(session_id, effective_rag_snapshot or "")
6246
6324
  elif isinstance(tree, dict):
6247
6325
  tree["rag_diagnostic"] = _rag_diagnostic(session_id, rag_snapshot)
6248
6326
  timed_stage("python_rag_enrich_ms", rag_start)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes