diff --git a/openjiuwen/core/sys_operation/cwd.py b/openjiuwen/core/sys_operation/cwd.py index bee5e411f..bbd5c05e4 100644 --- a/openjiuwen/core/sys_operation/cwd.py +++ b/openjiuwen/core/sys_operation/cwd.py @@ -144,6 +144,21 @@ def set_workspace(path: str) -> None: _state().workspace = _resolve(path) +def get_agent_history_base_dir() -> str: + """Base directory for per-session tool history (``.agent_history/``). + + Prefers the agent workspace; when none is configured, falls back to a + user-level directory (``~/.openjiuwen/agent_history``) instead of the + project CWD, so history files never pollute the user's project tree + (#1490). + """ + workspace = get_workspace() + if workspace: + return workspace + home = os.path.expanduser("~") + return os.path.join(home, ".openjiuwen", "agent_history") + + # ---- Team workspace: shared across team members --------------------------- def get_team_workspace() -> str | None: diff --git a/openjiuwen/harness/tools/filesystem.py b/openjiuwen/harness/tools/filesystem.py index 80c7b2ac4..969016adf 100644 --- a/openjiuwen/harness/tools/filesystem.py +++ b/openjiuwen/harness/tools/filesystem.py @@ -24,7 +24,7 @@ from openjiuwen.core.common.logging import logger from openjiuwen.core.foundation.tool.base import Tool from openjiuwen.core.sys_operation import SysOperation -from openjiuwen.core.sys_operation.cwd import get_cwd, get_workspace +from openjiuwen.core.sys_operation.cwd import get_agent_history_base_dir, get_cwd, get_workspace from openjiuwen.harness.prompts.tools import build_tool_card from openjiuwen.harness.tools.base_tool import ToolOutput @@ -904,7 +904,7 @@ def _detect_encoding(raw: bytes) -> str: return "utf-16-le" if raw[:2] == b"\xff\xfe" else "utf-8" def _build_history_path(self, session: Any) -> str: - base_dir = get_workspace() or str(pathlib.Path(get_cwd()).expanduser().resolve()) + base_dir = get_agent_history_base_dir() agent_id = ( session.agent_id() if hasattr(session, "agent_id") else session.get_agent_id() if hasattr(session, "get_agent_id") @@ -1221,7 +1221,7 @@ def _try_quote_variants(self, content: str, old_str: str) -> Optional[str]: return content[index:index + len(old_str)] def _build_history_path(self, session: Any) -> str: - base_dir = get_workspace() or str(pathlib.Path(get_cwd()).expanduser().resolve()) + base_dir = get_agent_history_base_dir() agent_id = ( session.agent_id() if hasattr(session, "agent_id") else session.get_agent_id() if hasattr(session, "get_agent_id") diff --git a/openjiuwen/harness/tools/shell/bash/_tool.py b/openjiuwen/harness/tools/shell/bash/_tool.py index 717a2a009..eb4f7c725 100644 --- a/openjiuwen/harness/tools/shell/bash/_tool.py +++ b/openjiuwen/harness/tools/shell/bash/_tool.py @@ -149,8 +149,8 @@ def _parse_inputs(inputs: Dict[str, Any]) -> _BashInputs: # ── invoke ──────────────────────────────────────────────── def _build_history_path(self, session: Any) -> str: - from openjiuwen.core.sys_operation.cwd import get_cwd, get_workspace - base_dir = get_workspace() or str(pathlib.Path(get_cwd()).expanduser().resolve()) + from openjiuwen.core.sys_operation.cwd import get_agent_history_base_dir + base_dir = get_agent_history_base_dir() agent_id = ( session.agent_id() if hasattr(session, "agent_id") else session.get_agent_id() if hasattr(session, "get_agent_id") diff --git a/openjiuwen/harness/tools/shell/powershell/_tool.py b/openjiuwen/harness/tools/shell/powershell/_tool.py index e7018f20c..159c127bd 100644 --- a/openjiuwen/harness/tools/shell/powershell/_tool.py +++ b/openjiuwen/harness/tools/shell/powershell/_tool.py @@ -125,8 +125,8 @@ def _parse_inputs(inputs: Dict[str, Any]) -> _PowerShellInputs: ) def _build_history_path(self, session: Any) -> str: - from openjiuwen.core.sys_operation.cwd import get_cwd, get_workspace - base_dir = get_workspace() or str(pathlib.Path(get_cwd()).expanduser().resolve()) + from openjiuwen.core.sys_operation.cwd import get_agent_history_base_dir + base_dir = get_agent_history_base_dir() return os.path.join( base_dir, ".agent_history", f"file_ops_{self._agent_id}_{session.get_session_id()}.json", diff --git a/tests/unit_tests/core/sys_operation/test_cwd.py b/tests/unit_tests/core/sys_operation/test_cwd.py new file mode 100644 index 000000000..5bab6bd5d --- /dev/null +++ b/tests/unit_tests/core/sys_operation/test_cwd.py @@ -0,0 +1,42 @@ +# coding: utf-8 +# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved. + +"""Unit tests for the per-agent CWD state helpers in cwd.py. + +Focus: ``get_agent_history_base_dir`` — the tool-history base directory +that must prefer the agent workspace and, when none is configured, fall +back to a user-level directory instead of the project CWD (#1490). +""" + +import os +import tempfile + +from openjiuwen.core.sys_operation.cwd import ( + get_agent_history_base_dir, + init_cwd, + set_workspace, +) + + +def _reset_cwd_state() -> None: + """Reinitialize the per-context CWD state without a workspace.""" + init_cwd(os.getcwd()) + + +class TestGetAgentHistoryBaseDir: + def test_returns_workspace_when_set(self): + workspace = tempfile.mkdtemp() + try: + set_workspace(workspace) + assert get_agent_history_base_dir() == os.path.realpath(workspace) + finally: + _reset_cwd_state() + + def test_falls_back_to_user_level_dir_when_workspace_unset(self): + _reset_cwd_state() + base = get_agent_history_base_dir() + assert ".openjiuwen" in base + assert "agent_history" in base + assert not base.startswith(os.getcwd()), ( + "history base must NOT be the project CWD (#1490)" + )