diff --git a/acp_adapter/session.py b/acp_adapter/session.py index 6e16016a6b3bb..ce6843ab6234c 100644 --- a/acp_adapter/session.py +++ b/acp_adapter/session.py @@ -56,7 +56,18 @@ def _normalize_cwd_for_compare(cwd: str | None) -> str: elif re.match(r"^/mnt/[A-Za-z]/", expanded): expanded = f"/mnt/{expanded[5].lower()}/{expanded[7:]}" - return os.path.normpath(expanded) + # Resolve symlink aliases so equivalent spellings of the same directory + # compare equal — macOS reports editor workspaces as ``/var/...`` while + # sessions get stored under ``/private/var/...`` (and ``/tmp`` vs + # ``/private/tmp``), which made ACP history filters silently drop a + # workspace's own sessions. ``os.path.realpath`` is lexical for missing + # paths (strict=False), so cwds that don't exist on this host — e.g. + # WSL-translated Windows drives — keep the previous normpath behavior. + # Ported from PrimeIntellect-ai/prime-agent#628. + try: + return os.path.realpath(expanded) + except OSError: + return os.path.normpath(expanded) def _build_session_title(title: Any, preview: Any, cwd: str | None) -> str: diff --git a/tests/acp/test_session.py b/tests/acp/test_session.py index 88d2a27998e62..3d8bc1908909b 100644 --- a/tests/acp/test_session.py +++ b/tests/acp/test_session.py @@ -158,6 +158,54 @@ class TestWslCwdTranslation: # --------------------------------------------------------------------------- +class TestSymlinkAliasNormalization: + """Ported from PrimeIntellect-ai/prime-agent#628 — symlink aliases of the + same directory (macOS ``/var`` vs ``/private/var``, ``/tmp`` vs + ``/private/tmp``) must compare equal, or ACP history filters silently drop + a workspace's own sessions.""" + + def test_symlink_alias_compares_equal(self, tmp_path): + real = tmp_path / "real" + real.mkdir() + alias = tmp_path / "alias" + alias.symlink_to(real) + assert acp_session._normalize_cwd_for_compare( + str(alias) + ) == acp_session._normalize_cwd_for_compare(str(real)) + + def test_distinct_dirs_still_compare_different(self, tmp_path): + a = tmp_path / "a" + b = tmp_path / "b" + a.mkdir() + b.mkdir() + assert acp_session._normalize_cwd_for_compare( + str(a) + ) != acp_session._normalize_cwd_for_compare(str(b)) + + def test_missing_path_keeps_lexical_normalization(self): + # realpath(strict=False) is lexical for nonexistent paths, so cwds + # that don't exist on this host (e.g. WSL-translated drives) behave + # exactly as the old normpath comparison did. + assert acp_session._normalize_cwd_for_compare( + "/nonexistent-hermes-test/x/../y" + ) == "/nonexistent-hermes-test/y" + + def test_list_sessions_matches_symlink_alias_cwd(self, manager, tmp_path): + real = tmp_path / "proj" + real.mkdir() + alias = tmp_path / "link" + alias.symlink_to(real) + state = manager.create_session(cwd=str(real)) + state.history.append({"role": "user", "content": "hello"}) + listed = manager.list_sessions(cwd=str(alias)) + assert [s["session_id"] for s in listed] == [state.session_id] + + +# --------------------------------------------------------------------------- +# list / cleanup +# --------------------------------------------------------------------------- + + class TestListAndCleanup: def test_list_sessions_empty(self, manager): assert manager.list_sessions() == []