answer42 0.3.3__tar.gz → 0.3.4__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.4}/PKG-INFO +1 -1
- {answer42-0.3.3 → answer42-0.3.4}/pyproject.toml +1 -1
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/parsers.py +52 -8
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/service.py +19 -0
- {answer42-0.3.3 → answer42-0.3.4}/.gitignore +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/LICENSE +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/README.md +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/credentials.example.json +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/docs/agent-installation.md +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/docs/architecture.md +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/docs/installation.md +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/scripts/build_cf.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/scripts/build_pages.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/scripts/e2e_stable.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/scripts/rag_cli.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/cf/Configuration.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/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.4}/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.4}/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.4}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/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.4}/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.4}/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.4}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/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.4}/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.4}/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.4}/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.4}/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.4}/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.4}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/credentials.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/platform.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/protocol.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/dump.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/runtime.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/server.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.3.3 → answer42-0.3.4}/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.4
|
|
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.4"
|
|
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():
|
|
@@ -266,6 +266,12 @@ class RagService:
|
|
|
266
266
|
continue
|
|
267
267
|
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
268
|
counts["attributes"] += 1
|
|
269
|
+
text_parts = [attr.object_full_name, attr.role, attr.name, search_text_variants(attr.name, attr.synonym), attr.synonym or "", attr.type_name or ""]
|
|
270
|
+
chunk_text = " ".join(part for part in text_parts if part)
|
|
271
|
+
cid = _insert_chunk(con, snapshot_id, oid, "attribute", chunk_text, attr.source_path, {"source": src["name"], "role": attr.role})
|
|
272
|
+
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))
|
|
273
|
+
_insert_embedding(con, cid, chunk_text)
|
|
274
|
+
counts["chunks"] += 1
|
|
269
275
|
for tp in parsed.table_parts:
|
|
270
276
|
oid = object_ids.get(tp.object_full_name)
|
|
271
277
|
if not oid:
|
|
@@ -284,12 +290,25 @@ class RagService:
|
|
|
284
290
|
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
291
|
form_ids[(form.object_full_name, form.name)] = cur.lastrowid
|
|
286
292
|
counts["forms"] += 1
|
|
293
|
+
text_parts = [form.object_full_name, "форма", form.name, search_text_variants(form.name), form.kind or ""]
|
|
294
|
+
chunk_text = " ".join(part for part in text_parts if part)
|
|
295
|
+
cid = _insert_chunk(con, snapshot_id, oid, "form", chunk_text, form.source_path, {"source": src["name"], "form": form.name})
|
|
296
|
+
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))
|
|
297
|
+
_insert_embedding(con, cid, chunk_text)
|
|
298
|
+
counts["chunks"] += 1
|
|
287
299
|
for elem in parsed.form_elements:
|
|
288
300
|
fid = form_ids.get((elem.object_full_name, elem.form_name))
|
|
289
301
|
if not fid:
|
|
290
302
|
continue
|
|
291
303
|
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
304
|
counts["form_elements"] += 1
|
|
305
|
+
oid = object_ids.get(elem.object_full_name)
|
|
306
|
+
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 ""]
|
|
307
|
+
chunk_text = " ".join(part for part in text_parts if part)
|
|
308
|
+
cid = _insert_chunk(con, snapshot_id, oid, "form_element", chunk_text, elem.source_path, {"source": src["name"], "form": elem.form_name, "element": elem.name})
|
|
309
|
+
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))
|
|
310
|
+
_insert_embedding(con, cid, chunk_text)
|
|
311
|
+
counts["chunks"] += 1
|
|
293
312
|
for dcs_field in parsed.data_composition_fields:
|
|
294
313
|
oid = object_ids.get(dcs_field.object_full_name)
|
|
295
314
|
if not oid:
|
|
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
|
|
File without changes
|