answer42 0.3.3__tar.gz → 0.3.5__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.3.3 → answer42-0.3.5}/PKG-INFO +1 -1
- {answer42-0.3.3 → answer42-0.3.5}/pyproject.toml +1 -1
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/parsers.py +52 -8
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/service.py +59 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/server.py +108 -2
- {answer42-0.3.3 → answer42-0.3.5}/.gitignore +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/LICENSE +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/README.md +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/credentials.example.json +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/docs/agent-installation.md +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/docs/architecture.md +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/docs/installation.md +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/scripts/build_cf.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/scripts/build_pages.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/scripts/e2e_stable.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/scripts/rag_cli.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/Configuration.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl" +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/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.3.3 → answer42-0.3.5}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/credentials.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/platform.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/protocol.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/dump.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/runtime.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.3.3 → answer42-0.3.5}/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.3.
|
|
3
|
+
Version: 0.3.5
|
|
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.3.
|
|
3
|
+
version = "0.3.5"
|
|
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"
|
|
Binary file
|
|
Binary file
|
|
@@ -21,6 +21,8 @@ from .model import (
|
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
KIND_DIRS = {
|
|
24
|
+
"Constants": "Константа",
|
|
25
|
+
"CommonForms": "ОбщаяФорма",
|
|
24
26
|
"Catalogs": "Справочник",
|
|
25
27
|
"Documents": "Документ",
|
|
26
28
|
"DataProcessors": "Обработка",
|
|
@@ -33,6 +35,8 @@ KIND_DIRS = {
|
|
|
33
35
|
"CalculationRegisters": "РегистрРасчета",
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
FORM_FILE_NAMES = {"Form.form", "Form.xml"}
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
def _strip_ns(tag: str) -> str:
|
|
38
42
|
return tag.rsplit("}", 1)[-1]
|
|
@@ -199,7 +203,10 @@ def parse_edt_source(source: SourceInfo) -> ParsedSource:
|
|
|
199
203
|
parsed.objects.append(MetadataObject(full, ru_kind, obj_name, synonym, str(mdo), source.format, hierarchical, [o for o in owners if o]))
|
|
200
204
|
if root is not None:
|
|
201
205
|
_extract_metadata_children(parsed, root, full, mdo)
|
|
202
|
-
|
|
206
|
+
if ru_kind == "ОбщаяФорма":
|
|
207
|
+
_extract_common_form(parsed, mdo.parent, full)
|
|
208
|
+
else:
|
|
209
|
+
_extract_forms(parsed, mdo.parent, full)
|
|
203
210
|
_extract_data_composition_schemas(parsed, mdo.parent, full)
|
|
204
211
|
_extract_help_pages(parsed, mdo.parent, full)
|
|
205
212
|
return parsed
|
|
@@ -231,12 +238,26 @@ def parse_designer_xml_source(source: SourceInfo) -> ParsedSource:
|
|
|
231
238
|
if root is not None:
|
|
232
239
|
_extract_metadata_children(parsed, root, full, xml)
|
|
233
240
|
object_dir = xml.parent if xml.parent != base else base / xml.stem
|
|
234
|
-
|
|
241
|
+
if ru_kind == "ОбщаяФорма":
|
|
242
|
+
_extract_common_form(parsed, object_dir, full)
|
|
243
|
+
else:
|
|
244
|
+
_extract_forms(parsed, object_dir, full)
|
|
235
245
|
_extract_data_composition_schemas(parsed, object_dir, full)
|
|
236
246
|
_extract_help_pages(parsed, object_dir, full)
|
|
237
247
|
return parsed
|
|
238
248
|
|
|
239
249
|
|
|
250
|
+
def _extract_common_form(parsed: ParsedSource, object_dir: Path, full: str) -> None:
|
|
251
|
+
form_xml = _find_form_file(object_dir)
|
|
252
|
+
parsed.forms.append(Form(full, "Форма", "CommonForm", str(form_xml) if form_xml.exists() else str(object_dir)))
|
|
253
|
+
if not form_xml.exists():
|
|
254
|
+
return
|
|
255
|
+
root = _xml_root(form_xml)
|
|
256
|
+
if root is not None:
|
|
257
|
+
_extract_form_elements(parsed, root, full, "Форма", form_xml)
|
|
258
|
+
_extract_embedded_data_composition_fields(parsed, root, full, "Форма", form_xml)
|
|
259
|
+
|
|
260
|
+
|
|
240
261
|
def _extract_metadata_children(parsed: ParsedSource, root: ET.Element, full: str, source_path: Path) -> None:
|
|
241
262
|
# Heuristic parser covering both EDT and designer XML structures.
|
|
242
263
|
# Keep object attributes and table-part columns separate: XML dumps often
|
|
@@ -273,9 +294,7 @@ def _extract_forms(parsed: ParsedSource, object_dir: Path, full: str) -> None:
|
|
|
273
294
|
return
|
|
274
295
|
for form_dir in sorted([p for p in forms_dir.iterdir() if p.is_dir()]):
|
|
275
296
|
form_name = form_dir.name
|
|
276
|
-
form_xml = form_dir
|
|
277
|
-
if not form_xml.exists():
|
|
278
|
-
form_xml = form_dir / "Form.xml"
|
|
297
|
+
form_xml = _find_form_file(form_dir)
|
|
279
298
|
parsed.forms.append(Form(full, form_name, None, str(form_xml) if form_xml.exists() else str(form_dir)))
|
|
280
299
|
if form_xml.exists():
|
|
281
300
|
root = _xml_root(form_xml)
|
|
@@ -284,23 +303,34 @@ def _extract_forms(parsed: ParsedSource, object_dir: Path, full: str) -> None:
|
|
|
284
303
|
_extract_embedded_data_composition_fields(parsed, root, full, form_name, form_xml)
|
|
285
304
|
|
|
286
305
|
|
|
306
|
+
def _find_form_file(form_dir: Path) -> Path:
|
|
307
|
+
for candidate_dir in (form_dir / "Ext", form_dir):
|
|
308
|
+
for name in FORM_FILE_NAMES:
|
|
309
|
+
form_file = candidate_dir / name
|
|
310
|
+
if form_file.exists():
|
|
311
|
+
return form_file
|
|
312
|
+
return form_dir / "Ext" / "Form.xml"
|
|
313
|
+
|
|
314
|
+
|
|
287
315
|
def _extract_form_elements(parsed: ParsedSource, root: ET.Element, full: str, form_name: str, source_path: Path) -> None:
|
|
288
316
|
for elem in root.iter():
|
|
289
317
|
tag = _strip_ns(elem.tag)
|
|
290
318
|
low = tag.lower()
|
|
291
|
-
if low not in {"element", "item", "items", "button", "command", "field", "table", "group"}:
|
|
319
|
+
if low not in {"element", "item", "items", "button", "command", "field", "table", "group", "extendedtooltip", "contextmenu"}:
|
|
292
320
|
continue
|
|
293
321
|
name = elem.attrib.get("name") or elem.attrib.get("Name") or _first_text(elem, ["name", "Name"])
|
|
294
322
|
if not name:
|
|
295
323
|
continue
|
|
324
|
+
title = elem.attrib.get("title") or elem.attrib.get("Title") or _first_text(elem, ["title", "Title", "caption", "Caption"])
|
|
325
|
+
data_path = _form_data_path(elem)
|
|
296
326
|
parsed.form_elements.append(
|
|
297
327
|
FormElement(
|
|
298
328
|
full,
|
|
299
329
|
form_name,
|
|
300
330
|
name,
|
|
301
331
|
tag,
|
|
302
|
-
|
|
303
|
-
|
|
332
|
+
title,
|
|
333
|
+
data_path,
|
|
304
334
|
elem.attrib.get("commandName") or _first_text(elem, ["commandName", "CommandName"]),
|
|
305
335
|
None,
|
|
306
336
|
str(source_path),
|
|
@@ -308,6 +338,20 @@ def _extract_form_elements(parsed: ParsedSource, root: ET.Element, full: str, fo
|
|
|
308
338
|
)
|
|
309
339
|
|
|
310
340
|
|
|
341
|
+
def _form_data_path(elem: ET.Element) -> str | None:
|
|
342
|
+
direct = elem.attrib.get("dataPath") or elem.attrib.get("path") or _direct_text(elem, ["dataPath", "path", "DataPath"])
|
|
343
|
+
if direct:
|
|
344
|
+
return direct
|
|
345
|
+
for child in list(elem):
|
|
346
|
+
if _strip_ns(child.tag).lower() == "datapath":
|
|
347
|
+
segments = [_text(segment) for segment in child.iter() if _strip_ns(segment.tag).lower() in {"segments", "segment"}]
|
|
348
|
+
values = [value for value in segments if value]
|
|
349
|
+
if values:
|
|
350
|
+
return ".".join(values)
|
|
351
|
+
return _text(child)
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
|
|
311
355
|
def _extract_embedded_data_composition_fields(parsed: ParsedSource, root: ET.Element, full: str, form_name: str, source_path: Path) -> None:
|
|
312
356
|
dataset_name: str | None = None
|
|
313
357
|
for elem in root.iter():
|
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
import math
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
|
+
import shutil
|
|
8
9
|
import subprocess
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
@@ -56,6 +57,45 @@ class RagService:
|
|
|
56
57
|
with self.store.connect() as con:
|
|
57
58
|
return [dict(r) for r in con.execute("SELECT * FROM sources ORDER BY id")]
|
|
58
59
|
|
|
60
|
+
def cleanup_generated_source_files_under(self, root: str | Path, *, remove_files: bool = True) -> dict[str, Any]:
|
|
61
|
+
"""Delete generated dump files under *root* while preserving indexed RAG rows.
|
|
62
|
+
|
|
63
|
+
The SQLite RAG index is self-contained after ``build_index`` succeeds, so
|
|
64
|
+
dumped Designer XML source files can be removed to save disk space. The
|
|
65
|
+
source registry rows are intentionally kept: they document which source
|
|
66
|
+
produced the indexed metadata and keep existing snapshots/index rows
|
|
67
|
+
intact via foreign keys.
|
|
68
|
+
"""
|
|
69
|
+
root_path = Path(root).expanduser().resolve()
|
|
70
|
+
referenced_sources: list[dict[str, Any]] = []
|
|
71
|
+
skipped: list[dict[str, Any]] = []
|
|
72
|
+
with self.store.connect() as con:
|
|
73
|
+
rows = [dict(r) for r in con.execute("SELECT * FROM sources ORDER BY id")]
|
|
74
|
+
for row in rows:
|
|
75
|
+
source_path = Path(str(row.get("path") or "")).expanduser()
|
|
76
|
+
try:
|
|
77
|
+
resolved_source = source_path.resolve()
|
|
78
|
+
resolved_source.relative_to(root_path)
|
|
79
|
+
except (OSError, ValueError):
|
|
80
|
+
continue
|
|
81
|
+
branch = str(row.get("branch") or "")
|
|
82
|
+
commit_hash = str(row.get("commit_hash") or "")
|
|
83
|
+
if branch or commit_hash:
|
|
84
|
+
skipped.append(row | {"reason": "source looks like a VCS checkout"})
|
|
85
|
+
continue
|
|
86
|
+
referenced_sources.append(row)
|
|
87
|
+
|
|
88
|
+
removed_paths: list[str] = []
|
|
89
|
+
if remove_files and root_path.exists() and referenced_sources:
|
|
90
|
+
shutil.rmtree(root_path)
|
|
91
|
+
removed_paths.append(str(root_path))
|
|
92
|
+
return {
|
|
93
|
+
"root": str(root_path),
|
|
94
|
+
"referenced_sources_count": len(referenced_sources),
|
|
95
|
+
"skipped_count": len(skipped),
|
|
96
|
+
"removed_paths": removed_paths,
|
|
97
|
+
}
|
|
98
|
+
|
|
59
99
|
def add_sources_from_directory(self, root: str, prefix: str = "", recursive: bool = True, max_depth: int = 4, watch: bool = False) -> dict[str, Any]:
|
|
60
100
|
discovered = self.discover_sources(root, recursive=recursive, max_depth=max_depth)
|
|
61
101
|
registered: list[dict[str, Any]] = []
|
|
@@ -266,6 +306,12 @@ class RagService:
|
|
|
266
306
|
continue
|
|
267
307
|
con.execute("INSERT INTO attributes(object_id,name,type_name,synonym,required,source_path,role,raw_json) VALUES(?,?,?,?,?,?,?,?)", (oid, attr.name, attr.type_name, attr.synonym, attr.required, attr.source_path, attr.role, json_dumps(attr.raw)))
|
|
268
308
|
counts["attributes"] += 1
|
|
309
|
+
text_parts = [attr.object_full_name, attr.role, attr.name, search_text_variants(attr.name, attr.synonym), attr.synonym or "", attr.type_name or ""]
|
|
310
|
+
chunk_text = " ".join(part for part in text_parts if part)
|
|
311
|
+
cid = _insert_chunk(con, snapshot_id, oid, "attribute", chunk_text, attr.source_path, {"source": src["name"], "role": attr.role})
|
|
312
|
+
con.execute("INSERT INTO search_fts(text, object_full_name, kind, source_path, chunk_id) VALUES(?,?,?,?,?)", (chunk_text, attr.object_full_name, "Реквизит", attr.source_path, cid))
|
|
313
|
+
_insert_embedding(con, cid, chunk_text)
|
|
314
|
+
counts["chunks"] += 1
|
|
269
315
|
for tp in parsed.table_parts:
|
|
270
316
|
oid = object_ids.get(tp.object_full_name)
|
|
271
317
|
if not oid:
|
|
@@ -284,12 +330,25 @@ class RagService:
|
|
|
284
330
|
cur = con.execute("INSERT INTO forms(object_id,name,kind,source_path,raw_json) VALUES(?,?,?,?,?)", (oid, form.name, form.kind, form.source_path, json_dumps(form.raw)))
|
|
285
331
|
form_ids[(form.object_full_name, form.name)] = cur.lastrowid
|
|
286
332
|
counts["forms"] += 1
|
|
333
|
+
text_parts = [form.object_full_name, "форма", form.name, search_text_variants(form.name), form.kind or ""]
|
|
334
|
+
chunk_text = " ".join(part for part in text_parts if part)
|
|
335
|
+
cid = _insert_chunk(con, snapshot_id, oid, "form", chunk_text, form.source_path, {"source": src["name"], "form": form.name})
|
|
336
|
+
con.execute("INSERT INTO search_fts(text, object_full_name, kind, source_path, chunk_id) VALUES(?,?,?,?,?)", (chunk_text, form.object_full_name, "Форма", form.source_path, cid))
|
|
337
|
+
_insert_embedding(con, cid, chunk_text)
|
|
338
|
+
counts["chunks"] += 1
|
|
287
339
|
for elem in parsed.form_elements:
|
|
288
340
|
fid = form_ids.get((elem.object_full_name, elem.form_name))
|
|
289
341
|
if not fid:
|
|
290
342
|
continue
|
|
291
343
|
con.execute("INSERT INTO form_elements(form_id,name,element_type,title,data_path,command_name,parent_name,source_path,raw_json) VALUES(?,?,?,?,?,?,?,?,?)", (fid, elem.name, elem.element_type, elem.title, elem.data_path, elem.command_name, elem.parent_name, elem.source_path, json_dumps(elem.raw)))
|
|
292
344
|
counts["form_elements"] += 1
|
|
345
|
+
oid = object_ids.get(elem.object_full_name)
|
|
346
|
+
text_parts = [elem.object_full_name, "элемент формы", elem.form_name, elem.name, search_text_variants(elem.name, elem.title, elem.data_path), elem.title or "", elem.data_path or "", elem.command_name or "", elem.element_type or ""]
|
|
347
|
+
chunk_text = " ".join(part for part in text_parts if part)
|
|
348
|
+
cid = _insert_chunk(con, snapshot_id, oid, "form_element", chunk_text, elem.source_path, {"source": src["name"], "form": elem.form_name, "element": elem.name})
|
|
349
|
+
con.execute("INSERT INTO search_fts(text, object_full_name, kind, source_path, chunk_id) VALUES(?,?,?,?,?)", (chunk_text, elem.object_full_name, "ЭлементФормы", elem.source_path, cid))
|
|
350
|
+
_insert_embedding(con, cid, chunk_text)
|
|
351
|
+
counts["chunks"] += 1
|
|
293
352
|
for dcs_field in parsed.data_composition_fields:
|
|
294
353
|
oid = object_ids.get(dcs_field.object_full_name)
|
|
295
354
|
if not oid:
|
|
@@ -63,6 +63,62 @@ _SHARED_FILE_SERVERS_FILE = os_support.runtime_path("ONEC_MCP_SHARED_FILE_SERVER
|
|
|
63
63
|
_SHARED_FILE_SERVERS_LOCK = os_support.runtime_path("ONEC_MCP_SHARED_FILE_SERVERS_LOCK", "shared-file-servers.lock")
|
|
64
64
|
_METADATA_OBJECTS_CACHE: dict[str, dict[str, Any]] = {}
|
|
65
65
|
|
|
66
|
+
TOOL_PROFILE_GROUPS: dict[str, set[str]] = {
|
|
67
|
+
"sessions": {
|
|
68
|
+
"sessions_list", "session_status", "start_session", "stop_session", "export_eventlog",
|
|
69
|
+
"credentials_check", "credentials_save", "credentials_remove", "credentials_list", "credentials_titles",
|
|
70
|
+
},
|
|
71
|
+
"windows": {"active_window", "windows_list", "activate_window", "goto_start_page", "goto_previous_window", "goto_next_window"},
|
|
72
|
+
"forms": {
|
|
73
|
+
"wait_object", "wait_form", "wait_window", "user_messages", "close_user_messages_panel", "current_error_info",
|
|
74
|
+
"set_file_dialog_result", "clear_file_dialog_result", "field_info", "field_value_text", "field_tooltip",
|
|
75
|
+
"field_dropdown_items", "form_next_element", "form_previous_element", "form_state", "form_default_button",
|
|
76
|
+
"wait_form_closed", "object_command_panel", "object_context_menu", "window_command_interface",
|
|
77
|
+
"command_interface_sections", "command_interface_commands", "command_interface_search", "group_expand", "group_collapse",
|
|
78
|
+
"group_current_page", "group_activate_page", "group_state", "decoration_links", "decoration_click_link", "decoration_click",
|
|
79
|
+
"ui_tree", "command_bar", "click_button", "find_object", "search_objects", "get_object", "activate_object",
|
|
80
|
+
"open_navigation_link", "current_element", "field_dropdown_state", "field_dropdown_close", "toggle_field",
|
|
81
|
+
"set_field_value", "activate_field", "choose_field_from_list", "choose_field_first_row", "choose_current_row",
|
|
82
|
+
"set_form_fields", "create_new_item", "save_form", "close_form", "form_open_settings",
|
|
83
|
+
},
|
|
84
|
+
"tables": {
|
|
85
|
+
"table_current_row", "table_selected_rows", "table_cell_text", "table_rows", "table_find_row", "table_goto_row",
|
|
86
|
+
"table_move_row", "table_add_row", "table_set_field", "table_set_row_values", "table_choose_field_from_list",
|
|
87
|
+
"table_edit_info", "table_select_all", "table_clear_selection", "table_delete_row", "table_copy_row",
|
|
88
|
+
"table_toggle_delete_mark", "table_begin_edit", "table_end_edit", "table_editing_state",
|
|
89
|
+
},
|
|
90
|
+
"tabular": {"tabular_documents", "tabular_document_text", "tabular_document_save", "tabular_document_state_display"},
|
|
91
|
+
"dynamic": {
|
|
92
|
+
"dynamic_list_metadata_available_fields", "dynamic_list_settings_available_fields", "dynamic_list_set_settings",
|
|
93
|
+
"dynamic_list_current_settings", "dynamic_list_sort_by_column", "dynamic_list_output", "dynamic_list_open_settings",
|
|
94
|
+
"dynamic_list_find", "dynamic_list_filter_by_current_value", "dynamic_list_clear_settings",
|
|
95
|
+
},
|
|
96
|
+
"rag": {
|
|
97
|
+
"rag_source_add", "rag_sources_add_from_directory", "rag_source_roots_list", "rag_available_configurations",
|
|
98
|
+
"rag_session_autoconfigure", "rag_sources_list", "rag_snapshot_create", "rag_snapshots_list", "rag_index_build",
|
|
99
|
+
"rag_lookup_object", "rag_query", "metadata_objects_from_catalog", "metadata_objects_catalog_cache_clear",
|
|
100
|
+
"configuration_name_version_from_subsystems", "installed_extensions_from_catalog",
|
|
101
|
+
},
|
|
102
|
+
"recording": {"recording_start", "recording_capture", "recording_stop", "screenshot"},
|
|
103
|
+
}
|
|
104
|
+
TOOL_PROFILE_CORE: set[str] = TOOL_PROFILE_GROUPS["sessions"] | {
|
|
105
|
+
"active_window", "windows_list", "activate_window", "goto_start_page", "goto_previous_window", "goto_next_window",
|
|
106
|
+
"wait_object", "wait_form", "wait_window", "user_messages", "close_user_messages_panel", "current_error_info",
|
|
107
|
+
"field_info", "field_value_text", "field_dropdown_items", "field_dropdown_state", "field_dropdown_close",
|
|
108
|
+
"toggle_field", "set_field_value", "activate_field", "choose_field_from_list", "choose_field_first_row", "choose_current_row",
|
|
109
|
+
"form_state", "form_default_button", "wait_form_closed", "click_button", "find_object", "search_objects", "get_object",
|
|
110
|
+
"activate_object", "open_navigation_link", "current_element", "table_current_row", "table_selected_rows", "table_cell_text",
|
|
111
|
+
"table_rows", "table_find_row", "table_goto_row", "table_move_row", "table_add_row", "table_set_field", "table_set_row_values",
|
|
112
|
+
"table_choose_field_from_list", "table_edit_info", "table_begin_edit", "table_end_edit", "table_editing_state",
|
|
113
|
+
"tabular_documents", "tabular_document_text", "tabular_document_save", "screenshot",
|
|
114
|
+
}
|
|
115
|
+
TOOL_PROFILE_ALIASES: dict[str, set[str]] = {
|
|
116
|
+
"core": TOOL_PROFILE_CORE,
|
|
117
|
+
"ui": TOOL_PROFILE_GROUPS["sessions"] | TOOL_PROFILE_GROUPS["windows"] | TOOL_PROFILE_GROUPS["forms"] | TOOL_PROFILE_GROUPS["tables"] | TOOL_PROFILE_GROUPS["tabular"] | TOOL_PROFILE_GROUPS["recording"],
|
|
118
|
+
"all": set(),
|
|
119
|
+
"full": set(),
|
|
120
|
+
}
|
|
121
|
+
|
|
66
122
|
|
|
67
123
|
@contextmanager
|
|
68
124
|
def _port_allocation_lock():
|
|
@@ -1880,6 +1936,7 @@ async def rag_session_autoconfigure(
|
|
|
1880
1936
|
cfe_files: Annotated[str, "Optional JSON/comma list of .cfe files to load into temporary file IBs and dump as extensions"] = "",
|
|
1881
1937
|
dump_root: Annotated[str, "Optional persistent directory for generated RAG source dumps"] = "",
|
|
1882
1938
|
cleanup_tmp_infobases: Annotated[bool, "Remove temporary file infobases created while loading CF/CFE files"] = True,
|
|
1939
|
+
cleanup_generated_sources: Annotated[bool, "Remove generated XML source dumps after a successful RAG index build"] = True,
|
|
1883
1940
|
*,
|
|
1884
1941
|
session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
|
|
1885
1942
|
) -> dict[str, Any]:
|
|
@@ -1898,8 +1955,10 @@ async def rag_session_autoconfigure(
|
|
|
1898
1955
|
(for example multitenant/separated-data infobases), pass `dump_username` and
|
|
1899
1956
|
`dump_password`; otherwise the current session credentials are reused.
|
|
1900
1957
|
Temporary infobases created from CF/CFE files are deleted after dumping by
|
|
1901
|
-
default
|
|
1902
|
-
|
|
1958
|
+
default. Generated XML source directories are removed after a successful
|
|
1959
|
+
index build by default, because the SQLite RAG index is self-contained; pass
|
|
1960
|
+
`cleanup_generated_sources=false` when the dumped XML source tree should be
|
|
1961
|
+
kept under `dump_root` for inspection or reuse.
|
|
1903
1962
|
"""
|
|
1904
1963
|
params = {
|
|
1905
1964
|
"snapshot_name": snapshot_name,
|
|
@@ -1916,6 +1975,7 @@ async def rag_session_autoconfigure(
|
|
|
1916
1975
|
"cfe_files": cfe_files,
|
|
1917
1976
|
"dump_root": dump_root,
|
|
1918
1977
|
"cleanup_tmp_infobases": cleanup_tmp_infobases,
|
|
1978
|
+
"cleanup_generated_sources": cleanup_generated_sources,
|
|
1919
1979
|
}
|
|
1920
1980
|
disabled = _rag_require_enabled(session_id, "rag_session_autoconfigure", params)
|
|
1921
1981
|
if disabled is not None:
|
|
@@ -2145,6 +2205,9 @@ async def rag_session_autoconfigure(
|
|
|
2145
2205
|
_persist_sessions()
|
|
2146
2206
|
|
|
2147
2207
|
index_result = _RAG.build_index(snapshot=sess.rag_snapshot) if build_index else None
|
|
2208
|
+
cleanup_generated_sources_result = None
|
|
2209
|
+
if cleanup_generated_sources and index_result is not None and dump_base_dir is not None:
|
|
2210
|
+
cleanup_generated_sources_result = _RAG.cleanup_generated_source_files_under(dump_base_dir)
|
|
2148
2211
|
result = {
|
|
2149
2212
|
"snapshot": sess.rag_snapshot,
|
|
2150
2213
|
"snapshot_result": snapshot,
|
|
@@ -2156,7 +2219,9 @@ async def rag_session_autoconfigure(
|
|
|
2156
2219
|
"extension_matches": extension_matches,
|
|
2157
2220
|
"dumps": dumps,
|
|
2158
2221
|
"generated_sources": generated_sources,
|
|
2222
|
+
"generated_sources_count": len(generated_sources),
|
|
2159
2223
|
"dump_root": str(dump_base_dir) if dump_base_dir else "",
|
|
2224
|
+
"cleanup_generated_sources": cleanup_generated_sources_result,
|
|
2160
2225
|
"available_sources_count": len(sources),
|
|
2161
2226
|
"session_snapshot": sess.rag_snapshot,
|
|
2162
2227
|
}
|
|
@@ -5660,6 +5725,40 @@ def _register_dev_tools(enabled: bool) -> None:
|
|
|
5660
5725
|
)
|
|
5661
5726
|
|
|
5662
5727
|
|
|
5728
|
+
def _tool_names_for_profile(profile: str) -> set[str] | None:
|
|
5729
|
+
parts = [part.strip().lower() for part in re.split(r"[,;\s]+", profile or "") if part.strip()]
|
|
5730
|
+
if not parts or any(part in {"all", "full"} for part in parts):
|
|
5731
|
+
return None
|
|
5732
|
+
selected: set[str] = set()
|
|
5733
|
+
unknown: list[str] = []
|
|
5734
|
+
for part in parts:
|
|
5735
|
+
names = TOOL_PROFILE_ALIASES.get(part) or TOOL_PROFILE_GROUPS.get(part)
|
|
5736
|
+
if names is None:
|
|
5737
|
+
unknown.append(part)
|
|
5738
|
+
continue
|
|
5739
|
+
selected.update(names)
|
|
5740
|
+
if unknown:
|
|
5741
|
+
known = sorted({*TOOL_PROFILE_GROUPS, *TOOL_PROFILE_ALIASES})
|
|
5742
|
+
raise ValueError(f"unknown tool profile(s): {', '.join(unknown)}; known profiles: {', '.join(known)}")
|
|
5743
|
+
return selected
|
|
5744
|
+
|
|
5745
|
+
|
|
5746
|
+
def _apply_tool_profile(profile: str) -> dict[str, Any]:
|
|
5747
|
+
allowed = _tool_names_for_profile(profile)
|
|
5748
|
+
if allowed is None:
|
|
5749
|
+
return {"profile": profile or "full", "mode": "full", "removed": []}
|
|
5750
|
+
existing = set(getattr(mcp, "_tool_manager")._tools.keys())
|
|
5751
|
+
removed: list[str] = []
|
|
5752
|
+
for name in sorted(existing - allowed):
|
|
5753
|
+
try:
|
|
5754
|
+
mcp.remove_tool(name)
|
|
5755
|
+
removed.append(name)
|
|
5756
|
+
except Exception:
|
|
5757
|
+
pass
|
|
5758
|
+
remaining = sorted(set(getattr(mcp, "_tool_manager")._tools.keys()))
|
|
5759
|
+
return {"profile": profile, "mode": "filtered", "remaining_count": len(remaining), "removed_count": len(removed), "removed": removed}
|
|
5760
|
+
|
|
5761
|
+
|
|
5663
5762
|
def main(argv: list[str] | None = None) -> None:
|
|
5664
5763
|
global _RAG_ENABLED, _CREDENTIAL_STORE_ENABLED, _DEV_TOOLS_ENABLED
|
|
5665
5764
|
|
|
@@ -5730,6 +5829,11 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
5730
5829
|
default=os.getenv("ONEC_MCP_SESSION_SCOPE_ID") or os.getenv("ONEC_MCP_SESSION_SCOPE") or "",
|
|
5731
5830
|
help="enable scoped lock/reconnect mode for the given session scope id; without this flag Answer42 uses legacy no-lock global state",
|
|
5732
5831
|
)
|
|
5832
|
+
parser.add_argument(
|
|
5833
|
+
"--tool-profile",
|
|
5834
|
+
default=os.getenv("ONEC_MCP_TOOL_PROFILE", "full"),
|
|
5835
|
+
help="comma-separated tool profile groups: core, ui, sessions, windows, forms, tables, tabular, dynamic, rag, recording, full, all",
|
|
5836
|
+
)
|
|
5733
5837
|
args = parser.parse_args(argv)
|
|
5734
5838
|
|
|
5735
5839
|
_RAG_ENABLED = not args.disable_rag
|
|
@@ -5741,6 +5845,8 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
5741
5845
|
_register_startup_rag_source_dirs(args.rag_source_dir or [], args.rag_source_prefix, not args.rag_source_no_recursive, args.rag_source_max_depth)
|
|
5742
5846
|
else:
|
|
5743
5847
|
logging.info("Built-in Answer42 RAG is disabled")
|
|
5848
|
+
profile_result = _apply_tool_profile(args.tool_profile)
|
|
5849
|
+
logging.info("Tool profile: %s (mode=%s, remaining=%d, removed=%d)", profile_result["profile"], profile_result["mode"], profile_result.get("remaining_count", 0), profile_result.get("removed_count", 0))
|
|
5744
5850
|
_start_autoreload_watcher(args.dev, args.dev_restart_timeout)
|
|
5745
5851
|
if args.session_scope:
|
|
5746
5852
|
_configure_session_scope(args.session_scope)
|
|
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
|
|
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
|