mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Address thread catalog review feedback
This commit is contained in:
+6
-6
@@ -5204,9 +5204,9 @@
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/subscribe"
|
||||
"threadCatalog/subscribe"
|
||||
],
|
||||
"title": "Thread/catalog/subscribeRequestMethod",
|
||||
"title": "ThreadCatalog/subscribeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -5217,7 +5217,7 @@
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "Thread/catalog/subscribeRequest",
|
||||
"title": "ThreadCatalog/subscribeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -5227,9 +5227,9 @@
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/unsubscribe"
|
||||
"threadCatalog/unsubscribe"
|
||||
],
|
||||
"title": "Thread/catalog/unsubscribeRequestMethod",
|
||||
"title": "ThreadCatalog/unsubscribeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -5240,7 +5240,7 @@
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "Thread/catalog/unsubscribeRequest",
|
||||
"title": "ThreadCatalog/unsubscribeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5041,6 +5041,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"createdAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -5109,11 +5113,16 @@
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"updatedAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cliVersion",
|
||||
"createdAt",
|
||||
"createdAtMs",
|
||||
"cwd",
|
||||
"ephemeral",
|
||||
"id",
|
||||
@@ -5121,7 +5130,8 @@
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"updatedAt"
|
||||
"updatedAt",
|
||||
"updatedAtMs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -5839,9 +5849,9 @@
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/changed"
|
||||
"threadCatalog/changed"
|
||||
],
|
||||
"title": "Thread/catalog/changedNotificationMethod",
|
||||
"title": "ThreadCatalog/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -5852,7 +5862,7 @@
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/catalog/changedNotification",
|
||||
"title": "ThreadCatalog/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
|
||||
+20
-10
@@ -643,9 +643,9 @@
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/subscribe"
|
||||
"threadCatalog/subscribe"
|
||||
],
|
||||
"title": "Thread/catalog/subscribeRequestMethod",
|
||||
"title": "ThreadCatalog/subscribeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -656,7 +656,7 @@
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "Thread/catalog/subscribeRequest",
|
||||
"title": "ThreadCatalog/subscribeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -666,9 +666,9 @@
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/unsubscribe"
|
||||
"threadCatalog/unsubscribe"
|
||||
],
|
||||
"title": "Thread/catalog/unsubscribeRequestMethod",
|
||||
"title": "ThreadCatalog/unsubscribeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -679,7 +679,7 @@
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "Thread/catalog/unsubscribeRequest",
|
||||
"title": "ThreadCatalog/unsubscribeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -4218,9 +4218,9 @@
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/changed"
|
||||
"threadCatalog/changed"
|
||||
],
|
||||
"title": "Thread/catalog/changedNotificationMethod",
|
||||
"title": "ThreadCatalog/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -4231,7 +4231,7 @@
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/catalog/changedNotification",
|
||||
"title": "ThreadCatalog/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -19611,6 +19611,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"createdAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
@@ -19679,11 +19683,16 @@
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"updatedAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cliVersion",
|
||||
"createdAt",
|
||||
"createdAtMs",
|
||||
"cwd",
|
||||
"ephemeral",
|
||||
"id",
|
||||
@@ -19691,7 +19700,8 @@
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"updatedAt"
|
||||
"updatedAt",
|
||||
"updatedAtMs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
+20
-10
@@ -1655,9 +1655,9 @@
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/subscribe"
|
||||
"threadCatalog/subscribe"
|
||||
],
|
||||
"title": "Thread/catalog/subscribeRequestMethod",
|
||||
"title": "ThreadCatalog/subscribeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -1668,7 +1668,7 @@
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "Thread/catalog/subscribeRequest",
|
||||
"title": "ThreadCatalog/subscribeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -1678,9 +1678,9 @@
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/unsubscribe"
|
||||
"threadCatalog/unsubscribe"
|
||||
],
|
||||
"title": "Thread/catalog/unsubscribeRequestMethod",
|
||||
"title": "ThreadCatalog/unsubscribeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -1691,7 +1691,7 @@
|
||||
"id",
|
||||
"method"
|
||||
],
|
||||
"title": "Thread/catalog/unsubscribeRequest",
|
||||
"title": "ThreadCatalog/unsubscribeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -12626,9 +12626,9 @@
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/catalog/changed"
|
||||
"threadCatalog/changed"
|
||||
],
|
||||
"title": "Thread/catalog/changedNotificationMethod",
|
||||
"title": "ThreadCatalog/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
@@ -12639,7 +12639,7 @@
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/catalog/changedNotification",
|
||||
"title": "ThreadCatalog/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -17390,6 +17390,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"createdAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -17458,11 +17462,16 @@
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"updatedAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cliVersion",
|
||||
"createdAt",
|
||||
"createdAtMs",
|
||||
"cwd",
|
||||
"ephemeral",
|
||||
"id",
|
||||
@@ -17470,7 +17479,8 @@
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"updatedAt"
|
||||
"updatedAt",
|
||||
"updatedAtMs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
+11
-1
@@ -181,6 +181,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"createdAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -249,11 +253,16 @@
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"updatedAtMs": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cliVersion",
|
||||
"createdAt",
|
||||
"createdAtMs",
|
||||
"cwd",
|
||||
"ephemeral",
|
||||
"id",
|
||||
@@ -261,7 +270,8 @@
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"updatedAt"
|
||||
"updatedAt",
|
||||
"updatedAtMs"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ import type { GitInfo } from "./GitInfo";
|
||||
import type { SessionSource } from "./SessionSource";
|
||||
import type { ThreadSource } from "./ThreadSource";
|
||||
|
||||
export type ThreadSummary = { id: string, sessionId: string, forkedFromId: string | null, parentThreadId: string | null, preview: string, ephemeral: boolean, modelProvider: string, createdAt: number, updatedAt: number, archivedAt: number | null, path: string | null, cwd: AbsolutePathBuf, cliVersion: string, source: SessionSource, threadSource: ThreadSource | null, agentNickname: string | null, agentRole: string | null, gitInfo: GitInfo | null, name: string | null, };
|
||||
export type ThreadSummary = { id: string, sessionId: string, forkedFromId: string | null, parentThreadId: string | null, preview: string, ephemeral: boolean, modelProvider: string, createdAt: number, createdAtMs: number, updatedAt: number, updatedAtMs: number, archivedAt: number | null, path: string | null, cwd: AbsolutePathBuf, cliVersion: string, source: SessionSource, threadSource: ThreadSource | null, agentNickname: string | null, agentRole: string | null, gitInfo: GitInfo | null, name: string | null, };
|
||||
|
||||
@@ -618,12 +618,12 @@ client_request_definitions! {
|
||||
serialization: None,
|
||||
response: v2::ThreadListResponse,
|
||||
},
|
||||
ThreadCatalogSubscribe => "thread/catalog/subscribe" {
|
||||
ThreadCatalogSubscribe => "threadCatalog/subscribe" {
|
||||
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
|
||||
serialization: None,
|
||||
response: v2::ThreadCatalogSubscribeResponse,
|
||||
},
|
||||
ThreadCatalogUnsubscribe => "thread/catalog/unsubscribe" {
|
||||
ThreadCatalogUnsubscribe => "threadCatalog/unsubscribe" {
|
||||
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
|
||||
serialization: None,
|
||||
response: v2::ThreadCatalogUnsubscribeResponse,
|
||||
@@ -1612,7 +1612,7 @@ server_notification_definitions! {
|
||||
Error => "error" (v2::ErrorNotification),
|
||||
ThreadStarted => "thread/started" (v2::ThreadStartedNotification),
|
||||
ThreadStatusChanged => "thread/status/changed" (v2::ThreadStatusChangedNotification),
|
||||
ThreadCatalogChanged => "thread/catalog/changed" (v2::ThreadCatalogChangedNotification),
|
||||
ThreadCatalogChanged => "threadCatalog/changed" (v2::ThreadCatalogChangedNotification),
|
||||
ThreadArchived => "thread/archived" (v2::ThreadArchivedNotification),
|
||||
ThreadDeleted => "thread/deleted" (v2::ThreadDeletedNotification),
|
||||
ThreadUnarchived => "thread/unarchived" (v2::ThreadUnarchivedNotification),
|
||||
|
||||
@@ -207,7 +207,11 @@ pub struct ThreadSummary {
|
||||
#[ts(type = "number")]
|
||||
pub created_at: i64,
|
||||
#[ts(type = "number")]
|
||||
pub created_at_ms: i64,
|
||||
#[ts(type = "number")]
|
||||
pub updated_at: i64,
|
||||
#[ts(type = "number")]
|
||||
pub updated_at_ms: i64,
|
||||
#[ts(type = "number | null")]
|
||||
pub archived_at: Option<i64>,
|
||||
pub path: Option<PathBuf>,
|
||||
|
||||
@@ -421,13 +421,13 @@ Enable `capabilities.experimentalApi` during initialization, then use `thread/li
|
||||
} }
|
||||
```
|
||||
|
||||
`thread/catalog/subscribe` watches all future thread metadata changes without draining existing history. For a race-free sidebar bootstrap, start buffering `thread/catalog/changed` notifications before sending the subscribe request. After the subscribe response arrives, call `thread/list`, then apply the buffered complete summaries idempotently and filter them client-side. The summary contains sidebar metadata such as id, preview/name, cwd, timestamps, archive state, git info, and source; it does not contain turns, items, messages, deltas, tool state, status, or runtime history.
|
||||
`threadCatalog/subscribe` watches all future thread metadata changes without draining existing history. For a race-free sidebar bootstrap, start buffering `threadCatalog/changed` notifications before sending the subscribe request. After the subscribe response arrives, call `thread/list`, then apply the buffered complete summaries idempotently and filter them client-side. The summary contains sidebar metadata such as id, preview/name, cwd, second and millisecond timestamps, archive state, git info, and source; it does not contain turns, items, messages, deltas, tool state, status, or runtime history.
|
||||
|
||||
```json
|
||||
{ "method": "thread/catalog/subscribe", "id": 21 }
|
||||
{ "method": "threadCatalog/subscribe", "id": 21 }
|
||||
{ "id": 21, "result": {} }
|
||||
{ "method": "thread/catalog/changed", "params": {
|
||||
"thread": { "id": "thr_z", "preview": "Fix tests", "updatedAt": 1730832222, "archivedAt": null }
|
||||
{ "method": "threadCatalog/changed", "params": {
|
||||
"thread": { "id": "thr_z", "preview": "Fix tests", "updatedAt": 1730832222, "updatedAtMs": 1730832222000, "archivedAt": null }
|
||||
} }
|
||||
```
|
||||
|
||||
|
||||
@@ -477,7 +477,9 @@ impl MessageProcessor {
|
||||
Arc::clone(&thread_manager),
|
||||
outgoing.clone(),
|
||||
Arc::clone(&config),
|
||||
Arc::clone(&thread_store),
|
||||
thread_state_manager.clone(),
|
||||
thread_catalog_subscriptions.clone(),
|
||||
state_db.clone(),
|
||||
Arc::clone(&goal_service),
|
||||
);
|
||||
@@ -508,7 +510,6 @@ impl MessageProcessor {
|
||||
Arc::clone(&config),
|
||||
config_manager.clone(),
|
||||
pending_thread_unloads,
|
||||
Arc::clone(&thread_store),
|
||||
thread_state_manager,
|
||||
thread_watch_manager,
|
||||
thread_catalog_subscriptions,
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::*;
|
||||
pub(crate) struct ThreadCatalogSubscriptions {
|
||||
outgoing: Arc<OutgoingMessageSender>,
|
||||
connection_ids: Arc<Mutex<HashSet<ConnectionId>>>,
|
||||
ephemeral_threads: Arc<Mutex<HashMap<String, ThreadSummary>>>,
|
||||
}
|
||||
|
||||
impl ThreadCatalogSubscriptions {
|
||||
@@ -11,6 +12,7 @@ impl ThreadCatalogSubscriptions {
|
||||
Self {
|
||||
outgoing,
|
||||
connection_ids: Arc::new(Mutex::new(HashSet::new())),
|
||||
ephemeral_threads: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +37,12 @@ impl ThreadCatalogSubscriptions {
|
||||
}
|
||||
|
||||
pub(super) async fn publish_thread_summary(&self, thread: ThreadSummary) {
|
||||
if thread.ephemeral {
|
||||
self.ephemeral_threads
|
||||
.lock()
|
||||
.await
|
||||
.insert(thread.id.clone(), thread.clone());
|
||||
}
|
||||
let connection_ids = self
|
||||
.connection_ids
|
||||
.lock()
|
||||
@@ -84,4 +92,30 @@ impl ThreadCatalogSubscriptions {
|
||||
thread_summary_from_stored_thread(stored_thread, fallback_provider, fallback_cwd);
|
||||
self.publish_thread_summary(summary).await;
|
||||
}
|
||||
|
||||
pub(super) async fn update_ephemeral_thread_summary(
|
||||
&self,
|
||||
thread_id: ThreadId,
|
||||
event: &EventMsg,
|
||||
) -> Option<ThreadSummary> {
|
||||
let mut threads = self.ephemeral_threads.lock().await;
|
||||
let thread = threads.get_mut(&thread_id.to_string())?;
|
||||
match event {
|
||||
EventMsg::UserMessage(user) if thread.preview.is_empty() => {
|
||||
thread.preview = user.message.clone();
|
||||
}
|
||||
EventMsg::UserMessage(_)
|
||||
| EventMsg::ThreadSettingsApplied(_)
|
||||
| EventMsg::ThreadRolledBack(_)
|
||||
| EventMsg::TurnComplete(_)
|
||||
| EventMsg::TurnAborted(_) => {}
|
||||
_ => return None,
|
||||
}
|
||||
let updated_at_ms =
|
||||
i64::try_from(time::OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000)
|
||||
.ok()?;
|
||||
thread.updated_at = updated_at_ms / 1_000;
|
||||
thread.updated_at_ms = updated_at_ms;
|
||||
Some(thread.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,25 @@ pub(crate) struct ThreadGoalRequestProcessor {
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
outgoing: Arc<OutgoingMessageSender>,
|
||||
config: Arc<Config>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
thread_state_manager: ThreadStateManager,
|
||||
thread_catalog_subscriptions: ThreadCatalogSubscriptions,
|
||||
state_db: Option<StateDbHandle>,
|
||||
goal_service: Arc<GoalService>,
|
||||
}
|
||||
|
||||
impl ThreadGoalRequestProcessor {
|
||||
#[expect(
|
||||
clippy::too_many_arguments,
|
||||
reason = "request processor dependencies stay explicit"
|
||||
)]
|
||||
pub(crate) fn new(
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
outgoing: Arc<OutgoingMessageSender>,
|
||||
config: Arc<Config>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
thread_state_manager: ThreadStateManager,
|
||||
thread_catalog_subscriptions: ThreadCatalogSubscriptions,
|
||||
state_db: Option<StateDbHandle>,
|
||||
goal_service: Arc<GoalService>,
|
||||
) -> Self {
|
||||
@@ -28,7 +36,9 @@ impl ThreadGoalRequestProcessor {
|
||||
thread_manager,
|
||||
outgoing,
|
||||
config,
|
||||
thread_store,
|
||||
thread_state_manager,
|
||||
thread_catalog_subscriptions,
|
||||
state_db,
|
||||
goal_service,
|
||||
}
|
||||
@@ -158,6 +168,7 @@ impl ThreadGoalRequestProcessor {
|
||||
.await;
|
||||
self.emit_thread_goal_updated_ordered(thread_id, goal, listener_command_tx)
|
||||
.await;
|
||||
self.publish_thread_catalog_change(thread_id).await;
|
||||
outcome.apply_runtime_effects(&self.goal_service).await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -212,10 +223,22 @@ impl ThreadGoalRequestProcessor {
|
||||
if cleared {
|
||||
self.emit_thread_goal_cleared_ordered(thread_id, listener_command_tx)
|
||||
.await;
|
||||
self.publish_thread_catalog_change(thread_id).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn publish_thread_catalog_change(&self, thread_id: ThreadId) {
|
||||
self.thread_catalog_subscriptions
|
||||
.publish_thread_change(
|
||||
&self.thread_store,
|
||||
thread_id,
|
||||
self.config.model_provider_id.as_str(),
|
||||
&self.config.cwd,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn state_db_for_materialized_thread(
|
||||
&self,
|
||||
thread_id: ThreadId,
|
||||
|
||||
@@ -8,7 +8,6 @@ pub(super) struct ListenerTaskContext {
|
||||
pub(super) thread_state_manager: ThreadStateManager,
|
||||
pub(super) outgoing: Arc<OutgoingMessageSender>,
|
||||
pub(super) pending_thread_unloads: Arc<Mutex<HashSet<ThreadId>>>,
|
||||
pub(super) thread_store: Arc<dyn ThreadStore>,
|
||||
pub(super) thread_watch_manager: ThreadWatchManager,
|
||||
pub(super) thread_catalog_subscriptions: ThreadCatalogSubscriptions,
|
||||
pub(super) thread_list_state_permit: Arc<Semaphore>,
|
||||
@@ -122,17 +121,6 @@ impl UnloadingState {
|
||||
}
|
||||
}
|
||||
|
||||
fn catalog_worthy_event(event: &EventMsg) -> bool {
|
||||
matches!(
|
||||
event,
|
||||
EventMsg::TurnStarted(_)
|
||||
| EventMsg::TurnComplete(_)
|
||||
| EventMsg::TurnAborted(_)
|
||||
| EventMsg::ThreadSettingsApplied(_)
|
||||
| EventMsg::ThreadRolledBack(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) enum ThreadShutdownResult {
|
||||
Complete,
|
||||
SubmitFailed,
|
||||
@@ -280,7 +268,6 @@ pub(super) async fn ensure_listener_task_running(
|
||||
thread_manager,
|
||||
thread_state_manager,
|
||||
pending_thread_unloads,
|
||||
thread_store,
|
||||
thread_watch_manager,
|
||||
thread_catalog_subscriptions,
|
||||
thread_list_state_permit,
|
||||
@@ -290,6 +277,8 @@ pub(super) async fn ensure_listener_task_running(
|
||||
..
|
||||
} = listener_task_context;
|
||||
let outgoing_for_task = Arc::clone(&outgoing);
|
||||
let mut thread_metadata_updated_rx = conversation.subscribe_thread_metadata_updated();
|
||||
let is_ephemeral = conversation.config_snapshot().await.ephemeral;
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -315,6 +304,22 @@ pub(super) async fn ensure_listener_task_running(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
stored_thread = async {
|
||||
match &mut thread_metadata_updated_rx {
|
||||
Some(rx) => rx.recv().await.ok(),
|
||||
None => futures::future::pending().await,
|
||||
}
|
||||
} => {
|
||||
if let Some(stored_thread) = stored_thread {
|
||||
thread_catalog_subscriptions
|
||||
.publish_thread_summary(thread_summary_from_stored_thread(
|
||||
stored_thread,
|
||||
fallback_model_provider.as_str(),
|
||||
&fallback_cwd,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
event = conversation.next_event() => {
|
||||
let event = match event {
|
||||
Ok(event) => event,
|
||||
@@ -366,14 +371,13 @@ pub(super) async fn ensure_listener_task_running(
|
||||
fallback_model_provider.clone(),
|
||||
)
|
||||
.await;
|
||||
if catalog_worthy_event(&event.msg) {
|
||||
if is_ephemeral
|
||||
&& let Some(summary) = thread_catalog_subscriptions
|
||||
.update_ephemeral_thread_summary(conversation_id, &event.msg)
|
||||
.await
|
||||
{
|
||||
thread_catalog_subscriptions
|
||||
.publish_thread_change(
|
||||
&thread_store,
|
||||
conversation_id,
|
||||
fallback_model_provider.as_str(),
|
||||
&fallback_cwd,
|
||||
)
|
||||
.publish_thread_summary(summary)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +876,6 @@ impl ThreadRequestProcessor {
|
||||
thread_state_manager: self.thread_state_manager.clone(),
|
||||
outgoing: Arc::clone(&self.outgoing),
|
||||
pending_thread_unloads: Arc::clone(&self.pending_thread_unloads),
|
||||
thread_store: Arc::clone(&self.thread_store),
|
||||
thread_watch_manager: self.thread_watch_manager.clone(),
|
||||
thread_catalog_subscriptions: self.thread_catalog_subscriptions.clone(),
|
||||
thread_list_state_permit: self.thread_list_state_permit.clone(),
|
||||
@@ -979,7 +978,6 @@ impl ThreadRequestProcessor {
|
||||
thread_state_manager: self.thread_state_manager.clone(),
|
||||
outgoing: Arc::clone(&self.outgoing),
|
||||
pending_thread_unloads: Arc::clone(&self.pending_thread_unloads),
|
||||
thread_store: Arc::clone(&self.thread_store),
|
||||
thread_watch_manager: self.thread_watch_manager.clone(),
|
||||
thread_catalog_subscriptions: self.thread_catalog_subscriptions.clone(),
|
||||
thread_list_state_permit: self.thread_list_state_permit.clone(),
|
||||
@@ -1239,6 +1237,12 @@ impl ThreadRequestProcessor {
|
||||
otel.name = "app_server.thread_start.config_snapshot",
|
||||
))
|
||||
.await;
|
||||
let stored_thread = thread
|
||||
.read_thread(
|
||||
/*include_archived*/ true, /*include_history*/ false,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
let mut thread = build_thread_from_snapshot(
|
||||
thread_id,
|
||||
session_configured.session_id.to_string(),
|
||||
@@ -1309,7 +1313,16 @@ impl ThreadRequestProcessor {
|
||||
reasoning_effort: config_snapshot.reasoning_effort,
|
||||
multi_agent_mode: config_snapshot.multi_agent_mode,
|
||||
};
|
||||
let catalog_summary = thread_summary_from_thread(thread.clone(), /*archived_at*/ None);
|
||||
let catalog_summary = stored_thread.map_or_else(
|
||||
|| thread_summary_from_thread(thread.clone(), /*archived_at*/ None),
|
||||
|stored_thread| {
|
||||
thread_summary_from_stored_thread(
|
||||
stored_thread,
|
||||
thread.model_provider.as_str(),
|
||||
&thread.cwd,
|
||||
)
|
||||
},
|
||||
);
|
||||
let notif = thread_started_notification(thread);
|
||||
listener_task_context
|
||||
.outgoing
|
||||
|
||||
@@ -280,9 +280,14 @@ pub(super) fn thread_summary_from_stored_thread(
|
||||
fallback_provider: &str,
|
||||
fallback_cwd: &AbsolutePathBuf,
|
||||
) -> ThreadSummary {
|
||||
let archived_at = thread.archived_at.map(|dt| dt.timestamp());
|
||||
let created_at_ms = thread.created_at.timestamp_millis();
|
||||
let updated_at_ms = thread.updated_at.timestamp_millis();
|
||||
let archived_at = thread.archived_at.as_ref().map(chrono::DateTime::timestamp);
|
||||
let (thread, _) = thread_from_stored_thread(thread, fallback_provider, fallback_cwd);
|
||||
thread_summary_from_thread(thread, archived_at)
|
||||
let mut summary = thread_summary_from_thread(thread, archived_at);
|
||||
summary.created_at_ms = created_at_ms;
|
||||
summary.updated_at_ms = updated_at_ms;
|
||||
summary
|
||||
}
|
||||
|
||||
pub(super) fn thread_summary_from_thread(
|
||||
@@ -298,7 +303,9 @@ pub(super) fn thread_summary_from_thread(
|
||||
ephemeral: thread.ephemeral,
|
||||
model_provider: thread.model_provider,
|
||||
created_at: thread.created_at,
|
||||
created_at_ms: thread.created_at.saturating_mul(1_000),
|
||||
updated_at: thread.updated_at,
|
||||
updated_at_ms: thread.updated_at.saturating_mul(1_000),
|
||||
archived_at,
|
||||
path: thread.path,
|
||||
cwd: thread.cwd,
|
||||
|
||||
@@ -76,7 +76,6 @@ pub(crate) struct TurnRequestProcessor {
|
||||
config: Arc<Config>,
|
||||
config_manager: ConfigManager,
|
||||
pending_thread_unloads: Arc<Mutex<HashSet<ThreadId>>>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
thread_state_manager: ThreadStateManager,
|
||||
thread_watch_manager: ThreadWatchManager,
|
||||
thread_catalog_subscriptions: ThreadCatalogSubscriptions,
|
||||
@@ -135,7 +134,6 @@ impl TurnRequestProcessor {
|
||||
config: Arc<Config>,
|
||||
config_manager: ConfigManager,
|
||||
pending_thread_unloads: Arc<Mutex<HashSet<ThreadId>>>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
thread_state_manager: ThreadStateManager,
|
||||
thread_watch_manager: ThreadWatchManager,
|
||||
thread_catalog_subscriptions: ThreadCatalogSubscriptions,
|
||||
@@ -151,7 +149,6 @@ impl TurnRequestProcessor {
|
||||
config,
|
||||
config_manager,
|
||||
pending_thread_unloads,
|
||||
thread_store,
|
||||
thread_state_manager,
|
||||
thread_watch_manager,
|
||||
thread_catalog_subscriptions,
|
||||
@@ -1425,7 +1422,6 @@ impl TurnRequestProcessor {
|
||||
thread_state_manager: self.thread_state_manager.clone(),
|
||||
outgoing: Arc::clone(&self.outgoing),
|
||||
pending_thread_unloads: Arc::clone(&self.pending_thread_unloads),
|
||||
thread_store: Arc::clone(&self.thread_store),
|
||||
thread_watch_manager: self.thread_watch_manager.clone(),
|
||||
thread_catalog_subscriptions: self.thread_catalog_subscriptions.clone(),
|
||||
thread_list_state_permit: self.thread_list_state_permit.clone(),
|
||||
|
||||
@@ -7,6 +7,8 @@ use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ThreadCatalogChangedNotification;
|
||||
use codex_app_server_protocol::ThreadInjectItemsParams;
|
||||
use codex_app_server_protocol::ThreadInjectItemsResponse;
|
||||
use codex_app_server_protocol::ThreadListParams;
|
||||
use codex_app_server_protocol::ThreadListResponse;
|
||||
use codex_app_server_protocol::ThreadSetNameParams;
|
||||
@@ -23,41 +25,37 @@ use tokio::time::timeout;
|
||||
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn catalog_subscription_reports_new_empty_thread() -> Result<()> {
|
||||
async fn catalog_subscription_reports_new_and_injected_thread() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut app = start_app(&codex_home).await?;
|
||||
subscribe(&mut app).await?;
|
||||
|
||||
let subscribe_id = app
|
||||
.send_raw_request("thread/catalog/subscribe", /*params*/ None)
|
||||
.await?;
|
||||
let _: codex_app_server_protocol::ThreadCatalogSubscribeResponse =
|
||||
read_response(&mut app, subscribe_id).await?;
|
||||
|
||||
let start_id = app
|
||||
.send_thread_start_request(ThreadStartParams::default())
|
||||
.await?;
|
||||
let started = read_response::<ThreadStartResponse>(&mut app, start_id).await?;
|
||||
let notification: JSONRPCNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
app.read_stream_until_notification_message("thread/catalog/changed"),
|
||||
)
|
||||
.await??;
|
||||
let changed: ThreadCatalogChangedNotification = serde_json::from_value(
|
||||
notification
|
||||
.params
|
||||
.expect("thread/catalog/changed should have params"),
|
||||
)?;
|
||||
|
||||
assert_eq!(changed.thread.id, started.thread.id);
|
||||
let started = start_thread(&mut app, ThreadStartParams::default()).await?;
|
||||
let changed = read_catalog_change(&mut app).await?;
|
||||
assert_eq!(changed.thread.id, started.id);
|
||||
assert_eq!(changed.thread.preview, "");
|
||||
assert!(
|
||||
!started
|
||||
.thread
|
||||
.path
|
||||
.expect("thread path should be present")
|
||||
.exists()
|
||||
);
|
||||
|
||||
let inject_id = app
|
||||
.send_thread_inject_items_request(ThreadInjectItemsParams {
|
||||
thread_id: started.id.clone(),
|
||||
items: vec![serde_json::json!({
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{"type": "input_text", "text": "injected"}],
|
||||
})],
|
||||
})
|
||||
.await?;
|
||||
let _: ThreadInjectItemsResponse = read_response(&mut app, inject_id).await?;
|
||||
let injected = read_catalog_change(&mut app).await?;
|
||||
assert_eq!(injected.thread.id, started.id);
|
||||
assert!(injected.thread.updated_at_ms >= changed.thread.updated_at_ms);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -82,11 +80,7 @@ async fn catalog_subscription_reports_thread_outside_loaded_page() -> Result<()>
|
||||
)?;
|
||||
let mut app = start_app(&codex_home).await?;
|
||||
|
||||
let subscribe_id = app
|
||||
.send_raw_request("thread/catalog/subscribe", /*params*/ None)
|
||||
.await?;
|
||||
let _: codex_app_server_protocol::ThreadCatalogSubscribeResponse =
|
||||
read_response(&mut app, subscribe_id).await?;
|
||||
subscribe(&mut app).await?;
|
||||
|
||||
let list_id = app
|
||||
.send_thread_list_request(ThreadListParams {
|
||||
@@ -98,6 +92,7 @@ async fn catalog_subscription_reports_thread_outside_loaded_page() -> Result<()>
|
||||
source_kinds: None,
|
||||
archived: None,
|
||||
cwd: None,
|
||||
parent_thread_id: None,
|
||||
use_state_db_only: false,
|
||||
search_term: None,
|
||||
})
|
||||
@@ -115,12 +110,12 @@ async fn catalog_subscription_reports_thread_outside_loaded_page() -> Result<()>
|
||||
|
||||
let notification: JSONRPCNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
app.read_stream_until_notification_message("thread/catalog/changed"),
|
||||
app.read_stream_until_notification_message("threadCatalog/changed"),
|
||||
)
|
||||
.await??;
|
||||
let raw_params = notification
|
||||
.params
|
||||
.expect("thread/catalog/changed should have params");
|
||||
.expect("threadCatalog/changed should have params");
|
||||
assert_eq!(raw_params["thread"].get("turns"), None);
|
||||
|
||||
let changed: ThreadCatalogChangedNotification = serde_json::from_value(raw_params)?;
|
||||
@@ -134,6 +129,41 @@ async fn catalog_subscription_reports_thread_outside_loaded_page() -> Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn catalog_subscription_reports_ephemeral_thread_updates() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut app = start_app(&codex_home).await?;
|
||||
subscribe(&mut app).await?;
|
||||
|
||||
let thread = start_thread(
|
||||
&mut app,
|
||||
ThreadStartParams {
|
||||
ephemeral: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let created = read_catalog_change(&mut app).await?;
|
||||
|
||||
let turn_id = app
|
||||
.send_turn_start_request(codex_app_server_protocol::TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![codex_app_server_protocol::UserInput::Text {
|
||||
text: "Ephemeral catalog preview".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: codex_app_server_protocol::TurnStartResponse = read_response(&mut app, turn_id).await?;
|
||||
let changed = read_catalog_change(&mut app).await?;
|
||||
assert_eq!(changed.thread.id, thread.id);
|
||||
assert_eq!(changed.thread.preview, "Ephemeral catalog preview");
|
||||
assert!(changed.thread.updated_at_ms >= created.thread.updated_at_ms);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_app(codex_home: &TempDir) -> Result<TestAppServer> {
|
||||
write_mock_responses_config_toml(
|
||||
codex_home.path(),
|
||||
@@ -149,6 +179,38 @@ async fn start_app(codex_home: &TempDir) -> Result<TestAppServer> {
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
async fn subscribe(app: &mut TestAppServer) -> Result<()> {
|
||||
let subscribe_id = app
|
||||
.send_raw_request("threadCatalog/subscribe", /*params*/ None)
|
||||
.await?;
|
||||
let _: codex_app_server_protocol::ThreadCatalogSubscribeResponse =
|
||||
read_response(app, subscribe_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_thread(
|
||||
app: &mut TestAppServer,
|
||||
params: ThreadStartParams,
|
||||
) -> Result<codex_app_server_protocol::Thread> {
|
||||
let start_id = app.send_thread_start_request(params).await?;
|
||||
let started = read_response::<ThreadStartResponse>(app, start_id).await?;
|
||||
Ok(started.thread)
|
||||
}
|
||||
|
||||
async fn read_catalog_change(app: &mut TestAppServer) -> Result<ThreadCatalogChangedNotification> {
|
||||
let notification: JSONRPCNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
app.read_stream_until_notification_message("threadCatalog/changed"),
|
||||
)
|
||||
.await??;
|
||||
serde_json::from_value(
|
||||
notification
|
||||
.params
|
||||
.ok_or_else(|| anyhow::anyhow!("threadCatalog/changed should have params"))?,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn rename_thread(app: &mut TestAppServer, thread_id: String, name: &str) -> Result<()> {
|
||||
let rename_id = app
|
||||
.send_thread_set_name_request(ThreadSetNameParams {
|
||||
|
||||
@@ -1080,6 +1080,14 @@ async fn thread_resume_keeps_paused_goal_paused() -> Result<()> {
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
let subscribe_id = mcp
|
||||
.send_raw_request("threadCatalog/subscribe", /*params*/ None)
|
||||
.await?;
|
||||
let _: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(subscribe_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let goal_id = mcp
|
||||
.send_raw_request(
|
||||
@@ -1102,6 +1110,11 @@ async fn thread_resume_keeps_paused_goal_paused() -> Result<()> {
|
||||
mcp.read_stream_until_notification_message("thread/goal/updated"),
|
||||
)
|
||||
.await??;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("threadCatalog/changed"),
|
||||
)
|
||||
.await??;
|
||||
mcp.clear_message_buffer();
|
||||
|
||||
let resume_id = mcp
|
||||
|
||||
@@ -416,6 +416,12 @@ impl CodexThread {
|
||||
self.codex.next_event().await
|
||||
}
|
||||
|
||||
pub fn subscribe_thread_metadata_updated(
|
||||
&self,
|
||||
) -> Option<tokio::sync::broadcast::Receiver<StoredThread>> {
|
||||
self.codex.session.subscribe_thread_metadata_updated()
|
||||
}
|
||||
|
||||
pub async fn agent_status(&self) -> AgentStatus {
|
||||
self.codex.agent_status().await
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ use codex_thread_store::LiveThreadInitGuard;
|
||||
use codex_thread_store::LocalThreadStore;
|
||||
use codex_thread_store::ReadThreadParams;
|
||||
use codex_thread_store::ResumeThreadParams;
|
||||
use codex_thread_store::StoredThread;
|
||||
use codex_thread_store::ThreadPersistenceMetadata;
|
||||
use codex_thread_store::ThreadStore;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -1154,6 +1155,13 @@ impl Session {
|
||||
self.services.live_thread.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn subscribe_thread_metadata_updated(
|
||||
&self,
|
||||
) -> Option<tokio::sync::broadcast::Receiver<StoredThread>> {
|
||||
self.live_thread()
|
||||
.map(LiveThread::subscribe_metadata_updated)
|
||||
}
|
||||
|
||||
/// Flush rollout writes and return the final durability-barrier result.
|
||||
#[instrument(name = "session.flush_rollout", level = "trace", skip_all)]
|
||||
pub(crate) async fn flush_rollout(&self) -> std::io::Result<()> {
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_rollout::RolloutPersistenceTelemetry;
|
||||
use codex_rollout::measure_and_filter_rollout_items;
|
||||
use codex_rollout::persisted_rollout_items;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::AppendThreadItemsParams;
|
||||
@@ -35,6 +36,7 @@ pub struct LiveThread {
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
metadata_sync: Arc<Mutex<ThreadMetadataSync>>,
|
||||
persistence_telemetry: RolloutPersistenceTelemetry,
|
||||
metadata_updated_tx: broadcast::Sender<StoredThread>,
|
||||
}
|
||||
|
||||
/// Owns a live thread while session initialization is still fallible.
|
||||
@@ -87,6 +89,8 @@ impl Drop for LiveThreadInitGuard {
|
||||
}
|
||||
|
||||
impl LiveThread {
|
||||
const METADATA_UPDATED_CHANNEL_CAPACITY: usize = 16;
|
||||
|
||||
pub async fn create(
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
params: CreateThreadParams,
|
||||
@@ -94,11 +98,13 @@ impl LiveThread {
|
||||
let thread_id = params.thread_id;
|
||||
let metadata_sync = ThreadMetadataSync::for_create(¶ms).await;
|
||||
thread_store.create_thread(params).await?;
|
||||
let (metadata_updated_tx, _) = broadcast::channel(Self::METADATA_UPDATED_CHANNEL_CAPACITY);
|
||||
Ok(Self {
|
||||
thread_id,
|
||||
thread_store,
|
||||
metadata_sync: Arc::new(Mutex::new(metadata_sync)),
|
||||
persistence_telemetry: RolloutPersistenceTelemetry::new(thread_id),
|
||||
metadata_updated_tx,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,14 +136,20 @@ impl LiveThread {
|
||||
}
|
||||
}
|
||||
}
|
||||
let (metadata_updated_tx, _) = broadcast::channel(Self::METADATA_UPDATED_CHANNEL_CAPACITY);
|
||||
Ok(Self {
|
||||
thread_id,
|
||||
thread_store,
|
||||
metadata_sync: Arc::new(Mutex::new(metadata_sync)),
|
||||
persistence_telemetry: RolloutPersistenceTelemetry::new(thread_id),
|
||||
metadata_updated_tx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn subscribe_metadata_updated(&self) -> broadcast::Receiver<StoredThread> {
|
||||
self.metadata_updated_tx.subscribe()
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
level = "trace",
|
||||
skip_all,
|
||||
@@ -172,13 +184,15 @@ impl LiveThread {
|
||||
.await
|
||||
.observe_appended_items(canonical_items.as_slice());
|
||||
if let Some(update) = update {
|
||||
self.thread_store
|
||||
let stored_thread = self
|
||||
.thread_store
|
||||
.update_thread_metadata(UpdateThreadMetadataParams {
|
||||
thread_id: self.thread_id,
|
||||
patch: update.patch.clone(),
|
||||
include_archived: true,
|
||||
})
|
||||
.await?;
|
||||
self.publish_metadata_updated(stored_thread);
|
||||
self.metadata_sync
|
||||
.lock()
|
||||
.await
|
||||
@@ -240,7 +254,8 @@ impl LiveThread {
|
||||
include_archived: bool,
|
||||
) -> ThreadStoreResult<()> {
|
||||
self.flush_pending_metadata_update().await?;
|
||||
self.thread_store
|
||||
let stored_thread = self
|
||||
.thread_store
|
||||
.update_thread_metadata(UpdateThreadMetadataParams {
|
||||
thread_id: self.thread_id,
|
||||
patch: ThreadMetadataPatch {
|
||||
@@ -250,6 +265,7 @@ impl LiveThread {
|
||||
include_archived,
|
||||
})
|
||||
.await?;
|
||||
self.publish_metadata_updated(stored_thread);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -259,13 +275,16 @@ impl LiveThread {
|
||||
include_archived: bool,
|
||||
) -> ThreadStoreResult<StoredThread> {
|
||||
self.flush_pending_metadata_update().await?;
|
||||
self.thread_store
|
||||
let stored_thread = self
|
||||
.thread_store
|
||||
.update_thread_metadata(UpdateThreadMetadataParams {
|
||||
thread_id: self.thread_id,
|
||||
patch,
|
||||
include_archived,
|
||||
})
|
||||
.await
|
||||
.await?;
|
||||
self.publish_metadata_updated(stored_thread.clone());
|
||||
Ok(stored_thread)
|
||||
}
|
||||
|
||||
/// Returns the live local rollout path for legacy local-only callers.
|
||||
@@ -306,17 +325,23 @@ impl LiveThread {
|
||||
let Some(update) = update else {
|
||||
return Ok(());
|
||||
};
|
||||
self.thread_store
|
||||
let stored_thread = self
|
||||
.thread_store
|
||||
.update_thread_metadata(UpdateThreadMetadataParams {
|
||||
thread_id: self.thread_id,
|
||||
patch: update.patch.clone(),
|
||||
include_archived: true,
|
||||
})
|
||||
.await?;
|
||||
self.publish_metadata_updated(stored_thread);
|
||||
self.metadata_sync
|
||||
.lock()
|
||||
.await
|
||||
.mark_pending_update_applied(&update);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_metadata_updated(&self, stored_thread: StoredThread) {
|
||||
let _ = self.metadata_updated_tx.send(stored_thread);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user