grok-cli-acp 0.1.2

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.
Files changed (100) hide show
  1. package/.env.example +42 -0
  2. package/.github/workflows/ci.yml +30 -0
  3. package/.github/workflows/rust.yml +22 -0
  4. package/.grok/.env.example +85 -0
  5. package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
  6. package/.grok/ENV_CONFIG_GUIDE.md +173 -0
  7. package/.grok/QUICK_REFERENCE.md +180 -0
  8. package/.grok/README.md +104 -0
  9. package/.grok/TESTING_GUIDE.md +393 -0
  10. package/CHANGELOG.md +465 -0
  11. package/CODE_REVIEW_SUMMARY.md +414 -0
  12. package/COMPLETE_FIX_SUMMARY.md +415 -0
  13. package/CONFIGURATION.md +489 -0
  14. package/CONTEXT_FILES_GUIDE.md +419 -0
  15. package/CONTRIBUTING.md +55 -0
  16. package/CURSOR_POSITION_FIX.md +206 -0
  17. package/Cargo.toml +88 -0
  18. package/ERROR_HANDLING_REPORT.md +361 -0
  19. package/FINAL_FIX_SUMMARY.md +462 -0
  20. package/FIXES.md +37 -0
  21. package/FIXES_SUMMARY.md +87 -0
  22. package/GROK_API_MIGRATION_SUMMARY.md +111 -0
  23. package/LICENSE +22 -0
  24. package/MIGRATION_TO_GROK_API.md +223 -0
  25. package/README.md +504 -0
  26. package/REVIEW_COMPLETE.md +416 -0
  27. package/REVIEW_QUICK_REFERENCE.md +173 -0
  28. package/SECURITY.md +463 -0
  29. package/SECURITY_AUDIT.md +661 -0
  30. package/SETUP.md +287 -0
  31. package/TESTING_TOOLS.md +88 -0
  32. package/TESTING_TOOL_EXECUTION.md +239 -0
  33. package/TOOL_EXECUTION_FIX.md +491 -0
  34. package/VERIFICATION_CHECKLIST.md +419 -0
  35. package/docs/API.md +74 -0
  36. package/docs/CHAT_LOGGING.md +39 -0
  37. package/docs/CURSOR_FIX_DEMO.md +306 -0
  38. package/docs/ERROR_HANDLING_GUIDE.md +547 -0
  39. package/docs/FILE_OPERATIONS.md +449 -0
  40. package/docs/INTERACTIVE.md +401 -0
  41. package/docs/PROJECT_CREATION_GUIDE.md +570 -0
  42. package/docs/QUICKSTART.md +378 -0
  43. package/docs/QUICK_REFERENCE.md +691 -0
  44. package/docs/RELEASE_NOTES_0.1.2.md +240 -0
  45. package/docs/TOOLS.md +459 -0
  46. package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
  47. package/docs/ZED_INTEGRATION.md +371 -0
  48. package/docs/extensions.md +464 -0
  49. package/docs/settings.md +293 -0
  50. package/examples/extensions/logging-hook/README.md +91 -0
  51. package/examples/extensions/logging-hook/extension.json +22 -0
  52. package/package.json +30 -0
  53. package/scripts/test_acp.py +252 -0
  54. package/scripts/test_acp.sh +143 -0
  55. package/scripts/test_acp_simple.sh +72 -0
  56. package/src/acp/mod.rs +741 -0
  57. package/src/acp/protocol.rs +323 -0
  58. package/src/acp/security.rs +298 -0
  59. package/src/acp/tools.rs +697 -0
  60. package/src/bin/banner_demo.rs +216 -0
  61. package/src/bin/docgen.rs +18 -0
  62. package/src/bin/installer.rs +217 -0
  63. package/src/cli/app.rs +310 -0
  64. package/src/cli/commands/acp.rs +721 -0
  65. package/src/cli/commands/chat.rs +485 -0
  66. package/src/cli/commands/code.rs +513 -0
  67. package/src/cli/commands/config.rs +394 -0
  68. package/src/cli/commands/health.rs +442 -0
  69. package/src/cli/commands/history.rs +421 -0
  70. package/src/cli/commands/mod.rs +14 -0
  71. package/src/cli/commands/settings.rs +1384 -0
  72. package/src/cli/mod.rs +166 -0
  73. package/src/config/mod.rs +2212 -0
  74. package/src/display/ascii_art.rs +139 -0
  75. package/src/display/banner.rs +289 -0
  76. package/src/display/components/input.rs +323 -0
  77. package/src/display/components/mod.rs +2 -0
  78. package/src/display/components/settings_list.rs +306 -0
  79. package/src/display/interactive.rs +1255 -0
  80. package/src/display/mod.rs +62 -0
  81. package/src/display/terminal.rs +42 -0
  82. package/src/display/tips.rs +316 -0
  83. package/src/grok_client_ext.rs +177 -0
  84. package/src/hooks/loader.rs +407 -0
  85. package/src/hooks/mod.rs +158 -0
  86. package/src/lib.rs +174 -0
  87. package/src/main.rs +65 -0
  88. package/src/mcp/client.rs +195 -0
  89. package/src/mcp/config.rs +20 -0
  90. package/src/mcp/mod.rs +6 -0
  91. package/src/mcp/protocol.rs +67 -0
  92. package/src/utils/auth.rs +41 -0
  93. package/src/utils/chat_logger.rs +568 -0
  94. package/src/utils/context.rs +390 -0
  95. package/src/utils/mod.rs +16 -0
  96. package/src/utils/network.rs +320 -0
  97. package/src/utils/rate_limiter.rs +166 -0
  98. package/src/utils/session.rs +73 -0
  99. package/src/utils/shell_permissions.rs +389 -0
  100. package/src/utils/telemetry.rs +41 -0
@@ -0,0 +1,407 @@
1
+ //! Extension loading and management
2
+ //!
3
+ //! This module provides functionality to discover, load, and initialize
4
+ //! extensions from configuration or extension directories.
5
+
6
+ use super::{Extension, ExtensionManager, Hook, HookManager, ToolContext};
7
+ use crate::config::ExtensionsConfig;
8
+ use anyhow::{anyhow, Result};
9
+ use serde::{Deserialize, Serialize};
10
+ use std::fs;
11
+ use std::path::{Path, PathBuf};
12
+ use tracing::{debug, info, warn};
13
+
14
+ /// Extension metadata from manifest file
15
+ #[derive(Debug, Clone, Serialize, Deserialize)]
16
+ pub struct ExtensionManifest {
17
+ /// Extension name
18
+ pub name: String,
19
+
20
+ /// Extension version
21
+ pub version: String,
22
+
23
+ /// Extension description
24
+ pub description: Option<String>,
25
+
26
+ /// Author information
27
+ pub author: Option<String>,
28
+
29
+ /// Extension type (hook, tool, etc.)
30
+ pub extension_type: ExtensionType,
31
+
32
+ /// Hooks configuration
33
+ #[serde(default)]
34
+ pub hooks: Vec<HookConfig>,
35
+
36
+ /// Extension dependencies
37
+ #[serde(default)]
38
+ pub dependencies: Vec<String>,
39
+
40
+ /// Enabled by default
41
+ #[serde(default = "default_true")]
42
+ pub enabled: bool,
43
+ }
44
+
45
+ fn default_true() -> bool {
46
+ true
47
+ }
48
+
49
+ /// Type of extension
50
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
51
+ #[serde(rename_all = "snake_case")]
52
+ pub enum ExtensionType {
53
+ /// Hook-based extension
54
+ Hook,
55
+ /// Tool provider extension
56
+ Tool,
57
+ /// Combined hook and tool extension
58
+ Combined,
59
+ }
60
+
61
+ /// Hook configuration from manifest
62
+ #[derive(Debug, Clone, Serialize, Deserialize)]
63
+ pub struct HookConfig {
64
+ /// Hook name/identifier
65
+ pub name: String,
66
+
67
+ /// Hook type
68
+ pub hook_type: HookType,
69
+
70
+ /// Optional script or command to execute
71
+ pub script: Option<String>,
72
+
73
+ /// Optional configuration
74
+ pub config: Option<serde_json::Value>,
75
+ }
76
+
77
+ /// Type of hook
78
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79
+ #[serde(rename_all = "snake_case")]
80
+ pub enum HookType {
81
+ /// Executes before tool invocation
82
+ BeforeTool,
83
+ /// Executes after tool invocation
84
+ AfterTool,
85
+ /// Both before and after
86
+ Both,
87
+ }
88
+
89
+ /// Loaded extension with its manifest
90
+ pub struct LoadedExtension {
91
+ pub manifest: ExtensionManifest,
92
+ pub path: PathBuf,
93
+ }
94
+
95
+ /// Extension loader for discovering and loading extensions
96
+ pub struct ExtensionLoader {
97
+ config: ExtensionsConfig,
98
+ loaded_extensions: Vec<LoadedExtension>,
99
+ }
100
+
101
+ impl ExtensionLoader {
102
+ /// Create a new extension loader with the given configuration
103
+ pub fn new(config: ExtensionsConfig) -> Self {
104
+ Self {
105
+ config,
106
+ loaded_extensions: Vec::new(),
107
+ }
108
+ }
109
+
110
+ /// Discover all available extensions from the extension directory
111
+ pub fn discover_extensions(&mut self) -> Result<Vec<ExtensionManifest>> {
112
+ if !self.config.enabled {
113
+ debug!("Extension system is disabled");
114
+ return Ok(Vec::new());
115
+ }
116
+
117
+ let extension_dir = match &self.config.extension_dir {
118
+ Some(dir) => dir.clone(),
119
+ None => self.get_default_extension_dir()?,
120
+ };
121
+
122
+ if !extension_dir.exists() {
123
+ info!(
124
+ "Extension directory does not exist: {}",
125
+ extension_dir.display()
126
+ );
127
+ return Ok(Vec::new());
128
+ }
129
+
130
+ let mut manifests = Vec::new();
131
+
132
+ for entry in fs::read_dir(&extension_dir)? {
133
+ let entry = entry?;
134
+ let path = entry.path();
135
+
136
+ if path.is_dir() {
137
+ match self.load_extension_manifest(&path) {
138
+ Ok(manifest) => {
139
+ info!("Discovered extension: {}", manifest.name);
140
+ self.loaded_extensions.push(LoadedExtension {
141
+ manifest: manifest.clone(),
142
+ path: path.clone(),
143
+ });
144
+ manifests.push(manifest);
145
+ }
146
+ Err(e) => {
147
+ warn!("Failed to load extension from {}: {}", path.display(), e);
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ Ok(manifests)
154
+ }
155
+
156
+ /// Load extension manifest from a directory
157
+ fn load_extension_manifest(&self, extension_path: &Path) -> Result<ExtensionManifest> {
158
+ let manifest_path = extension_path.join("extension.json");
159
+
160
+ if !manifest_path.exists() {
161
+ return Err(anyhow!(
162
+ "Extension manifest not found at {}",
163
+ manifest_path.display()
164
+ ));
165
+ }
166
+
167
+ let content = fs::read_to_string(&manifest_path)?;
168
+ let manifest: ExtensionManifest = serde_json::from_str(&content)?;
169
+
170
+ Ok(manifest)
171
+ }
172
+
173
+ /// Get the default extension directory
174
+ fn get_default_extension_dir(&self) -> Result<PathBuf> {
175
+ let home_dir =
176
+ dirs::home_dir().ok_or_else(|| anyhow!("Could not determine home directory"))?;
177
+ Ok(home_dir.join(".grok").join("extensions"))
178
+ }
179
+
180
+ /// Load and register all enabled extensions
181
+ pub fn load_extensions(&mut self, extension_manager: &mut ExtensionManager) -> Result<()> {
182
+ if !self.config.enabled {
183
+ debug!("Extension system is disabled, skipping extension loading");
184
+ return Ok(());
185
+ }
186
+
187
+ // Discover extensions if not already done
188
+ if self.loaded_extensions.is_empty() {
189
+ self.discover_extensions()?;
190
+ }
191
+
192
+ // Filter to only enabled extensions
193
+ let enabled_extensions: Vec<_> = self
194
+ .loaded_extensions
195
+ .iter()
196
+ .filter(|ext| {
197
+ ext.manifest.enabled
198
+ && (self.config.enabled_extensions.is_empty()
199
+ || self.config.enabled_extensions.contains(&ext.manifest.name))
200
+ })
201
+ .collect();
202
+
203
+ for loaded_ext in enabled_extensions {
204
+ match self.instantiate_extension(loaded_ext) {
205
+ Ok(extension) => {
206
+ info!("Loading extension: {}", loaded_ext.manifest.name);
207
+ extension_manager.register(extension);
208
+ }
209
+ Err(e) => {
210
+ warn!(
211
+ "Failed to instantiate extension {}: {}",
212
+ loaded_ext.manifest.name, e
213
+ );
214
+ }
215
+ }
216
+ }
217
+
218
+ Ok(())
219
+ }
220
+
221
+ /// Instantiate an extension from its loaded metadata
222
+ fn instantiate_extension(&self, loaded_ext: &LoadedExtension) -> Result<Box<dyn Extension>> {
223
+ // For now, we create a simple hook-based extension
224
+ // In a full implementation, this could load dynamic libraries or scripts
225
+ Ok(Box::new(ConfigBasedExtension {
226
+ manifest: loaded_ext.manifest.clone(),
227
+ }))
228
+ }
229
+
230
+ /// Get list of loaded extension manifests
231
+ pub fn get_loaded_extensions(&self) -> Vec<&ExtensionManifest> {
232
+ self.loaded_extensions.iter().map(|e| &e.manifest).collect()
233
+ }
234
+ }
235
+
236
+ /// A simple extension implementation based on configuration
237
+ struct ConfigBasedExtension {
238
+ manifest: ExtensionManifest,
239
+ }
240
+
241
+ impl Extension for ConfigBasedExtension {
242
+ fn name(&self) -> &str {
243
+ &self.manifest.name
244
+ }
245
+
246
+ fn register_hooks(&self, hook_manager: &mut HookManager) -> Result<()> {
247
+ for hook_config in &self.manifest.hooks {
248
+ let hook = create_hook_from_config(&self.manifest.name, hook_config)?;
249
+ hook_manager.register(hook);
250
+ }
251
+ Ok(())
252
+ }
253
+ }
254
+
255
+ /// Create a hook instance from configuration
256
+ fn create_hook_from_config(
257
+ extension_name: &str,
258
+ hook_config: &HookConfig,
259
+ ) -> Result<Box<dyn Hook>> {
260
+ Ok(Box::new(ConfigBasedHook {
261
+ extension_name: extension_name.to_string(),
262
+ config: hook_config.clone(),
263
+ }))
264
+ }
265
+
266
+ /// A hook implementation based on configuration
267
+ struct ConfigBasedHook {
268
+ extension_name: String,
269
+ config: HookConfig,
270
+ }
271
+
272
+ impl Hook for ConfigBasedHook {
273
+ fn name(&self) -> &str {
274
+ &self.config.name
275
+ }
276
+
277
+ fn before_tool(&self, context: &ToolContext) -> Result<bool> {
278
+ if self.config.hook_type == HookType::BeforeTool || self.config.hook_type == HookType::Both
279
+ {
280
+ debug!(
281
+ "Extension '{}' hook '{}' executing before tool '{}'",
282
+ self.extension_name, self.config.name, context.tool_name
283
+ );
284
+
285
+ // In a full implementation, this could execute a script or custom logic
286
+ // For now, we just log and continue
287
+ }
288
+ Ok(true)
289
+ }
290
+
291
+ fn after_tool(&self, context: &ToolContext, result: &str) -> Result<()> {
292
+ if self.config.hook_type == HookType::AfterTool || self.config.hook_type == HookType::Both {
293
+ debug!(
294
+ "Extension '{}' hook '{}' executing after tool '{}' (result length: {})",
295
+ self.extension_name,
296
+ self.config.name,
297
+ context.tool_name,
298
+ result.len()
299
+ );
300
+
301
+ // In a full implementation, this could execute a script or custom logic
302
+ // For now, we just log
303
+ }
304
+ Ok(())
305
+ }
306
+ }
307
+
308
+ #[cfg(test)]
309
+ mod tests {
310
+ use super::*;
311
+ use std::fs;
312
+ use tempfile::tempdir;
313
+
314
+ #[test]
315
+ fn test_load_extension_manifest() {
316
+ let temp_dir = tempdir().unwrap();
317
+ let ext_dir = temp_dir.path().join("test-extension");
318
+ fs::create_dir(&ext_dir).unwrap();
319
+
320
+ let manifest = ExtensionManifest {
321
+ name: "test-extension".to_string(),
322
+ version: "1.0.0".to_string(),
323
+ description: Some("Test extension".to_string()),
324
+ author: Some("Test Author".to_string()),
325
+ extension_type: ExtensionType::Hook,
326
+ hooks: vec![HookConfig {
327
+ name: "test-hook".to_string(),
328
+ hook_type: HookType::BeforeTool,
329
+ script: None,
330
+ config: None,
331
+ }],
332
+ dependencies: vec![],
333
+ enabled: true,
334
+ };
335
+
336
+ let manifest_path = ext_dir.join("extension.json");
337
+ let json = serde_json::to_string_pretty(&manifest).unwrap();
338
+ fs::write(&manifest_path, json).unwrap();
339
+
340
+ let config = ExtensionsConfig {
341
+ enabled: true,
342
+ extension_dir: Some(temp_dir.path().to_path_buf()),
343
+ enabled_extensions: vec![],
344
+ allow_config_extensions: true,
345
+ };
346
+
347
+ let loader = ExtensionLoader::new(config);
348
+ let loaded_manifest = loader.load_extension_manifest(&ext_dir).unwrap();
349
+
350
+ assert_eq!(loaded_manifest.name, "test-extension");
351
+ assert_eq!(loaded_manifest.version, "1.0.0");
352
+ assert_eq!(loaded_manifest.hooks.len(), 1);
353
+ }
354
+
355
+ #[test]
356
+ fn test_discover_extensions() {
357
+ let temp_dir = tempdir().unwrap();
358
+
359
+ // Create two test extensions
360
+ for i in 1..=2 {
361
+ let ext_dir = temp_dir.path().join(format!("extension-{}", i));
362
+ fs::create_dir(&ext_dir).unwrap();
363
+
364
+ let manifest = ExtensionManifest {
365
+ name: format!("extension-{}", i),
366
+ version: "1.0.0".to_string(),
367
+ description: None,
368
+ author: None,
369
+ extension_type: ExtensionType::Hook,
370
+ hooks: vec![],
371
+ dependencies: vec![],
372
+ enabled: true,
373
+ };
374
+
375
+ let manifest_path = ext_dir.join("extension.json");
376
+ let json = serde_json::to_string_pretty(&manifest).unwrap();
377
+ fs::write(&manifest_path, json).unwrap();
378
+ }
379
+
380
+ let config = ExtensionsConfig {
381
+ enabled: true,
382
+ extension_dir: Some(temp_dir.path().to_path_buf()),
383
+ enabled_extensions: vec![],
384
+ allow_config_extensions: true,
385
+ };
386
+
387
+ let mut loader = ExtensionLoader::new(config);
388
+ let manifests = loader.discover_extensions().unwrap();
389
+
390
+ assert_eq!(manifests.len(), 2);
391
+ }
392
+
393
+ #[test]
394
+ fn test_extension_disabled() {
395
+ let config = ExtensionsConfig {
396
+ enabled: false,
397
+ extension_dir: None,
398
+ enabled_extensions: vec![],
399
+ allow_config_extensions: false,
400
+ };
401
+
402
+ let mut loader = ExtensionLoader::new(config);
403
+ let manifests = loader.discover_extensions().unwrap();
404
+
405
+ assert_eq!(manifests.len(), 0);
406
+ }
407
+ }
@@ -0,0 +1,158 @@
1
+ pub mod loader;
2
+
3
+ use anyhow::Result;
4
+ use serde_json::Value;
5
+
6
+ #[derive(Debug, Clone)]
7
+ pub struct ToolContext {
8
+ pub tool_name: String,
9
+ pub args: Value,
10
+ }
11
+
12
+ pub trait Hook: Send + Sync {
13
+ fn name(&self) -> &str;
14
+
15
+ // Return Ok(true) to continue, Ok(false) to abort (silently or with log), Err to fail hard
16
+ fn before_tool(&self, _context: &ToolContext) -> Result<bool> {
17
+ Ok(true)
18
+ }
19
+
20
+ fn after_tool(&self, _context: &ToolContext, _result: &str) -> Result<()> {
21
+ Ok(())
22
+ }
23
+ }
24
+
25
+ pub struct HookManager {
26
+ hooks: Vec<Box<dyn Hook>>,
27
+ }
28
+
29
+ impl Default for HookManager {
30
+ fn default() -> Self {
31
+ Self::new()
32
+ }
33
+ }
34
+
35
+ impl HookManager {
36
+ pub fn new() -> Self {
37
+ Self { hooks: Vec::new() }
38
+ }
39
+
40
+ pub fn register(&mut self, hook: Box<dyn Hook>) {
41
+ self.hooks.push(hook);
42
+ }
43
+
44
+ pub fn execute_before_tool(&self, tool_name: &str, args: &Value) -> Result<bool> {
45
+ let context = ToolContext {
46
+ tool_name: tool_name.to_string(),
47
+ args: args.clone(),
48
+ };
49
+
50
+ for hook in &self.hooks {
51
+ if !hook.before_tool(&context)? {
52
+ return Ok(false);
53
+ }
54
+ }
55
+ Ok(true)
56
+ }
57
+
58
+ pub fn execute_after_tool(&self, tool_name: &str, args: &Value, result: &str) -> Result<()> {
59
+ let context = ToolContext {
60
+ tool_name: tool_name.to_string(),
61
+ args: args.clone(),
62
+ };
63
+
64
+ for hook in &self.hooks {
65
+ hook.after_tool(&context, result)?;
66
+ }
67
+ Ok(())
68
+ }
69
+ }
70
+
71
+ pub trait Extension: Send + Sync {
72
+ fn name(&self) -> &str;
73
+ fn register_hooks(&self, hook_manager: &mut HookManager) -> Result<()>;
74
+ }
75
+
76
+ pub struct ExtensionManager {
77
+ extensions: Vec<Box<dyn Extension>>,
78
+ }
79
+
80
+ impl Default for ExtensionManager {
81
+ fn default() -> Self {
82
+ Self::new()
83
+ }
84
+ }
85
+
86
+ impl ExtensionManager {
87
+ pub fn new() -> Self {
88
+ Self {
89
+ extensions: Vec::new(),
90
+ }
91
+ }
92
+
93
+ pub fn register(&mut self, extension: Box<dyn Extension>) {
94
+ self.extensions.push(extension);
95
+ }
96
+
97
+ pub fn register_all_hooks(&self, hook_manager: &mut HookManager) -> Result<()> {
98
+ for ext in &self.extensions {
99
+ ext.register_hooks(hook_manager)?;
100
+ }
101
+ Ok(())
102
+ }
103
+ }
104
+
105
+ #[cfg(test)]
106
+ mod tests {
107
+ use super::*;
108
+ use std::sync::{Arc, Mutex};
109
+
110
+ struct TestHook {
111
+ name: String,
112
+ before_called: Arc<Mutex<bool>>,
113
+ after_called: Arc<Mutex<bool>>,
114
+ }
115
+
116
+ impl Hook for TestHook {
117
+ fn name(&self) -> &str {
118
+ &self.name
119
+ }
120
+
121
+ fn before_tool(&self, _context: &ToolContext) -> Result<bool> {
122
+ let mut called = self.before_called.lock().unwrap();
123
+ *called = true;
124
+ Ok(true)
125
+ }
126
+
127
+ fn after_tool(&self, _context: &ToolContext, _result: &str) -> Result<()> {
128
+ let mut called = self.after_called.lock().unwrap();
129
+ *called = true;
130
+ Ok(())
131
+ }
132
+ }
133
+
134
+ #[test]
135
+ fn test_hooks_execution() {
136
+ let before = Arc::new(Mutex::new(false));
137
+ let after = Arc::new(Mutex::new(false));
138
+
139
+ let hook = TestHook {
140
+ name: "test".to_string(),
141
+ before_called: before.clone(),
142
+ after_called: after.clone(),
143
+ };
144
+
145
+ let mut manager = HookManager::new();
146
+ manager.register(Box::new(hook));
147
+
148
+ let args = serde_json::json!({});
149
+
150
+ assert!(manager.execute_before_tool("test_tool", &args).unwrap());
151
+ assert!(*before.lock().unwrap());
152
+
153
+ manager
154
+ .execute_after_tool("test_tool", &args, "result")
155
+ .unwrap();
156
+ assert!(*after.lock().unwrap());
157
+ }
158
+ }