Repository navigation
fix(chat): retain project-relative filenames in visible answers - #5975
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
REQUEST_CHANGES — [P1] 私有目录的等价路径绕过文件名脱敏。 精确 head:eefc9dee93eded774d1aacbca441dfbd798d7ba4;基础版本:9ba6148fccf335f2e38d800812dbb398008229c6。
动机
前端和飞书 Chat 用户在核对生成的文件时,需要从回答知道文件的相对位置。原先普通项目文件会整体变成脱敏标签,用户还要重新询问文件名。普通项目文件从脱敏标签变为相对文件名,可以直接核对产物。预期改善是保留普通文件的位置,同时继续隐藏独立声明的私有目录。此次边界仅是回答展现,不改变读取、写入、执行或权限门禁。普通文件的改善已验证;私有目录仍有下述回归,因此还不能交付整个承诺。
改动思路
改动复用现有回答解析、Markdown 修复和流式过滤的脱敏 owner,结构化状态、提议和权限门禁继续走默认完整脱敏。这比在飞书和前端分别增加替换规则更容易维护;也没有新增持久状态、命令或 provider 权限。最小且合适的边界是共享回答展现 owner,先判定规范化后的私有路径,再决定是否显示普通项目文件。当前 PR 的完整边界应是普通文件可核对、额外保护目录及项目外路径始终隐藏,并由相同规则覆盖最终回答与每次流式分块。
然而当前按原始字符串先匹配私有根,再允许项目根下任何不含父目录跳转的后缀。不同字符串可能指向同一个文件,单独排除父目录跳转不等于守住私有子目录。应在这个 owner 内补上规范化比较或保守隐藏,保留既有 Markdown 处理;不需要新增协议、全局配置或第二套路径决策源。边界相关的未来维护检查已做:值得补的是统一的路径比较语义及其真实入口负例;不建议扩张为新框架或无关语言迁移。
具体改动
全量 diff 为三文件,79 行新增、5 行删除:loopx/chat.py 增加展现参数并切换三处回答调用;tests/test_chat_path_redaction.py 补充普通文件、Windows、空格根、嵌套私有根、结构化提议、跳转路径和长分块案例;用户指南增加中英说明。新参数默认关闭只约束直接调用者;现有最终回答和流式路径已经明确启用,因此这是所有这些回答入口的默认展现变化,不能表述成用户需要打开的可选能力。
没有找到为本次“回答保留相对文件名”另立的书面验收规范;指南新增文字也是本次待审内容,不能作为独立验收证明。基础版本的脱敏 owner、现有用户指南的私有路径边界和实际调用者提供本轮对照。没有把未来 RFC、作者测试数量或本次新说明当作已满足验收。
关键代码讲解
_protected_path_replacements,loopx/chat.py:34:把第一个根标为项目,其余根标为私有,并按字符串长度优先匹配嵌套根。这是既有规则,调用者的保护根仍是输入权威;问题是比较使用原始拼写,不能识别路径等价。redact_local_paths,loopx/chat.py:145,新增分支 164–169:匹配项目根后,将后缀改成相对文件名,并检查解码后的组件中是否含父目录跳转。最终返回是可见文本;结构化调用不启用该分支。新增分支没有重新验证规范化后是否进入额外保护根,导致下述负例。redact_response_markdown,loopx/chat.py:177:既有本地内联链接仍保留可读标题,代码中的 Markdown 不作为链接改写;最后调用新的相对展现模式。普通正文的最终回答获得文件名,链接目的地仍按既有展示修复处理。VisibleResponseStreamFilter._accept_visible,loopx/chat.py:312:安全分界点及最终尾块都启用同一模式,原有长路径缓冲和审查 envelope 隔离仍在。因而新分类错误会同时出现在流式和最终文本,而非最终整理时才暴露。parse_agent_response,loopx/chat.py:571:完整 envelope 进入normalize_agent_response,普通文本和缺失关闭标签走既有保守分支。生产 ACP 的start_turn在loopx/chat_acp.py:324分别发送answer.delta和answer.final,同时传项目根与 agent 工作根;chat_runtime.py的 endpoint 分支实际加载这个 adapter。Codex、直接 provider 和 Goal Chat 也复用这组 parser/filter,未新增并行决策源。
对主干的风险
P1,定位 loopx/chat.py:164–169。 给出保护根 /fixture/project 与 /fixture/project/runtime。同一个真实文件可由 /fixture/project/./runtime/synthetic-secret.md 或 /fixture/project//runtime/synthetic-secret.md 表示,独立 filesystem resolve 验证它们与规范拼写指向同一私有文件。基础版本最终文本及所有两段分块均隐藏文件名;本 head 分别返回 ././runtime/synthetic-secret.md 和 ./runtime/synthetic-secret.md。结构化脱敏仍隐藏,直接规范拼写也隐藏,所以已有正例不会发现这个错误。
我还通过真正的 ACP subprocess/stdio adapter 发送合成通知:基础版本的 answer.delta、answer.final 隐藏文件名,本 head 的两个事件都暴露它。没有调用付费模型、读取实际私有文件、安装候选 Bot 或声称模型已经采纳。这里证明的是生产 transport 到展现 owner 的行为,合成 provider 不证明真实模型输出频率。
最小修复:在允许项目相对展现之前,按平台一致的、保守的路径比较语义处理点组件及重复分隔符,并重新判定所有额外保护根;或者对这些非规范拼写完整隐藏。保留普通项目文件相对名、项目根标签、Markdown 标题、结构化完整脱敏以及现有跳转拒绝。回归测试必须同时覆盖规范私有路径、两种等价拼写、普通文件、全部两段流式切分,以及生产 ACP start_turn 的最终/流式事件。修改完成后重跑下列五文件测试及独立等价路径负例,不能仅添加一个 helper 正例。
本 head 五文件本地 pytest 194 passed,相同基础版本命令 186 passed;Ruff 和 diff whitespace 检查通过。独立隐私 oracle 在基础版本退出 0,在 head 退出 1,失败仅为点组件、重复分隔符及 ACP 可见输出。测试命令为 uv run --no-sync --extra test python -m pytest -q tests/test_chat_response_parsing.py tests/test_chat_path_redaction.py tests/test_chat_activity.py tests/test_chat_agent.py tests/test_lark_private_progress.py。
mypy 原始命令 uv run --no-sync --extra test mypy loopx/chat.py 在两个版本均报告 4633 个错误;保留路径、错误正文、代码与重数,仅规范化行号后身份和细节完全一致。--follow-imports skip 在两个版本均为三个相同的既有错误。它们不能写成“mypy 通过”,也不是此次隐私回归的原因;未扩张本 PR 去修复其他模块。公开 diff 扫描中的 home 路径是合成项目外负例,未发现真实机器路径、私有 Goal 或凭据。未查询或等待 CI。
语义与 CI 对齐
本次复用现有局部路径分类 owner,不引入共享枚举或更广的 actor 生命周期。回答展现不授予文件访问;私有子目录必须隐藏是机器执行的投影边界,不能靠说明文字或模型自律补救。该边界的反例就是当前阻塞,CI 是否结束不改变结论。状态、提议、门禁仍采用原契约,不能以这些通道安全推断回答也安全。修复后需重新核验整个 PR 的两方向行为:保护私有文件,并让普通文件仍能用于核对。
我的整体评价
目标有真实价值,改动规模与复用现有 owner 的选择合理;无需为了这次展现修复建立新能力。long_horizon:未改变调度、重试、持久承诺或 later-turn 入口,保留;user_experience:普通文件核对减少一次追问,但私有文件名暴露构成回归。全量基础/head 对照判为 unintended drift;整项承诺尚未达成,而非“测试绿即可合并”。保留默认完整脱敏模式有明确结构化消费者,并非无用兼容壳。
阻塞只针对上述已复现路径;未把未测试的真实 Bot 安装、渲染 viewport、模型理解、Windows filesystem 或符号链接别名宣称通过。当前候选未安装,回滚可移除回答模式的三个启用调用;修复仍应留在同一个展现 owner。重新评审需要新 exact head、完整差异和私有等价路径/普通文件/结构化模式的真实入口对照。当前不批准,也不执行合并。
English verdict: REQUEST_CHANGES - eefc9de exposes separately protected filenames through dot-component/repeated-separator aliases in final and streamed answers. Reproduced at the real ACP stdio entrypoint against base 9ba6148; head 194/base 186 tests and Ruff pass, both mypy runs retain identical pre-existing errors. Normalize or conservatively redact aliases before allowing relative presentation, then rerun the full affected boundary. CI was not consulted.
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
APPROVE — 当前完整 PR 的私有路径回归已修复。 精确 head:86db3538d01c97864cf6c567357339329b02a974;基础版本:9ba6148fccf335f2e38d800812dbb398008229c6;上一轮问题版本:eefc9dee93eded774d1aacbca441dfbd798d7ba4。本轮重新评审完整基础/head 差异,并单独核对上次 review 后的修复;没有继承旧结论。
动机
前端和飞书 Chat 用户核对生成的文件时,需要知道文件在项目中的相对位置。普通报告原先只显示脱敏标签,现在显示 ./report.md;额外保护目录的等价路径仍隐藏,避免为了核对产物而泄露私有文件名。已验证普通文件相对名可读,私有目录的规范、点组件、重复及编码分隔符写法均隐藏;打包前端刷新后仍读回同一结果。本次只改变回答展现,不增加文件读写、执行、账户或合并权限,也不宣称候选已安装到实际 Bot。
上一版已经改善普通文件名,但会暴露私有目录的等价路径。此次检查同时保留普通文件的可读性、私有文件名隐藏和结构化字段完整脱敏;不是只重跑上一条负例。普通用户在原有对话中即可读文件名,不需要新开关、重新输入项目、额外确认或再次询问。
Chat readers need the relative location of an ordinary generated file. The base hides the whole path; the candidate makes ./report.md useful while continuing to hide separately protected roots. The previous candidate leaked private filenames through equivalent spellings. The repaired whole PR now preserves both usability and privacy at the tested production boundary. This is presentation only, with no additional filesystem, execution, account or merge authority and no claim of installed Bot adoption.
改动思路
选择现有共享回答展现 owner:在允许相对文件名之前先按路径组件检查额外保护根,结构化调用继续完整隐藏。当前 PR 的边界是普通项目文件名可核对,额外声明的私有根、项目外路径及父目录跳转继续隐藏;最终文本和流式分块复用同一规则。
这是既有 Python 回答展现与 transport 的局部规则,未增加通用控制面决策、持久状态、配置或 provider。维持共享 owner 比给前端、飞书和每个模型 adapter 分别加规则更容易维护。最强的不交付理由是“单位测试全绿仍可能隐藏泄漏或过度隐藏”:我用相同输入重放基础、旧问题和修复版本,并测试与私有目录仅共享名称前缀的普通目录,防止修复反向扩大隐藏范围。
相关的未来维护检查已应用:原来按字符串比较的私有边界在现有 owner 内补上组件比较,不建立第二个权限源或泛化文件系统框架。没有为了语言偏好强制重写稳定的展现层。project_relative 只改变字符串,既不访问文件,也不认证任意符号链接的文件系统身份。
The existing shared presentation owner is the right boundary. Decode separators, ignore redundant empty/dot components, then check private component prefixes before returning an ordinary relative filename. Keep structured consumers on full redaction. A smaller ordinary-only branch previously failed privacy; per-transport copies would duplicate the rule. The bounded related refactor is applied in the same owner; no speculative framework, state migration or unrelated language rewrite is needed.
具体改动
完整 PR 三文件,161 行新增、5 行删除:loopx/chat.py 为 33/4,路径测试为 119/1,用户指南为 9/0。上一轮之后增加 13 行生产代码与 69 行测试,围绕同一私有根比较和真实 ACP 回归。前一个版本的展现参数、普通文件行为及 Markdown 路径也重新核验。
没有找到本次相对文件名展现另立的独立书面验收规范;新增指南也是待审内容,不能自证正确。判断依据是实际核对文件的请求、基础版的私有根保护规则与生产调用者的输入契约。中英指南披露自动改变现有回答展现,并说明没有文件访问权限变化。直接 helper 的默认参数关闭保留结构化消费者;最终回答和流式调用明确启用,因此不能把它描述成用户需要激活的可选能力。
关键代码讲解
_protected_path_replacements,loopx/chat.py:34:第一个保护根表示项目,其余根表示私有位置,既有最长原始根优先仍保留,并处理 JSON 转义拼写。调用者给出的根仍是权威输入,未引入从文件名猜测权限的第二套来源。redact_local_paths,loopx/chat.py:145:path_parts先解码、统一分隔符、移除点与空组件,对 Windows drive 拼写做大小写归一;允许相对展现前重新比较全部私有根的组件前缀。私有路径立即隐藏,父目录跳转仍保守隐藏,runtime-copy不会因名称前缀被误判成runtime。默认完整模式保留状态、提议和门禁语义。redact_response_markdown,loopx/chat.py:190:已有平衡的本地内联链接保留可读标题,代码区不误作链接;最终调用答案展现模式。普通正文保留相对文件名;本地链接最终仍按既有可读标签规则处理,没有授予打开文件的能力。VisibleResponseStreamFilter._accept_visible,loopx/chat.py:325:已有安全分界与尾块缓冲都走相同的脱敏 owner。所有两段切分位置都核验,而非只检查完整回答;长路径缓冲和审查 envelope 的隔离继续保留。parse_agent_response,loopx/chat.py:584:完整 envelope 进入归一化,普通、损坏及缺失关闭标签的路径仍保守保留文字、拒绝提议/权限门禁/动作。生产ACPStdioAdapter.start_turn在chat_acp.py:328/416同时给出项目与 agent 工作根,分别产生流式和最终事件;Codex、provider、Goal 与 DSH 复用共享解析规则。
The whole diff is three cohesive files, including tests and bilingual disclosure. The repair since the previous review is a local normalized-component guard plus stronger regressions, rather than a new capability. The production callers are unchanged and continue to share the parser/filter. No independent new written specification was found; the guide is reviewed disclosure, not an independent oracle. The ordinary-file task and existing caller-protected-root contract supply the expected behavior.
对主干的风险
此前 P1 的全部触发条件已逐项对照:保护根 /fixture/project 与 /fixture/project/runtime,同一真实私有文件的规范、/./runtime 和 //runtime 写法通过文件系统 resolve 确认身份一致。原问题版本的最终文本、所有两段切分与真实 ACP 输出暴露文件名;基础版和本 head 隐藏。编码点组件与编码分隔符也隐藏,Windows 文本大小写/分隔符案例进入相关测试。普通文件、后续新文件、私有根名称前缀相似的普通目录保持可读,项目外路径与父目录跳转保守隐藏。
真实 ACP 检查使用实际 Python subprocess/stdio adapter 和所选解释器,模型通知是合成夹具。同一套 14 类输入与 provider 脚本在三个不可变 revision 上运行,夹具 SHA-256 相同:5879e570a69c6c8d69356c3d2432ce3c2efbdf8782d4c9f2954119b85a0b5eb4。独立隐私 oracle:基础版退出 0,旧问题版退出 1,当前 head 退出 0。旧版失败涵盖点组件、重复、编码点/分隔符与 ACP 事件;修复版普通文件正例仍通过。检查确实能检测原问题,不是从新实现的输出复制期望值。
本 head 五文件本地 pytest 203 passed,同命令基础版 186 passed。命令:uv run --no-sync --extra test python -m pytest -q tests/test_chat_response_parsing.py tests/test_chat_path_redaction.py tests/test_chat_activity.py tests/test_chat_agent.py tests/test_lark_private_progress.py。Ruff、diff whitespace 检查通过;mypy loopx/chat.py --follow-imports=silent 在基础与当前 head 均通过,这是有明确边界的单模块检查。
上一轮未限制导入的 mypy 在基础/旧问题版本均有 4633 个既有错误,仅规范化行号后保留的错误身份、详细正文、代码与重数完全一致;另一个 skip 模式有三个相同错误。这些历史失败保留,不宣称已修复,也不将当前 silent 检查写成全树类型检查通过。当前修复 helper 的相关行为有独立通过证据。
打包前端使用当前完整 head 重新构建,包含 TypeScript --noEmit 和 bundle 源码摘要校验。准备时发现既有被忽略的 bundle 缓存过期,重建后校验通过;保留 large-chunk 构建提示,未改变预算。实际 ACP owner 输出写入隔离的真实 Chat store,通过实际 HTTP session snapshot 进入打包 UI。Ego Lite 检查完整 viewport:用户请求、管家归属、普通文件名、私有占位符及继续输入框清晰;四条消息在存储重开、页面 reload 及单独 HTTP 读回后保持一致,没有重复启动工作。模型边界是合成输入,不是 live Bot;夹具不能证明生产排序、输出频率或部署采用。
公开完整 diff 扫描未见真实机器路径、私有 Goal、凭据或原始运行日志;home 路径为既有合成项目外负例。未查询、轮询或等待 CI。私有保护是代码执行的投影约束,不是让模型自律的建议。没有新共享枚举、协议、持久回执、授权、quota 或 scheduler 规则;结构化与回答模式不被混淆。
Independent same-fixture replay through the real ACP stdio boundary produces privacy oracle exits base 0 / previous defect 1 / current head 0, while ordinary filenames remain useful. Current local tests are 203 passed, base 186 passed; Ruff, whitespace and bounded silent-import mypy pass. Historical wide mypy failures remain recorded and are not claimed fixed. A fresh exact-source packaged build, real isolated store/HTTP readback and Ego Lite reload preserve four owner-derived synthetic messages. Paid model calls, live Lark/Bot adoption, native Windows filesystem identity and arbitrary symlink aliases are untested. CI was not consulted.
我的整体评价
当前完整 PR 达成该有界展现目标:普通产物可直接核对,上一轮私有别名回归修复,结构化边界保留。long_horizon 保留:没有调度、重试、持久承诺或累积状态变化,后续普通回答与存储重开/刷新读回仍可继续;user_experience 改善:原有对话直接展示普通文件名,省去一次追问,私有名称仍隐藏。未把整项展现改动的完成扩张成父级产品验收、安装完成或模型已经采纳。
相关重构已在共享比较 owner 内实施。无新增可选能力,现有对话展现就是相应前端入口;没有另外要求无关配置编辑器。回滚可恢复三处回答完整隐藏调用,不涉及数据迁移。仍保留文本脱敏的已知范围:这不是任意文件系统或符号链接访问安全证明。没有发现当前 head 的剩余实质阻塞。
这是 author-owned PR 的 COMMENTED 通过结论;GitHub 禁止同一账户正式自批准,因此不能把该记录或原始 REVIEW_REQUIRED 读成 GitHub APPROVED。通过结论、旧阻塞 review 收尾与合并权限分别核对;本评审不执行合并。旧 review 保留历史讨论;本 head 的结论基于新证据,不按“旧 commit”直接判定问题消失。
English verdict: APPROVE - 86db353. The whole PR now delivers useful ordinary relative filenames while preserving separately protected-root privacy, structured redaction and conservative traversal handling. The prior private-alias defect is independently reproduced on the old revision and absent on the reviewed head at parser, every stream cut and real ACP stdio boundaries. Packaged UI reload and real isolated store/HTTP readback are verified with synthetic provider output. Long-horizon behavior is preserved and the ordinary user journey improves without added consent or navigation. This author-owned COMMENTED conclusion is not formal GitHub self-approval or merge authority. No merge performed; CI not consulted.
A Chat reply that reports a project file currently collapses its whole location to
[project], making file readback and deliverables difficult to identify. Visible final answers and streamed answer text now retain a./project-relative filename; machine roots, other local paths, nested protected directories and traversal-shaped paths remain hidden. Local Markdown links still keep their readable labels without unusable destinations.The shared Python Chat presentation owner serves native Codex, ACP and model-provider adapters; no new provider, settings switch or authority rule is introduced. Structured status, proposals and gates retain full path redaction. The bilingual workspace guide describes the changed answer default.
Validation: 203 related tests passed (Chat parsing/paths/activity/agent and Lark private progress), including every two-chunk split of representative Unix/Windows paths, long split paths, nested private roots (including dot components, repeated/encoded separators and Windows case spellings) and proposal/status isolation. Ruff, targeted mypy (
loopx/chat.py --follow-imports=silent), diff checks and the public-boundary scan passed. A real read-only Codex app-server run read a disposable public fixture, retained its filename, returned the correct content and left the file unchanged. A real ACP subprocess/stdio fixture also verifies character-chunked private aliases and ordinary project filenames in bothanswer.deltaandanswer.final; the same privacy oracle fails against the initial PR head and passes against this revision. The ACP provider is synthetic and does not prove model output frequency. This validates the candidate adapters; the paired desktop/Bot installation and end-to-end Lark readback follow independent review and merge.The related refactor pass reuses the existing redaction owner and parser/stream call sites instead of adding a parallel path classifier or presentation framework.