fix: error when users try to start a session on a graph agent

This commit is contained in:
2026-05-18 12:55:17 -06:00
parent 503c9b4699
commit 35e1b14843
2 changed files with 107 additions and 7 deletions
+94 -1
View File
@@ -37,6 +37,7 @@ use std::fs::{File, OpenOptions, read_dir, read_to_string, remove_dir_all, remov
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use crate::graph;
pub struct AutoContinueConfig { pub struct AutoContinueConfig {
pub enabled: bool, pub enabled: bool,
@@ -1007,6 +1008,7 @@ impl RequestContext {
}) })
.map(|v| v.name.to_string()) .map(|v| v.name.to_string())
.collect(); .collect();
if let Some(agent) = &self.agent { if let Some(agent) = &self.agent {
declaration_names.extend( declaration_names.extend(
agent agent
@@ -1031,6 +1033,7 @@ impl RequestContext {
if item.is_empty() { if item.is_empty() {
continue; continue;
} }
if let Some(values) = app.mapping_tools.get(item) { if let Some(values) = app.mapping_tools.get(item) {
tool_names.extend( tool_names.extend(
values values
@@ -1089,9 +1092,11 @@ impl RequestContext {
&& !v.name.starts_with(MCP_DESCRIBE_META_FUNCTION_NAME_PREFIX) && !v.name.starts_with(MCP_DESCRIBE_META_FUNCTION_NAME_PREFIX)
}) })
.collect(); .collect();
if let Some(ref tool_names) = role_filter { if let Some(ref tool_names) = role_filter {
agent_functions.retain(|v| tool_names.contains(&v.name)); agent_functions.retain(|v| tool_names.contains(&v.name));
} }
let tool_names: HashSet<String> = agent_functions let tool_names: HashSet<String> = agent_functions
.iter() .iter()
.filter_map(|v| { .filter_map(|v| {
@@ -1157,6 +1162,7 @@ impl RequestContext {
if item.is_empty() { if item.is_empty() {
continue; continue;
} }
let item_invoke_name = let item_invoke_name =
format!("{}_{item}", MCP_INVOKE_META_FUNCTION_NAME_PREFIX); format!("{}_{item}", MCP_INVOKE_META_FUNCTION_NAME_PREFIX);
let item_search_name = let item_search_name =
@@ -1226,9 +1232,11 @@ impl RequestContext {
|| v.name.starts_with(MCP_DESCRIBE_META_FUNCTION_NAME_PREFIX) || v.name.starts_with(MCP_DESCRIBE_META_FUNCTION_NAME_PREFIX)
}) })
.collect(); .collect();
if let Some(ref server_names) = role_filter { if let Some(ref server_names) = role_filter {
agent_functions.retain(|v| server_names.contains(&v.name)); agent_functions.retain(|v| server_names.contains(&v.name));
} }
let tool_names: HashSet<String> = agent_functions let tool_names: HashSet<String> = agent_functions
.iter() .iter()
.filter_map(|v| { .filter_map(|v| {
@@ -2168,6 +2176,14 @@ impl RequestContext {
) )
.await?; .await?;
let is_graph_agent = graph::agent_has_graph(agent_name);
if is_graph_agent && session_name.is_some() {
bail!(
"Graph-based agent '{agent_name}' does not support sessions. \
The graph manages its own state; re-run without a session."
);
}
let mcp_servers = if app.mcp_server_support { let mcp_servers = if app.mcp_server_support {
(!agent.mcp_server_names().is_empty()).then(|| agent.mcp_server_names().join(",")) (!agent.mcp_server_names().is_empty()).then(|| agent.mcp_server_names().join(","))
} else { } else {
@@ -2189,8 +2205,10 @@ impl RequestContext {
); );
} }
// Graph agents manage their own state; never engage a session,
// not even an inherited app-level `agent_session` default.
let session_name = session_name.map(|v| v.to_string()).or_else(|| { let session_name = session_name.map(|v| v.to_string()).or_else(|| {
if self.macro_flag { if self.macro_flag || is_graph_agent {
None None
} else { } else {
agent.agent_session().map(|v| v.to_string()) agent.agent_session().map(|v| v.to_string())
@@ -3583,4 +3601,79 @@ mod tests {
"Agent should not be set when session check fails" "Agent should not be set when session check fails"
); );
} }
#[test]
#[serial]
fn use_agent_errors_when_graph_agent_given_explicit_session() {
let _guard = TestConfigDirGuard::new();
let mut ctx = create_test_ctx();
let app = ctx.app.config.clone();
let agent_name = format!(
"test_graph_agent_{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let agent_dir = paths::agent_data_dir(&agent_name);
create_dir_all(&agent_dir).unwrap();
write(
agent_dir.join("graph.yaml"),
format!(
"name: {agent_name}\nversion: \"1.0\"\nstart: done\nnodes:\n done:\n type: end\n output: ok\n"
),
)
.unwrap();
let abort = utils::create_abort_signal();
let result = run_async(ctx.use_agent(&app, &agent_name, Some("test_session"), abort));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("does not support sessions")
);
assert!(
ctx.agent.is_none(),
"Agent should not be set when the graph-agent session guard fails"
);
}
#[test]
#[serial]
fn use_agent_skips_inherited_session_for_graph_agent() {
let _guard = TestConfigDirGuard::new();
let mut ctx = create_test_ctx();
ctx.update_app_config(|app| app.agent_session = Some("inherited".to_string()));
let app = ctx.app.config.clone();
let agent_name = format!(
"test_graph_agent_{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let agent_dir = paths::agent_data_dir(&agent_name);
create_dir_all(&agent_dir).unwrap();
write(
agent_dir.join("graph.yaml"),
format!(
"name: {agent_name}\nversion: \"1.0\"\nstart: done\nnodes:\n done:\n type: end\n output: ok\n"
),
)
.unwrap();
let abort = utils::create_abort_signal();
run_async(ctx.use_agent(&app, &agent_name, None, abort)).unwrap();
assert!(ctx.agent.is_some(), "Graph agent should load successfully");
assert!(
ctx.session.is_none(),
"Graph agent must not engage a session, not even an inherited default"
);
}
} }
+13 -6
View File
@@ -17,7 +17,7 @@ use crate::utils::{
AbortSignal, abortable_run_with_spinner, create_abort_signal, dimmed_text, set_text, temp_file, AbortSignal, abortable_run_with_spinner, create_abort_signal, dimmed_text, set_text, temp_file,
}; };
use crate::resolve_oauth_client; use crate::{graph, resolve_oauth_client};
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use crossterm::cursor::SetCursorStyle; use crossterm::cursor::SetCursorStyle;
use fancy_regex::Regex; use fancy_regex::Regex;
@@ -32,6 +32,7 @@ use reedline::{
use reedline::{MenuBuilder, Signal}; use reedline::{MenuBuilder, Signal};
use std::sync::LazyLock; use std::sync::LazyLock;
use std::{env, process, sync::Arc}; use std::{env, process, sync::Arc};
use log::warn;
const MENU_NAME: &str = "completion_menu"; const MENU_NAME: &str = "completion_menu";
@@ -497,6 +498,12 @@ pub async fn run_repl_command(
), ),
}, },
".session" => { ".session" => {
if let Some(name) = graph::active_agent_graph_name(ctx) {
bail!(
"Graph-based agent '{name}' does not support sessions. \
The graph manages its own state."
);
}
let app = Arc::clone(&ctx.app.config); let app = Arc::clone(&ctx.app.config);
ctx.use_session(app.as_ref(), args, abort_signal.clone()) ctx.use_session(app.as_ref(), args, abort_signal.clone())
.await?; .await?;
@@ -508,7 +515,7 @@ pub async fn run_repl_command(
}; };
eprintln!("\n📢 {}", color.italic().paint("Autonaming the session."),); eprintln!("\n📢 {}", color.italic().paint("Autonaming the session."),);
if let Err(err) = ctx.autoname_session(app.as_ref()).await { if let Err(err) = ctx.autoname_session(app.as_ref()).await {
log::warn!("Failed to autonaming the session: {err}"); warn!("Failed to autonaming the session: {err}");
} }
if let Some(session) = ctx.session.as_mut() { if let Some(session) = ctx.session.as_mut() {
session.set_autonaming(false); session.set_autonaming(false);
@@ -860,10 +867,10 @@ async fn ask(
let app = Arc::clone(&ctx.app.config); let app = Arc::clone(&ctx.app.config);
if crate::graph::active_agent_graph_name(ctx).is_some() { if graph::active_agent_graph_name(ctx).is_some() {
ctx.before_chat_completion(&input)?; ctx.before_chat_completion(&input)?;
let output = let output =
crate::graph::run_active_agent_graph(ctx, &input.text(), abort_signal.clone()).await?; graph::run_active_agent_graph(ctx, &input.text(), abort_signal.clone()).await?;
app.print_markdown(&output)?; app.print_markdown(&output)?;
ctx.after_chat_completion(app.as_ref(), &input, &output, &[])?; ctx.after_chat_completion(app.as_ref(), &input, &output, &[])?;
return Ok(()); return Ok(());
@@ -937,7 +944,7 @@ async fn ask(
}; };
eprintln!("\n📢 {}", color.italic().paint("Autonaming the session."),); eprintln!("\n📢 {}", color.italic().paint("Autonaming the session."),);
if let Err(err) = ctx.autoname_session(app.as_ref()).await { if let Err(err) = ctx.autoname_session(app.as_ref()).await {
log::warn!("Failed to autonaming the session: {err}"); warn!("Failed to autonaming the session: {err}");
} }
if let Some(session) = ctx.session.as_mut() { if let Some(session) = ctx.session.as_mut() {
session.set_autonaming(false); session.set_autonaming(false);
@@ -964,7 +971,7 @@ async fn ask(
eprintln!("\n📢 {}", color.italic().paint("Compressing the session."),); eprintln!("\n📢 {}", color.italic().paint("Compressing the session."),);
if let Err(err) = ctx.compress_session().await { if let Err(err) = ctx.compress_session().await {
log::warn!("Failed to compress the session: {err}"); warn!("Failed to compress the session: {err}");
} }
if let Some(session) = ctx.session.as_mut() { if let Some(session) = ctx.session.as_mut() {
session.set_compressing(false); session.set_compressing(false);