33 KiB
数据存储与数据管理展示规范
本文是 skill 本地 SQLite 与匠厂宿主「数据管理」页面展示契约的权威说明。
产品定位: 数据管理既是业务数据与过程的可见面,也是与对话平级的 UI 执行面(表上 Action 走同一 CLI 闭环)。何时挂执行入口见 ../development/PRODUCT_SURFACES.md;本文管表结构与展示元数据。
实现上的 DDL 与默认中文元数据种子见 scripts/db/display_metadata.py;scripts/db/connection.py 的 init_db() 会一并初始化。
职责边界
技能负责
- 定义真实 SQLite 表结构
- 使用英文
snake_case表名和字段名 - 使用
_jiangchang_tables提供表级展示元数据 - 使用
_jiangchang_columns提供字段级展示元数据(含可选description表单说明、可选grid_help表格说明) - 需要「新增表单预填」时,在物理列上声明 SQLite
DEFAULT(勿把默认值只写在文案里) - 定义哪些字段可见、可搜索、可创建、可编辑
- 定义字段展示类型和枚举选项
- (可选)按业务规则维护单元格呈现旁路数据(语义 tone;见
CELL_PRESENTATION.md) - 保证元数据与真实数据库结构一致
- 保证元数据初始化幂等
宿主负责
- 读取
_jiangchang_*元数据 - 读取 SQLite
PRAGMA table_info - 根据 schema 渲染数据管理页面
- 根据
editable推导字段是否可创建或编辑 - 在新增 / 编辑表单中,若列有
_jiangchang_columns.description,在标签下方展示该说明(灰字;空则不显示) - 在数据管理表格中,若表有
_jiangchang_tables.grid_help则在表上方展示可折叠说明;若列有_jiangchang_columns.grid_help则在列头显示「?」;空/未写则不渲染。不把description当作表格说明 - 打开新增表单时,对可创建字段用 SQLite 列
DEFAULT(PRAGMA table_info.dflt_value)预填;无 DEFAULT 则留空 - 执行通用查询、新增、编辑、删除、导入、导出
- 对字段名、表名、类型和权限进行最终校验
- 不猜测技能业务含义
- 不根据具体技能名称写特殊逻辑
- 可在技能安装/更新成功后静默调用
python scripts/main.py init-db做幂等 ensure(内部即init_db());失败不影响安装结果 - 若技能启用了单元格呈现(见
CELL_PRESENTATION.md),读取旁路呈现数据并为单元格映射语义样式(tone);不猜测业务规则
初始化入口
幂等建库/迁移:python scripts/main.py init-db。业务仓储也可懒触发 init_db(),与 CLI 结果一致。
SQLite 没有可靠的 COMMENT ON TABLE/COLUMN;SQL 文件里的 -- 注释 也不会成为可查询结构。
因此中文展示名称必须写入元数据表;字段顺序必须写在 CREATE TABLE 的列定义顺序中(PRAGMA table_info 的 cid)。
禁止新技能使用宿主的旧版
_schema_meta表。仅_jiangchang_tables/_jiangchang_columns为正式契约。
技能名称(SKILL.md)
---
name: 中文业务名称
description: 中文技能说明
metadata:
openclaw:
slug: english-kebab-slug
---
name:用户可见的中文业务名称。description:能力说明,不是技能名称。metadata.openclaw.slug:机器标识,kebab-case 英文;目录名与默认数据库文件名使用 slug。slug 语义规范见../development/NAMING.md。- 复制模板后不得将
your-skill-slug、My Skill等占位内容作为正式技能发布名称。
数据库路径与命名
{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/{skill-slug}/{skill-slug}.db
- 数据库文件:英文 slug,例如
your-skill-slug.db(默认{skill-slug}.db)。 - 业务表、字段:英文 snake_case 物理名,例如
demo_items、created_at。 - 禁止用中文作为 SQLite 真实表名或字段名。
外部来源只读与本地数据可写
某些技能会从外部系统读取数据并写入技能自己的本地数据库。需要区分:
外部来源数据库:
可能只读,不应被修改
技能本地数据库:
可以根据业务语义允许新增、编辑或删除
不要把「外部来源只读」错误理解成「技能本地数据库一定只读」。技能通过 _jiangchang_tables.readonly 与 _jiangchang_columns.editable 声明本地表的实际写权限。
若创建/更新/删除/导入带有领域副作用(自动建目录、加密、级联清理、归一化字段),不要依赖宿主默认直写:在 assets/actions.json 用可选字段 replaces: create|update|delete|import 接管默认按钮。其中 import 时用户仍使用宿主「导入数据」面板,确认后由技能写库。详见 ACTIONS.md 与 assets/examples/replaces-*.action.example.json。
表与表之间:禁止 SQLite 外键
业务表之间的「属于哪条父记录 / 引用哪条来源」只存业务 id(整数或文本标量),禁止在 DDL 中写 SQLite FOREIGN KEY / REFERENCES,也不要指望 PRAGMA foreign_keys = ON 替你校验或级联。
| 要做的事 | 正确做法 | 禁止 |
|---|---|---|
| 子表指向父表 | 子表列如 case_id INTEGER NOT NULL;写入前在 repository/service 查父表是否存在 |
FOREIGN KEY (case_id) REFERENCES parent(id) |
| 父表记「最新一版」 | 父表列如 latest_draft_id INTEGER NOT NULL DEFAULT 0;由 service 更新指针 |
同上 |
| 删除 / 覆盖 / 级联清理 | repository/service 内显式 DELETE / UPDATE;需要时用 Action steps 的 deleteByFk |
依赖 DB 级联或宿主 CRUD 被 FK 拦住 |
| 数据管理里显示名称 | _jiangchang_columns.display_type = lookup + options_json 对象(见下文「跨表引用 lookup」) |
把 lookup 当成 DDL 外键 |
为什么: 宿主数据管理对业务表执行通用增删改,会直接写 SQLite。一旦 DDL 带外键,删除、迁移 rename 父表、或父表 id 变更时极易出现「本地数据操作失败」且难排查。关联语义属于技能业务,必须在代码里写清。
迁移注意: 避免「ALTER TABLE parent RENAME TO parent_old → 建新 parent → 删 old」这类会隐性改写子表 FK 指向的套路;优先 ADD COLUMN + 拷列,或重建子表且不带 FK。
display_metadata_validator 与 validate_template_database 会扫描业务表 DDL;含 FOREIGN KEY / REFERENCES 时报错(见 development/CHECKLIST.md A6-6)。
标准数据同步语义(外源 → 本地库)
适用于「同步 / refresh / collect」类能力。使用通用名称表述,勿把具体平台表名写进模板实现。
| 要求 | 说明 |
|---|---|
| 外部来源默认只读 | 技能不得修改外源 |
| 本地 SQLite 可事务写入 | 写入走 repository + 事务 |
| 幂等 | 同一业务键重复同步结果稳定 |
| 稳定唯一业务键 | 明确 upsert / 匹配字段 |
| 全量或增量 | 在实现与文档中显式声明 |
| 完整快照才提交 | 完整性校验通过后再 commit;调用方必须显式提供完整性状态(如 keyword-only 必填的 snapshot_complete),禁止默认视为完整,禁止靠数据条数猜测 |
| 空结果 / 读取失败保护 | 读取失败、分页不完整或不可信空结果(snapshot_complete=False)不得清库或误删;仅当完整快照显式确认为空(snapshot_complete=True 且结果为空)时,才可按全量失效策略处理旧数据 |
| 全量失效策略 | 物理删除、removed/is_active 标记、或保留历史——必须三选一并写清 |
| 部分失败 | 不得伪装成整体成功 |
| 半成品 | 事务失败不得留下可见不一致状态 |
建议统一返回统计:
{
"total": 100,
"inserted": 10,
"updated": 20,
"unchanged": 65,
"removed": 5
}
同步数据 vs 导出当前数据表 vs 特殊业务报告 vs HTML 页面
| 能力 | 语义 | 负责方 |
|---|---|---|
| 同步数据 | 外部来源 → 技能本地数据库 | 技能 Action / domain service |
| 导出当前数据表 | 本地库 → CSV/Excel 等 | 数据管理页宿主统一导出 |
| 生成特殊业务报告(文件) | 多表合并、特殊排版或附加计算的文件 | 仅当系统导出无法满足时,才提供独立 export-*;落 output/ |
| HTML 页面报告 | 侧栏可预览的整页 HTML | 写入 html/ + Action id render-html(见 ACTIONS.md) |
同步型 Action 默认只写库、不生成导出文件。兼容旧 export 命令需要刷新时,必须调用同一 sync 内核。可复用测试范式见 tests/samples/test_sync_contract.py.sample。
元数据表(宿主兼容)
宿主读取字段(与匠厂 metadata-resolver 一致):
_jiangchang_tables 表级标准
| 字段 | 必填 | 说明 |
|---|---|---|
table_name |
是 | 真实英文表名 |
display_name |
是 | 用户可见的中文业务名称 |
description |
否 | 表说明(表单/备注用途);不用于表格上方横幅 |
grid_help |
否 | 数据管理表格上方说明;空/未写则不出横幅。与 description 独立,禁止互拷(见下节) |
sort_order |
否 | 兼容保留字段,不用于替代物理字段顺序 |
visible |
是 | 1 在宿主数据管理中展示 / 0 隐藏 |
readonly |
是 | 1 整张表只读;0 允许宿主根据字段权限执行写操作 |
default_sort_json |
否 | 数据管理默认行排序(见下节);空则宿主用内置默认 |
default_visible_columns_json |
否 | 数据管理默认展示列(见下节);空则全显 |
表级默认排序 default_sort_json(宿主支持)
技能可声明该表在数据管理中的默认排序;宿主优先采用,未声明或声明无效时回退:
updated_at DESC → created_at DESC → 主键 DESC。
JSON 数组(可多字段,按顺序):
[{"column":"sent_at","direction":"desc"},{"column":"id","direction":"desc"}]
规则:
column必须是该表真实列名;敏感列会被宿主忽略direction仅asc/desc(缺省按desc)- 用户在 UI 上手动改排序后,以用户选择为准;声明只影响未选择时的默认
- 通过
upsert_table_metadata(..., default_sort_json=...)写入;旧库由init_display_metadata_tables幂等ALTER补列
示例(消息时间线):
upsert_table_metadata(
cursor,
table_name="messages",
display_name="消息",
readonly=0,
default_sort_json='[{"column":"sent_at","direction":"desc"},{"column":"id","direction":"desc"}]',
)
表级默认展示列 default_visible_columns_json(宿主支持)
技能可声明该表在数据管理表格中的默认展示列(软默认,不是硬隐藏):
- 已配置:首次打开(用户尚未改过「列」)只显示名单中的列;其余列仍在「列」面板中,用户可勾回
- 未配置 / 空 / 无效:显示全部 schema 可见列(与今日行为一致)
- 用户改过「列」或表头「隐藏此列」后:以本机偏好为准;「恢复默认列布局」后重新回到技能默认(若有)
JSON 数组(物理列名;也可写 {"column":"col"} 对象,宿主同样接受):
["stat_type","period","stage","ops_count","timeout_ratio"]
规则:
- 列名必须是该表真实列,且
_jiangchang_columns.visible=1(硬隐藏列本来就不进表格) - 与
visible=0不同:visible=0= 列从数据管理 schema /「列」面板移除;本字段只影响默认勾选状态 - 名单为空或全部无效时,宿主 fail-open:全显
- 通过
upsert_table_metadata(..., default_visible_columns_json=...)写入;旧库由init_display_metadata_tables幂等ALTER补列
示例(时效统计:默认不展示编号与时间戳列):
upsert_table_metadata(
cursor,
table_name="timeliness_stats",
display_name="时效统计",
readonly=1,
default_visible_columns_json=(
'["stat_type","period","stage","ops_count",'
'"timeout_open","timeout_done","timeout_total","timeout_ratio"]'
),
)
表格说明 grid_help(宿主支持)
技能可给数据管理表格写说明,与新增/编辑表单的 description 完全独立:
| 写在哪 | 宿主做什么 | 空/未写 |
|---|---|---|
_jiangchang_tables.grid_help |
表格上方可折叠横幅(首行摘要;有后续行才出展开) | 不出横幅 |
_jiangchang_columns.grid_help |
列头「?」,点击查看该列说明 | 不出「?」 |
规则:
- opt-in:大多数表/列不需要写;不写则行为与今日一致
- 禁止把
description拷进grid_help,也禁止把表格规则写进description(description只给表单灰字) - 多行文本时:第一行是摘要,其余行点「展开」后可见
- 通过
upsert_table_metadata(..., grid_help=...)/upsert_column_metadata(..., grid_help=...)写入;旧库由init_display_metadata_tables幂等ALTER补列 - 需已支持该字段的匠厂宿主;更旧版本会忽略未知列,不写则无影响
示例(仅在确有表格规则时写;模板默认业务表不要预填):
upsert_table_metadata(
cursor,
table_name="timeliness_records",
display_name="时效登记",
readonly=1,
grid_help="超时按环节规则判定。\n交单:预计到港日前 3 天须交单",
)
upsert_column_metadata(
cursor,
table_name="timeliness_records",
column_name="submit_date",
display_name="交单日期",
grid_help="预计到港日前 3 天须交单",
editable=0,
)
禁止:
- 使用中文真实表名
- 使用
_schema_meta作为新的正式契约 - 只写英文物理名而不写中文
display_name - 让元数据引用不存在的表
- 用
visible=0假装「默认不展示但用户还能勾出来」(那是硬隐藏,勾不回来)
_jiangchang_columns 字段级标准
| 字段 | 必填 | 说明 |
|---|---|---|
table_name |
是 | 所属物理表 |
column_name |
是 | 真实英文字段名 |
display_name |
是 | 用户可见的中文字段名(表头与表单标签);保持简短 |
description |
否 | 字段说明(给人看的短文案);宿主在新增/编辑表单标签下方展示(见下节);不要塞进 display_name |
grid_help |
否 | 数据管理表头「?」;空/未写则不出。与表单 description 独立,禁止互拷 |
display_order |
否 | 兼容字段,默认保持 NULL |
visible |
是 | 是否进入数据管理 schema /「列」面板(0 = 硬隐藏,用户无法勾回;默认不展示但可勾回请用表级 default_visible_columns_json) |
searchable |
是 | 是否参与搜索 |
editable |
是 | 1 业务字段允许宿主用于新增和编辑;0 系统/只读字段 |
display_type |
否 | 字段渲染类型;稳定取值见下节「表单与展示:稳定字段类型」 |
options_json |
否 | 枚举选项 JSON |
这两张表是数据字典,不是业务数据;宿主不会把它们当作普通业务表展示。
表单与展示:稳定字段类型(宿主支持,须与 Action 对称)
数据管理「新增 / 编辑」表单与 Skill Action 参数表单应使用同一套控件语义(配置入口不同,见 ACTIONS.md)。新技能只使用下表;宿主须两边都实现,避免「Action 有、CRUD 没有」。
本版稳定支持(须齐):
display_type(CRUD) |
物理类型建议 | 列表展示 | 新增/编辑控件 | 存库约定 |
|---|---|---|---|---|
text(或缺省) |
TEXT / 一般标量 |
原文 | 单行文本 | 原文 |
textarea |
TEXT |
原文(可截断) | 多行文本 | 原文 |
json |
TEXT |
原文 | 多行文本(JSON 字符串) | 原文;提交时宿主可做基础 JSON 合法性校验 |
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 对象;可选 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 字符串 |
url |
TEXT |
原文(可作链接) | 单行输入 type=url(薄层;校验从宽) |
原文 |
email |
TEXT |
原文 | 单行输入 type=email(薄层;校验从宽) |
原文 |
说明灰字与创建默认值见下一小节;与 Action 的对照写在 ACTIONS.md「表单字段类型对照」。
明确不在本版稳定范围(勿在新技能宣传为已支持):
- 文件 / 图片上传
- 复杂
oneOf/ nested object 表单(任意 JSON 数组当业务多选控件) - 把业务可编辑时间存成毫秒、ISO 文本或 SQLite
datetime文本(审计字段规范见「标准时间字段」)
Lookup 多选已纳入本版契约(见下文
multiple),与「任意多选/标签数组」不是同一能力。
datetime_unix_milliseconds:仅兼容列表展示旧数据;新技能不要用;可编辑时间一律 datetime_unix_seconds。
新增/编辑表单:字段说明与默认值(宿主支持)
数据管理「新增记录 / 编辑记录」与 Skill Action 弹窗是两套表单;CRUD 侧约定如下(交互对齐 Action 的 inputSchema.description / default,但配置入口不同)。
| 能力 | 技能写什么 | 宿主做什么 |
|---|---|---|
| 字段说明 | _jiangchang_columns.description(可选) |
新增/编辑表单中,在标签下方以灰字展示;空则不显示 |
| 创建默认值 | 物理列 SQLite DEFAULT(如 limit_n INTEGER NOT NULL DEFAULT 0) |
打开「新增」时,对可创建字段预填该默认值;无 DEFAULT 则留空 |
| 编辑已有行 | — | 不用 DEFAULT 覆盖已有行数据 |
| 表格列头标题 | display_name |
短名;不要把长说明塞进列头文字 |
| 表格列头「?」 | _jiangchang_columns.grid_help(可选) |
有内容才显示「?」;不回退 description |
| 表格上方说明 | _jiangchang_tables.grid_help(可选) |
有内容才显示横幅;不回退表级 description |
规则与禁止:
- 推荐对易填错、语义特殊的可编辑字段写
description(示例链接、0表示不限制等);非强制每个字段都有 - 禁止把说明硬塞进
display_name(如评论上限(0不限));表头会变脏,也无法与 Action 说明对齐 - 业务语义(如「0 = 不限制」)写在
description里;宿主只展示,不解读业务含义 - 第一期不新增元数据「表单默认值」列;CRUD 默认值 = DDL
DEFAULT,与 Action 的inputSchema.default分开配置 - 同一业务字段若同时出现在 Action 与新增表单,文案应对齐(短标题 + 说明),但分别写在
actions.json与_jiangchang_columns/ DDL - 数字类可编辑列推荐
display_type: "number"(或依赖 INT/REAL/NUMERIC 推断)并在 DDL 写清DEFAULT(如条数上限默认0)
示例(可编辑列种子 + DDL 默认):
-- 物理列:创建默认 0
comment_limit INTEGER NOT NULL DEFAULT 0
seed_column_metadata_batch(
cursor,
table_name="videos",
columns=(
{
"column_name": "video_url",
"display_name": "视频链接",
"editable": 1,
"display_type": "url",
"description": "填平台视频页链接;需包含可识别的视频路径片段。",
},
{
"column_name": "comment_limit",
"display_name": "采集评论条数",
"editable": 1,
"display_type": "number",
"description": "最多采多少条。填 0 = 不限制;填正整数则按该数量封顶。",
},
),
valid_column_names=physical,
)
Action 参数表单的 title / description / default / display_type 见 ACTIONS.md;勿与本节 CRUD 配置入口混用。
editable 与 createable 的关系
技能模板只维护 editable 这一份字段权限声明:
skill metadata:
editable
host runtime:
createable / editable
宿主根据以下因素计算字段是否能够出现在新增或编辑表单中:
- 表级
readonly - 字段
editable - 主键
- generated 字段
- sensitive 字段
- BLOB 类型
- not-null 约束
- default value
通用规则:
新增表单:
editable = 1
且不是主键
且不是 generated
且不是敏感字段
且不是 BLOB
编辑表单:
editable = 1
且不是主键
且不是 generated
且不是敏感字段
且不是 BLOB
若一张表:
readonly = 0
但所有字段 editable = 0
则:
- 这张表技术上可写,但不适合手工新增或编辑
- 宿主不应打开空白新增表单
- 技能应通过自身业务流程产生数据
- 这不是宿主猜测业务,而是技能通过元数据声明的结果
系统字段标准
通常保持 editable = 0 的字段类型包括:
- 自增主键
- generated 字段
created_at/updated_at- 同步时间戳
- 内容哈希
- 计算结果
- 内部状态
- 删除标记
- 审计字段
- 敏感凭据或密钥字段
不要把具体业务字段名写死;按字段类型和职责设置 editable。
表结构标准
- 业务表和字段使用英文
snake_case - 可写表优先使用单字段主键
- 自增主键优先放在
CREATE TABLE第一列 created_at和updated_at放在业务字段末尾- 字段展示顺序以
CREATE TABLE和PRAGMA table_info(cid)为准 - 不依赖
display_order重新排列字段 - 不要让宿主通过英文字段名推断展示顺序
- 复杂字段需要通过
display_type或visible明确处理
推荐顺序(可按业务裁剪,但已有字段应遵守相对位置):
1. 主键 id
2. 关联标识与核心业务字段
3. 普通业务字段
4. 状态、结果、错误等辅助字段
5. created_at(如有)
6. updated_at(如有)
标准时间字段(新技能统一规范)
新技能业务表若需要时间审计字段,统一采用 Unix 秒级时间戳,不要使用毫秒时间戳、ISO 字符串或 SQLite 文本 datetime。
| 项目 | 规范 |
|---|---|
| 物理字段 | created_at INTEGER、updated_at INTEGER |
| 存储格式 | Unix 秒级整数 |
展示元数据 display_type |
datetime_unix_seconds |
| 中文名 | created_at → 创建时间;updated_at → 更新时间 |
| 用户编辑 | 审计字段 editable = 0(系统维护,列表只展示格式化结果,不进可编辑表单) |
| 字段顺序 | 位于业务字段末尾:… → created_at → updated_at |
| 排序 | 不使用 display_order;顺序仅来自 PRAGMA table_info |
业务可编辑时间 vs 审计时间
| 场景 | editable |
列表 | 新增/编辑表单 |
|---|---|---|---|
created_at / updated_at 等审计列 |
0 |
格式化为 YYYY-MM-DD HH:mm:ss |
不出现(或只读,由宿主既有规则) |
| 业务时间列(预约、截止、活动开始等) | 1 |
同上格式化 | 日期时间控件;提交写入 Unix 秒整数 |
宿主对 display_type = datetime_unix_seconds:
- 列表:格式化为
YYYY-MM-DD HH:mm:ss展示,不改库内原始整数 - 可编辑表单:提供日期时间选择;保存时写回 Unix 秒(不要写 ISO 字符串、不要写毫秒)
自动生成与维护
- created_at:
INTEGER NOT NULL DEFAULT (unixepoch());插入时可省略,由数据库生成。 - updated_at:
INTEGER NOT NULL DEFAULT (unixepoch());插入时同样可省略。 - updated_at 更新:业务表通过 SQLite trigger
{table}_set_updated_at在业务字段更新后自动刷新(init_updated_at_trigger)。
实现参考:
- DDL 与 trigger:
scripts/db/connection.py、scripts/db/timestamp_columns.py - 中文元数据种子:
scripts/db/display_metadata.py - 自检:
scripts/db/display_metadata_validator.py
系统自动写入表的只读约定
技能内部系统自动写入、不供用户在宿主数据管理中手工维护的表,应声明:
| 层级 | 值 | 说明 |
|---|---|---|
表级 readonly |
1 |
宿主数据管理只读展示,不提供新增/编辑/删除表单 |
相关业务字段 editable |
0 |
元数据权限只约束宿主 UI,不限制技能代码写库 |
模板默认不建此类审计表。过程看 jiangchang.log,进度/终态看任务中心,业务结果看业务表。
表级 readonly = 1 时,宿主数据管理只读展示;技能 repository 仍可写入。
通用示例:demo_items(测试 fixture 参考)
以下表结构用于模板测试演示数据管理契约,不是模板默认业务表:
CREATE TABLE demo_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'draft',
generated_value TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
对应元数据要点:
| 物理字段 | editable | 说明 |
|---|---|---|
| id | 0 | 自增主键 |
| title | 1 | 业务字段 |
| description | 1 | 业务字段 |
| status | 1 | 枚举,options_json 演示 |
| generated_value | 0 | 系统/计算字段 |
| created_at | 0 | 系统时间 |
| updated_at | 0 | 系统时间 |
枚举字段 options_json
options_json 支持两种形态:
1)静态枚举(数组)
每项包含 value 与 label:
[
{"value": "draft", "label": "草稿"},
{"value": "published", "label": "已发布"},
{"value": "archived", "label": "已归档"}
]
宿主据此渲染选择框;技能侧负责保证 JSON 合法且与物理字段取值一致。
2)跨表引用 lookup(对象,用于关联 id 字段)
当字段取值依赖同库另一张业务表时,display_type 设为 lookup,options_json 写对象(不是数组)。这是展示与表单选择契约,不是 SQLite 外键。
单选(默认,与既有行为一致):
{
"ref_table": "platforms",
"value_column": "platform_key",
"label_column": "display_name",
"extra_column": "category",
"filter_column": "enabled",
"filter_value": 1
}
多选(平行扩展,仅多一个开关):
{
"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 |
是 | 选项主文案列(名称) |
extra_column |
否 | 第三展示列(不必叫「备注」);有值时拼进括号 |
filter_column / filter_value |
否 | 等值过滤(如只列出 enabled=1) |
multiple |
否 | true = 多选 Lookup;省略或 false = 单选(默认) |
宿主在加载表 schema 时查询 ref_table,把结果展开成 {value,label} 选项列表。
存库约定:
| 模式 | 物理列建议 | 存什么 |
|---|---|---|
| 单选 | 整数或文本标量 | 一个业务 id(如 2、douyin) |
| 多选 | TEXT |
逗号分隔 id,如 1,2,5(与 $selection.ids 风格一致);不要建联接子表当 UI 多选 |
统一展示文案(宿主固定一种,技能只配列):
- 单选默认:
{value} · {label},例如2 · 通用私信-留资、douyin · 抖音 - 配置了
extra_column且该行有非空值:{value} · {label}({extra}) - 未配置
extra_column、或额外列为空:不加() label为空或与value相同时:只显示value(可再带额外括号)- 多选列表格:按 id 拆开后逐个映射为 label,再用顿号/逗号拼接(如
豆包、元宝);过长可截断并提示「+N」
控件呈现(宿主策略,非第二套契约):
- 单选:下拉 Select(现状)
- 多选且选项较少(建议 ≤6):勾选列表
- 多选且选项较多:字段内 Chip + 可搜索下拉(不是再开一层业务弹窗)
Action 弹窗内联 Lookup 与上表 同构(字段名一致,含 multiple),见 ACTIONS.md「跨表引用:Lookup」。
示例:账号表的平台字段 → 平台信息表;凭据表的账号编号 → 账号信息表;私信 Action 选话术 → message_templates;抽检 Action 多选引擎 → engines + multiple: true。
幂等初始化标准
_jiangchang_tables和_jiangchang_columns必须通过 upsert 初始化- 多次执行初始化不能重复记录
- 重新初始化可以更新展示名、描述、
grid_help、visible、searchable、editable、display_type、options_json - 元数据不能残留旧字段(删除或重命名字段时必须同步清理)
- 元数据不能引用不存在的表或字段
- 新增字段或表时必须同步增加元数据
- 不要直接修改用户真实数据库作为迁移方式
实现 helper:
upsert_table_metadata/upsert_column_metadata— 幂等写入seed_column_metadata_batch— 批量 upsert;可选传入valid_column_names时在 upsert 后自动 pruneprune_stale_column_metadata— 清理已删除物理字段的元数据
新增业务表时如何初始化
- 在
connection.py(或迁移脚本)中CREATE TABLE真实物理表。 - 用
PRAGMA table_info或list_physical_column_names取得真实物理字段列表(顺序以cid为准)。 - 调用
upsert_table_metadata写入表级元数据。 - 调用
seed_column_metadata_batch,并传入valid_column_names=physical_column_names:
from db.display_metadata import list_physical_column_names, seed_column_metadata_batch, upsert_table_metadata
physical = list_physical_column_names(cursor, "demo_items")
upsert_table_metadata(cursor, table_name="demo_items", display_name="示例记录", readonly=0)
seed_column_metadata_batch(
cursor,
table_name="demo_items",
columns=DEMO_ITEMS_COLUMN_METADATA,
valid_column_names=physical,
)
传入 valid_column_names 时:先 upsert,再删除不在物理表中的陈旧 _jiangchang_columns 行。
字段删除或重命名时如何清理
- 在 DDL / 迁移中完成物理字段删除或重命名。
- 重新执行
seed_column_metadata_batch(..., valid_column_names=当前物理字段列表),自动 prune 旧字段元数据。 - 或单独调用
prune_stale_column_metadata(cursor, table_name, valid_column_names)。
不传入 valid_column_names 时不会自动 prune——模板不会猜测真实物理字段,避免误删;文档与实现均以此为准。
不要直接修改用户真实数据库作为迁移方式;元数据初始化必须使用真实物理字段列表。
中文命名质量
- 使用普通用户能理解的业务用语:
error_msg→ 错误信息,created_at→ 创建时间。 - 禁止:
Task Logs、input编号、errormsg、直接把物理名当display_name。 - 不宜向用户暴露的实现细节(如
payload_json、selector)应设visible = 0或给出可理解中文名。
元数据同步规则
- 新增用户可见业务表 → 同步
_jiangchang_tables。 - 新增用户可见字段 → 同步
_jiangchang_columns。 - 删除/重命名表或字段 → 同步清理或迁移元数据。
- 初始化脚本必须幂等。
- 元数据中的表、字段必须在库中真实存在;可见项不得缺少中文
display_name。
开发自检
init_db()后运行tests/test_display_metadata.py与tests/test_data_management_contract.py。- 若提供宿主 Action 入口,运行
tests/test_actions_manifest.py。 - 复制为真实技能后,使用
scripts/db/display_metadata_validator.py中的校验函数检查 SKILL 名称、slug 与元数据完整性。
模板原则
- 模板不做复杂历史迁移框架;新 skill 从当前 schema 起步。
- 元数据 DDL 与种子 helper 以
scripts/db/display_metadata.py为单一权威来源。 - 模板定义「如何声明能力」;具体技能定义「声明什么业务能力」;宿主负责「如何通用渲染和执行」。