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.
- {answer42-0.2.9 → answer42-0.2.11}/PKG-INFO +1 -1
- {answer42-0.2.9 → answer42-0.2.11}/pyproject.toml +1 -1
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/server.py +86 -16
- {answer42-0.2.9 → answer42-0.2.11}/.gitignore +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/LICENSE +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/README.md +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/credentials.example.json +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/docs/agent-installation.md +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/docs/architecture.md +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/docs/installation.md +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/scripts/build_cf.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/scripts/build_pages.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/scripts/e2e_stable.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/scripts/rag_cli.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/cf/Configuration.xml +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {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
- {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
- {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
- {answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {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
- {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
- {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
- {answer42-0.2.9 → answer42-0.2.11}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/client_cf/Configuration.xml +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/credentials.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/platform.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/protocol.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/parsers.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/service.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/runtime.py +0 -0
- {answer42-0.2.9 → answer42-0.2.11}/src/mcp_1c/skill_installer.py +0 -0
- {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.
|
|
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.
|
|
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"
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
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
|
|
589
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
600
|
+
return None
|
|
590
601
|
with importlib.resources.as_file(resource) as path:
|
|
591
|
-
|
|
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("
|
|
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
|
-
|
|
612
|
-
|
|
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(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{answer42-0.2.9 → answer42-0.2.11}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl
RENAMED
|
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
|