answer42 0.5.6__tar.gz → 0.5.7__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.5.6 → answer42-0.5.7}/PKG-INFO +1 -1
- {answer42-0.5.6 → answer42-0.5.7}/pyproject.toml +1 -1
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/credentials.py +25 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/server.py +56 -18
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/webclient_probe.py +7 -0
- {answer42-0.5.6 → answer42-0.5.7}/.gitignore +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/LICENSE +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/README.md +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/credentials.example.json +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/docs/agent-installation.md +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/docs/architecture.md +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/docs/installation.md +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/build_cf.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/build_pages.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/e2e_http_smoke.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/e2e_stable.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/rag_cli.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/scripts/read_webclient_start_info.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/Configuration.xml +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl" +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/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.5.6 → answer42-0.5.7}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/auth.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/platform.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/protocol.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/dump.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/parsers.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/service.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/summary_backend.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/summary_vector.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/runtime.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/win_desktop.py +0 -0
- {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/window_control.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: answer42
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
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.5.
|
|
3
|
+
version = "0.5.7"
|
|
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
|
|
@@ -110,6 +110,31 @@ class CredentialsStore:
|
|
|
110
110
|
|
|
111
111
|
return None
|
|
112
112
|
|
|
113
|
+
def matching_entries(
|
|
114
|
+
self,
|
|
115
|
+
base_url: str,
|
|
116
|
+
account_id: str | None = None,
|
|
117
|
+
) -> list[dict[str, object]]:
|
|
118
|
+
"""Return every exact and wildcard credential candidate for a URL.
|
|
119
|
+
|
|
120
|
+
Exact URL entries precede wildcard entries so callers can try the most
|
|
121
|
+
specific mapping first without discarding other matching wildcards.
|
|
122
|
+
"""
|
|
123
|
+
url = str(base_url or "").strip().rstrip("/")
|
|
124
|
+
if not url:
|
|
125
|
+
return []
|
|
126
|
+
entries = self._account_entries(account_id)
|
|
127
|
+
exact = [
|
|
128
|
+
dict(entry) for entry in entries
|
|
129
|
+
if str(entry.get("url", "")).rstrip("/") == url
|
|
130
|
+
]
|
|
131
|
+
wildcard = [
|
|
132
|
+
dict(entry) for entry in entries
|
|
133
|
+
if self._has_wildcards(str(entry.get("url", "")))
|
|
134
|
+
and fnmatch.fnmatch(url, str(entry.get("url", "")).rstrip("/"))
|
|
135
|
+
]
|
|
136
|
+
return exact + wildcard
|
|
137
|
+
|
|
113
138
|
def list_entries(
|
|
114
139
|
self,
|
|
115
140
|
account_id: str | None = None,
|
|
@@ -4646,17 +4646,26 @@ async def start_session(
|
|
|
4646
4646
|
start_monotonic = time.perf_counter()
|
|
4647
4647
|
stage_monotonic = start_monotonic
|
|
4648
4648
|
timings: dict[str, float] = {}
|
|
4649
|
+
supplied_username = username
|
|
4650
|
+
supplied_password = password
|
|
4649
4651
|
|
|
4650
4652
|
def mark_stage(name: str) -> None:
|
|
4651
4653
|
nonlocal stage_monotonic
|
|
4652
4654
|
now = time.perf_counter()
|
|
4653
4655
|
timings[f"{name}_seconds"] = round(now - stage_monotonic, 3)
|
|
4654
4656
|
stage_monotonic = now
|
|
4655
|
-
|
|
4657
|
+
input_looks_like_url = bool(re.match(r"^[A-Za-z][A-Za-z0-9+.-]*://", str(base_url or "")))
|
|
4658
|
+
# A URL without explicitly supplied credentials is resolved by the web
|
|
4659
|
+
# probe below. It must try every exact/wildcard candidate and retain the
|
|
4660
|
+
# one that authenticated, rather than preselecting the first match here.
|
|
4661
|
+
credential_ref = (
|
|
4662
|
+
None
|
|
4663
|
+
if input_looks_like_url and not username and not password
|
|
4664
|
+
else _resolve_credential_reference(base_url)
|
|
4665
|
+
)
|
|
4656
4666
|
credential_account_id = _current_account_id()
|
|
4657
4667
|
saved_credentials_used = False
|
|
4658
4668
|
saved_credentials_unverified = False
|
|
4659
|
-
input_looks_like_url = bool(re.match(r"^[A-Za-z][A-Za-z0-9+.-]*://", str(base_url or "")))
|
|
4660
4669
|
if credential_ref is not None:
|
|
4661
4670
|
# For saved titles/aliases, replace title -> real URL. For URL/wildcard matches,
|
|
4662
4671
|
# keep the original requested URL; using the wildcard pattern itself as /WS value
|
|
@@ -4685,11 +4694,19 @@ async def start_session(
|
|
|
4685
4694
|
base_url = ""
|
|
4686
4695
|
web_runtime_probe: dict[str, Any] = {"attempted": False, "status": "not_applicable"}
|
|
4687
4696
|
if base_url and initial_connection_mode == "ws":
|
|
4688
|
-
web_runtime_probe = await _probe_web_runtime_before_start(base_url,
|
|
4697
|
+
web_runtime_probe = await _probe_web_runtime_before_start(base_url, supplied_username, supplied_password)
|
|
4689
4698
|
if web_runtime_probe.get("authentication_failed"):
|
|
4690
4699
|
# No local 1C runtime/process has been started yet. Do not hide a
|
|
4691
4700
|
# confirmed publication login rejection behind a later timeout.
|
|
4692
4701
|
raise ValueError(str(web_runtime_probe.get("message") or "1C web authentication failed"))
|
|
4702
|
+
selected_credential_index = web_runtime_probe.pop("_selected_credential_index", None)
|
|
4703
|
+
if selected_credential_index is not None and not supplied_username and not supplied_password:
|
|
4704
|
+
candidates = _credentials_store().matching_entries(base_url, account_id=credential_account_id)
|
|
4705
|
+
selected = candidates[int(selected_credential_index)]
|
|
4706
|
+
username = str(selected["username"])
|
|
4707
|
+
password = str(selected["password"])
|
|
4708
|
+
saved_credentials_used = True
|
|
4709
|
+
saved_credentials_unverified = not bool(selected.get("verified", False))
|
|
4693
4710
|
inferred_summary_rag_config = "" if summary_rag_config else _summary_rag_config_from_42clouds_url(base_url)
|
|
4694
4711
|
effective_summary_rag_config = summary_rag_config or inferred_summary_rag_config
|
|
4695
4712
|
resolved_version = version or None
|
|
@@ -5371,25 +5388,46 @@ async def _probe_web_runtime_before_start(
|
|
|
5371
5388
|
"""
|
|
5372
5389
|
if not _is_web_base_url(base_url):
|
|
5373
5390
|
return {"attempted": False, "status": "not_web_publication"}
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5391
|
+
candidates: list[tuple[str, str, str, int | None]] = []
|
|
5392
|
+
if username or password:
|
|
5393
|
+
candidates.append((username, password, "explicit", None))
|
|
5394
|
+
elif _CREDENTIAL_STORE_ENABLED:
|
|
5395
|
+
for index, entry in enumerate(_credentials_store().matching_entries(base_url, account_id=_current_account_id())):
|
|
5396
|
+
candidates.append((entry["username"], entry["password"], "saved", index))
|
|
5397
|
+
if not candidates:
|
|
5398
|
+
candidates.append(("", "", "empty", None))
|
|
5399
|
+
|
|
5400
|
+
authentication_messages: list[str] = []
|
|
5401
|
+
unavailable_messages: list[str] = []
|
|
5402
|
+
for candidate_username, candidate_password, source, index in candidates:
|
|
5403
|
+
try:
|
|
5404
|
+
result = await asyncio.to_thread(probe_web_runtime, base_url, candidate_username, candidate_password)
|
|
5405
|
+
response: dict[str, Any] = {
|
|
5406
|
+
"attempted": True,
|
|
5407
|
+
"status": "ok",
|
|
5408
|
+
"runtime": result,
|
|
5409
|
+
"credentials_source": source,
|
|
5410
|
+
}
|
|
5411
|
+
if index is not None:
|
|
5412
|
+
response["_selected_credential_index"] = index
|
|
5413
|
+
return response
|
|
5414
|
+
except WebRuntimeAuthenticationError as exc:
|
|
5415
|
+
authentication_messages.append(str(exc))
|
|
5416
|
+
except (WebRuntimeProbeError, requests.RequestException) as exc:
|
|
5417
|
+
logging.info("Web runtime probe skipped for %s: %s", _normalize_base_url(base_url), exc)
|
|
5418
|
+
unavailable_messages.append(str(exc))
|
|
5419
|
+
if unavailable_messages:
|
|
5388
5420
|
return {
|
|
5389
5421
|
"attempted": True,
|
|
5390
5422
|
"status": "unavailable",
|
|
5391
|
-
"message":
|
|
5423
|
+
"message": unavailable_messages[-1],
|
|
5392
5424
|
}
|
|
5425
|
+
return {
|
|
5426
|
+
"attempted": True,
|
|
5427
|
+
"status": "authentication_failed",
|
|
5428
|
+
"authentication_failed": True,
|
|
5429
|
+
"message": authentication_messages[-1] if authentication_messages else "1C web authentication failed",
|
|
5430
|
+
}
|
|
5393
5431
|
|
|
5394
5432
|
|
|
5395
5433
|
def _users_url_for_base_url(base_url: str) -> str:
|
|
@@ -196,6 +196,13 @@ def _login(
|
|
|
196
196
|
key_size = reply[0]
|
|
197
197
|
encrypted_login_key = reply[1 : 1 + key_size]
|
|
198
198
|
login_key = _decrypt(encrypted_login_key, proof_key, _INNER_IV)
|
|
199
|
+
except WebRuntimeProbeError as exc:
|
|
200
|
+
# Some 1C web publications signal an invalid password only through a
|
|
201
|
+
# malformed encrypted proof instead of an HTTP 401/403. Treat it as an
|
|
202
|
+
# authentication rejection so callers can try the next stored match.
|
|
203
|
+
if "invalid password-proof CRC" in str(exc):
|
|
204
|
+
raise WebRuntimeAuthenticationError("1C web authentication failed: incorrect user or password") from exc
|
|
205
|
+
raise
|
|
199
206
|
except (IndexError, KeyError, ValueError, TypeError) as exc:
|
|
200
207
|
raise WebRuntimeProbeError("1C web authentication returned an unsupported password-proof response") from exc
|
|
201
208
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|