connectonion 0.5.8__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.
- connectonion/__init__.py +78 -0
- connectonion/address.py +320 -0
- connectonion/agent.py +450 -0
- connectonion/announce.py +84 -0
- connectonion/asgi.py +287 -0
- connectonion/auto_debug_exception.py +181 -0
- connectonion/cli/__init__.py +3 -0
- connectonion/cli/browser_agent/__init__.py +5 -0
- connectonion/cli/browser_agent/browser.py +243 -0
- connectonion/cli/browser_agent/prompt.md +107 -0
- connectonion/cli/commands/__init__.py +1 -0
- connectonion/cli/commands/auth_commands.py +527 -0
- connectonion/cli/commands/browser_commands.py +27 -0
- connectonion/cli/commands/create.py +511 -0
- connectonion/cli/commands/deploy_commands.py +220 -0
- connectonion/cli/commands/doctor_commands.py +173 -0
- connectonion/cli/commands/init.py +469 -0
- connectonion/cli/commands/project_cmd_lib.py +828 -0
- connectonion/cli/commands/reset_commands.py +149 -0
- connectonion/cli/commands/status_commands.py +168 -0
- connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
- connectonion/cli/docs/connectonion.md +1256 -0
- connectonion/cli/docs.md +123 -0
- connectonion/cli/main.py +148 -0
- connectonion/cli/templates/meta-agent/README.md +287 -0
- connectonion/cli/templates/meta-agent/agent.py +196 -0
- connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
- connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
- connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
- connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
- connectonion/cli/templates/minimal/README.md +56 -0
- connectonion/cli/templates/minimal/agent.py +40 -0
- connectonion/cli/templates/playwright/README.md +118 -0
- connectonion/cli/templates/playwright/agent.py +336 -0
- connectonion/cli/templates/playwright/prompt.md +102 -0
- connectonion/cli/templates/playwright/requirements.txt +3 -0
- connectonion/cli/templates/web-research/agent.py +122 -0
- connectonion/connect.py +128 -0
- connectonion/console.py +539 -0
- connectonion/debug_agent/__init__.py +13 -0
- connectonion/debug_agent/agent.py +45 -0
- connectonion/debug_agent/prompts/debug_assistant.md +72 -0
- connectonion/debug_agent/runtime_inspector.py +406 -0
- connectonion/debug_explainer/__init__.py +10 -0
- connectonion/debug_explainer/explain_agent.py +114 -0
- connectonion/debug_explainer/explain_context.py +263 -0
- connectonion/debug_explainer/explainer_prompt.md +29 -0
- connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
- connectonion/debugger_ui.py +1039 -0
- connectonion/decorators.py +208 -0
- connectonion/events.py +248 -0
- connectonion/execution_analyzer/__init__.py +9 -0
- connectonion/execution_analyzer/execution_analysis.py +93 -0
- connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
- connectonion/host.py +579 -0
- connectonion/interactive_debugger.py +342 -0
- connectonion/llm.py +801 -0
- connectonion/llm_do.py +307 -0
- connectonion/logger.py +300 -0
- connectonion/prompt_files/__init__.py +1 -0
- connectonion/prompt_files/analyze_contact.md +62 -0
- connectonion/prompt_files/eval_expected.md +12 -0
- connectonion/prompt_files/react_evaluate.md +11 -0
- connectonion/prompt_files/react_plan.md +16 -0
- connectonion/prompt_files/reflect.md +22 -0
- connectonion/prompts.py +144 -0
- connectonion/relay.py +200 -0
- connectonion/static/docs.html +688 -0
- connectonion/tool_executor.py +279 -0
- connectonion/tool_factory.py +186 -0
- connectonion/tool_registry.py +105 -0
- connectonion/trust.py +166 -0
- connectonion/trust_agents.py +71 -0
- connectonion/trust_functions.py +88 -0
- connectonion/tui/__init__.py +57 -0
- connectonion/tui/divider.py +39 -0
- connectonion/tui/dropdown.py +251 -0
- connectonion/tui/footer.py +31 -0
- connectonion/tui/fuzzy.py +56 -0
- connectonion/tui/input.py +278 -0
- connectonion/tui/keys.py +35 -0
- connectonion/tui/pick.py +130 -0
- connectonion/tui/providers.py +155 -0
- connectonion/tui/status_bar.py +163 -0
- connectonion/usage.py +161 -0
- connectonion/useful_events_handlers/__init__.py +16 -0
- connectonion/useful_events_handlers/reflect.py +116 -0
- connectonion/useful_plugins/__init__.py +20 -0
- connectonion/useful_plugins/calendar_plugin.py +163 -0
- connectonion/useful_plugins/eval.py +139 -0
- connectonion/useful_plugins/gmail_plugin.py +162 -0
- connectonion/useful_plugins/image_result_formatter.py +127 -0
- connectonion/useful_plugins/re_act.py +78 -0
- connectonion/useful_plugins/shell_approval.py +159 -0
- connectonion/useful_tools/__init__.py +44 -0
- connectonion/useful_tools/diff_writer.py +192 -0
- connectonion/useful_tools/get_emails.py +183 -0
- connectonion/useful_tools/gmail.py +1596 -0
- connectonion/useful_tools/google_calendar.py +613 -0
- connectonion/useful_tools/memory.py +380 -0
- connectonion/useful_tools/microsoft_calendar.py +604 -0
- connectonion/useful_tools/outlook.py +488 -0
- connectonion/useful_tools/send_email.py +205 -0
- connectonion/useful_tools/shell.py +97 -0
- connectonion/useful_tools/slash_command.py +201 -0
- connectonion/useful_tools/terminal.py +285 -0
- connectonion/useful_tools/todo_list.py +241 -0
- connectonion/useful_tools/web_fetch.py +216 -0
- connectonion/xray.py +467 -0
- connectonion-0.5.8.dist-info/METADATA +741 -0
- connectonion-0.5.8.dist-info/RECORD +113 -0
- connectonion-0.5.8.dist-info/WHEEL +4 -0
- connectonion-0.5.8.dist-info/entry_points.txt +3 -0
connectonion/asgi.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Raw ASGI utilities for HTTP/WebSocket handling.
|
|
2
|
+
|
|
3
|
+
This module contains the protocol-level code for handling HTTP and WebSocket
|
|
4
|
+
requests. Separated from host.py for better testing and smaller file size.
|
|
5
|
+
|
|
6
|
+
Design decision: Raw ASGI instead of Starlette/FastAPI for full protocol control.
|
|
7
|
+
See: docs/design-decisions/022-raw-asgi-implementation.md
|
|
8
|
+
"""
|
|
9
|
+
import hmac
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _json_default(obj):
|
|
18
|
+
"""Handle non-serializable objects like Pydantic models.
|
|
19
|
+
|
|
20
|
+
This enables native JSON serialization for Pydantic BaseModel instances
|
|
21
|
+
nested in API response dicts, following FastAPI's pattern.
|
|
22
|
+
"""
|
|
23
|
+
if isinstance(obj, BaseModel):
|
|
24
|
+
return obj.model_dump()
|
|
25
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def read_body(receive) -> bytes:
|
|
29
|
+
"""Read complete request body from ASGI receive."""
|
|
30
|
+
body = b""
|
|
31
|
+
while True:
|
|
32
|
+
m = await receive()
|
|
33
|
+
body += m.get("body", b"")
|
|
34
|
+
if not m.get("more_body"):
|
|
35
|
+
break
|
|
36
|
+
return body
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# CORS headers for cross-origin requests (e.g., frontend at o.openonion.ai
|
|
40
|
+
# calling deployed agents at *.agents.openonion.ai)
|
|
41
|
+
CORS_HEADERS = [
|
|
42
|
+
[b"access-control-allow-origin", b"*"],
|
|
43
|
+
[b"access-control-allow-methods", b"GET, POST, OPTIONS"],
|
|
44
|
+
[b"access-control-allow-headers", b"authorization, content-type"],
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def send_json(send, data: dict, status: int = 200):
|
|
49
|
+
"""Send JSON response via ASGI send."""
|
|
50
|
+
body = json.dumps(data, default=_json_default).encode()
|
|
51
|
+
headers = [[b"content-type", b"application/json"]] + CORS_HEADERS
|
|
52
|
+
await send({"type": "http.response.start", "status": status, "headers": headers})
|
|
53
|
+
await send({"type": "http.response.body", "body": body})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def send_html(send, html: bytes, status: int = 200):
|
|
57
|
+
"""Send HTML response via ASGI send."""
|
|
58
|
+
await send({
|
|
59
|
+
"type": "http.response.start",
|
|
60
|
+
"status": status,
|
|
61
|
+
"headers": [[b"content-type", b"text/html; charset=utf-8"]],
|
|
62
|
+
})
|
|
63
|
+
await send({"type": "http.response.body", "body": html})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def send_text(send, text: str, status: int = 200):
|
|
67
|
+
"""Send plain text response via ASGI send."""
|
|
68
|
+
headers = [[b"content-type", b"text/plain; charset=utf-8"]] + CORS_HEADERS
|
|
69
|
+
await send({"type": "http.response.start", "status": status, "headers": headers})
|
|
70
|
+
await send({"type": "http.response.body", "body": text.encode()})
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def handle_http(
|
|
74
|
+
scope,
|
|
75
|
+
receive,
|
|
76
|
+
send,
|
|
77
|
+
*,
|
|
78
|
+
handlers: dict,
|
|
79
|
+
storage,
|
|
80
|
+
trust: str,
|
|
81
|
+
result_ttl: int,
|
|
82
|
+
start_time: float,
|
|
83
|
+
blacklist: list | None = None,
|
|
84
|
+
whitelist: list | None = None,
|
|
85
|
+
):
|
|
86
|
+
"""Route HTTP requests to handlers.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
scope: ASGI scope dict (method, path, headers, etc.)
|
|
90
|
+
receive: ASGI receive callable
|
|
91
|
+
send: ASGI send callable
|
|
92
|
+
handlers: Dict of handler functions (input, session, sessions, health, info, auth)
|
|
93
|
+
storage: SessionStorage instance
|
|
94
|
+
trust: Trust level (open/careful/strict)
|
|
95
|
+
result_ttl: How long to keep results in seconds
|
|
96
|
+
start_time: Server start time
|
|
97
|
+
blacklist: Blocked identities
|
|
98
|
+
whitelist: Allowed identities
|
|
99
|
+
"""
|
|
100
|
+
method, path = scope["method"], scope["path"]
|
|
101
|
+
|
|
102
|
+
# Handle CORS preflight requests
|
|
103
|
+
if method == "OPTIONS":
|
|
104
|
+
headers = CORS_HEADERS + [[b"content-length", b"0"]]
|
|
105
|
+
await send({"type": "http.response.start", "status": 204, "headers": headers})
|
|
106
|
+
await send({"type": "http.response.body", "body": b""})
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Admin endpoints require API key auth
|
|
110
|
+
if path.startswith("/admin"):
|
|
111
|
+
headers = dict(scope.get("headers", []))
|
|
112
|
+
auth = headers.get(b"authorization", b"").decode()
|
|
113
|
+
expected = os.environ.get("OPENONION_API_KEY", "")
|
|
114
|
+
if not expected or not auth.startswith("Bearer ") or not hmac.compare_digest(auth[7:], expected):
|
|
115
|
+
await send_json(send, {"error": "unauthorized"}, 401)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
if method == "GET" and path == "/admin/logs":
|
|
119
|
+
result = handlers["admin_logs"]()
|
|
120
|
+
if "error" in result:
|
|
121
|
+
await send_json(send, result, 404)
|
|
122
|
+
else:
|
|
123
|
+
await send_text(send, result["content"])
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
if method == "GET" and path == "/admin/sessions":
|
|
127
|
+
await send_json(send, handlers["admin_sessions"]())
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
await send_json(send, {"error": "not found"}, 404)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if method == "POST" and path == "/input":
|
|
134
|
+
body = await read_body(receive)
|
|
135
|
+
try:
|
|
136
|
+
data = json.loads(body) if body else {}
|
|
137
|
+
except json.JSONDecodeError:
|
|
138
|
+
await send_json(send, {"error": "Invalid JSON"}, 400)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
prompt, identity, sig_valid, err = handlers["auth"](
|
|
142
|
+
data, trust, blacklist=blacklist, whitelist=whitelist
|
|
143
|
+
)
|
|
144
|
+
if err:
|
|
145
|
+
status = 401 if err.startswith("unauthorized") else 403 if err.startswith("forbidden") else 400
|
|
146
|
+
await send_json(send, {"error": err}, status)
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Extract session for conversation continuation
|
|
150
|
+
session = data.get("session")
|
|
151
|
+
result = handlers["input"](storage, prompt, result_ttl, session)
|
|
152
|
+
await send_json(send, result)
|
|
153
|
+
|
|
154
|
+
elif method == "GET" and path.startswith("/sessions/"):
|
|
155
|
+
result = handlers["session"](storage, path[10:])
|
|
156
|
+
await send_json(send, result or {"error": "not found"}, 404 if not result else 200)
|
|
157
|
+
|
|
158
|
+
elif method == "GET" and path == "/sessions":
|
|
159
|
+
await send_json(send, handlers["sessions"](storage))
|
|
160
|
+
|
|
161
|
+
elif method == "GET" and path == "/health":
|
|
162
|
+
await send_json(send, handlers["health"](start_time))
|
|
163
|
+
|
|
164
|
+
elif method == "GET" and path == "/info":
|
|
165
|
+
await send_json(send, handlers["info"](trust))
|
|
166
|
+
|
|
167
|
+
elif method == "GET" and path == "/docs":
|
|
168
|
+
# Serve static docs page
|
|
169
|
+
try:
|
|
170
|
+
base = Path(__file__).resolve().parent
|
|
171
|
+
html_path = base / "static" / "docs.html"
|
|
172
|
+
html = html_path.read_bytes()
|
|
173
|
+
except Exception:
|
|
174
|
+
html = b"<html><body><h1>ConnectOnion Docs</h1><p>Docs not found.</p></body></html>"
|
|
175
|
+
await send_html(send, html)
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
await send_json(send, {"error": "not found"}, 404)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def handle_websocket(
|
|
182
|
+
scope,
|
|
183
|
+
receive,
|
|
184
|
+
send,
|
|
185
|
+
*,
|
|
186
|
+
handlers: dict,
|
|
187
|
+
trust: str,
|
|
188
|
+
blacklist: list | None = None,
|
|
189
|
+
whitelist: list | None = None,
|
|
190
|
+
):
|
|
191
|
+
"""Handle WebSocket connections at /ws.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
scope: ASGI scope dict
|
|
195
|
+
receive: ASGI receive callable
|
|
196
|
+
send: ASGI send callable
|
|
197
|
+
handlers: Dict with 'ws_input' and 'auth' handlers
|
|
198
|
+
trust: Trust level
|
|
199
|
+
blacklist: Blocked identities
|
|
200
|
+
whitelist: Allowed identities
|
|
201
|
+
"""
|
|
202
|
+
if scope["path"] != "/ws":
|
|
203
|
+
await send({"type": "websocket.close", "code": 4004})
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
await send({"type": "websocket.accept"})
|
|
207
|
+
|
|
208
|
+
while True:
|
|
209
|
+
msg = await receive()
|
|
210
|
+
if msg["type"] == "websocket.disconnect":
|
|
211
|
+
break
|
|
212
|
+
if msg["type"] == "websocket.receive":
|
|
213
|
+
try:
|
|
214
|
+
data = json.loads(msg.get("text", "{}"))
|
|
215
|
+
except json.JSONDecodeError:
|
|
216
|
+
await send({"type": "websocket.send",
|
|
217
|
+
"text": json.dumps({"type": "ERROR", "message": "Invalid JSON"})})
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
if data.get("type") == "INPUT":
|
|
221
|
+
prompt, identity, sig_valid, err = handlers["auth"](
|
|
222
|
+
data, trust, blacklist=blacklist, whitelist=whitelist
|
|
223
|
+
)
|
|
224
|
+
if err:
|
|
225
|
+
await send({"type": "websocket.send",
|
|
226
|
+
"text": json.dumps({"type": "ERROR", "message": err})})
|
|
227
|
+
continue
|
|
228
|
+
if not prompt:
|
|
229
|
+
await send({"type": "websocket.send",
|
|
230
|
+
"text": json.dumps({"type": "ERROR", "message": "prompt required"})})
|
|
231
|
+
continue
|
|
232
|
+
result = handlers["ws_input"](prompt)
|
|
233
|
+
await send({"type": "websocket.send",
|
|
234
|
+
"text": json.dumps({"type": "OUTPUT", "result": result})})
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def create_app(
|
|
238
|
+
*,
|
|
239
|
+
handlers: dict,
|
|
240
|
+
storage,
|
|
241
|
+
trust: str = "careful",
|
|
242
|
+
result_ttl: int = 86400,
|
|
243
|
+
blacklist: list | None = None,
|
|
244
|
+
whitelist: list | None = None,
|
|
245
|
+
):
|
|
246
|
+
"""Create ASGI application.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
handlers: Dict of handler functions
|
|
250
|
+
storage: SessionStorage instance
|
|
251
|
+
trust: Trust level (open/careful/strict)
|
|
252
|
+
result_ttl: How long to keep results in seconds
|
|
253
|
+
blacklist: Blocked identities
|
|
254
|
+
whitelist: Allowed identities
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
ASGI application callable
|
|
258
|
+
"""
|
|
259
|
+
import time
|
|
260
|
+
start_time = time.time()
|
|
261
|
+
|
|
262
|
+
async def app(scope, receive, send):
|
|
263
|
+
if scope["type"] == "http":
|
|
264
|
+
await handle_http(
|
|
265
|
+
scope,
|
|
266
|
+
receive,
|
|
267
|
+
send,
|
|
268
|
+
handlers=handlers,
|
|
269
|
+
storage=storage,
|
|
270
|
+
trust=trust,
|
|
271
|
+
result_ttl=result_ttl,
|
|
272
|
+
start_time=start_time,
|
|
273
|
+
blacklist=blacklist,
|
|
274
|
+
whitelist=whitelist,
|
|
275
|
+
)
|
|
276
|
+
elif scope["type"] == "websocket":
|
|
277
|
+
await handle_websocket(
|
|
278
|
+
scope,
|
|
279
|
+
receive,
|
|
280
|
+
send,
|
|
281
|
+
handlers=handlers,
|
|
282
|
+
trust=trust,
|
|
283
|
+
blacklist=blacklist,
|
|
284
|
+
whitelist=whitelist,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return app
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Automatically analyze uncaught Python exceptions using AI with runtime frame inspection
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [sys, traceback, os, dotenv, console.py, debug_agent/__init__.py] | imported by [__init__.py] | tested by [tests/test_auto_debug_exception.py]
|
|
5
|
+
Data flow: auto_debug_exception() → installs sys.excepthook → on exception: calls original_hook (shows traceback) → finds relevant frame (last user code, not library) → extracts frame.f_locals → creates debug_agent with actual frame and traceback → sends prompt with exception details → agent uses runtime inspection tools (explore_namespace, execute_in_frame, inspect_object, validate_assumption, test_fix) → displays AI analysis
|
|
6
|
+
State/Effects: modifies sys.excepthook globally | loads .env for CONNECTONION_AUTO_DEBUG | creates debug Agent instances on exceptions | calls console.print() to display analysis | does not prevent exception from terminating program
|
|
7
|
+
Integration: exposes auto_debug_exception(model) | checks CONNECTONION_AUTO_DEBUG=false env to disable | creates debug_agent with frame, exception_traceback, model parameters | prompt guides AI to use runtime inspection tools
|
|
8
|
+
Performance: only activates on exceptions (zero overhead in normal execution) | debug agent creates LLM instance per exception | runtime inspection executes code in crashed frame
|
|
9
|
+
Errors: wraps AI analysis in try/except to avoid cascading failures | shows "AI analysis failed" message if agent crashes | handles missing frames gracefully | skips analysis if no relevant frame found
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import traceback
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def auto_debug_exception(model: str = "o4-mini"):
|
|
18
|
+
"""Enable AI debugging for uncaught exceptions ONLY.
|
|
19
|
+
|
|
20
|
+
Debugs crashes, raised exceptions, and failed assertions. Does NOT debug
|
|
21
|
+
logic errors, wrong outputs, or performance issues unless you convert them
|
|
22
|
+
to exceptions using raise/assert.
|
|
23
|
+
|
|
24
|
+
What gets debugged:
|
|
25
|
+
✅ Crashes: KeyError, TypeError, ZeroDivisionError, etc.
|
|
26
|
+
✅ Raised exceptions: raise ValueError("invalid input")
|
|
27
|
+
✅ Failed assertions: assert x > 0, "must be positive"
|
|
28
|
+
❌ Logic errors that don't raise exceptions
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
model: AI model to use (default: o4-mini for speed)
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
from connectonion import auto_debug_exception
|
|
35
|
+
|
|
36
|
+
# Enable AI debugging for exceptions
|
|
37
|
+
auto_debug_exception()
|
|
38
|
+
|
|
39
|
+
# Any uncaught exception gets AI analysis with runtime data
|
|
40
|
+
data = {"items": []}
|
|
41
|
+
avg = sum(data["items"]) / len(data["items"]) # ZeroDivisionError!
|
|
42
|
+
|
|
43
|
+
Environment:
|
|
44
|
+
Set CONNECTONION_AUTO_DEBUG=false to disable even when called.
|
|
45
|
+
"""
|
|
46
|
+
# Check if explicitly disabled via environment
|
|
47
|
+
if os.environ.get('CONNECTONION_AUTO_DEBUG', '').lower() == 'false':
|
|
48
|
+
return # User explicitly disabled it
|
|
49
|
+
|
|
50
|
+
# Save original hook for use in our handler
|
|
51
|
+
original_hook = sys.excepthook
|
|
52
|
+
|
|
53
|
+
def handle_exception(exc_type, exc_value, exception_traceback):
|
|
54
|
+
"""Handle an uncaught exception with AI runtime analysis."""
|
|
55
|
+
# First call original hook to show normal traceback
|
|
56
|
+
# (This ensures compatibility with other tools)
|
|
57
|
+
original_hook(exc_type, exc_value, exception_traceback)
|
|
58
|
+
|
|
59
|
+
# Then add our AI analysis
|
|
60
|
+
from .console import Console
|
|
61
|
+
console = Console()
|
|
62
|
+
|
|
63
|
+
# Find the most relevant frame (last user code, not library)
|
|
64
|
+
relevant_frame_info = None
|
|
65
|
+
actual_frame = None
|
|
66
|
+
actual_traceback = None
|
|
67
|
+
current_traceback = exception_traceback
|
|
68
|
+
|
|
69
|
+
while current_traceback:
|
|
70
|
+
frame = current_traceback.tb_frame
|
|
71
|
+
filename = frame.f_code.co_filename
|
|
72
|
+
|
|
73
|
+
# Skip system/library files
|
|
74
|
+
if not filename.startswith('<') and 'site-packages' not in filename:
|
|
75
|
+
relevant_frame_info = {
|
|
76
|
+
'file': filename,
|
|
77
|
+
'line': current_traceback.tb_lineno,
|
|
78
|
+
'function': frame.f_code.co_name,
|
|
79
|
+
'locals': {}
|
|
80
|
+
}
|
|
81
|
+
# Keep the actual frame and traceback for runtime inspection
|
|
82
|
+
actual_frame = frame
|
|
83
|
+
actual_traceback = exception_traceback # Keep the original traceback
|
|
84
|
+
|
|
85
|
+
# Capture some local variables (simple types only) for display
|
|
86
|
+
for name, value in frame.f_locals.items():
|
|
87
|
+
# Skip private/internal variables
|
|
88
|
+
if name.startswith('_'):
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Only capture simple types to avoid huge dumps
|
|
92
|
+
if isinstance(value, (str, int, float, bool, type(None))):
|
|
93
|
+
relevant_frame_info['locals'][name] = value
|
|
94
|
+
elif isinstance(value, (list, dict, tuple)):
|
|
95
|
+
# Just show type and size
|
|
96
|
+
relevant_frame_info['locals'][name] = f"{type(value).__name__}({len(value)})"
|
|
97
|
+
else:
|
|
98
|
+
# Just show type
|
|
99
|
+
relevant_frame_info['locals'][name] = type(value).__name__
|
|
100
|
+
|
|
101
|
+
current_traceback = current_traceback.tb_next
|
|
102
|
+
|
|
103
|
+
# If no user code found, use the last frame
|
|
104
|
+
if not relevant_frame_info and exception_traceback:
|
|
105
|
+
last_traceback = exception_traceback
|
|
106
|
+
while last_traceback.tb_next:
|
|
107
|
+
last_traceback = last_traceback.tb_next
|
|
108
|
+
|
|
109
|
+
frame = last_traceback.tb_frame
|
|
110
|
+
relevant_frame_info = {
|
|
111
|
+
'file': frame.f_code.co_filename,
|
|
112
|
+
'line': last_traceback.tb_lineno,
|
|
113
|
+
'function': frame.f_code.co_name,
|
|
114
|
+
'locals': {}
|
|
115
|
+
}
|
|
116
|
+
actual_frame = frame
|
|
117
|
+
actual_traceback = exception_traceback
|
|
118
|
+
|
|
119
|
+
# Skip if no relevant frame
|
|
120
|
+
if not relevant_frame_info:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# Run AI analysis with runtime inspection
|
|
124
|
+
console.print("\n[yellow]🤖 Analyzing with AI runtime inspection...[/yellow]")
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Use debug agent with runtime inspection tools
|
|
128
|
+
from .debug_agent import create_debug_agent
|
|
129
|
+
|
|
130
|
+
# Pass the actual frame and traceback for runtime inspection!
|
|
131
|
+
agent = create_debug_agent(
|
|
132
|
+
frame=actual_frame,
|
|
133
|
+
exception_traceback=actual_traceback,
|
|
134
|
+
model=model
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Build prompt that guides tool usage
|
|
138
|
+
prompt = f"""Debug this Python exception using your runtime inspection tools:
|
|
139
|
+
|
|
140
|
+
Exception: {exc_type.__name__}: {exc_value}
|
|
141
|
+
File: {relevant_frame_info['file']}
|
|
142
|
+
Line: {relevant_frame_info['line']}
|
|
143
|
+
Function: {relevant_frame_info['function']}()
|
|
144
|
+
|
|
145
|
+
You have LIVE ACCESS to the crashed program's state! Use your tools to investigate:
|
|
146
|
+
|
|
147
|
+
1. First explore what variables are available:
|
|
148
|
+
- Use explore_namespace() to see all variables
|
|
149
|
+
|
|
150
|
+
2. Then investigate the specific error:
|
|
151
|
+
- Use execute_in_frame() to check values and types
|
|
152
|
+
- Use inspect_object() to examine data structures
|
|
153
|
+
- Use validate_assumption() to test your hypotheses
|
|
154
|
+
|
|
155
|
+
3. Test potential fixes:
|
|
156
|
+
- Use test_fix() to verify solutions work with the actual data
|
|
157
|
+
- Use try_alternative() to explore different approaches
|
|
158
|
+
|
|
159
|
+
4. Finally, provide your analysis:
|
|
160
|
+
- **What I found**: Show the actual runtime values you discovered
|
|
161
|
+
- **Why it failed**: Explain with evidence from the runtime state
|
|
162
|
+
- **Verified fix**: A solution you tested that works"""
|
|
163
|
+
|
|
164
|
+
# Get AI analysis
|
|
165
|
+
result = agent.input(prompt)
|
|
166
|
+
|
|
167
|
+
# Display the analysis
|
|
168
|
+
console.print("\n[cyan bold]💡 AI Runtime Debug Analysis:[/cyan bold]")
|
|
169
|
+
console.print(result)
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
# If AI analysis fails, show a simple message
|
|
173
|
+
console.print(f"[dim]AI analysis failed: {e}[/dim]")
|
|
174
|
+
|
|
175
|
+
# Install our exception hook
|
|
176
|
+
sys.excepthook = handle_exception
|
|
177
|
+
|
|
178
|
+
# Simple confirmation
|
|
179
|
+
from .console import Console
|
|
180
|
+
console = Console()
|
|
181
|
+
console.print(f"[green]✅ Exception debugging enabled[/green] - AI will analyze uncaught exceptions with runtime inspection")
|