From a2686ba0fee74824d717189bbe36f9bf3b6f0014 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Thu, 10 Sep 2026 16:12:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(dashboard):=20=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E7=8A=B6=E6=80=81=E5=8D=A1=E7=89=87=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E3=80=8C=E7=8A=B6=E6=80=81=E5=A4=8D=E4=BD=8D=E3=80=8D=E6=8C=89?= =?UTF-8?q?=E9=92=AE=EF=BC=8C=E5=B9=B6=E4=BF=AE=E5=A4=8D=20reset=20?= =?UTF-8?q?=E8=AF=AF=E6=B8=85=E9=85=8D=E9=A2=9D=E7=94=A8=E9=87=8F;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overview 页「供应商状态」卡片标题栏右侧新增 ⟲ 状态复位 按钮,把处于熔断 / 限流等异常态的供应商一键复位为可正常访问,无需切到终端执行 CLI。 根因修复:QuotaGuard.reset() 此前把「状态机复位」与「用量计数清零」两件正交 的事耦合在一个方法里(_entries.clear() + _total = 0)。而窗口基线 load_baseline() 的唯一调用点在进程启动的 lifespan 钩子中、运行期不再回填,导致 Dashboard 的 「1d配额 45%」徽章一经 reset 便永久停在 0%。现只保留 _transition_to(WITHIN_QUOTA) (该方法本身已清 _cap_error_active 并还原探测间隔),CLI / API / Dashboard 三条 路径经单一事实源同时修复,无需 --keep-quota 之类开关。 语义取舍为「如实」:用量确已超过 token_budget × threshold_percent 时,复位后下 一次判定立即回落 QUOTA_EXCEEDED,不伪造用量数字;真正被解开的是熔断、Rate Limit 与上游 cap 错误卡死标志。 实现要点: - 按钮调用无 body 的 POST /api/reset,服务端据此跳过重排序,故不改动供应商优先级; - 新增 .btn-card-action 卡片标题栏控件通用类,复用 .card-title 既有的 justify-content: space-between 实现右对齐,零布局 CSS 改动,并纳入 :focus-visible 焦点环列表; - 交互沿用 persistTierOrder 的 in-flight 防并发范式与 copyFromParent 的瞬时反馈 范式(复位中… → ✓ 已复位 / ✗ 失败,1.5s 还原),成功后定向重渲染供应商列表, 不必等 10 分钟轮询。 顺带修复:「限速中」徽章读 rlInfo.limited,而后端 VendorTier.get_rate_limit_info() 产出的键是 is_rate_limited,键名不匹配使该徽章从未渲染过、Rate Limit 异常态在 UI 上完全不可观测。 测试与文档:新增 7 条用例(配额用量保留、cap 卡死解开、真实超限不放行、/api/reset 无 body 与 -v 两种形态均保留用量、按钮与键名前端守卫);旧用例 test_reset_clears_all_state 曾断言 window_usage_tokens == 0,等于把缺陷固化为契约, 已改写为 test_reset_preserves_window_usage。cli-reference / api-reference 补明「仅 复位状态、不清用量」,dashboard.md 新增「供应商状态复位」小节,issue.md 归档复盘。 隔离实例(端口 3399,复刻 anthropic 熔断 + zhipu 45.6% 配额 + 限速中)实机验证: 复位后熔断转正常、限速徽章消失、配额 456,193,510 tokens 原样保留、链路顺序不变; CLI `reset -p 3399 -v anthropic,zhipu` 同样保留用量。全量 1656 条测试通过。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- CHANGELOG.md | 4 ++ docs/.agents/issue.md | 46 +++++++++++++++ docs/guide/api-reference.md | 2 + docs/guide/cli-reference.md | 2 + docs/guide/dashboard.md | 14 +++++ src/coding/proxy/routing/quota_guard.py | 12 ++-- src/coding/proxy/server/dashboard.py | 45 ++++++++++++++- tests/test_app_routes.py | 75 +++++++++++++++++++++++++ tests/test_quota_guard.py | 27 ++++++++- 9 files changed, 219 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ba12e..59cecab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +- 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`,不伪造用量数字,真正被解开的是熔断、Rate Limit 与上游 cap 错误卡死标志;文档口径(`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 分钟轮询(实机验证); +- fix(dashboard): 修复「限速中」徽章从未渲染的问题——前端读 `rlInfo.limited`,而后端 `VendorTier.get_rate_limit_info()` 产出的键是 `is_rate_limited`,键名不匹配使 Rate Limit 异常态在 UI 上完全不可观测;补前端守卫测试锁定键名; + ## [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); diff --git a/docs/.agents/issue.md b/docs/.agents/issue.md index 82c76f1..5d567c8 100644 --- a/docs/.agents/issue.md +++ b/docs/.agents/issue.md @@ -4,6 +4,52 @@ --- +## `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`(即「点了没反应」)。这是**如实**行为 —— 宁可不放行,也不伪造用量数字;真正被解开的是熔断、Rate Limit 与 cap 错误卡死标志(`_cap_error_active`),后者正是 5h 限额型 vendor 的主要卡死来源。 + +**后续防范** + +- **写操作测试须补「不误伤其他运行时状态」守卫断言**:`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/guide/api-reference.md b/docs/guide/api-reference.md index 86ca332..2a4e90f 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`;真正被解开的是熔断、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..68f6dd7 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`;真正被解开的是熔断、Rate Limit 与上游 cap 错误卡死标志。 + ## 5. coding-proxy auth login 执行 OAuth 浏览器登录,获取供应商访问凭证。 diff --git a/docs/guide/dashboard.md b/docs/guide/dashboard.md index 0332f10..dd0447f 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,含清除上游 cap 错误卡死标志) | 供应商优先级顺序(无 body 即不触发重排序) | +| Rate Limit 冷却截止时间(→ 清除,「限速中」徽章消失) | 数据库用量记录(`coding-proxy usage` 统计不受影响) | + +执行期间按钮进入禁用态并显示「复位中…」,成功后短暂显示「✓ 已复位」并立即重渲染供应商列表(不必等 10 分钟轮询),失败显示「✗ 失败」,详情见浏览器控制台。 + +> 若某供应商的窗口用量**确实**已超过配额阈值,复位不会放行它 —— 用量数字不会被伪造,守卫将在下一次判定时立即回落 `QUOTA_EXCEEDED`。 + ### 时间范围选择器 页面右上角提供时间范围选择: diff --git a/src/coding/proxy/routing/quota_guard.py b/src/coding/proxy/routing/quota_guard.py index 7259b51..3fcbdf6 100644 --- a/src/coding/proxy/routing/quota_guard.py +++ b/src/coding/proxy/routing/quota_guard.py @@ -175,14 +175,18 @@ def load_baseline(self, total_tokens: int, vendor: str | None = None) -> None: ) def reset(self) -> None: - """手动重置为 WITHIN_QUOTA 状态.""" + """手动重置为 WITHIN_QUOTA 状态(保留滑动窗口用量计数). + + 仅复位状态机与 cap 错误标志;``_entries`` / ``_total`` 记录的是真实 + 用量,清零会使 ``usage_percent`` 永久归零(基线仅在进程启动时回填), + 故不予触碰。用量确已超阈值时,下一次判定将立即回落 QUOTA_EXCEEDED。 + """ 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", + "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..18d7117 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:
-
供应商状态
+
供应商状态
加载中…
@@ -1396,7 +1410,7 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None: if (tier.weekly_quota_guard) quotaHTML += renderQuotaBar(tier.weekly_quota_guard); const rlInfo = tier.rate_limit || {}; - const rlHtml = rlInfo.limited ? `限速中` : ''; + const rlHtml = rlInfo.is_rate_limited ? `限速中` : ''; // Pointer Events 重排:仅需 data-vendor 定位;手柄作为拖拽发起点(无原生 draggable) const dragAttrs = ` data-vendor="${tier.name}"`; @@ -1556,6 +1570,33 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None: }).catch(function() {}); } +// ── 供应商状态:一键复位(熔断 / 限流 / 配额守卫状态;不动额度用量与优先级)──── +var _vendorResetInFlight = false; +function resetVendorStatus(btn) { + if (_vendorResetInFlight) return; // 防并发,同 persistTierOrder 范式 + _vendorResetInFlight = true; + var label = btn.textContent; + btn.disabled = true; + btn.textContent = '复位中…'; + // 无 body → 服务端跳过重排序,仅对全部 tier 复位弹性设施状态 + fetch('/api/reset', { method: 'POST' }).then(function(res) { + if (!res.ok) throw new Error(res.status); + btn.textContent = '✓ 已复位'; + return fetchJSON('/api/status').then(function(status) { + updateVendorStatus(status); // 定向重渲染,不等 10 分钟轮询 + }); + }).catch(function(e) { + console.error('vendor reset failed:', e); + btn.textContent = '✗ 失败'; + }).finally(function() { + setTimeout(function() { + btn.textContent = label; + btn.disabled = false; + _vendorResetInFlight = false; + }, 1500); + }); +} + // ── Model Calling 实时状态 ──────────────────────────────── function updateModelCalling(status) { var wrap = document.getElementById('model-calling-wrap'); diff --git a/tests/test_app_routes.py b/tests/test_app_routes.py index 5c0aa53..e059d65 100644 --- a/tests/test_app_routes.py +++ b/tests/test_app_routes.py @@ -1067,6 +1067,52 @@ def test_reset_reorder_also_resets_circuit_breaker_and_rate_limit(): assert not anthropic_tier.is_rate_limited +def test_reset_preserves_quota_usage(): + """核心:/api/reset 复位守卫状态但**不**清空滑动窗口用量. + + 旧实现在 QuotaGuard.reset() 里连带 `_entries.clear()` + `_total = 0`, + 使 Dashboard 的配额百分比归零且无法自愈(基线仅在进程启动时回填)。 + """ + app = _make_reorder_app() + router = app.state.router + tier = router.tiers[0] + qg = tier.quota_guard + assert qg is not None + qg._enabled = True + qg._budget = 100_000 + qg.record_usage(40_000) + qg.notify_cap_error() # 制造 cap 卡死态 + assert qg.get_info()["state"] == "quota_exceeded" + + with TestClient(app) as client: + resp = client.post("/api/reset") + assert resp.status_code == 200 + + after = qg.get_info() + assert after["state"] == "within_quota" # 状态已复位 + assert after["window_usage_tokens"] == 40_000 # 用量原样保留 + assert after["usage_percent"] == 40.0 + assert qg.can_use_primary() is True # cap 卡死标志已清除 + + +def test_reset_preserves_quota_usage_with_reorder(): + """带 -v 重排序的 /api/reset 同样不得清空用量(CLI `coding reset -v ...` 路径).""" + app = _make_reorder_app() + qg = app.state.router.tiers[0].quota_guard + assert qg is not None + qg._enabled = True + qg._budget = 100_000 + qg.record_usage(45_000) + + with TestClient(app) as client: + resp = client.post( + "/api/reset", json={"vendors": ["anthropic", "zhipu", "copilot"]} + ) + assert resp.status_code == 200 + + assert qg.get_info()["window_usage_tokens"] == 45_000 + + def test_reorder_tiers_shared_reference(): """验证 reorder_tiers 使用切片赋值,Executor 立即可见.""" from coding.proxy.routing.router import RequestRouter @@ -1235,3 +1281,32 @@ def test_dashboard_serves_pointer_drag_reorder_and_no_cache(): # 已移除脆弱的原生 HTML5 DnD(不再渲染 draggable 属性 / 监听 dragstart) assert 'draggable="true"' not in html assert "addEventListener('dragstart'" not in html + + +def test_dashboard_serves_vendor_status_reset_control(): + """供应商状态卡片标题栏交付「状态复位」按钮,且走无 body 的 POST /api/reset. + + 无 body 是语义关键:服务端据此跳过重排序(routes.py 的 vendor_names 守卫), + 因此复位不会改动供应商优先级。 + """ + with _make_app() as client: + html = client.get("/dashboard").text + # 按钮随卡片标题栏交付 + assert 'id="btn-vendor-reset"' in html + assert "btn-card-action" in html + assert "resetVendorStatus(this)" in html + # 处理器在位,且为无 body 的 POST(不触发重排序) + assert "function resetVendorStatus(btn)" in html + assert "fetch('/api/reset', { method: 'POST' })" in html + assert "_vendorResetInFlight" in html # 防并发守卫 + + +def test_dashboard_rate_limit_badge_reads_correct_key(): + """「限速中」徽章须读后端真实键名 is_rate_limited(tier.get_rate_limit_info). + + 旧实现读 rlInfo.limited —— 后端从未产出该键,徽章永远不渲染。 + """ + with _make_app() as client: + html = client.get("/dashboard").text + assert "rlInfo.is_rate_limited" in html + assert "rlInfo.limited " not in html diff --git a/tests/test_quota_guard.py b/tests/test_quota_guard.py index 9d3ed9a..e222e18 100644 --- a/tests/test_quota_guard.py +++ b/tests/test_quota_guard.py @@ -90,14 +90,37 @@ def test_probe_success_restores_within_quota(): assert qg.get_info()["state"] == "within_quota" -def test_reset_clears_all_state(): +def test_reset_preserves_window_usage(): + """复位只清状态机,滑动窗口用量计数须原样保留.""" qg = _make_guard() qg.record_usage(500) qg.notify_cap_error() qg.reset() info = qg.get_info() assert info["state"] == "within_quota" - assert info["window_usage_tokens"] == 0 + assert info["window_usage_tokens"] == 500 + + +def test_reset_clears_cap_error_stall(): + """cap 错误卡死态:复位清除 _cap_error_active,预算未超时立即恢复放行.""" + qg = _make_guard(token_budget=1000) + qg.record_usage(100) + qg.notify_cap_error() + assert qg.can_use_primary() is False + qg.reset() + assert qg.can_use_primary() is True + assert qg.get_info()["window_usage_tokens"] == 100 + + +def test_reset_does_not_unblock_genuinely_exhausted_quota(): + """用量确实超阈值时,复位后下一次判定立即回落 EXCEEDED(不伪造用量).""" + qg = _make_guard(token_budget=1000, threshold_percent=99.0) + qg.record_usage(995) + assert qg.can_use_primary() is False + qg.reset() + assert qg.get_info()["state"] == "within_quota" + assert qg.can_use_primary() is False + assert qg.get_info()["window_usage_tokens"] == 995 def test_get_info_returns_correct_data(): From ed986c91f093f245b2ca1a6a3da4e25a78cdc400 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Thu, 10 Sep 2026 16:39:20 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix(quota-guard):=20=E7=94=A8=E9=87=8F?= =?UTF-8?q?=E8=B6=85=E9=98=88=E5=80=BC=E6=97=B6=E5=A4=8D=E4=BD=8D=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=8E=A8=E5=90=8E=E6=8E=A2=E6=B5=8B=E6=97=B6=E9=92=9F?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=B6=88=E9=99=A4=E5=A4=8D=E4=BD=8D=E5=90=8E?= =?UTF-8?q?=E7=9A=84=E8=AF=AF=E6=8A=A5=E5=A4=B1=E8=B4=A5;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 处理 PR #279 的两条评审意见。 回归修复(评审 #1):初版修复让 reset() 无条件 _transition_to(WITHIN_QUOTA), 用量仍超阈值时下一次判定再回落 QUOTA_EXCEEDED —— 而该回环会经 _transition_to(EXCEEDED) 把 _last_probe 刷成当前时刻,令探测恢复凭空推迟一个 probe_interval(默认 300s);反复点击「状态复位」可无限饿死探测(旧实现清零 用量,不存在此回环)。现 reset() 在用量仍超阈值且已处 EXCEEDED 时不再触发任何 状态转移,仅清 _cap_error_active 并把被 Retry-After 拉长的 _effective_probe_interval 还原为默认值——即「宁可不放行,也不推迟恢复」。 补 2 条回归用例(mock 时钟锁定探测点不被推后 / cap 拉长的探测间隔被还原), 并改写 test_reset_does_not_unblock_genuinely_exhausted_quota:超阈值时复位后 状态保持 quota_exceeded(旧断言 within_quota 恰是回环存在的证据)。 误报修复(评审 #2):/api/reset 已返回 200 后,紧随的 /api/status 定向刷新若 失败(网络抖动、进程重载)会被同一个 catch 误标为「✗ 失败」,诱导用户重复 点击——复位其实已生效。现刷新失败单独吞掉,仅影响展示。 文档(cli-reference / api-reference / dashboard.md)与 CHANGELOG、issue.md 复盘同步对齐终态语义:超阈值 → 保持 EXCEEDED、清 cap 卡死标志、还原探测间隔。 全量 1658 条测试通过。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- CHANGELOG.md | 4 +-- docs/.agents/issue.md | 4 ++- docs/guide/api-reference.md | 2 +- docs/guide/cli-reference.md | 2 +- docs/guide/dashboard.md | 4 +-- src/coding/proxy/routing/quota_guard.py | 38 +++++++++++++++----- src/coding/proxy/server/dashboard.py | 3 +- tests/test_quota_guard.py | 47 +++++++++++++++++++++++-- 8 files changed, 85 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59cecab..395efce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ ## [Unreleased] -- 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`,不伪造用量数字,真正被解开的是熔断、Rate Limit 与上游 cap 错误卡死标志;文档口径(`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 分钟轮询(实机验证); +- 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 上完全不可观测;补前端守卫测试锁定键名; ## [v0.5.2a8](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.5.2a8) - 2026-07-06 diff --git a/docs/.agents/issue.md b/docs/.agents/issue.md index 5d567c8..ebdddc0 100644 --- a/docs/.agents/issue.md +++ b/docs/.agents/issue.md @@ -32,7 +32,9 @@ CLI `reset` 只是 `POST /api/reset` 的瘦 HTTP 客户端,`/api/reset` 又对 在 `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`(即「点了没反应」)。这是**如实**行为 —— 宁可不放行,也不伪造用量数字;真正被解开的是熔断、Rate Limit 与 cap 错误卡死标志(`_cap_error_active`),后者正是 5h 限额型 vendor 的主要卡死来源。 +语义取舍:用量确已超过 `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` 两条回归用例。 **后续防范** diff --git a/docs/guide/api-reference.md b/docs/guide/api-reference.md index 2a4e90f..4dea153 100644 --- a/docs/guide/api-reference.md +++ b/docs/guide/api-reference.md @@ -277,7 +277,7 @@ 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`;真正被解开的是熔断、Rate Limit 与上游 cap 错误卡死标志。 +> **仅复位状态,不清用量**:配额守卫的滑动窗口用量计数(`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 diff --git a/docs/guide/cli-reference.md b/docs/guide/cli-reference.md index 68f6dd7..c424b56 100644 --- a/docs/guide/cli-reference.md +++ b/docs/guide/cli-reference.md @@ -160,7 +160,7 @@ 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`;真正被解开的是熔断、Rate Limit 与上游 cap 错误卡死标志。 +> **仅复位状态,不清用量**:配额守卫的滑动窗口用量计数(`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 diff --git a/docs/guide/dashboard.md b/docs/guide/dashboard.md index dd0447f..239ba41 100644 --- a/docs/guide/dashboard.md +++ b/docs/guide/dashboard.md @@ -42,12 +42,12 @@ http://127.0.0.1:3392/dashboard | 复位 | 保留 | | ---------------------------------------------------------- | ------------------------------------------------------------ | | 熔断器状态(→ CLOSED,「熔断 ×N」徽章转「正常」) | 配额窗口用量(「1d配额 45%」徽章数值不变) | -| 配额守卫状态(→ WITHIN_QUOTA,含清除上游 cap 错误卡死标志) | 供应商优先级顺序(无 body 即不触发重排序) | +| 配额守卫状态(用量未超阈值 → WITHIN_QUOTA;确已超阈值 → 保持 EXCEEDED,但仍清除 cap 卡死标志并还原探测间隔) | 供应商优先级顺序(无 body 即不触发重排序) | | Rate Limit 冷却截止时间(→ 清除,「限速中」徽章消失) | 数据库用量记录(`coding-proxy usage` 统计不受影响) | 执行期间按钮进入禁用态并显示「复位中…」,成功后短暂显示「✓ 已复位」并立即重渲染供应商列表(不必等 10 分钟轮询),失败显示「✗ 失败」,详情见浏览器控制台。 -> 若某供应商的窗口用量**确实**已超过配额阈值,复位不会放行它 —— 用量数字不会被伪造,守卫将在下一次判定时立即回落 `QUOTA_EXCEEDED`。 +> 若某供应商的窗口用量**确实**已超过配额阈值,复位不会放行它 —— 用量数字不会被伪造,守卫保持 `QUOTA_EXCEEDED`;但 cap 错误卡死标志会被清除、探测间隔还原为默认值,探测恢复不会被推迟。 ### 时间范围选择器 diff --git a/src/coding/proxy/routing/quota_guard.py b/src/coding/proxy/routing/quota_guard.py index 3fcbdf6..7978199 100644 --- a/src/coding/proxy/routing/quota_guard.py +++ b/src/coding/proxy/routing/quota_guard.py @@ -175,19 +175,39 @@ def load_baseline(self, total_tokens: int, vendor: str | None = None) -> None: ) def reset(self) -> None: - """手动重置为 WITHIN_QUOTA 状态(保留滑动窗口用量计数). + """手动复位配额守卫(保留滑动窗口用量计数). - 仅复位状态机与 cap 错误标志;``_entries`` / ``_total`` 记录的是真实 - 用量,清零会使 ``usage_percent`` 永久归零(基线仅在进程启动时回填), - 故不予触碰。用量确已超阈值时,下一次判定将立即回落 QUOTA_EXCEEDED。 + - 用量未超阈值 / 守卫非 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) - logger.info( - "Quota guard [%s]: manually reset to WITHIN_QUOTA (usage %d tokens preserved)", - self._window_label, - self._total, + 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 18d7117..293900c 100644 --- a/src/coding/proxy/server/dashboard.py +++ b/src/coding/proxy/server/dashboard.py @@ -1582,9 +1582,10 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None: fetch('/api/reset', { method: 'POST' }).then(function(res) { if (!res.ok) throw new Error(res.status); btn.textContent = '✓ 已复位'; + // 复位已生效,状态刷新失败不应误报失败、诱导重复点击 → 单独吞掉 return fetchJSON('/api/status').then(function(status) { updateVendorStatus(status); // 定向重渲染,不等 10 分钟轮询 - }); + }).catch(function() {}); }).catch(function(e) { console.error('vendor reset failed:', e); btn.textContent = '✗ 失败'; diff --git a/tests/test_quota_guard.py b/tests/test_quota_guard.py index e222e18..a2b6df0 100644 --- a/tests/test_quota_guard.py +++ b/tests/test_quota_guard.py @@ -113,16 +113,59 @@ def test_reset_clears_cap_error_stall(): def test_reset_does_not_unblock_genuinely_exhausted_quota(): - """用量确实超阈值时,复位后下一次判定立即回落 EXCEEDED(不伪造用量).""" + """用量确实超阈值时,复位不放行也不伪造用量——保持 EXCEEDED,不做状态回环.""" qg = _make_guard(token_budget=1000, threshold_percent=99.0) qg.record_usage(995) assert qg.can_use_primary() is False qg.reset() - assert qg.get_info()["state"] == "within_quota" + assert qg.get_info()["state"] == "quota_exceeded" assert qg.can_use_primary() is False assert qg.get_info()["window_usage_tokens"] == 995 +def test_reset_does_not_delay_probe_when_over_budget(): + """回归:用量仍超阈值时复位不得推后探测时钟. + + 若 reset() 走 WITHIN_QUOTA → 下一次判定再回落 QUOTA_EXCEEDED 的回环, + `_transition_to(EXCEEDED)` 会把 `_last_probe` 刷成当前时刻,令探测恢复 + 凭空推迟一个 probe_interval;反复点击「状态复位」即可无限饿死探测。 + """ + qg = _make_guard( + token_budget=1000, threshold_percent=99.0, probe_interval_seconds=300 + ) + base = time.monotonic() + with patch("coding.proxy.routing.quota_guard.time") as mock_time: + mock_time.monotonic.return_value = base + qg.record_usage(995) + assert qg.can_use_primary() is False # → EXCEEDED,_last_probe = base + + # 距下次探测仅剩 10s 时复位 + mock_time.monotonic.return_value = base + 290 + qg.reset() + + # 原定探测点(base+300)之后应照常放行探测,而非被推到 base+590 + mock_time.monotonic.return_value = base + 301 + assert qg.can_use_primary() is True + + +def test_reset_still_clears_cap_stall_when_over_budget(): + """用量超阈值但由 cap 错误拉长了探测间隔时,复位须还原 probe_interval.""" + qg = _make_guard( + token_budget=1000, threshold_percent=99.0, probe_interval_seconds=300 + ) + base = time.monotonic() + with patch("coding.proxy.routing.quota_guard.time") as mock_time: + mock_time.monotonic.return_value = base + qg.record_usage(995) + qg.notify_cap_error(retry_after_seconds=3600) # 探测间隔被拉到 3960s + assert qg.can_use_primary() is False + + qg.reset() + mock_time.monotonic.return_value = base + 301 + # 若 _effective_probe_interval 未还原为 300,此处仍会被拒 + assert qg.can_use_primary() is True + + def test_get_info_returns_correct_data(): qg = _make_guard(token_budget=2000, threshold_percent=80.0) qg.record_usage(1000) From 8fc09d9f117ef0c917206b57de1f312f4f714e66 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Thu, 10 Sep 2026 16:46:55 +0800 Subject: [PATCH 3/5] =?UTF-8?q?docs(agents):=20=E7=A7=BB=E9=99=A4=E4=BB=93?= =?UTF-8?q?=E5=BA=93=E5=86=85=E7=9A=84=20Agent=20=E5=8D=8F=E4=BD=9C?= =?UTF-8?q?=E6=B2=BB=E7=90=86=E6=96=87=E6=A1=A3;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除 AGENTS.md / CLAUDE.md(Agent 工程行为准则与协作协议)及其卫星规范 docs/.agents/browser-validation.md(浏览器验证协议)与 docs/.agents/reference-specifications.md(IEEE 引用规范模板)——四者均为 面向本机 AI Agent 环境的私有治理内容,不属于开源仓库交付物。 🤖 Generated with [Claude Code](https://claude.com/claude-code), [CodeX](https://openai.com), [Gemini Code Assist](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- AGENTS.md | 58 -------- CLAUDE.md | 1 - docs/.agents/browser-validation.md | 172 ----------------------- docs/.agents/reference-specifications.md | 16 --- 4 files changed, 247 deletions(-) delete mode 100644 AGENTS.md delete mode 120000 CLAUDE.md delete mode 100644 docs/.agents/browser-validation.md delete mode 100644 docs/.agents/reference-specifications.md 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/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/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/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. -``` From 2f4ea966643cd45c2c7a142ac2949f437ffa8e18 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Thu, 10 Sep 2026 16:47:06 +0800 Subject: [PATCH 4/5] =?UTF-8?q?build(deps):=20=E5=90=8C=E6=AD=A5=20uv.lock?= =?UTF-8?q?=20=E4=B8=AD=20coding-proxy=20=E7=89=88=E6=9C=AC=E8=87=B3=200.5?= =?UTF-8?q?.2a8;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pyproject.toml 已 bump 至 0.5.2a8,但 uv.lock 仍停留在 0.5.2a7, 补上发布时遗漏的锁文件版本同步。 🤖 Generated with [Claude Code](https://claude.com/claude-code), [CodeX](https://openai.com), [Gemini Code Assist](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 7682827..ca13d2b 100644 --- a/uv.lock +++ b/uv.lock @@ -74,7 +74,7 @@ wheels = [ [[package]] name = "coding-proxy" -version = "0.5.2a7" +version = "0.5.2a8" source = { editable = "." } dependencies = [ { name = "aiosqlite" }, From 635fa2cc3355e617b031d654a148ad95f7fa54d8 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Thu, 10 Sep 2026 16:50:54 +0800 Subject: [PATCH 5/5] =?UTF-8?q?docs(knowledge-map):=20=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=8C=87=E5=90=91=E5=B7=B2=E5=88=A0=E9=99=A4=20Agent=20?= =?UTF-8?q?=E6=B2=BB=E7=90=86=E6=96=87=E6=A1=A3=E7=9A=84=E6=AD=BB=E9=93=BE?= =?UTF-8?q?;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 随 8fc09d9 移除 AGENTS.md / browser-validation.md / reference-specifications.md 后同步收尾:knowledge-map 移除死链行与 AGENTS.md 锚定表述,合并「Agent 协作」 与「问题档案」两节(issue.md 实际位于 docs/.agents/,原 ../issue.md 链接为 死链,一并修正),路径基准勘误为 docs/.agents/;中英 README 的文档地图同步 移除 AGENTS.md 条目。全部相对链接经脚本自检可解析。 🤖 Generated with [Claude Code](https://claude.com/claude-code), [CodeX](https://openai.com), [Gemini Code Assist](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- README.md | 1 - docs/.agents/knowledge-map.md | 28 +++++++++------------------- docs/zh-CN/README.md | 1 - 3 files changed, 9 insertions(+), 21 deletions(-) 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/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/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 协作协议,强调**重构、复用与正交抽象**,是本仓库一切开发的指导方针。 ---