mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 05:12:26 +00:00
* feat: add interactive agent confirmation mode and fix tool loading - Add `interactive` and `confirm_callback` params to ToolUsingAgent and NativeReActAgent so CLI sessions can prompt user before tool execution - Fix tool resolution in `ask` and `chat` commands to fall back to config.tools.enabled when no --tools flag is provided - Register shell_exec tool in tools/__init__.py auto-discovery - Add dspy and gepa as optional learning extras (learning-dspy, learning-gepa) - Fix openjarvis-rust lock version (1.0.0 → 0.1.0) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(cli): support configured tool defaults and confirmations --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
"""Tests for ``jarvis chat`` interactive REPL command."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest import mock
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from click.testing import CliRunner
|
|
|
|
from openjarvis.agents._stubs import (
|
|
AgentContext,
|
|
AgentResult,
|
|
BaseAgent,
|
|
ToolUsingAgent,
|
|
)
|
|
from openjarvis.cli.chat_cmd import _read_input, chat
|
|
from openjarvis.core.config import JarvisConfig
|
|
from openjarvis.core.registry import AgentRegistry, ToolRegistry
|
|
from openjarvis.core.types import ToolCall, ToolResult
|
|
from openjarvis.tools._stubs import BaseTool, ToolSpec
|
|
|
|
|
|
class _SimpleChatAgent(BaseAgent):
|
|
agent_id = "simple_chat_agent"
|
|
|
|
def run(self, input, context: AgentContext | None = None, **kwargs):
|
|
return AgentResult(content="simple ok", turns=1)
|
|
|
|
|
|
class _DangerousChatTool(BaseTool):
|
|
tool_id = "dangerous_chat"
|
|
|
|
@property
|
|
def spec(self) -> ToolSpec:
|
|
return ToolSpec(
|
|
name="dangerous_chat",
|
|
description="Confirmation-gated chat tool.",
|
|
requires_confirmation=True,
|
|
)
|
|
|
|
def execute(self, **params) -> ToolResult:
|
|
return ToolResult(
|
|
tool_name="dangerous_chat",
|
|
content="chat executed!",
|
|
success=True,
|
|
)
|
|
|
|
|
|
class _ToolChatAgent(ToolUsingAgent):
|
|
agent_id = "tool_chat_agent"
|
|
|
|
def run(self, input, context: AgentContext | None = None, **kwargs):
|
|
result = self._executor.execute(
|
|
ToolCall(id="chat", name="dangerous_chat", arguments="{}")
|
|
)
|
|
return AgentResult(content=result.content, tool_results=[result], turns=1)
|
|
|
|
|
|
class TestChatCommand:
|
|
"""Test the Click command definition and help output."""
|
|
|
|
def test_command_exists(self) -> None:
|
|
result = CliRunner().invoke(chat, ["--help"])
|
|
assert result.exit_code == 0
|
|
assert "interactive" in result.output.lower() or "chat" in result.output.lower()
|
|
|
|
def test_options(self) -> None:
|
|
result = CliRunner().invoke(chat, ["--help"])
|
|
assert result.exit_code == 0
|
|
assert "--engine" in result.output
|
|
assert "--model" in result.output
|
|
assert "--agent" in result.output
|
|
assert "--tools" in result.output
|
|
assert "--system" in result.output
|
|
|
|
def test_slash_commands_listed(self) -> None:
|
|
result = CliRunner().invoke(chat, ["--help"])
|
|
assert result.exit_code == 0
|
|
assert "/quit" in result.output
|
|
|
|
|
|
class TestReadInput:
|
|
"""Test the _read_input helper function."""
|
|
|
|
def test_read_input_eof(self) -> None:
|
|
with mock.patch("builtins.input", side_effect=EOFError):
|
|
assert _read_input() is None
|
|
|
|
def test_read_input_keyboard_interrupt(self) -> None:
|
|
with mock.patch("builtins.input", side_effect=KeyboardInterrupt):
|
|
assert _read_input() is None
|
|
|
|
def test_read_input_normal(self) -> None:
|
|
with mock.patch("builtins.input", return_value="hello"):
|
|
assert _read_input() == "hello"
|
|
|
|
|
|
class TestChatAgents:
|
|
def test_simple_agent_does_not_receive_tool_only_kwargs(self) -> None:
|
|
engine = MagicMock()
|
|
engine.engine_id = "mock"
|
|
engine.generate.return_value = {"content": "engine fallback"}
|
|
config = JarvisConfig()
|
|
config.intelligence.default_model = "test-model"
|
|
|
|
AgentRegistry.register_value("simple_chat_agent", _SimpleChatAgent)
|
|
|
|
with (
|
|
patch("openjarvis.cli.chat_cmd.load_config", return_value=config),
|
|
patch("openjarvis.engine.get_engine", return_value=("mock", engine)),
|
|
patch("openjarvis.intelligence.register_builtin_models"),
|
|
):
|
|
result = CliRunner().invoke(
|
|
chat,
|
|
["--agent", "simple_chat_agent", "--model", "test-model"],
|
|
input="hello\n/quit\n",
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "simple ok" in result.output
|
|
assert "failed" not in result.output.lower()
|
|
|
|
def test_tool_agent_uses_legacy_agent_tools_and_prompts_confirmation(self) -> None:
|
|
engine = MagicMock()
|
|
engine.engine_id = "mock"
|
|
config = JarvisConfig()
|
|
config.intelligence.default_model = "test-model"
|
|
config.agent.tools = "dangerous_chat"
|
|
config.agent.max_turns = 3
|
|
|
|
AgentRegistry.register_value("tool_chat_agent", _ToolChatAgent)
|
|
ToolRegistry.register_value("dangerous_chat", _DangerousChatTool)
|
|
|
|
with (
|
|
patch("openjarvis.cli.chat_cmd.load_config", return_value=config),
|
|
patch("openjarvis.engine.get_engine", return_value=("mock", engine)),
|
|
patch("openjarvis.intelligence.register_builtin_models"),
|
|
):
|
|
result = CliRunner().invoke(
|
|
chat,
|
|
["--agent", "tool_chat_agent", "--model", "test-model"],
|
|
input="run tool\ny\n/quit\n",
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Confirm:" in result.output
|
|
assert "chat executed!" in result.output
|