Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions crates/goose-cli/src/commands/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ fn prompt_interactive_session_removal(sessions: &[Session]) -> Result<Vec<Sessio
&s.name
};
let truncated_desc = safe_truncate(desc, TRUNCATED_DESC_LENGTH);
let display_text = format!("{} - {} ({})", s.updated_at, truncated_desc, s.id);
let display_text =
format!("{} - {} ({})", session_activity_at(s), truncated_desc, s.id);
(display_text, s.clone())
})
.collect();
Expand Down Expand Up @@ -150,6 +151,10 @@ fn write_line_or_broken_pipe_ok<W: Write>(out: &mut W, line: &str) -> Result<boo
}
}

fn session_activity_at(session: &Session) -> chrono::DateTime<chrono::Utc> {
session.last_message_at.unwrap_or(session.updated_at)
}

pub async fn handle_session_list(
format: String,
ascending: bool,
Expand All @@ -170,9 +175,9 @@ pub async fn handle_session_list(
}

if ascending {
sessions.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
sessions.sort_by_key(session_activity_at);
} else {
sessions.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
sessions.sort_by_key(|b| std::cmp::Reverse(session_activity_at(b)));
}

if let Some(n) = limit {
Expand Down Expand Up @@ -206,7 +211,7 @@ pub async fn handle_session_list(
"{} - {} - {} - {}",
session.id,
session.name,
session.updated_at,
session_activity_at(&session),
display_path_with_tilde(&session.working_dir)
);
if !write_line_or_broken_pipe_ok(&mut out, &output)? {
Expand Down
97 changes: 46 additions & 51 deletions crates/goose/src/acp/response_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use agent_client_protocol::schema::{
use agent_client_protocol::{Client, ConnectionTo};
use goose_providers::model::ModelConfig;
use goose_providers::thinking::ThinkingEffort;
use serde::Serialize;
use strum::{EnumMessage, VariantNames};

use super::server::{build_usage_updates, DEFAULT_PROVIDER_ID, DEFAULT_PROVIDER_LABEL};
Expand All @@ -22,60 +23,54 @@ pub(super) fn session_provider_selection(session: &Session) -> &str {
.unwrap_or(DEFAULT_PROVIDER_ID)
}

pub(super) fn session_meta(session: &Session) -> serde_json::Map<String, serde_json::Value> {
let mut meta = serde_json::Map::new();
meta.insert(
"messageCount".to_string(),
serde_json::Value::Number(session.message_count.into()),
);
meta.insert(
"createdAt".to_string(),
serde_json::Value::String(session.created_at.to_rfc3339()),
);
if let Some(ref archived_at) = session.archived_at {
meta.insert(
"archivedAt".to_string(),
serde_json::Value::String(archived_at.to_rfc3339()),
);
}
meta.insert(
"userSetName".to_string(),
serde_json::Value::Bool(session.user_set_name),
);
meta.insert(
"sessionType".to_string(),
serde_json::Value::String(session.session_type.to_string()),
);
meta.insert(
"hasRecipe".to_string(),
serde_json::Value::Bool(session.recipe.is_some()),
);
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SessionMeta<'a> {
message_count: usize,
created_at: chrono::DateTime<chrono::Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
last_message_at: Option<chrono::DateTime<chrono::Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
archived_at: Option<chrono::DateTime<chrono::Utc>>,
user_set_name: bool,
session_type: String,
has_recipe: bool,
#[serde(skip_serializing_if = "Option::is_none")]
project_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
provider_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
model_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
last_message_snippet: Option<&'a str>,
}

if let Some(ref pid) = session.project_id {
meta.insert(
"projectId".to_string(),
serde_json::Value::String(pid.clone()),
);
}
if let Some(ref provider) = session.provider_name {
meta.insert(
"providerId".to_string(),
serde_json::Value::String(provider.clone()),
);
}
if let Some(ref mc) = session.model_config {
meta.insert(
"modelId".to_string(),
serde_json::Value::String(mc.model_name.clone()),
);
impl<'a> From<&'a Session> for SessionMeta<'a> {
fn from(session: &'a Session) -> Self {
Self {
message_count: session.message_count,
created_at: session.created_at,
last_message_at: session.last_message_at,
archived_at: session.archived_at,
user_set_name: session.user_set_name,
session_type: session.session_type.to_string(),
has_recipe: session.recipe.is_some(),
project_id: session.project_id.as_deref(),
provider_id: session.provider_name.as_deref(),
model_id: session
.model_config
.as_ref()
.map(|mc| mc.model_name.as_str()),
last_message_snippet: session.last_message_snippet.as_deref(),
}
}
if let Some(ref snippet) = session.last_message_snippet {
meta.insert(
"lastMessageSnippet".to_string(),
serde_json::Value::String(snippet.clone()),
);
}

pub(super) fn session_meta(session: &Session) -> serde_json::Map<String, serde_json::Value> {
match serde_json::to_value(SessionMeta::from(session)) {
Ok(serde_json::Value::Object(meta)) => meta,
_ => serde_json::Map::new(),
}
meta
}

pub(super) fn session_response_meta(
Expand Down
11 changes: 6 additions & 5 deletions crates/goose/src/acp/server/list_sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ const ACP_SESSION_LIST_TYPES: [SessionType; 3] =

#[derive(Debug, Serialize, Deserialize)]
struct SessionListCursorToken {
updated_at: chrono::DateTime<chrono::Utc>,
// Goose stores updated_at with second precision in common write paths, so the
// cursor needs the full (updated_at, id) sort key to avoid skipping tied rows.
#[serde(alias = "updated_at")]
sort_at: chrono::DateTime<chrono::Utc>,
// Goose stores timestamps with second precision in common write paths, so the
// cursor needs the full (sort_at, id) sort key to avoid skipping tied rows.
session_id: String,
filter_hash: String,
}
Expand Down Expand Up @@ -146,7 +147,7 @@ fn decode_session_list_cursor(
}

Ok(Some(SessionListCursor {
updated_at: token.updated_at,
sort_at: token.sort_at,
session_id: token.session_id,
}))
}
Expand All @@ -158,7 +159,7 @@ fn encode_session_list_cursor(
keyword: Option<&str>,
) -> Result<String, agent_client_protocol::Error> {
let token = SessionListCursorToken {
updated_at: cursor.updated_at,
sort_at: cursor.sort_at,
session_id: cursor.session_id.clone(),
filter_hash: session_list_filter_hash(cwd, session_types, keyword)?,
};
Expand Down
Loading
Loading