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,176 @@
1
+ import asyncio
2
+ import atexit
3
+ import threading
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from typing import List
6
+
7
+ from pydantic_ai import Agent
8
+
9
+ from code_puppy.config import (
10
+ get_global_model_name,
11
+ )
12
+ from code_puppy.model_factory import ModelFactory, make_model_settings
13
+
14
+ # Keep a module-level agent reference to avoid rebuilding per call
15
+ _summarization_agent = None
16
+ _agent_lock = threading.Lock()
17
+
18
+ # Safe sync runner for async agent.run calls
19
+ # Avoids "event loop is already running" by offloading to a separate thread loop when needed
20
+ _thread_pool: ThreadPoolExecutor | None = None
21
+
22
+ # Reload counter
23
+ _reload_count = 0
24
+
25
+
26
+ def _ensure_thread_pool():
27
+ global _thread_pool
28
+ # Check if pool is None OR if it's been shutdown
29
+ if _thread_pool is None or _thread_pool._shutdown:
30
+ _thread_pool = ThreadPoolExecutor(
31
+ max_workers=1, thread_name_prefix="summarizer-loop"
32
+ )
33
+ return _thread_pool
34
+
35
+
36
+ def _shutdown_thread_pool():
37
+ global _thread_pool
38
+ if _thread_pool is not None:
39
+ _thread_pool.shutdown(wait=False)
40
+ _thread_pool = None
41
+
42
+
43
+ atexit.register(_shutdown_thread_pool)
44
+
45
+
46
+ async def _run_agent_async(agent: Agent, prompt: str, message_history: List):
47
+ return await agent.run(prompt, message_history=message_history)
48
+
49
+
50
+ class SummarizationError(Exception):
51
+ """Raised when summarization fails with details about the failure."""
52
+
53
+ def __init__(self, message: str, original_error: Exception | None = None):
54
+ self.original_error = original_error
55
+ super().__init__(message)
56
+
57
+
58
+ def run_summarization_sync(prompt: str, message_history: List) -> List:
59
+ """Run the summarization agent synchronously.
60
+
61
+ Raises:
62
+ SummarizationError: If summarization fails for any reason.
63
+ """
64
+ try:
65
+ agent = get_summarization_agent()
66
+ except Exception as e:
67
+ raise SummarizationError(
68
+ f"Failed to initialize summarization agent: {type(e).__name__}: {e}",
69
+ original_error=e,
70
+ ) from e
71
+
72
+ # Handle claude-code models: prepend system prompt to user prompt
73
+ from code_puppy.model_utils import prepare_prompt_for_model
74
+
75
+ model_name = get_global_model_name()
76
+ prepared = prepare_prompt_for_model(
77
+ model_name, _get_summarization_instructions(), prompt
78
+ )
79
+ prompt = prepared.user_prompt
80
+
81
+ def _run_in_thread():
82
+ """
83
+ Run the async agent in a dedicated thread with its own event loop.
84
+ Uses run_until_complete instead of asyncio.run to avoid shutting down
85
+ the default executor (which breaks DBOS in the main thread).
86
+ Does NOT touch global event loop state.
87
+ """
88
+ loop = asyncio.new_event_loop()
89
+ try:
90
+ coro = agent.run(prompt, message_history=message_history)
91
+ return loop.run_until_complete(coro)
92
+ finally:
93
+ # Clean up without shutting down the default executor
94
+ try:
95
+ # Cancel pending tasks
96
+ pending = asyncio.all_tasks(loop)
97
+ for task in pending:
98
+ task.cancel()
99
+ if pending:
100
+ loop.run_until_complete(
101
+ asyncio.gather(*pending, return_exceptions=True)
102
+ )
103
+ loop.run_until_complete(loop.shutdown_asyncgens())
104
+ finally:
105
+ loop.close()
106
+
107
+ try:
108
+ # Always use thread pool since we're likely in an existing event loop
109
+ pool = _ensure_thread_pool()
110
+ result = pool.submit(_run_in_thread).result()
111
+ return result.new_messages()
112
+ except Exception as e:
113
+ error_type = type(e).__name__
114
+ error_msg = str(e) if str(e) else "(no details available)"
115
+ raise SummarizationError(
116
+ f"LLM call failed during summarization: [{error_type}] {error_msg}",
117
+ original_error=e,
118
+ ) from e
119
+
120
+
121
+ def _get_summarization_instructions() -> str:
122
+ """Get the system instructions for the summarization agent."""
123
+ return """You are a message summarization expert. Your task is to summarize conversation messages
124
+ while preserving important context and information. The summaries should be concise but capture the essential content
125
+ and intent of the original messages. This is to help manage token usage in a conversation history
126
+ while maintaining context for the AI to continue the conversation effectively.
127
+
128
+ When summarizing:
129
+ 1. Keep summary concise but informative
130
+ 2. Preserve important context and key information and decisions
131
+ 3. Keep any important technical details
132
+ 4. Don't summarize the system message
133
+ 5. Make sure all tool calls and responses are summarized, as they are vital
134
+ 6. Focus on token usage efficiency and system message preservation"""
135
+
136
+
137
+ def reload_summarization_agent():
138
+ """Create a specialized agent for summarizing messages when context limit is reached."""
139
+ from code_puppy.model_utils import prepare_prompt_for_model
140
+
141
+ models_config = ModelFactory.load_config()
142
+ model_name = get_global_model_name()
143
+ model = ModelFactory.get_model(model_name, models_config)
144
+
145
+ # Handle claude-code models: swap instructions (prompt prepending happens in run_summarization_sync)
146
+ instructions = _get_summarization_instructions()
147
+ prepared = prepare_prompt_for_model(
148
+ model_name, instructions, "", prepend_system_to_user=False
149
+ )
150
+ instructions = prepared.instructions
151
+
152
+ model_settings = make_model_settings(model_name)
153
+
154
+ agent = Agent(
155
+ model=model,
156
+ instructions=instructions,
157
+ output_type=str,
158
+ retries=1, # Fewer retries for summarization
159
+ model_settings=model_settings,
160
+ )
161
+ # NOTE: We intentionally DON'T wrap in DBOSAgent here.
162
+ # Summarization is a simple one-shot call that doesn't need durable execution,
163
+ # and DBOSAgent causes async event loop conflicts with run_sync().
164
+ return agent
165
+
166
+
167
+ def get_summarization_agent(force_reload=True):
168
+ """
169
+ Retrieve the summarization agent with the currently set MODEL_NAME.
170
+ Forces a reload if the model has changed, or if force_reload is passed.
171
+ """
172
+ global _summarization_agent
173
+ with _agent_lock:
174
+ if force_reload or _summarization_agent is None:
175
+ _summarization_agent = reload_summarization_agent()
176
+ return _summarization_agent
@@ -0,0 +1,418 @@
1
+ """Terminal utilities for cross-platform terminal state management.
2
+
3
+ Handles Windows console mode resets and Unix terminal sanity restoration.
4
+ """
5
+
6
+ import os
7
+ import platform
8
+ import subprocess
9
+ import sys
10
+ from typing import TYPE_CHECKING, Callable, Optional
11
+
12
+ if TYPE_CHECKING:
13
+ from rich.console import Console
14
+
15
+ # Store the original console ctrl handler so we can restore it if needed
16
+ _original_ctrl_handler: Optional[Callable] = None
17
+
18
+
19
+ def reset_windows_terminal_ansi() -> None:
20
+ """Reset ANSI formatting on Windows stdout/stderr.
21
+
22
+ This is a lightweight reset that just clears ANSI escape sequences.
23
+ Use this for quick resets after output operations.
24
+ """
25
+ if platform.system() != "Windows":
26
+ return
27
+
28
+ try:
29
+ sys.stdout.write("\x1b[0m") # Reset ANSI formatting
30
+ sys.stdout.flush()
31
+ sys.stderr.write("\x1b[0m")
32
+ sys.stderr.flush()
33
+ except Exception:
34
+ pass # Silently ignore errors - best effort reset
35
+
36
+
37
+ def reset_windows_console_mode() -> None:
38
+ """Full Windows console mode reset using ctypes.
39
+
40
+ This resets both stdout and stdin console modes to restore proper
41
+ terminal behavior after interrupts (Ctrl+C, Ctrl+D). Without this,
42
+ the terminal can become unresponsive (can't type characters).
43
+ """
44
+ if platform.system() != "Windows":
45
+ return
46
+
47
+ try:
48
+ import ctypes
49
+
50
+ kernel32 = ctypes.windll.kernel32
51
+
52
+ # Reset stdout
53
+ STD_OUTPUT_HANDLE = -11
54
+ handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
55
+
56
+ # Enable virtual terminal processing and line input
57
+ mode = ctypes.c_ulong()
58
+ kernel32.GetConsoleMode(handle, ctypes.byref(mode))
59
+
60
+ # Console mode flags for stdout
61
+ ENABLE_PROCESSED_OUTPUT = 0x0001
62
+ ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
63
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
64
+
65
+ new_mode = (
66
+ mode.value
67
+ | ENABLE_PROCESSED_OUTPUT
68
+ | ENABLE_WRAP_AT_EOL_OUTPUT
69
+ | ENABLE_VIRTUAL_TERMINAL_PROCESSING
70
+ )
71
+ kernel32.SetConsoleMode(handle, new_mode)
72
+
73
+ # Reset stdin
74
+ STD_INPUT_HANDLE = -10
75
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
76
+
77
+ # Console mode flags for stdin
78
+ ENABLE_LINE_INPUT = 0x0002
79
+ ENABLE_ECHO_INPUT = 0x0004
80
+ ENABLE_PROCESSED_INPUT = 0x0001
81
+
82
+ stdin_mode = ctypes.c_ulong()
83
+ kernel32.GetConsoleMode(stdin_handle, ctypes.byref(stdin_mode))
84
+
85
+ new_stdin_mode = (
86
+ stdin_mode.value
87
+ | ENABLE_LINE_INPUT
88
+ | ENABLE_ECHO_INPUT
89
+ | ENABLE_PROCESSED_INPUT
90
+ )
91
+ kernel32.SetConsoleMode(stdin_handle, new_stdin_mode)
92
+
93
+ except Exception:
94
+ pass # Silently ignore errors - best effort reset
95
+
96
+
97
+ def flush_windows_keyboard_buffer() -> None:
98
+ """Flush the Windows keyboard buffer.
99
+
100
+ Clears any pending keyboard input that could interfere with
101
+ subsequent input operations after an interrupt.
102
+ """
103
+ if platform.system() != "Windows":
104
+ return
105
+
106
+ try:
107
+ import msvcrt
108
+
109
+ while msvcrt.kbhit():
110
+ msvcrt.getch()
111
+ except Exception:
112
+ pass # Silently ignore errors - best effort flush
113
+
114
+
115
+ def reset_windows_terminal_full() -> None:
116
+ """Perform a full Windows terminal reset (ANSI + console mode + keyboard buffer).
117
+
118
+ Combines ANSI reset, console mode reset, and keyboard buffer flush
119
+ for complete terminal state restoration after interrupts.
120
+ """
121
+ if platform.system() != "Windows":
122
+ return
123
+
124
+ reset_windows_terminal_ansi()
125
+ reset_windows_console_mode()
126
+ flush_windows_keyboard_buffer()
127
+
128
+
129
+ def reset_unix_terminal() -> None:
130
+ """Reset Unix/Linux/macOS terminal to sane state.
131
+
132
+ Uses the `reset` command to restore terminal sanity.
133
+ Silently fails if the command isn't available.
134
+ """
135
+ if platform.system() == "Windows":
136
+ return
137
+
138
+ try:
139
+ subprocess.run(["reset"], check=True, capture_output=True)
140
+ except (subprocess.CalledProcessError, FileNotFoundError):
141
+ pass # Silently fail if reset command isn't available
142
+
143
+
144
+ def reset_terminal() -> None:
145
+ """Cross-platform terminal reset.
146
+
147
+ Automatically detects the platform and performs the appropriate
148
+ terminal reset operation.
149
+ """
150
+ if platform.system() == "Windows":
151
+ reset_windows_terminal_full()
152
+ else:
153
+ reset_unix_terminal()
154
+
155
+
156
+ def disable_windows_ctrl_c() -> bool:
157
+ """Disable Ctrl+C processing at the Windows console input level.
158
+
159
+ This removes ENABLE_PROCESSED_INPUT from stdin, which prevents
160
+ Ctrl+C from being interpreted as a signal at all. Instead, it
161
+ becomes just a regular character (^C) that gets ignored.
162
+
163
+ This is more reliable than SetConsoleCtrlHandler because it
164
+ prevents Ctrl+C from being processed before it reaches any handler.
165
+
166
+ Returns:
167
+ True if successfully disabled, False otherwise.
168
+ """
169
+ global _original_ctrl_handler
170
+
171
+ if platform.system() != "Windows":
172
+ return False
173
+
174
+ try:
175
+ import ctypes
176
+
177
+ kernel32 = ctypes.windll.kernel32
178
+
179
+ # Get stdin handle
180
+ STD_INPUT_HANDLE = -10
181
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
182
+
183
+ # Get current console mode
184
+ mode = ctypes.c_ulong()
185
+ if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
186
+ return False
187
+
188
+ # Save original mode for potential restoration
189
+ _original_ctrl_handler = mode.value
190
+
191
+ # Console mode flags
192
+ ENABLE_PROCESSED_INPUT = 0x0001 # This makes Ctrl+C generate signals
193
+
194
+ # Remove ENABLE_PROCESSED_INPUT to disable Ctrl+C signal generation
195
+ new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
196
+
197
+ if kernel32.SetConsoleMode(stdin_handle, new_mode):
198
+ return True
199
+ return False
200
+
201
+ except Exception:
202
+ return False
203
+
204
+
205
+ def enable_windows_ctrl_c() -> bool:
206
+ """Re-enable Ctrl+C at the Windows console level.
207
+
208
+ Restores the original console mode saved by disable_windows_ctrl_c().
209
+
210
+ Returns:
211
+ True if successfully re-enabled, False otherwise.
212
+ """
213
+ global _original_ctrl_handler
214
+
215
+ if platform.system() != "Windows":
216
+ return False
217
+
218
+ if _original_ctrl_handler is None:
219
+ return True # Nothing to restore
220
+
221
+ try:
222
+ import ctypes
223
+
224
+ kernel32 = ctypes.windll.kernel32
225
+
226
+ # Get stdin handle
227
+ STD_INPUT_HANDLE = -10
228
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
229
+
230
+ # Restore original mode
231
+ if kernel32.SetConsoleMode(stdin_handle, _original_ctrl_handler):
232
+ _original_ctrl_handler = None
233
+ return True
234
+ return False
235
+
236
+ except Exception:
237
+ return False
238
+
239
+
240
+ # Flag to track if we should keep Ctrl+C disabled
241
+ _keep_ctrl_c_disabled: bool = False
242
+
243
+
244
+ def set_keep_ctrl_c_disabled(value: bool) -> None:
245
+ """Set whether Ctrl+C should be kept disabled.
246
+
247
+ When True, ensure_ctrl_c_disabled() will re-disable Ctrl+C
248
+ even if something else (like prompt_toolkit) re-enables it.
249
+ """
250
+ global _keep_ctrl_c_disabled
251
+ _keep_ctrl_c_disabled = value
252
+
253
+
254
+ def ensure_ctrl_c_disabled() -> bool:
255
+ """Ensure Ctrl+C is disabled if it should be.
256
+
257
+ Call this after operations that might restore console mode
258
+ (like prompt_toolkit input).
259
+
260
+ Returns:
261
+ True if Ctrl+C is now disabled (or wasn't needed), False on error.
262
+ """
263
+ if not _keep_ctrl_c_disabled:
264
+ return True
265
+
266
+ if platform.system() != "Windows":
267
+ return True
268
+
269
+ try:
270
+ import ctypes
271
+
272
+ kernel32 = ctypes.windll.kernel32
273
+
274
+ # Get stdin handle
275
+ STD_INPUT_HANDLE = -10
276
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
277
+
278
+ # Get current console mode
279
+ mode = ctypes.c_ulong()
280
+ if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
281
+ return False
282
+
283
+ # Console mode flags
284
+ ENABLE_PROCESSED_INPUT = 0x0001
285
+
286
+ # Check if Ctrl+C processing is enabled
287
+ if mode.value & ENABLE_PROCESSED_INPUT:
288
+ # Disable it
289
+ new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
290
+ return bool(kernel32.SetConsoleMode(stdin_handle, new_mode))
291
+
292
+ return True # Already disabled
293
+
294
+ except Exception:
295
+ return False
296
+
297
+
298
+ def detect_truecolor_support() -> bool:
299
+ """Detect if the terminal supports truecolor (24-bit color).
300
+
301
+ Checks multiple indicators:
302
+ 1. COLORTERM environment variable (most reliable)
303
+ 2. TERM environment variable patterns
304
+ 3. Rich's Console color_system detection as fallback
305
+
306
+ Returns:
307
+ True if truecolor is supported, False otherwise.
308
+ """
309
+ # Check COLORTERM - this is the most reliable indicator
310
+ colorterm = os.environ.get("COLORTERM", "").lower()
311
+ if colorterm in ("truecolor", "24bit"):
312
+ return True
313
+
314
+ # Check TERM for known truecolor-capable terminals
315
+ term = os.environ.get("TERM", "").lower()
316
+ truecolor_terms = (
317
+ "xterm-direct",
318
+ "xterm-truecolor",
319
+ "iterm2",
320
+ "vte-256color", # Many modern terminals set this
321
+ )
322
+ if any(t in term for t in truecolor_terms):
323
+ return True
324
+
325
+ # Some terminals like iTerm2, Kitty, Alacritty set specific env vars
326
+ if os.environ.get("ITERM_SESSION_ID"):
327
+ return True
328
+ if os.environ.get("KITTY_WINDOW_ID"):
329
+ return True
330
+ if os.environ.get("ALACRITTY_SOCKET"):
331
+ return True
332
+ if os.environ.get("WT_SESSION"): # Windows Terminal
333
+ return True
334
+
335
+ # Use Rich's detection as a fallback
336
+ try:
337
+ from rich.console import Console
338
+
339
+ console = Console(force_terminal=True)
340
+ color_system = console.color_system
341
+ return color_system == "truecolor"
342
+ except Exception:
343
+ pass
344
+
345
+ return False
346
+
347
+
348
+ def print_truecolor_warning(console: Optional["Console"] = None) -> None:
349
+ """Print a big fat red warning if truecolor is not supported.
350
+
351
+ Args:
352
+ console: Optional Rich Console instance. If None, creates a new one.
353
+ """
354
+ if detect_truecolor_support():
355
+ return # All good, no warning needed
356
+
357
+ if console is None:
358
+ try:
359
+ from rich.console import Console
360
+
361
+ console = Console()
362
+ except ImportError:
363
+ # Rich not available, fall back to plain print
364
+ print("\n" + "=" * 70)
365
+ print("⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR)")
366
+ print("=" * 70)
367
+ print("Code Puppy looks best with truecolor support.")
368
+ print("Consider using a modern terminal like:")
369
+ print(" • iTerm2 (macOS)")
370
+ print(" • Windows Terminal (Windows)")
371
+ print(" • Kitty, Alacritty, or any modern terminal emulator")
372
+ print("")
373
+ print("You can also try setting: export COLORTERM=truecolor")
374
+ print("")
375
+ print("Note: The built-in macOS Terminal.app does not support truecolor")
376
+ print("(Sequoia and earlier). You'll need a different terminal app.")
377
+ print("=" * 70 + "\n")
378
+ return
379
+
380
+ # Get detected color system for diagnostic info
381
+ color_system = console.color_system or "unknown"
382
+
383
+ # Build the warning box
384
+ warning_lines = [
385
+ "",
386
+ "[bold bright_red on red]" + "━" * 72 + "[/]",
387
+ "[bold bright_red on red]┃[/][bold bright_white on red]"
388
+ + " " * 70
389
+ + "[/][bold bright_red on red]┃[/]",
390
+ "[bold bright_red on red]┃[/][bold bright_white on red] ⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR) ⚠️ [/][bold bright_red on red]┃[/]",
391
+ "[bold bright_red on red]┃[/][bold bright_white on red]"
392
+ + " " * 70
393
+ + "[/][bold bright_red on red]┃[/]",
394
+ "[bold bright_red on red]" + "━" * 72 + "[/]",
395
+ "",
396
+ f"[yellow]Detected color system:[/] [bold]{color_system}[/]",
397
+ "",
398
+ "[bold white]Code Puppy uses rich colors and will look degraded without truecolor.[/]",
399
+ "",
400
+ "[cyan]Consider using a modern terminal emulator:[/]",
401
+ " [green]•[/] [bold]iTerm2[/] (macOS) - https://iterm2.com",
402
+ " [green]•[/] [bold]Windows Terminal[/] (Windows) - Built into Windows 11",
403
+ " [green]•[/] [bold]Kitty[/] - https://sw.kovidgoyal.net/kitty",
404
+ " [green]•[/] [bold]Alacritty[/] - https://alacritty.org",
405
+ " [green]•[/] [bold]Warp[/] (macOS) - https://warp.dev",
406
+ "",
407
+ "[cyan]Or try setting the COLORTERM environment variable:[/]",
408
+ " [dim]export COLORTERM=truecolor[/]",
409
+ "",
410
+ "[dim italic]Note: The built-in macOS Terminal.app does not support truecolor (Sequoia and earlier).[/]",
411
+ "[dim italic]Setting COLORTERM=truecolor won't help - you'll need a different terminal app.[/]",
412
+ "",
413
+ "[bold bright_red]" + "─" * 72 + "[/]",
414
+ "",
415
+ ]
416
+
417
+ for line in warning_lines:
418
+ console.print(line)