Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/apps/cli/src/dispatch/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2477,14 +2477,14 @@ mod tests {
.save_permission_answer(
"job-permission",
&permission.request_id,
PermissionReply::Once,
PermissionReply::Once { feedback: None },
)
.expect("first answer"));
assert!(store
.save_permission_answer(
"job-permission",
&permission.request_id,
PermissionReply::Once,
PermissionReply::Once { feedback: None },
)
.expect("retry pending answer"));
let answer = store
Expand All @@ -2499,14 +2499,14 @@ mod tests {
.save_permission_answer(
"job-permission",
&permission.request_id,
PermissionReply::Once,
PermissionReply::Once { feedback: None },
)
.expect("retry resolved answer"));
assert!(store
.save_permission_answer(
"job-permission",
&permission.request_id,
PermissionReply::Always,
PermissionReply::Always { feedback: None },
)
.is_err());
}
Expand Down
5 changes: 4 additions & 1 deletion src/apps/cli/src/modes/exec/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,10 @@ impl ExecMode {
) -> Self {
let approval_mode = match runtime.approval_policy() {
crate::runtime::approval::CliApprovalPolicy::Auto => ExecApprovalMode::Auto,
crate::runtime::approval::CliApprovalPolicy::Ask
// Non-interactive execution cannot confirm AI-judge escalations,
// so requests that reach the user prompt are rejected.
crate::runtime::approval::CliApprovalPolicy::AiAuto
| crate::runtime::approval::CliApprovalPolicy::Ask
| crate::runtime::approval::CliApprovalPolicy::DisableAuto
| crate::runtime::approval::CliApprovalPolicy::Reject => ExecApprovalMode::Reject,
};
Expand Down
14 changes: 12 additions & 2 deletions src/apps/cli/src/peer_host/commands/permission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@ use crate::peer_host::state::PeerHostState;

fn permission_reply(request: &Value) -> Result<PermissionReply, String> {
match get_string(request, "reply")?.as_str() {
"once" => Ok(PermissionReply::Once),
"always" => Ok(PermissionReply::Always),
"once" => Ok(PermissionReply::Once {
feedback: request
.get("feedback")
.and_then(Value::as_str)
.map(str::to_string),
}),
"always" => Ok(PermissionReply::Always {
feedback: request
.get("feedback")
.and_then(Value::as_str)
.map(str::to_string),
}),
"reject" => Ok(PermissionReply::Reject {
feedback: request
.get("feedback")
Expand Down
34 changes: 30 additions & 4 deletions src/apps/cli/src/runtime/approval.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use bitfun_agent_runtime::permission::PERMISSION_MODE_CONTEXT_KEY;
use bitfun_agent_runtime::sdk::{PermissionRequest, AUTO_APPROVE_ASK_CONTEXT_KEY};
use bitfun_agent_runtime::permission::{
AI_AUTO_APPROVE_ASK_CONTEXT_KEY, AUTO_APPROVE_ASK_CONTEXT_KEY, PERMISSION_MODE_CONTEXT_KEY,
};
use bitfun_agent_runtime::sdk::PermissionRequest;
use bitfun_agent_runtime::user_questions::USER_INPUT_AVAILABLE_CONTEXT_KEY;
use bitfun_runtime_ports::PermissionMode;
use serde_json::{Map, Value};
Expand All @@ -12,6 +14,9 @@ pub(crate) enum CliApprovalPolicy {
DisableAuto,
Reject,
Auto,
/// Let the fast-model permission judge decide: safe requests auto-approve,
/// critical-risk requests are rejected, the rest escalate to the user.
AiAuto,
}

/// Build invocation-scoped approval metadata consumed by the shared Runtime.
Expand All @@ -30,7 +35,7 @@ pub(crate) fn approval_metadata(approval_policy: CliApprovalPolicy) -> Map<Strin
);
}
let auto_approve_ask = match approval_policy {
CliApprovalPolicy::Ask => None,
CliApprovalPolicy::Ask | CliApprovalPolicy::AiAuto => None,
CliApprovalPolicy::DisableAuto | CliApprovalPolicy::Reject => Some(false),
CliApprovalPolicy::Auto => Some(true),
};
Expand All @@ -55,6 +60,18 @@ pub(crate) fn approval_metadata(approval_policy: CliApprovalPolicy) -> Map<Strin
),
);
}
if matches!(approval_policy, CliApprovalPolicy::AiAuto) {
// The AI mode resolves through the same single value as every other
// surface. The legacy flag is kept so older runtimes still honor it.
metadata.insert(
PERMISSION_MODE_CONTEXT_KEY.to_string(),
Value::String(PermissionMode::AiAutoApprove.as_str().to_string()),
);
metadata.insert(
AI_AUTO_APPROVE_ASK_CONTEXT_KEY.to_string(),
Value::Bool(true),
);
}
metadata
}

Expand All @@ -75,7 +92,7 @@ mod tests {
use bitfun_agent_runtime::permission::PERMISSION_MODE_CONTEXT_KEY;
use bitfun_agent_runtime::sdk::{
PermissionDelegationContext, PermissionRequest, PermissionRequestSource,
PermissionRequestSourceKind, AUTO_APPROVE_ASK_CONTEXT_KEY,
PermissionRequestSourceKind, AI_AUTO_APPROVE_ASK_CONTEXT_KEY, AUTO_APPROVE_ASK_CONTEXT_KEY,
};
use bitfun_agent_runtime::user_questions::USER_INPUT_AVAILABLE_CONTEXT_KEY;
use serde_json::Map;
Expand Down Expand Up @@ -152,6 +169,15 @@ mod tests {
approval_metadata(CliApprovalPolicy::DisableAuto).get(AUTO_APPROVE_ASK_CONTEXT_KEY),
Some(&serde_json::Value::Bool(false))
);

let ai_auto = approval_metadata(CliApprovalPolicy::AiAuto);
assert_eq!(
ai_auto.get(AI_AUTO_APPROVE_ASK_CONTEXT_KEY),
Some(&serde_json::Value::Bool(true))
);
// AI judging keeps interactive escalation possible.
assert!(ai_auto.get(USER_INPUT_AVAILABLE_CONTEXT_KEY).is_none());
assert!(ai_auto.get(AUTO_APPROVE_ASK_CONTEXT_KEY).is_none());
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/apps/cli/src/shared_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1861,7 +1861,7 @@ mod tests {
},
PermissionRequestEvent::Replied {
request_id: request.request_id,
reply: bitfun_agent_runtime::sdk::PermissionReply::Once,
reply: bitfun_agent_runtime::sdk::PermissionReply::Once { feedback: None },
source: PermissionReplySource::User,
},
];
Expand Down
6 changes: 3 additions & 3 deletions src/apps/cli/src/ui/permission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ impl PermissionPrompt {
}
KeyCode::Esc => PermissionAction::Reply(PermissionReply::Reject { feedback: None }),
KeyCode::Enter => match self.selected_option {
0 => PermissionAction::Reply(PermissionReply::Once),
1 => PermissionAction::Reply(PermissionReply::Always),
0 => PermissionAction::Reply(PermissionReply::Once { feedback: None }),
1 => PermissionAction::Reply(PermissionReply::Always { feedback: None }),
_ => {
self.editing_reject_feedback = true;
PermissionAction::None
Expand Down Expand Up @@ -320,7 +320,7 @@ mod tests {

assert_eq!(
prompt.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
PermissionAction::Reply(PermissionReply::Always)
PermissionAction::Reply(PermissionReply::Always { feedback: None })
);
}

Expand Down
8 changes: 6 additions & 2 deletions src/apps/desktop/src/api/agentic_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1270,8 +1270,12 @@ pub enum PermissionReplyKind {

fn permission_reply(request: PermissionResponseRequest) -> PermissionReply {
match request.reply {
PermissionReplyKind::Once => PermissionReply::Once,
PermissionReplyKind::Always => PermissionReply::Always,
PermissionReplyKind::Once => PermissionReply::Once {
feedback: request.feedback,
},
PermissionReplyKind::Always => PermissionReply::Always {
feedback: request.feedback,
},
PermissionReplyKind::Reject => PermissionReply::Reject {
feedback: request.feedback,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ const ZH_CN_MESSAGES: [string, string][] = [
['remote.permissions.askDescription', '高风险操作会等待你确认。'],
['remote.permissions.auto', '自动批准'],
['remote.permissions.autoDescription', '自动通过原本需要确认的操作。'],
['remote.permissions.aiAuto', 'AI 自动批准'],
['remote.permissions.aiAutoDescription', '由快速模型判断安全性:安全操作自动通过,极高风险直接拒绝,其余等待你确认。'],
['remote.permissions.fullAccess', '完全访问'],
['remote.permissions.fullAccessDescription', '允许所有工具操作,不再请求确认。'],
['remote.permissions.loading', '正在读取桌面权限设置…'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface CreateSessionOptions {
modelId?: string;
}

export type RemotePermissionMode = 'ask' | 'auto' | 'full_access';
export type RemotePermissionMode = 'ask' | 'auto' | 'ai_auto' | 'full_access';

export interface PairRequest {
public_key: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ export struct RemoteControlSettingsSheet {
RemoteI18n.t('remote.permissions.autoDescription')
)
Divider().strokeWidth(1).color(LINE).margin({ left: 18, right: 18 })
this.PermissionModeRow(
'ai_auto',
RemoteI18n.t('remote.permissions.aiAuto'),
RemoteI18n.t('remote.permissions.aiAutoDescription')
)
Divider().strokeWidth(1).color(LINE).margin({ left: 18, right: 18 })
this.PermissionModeRow(
'full_access',
RemoteI18n.t('remote.permissions.fullAccess'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn protocol_round_trips_reviewed_permission_and_user_input_operations() {
RuntimeIpcOperation::RespondPermission {
session_id: "session-1".to_string(),
request_id: "permission-1".to_string(),
reply: PermissionReply::Once,
reply: PermissionReply::Once { feedback: None },
},
RuntimeIpcOperation::SubmitUserAnswers {
request: RuntimeUserAnswersRequest {
Expand Down
16 changes: 15 additions & 1 deletion src/crates/assembly/core/src/agentic/coordination/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ use bitfun_agent_runtime::deep_review::FocusedReviewAssignment;
use bitfun_agent_runtime::output_surface::{
supports_inline_markdown_images_for_source, TOOL_CONTEXT_INLINE_MARKDOWN_IMAGE_DISPLAY_KEY,
};
use bitfun_agent_runtime::permission::{AUTO_APPROVE_ASK_CONTEXT_KEY, PERMISSION_MODE_CONTEXT_KEY};
use bitfun_agent_runtime::permission::{
AI_AUTO_APPROVE_ASK_CONTEXT_KEY, AUTO_APPROVE_ASK_CONTEXT_KEY, PERMISSION_MODE_CONTEXT_KEY,
};
use bitfun_agent_runtime::remote_file_delivery::{
needs_computer_links_for_source, remote_file_delivery_reminder,
TOOL_CONTEXT_REMOTE_FILE_DELIVERY_KEY,
Expand Down Expand Up @@ -3707,6 +3709,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
for key in [
USER_INPUT_AVAILABLE_CONTEXT_KEY,
AUTO_APPROVE_ASK_CONTEXT_KEY,
AI_AUTO_APPROVE_ASK_CONTEXT_KEY,
] {
if let Some(value) = metadata_bool(Some(&user_message_metadata), key) {
child_context.insert(key.to_string(), value.to_string());
Expand Down Expand Up @@ -5818,6 +5821,15 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
PERMISSION_MODE_CONTEXT_KEY.to_string(),
submission_permission_mode.mode.as_str().to_string(),
);
if let Some(ai_auto_approve_ask) = metadata_bool(
user_message_metadata.as_ref(),
AI_AUTO_APPROVE_ASK_CONTEXT_KEY,
) {
context_vars.insert(
AI_AUTO_APPROVE_ASK_CONTEXT_KEY.to_string(),
ai_auto_approve_ask.to_string(),
);
}
if needs_computer_links_for_source(submission_policy.trigger_source) {
context_vars.insert(
TOOL_CONTEXT_REMOTE_FILE_DELIVERY_KEY.to_string(),
Expand Down Expand Up @@ -11916,6 +11928,7 @@ impl ConversationCoordinator {
workspace,
primary_model_facts: PrimaryModelFacts::default(),
context_vars: HashMap::new(),
current_user_message: Some(command.clone()),
subagent_parent_info: None,
permission_delegation: None,
delegation_policy: DelegationPolicy::top_level(),
Expand Down Expand Up @@ -13071,6 +13084,7 @@ mod tests {
workspace: None,
primary_model_facts: Default::default(),
context_vars: HashMap::new(),
current_user_message: None,
subagent_parent_info: None,
permission_delegation: None,
delegation_policy: DelegationPolicy::top_level(),
Expand Down
18 changes: 18 additions & 0 deletions src/crates/assembly/core/src/agentic/execution/execution_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,22 @@ impl ExecutionEngine {
)
}

/// Text of the last user-authored message in the history, used as the
/// stable session-context task summary for permission judging. System
/// reminders and tool results are ignored.
fn last_user_message_text(messages: &[Message]) -> Option<String> {
messages.iter().rev().find_map(|msg| {
if msg.role != MessageRole::User {
return None;
}
match &msg.content {
MessageContent::Text(text) => Some(text.clone()),
MessageContent::Multimodal { text, .. } => Some(text.clone()),
_ => None,
}
})
}

/// True if this message would contribute at least one image to the model (before pruning).
fn message_bears_images(msg: &Message) -> bool {
if Self::skip_message_for_model_send(msg) {
Expand Down Expand Up @@ -1653,6 +1669,7 @@ impl ExecutionEngine {
primary_model_facts: input.primary_model_facts.clone(),
agent_type: input.agent_type,
context_vars: input.execution_context_vars.clone(),
current_user_message: Self::last_user_message_text(input.messages),
permission_constraints: input.permission_constraints,
permission_runtime_ceiling: input.context.permission_runtime_ceiling.clone(),
delegation_policy: input.context.delegation_policy,
Expand Down Expand Up @@ -3690,6 +3707,7 @@ impl ExecutionEngine {
primary_model_facts: primary_model_facts.clone(),
agent_type: agent_type.clone(),
context_vars: round_context_vars,
current_user_message: Self::last_user_message_text(&messages),
permission_constraints: tool_policy.permission_constraints.clone(),
permission_runtime_ceiling: context.permission_runtime_ceiling.clone(),
delegation_policy: context.delegation_policy,
Expand Down
1 change: 1 addition & 0 deletions src/crates/assembly/core/src/agentic/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub(crate) mod conditional_instructions;
pub mod edit_constraint_guard;
pub mod execution_engine;
pub(crate) mod model_exchange_trace;
pub mod permission_ai_judge;
pub mod round_executor;
pub mod stream_processor;
pub mod types;
Expand Down
Loading