EvoScientist 0.0.1.dev5__tar.gz → 0.0.1.dev7__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.
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/EvoScientist.py +30 -11
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/__init__.py +5 -1
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/backends.py +29 -98
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/imessage/serve.py +7 -7
- evoscientist-0.0.1.dev7/EvoScientist/cli/__init__.py +26 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/_app.py +47 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/agent.py +60 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/channel.py +289 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/commands.py +470 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/interactive.py +717 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/mcp_ui.py +282 -0
- evoscientist-0.0.1.dev7/EvoScientist/cli/skills_cmd.py +73 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/config.py +17 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/llm/__init__.py +2 -0
- evoscientist-0.0.1.dev7/EvoScientist/llm/models.py +193 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/mcp/__init__.py +5 -2
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/mcp/client.py +131 -55
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/memory.py +26 -50
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/onboard.py +408 -178
- evoscientist-0.0.1.dev7/EvoScientist/sessions.py +345 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/display.py +23 -18
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/events.py +59 -4
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/tools/__init__.py +0 -2
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/utils.py +3 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist.egg-info/PKG-INFO +27 -24
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist.egg-info/SOURCES.txt +12 -2
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist.egg-info/requires.txt +4 -3
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/PKG-INFO +27 -24
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/README.md +22 -20
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/pyproject.toml +5 -4
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_backends.py +6 -6
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_llm.py +37 -46
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_mcp_client.py +1 -1
- evoscientist-0.0.1.dev7/tests/test_memory_merge.py +73 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_onboard.py +67 -8
- evoscientist-0.0.1.dev7/tests/test_sessions.py +287 -0
- evoscientist-0.0.1.dev7/tests/test_stream_events.py +87 -0
- evoscientist-0.0.1.dev7/tests/test_tools.py +18 -0
- evoscientist-0.0.1.dev5/EvoScientist/cli.py +0 -1653
- evoscientist-0.0.1.dev5/EvoScientist/llm/models.py +0 -115
- evoscientist-0.0.1.dev5/EvoScientist/tools/image.py +0 -74
- evoscientist-0.0.1.dev5/tests/test_tools.py +0 -107
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/__main__.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/__init__.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/base.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/imessage/__init__.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/imessage/channel_rpc.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/imessage/probe.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/imessage/rpc_client.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/channels/imessage/targets.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/middleware.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/paths.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/prompts.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/find-skills/SKILL.md +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/skill-creator/SKILL.md +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/skill-creator/references/output-patterns.md +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/skill-creator/references/workflows.md +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/__init__.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/emitter.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/formatter.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/state.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/tracker.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/stream/utils.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/subagent.yaml +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/tools/search.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/tools/skill_manager.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/tools/skills_manager.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist/tools/think.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist.egg-info/dependency_links.txt +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist.egg-info/entry_points.txt +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/EvoScientist.egg-info/top_level.txt +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/LICENSE +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/setup.cfg +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_cli_run_name.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_config.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_event_loop.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_imports.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_prompts.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_skills_manager.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_stream_emitter.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_stream_state.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_stream_tracker.py +0 -0
- {evoscientist-0.0.1.dev5 → evoscientist-0.0.1.dev7}/tests/test_stream_utils.py +0 -0
|
@@ -26,7 +26,7 @@ from .mcp import load_mcp_tools
|
|
|
26
26
|
from .middleware import create_skills_middleware, create_memory_middleware
|
|
27
27
|
from .prompts import RESEARCHER_INSTRUCTIONS, get_system_prompt
|
|
28
28
|
from .utils import load_subagents
|
|
29
|
-
from .tools import tavily_search, think_tool, skill_manager
|
|
29
|
+
from .tools import tavily_search, think_tool, skill_manager
|
|
30
30
|
from .paths import (
|
|
31
31
|
ensure_dirs,
|
|
32
32
|
default_workspace_dir,
|
|
@@ -115,11 +115,10 @@ backend = CompositeBackend(
|
|
|
115
115
|
tool_registry = {
|
|
116
116
|
"think_tool": think_tool,
|
|
117
117
|
"tavily_search": tavily_search,
|
|
118
|
-
"view_image": view_image,
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
# Base tools that every agent variant gets (before MCP)
|
|
122
|
-
BASE_TOOLS = [think_tool, skill_manager
|
|
121
|
+
BASE_TOOLS = [think_tool, skill_manager]
|
|
123
122
|
|
|
124
123
|
|
|
125
124
|
def _build_base_kwargs(base_backend, base_middleware):
|
|
@@ -190,21 +189,41 @@ base_middleware = [
|
|
|
190
189
|
]
|
|
191
190
|
|
|
192
191
|
# Default agent (no checkpointer) — used by langgraph dev / LangSmith / notebooks.
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
# Lazily constructed on first access so MCP tools are included without
|
|
193
|
+
# spawning subprocesses at import time.
|
|
194
|
+
_EvoScientist_agent = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _get_default_agent():
|
|
198
|
+
"""Build the default agent (with MCP, no checkpointer) on first access."""
|
|
199
|
+
global _EvoScientist_agent
|
|
200
|
+
if _EvoScientist_agent is None:
|
|
201
|
+
kwargs = load_mcp_and_build_kwargs(backend, base_middleware)
|
|
202
|
+
_EvoScientist_agent = create_deep_agent(**kwargs).with_config(
|
|
203
|
+
{"recursion_limit": 500}
|
|
204
|
+
)
|
|
205
|
+
return _EvoScientist_agent
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def __getattr__(name: str):
|
|
209
|
+
if name == "EvoScientist_agent":
|
|
210
|
+
return _get_default_agent()
|
|
211
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
197
212
|
|
|
198
213
|
|
|
199
|
-
def create_cli_agent(workspace_dir: str | None = None):
|
|
200
|
-
"""Create agent with
|
|
214
|
+
def create_cli_agent(workspace_dir: str | None = None, checkpointer=None):
|
|
215
|
+
"""Create agent with checkpointer for CLI multi-turn support.
|
|
201
216
|
|
|
202
217
|
Args:
|
|
203
218
|
workspace_dir: Optional per-session workspace directory. If provided,
|
|
204
219
|
creates a fresh backend rooted at this path. If None, uses the
|
|
205
220
|
module-level default backend (./workspace).
|
|
221
|
+
checkpointer: Optional LangGraph checkpointer. If None, falls back
|
|
222
|
+
to ``InMemorySaver`` (non-persistent).
|
|
206
223
|
"""
|
|
207
|
-
|
|
224
|
+
if checkpointer is None:
|
|
225
|
+
from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
|
|
226
|
+
checkpointer = InMemorySaver()
|
|
208
227
|
|
|
209
228
|
if workspace_dir:
|
|
210
229
|
set_active_workspace(workspace_dir)
|
|
@@ -242,5 +261,5 @@ def create_cli_agent(workspace_dir: str | None = None):
|
|
|
242
261
|
|
|
243
262
|
return create_deep_agent(
|
|
244
263
|
**kwargs,
|
|
245
|
-
checkpointer=
|
|
264
|
+
checkpointer=checkpointer,
|
|
246
265
|
).with_config({"recursion_limit": 500})
|
|
@@ -36,7 +36,11 @@ _EXPORTS: dict[str, tuple[str, str]] = {
|
|
|
36
36
|
# Tools
|
|
37
37
|
"tavily_search": (".tools", "tavily_search"),
|
|
38
38
|
"think_tool": (".tools", "think_tool"),
|
|
39
|
-
|
|
39
|
+
# Sessions
|
|
40
|
+
"get_checkpointer": (".sessions", "get_checkpointer"),
|
|
41
|
+
"generate_thread_id": (".sessions", "generate_thread_id"),
|
|
42
|
+
"list_threads": (".sessions", "list_threads"),
|
|
43
|
+
"delete_thread": (".sessions", "delete_thread"),
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
-
import subprocess
|
|
6
5
|
import uuid
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
|
|
9
|
-
from deepagents.backends import FilesystemBackend
|
|
8
|
+
from deepagents.backends import FilesystemBackend, LocalShellBackend
|
|
10
9
|
from deepagents.backends.filesystem import WriteResult, EditResult
|
|
11
10
|
from deepagents.backends.protocol import (
|
|
12
11
|
BackendProtocol,
|
|
13
12
|
ExecuteResponse,
|
|
14
13
|
FileDownloadResponse,
|
|
15
14
|
FileUploadResponse,
|
|
16
|
-
SandboxBackendProtocol,
|
|
17
15
|
)
|
|
18
16
|
|
|
19
17
|
# System path prefixes that should never appear in virtual paths.
|
|
@@ -226,25 +224,24 @@ class MergedReadOnlyBackend(BackendProtocol):
|
|
|
226
224
|
]
|
|
227
225
|
|
|
228
226
|
|
|
229
|
-
class CustomSandboxBackend(
|
|
227
|
+
class CustomSandboxBackend(LocalShellBackend):
|
|
230
228
|
"""
|
|
231
|
-
Custom sandbox backend - inherits
|
|
229
|
+
Custom sandbox backend - inherits LocalShellBackend with added safety.
|
|
232
230
|
|
|
233
231
|
Features:
|
|
234
232
|
- Inherits all file operations (ls, read, write, edit, grep, glob)
|
|
235
|
-
-
|
|
236
|
-
-
|
|
237
|
-
-
|
|
233
|
+
- Inherits shell command execution with output truncation and timeout
|
|
234
|
+
- Adds command validation to prevent directory traversal and dangerous operations
|
|
235
|
+
- Adds path sanitization to auto-correct common LLM path mistakes
|
|
238
236
|
- Compatible with LangGraph checkpointer (no thread locks)
|
|
239
237
|
"""
|
|
240
238
|
|
|
241
239
|
def __init__(
|
|
242
240
|
self,
|
|
243
241
|
root_dir: str = ".",
|
|
242
|
+
*,
|
|
244
243
|
virtual_mode: bool = True,
|
|
245
|
-
working_dir: str | None = None,
|
|
246
244
|
timeout: int = 300,
|
|
247
|
-
shell: str = "/bin/bash",
|
|
248
245
|
max_output_bytes: int = 100_000,
|
|
249
246
|
env: dict[str, str] | None = None,
|
|
250
247
|
inherit_env: bool = True,
|
|
@@ -255,35 +252,23 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
|
|
|
255
252
|
Args:
|
|
256
253
|
root_dir: File system root directory
|
|
257
254
|
virtual_mode: Whether to enable virtual path mode
|
|
258
|
-
working_dir: Working directory for command execution (defaults to root_dir)
|
|
259
255
|
timeout: Command execution timeout in seconds
|
|
260
|
-
shell: Shell program to use
|
|
261
256
|
max_output_bytes: Max output size before truncation (default 100KB)
|
|
262
257
|
env: Extra environment variables for subprocess
|
|
263
258
|
inherit_env: Whether to inherit parent process env (default True)
|
|
264
259
|
"""
|
|
265
|
-
super().__init__(
|
|
266
|
-
|
|
260
|
+
super().__init__(
|
|
261
|
+
root_dir=root_dir,
|
|
262
|
+
virtual_mode=virtual_mode,
|
|
263
|
+
timeout=timeout,
|
|
264
|
+
max_output_bytes=max_output_bytes,
|
|
265
|
+
env=env,
|
|
266
|
+
inherit_env=inherit_env,
|
|
267
|
+
)
|
|
268
|
+
# Override parent's "local-" prefix with our own
|
|
267
269
|
self._sandbox_id = f"evosci-{uuid.uuid4().hex[:8]}"
|
|
268
|
-
self.working_dir = working_dir or root_dir
|
|
269
|
-
self.timeout = timeout
|
|
270
|
-
self.shell = shell
|
|
271
|
-
self.virtual_mode = virtual_mode
|
|
272
|
-
self._max_output_bytes = max_output_bytes
|
|
273
|
-
|
|
274
|
-
# Build subprocess environment
|
|
275
|
-
if inherit_env:
|
|
276
|
-
self._env = {**os.environ, **(env or {})}
|
|
277
|
-
else:
|
|
278
|
-
self._env = dict(env) if env else {}
|
|
279
|
-
|
|
280
270
|
# Ensure working directory exists
|
|
281
|
-
os.makedirs(self.
|
|
282
|
-
|
|
283
|
-
@property
|
|
284
|
-
def id(self) -> str:
|
|
285
|
-
"""Unique identifier for the sandbox backend instance."""
|
|
286
|
-
return self._sandbox_id
|
|
271
|
+
os.makedirs(str(self.cwd), exist_ok=True)
|
|
287
272
|
|
|
288
273
|
def _resolve_path(self, key: str) -> Path:
|
|
289
274
|
"""Resolve path with sanitization to prevent nested directories.
|
|
@@ -326,74 +311,20 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
|
|
|
326
311
|
- Access to paths outside workspace
|
|
327
312
|
- Dangerous system commands
|
|
328
313
|
|
|
329
|
-
|
|
330
|
-
command: Command string to execute
|
|
331
|
-
|
|
332
|
-
Returns:
|
|
333
|
-
ExecuteResponse containing output, exit_code, and truncated flag
|
|
314
|
+
Then delegates to LocalShellBackend.execute() for actual execution.
|
|
334
315
|
"""
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if error:
|
|
339
|
-
return ExecuteResponse(
|
|
340
|
-
output=error,
|
|
341
|
-
exit_code=1,
|
|
342
|
-
truncated=False,
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
# Convert virtual paths to relative paths
|
|
346
|
-
if self.virtual_mode:
|
|
347
|
-
command = convert_virtual_paths_in_command(command=command)
|
|
348
|
-
|
|
349
|
-
result = subprocess.run(
|
|
350
|
-
command,
|
|
351
|
-
shell=True,
|
|
352
|
-
executable=self.shell,
|
|
353
|
-
cwd=self.working_dir,
|
|
354
|
-
capture_output=True,
|
|
355
|
-
text=True,
|
|
356
|
-
timeout=self.timeout,
|
|
357
|
-
env=self._env,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
output_parts = []
|
|
361
|
-
if result.stdout:
|
|
362
|
-
output_parts.append(result.stdout)
|
|
363
|
-
if result.stderr:
|
|
364
|
-
stderr_lines = result.stderr.strip().split("\n")
|
|
365
|
-
output_parts.extend(f"[stderr] {line}" for line in stderr_lines)
|
|
366
|
-
output = "\n".join(output_parts) if output_parts else ""
|
|
367
|
-
|
|
368
|
-
if result.returncode != 0:
|
|
369
|
-
output = f"{output.rstrip()}\n\nExit code: {result.returncode}"
|
|
370
|
-
|
|
371
|
-
truncated = False
|
|
372
|
-
if len(output) > self._max_output_bytes:
|
|
373
|
-
output = output[:self._max_output_bytes]
|
|
374
|
-
output += f"\n\n... Output truncated at {self._max_output_bytes} bytes."
|
|
375
|
-
truncated = True
|
|
376
|
-
|
|
377
|
-
return ExecuteResponse(
|
|
378
|
-
output=output,
|
|
379
|
-
exit_code=result.returncode,
|
|
380
|
-
truncated=truncated,
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
except subprocess.TimeoutExpired:
|
|
316
|
+
# Validate command safety
|
|
317
|
+
error = validate_command(command)
|
|
318
|
+
if error:
|
|
384
319
|
return ExecuteResponse(
|
|
385
|
-
output=
|
|
386
|
-
exit_code
|
|
387
|
-
truncated=False,
|
|
388
|
-
)
|
|
389
|
-
except Exception as e:
|
|
390
|
-
return ExecuteResponse(
|
|
391
|
-
output=f"Error executing command: {str(e)}",
|
|
392
|
-
exit_code=-1,
|
|
320
|
+
output=error,
|
|
321
|
+
exit_code=1,
|
|
393
322
|
truncated=False,
|
|
394
323
|
)
|
|
395
324
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
325
|
+
# Convert virtual paths to relative paths
|
|
326
|
+
if self.virtual_mode:
|
|
327
|
+
command = convert_virtual_paths_in_command(command=command)
|
|
328
|
+
|
|
329
|
+
# Delegate to parent for subprocess execution
|
|
330
|
+
return super().execute(command)
|
|
@@ -25,11 +25,6 @@ from typing import Callable
|
|
|
25
25
|
from . import IMessageChannel, IMessageConfig
|
|
26
26
|
from ..base import OutgoingMessage
|
|
27
27
|
|
|
28
|
-
logging.basicConfig(
|
|
29
|
-
level=logging.DEBUG,
|
|
30
|
-
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
31
|
-
datefmt="%H:%M:%S",
|
|
32
|
-
)
|
|
33
28
|
logger = logging.getLogger(__name__)
|
|
34
29
|
|
|
35
30
|
|
|
@@ -223,7 +218,7 @@ class IMessageServer:
|
|
|
223
218
|
await self.channel.send(OutgoingMessage(
|
|
224
219
|
recipient=sender,
|
|
225
220
|
content=response,
|
|
226
|
-
metadata=metadata,
|
|
221
|
+
metadata=metadata or {},
|
|
227
222
|
))
|
|
228
223
|
if self._on_activity:
|
|
229
224
|
try:
|
|
@@ -387,7 +382,7 @@ async def async_main():
|
|
|
387
382
|
config = IMessageConfig(
|
|
388
383
|
cli_path=args.cli_path,
|
|
389
384
|
db_path=args.db_path,
|
|
390
|
-
allowed_senders=
|
|
385
|
+
allowed_senders=list(args.allowed_senders) if args.allowed_senders else [],
|
|
391
386
|
include_attachments=args.attachments,
|
|
392
387
|
)
|
|
393
388
|
|
|
@@ -421,6 +416,11 @@ async def async_main():
|
|
|
421
416
|
|
|
422
417
|
def main():
|
|
423
418
|
"""Entry point."""
|
|
419
|
+
logging.basicConfig(
|
|
420
|
+
level=logging.DEBUG,
|
|
421
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
422
|
+
datefmt="%H:%M:%S",
|
|
423
|
+
)
|
|
424
424
|
asyncio.run(async_main())
|
|
425
425
|
|
|
426
426
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""EvoScientist CLI package."""
|
|
2
|
+
|
|
3
|
+
# Backward-compat re-exports (tests import these from EvoScientist.cli)
|
|
4
|
+
from ..stream.state import ( # noqa: F401
|
|
5
|
+
SubAgentState,
|
|
6
|
+
StreamState,
|
|
7
|
+
_parse_todo_items,
|
|
8
|
+
_build_todo_stats,
|
|
9
|
+
)
|
|
10
|
+
from .channel import ChannelMessage, _ChannelState # noqa: F401
|
|
11
|
+
from .agent import _deduplicate_run_name # noqa: F401
|
|
12
|
+
|
|
13
|
+
from ._app import app # noqa: F401
|
|
14
|
+
from . import commands # noqa: F401 — registers @app.command decorators
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
"""CLI entry point."""
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
21
|
+
warnings.filterwarnings("ignore", message=".*not known to support tools.*")
|
|
22
|
+
warnings.filterwarnings("ignore", message=".*type is unknown and inference may fail.*")
|
|
23
|
+
from .commands import _configure_logging
|
|
24
|
+
|
|
25
|
+
_configure_logging()
|
|
26
|
+
app()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Typer application objects — no intra-package imports to avoid circular deps."""
|
|
2
|
+
|
|
3
|
+
import typer # type: ignore[import-untyped]
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(
|
|
6
|
+
no_args_is_help=False,
|
|
7
|
+
add_completion=False,
|
|
8
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Config subcommand group
|
|
12
|
+
config_app = typer.Typer(help="Configuration management commands", invoke_without_command=True)
|
|
13
|
+
app.add_typer(config_app, name="config")
|
|
14
|
+
|
|
15
|
+
# MCP subcommand group
|
|
16
|
+
_MCP_HELP = """\
|
|
17
|
+
Configure and manage MCP servers
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
# Add a local MCP server (stdio auto-detected):
|
|
21
|
+
EvoSci mcp add local-server python -- /path/to/server.py
|
|
22
|
+
|
|
23
|
+
# Add an npx-based server:
|
|
24
|
+
EvoSci mcp add sequential-thinking npx -- -y @modelcontextprotocol/server-sequential-thinking
|
|
25
|
+
|
|
26
|
+
# Add an HTTP server (http auto-detected from URL):
|
|
27
|
+
EvoSci mcp add docs-langchain https://docs.langchain.com/mcp
|
|
28
|
+
|
|
29
|
+
# Add a stdio server with env vars (hardcoded):
|
|
30
|
+
EvoSci mcp add my-server node --env API_KEY=xxx -- server.js
|
|
31
|
+
|
|
32
|
+
# Add a server with runtime env ref (resolved from .env at startup):
|
|
33
|
+
EvoSci mcp add brave-search npx --env-ref BRAVE_API_KEY -- -y @modelcontextprotocol/server-brave-search
|
|
34
|
+
|
|
35
|
+
# Expose to a specific sub-agent (e.g. research-agent):
|
|
36
|
+
EvoSci mcp add brave-search npx --env-ref BRAVE_API_KEY -e research-agent -- -y @modelcontextprotocol/server-brave-search
|
|
37
|
+
|
|
38
|
+
# Expose to multiple agents:
|
|
39
|
+
EvoSci mcp add local-server python -e main,research-agent,code-agent -- /path/to/server.py
|
|
40
|
+
|
|
41
|
+
# Explicit transport override:
|
|
42
|
+
EvoSci mcp add my-sse https://example.com/sse --transport sse
|
|
43
|
+
|
|
44
|
+
Sub-agents (-e): planner-agent | research-agent | code-agent | debug-agent | data-analysis-agent | writing-agent
|
|
45
|
+
"""
|
|
46
|
+
mcp_app = typer.Typer(help=_MCP_HELP, invoke_without_command=True)
|
|
47
|
+
app.add_typer(mcp_app, name="mcp")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Agent loading and workspace helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..paths import new_run_dir, RUNS_DIR
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _shorten_path(path: str) -> str:
|
|
11
|
+
"""Shorten absolute path to relative path from current directory."""
|
|
12
|
+
if not path:
|
|
13
|
+
return path
|
|
14
|
+
try:
|
|
15
|
+
cwd = os.getcwd()
|
|
16
|
+
if path.startswith(cwd):
|
|
17
|
+
rel = path[len(cwd):].lstrip(os.sep)
|
|
18
|
+
return os.path.join(os.path.basename(cwd), rel) if rel else os.path.basename(cwd)
|
|
19
|
+
return path
|
|
20
|
+
except Exception:
|
|
21
|
+
return path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _deduplicate_run_name(name: str, runs_dir: Path = RUNS_DIR) -> str:
|
|
25
|
+
"""Return *name* if available, otherwise *name_1*, *name_2*, etc."""
|
|
26
|
+
if not (runs_dir / name).exists():
|
|
27
|
+
return name
|
|
28
|
+
i = 1
|
|
29
|
+
while (runs_dir / f"{name}_{i}").exists():
|
|
30
|
+
i += 1
|
|
31
|
+
return f"{name}_{i}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _create_session_workspace(name: str | None = None) -> str:
|
|
35
|
+
"""Create a per-session workspace directory and return its path.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: Optional human-friendly run name. Duplicates are resolved
|
|
39
|
+
by appending ``_1``, ``_2``, etc. Falls back to a timestamp
|
|
40
|
+
if *name* is None.
|
|
41
|
+
"""
|
|
42
|
+
if name:
|
|
43
|
+
session_id = _deduplicate_run_name(name)
|
|
44
|
+
else:
|
|
45
|
+
session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
46
|
+
workspace_dir = str(new_run_dir(session_id))
|
|
47
|
+
os.makedirs(workspace_dir, exist_ok=True)
|
|
48
|
+
return workspace_dir
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _load_agent(workspace_dir: str | None = None, checkpointer=None):
|
|
52
|
+
"""Load the CLI agent with optional persistent checkpointer.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
workspace_dir: Optional per-session workspace directory.
|
|
56
|
+
checkpointer: Optional LangGraph checkpointer (e.g. ``AsyncSqliteSaver``).
|
|
57
|
+
Falls back to ``InMemorySaver`` when ``None``.
|
|
58
|
+
"""
|
|
59
|
+
from ..EvoScientist import create_cli_agent
|
|
60
|
+
return create_cli_agent(workspace_dir=workspace_dir, checkpointer=checkpointer)
|