diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index f9fe87a..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,58 +0,0 @@ -# AGENTS.md - -## Collaboration Protocol (协作协议) - -本文件旨在规范 AI Agent(Claude Code、Antigravity 等)在本项目中的代码与文档协作行为。项目定位详见 [README.md](./README.md)。 - -- **Core Language**: Output MUST be in **Chinese (Simplified)** unless serving code/technical constraints. -- **Tone**: Professional, precise, and evidence-based. - -## Engineering Code of Conduct (工程行为准则) - -**Core Philosophy**: **Entropy Reduction (熵减)**. 通过上下文锚定、复用驱动与标准化流水线,对抗软件系统的无序熵增。 - -### 道 (Mindset - 认知心法) - -- **Context-Driven (上下文驱动)**: 上下文是第一性要素 (Context Quality First)。任何变更需建立在深度理解之上(CDD),拒绝基于关键字匹配的机械式修改。 -- **Minimal Intervention (最小干预)**: 遵循奥卡姆剃刀与 YAGNI 原则,仅实施必要的变更,推崇演进式设计 (Evolutionary Design) 而非过度设计。 -- **Evidence-Based (循证工程)**: 杜绝主观臆断,核心决策需以**最新**且**权威**的文献(IEEE 格式)为佐证,构建“设计-实现-验证”的完整反馈闭环,确保每一项工程行动都能产生可观测的反馈信号(测试、日志、监控),以验证假设并指导迭代。 -- **Systemic Integrity (系统完整性)**: 具备全局视角与二阶思维 (Second-Order Thinking),评估变更对上下游依赖及整个生态(Engine, Adapter, Agent, UI)的“涟漪效应”,不只关注变更的直接结果,更要预测“结果的结果”(如引入缓存导致的陈旧数据、重试机制引发的雪崩),优先保障整体稳定性与逻辑自洽。 -- **Knowledge Crystallization (知识结晶)**: 将系统视为有机体,通过将工程错误与 AI 失败案例转化为经验约束 (Negative Prompts) 和持久化知识,驱动系统的自我进化与持续熵减。 -- **Proactive Navigation (主动导航)**: 智能体不应止步于被动响应,需即时转化为“领航者”。在交付任务结果的同时,**必须**基于上下文预判并提出**下一步最佳行动建议 (Next Best Action)**,不仅交付“答案”,更要交付“路径”,消除用户决策的认知摩擦。 - -### 法 (Strategy - 架构原则) - -- **Plan-First Default (规划先行)**: 面对任何非琐碎任务(预估步骤 > 3 或涉及架构级决策),**必须**率先进入 Plan 模式。规划产物需明确界定:功能边界、边缘 Case 应对策略、与现有逻辑的交互锚点以及预计改动的爆炸半径。 -- **Subagent Strategy (子代理并发策略)**: 面对高复杂度命题,严禁主 Agent 单点统揽。应贯彻“算力换空间”思路,果断编排 Subagent 进行任务拆解与并行攻坚,主 Agent 的职责需严格收敛于上下文协同与最终成果的组装整合。 -- **Verification Before Done (交付前验证定式)**: 严禁在缺乏确凿运行证据的情况下标记任务为“已完成”。交付阶段**强制要求**提供客观自证材料:Diff 变更分析、测试用例覆盖、实施日志截图及核心链路边缘 Case 验证结果,并时刻以“方案是否能通过 Staff Engineer 严格审查”的视角自检。 -- **Reuse-Driven (复用驱动)**: Compose over Reinvent。系统变更**必须**主动参考业界经典设计模式与最佳实践。在进入实质性编码前,需率先对相关领域的成熟范式进行深度调研,并结合当前项目上下文输出充分的关联分析与方案梳理。坚决贯彻“拿来主义”,优先通过组合与集成来构建系统,防范闭门造车与重复造轮子。 -- **Boundary Management (边界管理)**: 严控模块/Agent 间的职责边界与契约,确保高内聚低耦合,防范隐式依赖穿透。 -- **Orthogonal Decomposition (正交分解)**: 坚持“正交地提取概念主体”。识别系统中独立变化的维度并进行解耦(如机制与策略分离),确保单一概念主体的变更具备局部性,避免逻辑纠缠。 -- **Single Source of Truth (单一事实源)**:严格维护唯一的权威定义源。引用时**必须**使用轻量级指针 (Link/ID) 而非数据副本 (Copy-Paste),从根源消除断裂 (Split-Brain) 风险。 - -### 术 (Tactics - 执行规范) - -- **Structured AI-Pair Pipeline (规范化 AI 结对流水线)**: 遵循 **Specification-Driven (规约驱动)** + **Context-Anchored (上下文锚定)** + **AI-Pair (AI 结对)** 模式,将开发固化为可审计的流水线,避免代码腐化为无法维护的“大泥球 (Big Ball of Mud)”。 -- **Operational Excellence (卓越运营)**: - 1. **Git Discipline**: 默认严禁调用 git commit;当用户显式要求提交时,一律使用 Claude Code 的自定义 Slash Command: `/commit-no-push` 进行操作(若非 Claude Code 运行环境,则读取 /commit-no-push 命令中的规则执行)。严禁执行 Rebase; - 2. **Temp Management**: 临时产物(执行计划等)一律收敛至 `.temp/` 并及时清理; - 3. **Link Validity**: 确保所有引用的 URL 可访问且具备明确的上下文价值; - 4. **Testing**: 统一在 tests/ 下维护测试用例,区分单元测试(unit)和集成测试(integration),所有测试的本地运行总时间控制在 3 min 以内; - 5. **Pre-commit Hooks**: 首次克隆仓库使用 `uv run pre-commit install` 激活本地 Git hooks,使 Ruff lint(含 auto-fix)、Ruff format 及通用代码卫生检查在每次 commit 前自动运行。若 hooks 自动修复了问题,提交会被中断,执行 `git add -p` 审阅修复内容后重新提交即可; - 6. **Issue**: 在 [issue.md](docs/.agents/issue.md) 中维护你处理过的 Issue 摘要(问题描述、表因根因、处理方式、后续防范、同类问题影响与处理注意事项等),便于同类问题的跨上下文处理;注意识别相同 Issue,不要同 Issue 多处维护; -- **Package Management Standardization (包管理规范)**: - 1. **Python**: 严禁使用 pip/poetry,**必须**统一使用 `uv` 进行包管理与脚本执行(如 `uv run`); - 2. **JavaScript/TypeScript**: 严禁使用 npm/yarn,**必须**统一使用 `pnpm` 进行包管理与脚本执行; -- **Database Management**: 谨慎操作,数据迁移、测试等操作严禁将现有数据删除,谨慎操作数据迁移的回滚,防止数据被清理。 -- **In-depth and close to the facts**:系统且全面地进行问题的分析,深入贴近事实,如有疑问,需先发问,不要乱做决定。 -- **Browser Validation Protocol (浏览器验证准则)**:Agent 不得自行完成、绕过或模拟任何 OAuth / SSO 认证流程,所有登录态均来源于用户已认证的 Chrome 主 profile(真实用户登录态)。完整协议(连通性自检、凭证管理、E2E 集成、实机回归等)详见 [浏览器验证协议](./docs/.agents/browser-validation.md); - 1. **安全红线**:禁止在 Sandbox 浏览器中跳转 Google 同意屏;禁止以模拟用户或第三方账号替代真实登录态;禁止要求用户在 chat 中粘贴密码、Cookie 或验证码; -- **Knowledge Map (知识索引)**:项目所有文档索引统一维护在 [知识索引](./docs/.agents/knowledge-map.md),并在文档目录变更时即时同步跟新; -- **Documentation Standards (文档规范)**: - 1. **Visual Documentation (图文并茂)**: 对于复杂逻辑,优先 **Mermaid Visualization Norms (Mermaid 可视化规范)**,构建”图文并茂”的直观文档; - - **色彩语义与兼容性**:为图表节点配置具备语义辨识度的色彩,并确保在深色模式(Dark Mode)下具有极高的对比度与清晰度; - - **逻辑模块化解构**:针对业务跨度较大的架构流程,强制采用 `subgraph` 容器进行层级解构与边界划分,以增强图表的自解说(Self-explaining)能力; - 2. **语言叙事**:用语精准,叙事完备,行文专业,聚焦核心,篇幅精炼,形象具体,体现真实作用与用户吸引性,字数恰当; - 3. **Direct Hyperlinking (直接跳转)**: 在文档中提及 Repo 内其他资源(文档/代码)时,**必须**构建可跳转的相对路径链接(如 `[Doc Name](./path.md)`),严禁使用”死文本”引用,以降低信息检索熵; - 4. **实操截图**:文档需要引入必要的浏览器实操截图时,需自行通过默认浏览器打开相关页面,通过实操现场截图并保留到文档路径进行文档引用; -- **Reference Specifications (IEEE)**:为保障工程决策的可追溯性与学术严谨性,核心引用需遵循 [reference-specifications.md](docs/.agents/reference-specifications.md)IEEE 标准引用格式; diff --git a/CHANGELOG.md b/CHANGELOG.md index 8947065..d13c1d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,19 @@ 本文件基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/) 规范维护,版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 -## [Unreleased] +## [v0.5.2](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.5.2) - 2026-09-10 + +- fix(quota-guard): `coding-proxy reset` / `POST /api/reset` 不再清空配额守卫的滑动窗口用量——根因是 `QuotaGuard.reset()` 把「状态机复位」与「用量计数清零」耦合在一个方法里(`_entries.clear()` + `_total = 0`),而窗口基线 `load_baseline()` 的唯一调用点在进程启动的 lifespan 钩子中、运行期不再回填,导致 Dashboard 的 `1d配额 45%` 徽章复位后永久停在 0%;现 `reset()` 只保留 `_transition_to(WITHIN_QUOTA)`(该方法本身已清 `_cap_error_active` 并还原探测间隔),CLI / API / Dashboard 三条路径经单一事实源同时修复。语义取舍为**如实**:用量确已超过 `token_budget × threshold_percent` 时守卫保持 `QUOTA_EXCEEDED`(不伪造用量数字),但仍清除上游 cap 错误卡死标志并把被 `Retry-After` 拉长的探测间隔还原为默认值——刻意不走 WITHIN_QUOTA → QUOTA_EXCEEDED 回环,因为 `_transition_to(EXCEEDED)` 会把 `_last_probe` 刷成当前时刻,令探测恢复凭空推迟一个 probe_interval,反复复位即可饿死探测(PR 评审发现的回归,补 2 条用例锁定);文档口径(`cli-reference.md` / `api-reference.md`)同步补明「仅复位状态、不清用量」; +- feat(dashboard): Overview 页「供应商状态」卡片标题栏右侧新增 **⟲ 状态复位** 按钮,一键把处于熔断 / 限流等异常态的供应商复位为可正常访问——调用无 body 的 `POST /api/reset`,因此**不触发重排序、不改动供应商优先级**,配合上述配额修复亦**不清空额度用量**;新增 `.btn-card-action` 卡片标题栏控件通用类(复用 `.card-title` 既有的 `justify-content: space-between`,零布局 CSS 改动)并纳入 `:focus-visible` 焦点环列表;交互沿用 `persistTierOrder` 的 in-flight 防并发范式与 `copyFromParent` 的瞬时反馈范式(复位中… → ✓ 已复位 / ✗ 失败,1.5s 还原),成功后定向重渲染供应商列表而不必等 10 分钟轮询;定向刷新 `/api/status` 失败时单独吞掉、不误报「✗ 失败」诱导重复点击(复位已生效,刷新失败仅影响展示)(实机验证); +- fix(dashboard): 修复「限速中」徽章从未渲染的问题——前端读 `rlInfo.limited`,而后端 `VendorTier.get_rate_limit_info()` 产出的键是 `is_rate_limited`,键名不匹配使 Rate Limit 异常态在 UI 上完全不可观测;补前端守卫测试锁定键名; + +> [!NOTE] +> 本版为 v0.5.2 周期(v0.5.2a1–a8)的正式收口版,完整变更见上方各 alpha 段落;以下条目为 v0.5.2a8 之后的增量(#279)。 + +## [v0.5.2a8](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.5.2a8) - 2026-07-06 + +- fix(vendor-logging): 修复流式 4xx/5xx 错误日志中文乱码——根因是 `logger.warning("...body=%s...", error_body[:500])` 对 `bytes` 走 `repr()`,非 ASCII 的 UTF-8 字节被转义为 `\xe6\x82\xa8` 之类不可读序列(上游限流/鉴权等中文错误信息无法辨认);新增 `decode_error_body(raw, limit=500)` 工具(`errors="replace"` 容错解码、先整体解码再按字符截断以避免在多字节 UTF-8 边界切断产生二次乱码,`limit` 语义为字符数),`base.py` / `copilot.py` / `antigravity.py` 三处流式错误日志统一收敛,并经 `model/__init__` 与 `vendors/base` 导出复用 (#278); +- fix(vendor-antigravity): 流式 scope 检测改用完整解码 body,与日志展示的 500 字符截断解耦——此前 `decode_error_body` 截断后的文本被同时喂给 `_mark_scope_error_if_needed` 子串检测,当 `ACCESS_TOKEN_SCOPE_INSUFFICIENT` 标记位于第 500 字符之后时会被截断丢弃,token 不再标记 `needs_reauth` 而持续以 scope 不足的凭证重试;现检测改用 `error_body.decode("utf-8", errors="replace")` 完整文本、日志展示仍用 500 截断,恢复与非流式路径(`send_message` 传完整 `response.text`)的行为一致性;补回归测试(构造标记位于 500 字符之后的 403 错误体,断言检测仍触发,旧截断实现下该用例失败)(#278); ## [v0.5.2a7](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.5.2a7) - 2026-07-04 diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 120000 index 47dc3e3..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md index 0bfb995..537ee8c 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,6 @@ To ensure this project outlives us all (long-term maintainability), we offer exh - 📖 **[User Guide](./docs/user-guide.md)** — From installation and bare-minimum configs to the semantic breakdown of every `config.yaml` field and common troubleshooting manuals. (Currently in Chinese) - 🏗️ **[Architecture Framework](./docs/framework.md)** — A meticulous decoding of underlying design patterns (Template Method, Circuit Breaker, State Machine, etc.), targeted at devs who want to peek into the matrix or contribute new vendors. (Currently in Chinese) -- 🤝 **[Engineering Guidelines (AGENTS.md)](./AGENTS.md)** — The systemic context mindset and AI Agent collaboration protocol. It preaches **refactoring, reuse, and orthogonal abstractions** and serves as the ultimate guiding light for all development in this repository. --- diff --git a/docs/.agents/browser-validation.md b/docs/.agents/browser-validation.md deleted file mode 100644 index ee4b705..0000000 --- a/docs/.agents/browser-validation.md +++ /dev/null @@ -1,172 +0,0 @@ -# Browser Validation Protocol(浏览器验证协议) - -> 由 [AGENTS.md §Browser Validation Protocol](../../AGENTS.md) 锚定的浏览器自动化与认证态使用协议。本协议是工程行为准则的子集,**任何 AI Agent 在执行浏览器自动化任务前必须完整遵循**。 -> -> **协议版本**:v1.0 | **生效范围**:所有面向本仓库的 AI Agent 协作场景 -> -> **关联工具**:`chrome-devtools` MCP、`claude-in-chrome` MCP、`playwright` MCP - -[TOC] - ---- - -## 1. 协议目的 - -为 AI Agent 在浏览器自动化场景下提供**统一、可审计、不可绕过**的认证态使用规范,解决以下问题: - -- AI Agent 不应也不可代用户决策"我是谁"——所有登录态归属问题必须由用户本人主导 -- 浏览器自动化能力一旦失控,可能在用户毫不知情时产生不可撤销的副作用(消息发送、订单提交、权限变更等) -- OAuth / SSO 同意屏在自动化上下文中存在被绕过的潜在风险,违反平台 ToS 与基本伦理 - -本协议通过"原则—红线—操作流程—验证"四层结构,将上述问题约束在工程可控范围内。 - ---- - -## 2. 核心原则 - -| 原则 | 具体含义 | -| -------------------------- | ----------------------------------------------------------------------------------------------------- | -| **登录态归属于用户** | Agent 不得自行完成、绕过或模拟任何 OAuth / SSO 认证流程;所有登录态来源于用户已认证的 Chrome 主 profile | -| **真实主 profile 优先** | 浏览器自动化默认接入用户日常使用的 Chrome 主 profile,复用其 Cookie / Session / SSO 状态 | -| **可审计、可回放** | 浏览器路径关键操作(点击、表单填写、跳转)应留下可被 GIF 回放或日志追溯的痕迹 | -| **最小副作用** | 优先以只读方式(查看、提取、断言)完成任务;写操作(提交、发送)需在协议第 5 节框架下显式确认 | - ---- - -## 3. 安全红线 - -> 以下条款**不可协商**,违反任一条款即视为协议违反。 - -1. **禁止跳转 Google 同意屏**:在 Sandbox / 自动化浏览器环境内**严禁**触发 Google OAuth 同意屏跳转。同意屏只能在用户主 profile 的真实浏览会话中由用户本人完成。 -2. **禁止模拟身份**:禁止以模拟用户身份、虚构 Cookie、第三方账号或测试账号替代真实登录态完成任务。 -3. **禁止凭证泄露**:禁止要求用户在 chat 中粘贴密码、Cookie、Session Token、二维码扫描结果或任何形式的验证码(含 6 位数字、短信、TOTP)。 -4. **禁止跨账号操作**:在多用户环境下,Agent 不得在未经显式确认的情况下切换 profile 或账号身份。 -5. **禁止规避 ToS**:不得通过 Headless 模式、UA 伪装、Captcha 自动求解等方式规避目标站点的服务条款。 -6. **禁止下载执行**:浏览器路径触发的任何文件下载需在主对话中显式确认;下载文件不得自动执行或注入到项目目录。 - ---- - -## 4. 连通性自检(Connectivity Probe) - -执行浏览器自动化任务前,Agent **必须**完成以下自检序列: - -| 步骤 | 操作 | 通过判据 | -| --------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- | -| 4.1 工具可用性 | 列出当前会话可用的 MCP 工具 | 至少存在 `chrome-devtools` / `claude-in-chrome` 之一 | -| 4.2 主 profile 加载 | 通过工具调用获取当前 Tab 列表或 Page 列表 | 返回非空,且 Tab 标题来自用户真实浏览历史而非空白会话 | -| 4.3 目标域名可达 | 通过 `navigate_page` 或 `browser_navigate` 访问目标域名首页 | HTTP 200 / 已登录态正常加载 | -| 4.4 登录态识别 | 在目标域名首页定位"已登录"标识(头像、用户名、退出按钮) | 能在 Snapshot / AOM 中找到一致标识 | -| 4.5 异常路径分类 | 若 4.4 失败,按"未登录 vs 会话过期 vs 拒绝服务"分类,**不**自动重登 | 输出明确分类,转入第 5 节的用户接力流程 | - -> **失败处置**:自检任一步骤失败,Agent **必须**停止任务、向用户输出诊断结论,**不得**尝试 OAuth / 凭证补救。 - ---- - -## 5. 凭证管理(Credential Lifecycle) - -### 5.1 发现路径 - -凭证通过以下路径**被动**发现,Agent **不**主动读取、导出或日志化: - -- 浏览器 Cookie / LocalStorage(仅由浏览器引擎内部使用) -- 浏览器扩展(如 Claude in Chrome)持有的 Session -- 用户在 chat 中以"我刚登录了 X"形式给出的事实陈述(非凭证本身) - -### 5.2 过期检测信号 - -| 信号 | 处置 | -| ---------------------------------------- | ----------------------------------------------- | -| HTTP 401 / 403 | 转 5.3 接力流程 | -| 重定向到登录页(含 `/login`、`/signin`) | 转 5.3 接力流程 | -| 同意屏触发(OAuth scope 变更) | **立即停止**,由用户在主 profile 完成同意 | -| Captcha 出现 | **立即停止**,输出"需用户介入" | - -### 5.3 用户接力流程(Re-authentication Handoff) - -``` -1. Agent 检测到登录态失效 -2. Agent 向用户输出:(a)失效域名 (b)建议在用户主 profile 完成登录的指引 -3. Agent 暂停浏览器任务,**不**触发任何登录流程 -4. 用户在真实浏览器完成登录后,回到 chat 通知 Agent -5. Agent 重新执行第 4 节连通性自检 -6. 自检通过后恢复任务 -``` - -### 5.4 凭证刷新约束 - -- Agent **不**调用任何 refresh_token / device_code 接口 -- Agent **不**触发邮箱链接、短信验证码、TOTP 输入 -- 凭证刷新由用户在原始登录路径自主完成 - ---- - -## 6. E2E 集成(End-to-End Integration) - -### 6.1 与项目 OAuth 模块的边界 - -本项目内置 GitHub Device Flow 与 Google OAuth 模块(`src/coding/proxy/auth/`)。浏览器协议与之的边界如下: - -- **项目 OAuth 模块**:服务端运行时凭证管理,由 `coding-proxy auth login/reauth` CLI 触发,目标是给 **proxy 自身**获取上游 API 凭证 -- **本协议**:客户端浏览器自动化场景,目标是让 **Agent 协助用户**完成日常任务(如查文档、填表单) - -二者**互不调用**:Agent 不调用 `coding-proxy auth` 替用户完成项目 OAuth;项目 OAuth 流程也不依赖本协议第 4 节自检。 - -### 6.2 与 CLI 命令的协同 - -| 场景 | 由谁触发 | -| ------------------------------- | ------------------------- | -| 给 proxy 注入 GitHub PAT | 用户运行 `auth login` | -| 给 proxy 注入 Google OAuth | 用户运行 `auth login` | -| 凭证过期重认证 | 用户运行 `auth reauth` | -| 浏览器查看 GitHub Token 状态 | Agent 通过本协议浏览器访问 | - -### 6.3 测试用例的浏览器隔离 - -- 单元测试(`tests/unit/`)**不**触发任何浏览器路径 -- 集成测试(`tests/integration/`)**不**触发任何浏览器路径 -- 浏览器路径仅在交互式 Agent 会话中触发,不进入 CI 自动化测试链路 - ---- - -## 7. 实机回归(Real-Device Regression) - -### 7.1 提交前的浏览器路径自检清单 - -涉及浏览器路径的改动在提交前需手工核验: - -- [ ] 第 4 节连通性自检在用户主 profile 通过 -- [ ] 第 3 节安全红线未被触碰(特别是同意屏、密码粘贴) -- [ ] 浏览器路径的关键操作有 GIF / Snapshot 留痕 -- [ ] 失败路径输出明确的用户接力指引 - -### 7.2 与 CI 的边界 - -CI 流水线(详见 [ops/ci-cd.md](../ops/ci-cd.md))**不**触发浏览器自动化路径。所有浏览器侧验证均在本地实机完成。 - -### 7.3 回归失败上报 - -若实机回归失败: - -1. 在 [docs/issue.md](../issue.md) 记录现象、根因、防范 -2. 若涉及协议本身缺陷,提交 PR 修订本文件并同步 [AGENTS.md](../../AGENTS.md) 锚点 -3. 不通过的 Agent 行为应在 [knowledge-map.md](./knowledge-map.md) 标注为已知问题 - ---- - -## 8. 引用规范 - -- 本协议章节可被 [AGENTS.md](../../AGENTS.md) / [CLAUDE.md](../../CLAUDE.md) 通过标题锚点形式引用 -- 修订本协议**必须**在 [docs/issue.md](../issue.md) 留存背景与决策记录 -- 协议条款发生变更时,需同步检查 [AGENTS.md §Browser Validation Protocol](../../AGENTS.md) 的兜底原则与本协议是否一致 - ---- - -## 附录 A:术语对照 - -| 术语 | 说明 | -| ------------------- | ----------------------------------------------------------------- | -| 主 profile | 用户日常使用的 Chrome / Edge 浏览器档案,含真实登录态 | -| Sandbox 浏览器 | 自动化工具启动的临时/隔离浏览器,无真实用户态 | -| 同意屏(Consent) | OAuth 流程中用户授予权限范围的页面 | -| 接力流程 | Agent 停止 → 用户介入完成 → Agent 恢复 的三段式协作 | -| 实机回归 | 在用户真实终端(非 CI)完成的端到端验证 | diff --git a/docs/.agents/issue.md b/docs/.agents/issue.md index 82c76f1..ebdddc0 100644 --- a/docs/.agents/issue.md +++ b/docs/.agents/issue.md @@ -4,6 +4,54 @@ --- +## `coding reset` 误清配额窗口用量(Dashboard 配额百分比归零且不自愈) + +**问题描述** + +执行 `coding-proxy reset`(含 `-v anthropic,zhipu` 形态)后,Dashboard「供应商状态」卡片里 zhipu 的「1d配额 45%」徽章掉到 **0%**,且不会随时间恢复。用户预期该指令只复位熔断等异常状态,不应触碰额度记录。 + +**表因** + +`/api/status` 的 `quota_guard.usage_percent` 由 `QuotaGuard.get_info()` 从内存字段 `_total` 计算,reset 后 `_total` 为 0。 + +**根因** + +`QuotaGuard.reset()`(`routing/quota_guard.py`)把**状态机复位**与**用量计数清零**两件正交的事耦合在了一个方法里: + +```python +self._transition_to(QuotaState.WITHIN_QUOTA) +self._entries.clear() # ← 连带清空滑动窗口 +self._total = 0 # ← 用量归零 +``` + +之所以「不自愈」,是因为窗口基线 `load_baseline()` 的**唯一**调用点在 `server/app.py` 的 lifespan 启动钩子里 —— 进程运行期间不会再回填,只能靠新请求从 0 重新累加。文档口径(`cli-reference.md` / `api-reference.md`)自始至终只承诺「配额守卫**状态** → WITHIN_QUOTA」,从未声明会清空用量,**实现与契约不一致**。 + +CLI `reset` 只是 `POST /api/reset` 的瘦 HTTP 客户端,`/api/reset` 又对全部 tier 调 `quota_guard.reset()` + `weekly_quota_guard.reset()`,故 CLI 与 API 两条路径同时中招。 + +**处理方式** + +在 `QuotaGuard.reset()` 中删除 `_entries.clear()` / `_total = 0` 两行,只保留 `_transition_to(QuotaState.WITHIN_QUOTA)`(该方法本身已负责清 `_cap_error_active` 并还原 `_effective_probe_interval`)。**单一事实源修复**:CLI、`/api/reset`、Dashboard 新增的「状态复位」按钮三条路径自动同时受益,无需各自加 `--keep-quota` 之类开关。 + +语义取舍:用量确已超过 `budget × threshold` 时,复位后守卫**保持** `QUOTA_EXCEEDED`(即「点了没反应」)。这是**如实**行为 —— 宁可不放行,也不伪造用量数字;但仍会清除 cap 错误卡死标志(`_cap_error_active`,正是 5h 限额型 vendor 的主要卡死来源)并把被 `Retry-After` 拉长的探测间隔还原为默认值。 + +**评审回归(PR #279)**:初版修复让 `reset()` 无条件 `_transition_to(WITHIN_QUOTA)`,用量仍超阈值时下一次判定再回落 `QUOTA_EXCEEDED` —— 而该回环会把 `_last_probe` 刷成当前时刻,令探测恢复凭空推迟一个 `probe_interval`(默认 300s),反复点击「状态复位」可**无限饿死探测**(旧实现清零用量,不存在此回环)。教训:**改「复位」语义时必须检查状态机每条转移的附带副作用**(`_transition_to` 不是纯赋值,EXCEEDED 分支带 `_last_probe = now`),修复后的 `reset()` 在用量仍超阈值时只清 `_cap_error_active` 与 `_effective_probe_interval`、不触发任何状态转移;补 `test_reset_does_not_delay_probe_when_over_budget`(mock 时钟断言探测点不被推后)与 `test_reset_still_clears_cap_stall_when_over_budget` 两条回归用例。 + +**后续防范** + +- **写操作测试须补「不误伤其他运行时状态」守卫断言**:`tests/test_app_routes.py::test_tier_order_preserves_quota_guard` 早已是这一范式的样板,但 `/api/reset` 侧长期缺失同类断言,缺陷才得以潜伏。新增写端点时,除「改了什么」外必须同时断言「没改什么」。 +- **警惕「状态机复位」与「计数清零」的耦合**:`reset()` 这类命名天然模糊。凡是同时持有状态字段与累计计数的组件(`CircuitBreaker` 的 `_failure_count` 属于状态、`QuotaGuard` 的 `_total` 属于观测数据),复位语义须显式区分二者。 +- **旧测试固化了错误行为**:`test_reset_clears_all_state` 曾断言 `window_usage_tokens == 0`,等于把缺陷写成契约。修复时应先让新测试在旧实现下 FAIL,确认其真正咬住缺陷。 + +**同类问题影响与处理注意事项** + +- **不可用线上进程验证**:`/api/reset` 会永久抹掉运行中进程的配额基线(直到重启)。验证须另起隔离实例(独立端口 + `/tmp` 数据库),严禁对用户正在使用的代理进程执行 reset。 +- **`weekly_quota_guard` 复用同一个类**,修复自动覆盖周级守卫,无需单独处理。 +- **`-v` 的重排序语义未变**:`-v` 仍是「提升/替换 N-tier 链路顺序」,不是「只复位这几个 vendor」的过滤器;Dashboard 按钮走无 body 路径,因此不触碰优先级。 + +**关联缺陷(同批修复)** + +Dashboard 的「限速中」徽章读 `rlInfo.limited`,而后端 `VendorTier.get_rate_limit_info()` 产出的键是 `is_rate_limited` —— 键名不匹配导致该徽章**从未渲染过**,Rate Limit 异常态在 UI 上完全不可观测。已同步修正为 `rlInfo.is_rate_limited`。 + ## Session 标题豁免前缀对 `[Session]` 兜底标题不生效(豁免仅 Level 1 生效) **问题描述** diff --git a/docs/.agents/knowledge-map.md b/docs/.agents/knowledge-map.md index 08bd983..9f2dd47 100644 --- a/docs/.agents/knowledge-map.md +++ b/docs/.agents/knowledge-map.md @@ -1,6 +1,6 @@ # Knowledge Map(知识索引) -> 项目所有文档的统一入口与权威索引。由 [AGENTS.md §Knowledge Map](../../AGENTS.md) 锚定,文档目录变更时**必须**即时同步更新本文件。 +> 项目所有文档的统一入口与权威索引。文档目录变更时**必须**即时同步更新本文件。 > > **使用方式**:按"受众 × 目的"二维定位所需文档;不确定起点时,从「入口导航」开始。 @@ -59,31 +59,21 @@ --- -## 5. Agent 协作([docs/agents/](./)) +## 5. Agent 协作与问题档案([docs/.agents/](./)) -> AGENTS.md 工程行为准则的卫星文件,定义 AI Agent 协作过程中的规范与协议。 +> 面向 AI Agent 协作与跨上下文问题沉淀的支撑文档。 -| 文档 | 主旨 | -| --------------------------------------------------------------- | --------------------------------------------- | -| [agents/knowledge-map.md](./knowledge-map.md) | 本文件——项目文档统一索引 | -| [agents/reference-specifications.md](./reference-specifications.md) | IEEE 文献引用格式模板与实践指南 | -| [agents/browser-validation.md](./browser-validation.md) | 浏览器验证协议(连通性自检、凭证管理、E2E) | +| 文档 | 主旨 | +| --------------------------------------------------- | --------------------------------------------- | +| [agents/knowledge-map.md](./knowledge-map.md) | 本文件——项目文档统一索引 | +| [agents/issue.md](./issue.md) | 已处理 Issue 摘要档案(表因、根因、防范) | --- -## 6. 问题档案 +## 6. 工程规范(顶层) | 文档 | 主旨 | | --------------------------------- | ----------------------------------------------------- | -| [docs/issue.md](../issue.md) | 已处理 Issue 摘要档案(表因、根因、防范) | - ---- - -## 7. 工程规范(顶层) - -| 文档 | 主旨 | -| --------------------------------- | ----------------------------------------------------- | -| [AGENTS.md](../../AGENTS.md) | 工程行为准则与 AI Agent 协作协议(与 CLAUDE.md 同源) | | [CHANGELOG.md](../../CHANGELOG.md)| 版本历史与变更日志 | --- @@ -91,5 +81,5 @@ ## 维护约束 1. **同步原则**:新增/删除/重命名 `docs/` 下任意 .md 文件时,**必须**同步本索引。 -2. **路径基准**:本文件位于 `docs/agents/`,所有相对路径以此为基准(向上一级 `../` 访问 `docs/`,向上两级 `../../` 访问仓库根)。 +2. **路径基准**:本文件位于 `docs/.agents/`,所有相对路径以此为基准(向上一级 `../` 访问 `docs/`,向上两级 `../../` 访问仓库根)。 3. **链接验证**:维护者修改本文件后应通过 grep 自检:所有 `[...](path)` 中的 `path` 文件存在。 diff --git a/docs/.agents/reference-specifications.md b/docs/.agents/reference-specifications.md deleted file mode 100644 index 896b866..0000000 --- a/docs/.agents/reference-specifications.md +++ /dev/null @@ -1,16 +0,0 @@ -# Reference Specifications (IEEE) - -> **模版准则**:[编号] 作者缩写. 姓, "文章标题," _刊名/会议名缩写 (斜体)_, 卷号, 期数, 页码, 年份. - -```latex -[1] A. Author, B. Author, and C. Author, "Title of paper," *Abbrev. Title of Journal*, vol. X, no. Y, pp. XX–XX, Year. -``` - -**引用实践** - -- **文内锚定**:采用标准上标链接形式:`描述内容[[1]](#ref1)`。 -- **文献索引**:底层采用 HTML 锚点 `id` 实现跳转稳定性。 - -```latex -[1] A. Vaswani et al., "Attention is all you need," Adv. Neural Inf. Process. Syst., vol. 30, pp. 5998–6008, 2017. -``` diff --git a/docs/guide/api-reference.md b/docs/guide/api-reference.md index 86ca332..4dea153 100644 --- a/docs/guide/api-reference.md +++ b/docs/guide/api-reference.md @@ -277,6 +277,8 @@ curl -X POST http://127.0.0.1:3392/api/reset \ **重置范围**:circuit_breaker(→ CLOSED)、quota_guard(→ WITHIN_QUOTA)、weekly_quota_guard(→ WITHIN_QUOTA)、rate_limit deadline(→ 清除)。 +> **仅复位状态,不清用量**:配额守卫的滑动窗口用量计数(`window_usage_tokens` / `usage_percent`)**原样保留**。该基线仅在进程启动时从数据库回填,一旦清零,Dashboard 的配额百分比会永久停在 0% 直到重启。若某 vendor 窗口用量确已超过 `token_budget × threshold_percent`,守卫**保持** `QUOTA_EXCEEDED`(不伪造用量),但仍会清除上游 cap 错误卡死标志并把被 `Retry-After` 拉长的探测间隔还原为默认值;真正被解开的是熔断、Rate Limit 与 cap 错误卡死标志。 + ## 7. GET /api/copilot/diagnostics 返回 Copilot 认证与交换链路的脱敏诊断信息。 diff --git a/docs/guide/cli-reference.md b/docs/guide/cli-reference.md index 7148fca..c424b56 100644 --- a/docs/guide/cli-reference.md +++ b/docs/guide/cli-reference.md @@ -160,6 +160,8 @@ coding-proxy reset [OPTIONS] **重置范围**:所有层级的熔断器状态(→ CLOSED)、配额守卫状态(→ WITHIN_QUOTA)、周级配额守卫状态(→ WITHIN_QUOTA)、Rate Limit 截止时间(→ 清除)。 +> **仅复位状态,不清用量**:配额守卫的滑动窗口用量计数(`window_usage_tokens` / `usage_percent`)**原样保留**。该基线仅在进程启动时从数据库回填,一旦清零,Dashboard 的配额百分比会永久停在 0% 直到重启。若某 vendor 窗口用量确已超过 `token_budget × threshold_percent`,守卫**保持** `QUOTA_EXCEEDED`(不伪造用量),但仍会清除上游 cap 错误卡死标志并把被 `Retry-After` 拉长的探测间隔还原为默认值;真正被解开的是熔断、Rate Limit 与 cap 错误卡死标志。 + ## 5. coding-proxy auth login 执行 OAuth 浏览器登录,获取供应商访问凭证。 diff --git a/docs/guide/dashboard.md b/docs/guide/dashboard.md index 0332f10..239ba41 100644 --- a/docs/guide/dashboard.md +++ b/docs/guide/dashboard.md @@ -35,6 +35,20 @@ http://127.0.0.1:3392/dashboard | Token 时间线(按供应商) | 各供应商按天的 Token 消耗堆叠图 | | Token 时间线(按供应商+模型) | 细粒度的模型级 Token 消耗堆叠图 | +### 供应商状态复位 + +「供应商状态」卡片标题栏右侧提供 **⟲ 状态复位** 按钮,用于把处于熔断 / 限流等异常态的供应商一键复位为可正常访问。等价于 [`POST /api/reset`](./api-reference.md#6-post-apireset) 的**无 body** 形态: + +| 复位 | 保留 | +| ---------------------------------------------------------- | ------------------------------------------------------------ | +| 熔断器状态(→ CLOSED,「熔断 ×N」徽章转「正常」) | 配额窗口用量(「1d配额 45%」徽章数值不变) | +| 配额守卫状态(用量未超阈值 → WITHIN_QUOTA;确已超阈值 → 保持 EXCEEDED,但仍清除 cap 卡死标志并还原探测间隔) | 供应商优先级顺序(无 body 即不触发重排序) | +| Rate Limit 冷却截止时间(→ 清除,「限速中」徽章消失) | 数据库用量记录(`coding-proxy usage` 统计不受影响) | + +执行期间按钮进入禁用态并显示「复位中…」,成功后短暂显示「✓ 已复位」并立即重渲染供应商列表(不必等 10 分钟轮询),失败显示「✗ 失败」,详情见浏览器控制台。 + +> 若某供应商的窗口用量**确实**已超过配额阈值,复位不会放行它 —— 用量数字不会被伪造,守卫保持 `QUOTA_EXCEEDED`;但 cap 错误卡死标志会被清除、探测间隔还原为默认值,探测恢复不会被推迟。 + ### 时间范围选择器 页面右上角提供时间范围选择: diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index c8fda53..36c94c9 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -175,7 +175,6 @@ graph RL - 📖 **[用户操作指引 (User Guide)](../user-guide.md)** — 从安装、最小配置要求,到每一项配置文件(`config.yaml`)的具体语义和常见排障指南。 - 🏗️ **[架构设计与工程方案 (Architecture Framework)](../framework.md)** — 详细解码底层设计模式(Template Method、Circuit Breaker、State Machine 等),适用于希望深入了解源码或贡献新供应商的开发者。 -- 🤝 **[工程准则 (AGENTS.md)](../../AGENTS.md)** — 系统的上下文心法和 AI Agent 协作协议,强调**重构、复用与正交抽象**,是本仓库一切开发的指导方针。 --- diff --git a/pyproject.toml b/pyproject.toml index 2d15fd1..8317099 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "coding-proxy" -version = "0.5.2a7" +version = "0.5.2" description = "A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao..." readme = "README.md" requires-python = ">=3.12" diff --git a/src/coding/proxy/config/config.default.yaml b/src/coding/proxy/config/config.default.yaml index e1bd596..6e584fa 100644 --- a/src/coding/proxy/config/config.default.yaml +++ b/src/coding/proxy/config/config.default.yaml @@ -712,5 +712,5 @@ session_policies: # ⚠️ 自定义 yaml 中该字段为整体替换(loader 的 list 合并约定,非追加合并); # 写空列表即关闭豁免。加载时会自动 strip + 去空 + 去重。 title_exempt_prefixes: - - "Write the title in the language" + - "Write the title in the" - "[Session]" diff --git a/src/coding/proxy/model/__init__.py b/src/coding/proxy/model/__init__.py index 4921e60..e2b70bf 100644 --- a/src/coding/proxy/model/__init__.py +++ b/src/coding/proxy/model/__init__.py @@ -25,6 +25,7 @@ UsageInfo, VendorCapabilities, VendorResponse, + decode_error_body, decode_json_body, extract_error_message, sanitize_headers_for_synthetic_response, @@ -83,6 +84,7 @@ "CopilotModelCatalog", "RequestCapabilities", "UsageInfo", + "decode_error_body", "decode_json_body", "extract_error_message", "sanitize_headers_for_synthetic_response", diff --git a/src/coding/proxy/model/vendor.py b/src/coding/proxy/model/vendor.py index dd5ef4f..867069a 100644 --- a/src/coding/proxy/model/vendor.py +++ b/src/coding/proxy/model/vendor.py @@ -75,6 +75,20 @@ def extract_error_message( return text[:500] if text else None +def decode_error_body(raw: bytes, limit: int = 500) -> str: + """将上游错误响应体(bytes)安全解码为可读文本,供日志展示. + + 直接以 ``%s`` 格式化 ``bytes`` 会走 ``repr()``,导致非 ASCII 的 UTF-8 + 字节被转义为 ``\\xe6\\x82\\xa8`` 之类的不可读序列(中文乱码)。本函数: + + - 使用 ``errors="replace"`` 容忍非法字节,非法字节降级为 ``�`` 而非抛异常, + 确保日志路径绝对健壮; + - 先整体解码再按字符截断,避免在多字节 UTF-8 边界切断产生乱码 + (因此 ``limit`` 语义为「字符数」而非「字节数」)。 + """ + return raw.decode("utf-8", errors="replace")[:limit] + + # ═══════════════════════════════════════════════════════════════ # 供应商核心数据类型 # ═══════════════════════════════════════════════════════════════ @@ -229,6 +243,7 @@ def age_seconds(self) -> int | None: "CopilotMisdirectedRequest", "CopilotModelCatalog", # 工具函数 + "decode_error_body", "decode_json_body", "extract_error_message", "sanitize_headers_for_synthetic_response", diff --git a/src/coding/proxy/routing/quota_guard.py b/src/coding/proxy/routing/quota_guard.py index 7259b51..7978199 100644 --- a/src/coding/proxy/routing/quota_guard.py +++ b/src/coding/proxy/routing/quota_guard.py @@ -175,15 +175,39 @@ def load_baseline(self, total_tokens: int, vendor: str | None = None) -> None: ) def reset(self) -> None: - """手动重置为 WITHIN_QUOTA 状态.""" + """手动复位配额守卫(保留滑动窗口用量计数). + + - 用量未超阈值 / 守卫非 EXCEEDED → 状态机回到 WITHIN_QUOTA; + - 用量确已超阈值 → 保持 QUOTA_EXCEEDED:不伪造用量,也不走 + WITHIN_QUOTA → QUOTA_EXCEEDED 回环(回环会把 ``_last_probe`` 刷成 + 当前时刻,令探测恢复凭空推迟一个 probe_interval,反复复位即饿死探测)。 + + 两种分支均清除 cap 错误卡死标志并还原探测间隔。``_entries`` / ``_total`` + 记录的是真实用量,清零会使 ``usage_percent`` 永久归零(基线仅在进程 + 启动时回填),任何情况下不予触碰。 + """ with self._lock: - self._transition_to(QuotaState.WITHIN_QUOTA) - self._entries.clear() - self._total = 0 - logger.info( - "Quota guard [%s]: manually reset to WITHIN_QUOTA", - self._window_label, + self._expire() + over = self._budget > 0 and self._total >= int( + self._budget * self._threshold ) + if over and self._state == QuotaState.QUOTA_EXCEEDED: + self._cap_error_active = False + self._effective_probe_interval = self._probe_interval + logger.info( + "Quota guard [%s]: manually reset (usage %d tokens over " + "threshold, EXCEEDED state kept, probe interval restored)", + self._window_label, + self._total, + ) + else: + self._transition_to(QuotaState.WITHIN_QUOTA) + logger.info( + "Quota guard [%s]: manually reset to WITHIN_QUOTA " + "(usage %d tokens preserved)", + self._window_label, + self._total, + ) def get_info(self) -> dict: """获取配额守卫状态信息.""" diff --git a/src/coding/proxy/server/dashboard.py b/src/coding/proxy/server/dashboard.py index 2694440..293900c 100644 --- a/src/coding/proxy/server/dashboard.py +++ b/src/coding/proxy/server/dashboard.py @@ -350,6 +350,19 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None: margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; } + /* 卡片标题栏右侧操作控件(.card-title 的 space-between 自动推至右端) */ + .btn-card-action { + padding: 4px 10px; border-radius: 6px; + background: rgba(48,54,61,.4); border: 1px solid rgba(255,255,255,.08); + color: var(--text-secondary); font-size: 12px; font-weight: 500; + letter-spacing: 0; cursor: pointer; white-space: nowrap; + transition: all .15s ease; + } + .btn-card-action:hover:not(:disabled) { + background: var(--bg-card-hover); color: var(--text-primary); + border-color: rgba(88,166,255,.3); + } + .btn-card-action:disabled { opacity: .35; cursor: default; } .chart-wrap { position: relative; height: 260px; min-width: 0; } .chart-wrap-lg { position: relative; height: 260px; min-width: 0; } .chart-wrap-xl { position: relative; height: 280px; min-width: 0; } @@ -685,6 +698,7 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None: .range-btn:focus-visible, .btn-refresh:focus-visible, .page-btn:focus-visible, + .btn-card-action:focus-visible, .copy-btn:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 2px; } .tab-pane { display: none; } .tab-pane.active { display: block; } @@ -904,7 +918,7 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None: