aps-agent/server/aps_home.py

144 lines
4.5 KiB
Python
Raw Normal View History

# ============================================================
# APS 用户主目录(moduleId: server-aps-home, 可重生 ✅)
# 对标 Codex ~/.codex:桌面端默认 ~/.aps/{skills,sessions,data,…}
# Web 部署默认仍用仓库内 server/data(可用 APS_HOME 覆盖)
# ============================================================
from __future__ import annotations
import json
import os
from pathlib import Path
# 与 Codex 类似的子目录约定
_LAYOUT = (
"skills", # 算法 / Agent Skill(每 skill 一目录 + manifest.json)
"sessions", # 会话落盘(桌面端持久化)
"cache",
"tmp",
"logs",
"data", # world / sqlite / knowledge / checkpoints
"packs", # 可选:用户级数据包
)
def mode() -> str:
"""运行形态:desktop | web(默认 web,便于仓库内开发与测试)。"""
m = (os.environ.get("APS_MODE") or "web").strip().lower()
return "desktop" if m in ("desktop", "desk", "electron", "app") else "web"
def aps_home() -> Path:
"""解析 APS 根目录。
优先级:
1. APS_HOME(显式,任意形态)
2. desktop → 用户主目录/.aps(对标 Codex ~/.codex;Windows=%USERPROFILE%\\.aps)
3. web → APS_DATA_DIR 或 仓库 server/data
"""
raw = os.environ.get("APS_HOME")
if raw:
return Path(raw).expanduser().resolve()
if mode() == "desktop":
# 安装到电脑后的唯一默认:用户根目录下的 .aps(不可落到安装目录)
return (Path.home() / ".aps").resolve()
data = os.environ.get("APS_DATA_DIR")
if data:
return Path(data).expanduser().resolve()
# 相对仓库:server/data(与历史路径兼容)
return Path("server/data").resolve()
def ensure_aps_home(home: Path | None = None) -> Path:
"""创建主目录与标准子目录;写入 config.json(若不存在)。"""
root = home or aps_home()
root.mkdir(parents=True, exist_ok=True)
for name in _LAYOUT:
(root / name).mkdir(parents=True, exist_ok=True)
cfg = root / "config.json"
if not cfg.exists():
cfg.write_text(
json.dumps({
"version": 1,
"mode": mode(),
"layout": list(_LAYOUT),
"note": "对标 Codex ~/.codex:skills / sessions / data 等本地落盘",
}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
return root
def skills_dir() -> Path:
return ensure_aps_home() / "skills"
def sessions_dir() -> Path:
return ensure_aps_home() / "sessions"
def default_projects_path() -> str:
"""工作区元数据(项目/会话/消息):落在 ~/.aps/sessions/workspace.json。"""
if os.environ.get("APS_PROJECTS_PATH"):
return os.environ["APS_PROJECTS_PATH"]
return str(sessions_dir() / "workspace.json")
def data_dir() -> Path:
"""世界状态 / DB / 知识库等运行数据目录。"""
root = ensure_aps_home()
# web 默认 APS_HOME 就是 server/data,数据平铺在根下以保持兼容
if mode() == "web" and not os.environ.get("APS_HOME"):
return root
d = root / "data"
d.mkdir(parents=True, exist_ok=True)
return d
def path_under_data(*parts: str) -> Path:
return data_dir().joinpath(*parts)
def default_world_path() -> str:
if os.environ.get("APS_WORLD_PATH"):
return os.environ["APS_WORLD_PATH"]
return str(path_under_data("world.json"))
def default_db_path() -> str:
if os.environ.get("APS_DB_PATH"):
return os.environ["APS_DB_PATH"]
return str(path_under_data("master.db"))
def default_checkpoint_path() -> str:
if os.environ.get("APS_CHECKPOINT_PATH"):
return os.environ["APS_CHECKPOINT_PATH"]
return str(path_under_data("checkpoints.json"))
def default_knowledge_path() -> str:
if os.environ.get("APS_KNOWLEDGE_PATH"):
return os.environ["APS_KNOWLEDGE_PATH"]
return str(path_under_data("knowledge.json"))
def default_embeddings_path() -> str:
if os.environ.get("APS_EMBEDDINGS_PATH"):
return os.environ["APS_EMBEDDINGS_PATH"]
return str(path_under_data("embeddings.json"))
def describe() -> dict:
"""供 /api/system/paths 与桌面关于页展示。"""
root = ensure_aps_home()
return {
"mode": mode(),
"home": str(root),
"skills": str(skills_dir()),
"sessions": str(sessions_dir()),
"workspace": default_projects_path(),
"data": str(data_dir()),
"world": default_world_path(),
"db": default_db_path(),
}