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.
Files changed (113) hide show
  1. connectonion/__init__.py +78 -0
  2. connectonion/address.py +320 -0
  3. connectonion/agent.py +450 -0
  4. connectonion/announce.py +84 -0
  5. connectonion/asgi.py +287 -0
  6. connectonion/auto_debug_exception.py +181 -0
  7. connectonion/cli/__init__.py +3 -0
  8. connectonion/cli/browser_agent/__init__.py +5 -0
  9. connectonion/cli/browser_agent/browser.py +243 -0
  10. connectonion/cli/browser_agent/prompt.md +107 -0
  11. connectonion/cli/commands/__init__.py +1 -0
  12. connectonion/cli/commands/auth_commands.py +527 -0
  13. connectonion/cli/commands/browser_commands.py +27 -0
  14. connectonion/cli/commands/create.py +511 -0
  15. connectonion/cli/commands/deploy_commands.py +220 -0
  16. connectonion/cli/commands/doctor_commands.py +173 -0
  17. connectonion/cli/commands/init.py +469 -0
  18. connectonion/cli/commands/project_cmd_lib.py +828 -0
  19. connectonion/cli/commands/reset_commands.py +149 -0
  20. connectonion/cli/commands/status_commands.py +168 -0
  21. connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
  22. connectonion/cli/docs/connectonion.md +1256 -0
  23. connectonion/cli/docs.md +123 -0
  24. connectonion/cli/main.py +148 -0
  25. connectonion/cli/templates/meta-agent/README.md +287 -0
  26. connectonion/cli/templates/meta-agent/agent.py +196 -0
  27. connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
  28. connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
  29. connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
  30. connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
  31. connectonion/cli/templates/minimal/README.md +56 -0
  32. connectonion/cli/templates/minimal/agent.py +40 -0
  33. connectonion/cli/templates/playwright/README.md +118 -0
  34. connectonion/cli/templates/playwright/agent.py +336 -0
  35. connectonion/cli/templates/playwright/prompt.md +102 -0
  36. connectonion/cli/templates/playwright/requirements.txt +3 -0
  37. connectonion/cli/templates/web-research/agent.py +122 -0
  38. connectonion/connect.py +128 -0
  39. connectonion/console.py +539 -0
  40. connectonion/debug_agent/__init__.py +13 -0
  41. connectonion/debug_agent/agent.py +45 -0
  42. connectonion/debug_agent/prompts/debug_assistant.md +72 -0
  43. connectonion/debug_agent/runtime_inspector.py +406 -0
  44. connectonion/debug_explainer/__init__.py +10 -0
  45. connectonion/debug_explainer/explain_agent.py +114 -0
  46. connectonion/debug_explainer/explain_context.py +263 -0
  47. connectonion/debug_explainer/explainer_prompt.md +29 -0
  48. connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
  49. connectonion/debugger_ui.py +1039 -0
  50. connectonion/decorators.py +208 -0
  51. connectonion/events.py +248 -0
  52. connectonion/execution_analyzer/__init__.py +9 -0
  53. connectonion/execution_analyzer/execution_analysis.py +93 -0
  54. connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
  55. connectonion/host.py +579 -0
  56. connectonion/interactive_debugger.py +342 -0
  57. connectonion/llm.py +801 -0
  58. connectonion/llm_do.py +307 -0
  59. connectonion/logger.py +300 -0
  60. connectonion/prompt_files/__init__.py +1 -0
  61. connectonion/prompt_files/analyze_contact.md +62 -0
  62. connectonion/prompt_files/eval_expected.md +12 -0
  63. connectonion/prompt_files/react_evaluate.md +11 -0
  64. connectonion/prompt_files/react_plan.md +16 -0
  65. connectonion/prompt_files/reflect.md +22 -0
  66. connectonion/prompts.py +144 -0
  67. connectonion/relay.py +200 -0
  68. connectonion/static/docs.html +688 -0
  69. connectonion/tool_executor.py +279 -0
  70. connectonion/tool_factory.py +186 -0
  71. connectonion/tool_registry.py +105 -0
  72. connectonion/trust.py +166 -0
  73. connectonion/trust_agents.py +71 -0
  74. connectonion/trust_functions.py +88 -0
  75. connectonion/tui/__init__.py +57 -0
  76. connectonion/tui/divider.py +39 -0
  77. connectonion/tui/dropdown.py +251 -0
  78. connectonion/tui/footer.py +31 -0
  79. connectonion/tui/fuzzy.py +56 -0
  80. connectonion/tui/input.py +278 -0
  81. connectonion/tui/keys.py +35 -0
  82. connectonion/tui/pick.py +130 -0
  83. connectonion/tui/providers.py +155 -0
  84. connectonion/tui/status_bar.py +163 -0
  85. connectonion/usage.py +161 -0
  86. connectonion/useful_events_handlers/__init__.py +16 -0
  87. connectonion/useful_events_handlers/reflect.py +116 -0
  88. connectonion/useful_plugins/__init__.py +20 -0
  89. connectonion/useful_plugins/calendar_plugin.py +163 -0
  90. connectonion/useful_plugins/eval.py +139 -0
  91. connectonion/useful_plugins/gmail_plugin.py +162 -0
  92. connectonion/useful_plugins/image_result_formatter.py +127 -0
  93. connectonion/useful_plugins/re_act.py +78 -0
  94. connectonion/useful_plugins/shell_approval.py +159 -0
  95. connectonion/useful_tools/__init__.py +44 -0
  96. connectonion/useful_tools/diff_writer.py +192 -0
  97. connectonion/useful_tools/get_emails.py +183 -0
  98. connectonion/useful_tools/gmail.py +1596 -0
  99. connectonion/useful_tools/google_calendar.py +613 -0
  100. connectonion/useful_tools/memory.py +380 -0
  101. connectonion/useful_tools/microsoft_calendar.py +604 -0
  102. connectonion/useful_tools/outlook.py +488 -0
  103. connectonion/useful_tools/send_email.py +205 -0
  104. connectonion/useful_tools/shell.py +97 -0
  105. connectonion/useful_tools/slash_command.py +201 -0
  106. connectonion/useful_tools/terminal.py +285 -0
  107. connectonion/useful_tools/todo_list.py +241 -0
  108. connectonion/useful_tools/web_fetch.py +216 -0
  109. connectonion/xray.py +467 -0
  110. connectonion-0.5.8.dist-info/METADATA +741 -0
  111. connectonion-0.5.8.dist-info/RECORD +113 -0
  112. connectonion-0.5.8.dist-info/WHEEL +4 -0
  113. connectonion-0.5.8.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,406 @@
1
+ """
2
+ Purpose: Provide runtime inspection tools for AI-powered exception debugging with live frame access
3
+ LLM-Note:
4
+ Dependencies: imports from [pathlib, typing, re] | imported by [debug_agent/agent.py, debug_agent/__init__.py, auto_debug_exception.py] | tested by [tests/test_runtime_inspector.py]
5
+ Data flow: auto_debug_exception() creates RuntimeInspector(frame, traceback) → stores frame.f_globals + frame.f_locals in self.namespace → AI agent calls methods: execute_in_frame(code) uses eval/exec, inspect_object(var) shows type/attrs/methods, validate_assumption(statement) tests hypothesis, test_fix(code) validates solutions, explore_namespace() lists all variables → returns formatted strings → AI interprets for debugging
6
+ State/Effects: stores frozen exception frame and namespace | execute_in_frame() can modify namespace via exec | no file I/O or external side effects | evaluates arbitrary Python code (security: only in debug context)
7
+ Integration: exposes RuntimeInspector class with methods: execute_in_frame(code), inspect_object(variable_name), validate_assumption(statement), test_fix(fix_code), try_alternative(code), explore_namespace(), get_traceback() | used as class-based tool (Agent auto-extracts methods via tool_factory.extract_methods_from_instance)
8
+ Performance: eval/exec are fast | namespace is dict copy (O(n) initial cost) | inspection uses dir() and getattr() | traceback formatting uses traceback module
9
+ Errors: execute_in_frame() catches exceptions and returns error strings (doesn't raise) | missing variables return "not found" messages | no frame returns "No runtime context available"
10
+ """
11
+
12
+ from pathlib import Path
13
+ from typing import Any, Optional, List, Dict
14
+ import re
15
+
16
+
17
+ class RuntimeInspector:
18
+ """Inspector that operates on a frozen exception runtime state.
19
+
20
+ Pass an instance of this class directly to the Agent as a tool.
21
+ ConnectOnion will automatically discover all public methods!
22
+ """
23
+
24
+ def __init__(self, frame=None, exception_traceback=None):
25
+ """Initialize with optional exception frame and traceback.
26
+
27
+ Args:
28
+ frame: The frame object where the exception occurred
29
+ exception_traceback: The traceback object from the exception
30
+ """
31
+ self.frame = frame
32
+ self.exception_traceback = exception_traceback
33
+ self.namespace = {}
34
+
35
+ if frame:
36
+ # Combine locals and globals for execution context
37
+ self.namespace.update(frame.f_globals)
38
+ self.namespace.update(frame.f_locals)
39
+
40
+ def set_context(self, frame, exception_traceback):
41
+ """Update the runtime context (called by auto_debug).
42
+
43
+ Args:
44
+ frame: The frame object where the exception occurred
45
+ exception_traceback: The traceback object from the exception
46
+ """
47
+ self.frame = frame
48
+ self.exception_traceback = exception_traceback
49
+ self.namespace = {}
50
+ self.namespace.update(frame.f_globals)
51
+ self.namespace.update(frame.f_locals)
52
+
53
+ def execute_in_frame(self, code: str) -> str:
54
+ """Execute Python code in the exception frame context.
55
+
56
+ Access all variables, call functions, and test hypotheses with real data.
57
+
58
+ Args:
59
+ code: Python code to execute in the exception frame
60
+
61
+ Returns:
62
+ The result of execution or error message
63
+
64
+ Examples:
65
+ execute_in_frame("type(profile)")
66
+ execute_in_frame("list(data.keys())")
67
+ execute_in_frame("profile.get('name', 'default')")
68
+ """
69
+ if not self.frame:
70
+ return "No runtime context available"
71
+
72
+ try:
73
+ result = eval(code, self.namespace)
74
+ return self._format_result(result)
75
+ except Exception as e:
76
+ # Try exec for statements (like assignments)
77
+ try:
78
+ exec(code, self.namespace)
79
+ return "Executed successfully"
80
+ except Exception:
81
+ return f"Error: {e}"
82
+
83
+ def inspect_object(self, variable_name: str) -> str:
84
+ """Deep inspection of an object in the runtime context.
85
+
86
+ Shows the object's type, attributes, methods, and current state.
87
+
88
+ Args:
89
+ variable_name: Name of the variable to inspect
90
+
91
+ Returns:
92
+ Detailed information about the object
93
+
94
+ Example:
95
+ inspect_object("profile")
96
+ """
97
+ if not self.frame:
98
+ return "No runtime context available"
99
+
100
+ if variable_name not in self.namespace:
101
+ return f"Variable '{variable_name}' not found in scope"
102
+
103
+ obj = self.namespace[variable_name]
104
+ result = [f"=== {variable_name} ==="]
105
+ result.append(f"Type: {type(obj).__name__}")
106
+ result.append(f"Value: {self._format_result(obj, max_length=200)}")
107
+
108
+ # Type-specific details
109
+ if isinstance(obj, dict):
110
+ result.append(f"Keys ({len(obj)}): {list(obj.keys())[:10]}")
111
+ if len(obj) > 10:
112
+ result.append(f" ... +{len(obj) - 10} more")
113
+ elif isinstance(obj, (list, tuple)):
114
+ result.append(f"Length: {len(obj)}")
115
+ if obj:
116
+ result.append(f"First: {self._format_result(obj[0], max_length=100)}")
117
+ elif hasattr(obj, '__dict__'):
118
+ attrs = vars(obj)
119
+ result.append(f"Attributes: {list(attrs.keys())[:10]}")
120
+
121
+ # Show methods (non-private)
122
+ methods = [m for m in dir(obj)
123
+ if not m.startswith('_') and callable(getattr(obj, m, None))]
124
+ if methods:
125
+ result.append(f"Methods: {methods[:10]}")
126
+ if len(methods) > 10:
127
+ result.append(f" ... +{len(methods) - 10} more")
128
+
129
+ return "\n".join(result)
130
+
131
+ def test_fix(self, original_code: str, fixed_code: str) -> str:
132
+ """Test a potential fix using the actual runtime data.
133
+
134
+ Compare what the original code produced vs what the fix would produce.
135
+
136
+ Args:
137
+ original_code: The code that caused the error
138
+ fixed_code: The proposed fix to test
139
+
140
+ Returns:
141
+ Comparison of results from both versions
142
+
143
+ Example:
144
+ test_fix("data['key']", "data.get('key', 'default')")
145
+ """
146
+ if not self.frame:
147
+ return "No runtime context available"
148
+
149
+ result = ["=== Testing Fix ==="]
150
+
151
+ # Test original
152
+ result.append(f"\nOriginal: {original_code}")
153
+ try:
154
+ original_result = eval(original_code, self.namespace)
155
+ result.append(f" → {self._format_result(original_result)}")
156
+ except Exception as e:
157
+ result.append(f" ✗ {e}")
158
+
159
+ # Test fix
160
+ result.append(f"\nFixed: {fixed_code}")
161
+ try:
162
+ fixed_result = eval(fixed_code, self.namespace)
163
+ result.append(f" → {self._format_result(fixed_result)}")
164
+ result.append(" ✓ Fix works!")
165
+ except Exception as e:
166
+ result.append(f" ✗ {e}")
167
+
168
+ return "\n".join(result)
169
+
170
+ def validate_assumption(self, assumption: str) -> str:
171
+ """Validate an assumption about the runtime state.
172
+
173
+ Test any assumption using the actual data.
174
+
175
+ Args:
176
+ assumption: Python expression that should return True/False
177
+
178
+ Returns:
179
+ Whether the assumption holds and details
180
+
181
+ Examples:
182
+ validate_assumption("isinstance(profile, dict)")
183
+ validate_assumption("'notifications' in profile")
184
+ validate_assumption("len(items) > 0")
185
+ """
186
+ if not self.frame:
187
+ return "No runtime context available"
188
+
189
+ result = [f"=== Validating: {assumption} ==="]
190
+
191
+ try:
192
+ validation_result = eval(assumption, self.namespace)
193
+
194
+ if validation_result is True:
195
+ result.append("✓ TRUE")
196
+ elif validation_result is False:
197
+ result.append("✗ FALSE")
198
+ else:
199
+ result.append(f"Result: {self._format_result(validation_result)} (not boolean)")
200
+
201
+ # Add helpful context
202
+ self._add_validation_context(assumption, result)
203
+
204
+ except Exception as e:
205
+ result.append(f"Error: {e}")
206
+
207
+ return "\n".join(result)
208
+
209
+ def trace_variable(self, variable_name: str) -> str:
210
+ """Trace how a variable changed through the call stack.
211
+
212
+ Shows the variable's value in each frame of the call stack.
213
+
214
+ Args:
215
+ variable_name: Name of the variable to trace
216
+
217
+ Returns:
218
+ The variable's values through the call stack
219
+
220
+ Example:
221
+ trace_variable("user_data")
222
+ """
223
+ if not self.exception_traceback:
224
+ return "No traceback available"
225
+
226
+ result = [f"=== Tracing '{variable_name}' ==="]
227
+
228
+ frame_num = 0
229
+ current_traceback = self.exception_traceback
230
+
231
+ while current_traceback:
232
+ frame = current_traceback.tb_frame
233
+ frame_num += 1
234
+
235
+ func_name = frame.f_code.co_name
236
+ filename = Path(frame.f_code.co_filename).name
237
+ line_no = current_traceback.tb_lineno
238
+
239
+ result.append(f"\n#{frame_num} {func_name}() at {filename}:{line_no}")
240
+
241
+ if variable_name in frame.f_locals:
242
+ value = frame.f_locals[variable_name]
243
+ result.append(f" {variable_name} = {self._format_result(value)}")
244
+ else:
245
+ result.append(f" (not in scope)")
246
+
247
+ current_traceback = current_traceback.tb_next
248
+
249
+ return "\n".join(result)
250
+
251
+ def explore_namespace(self) -> str:
252
+ """Explore all available variables in the exception context.
253
+
254
+ Returns:
255
+ List of all variables and their types
256
+ """
257
+ if not self.frame:
258
+ return "No runtime context available"
259
+
260
+ result = ["=== Available Variables ==="]
261
+
262
+ # Group by type for better organization
263
+ by_type: Dict[str, List[str]] = {}
264
+
265
+ for name, value in self.namespace.items():
266
+ if name.startswith('__'):
267
+ continue # Skip dunder variables
268
+
269
+ type_name = type(value).__name__
270
+ if type_name not in by_type:
271
+ by_type[type_name] = []
272
+ by_type[type_name].append(name)
273
+
274
+ # Show variables grouped by type
275
+ for type_name in sorted(by_type.keys()):
276
+ vars_list = by_type[type_name][:10] # Limit to 10 per type
277
+ if len(by_type[type_name]) > 10:
278
+ vars_list.append(f"... +{len(by_type[type_name]) - 10} more")
279
+ result.append(f"\n{type_name}: {', '.join(vars_list)}")
280
+
281
+ return "\n".join(result)
282
+
283
+ def try_alternative(self, failing_expr: str, *alternatives: str) -> str:
284
+ """Try multiple alternative expressions to find what works.
285
+
286
+ Useful for exploring different ways to access data.
287
+
288
+ Args:
289
+ failing_expr: The expression that's failing
290
+ *alternatives: Alternative expressions to try
291
+
292
+ Returns:
293
+ Results of all attempts
294
+
295
+ Example:
296
+ try_alternative(
297
+ "data['key']",
298
+ "data.get('key')",
299
+ "data.get('Key')", # Different case
300
+ "data.get('keys')" # Plural
301
+ )
302
+ """
303
+ if not self.frame:
304
+ return "No runtime context available"
305
+
306
+ result = ["=== Trying Alternatives ==="]
307
+
308
+ # Test original
309
+ result.append(f"\nOriginal: {failing_expr}")
310
+ try:
311
+ orig_result = eval(failing_expr, self.namespace)
312
+ result.append(f" ✓ Works: {self._format_result(orig_result)}")
313
+ except Exception as e:
314
+ result.append(f" ✗ {e}")
315
+
316
+ # Test alternatives
317
+ for alt in alternatives:
318
+ result.append(f"\nAlternative: {alt}")
319
+ try:
320
+ alt_result = eval(alt, self.namespace)
321
+ result.append(f" ✓ Works: {self._format_result(alt_result)}")
322
+ except Exception as e:
323
+ result.append(f" ✗ {e}")
324
+
325
+ return "\n".join(result)
326
+
327
+ def read_source_around_error(self, context_lines: int = 5) -> str:
328
+ """Read source code around the error location.
329
+
330
+ Args:
331
+ context_lines: Number of lines before and after to show
332
+
333
+ Returns:
334
+ Source code with line numbers, highlighting the error line
335
+ """
336
+ if not self.exception_traceback:
337
+ return "No traceback available"
338
+
339
+ # Get the file and line from the traceback
340
+ current_traceback = self.exception_traceback
341
+ while current_traceback.tb_next: # Go to the last frame
342
+ current_traceback = current_traceback.tb_next
343
+
344
+ filename = current_traceback.tb_frame.f_code.co_filename
345
+ line_number = current_traceback.tb_lineno
346
+
347
+ try:
348
+ path = Path(filename)
349
+ if not path.exists():
350
+ return f"File not found: {filename}"
351
+
352
+ with open(path, 'r') as f:
353
+ lines = f.readlines()
354
+
355
+ start = max(0, line_number - context_lines - 1)
356
+ end = min(len(lines), line_number + context_lines)
357
+
358
+ result = [f"=== {path.name}:{line_number} ===\n"]
359
+ for i in range(start, end):
360
+ line_num = i + 1
361
+ prefix = ">>>" if line_num == line_number else " "
362
+ result.append(f"{prefix} {line_num:4}: {lines[i].rstrip()}")
363
+
364
+ return "\n".join(result)
365
+ except Exception as e:
366
+ return f"Error reading source: {e}"
367
+
368
+ def _format_result(self, obj: Any, max_length: int = 500) -> str:
369
+ """Format an object for display."""
370
+ if obj is None:
371
+ return "None"
372
+ elif isinstance(obj, (str, int, float, bool)):
373
+ result = repr(obj)
374
+ elif isinstance(obj, (list, dict, tuple)):
375
+ result = repr(obj)
376
+ else:
377
+ result = f"{type(obj).__name__}: {str(obj)}"
378
+
379
+ if len(result) > max_length:
380
+ result = result[:max_length] + "..."
381
+ return result
382
+
383
+ def _add_validation_context(self, assumption: str, result: list):
384
+ """Add helpful context for validation results."""
385
+ # For isinstance checks, show actual type
386
+ if "isinstance" in assumption:
387
+ match = re.match(r'isinstance\((\w+),', assumption)
388
+ if match:
389
+ var_name = match.group(1)
390
+ if var_name in self.namespace:
391
+ actual_type = type(self.namespace[var_name]).__name__
392
+ result.append(f" Actual type: {actual_type}")
393
+
394
+ # For membership tests, show available options
395
+ if " in " in assumption:
396
+ parts = assumption.split(" in ")
397
+ if len(parts) == 2:
398
+ container_code = parts[1].strip().rstrip(')')
399
+ try:
400
+ container = eval(container_code, self.namespace)
401
+ if isinstance(container, dict):
402
+ result.append(f" Available keys: {list(container.keys())[:10]}")
403
+ elif isinstance(container, (list, tuple, set)):
404
+ result.append(f" Contains {len(container)} items")
405
+ except:
406
+ pass
@@ -0,0 +1,10 @@
1
+ """Debug explainer - AI-powered explanation of tool choices during debugging.
2
+
3
+ Provides runtime investigation capabilities to explain why an agent
4
+ chose to call a specific tool with specific arguments.
5
+ """
6
+
7
+ from .explain_agent import explain_tool_choice
8
+ from .explain_context import RuntimeContext
9
+
10
+ __all__ = ["explain_tool_choice", "RuntimeContext"]
@@ -0,0 +1,114 @@
1
+ """
2
+ Purpose: Create AI agent to explain why tools were chosen during debugging with experimental investigation capabilities
3
+ LLM-Note:
4
+ Dependencies: imports from [pathlib, explain_context.py, ../agent.py, inspect] | imported by [interactive_debugger.py] | no dedicated tests found
5
+ Data flow: interactive_debugger calls explain_tool_choice(breakpoint_context, agent, model) → extracts tool info (name, args, result, source code), agent info (system_prompt, available_tools), conversation history → creates RuntimeContext with experimental tools → creates explainer Agent with RuntimeContext as tool + explainer_prompt.md → sends comprehensive context prompt → Agent investigates and returns explanation string
6
+ State/Effects: reads explainer_prompt.md file | creates temporary explainer Agent instance | calls RuntimeContext methods (which make LLM requests) | log=False prevents logging | no persistent state
7
+ Integration: exposes explain_tool_choice(breakpoint_context, agent_instance, model) function | used by interactive_debugger WHY action | explainer agent has max_iterations=5 for investigation | RuntimeContext provides experimental debugging methods
8
+ Performance: one explainer agent per WHY request | extracts source via inspect.getsource() | may make multiple LLM calls if explainer uses investigation tools | synchronous blocking
9
+ Errors: FileNotFoundError if explainer_prompt.md missing | source extraction failures caught (returns "unavailable") | Agent creation and LLM errors propagate
10
+ """
11
+
12
+ from pathlib import Path
13
+ from .explain_context import RuntimeContext
14
+
15
+
16
+ def explain_tool_choice(
17
+ breakpoint_context,
18
+ agent_instance,
19
+ model: str = "co/gpt-5"
20
+ ) -> str:
21
+ """Explain why the agent chose this specific tool.
22
+
23
+ Provides all context information upfront so the explainer doesn't need
24
+ to call investigation tools.
25
+
26
+ Args:
27
+ breakpoint_context: BreakpointContext from the debugger
28
+ agent_instance: The Agent being debugged
29
+ model: AI model to use (default: co/gpt-5 for consistent debugging)
30
+
31
+ Returns:
32
+ Explanation string from the AI agent
33
+ """
34
+ from ..agent import Agent
35
+ import inspect
36
+
37
+ # Get all the information we need
38
+ tool_name = breakpoint_context.tool_name
39
+ tool_args = breakpoint_context.tool_args
40
+ user_prompt = breakpoint_context.user_prompt
41
+ tool_result = breakpoint_context.trace_entry.get('result')
42
+ tool_status = breakpoint_context.trace_entry.get('status')
43
+
44
+ # Get tool source code
45
+ tool = agent_instance.tools.get(tool_name)
46
+ tool_source = "Source unavailable"
47
+ if tool:
48
+ func = tool.run if hasattr(tool, 'run') else tool
49
+ while hasattr(func, '__wrapped__'):
50
+ func = func.__wrapped__
51
+ tool_source = inspect.getsource(func)
52
+
53
+ # Get agent information
54
+ agent_name = agent_instance.name
55
+ agent_system_prompt = agent_instance.system_prompt or "No system prompt"
56
+ available_tools = [t.name for t in agent_instance.tools] if agent_instance.tools else []
57
+ previous_tools = breakpoint_context.previous_tools
58
+ iteration = breakpoint_context.iteration
59
+
60
+ # Get conversation history
61
+ messages = agent_instance.current_session.get('messages', [])
62
+ recent_messages = messages[-3:] if len(messages) > 3 else messages
63
+
64
+ # Get next planned actions
65
+ next_actions = breakpoint_context.next_actions or []
66
+
67
+ # Create runtime context - its methods become investigation tools
68
+ runtime_ctx = RuntimeContext(breakpoint_context, agent_instance)
69
+
70
+ # Load system prompt from markdown file
71
+ prompt_file = Path(__file__).parent / "explainer_prompt.md"
72
+
73
+ # Create explainer agent with runtime context tools
74
+ explainer = Agent(
75
+ name="tool_choice_explainer",
76
+ system_prompt=prompt_file,
77
+ tools=[runtime_ctx], # Experimental tools for deeper investigation
78
+ model=model,
79
+ max_iterations=5, # Allow investigation steps if needed
80
+ log=False # Don't clutter user's logs with explainer agent activity
81
+ )
82
+
83
+ # Build the full context prompt
84
+ context_prompt = f"""The agent was asked: "{user_prompt}"
85
+
86
+ It chose to call: {tool_name}({tool_args})
87
+
88
+ ## Agent Information
89
+ - Agent name: {agent_name}
90
+ - System prompt: {agent_system_prompt}
91
+ - Iteration: {iteration}
92
+ - Available tools: {available_tools}
93
+ - Previous tools called: {previous_tools}
94
+
95
+ ## Tool Information
96
+ - Status: {tool_status}
97
+ - Arguments: {tool_args}
98
+ - Result: {tool_result}
99
+
100
+ ## Tool Source Code
101
+ ```python
102
+ {tool_source}
103
+ ```
104
+
105
+ ## Recent Conversation (last 3 messages)
106
+ {chr(10).join([f"- {msg.get('role')}: {str(msg.get('content', ''))[:200]}" for msg in recent_messages])}
107
+
108
+ ## What Agent Plans Next
109
+ {chr(10).join([f"- {action['name']}({action['args']})" for action in next_actions]) if next_actions else "No more tools planned"}
110
+
111
+ Please explain why this tool was called with these arguments based on the context above."""
112
+
113
+ result = explainer.input(context_prompt)
114
+ return result