Files
mnote/.codex/reasonix-tasks/results/batch-j-worker-a-http-proxy-fallback.md
T

133 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Batch J Worker A:旧 HTTP proxy / page AI fallback 调用链审查
> 创建时间:2026-05-21
>
> 只读审查,不修改代码。
## 1. 读取过的关键文件
| 文件 | 行数 | 内容 |
|------|------|------|
| `rust/crates/mnote-web/src/routes/hermes_client.rs` | 8202 | 首页面 AI Hermes 路由:session/run/tool 的 proxy 和 ACP 双路径 |
| `rust/crates/mnote-web/src/page_ai_workflow.rs` | — | **不存在**(不在根目录,在 `routes/` 下) |
| `rust/crates/mnote-web/src/routes/page_ai_workflow.rs` | 500+ | 页面 AI block-edit fast path |
| `rust/crates/mnote-web/src/acp_runtime.rs` | 500+ | ACP runtime manager |
| `rust/crates/mnote-web/src/acp_session_manager.rs` | 700+ | ACP session lifecycle |
| `rust/crates/mnote-web/src/routes/mod.rs` | 200+ | 全路线由注册 |
| `rust/crates/mnote-web/src/routes/compat.rs` | 200+ | 旧 `/api/ai-agent/run` 退役 guard |
## 2. 调用链分析
### 2.1 旧 HTTP proxy 路径
**判断函数:** `hermes_client.rs:2844` `hermes_http_proxy_enabled()`
```rust
fn hermes_http_proxy_enabled() -> bool {
["MNOTE_WEB_ENABLE_HERMES_HTTP_PROXY", "MNOTE_ENABLE_HERMES_HTTP_PROXY"]
.into_iter().find_map(env_or_dotenv)
.map(|v| matches!(v.trim().to_ascii_lowercase(), "1" | "true" | "yes"))
.unwrap_or(false)
}
```
**默认关闭**`unwrap_or(false)`)。HTTP proxy 仅在显式设 `MNOTE_WEB_ENABLE_HERMES_HTTP_PROXY=true` 时启用。
**上游 URL 来源:** `configured_upstream()``hermes_client.rs:2813`
- 检查 `MNOTE_WEB_HERMES_UPSTREAM_URL``MNOTE_HERMES_UPSTREAM_URL``MNOTE_HERMES_API_BASE_URL`
- 均不存在时返回 `None`
**`is_acp_profile()` 路由逻辑**`hermes_client.rs:2871`):
```rust
fn is_acp_profile(profile: &str) -> bool {
if !hermes_http_proxy_enabled() { return true; } // 默认全走 ACP
if profile == "reasonix" || profile == "hermes" { return true; }
// 还可通过 MNOTE_WEB_{PROFILE}_RUNTIME_TYPE=acp 环境变量控制
...
}
```
### 2.2 各路由入口分类
| Route | Handler | HTTP proxy 条件 | 默认走 | 分类 |
|-------|---------|-----------------|--------|------|
| `GET /client/sessions` | `list_sessions` | `!is_acp_profile(profile) && configured_upstream_for_profile(profile).is_some()` | **ACP** | `compat` |
| `GET /client/sessions/search` | `search_sessions` | 同上 | **ACP** | `compat` |
| `GET /client/gateway/health` | `gateway_health` | `!is_acp_profile(&profile)` → HTTP proxy | **ACP**(对于非 ACP profile 走 HTTP proxy | `compat` |
| `POST /client/runs` | `create_run` | `configured_upstream_for_profile(&registration.profile).is_some()` | **ACP** | `compat` |
| `GET /client/events/{run_id}` | `stream_events` | 先查 `acp_runtime_for_run()`,再 fallback `configured_upstream_for_profile()` | **ACP** | `compat` |
| `POST /client/runs/{run_id}/abort` | `abort_run` | 同上 | **ACP** | `compat` |
| `GET /client/models` | `list_models` | 直接 `configured_upstream()` | HTTP proxy(与 ACP 无关的 model 列表) | `compat` |
| 队列自动处理 | `auto_start_next_queued_run`line 5377 | `configured_upstream_for_profile(queued.profile)` | **ACP** | `compat` |
| `POST /api/ai-agent/run` | `compat::next_ai_agent_run` | — | **始终返回 410 GONE** | `retired` |
| `POST /api/page-ai/block-edit-workflow` | `page_ai_workflow::block_edit_workflow` | 绕过 ACP,直接调模型 API + `execute_mnote_tool_call` | debug-only fast path | `debug-only` |
### 2.3 page_ai_workflow 是否绕过 ACP / tool executor
**调用链:**
```
POST /api/page-ai/block-edit-workflow
→ looks_like_block_edit(message) ← 中文关键词匹配
→ call_block_edit_model() ← 直调 DeepSeek/etc APIHTTP
→ extract_markdown_plan_from_model_text()
→ 构造 ToolCallInput { tool_name: "mnote.doc.markdown_edit", capability_scope: ["block.write","page.write"], ... }
→ hermes_tools::execute_mnote_tool_call() ← 共享 tool executor
→ is_mnote_tool_disabled() & ensure_write_authorized() & 实际执行
```
**结论:**
1. **不经过旧 HTTP proxy** ✓ — 没有调用 `configured_upstream_for_profile`
2. **不经过 ACP session manager** — 直接调用 `execute_mnote_tool_call`,没有 `session/new` + `session/prompt` 生命周期
3. **但经过共享 tool executor** — 受 `is_mnote_tool_disabled()``ensure_write_authorized()``is_shared_read_scope()` 等守卫保护
4. **设计意图**`7-15` 设计文档说明 "local-first 普通正文编辑默认不经过它",该路径是给合模型快速做 search/replace 块编辑的 fast path,不是常规 AI 执行路径
### 2.4 `compat::next_ai_agent_run` 状态
`routes/compat.rs:10``next_ai_agent_run()` 始终返回 `410 GONE` + 消息"旧 /api/ai-agent/run 页面 AI 主链已退场"。**已确认退役**,无 P0 风险。
## 3. 各入口分类汇总
| 分类 | 条目 | 说明 |
|------|------|------|
| `default` | 无 | HTTP proxy 默认关闭;ACP 是默认 runtime |
| `compat` | `list_sessions`, `search_sessions`, `gateway_health`, `create_run`, `stream_events`, `abort_run`, `list_models`, 队列处理 | 仅当 `MNOTE_WEB_ENABLE_HERMES_HTTP_PROXY=true` 时激活;不影响正常用户 |
| `debug-only` | `page_ai_workflow::block_edit_workflow` | bypass ACP session,但受工具 executor 守卫保护 |
| `retired` | `compat::next_ai_agent_run` | 已返回 410,无降级路径 |
| `未被调用` | `configured_runtime_for_profile()`line 3368 | 标记 `#[allow(dead_code)]` |
## 4. P0 缺口评估
**当前没有 P0 必须修复的缺口。**
理由:
1. HTTP proxy 默认关闭(`hermes_http_proxy_enabled() = false`
2. `compat::next_ai_agent_run` 已正确返回 410
3. `page_ai_workflow` 经过共享 tool executor 守卫,不绕过权限检查
4. 所有路由的第一分支都是 ACP
## 5. 建议的后续修改范围
| 项 | 建议 | 紧急程度 |
|----|------|---------|
| HTTP proxy 完整退役 | 删除 `configured_upstream()` / `configured_upstream_for_profile()` / `proxy_json()` 相关代码 | P2cleanup |
| `list_models` 从 HTTP proxy 迁移 | 改为 ACP 的 `session/initialize` 返回 model 信息 | P2(依赖 ACP 协议增强) |
| `page_ai_workflow` 检查驱动 | 确保测试覆盖其 bypass ACP session 的行为的非期望路径(`task-page-block-ai-smoke.js` → 已存在) | 已有 |
| 旧环境变量清理 | 文档标记 `MNOTE_WEB_HERMES_UPSTREAM_URL` 等为退役 | P2 |
## 6. 建议测试
```
# 确认 HTTP proxy 默认关闭(infra 测试)
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web hermes_http_proxy -- --test-threads=1
# 确认 compat 路由返回 410(已存在)
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web direct_ai_agent_run_returns_legacy_retired_guard -- --test-threads=1
# 确认 page_ai_workflow 受守卫保护(已存在)
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web block_edit_workflow_respects_disabled_markdown_edit_tool -- --test-threads=1
```