feat: prepare 2.2.0 with UI and execution fixes

Add dark theme, richer Markdown and readable plugin results.
Improve Android input evidence, response integrity and diagnostics.
Unify lifecycle, guidance receipts and evidence sources across clients.
Keep direct action dispatch without an added AI quota approval layer.
Align version metadata at 2.2.0 and bundle Helper 1.2.2.

Validation: 3063 backend tests before the version bump; 44 after.
Frontend: 317 tests and production build passed.
This commit is contained in:
chenmingxuan committed 2026-10-26 00:44:13 +08:00
1 parent 83bcfd54d5
commit fac4ccb760
146 files changed
+5689 -2033

No files matched your search

+55 -3
View File
@@ -3,13 +3,15 @@
import json
from typing import Literal
from pydantic import BaseModel, Field, model_validator
from pydantic import BaseModel, ConfigDict, Field, model_validator
from artemis.core.tool_declaration import ToolDeclaration
from artemis.utils.notes import save_verified_note
class Stage(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str = Field(min_length=1, max_length=80)
scope: str = Field(
min_length=1,
@@ -30,6 +32,8 @@ class Stage(BaseModel):
class ProgressUpdate(BaseModel):
model_config = ConfigDict(extra="forbid")
stages: list[Stage] = Field(min_length=1, max_length=16)
reason: str = Field(
min_length=1,
@@ -53,13 +57,41 @@ class ProgressUpdate(BaseModel):
return self
class StageUpdate(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str = Field(min_length=1, max_length=80)
status: Literal["pending", "active", "done", "blocked"]
evidence: str | None = Field(default=None, max_length=1200)
class ProgressInput(BaseModel):
model_config = ConfigDict(extra="forbid")
stages: list[Stage] | None = Field(default=None, min_length=1, max_length=16)
stage_updates: list[StageUpdate] | None = Field(default=None, min_length=1, max_length=16)
reason: str = Field(min_length=1, max_length=600)
@model_validator(mode="after")
def one_update_form(self):
if (self.stages is None) == (self.stage_updates is None):
raise ValueError("stages 与 stage_updates 必须且只能提供一种")
if self.stage_updates and len({s.id for s in self.stage_updates}) != len(
self.stage_updates
):
raise ValueError("阶段补丁 id 不能重复")
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. "
"Initialize/replan with all stages, or use stage_updates [{id,status,evidence?}] for existing IDs; "
"patches preserve other stages and their order/scope/resources. Never provide both forms. "
"Keep outstanding scope; done requires recorded evidence. This ledger does not verify business success.",
ProgressUpdate.model_json_schema(),
ProgressInput.model_json_schema(),
)
@@ -69,7 +101,27 @@ class TaskProgress:
self.last_resource: str | None = None
def update(self, args: dict, *, resources: set[str], directory=None) -> str:
update = ProgressUpdate.model_validate(args)
incoming = ProgressInput.model_validate(args)
stages = incoming.stages
if incoming.stage_updates:
if self.value is None:
raise ValueError("首次记录必须提供完整 stages,不能仅提供阶段补丁")
updates = {s.id: s for s in incoming.stage_updates}
unknown = set(updates) - {s.id for s in self.value.stages}
if unknown:
raise ValueError(
f"阶段补丁引用了未知 id:{sorted(unknown)};新增阶段须提供完整 stages"
)
stages = [
{
**stage.model_dump(),
**updates[stage.id].model_dump(exclude_none=True),
}
if stage.id in updates
else stage.model_dump()
for stage in self.value.stages
]
update = ProgressUpdate.model_validate({"stages": stages, "reason": incoming.reason})
if any(set(stage.resources) - resources for stage in update.stages):
raise ValueError(f"阶段引用了不可用资源;可用:{sorted(resources)}")
if self.value: