From 493457c3eaf11813e54e4d40c5727860a2e2c5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 1 Oct 2026 15:48:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=92=E7=BB=9D=E5=8E=9F=E5=9B=A0=E7=95=99?= =?UTF-8?q?=E7=97=95=E5=89=8D=E7=BB=9F=E4=B8=80=E8=84=B1=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - log_denied_reason 在唯一出口调用 sanitize_diagnostic_message,透传的宿主错误文本不再把绝对路径写进日志 - 去重键改用脱敏后的文本,内存与 stderr 拷贝同样不带原文 - 新增 denied_reason_logging_scrubs_embedded_paths 用例固化该约定 - 同步更新 shared-memory 决策记录里的脱敏口径 --- .../src/agent/codex_app_server/execution.rs | 17 +++++++++++++++++ .../shared-memory/decision-log.md | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/execution.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/execution.rs index cd6b98452..10ab21172 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/execution.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/execution.rs @@ -389,6 +389,10 @@ impl ExecutionAdapter { /// 同一回合内 `(method, reason)` 只记一次:模型被拒后常反复重试同一动作,逐次记录会把真正 /// 有用的诊断刷掉;"这一轮被拒过哪些原因"仍然完整。 fn log_denied_reason(&self, method: &str, reason: &str) { + // 契约:留痕不带路径、上游正文或凭据。静态分类本来就没有;`lease-admit-failed` 这类透传的 + // 宿主错误文本可能内嵌绝对项目路径(如 `validate_patch_paths` 的路径校验错误),所以统一 + // 在唯一出口脱敏——去重键用脱敏后的文本,日志拷贝与 stderr 拷贝都不会带原文。 + let reason = crate::sanitize_diagnostic_message(reason, None); let key = format!("{method}\u{1}{reason}"); let thread_id = &self.thread_id; self.log_once(key, |distinct| { @@ -1847,4 +1851,17 @@ mod tests { MAX_DENIED_REASON_LOGS ); } + + #[test] + fn denied_reason_logging_scrubs_embedded_paths() { + let (_temp, adapter) = fixture(); + adapter.log_denied_reason( + "item/commandExecution/requestApproval", + "lease-admit-failed: 读取路径失败:/home/someone/secret-project/app.ts", + ); + let logged = adapter.denied_reasons_logged.lock().unwrap(); + let key = logged.iter().next().expect("拒绝原因应留痕"); + assert!(key.contains("lease-admit-failed"), "{key}"); + assert!(!key.contains("/home/someone"), "留痕不得带绝对路径:{key}"); + } } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index a00fa21cc..8e1fc3c67 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4,7 +4,7 @@ - 决策:宿主拒绝 app-server 的审批 / 交互请求时,原因必须落 AppData `RUST` 日志。稳定键 `agent.direct_codex.approval.denied`,字段为 `thread_id` / `method` / `reason` / `distinct_reasons`;未绑定宿主执行器时另记 `agent.direct_codex.interaction.no_adapter`(`method` / `outcome`,`outcome` 区分 `decline` / `empty-permissions` / `unsupported-method`);回包未送达另记 `agent.direct_codex.approval.response_delivery_failed`,`cause` 区分 `response-write-failed` 与 `turn-bind-mismatch`。 - 原因:线上对审批只回 `{"decision":"decline"}`,模型与用户都看不到是哪一道闸门(合同缺失 / 预算耗尽 / 阶段已收束 / item 不在途 / 无适配器)拦下的,只能靠复现。 -- 边界:`reason` 只接受 `execution.rs` 内的静态分类或宿主自己的错误文本,不带请求参数、上游正文、路径或凭据;有界去重一律按 `MAX_DENIED_REASON_LOGS = 64` 条封顶——适配器路径按回合内 `(method, reason)`、未绑定执行器路径按进程级 `(method, outcome)`、回包未送达按回合内原因各只记一次。 +- 边界:`reason` 只接受 `execution.rs` 内的静态分类或宿主自己的错误文本;透传的宿主错误在留痕出口统一走 `sanitize_diagnostic_message`,不带请求参数、上游正文、路径或凭据(去重键也用脱敏后的文本)。有界去重一律按 `MAX_DENIED_REASON_LOGS = 64` 条封顶——适配器路径按回合内 `(method, reason)`、未绑定执行器路径按进程级 `(method, outcome)`、回包未送达按回合内原因各只记一次。 - 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/execution.rs`。 - 验证:`cargo test --bin genarrative-ai-game-creator-shell agent::codex_app_server::execution::tests`(15 passed,含 `denied_approval_records_its_reason_once_per_turn`、`denied_reason_logging_is_bounded`)、`npm run check:encoding`、`git diff --check`。