Merge b2c2770a78 into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-04-20 18:54:36 -07:00
committed by GitHub
26 changed files with 251 additions and 17 deletions
@@ -265,6 +265,10 @@ impl GuardianReviewSessionManager {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "review session selection and trunk spawning must stay serialized"
)]
pub(crate) async fn run_review(
&self,
params: GuardianReviewSessionParams,
+12
View File
@@ -494,6 +494,10 @@ async fn execute_mcp_tool_call(
)
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP sandbox metadata reads through the session-owned manager guard"
)]
async fn augment_mcp_tool_request_meta_with_sandbox_state(
sess: &Session,
turn_context: &TurnContext,
@@ -1068,6 +1072,10 @@ fn mcp_tool_approval_callsite_mode(
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP approval metadata reads through the session-owned manager guard"
)]
pub(crate) async fn lookup_mcp_tool_metadata(
sess: &Session,
turn_context: &TurnContext,
@@ -1150,6 +1158,10 @@ fn get_mcp_app_resource_uri(
})
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP app metadata reads through the session-owned manager guard"
)]
async fn lookup_mcp_app_usage_metadata(
sess: &Session,
server: &str,
+4
View File
@@ -470,6 +470,10 @@ pub async fn reload_user_config(sess: &Arc<Session>) {
sess.reload_user_config_layer().await;
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP tool listing reads through the session-owned manager guard"
)]
pub async fn list_mcp_tools(sess: &Session, config: &Arc<Config>, sub_id: String) {
let mcp_connection_manager = sess.services.mcp_connection_manager.read().await;
let auth = sess.services.auth_manager.auth().await;
+28
View File
@@ -1,6 +1,10 @@
use super::*;
impl Session {
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn request_mcp_server_elicitation(
&self,
turn_context: &TurnContext,
@@ -80,6 +84,10 @@ impl Session {
rx_response.await.ok()
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and manager fallback must stay serialized"
)]
pub async fn resolve_elicitation(
&self,
server_name: String,
@@ -111,6 +119,10 @@ impl Session {
.await
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP resource calls are serialized through the session-owned manager guard"
)]
pub async fn list_resources(
&self,
server: &str,
@@ -124,6 +136,10 @@ impl Session {
.await
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP resource calls are serialized through the session-owned manager guard"
)]
pub async fn list_resource_templates(
&self,
server: &str,
@@ -137,6 +153,10 @@ impl Session {
.await
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP resource calls are serialized through the session-owned manager guard"
)]
pub async fn read_resource(
&self,
server: &str,
@@ -150,6 +170,10 @@ impl Session {
.await
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP tool calls are serialized through the session-owned manager guard"
)]
pub async fn call_tool(
&self,
server: &str,
@@ -165,6 +189,10 @@ impl Session {
.await
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP tool metadata reads through the session-owned manager guard"
)]
pub(crate) async fn resolve_mcp_tool_info(&self, tool_name: &ToolName) -> Option<ToolInfo> {
self.services
.mcp_connection_manager
+60
View File
@@ -1785,6 +1785,10 @@ impl Session {
/// be used to derive the available decisions via
/// [ExecApprovalRequestEvent::default_available_decisions].
#[allow(clippy::too_many_arguments)]
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn request_command_approval(
&self,
turn_context: &TurnContext,
@@ -1856,6 +1860,10 @@ impl Session {
rx_approve.await.unwrap_or(ReviewDecision::Abort)
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn request_patch_approval(
&self,
turn_context: &TurnContext,
@@ -1892,6 +1900,10 @@ impl Session {
rx_approve
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn request_permissions(
&self,
turn_context: &TurnContext,
@@ -1947,6 +1959,10 @@ impl Session {
rx_response.await.ok()
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn request_user_input(
&self,
turn_context: &TurnContext,
@@ -1979,6 +1995,10 @@ impl Session {
rx_response.await.ok()
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn notify_user_input_response(
&self,
sub_id: &str,
@@ -2004,6 +2024,10 @@ impl Session {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn notify_request_permissions_response(
&self,
call_id: &str,
@@ -2045,6 +2069,10 @@ impl Session {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn reads must stay consistent with the matching turn state"
)]
pub(crate) async fn granted_turn_permissions(&self) -> Option<PermissionProfile> {
let active = self.active_turn.lock().await;
let active = active.as_ref()?;
@@ -2057,6 +2085,10 @@ impl Session {
state.granted_permissions()
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn notify_dynamic_tool_response(&self, call_id: &str, response: DynamicToolResponse) {
let entry = {
let mut active = self.active_turn.lock().await;
@@ -2078,6 +2110,10 @@ impl Session {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn notify_approval(&self, approval_id: &str, decision: ReviewDecision) {
let entry = {
let mut active = self.active_turn.lock().await;
@@ -2238,6 +2274,10 @@ impl Session {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP app context rendering reads through the session-owned manager guard"
)]
pub(crate) async fn build_initial_context(
&self,
turn_context: &TurnContext,
@@ -2700,6 +2740,10 @@ impl Session {
/// Inject additional user input into the currently active turn.
///
/// Returns the active turn id when accepted.
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn steer_input(
&self,
input: Vec<UserInput>,
@@ -2759,6 +2803,10 @@ impl Session {
}
/// Returns the input if there was no task running to inject into.
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn inject_response_items(
&self,
input: Vec<ResponseInputItem>,
@@ -2832,6 +2880,10 @@ impl Session {
self.mailbox_rx.lock().await.has_pending_trigger_turn()
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn prepend_pending_input(&self, input: Vec<ResponseInputItem>) -> Result<(), ()> {
let mut active = self.active_turn.lock().await;
match active.as_mut() {
@@ -2844,6 +2896,10 @@ impl Session {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub async fn get_pending_input(&self) -> Vec<ResponseInputItem> {
let (pending_input, accepts_mailbox_delivery) = {
let mut active = self.active_turn.lock().await;
@@ -2899,6 +2955,10 @@ impl Session {
!self.idle_pending_input.lock().await.is_empty()
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state reads must remain atomic"
)]
pub async fn has_pending_input(&self) -> bool {
let (has_turn_pending_input, accepts_mailbox_delivery) = {
let active = self.active_turn.lock().await;
+4
View File
@@ -232,6 +232,10 @@ pub(crate) struct AppServerClientMetadata {
impl Session {
#[instrument(name = "session_init", level = "info", skip_all)]
#[allow(clippy::too_many_arguments)]
#[expect(
clippy::await_holding_invalid_type,
reason = "session initialization must serialize access through session-owned manager guards"
)]
pub(crate) async fn new(
mut session_configuration: SessionConfiguration,
config: Arc<Config>,
+4
View File
@@ -6332,6 +6332,10 @@ async fn abort_review_task_emits_exited_then_aborted_and_records_history() {
}
#[tokio::test]
#[expect(
clippy::await_holding_invalid_type,
reason = "test builds a router from session-owned MCP manager state"
)]
async fn fatal_tool_error_stops_turn_and_reports_error() {
let (session, turn_context, _rx) = make_session_and_context_with_rx().await;
let tools = {
@@ -298,6 +298,10 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
#[tokio::test]
#[cfg(unix)]
#[expect(
clippy::await_holding_invalid_type,
reason = "test mutates active turn state directly to seed granted permissions"
)]
async fn shell_handler_allows_sticky_turn_permissions_without_inline_request_permissions_feature() {
let (mut session, turn_context_raw) = make_session_and_context().await;
session
+8
View File
@@ -127,6 +127,10 @@ use tracing::warn;
/// - If the model sends only an assistant message, we record it in the
/// conversation history and consider the turn complete.
///
#[expect(
clippy::await_holding_invalid_type,
reason = "turn execution must keep active-turn state transitions atomic"
)]
pub(crate) async fn run_turn(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
@@ -1143,6 +1147,10 @@ async fn run_sampling_request(
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "tool router construction reads through the session-owned manager guard"
)]
pub(crate) async fn built_tools(
sess: &Session,
turn_context: &TurnContext,
+4
View File
@@ -259,6 +259,10 @@ pub(super) async fn build_enabled_tools(
collect_code_mode_tool_definitions(&specs)
}
#[expect(
clippy::await_holding_invalid_type,
reason = "nested tool router construction reads through the session-owned manager guard"
)]
async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
let nested_tools_config = exec.turn.tools_config.for_code_mode_nested_tools();
let listed_mcp_tools = exec
@@ -71,6 +71,10 @@ impl ToolHandler for DynamicToolHandler {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and dynamic tool response registration must remain atomic"
)]
async fn request_dynamic_tool(
session: &Session,
turn_context: &TurnContext,
@@ -240,6 +240,10 @@ impl ToolHandler for McpResourceHandler {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP resource listing reads through the session-owned manager guard"
)]
async fn handle_list_resources(
session: Arc<Session>,
turn: Arc<TurnContext>,
@@ -344,6 +348,10 @@ async fn handle_list_resources(
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "MCP resource template listing reads through the session-owned manager guard"
)]
async fn handle_list_resource_templates(
session: Arc<Session>,
turn: Arc<TurnContext>,
@@ -34,6 +34,10 @@ impl ToolHandler for ToolSuggestHandler {
ToolKind::Function
}
#[expect(
clippy::await_holding_invalid_type,
reason = "tool suggestion discovery reads through the session-owned manager guard"
)]
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
let ToolInvocation {
payload,
@@ -193,6 +197,10 @@ async fn verify_tool_suggestion_completed(
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "connector cache refresh reads through the session-owned manager guard"
)]
async fn refresh_missing_suggested_connectors(
session: &crate::session::session::Session,
turn: &crate::session::turn_context::TurnContext,
+16
View File
@@ -836,6 +836,10 @@ impl JsReplManager {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "js_repl kernel initialization must be serialized with kernel state"
)]
pub async fn execute(
&self,
session: Arc<Session>,
@@ -1162,6 +1166,10 @@ impl JsReplManager {
Ok(kernel_path)
}
#[expect(
clippy::await_holding_invalid_type,
reason = "js_repl stdin writes must be serialized per kernel"
)]
async fn write_message(
stdin: &Arc<Mutex<ChildStdin>>,
msg: &HostToKernel,
@@ -1209,6 +1217,10 @@ impl JsReplManager {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "js_repl child shutdown must serialize process inspection and termination"
)]
async fn kill_kernel_child(child: &Arc<Mutex<Child>>, reason: &'static str) {
let mut guard = child.lock().await;
let pid = guard.id();
@@ -1532,6 +1544,10 @@ impl JsReplManager {
}
}
#[expect(
clippy::await_holding_invalid_type,
reason = "nested js_repl tool routing reads through the session-owned manager guard"
)]
async fn run_tool_request(exec: ExecContext, req: RunToolRequest) -> RunToolResult {
if is_js_repl_internal_tool(&req.tool_name) {
let error = "js_repl cannot invoke itself".to_string();
+4
View File
@@ -239,6 +239,10 @@ impl ToolRegistry {
// }
// }
#[expect(
clippy::await_holding_invalid_type,
reason = "tool dispatch must keep active-turn accounting atomic"
)]
pub(crate) async fn dispatch_any(
&self,
invocation: ToolInvocation,
+12
View File
@@ -14,6 +14,10 @@ use super::ToolRouter;
use super::ToolRouterParams;
#[tokio::test]
#[expect(
clippy::await_holding_invalid_type,
reason = "test builds a router from session-owned MCP manager state"
)]
async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> {
let (session, mut turn) = make_session_and_context().await;
turn.tools_config.js_repl_tools_only = true;
@@ -68,6 +72,10 @@ async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> {
}
#[tokio::test]
#[expect(
clippy::await_holding_invalid_type,
reason = "test builds a router from session-owned MCP manager state"
)]
async fn js_repl_tools_only_allows_js_repl_source_calls() -> anyhow::Result<()> {
let (session, mut turn) = make_session_and_context().await;
turn.tools_config.js_repl_tools_only = true;
@@ -171,6 +179,10 @@ async fn js_repl_tools_only_blocks_namespaced_js_repl_tool() -> anyhow::Result<(
}
#[tokio::test]
#[expect(
clippy::await_holding_invalid_type,
reason = "test builds a router from session-owned MCP manager state"
)]
async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow::Result<()> {
let (session, turn) = make_session_and_context().await;
let mcp_tools = session