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