answer42 0.2.24__tar.gz → 0.2.26__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.
- {answer42-0.2.24 → answer42-0.2.26}/PKG-INFO +1 -1
- {answer42-0.2.24 → answer42-0.2.26}/pyproject.toml +1 -1
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +81 -19
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/credentials.py +53 -11
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/protocol.py +2 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/release_helper.py +52 -7
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/server.py +77 -20
- {answer42-0.2.24 → answer42-0.2.26}/.gitignore +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/LICENSE +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/README.md +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/credentials.example.json +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/docs/agent-installation.md +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/docs/architecture.md +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/docs/installation.md +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/scripts/build_cf.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/scripts/build_pages.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/scripts/e2e_stable.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/scripts/rag_cli.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/Configuration.xml +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/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
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/platform.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/rag/parsers.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/rag/service.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/runtime.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.2.24 → answer42-0.2.26}/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.
|
|
3
|
+
Version: 0.2.26
|
|
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.
|
|
3
|
+
version = "0.2.26"
|
|
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"
|
|
@@ -238,11 +238,33 @@
|
|
|
238
238
|
|
|
239
239
|
#Область КомандыMCP
|
|
240
240
|
|
|
241
|
+
&НаКлиенте
|
|
242
|
+
Функция ВерсияПротоколаМенеджераMCP()
|
|
243
|
+
Возврат "2026.07.02.2";
|
|
244
|
+
КонецФункции
|
|
245
|
+
|
|
246
|
+
&НаКлиенте
|
|
247
|
+
Функция ИнформацияОМенеджереMCP()
|
|
248
|
+
Возврат Новый Структура("manager_protocol_version", ВерсияПротоколаМенеджераMCP());
|
|
249
|
+
КонецФункции
|
|
250
|
+
|
|
251
|
+
&НаКлиенте
|
|
252
|
+
Процедура ПроверитьВерсиюПротоколаМенеджераMCP(Параметры)
|
|
253
|
+
ОжидаемаяВерсия = СвойствоСтруктуры(Параметры, "expected_manager_protocol_version", "");
|
|
254
|
+
Если Не ПустаяСтрока(ОжидаемаяВерсия) И ОжидаемаяВерсия <> ВерсияПротоколаМенеджераMCP() Тогда
|
|
255
|
+
ВызватьИсключение "Несовместимая версия MCPTestManager.cf: Python ожидает протокол "
|
|
256
|
+
+ ОжидаемаяВерсия + ", обработка поддерживает " + ВерсияПротоколаМенеджераMCP()
|
|
257
|
+
+ ". Пересоберите MCPTestManager.cf из актуальных исходников и перезапустите Answer42 MCP.";
|
|
258
|
+
КонецЕсли;
|
|
259
|
+
КонецПроцедуры
|
|
260
|
+
|
|
241
261
|
&НаКлиенте
|
|
242
262
|
Функция ВыполнитьКоманду(Метод, Параметры)
|
|
243
263
|
Если Метод = "ping" Тогда
|
|
244
264
|
Лог("ВыполнитьКоманду: ping");
|
|
245
|
-
Возврат Новый Структура("pong",
|
|
265
|
+
Возврат Новый Структура("pong,manager_protocol_version", Истина, ВерсияПротоколаМенеджераMCP());
|
|
266
|
+
ИначеЕсли Метод = "manager_info" Тогда
|
|
267
|
+
Возврат ИнформацияОМенеджереMCP();
|
|
246
268
|
ИначеЕсли Метод = "connect_test_client" Тогда
|
|
247
269
|
Лог("ВыполнитьКоманду: connect_test_client");
|
|
248
270
|
Возврат ПодключитьКлиентТестирования(Параметры);
|
|
@@ -462,6 +484,7 @@
|
|
|
462
484
|
|
|
463
485
|
&НаКлиенте
|
|
464
486
|
Функция ПодключитьКлиентТестирования(Параметры)
|
|
487
|
+
ПроверитьВерсиюПротоколаМенеджераMCP(Параметры);
|
|
465
488
|
URLБазы = СвойствоСтруктуры(Параметры, "base_url", "");
|
|
466
489
|
ТипПодключения = НРег(СвойствоСтруктуры(Параметры, "connection_mode", "ws"));
|
|
467
490
|
СтрокаПодключения = СвойствоСтруктуры(Параметры, "connection_value", URLБазы);
|
|
@@ -504,6 +527,7 @@
|
|
|
504
527
|
|
|
505
528
|
&НаКлиенте
|
|
506
529
|
Функция ПодключитьЗапущенныйКлиентТестирования(Параметры)
|
|
530
|
+
ПроверитьВерсиюПротоколаМенеджераMCP(Параметры);
|
|
507
531
|
URLБазы = СвойствоСтруктуры(Параметры, "base_url", "");
|
|
508
532
|
ТипПодключения = НРег(СвойствоСтруктуры(Параметры, "connection_mode", "ws"));
|
|
509
533
|
СтрокаПодключения = СвойствоСтруктуры(Параметры, "connection_value", URLБазы);
|
|
@@ -2926,12 +2950,12 @@
|
|
|
2926
2950
|
Глубина = СвойствоСтруктуры(Параметры, "max_depth", 8);
|
|
2927
2951
|
ВключатьТекст = СвойствоСтруктуры(Параметры, "include_text", Ложь);
|
|
2928
2952
|
Результат = Новый Массив;
|
|
2929
|
-
НайтиТабличныеДокументыРекурсивно(АктивноеОкно, Результат, ВключатьТекст, 0, Глубина);
|
|
2953
|
+
НайтиТабличныеДокументыРекурсивно(АктивноеОкно, Результат, ВключатьТекст, Параметры, 0, Глубина);
|
|
2930
2954
|
Возврат Новый Структура("count,items", Результат.Количество(), Результат);
|
|
2931
2955
|
КонецФункции
|
|
2932
2956
|
|
|
2933
2957
|
&НаКлиенте
|
|
2934
|
-
Процедура НайтиТабличныеДокументыРекурсивно(Объект, Результат, ВключатьТекст, ТекущаяГлубина, МаксимальнаяГлубина)
|
|
2958
|
+
Процедура НайтиТабличныеДокументыРекурсивно(Объект, Результат, ВключатьТекст, ПараметрыТекста, ТекущаяГлубина, МаксимальнаяГлубина)
|
|
2935
2959
|
Если ТекущаяГлубина > МаксимальнаяГлубина Тогда
|
|
2936
2960
|
Возврат;
|
|
2937
2961
|
КонецЕсли;
|
|
@@ -2944,20 +2968,31 @@
|
|
|
2944
2968
|
Элемент.Вставить("title", БезопасныйЗаголовок(Подчиненный));
|
|
2945
2969
|
Элемент.Вставить("type", Строка(ТипЗнч(Подчиненный)));
|
|
2946
2970
|
Элемент.Вставить("data", БезопасноеПредставлениеДанныхПоля(Подчиненный));
|
|
2971
|
+
РазмерыДокумента = РазмерыТабличногоДокументаИзПоля(Подчиненный);
|
|
2972
|
+
Если РазмерыДокумента.Свойство("error") Тогда
|
|
2973
|
+
Элемент.Вставить("dimensions_error", РазмерыДокумента.error);
|
|
2974
|
+
Иначе
|
|
2975
|
+
Элемент.Вставить("rows", РазмерыДокумента.rows);
|
|
2976
|
+
Элемент.Вставить("columns", РазмерыДокумента.columns);
|
|
2977
|
+
КонецЕсли;
|
|
2947
2978
|
Если ВключатьТекст Тогда
|
|
2948
|
-
ТекстДокумента = ТекстТабличногоДокументаИзПоля(
|
|
2979
|
+
ТекстДокумента = ТекстТабличногоДокументаИзПоля(Подчиненный, ПараметрыТекста);
|
|
2949
2980
|
Если ТекстДокумента.Свойство("error") Тогда
|
|
2950
2981
|
Элемент.Вставить("text_error", ТекстДокумента.error);
|
|
2951
2982
|
Иначе
|
|
2952
2983
|
Элемент.Вставить("rows", ТекстДокумента.rows);
|
|
2953
2984
|
Элемент.Вставить("columns", ТекстДокумента.columns);
|
|
2954
2985
|
Элемент.Вставить("address", ТекстДокумента.address);
|
|
2955
|
-
Элемент.Вставить("
|
|
2986
|
+
Элемент.Вставить("row_start", ТекстДокумента.row_start);
|
|
2987
|
+
Элемент.Вставить("row_end", ТекстДокумента.row_end);
|
|
2988
|
+
Элемент.Вставить("column_start", ТекстДокумента.column_start);
|
|
2989
|
+
Элемент.Вставить("column_end", ТекстДокумента.column_end);
|
|
2990
|
+
Элемент.Вставить("cells", ТекстДокумента.cells);
|
|
2956
2991
|
КонецЕсли;
|
|
2957
2992
|
КонецЕсли;
|
|
2958
2993
|
Результат.Добавить(Элемент);
|
|
2959
2994
|
КонецЕсли;
|
|
2960
|
-
НайтиТабличныеДокументыРекурсивно(Подчиненный, Результат, ВключатьТекст, ТекущаяГлубина + 1, МаксимальнаяГлубина);
|
|
2995
|
+
НайтиТабличныеДокументыРекурсивно(Подчиненный, Результат, ВключатьТекст, ПараметрыТекста, ТекущаяГлубина + 1, МаксимальнаяГлубина);
|
|
2961
2996
|
КонецЦикла;
|
|
2962
2997
|
Исключение
|
|
2963
2998
|
Инфо = ИнформацияОбОшибке();
|
|
@@ -2990,29 +3025,56 @@
|
|
|
2990
3025
|
Если Поле = Неопределено Тогда
|
|
2991
3026
|
Возврат Новый Структура("found", Ложь);
|
|
2992
3027
|
КонецЕсли;
|
|
2993
|
-
ДанныеТекста = ТекстТабличногоДокументаИзПоля(
|
|
3028
|
+
ДанныеТекста = ТекстТабличногоДокументаИзПоля(Поле, Параметры);
|
|
2994
3029
|
Если ДанныеТекста.Свойство("error") Тогда
|
|
2995
3030
|
Возврат Новый Структура("found,error", Истина, ДанныеТекста.error);
|
|
2996
3031
|
КонецЕсли;
|
|
2997
|
-
Возврат Новый Структура("found,rows,columns,address,
|
|
3032
|
+
Возврат Новый Структура("found,rows,columns,address,row_start,row_end,column_start,column_end,cells", Истина, ДанныеТекста.rows, ДанныеТекста.columns, ДанныеТекста.address, ДанныеТекста.row_start, ДанныеТекста.row_end, ДанныеТекста.column_start, ДанныеТекста.column_end, ДанныеТекста.cells);
|
|
2998
3033
|
КонецФункции
|
|
2999
3034
|
|
|
3000
3035
|
&НаКлиенте
|
|
3001
|
-
Функция
|
|
3036
|
+
Функция РазмерыТабличногоДокументаИзПоля(Поле)
|
|
3002
3037
|
Попытка
|
|
3003
|
-
|
|
3004
|
-
|
|
3038
|
+
Возврат Новый Структура("rows,columns", Поле.ПолучитьРазмерОбластиДанныхДокументаПоВертикали(), Поле.ПолучитьРазмерОбластиДанныхДокументаПоГоризонтали());
|
|
3039
|
+
Исключение
|
|
3040
|
+
Возврат Новый Структура("error", ОписаниеОшибки());
|
|
3041
|
+
КонецПопытки;
|
|
3042
|
+
КонецФункции
|
|
3043
|
+
|
|
3044
|
+
&НаКлиенте
|
|
3045
|
+
Функция ТекстТабличногоДокументаИзПоля(Поле, Параметры)
|
|
3046
|
+
Попытка
|
|
3047
|
+
Размеры = РазмерыТабличногоДокументаИзПоля(Поле);
|
|
3048
|
+
Если Размеры.Свойство("error") Тогда
|
|
3049
|
+
Возврат Новый Структура("error", Размеры.error);
|
|
3050
|
+
КонецЕсли;
|
|
3051
|
+
КоличествоСтрок = Размеры.rows;
|
|
3052
|
+
КоличествоКолонок = Размеры.columns;
|
|
3005
3053
|
Если КоличествоСтрок = 0 Или КоличествоКолонок = 0 Тогда
|
|
3006
|
-
Возврат Новый Структура("rows,columns,address,
|
|
3054
|
+
Возврат Новый Структура("rows,columns,address,row_start,row_end,column_start,column_end,cells", 0, 0, "", 0, 0, 0, 0, Новый Массив);
|
|
3055
|
+
КонецЕсли;
|
|
3056
|
+
НачальнаяСтрока = Макс(1, СвойствоСтруктуры(Параметры, "row_start", 1));
|
|
3057
|
+
НачальнаяКолонка = Макс(1, СвойствоСтруктуры(Параметры, "column_start", 1));
|
|
3058
|
+
КонечнаяСтрока = СвойствоСтруктуры(Параметры, "row_end", 0);
|
|
3059
|
+
КонечнаяКолонка = СвойствоСтруктуры(Параметры, "column_end", 0);
|
|
3060
|
+
МаксСтрок = СвойствоСтруктуры(Параметры, "max_rows", 50);
|
|
3061
|
+
МаксКолонок = СвойствоСтруктуры(Параметры, "max_columns", 20);
|
|
3062
|
+
Если КонечнаяСтрока <= 0 Тогда
|
|
3063
|
+
КонечнаяСтрока = ?(МаксСтрок > 0, НачальнаяСтрока + МаксСтрок - 1, КоличествоСтрок);
|
|
3064
|
+
КонецЕсли;
|
|
3065
|
+
Если КонечнаяКолонка <= 0 Тогда
|
|
3066
|
+
КонечнаяКолонка = ?(МаксКолонок > 0, НачальнаяКолонка + МаксКолонок - 1, КоличествоКолонок);
|
|
3067
|
+
КонецЕсли;
|
|
3068
|
+
КонечнаяСтрока = Мин(КоличествоСтрок, КонечнаяСтрока);
|
|
3069
|
+
КонечнаяКолонка = Мин(КоличествоКолонок, КонечнаяКолонка);
|
|
3070
|
+
Если НачальнаяСтрока > КоличествоСтрок Или НачальнаяКолонка > КоличествоКолонок Или КонечнаяСтрока < НачальнаяСтрока Или КонечнаяКолонка < НачальнаяКолонка Тогда
|
|
3071
|
+
Возврат Новый Структура("rows,columns,address,row_start,row_end,column_start,column_end,cells", КоличествоСтрок, КоличествоКолонок, "", НачальнаяСтрока, КонечнаяСтрока, НачальнаяКолонка, КонечнаяКолонка, Новый Массив);
|
|
3007
3072
|
КонецЕсли;
|
|
3008
|
-
|
|
3009
|
-
ТекстВсейОбласти = Поле.ПолучитьТекстОбласти(АдресВсейОбласти);
|
|
3010
|
-
МаксСтрок = Мин(КоличествоСтрок, 50);
|
|
3011
|
-
МаксКолонок = Мин(КоличествоКолонок, 20);
|
|
3073
|
+
АдресОбласти = АдресОбластиТабличногоДокументаMCP(НачальнаяСтрока, НачальнаяКолонка, КонечнаяСтрока, КонечнаяКолонка);
|
|
3012
3074
|
Ячейки = Новый Массив;
|
|
3013
|
-
Для НомерСтроки =
|
|
3075
|
+
Для НомерСтроки = НачальнаяСтрока По КонечнаяСтрока Цикл
|
|
3014
3076
|
СтрокаТекстов = Новый Массив;
|
|
3015
|
-
Для НомерКолонки =
|
|
3077
|
+
Для НомерКолонки = НачальнаяКолонка По КонечнаяКолонка Цикл
|
|
3016
3078
|
Попытка
|
|
3017
3079
|
АдресЯчейки = АдресОбластиТабличногоДокументаMCP(НомерСтроки, НомерКолонки, НомерСтроки, НомерКолонки);
|
|
3018
3080
|
СтрокаТекстов.Добавить(Поле.ПолучитьТекстОбласти(АдресЯчейки));
|
|
@@ -3022,7 +3084,7 @@
|
|
|
3022
3084
|
КонецЦикла;
|
|
3023
3085
|
Ячейки.Добавить(СтрокаТекстов);
|
|
3024
3086
|
КонецЦикла;
|
|
3025
|
-
Возврат Новый Структура("rows,columns,address,
|
|
3087
|
+
Возврат Новый Структура("rows,columns,address,row_start,row_end,column_start,column_end,cells", КоличествоСтрок, КоличествоКолонок, АдресОбласти, НачальнаяСтрока, КонечнаяСтрока, НачальнаяКолонка, КонечнаяКолонка, Ячейки);
|
|
3026
3088
|
Исключение
|
|
3027
3089
|
Возврат Новый Структура("error", ОписаниеОшибки());
|
|
3028
3090
|
КонецПопытки;
|
|
Binary file
|
|
@@ -58,38 +58,80 @@ class CredentialsStore:
|
|
|
58
58
|
# ------------------------------------------------------------------
|
|
59
59
|
|
|
60
60
|
def lookup(self, base_url: str) -> dict[str, str] | None:
|
|
61
|
-
"""Return {username, password} for *base_url*, or None."""
|
|
62
|
-
|
|
61
|
+
"""Return {username, password} for *base_url*, title or alias, or None."""
|
|
62
|
+
resolved = self.resolve(base_url)
|
|
63
|
+
if resolved is None:
|
|
64
|
+
return None
|
|
65
|
+
return {"username": resolved["username"], "password": resolved["password"]}
|
|
66
|
+
|
|
67
|
+
def resolve(self, base_url_or_title: str) -> dict[str, str] | None:
|
|
68
|
+
"""Return full credential entry for URL, title or alias, without mutating it."""
|
|
69
|
+
value = str(base_url_or_title or "").strip()
|
|
70
|
+
url = value.rstrip("/")
|
|
71
|
+
normalized_title = value.lower()
|
|
72
|
+
for entry in self._entries:
|
|
73
|
+
title = str(entry.get("title", "")).strip().lower()
|
|
74
|
+
aliases = [str(a).strip().lower() for a in entry.get("aliases", [])]
|
|
75
|
+
if title and title == normalized_title or normalized_title in aliases:
|
|
76
|
+
return dict(entry)
|
|
63
77
|
for entry in self._entries:
|
|
64
78
|
pattern = entry["url"].rstrip("/")
|
|
65
79
|
if url == pattern:
|
|
66
|
-
return
|
|
80
|
+
return dict(entry)
|
|
67
81
|
for entry in self._entries:
|
|
68
82
|
pattern = entry["url"].rstrip("/")
|
|
69
83
|
if fnmatch.fnmatch(url, pattern):
|
|
70
|
-
return
|
|
84
|
+
return dict(entry)
|
|
71
85
|
return None
|
|
72
86
|
|
|
73
|
-
def list_entries(self) -> list[dict[str,
|
|
74
|
-
"""Return credential URL patterns without usernames or passwords."""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
def list_entries(self) -> list[dict[str, object]]:
|
|
88
|
+
"""Return credential titles and URL patterns without usernames or passwords."""
|
|
89
|
+
result: list[dict[str, str]] = []
|
|
90
|
+
for entry in self._entries:
|
|
91
|
+
public = {"url": entry["url"]}
|
|
92
|
+
if entry.get("title"):
|
|
93
|
+
public["title"] = entry["title"]
|
|
94
|
+
if entry.get("aliases"):
|
|
95
|
+
public["aliases"] = list(entry["aliases"])
|
|
96
|
+
result.append(public)
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
def titles(self) -> list[dict[str, object]]:
|
|
100
|
+
"""Return named credential entries only, without secrets."""
|
|
101
|
+
return [e for e in self.list_entries() if e.get("title")]
|
|
102
|
+
|
|
103
|
+
def upsert(self, url: str, username: str, password: str, title: str = "", aliases: list[str] | None = None) -> bool:
|
|
78
104
|
"""Add or update a credential entry for *url*.
|
|
79
105
|
|
|
80
106
|
Returns True when an existing entry was updated, False when a new entry
|
|
81
107
|
was created. Usernames and passwords are intentionally not returned.
|
|
82
108
|
"""
|
|
83
109
|
url = url.rstrip("/")
|
|
110
|
+
title = str(title or "").strip()
|
|
111
|
+
aliases = [str(alias).strip() for alias in (aliases or []) if str(alias).strip()]
|
|
84
112
|
updated = False
|
|
85
113
|
for entry in self._entries:
|
|
86
|
-
if entry["url"].rstrip("/") == url:
|
|
114
|
+
if entry["url"].rstrip("/") == url or (title and str(entry.get("title", "")).strip() == title):
|
|
115
|
+
entry["url"] = url
|
|
87
116
|
entry["username"] = username
|
|
88
117
|
entry["password"] = password
|
|
118
|
+
if title:
|
|
119
|
+
entry["title"] = title
|
|
120
|
+
else:
|
|
121
|
+
entry.pop("title", None)
|
|
122
|
+
if aliases:
|
|
123
|
+
entry["aliases"] = aliases
|
|
124
|
+
else:
|
|
125
|
+
entry.pop("aliases", None)
|
|
89
126
|
updated = True
|
|
90
127
|
break
|
|
91
128
|
else:
|
|
92
|
-
|
|
129
|
+
entry = {"url": url, "username": username, "password": password}
|
|
130
|
+
if title:
|
|
131
|
+
entry["title"] = title
|
|
132
|
+
if aliases:
|
|
133
|
+
entry["aliases"] = aliases
|
|
134
|
+
self._entries.append(entry)
|
|
93
135
|
self._save()
|
|
94
136
|
return updated
|
|
95
137
|
|
|
@@ -35,9 +35,46 @@ def _replace_version(pyproject: Path, version: str) -> bool:
|
|
|
35
35
|
return True
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def _commits_since_last_tag() -> str:
|
|
39
|
+
previous_tag = _git_output(["describe", "--tags", "--abbrev=0", "--match", "v*"])
|
|
40
|
+
if previous_tag:
|
|
41
|
+
return _git_output(["log", "--pretty=format:- %s (%h)", f"{previous_tag}..HEAD"])
|
|
42
|
+
return _git_output(["log", "--pretty=format:- %s (%h)", "HEAD"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _default_release_notes(tag: str) -> str:
|
|
46
|
+
try:
|
|
47
|
+
commits = _commits_since_last_tag()
|
|
48
|
+
except subprocess.CalledProcessError:
|
|
49
|
+
commits = _git_output(["log", "--pretty=format:- %s (%h)", "-n", "20"])
|
|
50
|
+
if not commits.strip():
|
|
51
|
+
commits = "- Maintenance release"
|
|
52
|
+
return (
|
|
53
|
+
f"# Answer42 {tag}\n\n"
|
|
54
|
+
"## Changes\n"
|
|
55
|
+
f"{commits}\n\n"
|
|
56
|
+
"## Verification\n"
|
|
57
|
+
"- GitLab CI runs unit tests and package checks on branch and tag pipelines.\n"
|
|
58
|
+
"- PyPI publishing and GitLab Release creation are performed by the tag pipeline.\n"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _ensure_release_notes(repo_root: Path, tag: str, *, dry_run: bool, overwrite: bool) -> Path:
|
|
63
|
+
notes_path = repo_root / "release-notes" / f"{tag}.md"
|
|
64
|
+
if notes_path.exists() and notes_path.stat().st_size > 0 and not overwrite:
|
|
65
|
+
print(f"Release notes already exist: {notes_path.relative_to(repo_root)}")
|
|
66
|
+
return notes_path
|
|
67
|
+
text = _default_release_notes(tag)
|
|
68
|
+
print(f"+ write {notes_path.relative_to(repo_root)}")
|
|
69
|
+
if not dry_run:
|
|
70
|
+
notes_path.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
notes_path.write_text(text, encoding="utf-8")
|
|
72
|
+
return notes_path
|
|
73
|
+
|
|
74
|
+
|
|
38
75
|
def main(argv: list[str] | None = None) -> int:
|
|
39
76
|
parser = argparse.ArgumentParser(
|
|
40
|
-
description="Bump
|
|
77
|
+
description="Bump version, create release notes, commit, tag and push to trigger Answer42 release CI"
|
|
41
78
|
)
|
|
42
79
|
parser.add_argument("version", help="Version without leading v, e.g. 0.2.1")
|
|
43
80
|
parser.add_argument("--remote", default="origin", help="Git remote to push. Default: origin")
|
|
@@ -45,6 +82,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
45
82
|
parser.add_argument("--dry-run", action="store_true", help="Print commands without executing them")
|
|
46
83
|
parser.add_argument("--no-push", action="store_true", help="Create commit/tag locally but do not push")
|
|
47
84
|
parser.add_argument("--allow-dirty", action="store_true", help="Allow unrelated uncommitted changes")
|
|
85
|
+
parser.add_argument("--overwrite-notes", action="store_true", help="Overwrite existing release notes")
|
|
48
86
|
args = parser.parse_args(argv)
|
|
49
87
|
|
|
50
88
|
version = args.version.removeprefix("v")
|
|
@@ -63,21 +101,28 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
63
101
|
if existing_tags:
|
|
64
102
|
raise SystemExit(f"Tag already exists: {tag}")
|
|
65
103
|
|
|
66
|
-
print(f"
|
|
104
|
+
print(f"Preparing {tag}")
|
|
67
105
|
changed = True
|
|
68
106
|
if not args.dry_run:
|
|
69
107
|
changed = _replace_version(pyproject, version)
|
|
70
108
|
else:
|
|
71
|
-
print(f"+ update {pyproject}: version = {version!r}")
|
|
109
|
+
print(f"+ update {pyproject.relative_to(repo_root)}: version = {version!r}")
|
|
110
|
+
notes_path = _ensure_release_notes(
|
|
111
|
+
repo_root, tag, dry_run=args.dry_run, overwrite=args.overwrite_notes
|
|
112
|
+
)
|
|
72
113
|
|
|
114
|
+
files_to_add = [str(notes_path.relative_to(repo_root))]
|
|
73
115
|
if changed:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
print(f"Version is already {version}; tagging current HEAD without a version commit.")
|
|
116
|
+
files_to_add.insert(0, str(pyproject.relative_to(repo_root)))
|
|
117
|
+
_run(["git", "add", *files_to_add], dry_run=args.dry_run)
|
|
118
|
+
_run(["git", "commit", "-m", f"Release {tag}"], dry_run=args.dry_run)
|
|
78
119
|
_run(["git", "tag", "-a", tag, "-m", f"Release {tag}"], dry_run=args.dry_run)
|
|
79
120
|
if not args.no_push:
|
|
80
121
|
_run(["git", "push", args.remote, args.branch], dry_run=args.dry_run)
|
|
81
122
|
_run(["git", "push", args.remote, tag], dry_run=args.dry_run)
|
|
82
123
|
print("CI will publish PyPI package and create GitLab Release from the pushed tag.")
|
|
83
124
|
return 0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__": # pragma: no cover
|
|
128
|
+
raise SystemExit(main())
|
|
@@ -27,6 +27,7 @@ from mcp.server.fastmcp import FastMCP
|
|
|
27
27
|
|
|
28
28
|
from . import __version__
|
|
29
29
|
from . import os_support
|
|
30
|
+
from .protocol import MANAGER_PROTOCOL_VERSION
|
|
30
31
|
from . import platform as _platform
|
|
31
32
|
from . import runtime
|
|
32
33
|
from . import skill_installer
|
|
@@ -1697,6 +1698,13 @@ async def start_session(
|
|
|
1697
1698
|
rag_enabled: Annotated[bool, "Enable built-in RAG for this session"] = True,
|
|
1698
1699
|
) -> dict[str, Any]:
|
|
1699
1700
|
"""One-shot session factory: detect version, start infrastructure and connect the 1C test client."""
|
|
1701
|
+
credential_ref = _resolve_credential_reference(base_url)
|
|
1702
|
+
if credential_ref is not None and credential_ref.get("url"):
|
|
1703
|
+
base_url = str(credential_ref["url"])
|
|
1704
|
+
if not username:
|
|
1705
|
+
username = str(credential_ref.get("username", ""))
|
|
1706
|
+
if not password:
|
|
1707
|
+
password = str(credential_ref.get("password", ""))
|
|
1700
1708
|
base_url = _platform.normalize_infobase_url(base_url)
|
|
1701
1709
|
connection_mode = "auto"
|
|
1702
1710
|
connection_value = ""
|
|
@@ -2116,18 +2124,24 @@ def _lookup_credentials(base_url: str) -> dict[str, str] | None:
|
|
|
2116
2124
|
return _credentials_store().lookup(base_url)
|
|
2117
2125
|
|
|
2118
2126
|
|
|
2127
|
+
def _resolve_credential_reference(value: str) -> dict[str, str] | None:
|
|
2128
|
+
if not _CREDENTIAL_STORE_ENABLED or not value:
|
|
2129
|
+
return None
|
|
2130
|
+
return _credentials_store().resolve(value)
|
|
2131
|
+
|
|
2132
|
+
|
|
2119
2133
|
@mcp.tool()
|
|
2120
2134
|
def credentials_check(base_url: str) -> dict[str, object]:
|
|
2121
|
-
"""Check whether credentials exist for a base URL without returning secrets."""
|
|
2135
|
+
"""Check whether credentials exist for a base URL or saved title without returning secrets."""
|
|
2122
2136
|
if not _CREDENTIAL_STORE_ENABLED:
|
|
2123
2137
|
return {"base_url": base_url, "found": False, "disabled": True}
|
|
2124
|
-
credentials = _credentials_store().
|
|
2138
|
+
credentials = _credentials_store().resolve(base_url)
|
|
2125
2139
|
if credentials is None:
|
|
2126
2140
|
return {"base_url": base_url, "found": False}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
"
|
|
2130
|
-
|
|
2141
|
+
result: dict[str, object] = {"base_url": base_url, "found": True, "url": credentials.get("url", "")}
|
|
2142
|
+
if credentials.get("title"):
|
|
2143
|
+
result["title"] = credentials["title"]
|
|
2144
|
+
return result
|
|
2131
2145
|
|
|
2132
2146
|
|
|
2133
2147
|
@mcp.tool()
|
|
@@ -2135,6 +2149,8 @@ def credentials_save(
|
|
|
2135
2149
|
url: Annotated[str, "Base URL pattern (exact or wildcard like https://*.example.com/*)"],
|
|
2136
2150
|
username: Annotated[str, "1C username"],
|
|
2137
2151
|
password: Annotated[str, "1C password"],
|
|
2152
|
+
title: Annotated[str, "Optional saved base title/alias, e.g. bp27932"] = "",
|
|
2153
|
+
aliases: Annotated[str, "Optional comma-separated aliases for the same entry"] = "",
|
|
2138
2154
|
) -> dict[str, object]:
|
|
2139
2155
|
"""Save or update credentials for a URL pattern in the local credentials store.
|
|
2140
2156
|
|
|
@@ -2144,12 +2160,14 @@ def credentials_save(
|
|
|
2144
2160
|
"""
|
|
2145
2161
|
if not _CREDENTIAL_STORE_ENABLED:
|
|
2146
2162
|
raise _credential_store_disabled_error()
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
"
|
|
2152
|
-
|
|
2163
|
+
alias_list = [part.strip() for part in aliases.split(",") if part.strip()]
|
|
2164
|
+
updated = _credentials_store().upsert(url, username, password, title=title, aliases=alias_list)
|
|
2165
|
+
result: dict[str, object] = {"url": url, "saved": True, "updated": updated}
|
|
2166
|
+
if title:
|
|
2167
|
+
result["title"] = title
|
|
2168
|
+
if alias_list:
|
|
2169
|
+
result["aliases"] = alias_list
|
|
2170
|
+
return result
|
|
2153
2171
|
|
|
2154
2172
|
|
|
2155
2173
|
@mcp.tool()
|
|
@@ -2164,13 +2182,21 @@ def credentials_remove(
|
|
|
2164
2182
|
|
|
2165
2183
|
|
|
2166
2184
|
@mcp.tool()
|
|
2167
|
-
def credentials_list() -> list[dict[str,
|
|
2168
|
-
"""List configured credential URL patterns (no usernames or passwords)."""
|
|
2185
|
+
def credentials_list() -> list[dict[str, object]]:
|
|
2186
|
+
"""List configured credential URL patterns/titles (no usernames or passwords)."""
|
|
2169
2187
|
if not _CREDENTIAL_STORE_ENABLED:
|
|
2170
2188
|
return []
|
|
2171
2189
|
return _credentials_store().list_entries()
|
|
2172
2190
|
|
|
2173
2191
|
|
|
2192
|
+
@mcp.tool()
|
|
2193
|
+
def credentials_titles() -> list[dict[str, object]]:
|
|
2194
|
+
"""List saved credential titles/aliases that can be used as start_session base_url."""
|
|
2195
|
+
if not _CREDENTIAL_STORE_ENABLED:
|
|
2196
|
+
return []
|
|
2197
|
+
return _credentials_store().titles()
|
|
2198
|
+
|
|
2199
|
+
|
|
2174
2200
|
_PLACEHOLDER_PASSWORD_RE = re.compile(r"^\*{3,}$")
|
|
2175
2201
|
_AUTH_FAILURE_MARKERS = (
|
|
2176
2202
|
"пользователь иб не идентифицирован",
|
|
@@ -2448,6 +2474,23 @@ async def _wait_for_test_client_ready(sess: SessionState, host: str, port: int,
|
|
|
2448
2474
|
raise RuntimeError(message)
|
|
2449
2475
|
|
|
2450
2476
|
|
|
2477
|
+
async def _verify_manager_protocol(sess: SessionState) -> dict[str, Any]:
|
|
2478
|
+
info = await sess.bridge.call("manager_info", {})
|
|
2479
|
+
if not isinstance(info, dict):
|
|
2480
|
+
raise RuntimeError(
|
|
2481
|
+
"MCPTestManager.cf is too old or incompatible: manager_info returned an unexpected response. "
|
|
2482
|
+
f"Expected protocol {MANAGER_PROTOCOL_VERSION}. Rebuild/reload MCPTestManager.cf."
|
|
2483
|
+
)
|
|
2484
|
+
actual = str(info.get("manager_protocol_version") or "")
|
|
2485
|
+
if actual != MANAGER_PROTOCOL_VERSION:
|
|
2486
|
+
raise RuntimeError(
|
|
2487
|
+
"MCPTestManager.cf protocol mismatch: "
|
|
2488
|
+
f"Python expects {MANAGER_PROTOCOL_VERSION}, but 1C manager reports {actual or '<missing>'}. "
|
|
2489
|
+
"Rebuild MCPTestManager.cf from current sources and restart/reload Answer42 MCP."
|
|
2490
|
+
)
|
|
2491
|
+
return info
|
|
2492
|
+
|
|
2493
|
+
|
|
2451
2494
|
async def _connect_test_client_impl(
|
|
2452
2495
|
sess: SessionState,
|
|
2453
2496
|
*,
|
|
@@ -2465,6 +2508,7 @@ async def _connect_test_client_impl(
|
|
|
2465
2508
|
) -> Any:
|
|
2466
2509
|
"""Launch 1cv8c /TESTCLIENT from Python and attach the test manager to it."""
|
|
2467
2510
|
await _wait_for_bridge_connected(sess, timeout=60.0)
|
|
2511
|
+
manager_info = await _verify_manager_protocol(sess)
|
|
2468
2512
|
r_base = _platform.normalize_infobase_url(base_url or sess.base_url)
|
|
2469
2513
|
r_value = connection_value or r_base
|
|
2470
2514
|
mode = _normalize_connection_mode(connection_mode, r_value, r_base)
|
|
@@ -2594,7 +2638,8 @@ async def _connect_test_client_impl(
|
|
|
2594
2638
|
"port": r_port, "host": host, "client_pid": sess.test_client_process.pid,
|
|
2595
2639
|
"client_log": str(client_out_log), "client_stdout_log": str(sess.client_log_dir / client_stdout_log),
|
|
2596
2640
|
"connection_mode": mode, "connection_value": r_value,
|
|
2597
|
-
"target_connection_mode": target_connection_mode
|
|
2641
|
+
"target_connection_mode": target_connection_mode,
|
|
2642
|
+
"expected_manager_protocol_version": MANAGER_PROTOCOL_VERSION}
|
|
2598
2643
|
_touch_session(sess)
|
|
2599
2644
|
monitor_task: asyncio.Task[int | None] | None = None
|
|
2600
2645
|
try:
|
|
@@ -3236,13 +3281,19 @@ async def table_cell_text(
|
|
|
3236
3281
|
|
|
3237
3282
|
@mcp.tool()
|
|
3238
3283
|
async def tabular_documents(
|
|
3239
|
-
include_text: Annotated[bool, "Include tabular document
|
|
3284
|
+
include_text: Annotated[bool, "Include tabular document cells"] = False,
|
|
3240
3285
|
max_depth: Annotated[int, "Maximum search depth"] = 8,
|
|
3286
|
+
row_start: Annotated[int, "First row to read when include_text=true (1-based)"] = 1,
|
|
3287
|
+
row_end: Annotated[int, "Last row to read when include_text=true; 0 = row_start + max_rows - 1"] = 0,
|
|
3288
|
+
column_start: Annotated[int, "First column to read when include_text=true (1-based)"] = 1,
|
|
3289
|
+
column_end: Annotated[int, "Last column to read when include_text=true; 0 = column_start + max_columns - 1"] = 0,
|
|
3290
|
+
max_rows: Annotated[int, "Maximum rows to read when row_end=0; 0 = all rows"] = 50,
|
|
3291
|
+
max_columns: Annotated[int, "Maximum columns to read when column_end=0; 0 = all columns"] = 20,
|
|
3241
3292
|
*,
|
|
3242
3293
|
session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
|
|
3243
3294
|
) -> Any:
|
|
3244
|
-
"""List tabular document fields
|
|
3245
|
-
return await _bridge_call_recorded(session_id, "tabular_documents", {"include_text": include_text, "max_depth": max_depth})
|
|
3295
|
+
"""List tabular document fields with dimensions; optionally include a cells range."""
|
|
3296
|
+
return await _bridge_call_recorded(session_id, "tabular_documents", {"include_text": include_text, "max_depth": max_depth, "row_start": row_start, "row_end": row_end, "column_start": column_start, "column_end": column_end, "max_rows": max_rows, "max_columns": max_columns})
|
|
3246
3297
|
|
|
3247
3298
|
|
|
3248
3299
|
@mcp.tool()
|
|
@@ -3250,11 +3301,17 @@ async def tabular_document_text(
|
|
|
3250
3301
|
name: Annotated[str, "Tabular document field technical name"] = "",
|
|
3251
3302
|
title: Annotated[str, "Tabular document field visible title"] = "",
|
|
3252
3303
|
max_depth: Annotated[int, "Maximum search depth"] = 8,
|
|
3304
|
+
row_start: Annotated[int, "First row to read (1-based)"] = 1,
|
|
3305
|
+
row_end: Annotated[int, "Last row to read; 0 = row_start + max_rows - 1"] = 0,
|
|
3306
|
+
column_start: Annotated[int, "First column to read (1-based)"] = 1,
|
|
3307
|
+
column_end: Annotated[int, "Last column to read; 0 = column_start + max_columns - 1"] = 0,
|
|
3308
|
+
max_rows: Annotated[int, "Maximum rows to read when row_end=0; 0 = all rows"] = 50,
|
|
3309
|
+
max_columns: Annotated[int, "Maximum columns to read when column_end=0; 0 = all columns"] = 20,
|
|
3253
3310
|
*,
|
|
3254
3311
|
session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
|
|
3255
3312
|
) -> Any:
|
|
3256
|
-
"""Return
|
|
3257
|
-
return await _bridge_call_recorded(session_id, "tabular_document_text", {"name": name, "title": title, "max_depth": max_depth})
|
|
3313
|
+
"""Return dimensions and a cells range of a tabular document field in the active window."""
|
|
3314
|
+
return await _bridge_call_recorded(session_id, "tabular_document_text", {"name": name, "title": title, "max_depth": max_depth, "row_start": row_start, "row_end": row_end, "column_start": column_start, "column_end": column_end, "max_rows": max_rows, "max_columns": max_columns})
|
|
3258
3315
|
|
|
3259
3316
|
|
|
3260
3317
|
@mcp.tool()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{answer42-0.2.24 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|