use crate::cm_config::AgentConfig;
use crate::cm_types::{SessionMode, parse_optional_session_mode, parse_session_mode};
use crate::cm_internal::agent_role_turn::named_agent_role_for_tool_policy;
pub fn resolve_session_mode_for_turn(
request_mode: Option<&str>,
persisted_mode: Option<&str>,
role_default_mode: Option<SessionMode>,
default_mode: SessionMode,
) -> Result<SessionMode, String> {
if let Some(m) = parse_optional_session_mode(request_mode)? {
return Ok(m);
}
if let Some(m) = parse_optional_session_mode(persisted_mode)? {
return Ok(m);
}
if let Some(m) = role_default_mode {
return Ok(m);
}
Ok(default_mode)
}
#[must_use]
pub fn role_default_session_mode_for_turn(
cfg: &AgentConfig,
persisted_active: Option<&str>,
request_agent_role: Option<&str>,
) -> Option<SessionMode> {
let id = named_agent_role_for_tool_policy(cfg, persisted_active, request_agent_role)?;
cfg.roles_prompts
.agent_roles
.get(id.as_str())
.and_then(|spec| spec.default_session_mode)
}
#[must_use]
pub fn resolve_initial_session_mode(
cfg: &AgentConfig,
request_agent_role: Option<&str>,
) -> SessionMode {
match resolve_session_mode_for_turn(
None,
None,
role_default_session_mode_for_turn(cfg, None, request_agent_role),
cfg.roles_prompts.default_session_mode,
) {
Ok(m) => m,
Err(_) => SessionMode::Act,
}
}
pub fn session_mode_from_config_str(raw: &str) -> SessionMode {
match parse_session_mode(raw) {
Ok(m) => m,
Err(e) => {
tracing::warn!(
error = %e,
raw = %raw,
"invalid default_session_mode; falling back to act"
);
SessionMode::Act
}
}
}
#[must_use]
pub fn session_mode_requires_readonly_tools(mode: SessionMode) -> bool {
mode.requires_readonly_tools()
}
#[must_use]
pub fn session_mode_appendix_relpath(mode: SessionMode) -> &'static str {
match mode {
SessionMode::Ask => "config/prompts/mode_ask.md",
SessionMode::Plan => "config/prompts/mode_plan.md",
SessionMode::Act => "config/prompts/mode_act.md",
}
}
pub fn persisted_session_mode_after_turn(
persisted_active: Option<&str>,
request_session_mode: Option<&str>,
) -> Option<String> {
let req = request_session_mode
.map(str::trim)
.filter(|s| !s.is_empty());
if req.is_some() {
return req.map(str::to_string);
}
persisted_active
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_prefers_request_over_persisted() {
let m = resolve_session_mode_for_turn(
Some("ask"),
Some("act"),
Some(SessionMode::Plan),
SessionMode::Act,
)
.unwrap();
assert_eq!(m, SessionMode::Ask);
}
#[test]
fn resolve_falls_back_to_persisted() {
let m = resolve_session_mode_for_turn(
None,
Some("plan"),
Some(SessionMode::Ask),
SessionMode::Act,
)
.unwrap();
assert_eq!(m, SessionMode::Plan);
}
#[test]
fn resolve_falls_back_to_role_default() {
let m = resolve_session_mode_for_turn(None, None, Some(SessionMode::Ask), SessionMode::Act)
.unwrap();
assert_eq!(m, SessionMode::Ask);
}
#[test]
fn resolve_falls_back_to_default() {
let m = resolve_session_mode_for_turn(None, None, None, SessionMode::Ask).unwrap();
assert_eq!(m, SessionMode::Ask);
}
#[test]
fn persist_prefers_request_when_present() {
assert_eq!(
persisted_session_mode_after_turn(Some("act"), Some("ask")).as_deref(),
Some("ask")
);
}
#[test]
fn persist_keeps_previous_when_request_absent() {
assert_eq!(
persisted_session_mode_after_turn(Some("plan"), None).as_deref(),
Some("plan")
);
assert_eq!(persisted_session_mode_after_turn(None, None), None);
}
#[test]
fn ask_mode_readonly_survives_gate_clear_ordering() {
let mut step_constraint: Option<&'static str> = None;
if session_mode_requires_readonly_tools(SessionMode::Ask) {
step_constraint = Some("ReviewReadonly");
}
assert_eq!(step_constraint, Some("ReviewReadonly"));
}
}