feat(template): SRCP v1 standards, job_context/finish gold sample (v1.0.38, platform-kit 1.2.0)
All checks were successful
技能自动化发布 / release (push) Successful in 4s
All checks were successful
技能自动化发布 / release (push) Successful in 4s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -56,10 +56,13 @@ OpenClaw 技能常在客户电脑上异步执行,且大量依赖 RPA、浏览
|
||||
|
||||
| API | 用途 |
|
||||
|-----|------|
|
||||
| `emit` | 推送用户可读进度(不写 stdout) |
|
||||
| `emit` | 推送用户可读进度(不写 stdout);进入步骤前自动步骤闸门(SRCP) |
|
||||
| `step` | 结构化步骤 |
|
||||
| `finish` | 任务结束;**唯一**写单行 result JSON 到 stdout 的场景 |
|
||||
| `rpa_step` | RPA 专用步骤文案 |
|
||||
| `job_context` | 包裹 `cmd_run`;未捕获异常 / `JobStopped` 时自动 `finish` |
|
||||
| `rpa_step` | RPA 专用步骤:闸门 + ▶/✓ emit |
|
||||
| `interruptible_sleep` | RPA 等待(替代裸 `asyncio.sleep`),可暂停/停止 |
|
||||
| `checkpoint` | 长循环内 consult control(一般由 kit 自动调用) |
|
||||
|
||||
**路径**:
|
||||
|
||||
@@ -72,6 +75,33 @@ OpenClaw 技能常在客户电脑上异步执行,且大量依赖 RPA、浏览
|
||||
- `emit` **不写 stdout**;长任务应持续 emit,避免用户以为卡死。
|
||||
- `finish` 才输出单行 result JSON 供宿主解析。
|
||||
- RPA 类技能:`RpaVideoSession.add_step` 会自动同步 activity(无需重复手写每步 emit)。
|
||||
- **禁止**自建 `scripts/util/progress.py` 或向 stdout 打印 `type:progress` JSON;进度只走 Run Journal。
|
||||
- 宿主通过 `{job_id}.control.json` 下发暂停/继续/停止;技能侧由 platform-kit 在步骤闸门自动处理(见 §2.5)。
|
||||
|
||||
### 2.5 步骤控制(SRCP v1,platform-kit >= 1.2.0)
|
||||
|
||||
**权威行为定义在本文**;platform-kit README 为实现细节参考。
|
||||
|
||||
| 通道 | 方向 | 载体 |
|
||||
|------|------|------|
|
||||
| 进度 | 技能 → 宿主 | Run Journal(`emit` / `rpa_step` / `add_step`) |
|
||||
| 结果 | 技能 → 宿主 | `finish()` → Journal 终态 + stdout 一行 result |
|
||||
| 控制 | 宿主 → 技能 | `{JIANGCHANG_DATA_ROOT}/.jiangchang/runs/{job_id}.control.json` |
|
||||
|
||||
`control.json` 的 `command` 只认:`none` | `pause` | `resume` | `stop`。
|
||||
|
||||
技能作者三件事:
|
||||
|
||||
1. `cmd_run` 外包 `with job_context(skill=SKILL_SLUG):`
|
||||
2. 关键步骤 `emit(...)` 或 `@rpa_step` / `video.add_step`
|
||||
3. 每个出口 `finish(status=..., message=..., skill=SKILL_SLUG, **fields)` — **不要**手写多行 JSON 结果
|
||||
|
||||
RPA 等待用 `interruptible_sleep`,**不要**裸 `asyncio.sleep`(否则暂停响应滞后)。
|
||||
|
||||
暂停在**步骤边界**生效(当前 `@rpa_step` / `emit` 执行完后、下一步开始前),与影刀「当前指令完成后暂停」一致。
|
||||
|
||||
Journal 会写入 `type: lifecycle` 事件(`paused` / `resumed` / `stopping`),供宿主任务中心显示状态。
|
||||
|
||||
|
||||
### 2.3 `task_logs`(任务结果审计)
|
||||
|
||||
@@ -202,7 +232,7 @@ batch_progress current=7 total=20 target_id=store-001
|
||||
|
||||
```python
|
||||
from util.logging_config import get_skill_logger
|
||||
from jiangchang_skill_core.activity import emit
|
||||
from jiangchang_skill_core.activity import emit, finish, job_context, rpa_step, interruptible_sleep
|
||||
|
||||
from util.constants import SKILL_SLUG
|
||||
|
||||
@@ -210,37 +240,40 @@ log = get_skill_logger()
|
||||
|
||||
def cmd_run(target=None, input_id=None):
|
||||
task_type = "your_task"
|
||||
log.info(
|
||||
"task_start task_type=%s target_id=%s input_id=%s",
|
||||
task_type, target, input_id,
|
||||
)
|
||||
emit("开始处理任务", skill=SKILL_SLUG, stage="run")
|
||||
try:
|
||||
ok, reason = check_entitlement(SKILL_SLUG)
|
||||
if not ok:
|
||||
log.warning("entitlement_failed task_type=%s reason=%s", task_type, reason)
|
||||
return 1
|
||||
|
||||
t0 = time.monotonic()
|
||||
# ... 外部调用 ...
|
||||
elapsed_ms = int((time.monotonic() - t0) * 1000)
|
||||
with job_context(skill=SKILL_SLUG):
|
||||
log.info(
|
||||
"external_call_done system=%s operation=%s elapsed_ms=%d status=%s",
|
||||
"erp", "submit", elapsed_ms, "ok",
|
||||
)
|
||||
|
||||
tlr.save_task_log(..., status="success", ...)
|
||||
log.info("task_log_saved task_type=%s status=success", task_type)
|
||||
emit("任务完成", skill=SKILL_SLUG, stage="run")
|
||||
return 0
|
||||
except Exception:
|
||||
log.exception(
|
||||
"task_failed task_type=%s target_id=%s input_id=%s",
|
||||
"task_start task_type=%s target_id=%s input_id=%s",
|
||||
task_type, target, input_id,
|
||||
)
|
||||
emit("任务失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="run")
|
||||
tlr.save_task_log(..., status="failed", error_msg="...", ...)
|
||||
return 1
|
||||
emit("开始处理任务", skill=SKILL_SLUG, stage="run")
|
||||
try:
|
||||
ok, reason = check_entitlement(SKILL_SLUG)
|
||||
if not ok:
|
||||
log.warning("entitlement_failed task_type=%s reason=%s", task_type, reason)
|
||||
finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED")
|
||||
return 1
|
||||
|
||||
t0 = time.monotonic()
|
||||
# ... 外部调用 / RPA:@rpa_step 或 video.add_step;等待用 interruptible_sleep ...
|
||||
elapsed_ms = int((time.monotonic() - t0) * 1000)
|
||||
log.info(
|
||||
"external_call_done system=%s operation=%s elapsed_ms=%d status=%s",
|
||||
"erp", "submit", elapsed_ms, "ok",
|
||||
)
|
||||
|
||||
tlr.save_task_log(..., status="success", ...)
|
||||
log.info("task_log_saved task_type=%s status=success", task_type)
|
||||
finish(status="success", message="任务完成", skill=SKILL_SLUG)
|
||||
return 0
|
||||
except Exception:
|
||||
log.exception(
|
||||
"task_failed task_type=%s target_id=%s input_id=%s",
|
||||
task_type, target, input_id,
|
||||
)
|
||||
emit("任务失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="run")
|
||||
tlr.save_task_log(..., status="failed", error_msg="...", ...)
|
||||
finish(status="failed", message="任务执行异常", skill=SKILL_SLUG)
|
||||
return 1
|
||||
```
|
||||
|
||||
模板示范见 [`scripts/service/task_service.py`](../scripts/service/task_service.py) 的 `cmd_run`。
|
||||
|
||||
Reference in New Issue
Block a user