use std::path::Path;
use std::sync::Arc;
use argonauts_core::app::{MissingCli, SettingRow, ViewData};
use argonauts_core::input::{ROLE_SANDBOX_KEYS, SlashCommand};
use argonauts_core::model_picker::{EffortSlider, EffortStop};
use argonauts_core::names::RoleName;
use argonauts_core::settings::SettingScope;
use argonauts_core::views::LearningArea;
use argonauts_policy::{SettingsReloader, approval_policy};
use super::rows::{self, role_named};
use super::{args, labels, models};
use crate::builtin_adapters;
use crate::commands::adapters_here;
use crate::controller::Ctx;
use crate::host::services::installed;
pub(crate) fn picker(ctx: &Ctx, command: &SlashCommand) -> Vec<ViewData> {
let settings = &ctx.settings;
match command {
SlashCommand::Model { role: None, .. } => {
vec![view(
rows::rows(settings, Some("model"), None),
"model",
None,
)]
}
SlashCommand::Effort { role, .. } => {
if let Some(slider) = effort_slider(ctx, *role) {
return vec![ViewData::Effort { slider }];
}
let section = role.map(|role| format!("roles.{}.effort", role.as_str()));
vec![view(
effort_rows(ctx, section.as_deref()),
"effort",
section,
)]
}
SlashCommand::Workflow { .. } => vec![view(workflow_rows(ctx), "workflow", None)],
SlashCommand::Agents { .. } => {
vec![ViewData::Settings {
rows: rows::rows(settings, Some("agents"), None),
command: Some("agents".to_owned()),
section: None,
not_installed: not_installed(ctx),
}]
}
SlashCommand::Approvals { key, .. } => {
let section = key.as_deref().map(|key| args::under("approvals", key));
vec![
approvals(ctx),
view(
rows::rows(settings, Some("approvals"), section.as_deref()),
"approvals",
section,
),
]
}
SlashCommand::Limits { key, .. } => {
let section = key.as_deref().map(|key| args::under("limits", key));
let found = rows::rows(settings, Some("limits"), section.as_deref());
vec![view(found, "limits", section)]
}
SlashCommand::Fallback { role, key, .. } => {
let base = role.map(args::fallback_section);
let section = match (&base, key) {
(Some(base), Some(key)) => Some(args::under(base, key)),
(Some(base), None) => Some(base.clone()),
(None, Some(key)) => Some(args::under("fallback", key)),
(None, None) => None,
};
let found = rows::rows(settings, Some("fallback"), section.as_deref());
vec![view(found, "fallback", section)]
}
SlashCommand::Permissions {
role: None, key, ..
} => {
let found = rows::rows(settings, Some("permissions"), key.as_deref());
vec![view(found, "permissions", key.clone())]
}
SlashCommand::Permissions {
role: Some(role), ..
} => {
let section = format!("roles.{}.sandbox", role.as_str());
let found = rows::rows(settings, Some("permissions"), None)
.into_iter()
.filter(|row| {
rows::in_section(&row.key, §ion)
|| ROLE_SANDBOX_KEYS
.iter()
.any(|leaf| row.key == format!("sandbox.{leaf}"))
})
.collect();
vec![view(found, "permissions", Some(section))]
}
SlashCommand::Narrator { .. } => {
vec![view(
rows::rows(settings, Some("narrator"), None),
"narrator",
None,
)]
}
SlashCommand::Verbosity { .. } => {
vec![view(
rows::rows(settings, Some("verbosity"), None),
"verbosity",
None,
)]
}
SlashCommand::Theme { .. } => {
vec![view(
rows::rows(settings, Some("theme"), None),
"theme",
None,
)]
}
SlashCommand::Config { key, .. } => {
let section = key.as_deref().filter(|key| *key != "all");
let mut found = rows::rows(settings, Some("config"), section);
rows::with_picks(&mut found, &ctx.env.paths, &ctx.names.names());
vec![view(found, "config", section.map(str::to_owned))]
}
SlashCommand::Records { agent } => learning(ctx, LearningArea::Records, agent.clone()),
SlashCommand::LearnedWorkflows { name } => {
learning(ctx, LearningArea::LearnedWorkflows, name.clone())
}
SlashCommand::Skills => learning(ctx, LearningArea::Skills, None),
SlashCommand::Memory => learning(ctx, LearningArea::Memory, None),
_ => Vec::new(),
}
}
fn learning(ctx: &Ctx, area: LearningArea, name: Option<String>) -> Vec<ViewData> {
let mut settings_rows: Vec<SettingRow> = Vec::new();
for section in area.settings() {
for row in rows::rows(&ctx.settings, Some("config"), Some(section)) {
if !settings_rows.iter().any(|kept| kept.key == row.key) {
settings_rows.push(row);
}
}
}
let store = crate::learning::store(&ctx.env);
let focus = crate::learning::view::Focus { name };
match crate::learning::view::build(area, &store, ctx.settings(), &focus, settings_rows) {
Ok(view) => vec![ViewData::Learning {
view: Box::new(view),
}],
Err(err) => {
ctx.notice(
argonauts_core::NoteLevel::Warn,
format!("/{} couldn't read its files: {err:#}", area.command()),
);
Vec::new()
}
}
}
pub(crate) fn approvals(ctx: &Ctx) -> ViewData {
let shared = ctx.settings.shared();
let reload: SettingsReloader = Arc::new(move || Some((*shared).clone()));
let policy = approval_policy(
ctx.settings.shared(),
reload,
&ctx.env.state_dir,
Arc::clone(&ctx.services.workers),
Arc::clone(&ctx.services.steps),
);
let mut view = policy.view();
view.warnings = ctx
.settings
.resolved()
.warnings
.iter()
.filter(|warning| warning.contains("approvals."))
.map(|warning| warning.text.clone())
.collect();
ViewData::Approvals { view }
}
fn view(rows: Vec<SettingRow>, command: &str, section: Option<String>) -> ViewData {
ViewData::Settings {
rows,
command: Some(command.to_owned()),
section,
not_installed: Vec::new(),
}
}
fn not_installed(ctx: &Ctx) -> Vec<MissingCli> {
let resolved = ctx.settings.resolved();
let adapters = adapters_here(Some(&ctx.env), resolved).unwrap_or_else(|err| {
tracing::warn!("listing the agent CLIs for /agents: {err:#}");
builtin_adapters()
});
adapters
.iter()
.filter(|adapter| !installed(&resolved.settings, adapter))
.map(|adapter| MissingCli {
id: adapter.id().to_owned(),
install: adapter.setup_hints().install,
})
.collect()
}
fn effort_slider(ctx: &Ctx, role: Option<RoleName>) -> Option<EffortSlider> {
let role = role.filter(|role| *role != RoleName::Main);
let (worker, levels) = models::efforts(ctx, role);
let worker = worker?;
let registry = &ctx.services.registry;
let catalog = registry.catalog();
let entry = registry.get(&worker);
let label = catalog.label(&worker);
let name = entry
.as_ref()
.and_then(|entry| entry.name.clone())
.unwrap_or(label.name);
let default = entry
.and_then(|entry| entry.default_effort)
.or_else(|| catalog.entry(&worker)?.default_effort.clone())
.or_else(|| models::default_effort(ctx, &worker));
let stops = levels
.into_iter()
.map(|level| EffortStop {
description: catalog.effort_note(&worker, &level).unwrap_or_default(),
level,
})
.collect();
let current = labels::effort_set(ctx.settings(), role, &worker.agent);
let slider = EffortSlider::new(
role,
worker.clone(),
name.clone(),
stops,
default.as_deref(),
current.as_deref(),
);
match role {
None => {
let slider = slider.unwrap_or_else(|| EffortSlider::without_levels(None, worker, name));
Some(slider.with_ultracode(ctx.settings().ultracode))
}
Some(_) => slider,
}
}
fn effort_rows(ctx: &Ctx, section: Option<&str>) -> Vec<SettingRow> {
let mut found = rows::rows(&ctx.settings, Some("effort"), section);
for row in &mut found {
let role = row
.key
.split('.')
.nth(1)
.and_then(role_named)
.filter(|role| *role != RoleName::Main);
let (_, levels) = models::efforts(ctx, role);
row.allowed = levels.iter().map(|level| args::quoted(level)).collect();
}
found
}
fn workflow_rows(ctx: &Ctx) -> Vec<SettingRow> {
let current = ctx.settings().workflow.clone();
let layer = ctx.settings.layer("workflow");
let workflows = args::workflow_list(ctx);
let allowed: Vec<String> = workflows
.iter()
.map(|chosen| args::quoted(&chosen.name))
.collect();
workflows
.into_iter()
.map(|chosen| {
let description = match (chosen.description.is_empty(), chosen.source.as_str()) {
(_, "built-in") => chosen.description,
(true, source) => format!("From {}", ctx.env.shown(Path::new(source))),
(false, source) => format!(
"{} (from {})",
chosen.description,
ctx.env.shown(Path::new(source))
),
};
SettingRow {
key: "workflow".to_owned(),
value: args::quoted(&chosen.name),
layer: if chosen.name == current {
layer.clone()
} else {
"built-in".to_owned()
},
workflow_from: None,
default_scope: SettingScope::Project,
safety: false,
allowed: allowed.clone(),
description,
picked: None,
}
})
.collect()
}