skilllite 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
skilllite/core/handler.py CHANGED
@@ -5,16 +5,22 @@ This module handles:
5
5
  - Parsing tool calls from LLM responses
6
6
  - Executing tool calls
7
7
  - Formatting tool results
8
+
9
+ Updated to support UnifiedExecutionService for consistent sandbox level handling.
8
10
  """
9
11
 
10
12
  import json
11
- from typing import Any, Dict, List, Optional, TYPE_CHECKING
13
+ from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
12
14
 
13
15
  from .executor import ExecutionResult, SkillExecutor
14
16
  from .tools import ToolResult, ToolUseRequest
15
17
 
16
18
  if TYPE_CHECKING:
17
19
  from .registry import SkillRegistry
20
+ from ..sandbox.execution_service import UnifiedExecutionService
21
+
22
+ # Type alias for confirmation callback
23
+ ConfirmationCallback = Callable[[str, str], bool]
18
24
 
19
25
 
20
26
  class ToolCallHandler:
@@ -95,7 +101,71 @@ class ToolCallHandler:
95
101
  allow_network=allow_network,
96
102
  timeout=timeout
97
103
  )
98
-
104
+
105
+ def execute_with_unified_service(
106
+ self,
107
+ skill_name: str,
108
+ input_data: Dict[str, Any],
109
+ confirmation_callback: Optional[ConfirmationCallback] = None,
110
+ allow_network: Optional[bool] = None,
111
+ timeout: Optional[int] = None
112
+ ) -> ExecutionResult:
113
+ """
114
+ Execute a skill using the UnifiedExecutionService.
115
+
116
+ This method uses the unified execution layer which:
117
+ 1. Reads sandbox level at runtime (not from instance variables)
118
+ 2. Handles security scanning and confirmation
119
+ 3. Properly downgrades sandbox level after confirmation
120
+
121
+ Args:
122
+ skill_name: Name of the skill or multi-script tool
123
+ input_data: Input data for the skill
124
+ confirmation_callback: Callback for security confirmation
125
+ allow_network: Whether to allow network access
126
+ timeout: Execution timeout in seconds
127
+
128
+ Returns:
129
+ ExecutionResult with output or error
130
+ """
131
+ from ..sandbox.execution_service import UnifiedExecutionService
132
+
133
+ service = UnifiedExecutionService.get_instance()
134
+
135
+ # Check if it's a multi-script tool
136
+ tool_info = self._registry.get_multi_script_tool_info(skill_name)
137
+ if tool_info:
138
+ parent_skill = self._registry.get_skill(tool_info["skill_name"])
139
+ if not parent_skill:
140
+ return ExecutionResult(
141
+ success=False,
142
+ error=f"Parent skill not found: {tool_info['skill_name']}"
143
+ )
144
+ return service.execute_skill(
145
+ skill_info=parent_skill,
146
+ input_data=input_data,
147
+ entry_point=tool_info["script_path"],
148
+ confirmation_callback=confirmation_callback,
149
+ allow_network=allow_network,
150
+ timeout=timeout,
151
+ )
152
+
153
+ # Regular skill execution
154
+ info = self._registry.get_skill(skill_name)
155
+ if not info:
156
+ return ExecutionResult(
157
+ success=False,
158
+ error=f"Skill not found: {skill_name}"
159
+ )
160
+
161
+ return service.execute_skill(
162
+ skill_info=info,
163
+ input_data=input_data,
164
+ confirmation_callback=confirmation_callback,
165
+ allow_network=allow_network,
166
+ timeout=timeout,
167
+ )
168
+
99
169
  def execute_tool_call(
100
170
  self,
101
171
  request: ToolUseRequest,
@@ -130,7 +200,45 @@ class ToolCallHandler:
130
200
  tool_use_id=request.id,
131
201
  error=result.error or "Unknown error"
132
202
  )
133
-
203
+
204
+ def execute_tool_call_with_unified_service(
205
+ self,
206
+ request: ToolUseRequest,
207
+ confirmation_callback: Optional[ConfirmationCallback] = None,
208
+ allow_network: Optional[bool] = None,
209
+ timeout: Optional[int] = None
210
+ ) -> ToolResult:
211
+ """
212
+ Execute a tool call using the UnifiedExecutionService.
213
+
214
+ Args:
215
+ request: Tool use request from LLM
216
+ confirmation_callback: Callback for security confirmation
217
+ allow_network: Whether to allow network access
218
+ timeout: Execution timeout in seconds
219
+
220
+ Returns:
221
+ ToolResult with success or error
222
+ """
223
+ result = self.execute_with_unified_service(
224
+ skill_name=request.name,
225
+ input_data=request.input,
226
+ confirmation_callback=confirmation_callback,
227
+ allow_network=allow_network,
228
+ timeout=timeout
229
+ )
230
+
231
+ if result.success:
232
+ return ToolResult.success(
233
+ tool_use_id=request.id,
234
+ content=result.output
235
+ )
236
+ else:
237
+ return ToolResult.error(
238
+ tool_use_id=request.id,
239
+ error=result.error or "Unknown error"
240
+ )
241
+
134
242
  # ==================== LLM Response Parsing ====================
135
243
 
136
244
  def parse_tool_calls(self, response: Any) -> List[ToolUseRequest]:
@@ -186,7 +294,74 @@ class ToolCallHandler:
186
294
  )
187
295
  results.append(result)
188
296
  return results
189
-
297
+
298
+ def handle_tool_calls_with_unified_service(
299
+ self,
300
+ response: Any,
301
+ confirmation_callback: Optional[ConfirmationCallback] = None,
302
+ allow_network: Optional[bool] = None,
303
+ timeout: Optional[int] = None
304
+ ) -> List[ToolResult]:
305
+ """
306
+ Parse and execute all tool calls using UnifiedExecutionService.
307
+
308
+ This method uses the unified execution layer which:
309
+ 1. Reads sandbox level at runtime
310
+ 2. Handles security scanning and confirmation per-skill
311
+ 3. Properly downgrades sandbox level after confirmation
312
+
313
+ Args:
314
+ response: Response from OpenAI-compatible API
315
+ confirmation_callback: Callback for security confirmation
316
+ allow_network: Whether to allow network access
317
+ timeout: Execution timeout in seconds
318
+
319
+ Returns:
320
+ List of ToolResult objects
321
+ """
322
+ requests = self.parse_tool_calls(response)
323
+ results = []
324
+ for request in requests:
325
+ result = self.execute_tool_call_with_unified_service(
326
+ request,
327
+ confirmation_callback=confirmation_callback,
328
+ allow_network=allow_network,
329
+ timeout=timeout
330
+ )
331
+ results.append(result)
332
+ return results
333
+
334
+ def handle_tool_calls_claude_native_with_unified_service(
335
+ self,
336
+ response: Any,
337
+ confirmation_callback: Optional[ConfirmationCallback] = None,
338
+ allow_network: Optional[bool] = None,
339
+ timeout: Optional[int] = None
340
+ ) -> List[ToolResult]:
341
+ """
342
+ Parse and execute all Claude tool calls using UnifiedExecutionService.
343
+
344
+ Args:
345
+ response: Response from Claude's native API
346
+ confirmation_callback: Callback for security confirmation
347
+ allow_network: Whether to allow network access
348
+ timeout: Execution timeout in seconds
349
+
350
+ Returns:
351
+ List of ToolResult objects
352
+ """
353
+ requests = self.parse_tool_calls_claude_native(response)
354
+ results = []
355
+ for request in requests:
356
+ result = self.execute_tool_call_with_unified_service(
357
+ request,
358
+ confirmation_callback=confirmation_callback,
359
+ allow_network=allow_network,
360
+ timeout=timeout
361
+ )
362
+ results.append(result)
363
+ return results
364
+
190
365
  def handle_tool_calls_claude_native(
191
366
  self,
192
367
  response: Any,
skilllite/core/loops.py CHANGED
@@ -56,11 +56,12 @@ class AgenticLoop:
56
56
  custom_tool_handler: Optional[Callable] = None,
57
57
  enable_task_planning: bool = True,
58
58
  verbose: bool = True,
59
+ confirmation_callback: Optional[Callable[[str, str], bool]] = None,
59
60
  **kwargs
60
61
  ):
61
62
  """
62
63
  Initialize the agentic loop.
63
-
64
+
64
65
  Args:
65
66
  manager: SkillManager instance
66
67
  client: LLM client (OpenAI or Anthropic)
@@ -71,6 +72,9 @@ class AgenticLoop:
71
72
  custom_tool_handler: Optional custom tool handler function
72
73
  enable_task_planning: Whether to generate task list before execution
73
74
  verbose: Whether to print detailed logs
75
+ confirmation_callback: Callback for security confirmation (sandbox_level=3).
76
+ Signature: (security_report: str, scan_id: str) -> bool
77
+ If None and sandbox_level=3, will use interactive terminal confirmation.
74
78
  **kwargs: Additional arguments passed to the LLM
75
79
  """
76
80
  self.manager = manager
@@ -82,14 +86,157 @@ class AgenticLoop:
82
86
  self.custom_tool_handler = custom_tool_handler
83
87
  self.enable_task_planning = enable_task_planning
84
88
  self.verbose = verbose
89
+ self.confirmation_callback = confirmation_callback
85
90
  self.extra_kwargs = kwargs
86
91
  self.task_list: List[Dict] = []
87
-
92
+
93
+ # Initialize security scanner for sandbox_level=3
94
+ self._security_scanner = None
95
+ self._pending_confirmation = False # Track if confirmation is pending
96
+
88
97
  def _log(self, message: str) -> None:
89
98
  """Print log message if verbose mode is enabled."""
90
99
  if self.verbose:
91
100
  print(message)
92
-
101
+
102
+ def _get_security_scanner(self):
103
+ """Get or lazily initialize the security scanner."""
104
+ if self._security_scanner is None:
105
+ from .security import SecurityScanner
106
+ self._security_scanner = SecurityScanner()
107
+ return self._security_scanner
108
+
109
+ def _should_perform_security_scan(self) -> bool:
110
+ """Check if security scanning should be performed."""
111
+ import os
112
+ sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL", "3")
113
+ return sandbox_level == "3" and self.confirmation_callback is not None
114
+
115
+ def _interactive_confirmation(self, report: str, scan_id: str) -> bool:
116
+ """Default interactive terminal confirmation."""
117
+ self._log(f"\n{report}")
118
+ self._log("\n" + "=" * 60)
119
+ while True:
120
+ response = input("⚠️ Allow execution? (y/n): ").strip().lower()
121
+ if response in ['y', 'yes']:
122
+ return True
123
+ elif response in ['n', 'no']:
124
+ return False
125
+ self._log("Please enter 'y' or 'n'")
126
+
127
+ def _perform_security_confirmation_for_tools(
128
+ self,
129
+ tool_calls: List[Any]
130
+ ) -> bool:
131
+ """
132
+ Perform security scan and confirmation for tool calls.
133
+
134
+ Returns True if execution should proceed, False if denied.
135
+ Also handles skills that require elevated permissions.
136
+ """
137
+ import os
138
+ from .security import SecurityScanResult
139
+
140
+ sandbox_level = os.environ.get("SKILLBOX_SANDBOX_LEVEL", "3")
141
+ if sandbox_level != "3":
142
+ return True # No confirmation needed for levels 1-2
143
+
144
+ # Get skill tool names
145
+ skill_tool_names = set(self.manager.skill_names())
146
+ skill_tool_names.update(self.manager._registry.list_multi_script_tools())
147
+
148
+ # Scan each skill tool call
149
+ combined_issues = []
150
+ scanned_skills = set()
151
+ requires_elevated = False # Track if any skill requires elevated permissions
152
+
153
+ for tc in tool_calls:
154
+ tool_name = tc.function.name if hasattr(tc, 'function') else tc.get('name', '')
155
+
156
+ # Only scan skill tools, not custom tools
157
+ if tool_name not in skill_tool_names:
158
+ continue
159
+
160
+ # Get skill info
161
+ skill_name = tool_name.split('__')[0] if '__' in tool_name else tool_name
162
+ if skill_name in scanned_skills:
163
+ continue
164
+ scanned_skills.add(skill_name)
165
+
166
+ skill_info = self.manager.get_skill(skill_name)
167
+ if not skill_info:
168
+ continue
169
+
170
+ # Check if skill requires elevated permissions
171
+ if skill_info.metadata and getattr(skill_info.metadata, 'requires_elevated_permissions', False):
172
+ requires_elevated = True
173
+ self._log(f"🔓 Skill '{skill_name}' requires elevated permissions")
174
+
175
+ # Parse input data
176
+ try:
177
+ import json
178
+ input_data = json.loads(tc.function.arguments) if hasattr(tc, 'function') else {}
179
+ except (json.JSONDecodeError, AttributeError):
180
+ input_data = {}
181
+
182
+ # Perform security scan
183
+ scanner = self._get_security_scanner()
184
+ result = scanner.scan_skill(skill_info, input_data)
185
+ combined_issues.extend(result.issues)
186
+
187
+ # If skill requires elevated permissions, downgrade sandbox level
188
+ if requires_elevated and not combined_issues:
189
+ self._log("✅ Skill requires elevated permissions, downgrading to sandbox level 1...")
190
+ os.environ["SKILLBOX_SANDBOX_LEVEL"] = "1"
191
+ self._pending_confirmation = True
192
+ return True
193
+
194
+ if not combined_issues:
195
+ return True # No issues found
196
+
197
+ # Create combined scan result
198
+ high_count = sum(1 for i in combined_issues if i.get("severity") in ["Critical", "High"])
199
+ medium_count = sum(1 for i in combined_issues if i.get("severity") == "Medium")
200
+ low_count = sum(1 for i in combined_issues if i.get("severity") == "Low")
201
+
202
+ combined_result = SecurityScanResult(
203
+ is_safe=high_count == 0,
204
+ issues=combined_issues,
205
+ scan_id=f"batch-{len(scanned_skills)}",
206
+ high_severity_count=high_count,
207
+ medium_severity_count=medium_count,
208
+ low_severity_count=low_count,
209
+ )
210
+
211
+ if not combined_result.requires_confirmation:
212
+ return True # Only low/medium issues, proceed
213
+
214
+ # Ask for confirmation
215
+ report = combined_result.format_report()
216
+ self._log(f"\n🔒 Security scan detected potential issues:")
217
+
218
+ if self.confirmation_callback:
219
+ confirmed = self.confirmation_callback(report, combined_result.scan_id)
220
+ else:
221
+ confirmed = self._interactive_confirmation(report, combined_result.scan_id)
222
+
223
+ if confirmed:
224
+ # Temporarily downgrade sandbox level to allow execution
225
+ self._log("✅ User confirmed. Executing with sandbox level 1...")
226
+ os.environ["SKILLBOX_SANDBOX_LEVEL"] = "1"
227
+ self._pending_confirmation = True
228
+ return True
229
+ else:
230
+ self._log("❌ User denied execution.")
231
+ return False
232
+
233
+ def _restore_sandbox_level(self, original_level: str) -> None:
234
+ """Restore original sandbox level after execution."""
235
+ import os
236
+ if self._pending_confirmation:
237
+ os.environ["SKILLBOX_SANDBOX_LEVEL"] = original_level
238
+ self._pending_confirmation = False
239
+
93
240
  def _get_execution_system_prompt(self) -> str:
94
241
  """
95
242
  Generate the main execution system prompt for skill selection and file operations.
@@ -534,15 +681,18 @@ Based on the documentation, call the tools with correct parameters.
534
681
  # No tool calls
535
682
  if not message.tool_calls:
536
683
  self._log("📝 LLM did not call any tools")
537
-
684
+
538
685
  if self.enable_task_planning:
539
686
  completed_id = self._check_task_completion_in_content(message.content)
540
687
  if completed_id:
541
688
  self._update_task_list(completed_id)
542
-
689
+
543
690
  if self._check_all_tasks_completed():
544
691
  self._log("🎯 All tasks completed, ending iteration")
545
692
  return response
693
+ else:
694
+ # Tasks not complete and no tool calls - continue to next iteration
695
+ continue
546
696
  else:
547
697
  return response
548
698
 
@@ -570,17 +720,25 @@ Based on the documentation, call the tools with correct parameters.
570
720
  continue
571
721
 
572
722
  messages.append(message)
573
-
723
+
724
+ # Execute tools using unified execution service
574
725
  self._log(f"\n⚙️ Executing tools...")
726
+
575
727
  if self.custom_tool_handler:
728
+ # Custom tool handler takes precedence
576
729
  tool_results = self.custom_tool_handler(
577
730
  response, self.manager, allow_network, timeout
578
731
  )
579
732
  else:
580
- tool_results = self.manager.handle_tool_calls(
581
- response, allow_network=allow_network, timeout=timeout
733
+ # Use unified execution service with confirmation callback
734
+ # This handles security scanning, confirmation, and sandbox level management
735
+ tool_results = self.manager.handle_tool_calls_with_unified_service(
736
+ response,
737
+ confirmation_callback=self.confirmation_callback or self._interactive_confirmation,
738
+ allow_network=allow_network,
739
+ timeout=timeout
582
740
  )
583
-
741
+
584
742
  self._log(f"\n📊 Tool execution results:")
585
743
  for idx, (result, tc) in enumerate(zip(tool_results, message.tool_calls), 1):
586
744
  output = result.content
@@ -588,7 +746,7 @@ Based on the documentation, call the tools with correct parameters.
588
746
  output = output[:500] + "... (truncated)"
589
747
  self._log(f" {idx}. {tc.function.name}")
590
748
  self._log(f" Result: {output}")
591
-
749
+
592
750
  for result in tool_results:
593
751
  messages.append(result.to_openai_format())
594
752
 
@@ -690,19 +848,26 @@ Based on the documentation, call the tools with correct parameters.
690
848
  self._log(f" Arguments: {json.dumps(block.input, ensure_ascii=False)}")
691
849
 
692
850
  messages.append({"role": "assistant", "content": response.content})
693
-
851
+
852
+ # Execute tools using unified execution service
694
853
  self._log(f"\n⚙️ Executing tools...")
695
- tool_results = self.manager.handle_tool_calls_claude_native(
696
- response, allow_network=allow_network, timeout=timeout
854
+
855
+ # Use unified execution service with confirmation callback
856
+ # This handles security scanning, confirmation, and sandbox level management
857
+ tool_results = self.manager.handle_tool_calls_claude_native_with_unified_service(
858
+ response,
859
+ confirmation_callback=self.confirmation_callback or self._interactive_confirmation,
860
+ allow_network=allow_network,
861
+ timeout=timeout
697
862
  )
698
-
863
+
699
864
  self._log(f"\n📊 Tool execution results:")
700
865
  for idx, result in enumerate(tool_results, 1):
701
866
  output = result.content
702
867
  if len(output) > 500:
703
868
  output = output[:500] + "... (truncated)"
704
869
  self._log(f" {idx}. Result: {output}")
705
-
870
+
706
871
  formatted_results = self.manager.format_tool_results_claude_native(tool_results)
707
872
  messages.append({"role": "user", "content": formatted_results})
708
873
 
skilllite/core/manager.py CHANGED
@@ -304,7 +304,64 @@ class SkillManager:
304
304
  ) -> List[ToolResult]:
305
305
  """Parse and execute all tool calls from Claude's native API response."""
306
306
  return self._handler.handle_tool_calls_claude_native(response, allow_network, timeout)
307
-
307
+
308
+ def handle_tool_calls_with_unified_service(
309
+ self,
310
+ response: Any,
311
+ confirmation_callback: Optional[Callable[[str, str], bool]] = None,
312
+ allow_network: Optional[bool] = None,
313
+ timeout: Optional[int] = None
314
+ ) -> List[ToolResult]:
315
+ """
316
+ Parse and execute all tool calls using UnifiedExecutionService.
317
+
318
+ This method uses the unified execution layer which:
319
+ 1. Reads sandbox level at runtime
320
+ 2. Handles security scanning and confirmation per-skill
321
+ 3. Properly downgrades sandbox level after confirmation
322
+
323
+ Args:
324
+ response: Response from OpenAI-compatible API
325
+ confirmation_callback: Callback for security confirmation
326
+ allow_network: Whether to allow network access
327
+ timeout: Execution timeout in seconds
328
+
329
+ Returns:
330
+ List of ToolResult objects
331
+ """
332
+ return self._handler.handle_tool_calls_with_unified_service(
333
+ response,
334
+ confirmation_callback=confirmation_callback,
335
+ allow_network=allow_network,
336
+ timeout=timeout
337
+ )
338
+
339
+ def handle_tool_calls_claude_native_with_unified_service(
340
+ self,
341
+ response: Any,
342
+ confirmation_callback: Optional[Callable[[str, str], bool]] = None,
343
+ allow_network: Optional[bool] = None,
344
+ timeout: Optional[int] = None
345
+ ) -> List[ToolResult]:
346
+ """
347
+ Parse and execute all Claude tool calls using UnifiedExecutionService.
348
+
349
+ Args:
350
+ response: Response from Claude's native API
351
+ confirmation_callback: Callback for security confirmation
352
+ allow_network: Whether to allow network access
353
+ timeout: Execution timeout in seconds
354
+
355
+ Returns:
356
+ List of ToolResult objects
357
+ """
358
+ return self._handler.handle_tool_calls_claude_native_with_unified_service(
359
+ response,
360
+ confirmation_callback=confirmation_callback,
361
+ allow_network=allow_network,
362
+ timeout=timeout
363
+ )
364
+
308
365
  def format_tool_results_claude_native(self, results: List[ToolResult]) -> List[Dict[str, Any]]:
309
366
  """Format tool results for Claude's native API."""
310
367
  return self._handler.format_tool_results_claude_native(results)
@@ -335,6 +392,7 @@ class SkillManager:
335
392
  custom_tool_handler: Optional[Callable] = None,
336
393
  enable_task_planning: bool = True,
337
394
  verbose: bool = True,
395
+ confirmation_callback: Optional[Callable[[str, str], bool]] = None,
338
396
  **kwargs
339
397
  ) -> AgenticLoop:
340
398
  """
@@ -351,15 +409,16 @@ class SkillManager:
351
409
  custom_tool_handler: Optional custom tool handler
352
410
  enable_task_planning: Whether to generate task list before execution
353
411
  verbose: Whether to print detailed logs
412
+ confirmation_callback: Callback for security confirmation (sandbox_level=3)
354
413
  **kwargs: Additional arguments passed to the LLM
355
-
414
+
356
415
  Returns:
357
416
  AgenticLoop instance
358
-
417
+
359
418
  Example:
360
419
  # OpenAI-compatible (default)
361
420
  loop = manager.create_agentic_loop(client, "gpt-4")
362
-
421
+
363
422
  # Claude native API
364
423
  loop = manager.create_agentic_loop(client, "claude-3-opus",
365
424
  api_format="claude_native")
@@ -375,6 +434,7 @@ class SkillManager:
375
434
  custom_tool_handler=custom_tool_handler,
376
435
  enable_task_planning=enable_task_planning,
377
436
  verbose=verbose,
437
+ confirmation_callback=confirmation_callback,
378
438
  **kwargs
379
439
  )
380
440
 
@@ -421,14 +481,15 @@ class SkillManager:
421
481
  custom_tool_executor: Optional[Callable] = None,
422
482
  enable_task_planning: bool = True,
423
483
  verbose: bool = True,
484
+ confirmation_callback: Optional[Callable[[str, str], bool]] = None,
424
485
  **kwargs
425
486
  ) -> AgenticLoop:
426
487
  """
427
488
  Create an enhanced agentic loop with custom tools support.
428
-
489
+
429
490
  This method creates an AgenticLoop that can handle both skill tools
430
491
  and custom tools (like file operations).
431
-
492
+
432
493
  Args:
433
494
  client: LLM client (OpenAI-compatible)
434
495
  model: Model name to use
@@ -438,27 +499,32 @@ class SkillManager:
438
499
  custom_tool_executor: Executor function for custom tools
439
500
  enable_task_planning: Whether to generate task list before execution
440
501
  verbose: Whether to print detailed logs
502
+ confirmation_callback: Callback for security confirmation (sandbox_level=3)
441
503
  **kwargs: Additional arguments passed to the LLM
442
-
504
+
443
505
  Returns:
444
506
  AgenticLoop instance with enhanced capabilities
445
507
  """
446
508
  # Create custom tool handler that combines skill tools and custom tools
447
509
  def combined_tool_handler(response, manager, allow_network, timeout):
448
510
  from .tools import ToolUseRequest, ToolResult
449
-
511
+
450
512
  requests = ToolUseRequest.parse_from_openai_response(response)
451
513
  results = []
452
-
514
+
453
515
  # Get skill tool names
454
516
  skill_tool_names = set(self.skill_names())
455
517
  skill_tool_names.update(self._registry.list_multi_script_tools())
456
-
518
+
457
519
  for request in requests:
458
520
  if request.name in skill_tool_names:
459
- # Execute as skill tool
460
- result = self._handler.execute_tool_call(
461
- request, allow_network=allow_network, timeout=timeout
521
+ # Execute as skill tool using UnifiedExecutionService
522
+ # This handles security scanning, confirmation, and proper sandbox level
523
+ result = self._handler.execute_tool_call_with_unified_service(
524
+ request,
525
+ confirmation_callback=confirmation_callback,
526
+ allow_network=allow_network,
527
+ timeout=timeout
462
528
  )
463
529
  results.append(result)
464
530
  elif custom_tool_executor:
@@ -473,9 +539,9 @@ class SkillManager:
473
539
  results.append(ToolResult.error(
474
540
  request.id, f"No executor found for tool: {request.name}"
475
541
  ))
476
-
542
+
477
543
  return results
478
-
544
+
479
545
  return AgenticLoop(
480
546
  manager=self,
481
547
  client=client,
@@ -486,6 +552,7 @@ class SkillManager:
486
552
  custom_tool_handler=combined_tool_handler if custom_tool_executor else None,
487
553
  enable_task_planning=enable_task_planning,
488
554
  verbose=verbose,
555
+ confirmation_callback=confirmation_callback,
489
556
  **kwargs
490
557
  )
491
558