feat: release 2.2.0 with PC-only tests and execution reliability fixes

Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
This commit is contained in:
chenmingxuan committed 2026-10-27 01:13:41 +08:00
1 parent fac4ccb760
commit be6e8c2abd
79 files changed
+2263 -277

No files matched your search

+24 -7
View File
@@ -716,7 +716,23 @@ async def _process_plan_write(
new top-level completions. ``declared_intent`` marks surgical update_note
edits, which are exempt from the full-rewrite hand-slip check.
"""
rejection_message = check_plan_mutation_rejections(content_before, content_after, state)
before_snapshot = parse_plan(content_before)
after_snapshot = parse_plan(content_after)
before_counts = Counter(before_snapshot.milestone_texts)
duplicate_milestones = [
text
for text, count in Counter(after_snapshot.milestone_texts).items()
if count > max(1, before_counts[text])
]
rejection_message = (
"Your task_plan write was not applied because it introduced duplicate top-level "
f"milestones: {duplicate_milestones!r}. When using update_note, replacement must "
"only replace target; do not copy milestones outside target into replacement. "
"Expand target if replacing the whole section. Use distinct milestone titles "
"for intentionally repeated phases. Read the current plan before correcting it."
if duplicate_milestones
else check_plan_mutation_rejections(content_before, content_after, state)
)
if rejection_message is None and not declared_intent:
rejection_message = check_unintended_rewrite(content_before, content_after)
if rejection_message:
@@ -724,10 +740,13 @@ async def _process_plan_write(
if task_plan_path.exists():
task_plan_path.write_text(content_before, encoding="utf-8")
# Guidance, not a tool failure: status stays "success" so the Operator
# loop treats it as feedback content (matching the legacy envelope
# behavior where the SystemMessage carried no error status).
return ToolMessage(content=rejection_message, tool_call_id="", status="success")
# Duplicate writes are failed edits: defer dependent actions and let the
# Operator correct the target. Preserve legacy feedback for other guards.
return ToolMessage(
content=rejection_message,
tool_call_id="",
status="error" if duplicate_milestones else "success",
)
# Deterministic check-line guard (Operator-source writes only; the Planner
# node writes through unwrapped tools and retains revision authority):
@@ -748,8 +767,6 @@ async def _process_plan_write(
# nor retired (the ledger, the harvest gate and the run outcome all key a
# check item by ``(kind, text)``). Only a line whose ``(kind, text)`` is
# gone from the new plan counts as dropped.
before_snapshot = parse_plan(content_before)
after_snapshot = parse_plan(content_after)
missing = missing_check_items(before_snapshot, after_snapshot)
unprotected = getattr(ctx, "guidance_unprotected_checks", None)
if not isinstance(unprotected, set):