[codex-analytics] add extensible feature thread sources (#27063)

## Why
- `ThreadSource` currently defines a closed set of core-owned values
- Product features also create threads for background or scheduled work
- Adding every product-specific value to the core enum would require
repeated `codex-rs` protocol changes
- Feature-backed values let product callers provide precise attribution
while preserving the existing core classifications

## What Changed
- Adds `ThreadSource::Feature(String)` for app-owned thread source
values
- Represents all app-server v2 thread sources as scalar strings, so a
feature source is supplied as `"automation"`
- Persists and emits the feature's plain string label, so `"automation"`
produces `thread_source="automation"` in analytics
- Keeps `user`, `subagent`, and `memory_consolidation` as explicit
core-owned values and regenerates the app-server schemas and TypeScript
bindings

## Verification
- `just write-app-server-schema`
- `cargo check --workspace`
- `just test -p codex-protocol
feature_thread_source_serializes_as_its_app_owned_label`
- `just test -p codex-app-server-protocol
thread_sources_round_trip_as_scalar_labels`
- `cargo test -p codex-analytics
thread_initialized_event_serializes_expected_shape`
- `just fmt`
This commit is contained in:
marksteinbrick-oai
2026-06-09 12:27:10 -07:00
committed by GitHub
parent 99da697e4c
commit a71e040df5
32 changed files with 123 additions and 113 deletions
@@ -57,6 +57,30 @@ fn test_absolute_path() -> AbsolutePathBuf {
absolute_path("readable")
}
#[test]
fn thread_sources_round_trip_as_scalar_labels() {
for (source, label) in [
(ThreadSource::User, "user"),
(ThreadSource::Subagent, "subagent"),
(
ThreadSource::Feature("automation".to_string()),
"automation",
),
(ThreadSource::MemoryConsolidation, "memory_consolidation"),
] {
let value = serde_json::to_value(&source).expect("serialize thread source");
assert_eq!(value, json!(label));
assert_eq!(
serde_json::from_value::<ThreadSource>(value).expect("deserialize thread source"),
source
);
let core_source: codex_protocol::protocol::ThreadSource = source.clone().into();
assert_eq!(ThreadSource::from(core_source), source);
}
}
#[test]
fn approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent() {
assert_eq!(