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

+107
View File
@@ -1173,6 +1173,112 @@ def test_android_artifact_export_does_not_follow_other_session_links(isolated):
assert reports.session_artifacts({"id": "session"}) == []
def test_report_source_inventory_preserves_human_verdict_and_raw_evidence(
isolated, tmp_path, monkeypatch
):
from apps.admin_console.services import evidence_service, task_inspection
job = isolated.jobs(service.create_batch(payload())["id"])[0]
sid = job["session_id"]
action = {
"action": "plug_read",
"plugin_id": "data-reader",
"call_id": "recorded-call",
"read_only": True,
"source": {"kind": "api", "target_id": "business-service", "label": "安卓业务后台"},
}
with sqlite3.connect(isolated.path) as conn:
conn.execute(
"INSERT INTO sessions(session_id,initial_goal,start_time,end_time,status) VALUES (?,?,?,?,?)",
(sid, "检查业务状态", 1700000010, 1700000120, "completed"),
)
conn.execute(
"INSERT INTO steps(step_id,session_id,step_number,action_taken) VALUES (?,?,?,?)",
("actual-step", sid, 7, json.dumps(action)),
)
isolated.save_case_review(sid, "failed", "用户指出业务状态仍未落库")
monkeypatch.setattr(evidence_service, "TRACES_PATH", reports.TRACES_PATH)
report = reports.session_report(sid)
source = report["evidence_sources"]["sources"][0]
assert source["category"] == "auxiliary"
assert source["references"][0]["step_id"] == "actual-step"
assert report["test_result"]["status"] == "failed"
assert report["test_result"]["source"] == "human"
assert report["steps"][0]["action_taken"] == action
index = evidence_service.export_index(sid, report, [])
assert index["evidence_sources"] == report["evidence_sources"]
monkeypatch.setattr(task_inspection, "store", isolated)
monkeypatch.setattr(task_inspection, "session_repo", reports.SessionRepository())
assert (
task_inspection.inspect_task(sid, "report")["evidence_sources"]
== report["evidence_sources"]
)
from docx import Document
from openpyxl import load_workbook
word = tmp_path / "sources.docx"
sheet = tmp_path / "sources.xlsx"
reports.word_report(report, word)
reports.excel_report(report, sheet)
document = Document(word)
word_text = " ".join(
[p.text for p in document.paragraphs]
+ [cell.text for table in document.tables for row in table.rows for cell in row.cells]
)
workbook = load_workbook(sheet)
try:
excel_text = " ".join(
str(cell.value or "") for ws in workbook for row in ws for cell in row
)
finally:
workbook.close()
for text in (word_text, excel_text):
assert "接口数据" in text
assert "安卓业务后台" in text
assert "actual-step" in text
assert "用户指出业务状态仍未落库" in text
def test_reports_share_lifecycle_and_distinguish_guidance_receipt_from_consumption(
isolated, tmp_path, monkeypatch
):
from apps.admin_console.services import task_lifecycle
from artemis.runtime import task_control
job = isolated.jobs(service.create_batch(payload())["id"])[0]
job.update(
status="completed",
created_at=1700000010,
worker_started_at=1700000020,
ended_at=1700000120,
finalized_at=1700000125,
)
isolated.save_job(job)
monkeypatch.setattr(task_control, "TRACES_PATH", reports.TRACES_PATH)
task_control.add_instruction(job["session_id"], "保留证据", instruction_id="unused-guidance")
report = reports.session_report(job["session_id"])
assert report["lifecycle"] == task_lifecycle.task_lifecycle(
job["session_id"], db_path=isolated.path
)
assert report["lifecycle"]["durations"]["total"] == 115
assert report["lifecycle"]["durations"]["execution"] is None
assert report["instructions"][0]["status"] == "unapplied"
word = tmp_path / "lifecycle.docx"
excel = tmp_path / "lifecycle.xlsx"
reports.word_report(report, word)
reports.excel_report(report, excel)
document = Document(word)
word_text = "\n".join(p.text for p in document.paragraphs) + "\n".join(
cell.text for table in document.tables for row in table.rows for cell in row.cells
)
book = load_workbook(excel)
excel_text = "\n".join(str(cell.value) for sheet in book for row in sheet for cell in row)
for content in (word_text, excel_text):
assert "后台排队:10.0 秒" in content
assert "执行与整理:未记录" in content
assert "unused-guidance · 任务结束,未记录消费 · 接收" in content
def test_video_export_accepts_finalized_worker_trace_and_rejects_external_path(
isolated, tmp_path, monkeypatch
):
@@ -1188,6 +1294,7 @@ def test_video_export_accepts_finalized_worker_trace_and_rejects_external_path(
second.write_bytes(b"second segment")
repository = Mock()
repository.db_path = isolated.path
repository.get_video_recording_for_session.return_value = {"local_video_path": str(first)}
repository.get_session_by_id.return_value = {"video_filepath": str(first)}
monkeypatch.setattr(reports, "SessionRepository", lambda: repository)