zcbot/core/task.py

65 lines
2.3 KiB
Python

"""任务状态: DESIGN.md §7.1 规约,落 `<task_dir>/state.json`。
Task 是 Session 的上层概念 —— Session 只管对话消息,Task 还管 mode/description/
status/tokens/cost/timestamps,这些是跨轮次共享、和文件系统状态对齐的元数据。
文件路径约定(workspace/ 下):
tasks/<task_id>/state.json ← 此模块负责
tasks/<task_id>/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
from .session import atomic_write_text
@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:
self.updated_at = datetime.now().isoformat(timespec="seconds")
atomic_write_text(
task_dir / "state.json",
json.dumps(asdict(self), ensure_ascii=False, indent=2),
)
@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)