# v0.5.20 > 重试链路两处去噪:子代理限流器从单门改双门,正常多步工作不再被误杀;HTTP 重试期间相同错误不再每次都堆到对话气泡。 ## 修复 - **子代理限流器双门**(#78)。原 `verification` 类子代理 cap=5,5 个连续 PASS 的 PR 验证后第 6 个就被硬阻断——即使每次都成功。源码注释承诺「only fires on pathological loops, not on normal multi-step work」,实际行为违背了这条契约。本版把单门改成双门: - **总 cap 放宽**:`verification` 5 → 12。日常多 PR 工作有充足预算。 - **连续 FAIL streak 门**:新增 `noteOutcome(agentType, verdict)`。3 次连续 FAIL(无 PASS 中断)立即 cap,**与总数无关**。任何 PASS / UNKNOWN 清零 streak。这才是真正抓 verifier-loop 的语义。 - **保守 verdict 解析**:`parseVerdict(text)` 解析子代理输出的 `VERDICT: PASS|FAIL|CHANGES_NEEDED|APPROVE|UNCONFIRMED` 行,附带 `SECURITY:` `PLAN_REVIEW:` `ROOT CAUSE:` 同义词。**UNKNOWN 当 soft-PASS** ── 一次性研究类子代理(如 Explore)没有 verdict 结构的输出不会被误杀。 - 错误文案区分 `cappedReason: 'total' | 'streak' | 'none'`,用户能看到具体哪个门触发。 - 环境变量出口保留:`CLAUDE_CODE_AGENT_LIMIT_=N` 调高 total cap、`CLAUDE_CODE_AGENT_LIMITER_OFF=1` 整体禁用。 - **HTTP 重试期间相同错误不再刷屏对话**(#79)。原 `withRetry` 默认 `maxRetries=10`,每次失败只要错误是 `APIError` 就把 `SystemAPIErrorMessage` yield 到对话流。一次 flaky 上游恢复需要 2-3 次重试时,对话气泡里就会堆出 2-3 条一模一样的错误(用户原话「100% 显示出来」)。本版加同错误去重门控: - **首次出现的错误 key**:立即 yield(携带新信息)。 - **连续相同错误**:沉默到累计 ≥ 3 次(默认)才再 yield。 - **任何 429 rate-limit**:始终 yield —— wait-time 信息是它存在的意义。 - **错误 key**:用 `Error.name + APIError.status + Error.message` 拼接,401 与 503 即使 message 相同也不会折叠。 - **State 是 generator 闭包局部变量**,跨 withRetry 调用不泄露;persistent 模式 heartbeat 复用一次性算定的结果,避免长 persistent 等待重复增计数。 - 环境变量 `CLAUDE_CODE_RETRY_REPORT_AFTER=N` 可调阈值(默认 3)。 - 效果:一段 3 次相同错误的重试现在显示 2 个气泡而非 3 个;更长 streak 压缩更明显,但用户始终能看到通话还在重试。 ## 测试 - `src/tools/AgentTool/invocationLimiter.test.ts`:28 cases / 153 expects 全过。 - 14 个原测试按新 cap=12 适配 - 5 个 streak 行为新测试:3 FAIL trip / PASS clears / UNKNOWN soft-PASS / per-agent-type 隔离 / 12 PASS 触 total cap not streak - 9 个 parseVerdict 测试:empty / 标准 VERDICT 行 / FAIL 同义词 / APPROVE / 专家技能同义词 / 无 verdict / markdown 容忍 / 长 input tail - `src/services/api/withRetry.test.ts`:4 cases,3 个新测试覆盖 same-error 抑制、新 key 立即 yield、env override 提高阈值。 - 两个 PR 各自的 verification 子代理均独立 PASS,含状态隔离、heartbeat 放大、UNKNOWN soft-PASS 攻击面、throw/abort 场景 streak 残留等对抗性 probe。 ## 设计审查 - **UNKNOWN 当 soft-PASS 的攻击面**(#78):恶意子代理可省略 VERDICT 行避开 streak,但 total cap=12 仍兜底;且子代理 prompt 由调用方完全控制,不是普通输入面。 - **throw / abort 场景的 streak 状态**(#78):`noteInvocation` 已增计数但 `noteOutcome` 不跑——streak 保持原值不偏移,下次调用基于既有 streak 起步,PASS 仍能清零。 - **接入点对称性**(#78):`AgentTool.tsx` 中 `noteOutcome` 与 `noteInvocation` 用完全相同的 guard `(!toolUseContext.agentId && isBuiltInAgent(selectedAgent) && !isLimiterDisabled())`,避免 streak/total 计数不同步。 - **非 APIError 不进对话流**(#79):`Stream ended without receiving any events` 这类普通 Error 仍走外层 `CannotRetryError` 路径,本 PR 不扩大 `SystemAPIErrorMessage` 的类型契约(它要求 `error: APIError`)。改契约会扩散到所有消费者,超出本 PR 范围。 ## 范围 - `src/tools/AgentTool/invocationLimiter.ts`:核心改造,加 streak 状态、noteOutcome、parseVerdict、双门判定。 - `src/tools/AgentTool/AgentTool.tsx`:completed 分支加 `noteOutcome` 调用。 - `src/tools/AgentTool/invocationLimiter.test.ts`:14→28 测试。 - `src/services/api/withRetry.ts`:加 `DEFAULT_RETRY_REPORT_THRESHOLD` / `errorIdentityKey` / `shouldReportRetry`,2 处 yield 改 guard。 - `src/services/api/withRetry.test.ts`:1→4 测试。