fix(desktop): ship the VC++ runtime the Windows voice wheels need

onnxruntime (wake word) and ctranslate2 (local speech-to-text) link against
msvcp140.dll and msvcp140_1.dll, which the managed CPython does not ship.
On Windows without the system VC++ Redistributable both imports failed with
"DLL load failed: The specified module could not be found", so "Hey Hermes"
and voice chat transcription never worked.

- Bundle the Microsoft-signed VC143 CRT set (14.44.35211, matching the
  managed CPython's vcruntime140) as a Windows extra resource with a SHA-256
  manifest.
- Before every local backend start, copy missing or mismatching CRT files
  beside the managed python.exe (the process image directory, searched first),
  verifying digests; never overwrite the interpreter's own vcruntime and never
  block boot. Existing installs heal on their next launch.
- Report an unloadable native voice wheel as a VC++ runtime problem in the
  wake-word requirements, local transcription and `hermes doctor`, instead of
  treating installed package metadata as proof the wheel loads.
- Bump AITURK IDE to 1.0.0-beta.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
yilsemandClaude Opus 5 committed 2026-09-15 19:46:39 +03:00
1 parent 9a1d831d4b
commit 233885d3a7
20 files changed
+684 -5

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
"""Detect native voice wheels that are installed but cannot load on Windows.
Package metadata (what ``tools.lazy_deps`` checks) reports onnxruntime and
ctranslate2 as installed even when their DLLs cannot load because the
Microsoft Visual C++ runtime (msvcp140.dll) is missing. A lazy reinstall cannot
fix that, so callers need to tell the two cases apart. Importing the module in
a child interpreter does so without risking the calling process.
"""
from __future__ import annotations
import functools
import os
import re
import subprocess
import sys
from typing import Optional
_MODULE_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_.]*$")
# Windows reports the same loader failure in the user's UI language.
_DLL_LOAD_MARKERS = (
"dll load failed",
"could not find module",
"specified module could not be found",
"belirtilen modül bulunamadı",
"[winerror 126]",
)
VC_RUNTIME_HINT = (
"Ses bileşeni yüklenemedi: Microsoft Visual C++ çalışma zamanı (msvcp140.dll) eksik. "
"AITURK IDE'yi kapatıp yeniden açın; IDE eksik dosyaları otomatik olarak geri yükler."
)
def looks_like_dll_load_failure(message: str) -> bool:
text = (message or "").lower()
return any(marker in text for marker in _DLL_LOAD_MARKERS)
@functools.lru_cache(maxsize=None)
def native_load_error(module: str) -> Optional[str]:
"""Return the DLL load error for *module* on Windows, otherwise None.
None also covers "not installed" and inconclusive probes: this helper only
answers "installed but its native libraries cannot load". Cached per
process because the runtime cannot change under a running backend (the
desktop restores it before the backend starts).
"""
if sys.platform != "win32":
return None
if not _MODULE_RE.match(module):
raise ValueError(f"invalid module name: {module!r}")
try:
proc = subprocess.run(
[sys.executable, "-c", f"import {module}"],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=120,
env=dict(os.environ, PYTHONIOENCODING="utf-8"),
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
except (OSError, subprocess.TimeoutExpired):
return None
stderr = proc.stderr or ""
if proc.returncode == 0 or not looks_like_dll_load_failure(stderr):
return None
lines = [line.strip() for line in stderr.splitlines() if line.strip()]
return lines[-1] if lines else f"import {module} failed"
def voice_runtime_error_message(error: str) -> str:
return f"{VC_RUNTIME_HINT} ({error})"