feat: add plugin runtime and cross-device task orchestration

This commit is contained in:
chenmingxuan committed 2026-10-14 20:21:44 +08:00
1 parent 3ee4a5f7a8
commit 6663233ab8
82 files changed
+4147 -124

No files matched your search

@@ -450,6 +450,47 @@ def add_markdown_content(doc, content: str):
)
def plugin_operations(steps: list[dict]) -> list[dict]:
operations = []
for step in steps:
for trace in step.get("generic_tools", []):
payload = trace.get("payload") or {}
if isinstance(payload, str):
try:
payload = json.loads(payload)
except ValueError:
continue
if not isinstance(payload, dict) or not payload.get("plugin_id"):
continue
result = payload.get("result") or {}
if isinstance(result, str):
try:
result = json.loads(result)
except ValueError:
result = {}
if not isinstance(result, dict):
result = {}
content = "\n".join(
str(block.get("text", ""))
for block in result.get("content", [])
if isinstance(block, dict) and block.get("type") == "text"
)
operations.append(
{
"step": step.get("step_number"),
"plugin": payload["plugin_id"],
"version": payload.get("plugin_version", ""),
"tool": result.get("tool") or trace.get("name"),
"resources": ", ".join(payload.get("resource_ids") or []),
"status": result.get("status") or trace.get("status", "unknown"),
"content": (
content or payload.get("error") or result.get("message") or "未返回文字证据"
)[:6000],
}
)
return operations
def word_report(report: dict, path: Path):
doc = word_styles(Document())
doc.add_paragraph(report["name"], style="Title")
@@ -490,6 +531,15 @@ def word_report(report: dict, path: Path):
f"步骤 {step['step_number']} · {time_text(step.get('timestamp'))}\n"
).bold = True
paragraph.add_run(step.get("summary") or "已记录操作")
operations = plugin_operations(report["steps"])
if operations:
doc.add_heading("扩展插件操作", 2)
for operation in operations:
doc.add_paragraph(
f"步骤 {operation['step']} · {operation['plugin']} v{operation['version']} · {operation['tool']} · {operation['status']}"
)
doc.add_paragraph("资源:" + operation["resources"])
doc.add_paragraph(operation["content"])
evidence = report["steps"][:1] + report["steps"][-1:]
seen: set[str] = set()
images: list[tuple[Path, int]] = []
@@ -630,6 +680,27 @@ def excel_report(report: dict, path: Path):
],
(10, 22, 68, 26, 24),
)
operations = plugin_operations(report["steps"])
if operations:
sheet_rows(
wb.create_sheet("插件操作"),
[
("步骤", "插件", "版本", "工具", "资源", "调用状态", "观察与结果"),
*[
(
op["step"],
op["plugin"],
op["version"],
op["tool"],
op["resources"],
op["status"],
op["content"],
)
for op in operations
],
],
(10, 24, 14, 26, 24, 16, 80),
)
wb.save(path)
@@ -663,6 +734,30 @@ def session_screenshots(report: dict) -> list[tuple[Path, str]]:
f"{index:04d}_步骤{int(step.get('step_number') or index):04d}_{phase}{source.suffix.lower()}",
)
)
# Plugin observations are attached to their own tool traces rather than
# replacing a phone's step image in a mixed task.
seen: set[tuple[str, str]] = set()
for tool_index, trace in enumerate(step.get("generic_tools", []), 1):
payload = trace.get("payload") or {}
if isinstance(payload, str):
try:
payload = json.loads(payload)
except ValueError:
continue
if not isinstance(payload, dict) or not payload.get("plugin_id"):
continue
plugin_id = re.sub(r"[^a-zA-Z0-9_-]", "_", str(payload["plugin_id"]))[:48]
names = payload.get("image_names") or [payload.get("post_image_name")]
for image_index, name in enumerate(names, 1):
source = image_path(name)
if source and (plugin_id, str(name)) not in seen:
seen.add((plugin_id, str(name)))
screenshots.append(
(
source,
f"{index:04d}_插件_{plugin_id}_{tool_index:03d}_{image_index:02d}{source.suffix.lower()}",
)
)
return screenshots