Skip to content

修复 uv虚拟环境下,第一次启动报错,并且无法安装插件的问题。 - #1523

Open
436and251 wants to merge 2 commits into
lss233:masterfrom
436and251:master
Open

修复 uv虚拟环境下,第一次启动报错,并且无法安装插件的问题。#1523
436and251 wants to merge 2 commits into
lss233:masterfrom
436and251:master

Conversation

@436and251

@436and251 436and251 commented Aug 9, 2026

Copy link
Copy Markdown

clone之后,使用python3.12的新uv虚拟环境运行的项目。

Summary by Sourcery

在基于 uv 的环境中改进插件管理和系统更新的兼容性,并更新 MCP 集成以适配更新的库版本。

新功能:

  • 在未配置密钥时,自动生成安全的默认 Web 密钥(secret key)。
  • 通过 uv 或 pip 支持插件的安装、卸载以及后端更新,目标为当前 Python 环境。

错误修复:

  • 修复首次登录问题,确保在使用默认密钥且密码文件为空时,JWT 令牌能够被正确签发和验证。
  • 修复在 uv 虚拟环境下运行时插件安装和卸载失败的问题。
  • 确保 MCP 工具、提示(prompts)和资源缓存更新在处理更新版本 MCP 中的“method not found”错误时行为正确。

增强:

  • 将包管理器命令的构建集中到一个共享的实用工具中,以便在插件加载和系统更新流程中复用。

构建:

  • 限制 MCP 和 pygls 依赖版本范围,以保证与更新后的 MCP 错误处理逻辑兼容。

测试:

  • 添加一个身份验证测试,用于覆盖在使用默认生成的 Web 密钥时首次登录行为。
Original summary in English

Summary by Sourcery

Improve plugin management and system update compatibility with uv-based environments and update MCP integration for newer library versions.

New Features:

  • Automatically generate a secure default web secret key when none is configured.
  • Support plugin installation, uninstallation, and backend updates via uv or pip, targeting the current Python environment.

Bug Fixes:

  • Fix first-time login so JWT tokens are correctly issued and validated when using the default secret and an empty password file.
  • Fix plugin installation and uninstallation failures when running under uv virtual environments.
  • Ensure MCP tools, prompts, and resources cache updates correctly handle "method not found" errors with newer MCP versions.

Enhancements:

  • Centralize construction of package manager commands into a shared utility for reuse across plugin loading and system update flows.

Build:

  • Constrain MCP and pygls dependency versions to ranges compatible with the updated MCP error handling.

Tests:

  • Add an authentication test covering first login behavior with a default-generated web secret key.

@sourcery-ai

sourcery-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

此 PR 修复了在基于 uv 的 Python 3.12 环境中插件安装/卸载的问题,改进了系统更新安装以使用相同的包管理器抽象,加强了针对较新 MCP 版本的错误处理,生成了安全的默认 Web secret key 以保证首次登录可靠工作,并添加/调整了测试和依赖以匹配这些行为。

在插件和系统更新中使用 build_package_manager_command 的序列图

sequenceDiagram
    title build_package_manager_command selection for uv or pip
    participant PluginLoader
    participant SystemRoutes
    participant build_package_manager_command
    participant uv
    participant pip

    PluginLoader->>build_package_manager_command: build_package_manager_command("install", index_url, package_spec)
    SystemRoutes->>build_package_manager_command: build_package_manager_command("install", backend_file)

    alt [uv available]
        build_package_manager_command->>uv: shutil.which("uv")
        uv-->>build_package_manager_command: uv path
        build_package_manager_command-->>PluginLoader: [uv, "pip", action, "--python", sys.executable, args]
        build_package_manager_command-->>SystemRoutes: [uv, "pip", action, "--python", sys.executable, args]
    else [uv not available]
        build_package_manager_command->>pip: importlib.util.find_spec("pip")
        pip-->>build_package_manager_command: pip module found
        build_package_manager_command-->>PluginLoader: [sys.executable, "-m", "pip", action, args]
        build_package_manager_command-->>SystemRoutes: [sys.executable, "-m", "pip", action, args]
    end

    PluginLoader->>PluginLoader: install_plugin / uninstall_plugin uses cmd
    SystemRoutes->>SystemRoutes: perform_update uses cmd
Loading

首次 Web 登录流程的序列图

sequenceDiagram
    title First time login flow with secret_key and AuthService
    actor User
    participant WebAuthRoutes
    participant AuthService

    User->>WebAuthRoutes: POST /login
    WebAuthRoutes->>AuthService: is_first_time()
    alt [first time]
        AuthService-->>WebAuthRoutes: True
        WebAuthRoutes->>AuthService: create_access_token(timedelta_days_1)
        AuthService-->>WebAuthRoutes: access_token
        WebAuthRoutes->>AuthService: save_password(login_data.password)
        WebAuthRoutes-->>User: TokenResponse(access_token)
    else [not first time]
        AuthService-->>WebAuthRoutes: False
        WebAuthRoutes->>AuthService: verify_password(login_data.password)
        AuthService-->>WebAuthRoutes: verification_result
        opt [password valid]
            WebAuthRoutes->>AuthService: create_access_token(timedelta_days_1)
            AuthService-->>WebAuthRoutes: access_token
            WebAuthRoutes-->>User: TokenResponse(access_token)
        end
    end
Loading

文件级改动

Change Details Files
抽象插件包管理以支持基于 uv 的虚拟环境,并在 uv 不可用时回退到 pip。
  • 引入 build_package_manager_command 工具函数,在当前解释器下优先使用 uv pip,在其不可用时回退到 python -m pip,并在卸载操作中加入 -y
  • 重构 PluginLoader 中的插件安装、卸载和更新流程,通过新工具函数构建命令,并在集中位置构建包规格。
  • 在系统路由中,后端更新安装使用同一个包管理器命令构建器。
kirara_ai/plugin_manager/utils.py
kirara_ai/plugin_manager/plugin_loader.py
kirara_ai/web/api/system/routes.py
通过使用非空且安全的默认 secret,并验证首次登录行为,确保 Web 认证在首次启动时能正常工作。
  • 将 WebConfig.secret_key 的默认值从空字符串改为通过 secrets.token_hex 安全生成的随机 32 字节十六进制密钥。
  • 调整登录路由逻辑,使得在首次登录时先创建访问令牌再持久化密码,以符合首次使用语义。
  • 添加一个认证测试,引导临时配置并验证首次登录成功,以及已签发的令牌可以被 AuthService 校验。
kirara_ai/config/global_config.py
kirara_ai/web/auth/routes.py
tests/web/auth/test_auth.py
使 MCP 模块错误处理兼容较新 MCP 版本,并收紧依赖范围。
  • 在所有缓存更新路径中,更新 MCP 错误检查逻辑,将 e.error.codetypes.METHOD_NOT_FOUND 进行比较,而不是通过字符串匹配错误内容。
  • 在 pyproject.toml 中,将 mcp 和 pygls 依赖限制在与新错误结构兼容的特定版本范围内。
kirara_ai/mcp_module/manager.py
pyproject.toml

Tips and commands

与 Sourcery 交互

  • 触发新评审: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub Issue: 通过回复评审评论让 Sourcery 从该评论创建一个 issue。也可以在评审评论中回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题的任意位置写入 @sourcery-ai,即可在任意时间生成标题。也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 总结: 在 Pull Request 正文任意位置写入 @sourcery-ai summary,即可在任意时间在指定位置生成 PR 总结。也可以在 Pull Request 中评论 @sourcery-ai summary 来(重新)生成总结。
  • 生成评审者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成评审者指南。
  • 解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理了所有评论且不想再看到它们,这会很有用。
  • 取消所有 Sourcery 评审: 在 Pull Request 中评论 @sourcery-ai dismiss,即可取消所有现有的 Sourcery 评审。如果你想从一个新的评审开始,这尤其有用——别忘了评论 @sourcery-ai review 来触发新的评审!

自定义你的体验

访问你的 dashboard 来:

  • 启用或禁用评审功能,例如 Sourcery 生成的 Pull Request 总结、评审者指南等。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审说明。
  • 调整其他评审设置。

获取帮助

Original review guide in English

Reviewer's Guide

This PR fixes plugin installation/uninstallation in uv-based Python 3.12 environments, improves system update installation to use the same package manager abstraction, hardens MCP error handling against newer mcp versions, generates a secure default web secret key to make first login work reliably, and adds/adjusts tests and dependencies to match these behaviors.

Sequence diagram for build_package_manager_command usage in plugin and system updates

sequenceDiagram
    title build_package_manager_command selection for uv or pip
    participant PluginLoader
    participant SystemRoutes
    participant build_package_manager_command
    participant uv
    participant pip

    PluginLoader->>build_package_manager_command: build_package_manager_command("install", index_url, package_spec)
    SystemRoutes->>build_package_manager_command: build_package_manager_command("install", backend_file)

    alt [uv available]
        build_package_manager_command->>uv: shutil.which("uv")
        uv-->>build_package_manager_command: uv path
        build_package_manager_command-->>PluginLoader: [uv, "pip", action, "--python", sys.executable, args]
        build_package_manager_command-->>SystemRoutes: [uv, "pip", action, "--python", sys.executable, args]
    else [uv not available]
        build_package_manager_command->>pip: importlib.util.find_spec("pip")
        pip-->>build_package_manager_command: pip module found
        build_package_manager_command-->>PluginLoader: [sys.executable, "-m", "pip", action, args]
        build_package_manager_command-->>SystemRoutes: [sys.executable, "-m", "pip", action, args]
    end

    PluginLoader->>PluginLoader: install_plugin / uninstall_plugin uses cmd
    SystemRoutes->>SystemRoutes: perform_update uses cmd
Loading

Sequence diagram for the first-time web login flow

sequenceDiagram
    title First time login flow with secret_key and AuthService
    actor User
    participant WebAuthRoutes
    participant AuthService

    User->>WebAuthRoutes: POST /login
    WebAuthRoutes->>AuthService: is_first_time()
    alt [first time]
        AuthService-->>WebAuthRoutes: True
        WebAuthRoutes->>AuthService: create_access_token(timedelta_days_1)
        AuthService-->>WebAuthRoutes: access_token
        WebAuthRoutes->>AuthService: save_password(login_data.password)
        WebAuthRoutes-->>User: TokenResponse(access_token)
    else [not first time]
        AuthService-->>WebAuthRoutes: False
        WebAuthRoutes->>AuthService: verify_password(login_data.password)
        AuthService-->>WebAuthRoutes: verification_result
        opt [password valid]
            WebAuthRoutes->>AuthService: create_access_token(timedelta_days_1)
            AuthService-->>WebAuthRoutes: access_token
            WebAuthRoutes-->>User: TokenResponse(access_token)
        end
    end
Loading

File-Level Changes

Change Details Files
Abstract plugin package management to support uv-based virtual environments and fall back to pip when uv is unavailable.
  • Introduce build_package_manager_command utility that prefers uv pip with the current interpreter and falls back to python -m pip, including -y for uninstall operations.
  • Refactor plugin installation, uninstallation, and update flows in PluginLoader to construct commands via the new utility and build package specs centrally.
  • Use the same package manager command builder for backend update installation in system routes.
kirara_ai/plugin_manager/utils.py
kirara_ai/plugin_manager/plugin_loader.py
kirara_ai/web/api/system/routes.py
Ensure web authentication works on first startup by having a non-empty, secure default secret and validating first-login behavior.
  • Change WebConfig.secret_key from an empty-string default to a securely generated random 32-byte hex key via secrets.token_hex.
  • Adjust login route logic so that on first-time login an access token is created before persisting the password, aligning with first-use semantics.
  • Add an auth test that bootstraps a temporary config and verifies that first login succeeds and that the issued token can be validated by AuthService.
kirara_ai/config/global_config.py
kirara_ai/web/auth/routes.py
tests/web/auth/test_auth.py
Make MCP module error handling compatible with newer mcp versions and tighten dependency ranges.
  • Update MCP error checking to compare e.error.code against types.METHOD_NOT_FOUND instead of string matching on the error, in all cache update paths.
  • Constrain mcp and pygls dependencies in pyproject.toml to specific version ranges compatible with the new error structures.
kirara_ai/mcp_module/manager.py
pyproject.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些整体反馈:

  • 在首次登录的分支中,现在在调用 save_password 之前就创建了访问令牌;建议在发放令牌之前保持副作用(持久化密码哈希),这样如果保存失败,系统就不会处于“部分初始化但已有有效令牌”的状态。
  • 新的 WebConfig.secret_keydefault_factory 会在每次进程启动时生成一个全新的随机密钥,这会在重启后使所有现有 JWT 失效;如果系统需要长期有效的令牌,你可能需要确保密钥在重启之间保持稳定(例如从配置或存储中加载)。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- 在首次登录的分支中,现在在调用 `save_password` 之前就创建了访问令牌;建议在发放令牌之前保持副作用(持久化密码哈希),这样如果保存失败,系统就不会处于“部分初始化但已有有效令牌”的状态。
- 新的 `WebConfig.secret_key``default_factory` 会在每次进程启动时生成一个全新的随机密钥,这会在重启后使所有现有 JWT 失效;如果系统需要长期有效的令牌,你可能需要确保密钥在重启之间保持稳定(例如从配置或存储中加载)。

## Individual Comments

### Comment 1
<location path="kirara_ai/plugin_manager/utils.py" line_range="8-10" />
<code_context>
 from typing import Any, Dict, Optional


+def build_package_manager_command(action: str, *args: str) -> list[str]:
+    """Build a uv/pip command targeting the current Python environment."""
+    if uv := shutil.which("uv"):
+        return [uv, "pip", action, "--python", sys.executable, *args]
+    if importlib.util.find_spec("pip") is not None:
</code_context>
<issue_to_address>
**issue:** 通过 `uv` 执行卸载是交互式的,在非交互环境中可能会挂起。

在 `uv` 分支中,`build_package_manager_command` 构造的是 `uv pip uninstall`,但没有提供等价于 pip `-y` 的非交互选项。由于 `PluginLoader.uninstall_plugin` 和 Web 更新路径在没有 TTY 的环境中运行,这会导致 `uv pip uninstall` 阻塞等待确认。请添加合适的 `uv` 标志或对齐现有的 `assume_yes` 行为,以确保在两种后端中卸载流程都保持非交互式。
</issue_to_address>

### Comment 2
<location path="kirara_ai/config/global_config.py" line_range="112-113" />
<code_context>
     host: str = Field(default="127.0.0.1", description="Web服务绑定的IP地址")
     port: int = Field(default=8080, description="Web服务端口号")
-    secret_key: str = Field(default="", description="Web服务的密钥,用于JWT等加密")
+    secret_key: str = Field(
+        default_factory=lambda: secrets.token_hex(32),
+        description="Web服务的密钥,用于JWT等加密",
+    )
</code_context>
<issue_to_address>
**🚨 suggestion (security):** 如果不进行持久化,自动生成 `secret_key` 会在每次重启时使现有令牌失效。

由于 `default_factory` 会在未显式提供值时,每次实例化配置都生成新的 `secret_key`,当密钥没有被持久化时,所有现有的 JWT/会话将在每次重启后失效。如果你只希望在初始设置时进行一次密钥轮换,需要确保生成的密钥被存储并复用(例如持久化到磁盘),或者在配置中强制要求显式提供 `secret_key`,而不是在没有值时静默生成新密钥。

Suggested implementation:

```python
import secrets
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, ConfigDict, Field, model_validator
class WebConfig(BaseModel):
    host: str = Field(default="127.0.0.1", description="Web服务绑定的IP地址")
    port: int = Field(default=8080, description="Web服务端口号")
    secret_key: str = Field(
        ...,
        description="Web服务的密钥,用于JWT等加密(必须显式配置,避免重启时密钥变化导致JWT/会话失效)",
    )
    password_file: str = Field(
        default="./data/web/password.hash", description="密码哈希存储路径"
    )

```

1. 如果 `secrets` 没有在 `global_config.py` 的其他位置使用,可以安全地移除 `import secrets` 行以避免未使用的导入。
2. 确保在所有实例化 `WebConfig` 的地方(例如从配置文件或环境变量加载)都显式提供 `secret_key`;否则由于它现在是必填字段,pydantic 将抛出校验错误。
3. 如果之后决定支持“一次自动生成并持久化”的方案,需要在该模型之外添加额外逻辑,仅在首次生成密钥时存储它(例如写入配置文件或密钥库),并在之后将存储的值传入 `WebConfig`

Sourcery 对开源项目是免费的——如果你觉得这些评审有帮助,请考虑分享它们 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据这些反馈改进后续评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In the first-time login branch, the access token is now created before save_password is called; consider keeping the side-effect (persisting the password hash) before issuing a token so a failure to save cannot leave the system in a partially-initialized state with a valid token.
  • The new WebConfig.secret_key default_factory generates a fresh random key on each process start, which will invalidate all existing JWTs after restart; if long-lived tokens are expected, you may want to ensure the secret is stable across restarts (e.g., loaded from configuration or storage).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the first-time login branch, the access token is now created before `save_password` is called; consider keeping the side-effect (persisting the password hash) before issuing a token so a failure to save cannot leave the system in a partially-initialized state with a valid token.
- The new `WebConfig.secret_key` default_factory generates a fresh random key on each process start, which will invalidate all existing JWTs after restart; if long-lived tokens are expected, you may want to ensure the secret is stable across restarts (e.g., loaded from configuration or storage).

## Individual Comments

### Comment 1
<location path="kirara_ai/plugin_manager/utils.py" line_range="8-10" />
<code_context>
 from typing import Any, Dict, Optional


+def build_package_manager_command(action: str, *args: str) -> list[str]:
+    """Build a uv/pip command targeting the current Python environment."""
+    if uv := shutil.which("uv"):
+        return [uv, "pip", action, "--python", sys.executable, *args]
+    if importlib.util.find_spec("pip") is not None:
</code_context>
<issue_to_address>
**issue:** Uninstall via `uv` will be interactive and can hang in non-interactive contexts.

In the `uv` branch, `build_package_manager_command` builds `uv pip uninstall` without a non-interactive equivalent to pip’s `-y`. Since `PluginLoader.uninstall_plugin` and the web update path run this without a TTY, `uv pip uninstall` can block waiting for confirmation. Please add the appropriate `uv` flag or mirror the existing `assume_yes` behaviour so uninstall stays non-interactive for both backends.
</issue_to_address>

### Comment 2
<location path="kirara_ai/config/global_config.py" line_range="112-113" />
<code_context>
     host: str = Field(default="127.0.0.1", description="Web服务绑定的IP地址")
     port: int = Field(default=8080, description="Web服务端口号")
-    secret_key: str = Field(default="", description="Web服务的密钥,用于JWT等加密")
+    secret_key: str = Field(
+        default_factory=lambda: secrets.token_hex(32),
+        description="Web服务的密钥,用于JWT等加密",
+    )
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Auto-generating `secret_key` can invalidate existing tokens on each restart if not persisted.

Because `default_factory` generates a new `secret_key` whenever the config is instantiated without an explicit value, all existing JWTs/sessions will be invalidated on each restart when the key isn’t persisted. If you only want key rotation on initial setup, ensure the generated key is stored and reused (e.g., persisted to disk), or require an explicit `secret_key` in configuration instead of silently generating a new one.

Suggested implementation:

```python
import secrets
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, ConfigDict, Field, model_validator
class WebConfig(BaseModel):
    host: str = Field(default="127.0.0.1", description="Web服务绑定的IP地址")
    port: int = Field(default=8080, description="Web服务端口号")
    secret_key: str = Field(
        ...,
        description="Web服务的密钥,用于JWT等加密(必须显式配置,避免重启时密钥变化导致JWT/会话失效)",
    )
    password_file: str = Field(
        default="./data/web/password.hash", description="密码哈希存储路径"
    )

```

1. If `secrets` is not used elsewhere in `global_config.py`, you can safely remove the `import secrets` line to avoid an unused import.
2. Ensure that wherever `WebConfig` is instantiated (e.g., loading from a config file or environment variables), `secret_key` is always provided; otherwise pydantic will raise a validation error because it is now required.
3. If you later decide to support one-time auto-generation with persistence, you'll need additional logic outside this model to generate the key once, store it (e.g., in a config file or keystore), and pass the stored value into `WebConfig`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread kirara_ai/plugin_manager/utils.py
Comment thread kirara_ai/config/global_config.py Outdated
…JWT 失效。

现在改为:
WebConfig.secret_key 默认保持为空。
应用启动时仅在密钥为空时生成 64 字符随机密钥。
立即通过现有 ConfigLoader 写入 data/config.yaml。
后续启动直接复用已有密钥,不再轮换。
已存在的非空密钥不会被覆盖。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant