"""任务状态: DESIGN.md §7.1 规约,落 `/state.json`。 Task 是 Session 的上层概念 —— Session 只管对话消息,Task 还管 mode/description/ status/tokens/cost/timestamps,这些是跨轮次共享、和文件系统状态对齐的元数据。 文件路径约定(workspace/ 下): tasks//state.json ← 此模块负责 tasks//messages.json ← Session 落 """ from __future__ import annotations import json from dataclasses import asdict, dataclass, fields from datetime import datetime from pathlib import Path from typing import Optional @dataclass class TaskState: task_id: str mode: str = "" # 自由形式: coding / ppt / proposal / general / 自定 description: str = "" # 一句话描述,便于列表识别 status: str = "active" # active / completed / abandoned model: str = "" # caps.model_id model_profile: str = "" # 档案名,如 deepseek_v4.flash reasoning_effort: str = "" cwd: str = "" # 任务的工作基目录 created_at: str = "" # ISO 时间戳 updated_at: str = "" tokens_prompt: int = 0 tokens_completion: int = 0 cost_usd: float = 0.0 # 暂不算,留位 @property def tokens_total(self) -> int: return self.tokens_prompt + self.tokens_completion def save(self, task_dir: Path) -> None: task_dir.mkdir(parents=True, exist_ok=True) self.updated_at = datetime.now().isoformat(timespec="seconds") (task_dir / "state.json").write_text( json.dumps(asdict(self), ensure_ascii=False, indent=2), encoding="utf-8", ) @classmethod def load(cls, task_dir: Path) -> Optional["TaskState"]: p = task_dir / "state.json" if not p.exists(): return None try: data = json.loads(p.read_text(encoding="utf-8")) except Exception: return None if not isinstance(data, dict): return None # 容忍 schema 演化:只取已知字段,缺失字段用 dataclass 默认 known = {f.name for f in fields(cls)} kwargs = {k: v for k, v in data.items() if k in known} if "task_id" not in kwargs: kwargs["task_id"] = task_dir.name return cls(**kwargs)