ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

opencodex Cursor 适配器审计驱动修复:上下文延续、resource_exhausted 分流与传输加固的六项阻断项解决实录

opencodex Cursor 适配器审计驱动修复:上下文延续、resource_exhausted 分流与传输加固的六项阻断项解决实录 【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载导读本文基于 opencodex 仓库 devlog 单元260723_cursor_context_continuity中的第二轮审计合成文档完整还原一次“审计—反驳—修订”闭环针对 Cursor 适配器在真实生产日志中暴露的三类缺陷会话上下文丢失、resource_exhausted被过宽映射为 400、HTTP/2 传输层防护缺口审计轮次提出 6 个阻断项并逐一给出落地方案。读者读完可以掌握字节记账式响应状态存储的完整不变式、resource_exhausted400/429 分流的精确谓词设计、单次触发的传输终结守卫的提取与测试方法以及这些方案在当前仓库源码中的最终形态。背景一次由真实日志驱动的修复计划该 devlog 单元000_plan.md记录了 2026-07-23 一次基于线上证据的诊断。来自~/.opencodex的真实观测包括usage.jsonl中同一 Cursor 会话的totalTokens出现51676 → 107 → 78 → 111177 → …的非单调抖动——绝对上下文值与会话内单轮输出增量值交错说明对话上下文没有被正确携带17:45:30–17:45:48 之间连续 6 行 400 状态upstreamError均为Cursor resource limit exceeded: Cursor Connect error resource limit exceeded: ErrorCodex 客户端盲目重试了 5 次其流重试预算一条 504Cursor request timed out: Cursor transport timed out before first responseservice.log中模型发现超时No response within 8000ms并降级为静态目录responses-state.json中零条包含 cursor 延续内容的记录——直接证明 Cursor 的延续状态从未被持久化。由此定位三个根因RC1/RC2/RC3并拆分为三个依赖排序的阶段Phase 1 会话延续、Phase 2 错误分类、Phase 3 传输加固。每一阶段文档010、020、030都要经过审计轮次r1 FAIL 8 项 → r2 FAIL 6 项 → r3 GO-WITH-FIXES 2 项才能进入实现。002_audit_r2_synthesis.md 正是第二轮审计的合成结论同一审查者判定FAIL6 个阻断项全部 ACCEPT 并附带具体修订方案。它本身是一张高度浓缩的决策表以下逐项展开。阻断项 1High字节记账必须在加载/重置/替换路径上存活问题Phase 1 方案要求对store:false的 Cursor 请求强制记住响应状态但强制保留意味着每轮都要存储“完整展开后的输入”。延续项在展开时会复制前序条目到下一请求state.ts中items复制逻辑持久化时再次存储该展开输入——一条链上总体字节量接近二次方增长。原存储只按条数MAX_STORED_RESPONSES 1_000加 1 小时 TTL 约束没有内存字节上限Kiro 流量早已承担此风险。解决引入总字节高水位MAX_STORED_RESPONSE_BYTES当前源码为64 * 1024 * 1024见 state.ts并确立一组记账不变式确保任何路径都不会让计数器漂移唯一测量构造函数measuredEntry(items, providers, createdAt)用JSON.stringify(items).length计算sizeBytesprovider 状态可忽略是全仓库唯一计算尺寸的入口唯一删除助手deleteEntry(id)是唯一执行states.delete的地方同时递减storedResponseBytes。TTL、条数、字节、显式删除全部汇入它源码中该函数注释即为“The ONLY deletion point”见 state.ts替换先减后加对已存在 id 重新rememberResponseState时先走deleteEntry避免双重计数源码中replaceMapEntry实现了storedResponseBytes - existing?.sizeBytes ?? 0再累加见 state.ts加载时重算ensureLoaded在加载每个快照版本的每条记录时重新计算sizeBytes绝不信任持久化的尺寸防陈旧/被篡改的记账测试重置clearResponseStateMemoryForTests将计数器归零淘汰顺序pruneResponses()先 TTL、再条数之后在storedResponseBytes MAX_STORED_RESPONSE_BYTES时按最旧优先驱逐当前实现中居民条目超出上限时降级到 durable spill见 state.ts。测试要求restart-recompute持久化后清内存再加载ensureLoaded重推尺寸且上限仍正确、overwrite同一 id 重记不重复计数、TTL/count/byte 三路驱逐。当前源码还额外暴露了setResponseStateByteCapForTests(bytes | null)与getStoredResponseBytesForTests()见 state.ts正是审计指定的测试接缝。阻断项 2Med命名测试接缝让服务端测试真正经过调用点策略问题单元测试若不经过真实调用点而直接调用rememberResponseState就是同义反复tautological——r1 审计早已否决过此类“绕过策略的测试”。解决命名两条接缝上限钩子导出测试专用setResponseStateByteCapForTests(bytes | null)沿用state.ts既有的 clear-for-tests 约定null恢复默认值服务端测试扩展现有的resolveAdaptermock位于tests/server-combo-failover-e2e.test.ts:35增加一个分支对 cursor provider 返回createCursorAdapter(provider, { createTransport: fakeFactory })。由于createCursorAdapter内部会设置adapter.namemock 分支返回的适配器名字仍是cursor从而让调用点谓词见下真正激活。端到端链式证明要求POST 一个store:false的 Responses 请求 → 捕获response.id→ POST 携带previous_response_id的后续请求 → 断言 fake transport 两次收到同一个conversationId且由真实的:1537/:1574策略驱动。阻断项 3Highresource_exhausted必须保持 429谓词全面重设计问题原classifyCursorError()把每一个resource_exhausted都映射为Cursor resource limit exceeded400 /tool_catalog_too_large而 gRPC 的RESOURCE_EXHAUSTED通常是配额/速率耗尽。只有明确写着“tool catalog/registration too large”的文本才配得上 400。线上证据正是如此连续 6 个 400 触发客户端盲目重试风暴而它们本应作为 429 让客户端退避、让 combo 故障切换将其视为瞬态错误。解决谓词重设计为“先拒绝配额/速率提示再匹配尺寸模式”不做自由提示配对free cue pairingconst QUOTA_RATE_CUES [too many requests, quota, rate limit, rate-limit, throttl]; const REQUEST_TOO_LARGE_PATTERNS: (string | RegExp)[] [ tool catalog too large, tool registration too large, too many tools, message too large, payload too large, request too large, /request (?:body |size )?exceeds .*(?:size|limit)/, maximum allowed size, ]; export function isCursorRequestTooLargeDetail(lowerMessage: string): boolean { if (QUOTA_RATE_CUES.some(cue lowerMessage.includes(cue))) return false; return REQUEST_TOO_LARGE_PATTERNS.some(pattern typeof pattern string ? lowerMessage.includes(pattern) : pattern.test(lowerMessage), ); }判定表原始错误文本分类结果语义resource_exhausted: too many requests429配额提示优先拒绝速率限制resource_exhausted while loading tool catalog: quota exhausted429配额提示优先速率限制resource_exhausted: tool registration too large400工具目录溢出resource_exhausted: request exceeds maximum allowed size400请求过大裸resource_exhausted: Error429无尺寸模式速率/配额当前源码中该实现已落地QUOTA_RATE_CUES与REQUEST_TOO_LARGE_PATTERNS可在 cursor-errors.ts 直接看到尺寸模式集合在后续迭代中还补充了request exceeds .*size与request (?:body|size) exceeds .*(?:size|limit)两条正则。safeCursorErrorMessage保留resource[_ ]exhausted → resource limit exceeded的改写对两个前缀都无害见 cursor-errors.ts。src/lib/errors.ts中原本仅凭前缀判定的分支:102、:205不需要改动——前缀如今只会在真正的 too-large 场景出现429 路径也无需改动因为Cursor rate limit exceeded前缀已命中既有的rate limit关键字分支。测试要求端到端负例经safeCursorErrorMessage断言而非仅测谓词加入tests/cursor-errors.test.ts、tests/adapter-error-inline.test.ts与tests/request-log.test.ts请求日志行对通用场景记录429 rate_limit_exceeded。阻断项 4High真实 HTTP/2 集成测试数不清私有回调——抽取终结守卫与销毁回退问题真实 h2 集成测试无法统计私有的fail/finish回调次数也无法区分close()与destroy()。open()原本没有单次终结守卫failAndClear直接调failstreamend独立调finishrunTurn 生成器只是先读到failure/done谁就用谁。若贸然加 session 错误监听就会面临双重终结风险end 迟到的 session error、timeout session error。解决抽取两个小导出助手用注入替身做单元测试3-pre 单次终结守卫——createTerminalSettler已落地于 live-transport.tsexport function createTerminalSettler(hooks: { fail: (error: Error) void; finish: () void; clearTimer: () void; }): { settleFail: (error: Error) void; settleFinish: () void; settled: () boolean } { let settled false; return { settleFail(error) { if (settled) return; settled true; hooks.clearTimer(); hooks.fail(error); }, settleFinish() { if (settled) return; settled true; hooks.clearTimer(); hooks.finish(); }, settled: () settled, }; }open()每轮实例化一次所有终结路径failAndClear 的fail、expectedClose 的finish、首帧超时fail、streamend的finish、新增的 session 错误处理器都路由经settleFail/settleFinish。后到者只触发清定时器的安全空操作——保持 3-pre 语义与旧代码完全一致settleFail/settleFinish正是原fail/finish的返回值。3b destroy 回退——armTimeoutDestroyFallback已落地于 live-transport.tsexport function armTimeoutDestroyFallback( stream: { destroyed: boolean; destroy: () void }, session: { destroyed: boolean; destroy: () void }, graceMs: number, ): ReturnTypetypeof setTimeout { const timer setTimeout(() { try { if (!stream.destroyed) stream.destroy(); } catch { /* gone */ } try { if (!session.destroyed) session.destroy(); } catch { /* gone */ } }, graceMs); timer.unref?.(); return timer; }close()会等待在途帧而死掉的 socket 可能无视它graceMs后强制destroy()可防止停滞的 TLS 会话在超时后仍然残留。graceMs通过CursorTransportFactoryInput.timeoutDestroyGraceMs注入命名常量默认CURSOR_TIMEOUT_DESTROY_GRACE_MS 1_000测试可确定性缩小。测试分配settler 单元测试直接测四种组合fail→fail、fail→finish、finish→fail、finish→finish恰好触发一次钩子、clearTimer恰好一次destroy 回退用 fake stream/session 对象 短 grace 验证“未销毁则销毁、已销毁则跳过”。真实 h2 集成测试只保留冒烟验证不宣称回调计数断言 session 级 GOAWAY/destroy 后LiveCursorTransport.run()恰好抛出一个传输错误。阻断项 5MedCursor 分支绕过既有失败冷却——接入markModelsFetchFailure问题Cursor 模型发现失败时重复的目录轮询在故障期间每次都付出约 11.5 秒的超时代价。仓库中已有失败冷却机制markModelsFetchFailure位于 model-cache.ts但真实调用链上 Cursor 分支src/codex/catalog下多个 gather 文件绕过了它。解决在catalog.ts:1536附近的 Cursor 分支补两处读侧守卫新鲜缓存检查之后、fetchCursorUsableModels之前先查isModelsFetchCoolingDown(name)冷却中直接返回与失败后相同的 stale/configured 降级结果写侧标记发现失败时调用markModelsFetchFailure(name)。激活场景连续两次目录刷新、第一次发现失败——第二次不得再调用发现stub 调用计数为 1。该机制在当前源码中分布于 src/codex/catalog 各文件如combo-member.ts、gather-capture.ts都导入了isModelsFetchCoolingDown/markModelsFetchFailure。阻断项 6Med同一 conversationId 仅限原生/非轮转请求问题若把“同一 conversationId 贯穿整条链”做成全局规则会破坏外部模型工具结果轮转tool-result rotation——那是有意的行为。解决作用域收窄原生/非轮转请求链上各轮复用同一个_cursorConversationIdrequest-builder.ts中createCursorRequest保留已恢复的 id当前源码中request-builder.ts:371处if (parsed._cursorConversationId) return parsed._cursorConversationId正是该逻辑外部模型工具结果轮转request-builder.ts:180处有意铸造全新 id并重键用量缓存rekeyCursorContextUsage当前导出于 live-transport.ts——该行为保持不变此处“延续”的语义是新铸造的 id 才被持久化供下一轮使用。测试要求E2E 链式测试使用原生 Cursor 模型 纯文本延续断言 transport 收到相同conversationId独立用例断言外部模型轮转 重键 新 id 的持久化。由于 fake transport 无法观测模块私有的 trackerlive-transport.ts:71重键调用在适配器层覆盖createCursorAdapter的依赖新增可选rekeyContextUsage默认指向真实函数适配器单元测试注入 spy 断言轮转路径上以(oldId, newId)调用。审计轮次机制与跨阻断项约束无反驳No rebuttalsr2 的 6 个阻断项全部被设计文档接受为修订跨阻断项约束是 #4 抽取的助手保持 3-pre 语义完全一致settleFail/settleFinish成为createTerminalSettler的返回函数即后续阻断项的修订不得改变已被接受的既有语义。轮次历史r1 FAIL8 项→ r2 FAIL6 项→ r3 GO-WITH-FIXES2 项均折叠进文档修订。r3 的两个 Medium 残留——失败冷却的双侧接入、重键观察的适配器层 spy 接缝——正是上文阻断项 5、6 的最终形态。验证命令与测试面按各阶段文档验证覆盖为bun run typecheck bun test tests/responses-state.test.ts tests/cursor-request-builder.test.ts tests/cursor-protobuf-events.test.ts tests/server-combo-failover-e2e.test.ts bun test tests/cursor-errors.test.ts tests/adapter-error-inline.test.ts tests/request-log.test.ts tests/errors-adapter-failure.test.ts bun test tests/cursor-hardening.test.ts tests/cursor-live-transport.test.ts bun run test # 完整套件核心结论是所有 High 阻断项在 r3 前已全部解决两个 Medium 残留以具体文档修订折叠收官——这正是本项目“审计驱动开发”工作流spec-satisfaction repair 原型的一次完整样本。相关设计文档与源码证据路径见 devlog/_fin/260723_cursor_context_continuity、src/responses/state.ts、src/adapters/cursor/cursor-errors.ts、src/adapters/cursor/live-transport.ts 与 src/adapters/cursor/live-models.ts。赞分享【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载相关推荐OpenCodex Cursor 适配器修复实录store:false 下的会话上下文延续、resource_exhausted 错误分级与 HTTP/2 传输加固OpenCodex Cursor 适配器修复实录store:false 下的会话上下文延续、resource_exhausted 错误分级与 HTTP/2 传opencodex 三轮审计驱动修复Cursor 上下文连续性、错误分类与传输加固的 AUDIT-LOOP 实践opencodex 三轮审计驱动修复Cursor 上下文连续性、错误分类与传输加固的 AUDIT LOOP 实践 本文以 审计第三轮合成报告 https://OpenCodex Cursor 适配器上下文连续性修复对抗式审计 8 大 Blocker 的裁决与源码级修复全解OpenCodex Cursor 适配器上下文连续性修复对抗式审计 8 大 Blocker 的裁决与源码级修复全解 本篇基于 OpenCodex 仓库 dev创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表