# Skill Action Manifest(匠厂宿主) 本文件说明 `assets/actions.json` 与 `scripts/main.py` CLI 的对应关系,供 **Agent 编排**与宿主 **Skill Action Runtime** 使用。这是 Agent 侧关于 Action / 任务中心的**主契约**;实现细节与开发规范见 [`../development/SKILL_ACTION_RUNTIME.md`](../development/SKILL_ACTION_RUNTIME.md)。 **先读产品入口:** 对话 / 数据管理 / 定时任务在宿主上**平级**,何时挂哪个入口见 [`../development/PRODUCT_SURFACES.md`](../development/PRODUCT_SURFACES.md)(本文只讲字段与执行模型)。 **边界:** 用户市场四 Tab 见根目录 [`README.md`](../README.md) / [`TUTORIAL.md`](../TUTORIAL.md) / [`DEMO.md`](../DEMO.md) / [`CHANGELOG.md`](../CHANGELOG.md);CLI 参数见 [`CLI.md`](CLI.md)。 ## Agent 何时用 Action(与 CLI 的分工) | 场景 | 做法 | |------|------| | 短查询 / 只读 / 秒级 | bash + 共享 Python CLI(见 [`CLI.md`](CLI.md));**不必**强行走 Action | | 数据管理按钮 / 定时任务 / 技能详情按钮 | **必须**在 `actions.json` 声明对应 Action | | 长耗时 / RPA / 需要进度与取消 | **必须** `run_skill_action`;对应 Action 一般为 `executionProfile: "async"` | | 已声明的 `sync` Action | `run_skill_action` 当场返回结果,**不进**任务中心 | | 已声明的 `async` Action | `run_skill_action` 立即返回 `jobId`,**进入**任务中心 | **进不进任务中心只取决于该 Action 的 `executionProfile`,与从数据管理、定时任务还是 Agent 触发无关。** 禁止对长任务 `exec`/`bash` + `process poll` 干等 CLI。禁止 `uv run python` 跑技能脚本。 ## async 副作用 Action 最小组合(复制验收 checklist) 对每个「会开浏览器 / 长耗时 / 需进度或取消」的 Action,声明时建议同时满足: 1. `executionProfile: "async"`(进入任务中心) 2. 若要让 Agent 触发:`placements` **含** `agent` 3. 有可感知副作用时:提供 `confirmation.message` 4. 需要用户/Agent 入参时:简单 `inputSchema` + entrypoint 中的 `{{fieldName}}` 5. `SKILL.md`「用户意图路由表」与 `CLI.md`「Action 强制黑名单」**出现同一 action id / 命令** 6. 若处理的是**业务队列表中的一行**:遵守 [`WORK_ITEM_STATUS.md`](WORK_ITEM_STATUS.md)——默认只捞**可操作**角色;开始抢占为**进行中**;结束回写成功/失败(失败带原因);确认文案写清仅可操作态可处理 默认模板 `assets/actions.json` 仅含诊断类 sync Action,**不要**为通过本 checklist 而塞假业务 Action;业务技能按需添加。完整字段见下文 Phase 1。 ## 可选:域前缀 CLI(嵌套子命令) 小技能可继续扁平:`entrypoint.command = "run"`。 命令变多时,可采用**域前缀**:`command` 为域(如 ``),业务动词与参数放在 `args` 数组(如 `["", "--flag", "{{field}}"]`)。与扁平入口等价,须仍指向同一 domain service。 ## 模板严格规范 vs 宿主向后兼容 | 角色 | 定位 | |------|------| | **skill-template** | 新技能推荐的**严格规范**:必填字段、明确类型、`additionalProperties: false` | | **宿主运行时** | **向后兼容**的历史 manifest 解析器:可能接受部分缺省字段(如 `description`、`placements`、`entrypoint.args`、缺失的 `bind.tables`) | **新技能不应依赖宿主缺省行为。** 复制模板后按 [`assets/schemas/skill-actions.schema.json`](../assets/schemas/skill-actions.schema.json) 与本文完整填写 manifest。 JSON Schema 使用 `additionalProperties: false` 表达模板严格边界;这与宿主共享类型中的可选字段并不矛盾——宿主为兼容旧技能可能更宽松,模板为防漂移要求更严。 ## Action 是否必需 Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**决定(见 [`../development/PRODUCT_SURFACES.md`](../development/PRODUCT_SURFACES.md)),不是「可有可无的附属能力」: - 定位需要出现在**数据管理按钮**、**技能详情**、**定时任务**或 **Agent 直接调用**中时,**必须**提供 `assets/actions.json` 并声明对应入口。 - 定位确认无任何宿主结构化入口时,**可以不提供** `actions.json`。 - **含浏览器 RPA / 长耗时任务**的技能:**必须**提供至少一条 `executionProfile: "async"` 且 `placements` 含 `agent` 的 action(见 `POLICY-SKILL-ACTION-001`)。 ## 文件位置 ```text {skill_root}/assets/actions.json ``` 宿主扫描路径示例: ```text ~/.openclaw/skills/your-skill-slug/assets/actions.json ``` ## 顶层结构 | 字段 | 必填 | 说明 | |------|------|------| | `schemaVersion` | 是 | 当前固定为 `1` | | `skill` | 是 | 必须与 `SKILL.md` 的 `metadata.openclaw.slug` 一致 | | `actions` | 是 | Action 数组,每项 `id` 唯一;**数组相对顺序**决定数据管理 toolbar / row / batch 技能按钮的展示顺序(见下节) | 模板最小示例见 [`assets/actions.json`](../assets/actions.json);JSON Schema 见 [`assets/schemas/skill-actions.schema.json`](../assets/schemas/skill-actions.schema.json)。可选 Data Policy 见 [`POLICY.md`](POLICY.md) 与 [`assets/schemas/skill-policy-snapshot.schema.json`](../assets/schemas/skill-policy-snapshot.schema.json)。可选单元格呈现见 [`CELL_PRESENTATION.md`](CELL_PRESENTATION.md) 与 [`assets/schemas/skill-cell-presentation.schema.json`](../assets/schemas/skill-cell-presentation.schema.json)。 ## 六职责正交(必读) | 字段 / 层 | 只决定什么 | 不决定什么 | |-----------|------------|------------| | **`placements`** | Action **出现在哪里**(toolbar / row / batch / cron / agent / skill-detail;`row`=行级入口,壳由宿主呈现) | 不决定 sync/async,不决定点了是否弹窗,不决定行级用操作列还是右键,不决定谁能点 | | **`replaces`** | **可选**:接管数据管理默认「新增 / 编辑 / 删除 / **导入**」 | 不强制;无配置时宿主仍直写 SQLite;**import 时 UI 仍用宿主「导入数据」面板** | | **`bind.tables`** | 数据管理里出现在**哪些业务表** | 不决定业务实现 | | **`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 全开 | | **`entrypoint`** | 实际执行哪个 CLI 命令与参数 | 不写业务逻辑 | | **domain service** | **真正完成什么业务** | 不感知 toolbar / cron / agent 来源 | 一个业务能力通常只定义**一个** Action,用 `placements` 同时挂到多个入口。不要拆成 `sync-records-toolbar` / `sync-records-cron` 等副本,除非语义确实不同;即便拆分,也必须调用同一个领域业务内核。需要按用户/角色藏按钮或滤行时,**不要**按角色复制 Action,应启用顶层 `policy` 并由技能算出 Snapshot(见 [`POLICY.md`](POLICY.md))。 ## Phase 1 当前稳定支持(新技能应只使用这些) | 层级 | 稳定字段 / 能力 | |------|----------------| | 顶层 | `schemaVersion`、`skill`、`actions`;可选 **`policy`**(Data Policy 启用块);可选 **`cellPresentation`**(单元格呈现启用块) | | action | `id`、`label`、`description`、`placements`、`entrypoint`、**`executionProfile`(必填)**;可选 `hoverHint`、`inputSchema`、`confirmation`、**`interaction`**、**`interactionByPlacement`**、**`bind`**、**`replaces`**、**`hostEffects`**、**`steps`**、**`authz`**(仅文档提示) | | entrypoint | `type = cli`、`command`、`args`(**必填** JSON 数组,项为 `string` / `number` / `boolean`) | | placements | `toolbar`、`row`、`batch`、`skill-detail`、`agent`、`cron`;仅 `replaces` 时可为空数组 | | **replaces** | 可选 `"create"` \| `"update"` \| `"delete"` \| `"import"` — 接管默认新增/编辑/删除/导入(须 `bind.tables`) | | **bind** | `{ "tables": ["business_table"] }`;可选 `inputMapping`、`columnMapping`、**`labelColumn`**(行内/批量预填与选择摘要标签,见下节) | | **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`、可选 **`revealInForm`**;可选 `display_type` / `format`(含 `file` / `directory` 等);可选 **`pathRoot`**(file/directory 起始目录,相对技能数据根);可选 **`visibleWhen`**(字段条件显隐,见专节) | | confirmation | `{ "message": "..." }` — **风险文案**(confirm/form 弹窗黄条;agent 元数据);**不是**「要不要弹窗」的开关 | | **hoverHint** | 可选字符串。数据管理**工具栏悬停**文案;与 `description` / `confirmation.message` **独立**。省略则不显示悬停,不回退。短名已够懂时不要写。需宿主已支持该字段;当前实现为顶栏技能按钮,不含宿主内置删除/刷新/新增/导入/导出 | | **visibleWhen** | 可选。写在 **property** 上(非 action 顶层):依赖同表单其它字段值决定是否展示;隐藏字段不提交、不校验 `required`。**勿与** [`POLICY.md`](POLICY.md) L3 行态 `visibleWhen` 混淆;也**勿**用它藏「已选择」已表达的映射主键(见「选择摘要」) | | **revealInForm** | 可选。写在 **property** 上:有「已选择」时仍强制画出该 `readOnly` 字段;省略 = 映射只读字段默认不占参数区(仍提交) | | **executionProfile** | `"sync"` \| `"async"`(**必须显式写出**,禁止依赖缺省) | | **interaction** | `"auto"` \| `"direct"` \| `"confirm"` \| `"form"`(可选;省略≈`auto`) | | **interactionByPlacement** | 可选;按 placement 覆盖为 `direct`/`confirm`/`form`(不可写 `auto`) | | **hostEffects** | 可选;`onSuccess` / `onFailure` → `reloadTable` \| `refreshCatalog` \| `clearSelection`(匠厂宿主 ≥2.8.5) | | **steps** | 可选;有序 `{ when, do, table?, fk? }`,`when`=`before`\|`afterSuccess`\|`afterFailure`;新技能优先(≥2.8.5) | ## `bind.tables`(表级绑定) ```json { "bind": { "tables": ["business_table"] } } ``` 规则: - `bind` 为 object,`additionalProperties: false`;当前允许 `tables`(必填键当存在 bind 时)与可选 `inputMapping`、`columnMapping`、**`labelColumn`**。 - `tables` 非空数组、元素唯一、英文 **snake_case**、不得为空字符串。 - **`placements` 含 `toolbar` 时必须显式声明非空 `bind.tables`**(模板 Schema `if/then` + 自动测试强制)。 - `row` / `batch` **建议**同样声明 `bind.tables`,避免在无关表上露出按钮;未声明时宿主可能按兼容逻辑在更多表上展示。 - 表名须真实存在于技能 SQLite 与 `_jiangchang_tables`。 - 希望出现在多张表时,显式列出所有表;**不要**依赖「无 bind 则全表展示」——那是宿主对**旧技能**的兼容,不是新技能标准。 - **`inputMapping`(可选)**:把行/勾选映射到 `inputSchema` 字段,例如 `"leadId": "$row.$pk"`、`"ids": "$selection.ids"`。适合 `row` / `batch`。宿主仍会提交这些字段。映射字段建议标 `readOnly: true`:无「已选择」时只读展示;**有「已选择」时默认不占参数区**(仍提交,见下节)。不要用 `visibleWhen` 去「藏」映射主键。 - **`columnMapping`(可选)**:把 inputSchema 属性名映射到物理列名,供表单复用列展示名 / lookup。 - **`labelColumn`(可选)**:物理列名,供数据管理 Action 弹层「已选择」摘要展示 `#id + 标签`。**仅影响展示,不改变 CLI/提交 payload,也不决定业务是否跳过某些状态。** 省略时宿主用常见列名启发(如 `title` / `name` / `nickname` 等);再没有则只显示编号。列名不常见、或启发会选错列时再显式声明。 - `concurrency` / `locks` 仍未稳定,**不要**写入新技能 manifest。 ### 数据管理:选择摘要(宿主默认能力) 当用户在数据管理**勾选行**(或行内打开带行上下文的动作)后打开 `confirm` / `form` 弹层时,宿主会展示通用 **「已选择 · N 条」** 摘要(`#主键` + 可读标签;超过约 3 条可展开查看全部)。这是**宿主 UX**,对所有技能默认生效: - **技能零改动即可受益**;不要在 `confirmation.message` 里手写「已选 id 列表」,也不要为了面板去拼业务状态跳过文案。 - 摘要与执行解耦:勾选了谁由 UI 说明;**实际处理哪些行、是否跳过某些状态,仍由技能 CLI / domain service 决定**。 - 需要稳定标签列时,在 `bind` 上声明可选 `labelColumn`(见上)。示例:[`assets/examples/selection-label-column.action.example.json`](../assets/examples/selection-label-column.action.example.json)。 **与 `confirm` / `form` 的信息架构对齐(映射只读字段):** | 交互 | 参数区 | 映射主键(`inputMapping` + `readOnly`) | |------|--------|----------------------------------------| | `confirm` | 整区不画 | 不画;预设值仍提交 | | `form`(有「已选择」) | 只画用户要填/要改的字段 | **默认不画**;预设值仍提交 | | `form`(无「已选择」) | 正常画字段 | `readOnly` **只读展示**(如工具栏靠 lookup 手选作业) | - 需要用户在有「已选择」时仍看见某只读字段:该 property 加 **`revealInForm: true`**(逃生阀;不改变提交)。 - **禁止**用 `visibleWhen` 藏映射主键:`visibleWhen` 隐藏的字段**不进提交**、不校验 `required`,会弄断 `--id` / `{{job_id}}`。 - 非映射的只读说明字段(未出现在 `inputMapping`)在有摘要时**仍会显示**;仅映射上下文字段被默认折叠。 ## `replaces`(可选:接管默认新增 / 编辑 / 删除 / 导入) 有领域副作用的表(归一化字段、建目录、加密、级联清理)不要依赖宿主直写 SQLite。此时可为该表声明 `replaces`,**用户仍点宿主默认按钮**,由技能 CLI 完成写入。 ### 怎么选 | 场景 | 建议 | |------|------| | 纯表字段、无副作用 | 不配 `replaces`,宿主直写即可 | | 单条新增/编辑/删除有副作用 | `replaces: create` / `update` / `delete` | | 批量导入也有副作用(推荐) | 另配 `replaces: import`(与 create 可同表并存) | | 只有 create、暂无 import | 宿主「导入」面板确认后会**逐行调 create**(兼容);正式产品应补 `import` | ### 导入分流(必读) 点工具栏「导入」时: 1. **UI 一律**打开宿主「导入数据」面板(下载模板 → 选文件 → 映射 → 出错规则 → 预览)。 2. **写库**按表能力分流: - 有 `replaces: import` → 把选中文件路径 + 出错规则交给该 Action(技能解析并写入) - 仅有 `replaces: create` → 宿主按行调用 create(兼容) - 都没有 → 宿主直写 SQLite **不要**再做一张技能自己的「开始执行」导入表单来冒充导入体验。`placements` 对 import 保持 `[]`;`description` 写明「由宿主导入面板调用」。 可抄文件见 [`assets/examples/`](../assets/examples/):`replaces-*.action.example.json`。 ### 示例:create ```json { "id": "item-create", "label": "新增记录", "description": "新增业务记录并执行领域副作用(走技能 CLI,非宿主直写库)。", "replaces": "create", "placements": [], "executionProfile": "sync", "interaction": "form", "bind": { "tables": ["items"] }, "hostEffects": { "onSuccess": ["reloadTable"] }, "inputSchema": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "title": "名称" }, "note": { "type": "string", "title": "备注" } } }, "entrypoint": { "type": "cli", "command": "item", "args": ["create", "--name", "{{name}}", "--note", "{{note}}"] } } ``` ### 示例:update ```json { "id": "item-update", "label": "编辑记录", "description": "编辑业务记录并重算派生字段。", "replaces": "update", "placements": [], "executionProfile": "sync", "interaction": "form", "bind": { "tables": ["items"], "inputMapping": { "id": "$row.$pk", "name": "$row.name", "note": "$row.note" } }, "hostEffects": { "onSuccess": ["reloadTable"] }, "inputSchema": { "type": "object", "required": ["id", "name"], "properties": { "id": { "type": "number", "title": "编号", "readOnly": true }, "name": { "type": "string", "title": "名称" }, "note": { "type": "string", "title": "备注" } } }, "entrypoint": { "type": "cli", "command": "item", "args": ["update", "--id", "{{id}}", "--name", "{{name}}", "--note", "{{note}}"] } } ``` ### 示例:delete ```json { "id": "item-delete", "label": "删除记录", "description": "删除业务记录并清理关联副作用。", "replaces": "delete", "placements": [], "executionProfile": "sync", "interaction": "confirm", "confirmation": { "message": "确定删除该记录?关联数据将一并清理。" }, "bind": { "tables": ["items"], "inputMapping": { "id": "$row.$pk" } }, "hostEffects": { "onSuccess": ["reloadTable", "clearSelection"] }, "inputSchema": { "type": "object", "required": ["id"], "properties": { "id": { "type": "number", "title": "编号", "readOnly": true } } }, "entrypoint": { "type": "cli", "command": "item", "args": ["delete", "--id", "{{id}}"] } } ``` ### 示例:import ```json { "id": "item-import", "label": "导入记录", "description": "由宿主「导入数据」面板调用:解析 Excel/CSV 并写入业务表(含领域校验与派生字段)。", "replaces": "import", "placements": [], "executionProfile": "sync", "interaction": "form", "bind": { "tables": ["items"] }, "hostEffects": { "onSuccess": ["reloadTable"] }, "inputSchema": { "type": "object", "required": ["filePath"], "properties": { "filePath": { "type": "string", "title": "导入文件", "description": "由宿主导入面板传入的 Excel/CSV 路径。", "format": "file" }, "onError": { "type": "string", "title": "导入规则(出错时)", "enum": ["跳过该行,继续导入", "立即停止,不再导入后续行"], "default": "跳过该行,继续导入" } } }, "entrypoint": { "type": "cli", "command": "item", "args": ["import", "--file", "{{filePath}}", "--on-error", "{{onError}}"] } } ``` 规则: - **全可选**:无 `replaces` 时,宿主默认按钮仍直写库,行为与以前一致。 - 取值:`create`(新增)/ `update`(编辑)/ `delete`(删除)/ `import`(导入写库)。 - **必须**非空 `bind.tables`;同一技能同一表同一 `replaces` 至多一个 Action。 - 与 `placements` 正交:可只接管默认按钮(`placements: []`),不必再挂 toolbar「第二个新增/导入」。 - 点默认「新增/编辑/删除」时走该 Action 的 `interaction`(create/update 常用 `form`;delete 常用 `confirm`)。 - 点默认「导入」时**固定**打开宿主导入面板;确认后若有 `replaces:import` 则调用该 Action(`interaction` 不决定是否再弹技能表单)。 - `update` / `delete` 可用 `inputMapping`(`$row.$pk`、`$selection.ids`)预填主键。 - `import` 约定:`filePath`(或 `file`)+ 可选 `onError`(`skip`/`abort` 或上例中文枚举);技能自行解析文件与表头。 - 整表 `_jiangchang_tables.readonly=1` 时仍隐藏写按钮(优先于 `replaces`)。 - **列元数据复用(lookup)**:Action 表单(含 toolbar/batch 弹窗与 create/update 抽屉)可将 `inputSchema` 字段对齐业务列,复用中文名与 lookup 下拉: - 优先让属性名 = 物理列名(如 `platform_key`); - 或 `bind.columnMapping`:`{ "platform": "platform_key" }`; - 或字段上写 `"column": "platform_key"`。 - 当前表没有对应列时,用字段内联 `lookup`(见下文「跨表引用:Lookup」)。 - **不要**在 actions.json 里写死会过期的全量平台 `enum`,也**不要**让用户手填内部编号。 ## `interaction`(点击交互方式) 与 `executionProfile` **正交**:一个 Action 可以是 `async` + `direct`(一点就进任务中心),也可以是 `sync` + `form`(填参后当场返回)。 | 值 | 数据管理体验 | 典型场景 | |----|--------------|----------| | `direct` | **一点即跑**,无弹窗 | 打开目录、打开浏览器、无参只读、行内主键已映射 | | `confirm` | 轻量确认弹窗(无参数区;文案优先 `confirmation.message`;有勾选时宿主展示「已选择」摘要;映射主键仍提交) | 处理待发送、删除前确认、有副作用但无可编辑入参 | | `form` | 完整参数表单(有勾选时同样展示「已选择」摘要;**映射 `readOnly` 默认不占参数区**,见「选择摘要」) | 导入、选文件、查询条件、需用户改写的字段 | | `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": "打开浏览器", "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": ["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/预填,作者须自觉。 ## Action 按钮中文名(`label`,参考) `label` 是数据管理 **toolbar / row / batch** 以及技能详情直调上用户看见的短按钮名。本节是**参考契约,不是白名单**:行业专有动作(私信、审单、报关登记等)按业务起名。同类通用动作尽量用同一动词,避免每个技能一套叫法。 **写法:** 短中文,动词或「动词 + 对象」(约 2~8 字)。同一能力用**一个** Action 换 `placements`,不要为入口复制同义按钮。 以后增补:在下表加行,并在模板 `CHANGELOG.md` 记一笔。废弃说法挪到「不要叫」,不要直接删行。 ### 宿主已有,技能不要再做一套同义按钮 数据管理宿主已提供:**查看 / 编辑 / 删除 / 新增 / 导入 / 导出 / 刷新**。 技能要用 CLI 接管增删改导入时用 `replaces`(见上文),**不要**再把同义按钮挂到 `placements: ["row"]` 或 `toolbar`(例如「编辑平台」「删除 Key」与底下宿主「编辑」「删除」叠两套)。用户仍点宿主那几个字。 ### 建议动词(有则用之) | 类型 | 建议 `label` | 不要叫 | 说明 | |------|----------------|--------|------| | 基础设施 | 运行检查、查看版本、查看配置位置、初始化数据 | 健康检查、版本信息、配置路径、初始化数据库 | 多挂 `skill-detail` | | 打开 | 打开浏览器、打开目录、打开文件 | 打开文件位置、在资源管理器中打开、用浏览器打开 | 见下节静默成功 | | 改库 | 新增…、编辑…、删除…、启用/停用、设置默认…、加入待…、重置为待… | 表记录用「添加」;无必要拆成「启用」「停用」两个按钮 | 对象名可带,如「新增账号」 | | 外源入库 | 同步数据、全量同步、采集…、扫描… | 把宿主「刷新」叫成同步;把导出 CSV 叫成同步 | 默认用「同步数据」;全量才写「全量同步」 | | 队列与探测 | 处理待…、测试 API、测试网页、抽检本条、抽检所选、批量抽检 | 行 / 勾选 / 工具栏三个入口都叫「抽检」 | 行=本条,勾选=所选,不管勾选的批量=批量 | | 生成发布 | 生成 HTML、生成…、发布到… | 用「导出」代替宿主表导出 | 固定 id `render-html` 的 label 用「生成 HTML」 | ### 打开类:一点即开,成功不提示 打开目录 / 打开浏览器 / 打开文件:`interaction: "direct"`,`executionProfile` 通常 `"sync"`。 - **成功**:不要输出用户可见成功文案。若打印 JSON,令 `"message": ""`(宿主据此不弹「执行成功」)。操作系统已经弹出目录或浏览器,就是反馈。 - **失败**:仍 `ERROR::`(可加 `HINT:`)。 - **不要**用宿主「查看」表示打开目录或浏览器——「查看」是只读详情。 ## `hoverHint`(工具栏悬停,可选) 与 `label` / `description` / `confirmation.message` **独立**。只决定数据管理**顶栏技能按钮**鼠标悬停时显示什么。 - **省略 / 空白** ⇒ 不显示悬停,**不回退**灰色说明或确认黄条。 - 短按钮名已经够懂时**不要写**,避免噪音。适合:业务黑话、会覆盖本机/远程、不点进去看不出后果。 - 悬停文案不要写成「确认开始?」;那是点下去之后黄条的口吻。 - 当前宿主实现为**工具栏技能按钮**;宿主内置删除/刷新/新增/导入/导出不走本字段。 - 需宿主已支持 `hoverHint`;更旧宿主会忽略未知字段(模板 Schema 仍会校验新技能)。 ```json { "id": "kb-sync-feishu", "label": "拉取到本机", "description": "把飞书知识库覆盖到本机工作区。未发布的本地修改会丢失。", "hoverHint": "将用飞书覆盖本机知识库,未发布的本地修改会丢失。", "placements": ["toolbar"], "executionProfile": "async", "interaction": "confirm", "confirmation": { "message": "将用飞书覆盖本机知识库,未发布的本地修改会丢失。确认开始?" }, "bind": { "tables": ["kb_sources"] } } ``` ## `hostEffects` / `steps`(执行前后宿主编排,≥2.8.5) 与 `placements` / `executionProfile` / `interaction` **正交**。决定的是:数据管理里点完 Action 之后(或之前),宿主要不要**刷新当前表、刷新目录、清空多选、级联删子表**等。 - **省略 / 空** ⇒ 宿主**不**额外改 UI(≥2.8.5 起不再对 mutation 硬编码刷新)。本地库若已由技能写新,界面仍可能旧,直到用户点内置「刷新」或本 Action 声明了 `reloadTable`。 - **仅数据管理交互路径生效**(toolbar / row / batch / skill-detail / `replaces`);**cron 与 agent 忽略**(与 `interaction` 相同)。 - 需匠厂宿主 **≥ 2.8.5**;更旧宿主会忽略未知字段(若未做严格 Schema 校验)。 - **新技能优先写 `steps`**;`hostEffects` 仍可作为「只刷 UI」的简写。两者可并存;同一 UI 效果若两边都声明,宿主可能各跑一次。 ### `hostEffects`(UI 副作用简写) ```json { "hostEffects": { "onSuccess": ["reloadTable"], "onFailure": ["clearSelection"] } } ``` | 字段 | 触发时机 | |------|----------| | `onSuccess` | **sync** 退出码 0;或 **async** 任务已被任务中心**接收**(不是 Job 跑完) | | `onFailure` | **sync** 失败;或 **async** **启动被拒** | 闭集 effect id:`reloadTable`(重载当前表 + 常伴随 Data Policy 再拉)、`refreshCatalog`(刷新技能目录)、`clearSelection`(清空多选)。未知 id 丢弃。 **async 注意**:`sync-data` 一类长同步若写 `onSuccess: ["reloadTable"]`,会在**入队成功时**就刷表,可能仍是旧数据;更适合 sync 的新建/登记,或等 Job 真正完成后再刷(若宿主后续扩展完成回调)。 ### `steps`(有序编排,推荐) ```json { "steps": [ { "when": "before", "do": "deleteByFk", "table": "child_rows", "fk": "parent_id" }, { "when": "afterSuccess", "do": "reloadTable" }, { "when": "afterFailure", "do": "clearSelection" } ] } ``` | `when` | 含义 | |--------|------| | `before` | 调用 CLI **之前** | | `afterSuccess` | 同 `hostEffects.onSuccess` | | `afterFailure` | 同 `hostEffects.onFailure` | | `do` | 说明 | |------|------| | `reloadTable` / `refreshCatalog` / `clearSelection` | 同 hostEffects | | `deleteByFk` | 按外键批量删子表;须 `table` + `fk`;行按钮会把当前行主键当父键 | | `deleteRows` | 删指定表行;须 `table` | 规则:单次最多 **8** 步;未知步骤丢弃;`deleteByFk` / `deleteRows` 仅走 Skill Action steps 路径时可删 `metadata_readonly` 投影表(人工 CRUD 仍守只读)。整库只读 / 视图仍禁止写。 典型:业务表「新建 / 登记 / 更新」成功后加 `{ "when": "afterSuccess", "do": "reloadTable" }`,避免用户傻等旧表格。 ```json { "id": "register-one", "label": "登记本行", "description": "更新当前行业务字段。", "placements": ["row"], "executionProfile": "sync", "interaction": "form", "bind": { "tables": ["business_records"], "inputMapping": { "recordId": "$row.$pk" } }, "steps": [{ "when": "afterSuccess", "do": "reloadTable" }], "entrypoint": { "type": "cli", "command": "register", "args": ["--id", "{{recordId}}"] } } ``` 默认模板 `actions.json` 仅含 `health` / `version` / `config-path`,不放在 toolbar/row/batch,故无需 `bind` 示例污染默认 manifest。业务示例: ```json { "id": "sync-records", "label": "同步记录", "description": "从外部来源同步到技能本地数据库,不生成导出文件。", "placements": ["toolbar", "cron", "agent", "skill-detail"], "executionProfile": "async", "bind": { "tables": ["business_records"] }, "entrypoint": { "type": "cli", "command": "sync-records", "args": [] } } ``` 行内示例: ```json { "id": "retry-one", "label": "重试本行", "description": "对当前行执行一次业务重试。", "placements": ["row"], "executionProfile": "async", "bind": { "tables": ["business_records"], "inputMapping": { "recordId": "$row.$pk" } }, "inputSchema": { "type": "object", "required": ["recordId"], "properties": { "recordId": { "type": "string", "title": "记录 ID", "readOnly": true } } }, "entrypoint": { "type": "cli", "command": "retry", "args": ["--id", "{{recordId}}"] } } ``` ## 预留能力(当前不可当作已实现能力) | 类别 | 预留项 | 说明 | |------|--------|------| | action 字段 | `concurrency`、`locks` | 并发与锁控制,未稳定开放 | | inputSchema | `array`、nested `object`、`oneOf` 等 | 复杂表单,不属于当前推荐范围 | `bind`、`row`、`batch` **不是**保留能力;已按匠厂宿主正式支持写入本规范。 ## 执行模型 宿主将 action 转为 argv,再用**共享 Python**(用户数据目录下 `python-runtime/.venv`)执行: ```text python {skill_root}/scripts/main.py ``` - `entrypoint.type` 当前只支持 `cli` - `entrypoint.command` → CLI 子命令(须真实存在) - `entrypoint.args` → 子命令参数(**必须为 JSON 数组**) - `{{fieldName}}` → 来自 `inputSchema` 用户输入的简单模板替换 完整子命令定义见 [`scripts/cli/app.py`](../scripts/cli/app.py)。 ### `executionProfile`(仅 sync / async) **心智**:多数 Action 按 **sync**(短调用、不进任务中心);只有长耗时 / RPA / 需进度控制时才标 **async**。匠厂宿主若**省略**该字段,按 **sync** 处理;模板与新技能仍要求**每个 Action 显式写出** `sync` 或 `async`,禁止发明 `auto` / `background` / `deferred`,也禁止赌省略行为。 | 值 | 宿主行为 | 任务中心 | |----|----------|----------| | `sync`(或省略时的宿主缺省) | 调用方等待结束,直接返回结构化结果或结构化错误;**不**创建后台 Job | 不进 | | `async`(须显式声明) | 宿主创建后台 Job,立即返回 `jobId`;用 `emit` / `checkpoint` / `finish` 报告进度与终态 | **进入** | 合法组合(全部允许):`sync|async` × `toolbar|row|batch|cron|agent|skill-detail`。 经验建议(**不是** placement 硬限制):耗时不可预测、需要暂停/停止/进度展示的任务,用 `async`;其余默认写 `sync`。 直接执行短 CLI 时,终端自然等待进程结束。宿主通过 Action 调用同一 CLI 时,再按 `executionProfile` 决定同步等待还是建 Job。技能**不得**自行实现第二套任务中心或后台队列。 ### CLI 输出契约 技能 CLI 必须: - 使用稳定的退出码 - 成功时输出可解析的结构化结果 - **例外(打开类)**:打开目录 / 打开浏览器 / 打开文件成功时不要输出用户可见成功文案;若打印 JSON,令 `"message": ""`。失败仍走下一款错误约定 - 失败时输出稳定错误信息 - 对可预期错误提供稳定错误码 - 对用户可处理的问题提供 `HINT` - 不依赖 Agent 连续对话才能完成 - 不自行实现宿主任务中心 - 不在 CLI 中硬编码宿主安装目录 - 不直接依赖某个具体 UI 页面 建议错误输出格式: ```text ERROR:: HINT: ``` ## placements(入口位置) | 值 | 含义 | |----|------| | `toolbar` | 数据管理表顶栏技能按钮(须配合 `bind.tables`) | | `row` | 数据管理**行级入口**(建议 `bind.tables` + `inputMapping`;表需有主键)。技能声明后,由宿主呈现为操作列链接、**行右键菜单**和/或行内「⋯」等壳;**不**新增 `row-context-menu` 等 placement | | `batch` | 数据管理批量操作(需勾选行;无勾选时宿主禁用按钮) | | `skill-detail` | 技能详情页直调(与安装/四 Tab 同属技能市场详情;已声明则须自测) | | `agent` | Agent 可直接调用(`run_skill_action`) | | `cron` | 定时任务「技能直调」 | ### 用户可见性与教程准确性 `placements` 决定 Action **可以**出现在哪;教程只能写用户在宿主里**实际能点到**的路径。完整用户文档契约见 [`../development/TUTORIAL_SURFACES.md`](../development/TUTORIAL_SURFACES.md)。 | 规则 | 说明 | |------|------| | **skill-detail ≠ 一定可见** | 声明 `skill-detail` 不保证详情页渲染出按钮;当前宿主详情常只有说明/教程/演示/更新日志四 Tab。**禁止**把「技能详情 → 点某某」写成用户主路径或教程唯一入口 | | **准备 / 载入 / 导入 / 重置** | 若技能以数据管理为主路径,此类能力优先 `toolbar` + 非空 `bind.tables`(空表也可点的准备动作尤其如此);可同时挂 `agent`;`skill-detail` 仅作可选兼容 | | **写入后刷新** | 会写入业务表的 Action 建议 `steps: [{ "when": "afterSuccess", "do": "refreshCatalog" }]`,避免用户点完表不更新 | | **教程按钮名** | 必须等于本文件 `label`;对照表与数据管理顶栏/行内文案一致 | | **README vs 教程** | 说明 Tab 不写分步;操作步骤只写 `TUTORIAL.md` | ### 行级入口 `row`(宿主呈现契约) - **技能决定有什么**:`placements` 含 `"row"`(且通过 `bind.tables` / policy 等过滤)的 Action,构成该表行的技能菜单项集合;配什么就出现什么。 - **宿主决定怎么摆**:可用操作列、行右键、行内「⋯」等呈现**同一批** Action;点击均走同一 `launch`(`placement: "row"`),`interaction` / `executionProfile` 与壳无关。 - **顺序**:各壳内技能项顺序 = 下方「数据管理技能按钮顺序」契约(`actions` 数组相对顺序)。 - **宿主内置项**:查看 / 编辑 / 删除由宿主提供,**不是**技能 `actions.json` 配置出来的;若出现在行右键,须与技能项分组(例如分隔线后),且不得要求技能再配一份菜单 DSL。 - **禁止**:为右键单独发明 placement、菜单注册表或第二套 Action 列表。 ### 数据管理技能按钮顺序(宿主正式契约) 宿主**没有**单独的 `sortOrder` / `uiOrder` 字段。同一 `placement`(`toolbar` / `row` / `batch`)下,技能按钮的展示顺序 = `actions` 数组中**匹配该项 placement(且通过 `bind.tables` 过滤)的相对先后**。 规则: 1. 想调整顺序 → **调整 `actions` 数组先后**(不必改 `id` / `label`)。 2. 无关 placement 的 Action(如仅 `skill-detail` 的 `health`)夹在中间**不影响** toolbar/row 的相对顺序;宿主按匹配子集保序渲染。 3. 同一 Action 可同时挂多个 placement;它在各入口的位置,分别是该入口匹配子集里的相对位置。 4. **宿主内置按钮不参与本契约、位置固定**: - 表顶栏右侧:删除(有勾选时)/ 刷新 / 新增 / 导入 / 导出 —— 技能 toolbar/batch 按钮在左侧,不能与右侧内置按钮混排。 - 行级:技能 `row` 项按本顺序出现在操作列 / 行右键(及可选「⋯」)中;宿主「查看」→(非只读)「编辑」→「删除」为内置项,位置由宿主固定(操作列常在技能项之后;右键中可与技能项分组)。 5. **表顶栏全部直接展示,无「更多操作」溢出菜单**:匹配到的 toolbar/batch 技能按钮一律渲染(顺序仍按本契约)。**行级**不以「顶栏溢出菜单」模型截断;行右键是 `row` 的正式呈现壳之一,不是 toolbar 式「更多操作」截断。 6. **表顶栏宽窄布局(宿主行为,技能无需配置)**: - 够宽:左右各横排一行(左技能按钮 + 右宿主工具)。 - 不够宽:保持左右分栏,栏内按钮上下竖排(左栏技能、右栏刷新/导出等)。 禁止: - 臆造 `sortOrder` / `order` / `priority` 等字段指望宿主排序(模板 Schema `additionalProperties: false` 会拒收)。 - 臆造 `tooltip` / `title` 等字段当悬停(用可选 `hoverHint`;不要把 `confirmation.message` 复用成悬停)。 - 依赖或宣传表顶栏「更多操作」溢出菜单(宿主已取消该能力)。 - 把「表格行数据默认排序」与按钮顺序混淆——行排序见 [`SCHEMA.md`](SCHEMA.md) 的 `default_sort_json`。 - 把「默认展示列」与 `visible=0` 硬隐藏混淆——默认展示列见 [`SCHEMA.md`](SCHEMA.md) 的 `default_visible_columns_json`。 - 新增 `row-context-menu` 等 placement,或要求技能再维护一份与 `row` 平行的菜单清单。 ### Action 类型 × placements(常见示例,不是限制) 下表只表示**常见用法示例**。实际 `placements` 以 manifest 配置为准;**`sync` / `async` 不影响展示入口**。已声明的入口都要在宿主自测,不要只测其中一条。 | Action 类型 | 常见 executionProfile | 常见 placements | confirmation | |-------------|----------------------|-----------------|--------------| | health / version / config-path | sync | skill-detail, agent | 无 | | 统计 / 只读查询 | sync | skill-detail, agent | 无 | | 数据同步(外源 → 本地库) | sync 或 async | toolbar + cron + agent + skill-detail(按需) | 可选 | | 单行重试 / 行内处理 | sync 或 async | row(可加 agent) | 可选 | | 批量处理勾选行 | sync 或 async | batch(可加 toolbar/cron) | 可选 | | 导入 / 重置 | sync 或 async | **优先** toolbar(须 `bind.tables`)+ agent;skill-detail 仅可选 | 可选 | | 单条副作用 RPA | async(建议) | agent, skill-detail | **建议** | | 批量顺序 pick RPA | async(建议) | toolbar, cron, agent | **建议** | ## 同步数据 vs 导出当前表 vs 特殊业务报告 vs HTML 页面 不要混用「导出」一词: | 能力 | 语义 | 谁负责 | |------|------|--------| | **同步数据**(sync / refresh / collect) | 外部来源 → 技能本地数据库 | 技能 Action / domain service | | **导出当前数据表** | 技能本地数据库 → CSV/Excel 等 | 数据管理页**宿主统一导出** | | **生成特殊业务报告(文件)** | 多表合并、特殊排版或附加计算的 **xlsx/csv/json 等文件** | 仅当系统导出无法满足时,才提供独立 `export-*` Action;文件落 `output/` | | **HTML 页面报告** | 可在数据管理侧栏预览的整页 HTML | 技能写入 `{skill_data}/html/*.html` + Action id **`render-html`**(见下节) | 同步型 Action 默认:读外部数据、写技能自己的库、**不**生成 CSV/Excel/JSON 文件、不重复实现宿主表导出。兼容旧 export 命令若需要刷新数据,必须调用同一个 sync 业务内核。通用同步语义见 [`SCHEMA.md`](SCHEMA.md) 与 [`../development/SKILL_ACTION_RUNTIME.md`](../development/SKILL_ACTION_RUNTIME.md)。 ## HTML 页面报告(数据管理预览) > 一句话:**技能负责生成并覆盖 `html/*.html`;宿主负责发现、预览、用 `render-html` 触发重新生成。** ### 何时用 - 需要**可浏览的整页报告**(KPI + 表格 + 自带样式),而不是再导出一张表 - 与 `export-*` / 宿主表导出**并列**:HTML 走 `html/`;文件型特殊报告仍走 `output/` + `export-*` ### 目录与发现(宿主契约) | 项 | 约定 | |----|------| | 路径 | `{skill_data}/html/*.html`,**只扫一层**(子目录忽略) | | helper | `util.runtime_paths.get_html_dir()`;写入用 `service.html_page_service.write_html_page` | | 侧栏名 | 取文件内 ``(缺省则用文件名) | | 预览 | 宿主 iframe 预览;右键:重新生成 / 浏览器打开 / 刷新 | | 模板资源 | 建议放 `assets/html/*.html`(安装包内),渲染后**写出**到数据目录 `html/` | ### Action:`render-html`(固定 id) 宿主「重新生成」**硬编码**查找 Action id `render-html`(见 `service.html_page_service.HOST_HTML_RENDER_ACTION_ID`)。技能未提供该 id → 提示「该技能未提供生成 HTML」。 推荐形态: | 字段 | 建议 | |------|------| | `id` | **必须** `render-html` | | `executionProfile` | `sync` | | `interaction` | `direct` | | `placements` | `toolbar` + `skill-detail`(配合表顶栏与 HTML 右键);可选 `agent` | | `bind.tables` | 数据**来源表**(含 `toolbar` 时必填);宿主从 HTML 页重新生成时会用 bind 补齐跑表上下文 | | `steps` | `afterSuccess` → **`refreshCatalog`**(重扫 `htmlPages`;不要只 `reloadTable`) | | `entrypoint` | CLI 子命令(如 `render-html`)调用同一写入内核 | 完整 JSON 形状见 [`../assets/examples/render-html.action.example.json`](../assets/examples/render-html.action.example.json)。最小页面壳见 [`../assets/html/example-report.html`](../assets/html/example-report.html)(已含数据管理 iframe 预览的布局基线;复制后可按需扩展 `{{BODY}}` 内结构)。 ### 宿主预览布局 数据管理在 **iframe** 内预览 `html/*.html`;宿主 iframe 已占满右侧预览区,**技能页不应再人为收窄正文**(尤其数据管理窗口通常较窄)。 | 级别 | 约定 | |------|------| | **必须** | 页面横向**铺满** iframe 可用宽度:`main { width: 100%; min-width: 0; }` | | **必须** | **禁止** `main`(或等价正文容器)使用 `max-width: Npx` 且**无** `margin: 0 auto`(宽屏会左对齐留白;窄宿主窗浪费横向空间) | | **必须** | `html` / `body` 设 `overflow-x: hidden`;滚动以**纵向**为主,避免横向滚动条 | | **必须** | 写入 `{{BODY}}` 的业务数据经 `html.escape`(或等价)转义,防 XSS | | **应当** | `header` 与 `main` 水平内边距一致(如共用 CSS 变量或 `clamp()`) | | **应当** | 多列表格:`width: 100%` + 单元格 `word-break` / `overflow-wrap`,长文本在格内换行 | | **应当** | KPI / 卡片网格用 `repeat(auto-fit, minmax(…, 1fr))` 等流体列,勿死写固定列数 | | **禁止** | 在 `<td>` 上用 `max-width` 限制「说明列」等业务列(易导致列宽异常或横向溢出) | | **禁止** | 依赖外链 CSS/JS 或 `networkidle` 式加载(iframe `sandbox` 仅 `allow-scripts`;页面应自包含) | 长文阅读型报告若确需版心,可对整个 `main` 使用 `max-width` **且** `margin: 0 auto`,并令 `header` 与 `main` 同宽;**数据看板类**默认不设 `max-width`。 ### 实现要点 1. 用 `load_html_template` + `render_html_placeholders` + `write_html_page`(原子 `*.tmp` + `os.replace`)覆盖同名文件;**不要**每次另存带时间戳的副本(除非产品明确要求历史版本)。 2. 页头时间建议用**写出时刻**(「报告生成于」),不要误用业务表里过期的 `generated_at` 冒充「刚重新生成」。 3. 可选:数据重算成功后调用静默写入(吞掉错误),保持侧栏报告与库一致。 4. 权限:按业务决定谁能生成(管理员 / 角色);在 CLI/`cmd_render_html` 内强制校验。 5. CHANGELOG / 用户说明:模板或样式升级后,提醒「请在数据管理对报告执行一次重新生成」。 ### 验收(复制后自测) - [ ] `html/` 下出现 `.html`,数据管理侧栏能看到报告项,预览正常 - [ ] 预览区**横向铺满**、**无横向滚动条**;拖窄侧栏后内容自适应(见上节「宿主预览布局」) - [ ] 表顶栏(若声明 toolbar)或报告右键「重新生成」能跑通;成功后预览刷新 - [ ] 未登录/无权限用户被拒绝,且有可读错误 - [ ] 成功路径带 `refreshCatalog`(或等价让侧栏/预览看到更新) ## inputSchema 与参数表单 ### 跨表引用:Lookup(宿主已支持;勿让用户填编号) 当 Action 参数要引用**同库另一张业务表**的一行(话术模板、平台、账号等)时,必须用 Lookup 下拉,**禁止**做成「请填写编号」的数字框(用户记不住内部 id,体验差)。 | 场景 | 怎么配 | 说明 | |------|--------|------| | 当前表已有同含义列,且列上已是 CRUD lookup | `column` / `bind.columnMapping`,或属性名 = 列名 | toolbar/batch 弹窗与 create/update 抽屉都会复用该列已解析的选项 | | 当前表没有该列(如在「潜客」表上选「话术模板」) | `display_type: "lookup"` + 字段内联 `lookup` 对象 | 打开表单时宿主按当前技能库解析选项 | 内联 `lookup` 与列元数据 `options_json` **同构**(见 [`SCHEMA.md`](SCHEMA.md)「跨表引用 lookup」): ```json { "templateId": { "type": "number", "title": "话术模板", "description": "仅在正文留空时生效;选 -- 表示自动挑选。", "display_type": "lookup", "lookup": { "ref_table": "message_templates", "value_column": "id", "label_column": "name", "extra_column": "channel", "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 } } } ``` | 字段 | 必填 | 说明 | |------|------|------| | `ref_table` | 是 | 同库被引用表 | | `value_column` | 是 | 提交给 CLI 的值(业务 id / 编号) | | `label_column` | 是 | 主文案列 | | `extra_column` | 否 | 第三展示列(任意列名,不限「备注」) | | `filter_column` / `filter_value` | 否 | 等值过滤 | | `multiple` | 否 | `true` = 多选;省略/`false` = 单选(默认) | **下拉/选项展示(宿主统一,技能只配列):** 单选为 `{value} · {label}`;有非空 `extra_column` 时为 `{value} · {label}({extra})`。例:`2 · 通用私信-留资`、`2 · 通用私信-留资(dm)`。 **多选存值与模板:** 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)。 ### 表单字段类型对照(须与 CRUD 对称) Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件语义**。CRUD 侧配置见 [`SCHEMA.md`](SCHEMA.md)「表单与展示:稳定字段类型」;本表只写 Action 怎么配。 | 控件语义 | Action `inputSchema` 怎么写 | CRUD 怎么写(对照) | |----------|----------------------------|---------------------| | 单行文本 | `type: string` | `display_type: text` 或缺省 | | 多行文本 | `display_type` 或 `format`: `textarea` | `display_type: textarea` | | JSON 长文本 | `type: object`/`array`(以 JSON 字符串编辑)或 `textarea` | `display_type: json` / `textarea` | | 数字 | `type: number` | `display_type: number` 或 INT/REAL/NUMERIC | | 开关 | `type: boolean` | `display_type: boolean` | | 枚举下拉 | `enum: [...]` | `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 秒** | | URL(薄层) | `display_type` 或 `format`: `url` | `display_type: url` | | Email(薄层) | `display_type` 或 `format`: `email` | `display_type: email` | | **本地文件路径** | `display_type`/`format`: **`file`**;可选 **`pathRoot`** | CRUD 一般不用;见下节 | | **本地目录路径** | `display_type`/`format`: **`directory`**;可选 **`pathRoot`** | CRUD 一般不用;见下节 | | 说明灰字 | `description` | `_jiangchang_columns.description` | | 表格横幅 / 列头「?」 | (无;Action 不用) | `_jiangchang_tables.grid_help` / `_jiangchang_columns.grid_help` | | 默认值 | `default` | SQLite 列 `DEFAULT` | | 只读 | `readOnly: true` | 列 `editable: 0` / 宿主只读规则 | | **条件显隐** | **`visibleWhen`**(见下节) | **本版 CRUD 无对称**;仅 Action / 定时任务参数表单 | | **列筛选「值」** | **无对称**(Action 无表格筛选) | 见 [`SCHEMA.md`](SCHEMA.md)「数据管理:列筛选与全局搜索」:枚举 / boolean / lookup 在筛选栏用**下拉 Select**(非开关、非分段单选);存库仍用 value,展示与搜索认 label | **本版不做(勿宣传):** 浏览器内文件上传控件、复杂 `oneOf` / nested object 专用表单;`visibleWhen` 多条件 AND/OR 树、跨 Action 依赖、CRUD 新增/编辑对称显隐;Action 参数表单的列筛选对称。 (**Lookup 多选**已支持:同一 `lookup` + `multiple: true`,见上文;不是任意标签数组。) (**本地路径选择**已支持:`file` / `directory` + 可选 `pathRoot`,见下节;提交值仍是本机绝对路径字符串,不是 multipart 上传。) 日期时间约定: - Action 提交与 CRUD 写库统一用 Unix **秒**整数(与 SCHEMA 标准时间字段一致) - `date` 只表示日历日,值为 `YYYY-MM-DD` - 脏/无法归一的旧预设:日期控件可回退为普通文本,避免卡死表单 ### 本地路径选择与 `pathRoot` 用于数据管理 / 定时任务 **Action 参数表单**里选本机文件或目录(原生对话框)。与宿主工具栏内置「导入」面板(默认打开本技能 `input/`,见 [`../development/DATA_PATHS.md`](../development/DATA_PATHS.md))是**两条 UI 路径**,不要混为一谈。 | 字段 | 含义 | |------|------| | `display_type` / `format`: `file` \| `directory` | 渲染「选文件 / 选文件夹」控件;提交值为本机**绝对路径**字符串 | | **`pathRoot`(可选)** | 对话框**起始目录**,相对**当前技能数据根** `{JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/` | 宿主行为(匠厂已支持): | `pathRoot` | 打开对话框时 | |------------|--------------| | 省略 / 空 | `{skill_data}/input` | | `input/库存数据` 等 | `{skill_data}/` + 该相对路径 | | `output` / `output/…` | 落在技能 `output` 下 | | 裸 `库存数据` | 视为 `input/库存数据` | | 绝对路径、盘符、`..` | 非法 → 回退 `{skill_data}/input` | 硬规则: 1. **禁止**写用户 ID、盘符、绝对路径;用户与数据根由宿主按当前登录解析。 2. **`description` 只给人看**,宿主**不会**解析文案来决定起始目录;要默认进子目录必须写 `pathRoot`。 3. Agent / CLI **忽略** `pathRoot`(仍直接传路径字符串)。 4. 输入框**已有路径**时,宿主优先用已有值作为起始(便于再次浏览)。 ```json { "id": "refresh-inventory", "label": "刷新库存数据", "description": "从输入目录选择 JSON 快照并更新库存。", "placements": ["toolbar", "agent"], "executionProfile": "sync", "interaction": "form", "bind": { "tables": ["sku_inventory"] }, "inputSchema": { "type": "object", "required": ["file"], "properties": { "file": { "type": "string", "title": "库存数据文件", "description": "请选择 input/库存数据/ 下的 JSON。", "format": "file", "display_type": "file", "pathRoot": "input/库存数据" } } }, "entrypoint": { "type": "cli", "command": "refresh-inventory", "args": ["--file", "{{file}}"] } } ``` 形状参考:[`assets/examples/path-root-file-action.example.json`](../assets/examples/path-root-file-action.example.json)。 ### 当前宿主稳定支持(摘要) - 根:`object`;属性:`string` / `number` / `boolean`(`object`/`array` 仅作 JSON 多行编辑兼容,新技能慎用) - `enum` / `default` / `required` / `sensitive` / `readOnly` / 可选 `revealInForm` - `display_type` / `format`:`textarea` | `date` | `datetime_unix_seconds` | `url` | `email` | **`lookup`** | **`file`** | **`directory`** - 可选 **`pathRoot`**(仅 `file` / `directory`):相对技能数据根的起始目录;省略默认 `{skill_data}/input` - 字段内联 `lookup`(与 CRUD `options_json` lookup 对象同构;可选 `extra_column`、`multiple`) - 属性级 `description`(表单标签下方灰字说明) - 简单模板替换(`{{fieldName}}`) - 可选空数字字段提交时由宿主省略,避免 `""` 触发 number 校验失败 - **字段条件显隐 `visibleWhen`**(可选):依赖同表单其它字段值决定是否展示;隐藏字段不进提交、不校验 `required` - **选择摘要下的映射只读折叠**:`form` +「已选择」时,`inputMapping` 映射且 `readOnly` 的字段默认不渲染(仍提交);`revealInForm: true` 可强制显示;**勿**用 `visibleWhen` 代替 ### 字段条件显隐(`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` 灰字写「仅某某时填写」。 **与数据管理「新增 / 编辑」表单的分工(勿混用配置入口):** | 表单 | 短标题 | 说明灰字 | 默认值 | 控件类型 | |------|--------|----------|--------|----------| | Skill Action 弹窗 | `inputSchema.title` | `inputSchema.description` | `inputSchema.default` | `type` + `display_type`/`format` | | 数据管理新增/编辑 | `_jiangchang_columns.display_name` | `_jiangchang_columns.description` | 物理列 SQLite `DEFAULT` | `display_type` + 物理类型推断 | 数据管理**表格**上方说明与列头「?」用 `_jiangchang_tables.grid_help` / `_jiangchang_columns.grid_help`(空则不渲染),**不要**用 `description`。见 [`SCHEMA.md`](SCHEMA.md)「表格说明 `grid_help`」。 CRUD 侧宿主义务与禁止事项见 [`SCHEMA.md`](SCHEMA.md)「表单与展示:稳定字段类型」「新增/编辑表单:字段说明与默认值」。同一业务含义两边文案与控件应对齐,但分别配置。 示例: ```json { "inputSchema": { "type": "object", "required": ["query"], "properties": { "query": { "type": "string", "title": "查询条件", "description": "支持关键词或完整业务编号" }, "limit": { "type": "number", "title": "返回数量", "description": "最多返回多少条;填 0 表示不限制(若业务支持)", "default": 50 }, "dueDate": { "type": "string", "title": "截止日期", "display_type": "date", "description": "日历日,格式 YYYY-MM-DD" }, "publishAt": { "type": "number", "title": "发布时间", "display_type": "datetime_unix_seconds", "description": "提交值为 Unix 秒" }, "homepage": { "type": "string", "title": "主页链接", "display_type": "url" } } } } ``` 批量 pick 参数约定(有队列表时): ```json { "pickCount": { "type": "number", "title": "处理数量", "description": "从队列选取「可操作」状态记录的条数(具体状态名由本技能定义,见 WORK_ITEM_STATUS)" } } ``` 默认**不要**把失败/成功/跳过混进该 pick;重做靠人工改回可操作态。详见 [`WORK_ITEM_STATUS.md`](WORK_ITEM_STATUS.md)。 若还需要默认篇数,可在同一 `properties` 里为 `pickCount` 增加 `"default": 1`、`"minimum": 1`。 约定: - `boolean` 在宿主中渲染为开关 - `enum` 在宿主中渲染为选择框 - `type: number` 渲染为数字输入 - `display_type`/`format: date` 渲染为日期(日);`datetime_unix_seconds` 渲染为日期时间并按 **Unix 秒**提交 - `display_type`/`format: url` / `email` 为薄层输入类型(校验从宽,勿当强校验网关) - `required` 字段在提交前校验 - `sensitive` 字段不得出现在低信任入口 - `readOnly: true`:数据管理表单中标记为只读;**有「已选择」且该字段来自 `inputMapping`(或无 mapping 时的约定 id 字段)时,宿主默认不渲染该控件,预设值仍随提交传入**(与 `confirm` 无参数区对齐信息架构)。无「已选择」时仍只读展示。需要强制露出时加 `revealInForm: true`。**不要**用 `visibleWhen` 藏映射主键(会不提交)。 - `placements` 含 `batch` 的动作在数据管理表顶常驻,无勾选时禁用(与表删除一致) - 需要确认的副作用操作使用 `confirmation.message` - 短名看不懂或会覆盖数据时,用独立的 `hoverHint`;不要把确认黄条复用成悬停 - 模板参数使用 `{{fieldName}}`,且必须在 `inputSchema.properties` 中声明 - `entrypoint.args` 项当前只允许 `string`、`number`、`boolean`;不允许对象或 `null` - **不要**在当前模板中宣传文件上传、任意 JSON 多选数组、`oneOf`、复杂 nested object 等尚未稳定支持的能力;Lookup 多选请用 `lookup.multiple` ## 风险与入口配置 - 查询、诊断、版本检查可以放到 `skill-detail` 或 `agent` - 低风险、常用动作才考虑放 `toolbar`(须 `bind.tables`;executionProfile 按耗时自选) - 会修改本地数据、访问外部系统、执行长时间任务的动作必须谨慎配置 - 有副作用的动作应添加 `confirmation.message` - 高风险动作不应默认出现在 `toolbar` - 含 `sensitive` 参数的 action 不应放入 `toolbar` 或 `cron`,除非有明确安全设计 - 不要为了「按钮多」而暴露所有 CLI 子命令 - 一个 CLI 命令可以拆成多个语义清晰的 action,但每个 action 必须有明确语义,并复用同一业务内核 - **Agent 必须用 `run_skill_action`,禁止 `exec` 直跑长任务 CLI**(见 `SKILL_ACTION_RUNTIME.md`) - **禁止**按 `source=toolbar/cron/agent/skill-detail` 在业务代码中分叉业务行为 ## 模板示例 action 一览 | id | CLI | executionProfile | placements | 说明 | |----|-----|------------------|------------|------| | `health` | `health` | sync | skill-detail, agent | 运行环境检查 | | `version` | `version` | sync | skill-detail | 版本 JSON | | `config-path` | `config-path` | sync | skill-detail | 配置路径 JSON | 业务技能复制后,按需增加例如: | 模式 | id 示例 | executionProfile | placements | bind | |------|---------|------------------|------------|------| | 数据同步 | `sync-records` | async 或 sync | toolbar, cron, agent, skill-detail | 必填 tables | | 单条 RPA | `your-run` | async(建议) | agent, skill-detail | 按需 | | 队列 pick | `your-run-pick` | async(建议) | toolbar, cron, agent | 有 toolbar 则必填 | | 统计 | `your-stats` | sync | skill-detail, agent | 无 | 不需要宿主入口时可删除整个 `actions.json`(无 RPA 长任务时)。 ## 与宿主 API(参考) | 宿主 API | 用途 | |----------|------| | `GET /api/skill-actions/{skillSlug}` | 读取 manifest | | `POST /api/skill-actions/run` | `{ skillSlug, actionId, input?, source? }` | | `GET /api/skill-actions/jobs` | 任务中心 | 具体路径与字段以宿主实现为准;技能侧只需保证 manifest 与 CLI 契约稳定。