mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add persisted thread pinning to the app server (#34840)
## What changed - Add `isPinned` to thread responses and allow `thread/metadata/update` to pin or unpin stored threads. - Add an `isPinned` filter to `thread/list`, including cursor-based pagination and combinations with relationship filters. - Persist pin state in SQLite without modifying rollout files, default existing threads to unpinned, and preserve pins through reconciliation and archive transitions. ## Testing - Cover protocol serialization, database migration and indexing, pin updates, filtered pagination, rollout reconciliation, and archive/unarchive behavior. GitOrigin-RevId: c2eacabd6dae465e2ae6ce8a169e00740960cdb7
This commit is contained in:
1 parent
d7e8f4c3dc
commit
400ee190c3
71 files changed
+1058
-60
No files matched your search
@@ -185,6 +185,7 @@ fn thread_resume_response_round_trips_initial_turns_page() {
|
||||
parent_thread_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
is_pinned: true,
|
||||
history_mode: Default::default(),
|
||||
model_provider: "openai".to_string(),
|
||||
created_at: 1,
|
||||
@@ -225,6 +226,17 @@ fn thread_resume_response_round_trips_initial_turns_page() {
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&response).expect("serialize thread resume response");
|
||||
assert_eq!(value["thread"]["isPinned"], json!(true));
|
||||
|
||||
let mut legacy_thread = value["thread"].clone();
|
||||
legacy_thread
|
||||
.as_object_mut()
|
||||
.expect("serialized thread should be an object")
|
||||
.remove("isPinned");
|
||||
let legacy_thread =
|
||||
serde_json::from_value::<Thread>(legacy_thread).expect("deserialize legacy thread");
|
||||
assert!(!legacy_thread.is_pinned);
|
||||
|
||||
assert_eq!(
|
||||
value.get("initialTurnsPage"),
|
||||
Some(&json!({
|
||||
@@ -356,6 +368,42 @@ fn thread_list_params_accepts_state_db_only_flag() {
|
||||
assert!(params.use_state_db_only);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_list_params_accepts_pinned_filter() {
|
||||
for is_pinned in [true, false] {
|
||||
let params = serde_json::from_value::<ThreadListParams>(json!({
|
||||
"isPinned": is_pinned,
|
||||
}))
|
||||
.expect("pinned filter should deserialize");
|
||||
|
||||
assert_eq!(params.is_pinned, Some(is_pinned));
|
||||
}
|
||||
|
||||
let params = serde_json::from_value::<ThreadListParams>(json!({}))
|
||||
.expect("omitted pinned filter should deserialize");
|
||||
assert_eq!(params.is_pinned, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_metadata_update_params_accepts_pinned_patch() {
|
||||
for is_pinned in [true, false] {
|
||||
let params = serde_json::from_value::<ThreadMetadataUpdateParams>(json!({
|
||||
"threadId": "thr_123",
|
||||
"isPinned": is_pinned,
|
||||
}))
|
||||
.expect("pinned metadata patch should deserialize");
|
||||
|
||||
assert_eq!(params.is_pinned, Some(is_pinned));
|
||||
assert_eq!(params.git_info, None);
|
||||
}
|
||||
|
||||
let params = serde_json::from_value::<ThreadMetadataUpdateParams>(json!({
|
||||
"threadId": "thr_123",
|
||||
}))
|
||||
.expect("omitted pinned metadata patch should deserialize");
|
||||
assert_eq!(params.is_pinned, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collab_agent_state_maps_interrupted_status() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -865,6 +865,9 @@ pub struct ThreadMetadataUpdateParams {
|
||||
/// provide a string to replace the stored value.
|
||||
#[ts(optional = nullable)]
|
||||
pub git_info: Option<ThreadMetadataGitInfoUpdateParams>,
|
||||
/// Patch whether this thread is pinned. Omit to leave the stored value unchanged.
|
||||
#[ts(optional = nullable)]
|
||||
pub is_pinned: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -1118,6 +1121,9 @@ pub struct ThreadListParams {
|
||||
/// If false or null, only non-archived threads are returned.
|
||||
#[ts(optional = nullable)]
|
||||
pub archived: Option<bool>,
|
||||
/// Optional pinned filter; when set, only threads matching this value are returned.
|
||||
#[ts(optional = nullable)]
|
||||
pub is_pinned: Option<bool>,
|
||||
/// Optional cwd filter or filters; when set, only threads whose session cwd
|
||||
/// exactly matches one of these paths are returned.
|
||||
#[ts(optional = nullable, type = "string | Array<string> | null")]
|
||||
|
||||
@@ -183,6 +183,9 @@ pub struct Thread {
|
||||
pub preview: String,
|
||||
/// Whether the thread is ephemeral and should not be materialized on disk.
|
||||
pub ephemeral: bool,
|
||||
/// Whether the thread has been pinned by the user.
|
||||
#[serde(default)]
|
||||
pub is_pinned: bool,
|
||||
/// Persisted thread history contract selected when this thread was created.
|
||||
#[experimental("thread.historyMode")]
|
||||
#[serde(default)]
|
||||
|
||||
Reference in new issue
Block a user