收紧DirectProject公开事件投影

item事件仅传递类型与完成标记,不转发完整app-server载荷\nturn完成事件仅保留安全终态\n补充事件投影脱敏测试
This commit is contained in:
2026-09-15 17:38:49 +08:00
committed by kdletters
parent 147976218b
commit f138cd7d55
@@ -717,6 +717,25 @@ fn direct_codex_safe_activity_for_item_value(item: &serde_json::Value) -> &'stat
direct_codex_safe_activity_for_item(item_type)
}
/// Project an app-server item into the small public payload carried by the
/// DirectProject event queue. Full item contents are persisted in JSONL and
/// must not be forwarded through the runtime event stream.
fn direct_thread_item_started_payload(item: &serde_json::Value) -> serde_json::Value {
serde_json::json!({
"itemType": item
.get("type")
.and_then(serde_json::Value::as_str)
.unwrap_or("unknown"),
})
}
fn direct_thread_item_id(item: &serde_json::Value) -> Option<String> {
item.get("id")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.is_empty())
.map(str::to_string)
}
fn direct_codex_command_is_game_verification(command: &str) -> bool {
let command = command.to_ascii_lowercase();
command.contains("game.static_smoke")
@@ -2933,18 +2952,14 @@ impl CodexAppServerConnection {
})?
.map_err(platform_llm::LlmError::InvalidRequest)?;
direct_project_history.complete_item(&item);
let item_id = item
.get("id")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.is_empty())
.map(str::to_string);
let item_id = direct_thread_item_id(&item);
append_direct_thread_event(
&direct_thread_id,
DirectThreadRawEventDraft {
event_type: "item.completed".to_string(),
turn_id: turn_id.clone(),
item_id,
payload: serde_json::json!({ "item": item }),
payload: serde_json::json!({}),
},
);
}
@@ -3013,18 +3028,14 @@ impl CodexAppServerConnection {
&& self.inner.workspace_mode
== CodexAppServerWorkspaceMode::DirectProject
{
let item_id = item
.get("id")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.is_empty())
.map(str::to_string);
let item_id = direct_thread_item_id(item);
append_direct_thread_event(
&direct_thread_id,
DirectThreadRawEventDraft {
event_type: "item.started".to_string(),
turn_id: turn_id.clone(),
item_id,
payload: serde_json::json!({ "item": item }),
payload: direct_thread_item_started_payload(item),
},
);
}
@@ -3057,7 +3068,7 @@ impl CodexAppServerConnection {
event_type: "turn.completed".to_string(),
turn_id: turn_id.clone(),
item_id: None,
payload: params.clone(),
payload: serde_json::json!({ "status": status }),
},
);
}
@@ -4035,6 +4046,22 @@ pub(crate) fn build_direct_codex_history_prompt(
mod tests {
use super::*;
#[test]
fn direct_thread_item_projection_drops_full_app_server_payload() {
let item = serde_json::json!({
"id": "item-1",
"type": "mcpToolCall",
"tool": "agc_write_file",
"arguments": { "path": "game/index.html", "token": "secret" },
"result": { "content": "large output" }
});
assert_eq!(direct_thread_item_id(&item).as_deref(), Some("item-1"));
assert_eq!(
direct_thread_item_started_payload(&item),
serde_json::json!({ "itemType": "mcpToolCall" })
);
}
#[test]
fn direct_item_activities_are_closed_safe_categories() {
let allowed = [