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.
- code_puppy/agent.py +15 -17
- code_puppy/agents/agent_manager.py +320 -9
- code_puppy/agents/base_agent.py +58 -2
- code_puppy/agents/runtime_manager.py +68 -42
- code_puppy/command_line/command_handler.py +82 -33
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/add_command.py +183 -0
- code_puppy/command_line/mcp/base.py +35 -0
- code_puppy/command_line/mcp/handler.py +133 -0
- code_puppy/command_line/mcp/help_command.py +146 -0
- code_puppy/command_line/mcp/install_command.py +176 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +126 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +92 -0
- code_puppy/command_line/mcp/search_command.py +117 -0
- code_puppy/command_line/mcp/start_all_command.py +126 -0
- code_puppy/command_line/mcp/start_command.py +98 -0
- code_puppy/command_line/mcp/status_command.py +185 -0
- code_puppy/command_line/mcp/stop_all_command.py +109 -0
- code_puppy/command_line/mcp/stop_command.py +79 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +259 -0
- code_puppy/command_line/model_picker_completion.py +21 -4
- code_puppy/command_line/prompt_toolkit_completion.py +9 -0
- code_puppy/config.py +5 -5
- code_puppy/main.py +23 -17
- code_puppy/mcp/__init__.py +42 -16
- code_puppy/mcp/async_lifecycle.py +51 -49
- code_puppy/mcp/blocking_startup.py +125 -113
- code_puppy/mcp/captured_stdio_server.py +63 -70
- code_puppy/mcp/circuit_breaker.py +63 -47
- code_puppy/mcp/config_wizard.py +169 -136
- code_puppy/mcp/dashboard.py +79 -71
- code_puppy/mcp/error_isolation.py +147 -100
- code_puppy/mcp/examples/retry_example.py +55 -42
- code_puppy/mcp/health_monitor.py +152 -141
- code_puppy/mcp/managed_server.py +100 -93
- code_puppy/mcp/manager.py +168 -156
- code_puppy/mcp/registry.py +148 -110
- code_puppy/mcp/retry_manager.py +63 -61
- code_puppy/mcp/server_registry_catalog.py +271 -225
- code_puppy/mcp/status_tracker.py +80 -80
- code_puppy/mcp/system_tools.py +47 -52
- code_puppy/messaging/message_queue.py +20 -13
- code_puppy/messaging/renderers.py +30 -15
- code_puppy/state_management.py +103 -0
- code_puppy/tui/app.py +64 -7
- code_puppy/tui/components/chat_view.py +3 -3
- code_puppy/tui/components/human_input_modal.py +12 -8
- code_puppy/tui/screens/__init__.py +2 -2
- code_puppy/tui/screens/mcp_install_wizard.py +208 -179
- code_puppy/tui/tests/test_agent_command.py +3 -3
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/METADATA +1 -1
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/RECORD +60 -42
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.135.data → code_puppy-0.0.137.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/entry_points.txt +0 -0
- {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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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())
|