answer42 0.2.9__tar.gz → 0.2.11__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.9 → answer42-0.2.11}/PKG-INFO +1 -1
  2. {answer42-0.2.9 → answer42-0.2.11}/pyproject.toml +1 -1
  3. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  4. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  5. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/server.py +86 -16
  6. {answer42-0.2.9 → answer42-0.2.11}/.gitignore +0 -0
  7. {answer42-0.2.9 → answer42-0.2.11}/LICENSE +0 -0
  8. {answer42-0.2.9 → answer42-0.2.11}/README.md +0 -0
  9. {answer42-0.2.9 → answer42-0.2.11}/credentials.example.json +0 -0
  10. {answer42-0.2.9 → answer42-0.2.11}/docs/agent-installation.md +0 -0
  11. {answer42-0.2.9 → answer42-0.2.11}/docs/architecture.md +0 -0
  12. {answer42-0.2.9 → answer42-0.2.11}/docs/assets/answer42-logo.png +0 -0
  13. {answer42-0.2.9 → answer42-0.2.11}/docs/assets/platform42-logo.svg +0 -0
  14. {answer42-0.2.9 → answer42-0.2.11}/docs/installation.md +0 -0
  15. {answer42-0.2.9 → answer42-0.2.11}/scripts/build_cf.py +0 -0
  16. {answer42-0.2.9 → answer42-0.2.11}/scripts/build_pages.py +0 -0
  17. {answer42-0.2.9 → answer42-0.2.11}/scripts/e2e_stable.py +0 -0
  18. {answer42-0.2.9 → answer42-0.2.11}/scripts/openclaw_mcp_autoreload.py +0 -0
  19. {answer42-0.2.9 → answer42-0.2.11}/scripts/rag_cli.py +0 -0
  20. {answer42-0.2.9 → answer42-0.2.11}/src/cf/ConfigDumpInfo.xml +0 -0
  21. {answer42-0.2.9 → answer42-0.2.11}/src/cf/Configuration.xml +0 -0
  22. {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  23. {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl" +0 -0
  24. {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  25. {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  26. {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  27. {answer42-0.2.9 → answer42-0.2.11}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  28. {answer42-0.2.9 → answer42-0.2.11}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  29. {answer42-0.2.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/src/client_cf/ConfigDumpInfo.xml +0 -0
  32. {answer42-0.2.9 → answer42-0.2.11}/src/client_cf/Configuration.xml +0 -0
  33. {answer42-0.2.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/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.9 → answer42-0.2.11}/src/mcp_1c/__init__.py +0 -0
  40. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/__init__.py +0 -0
  41. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  42. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  43. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/bridge.py +0 -0
  44. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/credentials.py +0 -0
  45. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/os_support.py +0 -0
  46. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/platform.py +0 -0
  47. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/protocol.py +0 -0
  48. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/__init__.py +0 -0
  49. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/detect.py +0 -0
  50. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/model.py +0 -0
  51. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/parsers.py +0 -0
  52. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/service.py +0 -0
  53. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/store.py +0 -0
  54. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/recorder.py +0 -0
  55. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/release_helper.py +0 -0
  56. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/runtime.py +0 -0
  57. {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/skill_installer.py +0 -0
  58. {answer42-0.2.9 → answer42-0.2.11}/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.9
3
+ Version: 0.2.11
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.9"
3
+ version = "0.2.11"
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"
@@ -254,14 +254,18 @@ def _acquire_shared_file_server(
254
254
  cf = Path(cf_path) if cf_path else None
255
255
  if cf and not cf.exists():
256
256
  raise FileNotFoundError(f"Shared file infobase CF not found at {cf}.")
257
+ if cf and resolved_db_path.exists() and not _cf_marker_matches(resolved_db_path, cf):
258
+ logging.info("Shared file infobase %s has stale Answer42 CF marker; recreating", resolved_db_path)
259
+ shutil.rmtree(resolved_db_path, ignore_errors=True)
257
260
  # Shared file infobases are intentionally persistent across several
258
261
  # MCP server processes. If a previous process already created the
259
262
  # database but died before writing the registry entry, do not wipe it
260
- # here: just publish it through a fresh ibsrv.
263
+ # here: just publish it through a fresh ibsrv unless its CF marker is stale.
261
264
  if not resolved_db_path.exists() or not any(resolved_db_path.iterdir()):
262
265
  runtime.create_file_infobase(ibcmd, resolved_db_path)
263
266
  if cf:
264
267
  runtime.load_configuration(ibcmd, resolved_db_path, str(cf))
268
+ _write_cf_marker(resolved_db_path, cf)
265
269
  elif not resolved_db_path.exists():
266
270
  raise FileNotFoundError(f"File infobase directory not found: {resolved_db_path}")
267
271
 
@@ -580,19 +584,41 @@ def _packaged_cf_name(src_dir: str) -> str:
580
584
  return "MCPTestManager.cf"
581
585
 
582
586
 
583
- def _copy_packaged_cf(target: Path, src_dir: str) -> bool:
587
+ def _file_sha256(path: Path | str) -> str:
588
+ digest = hashlib.sha256()
589
+ with Path(path).open("rb") as fh:
590
+ for chunk in iter(lambda: fh.read(1024 * 1024), b""):
591
+ digest.update(chunk)
592
+ return digest.hexdigest()
593
+
594
+
595
+ def _packaged_cf_path(src_dir: str) -> Path | None:
584
596
  cf_name = _packaged_cf_name(src_dir)
585
597
  try:
586
598
  resource = importlib.resources.files("mcp_1c.assets").joinpath(cf_name)
587
599
  if not resource.is_file():
588
- return False
589
- target.parent.mkdir(parents=True, exist_ok=True)
600
+ return None
590
601
  with importlib.resources.as_file(resource) as path:
591
- shutil.copy2(path, target)
602
+ return Path(path)
603
+ except Exception as exc:
604
+ logging.debug("Packaged CF artifact %s is not available: %s", cf_name, exc)
605
+ return None
606
+
607
+
608
+ def _copy_packaged_cf(target: Path, src_dir: str, *, force: bool = False) -> bool:
609
+ cf_name = _packaged_cf_name(src_dir)
610
+ source = _packaged_cf_path(src_dir)
611
+ if source is None:
612
+ return False
613
+ try:
614
+ if target.exists() and not force and _file_sha256(target) == _file_sha256(source):
615
+ return True
616
+ target.parent.mkdir(parents=True, exist_ok=True)
617
+ shutil.copy2(source, target)
592
618
  logging.info("Copied packaged CF artifact %s to %s", cf_name, target)
593
619
  return True
594
620
  except Exception as exc:
595
- logging.debug("Packaged CF artifact %s is not available: %s", cf_name, exc)
621
+ logging.debug("Could not copy packaged CF artifact %s: %s", cf_name, exc)
596
622
  return False
597
623
 
598
624
 
@@ -600,17 +626,18 @@ def _ensure_cf_built(out_cf: str, *, src_dir: str, platform_dir: Path) -> str:
600
626
  """Build a .cf artifact when XML sources are available; otherwise copy packaged CF."""
601
627
  target = Path(out_cf)
602
628
  source = Path(src_dir)
603
- if not _source_newer_than(target, source):
604
- return str(target)
605
- if not source.exists():
606
- if _copy_packaged_cf(target, src_dir):
607
- return str(target)
608
- raise FileNotFoundError(f"CF source directory not found and no packaged CF is available: {source}")
609
629
  script = Path(__file__).resolve().parents[2] / "scripts" / "build_cf.py"
610
- if not script.exists():
611
- if _copy_packaged_cf(target, src_dir):
612
- return str(target)
630
+ if not source.exists() or not script.exists():
631
+ packaged = _packaged_cf_path(src_dir)
632
+ if packaged is not None:
633
+ force = not target.exists() or _file_sha256(target) != _file_sha256(packaged)
634
+ if _copy_packaged_cf(target, src_dir, force=force):
635
+ return str(target)
636
+ if not source.exists():
637
+ raise FileNotFoundError(f"CF source directory not found and no packaged CF is available: {source}")
613
638
  raise FileNotFoundError(f"CF build script not found and no packaged CF is available: {script}")
639
+ if not _source_newer_than(target, source):
640
+ return str(target)
614
641
  target.parent.mkdir(parents=True, exist_ok=True)
615
642
  env = os.environ.copy()
616
643
  env["ONEC_PLATFORM_DIR"] = str(platform_dir)
@@ -633,6 +660,32 @@ def _ensure_cf_built(out_cf: str, *, src_dir: str, platform_dir: Path) -> str:
633
660
  return str(target)
634
661
 
635
662
 
663
+ def _cf_marker_path(db_path: Path) -> Path:
664
+ return db_path.parent / f".{db_path.name}.answer42-cf.json"
665
+
666
+
667
+ def _cf_marker_data(cf_path: Path | str) -> dict[str, str]:
668
+ path = Path(cf_path)
669
+ return {"answer42_version": __version__, "cf_sha256": _file_sha256(path), "cf_path": str(path)}
670
+
671
+
672
+ def _write_cf_marker(db_path: Path, cf_path: Path | str) -> None:
673
+ marker = _cf_marker_path(db_path)
674
+ marker.write_text(json.dumps(_cf_marker_data(cf_path), ensure_ascii=False, indent=2), encoding="utf-8")
675
+
676
+
677
+ def _cf_marker_matches(db_path: Path, cf_path: Path | str) -> bool:
678
+ marker = _cf_marker_path(db_path)
679
+ if not db_path.exists() or not marker.exists() or not Path(cf_path).exists():
680
+ return False
681
+ try:
682
+ stored = json.loads(marker.read_text(encoding="utf-8"))
683
+ except Exception:
684
+ return False
685
+ expected = _cf_marker_data(cf_path)
686
+ return stored.get("answer42_version") == expected["answer42_version"] and stored.get("cf_sha256") == expected["cf_sha256"]
687
+
688
+
636
689
  def _normalize_base_url(url: str) -> str:
637
690
  return _platform.normalize_infobase_url(str(url or "").strip())
638
691
 
@@ -777,6 +830,17 @@ async def _ensure_restored_session(session_id: str = DEFAULT_SESSION) -> Session
777
830
  sess = persisted.get(session_id)
778
831
  if sess is None:
779
832
  raise ValueError(f"Session {session_id!r} not found. Call start_session first.")
833
+ cf_path = _ensure_cf_built(runtime.DEFAULT_CF_BUILT, src_dir="src/cf", platform_dir=sess.platform_dir)
834
+ if not _cf_marker_matches(sess.db_path, cf_path):
835
+ _cleanup_stale_session_processes(sess, "stale manager CF marker")
836
+ shutil.rmtree(sess.data_dir, ignore_errors=True)
837
+ persisted.pop(session_id, None)
838
+ STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
839
+ STATE_FILE.write_text(json.dumps({sid: s.to_json() for sid, s in persisted.items()}, ensure_ascii=False, indent=2), encoding="utf-8")
840
+ raise ValueError(
841
+ f"Session {session_id!r} was created with an older Answer42 manager CF. "
842
+ "Its temporary manager infobase was removed; call start_session again."
843
+ )
780
844
  await sess.bridge.start()
781
845
  _SESSIONS[session_id] = sess
782
846
  logging.info("Restored MCP session %s from %s; bridge=%s", session_id, STATE_FILE, sess.bridge.url)
@@ -1700,6 +1764,7 @@ async def start_session(
1700
1764
  platform_dir=platform_dir,
1701
1765
  )
1702
1766
  runtime.load_configuration(sess.ibcmd, sess.db_path, cf_path)
1767
+ _write_cf_marker(sess.db_path, cf_path)
1703
1768
  except Exception:
1704
1769
  await _cleanup_failed_start_session(sess, "infobase preparation failure")
1705
1770
  raise
@@ -1801,6 +1866,7 @@ async def start_session(
1801
1866
  platform_dir=platform_dir,
1802
1867
  )
1803
1868
  runtime.load_configuration(sess.ibcmd, sess.client_db_path, client_cf_path)
1869
+ _write_cf_marker(sess.client_db_path, client_cf_path)
1804
1870
  sess.client_runtime = runtime.ManagerRuntime(
1805
1871
  infobase=runtime.InfobaseSpec(db_path=sess.client_db_path),
1806
1872
  server=runtime.StandaloneServerSpec(address="", port=0, name="file-direct", data_dir=sess.client_server_data_dir),
@@ -4048,7 +4114,11 @@ def screenshot(
4048
4114
  else:
4049
4115
  os.environ["DISPLAY"] = previous_display
4050
4116
  except ImportError as exc:
4051
- raise RuntimeError("Install screenshot extra: pip install '.[screenshot]' or mss") from exc
4117
+ raise RuntimeError(
4118
+ "Screenshot support requires the optional dependency 'mss'. "
4119
+ "Install it with: pip install 'answer42[screenshot]' "
4120
+ "or, for pipx installs: pipx inject answer42 mss."
4121
+ ) from exc
4052
4122
 
4053
4123
  data = output_path.read_bytes()
4054
4124
  result = {"path": str(output_path), "bytes": len(data), "base64": base64.b64encode(data).decode("ascii")}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes