diff --git a/src/openharness/prompts/context.py b/src/openharness/prompts/context.py index effedba..2b73a33 100644 --- a/src/openharness/prompts/context.py +++ b/src/openharness/prompts/context.py @@ -16,6 +16,7 @@ from openharness.memory import load_memory_prompt from openharness.memory.relevance import format_relevant_memories, select_relevant_memories from openharness.memory.usage import mark_memory_used from openharness.personalization.rules import load_local_rules +from openharness.permissions.modes import PermissionMode from openharness.prompts.claudemd import load_claude_md_prompt from openharness.prompts.system_prompt import build_system_prompt from openharness.skills.loader import load_skill_registry @@ -76,6 +77,28 @@ def _build_delegation_section() -> str: ) +def _build_permission_mode_section(settings: Settings) -> str: + """Build current permission-mode guidance for the model.""" + mode = settings.permission.mode + if mode == PermissionMode.PLAN: + guidance = ( + "Plan mode is enabled. Treat this session as read-only planning and analysis. " + "Do not call mutating tools such as file writes, edits, package installs, " + "state-changing shell commands, or task-spawning actions unless the user exits plan mode." + ) + elif mode == PermissionMode.FULL_AUTO: + guidance = ( + "Full-auto permission mode is enabled. You may use mutating tools when they are necessary " + "for the user's request, while still keeping changes scoped and intentional." + ) + else: + guidance = ( + "Default permission mode is enabled. Read-only tools can run directly; mutating tools " + "may require explicit user approval." + ) + return f"# Current Permission Mode\n{guidance}" + + def build_runtime_system_prompt( settings: Settings, *, @@ -94,6 +117,8 @@ def build_runtime_system_prompt( if not is_coordinator_mode() and settings.system_prompt is None: sections[0] = build_system_prompt(cwd=str(cwd)) + sections.append(_build_permission_mode_section(settings)) + if settings.fast_mode: sections.append( "# Session Mode\nFast mode is enabled. Prefer concise replies, minimal tool use, and quicker progress over exhaustive exploration." diff --git a/src/openharness/ui/runtime.py b/src/openharness/ui/runtime.py index 1ceff3b..feda659 100644 --- a/src/openharness/ui/runtime.py +++ b/src/openharness/ui/runtime.py @@ -601,6 +601,17 @@ def refresh_runtime_client(bundle: RuntimeBundle) -> None: default_model=settings.model, ) bundle.engine.set_model(settings.model) + bundle.engine.set_effort(settings.effort) + bundle.engine.set_permission_checker(PermissionChecker(settings.permission)) + system_prompt = build_runtime_system_prompt( + settings, + cwd=bundle.cwd, + latest_user_prompt=_last_user_text(bundle.engine.messages), + extra_skill_dirs=bundle.extra_skill_dirs, + extra_plugin_roots=bundle.extra_plugin_roots, + include_project_memory=bundle.include_project_memory, + ) + bundle.engine.set_system_prompt(system_prompt) sync_app_state(bundle) diff --git a/tests/test_prompts/test_claudemd.py b/tests/test_prompts/test_claudemd.py index d804e92..975e4f9 100644 --- a/tests/test_prompts/test_claudemd.py +++ b/tests/test_prompts/test_claudemd.py @@ -58,6 +58,23 @@ def test_build_runtime_system_prompt_combines_sections(tmp_path: Path, monkeypat assert "Memory" in prompt +def test_build_runtime_system_prompt_includes_plan_mode_guidance(tmp_path: Path, monkeypatch): + monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data")) + repo = tmp_path / "repo" + repo.mkdir() + + prompt = build_runtime_system_prompt( + Settings(permission={"mode": "plan"}), + cwd=repo, + latest_user_prompt="inspect only", + ) + + assert "Current Permission Mode" in prompt + assert "Plan mode is enabled" in prompt + assert "read-only planning and analysis" in prompt + assert "Do not call mutating tools" in prompt + + def test_build_runtime_system_prompt_includes_project_context_and_fast_mode(tmp_path: Path, monkeypatch): monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data")) repo = tmp_path / "repo" diff --git a/tests/test_ui/test_runtime_plan_mode.py b/tests/test_ui/test_runtime_plan_mode.py new file mode 100644 index 0000000..e80ed9b --- /dev/null +++ b/tests/test_ui/test_runtime_plan_mode.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from openharness.ui.runtime import build_runtime, close_runtime, handle_line + + +class _StaticApiClient: + async def stream_message(self, request): + del request + if False: + yield None + + +@pytest.mark.asyncio +async def test_plan_command_refreshes_engine_system_prompt(tmp_path: Path, monkeypatch): + monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config")) + monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data")) + monkeypatch.setenv("OPENHARNESS_LOGS_DIR", str(tmp_path / "logs")) + + bundle = await build_runtime(cwd=str(tmp_path), api_client=_StaticApiClient()) + try: + assert "Plan mode is enabled" not in bundle.engine.system_prompt + + async def print_system(text: str) -> None: + del text + + async def render_event(event) -> None: + del event + + async def clear_output() -> None: + pass + + should_continue = await handle_line( + bundle, + "/plan on", + print_system=print_system, + render_event=render_event, + clear_output=clear_output, + ) + + assert should_continue is True + assert bundle.app_state.get().permission_mode == "plan" + assert "Plan mode is enabled" in bundle.engine.system_prompt + assert "Do not call mutating tools" in bundle.engine.system_prompt + finally: + await close_runtime(bundle)