feat: AI based permission checks

This commit is contained in:
Georg Bauer
2026-07-30 09:47:54 +02:00
parent c101ca1380
commit 73d7c8af27
14 changed files with 413 additions and 28 deletions

View File

@@ -1225,12 +1225,34 @@ impl App {
self.agent_tools = Some((session_id, Arc::new(Mutex::new(tools))));
}
let tools = Arc::clone(&self.agent_tools.as_ref().unwrap().1);
let approval_mode = match self.permission_mode {
PermissionMode::Heuristic => crate::agent::ShellApprovalMode::Heuristic,
PermissionMode::Ai => {
let effective = crate::settings::effective_settings(
self.config.model,
&self.config.generation,
&self.config.runtime,
&models_path(),
)?;
let service = self
.generation_service
.as_ref()
.ok_or_else(|| "The model runtime is unavailable.".to_owned())?;
crate::agent::ShellApprovalMode::Ai(Box::new(crate::agent::AiRiskClassifier::new(
service.clone(),
effective.engine,
effective.turn,
transient_cache_path(),
Duration::from_secs(self.config.idle_timeout_minutes.max(1) as u64 * 60),
)))
}
};
self.tool_cards = calls
.iter()
.cloned()
.map(crate::agent::ToolCard::parsing)
.collect();
self.active_tools = Some(crate::agent::execute_async(tools, calls));
self.active_tools = Some(crate::agent::execute_async(tools, calls, approval_mode));
self.activity = Some("Parsing tool calls…".into());
Ok(())
}

View File

@@ -4,6 +4,7 @@ use std::sync::RwLock;
#[derive(Clone)]
pub(super) struct PreferenceDraft {
pub(super) model: ModelChoice,
pub(super) default_permission_mode: PermissionMode,
pub(super) legacy_mtp_enabled: bool,
pub(super) dspark_enabled: bool,
pub(super) idle_timeout_minutes: String,
@@ -63,6 +64,7 @@ impl PreferenceDraft {
let speculative = &runtime.speculative;
Self {
model: config.model,
default_permission_mode: config.default_permission_mode,
legacy_mtp_enabled: speculative.legacy_mtp_enabled,
dspark_enabled: speculative.dspark_enabled,
idle_timeout_minutes: config.idle_timeout_minutes.to_string(),
@@ -407,6 +409,7 @@ impl App {
};
let config = Config {
model: self.preference_draft.model,
default_permission_mode: self.preference_draft.default_permission_mode,
idle_timeout_minutes,
a2ui_enabled: self.preference_draft.a2ui_enabled,
endpoint: EndpointConfig {
@@ -524,6 +527,10 @@ impl App {
}
self.preference_error = None;
}
Message::PreferencePermissionModeChanged(mode) => {
self.preference_draft.default_permission_mode = mode;
self.preference_error = None;
}
Message::PreferenceLegacyMtpChanged(enabled) => {
self.preference_draft.legacy_mtp_enabled =
self.preference_draft.model.supports_dspark() && enabled;

View File

@@ -171,6 +171,7 @@ impl App {
self.drafts.entry(project_id).or_insert(title);
self.remember_project(project_id);
self.selected_session = None;
self.permission_mode = self.config.default_permission_mode;
self.conversation.clear();
self.chat_follow_tail = true;
self.context_notice = None;
@@ -211,7 +212,7 @@ impl App {
.database
.as_mut()
.ok_or_else(|| "The project database is unavailable.".to_owned())?;
let session = database.create_session(project_id, &title)?;
let session = database.create_session(project_id, &title, self.permission_mode)?;
self.drafts.remove(&project_id);
self.remember_project(project_id);
self.selected_session = Some(session.id);

View File

@@ -12,7 +12,9 @@ use super::{
ModelDownload, ModelOperation, PreferenceSection, ProjectChoice, chat_scroll_id, composer_id,
models_path, preferences_scroll_id,
};
use crate::config::{GIT_DIFF_ALGORITHMS, GIT_DIFF_LAYOUTS, GIT_DIFF_WHITESPACE_MODES};
use crate::config::{
GIT_DIFF_ALGORITHMS, GIT_DIFF_LAYOUTS, GIT_DIFF_WHITESPACE_MODES, PERMISSION_MODES,
};
use crate::database::{ProjectWithSessions, Session, SessionState};
use crate::model::{
self, DownloadPhase, MODEL_CHOICES, ManagedArtifact, ManagedArtifactState, ModelChoice,

View File

@@ -334,6 +334,13 @@ impl App {
Space::new().width(Length::Fill),
project_control,
branch_control,
pick_list(
&PERMISSION_MODES[..],
Some(self.permission_mode),
Message::PermissionModeChanged,
)
.text_size(12)
.padding([2, 6]),
icon(ICON_MODEL, 16),
text(self.config.model.to_string()).size(12),
action,

View File

@@ -126,6 +126,20 @@ impl App {
.on_toggle(Message::PreferenceA2uiChanged),
"Lets the local model build validated native charts, tables, forms and other interactive chat surfaces. Turning it off removes the A2UI catalog from the system prompt.",
),
row![
hint(
text("Default shell permission mode").size(13).width(Length::Fill),
"Heuristic uses the built-in command classifier. AI based asks the local model once before each shell command and prompts when it reports risk.",
),
pick_list(
&PERMISSION_MODES[..],
Some(self.preference_draft.default_permission_mode),
Message::PreferencePermissionModeChanged,
)
.width(180),
]
.spacing(12)
.align_y(Alignment::Center),
]
.spacing(10),
);