code-puppy 0.0.135__py3-none-any.whl → 0.0.137__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.
Files changed (61) hide show
  1. code_puppy/agent.py +15 -17
  2. code_puppy/agents/agent_manager.py +320 -9
  3. code_puppy/agents/base_agent.py +58 -2
  4. code_puppy/agents/runtime_manager.py +68 -42
  5. code_puppy/command_line/command_handler.py +82 -33
  6. code_puppy/command_line/mcp/__init__.py +10 -0
  7. code_puppy/command_line/mcp/add_command.py +183 -0
  8. code_puppy/command_line/mcp/base.py +35 -0
  9. code_puppy/command_line/mcp/handler.py +133 -0
  10. code_puppy/command_line/mcp/help_command.py +146 -0
  11. code_puppy/command_line/mcp/install_command.py +176 -0
  12. code_puppy/command_line/mcp/list_command.py +94 -0
  13. code_puppy/command_line/mcp/logs_command.py +126 -0
  14. code_puppy/command_line/mcp/remove_command.py +82 -0
  15. code_puppy/command_line/mcp/restart_command.py +92 -0
  16. code_puppy/command_line/mcp/search_command.py +117 -0
  17. code_puppy/command_line/mcp/start_all_command.py +126 -0
  18. code_puppy/command_line/mcp/start_command.py +98 -0
  19. code_puppy/command_line/mcp/status_command.py +185 -0
  20. code_puppy/command_line/mcp/stop_all_command.py +109 -0
  21. code_puppy/command_line/mcp/stop_command.py +79 -0
  22. code_puppy/command_line/mcp/test_command.py +107 -0
  23. code_puppy/command_line/mcp/utils.py +129 -0
  24. code_puppy/command_line/mcp/wizard_utils.py +259 -0
  25. code_puppy/command_line/model_picker_completion.py +21 -4
  26. code_puppy/command_line/prompt_toolkit_completion.py +9 -0
  27. code_puppy/config.py +5 -5
  28. code_puppy/main.py +23 -17
  29. code_puppy/mcp/__init__.py +42 -16
  30. code_puppy/mcp/async_lifecycle.py +51 -49
  31. code_puppy/mcp/blocking_startup.py +125 -113
  32. code_puppy/mcp/captured_stdio_server.py +63 -70
  33. code_puppy/mcp/circuit_breaker.py +63 -47
  34. code_puppy/mcp/config_wizard.py +169 -136
  35. code_puppy/mcp/dashboard.py +79 -71
  36. code_puppy/mcp/error_isolation.py +147 -100
  37. code_puppy/mcp/examples/retry_example.py +55 -42
  38. code_puppy/mcp/health_monitor.py +152 -141
  39. code_puppy/mcp/managed_server.py +100 -93
  40. code_puppy/mcp/manager.py +168 -156
  41. code_puppy/mcp/registry.py +148 -110
  42. code_puppy/mcp/retry_manager.py +63 -61
  43. code_puppy/mcp/server_registry_catalog.py +271 -225
  44. code_puppy/mcp/status_tracker.py +80 -80
  45. code_puppy/mcp/system_tools.py +47 -52
  46. code_puppy/messaging/message_queue.py +20 -13
  47. code_puppy/messaging/renderers.py +30 -15
  48. code_puppy/state_management.py +103 -0
  49. code_puppy/tui/app.py +64 -7
  50. code_puppy/tui/components/chat_view.py +3 -3
  51. code_puppy/tui/components/human_input_modal.py +12 -8
  52. code_puppy/tui/screens/__init__.py +2 -2
  53. code_puppy/tui/screens/mcp_install_wizard.py +208 -179
  54. code_puppy/tui/tests/test_agent_command.py +3 -3
  55. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/METADATA +1 -1
  56. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/RECORD +60 -42
  57. code_puppy/command_line/mcp_commands.py +0 -1789
  58. {code_puppy-0.0.135.data → code_puppy-0.0.137.data}/data/code_puppy/models.json +0 -0
  59. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/WHEEL +0 -0
  60. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/entry_points.txt +0 -0
  61. {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/licenses/LICENSE +0 -0
@@ -7,6 +7,7 @@ to handle transient failures gracefully with intelligent backoff strategies.
7
7
  """
8
8
 
9
9
  import asyncio
10
+ import logging
10
11
  import random
11
12
  import sys
12
13
  from pathlib import Path
@@ -16,103 +17,113 @@ from typing import Any
16
17
  project_root = Path(__file__).parents[3]
17
18
  sys.path.insert(0, str(project_root))
18
19
 
19
- from code_puppy.mcp.retry_manager import get_retry_manager, retry_mcp_call
20
+ from code_puppy.mcp.retry_manager import get_retry_manager, retry_mcp_call # noqa: E402
21
+
22
+ logger = logging.getLogger(__name__)
20
23
 
21
24
 
22
25
  class MockMCPServer:
23
26
  """Mock MCP server for demonstration purposes."""
24
-
27
+
25
28
  def __init__(self, failure_rate: float = 0.3):
26
29
  """
27
30
  Initialize the mock server.
28
-
31
+
29
32
  Args:
30
33
  failure_rate: Probability of failure (0.0 to 1.0)
31
34
  """
32
35
  self.failure_rate = failure_rate
33
36
  self.call_count = 0
34
-
37
+
35
38
  async def list_tools(self) -> list:
36
39
  """Simulate listing available tools."""
37
40
  self.call_count += 1
38
-
41
+
39
42
  # Simulate random failures
40
43
  if random.random() < self.failure_rate:
41
- raise ConnectionError(f"Simulated connection failure (call #{self.call_count})")
42
-
44
+ raise ConnectionError(
45
+ f"Simulated connection failure (call #{self.call_count})"
46
+ )
47
+
43
48
  return [
44
49
  {"name": "read_file", "description": "Read a file"},
45
50
  {"name": "write_file", "description": "Write a file"},
46
- {"name": "list_directory", "description": "List directory contents"}
51
+ {"name": "list_directory", "description": "List directory contents"},
47
52
  ]
48
-
53
+
49
54
  async def call_tool(self, name: str, args: dict) -> Any:
50
55
  """Simulate calling a tool."""
51
56
  self.call_count += 1
52
-
57
+
53
58
  # Simulate random failures
54
59
  if random.random() < self.failure_rate:
55
60
  if random.random() < 0.5:
56
61
  raise ConnectionError(f"Connection failed for {name}")
57
62
  else:
58
63
  # Simulate a 500 error
59
- import httpx
60
64
  from unittest.mock import Mock
65
+
66
+ import httpx
67
+
61
68
  response = Mock()
62
69
  response.status_code = 500
63
- raise httpx.HTTPStatusError("Server Error", request=Mock(), response=response)
64
-
70
+ raise httpx.HTTPStatusError(
71
+ "Server Error", request=Mock(), response=response
72
+ )
73
+
65
74
  return f"Tool '{name}' executed with args: {args}"
66
75
 
67
76
 
68
77
  async def demonstrate_basic_retry():
69
78
  """Demonstrate basic retry functionality."""
70
79
  print("=== Basic Retry Demonstration ===")
71
-
80
+
72
81
  retry_manager = get_retry_manager()
73
82
  server = MockMCPServer(failure_rate=0.5) # 50% failure rate
74
-
83
+
75
84
  async def list_tools_call():
76
85
  return await server.list_tools()
77
-
86
+
78
87
  try:
79
88
  result = await retry_manager.retry_with_backoff(
80
89
  func=list_tools_call,
81
90
  max_attempts=3,
82
91
  strategy="exponential",
83
- server_id="demo-server"
92
+ server_id="demo-server",
84
93
  )
85
94
  print(f"✅ Success: Retrieved {len(result)} tools")
86
95
  print(f"Server call count: {server.call_count}")
87
96
  except Exception as e:
88
97
  print(f"❌ Failed after retries: {e}")
89
-
98
+
90
99
  # Check retry stats
91
100
  stats = await retry_manager.get_retry_stats("demo-server")
92
- print(f"Retry stats: total={stats.total_retries}, successful={stats.successful_retries}")
101
+ print(
102
+ f"Retry stats: total={stats.total_retries}, successful={stats.successful_retries}"
103
+ )
93
104
  print()
94
105
 
95
106
 
96
107
  async def demonstrate_different_strategies():
97
108
  """Demonstrate different backoff strategies."""
98
109
  print("=== Backoff Strategies Demonstration ===")
99
-
110
+
100
111
  strategies = ["fixed", "linear", "exponential", "exponential_jitter"]
101
-
112
+
102
113
  for strategy in strategies:
103
114
  print(f"\n{strategy.upper()} strategy:")
104
115
  server = MockMCPServer(failure_rate=0.7) # High failure rate
105
-
116
+
106
117
  try:
107
118
  start_time = asyncio.get_event_loop().time()
108
-
119
+
109
120
  result = await retry_mcp_call(
110
121
  func=lambda: server.call_tool("read_file", {"path": "/example.txt"}),
111
122
  server_id=f"server-{strategy}",
112
123
  max_attempts=3,
113
- strategy=strategy
124
+ strategy=strategy,
114
125
  )
115
-
126
+
116
127
  end_time = asyncio.get_event_loop().time()
117
128
  print(f" ✅ Success: {result}")
118
129
  print(f" Time taken: {end_time - start_time:.2f}s")
@@ -127,51 +138,53 @@ async def demonstrate_different_strategies():
127
138
  async def demonstrate_concurrent_retries():
128
139
  """Demonstrate concurrent retry operations."""
129
140
  print("\n=== Concurrent Retries Demonstration ===")
130
-
141
+
131
142
  retry_manager = get_retry_manager()
132
-
143
+
133
144
  # Create multiple servers with different failure rates
134
145
  servers = [
135
146
  ("reliable-server", MockMCPServer(failure_rate=0.1)),
136
147
  ("unreliable-server", MockMCPServer(failure_rate=0.8)),
137
- ("moderate-server", MockMCPServer(failure_rate=0.4))
148
+ ("moderate-server", MockMCPServer(failure_rate=0.4)),
138
149
  ]
139
-
150
+
140
151
  async def make_call(server_name: str, server: MockMCPServer):
141
152
  """Make a call with retry handling."""
142
153
  try:
143
- result = await retry_manager.retry_with_backoff(
154
+ await retry_manager.retry_with_backoff(
144
155
  func=lambda: server.list_tools(),
145
156
  max_attempts=3,
146
157
  strategy="exponential_jitter",
147
- server_id=server_name
158
+ server_id=server_name,
148
159
  )
149
160
  return f"{server_name}: Success (calls: {server.call_count})"
150
161
  except Exception as e:
151
162
  return f"{server_name}: Failed - {e} (calls: {server.call_count})"
152
-
163
+
153
164
  # Run concurrent calls
154
165
  tasks = [make_call(name, server) for name, server in servers]
155
166
  results = await asyncio.gather(*tasks)
156
-
167
+
157
168
  print("Concurrent results:")
158
169
  for result in results:
159
170
  print(f" {result}")
160
-
171
+
161
172
  # Show overall stats
162
173
  print("\nOverall retry statistics:")
163
174
  all_stats = await retry_manager.get_all_stats()
164
175
  for server_id, stats in all_stats.items():
165
176
  success_rate = (stats.successful_retries / max(stats.total_retries, 1)) * 100
166
- print(f" {server_id}: {stats.total_retries} retries, {success_rate:.1f}% success rate")
177
+ print(
178
+ f" {server_id}: {stats.total_retries} retries, {success_rate:.1f}% success rate"
179
+ )
167
180
 
168
181
 
169
182
  async def demonstrate_error_classification():
170
183
  """Demonstrate error classification for retry decisions."""
171
184
  print("\n=== Error Classification Demonstration ===")
172
-
185
+
173
186
  retry_manager = get_retry_manager()
174
-
187
+
175
188
  # Test different error types
176
189
  test_errors = [
177
190
  ConnectionError("Network connection failed"),
@@ -179,9 +192,9 @@ async def demonstrate_error_classification():
179
192
  ValueError("JSON decode error: invalid format"),
180
193
  ValueError("Schema validation failed"),
181
194
  Exception("Authentication failed"),
182
- Exception("Permission denied")
195
+ Exception("Permission denied"),
183
196
  ]
184
-
197
+
185
198
  print("Error retry decisions:")
186
199
  for error in test_errors:
187
200
  should_retry = retry_manager.should_retry(error)
@@ -193,16 +206,16 @@ async def main():
193
206
  """Run all demonstrations."""
194
207
  print("RetryManager Example Demonstrations")
195
208
  print("=" * 50)
196
-
209
+
197
210
  await demonstrate_basic_retry()
198
211
  await demonstrate_different_strategies()
199
212
  await demonstrate_concurrent_retries()
200
213
  await demonstrate_error_classification()
201
-
214
+
202
215
  print("\n🎉 All demonstrations completed!")
203
216
 
204
217
 
205
218
  if __name__ == "__main__":
206
219
  # Set a seed for reproducible results in the demo
207
220
  random.seed(42)
208
- asyncio.run(main())
221
+ asyncio.run(main())