fix(acp): separate acp sessions from user sessions (#7857)
Signed-off-by: Rabi Mishra <mishra.rabi@gmail.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -848,7 +848,7 @@ impl GooseAcpAgent {
|
|||||||
.create_session(
|
.create_session(
|
||||||
args.cwd.clone(),
|
args.cwd.clone(),
|
||||||
"ACP Session".to_string(),
|
"ACP Session".to_string(),
|
||||||
SessionType::User,
|
SessionType::Acp,
|
||||||
self.goose_mode,
|
self.goose_mode,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1246,7 +1246,7 @@ impl GooseAcpAgent {
|
|||||||
async fn on_list_sessions(&self) -> Result<ListSessionsResponse, sacp::Error> {
|
async fn on_list_sessions(&self) -> Result<ListSessionsResponse, sacp::Error> {
|
||||||
let sessions = self
|
let sessions = self
|
||||||
.session_manager
|
.session_manager
|
||||||
.list_sessions()
|
.list_sessions_by_types(&[SessionType::Acp])
|
||||||
.await
|
.await
|
||||||
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
|
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
|
||||||
let session_infos: Vec<SessionInfo> = sessions
|
let session_infos: Vec<SessionInfo> = sessions
|
||||||
@@ -1422,7 +1422,7 @@ impl GooseAcpAgent {
|
|||||||
) -> Result<ImportSessionResponse, sacp::Error> {
|
) -> Result<ImportSessionResponse, sacp::Error> {
|
||||||
let session = self
|
let session = self
|
||||||
.session_manager
|
.session_manager
|
||||||
.import_session(&req.data)
|
.import_session(&req.data, Some(SessionType::Acp))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
|
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
|
||||||
let session_json = serde_json::to_value(&session)
|
let session_json = serde_json::to_value(&session)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use goose::agents::ExtensionConfig;
|
use goose::agents::ExtensionConfig;
|
||||||
use goose::recipe::Recipe;
|
use goose::recipe::Recipe;
|
||||||
use goose::session::session_manager::SessionInsights;
|
use goose::session::session_manager::{SessionInsights, SessionType};
|
||||||
use goose::session::{EnabledExtensionsState, Session};
|
use goose::session::{EnabledExtensionsState, Session};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -357,7 +357,7 @@ async fn import_session(
|
|||||||
) -> Result<Json<Session>, StatusCode> {
|
) -> Result<Json<Session>, StatusCode> {
|
||||||
let session = state
|
let session = state
|
||||||
.session_manager()
|
.session_manager()
|
||||||
.import_session(&request.json)
|
.import_session(&request.json, Some(SessionType::User))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||||
|
|
||||||
@@ -583,7 +583,14 @@ async fn search_sessions(
|
|||||||
|
|
||||||
let search_results = state
|
let search_results = state
|
||||||
.session_manager()
|
.session_manager()
|
||||||
.search_chat_history(query, Some(limit), after_date, before_date, None)
|
.search_chat_history(
|
||||||
|
query,
|
||||||
|
Some(limit),
|
||||||
|
after_date,
|
||||||
|
before_date,
|
||||||
|
None,
|
||||||
|
vec![SessionType::User, SessionType::Scheduled],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::agents::extension::PlatformExtensionContext;
|
use crate::agents::extension::PlatformExtensionContext;
|
||||||
use crate::agents::mcp_client::{Error, McpClientTrait};
|
use crate::agents::mcp_client::{Error, McpClientTrait};
|
||||||
use crate::agents::tool_execution::ToolCallContext;
|
use crate::agents::tool_execution::ToolCallContext;
|
||||||
|
use crate::session::session_manager::SessionType;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
@@ -58,6 +59,13 @@ impl ChatRecallClient {
|
|||||||
Ok(Self { info, context })
|
Ok(Self { info, context })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_session_types(&self) -> Vec<SessionType> {
|
||||||
|
match self.context.session.as_ref().map(|s| s.session_type) {
|
||||||
|
Some(SessionType::Acp) => vec![SessionType::Acp],
|
||||||
|
_ => vec![SessionType::User, SessionType::Scheduled],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
async fn handle_chatrecall(
|
async fn handle_chatrecall(
|
||||||
&self,
|
&self,
|
||||||
@@ -177,6 +185,7 @@ impl ChatRecallClient {
|
|||||||
after_date,
|
after_date,
|
||||||
before_date,
|
before_date,
|
||||||
exclude_session_id,
|
exclude_session_id,
|
||||||
|
self.search_session_types(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::conversation::message::MessageContent;
|
use crate::conversation::message::MessageContent;
|
||||||
|
use crate::session::session_manager::SessionType;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -52,6 +53,7 @@ pub struct ChatHistorySearch<'a> {
|
|||||||
after_date: Option<DateTime<Utc>>,
|
after_date: Option<DateTime<Utc>>,
|
||||||
before_date: Option<DateTime<Utc>>,
|
before_date: Option<DateTime<Utc>>,
|
||||||
exclude_session_id: Option<String>,
|
exclude_session_id: Option<String>,
|
||||||
|
session_types: Vec<SessionType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ChatHistorySearch<'a> {
|
impl<'a> ChatHistorySearch<'a> {
|
||||||
@@ -62,6 +64,7 @@ impl<'a> ChatHistorySearch<'a> {
|
|||||||
after_date: Option<DateTime<Utc>>,
|
after_date: Option<DateTime<Utc>>,
|
||||||
before_date: Option<DateTime<Utc>>,
|
before_date: Option<DateTime<Utc>>,
|
||||||
exclude_session_id: Option<String>,
|
exclude_session_id: Option<String>,
|
||||||
|
session_types: Vec<SessionType>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pool,
|
pool,
|
||||||
@@ -70,6 +73,7 @@ impl<'a> ChatHistorySearch<'a> {
|
|||||||
after_date,
|
after_date,
|
||||||
before_date,
|
before_date,
|
||||||
exclude_session_id,
|
exclude_session_id,
|
||||||
|
session_types,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +106,10 @@ impl<'a> ChatHistorySearch<'a> {
|
|||||||
query_builder = query_builder.bind(exclude_id);
|
query_builder = query_builder.bind(exclude_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for t in &self.session_types {
|
||||||
|
query_builder = query_builder.bind(t.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(after) = self.after_date {
|
if let Some(after) = self.after_date {
|
||||||
query_builder = query_builder.bind(after);
|
query_builder = query_builder.bind(after);
|
||||||
}
|
}
|
||||||
@@ -159,6 +167,16 @@ impl<'a> ChatHistorySearch<'a> {
|
|||||||
sql.push_str(" AND s.id != ?");
|
sql.push_str(" AND s.id != ?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.session_types.is_empty() {
|
||||||
|
let placeholders: String = self
|
||||||
|
.session_types
|
||||||
|
.iter()
|
||||||
|
.map(|_| "?")
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
sql.push_str(&format!(" AND s.session_type IN ({})", placeholders));
|
||||||
|
}
|
||||||
|
|
||||||
if self.after_date.is_some() {
|
if self.after_date.is_some() {
|
||||||
sql.push_str(" AND m.timestamp >= ?");
|
sql.push_str(" AND m.timestamp >= ?");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,25 @@ use std::sync::{Arc, LazyLock};
|
|||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
pub const CURRENT_SCHEMA_VERSION: i32 = 8;
|
pub const CURRENT_SCHEMA_VERSION: i32 = 9;
|
||||||
pub const SESSIONS_FOLDER: &str = "sessions";
|
pub const SESSIONS_FOLDER: &str = "sessions";
|
||||||
pub const DB_NAME: &str = "sessions.db";
|
pub const DB_NAME: &str = "sessions.db";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq, Default)]
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
ToSchema,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Default,
|
||||||
|
strum::Display,
|
||||||
|
strum::EnumString,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum SessionType {
|
pub enum SessionType {
|
||||||
#[default]
|
#[default]
|
||||||
User,
|
User,
|
||||||
@@ -33,35 +46,7 @@ pub enum SessionType {
|
|||||||
Hidden,
|
Hidden,
|
||||||
Terminal,
|
Terminal,
|
||||||
Gateway,
|
Gateway,
|
||||||
}
|
Acp,
|
||||||
|
|
||||||
impl std::fmt::Display for SessionType {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
SessionType::User => write!(f, "user"),
|
|
||||||
SessionType::SubAgent => write!(f, "sub_agent"),
|
|
||||||
SessionType::Hidden => write!(f, "hidden"),
|
|
||||||
SessionType::Scheduled => write!(f, "scheduled"),
|
|
||||||
SessionType::Terminal => write!(f, "terminal"),
|
|
||||||
SessionType::Gateway => write!(f, "gateway"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for SessionType {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"user" => Ok(SessionType::User),
|
|
||||||
"sub_agent" => Ok(SessionType::SubAgent),
|
|
||||||
"hidden" => Ok(SessionType::Hidden),
|
|
||||||
"scheduled" => Ok(SessionType::Scheduled),
|
|
||||||
"terminal" => Ok(SessionType::Terminal),
|
|
||||||
"gateway" => Ok(SessionType::Gateway),
|
|
||||||
_ => Err(anyhow::anyhow!("Invalid session type: {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static SESSION_STORAGE: LazyLock<Arc<SessionStorage>> =
|
static SESSION_STORAGE: LazyLock<Arc<SessionStorage>> =
|
||||||
@@ -323,15 +308,23 @@ impl SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_insights(&self) -> Result<SessionInsights> {
|
pub async fn get_insights(&self) -> Result<SessionInsights> {
|
||||||
self.storage.get_insights().await
|
self.storage
|
||||||
|
.get_insights(&[SessionType::User, SessionType::Scheduled])
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn export_session(&self, id: &str) -> Result<String> {
|
pub async fn export_session(&self, id: &str) -> Result<String> {
|
||||||
self.storage.export_session(id).await
|
self.storage.export_session(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn import_session(&self, json: &str) -> Result<Session> {
|
pub async fn import_session(
|
||||||
self.storage.import_session(self, json).await
|
&self,
|
||||||
|
json: &str,
|
||||||
|
session_type_override: Option<SessionType>,
|
||||||
|
) -> Result<Session> {
|
||||||
|
self.storage
|
||||||
|
.import_session(self, json, session_type_override)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn copy_session(&self, session_id: &str, new_name: String) -> Result<Session> {
|
pub async fn copy_session(&self, session_id: &str, new_name: String) -> Result<Session> {
|
||||||
@@ -376,9 +369,17 @@ impl SessionManager {
|
|||||||
after_date: Option<chrono::DateTime<chrono::Utc>>,
|
after_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
before_date: Option<chrono::DateTime<chrono::Utc>>,
|
before_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
exclude_session_id: Option<String>,
|
exclude_session_id: Option<String>,
|
||||||
|
session_types: Vec<SessionType>,
|
||||||
) -> Result<crate::session::chat_history_search::ChatRecallResults> {
|
) -> Result<crate::session::chat_history_search::ChatRecallResults> {
|
||||||
self.storage
|
self.storage
|
||||||
.search_chat_history(query, limit, after_date, before_date, exclude_session_id)
|
.search_chat_history(
|
||||||
|
query,
|
||||||
|
limit,
|
||||||
|
after_date,
|
||||||
|
before_date,
|
||||||
|
exclude_session_id,
|
||||||
|
session_types,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,6 +920,19 @@ impl SessionStorage {
|
|||||||
.execute(&mut **tx)
|
.execute(&mut **tx)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
9 => {
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
UPDATE sessions
|
||||||
|
SET session_type = 'acp'
|
||||||
|
WHERE session_type = 'user'
|
||||||
|
AND name = 'ACP Session'
|
||||||
|
AND user_set_name = FALSE
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.execute(&mut **tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
anyhow::bail!("Unknown migration version: {}", version);
|
anyhow::bail!("Unknown migration version: {}", version);
|
||||||
}
|
}
|
||||||
@@ -1316,17 +1330,32 @@ impl SessionStorage {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_insights(&self) -> Result<SessionInsights> {
|
async fn get_insights(&self, types: &[SessionType]) -> Result<SessionInsights> {
|
||||||
let pool = self.pool().await?;
|
if types.is_empty() {
|
||||||
let row = sqlx::query_as::<_, (i64, Option<i64>)>(
|
return Ok(SessionInsights {
|
||||||
|
total_sessions: 0,
|
||||||
|
total_tokens: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholders: String = types.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
|
||||||
|
let query = format!(
|
||||||
r#"
|
r#"
|
||||||
SELECT COUNT(*) as total_sessions,
|
SELECT COUNT(*) as total_sessions,
|
||||||
COALESCE(SUM(COALESCE(accumulated_total_tokens, total_tokens, 0)), 0) as total_tokens
|
COALESCE(SUM(COALESCE(accumulated_total_tokens, total_tokens, 0)), 0) as total_tokens
|
||||||
FROM sessions
|
FROM sessions
|
||||||
|
WHERE session_type IN ({})
|
||||||
"#,
|
"#,
|
||||||
)
|
placeholders
|
||||||
.fetch_one(pool)
|
);
|
||||||
.await?;
|
|
||||||
|
let pool = self.pool().await?;
|
||||||
|
let mut q = sqlx::query_as::<_, (i64, Option<i64>)>(&query);
|
||||||
|
for t in types {
|
||||||
|
q = q.bind(t.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = q.fetch_one(pool).await?;
|
||||||
|
|
||||||
Ok(SessionInsights {
|
Ok(SessionInsights {
|
||||||
total_sessions: row.0 as usize,
|
total_sessions: row.0 as usize,
|
||||||
@@ -1343,6 +1372,7 @@ impl SessionStorage {
|
|||||||
&self,
|
&self,
|
||||||
session_manager: &SessionManager,
|
session_manager: &SessionManager,
|
||||||
json: &str,
|
json: &str,
|
||||||
|
session_type_override: Option<SessionType>,
|
||||||
) -> Result<Session> {
|
) -> Result<Session> {
|
||||||
let import: Session = serde_json::from_str(json)?;
|
let import: Session = serde_json::from_str(json)?;
|
||||||
|
|
||||||
@@ -1350,7 +1380,7 @@ impl SessionStorage {
|
|||||||
.create_session(
|
.create_session(
|
||||||
import.working_dir.clone(),
|
import.working_dir.clone(),
|
||||||
import.name.clone(),
|
import.name.clone(),
|
||||||
import.session_type,
|
session_type_override.unwrap_or(import.session_type),
|
||||||
import.goose_mode,
|
import.goose_mode,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -1445,6 +1475,7 @@ impl SessionStorage {
|
|||||||
after_date: Option<chrono::DateTime<chrono::Utc>>,
|
after_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
before_date: Option<chrono::DateTime<chrono::Utc>>,
|
before_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
exclude_session_id: Option<String>,
|
exclude_session_id: Option<String>,
|
||||||
|
session_types: Vec<SessionType>,
|
||||||
) -> Result<crate::session::chat_history_search::ChatRecallResults> {
|
) -> Result<crate::session::chat_history_search::ChatRecallResults> {
|
||||||
use crate::session::chat_history_search::ChatHistorySearch;
|
use crate::session::chat_history_search::ChatHistorySearch;
|
||||||
|
|
||||||
@@ -1456,6 +1487,7 @@ impl SessionStorage {
|
|||||||
after_date,
|
after_date,
|
||||||
before_date,
|
before_date,
|
||||||
exclude_session_id,
|
exclude_session_id,
|
||||||
|
session_types,
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
@@ -1753,7 +1785,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let exported = sm.export_session(&original.id).await.unwrap();
|
let exported = sm.export_session(&original.id).await.unwrap();
|
||||||
let imported = sm.import_session(&exported).await.unwrap();
|
let imported = sm.import_session(&exported, None).await.unwrap();
|
||||||
|
|
||||||
assert_ne!(imported.id, original.id);
|
assert_ne!(imported.id, original.id);
|
||||||
assert_eq!(imported.name, DESCRIPTION);
|
assert_eq!(imported.name, DESCRIPTION);
|
||||||
@@ -1770,6 +1802,69 @@ mod tests {
|
|||||||
assert_eq!(conversation.messages()[1].role, Role::Assistant);
|
assert_eq!(conversation.messages()[1].role, Role::Assistant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_list_sessions_filters_by_type() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let sm = SessionManager::new(temp_dir.path().to_path_buf());
|
||||||
|
|
||||||
|
let user_session = sm
|
||||||
|
.create_session(
|
||||||
|
PathBuf::from("/tmp/test"),
|
||||||
|
"User session".to_string(),
|
||||||
|
SessionType::User,
|
||||||
|
GooseMode::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sm.add_message(
|
||||||
|
&user_session.id,
|
||||||
|
&Message {
|
||||||
|
id: None,
|
||||||
|
role: Role::User,
|
||||||
|
created: chrono::Utc::now().timestamp_millis(),
|
||||||
|
content: vec![MessageContent::text("hello world")],
|
||||||
|
metadata: Default::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let acp_session = sm
|
||||||
|
.create_session(
|
||||||
|
PathBuf::from("/tmp/test"),
|
||||||
|
"ACP session".to_string(),
|
||||||
|
SessionType::Acp,
|
||||||
|
GooseMode::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sm.add_message(
|
||||||
|
&acp_session.id,
|
||||||
|
&Message {
|
||||||
|
id: None,
|
||||||
|
role: Role::User,
|
||||||
|
created: chrono::Utc::now().timestamp_millis(),
|
||||||
|
content: vec![MessageContent::text("hello acp")],
|
||||||
|
metadata: Default::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let default_sessions = sm.list_sessions().await.unwrap();
|
||||||
|
assert_eq!(default_sessions.len(), 1);
|
||||||
|
assert_eq!(default_sessions[0].name, "User session");
|
||||||
|
|
||||||
|
let acp_sessions = sm
|
||||||
|
.list_sessions_by_types(&[SessionType::Acp])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(acp_sessions.len(), 1);
|
||||||
|
assert_eq!(acp_sessions[0].name, "ACP session");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_import_session_with_description_field() {
|
async fn test_import_session_with_description_field() {
|
||||||
const OLD_FORMAT_JSON: &str = r#"{
|
const OLD_FORMAT_JSON: &str = r#"{
|
||||||
@@ -1786,7 +1881,7 @@ mod tests {
|
|||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let sm = SessionManager::new(temp_dir.path().to_path_buf());
|
let sm = SessionManager::new(temp_dir.path().to_path_buf());
|
||||||
|
|
||||||
let imported = sm.import_session(OLD_FORMAT_JSON).await.unwrap();
|
let imported = sm.import_session(OLD_FORMAT_JSON, None).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(imported.name, "Old format session");
|
assert_eq!(imported.name, "Old format session");
|
||||||
assert!(imported.user_set_name);
|
assert!(imported.user_set_name);
|
||||||
@@ -1865,4 +1960,73 @@ mod tests {
|
|||||||
let reloaded = sm.get_session(&session.id, false).await.unwrap();
|
let reloaded = sm.get_session(&session.id, false).await.unwrap();
|
||||||
assert_eq!(reloaded.goose_mode, GooseMode::default());
|
assert_eq!(reloaded.goose_mode, GooseMode::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_acp_session_migration() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let db_path = temp_dir.path().join(SESSIONS_FOLDER).join(DB_NAME);
|
||||||
|
|
||||||
|
if let Some(parent) = db_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.connect_with(
|
||||||
|
SqliteConnectOptions::new()
|
||||||
|
.filename(&db_path)
|
||||||
|
.create_if_missing(true),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
SessionStorage::create_schema(&pool).await.unwrap();
|
||||||
|
|
||||||
|
// Demote the schema back to v8 to simulate a database
|
||||||
|
// that has never seen migration 9.
|
||||||
|
sqlx::query("UPDATE schema_version SET version = 8")
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO sessions (id, name, user_set_name, session_type, working_dir, extension_data, goose_mode)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
)
|
||||||
|
.bind("user_id")
|
||||||
|
.bind("User Session")
|
||||||
|
.bind(false)
|
||||||
|
.bind("user")
|
||||||
|
.bind("/tmp")
|
||||||
|
.bind("{}")
|
||||||
|
.bind("auto")
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO sessions (id, name, user_set_name, session_type, working_dir, extension_data, goose_mode)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
)
|
||||||
|
.bind("acp_id")
|
||||||
|
.bind("ACP Session")
|
||||||
|
.bind(false)
|
||||||
|
.bind("user")
|
||||||
|
.bind("/tmp")
|
||||||
|
.bind("{}")
|
||||||
|
.bind("auto")
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
pool.close().await;
|
||||||
|
|
||||||
|
let sm = SessionManager::new(temp_dir.path().to_path_buf());
|
||||||
|
sm.storage().pool().await.unwrap(); // Triggers migration
|
||||||
|
|
||||||
|
let user_session = sm.storage().get_session("user_id", false).await.unwrap();
|
||||||
|
assert_eq!(user_session.session_type, SessionType::User);
|
||||||
|
|
||||||
|
let acp_session = sm.storage().get_session("acp_id", false).await.unwrap();
|
||||||
|
assert_eq!(acp_session.session_type, SessionType::Acp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7904,7 +7904,8 @@
|
|||||||
"sub_agent",
|
"sub_agent",
|
||||||
"hidden",
|
"hidden",
|
||||||
"terminal",
|
"terminal",
|
||||||
"gateway"
|
"gateway",
|
||||||
|
"acp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SessionsQuery": {
|
"SessionsQuery": {
|
||||||
|
|||||||
@@ -1276,7 +1276,7 @@ export type SessionReplyResponse = {
|
|||||||
request_id: string;
|
request_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SessionType = 'user' | 'scheduled' | 'sub_agent' | 'hidden' | 'terminal' | 'gateway';
|
export type SessionType = 'user' | 'scheduled' | 'sub_agent' | 'hidden' | 'terminal' | 'gateway' | 'acp';
|
||||||
|
|
||||||
export type SessionsQuery = {
|
export type SessionsQuery = {
|
||||||
limit: number;
|
limit: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user