1use std::collections::HashMap;
7use std::error::Error;
8use std::path::PathBuf;
9use std::sync::Arc;
10use std::sync::OnceLock;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::time::Instant;
13use tokio::sync::broadcast;
14
15use claude_code_agent_sdk::types::config::PermissionMode as SdkPermissionMode;
16use claude_code_agent_sdk::types::mcp::McpSdkServerConfig;
17use claude_code_agent_sdk::{
18 ClaudeAgentOptions, ClaudeClient, HookEvent, HookMatcher, McpServerConfig, McpServers,
19 SystemPrompt, SystemPromptPreset,
20};
21use sacp::JrConnectionCx;
22use sacp::link::AgentToClient;
23use sacp::schema::McpServer;
24use tokio::sync::RwLock;
25use tracing::instrument;
26
27use crate::converter::NotificationConverter;
28use crate::hooks::{HookCallbackRegistry, create_post_tool_use_hook, create_pre_tool_use_hook};
29use crate::mcp::AcpMcpServer;
30use crate::permissions::create_can_use_tool_callback;
31use crate::settings::{PermissionChecker, SettingsManager};
32use crate::terminal::TerminalClient;
33use crate::types::{AgentConfig, AgentError, NewSessionMeta, Result};
34
35use super::BackgroundProcessManager;
36use super::permission::{PermissionHandler, PermissionMode};
37use super::usage::UsageTracker;
38
39fn get_acp_replacement_tools() -> Vec<&'static str> {
47 vec![
48 "Bash",
50 "BashOutput",
51 "KillShell",
52 "Read",
54 "Write",
55 "Edit",
56 ]
57}
58
59pub struct Session {
64 pub session_id: String,
66 pub cwd: PathBuf,
68 client: RwLock<ClaudeClient>,
70 cancelled: Arc<AtomicBool>,
72 permission: Arc<RwLock<PermissionHandler>>,
74 usage_tracker: UsageTracker,
76 converter: NotificationConverter,
78 connected: AtomicBool,
80 hook_callback_registry: Arc<HookCallbackRegistry>,
82 permission_checker: Arc<RwLock<PermissionChecker>>,
84 permission_mode: Arc<RwLock<PermissionMode>>,
86 current_model: OnceLock<String>,
88 acp_mcp_server: Arc<AcpMcpServer>,
90 background_processes: Arc<BackgroundProcessManager>,
92 external_mcp_servers: OnceLock<Vec<McpServer>>,
95 external_mcp_connected: AtomicBool,
97 connection_cx_lock: Arc<OnceLock<JrConnectionCx<AgentToClient>>>,
100 cancel_sender: broadcast::Sender<()>,
102}
103
104impl Session {
105 #[instrument(
117 name = "session_create",
118 skip(config, meta),
119 fields(
120 session_id = %session_id,
121 cwd = ?cwd,
122 has_meta = meta.is_some(),
123 )
124 )]
125 pub fn new(
126 session_id: String,
127 cwd: PathBuf,
128 config: &AgentConfig,
129 meta: Option<&NewSessionMeta>,
130 ) -> Result<Arc<Self>> {
131 let start_time = Instant::now();
132
133 tracing::info!(
134 session_id = %session_id,
135 cwd = ?cwd,
136 "Creating new session"
137 );
138
139 let hook_callback_registry = Arc::new(HookCallbackRegistry::new());
141
142 let settings_manager = SettingsManager::new(&cwd)
145 .unwrap_or_else(|e| {
146 tracing::warn!("Failed to load settings manager from cwd: {}. Using default settings.", e);
147 match dirs::home_dir() {
149 Some(home) => {
150 tracing::info!("Attempting to load settings from home directory");
151 SettingsManager::new(&home).unwrap_or_else(|e2| {
152 tracing::error!("Failed to load settings from home directory: {}. Using minimal default settings.", e2);
153 SettingsManager::new_with_settings(crate::settings::Settings::default(), "/")
155 })
156 }
157 None => {
158 tracing::error!("No home directory found. Using minimal default settings.");
159 SettingsManager::new_with_settings(crate::settings::Settings::default(), "/")
160 }
161 }
162 });
163 let permission_checker = Arc::new(RwLock::new(PermissionChecker::new(
166 settings_manager.settings().clone(),
167 &cwd,
168 )));
169
170 let permission_mode = Arc::new(RwLock::new(PermissionMode::BypassPermissions));
174
175 let connection_cx_lock: Arc<OnceLock<JrConnectionCx<AgentToClient>>> =
177 Arc::new(OnceLock::new());
178
179 let pre_tool_use_hook = create_pre_tool_use_hook(
181 connection_cx_lock.clone(),
182 session_id.clone(),
183 Some(permission_checker.clone()),
184 permission_mode.clone(),
185 );
186 let post_tool_use_hook = create_post_tool_use_hook(hook_callback_registry.clone());
187
188 let mut hooks_map: HashMap<HookEvent, Vec<HookMatcher>> = HashMap::new();
190 hooks_map.insert(
191 HookEvent::PreToolUse,
192 vec![
193 HookMatcher::builder()
194 .hooks(vec![pre_tool_use_hook])
195 .build(),
196 ],
197 );
198 hooks_map.insert(
199 HookEvent::PostToolUse,
200 vec![
201 HookMatcher::builder()
202 .hooks(vec![post_tool_use_hook])
203 .build(),
204 ],
205 );
206
207 tracing::info!(
208 session_id = %session_id,
209 hooks_count = 2,
210 "Hooks configured: PreToolUse, PostToolUse"
211 );
212
213 let permission_handler = Arc::new(RwLock::new(PermissionHandler::with_checker(
217 permission_checker.clone(),
218 )));
219
220 let session_lock: Arc<OnceLock<Arc<Session>>> = Arc::new(OnceLock::new());
222
223 let acp_mcp_server = Arc::new(AcpMcpServer::new("acp", env!("CARGO_PKG_VERSION")));
225
226 let background_processes = Arc::new(BackgroundProcessManager::new());
228
229 let mut mcp_servers_dict = HashMap::new();
231 mcp_servers_dict.insert(
232 "acp".to_string(),
233 McpServerConfig::Sdk(McpSdkServerConfig {
234 name: "acp".to_string(),
235 instance: acp_mcp_server.clone(),
236 }),
237 );
238
239 tracing::info!(
240 session_id = %session_id,
241 mcp_server_count = mcp_servers_dict.len(),
242 "MCP servers configured"
243 );
244
245 let can_use_tool_callback = create_can_use_tool_callback(session_lock.clone());
247
248 let mut options = ClaudeAgentOptions::builder()
250 .cwd(cwd.clone())
251 .hooks(hooks_map)
252 .mcp_servers(McpServers::Dict(mcp_servers_dict))
253 .can_use_tool(can_use_tool_callback)
254 .permission_mode(SdkPermissionMode::BypassPermissions)
255 .build();
256
257 tracing::info!(
259 session_id = %session_id,
260 has_can_use_tool = options.can_use_tool.is_some(),
261 has_hooks = options.hooks.is_some(),
262 "Options configured after build"
263 );
264
265 match &options.mcp_servers {
267 McpServers::Dict(dict) => {
268 tracing::debug!(
269 session_id = %session_id,
270 servers = ?dict.keys().collect::<Vec<_>>(),
271 "MCP servers registered"
272 );
273 }
274 McpServers::Empty => {
275 tracing::warn!(
276 session_id = %session_id,
277 "MCP servers is Empty - this is unexpected!"
278 );
279 }
280 McpServers::Path(p) => {
281 tracing::warn!(
282 session_id = %session_id,
283 path = ?p,
284 "MCP servers is Path - this is unexpected!"
285 );
286 }
287 }
288
289 let acp_tools = get_acp_replacement_tools();
292 options.use_acp_tools(&acp_tools);
293
294 options.include_partial_messages = true;
297
298 tracing::debug!(
299 session_id = %session_id,
300 acp_tools = ?acp_tools,
301 disallowed_tools = ?options.disallowed_tools,
302 allowed_tools = ?options.allowed_tools,
303 "ACP tools configured"
304 );
305
306 config.apply_to_options(&mut options);
308
309 tracing::debug!(
310 session_id = %session_id,
311 model = ?options.model,
312 fallback_model = ?options.fallback_model,
313 max_thinking_tokens = ?options.max_thinking_tokens,
314 base_url = ?config.base_url,
315 api_key = ?config.masked_api_key(),
316 env_vars_count = options.env.len(),
317 "Agent config applied"
318 );
319
320 if let Some(meta) = meta {
322 if let Some(replace) = meta.get_system_prompt_replace() {
324 options.system_prompt = Some(SystemPrompt::Text(replace.to_string()));
326 tracing::info!(
327 session_id = %session_id,
328 prompt_len = replace.len(),
329 "Using custom system prompt from meta (replace)"
330 );
331 } else if let Some(append) = meta.get_system_prompt_append() {
332 let preset = SystemPromptPreset::with_append("claude_code", append);
334 options.system_prompt = Some(SystemPrompt::Preset(preset));
335 tracing::info!(
336 session_id = %session_id,
337 append_len = append.len(),
338 "Appending to system prompt from meta"
339 );
340 }
341
342 if let Some(resume_id) = meta.get_resume_session_id() {
344 options.resume = Some(resume_id.to_string());
345 tracing::info!(
346 session_id = %session_id,
347 resume_session_id = %resume_id,
348 "Resuming from previous session"
349 );
350 }
351
352 if let Some(tokens) = meta.get_max_thinking_tokens() {
354 options.max_thinking_tokens = Some(tokens);
355 tracing::info!(
356 session_id = %session_id,
357 max_thinking_tokens = tokens,
358 "Extended thinking mode enabled via meta"
359 );
360 }
361 }
362
363 let client = ClaudeClient::new(options);
365
366 let elapsed = start_time.elapsed();
367 tracing::info!(
368 session_id = %session_id,
369 elapsed_ms = elapsed.as_millis(),
370 "Session created successfully"
371 );
372
373 let cwd_for_converter = cwd.clone();
375
376 let session = Self {
378 session_id,
379 cwd,
380 client: RwLock::new(client),
381 cancelled: Arc::new(AtomicBool::new(false)),
382 permission: permission_handler,
383 usage_tracker: UsageTracker::new(),
384 converter: NotificationConverter::with_cwd(cwd_for_converter),
385 connected: AtomicBool::new(false),
386 hook_callback_registry,
387 permission_checker,
388 permission_mode,
389 current_model: OnceLock::new(),
390 acp_mcp_server,
391 background_processes,
392 external_mcp_servers: OnceLock::new(),
393 external_mcp_connected: AtomicBool::new(false),
394 connection_cx_lock,
395 cancel_sender: broadcast::channel(1).0,
396 };
397
398 let session_arc = Arc::new(session);
400
401 drop(session_lock.set(session_arc.clone()));
403
404 Ok(session_arc)
405 }
406
407 pub fn set_external_mcp_servers(&self, servers: Vec<McpServer>) {
413 if !servers.is_empty() {
414 tracing::info!(
415 session_id = %self.session_id,
416 server_count = servers.len(),
417 "Storing external MCP servers for later connection"
418 );
419
420 for server in &servers {
421 match server {
422 McpServer::Stdio(s) => {
423 tracing::debug!(
424 session_id = %self.session_id,
425 server_name = %s.name,
426 command = ?s.command,
427 args = ?s.args,
428 "External MCP server (stdio)"
429 );
430 }
431 McpServer::Http(s) => {
432 tracing::debug!(
433 session_id = %self.session_id,
434 server_name = %s.name,
435 url = %s.url,
436 "External MCP server (http)"
437 );
438 }
439 McpServer::Sse(s) => {
440 tracing::debug!(
441 session_id = %self.session_id,
442 server_name = %s.name,
443 url = %s.url,
444 "External MCP server (sse)"
445 );
446 }
447 _ => {
448 tracing::debug!(
449 session_id = %self.session_id,
450 "External MCP server (unknown type)"
451 );
452 }
453 }
454 }
455 }
456
457 if self.external_mcp_servers.get().is_none() {
459 drop(self.external_mcp_servers.set(servers));
460 }
461 }
462
463 pub fn set_connection_cx(&self, cx: JrConnectionCx<AgentToClient>) {
468 if self.connection_cx_lock.get().is_none() {
469 drop(self.connection_cx_lock.set(cx));
470 }
471 }
472
473 pub fn get_connection_cx(&self) -> Option<&JrConnectionCx<AgentToClient>> {
477 self.connection_cx_lock.get()
478 }
479
480 #[instrument(
485 name = "connect_external_mcp_servers",
486 skip(self),
487 fields(session_id = %self.session_id)
488 )]
489 pub async fn connect_external_mcp_servers(&self) -> Result<()> {
490 if self.external_mcp_connected.load(Ordering::SeqCst) {
492 tracing::debug!(
493 session_id = %self.session_id,
494 "External MCP servers already connected"
495 );
496 return Ok(());
497 }
498
499 let servers = match self.external_mcp_servers.get() {
501 Some(s) => s,
502 None => {
503 tracing::debug!(
504 session_id = %self.session_id,
505 "No external MCP servers to connect"
506 );
507 self.external_mcp_connected.store(true, Ordering::SeqCst);
508 return Ok(());
509 }
510 };
511
512 let servers_vec: Vec<_> = servers.iter().cloned().collect();
514
515 let server_count = servers_vec.len();
516 let start_time = Instant::now();
517
518 tracing::info!(
519 session_id = %self.session_id,
520 server_count = server_count,
521 "Connecting to external MCP servers"
522 );
523
524 let external_manager = self.acp_mcp_server.mcp_server().external_manager();
525
526 let mut success_count = 0;
527 let mut error_count = 0;
528
529 for server in servers_vec.iter() {
530 match server {
531 McpServer::Stdio(s) => {
532 let server_start = Instant::now();
533
534 tracing::info!(
535 session_id = %self.session_id,
536 server_name = %s.name,
537 command = ?s.command,
538 args = ?s.args,
539 "Connecting to external MCP server (stdio)"
540 );
541
542 let env: Option<HashMap<String, String>> = if s.env.is_empty() {
544 None
545 } else {
546 Some(
547 s.env
548 .iter()
549 .map(|e| (e.name.clone(), e.value.clone()))
550 .collect(),
551 )
552 };
553
554 match external_manager
555 .connect(
556 s.name.clone(),
557 s.command.to_string_lossy().as_ref(),
558 &s.args,
559 env.as_ref(),
560 Some(self.cwd.as_path()),
561 )
562 .await
563 {
564 Ok(_) => {
565 success_count += 1;
566 let elapsed = server_start.elapsed();
567 tracing::info!(
568 session_id = %self.session_id,
569 server_name = %s.name,
570 elapsed_ms = elapsed.as_millis(),
571 "Successfully connected to external MCP server"
572 );
573 }
574 Err(e) => {
575 error_count += 1;
576 let elapsed = server_start.elapsed();
577 tracing::error!(
578 session_id = %self.session_id,
579 server_name = %s.name,
580 error = %e,
581 elapsed_ms = elapsed.as_millis(),
582 "Failed to connect to external MCP server"
583 );
584 }
585 }
586 }
587 McpServer::Http(s) => {
588 tracing::warn!(
589 session_id = %self.session_id,
590 server_name = %s.name,
591 url = %s.url,
592 "HTTP MCP servers not yet supported"
593 );
594 }
595 McpServer::Sse(s) => {
596 tracing::warn!(
597 session_id = %self.session_id,
598 server_name = %s.name,
599 url = %s.url,
600 "SSE MCP servers not yet supported"
601 );
602 }
603 _ => {
604 tracing::warn!(
605 session_id = %self.session_id,
606 "Unknown MCP server type - not supported"
607 );
608 }
609 }
610 }
611
612 let total_elapsed = start_time.elapsed();
613 tracing::info!(
614 session_id = %self.session_id,
615 total_servers = server_count,
616 success_count = success_count,
617 error_count = error_count,
618 total_elapsed_ms = total_elapsed.as_millis(),
619 "Finished connecting external MCP servers"
620 );
621
622 self.external_mcp_connected.store(true, Ordering::SeqCst);
623 Ok(())
624 }
625
626 #[instrument(
630 name = "session_connect",
631 skip(self),
632 fields(session_id = %self.session_id)
633 )]
634 pub async fn connect(&self) -> Result<()> {
635 if self.connected.load(Ordering::SeqCst) {
636 tracing::debug!(
637 session_id = %self.session_id,
638 "Already connected to Claude CLI"
639 );
640 return Ok(());
641 }
642
643 let start_time = Instant::now();
644 tracing::info!(
645 session_id = %self.session_id,
646 cwd = ?self.cwd,
647 "Connecting to Claude CLI..."
648 );
649
650 let mut client = self.client.write().await;
651 client.connect().await.map_err(|e| {
652 let agent_error = AgentError::from(e);
653 tracing::error!(
654 session_id = %self.session_id,
655 error = %agent_error,
656 error_code = ?agent_error.error_code(),
657 is_retryable = %agent_error.is_retryable(),
658 error_chain = ?agent_error.source(),
659 "Failed to connect to Claude CLI"
660 );
661 agent_error
662 })?;
663
664 self.connected.store(true, Ordering::SeqCst);
665
666 let elapsed = start_time.elapsed();
667 tracing::info!(
668 session_id = %self.session_id,
669 elapsed_ms = elapsed.as_millis(),
670 "Successfully connected to Claude CLI"
671 );
672
673 Ok(())
674 }
675
676 #[instrument(
678 name = "session_disconnect",
679 skip(self),
680 fields(session_id = %self.session_id)
681 )]
682 pub async fn disconnect(&self) -> Result<()> {
683 if !self.connected.load(Ordering::SeqCst) {
684 tracing::debug!(
685 session_id = %self.session_id,
686 "Already disconnected from Claude CLI"
687 );
688 return Ok(());
689 }
690
691 let start_time = Instant::now();
692 tracing::info!(
693 session_id = %self.session_id,
694 "Disconnecting from Claude CLI..."
695 );
696
697 let mut client = self.client.write().await;
698 client.disconnect().await.map_err(|e| {
699 let agent_error = AgentError::from(e);
700 tracing::error!(
701 session_id = %self.session_id,
702 error = %agent_error,
703 error_code = ?agent_error.error_code(),
704 is_retryable = %agent_error.is_retryable(),
705 error_chain = ?agent_error.source(),
706 "Failed to disconnect from Claude CLI"
707 );
708 agent_error
709 })?;
710
711 self.connected.store(false, Ordering::SeqCst);
712
713 let elapsed = start_time.elapsed();
714 tracing::info!(
715 session_id = %self.session_id,
716 elapsed_ms = elapsed.as_millis(),
717 "Disconnected from Claude CLI"
718 );
719
720 Ok(())
721 }
722
723 pub fn is_connected(&self) -> bool {
725 self.connected.load(Ordering::SeqCst)
726 }
727
728 pub async fn client(&self) -> tokio::sync::RwLockReadGuard<'_, ClaudeClient> {
730 self.client.read().await
731 }
732
733 pub async fn client_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, ClaudeClient> {
735 self.client.write().await
736 }
737
738 pub fn cancel_receiver(&self) -> broadcast::Receiver<()> {
743 self.cancel_sender.subscribe()
744 }
745
746 pub fn is_cancelled(&self) -> bool {
748 self.cancelled.load(Ordering::SeqCst)
749 }
750
751 #[instrument(
753 name = "session_cancel",
754 skip(self),
755 fields(session_id = %self.session_id)
756 )]
757 pub async fn cancel(&self) {
758 tracing::info!(
759 session_id = %self.session_id,
760 "Cancelling session and sending interrupt signal"
761 );
762
763 self.cancelled.store(true, Ordering::SeqCst);
764
765 if let Ok(client) = self.client.try_read() {
767 if let Err(e) = client.interrupt().await {
768 tracing::warn!(
769 session_id = %self.session_id,
770 error = %e,
771 "Failed to send interrupt signal to Claude CLI"
772 );
773 } else {
774 tracing::info!(
775 session_id = %self.session_id,
776 "Interrupt signal sent to Claude CLI"
777 );
778 }
779 } else {
780 tracing::warn!(
781 session_id = %self.session_id,
782 "Could not acquire client lock for interrupt"
783 );
784 }
785 }
786
787 pub fn reset_cancelled(&self) {
789 self.cancelled.store(false, Ordering::SeqCst);
790 }
791
792 pub async fn permission(&self) -> tokio::sync::RwLockReadGuard<'_, PermissionHandler> {
794 self.permission.read().await
795 }
796
797 pub async fn permission_mode(&self) -> PermissionMode {
799 self.permission.read().await.mode()
800 }
801
802 pub async fn set_permission_mode(&self, mode: PermissionMode) {
807 self.permission.write().await.set_mode(mode);
809 *self.permission_mode.write().await = mode;
811
812 tracing::info!(
813 session_id = %self.session_id,
814 mode = mode.as_str(),
815 "Permission mode updated"
816 );
817 }
818
819 pub async fn add_permission_allow_rule(&self, tool_name: &str) {
823 self.permission.read().await.add_allow_rule(tool_name).await;
824 }
825
826 #[allow(dead_code)]
830 pub fn current_model(&self) -> Option<String> {
831 self.current_model.get().cloned()
832 }
833
834 #[allow(dead_code)]
838 pub fn set_model(&self, model_id: String) {
839 if self.current_model.get().is_none() {
841 drop(self.current_model.set(model_id));
842 }
843 }
844
845 pub fn usage_tracker(&self) -> &UsageTracker {
847 &self.usage_tracker
848 }
849
850 pub fn converter(&self) -> &NotificationConverter {
852 &self.converter
853 }
854
855 pub fn hook_callback_registry(&self) -> &Arc<HookCallbackRegistry> {
857 &self.hook_callback_registry
858 }
859
860 pub fn permission_checker(&self) -> &Arc<RwLock<PermissionChecker>> {
862 &self.permission_checker
863 }
864
865 pub fn register_post_tool_use_callback(
867 &self,
868 tool_use_id: String,
869 callback: crate::hooks::PostToolUseCallback,
870 ) {
871 self.hook_callback_registry
872 .register_post_tool_use(tool_use_id, callback);
873 }
874
875 pub fn acp_mcp_server(&self) -> &Arc<AcpMcpServer> {
877 &self.acp_mcp_server
878 }
879
880 pub fn background_processes(&self) -> &Arc<BackgroundProcessManager> {
882 &self.background_processes
883 }
884
885 pub async fn configure_acp_server(
890 &self,
891 connection_cx: JrConnectionCx<AgentToClient>,
892 terminal_client: Option<Arc<TerminalClient>>,
893 ) {
894 self.acp_mcp_server.set_session_id(&self.session_id);
895 self.acp_mcp_server.set_connection(connection_cx);
896 self.acp_mcp_server.set_cwd(self.cwd.clone()).await;
897 self.acp_mcp_server
898 .set_background_processes(self.background_processes.clone());
899 self.acp_mcp_server
900 .set_permission_checker(self.permission_checker.clone());
901
902 if let Some(client) = terminal_client {
903 self.acp_mcp_server.set_terminal_client(client);
904 }
905
906 let cancelled_flag = self.cancelled.clone();
908 let session_id = self.session_id.clone();
909 let cancel_sender = self.cancel_sender.clone();
910
911 self.acp_mcp_server
912 .set_cancel_callback(move || {
913 tracing::info!(
914 session_id = %session_id,
915 "MCP cancel callback invoked, sending cancel signal"
916 );
917 cancelled_flag.store(true, Ordering::SeqCst);
918 let _ = cancel_sender.send(());
920 })
921 .await;
922 }
923}
924
925#[allow(clippy::missing_fields_in_debug)]
926impl std::fmt::Debug for Session {
927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928 f.debug_struct("Session")
929 .field("session_id", &self.session_id)
930 .field("cwd", &self.cwd)
931 .field("cancelled", &self.cancelled.load(Ordering::Relaxed))
932 .field("connected", &self.connected.load(Ordering::Relaxed))
933 .finish()
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use super::*;
940
941 fn test_config() -> AgentConfig {
942 AgentConfig {
943 base_url: None,
944 api_key: None,
945 model: None,
946 small_fast_model: None,
947 max_thinking_tokens: None,
948 }
949 }
950
951 #[test]
952 fn test_session_new() {
953 let session = Session::new(
954 "test-session-1".to_string(),
955 PathBuf::from("/tmp"),
956 &test_config(),
957 None,
958 )
959 .unwrap();
960
961 assert_eq!(session.session_id, "test-session-1");
962 assert_eq!(session.cwd, PathBuf::from("/tmp"));
963 assert!(!session.is_cancelled());
964 assert!(!session.is_connected());
965 }
966
967 #[tokio::test]
968 async fn test_session_cancel() {
969 let session = Session::new(
970 "test-session-2".to_string(),
971 PathBuf::from("/tmp"),
972 &test_config(),
973 None,
974 )
975 .unwrap();
976
977 assert!(!session.is_cancelled());
978 session.cancel().await;
979 assert!(session.is_cancelled());
980 session.reset_cancelled();
981 assert!(!session.is_cancelled());
982 }
983
984 #[tokio::test]
985 async fn test_session_permission_mode() {
986 let session = Session::new(
987 "test-session-3".to_string(),
988 PathBuf::from("/tmp"),
989 &test_config(),
990 None,
991 )
992 .unwrap();
993
994 assert_eq!(session.permission_mode().await, PermissionMode::BypassPermissions);
995 session
996 .set_permission_mode(PermissionMode::AcceptEdits)
997 .await;
998 assert_eq!(session.permission_mode().await, PermissionMode::AcceptEdits);
999 }
1000}