diff --git a/reme/components/agent_wrapper/cc_session_store.py b/reme/components/agent_wrapper/cc_session_store.py index ca600c1b..185ee82f 100644 --- a/reme/components/agent_wrapper/cc_session_store.py +++ b/reme/components/agent_wrapper/cc_session_store.py @@ -104,7 +104,7 @@ class CcFileSessionStore: if not session_dir.is_dir(): return [] return [ - str(path.relative_to(session_dir).with_suffix("")) + path.relative_to(session_dir).with_suffix("").as_posix() for path in session_dir.rglob("*.jsonl") if path.is_file() ] diff --git a/reme/components/agent_wrapper/codex_agent_wrapper.py b/reme/components/agent_wrapper/codex_agent_wrapper.py index 733b55e0..b744452d 100644 --- a/reme/components/agent_wrapper/codex_agent_wrapper.py +++ b/reme/components/agent_wrapper/codex_agent_wrapper.py @@ -175,7 +175,8 @@ class CodexAgentWrapper(BaseAgentWrapper): snapshot_dir.mkdir(parents=True, exist_ok=True) fd, raw_path = tempfile.mkstemp(prefix="config-", suffix=".json", dir=snapshot_dir) try: - os.fchmod(fd, 0o600) + if hasattr(os, "fchmod"): + os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as stream: json.dump(self.app_context.app_config.model_dump(mode="json"), stream) except BaseException: diff --git a/reme/steps/evolve/auto_memory.py b/reme/steps/evolve/auto_memory.py index 5e513fab..431785c3 100644 --- a/reme/steps/evolve/auto_memory.py +++ b/reme/steps/evolve/auto_memory.py @@ -331,7 +331,7 @@ class AutoMemoryStep(BaseStep): if can_append: new_msgs = merged[len(existing) :] if new_msgs: - async with aiofiles.open(path, "a", encoding="utf-8") as f: + async with aiofiles.open(path, "a", encoding="utf-8", newline="") as f: for msg in new_msgs: await f.write(_sanitize_msg_for_save(msg).model_dump_json() + "\n") self.logger.info( @@ -344,7 +344,7 @@ class AutoMemoryStep(BaseStep): f"existing={len(existing)} total={len(merged)}", ) else: - async with aiofiles.open(path, "w", encoding="utf-8") as f: + async with aiofiles.open(path, "w", encoding="utf-8", newline="") as f: for msg in merged: await f.write(_sanitize_msg_for_save(msg).model_dump_json() + "\n") self.logger.info( diff --git a/tests/unit/test_codex_agent_wrapper.py b/tests/unit/test_codex_agent_wrapper.py index c3072dce..6f436d52 100644 --- a/tests/unit/test_codex_agent_wrapper.py +++ b/tests/unit/test_codex_agent_wrapper.py @@ -5,6 +5,7 @@ import asyncio from dataclasses import dataclass import json +import os from pathlib import Path import stat import sys @@ -575,6 +576,18 @@ def test_effective_mcp_config_snapshot_is_private_and_removed_on_close(tmp_path) assert not snapshot.exists() +def test_effective_mcp_config_snapshot_needs_no_fchmod(tmp_path, monkeypatch): + """A platform without ``os.fchmod`` (Windows) still gets a usable snapshot.""" + monkeypatch.delattr(os, "fchmod", raising=False) + + wrapper, _job = _wrapper(tmp_path) + config = wrapper._mcp_server_config({"job_tools": ["search"]}) + snapshot = Path(config["args"][config["args"].index("--config") + 1]) + + assert snapshot.exists() + assert json.loads(snapshot.read_text(encoding="utf-8"))["workspace_dir"] == str(tmp_path) + + @pytest.mark.asyncio async def test_effective_snapshot_exposes_parent_only_custom_job(tmp_path): from fastmcp import Client