Agent 对话、工具循环、持久化与 MCP 可靠性增强 - #1549
Open
cyfung1031 wants to merge 76 commits into
Open
Conversation
针对 scriptscat#1545:UI 对话此前硬编码 50 次工具调用上限且无法调整,达到上限 后用户误以为必须新建对话、丢失已探索的上下文。 - 新增 AgentConfigRepo(chatMaxIterations,默认 50,1-1000),Settings 页新增"对话"分类可视化配置 - 达到 max_iterations 时持久化 errorCode,聊天界面据此渲染"继续对话" 按钮,一键复用已持久化的完整历史继续执行 - 全部 8 个 locale 补齐相应文案 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
针对 scriptscat#1545 的 Token 消耗问题:长 tool loop 每轮都全量计费输入 token, 且 execute_script 返回值(如 DOM dump、模块映射)不设上限,被完整保留 并在后续每轮重复发送。 - Anthropic provider 为最后一条消息追加 cache_control 断点,使已产生的 历史前缀被缓存,下一轮仅新增部分计费(system/tools 断点已存在,此为 第三个断点,未超过 4 个断点上限) - execute_script 返回值超过 30000 字符时截断为首尾各 15000 字符并标注 truncated / original_length,避免超大返回值反复占用上下文 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
针对 scriptscat#1545:在触及 80% 的 autoCompact 阈值之前,长对话的旧 tool 结果 (如 DOM dump)会在后续每一轮都被完整重复发送,进一步放大 Token 消耗。 - 新增 elideOldToolResults:保留最近 5 轮 assistant/tool 消息原文, 更早的 tool 结果替换为占位文本;只裁剪内存中传给 LLM 的消息,不影响 chatRepo 持久化与 UI 历史 - 在 40% / 60% 两个上下文占用阈值各触发一次(而非逐轮触发),避免 频繁重写消息前缀导致 Anthropic 的 prompt cache 断点失效 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
针对 scriptscat#1545 的 Loop Guard 诉求:现有 tool_call_guard 检测到重复/死循环 模式时只向 LLM 注入提醒,LLM 不理会则持续烧 Token,用户无法介入。 - ToolLoopOrchestrator 新增 askUserForGuard 可选回调:循环检测连续命中 达到 2 次时暂停循环,询问用户"继续"或"停止";回答停止则以 done(非 error)优雅收尾并持久化提示 - 仅 UI 对话(含后台会话,复用既有 ask_user 事件/resolver 机制,5 分钟 无人应答默认继续)传入该回调;定时任务与子代理不传,保持原有的 仅告警不暂停行为,避免无人值守场景被阻塞 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
针对 PR scriptscat#1549 的审查反馈: 1. 【阻断性】继续对话时,历史中 max_iterations 错误占位消息(content 为空字符串)此前会被完整重放进 LLM 请求,部分 provider(如 Anthropic)会因空 content 拒绝请求。buildAndPersistUserMessage 现在 跳过带 error 字段的历史消息,仅用于 UI 展示,不进入 LLM 上下文。 2. 循环检测升级提问 5 分钟无人应答超时后,此前只删除 resolver,未清除 后台会话的 rc.pendingAskUser,导致后续 attach 的 UI 仍会看到已失效 的提问,回答会静默无效。超时回调现在同步清除 pendingAskUser。 3. chatMaxIterations 的合法范围此前只在 Settings UI 校验, AgentConfigRepo 直接读写未归一化的原始值;损坏的 storage、旧版本 遗留值或绕过 UI 的写入都可能导致 0/负数/超大值,进而导致循环立即 报错或失控运行。新增 normalizeChatMaxIterations,在 getConfig / saveConfig 中统一归一化到 [1, 1000];ChatService 解析最终值时改用 ?? 并对结果做同样的兜底截断。 4. 循环检测升级的连续命中计数此前永不重置,用户选择"继续"后,此后 每一次告警都会重新暂停询问,比"连续命中 2 次暂停"更激进。现在回答 继续后重置计数,需再次连续命中 2 次才会重新暂停。 均已按 TDD 补充回归测试(先复现失败用例,再修复)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment was marked as outdated.
This comment was marked as outdated.
2 tasks
This comment was marked as outdated.
This comment was marked as outdated.
This comment has been minimized.
This comment has been minimized.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
cyfung1031
marked this pull request as draft
July 12, 2026 00:45
每轮 LLM 调用前用当前 messages + 工具定义重新估算体积,超出安全阈值时立即裁剪; 不再仅依赖上一轮响应的 usage 反馈,避免巨大 tool 结果在下一次请求发出前就把上下文撑爆。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
file/audio 从不被 provider 内联,image 只在 vision 模型上才会解析为 data URL; 按类型和模型能力估算体积,避免非 vision 场景或无法读取的附件把预算估算撑到 Infinity。 裁剪后的占位文本保留 type/attachmentId(OPFS 路径),而非完全丢弃可恢复信息。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
非流式与流式适配器都只处理了 tool_call_start/delta,从未处理 tool_call_complete 和 new_message,导致 ephemeral 历史中的 toolCalls 没有结果/终态,且多轮对话被压平成一条 消息、最终回复重复追加。按轮次边界重建 assistant + 对应 tool 消息,并在 StreamChunk 新增 tool_call_complete/new_message 类型透传给用户脚本。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
orchestrator 的 callLLM/autoCompact 原生失败只 throw、不 sendEvent,导致实时 UI 收不到 子代理的终态事件、卡在 running 直到刷新;在捕获异常时补发一次 error 事件(若尚未上报过 终态)。同时 tool_call_complete 记录状态改为 event.status ?? "completed",避免持久化后 把失败的嵌套工具显示为成功。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
后台会话 attach 收到 askUserResponse 后自己广播一次 ask_user_resolved,resolver (ask_user.ts / 循环检测 loop-guard)自己又广播一次,同一次回答产生两条终态事件; loop-guard 超时路径也是先发 ask_user_expired 又紧接着触发 ask_user_resolved。 改为终态事件只由 resolver 在恰好一处发出,attach 不再重复广播,超时/abort 只发 expired,只有真实回答才发 resolved。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…treating them as opaque api_error The byte-count token estimator is a conservative heuristic that can still underestimate against arbitrary provider-compatible models, and the hard admission check trusts it to gate context_too_large before ever calling the provider. When the estimate underestimates and the provider itself rejects the request for exceeding its context window, that error had no dedicated classification and fell through to the generic "api_error" bucket. classifyErrorCode now recognizes the common context-length-exceeded phrasing used by OpenAI/Anthropic and compatible APIs and reports context_too_large, giving callers the same recovery signal whether the local estimate or the provider caught the oversized request. Full exact-token-count guarantees across arbitrary models remain out of scope; this is the advisory-estimate fallback path the finding calls for. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
这些引用只在本轮审计对话里有意义,脱离上下文后既不利于阅读代码, 也无助于代码评审或后续维护。保留每条注释里真正解释"为什么"的实质 内容,只删除指向内部编号的追溯性标注;测试用例标题同理,只去掉 "【finding N 回归】" 前缀,描述性文字本身保持不变。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
审查本 PR 引入的注释:删除只是复述紧邻下一行代码(如 "发送 done 事件" + sendEvent(...)、"继续循环" + continue)的注释;删除与函数 JSDoc 内容重复的方法体内说明(SessionToolRegistry 隔离性、load_skill 预 加载副作用);删除 tool_loop_orchestrator.ts 中一处描述已经错位、 不再对应下方代码的过时注释;修正一处中英混排的错别字 ("先positively" -> "先明确")。保留的注释都解释了非显而易见的 "为什么"(并发/竞态不变量、二义性提交处理、附件所有权规则等), 未做改动。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…tinue double-click, fix AskUserBlock option key - tool_registry.ts: script-tool结构化结果的附件保存失败此前只在 signal.aborted 时才落为 error结果,OPFS配额超限等其他失败会被误判为"非JSON/非结构化结果"回退到按原始字符串处理, 从而把附件其实未写入的失败当作成功上报。拆分 JSON.parse 与 saveAttachments 的错误处理, saveAttachments失败一律落为error结果。 - ChatArea.tsx: "继续对话"按钮点击后依赖异步 isStreaming state 去重,同一渲染帧内的两次 点击可能都读到旧值 false 从而各自发起一次 handleSend,产生重复会话/端口泄漏。新增同步 ref 在点击时立即锁定,避免重复提交。 - AskUserBlock.tsx: 选项列表 key 使用 label(opt) 而非 index,当 optionValues 使 两个选项标签相同但取值不同时会导致 React key 冲突,可能把选中状态错误应用到另一个选项。
This comment has been minimized.
This comment has been minimized.
…t truncation boundary 大结果截断时按原始字符偏移量二分定位首尾保留区间,未考虑 UTF-16 代理对:截断点落在 emoji 等非 BMP 字符中间会切出孤立高/低代理项,JSON.stringify 虽仍能转义输出但字符本身 已损坏。截断边界落在代理对中间时向内收缩一位,改为完整排除该字符。
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
三个 High、五个 Medium、一个 Low:
- tool-round 提交确认读失败(indeterminate)时不再直接当作成功发布:先幂等重试一次
commitToolRound,仍无法确认落盘状态才以 persist_indeterminate 终止,不删除可能仍被
引用的附件。
- CAT.agent.task.update()/remove() 现在强制要求调用方携带 get()/list() 返回的
generation/revision;服务端对 update/delete 也不再把版本号当可选项,缺失时直接拒绝,
避免 update 被 CAS 误判失败、也避免过期引用 stale-delete 掉被重建的新任务。
- Options 的 chat/attach/compact/clearMessages/deleteMessages 现在都携带当前会话的
generation;服务端 compact 分支补上了与其它分支一致的 generation 校验,堵住了此前
"过期标签页可以操作被删除重建的同 ID 会话" 的漏洞。
- Options 新建任务不再允许选择 event 模式(没有可注入的目标脚本 UUID);service 端对
create/update 统一校验 sourceScriptUuid 必须指向已安装脚本。
- 会话任务列表持久化为 { generation, revision, tasks } 快照,会话 ID 复用且旧任务文件
清理失败时不会再读到上一代的任务。
- 生成数据丢失的 warning 现在覆盖所有路径:带工具调用的 assistant 消息、工具轮开始前的
即时提示、子代理详情持久化、ChatArea 的子代理分支、以及非流式/流式 userscript API。
- 上下文预算的字节数启发式估算不再是终审:裁剪到底后仍超预算但未达到 2 倍安全阈值时,
放行给 provider 自行判定,只有极端超限时才本地拒绝,避免估算偏差误伤本可放下的请求。
- 并行 agent 工具调用现在通过显式 toolCallId → agentId 映射定位子代理状态,不再靠"猜
第一个正在运行的子代理"。
- 编辑用户消息时新增的附件预览 URL 保存成功后会被释放,组件卸载时也会清理,不再泄漏。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Collaborator
Author
|
本轮已完成同步与收尾,当前 PR head 为 经复现、回归测试和 3 个 subagent 的 fresh review,按 PickInvariant 逐项检查后处理了以下确认有效的问题:
验证结果:
本轮 fresh review 中未能以可重复证据确认的风险(例如 GC 失败后的持久化重试债务、跨上下文全局扫描锁竞争、极窄的取消时序窗口)没有扩大改动范围;现有实现对这些情况采取安全保留/失败关闭策略。 |
cyfung1031
marked this pull request as ready for review
August 11, 2026 07:22
Collaborator
Author
|
@CodFrm 你人手实测吧 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist / 检查清单
背景
本 PR 关联 Issue #1545,解决内置 Agent 在长时间、高频 Tool Calling、后台任务和会话续接场景中的可靠性与 Token 成本问题。
原始需求包括:
在后续实现和审查中,又确认并处理了跨上下文写入、OPFS 提交确认、会话 ID 复用、任务快照代际隔离、MCP 连接生命周期以及结构化错误传播等问题。分支已同步最新 main,并完成了与官方 MCP SDK 迁移相关的冲突语义合并。
本次改动 / 实现内容
1. 可配置最大工具调用次数,并支持原会话继续执行
chatMaxIterations:errorCode: "max_iterations"的错误消息;2. 降低长 Tool Loop 的上下文和 Token 成本
execute_script按最终 JSON envelope 限制结果大小,保留首尾内容并标记截断;截断不会拆分 UTF-16 surrogate pair,也覆盖引号、反斜杠等转义内容。context_too_large,并保留可信的结构化错误码。3. Loop Guard 与用户介入
continue/stopaction,拒绝任意自定义输入。4. 取消、终态和跨上下文生命周期
done/error/tool_call_complete等终态事件。5. OPFS 持久化、会话代际与任务 CAS
6. MCP 连接与结果处理
@modelcontextprotocol/sdk客户端实现,并保留 request AbortSignal。listTools失败时关闭临时 client,不注册半成品工具。a-b/a_b等碰撞。structuredContent在普通结果、image 附件、ToolRegistry 归一化和结构化错误路径中均得到保留。7. 用户脚本 API、类型和 UI
实现考虑
兼容性
execute_script、上下文裁剪和 Anthropic cache 不改变 OPFS 中的原始消息。已知限制
require警告,不是本 PR 新增的失败。建议审查重点
关联
验证 / Test plan
pnpm run test:ci— 完整测试套件通过。pnpm run lint:ci— Prettier、TypeScript、i18n、issue-template 检查和 ESLint 全部通过。pnpm run build— 生产构建成功;仅有已知 bundle size 与 Monaco 动态require警告。Screenshots / 截图
N/A — 本 PR 没有新增页面视觉设计或需要截图的 UI 变更。