Add session metadata: creator tracking, descriptions, timestamps, and sorting

Sessions now track who created them (Human vs Agent with name),
optional descriptions, and creation timestamps. Agents can create
and update sessions via WebSocket and MCP. ListSessions supports
sorting by name, created_at, created_by, or element_count.

New MCP tools: create_session, update_session. Updated list_sessions
with sort_by/sort_order params. Tab bar shows robot icon for
agent-created sessions with hover tooltips.
This commit is contained in:
David Ibia
2026-02-09 17:20:50 +01:00
parent b140d93163
commit 43f1beea16
7 changed files with 477 additions and 25 deletions

View File

@@ -11,6 +11,36 @@ pub struct SessionIdParam {
pub session_id: Option<String>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct ListSessionsParam {
#[schemars(
description = "Sort field: 'name', 'created_at' (default), 'created_by', or 'element_count'"
)]
pub sort_by: Option<String>,
#[schemars(description = "Sort order: 'asc' (default) or 'desc'")]
pub sort_order: Option<String>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CreateSessionParam {
#[schemars(description = "Session name. If omitted, auto-generated.")]
pub name: Option<String>,
#[schemars(description = "Session description.")]
pub description: Option<String>,
#[schemars(description = "Name of the agent creating the session (identifies the creator).")]
pub created_by_name: Option<String>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct UpdateSessionParam {
#[schemars(description = "ID of the session to update.")]
pub session_id: String,
#[schemars(description = "New session name.")]
pub name: Option<String>,
#[schemars(description = "New session description.")]
pub description: Option<String>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetElementParam {
#[schemars(description = "Session ID to target. If omitted, uses the active session.")]
@@ -155,10 +185,62 @@ impl AgCanvasServer {
}
#[tool(
description = "List all open sessions/tabs in agcanvas. Returns session IDs, names, and whether they have SVG or drawing content loaded."
description = "List all open sessions/tabs in agcanvas. Returns session IDs, names, creator info, description, timestamps, and whether they have SVG or drawing content loaded. Supports sorting."
)]
async fn list_sessions(&self) -> Result<CallToolResult, McpError> {
let request = serde_json::json!({"type": "ListSessions"});
async fn list_sessions(
&self,
Parameters(params): Parameters<ListSessionsParam>,
) -> Result<CallToolResult, McpError> {
let mut request = serde_json::json!({"type": "ListSessions"});
let obj = request.as_object_mut().unwrap();
if let Some(v) = params.sort_by {
obj.insert("sort_by".into(), v.into());
}
if let Some(v) = params.sort_order {
obj.insert("sort_order".into(), v.into());
}
self.call_agcanvas(&request).await
}
#[tool(
description = "Create a new session/tab in agcanvas. The session is created by an agent. Returns the created session with its ID and metadata."
)]
async fn create_session(
&self,
Parameters(params): Parameters<CreateSessionParam>,
) -> Result<CallToolResult, McpError> {
let mut request = serde_json::json!({"type": "CreateSession"});
let obj = request.as_object_mut().unwrap();
if let Some(v) = params.name {
obj.insert("name".into(), v.into());
}
if let Some(v) = params.description {
obj.insert("description".into(), v.into());
}
if let Some(v) = params.created_by_name {
obj.insert("created_by_name".into(), v.into());
}
self.call_agcanvas(&request).await
}
#[tool(
description = "Update an existing session's name or description. Only provided fields are changed."
)]
async fn update_session(
&self,
Parameters(params): Parameters<UpdateSessionParam>,
) -> Result<CallToolResult, McpError> {
let mut request = serde_json::json!({
"type": "UpdateSession",
"session_id": params.session_id,
});
let obj = request.as_object_mut().unwrap();
if let Some(v) = params.name {
obj.insert("name".into(), v.into());
}
if let Some(v) = params.description {
obj.insert("description".into(), v.into());
}
self.call_agcanvas(&request).await
}