
qwen-code 模拟 sed -i 编辑与文件历史回滚从命令解析到 /rewind 的完整链路【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读sed -i是 AI 编码代理在终端中最常发出的就地编辑命令之一但它原本以不可见的 shell 命令形式执行——用户看不到将要发生什么改动改动也不进入 qwen-code 的文件历史更无法通过/rewind回滚。qwen-code 提供了一套模拟 sed 编辑能力对满足条件的简单sed -i替换命令将其解析、仿真为一次文件编辑Edit/WriteFile 同款路径展示文件级 diff 供审批并纳入文件历史快照以便回滚对无法安全仿真的 sed 形式则原样退回 shell 执行。本文以 2026-06-15-simulated-sed-file-history.md 的 E2E 测试计划为主线结合 sedEditParser.ts 与 shell.ts 的源码实现完整梳理这条链路。一、功能目标与设计边界按照该 E2E 测试计划的 Goal该功能的验证目标有三点预览一个简单的sed -ishell 编辑应被预览为一次文件编辑追踪改动应被记录进文件历史file history回滚改动应可通过/rewind撤销。同时Expected Result 明确了两条边界简单的单文件替换被当作 Edit/WriteFile 一类变更追踪并支持回滚不支持的 sed 形式如 glob 多文件替换保持原有的 shell 行为不变。这一能仿真就仿真、不能仿真就退回真实 sed的降级策略是整套设计的安全底线仿真失败时的兜底是真实执行真实 sed行为与用户显式输入完全一致绝不静默改写文件。二、sed 命令解析什么命令值得仿真模拟链路的第一步是把命令字符串解析为结构化信息实现在 packages/core/src/utils/sedEditParser.ts。入口函数parseSedEditCommand(command): SedEditInfo | null返回null即表示不仿真走 shell。SedEditInfo结构包含五个字段sedEditParser.ts#L13-L19字段含义filePath目标文件路径pattern替换模式replacement替换文本flags替换标志如g、数字 occurrenceextendedRegex是否为扩展正则-E/-r2.1 命令词法解析解析器先用shell-quote的parse对命令做词法切分sedEditParser.ts#L26-L31再逐个 token 分类。任何无法稳定翻译成 JS 正则的语法都直接返回null支持的就地标志-i可携带空后缀参数、--in-place、--in-place以及安全合并形式-Ei/-ri即i必须位于末尾见 isSafeCombinedFlagArg拒绝的备份后缀形式-i.bak、--in-placeSUFFIX以及-Eir、-riE这类会把r/E当作备份后缀的合并标志——仿真不产生备份文件用户要求了备份却没得到属于静默行为偏差因此直接降级支持的正则标志-E、-r、--regexp-extended支持的表达式写法裸表达式、-e EXPR、--expression EXPR、--expressionEXPR拒绝未知选项任何以-开头但不属于上述集合的参数一律返回null。2.2 表达式级校验表达式部分只接受形如s/pattern/replacement/flags的替换parseSubstitutionsedEditParser.ts#L379-L440并做如下限制不含换行符必须严格以s/开头flags仅支持/^[g0-9]*$/且最多一段数字即g或单个 occurrence 数字如s/foo/bar/2替换文本中的转义仅允许\/、\、\\、\1-\9hasUnsupportedReplacementEscape表达式与文件路径中不得包含 shell 变量引用反引号、$VAR、${}、$(等见 hasShellVariableReference文件路径不得以~开头或包含$sedEditParser.ts#L127-L132。2.3 正则翻译与语义保真检查即便通过了上述语法检查解析器仍要保证仿真结果与真实 sed 一致否则宁可退回真实 sed。toJavascriptPattern负责把 POSIX BRE基本正则翻译为 JS 正则BRE 操作符 ? | ( ) { }需要反斜杠转义、^/$在非首尾位置需转义为字面量sedEditParser.ts#L505-L559。随后canCompileSedPatternsedEditParser.ts#L178-L201执行四项语义分歧检查命中即拒绝仿真POSIX 字符类[[:alpha:]]这类语法在 JS 中无对应语义首部]字面量sed 中[]的]是字面成员而 JS 中[]是永不匹配的空类、[^]匹配任意字符——仿真会造成静默丢字节源码注释明确以s/[^]]/X/g为例必须拒绝sed/JS 分歧转义\\\d\w\D\W\S等转义在两种引擎中含义不同超出捕获组数量的反向引用替换文本中出现\N而模式中捕获组不足时sed 与 JS 行为不一致。此外hasUnsafeQuantifiedGroupsedEditParser.ts#L276-L330会拒绝带交替或嵌套量词的被量词化分组避免 BRE→JS 翻译时出现灾难性回溯等不可控行为。三、Shell 工具中的仿真执行链路解析与仿真的实际挂载点在 packages/core/src/tools/shell.ts 的ShellToolInvocation类中。整体流程如下3.1 判定与前置条件getSedEditInfo()shell.ts#L1699-L1711会先排除两类命令后台命令is_background带环境变量前缀的命令如LC_ALLC sed ...通过LEADING_ENV_ASSIGNMENT_RE检测。随后用stripShellWrapper剥离可能的 shell 包装如bash -c ...后再解析避免把echo foo | sed ...之类管道场景误判为可仿真命令。3.2 准备阶段读取、仿真、生成 diffprepareSedEditshell.ts#L1738-L1766完成解析目标文件绝对路径相对路径基于params.directory或配置的目标目录解析拒绝符号链接目标——仿真写入会绕过链接直接降级回真实 shell执行先读后写检查checkSedPriorRead文件未被读取过则拒绝仿真保证 Agent 不会对未读文件盲目修改读取原文件内容调用applySedSubstitution(content, sedInfo)生成新内容记录originalContent/newContent与行尾line ending元数据供写入时保持一致。applySedSubstitutionsedEditParser.ts#L471-L503按行执行替换正确处理g全局替换与数字 occurrence如s/foo/bar/2只替换第 2 处空匹配与零宽匹配的边界避免死循环式无限替换捕获组反向引用与整体匹配引用buildReplacementsedEditParser.ts#L626-L654。随后makeSedEditDisplayshell.ts#L1768-L1789生成标准FileDiffunified patch 变更统计这正是测试计划第 4 步所要求的从foo foo到bar bar的文件 diff 审批界面。3.3 审批与写入executeSedEditshell.ts#L1864-L2019是核心流程二次读取校验若文件在审批期间被外部修改originalContent变化返回FILE_CHANGED_SINCE_READ提示重新读取后重试shell.ts#L1899-L1907无变化短路仿真结果与原内容一致时返回sed edit made no changes to ...不写盘对应测试 shell.test.ts#L1292文件历史备份写入前调用getFileHistoryService().trackEdit(filePath)记录备份。这是 best-effort 操作——即使备份失败也不阻断写入只记录 warn 日志shell.ts#L1935-L1949对应测试 continues applying a simulated sed edit when file history tracking fails写入文件通过writeTextFile落盘toolWriteOrigin: shell_sed_edit标记来源shell.ts#L1956-L1961后续元数据非用户改写的仿真编辑会记录 commit attribution并刷新文件读缓存两者失败均非致命错误映射写盘失败按EACCES权限、ENOSPC磁盘满、EISDIR目标是目录等映射为对应ToolErrorType。3.4 取消与超时语义由于仿真编辑涉及多步异步操作读文件、备份、写盘代码通过waitForSedOperation接入 AbortSignal 取消传播shell.ts#L1827-L1862审批前取消 → 返回Command cancelled by user超时 → 返回EXECUTION_TIMEOUT错误写盘开始后才收到取消 → 仍等待写盘完成让返回结果反映磁盘真实状态shell.ts#L1954-L1955对应测试 awaits an in-flight sed write after cancellation starts。四、文件历史与 /rewind 回滚4.1 文件历史快照文件历史由 packages/core/src/services/fileHistoryService.ts 的FileHistoryService实现。核心模型是按 turn 快照FileHistorySnapshot以promptId为键包含trackedFileBackups每个被追踪文件的备份路径、版本号、时间戳与timestampfileHistoryService.ts#L41-L45trackEdit(filePath)fileHistoryService.ts#L667在写入前复制原文件作为备份供后续回滚使用快照序列化/反序列化serializeSnapshot/deserializeSnapshots支持持久化供跨会话恢复。正是executeSedEdit在第 3 步调用的trackEdit让 sed 仿真的改动与 Edit/WriteFile 一样进入快照体系——这就是测试计划第 7、8 步/rewind后file.txt恢复为foo foo能够成立的根基。4.2 /rewind 命令/rewind是内置斜杠命令定义在 packages/cli/src/ui/commands/rewindCommand.tsname: rewind、altNames: [rollback]仅在 interactive 模式可用触发一个rewind对话框。FileHistoryService提供了rewind相关的恢复实现如applySnapshot、restoreFromSnapshots及filesFailed机制——备份捕获失败的路径会在回滚时显式报告而不是静默用过期备份冒充当前内容见 fileHistoryService.ts#L28-L39 的failed字段设计。五、E2E 手动验证流程5.1 主流程Main Flow对照 2026-06-15-simulated-sed-file-history.md 的手动步骤结合源码可得到如下验证要点准备创建临时项目file.txt内容为foo foo启动在该项目内启动 qwen-code请求让 Agent 执行sed -i s/foo/bar/g file.txt预览权限界面应展示从foo foo到bar bar的文件 diff而非单纯的 shell 命令确认——这一步验证parseSedEditCommand命中仿真路径且makeSedEditDisplay生成了正确的FileDiff批准同意该编辑确认file.txt内容变为bar bar验证applySedSubstitution的g全局替换与writeTextFile落盘回滚执行/rewind回到 sed 编辑前的 turn恢复file.txt恢复为foo foo验证trackEdit备份与快照恢复链路。5.2 回退流程Fallback Flow在同一项目中让 Agent 执行sed -i s/foo/bar/g *.txt确认该命令走普通 shell 确认路径——glob 多文件编辑故意不仿真取消该命令。这一行为与解析器的实现完全一致parseSedEditCommand对第二个 token 解析出*.txt作为filePath时虽可能通过语法检查但多文件 glob 属于测试计划明确声明的unsupported sed forms因此降级。此外-i.bak备份后缀、符号链接目标、未先读文件、后台执行、环境变量前缀命令等场景均有对应的降级测试见 shell.test.ts 附近的 falls back to shell execution for ... 系列用例。六、设计取舍与运维启示从源码可以总结出这套模拟机制的几个关键设计原则安全优先宁降级不错改所有翻译可能产生行为分歧的 sed 构造POSIX 字符类、首部]、特殊转义、超范围反向引用、备份后缀一律拒绝仿真。正如 sedEditParser.ts 注释所言errring toward the real sed costs nothing but a fast path; erring the other way rewrites a file wrongly偏向真实 sed 只是失去快路径而偏向仿真则可能错误改写文件。文件历史是尽力而为trackEdit失败不阻断编辑写入避免 shell 兼容编辑被文件历史系统拖垮但/rewind对缺失/失败的备份有显式报告机制不会静默用过期内容冒充当前状态。一致性校验兜底审批后写盘前再次比对文件内容防止读-改-写期间的并发修改造成覆盖丢失。取消语义完备取消传播贯穿读文件、备份、写盘全流程且写盘启动后以磁盘状态为准。对于使用者来说这意味着日常的简单单文件替换sed -i s/foo/bar/g file.txt可以享受到与编辑器写入同等的审批 diff、文件历史、/rewind回滚体验而复杂 sed 脚本、多文件 glob、备份后缀等高级用法依然走真实 shell行为与手动执行完全一致可以放心使用。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考