fix(runtime): refresh prompt for permission mode changes
Inject the active permission mode into the runtime system prompt so plan/default/full-auto state is visible to the model. Refresh the engine model, effort, permission checker, and system prompt when runtime-affecting slash commands update settings. Based-on: #268
This commit is contained in:
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user