docs: 沉淀 Action 表单 visibleWhen 条件显隐契约

This commit is contained in:
2026-08-16 14:26:56 +08:00
parent 6ebc570632
commit 31f8dde69b
12 changed files with 234 additions and 9 deletions

View File

@@ -9,6 +9,12 @@
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
## 1.0.84
- 沉淀 Action 参数表单字段条件显隐契约 `visibleWhen`(先选模式再出对应参数)
- 文档入口:`ACTIONS.md` / `SKILL_ACTION_RUNTIME.md` / `CHECKLIST` A4-7;与 Policy L3 行态 `visibleWhen` 消歧
- 新增示例 `assets/examples/visible-when-action.example.json`;Schema 已支持该字段
## 1.0.83
- 沉淀跨表 Lookup 契约:Action 可用内联 `lookup` 或绑列下拉,禁止让用户手填内部编号

View File

@@ -1,7 +1,7 @@
---
name: 技能开发模板(通用业务版)
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
version: 1.0.83
version: 1.0.84
author: 深圳匠厂科技有限公司
metadata:
openclaw:

View File

@@ -5,3 +5,4 @@
- `skill-cell-presentation.example.json`:数据管理单元格呈现文档形状参考(见 `references/CELL_PRESENTATION.md`)。
- `render-html.action.example.json`:HTML 报告「重新生成」Action 形状参考(固定 id `render-html`;见 `references/ACTIONS.md`)。
- `lookup-action.example.json`:Action 内联 Lookup 下拉形状参考(跨表引用勿填编号;见 `references/ACTIONS.md`「跨表引用:Lookup」)。
- `visible-when-action.example.json`:Action 参数字段条件显隐 `visibleWhen` 形状参考(互斥模式联动;见 `references/ACTIONS.md`「字段条件显隐」;≠ Policy L3)。

View File

@@ -0,0 +1,72 @@
{
"_comment": "形状参考:互斥模式用 inputSchema.properties.*.visibleWhen 联动显隐,勿只靠 description 灰字。完整契约见 references/ACTIONS.md「字段条件显隐」。勿与 POLICY.md L3 行态 visibleWhen 混淆。",
"schemaVersion": 1,
"skill": "your-skill-slug",
"actions": [
{
"id": "scrape-links-demo",
"label": "采集链接(示例)",
"description": "演示按采集方式切换参数:关键词路径 vs 作者主页路径。",
"placements": ["toolbar"],
"executionProfile": "async",
"interaction": "form",
"bind": {
"tables": ["demo_items"]
},
"confirmation": {
"message": "将按所选方式采集链接(示例确认文案)。是否继续?"
},
"entrypoint": {
"type": "cli",
"command": "run",
"args": [
"--collect-mode",
"{{collectMode}}",
"--keyword",
"{{keyword}}",
"--author-url",
"{{authorUrl}}",
"--max-count",
"{{maxCount}}"
]
},
"inputSchema": {
"type": "object",
"required": ["collectMode"],
"properties": {
"collectMode": {
"type": "string",
"title": "采集方式",
"description": "先选方式,下方只出现对应参数。",
"enum": ["按关键词采集", "按作者主页采集"],
"default": "按关键词采集"
},
"keyword": {
"type": "string",
"title": "关键词",
"description": "例如 rpa。",
"visibleWhen": {
"field": "collectMode",
"equals": "按关键词采集"
}
},
"authorUrl": {
"type": "string",
"title": "作者主页链接",
"description": "例如 https://example.com/author/...",
"display_type": "url",
"visibleWhen": {
"collectMode": "按作者主页采集"
}
},
"maxCount": {
"type": "number",
"title": "数量上限",
"default": 50,
"minimum": 1
}
}
}
}
]
}

View File

@@ -131,6 +131,48 @@
"lookup": {
"$ref": "#/$defs/lookupConfig",
"description": "可选。内联同库表关联 Lookup(与列 options_json lookup 对象同构)。用于当前表没有对应列时;推荐同时设 display_type=lookup。见 ACTIONS.md「跨表引用:Lookup」。"
},
"visibleWhen": {
"description": "可选。表单字段条件显隐。宿主在数据管理/定时任务参数表单中:规则不满足则隐藏该字段、提交时省略、且不校验 required。支持 {field,equals} / {field,in:[...]},或单键简写 {collectMode:\"按关键词采集\"} / {collectMode:[\"a\",\"b\"]}。Agent/CLI 无 UI 时同样按当前 input 求值后剥离隐藏字段。",
"oneOf": [
{
"type": "object",
"required": ["field", "equals"],
"additionalProperties": false,
"properties": {
"field": { "type": "string", "minLength": 1 },
"equals": { "type": ["string", "number", "boolean"] }
}
},
{
"type": "object",
"required": ["field", "in"],
"additionalProperties": false,
"properties": {
"field": { "type": "string", "minLength": 1 },
"in": {
"type": "array",
"minItems": 1,
"items": { "type": ["string", "number", "boolean"] }
}
}
},
{
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"additionalProperties": {
"oneOf": [
{ "type": ["string", "number", "boolean"] },
{
"type": "array",
"minItems": 1,
"items": { "type": ["string", "number", "boolean"] }
}
]
}
}
]
}
}
},

View File

@@ -97,6 +97,7 @@ checklist_version: 1.0
- [ ] A4-4 **P1** 进行中的进度/步骤信息对用户可理解
- [ ] A4-5 **P1** 已对外暴露的动作参数覆盖主路径关键输入(无“命令行能做、动作入口不能做”的关键缺口)
- [ ] A4-6 **P0** 若 Action 需引用同库另一张业务表(话术/平台/账号等):参数为 Lookup 下拉(内联 `lookup` 或绑列),**不是**手填内部编号;可选「自动」可留空提交
- [ ] A4-7 **P1** 若 Action 有互斥采集/操作模式(如「按关键词 / 按主页」):用 `inputSchema` 字段级 **`visibleWhen`** 联动显隐,勿只靠 `description` 灰字说明「仅某某时填写」(见 `references/ACTIONS.md`;勿与 Policy L3 行态 `visibleWhen` 混淆)
---

View File

@@ -7,7 +7,7 @@
| 文档 | 职责 |
|------|------|
| 本文 | sync/async、任务中心、Agent 禁令、单内核编排、队列 pick、同步 vs 导出 |
| [`../references/ACTIONS.md`](../references/ACTIONS.md) | `actions.json` 字段、placements、`bind.tables`、inputSchema、跨表 Lookup |
| [`../references/ACTIONS.md`](../references/ACTIONS.md) | `actions.json` 字段、placements、`bind.tables`、inputSchema、跨表 Lookup、字段条件显隐 `visibleWhen` |
| [`LOGGING.md`](LOGGING.md) | `job_context` / `emit` / `finish` / Run Journal |
| [`DATA_PATHS.md`](DATA_PATHS.md) | 下载/导入等落盘路径 |
@@ -19,7 +19,7 @@
1. **宿主管调度,技能管业务** — 不自行实现任务中心 / Job 轮询 / 后台守护进程。
2. **单业务内核、多入口** — 数据管理、定时任务、Agent、技能详情与直接 CLI 复用同一 domain service;禁止按入口复制业务逻辑。
3. **五职责正交** — `placements` / `bind.tables` / `executionProfile` / `entrypoint` / domain service 各管一事(见 ACTIONS.md)。可选第六轴 **policy**(见 `references/POLICY.md`):未启用 = L0 与今日一致。可选 **`hostEffects` / `steps`**(见 ACTIONS.md):数据管理交互前后的 UI/级联编排;省略 = 不额外改 UI(匠厂宿主 ≥2.8.5)。可选 **`hoverHint`**(见 ACTIONS.md):数据管理工具栏悬停;省略 = 不显示,不回退说明或确认黄条。可选 **`cellPresentation`**(见 `references/CELL_PRESENTATION.md`):单元格语义标色;省略 = P0。
3. **五职责正交** — `placements` / `bind.tables` / `executionProfile` / `entrypoint` / domain service 各管一事(见 ACTIONS.md)。可选第六轴 **policy**(见 `references/POLICY.md`):未启用 = L0 与今日一致。可选 **`hostEffects` / `steps`**(见 ACTIONS.md):数据管理交互前后的 UI/级联编排;省略 = 不额外改 UI(匠厂宿主 ≥2.8.5)。可选 **`hoverHint`**(见 ACTIONS.md):数据管理工具栏悬停;省略 = 不显示,不回退说明或确认黄条。可选 **`visibleWhen`**(见 ACTIONS.md):Action 参数表单字段条件显隐;省略 = 字段始终平铺;**勿与** Policy L3 行态 `visibleWhen` 混淆。可选 **`cellPresentation`**(见 `references/CELL_PRESENTATION.md`):单元格语义标色;省略 = P0。
4. **长耗时建议 async** — 开浏览器、RPA、HTTP 大文件下载等建议 `executionProfile: "async"`,进**任务中心**;这是对**方法**的选择,**不**限制可以出现在哪些 placement / 入口。
5. **Agent:短 CLI / 长 Action** — 短查询可用共享 Python bash CLI;长任务 / RPA **禁止** `exec` + `process poll`,必须 `run_skill_action`。
6. **进度走 Journal** — `emit` / `video.add_step` → Run Journal;UI / Skill Run Card / 任务中心展示;**不靠 stdout 流**。

View File

@@ -86,6 +86,7 @@ Action 是**可选能力**:
| **`executionProfile`** | 宿主是**同步等待**还是**异步 Job** | 不限制可展示入口,不决定是否弹窗 |
| **`interaction`** | 可点击入口(数据管理 toolbar/row/batch、默认 CRUD 接管、未来 skill-detail)**点了怎么交互** | 不决定 sync/async;**cron 触发与 agent 忽略** |
| **`hoverHint`** | **可选**:数据管理**工具栏悬停**文案 | 不决定短按钮名;不决定点下去弹不弹窗;**不回退** `description` / `confirmation.message`;省略 = 不显示悬停。当前宿主实现为顶栏技能按钮 |
| **`inputSchema.visibleWhen`** | **可选**:Action **参数表单**字段条件显隐(先选模式再出对应参数) | **不是** Data Policy L3 行态 `visibleWhen`;不决定 placements;旧宿主忽略则字段平铺全显 |
| **`hostEffects` / `steps`** | **可选**:数据管理交互跑完前后,宿主要不要刷新表 / 清选区 / 级联删等 | 不决定业务写什么;**省略 = 宿主不额外改 UI**(≥2.8.5 起不再硬编码 mutation 后刷新);cron/agent 忽略 |
| **`cellPresentation`** | **可选**:某表某行某列的语义标色/强调(tone + 说明) | 不替代业务列;不下发 CSS;用途不限(超时、预警、驳回等);见 [`CELL_PRESENTATION.md`](CELL_PRESENTATION.md) |
| **`policy` / Snapshot** | **可选**:当前身份在数据管理下允许哪些 Action / 宿主内置能力 / 行列范围 | 不替代 placements;不替代 domain 写路径鉴权;多数技能不配 = L0 全开 |
@@ -106,9 +107,10 @@ Action 是**可选能力**:
| **bind** | `{ "tables": ["business_table"] }`;可选 `inputMapping`(行内/批量预填,见下节) |
| **policy** | 可选;`enabled` + `entrypoint`(推荐 `policy` CLI);见 POLICY.md。未启用 = L0 |
| **cellPresentation** | 可选;`{ "enabled": true }` 时宿主应加载单元格呈现;见 [`CELL_PRESENTATION.md`](CELL_PRESENTATION.md)。未启用 = P0 |
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly`;可选 `display_type` / `format`(`textarea` / `date` / `datetime_unix_seconds` / `url` / `email`) |
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly`;可选 `display_type` / `format`(`textarea` / `date` / `datetime_unix_seconds` / `url` / `email` / `lookup`);可选 **`visibleWhen`**(字段条件显隐,见专节) |
| confirmation | `{ "message": "..." }` — **风险文案**(confirm/form 弹窗黄条;agent 元数据);**不是**「要不要弹窗」的开关 |
| **hoverHint** | 可选字符串。数据管理**工具栏悬停**文案;与 `description` / `confirmation.message` **独立**。省略则不显示悬停,不回退。短名已够懂时不要写。需宿主已支持该字段;当前实现为顶栏技能按钮,不含宿主内置删除/刷新/新增/导入/导出 |
| **visibleWhen** | 可选。写在 **property** 上(非 action 顶层):依赖同表单其它字段值决定是否展示;隐藏字段不提交、不校验 `required`。**勿与** [`POLICY.md`](POLICY.md) L3 行态 `visibleWhen` 混淆 |
| **executionProfile** | `"sync"` \| `"async"`(**必须显式写出**,禁止依赖缺省) |
| **interaction** | `"auto"` \| `"direct"` \| `"confirm"` \| `"form"`(可选;省略≈`auto`) |
| **interactionByPlacement** | 可选;按 placement 覆盖为 `direct`/`confirm`/`form`(不可写 `auto`) |
@@ -656,8 +658,9 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
| 说明灰字 | `description` | `_jiangchang_columns.description` |
| 默认值 | `default` | SQLite 列 `DEFAULT` |
| 只读 | `readOnly: true` | 列 `editable: 0` / 宿主只读规则 |
| **条件显隐** | **`visibleWhen`**(见下节) | **本版 CRUD 无对称**;仅 Action / 定时任务参数表单 |
**本版不做(勿宣传):** 文件上传、多选/标签数组、复杂 `oneOf` / nested object 专用表单。
**本版不做(勿宣传):** 文件上传、多选/标签数组、复杂 `oneOf` / nested object 专用表单;`visibleWhen` 多条件 AND/OR 树、跨 Action 依赖、CRUD 新增/编辑对称显隐。
日期时间约定:
@@ -674,6 +677,46 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
- 属性级 `description`(表单标签下方灰字说明)
- 简单模板替换(`{{fieldName}}`)
- 可选空数字字段提交时由宿主省略,避免 `""` 触发 number 校验失败
- **字段条件显隐 `visibleWhen`**(可选):依赖同表单其它字段值决定是否展示;隐藏字段不进提交、不校验 `required`
### 字段条件显隐(`visibleWhen`)
用于「先选模式,再出对应参数」类表单。写在 `inputSchema.properties.<field>` 上(**不是** action 顶层字段)。
> **消歧义:** 本文的 `visibleWhen` = **Action 参数表单控件显隐**。
> [`POLICY.md`](POLICY.md) L3 预留的 `visibleWhen` = **行态/权限策略**(未冻结),**不是**同一能力。勿混用、勿互相替代。
```json
"keyword": {
"type": "string",
"title": "关键词",
"visibleWhen": { "field": "collectMode", "equals": "按关键词采集" }
},
"authorUrl": {
"type": "string",
"title": "作者主页链接",
"visibleWhen": { "collectMode": "按作者主页采集" }
}
```
支持形态:
| 写法 | 含义 |
|------|------|
| `{ "field": "x", "equals": "a" }` | `x === a` 时显示 |
| `{ "field": "x", "in": ["a", "b"] }` | `x` 属于集合时显示 |
| `{ "x": "a" }` / `{ "x": ["a","b"] }` | 单键简写,同上 |
宿主行为(需已支持该字段的匠厂宿主;更旧版本会忽略未知字段 → 参数平铺全显):
- 数据管理 / 定时任务参数表单按当前值求值并隐藏
- 若依赖字段自身因 `visibleWhen` 隐藏,则下游字段一并隐藏(避免切模式后残留)
- 提交与服务端 `validateActionInput` 会剥离隐藏字段;隐藏字段不校验 `required`
- 无 `visibleWhen` 的字段保持始终可见(兼容旧技能)
形状参考:[`assets/examples/visible-when-action.example.json`](../assets/examples/visible-when-action.example.json)。
互斥模式**优先**用 `visibleWhen`,不要只靠 `description` 灰字写「仅某某时填写」。
**与数据管理「新增 / 编辑」表单的分工(勿混用配置入口):**

View File

@@ -2,7 +2,8 @@
> **状态:正式契约 v1.0(`schemaVersion` 1)。**
> **目标:** 冻结「启用方式 + Snapshot 最小字段 + L2 scope 白名单」,使多数技能(L0)零配置,需要权限的技能(L1/L2)有稳定扩展面。
> **非目标:** 宿主实现业务用户/角色库;规定飞书/HTTP/本地表等权限数据源;本期不强制 L3(`visibleWhen` 等)。
> **非目标:** 宿主实现业务用户/角色库;规定飞书/HTTP/本地表等权限数据源;本期不强制 L3(行态 `visibleWhen` 等)。
> **注意:** L3 预留的行态 `visibleWhen` **不是** Action 参数表单的 `inputSchema.properties.*.visibleWhen`(表单控件显隐,见 [`ACTIONS.md`](ACTIONS.md)「字段条件显隐」)。二者同名不同义,勿混用。
> **配套 Schema:** [`../assets/schemas/skill-policy-snapshot.schema.json`](../assets/schemas/skill-policy-snapshot.schema.json);启用块见 [`../assets/schemas/skill-actions.schema.json`](../assets/schemas/skill-actions.schema.json)。
相关正交轴见 [`ACTIONS.md`](ACTIONS.md)。本契约增加第六轴:**policy(当前身份允许什么)**。
@@ -16,7 +17,7 @@
| **L0** | 无 `policy`,或 `policy.enabled !== true` | **与今日完全一致**:不拉 Snapshot,不按身份过滤按钮/行/列 |
| **L1** | `policy.enabled: true`,Snapshot 至少给出 Action 授权 | 数据管理展示 skill Action 前与 allowlist 求交;可选限制宿主内置能力 |
| **L2** | 在 L1 基础上 Snapshot 带 `data.tables[table]` | 列表查询应用声明式 `rows.scope`;列按 `columns` 投影 |
| **L3** | (预留,本契约不冻结行为) | 如行态 `visibleWhen`、菜单级藏表、`rows.mode=delegate` 等 |
| **L3** | (预留,本契约不冻结行为) | 如**行态** `visibleWhen`、菜单级藏表、`rows.mode=delegate` 等(≠ Action 表单 `inputSchema.visibleWhen`) |
**80% 技能停留 L0。** 启用 policy 的技能必须实现约定 CLI,并在写路径自行再鉴权(见 §7)。
@@ -366,7 +367,7 @@ IF mode == open: 候选不变
|----|------|
| `actions.mode: denylist` | 另开 `schemaVersion` |
| `valueFrom: actor.roles` | **明确不做** |
| `visibleWhen` / 嵌套 scope / `like` | L3 或永不做 |
| `visibleWhen` / 嵌套 scope / `like` | L3 或永不做(此处指**行态**策略字段;Action 表单 `inputSchema.visibleWhen` 见 ACTIONS.md,不属本表) |
| `rows.mode: delegate` 宿主实现 | 字段名已占坑 |
| 平台全局 Role UI / cron·agent 套用 Snapshot | 另议 |

View File

@@ -1,6 +1,6 @@
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
SKILL_SLUG = "your-skill-slug"
SKILL_VERSION = "1.0.83"
SKILL_VERSION = "1.0.84"
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
PLATFORM_KIT_MIN_VERSION = "1.2.11"

View File

@@ -257,6 +257,38 @@ class TestActionsManifest(unittest.TestCase):
self.assertNotIn("hoverHint", action_def.get("required") or [])
self.assertEqual(props["hoverHint"].get("type"), "string")
def test_schema_defines_visible_when_on_input_property(self) -> None:
schema = _load_actions_schema()
prop_def = schema["$defs"]["inputProperty"]
props = prop_def["properties"]
self.assertIn("visibleWhen", props)
self.assertNotIn("visibleWhen", prop_def.get("required") or [])
# Must live on input property, not action top-level (≠ Policy L3).
action_props = schema["$defs"]["action"]["properties"]
self.assertNotIn("visibleWhen", action_props)
def test_default_manifest_omits_visible_when(self) -> None:
for action in _load_actions_manifest()["actions"]:
properties = ((action.get("inputSchema") or {}).get("properties")) or {}
for name, prop in properties.items():
self.assertNotIn(
"visibleWhen",
prop if isinstance(prop, dict) else {},
msg=f"{action['id']}.{name} default template should omit visibleWhen",
)
def test_visible_when_example_exists_and_uses_field_rules(self) -> None:
path = os.path.join(
get_skill_root(), "assets", "examples", "visible-when-action.example.json"
)
self.assertTrue(os.path.isfile(path), msg="visible-when example must exist")
with open(path, encoding="utf-8") as f:
example = json.load(f)
props = example["actions"][0]["inputSchema"]["properties"]
self.assertIn("visibleWhen", props["keyword"])
self.assertIn("visibleWhen", props["authorUrl"])
self.assertNotIn("visibleWhen", props["collectMode"])
def test_default_manifest_omits_hover_hint(self) -> None:
for action in _load_actions_manifest()["actions"]:
self.assertNotIn(
@@ -554,6 +586,8 @@ class TestActionsManifest(unittest.TestCase):
"hostEffects",
"steps",
"hoverHint",
"visibleWhen",
"字段条件显隐",
"reloadTable",
"afterSuccess",
"afterFailure",

View File

@@ -363,9 +363,34 @@ class TestDocsStandards(unittest.TestCase):
"_jiangchang_columns.description",
"datetime_unix_seconds",
"SCHEMA.md",
"字段条件显隐",
"visibleWhen",
):
self.assertIn(marker, text, msg=f"ACTIONS.md missing {marker!r}")
def test_actions_md_disambiguates_visible_when_from_policy(self) -> None:
actions = self._read("references/ACTIONS.md")
policy = self._read("references/POLICY.md")
self.assertIn("字段条件显隐", actions)
self.assertIn("inputSchema.properties", actions)
self.assertTrue(
"POLICY.md" in actions and "L3" in actions,
msg="ACTIONS.md must point readers away from Policy L3 visibleWhen",
)
self.assertTrue(
"inputSchema" in policy and "ACTIONS.md" in policy,
msg="POLICY.md must disambiguate L3 visibleWhen from Action form visibleWhen",
)
def test_visible_when_example_is_listed(self) -> None:
text = self._read("assets/examples/README.md")
self.assertIn("visible-when-action.example.json", text)
checklist = self._read("development/CHECKLIST.md")
self.assertIn("A4-7", checklist)
self.assertIn("visibleWhen", checklist)
runtime = self._read("development/SKILL_ACTION_RUNTIME.md")
self.assertIn("visibleWhen", runtime)
def test_guarded_docs_do_not_reference_amazon_implementation(self) -> None:
forbidden = (
"download-settlement-report-amazon",