answer42 0.4.74__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 (65) hide show
  1. {answer42-0.4.74 → answer42-0.4.76}/PKG-INFO +1 -1
  2. {answer42-0.4.74 → answer42-0.4.76}/pyproject.toml +1 -1
  3. {answer42-0.4.74 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +74 -14
  4. answer42-0.4.76/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  5. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/protocol.py +1 -1
  6. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/service.py +8 -4
  7. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/summary_backend.py +86 -1
  8. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/server.py +94 -16
  9. answer42-0.4.74/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  10. {answer42-0.4.74 → answer42-0.4.76}/.gitignore +0 -0
  11. {answer42-0.4.74 → answer42-0.4.76}/LICENSE +0 -0
  12. {answer42-0.4.74 → answer42-0.4.76}/README.md +0 -0
  13. {answer42-0.4.74 → answer42-0.4.76}/credentials.example.json +0 -0
  14. {answer42-0.4.74 → answer42-0.4.76}/docs/agent-installation.md +0 -0
  15. {answer42-0.4.74 → answer42-0.4.76}/docs/architecture.md +0 -0
  16. {answer42-0.4.74 → answer42-0.4.76}/docs/assets/answer42-logo.png +0 -0
  17. {answer42-0.4.74 → answer42-0.4.76}/docs/assets/platform42-logo.svg +0 -0
  18. {answer42-0.4.74 → answer42-0.4.76}/docs/installation.md +0 -0
  19. {answer42-0.4.74 → answer42-0.4.76}/scripts/build_cf.py +0 -0
  20. {answer42-0.4.74 → answer42-0.4.76}/scripts/build_pages.py +0 -0
  21. {answer42-0.4.74 → answer42-0.4.76}/scripts/e2e_http_smoke.py +0 -0
  22. {answer42-0.4.74 → answer42-0.4.76}/scripts/e2e_stable.py +0 -0
  23. {answer42-0.4.74 → answer42-0.4.76}/scripts/openclaw_mcp_autoreload.py +0 -0
  24. {answer42-0.4.74 → answer42-0.4.76}/scripts/rag_cli.py +0 -0
  25. {answer42-0.4.74 → answer42-0.4.76}/src/cf/ConfigDumpInfo.xml +0 -0
  26. {answer42-0.4.74 → answer42-0.4.76}/src/cf/Configuration.xml +0 -0
  27. {answer42-0.4.74 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  28. {answer42-0.4.74 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  29. {answer42-0.4.74 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  30. {answer42-0.4.74 → answer42-0.4.76}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  31. {answer42-0.4.74 → answer42-0.4.76}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  32. {answer42-0.4.74 → answer42-0.4.76}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  33. {answer42-0.4.74 → 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
  34. {answer42-0.4.74 → 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
  35. {answer42-0.4.74 → answer42-0.4.76}/src/client_cf/ConfigDumpInfo.xml +0 -0
  36. {answer42-0.4.74 → answer42-0.4.76}/src/client_cf/Configuration.xml +0 -0
  37. {answer42-0.4.74 → 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
  38. {answer42-0.4.74 → 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
  39. {answer42-0.4.74 → 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
  40. {answer42-0.4.74 → 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
  41. {answer42-0.4.74 → 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
  42. {answer42-0.4.74 → 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
  43. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/__init__.py +0 -0
  44. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  45. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/assets/__init__.py +0 -0
  46. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  47. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  48. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/assets/skills/answer42-universal-report/SKILL.md +0 -0
  49. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/auth.py +0 -0
  50. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/bridge.py +0 -0
  51. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/credentials.py +0 -0
  52. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/os_support.py +0 -0
  53. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/platform.py +0 -0
  54. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/__init__.py +0 -0
  55. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/detect.py +0 -0
  56. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/dump.py +0 -0
  57. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/model.py +0 -0
  58. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/parsers.py +0 -0
  59. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/store.py +0 -0
  60. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/rag/summary_vector.py +0 -0
  61. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/recorder.py +0 -0
  62. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/release_helper.py +0 -0
  63. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/runtime.py +0 -0
  64. {answer42-0.4.74 → answer42-0.4.76}/src/mcp_1c/skill_installer.py +0 -0
  65. {answer42-0.4.74 → 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.74
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.74"
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"
@@ -338,7 +338,7 @@
338
338
 
339
339
  &НаКлиенте
340
340
  Функция ВерсияПротоколаМенеджераMCP()
341
- Возврат "2026.09.01.20";
341
+ Возврат "2026.09.01.23";
342
342
  КонецФункции
343
343
 
344
344
  &НаКлиенте
@@ -7634,21 +7634,78 @@
7634
7634
  КонецЕсли;
7635
7635
  КонецЕсли;
7636
7636
  Если Не ТаблицаВРежимеРедактированияMCP(Таблица) Тогда
7637
- ВызватьИсключение "Таблица " + ИмяТаблицы + " вышла из режима редактирования перед вводом в редактор " + ИмяРедактора
7638
- + "; повторный вход: " + СвойствоСтруктуры(РезультатПовторногоРедактированияПередВводом, "error", "");
7637
+ // После Активизировать() web-клиент может закрыть formal edit-mode, но
7638
+ // оставить live-редактор ячейки видимым и доступным. Это тот же безопасный
7639
+ // узкий fallback, что выше: вводим в текущий editor и ниже подтверждаем
7640
+ // значение только по фактической строке таблицы.
7641
+ Если ВидимыйРедакторТаблицыДоступенMCP(Поле) Тогда
7642
+ Попытка
7643
+ РезультатУстановки = ВвестиТекстВВидимыйРедакторТаблицыБезРежимаMCP(Поле, ИмяПоля, Значение, СоздатьЕслиОтсутствует);
7644
+ ИспользованВидимыйРедакторБезРежима = Истина;
7645
+ Исключение
7646
+ ОшибкаУстановки = ОписаниеОшибки();
7647
+ КонецПопытки;
7648
+ Иначе
7649
+ ВызватьИсключение "Таблица " + ИмяТаблицы + " вышла из режима редактирования перед вводом в редактор " + ИмяРедактора
7650
+ + "; повторный вход: " + СвойствоСтруктуры(РезультатПовторногоРедактированияПередВводом, "error", "");
7651
+ КонецЕсли;
7639
7652
  КонецЕсли;
7640
7653
  КонецЕсли;
7641
- ПроверитьРедактированиеРодительскойТаблицыПередВводомMCP(Поле, "Перед вводом поля " + ИмяПоля);
7642
-
7643
- Попытка
7644
- РезультатУстановки = ВвестиТекстВРедакторТаблицыMCP(Таблица, Поле, ИмяПоля, Значение, СоздатьЕслиОтсутствует);
7645
- Исключение
7646
- // Для некоторых редакторов ячеек ВвестиТекст() может бросить исключение уже
7647
- // после фактического изменения значения. Завершаем редактирование и ниже
7648
- // проверяем строку. Если значение не подтвердится, для ссылочных полей будет
7649
- // отдельный fallback через штатную форму выбора.
7650
- ОшибкаУстановки = ОписаниеОшибки();
7651
- КонецПопытки;
7654
+ Если Не ИспользованВидимыйРедакторБезРежима Тогда
7655
+ Если Не ТаблицаВРежимеРедактированияMCP(Таблица) И ВидимыйРедакторТаблицыДоступенMCP(Поле) Тогда
7656
+ // Четвертая точка web-гонки: edit-mode может пропасть уже между
7657
+ // проверкой после Активизировать() и guard-ом родительской таблицы.
7658
+ // Не вводим вслепую: используем ограниченный путь visible-editor и
7659
+ // ниже подтверждаем результат по фактической строке.
7660
+ Попытка
7661
+ РезультатУстановки = ВвестиТекстВВидимыйРедакторТаблицыБезРежимаMCP(Поле, ИмяПоля, Значение, СоздатьЕслиОтсутствует);
7662
+ ИспользованВидимыйРедакторБезРежима = Истина;
7663
+ Исключение
7664
+ ОшибкаУстановки = ОписаниеОшибки();
7665
+ КонецПопытки;
7666
+ КонецЕсли;
7667
+ КонецЕсли;
7668
+ Если Не ИспользованВидимыйРедакторБезРежима Тогда
7669
+ Попытка
7670
+ ПроверитьРедактированиеРодительскойТаблицыПередВводомMCP(Поле, "Перед вводом поля " + ИмяПоля);
7671
+ Исключение
7672
+ ОшибкаПроверкиРодительскойТаблицы = ОписаниеОшибки();
7673
+ Если ВидимыйРедакторТаблицыДоступенMCP(Поле) Тогда
7674
+ Попытка
7675
+ РезультатУстановки = ВвестиТекстВВидимыйРедакторТаблицыБезРежимаMCP(Поле, ИмяПоля, Значение, СоздатьЕслиОтсутствует);
7676
+ ИспользованВидимыйРедакторБезРежима = Истина;
7677
+ Исключение
7678
+ ОшибкаУстановки = ?(ПустаяСтрока(ОшибкаУстановки), "", ОшибкаУстановки + "; ")
7679
+ + "parent_guard=" + ОшибкаПроверкиРодительскойТаблицы + "; visible_editor=" + ОписаниеОшибки();
7680
+ КонецПопытки;
7681
+ Иначе
7682
+ ВызватьИсключение ОшибкаПроверкиРодительскойТаблицы;
7683
+ КонецЕсли;
7684
+ КонецПопытки;
7685
+ КонецЕсли;
7686
+ Если Не ИспользованВидимыйРедакторБезРежима Тогда
7687
+ Попытка
7688
+ РезультатУстановки = ВвестиТекстВРедакторТаблицыMCP(Таблица, Поле, ИмяПоля, Значение, СоздатьЕслиОтсутствует);
7689
+ Исключение
7690
+ // Для некоторых редакторов ячеек ВвестиТекст() может бросить исключение уже
7691
+ // после фактического изменения значения. Завершаем редактирование и ниже
7692
+ // проверяем строку. Если значение не подтвердится, для ссылочных полей будет
7693
+ // отдельный fallback через штатную форму выбора. Если исключение связано
7694
+ // с очередной потерей formal edit-mode, но сам live-редактор всё ещё доступен,
7695
+ // пробуем ограниченный visible-editor путь и всё равно подтверждаем строкой.
7696
+ ОшибкаПрямогоРедактора = ОписаниеОшибки();
7697
+ Если ВидимыйРедакторТаблицыДоступенMCP(Поле) Тогда
7698
+ Попытка
7699
+ РезультатУстановки = ВвестиТекстВВидимыйРедакторТаблицыБезРежимаMCP(Поле, ИмяПоля, Значение, СоздатьЕслиОтсутствует);
7700
+ ИспользованВидимыйРедакторБезРежима = Истина;
7701
+ Исключение
7702
+ ОшибкаУстановки = ОшибкаПрямогоРедактора + "; visible_editor_after_direct_exception=" + ОписаниеОшибки();
7703
+ КонецПопытки;
7704
+ Иначе
7705
+ ОшибкаУстановки = ОшибкаПрямогоРедактора;
7706
+ КонецЕсли;
7707
+ КонецПопытки;
7708
+ КонецЕсли;
7652
7709
  КонецЕсли;
7653
7710
  КонецЕсли;
7654
7711
  КонецЕсли;
@@ -7703,10 +7760,13 @@
7703
7760
  КонецЕсли;
7704
7761
  Исключение
7705
7762
  ОшибкаУстановки = ?(ПустаяСтрока(ОшибкаУстановки), "", ОшибкаУстановки + "; ") + "ФормаВыбора=" + ОписаниеОшибки();
7763
+ ЗавершитьРедактированиеСтрокиТаблицыMCP(Таблица);
7706
7764
  ВосстановитьПозициюСтрокиТаблицыMCP(Таблица, РезультатПозиционированияСтроки, ТекстыПоискаСтроки, ВсеТекстыСтрокиОбязательны);
7707
7765
  КонецПопытки;
7708
7766
  КонецЕсли;
7709
7767
  Если Не Проверено Тогда
7768
+ ЗавершитьРедактированиеСтрокиТаблицыMCP(Таблица);
7769
+ ВосстановитьПозициюСтрокиТаблицыMCP(Таблица, РезультатПозиционированияСтроки, ТекстыПоискаСтроки, ВсеТекстыСтрокиОбязательны);
7710
7770
  ДополнениеОшибки = ?(ПустаяСтрока(ОшибкаУстановки), "", "; ошибка set_field_value: " + ОшибкаУстановки);
7711
7771
  ВызватьИсключение "Поле таблицы " + ИмяПоля + " установлено через " + ИмяРедактора + ", но значение не подтвердилось; текущее значение: " + Проверка.value
7712
7772
  + ". Если это ссылочное поле, значение могло не найтись в справочнике: " + ПодсказкаСозданияСсылочногоЗначенияMCP(ИмяПоля, Значение)
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import Any
5
5
 
6
- MANAGER_PROTOCOL_VERSION = "2026.09.01.20"
6
+ MANAGER_PROTOCOL_VERSION = "2026.09.01.23"
7
7
 
8
8
 
9
9
  @dataclass(slots=True)
@@ -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