Install and update artemis-use with task diagnostics and plugin guidance. Preserve cross-device file receipts, review canonical reports, guard stage transitions and reduce small history summaries. Improve exploration lifecycle, request recovery and browser live connections.
110 lines
4.8 KiB
Python
110 lines
4.8 KiB
Python
"""Small execution-stage ledger; no extra planner/model or scheduler."""
|
|
|
|
import json
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from artemis.core.tool_declaration import ToolDeclaration
|
|
from artemis.utils.notes import save_verified_note
|
|
|
|
|
|
class Stage(BaseModel):
|
|
id: str = Field(min_length=1, max_length=80)
|
|
scope: str = Field(
|
|
min_length=1,
|
|
max_length=600,
|
|
description="Requirements covered by this stage, including its exit condition.",
|
|
)
|
|
resources: list[str] = Field(
|
|
min_length=1,
|
|
max_length=8,
|
|
description="android or actual plugin IDs; several only when the stage requires interleaving.",
|
|
)
|
|
status: Literal["pending", "active", "done", "blocked"]
|
|
evidence: str = Field(
|
|
default="",
|
|
max_length=1200,
|
|
description="Observed facts and step/file references; required for done/blocked. done means scope checked, not test passed.",
|
|
)
|
|
|
|
|
|
class ProgressUpdate(BaseModel):
|
|
stages: list[Stage] = Field(min_length=1, max_length=16)
|
|
reason: str = Field(
|
|
min_length=1,
|
|
max_length=600,
|
|
description="Why the stage changed; when replanning, explain changed requirements or blocking evidence.",
|
|
)
|
|
|
|
@model_validator(mode="after")
|
|
def ordered(self):
|
|
if len({s.id for s in self.stages}) != len(self.stages):
|
|
raise ValueError("阶段 id 不能重复")
|
|
if sum(s.status == "active" for s in self.stages) > 1:
|
|
raise ValueError("只能有一个当前阶段")
|
|
outstanding = False
|
|
for stage in self.stages:
|
|
if stage.status in {"done", "blocked"} and not stage.evidence.strip():
|
|
raise ValueError("完成或阻塞阶段必须记录事实与步骤/文件依据")
|
|
if stage.status in {"active", "done"} and outstanding:
|
|
raise ValueError("前面的阶段尚未检查完,不能提前推进;需先完成或注明阻塞")
|
|
outstanding = outstanding or stage.status in {"active", "pending"}
|
|
return self
|
|
|
|
|
|
PROGRESS_TOOL = ToolDeclaration(
|
|
"update_task_progress",
|
|
"Record ordered stages for a cross-resource task before acting. Follow the user's sequence; "
|
|
"read-only preflight is allowed on other resources. Update only at stage boundaries or genuine "
|
|
"replanning, not every turn. Can precede the next action in the same tool-call list. "
|
|
"Keep outstanding scope; done requires recorded evidence. This ledger does not verify business success.",
|
|
ProgressUpdate.model_json_schema(),
|
|
)
|
|
|
|
|
|
class TaskProgress:
|
|
def __init__(self):
|
|
self.value: ProgressUpdate | None = None
|
|
self.last_resource: str | None = None
|
|
|
|
def update(self, args: dict, *, resources: set[str], directory=None) -> str:
|
|
update = ProgressUpdate.model_validate(args)
|
|
if any(set(stage.resources) - resources for stage in update.stages):
|
|
raise ValueError(f"阶段引用了不可用资源;可用:{sorted(resources)}")
|
|
if self.value:
|
|
missing = {s.id for s in self.value.stages} - {s.id for s in update.stages}
|
|
if missing:
|
|
raise ValueError(
|
|
f"不能删除既有阶段 {sorted(missing)};保留范围并记录完成依据或阻塞原因"
|
|
)
|
|
if directory:
|
|
save_verified_note(directory, "task_progress", update.model_dump_json(indent=2))
|
|
self.value = update
|
|
return json.dumps(
|
|
{
|
|
"ok": True,
|
|
"active": next((s.id for s in update.stages if s.status == "active"), None),
|
|
"saved": bool(directory),
|
|
},
|
|
ensure_ascii=False,
|
|
)
|
|
|
|
def guard(self, resource: str | None, *, completed: bool = False) -> str | None:
|
|
if completed and self.value:
|
|
missing = [s.scope for s in self.value.stages if s.status in {"active", "pending"}]
|
|
if missing:
|
|
return (
|
|
"仍有未检查完的阶段:" + "; ".join(missing) + "。先核对并更新进度,再结束任务。"
|
|
)
|
|
if resource is None:
|
|
return None
|
|
if self.value:
|
|
active = next((s for s in self.value.stages if s.status == "active"), None)
|
|
if active is None or resource not in active.resources:
|
|
scope = active.scope if active else "尚未选择"
|
|
return f"当前阶段:{scope};暂未执行 {resource} 操作。先完成当前阶段或根据真实阻塞调整 update_task_progress;其他端只读核验仍可进行。"
|
|
elif self.last_resource and resource != self.last_resource:
|
|
return "跨端操作前需用 update_task_progress 记录已完成与待做范围,遵循用户指定顺序;只读预检不能直接扩展为另一端整段操作。"
|
|
return None
|