diff --git a/CHANGELOG.md b/CHANGELOG.md index f145f76..c388db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ - 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节 - 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段 +## 1.0.62 + +- Skill Action 新增 `interaction` / `interactionByPlacement`:声明数据管理点击是直通、轻确认还是参数表单(与 `executionProfile` 正交) +- 明确 `confirmation` 只负责风险文案;cron 触发与 agent 忽略 `interaction` +- 补充 ACTIONS.md 示例(打开文件位置 = direct,打开浏览器 = confirm) + ## 1.0.61 - 桌面 RPA 标准反哺:明确 Name≠Value、主窗最大化与禁止误 `SW_RESTORE`、同名叶子须绑父容器 diff --git a/SKILL.md b/SKILL.md index a034c83..69a5916 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 技能开发模板(通用业务版) description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。" -version: 1.0.61 +version: 1.0.62 author: 深圳匠厂科技有限公司 metadata: openclaw: diff --git a/assets/schemas/skill-actions.schema.json b/assets/schemas/skill-actions.schema.json index a6d9933..dd3b188 100644 --- a/assets/schemas/skill-actions.schema.json +++ b/assets/schemas/skill-actions.schema.json @@ -156,6 +156,24 @@ "enum": ["sync", "async"], "description": "sync=调用方等待结构化结果(不建 Job);async=宿主建后台 Job 并进任务中心。必须显式写出;长耗时建议 async,但不限制 placements。" }, + "interaction": { + "type": "string", + "enum": ["auto", "direct", "confirm", "form"], + "description": "数据管理等可点击入口的交互方式(与 executionProfile 正交)。direct=一点即跑;confirm=轻量确认(文案用 confirmation.message);form=参数表单;auto/省略=宿主按 inputSchema+confirmation 推断。cron 触发与 agent 忽略本字段。" + }, + "interactionByPlacement": { + "type": "object", + "additionalProperties": false, + "properties": { + "toolbar": { "type": "string", "enum": ["direct", "confirm", "form"] }, + "row": { "type": "string", "enum": ["direct", "confirm", "form"] }, + "batch": { "type": "string", "enum": ["direct", "confirm", "form"] }, + "cron": { "type": "string", "enum": ["direct", "confirm", "form"] }, + "agent": { "type": "string", "enum": ["direct", "confirm", "form"] }, + "skill-detail": { "type": "string", "enum": ["direct", "confirm", "form"] } + }, + "description": "按 placement 覆盖 interaction(只能写 concrete 模式,不能写 auto)。常用于同一 Action 在 row 直通、toolbar 仍要表单。" + }, "bind": { "$ref": "#/$defs/bind" } }, "if": { diff --git a/references/ACTIONS.md b/references/ACTIONS.md index a5d5379..57c03d6 100644 --- a/references/ACTIONS.md +++ b/references/ACTIONS.md @@ -80,9 +80,10 @@ Action 是**可选能力**: | 字段 / 层 | 只决定什么 | 不决定什么 | |-----------|------------|------------| -| **`placements`** | Action **出现在哪里**(toolbar / row / batch / cron / agent / skill-detail) | 不决定 sync/async | +| **`placements`** | Action **出现在哪里**(toolbar / row / batch / cron / agent / skill-detail) | 不决定 sync/async,不决定点了是否弹窗 | | **`bind.tables`** | 数据管理里出现在**哪些业务表** | 不决定业务实现 | -| **`executionProfile`** | 宿主是**同步等待**还是**异步 Job** | 不限制可展示入口 | +| **`executionProfile`** | 宿主是**同步等待**还是**异步 Job** | 不限制可展示入口,不决定是否弹窗 | +| **`interaction`** | 可点击入口(数据管理 toolbar/row/batch、未来 skill-detail)**点了怎么交互** | 不决定 sync/async;**cron 触发与 agent 忽略** | | **`entrypoint`** | 实际执行哪个 CLI 命令与参数 | 不写业务逻辑 | | **domain service** | **真正完成什么业务** | 不感知 toolbar / cron / agent 来源 | @@ -93,13 +94,15 @@ Action 是**可选能力**: | 层级 | 稳定字段 / 能力 | |------|----------------| | 顶层 | `schemaVersion`、`skill`、`actions` | -| action | `id`、`label`、`description`、`placements`、`entrypoint`、**`executionProfile`(必填)**;可选 `inputSchema`、`confirmation`、**`bind`** | +| action | `id`、`label`、`description`、`placements`、`entrypoint`、**`executionProfile`(必填)**;可选 `inputSchema`、`confirmation`、**`interaction`**、**`interactionByPlacement`**、**`bind`** | | entrypoint | `type = cli`、`command`、`args`(**必填** JSON 数组,项为 `string` / `number` / `boolean`) | | placements | `toolbar`、`row`、`batch`、`skill-detail`、`agent`、`cron` | | **bind** | `{ "tables": ["business_table"] }`;可选 `inputMapping`(行内/批量预填,见下节) | | inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly` | -| confirmation | `{ "message": "..." }` | +| confirmation | `{ "message": "..." }` — **风险文案**(confirm/form 弹窗黄条;agent 元数据);**不是**「要不要弹窗」的开关 | | **executionProfile** | `"sync"` \| `"async"`(**必须显式写出**,禁止依赖缺省) | +| **interaction** | `"auto"` \| `"direct"` \| `"confirm"` \| `"form"`(可选;省略≈`auto`) | +| **interactionByPlacement** | 可选;按 placement 覆盖为 `direct`/`confirm`/`form`(不可写 `auto`) | ## `bind.tables`(表级绑定) @@ -122,6 +125,79 @@ Action 是**可选能力**: - **`inputMapping`(可选)**:把行/勾选映射到 `inputSchema` 字段,例如 `"leadId": "$row.$pk"`、`"ids": "$selection.ids"`。适合 `row` / `batch`。宿主仍会提交这些字段;可用 `inputSchema.properties.*.readOnly: true` 在表单中只读展示。 - `concurrency` / `locks` 仍未稳定,**不要**写入新技能 manifest。 +## `interaction`(点击交互方式) + +与 `executionProfile` **正交**:一个 Action 可以是 `async` + `direct`(一点就进任务中心),也可以是 `sync` + `form`(填参后当场返回)。 + +| 值 | 数据管理体验 | 典型场景 | +|----|--------------|----------| +| `direct` | **一点即跑**,无弹窗 | 打开文件位置、无参只读、行内主键已映射 | +| `confirm` | 轻量确认弹窗(无参数区;文案优先 `confirmation.message`) | 打开浏览器、有副作用但无可编辑入参 | +| `form` | 完整参数表单(现状默认路径) | 导入、查询条件、需用户改写的字段 | +| `auto` / 省略 | 宿主推断:有**可编辑**参数 → `form`;仅有 `confirmation` → `confirm`;否则 → `direct` | 旧技能兼容;新技能建议显式写出 | + +```json +{ + "id": "reveal-profile", + "label": "打开文件位置", + "description": "在资源管理器中打开 Profile 目录。", + "placements": ["row"], + "executionProfile": "sync", + "interaction": "direct", + "bind": { + "tables": ["accounts"], + "inputMapping": { "accountId": "$row.$pk" } + }, + "inputSchema": { + "type": "object", + "required": ["accountId"], + "properties": { + "accountId": { "type": "number", "title": "账号 ID", "readOnly": true } + } + }, + "entrypoint": { + "type": "cli", + "command": "account", + "args": ["reveal-profile", "{{accountId}}"] + } +} +``` + +```json +{ + "id": "open-browser", + "label": "打开浏览器", + "placements": ["row"], + "executionProfile": "async", + "interaction": "confirm", + "confirmation": { "message": "将用该账号 Profile 打开浏览器,仅供查看。" }, + "bind": { + "tables": ["accounts"], + "inputMapping": { "accountId": "$row.$pk" } + }, + "inputSchema": { + "type": "object", + "required": ["accountId"], + "properties": { + "accountId": { "type": "number", "readOnly": true } + } + }, + "entrypoint": { + "type": "cli", + "command": "account", + "args": ["open", "{{accountId}}"] + } +} +``` + +可选 `interactionByPlacement`:同一 Action 在不同入口用不同交互(例如 `row: "direct"`、`toolbar: "form"`)。值只能是 `direct`/`confirm`/`form`,不能写 `auto`。 + +**边界:** + +- **cron 配置**:仍展示可编辑参数;`confirmation.message` 可作为配置页风险提示(由宿主决定)。 +- **cron 触发 / agent**:忽略 `interaction`(无人值守 / 模型填参);`confirmation` 对 agent 仍作 `requiresConfirmation` 元数据。 +- 不要用 `interaction: "direct"` 掩盖需要用户填写的可编辑字段——宿主在 `auto` 下会因可编辑参数回落到 `form`;显式 `direct` + 可编辑字段时参数取 default/预填,作者须自觉。 + 默认模板 `actions.json` 仅含 `health` / `version` / `config-path`,不放在 toolbar/row/batch,故无需 `bind` 示例污染默认 manifest。业务示例: ```json diff --git a/tests/test_actions_manifest.py b/tests/test_actions_manifest.py index 54783c8..081f0cd 100644 --- a/tests/test_actions_manifest.py +++ b/tests/test_actions_manifest.py @@ -216,6 +216,25 @@ class TestActionsManifest(unittest.TestCase): self.assertEqual(bind_def.get("required"), ["tables"]) self.assertIs(bind_def.get("additionalProperties"), False) + def test_schema_defines_interaction_fields(self) -> None: + schema = _load_actions_schema() + action_def = schema["$defs"]["action"] + props = action_def["properties"] + self.assertIn("interaction", props) + self.assertEqual( + set(props["interaction"].get("enum") or []), + {"auto", "direct", "confirm", "form"}, + ) + self.assertIn("interactionByPlacement", props) + by_placement = props["interactionByPlacement"] + self.assertIs(by_placement.get("additionalProperties"), False) + for key in ("toolbar", "row", "batch", "cron", "agent", "skill-detail"): + self.assertIn(key, by_placement.get("properties") or {}) + self.assertEqual( + set((by_placement["properties"][key].get("enum") or [])), + {"direct", "confirm", "form"}, + ) + def test_manifest_validates_against_json_schema(self) -> None: try: import jsonschema @@ -469,6 +488,8 @@ class TestActionsManifest(unittest.TestCase): "additionalProperties", "placements", "executionProfile", + "interaction", + "confirmation", ): self.assertIn(marker, text, msg=f"ACTIONS.md missing {marker!r}") for forbidden in (