Track artifact operations from trusted plugin markers (#38057)

## What changed

- Recognize validated create and edit marker commands from the trusted presentations, documents, spreadsheets, and PDF plugins.
- Emit a `codex_artifact_operation` analytics event plus started-count and expected-output-count metrics when a recognized marker starts through unified exec.
- Attribute remote executor commands only when their plugin cache path and script contents match a trusted local plugin, including primary-runtime plugins.

## Testing

- Cover supported marker combinations and reject mismatched plugins, paths, arguments, counts, and output formats.
- Cover local, relocated remote, and Windows-style plugin attribution, including rejection of modified remote scripts.

GitOrigin-RevId: 475cc1689aa7f371a24dec0a8bdb6f7fd5a2e922
This commit is contained in:
edwardysun3 authored and copyberry committed 2026-08-11 19:26:41 +00:00
1 parent 285abc0368
commit 44d992c14e
18 files changed
+971 -60

No files matched your search

+40
View File
@@ -42,8 +42,16 @@ use crate::events::ThreadArchiveEventParams;
#[cfg(debug_assertions)]
use crate::events::ToolItemTerminalStatus;
use crate::events::TrackEventRequest;
#[cfg(debug_assertions)]
use crate::events::codex_artifact_operation_event_request;
use crate::facts::AnalyticsFact;
#[cfg(debug_assertions)]
use crate::facts::ArtifactOperation;
#[cfg(debug_assertions)]
use crate::facts::ArtifactOperationLifecycle;
use crate::facts::InvocationType;
#[cfg(debug_assertions)]
use crate::facts::TrackEventsContext;
use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;
use codex_app_server_protocol::AskForApproval as AppServerAskForApproval;
use codex_app_server_protocol::ClientRequest;
@@ -138,6 +146,32 @@ fn sample_skill_track_event(thread_id: &str, plugin_id: Option<&str>) -> TrackEv
})
}
#[cfg(debug_assertions)]
fn sample_artifact_operation_event(thread_id: &str) -> TrackEventRequest {
TrackEventRequest::ArtifactOperation(codex_artifact_operation_event_request(
TrackEventsContext {
model_slug: "gpt-5.1-codex".to_string(),
thread_id: thread_id.to_string(),
turn_id: "turn-1".to_string(),
product_client_id: "codex_desktop".to_string(),
},
ArtifactOperation {
item_id: format!("item-{thread_id}"),
lifecycle: ArtifactOperationLifecycle::Started,
occurred_at_ms: 1,
plugin_id: "presentations@openai-primary-runtime".to_string(),
script_path: "skills/presentations/container_tools/mark_artifact_operation_started.mjs"
.to_string(),
skill: "presentations".to_string(),
artifact_type: "presentation".to_string(),
operation_kind: "create".to_string(),
expected_output_count: 1,
output_format: "pptx".to_string(),
execution_backend: "unified_exec".to_string(),
},
))
}
fn sample_regular_track_event(thread_id: &str) -> TrackEventRequest {
sample_skill_track_event(thread_id, /*plugin_id*/ None)
}
@@ -393,6 +427,7 @@ async fn api_key_auth_sends_only_plugin_events_to_codex_backend() {
sample_plugin_used_track_event("plugin-used", Some("sample@test")),
sample_skill_track_event("plugin-skill", Some("sample@test")),
sample_mcp_tool_call_event("plugin-mcp", Some("sample@test")),
sample_artifact_operation_event("plugin-artifact"),
],
)
.await;
@@ -442,6 +477,11 @@ async fn api_key_auth_sends_only_plugin_events_to_codex_backend() {
"plugin_id": "sample@test",
"thread_id": "plugin-mcp",
}),
serde_json::json!({
"event_type": "codex_artifact_operation",
"plugin_id": "presentations@openai-primary-runtime",
"thread_id": "plugin-artifact",
}),
]
);