chore: auto release commit (2026-07-03 12:09:55)
All checks were successful
技能自动化发布 / release (push) Successful in 5s

This commit is contained in:
2026-07-03 12:09:55 +08:00
parent 7136689efe
commit 4a0be93b4a
22 changed files with 1613 additions and 575 deletions

View File

@@ -1,14 +1,11 @@
"""任务编排、日志查询模板。
通用业务编排层:负责参数校验、鉴权、调用具体业务实现、写入任务日志。
复制后在 cmd_run 中实现真正的业务逻辑。
"""
"""任务编排:下载 Amazon 结算报表、写入 task_logs。"""
from __future__ import annotations
import asyncio
import json
import os
import re
import uuid
from typing import Any, Dict, Optional
@@ -16,71 +13,186 @@ from jiangchang_skill_core import collect_runtime_diagnostics, config, format_ru
from jiangchang_skill_core.rpa.video_session import RpaVideoSession
from db import task_logs_repository as tlr
from service.amazon_report_adapter import get_adapter
from service.entitlement_service import check_entitlement
from service.task_run_support import (
_print_video_summary,
build_video_info,
merge_video_into_result_summary,
)
from util.constants import PLATFORM_KIT_MIN_VERSION, SKILL_SLUG, SKILL_VERSION
from util.constants import (
DEFAULT_DATE_FROM,
DEFAULT_DATE_TO,
PLATFORM_KIT_MIN_VERSION,
SKILL_SLUG,
SKILL_VERSION,
)
from util.runtime_paths import get_skill_data_dir, get_skill_root
from util.timeutil import unix_to_iso
async def _run_template_demo(target: Optional[str], input_id: Optional[str]) -> tuple[int, dict[str, Any]]:
"""最小 RpaVideoSession 示范:不启动浏览器、不执行业务,仅演示录屏包裹范式。"""
batch_id = uuid.uuid4().hex[:12]
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
def _validate_date(label: str, value: str) -> Optional[str]:
v = (value or "").strip()
if not v:
return f"{label} 不能为空"
if not _DATE_RE.match(v):
return f"{label} 格式须为 YYYY-MM-DD"
return None
def _artifacts_dir(batch_id: str) -> str:
return os.path.join(get_skill_data_dir(), "rpa-artifacts", batch_id)
def _downloads_dir() -> str:
path = os.path.join(get_skill_data_dir(), "downloads")
os.makedirs(path, exist_ok=True)
return path
async def _run_with_video(
*,
date_from: str,
date_to: str,
seller_code: Optional[str],
batch_id: str,
) -> tuple[dict[str, Any], dict[str, Any]]:
"""可选 RpaVideoSession 包裹 adapter 执行。"""
data_dir = get_skill_data_dir()
art_dir = _artifacts_dir(batch_id)
os.makedirs(art_dir, exist_ok=True)
async with RpaVideoSession(
skill_slug=SKILL_SLUG,
skill_data_dir=data_dir,
batch_id=batch_id,
title="开始执行示例任务",
closing_title="示例任务执行完成",
title="开始下载 Amazon 结算报表",
closing_title="结算报表下载完成",
) as video:
video.add_step("准备执行示例任务")
if target:
video.add_step(f"接收目标参数:{target}")
if input_id:
video.add_step(f"接收输入标识:{input_id}")
video.add_step("示例任务执行完成")
video.add_step("准备下载结算报表")
video.add_step(f"日期范围:{date_from}{date_to}")
if seller_code:
video.add_step(f"指定店铺:{seller_code}")
adapter = get_adapter(artifacts_dir=art_dir)
video.add_step(f"使用 adapter{adapter.name}")
result = adapter.download_settlement(
date_from=date_from,
date_to=date_to,
seller_code=seller_code,
downloads_dir=_downloads_dir(),
artifacts_dir=art_dir,
)
if result.ok:
video.add_step(f"下载成功:{result.filename or ''}")
else:
video.add_step(f"下载失败:{result.error_msg or '未知错误'}")
video_summary = video.summary()
video_info = build_video_info(video_summary, {}, {})
return 1, video_info
payload: dict[str, Any] = {
"ok": result.ok,
"job_id": result.job_id,
"file_path": result.file_path,
"filename": result.filename,
"date_from": result.date_from,
"date_to": result.date_to,
"seller_code": result.seller_code or seller_code,
"adapter": result.artifacts.get("adapter") or adapter.name,
"artifacts": result.artifacts,
}
if not result.ok:
payload["error"] = result.error_msg
return payload, video_info
def cmd_run(target: Optional[str] = None, input_id: Optional[str] = None) -> int:
"""通用任务执行入口模板。复制后请实现真实业务逻辑。"""
def cmd_run(
date_from: Optional[str] = None,
date_to: Optional[str] = None,
seller_code: Optional[str] = None,
) -> int:
ok, reason = check_entitlement(SKILL_SLUG)
if not ok:
print(f" {reason}")
print(f"ERROR:ENTITLEMENT {reason}")
return 1
rc, video_info = asyncio.run(_run_template_demo(target, input_id))
_print_video_summary(video_info)
d_from = (date_from or DEFAULT_DATE_FROM).strip()
d_to = (date_to or DEFAULT_DATE_TO).strip()
seller = (seller_code or "").strip() or None
summary_payload = merge_video_into_result_summary(
{
"template_demo": True,
"target": target,
"input_id": input_id,
},
video_info,
)
for label, val in (("date_from", d_from), ("date_to", d_to)):
err = _validate_date(label, val)
if err:
print(f"ERROR:INVALID_ARGS {err}")
return 1
batch_id = uuid.uuid4().hex[:12]
record_video = config.get_bool("OPENCLAW_RECORD_VIDEO", default=True)
if record_video:
payload, video_info = asyncio.run(
_run_with_video(
date_from=d_from,
date_to=d_to,
seller_code=seller,
batch_id=batch_id,
)
)
else:
art_dir = _artifacts_dir(batch_id)
os.makedirs(art_dir, exist_ok=True)
adapter = get_adapter(artifacts_dir=art_dir)
result = adapter.download_settlement(
date_from=d_from,
date_to=d_to,
seller_code=seller,
downloads_dir=_downloads_dir(),
artifacts_dir=art_dir,
)
payload = {
"ok": result.ok,
"job_id": result.job_id,
"file_path": result.file_path,
"filename": result.filename,
"date_from": result.date_from,
"date_to": result.date_to,
"seller_code": result.seller_code or seller,
"adapter": result.artifacts.get("adapter") or adapter.name,
"artifacts": result.artifacts,
}
if not result.ok:
payload["error"] = result.error_msg
video_info = {}
_print_video_summary(video_info if record_video else None)
summary_payload = merge_video_into_result_summary(payload, video_info if record_video else None)
status = "success" if payload.get("ok") else "failed"
tlr.save_task_log(
task_type="demo",
target_id=target,
input_id=input_id,
input_title="模板示例任务",
status="failed",
error_msg="模板仓库未实现真实业务",
task_type="download_settlement",
target_id=seller or "default",
input_id=f"{d_from}_{d_to}",
input_title=f"结算报表 {d_from} ~ {d_to}",
status=status,
error_msg=None if payload.get("ok") else str(payload.get("error") or ""),
result_summary=json.dumps(summary_payload, ensure_ascii=False),
)
print("❌ 这是模板仓库,请复制后在 scripts/service/task_service.py 中实现 cmd_run 的真实业务逻辑。")
return rc
stdout_payload = {
"ok": payload.get("ok"),
"job_id": payload.get("job_id"),
"file_path": payload.get("file_path"),
"filename": payload.get("filename"),
"date_from": payload.get("date_from"),
"date_to": payload.get("date_to"),
}
print(json.dumps(stdout_payload, ensure_ascii=False))
return 0 if payload.get("ok") else 1
def cmd_logs(