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,616 @@
|
|
|
1
|
+
# agent_tools.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import hashlib
|
|
4
|
+
import itertools
|
|
5
|
+
import json
|
|
6
|
+
import pickle
|
|
7
|
+
import re
|
|
8
|
+
import traceback
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from functools import partial
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Set
|
|
13
|
+
|
|
14
|
+
from dbos import DBOS, SetWorkflowID
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
# Import Agent from pydantic_ai to create temporary agents for invocation
|
|
18
|
+
from pydantic_ai import Agent, RunContext, UsageLimits
|
|
19
|
+
from pydantic_ai.messages import ModelMessage
|
|
20
|
+
|
|
21
|
+
from code_puppy.config import (
|
|
22
|
+
DATA_DIR,
|
|
23
|
+
get_message_limit,
|
|
24
|
+
get_use_dbos,
|
|
25
|
+
get_value,
|
|
26
|
+
)
|
|
27
|
+
from code_puppy.messaging import (
|
|
28
|
+
SubAgentInvocationMessage,
|
|
29
|
+
SubAgentResponseMessage,
|
|
30
|
+
emit_error,
|
|
31
|
+
emit_info,
|
|
32
|
+
emit_success,
|
|
33
|
+
get_message_bus,
|
|
34
|
+
get_session_context,
|
|
35
|
+
set_session_context,
|
|
36
|
+
)
|
|
37
|
+
from code_puppy.tools.common import generate_group_id
|
|
38
|
+
from code_puppy.tools.subagent_context import subagent_context
|
|
39
|
+
|
|
40
|
+
# Set to track active subagent invocation tasks
|
|
41
|
+
_active_subagent_tasks: Set[asyncio.Task] = set()
|
|
42
|
+
|
|
43
|
+
# Atomic counter for DBOS workflow IDs - ensures uniqueness even in rapid back-to-back calls
|
|
44
|
+
# itertools.count() is thread-safe for next() calls
|
|
45
|
+
_dbos_workflow_counter = itertools.count()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _generate_dbos_workflow_id(base_id: str) -> str:
|
|
49
|
+
"""Generate a unique DBOS workflow ID by appending an atomic counter.
|
|
50
|
+
|
|
51
|
+
DBOS requires workflow IDs to be unique across all executions.
|
|
52
|
+
This function ensures uniqueness by combining the base_id with
|
|
53
|
+
an atomically incrementing counter.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
base_id: The base identifier (e.g., group_id from generate_group_id)
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A unique workflow ID in format: {base_id}-wf-{counter}
|
|
60
|
+
"""
|
|
61
|
+
counter = next(_dbos_workflow_counter)
|
|
62
|
+
return f"{base_id}-wf-{counter}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _generate_session_hash_suffix() -> str:
|
|
66
|
+
"""Generate a short SHA1 hash suffix based on current timestamp for uniqueness.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
A 6-character hex string, e.g., "a3f2b1"
|
|
70
|
+
"""
|
|
71
|
+
timestamp = str(datetime.now().timestamp())
|
|
72
|
+
return hashlib.sha1(timestamp.encode()).hexdigest()[:6]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Regex pattern for kebab-case session IDs
|
|
76
|
+
SESSION_ID_PATTERN = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
|
77
|
+
SESSION_ID_MAX_LENGTH = 128
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _validate_session_id(session_id: str) -> None:
|
|
81
|
+
"""Validate that a session ID follows kebab-case naming conventions.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
session_id: The session identifier to validate
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: If the session_id is invalid
|
|
88
|
+
|
|
89
|
+
Valid format:
|
|
90
|
+
- Lowercase letters (a-z)
|
|
91
|
+
- Numbers (0-9)
|
|
92
|
+
- Hyphens (-) to separate words
|
|
93
|
+
- No uppercase, no underscores, no special characters
|
|
94
|
+
- Length between 1 and 128 characters
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
Valid: "my-session", "agent-session-1", "discussion-about-code"
|
|
98
|
+
Invalid: "MySession", "my_session", "my session", "my--session"
|
|
99
|
+
"""
|
|
100
|
+
if not session_id:
|
|
101
|
+
raise ValueError("session_id cannot be empty")
|
|
102
|
+
|
|
103
|
+
if len(session_id) > SESSION_ID_MAX_LENGTH:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Invalid session_id '{session_id}': must be {SESSION_ID_MAX_LENGTH} characters or less"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if not SESSION_ID_PATTERN.match(session_id):
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Invalid session_id '{session_id}': must be kebab-case "
|
|
111
|
+
"(lowercase letters, numbers, and hyphens only). "
|
|
112
|
+
"Examples: 'my-session', 'agent-session-1', 'discussion-about-code'"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _get_subagent_sessions_dir() -> Path:
|
|
117
|
+
"""Get the directory for storing subagent session data.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Path to XDG data directory/subagent_sessions/
|
|
121
|
+
"""
|
|
122
|
+
sessions_dir = Path(DATA_DIR) / "subagent_sessions"
|
|
123
|
+
sessions_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
124
|
+
return sessions_dir
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _save_session_history(
|
|
128
|
+
session_id: str,
|
|
129
|
+
message_history: List[ModelMessage],
|
|
130
|
+
agent_name: str,
|
|
131
|
+
initial_prompt: str | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Save session history to filesystem.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
session_id: The session identifier (must be kebab-case)
|
|
137
|
+
message_history: List of messages to save
|
|
138
|
+
agent_name: Name of the agent being invoked
|
|
139
|
+
initial_prompt: The first prompt that started this session (for .txt metadata)
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ValueError: If session_id is not valid kebab-case format
|
|
143
|
+
"""
|
|
144
|
+
# Validate session_id format before saving
|
|
145
|
+
_validate_session_id(session_id)
|
|
146
|
+
|
|
147
|
+
sessions_dir = _get_subagent_sessions_dir()
|
|
148
|
+
|
|
149
|
+
# Save pickle file with message history
|
|
150
|
+
pkl_path = sessions_dir / f"{session_id}.pkl"
|
|
151
|
+
with open(pkl_path, "wb") as f:
|
|
152
|
+
pickle.dump(message_history, f)
|
|
153
|
+
|
|
154
|
+
# Save or update txt file with metadata
|
|
155
|
+
txt_path = sessions_dir / f"{session_id}.txt"
|
|
156
|
+
if not txt_path.exists() and initial_prompt:
|
|
157
|
+
# Only write initial metadata on first save
|
|
158
|
+
metadata = {
|
|
159
|
+
"session_id": session_id,
|
|
160
|
+
"agent_name": agent_name,
|
|
161
|
+
"initial_prompt": initial_prompt,
|
|
162
|
+
"created_at": datetime.now().isoformat(),
|
|
163
|
+
"message_count": len(message_history),
|
|
164
|
+
}
|
|
165
|
+
with open(txt_path, "w") as f:
|
|
166
|
+
json.dump(metadata, f, indent=2)
|
|
167
|
+
elif txt_path.exists():
|
|
168
|
+
# Update message count on subsequent saves
|
|
169
|
+
try:
|
|
170
|
+
with open(txt_path, "r") as f:
|
|
171
|
+
metadata = json.load(f)
|
|
172
|
+
metadata["message_count"] = len(message_history)
|
|
173
|
+
metadata["last_updated"] = datetime.now().isoformat()
|
|
174
|
+
with open(txt_path, "w") as f:
|
|
175
|
+
json.dump(metadata, f, indent=2)
|
|
176
|
+
except Exception:
|
|
177
|
+
pass # If we can't update metadata, no big deal
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _load_session_history(session_id: str) -> List[ModelMessage]:
|
|
181
|
+
"""Load session history from filesystem.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
session_id: The session identifier (must be kebab-case)
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of ModelMessage objects, or empty list if session doesn't exist
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ValueError: If session_id is not valid kebab-case format
|
|
191
|
+
"""
|
|
192
|
+
# Validate session_id format before loading
|
|
193
|
+
_validate_session_id(session_id)
|
|
194
|
+
|
|
195
|
+
sessions_dir = _get_subagent_sessions_dir()
|
|
196
|
+
pkl_path = sessions_dir / f"{session_id}.pkl"
|
|
197
|
+
|
|
198
|
+
if not pkl_path.exists():
|
|
199
|
+
return []
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
with open(pkl_path, "rb") as f:
|
|
203
|
+
return pickle.load(f)
|
|
204
|
+
except Exception:
|
|
205
|
+
# If pickle is corrupted or incompatible, return empty history
|
|
206
|
+
return []
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class AgentInfo(BaseModel):
|
|
210
|
+
"""Information about an available agent."""
|
|
211
|
+
|
|
212
|
+
name: str
|
|
213
|
+
display_name: str
|
|
214
|
+
description: str
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class ListAgentsOutput(BaseModel):
|
|
218
|
+
"""Output for the list_agents tool."""
|
|
219
|
+
|
|
220
|
+
agents: List[AgentInfo]
|
|
221
|
+
error: str | None = None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class AgentInvokeOutput(BaseModel):
|
|
225
|
+
"""Output for the invoke_agent tool."""
|
|
226
|
+
|
|
227
|
+
response: str | None
|
|
228
|
+
agent_name: str
|
|
229
|
+
session_id: str | None = None
|
|
230
|
+
error: str | None = None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def register_list_agents(agent):
|
|
234
|
+
"""Register the list_agents tool with the provided agent.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
agent: The agent to register the tool with
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
@agent.tool
|
|
241
|
+
def list_agents(context: RunContext) -> ListAgentsOutput:
|
|
242
|
+
"""List all available sub-agents that can be invoked."""
|
|
243
|
+
# Generate a group ID for this tool execution
|
|
244
|
+
group_id = generate_group_id("list_agents")
|
|
245
|
+
|
|
246
|
+
from rich.text import Text
|
|
247
|
+
|
|
248
|
+
from code_puppy.config import get_banner_color
|
|
249
|
+
|
|
250
|
+
list_agents_color = get_banner_color("list_agents")
|
|
251
|
+
emit_info(
|
|
252
|
+
Text.from_markup(
|
|
253
|
+
f"\n[bold white on {list_agents_color}] LIST AGENTS [/bold white on {list_agents_color}]"
|
|
254
|
+
),
|
|
255
|
+
message_group=group_id,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
from code_puppy.agents import get_agent_descriptions, get_available_agents
|
|
260
|
+
|
|
261
|
+
# Get available agents and their descriptions from the agent manager
|
|
262
|
+
agents_dict = get_available_agents()
|
|
263
|
+
descriptions_dict = get_agent_descriptions()
|
|
264
|
+
|
|
265
|
+
# Convert to list of AgentInfo objects
|
|
266
|
+
agents = [
|
|
267
|
+
AgentInfo(
|
|
268
|
+
name=name,
|
|
269
|
+
display_name=display_name,
|
|
270
|
+
description=descriptions_dict.get(name, "No description available"),
|
|
271
|
+
)
|
|
272
|
+
for name, display_name in agents_dict.items()
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
# Accumulate output into a single string and emit once
|
|
276
|
+
# Use Text.from_markup() to pass a Rich object that won't be escaped
|
|
277
|
+
lines = []
|
|
278
|
+
for agent_item in agents:
|
|
279
|
+
lines.append(
|
|
280
|
+
f"- [bold]{agent_item.name}[/bold]: {agent_item.display_name}\n"
|
|
281
|
+
f" [dim]{agent_item.description}[/dim]"
|
|
282
|
+
)
|
|
283
|
+
emit_info(Text.from_markup("\n".join(lines)), message_group=group_id)
|
|
284
|
+
|
|
285
|
+
return ListAgentsOutput(agents=agents)
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
error_msg = f"Error listing agents: {str(e)}"
|
|
289
|
+
emit_error(error_msg, message_group=group_id)
|
|
290
|
+
return ListAgentsOutput(agents=[], error=error_msg)
|
|
291
|
+
|
|
292
|
+
return list_agents
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def register_invoke_agent(agent):
|
|
296
|
+
"""Register the invoke_agent tool with the provided agent.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
agent: The agent to register the tool with
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
@agent.tool
|
|
303
|
+
async def invoke_agent(
|
|
304
|
+
context: RunContext, agent_name: str, prompt: str, session_id: str | None = None
|
|
305
|
+
) -> AgentInvokeOutput:
|
|
306
|
+
"""Invoke a specific sub-agent with a given prompt.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
agent_name: The name of the agent to invoke
|
|
310
|
+
prompt: The prompt to send to the agent
|
|
311
|
+
session_id: Optional session ID for maintaining conversation memory across invocations.
|
|
312
|
+
Must be kebab-case. Hash suffix auto-appended for new sessions.
|
|
313
|
+
To continue a session, use the full session_id from the previous response.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
AgentInvokeOutput: Contains response, agent_name, session_id, and error fields.
|
|
317
|
+
"""
|
|
318
|
+
from code_puppy.agents.agent_manager import load_agent
|
|
319
|
+
|
|
320
|
+
# Validate user-provided session_id if given
|
|
321
|
+
if session_id is not None:
|
|
322
|
+
try:
|
|
323
|
+
_validate_session_id(session_id)
|
|
324
|
+
except ValueError as e:
|
|
325
|
+
# Return error immediately if session_id is invalid
|
|
326
|
+
group_id = generate_group_id("invoke_agent", agent_name)
|
|
327
|
+
emit_error(str(e), message_group=group_id)
|
|
328
|
+
return AgentInvokeOutput(
|
|
329
|
+
response=None, agent_name=agent_name, error=str(e)
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Generate a group ID for this tool execution
|
|
333
|
+
group_id = generate_group_id("invoke_agent", agent_name)
|
|
334
|
+
|
|
335
|
+
# Check if this is an existing session or a new one
|
|
336
|
+
# For user-provided session_id, check if it exists
|
|
337
|
+
# For None, we'll generate a new one below
|
|
338
|
+
if session_id is not None:
|
|
339
|
+
message_history = _load_session_history(session_id)
|
|
340
|
+
is_new_session = len(message_history) == 0
|
|
341
|
+
else:
|
|
342
|
+
message_history = []
|
|
343
|
+
is_new_session = True
|
|
344
|
+
|
|
345
|
+
# Generate or finalize session_id
|
|
346
|
+
if session_id is None:
|
|
347
|
+
# Auto-generate a session ID with hash suffix for uniqueness
|
|
348
|
+
# Example: "qa-expert-session-a3f2b1"
|
|
349
|
+
hash_suffix = _generate_session_hash_suffix()
|
|
350
|
+
session_id = f"{agent_name}-session-{hash_suffix}"
|
|
351
|
+
elif is_new_session:
|
|
352
|
+
# User provided a base name for a NEW session - append hash suffix
|
|
353
|
+
# Example: "review-auth" -> "review-auth-a3f2b1"
|
|
354
|
+
hash_suffix = _generate_session_hash_suffix()
|
|
355
|
+
session_id = f"{session_id}-{hash_suffix}"
|
|
356
|
+
# else: continuing existing session, use session_id as-is
|
|
357
|
+
|
|
358
|
+
# Lazy imports to avoid circular dependency
|
|
359
|
+
from code_puppy.agents.subagent_stream_handler import subagent_stream_handler
|
|
360
|
+
|
|
361
|
+
# Emit structured invocation message via MessageBus
|
|
362
|
+
bus = get_message_bus()
|
|
363
|
+
bus.emit(
|
|
364
|
+
SubAgentInvocationMessage(
|
|
365
|
+
agent_name=agent_name,
|
|
366
|
+
session_id=session_id,
|
|
367
|
+
prompt=prompt,
|
|
368
|
+
is_new_session=is_new_session,
|
|
369
|
+
message_count=len(message_history),
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Save current session context and set the new one for this sub-agent
|
|
374
|
+
previous_session_id = get_session_context()
|
|
375
|
+
set_session_context(session_id)
|
|
376
|
+
|
|
377
|
+
# Set terminal session for browser-based terminal tools
|
|
378
|
+
# This uses contextvars which properly propagate through async tasks
|
|
379
|
+
from code_puppy.tools.browser.terminal_tools import (
|
|
380
|
+
_terminal_session_var,
|
|
381
|
+
set_terminal_session,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
terminal_session_token = set_terminal_session(f"terminal-{session_id}")
|
|
385
|
+
|
|
386
|
+
# Set browser session for browser tools (qa-kitten, etc.)
|
|
387
|
+
# This allows parallel agent invocations to each have their own browser
|
|
388
|
+
from code_puppy.tools.browser.browser_manager import (
|
|
389
|
+
set_browser_session,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
browser_session_token = set_browser_session(f"browser-{session_id}")
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
# Lazy import to break circular dependency with messaging module
|
|
396
|
+
from code_puppy.model_factory import ModelFactory, make_model_settings
|
|
397
|
+
|
|
398
|
+
# Load the specified agent config
|
|
399
|
+
agent_config = load_agent(agent_name)
|
|
400
|
+
|
|
401
|
+
# Get the current model for creating a temporary agent
|
|
402
|
+
model_name = agent_config.get_model_name()
|
|
403
|
+
models_config = ModelFactory.load_config()
|
|
404
|
+
|
|
405
|
+
# Only proceed if we have a valid model configuration
|
|
406
|
+
if model_name not in models_config:
|
|
407
|
+
raise ValueError(f"Model '{model_name}' not found in configuration")
|
|
408
|
+
|
|
409
|
+
model = ModelFactory.get_model(model_name, models_config)
|
|
410
|
+
|
|
411
|
+
# Create a temporary agent instance to avoid interfering with current agent state
|
|
412
|
+
instructions = agent_config.get_full_system_prompt()
|
|
413
|
+
|
|
414
|
+
# Add AGENTS.md content to subagents
|
|
415
|
+
puppy_rules = agent_config.load_puppy_rules()
|
|
416
|
+
if puppy_rules:
|
|
417
|
+
instructions += f"\n\n{puppy_rules}"
|
|
418
|
+
|
|
419
|
+
# Apply prompt additions (like file permission handling) to temporary agents
|
|
420
|
+
from code_puppy import callbacks
|
|
421
|
+
from code_puppy.model_utils import prepare_prompt_for_model
|
|
422
|
+
|
|
423
|
+
prompt_additions = callbacks.on_load_prompt()
|
|
424
|
+
if len(prompt_additions):
|
|
425
|
+
instructions += "\n" + "\n".join(prompt_additions)
|
|
426
|
+
|
|
427
|
+
# Handle claude-code models: swap instructions, and prepend system prompt only on first message
|
|
428
|
+
prepared = prepare_prompt_for_model(
|
|
429
|
+
model_name,
|
|
430
|
+
instructions,
|
|
431
|
+
prompt,
|
|
432
|
+
prepend_system_to_user=is_new_session, # Only prepend on first message
|
|
433
|
+
)
|
|
434
|
+
instructions = prepared.instructions
|
|
435
|
+
prompt = prepared.user_prompt
|
|
436
|
+
|
|
437
|
+
import uuid as _uuid
|
|
438
|
+
|
|
439
|
+
subagent_name = f"temp-invoke-agent-{session_id}-{_uuid.uuid4().hex[:8]}"
|
|
440
|
+
model_settings = make_model_settings(model_name)
|
|
441
|
+
|
|
442
|
+
# Get MCP servers for sub-agents (same as main agent)
|
|
443
|
+
from code_puppy.mcp_ import get_mcp_manager
|
|
444
|
+
|
|
445
|
+
mcp_servers = []
|
|
446
|
+
mcp_disabled = get_value("disable_mcp_servers")
|
|
447
|
+
if not (
|
|
448
|
+
mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on")
|
|
449
|
+
):
|
|
450
|
+
manager = get_mcp_manager()
|
|
451
|
+
mcp_servers = manager.get_servers_for_agent()
|
|
452
|
+
|
|
453
|
+
if get_use_dbos():
|
|
454
|
+
from pydantic_ai.durable_exec.dbos import DBOSAgent
|
|
455
|
+
|
|
456
|
+
# For DBOS, create agent without MCP servers (to avoid serialization issues)
|
|
457
|
+
# and add them at runtime
|
|
458
|
+
temp_agent = Agent(
|
|
459
|
+
model=model,
|
|
460
|
+
instructions=instructions,
|
|
461
|
+
output_type=str,
|
|
462
|
+
retries=10,
|
|
463
|
+
toolsets=[], # MCP servers added separately for DBOS
|
|
464
|
+
history_processors=[agent_config.message_history_accumulator],
|
|
465
|
+
model_settings=model_settings,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Register the tools that the agent needs
|
|
469
|
+
from code_puppy.tools import register_tools_for_agent
|
|
470
|
+
|
|
471
|
+
agent_tools = agent_config.get_available_tools()
|
|
472
|
+
register_tools_for_agent(temp_agent, agent_tools, model_name=model_name)
|
|
473
|
+
|
|
474
|
+
# Wrap with DBOS - no streaming for sub-agents
|
|
475
|
+
dbos_agent = DBOSAgent(
|
|
476
|
+
temp_agent,
|
|
477
|
+
name=subagent_name,
|
|
478
|
+
)
|
|
479
|
+
temp_agent = dbos_agent
|
|
480
|
+
|
|
481
|
+
# Store MCP servers to add at runtime
|
|
482
|
+
subagent_mcp_servers = mcp_servers
|
|
483
|
+
else:
|
|
484
|
+
# Non-DBOS path - include MCP servers directly in the agent
|
|
485
|
+
temp_agent = Agent(
|
|
486
|
+
model=model,
|
|
487
|
+
instructions=instructions,
|
|
488
|
+
output_type=str,
|
|
489
|
+
retries=10,
|
|
490
|
+
toolsets=mcp_servers,
|
|
491
|
+
history_processors=[agent_config.message_history_accumulator],
|
|
492
|
+
model_settings=model_settings,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# Register the tools that the agent needs
|
|
496
|
+
from code_puppy.tools import register_tools_for_agent
|
|
497
|
+
|
|
498
|
+
agent_tools = agent_config.get_available_tools()
|
|
499
|
+
register_tools_for_agent(temp_agent, agent_tools, model_name=model_name)
|
|
500
|
+
|
|
501
|
+
subagent_mcp_servers = None
|
|
502
|
+
|
|
503
|
+
# Run the temporary agent with the provided prompt as an asyncio task
|
|
504
|
+
# Pass the message_history from the session to continue the conversation
|
|
505
|
+
workflow_id = None # Track for potential cancellation
|
|
506
|
+
|
|
507
|
+
# Always use subagent_stream_handler to silence output and update console manager
|
|
508
|
+
# This ensures all sub-agent output goes through the aggregated dashboard
|
|
509
|
+
stream_handler = partial(subagent_stream_handler, session_id=session_id)
|
|
510
|
+
|
|
511
|
+
# Wrap the agent run in subagent context for tracking
|
|
512
|
+
with subagent_context(agent_name):
|
|
513
|
+
if get_use_dbos():
|
|
514
|
+
# Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
|
|
515
|
+
workflow_id = _generate_dbos_workflow_id(group_id)
|
|
516
|
+
|
|
517
|
+
# Add MCP servers to the DBOS agent's toolsets
|
|
518
|
+
# (temp_agent is discarded after this invocation, so no need to restore)
|
|
519
|
+
if subagent_mcp_servers:
|
|
520
|
+
temp_agent._toolsets = (
|
|
521
|
+
temp_agent._toolsets + subagent_mcp_servers
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
with SetWorkflowID(workflow_id):
|
|
525
|
+
task = asyncio.create_task(
|
|
526
|
+
temp_agent.run(
|
|
527
|
+
prompt,
|
|
528
|
+
message_history=message_history,
|
|
529
|
+
usage_limits=UsageLimits(
|
|
530
|
+
request_limit=get_message_limit()
|
|
531
|
+
),
|
|
532
|
+
event_stream_handler=stream_handler,
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
_active_subagent_tasks.add(task)
|
|
536
|
+
else:
|
|
537
|
+
task = asyncio.create_task(
|
|
538
|
+
temp_agent.run(
|
|
539
|
+
prompt,
|
|
540
|
+
message_history=message_history,
|
|
541
|
+
usage_limits=UsageLimits(request_limit=get_message_limit()),
|
|
542
|
+
event_stream_handler=stream_handler,
|
|
543
|
+
)
|
|
544
|
+
)
|
|
545
|
+
_active_subagent_tasks.add(task)
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
result = await task
|
|
549
|
+
finally:
|
|
550
|
+
_active_subagent_tasks.discard(task)
|
|
551
|
+
if task.cancelled():
|
|
552
|
+
if get_use_dbos() and workflow_id:
|
|
553
|
+
await DBOS.cancel_workflow_async(workflow_id)
|
|
554
|
+
|
|
555
|
+
# Extract the response from the result
|
|
556
|
+
response = result.output
|
|
557
|
+
|
|
558
|
+
# Update the session history with the new messages from this interaction
|
|
559
|
+
# The result contains all_messages which includes the full conversation
|
|
560
|
+
updated_history = result.all_messages()
|
|
561
|
+
|
|
562
|
+
# Save to filesystem (include initial prompt only for new sessions)
|
|
563
|
+
_save_session_history(
|
|
564
|
+
session_id=session_id,
|
|
565
|
+
message_history=updated_history,
|
|
566
|
+
agent_name=agent_name,
|
|
567
|
+
initial_prompt=prompt if is_new_session else None,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Emit structured response message via MessageBus
|
|
571
|
+
bus.emit(
|
|
572
|
+
SubAgentResponseMessage(
|
|
573
|
+
agent_name=agent_name,
|
|
574
|
+
session_id=session_id,
|
|
575
|
+
response=response,
|
|
576
|
+
message_count=len(updated_history),
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
# Emit clean completion summary
|
|
581
|
+
emit_success(
|
|
582
|
+
f"✓ {agent_name} completed successfully", message_group=group_id
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
return AgentInvokeOutput(
|
|
586
|
+
response=response, agent_name=agent_name, session_id=session_id
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
except Exception as e:
|
|
590
|
+
# Emit clean failure summary
|
|
591
|
+
emit_error(f"✗ {agent_name} failed: {str(e)}", message_group=group_id)
|
|
592
|
+
|
|
593
|
+
# Full traceback for debugging
|
|
594
|
+
error_msg = f"Error invoking agent '{agent_name}': {traceback.format_exc()}"
|
|
595
|
+
emit_error(error_msg, message_group=group_id)
|
|
596
|
+
|
|
597
|
+
return AgentInvokeOutput(
|
|
598
|
+
response=None,
|
|
599
|
+
agent_name=agent_name,
|
|
600
|
+
session_id=session_id,
|
|
601
|
+
error=error_msg,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
finally:
|
|
605
|
+
# Restore the previous session context
|
|
606
|
+
set_session_context(previous_session_id)
|
|
607
|
+
# Reset terminal session context
|
|
608
|
+
_terminal_session_var.reset(terminal_session_token)
|
|
609
|
+
# Reset browser session context
|
|
610
|
+
from code_puppy.tools.browser.browser_manager import (
|
|
611
|
+
_browser_session_var,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
_browser_session_var.reset(browser_session_token)
|
|
615
|
+
|
|
616
|
+
return invoke_agent
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Ask User Question tool for code-puppy.
|
|
2
|
+
|
|
3
|
+
This tool allows agents to ask users interactive multiple-choice questions
|
|
4
|
+
through a terminal TUI interface. Uses prompt_toolkit for the split-panel
|
|
5
|
+
UI similar to the /colors command.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .handler import ask_user_question
|
|
9
|
+
from .models import (
|
|
10
|
+
AskUserQuestionInput,
|
|
11
|
+
AskUserQuestionOutput,
|
|
12
|
+
Question,
|
|
13
|
+
QuestionAnswer,
|
|
14
|
+
QuestionOption,
|
|
15
|
+
)
|
|
16
|
+
from .registration import register_ask_user_question
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ask_user_question",
|
|
20
|
+
"register_ask_user_question",
|
|
21
|
+
"AskUserQuestionInput",
|
|
22
|
+
"AskUserQuestionOutput",
|
|
23
|
+
"Question",
|
|
24
|
+
"QuestionAnswer",
|
|
25
|
+
"QuestionOption",
|
|
26
|
+
]
|