docs: add lookup.multiple multi-select contract (v1.0.93)
All checks were successful
技能自动化发布 / release (push) Successful in 5s

This commit is contained in:
2026-08-21 16:19:13 +08:00
parent fbf326d525
commit 225d5c0777
10 changed files with 130 additions and 21 deletions

View File

@@ -9,6 +9,18 @@
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
## 1.0.93
- 沉淀 Lookup **多选**契约:同一 `lookup` / `options_json` 对象上加 `multiple: true`(平行扩展单选,不另造 display_type)
- 多选存 `TEXT` 逗号分隔 id;列表展示映射为多个 label;控件由宿主按选项数量选择(短列表勾选 / 长列表 Chip+下拉)
- 更新 SCHEMA / ACTIONS / skill-actions.schema / lookup 示例与 options_json 校验
## 1.0.93
- 沉淀 Lookup **多选**契约:同一 `lookup` / `options_json` 对象加 `multiple: true`,不另造类型
- 多选存 `TEXT` 逗号分隔 id;短列表勾选 / 长列表 Chip+下拉为宿主渲染策略
- ACTIONS / SCHEMA / schema / 示例与文档守护对齐
## 1.0.92
- 沉淀数据管理 `replaces: import`:导入 UI 统一宿主面板,确认后由技能写库

View File

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

View File

@@ -4,6 +4,6 @@
- `skill-policy-snapshot.*.example.json`:Data Policy Snapshot 形状参考(见 `references/POLICY.md`)。
- `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」)。
- `lookup-action.example.json`:Action 内联 Lookup 形状参考(单选 + `multiple: true` 多选并列;跨表引用勿填编号;见 `references/ACTIONS.md`「跨表引用:Lookup」)。
- `visible-when-action.example.json`:Action 参数字段条件显隐 `visibleWhen` 形状参考(互斥模式联动;见 `references/ACTIONS.md`「字段条件显隐」;≠ Policy L3)。
- `replaces-create.action.example.json` / `replaces-update.action.example.json` / `replaces-delete.action.example.json` / `replaces-import.action.example.json`:数据管理默认按钮接管(`replaces`)形状参考;**import 时 UI 仍用宿主「导入数据」面板**,确认后调技能写库(见 `references/ACTIONS.md`「`replaces`」)。

View File

@@ -1,12 +1,12 @@
{
"_comment": "形状参考:Action 参数引用同库另一张表时用 Lookup 下拉,勿让用户填内部编号。完整契约见 references/ACTIONS.md「跨表引用:Lookup」与 references/SCHEMA.md。",
"_comment": "形状参考:Action 参数引用同库另一张表时用 Lookup。单选与多选同一 lookup 对象,多选只加 multiple:true。完整契约见 references/ACTIONS.md「跨表引用:Lookup」与 references/SCHEMA.md。",
"schemaVersion": 1,
"skill": "your-skill-slug",
"actions": [
{
"id": "send-with-template",
"label": "按模板发送(示例)",
"description": "演示内联 lookup:从 message_templates 选模板;-- 表示自动挑选。",
"description": "演示内联 lookup 单选:从 message_templates 选模板;-- 表示自动挑选。",
"placements": ["toolbar"],
"executionProfile": "async",
"interaction": "form",
@@ -43,6 +43,41 @@
}
}
}
},
{
"id": "probe-with-engines",
"label": "按引擎抽检(多选示例)",
"description": "演示 lookup.multiple:从 engines 多选,提交为逗号分隔 id。",
"placements": ["toolbar"],
"executionProfile": "async",
"interaction": "form",
"bind": {
"tables": ["queries"]
},
"entrypoint": {
"type": "cli",
"command": "query",
"args": ["batch-probe", "--engine-ids", "{{engineIds}}"]
},
"inputSchema": {
"type": "object",
"properties": {
"engineIds": {
"type": "string",
"title": "将使用的引擎",
"description": "可多选;提交值为逗号分隔编号,如 1,2。",
"display_type": "lookup",
"lookup": {
"ref_table": "engines",
"value_column": "id",
"label_column": "display_name",
"filter_column": "enabled",
"filter_value": 1,
"multiple": true
}
}
}
}
}
]
}

View File

@@ -214,9 +214,13 @@
"filter_value": {
"type": ["string", "number", "boolean", "null"],
"description": "可选。与 filter_column 配对的等值"
},
"multiple": {
"type": "boolean",
"description": "可选。true=多选 Lookup(存/提交 TEXT 逗号分隔 id);省略或 false=单选(默认)。与 CRUD options_json lookup 同构。"
}
},
"description": "同库 FK 式 Lookup。展示:value · label;有 extra 时 value · label(extra)。宿主亦接受 camelCase 键名。"
"description": "同库 FK 式 Lookup。展示:value · label;有 extra 时 value · label(extra)。multiple 时多选。宿主亦接受 camelCase 键名。"
},
"inputSchema": {
"type": "object",

View File

@@ -733,7 +733,7 @@ HINT: <next step>
| 当前表已有同含义列,且列上已是 CRUD lookup | `column` / `bind.columnMapping`,或属性名 = 列名 | toolbar/batch 弹窗与 create/update 抽屉都会复用该列已解析的选项 |
| 当前表没有该列(如在「潜客」表上选「话术模板」) | `display_type: "lookup"` + 字段内联 `lookup` 对象 | 打开表单时宿主按当前技能库解析选项 |
内联 `lookup` 与列元数据 `options_json` **同构**(见 [`SCHEMA.md`](SCHEMA.md)「表关联 lookup」):
内联 `lookup` 与列元数据 `options_json` **同构**(见 [`SCHEMA.md`](SCHEMA.md)「跨表引用 lookup」):
```json
{
@@ -750,6 +750,20 @@ HINT: <next step>
"filter_column": "is_active",
"filter_value": 1
}
},
"engineIds": {
"type": "string",
"title": "监测引擎",
"description": "可多选;提交为逗号分隔编号。",
"display_type": "lookup",
"lookup": {
"ref_table": "engines",
"value_column": "id",
"label_column": "display_name",
"filter_column": "enabled",
"filter_value": 1,
"multiple": true
}
}
}
```
@@ -761,16 +775,22 @@ HINT: <next step>
| `label_column` | 是 | 主文案列 |
| `extra_column` | 否 | 第三展示列(任意列名,不限「备注」) |
| `filter_column` / `filter_value` | 否 | 等值过滤 |
| `multiple` | 否 | `true` = 多选;省略/`false` = 单选(默认) |
**下拉展示(宿主统一,技能只配列):** `{value} · {label}`;有非空 `extra_column` 时为 `{value} · {label}({extra})`。例:`2 · 通用私信-留资`、`2 · 通用私信-留资(dm)`。
**下拉/选项展示(宿主统一,技能只配列):** 单选为 `{value} · {label}`;有非空 `extra_column` 时为 `{value} · {label}({extra})`。例:`2 · 通用私信-留资`、`2 · 通用私信-留资(dm)`。
**可选「自动」:** 字段不要放进 `required`;不要用 `minimum: 1` 卡死留空。宿主会省略空的可选数字参数,避免「提交的数据无效」。
**多选存值与模板:** Action / CRUD 存 **`TEXT` 逗号分隔 id**(如 `1,2,5`);`{{engineIds}}` 原样替换进 CLI。多选字段在 `inputSchema` 里用 `type: "string"`(逗号串),不要用尚未稳定的 JSON array 当业务控件。
**多选控件(宿主策略):** 选项少(建议 ≤6)用勾选列表;选项多用字段内 Chip + 可搜索下拉(不是再开业务弹窗)。
**可选「自动」(单选常见):** 字段不要放进 `required`;不要用 `minimum: 1` 卡死留空。宿主会省略空的可选数字参数,避免「提交的数据无效」。
**反模式(勿做):**
- 标题写成「某某编号」,说明里教用户去左侧表抄 id
- 用静态 `enum` 罗列会增长/变更的业务行(会过期)
- 在技能里自己拼假 label 列代替宿主统一格式(难维护)
- 另造 `lookup_multi` / 第二套 display_type(应只加 `multiple: true`)
形状参考:[`assets/examples/lookup-action.example.json`](../assets/examples/lookup-action.example.json)。
@@ -786,7 +806,7 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
| 数字 | `type: number` | `display_type: number` 或 INT/REAL/NUMERIC |
| 开关 | `type: boolean` | `display_type: boolean` |
| 枚举下拉 | `enum: [...]` | `options_json` 数组 |
| 关联下拉(Lookup) | 内联 `display_type: lookup` + `lookup`,或绑列 `column` / `columnMapping` | `display_type: lookup` + `options_json` 对象 |
| 关联下拉(Lookup) | 内联 `display_type: lookup` + `lookup`(可选 `multiple`),或绑列 | 同左;多选存 `TEXT` 逗号分隔 id |
| 密码 | `sensitive: true` | 列 `sensitive` |
| 日期(日) | `display_type` 或 `format`: `date` | `display_type: date` → 存 `YYYY-MM-DD` |
| 日期时间 | `display_type` 或 `format`: `datetime_unix_seconds` | 同左;**存/传 Unix 秒** |
@@ -798,7 +818,8 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
| 只读 | `readOnly: true` | 列 `editable: 0` / 宿主只读规则 |
| **条件显隐** | **`visibleWhen`**(见下节) | **本版 CRUD 无对称**;仅 Action / 定时任务参数表单 |
**本版不做(勿宣传):** 文件上传、多选/标签数组、复杂 `oneOf` / nested object 专用表单;`visibleWhen` 多条件 AND/OR 树、跨 Action 依赖、CRUD 新增/编辑对称显隐。
**本版不做(勿宣传):** 文件上传、复杂 `oneOf` / nested object 专用表单;`visibleWhen` 多条件 AND/OR 树、跨 Action 依赖、CRUD 新增/编辑对称显隐。
(**Lookup 多选**已支持:同一 `lookup` + `multiple: true`,见上文;不是任意标签数组。)
日期时间约定:
@@ -811,7 +832,7 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
- 根:`object`;属性:`string` / `number` / `boolean`(`object`/`array` 仅作 JSON 多行编辑兼容,新技能慎用)
- `enum` / `default` / `required` / `sensitive` / `readOnly`
- `display_type` / `format`:`textarea` | `date` | `datetime_unix_seconds` | `url` | `email` | **`lookup`**
- 字段内联 `lookup`(与 CRUD `options_json` lookup 对象同构;可选 `extra_column`)
- 字段内联 `lookup`(与 CRUD `options_json` lookup 对象同构;可选 `extra_column`、`multiple`)
- 属性级 `description`(表单标签下方灰字说明)
- 简单模板替换(`{{fieldName}}`)
- 可选空数字字段提交时由宿主省略,避免 `""` 触发 number 校验失败
@@ -937,7 +958,7 @@ CRUD 侧宿主义务与禁止事项见 [`SCHEMA.md`](SCHEMA.md)「表单与展
- 短名看不懂或会覆盖数据时,用独立的 `hoverHint`;不要把确认黄条复用成悬停
- 模板参数使用 `{{fieldName}}`,且必须在 `inputSchema.properties` 中声明
- `entrypoint.args` 项当前只允许 `string`、`number`、`boolean`;不允许对象或 `null`
- **不要**在当前模板中宣传文件上传、多选数组、`oneOf`、复杂 nested object 等尚未稳定支持的能力
- **不要**在当前模板中宣传文件上传、任意 JSON 多选数组、`oneOf`、复杂 nested object 等尚未稳定支持的能力;Lookup 多选请用 `lookup.multiple`
## 风险与入口配置
- 查询、诊断、版本检查可以放到 `skill-detail` 或 `agent`

View File

@@ -308,7 +308,7 @@ upsert_column_metadata(
| `number` | `INTEGER` / `REAL` / `NUMERIC` | 原文 | **数字输入**(`type=number`) | 数字;无 `display_type` 时,物理类型为 INT/REAL/NUMERIC 也应走数字控件 |
| `boolean` | `INTEGER` 0/1 或 BOOL | 开关语义展示 | 开关 | `0` / `1` |
| (枚举) | 任意 + `options_json` **数组** | 标签映射 | 下拉选择 | 选项 `value` |
| `lookup` | 跨表引用 id 标量 + `options_json` **对象** | 标签映射 | 下拉选择(查关联表) | 业务 id(非 SQLite 外键) |
| `lookup` | 跨表引用 id;`options_json` **对象**;可选 `multiple` | 单选:标签映射;多选:多标签(如「豆包、元宝」) | 单选下拉;多选见下文 | 单选:业务 id 标量;多选:`TEXT` 逗号分隔 id(非 SQLite 外键) |
| (敏感) | 任意;列 `sensitive=1` | 掩码/隐藏策略由宿主定 | 新建可用密码框;编辑策略与既有宿主一致 | 原文(勿日志明文) |
| `date` | `TEXT` 推荐 `YYYY-MM-DD`,或可安全归一的值 | 日期 | **日期选择**(日) | `YYYY-MM-DD` |
| `datetime_unix_seconds` | `INTEGER` | `YYYY-MM-DD HH:mm:ss` | **日期时间选择**;提交写回 **Unix 秒** | Unix **秒**整数;禁止改成 ISO 字符串 |
@@ -320,9 +320,11 @@ upsert_column_metadata(
**明确不在本版稳定范围(勿在新技能宣传为已支持):**
- 文件 / 图片上传
- 多选、标签数组、复杂 `oneOf` / nested object 表单
- 复杂 `oneOf` / nested object 表单(任意 JSON 数组当业务多选控件)
- 把业务可编辑时间存成毫秒、ISO 文本或 SQLite `datetime` 文本(审计字段规范见「标准时间字段」)
> **Lookup 多选**已纳入本版契约(见下文 `multiple`),与「任意多选/标签数组」不是同一能力。
`datetime_unix_milliseconds`:仅兼容**列表展示**旧数据;**新技能不要用**;可编辑时间一律 `datetime_unix_seconds`。
#### 新增/编辑表单:字段说明与默认值(宿主支持)
@@ -573,7 +575,9 @@ CREATE TABLE demo_items (
### 2)跨表引用 lookup(对象,用于关联 id 字段)
当字段取值依赖同库另一张业务表时,`display_type` 设为 `lookup`,`options_json` 写对象(**不是**数组)。这是**展示与表单下拉**契约,**不是** SQLite 外键:
当字段取值依赖同库另一张业务表时,`display_type` 设为 `lookup`,`options_json` 写对象(**不是**数组)。这是**展示与表单选择**契约,**不是** SQLite 外键。
**单选(默认,与既有行为一致):**
```json
{
@@ -586,26 +590,54 @@ CREATE TABLE demo_items (
}
```
**多选(平行扩展,仅多一个开关):**
```json
{
"ref_table": "engines",
"value_column": "id",
"label_column": "display_name",
"filter_column": "enabled",
"filter_value": 1,
"multiple": true
}
```
| 字段 | 必填 | 说明 |
|---|---|---|
| `ref_table` | 是 | 同库被引用表名(snake_case 标识符) |
| `value_column` | 是 | 写入当前字段的值列(业务 id / 编号) |
| `label_column` | 是 | 下拉主文案列(名称) |
| `label_column` | 是 | 选项主文案列(名称) |
| `extra_column` | 否 | 第三展示列(不必叫「备注」);有值时拼进括号 |
| `filter_column` / `filter_value` | 否 | 等值过滤(如只列出 `enabled=1`) |
| `multiple` | 否 | `true` = 多选 Lookup;省略或 `false` = **单选**(默认) |
宿主在加载表 schema 时查询 `ref_table`,把结果展开成 `{value,label}` 下拉选项;**物理列仍存标量**(如 `platform_key` 文本、`account_id` 整数),不做嵌套子表。
宿主在加载表 schema 时查询 `ref_table`,把结果展开成 `{value,label}` 选项列表。
**存库约定:**
| 模式 | 物理列建议 | 存什么 |
|------|------------|--------|
| 单选 | 整数或文本标量 | 一个业务 id(如 `2`、`douyin`) |
| 多选 | **`TEXT`** | 逗号分隔 id,如 `1,2,5`(与 `$selection.ids` 风格一致);**不要**建联接子表当 UI 多选 |
**统一展示文案(宿主固定一种,技能只配列):**
- 默认:`{value} · {label}`,例如 `2 · 通用私信-留资`、`douyin · 抖音`
- 单选默认:`{value} · {label}`,例如 `2 · 通用私信-留资`、`douyin · 抖音`
- 配置了 `extra_column` 且该行有非空值:`{value} · {label}({extra})`
- 未配置 `extra_column`、或额外列为空:不加 `()`
- `label` 为空或与 `value` 相同时:只显示 `value`(可再带额外括号)
- **多选列表格**:按 id 拆开后逐个映射为 label,再用顿号/逗号拼接(如 `豆包、元宝`);过长可截断并提示「+N」
Action 弹窗内联 Lookup 与上表 **同构**(字段名一致),见 [`ACTIONS.md`](ACTIONS.md)「跨表引用:Lookup」。
**控件呈现(宿主策略,非第二套契约):**
示例:账号表的平台字段 → 平台信息表;凭据表的账号编号 → 账号信息表;私信 Action 选话术 → `message_templates`。
- 单选:下拉 Select(现状)
- 多选且选项较少(建议 ≤6):勾选列表
- 多选且选项较多:字段内 Chip + 可搜索下拉(**不是**再开一层业务弹窗)
Action 弹窗内联 Lookup 与上表 **同构**(字段名一致,含 `multiple`),见 [`ACTIONS.md`](ACTIONS.md)「跨表引用:Lookup」。
示例:账号表的平台字段 → 平台信息表;凭据表的账号编号 → 账号信息表;私信 Action 选话术 → `message_templates`;抽检 Action 多选引擎 → `engines` + `multiple: true`。
## 幂等初始化标准

View File

@@ -219,6 +219,8 @@ def validate_options_json(options_json: str | None) -> tuple[list[str], list[str
errors.append(f"options_json.{key} 必须是安全标识符,当前为 {raw!r}")
if parsed.get("filter_column") not in (None, "") and "filter_value" not in parsed:
warnings.append("options_json lookup 声明了 filter_column 但未提供 filter_value")
if "multiple" in parsed and not isinstance(parsed.get("multiple"), bool):
errors.append("options_json lookup.multiple 必须是布尔值")
return errors, warnings
if not isinstance(parsed, list):

View File

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

View File

@@ -353,6 +353,8 @@ class TestDocsStandards(unittest.TestCase):
"表与表之间:禁止 SQLite 外键",
"禁止 SQLite 外键",
"跨表引用 lookup",
"\"multiple\": true",
"Lookup 多选",
):
self.assertIn(marker, text, msg=f"SCHEMA.md missing {marker!r}")
@@ -364,6 +366,7 @@ class TestDocsStandards(unittest.TestCase):
"跨表引用:Lookup",
"display_type: \"lookup\"",
"extra_column",
"multiple",
"数据管理新增/编辑",
"_jiangchang_columns.description",
"datetime_unix_seconds",