answer42 0.4.64__tar.gz → 0.4.65__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.64 → answer42-0.4.65}/PKG-INFO +1 -1
  2. {answer42-0.4.64 → answer42-0.4.65}/pyproject.toml +1 -1
  3. {answer42-0.4.64 → answer42-0.4.65}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +195 -52
  4. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/server.py +159 -2
  5. {answer42-0.4.64 → answer42-0.4.65}/.gitignore +0 -0
  6. {answer42-0.4.64 → answer42-0.4.65}/LICENSE +0 -0
  7. {answer42-0.4.64 → answer42-0.4.65}/README.md +0 -0
  8. {answer42-0.4.64 → answer42-0.4.65}/credentials.example.json +0 -0
  9. {answer42-0.4.64 → answer42-0.4.65}/docs/agent-installation.md +0 -0
  10. {answer42-0.4.64 → answer42-0.4.65}/docs/architecture.md +0 -0
  11. {answer42-0.4.64 → answer42-0.4.65}/docs/assets/answer42-logo.png +0 -0
  12. {answer42-0.4.64 → answer42-0.4.65}/docs/assets/platform42-logo.svg +0 -0
  13. {answer42-0.4.64 → answer42-0.4.65}/docs/installation.md +0 -0
  14. {answer42-0.4.64 → answer42-0.4.65}/scripts/build_cf.py +0 -0
  15. {answer42-0.4.64 → answer42-0.4.65}/scripts/build_pages.py +0 -0
  16. {answer42-0.4.64 → answer42-0.4.65}/scripts/e2e_http_smoke.py +0 -0
  17. {answer42-0.4.64 → answer42-0.4.65}/scripts/e2e_stable.py +0 -0
  18. {answer42-0.4.64 → answer42-0.4.65}/scripts/openclaw_mcp_autoreload.py +0 -0
  19. {answer42-0.4.64 → answer42-0.4.65}/scripts/rag_cli.py +0 -0
  20. {answer42-0.4.64 → answer42-0.4.65}/src/cf/ConfigDumpInfo.xml +0 -0
  21. {answer42-0.4.64 → answer42-0.4.65}/src/cf/Configuration.xml +0 -0
  22. {answer42-0.4.64 → answer42-0.4.65}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  23. {answer42-0.4.64 → answer42-0.4.65}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  24. {answer42-0.4.64 → answer42-0.4.65}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  25. {answer42-0.4.64 → answer42-0.4.65}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  26. {answer42-0.4.64 → answer42-0.4.65}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  27. {answer42-0.4.64 → answer42-0.4.65}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  28. {answer42-0.4.64 → answer42-0.4.65}/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
  29. {answer42-0.4.64 → answer42-0.4.65}/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
  30. {answer42-0.4.64 → answer42-0.4.65}/src/client_cf/ConfigDumpInfo.xml +0 -0
  31. {answer42-0.4.64 → answer42-0.4.65}/src/client_cf/Configuration.xml +0 -0
  32. {answer42-0.4.64 → answer42-0.4.65}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  33. {answer42-0.4.64 → answer42-0.4.65}/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
  34. {answer42-0.4.64 → answer42-0.4.65}/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
  35. {answer42-0.4.64 → answer42-0.4.65}/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
  36. {answer42-0.4.64 → answer42-0.4.65}/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
  37. {answer42-0.4.64 → answer42-0.4.65}/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
  38. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/__init__.py +0 -0
  39. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  40. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  41. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/assets/__init__.py +0 -0
  42. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  43. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  44. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/assets/skills/answer42-universal-report/SKILL.md +0 -0
  45. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/auth.py +0 -0
  46. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/bridge.py +0 -0
  47. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/credentials.py +0 -0
  48. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/os_support.py +0 -0
  49. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/platform.py +0 -0
  50. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/protocol.py +0 -0
  51. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/__init__.py +0 -0
  52. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/detect.py +0 -0
  53. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/dump.py +0 -0
  54. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/model.py +0 -0
  55. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/parsers.py +0 -0
  56. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/service.py +0 -0
  57. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/store.py +0 -0
  58. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/summary_backend.py +0 -0
  59. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/rag/summary_vector.py +0 -0
  60. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/recorder.py +0 -0
  61. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/release_helper.py +0 -0
  62. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/runtime.py +0 -0
  63. {answer42-0.4.64 → answer42-0.4.65}/src/mcp_1c/skill_installer.py +0 -0
  64. {answer42-0.4.64 → answer42-0.4.65}/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.64
3
+ Version: 0.4.65
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.64"
3
+ version = "0.4.65"
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"
@@ -2302,6 +2302,7 @@
2302
2302
  &НаКлиенте
2303
2303
  Функция ПоискВКомандахКомандногоИнтерфейса(Команды, СтрокаПоиска, Максимум)
2304
2304
  Результат = Новый Массив;
2305
+ КлючиСовпадений = Новый Соответствие;
2305
2306
  НормПоиск = НРег(СтрокаПоиска);
2306
2307
  КороткийПоиск = НормПоиск;
2307
2308
  Если СтрДлина(КороткийПоиск) > 3 Тогда
@@ -2315,13 +2316,18 @@
2315
2316
  ТекстДляПоиска = НРег(Заголовок + " " + ТекстПути + " " + Ссылка);
2316
2317
  Если СтрНайти(ТекстДляПоиска, НормПоиск) > 0
2317
2318
  Или (КороткийПоиск <> НормПоиск И СтрНайти(ТекстДляПоиска, КороткийПоиск) > 0) Тогда
2319
+ Оценка = КомандныйИнтерфейсОценкаСовпадения(Заголовок, ТекстПути, Ссылка, НормПоиск);
2320
+ КлючСовпадения = КомандныйИнтерфейсКлючСовпадения(Заголовок, Ссылка);
2321
+ Если КлючиСовпадений.Получить(КлючСовпадения) <> Неопределено Тогда
2322
+ Продолжить;
2323
+ КонецЕсли;
2318
2324
  Совпадение = Новый Структура;
2319
2325
  Совпадение.Вставить("title", Заголовок);
2320
2326
  Совпадение.Вставить("navigation_url", Ссылка);
2321
2327
  Совпадение.Вставить("path", Путь);
2322
2328
  Совпадение.Вставить("path_text", ТекстПути);
2323
- Оценка = КомандныйИнтерфейсОценкаСовпадения(Заголовок, ТекстПути, Ссылка, НормПоиск);
2324
2329
  Совпадение.Вставить("score", Оценка);
2330
+ КлючиСовпадений.Вставить(КлючСовпадения, Совпадение);
2325
2331
  ИндексВставки = Результат.Количество();
2326
2332
  Для НомерСовпадения = 0 По Результат.Количество() - 1 Цикл
2327
2333
  Если СвойствоСтруктуры(Результат[НомерСовпадения], "score", 0) < Оценка Тогда
@@ -2331,13 +2337,23 @@
2331
2337
  КонецЦикла;
2332
2338
  Результат.Вставить(ИндексВставки, Совпадение);
2333
2339
  Если Максимум > 0 И Результат.Количество() > Максимум Тогда
2340
+ УдаляемоеСовпадение = Результат[Результат.Количество() - 1];
2334
2341
  Результат.Удалить(Результат.Количество() - 1);
2342
+ КлючиСовпадений.Удалить(КомандныйИнтерфейсКлючСовпадения(СвойствоСтруктуры(УдаляемоеСовпадение, "title", ""), СвойствоСтруктуры(УдаляемоеСовпадение, "navigation_url", "")));
2335
2343
  КонецЕсли;
2336
2344
  КонецЕсли;
2337
2345
  КонецЦикла;
2338
2346
  Возврат Результат;
2339
2347
  КонецФункции
2340
2348
 
2349
+ &НаКлиенте
2350
+ Функция КомандныйИнтерфейсКлючСовпадения(Заголовок, Ссылка)
2351
+ Если Не ПустаяСтрока(Ссылка) Тогда
2352
+ Возврат НРег(Ссылка);
2353
+ КонецЕсли;
2354
+ Возврат НРег(Заголовок);
2355
+ КонецФункции
2356
+
2341
2357
  &НаКлиенте
2342
2358
  Функция КомандныйИнтерфейсПутьВСтроку(Путь)
2343
2359
  Результат = "";
@@ -2352,19 +2368,26 @@
2352
2368
 
2353
2369
  &НаКлиенте
2354
2370
  Функция КомандныйИнтерфейсОценкаСовпадения(Заголовок, ТекстПути, Ссылка, НормПоиск)
2355
- Если НРег(Заголовок) = НормПоиск Тогда
2356
- Возврат 100;
2357
- КонецЕсли;
2358
- Если СтрНайти(НРег(Заголовок), НормПоиск) > 0 Тогда
2359
- Возврат 80;
2371
+ Оценка = 0;
2372
+ НормЗаголовок = НРег(Заголовок);
2373
+ НормСсылка = НРег(Ссылка);
2374
+ Если НормЗаголовок = НормПоиск Тогда
2375
+ Оценка = 100;
2376
+ ИначеЕсли СтрНайти(НормЗаголовок, НормПоиск) > 0 Тогда
2377
+ Оценка = 80;
2378
+ ИначеЕсли СтрНайти(НРег(ТекстПути), НормПоиск) > 0 Тогда
2379
+ Оценка = 50;
2380
+ ИначеЕсли СтрНайти(НормСсылка, НормПоиск) > 0 Тогда
2381
+ Оценка = 30;
2360
2382
  КонецЕсли;
2361
- Если СтрНайти(НРег(ТекстПути), НормПоиск) > 0 Тогда
2362
- Возврат 50;
2383
+ Если СтрНайти(НормСсылка, "справочник.") > 0 Тогда
2384
+ Оценка = Оценка + 15;
2385
+ ИначеЕсли СтрНайти(НормСсылка, "документ.") > 0 Тогда
2386
+ Оценка = Оценка + 8;
2387
+ ИначеЕсли СтрНайти(НормСсылка, "отчет.") > 0 Тогда
2388
+ Оценка = Оценка - 5;
2363
2389
  КонецЕсли;
2364
- Если СтрНайти(НРег(Ссылка), НормПоиск) > 0 Тогда
2365
- Возврат 30;
2366
- КонецЕсли;
2367
- Возврат 0;
2390
+ Возврат Оценка;
2368
2391
  КонецФункции
2369
2392
 
2370
2393
  &НаКлиенте
@@ -2820,7 +2843,7 @@
2820
2843
  Исключение
2821
2844
  Ошибки.Добавить("Активизировать: " + ОписаниеОшибки());
2822
2845
  КонецПопытки;
2823
- Возврат Новый Структура("maximized,method,errors,window", Истина, "activate_only", Ошибки, ОбъектВСтруктуру(АктивноеОкно, 0));
2846
+ Возврат Новый Структура("maximized,method,errors,window", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно));
2824
2847
  КонецФункции
2825
2848
 
2826
2849
  &НаКлиенте
@@ -2831,40 +2854,65 @@
2831
2854
  ПоказыватьСкрытые = СвойствоСтруктуры(Параметры, "include_hidden", Ложь);
2832
2855
  ВключатьДанные = СвойствоСтруктуры(Параметры, "include_data", Ложь);
2833
2856
  ВключатьКомандныеПанели = СвойствоСтруктуры(Параметры, "include_command_panels", Ложь);
2857
+ ВключатьСостояниеВидимости = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "include_visibility_state", Истина), Истина);
2858
+ ВключатьСостояниеДоступности = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "include_availability_state", Истина), Истина);
2859
+ ВключатьСостояниеТолькоПросмотр = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "include_read_only_state", Истина), Истина);
2860
+ ВключатьТекущееСостояние = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "include_current_state", Истина), Истина);
2834
2861
  ИмяРодителя = СвойствоСтруктуры(Параметры, "parent_name", "");
2835
2862
  ПлоскийРежим = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "flat", Ложь), Ложь);
2836
2863
  ИмяФильтра = СвойствоСтруктуры(Параметры, "name", "");
2837
2864
  ЗаголовокФильтра = СвойствоСтруктуры(Параметры, "title", "");
2838
2865
  ТипФильтра = СвойствоСтруктуры(Параметры, "type", "");
2839
2866
  ТолькоКомандныеПанели = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "command_panels_only", Ложь), Ложь);
2867
+ ВключатьТайминги = БулевоИзЗначенияMCP(СвойствоСтруктуры(Параметры, "include_timings", Ложь), Ложь);
2840
2868
  Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
2869
+ Тайминги = Новый Структура;
2841
2870
  Лог("ui_tree: start include_hidden=" + Строка(ПоказыватьСкрытые) + " include_data=" + Строка(ВключатьДанные) + " parent_name='" + ИмяРодителя + "' flat=" + Строка(ПлоскийРежим));
2842
2871
  ОтправитьСобытиеПрогрессаMCP("ui_tree", "start", Новый Структура("include_hidden,include_data,parent_name,flat", ПоказыватьСкрытые, ВключатьДанные, ИмяРодителя, ПлоскийРежим));
2872
+ НачалоАктивногоОкна = ТекущаяУниверсальнаяДатаВМиллисекундах();
2843
2873
  АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
2874
+ Тайминги.Вставить("active_window_ms", ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоАктивногоОкна);
2844
2875
  ОтправитьСобытиеПрогрессаMCP("ui_tree", "active_window_done");
2845
2876
  КешСериализации = НовыйКешПоискаКнопкиMCP();
2846
2877
  СчетчикУзлов = Новый Структура("nodes", 0);
2847
2878
  КорневойОбъект = АктивноеОкно;
2848
2879
  Если Не ПустаяСтрока(ИмяРодителя) Тогда
2880
+ НачалоПоискаРодителя = ТекущаяУниверсальнаяДатаВМиллисекундах();
2849
2881
  КорневойОбъект = НайтиОбъектБыстроMCP(АктивноеОкно, ИмяРодителя, "", "", КешСериализации);
2850
2882
  Если КорневойОбъект = Неопределено Тогда
2851
2883
  КорневойОбъект = НайтиОбъектРекурсивно(АктивноеОкно, ИмяРодителя, "", "", 0, СчетчикУзлов, КешСериализации);
2852
2884
  КонецЕсли;
2885
+ Тайминги.Вставить("parent_search_ms", ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоПоискаРодителя);
2853
2886
  Если КорневойОбъект = Неопределено Тогда
2854
2887
  Время = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
2855
2888
  Лог("ui_tree: parent not found ms=" + Строка(Время) + " nodes=" + Строка(СчетчикУзлов.nodes));
2856
- Возврат Новый Структура("found,parent_name,reason,_tree_ms,_nodes_serialized,_nodes_visited", Ложь, ИмяРодителя, "parent_not_found", Время, СчетчикУзлов.nodes, СчетчикУзлов.nodes);
2889
+ РезультатНеНайден = Новый Структура("found,parent_name,reason,_tree_ms,_nodes_serialized,_nodes_visited", Ложь, ИмяРодителя, "parent_not_found", Время, СчетчикУзлов.nodes, СчетчикУзлов.nodes);
2890
+ Если ВключатьТайминги Тогда
2891
+ Тайминги.Вставить("total_ms", Время);
2892
+ Тайминги.Вставить("nodes_visited", СчетчикУзлов.nodes);
2893
+ Тайминги.Вставить("serialize_stats", КешСериализации.Статистика);
2894
+ РезультатНеНайден.Вставить("_timings", Тайминги);
2895
+ КонецЕсли;
2896
+ Возврат РезультатНеНайден;
2857
2897
  КонецЕсли;
2858
2898
  СчетчикУзлов.nodes = 0;
2859
2899
  КонецЕсли;
2860
2900
  Если ПлоскийРежим Тогда
2861
- Возврат ПлоскоеДеревоИнтерфейсаMCP(КорневойОбъект, ИмяФильтра, ЗаголовокФильтра, ТипФильтра, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, КешСериализации, СчетчикУзлов, Начало, ИмяРодителя);
2901
+ Возврат ПлоскоеДеревоИнтерфейсаMCP(КорневойОбъект, ИмяФильтра, ЗаголовокФильтра, ТипФильтра, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Начало, ИмяРодителя, ВключатьТайминги, Тайминги);
2862
2902
  КонецЕсли;
2863
- Результат = ОбъектВСтруктуру(КорневойОбъект, 0, ПоказыватьСкрытые, Истина, Истина, Ложь, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели);
2903
+ НачалоСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
2904
+ Результат = ОбъектВСтруктуру(КорневойОбъект, 0, ПоказыватьСкрытые, Истина, Истина, Ложь, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
2905
+ Тайминги.Вставить("serialize_tree_ms", ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоСериализации);
2864
2906
  Время = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
2865
2907
  Если Результат <> Неопределено И Результат <> "__skip__" Тогда
2866
2908
  Результат.Вставить("_tree_ms", Время);
2867
2909
  Результат.Вставить("_nodes_serialized", СчетчикУзлов.nodes);
2910
+ Если ВключатьТайминги Тогда
2911
+ Тайминги.Вставить("total_ms", Время);
2912
+ Тайминги.Вставить("nodes_serialized", СчетчикУзлов.nodes);
2913
+ Тайминги.Вставить("serialize_stats", КешСериализации.Статистика);
2914
+ Результат.Вставить("_timings", Тайминги);
2915
+ КонецЕсли;
2868
2916
  Если Не ПустаяСтрока(ИмяРодителя) Тогда
2869
2917
  Результат.Вставить("root_parent_name", ИмяРодителя);
2870
2918
  КонецЕсли;
@@ -2875,21 +2923,35 @@
2875
2923
  КонецФункции
2876
2924
 
2877
2925
  &НаКлиенте
2878
- Функция ПлоскоеДеревоИнтерфейсаMCP(КорневойОбъект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, КешСериализации, СчетчикУзлов, Начало, ИмяРодителя)
2926
+ Функция ПлоскоеДеревоИнтерфейсаMCP(КорневойОбъект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Начало, ИмяРодителя, ВключатьТайминги = Ложь, Тайминги = Неопределено)
2879
2927
  Найденные = Новый Массив;
2880
2928
  Метод = "flat_traversal";
2929
+ Если Тайминги = Неопределено Тогда
2930
+ Тайминги = Новый Структура;
2931
+ КонецЕсли;
2881
2932
  Если Не ТолькоКомандныеПанели И ПустаяСтрока(Заголовок) И (Не ПустаяСтрока(Имя) Или Не ПустаяСтрока(ТипОбъекта)) Тогда
2882
- НайденныеБыстро = НайтиУзлыДереваИнтерфейсаБыстроMCP(КорневойОбъект, Имя, ТипОбъекта, ПоказыватьСкрытые, ВключатьДанные, КешСериализации);
2933
+ НачалоБыстрогоПоиска = ТекущаяУниверсальнаяДатаВМиллисекундах();
2934
+ НайденныеБыстро = НайтиУзлыДереваИнтерфейсаБыстроMCP(КорневойОбъект, Имя, ТипОбъекта, ПоказыватьСкрытые, ВключатьДанные, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации);
2935
+ Тайминги.Вставить("fast_search_ms", ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоБыстрогоПоиска);
2883
2936
  Если НайденныеБыстро <> Неопределено Тогда
2884
2937
  Найденные = НайденныеБыстро;
2885
2938
  Метод = "fast_platform_search_objects";
2886
2939
  КонецЕсли;
2887
2940
  КонецЕсли;
2888
2941
  Если Метод = "flat_traversal" Тогда
2889
- НайтиУзлыДереваИнтерфейсаПлоскоMCP(КорневойОбъект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, КешСериализации, СчетчикУзлов, Найденные, 0, Ложь);
2942
+ НачалоПлоскогоОбхода = ТекущаяУниверсальнаяДатаВМиллисекундах();
2943
+ НайтиУзлыДереваИнтерфейсаПлоскоMCP(КорневойОбъект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Найденные, 0, Ложь);
2944
+ Тайминги.Вставить("flat_traversal_ms", ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоПлоскогоОбхода);
2890
2945
  КонецЕсли;
2891
2946
  Время = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
2892
2947
  Результат = Новый Структура("found,count,objects,flat,filter,method,_tree_ms,_nodes_serialized,_nodes_visited", Найденные.Количество() > 0, Найденные.Количество(), Найденные, Истина, Новый Структура("name,title,type,command_panels_only", Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели), Метод, Время, Найденные.Количество(), СчетчикУзлов.nodes);
2948
+ Если ВключатьТайминги Тогда
2949
+ Тайминги.Вставить("total_ms", Время);
2950
+ Тайминги.Вставить("nodes_serialized", Найденные.Количество());
2951
+ Тайминги.Вставить("nodes_visited", СчетчикУзлов.nodes);
2952
+ Тайминги.Вставить("serialize_stats", КешСериализации.Статистика);
2953
+ Результат.Вставить("_timings", Тайминги);
2954
+ КонецЕсли;
2893
2955
  Если Не ПустаяСтрока(ИмяРодителя) Тогда
2894
2956
  Результат.Вставить("root_parent_name", ИмяРодителя);
2895
2957
  КонецЕсли;
@@ -2899,7 +2961,7 @@
2899
2961
  КонецФункции
2900
2962
 
2901
2963
  &НаКлиенте
2902
- Процедура НайтиУзлыДереваИнтерфейсаПлоскоMCP(Объект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина, ВнутриКоманднойПанели)
2964
+ Процедура НайтиУзлыДереваИнтерфейсаПлоскоMCP(Объект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина, ВнутриКоманднойПанели)
2903
2965
  Если Объект = Неопределено Тогда
2904
2966
  Возврат;
2905
2967
  КонецЕсли;
@@ -2921,17 +2983,17 @@
2921
2983
  ВключитьВПлоскийРезультат = Не ТолькоКомандныеПанели Или ВнутриКоманднойПанели Или ТекущийУзелКоманднойПанели;
2922
2984
  Если ОбъектСоответствует(ИмяТекущего, ЗаголовокТекущего, ТипТекущего, Имя, Заголовок, ТипОбъекта)
2923
2985
  И ВключитьВПлоскийРезультат Тогда
2924
- Найденные.Добавить(ОбъектВСтруктуру(Объект, 0, ПоказыватьСкрытые, Истина, Истина, Ложь, КешСериализации, ВключатьДанные, Ложь, Неопределено, Ложь));
2986
+ Найденные.Добавить(ОбъектВСтруктуру(Объект, 0, ПоказыватьСкрытые, Истина, Истина, Ложь, КешСериализации, ВключатьДанные, Ложь, Неопределено, Ложь, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние));
2925
2987
  КонецЕсли;
2926
2988
  Если ВключатьКомандныеПанели Тогда
2927
2989
  Панель = КоманднаяПанельСКешемMCP(Объект, КешСериализации);
2928
2990
  Если Панель <> Неопределено Тогда
2929
- НайтиУзлыДереваИнтерфейсаПлоскоMCP(Панель, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина + 1, ВнутриКоманднойПанели Или ТекущийУзелКоманднойПанели);
2991
+ НайтиУзлыДереваИнтерфейсаПлоскоMCP(Панель, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина + 1, ВнутриКоманднойПанели Или ТекущийУзелКоманднойПанели);
2930
2992
  КонецЕсли;
2931
2993
  КонецЕсли;
2932
2994
  Подчиненные = БезопасныеПодчиненныеСКешемMCP(Объект, КешСериализации);
2933
2995
  Для Каждого Подчиненный Из Подчиненные Цикл
2934
- НайтиУзлыДереваИнтерфейсаПлоскоMCP(Подчиненный, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина + 1, ВнутриКоманднойПанели Или ТекущийУзелКоманднойПанели);
2996
+ НайтиУзлыДереваИнтерфейсаПлоскоMCP(Подчиненный, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина + 1, ВнутриКоманднойПанели Или ТекущийУзелКоманднойПанели);
2935
2997
  КонецЦикла;
2936
2998
  Исключение
2937
2999
  Инфо = ИнформацияОбОшибке();
@@ -2940,7 +3002,7 @@
2940
3002
  КонецПроцедуры
2941
3003
 
2942
3004
  &НаКлиенте
2943
- Функция НайтиУзлыДереваИнтерфейсаБыстроMCP(КорневойОбъект, Имя, ТипОбъектаСтрокой, ПоказыватьСкрытые, ВключатьДанные, КешСериализации)
3005
+ Функция НайтиУзлыДереваИнтерфейсаБыстроMCP(КорневойОбъект, Имя, ТипОбъектаСтрокой, ПоказыватьСкрытые, ВключатьДанные, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации)
2944
3006
  ТипОбъекта = ТипТестируемогоОбъектаMCP(ТипОбъектаСтрокой);
2945
3007
  Если Не ПустаяСтрока(ТипОбъектаСтрокой) И ТипОбъекта = Неопределено Тогда
2946
3008
  Возврат Неопределено;
@@ -3543,10 +3605,18 @@
3543
3605
 
3544
3606
  &НаКлиенте
3545
3607
  Функция НовыйКешПоискаКнопкиMCP()
3546
- Статистика = Новый Структура("nodes,children_arrays,buttons_checked,fast_attempts,fast_found,fast_ms,method", 0, 0, 0, 0, Ложь, 0, "");
3608
+ Статистика = Новый Структура("nodes,children_arrays,buttons_checked,fast_attempts,fast_found,fast_ms,method,serialize_visibility_ms,serialize_type_ms,serialize_base_props_ms,serialize_data_ms,serialize_children_fetch_ms,serialize_current_page_ms,serialize_pages_ms,serialize_command_panels_ms,serialize_children_loop_ms", 0, 0, 0, 0, Ложь, 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0);
3547
3609
  Возврат Новый Структура("Методы,Свойства,Подчиненные,КомандныеПанели,ТекущиеСтраницы,Статистика", Новый Соответствие, Новый Соответствие, Новый Соответствие, Новый Соответствие, Новый Соответствие, Статистика);
3548
3610
  КонецФункции
3549
3611
 
3612
+ &НаКлиенте
3613
+ Процедура ДобавитьТаймингСериализацииMCP(КешПоиска, ИмяПоля, Начало)
3614
+ Если КешПоиска = Неопределено Тогда
3615
+ Возврат;
3616
+ КонецЕсли;
3617
+ КешПоиска.Статистика.Вставить(ИмяПоля, СвойствоСтруктуры(КешПоиска.Статистика, ИмяПоля, 0) + (ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало));
3618
+ КонецПроцедуры
3619
+
3550
3620
  &НаКлиенте
3551
3621
  Функция КлючСвойстваКешаMCP(ИмяСвойства)
3552
3622
  Возврат "property:" + ИмяСвойства;
@@ -9272,7 +9342,7 @@
9272
9342
  #Область Сериализация
9273
9343
 
9274
9344
  &НаКлиенте
9275
- Функция ОбъектВСтруктуру(Объект, ТекущаяГлубина, ПоказыватьСкрытые = Истина, РодительскаяВидимость = Истина, РодительскаяДоступность = Истина, РодительТолькоПросмотр = Ложь, КешСериализации = Неопределено, ВключатьДанные = Истина, СериализоватьДетей = Истина, СчетчикУзлов = Неопределено, ВключатьКомандныеПанели = Истина)
9345
+ Функция ОбъектВСтруктуру(Объект, ТекущаяГлубина, ПоказыватьСкрытые = Истина, РодительскаяВидимость = Истина, РодительскаяДоступность = Истина, РодительТолькоПросмотр = Ложь, КешСериализации = Неопределено, ВключатьДанные = Истина, СериализоватьДетей = Истина, СчетчикУзлов = Неопределено, ВключатьКомандныеПанели = Истина, ВключатьСостояниеВидимости = Истина, ВключатьСостояниеДоступности = Истина, ВключатьСостояниеТолькоПросмотр = Истина, ВключатьТекущееСостояние = Истина)
9276
9346
  Если Объект = Неопределено Тогда
9277
9347
  Возврат Неопределено;
9278
9348
  КонецЕсли;
@@ -9285,18 +9355,56 @@
9285
9355
  Если ЭтоСкрываемаяГруппа(Объект) Тогда
9286
9356
  Возврат "__skip__";
9287
9357
  КонецЕсли;
9288
- СобственнаяВидимость = БезопасноеЗначениеМетодаСКешемMCP(Объект, "ТекущаяВидимость", БезопасноеСвойствоСКешемMCP(Объект, "Видимость", Истина, КешСериализации), КешСериализации);
9289
- СобственнаяДоступность = БезопасноеЗначениеМетодаСКешемMCP(Объект, "ТекущаяДоступность", БезопасноеСвойствоСКешемMCP(Объект, "Доступность", Истина, КешСериализации), КешСериализации);
9290
- СобственноеТолькоПросмотр = БезопасноеЗначениеМетодаСКешемMCP(Объект, "ТекущееТолькоПросмотр", БезопасноеСвойствоСКешемMCP(Объект, "ТолькоПросмотр", Ложь, КешСериализации), КешСериализации);
9291
- Видимость = СобственнаяВидимость;
9292
- Доступность = СобственнаяДоступность;
9293
- ТолькоПросмотр = СобственноеТолькоПросмотр;
9358
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9359
+ ТипСтрокой = Строка(ТипЗнч(Объект));
9360
+ ТипОбъекта = НРег(НормализованноеИмяТипа(ТипСтрокой));
9361
+ ЭтоДекорация = ТипОбъекта = "тестируемаядекорацияформы";
9362
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_type_ms", НачалоФазыСериализации);
9363
+
9364
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9365
+ Если Не РодительскаяВидимость Тогда
9366
+ // Невидимость родителя распространяется на всех потомков, не дергаем дорогие проверки состояния ребенка.
9367
+ СобственнаяВидимость = Ложь;
9368
+ ИначеЕсли ВключатьСостояниеВидимости Тогда
9369
+ СобственнаяВидимость = БезопасноеЗначениеМетодаСКешемMCP(Объект, "ТекущаяВидимость", БезопасноеСвойствоСКешемMCP(Объект, "Видимость", Истина, КешСериализации), КешСериализации);
9370
+ Иначе
9371
+ // Видимость не нужна ни для фильтрации, ни для ответа; считаем узел видимым, чтобы не блокировать другие явно запрошенные состояния.
9372
+ СобственнаяВидимость = Истина;
9373
+ КонецЕсли;
9374
+ Видимость = РодительскаяВидимость И СобственнаяВидимость;
9375
+ Если Не Видимость Тогда
9376
+ СобственнаяДоступность = Ложь;
9377
+ СобственноеТолькоПросмотр = Ложь;
9378
+ Доступность = Ложь;
9379
+ ТолькоПросмотр = Ложь;
9380
+ ИначеЕсли ЭтоДекорация Или Не ВключатьСостояниеДоступности Тогда
9381
+ // У декораций и легких профилей нет полезной доступности/только просмотра; не тратим время на platform calls.
9382
+ СобственнаяДоступность = РодительскаяДоступность;
9383
+ СобственноеТолькоПросмотр = Ложь;
9384
+ Доступность = РодительскаяДоступность;
9385
+ ТолькоПросмотр = РодительТолькоПросмотр;
9386
+ ИначеЕсли Не РодительскаяДоступность Тогда
9387
+ // Недоступность родителя распространяется на потомков, read_only не проверяем.
9388
+ СобственнаяДоступность = Ложь;
9389
+ СобственноеТолькоПросмотр = Ложь;
9390
+ Доступность = Ложь;
9391
+ ТолькоПросмотр = РодительТолькоПросмотр;
9392
+ Иначе
9393
+ СобственнаяДоступность = БезопасноеЗначениеМетодаСКешемMCP(Объект, "ТекущаяДоступность", БезопасноеСвойствоСКешемMCP(Объект, "Доступность", Истина, КешСериализации), КешСериализации);
9394
+ Доступность = РодительскаяДоступность И СобственнаяДоступность;
9395
+ Если Доступность И ВключатьСостояниеТолькоПросмотр Тогда
9396
+ СобственноеТолькоПросмотр = БезопасноеЗначениеМетодаСКешемMCP(Объект, "ТекущееТолькоПросмотр", БезопасноеСвойствоСКешемMCP(Объект, "ТолькоПросмотр", Ложь, КешСериализации), КешСериализации);
9397
+ ТолькоПросмотр = РодительТолькоПросмотр Или СобственноеТолькоПросмотр;
9398
+ Иначе
9399
+ СобственноеТолькоПросмотр = Ложь;
9400
+ ТолькоПросмотр = РодительТолькоПросмотр;
9401
+ КонецЕсли;
9402
+ КонецЕсли;
9403
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_visibility_ms", НачалоФазыСериализации);
9294
9404
  Если Не ПоказыватьСкрытые И ТекущаяГлубина > 0 И Не Видимость Тогда
9295
9405
  Возврат Неопределено;
9296
9406
  КонецЕсли;
9297
-
9298
- ТипСтрокой = Строка(ТипЗнч(Объект));
9299
- ТипОбъекта = НРег(НормализованноеИмяТипа(ТипСтрокой));
9407
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9300
9408
  Результат = Новый Структура;
9301
9409
  Результат.Вставить("type", ТипСтрокой);
9302
9410
  Результат.Вставить("name", БезопасноеСвойствоСКешемMCP(Объект, "Имя", "", КешСериализации));
@@ -9305,14 +9413,30 @@
9305
9413
  Результат.Вставить("navigation_url", БезопасноеСвойствоСКешемMCP(Объект, "НавигационнаяСсылка", "", КешСериализации));
9306
9414
  Результат.Вставить("main", БезопасноеСвойствоСКешемMCP(Объект, "Основное", БезопасноеСвойствоСКешемMCP(Объект, "ГлавноеОкно", Ложь, КешСериализации), КешСериализации));
9307
9415
  Результат.Вставить("home", БезопасноеСвойствоСКешемMCP(Объект, "НачальнаяСтраница", Ложь, КешСериализации));
9308
- Результат.Вставить("visible", Видимость);
9309
- Результат.Вставить("available", Доступность);
9310
- Результат.Вставить("enabled", Доступность);
9311
- Результат.Вставить("read_only", ТолькоПросмотр);
9312
- Результат.Вставить("current_visible", СобственнаяВидимость);
9313
- Результат.Вставить("current_available", СобственнаяДоступность);
9314
- Результат.Вставить("current_read_only", СобственноеТолькоПросмотр);
9416
+ Если ВключатьСостояниеВидимости Тогда
9417
+ Результат.Вставить("visible", Видимость);
9418
+ КонецЕсли;
9419
+ Если ВключатьСостояниеДоступности Тогда
9420
+ Результат.Вставить("available", Доступность);
9421
+ Результат.Вставить("enabled", Доступность);
9422
+ КонецЕсли;
9423
+ Если ВключатьСостояниеТолькоПросмотр Тогда
9424
+ Результат.Вставить("read_only", ТолькоПросмотр);
9425
+ КонецЕсли;
9426
+ Если ВключатьТекущееСостояние Тогда
9427
+ Если ВключатьСостояниеВидимости Тогда
9428
+ Результат.Вставить("current_visible", СобственнаяВидимость);
9429
+ КонецЕсли;
9430
+ Если ВключатьСостояниеДоступности Тогда
9431
+ Результат.Вставить("current_available", СобственнаяДоступность);
9432
+ КонецЕсли;
9433
+ Если ВключатьСостояниеТолькоПросмотр Тогда
9434
+ Результат.Вставить("current_read_only", СобственноеТолькоПросмотр);
9435
+ КонецЕсли;
9436
+ КонецЕсли;
9437
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_base_props_ms", НачалоФазыСериализации);
9315
9438
 
9439
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9316
9440
  Если ВключатьДанные И (ТипОбъекта = "тестируемоеполеформы" Или ТипОбъекта = "тестируемаякнопкаформы"
9317
9441
  Или ТипОбъекта = "тестируемаякнопкакоманднойпанели" Или ТипОбъекта = "тестируемаятаблицаформы"
9318
9442
  Или ТипОбъекта = "тестируемыенадпись" Или ТипОбъекта = "тестируемыйпереключатель"
@@ -9330,60 +9454,78 @@
9330
9454
  Иначе
9331
9455
  Результат.Вставить("data", "");
9332
9456
  КонецЕсли;
9457
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_data_ms", НачалоФазыСериализации);
9333
9458
 
9334
9459
  Если Не СериализоватьДетей Тогда
9335
9460
  Возврат Результат;
9336
9461
  КонецЕсли;
9337
9462
 
9463
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9338
9464
  Дети = БезопасныеПодчиненныеСКешемMCP(Объект, КешСериализации);
9465
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_children_fetch_ms", НачалоФазыСериализации);
9339
9466
  МассивДетей = Новый Массив;
9340
9467
  ИмяТекущейСтраницы = "";
9341
9468
  ТекущаяСтраницаГруппы = Неопределено;
9469
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9342
9470
  Попытка
9343
9471
  ТекущаяСтраницаГруппы = Объект.ПолучитьТекущуюСтраницу();
9344
9472
  ИмяТекущейСтраницы = БезопасноеСвойствоСКешемMCP(ТекущаяСтраницаГруппы, "Имя", "", КешСериализации);
9345
9473
  Если Не ПустаяСтрока(ИмяТекущейСтраницы) Тогда
9346
9474
  Результат.Вставить("current_page_name", ИмяТекущейСтраницы);
9347
- Результат.Вставить("current_page", ОбъектВСтруктуру(ТекущаяСтраницаГруппы, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, Ложь, СчетчикУзлов, Ложь));
9475
+ Результат.Вставить("current_page", ОбъектВСтруктуру(ТекущаяСтраницаГруппы, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, Ложь, СчетчикУзлов, Ложь, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние));
9348
9476
  КонецЕсли;
9349
9477
  Исключение
9350
9478
  ИмяТекущейСтраницы = "";
9351
9479
  ТекущаяСтраницаГруппы = Неопределено;
9352
9480
  КонецПопытки;
9481
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_current_page_ms", НачалоФазыСериализации);
9353
9482
  Если Не ПустаяСтрока(ИмяТекущейСтраницы) Тогда
9483
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9354
9484
  Страницы = Новый Массив;
9355
9485
  Для Каждого СтраницаГруппы Из Дети Цикл
9356
- ДанныеСтраницы = ОбъектВСтруктуру(СтраницаГруппы, ТекущаяГлубина + 1, Истина, Видимость, Доступность, ТолькоПросмотр, КешСериализации, Ложь, Ложь, СчетчикУзлов, Ложь);
9486
+ СтраницаТекущая = БезопасноеСвойствоСКешемMCP(СтраницаГруппы, "Имя", "", КешСериализации) = ИмяТекущейСтраницы;
9487
+ ВидимостьСтраницы = Видимость И СтраницаТекущая;
9488
+ ДанныеСтраницы = ОбъектВСтруктуру(СтраницаГруппы, ТекущаяГлубина + 1, Истина, ВидимостьСтраницы, Доступность, ТолькоПросмотр, КешСериализации, Ложь, Ложь, СчетчикУзлов, Ложь, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
9357
9489
  Если ДанныеСтраницы <> Неопределено Тогда
9358
- ДанныеСтраницы.Вставить("current", БезопасноеСвойствоСКешемMCP(СтраницаГруппы, "Имя", "", КешСериализации) = ИмяТекущейСтраницы);
9490
+ ДанныеСтраницы.Вставить("current", СтраницаТекущая);
9359
9491
  Страницы.Добавить(ДанныеСтраницы);
9360
9492
  КонецЕсли;
9361
9493
  КонецЦикла;
9362
9494
  Результат.Вставить("pages", Страницы);
9495
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_pages_ms", НачалоФазыСериализации);
9363
9496
  КонецЕсли;
9497
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9364
9498
  Если ВключатьКомандныеПанели И (ТипОбъекта = "тестируемаяформа" Или ТипОбъекта = "тестируемоеокно" Или ТипОбъекта = "тестируемаятаблицаформы" Или ТипОбъекта = "тестируемаягруппаформы") Тогда
9365
9499
  Панель = КоманднаяПанельСКешемMCP(Объект, КешСериализации);
9366
9500
  Если Панель <> Неопределено Тогда
9367
- СтруктураПанели = ОбъектВСтруктуру(Панель, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели);
9501
+ СтруктураПанели = ОбъектВСтруктуру(Панель, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
9368
9502
  Если СтруктураПанели <> Неопределено Тогда
9369
9503
  МассивДетей.Добавить(СтруктураПанели);
9370
9504
  КонецЕсли;
9371
9505
  КонецЕсли;
9372
9506
  КонецЕсли;
9507
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_command_panels_ms", НачалоФазыСериализации);
9373
9508
  СкрытыхДетей = 0;
9509
+ НачалоФазыСериализации = ТекущаяУниверсальнаяДатаВМиллисекундах();
9374
9510
  Для Каждого Ребенок Из Дети Цикл
9375
9511
  ИмяРебенка = БезопасноеСвойствоСКешемMCP(Ребенок, "Имя", "", КешСериализации);
9376
- Если Не ПоказыватьСкрытые И Не ПустаяСтрока(ИмяТекущейСтраницы) И ИмяРебенка <> ИмяТекущейСтраницы Тогда
9512
+ СтраницаРебенкаНеактивна = Не ПустаяСтрока(ИмяТекущейСтраницы) И ИмяРебенка <> ИмяТекущейСтраницы;
9513
+ Если Не ПоказыватьСкрытые И СтраницаРебенкаНеактивна Тогда
9377
9514
  СкрытыхДетей = СкрытыхДетей + 1;
9378
9515
  Продолжить;
9379
9516
  КонецЕсли;
9380
9517
  Если Не ПустаяСтрока(ИмяТекущейСтраницы) И ИмяРебенка = ИмяТекущейСтраницы Тогда
9381
9518
  Продолжить;
9382
9519
  КонецЕсли;
9383
- СтруктураРебенка = ОбъектВСтруктуру(Ребенок, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели);
9520
+ ВидимостьРебенка = Видимость;
9521
+ Если СтраницаРебенкаНеактивна Тогда
9522
+ // Дочерние элементы неактивных страниц считаем невидимыми.
9523
+ ВидимостьРебенка = Ложь;
9524
+ КонецЕсли;
9525
+ СтруктураРебенка = ОбъектВСтруктуру(Ребенок, ТекущаяГлубина + 1, ПоказыватьСкрытые, ВидимостьРебенка, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
9384
9526
  Если СтруктураРебенка <> Неопределено Тогда
9385
9527
  Если СтруктураРебенка = "__skip__" Тогда
9386
- ДобавитьПодчиненныеПропущеннойГруппыMCP(МассивДетей, Ребенок, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, СчетчикУзлов, ВключатьКомандныеПанели);
9528
+ ДобавитьПодчиненныеПропущеннойГруппыMCP(МассивДетей, Ребенок, ТекущаяГлубина + 1, ПоказыватьСкрытые, Видимость, Доступность, ТолькоПросмотр, КешСериализации, ВключатьДанные, СчетчикУзлов, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
9387
9529
  Иначе
9388
9530
  МассивДетей.Добавить(СтруктураРебенка);
9389
9531
  КонецЕсли;
@@ -9391,6 +9533,7 @@
9391
9533
  СкрытыхДетей = СкрытыхДетей + 1;
9392
9534
  КонецЕсли;
9393
9535
  КонецЦикла;
9536
+ ДобавитьТаймингСериализацииMCP(КешСериализации, "serialize_children_loop_ms", НачалоФазыСериализации);
9394
9537
  Результат.Вставить("children", МассивДетей);
9395
9538
  Если СкрытыхДетей > 0 Тогда
9396
9539
  Результат.Вставить("hidden_children_count", СкрытыхДетей);
@@ -9401,13 +9544,13 @@
9401
9544
  #КонецОбласти
9402
9545
 
9403
9546
  &НаКлиенте
9404
- Процедура ДобавитьПодчиненныеПропущеннойГруппыMCP(МассивДетей, ПропущеннаяГруппа, ТекущаяГлубина, ПоказыватьСкрытые, РодительскаяВидимость, РодительскаяДоступность, РодительТолькоПросмотр, КешСериализации = Неопределено, ВключатьДанные = Истина, СчетчикУзлов = Неопределено, ВключатьКомандныеПанели = Истина)
9547
+ Процедура ДобавитьПодчиненныеПропущеннойГруппыMCP(МассивДетей, ПропущеннаяГруппа, ТекущаяГлубина, ПоказыватьСкрытые, РодительскаяВидимость, РодительскаяДоступность, РодительТолькоПросмотр, КешСериализации = Неопределено, ВключатьДанные = Истина, СчетчикУзлов = Неопределено, ВключатьКомандныеПанели = Истина, ВключатьСостояниеВидимости = Истина, ВключатьСостояниеДоступности = Истина, ВключатьСостояниеТолькоПросмотр = Истина, ВключатьТекущееСостояние = Истина)
9405
9548
  ПодчиненныеПропущенной = БезопасныеПодчиненныеСКешемMCP(ПропущеннаяГруппа, КешСериализации);
9406
9549
  Для Каждого ПодчиненныйПропущенной Из ПодчиненныеПропущенной Цикл
9407
- СтруктураПодчиненного = ОбъектВСтруктуру(ПодчиненныйПропущенной, ТекущаяГлубина, ПоказыватьСкрытые, РодительскаяВидимость, РодительскаяДоступность, РодительТолькоПросмотр, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели);
9550
+ СтруктураПодчиненного = ОбъектВСтруктуру(ПодчиненныйПропущенной, ТекущаяГлубина, ПоказыватьСкрытые, РодительскаяВидимость, РодительскаяДоступность, РодительТолькоПросмотр, КешСериализации, ВключатьДанные, Истина, СчетчикУзлов, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
9408
9551
  Если СтруктураПодчиненного <> Неопределено Тогда
9409
9552
  Если СтруктураПодчиненного = "__skip__" Тогда
9410
- ДобавитьПодчиненныеПропущеннойГруппыMCP(МассивДетей, ПодчиненныйПропущенной, ТекущаяГлубина, ПоказыватьСкрытые, РодительскаяВидимость, РодительскаяДоступность, РодительТолькоПросмотр, КешСериализации, ВключатьДанные, СчетчикУзлов, ВключатьКомандныеПанели);
9553
+ ДобавитьПодчиненныеПропущеннойГруппыMCP(МассивДетей, ПодчиненныйПропущенной, ТекущаяГлубина, ПоказыватьСкрытые, РодительскаяВидимость, РодительскаяДоступность, РодительТолькоПросмотр, КешСериализации, ВключатьДанные, СчетчикУзлов, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние);
9411
9554
  Иначе
9412
9555
  МассивДетей.Добавить(СтруктураПодчиненного);
9413
9556
  КонецЕсли;
@@ -1907,6 +1907,28 @@ def _ui_tree_field_names(fields: str, profile: str) -> set[str] | None:
1907
1907
  raise ValueError(f"Unsupported ui_tree fields: {fields!r}. Expected one of: {allowed}.")
1908
1908
 
1909
1909
 
1910
+ def _ui_tree_state_options(field_names: set[str] | None, *, include_hidden: bool) -> dict[str, bool]:
1911
+ if field_names is None:
1912
+ return {
1913
+ "include_visibility_state": True,
1914
+ "include_availability_state": True,
1915
+ "include_read_only_state": True,
1916
+ "include_current_state": True,
1917
+ }
1918
+ # Visibility is also a traversal/filtering input: with include_hidden=False the
1919
+ # 1C side must still read it even when the selected field set later omits it.
1920
+ include_visibility_state = not include_hidden or bool({"visible", "current_visible", "hidden_children_count"} & field_names)
1921
+ include_availability_state = bool({"available", "enabled", "current_available"} & field_names)
1922
+ include_read_only_state = bool({"read_only", "current_read_only"} & field_names)
1923
+ include_current_state = bool({"current_visible", "current_available", "current_read_only"} & field_names)
1924
+ return {
1925
+ "include_visibility_state": include_visibility_state,
1926
+ "include_availability_state": include_availability_state,
1927
+ "include_read_only_state": include_read_only_state,
1928
+ "include_current_state": include_current_state,
1929
+ }
1930
+
1931
+
1910
1932
  def _apply_ui_fields(value: Any, allowed: set[str] | None) -> Any:
1911
1933
  if allowed is None:
1912
1934
  return value
@@ -2159,6 +2181,25 @@ def _attach_ui_tree_metrics(result: Any, metrics: dict[str, Any]) -> Any:
2159
2181
  return result
2160
2182
 
2161
2183
 
2184
+ def _build_ui_tree_timings(tree: Any, *, bridge_ms: float, stages: dict[str, float]) -> dict[str, Any]:
2185
+ timings: dict[str, Any] = {"bridge_ms": round(bridge_ms, 3)}
2186
+ if isinstance(tree, dict) and isinstance(tree.get("_timings"), dict):
2187
+ timings["onec"] = tree.get("_timings")
2188
+ for key, value in stages.items():
2189
+ timings[key] = round(value, 3)
2190
+ timings["python_total_ms"] = round(sum(stages.values()), 3)
2191
+ timings["total_observed_ms"] = round(bridge_ms + sum(stages.values()), 3)
2192
+ return timings
2193
+
2194
+
2195
+ def _attach_optional_timings(result: Any, timings: dict[str, Any] | None, include_timings: bool) -> Any:
2196
+ if include_timings and isinstance(result, dict) and timings is not None:
2197
+ result["_timings"] = timings
2198
+ elif not include_timings and isinstance(result, dict):
2199
+ result.pop("_timings", None)
2200
+ return result
2201
+
2202
+
2162
2203
  def _outline_ui_tree(node: Any, indent: int = 0) -> str:
2163
2204
  if not isinstance(node, dict):
2164
2205
  return ""
@@ -2418,6 +2459,8 @@ def _ui_tree_maybe_resource_response(
2418
2459
  preview_max_nodes=preview_max_nodes,
2419
2460
  preview_format=preview_format,
2420
2461
  )
2462
+ if isinstance(inline_response, dict) and isinstance(inline_response.get("_timings"), dict):
2463
+ response["_timings"] = inline_response["_timings"]
2421
2464
  response["delivery_requested"] = delivery
2422
2465
  response["delivery_reason"] = "explicit_resource" if (delivery or "").strip().lower() == "resource" else "auto_threshold_exceeded"
2423
2466
  response["auto_threshold_tokens"] = UI_TREE_RESOURCE_AUTO_THRESHOLD_TOKENS
@@ -2906,6 +2949,51 @@ def _strip_bridge_timings(result: Any) -> Any:
2906
2949
  cleaned.pop("_timings", None)
2907
2950
  return cleaned
2908
2951
 
2952
+
2953
+ def _compact_window_summary(value: Any) -> Any:
2954
+ if not isinstance(value, dict):
2955
+ return value
2956
+ keep_keys = (
2957
+ "type", "name", "title", "form_name", "navigation_url",
2958
+ "visible", "available", "read_only", "active_form_title", "active_form_name",
2959
+ "source", "maximized", "method", "ok", "error", "geometry", "candidates",
2960
+ )
2961
+ summary = {key: _copy_compact_object(value[key]) for key in keep_keys if key in value and value.get(key) not in (None, "", [], {})}
2962
+ if "enabled" in value and value.get("enabled") != value.get("available"):
2963
+ summary["enabled"] = value.get("enabled")
2964
+ for key in ("main", "home"):
2965
+ if value.get(key) is True:
2966
+ summary[key] = True
2967
+ return summary
2968
+
2969
+
2970
+ def _compact_start_test_client_result(result: Any, *, include_timings: bool = False) -> Any:
2971
+ if not isinstance(result, dict):
2972
+ return result
2973
+ keep_keys = (
2974
+ "connected", "base_url", "connection_mode", "connection_value",
2975
+ "target_connection_mode", "port", "client_pid", "client_log",
2976
+ "client_stdout_log", "auto_maximize", "auto_maximize_error",
2977
+ )
2978
+ compact = {key: _copy_compact_object(result[key]) for key in keep_keys if key in result and result.get(key) not in (None, "", [], {})}
2979
+ if isinstance(compact.get("auto_maximize"), dict):
2980
+ compact["auto_maximize"] = _compact_window_summary(compact["auto_maximize"])
2981
+ maximized = result.get("maximized")
2982
+ if isinstance(maximized, dict):
2983
+ compact_maximized = {
2984
+ key: _copy_compact_object(maximized[key])
2985
+ for key in ("maximized", "method", "errors")
2986
+ if key in maximized and maximized.get(key) not in (None, "", [], {})
2987
+ }
2988
+ if maximized.get("window") not in (None, "", [], {}):
2989
+ compact_maximized["window"] = _compact_window_summary(maximized.get("window"))
2990
+ compact["maximized"] = compact_maximized
2991
+ elif maximized not in (None, "", [], {}):
2992
+ compact["maximized"] = maximized
2993
+ if include_timings and isinstance(result.get("_timings"), dict):
2994
+ compact["_timings"] = result["_timings"]
2995
+ return compact
2996
+
2909
2997
  async def _bridge_call_recorded(
2910
2998
  session_id: str,
2911
2999
  method: str,
@@ -4089,8 +4177,18 @@ async def start_session(
4089
4177
  command_parameter: Annotated[str, "Additional test-client /C value; when extra_args already contains /C, this value is appended with ';'"] = "",
4090
4178
  rag_enabled: Annotated[bool, "Enable built-in RAG for this session"] = False,
4091
4179
  summary_rag_config: Annotated[str, "Explicit Summary RAG configuration alias/code, e.g. bp, accounting30, Бухгалтерия предприятия. Enables configuration+fresh summary snapshot without live autodetection."] = "",
4180
+ include_timings: Annotated[bool, "Include diagnostic startup timing breakdown in the returned _timings field"] = False,
4092
4181
  ) -> dict[str, Any]:
4093
4182
  """One-shot session factory: detect version, start infrastructure and connect the 1C test client."""
4183
+ start_monotonic = time.perf_counter()
4184
+ stage_monotonic = start_monotonic
4185
+ timings: dict[str, float] = {}
4186
+
4187
+ def mark_stage(name: str) -> None:
4188
+ nonlocal stage_monotonic
4189
+ now = time.perf_counter()
4190
+ timings[f"{name}_seconds"] = round(now - stage_monotonic, 3)
4191
+ stage_monotonic = now
4094
4192
  credential_ref = _resolve_credential_reference(base_url)
4095
4193
  credential_account_id = _current_account_id()
4096
4194
  saved_credentials_used = False
@@ -4134,6 +4232,7 @@ async def start_session(
4134
4232
  actual_version = _platform.platform_version_from_dir(platform_dir)
4135
4233
  _platform.require_websocket_support(actual_version)
4136
4234
  logging.info("start_session: version=%s platform_dir=%s", actual_version, platform_dir)
4235
+ mark_stage("resolve_credentials_version_platform")
4137
4236
 
4138
4237
  sid = session_id or DEFAULT_SESSION
4139
4238
  if sid in _SESSIONS:
@@ -4171,6 +4270,7 @@ async def start_session(
4171
4270
  sess.client_xvfb_process = runtime.start_xvfb(client_display)
4172
4271
  sess.client_xvfb_pid = sess.client_xvfb_process.pid
4173
4272
  await sess.bridge.start()
4273
+ mark_stage("allocate_ports_start_xvfb_bridge")
4174
4274
  _SESSIONS[sid] = sess
4175
4275
  _persist_sessions()
4176
4276
  logging.info("start_session: bridge listening on ws://127.0.0.1:%s", ws_port)
@@ -4197,6 +4297,7 @@ async def start_session(
4197
4297
  )
4198
4298
  runtime.load_configuration(sess.ibcmd, sess.db_path, cf_path)
4199
4299
  _write_cf_marker(sess.db_path, cf_path)
4300
+ mark_stage("prepare_manager_infobase")
4200
4301
  except Exception:
4201
4302
  await _cleanup_failed_start_session(sess, "infobase preparation failure")
4202
4303
  raise
@@ -4233,6 +4334,7 @@ async def start_session(
4233
4334
  runtime_state.manager_process = manager_proc
4234
4335
  sess.manager_pid = manager_proc.pid
4235
4336
  await _wait_for_bridge_connected(sess, timeout=60.0)
4337
+ mark_stage("start_manager_and_wait_bridge")
4236
4338
  except Exception as exc:
4237
4339
  diagnostic_error = _manager_failure_diagnostics(sess, exc)
4238
4340
  await _cleanup_failed_start_session(sess, "manager startup failure")
@@ -4318,6 +4420,7 @@ async def start_session(
4318
4420
  except Exception:
4319
4421
  await _cleanup_failed_start_session(sess, "demo test-client preparation failure")
4320
4422
  raise
4423
+ mark_stage("prepare_demo_client_if_needed")
4321
4424
 
4322
4425
  connect_result: Any = None
4323
4426
  connect_base_url = base_url
@@ -4336,6 +4439,7 @@ async def start_session(
4336
4439
  extra_args=extra_args, execute=execute, command_parameter=command_parameter,
4337
4440
  connection_mode=connect_connection_mode, connection_value=connect_connection_value,
4338
4441
  )
4442
+ mark_stage("connect_test_client")
4339
4443
  except Exception as exc:
4340
4444
  enriched = await _enrich_auth_failure_for_web_base(exc, connect_base_url)
4341
4445
  target_mode = _normalize_connection_mode(
@@ -4349,6 +4453,7 @@ async def start_session(
4349
4453
  if saved_credentials_used:
4350
4454
  _mark_credentials_verified(connect_base_url or connect_connection_value, username, password, credential_account_id)
4351
4455
  _auto_maximize_test_client_result(connect_result, sess.client_display or sess.display)
4456
+ mark_stage("auto_maximize_test_client")
4352
4457
 
4353
4458
  if effective_summary_rag_config:
4354
4459
  sess.rag_notice = _configure_summary_rag_from_config_alias(sess, effective_summary_rag_config)
@@ -4388,12 +4493,15 @@ async def start_session(
4388
4493
  enabled=False,
4389
4494
  reason="rag_not_requested_or_no_web_base_url",
4390
4495
  )
4496
+ mark_stage("configure_rag")
4391
4497
 
4392
4498
  if sess.idle_timeout_seconds > 0:
4393
4499
  asyncio.create_task(_idle_watch_session(sid))
4394
4500
 
4395
4501
  _persist_sessions()
4396
- return {
4502
+ mark_stage("persist_session")
4503
+ timings["total_seconds"] = round(time.perf_counter() - start_monotonic, 3)
4504
+ response = {
4397
4505
  "session_id": sid, "version": actual_version,
4398
4506
  "platform_dir": str(platform_dir), "display": display,
4399
4507
  "client_display": sess.client_display,
@@ -4404,8 +4512,11 @@ async def start_session(
4404
4512
  "server_rag_enabled": _RAG_ENABLED,
4405
4513
  "rag_notice": sess.rag_notice,
4406
4514
  "idle_timeout_seconds": sess.idle_timeout_seconds,
4407
- "test_client": connect_result, **pipeline_info,
4515
+ "test_client": _compact_start_test_client_result(connect_result, include_timings=include_timings), **pipeline_info,
4408
4516
  }
4517
+ if include_timings:
4518
+ response["_timings"] = timings
4519
+ return response
4409
4520
 
4410
4521
 
4411
4522
  async def _stop_session_impl(session_id: str = DEFAULT_SESSION, clean_data: bool = False) -> dict[str, Any]:
@@ -4991,8 +5102,19 @@ async def _connect_test_client_impl(
4991
5102
  connection_value: str = "",
4992
5103
  ) -> Any:
4993
5104
  """Launch 1cv8c /TESTCLIENT from Python and attach the test manager to it."""
5105
+ start_monotonic = time.perf_counter()
5106
+ stage_monotonic = start_monotonic
5107
+ timings: dict[str, float] = {}
5108
+
5109
+ def mark_stage(name: str) -> None:
5110
+ nonlocal stage_monotonic
5111
+ now = time.perf_counter()
5112
+ timings[f"{name}_seconds"] = round(now - stage_monotonic, 3)
5113
+ stage_monotonic = now
5114
+
4994
5115
  await _wait_for_bridge_connected(sess, timeout=60.0)
4995
5116
  manager_info = await _verify_manager_protocol(sess)
5117
+ mark_stage("verify_manager_protocol")
4996
5118
  r_base = _platform.normalize_infobase_url(base_url or sess.base_url)
4997
5119
  r_value = connection_value or r_base
4998
5120
  mode = _normalize_connection_mode(connection_mode, r_value, r_base)
@@ -5121,6 +5243,7 @@ async def _connect_test_client_impl(
5121
5243
  log_name=client_stdout_log,
5122
5244
  )
5123
5245
  sess.test_client_pid = sess.test_client_process.pid
5246
+ mark_stage("spawn_test_client_process")
5124
5247
  params = {"base_url": r_base, "username": r_user, "password": r_pass,
5125
5248
  "port": r_port, "host": host, "client_pid": sess.test_client_process.pid,
5126
5249
  "client_log": str(client_out_log), "client_stdout_log": str(sess.client_log_dir / client_stdout_log),
@@ -5132,6 +5255,7 @@ async def _connect_test_client_impl(
5132
5255
  try:
5133
5256
  wait_timeout = float(os.getenv("ONEC_MCP_TEST_CLIENT_READY_TIMEOUT", "55"))
5134
5257
  await _wait_for_test_client_ready(sess, host, r_port, timeout=wait_timeout)
5258
+ mark_stage("wait_test_client_port")
5135
5259
  if sess.test_client_process is not None:
5136
5260
  monitor_task = asyncio.create_task(_wait_for_test_client_process_exit(sess.test_client_process))
5137
5261
  attach_task = asyncio.create_task(sess.bridge.call("attach_test_client", params))
@@ -5159,6 +5283,10 @@ async def _connect_test_client_impl(
5159
5283
  for pending_task in pending:
5160
5284
  pending_task.cancel()
5161
5285
  result = await attach_task
5286
+ mark_stage("attach_test_client")
5287
+ if isinstance(result, dict):
5288
+ timings["total_seconds"] = round(time.perf_counter() - start_monotonic, 3)
5289
+ result["_timings"] = timings
5162
5290
  except Exception as exc:
5163
5291
  runtime.kill_process(sess.test_client_process, "test-client")
5164
5292
  sess.test_client_process = None
@@ -5646,6 +5774,7 @@ async def ui_tree(
5646
5774
  rag_snapshot: Annotated[str, "Optional RAG snapshot name/id used for type enrichment"] = "",
5647
5775
  delivery: Annotated[str, "Delivery mode: auto (default) returns small results inline and saves responses above token threshold as resource; inline always returns the selected JSON/outline/yaml; resource always saves full JSON and returns resource_uri + summary + preview"] = "auto",
5648
5776
  preview_max_nodes: Annotated[int, "For delivery=auto/resource, number of top nodes/matches to include in the small inline preview when a resource is returned"] = 40,
5777
+ include_timings: Annotated[bool, "Include diagnostic timing breakdown in the returned _timings field"] = False,
5649
5778
  *,
5650
5779
  session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
5651
5780
  ) -> Any:
@@ -5679,6 +5808,11 @@ async def ui_tree(
5679
5808
  `ui_tree_resource_children`, or `ui_tree_diff` instead of loading the whole
5680
5809
  tree into the agent context.
5681
5810
  """
5811
+ stage_timings: dict[str, float] = {}
5812
+
5813
+ def timed_stage(name: str, started: float) -> None:
5814
+ stage_timings[name] = stage_timings.get(name, 0.0) + ((time.monotonic() - started) * 1000)
5815
+
5682
5816
  delivery_key = (delivery or "auto").strip().lower()
5683
5817
  if delivery_key not in {"auto", "inline", "resource"}:
5684
5818
  raise ValueError(f"Unsupported ui_tree delivery: {delivery!r}. Expected auto, inline or resource.")
@@ -5687,6 +5821,7 @@ async def ui_tree(
5687
5821
  group_mode_key = _validate_ui_tree_choice("group_mode", group_mode, _UI_TREE_GROUP_MODES)
5688
5822
  fmt = _validate_ui_tree_choice("format", format, _UI_TREE_FORMATS)
5689
5823
  field_names = _ui_tree_field_names(fields, profile_key)
5824
+ state_options = _ui_tree_state_options(field_names, include_hidden=include_hidden)
5690
5825
  if profile_key in {"full", "diagnostic", "data"}:
5691
5826
  profile_include_command_panels = True
5692
5827
  elif profile_key == "command_panels":
@@ -5714,6 +5849,8 @@ async def ui_tree(
5714
5849
  "include_hidden": include_hidden,
5715
5850
  "include_data": include_data,
5716
5851
  "include_command_panels": include_command_panels_effective,
5852
+ "include_timings": include_timings,
5853
+ **state_options,
5717
5854
  }
5718
5855
  if parent_name:
5719
5856
  bridge_params["parent_name"] = parent_name
@@ -5726,15 +5863,20 @@ async def ui_tree(
5726
5863
  bridge_ms = (time.monotonic() - bridge_start) * 1000
5727
5864
  transform_start = time.monotonic()
5728
5865
  if parent_name and isinstance(tree, dict) and tree.get("reason") != "parent_not_found" and tree.get("root_parent_name") != parent_name:
5866
+ parent_lookup_start = time.monotonic()
5729
5867
  parent_node = _find_ui_node_by_name(tree, parent_name)
5730
5868
  if parent_node is None:
5731
5869
  tree = {"found": False, "parent_name": parent_name, "reason": "parent_not_found"}
5732
5870
  else:
5733
5871
  tree = parent_node
5734
5872
  tree["root_parent_name"] = parent_name
5873
+ timed_stage("python_parent_lookup_ms", parent_lookup_start)
5874
+ summary_start = time.monotonic()
5735
5875
  if isinstance(tree, dict):
5736
5876
  tree["tree_summary"] = _summarize_ui_tree(tree, include_hidden=include_hidden)
5877
+ timed_stage("python_summary_ms", summary_start)
5737
5878
  if include_rag_types:
5879
+ rag_start = time.monotonic()
5738
5880
  sess = _session(session_id)
5739
5881
  if _RAG_ENABLED and sess.rag_enabled:
5740
5882
  tree = _enrich_ui_tree_with_rag_types(tree, snapshot=rag_snapshot or sess.rag_snapshot or None)
@@ -5742,14 +5884,18 @@ async def ui_tree(
5742
5884
  tree["rag_diagnostic"] = _rag_diagnostic(session_id, rag_snapshot)
5743
5885
  elif isinstance(tree, dict):
5744
5886
  tree["rag_diagnostic"] = _rag_diagnostic(session_id, rag_snapshot)
5887
+ timed_stage("python_rag_enrich_ms", rag_start)
5745
5888
 
5746
5889
  if parent_name and isinstance(tree, dict) and tree.get("found") is False and tree.get("reason") == "parent_not_found":
5747
5890
  tree["tree_summary"] = _summarize_ui_tree(tree, include_hidden=include_hidden)
5748
5891
  python_transform_ms = (time.monotonic() - transform_start) * 1000
5892
+ stage_timings["python_transform_ms"] = python_transform_ms
5893
+ ui_timings = _build_ui_tree_timings(tree, bridge_ms=bridge_ms, stages=stage_timings)
5749
5894
  if fmt == "outline":
5750
5895
  response = {"format": "outline", "text": "", "tree_summary": tree.get("tree_summary"), "profile": profile_key, "parent_name": parent_name, "found": False, "reason": "parent_not_found"}
5751
5896
  response = _apply_ui_fields(response, field_names)
5752
5897
  response = _attach_ui_tree_metrics(response, _ui_tree_metrics(tree, bridge_ms=bridge_ms, python_transform_ms=python_transform_ms, result=response))
5898
+ response = _attach_optional_timings(response, ui_timings, include_timings)
5753
5899
  return _ui_tree_maybe_resource_response(
5754
5900
  response,
5755
5901
  inline_response=response,
@@ -5763,6 +5909,7 @@ async def ui_tree(
5763
5909
  if fmt == "yaml":
5764
5910
  response = {"format": "yaml", "yaml": _to_simple_yaml(tree), "tree_summary": tree.get("tree_summary"), "profile": profile_key, "parent_name": parent_name}
5765
5911
  response = _attach_ui_tree_metrics(response, _ui_tree_metrics(tree, bridge_ms=bridge_ms, python_transform_ms=python_transform_ms, result=response))
5912
+ response = _attach_optional_timings(response, ui_timings, include_timings)
5766
5913
  return _ui_tree_maybe_resource_response(
5767
5914
  tree,
5768
5915
  inline_response=response,
@@ -5775,6 +5922,7 @@ async def ui_tree(
5775
5922
  )
5776
5923
  tree = _apply_ui_fields(tree, field_names)
5777
5924
  tree = _attach_ui_tree_metrics(tree, _ui_tree_metrics(tree, bridge_ms=bridge_ms, python_transform_ms=python_transform_ms, result=tree))
5925
+ tree = _attach_optional_timings(tree, ui_timings, include_timings)
5778
5926
  return _ui_tree_maybe_resource_response(
5779
5927
  tree,
5780
5928
  inline_response=tree,
@@ -5788,6 +5936,7 @@ async def ui_tree(
5788
5936
 
5789
5937
  summary = tree.get("tree_summary") if isinstance(tree, dict) else None
5790
5938
 
5939
+ result_build_start = time.monotonic()
5791
5940
  if filtered:
5792
5941
  if isinstance(tree, dict) and isinstance(tree.get("objects"), list):
5793
5942
  objects = [obj for obj in tree["objects"] if isinstance(obj, dict)]
@@ -5846,8 +5995,13 @@ async def ui_tree(
5846
5995
  compact_tree["rag_diagnostic"] = tree["rag_diagnostic"]
5847
5996
  result = compact_tree
5848
5997
 
5998
+ timed_stage("python_result_build_ms", result_build_start)
5999
+ field_filter_start = time.monotonic()
5849
6000
  result = _apply_ui_fields(result, field_names)
6001
+ timed_stage("python_field_filter_ms", field_filter_start)
5850
6002
  python_transform_ms = (time.monotonic() - transform_start) * 1000
6003
+ stage_timings["python_transform_ms"] = python_transform_ms
6004
+ ui_timings = _build_ui_tree_timings(tree, bridge_ms=bridge_ms, stages=stage_timings)
5851
6005
  if fmt == "outline":
5852
6006
  format_start = time.monotonic()
5853
6007
  text = _outline_ui_nodes(result.get("objects", [])) if isinstance(result, dict) and "objects" in result else _outline_ui_tree(result)
@@ -5856,6 +6010,7 @@ async def ui_tree(
5856
6010
  response["parent_name"] = parent_name
5857
6011
  metrics = _ui_tree_metrics(tree, bridge_ms=bridge_ms, python_transform_ms=python_transform_ms, format_ms=(time.monotonic() - format_start) * 1000, result=response)
5858
6012
  response = _attach_ui_tree_metrics(response, metrics)
6013
+ response = _attach_optional_timings(response, ui_timings, include_timings)
5859
6014
  return _ui_tree_maybe_resource_response(
5860
6015
  result,
5861
6016
  inline_response=response,
@@ -5873,6 +6028,7 @@ async def ui_tree(
5873
6028
  response["parent_name"] = parent_name
5874
6029
  metrics = _ui_tree_metrics(tree, bridge_ms=bridge_ms, python_transform_ms=python_transform_ms, format_ms=(time.monotonic() - format_start) * 1000, result=response)
5875
6030
  response = _attach_ui_tree_metrics(response, metrics)
6031
+ response = _attach_optional_timings(response, ui_timings, include_timings)
5876
6032
  return _ui_tree_maybe_resource_response(
5877
6033
  result,
5878
6034
  inline_response=response,
@@ -5885,6 +6041,7 @@ async def ui_tree(
5885
6041
  )
5886
6042
  metrics = _ui_tree_metrics(tree, bridge_ms=bridge_ms, python_transform_ms=python_transform_ms, result=result)
5887
6043
  result = _attach_ui_tree_metrics(result, metrics)
6044
+ result = _attach_optional_timings(result, ui_timings, include_timings)
5888
6045
  return _ui_tree_maybe_resource_response(
5889
6046
  result,
5890
6047
  inline_response=result,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes