codepp 0.0.437__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 (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,692 @@
1
+ import asyncio
2
+ import logging
3
+ import traceback
4
+ from typing import Any, Callable, Dict, List, Literal, Optional
5
+
6
+ PhaseType = Literal[
7
+ "startup",
8
+ "shutdown",
9
+ "invoke_agent",
10
+ "agent_exception",
11
+ "version_check",
12
+ "edit_file",
13
+ "create_file",
14
+ "replace_in_file",
15
+ "delete_snippet",
16
+ "delete_file",
17
+ "run_shell_command",
18
+ "load_model_config",
19
+ "load_models_config",
20
+ "load_prompt",
21
+ "agent_reload",
22
+ "custom_command",
23
+ "custom_command_help",
24
+ "file_permission",
25
+ "pre_tool_call",
26
+ "post_tool_call",
27
+ "stream_event",
28
+ "register_tools",
29
+ "register_agents",
30
+ "register_model_type",
31
+ "get_model_system_prompt",
32
+ "agent_run_start",
33
+ "agent_run_end",
34
+ "register_mcp_catalog_servers",
35
+ "register_browser_types",
36
+ "get_motd",
37
+ "register_model_providers",
38
+ "message_history_processor_start",
39
+ "message_history_processor_end",
40
+ ]
41
+ CallbackFunc = Callable[..., Any]
42
+
43
+ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
44
+ "startup": [],
45
+ "shutdown": [],
46
+ "invoke_agent": [],
47
+ "agent_exception": [],
48
+ "version_check": [],
49
+ "edit_file": [],
50
+ "create_file": [],
51
+ "replace_in_file": [],
52
+ "delete_snippet": [],
53
+ "delete_file": [],
54
+ "run_shell_command": [],
55
+ "load_model_config": [],
56
+ "load_models_config": [],
57
+ "load_prompt": [],
58
+ "agent_reload": [],
59
+ "custom_command": [],
60
+ "custom_command_help": [],
61
+ "file_permission": [],
62
+ "pre_tool_call": [],
63
+ "post_tool_call": [],
64
+ "stream_event": [],
65
+ "register_tools": [],
66
+ "register_agents": [],
67
+ "register_model_type": [],
68
+ "get_model_system_prompt": [],
69
+ "agent_run_start": [],
70
+ "agent_run_end": [],
71
+ "register_mcp_catalog_servers": [],
72
+ "register_browser_types": [],
73
+ "get_motd": [],
74
+ "register_model_providers": [],
75
+ "message_history_processor_start": [],
76
+ "message_history_processor_end": [],
77
+ }
78
+
79
+ logger = logging.getLogger(__name__)
80
+
81
+
82
+ def register_callback(phase: PhaseType, func: CallbackFunc) -> None:
83
+ if phase not in _callbacks:
84
+ raise ValueError(
85
+ f"Unsupported phase: {phase}. Supported phases: {list(_callbacks.keys())}"
86
+ )
87
+
88
+ if not callable(func):
89
+ raise TypeError(f"Callback must be callable, got {type(func)}")
90
+
91
+ # Prevent duplicate registration of the same callback function
92
+ # This can happen if plugins are accidentally loaded multiple times
93
+ if func in _callbacks[phase]:
94
+ logger.debug(
95
+ f"Callback {func.__name__} already registered for phase '{phase}', skipping"
96
+ )
97
+ return
98
+
99
+ _callbacks[phase].append(func)
100
+ logger.debug(f"Registered async callback {func.__name__} for phase '{phase}'")
101
+
102
+
103
+ def unregister_callback(phase: PhaseType, func: CallbackFunc) -> bool:
104
+ if phase not in _callbacks:
105
+ return False
106
+
107
+ try:
108
+ _callbacks[phase].remove(func)
109
+ logger.debug(
110
+ f"Unregistered async callback {func.__name__} from phase '{phase}'"
111
+ )
112
+ return True
113
+ except ValueError:
114
+ return False
115
+
116
+
117
+ def clear_callbacks(phase: Optional[PhaseType] = None) -> None:
118
+ if phase is None:
119
+ for p in _callbacks:
120
+ _callbacks[p].clear()
121
+ logger.debug("Cleared all async callbacks")
122
+ else:
123
+ if phase in _callbacks:
124
+ _callbacks[phase].clear()
125
+ logger.debug(f"Cleared async callbacks for phase '{phase}'")
126
+
127
+
128
+ def get_callbacks(phase: PhaseType) -> List[CallbackFunc]:
129
+ return _callbacks.get(phase, []).copy()
130
+
131
+
132
+ def count_callbacks(phase: Optional[PhaseType] = None) -> int:
133
+ if phase is None:
134
+ return sum(len(callbacks) for callbacks in _callbacks.values())
135
+ return len(_callbacks.get(phase, []))
136
+
137
+
138
+ def _trigger_callbacks_sync(phase: PhaseType, *args, **kwargs) -> List[Any]:
139
+ callbacks = get_callbacks(phase)
140
+ if not callbacks:
141
+ logger.debug(f"No callbacks registered for phase '{phase}'")
142
+ return []
143
+
144
+ results = []
145
+ for callback in callbacks:
146
+ try:
147
+ result = callback(*args, **kwargs)
148
+ # Handle async callbacks - if we get a coroutine, run it
149
+ if asyncio.iscoroutine(result):
150
+ # Try to get the running event loop
151
+ try:
152
+ asyncio.get_running_loop()
153
+ # We're in an async context already - this shouldn't happen for sync triggers
154
+ # but if it does, we can't use run_until_complete
155
+ logger.warning(
156
+ f"Async callback {callback.__name__} called from async context in sync trigger"
157
+ )
158
+ results.append(None)
159
+ continue
160
+ except RuntimeError:
161
+ # No running loop - we're in a sync/worker thread context
162
+ # Use asyncio.run() which is safe here since we're in an isolated thread
163
+ result = asyncio.run(result)
164
+ results.append(result)
165
+ logger.debug(f"Successfully executed callback {callback.__name__}")
166
+ except Exception as e:
167
+ logger.error(
168
+ f"Callback {callback.__name__} failed in phase '{phase}': {e}\n"
169
+ f"{traceback.format_exc()}"
170
+ )
171
+ results.append(None)
172
+
173
+ return results
174
+
175
+
176
+ async def _trigger_callbacks(phase: PhaseType, *args, **kwargs) -> List[Any]:
177
+ callbacks = get_callbacks(phase)
178
+
179
+ if not callbacks:
180
+ logger.debug(f"No callbacks registered for phase '{phase}'")
181
+ return []
182
+
183
+ logger.debug(f"Triggering {len(callbacks)} async callbacks for phase '{phase}'")
184
+
185
+ results = []
186
+ for callback in callbacks:
187
+ try:
188
+ result = callback(*args, **kwargs)
189
+ if asyncio.iscoroutine(result):
190
+ result = await result
191
+ results.append(result)
192
+ logger.debug(f"Successfully executed async callback {callback.__name__}")
193
+ except Exception as e:
194
+ logger.error(
195
+ f"Async callback {callback.__name__} failed in phase '{phase}': {e}\n"
196
+ f"{traceback.format_exc()}"
197
+ )
198
+ results.append(None)
199
+
200
+ return results
201
+
202
+
203
+ async def on_startup() -> List[Any]:
204
+ return await _trigger_callbacks("startup")
205
+
206
+
207
+ async def on_shutdown() -> List[Any]:
208
+ return await _trigger_callbacks("shutdown")
209
+
210
+
211
+ async def on_invoke_agent(*args, **kwargs) -> List[Any]:
212
+ return await _trigger_callbacks("invoke_agent", *args, **kwargs)
213
+
214
+
215
+ async def on_agent_exception(exception: Exception, *args, **kwargs) -> List[Any]:
216
+ return await _trigger_callbacks("agent_exception", exception, *args, **kwargs)
217
+
218
+
219
+ async def on_version_check(*args, **kwargs) -> List[Any]:
220
+ return await _trigger_callbacks("version_check", *args, **kwargs)
221
+
222
+
223
+ def on_load_model_config(*args, **kwargs) -> List[Any]:
224
+ return _trigger_callbacks_sync("load_model_config", *args, **kwargs)
225
+
226
+
227
+ def on_load_models_config() -> List[Any]:
228
+ """Trigger callbacks to load additional model configurations.
229
+
230
+ Plugins can register callbacks that return a dict of model configurations
231
+ to be merged with the built-in models.json. Plugin models override built-in
232
+ models with the same name.
233
+
234
+ Returns:
235
+ List of model config dicts from all registered callbacks.
236
+ """
237
+ return _trigger_callbacks_sync("load_models_config")
238
+
239
+
240
+ def on_edit_file(*args, **kwargs) -> Any:
241
+ return _trigger_callbacks_sync("edit_file", *args, **kwargs)
242
+
243
+
244
+ def on_create_file(*args, **kwargs) -> Any:
245
+ return _trigger_callbacks_sync("create_file", *args, **kwargs)
246
+
247
+
248
+ def on_replace_in_file(*args, **kwargs) -> Any:
249
+ return _trigger_callbacks_sync("replace_in_file", *args, **kwargs)
250
+
251
+
252
+ def on_delete_snippet(*args, **kwargs) -> Any:
253
+ return _trigger_callbacks_sync("delete_snippet", *args, **kwargs)
254
+
255
+
256
+ def on_delete_file(*args, **kwargs) -> Any:
257
+ return _trigger_callbacks_sync("delete_file", *args, **kwargs)
258
+
259
+
260
+ async def on_run_shell_command(*args, **kwargs) -> Any:
261
+ return await _trigger_callbacks("run_shell_command", *args, **kwargs)
262
+
263
+
264
+ def on_agent_reload(*args, **kwargs) -> Any:
265
+ return _trigger_callbacks_sync("agent_reload", *args, **kwargs)
266
+
267
+
268
+ def on_load_prompt():
269
+ return _trigger_callbacks_sync("load_prompt")
270
+
271
+
272
+ def on_custom_command_help() -> List[Any]:
273
+ """Collect custom command help entries from plugins.
274
+
275
+ Each callback should return a list of tuples [(name, description), ...]
276
+ or a single tuple, or None. We'll flatten and sanitize results.
277
+ """
278
+ return _trigger_callbacks_sync("custom_command_help")
279
+
280
+
281
+ def on_custom_command(command: str, name: str) -> List[Any]:
282
+ """Trigger custom command callbacks.
283
+
284
+ This allows plugins to register handlers for slash commands
285
+ that are not built into the core command handler.
286
+
287
+ Args:
288
+ command: The full command string (e.g., "/foo bar baz").
289
+ name: The primary command name without the leading slash (e.g., "foo").
290
+
291
+ Returns:
292
+ Implementations may return:
293
+ - True if the command was handled (and no further action is needed)
294
+ - A string to be processed as user input by the caller
295
+ - None to indicate not handled
296
+ """
297
+ return _trigger_callbacks_sync("custom_command", command, name)
298
+
299
+
300
+ def on_file_permission(
301
+ context: Any,
302
+ file_path: str,
303
+ operation: str,
304
+ preview: str | None = None,
305
+ message_group: str | None = None,
306
+ operation_data: Any = None,
307
+ ) -> List[Any]:
308
+ """Trigger file permission callbacks.
309
+
310
+ This allows plugins to register handlers for file permission checks
311
+ before file operations are performed.
312
+
313
+ Args:
314
+ context: The operation context
315
+ file_path: Path to the file being operated on
316
+ operation: Description of the operation
317
+ preview: Optional preview of changes (deprecated - use operation_data instead)
318
+ message_group: Optional message group
319
+ operation_data: Operation-specific data for preview generation (recommended)
320
+
321
+ Returns:
322
+ List of boolean results from permission handlers.
323
+ Returns True if permission should be granted, False if denied.
324
+ """
325
+ # For backward compatibility, if operation_data is provided, prefer it over preview
326
+ if operation_data is not None:
327
+ preview = None
328
+ return _trigger_callbacks_sync(
329
+ "file_permission",
330
+ context,
331
+ file_path,
332
+ operation,
333
+ preview,
334
+ message_group,
335
+ operation_data,
336
+ )
337
+
338
+
339
+ async def on_pre_tool_call(
340
+ tool_name: str, tool_args: dict, context: Any = None
341
+ ) -> List[Any]:
342
+ """Trigger callbacks before a tool is called.
343
+
344
+ This allows plugins to inspect, modify, or log tool calls before
345
+ they are executed.
346
+
347
+ Args:
348
+ tool_name: Name of the tool being called
349
+ tool_args: Arguments being passed to the tool
350
+ context: Optional context data for the tool call
351
+
352
+ Returns:
353
+ List of results from registered callbacks.
354
+ """
355
+ return await _trigger_callbacks("pre_tool_call", tool_name, tool_args, context)
356
+
357
+
358
+ async def on_post_tool_call(
359
+ tool_name: str,
360
+ tool_args: dict,
361
+ result: Any,
362
+ duration_ms: float,
363
+ context: Any = None,
364
+ ) -> List[Any]:
365
+ """Trigger callbacks after a tool completes.
366
+
367
+ This allows plugins to inspect tool results, log execution times,
368
+ or perform post-processing.
369
+
370
+ Args:
371
+ tool_name: Name of the tool that was called
372
+ tool_args: Arguments that were passed to the tool
373
+ result: The result returned by the tool
374
+ duration_ms: Execution time in milliseconds
375
+ context: Optional context data for the tool call
376
+
377
+ Returns:
378
+ List of results from registered callbacks.
379
+ """
380
+ return await _trigger_callbacks(
381
+ "post_tool_call", tool_name, tool_args, result, duration_ms, context
382
+ )
383
+
384
+
385
+ async def on_stream_event(
386
+ event_type: str, event_data: Any, agent_session_id: str | None = None
387
+ ) -> List[Any]:
388
+ """Trigger callbacks for streaming events.
389
+
390
+ This allows plugins to react to streaming events in real-time,
391
+ such as tokens being generated, tool calls starting, etc.
392
+
393
+ Args:
394
+ event_type: Type of the streaming event
395
+ event_data: Data associated with the event
396
+ agent_session_id: Optional session ID of the agent emitting the event
397
+
398
+ Returns:
399
+ List of results from registered callbacks.
400
+ """
401
+ return await _trigger_callbacks(
402
+ "stream_event", event_type, event_data, agent_session_id
403
+ )
404
+
405
+
406
+ def on_register_tools() -> List[Dict[str, Any]]:
407
+ """Collect custom tool registrations from plugins.
408
+
409
+ Each callback should return a list of dicts with:
410
+ - "name": str - the tool name
411
+ - "register_func": callable - function that takes an agent and registers the tool
412
+
413
+ Example return: [{"name": "my_tool", "register_func": register_my_tool}]
414
+ """
415
+ return _trigger_callbacks_sync("register_tools")
416
+
417
+
418
+ def on_register_agents() -> List[Dict[str, Any]]:
419
+ """Collect custom agent registrations from plugins.
420
+
421
+ Each callback should return a list of dicts with either:
422
+ - "name": str, "class": Type[BaseAgent] - for Python agent classes
423
+ - "name": str, "json_path": str - for JSON agent files
424
+
425
+ Example return: [{"name": "my-agent", "class": MyAgentClass}]
426
+ """
427
+ return _trigger_callbacks_sync("register_agents")
428
+
429
+
430
+ def on_register_model_types() -> List[Dict[str, Any]]:
431
+ """Collect custom model type registrations from plugins.
432
+
433
+ This hook allows plugins to register custom model types that can be used
434
+ in model configurations. Each callback should return a list of dicts with:
435
+ - "type": str - the model type name (e.g., "antigravity", "claude_code")
436
+ - "handler": callable - function(model_name, model_config, config) -> model instance
437
+
438
+ The handler function receives:
439
+ - model_name: str - the name of the model being created
440
+ - model_config: dict - the model's configuration from models.json
441
+ - config: dict - the full models configuration
442
+
443
+ The handler should return a model instance or None if creation fails.
444
+
445
+ Example callback:
446
+ def register_my_model_types():
447
+ return [{
448
+ "type": "my_custom_type",
449
+ "handler": create_my_custom_model,
450
+ }]
451
+
452
+ Example return: [{"type": "antigravity", "handler": create_antigravity_model}]
453
+ """
454
+ return _trigger_callbacks_sync("register_model_type")
455
+
456
+
457
+ def on_get_model_system_prompt(
458
+ model_name: str, default_system_prompt: str, user_prompt: str
459
+ ) -> List[Dict[str, Any]]:
460
+ """Allow plugins to provide custom system prompts for specific model types.
461
+
462
+ This hook allows plugins to override the system prompt handling for custom
463
+ model types (like claude_code or antigravity models). Each callback receives
464
+ the model name and should return a dict if it handles that model type, or None.
465
+
466
+ Args:
467
+ model_name: The name of the model being used (e.g., "claude-code-sonnet")
468
+ default_system_prompt: The default system prompt from the agent
469
+ user_prompt: The user's prompt/message
470
+
471
+ Each callback should return a dict with:
472
+ - "instructions": str - the system prompt/instructions to use
473
+ - "user_prompt": str - the (possibly modified) user prompt
474
+ - "handled": bool - True if this callback handled the model
475
+
476
+ Or return None if the callback doesn't handle this model type.
477
+
478
+ Example callback:
479
+ def get_my_model_system_prompt(model_name, default_system_prompt, user_prompt):
480
+ if model_name.startswith("my-custom-"):
481
+ return {
482
+ "instructions": "You are MyCustomBot.",
483
+ "user_prompt": f"{default_system_prompt}\n\n{user_prompt}",
484
+ "handled": True,
485
+ }
486
+ return None # Not handled by this callback
487
+
488
+ Returns:
489
+ List of results from registered callbacks (dicts or None values).
490
+ """
491
+ return _trigger_callbacks_sync(
492
+ "get_model_system_prompt", model_name, default_system_prompt, user_prompt
493
+ )
494
+
495
+
496
+ async def on_agent_run_start(
497
+ agent_name: str,
498
+ model_name: str,
499
+ session_id: str | None = None,
500
+ ) -> List[Any]:
501
+ """Trigger callbacks when an agent run starts.
502
+
503
+ This fires at the beginning of run_with_mcp, before the agent task is created.
504
+ Useful for:
505
+ - Starting background tasks (like token refresh heartbeats)
506
+ - Logging/analytics
507
+ - Resource allocation
508
+
509
+ Args:
510
+ agent_name: Name of the agent starting
511
+ model_name: Name of the model being used
512
+ session_id: Optional session identifier
513
+
514
+ Returns:
515
+ List of results from registered callbacks.
516
+ """
517
+ return await _trigger_callbacks(
518
+ "agent_run_start", agent_name, model_name, session_id
519
+ )
520
+
521
+
522
+ async def on_agent_run_end(
523
+ agent_name: str,
524
+ model_name: str,
525
+ session_id: str | None = None,
526
+ success: bool = True,
527
+ error: Exception | None = None,
528
+ response_text: str | None = None,
529
+ metadata: dict | None = None,
530
+ ) -> List[Any]:
531
+ """Trigger callbacks when an agent run ends.
532
+
533
+ This fires at the end of run_with_mcp, in the finally block.
534
+ Always fires regardless of success/failure/cancellation.
535
+
536
+ Useful for:
537
+ - Stopping background tasks (like token refresh heartbeats)
538
+ - Workflow orchestration (like Ralph's autonomous loop)
539
+ - Logging/analytics
540
+ - Resource cleanup
541
+ - Detecting completion signals in responses
542
+
543
+ Args:
544
+ agent_name: Name of the agent that finished
545
+ model_name: Name of the model that was used
546
+ session_id: Optional session identifier
547
+ success: Whether the run completed successfully
548
+ error: Exception if the run failed, None otherwise
549
+ response_text: The final text response from the agent (if successful)
550
+ metadata: Optional dict with additional context (tokens used, etc.)
551
+
552
+ Returns:
553
+ List of results from registered callbacks.
554
+ """
555
+ return await _trigger_callbacks(
556
+ "agent_run_end",
557
+ agent_name,
558
+ model_name,
559
+ session_id,
560
+ success,
561
+ error,
562
+ response_text,
563
+ metadata,
564
+ )
565
+
566
+
567
+ def on_register_mcp_catalog_servers() -> List[Any]:
568
+ """Trigger callbacks to register additional MCP catalog servers.
569
+
570
+ Plugins can register callbacks that return List[MCPServerTemplate] to add
571
+ servers to the MCP catalog/marketplace.
572
+
573
+ Returns:
574
+ List of results from all registered callbacks (each should be a list of MCPServerTemplate).
575
+ """
576
+ return _trigger_callbacks_sync("register_mcp_catalog_servers")
577
+
578
+
579
+ def on_register_browser_types() -> List[Any]:
580
+ """Trigger callbacks to register custom browser types/providers.
581
+
582
+ Plugins can register callbacks that return a dict mapping browser type names
583
+ to initialization functions. This allows plugins to provide custom browser
584
+ implementations (like Camoufox for stealth browsing).
585
+
586
+ Each callback should return a dict with:
587
+ - key: str - the browser type name (e.g., "camoufox", "firefox-stealth")
588
+ - value: callable - async initialization function that takes (manager, **kwargs)
589
+ and sets up the browser on the manager instance
590
+
591
+ Example callback:
592
+ def register_my_browser_types():
593
+ return {
594
+ "camoufox": initialize_camoufox,
595
+ "my-stealth-browser": initialize_my_stealth,
596
+ }
597
+
598
+ Returns:
599
+ List of dicts from all registered callbacks.
600
+ """
601
+ return _trigger_callbacks_sync("register_browser_types")
602
+
603
+
604
+ def on_get_motd() -> List[Any]:
605
+ """Trigger callbacks to get custom MOTD content.
606
+
607
+ Plugins can register callbacks that return a tuple of (message, version).
608
+ The last non-None result will be used as the MOTD.
609
+
610
+ Returns:
611
+ List of (message, version) tuples from registered callbacks.
612
+ """
613
+ return _trigger_callbacks_sync("get_motd")
614
+
615
+
616
+ def on_register_model_providers() -> List[Any]:
617
+ """Trigger callbacks to register custom model provider classes.
618
+
619
+ Plugins can register callbacks that return a dict mapping provider names
620
+ to model classes. Example: {"walmart_gemini": WalmartGeminiModel}
621
+
622
+ Returns:
623
+ List of dicts from all registered callbacks.
624
+ """
625
+ return _trigger_callbacks_sync("register_model_providers")
626
+
627
+
628
+ def on_message_history_processor_start(
629
+ agent_name: str,
630
+ session_id: str | None,
631
+ message_history: List[Any],
632
+ incoming_messages: List[Any],
633
+ ) -> List[Any]:
634
+ """Trigger callbacks at the start of message history processing.
635
+
636
+ This hook fires at the beginning of the message_history_accumulator,
637
+ before any deduplication or processing occurs. Useful for:
638
+ - Logging/debugging message flow
639
+ - Observing raw incoming messages
640
+ - Analytics on message history growth
641
+
642
+ Args:
643
+ agent_name: Name of the agent processing messages
644
+ session_id: Optional session identifier
645
+ message_history: Current message history (before processing)
646
+ incoming_messages: New messages being added
647
+
648
+ Returns:
649
+ List of results from registered callbacks.
650
+ """
651
+ return _trigger_callbacks_sync(
652
+ "message_history_processor_start",
653
+ agent_name,
654
+ session_id,
655
+ message_history,
656
+ incoming_messages,
657
+ )
658
+
659
+
660
+ def on_message_history_processor_end(
661
+ agent_name: str,
662
+ session_id: str | None,
663
+ message_history: List[Any],
664
+ messages_added: int,
665
+ messages_filtered: int,
666
+ ) -> List[Any]:
667
+ """Trigger callbacks at the end of message history processing.
668
+
669
+ This hook fires at the end of the message_history_accumulator,
670
+ after deduplication and filtering has been applied. Useful for:
671
+ - Logging/debugging final message state
672
+ - Analytics on deduplication effectiveness
673
+ - Observing what was actually added to history
674
+
675
+ Args:
676
+ agent_name: Name of the agent processing messages
677
+ session_id: Optional session identifier
678
+ message_history: Final message history (after processing)
679
+ messages_added: Count of new messages that were added
680
+ messages_filtered: Count of messages that were filtered out (dupes/empty)
681
+
682
+ Returns:
683
+ List of results from registered callbacks.
684
+ """
685
+ return _trigger_callbacks_sync(
686
+ "message_history_processor_end",
687
+ agent_name,
688
+ session_id,
689
+ message_history,
690
+ messages_added,
691
+ messages_filtered,
692
+ )