deepy-cli 0.2.17__tar.gz → 0.2.18__tar.gz
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.
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/PKG-INFO +1 -1
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/pyproject.toml +1 -1
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/config/__init__.py +4 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/config/settings.py +24 -1
- deepy_cli-0.2.18/src/deepy/data/tools/test_shell.md +16 -0
- deepy_cli-0.2.18/src/deepy/llm/agent.py +267 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/runner.py +1 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/system.py +8 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/tool_docs.py +1 -0
- deepy_cli-0.2.18/src/deepy/subagents.py +323 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/agents.py +55 -1
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/builtin.py +25 -0
- deepy_cli-0.2.18/src/deepy/tools/test_shell.py +529 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/app.py +22 -3
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/widgets.py +10 -3
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/message_view.py +5 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/slash_commands.py +48 -14
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/terminal.py +139 -44
- deepy_cli-0.2.17/src/deepy/llm/agent.py +0 -61
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/README.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/background_tasks.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/cli.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/Search.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/apply_patch.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/edit_text.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/read_file.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/task_list.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/task_output.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/task_stop.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/data/tools/write_file.md +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/input_suggestions.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/provider.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/mcp.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/session_cost.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/sessions/jsonl.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/search.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/commands.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/compat.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/diff.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/runner.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/screens.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/tui/state.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/prompt_input.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/status_footer.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/ui/welcome.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.17 → deepy_cli-0.2.18}/src/deepy/utils/notify.py +0 -0
|
@@ -23,8 +23,10 @@ from .settings import (
|
|
|
23
23
|
REASONING_MODES,
|
|
24
24
|
Settings,
|
|
25
25
|
SUPPORTED_DEEPSEEK_MODELS,
|
|
26
|
+
TestShellToolConfig,
|
|
26
27
|
UI_THEME_OPTIONS,
|
|
27
28
|
UI_THEMES,
|
|
29
|
+
ToolsConfig,
|
|
28
30
|
UiConfig,
|
|
29
31
|
XIAOMI_MODEL_CATALOG,
|
|
30
32
|
allows_custom_model_for_provider,
|
|
@@ -79,8 +81,10 @@ __all__ = [
|
|
|
79
81
|
"REASONING_MODES",
|
|
80
82
|
"Settings",
|
|
81
83
|
"SUPPORTED_DEEPSEEK_MODELS",
|
|
84
|
+
"TestShellToolConfig",
|
|
82
85
|
"UI_THEME_OPTIONS",
|
|
83
86
|
"UI_THEMES",
|
|
87
|
+
"ToolsConfig",
|
|
84
88
|
"UiConfig",
|
|
85
89
|
"XIAOMI_MODEL_CATALOG",
|
|
86
90
|
"allows_custom_model_for_provider",
|
|
@@ -323,6 +323,12 @@ def _as_str(value: Any, default: str = "") -> str:
|
|
|
323
323
|
return value.strip() if isinstance(value, str) and value.strip() else default
|
|
324
324
|
|
|
325
325
|
|
|
326
|
+
def _as_string_tuple(value: Any) -> tuple[str, ...]:
|
|
327
|
+
if not isinstance(value, list):
|
|
328
|
+
return ()
|
|
329
|
+
return tuple(item.strip() for item in value if isinstance(item, str) and item.strip())
|
|
330
|
+
|
|
331
|
+
|
|
326
332
|
@dataclass(frozen=True)
|
|
327
333
|
class ModelConfig:
|
|
328
334
|
provider: str = DEFAULT_PROVIDER
|
|
@@ -468,13 +474,30 @@ class WebSearchToolConfig:
|
|
|
468
474
|
)
|
|
469
475
|
|
|
470
476
|
|
|
477
|
+
@dataclass(frozen=True)
|
|
478
|
+
class TestShellToolConfig:
|
|
479
|
+
allow_patterns: tuple[str, ...] = ()
|
|
480
|
+
approval_required_patterns: tuple[str, ...] = ()
|
|
481
|
+
|
|
482
|
+
@classmethod
|
|
483
|
+
def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
|
|
484
|
+
return cls(
|
|
485
|
+
allow_patterns=_as_string_tuple(raw.get("allow_patterns")),
|
|
486
|
+
approval_required_patterns=_as_string_tuple(raw.get("approval_required_patterns")),
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
471
490
|
@dataclass(frozen=True)
|
|
472
491
|
class ToolsConfig:
|
|
473
492
|
web_search: WebSearchToolConfig = field(default_factory=WebSearchToolConfig)
|
|
493
|
+
test_shell: TestShellToolConfig = field(default_factory=TestShellToolConfig)
|
|
474
494
|
|
|
475
495
|
@classmethod
|
|
476
496
|
def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
|
|
477
|
-
return cls(
|
|
497
|
+
return cls(
|
|
498
|
+
web_search=WebSearchToolConfig.from_mapping(_as_mapping(raw.get("web_search"))),
|
|
499
|
+
test_shell=TestShellToolConfig.from_mapping(_as_mapping(raw.get("test_shell"))),
|
|
500
|
+
)
|
|
478
501
|
|
|
479
502
|
|
|
480
503
|
@dataclass(frozen=True)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## test_shell
|
|
2
|
+
|
|
3
|
+
Run constrained verification commands for tester subagents.
|
|
4
|
+
|
|
5
|
+
Args: `command`, optional `description`, optional `timeout_ms`, optional
|
|
6
|
+
`approval_token`.
|
|
7
|
+
|
|
8
|
+
`test_shell` parses the command into argv and does not run it through an
|
|
9
|
+
unrestricted raw shell. It rejects shell composition such as pipes, separators,
|
|
10
|
+
redirection, command substitution, heredocs, and background operators.
|
|
11
|
+
|
|
12
|
+
Low-risk verification commands run immediately and return command, cwd,
|
|
13
|
+
exit-code, elapsed time, stdout, stderr, and truncation metadata. Medium-risk
|
|
14
|
+
commands return `approval_required` with an `approvalToken`; the main Deepy
|
|
15
|
+
agent must ask the user before retrying the same command with that token.
|
|
16
|
+
Destructive, publishing, mutating, or unsupported commands are denied.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from deepy.config import Settings
|
|
7
|
+
from deepy.mcp import sdk_mcp_tool_name
|
|
8
|
+
from deepy.prompts import build_system_prompt
|
|
9
|
+
from deepy.skills import SkillInfo
|
|
10
|
+
from deepy.subagents import SubagentDefinition, discover_subagents
|
|
11
|
+
from deepy.tools import ToolRuntime
|
|
12
|
+
from deepy.tools.agents import build_function_tools
|
|
13
|
+
from deepy.tools.result import ToolResult
|
|
14
|
+
|
|
15
|
+
from .provider import ProviderBundle, build_provider_bundle
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from agents.mcp import MCPServer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_deepy_agent(
|
|
22
|
+
settings: Settings,
|
|
23
|
+
runtime: ToolRuntime,
|
|
24
|
+
*,
|
|
25
|
+
project_root: Path,
|
|
26
|
+
provider: ProviderBundle | None = None,
|
|
27
|
+
loaded_skills: list[SkillInfo] | None = None,
|
|
28
|
+
mcp_servers: list[MCPServer] | None = None,
|
|
29
|
+
preferred_mcp_web_search_tools: list[str] | None = None,
|
|
30
|
+
emit_event: Any | None = None,
|
|
31
|
+
):
|
|
32
|
+
from agents import Agent
|
|
33
|
+
|
|
34
|
+
provider = provider or build_provider_bundle(settings)
|
|
35
|
+
main_tools = build_function_tools(
|
|
36
|
+
runtime,
|
|
37
|
+
mimo_schema_compatibility=uses_mimo_tool_schema_compatibility(
|
|
38
|
+
settings.model.provider,
|
|
39
|
+
settings.model.name,
|
|
40
|
+
),
|
|
41
|
+
preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
|
|
42
|
+
)
|
|
43
|
+
subagent_tools = build_subagent_tools(
|
|
44
|
+
settings,
|
|
45
|
+
runtime,
|
|
46
|
+
project_root=project_root,
|
|
47
|
+
provider=provider,
|
|
48
|
+
mcp_servers=list(mcp_servers or []),
|
|
49
|
+
preferred_mcp_web_search_tools=preferred_mcp_web_search_tools or [],
|
|
50
|
+
mimo_schema_compatibility=uses_mimo_tool_schema_compatibility(
|
|
51
|
+
settings.model.provider,
|
|
52
|
+
settings.model.name,
|
|
53
|
+
),
|
|
54
|
+
emit_event=emit_event,
|
|
55
|
+
)
|
|
56
|
+
return Agent(
|
|
57
|
+
name="Deepy",
|
|
58
|
+
instructions=build_system_prompt(
|
|
59
|
+
project_root,
|
|
60
|
+
settings,
|
|
61
|
+
loaded_skills=loaded_skills,
|
|
62
|
+
preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
|
|
63
|
+
),
|
|
64
|
+
model=provider.model,
|
|
65
|
+
model_settings=provider.model_settings,
|
|
66
|
+
tools=[*main_tools, *subagent_tools],
|
|
67
|
+
mcp_servers=list(mcp_servers or []),
|
|
68
|
+
mcp_config={"include_server_in_tool_names": True},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def uses_mimo_tool_schema_compatibility(provider: str, model: str) -> bool:
|
|
73
|
+
normalized_provider = provider.strip().lower()
|
|
74
|
+
normalized_model = model.strip().lower()
|
|
75
|
+
if normalized_provider == "xiaomi":
|
|
76
|
+
return normalized_model in {"mimo-v2.5", "mimo-v2.5-pro"}
|
|
77
|
+
if normalized_provider == "openrouter":
|
|
78
|
+
return normalized_model in {"xiaomi/mimo-v2.5", "xiaomi/mimo-v2.5-pro"}
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def build_subagent_tools(
|
|
83
|
+
settings: Settings,
|
|
84
|
+
runtime: ToolRuntime,
|
|
85
|
+
*,
|
|
86
|
+
project_root: Path,
|
|
87
|
+
provider: ProviderBundle,
|
|
88
|
+
mcp_servers: list[MCPServer],
|
|
89
|
+
preferred_mcp_web_search_tools: list[str],
|
|
90
|
+
mimo_schema_compatibility: bool = False,
|
|
91
|
+
emit_event: Any | None = None,
|
|
92
|
+
) -> list[Any]:
|
|
93
|
+
from agents import Agent
|
|
94
|
+
|
|
95
|
+
discovery = discover_subagents(project_root)
|
|
96
|
+
tools: list[Any] = []
|
|
97
|
+
for definition in discovery.definitions:
|
|
98
|
+
subagent = Agent(
|
|
99
|
+
name=f"Deepy {definition.name}",
|
|
100
|
+
instructions=_subagent_instructions(definition, preferred_mcp_web_search_tools),
|
|
101
|
+
model=definition.model or provider.model,
|
|
102
|
+
model_settings=provider.model_settings,
|
|
103
|
+
tools=build_function_tools(
|
|
104
|
+
runtime,
|
|
105
|
+
mimo_schema_compatibility=mimo_schema_compatibility,
|
|
106
|
+
preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
|
|
107
|
+
include_tools=set(definition.tools),
|
|
108
|
+
),
|
|
109
|
+
mcp_servers=_search_mcp_servers_for_subagent(
|
|
110
|
+
definition,
|
|
111
|
+
mcp_servers,
|
|
112
|
+
preferred_mcp_web_search_tools,
|
|
113
|
+
),
|
|
114
|
+
mcp_config={"include_server_in_tool_names": True},
|
|
115
|
+
)
|
|
116
|
+
tools.append(
|
|
117
|
+
subagent.as_tool(
|
|
118
|
+
tool_name=definition.tool_name,
|
|
119
|
+
tool_description=definition.description,
|
|
120
|
+
custom_output_extractor=_subagent_output_extractor(definition),
|
|
121
|
+
on_stream=_subagent_stream_handler(definition, emit_event),
|
|
122
|
+
max_turns=definition.max_turns,
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
return tools
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _subagent_instructions(
|
|
129
|
+
definition: SubagentDefinition,
|
|
130
|
+
preferred_mcp_web_search_tools: list[str],
|
|
131
|
+
) -> str:
|
|
132
|
+
search_mcp = ""
|
|
133
|
+
if definition.mcp.inherit_search and preferred_mcp_web_search_tools:
|
|
134
|
+
search_mcp = (
|
|
135
|
+
"\n\nSearch-class MCP tools available to this subagent: "
|
|
136
|
+
+ ", ".join(preferred_mcp_web_search_tools)
|
|
137
|
+
+ ". Use them only for search/current-information work."
|
|
138
|
+
)
|
|
139
|
+
return (
|
|
140
|
+
f"{definition.instructions.strip()}\n\n"
|
|
141
|
+
"Return one concise final report to the main Deepy agent. Include assigned scope, "
|
|
142
|
+
"key findings or actions, relevant file paths or commands, and unresolved issues. "
|
|
143
|
+
"Do not ask the user directly; report blockers or approval needs to the main agent."
|
|
144
|
+
f"{search_mcp}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _subagent_output_extractor(definition: SubagentDefinition):
|
|
149
|
+
async def extract(result: Any) -> str:
|
|
150
|
+
output = getattr(result, "final_output", "")
|
|
151
|
+
text = output if isinstance(output, str) else str(output or "")
|
|
152
|
+
return ToolResult.ok_result(
|
|
153
|
+
definition.tool_name,
|
|
154
|
+
text,
|
|
155
|
+
metadata={
|
|
156
|
+
"kind": "subagent_result",
|
|
157
|
+
"subagent": definition.name,
|
|
158
|
+
"source": definition.source,
|
|
159
|
+
},
|
|
160
|
+
).to_json()
|
|
161
|
+
|
|
162
|
+
return extract
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _subagent_stream_handler(definition: SubagentDefinition, emit_event: Any | None):
|
|
166
|
+
async def handle(event: Any) -> None:
|
|
167
|
+
if emit_event is None:
|
|
168
|
+
return
|
|
169
|
+
from .events import DeepyStreamEvent, normalize_stream_event
|
|
170
|
+
from deepy.ui.message_view import format_tool_display_label
|
|
171
|
+
|
|
172
|
+
normalized = normalize_stream_event(event)
|
|
173
|
+
if normalized is None or normalized.kind != "tool_call":
|
|
174
|
+
return
|
|
175
|
+
nested_tool = normalized.name or "tool"
|
|
176
|
+
emit_event(
|
|
177
|
+
DeepyStreamEvent(
|
|
178
|
+
kind="status",
|
|
179
|
+
name=definition.tool_name,
|
|
180
|
+
text=(
|
|
181
|
+
f"{format_tool_display_label(definition.tool_name)} progress - "
|
|
182
|
+
f"using {nested_tool}"
|
|
183
|
+
),
|
|
184
|
+
payload={
|
|
185
|
+
"kind": "subagent_progress",
|
|
186
|
+
"subagent": definition.name,
|
|
187
|
+
"tool": nested_tool,
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return handle
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _search_mcp_servers_for_subagent(
|
|
196
|
+
definition: SubagentDefinition,
|
|
197
|
+
servers: list[MCPServer],
|
|
198
|
+
preferred_tools: list[str],
|
|
199
|
+
) -> list[MCPServer]:
|
|
200
|
+
if not definition.mcp.inherit_search or not preferred_tools:
|
|
201
|
+
return []
|
|
202
|
+
allowed = set(preferred_tools)
|
|
203
|
+
filtered: list[MCPServer] = []
|
|
204
|
+
for server in servers:
|
|
205
|
+
if not _looks_like_mcp_server(server):
|
|
206
|
+
continue
|
|
207
|
+
filtered.append(_SearchOnlyMcpServer(server, allowed)) # type: ignore[arg-type]
|
|
208
|
+
return filtered
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _looks_like_mcp_server(server: object) -> bool:
|
|
212
|
+
return all(hasattr(server, attr) for attr in ("call_tool", "list_tools", "name"))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _mcp_server_base() -> type[Any]:
|
|
216
|
+
from agents.mcp import MCPServer
|
|
217
|
+
|
|
218
|
+
return MCPServer
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class _SearchOnlyMcpServer(_mcp_server_base()):
|
|
222
|
+
def __init__(self, wrapped: MCPServer, allowed_model_names: set[str]) -> None:
|
|
223
|
+
super().__init__(
|
|
224
|
+
use_structured_content=bool(getattr(wrapped, "use_structured_content", False)),
|
|
225
|
+
failure_error_function=getattr(wrapped, "_failure_error_function", None),
|
|
226
|
+
)
|
|
227
|
+
self._wrapped = wrapped
|
|
228
|
+
self._allowed_model_names = allowed_model_names
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def name(self) -> str:
|
|
232
|
+
return str(getattr(self._wrapped, "name", "mcp"))
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def cached_tools(self) -> Any:
|
|
236
|
+
return getattr(self._wrapped, "cached_tools", None)
|
|
237
|
+
|
|
238
|
+
async def connect(self) -> None:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
async def cleanup(self) -> None:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
async def list_tools(self, *args: Any, **kwargs: Any) -> list[Any]:
|
|
245
|
+
listed = await self._wrapped.list_tools(*args, **kwargs)
|
|
246
|
+
return [
|
|
247
|
+
tool
|
|
248
|
+
for tool in listed
|
|
249
|
+
if sdk_mcp_tool_name(self.name, str(getattr(tool, "name", "")))
|
|
250
|
+
in self._allowed_model_names
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
async def call_tool(
|
|
254
|
+
self,
|
|
255
|
+
tool_name: str,
|
|
256
|
+
arguments: dict[str, Any] | None,
|
|
257
|
+
meta: dict[str, Any] | None = None,
|
|
258
|
+
) -> Any:
|
|
259
|
+
if sdk_mcp_tool_name(self.name, tool_name) not in self._allowed_model_names:
|
|
260
|
+
raise PermissionError(f"MCP tool is not available to this subagent: {tool_name}")
|
|
261
|
+
return await self._wrapped.call_tool(tool_name, arguments, meta)
|
|
262
|
+
|
|
263
|
+
async def list_prompts(self) -> Any:
|
|
264
|
+
return await self._wrapped.list_prompts()
|
|
265
|
+
|
|
266
|
+
async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> Any:
|
|
267
|
+
return await self._wrapped.get_prompt(name, arguments)
|
|
@@ -62,6 +62,14 @@ Core rules:
|
|
|
62
62
|
tasks so progress tracking does not create noise.
|
|
63
63
|
- `todo_write` is only for local task tracking. Do not treat it as subagent
|
|
64
64
|
delegation, a `task` tool, or a plan approval mode.
|
|
65
|
+
- Use subagent tools when a task has a clear independent specialist slice:
|
|
66
|
+
`subagent_explore` for broad read-only investigation, `subagent_reviewer` for
|
|
67
|
+
focused review, and `subagent_tester` for reproduction or verification. Keep
|
|
68
|
+
Deepy responsible for final synthesis and do not delegate tiny one-step work.
|
|
69
|
+
- If a subagent reports `test_shell` `approval_required`, ask the user through
|
|
70
|
+
`AskUserQuestion` with the exact command, policy reason, and approval token.
|
|
71
|
+
Retry only the same command through the constrained `test_shell` path after
|
|
72
|
+
the user approves; do not broaden access to raw shell.
|
|
65
73
|
|
|
66
74
|
Tool protocol:
|
|
67
75
|
Tool results are JSON strings: ok, name, output, error, metadata, awaitUserResponse.
|