answer42 0.2.25__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.25 → answer42-0.2.26}/PKG-INFO +1 -1
- {answer42-0.2.25 → answer42-0.2.26}/pyproject.toml +1 -1
- {answer42-0.2.25 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +57 -19
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/credentials.py +53 -11
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/protocol.py +1 -1
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/server.py +56 -19
- {answer42-0.2.25 → answer42-0.2.26}/.gitignore +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/LICENSE +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/README.md +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/credentials.example.json +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/docs/agent-installation.md +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/docs/architecture.md +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/docs/installation.md +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/scripts/build_cf.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/scripts/build_pages.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/scripts/e2e_stable.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/scripts/rag_cli.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/cf/Configuration.xml +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.2.25 → 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.25 → 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.25 → answer42-0.2.26}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.2.25 → 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.25 → 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.25 → 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.25 → answer42-0.2.26}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.2.25 → 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.25 → 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.25 → 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.25 → 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.25 → 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.25 → 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.25 → answer42-0.2.26}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/platform.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/rag/parsers.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/rag/service.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/runtime.py +0 -0
- {answer42-0.2.25 → answer42-0.2.26}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.2.25 → 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"
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
|
|
241
241
|
&НаКлиенте
|
|
242
242
|
Функция ВерсияПротоколаМенеджераMCP()
|
|
243
|
-
Возврат "2026.07.02.
|
|
243
|
+
Возврат "2026.07.02.2";
|
|
244
244
|
КонецФункции
|
|
245
245
|
|
|
246
246
|
&НаКлиенте
|
|
@@ -2950,12 +2950,12 @@
|
|
|
2950
2950
|
Глубина = СвойствоСтруктуры(Параметры, "max_depth", 8);
|
|
2951
2951
|
ВключатьТекст = СвойствоСтруктуры(Параметры, "include_text", Ложь);
|
|
2952
2952
|
Результат = Новый Массив;
|
|
2953
|
-
НайтиТабличныеДокументыРекурсивно(АктивноеОкно, Результат, ВключатьТекст, 0, Глубина);
|
|
2953
|
+
НайтиТабличныеДокументыРекурсивно(АктивноеОкно, Результат, ВключатьТекст, Параметры, 0, Глубина);
|
|
2954
2954
|
Возврат Новый Структура("count,items", Результат.Количество(), Результат);
|
|
2955
2955
|
КонецФункции
|
|
2956
2956
|
|
|
2957
2957
|
&НаКлиенте
|
|
2958
|
-
Процедура НайтиТабличныеДокументыРекурсивно(Объект, Результат, ВключатьТекст, ТекущаяГлубина, МаксимальнаяГлубина)
|
|
2958
|
+
Процедура НайтиТабличныеДокументыРекурсивно(Объект, Результат, ВключатьТекст, ПараметрыТекста, ТекущаяГлубина, МаксимальнаяГлубина)
|
|
2959
2959
|
Если ТекущаяГлубина > МаксимальнаяГлубина Тогда
|
|
2960
2960
|
Возврат;
|
|
2961
2961
|
КонецЕсли;
|
|
@@ -2968,20 +2968,31 @@
|
|
|
2968
2968
|
Элемент.Вставить("title", БезопасныйЗаголовок(Подчиненный));
|
|
2969
2969
|
Элемент.Вставить("type", Строка(ТипЗнч(Подчиненный)));
|
|
2970
2970
|
Элемент.Вставить("data", БезопасноеПредставлениеДанныхПоля(Подчиненный));
|
|
2971
|
+
РазмерыДокумента = РазмерыТабличногоДокументаИзПоля(Подчиненный);
|
|
2972
|
+
Если РазмерыДокумента.Свойство("error") Тогда
|
|
2973
|
+
Элемент.Вставить("dimensions_error", РазмерыДокумента.error);
|
|
2974
|
+
Иначе
|
|
2975
|
+
Элемент.Вставить("rows", РазмерыДокумента.rows);
|
|
2976
|
+
Элемент.Вставить("columns", РазмерыДокумента.columns);
|
|
2977
|
+
КонецЕсли;
|
|
2971
2978
|
Если ВключатьТекст Тогда
|
|
2972
|
-
ТекстДокумента = ТекстТабличногоДокументаИзПоля(
|
|
2979
|
+
ТекстДокумента = ТекстТабличногоДокументаИзПоля(Подчиненный, ПараметрыТекста);
|
|
2973
2980
|
Если ТекстДокумента.Свойство("error") Тогда
|
|
2974
2981
|
Элемент.Вставить("text_error", ТекстДокумента.error);
|
|
2975
2982
|
Иначе
|
|
2976
2983
|
Элемент.Вставить("rows", ТекстДокумента.rows);
|
|
2977
2984
|
Элемент.Вставить("columns", ТекстДокумента.columns);
|
|
2978
2985
|
Элемент.Вставить("address", ТекстДокумента.address);
|
|
2979
|
-
Элемент.Вставить("
|
|
2986
|
+
Элемент.Вставить("row_start", ТекстДокумента.row_start);
|
|
2987
|
+
Элемент.Вставить("row_end", ТекстДокумента.row_end);
|
|
2988
|
+
Элемент.Вставить("column_start", ТекстДокумента.column_start);
|
|
2989
|
+
Элемент.Вставить("column_end", ТекстДокумента.column_end);
|
|
2990
|
+
Элемент.Вставить("cells", ТекстДокумента.cells);
|
|
2980
2991
|
КонецЕсли;
|
|
2981
2992
|
КонецЕсли;
|
|
2982
2993
|
Результат.Добавить(Элемент);
|
|
2983
2994
|
КонецЕсли;
|
|
2984
|
-
НайтиТабличныеДокументыРекурсивно(Подчиненный, Результат, ВключатьТекст, ТекущаяГлубина + 1, МаксимальнаяГлубина);
|
|
2995
|
+
НайтиТабличныеДокументыРекурсивно(Подчиненный, Результат, ВключатьТекст, ПараметрыТекста, ТекущаяГлубина + 1, МаксимальнаяГлубина);
|
|
2985
2996
|
КонецЦикла;
|
|
2986
2997
|
Исключение
|
|
2987
2998
|
Инфо = ИнформацияОбОшибке();
|
|
@@ -3014,29 +3025,56 @@
|
|
|
3014
3025
|
Если Поле = Неопределено Тогда
|
|
3015
3026
|
Возврат Новый Структура("found", Ложь);
|
|
3016
3027
|
КонецЕсли;
|
|
3017
|
-
ДанныеТекста = ТекстТабличногоДокументаИзПоля(
|
|
3028
|
+
ДанныеТекста = ТекстТабличногоДокументаИзПоля(Поле, Параметры);
|
|
3018
3029
|
Если ДанныеТекста.Свойство("error") Тогда
|
|
3019
3030
|
Возврат Новый Структура("found,error", Истина, ДанныеТекста.error);
|
|
3020
3031
|
КонецЕсли;
|
|
3021
|
-
Возврат Новый Структура("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);
|
|
3033
|
+
КонецФункции
|
|
3034
|
+
|
|
3035
|
+
&НаКлиенте
|
|
3036
|
+
Функция РазмерыТабличногоДокументаИзПоля(Поле)
|
|
3037
|
+
Попытка
|
|
3038
|
+
Возврат Новый Структура("rows,columns", Поле.ПолучитьРазмерОбластиДанныхДокументаПоВертикали(), Поле.ПолучитьРазмерОбластиДанныхДокументаПоГоризонтали());
|
|
3039
|
+
Исключение
|
|
3040
|
+
Возврат Новый Структура("error", ОписаниеОшибки());
|
|
3041
|
+
КонецПопытки;
|
|
3022
3042
|
КонецФункции
|
|
3023
3043
|
|
|
3024
3044
|
&НаКлиенте
|
|
3025
|
-
Функция ТекстТабличногоДокументаИзПоля(
|
|
3045
|
+
Функция ТекстТабличногоДокументаИзПоля(Поле, Параметры)
|
|
3026
3046
|
Попытка
|
|
3027
|
-
|
|
3028
|
-
|
|
3047
|
+
Размеры = РазмерыТабличногоДокументаИзПоля(Поле);
|
|
3048
|
+
Если Размеры.Свойство("error") Тогда
|
|
3049
|
+
Возврат Новый Структура("error", Размеры.error);
|
|
3050
|
+
КонецЕсли;
|
|
3051
|
+
КоличествоСтрок = Размеры.rows;
|
|
3052
|
+
КоличествоКолонок = Размеры.columns;
|
|
3029
3053
|
Если КоличествоСтрок = 0 Или КоличествоКолонок = 0 Тогда
|
|
3030
|
-
Возврат Новый Структура("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", КоличествоСтрок, КоличествоКолонок, "", НачальнаяСтрока, КонечнаяСтрока, НачальнаяКолонка, КонечнаяКолонка, Новый Массив);
|
|
3031
3072
|
КонецЕсли;
|
|
3032
|
-
|
|
3033
|
-
ТекстВсейОбласти = Поле.ПолучитьТекстОбласти(АдресВсейОбласти);
|
|
3034
|
-
МаксСтрок = Мин(КоличествоСтрок, 50);
|
|
3035
|
-
МаксКолонок = Мин(КоличествоКолонок, 20);
|
|
3073
|
+
АдресОбласти = АдресОбластиТабличногоДокументаMCP(НачальнаяСтрока, НачальнаяКолонка, КонечнаяСтрока, КонечнаяКолонка);
|
|
3036
3074
|
Ячейки = Новый Массив;
|
|
3037
|
-
Для НомерСтроки =
|
|
3075
|
+
Для НомерСтроки = НачальнаяСтрока По КонечнаяСтрока Цикл
|
|
3038
3076
|
СтрокаТекстов = Новый Массив;
|
|
3039
|
-
Для НомерКолонки =
|
|
3077
|
+
Для НомерКолонки = НачальнаяКолонка По КонечнаяКолонка Цикл
|
|
3040
3078
|
Попытка
|
|
3041
3079
|
АдресЯчейки = АдресОбластиТабличногоДокументаMCP(НомерСтроки, НомерКолонки, НомерСтроки, НомерКолонки);
|
|
3042
3080
|
СтрокаТекстов.Добавить(Поле.ПолучитьТекстОбласти(АдресЯчейки));
|
|
@@ -3046,7 +3084,7 @@
|
|
|
3046
3084
|
КонецЦикла;
|
|
3047
3085
|
Ячейки.Добавить(СтрокаТекстов);
|
|
3048
3086
|
КонецЦикла;
|
|
3049
|
-
Возврат Новый Структура("rows,columns,address,
|
|
3087
|
+
Возврат Новый Структура("rows,columns,address,row_start,row_end,column_start,column_end,cells", КоличествоСтрок, КоличествоКолонок, АдресОбласти, НачальнаяСтрока, КонечнаяСтрока, НачальнаяКолонка, КонечнаяКолонка, Ячейки);
|
|
3050
3088
|
Исключение
|
|
3051
3089
|
Возврат Новый Структура("error", ОписаниеОшибки());
|
|
3052
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
|
|
|
@@ -1698,6 +1698,13 @@ async def start_session(
|
|
|
1698
1698
|
rag_enabled: Annotated[bool, "Enable built-in RAG for this session"] = True,
|
|
1699
1699
|
) -> dict[str, Any]:
|
|
1700
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", ""))
|
|
1701
1708
|
base_url = _platform.normalize_infobase_url(base_url)
|
|
1702
1709
|
connection_mode = "auto"
|
|
1703
1710
|
connection_value = ""
|
|
@@ -2117,18 +2124,24 @@ def _lookup_credentials(base_url: str) -> dict[str, str] | None:
|
|
|
2117
2124
|
return _credentials_store().lookup(base_url)
|
|
2118
2125
|
|
|
2119
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
|
+
|
|
2120
2133
|
@mcp.tool()
|
|
2121
2134
|
def credentials_check(base_url: str) -> dict[str, object]:
|
|
2122
|
-
"""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."""
|
|
2123
2136
|
if not _CREDENTIAL_STORE_ENABLED:
|
|
2124
2137
|
return {"base_url": base_url, "found": False, "disabled": True}
|
|
2125
|
-
credentials = _credentials_store().
|
|
2138
|
+
credentials = _credentials_store().resolve(base_url)
|
|
2126
2139
|
if credentials is None:
|
|
2127
2140
|
return {"base_url": base_url, "found": False}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
"
|
|
2131
|
-
|
|
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
|
|
2132
2145
|
|
|
2133
2146
|
|
|
2134
2147
|
@mcp.tool()
|
|
@@ -2136,6 +2149,8 @@ def credentials_save(
|
|
|
2136
2149
|
url: Annotated[str, "Base URL pattern (exact or wildcard like https://*.example.com/*)"],
|
|
2137
2150
|
username: Annotated[str, "1C username"],
|
|
2138
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"] = "",
|
|
2139
2154
|
) -> dict[str, object]:
|
|
2140
2155
|
"""Save or update credentials for a URL pattern in the local credentials store.
|
|
2141
2156
|
|
|
@@ -2145,12 +2160,14 @@ def credentials_save(
|
|
|
2145
2160
|
"""
|
|
2146
2161
|
if not _CREDENTIAL_STORE_ENABLED:
|
|
2147
2162
|
raise _credential_store_disabled_error()
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
"
|
|
2153
|
-
|
|
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
|
|
2154
2171
|
|
|
2155
2172
|
|
|
2156
2173
|
@mcp.tool()
|
|
@@ -2165,13 +2182,21 @@ def credentials_remove(
|
|
|
2165
2182
|
|
|
2166
2183
|
|
|
2167
2184
|
@mcp.tool()
|
|
2168
|
-
def credentials_list() -> list[dict[str,
|
|
2169
|
-
"""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)."""
|
|
2170
2187
|
if not _CREDENTIAL_STORE_ENABLED:
|
|
2171
2188
|
return []
|
|
2172
2189
|
return _credentials_store().list_entries()
|
|
2173
2190
|
|
|
2174
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
|
+
|
|
2175
2200
|
_PLACEHOLDER_PASSWORD_RE = re.compile(r"^\*{3,}$")
|
|
2176
2201
|
_AUTH_FAILURE_MARKERS = (
|
|
2177
2202
|
"пользователь иб не идентифицирован",
|
|
@@ -3256,13 +3281,19 @@ async def table_cell_text(
|
|
|
3256
3281
|
|
|
3257
3282
|
@mcp.tool()
|
|
3258
3283
|
async def tabular_documents(
|
|
3259
|
-
include_text: Annotated[bool, "Include tabular document
|
|
3284
|
+
include_text: Annotated[bool, "Include tabular document cells"] = False,
|
|
3260
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,
|
|
3261
3292
|
*,
|
|
3262
3293
|
session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
|
|
3263
3294
|
) -> Any:
|
|
3264
|
-
"""List tabular document fields
|
|
3265
|
-
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})
|
|
3266
3297
|
|
|
3267
3298
|
|
|
3268
3299
|
@mcp.tool()
|
|
@@ -3270,11 +3301,17 @@ async def tabular_document_text(
|
|
|
3270
3301
|
name: Annotated[str, "Tabular document field technical name"] = "",
|
|
3271
3302
|
title: Annotated[str, "Tabular document field visible title"] = "",
|
|
3272
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,
|
|
3273
3310
|
*,
|
|
3274
3311
|
session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
|
|
3275
3312
|
) -> Any:
|
|
3276
|
-
"""Return
|
|
3277
|
-
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})
|
|
3278
3315
|
|
|
3279
3316
|
|
|
3280
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.25 → 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
|
|
File without changes
|