answer42 0.2.22__tar.gz → 0.2.23__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 (58) hide show
  1. {answer42-0.2.22 → answer42-0.2.23}/PKG-INFO +1 -1
  2. {answer42-0.2.22 → answer42-0.2.23}/pyproject.toml +1 -1
  3. {answer42-0.2.22 → answer42-0.2.23}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +129 -78
  4. {answer42-0.2.22 → answer42-0.2.23}/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 +1 -1
  5. {answer42-0.2.22 → answer42-0.2.23}/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 +2 -2
  6. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/server.py +54 -5
  7. {answer42-0.2.22 → answer42-0.2.23}/.gitignore +0 -0
  8. {answer42-0.2.22 → answer42-0.2.23}/LICENSE +0 -0
  9. {answer42-0.2.22 → answer42-0.2.23}/README.md +0 -0
  10. {answer42-0.2.22 → answer42-0.2.23}/credentials.example.json +0 -0
  11. {answer42-0.2.22 → answer42-0.2.23}/docs/agent-installation.md +0 -0
  12. {answer42-0.2.22 → answer42-0.2.23}/docs/architecture.md +0 -0
  13. {answer42-0.2.22 → answer42-0.2.23}/docs/assets/answer42-logo.png +0 -0
  14. {answer42-0.2.22 → answer42-0.2.23}/docs/assets/platform42-logo.svg +0 -0
  15. {answer42-0.2.22 → answer42-0.2.23}/docs/installation.md +0 -0
  16. {answer42-0.2.22 → answer42-0.2.23}/scripts/build_cf.py +0 -0
  17. {answer42-0.2.22 → answer42-0.2.23}/scripts/build_pages.py +0 -0
  18. {answer42-0.2.22 → answer42-0.2.23}/scripts/e2e_stable.py +0 -0
  19. {answer42-0.2.22 → answer42-0.2.23}/scripts/openclaw_mcp_autoreload.py +0 -0
  20. {answer42-0.2.22 → answer42-0.2.23}/scripts/rag_cli.py +0 -0
  21. {answer42-0.2.22 → answer42-0.2.23}/src/cf/ConfigDumpInfo.xml +0 -0
  22. {answer42-0.2.22 → answer42-0.2.23}/src/cf/Configuration.xml +0 -0
  23. {answer42-0.2.22 → answer42-0.2.23}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  24. {answer42-0.2.22 → answer42-0.2.23}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  25. {answer42-0.2.22 → answer42-0.2.23}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  26. {answer42-0.2.22 → answer42-0.2.23}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  27. {answer42-0.2.22 → answer42-0.2.23}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  28. {answer42-0.2.22 → answer42-0.2.23}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  29. {answer42-0.2.22 → answer42-0.2.23}/src/client_cf/ConfigDumpInfo.xml +0 -0
  30. {answer42-0.2.22 → answer42-0.2.23}/src/client_cf/Configuration.xml +0 -0
  31. {answer42-0.2.22 → answer42-0.2.23}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  32. {answer42-0.2.22 → answer42-0.2.23}/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
  33. {answer42-0.2.22 → answer42-0.2.23}/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
  34. {answer42-0.2.22 → answer42-0.2.23}/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
  35. {answer42-0.2.22 → answer42-0.2.23}/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
  36. {answer42-0.2.22 → answer42-0.2.23}/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
  37. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/__init__.py +0 -0
  38. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  39. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  40. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/assets/__init__.py +0 -0
  41. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  42. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  43. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/bridge.py +0 -0
  44. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/credentials.py +0 -0
  45. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/os_support.py +0 -0
  46. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/platform.py +0 -0
  47. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/protocol.py +0 -0
  48. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/rag/__init__.py +0 -0
  49. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/rag/detect.py +0 -0
  50. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/rag/model.py +0 -0
  51. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/rag/parsers.py +0 -0
  52. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/rag/service.py +0 -0
  53. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/rag/store.py +0 -0
  54. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/recorder.py +0 -0
  55. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/release_helper.py +0 -0
  56. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/runtime.py +0 -0
  57. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/skill_installer.py +0 -0
  58. {answer42-0.2.22 → answer42-0.2.23}/src/mcp_1c/window_control.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: answer42
3
- Version: 0.2.22
3
+ Version: 0.2.23
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.2.22"
3
+ version = "0.2.23"
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"
@@ -298,6 +298,8 @@
298
298
  Возврат ТекстТабличногоДокумента(Параметры);
299
299
  ИначеЕсли Метод = "tabular_document_save" Тогда
300
300
  Возврат СохранитьТабличныйДокументMCP(Параметры);
301
+ ИначеЕсли Метод = "tabular_document_state_display" Тогда
302
+ Возврат ОтображениеСостоянияТабличногоДокументаMCP(Параметры);
301
303
  ИначеЕсли Метод = "set_field_value" Тогда
302
304
  Возврат УстановитьЗначениеПоля(Параметры);
303
305
  ИначеЕсли Метод = "activate_field" Тогда
@@ -2938,19 +2940,22 @@
2938
2940
  Попытка
2939
2941
  Подчиненные = БезопасныеПодчиненные(Объект);
2940
2942
  Для Каждого Подчиненный Из Подчиненные Цикл
2941
- ТипТек = НормализованноеИмяТипа(Строка(ТипЗнч(Подчиненный)));
2942
- Если ТипТек = "тестируемоеполетабличногодокументаформы" Тогда
2943
+ Если ЭтоИскомоеПолеТабличногоДокумента(Подчиненный, "", "") Тогда
2943
2944
  Элемент = Новый Структура;
2944
2945
  Элемент.Вставить("name", БезопасноеСвойство(Подчиненный, "Имя", ""));
2945
2946
  Элемент.Вставить("title", БезопасныйЗаголовок(Подчиненный));
2947
+ Элемент.Вставить("type", Строка(ТипЗнч(Подчиненный)));
2948
+ Элемент.Вставить("data", БезопасноеПредставлениеДанныхПоля(Подчиненный));
2946
2949
  Если ВключатьТекст Тогда
2947
- Попытка
2948
- Область = Подчиненный.ПолучитьТекущуюОбласть();
2949
- Текст = БезопасныйАдресТекущейОбласти(Область);
2950
- Элемент.Вставить("text", Текст);
2951
- Исключение
2952
- Элемент.Вставить("text_error", ОписаниеОшибки());
2953
- КонецПопытки;
2950
+ ТекстДокумента = ТекстТабличногоДокументаИзПоля(Подчиненный);
2951
+ Если ТекстДокумента.Свойство("error") Тогда
2952
+ Элемент.Вставить("text_error", ТекстДокумента.error);
2953
+ Иначе
2954
+ Элемент.Вставить("rows", ТекстДокумента.rows);
2955
+ Элемент.Вставить("columns", ТекстДокумента.columns);
2956
+ Элемент.Вставить("address", ТекстДокумента.address);
2957
+ Элемент.Вставить("text", ТекстДокумента.text);
2958
+ КонецЕсли;
2954
2959
  КонецЕсли;
2955
2960
  Результат.Добавить(Элемент);
2956
2961
  КонецЕсли;
@@ -2987,35 +2992,101 @@
2987
2992
  Если Поле = Неопределено Тогда
2988
2993
  Возврат Новый Структура("found", Ложь);
2989
2994
  КонецЕсли;
2995
+ ДанныеТекста = ТекстТабличногоДокументаИзПоля(Поле);
2996
+ Если ДанныеТекста.Свойство("error") Тогда
2997
+ Возврат Новый Структура("found,error", Истина, ДанныеТекста.error);
2998
+ КонецЕсли;
2999
+ Возврат Новый Структура("found,rows,columns,address,text,cells", Истина, ДанныеТекста.rows, ДанныеТекста.columns, ДанныеТекста.address, ДанныеТекста.text, ДанныеТекста.cells);
3000
+ КонецФункции
3001
+
3002
+ &НаКлиенте
3003
+ Функция ТекстТабличногоДокументаИзПоля(Поле)
2990
3004
  Попытка
2991
- Поле.УстановитьТекущуюОбласть(1, 1, 1, 1);
2992
- КоличествоСтрок = БезопасноеСвойство(Поле, "КоличествоСтрок", 0);
2993
- КоличествоКолонок = БезопасноеСвойство(Поле, "КоличествоКолонок", 0);
3005
+ КоличествоСтрок = Поле.ПолучитьРазмерОбластиДанныхДокументаПоВертикали();
3006
+ КоличествоКолонок = Поле.ПолучитьРазмерОбластиДанныхДокументаПоГоризонтали();
2994
3007
  Если КоличествоСтрок = 0 Или КоличествоКолонок = 0 Тогда
2995
- Возврат Новый Структура("found,rows,columns,cells", Истина, 0, 0, Новый Массив);
3008
+ Возврат Новый Структура("rows,columns,address,text,cells", 0, 0, "", "", Новый Массив);
2996
3009
  КонецЕсли;
3010
+ АдресВсейОбласти = АдресОбластиТабличногоДокументаMCP(1, 1, КоличествоСтрок, КоличествоКолонок);
3011
+ ТекстВсейОбласти = Поле.ПолучитьТекстОбласти(АдресВсейОбласти);
2997
3012
  МаксСтрок = Мин(КоличествоСтрок, 50);
2998
3013
  МаксКолонок = Мин(КоличествоКолонок, 20);
2999
3014
  Ячейки = Новый Массив;
3000
3015
  Для НомерСтроки = 1 По МаксСтрок Цикл
3001
- Строка = Новый Массив;
3016
+ СтрокаТекстов = Новый Массив;
3002
3017
  Для НомерКолонки = 1 По МаксКолонок Цикл
3003
3018
  Попытка
3004
- Область = Поле.Область(НомерСтроки, НомерКолонки);
3005
- Текст = БезопасноеСвойство(Область, "Текст", "");
3006
- Строка.Добавить(?(ТипЗнч(Текст) = Тип("Строка"), Текст, Строка(Текст)));
3019
+ АдресЯчейки = АдресОбластиТабличногоДокументаMCP(НомерСтроки, НомерКолонки, НомерСтроки, НомерКолонки);
3020
+ СтрокаТекстов.Добавить(Поле.ПолучитьТекстОбласти(АдресЯчейки));
3007
3021
  Исключение
3008
- Строка.Добавить("");
3022
+ СтрокаТекстов.Добавить("");
3009
3023
  КонецПопытки;
3010
3024
  КонецЦикла;
3011
- Ячейки.Добавить(Строка);
3025
+ Ячейки.Добавить(СтрокаТекстов);
3012
3026
  КонецЦикла;
3013
- Возврат Новый Структура("found,rows,columns,cells", Истина, КоличествоСтрок, КоличествоКолонок, Ячейки);
3027
+ Возврат Новый Структура("rows,columns,address,text,cells", КоличествоСтрок, КоличествоКолонок, АдресВсейОбласти, ТекстВсейОбласти, Ячейки);
3028
+ Исключение
3029
+ Возврат Новый Структура("error", ОписаниеОшибки());
3030
+ КонецПопытки;
3031
+ КонецФункции
3032
+
3033
+ &НаКлиенте
3034
+ Функция АдресОбластиТабличногоДокументаMCP(НачальнаяСтрока, НачальнаяКолонка, КонечнаяСтрока, КонечнаяКолонка)
3035
+ Начало = "R" + Строка(НачальнаяСтрока) + "C" + Строка(НачальнаяКолонка);
3036
+ Конец = "R" + Строка(КонечнаяСтрока) + "C" + Строка(КонечнаяКолонка);
3037
+ Возврат Начало + ":" + Конец;
3038
+ КонецФункции
3039
+
3040
+ &НаКлиенте
3041
+ Функция ОтображениеСостоянияТабличногоДокументаMCP(Параметры)
3042
+ Если ТестируемоеПриложение = Неопределено Тогда
3043
+ ВызватьИсключение "Тестируемое приложение не подключено.";
3044
+ КонецЕсли;
3045
+ АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
3046
+ Имя = СвойствоСтруктуры(Параметры, "name", "");
3047
+ Заголовок = СвойствоСтруктуры(Параметры, "title", "");
3048
+ Глубина = СвойствоСтруктуры(Параметры, "max_depth", 8);
3049
+ Поле = НайтиТабличныйДокументПоПараметрам(АктивноеОкно, Имя, Заголовок, 0, Глубина);
3050
+ Если Поле = Неопределено Тогда
3051
+ Возврат Новый Структура("found", Ложь);
3052
+ КонецЕсли;
3053
+ Попытка
3054
+ Отображение = Поле.ПолучитьОтображениеСостояния();
3055
+ Если Отображение = Неопределено Тогда
3056
+ Возврат Новый Структура("found,state_display", Истина, Неопределено);
3057
+ КонецЕсли;
3058
+ Состояние = Новый Структура;
3059
+ Состояние.Вставить("display_variant", Строка(БезопасноеСвойство(Отображение, "ВариантОтображения", "")));
3060
+ Состояние.Вставить("visible", БезопасноеСвойство(Отображение, "Видимость", Ложь));
3061
+ Состояние.Вставить("additional_show_mode", Строка(БезопасноеСвойство(Отображение, "ДополнительныйРежимОтображения", "")));
3062
+ Состояние.Вставить("command_name", Строка(БезопасноеСвойство(Отображение, "ИмяКоманды", "")));
3063
+ Состояние.Вставить("picture", Строка(БезопасноеСвойство(Отображение, "Картинка", "")));
3064
+ Состояние.Вставить("text", Строка(БезопасноеСвойство(Отображение, "Текст", "")));
3065
+ Возврат Новый Структура("found,state_display", Истина, Состояние);
3014
3066
  Исключение
3015
3067
  Возврат Новый Структура("found,error", Истина, ОписаниеОшибки());
3016
3068
  КонецПопытки;
3017
3069
  КонецФункции
3018
3070
 
3071
+ &НаКлиенте
3072
+ Функция ФайлСуществуетMCP(Путь)
3073
+ Попытка
3074
+ Файл = Новый Файл(Путь);
3075
+ Возврат Файл.Существует();
3076
+ Исключение
3077
+ Возврат Ложь;
3078
+ КонецПопытки;
3079
+ КонецФункции
3080
+
3081
+ &НаКлиенте
3082
+ Функция БезопасноеПредставлениеДанныхПоля(Поле)
3083
+ Попытка
3084
+ Возврат Строка(Поле.ПолучитьПредставлениеДанных());
3085
+ Исключение
3086
+ Возврат "";
3087
+ КонецПопытки;
3088
+ КонецФункции
3089
+
3019
3090
  &НаКлиенте
3020
3091
  Функция СохранитьТабличныйДокументMCP(Параметры)
3021
3092
  Если ТестируемоеПриложение = Неопределено Тогда
@@ -3036,62 +3107,34 @@
3036
3107
  КонецЕсли;
3037
3108
 
3038
3109
  // Для tested form field extension for spreadsheet document direct Записать()
3039
- // недоступен/ненадежен; используем штатный метод tested field
3040
- // ЗаписатьСодержимоеВФайл(<СохранитьКак>) и очередь результата файлового
3041
- // диалога. Для PDF пробуем несколько индексов фильтра и оба значения
3042
- // <СохранитьКак> (Истина/Ложь), чтобы не угадывать поведение конкретной формы.
3043
- Если ФорматФайла = "pdf" Тогда
3044
- Файлы = Новый Массив;
3045
- Ошибки = Новый Массив;
3046
- ВариантыСохранитьКак = Новый Массив;
3047
- ВариантыСохранитьКак.Добавить(Истина);
3048
- ВариантыСохранитьКак.Добавить(Ложь);
3049
- Для Каждого СохранитьКак Из ВариантыСохранитьКак Цикл
3050
- Для ИндексФильтра = 1 По 10 Цикл
3051
- ПутьПопытки = ДобавитьСуффиксКПутиMCP(Путь, "_saveas_" + ?(СохранитьКак, "true", "false") + "_filter" + Строка(ИндексФильтра));
3052
- Попытка
3053
- ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, ПутьПопытки, ИндексФильтра);
3054
- Поле.ЗаписатьСодержимоеВФайл(СохранитьКак);
3055
- ОписаниеФайла = Новый Структура("path,filter_index,save_as", ПутьПопытки, ИндексФильтра, СохранитьКак);
3056
- Файлы.Добавить(ОписаниеФайла);
3057
- Исключение
3058
- Ошибки.Добавить(Новый Структура("filter_index,save_as,error", ИндексФильтра, СохранитьКак, ОписаниеОшибки()));
3059
- КонецПопытки;
3060
- КонецЦикла;
3110
+ // недоступен/ненадежен; сначала для любого формата перебираем фильтры
3111
+ // штатного файлового диалога, а Python-сторона проверяет сигнатуру/расширение
3112
+ // реально созданного файла нужного формата.
3113
+ ФайлыФильтров = Новый Массив;
3114
+ ОшибкиФильтров = Новый Массив;
3115
+ ВариантыСохранитьКак = Новый Массив;
3116
+ ВариантыСохранитьКак.Добавить(Истина);
3117
+ ВариантыСохранитьКак.Добавить(Ложь);
3118
+ Для Каждого СохранитьКак Из ВариантыСохранитьКак Цикл
3119
+ Для ИндексФильтра = 1 По 30 Цикл
3120
+ ПутьПопытки = ДобавитьСуффиксКПутиMCP(Путь, "_saveas_" + ?(СохранитьКак, "true", "false") + "_filter" + Строка(ИндексФильтра));
3121
+ Попытка
3122
+ ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, ПутьПопытки, ИндексФильтра);
3123
+ Поле.ЗаписатьСодержимоеВФайл(СохранитьКак);
3124
+ // Не проверяем существование файла на стороне 1С синхронно: web/ws
3125
+ // клиент может материализовать файл чуть позже. Возвращаем все
3126
+ // успешные попытки, а Python-сторона проверяет факт, сигнатуру и формат.
3127
+ ФайлыФильтров.Добавить(Новый Структура("path,filter_index,save_as", ПутьПопытки, ИндексФильтра, СохранитьКак));
3128
+ Исключение
3129
+ ОшибкиФильтров.Добавить(Новый Структура("filter_index,save_as,error", ИндексФильтра, СохранитьКак, ОписаниеОшибки()));
3130
+ КонецПопытки;
3061
3131
  КонецЦикла;
3062
- Если Файлы.Количество() > 0 Тогда
3063
- Возврат Новый Структура("saved,path,format,method,files,errors", Истина, Путь, ФорматФайла, "write_content_to_file_dialog_filters", Файлы, Ошибки);
3064
- КонецЕсли;
3065
- Возврат Новый Структура("saved,path,format,method,files,errors", Ложь, Путь, ФорматФайла, "write_content_to_file_dialog_filters", Файлы, Ошибки);
3132
+ КонецЦикла;
3133
+ Если ФайлыФильтров.Количество() > 0 Тогда
3134
+ Возврат Новый Структура("saved,path,format,method,files,errors", Истина, Путь, ФорматФайла, "write_content_to_file_dialog_filters", ФайлыФильтров, ОшибкиФильтров);
3066
3135
  КонецЕсли;
3067
3136
 
3068
- ОшибкаЗаписатьСодержимое = "";
3069
- Попытка
3070
- ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, Путь, ИндексФильтраДиалогаТабличногоДокументаMCP(ФорматФайла));
3071
- Поле.ЗаписатьСодержимоеВФайл(Истина);
3072
- Возврат Новый Структура("saved,path,format,method,save_as", Истина, Путь, ФорматФайла, "write_content_to_file_dialog", Истина);
3073
- Исключение
3074
- ОшибкаЗаписатьСодержимое = ОписаниеОшибки();
3075
- КонецПопытки;
3076
- Попытка
3077
- ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, Путь, ИндексФильтраДиалогаТабличногоДокументаMCP(ФорматФайла));
3078
- Поле.ЗаписатьСодержимоеВФайл(Ложь);
3079
- Возврат Новый Структура("saved,path,format,method,save_as", Истина, Путь, ФорматФайла, "write_content_to_file_dialog", Ложь);
3080
- Исключение
3081
- Если Не ПустаяСтрока(ОшибкаЗаписатьСодержимое) Тогда
3082
- ОшибкаЗаписатьСодержимое = ОшибкаЗаписатьСодержимое + Символы.ПС + ОписаниеОшибки();
3083
- Иначе
3084
- ОшибкаЗаписатьСодержимое = ОписаниеОшибки();
3085
- КонецЕсли;
3086
- КонецПопытки;
3087
-
3088
- Попытка
3089
- ТипФайла = ТипФайлаТабличногоДокументаMCP(ФорматФайла);
3090
- Поле.Записать(Путь, ТипФайла);
3091
- Возврат Новый Структура("saved,path,format,method", Истина, Путь, ФорматФайла, "write_direct");
3092
- Исключение
3093
- Возврат Новый Структура("saved,path,format,error,content_error", Ложь, Путь, ФорматФайла, ОписаниеОшибки(), ОшибкаЗаписатьСодержимое);
3094
- КонецПопытки;
3137
+ Возврат Новый Структура("saved,path,format,method,files,errors,error", Ложь, Путь, ФорматФайла, "write_content_to_file_dialog_filters", ФайлыФильтров, ОшибкиФильтров, "Ни один фильтр файлового диалога не выполнил сохранение табличного документа");
3095
3138
  КонецФункции
3096
3139
 
3097
3140
  &НаКлиенте
@@ -3118,8 +3161,6 @@
3118
3161
  Возврат ТипФайлаТабличногоДокумента.XLSX;
3119
3162
  ИначеЕсли Формат = "xls" Тогда
3120
3163
  Возврат ТипФайлаТабличногоДокумента.XLS;
3121
- ИначеЕсли Формат = "mxl" Тогда
3122
- Возврат ТипФайлаТабличногоДокумента.MXL;
3123
3164
  ИначеЕсли Формат = "html" Или Формат = "htm" Тогда
3124
3165
  Возврат ТипФайлаТабличногоДокумента.HTML;
3125
3166
  ИначеЕсли Формат = "txt" Тогда
@@ -3138,8 +3179,6 @@
3138
3179
  Возврат 2;
3139
3180
  ИначеЕсли Формат = "xls" Тогда
3140
3181
  Возврат 3;
3141
- ИначеЕсли Формат = "mxl" Тогда
3142
- Возврат 4;
3143
3182
  ИначеЕсли Формат = "html" Или Формат = "htm" Тогда
3144
3183
  Возврат 5;
3145
3184
  ИначеЕсли Формат = "txt" Тогда
@@ -3179,7 +3218,10 @@
3179
3218
  ТипТек = НормализованноеИмяТипа(Строка(ТипЗнч(Объект)));
3180
3219
  ЭтоПолеТабличногоДокумента = ТипТек = "тестируемоеполетабличногодокументаформы";
3181
3220
  Если Не ЭтоПолеТабличногоДокумента И ТипТек = "тестируемоеполеформы" Тогда
3182
- ЭтоПолеТабличногоДокумента = ПредставлениеДанныхПоляТабличныйДокумент(Объект);
3221
+ // На некоторых отчетах ПолучитьПредставлениеДанных() у поля табличного
3222
+ // документа падает, пока поле недоступно для интерактивных действий.
3223
+ // Поэтому сначала проверяем Вид поля, затем представление данных.
3224
+ ЭтоПолеТабличногоДокумента = ВидПоляТабличныйДокумент(Объект) Или ПредставлениеДанныхПоляТабличныйДокумент(Объект);
3183
3225
  КонецЕсли;
3184
3226
  Если Не ЭтоПолеТабличногоДокумента Тогда
3185
3227
  Возврат Ложь;
@@ -3189,6 +3231,15 @@
3189
3231
  Возврат (ПустаяСтрока(Имя) Или ИмяТек = Имя) И (ПустаяСтрока(Заголовок) Или ЗаголовокТек = Заголовок);
3190
3232
  КонецФункции
3191
3233
 
3234
+ &НаКлиенте
3235
+ Функция ВидПоляТабличныйДокумент(Поле)
3236
+ Попытка
3237
+ Возврат НРег(Строка(Поле.Вид)) = "полетабличногодокумента";
3238
+ Исключение
3239
+ Возврат Ложь;
3240
+ КонецПопытки;
3241
+ КонецФункции
3242
+
3192
3243
  &НаКлиенте
3193
3244
  Функция ПредставлениеДанныхПоляТабличныйДокумент(Поле)
3194
3245
  Попытка
@@ -36,7 +36,7 @@
36
36
  <Owners/>
37
37
  <SubordinationUse>ToItems</SubordinationUse>
38
38
  <CodeLength>9</CodeLength>
39
- <DescriptionLength>25</DescriptionLength>
39
+ <DescriptionLength>80</DescriptionLength>
40
40
  <CodeType>String</CodeType>
41
41
  <CodeAllowedLength>Variable</CodeAllowedLength>
42
42
  <CodeSeries>WholeCatalog</CodeSeries>
@@ -36,7 +36,7 @@
36
36
  <Owners/>
37
37
  <SubordinationUse>ToItems</SubordinationUse>
38
38
  <CodeLength>9</CodeLength>
39
- <DescriptionLength>25</DescriptionLength>
39
+ <DescriptionLength>80</DescriptionLength>
40
40
  <CodeType>String</CodeType>
41
41
  <CodeAllowedLength>Variable</CodeAllowedLength>
42
42
  <CodeSeries>WholeCatalog</CodeSeries>
@@ -90,7 +90,7 @@
90
90
  <Type>
91
91
  <v8:Type>xs:string</v8:Type>
92
92
  <v8:StringQualifiers>
93
- <v8:Length>10</v8:Length>
93
+ <v8:Length>80</v8:Length>
94
94
  <v8:AllowedLength>Variable</v8:AllowedLength>
95
95
  </v8:StringQualifiers>
96
96
  </Type>
@@ -3288,7 +3288,7 @@ async def tabular_document_save(
3288
3288
  name: Annotated[str, "Tabular document field technical name"] = "",
3289
3289
  title: Annotated[str, "Tabular document field visible title"] = "",
3290
3290
  path: Annotated[str, "Output file path; empty = auto-generated temp path"] = "",
3291
- format: Annotated[str, "Output format: pdf | xlsx | xls | mxl | html | txt"] = "pdf",
3291
+ format: Annotated[str, "Output format: pdf | xlsx | xls | html | txt"] = "pdf",
3292
3292
  max_depth: Annotated[int, "Maximum search depth"] = 8,
3293
3293
  *,
3294
3294
  session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
@@ -3298,15 +3298,32 @@ async def tabular_document_save(
3298
3298
  return _select_saved_tabular_document_file(result, format)
3299
3299
 
3300
3300
 
3301
+ @mcp.tool()
3302
+ async def tabular_document_state_display(
3303
+ name: Annotated[str, "Tabular document field technical name"] = "",
3304
+ title: Annotated[str, "Tabular document field visible title"] = "",
3305
+ max_depth: Annotated[int, "Maximum search depth"] = 8,
3306
+ *,
3307
+ session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
3308
+ ) -> Any:
3309
+ """Return the state display of a tabular document field, when exposed by 1C."""
3310
+ return await _bridge_call_recorded(session_id, "tabular_document_state_display", {"name": name, "title": title, "max_depth": max_depth})
3311
+
3312
+
3301
3313
  def _select_saved_tabular_document_file(result: Any, format: str) -> Any:
3302
3314
  if not isinstance(result, dict):
3303
3315
  return result
3304
3316
  files = result.get("files")
3305
3317
  if not isinstance(files, list) or not files:
3318
+ if result.get("found", True) is not False:
3319
+ result["saved"] = False
3320
+ result.setdefault("error", f"No files were produced for requested tabular document format: {format.lower()}")
3306
3321
  return result
3307
3322
  selected = _find_saved_tabular_document_file(files, format)
3308
3323
  if selected is None:
3309
3324
  result["selected"] = None
3325
+ result["saved"] = False
3326
+ result["error"] = f"No produced file matches requested tabular document format: {format.lower()}"
3310
3327
  return result
3311
3328
  selected_path = selected.get("path")
3312
3329
  requested_path = result.get("path")
@@ -3333,22 +3350,54 @@ def _find_saved_tabular_document_file(files: list[Any], format: str) -> dict[str
3333
3350
  if not p.is_file():
3334
3351
  continue
3335
3352
  try:
3336
- header = p.read_bytes()[:8]
3353
+ data = p.read_bytes()
3354
+ header = data[:8]
3337
3355
  except OSError:
3338
3356
  continue
3339
3357
  if wanted == "pdf" and header.startswith(b"%PDF"):
3340
3358
  return item
3341
- if wanted in {"xlsx", "docx"} and header.startswith(b"PK\x03\x04"):
3359
+ if wanted == "xlsx" and _zip_contains(p, "xl/workbook.xml"):
3360
+ return item
3361
+ if wanted == "docx" and _zip_contains(p, "word/document.xml"):
3342
3362
  return item
3343
3363
  if wanted == "xls" and header.startswith(bytes.fromhex("d0cf11e0a1b11ae1")):
3344
3364
  return item
3345
- if wanted in {"html", "htm"} and (header.startswith(b"<") or header.startswith(b"\xef\xbb\xbf<")):
3365
+ if wanted in {"html", "htm"} and (data.startswith(b"<") or data.startswith(b"\xef\xbb\xbf<")):
3346
3366
  return item
3347
- if wanted in {"txt", "mxl"} and p.stat().st_size > 0:
3367
+ if wanted == "txt" and _looks_like_plain_text(data):
3348
3368
  return item
3349
3369
  return None
3350
3370
 
3351
3371
 
3372
+ def _zip_contains(path: Path, member: str) -> bool:
3373
+ import zipfile
3374
+
3375
+ try:
3376
+ with zipfile.ZipFile(path) as archive:
3377
+ return member in archive.namelist()
3378
+ except Exception:
3379
+ return False
3380
+
3381
+
3382
+ def _looks_like_plain_text(data: bytes) -> bool:
3383
+ if not data:
3384
+ return False
3385
+ sample = data[:4096]
3386
+ if sample.startswith(b"\xef\xbb\xbf"):
3387
+ sample = sample[3:]
3388
+ stripped = sample.lstrip().lower()
3389
+ if stripped.startswith((b"<", b"<!doctype", b"<html")):
3390
+ return False
3391
+ if b"\x00" in sample:
3392
+ return False
3393
+ try:
3394
+ sample.decode("utf-8")
3395
+ return True
3396
+ except UnicodeDecodeError:
3397
+ return False
3398
+
3399
+
3400
+
3352
3401
  @mcp.tool()
3353
3402
  async def toggle_field(
3354
3403
  field_name: Annotated[str, "Field technical name"],
File without changes
File without changes
File without changes
File without changes
File without changes