Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions backend/app/faros/runtime/state_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def list_runs(self) -> List[Dict[str, Any]]:
runs = []
for path in sorted(self.root.glob("*/run.json"), reverse=True):
try:
runs.append(json.loads(path.read_text()))
runs.append(json.loads(path.read_text(encoding="utf-8")))
except Exception:
continue
return runs
Expand All @@ -91,7 +91,7 @@ def get_run(self, run_id: str) -> Optional[Dict[str, Any]]:
path = self._run_path(run_id)
if not path.is_file():
return None
return json.loads(path.read_text())
return json.loads(path.read_text(encoding="utf-8"))

def update_run(self, run_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
record = self.get_run(run_id)
Expand Down Expand Up @@ -120,7 +120,7 @@ def list_events(self, run_id: str) -> List[Dict[str, Any]]:
path = self._events_path(run_id)
if not path.is_file():
return []
return json.loads(path.read_text())
return json.loads(path.read_text(encoding="utf-8"))

def append_event(self, run_id: str, event: Dict[str, Any]) -> None:
events = self.list_events(run_id)
Expand All @@ -131,7 +131,7 @@ def list_artifacts(self, run_id: str) -> List[Dict[str, Any]]:
path = self._artifacts_path(run_id)
if not path.is_file():
return []
return json.loads(path.read_text())
return json.loads(path.read_text(encoding="utf-8"))

def append_artifacts(self, run_id: str, artifacts: List[Dict[str, Any]]) -> None:
existing: List[Dict[str, Any]] = []
Expand All @@ -158,7 +158,7 @@ def get_memory(self, run_id: str) -> Dict[str, Any]:
path = self._memory_path(run_id)
if not path.is_file():
return {}
return json.loads(path.read_text())
return json.loads(path.read_text(encoding="utf-8"))

def save_memory(self, run_id: str, memory: Dict[str, Any]) -> None:
self._save_json(self._memory_path(run_id), memory)
Expand All @@ -181,4 +181,4 @@ def _validate_step_updates(self, current_step: Dict[str, Any], updates: Dict[str

def _save_json(self, path: Path, payload: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2, default=str))
path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
59 changes: 59 additions & 0 deletions backend/tests/test_pr_b21_state_store_encoding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Regression: FAROS state store must persist non-ASCII run data on any locale.

read_text()/write_text() in state_store omitted encoding="utf-8", so run
records containing Chinese paper titles or Unicode agent outputs raised
UnicodeEncodeError/UnicodeDecodeError on hosts whose default locale is
not UTF-8 (Windows cp1252, minimal C/POSIX containers).
"""

import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from app.faros.runtime.state_store import FarosStateStore


def _make_run(store):
return store.create_run(
blueprint_id="bp_test",
profile_id="pf_test",
execution_mode="sequential",
inputs={"topic": "基于检索增强的科学写作框架研究"},
steps=[],
)


def test_run_roundtrip_preserves_chinese_inputs(tmp_path):
store = FarosStateStore(root=tmp_path)
run = _make_run(store)

loaded = store.get_run(run["id"])

assert loaded is not None
assert loaded["inputs"]["topic"] == "基于检索增强的科学写作框架研究"


def test_list_runs_survives_nonascii_records(tmp_path):
store = FarosStateStore(root=tmp_path)
_make_run(store)

runs = store.list_runs()

assert len(runs) == 1
assert runs[0]["inputs"]["topic"] == "基于检索增强的科学写作框架研究"


def test_memory_and_events_preserve_unicode(tmp_path):
store = FarosStateStore(root=tmp_path)
run = _make_run(store)

store.save_memory(run["id"], {"summary": "实验结果表明方法有效 ✓"})
store.append_event(run["id"], {"type": "log", "message": "步骤完成:数据预处理"})
store.append_artifacts(run["id"], [{"name": "图表", "path": "/tmp/图表.png"}])

assert store.get_memory(run["id"])["summary"] == "实验结果表明方法有效 ✓"
assert store.list_events(run["id"])[0]["message"] == "步骤完成:数据预处理"
assert store.list_artifacts(run["id"])[0]["name"] == "图表"

print("PASS: state store non-ASCII round-trips")