code-puppy 0.0.214__py3-none-any.whl → 0.0.366__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 (231) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +2 -0
  3. code_puppy/agents/agent_c_reviewer.py +59 -6
  4. code_puppy/agents/agent_code_puppy.py +7 -1
  5. code_puppy/agents/agent_code_reviewer.py +12 -2
  6. code_puppy/agents/agent_cpp_reviewer.py +73 -6
  7. code_puppy/agents/agent_creator_agent.py +45 -4
  8. code_puppy/agents/agent_golang_reviewer.py +92 -3
  9. code_puppy/agents/agent_javascript_reviewer.py +101 -8
  10. code_puppy/agents/agent_manager.py +81 -4
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +28 -6
  15. code_puppy/agents/agent_qa_expert.py +98 -6
  16. code_puppy/agents/agent_qa_kitten.py +12 -7
  17. code_puppy/agents/agent_security_auditor.py +113 -3
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +106 -7
  20. code_puppy/agents/base_agent.py +802 -176
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/pack/__init__.py +34 -0
  23. code_puppy/agents/pack/bloodhound.py +304 -0
  24. code_puppy/agents/pack/husky.py +321 -0
  25. code_puppy/agents/pack/retriever.py +393 -0
  26. code_puppy/agents/pack/shepherd.py +348 -0
  27. code_puppy/agents/pack/terrier.py +287 -0
  28. code_puppy/agents/pack/watchdog.py +367 -0
  29. code_puppy/agents/prompt_reviewer.py +145 -0
  30. code_puppy/agents/subagent_stream_handler.py +276 -0
  31. code_puppy/api/__init__.py +13 -0
  32. code_puppy/api/app.py +169 -0
  33. code_puppy/api/main.py +21 -0
  34. code_puppy/api/pty_manager.py +446 -0
  35. code_puppy/api/routers/__init__.py +12 -0
  36. code_puppy/api/routers/agents.py +36 -0
  37. code_puppy/api/routers/commands.py +217 -0
  38. code_puppy/api/routers/config.py +74 -0
  39. code_puppy/api/routers/sessions.py +232 -0
  40. code_puppy/api/templates/terminal.html +361 -0
  41. code_puppy/api/websocket.py +154 -0
  42. code_puppy/callbacks.py +142 -4
  43. code_puppy/chatgpt_codex_client.py +283 -0
  44. code_puppy/claude_cache_client.py +586 -0
  45. code_puppy/cli_runner.py +916 -0
  46. code_puppy/command_line/add_model_menu.py +1079 -0
  47. code_puppy/command_line/agent_menu.py +395 -0
  48. code_puppy/command_line/attachments.py +10 -5
  49. code_puppy/command_line/autosave_menu.py +605 -0
  50. code_puppy/command_line/clipboard.py +527 -0
  51. code_puppy/command_line/colors_menu.py +520 -0
  52. code_puppy/command_line/command_handler.py +176 -738
  53. code_puppy/command_line/command_registry.py +150 -0
  54. code_puppy/command_line/config_commands.py +715 -0
  55. code_puppy/command_line/core_commands.py +792 -0
  56. code_puppy/command_line/diff_menu.py +863 -0
  57. code_puppy/command_line/load_context_completion.py +15 -22
  58. code_puppy/command_line/mcp/base.py +0 -3
  59. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  60. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  61. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  62. code_puppy/command_line/mcp/edit_command.py +148 -0
  63. code_puppy/command_line/mcp/handler.py +9 -4
  64. code_puppy/command_line/mcp/help_command.py +6 -5
  65. code_puppy/command_line/mcp/install_command.py +15 -26
  66. code_puppy/command_line/mcp/install_menu.py +685 -0
  67. code_puppy/command_line/mcp/list_command.py +2 -2
  68. code_puppy/command_line/mcp/logs_command.py +174 -65
  69. code_puppy/command_line/mcp/remove_command.py +2 -2
  70. code_puppy/command_line/mcp/restart_command.py +12 -4
  71. code_puppy/command_line/mcp/search_command.py +16 -10
  72. code_puppy/command_line/mcp/start_all_command.py +18 -6
  73. code_puppy/command_line/mcp/start_command.py +47 -25
  74. code_puppy/command_line/mcp/status_command.py +4 -5
  75. code_puppy/command_line/mcp/stop_all_command.py +7 -1
  76. code_puppy/command_line/mcp/stop_command.py +8 -4
  77. code_puppy/command_line/mcp/test_command.py +2 -2
  78. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  79. code_puppy/command_line/mcp_completion.py +174 -0
  80. code_puppy/command_line/model_picker_completion.py +75 -25
  81. code_puppy/command_line/model_settings_menu.py +884 -0
  82. code_puppy/command_line/motd.py +14 -8
  83. code_puppy/command_line/onboarding_slides.py +179 -0
  84. code_puppy/command_line/onboarding_wizard.py +340 -0
  85. code_puppy/command_line/pin_command_completion.py +329 -0
  86. code_puppy/command_line/prompt_toolkit_completion.py +463 -63
  87. code_puppy/command_line/session_commands.py +296 -0
  88. code_puppy/command_line/utils.py +54 -0
  89. code_puppy/config.py +898 -112
  90. code_puppy/error_logging.py +118 -0
  91. code_puppy/gemini_code_assist.py +385 -0
  92. code_puppy/gemini_model.py +602 -0
  93. code_puppy/http_utils.py +210 -148
  94. code_puppy/keymap.py +128 -0
  95. code_puppy/main.py +5 -698
  96. code_puppy/mcp_/__init__.py +17 -0
  97. code_puppy/mcp_/async_lifecycle.py +35 -4
  98. code_puppy/mcp_/blocking_startup.py +70 -43
  99. code_puppy/mcp_/captured_stdio_server.py +2 -2
  100. code_puppy/mcp_/config_wizard.py +4 -4
  101. code_puppy/mcp_/dashboard.py +15 -6
  102. code_puppy/mcp_/managed_server.py +65 -38
  103. code_puppy/mcp_/manager.py +146 -52
  104. code_puppy/mcp_/mcp_logs.py +224 -0
  105. code_puppy/mcp_/registry.py +6 -6
  106. code_puppy/mcp_/server_registry_catalog.py +24 -5
  107. code_puppy/messaging/__init__.py +199 -2
  108. code_puppy/messaging/bus.py +610 -0
  109. code_puppy/messaging/commands.py +167 -0
  110. code_puppy/messaging/markdown_patches.py +57 -0
  111. code_puppy/messaging/message_queue.py +17 -48
  112. code_puppy/messaging/messages.py +500 -0
  113. code_puppy/messaging/queue_console.py +1 -24
  114. code_puppy/messaging/renderers.py +43 -146
  115. code_puppy/messaging/rich_renderer.py +1027 -0
  116. code_puppy/messaging/spinner/__init__.py +21 -5
  117. code_puppy/messaging/spinner/console_spinner.py +86 -51
  118. code_puppy/messaging/subagent_console.py +461 -0
  119. code_puppy/model_factory.py +634 -83
  120. code_puppy/model_utils.py +167 -0
  121. code_puppy/models.json +66 -68
  122. code_puppy/models_dev_api.json +1 -0
  123. code_puppy/models_dev_parser.py +592 -0
  124. code_puppy/plugins/__init__.py +164 -10
  125. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  126. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  127. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  128. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  129. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  130. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  131. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  132. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  133. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  134. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  135. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  136. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  137. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  138. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  139. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  140. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  141. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  142. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  143. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  144. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  145. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  146. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  147. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  148. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  149. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  150. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  152. code_puppy/plugins/example_custom_command/README.md +280 -0
  153. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  154. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  155. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  156. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  157. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  158. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  159. code_puppy/plugins/oauth_puppy_html.py +228 -0
  160. code_puppy/plugins/shell_safety/__init__.py +6 -0
  161. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  162. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  163. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  164. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  165. code_puppy/prompts/codex_system_prompt.md +310 -0
  166. code_puppy/pydantic_patches.py +131 -0
  167. code_puppy/reopenable_async_client.py +8 -8
  168. code_puppy/round_robin_model.py +9 -12
  169. code_puppy/session_storage.py +2 -1
  170. code_puppy/status_display.py +21 -4
  171. code_puppy/summarization_agent.py +41 -13
  172. code_puppy/terminal_utils.py +418 -0
  173. code_puppy/tools/__init__.py +37 -1
  174. code_puppy/tools/agent_tools.py +536 -52
  175. code_puppy/tools/browser/__init__.py +37 -0
  176. code_puppy/tools/browser/browser_control.py +19 -23
  177. code_puppy/tools/browser/browser_interactions.py +41 -48
  178. code_puppy/tools/browser/browser_locators.py +36 -38
  179. code_puppy/tools/browser/browser_manager.py +316 -0
  180. code_puppy/tools/browser/browser_navigation.py +16 -16
  181. code_puppy/tools/browser/browser_screenshot.py +79 -143
  182. code_puppy/tools/browser/browser_scripts.py +32 -42
  183. code_puppy/tools/browser/browser_workflows.py +44 -27
  184. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  185. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  186. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  187. code_puppy/tools/browser/terminal_tools.py +525 -0
  188. code_puppy/tools/command_runner.py +930 -147
  189. code_puppy/tools/common.py +1113 -5
  190. code_puppy/tools/display.py +84 -0
  191. code_puppy/tools/file_modifications.py +288 -89
  192. code_puppy/tools/file_operations.py +226 -154
  193. code_puppy/tools/subagent_context.py +158 -0
  194. code_puppy/uvx_detection.py +242 -0
  195. code_puppy/version_checker.py +30 -11
  196. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  197. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  198. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
  199. code_puppy-0.0.366.dist-info/RECORD +217 -0
  200. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  201. code_puppy/command_line/mcp/add_command.py +0 -183
  202. code_puppy/messaging/spinner/textual_spinner.py +0 -106
  203. code_puppy/tools/browser/camoufox_manager.py +0 -216
  204. code_puppy/tools/browser/vqa_agent.py +0 -70
  205. code_puppy/tui/__init__.py +0 -10
  206. code_puppy/tui/app.py +0 -1105
  207. code_puppy/tui/components/__init__.py +0 -21
  208. code_puppy/tui/components/chat_view.py +0 -551
  209. code_puppy/tui/components/command_history_modal.py +0 -218
  210. code_puppy/tui/components/copy_button.py +0 -139
  211. code_puppy/tui/components/custom_widgets.py +0 -63
  212. code_puppy/tui/components/human_input_modal.py +0 -175
  213. code_puppy/tui/components/input_area.py +0 -167
  214. code_puppy/tui/components/sidebar.py +0 -309
  215. code_puppy/tui/components/status_bar.py +0 -185
  216. code_puppy/tui/messages.py +0 -27
  217. code_puppy/tui/models/__init__.py +0 -8
  218. code_puppy/tui/models/chat_message.py +0 -25
  219. code_puppy/tui/models/command_history.py +0 -89
  220. code_puppy/tui/models/enums.py +0 -24
  221. code_puppy/tui/screens/__init__.py +0 -17
  222. code_puppy/tui/screens/autosave_picker.py +0 -175
  223. code_puppy/tui/screens/help.py +0 -130
  224. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  225. code_puppy/tui/screens/settings.py +0 -306
  226. code_puppy/tui/screens/tools.py +0 -74
  227. code_puppy/tui_state.py +0 -55
  228. code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
  229. code_puppy-0.0.214.dist-info/RECORD +0 -131
  230. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
  231. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,296 @@
1
+ """Command handlers for Code Puppy - SESSION commands.
2
+
3
+ This module contains @register_command decorated handlers that are automatically
4
+ discovered by the command registry system.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
10
+ from code_puppy.command_line.command_registry import register_command
11
+ from code_puppy.config import CONTEXTS_DIR
12
+ from code_puppy.session_storage import list_sessions, load_session, save_session
13
+
14
+
15
+ # Import get_commands_help from command_handler to avoid circular imports
16
+ # This will be defined in command_handler.py
17
+ def get_commands_help():
18
+ """Lazy import to avoid circular dependency."""
19
+ from code_puppy.command_line.command_handler import get_commands_help as _gch
20
+
21
+ return _gch()
22
+
23
+
24
+ @register_command(
25
+ name="session",
26
+ description="Show or rotate autosave session ID",
27
+ usage="/session [id|new]",
28
+ aliases=["s"],
29
+ category="session",
30
+ detailed_help="""
31
+ Manage autosave sessions.
32
+
33
+ Commands:
34
+ /session Show current session ID
35
+ /session id Show current session ID
36
+ /session new Create new session and rotate ID
37
+
38
+ Sessions are used for auto-saving conversation history.
39
+ """,
40
+ )
41
+ def handle_session_command(command: str) -> bool:
42
+ """Handle /session command."""
43
+ from code_puppy.config import (
44
+ AUTOSAVE_DIR,
45
+ get_current_autosave_id,
46
+ get_current_autosave_session_name,
47
+ rotate_autosave_id,
48
+ )
49
+ from code_puppy.messaging import emit_info, emit_success, emit_warning
50
+
51
+ tokens = command.split()
52
+
53
+ if len(tokens) == 1 or tokens[1] == "id":
54
+ sid = get_current_autosave_id()
55
+ emit_info(
56
+ f"[bold magenta]Autosave Session[/bold magenta]: {sid}\n"
57
+ f"Files prefix: {Path(AUTOSAVE_DIR) / get_current_autosave_session_name()}"
58
+ )
59
+ return True
60
+ if tokens[1] == "new":
61
+ new_sid = rotate_autosave_id()
62
+ emit_success(f"New autosave session id: {new_sid}")
63
+ return True
64
+ emit_warning("Usage: /session [id|new]")
65
+ return True
66
+
67
+
68
+ @register_command(
69
+ name="compact",
70
+ description="Summarize and compact current chat history (uses compaction_strategy config)",
71
+ usage="/compact",
72
+ category="session",
73
+ )
74
+ def handle_compact_command(command: str) -> bool:
75
+ """Compact message history using configured strategy."""
76
+ from code_puppy.agents.agent_manager import get_current_agent
77
+ from code_puppy.config import get_compaction_strategy, get_protected_token_count
78
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
79
+
80
+ try:
81
+ agent = get_current_agent()
82
+ history = agent.get_message_history()
83
+ if not history:
84
+ emit_warning("No history to compact yet. Ask me something first!")
85
+ return True
86
+
87
+ current_agent = get_current_agent()
88
+ before_tokens = sum(
89
+ current_agent.estimate_tokens_for_message(m) for m in history
90
+ )
91
+ compaction_strategy = get_compaction_strategy()
92
+ protected_tokens = get_protected_token_count()
93
+ emit_info(
94
+ f"🤔 Compacting {len(history)} messages using {compaction_strategy} strategy... (~{before_tokens} tokens)"
95
+ )
96
+
97
+ current_agent = get_current_agent()
98
+ if compaction_strategy == "truncation":
99
+ compacted = current_agent.truncation(history, protected_tokens)
100
+ summarized_messages = [] # No summarization in truncation mode
101
+ else:
102
+ # Default to summarization
103
+ compacted, summarized_messages = current_agent.summarize_messages(
104
+ history, with_protection=True
105
+ )
106
+
107
+ if not compacted:
108
+ emit_error("Compaction failed. History unchanged.")
109
+ return True
110
+
111
+ agent.set_message_history(compacted)
112
+
113
+ current_agent = get_current_agent()
114
+ after_tokens = sum(
115
+ current_agent.estimate_tokens_for_message(m) for m in compacted
116
+ )
117
+ reduction_pct = (
118
+ ((before_tokens - after_tokens) / before_tokens * 100)
119
+ if before_tokens > 0
120
+ else 0
121
+ )
122
+
123
+ strategy_info = (
124
+ f"using {compaction_strategy} strategy"
125
+ if compaction_strategy == "truncation"
126
+ else "via summarization"
127
+ )
128
+ emit_success(
129
+ f"✨ Done! History: {len(history)} → {len(compacted)} messages {strategy_info}\n"
130
+ f"🏦 Tokens: {before_tokens:,} → {after_tokens:,} ({reduction_pct:.1f}% reduction)"
131
+ )
132
+ return True
133
+ except Exception as e:
134
+ emit_error(f"/compact error: {e}")
135
+ return True
136
+
137
+
138
+ @register_command(
139
+ name="truncate",
140
+ description="Truncate history to N most recent messages (e.g., /truncate 10)",
141
+ usage="/truncate <N>",
142
+ category="session",
143
+ )
144
+ def handle_truncate_command(command: str) -> bool:
145
+ """Truncate message history to N most recent messages."""
146
+ from code_puppy.agents.agent_manager import get_current_agent
147
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
148
+
149
+ tokens = command.split()
150
+ if len(tokens) != 2:
151
+ emit_error("Usage: /truncate <N> (where N is the number of messages to keep)")
152
+ return True
153
+
154
+ try:
155
+ n = int(tokens[1])
156
+ if n < 1:
157
+ emit_error("N must be a positive integer")
158
+ return True
159
+ except ValueError:
160
+ emit_error("N must be a valid integer")
161
+ return True
162
+
163
+ agent = get_current_agent()
164
+ history = agent.get_message_history()
165
+ if not history:
166
+ emit_warning("No history to truncate yet. Ask me something first!")
167
+ return True
168
+
169
+ if len(history) <= n:
170
+ emit_info(
171
+ f"History already has {len(history)} messages, which is <= {n}. Nothing to truncate."
172
+ )
173
+ return True
174
+
175
+ # Always keep the first message (system message) and then keep the N-1 most recent messages
176
+ truncated_history = [history[0]] + history[-(n - 1) :] if n > 1 else [history[0]]
177
+
178
+ agent.set_message_history(truncated_history)
179
+ emit_success(
180
+ f"Truncated message history from {len(history)} to {len(truncated_history)} messages (keeping system message and {n - 1} most recent)"
181
+ )
182
+ return True
183
+
184
+
185
+ @register_command(
186
+ name="autosave_load",
187
+ description="Load an autosave session interactively",
188
+ usage="/autosave_load",
189
+ aliases=["resume"],
190
+ category="session",
191
+ )
192
+ def handle_autosave_load_command(command: str) -> bool:
193
+ """Load an autosave session."""
194
+ # Return a special marker to indicate we need to run async autosave loading
195
+ return "__AUTOSAVE_LOAD__"
196
+
197
+
198
+ @register_command(
199
+ name="dump_context",
200
+ description="Save current message history to file",
201
+ usage="/dump_context <name>",
202
+ category="session",
203
+ )
204
+ def handle_dump_context_command(command: str) -> bool:
205
+ """Dump message history to a file."""
206
+ from code_puppy.agents.agent_manager import get_current_agent
207
+ from code_puppy.messaging import emit_error, emit_success, emit_warning
208
+
209
+ tokens = command.split()
210
+ if len(tokens) != 2:
211
+ emit_warning("Usage: /dump_context <session_name>")
212
+ return True
213
+
214
+ session_name = tokens[1]
215
+ agent = get_current_agent()
216
+ history = agent.get_message_history()
217
+
218
+ if not history:
219
+ emit_warning("No message history to dump!")
220
+ return True
221
+
222
+ try:
223
+ metadata = save_session(
224
+ history=history,
225
+ session_name=session_name,
226
+ base_dir=Path(CONTEXTS_DIR),
227
+ timestamp=datetime.now().isoformat(),
228
+ token_estimator=agent.estimate_tokens_for_message,
229
+ )
230
+ emit_success(
231
+ f"✅ Context saved: {metadata.message_count} messages ({metadata.total_tokens} tokens)\n"
232
+ f"📁 Files: {metadata.pickle_path}, {metadata.metadata_path}"
233
+ )
234
+ return True
235
+
236
+ except Exception as exc:
237
+ emit_error(f"Failed to dump context: {exc}")
238
+ return True
239
+
240
+
241
+ @register_command(
242
+ name="load_context",
243
+ description="Load message history from file",
244
+ usage="/load_context <name>",
245
+ category="session",
246
+ )
247
+ def handle_load_context_command(command: str) -> bool:
248
+ """Load message history from a file."""
249
+ from rich.text import Text
250
+
251
+ from code_puppy.agents.agent_manager import get_current_agent
252
+ from code_puppy.config import rotate_autosave_id
253
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
254
+
255
+ tokens = command.split()
256
+ if len(tokens) != 2:
257
+ emit_warning("Usage: /load_context <session_name>")
258
+ return True
259
+
260
+ session_name = tokens[1]
261
+ contexts_dir = Path(CONTEXTS_DIR)
262
+ session_path = contexts_dir / f"{session_name}.pkl"
263
+
264
+ try:
265
+ history = load_session(session_name, contexts_dir)
266
+ except FileNotFoundError:
267
+ emit_error(f"Context file not found: {session_path}")
268
+ available = list_sessions(contexts_dir)
269
+ if available:
270
+ emit_info(f"Available contexts: {', '.join(available)}")
271
+ return True
272
+ except Exception as exc:
273
+ emit_error(f"Failed to load context: {exc}")
274
+ return True
275
+
276
+ agent = get_current_agent()
277
+ agent.set_message_history(history)
278
+ total_tokens = sum(agent.estimate_tokens_for_message(m) for m in history)
279
+
280
+ # Rotate autosave id to avoid overwriting any existing autosave
281
+ try:
282
+ new_id = rotate_autosave_id()
283
+ autosave_info = Text.from_markup(
284
+ f"\n[dim]Autosave session rotated to: {new_id}[/dim]"
285
+ )
286
+ except Exception:
287
+ autosave_info = Text("")
288
+
289
+ # Build the success message with proper Text concatenation
290
+ success_msg = Text(
291
+ f"✅ Context loaded: {len(history)} messages ({total_tokens} tokens)\n"
292
+ f"📁 From: {session_path}"
293
+ )
294
+ success_msg.append_text(autosave_info)
295
+ emit_success(success_msg)
296
+ return True
@@ -37,3 +37,57 @@ def make_directory_table(path: str = None) -> Table:
37
37
  for f in sorted(files):
38
38
  table.add_row("[yellow]file[/yellow]", f"{f}")
39
39
  return table
40
+
41
+
42
+ def _reset_windows_console() -> None:
43
+ """Reset Windows console to normal input mode.
44
+
45
+ After a prompt_toolkit Application exits on Windows, the console can be
46
+ left in a weird state where Enter doesn't work properly. This resets it.
47
+ """
48
+ import sys
49
+
50
+ if sys.platform != "win32":
51
+ return
52
+
53
+ try:
54
+ import ctypes
55
+
56
+ kernel32 = ctypes.windll.kernel32
57
+ # Get handle to stdin
58
+ STD_INPUT_HANDLE = -10
59
+ handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
60
+
61
+ # Enable line input and echo (normal console mode)
62
+ # ENABLE_LINE_INPUT = 0x0002
63
+ # ENABLE_ECHO_INPUT = 0x0004
64
+ # ENABLE_PROCESSED_INPUT = 0x0001
65
+ NORMAL_MODE = 0x0007 # Line input + echo + processed
66
+ kernel32.SetConsoleMode(handle, NORMAL_MODE)
67
+ except Exception:
68
+ pass # Silently ignore errors - this is best-effort
69
+
70
+
71
+ def safe_input(prompt_text: str = "") -> str:
72
+ """Cross-platform safe input that works after prompt_toolkit Applications.
73
+
74
+ On Windows, raw input() can fail after a prompt_toolkit Application exits
75
+ because the terminal can be left in a weird state. This function resets
76
+ the Windows console mode before calling input().
77
+
78
+ Args:
79
+ prompt_text: The prompt to display to the user
80
+
81
+ Returns:
82
+ The user's input string (stripped)
83
+
84
+ Raises:
85
+ KeyboardInterrupt: If user presses Ctrl+C
86
+ EOFError: If user presses Ctrl+D/Ctrl+Z
87
+ """
88
+ # Reset Windows console to normal mode before reading input
89
+ _reset_windows_console()
90
+
91
+ # Use standard input() - now that console is reset, it should work
92
+ result = input(prompt_text)
93
+ return result.strip() if result else ""