82 lines
2.4 KiB
Python
82 lines
2.4 KiB
Python
"""不可变 run 目录读写。不认识 Muse 业务对象。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
from collections.abc import Iterator, Mapping
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Any, BinaryIO
|
|
|
|
DEFAULT_RUN_DIR_ROOT = Path("/tmp/muse-agent-runs")
|
|
_RUN_ID_PATTERN = re.compile(r"^[A-Za-z0-9_.-]{1,64}$")
|
|
|
|
|
|
class RunDirectoryError(OSError):
|
|
"""run 目录创建或写入失败。"""
|
|
|
|
|
|
def validate_run_id(run_id: str) -> bool:
|
|
return isinstance(run_id, str) and _RUN_ID_PATTERN.fullmatch(run_id) is not None
|
|
|
|
|
|
def prepare_run_dir(run_id: str, run_dir: str | Path | None = None) -> Path:
|
|
"""创建仅当前用户可访问的 run 目录;默认根目录可复用,本次目录不可覆盖。"""
|
|
|
|
run_dir_path = Path(run_dir) if run_dir is not None else DEFAULT_RUN_DIR_ROOT / run_id
|
|
if run_dir is None:
|
|
DEFAULT_RUN_DIR_ROOT.mkdir(parents=True, mode=0o700, exist_ok=True)
|
|
DEFAULT_RUN_DIR_ROOT.chmod(0o700)
|
|
run_dir_path.mkdir(parents=True, mode=0o700, exist_ok=False)
|
|
run_dir_path.chmod(0o700)
|
|
return run_dir_path
|
|
|
|
|
|
def write_private_text(path: Path, text: str) -> None:
|
|
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
handle.write(text)
|
|
path.chmod(0o600)
|
|
|
|
|
|
def write_private_json(path: Path, value: Mapping[str, Any]) -> None:
|
|
write_private_text(path, json.dumps(value, ensure_ascii=False, indent=2))
|
|
|
|
|
|
def seed_run_inputs(
|
|
run_dir: Path,
|
|
*,
|
|
spec_text: str,
|
|
system_prompt: str,
|
|
user_message: str,
|
|
) -> None:
|
|
write_private_text(run_dir / "task-spec.json", spec_text)
|
|
write_private_text(run_dir / "system-prompt.txt", system_prompt)
|
|
write_private_text(run_dir / "user-message.txt", user_message)
|
|
|
|
|
|
@contextmanager
|
|
def open_transcript(run_dir: Path) -> Iterator[BinaryIO]:
|
|
path = run_dir / "transcript.jsonl"
|
|
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
handle = os.fdopen(fd, "wb")
|
|
try:
|
|
yield handle
|
|
path.chmod(0o600)
|
|
finally:
|
|
handle.close()
|
|
|
|
|
|
def read_transcript_text(run_dir: Path) -> str:
|
|
return (run_dir / "transcript.jsonl").read_text(encoding="utf-8")
|
|
|
|
|
|
def remove_local_raw(run_dir: Path) -> None:
|
|
for name in ("transcript.jsonl", "final-message.txt"):
|
|
try:
|
|
(run_dir / name).unlink(missing_ok=True)
|
|
except OSError:
|
|
pass
|