answer42 0.2.26__tar.gz → 0.2.27__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.26 → answer42-0.2.27}/PKG-INFO +1 -1
  2. {answer42-0.2.26 → answer42-0.2.27}/pyproject.toml +1 -1
  3. {answer42-0.2.26 → answer42-0.2.27}/scripts/openclaw_mcp_autoreload.py +73 -2
  4. {answer42-0.2.26 → answer42-0.2.27}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +88 -35
  5. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  6. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/protocol.py +1 -1
  7. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/server.py +7 -2
  8. {answer42-0.2.26 → answer42-0.2.27}/.gitignore +0 -0
  9. {answer42-0.2.26 → answer42-0.2.27}/LICENSE +0 -0
  10. {answer42-0.2.26 → answer42-0.2.27}/README.md +0 -0
  11. {answer42-0.2.26 → answer42-0.2.27}/credentials.example.json +0 -0
  12. {answer42-0.2.26 → answer42-0.2.27}/docs/agent-installation.md +0 -0
  13. {answer42-0.2.26 → answer42-0.2.27}/docs/architecture.md +0 -0
  14. {answer42-0.2.26 → answer42-0.2.27}/docs/assets/answer42-logo.png +0 -0
  15. {answer42-0.2.26 → answer42-0.2.27}/docs/assets/platform42-logo.svg +0 -0
  16. {answer42-0.2.26 → answer42-0.2.27}/docs/installation.md +0 -0
  17. {answer42-0.2.26 → answer42-0.2.27}/scripts/build_cf.py +0 -0
  18. {answer42-0.2.26 → answer42-0.2.27}/scripts/build_pages.py +0 -0
  19. {answer42-0.2.26 → answer42-0.2.27}/scripts/e2e_stable.py +0 -0
  20. {answer42-0.2.26 → answer42-0.2.27}/scripts/rag_cli.py +0 -0
  21. {answer42-0.2.26 → answer42-0.2.27}/src/cf/ConfigDumpInfo.xml +0 -0
  22. {answer42-0.2.26 → answer42-0.2.27}/src/cf/Configuration.xml +0 -0
  23. {answer42-0.2.26 → answer42-0.2.27}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  24. {answer42-0.2.26 → answer42-0.2.27}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  25. {answer42-0.2.26 → answer42-0.2.27}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  26. {answer42-0.2.26 → answer42-0.2.27}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  27. {answer42-0.2.26 → answer42-0.2.27}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  28. {answer42-0.2.26 → answer42-0.2.27}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  29. {answer42-0.2.26 → answer42-0.2.27}/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
  30. {answer42-0.2.26 → answer42-0.2.27}/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
  31. {answer42-0.2.26 → answer42-0.2.27}/src/client_cf/ConfigDumpInfo.xml +0 -0
  32. {answer42-0.2.26 → answer42-0.2.27}/src/client_cf/Configuration.xml +0 -0
  33. {answer42-0.2.26 → answer42-0.2.27}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  34. {answer42-0.2.26 → answer42-0.2.27}/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
  35. {answer42-0.2.26 → answer42-0.2.27}/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
  36. {answer42-0.2.26 → answer42-0.2.27}/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
  37. {answer42-0.2.26 → answer42-0.2.27}/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
  38. {answer42-0.2.26 → answer42-0.2.27}/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
  39. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/__init__.py +0 -0
  40. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  41. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/assets/__init__.py +0 -0
  42. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  43. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  44. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/bridge.py +0 -0
  45. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/credentials.py +0 -0
  46. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/os_support.py +0 -0
  47. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/platform.py +0 -0
  48. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/rag/__init__.py +0 -0
  49. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/rag/detect.py +0 -0
  50. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/rag/model.py +0 -0
  51. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/rag/parsers.py +0 -0
  52. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/rag/service.py +0 -0
  53. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/rag/store.py +0 -0
  54. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/recorder.py +0 -0
  55. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/release_helper.py +0 -0
  56. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/runtime.py +0 -0
  57. {answer42-0.2.26 → answer42-0.2.27}/src/mcp_1c/skill_installer.py +0 -0
  58. {answer42-0.2.26 → answer42-0.2.27}/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.26
3
+ Version: 0.2.27
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.26"
3
+ version = "0.2.27"
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"
@@ -11,6 +11,7 @@ import argparse
11
11
  import atexit
12
12
  import hashlib
13
13
  import os
14
+ import signal
14
15
  import subprocess
15
16
  import sys
16
17
  import time
@@ -35,12 +36,73 @@ def snapshot(root: Path, patterns: tuple[str, ...]) -> str:
35
36
  return digest.hexdigest()
36
37
 
37
38
 
39
+ def _active_answer42_pids() -> list[int]:
40
+ """Return active Answer42 MCP stdio process PIDs, excluding this watcher."""
41
+ current_pid = os.getpid()
42
+ try:
43
+ result = subprocess.run(
44
+ ["ps", "-eo", "pid=,args="],
45
+ text=True,
46
+ stdout=subprocess.PIPE,
47
+ stderr=subprocess.DEVNULL,
48
+ check=False,
49
+ )
50
+ except Exception:
51
+ return []
52
+ pids: list[int] = []
53
+ for line in result.stdout.splitlines():
54
+ stripped = line.strip()
55
+ if not stripped:
56
+ continue
57
+ pid_text, _, cmd = stripped.partition(" ")
58
+ try:
59
+ pid = int(pid_text)
60
+ except ValueError:
61
+ continue
62
+ if pid == current_pid:
63
+ continue
64
+ if "openclaw_mcp_autoreload.py" in cmd:
65
+ continue
66
+ if "--dev openclaw" not in cmd:
67
+ continue
68
+ if "/answer42" in cmd or "mcp_1c.server" in cmd:
69
+ pids.append(pid)
70
+ return pids
71
+
72
+
73
+ def kill_active_answer42(timeout: float = 5.0) -> list[int]:
74
+ """Terminate active Answer42 MCP processes so next tool call starts fresh code/env."""
75
+ pids = _active_answer42_pids()
76
+ if not pids:
77
+ return []
78
+ for pid in pids:
79
+ try:
80
+ os.kill(pid, signal.SIGTERM)
81
+ except ProcessLookupError:
82
+ pass
83
+ deadline = time.monotonic() + timeout
84
+ remaining = set(pids)
85
+ while remaining and time.monotonic() < deadline:
86
+ time.sleep(0.2)
87
+ for pid in list(remaining):
88
+ try:
89
+ os.kill(pid, 0)
90
+ except ProcessLookupError:
91
+ remaining.discard(pid)
92
+ for pid in sorted(remaining):
93
+ try:
94
+ os.kill(pid, signal.SIGKILL)
95
+ except ProcessLookupError:
96
+ pass
97
+ return pids
98
+
99
+
38
100
  def run_reload(server: str | None) -> int:
39
101
  cmd = ["openclaw", "mcp", "reload"]
40
102
  # Current OpenClaw reload command is global; keep --server support ready if added later.
41
103
  if server:
42
104
  probe = subprocess.run(["openclaw", "mcp", "reload", "--help"], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
43
- if f"--server" in probe.stdout:
105
+ if "--server" in probe.stdout:
44
106
  cmd.extend(["--server", server])
45
107
  result = subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
46
108
  sys.stdout.write(result.stdout)
@@ -59,7 +121,12 @@ def main() -> int:
59
121
  default=30.0,
60
122
  help="seconds to wait after the last detected change before reloading OpenClaw MCP runtimes",
61
123
  )
62
- parser.add_argument("--server", default="answer42")
124
+ parser.add_argument("--server", default="Answer42")
125
+ parser.add_argument(
126
+ "--no-kill-active",
127
+ action="store_true",
128
+ help="do not terminate active Answer42 stdio processes after OpenClaw cache reload",
129
+ )
63
130
  parser.add_argument("--once", action="store_true", help="wait for one change, reload once, exit")
64
131
  parser.add_argument("--pid-file", default="", help="write watcher pid to this file and remove it on exit")
65
132
  args = parser.parse_args()
@@ -108,6 +175,10 @@ def main() -> int:
108
175
  print("[mcp-autoreload] change detected; running openclaw mcp reload", flush=True)
109
176
  code = run_reload(args.server)
110
177
  print(f"[mcp-autoreload] reload exit code {code}", flush=True)
178
+ if code == 0 and not args.no_kill_active:
179
+ killed = kill_active_answer42()
180
+ if killed:
181
+ print(f"[mcp-autoreload] terminated active Answer42 MCP processes: {killed}", flush=True)
111
182
  if args.once:
112
183
  return code
113
184
 
@@ -240,7 +240,7 @@
240
240
 
241
241
  &НаКлиенте
242
242
  Функция ВерсияПротоколаМенеджераMCP()
243
- Возврат "2026.07.02.2";
243
+ Возврат "2026.07.02.3";
244
244
  КонецФункции
245
245
 
246
246
  &НаКлиенте
@@ -1646,11 +1646,13 @@
1646
1646
  АктивноеОкно = КомандныйИнтерфейсЦелевоеОкно(Параметры);
1647
1647
  Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1648
1648
  Если Интерфейс = Неопределено Тогда
1649
- Возврат Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1649
+ РезультатКоманды = Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1650
1650
  Иначе
1651
1651
  Дерево = КомандныйИнтерфейсОбъектВСтруктуру(Интерфейс, 0, Глубина);
1652
- Возврат Новый Структура("available,interface,window", Истина, Дерево, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1652
+ РезультатКоманды = Новый Структура("available,interface,window", Истина, Дерево, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1653
1653
  КонецЕсли;
1654
+ ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(АктивноеОкно);
1655
+ Возврат РезультатКоманды;
1654
1656
  Исключение
1655
1657
  Возврат Новый Структура("available,reason,error", Ложь, "not_supported", ОписаниеОшибки());
1656
1658
  КонецПопытки;
@@ -1666,11 +1668,13 @@
1666
1668
  АктивноеОкно = КомандныйИнтерфейсЦелевоеОкно(Параметры);
1667
1669
  Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1668
1670
  Если Интерфейс = Неопределено Тогда
1669
- Возврат Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1671
+ РезультатКоманды = Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1672
+ Иначе
1673
+ Разделы = РазделыКомандногоИнтерфейса(Интерфейс);
1674
+ РезультатКоманды = Новый Структура("available,sections,window", Истина, Разделы, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1670
1675
  КонецЕсли;
1671
-
1672
- Разделы = РазделыКомандногоИнтерфейса(Интерфейс);
1673
- Возврат Новый Структура("available,sections,window", Истина, Разделы, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1676
+ ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(АктивноеОкно);
1677
+ Возврат РезультатКоманды;
1674
1678
  Исключение
1675
1679
  Возврат Новый Структура("available,reason,error", Ложь, "not_supported", ОписаниеОшибки());
1676
1680
  КонецПопытки;
@@ -1682,30 +1686,41 @@
1682
1686
  ВызватьИсключение "Тестируемое приложение не подключено.";
1683
1687
  КонецЕсли;
1684
1688
 
1689
+ АктивноеОкно = Неопределено;
1690
+ КнопкаРаздела = Неопределено;
1685
1691
  Попытка
1686
1692
  Глубина = СвойствоСтруктуры(Параметры, "max_depth", 4);
1687
1693
  ЗаголовокРаздела = СвойствоСтруктуры(Параметры, "section", "");
1688
1694
  АктивноеОкно = КомандныйИнтерфейсЦелевоеОкно(Параметры);
1689
1695
  Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1690
1696
  Если Интерфейс = Неопределено Тогда
1691
- Возврат Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1692
- КонецЕсли;
1693
-
1694
- Корень = Интерфейс;
1695
- Если Не ПустаяСтрока(ЗаголовокРаздела) Тогда
1696
- КнопкаРаздела = КомандныйИнтерфейсНайтиРаздел(Интерфейс, ЗаголовокРаздела);
1697
- Если КнопкаРаздела = Неопределено Тогда
1698
- Возврат Новый Структура("available,found,reason,section,window", Истина, Ложь, "section_not_found", ЗаголовокРаздела, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1699
- КонецЕсли;
1700
- КнопкаРаздела.Нажать();
1701
- Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1697
+ РезультатКоманды = Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1698
+ Иначе
1702
1699
  Корень = Интерфейс;
1700
+ Если Не ПустаяСтрока(ЗаголовокРаздела) Тогда
1701
+ КнопкаРаздела = КомандныйИнтерфейсНайтиРаздел(Интерфейс, ЗаголовокРаздела);
1702
+ Если КнопкаРаздела = Неопределено Тогда
1703
+ РезультатКоманды = Новый Структура("available,found,reason,section,window", Истина, Ложь, "section_not_found", ЗаголовокРаздела, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1704
+ Иначе
1705
+ КнопкаРаздела.Нажать();
1706
+ Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1707
+ Корень = Интерфейс;
1708
+ Дерево = КомандныйИнтерфейсОбъектВСтруктуру(Корень, 0, Глубина);
1709
+ ЛокКоманды = КомандыКомандногоИнтерфейса(Корень, Истина);
1710
+ РезультатКоманды = Новый Структура("available,found,section,tree,commands,window", Истина, Истина, ЗаголовокРаздела, Дерево, ЛокКоманды, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1711
+ КонецЕсли;
1712
+ Иначе
1713
+ Дерево = КомандныйИнтерфейсОбъектВСтруктуру(Корень, 0, Глубина);
1714
+ ЛокКоманды = КомандыКомандногоИнтерфейса(Корень, Истина);
1715
+ РезультатКоманды = Новый Структура("available,found,section,tree,commands,window", Истина, Истина, ЗаголовокРаздела, Дерево, ЛокКоманды, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1716
+ КонецЕсли;
1703
1717
  КонецЕсли;
1704
-
1705
- Дерево = КомандныйИнтерфейсОбъектВСтруктуру(Корень, 0, Глубина);
1706
- ЛокКоманды = КомандыКомандногоИнтерфейса(Корень, Истина);
1707
- Возврат Новый Структура("available,found,section,tree,commands,window", Истина, Истина, ЗаголовокРаздела, Дерево, ЛокКоманды, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1718
+ ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(АктивноеОкно, КнопкаРаздела);
1719
+ Возврат РезультатКоманды;
1708
1720
  Исключение
1721
+ Если АктивноеОкно <> Неопределено Тогда
1722
+ ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(АктивноеОкно, КнопкаРаздела);
1723
+ КонецЕсли;
1709
1724
  Возврат Новый Структура("available,reason,error", Ложь, "not_supported", ОписаниеОшибки());
1710
1725
  КонецПопытки;
1711
1726
  КонецФункции
@@ -1716,6 +1731,8 @@
1716
1731
  ВызватьИсключение "Тестируемое приложение не подключено.";
1717
1732
  КонецЕсли;
1718
1733
 
1734
+ АктивноеОкно = Неопределено;
1735
+ КнопкаРаздела = Неопределено;
1719
1736
  Попытка
1720
1737
  СтрокаПоиска = СокрЛП(СвойствоСтруктуры(Параметры, "query", ""));
1721
1738
  Если ПустаяСтрока(СтрокаПоиска) Тогда
@@ -1727,26 +1744,62 @@
1727
1744
  АктивноеОкно = КомандныйИнтерфейсЦелевоеОкно(Параметры);
1728
1745
  Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1729
1746
  Если Интерфейс = Неопределено Тогда
1730
- Возврат Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1731
- КонецЕсли;
1732
-
1733
- Если Не ПустаяСтрока(ЗаголовокРаздела) Тогда
1734
- КнопкаРаздела = КомандныйИнтерфейсНайтиРаздел(Интерфейс, ЗаголовокРаздела);
1735
- Если КнопкаРаздела = Неопределено Тогда
1736
- Возврат Новый Структура("available,found,reason,query,section,matches,window", Истина, Ложь, "section_not_found", СтрокаПоиска, ЗаголовокРаздела, Новый Массив, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1747
+ РезультатПоиска = Новый Структура("available,reason,window", Ложь, "not_available", ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1748
+ Иначе
1749
+ Если Не ПустаяСтрока(ЗаголовокРаздела) Тогда
1750
+ КнопкаРаздела = КомандныйИнтерфейсНайтиРаздел(Интерфейс, ЗаголовокРаздела);
1751
+ Если КнопкаРаздела = Неопределено Тогда
1752
+ РезультатПоиска = Новый Структура("available,found,reason,query,section,matches,window", Истина, Ложь, "section_not_found", СтрокаПоиска, ЗаголовокРаздела, Новый Массив, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1753
+ Иначе
1754
+ КнопкаРаздела.Нажать();
1755
+ Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1756
+ ВсеКоманды = КомандыКомандногоИнтерфейса(Интерфейс, Истина);
1757
+ Совпадения = ПоискВКомандахКомандногоИнтерфейса(ВсеКоманды, СтрокаПоиска, Максимум);
1758
+ РезультатПоиска = Новый Структура("available,found,query,section,count,matches,window", Истина, Совпадения.Количество() > 0, СтрокаПоиска, ЗаголовокРаздела, Совпадения.Количество(), Совпадения, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1759
+ КонецЕсли;
1760
+ Иначе
1761
+ ВсеКоманды = КомандыКомандногоИнтерфейса(Интерфейс, Истина);
1762
+ Совпадения = ПоискВКомандахКомандногоИнтерфейса(ВсеКоманды, СтрокаПоиска, Максимум);
1763
+ РезультатПоиска = Новый Структура("available,found,query,section,count,matches,window", Истина, Совпадения.Количество() > 0, СтрокаПоиска, ЗаголовокРаздела, Совпадения.Количество(), Совпадения, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1737
1764
  КонецЕсли;
1738
- КнопкаРаздела.Нажать();
1739
- Интерфейс = АктивноеОкно.ПолучитьКомандныйИнтерфейс();
1740
1765
  КонецЕсли;
1741
-
1742
- ВсеКоманды = КомандыКомандногоИнтерфейса(Интерфейс, Истина);
1743
- Совпадения = ПоискВКомандахКомандногоИнтерфейса(ВсеКоманды, СтрокаПоиска, Максимум);
1744
- Возврат Новый Структура("available,found,query,section,count,matches,window", Истина, Совпадения.Количество() > 0, СтрокаПоиска, ЗаголовокРаздела, Совпадения.Количество(), Совпадения, ОбъектВСтруктуру(АктивноеОкно, 0, 0));
1766
+ ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(АктивноеОкно, КнопкаРаздела);
1767
+ Возврат РезультатПоиска;
1745
1768
  Исключение
1769
+ Если АктивноеОкно <> Неопределено Тогда
1770
+ ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(АктивноеОкно, КнопкаРаздела);
1771
+ КонецЕсли;
1746
1772
  Возврат Новый Структура("available,reason,error", Ложь, "not_supported", ОписаниеОшибки());
1747
1773
  КонецПопытки;
1748
1774
  КонецФункции
1749
1775
 
1776
+ &НаКлиенте
1777
+ Процедура ЗакрытьКомандныйИнтерфейсПослеРаботыMCP(Окно, КнопкаРаздела = Неопределено)
1778
+ // Некоторые методы test-client оставляют раскрытый командный интерфейс поверх
1779
+ // формы. После этого интерактивные действия по форме могут падать с
1780
+ // "Недоступный пользователю элемент управления...". Возвращаем UI в форму:
1781
+ // если нажимали кнопку раздела — нажимаем её повторно (переключатель панели),
1782
+ // затем активизируем окно и первую найденную форму.
1783
+ Если КнопкаРаздела <> Неопределено Тогда
1784
+ Попытка
1785
+ КнопкаРаздела.Нажать();
1786
+ Исключение
1787
+ // Игнорируем: cleanup не должен ломать полезный результат команды.
1788
+ КонецПопытки;
1789
+ КонецЕсли;
1790
+ Попытка
1791
+ Окно.Активизировать();
1792
+ Исключение
1793
+ КонецПопытки;
1794
+ Попытка
1795
+ Форма = НайтиОбъектРекурсивно(Окно, "", "", "тестируемаяформа", 0, 8);
1796
+ Если Форма <> Неопределено Тогда
1797
+ Форма.Активизировать();
1798
+ КонецЕсли;
1799
+ Исключение
1800
+ КонецПопытки;
1801
+ КонецПроцедуры
1802
+
1750
1803
  &НаКлиенте
1751
1804
  Функция КомандныйИнтерфейсЦелевоеОкно(Параметры)
1752
1805
  ОкноЗаголовок = СвойствоСтруктуры(Параметры, "window_title", "");
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import Any
5
5
 
6
- MANAGER_PROTOCOL_VERSION = "2026.07.02.2"
6
+ MANAGER_PROTOCOL_VERSION = "2026.07.02.3"
7
7
 
8
8
 
9
9
  @dataclass(slots=True)
@@ -1699,8 +1699,13 @@ async def start_session(
1699
1699
  ) -> dict[str, Any]:
1700
1700
  """One-shot session factory: detect version, start infrastructure and connect the 1C test client."""
1701
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"])
1702
+ input_looks_like_url = bool(re.match(r"^[A-Za-z][A-Za-z0-9+.-]*://", str(base_url or "")))
1703
+ if credential_ref is not None:
1704
+ # For saved titles/aliases, replace title -> real URL. For URL/wildcard matches,
1705
+ # keep the original requested URL; using the wildcard pattern itself as /WS value
1706
+ # would make the 1C client fail with invalid connection parameters.
1707
+ if credential_ref.get("url") and not input_looks_like_url:
1708
+ base_url = str(credential_ref["url"])
1704
1709
  if not username:
1705
1710
  username = str(credential_ref.get("username", ""))
1706
1711
  if not password:
File without changes
File without changes
File without changes
File without changes
File without changes