fix(platforms): QR fallback tip uses uv against the running interpreter

The standard Hermes install is a `uv venv`, which ships no `pip` module:
`<venv>/bin/python -m pip install qrcode` fails with "No module named pip"
(the exact console output in #111695). Switch all four QR-fallback tips
(Feishu, WeCom, QQ onboarding, Telegram managed bot) to
`uv pip install --python <sys.executable> qrcode`, the form the in-tree
plugin install hints already use (hindsight, mem0), so the printed command
works as-is and still targets the active profile's interpreter.

Adds the Feishu-surface invariant test from #111696 and tightens the
Telegram test to the working command form.

Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>
This commit is contained in:
teknium1
2026-09-15 11:39:59 -07:00
committed by Teknium
parent 505b36b7af
commit c2b93ae5ac
6 changed files with 35 additions and 7 deletions

View File

@@ -98,7 +98,7 @@ def qr_register(timeout_seconds: int = 600) -> Optional[dict]:
print(f" Scan the QR code above, or open this URL directly:\n {url}")
else:
print(f" Open this URL in QQ on your phone:\n {url}")
print(f" Tip: {sys.executable} -m pip install qrcode to display a scannable QR code here")
print(f" Tip: uv pip install --python {sys.executable} qrcode to display a scannable QR code here")
print()
while time.monotonic() < deadline:
try:

View File

@@ -76,7 +76,7 @@ def render_qr_terminal(url: str) -> str:
def print_qr_code(url: str, *, include_link: bool = True) -> None:
"""Print a QR code to stdout, with URL fallback if qrcode is missing."""
print(render_qr_terminal(url) or f" (Install 'qrcode' for a scannable QR code: {sys.executable} -m pip install qrcode)")
print(render_qr_terminal(url) or f" (Install 'qrcode' for a scannable QR code: uv pip install --python {sys.executable} qrcode)")
if include_link:
print(f" Link: {url}")

View File

@@ -4104,7 +4104,7 @@ def _qr_register_inner(*, initial_domain: str, timeout_seconds: int) -> Optional
print(f"\n Scan the QR code above, or open this URL directly:\n {qr_url}")
else:
print(f" Open this URL in Feishu / Lark on your phone:\n\n {qr_url}\n")
print(f" Tip: {sys.executable} -m pip install qrcode to display a scannable QR code here next time")
print(f" Tip: uv pip install --python {sys.executable} qrcode to display a scannable QR code here next time")
print()
result = _poll_registration(
device_code=begin["device_code"], interval=begin["interval"],

View File

@@ -675,7 +675,7 @@ def qr_scan_for_bot_info(*, timeout_seconds: int = _QR_POLL_TIMEOUT) -> Optional
print(f"\n Scan the QR code above, or open this URL directly:\n {page_url}")
except Exception:
print(f" Open this URL in WeCom on your phone:\n\n {page_url}\n")
print(f" Tip: {sys.executable} -m pip install qrcode to display a scannable QR code here next time")
print(f" Tip: uv pip install --python {sys.executable} qrcode to display a scannable QR code here next time")
print("\n Fetching configuration results...", end="", flush=True)
deadline = time.monotonic() + timeout_seconds
query_url = f"{_QR_QUERY_URL}?scode={urllib.parse.quote(scode)}"

View File

@@ -1,6 +1,7 @@
"""Tests for plugins.platforms.feishu.adapter — Feishu scan-to-create registration."""
import json
import sys
from unittest.mock import patch, MagicMock
import pytest
@@ -221,6 +222,31 @@ class TestQrRegister:
result = qr_register()
assert result is None
@patch("plugins.platforms.feishu.adapter._render_qr", return_value=False)
@patch("plugins.platforms.feishu.adapter._poll_registration", return_value=None)
@patch("plugins.platforms.feishu.adapter._begin_registration")
@patch("plugins.platforms.feishu.adapter._init_registration")
def test_qr_fallback_tip_targets_active_interpreter(
self, mock_init, mock_begin, mock_poll, mock_render, capsys
):
"""#111695: the install tip must target the running venv (uv, no pip module)."""
from plugins.platforms.feishu.adapter import _qr_register_inner
mock_begin.return_value = {
"device_code": "dc_123",
"qr_url": "https://example.com/qr",
"user_code": "ABCD",
"interval": 1,
"expire_in": 60,
}
assert _qr_register_inner(initial_domain="feishu", timeout_seconds=60) is None
output = capsys.readouterr().out
assert "https://example.com/qr" in output
assert f"uv pip install --python {sys.executable} qrcode" in output
assert "Tip: pip install qrcode" not in output
# -- Contract: expected errors → None, unexpected errors → propagate --

View File

@@ -38,14 +38,16 @@ class TestQRCode:
def test_print_qr_code_tip_targets_active_interpreter(self, capsys):
# Regression for #111695: a bare `pip install` targets the wrong
# environment when Hermes runs in an isolated venv. The fallback tip
# must name the interpreter that is actually running.
# environment when Hermes runs in an isolated venv (which has no pip
# module at all). The fallback tip must name the interpreter that is
# actually running, via uv.
import sys
with patch.dict("sys.modules", {"qrcode": None}):
print_qr_code("https://t.me/newbot/Bot/test_bot")
captured = capsys.readouterr()
assert sys.executable in captured.out
assert f"uv pip install --python {sys.executable} qrcode" in captured.out
assert " pip install qrcode)" not in captured.out
class TestCreatePairing: