取消 DirectProject 成功回复脱敏
DirectProject 流式文本和最终回复只移除 thinking 包装,成功正文按用户项目内容原样展示。 删除路径、链接和凭据占位符替换以及回复长度截断逻辑。 同步删除最终回复必须脱敏的文档要求,并更新回归测试验证项目内容保持原样。
This commit is contained in:
@@ -14,7 +14,6 @@ const DIRECT_AGC_ENGINEERING_GUIDANCE: &str = "AGC 工程合同(仅说明项
|
||||
const DIRECT_CODEX_ART_SPEC_ASSET_PATH: &str = "assets/art-spec.png";
|
||||
const DIRECT_CODEX_BACKGROUND_ASSET_PATH: &str = "assets/direct-game-background.png";
|
||||
const DIRECT_CODEX_SPRITESHEET_ASSET_PATH: &str = "assets/art-spritesheet.png";
|
||||
const MAX_DIRECT_VISIBLE_REPLY_CHARS: usize = 16 * 1024;
|
||||
const PLATFORM_GENERATION_SOURCE_PRESERVED_NO_RETRY_PREFIX: &str =
|
||||
"platform-generation-source-preserved-no-retry:";
|
||||
const DIRECT_TAONIER_LOCAL_RECONCILIATION_PREFIX: &str =
|
||||
@@ -3661,24 +3660,15 @@ fn sync_direct_codex_project_outputs_at(
|
||||
sync_direct_codex_project_file_projection_at(root, previous_output_fingerprint)
|
||||
}
|
||||
|
||||
/// Project Codex text into the only form that may cross the DirectProject UI
|
||||
/// boundary. The app-server stream can contain reasoning blocks, URLs,
|
||||
/// credentials, or host paths before the final reply is known; those values
|
||||
/// must never be emitted as an intermediate chat message or persisted as the
|
||||
/// user-visible assistant turn.
|
||||
fn project_direct_codex_visible_text(root: &Path, value: &str) -> Option<String> {
|
||||
/// Project Codex text for the user-visible DirectProject stream and reply.
|
||||
/// Reasoning wrappers are still removed because they are not reply text, but
|
||||
/// the user owns the project and the resulting reply is not redacted here.
|
||||
fn project_direct_codex_visible_text(value: &str) -> Option<String> {
|
||||
let stripped = strip_incomplete_direct_thinking_marker(&strip_llm_thinking_blocks(value));
|
||||
if stripped.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
let redacted = redact_agent_runtime_error(root, &stripped, MAX_DIRECT_VISIBLE_REPLY_CHARS)
|
||||
.replace("<redacted-url>", "(链接已隐藏)")
|
||||
.replace("$PROJECT_ROOT", "(项目路径已隐藏)")
|
||||
.replace("<absolute-path>", "(路径已隐藏)")
|
||||
.replace("[redacted-secret]", "(敏感信息已隐藏)")
|
||||
.replace("[redacted-sensitive-field]", "(敏感字段已隐藏)")
|
||||
.replace("[redacted sensitive context]", "(内部信息已隐藏)");
|
||||
let visible = redacted.trim().to_string();
|
||||
let visible = stripped.trim().to_string();
|
||||
(!visible.is_empty()).then_some(visible)
|
||||
}
|
||||
|
||||
@@ -3695,14 +3685,13 @@ fn strip_incomplete_direct_thinking_marker(value: &str) -> String {
|
||||
}
|
||||
|
||||
fn project_direct_codex_accumulated_text(
|
||||
root: &Path,
|
||||
stream_enabled: bool,
|
||||
accumulated_text: &str,
|
||||
) -> Option<String> {
|
||||
if !stream_enabled {
|
||||
return None;
|
||||
}
|
||||
project_direct_codex_visible_text(root, accumulated_text)
|
||||
project_direct_codex_visible_text(accumulated_text)
|
||||
}
|
||||
|
||||
pub(crate) fn build_direct_codex_system_prompt(root: &Path) -> Result<String, String> {
|
||||
@@ -3915,7 +3904,7 @@ async fn run_direct_game_creator_turn_inner(
|
||||
let mut observer = move |observation: DirectCodexTurnObservation| match observation {
|
||||
DirectCodexTurnObservation::AccumulatedText(accumulated_text) => {
|
||||
let visible_text =
|
||||
project_direct_codex_accumulated_text(root, stream_enabled, &accumulated_text);
|
||||
project_direct_codex_accumulated_text(stream_enabled, &accumulated_text);
|
||||
if visible_text.is_none() {
|
||||
return;
|
||||
}
|
||||
@@ -3926,7 +3915,7 @@ async fn run_direct_game_creator_turn_inner(
|
||||
}
|
||||
DirectCodexTurnObservation::IntermediateText(intermediate_text) => {
|
||||
let visible_text = if stream_enabled {
|
||||
project_direct_codex_visible_text(root, &intermediate_text)
|
||||
project_direct_codex_visible_text(&intermediate_text)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -3963,7 +3952,7 @@ async fn run_direct_game_creator_turn_inner(
|
||||
.await
|
||||
}
|
||||
.map_err(|error| DirectCodexTurnFailure::new(DirectCodexFailureStage::CodeGeneration, error))?;
|
||||
let visible_reply = project_direct_codex_visible_text(root, &reply).ok_or_else(|| {
|
||||
let visible_reply = project_direct_codex_visible_text(&reply).ok_or_else(|| {
|
||||
DirectCodexTurnFailure::new(
|
||||
DirectCodexFailureStage::CodeGeneration,
|
||||
"陶泥儿未返回可展示的回复".to_string(),
|
||||
@@ -4662,58 +4651,48 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_visible_stream_projection_hides_internal_content() {
|
||||
let root = tempfile::tempdir().expect("direct stream root");
|
||||
let project_file = root.path().join("game/index.html");
|
||||
fn direct_visible_stream_projection_keeps_user_project_reply_content() {
|
||||
let project_file = std::path::Path::new("game/index.html");
|
||||
let raw = format!(
|
||||
"先说一句\n<think>内部推理不应显示</think>\n来源 https://example.test/a\n路径 {}\nauthorization: Bearer secret-value-123",
|
||||
project_file.display()
|
||||
);
|
||||
let visible =
|
||||
project_direct_codex_visible_text(root.path(), &raw).expect("safe visible stream text");
|
||||
let visible = project_direct_codex_visible_text(&raw).expect("visible stream text");
|
||||
assert!(visible.contains("先说一句"), "{visible}");
|
||||
assert!(!visible.contains("内部推理"), "{visible}");
|
||||
assert!(!visible.contains("https://example.test"), "{visible}");
|
||||
assert!(visible.contains("https://example.test"), "{visible}");
|
||||
assert!(
|
||||
!visible.contains(project_file.to_string_lossy().as_ref()),
|
||||
"{visible}"
|
||||
);
|
||||
assert!(!visible.contains("secret-value-123"), "{visible}");
|
||||
assert!(visible.contains("链接已隐藏"), "{visible}");
|
||||
assert!(
|
||||
visible.contains("项目路径已隐藏") || visible.contains("路径已隐藏"),
|
||||
visible.contains(project_file.to_string_lossy().as_ref()),
|
||||
"{visible}"
|
||||
);
|
||||
assert!(visible.contains("secret-value-123"), "{visible}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_visible_stream_projection_drops_unclosed_thinking_only_delta() {
|
||||
let root = tempfile::tempdir().expect("direct stream root");
|
||||
assert_eq!(
|
||||
project_direct_codex_visible_text(root.path(), "<think>secret reasoning"),
|
||||
project_direct_codex_visible_text("<think>secret reasoning"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_visible_stream_projection_hides_partial_thinking_tag() {
|
||||
let root = tempfile::tempdir().expect("direct stream root");
|
||||
assert_eq!(
|
||||
project_direct_codex_visible_text(root.path(), "已公开内容\n<thi"),
|
||||
project_direct_codex_visible_text("已公开内容\n<thi"),
|
||||
Some("已公开内容".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_accumulated_text_respects_the_explicit_stream_setting() {
|
||||
let root = tempfile::tempdir().expect("direct stream root");
|
||||
assert_eq!(
|
||||
project_direct_codex_accumulated_text(root.path(), false, "阶段性回复"),
|
||||
project_direct_codex_accumulated_text(false, "阶段性回复"),
|
||||
None,
|
||||
"stream=false 只能保留阶段状态,不能向聊天窗口发增量文本"
|
||||
);
|
||||
assert_eq!(
|
||||
project_direct_codex_accumulated_text(root.path(), true, "阶段性回复"),
|
||||
project_direct_codex_accumulated_text(true, "阶段性回复"),
|
||||
Some("阶段性回复".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
- 安装包侧车:Windows x64 release 固定随 Tauri resource 打包 `@openai/codex@0.147.0` 的原生 `codex.exe`;Rust build script 从 AGC 子包锁定依赖 stage 到 resource,并写入版本与 SHA-256 清单。Windows 侧车映射只写入 `tauri.windows.conf.json`,通用 `tauri.conf.json` 不得让 Linux / macOS 构建依赖未生成的 Windows 二进制。运行时只在文件摘要和 `codex-cli` 版本同时匹配清单时优先选内置侧车;缺失、损坏或版本漂移时跳过它,按既有 npm 安装、PATH 顺序回退。安装包同时携带 Apache-2.0 第三方声明;API Key、`auth.json`、Cookie、Token、用户 `CODEX_HOME`、用户配置和项目数据绝不打包。
|
||||
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json` 的 `bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。
|
||||
- CLI 安全边界:CLI 固定使用 argv 启动,禁止 shell 拼接;工作目录使用本次请求专用的空临时目录,不把游戏项目绝对路径写入 prompt、stdout、stderr 或持久记录。调用固定使用 ephemeral、忽略用户配置和 exec rules、read-only sandbox、never approval,并关闭 Codex shell tool;只继承 CLI 运行和认证所需的最小环境,显式移除宿主 `CODEX_API_KEY`。用户级 Codex 登录态继续由本机 Codex 自己读取,API Key、auth 文件、Cookie、Token、`CODEX_HOME` 私有内容不得复制到项目配置、Runtime sidecar、Agent DB、conversation 或日志;stdout / stderr 无换行时也受硬上限约束,stderr 诊断只记录固定分类、字节数和 SHA-256。
|
||||
- 协议边界:Runtime 把既有 `LlmRunRequest` 的消息和当前函数目录编码为有界 prompt,并从同一函数 JSON Schema 生成 Codex structured-output schema。CLI 输出转换为现有 `LlmRunResponse / LlmToolCall` 后,继续经过 native tool / MCP 参数校验、动作上限、权限、pending、receipt、验证与格式修复链;最终回复仍走现有脱敏和唯一提交路径,不新增平行响应协议。
|
||||
- 协议边界:Runtime 把既有 `LlmRunRequest` 的消息和当前函数目录编码为有界 prompt,并从同一函数 JSON Schema 生成 Codex structured-output schema。CLI 输出转换为现有 `LlmRunResponse / LlmToolCall` 后,继续经过 native tool / MCP 参数校验、动作上限、权限、pending、receipt、验证与格式修复链;最终回复仍走唯一提交路径,不新增平行响应协议。
|
||||
- 取消与恢复:Codex 子进程绑定当前 Provider request lifecycle,取消、暂停、Runner draining 或 GUI owner 丢失时终止并回收当前进程;started 后没有可信终态仍沿现有 Provider reconciliation 处理。`agentMode`、CLI 可执行身份和影响输出的 Codex 参数进入 `providerConfigFingerprint`,模式切换不得消费另一模式遗留的 retry/handoff。
|
||||
- 可用性与兼容:`check_game_creator_llm_config` 在 `codex_cli` 模式检查 Codex 可执行文件和版本,不再要求 HTTP API Key;在 `provider` 模式保持原有 API Key、base URL、model 和逐 Agent override 校验。设置面板始终保留旧 Provider 配置,模式切回后不得丢失既有密钥或逐 Agent设置。配置向导若写入 HTTP Provider 凭据,必须显式把模式切到 `provider`。
|
||||
- 验收:覆盖缺省模式、显式 Provider 回退、配置 round-trip、CLI 缺失/非零退出/损坏 JSONL/超限输出、structured tool calls、普通最终回复、进程取消、模式指纹漂移和旧 Provider 回归;同时执行 AGC 定向 Rust 测试、前端配置测试与 typecheck、配置门禁、编码检查和 `git diff --check`。
|
||||
|
||||
Reference in New Issue
Block a user