Spaces:
Paused
Paused
| """管理员登录态持久化。 | |
| 统一使用项目根目录下的 `state.json` 文件保存: | |
| - session_token | |
| - password | |
| - account_id | |
| - workspace_name | |
| - updated_at | |
| 兼容: | |
| - 旧的纯文本 `session`(仅保存 session token) | |
| """ | |
| import json | |
| import os | |
| import time | |
| from contextlib import contextmanager | |
| from contextvars import ContextVar | |
| from pathlib import Path | |
| from autoteam.textio import read_text, write_text | |
| PROJECT_ROOT = Path(__file__).parent.parent.parent | |
| STATE_FILE = PROJECT_ROOT / "state.json" | |
| LEGACY_SESSION_FILE = PROJECT_ROOT / "session" | |
| STATE_FILE_MODE = 0o666 | |
| _ADMIN_STATE_OVERRIDE: ContextVar[dict | None] = ContextVar("autoteam_admin_state_override", default=None) | |
| def _normalize_state(data): | |
| if not isinstance(data, dict): | |
| return {} | |
| return { | |
| "email": data.get("email", "") or "", | |
| "session_token": data.get("session_token", "") or "", | |
| "password": data.get("password", "") or "", | |
| "account_id": data.get("account_id", "") or "", | |
| "workspace_name": data.get("workspace_name", "") or "", | |
| "updated_at": data.get("updated_at"), | |
| } | |
| def _load_state_from_file(path: Path): | |
| if not path.exists(): | |
| return {} | |
| try: | |
| raw = read_text(path).strip() | |
| except Exception: | |
| return {} | |
| if not raw: | |
| return {} | |
| try: | |
| return _normalize_state(json.loads(raw)) | |
| except Exception: | |
| # 兼容旧版纯文本 session 文件 | |
| return { | |
| "email": "", | |
| "session_token": raw, | |
| "account_id": "", | |
| "workspace_name": "", | |
| "updated_at": path.stat().st_mtime, | |
| } | |
| def _save_state(state): | |
| # 如果是软链,写入目标路径(避免 Docker 场景下误删/替换软链) | |
| target = STATE_FILE.resolve() | |
| write_text(target, json.dumps(_normalize_state(state), indent=2, ensure_ascii=False)) | |
| # Docker bind mount 下文件常由容器用户写入;给宿主机用户保留可访问权限 | |
| os.chmod(target, STATE_FILE_MODE) | |
| def _migrate_legacy_state(): | |
| if STATE_FILE.exists(): | |
| return | |
| state = _load_state_from_file(LEGACY_SESSION_FILE) | |
| if state: | |
| _save_state(state) | |
| try: | |
| LEGACY_SESSION_FILE.unlink() | |
| except Exception: | |
| pass | |
| def load_admin_state(): | |
| override = _ADMIN_STATE_OVERRIDE.get() | |
| if isinstance(override, dict): | |
| return dict(override) | |
| _migrate_legacy_state() | |
| return _load_state_from_file(STATE_FILE) | |
| def save_admin_state(state): | |
| _save_state(state) | |
| def update_admin_state(**kwargs): | |
| override = _ADMIN_STATE_OVERRIDE.get() | |
| if isinstance(override, dict): | |
| state = _normalize_state({**override, **kwargs}) | |
| state["updated_at"] = time.time() | |
| _ADMIN_STATE_OVERRIDE.set(state) | |
| return state | |
| state = load_admin_state() | |
| state.update(kwargs) | |
| state["updated_at"] = time.time() | |
| save_admin_state(state) | |
| return state | |
| def temporary_admin_state(**kwargs): | |
| """Use an in-thread admin state without mutating global ``state.json``.""" | |
| base = load_admin_state() | |
| state = _normalize_state({**base, **kwargs}) | |
| state["updated_at"] = state.get("updated_at") or time.time() | |
| token = _ADMIN_STATE_OVERRIDE.set(state) | |
| try: | |
| yield state | |
| finally: | |
| _ADMIN_STATE_OVERRIDE.reset(token) | |
| def clear_admin_state(): | |
| if STATE_FILE.exists(): | |
| # 写空内容而不是删除(保护 Docker 软链) | |
| target = STATE_FILE.resolve() | |
| write_text(target, "{}") | |
| os.chmod(target, STATE_FILE_MODE) | |
| if LEGACY_SESSION_FILE.exists(): | |
| LEGACY_SESSION_FILE.unlink() | |
| def get_admin_email(): | |
| """Round 12 S7 — route through WorkspacePool active workspace. | |
| Backwards-compat: if the pool is empty / module unavailable, fall back | |
| to the legacy state.json read so single-workspace installs are unaffected. | |
| """ | |
| override = _ADMIN_STATE_OVERRIDE.get() | |
| if isinstance(override, dict) and override.get("email"): | |
| return override["email"] | |
| try: | |
| from autoteam.workspace_pool import default_pool | |
| active = default_pool.get_active() | |
| if active and active.get("admin_email"): | |
| return active["admin_email"] | |
| except Exception: | |
| pass | |
| return load_admin_state().get("email", "") | |
| def get_admin_session_token(): | |
| return load_admin_state().get("session_token", "") | |
| def _is_valid_uuid(value: str) -> bool: | |
| """检查是否为有效的 UUID 格式""" | |
| import re | |
| return bool(re.match(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", value, re.I)) | |
| def get_chatgpt_account_id(): | |
| """Round 12 S7 — route through WorkspacePool active workspace. | |
| Backwards-compat: pool empty → legacy state.json read → CHATGPT_ACCOUNT_ID env. | |
| Pool ID still must pass UUID validation (filters legacy `user-xxx` ids). | |
| """ | |
| override = _ADMIN_STATE_OVERRIDE.get() | |
| if isinstance(override, dict): | |
| override_id = (override.get("account_id") or "").strip() | |
| if override_id and _is_valid_uuid(override_id): | |
| return override_id | |
| try: | |
| from autoteam.workspace_pool import default_pool | |
| active = default_pool.get_active() | |
| if active: | |
| pool_aid = (active.get("account_id") or "").strip() | |
| if pool_aid and _is_valid_uuid(pool_aid): | |
| return pool_aid | |
| except Exception: | |
| pass | |
| state = load_admin_state() | |
| state_id = state.get("account_id", "") | |
| # state.json 里的值必须是 UUID 格式才有效(user-xxx 是 user ID 不是 account ID) | |
| if state_id and _is_valid_uuid(state_id): | |
| return state_id | |
| return os.environ.get("CHATGPT_ACCOUNT_ID", "") | |
| def get_admin_password(): | |
| return load_admin_state().get("password", "") | |
| def get_chatgpt_workspace_name(): | |
| state = load_admin_state() | |
| return state.get("workspace_name", "") | |
| def get_admin_state_summary(): | |
| state = load_admin_state() | |
| return { | |
| "configured": bool(state.get("session_token") and state.get("account_id")), | |
| "email": state.get("email", ""), | |
| "account_id": state.get("account_id", ""), | |
| "workspace_name": state.get("workspace_name", ""), | |
| "session_present": bool(state.get("session_token")), | |
| "password_saved": bool(state.get("password")), | |
| "updated_at": state.get("updated_at"), | |
| } | |