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.
Files changed (64) hide show
  1. {answer42-0.5.6 → answer42-0.5.7}/PKG-INFO +1 -1
  2. {answer42-0.5.6 → answer42-0.5.7}/pyproject.toml +1 -1
  3. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  4. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  5. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/credentials.py +25 -0
  6. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/server.py +56 -18
  7. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/webclient_probe.py +7 -0
  8. {answer42-0.5.6 → answer42-0.5.7}/.gitignore +0 -0
  9. {answer42-0.5.6 → answer42-0.5.7}/LICENSE +0 -0
  10. {answer42-0.5.6 → answer42-0.5.7}/README.md +0 -0
  11. {answer42-0.5.6 → answer42-0.5.7}/credentials.example.json +0 -0
  12. {answer42-0.5.6 → answer42-0.5.7}/docs/agent-installation.md +0 -0
  13. {answer42-0.5.6 → answer42-0.5.7}/docs/architecture.md +0 -0
  14. {answer42-0.5.6 → answer42-0.5.7}/docs/assets/answer42-logo.png +0 -0
  15. {answer42-0.5.6 → answer42-0.5.7}/docs/assets/platform42-logo.svg +0 -0
  16. {answer42-0.5.6 → answer42-0.5.7}/docs/installation.md +0 -0
  17. {answer42-0.5.6 → answer42-0.5.7}/scripts/build_cf.py +0 -0
  18. {answer42-0.5.6 → answer42-0.5.7}/scripts/build_pages.py +0 -0
  19. {answer42-0.5.6 → answer42-0.5.7}/scripts/e2e_http_smoke.py +0 -0
  20. {answer42-0.5.6 → answer42-0.5.7}/scripts/e2e_stable.py +0 -0
  21. {answer42-0.5.6 → answer42-0.5.7}/scripts/openclaw_mcp_autoreload.py +0 -0
  22. {answer42-0.5.6 → answer42-0.5.7}/scripts/rag_cli.py +0 -0
  23. {answer42-0.5.6 → answer42-0.5.7}/scripts/read_webclient_start_info.py +0 -0
  24. {answer42-0.5.6 → answer42-0.5.7}/src/cf/ConfigDumpInfo.xml +0 -0
  25. {answer42-0.5.6 → answer42-0.5.7}/src/cf/Configuration.xml +0 -0
  26. {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  27. {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
  28. {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
  29. {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
  30. {answer42-0.5.6 → answer42-0.5.7}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  31. {answer42-0.5.6 → answer42-0.5.7}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  32. {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
  33. {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
  34. {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
  35. {answer42-0.5.6 → answer42-0.5.7}/src/client_cf/ConfigDumpInfo.xml +0 -0
  36. {answer42-0.5.6 → answer42-0.5.7}/src/client_cf/Configuration.xml +0 -0
  37. {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
  38. {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
  39. {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
  40. {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
  41. {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
  42. {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
  43. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/__init__.py +0 -0
  44. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/assets/__init__.py +0 -0
  45. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/auth.py +0 -0
  46. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/bridge.py +0 -0
  47. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/os_support.py +0 -0
  48. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/platform.py +0 -0
  49. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/protocol.py +0 -0
  50. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/__init__.py +0 -0
  51. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/detect.py +0 -0
  52. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/dump.py +0 -0
  53. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/model.py +0 -0
  54. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/parsers.py +0 -0
  55. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/service.py +0 -0
  56. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/store.py +0 -0
  57. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/summary_backend.py +0 -0
  58. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/rag/summary_vector.py +0 -0
  59. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/recorder.py +0 -0
  60. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/release_helper.py +0 -0
  61. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/runtime.py +0 -0
  62. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/skill_installer.py +0 -0
  63. {answer42-0.5.6 → answer42-0.5.7}/src/mcp_1c/win_desktop.py +0 -0
  64. {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.6
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.6"
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"
@@ -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
- credential_ref = _resolve_credential_reference(base_url)
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, username, password)
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
- if not username or not password:
5375
- return {"attempted": False, "status": "credentials_not_provided"}
5376
- try:
5377
- result = await asyncio.to_thread(probe_web_runtime, base_url, username, password)
5378
- return {"attempted": True, "status": "ok", "runtime": result}
5379
- except WebRuntimeAuthenticationError as exc:
5380
- return {
5381
- "attempted": True,
5382
- "status": "authentication_failed",
5383
- "authentication_failed": True,
5384
- "message": str(exc),
5385
- }
5386
- except (WebRuntimeProbeError, requests.RequestException) as exc:
5387
- logging.info("Web runtime probe skipped for %s: %s", _normalize_base_url(base_url), exc)
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": str(exc),
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