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.
Files changed (59) hide show
  1. {answer42-0.3.3 → answer42-0.3.4}/PKG-INFO +1 -1
  2. {answer42-0.3.3 → answer42-0.3.4}/pyproject.toml +1 -1
  3. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  4. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  5. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/parsers.py +52 -8
  6. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/service.py +19 -0
  7. {answer42-0.3.3 → answer42-0.3.4}/.gitignore +0 -0
  8. {answer42-0.3.3 → answer42-0.3.4}/LICENSE +0 -0
  9. {answer42-0.3.3 → answer42-0.3.4}/README.md +0 -0
  10. {answer42-0.3.3 → answer42-0.3.4}/credentials.example.json +0 -0
  11. {answer42-0.3.3 → answer42-0.3.4}/docs/agent-installation.md +0 -0
  12. {answer42-0.3.3 → answer42-0.3.4}/docs/architecture.md +0 -0
  13. {answer42-0.3.3 → answer42-0.3.4}/docs/assets/answer42-logo.png +0 -0
  14. {answer42-0.3.3 → answer42-0.3.4}/docs/assets/platform42-logo.svg +0 -0
  15. {answer42-0.3.3 → answer42-0.3.4}/docs/installation.md +0 -0
  16. {answer42-0.3.3 → answer42-0.3.4}/scripts/build_cf.py +0 -0
  17. {answer42-0.3.3 → answer42-0.3.4}/scripts/build_pages.py +0 -0
  18. {answer42-0.3.3 → answer42-0.3.4}/scripts/e2e_stable.py +0 -0
  19. {answer42-0.3.3 → answer42-0.3.4}/scripts/openclaw_mcp_autoreload.py +0 -0
  20. {answer42-0.3.3 → answer42-0.3.4}/scripts/rag_cli.py +0 -0
  21. {answer42-0.3.3 → answer42-0.3.4}/src/cf/ConfigDumpInfo.xml +0 -0
  22. {answer42-0.3.3 → answer42-0.3.4}/src/cf/Configuration.xml +0 -0
  23. {answer42-0.3.3 → answer42-0.3.4}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  24. {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
  25. {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
  26. {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
  27. {answer42-0.3.3 → answer42-0.3.4}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  28. {answer42-0.3.3 → answer42-0.3.4}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  29. {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
  30. {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
  31. {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
  32. {answer42-0.3.3 → answer42-0.3.4}/src/client_cf/ConfigDumpInfo.xml +0 -0
  33. {answer42-0.3.3 → answer42-0.3.4}/src/client_cf/Configuration.xml +0 -0
  34. {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
  35. {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
  36. {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
  37. {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
  38. {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
  39. {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
  40. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/__init__.py +0 -0
  41. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/__init__.py +0 -0
  42. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  43. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  44. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/bridge.py +0 -0
  45. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/credentials.py +0 -0
  46. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/os_support.py +0 -0
  47. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/platform.py +0 -0
  48. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/protocol.py +0 -0
  49. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/__init__.py +0 -0
  50. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/detect.py +0 -0
  51. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/dump.py +0 -0
  52. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/model.py +0 -0
  53. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/rag/store.py +0 -0
  54. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/recorder.py +0 -0
  55. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/release_helper.py +0 -0
  56. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/runtime.py +0 -0
  57. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/server.py +0 -0
  58. {answer42-0.3.3 → answer42-0.3.4}/src/mcp_1c/skill_installer.py +0 -0
  59. {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
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"
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"
@@ -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
- _extract_forms(parsed, mdo.parent, full)
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
- _extract_forms(parsed, object_dir, full)
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 / "Ext" / "Form.xml"
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
- elem.attrib.get("title") or elem.attrib.get("Title") or _first_text(elem, ["title", "Title", "caption", "Caption"]),
303
- elem.attrib.get("dataPath") or elem.attrib.get("path") or _first_text(elem, ["dataPath", "path", "DataPath"]),
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