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.
- code_puppy/__init__.py +10 -0
- code_puppy/__main__.py +10 -0
- code_puppy/agents/__init__.py +31 -0
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +147 -0
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +630 -0
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_helios.py +122 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +742 -0
- code_puppy/agents/agent_pack_leader.py +380 -0
- code_puppy/agents/agent_planning.py +165 -0
- code_puppy/agents/agent_python_programmer.py +167 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_scheduler.py +121 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +2145 -0
- code_puppy/agents/event_stream_handler.py +348 -0
- code_puppy/agents/json_agent.py +202 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +296 -0
- code_puppy/agents/pack/husky.py +307 -0
- code_puppy/agents/pack/retriever.py +380 -0
- code_puppy/agents/pack/shepherd.py +327 -0
- code_puppy/agents/pack/terrier.py +281 -0
- code_puppy/agents/pack/watchdog.py +357 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +453 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +75 -0
- code_puppy/api/routers/sessions.py +234 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +674 -0
- code_puppy/chatgpt_codex_client.py +338 -0
- code_puppy/claude_cache_client.py +664 -0
- code_puppy/cli_runner.py +1038 -0
- code_puppy/command_line/__init__.py +1 -0
- code_puppy/command_line/add_model_menu.py +1092 -0
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +704 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +526 -0
- code_puppy/command_line/command_handler.py +283 -0
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +719 -0
- code_puppy/command_line/core_commands.py +853 -0
- code_puppy/command_line/diff_menu.py +865 -0
- code_puppy/command_line/file_path_completion.py +73 -0
- code_puppy/command_line/load_context_completion.py +52 -0
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/base.py +32 -0
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +138 -0
- code_puppy/command_line/mcp/help_command.py +147 -0
- code_puppy/command_line/mcp/install_command.py +214 -0
- code_puppy/command_line/mcp/install_menu.py +705 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +235 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +100 -0
- code_puppy/command_line/mcp/search_command.py +123 -0
- code_puppy/command_line/mcp/start_all_command.py +135 -0
- code_puppy/command_line/mcp/start_command.py +117 -0
- code_puppy/command_line/mcp/status_command.py +184 -0
- code_puppy/command_line/mcp/stop_all_command.py +112 -0
- code_puppy/command_line/mcp/stop_command.py +80 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +334 -0
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +197 -0
- code_puppy/command_line/model_settings_menu.py +932 -0
- code_puppy/command_line/motd.py +91 -0
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +342 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +846 -0
- code_puppy/command_line/session_commands.py +302 -0
- code_puppy/command_line/skills_completion.py +160 -0
- code_puppy/command_line/uc_menu.py +893 -0
- code_puppy/command_line/utils.py +93 -0
- code_puppy/command_line/wiggum_state.py +78 -0
- code_puppy/config.py +1787 -0
- code_puppy/error_logging.py +133 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +754 -0
- code_puppy/hook_engine/README.md +105 -0
- code_puppy/hook_engine/__init__.py +15 -0
- code_puppy/hook_engine/aliases.py +155 -0
- code_puppy/hook_engine/engine.py +195 -0
- code_puppy/hook_engine/executor.py +293 -0
- code_puppy/hook_engine/matcher.py +145 -0
- code_puppy/hook_engine/models.py +222 -0
- code_puppy/hook_engine/registry.py +106 -0
- code_puppy/hook_engine/validator.py +141 -0
- code_puppy/http_utils.py +361 -0
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +10 -0
- code_puppy/mcp_/__init__.py +66 -0
- code_puppy/mcp_/async_lifecycle.py +286 -0
- code_puppy/mcp_/blocking_startup.py +469 -0
- code_puppy/mcp_/captured_stdio_server.py +275 -0
- code_puppy/mcp_/circuit_breaker.py +290 -0
- code_puppy/mcp_/config_wizard.py +507 -0
- code_puppy/mcp_/dashboard.py +308 -0
- code_puppy/mcp_/error_isolation.py +407 -0
- code_puppy/mcp_/examples/retry_example.py +226 -0
- code_puppy/mcp_/health_monitor.py +589 -0
- code_puppy/mcp_/managed_server.py +428 -0
- code_puppy/mcp_/manager.py +807 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +451 -0
- code_puppy/mcp_/retry_manager.py +337 -0
- code_puppy/mcp_/server_registry_catalog.py +1126 -0
- code_puppy/mcp_/status_tracker.py +355 -0
- code_puppy/mcp_/system_tools.py +209 -0
- code_puppy/mcp_prompts/__init__.py +1 -0
- code_puppy/mcp_prompts/hook_creator.py +103 -0
- code_puppy/messaging/__init__.py +255 -0
- code_puppy/messaging/bus.py +613 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +361 -0
- code_puppy/messaging/messages.py +569 -0
- code_puppy/messaging/queue_console.py +271 -0
- code_puppy/messaging/renderers.py +311 -0
- code_puppy/messaging/rich_renderer.py +1153 -0
- code_puppy/messaging/spinner/__init__.py +83 -0
- code_puppy/messaging/spinner/console_spinner.py +240 -0
- code_puppy/messaging/spinner/spinner_base.py +96 -0
- code_puppy/messaging/subagent_console.py +460 -0
- code_puppy/model_factory.py +848 -0
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +168 -0
- code_puppy/models.json +130 -0
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +186 -0
- code_puppy/plugins/agent_skills/__init__.py +22 -0
- code_puppy/plugins/agent_skills/config.py +175 -0
- code_puppy/plugins/agent_skills/discovery.py +136 -0
- code_puppy/plugins/agent_skills/downloader.py +392 -0
- code_puppy/plugins/agent_skills/installer.py +22 -0
- code_puppy/plugins/agent_skills/metadata.py +219 -0
- code_puppy/plugins/agent_skills/prompt_builder.py +100 -0
- code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
- code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
- code_puppy/plugins/agent_skills/skills_menu.py +781 -0
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +133 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
- code_puppy/plugins/antigravity_oauth/storage.py +288 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +863 -0
- code_puppy/plugins/antigravity_oauth/utils.py +168 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +295 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +499 -0
- code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy/plugins/claude_code_hooks/config.py +131 -0
- code_puppy/plugins/claude_code_hooks/register_callbacks.py +163 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy/plugins/claude_code_oauth/config.py +52 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
- code_puppy/plugins/claude_code_oauth/utils.py +601 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +48 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +528 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/hook_creator/__init__.py +1 -0
- code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy/plugins/hook_manager/__init__.py +1 -0
- code_puppy/plugins/hook_manager/config.py +277 -0
- code_puppy/plugins/hook_manager/hooks_menu.py +551 -0
- code_puppy/plugins/hook_manager/register_callbacks.py +205 -0
- code_puppy/plugins/oauth_puppy_html.py +224 -0
- code_puppy/plugins/scheduler/__init__.py +1 -0
- code_puppy/plugins/scheduler/register_callbacks.py +88 -0
- code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
- code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/plugins/synthetic_status/__init__.py +1 -0
- code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
- code_puppy/plugins/synthetic_status/status_api.py +147 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +302 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +317 -0
- code_puppy/reopenable_async_client.py +232 -0
- code_puppy/round_robin_model.py +150 -0
- code_puppy/scheduler/__init__.py +41 -0
- code_puppy/scheduler/__main__.py +9 -0
- code_puppy/scheduler/cli.py +118 -0
- code_puppy/scheduler/config.py +126 -0
- code_puppy/scheduler/daemon.py +280 -0
- code_puppy/scheduler/executor.py +155 -0
- code_puppy/scheduler/platform.py +19 -0
- code_puppy/scheduler/platform_unix.py +22 -0
- code_puppy/scheduler/platform_win.py +32 -0
- code_puppy/session_storage.py +338 -0
- code_puppy/status_display.py +257 -0
- code_puppy/summarization_agent.py +176 -0
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +470 -0
- code_puppy/tools/agent_tools.py +616 -0
- code_puppy/tools/ask_user_question/__init__.py +26 -0
- code_puppy/tools/ask_user_question/constants.py +73 -0
- code_puppy/tools/ask_user_question/demo_tui.py +55 -0
- code_puppy/tools/ask_user_question/handler.py +232 -0
- code_puppy/tools/ask_user_question/models.py +304 -0
- code_puppy/tools/ask_user_question/registration.py +36 -0
- code_puppy/tools/ask_user_question/renderers.py +309 -0
- code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
- code_puppy/tools/ask_user_question/theme.py +155 -0
- code_puppy/tools/ask_user_question/tui_loop.py +423 -0
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +378 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +534 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +1346 -0
- code_puppy/tools/common.py +1409 -0
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +739 -0
- code_puppy/tools/file_operations.py +802 -0
- code_puppy/tools/scheduler_tools.py +412 -0
- code_puppy/tools/skills_tools.py +251 -0
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/tools/tools_content.py +51 -0
- code_puppy/tools/universal_constructor.py +889 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +82 -0
- newcode-0.1.1.data/data/code_puppy/models.json +130 -0
- newcode-0.1.1.data/data/code_puppy/models_dev_api.json +1 -0
- newcode-0.1.1.dist-info/METADATA +154 -0
- newcode-0.1.1.dist-info/RECORD +289 -0
- newcode-0.1.1.dist-info/WHEEL +4 -0
- newcode-0.1.1.dist-info/entry_points.txt +3 -0
- newcode-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Command handlers for 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
|
+
|
|
297
|
+
# Display recent message history for context
|
|
298
|
+
from code_puppy.command_line.autosave_menu import display_resumed_history
|
|
299
|
+
|
|
300
|
+
display_resumed_history(history)
|
|
301
|
+
|
|
302
|
+
return True
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Prompt-toolkit completion for `/skills`.
|
|
2
|
+
|
|
3
|
+
Mirrors MCPCompleter but simpler:
|
|
4
|
+
- Completes subcommands for `/skills ...`
|
|
5
|
+
- For `/skills install ...`, completes skill ids from the remote catalog
|
|
6
|
+
|
|
7
|
+
This module is intentionally defensive: if the remote catalog isn't available,
|
|
8
|
+
completion simply returns no skill ids.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
from typing import Iterable, List
|
|
16
|
+
|
|
17
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
18
|
+
from prompt_toolkit.document import Document
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_catalog_skill_ids() -> List[str]:
|
|
24
|
+
"""Load skill ids from the remote catalog (lazy, cached)."""
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from code_puppy.plugins.agent_skills.skill_catalog import catalog
|
|
28
|
+
|
|
29
|
+
return [entry.id for entry in catalog.get_all()]
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.debug(f"Could not load skill ids: {e}")
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SkillsCompleter(Completer):
|
|
36
|
+
"""Completer for /skills subcommands."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, trigger: str = "/skills"):
|
|
39
|
+
"""Initialize the skills completer.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
trigger: The slash command prefix to trigger completion.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
self.trigger = trigger
|
|
46
|
+
self.subcommands = {
|
|
47
|
+
"list": "List all installed skills",
|
|
48
|
+
"install": "Browse & install from catalog",
|
|
49
|
+
"enable": "Enable skills integration globally",
|
|
50
|
+
"disable": "Disable skills integration globally",
|
|
51
|
+
"toggle": "Toggle skills system on/off",
|
|
52
|
+
"refresh": "Refresh skill cache",
|
|
53
|
+
"help": "Show skills help",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
self._skill_ids_cache: List[str] | None = None
|
|
57
|
+
self._cache_timestamp: float | None = None
|
|
58
|
+
|
|
59
|
+
def _get_skill_ids(self) -> List[str]:
|
|
60
|
+
"""Get skill ids with 30-second cache."""
|
|
61
|
+
|
|
62
|
+
current_time = time.time()
|
|
63
|
+
if (
|
|
64
|
+
self._skill_ids_cache is None
|
|
65
|
+
or self._cache_timestamp is None
|
|
66
|
+
or current_time - self._cache_timestamp > 30
|
|
67
|
+
):
|
|
68
|
+
self._skill_ids_cache = load_catalog_skill_ids()
|
|
69
|
+
self._cache_timestamp = current_time
|
|
70
|
+
|
|
71
|
+
return self._skill_ids_cache or []
|
|
72
|
+
|
|
73
|
+
def get_completions(
|
|
74
|
+
self, document: Document, complete_event
|
|
75
|
+
) -> Iterable[Completion]:
|
|
76
|
+
"""Yield completions for /skills subcommands and skill ids."""
|
|
77
|
+
|
|
78
|
+
text = document.text
|
|
79
|
+
cursor_position = document.cursor_position
|
|
80
|
+
text_before_cursor = text[:cursor_position]
|
|
81
|
+
|
|
82
|
+
# Only trigger if /skills is at the very beginning of the line
|
|
83
|
+
stripped_text = text_before_cursor.lstrip()
|
|
84
|
+
if not stripped_text.startswith(self.trigger):
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Find where /skills starts (after any leading whitespace)
|
|
88
|
+
skills_pos = text_before_cursor.find(self.trigger)
|
|
89
|
+
skills_end = skills_pos + len(self.trigger)
|
|
90
|
+
|
|
91
|
+
# Require a space after /skills before showing completions
|
|
92
|
+
if (
|
|
93
|
+
skills_end >= len(text_before_cursor)
|
|
94
|
+
or text_before_cursor[skills_end] != " "
|
|
95
|
+
):
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# Everything after /skills (after the space)
|
|
99
|
+
after_skills = text_before_cursor[skills_end + 1 :].strip()
|
|
100
|
+
|
|
101
|
+
# If nothing after /skills, show all subcommands
|
|
102
|
+
if not after_skills:
|
|
103
|
+
for subcommand, description in sorted(self.subcommands.items()):
|
|
104
|
+
yield Completion(
|
|
105
|
+
subcommand,
|
|
106
|
+
start_position=0,
|
|
107
|
+
display=subcommand,
|
|
108
|
+
display_meta=description,
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
parts = after_skills.split()
|
|
113
|
+
|
|
114
|
+
# Special-case: /skills install <skill-id>
|
|
115
|
+
if len(parts) >= 1:
|
|
116
|
+
subcommand = parts[0].lower()
|
|
117
|
+
|
|
118
|
+
if subcommand == "install":
|
|
119
|
+
# Case 1: exactly `install ` -> show all ids
|
|
120
|
+
if len(parts) == 1 and text.endswith(" "):
|
|
121
|
+
for skill_id in sorted(self._get_skill_ids()):
|
|
122
|
+
yield Completion(
|
|
123
|
+
skill_id,
|
|
124
|
+
start_position=0,
|
|
125
|
+
display=skill_id,
|
|
126
|
+
display_meta="Skill",
|
|
127
|
+
)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Case 2: `install <partial>` -> filter ids
|
|
131
|
+
if len(parts) == 2 and cursor_position > (
|
|
132
|
+
skills_end + 1 + len(subcommand) + 1
|
|
133
|
+
):
|
|
134
|
+
partial = parts[1]
|
|
135
|
+
start_position = -len(partial)
|
|
136
|
+
for skill_id in sorted(self._get_skill_ids()):
|
|
137
|
+
if skill_id.lower().startswith(partial.lower()):
|
|
138
|
+
yield Completion(
|
|
139
|
+
skill_id,
|
|
140
|
+
start_position=start_position,
|
|
141
|
+
display=skill_id,
|
|
142
|
+
display_meta="Skill",
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# If we only have one part and no trailing space, complete subcommands
|
|
147
|
+
if len(parts) == 1 and not text.endswith(" "):
|
|
148
|
+
partial = parts[0]
|
|
149
|
+
for subcommand, description in sorted(self.subcommands.items()):
|
|
150
|
+
if subcommand.startswith(partial):
|
|
151
|
+
yield Completion(
|
|
152
|
+
subcommand,
|
|
153
|
+
start_position=-(len(partial)),
|
|
154
|
+
display=subcommand,
|
|
155
|
+
display_meta=description,
|
|
156
|
+
)
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
# Otherwise, no further completion.
|
|
160
|
+
return
|