51 Commits
Author SHA1 Message Date
KeyInfo Bot f7b1fa4b68 docs: update ManySkills and Gitea plugin guidance 2026-11-05 19:40:33 +08:00
KeyInfo Bot 6e9d718286 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-05 19:34:19 +08:00
KeyInfo Bot 6092258537 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-05 00:03:16 +08:00
KeyInfo Bot d8b11a58ac Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-04 17:28:46 +08:00
KeyInfo Bot 04673a8de8 feat: add unicom screenshots and update mobile aijia 2026-11-04 10:02:00 +08:00
KeyInfo Bot 09b9de33dd Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-04 00:03:12 +08:00
KeyInfo Bot c0412f1662 fix: skip unchanged mobile aijia releases 2026-11-03 16:36:43 +08:00
KeyInfo Bot 47fc6b010b feat: add mobile aijia screenshots skill 2026-11-03 16:34:23 +08:00
KeyInfo Bot a4b05247d8 fix: package caveman codex session hook 2026-11-03 10:08:51 +08:00
KeyInfo Bot 565b6c7482 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-03 00:03:16 +08:00
KeyInfo Bot f446dead3b Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-02 00:03:04 +08:00
KeyInfo Bot 137ae7d820 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-11-01 00:03:00 +08:00
KeyInfo Bot bd2c873b8a Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-31 00:02:59 +08:00
KeyInfo Bot b397f552f0 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-30 00:03:35 +08:00
KeyInfo Bot 2fb1818ebf fix: adapt hallmark codex skill metadata 2026-10-29 23:28:35 +08:00
KeyInfo Bot c4c748aac1 feat: add hallmark design skill 2026-10-29 23:27:10 +08:00
KeyInfo Bot ed33e12b7d Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-29 00:03:25 +08:00
KeyInfo Bot 79c20679c5 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-28 10:01:14 +08:00
KeyInfo Bot 360779e38d Remove bundled Artemis automation skill 2026-10-27 16:13:45 +08:00
KeyInfo Bot facfcea832 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-27 15:59:53 +08:00
KeyInfo Bot c09512f26a Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-27 15:58:23 +08:00
KeyInfo Bot f4d6b4228b Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-27 00:03:14 +08:00
KeyInfo Bot 6a0a022257 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-26 00:03:15 +08:00
KeyInfo Bot e1981742ba Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-25 00:02:43 +08:00
KeyInfo Bot e224150d81 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-24 00:02:56 +08:00
KeyInfo Bot ed52dd300b Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-23 00:02:41 +08:00
KeyInfo Bot 949a960835 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-22 00:02:38 +08:00
KeyInfo Bot 64087ca43c Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-21 14:51:10 +08:00
KeyInfo Bot 6056566972 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-18 00:02:41 +08:00
KeyInfo Bot 3b56fa43b1 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-16 00:02:39 +08:00
KeyInfo Bot cdebff3acd Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-15 11:05:45 +08:00
KeyInfo Bot 049ed5ace3 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-15 00:02:41 +08:00
KeyInfo Bot c03a1a6924 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-14 10:11:59 +08:00
KeyInfo Bot b3d81ca21f Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-13 00:02:36 +08:00
KeyInfo Bot ebe567a174 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-12 12:59:59 +08:00
KeyInfo Bot 9e74428671 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-12 00:02:42 +08:00
KeyInfo Bot cbde777e05 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-11 00:02:38 +08:00
KeyInfo Bot 453b104fe1 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-10 00:02:42 +08:00
KeyInfo Bot 1545ca085e Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-09 18:29:12 +08:00
KeyInfo Bot 5ced4f85a8 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-09 10:06:39 +08:00
KeyInfo Bot e683f50194 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-09 00:02:47 +08:00
KeyInfo Bot 7757d4f8d6 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-08 00:02:43 +08:00
KeyInfo Bot d6043fc982 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-07 00:02:44 +08:00
KeyInfo Bot ee2b89a64b Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-06 15:34:26 +08:00
KeyInfo Bot 598c8df2cd Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-06 15:33:04 +08:00
KeyInfo Bot 36c089fee9 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-06 15:29:22 +08:00
KeyInfo Bot be7ce9bda2 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-06 00:02:11 +08:00
KeyInfo Bot 7c1d798d93 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-05 00:01:56 +08:00
KeyInfo Bot 03caf59d49 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-04 00:01:55 +08:00
KeyInfo Bot e9b0e0c8fe Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-03 00:02:02 +08:00
KeyInfo Bot 6e0dfc4b44 Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
2026-10-02 00:02:09 +08:00
2886 changed files with 247982 additions and 28765 deletions

No files matched your search

+48 -24
View File
@@ -160,18 +160,6 @@
},
"category": "Productivity"
},
{
"name": "artemis-automation",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/artemis-automation"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "artemis-case-prep",
"source": {
@@ -292,6 +280,18 @@
},
"category": "Developer Tools"
},
{
"name": "mobile-aijia-daily-screenshots",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/mobile-aijia-daily-screenshots"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "next-skills",
"source": {
@@ -316,6 +316,30 @@
},
"category": "Developer Tools"
},
{
"name": "superpowers-zh",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/superpowers-zh"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "unicom-zhijia-daily-screenshots",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/unicom-zhijia-daily-screenshots"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "updating-gitea-repositories",
"source": {
@@ -364,6 +388,18 @@
},
"category": "Creativity"
},
{
"name": "hallmark",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/hallmark"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Creativity"
},
{
"name": "taste-skill",
"source": {
@@ -400,18 +436,6 @@
},
"category": "Creativity"
},
{
"name": "postgres",
"source": {
"source": "local",
"path": "./plugins/codex/plugins/postgres"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Data & Analytics"
},
{
"name": "literature-review",
"source": {
+19 -5
View File
@@ -23,6 +23,18 @@ Codex 插件页的大标题按 marketplace 来源分组,不是按插件 `categ
有些插件是 skill 集合,安装一个 `plugin-id` 后会同时提供多个 `$skill-name`。可安装项和安装后可用的 skills 均以 Codex 的实际输出为准。
`superpowers-zh` 是 Superpowers 的简体中文版本,提供规划、测试驱动开发、调试和代码审查等工作流。请使用上述插件 ID 安装;其内容跟随中文版正式发版,Codex 组件保持上游声明。
`docx`、`pdf`、`pptx` 和 `xlsx` 提供简体中文入口与 Windows PowerShell 指引。首次使用先检查 Python、Node、中文字体和实际需要的依赖;Office 转换需要 LibreOffice,PDF 逐页预览使用 PyMuPDF,无需 Poppler。中文文件路径必须加引号,交付前检查缺字、版式与公式结果。
`find-skills` 使用 Vercel Skills CLI 搜索技能,需要 Node.js 与 `npx`。搜索结果里的安装命令适用于其上游生态;安装本市场插件仍使用上述 Codex 命令。
`taste-skill` 用于网站与工作界面的设计和真实浏览器检查。运行其脚本需要 Node.js 22+ 和已安装的 Chrome、Edge 或 Chromium;素材搜索需要网络访问。
`hallmark` 用于设计新网站和页面,提供视觉主题与浏览器检查;只有明确指定 Hallmark 时才重设计或审阅现有页面。主题和检查脚本需要 Node.js,浏览器检查需要可用浏览器;可选图像生成需要 Together AI 凭据与网络。
`generate-agents-md`、`design-md`、`eapil-llmapi-structured`、`eapil-gpt2-image` 和 `eapil-voice` 的内容与版本跟随 [ManySkills 正式发版](https://git.playones.com/chenmingxuan/ManySkills/releases),安装 ID 保持不变。图像、LLM 和音频服务需要用户自行配置相应凭据。`eapil-gpt2-image` 1.2.0 起不再支持 `--api-key`,请安全配置 `EAPIL_API_KEY`,或按插件说明复用受支持的 Codex 凭据。
`dingtalk-workspace` 提供钉钉官方办公协作 Skills。使用前请单独安装与插件版本匹配的 [dws CLI](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases),执行 `dws auth login` 并取得企业授权;辅助脚本需要 Python 3。建议使用官方预编译二进制,避免安装器额外安装重复 Skills;升级 CLI 时可使用 `dws upgrade --skip-skills`,Skills 由本市场更新。插件不会附带账号凭据,消息发送、文档修改和审批等操作需要明确的用户指令。
例如 `grill-me` 插件会同时安装用户入口 `$grill-me` 和必需的基础能力 `$grilling`;请安装插件整体,不要只复制其中一个 skill 目录。
@@ -33,19 +45,21 @@ Codex 插件页的大标题按 marketplace 来源分组,不是按插件 `categ
`embedded-app-pc-debug`、`firmware-app-development` 和 `firmware-lowlevel-development` 分别覆盖 Windows 电脑端设备调试、固件应用层开发及 BSP/PSP 等底层开发流程。使用前应确认项目工具链、目标设备和访问权限;构建、设备写入、刷机、升级、上传和发布等操作必须按插件门禁取得明确确认。
`artemis-automation` 提供 Android 真机或模拟器的自然语言测试、批量用例、失败诊断、历史检索和报告交付流程。市场只分发 Skill;使用前需单独部署并配置兼容的 ARTEMIS MCP、Android 设备和模型网关。
`artemis-case-prep` 根据原始 Android 测试工作簿筛选可自动化用例,保留原表副本并生成七列 ARTEMIS 任务表;它只准备文档,不启动测试。导入前请检查表头与当前 ARTEMIS 版本是否匹配。
部分插件依赖特定运行环境。`$updating-gitea-repositories` 面向 Gitea HTTPS、GCM 和 PAT 工作流;`$etunel-role-collaboration` 仅在 Etunel 提供角色、成员、Hook 和任务上下文时使用。安装前应查看插件详情并确认运行前置条件。
`mobile-aijia-daily-screenshots` 和 `unicom-zhijia-daily-screenshots` 使用 ARTEMIS Flash 分别采集移动爱家和联通智家设备截图并整理结果,需要 ARTEMIS、`artemis-use`、Android 模拟器及相应 App;钉钉通知需要 DWS 与对应 Skills。创建计划时须确认完整配置和通知授权,执行时核实设备与授权范围。
`etunel-role-collaboration` 的插件版本对应上游正式 release 的版本号。更新插件不会自动修改 Etunel 的 Hook 配置或迁移在途项目;实际项目仍需按已确认的流程基线使用。
`$updating-gitea-repositories` 支持在 Windows、macOS、WSL 和 Linux 中安全复用凭据、诊断认证问题并验证 Gitea HTTPS 仓库更新。具体认证方式和前置条件请查看插件说明。
`etunel-role-collaboration` 保留原安装 ID,v2.0.0 起提供 `$ep-hub` 项目中心,展示名为 `ep-hub`,按八阶段管理交付、真人确认、进度、交接和成果留档,不再提供旧的全角色流程入口。Etunel 配置与通信需目标环境已安装 `etunel-use`;缺少时仍可整理记录与待决定事项,不得声称配置或通信已完成。
该插件跟随 [EP-Hub-Skill 正式发版](https://git.playones.com/chenmingxuan/EP-Hub-Skill/releases)。升级后开始新会话并调用 `$ep-hub`;更新不会自动修改 Hook、职责契约或迁移在途项目。迁移须明确确认范围,保留既有任务、时间、成果和确认历史。可选钉钉通知脚本需从同版仓库另行部署,依赖 Python 3.9+ 和已登录的原生 DWS;旧 `progress` 调用必须补 `--stage`,`--dry-run` 仅离线预览,不代表通知已发送。
`caveman` 插件中的大多数工作流可直接使用。可选的 `$caveman-compress` 需要 Python 3.10,以及 `ANTHROPIC_API_KEY` 或已登录的 Claude CLI;仅在用户明确指定文件后执行,并会把该文件内容发送给 Anthropic。该插件不包含 Claude Code 专用统计 hooks、statusline、Caveman CLI、代理或 MCP shrink。
如果插件刚安装到当前 Codex 会话中,启动新的 Codex thread 或新的 Codex 调用后再依赖新增 skills/hooks/MCP/app 配置。
`caveman` 包含 Codex `SessionStart` hook,信任并启用后会在启动、恢复会话时注入精简表达模式。可要求 `normal mode` 停用当前模式;恢复会话时上游 hook 会再次注入。需要持续关闭时,请在 Codex 中禁用该 hook 或插件。
`caveman` 包含 Codex `SessionStart` hook,需要 Node.js。信任并启用后,启动或恢复会话时会根据配置注入默认模式,未配置时默认为 Caveman。可要求 `normal mode` 停用当前模式;需要持续关闭时,可设置 `CAVEMAN_DEFAULT_MODE=off` 或在 Codex 中禁用该 hook 或插件。
卸载插件:
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="360" viewBox="0 0 48 48"><title>docx</title><g fill="none"><path fill="url(#SVGEun4xcUP)" d="M11 43h26a3 3 0 0 0 3-3V18l-10-4l-4-10H11a3 3 0 0 0-3 3v33a3 3 0 0 0 3 3"/><path fill="url(#SVGcM4dxcaR)" fill-opacity=".5" d="M11 43h26a3 3 0 0 0 3-3V18l-10-4l-4-10H11a3 3 0 0 0-3 3v33a3 3 0 0 0 3 3"/><path fill="url(#SVGs75ELblf)" d="M26 15V4l14 14H29a3 3 0 0 1-3-3"/><path fill="url(#SVGdS9JudtG)" fill-opacity=".9" d="M16.25 23a1.25 1.25 0 1 0 0 2.5h15.5a1.25 1.25 0 1 0 0-2.5zM15 30.25c0-.69.56-1.25 1.25-1.25h15.5a1.25 1.25 0 1 1 0 2.5h-15.5c-.69 0-1.25-.56-1.25-1.25M16.25 35a1.25 1.25 0 1 0 0 2.5h15.5a1.25 1.25 0 1 0 0-2.5z"/><defs><linearGradient id="SVGEun4xcUP" x1="30.4" x2="33.484" y1="4" y2="36.911" gradientUnits="userSpaceOnUse"><stop stop-color="#6ce0ff"/><stop offset="1" stop-color="#4894fe"/></linearGradient><linearGradient id="SVGs75ELblf" x1="32.977" x2="29.477" y1="9.833" y2="15.667" gradientUnits="userSpaceOnUse"><stop stop-color="#9ff0f9"/><stop offset="1" stop-color="#b3e0ff"/></linearGradient><linearGradient id="SVGdS9JudtG" x1="33" x2="17.47" y1="43.3" y2="15.805" gradientUnits="userSpaceOnUse"><stop stop-color="#9deaff"/><stop offset="1" stop-color="#fff"/></linearGradient><radialGradient id="SVGcM4dxcaR" cx="0" cy="0" r="1" gradientTransform="rotate(134.348 19.568 11.308)scale(24.7968 14.6539)" gradientUnits="userSpaceOnUse"><stop offset=".362" stop-color="#4a43cb"/><stop offset="1" stop-color="#4a43cb" stop-opacity="0"/></radialGradient></defs></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="360" viewBox="0 0 24 24"><title>find-skills</title><g fill="none"><path fill="url(#SVG5b4mGcbO)" d="M21 19c0 2.5-4.03 4-9 4s-9-1.5-9-4c0-2.406 4.03-4 9-4s9 1.5 9 4"/><path fill="url(#SVGfOKLrdNQ)" d="M12 2a7.5 7.5 0 0 0-7.5 7.5c0 1.932 1.064 3.8 2.268 5.316c1.22 1.537 2.678 2.829 3.655 3.622c.926.75 2.228.75 3.154 0c.977-.793 2.435-2.085 3.655-3.622C18.436 13.301 19.5 11.432 19.5 9.5A7.5 7.5 0 0 0 12 2"/><path fill="url(#SVGOpCNSdas)" d="M14.5 9.5a2.5 2.5 0 1 1-5 0a2.5 2.5 0 0 1 5 0"/><defs><linearGradient id="SVGfOKLrdNQ" x1="1.219" x2="13.202" y1="-2.857" y2="16.549" gradientUnits="userSpaceOnUse"><stop stop-color="#f97dbd"/><stop offset="1" stop-color="#d7257d"/></linearGradient><linearGradient id="SVGOpCNSdas" x1="9.79" x2="12.394" y1="9.721" y2="12.428" gradientUnits="userSpaceOnUse"><stop stop-color="#fdfdfd"/><stop offset="1" stop-color="#fecbe6"/></linearGradient><radialGradient id="SVG5b4mGcbO" cx="0" cy="0" r="1" gradientTransform="rotate(-10.678 100.2 -51.93)scale(14.3921 6.38107)" gradientUnits="userSpaceOnUse"><stop stop-color="#7b7bff"/><stop offset=".502" stop-color="#a3a3ff"/><stop offset="1" stop-color="#ceb0ff"/></radialGradient></defs></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect x="5" y="5" width="54" height="54" rx="14" fill="#17272c"/>
<path d="M14 15h16v16H14z" fill="#ffb84f"/>
<path d="M34 15h16v16H34z" fill="#ff6d79"/>
<path d="M14 35h16v15H14z" fill="#70d9c5"/>
<path d="M34 35h16v15H34z" fill="#a99bf7"/>
<path d="M20 20h4v6h-4zm20 0h4v6h-4zM20 39h4v6h-4zm20 0h4v6h-4z" fill="#17272c"/>
</svg>

After

Width:  |  Height:  |  Size: 404 B

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect x="5" y="5" width="54" height="54" rx="14" fill="#15313c"/>
<rect x="19" y="10" width="27" height="44" rx="6" fill="#81d8ff"/>
<rect x="22" y="15" width="21" height="32" rx="2" fill="#f3fbff"/>
<path d="M22 40l6-7 5 4 4-6 6 8v8H22z" fill="#64d6a4"/>
<circle cx="29" cy="23" r="4" fill="#ffc957"/>
<path d="M27 12h11" stroke="#15313c" stroke-width="2" stroke-linecap="round"/>
<circle cx="32.5" cy="50.5" r="1.5" fill="#15313c"/>
<circle cx="46" cy="43" r="9" fill="#ffbd48"/>
<circle cx="46" cy="43" r="4" fill="#ec6a5e"/>
</svg>

After

Width:  |  Height:  |  Size: 615 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="360" viewBox="0 0 32 32"><title>pdf</title><g fill="none"><path fill="url(#SVGbYeP8enY)" d="M8.5 4A3.5 3.5 0 0 0 5 7.5v19A3.5 3.5 0 0 0 8.5 30h8.974c.146-.328.353-.634.621-.903l8.505-8.505a3 3 0 0 1 .4-.336V7.5A3.5 3.5 0 0 0 23.5 4z"/><path fill="url(#SVGjpE8fcaQ)" fill-opacity=".9" d="M10 13a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H11a1 1 0 0 1-1-1"/><path fill="url(#SVGjpE8fcaQ)" fill-opacity=".9" d="M10 18a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1"/><path fill="url(#SVGjpE8fcaQ)" fill-opacity=".9" d="M11 22a1 1 0 1 0 0 2h10a1 1 0 1 0 0-2z"/><path fill="url(#SVG8Zdw7rWn)" fill-opacity=".4" d="M8.5 4A3.5 3.5 0 0 0 5 7.5v19A3.5 3.5 0 0 0 8.5 30h8.974c.146-.328.353-.634.621-.903l8.505-8.505a3 3 0 0 1 .4-.336V7.5A3.5 3.5 0 0 0 23.5 4z"/><path fill="url(#SVGYPBejbdj)" fill-opacity=".4" d="M8.5 4A3.5 3.5 0 0 0 5 7.5v19A3.5 3.5 0 0 0 8.5 30h8.974c.146-.328.353-.634.621-.903l8.505-8.505a3 3 0 0 1 .4-.336V7.5A3.5 3.5 0 0 0 23.5 4z"/><path fill="url(#SVGTGP5Fd9u)" fill-opacity=".4" d="M8.5 4A3.5 3.5 0 0 0 5 7.5v19A3.5 3.5 0 0 0 8.5 30h8.974c.146-.328.353-.634.621-.903l8.505-8.505a3 3 0 0 1 .4-.336V7.5A3.5 3.5 0 0 0 23.5 4z"/><path fill="url(#SVGKHVETdmG)" d="M10 5a3 3 0 0 1 3-3h6a3 3 0 1 1 0 6h-6a3 3 0 0 1-3-3"/><path fill="url(#SVG2Rqg1dqL)" d="m20.539 29.469l7.61-7.543l-4.073-4.074l-7.567 7.64l.308 3.695z"/><path fill="url(#SVGTaD2n6OC)" d="m26.937 23.14l2.211-2.214a2.88 2.88 0 0 0 .072-4.017a2.88 2.88 0 0 0-4.144-.057l-2.238 2.241z"/><path fill="url(#SVGBGLG8drx)" d="M24.094 17.838a5.43 5.43 0 0 0 4.106 4.038l-1.55 1.551a5.43 5.43 0 0 1-4.106-4.04z"/><path fill="url(#SVGxKDc0jMv)" d="m20.539 29.47l.223-.223s-1.726-.661-2.535-1.47c-.809-.81-1.47-2.534-1.47-2.534l-.248.249a2.66 2.66 0 0 0-.686 1.206l-.79 3.051a1 1 0 0 0 1.217 1.22l3.02-.778a2.8 2.8 0 0 0 1.269-.722"/><defs><linearGradient id="SVGbYeP8enY" x1="5" x2="25.632" y1="6.6" y2="31.15" gradientUnits="userSpaceOnUse"><stop stop-color="#36dff1"/><stop offset="1" stop-color="#0094f0"/></linearGradient><linearGradient id="SVGjpE8fcaQ" x1="17.5" x2="8.622" y1="24" y2="13.125" gradientUnits="userSpaceOnUse"><stop stop-color="#9deaff"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="SVGKHVETdmG" x1="16" x2="16" y1="2" y2="8" gradientUnits="userSpaceOnUse"><stop stop-color="#ffe06b"/><stop offset="1" stop-color="#fab500"/></linearGradient><linearGradient id="SVG2Rqg1dqL" x1="19.861" x2="26.044" y1="19.948" y2="26.149" gradientUnits="userSpaceOnUse"><stop stop-color="#ffa43d"/><stop offset="1" stop-color="#fb5937"/></linearGradient><linearGradient id="SVGTaD2n6OC" x1="28.502" x2="25.869" y1="17.485" y2="19.969" gradientUnits="userSpaceOnUse"><stop stop-color="#f97dbd"/><stop offset="1" stop-color="#dd3ce2"/></linearGradient><linearGradient id="SVGBGLG8drx" x1="25.469" x2="21.489" y1="21.664" y2="19.902" gradientUnits="userSpaceOnUse"><stop stop-color="#ff921f"/><stop offset="1" stop-color="#ffe994"/></linearGradient><linearGradient id="SVGxKDc0jMv" x1="14.174" x2="18.325" y1="26.847" y2="30.975" gradientUnits="userSpaceOnUse"><stop offset=".255" stop-color="#ffd394"/><stop offset="1" stop-color="#ff921f"/></linearGradient><radialGradient id="SVG8Zdw7rWn" cx="0" cy="0" r="1" gradientTransform="matrix(8.9375 0 0 7.86963 16 2.556)" gradientUnits="userSpaceOnUse"><stop stop-color="#0a1852"/><stop offset="1" stop-color="#0a1852" stop-opacity="0"/></radialGradient><radialGradient id="SVGYPBejbdj" cx="0" cy="0" r="1" gradientTransform="matrix(8.9375 0 0 8.87743 16 2.556)" gradientUnits="userSpaceOnUse"><stop stop-color="#0a1852"/><stop offset="1" stop-color="#0a1852" stop-opacity="0"/></radialGradient><radialGradient id="SVGTGP5Fd9u" cx="0" cy="0" r="1" gradientTransform="rotate(135.565 7.13 16.2)scale(14.4424 6.48727)" gradientUnits="userSpaceOnUse"><stop stop-color="#0a1852"/><stop offset="1" stop-color="#0a1852" stop-opacity="0"/></radialGradient></defs></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="360" viewBox="0 0 24 24"><title>pptx</title><g fill="none"><path fill="url(#SVGdFie5cDw)" d="M8 6.25A2.25 2.25 0 0 1 10.25 4h7.5A2.25 2.25 0 0 1 20 6.25v8.5A2.25 2.25 0 0 1 17.75 17h-7.5A2.25 2.25 0 0 1 8 14.75z"/><path fill="url(#SVGXYfMve6h)" d="M8 6.25A2.25 2.25 0 0 1 10.25 4h7.5A2.25 2.25 0 0 1 20 6.25v8.5A2.25 2.25 0 0 1 17.75 17h-7.5A2.25 2.25 0 0 1 8 14.75z"/><path fill="url(#SVGCfFQgeqH)" d="M4 4.25A2.25 2.25 0 0 1 6.25 2h9a2.25 2.25 0 0 1 2.25 2.25v10.5A2.25 2.25 0 0 1 15.25 17h-9A2.25 2.25 0 0 1 4 14.75z"/><path fill="url(#SVGBFc5OckO)" d="M5.25 8A2.25 2.25 0 0 0 3 10.25v8.5A3.25 3.25 0 0 0 6.25 22h11.5A3.25 3.25 0 0 0 21 18.75v-1.5A2.25 2.25 0 0 0 18.75 15h-2.846a.75.75 0 0 1-.55-.24l-5.61-6.04A2.25 2.25 0 0 0 8.097 8z"/><defs><linearGradient id="SVGdFie5cDw" x1="21.8" x2="23.639" y1="19.5" y2="5.773" gradientUnits="userSpaceOnUse"><stop stop-color="#bb45ea"/><stop offset="1" stop-color="#9c6cfe"/></linearGradient><linearGradient id="SVGXYfMve6h" x1="20" x2="17" y1="8.5" y2="8.5" gradientUnits="userSpaceOnUse"><stop offset=".338" stop-color="#5750e2" stop-opacity="0"/><stop offset="1" stop-color="#5750e2"/></linearGradient><linearGradient id="SVGBFc5OckO" x1="6.857" x2="6.857" y1="8" y2="27.091" gradientUnits="userSpaceOnUse"><stop offset=".241" stop-color="#ffd638"/><stop offset=".637" stop-color="#fab500"/><stop offset=".985" stop-color="#ca6407"/></linearGradient><radialGradient id="SVGCfFQgeqH" cx="0" cy="0" r="1" gradientTransform="matrix(8.775 -11.5 18.53666 14.14428 8.05 14)" gradientUnits="userSpaceOnUse"><stop offset=".228" stop-color="#2764e7"/><stop offset=".685" stop-color="#5cd1ff"/><stop offset="1" stop-color="#6ce0ff"/></radialGradient></defs></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect x="5" y="5" width="54" height="54" rx="14" fill="#473349"/>
<rect x="18" y="10" width="28" height="44" rx="6" fill="#ff9877"/>
<rect x="21" y="15" width="22" height="32" rx="2" fill="#fff6ec"/>
<path d="M21 39l6-7 5 4 4-6 7 9v8H21z" fill="#8bd6bd"/>
<circle cx="28" cy="23" r="4" fill="#ffc65a"/>
<path d="M27 12h10" stroke="#473349" stroke-width="2" stroke-linecap="round"/>
<circle cx="32" cy="50.5" r="1.5" fill="#473349"/>
<circle cx="46" cy="43" r="9" fill="#79c9f4"/>
<circle cx="46" cy="43" r="4" fill="#526bdb"/>
</svg>

After

Width:  |  Height:  |  Size: 613 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="360" viewBox="0 0 48 48"><title>xlsx</title><g fill="none"><rect width="36" height="36" x="6" y="6" fill="url(#SVG1iPhScTz)" rx="8.5"/><path fill="url(#SVGM1bApbhK)" d="M12.25 6A6.25 6.25 0 0 0 6 12.25v4.25h10.5V6zM19 6v10.5h10V6zm-2.5 13H6v10h10.5zM19 29V19h10v10zm-2.5 2.5H6v4.25A6.25 6.25 0 0 0 12.25 42h4.25zM19 42h10V31.5H19zm12.5 0V31.5H42v4.25A6.25 6.25 0 0 1 35.75 42zM42 19v10H31.5V19zm0-2.5v-4.25A6.25 6.25 0 0 0 35.75 6H31.5v10.5z"/><defs><linearGradient id="SVG1iPhScTz" x1="8.571" x2="39.429" y1="8.571" y2="39.429" gradientUnits="userSpaceOnUse"><stop stop-color="#b0f098"/><stop offset="1" stop-color="#98f0b0"/></linearGradient><linearGradient id="SVGM1bApbhK" x1="6" x2="42" y1="6" y2="42" gradientUnits="userSpaceOnUse"><stop stop-color="#42b870"/><stop offset="1" stop-color="#1a7f7c"/></linearGradient></defs></g></svg>

After

Width:  |  Height:  |  Size: 900 B

+626 -95
View File
@@ -1,5 +1,552 @@
{
"sources": [
{
"id": "unicom-zhijia-daily-screenshots",
"repo": "https://git.playones.com/hejunxian/unicom-zhijia-daily-screenshots.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"skipUnchangedOnForce": true,
"adapter": "claude-skill",
"pluginName": "unicom-zhijia-daily-screenshots",
"category": "Developer Tools",
"skillPath": ".",
"sparseCheckout": ["SKILL.md", "agents", "references"],
"webpCompressPaths": [{"path": "references", "quality": 90}],
"marketAssets": [
{"source": "config/assets/unicom-zhijia-daily-screenshots.svg", "destination": "assets/icon.svg"}
],
"manifestOverrides": {
"description": "通过 ARTEMIS 采集联通智家设备截图并按授权方式发送结果。",
"author": {"name": "hejunxian", "url": "https://git.playones.com/hejunxian"},
"homepage": "https://git.playones.com/hejunxian/unicom-zhijia-daily-screenshots",
"repository": "https://git.playones.com/hejunxian/unicom-zhijia-daily-screenshots.git",
"license": "UNKNOWN",
"keywords": ["android", "artemis", "screenshots", "dingtalk"],
"interface": {
"displayName": "unicom-zhijia-daily-screenshots",
"shortDescription": "采集联通智家截图并按授权方式发送结果。",
"longDescription": "使用 ARTEMIS Flash 采集联通智家设备的直播、通话、云台、告警和回放截图并整理证据。需要 ARTEMIS、artemis-use、Android 模拟器与联通智家 App;钉钉通知需要 DWS 及对应 Skills。创建计划时确认完整配置及通知授权,执行时核实设备与授权范围。",
"developerName": "hejunxian",
"category": "Developer Tools",
"capabilities": ["Interactive", "Read", "Write"],
"defaultPrompt": ["使用 unicom-zhijia-daily-screenshots 准备一次截图任务。"],
"logo": "./assets/icon.svg",
"composerIcon": "./assets/icon.svg"
}
}
},
{
"id": "mobile-aijia-daily-screenshots",
"repo": "https://git.playones.com/hejunxian/mobile-aijia-daily-screenshots.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"skipUnchangedOnForce": true,
"adapter": "claude-skill",
"pluginName": "mobile-aijia-daily-screenshots",
"category": "Developer Tools",
"skillPath": ".",
"sparseCheckout": ["SKILL.md", "agents", "references"],
"marketAssets": [
{"source": "config/assets/mobile-aijia-daily-screenshots.svg", "destination": "assets/icon.svg"}
],
"manifestOverrides": {
"description": "通过 ARTEMIS 采集移动爱家设备截图并按确认方式发送结果。",
"author": {"name": "hejunxian", "url": "https://git.playones.com/hejunxian"},
"homepage": "https://git.playones.com/hejunxian/mobile-aijia-daily-screenshots",
"repository": "https://git.playones.com/hejunxian/mobile-aijia-daily-screenshots.git",
"license": "UNKNOWN",
"keywords": ["android", "artemis", "screenshots", "dingtalk"],
"interface": {
"displayName": "mobile-aijia-daily-screenshots",
"shortDescription": "采集移动爱家截图并按确认方式发送结果。",
"longDescription": "使用 ARTEMIS Flash 采集移动爱家设备的直播、对讲、云台、告警和回放截图并整理证据。需要可用的 ARTEMIS、artemis-use、Android 模拟器与移动爱家 App;钉钉通知需要 DWS 及对应 Skills。创建计划时确认完整配置及通知授权,执行时核实设备与授权范围。",
"developerName": "hejunxian",
"category": "Developer Tools",
"capabilities": ["Interactive", "Read", "Write"],
"defaultPrompt": ["使用 mobile-aijia-daily-screenshots 准备一次截图任务。"],
"logo": "./assets/icon.svg",
"composerIcon": "./assets/icon.svg"
}
}
},
{
"id": "hallmark",
"repo": "https://github.com/Nutlope/hallmark.git",
"ref": "main",
"adapter": "claude-skill",
"pluginName": "hallmark",
"category": "Creativity",
"skillPath": "skills/hallmark",
"sparseCheckout": ["skills/hallmark"],
"webpCompressPaths": ["references/themes"],
"skillTextReplacements": [
{"path": "agents/openai.yaml", "old": "dependencies: []", "new": "dependencies: {}"},
{"path": "SKILL.md", "old": "<slug>.jpg", "new": "<slug>.webp"},
{"path": "scripts/theme.mjs", "old": "'.jpg'", "new": "'.webp'"},
{"path": "scripts/theme.mjs", "old": "${t.slug}.jpg", "new": "${t.slug}.webp"},
{"path": "scripts/theme.mjs", "old": "${slug}.jpg", "new": "${slug}.webp"}
],
"marketAssets": [{"source": "config/assets/hallmark.svg", "destination": "assets/market-icon.svg"}],
"manifestOverrides": {
"version": "2.0.0",
"description": "Hallmark 提供网站与页面的视觉设计、主题选择和浏览器检查工作流。",
"author": {"name": "Together AI", "url": "https://github.com/Nutlope"},
"homepage": "https://github.com/Nutlope/hallmark",
"repository": "https://github.com/Nutlope/hallmark",
"license": "MIT",
"keywords": ["hallmark", "design", "frontend", "themes", "accessibility"],
"interface": {
"displayName": "Hallmark",
"shortDescription": "设计网站与页面,选择视觉主题并检查最终效果。",
"longDescription": "Hallmark 提供视觉主题、设计参考、页面重设计和审阅工作流。主题与检查脚本需要 Node.js,浏览器检查需要可用的浏览器;可选的图像生成需要 Together AI 凭据和网络。",
"developerName": "Together AI",
"category": "Creativity",
"defaultPrompt": ["使用 Hallmark 为我的项目设计一个网页。"],
"logo": "./assets/market-icon.svg",
"composerIcon": "./assets/market-icon.svg"
}
},
"skillDescriptions": {"hallmark": "用于设计新网站和页面;仅在用户明确指定 Hallmark 时重设计或审阅现有页面。"}
},
{
"id": "docx",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"pluginName": "docx",
"category": "Productivity",
"skillPath": "skills/docx",
"sparseCheckout": [
"skills/docx"
],
"materializePaths": [
{
"source": "skills/docx/SKILL.md",
"destination": "upstream-guide.md"
}
],
"marketAssets": [
{
"source": "config/skill-overrides/docx/SKILL.md",
"destination": "skills/docx/SKILL.md"
},
{
"source": "config/skill-overrides/common/platform.md",
"destination": "skills/docx/references/platform.md"
},
{
"source": "config/assets/docx.svg",
"destination": "assets/market-icon.svg"
},
{
"source": "config/skill-overrides/common/soffice.py",
"destination": "skills/docx/scripts/office/soffice.py"
},
{
"source": "config/skill-overrides/common/render_pdf.py",
"destination": "skills/docx/scripts/render_pdf.py"
},
{
"source": "config/skill-overrides/common/office_package.py",
"destination": "skills/docx/scripts/office_package.py"
},
{
"source": "config/skill-overrides/docx/accept_changes.py",
"destination": "skills/docx/scripts/accept_changes.py"
}
],
"manifestOverrides": {
"version": "2.0.0",
"description": "创建、读取和编辑 Word 文档,适配简体中文排版与 Windows。",
"author": {
"name": "Anthropic; EAPIL localization",
"url": "https://github.com/anthropics"
},
"license": "Proprietary",
"keywords": [
"docx",
"documents",
"zh-CN",
"windows"
],
"interface": {
"displayName": "docx",
"shortDescription": "创建、读取和编辑 Word 文档,适配简体中文排版与 Windows。",
"longDescription": "创建、读取和编辑 Word 文档,适配简体中文排版与 Windows。 包含最新上游工具及简体中文入口、PowerShell 指引和跨平台辅助脚本。依赖需按任务检查并安装。",
"developerName": "Anthropic; EAPIL",
"category": "Productivity",
"defaultPrompt": [
"使用 docx 处理这份中文文档,并检查最终输出。"
],
"logo": "./assets/market-icon.svg",
"composerIcon": "./assets/market-icon.svg"
}
}
},
{
"id": "pdf",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"pluginName": "pdf",
"category": "Productivity",
"skillPath": "skills/pdf",
"sparseCheckout": [
"skills/pdf"
],
"materializePaths": [
{
"source": "skills/pdf/SKILL.md",
"destination": "upstream-guide.md"
}
],
"marketAssets": [
{
"source": "config/skill-overrides/pdf/SKILL.md",
"destination": "skills/pdf/SKILL.md"
},
{
"source": "config/skill-overrides/common/platform.md",
"destination": "skills/pdf/references/platform.md"
},
{
"source": "config/assets/pdf.svg",
"destination": "assets/market-icon.svg"
},
{
"source": "config/skill-overrides/common/render_pdf.py",
"destination": "skills/pdf/scripts/convert_pdf_to_images.py"
}
],
"manifestOverrides": {
"version": "2.0.0",
"description": "读取、创建和填写 PDF,支持中文字体与免 Poppler 预览。",
"author": {
"name": "Anthropic; EAPIL localization",
"url": "https://github.com/anthropics"
},
"license": "Proprietary",
"keywords": [
"pdf",
"documents",
"zh-CN",
"windows"
],
"interface": {
"displayName": "pdf",
"shortDescription": "读取、创建和填写 PDF,支持中文字体与免 Poppler 预览。",
"longDescription": "读取、创建和填写 PDF,支持中文字体与免 Poppler 预览。 包含最新上游工具及简体中文入口、PowerShell 指引和跨平台辅助脚本。依赖需按任务检查并安装。",
"developerName": "Anthropic; EAPIL",
"category": "Productivity",
"defaultPrompt": [
"使用 pdf 处理这份中文文档,并检查最终输出。"
],
"logo": "./assets/market-icon.svg",
"composerIcon": "./assets/market-icon.svg"
}
}
},
{
"id": "pptx",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"pluginName": "pptx",
"category": "Productivity",
"skillPath": "skills/pptx",
"sparseCheckout": [
"skills/pptx"
],
"materializePaths": [
{
"source": "skills/pptx/SKILL.md",
"destination": "upstream-guide.md"
}
],
"marketAssets": [
{
"source": "config/skill-overrides/pptx/SKILL.md",
"destination": "skills/pptx/SKILL.md"
},
{
"source": "config/skill-overrides/common/platform.md",
"destination": "skills/pptx/references/platform.md"
},
{
"source": "config/assets/pptx.svg",
"destination": "assets/market-icon.svg"
},
{
"source": "config/skill-overrides/common/soffice.py",
"destination": "skills/pptx/scripts/office/soffice.py"
},
{
"source": "config/skill-overrides/common/render_pdf.py",
"destination": "skills/pptx/scripts/render_pdf.py"
},
{
"source": "config/skill-overrides/common/office_package.py",
"destination": "skills/pptx/scripts/office_package.py"
}
],
"manifestOverrides": {
"version": "2.0.0",
"description": "创建和编辑 PowerPoint,保留模板与图表,适配中文和 Windows。",
"author": {
"name": "Anthropic; EAPIL localization",
"url": "https://github.com/anthropics"
},
"license": "Proprietary",
"keywords": [
"pptx",
"documents",
"zh-CN",
"windows"
],
"interface": {
"displayName": "pptx",
"shortDescription": "创建和编辑 PowerPoint,保留模板与图表,适配中文和 Windows。",
"longDescription": "创建和编辑 PowerPoint,保留模板与图表,适配中文和 Windows。 包含最新上游工具及简体中文入口、PowerShell 指引和跨平台辅助脚本。依赖需按任务检查并安装。",
"developerName": "Anthropic; EAPIL",
"category": "Productivity",
"defaultPrompt": [
"使用 pptx 处理这份中文文档,并检查最终输出。"
],
"logo": "./assets/market-icon.svg",
"composerIcon": "./assets/market-icon.svg"
}
}
},
{
"id": "xlsx",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"pluginName": "xlsx",
"category": "Productivity",
"skillPath": "skills/xlsx",
"sparseCheckout": [
"skills/xlsx"
],
"materializePaths": [
{
"source": "skills/xlsx/SKILL.md",
"destination": "upstream-guide.md"
}
],
"marketAssets": [
{
"source": "config/skill-overrides/xlsx/SKILL.md",
"destination": "skills/xlsx/SKILL.md"
},
{
"source": "config/skill-overrides/common/platform.md",
"destination": "skills/xlsx/references/platform.md"
},
{
"source": "config/assets/xlsx.svg",
"destination": "assets/market-icon.svg"
},
{
"source": "config/skill-overrides/common/soffice.py",
"destination": "skills/xlsx/scripts/office/soffice.py"
},
{
"source": "config/skill-overrides/common/render_pdf.py",
"destination": "skills/xlsx/scripts/render_pdf.py"
},
{
"source": "config/skill-overrides/common/office_package.py",
"destination": "skills/xlsx/scripts/office_package.py"
},
{
"source": "config/skill-overrides/xlsx/recalc.py",
"destination": "skills/xlsx/scripts/recalc.py"
}
],
"manifestOverrides": {
"version": "2.0.0",
"description": "创建和编辑 Excel,验证公式,适配中文数据与 Windows Excel。",
"author": {
"name": "Anthropic; EAPIL localization",
"url": "https://github.com/anthropics"
},
"license": "Proprietary",
"keywords": [
"xlsx",
"documents",
"zh-CN",
"windows"
],
"interface": {
"displayName": "xlsx",
"shortDescription": "创建和编辑 Excel,验证公式,适配中文数据与 Windows Excel。",
"longDescription": "创建和编辑 Excel,验证公式,适配中文数据与 Windows Excel。 包含最新上游工具及简体中文入口、PowerShell 指引和跨平台辅助脚本。依赖需按任务检查并安装。",
"developerName": "Anthropic; EAPIL",
"category": "Productivity",
"defaultPrompt": [
"使用 xlsx 处理这份中文文档,并检查最终输出。"
],
"logo": "./assets/market-icon.svg",
"composerIcon": "./assets/market-icon.svg"
}
}
},
{
"id": "find-skills",
"repo": "https://github.com/vercel-labs/skills.git",
"ref": "main",
"adapter": "claude-skill",
"pluginName": "find-skills",
"category": "Productivity",
"skillPath": "skills/find-skills",
"sparseCheckout": [
"skills/find-skills",
"LICENSE"
],
"includeRootFiles": [
"LICENSE"
],
"marketAssets": [
{
"source": "config/assets/find-skills.svg",
"destination": "assets/market-icon.svg"
}
],
"skillDescriptions": {
"find-skills": "当用户想查找、选择或安装现成 Agent Skill 时,搜索技能生态并核对来源与适用性。"
},
"manifestOverrides": {
"description": "从 Vercel Skills 生态发现和安装适合任务的 Agent Skills。",
"author": {
"name": "Vercel",
"url": "https://github.com/vercel-labs"
},
"license": "MIT",
"interface": {
"displayName": "find-skills",
"shortDescription": "查找并选择适合任务的 Agent Skills。",
"longDescription": "跟随 vercel-labs/skills 的 find-skills 原始技能,使用 Skills CLI 搜索并核对来源。需要 Node.js 和 npx。",
"developerName": "Vercel",
"category": "Productivity",
"defaultPrompt": [
"使用 find-skills 查找适合当前任务的技能。"
],
"logo": "./assets/market-icon.svg",
"composerIcon": "./assets/market-icon.svg"
}
}
},
{
"id": "generate-agents-md",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "codex-plugin",
"pluginName": "generate-agents-md",
"category": "Developer Tools",
"sourcePath": "plugins/generate-agents-md",
"sparseCheckout": [
"plugins/generate-agents-md"
],
"include": [
".codex-plugin",
"skills",
"assets"
],
"manifestOverrides": {
"repository": "https://git.playones.com/chenmingxuan/ManySkills",
"homepage": "https://git.playones.com/chenmingxuan/ManySkills/src/branch/main/plugins/generate-agents-md"
}
},
{
"id": "design-md",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "codex-plugin",
"pluginName": "design-md",
"category": "Creativity",
"sourcePath": "plugins/design-md",
"sparseCheckout": [
"plugins/design-md"
],
"include": [
".codex-plugin",
"skills",
"assets"
],
"manifestOverrides": {
"repository": "https://git.playones.com/chenmingxuan/ManySkills",
"homepage": "https://git.playones.com/chenmingxuan/ManySkills/src/branch/main/plugins/design-md"
}
},
{
"id": "eapil-llmapi-structured",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "codex-plugin",
"pluginName": "eapil-llmapi-structured",
"category": "Developer Tools",
"sourcePath": "plugins/eapil-llmapi-structured",
"sparseCheckout": [
"plugins/eapil-llmapi-structured"
],
"include": [
".codex-plugin",
"skills",
"assets"
],
"manifestOverrides": {
"repository": "https://git.playones.com/chenmingxuan/ManySkills",
"homepage": "https://git.playones.com/chenmingxuan/ManySkills/src/branch/main/plugins/eapil-llmapi-structured"
}
},
{
"id": "eapil-gpt2-image",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "codex-plugin",
"pluginName": "eapil-gpt2-image",
"category": "Creativity",
"sourcePath": "plugins/eapil-gpt2-image",
"sparseCheckout": [
"plugins/eapil-gpt2-image"
],
"include": [
".codex-plugin",
"skills",
"assets"
],
"manifestOverrides": {
"repository": "https://git.playones.com/chenmingxuan/ManySkills",
"homepage": "https://git.playones.com/chenmingxuan/ManySkills/src/branch/main/plugins/eapil-gpt2-image"
}
},
{
"id": "eapil-voice",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "codex-plugin",
"pluginName": "eapil-voice",
"category": "Creativity",
"sourcePath": "plugins/eapil-voice",
"sparseCheckout": [
"plugins/eapil-voice"
],
"include": [
".codex-plugin",
"skills",
"assets"
],
"manifestOverrides": {
"repository": "https://git.playones.com/chenmingxuan/ManySkills",
"homepage": "https://git.playones.com/chenmingxuan/ManySkills/src/branch/main/plugins/eapil-voice"
}
},
{
"id": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
@@ -68,6 +615,37 @@
"LICENSE"
]
},
{
"id": "superpowers-zh",
"repo": "https://github.com/AreChen/superpowers-zh.git",
"releaseChannel": "latest-stable",
"releaseTagSuffix": "zh",
"adapter": "codex-plugin",
"pluginName": "superpowers-zh",
"category": "Developer Tools",
"sourcePath": ".",
"sparseCheckout": [
".codex-plugin",
"skills",
"assets/superpowers-small.svg",
"assets/app-icon.png",
"LICENSE"
],
"include": [
".codex-plugin",
"skills",
"assets",
"LICENSE"
],
"manifestOverrides": {
"name": "superpowers-zh",
"interface": {
"displayName": "Superpowers-zh",
"developerName": "AreChen / Jesse Vincent",
"category": "Developer Tools"
}
}
},
{
"id": "caveman",
"repo": "https://github.com/JuliusBrussee/caveman.git",
@@ -78,6 +656,8 @@
"sourcePath": "plugins/caveman",
"sparseCheckout": [
".codex/hooks.json",
".codex/codex-sessionstart.js",
"src/hooks/caveman-config.js",
"package.json",
"plugins/caveman",
"skills/caveman-commit/SKILL.md",
@@ -103,6 +683,14 @@
"source": ".codex/hooks.json",
"destination": "hooks/hooks.json"
},
{
"source": ".codex/codex-sessionstart.js",
"destination": ".codex/codex-sessionstart.js"
},
{
"source": "src/hooks/caveman-config.js",
"destination": "src/hooks/caveman-config.js"
},
{
"source": "skills/caveman-commit/SKILL.md",
"destination": "skills/caveman-commit/SKILL.md"
@@ -136,6 +724,13 @@
"destination": "skills/verify-and-stop"
}
],
"pluginTextReplacements": [
{
"path": "hooks/hooks.json",
"old": "$(git rev-parse --show-toplevel)/.codex/codex-sessionstart.js",
"new": "${PLUGIN_ROOT}/.codex/codex-sessionstart.js"
}
],
"manifestVersionPath": "package.json",
"manifestOverrides": {
"hooks": "./hooks/hooks.json",
@@ -143,7 +738,7 @@
"interface": {
"displayName": "Caveman",
"shortDescription": "精简 Codex 输出,并提供调查、实现、迁移、重构、评审和验证工作流。",
"longDescription": "Caveman 提供精简输出、提交信息、代码评审、问题调查、精益实现、安全重构、迁移、补丁和验证工作流。包含 Codex SessionStart hook:信任并启用后,在启动或恢复会话时注入 Caveman 模式;可要求 normal mode 停用,但恢复会话会再次注入。另含可选的 caveman-compress,需要 Python 3.10 及 Anthropic API Key 或 Claude CLI,仅在用户明确指定文件后把该文件内容发送给 Anthropic。Claude Code 专用统计 hooks、statusline、Caveman CLI、代理和 MCP shrink 不包含在此插件中。",
"longDescription": "Caveman 提供精简输出、提交信息、代码评审、问题调查、精益实现、安全重构、迁移、补丁和验证工作流。包含 Codex SessionStart hook:需要 Node.js,信任并启用后在启动或恢复会话时按配置注入默认模式,默认 Caveman;可通过 CAVEMAN_DEFAULT_MODE=off 或配置关闭。另含可选的 caveman-compress,需要 Python 3.10 及 Anthropic API Key 或 Claude CLI,仅在用户明确指定文件后把该文件内容发送给 Anthropic。Claude Code 专用统计 hooks、statusline、Caveman CLI、代理和 MCP shrink 不包含在此插件中。",
"developerName": "Julius Brussee",
"category": "Developer Tools",
"capabilities": [
@@ -179,17 +774,22 @@
"id": "taste-skill",
"repo": "https://github.com/Leonxlnx/taste-skill.git",
"ref": "main",
"adapter": "skill-collection",
"adapter": "codex-plugin",
"pluginName": "taste-skill",
"category": "Creativity",
"skillsPath": "skills",
"sourcePath": ".",
"sparseCheckout": [
".codex-plugin",
"skills",
"LICENSE"
],
"include": [
".codex-plugin",
"skills",
"LICENSE"
],
"manifestOverrides": {
"version": "0.1.0",
"description": "Taste Skill 是面向 AI Agent 的前端设计、重设计、图像参考生成和高质量输出约束 skill 集合。",
"description": "Taste Skill 提供网站、产品页面和工作界面的设计方向与真实浏览器验证工作流。",
"author": {
"name": "Leonxlnx",
"url": "https://github.com/Leonxlnx"
@@ -207,8 +807,8 @@
],
"interface": {
"displayName": "Taste Skill",
"shortDescription": "反模板化前端设计、重设计和图像参考生成 skill 集合。",
"longDescription": "Taste Skill 提供 design-taste-frontend、gpt-taste、image-to-code、redesign、minimalist、brutalist、soft、stitch、output enforcement,以及 Web、移动端和 brandkit 图像生成等多个 skills,用于让 AI Agent 产出更有设计判断、更少模板感的前端界面和视觉参考。",
"shortDescription": "面向网站与工作界面的设计方向和浏览器检查。",
"longDescription": "Taste Skill 提供网站、产品页面和仪表盘等工作界面的设计方向、参考素材和真实浏览器检查流程。脚本需要 Node.js 22 或更高版本,以及已安装的 Chrome、Edge 或 Chromium;素材搜索还需网络访问。",
"developerName": "Leonxlnx",
"category": "Creativity",
"capabilities": [
@@ -226,19 +826,7 @@
}
},
"skillDescriptions": {
"brandkit": "用于生成高端品牌指南、Logo 系统、视觉世界和品牌应用参考图的图像生成 skill。",
"brutalist-skill": "用于生成工业粗野主义、瑞士排版和战术终端风格界面的前端设计 skill。",
"gpt-tasteskill": "面向 GPT/Codex 的高强度前端设计和 GSAP 动效规则 skill。",
"image-to-code-skill": "用于先生成网站设计参考图、再分析并实现前端代码的 image-to-code 工作流。",
"imagegen-frontend-mobile": "用于生成高质量移动端 App 屏幕和流程参考图的图像生成 skill。",
"imagegen-frontend-web": "用于生成高质量网站、落地页和多区块页面参考图的图像生成 skill。",
"minimalist-skill": "用于生成克制、编辑感、类 Notion/Linear 的极简产品界面。",
"output-skill": "用于约束 AI 完整输出代码,避免占位注释、省略实现和半成品交付。",
"redesign-skill": "用于审计并升级已有项目界面,修复布局、层级、间距和通用 AI 风格问题。",
"soft-skill": "用于生成高端、柔和、留白充足且具有精致动效的视觉设计。",
"stitch-skill": "用于生成 Google Stitch 兼容的 DESIGN.md 语义设计系统规则。",
"taste-skill": "用于落地页、作品集和重设计任务的默认 anti-slop 前端设计 skill。",
"taste-skill-v1": "Taste Skill v1 兼容版本,用于需要保持原始行为的项目。"
"taste-skill": "用于网站、产品页面、仪表盘和工作界面的设计、重设计与真实浏览器检查。"
}
},
{
@@ -597,8 +1185,8 @@
{
"id": "updating-gitea-repositories",
"repo": "https://git.playones.com/huzhifa/skill-git-push.git",
"repoProvider": "gitea",
"ref": "main",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "codex-plugin",
"pluginName": "updating-gitea-repositories",
"category": "Developer Tools",
@@ -616,8 +1204,8 @@
"license": "UNKNOWN",
"interface": {
"displayName": "Gitea 仓库安全更新",
"shortDescription": "通过 GCM 和 PAT 安全提交并验证 Gitea 仓库更新。",
"longDescription": "在 macOS、Windows 和 WSL 中检查或安装 Git Credential Manager,安全处理 Gitea PAT,精确暂存目标变更,并在 HTTPS 推送后验证远程提交。",
"shortDescription": "安全复用凭据并验证 Gitea 仓库更新。",
"longDescription": "在 Windows、macOS、WSL 和 Linux 中安全复用 Gitea 凭据,诊断认证问题,并在 HTTPS 推送后验证远程提交。",
"developerName": "Eapil",
"category": "Developer Tools",
"capabilities": [
@@ -634,7 +1222,7 @@
}
},
"skillDescriptions": {
"updating-gitea-repositories": "用于通过 HTTPS、Git Credential Manager 和 PAT 在 macOS、Windows 或 WSL 中安全发布和更新 Gitea 仓库,并在推送后验证远程提交。"
"updating-gitea-repositories": "用于在 Windows、macOS、WSL 和 Linux 中安全复用 Gitea 凭据、诊断认证问题,并通过 HTTPS 发布和验证仓库更新。"
}
},
{
@@ -645,13 +1233,13 @@
"adapter": "claude-skill",
"pluginName": "etunel-role-collaboration",
"category": "Business & Operations",
"skillPath": "etunel-role-collaboration",
"skillPath": "ep-hub",
"sparseCheckout": [
"etunel-role-collaboration"
"ep-hub"
],
"brandColor": "#0F766E",
"manifestOverrides": {
"description": "Etunel 多 Agent 项目的角色识别、Hub 中介、依赖任务波次、成果交接和消息流程协作规范。",
"description": "EP Hub 项目中心:按八阶段管理交付任务、真人确认、项目进度、信息交接和成果留档。",
"author": {
"name": "chenmingxuan",
"url": "https://git.playones.com/chenmingxuan"
@@ -662,14 +1250,15 @@
"keywords": [
"etunel",
"multi-agent",
"role-collaboration",
"ep-hub",
"project-hub",
"human-confirmation",
"workflow"
],
"interface": {
"displayName": "Etunel 多角色协作",
"shortDescription": "约束 Hub 中介、成员状态、依赖任务波次、正式成果与消息流程。",
"longDescription": "用于 Etunel 多 Agent 项目中的角色和成员识别、Hub 唯一跨角色中介、依赖就绪任务波次、正式成果交接、异常协调及消息流程。该 skill 依赖 Etunel 运行时提供的 Hook、角色契约和任务上下文。",
"displayName": "ep-hub",
"shortDescription": "项目中心:八阶段交付、真人确认、进度与成果留档。",
"longDescription": "提供 $ep-hub 项目中心 Skill,管理八阶段交付、真人确认、信息交接、项目进度和五类记录模板。Etunel 配置与通信使用目标环境已安装的 etunel-use;可选钉钉通知需单独部署同版脚本、Python 3.9+ 和已登录的原生 DWS,progress 调用必须提供 --stage。沿用 etunel-role-collaboration 安装 ID 以支持已有用户升级;更新插件不自动修改 Hook、角色契约或迁移在途项目,旧基线和完成事实须保留。",
"developerName": "chenmingxuan",
"category": "Business & Operations",
"capabilities": [
@@ -678,17 +1267,14 @@
"Write"
],
"defaultPrompt": [
"使用 Etunel 多角色协作识别当前角色并推进项目任务。",
"使用 Etunel 多角色协作检查任务依赖、成果和交接状态。"
"使用 $ep-hub 根据现有项目记录整理当前阶段、交付成果和需要真人决定的事项。",
"使用 $ep-hub 检查项目进度、跨阶段遗留和成果确认,不擅自迁移既有流程。"
],
"websiteURL": "https://git.playones.com/chenmingxuan/EP-Hub-Skill",
"privacyPolicyURL": "https://git.playones.com/chenmingxuan/EP-Hub-Skill",
"termsOfServiceURL": "https://git.playones.com/chenmingxuan/EP-Hub-Skill",
"brandColor": "#0F766E"
}
},
"skillDescriptions": {
"etunel-role-collaboration": "用于 Etunel 多 Agent 项目的角色识别、Hub 中介、依赖任务波次、正式成果、人类负责人确认、阶段交接和消息流程协作。"
}
},
{
@@ -794,61 +1380,6 @@
}
}
},
{
"id": "artemis-automation",
"repo": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git",
"releaseChannel": "latest-stable",
"releaseProvider": "gitea",
"adapter": "claude-skill",
"pluginName": "artemis-automation",
"category": "Developer Tools",
"skillPath": "skills/artemis-automation",
"sparseCheckout": [
"skills/artemis-automation",
"LICENSE"
],
"brandColor": "#2563EB",
"manifestOverrides": {
"description": "使用 ARTEMIS MCP 完成 Android 自动化测试、批量执行、失败诊断、历史检索和报告交付。",
"author": {
"name": "chenmingxuan",
"url": "https://git.playones.com/chenmingxuan"
},
"homepage": "https://git.playones.com/chenmingxuan/Artemis-Eapil/src/branch/main/skills/artemis-automation",
"repository": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git",
"license": "Apache-2.0",
"keywords": [
"artemis",
"android",
"automation",
"testing",
"mcp"
],
"interface": {
"displayName": "ARTEMIS Android 自动化",
"shortDescription": "通过 ARTEMIS MCP 执行 Android 测试并交付报告。",
"longDescription": "通过 ARTEMIS MCP 在 Android 真机或模拟器上执行自然语言测试、批量用例、失败诊断、历史检索及 Word/Excel 报告交付。插件只分发 Skill;使用前必须单独部署并配置与该版本兼容的 ARTEMIS MCP、Android 设备和所需模型网关。",
"developerName": "chenmingxuan",
"category": "Developer Tools",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"使用 ARTEMIS Android 自动化检查设备并执行这个测试用例。",
"使用 ARTEMIS Android 自动化批量执行这些用例并导出报告。"
],
"websiteURL": "https://git.playones.com/chenmingxuan/Artemis-Eapil",
"privacyPolicyURL": "https://git.playones.com/chenmingxuan/Artemis-Eapil",
"termsOfServiceURL": "https://git.playones.com/chenmingxuan/Artemis-Eapil/src/branch/main/LICENSE",
"brandColor": "#2563EB"
}
},
"includeRootFiles": [
"LICENSE"
]
},
{
"id": "embedded-app-pc-debug",
"repo": "https://git.playones.com/zoukai/embedded-app-pc-debug.git",
@@ -1025,7 +1556,7 @@
"brandColor": "#000000",
"manifestOverrides": {
"version": "0.2.0",
"description": "Next.js Agent Skills 提供 Cache Components 迁移、优化和 dev loop 验证工作流。",
"description": "Next.js Agent Skills 提供缓存、预取、打包优化和 dev loop 验证工作流。",
"author": {
"name": "Vercel",
"url": "https://github.com/vercel"
@@ -1043,8 +1574,8 @@
],
"interface": {
"displayName": "Next.js Agent Skills",
"shortDescription": "Next.js Cache Components 迁移、优化和 dev loop 验证工作流。",
"longDescription": "Next.js Agent Skills 现在跟随 Next.js 仓库 canary/skills 版本,包含 next-cache-components-adoption、next-cache-components-optimizer 和 next-dev-loop 三个技能,覆盖 Cache Components 启用迁移、静态壳/即时导航优化,以及通过 Next.js dev MCP 与真实浏览器验证运行时行为。",
"shortDescription": "Next.js 缓存、预取、打包优化与 dev loop 验证工作流。",
"longDescription": "Next.js Agent Skills 跟随 Next.js 仓库 canary/skills,覆盖 Cache Components、部分预取的采用和优化、打包分析,以及通过 Next.js dev MCP 与真实浏览器验证运行时行为。技能数量以上游目录和安装结果为准。",
"developerName": "Vercel",
"category": "Developer Tools",
"capabilities": [
+166 -49
View File
@@ -1,13 +1,130 @@
{
"sources": [
{
"id": "unicom-zhijia-daily-screenshots",
"pluginName": "unicom-zhijia-daily-screenshots",
"repo": "https://git.playones.com/hejunxian/unicom-zhijia-daily-screenshots.git",
"ref": "v1.0.1",
"adapter": "claude-skill",
"commit": "9be948fff1d6bb8bfb46f01fc1d26d55ecf8bfc5",
"syncedAt": "2026-11-04T02:00:38Z"
},
{
"id": "mobile-aijia-daily-screenshots",
"pluginName": "mobile-aijia-daily-screenshots",
"repo": "https://git.playones.com/hejunxian/mobile-aijia-daily-screenshots.git",
"ref": "v1.0.1",
"adapter": "claude-skill",
"commit": "5d331555678eaa581ede62337fc3ae60011354f5",
"syncedAt": "2026-11-04T02:00:38Z"
},
{
"id": "hallmark",
"pluginName": "hallmark",
"repo": "https://github.com/Nutlope/hallmark.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "de4999e0cd7a7a41df0b4e6903fa02fe9f1492a8",
"syncedAt": "2026-11-04T16:00:00Z"
},
{
"id": "docx",
"pluginName": "docx",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "ca45fc496a3436251f191354091d557da31c15b5",
"syncedAt": "2026-10-29T16:00:00Z"
},
{
"id": "pdf",
"pluginName": "pdf",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "ca45fc496a3436251f191354091d557da31c15b5",
"syncedAt": "2026-10-29T16:00:00Z"
},
{
"id": "pptx",
"pluginName": "pptx",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "ca45fc496a3436251f191354091d557da31c15b5",
"syncedAt": "2026-10-29T16:00:00Z"
},
{
"id": "xlsx",
"pluginName": "xlsx",
"repo": "https://github.com/anthropics/skills.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "ca45fc496a3436251f191354091d557da31c15b5",
"syncedAt": "2026-10-29T16:00:00Z"
},
{
"id": "find-skills",
"pluginName": "find-skills",
"repo": "https://github.com/vercel-labs/skills.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "cabe07254cddf16a3a650253f0ba03d543d8a86e",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "generate-agents-md",
"pluginName": "generate-agents-md",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"ref": "v1.2.0",
"adapter": "codex-plugin",
"commit": "a5d31c54a52eb2b98ea40e1052b95a418948590a",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "design-md",
"pluginName": "design-md",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"ref": "v1.2.0",
"adapter": "codex-plugin",
"commit": "a5d31c54a52eb2b98ea40e1052b95a418948590a",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "eapil-llmapi-structured",
"pluginName": "eapil-llmapi-structured",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"ref": "v1.2.0",
"adapter": "codex-plugin",
"commit": "a5d31c54a52eb2b98ea40e1052b95a418948590a",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "eapil-gpt2-image",
"pluginName": "eapil-gpt2-image",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"ref": "v1.2.0",
"adapter": "codex-plugin",
"commit": "a5d31c54a52eb2b98ea40e1052b95a418948590a",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "eapil-voice",
"pluginName": "eapil-voice",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"ref": "v1.2.0",
"adapter": "codex-plugin",
"commit": "a5d31c54a52eb2b98ea40e1052b95a418948590a",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "dingtalk-workspace",
"pluginName": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
"ref": "v1.0.62",
"ref": "v1.0.67",
"adapter": "skill-collection",
"commit": "70323e1486e64b1ca823fa2bd9c48b9b07f88519",
"syncedAt": "2026-09-16T15:59:59Z"
"commit": "63a7c4906c7dee10750011ea309b9eb78abb7eb0",
"syncedAt": "2026-11-04T09:24:25Z"
},
{
"id": "ui-ux-pro-max",
@@ -15,8 +132,17 @@
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "09170eec67eefd46a7ae85de61b40c194020f997",
"syncedAt": "2026-09-27T16:00:00Z"
"commit": "b7d5353e0130feb8c5574ef1251f8a1db1873ccb",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "superpowers-zh",
"pluginName": "superpowers-zh",
"repo": "https://github.com/AreChen/superpowers-zh.git",
"ref": "v7.0.2-zh.1",
"adapter": "codex-plugin",
"commit": "79c797bd8c9d5273a5fba5eae5ac9ba27613d0d8",
"syncedAt": "2026-11-04T09:24:25Z"
},
{
"id": "caveman",
@@ -24,17 +150,17 @@
"repo": "https://github.com/JuliusBrussee/caveman.git",
"ref": "main",
"adapter": "codex-plugin",
"commit": "2fd153c67988e980fb0b2455c90832159a6a5a25",
"syncedAt": "2026-09-22T16:08:55Z"
"commit": "2e068f735253cce28256744804785f01887d9646",
"syncedAt": "2026-11-02T16:00:00Z"
},
{
"id": "taste-skill",
"pluginName": "taste-skill",
"repo": "https://github.com/Leonxlnx/taste-skill.git",
"ref": "main",
"adapter": "skill-collection",
"commit": "ce26fc25c0e5e8cab638f883de62d9a86ee5e45b",
"syncedAt": "2026-09-26T16:00:00Z"
"adapter": "codex-plugin",
"commit": "9b4bf937a1f69348126959c15d2f99a73e65699b",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "shadcn",
@@ -42,8 +168,8 @@
"repo": "https://github.com/shadcn-ui/ui.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "db2db460a26fa84fb65c8d903b213925fbdee9ed",
"syncedAt": "2026-09-28T16:00:01Z"
"commit": "8a3536678a528cc012b9e62484290e79d0be410f",
"syncedAt": "2026-11-04T09:24:25Z"
},
{
"id": "frontend-slides",
@@ -60,8 +186,8 @@
"repo": "https://github.com/op7418/guizang-ppt-skill.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "c91369c449d34755d320a8b81d0734000d99d1ab",
"syncedAt": "2026-08-08T16:00:01Z"
"commit": "93d21bf4a73a851907a6eb00acbf30618236667a",
"syncedAt": "2026-10-30T16:00:00Z"
},
{
"id": "html-ppt",
@@ -78,8 +204,8 @@
"repo": "https://github.com/hugohe3/ppt-master.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "680de11f1bef4628b68d5daad9dffec569fbd51f",
"syncedAt": "2026-09-27T16:00:00Z"
"commit": "f3633771fe699d327f02ae86393831bbcf5169f7",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "grill-me",
@@ -87,62 +213,53 @@
"repo": "https://github.com/mattpocock/skills.git",
"ref": "main",
"adapter": "skill-collection",
"commit": "d81f3a183412e71a5b1e84ca21bc1a35eea03a60",
"syncedAt": "2026-09-29T16:00:00Z"
"commit": "4ae2317ddb22b046e26455e4f9c9b3aaaf62212d",
"syncedAt": "2026-10-31T16:00:01Z"
},
{
"id": "updating-gitea-repositories",
"pluginName": "updating-gitea-repositories",
"repo": "https://git.playones.com/huzhifa/skill-git-push.git",
"ref": "main",
"ref": "v1.0.0",
"adapter": "codex-plugin",
"commit": "3a0b288dfa4df0657dc63c0fd424069432016595",
"syncedAt": "2026-09-03T09:20:38Z"
"commit": "dfaf9130e1e892a15f28b8daa17f8044c65d72ff",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "etunel-role-collaboration",
"pluginName": "etunel-role-collaboration",
"repo": "https://git.playones.com/chenmingxuan/EP-Hub-Skill.git",
"ref": "v1.0.4",
"ref": "v2.1.0",
"adapter": "claude-skill",
"commit": "1d85de4c7c1cd0138d94b211586c9008b28094df",
"syncedAt": "2026-09-15T09:51:52Z"
"commit": "075837d51f5816ad42154f84683ff6b5605654fc",
"syncedAt": "2026-10-21T06:48:34Z"
},
{
"id": "hardware-development",
"pluginName": "hardware-development",
"repo": "https://git.playones.com/xufeng/hardware-development.git",
"ref": "v1.2.0",
"ref": "v1.3.0",
"adapter": "claude-skill",
"commit": "fcbde2e9cfa14f3fd2e154d7bef6bd264da61fcd",
"syncedAt": "2026-09-17T16:00:00Z"
"commit": "7ec66f26a4a8c376c13eeb41fbe4f70a538c085b",
"syncedAt": "2026-10-09T02:04:00Z"
},
{
"id": "artemis-case-prep",
"pluginName": "artemis-case-prep",
"repo": "https://git.playones.com/hejunxian/artemis-case-prep.git",
"ref": "v1.1.0",
"ref": "v1.2.0",
"adapter": "claude-skill",
"commit": "e1e094513f4b9360231022f6d18a0ebc6a04dc7d",
"syncedAt": "2026-09-28T10:12:45Z"
},
{
"id": "artemis-automation",
"pluginName": "artemis-automation",
"repo": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git",
"ref": "v1.2.3",
"adapter": "claude-skill",
"commit": "59f458205d7f5ec041a9dc11acc1122f128e827b",
"syncedAt": "2026-09-29T16:00:00Z"
"commit": "f0b9fc6efac82f825ffc27051a04d47f22ba5362",
"syncedAt": "2026-10-21T06:48:34Z"
},
{
"id": "embedded-app-pc-debug",
"pluginName": "embedded-app-pc-debug",
"repo": "https://git.playones.com/zoukai/embedded-app-pc-debug.git",
"ref": "v1.0.0",
"ref": "v1.0.1",
"adapter": "claude-skill",
"commit": "cfb586cb5ffc5a69e66cd9adbc57e4ebf725d1d3",
"syncedAt": "2026-09-15T09:16:34Z"
"commit": "961ccbe8e0bc7473c99f052e5502ae9e89388cbf",
"syncedAt": "2026-10-22T16:00:00Z"
},
{
"id": "firmware-app-development",
@@ -157,10 +274,10 @@
"id": "firmware-lowlevel-development",
"pluginName": "firmware-lowlevel-development",
"repo": "https://git.playones.com/tangfahong/firmware-lowlevel-development.git",
"ref": "v1.0.0",
"ref": "v1.0.1",
"adapter": "skill-collection",
"commit": "bcea35a91eb8cdd59de90ee79a8ffa0d80037668",
"syncedAt": "2026-09-15T09:16:34Z"
"commit": "ea4ff92835aa19f52dbb34df4aab47cb3d418f83",
"syncedAt": "2026-10-09T10:26:29Z"
},
{
"id": "next-skills",
@@ -168,8 +285,8 @@
"repo": "https://github.com/vercel/next.js.git",
"ref": "canary",
"adapter": "skill-collection",
"commit": "bc8bf60a350071dc192c454674eab3574db8b50c",
"syncedAt": "2026-09-29T16:00:00Z"
"commit": "7fb24bf4b8362b9760dd4449ad43af3c652e14bb",
"syncedAt": "2026-11-05T11:30:20Z"
},
{
"id": "shuorenhua",
@@ -177,8 +294,8 @@
"repo": "https://github.com/MrGeDiao/shuorenhua.git",
"ref": "main",
"adapter": "claude-skill",
"commit": "9e6400d5e2cc050485c38fbf0dda682bce66c180",
"syncedAt": "2026-09-27T16:00:00Z"
"commit": "0a001940bf1fc70f29f29a46c3587749d5badb45",
"syncedAt": "2026-11-01T16:00:00Z"
}
]
}
@@ -0,0 +1,52 @@
"""Unpack or repack an OOXML document without shell-specific ZIP commands."""
import argparse
import os
import tempfile
import zipfile
from pathlib import Path, PurePosixPath
def unpack(source, destination):
root = Path(destination).resolve()
if root.exists() and any(root.iterdir()):
raise ValueError("Unpack destination must be empty")
with zipfile.ZipFile(source) as archive:
for info in archive.infolist():
name = PurePosixPath(info.filename.replace("\\", "/"))
if name.is_absolute() or ".." in name.parts or any(":" in part for part in name.parts):
raise ValueError(f"Unsafe ZIP entry: {info.filename}")
if (info.external_attr >> 16) & 0o170000 == 0o120000:
raise ValueError(f"ZIP symlink is not supported: {info.filename}")
archive.extractall(root)
def pack(source, destination):
root = Path(source).resolve()
output = Path(destination).resolve()
if output.is_relative_to(root):
raise ValueError("Output must be outside the unpacked directory")
if not (root / "[Content_Types].xml").is_file():
raise ValueError("Source must be the package root containing [Content_Types].xml")
output.parent.mkdir(parents=True, exist_ok=True)
fd, temporary = tempfile.mkstemp(suffix=output.suffix, dir=output.parent)
os.close(fd)
try:
with zipfile.ZipFile(temporary, "w", zipfile.ZIP_DEFLATED) as archive:
for file in sorted(root.rglob("*")):
if file.is_symlink():
raise ValueError(f"Package symlink is not supported: {file}")
if file.is_file():
archive.write(file, file.relative_to(root).as_posix())
os.replace(temporary, output)
finally:
Path(temporary).unlink(missing_ok=True)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=("unpack", "pack"))
parser.add_argument("input")
parser.add_argument("output")
args = parser.parse_args()
{"unpack": unpack, "pack": pack}[args.action](args.input, args.output)
+44
View File
@@ -0,0 +1,44 @@
# 简体中文与 Windows 环境
以下命令中的 `$skill` 是当前已安装 Skill 的真实目录;不要硬编码开发者机器路径。脚本使用 Python 3.10+。先检查现有 Python/Node 环境,缺少哪个依赖再安装哪个。
```powershell
$env:PYTHONUTF8 = "1"
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
python --version
node --version
```
若只有 `py`,把命令中的 `python` 替换为 `py -3`。PowerShell 调用带空格的可执行文件路径使用 `& "路径"`;Python 脚本路径作为独立参数传入,不拼接 shell 命令。中文路径、空格路径均需引号。
需要独立依赖环境时,在任务工作目录创建 `.venv`,使用 `& ".\.venv\Scripts\python.exe" -m pip install <缺失包>`。无需改变系统执行策略或激活脚本。Node 库安装到任务目录,不默认全局安装。不要假设 Codex、Claude 或容器已预装所有依赖。
## 字体与文本
- 用户未指定语言时,中文用户的说明、标题、图例和交付文案使用简体中文;引文、产品名、代码和原文专有名词保持原样。
- 新文件优先使用本机可用的微软雅黑、等线、宋体或思源黑体。先查字体;声明字体名不等于字体已安装。Word 配置 `w:rFonts/@w:eastAsia` 与 `zh-CN`,PPT 配置主题字体和文本框字体,Excel 使用 `Microsoft YaHei` 等实际存在的字体。
- PDF 优先嵌入有再分发许可的中文字体;ReportLab 简单中文文档可用 `UnicodeCIDFont('STSong-Light')`,交付前仍检查目标阅读器效果。不要用 Helvetica 绘制中文,也不要把方块字当正常输出。
- 隔离的转换器可能只加载自带字体,即使系统已经安装中文字体。实际查看渲染图;使用 fontconfig 的环境可指定现有、可信的 `FONTCONFIG_FILE`,确保包含中文字体目录。不要把某台机器的配置路径硬编码到文档或脚本。
- CSV 面向 Windows Excel 时可用 `utf-8-sig`;先保留已有文件编码,不要自动改写所有输入。XML、JSON 和新建脚本使用 UTF-8。
- 标点、单位、日期、人民币格式、身份证号和前导零按用户语义保留;不要默认套用美式日期或把长编号转为数字。
## 转换与预览
Office 文档转 PDF 使用随附的跨平台 `scripts/office/soffice.py`。它按 `SOFFICE_PATH`、PATH、Windows Program Files、macOS 应用目录寻找 LibreOffice,并为转换创建独立临时 profile。
```powershell
# 仅在自动检测不到时设置;不要求添加系统 PATH
$env:SOFFICE_PATH = "C:\Program Files\LibreOffice\program\soffice.com"
python "$skill\scripts\office\soffice.py" --headless --convert-to pdf --outdir ".\preview" ".\中文 文档.docx"
python "$skill\scripts\render_pdf.py" ".\preview\中文 文档.pdf" ".\preview\pages"
```
按实际需要安装 LibreOffice、`pymupdf`、`defusedxml`、`lxml` 等缺失依赖。PDF 的 `scripts/convert_pdf_to_images.py` 与其他 Skill 的 `scripts/render_pdf.py` 均使用 PyMuPDF,不需要 Poppler。`pptx` 上游缩略图脚本仍需 Poppler;无 Poppler 时走 PDF 渲染流程。
使用 Python `scripts/office_package.py unpack <输入> <空目录>` 和 `pack <包根目录> <输出>` 处理 Office ZIP,无需 `unzip`、`zip`、`rm` 或 Bash。输出必须在解包目录之外。
Word 的修订接受脚本需要支持 Basic 宏的 LibreOffice;无宏支持或超时必须报告失败,也可在用户允许时通过 Word 接受修订。
## 验证与交付
生成或编辑后保存到新文件;先检查内容、公式或 OOXML 结构,再渲染并查看页面,检查中文缺字、表格越界、文本遮挡和分页。转换失败或字体缺失时说明具体原因,不把“文件保存成功”当成渲染通过。保留输入,除非用户明确要求覆盖。
@@ -0,0 +1,29 @@
"""Render PDF pages without Poppler; requires PyMuPDF."""
import argparse
from pathlib import Path
import pymupdf
def convert(pdf_path, output_dir, max_dim=1600):
if max_dim <= 0:
raise ValueError("max_dim must be positive")
output = Path(output_dir)
output.mkdir(parents=True, exist_ok=True)
with pymupdf.open(pdf_path) as document:
for index, page in enumerate(document):
scale = min(max_dim / page.rect.width, max_dim / page.rect.height)
pixmap = page.get_pixmap(matrix=pymupdf.Matrix(scale, scale), alpha=False)
target = output / f"page_{index + 1}.png"
pixmap.save(str(target))
print(target)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input")
parser.add_argument("output")
parser.add_argument("--max-dim", type=int, default=1600)
args = parser.parse_args()
convert(args.input, args.output, args.max_dim)
+61
View File
@@ -0,0 +1,61 @@
"""Portable LibreOffice runner for Windows, macOS and Linux."""
import os
import platform
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
def resolve_soffice_binary():
explicit = os.environ.get("SOFFICE_PATH")
if explicit:
candidate = Path(explicit).expanduser()
if candidate.is_file():
return str(candidate)
raise FileNotFoundError(f"SOFFICE_PATH does not exist: {candidate}")
names = ("soffice.com", "soffice.exe", "soffice") if platform.system() == "Windows" else ("soffice",)
for name in names:
binary = shutil.which(name)
if binary:
return binary
candidates = [Path("/Applications/LibreOffice.app/Contents/MacOS/soffice")]
if platform.system() == "Windows":
candidates = [
Path(root) / "LibreOffice/program" / name
for root in (os.environ.get("ProgramFiles"), os.environ.get("ProgramFiles(x86)")) if root
for name in ("soffice.com", "soffice.exe")
]
for candidate in candidates:
if candidate.is_file():
return str(candidate)
raise FileNotFoundError("LibreOffice not found. Install it or set SOFFICE_PATH to its executable.")
def get_soffice_env():
env = os.environ.copy()
env["PYTHONUTF8"] = "1"
if platform.system() == "Linux":
env.setdefault("SAL_USE_VCLPLUGIN", "svp")
return env
def run_soffice(args, **kwargs):
binary = resolve_soffice_binary()
kwargs.setdefault("env", get_soffice_env())
kwargs.setdefault("timeout", 120)
if any(str(arg).startswith("-env:UserInstallation=") for arg in args):
return subprocess.run([binary, *args], **kwargs)
with tempfile.TemporaryDirectory(prefix="skill-lo-", ignore_cleanup_errors=True) as profile:
profile_arg = f"-env:UserInstallation={Path(profile).resolve().as_uri()}"
return subprocess.run([binary, profile_arg, *args], **kwargs)
if __name__ == "__main__":
try:
raise SystemExit(run_soffice(sys.argv[1:]).returncode)
except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
print(str(exc), file=sys.stderr)
raise SystemExit(1)
+38
View File
@@ -0,0 +1,38 @@
---
name: docx
description: 创建、读取、编辑和检查 Word 文档或模板(.docx/.dotx),包括中文排版、表格、批注和修订;适用于简体中文与 Windows PowerShell 环境。
license: Proprietary. LICENSE.txt has complete terms
---
# Word 文档
开始操作前读取 [references/platform.md](references/platform.md),确认 Python、Node、中文字体及需要的转换工具。命令中的脚本路径均相对本 Skill。
| 任务 | 选择 |
| --- | --- |
| 新建文档 | `docx`(Node)或 `python-docx`,按所需版式选择 |
| 提取正文或表格 | `python-docx`;需要查看修订时用 Pandoc 或检查 OOXML |
| 编辑复杂模板、批注或修订 | 保留 OOXML 结构,使用随附脚本 |
| .doc 旧文件 | LibreOffice 先转换为 .docx |
## 中文版式
未指定页面时,新文档采用 A4;已有文档沿用其页面和样式。正文中文字体设置 East Asian 字体,语言为 `zh-CN`,标题使用真正的标题样式。保持全角标点、编号、金额和原文引文;中文表格不要依赖空格对齐。
`docx` 创建时注意:表格同时指定列宽和单元格宽度;`ShadingType.CLEAR` 避免黑底;列表用 numbering 配置;`ImageRun` 指定类型;分页符放进 Paragraph。每个逻辑段落独立创建,不用换行模拟段落。需要 API 细节时按需读取 [upstream-guide.md](upstream-guide.md)。
## 编辑与审阅
先确认要清稿、批注还是保留修订;不要因文档类型自动强制红线。原有修订与批注保持不变,除非用户要求接受、拒绝或删除。
```powershell
python "$skill\scripts\office_package.py" unpack ".\输入.docx" ".\unpacked"
python "$skill\scripts\merge_runs.py" ".\unpacked"
# 按需编辑 word/document.xml,保持命名空间与节点顺序
python "$skill\scripts\office_package.py" pack ".\unpacked" ".\输出.docx"
python "$skill\scripts\office\validate.py" ".\输出.docx" --original ".\输入.docx"
```
`scripts/comment.py` 支持为文档添加批注;输出的定位节点需放到对应正文位置才能显示。`scripts/accept_changes.py` 仅在用户要求接受全部修订时使用,输出新文件;超时、文件未保存或仍有修订均算失败。
最终转换为 PDF,用 `scripts/render_pdf.py` 生成预览并查看中文字体、表格、页眉页脚、分页和页码。保留可编辑 Word 文件与用户需要的最终副本。
@@ -0,0 +1,75 @@
"""Accept tracked changes into a new DOCX using an isolated LibreOffice profile."""
import argparse
import shutil
import subprocess
import tempfile
import zipfile
from pathlib import Path
from defusedxml import ElementTree
from office.soffice import run_soffice
MACRO = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
Sub AcceptAllTrackedChanges()
Dim dispatcher As Object
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dispatcher.executeDispatch(ThisComponent.CurrentController.Frame, ".uno:AcceptAllTrackedChanges", "", 0, Array())
ThisComponent.store()
ThisComponent.close(True)
End Sub
</script:module>'''
WORD_NS = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
def accept_changes(input_file, output_file, timeout=120):
source = Path(input_file).resolve()
output = Path(output_file).resolve()
if source == output or not source.is_file() or source.suffix.lower() != ".docx":
return None, "Input must be an existing DOCX and output must be a different file"
output.parent.mkdir(parents=True, exist_ok=True)
try:
with tempfile.TemporaryDirectory(prefix="docx-accept-", ignore_cleanup_errors=True) as temporary:
root = Path(temporary).resolve()
profile = root / "profile"
profile_arg = f"-env:UserInstallation={profile.as_uri()}"
run_soffice([profile_arg, "--headless", "--terminate_after_init"], check=True, capture_output=True, timeout=timeout)
macro_dir = profile / "user/basic/Standard"
macro_dir.mkdir(parents=True, exist_ok=True)
(macro_dir / "Module1.xba").write_text(MACRO, encoding="utf-8")
candidate = root / "accepted.docx"
shutil.copy2(source, candidate)
before = candidate.stat().st_mtime_ns
run_soffice([
profile_arg, "--headless", "--norestore",
"vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application",
str(candidate),
], check=True, capture_output=True, timeout=timeout)
if candidate.stat().st_mtime_ns == before:
return None, "LibreOffice did not save the document"
with zipfile.ZipFile(candidate) as archive:
for name in archive.namelist():
if not name.startswith("word/") or not name.endswith(".xml"):
continue
document = ElementTree.fromstring(archive.read(name))
if any(element.tag in {WORD_NS + "ins", WORD_NS + "del", WORD_NS + "moveFrom", WORD_NS + "moveTo"}
or (element.tag.startswith(WORD_NS) and element.tag.endswith("Change"))
for element in document.iter()):
return None, f"Tracked changes remain in {name}"
shutil.copy2(candidate, output)
return None, f"Accepted tracked changes: {output}"
except (OSError, subprocess.SubprocessError, zipfile.BadZipFile) as exc:
return None, f"Failed to accept tracked changes: {exc}"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input")
parser.add_argument("output")
parser.add_argument("--timeout", type=int, default=120)
args = parser.parse_args()
_, message = accept_changes(args.input, args.output, args.timeout)
print(message)
raise SystemExit(0 if message.startswith("Accepted tracked changes:") else 1)
+35
View File
@@ -0,0 +1,35 @@
---
name: pdf
description: 读取、创建、合并、拆分、旋转、OCR 或填写 PDF 表单,处理简体中文字体与页面预览;适用于 Windows PowerShell 和跨平台终端。
license: Proprietary. LICENSE.txt has complete terms
---
# PDF 文档
先读取 [references/platform.md](references/platform.md),确认中文字体、Python 与实际需要的依赖。PDF 不等于扫描图片,先判断页面是否有可提取文字。
| 任务 | 工具 |
| --- | --- |
| 文本、页面预览、坐标检查 | PyMuPDF |
| 合并、拆分、旋转、元数据、加密 | pypdf |
| 表格提取 | pdfplumber,提取后核对表头、合并单元格与换行 |
| 新建中文 PDF | ReportLab 或可用的 Office/HTML 转 PDF 工具 |
| 扫描页 OCR | 本机可用的 OCR;Tesseract 中文需 `chi_sim` 数据 |
## 中文与排版
新文档默认简体中文、A4;已有版式或用户指定版式优先。中文必须使用实际可用的中文字体。优先嵌入授权允许的字体;ReportLab 简单文档可注册 `UnicodeCIDFont('STSong-Light')`。复制文本不乱码、渲染无缺字都需检查。
OCR 只针对扫描页或提取失败的页面;中文可用 `chi_sim+eng`,未装语言包时报告缺失。OCR 结果需核对数字、单位、姓名与表格关系,不凭猜测补全。
## 表单
涉及填写时按需读取 [forms.md](forms.md),先判断是否有 AcroForm 字段。可填表单使用随附 `extract_form_field_info.py`、`fill_fillable_fields.py`;非可填表单按页面坐标叠加,并检查原内容与填写区域。不要把签名图片视作数字签名。
## 预览与检查
```powershell
python "$skill\scripts\convert_pdf_to_images.py" ".\输入 中文.pdf" ".\preview"
```
此脚本使用 PyMuPDF,无需 Poppler,逐页渲染以避免一次加载整个大文档。合并或拆分后核对页面数量和顺序;填写后检查可见内容、字段值与页面边界。更多提取与转换方案见 [reference.md](reference.md),旧平台专用命令以本地环境指导为准。
+34
View File
@@ -0,0 +1,34 @@
---
name: pptx
description: 创建、读取、编辑和检查 PowerPoint 演示文稿或模板(.pptx/.potx),保留模板与可编辑图表,适配简体中文字体和 Windows PowerShell。
license: Proprietary. LICENSE.txt has complete terms
---
# PowerPoint 演示文稿
先读取 [references/platform.md](references/platform.md),确认字体、Node/Python 与需要的转换工具。先看用户模板或参考稿,再决定布局、配色与内容密度。
| 任务 | 选择 |
| --- | --- |
| 新建幻灯片 | `pptxgenjs`;简单任务也可使用 `python-pptx` |
| 提取文本 | `python-pptx` 或 markitdown;检查备注与隐藏页 |
| 复杂模板、母版或原生图表编辑 | OOXML 加随附脚本 |
| .ppt 旧格式 | LibreOffice 先转 .pptx |
## 中文与画布
新建稿未指定比例时用 16:9,设置画布后再添加元素。用户模板比例和主题优先。使用本机可用的微软雅黑、等线或思源黑体,同时设置主题字体与正文文本字体;简体中文语言标记使用 `zh-CN`。长中文标题主动换行,表格与数据标签保留足够空间,不能只缩小字号隐藏溢出。
`pptxgenjs` 颜色使用六位 HEX,不带 `#`;每次添加元素使用独立选项对象。文本框 margin 与字体测量要一致;原生图表、表格和文本保持可编辑。坐标单位为英寸,必须落在实际画布内。库限制、组合图表和模板 OOXML 细节按需读取 [upstream-guide.md](upstream-guide.md)。
## 模板编辑
先完成复制、删除或重排幻灯片,再改正文。使用 `scripts/add_slide.py` 注册复制页,最终页序确定后运行 `scripts/clean.py` 清理失去引用的部分。不要只复制 slide XML;关系、媒体、母版和内容类型必须一起处理。用 `scripts/office_package.py` 解包和重打包,避免依赖 Bash ZIP 命令。
```powershell
python "$skill\scripts\office\validate.py" ".\输出.pptx" --original ".\模板.pptx"
python "$skill\scripts\office\soffice.py" --headless --convert-to pdf --outdir ".\preview" ".\输出.pptx"
python "$skill\scripts\render_pdf.py" ".\preview\输出.pdf" ".\preview\pages"
```
没有模板时省略 `--original`。每页检查文字溢出、缺字、对齐、遮挡、图表标签、图片分辨率和页序。需要模板缩略图且 Poppler 可用时运行 `scripts/thumbnail.py` 并指定独立输出前缀;否则用上面的免 Poppler 逐页预览。
+37
View File
@@ -0,0 +1,37 @@
---
name: xlsx
description: 创建、读取、清理、编辑和检查 Excel 工作簿或 CSV/TSV,处理公式、图表、中文表头及 Windows Excel 编码,保留现有格式与宏。
license: Proprietary. LICENSE.txt has complete terms
---
# Excel 工作簿
先读取 [references/platform.md](references/platform.md),确认 Python、中文字体及是否需要公式计算引擎。复杂工作簿先检查公式、外部链接、宏、图表和现有样式,再选择处理方法。
- `openpyxl` 用于普通 .xlsx 单元格、公式和样式;`.xlsm` 读取时用 `keep_vba=True`,保留扩展名。不保证复杂 Excel 功能无损,必须检查实际需要保留的部分。
- `pandas` 适合表格数据清理;不要用整表重写处理需要保留样式、公式或图表的原文件。
- `.xls` 旧格式先使用兼容工具读取或转换;不要直接交给 openpyxl。
## 简体中文与数据类型
用户未指定语言时用简体中文表头、图例与说明。新文件使用可用的中文字体,表头、行高与列宽按中文实际长度设置;已有工作簿沿用其约定。身份证号、订单号、手机号和前导零存为文本,日期存真实日期值并设置明确格式,金额单位按用户要求标注。
Windows Excel 打开的新 CSV 可写 `utf-8-sig`;读取旧 CSV 先判断编码和分隔符。CSV 导出不保留多表、公式和样式,转换前明确所需交付格式。
## 公式与缓存
按用户语义写公式,验证引用范围与预期结果;不要用硬编码数值代替可重算公式。跨表引用含空格或中文标点时正确引用工作表名。合并区域只写左上角。
openpyxl 不计算公式。检查时分别用 `data_only=False` 读取公式、`data_only=True` 读取缓存;不能保存 `data_only=True` 的工作簿,否则公式会被数值覆盖。
```powershell
python "$skill\scripts\recalc.py" ".\输出 中文.xlsx" 120
```
重算会改写指定工作簿,先保留输入。脚本使用独立 LibreOffice profile,自动识别 Windows 的安装路径。检查 JSON 的 `status`、`total_errors` 和具体单元格;`errors_found` 即使进程退出码为零也未通过。含 `error` 的结果表示没有完成重算。
本市场的重算脚本只支持普通 `.xlsx`,无需安装 Basic 宏;使用临时副本转换,发现公式错误时不覆盖输入。不支持上游文档的 `--force` 参数,拒绝宏格式和外部链接工作簿。
有外部链接、宏、动态数组或 Excel 专用函数时,优先使用目标 Excel 环境验证,不能盲目用 LibreOffice 往返保存。兼容公式、缓存丢失和链接风险按需读取 [upstream-guide.md](upstream-guide.md)。无法运行计算引擎时说明“尚未重算”,不要声称公式已验证。
交付前核对数值、公式、中文显示、筛选、冻结窗格、打印区域和图表。用新输出文件保留输入;抽查代表行、合计和异常情况,避免只检查文件能够打开。
+91
View File
@@ -0,0 +1,91 @@
"""Recalculate an XLSX through a temporary LibreOffice import/export, without macros."""
import argparse
import json
import os
import shutil
import subprocess
import tempfile
import zipfile
from pathlib import Path
from openpyxl import load_workbook
from office.soffice import run_soffice
def inspect_workbook(path):
formulas = load_workbook(path, data_only=False)
values = load_workbook(path, data_only=True)
try:
formula_cells = set()
errors = {}
for sheet in formulas:
if not hasattr(sheet, "iter_rows"):
continue
for row in sheet.iter_rows():
for cell in row:
location = f"{sheet.title}!{cell.coordinate}"
if cell.data_type == "f":
formula_cells.add(location)
cached = values[sheet.title][cell.coordinate]
if cached.data_type == "e":
errors.setdefault(str(cached.value), []).append(location)
elif cell.data_type == "f" and cached.value is None:
errors.setdefault("MISSING_FORMULA_CACHE", []).append(location)
return formula_cells, errors
finally:
formulas.close()
values.close()
def recalc(filename, timeout=120):
source = Path(filename).expanduser().resolve()
if not source.is_file() or source.suffix.lower() != ".xlsx":
return {"error": "Input must be an existing .xlsx file; use Excel for macro-enabled workbooks"}
try:
with zipfile.ZipFile(source) as archive:
if any(name.startswith("xl/externalLinks/") for name in archive.namelist()):
return {"error": "External workbook links require validation in Excel; original file was not modified"}
original_formulas, _ = inspect_workbook(source)
with tempfile.TemporaryDirectory(prefix="xlsx-recalc-", dir=source.parent, ignore_cleanup_errors=True) as temporary:
root = Path(temporary)
input_dir, output_dir = root / "input", root / "output"
input_dir.mkdir()
output_dir.mkdir()
candidate = input_dir / source.name
shutil.copy2(source, candidate)
result = run_soffice([
"--headless", "--norestore", "--convert-to", "xlsx:Calc MS Excel 2007 XML",
"--outdir", str(output_dir), str(candidate),
], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
recalculated = output_dir / source.name
if result.returncode or not recalculated.is_file():
return {"error": "LibreOffice did not export a recalculated workbook", "details": result.stderr or result.stdout}
formula_cells, errors = inspect_workbook(recalculated)
if original_formulas != formula_cells:
return {"error": "Formula locations changed during conversion; original file was not modified"}
error_count = sum(len(locations) for locations in errors.values())
report = {
"status": "errors_found" if error_count else "success",
"total_formulas": len(formula_cells),
"total_errors": error_count,
"error_summary": {
error: {"count": len(locations), "locations": locations[:100]}
for error, locations in errors.items()
},
}
if not errors:
os.replace(recalculated, source)
return report
except (OSError, subprocess.SubprocessError, zipfile.BadZipFile, ValueError) as exc:
return {"error": str(exc)}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input")
parser.add_argument("timeout", nargs="?", type=int, default=120)
args = parser.parse_args()
report = recalc(args.input, args.timeout)
print(json.dumps(report, ensure_ascii=False, indent=2))
raise SystemExit(1 if "error" in report else 0)
+48 -24
View File
@@ -160,18 +160,6 @@
},
"category": "Productivity"
},
{
"name": "artemis-automation",
"source": {
"source": "local",
"path": "./plugins/artemis-automation"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "artemis-case-prep",
"source": {
@@ -292,6 +280,18 @@
},
"category": "Developer Tools"
},
{
"name": "mobile-aijia-daily-screenshots",
"source": {
"source": "local",
"path": "./plugins/mobile-aijia-daily-screenshots"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "next-skills",
"source": {
@@ -316,6 +316,30 @@
},
"category": "Developer Tools"
},
{
"name": "superpowers-zh",
"source": {
"source": "local",
"path": "./plugins/superpowers-zh"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "unicom-zhijia-daily-screenshots",
"source": {
"source": "local",
"path": "./plugins/unicom-zhijia-daily-screenshots"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
},
{
"name": "updating-gitea-repositories",
"source": {
@@ -364,6 +388,18 @@
},
"category": "Creativity"
},
{
"name": "hallmark",
"source": {
"source": "local",
"path": "./plugins/hallmark"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Creativity"
},
{
"name": "taste-skill",
"source": {
@@ -400,18 +436,6 @@
},
"category": "Creativity"
},
{
"name": "postgres",
"source": {
"source": "local",
"path": "./plugins/postgres"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Data & Analytics"
},
{
"name": "literature-review",
"source": {
@@ -1,41 +0,0 @@
{
"name": "artemis-automation",
"version": "1.2.3",
"description": "使用 ARTEMIS MCP 完成 Android 自动化测试、批量执行、失败诊断、历史检索和报告交付。",
"author": {
"name": "chenmingxuan",
"url": "https://git.playones.com/chenmingxuan"
},
"homepage": "https://git.playones.com/chenmingxuan/Artemis-Eapil/src/branch/main/skills/artemis-automation",
"repository": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git",
"license": "Apache-2.0",
"keywords": [
"artemis",
"android",
"automation",
"testing",
"mcp"
],
"skills": "./skills/",
"interface": {
"displayName": "ARTEMIS Android 自动化",
"shortDescription": "通过 ARTEMIS MCP 执行 Android 测试并交付报告。",
"longDescription": "通过 ARTEMIS MCP 在 Android 真机或模拟器上执行自然语言测试、批量用例、失败诊断、历史检索及 Word/Excel 报告交付。插件只分发 Skill;使用前必须单独部署并配置与该版本兼容的 ARTEMIS MCP、Android 设备和所需模型网关。",
"developerName": "chenmingxuan",
"category": "Developer Tools",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"使用 ARTEMIS Android 自动化检查设备并执行这个测试用例。",
"使用 ARTEMIS Android 自动化批量执行这些用例并导出报告。"
],
"websiteURL": "https://git.playones.com/chenmingxuan/Artemis-Eapil",
"privacyPolicyURL": "https://git.playones.com/chenmingxuan/Artemis-Eapil",
"termsOfServiceURL": "https://git.playones.com/chenmingxuan/Artemis-Eapil/src/branch/main/LICENSE",
"brandColor": "#2563EB",
"screenshots": []
}
}
@@ -1,9 +0,0 @@
{
"sourceId": "artemis-automation",
"repo": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git",
"ref": "v1.2.3",
"commit": "59f458205d7f5ec041a9dc11acc1122f128e827b",
"adapter": "claude-skill",
"sourcePath": "skills/artemis-automation",
"syncedAt": "2026-09-29T16:00:00Z"
}
@@ -1,37 +0,0 @@
---
name: artemis-automation
description: 使用 ARTEMIS MCP 在 Android 真机或模拟器上完成自然语言测试、批量用例执行、失败诊断、历史检索与 Word/Excel 报告交付。适用于用户希望 AI 操作或测试 Android 应用、导入测试用例或分析 ARTEMIS 执行结果。
---
# ARTEMIS 自动化
使用项目提供的 MCP 工具操作 Android。报告由 ARTEMIS 自己生成,不要求宿主 AI 具备 Codex 私有工具、Word 或 Excel 插件。该 Skill 不提供桌面/浏览器自动化,也不提供历史动作脚本回放。
## 从需求到执行
1. 明确目标、预期结果、目标应用和必要前置条件。用户已授权执行且信息足够时直接开展,不逐步索要批准;缺少会改变业务操作的条件时再询问。用例起点按实际需求安排,不默认清数据、重装、重启应用或退出登录。
2. 用 `mobile_workspace` 查看设备和队列。沿用用户已指定的设备;多台可用设备且没有选择时先询问,不擅自挑选实体手机。任务参数显式传入 `device_serial`。同设备串行,不同设备可并行;本 Skill 不要求创建其他 AI 子代理。
3. 首次使用、工具出错、任务拒绝或设备异常,先 `mobile_diagnose`。按 `next_steps` 的依赖顺序处理;`attempt_fix=true` 可尝试工具自带修复。只有凭据相关故障才使用 `verify_credentials=true`,观察异常才使用 `probe_device=true`。不要盲目重发任务。MCP 不可用时在项目目录运行 `uv run artemis doctor` 并检查宿主 MCP 日志。
4. 选择执行模式:Flash 适合路径明确的界面操作;Pro 适合动态分支、持续计划、ADB/日志诊断或需要 Checker 明确校验的任务。**`mobile_run_task.model` 的 `flash`/`pro` 是执行模式,不是 API 模型名称**。步数长不自动等于需要 Pro;视频能力只有显式启用后才可用。
5. 单任务用 `mobile_run_task`;批量任务用 `mobile_batch`,读 [批量任务与交付](references/batches.md)。AI 可直接提交结构化用例,不必先创建 Excel。用户提供 Excel 时先 `mobile_task_file(action="preview", path=绝对路径)`,修正所有行错误后提交。提示词列原样作为目标,前置约束列只作元数据。
6. 单任务返回 `trace_id` 后,用 `mobile_manage_task(action="status", trace_id=...)` 跟踪;网页任务也可用其任务编号。批次用 `mobile_batch(action="get", batch_id=...)`。需要人工补充时用 `pause`,其返回的 `pausing` 只表示请求已接收;轮询到 `task_paused` 才表示设备已释放。`inject_instruction` 会立即取消进行中的模型回复或只读诊断并重新观察手机,已开始的设备动作会先安全结束;暂停中提交则恢复。`guidance_history` 的 `pending/consumed` 区分提交和执行端领取,无新指导用 `resume`。恢复进入设备 FIFO 队尾并重新观察手机状态。尊重工具建议的等待周期,避免连续高频轮询;停滞或失败先诊断,不把同一业务任务自动重提。
## 结果、证据与交付
- 执行状态与业务判定分开:`completed` 只说明执行结束,不等于测试通过。检查工具返回的验证摘要、预期/实际结果,以及 `mobile_inspect_trace` 的步骤/截图。证据不足标记“未验证”;前置条件不满足说明阻塞原因,不能编造通过结论。
- 用 `mobile_export_report(session_id=...)` 获取单任务报告;`formats=["screenshots"]` 获取排序截图,`["video"]` 获取保存的录屏分段,`["all"]` 获取 Word、Excel 和所有可用资料的 ZIP。批次传 `batch_id` 获取汇总和各项任务报告的 ZIP。未指定格式时使用保存的 Word/Excel 偏好。执行中的导出只是当前快照。
- 工具返回 MCP 主机上的绝对文件路径和下载 URL。向用户交付真实生成的文件链接,并说明完成、失败、未验证、跳过数量及重要限制。不要只说“报告已生成”而不交付文件。
- 用 `mobile_search_history` 按名称、目标、编号或标签搜索全部历史;日期先按用户时区转换为 Unix 秒,`start` 含边界、`end` 不含边界。按返回的 `total/page/size` 翻页,不能把第一页当作全部历史。
- `mobile_inspect_trace` 是只读步骤证据查看;其中历史上下文的 replay 术语不表示重新执行设备动作。
## 配置和恢复边界
- `mobile_config` 读取非敏感设置。只有用户明确要求保存或修改默认配置时,才传 `changes` 与 `persist_defaults=true`;这些设置影响后续任务,不能为一次临时需求擅自改变以后所有任务。
- 模型名称支持预设 `gpt-5.6-luna`、`mimo-v2.6-flash` 和自定义名称,取决于用户配置的 Responses 网关。思考强度最高支持 `max`,默认 `high`、单次请求 300 秒、失败后最多重试 5 次、固定间隔 10 秒;默认关闭视频分析与录屏。不根据模型名称猜测网关能力。
- 强度选 `default` 表示不发送该参数,和 `none`(显式要求关闭)不同。接口拒绝强度参数时明确呈现失败,先询问用户是否改用 `default`,不能静默降级或换模型。
- API Key 只通过网页或 `.env` 配置。不要请求用户把 key 发到聊天中,不读取/回显已有 key。保存后新任务生效;直接编辑环境文件后需重启相应服务,运行中的任务保留自己的配置。
- 录屏与 AI 视频分析分别配置。批量任务的 `task_config` 可覆盖单项,不修改全局设置;只开视频分析时录制内容不作为可下载文件保存。暂停时停止录屏,恢复后按片段继续,不能把其他任务画面混入该任务资料。
- 请求重试是模型调用恢复,不是测试用例重跑。批次默认失败继续、不自动重跑;服务重启后保留状态并暂停,用户明确要求后才继续或复制失败项。
- 批次暂停只停止后续派发;当前任务等待指导时用 `mobile_manage_task(action="pause", trace_id=...)`,真正取消用 `stop`。用户明确要求删除批次记录时先 `mobile_batch(action="get")` 核对编号,再用 `delete`;执行中/排队中的批次不能删,删除会清除该批次的任务历史、报告和文件。服务重启后运行/暂停的任务标记中断,不自动重跑。停止可能保留已经产生的业务效果,不声称自动回滚。
需要编写外部可执行测试代码时,先通过 ARTEMIS 实际探索目标应用,确认页面、定位器和转换。优先动态定位,框架支持时才以已验证坐标兜底;在最终脚本中明确等待条件,不将 AI 思考耗时当作确定性等待。
@@ -1,4 +0,0 @@
interface:
display_name: "ARTEMIS Android 自动化"
short_description: "通过 MCP 完成 Android 单次与批量测试、失败诊断和报告交付"
default_prompt: "使用 $artemis-automation 检查设备,执行我指定的 Android 测试用例并交付有证据的报告。"
@@ -1,65 +0,0 @@
# 批量任务与报告交付
## 直接提交用例
用户要求执行明确的测试集合时,将每个独立提示词转成一个 case。`goal` 是唯一发送给 ARTEMIS 的任务正文;`case_id`、`category`、`subcategory`、`name`、`preconditions` 只作元数据。`task_config` 可覆盖该项执行配置,不影响其他项。
先生成唯一 `request_id`(例如 UUID),保存在当前任务记录中,然后调用:
```json
{
"action": "create",
"request_id": "52c3e95d-7e31-4e41-b42d-27aa3774fb2d",
"batch": {
"name": "系统设置冒烟测试",
"device_serial": "用户已选择的设备编号",
"profile": "pro",
"formats": ["docx", "xlsx"],
"stop_on_failure": false,
"timeout_seconds": 1800,
"cases": [
{
"case_id": "DEMO-001",
"category": "系统设置",
"subcategory": "首页",
"name": "设置首页可达",
"preconditions": "设备已解锁。",
"goal": "打开系统设置,确认设置首页可见,不修改任何设置;记录实际文字与截图证据。",
"task_config": {
"profile": "pro",
"model": "mimo-v2.6-flash",
"reasoning_effort": "high",
"locked_app_package": "com.android.settings",
"record_video": true,
"analyze_video": false,
"request_timeout_seconds": 300
}
}
]
}
}
```
`create` 只保存已校验的草稿。检查返回批次编号和范围,用户已经授权执行时接着 `mobile_batch(action="start", batch_id=...)`;用户只要求准备方案时留在草稿,不启动。保存 `batch_id`,用 `get` 跟踪各项状态。
创建请求超时可能已经落库。先 `lookup(request_id=...)`;需要重发时保留**相同 request_id 和相同内容**。服务会返回原批次,内容不同会拒绝。不要通过换编号“修复超时”,否则会形成重复批次。
## Excel 导入
`mobile_task_file(action="template")` 返回空白模板的本地路径。“任务”页七列依次为:用例编号、检测项目、测试子项、测试用例、前置约束提取、Artemis提示词、任务配置(JSON)。仅提示词必填;说明页有一条示例,不会导入执行。JSON 支持 `profile`、`model`、`reasoning_effort`、`device_serial`、`verification_level`、`explorer_mode`、`locked_app_package`、`record_video`、`analyze_video`、`request_timeout_seconds`、`max_retries`、`retry_interval_seconds`、`timeout_seconds`。未知字段或密钥被拒绝。每行独立,不支持公式、宏、外部链接。
`preview` 返回 `valid/tasks/errors`,不会执行。所有错误处理完后,将 `tasks` 中每项的预览字段 `row` 去掉,作为 `batch.cases` 提交。不要跳过错误行却声称全部导入成功。
## 状态与异常
- 批次 `draft` 等待开始;`running` 调度中;`paused` 等待处理;`completed` 表示所有项进入终态,不代表全部通过。
- 单项 `pending/queued/running/pausing/task_paused/resuming/finalizing` 尚未结束,`completed/failed/cancelled/interrupted/skipped` 是终态。`pausing` 是请求已发出而设备仍被占用,`task_paused` 才表示设备已释放;`finalizing` 在生成报告,不能提前宣称报告已完成。
- 批次 `pause` 保留当前执行,只暂停后续派发;单项 `mobile_manage_task(pause)` 会在安全边界释放设备,Pro 的模型等待与只读诊断可中断。恢复后重新观察画面,并排到设备队尾,不阻塞后续项。`mobile_manage_task(guidance_history)` 可核对指导是否从 `pending` 变为 `consumed`,但仍需检查后续操作是否符合指导。设备断开、模型持续失败或任务达到批次时限会暂停后续派发;用诊断确认恢复后再响应用户的继续指令。
- `resume` 只继续未开始项;`retry_failed` 复制失败、取消和中断项为新草稿,不修改原结果。必须来自用户的明确重跑请求。该动作不提供创建去重,响应不确定时先 `list` 查找新批次,不自动重复调用。
- 用户明确要求删除批次记录时,先 `get` 核对批次编号与状态,再 `delete`;正在执行、排队或等待指导的批次不能删除。删除会同时移除该批次任务历史、报告和文件,无法撤销。
## 报告
`mobile_export_report(batch_id=...)` 下载批次 ZIP,包含汇总和各项报告,格式由创建批次时的设置决定。单任务传 `session_id`(单任务 MCP 的 `trace_id`)以及可选 `formats=["docx"]`、`["screenshots"]`、`["video"]` 或 `["all"]`。默认格式通过 `mobile_config(section="reports")` 查看。录屏与 AI 视频分析独立,只有保存了录屏才可下载片段。
结果文件在 MCP 主机的 `artifacts/mcp_exports/`。不要把 Office 文件内容当作文本塞入上下文;先检查工具返回的结构化结论,需要进一步审阅时再用宿主具备的文档工具打开文件。失败和未验证项也必须交付,保持原始证据。
@@ -1,6 +1,6 @@
{
"name": "artemis-case-prep",
"version": "1.1.0",
"version": "1.2.0",
"description": "从 Android 原始测试工作簿筛选可由 ARTEMIS 自动化的用例,标记原表副本并生成七列任务表。",
"author": {
"name": "hejunxian",
@@ -1,9 +1,9 @@
{
"sourceId": "artemis-case-prep",
"repo": "https://git.playones.com/hejunxian/artemis-case-prep.git",
"ref": "v1.1.0",
"commit": "e1e094513f4b9360231022f6d18a0ebc6a04dc7d",
"ref": "v1.2.0",
"commit": "f0b9fc6efac82f825ffc27051a04d47f22ba5362",
"adapter": "claude-skill",
"sourcePath": ".",
"syncedAt": "2026-09-28T10:12:45Z"
"syncedAt": "2026-10-21T06:48:34Z"
}
@@ -1,12 +1,16 @@
---
name: artemis-case-prep
description: 从多工作表原始 Android 测试用例中筛选适用于目标设备且可由 ARTEMIS 自动执行的用例,核实测试环境和用例信息后标记原表副本,并填写七列批量任务模板。用于准备任务文档,不用于直接运行批次或改造导入器。
description: 从多工作表原始 Android 测试用例中筛选适用于目标设备且可由 ARTEMIS 自动执行的用例,核实测试环境和用例信息后标记原表副本,并填写七列批量任务模板。也用于依据已有 ARTEMIS 运行证据修订任务提示词;不用于直接运行批次或改造导入器。
metadata:
version: "1.2.0"
---
# ARTEMIS 用例准备
把原始测试用例工作簿转成两份可追溯的文件:保留原有内容并新增“是否选中作为自动化执行”(`是`/`否`)列的原表副本,以及只包含选中用例的七列任务表。优先使用用户提供且符合下述七列表头的模板;未提供时使用 [七列模板](assets/artemis-tasks.xlsx)。不要覆盖输入文件。
首次转换按下述适用性与信息完整性要求准备正式文件;已有任务表复盘时,按 [依据已有运行修订提示词](#依据已有运行修订提示词) 处理。参考文件中的历史环境、批次、条数、路径和用户确认只在可对应的原项目内适用,不能代替当前用户的确认。仅要求更新 skill 时只修改技能文件。
## 先确认适用性,再判断自动化
1. 逐页识别真正的用例表、表头、续行、合并单元格及行级适用性;不要以固定列号、`max_row` 或单独的用例编号推断用例。行政清单、报告页和空白格式行不算用例。遇到本项目附件的异构页时,读 [字段与边界](references/workbook-contract.md)。其他原始文档仍须按其实际结构重新识别。
@@ -22,24 +26,54 @@ description: 从多工作表原始 Android 测试用例中筛选适用于目标
- 在原表**副本**的用例表增加且只增加所要求的自动化标记列(若已有此列则更新,不重复新增);每条用例填 `是` 或 `否`。先核对目标位置不会覆盖既有数据,保留原工作表、原文、图片、合并、格式及非用例页。若所选表格工具不能保真,停止交付并说明损失,不输出貌似完整的副本。遵循可用的 spreadsheet 工作流进行编辑、渲染与导出检查。
- 任务表严格沿用 `任务` 页的七列顺序:`用例编号 / 检测项目 / 测试子项 / 测试用例 / 前置约束提取 / Artemis提示词 / 任务配置(JSON)`。只写入适用且自动化列为“是”、提示词所需信息已补齐的用例;其他工作表保持模板原样。`用例编号` 只填写原表显示的编号,不附 `(Sheet1!2)`、`〔来源页!行号〕` 等工作表/行号后缀;原表编号不改。重复编号保持原样,处理时另以来源工作表和行号定位用例。`Artemis提示词` 是唯一提交给 AI 的任务正文,其他文本列只作元数据,不自动拼入提示词。
- `任务配置(JSON)` 可留空,表示继承批次或全局配置;确需逐项覆盖时,只写当前 ARTEMIS 导入器支持的字段和值,使用合法 JSON 对象,不写密钥、占位值或未经确认的设备序列号。模拟器名称和被测设备名称不能当作 `device_serial`;填写前核对实际序列号。字段及导入边界见 [七列契约](references/workbook-contract.md#模板)。
- “前置约束提取”写目标测试 App 版本及原文实际要求的设备、账号、网络和状态;一致性用例还写明比较字段与独立预期值/来源,草稿中可明确标出待补项,但不把未确认条件写成已满足。“Artemis提示词”**必须以 `【原表显示的用例编号】` 紧接原“测试用例”文本开头**,随后接完整执行说明;例如 `【1.1】【声波配网】手动长按重置恢复出厂后首次绑定完成配网流程` 后另起执行说明。这里的连接是文本拼接,不写字面 `+`;提示词前缀不附来源页/行号。执行说明写明沿用测试开始前统一核验的 App 版本,不要求每条用例再次检查;还须写明起点、操作意图、必要等待/状态转移、可观察的预期及判定方式;一致性断言须明确实际值与哪个预期值比较。直播页的分辨率切换、全屏、声音开启/关闭等目标控件初次未显示时,可先观察画面、轻点直播画面一次并重新观察可能浮现的控件或二级菜单;声音开关仅以可观察的 UI 状态核验,不把需要人工听音的结果判为自动化可核验。卡回看和云回看可将有颜色区域作为有数据的选时线索、空白区域作为无数据线索;选有数据片段时先选择过去且有颜色的区域,再用回放结果核验,不仅凭颜色判通过,也不设计拖动到未来。**仅移动爱家 App** 的卡回放用例可在点击“云回看”后观察是否出现切换入口。以上均是适用场景的条件性提示,不发明 UI 定位器、页面路径、精确坐标或功能。无可用原编号或测试用例文本时列出来源行请用户补齐,不造编号或标题。给 Codex 的通用截图、录屏、证据归档及结论要求不写入“Artemis提示词”;更多写法见 [字段与边界](references/workbook-contract.md)。
- “前置约束提取”及每条正式“Artemis提示词”都要写明已确认的模拟器名称或真机环境、测试 App 名称与版本、被测设备名称。执行说明沿用测试开始前统一核验的安装版本,不要求逐条再查。用例缺失的操作、前置或预期信息只列在单独的待补清单中;补齐前不写该条提示词,也不将占位内容放进任务表。
- 每条“Artemis提示词”在完成本用例的操作和结果观察后,要求返回目标 App 内的设备首页并确认首页可见;用例失败或未验证时也应尝试返回。不要把 Android 系统桌面当成设备首页,也不要编造返回路径。若因解绑、页面异常等原因无法返回,说明最终页面和原因。Codex 在开始下一条前核对设备首页已恢复;未恢复时先处理状态问题,避免用例相互干扰。
- **移动爱家 App** 点击“设置”进入设置页后,先等待页面刷新出实际设置内容并重新观察,确认目标控件属于刷新后的页面,再进行点击或滑动;不能把刚进入时的空白、占位或旧页面当作最终内容。设置页功能项较多;相关用例在已加载的首屏未找到目标时,提示词应允许向上滑动更长距离以查看下方内容,并在每次滑动后重新观察和定位。不要把首屏未见当作功能缺失,也不要规定未经核实的固定等待时间、坐标、距离或盲滑次数;见 [设置页查找边界](references/workbook-contract.md#提示词与决策)。
- 核对原表每条真实用例的标记均为 `是`/`否`,待确认条目全部处理后,任务表数据行数恰等于“是”的数量;逐行检查编号列只含原表显示的编号、提示词的编号和用例文本前缀,且每条提示词都要求结束时返回 App 内设备首页;核对模拟器或真机环境、App 名称与版本、被测设备名称与用户确认值一致,且提示词没有逐条核验版本、通用截图录屏、证据归档或结论指令。第七列应为空或为可解析、仅含受支持字段的 JSON 对象。抽查一致性用例有独立参照且比较字段明确,再抽查不同表头布局、重复编号、合并/续行和排除项。确认输入文件未改、两份输出的非目标内容未意外变化,且七列表头、提示词与来源可回溯。
- “前置约束提取”及每条正式“Artemis提示词”均须包含已确认的模拟器名称或真机环境、测试 App 名称与版本、被测设备名称,以及本条实际所需的账号、网络、状态等前置;一致性用例写明比较字段及独立预期值/来源。可在执行时只读取得的既有配置、实际名称和本次操作起止,不强制用户事先手填;独立预期、适用性、外部场景等仍须落实。尚缺资料只列单独待补清单,未补齐时不编写该条正式提示词,不把占位内容放进任务表。
- “Artemis提示词”必须以 `【原表显示的用例编号】` 紧接原“测试用例”文本(仅去首尾多余空白)开头,再另起完整说明。例如 `【1.1】【声波配网】手动长按重置恢复出厂后首次绑定完成配网流程`;连接是文本拼接,不写字面 `+`,不附来源页/行号。原编号或用例文本缺失时请用户补齐,不造编号或标题。执行说明写明沿用测试前统一核验的 App 版本、实际起点、操作意图与顺序、必要等待/状态转移、可观察预期及判定方式,不要求逐条再查版本。保留原量词、时长、观察端和判据,不发明界面路径、定位器、坐标或功能,不把相邻用例动作塞入共用段。
- 先按 [用例完成检查与收尾](references/workbook-contract.md#用例完成检查与收尾) 逐项核对本条必需操作和结果观察。某项失败、弹窗文案不同或缺少部分断言依据时,保留该项结果,继续可独立执行的操作与回读;仍能补做的漏项先补完。全部必要项完成,或剩余项有具体阻碍且有限恢复无效,或用户取消/实际预算终止时,再收尾。“失败或未验证也收尾”不表示首个偏差即可结束。
- **移动爱家用例保留强制停止收尾**:完成本条操作和观察、达到上述退出条件后,强制停止 `com.cmri.universalapp`;失败或未验证同样执行。执行端有实际可用的应用停止能力时可直接使用,支持 ADB 时可用 `am force-stop com.cmri.universalapp`。执行端不支持或停止失败,由 Codex 在本条结束、设备空闲且下一条尚未启动时通过宿主 ADB 补做;开始下一条前确认主进程及 `com.cmri.universalapp:` 前缀子进程均已停止,未确认则先处理收尾。下一条重新启动 App、等待加载并建立本条前置。用户当前明确另有收尾安排时按其要求;其他 App 按该任务约定,未约定时返回 App 内设备首页并确认,不误用移动爱家包名,也不把系统桌面当设备首页。
- 按本条场景读取下表,采用实际适用的操作,不把专项数值和历史入口推广为所有用例的默认值。
本七列模板与当前查看的 ARTEMIS `task_import.py` 表头一致,但不能仅凭表头声称文件已通过导入。用户将来要求执行时,先用当时服务的预览校验,再按执行授权启动;本 skill 不改导入器、不调用 `mobile_batch`、不启动设备。编写外部可执行测试代码则先用 ARTEMIS 探索实际应用。
| 场景 | 需要读取及核对的规则 |
| --- | --- |
| 直播清晰度、全屏、声音等短时浮层 | [短时浮层与连续操作](references/transient-controls.md):首次用同动作早帧识别入口,核对当前布局;已定位入口在实际支持的短序列内完成唤出与点击。声音图标只证明 UI 状态,不能代替听音;旧早帧不是当前状态。 |
| 设置长列表、表单、云台、计划、图片操作 | [提示词与决策](references/workbook-contract.md#提示词与决策) 及 [设置页查找](references/workbook-contract.md#设置页查找与到底确认):移动爱家设置先等实际内容刷新;未见目标时按实际进展继续查看下方及相关子页,核实到底,不固定盲滑次数。仅对已对应项目采用16.1等用户专项例外。 |
| 云回看、卡回放与选时 | [回看选时](references/workbook-contract.md#提示词与决策):按实际存储类型、日期、时区与过去区间选时;颜色只作线索,灰色也可能有数据,以回读与实际播放核验。难定位时可点击实际可见“+”放大时间轴,再拖蓝色光标并回读,精确时间要求不放宽。仅移动爱家卡回放可在“云回看”后观察切换入口。本次生成型回看先完成规定档位及真实时长,再查对应新录像,不用旧片段替代。 |
| 分辨率截图与相册核验 | [设备截图分辨率与相册查看](references/workbook-contract.md#设备截图分辨率与相册查看):识别本次截图原图、读取宽×高,与已确认预期比较;核对本条有效应用锁定配置允许切换相册,核验后按本条约定收尾。 |
| 持续播放、等待、录像与预算 | [长时任务预算与实际计时](references/workbook-contract.md#长时任务预算与实际计时):用可靠起点、截止和剩余时长,保留原业务时长;单条有效预算须包含启动、定位、测量、有限恢复和收尾。30分钟业务不能仅给1800秒总时限,可按实际流程在受支持的单条配置覆盖如 `{"timeout_seconds":3600}`,保留其他配置,不改全局默认。 |
| 既有编码、AOV、云/卡回看专项 | [编码与回看补充](references/encoding-playback-feedback.md) 及 [既有项目反馈](references/project-feedback.md):按对应原文、版本和用户确认应用,区分 App 业务录像与操作录屏、各组合实际计时、设置回读和业务效果。 |
- 给 Codex 的通用截图、录屏、证据归档、结论和模型诊断要求不写入每条“Artemis提示词”。用户明确指定的画面截图、操作视频、日志地址/文件等本条交付物,可以写明动作、数量、时段及关联方式;操作录屏必须通过受支持的单条配置实际开启,不能只写提示词,也不以 App 内录像替代。采集完成与业务通过分别核验。
- 正式输出前确认所有真实用例的自动化标记均为 `是`/`否`、所有待确认项已处理,任务数据行数恰等于“是”的数量。逐行核对原编号、提示词原文前缀、四项环境、原动作/判据及约定收尾;移动爱家包名和进程检查准确。第七列为空或为仅含支持字段的可解析 JSON 对象。抽查独立比较参照、异构表头、重复编号、合并/续行和排除项;核实输入未改、非目标内容保真、模板七列及来源可追溯。提示词不得出现逐条查版本、通用归档或模型诊断口令。
本七列模板沿用已查看的 ARTEMIS `task_import.py` 契约,不能仅凭表头声称已导入。用户以后要求执行时,由执行方用当时服务的预览校验核实真实支持字段和逐行错误,再按执行授权启动。本 skill 不改导入器、不调用 `mobile_batch`、不启动设备;编写外部可执行测试代码前须用 ARTEMIS 探索实际应用。
## 依据已有运行修订提示词
用户要求复盘任务或重出既有任务表时,沿稳定 batch ID、session/trace ID 与原用例对应,读取 [已有任务表复盘修订](references/workbook-contract.md#已有任务表复盘修订)。分别保留原终态、业务判定、Checker、实际步骤和复核结论,按证据修正本条的范围、前置、定位、时序和断言;用户新确认路径不冒充旧 trace 已走通,历史 `completed` 不证明业务通过。发现提示词、执行器观测或模型链路问题时分别记录,技能文字修改不代表后端修复或设备复测完成。
默认以用户指定或最新已交付的七列 `.xlsx` 为底稿,只回填受影响行的完整提示词和必要单条配置,交付保留其他用例、原编号/顺序、环境、辅助页、样式及批注的新副本,不覆盖底稿。上次交付后已定稿且尚未回填的相关修订一并带入。用户明确仅要单条或其他格式时按其要求。只有用户明确要求全量重审才逐条改写全量,不因单条优化、更新 skill 或历史完成状态自动增删集合。
用户明确改为操作、指定截图/视频/日志采集及人工判断时,按新范围编写,保留原判据及人工待判,不用首次全自动筛选规则排除整条,也不把采集成功写成产品通过。暂不适用用例从可导入的“任务”页移出,不伪造 `enabled`/`skip` 字段。用户要求保留全量但条件未齐时,可另交明确标识的审阅稿和待补清单;不得混为正式可派发表或自动改原表标记。
仅在当前请求可明确对应来源项目时,读 [既有项目反馈与专项修订](references/project-feedback.md);其中68条集合、删除清单、固定环境、V6/V7、数值、特殊入口及桌面命名只用于该项目,具体规则与 [覆盖核验](references/workbook-contract.md#本轮68条重出与覆盖核验) 对照。新项目按实际资料重新识别;交付位置沿用当前请求或工作区输出约定。没有当前底稿而需回填时再核实来源,不因只修改 skill 自动生成任务表。
## Codex 执行与证据归档(不写入 Artemis 提示词)
后续正式执行前,执行方须实际确认登录状态、目标设备可见及本轮所需在线状态,不能只核验安装版本就视为执行环境已就绪。用户已授权自动登录且存在现有账号、已保存凭据或可用免密入口时,按实际界面尝试并观察反馈;密码框显示占位文字或看似为空,不足以断定缺少凭据。只有实际登录反馈或必要输入明确缺失时才记录对应阻碍,不猜造密码、验证码或登录路径。登录成功与本轮所需设备状态分别核验;用例或本轮要求核对固件时,另按已确认目标核验。共同的登录障碍应作为环境问题关联受影响用例。
以下是已有任务表的后续执行约定,不替代前文准备正式任务表的待补要求。用户已明确“全量处理、遇阻先跳过并继续”时,执行方在派发前核对人工、实物或外部工具条件;可独立执行的部分继续核验,核心动作或断言全部依赖未具备条件的条目直接记录跳过/阻塞、缺失条件与未执行分支,不交给 Flash 等待其解决外部条件。“全量处理”应保留每条用例记录,不表示条件已具备或断言已通过。沿用用户已选安排,不逐条重复索取同一条件,不将受阻项判为产品失败或通过。
后续执行测试时,Codex 在关键操作前后截图并保留本任务录屏;记录证据路径,以便归档到工作区“测试佐证”目录。若工具只能保存到 ARTEMIS trace,报告 trace 和证据路径。前置条件不成立、控件未找到或证据不能证明预期时,如实报告阻塞或未验证,不写通过。这些要求由 Codex 处理,不拼入任务表每条“Artemis提示词”。
短时控件“点击后未见”时,执行方按 [瞬态取证与诊断](references/transient-controls.md#执行方的取证与诊断) 对齐真实落点、首次有效画面或已有录像及后续观测时序,区分点错位置、曾显示后隐藏和未取得短时证据。保留即时截图、调整观测策略及诊断 helper/UIAutomator2 属于执行方职责;本 skill 只编写相关操作意图与交接要求,不启动设备、修改后端或改变全局默认。
执行方先区分已声明的业务等待与模型停滞,不能只因 UI 步骤不增长就中断合法计时。模型请求反复超时、仅间歇恢复少量动作时,也按 [异常超时复盘](references/workbook-contract.md#已有任务表复盘修订) 核查跨回合累计耗时、实际目标进度、预算及收尾,不能用一次成功点击或短等待清除异常审计,不把等待模型当作业务观察;长时业务则核对实际等待参数、截止时间及剩余总预算。异常工具参数、重试和停滞监控由执行方处理,不向每条业务提示词追加自我催促、重试口令或固定模型等待;本 skill 的文字修订不能代替执行层的参数校验与超时修复。
## 执行状态显示取消但之后完成
ARTEMIS 执行期间,网页或列表可能显示“取消”/“正在取消”,而之后的持久批次或任务记录又显示已完成。不要依据单次界面快照判最终状态,也不要把“已完成”直接等同于测试通过。执行方应按稳定的批次 ID、session/trace ID 重新读取 ARTEMIS 持久状态,确认取消是否只是请求/过渡状态、任务最终状态,以及报告是否已就绪;检查应遵从工具建议并保持低频,避免连续轮询。
- 最终持久记录为 `completed` 且报告已就绪时,核对 trace/报告中的实际步骤、证据和业务判定,再分别记录通过、失败或未验证。
- 最终持久记录为 `cancelled` 时,它是终态;保留取消记录及已产生的部分证据,不得改写成完成,也不要因界面状态不一致而自动重提同一用例。重跑须有明确授权,并作为新的执行记录。
- 最终持久记录为 `cancelled` 时,它是终态;保留取消记录及已产生的部分证据,不得改写成完成,也不要因界面状态不一致而自动重提同一用例。确认终态后,按同一运行 ID 核对最新保存步骤及对应画面;若检查或取消期间又完成了动作,更新已执行范围,不沿用取消前快照的未执行结论。重跑须有明确授权,并作为新的执行记录。
- 若批次、session 与网页显示互相矛盾,或报告尚未就绪,记录各自 ID、状态和检查时间,做必要的低频只读核验;矛盾未消除前标记“状态待核实”,不得统计为通过或已完成。
此规则只用于解释执行记录,不改变本 skill 的职责范围:本 skill 负责准备与核验用例文档,不负责启动、取消、恢复或重跑 ARTEMIS 任务。
@@ -0,0 +1,92 @@
# 编码、直播录像与回看补充
用于依据用户 2026-10-20 对编码与回看运行的两轮反馈修订任务表,第二轮重点为9.101、9.122、9.139和12.14。本文的数值、路径及日期/刻度是这些用例在本轮已确认的要求,其他设备、App 或用例按自己的原文与实际界面确定;不将示例升级为全局默认。运行 ID 用于对应原行,用户反馈与实际 trace 分别注明来源,不把补充要求写成旧运行已执行或通过的证据。
## 写入任务表的共同规则
- 每个被测状态均完成“设置并保存回读 → 在该状态仍生效时观察直播或录制 → 再切下一态”。开关成功与直播正常分别验收,末态直播正常不能补证之前各态。缺少静态压降测量条件时继续可独立完成的直播观察,画面正常也不代替真实码率效果。
- 本轮设备编码设置入口是“设备设置页下方 → 自定义编码参数”;9.139第二轮用户称“设备设置 → 自定义码率参数”,按该节核实当前入口标签与实际编码字段。直播清晰度仍在直播页选择。未定位时按 [设置页查找](workbook-contract.md#设置页查找与到底确认) 继续核实,不能用近名页面或设置页分辨率代替直播档位。短时浮层按 [连续操作规则](transient-controls.md) 定位与操作。
- 直播检查须进入同设备并等到真实出图,用多时点画面/播放反馈观察是否持续播放,记录花屏、黑屏、绿屏、卡住、持续加载等实际现象。按钮、单帧或“设置成功”不能单独证明画面正常;不凭空增加固定观察时长、听音结论或压降阈值。
- App 内直播录像与 ARTEMIS 操作录屏分开。业务要求录制视频时,实际定位录像控件、点击开始并确认录像计时/录制中反馈,以确认开始的时点计时,持续规定时长后实际点击停止并确认保存。按可靠时钟核验经过时间,有可取得媒体时再核验有效时长;开始、计满、停止、保存四项分别核对,不能把等待回执、模型耗时或自动化屏幕录屏当成本次录像。录像存在与可播放、画面判据分别确认,缺档、短片、未保存或时长缺证分别记缺项。
- 本轮12.14的录像按钮由用户确认位于直播页面右下角;这是当前App的定位线索,不是固定坐标。两条漏录运行的当前UI已有“开始录像”无障碍描述,虽然该节点资源ID为空,仍应按当前文字/无障碍语义及实际控件关联定位,不能因没有resource-id就跳过。按实际标签、图标和点击后计时/录制中反馈确认身份,不能把声音/静音开关、拍照或截图按钮当成录像;误点须重新定位并实际启动录像,未见计时或录制反馈不能直接进入业务等待。已实际确认同设备直播录像入口时可在后续段复用其语义位置,每段仍核对当前控件及录制反馈。
- “尚未尝试”与“实际受阻”分开:缺少可信默认、SDK/RTSP、码率分析、媒体提取或长期挂测条件,只保留其对应断言缺项;无实际阻碍时继续App内可做的录像定位、开始、真实计时、停止保存与其余组合。计划中写过录像、观察直播若干秒、因未尝试而称无法录制,均不算已完成或已证明受阻。仅在当前实际尝试及有限纠正后仍有明确控件、加载、状态冲突或工具障碍,或用户取消/实际预算终止时保留受影响段与具体反馈;不得把未验证清单当作省略可做动作的理由。
- 收尾前按状态/组合核对覆盖,保留原用例的其他有效动作、断言及收尾;单项偏差不提前停止独立可做项。不把新增要求叠加成重复录制或删去原有范围。未明确缩减集合时,改写只回填对应行,保留用户指定底稿的全部用例、编号、顺序、样式、辅助页与其他配置;用户明确只要本轮条目时按其指定集合输出。
## 9.101:两种码率模式与六段30秒录像
对应首轮运行 `26415f37-4870-4886-8735-a4f35061f9b4`、第二轮运行 `670f780a-513a-4236-b4a2-dd4f7e9d8252`。用户第二轮再次指出缺少直播30秒录像,因此本条须把六段App内录像写成独立必做阶段,不能只强化码率设置或直播观察。本轮已经给出具体目标,不再沿用“任选合法且不同于当前的码率”替代这些值。
第二轮实际因清晰度未确认而六段均未录,第23、24、25、42步已有“开始录像”无障碍描述但未实际点击,不能把这一缺口改写成录像能力已尝试且失败。后续按短时控件规则完成当前页清晰度定位与选择;旧运行未取得清晰度浮层证据,不能臆断为浮层曾显示后隐藏。当前档位已能确认时先完成其可做录像;某档确实无法确认时保留该组合未验证,继续其余可做档位及码率分支,不由一个未确认档位推定六段全部不可录。若经有限定位清晰度仍不能确认、但当前App录像入口可用,至少尝试一段原定30秒的开始、计时、停止保存,标明清晰度未确认而不计入任何指定组合;不为此强制重复六段未知档位录像。
| 设置阶段 | 本轮目标 | 直播页逐档录像 |
| --- | --- | --- |
| VBR | 平均值 2000、峰值 2300 | 标清、高清、超清各独立录制至少完整30秒 |
| CBR | 平均值 2000 | 标清、高清、超清各独立录制至少完整30秒 |
先记录原模式、字段、单位与开关状态,实际选择 VBR 并分别填写平均/峰值,核对必要保存及重新进入回读;不能只改一个字段或把目标值写进记录而未操作。保持该设置回到直播,依次核对标清、高清、超清选中及真实播放,每档实际点击App录像按钮、确认开始计时,持续录制至少完整30秒,再实际停止并确认保存后切档。完成 VBR 三段后,回到码率页选择 CBR、填写平均值2000并保存回读,再按标清、高清、超清完成同样三段,共六段。仅看直播30秒、切换三档或开启操作录屏均不算本条录像。CBR 未提供峰值字段时不编造该字段,不把 VBR 峰值继续当 CBR 验收项;字段单位与合法范围依实际页面记录,目标不可接受时保留该分支受阻,不擅自改数值或单位。
每段关联模式、实际设置值、直播档位、录制起止、实际计时及保存结果,能取得媒体时补核有效时长。收尾前按VBR/CBR各三档逐段检查四项动作,缺哪个阶段就补做仍可做的该阶段,不以“码率设置完成”覆盖六段录像。基础录制时长为 `6×30=180秒`,总预算还须覆盖设置、切换、加载和收尾;不从180秒直接推出总任务时限。保留原智能编码自动关闭及厂复默认分支的证据边界,按 [码率原范围](workbook-contract.md#用户确认路径与人工判定采集) 分别核验,缺少厂复默认依据不阻断本轮六段录像,不为补录擅自恢复出厂,也不追加12.14的实测码率或5分钟要求。
## 9.122:全部实际侦测关闭、AOV与退出后返回
对应首轮运行 `a80f256c-0b79-4496-8783-5a5ac5c230e2`、第二轮运行 `716e7b55-f60b-442b-b6c9-cc3d9fb8aeb4`。第二轮用户明确目标为AOV,不是智能编码。先在设备设置检查本设备实际存在的所有侦测/跟踪及 AI 功能,逐项读取并关闭、完成必要保存回读,再开启 AOV;不能只关移动侦测和声音侦测就宣布“全部已关”。
查找范围包括本轮用户列出的:人形侦测、移动侦测、声音侦测、人脸侦测、电瓶车识别、车辆识别、移动追踪、AI周界、AI设置等。它们是查找范围示例,按主设置页及真实子页核对;AI设置或 AI周界如为分组,进入关闭实际存在且可操作的相关算法/功能,并回读状态。近名项目不互相替代,不能把分组入口当成单一开关。找到的新侦测功能也纳入本条“全部实际存在”范围;未见项记录已查范围与尚未确认项,不臆造开关、不把未查写成不存在,不关闭与本条无关的安全或系统功能。
在“自定义编码参数”先读取并区分“AOV”和“智能编码”各自的标签、当前状态及所关联开关,实际操作明确属于“AOV”这一行的控件;不能凭相邻位置、颜色或已开启的智能编码推定AOV已开。AOV已开启则核验保持;关闭时开启AOV,并在必要保存后重新进入,回读同一“AOV”行确为开启,再退出App。AOV未定位时继续本页相关查找并记录缺口,不开启智能编码充当替代,也不把误点后的智能编码状态写作AOV生效。两功能身份未核实时不得声称本条前置已建立。
第二轮第24、26、27步实际点击相邻智能编码开关,智能编码多次变化而AOV一直关闭。两行共用 `setting_switch` 资源ID,单凭该ID或缓存坐标不能定位AOV;应将当前“AOV”可见文字与其所在行开关绑定,动作后回读同一行。该证据支持目标误定位与AOV未开启,不支持AOV开关存在产品故障;旧点击坐标不写入新提示词。
以实际退出并确认离开 App 的时点开始计时,保持不进入该 App 满60秒。设置页等待及App仍在前台的时段不算退出等待,也不能凭模型回合数或估计处理耗时推定已满60秒;退出状态实际持续期间经过的时间按可靠时钟计入;不为“退出”额外清数据、重装或默认强制停止,具体退出动作沿已确认安排。满60秒后重新进入 App、打开同设备直播,核实真实出图、持续播放及异常。分别记录侦测关闭覆盖、明确的AOV标签与开启回读、退出起止/60秒、返回直播结果;不能将智能编码开启记录移作AOV证据。
既有原文或用户仍保留的码率设置、真实动/静场景、不开直播挂测及对应生成视频“正常播放、无快播”继续作为独立范围;不凭本轮补充新增未指定码率。若仍需码率配置,在关闭侦测后、开启 AOV 前完成,记录 AOV 开启后的实际参数,不反复改码率干扰 AOV。60秒退出与直播检查是本轮必做的明确链路,不以“长期挂测时长未提供”省略;60秒也不自动证明其他原挂测条件或生成流通过。收尾恢复在直播结果观察后按既定安排执行。
## 9.137、12.29、12.3:每个规定智能编码状态都看直播
| 编号与对应运行 | 必须增加的观察阶段 | 仍独立保留的原范围 |
| --- | --- | --- |
| 9.137;`1feee1ea-4ae9-4233-b248-21c760575ad5` | 开启并回读后进入直播观察;再关闭并回读后再次进入直播观察 | 开关、可信默认及真实静态压降等原断言 |
| 12.29;`b659b7be-292c-4537-a7e9-6c90e30c7d18` | 开启态和关闭态分别观察直播花屏等异常,关闭后的观察完成再恢复 | 能力/配置项上报、默认、两态实际静态输出等 |
| 12.3;`fa5aef3c-93c1-4f16-88bf-f0d2a6c8cb76` | 开启并保存回读后进入直播观察花屏等异常 | 真实静态场景输出及关联效果标准;本条不增加关闭态 |
本轮9.137、12.29及12.3已确认“智能编码”就是原文的“静态码率压降”开关,不需寻找额外独立同名控件。12.3已回读智能编码开启时可核验并保持、继续直播和效果观察,不能因无另一个字面名为静态码率压降的控件将开启态判缺失;本字段映射限已确认设备/App,不推广为其他近名设置均等价。每一态的直播观察在该态仍生效期间完成。9.137、12.29不能仅在两次开关后最后看一次直播;12.3不能因为共用入口就增加关闭/默认或四组合录像。初始已开可核验保持并观察开启态,不为凑状态变化先关闭;继续按原顺序完成本条所需后续状态。真实测量和直播画面两类缺项分别记录,详情沿 [各条原断言](workbook-contract.md#用户确认路径与人工判定采集)。
## 9.139:参数变更后的两次重置提示分支
对应首轮运行 `dba4d0f0-baad-4962-83f9-ad26bb7fdc9d`、第二轮运行 `17086c4d-c92b-42fc-bb3c-31b5da99779e`。第二轮用户明确入口为“设备设置 → 自定义码率参数”。上轮同类入口称“自定义编码参数”;按当前实际标签进入,并以码率、帧率、I帧间隔及AOV/智能编码等实际编码字段核实是否为该目标页,仅文案差异不另跑通用AI或画面页,名称相近也不能单独证明页面等同。未定位时在设备设置列表按已确认路径继续查找,不以“画面与声音”“AI设置”或直播清晰度替代参数入口;实际入口名与用户线索分别记录,不倒填第二轮trace已走通。
第二轮实际进入的是设备能力集原始Config展示页,未进入可操作的自定义参数页。只读能力/配置文本只能提供实际展示的信息,不能代替本条参数输入、开关操作、弹窗触发及保存回读;发现进入该页后应返回设备设置沿目标入口继续,不在原始文本中寻找交互开关或直接结束互斥验证。
完成原码率/帧率/I帧间隔参数变更及自动关闭分支后,仍须实际开启 AOV 或智能编码触发恢复提示,不能在参数修改后结束。按原量词任选可用参数变更,不因本轮列出三项就强制全参数遍历。
两个功能分别在本次有效参数变更前置下开启。有可信默认时选合法非默认值;缺少默认依据时选合法且不同于变更前的值,实际变更、保存回读后继续弹窗及三字段核验,仅“恢复成默认”比较待核,不能因无法证明非默认而跳过两分支。参数变更触发保存/应用时,先读取当前自动关闭弹窗全文,再确认并完成保存、重新进入回读,不能先离页再回头核对已消失的提示。
随后分别开启AOV、智能编码,各自在离页前核对完整原预期弹窗“设置此功能,码率、帧率、I帧间隔设置将恢复成默认值”,记录实际全文及有无提示,再确认并回读码率、帧率、I帧间隔三项。前一分支已恢复参数时,应重新建立有效参数变更才做后一分支,不能在无变更时开第二个开关并认定重置提示已验证;实际参数变更无法保存或生效时才保留对应触发前置缺口。弹窗、开启状态及三参数默认比较分别验收;前段自动关闭提示即使漏写AOV或其他文案不同,也应保留该差异并继续后面AOV、智能编码各自恢复分支,不能以首个提示不符为由提前收尾。文案差异同样不阻断可做回读;可信默认依据缺少时读回值不冒称默认。保持 [完整互斥核验](workbook-contract.md#编码参数互斥与完整核验9139) 的参数修改前后态、自动关闭、直接关闭及收尾要求,AOV和智能编码不被误设成彼此互斥。
## 12.14:智能编码下四组合各录5分钟
首轮用户称“9.14”,但稳定运行 `5bbcedfe-efa5-4a71-8a6f-0751f4f89ece` 的 `sessions.initial_goal` 以 `【12.14】码率测试` 开头,指定底稿也为12.14;用户已确认优化原12.14并保留编号。第二轮对应运行 `d064bf8c-0118-46ed-8d8b-05de415dd8c5`,用户再次明确必须在直播页录像,按钮位于右下角;该位置作为当前App线索按当前布局核实,不写固定坐标。
第二轮第15、16、29步实际页面已有“开始录像”无障碍描述,但四段均未开始;第16步点“相机”后出现预览缩略图,属于拍照反馈,不是录像开始。该轮仅在自定义分辨率面板看到960×540、2304×1296、2560×1440选项,未选直播高清/超清,不能据此声称直播档位切换已完成。不能以码率分析条件不足或时长等待尚未执行推导App内录像不可做。新提示词须落到实际录像动作和四段时长,未尝试、未录、未保存分别保留,不把历史业务结束状态等同录像覆盖完成。
开启智能编码并保存回读,在该设置页核对码流/编码格式切换,严格按以下顺序完成四段:`H265×高清 → H265×超清 → H264×高清 → H264×超清`。先选择H265并回读,回同设备App直播页选高清,等真实出图,在右下区域定位实际录像按钮并点击;核实不是声音或拍照功能,确认本段录像计时/录制中反馈后开始可靠计时,连续完整300秒,再实际点击停止并确认保存。完成后再切超清并重复完整动作。两段H265均结束后回设置页切H264、保存回读,再在App直播页按高清、超清分别完成同样的开始、完整300秒、停止保存,共四次App内直播录像。四个组合逐段关联实际codec、档位、智能编码状态、起止、时长及保存结果,不能仅查看切换菜单、直播播放5分钟、把一段复制为四段或录制中途切档。
本轮明确要求在App直播页完成上述四次录像。RTSP等实时码流采集只可补充真实码率定量分析,不能代替App内业务录像,也不能因外部码流可采集而跳过App录像的开始、300秒、停止保存;缺少分析/提取能力同样不阻断可做的四段App录像。每段真实等待是业务动作,尚未执行等待不能写为等待受阻,模型处理耗时或一次短等待不证明已录满300秒;录像仍在持续且无实际障碍时,按可靠截止时间继续剩余时长,不能切档即收尾。开始、真实计满、停止和保存分别确认后才标本段完成;保存失败与时长缺证分别保留,不以“已录制”笼统覆盖。
当前已确认的直播档位先录,某个切档入口或组合受阻不吞掉其余可做段。经有限纠正仍无法确认任何清晰度、但实际录像入口可用时,尝试一段原定300秒的App录像,标明档位未确认,不计入四个指定组合;其余切档缺项保留,不为补覆盖重复录四段未知档位视频。
复用 [四组合录制方法](workbook-contract.md#编码组合录制与码率判定1214) 中的实际选中、媒体时长核对、素材来源及预算规则时,以此本轮App录像范围为准;编码或清晰度切换后核对智能编码仍生效,发生冲突时记录,不用固定码率代替。基础业务录制已1200秒,按真实总流程保留足够余量及已有合理配置。本条原12.14的全部像素档阈值、峰值及静/动态总量判据继续沿原范围独立保留;四段录像不自动代表其全部通过,也不因本轮补充就缩减原范围。
## 12.24:云回看左下日期与最近7日告警抽查
对应运行 `4eeb25f7-0cf3-4872-b2f2-7fd0bdbb5eef`。最近7日云告警视频抽查是本轮明确必做项,不能只看普通时间轴、告警列表或“一天快放”。确认同设备云回看,在本轮已确认的左下角日期入口选择日期,按选中反馈回读日期、等待该日数据加载;不固定像素坐标,也不从旧截图沿用定位。以可靠当前日期/时区列明最近7日范围,未另指定时按当天及前6个自然日,当天只查已过去时段,不等待未来七天。
逐日查看可用告警记录,在实际有告警的日期抽样选择告警视频并实际播放,关联日期、事件类型、事件时间/片段及播放结果,检查是否正常出图和持续播放。按实际分布覆盖不同日期,不因某日无告警就虚构样本,也不以同一天多个片段充当七日覆盖;用户未指定样本量时按可用记录合理抽样并说明已抽查数量/范围,不把抽查扩成必须播放七天全部视频。日期无记录、保留不足、服务不可用或播放失败分别记录;空列表不证明应有事件无丢失,正常时间轴也不证明告警视频可播放。
保留 [原最近七日云/卡时间轴检查](workbook-contract.md#最近七天云存卡存空洞检查) 及原7×24小时、每日10事件、无丢失/可播放范围,各自记录覆盖与待核项;新增云告警抽查不取代双存储历史空洞检查,也不要求无历史的新挂测作为开始抽查前置。完成可做的日期选择与抽查后再按本条约定收尾。
## 12.28:卡回看和云回看分别调到12H刻度
对应运行 `36d12ee1-1289-430a-8fba-f103e75bff85`。实际进入同设备卡回看、云回看,两侧均需使用页面下角实际可见的放大镜缩放控件,将时间轴显示跨度调至 `12H` 并核对显示;本轮未确认左右位置,按当前页面识别,不写固定坐标或盲点次数。每次操作后观察刻度/跨度反馈,必要时在实际可用的增/减控件之间调整;若界面用其他标签,记录实际刻度,未达到12H时不声称完成。切换存储后重新确认,不能从云侧12H推定卡侧同样生效。
`12H` 是显示尺度, [最近十二小时窗口](workbook-contract.md#最近十二小时云存卡存空洞检查) 是实际检查时间范围,两者分别满足。调到12H后仍需确认选中日期、窗口起止与轨段已加载,按同一请求窗口检查两侧,跨日分别选日并覆盖交集;仅见12H标签或一屏正常不等于最近12小时全部已查。保留原空洞颜色/应录制解释、逐洞播放反馈,以及动态高码率负载与至少12小时无重启/掉线等独立判据。缩放受阻时记录控件、实际跨度和已查/未查范围,不用存储卡容量页代替、不反复改侦测来冒充历史负载。
@@ -0,0 +1,79 @@
# 既有项目反馈与专项修订
本文保留桌面优化版记录的既有移动爱家项目反馈。“本轮”、用户确认、运行 ID、68条集合、V6/V7底稿、雷电模拟器、移动爱家 V99.0.0、长虹SC52034、指定参数和桌面交付位置,均指该来源项目的记录。仅在当前用户明确沿用相同项目/任务表,或当前原文与确认资料可明确对应时采用相应专项;不能把附件里的旧对话当作本次新确认,也不把仅编号相同视为同一用例。
仅更新、合并本 skill 不触发 Excel 改写、桌面写入、测试派发或历史结果回写。用户实际要求修订任务表时,才按当前指定或最新已交付底稿回填;当前请求的范围、路径及已确认资料优先。通用收尾遵循 [完成与收尾](workbook-contract.md#用例完成检查与收尾) 及主 skill:移动爱家保留强停规则,不因本参考的页面操作省略进程核对。
以下是来源项目的反馈与规则,保留其业务范围及证据限制;合并文件本身不表示已重新读取原 trace 或完成设备复测。
用户要求复盘已有任务并重出提示词时,沿稳定 batch ID、session/trace ID 与本条原文建立对应,读 [已有任务表复盘修订](workbook-contract.md#已有任务表复盘修订)。逐条修正有证据的范围扩大、定位等待及前置假设,保留有效规则、原断言和用户的执行安排。用户新确认的页面路径可用于本轮提示词,注明来源,不冒充旧 trace 已验证;涉及本轮移动爱家路径或人工采集时,读 [用户确认路径与人工判定采集](workbook-contract.md#用户确认路径与人工判定采集)。用户明确改为指定画面截图、操作录屏、日志地址/文件采集或自定义图片上传,结果人工判断时,按新范围编写操作与交付要求,人工结论保留待判,不套用首次全自动筛选规则排除整条。这些指定内容是该条的验收交付物,与前文不写入提示词的通用证据归档要求分别处理;操作录屏须以受支持的单条任务配置实际开启,不能仅写提示词或用App内录像代替。全量审阅稿与按历史执行完成筛选的任务表分别标识;暂不适用记录移出“任务”页,不能因重写或历史 completed 就认为业务已通过或条件已解决。
**单条复盘只改对应行,默认交付Excel任务表副本**:当用户要求修订并重出既有任务表时,按该项目已确认的格式,以用户指定或最新已交付的七列Artemis任务表为底稿,只回填受影响用例的完整提示词及确有必要的单条配置;主交付为保留原表结构和用例集合的新版`.xlsx`,按“artemis-tasks(调试版)_设备_本次优化内容_条数V版本_日期.xlsx”命名并保存桌面。保留原编号、标题、顺序、环境、原动作/判据、其他条目、辅助页、样式及批注,不覆盖底稿,不把“只改对应用例”解释成默认仅交TXT/JSON或需要重写全部用例。上次Excel交付后已定稿且尚未回填的相关修订一并准确带入,不回退成旧提示词。TXT/JSON仅在用户另行指定或作为附加资料时交付;用户明确仅要单条Excel时才缩减行集合。
仅当用户明确要求对既有68条全量重新审阅、应用skill或改写时,先按 [本轮68条重出与覆盖核验](workbook-contract.md#本轮68条重出与覆盖核验) 沿用用户指定或本轮最新已交付的68条集合(本次重出底稿为V6)、已确认环境及人工交付范围,逐条应用本skill相应规则;不重新取历史完成任务并集,也不要求重复确认已知资料。本轮明确删除4.12、9.61、12.8、13.12、16.2、1.35、23.33;只从本轮任务与配套分类移除,保留仍适用的通用知识和历史复盘。输出附技能来源与逐条覆盖核验,不能仅改版本名或在所有提示词末尾统一追加一段而声称已完成整合。
本轮3.3/3.6新增失败已确认使用V7正文,早帧/原录像均已显示浮层,执行端只交较晚隐藏图导致首次入口未定位。按 [短时浮层与连续操作](transient-controls.md) 使用同动作较早图识别入口、当前末帧核布局、目标隐藏后在同一受支持序列重新唤出并点击;早帧不当当前状态,双击不替代两个目标的连续操作。只写“快速点击”或在同一隐藏图反复询问Explorer不能补足首次发现;技能文字、执行层早帧实际传递和设备复测分别验收。
本轮4.11转动噪声用例按用户补充的 [四向长按与操作视频](workbook-contract.md#云台四向长按与操作视频411) 执行采集:上、下、左、右各真实持续按住5秒,逐方向松手并观察,不能用四个短点或单方向1800ms替代。单条配置开启操作录屏;缺少可靠声音或场景比较证据时保留原噪声判定待核,仍完成可独立执行的四向动作和视频交付。四方向动作及视频缺项不能被“执行完成”掩盖,不将此要求推广为所有用例开启录屏。
本轮6.5“回放不同清晰度的云回放”按 [直播逐档五分钟与对应云录像](workbook-contract.md#直播逐档五分钟与对应云录像65) 明确阶段依赖:云存状态未核实时实际进入同设备云回看,核类型/日期并尝试播放过去片段,记录可访问范围或明确限制;此独立探查可先做,清晰度局部受阻也不能跳过。旧片段不代验本次新录制。核实直播清晰度菜单与实际选中,每档连续播放300秒并生成起止表,之后才核查对应新云录像。整屏中心不能当视频中心,视频外/相机误点须重新定位纠正;画面内容模糊只影响相应画质判据,不能单独阻断可做切档和计时。旧云录像、生命周期completed或仅查看菜单均不补足本次必做动作。
本轮7.4“回放不同清晰度的卡回放5分钟”按 [本次切换点定位与操作视频](workbook-contract.md#卡回放本次切换点定位与操作视频74) 编写:直播可用档各播放300秒并记录切换边界;选中高清等档位后须实际建立本档t0、使用受支持等待按真实剩余时间继续,未做等待不构成等待受阻,不能切档即强停。实际进入卡回看后必须拖动时间轴到本次各档及切换前后,回读选中时刻并播放;没有先见录像条不构成禁止选时的理由,推广卡也不代表卡时间轴不可用。按用户新范围仅采集操作视频,音频原判据保留人工待核,不阻断选时和可用档位;本条配置开启record_video、关闭analyze_video,保留Flash和3600秒预算。卡录像随直播变码率或固定码率均可,不强加变化策略。
本轮3.2“设备缩略图展示”按 [缩略图顶部下拉与直播返回更新](workbook-contract.md#缩略图顶部下拉与直播返回更新32) 分开编写两条观察链:在实际缩略图模式记录同设备目标基线,先回到并确认列表最顶部,再独立执行一次下拉刷新,核实触发及完成反馈后重新定位目标比较;从列表中部向下滑、看见首卡或手势回执均不证明刷新。触发仍未证实时保留手动刷新未验证,继续独立的进入直播、返回列表后更新检查;返回更新不能补证之前下拉已触发,也不要求新缩略图与某张较早保存的直播帧逐像素一致。缺少刷新前提证据时,不因旧图未变直接判产品失败。
本轮设备型号用例 9.2 按上述用户确认路径,先点击目标设备“设置”页最上方的设备图标,再从打开的实际页面读取型号字段,与已取得的独立厂商型号按本条既定规则比较;不只向下遍历设置列表,也不以设备昵称或图标外观代替型号。
本轮9.32休眠用例按 [休眠直播检查与唤醒恢复](workbook-contract.md#休眠直播检查与唤醒恢复932) 编写:确认休眠开启后,必须保持该状态实际进入同设备直播,读取休眠提示及推流现象;观察完成或确有局部受阻后,实际唤醒或在设置关闭休眠并回读,再进入直播确认恢复播放。开关关闭及“设置成功”只证明设置反馈,BACK到设置外层不算进入直播。某项偏差、智能喇叭/智话或硬件分支依据缺失不阻断独立可做的直播检查与唤醒;本次末态为已唤醒,不为恢复初始值重新开启休眠,不串入9.33实体遮挡动作。
本轮9.31“设备支持按需上传日志”按用户新确认的 [自动维护最近日期检查](workbook-contract.md#设备自动维护最近日期检查931) 执行:在目标设备设置页回到最顶部并实际点击设备图标,进入信息页后对真实“设备型号”连续点击5次,核实开发者模式反馈,再进入“设备自动维护配置”读取“最近维护日期”。本次只判断是否显示日期,原维护周期、重启/日志上传、跨日与离线等判据保留为来源待核,不修改维护/日志参数或触发维护。可读字段有日期、已读无日期、入口/加载受阻三种情况分别记录;有日期不等于原维护和日志机制全部通过。
本轮 9.69 日志用例按用户确认的“设备设置 → 顶部设备图标 → 详情 → 设备日志管理”采集:记录初始档位,实际调整两次,每次回读并取得、关联完整日志获取地址;可下载文件由宿主按实际能力留存。日志等级与 App 档位的一致性保留人工核对,不以内部数值映射或自动正文核查缺失阻断采集;采集完成不等于业务通过,具体写法见上述参考。
本轮 9.21 设置项检查,按上述参考先读取原七组升级前设置,再回到同设备“设置”打开“固件升级”,检查是否有可用新版本。保留未读配置的覆盖缺口,同时继续可独立完成的版本检查;不能在尚未查询前就以没有 OTA 完成证据或未人工提供升级包结束。版本检查与真实 OTA 后的设置保持比较分别验收。
本轮 13.13“固件包版本校验”按用户确认,在目标设备“设置 → 固件升级”读取“当前版本”,该值就是本条的固件包版本,不要求界面标签字面写成“固件包版本”。沿 [用户确认路径与人工判定采集](workbook-contract.md#用户确认路径与人工判定采集) 仅核原三段纯数字、每段最多5位,保留版本原串与前导零;不追加与预期版本相等或实际升级断言。历史 abeb226f 已走通此路径,按确认口径复核格式通过,原失败属于字段含义误判。
本轮 9.101 码率用例按 [本轮编码与回看补充](encoding-playback-feedback.md#9101两种码率模式与六段30秒录像) 在“自定义编码参数”设置 VBR 平均2000、峰值2300,保存回读后到直播标清、高清、超清各独立录30秒;再设 CBR 平均2000、保存回读,重复三档各30秒,共六段。第二轮再次漏录,提示词须把每段“实际点击App录像开始并确认计时 → 真实录满30秒 → 点击停止 → 确认保存”写成必做阶段,直播观察、操作录屏或未尝试不能算录像完成或受阻。原智能编码自动关闭及厂复默认分支保留,缺默认依据不跳过可做录像,不擅自恢复出厂。具体数值只适用于本条,不再用任选码率代替已确认目标。
本轮 9.122 按 [全部侦测关闭、AOV与退出后返回](encoding-playback-feedback.md#9122全部实际侦测关闭aov与退出后返回),检查并关闭设备实际存在的全部侦测/跟踪及相关AI功能,包含主设置页与AI周界、AI设置中的实际相关开关;不能只关两项。再在“自定义编码参数”核实“AOV”标签及同一行开关,实际开启AOV并保存回读,不能开启相邻“智能编码”作替代或混记状态。退出App后真实等待60秒,返回同设备直播观察。原仍适用的码率设置、不开直播挂测及对应生成流另列阶段,60秒退出与直播不替代长期断言,未具备长期条件也不省略本轮明确链路。
本轮 9.133 分辨率用例按用户确认路径,在目标设备“设置”页下方打开“自定义编码参数”,先查看实际分辨率、当前选中值及可选项;不再以“画面与声音”或直播清晰度标签代替入口及数值。原用例的按设备支持档位切换、保存回读及切换后直播音画检查继续保留,查看完成不等于整条通过;不串入码率修改、AOV 或智能编码互斥要求,细节见上述参考。
本轮 9.137 智能编码开关在“自定义编码参数”读取初态、按原顺序开启和关闭并分别保存回读;每一态仍生效时都进入同设备直播观察画面,不能开关均结束后只看一次。原默认、两态实际静态输出及压降判据保留,与新增直播观察分别验收;详见 [每个智能编码状态检查直播](encoding-playback-feedback.md#91371229123每个规定智能编码状态都看直播)。
本轮 12.29“静态码率压降开关(暨‘智能编码’开关)”到设备设置页下方“自定义编码参数”操作,沿原步骤保留能力/配置项上报、可信默认和开关两态真实静态输出;开启后、关闭后分别进入同设备直播检查花屏等异常,再进入下一态或恢复,详见 [每个智能编码状态检查直播](encoding-playback-feedback.md#91371229123每个规定智能编码状态都看直播)。缺实测依据不省略可做直播观察,画面正常也不代替压降效果;不串入四组合录像、固定5分钟、AOV或互斥专项。
本轮历史记录编号12.3“静态码率压降效果测试”沿“自定义编码参数”核对并开启智能编码、保存回读,开启态进入同设备直播检查花屏等异常,再按本条真实静态输出及已取得的关联规范判压降效果,详见 [每个智能编码状态检查直播](encoding-playback-feedback.md#91371229123每个规定智能编码状态都看直播)。本条不增加关闭/默认或四组合5分钟;原定量标准缺失时保留测得数据和判据缺项,不编造比例。模型反复超时按执行方异常处理,不归为设备效果失败。
本轮 9.138 AOV 开关同样到设备设置页下方“自定义编码参数”操作,读取初态,按原步骤开启、关闭并分别保存回读。供电类型及可信默认起点只用于判断常电/低功耗的原默认分支,缺少时保留默认未验证,继续可独立执行的开关核验;不从室内云台类型猜供电类型,不串入9.122的码率修改或挂测要求,细节见上述参考。
本轮 9.139 按 [编码参数互斥与完整核验](workbook-contract.md#编码参数互斥与完整核验9139) 从第二轮用户指定的“设备设置 → 自定义码率参数”进入;若当前显示上轮名称“自定义编码参数”,核实实际编码字段与目标页,不改到通用AI/画面入口。保留原“任选一项”参数变更及自动关闭;后续必须分别在有效参数变更条件下开启AOV、智能编码,核对完整弹窗“设置此功能,码率、帧率、I帧间隔设置将恢复成默认值”并回读三参数。两分支不能共用已重置的前置,不因提示偏差提前收尾;详见 [重置提示分支补充](encoding-playback-feedback.md#9139参数变更后的两次重置提示分支)。原关闭、可信默认等判据保留,不将两开关误设为彼此互斥。
本轮 12.6 直播时延用例仍要求实际连续播放满30分钟后测量 `t≤2s`。按上述长时规则预留单条预算,以确认直播实际播放正常的时点计时,按真实截止时间分段等待并检查播放状态,不从任务启动起算,也不在已有播放时间之后机械追加完整30分钟。独立采集至呈现的时延测量方法须落实,不能以OSD与系统时钟表面差值代替;等待完成、直播持续及测得时延分别验收。
本轮12.14已按用户确认保留原编号(首轮曾称9.14)。按 [四组合录制补充](encoding-playback-feedback.md#1214智能编码下四组合各录5分钟) 在“自定义编码参数”开启智能编码并保存回读,依次完成 `H265×高清 → H265×超清 → H264×高清 → H264×超清`。每段都在同设备App直播页按当前布局定位右下角实际录像按钮,核实不是声音或拍照控件,点击开始并确认计时,真实录满300秒后点击停止、确认保存,再切下一组合;四段共1200秒,另预留切换、加载、保存、分析及收尾余量。RTSP采集或操作录屏不能替代App录像;未尝试录像不算受阻,缺分析/提取能力不跳过仍可做的四段。按 [码率测量与判定](workbook-contract.md#编码组合录制与码率判定1214) 逐段关联实际codec、视频尺寸、智能编码状态及真实素材,计算平均视频码率并按实际像素档匹配原阈值;均值、峰值及静/动态总量分别判定,原断言缺项单列,不合并成整条通过或扩成全部五档遍历。
本轮 12.24 按 [最近7日云告警抽查](encoding-playback-feedback.md#1224云回看左下日期与最近7日告警抽查) 在云回看左下日期入口逐日选择并回读最近7日,抽查实际告警视频并播放,不用普通时间轴或一日多片代替多日覆盖。原 [云存卡存空洞检查](workbook-contract.md#最近七天云存卡存空洞检查) 与7×24小时、每日10事件、无丢失/可播放分别保留;纯白/灰彩口径须结合应录制区间,无记录与未查分别报告。先查既有历史,不等待未来7日。
本轮 12.28 按 [双存储放大镜与12H刻度](encoding-playback-feedback.md#1228卡回看和云回看分别调到12h刻度) 分别进入卡回看、云回看,操作页面下角实际放大镜缩放控件并确认时间轴为12H。显示尺度与 [最近十二小时检查窗口](workbook-contract.md#最近十二小时云存卡存空洞检查) 分别核验,按同一窗口和跨日交集检查,不能只见12H就宣布范围已覆盖。保留空洞/回放和原负载、至少12小时无重启掉线判据,缺新挂测不阻断回看,容量页不能替代卡回看。
本轮 9.65 logo 水印用例,按 [提示词与决策](workbook-contract.md#提示词与决策) 保留“确认支持与初始状态 → 开启并核验各指定渠道 → 关闭并核验”的顺序,分别核实可信默认、实际开关和视频水印,不预设未走通的入口。多个渠道须对应同设备与开关生效时段,缺项分别保留,不能把模型调用异常写成水印功能失败。
本轮室内云台机的夜视画面/距离取图,按上述参考的设备能力分支编写:未见“智能夜视”且已核实夜视页的“自动”选项时,可保留或选择“自动”继续指定取图;提示词须明确不得关闭夜视或开启全彩模式作为替代,实际模式如实记录,智能夜视专项断言与人工距离结论分别保留未验证/待判。
本轮人形侦测告警及其事件视频用例,按 [人形侦测与告警判断](workbook-contract.md#人形侦测与告警判断) 编写:先到目标设备“设置”查找人形侦测,未见时说明可能不支持、需人工核对;找到后先读启用状态,再按本条范围判断告警。不能以告警分类或历史列表未见“人形”代替设置核查,也不将“查看是否开启”扩成未经授权的开启操作。
**16.1“人脸抓拍逻辑”优先采用用户2026-10-21的新口径**:按 [第16部分入口与16.1例外](workbook-contract.md#第16部分云化ai算法范围与入口),先只查同设备设置主页面的“人脸侦测”;主列表加载并完成必要查找后,有入口即按用户口径支持本条端侧算法,无入口记录“设备不支持端侧算法,需人工判断”。未加载、未查完或滚动受阻不是“没有”。入口灰色、关闭或标“云存储专属”仍属有入口,但不证明启用、服务可用或抓拍效果通过。支持后进入已找到的人脸侦测页,只查看本条原逻辑/模式及核验原抓拍判据,不新增开关、参数修改或其他云化指标;无真实场景/图片时保留效果未验证,不进入AI设置、AI周界等另找替代算法,历史记录不倒填通过。
其余原第16部分云化AI用例继续按 [云化AI算法范围与入口](workbook-contract.md#第16部分云化ai算法范围与入口) 到目标设备“设置 → AI设置”核对本条对应算法,只有该入口内的算法才计入云化AI。对这些条目,主页面的端侧“人脸侦测”即使标有“云存储专属”,也不能替代目标云化算法或证明其支持。未见时按设置页及AI列表实际进展继续查找;合理查找后仍无对应入口/算法,说明可能不支持并保留人工核对及未查范围,不断言不支持、不改测端侧功能。找到入口与效果达标分别验收,原适用条件、样本和指标仍按各条保留;16.1例外不改变16.10、16.75、16.124及9.122的既有路径与范围。
本轮16.10用户补充须先确认并开启“人脸-AI”:在上述AI设置内定位实际对应的云化人脸算法,读取初态,关闭时开启并核对必要保存/回读,已开启则保持;不以翻转视频源开关代替人脸AI启用。随后保留真实倒装及翻转生效期间的原效果检查,关联本次有效状态下的人脸上报图片和云录像,不能用启用前的旧声响事件取代,具体按上述参考的16.10分支编写。本次开启要求限该条补充,不将其他仅查看的16.*用例统一改为开启。
本轮AI车辆识别(含16.75)及AI电瓶车识别(含16.124)的AI识别,均按用户确认属于各自目标算法,不能由“AI周界”中的汽车/非机动车目标、区域入侵、越界侦测或普通移动侦测代替,也不再将这些功能作为目标算法的候选入口。先在设备设置页沿上述AI设置路径查找本条对应算法开关,通用“车辆识别”不自动等同电瓶车识别;充分查找后仍未见,说明可能不支持该算法、需人工核对,保留已查/未查范围,不判确定不支持或算法效果失败。具体按上述参考的车辆、电瓶车分支分别保留本条原动作与判据,经确证共享的参数/控件仅按实际范围复用,不证明算法身份或已启用;16.75仅核原真实区外车辆负向,16.124仅查看电瓶车画框、侦测开关、灵敏度和区域四项支持,不把两条条件互相套用或追加效果指标。
本轮 8.30 声音告警用例,按用户已明确的 [声音侦测开启与两分钟后检查](workbook-contract.md#声音侦测开启与两分钟后检查) 编写:先进入目标设备“设置”,开启声音侦测并确认生效;已开启则保持。以本次确认时点起计满2分钟,再查询同设备对应时段的告警。声音侦测开启、告警产生与原文哭声视频质量分别核验,不只翻查旧告警,也不把缺少声源证据当作省略开启、等待和查询的理由。
本轮1.14“7天挂测设备异响检查”按用户补充的 [声音侦测状态与最近七天告警](workbook-contract.md#声音侦测状态与最近七天告警) 编写:先到同设备“设置 → 声音侦测”查看是否开启,保持原设置,再逐日检查最近7天已有声音告警,记录实际日期、夜间记录和已查/未查范围。当前状态不证明七天历史状态,一条告警或空列表不代表七天结论;“较大声响”标签也不能单独证明设备自身异响。保留原第二监控设备及连续七天含夜间的条件与待核项,不因缺少这些资料跳过可独立查询的历史;本条不套用8.30的强制开启、2分钟等待,也不串入云卡空洞检查。
本轮23.28“更换已应用自定义桌面”按用户新确认的 [自定义图片上传与人工核对](workbook-contract.md#自定义图片上传与人工核对) 执行范围编写:在同设备“设置 → 桌面显示 → 自定义”完成图片上传,确认本次新增图片在App列表中可辨、稳定留存,即算本轮自动上传执行完成;选择自定义桌面无明确反馈时记录现象并交人工核对,不因缺应用按钮、“使用中”或实体屏幕证据反复重试、判上传失败。原A/B/C切换及实体显示、无重启离线等结果保留人工待判,不再作为本轮上传完成门槛;不为补齐旧门槛另切系统主题或删掉待核图片,按既定保留/恢复及App收尾安排交接。
@@ -0,0 +1,56 @@
# 直播短时浮层与连续操作
用于准备或修订直播清晰度、全屏及声音开关等用例,以及复盘“点击画面后未见功能”的执行记录。只在本条需要相关控件时使用;设置页等持续页面仍按其实际加载条件等待。本技能不启动设备、不执行连续点击、不修改观测器或后端配置。
## 编写任务中的操作说明
1. **先核对落点并唤出。** 按当前画面辨认真实视频边界;目标当前不可见且尚无本次唤出证据时,在画面内无按钮区域单击一次唤出控件,再查看该动作的观测。目标当前已显示时直接操作,不额外单击把它隐藏。避开下方录像工具栏、抽屉拖柄和云台面板。动作说明写“点视频”不证明实际命中;只有面板收起/展开时先纠正定位。全屏、横竖屏、抽屉或布局改变后重新识别。优先采用执行端实际支持的文本、ID等定位;坐标仅使用当前布局已验证的位置,不复制历史任务坐标。
2. **首次发现要利用同动作早帧。** 唤出后同时核对执行端实际提供的较早动作后画面和当前末帧,从可见图像辨明清晰度文字、全屏或声音控件的位置与作用;UI树没有“高清”不否定图像里的“高清”。较早画面是已发生的短时证据,不是当前状态,也不提供当前元素索引。它能补齐首次入口定位,不能因末帧已隐藏而丢弃,或只在同一张隐藏末帧上反复询问Explorer。没有早帧时按下述取证分支核实,仍不明则保留入口未定位,不能靠双击或猜坐标补足。
3. **定位后在当前布局内完成连续操作。** 将较早画面与当前末帧核对为同设备、同视频边界、方向、工具栏及面板布局后,才把该入口用于当前布局;横竖屏、面板或页面改变后重新定位。当前已显示目标时直接点目标,避免再点画面将它隐藏;当前已隐藏且布局仍一致时,在一个实际支持的短序列内完成“唤出浮层 → 点击已确认目标入口”,中间不采完整UI树、不等待下一轮模型。当前Flash实际开放 `click_sequence` 时应使用它;两个模型回合的单击、同点 `times=2` 双击,都不能代替两个不同目标的连续操作。未验证双击语义时不以双击试代替全屏或清晰度入口;其他执行模式只采用其真实支持的连续动作能力,不发明工具或参数,不套历史截图的元素索引,也不在布局已失效时照搬位置。
4. **等待业务反馈。** 点击入口后,以菜单出现、全屏状态、声音图标状态等本条可观察结果为继续条件。清晰度用例须按原文选择实际档位并核对选中反馈和所需播放结果;菜单打开不等于切换成功。目标已经可见时直接操作,不再额外点击画面导致浮层被切换隐藏。
5. **有限恢复。** 已确认的浮层隐藏后,可依据仍有效的布局重新唤出并连续点击。若再次因相同的慢观测错过窗口,停止这一循环,交执行方核对能力和观测耗时;不靠反复点画面、延长等待或增加步数解决。仍不能操作时只记录本条未完成范围,不转去无关设置页泛查,也不推断功能不存在。只有本条确需其他入口且有实际线索时才探索相关路径。
若本条只要求在某档位持续播放(如3.3),本次当前画面已明确显示所需“高清”时可确认并保持,不为确认增加无意义的改档再改回;同动作早帧可作为该时点的档位证据,须结合之后是否改档、是否仍为同设备播放核实适用范围,不能把任意历史标签当当前状态。3.6等原文要求往返切换的用例仍须实际完成各次切换,初始已高清不免除后续操作;3.3的有效10分钟及质量观察也不因入口曾显示而完成。
可拼入已补齐环境、原步骤和判据的正式提示词的动作段示例(不是完整可派发任务):
> 在当前视频内唤出控件,首次从同动作提供的较早画面识别清晰度入口,同时用当前末帧核对设备、视频边界和面板布局。早帧出现而末帧隐藏表示曾显示;布局仍相同时,用实际支持的 `click_sequence` 在同序列中重新唤出并点已确认入口,不拆成两个模型回合或改成同点双击。目标当前可见则直接点;布局变化重新定位。打开菜单后按本条原要求核对实际档位及播放,较早画面不作为当前元素索引或完整业务通过依据。
只取本条适用部分;声音用例不追加清晰度切换,全屏用例不追加听音或画质判断。工具配置、观测日志和通用证据归档不拼入每条任务正文。原用例明确要求的业务时长仍须实际计时,不以模型处理时间代替。
## 执行方的取证与诊断
以下供后续获准执行的 Codex 交接和只读复盘使用,不是本技能的设备执行步骤,也不是保证提示词可控制底层观测器的承诺。
- 对齐同一 trace 的实际动作与落点、最早有效画面或已有录像、后续截图及返回时点。条件允许时保留点击后的首张有效截图,并记录后续隐藏状态;工具只返回末帧时明确限制,从已有录屏补证,不声称未保存的首帧已取得。不为捕获浮层重复触发录制、通话等业务动作。
- 对短时控件分别核验“曾显示”与“后续目标操作完成”;不要求整幅直播画面稳定后才认可浮层。多轮稳态采样可能丢失瞬态,提示词无法修复执行器行为;超出能力时记录限制并交接所需观测改进。
- 先核执行器是否已经采到早帧、但只把最终稳定帧传给模型。此时仅追加“快速点击”仍不能让首次未知入口被发现。执行层应保留当前末帧与当前元素,另把同动作的最早有效画面作为明确标时序的历史证据提供给实际决策端;不得直接将早帧替换成当前状态。发现信息不仅要写进日志,还要核对Flash/Pro实际消费的多模态上下文;只保存文件或发一段“请看早帧”文字不等于模型已看到图像。早末帧整幅图像感知哈希接近也不证明短时控件相同。
- 区分动作开始、截图采样、图片入库、状态返回及模型决策时间。入库时间不能冒充精确采图时刻,整段耗时也不能全部归为模型延迟。截图与 UI 树可能采于不同时刻,不能拿晚时点的空元素列表否定录像中的早时点显示。
- 核对原始 Flash/Pro 配置和 Runner 日志;摘要标签不一致时保留差异,不凭标签选择修复方式。既有录像按真实时间戳用 FFmpeg 等可靠工具抽帧;变帧率录像的 OpenCV 按时间定位须先验证时间映射,不按帧号和名义帧率直接推定时刻。
- 观测异常、任务工具报错或设备问题先按 ARTEMIS 工作流调用 `mobile_diagnose`。日志出现 helper 不可用、UIAutomator2 停止/恢复、后端回退或多轮观测时,记录它们与操作窗口的时间关系;不能把本次相关性推广成所有失败的根因。helper 缺失只是可选能力降级时,不因它单独判整机阻塞,也不反复诊断同一可选项。
- 构建或部署 helper、调整后端恢复策略或修改默认配置,交执行方按实际诊断和授权另行处理。技能更新不代表这些修复已完成;不自动全局开启录屏、切换模型/执行模式、增大超时或重提原业务任务。需要新增录屏时遵循当前任务的配置与授权,不能事后补拍冒充原执行证据。
| 证据 | 本条应保留的结论 |
| --- | --- |
| 实际落点在拖柄/工具栏,只有面板变化 | 定位未成功;重新定位后有限纠正,不能写成已点视频仍无控件。 |
| 录像或早帧明确出现浮层,末帧已隐藏 | 唤出成功;若没完成入口点击,则记录短时交互未完成,不再写“未弹出”。 |
| 仅有较晚截图或晚时点 UI 树,缺少即时画面 | 未取得足以判定是否弹出的短时证据;不确认为 App 缺陷。 |
| 仅有 tap success / dispatched 回执 | 只证明调用结果,仍需实际界面证据;不直接记业务成功。 |
| 菜单已打开,但没有目标选中或播放结果 | 入口打开成功,原用例后续断言仍未验证或未完成。 |
## 已验证样本与适用限制
2026-10-16 对下列历史任务的复盘已用原始录像交叉核验:
- `50691d8e-2390-4189-b6ac-4cb3c5c32e84`:第 4、6 步确实显示浮层;动作至最终 post 图片入库分别约 25.47、27.50 秒。第 5 步另有点到云台拖柄的定位问题,两类原因分别保留。
- `cd09d017-d5fc-4f12-b366-535d2638199a`:第 4、6、7 步均曾显示声音、高清及全屏浮层;对应入库耗时约 30.03、18.33、31.04 秒。
- 本次页面浮层约 5 秒后隐藏;同设备核验中,约 1 秒内完成唤出及点击“高清”可打开档位菜单。两份原始日志实际走 FlashRunner,相关观测中存在 helper 不可用及 UIAutomator2 回退。
2026-10-16的两个新增失败已精确核对数据库 `initial_goal` 与V7对应F列一致,排除了导入旧稿;原始执行同为FlashRunner,摘要误标Pro不覆盖原始配置:
- `b00d7a56-0a19-4091-a81f-b69923d2385b`(3.3):第5、7步点位均在真实视频内,动作后最早观测图片已显示“高清”、声音和全屏;第一组早图登记17:11:41.557,最终引用隐藏图登记17:11:46.710,第二组17:12:38.029/17:12:43.661。整步分别约9.65/8.44秒;录屏按真实PTS确认两次浮层各约5秒。早图确已采得但被后续稳定化末图覆盖,是首次入口无法建立的直接原因,不再归为点错面板或产品未弹出。未执行 `click_sequence`,也未开始有效高清10分钟;历史 `failed/blocked` 及质量缺项保持。
- `12f95604-fdcb-426d-a827-af4d4704081c`(3.6):第5、6、8步后录屏均显示高清/全屏浮层,真实PTS可见区间约96.551–101.106、113.613–118.324、169.850–174.483秒。动作开始至最终图片登记约9.13/24.12/21.73秒;这是登记间隔,不冒充精确采图耗时。只执行普通单击、一次同点双击及收云台,未执行两目标连续序列;Explorer在隐藏末图返回无候选不否定此前出现。259.5秒结束为主动受阻收尾,不是总预算超时;原往返切换、全屏及质量仍未完成。
这两个样本说明“较早图已显示 → 当前核布局 → 重新唤出并连续命中”的首次定位闭环必须在执行端可见,不能仅重复文字要求。helper缺失和UIAutomator2恢复可能放大时延,但不是“全部失败都由helper引起”的结论;本技能的修订与执行层代码修复、服务加载及设备复测分别验收,不改写旧trace或宣称已重新跑通。
上述秒数是本次样本,不是所有 App 的固定等待、超时或连续点击间隔;本技能不固化本次型号、分辨率或坐标。不能把所有“未见”都归为慢观测,也不能因样本成功自动更改历史业务结果。修订历史结论时仅覆盖实际证据支持的断言,保留清晰度往返切换、长时播放及音画质量等尚未完成或需人工判断的范围。
@@ -1,6 +1,16 @@
# 七列任务表与判定边界
本参考记录用户所附模板与原始用例的非显然结构,供处理这类工作簿时使用。新输入须重新核对表头和适用性,不要把示例页当作通用列号映射。
本参考记录七列模板、原始用例结构与桌面优化版的项目反馈。模板、原编号、正式提示词信息完整性和证据边界用于通用准备;出现“本轮”、特定编号、环境、路径、数值、条数或运行 ID 的内容仅用于可明确对应的来源项目。新输入须重新核对,不以同编号或附件中的旧确认代替当前资料,也不从历史文字推定当前设备状态。
移动爱家收尾沿用当前安装版的强停及主/前缀子进程核对;优化版“默认返回设备首页”仅作为其他 App 未约定收尾时的回退。当前用户明确的安排优先。业务动作中的“退出App后等待再返回”与整条结束收尾分别处理,不为中间退出擅自加上强停。仅合并 skill 不派发任务、不重写 Excel、不回写历史记录;本文历史样本来自所附文档,合并本身不证明已重新核对原始 trace。
## 按需查找
- 七列结构与支持字段:[模板](#模板);异构表与行定位:[原始用例](#原始用例)。
- 编写与状态观察:[提示词与决策](#提示词与决策)、[设置页查找](#设置页查找与到底确认)、[截图分辨率与相册](#设备截图分辨率与相册查看)。
- 计时、预算及退出:[长时任务](#长时任务预算与实际计时)、[完成检查与收尾](#用例完成检查与收尾)。
- 既有项目逐条补充:[项目反馈索引](project-feedback.md)、[编码与回看补充](encoding-playback-feedback.md);相应编号专项只在对应原项目使用。
- 全量项目重出:[68条覆盖核验](#本轮68条重出与覆盖核验);状态、模型异常与历史证据:[复盘修订](#已有任务表复盘修订)。
## 模板
@@ -20,9 +30,47 @@
不要把新增一列当作导入成功证明。执行前用当时的 `mobile_task_file(action="preview")` 检查文件和逐行错误;本 skill 只准备文档,不启动批次。
## 本轮68条重出与覆盖核验
本节仅在用户明确要求全量重新审阅、应用skill或改写时,用于已确认的移动爱家/长虹SC52034任务集合整合;后续单条复盘默认沿用最新七列任务表,只改对应行的提示词及必要配置,再交付保持原用例集合和格式的Excel副本。保留68行并输出新版xlsx不等于重新改写68条,不默认只交TXT/JSON,也不清空或重写其他条目。本节不作为其他设备或首次原表筛选的固定清单。以指定75条V4删除7条形成的68条集合为范围,读取用户指定或本轮最新已交付版本(本次重出底稿为V6,后续不因本节示例退回旧版),保留原编号、标题、顺序、已确认的雷电模拟器/移动爱家V99.0.0/长虹SC52034环境,以及用户指定的人工判定和收尾安排。沿用已知资料,不因重新应用skill而要求重复提供四项环境信息,也不依据历史completed或新的完成任务并集增回用例。
本轮暂不适用项必须从“任务”页及对应分类行移除:
| 编号 | 用户明确删除的用例 |
| --- | --- |
| 4.12 | 物理遮挡 |
| 9.61 | 设备勿扰(针对具备主动呼叫功能设备) |
| 12.8 | 测试APP绑定时延 |
| 13.12 | 固件MD5校验 |
| 16.2 | 人脸抓拍实际场景算法效果准确率测试 |
| 1.35 | Day7 14:00–17:00智能喇叭持续播放情况 |
| 23.33 | 智能提醒播报频次设置 |
删除限本轮执行集合,不删除仍适用的知识及历史样本(如16.2的云化AI入口误判经验),不推广成所有设备永久不适用。用户后续明确改变范围时按新要求更新。允许在“优化说明”中保留仅含编号、标题及删除依据的变更清单,不放回可导入任务。核对剩余恰为68条,编号及标题逐行对应输入,特别保留1.14、13.13、23.28及4.8,避免按近似文本误删。
逐条按下表路由,核对动作、观察与判据,不把每项规则同时塞入所有用例:
| 本轮优化 | 对应规则与适用边界 |
| --- | --- |
| 直播短时浮层、落点与连续操作 | [短时浮层与连续操作](transient-controls.md);仅本条确需清晰度、全屏或声音控件时使用,点击回执不证明业务成功。 |
| 4.11四向真实长按与操作视频 | [四向长按与操作视频](#云台四向长按与操作视频411);逐方向5秒并松手、保留实际操作视频,动作采集与原噪声判定分别验收。 |
| 缩略图顶部二次下拉 | [3.2两条更新链](#缩略图顶部下拉与直播返回更新32);手动刷新与退出直播后更新分别核验。 |
| 设置长列表、设备信息、日志、固件及夜视 | [设置页查找](#设置页查找与到底确认)、[用户确认路径与采集](#用户确认路径与人工判定采集);9.69两次调档取完整地址人工判级,13.13当前版本按原三段格式,夜视自动取图例外限本条而不改9.27模式专项。 |
| 人形与声音告警 | [人形设置与状态](#人形侦测与告警判断)、[8.30开启后两分钟](#声音侦测开启与两分钟后检查)、[1.14状态与七天历史](#声音侦测状态与最近七天告警);三条顺序和修改授权不能互相套用。 |
| 自定义编码参数及互斥 | [本轮各参数路径和范围](#用户确认路径与人工判定采集)、[9.139完整核验](#编码参数互斥与完整核验9139);码率、分辨率、智能编码与AOV各按本条范围,不能将两开关预设为彼此互斥。 |
| 真实时长与视频码率 | [12.14四组合录像(本轮称9.14)](encoding-playback-feedback.md#1214智能编码下四组合各录5分钟)、[9.101六段30秒](encoding-playback-feedback.md#9101两种码率模式与六段30秒录像)、[长时预算与计时](#长时任务预算与实际计时)、[12.14四组合测量](#编码组合录制与码率判定1214);实际视频不是手机屏录,单条预算不代替真实业务时长或测量。 |
| 云存/卡存历史空洞 | [12.24最近七天](#最近七天云存卡存空洞检查)、[12.28最近十二小时](#最近十二小时云存卡存空洞检查);纯白/灰彩口径结合应录制时段,原长稳条件另核。 |
| 第16部分算法入口、人脸启用、车辆及电瓶车 | [第16部分AI入口](#第16部分云化ai算法范围与入口);16.1按2026-10-21最新口径只查主页面人脸侦测判断本条端侧支持,其余云化用例仍只认AI设置;16.10、16.75负向与16.124四项支持保留各自范围。 |
| 自定义图片上传和人工待核 | [23.28用户调整范围](#自定义图片上传与人工核对);上传完成不等于原A/B/C及实体效果通过,其他图片用例不套此例外。 |
| 完成检查、异常及收尾 | [完成后再收尾](#用例完成检查与收尾)、[预算与异常](#长时任务预算与实际计时)、[历史复盘](#已有任务表复盘修订);可做项继续,合法等待与累计模型异常分开,宿主监控不灌入每条业务正文。 |
输出前对68条分别记录采用的规则、保留的原动作/判据、实际修改或“核对后沿用”的依据。已正确的内容可以保留,不为产生差异追加业务动作;删除冲突旧指令、无关规则及重复段落时保留等价要求。用户已改变范围的原步骤标为来源/人工待核,不能同时保留为必做自动门槛。通用宿主归档、模型调用诊断与监控留在交付说明,只有用户指定的截图、录屏、日志或视频是本条验收动作时才写入正文。模板保持七列;新增优化说明或覆盖核验页作为本轮交付元数据,不参与导入。
单条配置沿用已确认值,仅在本条需求确实变化时调整受支持字段并说明;保留4.8操作录屏及6.5、7.4、12.6、12.14既有时长预算,不擅自切换全局模型、开启全局录屏或增大默认时限。保存实际读取的skill文件及哈希、逐条覆盖结果、源/输出身份和真实导入预览结果,便于区分“已按skill准备”与“已在设备上执行通过”。本流程只生成新副本,保留输入及历史结果,不启动批次。
## 原始用例
所附“家庭安防多形态终端软件测试用例(移动看家2.0)”有 33 个可见页,不是一个统一表:典型页用 A:G 记录编号、项目、子项、用例、预置条件、步骤、预期;`8告警事件` 表头在第 2 行;`13安全` 的 D:H 才是名称、摘要、前置、步骤、结果;`16 云化AI专项测试-基础包` 用 E:I 记录子项到结果并有重复编号列。页标题还限定白盒、无卡槽、带屏等范围。右侧“新设备/固件变更”之类提测情形不是设备支持声明。`1设备绑定` 的“是否适用”用例行尚为空;设备功能集也未完成真实设备支持信息。因此当前原始文件不满足正式全量转换前置条件。
所附“家庭安防多形态终端软件测试用例(移动看家2.0)”有 33 个可见页,不是一个统一表:典型页用 A:G 记录编号、项目、子项、用例、预置条件、步骤、预期;`8告警事件` 表头在第 2 行;`13安全` 的 D:H 才是名称、摘要、前置、步骤、结果;`16 云化AI专项测试-基础包` 用 E:I 记录子项到结果并有重复编号列。页标题还限定白盒、无卡槽、带屏等范围。右侧“新设备/固件变更”之类提测情形不是设备支持声明。`1设备绑定` 的“是否适用”用例行尚为空;设备功能集也未完成真实设备支持信息。当时该原始文件不满足正式全量转换前置条件;这是原附件的历史状态,新输入是否满足要求须重新核对。
识别时从标题、表头和有实际步骤/期望的行建立用例记录;保留原始 `sheet + row + 编号` 键。合并单元格或续行可承接同一用例,但不要把每个格式行计成独立用例。对无法对应到一条用例的条目报告歧义,不悄悄丢弃。将适用性缺失的行集中列出,先请用户完成原始文档,再生成正式输出。
@@ -34,24 +82,397 @@
对每条“是”,首字符起按 `【原表显示的用例编号】` + 原“测试用例”文本(仅去首尾多余空白)拼接。保留原文已有的 `【】`,不要再给整段用例标题套一层方括号;编号列和提示词前缀均不附来源工作表/行号,也不要写字面 `+`。例如来源为 `1设备绑定!3` 的用例,编号列填 `1.1`,提示词前缀是 `【1.1】【声波配网】手动长按重置恢复出厂后首次绑定完成配网流程`。这只是格式示例,该行还需人工复位,不能因此标成自动化“是”。若 Excel 数值编号的显示格式与底层数值不同(如 `1.10`),采用原表显示的编号。
前缀之后另起完整说明,至少明确:用户确认的模拟器名称或真机环境、测试 App 名称与版本、被测设备名称;沿用测试开始前统一核验过的安装版本,不要求当前用例再次核对;在目标 Android 环境与应用的实际当前状态开始;需要建立的前置条件;依据原文进行的操作;等待**可观察的界面状态**而不是依赖模型思考时间;原文的预期结果和可观察的判定依据。必要的 SN、秘钥、型号可直接写入对应任务,但不能给无关用例灌入这些值。不要把缺失的原文判据补成看似确定的断言。关键操作前后截图、录屏保留、证据路径及“测试佐证”目录归档、阻塞/未验证结论属于 Codex 的执行与报告要求,不写入“Artemis提示词”。
前缀之后另起完整说明,至少明确:用户确认的模拟器名称或真机环境、测试 App 名称与版本、被测设备名称;沿用测试开始前统一核验过的安装版本,不要求当前用例再次核对;在目标 Android 环境与应用的实际当前状态开始;需要建立的前置条件;依据原文进行的操作;等待**可观察的界面状态**而不是依赖模型思考时间;原文的预期结果和可观察的判定依据。必要的 SN、秘钥、型号可直接写入对应任务,但不能给无关用例灌入这些值。不要把缺失的原文判据补成看似确定的断言。互斥类用例按原文明确关系对象及触发条件,不能把组间互斥扩大为同组成员彼此互斥;原文要求关闭的功能、恢复的参数范围和完整提示文案仍须保留。关键操作前后截图、录屏保留、证据路径及“测试佐证”目录归档、阻塞/未验证结论属于 Codex 的执行与报告要求,不写入“Artemis提示词”。
每条用例完成操作与结果观察后,提示 ARTEMIS 返回目标 App 内的设备首页,重新观察并确认首页已显示;即使用例失败或未验证,也尝试完成这一收尾。此处不是 Android 系统桌面,不预设未经核实的导航入口。若用例使设备首页不可达或返回失败,说明当前页面和原因;Codex 在运行下一条用例前确认设备首页已经恢复,未恢复则先处理该状态。
复用执行段时,逐条将新增动作和判据对应回本条原文;查找设置页等操作只在本条确需时加入。系统推荐桌面“选择一套并应用”的用例,不沿用其他用例的 A/B/C 多次更换、自定义桌面、重启或离线验证。原文确实要求这些分支时分别保留;不让额外动作或缺少额外资料改变本条验收。
直播画面中的分辨率切换、全屏、声音开启/关闭等控件可能在点击画面后才显示,也可能藏在随之出现的二级菜单。仅当用例操作确实位于直播页且初次观察未找到目标元素时,提示 ARTEMIS 先截取/查看当前页面,轻点不覆盖明显按钮的直播画面区域一次,再观察新的控件或菜单并尝试定位目标。菜单若短暂显示,应在其可见时完成后续操作;若仍不存在,记录观察和截图,报告未找到,不反复盲点或编造菜单位置。声音开关可核对界面呈现的开/关状态、图标或可观察反馈;若预期要求确认实际声音质量或是否听得到,不能以图标变化冒充听音验证。此提示不是对当前 App 实际界面的预先验证,不能套用于设置页等其他场景。
每条用例先按 [完成检查与收尾条件](#用例完成检查与收尾) 核对操作及结果观察,再按约定收尾。移动爱家在达到退出条件后强制停止 `com.cmri.universalapp` 并由执行方核对主进程及前缀子进程;用户当前明确另有收尾时遵从。其他 App 按当前任务约定,未约定时返回目标 App 内设备首页并确认可见,不将移动爱家包名套给其他 App,也不同时追加冲突收尾。失败或未验证也应收尾,但单项偏差不表示整条可以提前结束。此处不是 Android 系统桌面,不预设未经核实的导航入口;下一条起点与收尾一致。若约定收尾失败,说明当前页面和原因,Codex 在下一条前核对约定状态。
要求强制停止 App 时,先核对当前执行端实际开放的应用管理能力。来源项目的 Flash 执行记录包含原生应用停止动作,执行时须核对当时实际开放的能力;缺少通用 ADB 工具不等于不能停止 App。支持时使用应用管理停止功能,不要求执行者绕到模拟器设置寻找“强制停止”。若执行端不支持或停止失败,收尾交由已授权的宿主 Codex:确认该任务已结束、目标设备空闲且下一条尚未启动后,按已确认的序列号与包名完成停止,并核对用户要求的主进程及子进程状态。看到 Android 桌面不能独立证明进程已停止;不能为补做收尾而中断另一个正在执行的任务。该分工属于后续执行约定,本 skill 不启动或控制批次。
移动爱家支持 ADB 时可执行 `am force-stop com.cmri.universalapp`。若执行端能力不足或未成功,由 Codex 在任务结束、目标设备空闲且下一条尚未启动时,用本条已确认的实际序列号执行 `adb -s <device_serial> shell am force-stop com.cmri.universalapp`;示例中的序列号不是正式任务表占位值。需要宿主收尾时逐条派发,确认本条收尾再开始下一条,避免与运行任务并发操作设备。下一条前检查 Android 进程列表,确认 `com.cmri.universalapp` 及 `com.cmri.universalapp:` 前缀子进程全部停止;停止后又被拉起或无法确认时先处理。下一条重新启动移动爱家、等待真实页面加载并建立前置,版本沿用本轮统一核验结果。失败或未验证也按同一退出条件收尾,不因首个偏差跳过其余可做项。
直播画面中的分辨率切换、全屏、声音开启/关闭等控件可能在轻点画面后短暂显示,也可能通往二级菜单。仅对实际需要这些控件的用例,按 [短时浮层与连续操作](transient-controls.md) 编写:先确认实际视频边界和入口;入口未知时及时定位,入口与当前布局已确认时在可见窗口内连续完成唤出和点击,不把完整观测或整幅直播画面稳定作为两次点击之间的必经步骤。动作描述不证明实际命中;只看到面板展开/折叠须核对落点,布局改变须重新定位。声音开关以实际 UI 状态核验,不能用图标变化代替听音或声音质量判断;本规则不预先证明目标 App 的具体控件存在,也不套用于设置页。
云台控制分别核对方向按钮实际命中、同一直播场景的固定特征变化及原文要求的方向/到位结果。动作已发出、时间戳前进、广告或面板变化不证明摄像头转动;反馈仍在变化或提示需要再次观察时,在换方向、重启或收尾前先补充观察该方向的画面结果。有限补充观察后仍不可辨时,记录对应断言未验证,不反复盲点,也不直接判云台不动。普通方向响应与最终机械限位验收分别判断:原文或用户已确认判据明确要求的独立参照须满足;不能把原文未规定且未核实为必需的角度/规格资料,擅自升级为所有云台动作的硬前置。可独立观察的原分支继续核验,最终限位缺少可信到位证据仍不能判通过,画面不再变化也不等于机械限位到达。
“保存/重启后保持”类用例先按原步骤和预期明确比较对象:当前云台方位、命名预置位或其他已保存设置,不能只凭标题将对象互换或新增创建动作。当前方位保持须先观察调整已生效,并取得可辨的重启前场景/位置参照;已保存配置保持须取得有效起始配置及其标识,原文要求创建/保存时才安排该动作并确认实际保存。原文对象含混时列为待确认;已有任务按用户遇阻处理约定记录受影响断言与缺项。空机位列表不能证明配置持久性,也不能单独否定当前方位可核验;缺乏有效起点时,不用空→空或模糊场景未变证明保持,也不判重启丢失。区分 App 停止/重启与被测设备重启,按原文等待设备恢复及实际直播结果,不用 App 重启代替设备重启。
表单输入须按当前页面确认目标字段,并在输入后核对实际文本和保存条件;动作回执、焦点标记或键盘是否显示都不能单独证明输入成功。字段仍显示占位文字、预期文本未出现或保存条件未满足时,不继续假设已保存;依据当前字段状态有限重新定位或使用实际可用的输入能力,仍未生效则记录未完成范围。输入工具/通道错误与 App 行为分别留待核实,不因多次点按无变化直接判 App 缺陷,也不把工具显示已发出写成文本已进入字段。开关操作也须核对所属设置与实际命中反馈;点到状态栏等无关元素只说明定位未成功,重新观察目标开关后有限纠正,不将该次操作记为开关已切换。该提示不指定未经核实的输入法、坐标或工具回退方式。
带屏设备用例逐项注明观察端:App 的通话状态、配置回读,摄像头实体屏幕,以及对端声音是不同证据来源。“通话中”不证明视频接通;App 的“视频未接通”、编解码调试字段或未显示结束文字属于本端观察,须核对其与原文所指通话通道和屏幕的对应关系,再判断是否违反该项预期。实体屏幕中对方视频、计时、结束文字保持 1 秒和待机恢复,须有对应设备端、对应时点的实际观察;手机界面或模型回合间隔不能代替。缺少带屏适用性或实体观察条件时保留受影响断言,不从 App 控件可见就推断硬件支持,也不将未查到的屏幕效果直接判失败。
桌面主题的 App 预览、应用后“使用中”标记和设备实际待机屏幕分别核验。预览图中的样例日期/时间/星期不代表设备当前值;当前月球主题“使用中”只证明当前选择,不能独立证明出厂默认。原文要求默认状态时须核对可信起点,不能为了补证擅自恢复出厂;更换后的壁纸及日期/时间/星期仍需实体屏幕证据。沿用本轮已确认的保留、恢复或后续用例衔接安排,不自行恢复掉下一条需要的配置,也不以停止 App 代表配置已恢复。
图片上传用例先在目标 App 的实际选择器中确认本轮素材可见、可识别且可选。文件已复制到设备、哈希一致、媒体扫描广播成功或媒体库已有记录,都不能替代这一步。不可见时由已授权执行方按实际权限、媒体登记及刷新情况处理后再核对;仍不可用时保留受影响分支,不改选用户既有照片。可在本轮授权内自主准备的测试素材,不另设为必须由用户提供的条件;本 skill 只编写所需前置与操作,不执行素材导入或设备操作。
分开核验选图、返回 App 的计数/预览、上传反馈、应用和实体显示。选中 N 张、显示 N/N 或添加按钮变灰,只证明所见接受数量与界面状态,不能据此声称第 N+1 张被拒绝、后台逐张成功或设备端生效。原文要求数量上限时,在本条范围及执行授权内尝试超限并观察实际限制;未尝试则保留该项未验证。上传失败分支须由执行方在授权范围内建立真实可控的部分/全部失败并核对原提示;未选择或无法建立方法只影响该分支,不因“未由用户提供失败方案”阻断独立的 App 操作,也不将未触发提示判为产品失败。保留原文对图片格式、大小等测试范围的限定,不擅自扩项。本轮23.28用户已将完成门槛改为上传自定义图片、结果人工核对,按 [本条上传完成口径](#自定义图片上传与人工核对) 采用新增图片身份与留存证据,不强求原应用/实体结果作为自动完成条件。
自定义图片删除或删后再上传时,区分普通列表、删除选择页和确认弹窗;退出选择页的“取消删除”不能代替弹窗“取消”。按本条原文核对勾选对象、弹窗及取消/确认后的图片内容和数量,未出现弹窗就保留该分支未完成。若重新进入删除页时已有勾选但确认无响应,在本轮获准删除的测试素材内,可先实际取消勾选至空圈,再重新勾选并核对确认弹窗、对象和数量变化;只作有限恢复,分别保留首次异常与恢复结果,不据此断言内部根因。删除、上传或列表重排后,按当前图片的可辨标识与内容重新定位,不复用旧位置或元素编号,也不凭动作说明里的图片名称判断实际命中;数量变化不能替代目标身份回读。目标尚未出现在当前可见范围时先查找并重新观察,身份仍不能确认则保留对应操作与应用结果未验证。
缩略图刷新用例先识别目标卡片的实际展示区域及图像内容,再与已观察的同设备直播场景比较。卡片为摄像机产品外观图、直播为天花板等明显不同场景时,内容差异可以直接核验,不能只因卡片没有时间戳就归为“同一静态场景无法区分新旧”。记录具体差异,核对该区域是否为用例要求的缩略图、当前显示模式以及实际刷新/退出后的稳定状态;未完成这些核对时保留异常现象与原因待核实,不立即判产品失败。只有确为同一静态场景且没有独立时间或其他可辨变化时,才采用新旧无法区分的未验证分支;不临时编造刷新时延阈值。本轮3.2手动刷新还须先确认列表到顶、再独立下拉并核实触发,按 [两条更新链的边界](#缩略图顶部下拉与直播返回更新32) 与直播返回更新分别判断。
**移动爱家 App** 点击“设置”后,设置页可能先显示空白、占位或尚未刷新的内容。提示词应要求等待并重新观察,以真实设置内容出现、目标区域可辨认为继续操作的条件;在此之前不点击疑似目标控件,也不把未出现的功能判为缺失。若刷新后仍不能确认页面内容,记录页面状态与证据并报告未能定位,不以固定秒数的等待代替确认。
App 设置页往往较长,目标功能可能在首屏下方。整理设置页用例时,先确认页面已刷新出实际内容;如已加载的首屏未见目标,提示 ARTEMIS 在设置列表中向上滑动较长距离(让下方内容出现),每次滑动后重新查看页面并查找目标;若仍未见,可根据页面变化继续滑动,直到可确认已到列表底部,再记录未找到。不要在未查看新页面时连续盲滑,也不要把用户的“多滑一点”写成未验证的固定像素、次数或坐标。
查找目标时区分灰色分组标题与实际可点击入口。已加载的主列表未出现目标名称,但存在语义相关且可进入的设置项时,可查看该子页并重新观察,查完后返回继续查找;不修改无关设置,也不反复进入已查页面。例如夜视用例在实际页面见到带箭头的“画面与声音”时,可将其作为候选入口查看,但不能据此宣称夜视一定在该菜单内。只滚动到主列表底部而未检查相关子页时,结论只能是“本次已检查的顶层列表未见目标”,须注明未查范围,不能推断 App 或设备不支持该功能。提示词不固化未经实测的二级路径、按钮位置或坐标。
卡回看和云回看的日期/时间轴上,有颜色的区域通常表示有数据,空白区域通常表示无数据。整理有录像片段的回放用例提示词时,先识别当前选中的回看类型及其有颜色区域,再在已经过去的时间中选取该区域并尝试回放,以实际播放/页面结果验证;不要假设卡与云的有数据区相同,也不要仅凭颜色判定回放成功。若用例本身要检验无数据区域,则按原文选择空白的过去时间并核对无数据反馈,不能拿它作为有录像回放失败的证据。选取回放片段前核对当前设备/App 时间和所选时间的时区及先后,不要求拖到未来时间;不能把未来片段不存在判成回放失败。若原用例专门检验时间轴上界,则观察是否无法选取或播放未来时间,记录界面证据,不假定具体控件反馈。颜色含义是用户提供的一般操作线索,具体 App 界面仍须在执行时观察确认。
查找同名或语义相近控件时,先核对其所属功能、检测对象与原文的对应关系;用户已明确区分的功能不再作为替代候选。本轮AI车辆识别、AI电瓶车识别均与AI周界属于不同目标,不再把AI周界中的汽车/非机动车目标、区域入侵或越界侦测列为这些算法候选。普通移动侦测的开关、区域或灵敏度同样不能证明目标算法的身份、支持或已启用,通用“车辆识别”也不自动等同电瓶车识别,按 [车辆及电瓶车算法身份规则](#第16部分云化ai算法范围与入口) 查本条对应开关;原移动侦测区域仅在与目标车辆算法共享配置及映射范围均有确证时按实际范围复用参数证据,电瓶车四项支持的有效共享控件按本条分支分别映射,均不能据此等同算法身份或证明效果。其他尚未明确排除的候选入口打开其他业务页时,先核对其与原功能的对应关系;名称相似或候选控件保存、回读成功不证明原用例设置已完成。尚未核实时只记录候选路径结果及原功能受阻/未验证,不把它当作原功能跳转错误,也不据此推断原功能必须付费。有已核实的共享配置关系时按其实际覆盖范围使用,不覆盖本轮已明确排除的AI车辆/电瓶车识别与AI周界边界。
App 设置页目标未见时,按 [设置页查找与到底确认](#设置页查找与到底确认) 继续检查长列表及相关子页;找到入口或确认滚动边界后再确定下一步,不把尚在推进的页面当作已查到底。
以既有录像为样本的卡回看和云回看,在日期/时间轴上先核对当前存储类型和实际录像轨段,再按本轮已确认的颜色含义选择已有录像;灰色也可能是正常录像,不能把“有颜色”限于鲜艳色。整理有录像片段的回放用例提示词时,先在已经过去的有数据区域选时并尝试回放,以实际播放/页面结果验证;不要假设卡与云的有数据区相同,也不要仅凭颜色判定回放成功。本轮12.24按 [七天空洞检查](#最近七天云存卡存空洞检查) 使用“应录制期间纯白为空洞、灰色/彩色为正常显示”的用户口径,不推广为所有 App 的固定配色。选择回看日期后,依据当前选中状态或日历回读确认目标日已生效,再关联时间轴、事件卡与播放结果;列表出现某日或录像标记不证明已选该日。若用例本身检验无数据区域,按原文选择空白的过去时间并核对反馈,结合录制计划区分正常无数据和应录制却缺段;不能把非录制时段作为有录像回放失败的证据。选取片段前核对可靠当前时间、所选时间的时区及先后,不要求拖到未来;不能把未来片段不存在判成回放失败。若原用例专门检验时间轴上界,则观察是否无法选取或播放未来时间,记录界面证据,不假定具体控件反馈。
6.5及7.4等本次生成型回看不采用先挑已有录像的顺序;按 [直播逐档五分钟与对应云录像](#直播逐档五分钟与对应云录像65) 先实际完成本次直播档位与时长,再定位本次对应过去区间。查看旧片段可以说明已有服务或素材状态,不能补证生成阶段。7.4另按 [卡回放切换点定位与视频](#卡回放本次切换点定位与操作视频74) 将本次过去时刻作为实际选时目标,不能以“无证据区间”为由不拖时间轴就判录像缺失;本次过去目标不要求事先可见彩色轨段才准选择,选择后按真实播放或无数据反馈记录结果。
云回看或卡回看需要把进度条拖至指定时间、但不好拖动或难以定位时,可先点击页面上的“+”放大时间轴,重新观察时间刻度与蓝色光标,再将蓝色光标拖至已记录的目标时间点附近。拖动后读取实际选中时间并观察回放结果;不能仅凭光标接近目标就认定预期满足。若原用例要求精确时间,按原要求定位和判定,不自行定义“附近”的误差范围。这个方法是用户提供的操作线索,执行时确认“+”和蓝色光标确实存在,不预设按钮坐标、放大倍率或点击次数;选时仍遵循上述日期、过去时间及有数据/无数据区域要求。
**仅移动爱家 App**:卡回放目标入口未见时,可先点击“云回看”,重新观察是否出现云回看/卡回放切换,再选择卡回放。其他 App 不沿用这条入口提示;切换未出现时记录当前页面并报告未找到,不编造固定按钮位置。此路径是用户提供的操作线索,真正编写可执行测试代码前仍须用 ARTEMIS 在目标 App 验证。
按设备类型选择分支时,先核对本轮资料的来源、用户确认情况及适用范围。已确认的室内/室外类型等输入直接用于选择原文对应判据,不在同一提示词中又将该分支列为“未提供”;若资料存在但尚未确认,应明确写待确认及原因。设备类型已知与真实夜视场景、独立测距或现场试听尚未核验是不同条件;报告缺项时也作此区分。不能从 App 画面拍摄的是室内场景就反推设备类型,也不改原文的距离、时长或其他阈值。 按硬件能力区分的成对用例分别提取各自原步骤和预期。例如休眠“不支持遮挡机位”分支未要求实体遮挡时,不套用“支持”分支的自动转位、遮挡动作或验收前置;目标设备对应分支尚未核实时记录待确认,不据此新增原文未要求的断言。
整理前置资料时,区分可从目标 App 只读取得的既有名称、数量、配置状态,与必须来自独立来源的预期值和外部条件。原文或已核实界面表明前者可读取时,提示词应先核对对应设备、账号与时点并读取记录;“未人工提供”不等于不存在或无法取得,尚未读取只能记为“未取得/未核实”。读取既有配置与创建、修改配置是不同动作,不能擅自新建机位来补齐保持测试。厂商/设备能力依据、标签原值、第二账号及现场观察等必要独立条件仍须落实,不能用被测输出生成独立预期。无法可靠取得资料时,列明实际未查或受阻范围,并按用户已确认的等待/跳过安排处理,保留未验证断言。保持前置条件原文的时点和范围:仅要求“未开通”等当前状态时,不擅自改成历史“从未开通”;标题涉及“首次”但历史范围未明时列为待确认,不凭标题加强条件。账号已有服务、目标设备已关联及当前录像可播放分别核对,前者不证明后两者。
日志/报文类用例保留原文允许的取证路径。原文允许“日志或报文”时,不将两条路径同时列为必需,也不一律预设由外部人员供证。沿用用户已确认的取证途径与执行分工。已核实 App 可生成日志文件/链接且宿主具备实际读取能力、读取在授权范围内时,提示词写明 App 应完成的采集及可观察反馈,Codex 执行方负责宿主文件取得、按实际格式读取正文和核查;不只凭文件后缀判断格式。用户明确仅采集日志地址/文件交人工核对时,以取得可追溯资料为采集目标,正文判级和一致性结论保留人工待判,不强制自动解析作为采集前提。App 未展示正文只能说明本端未读,不能据此认定没有日志来源。执行记录分别注明来源生成、地址取得、文件取得和正文核查;核对原文要求的日志等级、字段/接口映射和事件时点,历史单次调用或成功码不能证明本次触发、时间实际同步、“每次上线”或“每天凌晨”的完整机制。
临时日志等级等诊断设置先读取并记录原值,需自动判级时依据已确认的档位含义修改,不把“高”推定为 debug,也不把原值推定为出厂默认。用户明确按实际控件调档采集、结果交人工核对时,可记录可读标签或所选位置,不以内部数值或语义映射未知阻断已授权的调档与采集;例如第三刻度不能自行写成等级3。本条约定的采集与观察结束且没有继续采集需求时,若本轮未要求继续保留则恢复原值并观察反馈;停止 App 不能代替设置恢复。未恢复时记录最终值与待处理事项,后续补恢复关联实际执行的运行 ID,不倒填为原用例已恢复。不要固化本次刻度坐标,沿用用户明确要求保留诊断状态的安排。
凡要求信息一致、匹配或同步,编写提示词前核对具体比较字段、独立的预期值或可信来源,以及对应设备、账号、App 版本等范围。比如原用例要求“设备型号和厂商提供的一致”,须先取得厂商提供的目标型号,再将 App 中读到的型号与它比较;不能把 App 显示值同时当成预期和实际。缺少参照信息时列明待补项,等待用户补齐后再编写该条提示词。
例:原文若要求“进入系统设置,修改一个可见开关并确认状态”,提示词应说明识别目标开关、记录初始状态、执行切换、等界面显示目标状态并记录证据;若原文还要求回退,则依原文回退。示例只说明写法,不证明附件中的某条用例具有这些步骤。
升级类用例按本条原步骤和预期提取条件,不统一套用相邻用例的前置资料。例如只核验升级进度及重启后成功上报时,不追加“升级前全部设置和机位参照”;原文要求升级前后设置保持时,才记录相应基准并在真实升级后逐项比较。真实升级起点、可用且适配的 OTA 包与部署条件仍须落实;已确认可从 App 升级页查询时,先读取实际版本检查结果,不因未人工提供包就省略查询。不能用“当前已是最新”代替本条升级执行。
排除边界:PC 管理端、外接 console、实物遮挡/断电、需要人工听音/看画质、无法自主建立的网络或硬件状态,不标“是”。长时或重复的 Android 操作不是单凭次数就排除,但需有可测的时间/次数、可恢复前置状态和明确结果;缺任一关键条件则标“否”。ARTEMIS Pro 可观察 ADB/日志,不代表能代替外部硬件、PC 或人的感官。
原文要求在本次操作中生成的录制/播放区间、起止时刻和切换边界,在已有可靠独立时钟可用时由执行者按真实动作记录,不因尚未生成就列为必须外部预先提供的资料。原文指定既有录像区间、长稳日期或独立喂流/质量参照时仍须落实;不能用 App 时间轴两端显示互相证明实际喂流时长。保留原文完整时长,不以模型耗时替代计时。显示格式、时区和对应时点未核实时,不只凭状态栏或OSD的表面数值差认定时钟偏移、NTP异常或直播时延。原文“建议”“两种策略均可”“暂无硬性要求”等限定也须保留,不将推荐策略追加为整条用例的硬前置;分别说明可独立核验的操作、尚缺判据的断言与真实外部条件,必要条件未满足时仍按用户约定跳过受阻部分。
原文要求设置重复计划而未限定具体时间值时,先核对计划作用对象,再核对实际界面中既有预设的起止、跨日和循环语义。设备休眠、定时唤醒与屏幕熄灭计划不能仅凭名称相近或互为亮灭状态就等同;只见“关闭屏幕”总开关时,只能说明该页未见计划编辑项,继续按已查范围和相关候选入口核实,不推断全 App 没有计划能力。在已确认允许选择普通可逆测试参数的执行范围内,且能记录原值并确认本轮允许的恢复方式时,可依据可靠当前时间采用有效预设或选择本轮值,不仅因没有人工提供自定义参数就增设外部硬前置。提示词要求实际设置后核对保存反馈、回读及原文要求的生效时点和对应状态;预设可见不证明已保存、已生效或恢复成功。原文规定的固定值、独立期望、业务账号、真实场景和其他必要参照仍须落实,参数语义、时间来源或恢复安排不明时保留待确认;不固化未经核实的保存路径或时段。
例:原文若要求“进入系统设置,修改一个可见开关并确认状态”,提示词应说明识别目标开关、记录初始状态、执行切换、等界面显示目标状态并记录证据;若原文还要求回退,则依原文回退。原文要求在某设置状态下观察效果时,先在该状态仍生效期间完成对应结果观察,再按原文或本轮已确认的恢复安排处理;恢复后的观察不能替代修改状态下的结果。示例只说明写法,不证明附件中的某条用例具有这些步骤。
logo 水印用例(本轮 9.65)先按实际设置核实目标设备支持及开关,读取初始状态;尚未验证的入口只写查找意图,不编造“水印”必在某子页。当前关闭与出厂默认关闭分别核验,缺少可信默认来源时保留默认断言未验证,不擅自恢复出厂。按原步骤开启并回读,保持开启期间核查同设备直播右下角,以及原文要求的云/卡录像、告警、设备列表截图和区域设置画面;记录开启生效时点,关联同一状态时段生成的目标内容,旧录像、旧截图、App 固定标志或仅开关变化不能代替流中水印。各渠道分别记录已验证与缺项;新录像或告警素材尚不可用时有限等待/查询,不伪造外部触发、不无限等待,也不省掉其他可独立核验的渠道。开启状态下的观察结束后,按原步骤关闭并回读、核查原文要求的关闭效果;不能提前关闭后再补开态结论,或用统一“恢复初始值”覆盖原文明确的关闭要求,用户另有明确安排时沿用。开关可操作、各渠道显示、默认状态及关闭效果分别判定,单项完成不等于整条通过。
排除边界:PC 管理端、外接 console、实物遮挡/断电、需要人工听音/看画质、无法自主建立的网络或硬件状态,不标“是”。长时或重复的 Android 操作不是单凭次数就排除,但需有可测的时间/次数、可恢复前置状态和明确结果;信息尚未明确时先列待补项并澄清,只有确认所需条件无法建立或结果无法自动核验时才标“否”。ARTEMIS Pro 可观察 ADB/日志,不代表能代替外部硬件、PC 或人的感官。
当前查看的 `task_import.py` 要求 `任务` 页首行严格为上述七列,并禁止公式、宏和外部链接;`填写说明` 页不作为任务导入。实际部署的 ARTEMIS 版本可能不同,后续执行前核实服务的真实导入契约与逐行预览结果。
## 缩略图顶部下拉与直播返回更新(3.2)
适用于本轮3.2“设备缩略图展示”。用户针对 `0cdd62d4-df03-4aef-9c90-7df979250cef` 明确:需在缩略图展示页回到最上方后再次下拉,不能把列表回滚当刷新;进入直播再返回后的图像更新属于另一条结果。本节修订操作前提及证据边界,不把历史业务失败倒写成整条通过。
1. **正确模式及目标基线**:先按本轮已确认摄像机图标入口进入实际缩略图展示页,核对目标设备、卡片与图像区域,记录原图可辨内容及可读时间。目标在下方时可先定位并记录,然后回到该列表顶部;默认产品外观卡片或其他设备缩略图不能作为目标基线。
2. **回到顶部与下拉刷新分两段**:在真实可滚动区域向下滑,逐次观察页首内容、首卡及其位置变化,确认已到该列表最顶端;只见首卡、固定工具栏或列表向上方移动不等于到顶。一次无变化先核对手势区域、加载及遮挡,有限调整;到顶依据仍不充分时记录未确认,不发明固定次数或历史坐标。已经确认在顶部时直接进入下一段,无需额外滚动。
3. **到顶后独立下拉并核反馈**:确认顶部后,在列表内容区域另做一次下拉,避开状态栏、导航、播放和卡片功能按钮。核对实际可见的下拉提示、刷新指示及结束,或与本次动作对应的内容重新加载等有效响应,再等待列表稳定;不预设本App一定有某种转圈或文字。刷新反馈可能短暂出现,应结合及时观察或已有过程证据,不能强求较晚单张截图仍保留动画;手势成功、列表位移及回弹本身也不能证明刷新已触发。触发无法确认时有限纠正,仍无有效证据记“手动下拉刷新触发未验证”,不把旧图未变直接判刷新功能失败。
4. **手动刷新后独立比较**:完成上段核验后,目标不在首屏时重新滚动找到同一设备,读取稳定图像及可读时间,与刷新前基线比较,分别记录触发/完成依据和图像更新结果。不要在取得该分支结果前进入直播,以免后一个动作改变缩略图而混淆来源。触发或比较受阻时保留具体缺项,继续可独立执行的直播返回分支;不新增固定刷新秒数、缓存时效或后台实现推断。
5. **直播返回更新单独验收**:进入同一目标设备直播,确认实际播放,记录可辨场景及可读时间;退出回原缩略图模式,必要时重新确认模式并定位目标,再比较图像和时间变化。新图相对旧图有明确变化可记录“返回后更新已观察到”,不因前一手动刷新未验证而抹去,也不能由此补证前一下拉已成功。原“退出直播最近画面截图”仍按同设备、对应时点及内容的实际证据核验;较早保存的单帧与返回图构图不同,不单独证明更新失败,也不要求逐像素一致。确为同一静态场景且无可辨时间/变化时保留新旧无法区分,不倒填通过。
6. **结论与收尾**:手动刷新和直播返回更新分别保留已做、受阻及未验证项;两条均已覆盖或剩余项有具体受阻且有限纠正无效后,沿原强停App及执行方进程核对安排收尾。不用进入/返回直播代替顶部手动下拉,不新增云台转动、清缓存、重启设备或其他改变场景的操作来凑更新。
## 自定义图片上传与人工核对
适用于用户针对 `1c060f98-8e88-4d21-88fa-840144f2ef41` 为23.28“更换已应用自定义桌面”明确调整的范围:选择自定义桌面没有相应反馈时,结果人工核对;只要能上传自定义图片就算本次执行完成。这是本条已确认的完成口径,不推广为其他桌面、图片数量上限或上传失败用例一律只上传即可。
1. **完成实际App上传**:进入同一目标设备“设置 → 桌面显示 → 自定义”,按实际“添加图片”等入口从可用选择器选取本轮已获准的可辨图片,完成页面要求的提交/上传。保留素材实际可见、可选及身份核对要求,不因本条放宽效果验收就改选未经本轮允许的用户既有照片;不为凑上传新增数量、格式或删除遍历。上传前后分别核对图片内容与列表状态;只在相册勾选、点击提交或计数变化,均不能单独证明新增目标图已进入App列表。
2. **以新增图片留存确认操作完成**:提交返回后,本次目标图片在自定义列表中实际出现,内容/标识可辨,有限观察或再次返回仍在,且无明确上传失败反馈,可作为本条App侧上传执行完成的依据;结合实际数量变化说明,不仅凭N/N或按钮颜色。无需强求特定“上传成功”吐司;后台逐文件传输和实体显示不由此推断。目标图未出现、仍无法确认身份或明确报上传失败时,按当前反馈有限纠正,仍未完成则如实保留上传受阻,不能用“人工核对”掩盖尚未完成的上传。
3. **选择无反馈交人工,达到新门槛后收尾**:选中自定义页或图片后,若有限观察仍无明确应用、选中或设备端反馈,记录当前页面、实际操作及未见反馈,结果交人工核对;这不是上传失败或设备不支持的充分证据。不反复点击同一图片、虚构应用按钮或为补旧验收切换其他系统主题。原A/B/C切换、B/C实体屏幕显示及过程无重启/离线的要求保留为来源判据和人工待核结果,不再阻断本次上传完成,也不要求先建立实体A才能执行上传。按新口径分别交付“上传执行完成/受阻”和“桌面效果人工待核”,不能写成整条原功能自动通过。
4. **保留待核对象及原App收尾**:记录上传图片身份、实际列表留存和最终页面/配置,按本轮已明确的保留、恢复或后续衔接安排处理;未有清理安排时,不为了结束而删除待核新图或覆盖待核状态。若旧运行已切换系统主题,记录其真实末态,不冒称已恢复或要求重放旧操作。按原要求强制停止App并由执行方核对约定进程状态,不能把收尾代替未完成上传;此skill仍仅修订操作说明,不重跑设备或回写历史结果。
## 直播逐档五分钟与对应云录像(6.5)
适用于6.5“回放不同清晰度的云回放”。用户针对 `f0044b0a-4439-434a-9d88-a092ad232a90` 再次确认:在直播页面切换分辨率/清晰度,每个实际可用档位播放5分钟,再查看对应云存。保留原“音视频是否在不同清晰度模式下切换;不存在断点”判据及本条原收尾、配置;本节只修本条依赖与完成条件,不加12.14的H265/H264四组合、码率测量或App内录像动作。
1. **先区分可辨内容与实际播放**:本条清晰度入口在直播页,按实际菜单读取档位名称,不以设备设置“自定义编码参数”代替,也不由高清/超清标签猜具体像素值。真实出图、播放器反馈、多时点画面或可用连续证据用于确认播放;画面主体失焦、灰白或缺少清晰固定参照不能单独否定播放,更不能直接跳过菜单、切档和计时。画质与声音缺证据分别保留待核。持续加载、离线、错误、断流需有限恢复且不计为有效播放;不为可辨场景反复调云台或扩展无关设置。
2. **误点反馈必须纠正**:先按当前截图分别核真实视频矩形、相机/录像工具栏及云台面板拖柄,整屏中心或面板中心不等于视频中心,使用归一化坐标时按本次截图尺寸核落点在视频内,不套历史坐标或偏移;切页返回后重新定位。随后唤出落点在图像内无按钮处并远离底部工具栏、拖柄和云台。出现截图预览、录像状态或其他工具反馈,说明未完成正确唤出;重新观察、定位并有限纠正,不把误点后未见菜单当作设备不支持。短时浮层沿 [早图与连续操作](transient-controls.md) 识别入口、核当前布局;已隐藏且布局未变时用实际受支持的连续点击完成重新唤出与入口点击,不使用固定旧坐标、旧元素索引或同点双击。
3. **阶段A/B为逐档选择和真实300秒**:打开直播清晰度菜单并记录本设备实际全部可用档位。每档先实际选择并核对选中与恢复播放,再保持该档连续播放300秒;完成后才切下一档。初始档已选中可核验后从本次时点计时,其余档仍须实际切换;不能只打开菜单、只看标签、只测一档或把多档合计5分钟当作完成。实际只有一档时记录“不同清晰度”覆盖不足,不臆造其他档位。
4. **本次起止表由执行动作产生**:逐档记录设备、档位、选中反馈、可靠开始时刻t0、截止t0+300秒、实际结束时刻、切换边界及中断。使用实际支持的时钟和分段等待,按剩余真实时间安排,段间核播放状态与推进;不从任务开始、回看或模型回合数计时,也不机械追加已过的完整300秒。模型处理耗时只有播放条件持续成立时才计入,不能按平均耗时估算。真实中断如实记录,允许恢复后重新建立完整区间或保留该档未完成,不拼接冒充连续5分钟;无人工预先提供本次时刻表的要求。
5. **独立云存探查与阶段C分开**:云存状态未核实时先实际进入同设备云回看,确认当前存储类型、加载、已选日期、套餐/访问反馈、录像列表或时间轴;有线索时选已过去的时段实际点播或拖轴并回读日期时间、播放推进。灰色/彩色均可作查找线索,不以单个空时段、推广卡、其他服务未开通或只见入口文字断言无云存;默认日期空白时按实际可选的相关近期日期/时段有界查找,区分明确未开通/到期或权限限制、已查范围无录像、加载故障/未核实,不购买或改录制模式。此探查可在A/B前执行,清晰度局部受阻时也应独立尝试,不能仅写“云服务未核实”就结束。旧片段可播放只说明该历史范围可访问,不证明当前新录制或本次档位视频已生成;探查结束回同设备直播重新定位,继续可做的A/B,单纯无旧片段不构成禁止生成,真实服务/设备故障影响相应依赖阶段。阶段C仍只核本次已生成片段,不能以预探查替代。进入对应云录像核查前,逐档核对实际选中、连续300秒、起止与切换边界。可补做漏项先留在/返回直播补完;确有具体故障且有限恢复无效时记录受阻阶段与缺项,不用旧录像补成已完成。为核云服务而临时查看回看页,不能代替A/B,具备条件后仍须回直播完成。全部实际档位完成后,才按同设备、实际时区、已生效日期、本次过去区间定位云录像;灰色/彩色是本轮有数据线索,实际播放及时间回读才确认对应,不把视频灰白等同于时间轴纯白空洞。
6. **对应录像与原判据分别核验**:逐档及切换边界实际回放,观察音视频切换、播放推进与连续性,保留原无断点要求;回放页当前清晰度标签、时间轴颜色或任意旧事件均不证明本次云录像历史编码发生切换。新录像未就绪的分支仅在A/B已完成并记录本次区间后适用,按实际反馈与剩余预算有界等待、刷新或重进回查;记录可查范围与缺项,不无限等、不选未来、不换任意旧日期。云服务需关联目标设备,不从其他服务卡“未开通”推云存状态,不擅自购买或更改录制模式。
7. **收尾核对具体覆盖**:先核独立云存探查是否实际访问/试播或取得明确页面限制;清晰度失败且入口仍可用时先补探查,未访问不能当已确认无云存,已查无片段结论限定范围。再分别记录“生成未做/部分完成”“生成已做但对应云录像未就绪”“对应云录像已查及原判据覆盖”。画质、声音或历史编码证据不足只影响相关断言,继续仍可独立执行的动作和观察。未做且仍能执行的逐档操作/计时不能仅标未验证后停止App;满足全部必做项、剩余确有具体受阻且有限恢复无效、用户取消或实际预算终止后,沿原强停App安排收尾。生命周期completed不等于本条业务完成或通过;本次文案修订不改历史结果、不自动重跑。沿用已确认的 `{"profile":"flash","timeout_seconds":3600}`,不改全局配置。
## 卡回放本次切换点定位与操作视频(7.4)
用户针对 `5250cbe5-e63f-488d-b315-204aad62fc0f` 明确:在卡回看拖动进度条到切换分辨率的时点查看,本条不需要ARTEMIS判断音频,只保留操作视频。原编号、直播每档5分钟、TF卡前置及卡回放顺序保留;原音频判据转人工待核,不能继续作为自动完成的硬门槛,也不推广到其他用例。
1. **记录本次直播样本**:在直播页按实际菜单核清晰度,逐个可用档位选择、确认选中与恢复播放,每档连续300秒,记录同设备、档位、开始/结束和每次切换边界。初始档核验或切至高清等档位并确认恢复播放后,实际建立本档t0;尚未到t0+300秒且直播持续时,下一动作应为受支持的等待配合观察,不能因尚未完成等待/回看直接stop。当前执行端wait_for_delay(time_in_ms)以毫秒计,300秒为300000毫秒;按工具单次上限及真实剩余时间分段,不每轮重等完整300000毫秒,不拿总会话时长代替本档计时。未调用等待不是等待能力受阻,实际错误则保留回执并有限处理。计时按 [真实计时](#长时任务预算与实际计时),浮层按 [早图与连续操作](transient-controls.md)。高清/标清等实际可用档不能因另一档受限或音频待人工而省略;仅30秒体验、付费限制或无法维持300秒的档单列受限,不购买、不反复试用拼足5分钟,继续其余可用档及已完成区间回看。
2. **核类型与日期后实际拖动**:确认同设备、实际“卡回看/卡回放”、对应日期已选中,不能把“今”已选却未列入旧摘要写成日期未知。进入时的默认旧帧/指针只代表当前选时,未定位本次时段前不能断言卡录像不存在。右侧云服务推广卡不代表整页为云回看或卡时间轴不可用;灰色也可能是本轮正常录像轨段。确有无卡、权限/加载错误时记录真实反馈,可按已授权范围只读核存储状态;不把缺少实体插卡证明加成一切UI操作前置,不格式化、不改录像计划。
3. **本次时点是必做选时目标**:读取刻度、指针与可拖动区域,先在实际时间轴内小幅试拖,回读前后选中时刻确定方向;再小步拖至本次各档区间和每个实际切换点之前,松手待加载/恢复播放,核日期、时区及播放时间确实对应,再播放跨过边界并记录切换后的显示。已有本次过去起止表就可用于定位,不要求先有彩色条/预览/已播放证据才准拖;时间轴颜色不是拖动许可,也不是播放结果。目标不在当前视段时继续在时间轴内推进,必要时用实际存在的缩放/日期入口;不在事件列表或推广卡上滑动冒充选时,不固化方向、坐标或精度阈值,不选未来。
4. **拖动无效与无录像分别验收**:手势回执成功不等于选时成功。时刻未变时先核手势是否在时间轴、是否被弹层拦截以及页面是否加载完成,再有限调整落点/幅度并回读。确实无法拖动则记录动作、时刻与受阻范围;拖至目标后明确无数据/不可播放才记录相应区间结果,可按实际反馈有界回查近期索引。不能因未拖就说本次未生成,也不因有灰色轨段就说录像必然正常;不得用其他时间或云录像替代。
5. **原策略和音频边界**:保留厂商可自定义卡码率、随直播切换码率或固定码率均可、暂无硬性要求。只记录对应片段实际显示、时间推进及切换前后操作,不从播放标签、模糊观感或未变分辨率断言卡编码策略失败。原音频效果交人工;不要求自动听音、音频采样、扬声器权限或有效音轨,不因缺音频证据停止其他可做动作。
6. **单条录屏与完成口径**:沿用Flash及3600秒,本条配置仅增加 `record_video:true`、`analyze_video:false`,不改全局。视频覆盖直播清晰度选择、各档播放、卡回看类型/日期、实际拖轴、时刻回读及对应播放过程;App内录像和截图不能替代模拟器操作录屏。宿主在派发及执行期间核实际录制启动与持续状态,任务结束后核最终文件及可播放性,给出真实路径并关联各阶段时间;缺操作片段或文件保留采集缺项,不让Flash调用不存在的录屏控制工具,也不要求Flash等最终成片才停App。视频无音轨不构成本条音频自动失败,不承诺该视频可供听音。本次采集完成须待宿主取得最终视频确认,与原人工业务结论分开;只选中高清等档位且无真实终止条件时,继续本档计时、其他可用档及卡回看,不能先强停再把未做项全部列未验证。音频待人工、其他档试用限制、成片尚未结束不阻断仍可执行的阶段;仍可执行的拖动/回读先补完,再按原强停收尾,真故障或预算退出如实记未完成。保留旧结果,不自动重跑。
## 休眠直播检查与唤醒恢复(9.32)
本节针对任务`e898b8d4-9242-4086-b024-e5e7f78c9b22`及用户补充的9.32范围:开启休眠后必须查看直播,并执行唤醒、核验直播恢复。保留本条“不支持云台遮挡机位”的原文和适用性待核项,不给9.32追加9.33的实体转位遮挡,也不把本次改动自动写到9.33或定时休眠用例。
1. **先建立并核对休眠态**:确认同一目标设备,记录休眠初始状态;实际关闭时开启并核必要保存/回读,已开启则核验后沿用,不为凑开关动作先关再开。开关回执、颜色及“设置成功”分别如实记录,不单独证明视频流已休眠。可信硬件分支资料不足时保留该项未核,不因此跳过用户已明确要求且可独立完成的App操作。
2. **在唤醒前实际进入直播**:从休眠设置返回同设备直播并确认已进入实际视频区域;一次BACK可能只到设备设置外层,须按当前页面继续导航。保持休眠生效,先读取App休眠提示及实际黑屏、马赛克或其他推流现象,不先点击“唤醒”。进入时出现唤醒提示则先观察当前休眠反馈,再进入唤醒阶段。若进入直播会自动唤醒,记录实际转变及休眠态观察缺项,不声称仍在休眠;普通离线、持续加载或原有占位图不能自动判为休眠推流符合预期。原文预期样式不符、画面仍正常等分别记偏差,仍继续可执行的恢复。
3. **真正唤醒并回读**:按当前界面实际提供的直播“唤醒”等入口,或返回同设备设置关闭“设备休眠”,核必要确认/保存和反馈,再回读休眠关闭状态;不存在专属唤醒按钮时使用已定位的休眠开关,不臆造路径或连续盲点。先确认当前状态,已经关闭时保持,不重复翻转。原智能喇叭、智话等功能条件缺失只影响各自断言,不能阻断这次独立唤醒;不新增重启、断电、实体遮挡或云台动作。
4. **唤醒后再查看直播恢复**:进入或留在同设备直播,等待实际加载,检查休眠提示是否解除、当前视频是否出图及有无可观察的播放推进;结合画面时钟、场景变化或实际播放状态,不只凭旧单帧、播放图标、开关关闭或“设置成功”判恢复正常。若直播恢复但开关无法回读,分别记录画面已恢复及设置状态未验证;持续黑屏/加载/报错则如实保留恢复异常或未验证,不把它当正常休眠继续结束。检查只覆盖本条恢复,不追加固定播放时长或音画质量指标。
5. **检查覆盖及末态后收尾**:明确核对“休眠开启/回读—休眠期间直播观察—唤醒/关闭及反馈回读—唤醒后直播恢复”四阶段。尚可执行的漏项先补完;原智能喇叭/智话、硬件分支及实体/声音观察缺条件时各自保留未验证,不冒充整条通过。有限纠正当前页、加载、弹窗或实际入口后仍无法继续时,记录具体受阻阶段、最后可见休眠/直播状态及是否仍待人工唤醒,再按原强停App及进程核对安排收尾。本次以用户要求的唤醒末态为准,不为恢复初始休眠值再次开启休眠;强停App既不代表唤醒,也不代表已完成两次直播检查。保持本条Flash及原任务配置,不为普通状态检查增加录屏、长时等待或全局改动。
## 长时任务预算与实际计时
1. **派发前核对有效总预算**:真实持续播放/等待时长只是整条执行的一部分,还要计入启动 App、定位设备、加载、测量、有限恢复及收尾,按实际估算预留余量。核对当前单条覆盖值及继承的批次/全局时限;提示词写“等待30分钟”不修改调度预算,普通业务等待计入总时限。30分钟播放用例不能继承恰好1800秒总预算;在受支持的本条 `任务配置(JSON)` 中明确覆盖,例如 `{"timeout_seconds":3600}`,有其他已确认字段时合并保留。本示例只用于本类时长及余量安排,不统一提高所有用例或全局默认,也不以延长模型请求超时代替业务预算。超出当前平台单条支持时限的长周期按已有编排条件处理,不能填写不受支持的数值。
2. **明确业务计时起点与剩余时间**:先确认目标设备直播已实际出图并在播放;加载占位、灰白模糊单帧或“直播”标签不能单独证明正常持续播放,也不能仅凭内容模糊就否定实际播放或阻断仍可操作的清晰度选择。按多时点画面/播放器反馈与实际可用连续证据核对播放状态,画质可辨性与播放状态分别判断;明确持续加载、离线、播放错误或断流不计有效播放。以本次确认正常播放的可靠时点为 `t0`,记录实际截止时间 `t0+原时长`;每轮依据可靠时钟计算已过及剩余时间,期间导航、观察和模型处理只有在播放条件持续成立时才计入,不按回合数或平均模型耗时推定。不能从任务启动、排队或未知历史播放时点起算,也不在已过部分有效播放时间后重新追加完整30分钟。进入等待前再次核对剩余总预算是否足够完成剩余业务、测量和收尾,预算不足不发送注定跨过任务截止时间的长等待;记录调度条件缺口,后续按已有授权配置与执行安排处理,不缩短原时长。
3. **分段等待并核对播放连续性**:使用实际支持的等待、时钟和观察能力,按剩余时间安排较短等待段,段间观察同设备、播放推进、有无断流/错误/离开直播;宿主按工具建议进行有界状态查询,避免一条全程等待使中途没有检查机会。分段解决可观察性,不能解决总预算不足;段间实际耗时计入可靠计时,不能机械叠加成30分钟外的额外等待。仅结束时仍停在直播页或时间戳增长不充分证明全程无中断,结论按实际观察及可用连续证据的覆盖报告。实际中断时保留中断及其时点,需满足连续时长时应在允许恢复后重新建立有效区间或保留该断言未完成,不能把中断前后拼接冒充连续30分钟。
4. **12.6 的等待与时延测量分开验收**:原要求连续实况播放30分钟后记录图像时延 `t`,判据 `t≤2s` 保留。执行前核对可用的独立采集至呈现时间参照或可靠同步测量方法,自主可建立的普通计时由执行方准备,不强制唯一工具,也不编造外部时钟或现场素材。方法未落实时记录时延测量缺项,等待本身不能补足测量能力;可独立执行的播放观察按本轮安排保留。OSD 与系统时间在时区、格式、时钟同步及对应采集时点未核实时,不能把表面差值当作时延或 NTP 缺陷;录屏时长、等待回执或总会话时长也不证明 `t≤2s`。
5. **区分合法等待与异常停滞**:核对最后有效动作、等待参数及其开始/结束状态,再判断没有新 UI 步骤的原因。明确正在执行、仍在原业务时窗且预算充分的等待,不套用“模型无进展”退出逻辑;连续模型请求超时、未产生等待动作或等待超出真实预算则按异常分支诊断。超时终止后单独核对 App 收尾,不能把 runner 被回收、设备锁释放或录屏结束当成已强制停止 App。本 skill 只编写预算、阶段及交接要求,不实际改变全局时限、启动长时任务或补做设备动作。
## 用例完成检查与收尾
编写提示词时,把本条原操作、各状态下的结果观察及恢复要求对应成必要阶段,明确进入收尾前还须完成哪些读取和比较;使用执行端实际可用的计划/检查项能力,不发明工具。普通短任务可直接写清阶段,不强制更换 Flash/Pro 或增添专门文件。
1. **逐项核对后再结束**:在返回首页、强制停止 App 或上报整条结束前,对照本条必需项区分已执行、尚未执行及实际受阻。操作已完成但对应效果未观察,或要求多个字段却只读了一项,均属未完成;无实际阻碍时继续补做。原文“任意一项”仍只需按其量词执行,不能把退出检查变成额外遍历。
2. **偏差与后续覆盖分开**:弹窗文案不符、某项失败、可信默认依据缺失等,只影响对应判定;按原预期保留偏差,继续独立可做的开关、字段回读及其他分支。不能以“已发现问题”“未验证”或“准备收尾”为由放弃仍可执行的项目。App内业务录像的开始、规定真实时长、停止与保存分别检查;未尝试录像/等待不构成它们受阻,缺SDK/RTSP、提取分析或长期条件不直接阻断可做App录像,详见 [录像共同规则](encoding-playback-feedback.md#写入任务表的共同规则)。某动作会破坏尚需观察的状态时,先观察该状态再继续或恢复;禁止用手动恢复、重复已有动作掩盖缺项。
3. **明确退出条件并保留原收尾**:全部必要动作与可做观察已执行后收尾;剩余项确实受阻时先按该分支有限纠正,保留具体缺失条件、尝试动作及反馈,并完成其余独立项目后收尾。用户取消、实际预算终止或不可继续的设备/工具故障时按真实情况退出,不无限重试、不越权补条件。失败和受阻也执行原约定的 App 收尾,但停止 App、释放设备或执行状态 `completed` 都不能代替业务完成。宿主终态检查发现漏项时如实标明覆盖缺口,不倒填通过;未获重跑安排时不自动新建任务补测。
## 云台四向长按与操作视频(4.11)
本轮4.11“转动噪声要求”按用户新增要求,先完成上、下、左、右各真实持续按住5秒并保留操作视频;原“设备转动过程中无明显噪声”继续保留,不以无声音证据为由省略仍可执行的四向动作。此处只更新本条采集和完成门槛,不给4.3机械限位或4.8真实OTA增加或删除断言。
1. **按当前页面定位四个方向。** 确认目标设备直播,打开实际云台面板,核对四个箭头及命中位置;不是点击方向盘内圈、预置位图标或视频画面。记录可辨的固定场景参照;无可辨画面时保留转动效果未验证,但按钮可可靠定位且不存在不可继续的故障时仍执行四方向按压。原步骤的“旋转视频源”若含义或入口尚未核实,单列该步骤缺项,不凭空切换镜像、倒装、横竖屏,也不以此跳过明确可做的四向动作。
2. **逐方向真实按住5000ms再松开。** 按上→下→左→右顺序分别执行实际持续按压,再观察松手后的状态及固定特征变化,随后定位下一方向。使用执行端真实提供的时长参数;当前安卓工具`long_press`的`duration`以毫秒计,5秒写5000,执行时仍核其实际工具约束及回执,不编造参数。一次短点后`wait(5)`、`click_sequence`的`delay_ms=5000`、四个快速tap或仅一个方向的1800ms长按,都不满足四向各5秒;模型推理和截图耗时不抵扣按住时长。若时长能力不可用或动作报错,保留对应方向受阻,不擅自截短或改成连续短点。
3. **四项覆盖明确记账。** 对上、下、左、右分别关联实际动作、请求按压时长、回执、松手及画面反馈,缺方向、短时长或无回执分别记缺项。参数和成功回执可证明已请求/执行对应按压,不单独证明机械转动或精确物理触点时长;画面变化仍需可辨参照。单方向效果不明时有限观察与定位纠正,不能反复用上方向代替其余三项。缺少噪声证据只影响声音判定,不阻断其余可做操作。
4. **以单条配置保存操作视频。** 本条`任务配置(JSON)`设置`record_video:true`;按当前人工交付范围可设`analyze_video:false`,保留其余配置,不改变全局默认。操作视频覆盖按压前、四次持续按压、松手后观察,保存并关联本任务真实文件、可播放性和各方向时间。宿主执行方核对实际录制启动及结束产物,不让Flash调用不存在的录屏查询工具;App内录像仅录摄像头画面,不能替代模拟器按钮操作视频,截图也不能替代。视频无音轨或无法确认声音来源时,不能证明设备机械噪声;不得默认新增录音权限或改全局音频配置。
5. **完成动作采集再收尾,业务结论另核。** 四方向均达到本条按压要求且操作视频已保存,才可说本轮动作/视频采集完成;宿主尚未取得视频时标待交付,实际故障退出时列出已做与缺项,不宣称完整完成。保留原强停App收尾,但不能因没有录音或一方向画面不明就提前停止其余可执行动作。原噪声结论只有可靠转动期间声音证据才可判定,否则交人工核对;自动采集完成不等于无明显噪声通过。
历史样本`de6e9a60-3659-4431-9fa2-790a96402a56`实际初始目标为4.11:第3步`click_sequence`只发四个tap,`delay_ms=800`为点间等待;第4步仅向上`long_press duration=1800`;第5步停止App,第6步主动上报`completed`。原图及顺序解码录像核对四个落点较对应箭头整体偏上约144–175像素,落在拖柄附近、圆盘中心或盘外空白;第4步重复错误上向位置,属于视觉定位错误,不是归一化坐标换算错误,也没有证据表明动作时面板上移。最终业务结果是`inconclusive`,不是通过;PASS目录名和success日志不覆盖该业务结论。原提示词仅写“点击云台上下左右”和取得噪声,未将四向5000ms及视频交付作为本条显式门槛;其他云台用例已有5秒规则不能自动补入本条执行正文。录制停止虽报错,本地MP4仍存在且完整解码通过,约148.74秒、只有视频流无音轨,不能证明机械声音。后续修订不倒填该历史任务四向5秒已完成,也不自动重跑;样本偏移和坐标不作为新布局的固定修正量。
## 设置页查找与到底确认
设置页用例的提示词须包含本条所需的持续查找及停止条件;不能只写“合理查找后未见则结束”,让执行者在仍有下方条目时提前放弃。以下按页面实际反馈执行,不以增加任务时限或盲滑次数代替覆盖:
1. **先加载并沿已确认路径查找**:确认目标设备及设置内容已加载。已有用户确认路径时优先定位实际入口;本轮码率、分辨率、智能编码及 AOV 的设备编码配置到设置页下方“自定义编码参数”查找,9.139第二轮用户称“自定义码率参数”,按当前入口标签与实际码率/帧率/I帧间隔及相关开关核实目标。进入可操作页后再找目标控件;能力集或原始Config只读展示不等于编码参数设置页,不能只在主列表匹配功能名称或反复进入“画面与声音”“AI设置”。12.14 采样时的高清/超清选择仍在直播页操作,按 [编码组合录制](#编码组合录制与码率判定1214) 区分两处作用。第16部分中16.1按 [最新端侧例外](#第16部分云化ai算法范围与入口) 先只查主页面“人脸侦测”,其余云化AI用例仍到“设置 → AI设置”核对本条算法,不能以近名端侧功能或“云存储专属”标记替代云化目标。其他功能仍按自己的已确认路径或实际相关入口,不一律塞入编码参数页。目标出现即进入,不要求每条先遍历到底。
2. **有新内容就继续向下查**:目标尚未出现时,在实际可滚动列表区域向上滑动,让更下方内容出现;可采用适合当前页面的较长手势,但保留部分已见条目作为衔接,避免越过未查区域。每次等待页面稳定后,比较顶部/底部条目及其位置,检查新出现的行和入口。只要新条目出现或列表位置仍在推进,就继续查找,不能因已滑数次、看到“功能设置”“增值服务”“其他”“固件升级”“设备检测”或“网络切换”而宣称到底。本轮这些区域之后仍可能有开发者选项及“自定义编码参数”,以当前页面为准,不固化分组顺序或坐标。
3. **核实末尾,区分手势无效与到底**:明确指向列表底端的边界反馈可以作为末尾证据,普通手势执行成功或滑动动画结束不证明到底;没有此类反馈时,先确认手势落在正确列表、无弹层拦截、内容已加载稳定,再有效续滑并核对末尾条目及相对位置是否不再推进。单次无变化不证明到底,先有限调整手势落点或幅度并重新观察;仍无法辨认是手势无效、页面未加载还是已到末尾时,报告“滚动受阻/末尾未确认”,记录最后可见范围及未查部分,不写已遍历完整。不得连续无观察盲滑,也不发明固定像素、滑动次数或无期限重试。
4. **末尾与功能结论分开**:目标仍未定位且已确认主列表末尾时,通常仍须核对与目标相关且尚未查看的实际可点击子页,查完回到已记录位置继续,不循环同一候选页、不修改无关设置。**16.1例外只以完整主列表中是否有“人脸侦测”为本条支持判断,不进入AI设置、AI周界等子页另找算法**,按下节的最新用户口径记录结果;主列表未加载、未查完或滚动受阻不能按无入口处理。其他用例区分“已定位”“已确认主列表到底但相关子页未查”“已查范围未见”“滚动/加载受阻”;注明已查入口、最后可见条目、末尾依据和未查范围。通常未定位不等于设备不支持,按本条能力分支保留待核对结论。任务预算或实际页面故障使查找中断时如实报告部分覆盖,不能为满足到底要求无限运行。
## 人形侦测与告警判断
按本轮用户针对移动爱家目标设备的补充,人形侦测告警及其事件视频用例先核对设置,再核对启用状态,最后按本条范围判断告警;不能从告警筛选分类、空历史或直播画面反推设备能力。后续提示词按以下分支编写,不预设未经核实的二级菜单:
1. **先查设备设置**:确认是同一目标设备的“设置”页,等待实际内容加载,按前述设置页查找边界检查列表下方及语义相关子页。找到“人形侦测”或已核实对应同一功能的设置才进入启用检查;未见时记录已查与未查范围,说明“设备设置中未见人形侦测,可能不支持,需人工核对”。查找不完整或页面未加载时另记定位受阻,不能直接判设备不支持、产品失败或用例通过。
2. **再查是否开启**:读取并记录该功能的实际开关/启用状态,入口存在不等于已启用。关闭时记录当前未开启;若本条要求验证本次触发,说明该触发前置未满足。仅在本条原步骤或本轮授权要求开启时才操作,并核对保存反馈及回读,不能把查看改成默认开启。状态读不出或开启未确认时,保留启用状态及本次触发结论未验证;当前关闭也不能证明历史从未产生告警。
3. **最后判断告警**:对本次触发的判断,须先确认人形侦测已开启,再核对本条要求的真实人形触发条件、对应设备与时点及有效观察时窗,查找对应人形事件,记录在有效时窗内告警产生或未产生。前置满足且观察完整时按原预期判定;缺少真实触发、有效时窗或可靠的观察证据时写明缺项和告警未验证,不能仅以无记录判漏报。不编造固定等待阈值或让 ARTEMIS 等待未落实的外部动作。区域入侵、普通移动侦测与人形侦测仅在功能对应关系已核实时按覆盖范围使用。事件生成、消息推送与视频可播放分别按原预期核验,不互相代替。
4. **保留事件视频用例的范围**:8.8“人形告警视频质量”在设置及启用状态核查后,仍可查找同设备其他日期的既有真实人形事件,核实事件身份后播放对应视频,再检查本条原音画要求;本条原文允许既有素材,不因新增设置核查一律强制新触发。当前已开启不证明历史事件发生时的配置或本次已触发,未找到可确认的人形视频时保留视频及音画质量未验证。直播噪点或其他类别的视频不能替代目标视频证据;按本轮已确认安排,需人工评价的音画结论仍交人工,不以播放或采集完成代替通过。
## 声音侦测开启与两分钟后检查
本轮用户针对 8.30“声音告警视频质量”补充了操作顺序:到目标设备“设置”开启声音侦测,等2分钟后检查对应告警。这是本条已确认的开启安排;编写后续提示词时直接采用,不重复询问是否允许开启,也不将历史列表查找替代这些动作。本 skill 仍只修订提示词,不启动或重跑设备任务。
1. **先开启并确认**:进入同一目标设备的“设置”,等待实际内容加载,按设置页查找边界定位声音侦测。读取初始状态;关闭时开启,存在保存操作时完成保存,再回读确认已开启;原已开启则保持,不先关再开。未定位、无法读取或开启未生效时记录具体阻碍及已查/未查范围,未见入口的支持情况留待人工核对,不能把点击回执或告警分类当作已开启,也不修改无关设置。
2. **从确认时点等2分钟**:以本次确认声音侦测已开启且必要保存完成的时点为 `t0`,原已开启时也以本次确认时点起算,记录实际时间及关联时区。保持该设置生效,自 `t0` 实际经过2分钟(120秒)后再检查告警;不从启动 App、任务开始或未知的历史开启时间起算,也不在检查前关闭侦测、恢复设置或停止 App。执行方依据可靠时钟计算剩余等待,可将期间导航、观察及模型处理的实际耗时计入已过时间,不能按回合数或模型平均耗时估算已经等满;需要显式等待时按实际支持能力补足剩余时间,不无限等待或机械叠加额外2分钟。计时无法证实时保留等待未验证。
3. **检查对应告警**:等待完成后进入同设备告警列表,核对当前日期/时区及实际选中日期,按页面实际能力刷新或重新进入查询,核对 `t0` 后对应时段的声音事件、事件时间与类型,记录查到或未查到。旧日期、开启前的记录、区域入侵或画面变化不能证明本次声音告警正常;消息推送、事件生成和视频可播放仍按本条预期分别核验。记录观察期间实际声音刺激及来源(若有);现场声源尚未核实时,仍完成可独立执行的开启、2分钟等待和查询,不无限等声源。仅有“已开启 + 等满2分钟 + 无记录”而无有效声音触发证据时,只能记本时段未观察到声音告警、触发能力未验证,不能直接判漏报;触发及观察证据完整时按本条预期判定,不把2分钟擅自推广为所有声音告警的性能阈值。
4. **保留哭声视频断言**:原 8.30 正文指定哭声侦测事件,声音侦测与独立哭声侦测是否共用配置须据实际功能核实,不能只凭名称相近认定相同。普通声音告警可记录本次新增检查结果,但不自动满足原哭声视频断言;确认真实哭声事件后播放对应视频,核验原文所列音画要求,需人工评价的部分按本轮安排留待人工。既有真实哭声视频可覆盖原视频质量检查,但不能替代本次开启、等待及新告警检查;未取得目标视频时保留该部分未验证,不把设置成功或查询完成写成音画通过。告警检查结束后沿用本轮已确认的设置保持/恢复及 App 收尾安排。
## 声音侦测状态与最近七天告警
适用于用户针对 `1ff4c406-14a2-40b6-968f-75fb5e4bf3c2` 为1.14“7天挂测设备异响检查”补充的顺序:先查看声音侦测是否开启,再检查最近7天声音告警。此处查询已有历史,不继承8.30的开启及120秒新触发安排,也不复用12.24的云/卡存时间轴、颜色空洞或每日事件数要求。
1. **先核当前声音侦测状态**:确认同一目标设备,从其设置页按实际加载内容定位“声音侦测”,首屏未见时按 [设置页查找规则](#设置页查找与到底确认) 继续;进入实际详情,读取启用状态及可见配置,不能以主列表入口、指示灯开关或历史告警反推已开启。用户本次仅要求查看,保持声音侦测及灵敏度原值,不自动开启、调档或先关再开。关闭、入口未定位或状态不可读时记录具体情况和原开启条件的缺口,继续可独立查询的七天历史;当前关闭不代表过去无记录,当前开启也不证明七天内持续开启。
2. **固定七天范围并逐日查看**:以开始查询时的可靠日期/时区确定最近7天,未另指定时按当天及前6个自然日列明日期,当天只查至本次已过去的查询时点;用户明确完整日期或滚动168小时口径时沿用,不能将部分当天冒称完整七天挂测。进入同设备告警页,按实际可用的“声音/声响”等筛选核对事件类别,未提供筛选时依据实际事件类型查找,不用“全部”列表的一屏代替。逐日选中并回读日期,等待记录加载,按可用分页或列表滚动查看该日范围,查询仍有新记录时继续,明确真实已查时段及末尾;一条记录、日期栏出现七天或仅查看今天均不证明七天覆盖。嘈杂环境按原要求重点核对夜晚,同时保留其他时段的覆盖,不虚构固定夜间时段或忽略白天。历史保留不足、无权限、加载失败或分页受阻分别记录,不等待未来7天、改设备时间或补造历史。
3. **分别记录告警与异响证据**:按日期列出实际查到的声音告警时间、类型及可核实数量/范围;只有该范围已查完整且明确无记录时才写“未查到声音告警”,未查或不可访问不填0。对疑似异常记录按实际可用入口核对对应音视频及声源资料,关联设备、事件日期和时点;可靠核听或交人工确认是否为设备自身异常声响,不能仅以“较大声响”标签判故障,也不能以有声响告警直接否定原“无异常响声”预期。无告警同样不证明过去没有异响、侦测有效或没有漏报,当前灵敏度不证明历史配置。没有可判定声音资料时保留异响性质/来源待核,继续其他日期,不把查询完成写成无异响通过。
4. **保留原挂测条件及收尾**:原绑定、在线、挂测设备声音侦测开启、另一监控设备对准挂测设备且也开启声音侦测、连续七天含夜晚的范围均保留,需原挂测结论时另核可靠历史起止、配置及对应观察资料。第二监控设备身份和布置尚未确认时,记录缺项,不将列表中任意另一台离线设备直接当作该监控机;这些缺口不阻断本次已具备条件的状态读取和七天历史查询。分别交付当前设置状态、逐日告警覆盖/记录和原异响判据的未验证范围,不以本次历史查询替代完整七天挂测。完成可做项后沿原强制停止App及下一条前核对主/前缀子进程的安排收尾,本条未改设置不新增恢复动作。
## 编码参数互斥与完整核验(9.139)
本条验证“码率/帧率/I帧间隔参数组”与“AOV/智能编码功能组”的互斥及默认恢复;AOV 与智能编码可同时开启,不要求彼此互斥。第二轮用户指定目标设备“设置 → 自定义码率参数”;上轮实际进入的入口名为“自定义编码参数”,当前按实际标签与编码字段核实可操作目标页。不同运行的进入证据不可互相倒填:第二轮只读设备能力集原始Config并未进入参数操作页,不能以该页取代设置入口或据此判互斥通过/失败。按以下阶段对应原步骤,完成检查后才停止 App;按 [两次重置提示分支](encoding-playback-feedback.md#9139参数变更后的两次重置提示分支) 保留完整提示和两分支回读,不在入口偏离或参数修改后提前收尾。
1. **读取基线**:先读码率(如有平均/峰值分别记录)、帧率、I帧间隔及两个开关,页面有 `gop设置` 时核实与本条 I帧间隔的对应关系后进入读取。当前值与可信默认依据分别记录,不能把本次首次读到的1000、1250、15等数值直接认定默认,也不为补默认依据新增恢复出厂。缺少可信默认只影响默认比较,不阻止三项读取及可独立完成的开关核验。
2. **参数修改与自动关闭**:按原文任选一个实际支持的参数改为合法且不同于原值的值,不强制修改全部三项或遍历所有值。为观察要求的自动关闭,先确认两个功能的可观察前态,在本条已授权开关操作内建立开启条件。变更参数并触发保存/应用后,先读取当前弹窗全文,与原预期“设置此功能将会关闭AOV和智能编码功能”比较,确认后完成保存并重进回读参数及两开关;不能先保存离页再核已消失的提示。原已关闭而修改后仍关闭的项,不能声称发生自动关闭,不手动关闭来冒充自动互斥。前态无法建立时保留对应关闭转变未验证,不扩大操作范围。
3. **分别开启并完整回读**:AOV 和智能编码各自的恢复分支,均须在本次已保存有效参数变更的前置下检查。有可信默认时选择合法非默认值;无可信默认时选择合法且不同于变更前的值并保存回读,继续测弹窗、开关及三字段,仅默认恢复比较保留待核,不以无法证明非默认为由省略两分支。前一功能已恢复参数后,后一分支重新建立原文允许的任一参数变更,不能直接开第二个开关并把无变更时无提示当作触发过恢复。分别开启目标功能后,在确认及离页前读取当前完整提示,与原预期“设置此功能,码率、帧率、I帧间隔设置将恢复成默认值”比较;确认开启后逐项进入回读码率、帧率、I帧间隔,有依据时与各项可信默认比较,再进入下一分支。不能只读被修改的那一项;初始已处于默认的项可验证仍等于默认,不能据此证明发生非默认到默认的转变。文案只列部分参数时保留差异,仍读取其余两项;某一项打不开或缺默认依据时记具体缺口,继续其他可读项,不把未查 GOP 当作受阻。
4. **关闭及收尾前核对**:分别关闭 AOV、智能编码,核对原文“不需先校验三参数即可直接关闭”的逻辑与状态反馈。返回或停止 App 前,核对参数修改、两开关自动关闭前后态、两次开启各自的三参数回读、完整提示及关闭操作是否均已覆盖;仍可打开的未查字段或分支应补做。已有失败与真实未验证项分别保留,按本轮恢复及收尾安排记录末态;不因发现文案差异就结束,不以补测为名增加全部参数变更或相邻用例的实测码率、挂测和直播断言。
## 编码组合录制与码率判定(12.14)
适用于本轮用户针对任务 `eeafe931-ae71-41ef-91a4-bfed4d4a547e` 补充的12.14“码率测试”。设备设置与直播清晰度路径是用户新确认的操作要求,不是旧 trace 已完成的事实;原适用条件、像素档判据及收尾仍保留,不串入9.133的所有分辨率切换或9.139的互斥专项。
1. **设置智能编码与实际编码格式**:目标设备“设置”加载后,在列表内继续向下查找并打开“自定义编码参数”;未见时按 [设置页查找规则](#设置页查找与到底确认) 继续,不在“画面与声音”未见就结束。读取初态,开启明确属于“智能编码”的开关并核对必要保存、回读;已开启则核验并保持,不把相邻AOV的标签/状态混记。先选择 H265、完成其高清和超清两段后再选择H264,每次确认实际选中及保存生效。切换编码或清晰度后再次核对智能编码仍开启,不能只在第一段前打开一次就假定所有视频前置相同;若状态确有变化,在本条授权范围内重新开启并复核,真实冲突则保留受影响组合及反馈,不用手动固定码率替代智能编码,也不增加 AOV、恢复出厂或默认值验证。
2. **在直播页依次选择四个组合**:按 `H265×高清 → H265×超清 → H264×高清 → H264×超清` 执行。每种编码返回同设备直播,在实际视频区域唤出清晰度入口并依次选择高清、超清,按 [短时浮层规则](transient-controls.md) 及时完成点击;不能点下方面板拖柄后仅凭未见控件结束。分别核对四组合的选中反馈、实际播放及对应码流。编码格式来自设置,采样清晰度在直播页切换;9.133在设置页读取/修改设备分辨率的路径不能代替本条清晰度操作。记录每段实际视频的 codec、宽×高及可信像素档映射,不把“超清/高清”、设备型号或一次配置读数直接等同于800w/500w/400w/300w/200w。选项不存在或切换未生效时记录该组合缺项,继续其余可做组合,不把其他组合复制计数;不要求一台设备遍历原文列举的全部五档像素。
3. **每段独立完成App内录像5分钟**:本轮明确四段均在App直播页完成,RTSP等外部码流只作测量补充,不替代业务录像。按用户右下角线索,在当前直播布局定位实际录像按钮,核实标签/图标与点击后录像计时或录制中反馈,不能以声音开关或拍照按钮替代。实际点击开始并确认后,以可靠时钟记录起点和截止时间,持续完整300秒,再实际点击停止并确认保存;单次短等待未满时继续剩余时长,未尝试等待不能写为等待受阻。切换编码、清晰度前先停止并保存本段,不能中途切档、复用一段或用等候回执代替成片。可取得素材时进一步核验媒体有效时长,短于300秒不算完整5分钟;超过300秒须按实际时长计算均值,5分钟总量另取有明确边界的连续300秒窗口,不能把整段超长总量直接比较。文件名、模型耗时、App录像图标或自动化录屏总时长均不能单独证明本段完整。缺少提取或分析能力不跳过仍可做的四段App录像,开始、计满、停止、保存及媒体核验缺项分别记录,沿本轮恢复与强制停止 App 安排收尾。
4. **采集来源与执行分工**:逐段取得App录像及其实际来源;需要外部可测码流时只补充同设备对应时段的定量分析。App录像须确认与所测设备直播码流对应、未被重新编码,不能只因存到相册就视为摄像头原始输出。ARTEMIS/scrcpy 的 Android 屏幕录像编码的是手机界面,不能用于本条摄像头码率判定;设置标称码率和直播瞬时速率也不能代替整段实测。执行端不支持提取或解析时,保留已实际完成的App录制、计时、保存结果与素材位置,由已授权宿主按实际序列号、文件访问能力完成提取及分析,不给 Flash 编造 ADB、媒体分析或文件工具,也不新增唯一 SDK/工具前置。素材尚未取得、无法解码或同源性未证实时如实报告对应测量缺项,不猜数值。该条视频与码率表是用户指定交付物,可写入本条提示词;通用屏幕录制和归档要求仍留给 Codex。
5. **逐段计算和输出平均视频码率**:用实际媒体解析能力取得该段所测视频流的有效字节数 `B_video`(排除音频与容器开销)及有效媒体时长 `T`,计算 `平均视频码率(Kbps)=B_video×8/T/1000`,说明视频流选择、时间边界及单位。多视频轨时明确实际被测轨,不能把全部轨相加当单路。文件总大小或容器整体 bit_rate 只能标为含音频/封装的文件总平均码率,未经等价性核实不能替代上述结果;仅对已确认是纯视频码流的原始素材可直接以其有效总字节计算。峰值须另用有效码流样本及有依据的统计窗口测得并说明方法,不能用均值、最大配置值或单个最大帧直接代替;原文未给统计窗口时保留方法口径待核实,不擅自新增固定窗口判据。
6. **按真实像素档与场景分别判定**:每段输出组合、智能编码状态、实际 codec/尺寸、像素档依据、场景、起止及有效秒数、视频有效字节/总文件字节的区别、实际素材路径/来源、平均Kbps、对应阈值、均值判定,以及峰值/5分钟总量的结果或具体缺项。均值阈值按下表选择,不凭 H265/H264 自创新阈值;像素映射尚未核实时仍可输出实测值,但合规判定保留未验证,不能套最近档。平均与峰值使用 `≤`,总量区间两端均严格排除。原表使用 Kbps、MBytes,保留原单位并说明换算;字节范围或 MB/MiB 口径有歧义且影响边界判定时列明待核实,不能静默换算后改阈值。
| 实际适用像素档 | 平均码率上限(Kbps) | 峰值上限(Kbps) | 静态5分钟总量(MBytes,开区间) | 动态5分钟总量(MBytes,开区间) |
| --- | --- | --- | --- | --- |
| 800w | 3072 | 5120 | (56.5, 90) | (112.5, 180) |
| 500w | 2048 | 3072 | (37.5, 56.5) | (75, 113) |
| 400w | 1536 | 2560 | (30, 45) | (60, 90) |
| 300w | 1024 | 1920 | (24, 36) | (48, 72) |
| 200w | 700 | 1024 | (13, 18) | (26, 36) |
7. **均值交付与完整用例覆盖分开**:本次明确四个组合各录5分钟并报告均值,不将“均值达标”写成整条原用例通过。原峰值要求草地等丰富画面;原给定片源可用于平均码率,使用时摄像头画面须全覆盖片源且避免反光,5分钟总量原文只是建议使用实际场景,不将某一种片源或草地升级为所有均值录制的硬前置。原静态、动态各5分钟总量断言分别保留:混合动静的一段5分钟不能同时当作完整静态与动态窗口。若本轮只具备四段某一场景素材,输出该范围均值与可判项,另一场景及未测峰值明确未验证;确按完整原范围分别采集时,按真实适用组合安排独立场景窗口,不静默扩大为所有五种像素档的全排列,也不自动改为仅人工判定均值。
8. **按实际录制数量预留总预算**:基础四段的业务录制时间已是 `4×300=1200秒`,另计设置/切换、加载、保存、提取、分析、有限恢复和收尾余量,按 [长时任务规则](#长时任务预算与实际计时) 核对实际生效的单条/批次/全局时限。四段流程可按实际估算使用本条 `{"timeout_seconds":3600}` 作为有余量示例并保留其他已确认字段;若完整静/动态覆盖需八段独立窗口,仅业务录制已2400秒,预算应重新估算,不能机械继承1800秒或认为同一示例对所有范围都充分。录制期间按可靠时钟、媒体时间与实际状态核验,不按模型平均延迟缩短300秒,不因没有新UI步骤中断有效录制;宿主提取分析另有编排时明确交接与最终合规结果由谁完成,不能在素材未分析时直接报整条通过。
## 最近七天云存卡存空洞检查
适用于用户针对 `ccd4adcb-fe82-4050-9c8a-319395d333e7` 补充的12.24检查范围:查看目标设备已有的最近7天云存、卡存录像时间轴。下面的颜色口径由本轮用户确认,不作为旧 trace 已发现空洞的证据,也不推广到其他 App。本轮又明确须在云回看使用左下日期选择最近7日,抽查实际告警视频并播放,按 [最近7日云告警补充](encoding-playback-feedback.md#1224云回看左下日期与最近7日告警抽查) 与本节双存储空洞检查分别完成,不能以时间轴或一日快放替代告警视频。
1. **先确定七天范围,查看已有历史**:依据可靠当前日期/时区及 App 日期选择器明确具体起止,不从识别冲突或未经核实的摄像头OSD年份推算。未另指定时按执行当天及前6个自然日逐日列明,当天只查至已过去的可查询时点;说明此日期口径,用户明确滚动168小时或其他边界时沿用。不能把七个日期等同完整168小时挂机,不能在本次交互里等待未来7天或用同日多次查看冒充多日。保留原7×24小时、每天10事件原文,但先执行本轮可独立完成的历史检查,不因没有新挂测或事件台账直接停止。
2. **云存、卡存分别定位与核对状态**:确认同一目标设备,进入云回看并核对实际选中类型;卡回放入口未见时沿本轮移动爱家已确认线索,在云回看后观察实际存储类型切换入口,再选择卡存。不假设固定坐标或默认类型,切换后重新确认设备、类型、日期。分别读取实际可见的存储状态、历史保留范围及录像模式/计划;不续费、开通云服务、格式化卡、清空录像或擅自改录制计划来补历史。无卡、权限/加载失败、服务到期、历史保留不足或入口未定位,分别记录实际反馈、可查范围和受阻项,不写成设备不支持或整侧无空洞;一侧受阻继续另一侧。
3. **逐日覆盖实际录制时间轴**:两种存储各检查七个日期,选择后回读已选日期,等待对应录像轨段实际加载。按时间刻度、选时指针和实际缩放/拖动能力查看该日已过去的应录制区间;先小幅操作确认方向和选中时间变化,再分段推进,保留相邻时段重叠以免漏查。有窄缺口时用实际支持的放大/缩放复核,不发明固定像素阈值或最小允许空洞时长。单屏、一天快放、“暂无告警”或日期列表出现七天不证明逐日已查;粗尺度未见白段不能保证不存在更小空洞,按实际刻度与覆盖报告。导航仍在推进时继续,真实预算、加载或交互受阻则说明已查/未查区间,不能以零空洞代填未查日期,也不要求把七天录像按原速全部播放。
4. **按用户颜色定义标记空洞**:仅对已加载的实际录像轨段判断:在应录制期间出现的纯白区段按本轮口径记为空洞;灰色或彩色轨段均为正常有录像显示,不能把灰色当空洞。视频画面灰白、页面背景、刻度线间留白、未加载占位与录像轨段颜色是不同对象。先有限等待/刷新或重新选日以排除未加载,确认区段及边界;正常颜色仍须另核播放,不能凭灰色/彩色就宣布可正常回放。对已见白段保留现象,不将“还能播放”直接抹去时间轴显示空洞;实际录像是否缺失及根因分别核实。
5. **以历史应录制范围解释白段**:结合实际连续/事件录制模式、计划、服务/卡可用范围及相关历史资料判断白段是否处于应录制时段。计划外、事件模式未触发区间、未启用录像期间、保留范围外及未来时刻不能无条件算丢录;当前计划不能独立证明七天前也按同一计划录制。历史依据不足时照常记录白段及时间轴覆盖,标记“是否应录制待核实”,不编造历史配置、事件触发或允许丢录阈值,也不让这项缺口阻断其他日期和存储检查。已确认在应录制期间的白段按用户口径报告空洞;仅凭时间轴不能确定是录像文件缺失、索引显示或其他原因。
6. **逐洞定位并核对播放**:记录存储类型、日期/时区、空洞起止、可辨时长、时间轴刻度及是否完整定位边界;跨午夜的区段分别核对相邻日并关联,不重复计数。边界超出已查范围或刻度不够精细时注明未知/约值,不虚构精确秒数。依实际选时能力查看洞前、洞内、洞后反馈,核对选中时间与实际播放时间是否对应,记录无录像、播放失败、自动跳到邻段或实际可播;只看到视频播放不能证明所选白段有数据。灰色/彩色区段的播放异常单独记录,不能改称白色空洞;不因一洞异常提前结束其他独立可查日期。
7. **分别交付逐日覆盖和空洞明细**:按“存储类型+日期”列出实际已查起止、应录制依据、轨段状态、空洞数量/明细、边界精度、回放结果和未查/受阻范围。两侧均可查时应有各七日的覆盖记录;不能用云存结果代表卡存,或因某侧历史不足把另一侧也跳过。已完整检查且具备判据的范围才写“未见空洞”,缺失日期/区间写未查或不可用,不能填0或整条通过。原事件无丢失与正常播放若要验收,仍需实际每日事件发生记录/独立台账和对应录像逐项核对;仅查时间轴或告警列表为空不证明每天10事件均留存。分别报告本轮历史空洞检查结果、原稳定性断言未验证范围,完成可做检查后沿原要求强制停止 App,不能用原长期条件缺失代替本轮检查。
## 最近十二小时云存卡存空洞检查
适用于用户针对 `fb5bb019-6307-45d6-949d-3958de56a565` 为12.28追加的最近12小时历史检查。复用 [七天空洞规则](#最近七天云存卡存空洞检查) 中的云/卡入口核对、实际轨段加载、颜色口径、历史应录制依据、边界定位及回放核验;只复用共同方法,不继承其七个日期、7×24小时或每日10事件要求。此前12.24七天范围继续保留,不被本条替换。本轮新增卡回看、云回看均使用页面下角放大镜将时间轴调到12H,按 [12H缩放补充](encoding-playback-feedback.md#1228卡回看和云回看分别调到12h刻度) 核对两侧实际显示;12H显示尺度与本节滚动检查窗口分别满足,见到标签不等于查完12小时。
1. **固定同一个滚动12小时窗口**:以开始本次历史核验时的可靠当前时间为 `T_end`,记录时区及精确起止 `T_start=T_end−12小时`。云存、卡存共用此窗口,不在切换存储后重新取当前时间使窗口漂移,也不把最近12小时改成当天00:00起、12个整点或七个日期。窗口末端尚未生成/上传或索引延迟时有限刷新后记录未查/待确认近端区间,不能把结束时点悄悄向前移动、缩短总窗口或以0空洞代填。OSD或设备/App时钟未核实时按可靠时钟与实际日期/时区关系注明歧义,不从异常年份推算。
2. **两种存储分别覆盖,跨日分别选日**:从同设备云回看、卡回放进入实际录像时间轴,切换后确认类型、已选日期和加载结果;“设置→存储卡”显示容量、已安装或格式化按钮只证明管理页所见状态,不能替代卡回放。按窗口与各日期的交集逐段拖动/缩放检查;跨午夜时两日均须实际选中并回读,先核对拖动方向与选中时刻,保留相邻视段衔接,检查窗口起止附近。不能只查看今天、一屏或一天快放后就宣布完整12小时。存储不可用、权限/加载异常或历史不足时保留原请求窗口、实际已查与未查范围,一侧受阻继续另一侧,不推断设备不支持。
3. **沿用户颜色口径逐洞记录**:应录制期间已加载的时间轴录像轨段纯白为空洞,灰色/彩色为正常有录像显示;与视频画面灰白、页面背景、刻度留白或未加载占位分别处理。结合实际录像模式/计划及历史依据核对是否应录制,不将计划外、事件未触发、保留范围外或未来时段无条件算丢录;历史依据不足仍记录白段,注明是否应录制待核实,不停止其余可做检查。逐洞记录存储、日期/时区、起止/可辨时长、刻度精度和洞前/洞内/洞后反馈,核对回放没有自动跳到其他有录像时刻;跨午夜同一缺段关联去重。边界不清时保留约值或未知,不编精确秒数。灰色/彩色仅证明正常轨段显示,播放结果另判;白段实际可播时保留时间轴空洞现象,数据是否缺失另核。
4. **分别输出覆盖与原稳定性结论**:每侧报告相同请求窗口、按日期分段的已查/未查区间、应录制依据、空洞明细、回放异常及实际精度,只有已完整检查且具备判据的范围可写未见空洞。无卡、云服务不可用、历史不足或粗尺度未查部分不填0,也不用云存代替卡存。无需等未来12小时或完整原速播放12小时;检查已有记录时缺少新负载台账不能阻断回看,也不能通过现在改灵敏度、码率或计划来证明过去的负载。原12.28的动态高码率直存、SD卡、实际适用CPU/内存负载、连续至少12小时及无重启/掉线仍独立保留;例举功能不等于设备全部支持。无白洞不能证明当时满足负载或从未重启/掉线,有白洞也不能直接归因重启/掉线。需要原稳定性通过时须另有对应负载、真实连续时长及可靠重启/掉线证据,不以历史回看替代;本次不因此自动新建12小时挂测。完成本轮独立可做项后沿原要求强制停止App,未完成/受阻范围如实报告。
## 第16部分云化AI算法范围与入口
本节按条目区分最新用户范围。**16.1“人脸抓拍逻辑”优先执行以下2026-10-21端侧例外,不应用其后通用云化算法查找与替代限制**;其余原第16部分云化AI用例继续沿用原口径。用户针对 `3664b280-49da-43eb-9bbb-4bcd77afe859` 确认只有目标设备“设置 → AI设置”内算法才计入云化AI,主页面“功能设置”中的人脸侦测属于端侧算法;该架构区分仍有效,16.1最新要求改为核查该端侧算法。本节仅用于本轮移动爱家与目标设备,不据名称推定其他App或设备架构。
**16.1主页面人脸侦测与端侧支持例外**(对应历史 `e00a853e-8460-4e7e-bf0e-bf4bc3a943bb`):
- 先确认同一目标设备并进入设置主页面,等待内容实际加载。当前可见“人脸侦测”时即可判断入口存在,无需为支持判定继续遍历;未见时按主列表实际进展完成必要滚动、加载与末尾核实。只查该主列表,不进入“AI设置”“AI周界”或其他算法子页补找替代入口,不沿用“主列表未见还须查相关子页”的通用分支。
- 主页面有“人脸侦测”入口,即按用户口径认定支持**本条端侧算法**;灰色、关闭或带“云存储专属”仍属入口存在,分别记录实际启用、可用/服务状态,不把它们当作入口不存在。支持不等于已开启、服务已开通、抓拍逻辑或效果通过。支持后进入已找到的人脸侦测页,只查看本条原要求的逻辑条件/模式,不新增开关、参数修改或付费开通;入口不可点或页面受限时保留查看受阻,不反改支持判断。
- 主列表已加载且必要查找确已完整,仍无“人脸侦测”时,按用户指定口径记录“设备不支持端侧算法,需人工判断”,同时保留已查范围,不再进入AI设置查云化人脸或改测AI周界。未加载、未查完、权限/滚动受阻或预算中断时,记录“主页面入口未确认”及具体覆盖缺口,不能写“没有”或套用不支持表述。
- 支持后继续16.1原“人脸抓拍逻辑”的查看动作、真实前置与判据,入口确认不能代替结果观察;无真实场景、人脸图片或效果依据时分别保留未验证,不新增16.2等其他用例的云化准确率、误抓率或指标。此例外不改变16.10云化人脸启用、16.75车辆负向、16.124电瓶车四项支持,也不改变9.122查AI设置/AI周界关闭全部实际侦测的范围。
- 历史第6、7、15步已显示主页面“人脸侦测”,旧提示要求云化并排除端侧后又进入AI设置。该事实用于解释旧范围与最新用户要求的差异;保留旧运行原始状态和业务判定,不倒填为16.1抓拍逻辑已执行或通过,不修改历史证据。本skill只修订后续规则,不启动设备或重跑任务。
**以下1—7项仅适用于其余云化AI用例,不覆盖上述16.1例外。**
1. **先核对本条云化算法身份与入口**:确认同一目标设备,进入设备设置并打开实际“AI设置”,等待算法列表加载,再核对与本条原用例对应的算法、可见状态和参数;不是看到任意AI服务就认定本条算法存在。主设置页的人脸侦测、同名开关、历史告警或“云存储专属”标签均不能证明云化AI支持、已开启或已产生云化结果,也不能用于替代本条操作与效果证据。不能为凑齐云化测试去开启端侧人脸侦测。
2. **未找到时区分覆盖与支持情况**:设置主列表首屏未见“AI设置”,或进入后首屏未见目标算法时,按 [设置页查找与到底确认](#设置页查找与到底确认) 每次滚动后重新观察,有新条目就继续,确认实际查找范围与可靠末尾,不设固定滑动次数或复用旧坐标。区分“AI设置入口未定位”“AI设置内未见本条算法”与“页面未加载/权限或滚动受阻”,分别记录已查和未查范围。合理查找后未见入口或算法,可说明设备可能不支持云化AI或该目标云化算法,需人工核对;仅缺某一算法不扩大为全部云化算法均不支持,加载受阻也不算已查无此功能。保留本条云化分支未验证及具体原因,不判算法效果失败、通过或自动改为端侧测试,也不凭此自动改写原表适用性。
3. **找到后按本条动作和判据继续**:先读实际状态,原步骤或用户已明确补充要求开启、改参数时,按本条范围操作并核对必要保存/回读;只要求查看且无新增开启要求时不增设开关动作。是否存在、是否启用与准确率/抓拍/告警等效果分别验收;效果必须来自该云化算法及对应真实场景、样本和可核验结果,不能拿端侧结果代替。入口存在不等于已开通、启用或达标;服务或权限限制如实记录,不为补测新增付费开通。缺少效果样本不阻断独立可做的AI设置入口和算法核对。
4. **保留各条原有适用性与指标**:本条入口确认不消除室内/室外、架设、真实标注样本及效果判据的原缺口;16.2中的准确率、误抓率、重复抓拍等指标仍按其原文与已确认适用口径核验,不把室外专用焦距/架设条件直接套给室内云台机,也不向其他16.*用例追加16.2全部指标。云化目标尚未确认时,不用端侧开关状态或未获得统计值推断云化算法优劣。完成可做项或受阻退出后沿原约定收尾,新路径不倒填旧运行已完成。
5. **16.10先确认并开启人脸-AI**:用户针对 `2bca40cb-1d1e-4644-a349-169e6e97dda8` 补充确认开启步骤。先进入同设备“设置 → AI设置”,定位用户所指“人脸-AI”对应的实际云化人脸算法,记录实际标签、初态及可用状态,不预设更深子页名称。关闭时开启并完成页面实际要求的保存,依据状态变化及重新进入/可用回读确认仍已启用;已开启则保持并核验,不为凑动作先关闭再开。入口点击、操作已发出或一次成功提示不能单独证明人脸AI启用,“翻转视频源”启用更不是该算法开关。未找到、启用失败或权限/服务受限时按上条查找与支持规则记录,继续独立可做的原操作,不能换开端侧人脸侦测。原真实设备倒装、设置旋转视频源、查看人脸上报图及云存储的要求保留:在本次云化人脸已启用且翻转仍生效的真实倒装条件下,按实际反馈取得可归属于该设备、该算法及该有效时段的人脸上报图片和对应云录像,核对方向后再按既定安排恢复。启用前的旧录像、声响告警图、无可辨人脸或方向的灰白画面均不能证明本条效果;不靠固定等待或任意告警补证。缺少真实倒装/新素材时分开保留条件和效果缺项,不让这些缺口替代已获准的人脸AI查找、开启和回读,也不把设置成功写成整条通过。观察结束后保留原翻转恢复及强制停止App收尾,人脸AI初态、末态及本轮既定保留/恢复安排分别记录;本补充不扩为其他16.*用例一律开启或追加16.2准确率指标。
6. **AI车辆识别与AI周界分别处理(含16.75)**:用户针对 `31ac0cd2-2193-42f2-98dd-c26bb01e9796` 明确本条AI识别不是AI周界中的功能。沿本轮已确认的“同设备设置 → AI设置”查找实际对应的AI车辆识别算法及开关,记录实际名称和状态,不预设更深页面或坐标;“AI周界”内的汽车目标、区域入侵、越界侦测,以及普通移动侦测开关/区域/灵敏度,均不是该目标算法,不将它们继续列为本轮车辆算法候选,也不能借其开启、区域配置或成功反馈证明目标支持与生效。设备设置首屏未见时继续核对列表及AI设置实际内容,按前述滚动、加载和末尾规则记录查找范围;充分查找仍未见对应开关,可说明设备可能不支持该AI车辆识别算法、需人工核对,受阻/未查范围另列,不直接断言不支持、判效果失败或改测AI周界。找到后按本条原动作核查状态与效果,不自动追加开启或修改无关设置。16.75保留原“设备具有前端AI检测能力”等条件及真实区外车辆负向要求,不据“前端AI”字样将周界功能等同目标,也不臆断实际运算架构;目标能力、启用情况、检测区域和车辆实际区外位置均须有对应有效依据,未经共享映射确证的普通移动侦测全画面默认框不能当作目标算法检测区域。原允许的移动侦测区域复用须先确认目标车辆算法身份,并有可信依据证实同一配置实际作用于该算法及其映射范围;这仅覆盖区域参数证据,不证明目标算法支持、已启用或效果,也不能重新将AI周界等同目标算法。没有真实车辆经过或对应记录核对时,空画面、空事件列表不证明“不识别、不记录”通过;不新增区内识别成功前提、全档遍历或其他算法准确率指标。算法身份/开关未确认与缺少真实刺激分别报告,完成可做核查后沿原安排收尾。
7. **AI电瓶车识别与四项设置支持(16.124)**:用户针对 `4e1d97db-c0f5-4772-86f6-2536148e7de6` 明确本条AI电瓶车识别不是AI周界中的功能。先在同设备设置中沿已确认的“AI设置”查找实际对应的电瓶车算法及开关,记录实际标签、支持来源及可用状态;AI周界中的非机动车/电瓶车目标、区域入侵或越界侦测均不作为本条算法候选,普通移动侦测、端侧功能或通用“车辆识别”条目也不自动证明目标电瓶车算法支持。未见时按前述设置列表和AI列表的实际进展继续查找、核对加载及末尾,合理查找后仍未找到对应开关,可说明设备可能不支持该AI电瓶车算法、需人工核对;查找未完、加载/权限受阻分别记录,不断言不支持。通用车辆识别显示“去开通”仅覆盖该条目的服务状态,不证明电瓶车算法存在、缺失或必须付费,不为补查新增付费开通。确认目标算法身份后,按原步骤仅查看并分别核实四项支持:电瓶车检测区域画框、侦测功能开关、侦测灵敏度设置、侦测区域设置,保留原四项而不因画框与区域可共用就合并漏项。可明确映射到本算法的界面、能力或有效配置作为支持依据;确证共享配置的同一控件可支撑多项,不要求四个专属入口,也不能仅凭通用移动侦测有这些控件就判支持。某项缺依据时列该项未验证,继续其他可独立核对项;当前开关关闭不等于不支持。原仅要求查看,不新增开启、修改灵敏度/区域、保存遍历、真实电瓶车触发或准确率测试,也不移入16.75区外负向条件或另造非电梯场景分支。完成可做核查后沿原约定收尾,不把未定位算法改判为四项功能失败或通过。
## 设备自动维护最近日期检查(9.31)
本节依据用户针对 `2e1d02c2-6134-416e-b45a-92563b85f3c1` 新确认的路径与判据,仅用于本轮9.31“设备支持按需上传日志”(分类为设备自动维护)。本次执行范围改为读取维护日期的显示情况;原预置、步骤与预期保留为来源,原开启日志、设置维护时间、真实维护周期及设备重启/日志上传等不再作为本次只读检查的自动门槛,不推广到其他维护或日志用例。
1. **先点顶部设备图标,后找型号**:进入目标设备设置并等待加载;已经向下滚动时先回到实际最顶部,重新定位设备外观图标并实际点击,观察是否进入设备信息/详情页。图标节点无文字或自身clickable=false时,核其可点击父容器和当前图标所在的实际点击区域,不因此跳过;避开相邻的昵称编辑入口。型号字段应在信息页查找,不因设置主列表当前无型号就连续下滑或结束。昵称、位置、共享状态、固件版本及主列表“开发者选项(正式版本不会存在此功能)”分组均不能代替真实设备型号或已开启开发者模式的证据。顶部图标未打开时有限纠正并记录反馈,不直接宣称字段不存在。
2. **对真实型号连续五击**:读取目标设备实际“设备型号”标签及对应值,按当前布局定位该字段的可点击区域;使用执行端实际支持的一次连续5次点击(例如click的times=5)或等价短序列,避免拆成五轮模型问答。不点昵称、设备图标、固件值或主列表分组冒充五击,不固化坐标或编造点击间隔阈值。动作后核实开发者模式提示/新入口,按实际反馈进入;手势回执本身不证明模式已出现。无反馈时核命中位置与当前页面并有限纠正,不无限重复。
3. **进入配置只读日期**:在开发者模式实际开放后,定位并进入“设备自动维护配置”,等待数据加载,读取“最近维护日期”的实际标签、原始显示值及设备身份。页面可滚动且目标尚未出现时继续在该配置页查找;未定位字段与已读字段为空分别报告,不回到无关设置反复泛查。用户确认路径用于新稿,不能倒填历史执行已走通。
4. **按日期是否显示判断**:字段加载完成且显示可辨日期值时,本次日期显示检查满足;不要求日期等于今天、不新增维护间隔、新鲜度或固定格式校验。字段明确空白、占位符、暂无/未维护等无日期状态时,保留原显示并记录本次未显示日期,不擅自执行维护来补值。无法进入、字段未定位、持续加载、报错或文字不可读时为受阻/未验证,不能据此断言设备没有维护日期。含义不清的值照录并保留待核,不从其他日期、日志时间或系统时间反推。
5. **本次完成与原机制分开**:不修改维护开关、开始时刻、周期、日志获取,不触发立即维护/重启,不改时钟、断网或解除绑定,也不等待跨日/数日来凑原判据。默认开启、1:00:00–3:59:59、1–7天/默认7天、重启与日志上传、语音/云台行为、离线未绑定仍执行等原预期只作来源与未验证范围。日期存在不证明这些机制通过;本次路径和读取已完成或确有具体受阻且有限纠正无效后,沿原强停App及执行方进程核对安排收尾。单条Flash及既有配置保持,不为只读日期增加长时预算或录屏要求。
## 用户确认路径与人工判定采集
以下路径由本轮用户针对移动爱家 V99.0.0、长虹SC52034明确确认;按该版本与目标范围使用,不推广到其他 App 或未经确认的布局。用户确认与历史 trace 已走通分别记录。执行时仍以当前页面和实际选中反馈核对,路径不匹配时有限重新观察,不猜坐标或将近义选项自动等同。
- **设备信息入口与型号(9.2)**:用户确认设备信息需在目标设备“设置”页最上方点击设备图标查看。先等待设置内容加载,若已滚动则回到顶部并重新定位设备图标(设备外观图),点击后观察打开的实际页面及字段,读取本条所需的型号原值;不预设新页面的准确名称、字段全集或坐标,也不在主列表中反复寻找字面完全一致的“设备设置”行。9.2将实际型号与本条已取得的独立厂商型号“长虹SC52034”按既有逐字规则比较,品牌前缀等差异如实保留,比较口径未明确调整前不擅自去品牌、归一化或改预期;设备昵称、卡片标题、外观和固件版本均不能替代型号字段。入口未打开或未见目标字段时,有限重新观察并记录入口结果、已查范围与型号未验证;用户确认路径不等于历史 trace 已读取型号或验证通过。
- **设备日志管理与人工核对(9.69)**:按用户确认的“目标设备设置页 → 最上方设备图标 → 详情 → 设备日志管理”进入,等待实际内容加载;不以设置主列表泛查或“设备检测/网络检测”代替日志入口。先记录初始档位及日志获取状态,存在日志获取开关时按本条要求开启并确认。依据实际可用控件完成两次相对前态的档位变化,每次均核对保存/回读并取得该次对应的完整日志获取地址,记录第1/2次、可读档位标签或位置、设备及变更/取址时点。无变化的点击不算调整;不追加全部档位遍历,不把刻度序位推成内部等级,未知映射留给人工,不阻断独立采集。通过实际可用的复制、分享或下载入口提取完整地址,不能用省略地址的截图冒充已取得完整链接;若两次显示同一地址,保留各次关联,不能虚构两个不同链接。地址可下载且宿主具备实际能力时,及时按各次关联保存对应文件;地址取得与文件下载分别记录,下载失败或无法区分对应内容时注明缺项,不宣称已留存日志。两次采集结束后按上述诊断设置恢复规则处理,不在取得对应资料前恢复,不用单独收尾恢复冒充漏做的测试调整。本轮日志正文等级与 App 档位是否一致交人工核对,保留原判据和待判状态;自动只报告操作及资料采集范围,不因两次调档、链接或下载成功判业务通过。
- **功能设置检查后查询固件升级(9.21)**:按原七组范围读取本次升级前设置,完成可读取项后,回到同设备“设置”并查找、打开实际“固件升级”入口,等待页面加载及版本检查结果;页面有实际检查操作时按界面查询,不只读主列表的当前版本号或有无提示标记。未读设置列为覆盖缺口,不一律视为用户未提供;缺口不阻止独立查询版本,也不能据此声称基线完整。分别记录当前固件、可升级版本(若有)及查询结果:有新版本时记录目标版本及页面反馈,按本条已有 OTA 执行安排推进真实升级,并在完成、设备恢复后回读原设置逐项比较;本条查询不扩展为部署外部包、刷机或修改 OTA 服务。明确显示已是最新或暂无新版本时,保留已完成基线与查询结果,说明当前无可用升级、OTA 及升级后保持未验证,不循环查找或以 App 重启代替升级。页面未加载、检查报错或入口未定位时记录版本检查受阻及范围,不写成已是最新或没有升级包。升级前未读项目与升级后未比较项目分别保留;只有版本检查完成不能判原七组设置升级前后保持一致,发现新版本也不等于已升级完成。
- **固件包版本字段与格式(13.13)**:用户针对abeb226f明确确认,本条“固件包版本”就是目标设备“设置 → 固件升级”页面的设备版本;页面采用“当前版本”标签也为有效取值,不再以缺少字面“固件包版本”字段拒绝判定。进入实际升级页并等待内容加载,记录目标设备、页面标签及版本原串;同页有当前/可升级两个版本时分别标明,本条按已确认的当前版本取值,不混用App安装版本、设备昵称或其他设备版本。保留原预期“3段式、纯数字、每段最多5位”:只对版本值核对两个点分隔的三个非空数字段,每段1至5位;`1.0.01`为1/1/2位,符合要求,原未禁止前导零,不转整数抹去`01`或据此判失败。本条仅查看并校验格式,环境元数据中的预期固件版本不自动增加相等断言,也不要求外部包下载/解包、OTA升级或升级后设置比较;“当前已是最新版本”不是格式判据,有无新版本不阻断读取当前版本。实际版本未加载或不可读时记录受阻,仍按原约定收尾。此字段映射限本轮已确认的页面和13.13范围,不推广为其他信息字段可任意近义替代。
- **自定义编码参数与码率(9.101)**:按用户确认的“目标设备设置页 → 下方‘自定义编码参数’”进入,等待实际内容加载;首屏未见时在设置列表继续向上滑动查看更下方,每次滑动后重新观察,不把“固件升级/设备检测/流量播放提醒”所在一屏当作已查到底,也不以“画面与声音”“AI设置”或直播 HD/清晰度切换代替码率入口。按实际页面确认码率字段、作用码流/通道(如有)、单位及合法范围,记录初值;具体目标优先沿用原文或用户最新确认值;本轮9.101已明确VBR平均2000/峰值2300、CBR平均2000,并要求每模式直播标清/高清/超清各录30秒,按 [六段录像补充](encoding-playback-feedback.md#9101两种码率模式与六段30秒录像) 编写,不再任选其他值替代。仅在确实未指定数值的其他任务中,可选择该页实际支持且不同于当前的有效值;不能把本条数值、单位或档位固定到所有设备。修改后核对实际输入/选中值、必要保存反馈及再次进入后的回读,避免同时修改无关编码参数。原判据要求设置码率后智能编码自动关闭,须分别记录修改前后的可观察状态或对应有效证据;智能编码入口沿本轮用户确认的“自定义编码参数”查找,具体控件及状态以实际界面为准,不能手动关闭智能编码后声称自动互斥成功;修改前已关闭、修改后仍关闭也不证明发生自动关闭,互斥转变保留未验证。智能编码状态无法取得时保留互斥断言未验证,继续可独立完成的码率设置及回读,单独设置成功不等于整条通过。厂复后默认开启仍作为独立分支保留,不用当前开启、手动重开或恢复码率代替,也不为补证擅自恢复出厂;原文1.48/1.49旧版注记不证明当前 App 的支持情况。完成修改状态下的观察后沿用本轮恢复/收尾安排并记录末态。入口未定位时保留已查/未查范围,不从错查候选页推断设备无此能力。
- **侦测关闭、AOV 与退出后直播检查(9.122)**:本轮最新顺序按 [全部侦测关闭与60秒退出](encoding-playback-feedback.md#9122全部实际侦测关闭aov与退出后返回):先核对设备实际存在的全部侦测/跟踪与相关AI功能,进入实际子页关闭并保存回读,不能只处理两项或漏掉AI周界、AI设置内相关算法;再到设备设置下方“自定义编码参数”开启AOV,回读后退出App,按实际退出时点计满60秒,再进入同设备直播检查真实出图、播放推进及异常。支持能力不证明开启、设置成功不证明播放正常,设置页等待不能抵扣退出等待,退出状态实际持续时间按可靠时钟核对,不凭模型回合耗时推定。原仍保留的码率修改若需执行,在侦测关闭后、AOV开启前完成,不把历史“任选有效码率”当作本轮必需新增动作;AOV造成参数恢复时记录实际末态,不反复改码率干扰AOV。原真实动/静场景、不启动直播挂测以及本次生成流“正常播放、无快播”仍另列独立范围,60秒退出不代验全部挂测,缺长期条件也不阻断本轮可做的明确链路。直播结果观察后沿既定恢复与App收尾安排执行;不继承9.101的互斥或厂复默认,也不向9.101追加AOV。
- **自定义编码参数中的分辨率(9.133)**:用户确认本条先在“目标设备设置页 → 下方‘自定义编码参数’”查看分辨率,替代原步骤中未适配本轮界面的“画面设置”入口。等待设置内容加载,向下查找并在每次滑动后重新观察,打开实际子页再读取分辨率字段、当前选中值及可选项;有清晰度或码流分组时,分别关联实际所属分组,同一清晰度可对应多个分辨率,不能从“标清/高清/超高清”、直播 HD 标签、画面观感或码率数值反推具体像素尺寸。只查看时保持原值,不因进入编码页而顺带修改码率或开启 AOV。原 9.133 仍要求切换分辨率并查看直播效果:按设备实际支持项完成原要求的切换,每次核对选择、必要保存及重新进入回读,并在该次分辨率仍生效时检查同设备直播,再进行下一次切换或恢复。原列举的数值、分类及切换示例保留为来源要求,不能全部当作本设备已上报能力,不强加全档或清晰度与分辨率的全部组合遍历,不修补原文孤立的“c”为猜测数值;不支持的示例与尚未验证的分支分别说明。原文默认 2304*1296 按原适用范围核对,当前选中或手动改回不证明出厂默认,也不为补证新增恢复出厂。配置回读与原直播“播放流畅、无花屏/卡顿/跳帧/绿屏、音视频同步、声音无失真”分别核验,不能由保存成功推出实际视频流分辨率或音画通过;需要人工听音、音画质量判断的部分按本轮安排保留待判,未获得有效观察的部分保持未验证,不指定唯一测流工具。入口未定位或字段未加载时记录已查/未查范围,不从候选页未见控件推断设备不支持。完成观察后沿用本轮恢复及 App 收尾安排,本条不继承 9.101 的智能编码互斥或 9.122 的 AOV 挂测要求。
- **自定义编码参数中的智能编码开关(9.137;12.29共用入口与实测边界)**:按用户确认的“目标设备设置页 → 下方‘自定义编码参数’”进入,替代原“画面设置”候选路径;等待内容加载,向下查找时每次滑动后重新观察,不以“AI设置”的智能服务或“画面与声音”未见目标推断不支持。定位实际“智能编码”开关,原文已明确它与静态码率压降开关为同一功能;界面采用其他名称时仍核实对应关系,不从近名、能力布尔值或静态配置文本推断可操作开关。先记录原始状态,按原步骤先开启并核对保存/回读,再关闭并核对保存/回读;初始已开启时保持并核验开启态,不为凑次数先关闭,两态观察完成不等于必须发生两次状态变化。在每个被测状态仍生效时,先实际进入同设备直播检查持续出图及花屏、黑屏、绿屏、卡顿等异常,再按实际可用的观察能力采集有效静态场景的实际输出码率,关联设备、码流、分辨率、时段及开关状态,在可比条件下核验“开启识别并降低静态输出码率、关闭后正常输出”的原预期,再进入下一态或恢复。设置页标称/目标码率、输入的1000/1250等数值、开关颜色或一次保存成功均不能当作实测输出及压降效果;不虚构压降比例、正常码率阈值、等待秒数或不存在的测量工具。缺少有效静态场景、实测来源或足够判据时保留具体效果未验证,继续可独立完成的开关操作和两态各自直播观察;开关、直播和真实压降分别验收,不把设置成功或画面正常写成整条通过。具体按 [每态直播补充](encoding-playback-feedback.md#91371229123每个规定智能编码状态都看直播)。原“默认开启”须有可信默认起点或对应资料,当前开启及手动重开不证明默认;缺少默认依据不阻断两态操作,不因补证新增恢复出厂。保留原备注引用的12.29–12.32智能编码要求,取得对应原文时据其补足判据,不编造其内容或自动扩为执行全部关联用例。本条不要求顺带修改固定码率、分辨率、开启 AOV 或验证9.101的自动互斥;手动码率变更可能干扰开关状态及效果,不能用它代替本条开关操作。完成两态结果观察后沿用本轮设置恢复及 App 收尾安排,入口未定位时保留已查/未查范围及未完成断言。
- **静态码率压降的本条断言(12.29)**:用户再次确认智能编码开关位于设备设置页下方“自定义编码参数”;按上条共享的定位、保存回读、真实静态场景两态输出、默认依据及恢复边界编写,开启和关闭顺序来自12.29本条原步骤。另保留原能力集及配置项上报/支持核查:通过执行端实际可用的页面或诊断来源读取对应值及来源,不将开关可见、当前开启或能力布尔值等同于已核完上报、可信默认或压降效果;缺上报/默认证据时分别保留未验证,继续可独立完成的开关操作。开启时先进入直播观察画面并采实际静态输出以核验识别和压降,关闭后在仍关闭时再次进入直播观察花屏等异常并核验正常输出,再按既定安排恢复;两态直播不能合并成末态一次,按 [每态直播补充](encoding-playback-feedback.md#91371229123每个规定智能编码状态都看直播) 分别记录;可比场景、码流和分辨率应关联,不用标称码率或纯屏幕录像代替实测,不编造压降比例、正常输出阈值或固定采样时长。本条不要求12.14的H265/H264及超清/高清四组合和每段5分钟,也不引入AOV、固定码率修改、9.101自动互斥或恢复出厂。原9.137对12.29–12.32的备注仍保留;本轮仅已核实12.29原文,不据此代填其余关联用例内容或自动扩为执行全部关联项。
- **静态码率压降效果的本条范围(历史编号12.3)**:任务1725bc2a的原预置要求设备端能力集上报支持,原动作仅打开静态码率压降开关。沿用户已确认的“设备设置页下方→自定义编码参数”找到智能编码,本轮已确证它就是原静态码率压降开关,不需要另找字面同名的独立控件;读取本条支持依据与当前状态,开启并核对必要保存/回读;已回读开启则核验并保持、继续直播观察,不把额外同名控件未见判为开启缺失,也不借前一任务状态作为本条证据。随后保持开启,实际进入同设备直播检查持续出图及花屏等异常,按 [开启态直播补充](encoding-playback-feedback.md#91371229123每个规定智能编码状态都看直播) 记录;再在该开启状态及真实静态场景下,采集同设备/码流/分辨率的实际输出,按已取得的对应效果规范与有效参照判断压降,标称码率、当前on或灰白直播单帧均不证明效果。原分类提示参考《静态场景码率压降要求》,未取得相关标准或测量依据时分别记录实测与判据缺项,不虚构比例、阈值、固定采样秒数、5分钟或必须由指定人员提供的条件。保持本条开启和效果范围,不自动添加12.29的关闭/默认断言、12.14的H265/H264四组合,或为造参照先修改固定码率/开启AOV;确需额外对照测量时应说明其方法依据及授权范围。缺效果依据不阻断可独立完成的开启和回读,操作完成不等于业务通过;按本轮既定恢复及强制停止App安排收尾。
- **自定义编码参数中的 AOV 开关(9.138)**:按用户确认的“目标设备设置页 → 下方‘自定义编码参数’”进入,未见时遵循上述持续查找与末尾确认规则,不把“固件升级/设备检测/网络切换”一屏当作已查到底。读取实际 AOV 初始状态,按原顺序确认开启、核对必要保存并重新进入回读,再关闭并核对保存/回读;初始已开启时先核验开启态,不为凑次数先关闭,点击回执或支持字段不能替代启用状态。原默认分支“常电设备默认关闭、低功耗设备默认开启,以设备上报为准”继续保留:使用独立且可信的供电类型资料与默认起点/设备上报依据,不从室内云台类型、当前开关或 `support_aov` 反推供电类别及默认值。缺少供电类型或可信默认证据时仅保留该断言未验证,继续可独立完成的开关操作,不为补证新增恢复出厂。原前置条件及备注引用9.114–9.130保留,不自行改写能力条件、编造关联用例内容或扩为执行全部关联项;本条不继承9.122的码率修改、动静场景挂测或直播检查。开关生效与 AOV 后续录制/播放效果分别验收,完成本条结果观察后沿用本轮设置恢复及 App 收尾安排。
- **缩略图模式**:先进入 App“设备”页(设备列表),点击右上角“+”左侧的摄像机小图标,进入实际展示缩略图的页面。3.2按 [顶部下拉与返回更新](#缩略图顶部下拉与直播返回更新32) 先确认列表最顶端,再独立下拉核实刷新;找目标后从中部向下滚一次不能代替。手动刷新结果取得后再做进入直播、退出回列表比较,两条来源分开。默认设备产品图卡片不能直接当作该缩略图;以模式切换反馈确认目标区域,不能因旧记录里产品图不变就判缩略图缺陷。
- **直播控件**:用户确认的轻点直播画面唤出入口与后续运行中确实出现的浮层分别记录。进入直播并确认实际视频边界后,在中心或邻近无按钮区域唤出;入口与当前布局已确认时,按 [短时浮层与连续操作](transient-controls.md) 及时连续点击目标,浮层已隐藏则有限重新唤出,避免每次都等待完整观测后才决定下一点。避开录像按钮、下方控制条、拖柄和云台面板,不固定屏幕坐标;布局变化后重新识别。此分支不要求无关用例切换分辨率。6.5按 [直播逐档五分钟与对应云录像](#直播逐档五分钟与对应云录像65) 逐档真实播放后再查对应录像;内容模糊与播放故障分开判断,不把仅适用于云台/场景比较的可辨参照要求变成清晰度菜单的硬前置。
- **夜视取图与能力分支**:本轮以夜视画面采集、人工判断为交付目标时,先按“设备设置 → 画面与声音 → 夜视”核对当前选项和选中状态;实际存在“智能夜视”时按已确认安排选择并回读。目标已确认为室内云台机、当前夜视页未见“智能夜视”且已核实该页“自动”选项时,按用户本轮补充保留当前已选的“自动”,或选择该“自动”并回读,再返回同设备直播取得本条要求的画面,不仅因缺少智能选项终止独立可做的取图。**不得为继续取图将夜视设为“关”,也不得开启全彩/全彩夜视模式作为替代**;用户没有要求一律强制选“开”。记录实际选项与选中状态,使用“自动”时不能写成已开启“智能夜视”,也不把某次页面未见该项推广为该设备或所有室内云台机均不支持。若可用分支仍无法核实,保留具体原因与未执行范围。3.8还需记录直播画面的日期、时间及时区,进入同设备云回看,在对应过去录像时点播放稳定后再留存一张;3.14仅要求本轮直播截图。两条业务画质/距离结论交人工判断;自动操作和截图采集完成不等于原业务通过。不得为取图擅自遮挡实物、制造夜间场景;“自动”或“智能夜视”配置均不证明红外已触发、画面应为黑白或夜视距离达标。原判据保留供人工使用,缺少现场/测距影响人工结论,不阻止独立可做的配置及取图。若原用例专项检验智能夜视切换、补光或联动等必须由智能模式建立的条件,保留该断言与未验证范围,不能用“自动”判其满足。
- **云回看选时**:先确认云回看类型、设备、日期选中状态、时间轴刻度及过去有数据区。拖动前识别时间轴可拖动区域和选时指针;先小幅试拖并回读选中时间的变化,确认实际方向,再小步对准目标时间,不预设向左/向右、像素距离或固定屏幕坐标。松手后等待实际播放恢复,核对播放日期/OSD或可读播放时间与目标对应。不能只凭刻度移动或有颜色就认定选时/播放成功。取对应直播时刻时不拿其他时段替代;新录像尚未生成、目标无数据或仍不可播放时保留已取得的直播图并注明云图待补,不选择未来时刻或无限等待。
- **直播出图与云台**:云台或依赖画面比较的用例,先确认直播已出图且有可辨参照;若画面灰白、模糊或疑似遮挡,可在已授权的云台操作范围内有限调整方向,再重新观察,不能声称已移除实体遮挡。用户本轮要求的方向验证按上、下、左、右各长按5秒,使用实际支持的持续按压能力并核对持续时间及按钮命中;能力不支持时如实记录,点击不冒充5秒长按。每方向松手后待画面稳定再比较固定特征,必要点击只能按实际能力记录。普通方向响应与机械限位分别判断;时间戳推进或画面不再变化不单独证明云台已到限位。4.3限位用例保留原连续控制至限位的范围,按每次5秒控制、观察后再决定是否继续,不把一次5秒长按当作到位保证。
- **云台操作录屏与人工判定(4.8)**:按用户确认的上、下、左、右各真实长按5秒,保留动作前、按压过程和松手后的模拟器画面供人工判断。仅本条通过受支持的`任务配置(JSON)`开启`record_video: true`;本轮人工判定可显式关闭`analyze_video`,不改变全局默认。录制的是运行App的Android模拟器屏幕,不以App内录像按钮代替,也不承诺录下宿主桌面或窗口外框。宿主Codex执行方负责在派发前核对录屏能力与单条配置,并核实本任务实际录制启动反馈;不向Flash下发不存在的录屏控制/状态查询动作。宿主在结束后取得本运行真实录屏片段及路径,关联运行ID、方向、时间,缺片段或录像未取得时保留采集缺项。自动操作/录屏完成与人工业务判断分开,不自动判云台或OTA保持通过。4.8原文的真实OTA前后预置位/机位保持范围继续保留;可读基线本次实读,有真实已授权适配升级时才执行,显示已最新或无可用升级时保留已做的方向录屏与OTA未验证范围,不以App重启替代,也不扩大刷机权限。
- **预置位新增与召回**:4.5应直达直播云台/实际“我的机位”入口,不夹入无关的设备设置列表泛查。空列表时按当前实际带“+”的新增图标操作,不把摘要误识别的“图片”字样写成精确文字定位器。实际出现名称字段才输入并核对文本;依据当前表单保存、列表回读,移开视角后召回并比较原场景。仅已打开空列表不代表完成保存/召回;缺少可辨画面时保留对应位置比对未验证。
用户将指定画面截图、操作录屏或日志地址/文件明确作为本条交付目标时,可将画面、动作区间、档位、数量及关联时间写入 Artemis 提示词;操作录屏按上述单条配置启用,提示词本身不能启动宿主录制。本轮9.69的日志地址/文件按上述规则关联实际取得结果。优先使用执行端实际开放的截图能力;只有自动步骤截图时,持续页面可在目标状态稳定后再次观察并记录对应步骤,短时浮层则由执行方争取保留最早有效画面或从已有录像提取,不等整幅直播稳定后才取证;无法捕获时明确缺项,不发明截图工具或预先承诺文件名。宿主将实际资料与运行 ID、对应状态和时间逐一关联后才记为采集完成。通用录屏、归档目录等仍由 Codex 处理;人工业务结论保持待判,不以“采集完成”替代产品通过,也不将未取得的图片、视频或日志记为已留存。
## 已有任务表复盘修订
云存探查样本`d632632d-ec54-4935-8675-0a7a5ed090b2`为6.5,6步约187.142秒;第3步将整页下方面板/拖柄附近误当视频中心,视频外点击后Explorer未找到清晰度,随后进入设置并强停。可见云回看入口未点击,无日期/选时/云录像播放证据;历史只说云服务未核实,没有证据表明已经误判未开通。旧正文已有临时核服务与连续点击要求,本轮强化实际独立探查、正确视频矩形及收尾覆盖;历史生命周期failed、正式业务blocked,中间Checker为inconclusive,保持各自记录,不能据ARTEMIS操作录屏推有云存,不能倒填已查视频。
切档即停样本`20a19b7a-f2e5-469a-9508-eaf18401eb52`为7.4,7步约267.681秒;第5步选高清,第6步主动停止App,第7步报告失败,没有wait、卡回看或拖轴,动作均成功。高清动作返回/末帧至stop仅约9.678秒,未建立本档300秒;3600秒预算未耗尽,未见取消或不可继续故障,等待能力实际存在而未调用。摘要503与辅助服务回退后操作继续,停录WinError 2发生在主动停App后,均不能作为早停原因;本地录像存在且首/中/尾抽样可读,不包含未做的卡回看。正式生命周期failed/业务blocked,最终Checker原答inconclusive,保留差异。按本条选中后计时及完整阶段修订,不加预算、不改模型、不回写旧结果或自动重跑。
休眠样本`e898b8d4-9242-4086-b024-e5e7f78c9b22`为9.32,12步约201.525秒:第6–7步开启并确认休眠,第8步BACK只到设备设置外层,第9步再次进入休眠,第10步关闭开关并出现“设置成功”,第11步直接强停App,第12步主动结束。已有关闭尝试,缺的是休眠开启后的直播观察及关闭后的直播恢复验证;不能说完全没执行关闭,也不能将开关灰色视为真实推流恢复。原正文已有推流与恢复要求,执行未落实页面和顺序;第5步索引错误在第6步已纠正,不是持续阻塞,无总超时终止证据。生命周期completed、正式业务inconclusive及原配置保持,按 [9.32休眠直播与唤醒恢复](#休眠直播检查与唤醒恢复932) 补串行必做阶段,不回写历史为通过、不自动重跑。
自动维护日期样本 `2e1d02c2-6134-416e-b45a-92563b85f3c1` 为9.31,原执行正文已明确设置页最上方设备图标与型号五击。实际14步中在设置列表5次向下查看、4次回顶,未点击顶部设备图标、未读取真实型号或执行五击,也未进入设备自动维护配置;回顶后仍因当前设置页未见型号即强停并报告失败。主列表开发者分组、自定义编码参数及固件升级版本不补证已进入模式。会话约215.761秒,属于主动受阻收尾,历史执行failed、业务blocked保留。按用户新确认的 [9.31最近维护日期](#设备自动维护最近日期检查931) 修订路径落实和日期存在性判据,原周期、重启、日志与跨日条件仍未验证,不回写历史为已读取日期、已维护或已通过。
卡回放选时样本 `5250cbe5-e63f-488d-b315-204aad62fc0f` 为7.4,原正文已写逐档各5分钟及条件式“需选时先试拖”。实际使用早图与click_sequence打开清晰度菜单,高清初始选中后切标清并等待300秒,但高清未补足5分钟、超高清受30秒体验限制未选择;进入卡回看后无拖动、选日或选时回读,第13步推理“不拖动到无证据的区间”即强停。原图“今”已选,指针停约12:56,时间轴灰色段可见,未定位本次约13:08切换与随后播放时段;右侧推广卡不能证明卡时间轴无效,也不能据默认旧帧认定无TF卡或本次录像不存在。历史执行failed、业务blocked保留;约644.4秒、未达3600秒总预算。按 [7.4实际选时与视频采集](#卡回放本次切换点定位与操作视频74) 将选时由条件句改为必做、区分拖动无效与目标无数据,并按用户补充把音频判定交人工;不以新口径倒填旧运行已拖轴或已通过。
云回看逐档样本 `f0044b0a-4439-434a-9d88-a092ad232a90` 为6.5,实际初始正文已明确逐档各5分钟再查对应云录像。共6步:启动、进直播、误点相机、进云回看、强停、报告completed;无清晰度选项点击或等待调用。第3步归一化(520,550)换算像素(561,1056)落在相机工具栏,早图有截图预览,本次已有early_post_observation,不能再归因为早图完全缺失;误点后未纠正就跳过直播阶段。会话159.397秒,设备卡点击至云回看最多69.223秒,未触及3600秒总预算;任何一档均不足300秒。回看“今”实际选中,画面为2026-10-19 12:53:11,早于任务13:01:33开始;旧事件不对应本次。其他服务卡“未开通”不证明云存未开通。历史执行completed、业务blocked保留,PASS目录名不代表通过;原录像实际存在并完整解码,156.81秒、无音轨,不能由停止录屏报错推定无录像。按 [6.5阶段与对应新录像](#直播逐档五分钟与对应云录像65) 修订,不固化样本坐标、不回写旧结果。
直播浮层再次失败样本 `b00d7a56-0a19-4091-a81f-b69923d2385b`(3.3)与 `12f95604-fdcb-426d-a827-af4d4704081c`(3.6)均已逐字核对使用V7正文;“未出现控件”的旧报告被实际早帧及录屏否定。两者落点在视频内,浮层约5秒后隐藏,执行器继续多轮稳态观测仅返回晚帧,首次入口因此始终未知,随后没有执行 `click_sequence`;3.6另尝试无依据的同点双击。按 [首次发现、早末帧和本次证据](transient-controls.md) 修订,不能再只追加“快速点击”,也不能归为旧稿未导入、产品没弹出或任务总超时。3.3实际高清持续10分钟、3.6小窗/全屏往返及质量仍缺项,原 `failed/blocked` 保留。代码层保留早帧供决策、技能流程更新与新设备复测是三个独立状态;不以文件校验或单元测试通过倒填业务跑通。
缩略图刷新样本 `0cdd62d4-df03-4aef-9c90-7df979250cef` 是3.2“设备缩略图展示”。第3步已进入真实缩略图模式,页首欢迎区和标题完整;第4–5步向下浏览至长虹第三张目标卡片,旧图时间16:04:49。全链仅第6步一次向下手势,其后只是较上方首卡重新出现,欢迎区仍未显示、标题被固定栏裁切;没有确认到顶后再次下拉,也无刷新触发的有效证据,第7步随即向下浏览回目标。原“找到目标并下拉”说明漏了顶部前提,执行又把普通回滚称为刷新,终检沿用动作自述与旧图不变判失败,三处须分别修正。第8–9步实际进入同设备直播,记录16:09:42及16:10:00;第10步返回后新图时间16:10:16且内容改变,返回更新已观察到,不能写成整条没有更新;新图与较早直播帧构图不同不等于更新失败,也不证明精确最后一帧匹配。历史执行 `completed`、业务 `failed` 原值保留,复核另记“手动下拉刷新触发未验证;退出直播后目标缩略图更新已观察到,原刷新失败依据不足”,按 [本条两段操作与独立验收](#缩略图顶部下拉与直播返回更新32) 修订,不倒填手动分支已完成或整条通过。日志实际FlashRunner与摘要Pro标签差异保留。第11步停止App有回执及桌面反馈,第12步报告结束,主/前缀子进程未独立核验的范围仍保留。
自定义桌面样本 `1c060f98-8e88-4d21-88fa-840144f2ef41` 是23.28“更换已应用自定义桌面”。第12–14步从相机胶卷选图并提交,返回自定义列表后新增可辨室内照片、计数由1/6变2/6,第19步再次返回仍见该新图,按用户本次确认的口径已有App上传执行完成证据。第15/20步点击图片仍在列表,未见预览、应用或“使用中”,只支持选择/应用效果待核,不足以认定B应用失败;第17–18步实际选择家庭场景系统主题C且App显示“使用中”,不能由动作自称“月球”或该标记推出实体显示及无重启/离线。原实体A也未核实。旧删除操作后3/6变1/6与动作自称删一张的关系未充分核对,不据此编造精确删除对象或恢复情况,也不将旧选用的用户既有照片当作后续素材准备范例。历史执行 `failed`、业务 `failed`、`execution_failed` 原值保留,复核另记“按用户新口径上传执行完成,桌面结果人工待核”;采用 [本条上传与人工核对范围](#自定义图片上传与人工核对),不将原B/C效果缺证继续作为新上传任务失败依据,不倒填原完整A/B/C通过。第21步停止App有回执及桌面反馈,第22步报告结束;无恢复初始主题或删除新图记录,主/前缀子进程未独立核验的范围保留。
七天声音告警样本 `1ff4c406-14a2-40b6-968f-75fb5e4bf3c2` 是1.14“7天挂测设备异响检查”。第6步先查看10月15日01:57:58一条“较大声响”,视频停00:00、时长00:26,未见有效播放核听或声源确认;第7–10步才进入设置,确认声音侦测蓝色开启、灵敏度实际位于“高”,未修改。没有逐日选查最近7天或取得完整含夜间观察。历史执行 `completed`、业务 `failed` 原值保留;原失败将一条声音告警直接视为设备异响,复核认为该依据不足,设备自身异常声源及原七天“无异响”结果仍待核,不能改写成已证实故障或已通过。原列表A90S-2离线只证明该可见设备的状态,未证它就是已布置的第二监控机,不据此认定实际第二机离线。后续按用户补充的 [先查状态再查七天声音告警](#声音侦测状态与最近七天告警) 修订,不自动开启或移入8.30的两分钟等待,也不以原长期条件缺项直接跳过历史。第11步停止App有回执及桌面反馈,第12步报告完成,主/前缀子进程未独立核验的范围保留;新顺序与七天覆盖不倒填旧记录。
电瓶车算法入口样本 `4e1d97db-c0f5-4772-86f6-2536148e7de6` 是16.124“电瓶车告警功能必须支持的设置项”。第8步仅查看通用移动侦测的开启开关、高灵敏度和区域框,无电瓶车映射;第11步确实进入AI设置,第12–13步向下查看,见“车辆识别”(说明“有车辆出现时视频记录”,状态“去开通”)及后续时光轨迹、出入通知、设备巡检,未取得目标电瓶车算法/开关。第13步仍有新条目,第14步即返回,无可靠续滑不变或明确底端证据,不能将“已查AI设置”写成已穷尽。第15–16步只读AI周界和区域入侵,未开启或修改;按用户随后确认,不再将这些功能作为电瓶车算法候选。历史执行 `completed`、业务 `inconclusive` 保留,未把通用控件判成四项支持通过;本次沿 [电瓶车独立算法与四项支持](#第16部分云化ai算法范围与入口) 修订,保留实际已查AI列表范围,不倒填目标已找到、确定不支持、必须付费或已完成四项核验。第17步停止App有回执及桌面反馈,第18步报告结束,主/前缀子进程未独立核验的范围保留。通用控件映射不足与未找到目标算法分别记录,不引入车辆16.75的实车区外负向要求。
车辆算法入口样本 `31ac0cd2-2193-42f2-98dd-c26bb01e9796` 是16.75“设备功能”,归属车辆识别/AI车辆识别功能。第5步进入设备设置,第6–7步向下查看但未证明到底;第8步只读“AI周界”,区域入侵和越界侦测均未开启,第10步只读“移动侦测”的开启状态、高灵敏度与默认全画面框,未修改这些开关或区域。没有进入AI设置、确认目标AI车辆算法/开关,也没有真实区外车辆刺激及对应识别/记录结果。旧执行本已说明候选不等同并保留受阻,不能描述成已经开启AI周界、执行了车辆算法或误判通过;本次按用户确认进一步排除这条候选路径,采用 [AI车辆识别独立入口及未见开关分支](#第16部分云化ai算法范围与入口)。历史执行 `failed`、业务 `blocked`、`execution_failed` 保留,不属于超时或已证实的算法缺陷,也不倒填为设备确定不支持。第11步停止App有回执和桌面反馈,第12步报告失败,主/前缀子进程未独立核验的范围保留;原真实区外负向条件不因入口纠偏被删除或扩为区内正向测试。
人脸AI启用缺项样本 `2bca40cb-1d1e-4644-a349-169e6e97dda8` 是16.10“设备倒装时旋转视频源,上报人脸图片需翻转处理”。历史未进入AI设置,也未读取、开启或保存回读云化人脸算法;第7步只成功开启“画面与声音 → 翻转视频源”,第17步关闭恢复,两次均有开关变化及“设置成功”。第11/13步查看的是01:57:58“较大声响”旧事件及其灰白模糊图片/录像,早于本轮翻转启用,既不是已确认的人脸算法上报,也不能补证本次修改态效果;真实实体倒装亦未证实。第18步停止App有回执及桌面反馈,主/前缀子进程未独立核验。历史执行 `completed`、业务 `inconclusive` 保留;本次按用户补充的 [16.10人脸AI开启分支](#第16部分云化ai算法范围与入口) 增加启用和回读,再取得匹配的人脸图/云录像,不把翻转开关或旧事件倒填为人脸AI已开启、云化效果通过或设备不支持。
云化算法范围样本 `3664b280-49da-43eb-9bbb-4bcd77afe859` 是16.2“人脸抓拍实际场景算法效果准确率测试”。第5步进入设备设置,第6–7步向下查看,第8步打开主列表“功能设置”中的“人脸侦测”(标有“云存储专属”),子页仅见灰色关闭开关;未开启该开关,也未进入“AI设置”或取得对应云化算法信息,第9步停止App,第10步报告结束。按用户随后确认,此处属于端侧人脸侦测,不能作为本条云化算法存在或状态的证据;原查找范围也未证明AI设置已查完,不能直接认定设备不支持云化算法。历史执行 `completed`、业务 `inconclusive` 保留,原缺少适用口径、真实样本及指标的情况仍成立;复核补充云化入口漏查与算法对象不符,后续优先按 [云化AI入口与能力边界](#第16部分云化ai算法范围与入口) 核对,不因室外条件或样本缺项跳过可独立查看的AI设置。停止App有回执及桌面反馈,主/前缀子进程未独立核验的范围保留;不改写旧记录为已开启端侧、已验证云化、确定不支持或算法效果失败。
版本格式样本 `abeb226f-41a8-43ef-bcbc-df945873d3a2` 是13.13“固件包版本校验”,原仅要求查看版本并核三段纯数字、每段最多5位。第7步后设置列表可见“固件升级 1.0.01”,第8步点击该入口;第9步动作前截图已显示“固件升级”“当前已是最新版本”“当前版本 1.0.01”,随后停止App。历史执行 `completed`、业务 `failed`,失败理由是页面未出现字面“固件包版本”;用户随后明确该页版本就是本条目标字段,确认操作正确。结合原截图与该字段对应关系,本次复核应更正为取值路径正确、版本格式通过,`1.0.01`三段位数为1/1/2,不能继续沿用原字面标签误判;保留原结果与用户确认后的复核结论及依据分别追溯,不改写旧DB/trace或冒称已重跑。停止App动作与桌面反馈已有记录,主/前缀子进程未独立核验的范围仍保留,不影响已取得的格式证据。后续按 [本条字段与格式规则](#用户确认路径与人工判定采集) 修订,不追加版本相等或真实升级要求。
间歇恢复后总超时样本 `1725bc2a-caa3-492b-bb1d-67a4125ce04c` 的持久编号是12.3,名称“静态码率压降效果测试”;原始执行为FlashRunner,摘要标Pro。只完成启动、关弹窗、进入设备直播和等待10秒四步;Turn3一次、Turn4三次、Turn5一次,共5次模型请求timeout及10秒重试间隔,耗尽继承批次的1800秒总预算。第3步点击描述达56,791字符且包含重复自我叙述,点击仍成功;其后上下文增大,不能把异常文本与后续超时的先后关系当作唯一根因。间歇动作恢复不补回已用时间,唯一明确业务等待只是10秒,不同于12.6样本的30分钟等待预算不足。未进入设置、读取能力或智能编码状态、开启回读、测量真实静态输出,也未执行强停App;历史 `failed / blocked / task_timeout`、业务与收尾缺项均保留。录屏持久状态ready且无错误不支持归因为录屏故障,旧控制文件running不能覆盖最终失败终态。按下述累计异常与重试预算规则、[本条效果范围](#用户确认路径与人工判定采集) 修订,不倒填路径已走通、静态压降失败或通过。
静态压降样本 `156895f6-c3fe-4e5b-acd5-e3bdeb3003bc` 是12.29“静态码率压降开关(暨‘智能编码’开关)”。实际15步中,第5–7步仅查到仍在推进的功能设置,第8–9步查看“AI设置”,第11–12步反向回滚,第13步进入“画面与声音”,随后停止App并报告失败;未进入“自定义编码参数”、未读智能编码初态、能力/配置项上报或实测两态输出。后续沿用户确认的 [智能编码入口与本条边界](#用户确认路径与人工判定采集) 修订,不能因候选页未见控件认定设备不支持。历史执行 `failed`、业务 `blocked`、`execution_failed` 保留;实际FlashRunner与摘要Pro分别记录。第2步点击描述异常冗长,但点击成功且后续继续,不据此认定为本次入口未定位根因;收尾后录屏报错也不解释前序漏查。强停App回执成功与主/子进程未独立核验分别保留,新路径不倒填旧开关或压降效果通过。
十二小时历史样本 `fb5bb019-6307-45d6-949d-3958de56a565` 的原任务是12.28“设备运行稳定性测试”,要求动态高码率直存、SD卡及实际适用负载下至少12小时无重启/掉线。实际19步主要检查/调整移动侦测、声音侦测及移动追踪,第17步只看存储卡容量16.69G/28.82G,第18步强停、第19步主动报告失败;未进入云回看、卡回放或检查任何12小时时间轴。后续按用户新增的 [最近十二小时检查](#最近十二小时云存卡存空洞检查) 独立查看已有历史,不以新挂测/负载缺项阻断,也不把容量页当卡存录像或改开关当长期负载证据。历史执行 `failed`、业务 `blocked`、`execution_failed` 保留,实际FlashRunner与摘要Pro分别记录;本次不是任务总超时。收尾后屏幕录像停止报错与前序未查时间轴分开,旧运行不能倒填为按新颜色口径已发现/排除空洞或稳定性通过。
稳定性历史样本 `ccd4adcb-fe82-4050-9c8a-319395d333e7` 的原任务是12.24,要求7×24小时挂机、每天10事件、事件无丢失且能正常播放。实际8步只查看告警页“暂无告警”、当前云回看约00:00–00:50的一屏及“一天快放”播放,未切卡存、未逐日选最近7天、未拖动覆盖录制时段或记录空洞。第6步摘要称空白时间轴,未经本轮颜色口径与应录制条件复核,不能倒填成已发现空洞;视频画面灰白也不证明时间轴纯白缺段。后续按 [最近七天历史检查](#最近七天云存卡存空洞检查) 优先查看两种存储既有记录,原长稳和每日事件断言分别保留,不以缺新7天挂测阻断历史检查。历史执行 `failed`、业务 `blocked`、`execution_failed` 保留;日志存在一次模型请求超时重试,但本次最终主动报告失败,不是任务总超时。实际 FlashRunner 与摘要 Pro 分别记录,OSD年份识别冲突不作为最近七天日期依据;强停动作成功与进程列表未独立核验分别保留,收尾后的录屏报错不冒充前序未检查的原因。
码率实测样本 `eeafe931-ae71-41ef-91a4-bfed4d4a547e` 的原任务是12.14。实际10步中,第5步仅查看“画面与声音”,未继续滑动设置列表、未进入“自定义编码参数”;第8步动作描述称点直播画面,实际视觉摘要指出落点在底部面板上沿灰色拖柄。第9步停止 App,第10步报告失败;未开启/核验智能编码、选择 H265/H264、切换超清/高清,也未录制任一完整5分钟素材或输出平均码率。后续按用户补充的 [四组合录制与实测判定](#编码组合录制与码率判定1214) 修订,不能从错点后未见控件或候选页未见参数推断设备不支持。历史执行 `failed`、业务结果 `blocked`、`execution_failed` 保留;原始 FlashRunner 与摘要 Pro 分别记录。会话结束后的自动化屏幕录制停止报错是独立取证问题,不能作为此前查找结束的原因或摄像头码率来源;停止 App 回执成功不等于已单独检查主进程及子进程。用户确认路径不倒填成旧运行证据,无新素材则不虚构四段视频或实测数值。
用户要求“指定批次执行完成的用例 + 本次优化用例”时,先精确核对批次名、创建时点和稳定 ID,以该批逐条最终执行状态 `completed` 建立集合,保留业务结果原值;与优化用例的来源行映射后取并集。跨批次映射须核对完整序位、编号、名称及所属项目,不依赖编号唯一。用户新标记的“暂不适用、测试不执行”优先于历史完成状态,从派发集合排除;全量记录可保存在“暂不适用”等非“任务”页。当前导入器不支持 `enabled`/`skip` 字段,不能只在前置列写“不执行”却仍保留为“任务”行,也不伪造配置字段。历史 completed 只表示曾执行结束,筛选版如实说明其来源和仍需落实的条件,不承诺将来必定完成或业务通过。
复盘异常超时时,分别核对模型请求超时/重试、任务总时限、暂停期限、设备观测/动作超时及业务等待;以原始配置、日志时间轴、动作耗时及终态结果定位。模型把大量重复自我催促文字写进工具参数时,记录为执行器/模型响应异常,不归为产品或设备动作失败;提示词只写本条目标、已确认入口、必要反馈和有限恢复,动作描述只含当前控件,不继续加入无关泛查。此类精简只降低任务歧义,不能宣称修复了模型服务;诊断和后续编排留给执行方,不静默切换模型、修改全局配置或延长时限。摘要所报 Flash/Pro 与实际配置/日志不符时分别保存,不能用错误摘要覆盖原始执行方式。
执行方发现异常参数或连续模型超时且 UI 步骤不增长时,先调用 `mobile_diagnose`,同时保留最后一次成功动作、当前重试轮次及真实已过时间;诊断当前正常不反证历史模型请求正常,HTTP 200 也不能单独证明流式响应已完整结束。按已确认的请求/重试与无进展预算提早处理受阻任务,需结束运行时使用已有授权的任务管理能力,不等任务总时限耗尽,也不把模型重试变成业务用例自动重提。已获准的恢复或新运行先重新观察当前页面,以最后确认阶段为参照,不能重放已成功动作或复用旧坐标。任务超时或取消后另核约定的 App 收尾及证据文件:终止 runner、释放设备、停止 UIAutomator2 均不证明已强制停止 App;按既有授权和设备空闲条件补做,未补则保留收尾缺项。录屏停止报错不等于文件必定不存在;取得真实文件后检查可读性与覆盖区间,不将编译器的空步骤摘要当作整条没有原始证据。这些属于执行方交接,不拼入业务提示词,也不由本 skill 实际执行。
模型异常间歇恢复时,执行方仍应记录跨回合累计请求/重试耗时、超时次数、异常参数及原目标实际进度;一次成功点击或短等待只证明该动作完成,不能抹去已消耗预算或直接认定链路恢复稳定,也不能误把其中真实合法等待的时长记成模型耗时。联合核对单次请求时限、重试次数/间隔与整条剩余预算,为必要业务、诊断及收尾留余量;若采用300秒单次请求、最多重试5次及每次10秒间隔,一个逻辑请求最坏已达 `6×300+5×10=1850秒`,不能认为1800秒总时限足以等完该请求再做业务。该计算是策略上界示例,历史值以当次配置/日志为准,不自动调参或延长全局时限。执行端“等待模型”提示、HTTP200、无工具回退或动作失败兜底,不证明正在内部重试的完整请求已结束;是否能提前中断以实际网关和宿主能力为准,技能文字不能代替运行层预算感知与参数校验。动作描述含伪工具调用、重复自我催促或异常长段,即使动作成功也保留模型输出异常;诊断只引用控件、长度、简短摘要和原始证据路径,不把长重复全文再次注入业务提示词或恢复指令,不改写旧trace来隐藏异常。
复盘“直播点击后没有控件”时,按 [瞬态取证与诊断](transient-controls.md#执行方的取证与诊断) 核对最早画面与原始录像,不只引用步骤摘要或最终截图。录像证明浮层曾出现时,仅更正该项原因及有证据覆盖的范围,仍保留清晰度切换、播放时长等未完成断言;典型样本及适用限制见 [本次复盘依据](transient-controls.md#已验证样本与适用限制)。
夜视取图样本 `1d2e2c13-8e31-40b7-a295-6654df9a1860` 的第 6 步实际只见“自动”(已选)、“开”“关”;执行未修改模式,因当时提示词强制要求“智能夜视”而中止,未补目标直播图。用户后续确认室内云台可能无智能选项且不得关闭夜视或开启全彩,后续提示词采用上述能力分支;不能倒填该历史任务已完成取图,也不据模式名称差异直接认定产品缺陷。
人形告警视频样本 `5a12a2ec-93e1-43da-9255-0d6f65c752a0` 的第 5–6 步只查看告警列表及“全部告警 / 区域入侵 / 画面变化”分类,未进入设备“设置”,未核查人形侦测是否存在或开启,也未取得可确认的人形事件视频。后续按 [人形侦测与告警判断](#人形侦测与告警判断) 补齐判断顺序;历史执行 `failed`、业务结果 `blocked` 如实保留,既不能改写为设备不支持或告警故障,也不能倒填已完成设置核查与视频质量验证。
声音告警视频样本 `ecfbbe36-241a-432b-b314-5645eba599d8` 的第 4–10 步仅在告警列表查看区域入侵、画面变化、类型及日期,第 10 步选中10月11日并显示“暂无告警”;未进入设备“设置”,未开启或确认声音侦测,也未建立开启后的2分钟等待与对应新告警检查。历史执行 `failed`、业务结果 `blocked` 保留,未找到可确认的哭声视频只覆盖当时所查范围,不证明功能不支持或本次触发失败。后续按用户新增的 [声音侦测开启与两分钟后检查](#声音侦测开启与两分钟后检查) 补齐动作,不倒填历史已开启、已等待或视频质量已验证。
设备型号样本 `097f7d3e-17ff-42ab-9be6-fc0186ab009c` 的原任务是 9.2“验证设备型号”。第 5 步进入设置,第 6 步操作前的首屏截图可见顶部设备图标,但第 6–9 步只向下滚动列表,未点击该图标,未读到实际型号;看到“固件升级 1.0.01”也不能完成型号比较。后续采用用户补充的 [设备信息入口](#用户确认路径与人工判定采集),先点设置页最上方设备图标再读目标字段。历史执行 `failed`、业务结果 `blocked` 保留,不能倒填已进入该信息页、实际型号与厂商一致或已通过,也不能将漏查入口认定为 App 缺失型号信息。
设置保持样本 `10bbf798-eb8e-42f1-b042-624664a9f54d` 的原任务是 9.21“设置项检查”,要求原七组设置在真实 OTA 前后保持一致。实际第 6–16 步只读取部分升级前配置,未完成全部目标项,也未进入“固件升级”查询可用版本,随后停止 App;无升级完成和升级后读数。后续按用户补充的 [功能设置后查询固件升级](#用户确认路径与人工判定采集) 接续检查,不能把未查询写成没有新版本,也不拿其他 trace 的当前版本读数代替本次升级检查。历史执行 `failed`、业务结果 `blocked` 保留,不倒填已检查版本、已升级或设置保持通过。
水印样本 `bdc2129c-39f1-4dbd-991f-97f1770efb19` 的原任务是 9.65“‘移动爱家’logo 水印开关”。原始日志为 FlashRunner,摘要却标 Pro;仅记录启动 App 和关闭发现设备弹窗两步。第 2 步参数夹带大量重复自我催促文本,但点击实际成功;第 3 轮连续记录 5 次模型请求超时并重试,最终由 1800 秒任务时限终止。执行 `failed`、业务结果 `blocked`,未进入目标设备、水印设置或任何水印结果检查,不能归为点击无效、设备不支持水印或水印缺陷;异常文本与后续超时的先后关系已证实,具体模型服务/传输根因仍待调用层证据。日志另有录屏停止报错,且未见约定的停止 App 动作,分别保留取证与收尾核查,不倒填已完成。
日志样本 `591d3c66-9cfb-41dd-92e2-65434b4f977d` 的原任务是 9.69“设备支持按需上传日志”。第2步进入设置后仅向下查列表,第7步“设备检测”实际打开网络检测页,未点击顶部设备图标进入详情,未调整日志档位,也未取得日志地址或正文。用户随后确认详情中的“设备日志管理”入口及“两次调档、取得日志地址、人工核对”安排,后续采用上述采集分支;历史执行 `failed`、业务结果 `blocked` 保留,不将旧任务泛查当成无日志能力,也不倒填已经调档、取址或等级一致性通过。
码率样本 `14178322-843c-4742-bda3-8144b3aaf9dd` 的原任务是 9.101“码率大小设置”。第7步查看“画面与声音”,第11–12步进入“AI设置”后仅见智能守护等服务,第14步滑到“固件升级/设备检测/网络切换/常见问题/流量播放提醒”区域,随后停止 App;未打开“自定义编码参数”,也未设置码率或核验智能编码互斥。后续按用户补充的 [自定义编码参数入口](#用户确认路径与人工判定采集) 修订,不将原候选页未见控件当作设备不支持。历史执行 `failed`、业务结果 `blocked` 保留,用户确认路径不等于旧 trace 已走通,设置保存、互斥及厂复默认均不倒填通过。
AOV 样本 `151fb9f8-f144-4ecc-99a3-34fbdaed66cb` 的原任务是 9.122“不开启任何告警检测时,在正常环境下挂测(有动态、静态画面)”,预置 AOV 开启,原步骤要求不开直播挂测并查看生成视频流,预期正常播放、无快播。第29步能力集已显示 `support_aov:true`、`aov_on:false`;第30–31步设置页可见“自定义编码参数”,但未打开,随后仍查能力及可视化配置,第38步 AOV 仍为关闭,未设置码率、完成挂测或核验真实视频流。后续按用户补充的 [码率、AOV 与直播检查](#用户确认路径与人工判定采集) 修订,原不开直播挂测与新增直播检查分阶段处理;历史执行 `failed`、业务结果 `blocked` 保留,不将漏查子页认定为没有开启入口,也不倒填码率设置、AOV 开启或播放验证通过。
分辨率样本 `90fcbf99-b040-40a3-aadc-5f89651a0fda` 的原任务是 9.133“分辨率设置(每个清晰度下可上报多个分辨率)”。第5–6步仅检查“画面与声音”,第8–9步查看设置列表部分区域,第9步仍在存储卡、呼叫及功能设置区域;未进入“自定义编码参数”,未读取具体分辨率或支持项,也未完成切换及切换后直播检查。后续按用户补充的 [编码参数分辨率入口](#用户确认路径与人工判定采集) 修订;历史执行 `failed`、业务结果 `failed` 原值保留,但已有证据只支持入口未定位及验证未完成,不证明设备不支持或分辨率切换存在产品缺陷。用户确认路径不等于旧 trace 已走通,不倒填读取、切换或直播音画通过。
智能编码样本 `77d21339-635d-4f13-8bc9-c5a44e3ab21c` 的原任务是 9.137“智能编码开关”。第7步检查“画面与声音”,第10–11步查看主设置列表至“功能设置”,第12步进入“AI设置”后仅见智能服务,未进入“自定义编码参数”;未读到开关原值、未开启或关闭,也未采集有效静态场景的输出码率。后续按用户补充的 [智能编码开关入口](#用户确认路径与人工判定采集) 修订;历史执行 `completed`、业务结果 `blocked` 原值保留,执行结束不等于默认开启、开关生效或压降效果通过。用户确认路径不倒填为旧 trace 已完成,也不从候选页面未见开关推断设备不支持。
AOV 开关样本 `fd3c3a71-42a0-4aa9-b371-5fc5ad314d6a` 的原任务是 9.138“AOV开关”。第5步查看“画面与声音”后返回,第7–9步滑动主设置且每次内容仍向下推进,第9步仅到增值服务、其他及“固件升级/设备检测/网络切换”,第10步便停止 App;结束前截图底端仍为“网络切换”,没有有效续滑后不再推进或可靠边界证据。原说明“已查至设置底部区域”不能证明到底,未进入“自定义编码参数”、未读 AOV 初态、未执行开关。后续按用户确认入口及 [持续查找与末尾确认](#设置页查找与到底确认) 修订;历史执行 `failed`、业务结果 `blocked` 保留,不把供电类型缺项扩大为普通开关不可执行,也不倒填默认状态或两态核验通过。
提前收尾样本 `fe3d8156-d32a-448f-805f-2ca02ffa8671` 的原任务是 9.139 编码参数与 AOV/智能编码互斥。第14步已进入“自定义编码参数”,可见 `gop设置`;第24步在 AOV 开启后只回读码率,第39步在智能编码开启后只回读帧率15,GOP 未读、每次开启后的三项恢复核验均不完整。第40步推理同时写“已完成”及“gop未修改/未核默认”,随后主动停止 App,第41步主动上报 `completed`;会话约473.8秒,未见超时或外部取消,末尾 Checker 才指出覆盖缺口。原始执行为 FlashRunner,摘要标 Pro 的差异分别保留;录屏停止报错发生在会话结束后,不能作为提前停止原因。历史执行 `completed`、业务结果 `failed` 保留;按 [完成检查与收尾](#用例完成检查与收尾) 及 [9.139分阶段核验](#编码参数互斥与完整核验9139) 修订,不能把任意一项参数变更解释为只读一项恢复结果,也不把原已关闭的 AOV 在变更后仍关闭认定为发生自动关闭。既有文案差异与未完成断言分别记录,不删除强制停止约定或以延长时限代替补全检查;本 skill 修改不等于已改变后端 Checker 或重跑旧任务。
直播时延样本 `49bbfe7a-f53f-4b03-8103-093036e2616b` 的原任务是12.6,要求实际播放30分钟后测 `t≤2s`。原始 FlashRunner 完成3个步骤,第4轮明确执行 `wait_for_delay(time_in_ms=1800000)`,等待尚未回返即被任务总时限终止;不是已证实的模型重试或无响应。单条 `timeout_seconds` 未覆盖,继承批次 `54f29c0f-1374-46d9-ae17-0f834bd25e87` 的1800秒预算;等待前已消耗约87.5秒,实际只等约1713.4秒(28分33秒),更无时延测量和收尾余量。按 [长时预算与计时](#长时任务预算与实际计时) 修订本条预算及等待方式;分段等待不能替代预算修正,不缩短30分钟或删除2秒判据。历史执行 `failed`、业务结果 `blocked` 保留,时延、连续播放及 App 收尾未完成均不倒填;摘要 Pro 与原日志 Flash 分别记录,不能沿用水印样本的模型超时根因。
以用户指定或已核实关联的批次为范围,按每个 session/trace ID 对齐原提示词、执行终态、业务结果、原始 Checker 和实际步骤;同编号不能合并。用户给同一运行ID对应多个用例时,先核对该ID实际任务;映射不符则保留原ID与差异,按用户确认的用例原文修订,不将其他任务结论归入本条。保留各来源判定与独立审计,执行 completed、取消和错误包装不能直接转成产品结论。沿用已完成且证据未变的复盘;新证据只更新对应运行和覆盖范围。汇总独立审计时只统计明确保存的审计字段,缺失项单列,不从平台业务结果补入。
修订动作与本条原步骤、预期逐项对应,保持量词与观察端。原动作是“任意一项”时,不把遍历全部选项作为通过前提;同条预期要求三项恢复默认仍须分别核对。原文仅查看支持项时,以可明确映射到目标功能的界面、能力或有效配置分别核实支持,不追加专属独立页面、保存遍历、真人触发或效果场景;共享控件可以分别支持多个要求。视频质量只核本条列出的画面、声音或同步断言,不把相邻事件、人群或电瓶车场景混入,也不将“指定现场人员”写成唯一取证方式。
用户明确要求保留全量既有任务并重新出稿时,可以保留全部条目的原前置、步骤、预期及已确认环境,逐条编写有条件的执行说明,并另列尚缺输入与可执行范围。未具备条件的条目标为审阅稿,不伪装成正式可派发用例,也不以占位值代填业务参数。普通可逆参数、计时起点与已授权素材由执行方按实际能力准备;独立预期、真实物理场景、第二账号、OTA 和跨日周期仍须落实。此分支不替代首次正式转换时的适用性和待补要求,不自动改变原表标记。
真实日期、三小时、24小时或七天任务,在派发前由执行方核对实际时窗、连续观察来源及执行预算;未到时窗不让单条交互任务等待未来数日,也不以多次同日任务冒充多日。原操作和时长保留,App配置与长时效果分别验收。有界业务的总预算不足按上述长时规则在单条配置中修正,不混同模型链路故障。已实际执行的业务等待不能仅因没有新增UI步骤归为异常;连续模型连接错误、未产生动作和请求重试则先由执行方诊断并按已获授权决定后续派发,不能用加长提示词、增大模型超时、反复重提或删除原断言冒充修复。这些编排和故障处理要求写在执行说明文档,不灌入每条 Artemis 任务正文。
## 设备截图分辨率与相册查看
分辨率相关用例可临时离开测试 App,在同一 Android 环境的手机相册/图库中查看图片分辨率;沿用当前用户对该操作的授权与有效应用锁定配置。整理这类提示词时,可要求通过测试 App 的设备截图/抓拍功能保存图片,等待保存完成,再切换到相册,根据本次截图的时间、文件名或画面内容找到对应原图,打开图片详情读取像素宽×高。截图入口、保存位置与相册详情入口须按实际界面观察,不预设固定菜单、坐标或相册名称。不能仅凭“最新一张”认定是本次图片。
以该截图原图的像素宽×高作为实际值,与原用例或用户已确认的预期尺寸比较。若用例要求核验视频流分辨率,须先由原文或用户资料确认截图尺寸与该断言的对应关系;图片尺寸本身不补足缺失的判据。查看和核验完成、达到本条退出条件后,移动爱家按统一收尾要求强制停止 `com.cmri.universalapp` 并由执行方核对主/前缀子进程;其他 App 或用户当前明确另有安排时按其约定收尾。
Codex 准备任务配置时须核对有效的 `locked_app_package` 及其继承值,确保该任务可以切换到相册;若当前配置把操作限制在测试 App,按服务支持的方式在该任务范围取消应用锁定,不修改其他任务或全局默认。设备截图和相册查看是这类用例的具体操作,不将给 Codex 的通用截图、录屏及证据归档段落附到提示词中。
@@ -1,6 +1,6 @@
{
"name": "caveman",
"version": "2.7.0",
"version": "4.0.1",
"description": "Caveman 是面向 Codex 的精简表达、代码工作流和上下文压缩 skill 集合。",
"author": {
"name": "Julius Brussee",
@@ -8,7 +8,7 @@
},
"homepage": "https://github.com/JuliusBrussee/caveman",
"repository": "https://github.com/JuliusBrussee/caveman",
"license": "MIT",
"license": "Apache-2.0",
"keywords": [
"productivity",
"communication",
@@ -19,7 +19,7 @@
"interface": {
"displayName": "Caveman",
"shortDescription": "精简 Codex 输出,并提供调查、实现、迁移、重构、评审和验证工作流。",
"longDescription": "Caveman 提供精简输出、提交信息、代码评审、问题调查、精益实现、安全重构、迁移、补丁和验证工作流。包含 Codex SessionStart hook:信任并启用后,在启动或恢复会话时注入 Caveman 模式;可要求 normal mode 停用,但恢复会话会再次注入。另含可选的 caveman-compress,需要 Python 3.10 及 Anthropic API Key 或 Claude CLI,仅在用户明确指定文件后把该文件内容发送给 Anthropic。Claude Code 专用统计 hooks、statusline、Caveman CLI、代理和 MCP shrink 不包含在此插件中。",
"longDescription": "Caveman 提供精简输出、提交信息、代码评审、问题调查、精益实现、安全重构、迁移、补丁和验证工作流。包含 Codex SessionStart hook:需要 Node.js,信任并启用后在启动或恢复会话时按配置注入默认模式,默认 Caveman;可通过 CAVEMAN_DEFAULT_MODE=off 或配置关闭。另含可选的 caveman-compress,需要 Python 3.10 及 Anthropic API Key 或 Claude CLI,仅在用户明确指定文件后把该文件内容发送给 Anthropic。Claude Code 专用统计 hooks、statusline、Caveman CLI、代理和 MCP shrink 不包含在此插件中。",
"developerName": "Julius Brussee",
"category": "Developer Tools",
"capabilities": [
@@ -0,0 +1,210 @@
#!/usr/bin/env node
// Codex SessionStart hook (#185).
//
// The repo-local `.codex/hooks.json` used to hardcode a single static echo of
// the `full`-level rules, so `CAVEMAN_DEFAULT_MODE`, a repo-local
// `.caveman/config.json` / `.caveman.json`, or a user config `defaultMode`
// had no effect on Codex sessions — and an `off` default still injected
// rules. This script replaces the hardcoded echo and resolves the mode the
// same way the Claude SessionStart hook does, through
// `src/hooks/caveman-config.js`.
//
// The command in `.codex/hooks.json` resolves this file from the git root
// (`$(git rev-parse --show-toplevel)/.codex/...`) because Codex runs command
// hooks with the SESSION cwd as their working directory and can be launched
// from a subdirectory (Codex hooks docs: "prefer resolving from the git root
// instead of using a relative path"). Discovery of `.codex/hooks.json` is
// git-root based, so cwd may legitimately differ from the manifest's home;
// `__dirname` below is what keeps script-relative resolution stable either
// way.
//
// Keep this fail-open and cheap: any error prints nothing and exits 0, so a
// broken install can never block a Codex session. Never call process.exit()
// after the ruleset write — pipe stdout is asynchronous and process.exit()
// can drop pending output, leaving caveman silently unactivated. Exit by
// falling off the end of the script instead (exit code 0 throughout).
//
// Resolution order for default mode (identical to caveman-config.js):
// 1. CAVEMAN_DEFAULT_MODE environment variable
// 2. Repo-local config: <cwd>/.caveman/config.json or <cwd>/.caveman.json,
// walking up to the filesystem root
// 3. User config: $XDG_CONFIG_HOME/caveman/config.json, then
// ~/.config/caveman/config.json (macOS/Linux) or
// %APPDATA%\caveman\config.json then ~/.config/caveman/config.json
// (Windows)
// 4. 'caveman'
//
// Deliberate non-goal (#185 scope): this hook resolves the CONFIGURED default
// only. It does not read the legacy `~/.claude/.caveman-active` mirror or the
// per-session store, so a mode switched mid-session is not resumed here —
// Codex has no tracker hook wired in this repo-local setup, so there is
// nothing session-scoped to read without guessing at another host's state.
const fs = require('fs');
const path = require('path');
const os = require('os');
// Hand-copy of caveman-config.js VALID_MODES, used only when that module is
// unavailable. tests/test_hook_missing_sibling.js asserts the Claude hook's
// copy stays equal to the real one; tests/hooks/codex-sessionstart.test.mjs
// asserts this copy does too.
const FALLBACK_VALID_MODES = [
'off', 'caveman', 'ultracave', 'megacave',
'commit', 'review', 'compress'
];
// Hand-copy of caveman-config.js LEGACY_MODES: a config still naming a
// pre-three-skill level resolves the same way when degraded.
const FALLBACK_LEGACY_MODES = {
lite: 'caveman', full: 'caveman', ultra: 'ultracave',
wenyan: 'megacave', 'wenyan-lite': 'megacave',
'wenyan-full': 'megacave', 'wenyan-ultra': 'megacave',
};
// Default-mode sources also accept the 'manual' startup policy, like the real
// resolver's VALID_DEFAULT_MODES.
function fallbackCanonicalMode(raw) {
if (typeof raw !== 'string') return null;
const m = raw.toLowerCase();
if (FALLBACK_VALID_MODES.includes(m) || m === 'manual') return m;
return Object.prototype.hasOwnProperty.call(FALLBACK_LEGACY_MODES, m) ? FALLBACK_LEGACY_MODES[m] : null;
}
// Modes that inject nothing: the user opted out (`off`) or starts inactive
// (`manual`), and rules must not be force-injected (caveman-activate.js treats
// both the same way on the Claude side).
const SILENT_MODES = new Set(['off', 'manual']);
// Modes with their own independent skill files — not caveman prose modes.
// The Claude hook emits a one-line pointer for them; so does this.
const INDEPENDENT_MODES = new Set(['commit', 'review', 'compress']);
// Hand-copy of caveman-config.js readModeFromConfigFile, used only when that
// module is unavailable. Refuses symlinked config files, symmetric with the
// real resolver's findRepoConfigPath/readModeFromConfigFile policy.
function fallbackReadMode(file) {
try {
if (!fs.lstatSync(file).isFile()) return null;
// A BOM or UTF-16LE (PowerShell 5.1) must not drop an opt-out here either.
const buf = fs.readFileSync(file);
const text = buf[0] === 0xff && buf[1] === 0xfe ? buf.toString('utf16le', 2) : buf.toString('utf8').replace(/^\uFEFF/, '');
return fallbackCanonicalMode(JSON.parse(text).defaultMode);
} catch (e) { /* absent, unreadable, or malformed → next source */ }
return null;
}
function fallbackUserConfigMode() {
let userConfigPath;
if (process.env.XDG_CONFIG_HOME) {
userConfigPath = path.join(process.env.XDG_CONFIG_HOME, 'caveman', 'config.json');
} else if (process.platform === 'win32') {
userConfigPath = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'caveman', 'config.json');
// Then ~/.config, the path the docs give for every platform.
return fallbackReadMode(userConfigPath)
|| fallbackReadMode(path.join(os.homedir(), '.config', 'caveman', 'config.json'));
} else {
userConfigPath = path.join(os.homedir(), '.config', 'caveman', 'config.json');
}
return fallbackReadMode(userConfigPath);
}
// Minimal stand-in for caveman-config.getDefaultMode. Mirrors the real
// resolution order rather than reading only the env var — a degrade that
// ignores a user config saying `defaultMode: "off"` would INVERT the user's
// intent by force-injecting rules, the same reasoning caveman-activate.js
// documents for its own fallback.
function fallbackGetDefaultMode(startDir) {
// 1. Environment variable. No .trim() — the real resolver does not trim,
// and a degraded path accepting " ultra" where the intact one rejects it
// is drift in a whitelist.
const envMode = fallbackCanonicalMode(process.env.CAVEMAN_DEFAULT_MODE);
if (envMode) return envMode;
// 2. Repo-local config, walking up. Bounded at 64 like findRepoConfigPath.
try {
let dir = path.resolve(startDir || process.cwd());
for (let i = 0; i < 64; i++) {
for (const rel of ['.caveman/config.json', '.caveman.json']) {
const mode = fallbackReadMode(path.join(dir, rel));
if (mode) return mode;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
} catch (e) { /* fall through to user config */ }
// 3. User config, then 4. the built-in default.
return fallbackUserConfigMode() || 'caveman';
}
// The hook dir is `<repo>/.codex`, so the shared resolver lives one level up.
// Resolve it once; anything missing just degrades to the hand-copied logic.
const hookDir = __dirname;
let shared = null;
try {
// eslint-disable-next-line global-require
shared = require(path.join(hookDir, '..', 'src', 'hooks', 'caveman-config.js'));
} catch (e) {
try {
// Standalone install layout: hooks live beside the lib instead.
// eslint-disable-next-line global-require
shared = require(path.join(hookDir, 'caveman-config.js'));
} catch (e2) {
shared = null;
}
}
function main() {
const getDefaultMode = (shared && typeof shared.getDefaultMode === 'function')
? shared.getDefaultMode
: fallbackGetDefaultMode;
const VALID_MODES = (shared && Array.isArray(shared.VALID_MODES)) ? shared.VALID_MODES : FALLBACK_VALID_MODES;
const mode = getDefaultMode();
if (!VALID_MODES.includes(mode) || SILENT_MODES.has(mode)) {
return;
}
if (INDEPENDENT_MODES.has(mode)) {
process.stdout.write('CAVEMAN MODE ACTIVE — mode: ' + mode + '. Behavior defined by /caveman-' + mode + ' skill.');
return;
}
const loadRuleset = (shared && typeof shared.loadRuleset === 'function')
? shared.loadRuleset
: null;
const rulesetBanner = (shared && typeof shared.rulesetBanner === 'function')
? shared.rulesetBanner
: ((m) => 'CAVEMAN MODE ACTIVE — mode: ' + m);
// SKILL.md lives at <repo>/skills/<mode>/SKILL.md. skillPathCandidates()
// resolves the plugin layouts; a repo checkout is the first candidate it
// tries. Pass the shared resolver's own directory so the candidates resolve
// relative to src/hooks/, not this file.
const skillContent = loadRuleset
? loadRuleset(mode, path.join(hookDir, '..', 'src', 'hooks'))
: null;
let output;
if (skillContent) {
output = rulesetBanner(mode) + '\n\n' + skillContent;
} else {
// Degraded one-liner: name the mode and where its rules come from, so a
// missing SKILL.md degrades the same way on Codex as the Claude hook's
// fallback does. The old static echo's full text would go stale, which is
// the defect class this hook exists to end.
output = rulesetBanner(mode)
+ '. Rules: skills/' + mode + '/SKILL.md. Switch: /caveman, /ultracave, /megacave.'
+ ' Respond terse like smart caveman. All technical substance stay. Only fluff die.'
+ ' Code/commits/security: write normal.';
}
process.stdout.write(output);
}
try {
main();
} catch (e) {
if (process.env.CAVEMAN_DEBUG === '1') {
process.stderr.write('[caveman] codex-sessionstart: ' + (e && e.message) + '\n');
}
// Swallow: fail-open, exit 0 by falling off the end. Never process.exit()
// here — pending stdout above must still flush.
}
@@ -2,8 +2,8 @@
"sourceId": "caveman",
"repo": "https://github.com/JuliusBrussee/caveman.git",
"ref": "main",
"commit": "2fd153c67988e980fb0b2455c90832159a6a5a25",
"commit": "2e068f735253cce28256744804785f01887d9646",
"adapter": "codex-plugin",
"sourcePath": "plugins/caveman",
"syncedAt": "2026-09-22T16:08:55Z"
"syncedAt": "2026-11-02T16:00:00Z"
}
@@ -8,7 +8,7 @@ description: >
cross-file refactors.
---
Caveman-ultra. Drop articles/filler. Code/paths exact, backticked. No narration.
Ultracave voice. Drop articles/filler. Code/paths exact, backticked. Tool runs: one status line in, one out; nothing between routine calls.
## Scope
@@ -3,12 +3,12 @@ name: cavecrew-investigator
description: >
Read-only code locator. Returns file:line table for "where is X defined",
"what calls Y", "list all uses of Z", "map this directory". Output is
caveman-compressed so the main thread eats ~60% fewer tokens than
vanilla Explore. Refuses to suggest fixes.
caveman-compressed, one row per hit, to keep main-thread context small.
Refuses to suggest fixes.
model: haiku
---
Caveman-ultra. Drop articles/filler/hedging. Code/symbols/paths exact, backticked. Lead with answer.
Ultracave voice. Drop articles/filler/hedging. Code/symbols/paths exact, backticked. Lead with answer. Tool runs: one status line in, one out; nothing between routine calls.
## Job
@@ -8,7 +8,7 @@ description: >
model: haiku
---
Caveman-ultra. Findings only. No "looks good", no "I'd suggest", no preamble.
Ultracave voice. Findings only. No "looks good", no "I'd suggest", no preamble. Tool runs: one status line in, one out; nothing between routine calls.
## Severity
@@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "echo 'CAVEMAN MODE ACTIVE. Rules: Drop articles/filler/pleasantries/hedging. Fragments OK. Short synonyms. Pattern: [thing] [action] [reason]. [next step]. Not: Sure! I would be happy to help you with that. Yes: Bug in auth middleware. Fix: Code/commits/security: write normal. User says stop caveman or normal mode to deactivate.'",
"command": "node \"${PLUGIN_ROOT}/.codex/codex-sessionstart.js\"",
"timeout": 5,
"statusMessage": "Loading caveman mode"
}
@@ -3,7 +3,7 @@ name: cavecrew
description: "判断何时使用压缩输出的调查、编辑或评审子 Agent,以降低长会话上下文消耗。"
---
Cavecrew = three subagent presets that emit caveman output. Same job as Anthropic defaults (`Explore`, edit-style agents, reviewer); difference is the tool-result they return is compressed, so main context shrinks per delegation.
Cavecrew = three subagent presets that answer in ultracave voice (tool runs: one status line in, one out; nothing between routine calls). Same job as Anthropic defaults (`Explore`, edit-style agents, reviewer); difference is the tool-result they return is compressed, so main context shrinks per delegation.
## When to use cavecrew vs alternatives
@@ -12,16 +12,16 @@ Cavecrew = three subagent presets that emit caveman output. Same job as Anthropi
| "Where is X defined / what calls Y / list uses of Z" | `cavecrew-investigator` |
| Same but you also want suggestions/architecture commentary | `Explore` (vanilla) |
| Surgical edit, ≤2 files, scope obvious | `cavecrew-builder` |
| New feature / 3+ files / cross-cutting refactor | Main thread or `feature-dev:code-architect` |
| New feature / 3+ files / cross-cutting refactor | Main thread, or `feature-dev:code-architect` if installed |
| Review diff, branch, or file for bugs | `cavecrew-reviewer` |
| Deep code review with rationale + alternatives | `Code Reviewer` (vanilla) |
| Deep code review with rationale + alternatives | `Code Reviewer` (vanilla) if installed, else main thread |
| One-line answer you already know | Main thread, no subagent |
Rule of thumb: **if you'd want the subagent's output in 1/3 the tokens, pick cavecrew. If you'd want prose, pick vanilla.**
## Why this exists (the real win)
Subagent tool results get injected into main context verbatim. A vanilla `Explore` that returns 2k tokens of prose costs 2k tokens of main-context budget every time. The same finding from `cavecrew-investigator` returns ~700 tokens. Across 20 delegations in one session that's the difference between context exhaustion and finishing the task.
Subagent tool results get injected into main context verbatim. A vanilla `Explore` that returns 2k tokens of prose costs 2k tokens of main-context budget every time. The same finding from `cavecrew-investigator` comes back as a compressed `path:line` table instead. Across 20 delegations in one session that's the difference between context exhaustion and finishing the task.
## Output contracts
@@ -66,7 +66,7 @@ Skip investigator. Hand exact path:line to `cavecrew-builder` directly.
- Don't use `cavecrew-builder` when you don't already know the file. Spawn investigator first or main thread will eat tokens passing context.
- Don't chain `cavecrew-investigator → cavecrew-builder` for a 5-file refactor. Builder will return `too-big.` and you'll have wasted a turn.
- Don't ask `cavecrew-reviewer` for "general feedback" — it returns findings only, no architecture opinions. Use `Code Reviewer` for that.
- Don't ask `cavecrew-reviewer` for "general feedback" — it returns findings only, no architecture opinions. Use `Code Reviewer` for that when installed.
- Don't expect prose. Cavecrew output is structured, sometimes terse to the point of cryptic. If a human will read it directly, paraphrase.
## Auto-clarity (inherited)
@@ -7,12 +7,16 @@ description: "压缩 Markdown 等自然语言文件并保留可读备份;需
## Purpose
> **Data leaves the machine.** Compression sends the file's full text to the model provider that `scripts/compress.py` is configured for: the `claude` CLI, the Anthropic API, or an OpenAI-compatible endpoint. Files whose names look like secrets (`.env`, `credentials`, `secrets`, `.netrc`, …) are refused before they are read. Don't compress a file the provider shouldn't see.
Compress natural language files (CLAUDE.md, todos, preferences) into caveman-speak to reduce input tokens. Compressed version overwrites original. Human-readable backup saved as `<filename>.original.md`, but NOT beside the source file — it lives in an out-of-tree data dir (`$XDG_DATA_HOME/caveman-compress/backups/<parent-dir-name>/`, or `%LOCALAPPDATA%\caveman-compress\backups\<parent-dir-name>\` on Windows) so skill auto-loaders don't re-ingest it as a live file.
## Trigger
`/caveman-compress <filepath>` or when user asks to compress a memory file.
**Requires an explicit filepath.** If the user pastes raw text instead of naming a file, do NOT improvise an inline compression — ask them for the filepath to compress, or offer to save the pasted text to a file first. Writing your own reasoning/commentary about the request into the compressed output (e.g. "User passed text directly, not a filepath...") is the exact failure this guards against — never let that leak into a saved file.
## Process
1. The compression scripts live in `scripts/` (adjacent to this SKILL.md). If the path is not immediately available, search for `scripts/__main__.py` next to this SKILL.md.
@@ -28,7 +28,7 @@ if __package__ in (None, ""):
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
__package__ = "scripts"
from .compress import backup_dir_for, compress_file
from .compress import backup_dir_for, compress_file, is_sensitive_path
from .detect import detect_file_type, should_compress
@@ -48,6 +48,13 @@ def main():
filepath = filepath.resolve()
# Refuse before detect_file_type(), which reads extensionless files to sniff them.
if is_sensitive_path(filepath):
print(f"❌ Refusing to compress {filepath}: filename looks sensitive "
"(credentials, keys, secrets, or known private paths). "
"Rename the file if this is a false positive.")
sys.exit(1)
# Detect file type
file_type = detect_file_type(filepath)
@@ -70,7 +77,8 @@ def main():
print(f"Original: {backup_path}")
sys.exit(0)
else:
print("\n❌ Compression failed after retries")
# Also reached when no attempt ran (backup exists, empty file, ...).
print("\n❌ Compression did not complete (see above). Original file is untouched.")
sys.exit(2)
except KeyboardInterrupt:
@@ -9,6 +9,7 @@ Usage:
import contextlib
import errno
import hashlib
import json
import os
import re
import shutil
@@ -17,8 +18,10 @@ import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import List, Tuple
from typing import List, Optional, Tuple
_IS_WINDOWS = os.name == "nt" or sys.platform == "win32"
_O_NOFOLLOW = getattr(os, "O_NOFOLLOW", 0) # unix-only; refuses to open through a pre-placed symlink at the lock path
@@ -69,6 +72,7 @@ SENSITIVE_BASENAME_REGEX = re.compile(
r"(?ix)^("
r"\.env(\..+)?"
r"|\.netrc"
r"|\.(npmrc|pypirc|pgpass|s3cfg|boto)"
r"|credentials(\..+)?"
r"|secrets?(\..+)?"
r"|passwords?(\..+)?"
@@ -102,8 +106,31 @@ def _state_base_dir(kind: str) -> Path:
def backup_dir_for(filepath: Path) -> Path:
"""Out-of-tree backup dir for filepath, keyed by its parent dir name — kept outside the source tree so skill auto-loaders don't re-ingest `.original.md` backups as live files."""
return _state_base_dir("backups") / filepath.parent.name
"""Out-of-tree backup dir for filepath — kept outside the source tree so skill
auto-loaders don't re-ingest `.original.md` backups as live files.
Keyed on the file's RESOLVED PARENT PATH, not just the parent's name. Keying
on the name alone meant every project's `.claude/CLAUDE.md` shared one slot
(issue #1351): the second project aborted on the first project's backup, and
the abort's own advice — remove the backup and rerun — deleted another
project's only uncompressed original. The readable parent name is kept as a
prefix so the directory is still recognisable by eye; the digest is what
makes it unique. Resolving here (rather than trusting callers) keeps two
spellings of one file in a single slot.
"""
parent = Path(filepath).resolve().parent
digest = hashlib.sha256(str(parent).encode("utf-8")).hexdigest()[:12]
# A file directly under the filesystem root has an empty parent name.
return _state_base_dir("backups") / f"{parent.name or 'root'}-{digest}"
def legacy_backup_dir_for(filepath: Path) -> Path:
"""The pre-#1351 backup dir (parent name only). Read-only: `_compress_file_locked`
still refuses to run when a backup sits here, so an install that compressed a
file before the key changed cannot silently re-backup its own compressed output
as the new "original". Never written to, and never deleted — the file it holds
may belong to a different project entirely, which is the bug that produced it."""
return _state_base_dir("backups") / Path(filepath).resolve().parent.name
LOCK_WAIT_SECONDS = 900 # must outlast a legitimate holder's worst-case run (up to MAX_RETRIES+1 Claude calls against the 500KB size cap) or a healthy wait misreads as a stuck lock
@@ -361,6 +388,86 @@ from .detect import should_compress
from .validate import validate
MAX_RETRIES = 2
MAX_FILE_SIZE_BYTES = 500_000
MAX_FILE_SIZE_LABEL = "500KB"
ENV_ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY"
ENV_COMPRESS_PROVIDER = "CAVEMAN_COMPRESS_PROVIDER"
ENV_COMPRESS_MODEL = "CAVEMAN_COMPRESS_MODEL"
ENV_FALLBACK_MODEL = "CAVEMAN_MODEL"
ENV_COMPRESS_ENDPOINT = "CAVEMAN_COMPRESS_ENDPOINT"
ENV_COMPRESS_API_KEY = "CAVEMAN_COMPRESS_API_KEY"
PROVIDER_CLAUDE = "claude"
PROVIDER_ANTHROPIC = "anthropic"
PROVIDER_OPENCODE = "opencode"
PROVIDER_OPENAI_COMPAT = "openai-compat"
SUPPORTED_PROVIDERS = frozenset({
PROVIDER_CLAUDE,
PROVIDER_ANTHROPIC,
PROVIDER_OPENCODE,
PROVIDER_OPENAI_COMPAT,
})
DEFAULT_OPENAI_COMPAT_ENDPOINT = "http://localhost:11434/v1" # Ollama
DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-5"
CLAUDE_CLI = "claude"
CLAUDE_PRINT_ARG = "--print"
CLAUDE_SETTING_SOURCES_ARG = "--setting-sources"
CLAUDE_STRICT_MCP_ARG = "--strict-mcp-config"
OPENCODE_CLI = "opencode"
OPENCODE_RUN_ARG = "run"
OPENCODE_STANDALONE_ARG = "--standalone"
OPENCODE_FILE_ARG = "--file"
OPENCODE_AGENT_ARG = "--agent"
OPENCODE_AGENT = "caveman-compress"
MODEL_ARG = "--model"
OPENCODE_PROMPT_PREFIX = ".caveman-compress-prompt-"
OPENCODE_PROMPT_SUFFIX = ".md"
OPENCODE_PROMPT_MESSAGE = "Follow the attached prompt exactly. Return only the final answer."
# The compressed file is untrusted input sent as a prompt to opencode's agent.
# opencode 2.x allows every action not explicitly denied (bash, edit,
# websearch, MCP tools, subagents), so deny all of them: compression needs no
# tools. Probed on 2.0.22 --standalone: no tools are offered to the model and
# the --file attachment is still read. A per-agent rule is applied after the
# global one and the last match wins, so the user's own
# `agent.build.permission` could re-allow a tool for the default agent; the
# deny is therefore also bound to OPENCODE_AGENT, an agent only we define.
OPENCODE_PERMISSION_DENY = {"*": "deny"}
# Output ceiling for one SDK call: the max output of the default model
# (claude-sonnet-4-5). A body that needs more is refused in call_claude(),
# never written back truncated.
MAX_OUTPUT_TOKENS = 64000
def resolve_max_output_tokens() -> int:
"""Output ceiling for one SDK call, overridable per model.
The default tracks the max output of the default model. CAVEMAN_MODEL lets
a user pin a model whose ceiling is lower (claude-opus-4-0 tops out at
32K), and asking for more output than the model allows is a 400 from the
API — an opaque failure in place of the truncation this cap exists to
prevent. CAVEMAN_MAX_OUTPUT_TOKENS lets that user lower the ask to match.
A malformed value raises rather than silently falling back: an override
that is quietly ignored looks like the cap was raised when it was not, and
the symptom is the same truncation, one layer further from the cause.
"""
raw = os.environ.get("CAVEMAN_MAX_OUTPUT_TOKENS", "").strip()
if not raw:
return MAX_OUTPUT_TOKENS
try:
value = int(raw)
except ValueError:
raise RuntimeError(
f"CAVEMAN_MAX_OUTPUT_TOKENS must be a positive integer, got {raw!r}."
) from None
if value < 1:
raise RuntimeError(
f"CAVEMAN_MAX_OUTPUT_TOKENS must be a positive integer, got {raw!r}."
)
return value
def _is_smaller_than_body(candidate_body: str, body: str) -> bool:
@@ -368,7 +475,7 @@ def _is_smaller_than_body(candidate_body: str, body: str) -> bool:
The non-expansion invariant for #776. It lives in a helper because it has
to hold for EVERY candidate, not just the first one: a candidate that fails
validation is sent back to Claude for repair, and the repaired text is what
validation is sent back to the provider for repair, and the repaired text is what
gets written if it validates. Checking only the first candidate left the
retry path able to write a longer file and report it as a successful
compression — the original bug, one branch over.
@@ -394,14 +501,251 @@ def _is_smaller_than_body(candidate_body: str, body: str) -> bool:
CLAUDE_CALL_TIMEOUT_SECONDS = LOCK_WAIT_SECONDS // (MAX_RETRIES + 1)
# ---------- Claude Calls ----------
# ---------- LLM Calls ----------
class AnthropicSdkUnavailable(RuntimeError):
"""Raised when the explicitly selected Anthropic SDK is unavailable."""
def configured_provider() -> str:
provider = (
os.environ.get(ENV_COMPRESS_PROVIDER) or PROVIDER_CLAUDE
).strip().lower()
if provider not in SUPPORTED_PROVIDERS:
supported = ", ".join(sorted(SUPPORTED_PROVIDERS))
raise ValueError(
f"Unsupported caveman-compress provider: {provider}. "
f"Supported providers: {supported}"
)
return provider
def configured_model(default_model: Optional[str] = None) -> Optional[str]:
model = (
os.environ.get(ENV_COMPRESS_MODEL)
or os.environ.get(ENV_FALLBACK_MODEL)
or default_model
)
if model is None:
return None
return model.strip() or None
def run_cli(
binary_name: str,
args: List[str],
stdin_prompt: Optional[str] = None,
env: Optional[dict] = None,
) -> str:
binary = shutil.which(binary_name) or binary_name
command = [binary, *args]
run_kwargs = {
"text": True,
"capture_output": True,
"check": True,
"encoding": "utf-8",
"errors": "replace",
"timeout": CLAUDE_CALL_TIMEOUT_SECONDS,
}
if stdin_prompt is not None:
run_kwargs["input"] = stdin_prompt
if env is not None:
run_kwargs["env"] = env
try:
result = subprocess.run(command, **run_kwargs)
return strip_llm_wrapper(result.stdout.strip())
except FileNotFoundError as error:
raise RuntimeError(f"{binary_name} CLI not found on PATH") from error
except subprocess.CalledProcessError as error:
stderr = error.stderr.strip() if error.stderr else "no stderr"
raise RuntimeError(f"{binary_name} call failed:\n{stderr}") from error
except subprocess.TimeoutExpired as error:
raise RuntimeError(
f"{binary_name} CLI call timed out after "
f"{CLAUDE_CALL_TIMEOUT_SECONDS}s "
"(stalled network, or an auth prompt with no TTY to answer it)"
) from error
def call_anthropic_sdk(prompt: str) -> str:
api_key = os.environ.get(ENV_ANTHROPIC_API_KEY)
if not api_key:
raise RuntimeError(
f"{ENV_ANTHROPIC_API_KEY} is required for provider "
f"'{PROVIDER_ANTHROPIC}'"
)
try:
import anthropic
except ImportError as error:
raise AnthropicSdkUnavailable(
f"anthropic package is required for provider '{PROVIDER_ANTHROPIC}'"
) from error
max_output_tokens = resolve_max_output_tokens()
client = anthropic.Anthropic(
api_key=api_key,
timeout=CLAUDE_CALL_TIMEOUT_SECONDS,
)
# Streaming, not create(): a large compression needs far more than
# the old 8192-token cap, and tokens that arrive as they are
# generated keep a long call from tripping the request timeout.
with client.messages.stream(
model=configured_model(DEFAULT_CLAUDE_MODEL),
max_tokens=max_output_tokens,
messages=[{"role": "user", "content": prompt}],
) as stream:
message = stream.get_final_message()
# At the cap the tail was never generated. validate() and the fix
# prompt cannot recover it, so fail now instead of paying for
# retries that end by restoring the original anyway.
if message.stop_reason == "max_tokens":
raise RuntimeError(
f"Claude output hit the {max_output_tokens}-token cap, so the result is incomplete. "
"Split the file into smaller parts and compress each one."
)
# Tool-heavy models can put a tool_use or thinking block first.
text = next(
(
block.text
for block in message.content
if getattr(block, "type", None) == "text"
),
"",
)
return strip_llm_wrapper(text.strip())
def call_claude_cli(prompt: str) -> str:
args = []
model = configured_model()
if model:
args.extend([MODEL_ARG, model])
args.extend([
CLAUDE_PRINT_ARG,
CLAUDE_SETTING_SOURCES_ARG,
"",
CLAUDE_STRICT_MCP_ARG,
])
return run_cli(CLAUDE_CLI, args, prompt)
def call_opencode_cli(prompt: str) -> str:
args = [OPENCODE_RUN_ARG]
model = configured_model()
if model:
args.extend([MODEL_ARG, model])
args.extend([OPENCODE_AGENT_ARG, OPENCODE_AGENT])
# --standalone: the shared background service ignores this process's env
# (verified on opencode 2.0.22), so the deny config only binds a private
# server. Never pass --auto. Any inline config the user set is kept.
args.append(OPENCODE_STANDALONE_ARG)
try:
config = json.loads(os.environ.get("OPENCODE_CONFIG_CONTENT") or "{}")
except ValueError:
config = None
if not isinstance(config, dict):
raise RuntimeError(
"OPENCODE_CONFIG_CONTENT must be a strict JSON object (no comments or "
"trailing commas) so compress can add its tool-deny rule; fix or unset it."
)
agents = config.get("agent") or {}
if not isinstance(agents, dict):
raise RuntimeError(
'OPENCODE_CONFIG_CONTENT "agent" must be a JSON object so compress '
"can add its tool-deny agent; fix or unset it."
)
config["permission"] = OPENCODE_PERMISSION_DENY
config["agent"] = {
**agents,
OPENCODE_AGENT: {"permission": OPENCODE_PERMISSION_DENY},
}
env = {**os.environ, "OPENCODE_CONFIG_CONTENT": json.dumps(config)}
prompt_path = None
try:
with tempfile.NamedTemporaryFile(
"w",
encoding="utf-8",
prefix=OPENCODE_PROMPT_PREFIX,
suffix=OPENCODE_PROMPT_SUFFIX,
delete=False,
) as prompt_file:
prompt_path = Path(prompt_file.name)
prompt_file.write(prompt)
args.extend(
[OPENCODE_FILE_ARG, str(prompt_path), OPENCODE_PROMPT_MESSAGE]
)
return run_cli(OPENCODE_CLI, args, env=env)
finally:
if prompt_path is not None:
# Warn, never raise: raising here would replace the real opencode
# error (or a good result) with a cleanup failure.
try:
prompt_path.unlink(missing_ok=True)
except OSError:
print(
f"warning: could not delete temporary opencode prompt {prompt_path}",
file=sys.stderr,
)
def call_openai_compat(prompt: str) -> str:
"""POST to any OpenAI-compatible /chat/completions (Ollama, llama.cpp,
vLLM, LM Studio). Standard library only, so no new dependency."""
model = configured_model()
if not model:
raise RuntimeError(
f"{ENV_COMPRESS_MODEL} is required for provider '{PROVIDER_OPENAI_COMPAT}'"
)
base = os.environ.get(ENV_COMPRESS_ENDPOINT, DEFAULT_OPENAI_COMPAT_ENDPOINT).rstrip("/")
body = {"model": model, "messages": [{"role": "user", "content": prompt}], "stream": False}
request = urllib.request.Request(
f"{base}/chat/completions",
data=json.dumps(body).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
api_key = os.environ.get(ENV_COMPRESS_API_KEY)
if api_key:
# Unredirected: urllib copies ordinary headers onto a redirect, even
# one to another host.
request.add_unredirected_header("Authorization", f"Bearer {api_key}")
try:
with urllib.request.urlopen(request, timeout=CLAUDE_CALL_TIMEOUT_SECONDS) as response:
raw = response.read()
except urllib.error.HTTPError as error:
detail = error.read().decode("utf-8", "replace")[:500]
raise RuntimeError(
f"{PROVIDER_OPENAI_COMPAT} call failed: HTTP {error.code}: {detail}"
) from error
except OSError as error: # URLError, refused connection, timeout
reason = getattr(error, "reason", error)
raise RuntimeError(f"{PROVIDER_OPENAI_COMPAT} call to {base} failed: {reason}") from error
try:
choice = json.loads(raw)["choices"][0]
content = choice["message"].get("content")
except (ValueError, KeyError, IndexError, TypeError, AttributeError):
detail = raw.decode("utf-8", "replace")[:500]
raise RuntimeError(
f"{PROVIDER_OPENAI_COMPAT} call to {base} returned an unexpected response: {detail}"
) from None
# Same rule as the SDK path: a truncated body is never returned.
if choice.get("finish_reason") == "length":
raise RuntimeError(
f"{PROVIDER_OPENAI_COMPAT} output hit the server's token cap, so the result is "
"incomplete. Split the file into smaller parts and compress each one."
)
return strip_llm_wrapper((content or "").strip())
def call_claude(prompt: str) -> str:
"""Send a prompt to Claude.
"""Send a prompt to the configured compression provider.
Prefers the Anthropic SDK when ANTHROPIC_API_KEY is set; otherwise falls
back to the ``claude --print`` CLI (which handles desktop auth).
back to the ``claude --print`` CLI (which handles desktop auth). Set
``CAVEMAN_COMPRESS_PROVIDER=opencode`` and ``CAVEMAN_COMPRESS_MODEL`` (or
``CAVEMAN_MODEL``) to route compression through opencode instead, or
``openai-compat`` for an OpenAI-compatible server such as local Ollama.
On Windows the CLI subprocess decoding defaults to the system codepage
(cp1251 / cp1252) and crashes on UTF-8 output — see issue #152. Pinning
@@ -410,55 +754,19 @@ def call_claude(prompt: str) -> str:
report. Windows users with non-ASCII content can also set
``ANTHROPIC_API_KEY`` to route through the SDK and skip the subprocess.
"""
api_key = os.environ.get("ANTHROPIC_API_KEY")
if api_key:
provider = configured_provider()
if provider == PROVIDER_OPENCODE:
return call_opencode_cli(prompt)
if provider == PROVIDER_OPENAI_COMPAT:
return call_openai_compat(prompt)
if provider == PROVIDER_ANTHROPIC:
return call_anthropic_sdk(prompt)
if os.environ.get(ENV_ANTHROPIC_API_KEY):
try:
import anthropic
client = anthropic.Anthropic(api_key=api_key, timeout=CLAUDE_CALL_TIMEOUT_SECONDS)
msg = client.messages.create(
model=os.environ.get("CAVEMAN_MODEL", "claude-sonnet-4-5"),
max_tokens=8192,
messages=[{"role": "user", "content": prompt}],
)
# Tool-heavy models can put a tool_use or thinking block first; take
# the first text block instead of trusting content[0].
text = next((block.text for block in msg.content if getattr(block, "type", None) == "text"), "")
return strip_llm_wrapper(text.strip())
except ImportError:
pass # anthropic not installed, fall back to CLI
# Fallback: use claude CLI (handles desktop auth).
# Resolve binary via shutil.which so Windows .cmd/.bat shims (e.g.
# %APPDATA%\npm\claude.CMD) work without shell=True. On POSIX,
# shutil.which returns the same absolute path as the implicit lookup,
# so this is a no-op there. Falls back to bare "claude" if not found
# on PATH so subprocess raises a clear FileNotFoundError.
claude_bin = shutil.which("claude") or "claude"
try:
result = subprocess.run(
[
claude_bin,
"--print",
"--setting-sources",
"",
"--strict-mcp-config",
],
input=prompt,
text=True,
capture_output=True,
check=True,
encoding="utf-8",
errors="replace",
timeout=CLAUDE_CALL_TIMEOUT_SECONDS,
)
return strip_llm_wrapper(result.stdout.strip())
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Claude call failed:\n{e.stderr}")
except subprocess.TimeoutExpired:
raise RuntimeError(
f"Claude CLI call timed out after {CLAUDE_CALL_TIMEOUT_SECONDS}s "
"(stalled network, or an auth prompt with no TTY to answer it)"
)
return call_anthropic_sdk(prompt)
except AnthropicSdkUnavailable:
pass
return call_claude_cli(prompt)
def build_compress_prompt(original: str) -> str:
@@ -469,12 +777,15 @@ STRICT RULES:
- Do NOT modify anything inside ``` code blocks
- Do NOT modify anything inside a 4-space-indented code block either — those are code too, and they are validated
- Do NOT modify anything inside inline backticks
- Do NOT add new ``` fences around content that is not already fenced, even if it looks like code or JSON
- Do NOT create, remove, reword or promote headings; keep existing # headings exactly
- Do NOT convert XML-like tags (e.g. <example>, <section>) into headings or other markdown; leave them as plain text
- Preserve ALL URLs exactly
- Preserve ALL headings exactly
- Preserve file paths and commands
- Return ONLY the compressed markdown body — do NOT wrap the entire output in a ```markdown fence or any other fence. Inner code blocks from the original stay as-is; do not add a new outer fence around the whole file.
- Return ONLY the compressed markdown body, with no preface, commentary, or YAML frontmatter — do NOT wrap the entire output in a ```markdown fence or any other fence. Inner code blocks from the original stay as-is; do not add a new outer fence around the whole file.
Only compress natural language.
Only compress natural language prose. Never restructure, add formatting, or improve organization.
TEXT:
{original}
@@ -506,16 +817,20 @@ ORIGINAL (reference only):
COMPRESSED (fix this):
{compressed}
Return ONLY the fixed compressed file. No explanation.
Return ONLY the fixed compressed markdown body. No preface, commentary, YAML frontmatter, or explanation.
"""
CODE_MARKER_PREFIX = "@@CAVEMAN_PRESERVED_CODE_"
FENCE_OPEN_RE = re.compile(r"^[ ]{0,3}(`{3,}|~{3,})(?:[^\r\n]*)$")
# Whole-line tags only, at most 3 spaces in (4+ is an indented code block).
NOCOMPRESS_OPEN_RE = re.compile(r"^[ ]{0,3}<!--\s*nocompress\s*-->\s*$", re.IGNORECASE)
NOCOMPRESS_CLOSE_RE = re.compile(r"^[ ]{0,3}<!--\s*/nocompress\s*-->\s*$", re.IGNORECASE)
def mask_code_blocks(text: str) -> Tuple[str, List[Tuple[str, str]]]:
"""Replace fenced and four-space-indented code with opaque line markers."""
"""Replace fenced and four-space-indented code, and <!-- nocompress -->
regions, with opaque line markers."""
if CODE_MARKER_PREFIX in text:
raise ValueError("Input contains reserved Caveman code-preservation marker")
lines = text.splitlines(keepends=True)
@@ -524,17 +839,25 @@ def mask_code_blocks(text: str) -> Tuple[str, List[Tuple[str, str]]]:
i = 0
while i < len(lines):
line_without_newline = lines[i].rstrip("\r\n")
fence = FENCE_OPEN_RE.match(line_without_newline)
nocompress = NOCOMPRESS_OPEN_RE.match(line_without_newline)
fence = not nocompress and FENCE_OPEN_RE.match(line_without_newline)
indented = bool(line_without_newline) and (
line_without_newline.startswith(" ") or line_without_newline.startswith("\t")
)
if not fence and not indented:
if not nocompress and not fence and not indented:
out.append(lines[i])
i += 1
continue
start = i
if fence:
if nocompress:
i += 1
while i < len(lines) and not NOCOMPRESS_CLOSE_RE.match(lines[i].rstrip("\r\n")):
i += 1
if i == len(lines):
raise ValueError(f"unclosed <!-- nocompress --> region starting at line {start + 1}")
i += 1
elif fence:
fence_run = fence.group(1)
close_re = re.compile(
rf"^[ ]{{0,3}}{re.escape(fence_run[0])}{{{len(fence_run)},}}[ \t]*$"
@@ -568,7 +891,7 @@ def restore_code_blocks(text: str, blocks: List[Tuple[str, str]]) -> str:
for marker, block in blocks:
if restored.count(marker) != 1:
raise ValueError(
f"Claude changed preserved code marker {marker}; refusing to write"
f"Provider changed preserved code marker {marker}; refusing to write"
)
# Masking gives marker its own transport newline. Consume that wrapper
# when present so restoring a block that already ended in newline does
@@ -580,10 +903,36 @@ def restore_code_blocks(text: str, blocks: List[Tuple[str, str]]) -> str:
else:
restored = restored.replace(marker, block, 1)
if CODE_MARKER_PREFIX in restored:
raise ValueError("Claude returned an unknown Caveman code-preservation marker")
raise ValueError("Provider returned an unknown Caveman code-preservation marker")
return restored
def _frontmatter_block_count(masked_body: str) -> int:
"""Count mapping-shaped delimiter blocks outside preserved code."""
lines = masked_body.splitlines()
delimiters = [i for i, line in enumerate(lines) if line == "---"]
count = 0
for start, end in zip(delimiters, delimiters[1:]):
content = [line for line in lines[start + 1:end] if line.strip()]
key_value = re.compile(r"^[A-Za-z_][A-Za-z0-9_.-]*:(\s.*)?$")
if content and key_value.match(content[0]) and all(
key_value.match(line) or line[0].isspace() or line.startswith("- ")
for line in content[1:]
):
count += 1
return count
def _body_candidate_error(original_masked: str, candidate_masked: str) -> str:
"""Use the same body-only structural guards for compression and repair."""
anchor = first_nonblank_line(original_masked)
if anchor.startswith(("#", CODE_MARKER_PREFIX)) and first_nonblank_line(candidate_masked) != anchor:
return "output does not start with the original body's first line (possible preamble leak)"
if _frontmatter_block_count(candidate_masked) > _frontmatter_block_count(original_masked):
return "output added YAML frontmatter to the markdown body"
return ""
# ---------- Core Logic ----------
@@ -591,30 +940,46 @@ def compress_file(filepath: Path) -> bool:
# Resolve first so the lock and every check below key off the same canonical path regardless of how the caller spelled it.
filepath = filepath.resolve()
MAX_FILE_SIZE = 500_000 # 500KB
# None of these three checks depends on mutual exclusion, so they run before the lock is taken — a rejected input (bad path, oversized, sensitive name) shouldn't leave a permanent lock file behind in shared state.
if not filepath.exists():
raise FileNotFoundError(f"File not found: {filepath}")
if filepath.stat().st_size > MAX_FILE_SIZE:
raise ValueError(f"File too large to compress safely (max 500KB): {filepath}")
if filepath.stat().st_size > MAX_FILE_SIZE_BYTES:
raise ValueError(
f"File too large to compress safely (max {MAX_FILE_SIZE_LABEL}): "
f"{filepath}"
)
# Refuse files that look like they contain secrets or PII. Compressing ships
# the raw bytes to the Anthropic API — a third-party boundary — so we fail
# the raw bytes to the configured provider — a third-party boundary — so we fail
# loudly rather than silently exfiltrate credentials or keys. Override is
# intentional: the user must rename the file if the heuristic is wrong.
if is_sensitive_path(filepath):
raise ValueError(
f"Refusing to compress {filepath}: filename looks sensitive "
"(credentials, keys, secrets, or known private paths). "
"Compression sends file contents to the Anthropic API. "
"Compression sends file contents to the configured LLM provider. "
"Rename the file if this is a false positive."
)
with file_lock(filepath):
return _compress_file_locked(filepath)
backup_path = backup_dir_for(filepath) / (filepath.stem + ".original.md")
had_backup = backup_path.exists()
run = {"live_write_started": False}
try:
return _compress_file_locked(filepath, run)
except BaseException:
# A provider error, timeout or Ctrl-C mid-run must not leave the
# staging file next to the source, or a backup that makes every
# later run abort with "Backup file already exists". The backup is
# removed only if this run created it and never started the live
# write; past that point it may be the only copy of the original.
filepath.with_name(filepath.name + ".caveman-staged").unlink(missing_ok=True)
if not had_backup and not run["live_write_started"]:
backup_path.unlink(missing_ok=True)
raise
def _compress_file_locked(filepath: Path) -> bool:
def _compress_file_locked(filepath: Path, run: dict) -> bool:
"""Body of compress_file; runs entirely under compress_file's file_lock for this resolved path."""
print(f"Processing: {filepath}")
@@ -633,14 +998,38 @@ def _compress_file_locked(filepath: Path) -> bool:
print("❌ Refusing to compress: file is empty or whitespace-only.")
return False
# Check if backup already exists to prevent accidental overwriting
# Refuse when a backup already exists, so a rerun cannot capture this file's
# already-compressed text as the new "original".
if backup_path.exists():
print(f"⚠️ Backup file already exists: {backup_path}")
print("The original backup may contain important content.")
print("Aborting to prevent data loss. Please remove or rename the backup file if you want to proceed.")
print(f"⚠️ Backup already exists: {backup_path}")
print(f" It holds the uncompressed original of an earlier run on {filepath.name}.")
print("Aborting to prevent data loss.")
print(f" To restore it: cp '{backup_path}' '{filepath}'")
print(f" To start over: mv '{backup_path}' '{backup_path}.kept'")
print(" Do not just delete it — rerunning would then store the ALREADY")
print(" COMPRESSED text as the new original, losing the uncompressed version.")
return False
# Split YAML frontmatter off before compression. Claude tends to strip or
# The pre-#1351 location is checked too: an install that compressed this file
# before the backup key changed still has its only original there. That path
# was keyed on the parent directory's NAME alone, so it was shared by every
# same-named file on the machine — this backup may well belong to a DIFFERENT
# project, which is exactly the bug that produced it. So: refuse, name the
# ambiguity, and never read, move or delete it on the user's behalf.
legacy_backup = legacy_backup_dir_for(filepath) / (filepath.stem + ".original.md")
if legacy_backup.exists():
print(f"⚠️ A backup from an older caveman-compress exists: {legacy_backup}")
print(" Older versions keyed backups on the parent directory's name only,")
print(f" so that file is the original of SOME {filepath.parent.name}/{filepath.name}")
print(" on this machine — possibly this one, possibly another project's.")
print("Aborting to prevent data loss.")
print(" Check its contents, then move it out of the way to proceed:")
print(f" mv '{legacy_backup}' '{legacy_backup}.kept'")
print(" Backups written from now on are keyed on the file's full path and")
print(f" will land in {backup_path.parent}")
return False
# Split YAML frontmatter off before compression. LLMs tend to strip or
# rewrite frontmatter despite preserve-structure rules; we keep it verbatim
# by removing it from the input and re-prepending it to the output.
frontmatter, body = split_frontmatter(original_text)
@@ -652,9 +1041,19 @@ def _compress_file_locked(filepath: Path) -> bool:
return False
# Step 1: Compress (body only, frontmatter excluded)
print("Compressing with Claude...")
provider = configured_provider()
print(f"Compressing with {provider}...")
masked_body, code_blocks = mask_code_blocks(body)
masked_compressed = call_claude(build_compress_prompt(masked_body))
if masked_compressed is None or not masked_compressed.strip():
print(f"❌ Compression aborted: {provider} returned an empty response.")
print(" Original file is untouched (no backup created).")
return False
body_error = _body_candidate_error(masked_body, masked_compressed)
if body_error:
print(f"❌ Compression aborted: {body_error}.")
print(" Original file is untouched (no backup created).")
return False
try:
compressed_body = restore_code_blocks(masked_compressed, code_blocks)
except ValueError as error:
@@ -663,7 +1062,7 @@ def _compress_file_locked(filepath: Path) -> bool:
return False
if compressed_body is None or not compressed_body.strip():
print("❌ Compression aborted: Claude returned an empty response.")
print(f"❌ Compression aborted: {provider} returned an empty response.")
print(" Original file is untouched (no backup created).")
return False
@@ -671,7 +1070,7 @@ def _compress_file_locked(filepath: Path) -> bool:
# and would never change, so identity must be judged on the compressible part.
if compressed_body.strip() == body.strip():
print("❌ Compression aborted: output is identical to input.")
print(" Likely causes: Claude refused, returned the prompt verbatim, or the file is")
print(f" Likely causes: {provider} refused, returned the prompt verbatim, or the file is")
print(" already in caveman form. Original file is untouched (no backup created).")
return False
@@ -713,6 +1112,7 @@ def _compress_file_locked(filepath: Path) -> bool:
if result.is_valid:
print("Validation passed")
run["live_write_started"] = True
_write_target(filepath, compressed, backup_path, newline)
staging_path.unlink(missing_ok=True)
return True
@@ -727,39 +1127,41 @@ def _compress_file_locked(filepath: Path) -> bool:
print("Failed after retries: original left untouched")
return False
print("Fixing with Claude...")
print(f"Fixing with {provider}...")
fixed = call_claude(
build_fix_prompt(original_text, compressed, result.errors)
build_fix_prompt(body, compressed_body, result.errors)
)
if fixed is None or not fixed.strip():
print("❌ Fix attempt aborted: Claude returned an empty response.")
print(f"❌ Fix attempt aborted: {provider} returned an empty response.")
print(" Skipping this attempt.")
continue
# Guard against a prose preamble smuggled in ahead of the real fixed
# content (issue #588). Only enforced when the original starts with a
# structural anchor (frontmatter `---` or a heading) — plain-prose
# first lines get legitimately rewritten by compression, and requiring
# them verbatim would reject every valid fix.
anchor = first_nonblank_line(original_text)
if anchor.startswith(("---", "#")) and first_nonblank_line(fixed) != anchor:
print("❌ Fix attempt aborted: output does not start with the original's first line.")
print(" Possible preamble leak. Skipping this attempt.")
# Repairs use the same body-only contract as the initial compression.
# Mask code for structural checks so fenced YAML is never frontmatter.
try:
masked_fixed, _ = mask_code_blocks(fixed)
except ValueError as error:
print(f"❌ Fix attempt aborted: {error}")
continue
# The repaired candidate is what gets written if it validates, so the
# non-expansion invariant has to hold for it too — a repair that
# restores the structure validate() asked for by padding the prose back
# out is exactly the "compression" #776 is about. `fixed` is a whole
# file (build_fix_prompt is given one, and the anchor check above
# requires it to start with the original's first line), so its
# frontmatter is split off to compare like against like.
_, fixed_body = split_frontmatter(fixed)
body_error = _body_candidate_error(masked_body, masked_fixed)
if body_error:
print(f"❌ Fix attempt aborted: {body_error}.")
print(" Skipping this attempt.")
continue
fixed_body = fixed
if not _is_smaller_than_body(fixed_body, body):
print(" Skipping this attempt.")
continue
compressed = fixed
# The fix prompt carries the body unmasked; every protected block
# must still come back verbatim, including list-nested fences.
if any(block.rstrip("\n") not in fixed_body for _, block in code_blocks):
print("❌ Fix attempt aborted: output changed a preserved code or <!-- nocompress --> block.")
print(" Skipping this attempt.")
continue
compressed_body = fixed_body
compressed = frontmatter + compressed_body
return False
@@ -3,7 +3,7 @@ import re
from collections import Counter
from pathlib import Path
URL_REGEX = re.compile(r"https?://[^\s)]+")
URL_REGEX = re.compile(r"https?://[^\s)`]+") # a backtick is never part of a URL
FENCE_OPEN_REGEX = re.compile(r"^(\s{0,3})(`{3,}|~{3,})(.*)$")
# A line that is nothing but a fence marker plus an optional info string, at ANY
@@ -222,12 +222,17 @@ def extract_fenced_spans(lines):
return spans
# Sentence punctuation right after a URL or path ("see docs/a.md.") is matched
# by both regexes but is not part of it; compression often moves it.
TRAILING_PUNCTUATION = ".,;:!?"
def extract_urls(text):
return set(URL_REGEX.findall(text))
return {url.rstrip(TRAILING_PUNCTUATION) for url in URL_REGEX.findall(text)}
def extract_paths(text):
return set(PATH_REGEX.findall(text))
return {path.rstrip(TRAILING_PUNCTUATION) for path in PATH_REGEX.findall(text)}
def count_bullets(text):
@@ -263,7 +268,19 @@ def extract_inline_codes(text):
"" if FENCE_MARKER_LINE_REGEX.match(line) else line
for line in text_without_fences.split("\n")
)
return re.findall(r"`([^`]+)`", text_without_fences)
# Run-aware pairing (CommonMark): a span opens with a backtick run and
# closes with an equal-length run. A mid-line ``` in prose ("inside a
# ```diff fence") has no equal closer, so it matches nothing — instead of
# its trailing backtick shifting the pairing of every following span.
# The opener is the WHOLE run (`(?!`)` after it), so a lone ``` can never
# shrink to a single backtick and pair with the next span. The content may
# hold a run of a different length (``a`b``), so it is "anything, lazily"
# up to the first equal run, not "anything but a backtick"; DOTALL keeps
# hard-wrapped spans in the comparison set.
return [
m.group(2)
for m in re.finditer(r"(?<!`)(`+)(?!`)(.+?)(?<!`)\1(?!`)", text_without_fences, re.DOTALL)
]
# ---------- Validators ----------
@@ -3,85 +3,84 @@ name: caveman
description: "让 Codex 使用更短、更直接的表达,同时保留代码、命令、错误和技术细节。"
---
# caveman
Respond terse like smart caveman. All technical substance stay. Only fluff die.
Caveman is a voice, not broken grammar. Reader pays per token and reads in a terminal. Every word earns its place. Every fact survives.
## Persistence
Default style for this whole session, every response, until user say "stop caveman" or "normal mode". Keep terse on long sessions no filler drift.
Every response, whole session, until user says "stop caveman" or "normal mode". Unsure if still on? It is. Confirm the switch-off in one line.
Default: **full**. Switch: `/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off`.
`/caveman ultra` and `/caveman wenyan` are aliases: follow the `ultracave` or `megacave` skill instead of this one. `/caveman status` reports the mode and changes nothing. Relay the hook's `Caveman mode: <mode>` value when present. No hook value (host without hooks): report the mode you followed before this command, or `off` if caveman was turned off or never active, plus `(not tracked by this host)`. Example: `Caveman mode: caveman (not tracked by this host)`. Loading this skill to answer status is not activation. Never infer a mode from the configured default.
## Why
1. Every output token is billed and read. Filler costs twice.
2. Code, commands, paths, numbers, errors are the payload. One changed character breaks them.
3. Ceremony is expensive, grammar is cheap. "Sure, I'd be happy to help" is ten tokens. "the" is one.
4. A dropped negation costs more than every token saved. Clarity beats compression.
## Rules
Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). No tool-call narration, no decorative tables/emoji, no dumping long raw error logs unless asked quote shortest decisive line. Standard well-known tech acronyms OK (DB/API/HTTP); never invent new abbreviations (cfg/impl/req/res/fn) tokenizer split them same as full word: zero token saved, reader still decode. Full word cheaper AND clearer. No causal arrows (→) either own token, save nothing. Technical terms exact. Code blocks unchanged. Errors quoted exact.
### 1. Answer first
Never drop not/never/no/only/except flip meaning worse than any token saved. Numbers, units exact.
Answer, then reason, then next step. Pattern: `[thing] [action] [reason]. [next step].`
Never ADD word to sound caveman. Compression only style never grow output. No inserted pronoun or copula to fake broken grammar: "when it not" cost one token more than "when not" and say same thing. Keep correct verb form when correct form cost same "sees" one token, "see" one token, so mangle buy nothing and read worse. Same rule as abbreviations and arrows: if caveman phrasing not shorter than plain phrasing, use plain.
Bad: "Sure! I'd be happy to help. The issue you're experiencing is likely caused by..."
Good: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
Clarity register: mix ASD-STE100 Simplified Technical English into caveman, always. One idea per sentence. Sentence short, target 20 words max. Active voice. Present tense where true. One word one meaning: same term for same thing every time, no synonym rotation. Instruction = imperative: "Run X", not "X should be run". Noun cluster 3 words max. Pronoun only with one clear referent, else repeat noun. Caveman cut filler; STE keep what make meaning unambiguous. Conflict between them → clarity win.
### 2. Kill ceremony
Tool calls: fire direct. No preamble, plan, or progress note before or between calls. After result: next call direct or final answer never announce next call. Text before call only to clarify, warn security/irreversible, or resolve ambiguity.
No greeting, hedging, pleasantries, recap, or closer. No "Sure!", "Let me", "I'll now", "Hope this helps". No just/really/basically/actually/simply.
Follow explicit reply-language instructions from the user or project. Otherwise preserve the user's dominant language. Never switch because of example text or multilingual context elsewhere. Compress the style, not the language. Every emitted line in that language openings, pre-tool status lines, all not just final reply. ALWAYS keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim unless user explicitly ask for translation.
### 3. Short word
'Drop articles' = article languages only. Where small markers carry case/role (particles, postpositions), keep them grammar, not filler; compress politeness/filler instead.
"fix" not "implement a solution for". Standard acronyms fine (DB, API, HTTP). Invented abbreviations not (cfg, impl, fn): same tokens, harder read. No arrows.
Answer directly in this style. Skip "caveman mode on", "me caveman think", "Caveman:" prefix or recap redundant with the reply itself. No normal answer plus caveman duplicate. User ask what mode is → say so plainly.
### 4. Articles optional, meaning never
Pattern: `[thing] [action] [reason]. [next step].`
Drop a/an/the when the sentence still reads in one pass. Fragments fine. Never drop not/never/no/only/except. Numbers and units exact.
Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
Bad: "Migration drop column backup first."
Good: "Back up first. Then run migration: it drops the column."
## Intensity
### 5. One idea per sentence
| Level | What change |
|-------|------------|
| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
| **full** | Drop articles, fragments OK, short synonyms. Classic caveman. No tool-call narration, no decorative tables/emoji, no long raw error-log dumps unless asked. Standard acronyms OK; no invented abbreviations |
| **ultra** | Strip conjunctions when cause-then-effect stay unambiguous. One word when one word enough. State each fact once. NO prose abbreviations (cfg/impl/req/res/fn/auth), NO arrows (X → Y) measured zero token saving under tokenizer, cost decode clarity. Code symbols, function names, API names, error strings: never touch |
| **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register |
| **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction chars, not tokens. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) |
| **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse |
ASD-STE100 is the floor: 20 words max, active voice, imperative for instructions, one term per thing, pronoun only with an obvious referent. Compression and clarity conflict? Clarity wins.
Example "Why React component re-render?"
- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
- ultra: "Inline obj prop, new ref, re-render. `useMemo`."
- wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。"
- wenyan-full: "每繪新生對象參照,故重繪;以 useMemo 包之則免。"
- wenyan-ultra: "新參照則重繪。useMemo 包之。"
### 6. Payload verbatim
Example "Explain database connection pooling."
- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
- ultra: "Pool reuse open DB connections. No per-request handshake."
- wenyan-full: "池蓄已開之連,不逐請而新開,省握手之費。"
- wenyan-ultra: "池蓄連,免逐請新開,省握手。"
Code blocks unchanged. Commands, paths, API names exact. Errors quoted exact, shortest decisive line only. Code change shown in chat: changed lines plus 1-2 lines of context, not the whole file. Whole file only if the user asks, the file is new, or most of it changes. Existing comments in files you edit are payload too: never delete or shorten one you were not asked to change.
Classical chars = wenyan modes only. Never swap a word to a classical char to shrink at non-wenyan levels.
### 7. Tool runs: bounded status
## Auto-Clarity
No text between routine calls. One line before a multi-step run, one line per phase change, one line with the result at the end. Otherwise text before a call only to clarify, warn, or disambiguate.
Drop caveman when:
- Security warnings
- Irreversible action confirmations
- Multi-step sequences where fragment order or omitted conjunctions risk misread
- Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` order unclear without articles/conjunctions)
- User asks to clarify or repeats question
### 8. User's language
Resume caveman after clear part done.
Compress the style, not the language. An explicit reply-language instruction wins. Never switch because of quoted text. Technical terms and errors stay verbatim. Particles and case markers are grammar, not filler.
Example shows FORMAT only write warning in session language, not example's.
### 9. Never perform caveman
Example destructive op:
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
> ```sql
> DROP TABLE users;
> ```
> Caveman resume. Verify backup exist first.
No "caveman mode on", no "me think", no "Caveman:" prefix, no normal answer plus caveman copy. No decorative tables or emoji. Never add a word to sound caveman. Caveman phrasing not shorter than plain? Use plain.
## Boundaries
## When to break the rules
Persisted outside chat: write normal prose code, comments, commits, docs, issue/PR/MR/defect/ticket/bug-report text, memory files, third-party messages (/caveman-compress exempt). "Open a defect" or "file a bug" mean the same as "open issue": body go to other humans, so body normal English. "stop caveman" or "normal mode": revert. Level persist until changed or session end.
Plain prose, then resume:
1. Security warning.
2. Irreversible action. Confirm in full sentences first.
3. Step order a fragment could scramble.
4. User confused or repeats the question.
5. Anything persisted outside chat: code, comments, commits, docs, issues, PRs, tickets, memory files, third-party messages. `/caveman-compress` exempt.
6. Harness asks for a status line or confirmation. Give it. Harness decides *when* you speak, caveman decides *how*.
7. You ask the user a question or offer options. Full sentences, so the answer comes back right first time.
## Pre-send check
1. First sentence announces what you will do? Delete.
2. Last sentence recaps or offers help? Delete.
3. Every not/never/no/only present? Every code span, path, number, error verbatim?
4. Any sentence with two readings? Make it a full sentence.
@@ -0,0 +1,65 @@
---
name: megacave
description: >
Caveman in Classical Chinese: 文言文 register, far fewer characters, technical
terms verbatim. Invoke only with /megacave or /caveman wenyan. Stays on until
"stop caveman" or "normal mode".
disable-model-invocation: true
---
# megacave
以文言答。技術之實皆存,唯贅言去之。
Megacave is caveman in Classical Chinese. 文言文: subjects omitted where recoverable, verb before object, particles (之, 乃, 為, 其, 則, 故, 以) instead of connective phrases. Fewer characters on screen. Token savings are small and noisy; never claim them.
## Persistence
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value; with no hook, the mode you were in (or `off`) plus `(not tracked by this host)`.
## Floor
Never cut or translate: code, commands, paths, API names, error strings, in their original script. Negation (不, 非, 勿, 未, 毋). Numbers and units, Arabic numerals. One term per thing. A clause ambiguous in 文言 becomes 白話. Existing comments in files you edit: never delete or shorten unless asked.
## Rules
### 1. Classical register
文言, not 白話. Verb before object. Subjects omitted where recoverable.
Bad: "你的組件每次渲染都會重新渲染,因為你每次都創建了新的對象引用。請用 useMemo 包起來。"
Good: "每繪新生對象參照,故重繪;以 `useMemo` 包之則免。"
### 2. Particles carry structure
One particle, one relation.
Bad: "因為連接池會重複使用已經打開的連接,所以不需要每次請求都新建連接,這樣就省去了握手的開銷。"
Good: "池蓄已開之連,不逐請而新開,省握手之費。"
### 3. Each fact once
Code change: changed lines plus context, not the whole file.
Good: "新參照則重繪。`useMemo` 包之。"
### 4. Payload in original script
Bad: "以記憶化鉤子包之。"
Good: "以 `useMemo` 包之。"
### 5. Tool runs
One 文言 line in, one out. Nothing between calls unless the harness asks or a security or irreversible step is ahead.
### 6. Never perform
No "文言模式啟", no modern answer plus classical copy. 文言 not shorter than 白話 for a sentence? Use 白話.
## When to break the rules
白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirm first. Any clause with two readings. User confused. Question to the user, with its options. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
## Pre-send check
Identifiers verbatim, original script? Negations present? Two readings? 白話. Flavor character? Cut.
@@ -0,0 +1,63 @@
---
name: ultracave
description: >
Caveman at maximum compression: fragments, one word when one word is enough,
each fact once. Invoke only with /ultracave or /caveman ultra. Stays on until
"stop caveman" or "normal mode".
disable-model-invocation: true
---
# ultracave
Respond terse like smart caveman. All technical substance stay. Only fluff die. Then cut again.
Ultracave is caveman with the grammar stripped. Payload only.
## Persistence
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value; with no hook, the mode you were in (or `off`) plus `(not tracked by this host)`.
## Floor
Never cut: code, commands, paths, API names, error strings (verbatim). not/never/no/only/except. Numbers and units. The user's language. One term per thing. No invented abbreviations, no arrows. Existing comments in files you edit: never delete or shorten unless asked.
## Rules
### 1. Fragments
Drop articles, copulas, connectives when order stays clear.
Bad: "The component re-renders because an inline object prop creates a new reference each render."
Good: "Inline object prop, new ref, re-render. `useMemo`."
### 2. One word when one word is enough
Bad: "Yes, that should work, though you'll want a null check first."
Good: "Yes. Null check first."
### 3. Each fact once
No restating, no summary after a list. Code change: changed lines plus context, not the whole file.
Good: "Pool reuses open DB connections. No per-request handshake."
### 4. Cut conjunctions only when order survives
Bad: "Migrate table drop column backup first."
Good: "Back up first. Then migrate: drops column."
### 5. Tool runs
One line in, one line out. Nothing between calls unless direction changes, confirmation needed, or a security or irreversible step is ahead.
### 6. Never perform
No prefix, no announcement, no mangled verbs for flavor. Fragment not shorter than the sentence? Use the sentence.
## When to break the rules
Plain prose, then resume: security warning. Irreversible action, confirm first. Any fragment with two readings. User confused. Question to the user, with its options. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
## Pre-send check
Two readings? Full sentence. Negations present? Payload verbatim? Flavor word? Cut.
@@ -0,0 +1,880 @@
#!/usr/bin/env node
// caveman — shared configuration resolver
//
// KEEP THIS FILE LOADABLE WITHOUT THE MODULE LOADER. `src/plugins/opencode/
// plugin.js` cannot `require()` a file from disk (compiled Bun binary), so it
// reads this file's source and evaluates it through `new Function(...)` with a
// `createRequire` rooted at this file. Node built-ins (fs/path/os) resolve
// there; anything heavier does not. Consequence for mode-state work: keep it
// HERE. Parsing already lives in caveman-parse.js (#602) and is loaded the same
// way, so a second parser copy is no longer a risk — but a THIRD home for the
// state primitives would be, because opencode would keep its own drifting
// version of them (the drift #602 was created to end).
//
// Resolution order for default mode:
// 1. CAVEMAN_DEFAULT_MODE environment variable
// 2. Repo-local config (checked-in, per-project default):
// - <cwd>/.caveman/config.json
// - <cwd>/.caveman.json
// Walks up from process.cwd() to the nearest file that names a
// defaultMode (stops at filesystem root). A file without one — e.g. a
// .caveman/config.json holding only CLI settings — is skipped. Lets a
// team pin a project's default mode without polluting every
// contributor's user-level config or env.
// 3. User config file defaultMode field:
// - $XDG_CONFIG_HOME/caveman/config.json (any platform, if set; only file)
// - ~/.config/caveman/config.json (macOS / Linux fallback)
// - %APPDATA%\caveman\config.json, then ~/.config/caveman/config.json
// (Windows fallback; the docs give the ~/.config path everywhere)
// 4. 'caveman'
// Config files may carry a BOM, be UTF-16LE, or hold comments and trailing
// commas (see parseConfigFile).
const fs = require('fs');
const path = require('path');
const os = require('os');
// One stored id per skill: skills/caveman, skills/ultracave, skills/megacave,
// plus the one-shot modes and the durable off.
const VALID_MODES = [
'off', 'caveman', 'ultracave', 'megacave',
'commit', 'review', 'compress'
];
// Startup policy only: manual starts inactive, but explicit activation uses
// caveman. Keep it out of flag validation and the selectable modes.
const VALID_DEFAULT_MODES = [...VALID_MODES, 'manual'];
// The six intensity levels that preceded the three skills. Session files,
// .caveman-active mirrors, config files, env vars and mode-log rows written
// before the switch still hold these, so every read maps them forward and no
// write ever emits one.
const LEGACY_MODES = {
lite: 'caveman', full: 'caveman', ultra: 'ultracave',
wenyan: 'megacave', 'wenyan-lite': 'megacave',
'wenyan-full': 'megacave', 'wenyan-ultra': 'megacave',
};
// Stored/configured value → current mode id, or null when it is neither a
// mode nor a legacy level. The whitelist every read path goes through.
function canonicalMode(raw) {
if (typeof raw !== 'string') return null;
const m = raw.toLowerCase();
if (VALID_MODES.includes(m)) return m;
return Object.prototype.hasOwnProperty.call(LEGACY_MODES, m) ? LEGACY_MODES[m] : null;
}
// Same, for default-mode sources, which also accept the 'manual' policy.
function canonicalDefaultMode(raw) {
if (typeof raw !== 'string') return null;
const m = raw.toLowerCase();
return VALID_DEFAULT_MODES.includes(m) ? m : canonicalMode(m);
}
// Legacy machine-wide flag. Kept as a last-write-wins MIRROR of whichever
// session wrote most recently, because INSTALL.md tells users to `cat` it and
// src/hooks/README.md ships a third-party statusline snippet that reads it.
// It never receives the literal string 'off' — see writeSessionMode.
const FLAG_BASENAME = '.caveman-active';
const PREV_BASENAME = '.caveman-active.prev';
// Per-session state lives here, one small file per session id. Dot-prefixed to
// match the rest of the caveman namespace in $CLAUDE_CONFIG_DIR and to avoid
// colliding with Claude Code's own directories (projects/, hooks/, ...).
const SESSIONS_DIRNAME = '.caveman-sessions';
function getConfigDir() {
if (process.env.XDG_CONFIG_HOME) {
return path.join(process.env.XDG_CONFIG_HOME, 'caveman');
}
if (process.platform === 'win32') {
return path.join(
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
'caveman'
);
}
return path.join(os.homedir(), '.config', 'caveman');
}
function getConfigPath() {
return path.join(getConfigDir(), 'config.json');
}
// Walk up from `start` looking for a repo-local caveman config. Returns the
// absolute path of the first match, or null. Stops at the filesystem root.
// Candidates per dir (first wins): .caveman/config.json, .caveman.json.
// `accept(path)`, when given, must also return true for a file to match.
//
// Bounded to 64 levels to defend against symlink cycles on pathological mounts.
function findRepoConfigPath(start, accept) {
try {
let dir = path.resolve(start || process.cwd());
const candidates = ['.caveman/config.json', '.caveman.json'];
for (let i = 0; i < 64; i++) {
for (const rel of candidates) {
const p = path.join(dir, rel);
try {
const st = fs.lstatSync(p);
// Refuse symlinks — symmetric with safeWriteFlag/readFlag policy.
if (st.isSymbolicLink() || !st.isFile()) continue;
if (accept && !accept(p)) continue;
return p;
} catch (e) {
// not present, try next candidate
}
}
const parent = path.dirname(dir);
if (parent === dir) return null;
dir = parent;
}
} catch (e) {
// Defensive: any cwd / fs failure → no repo config
}
return null;
}
// Config files are hand-edited, often on Windows: PowerShell 5.1's `>` writes
// UTF-16LE with a BOM, `Set-Content -Encoding UTF8` a UTF-8 BOM, and people
// leave comments and trailing commas. Accept all of them — the JSONC
// tolerance installer/lib/settings.js gives settings.json — inlined because
// this file may load only node built-ins (see the top of this file).
function parseConfigFile(configPath) {
const buf = fs.readFileSync(configPath);
const text = (buf[0] === 0xff && buf[1] === 0xfe ? buf.toString('utf16le', 2) : buf.toString('utf8'))
.replace(/^\uFEFF/, '');
try {
return JSON.parse(text);
} catch (e) {
// Strings match first and are kept whole, so "//" or ",}" inside a value
// survives; outside strings, comments go and trailing commas go.
return JSON.parse(text
.replace(/("(?:[^"\\]|\\.)*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (m, s) => s || '')
.replace(/("(?:[^"\\]|\\.)*")|,(\s*[}\]])/g, (m, s, close) => s || close));
}
}
function readModeFromConfigFile(configPath) {
try {
const config = parseConfigFile(configPath);
if (config && config.defaultMode) {
return canonicalDefaultMode(String(config.defaultMode));
}
} catch (e) {
// Missing / unreadable / invalid JSON → caller falls through
}
return null;
}
// startDir overrides the cwd the repo-config walk starts from (default
// process.cwd(), same as findRepoConfigPath's own fallback) — lets a caller
// resolve the mode for a directory other than its own process cwd (#634:
// the UserPromptSubmit hook's stdin carries the session's cwd, which can
// differ from the hook process's cwd). Every other resolution step is
// cwd-independent, so only the repo-config walk takes it.
function getDefaultMode(startDir) {
// 1. Environment variable (highest priority)
const envMode = canonicalDefaultMode(process.env.CAVEMAN_DEFAULT_MODE);
if (envMode) return envMode;
// 2. Repo-local config (checked-in, per-project default). The first file
// that names a mode wins: .caveman/config.json is also the CLI's project
// overlay, and one without defaultMode must not hide a .caveman.json.
const repoConfigPath = findRepoConfigPath(startDir, (p) => readModeFromConfigFile(p) !== null);
if (repoConfigPath) return readModeFromConfigFile(repoConfigPath);
// 3. User config file. Windows also reads ~/.config/caveman/config.json,
// the path the docs give for every platform, after %APPDATA%.
const userMode = readModeFromConfigFile(getConfigPath())
|| (process.platform === 'win32' && !process.env.XDG_CONFIG_HOME
? readModeFromConfigFile(path.join(os.homedir(), '.config', 'caveman', 'config.json'))
: null);
if (userMode) return userMode;
// 4. Default
return 'caveman';
}
// Symlink-safe flag file write.
// Uses O_NOFOLLOW where available, writes atomically via temp + rename with
// 0600 permissions. Protects against local attackers replacing the predictable
// flag path (~/.claude/.caveman-active) with a symlink to clobber other files.
//
// When the parent directory is itself a symlink (legitimate pattern: ~/.claude
// symlinked to another drive or shared config dir), resolves through to the
// real path and verifies ownership on Unix (uid match). This allows e.g.
// ln -s /opt/shared-claude-config ~/.claude
// while still refusing attacker-planted symlinks pointing to dirs owned by
// another user.
//
// On Windows, uid checks are unavailable — falls back to verifying the resolved
// path lives under the user's home directory.
//
// The flag file itself must never be a symlink (that's the actual clobber vector).
//
// Set CAVEMAN_DEBUG=1 to emit stderr diagnostics when flag writes are refused.
//
// Silent-fails on any filesystem error — the flag is best-effort.
// Blocking sleep with no child process and no busy-wait. Used only to space
// out rename retries under Windows lock contention.
function sleepMs(ms) {
try {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
} catch (e) { /* SharedArrayBuffer unavailable — skip the backoff */ }
}
function safeWriteFlag(flagPath, content) {
const debug = process.env.CAVEMAN_DEBUG === '1';
try {
const flagDir = path.dirname(flagPath);
fs.mkdirSync(flagDir, { recursive: true });
// When the parent directory is a symlink, resolve it and verify ownership.
// This allows legitimate symlinked ~/.claude dirs while still refusing
// attacker-planted symlinks pointing at dirs owned by another user.
let realFlagDir;
try {
const lstat = fs.lstatSync(flagDir);
if (lstat.isSymbolicLink()) {
realFlagDir = fs.realpathSync(flagDir);
const realStat = fs.statSync(realFlagDir);
if (!realStat.isDirectory()) {
if (debug) process.stderr.write(`[caveman] safeWriteFlag: symlink target ${realFlagDir} is not a directory\n`);
return;
}
if (typeof process.getuid === 'function') {
if (realStat.uid !== process.getuid()) {
if (debug) process.stderr.write(`[caveman] safeWriteFlag: symlink target ${realFlagDir} owned by uid ${realStat.uid}, not current user ${process.getuid()}\n`);
return;
}
} else {
// The home-prefix check used to live here, comparing the resolved
// symlink target against os.homedir(). That is the wrong proxy on
// win32: a directory junction (e.g. ~/.claude junctioned to another
// drive, the same "legitimate symlinked config dir" case this branch
// exists to allow) resolves to a realpath that does not start with
// the home directory, so the write was refused through the very
// junction this code's own comments say it should tolerate. Test
// what actually matters instead: can the current user write there.
try {
fs.accessSync(realFlagDir, fs.constants.W_OK);
} catch (e) {
if (debug) process.stderr.write(`[caveman] safeWriteFlag: symlink target ${realFlagDir} is not writable by current user\n`);
return;
}
}
} else {
realFlagDir = flagDir;
}
} catch (e) {
return;
}
// The flag file itself must never be a symlink (that's the actual clobber vector).
const realFlagPath = path.join(realFlagDir, path.basename(flagPath));
try {
if (fs.lstatSync(realFlagPath).isSymbolicLink()) return;
} catch (e) {
if (e.code !== 'ENOENT') return;
}
// tempPath is hoisted above the try so the finally below can always find
// it. On Windows, renameSync onto an existing target throws EPERM/EBUSY/
// EACCES/EEXIST when another process (statusline read, a concurrent
// session's hook) holds the file open without FILE_SHARE_DELETE —
// without the retry + guaranteed cleanup here, every such miss left an
// orphaned .caveman-active.<pid>.<ts> file behind (#511/#578/#657).
let tempPath;
try {
tempPath = path.join(realFlagDir, `.caveman-active.${process.pid}.${Date.now()}`);
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | O_NOFOLLOW;
let fd;
try {
fd = fs.openSync(tempPath, flags, 0o600);
fs.writeSync(fd, String(content));
try { fs.fchmodSync(fd, 0o600); } catch (e) { /* best-effort on Windows */ }
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
// Retry brief lock contention a few times, backing off between attempts.
// Three synchronous attempts with no delay all complete inside a few
// microseconds, so whichever handle blocked the first (a statusline read,
// a concurrent session's hook, an AV scan) is still held for the other
// two — effectively one attempt with two extra syscalls. Sleep for real:
// Atomics.wait blocks this thread without pulling in a child process,
// which is what a hook budget can least afford.
let renamed = false;
for (let attempt = 0; attempt < 3; attempt++) {
try {
fs.renameSync(tempPath, realFlagPath);
renamed = true;
break;
} catch (e) {
const transient = e.code === 'EPERM' || e.code === 'EBUSY' ||
e.code === 'EACCES' || e.code === 'EEXIST';
if (!transient) throw e;
if (attempt < 2) sleepMs(10 * (attempt + 1));
}
}
if (!renamed && debug) {
process.stderr.write('[caveman] safeWriteFlag: rename contended after 3 attempts; flag not updated this write\n');
}
} finally {
if (tempPath) {
try {
fs.unlinkSync(tempPath);
} catch (error) {
if (debug && error.code !== 'ENOENT') {
process.stderr.write(`[caveman] safeWriteFlag: failed to remove temp flag ${tempPath}: ${error.message}\n`);
}
}
}
}
} catch (e) {
// Silent fail — flag is best-effort
}
}
// Symlink-safe flag file delete. Symmetric with safeWriteFlag: resolves
// through a symlinked/junctioned parent the same way (ownership check on
// Unix, writable-parent check on win32) and refuses to touch a target that
// is itself a symlink, before unlinking. Without this, any caller that wants
// to clear the flag could delete straight through a junction that
// safeWriteFlag would refuse to write through in the first place — a
// write/delete asymmetry that lets the flag be destroyed but never
// recreated on that machine.
//
// Silent-fails on any filesystem error — the flag is best-effort.
function safeDeleteFlag(flagPath) {
const debug = process.env.CAVEMAN_DEBUG === '1';
try {
const flagDir = path.dirname(flagPath);
let realFlagDir;
try {
const lstat = fs.lstatSync(flagDir);
if (lstat.isSymbolicLink()) {
realFlagDir = fs.realpathSync(flagDir);
const realStat = fs.statSync(realFlagDir);
if (!realStat.isDirectory()) {
if (debug) process.stderr.write(`[caveman] safeDeleteFlag: symlink target ${realFlagDir} is not a directory\n`);
return;
}
if (typeof process.getuid === 'function') {
if (realStat.uid !== process.getuid()) {
if (debug) process.stderr.write(`[caveman] safeDeleteFlag: symlink target ${realFlagDir} owned by uid ${realStat.uid}, not current user ${process.getuid()}\n`);
return;
}
} else {
try {
fs.accessSync(realFlagDir, fs.constants.W_OK);
} catch (e) {
if (debug) process.stderr.write(`[caveman] safeDeleteFlag: symlink target ${realFlagDir} is not writable by current user\n`);
return;
}
}
} else {
realFlagDir = flagDir;
}
} catch (e) {
return;
}
const realFlagPath = path.join(realFlagDir, path.basename(flagPath));
try {
if (fs.lstatSync(realFlagPath).isSymbolicLink()) return;
} catch (e) {
return;
}
fs.unlinkSync(realFlagPath);
} catch (e) {
// Silent fail — flag is best-effort
}
}
// Symlink-safe, size-capped, whitelist-validated flag file read.
// Symmetric with safeWriteFlag: refuses symlinks at the target, caps the read,
// and rejects anything that isn't a known mode. Returns null on any anomaly.
//
// Without this, a local attacker with write access to ~/.claude/ could replace
// the flag with a symlink to ~/.ssh/id_rsa (or any user-readable secret). Every
// reader — statusline, per-turn reinforcement — would slurp that content and
// either echo it to the terminal or inject it into model context.
//
// MAX_FLAG_BYTES is a hard cap. The longest legitimate value is the legacy
// "wenyan-ultra" (12 bytes); 64 leaves slack without enabling exfil.
const MAX_FLAG_BYTES = 64;
function readFlag(flagPath) {
try {
let st;
try {
st = fs.lstatSync(flagPath);
} catch (e) {
return null;
}
if (st.isSymbolicLink() || !st.isFile()) return null;
if (st.size > MAX_FLAG_BYTES) return null;
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_RDONLY | O_NOFOLLOW;
let fd;
let out;
try {
fd = fs.openSync(flagPath, flags);
const buf = Buffer.alloc(MAX_FLAG_BYTES);
const n = fs.readSync(fd, buf, 0, MAX_FLAG_BYTES, 0);
out = buf.slice(0, n).toString('utf8');
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
// Legacy levels come back as their current id, so every caller of
// readFlag (and the session helpers built on it) sees only VALID_MODES.
return canonicalMode(out.trim());
} catch (e) {
return null;
}
}
// Symlink-safe append. Same parent-dir + symlink-target rules as safeWriteFlag,
// but opens with O_APPEND so concurrent writers from different sessions don't
// clobber each other. Used for the lifetime stats log
// ($CLAUDE_CONFIG_DIR/.caveman-history.jsonl).
//
// Silent-fails on any filesystem error.
function appendFlag(filePath, line) {
const debug = process.env.CAVEMAN_DEBUG === '1';
try {
const dir = path.dirname(filePath);
fs.mkdirSync(dir, { recursive: true });
let realDir;
try {
const lstat = fs.lstatSync(dir);
if (lstat.isSymbolicLink()) {
realDir = fs.realpathSync(dir);
const realStat = fs.statSync(realDir);
if (!realStat.isDirectory()) return;
if (typeof process.getuid === 'function') {
if (realStat.uid !== process.getuid()) {
if (debug) process.stderr.write(`[caveman] appendFlag: symlink target ${realDir} owned by uid ${realStat.uid}\n`);
return;
}
} else {
// Same reasoning as safeWriteFlag's win32 branch: a directory
// junction resolves to a realpath with no home-directory prefix, so
// the old check refused the legitimate junctioned config dir it was
// meant to allow — and silently stopped the lifetime stats log from
// recording anything. Test what the guard actually cares about.
try {
fs.accessSync(realDir, fs.constants.W_OK);
} catch (e) {
if (debug) process.stderr.write(`[caveman] appendFlag: symlink target ${realDir} is not writable by current user\n`);
return;
}
}
} else {
realDir = dir;
}
} catch (e) {
return;
}
const realPath = path.join(realDir, path.basename(filePath));
try {
if (fs.lstatSync(realPath).isSymbolicLink()) return;
} catch (e) {
if (e.code !== 'ENOENT') return;
}
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND | O_NOFOLLOW;
let fd;
try {
fd = fs.openSync(realPath, flags, 0o600);
fs.writeSync(fd, String(line).replace(/\n$/, '') + '\n');
try { fs.fchmodSync(fd, 0o600); } catch (e) { /* best-effort on Windows */ }
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
} catch (e) {
// Silent fail — history is best-effort
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Per-session mode state
//
// The mode is applied per session but used to be stored in ONE file per
// machine. That single fact caused four bugs: parallel sessions shared a mode,
// SessionStart re-derived the default and clobbered an explicit "stop caveman"
// after every auto-compaction, the statusline badge showed the same mode in
// every window, and "off" — expressed as file ABSENCE — could never survive a
// SessionStart.
//
// #691 fixed the compaction case for a mid-session LEVEL change by branching on
// the hook payload's `source`. It cannot fix the other three, because all of
// them are consequences of the storage shape rather than of when the hook
// re-derives: one file cannot hold two windows' modes, and an absent file
// cannot express "deliberately off" distinguishably from "never set".
//
// Storage: $CLAUDE_CONFIG_DIR/.caveman-sessions/<session_id>.mode (+ .prev),
// with the legacy flag kept as a compat mirror.
//
// Every function below accepts a sessionId that may be null or malformed and
// degrades to the previous machine-wide behavior in that case. That is the
// whole backward-compatibility story: the old code path IS the fallback
// branch, so a caller with no session id behaves exactly as it did before.
// ─────────────────────────────────────────────────────────────────────────────
// A session id becomes part of a filesystem path, so it is a path-traversal
// vector. Whitelist the alphabet rather than blacklisting separators — same
// philosophy as VALID_MODES. Claude Code sends a UUID, but the regex stays
// deliberately broad so a future id format degrades to the legacy path instead
// of throwing. Length-capped so a hostile id cannot blow past filename limits.
const SESSION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
function validateSessionId(id) {
if (typeof id !== 'string' || !SESSION_ID_RE.test(id)) return null;
return id;
}
function legacyFlagPath(claudeDir) {
return path.join(claudeDir, FLAG_BASENAME);
}
function sessionsDir(claudeDir) {
return path.join(claudeDir, SESSIONS_DIRNAME);
}
// Returns null for any id the whitelist rejects. The resolved-path containment
// check is redundant while SESSION_ID_RE holds, but it is cheap and it defends
// against a future edit loosening the regex — the same defense-in-depth style
// as the symlink checks in safeWriteFlag.
function sessionStatePath(claudeDir, sessionId, ext) {
const sid = validateSessionId(sessionId);
if (!sid) return null;
const dir = sessionsDir(claudeDir);
const candidate = path.join(dir, sid + ext);
if (path.dirname(path.resolve(candidate)) !== path.resolve(dir)) return null;
return candidate;
}
function sessionActivePath(claudeDir, sessionId) {
return sessionStatePath(claudeDir, sessionId, '.mode');
}
function sessionPrevPath(claudeDir, sessionId) {
return sessionStatePath(claudeDir, sessionId, '.prev');
}
// Both "no file" and the literal 'off' mean caveman is off. Collapsing them to
// a single null is what implements the dual-read compat requirement: a missing
// file is the old spelling, a literal 'off' the new durable one.
function offToNull(mode) {
return (!mode || mode === 'off') ? null : mode;
}
// "What mode is in effect right now" — for every reader (per-turn
// reinforcement gate, ruleset re-emission, statusline, stats).
function resolveActiveMode(claudeDir, sessionId) {
const sessionPath = sessionActivePath(claudeDir, sessionId);
if (sessionPath) {
const stored = readFlag(sessionPath);
if (stored !== null) return offToNull(stored);
}
return offToNull(readFlag(legacyFlagPath(claudeDir)));
}
// "What is literally written for THIS session" — no legacy fallback, and 'off'
// comes back verbatim. Two callers need exactly this: recordModeChange (whose
// comparison against the shared mirror would diff against whatever some OTHER
// session wrote last and spam the log with phantom transitions) and the
// SessionStart continuation branch, which has to tell "this session chose off"
// apart from "this session has no state yet".
function readSessionModeRaw(claudeDir, sessionId) {
const sessionPath = sessionActivePath(claudeDir, sessionId);
if (!sessionPath) return readFlag(legacyFlagPath(claudeDir));
return readFlag(sessionPath);
}
// Single writer for the active mode. Pass null (or 'off') to deactivate.
//
// The session file stores 'off' literally — that is what makes deactivation
// durable across SessionStart. The legacy mirror instead gets UNLINKED, and
// never holds the literal 'off', on purpose: 'off' is already in VALID_MODES,
// so an older caveman-mode-tracker.js reading it from the legacy path would
// clear its !INDEPENDENT_MODES check and inject "CAVEMAN MODE ACTIVE (off)",
// and an older caveman-statusline.sh would render [CAVEMAN:OFF] instead of
// staying silent. Mixed-version installs are real — plugin hooks and
// standalone hooks can both be registered, and settings.json holds a
// statusline path baked in at install time.
function writeSessionMode(claudeDir, sessionId, modeOrNull) {
const canonical = modeOrNull ? canonicalMode(modeOrNull) : 'off';
if (!canonical) return;
const sessionPath = sessionActivePath(claudeDir, sessionId);
if (sessionPath) safeWriteFlag(sessionPath, canonical);
const legacy = legacyFlagPath(claudeDir);
if (canonical === 'off') {
safeDeleteFlag(legacy);
} else {
safeWriteFlag(legacy, canonical);
}
}
// Displaced-prose-mode memory for one-shot independent modes (#599), scoped to
// the session. Without the scoping, two windows each running /caveman-commit
// would overwrite each other's "where to return to".
//
// Unlike the active mode, prev is stored in EXACTLY ONE place — the session
// file when there is a valid session id, the legacy file otherwise. It is not
// mirrored, and the read/clear helpers must not cross that line either.
//
// Falling back to the legacy prev whenever a session simply has none of its own
// is a real bug, not a convenience: a session where caveman was never on saves
// no prev (there is nothing to displace), so after /caveman-commit it would
// restore from whatever stale machine-wide prev another context left behind and
// switch itself on at that mode. Symmetrically, clearing must not reach across
// and delete machine-wide state on behalf of one session.
function writeSessionPrev(claudeDir, sessionId, mode) {
const canonical = canonicalMode(mode);
if (!canonical) return;
const p = sessionPrevPath(claudeDir, sessionId) || path.join(claudeDir, PREV_BASENAME);
safeWriteFlag(p, canonical);
}
function readSessionPrev(claudeDir, sessionId) {
const p = sessionPrevPath(claudeDir, sessionId);
if (p) return readFlag(p);
return readFlag(path.join(claudeDir, PREV_BASENAME));
}
function clearSessionPrev(claudeDir, sessionId) {
const p = sessionPrevPath(claudeDir, sessionId);
if (p) {
safeDeleteFlag(p);
return;
}
safeDeleteFlag(path.join(claudeDir, PREV_BASENAME));
}
// Sweep stale per-session files. Called from SessionStart on a genuinely new
// session only — not on every compaction, which is frequent in a long session
// and would spend the 5s hook budget on directory walks.
//
// maxDeletes caps the first sweep on a machine that already accumulated many
// sessions. TTL is overridable via env so tests need not fake two weeks.
const SESSION_TTL_MS = 14 * 24 * 60 * 60 * 1000;
function gcSessionStore(claudeDir, opts) {
const options = opts || {};
const envTtl = Number(process.env.CAVEMAN_SESSION_TTL_MS);
const maxAgeMs = options.maxAgeMs
|| (Number.isFinite(envTtl) && envTtl > 0 ? envTtl : SESSION_TTL_MS);
const maxDeletes = options.maxDeletes || 500;
try {
const dir = sessionsDir(claudeDir);
const entries = fs.readdirSync(dir);
const cutoff = Date.now() - maxAgeMs;
let deleted = 0;
for (const name of entries) {
if (deleted >= maxDeletes) break;
const p = path.join(dir, name);
try {
const st = fs.lstatSync(p);
// Refuse symlinks rather than following them into someone else's file.
if (st.isSymbolicLink() || !st.isFile()) continue;
if (st.mtimeMs < cutoff) { fs.unlinkSync(p); deleted++; }
} catch (e) { /* vanished or unreadable — skip */ }
}
return deleted;
} catch (e) {
// Missing directory (fresh install) or any fs failure — nothing to do.
return 0;
}
}
// Mode-transition log (#601). Whenever the active mode actually changes,
// append {ts, mode, prev, session_id} to
// $CLAUDE_CONFIG_DIR/.caveman-mode-log.jsonl so caveman-stats can attribute
// output tokens to the mode that was active when each message was generated,
// instead of whatever mode the flag holds at stats time. mode/prev are a
// VALID_MODES string or null (null = caveman off). prev lets stats attribute
// messages that predate the first logged transition of a session. No-op when
// the mode is unchanged; best-effort like all flag IO.
//
// session_id is omitted (not null) when unknown, so JSON.stringify drops the
// key and readers written before this field existed see the old shape.
const MODE_LOG_BASENAME = '.caveman-mode-log.jsonl';
// Normalizing through offToNull before comparing matters here: without it a
// durable-off write looks like a transition from 'off' to null and lands a
// phantom entry in the log — and attributing output tokens to a mode named
// 'off' would be meaningless besides.
function recordModeChange(claudeDir, newMode, sessionId) {
try {
if (newMode && !canonicalMode(newMode)) return; // not a mode: log nothing
const current = offToNull(readSessionModeRaw(claudeDir, sessionId));
const next = offToNull(canonicalMode(newMode));
if (current === next) return;
const entry = { ts: Date.now(), mode: next, prev: current };
const sid = validateSessionId(sessionId);
if (sid) entry.session_id = sid;
appendFlag(path.join(claudeDir, MODE_LOG_BASENAME), JSON.stringify(entry));
} catch (e) {
// Silent fail — the log is best-effort
}
}
// Symlink-safe history read. Returns lines (untrimmed) or empty array on any
// anomaly. Caller is responsible for parsing JSON. Does NOT enforce a size cap
// the way readFlag does — history is expected to grow with use.
function readHistory(filePath) {
try {
const st = fs.lstatSync(filePath);
if (st.isSymbolicLink() || !st.isFile()) return [];
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_RDONLY | O_NOFOLLOW;
let fd;
let raw;
try {
fd = fs.openSync(filePath, flags);
raw = fs.readFileSync(fd, 'utf8');
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
return raw.split('\n').filter(line => line.trim());
} catch (e) {
return [];
}
}
// ---------------------------------------------------------------------------
// Caveman ruleset — skills/<mode>/SKILL.md, whole body
//
// Each prose mode is its own skill, so the stored id IS the skill id and the
// body needs no per-level filtering. caveman-activate.js injects it at
// SessionStart; caveman-mode-tracker.js re-injects it when the user switches
// mode mid-session (#975) and reads its thesis line for per-turn
// reinforcement.
//
// It lives here rather than in a new `caveman-ruleset.js` sibling for the reason
// the "Keep mode-state logic in caveman-config.js" rule gives: the hook file set
// is pinned in three places (checksums.sha256, verify_repo.py, the install
// lists) and src/plugins/opencode/plugin.js can only evaluate files it knows by
// name, so a second shared sibling is a standing drift risk. This module is
// already the one file every loader resolves.
const SKILL_MODES = ['caveman', 'ultracave', 'megacave'];
// Used when no SKILL.md resolves (standalone hook install without skills dir).
// Each is the first line under the skill's `# <id>` heading.
const FALLBACK_THESIS = {
caveman: 'Respond terse like smart caveman. All technical substance stay. Only fluff die.',
ultracave: 'Respond terse like smart caveman. All technical substance stay. Only fluff die. Then cut again.',
megacave: '以文言答。技術之實皆存,唯贅言去之。',
};
// Candidate locations, tried in order (#587/#589 — the old single '..' path
// resolved to <plugin_root>/src/skills/, which does not exist, so plugin
// installs silently used the stale fallback ruleset):
// 1. $CLAUDE_PLUGIN_ROOT/skills/<id>/SKILL.md — Claude Code sets
// CLAUDE_PLUGIN_ROOT when invoking plugin hooks; authoritative when present.
// 2. ../../skills/<id>/SKILL.md — hook at <plugin_root>/src/hooks/
// (plugin.json layout) or a repo checkout.
// 3. ../skills/<id>/SKILL.md — standalone install with hooks at
// $CLAUDE_CONFIG_DIR/hooks/ and the skill at $CLAUDE_CONFIG_DIR/skills/<id>/.
// skillId becomes a path segment: callers pass a SKILL_MODES id only.
function skillPathCandidates(hookDir, skillId) {
const dir = hookDir || __dirname;
const id = skillId || 'caveman';
const candidates = [];
if (process.env.CLAUDE_PLUGIN_ROOT) {
candidates.push(path.join(process.env.CLAUDE_PLUGIN_ROOT, 'skills', id, 'SKILL.md'));
}
candidates.push(
path.join(dir, '..', '..', 'skills', id, 'SKILL.md'),
path.join(dir, '..', 'skills', id, 'SKILL.md')
);
return candidates;
}
// The mode's SKILL.md body with YAML frontmatter stripped, or null when the
// mode has no skill file (off, one-shot modes) or none resolves — callers
// then use fallbackRuleset() below.
function loadRuleset(mode, hookDir) {
const id = canonicalMode(mode);
if (!SKILL_MODES.includes(id)) return null;
for (const candidate of skillPathCandidates(hookDir, id)) {
try {
return fs.readFileSync(candidate, 'utf8').replace(/^---[\s\S]*?---\s*/, '');
} catch (e) { /* try next candidate */ }
}
return null;
}
// First non-empty line after the skill's `# <id>` heading — the one-sentence
// statement of the mode, used for per-turn reinforcement.
function thesisLine(mode, hookDir) {
const id = canonicalMode(mode);
if (!SKILL_MODES.includes(id)) return null;
const lines = (loadRuleset(id, hookDir) || '').split(/\r?\n/);
const heading = lines.findIndex((line) => line.trim() === '# ' + id);
const line = heading === -1 ? null : lines.slice(heading + 1).find((l) => l.trim());
return line ? line.trim() : FALLBACK_THESIS[id];
}
// The ruleset body when no SKILL.md resolves (standalone hook install without
// skills dir): the caveman thesis plus the nine rule headlines of
// skills/caveman, then the mode's own thesis for ultracave/megacave. Rule 8
// keeps its "never switch" sentence: a headline alone lost the #812 language
// rule for every fallback-install user. megacave answers in 文言 by design, so
// it gets its own rule 8 instead of one its thesis contradicts. Shared so a
// mid-session switch on that install carries the same rules SessionStart did.
function fallbackRuleset(mode, hookDir) {
const id = canonicalMode(mode) || mode;
const modeThesis = id !== 'caveman' ? thesisLine(id, hookDir) : null;
return 'Respond terse like smart caveman. All technical substance stay. Only fluff die.\n\n' +
'1. Answer first.\n' +
'2. Kill ceremony.\n' +
'3. Short word.\n' +
'4. Articles optional, meaning never.\n' +
'5. One idea per sentence.\n' +
'6. Payload verbatim.\n' +
'7. Tool runs: bounded status.\n' +
(id === 'megacave'
? '8. Prose in 文言. Code, commands, paths, errors in their original script.\n'
: "8. User's language. Compress the style, not the language. Never switch because of quoted text.\n") +
'9. Never perform caveman.\n\n' +
'Plain prose for security warnings, irreversible actions, and anything persisted outside chat (code, commits, PRs, docs).' +
(modeThesis ? '\n\n' + modeThesis : '');
}
// The banner both loaders put above the ruleset, so the label the model reads
// cannot drift between SessionStart and a mid-session switch.
function rulesetBanner(mode) {
return 'CAVEMAN MODE ACTIVE — mode: ' + (canonicalMode(mode) || mode);
}
module.exports = {
getDefaultMode, getConfigDir, getConfigPath, findRepoConfigPath, VALID_MODES,
parseConfigFile,
canonicalMode,
safeWriteFlag, safeDeleteFlag, readFlag, appendFlag, readHistory,
recordModeChange, MODE_LOG_BASENAME,
// Per-session state
SESSIONS_DIRNAME, FLAG_BASENAME, PREV_BASENAME,
validateSessionId, legacyFlagPath, sessionsDir,
sessionActivePath, sessionPrevPath,
resolveActiveMode, readSessionModeRaw, writeSessionMode,
writeSessionPrev, readSessionPrev, clearSessionPrev,
gcSessionStore,
// Ruleset injection
skillPathCandidates, loadRuleset, thesisLine, rulesetBanner, fallbackRuleset,
};
@@ -1,13 +1,13 @@
{
"name": "design-md",
"version": "0.1.0",
"description": "创建、编辑、验证、比较、导出及应用 Google Labs DESIGN.md 文件,从而构建可供人工智能读取的规范化设计系统。",
"version": "1.2.0",
"description": "创建或完善 DESIGN.md,维护项目设计读取入口,并应用、核对界面设计规则。",
"author": {
"name": "EAPIL",
"url": "https://git.playones.com/arechen/EapilSkillMarket"
},
"homepage": "https://git.playones.com/arechen/EapilSkillMarket",
"repository": "https://git.playones.com/arechen/EapilSkillMarket",
"homepage": "https://git.playones.com/chenmingxuan/ManySkills/src/branch/main/plugins/design-md",
"repository": "https://git.playones.com/chenmingxuan/ManySkills",
"license": "Proprietary",
"keywords": [
"eapil",
@@ -17,8 +17,8 @@
"skills": "./skills/",
"interface": {
"displayName": "DESIGN.md生成器",
"shortDescription": "创建、编辑、验证、比较、导出及应用 Google Labs DESIGN.md 文件,从而构建可供人工智能读取的规范化设计系统。",
"longDescription": "创建、编辑、验证、比较、导出及应用 Google Labs DESIGN.md 文件,从而构建可供人工智能读取的规范化设计系统。",
"shortDescription": "生成设计指南,连接 Agent 读取入口,落实并核对界面设计。",
"longDescription": "基于项目证据创建或完善 Google Labs DESIGN.md,按 UI 作用域维护 AGENTS.md 入口,将设计意图和 token 落实到代码,并区分文档校验与渲染结果验证。",
"developerName": "EAPIL",
"category": "Creativity",
"capabilities": [
@@ -0,0 +1,9 @@
{
"sourceId": "design-md",
"repo": "https://git.playones.com/chenmingxuan/ManySkills.git",
"ref": "v1.2.0",
"commit": "a5d31c54a52eb2b98ea40e1052b95a418948590a",
"adapter": "codex-plugin",
"sourcePath": "plugins/design-md",
"syncedAt": "2026-11-05T11:30:20Z"
}
@@ -1,100 +1,28 @@
---
name: design-md
description: Create, edit, validate, compare, export, and apply Google Labs DESIGN.md files for AI-readable design systems. Use when the user asks to create or use DESIGN.md, convert brand/UI guidance into DESIGN.md, derive design rules from an existing app or screenshots, keep frontend implementation aligned with design tokens and prose, validate DESIGN.md with the @google/design.md CLI, or export DESIGN.md tokens to Tailwind, CSS, JSON, or DTCG formats.
description: Create or revise DESIGN.md, connect it to project instructions, or apply and audit its visual rules in UI work.
---
# DESIGN.md
Use this skill to make `DESIGN.md` a durable source of truth for a product's visual identity. A `DESIGN.md` combines exact machine-readable tokens in YAML front matter with human-readable design rationale in Markdown.
Make the design usable across later UI tasks: a clear visual intent, exact values where known, an instruction entrypoint, and evidence that the implementation follows it. A saved DESIGN.md is context, not an automatic loader or enforcement mechanism.
## Core Workflow
## Choose the work
1. Locate and read any existing `DESIGN.md` before UI work. Treat it as design context for implementation, review, and refactoring.
2. If creating one, gather concrete inputs first: existing CSS/Tailwind/theme files, component styles, screenshots, brand notes, typography choices, accessibility constraints, and target audience.
3. Write exact tokens in YAML front matter and explain intent in prose. Tokens provide values; prose explains how to apply them.
4. Validate with the official CLI whenever Node/npm is available. Fix errors before considering the file ready.
5. Apply tokens and prose to implementation, then update `DESIGN.md` if the code intentionally changes the design system.
- **Create or revise:** Use the user's references and actual brand/theme/component evidence. Describe distinctive intent and important states; include tokens when useful. Read [format and tooling](references/design-md-reference.md) for schema, lint, diff, or export. The [template](assets/DESIGN.md.template) is a scaffold, not a palette or visual default. Remove or resolve its prompts before delivery.
- **Connect future tasks:** When creating or maintaining a design guide, merge a short, scoped reading rule into the project's active AGENTS.md. Preserve existing instructions; use paths relative to that guide. For placement, multiple apps, and a starting snippet, read [integration and application](references/applying-design.md). Do not claim a running session will automatically reload new instructions.
- **Apply or repair drift:** Resolve the design applicable to the target UI, understand its prose and token roles, and map this task's constraints to actual theme/component code. Follow [integration and application](references/applying-design.md) for scope and implementation checks. Small, clear UI changes need only their relevant constraints, not a full design-system rewrite.
Read `references/design-md-reference.md` when creating, editing, validating, exporting, or applying a `DESIGN.md`.
Use the user's explicit choice of design. Otherwise, use the nearest applicable DESIGN.md toward the project root; consume shared or ancestor guides only when inheritance is established. This is this skill's selection policy, not a file-loading feature of the format. Separate apps can have separate design sources.
Use `assets/DESIGN.md.template` as a starting point for new files when the repo has no existing `DESIGN.md`.
## Preserve intent
## Creating DESIGN.md
Treat the accepted guide as the design target. Repair code that deviates. Change the guide only when the task intentionally changes the design or authorizes filling a genuine gap; document that change rather than retroactively legitimizing an accidental implementation. Surface conflicting or missing decisions when they materially affect the result. Preserve valid custom sections and token keys.
Prefer a specific design reference over vague adjectives. For example, "enterprise observability console with dense tables and quiet operational controls" is more useful than "modern, clean, premium." Include intentional negative constraints only when they prevent likely drift.
Bind values into the project's existing styling system; check overrides, component variants, and computed styles. Exact tokens alone do not express density, hierarchy, brand character, or responsive behavior. Prose does not replace known exact values.
Structure the file as:
## Verification and delivery
```markdown
---
version: alpha
name: Product Design System
colors:
primary: "#1A1C1E"
typography:
body-md:
fontFamily: Inter
fontSize: 16px
fontWeight: 400
lineHeight: 1.5
---
When the guide changes, use applicable format checks; CLI lint and export do not verify the UI. When UI changes, check representative rendered screens and affected states/sizes against the task's design constraints. Use existing browser/tests where available. If rendering is unavailable, report which checks remain; do not call a code-only or document-only check visual verification.
## Overview
...
```
Use canonical `##` sections in this order when relevant: `Overview`, `Colors`, `Typography`, `Layout`, `Elevation & Depth`, `Shapes`, `Components`, `Do's and Don'ts`. Preserve custom sections when editing existing files.
Include token groups that the implementation can actually use:
- `colors`: CSS color strings such as hex, rgb(), hsl(), oklch(), named colors, or transparent.
- `typography`: objects with font family, size, weight, line height, letter spacing, and optional feature/variation settings.
- `rounded`: radius scale values such as `4px`, `8px`, or `9999px`.
- `spacing`: spacing scale values such as `4px`, `8px`, `1rem`, or domain-specific numeric values.
- `components`: common component style tokens and state variants using literal values or references like `{colors.primary}`.
## Applying DESIGN.md
When building UI from an existing `DESIGN.md`:
1. Parse the YAML tokens first for exact values.
2. Read the prose for personality, density, hierarchy, interaction emphasis, and constraints.
3. Map tokens into the local styling system: CSS variables, Tailwind theme, component props, design-token JSON, or framework-specific theme configuration.
4. Use the design's named roles, not arbitrary nearby values. Do not invent new colors, radii, shadows, or type styles unless the task requires extending the system.
5. Keep visual decisions consistent across new screens and components. When a required pattern is absent, extend the `DESIGN.md` deliberately and validate it.
## CLI
Use the official package for checks and conversion:
```powershell
npx -p @google/design.md designmd lint DESIGN.md
npx -p @google/design.md designmd diff DESIGN.md DESIGN-v2.md
npx -p @google/design.md designmd export --format css-tailwind DESIGN.md > theme.css
npx -p @google/design.md designmd export --format json-tailwind DESIGN.md > tailwind.theme.json
npx -p @google/design.md designmd export --format dtcg DESIGN.md > tokens.json
```
On macOS/Linux, `npx @google/design.md ...` is also acceptable. On Windows/PowerShell, prefer the `designmd` alias via `npx -p @google/design.md designmd ...` because the `.md` executable name can collide with Markdown file associations.
If CLI access is unavailable, manually check:
- Front matter starts and ends with exact `---` fences.
- Token references resolve to existing YAML paths.
- Component text/background color pairs meet WCAG AA contrast for normal text when possible.
- Present sections follow canonical order.
- No duplicate section headings exist.
## Editing Rules
Preserve unknown sections and custom token keys unless the user asks to remove them. Unknown content is allowed by the format and often contains project-specific design context.
Reject or repair duplicate canonical sections, broken token references, obvious misspelled top-level keys, and invalid YAML.
Use `diff` when comparing versions. Treat added errors or warnings as a regression unless the user explicitly accepts the change.
## Sources
Primary reference: https://github.com/google-labs-code/design.md
Official spec: https://github.com/google-labs-code/design.md/blob/main/docs/spec.md
Report the active design and scope, changed files, relevant rule-to-code mappings, checks performed, and any unresolved drift. Keep the mapping in the handoff or existing project docs rather than creating extra reports for every small task.
@@ -1,4 +1,4 @@
interface:
display_name: "DESIGN.md"
short_description: "Create and apply AI-readable design systems"
default_prompt: "Use $design-md to create or apply a DESIGN.md for this UI project."
short_description: "Create, connect, and apply project design rules"
default_prompt: "Use $design-md to create or apply this project's DESIGN.md, maintain its scoped instruction entrypoint, and verify the relevant UI result."
@@ -0,0 +1,7 @@
<!-- Adapt the UI scope and DESIGN.md link to the target project. Merge into the effective AGENTS.md without replacing existing rules. This snippet is not a finished project instruction file. -->
## UI design
For UI changes in this guide's scope, read [DESIGN.md](DESIGN.md) and apply its design intent and exact values to the relevant theme and components. Backend-only tasks do not need this visual context.
Check representative rendered results and affected states/sizes against the rules used in the task. Repair implementation drift; do not rewrite the accepted design to excuse it. If the task deliberately changes the design, update its guide and implementation consistently.
@@ -1,103 +1,33 @@
---
version: alpha
name: Product Design System
description: Replace with a short description of the product or brand identity.
colors:
primary: "#111827"
on-primary: "#FFFFFF"
secondary: "#4B5563"
tertiary: "#2563EB"
neutral: "#F9FAFB"
surface: "#FFFFFF"
on-surface: "#111827"
border: "#E5E7EB"
typography:
headline-lg:
fontFamily: Inter
fontSize: 32px
fontWeight: 700
lineHeight: 1.15
headline-md:
fontFamily: Inter
fontSize: 24px
fontWeight: 650
lineHeight: 1.2
body-md:
fontFamily: Inter
fontSize: 16px
fontWeight: 400
lineHeight: 1.5
label-md:
fontFamily: Inter
fontSize: 14px
fontWeight: 600
lineHeight: 1.2
rounded:
sm: 4px
md: 8px
lg: 12px
full: 9999px
spacing:
xs: 4px
sm: 8px
md: 16px
lg: 24px
xl: 32px
components:
page:
backgroundColor: "{colors.neutral}"
textColor: "{colors.on-surface}"
button-primary:
backgroundColor: "{colors.tertiary}"
textColor: "{colors.on-primary}"
typography: "{typography.label-md}"
rounded: "{rounded.md}"
padding: 12px
card:
backgroundColor: "{colors.surface}"
textColor: "{colors.on-surface}"
rounded: "{rounded.lg}"
padding: 24px
divider:
backgroundColor: "{colors.border}"
height: 1px
---
<!-- Scaffold only. Replace each bracketed prompt with project evidence, or omit an irrelevant section. Do not deliver these prompts as a finished guide. Add optional YAML tokens above this comment only when real values are known. No palette, font, radius, or component treatment is a default. -->
## Overview
Describe the specific product, audience, visual reference, interaction density, and emotional target. Prefer concrete references over generic terms.
[Product, audience, specific visual reference, distinctive identity and interaction density.]
## Colors
- **Primary** is used for core text and the strongest structural elements.
- **Secondary** is used for captions, metadata, borders, and low-emphasis controls.
- **Tertiary** is used for the primary action and focused interactive emphasis.
- **Neutral** is the page foundation.
- **Surface** is used for panels, inputs, cards, and modal content.
[Actual color roles and known values. Explain accent use and the relevant text/background pairs.]
## Typography
Describe the type hierarchy, role of each family, weight strategy, line-height rhythm, casing, and how dense or relaxed text should feel.
[Verified font availability, hierarchy, density, weights and fallback behavior.]
## Layout
Describe grid behavior, max content width, gutters, spacing rhythm, responsive behavior, and whether the UI should feel dense, spacious, editorial, operational, playful, or utilitarian.
[Concrete container, spacing and responsive rules relevant to this product.]
## Elevation & Depth
Describe how hierarchy is created: shadows, borders, tonal layers, color contrast, flat surfaces, or another strategy.
[How hierarchy is expressed: borders, surfaces, elevation or another evidenced approach.]
## Shapes
Describe radius usage and exceptions. State whether the system is sharp, soft, geometric, organic, or mixed.
[Shape language and justified exceptions. Use exact values when known.]
## Components
Describe primary buttons, secondary buttons, inputs, cards, lists, tables, navigation, dialogs, tabs, chips, tooltips, and state variants that matter to the product.
[Relevant components and their important states; connect each to the design roles or tokens.]
## Do's and Don'ts
- Do keep token usage consistent across screens.
- Do preserve the intended hierarchy and interaction emphasis.
- Don't introduce new colors, radii, shadows, or type styles without updating this file.
- Don't use generic decorative effects that conflict with the overview.
[Only a few intentional constraints that prevent likely drift; accepted design changes require task evidence.]
@@ -0,0 +1,52 @@
# Connect and apply a design
This workflow belongs to this skill. Google Labs defines the DESIGN.md representation; it does not provide automatic Codex loading or a UI-enforcement hook.
## Choose the active source
Use a design explicitly selected by the user. Otherwise, locate DESIGN.md from the target UI directory toward the project root and use the closest relevant guide. Inspect stated scope and inheritance; do not merge unrelated palettes or silently apply a sibling application's guide. If a task spans multiple apps, identify a source for each.
Where shared root and local design rules intentionally compose, use the local rules for their declared overrides. A missing local detail is not permission to borrow a conflicting style. If the choice changes the outcome and cannot be inferred, ask a focused question.
## Keep it discoverable
When creating or maintaining DESIGN.md, merge a small UI-conditioned reading rule into the active project AGENTS.md, preserving existing instructions. Start from [the snippet](../assets/AGENTS.design-snippet.md) only if helpful. Adapt it to actual UI paths and document locations; paths in links are relative to the instruction file.
For multiple apps, the root guide can route console UI work to apps/console/DESIGN.md and storefront work to apps/storefront/DESIGN.md. A nested AGENTS.md is useful for distinct local instructions, but is not a substitute for ensuring the session actually receives the route. Do not copy the full design into always-loaded instructions. Backend-only tasks do not need to load visual guidance.
Codex documents startup discovery from the project root toward its working directory. DESIGN.md is not itself an automatically discovered instruction file. Adding or changing AGENTS.md does not prove the active session has reloaded it. For lasting integration, verify in a fresh session or explicitly supply the relevant guide for the current task. Check the chosen host's own loading rules if supporting another agent.
If the project has existing agent instructions, merge into the effective guide; do not introduce AGENTS.override.md or duplicate competing loader files merely to force precedence. If the task expressly limits edits to DESIGN.md, honor that limit and provide the reading rule as a handoff instead.
## Convert intent to implementation
Understand the prose, then the exact values and semantic roles. For this task, identify the constraints that affect the result: for example a control radius, accent role, text density, border hierarchy, font family, or mobile layout. Keep a compact rule-to-code mapping in working notes or the handoff; a small change may need only a sentence.
Map tokens into the current CSS variables, theme, utility system, or component API. Reuse semantic roles instead of selecting nearby literals. Inspect the existing cascade: global selectors, inline values, utilities, theme modes, library defaults and component variants can override a correct token declaration. Exported CSS that is not imported or is overridden has not applied the design.
For known measurements, bind or use the exact value. For prose-only intent, choose an implementation consistent with its specific reference and hierarchy. A required new state can use existing roles; introduce a new token only if the task justifies it. Keep unrequested theme redesigns outside scope.
If an implementation deviates from the accepted guide, fix the implementation. Change DESIGN.md only for a requested design change or an authorized, evidenced gap. Do not rewrite the document to match accidental drift. Preserve functional behavior and accessibility while repairing styles.
## Verify the result
Use checks appropriate to the changed constraints:
- Inspect computed styles for critical exact values after all overrides are applied.
- Inspect a representative rendered page for hierarchy, density, spacing, component shape and brand character that numeric checks cannot establish.
- Exercise affected hover/focus/disabled/error states and responsive sizes when those rules matter to this change; do not expand a tiny edit into a full UI audit.
- Run existing relevant UI/accessibility/build checks when they provide useful evidence.
Source inspection can catch an obvious unbound token, but it is not rendered verification. Document lint checks the document; export checks conversion; neither establishes implementation adherence. Record actual checks, unresolved drift, and missing browser or app access. Do not claim every future model will obey the guide.
## Delivery
Identify the active design file and UI scope, integration entrypoint, relevant constraints and implementation locations, and verification evidence. Report intentionally changed design rules separately from code repairs. Do not add persistent mapping reports, full visual regression suites, or extra tooling unless the task warrants them.
## Sources
Reviewed 2026-09-30:
- [Google Labs design philosophy](https://github.com/google-labs-code/design.md/blob/main/PHILOSOPHY.md): design context and intent.
- [Google Labs format](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md): tokens and prose.
- [Codex instruction discovery](https://learn.chatgpt.com/docs/agent-configuration/agents-md): instruction loading; DESIGN.md integration above is this skill's policy.
@@ -1,198 +1,75 @@
# DESIGN.md Reference
# DESIGN.md format and tooling
Use this reference after the `design-md` skill triggers. It is a compact working guide based on the Google Labs DESIGN.md alpha specification.
Use for creating a guide or handling schema, lint, diff, and export. For discovery, instruction integration, and implementation adherence, use [applying-design.md](applying-design.md).
## Purpose
## Current format
`DESIGN.md` is a plain-text design-system file for coding agents. It has two layers:
Reviewed 2026-09-30 against the Google Labs alpha specification and CLI 0.4.0. The CLI package version is separate from `version: alpha` in a document and from this plugin's release version.
- YAML front matter for machine-readable design tokens.
- Markdown prose for design rationale, usage guidance, and constraints.
A guide has a Markdown body and optional YAML front matter. With tokens, the YAML block must be at the beginning between exact `---` lines. Tokens carry normative values; prose explains their roles and fills in design decisions they cannot express. Prose-only guides are valid; do not invent token groups to satisfy a checklist.
Tokens are the exact values. Prose explains the design intent and tells the agent how to apply the values.
When relevant, canonical `##` sections occur in this order: Overview (or Brand & Style), Colors, Typography, Layout (or Layout & Spacing), Elevation & Depth (or Elevation), Shapes, Components, Do's and Don'ts. Omit irrelevant sections; preserve custom sections. Duplicate headings are errors. Section order comes from this format, unlike AGENTS.md's free-form headings.
## File Shape
## Tokens
```markdown
---
version: alpha
name: Product Name
description: Optional one-line summary
colors:
primary: "#1A1C1E"
typography:
body-md:
fontFamily: Inter
fontSize: 16px
fontWeight: 400
lineHeight: 1.5
rounded:
sm: 4px
spacing:
md: 16px
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
rounded: "{rounded.sm}"
padding: 12px
---
- `name`: design-system name; optional `version` currently uses `alpha`, and `description` is optional.
- `omitted`: optional strings or objects with `section` and optional `reason`, for deliberately absent groups. Use for genuine omissions, not to hide problems.
- `colors`: named CSS colors; semantic roles explain where to use them.
- `typography`: named objects using `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, `letterSpacing`, `fontFeature`, `fontVariation` as applicable.
- `rounded`: dimensions; `spacing`: dimensions or numbers. Supported dimension units are px, em, rem; explain unitless spacing values. Unitless lineHeight is a font-size multiplier.
- `components`: component/state properties; known properties include backgroundColor, textColor, typography, rounded, padding, size, height, width. Custom properties can produce review warnings.
## Overview
...
```
References use `{path.to.token}`. Most targets are primitive values; component typography may reference a full typography object. Resolve every reference; unknown valid token names are allowed.
The front matter block must be at the top of the file and delimited by lines containing exactly `---`.
## Token Schema
Top-level token groups:
- `version`: optional, current alpha value is `alpha`.
- `name`: design-system name.
- `description`: optional short summary.
- `colors`: map of color token names to CSS color strings.
- `typography`: map of type role names to typography objects.
- `rounded`: map of radius scale names to dimensions.
- `spacing`: map of spacing names to dimensions or numbers.
- `components`: map of component/state names to component property tokens.
Common color token names: `primary`, `secondary`, `tertiary`, `neutral`, `surface`, `on-surface`, `error`, `on-primary`.
Common typography token names: `headline-display`, `headline-lg`, `headline-md`, `body-lg`, `body-md`, `body-sm`, `label-lg`, `label-md`, `label-sm`.
Common rounded token names: `none`, `sm`, `md`, `lg`, `xl`, `full`.
## Token Types
Color values may be any valid CSS color string. Hex `#RRGGBB` is the safest default for broad tooling support.
Dimensions are numbers with `px`, `em`, or `rem` units. Use unitless `lineHeight` only when intended as a multiplier.
Typography objects may contain:
- `fontFamily`
- `fontSize`
- `fontWeight`
- `lineHeight`
- `letterSpacing`
- `fontFeature`
- `fontVariation`
Token references use `{path.to.token}`. Most references should point to primitive values, such as `{colors.primary}`. Component tokens may reference composite typography objects, such as `{typography.label-md}`.
## Section Order
Use `##` headings. Sections are optional, but present canonical sections should appear in this order:
1. `Overview` or `Brand & Style`
2. `Colors`
3. `Typography`
4. `Layout` or `Layout & Spacing`
5. `Elevation & Depth` or `Elevation`
6. `Shapes`
7. `Components`
8. `Do's and Don'ts`
An optional `#` title may appear before the sections. It is not parsed as a design section.
## Section Guidance
`Overview`: describe the product, audience, density, personality, and emotional target. Use specific visual references. Avoid generic adjective lists.
`Colors`: define roles, not just hex values. Explain where each color appears and where it must not appear.
`Typography`: define hierarchy, font families, weights, scale, line heights, casing, data/label treatments, and reading-density decisions.
`Layout`: define grid, max widths, spacing rhythm, density, page gutters, container behavior, and responsive expectations.
`Elevation & Depth`: define whether hierarchy comes from shadows, borders, tonal layers, contrast, surface color, or no elevation.
`Shapes`: define radius, border geometry, sharp/soft language, and where exceptions are allowed.
`Components`: define buttons, inputs, cards, tables, lists, chips, tabs, dialogs, tooltips, and state variants that matter to the product.
`Do's and Don'ts`: include practical guardrails. Keep them intentional; if the list gets long, strengthen the overview and component guidance.
## Components
Known component property tokens:
- `backgroundColor`
- `textColor`
- `typography`
- `rounded`
- `padding`
- `size`
- `height`
- `width`
Represent states and variants as related component keys:
This small schema example illustrates references, not a recommended palette:
```yaml
version: alpha
name: Example only
colors:
primary: "#152820"
on-action: "#FFFFFF"
rounded:
control: 0px
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
rounded: "{rounded.md}"
padding: 12px
button-primary-hover:
backgroundColor: "{colors.primary-hover}"
textColor: "{colors.on-action}"
rounded: "{rounded.control}"
```
Unknown component properties are allowed but should produce a warning during review.
Choose actual values from the project's design inputs. Define meaningful hover, focus, disabled, or error states where this task needs them; do not fabricate a full component library.
## Consumer Behavior
## Authoring
Preserve unknown section headings. Preserve unknown valid token names. Accept unknown component properties with review warnings. Reject duplicate section headings. Fix broken references.
Start with the product, audience, concrete visual reference, and distinctive layout/hierarchy. Explain scarce accents, typography roles, spacing, shape, elevation, and responsive behavior in terms an implementer can check. Keep prohibitions few and specific. Include exact measurements or token roles when the input supports them; identify unresolved design decisions rather than substituting a generic theme.
## Official CLI
Preserve established custom content when editing. If brand prose and tokens disagree, resolve the intended target using the user's request and source evidence; do not silently choose whichever matches existing code.
Install or run directly:
## CLI
```bash
npm install @google/design.md
npx @google/design.md lint DESIGN.md
```
Windows/PowerShell reliable form:
Node 18+ is required. Prefer the project's already pinned compatible package. The commands below were verified against 0.4.0; they run from the target project directory. The explicit `designmd` alias also avoids Windows .md file-association problems.
```powershell
npx -p @google/design.md designmd lint DESIGN.md
npx --yes --package @google/design.md@0.4.0 designmd lint DESIGN.md
npx --yes --package @google/design.md@0.4.0 designmd diff DESIGN.md DESIGN-v2.md
npx --yes --package @google/design.md@0.4.0 designmd export --format css-vars --prefix brand DESIGN.md
npx --yes --package @google/design.md@0.4.0 designmd export --format css-tailwind DESIGN.md
npx --yes --package @google/design.md@0.4.0 designmd export --format json-tailwind DESIGN.md
npx --yes --package @google/design.md@0.4.0 designmd export --format dtcg DESIGN.md
```
Useful commands:
Inspect output before integrating it. css-vars emits custom properties; css-tailwind targets Tailwind v4, json-tailwind targets Tailwind v3 theme.extend, and dtcg emits design tokens. Exporting does not wire the result into the app. Write files with the project's UTF-8 convention; avoid overwriting a hand-maintained theme through blind redirection.
```powershell
npx -p @google/design.md designmd lint DESIGN.md
npx -p @google/design.md designmd diff DESIGN.md DESIGN-v2.md
npx -p @google/design.md designmd export --format json-tailwind DESIGN.md > tailwind.theme.json
npx -p @google/design.md designmd export --format css-tailwind DESIGN.md > theme.css
npx -p @google/design.md designmd export --format dtcg DESIGN.md > tokens.json
npx -p @google/design.md designmd spec --rules
```
In 0.4.0, lint exits 1 for errors, 0 otherwise; file-read failures exit 2. Review warnings by their meaning: missing optional groups and custom fields need not be failures. For `missing-primary`, clarify the intended primary role using an existing approved color or token reference when appropriate; do not invent a new hue to silence the warning. Preserve existing semantic names. If the design deliberately has no primary role, explain that decision rather than implying lint guarantees model behavior.
`lint` exits nonzero when errors are present. `diff` exits nonzero when the later file has regressions, such as more errors or warnings.
Export can succeed despite lint errors, so its exit status is not a validation gate. Diff detects token changes and increased error/warning counts; it does not assess changed prose intent or rendered UI. Compare prose and implementation separately.
## Authoring Checklist
Without CLI access, check YAML validity where present, reference resolution, relevant section order, duplicate headings, and documented color-pair contrast. Label this manual validation and state its limits. Use project accessibility checks for the actual rendered states.
- Use a concrete visual reference in `Overview`.
- Include exact values for reusable colors, typography, spacing, radii, and core components.
- Explain scarcity rules for accents, shadows, display type, decorative elements, and motion.
- Use token references for component values instead of duplicating literals.
- Keep prose actionable for future UI generation.
- Validate with the CLI and fix structural errors.
## Primary sources
## Applying Checklist
- Read `DESIGN.md` before editing UI.
- Bind exact tokens to CSS variables, Tailwind theme entries, framework theme objects, or component constants.
- Use prose for unstated choices such as density, hierarchy, affordance strength, and restraint.
- Avoid introducing style drift. Add new tokens only when the task expands the design system.
- Re-run lint after editing `DESIGN.md`.
## Source Links
- Repository: https://github.com/google-labs-code/design.md
- Specification: https://github.com/google-labs-code/design.md/blob/main/docs/spec.md
- Philosophy: https://github.com/google-labs-code/design.md/blob/main/PHILOSOPHY.md
- [Format specification](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md)
- [Design philosophy](https://github.com/google-labs-code/design.md/blob/main/PHILOSOPHY.md)
- [CLI 0.4.0 release](https://github.com/google-labs-code/design.md/releases/tag/0.4.0)
- [Export implementation](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/commands/export.ts) and [diff implementation](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/commands/diff.ts)
@@ -1,6 +1,6 @@
{
"name": "dingtalk-workspace",
"version": "1.0.62",
"version": "1.0.67",
"description": "通过 dws CLI 操作钉钉消息、文档、表格、日历、待办和知识库的官方 Skill 集合。需要另行安装匹配版本的 dws、登录并取得企业授权;辅助脚本需要 Python 3。",
"author": {
"name": "DingTalk",
@@ -4,6 +4,21 @@ Copyright 2026 Alibaba Group
This product includes software developed at
DingTalk (https://www.dingtalk.com/).
The embedded DingTalk AI Card V0.8 protocol data includes definitions and
descriptions retained or adapted from the A2UI (Agent to UI) Protocol v1.0,
baseline commit f5e945a93da36a5d126228f2ad6fbe68809ef8f7. Those parts
are licensed under Apache-2.0. DingTalk modified the message envelope,
common types, and component/function catalogs; the source distribution's
skills/multi/dingtalk-aicard/references/protocol/NOTICE records the per-file
provenance. This CLI is not published or endorsed by the A2UI project.
The statically linked SafeChat message-crypto backend
(third_party/safechat-go-sdk/lib/*/libsafechat.a, including the
openharmony/arm64 variant) embeds a build of OpenSSL. OpenSSL is licensed
under the Apache License 2.0 with the OpenSSL exception
(https://www.openssl.org/source/license.html). The archives are provided by
the SafeChat SDK vendor; report upstream issues through the vendor channel.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -15,3 +30,86 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
------------------------------------------------------------------------
Unicode 字符数据
internal/card/a2ui/assets.json 中的 XID_Start 与 XID_Continue 字符范围
派生自 Unicode 17.0.0 的 DerivedCoreProperties.txt;数据源版权声明为
© 2025 Unicode®, Inc.。
数据源:https://www.unicode.org/Public/17.0.0/ucd/DerivedCoreProperties.txt
许可原文:https://www.unicode.org/license.txt
以下保留 Unicode 官方版权和许可通知,适用于上述派生字符数据:
UNICODE LICENSE V3
COPYRIGHT AND PERMISSION NOTICE
Copyright © 1991-2026 Unicode, Inc.
NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.
------------------------------------------------------------------------
regexp2 正则表达式库
本产品包含 github.com/dlclark/regexp2 v1.11.5。
项目:https://github.com/dlclark/regexp2
许可原文:https://github.com/dlclark/regexp2/blob/v1.11.5/LICENSE
以下保留该依赖的版权和许可通知:
The MIT License (MIT)
Copyright (c) Doug Clark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,9 +1,9 @@
{
"sourceId": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
"ref": "v1.0.62",
"commit": "70323e1486e64b1ca823fa2bd9c48b9b07f88519",
"ref": "v1.0.67",
"commit": "63a7c4906c7dee10750011ea309b9eb78abb7eb0",
"adapter": "skill-collection",
"sourcePath": "skills/multi",
"syncedAt": "2026-09-16T15:59:59Z"
"syncedAt": "2026-11-04T09:24:25Z"
}
@@ -0,0 +1,203 @@
Copyright 2026 Alibaba Group
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,12 @@
DingTalk AI Card Skill V0.8
Copyright 2026 Alibaba Group (Skill-authored material, except where noted)
This Skill is distributed under the Apache License, Version 2.0, in LICENSE.
Its references/protocol/ directory includes definitions and descriptions
retained or adapted from the A2UI 1.0 reference. Keep the protocol's own
LICENSE and NOTICE with that directory; its NOTICE records the pinned
upstream revision, DingTalk modifications, and attribution.
This Skill is maintained by DingTalk. It is not published or endorsed by the
A2UI project. Apache-2.0 grants no trademark rights; references to A2UI
identify the upstream work.
@@ -0,0 +1,150 @@
---
name: dingtalk-aicard
description: >
Create, edit, and validate DingTalk AI Card (A2UI) JSON files with native
DWS commands. Use for cards built from requirements or images, structural
protocol errors, named component or function contract lookup, and cloud
templates created, retrieved, or revised by template ID and version.
Send a card to the current user only when a preview is requested.
metadata:
cli_version: ">=0.2.14"
category: product
requires:
bins:
- dws
cliHelp: "dws aicard --help"
aicardVersion: "V0.8"
protocolVersion: "1.0"
catalogId: https://dingtalk.com/card/a2ui/catalogs/public/catalog.json
---
# DingTalk AI Cards V0.8
Create or edit A2UI from user content or images; query needed contracts. Distinguish local validation, delivery, and rendering. DingTalk V0.8 uses A2UI 1.0: message `version` is `"v1.0"`; send API `protocolVersion` is `"1.0"`.
Follow the requested lookup, file, send, or update; references are not a mandatory sequence. Obey command and protocol requirements. Ask only if missing facts, interaction meaning, or target affect the outcome.
## Route by intent
Design supplies visual principles; patterns give reusable content and interaction strategies; templates are complete scene examples. Read only the relevant reference. Do not preload the complete protocol, every pattern, or the whole guide.
| Intent | Start with | Optional follow-up |
|---|---|---|
| Set up lookup and validation | [Execution environment](#execution-environment) | Installed command help |
| Reuse a bundled template | Its [reuse boundary](references/templates/README.md) and selected JSON | Components outside the data model; pattern for changed behavior |
| Create or redesign from requirements | [Visual focus and layout](references/design.md) | One [pattern](#composition-patterns); a template only if compatible |
| Reconstruct an image | [Image reconstruction](references/design.md#image-reconstruction) | Selected component contracts |
| Query a known name | [Query a named contract](#query-a-named-contract) | The result's source pointer |
| Find an unknown name | [Component](references/index/components.md), [function](references/index/functions.md), or [Token](references/index/tokens.md) index | Query only the selected names |
| Construct or repair a card, or prepare an update | Current messages and [Construct and validate](#construct-and-validate) | Affected guidance, diagnostic pointer, and named contract |
| Send, preview, or deliver an update | [Delivery boundary](#delivery-boundary) | Installed command help |
Use patterns for composition guidance and linked JSON for message shapes. Read a named capability example directly; use the [example index](references/protocol/examples/README.md) only to choose one. Template reuse must match structure, collection sizes, and interactions.
## Composition patterns
| Content | Reference |
|---|---|
| Notifications and reminders | [Short notifications](references/patterns/detail.md#short-notifications) |
| Object facts and decisions | [Detail](references/patterns/detail.md) |
| Illustrated content and attachments | [Content](references/patterns/content.md) |
| Metrics and reports | [Report](references/patterns/report.md) |
| Editable inputs and submission | [Form](references/patterns/form.md) |
| Generation and execution updates | [Progress](references/patterns/progress.md) |
For editable inputs, also consult the form pattern for bindings and submission,
even when another pattern describes the card.
When replaying an execution example, read [Examples and replay](references/patterns/progress.md#examples-and-replay) for its batch boundaries, even if the JSON was selected directly.
## Execution environment
Use native DWS commands. Check `dws aicard --help` for `explain`, `lint`, `preview`, and `template`; copying this Skill does not install them. Lookup and lint are offline and need no Profile. Check leaf help and JSON envelopes (`ok`, `outcome`, `data`, `error.details`). Missing commands permit a draft, never a DWS validation claim.
### Cloud templates
| Intent | Command |
|---|---|
| Save a validated card | `dws aicard template create --file <file>` |
| Read a known ID and version | `dws aicard template get --template-id <id> --version <version> --out <new-file>` |
| Revise a template | `dws aicard template update --template-id <id> --file <file>` |
Cloud calls need the intended Profile, organization, and environment, plus an explicit `--mcp-id` or `DWS_AICARD_TEMPLATE_MCP_ID`; there is no default. `get` needs a known ID and version, not keywords. To derive a template, get it, edit the file, and create a new one; `update` appends a version under the same ID. Report the returned ID and version; never retry an uncertain write. A read of an absent version may return the latest content, so strict use must match the approved digest.
Use approved business bindings and data paths; do not substitute local samples. Business configuration owns activation and rollback. Send complete new cards or authorized instance deltas; delivery never modifies template assets.
## Query a named contract
```bash
dws aicard explain Tabs --format json
dws aicard explain Text Row Column --compact --format json
```
Query only needed names. Compact `$contractRef` and `definitions` are lookup metadata, not A2UI fields. Query without `--compact` for expanded fields; `source` locates omitted details.
## Construct and validate
Use `references/protocol/` as the contract. Write an ordered JSON message array.
Within one card, keep the same `surfaceId`; choose an ID suitable for that card
when creating it. A complete new card requires Surface creation, root data
initialization (even `{}` for static content), and a component with ID `root`.
The recommended explicit form is:
```json
[
{"version":"v1.0","createSurface":{"surfaceId":"example-surface","catalogId":"https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"}},
{"version":"v1.0","updateDataModel":{"surfaceId":"example-surface","path":"/","value":{}}},
{"version":"v1.0","updateComponents":{"surfaceId":"example-surface","components":[{"id":"root","component":"Text","text":"Example"}]}}
]
```
This is a structural example, not a business card to send. Protocol-valid inline
initialization in `createSurface.dataModel` and `createSurface.components` is also
accepted; three separate messages are not the only valid representation.
| Scenario | Messages and checks |
|---|---|
| Complete new card | Create and initialize as above; use `--preflight new-card` |
| Host-created empty Surface | Initialize its existing ID without `createSurface`; use Schema and resource checks |
| Existing card update | Keep IDs and send only intentional changes; use Schema and resource checks, then check against known current state |
Removing `createSurface` from a complete file does not make it a safe delta.
Do not replay form defaults or unrelated initial data: that can overwrite user
input. Resource checks alone cannot prove an update preserves state or references.
Preserve unrelated styling when editing. When reusing a template, keep its frame and insets; the new-card defaults below do not override template styling or explicit surface designs. For ordinary new cards, prefer a root `Column` without `backgroundColor` or `backgroundColorToken`. If a root `Card` is needed, use `backgroundColor: "#00FFFFFF"` and omit `backgroundColorToken`: omitting both fields gives Card an opaque default, and a configured background Token takes precedence over the color. See [design guidance](references/design.md#root-surface).
Prefer `Markdown` for rich text and static tables; use `Text` for short titles and labels. See [report composition](references/patterns/report.md) for table selection and pagination. Omit root `padding` for ordinary new cards; omission is not equivalent to explicit `0`. Do not default to `CardHeader`; preserve it when requested and check [insets](references/design.md#headers-and-card-insets). These are authoring defaults, not Schema restrictions.
Preserve supplied facts and requested interactions; label demonstration data. Replace `REPLACE_*` placeholders before delivery. Do not present unverified resources, unconnected actions, or simulated states as real and working.
Choose literals or bindings as the field contract permits. Initialize bound data with the required types and update it through `updateDataModel`; change component literals and layout through `updateComponents`. `sendDataModel` controls whether A2A messages include the full data model, not whether bindings work.
```bash
dws aicard lint --file a2ui.json --format json
```
Choose `--preflight new-card` or `--preflight resources` for the selected scenario. Use `--emit` only when serialized message strings are needed; it cannot be combined with `--fragment`. Check exit status, `data.valid`, and `data.preflight.valid` when selected. Fix errors at their diagnostic pointers and rerun the selected check. `dws aicard lint --self-check --format json` verifies the full package.
For group cards that @ members, Markdown content uses `<a atId="openDingTalkId">nickname</a>` for a member and `<a atId="all">everyone</a>` for everyone. Pass the declared ids with `--at-open-dingtalk-ids` alongside `--preflight`: lint then fails on ids declared but unused (`mention.declared_unused`) or used but undeclared (`mention.undeclared`). IM-style `<@id>` placeholders never notify in cards; explicit lint preflight reports `mention.im_placeholder`, excluding Markdown code examples. The reserved `all` stays in the Markdown body only; it is never listed. Lint has no `--at-all` option and does not enforce the send-time 100-declaration limit or everyone-tag pairing; run send dry-run for those checks. Static matching accepts literal Markdown and resolvable absolute data paths from the same batch; runtime expressions remain unverified. Markdown code spans, code blocks and escaped tag examples are ignored. Matching uses a double-quoted `atId` as the sole attribute.
## Validation scope
Lint checks syntax and Schema. New-card preflight checks initialization, root, IDs, final-snapshot static references, cycles, and ownership. Multiple parents, repeated child slots, shared template sources, and static/template overlap fail even with empty arrays; unreachable nodes warn. Use distinct IDs per placement. Bindings, dynamic expansion, intermediate frames, and rendering remain unverified. Resource preflight rejects literal `data:` resource fields (`resource.inline_data_unsupported`); Markdown/`fallbackMarkdown` markers, even code/comments, only warn (`resource.markdown_data_unverified`) for review. This does not parse Markdown/HTML, evaluate bindings, or fetch resources; a clean report proves neither absence of inline media nor loading. Prefer HTTPS [assets](references/design.md#media-resources). `--fragment` never bypasses initialization. Do not silently remove requested data, components, or interactions.
Use self-check for package integrity, not named lookup. Review facts, actions, visual completeness, and first-frame values. Verify loading, functions, rendering, interaction, and writeback on the client; report delivery evidence separately.
## Delivery boundary
Follow the installed dingtalk-chat A2UI guidance for creation, updates, summaries, annotations, and completion; check leaf help for `--summary`, `--support-forward`, and update flags. A missing capability blocks delivery.
Send only when requested. Resolve exactly one target under the sending Profile: `--conversation-id` for a conversation or `--open-dingtalk-id` for a person. Names are not target IDs. JSON-encode the emitted `data.a2uiMessages` as one `--content` argument for `dws chat message send-a2ui-card`, with a readable fallback `--summary` and a policy-based `--support-forward=true|false` (default false). For a card instantiated from a business template, update only the data paths its business configuration authorizes.
A group card that @ members must keep the Markdown `<a atId>` tags and the send flags consistent: list each member id with `--at-open-dingtalk-ids` (sent as `atOpenDingTalkIds`, at most 100 declarations including duplicates) and add `--at-all` only when the body uses `<a atId="all">` (sent as `atAll`; the CLI rewrites the tag to the server highlight alias `<a atId="10">` on the wire). With explicit mention flags, a statically known mismatch fails with exit code 3. Valid Markdown bindings may receive their body after placeholder creation; the CLI cannot certify future values. Without mention flags, legacy bodies are preserved, including direct-chat markup. Single-chat cards reject explicit @ flags. The server fails closed: an invalid or unresolvable declared id, over-limit declarations, @ in a single chat, or an alias conflict rejects card creation with INVALID_PARAM and a nonzero exit; body tags that were never declared get no highlight or notification.
Creation starts in PROCESSING. Update with `dws chat message update-a2ui-card --biz-id <bizId> --content <message-strings> --flow-status <state>` under the original profile. Keep the same `surfaceId` and component IDs; send only intended `updateDataModel` or `updateComponents` changes, not another `createSurface` or unrelated defaults. Static cards require `--flow-status FINISH` with a nonempty valid delta; prepare it before sending and preserve business data. Stream complete messages, not JSON tokens. Resource preflight checks only the delta. If current state is known, validate it with the delta; Schema validity does not prove business safety.
`dws aicard preview --file a2ui.json` sends a self-preview in PROCESSING; `--dry-run` does not send. Finish a static preview with `dws chat message update-a2ui-card --flow-status FINISH`, the server-issued `data.bizId` (never request-side `bizCardId`), and `--a2ui-annotations` marking its main content component as `artifact`; otherwise the conversation summary becomes the server default. If it is absent, inspect `updateWarning` and the receipt; do not recreate automatically. `openTaskId` cannot be used with `query-send-status`.
After sending, identify this exact card from returned instance or message identifiers under the same Profile and target; generic card text and a nearby timestamp are not sufficient. Report request acceptance, readback, asynchronous summary, client rendering, and interaction separately; missing evidence stays unverified.
Report the file, checks actually run, and delivery result when requested. Local validation and request acceptance do not prove client rendering.
@@ -0,0 +1,188 @@
# Design guidance: make a valid card effective
`lint` checks JSON syntax and protocol structure. This guide helps choose information structure, layout, and visual hierarchy. Adapt visual advice to the user; verify the result on a target client. The bundled protocol governs fields, defaults, and enumerations. Use the contract lookup command in the Skill entrypoint for exact definitions.
## Global design guidance
1. **Prefer a transparent root for ordinary new cards.** Let the host provide the outer background instead of approximating its color. Preserve explicit surface designs, a reused template's frame and insets, and unrelated existing styling; see [root surface](#root-surface) and [headers and insets](#headers-and-card-insets).
2. **Show generation state when relevant.** For an Agent that is generating or executing, a `Markdown` component may prefix its `content` with `[AIGenerating]`. DingTalk supports this convention across its six platforms; remove it on completion, failure, or cancellation. For panel titles and execution updates, see the [progress pattern](patterns/progress.md).
3. **Group execution details.** Use `CollapsiblePanel` with `variant: "reasoning"` for an Agent generation or execution process. Choose its expanded state and internal layout from the content.
## 1. What each check establishes
| Layer | Checks | Evidence |
|---|---|---|
| Tools | Lint checks protocol structure; new-card preflight also checks initialization, final-snapshot references and component ownership, static cycles, and the inline-resource policy. See the Skill's [validation scope](../SKILL.md#validation-scope) for exact coverage | Run checks for the delivery scenario. Fix errors at their locations; decide whether an unreferenced-component warning matters for this card |
| Author review | Preserve user information, operations, and visual requirements; provide appropriate first-frame binding values; make priority, grouping, and density clear | Compare each request with its component or action and the host's existing state. Visual and content completeness are not lint diagnostics |
| Runtime acceptance | Resource loading, function evaluation, delivery, actual layout, interactions, and state writeback | Verify in the target host. Report request acceptance, conversation readback, and client rendering separately |
Passing structural checks does not establish the other two layers. Explain unsupported behavior or missing runtime evidence at delivery. Ensure that every requested piece of information, visual element, and action has an appropriate representation. Do not invent events or URLs to fill a checklist.
## Image reconstruction
**Identify regions and purpose before generating.** Record visible copy, numbers, region order, buttons, and states; distinguish interface layout from image assets before looking up components. This analysis guides implementation and need not become another file or exposed reasoning. Ask only about critical facts or interactions that cannot be determined. Do not guess unreadable content.
| Content in the image | Implementation |
|---|---|
| Interface copy, data, buttons, forms, lists | Preserve content and state with structured components. Bind data-driven fields only where supported, with typed initial values; update other fields through `updateComponents` |
| Photography, marketing posters, complex illustrations | Use the original asset or a reliable crop in `Image`; preserve its subject and essential text. Poster text need not be forcibly decomposed into components |
| Brand marks and specific document or product icons | Use a real asset or a crop of the reference. Do not impersonate them with emoji, ordinary characters, or vaguely similar icons. If the asset is unavailable, report the gap and, when appropriate, use an explicit text description |
| Generic action or status icons | Prefer a registered `Icon` with matching meaning. Otherwise use a supplied asset or text; do not invent an Icon name |
Crop media to the media region itself. Do not bake surrounding interface copy or interactive controls into the image; text and marks that belong to a poster may remain inside it. Do not substitute a screenshot of the entire interface for components, or destroy poster artwork just to make it structural. Use verified, host-accessible image URLs, preferably HTTPS. Avoid inline image bytes and placeholder paths; report a missing compatible asset. Resource fields are checked directly; Markdown resource text requires manual review as described under [media resources](#media-resources).
**Implement only known interactions.** For appearance-only reproduction, use a valid static representation and state that behavior is not connected. A pictured button does not reveal its event or URL. Implement bindings and actions when the user explicitly requires a form, data update, or host action. Extra business data and behavior require user input or a verifiable source.
**Compare before delivery.** Check key copy, numbers, dates, prices, region order, buttons, tags, and media against the source. Look for invented business facts or substituted brand assets. The component count need not equal the number of visual elements; preserve meaning and use. Report unreadable, unavailable, or unsupported content. Neither this comparison nor protocol preflight proves client visual fidelity.
At the requested logical width, compare text size, wrapping, and any block alignment required by the reference design. Choose `Text.sizeToken` from measurements in the target renderer; Token names alone do not establish pixel sizes. Keep the reference and report remaining visual differences.
## 2. Information roles and blocks
First rank the content: order the facts, group them by relationship, and assign visual weight before selecting components. A report may lead with the conclusion; an execution card may lead with progress. Keep supporting metadata quieter than the focal content. Short content needs no extra blocks. Do not add titles, metrics, or actions merely to fill a template. These combinations are optional layout notation, not a fixed order or required structure.
| Role | Purpose | Components | Layout notation |
|---|---|---|---|
| `header` | Title, source, and status | Row + Text + Tag | `Row(align=center, gap=8) > Text(bold, weight=1) + Tag` |
| `hero` | Key cover or person image | Image | `Image(variant=header, fit=cover, cornerRadius=10)` |
| `facts` | Label-value facts | Column + Row + Text | One-line rows use `align=center`; wrapping values use `align=start` and matching label/value typography so the label aligns with the value's first line; see [key-value rows](#key-value-rows) |
| `summary` | Conclusion-first prose | Markdown | Literal or bound content, for example `/content/body` |
| `metrics` | A few important measures | Row + Column + Text | `Row(gap=8) > Column(weight=1, padding=12) > Text(value, bold) + Text(label, caption)` |
| `data` | Comparison, trend, or composition | Markdown / Chart | For static tabular data, prefer Markdown table syntax over a separate `Table` component. Use a suitable Chart for graphical data and query its contract; see [report composition](patterns/report.md) for selection and data rules |
| `list` | Repeated entries | Column + Card + Text | — |
| `timeline` | Time- or stage-ordered progress | Column + Row + Text + Tag | — |
| `attachments` | File results | Divider + Text + File | `File(fileName, url, description, onPreview)` |
| `form` | Text and choice inputs | TextField + ChoicePicker | Group and bind business fields, such as `/form/*`; submit-button `context` refers to the relevant paths. See [form composition](patterns/form.md) |
| `actions` | Main and optional secondary action | Divider + Row + Button + Text | `Row(align=center, gap=8) > Button(default) + Button(primary)`; each Button's `child` references a Text |
| `details` | Infrequent supporting information | CollapsiblePanel + Markdown + Link | `CollapsiblePanel(variant=indented, defaultExpanded=false, maxHeight=240) > Column > Markdown + Link(openUrl)` |
| `progress` | Generation or execution stage | CollapsiblePanel + Markdown | `CollapsiblePanel(variant=reasoning) > Markdown` |
| `terminal-status` | Stable completed, failed, or cancelled state | Tag + Text + Link | — |
States, icons, illustrated entries, and highlights may support a main block or stand alone when content is simple. They do not each require a top-level section.
| Local combination | Use | Layout or choice |
|---|---|---|
| Compact header | Schedule, task, light reminder | `Text`; add a `Row` with a `Tag` only when there is a status to show |
| Content section | Related content needing a bounded visual surface | `Card(padding=12, cornerRadius=8, borderWidth=0.5) > Column(gap=8) > content` |
| Status | Current stage or risk | Tag + Text |
| Icon | Action, source, or status | Icon or Text.icon; use a registered matching icon, or a supplied image or explanation if none exists. Do not invent a name or default to emoji for a specific icon |
| Illustrated item | Compact image-and-text content | `Row(align=center, gap=12) > Column(weight=1) + Image(variant=smallFeature)`; see [content composition](patterns/content.md) for other image variants |
| Highlight | Key conclusion or local emphasis | See [local highlights](#local-highlights); this is a design technique, not a protocol field |
Select a relevant pattern from the Skill's [composition-pattern table](../SKILL.md#composition-patterns). Approval, schedules, and tasks share the detail pattern; ongoing generation uses the [progress pattern](patterns/progress.md). For mixed content, select only needed blocks and avoid duplicating headers or actions. When concrete message shapes would help, read the pattern's linked JSON; use the [example index](protocol/examples/README.md) only if unsure which fits. Examples initialize new cards; adapt them for host-created Surfaces or updates as described in the [Skill entrypoint](../SKILL.md#construct-and-validate). The protocol and contract lookup govern fields.
## 3. Host behavior and layout
The bundled protocol governs field capabilities. Visual numbers are starting points, not extra protocol limits. One preview width is neither a minimum supported width nor an acceptance gate; check wrapping, overflow, and actions at the actual narrow and wide container widths supported by the target host.
Use Markdown for rich text and Text for short titles and labels. Reserve Markdown headings for actual document sections; a small field or status label does not need heading hierarchy. Keep independent facts, statuses, and controls in appropriate components rather than flattening the card into one Markdown block.
1. `Row.align=stretch` can make containers in a row the same height. Container children and leaf components are handled differently; consult the Row contract for client behavior and integration boundaries.
2. Check long titles, metrics, and multiple columns at the target conversation width. Wrap or reduce adjacent items when space is limited; do not assume identical client widths.
3. `Image.variant=smallFeature` is a starting point for illustrated cards; adjust to the asset and protocol size guidance. Use [content composition](patterns/content.md) for other image variants and media behavior.
4. Usually emphasize one primary action within a visible action area. Assess separate Tabs or states independently, not by counting every Button in the component list. Label actions by what they do, not by phrases such as “click the button below.”
5. Remove empty rows and dividers without information value. Choose truncation, full display, or collapse for long content; do not hide necessary text with `maxLine`.
6. Check media URLs under the host policy; lint does not fetch them. Prefer Markdown table syntax for static tabular data. Limit rows or link to details for long tables. If a separate `Table` is explicitly required, its pagination is local, so provide the data at once; see [report composition](patterns/report.md).
7. Query a component for defaults and conditions under which a field takes effect. The host may apply defaults when fields such as `Image.variant` are omitted.
Keep related content close and separate groups with appropriate space. Set `gap` and `padding` on the containers responsible for layout rather than stacking padding at several levels. Multiples of 2 are a reasonable spacing starting point, adjustable for host defaults, density, and user requirements; they are not validation rules.
### Root surface
For ordinary new cards, prefer a transparent root so the host supplies the outer background. A configured color or theme token is not guaranteed to match the message surface across clients, themes, or tenants. Do not use `common_bg_color` merely to imitate the host background.
| Intent | Root and background |
|---|---|
| Ordinary vertical content | Prefer `Column`; omit `backgroundColor` and `backgroundColorToken` |
| A root `Card` is needed without a separate surface | Set `backgroundColor: "#00FFFFFF"`; omit `backgroundColorToken` |
| An explicitly requested theme-aware surface | Use a registered `backgroundColorToken` and verify the result in the target host |
| An explicitly requested custom `Card` surface | Use `backgroundColor` with `light` and `dark` values, or a single color when a fixed color is intended |
| An edit to an existing card | Preserve the root, background, and unrelated styling unless the requested change requires otherwise |
The public `Card` contract uses `#FFFFFFFF` in light mode and `#FF1F1F1F` in dark mode when both background fields are omitted. Omission is therefore not a request for transparency. `backgroundColorToken` takes precedence over `backgroundColor`; leave it out when requesting a transparent surface. `Column` also supports borders and corner rounding, so those features alone do not require an extra `Card` wrapper.
Use filled panels for local grouping or emphasis, not as an automatic full-body layer beneath a transparent root. The content-section `Card` above illustrates such a local panel. Preserve business-content backgrounds required by the user or reference design; transparency is a default, not a reason to override that design. When reconstructing a screenshot, do not reproduce the host chat bubble or window background as the card's own fill unless the user explicitly requests a separate surface.
These are authoring defaults, not Schema restrictions or cross-client rendering guarantees. Default, opaque, and themed `Card` backgrounds remain valid. Check background, insets, and readability in the target client's light and dark themes before claiming visual consistency.
### Headers and card insets
Omit root `padding` for ordinary cards: omission allows default insets, while explicit `0` can leave mobile content flush against the edge. Do not add a second whole-body inset by default; local content panels may still need padding.
Use Text or a Row for ordinary titles rather than defaulting to CardHeader. `CardHeader` can trigger header-specific spacing that differs across hosts. When explicitly requested, preserve it and verify insets and any requested corner rounding on target clients; do not assume a full-bleed header or identical spacing. These are authoring defaults, not Schema restrictions: `CardHeader` and explicit zero padding remain valid.
### Key-value rows
- Put related facts in one `Column`. For a single-line key-value pair use `Row(align=center, gap=8)`. If the value can wrap, use `Row(align=start, gap=8)` and the same text size/line height for label and value; color may mute the label. This keeps the label centered against the value's first line instead of the entire paragraph. Give the value `weight=1` for remaining width.
- End a visible key label with a colon (`:` in Chinese text), such as `ISO 8601:` or `Elapsed time:`. Keep the value in a separate Text component so it can wrap without moving the label.
- Labels and values may both use `body`, with `common_level2_base_color` and `common_level1_base_color` respectively. Consider `caption` labels for compact content.
- Omit `weight` for a naturally sized label; do not write `weight=0`. Labels of different lengths do not guarantee aligned value columns. Stack a long label and value vertically when width is insufficient rather than assuming unsupported fixed columns.
- `Text` displays at most three lines by default. Set sufficient `maxLine` for important long values, or use full text or collapsible details. Do not truncate every value to two lines.
### Local highlights
- An ordinary content section may use `Card.backgroundColorToken=common_fg_z1_color`, `borderWidth=0.5`, and `borderColorToken=common_line_hard_color`.
- A highlighted content section may use a registered `extended_*0_color` for `Card.backgroundColorToken`, the matching color family's `extended_*1_color` for `borderColorToken`, and `borderWidth=0.5`. For example, pair `extended_blue0_color` with `extended_blue1_color`; never mix families.
- `padding=12` and `cornerRadius=8` are starting points for a local `Card`; adjust to the content and host spacing. `Card` accepts one `child`, so wrap multiple elements in a `Column` or `Row`.
- Give a key conclusion or risk its own block when needed, for example `Column(padding=12, gap=8) > Text(bold) + Markdown`; add a local background only when useful.
- Emphasize a short label within `Markdown.content`, for example `**Risk:** Only completed tasks are included.`, without wrapping every note in another container.
- Choose background and border independently according to the hierarchy; neither is mandatory. When both appear, keep them in one semantic color family and select actual values from the [Token index](index/tokens.md).
- `borderWidth` is numeric, for example `0.5`, not a string with units. `*` denotes a family only: send complete registered Token IDs and do not put Token names in hex-color fields.
- Use component defaults when no emphasis is needed. Do not add a colored container for decoration or repeat the same highlighted block in every subsection. A short bold label often suffices.
- Use only supported fields. Simplify a one-sided indent or inline treatment that cannot be expressed accurately. Do not invent a field or pad with spaces; container `padding` affects all sides.
## 4. Color discipline
Follow the user's requested style. When color helps emphasize key metrics, titles, or focal areas, choose a suitable color family; neutral component defaults are also valid. A local panel can pair a pale background with stronger accents from the same family while ordinary body text stays neutral. A transparent root does not require a monochrome card. Avoid coloring every section, and preserve business-status meanings independently of the visual palette.
Prefer registered Tokens or semantic component variants (`Tag.theme`, `CardHeader.theme`). Some fields accept hex colors; consult their contracts. For semantic colors, consider:
| Meaning | `Tag.theme` / `CardHeader.theme` | Text `colorToken` |
|---|---|---|
| Success / accepted | `green` | `common_green1_color` |
| Failure / warning / dangerous action | `red` | `common_red1_color` |
| Pending / reminder | `orange` | `common_orange1_color` |
| Information / neutral | `blue` | — |
| Primary text | — | `common_level1_base_color` |
| Secondary text | — | `common_level2_base_color` |
| Supporting / placeholder text | — | `common_level3_base_color` |
| Divider | — | `common_line_light_color` (`Divider.borderColorToken`) |
Use a Token or theme for one meaning within a card. Express important status in text, not color alone. The host resolves actual colors, so a local block needs sufficient contrast against its background. Prefer `Tag.theme` for capsule states and contract-supported Tokens for container colors. Component defaults are valid too; a card does not need a quota of explicit Tokens. See the full [Token index](index/tokens.md).
Check text against its actual background in the target theme. Use WCAG 1.4.3's 4.5:1 threshold for normal text and 3:1 for large text, following its [size definitions and exceptions](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html). Semantic Tokens and component defaults do not establish sufficient contrast.
Keep ordinary key-value labels neutral; use semantic color for a value only when it conveys status or trend. Three or fewer non-gray semantic colors is a starting point for an ordinary information card, not a limit on charts, brands, or complex states.
## 5. Hierarchy and density
Choose the main visual area from the content: an available relevant image for illustrated content, key metrics for a report, or the current goal for a task. Give it more emphasis than supporting details without inventing content for decoration. Remove copy that merely repeats an adjacent heading or action; consolidate only equivalent missing-information notices, preserving distinct business meanings. When useful, put secondary details in a collapsed section or a details Tab; keep essential risks and required inputs visible, and do not add Tabs to simple cards.
- **Text hierarchy:** emphasize focal `Text` with `bold=true` when needed. Use `variant=body` for normal text or `variant=caption` for smaller supporting text. Optional `colorToken` values include `common_level1_base_color` for primary text and `common_level2_base_color` for secondary text; preserve requested typography.
- **Top-level grouping:** two to five groups can guide a complex card. A short notice may have one, and complex content may need more.
- **Group with containers:** put related fields in one `Column` or `Card`. Use `Divider` between semantic sections, such as content and actions, not between every row.
- **Contain long content:** use `CollapsiblePanel(defaultExpanded=false)` for details. Offer a “View all” `Link` when a real full-content destination exists.
- **Separate display fallback from business data:** missing information may display “—” or “None yet,” or omit an empty section. Never write display fallback into a form, number, or boolean value. Empty string, unchecked `false`, unselected `[]`, and numeric `0` retain their distinct meanings.
## 6. Revise feedback at the right field
After a user reviews the card, locate the affected fields and check related effects:
| Feedback | Change | Recheck |
|---|---|---|
| Copy or wording | The corresponding `/content/*` value in `updateDataModel`, or `Text.text` / `Markdown.content` | Factual accuracy |
| Color or style | `Tag.theme` / `CardHeader.theme` / `colorToken` | Meaning and contrast |
| Layout | `Row` ↔ `Column`, `align` / `gap` / `weight` / `padding` | Grouping and references |
| Missing information | Add it to the relevant role; initialize its model value when bound | Information and bindings |
| Too dense or too sparse | `gap` / `padding` / `maxLine`, or collapse details in `CollapsiblePanel` | Density at actual width |
| Button label or appearance | Referenced `Button.child` Text or its binding; `ButtonGroup.buttons[].label`; supported style or disabled fields | Preserve the action when only presentation changes |
| Button behavior | Inspect the existing `action.event` or `action.functionCall` and change only the requested behavior; event data belongs in `action.event.context` | Intended behavior, parameters, and existing result bindings |
Rerun lint and deliver the revised file and check results. Send a real card only when requested and supported by an available delivery route.
## Media resources
Prefer verified HTTPS assets. Retain another URL scheme or relative address only when allowed by the field contract and verified in the target host. This authoring policy avoids inline `data:` resources, including Base64; it does not change public Schema validity or establish client support. Explain the policy and obtain a compatible asset before delivery.
Resource preflight rejects literal `data:` values in component resource fields with `resource.inline_data_unsupported`. Markdown content and `fallbackMarkdown` containing `data:` text receive a nonblocking `resource.markdown_data_unverified` warning for manual review, including when that text is only a code example or comment. This is a marker check, not a Markdown/HTML parser or a complete resource detector. Review image destinations before delivery; an absent warning proves neither absence of inline images nor loading support. For example, an Image URL bound to a `data:` value receives no diagnostic. Table rows and chart data/config remain business values. The check does not fetch URLs or evaluate bindings/functions; verify resolved resources and client loading separately.
Use supplied or verified assets. If one is unavailable, disclose the gap or ask for an accessible URL; do not invent URLs, impersonate a real avatar with an unrelated image, or silently upload private screenshots. Any agreed placeholder must be identified as a substitution, not faithful reconstruction.
@@ -0,0 +1,57 @@
# Component index (47 total; 10 common, 37 others)
Choose a component here, then query its fields and minimal example as shown in the Skill entrypoint. Start with common components and inspect other groups only when needed.
Required fields include `id` and `component`. Child references show the field path to child components; `[]` denotes an array item.
| Component | Group | Required | Child references | Purpose |
|---|---|---|---|---|
| `Button` | Common components | action, child, id | child | Runs a connected action; its label belongs in the child component. |
| `Card` | Common components | child, id | child | Single-child card container with padding, background, and rounded corners. |
| `Column` | Common components | children, id | children | Child nodes are stacked from top to bottom. |
| `Divider` | Common components | id | — | Horizontal or vertical separator line. |
| `Icon` | Common components | id, name | — | DingTalk icon set and loading status. |
| `Image` | Common components | id, url | — | Image display with a dark-theme image alternative. |
| `Markdown` | Common components | content, id | — | Markdown headings, lists, code, and quotes. |
| `Row` | Common components | children, id | children | Child nodes are arranged from left to right. |
| `Tag` | Common components | id, text | — | Compact status or category label styled with theme and variant. |
| `Text` | Common components | id, text | — | Text, font size, color, and streaming state. |
| `ButtonGroup` | Composition | buttons, id | — | Groups inline buttons; static prototypes may omit item actions. |
| `CardHeader` | Composition | id, title | trailing | Unified card header with a theme, dynamic title, dark-theme icon, and trailing content. |
| `CollapsiblePanel` | Composition | children, id, title | children | Unified collapsible panel, collapsed by default. |
| `ColumnLayout` | Composition | children, id | children | Responsive columns from one to six. |
| `GridLayout` | Composition | children, id | children | Grid layout supporting static children and data templates. |
| `Link` | Composition | id, text | — | Clickable link text and navigation. |
| `List` | Composition | children, id | children | Arranges components or data templates horizontally or vertically. |
| `Loop` | Composition | children, id | children | Data-driven repeated-rendering container. |
| `ScrollView` | Composition | id | children | A horizontal or vertical scroll container. |
| `Stack` | Composition | child, id | child, overlays.[].child | Z-axis stacking container: A **base layer** determines the size, on which 0~N **overlay… |
| `Tabs` | Composition | id, tabs | tabs.[].child | Tab header and content panel. |
| `CheckBox` | Inputs | id, label, value | — | Boolean selection. |
| `CheckableImageList` | Inputs | id, images, value | — | Selectable layout for one to nine images, cropped to 4:3 with row and column gaps of 12. |
| `CheckboxListMulti` | Inputs | children, id | children | Multi-select list and local state. |
| `ChoicePicker` | Inputs | id, options, value | — | Selects one or more options; variant determines selection mode. |
| `ConversationPicker` | Inputs | id, value | — | Call the DingTalk conversation selector. |
| `DateTimeInput` | Inputs | id, value | — | Date and time picker entry. |
| `ImageUpload` | Inputs | id, value | — | Call DingTalk image upload, fill the upload result back into the `value` binding path, … |
| `InputList` | Inputs | id, value | — | Input list whose rows can be added and removed. |
| `NumberInput` | Inputs | id, value | — | Numeric input entry that opens an input panel and displays the result. |
| `Rating` | Inputs | id, value | — | Star rating. |
| `Slider` | Inputs | id, max, value | — | Numeric slider. |
| `Switch` | Inputs | id | — | Switch state and size. |
| `TextField` | Inputs | id, label | — | Collects text and saves input through a value binding. |
| `UserPicker` | Inputs | id, value | — | Opens the DingTalk contact picker to select users. |
| `AudioPlayer` | Data and media | id, url | — | Audio cover, title, and playback progress. |
| `Avatar` | Data and media | id | — | Avatar image. |
| `AvatarGroup` | Data and media | id, items | — | Avatar stacking and overflow count. |
| `Chart` | Data and media | data, id | — | Shows trends, comparisons, or proportions using contract-defined data. |
| `Countdown` | Data and media | endTime, id | — | Countdown and end event. |
| `ElapsedTime` | Data and media | id | — | Elapsed-time counter. |
| `File` | Data and media | fileName, id, url | — | File artifact. |
| `ImageCarousel` | Data and media | id, items | — | DingTalk image carousel component. |
| `ImageList` | Data and media | id, images | — | Multiple image thumbnails and quantity aggregation. |
| `ProgressBar` | Data and media | id, value | — | Shows completion as a continuous bar or compact blocks. |
| `Table` | Data and media | data, id | — | Shows structured records for row-by-row review with local pagination. |
| `Video` | Data and media | id, url | — | Cover, inline playback, and control bar. |
---
Generated from `references/protocol/` (fingerprint `cf4611469d2c3f5e`). Do not edit this index: protocol changes regenerate it. For exact fields and a minimal example, query a name as shown in the Skill entrypoint; the whole catalog need not enter the model context.
@@ -0,0 +1,49 @@
# Function index (40 total; 7 host actions)
Value functions use `{"call": …, "args": {…}}` and may nest. Host actions require explicit `catalogId: urn:dingtalk:a2ui:host:v1`; the system function `@index` must not carry it. `checks[].condition` requires a boolean-returning function.
| Function | Group | Returns | Required arguments | Host action | Purpose |
|---|---|---|---|---|---|
| `and` | Core functions | boolean | values | | Performs logical AND on a list of boolean values. |
| `email` | Core functions | boolean | value | | Checks that the value is a valid email address. |
| `formatCurrency` | Core functions | string | currency, value | | Formats a number as a currency string. |
| `formatDate` | Core functions | string | format, value | | Formats a timestamp into a string using Unicode TR35 date pattern. |
| `formatNumber` | Core functions | string | value | | Formats a number with grouping and decimal precision. |
| `formatString` | Core functions | string | value | | Uses `${/path}` for data-path interpolation. |
| `length` | Core functions | boolean | value | | Checks string length constraints. |
| `not` | Core functions | boolean | value | | Performs logical NOT on a boolean value. |
| `numeric` | Core functions | boolean | value | | Checks numeric range constraints. |
| `openUrl` | Core functions | void | url | | Opens the specified URL in a browser or handler. |
| `or` | Core functions | boolean | values | | Performs logical OR on a list of boolean values. |
| `pluralize` | Core functions | string | other, value | | Returns a localized string based on CLDR plural category of the count. |
| `regex` | Core functions | boolean | pattern, value | | Checks that the value matches a regular expression string. |
| `required` | Core functions | boolean | value | | Checks that the value is not null, undefined, or empty. |
| `add` | Expressions | number | a, b | | DingTalk extension function. |
| `arrayFind` | Expressions | any | target, value | | DingTalk extension function. |
| `arrayGet` | Expressions | any | index, value | | DingTalk extension function. |
| `cond` | Expressions | any | else, if, then | | DingTalk extension function. |
| `div` | Expressions | number | a, b | | DingTalk extension function. |
| `eq` | Expressions | boolean | a, b | | Tests whether two values are equal. |
| `getLength` | Expressions | number | value | | DingTalk extension function. |
| `gt` | Expressions | boolean | a, b | | DingTalk extension function. |
| `gte` | Expressions | boolean | a, b | | DingTalk extension function. |
| `lt` | Expressions | boolean | a, b | | DingTalk extension function. |
| `lte` | Expressions | boolean | a, b | | DingTalk extension function. |
| `mod` | Expressions | number | a, b | | DingTalk extension function. |
| `mul` | Expressions | number | a, b | | DingTalk extension function. |
| `ne` | Expressions | boolean | a, b | | Tests whether two values are unequal. |
| `objectGet` | Expressions | any | key, value | | Reads an object property by `key`. |
| `sub` | Expressions | number | a, b | | DingTalk extension function. |
| `toDouble` | Expressions | number | value | | DingTalk extension function. |
| `toLong` | Expressions | number | value | | DingTalk extension function. |
| `copyText` | Host actions | void | text | Yes | Copies text. |
| `openChat` | Host actions | void | openConversationId | Yes | Opens a conversation. |
| `previewImages` | Host actions | void | urls | Yes | Previews 1–100 images. |
| `previewVideo` | Host actions | void | url | Yes | Previews a video. |
| `promptText` | Host actions | object | — | Yes | Opens a single-line input panel. |
| `showConfirm` | Host actions | object | message | Yes | Shows a confirmation dialog. |
| `showModal` | Host actions | object | buttons, message | Yes | Shows a dialog with custom buttons. |
| `@index` | System function | number | — | | Returns the 0-based index of the current item when rendering a dynamic list fro… |
---
Generated from `references/protocol/` (fingerprint `cf4611469d2c3f5e`). Do not edit this index: protocol changes regenerate it. For exact fields and a minimal example, query a name as shown in the Skill entrypoint; the whole catalog need not enter the model context.
@@ -0,0 +1,134 @@
# Token index
`colorToken` and `IconName` use protocol enums. Prefer a registered full ID for `sizeToken`; other strings depend on host compatibility or fallback. Follow each field's Schema for container colors. Use a given Token for one meaning within a card.
## Colors: `ColorToken` (57 items)
- **yellow**: `extended_yellow0_color`, `extended_yellow1_color`, `extended_yellow6_color`
- **orange**: `extended_orange0_color`, `extended_orange1_color`, `extended_orange6_color`, `common_orange1_color`
- **red**: `extended_red0_color`, `extended_red1_color`, `extended_red6_color`, `common_red1_color`
- **magenta**: `extended_magenta0_color`, `extended_magenta1_color`, `extended_magenta6_color`
- **purple**: `extended_purple0_color`, `extended_purple1_color`, `extended_purple6_color`
- **deeppurple**: `extended_deeppurple0_color`, `extended_deeppurple1_color`, `extended_deeppurple6_color`
- **blue**: `extended_blue0_color`, `extended_blue1_color`, `extended_blue6_color`
- **darkgreen**: `extended_darkgreen0_color`, `extended_darkgreen1_color`, `extended_darkgreen6_color`
- **green**: `extended_green0_color`, `extended_green1_color`, `extended_green6_color`, `common_green1_color`
- **level**: `common_level1_base_color`, `common_level2_base_color`, `common_level3_base_color`, `common_level4_base_color`
- **stamp**: `common_stamp_color`
- **link**: `common_link_color`
- **bg**: `common_bg_color`
- **fg**: `common_fg_z1_color`
- **line**: `common_line_light_color`, `common_line_hard_color`
- **white**: `common_white1_color`, `common_white2_color`, `common_white3_color`, `common_white4_color`, `common_white5_color`, `common_white6_color`
- **black**: `common_black1_color`, `common_black2_color`, `common_black3_color`, `common_black4_color`, `common_black5_color`, `common_black6_color`
- **theme**: `theme_primary3_color`, `theme_primary2_color`, `theme_primary_hover_color`, `theme_primary1_color`, `theme_primary_press_color`
| Token | Meaning |
|---|---|
| `extended_yellow0_color` | Yellow background color; used for background filling in areas o… |
| `extended_yellow1_color` | Yellow border color; used for outlining areas of this color sch… |
| `extended_yellow6_color` | Yellow text color; used for text emphasis in this color scheme. |
| `extended_orange0_color` | Orange background color; used for area background filling in th… |
| `extended_orange1_color` | Orange border color; used for outlining areas of this color sch… |
| `extended_orange6_color` | Orange text color; used for text emphasis in this color scheme. |
| `extended_red0_color` | Red background color; used for background filling in areas of t… |
| `extended_red1_color` | Red border color; used for the stroke of areas in this color sc… |
| `extended_red6_color` | Red text color; used for emphasizing text in this color scheme. |
| `extended_magenta0_color` | Magenta background color; used for background filling in areas … |
| `extended_magenta1_color` | Magenta border color; used for outlining areas of this color sc… |
| `extended_magenta6_color` | Magenta text color; used for text emphasis in this color scheme. |
| `extended_purple0_color` | Purple background color; used for background filling in areas o… |
| `extended_purple1_color` | Purple border color; used for the stroke of areas in this color… |
| `extended_purple6_color` | Purple text color; used for emphasizing text in this color sche… |
| `extended_deeppurple0_color` | Deep purple background color; used for background filling in ar… |
| `extended_deeppurple1_color` | Deep purple border color; used for outlining areas of this colo… |
| `extended_deeppurple6_color` | Deep purple text color; used for text emphasis in this color sc… |
| `extended_blue0_color` | Blue background color; used for background filling in areas of … |
| `extended_blue1_color` | Blue border color; used for the stroke of areas in this color s… |
| `extended_blue6_color` | Blue text color; used for emphasizing text in this color scheme. |
| `extended_darkgreen0_color` | Dark green background color; used for area background filling i… |
| `extended_darkgreen1_color` | Dark green border color; used for outlining areas of this color… |
| `extended_darkgreen6_color` | Dark green text color; used for text emphasis in this color sch… |
| `extended_green0_color` | Green background color; used for background filling in areas of… |
| `extended_green1_color` | Green border color; used for outlining areas of this color sche… |
| `extended_green6_color` | Green text color; used for text emphasis in this color scheme. |
| `common_level1_base_color` | Primary text and icons; used for titles, body text, and other m… |
| `common_level2_base_color` | Secondary text and icons; used for secondary information and au… |
| `common_level3_base_color` | Tertiary text and icons; used for weaker supplementary informat… |
| `common_level4_base_color` | Text and icon color in a disabled state; only conveys visual st… |
| `common_stamp_color` | Watermark color; used for low-interference watermark content. |
| `common_link_color` | Hyperlink text and highlight border color. |
| `common_bg_color` | Background color of the area; used for the background of a page… |
| `common_fg_z1_color` | Foreground area background color; used for content areas on top… |
| `common_line_light_color` | Weak divider; used for lighter content separation. |
| `common_line_hard_color` | Strong divider line; used for content separation where a cleare… |
| `common_red1_color` | Danger state color; used to convey dangerous or erroneous seman… |
| `common_orange1_color` | Warning state color; used for prompts that require the user's a… |
| `common_green1_color` | Success state color; used for success or completion feedback. |
| `common_white1_color` | White mask; used for white overlay, actual transparency is dete… |
| `common_white2_color` | White mask (designed as 40% level); used for white overlays, ac… |
| `common_white3_color` | White mask (designed as 30% level); used for white overlays, ac… |
| `common_white4_color` | White mask (designed as 20% level); used for white overlays, ac… |
| `common_white5_color` | White mask (designed as 10% level); used for white overlay, act… |
| `common_white6_color` | White mask (designed as 5% level); used for white overlay, actu… |
| `common_black1_color` | Black mask; used for black overlay, actual transparency is dete… |
| `common_black2_color` | Black mask (designed as 40% level); used for black overlays, ac… |
| `common_black3_color` | Black mask (designed as 30% level); used for black overlays, ac… |
| `common_black4_color` | Black mask (designed as 20% level); used for black overlays, ac… |
| `common_black5_color` | Black mask (designed as 10% level); used for black overlays, ac… |
| `common_black6_color` | Black mask (designed as 5% level); used for black overlays, act… |
| `theme_primary3_color` | Theme background color; used for the background of areas that c… |
| `theme_primary2_color` | Light theme color; used for lighter theme color highlights and … |
| `theme_primary_hover_color` | Theme hover color; used for the visual feedback of the theme co… |
| `theme_primary1_color` | Theme primary color; used for main theme emphasis. |
| `theme_primary_press_color` | Theme pressed color; used for the visual feedback of the theme … |
## Font sizes: `SizeToken` (12 items)
| Token | Meaning |
|---|---|
| `common_largetitle_text_style__font_size` | Main title, used as the heading for a page or major content section. |
| `common_supertitle_text_style__font_size` | Extra-large title, used as the main title for a page or module. |
| `common_h1_text_style__font_size` | Level 1 heading, used for card or content area titles. |
| `common_h2_text_style__font_size` | Level 2 heading, used for content-group headings. |
| `common_h3_text_style__font_size` | Level 3 heading, used for smaller-level content titles. |
| `common_h4_text_style__font_size` | Level 4 heading, used for subheadings or supplementary information unde… |
| `common_body_text_style__font_size` | Main text, used for the primary content. |
| `common_action_text_style__font_size` | Action text, used for buttons, links, and other operational copy. |
| `common_description_text_style__font_size` | Auxiliary explanation, used for secondary information and supplementary… |
| `common_footnote_text_style__font_size` | Footnotes, used for sources, notes, image captions, or status tags. |
| `common_tiny_text_style__font_size` | Small text prompts, used as subtle hints in space-constrained scenarios. |
| `common_subhead_text_style__font_size` | Subtitle, used for supplementary information below the title. |
## Icons: `IconName` (24 items)
| Token | Meaning |
|---|---|
| `Search_L_outlined` | Search. |
| `Language_L_outlined` | Language-switch icon. |
| `CodeProgram_L_outlined` | Code. |
| `Picture_L_outlined` | Image. |
| `Folder_L_outlined` | Folder. |
| `Edit_L_outlined` | Edit or pen icon. |
| `ListView_L_outlined` | List View 1. |
| `ManagementBackground_L_outlined` | Admin console icon. |
| `Tool_L_outlined` | Tools. |
| `Delete_L_outlined` | Delete. |
| `SpinLoading_L_outlined` | Loading spinner icon. |
| `UploadOne_L_outlined` | Upload. |
| `DownloadAndSave_L_outlined` | Download and save. |
| `Check_L_outlined` | Check mark. |
| `Copy_L_outlined` | Copy. |
| `Close_L_outlined` | Close, cross. |
| `More_L_outlined` | More, ellipsis, three dots. |
| `Error_L_outlined` | Warning, error reminder, circle with exclamation mark. |
| `InformationThere_L_outlined` | Information prompt, circular letter i. |
| `Setting_L_outlined` | Settings, gear. |
| `ExpandThere_L_outlined` | Expand. |
| `Collapse_L_outlined` | Collapse. |
| `Link_L_outlined` | Link, chain. |
| `Share_L_outlined` | Share, forward. |
---
Generated from `references/protocol/` (fingerprint `cf4611469d2c3f5e`). Do not edit this index: protocol changes regenerate it. For exact fields and a minimal example, query a name as shown in the Skill entrypoint; the whole catalog need not enter the model context.
@@ -0,0 +1,22 @@
# Content: illustrated information and resources
Use for summaries, recommendations, campaign descriptions, media, and file results. Include images and destinations only when the assets and behavior exist.
| Needed presentation | Optional components | When to choose them |
|---|---|---|
| Prose and citation | `Markdown + Link` | Continuous text, paragraphs, and source. Keep only the prose when no link exists |
| Cover and summary | `Column > Image + Text/Markdown + ButtonGroup` | Content or product with an existing main visual |
| Compact illustrated item | `Row > Image + Column(weight=1, Text, Text)` | Choose an explicit Image variant; `smallFeature` is a thumbnail candidate. Image and text can swap sides |
| Whole item opens details | `Row(action) / Column(action) > illustrated content` | Put a known detail action on the container instead of adding a redundant button; keep unknown behavior static |
| Multiple recommendations | `List > illustrated or text items` | Direct child components for static entries; a template for data-driven entries |
| Multiple evidence images | `Text + ImageList` | Photos or result gallery; consult `ImageCarousel` for individual switching |
| File result | `Text/Markdown + File` | Filename, download resource, and description; do not disguise plain text as an attachment |
| Audio or video | `AudioPlayer` or `Video`, with explanation | Use only with a real playable resource; a static cover is not playback |
Do not omit `Image.variant` in a compact illustrated item: the current default is full-row width and 200 high. Choose dimensions for the asset and layout. `fit=contain` preserves the whole image; `fit=cover` permits cropping. When the outer item opens details and its image is only a thumbnail, `previewEnabled=false` can disable the image's own large preview.
`List.children` accepts component IDs or a template object; `ImageList.images` is an array of image URLs. They have different shapes. A `File` preview is configured by `onPreview`; its appearance alone does not make it clickable.
Keep genuine posters and photos as images, while interface text and controls remain structured. See [image reconstruction](../design.md#image-reconstruction) for brand assets and crop boundaries. For repeated item templates, inspect `Loop` in the [data report example](../protocol/examples/data-report.json).
When content is absent, show a clear empty result instead of an empty media container. If media or attachments arrive later, preserve already-valid summary text and update only the affected content.
@@ -0,0 +1,36 @@
# Detail: object facts and decisions
Use for an approval request, schedule item, task, order, or another object with facts and actions. Select only needed combinations; a title, status, and button are not mandatory on every card.
| Needed presentation | Optional components | When to choose them |
|---|---|---|
| Title and current status | `Text`, or `Row > Text + Tag` | Add a Tag only for an actual status; for CardHeader see [header layouts](../design.md#headers-and-card-insets) |
| Labels and values | `Column > Row > Text + Text` | A few object attributes; see [key-value rows](../design.md#key-value-rows) for long values |
| Fact and adjacent destination | `Row > Text + Link` | A role and "View role," or an attachment name and its details |
| Reason or supporting material | `Markdown`, `File`, optionally within `CollapsiblePanel` | Choose separately for prose, a real file, and collapsible detail |
| Adjacent text actions | `ButtonGroup` | Inline labels and actions; enable narrow-width wrapping if the contract permits |
| Action with submit validation | `Button > Text` | Use a standalone Button when `checks` are needed; arrange several Buttons in a Row or Column |
`CardHeader.trailing` is one component ID. `Button.child` references Text; the current renderer does not support wrapping arbitrary layouts such as Row in a Button. `ButtonGroup` items are not child component IDs and do not take `checks`. For a clickable illustrated block, see the container-action combination in [content](content.md).
Preserve recognizable date and timezone in schedules, and show old and new values together when relevant. Update decision and task state from a real business result; clicking alone does not mean success. Add only necessary inputs from the [form pattern](form.md) when the user must provide a comment or parameters.
## Short notifications
Use for a short result, risk alert, or status change. Choose by the event semantics, reading behavior, and expected action, not by words such as "notification" or "reminder" alone. A decision, investigation, report, or data-entry request may need a different composition.
Make the event and its consequence easy to scan. Group the cause and related observations together, then place the next step after the explanation. Keep source and status secondary unless the status itself is the focal fact. A short notice can still need these distinct semantic groups; do not flatten them into one paragraph or give every sentence an equally prominent colored box.
| Needed presentation | Optional components | When to choose them |
|---|---|---|
| Compact reminder | `Row > Icon + Text + Link`, then `Text` or `Markdown` | Title, source, and detail destination in one row; add icon and link only if useful |
| Title and explanation | `Column > Text + Markdown` | Add status or actions only when needed |
| Result or exception | `Row > Tag + Text`, then explanation | A distinct status and body; a very short result may use text alone |
| Capacity or quota | `Text + ProgressBar + Text` | A real computable ratio; show a number directly if only balance or remaining amount is known |
`Tag` supplies status styling without hand-built colors. For a requested `CardHeader`, use the [header and inset guidance](../design.md#headers-and-card-insets); use [image reconstruction](../design.md#image-reconstruction) for a branded title icon.
- Keep the event or issue title as the focal fact. Do not replace it with a generic header or duplicate the sole title in a separate identity strip.
- For appearance-only reproduction, `ButtonGroup.buttons[]` items may omit `action` and keep their noninteractive appearance. Standalone `Button.action` remains required by the protocol.
Use [information roles](../design.md#2-information-roles-and-blocks), [hierarchy and density](../design.md#5-hierarchy-and-density), and [target-width guidance](../design.md#3-host-behavior-and-layout) for grouping, emphasis, and wrapping.
@@ -0,0 +1,39 @@
# Form: input and feedback submission
Use for parameters, settings, ratings, and comments. For display-only information, use the [detail pattern](detail.md).
| Input need | Optional components | When to choose them |
|---|---|---|
| Free text or comments | `TextField + Button` | Submit entered text; group multiple fields by business purpose |
| Fixed choices or feedback reasons | `ChoicePicker + Button` | Single or multiple choice; a static Tag is not a selectable input |
| Number, date, or person | `NumberInput / DateTimeInput / UserPicker` | Use the native input for the field meaning instead of turning everything into text |
| Boolean setting | `Text + Switch` or `CheckBox` | A settings row or a choice with full explanation; the business flow decides whether it takes effect immediately or on submit |
| Rating and explanation | `Rating + TextField + Button` | Optional comment with a star rating; use ChoicePicker for named satisfaction choices |
| Image or variable entries | `ImageUpload` or `InputList`, with `Button` | Only when uploading or dynamic entry is required |
`ChoicePicker.variant` sets single or multiple selection. `displayStyle` sets presentation: `chips` for inline tags, `checkbox` for inline vertical choices, and `dropdown` (the default) for a compact entry opening the native picker. `chips` and `checkbox` write local state through `value` binding without firing an action; the submit Button collects their values in `context`. A configured `dropdown` action fires after confirming a selection.
When a separate Button submits edited values, verify that bindings carry the current values into its event context in the target renderer. A changed control display or a picker callback containing the new value does not prove that the independent submit action reads it.
Bind collected values with suitable initial values. Even single-select `ChoicePicker.value` uses an array of strings. Omitting `TextField.value` does not create a writeback path. Put submit validation on standalone `Button.checks`, not on TextField or a ButtonGroup item.
Make each field recognizable before and after input. Component labels have different roles:
| Component | Field-title choice |
|---|---|
| `TextField` | Its native `label` stays above the input. Use it as the field title; an empty label removes that title. |
| `ChoicePicker` (dropdown), `DateTimeInput` | Their `label` is an in-control prompt that gives way to the selected value. Add a nearby `Text` title when the field would otherwise lose its meaning. For inline `ChoicePicker` styles, title the group only when its options do not explain their purpose. |
| `NumberInput`, `UserPicker` | Native `label`, `title`, and `placeholder` serve the input panel or control. Add a nearby `Text` title when the field needs a persistent heading in the card. |
| `CheckBox`, `Switch` | `CheckBox.label` names the choice beside its control; a `Switch` can use an adjacent `Text` in a settings row. Do not add a duplicate heading when the choice is already clear. |
| `ImageUpload` | Its `label` is the upload control's caption. Use a nearby `Text` if the field itself needs a separate name. |
Use the component contract for exact fields. A placeholder or current value alone should not carry a field name that must remain visible. Add a short purpose or outcome line for a longer form when the surrounding context does not explain it.
Preserve user input after failure; show a completed state only after a real result. See [form interaction](../protocol/examples/form-interaction.json) for bindings, submit context, and checks, and [host action](../protocol/examples/host-action.json) for a host dialog and result writeback.
## Example interaction notes
- In the form example, `required(false)` passes; an agreement check must test the boolean value directly. ImageUpload reports only successful uploads, not cancellation or failure.
- For host dialogs, use the explicit host catalog `urn:dingtalk:a2ui:host:v1`. The host-action example passes `initialValue` to `promptText` and reads `{status: "success", data: {text: "..."}}` from `resultPath`. Keep an empty state before a result arrives; a missing-initial-value warning on that asynchronous output binding is expected.
- Cancellation preserves the previous result; a failed result has no `data.text`. Handle the status rather than treating every callback as new successful input.
- ButtonGroup items own their metadata, including result writeback configuration; they do not inherit it from the group. Query the action contract before adapting the example.
@@ -0,0 +1,50 @@
# Progress: Agent execution and results
Use for generation, search, analysis, tool execution, and other ongoing processes such as uploads. An ordinary task detail does not need a reasoning panel; use the [detail pattern](detail.md).
For one status, a short Text or Markdown block, optionally in one panel, is enough. Group tools only when useful. Default to concise operation summaries; include necessary input/output details when the user requests diagnostics and the content is appropriate to disclose. For a known ratio, use `ProgressBar`; for stages or counts, use Text with an optional `Row + Tag`, without inventing a percentage.
## State and presentation
For a standalone Markdown running indicator, prefix `content` with `[AIGenerating]` and remove it on completion, failure, or cancellation. Panel-based execution uses the conventions below.
For an Agent reasoning or tool-execution flow, use this recommended pattern. Ordinary upload or task progress does not require reasoning styling or generation effects:
- The outer `CollapsiblePanel(variant=reasoning)` shows the current operation in its title, with the host's `[AIGenerating]` convention and `textEffect: shimmer` while running. On completion, show completion text and omit both the generating marker and `textEffect`.
- When a public planning step belongs in the requested flow, a thinking placeholder such as `[AIGenerating]Thinking...` can precede the first tool group. Use only a concise public plan or operation summary as its timeline item; never expose private reasoning or invent an unobserved step. A placeholder is presentation, not evidence that reasoning occurred. When present, keep this item first; it is not a tool group and has no loading or completion icon. Reuse a host-owned placeholder rather than creating another card.
- Group related tools in nested `indented` panels when useful. While running, set `icon` to the light loading GIF URL and `darkIcon` to the dark loading GIF URL. For the requested DingTalk named-icon completion style, replace `icon` with `"Check_L_outlined"` when the tool group succeeds and omit the loading `darkIcon`. Do not show a success check for failed or cancelled groups.
- Panel icons accept image URLs or registered icon names. Tool-row named icons use `Icon.name`.
- Tool rows use `Icon.name`, chosen from [IconName](../protocol/common-types-visual.json#/$defs/IconName) for the tool type, not image URLs. `SpinLoading_L_outlined` alone does not enable rotation. Keep tool icons separate from panel status images; the outer panel uses the generation marker rather than a loading icon.
- When authorized diagnostics need input or output details, show selected, sanitized excerpts rather than raw tool dumps; exclude credentials, private reasoning, and unrelated inputs. Make the call its own nested `indented` panel: the title names the call, `icon` carries the tool-type icon name, and a Markdown child shows the excerpt as a fenced code block. Set `maxHeight` so long code scrolls inside the panel, and replace only that Markdown definition when the result arrives. Code-block text color, syntax colors, and background are renderer-owned; Markdown has no color field, and `<font color>` inside a fence is shown as literal text. Use `Text.colorToken` outside the block when a value must be highlighted.
- For the compact example style, use `common_level2_base_color` on panel titles and tool rows, and `common_action_text_style__font_size` on tool-row Text. These are styling defaults, not protocol requirements; preserve requested styling. Panel-title size is renderer-owned; do not invent a size field.
- Children may contain any contract-supported composition, including Markdown, File, charts, or forms. Content placement and reveal timing are task choices, not restrictions imposed by the execution state.
| State | Title and effects | Content |
|---|---|---|
| Running | Name the current operation; keep generation effects active | Show actual steps and any available partial result |
| Completed | Show completion; remove the generating marker, shimmer, and loading indicators | Show the final content or artifact |
| Failed or cancelled | Name the actual terminal state; stop generation effects | Preserve valid results; offer retry only through a real action |
Distinguish generated results from user confirmation. Completion describes the A2UI content state, not a delivery API status.
## Updates and interaction
- Start panels with reachable content so expansion reveals useful information. Set `defaultExpanded` only for the initial state; later updates must respect the user's local toggle state.
- `CollapsiblePanel.title` is a nonempty string and `textEffect` is a string enum, not a `{path: ...}` binding. Update them through `updateComponents`; update bound body content through `updateDataModel`.
- Keep the same `surfaceId` and existing component IDs. When adding a step, update its parent's `children` too. Supply the replacement component definition when clearing effects; do not send an unsupported empty `textEffect`.
- Shimmer, generation indicators, loading GIFs, and collapse interactions are supported across iOS, Android, HarmonyOS, Windows, macOS, and Web. Check the actual card's resource loading and interactions separately from protocol validation.
Initialize new bound fields with correctly typed values before using them. A text update through `updateDataModel` replaces the value at its path; it does not append characters. Do not replay initial values for existing editable fields.
## Examples and replay
The message positions below are one-based. Read one matching JSON, not both by default.
| Example | Batch 1 | Batch 2 | Batch 3 | Batch 4 |
|---|---|---|---|---|
| [agent-run-progress.json](../protocol/examples/agent-run-progress.json) | Messages 1–3: initialize thinking | Message 4: start reading | Message 5: finish reading, start analysis | None; intentionally stays running |
| [agent-run.json](../protocol/examples/agent-run.json) | Messages 1–3: initialize thinking | Message 4: start reading | Message 5: finish reading, start analysis | Message 6: complete and reveal results |
The batches describe successive A2UI states on the same Surface; applying the entire array at once may hide intermediate states. Timing belongs to the consuming application, not the JSON. Keep the running-only example unfinished when demonstrating that state. For a requested complete lifecycle, include the actual completed, failed, or cancelled content state; do not infer success from the end of a message array.
The complete example reveals Markdown and File outside the panel in its final batch. Its File is a public sample PDF, not a generated report, and has no `onPreview` callback. Replace sample resources for real tasks and configure file interaction when required.
@@ -0,0 +1,23 @@
# Report: metrics, records, and conclusions
Use for a data summary, analytical report, monitoring, or execution result. Select combinations for the content; a text-only report does not need a chart.
| Needed presentation | Optional components | When to choose them |
|---|---|---|
| Conclusion and analysis | `Markdown` | Continuous explanation, paragraphs, and inline emphasis |
| A few adjacent metrics | `Row > Column(Text, Text, Text)` | Label, value, and change in each group; an optional Card can provide a local background |
| Metrics at wide and narrow widths | `ColumnLayout > metric groups` | Query and enable `responsive` when narrow layouts must become vertical; this is not an automatic wrapping grid |
| Status or exception list | `Column/List > Row(Icon/Tag, Text)` | Status and separate explanation per item; choose a registered icon |
| Static tabular records | `Markdown` with table syntax | Exact comparison by column; prefer this over a separate `Table` component |
| Trend or distribution | `Chart` | Data suited to a graphic; query its contract, and add a table separately when useful |
| Method and full report | `CollapsiblePanel`, `File`, or `Link` | Supplemental details, an attachment, or a real detail destination |
See [information roles](../design.md#2-information-roles-and-blocks) for the underlying layout notation.
For static tabular data, use table syntax in `Markdown.content` by default. Use a separate `Table` only when explicitly requested or when a required capability calls for it; query its contract first. `Table.data` is structured data and its pagination runs locally. `Chart.data` holds chart type and data, not component IDs. `ColumnLayout.columns` does not automatically wrap any number of items into groups; build actual columns according to its responsive contract.
Preserve numeric units, time ranges, and comparison baselines. Do not represent missing data as zero. Use [layout guidance](../design.md#3-host-behavior-and-layout), [text hierarchy](../design.md#5-hierarchy-and-density), and [color guidance](../design.md#4-color-discipline) rather than copying cramped, truncated, or gradient-heavy sample cards.
The [data report example](../protocol/examples/data-report.json) illustrates protocol capabilities and field shapes; it does not override the Markdown recommendation for static tables.
In that example, the full table dataset is supplied once for local pagination. Loop bindings are relative to each row. The Stack includes both its base child and a referenced corner label.
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Google LLC.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -0,0 +1,115 @@
DingTalk AICard Protocol V0.8
Copyright 2026 DingTalk (China) Information Technology Co., Ltd.
(DingTalk modifications and this notice)
This protocol package includes schema definitions and descriptions derived from the
A2UI (Agent to UI) Protocol project, which is licensed under the
Apache License, Version 2.0. A copy of that license is included in the
LICENSE file in this directory.
Upstream project : A2UI (Agent to UI) Protocol
Upstream license : Apache License, Version 2.0
Specification : v1.0 (Status: Candidate)
Baseline commit : f5e945a93da36a5d126228f2ad6fbe68809ef8f7 (2026-09-15)
--------------------------------------------------------------------------------
DERIVED FILES AND MODIFICATIONS (Apache-2.0 section 4(b))
--------------------------------------------------------------------------------
The files below identify DingTalk modifications and distinguish upstream-derived
material from DingTalk-authored additions:
1. agent-to-renderer.json
Derived from : specification/v1_0/json/agent_to_renderer.json
Modified :
- "$id" changed from "https://a2ui.org/specification/v1_0/agent_to_renderer.json"
to "https://dingtalk.com/card/a2ui/agent-to-renderer.json". The upstream
"$id" is intentionally NOT reused, so that this package is never mistaken
for an official A2UI file and never collides with one inside a shared
validator registry.
- Added "$defs/anyComponent", which unions the four component shards of this
package. Upstream resolves a single placeholder "catalog.json" instead.
- References to "common_types.json" retargeted to "./common-types-basic.json".
- Removed the "callRendererFunction" and "agentFunctionResponse" message
branches, together with their "$defs", because the current implementation
does not support the corresponding RPC flows. The remaining
four branches are identical in name and order to upstream v1.0.
2. common-types-basic.json
Derived from : specification/v1_0/json/common_types.json
Unchanged definitions : "$defs/ComponentId", "$defs/Extensions".
Modified : Removed FunctionResponse and CallId with the unsupported RPC
flows. Remaining definitions include reference adjustments
and DingTalk customizations.
The common-type files as a whole are NOT a drop-in replacement
for upstream "common_types.json". Two important semantic
changes are:
- "Action" : adds "responsePath" and "wantResponse"; omits upstream
"userMessage".
- "CheckRule" : "condition" is a DynamicBoolean and "message" is required,
whereas upstream evaluates "condition" to a ValidationResult
object and treats "message" as an optional fallback.
3. common-types-extended.json
DingTalk literal-object and host-action value-tree, binding, ownership and
result-path types. Function result schemas are documented in the host-function
catalog next to each function. Not part of the upstream specification.
4. common-types-visual.json
DingTalk visual types (ColorToken, SizeToken and IconName), moved without
changing their definitions from common-types-extended.json. Not part of
the upstream specification.
5. catalog-components-common.json, catalog-components-composition.json,
catalog-components-inputs.json, catalog-components-specialized.json,
catalog-functions-core.json, catalog-functions-expressions.json,
catalog-functions-host-actions.json
DingTalk-maintained catalog shards. The four component shards and
catalog-functions-core.json retain or adapt selected definitions and
description text from specification/v1_0/catalogs/basic/catalog.json;
their content is not wholly DingTalk-original. DingTalk has changed and
extended those entries and split them into task-specific shards.
catalog-functions-expressions.json and catalog-functions-host-actions.json
contain DingTalk-specific functions. All seven shards follow the upstream
v1.0 catalog structure and pass its "catalog_definition.json" meta
contract; they are not unmodified official catalogs. Empty union branches
use false.
6. examples/
DingTalk-authored message examples. Not derived from upstream.
--------------------------------------------------------------------------------
TRADEMARKS AND SCOPE
--------------------------------------------------------------------------------
This package is maintained by DingTalk. It is neither certified, endorsed, nor
published by the A2UI project. Apache-2.0 grants no trademark rights; "A2UI" is
referenced here solely to identify the upstream work from which parts of this
package are derived.
The derived schema files and this notice describe this package's scope.
Nothing here constitutes a warranty of full interoperability with official
A2UI renderers or SDKs.
--------------------------------------------------------------------------------
中文摘要(以上述英文内容为准)
--------------------------------------------------------------------------------
本包部分 Schema 定义与说明派生自 A2UI 协议项目(Apache-2.0 许可),许可证全文见同目录 LICENSE。
上游基线为 A2UI 规范 v1.0(状态:Candidate),提交 f5e945a93d(2026-09-15)。
已改编并修改的文件:agent-to-renderer.json(改 $id、增 anyComponent、公共类型引用
重定向并裁剪两类当前不支持的 RPC 消息)、common-types-basic.json(原样保留
ComponentId 与 Extensions;随未开放的 RPC 流程移除 FunctionResponse 和 CallId;
其他定义包含引用调整和钉钉定制,Action 与 CheckRule 存在语义差异)。公共类型文件
整体不能直接替代上游 common_types.json。四份组件 catalog 与
catalog-functions-core.json 保留或改编了官方 Basic Catalog 的部分定义与说明,
由钉钉扩展并分片,不属于纯钉钉原创内容。catalog-functions-expressions.json、
catalog-functions-host-actions.json、common-types-extended.json、
common-types-visual.json 与 examples/ 为钉钉新增内容。
本包消息集为官方四支子集(不含 callRendererFunction 与 agentFunctionResponse)。
本包由钉钉维护,未获 A2UI 项目认证或背书。Apache-2.0 不授予商标权利,文中提及
"A2UI" 仅用于标识派生来源。本包范围由随包 Schema 和本说明界定,不构成与官方渲染器或
SDK 完全互操作的担保。
@@ -0,0 +1,216 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/agent-to-renderer.json",
"title": "DingTalk AICard Protocol V0.8 A2UI Message Envelope",
"description": "Describes a JSON payload for an A2UI (Agent to UI) message, which is used to dynamically construct and update user interfaces. Modified by DingTalk from the A2UI v1.0 agent_to_renderer.json envelope: the catalog placeholder is bound to this package, and common types resolve to ./common-types-basic.json.",
"type": "object",
"oneOf": [
{
"$ref": "#/$defs/CreateSurfaceMessage"
},
{
"$ref": "#/$defs/UpdateComponentsMessage"
},
{
"$ref": "#/$defs/UpdateDataModelMessage"
},
{
"$ref": "#/$defs/DeleteSurfaceMessage"
}
],
"$defs": {
"CreateSurfaceMessage": {
"type": "object",
"properties": {
"version": {
"const": "v1.0"
},
"createSurface": {
"type": "object",
"description": "Signals the renderer to create a new surface and begin rendering it. Creating a surface implicitly instantiates the canonical 'Surface' container component ('./common-types-basic.json#/$defs/Surface') with 'child': 'root'. It is an error to try to create a surface with an existing ID without first deleting it; surfaceId MUST be globally unique for the renderer's lifetime. When this message is sent, the renderer expects 'updateComponents' and/or 'updateDataModel' messages for the same surfaceId to define the component tree.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface to be rendered. It must be globally unique for the renderer's lifetime."
},
"catalogId": {
"description": "A string that uniquely identifies the default catalog for this surface. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. 'mycompany.com:somecatalog'. Components and function calls that do not explicitly specify a catalogId will use this surface-level default catalogId.",
"type": "string"
},
"sendDataModel": {
"type": "boolean",
"description": "If true, the renderer will send the full data model of this surface in the metadata of every A2A message sent to the agent that created the surface. Defaults to false."
},
"components": {
"$ref": "#/$defs/ComponentsList"
},
"dataModel": {
"type": "object",
"description": "The initial root data model object for the surface.",
"additionalProperties": true
},
"metadata": {
"type": "object",
"description": "Optional surface-level metadata.",
"properties": {
"extensions": {
"$ref": "./common-types-basic.json#/$defs/Extensions"
}
},
"additionalProperties": false
}
},
"required": [
"surfaceId"
],
"additionalProperties": false
}
},
"required": [
"createSurface",
"version"
],
"additionalProperties": false
},
"Component": {
"description": "A UI component for the surface, combining baseline envelope properties with the catalog component definition.",
"allOf": [
{
"$ref": "./common-types-basic.json#/$defs/ComponentCommon"
},
{
"$ref": "#/$defs/anyComponent"
},
{
"type": "object",
"properties": {
"component": {
"not": {
"const": "Surface"
}
}
}
}
],
"unevaluatedProperties": false
},
"ComponentsList": {
"type": "array",
"description": "A list containing UI components for the surface.",
"minItems": 1,
"items": {
"$ref": "#/$defs/Component"
}
},
"UpdateComponentsMessage": {
"type": "object",
"properties": {
"version": {
"const": "v1.0"
},
"updateComponents": {
"type": "object",
"description": "Updates a surface with a new set of components. This message can be sent multiple times to update the component tree of an existing surface. One of the components in one of the components lists MUST have an 'id' of 'root' to serve as the root of the component tree. The createSurface message MUST have been previously sent for this surfaceId.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface to be updated. It must be globally unique for the renderer's lifetime."
},
"components": {
"$ref": "#/$defs/ComponentsList"
}
},
"required": [
"surfaceId",
"components"
],
"additionalProperties": false
}
},
"required": [
"updateComponents",
"version"
],
"additionalProperties": false
},
"UpdateDataModelMessage": {
"type": "object",
"properties": {
"version": {
"const": "v1.0"
},
"updateDataModel": {
"type": "object",
"description": "Updates the data model for an existing surface. This message can be sent multiple times to update the data model. The createSurface message MUST have been previously sent for this surfaceId.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface this data model update applies to. It must be globally unique for the renderer's lifetime."
},
"path": {
"type": "string",
"description": "An optional path to a location within the data model (e.g., '/user/name'). If omitted, or set to '/', refers to the entire data model."
},
"value": {
"description": "The data to be updated in the data model. To delete the key/value at 'path', set 'value' explicitly to null."
}
},
"required": [
"surfaceId",
"value"
],
"additionalProperties": false
}
},
"required": [
"updateDataModel",
"version"
],
"additionalProperties": false
},
"DeleteSurfaceMessage": {
"type": "object",
"properties": {
"version": {
"const": "v1.0"
},
"deleteSurface": {
"type": "object",
"description": "Signals the renderer to delete the surface identified by 'surfaceId'. The createSurface message MUST have been previously sent for this surfaceId.",
"properties": {
"surfaceId": {
"type": "string",
"description": "The unique identifier for the UI surface to be deleted. It must be globally unique for the renderer's lifetime."
}
},
"required": [
"surfaceId"
],
"additionalProperties": false
}
},
"required": [
"deleteSurface",
"version"
],
"additionalProperties": false
},
"anyComponent": {
"description": "Any component from the four component shards of the DingTalk public catalog.",
"oneOf": [
{
"$ref": "./catalog-components-common.json#/$defs/anyComponent"
},
{
"$ref": "./catalog-components-composition.json#/$defs/anyComponent"
},
{
"$ref": "./catalog-components-inputs.json#/$defs/anyComponent"
},
{
"$ref": "./catalog-components-specialized.json#/$defs/anyComponent"
}
]
}
}
}
@@ -0,0 +1,700 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-components-common.json",
"title": "DingTalk AICard Protocol V0.8 Common components",
"description": "10 components and 0 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog. Modified by DingTalk from selected entries of the A2UI v1.0 Basic Catalog; some upstream definitions and descriptions are retained. See NOTICE.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {
"Text": {
"type": "object",
"description": "Text, font size, color, and streaming state. `textEffect` can add a shimmer effect.",
"properties": {
"component": {
"const": "Text",
"description": "Public component type identifier."
},
"text": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Text content to display. Supports literals, data bindings, and value functions. **Object forms such as `{content, i18n}` are not accepted**; the server must localize multilingual copy before sending it."
},
"variant": {
"type": "string",
"enum": [
"caption",
"body"
],
"description": "Text style preset; omitted means `body`. Metrics first use the host style tokens. If unavailable, font size/line height fall back to 14/21 on desktop and 17/21 on mobile for `body`, or 12/18 on desktop and 12/16 on mobile for `caption`. A successfully resolved explicit `sizeToken` takes precedence; these fallback metrics are not fixed values for every client."
},
"sizeToken": {
"$ref": "./common-types-visual.json#/$defs/SizeToken",
"description": "Native DDesign font-size Token ID. See the common `SizeToken` type for names and individual uses. Specify the full `__font_size` key; it takes priority over `variant`. Line height attempts to use the matching `__line_height` token and falls back to the `variant` preset if the host cannot resolve it. Do not use design labels such as `font-size/body` or style names without a suffix."
},
"bold": {
"type": "boolean",
"description": "Whether the text is bold."
},
"italic": {
"type": "boolean",
"description": "Whether the text is italic."
},
"strikeThrough": {
"type": "boolean",
"description": "Whether to apply strikethrough."
},
"colorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name such as `common_level1_base_color`. The renderer calls the host design-token service to resolve it for the **current client platform and theme**, making this the preferred way to keep colors consistent across platforms. This has the highest priority: when resolved, `color`, `customLightColor`, and `customDarkColor` are ignored. If the host cannot resolve the token, evaluation falls back to the subsequent color sources."
},
"customLightColor": {
"type": "string",
"description": "Custom text color for light mode (hex)."
},
"customDarkColor": {
"type": "string",
"description": "Custom text color for dark mode (hex). The renderer selects and sends the light or dark value for the current theme. A theme change takes effect after the card's next refresh."
},
"maxLine": {
"type": "integer",
"description": "Truncate after exceeding this number of lines. **By default, only 3 lines are displayed if not configured**. Long text will be silently truncated; to display it fully, please explicitly set a sufficiently large value."
},
"icon": {
"type": "object",
"description": "Semantic leading icon. Use exactly one of `{\"name\":\"icon library name\"}` or `{\"url\":\"light-theme URL\",\"darkUrl\":\"dark-theme URL\"}`. `darkUrl` is optional and falls back to `url`. **Exactly one of `name` and `url` must be provided**; providing both or neither is invalid. ⚠️ **Mutually exclusive with `textEffect`**: a `Text` component with `textEffect` uses a different rendering path with no icon slot. If both are sent, **the icon is retained and the effect is ignored**.",
"properties": {
"name": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Registered icon-library name; mutually exclusive with `url`."
},
"url": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Light-theme image URL; mutually exclusive with `name`."
},
"darkUrl": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Dark-theme image URL. Effective only when `url` is used; falls back to `url` when omitted."
}
}
},
"textEffect": {
"type": "string",
"enum": [
"shimmer"
],
"description": "Text presentation effect. `shimmer` repeatedly sweeps a highlight across the text and is intended for streaming wait states such as \"Thinking...\". If omitted, no effect is applied. On `CollapsiblePanel`, it applies to the panel title. Supported on iOS, Android, HarmonyOS, Windows, macOS, and Web."
},
"onLinkClick": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "The value must be an `Action` from the base protocol: the `event` branch reports to the agent, while the `functionCall` branch runs locally in the renderer. ⚠️ **The current implementation is reduced to \"a click anywhere on the Text node\"**: clicking any part of the text fires the action, **and the event does not carry which link was clicked**. The field therefore suits coarse-grained tracking such as \"the user read this text containing a link\", but cannot distinguish which link was followed. Link navigation itself is unaffected: the renderer opens it directly without going through the card event system."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"text"
]
},
"Markdown": {
"type": "object",
"description": "Markdown headings, lists, code, and quotes.",
"properties": {
"component": {
"const": "Markdown",
"description": "Public component type identifier."
},
"content": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Markdown body. In addition to standard Markdown, DingTalk supports these @-mention extensions: `<a atId=\"{openDingTalkId}\">nickname</a>` mentions a specific member, and `<a atId=\"all\">everyone</a>` mentions everyone (`all` is a reserved ID). Every member `atId` in the body must be declared at send time via `--at-open-dingtalk-ids` (sent as the card-creation parameter `atOpenDingTalkIds`, at most 100 declarations including duplicates); the reserved ID `all` is declared with `--at-all` (sent as `atAll`) and never enters the id list. The server resolves each declared `openDingTalkId` to a user and rejects card creation with an INVALID_PARAM error when a declared id is invalid or cannot be resolved. Write only the display name inside the tag and do not add `@` yourself; the renderer prepends `@` when rendering an at node, so a self-written one renders as `@@`. An `atId` used in the body but not declared is neither highlighted nor notified, so declare every id you use. The `<@openDingTalkId>` and `<@all>` placeholders used by IM Markdown messages **do not work** in the card flow and are displayed literally. When sending with `--at-all`, the CLI rewrites the body's `<a atId=\"all\">` tag to the server highlight alias and sends `atAll=true`. The body syntax and this field's contract stay unchanged once it ships."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"content"
]
},
"Image": {
"type": "object",
"description": "Image display with a dark-theme image alternative. When variant is omitted, the current DingTalk implementation builds a full-row image with height 200; available width remains subject to the parent layout.",
"properties": {
"component": {
"const": "Image",
"description": "Public component type identifier."
},
"url": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Light mode and default image URL."
},
"darkUrl": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Dark mode image URL; if not configured, the renderer side will continue to use the URL."
},
"fit": {
"type": "string",
"enum": [
"contain",
"cover",
"fill",
"none",
"scaleDown"
],
"description": "Image scaling within its container. When omitted, the current DingTalk implementation crops to fill, corresponding to `cover`; this differs from the official Basic Catalog default of `fill`. Specify a value from this field's vocabulary to control containment, cropping or stretching."
},
"variant": {
"type": "string",
"enum": [
"icon",
"avatar",
"smallFeature",
"mediumFeature",
"largeFeature",
"header"
],
"description": "Image size and style hint. Omitted means full-row width and height 200, rather than the official Basic Catalog default `mediumFeature`. Current DingTalk sizes are: `icon` 24×24, `avatar` 40×40, `smallFeature` 96×96, `mediumFeature` 160×120, and `largeFeature` 320×200; `header` uses full-row width and height 200. Available width is subject to parent constraints, and clients must integrate the corresponding Nest implementation."
},
"cornerRadius": {
"type": "number",
"description": "Image corner radius in px; omitted means `6`. `ImageList` defaults to `4`, and `ImageCarousel` to `12`. The radius clips the image's outer container. Use a finite non-negative number; `0` gives square corners."
},
"previewEnabled": {
"type": "boolean",
"description": "Whether to allow clicking the image to open a large preview, default `true`. ⚠️ Default is **on**: if this field is not set, as long as `url` is an address that can be loaded on the renderer, the image is clickable. To make it a pure display image, explicitly set `false`."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"url"
]
},
"Icon": {
"type": "object",
"description": "DingTalk icon set and loading status. Current icon size is 14 with line height 22. Without a resolved color configuration, built-in light/dark colors are used; valid explicit colors take precedence.",
"properties": {
"component": {
"const": "Icon",
"description": "Public component type identifier."
},
"name": {
"anyOf": [
{
"$ref": "./common-types-visual.json#/$defs/IconName"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
],
"description": "Only the 24 case-sensitive names in the `IconName` list below are available. Data bindings and value functions are supported, but their results must also be names from the list. SVG objects, image URLs, and custom semantic aliases are not accepted."
},
"colorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name such as `common_level1_base_color`. The renderer calls the host design-token service to resolve it for the **current client platform and theme**, making this the preferred way to keep colors consistent across platforms. This has the highest priority: when resolved, `customLightColor` and `customDarkColor` are ignored. If the host cannot resolve the token, evaluation falls back to the subsequent color sources."
},
"customLightColor": {
"type": "string",
"description": "Custom light mode color (hexadecimal)."
},
"customDarkColor": {
"type": "string",
"description": "Custom dark-theme color in hexadecimal. The renderer selects and sends the light or dark value for the current theme. A theme change takes effect after the card's next refresh."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"name"
]
},
"Divider": {
"type": "object",
"description": "Horizontal or vertical separator line.",
"properties": {
"component": {
"const": "Divider",
"description": "Component type identification field."
},
"axis": {
"type": "string",
"enum": [
"horizontal",
"vertical"
],
"description": "Divider direction, default is `horizontal`. `vertical` is used for vertical separation between elements within the same row (such as button dividers in the bottom action bar), rendered with a fixed line width and stretched to the height of the parent container, so **the parent container is required to be `Row` and the elements in that row must have consistent height**; for full-height vertical bars spanning multiple rows, it is also used, placed in the first position of `Row`. Line width and color are controlled by `borderWidth` / `borderColor`."
},
"borderWidth": {
"type": "number",
"description": "Line thickness in px; default `0.5`, the hairline value from the design specification. Its name and value semantics match the same field on `Row`, `Column`, `Card`, and `Stack`, and correspond to CSS `border-width`. **It always describes line thickness, regardless of direction**: when `axis` is `horizontal`, it determines line height; when `axis` is `vertical`, it determines line width. The value must be finite and non-negative. Invalid values are silently discarded and fall back to `0.5` without failing the card."
},
"borderColor": {
"type": "string",
"description": "Line color in `#AARRGGBB` form; default `#1F111F2C`, a neutral gray at approximately 12% opacity. Corresponds to CSS `border-color`. **Only a single color value is accepted; a `{light,dark}` pair is not supported**, so the color does not change with the theme. Use `borderColorToken` below when theme-aware color is required."
},
"borderColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the line color. It has **higher priority than `borderColor`** and is the only way to make the divider follow light and dark themes. The design-system line-color families are `common_line_light_color` (16% in light mode, 10% in dark mode) and `common_line_hard_color` (24% in light mode, 18% in dark mode). The legacy default `#1F111F2C` differs from both; **specify a token explicitly to align with the design system**. If the host cannot resolve the token, it is treated as omitted and falls back to `borderColor` without reporting an error."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component"
]
},
"Tag": {
"type": "object",
"description": "Semantic tag whose theme determines text color, background color, border color, and border width together.",
"properties": {
"component": {
"const": "Tag",
"description": "Public component type identifier."
},
"text": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Label text."
},
"theme": {
"type": "string",
"enum": [
"black",
"gray",
"red",
"orange",
"green",
"blue"
],
"description": "**Semantic theme: one value determines the text color, background color, border color, and border width together.** The design system fixes these relationships, so arbitrary hexadecimal colors are not accepted. Defaults to `gray`; unknown values fall back to `gray` without reporting an error. **This differs from the `color` field on `Text` and `Icon`**, which specifies a single foreground color. Here the field defines the entire tag color scheme, so it is named `theme`, consistent with `CardHeader.theme`."
},
"variant": {
"type": "string",
"enum": [
"filled",
"hollow"
],
"description": "Tag style; default `filled`. **The two styles are mutually exclusive**: `filled` has a background from the 12%-opacity level of `theme` and no border; `hollow` has a transparent background and a 1px border from the 48%-opacity level of `theme`. Both use the same text color. **A tag cannot have both a filled background and a border**; compose that appearance with `Row` instead."
},
"maxWidth": {
"type": "number",
"description": "Maximum tag width in px; omitted means `999`, a numeric limit rather than unlimited width. Text is truncated beyond it."
},
"weight": {
"type": "number",
"description": "Flex layout weight."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"text"
]
},
"Card": {
"type": "object",
"description": "Single-child card container with padding, background, and rounded corners.",
"properties": {
"component": {
"const": "Card",
"description": "Component type identification field."
},
"child": {
"$ref": "./common-types-basic.json#/$defs/Child",
"description": "The ID of the single child component to be rendered inside the card. To display multiple elements, you MUST wrap them in a layout component (like Column or Row) and pass that container's ID here. Do NOT pass multiple IDs or a non-existent ID."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"backgroundColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the card surface, such as `common_bg_color`. The host resolves it for the **current client platform, theme, and tenant**, making this the preferred way to keep the surface consistent across platforms and themes. It has **higher priority than `backgroundColor`**. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `backgroundColor`; the default surface color is used only when that field is also omitted. No error is reported."
},
"backgroundColor": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {}
}
],
"description": "Card surface color. Two forms are supported: a string in `#AARRGGBB` form is a **single value** that does not change with the light or dark theme; `{\"light\":\"#…\",\"dark\":\"#…\"}` provides **light and dark values** selected by the renderer for the current theme and is recommended. If omitted, the default surface color is used: `#FFFFFFFF` in light mode and `#FF1F1F1F` in dark mode."
},
"cornerRadius": {
"type": "number",
"description": "Card-surface corner radius in px; default `8`. Must be a non-negative number."
},
"borderWidth": {
"type": "number",
"description": "Border width in px; default `0`, meaning no border. **A visible border requires `borderColor` to be configured as well.**"
},
"borderColor": {
"type": "string",
"description": "Stroke color, hexadecimal. Only visible when `borderWidth` is also set; if a color is set, a solid line style is automatically applied."
},
"borderColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the border color. It has **higher priority than `borderColor`** and is resolved by the host for the **current client platform, theme, and tenant**. This is the only way to make the border follow light and dark themes: `borderColor` accepts a single value that remains unchanged in dark mode. The line-color families are `common_line_light_color` and `common_line_hard_color`; the host resolves their actual values and opacity. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `borderColor` without reporting an error."
},
"padding": {
"type": "number",
"description": "Uniform padding on all four sides, unit px, default `12`. Must be a number and not less than 0."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"child",
"component"
]
},
"Row": {
"type": "object",
"description": "Child nodes are arranged from left to right.",
"properties": {
"component": {
"const": "Row",
"description": "Component type identification field."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list. Children cannot be defined inline, they must be referred to by ID."
},
"justify": {
"type": "string",
"enum": [
"start",
"center",
"end",
"spaceBetween",
"spaceAround",
"spaceEvenly",
"stretch"
],
"description": "Main-axis layout; the seven values match the official vocabulary. `start`, `center` and `end` control where children sit along the main axis; `spaceBetween`, `spaceAround` and `spaceEvenly` distribute the remaining space exactly like CSS `justify-content` (`gap` still applies only between adjacent children and is never added at the edges). **`spaceBetween` with two children**: positioning matches CSS while the content fits, and an over-long last item wraps within the remaining width instead of overflowing. In the other arrangements, once a weighted child appears in the row, subsequent children are measured against the full row width, so content that does not fit on one line overflows and is clipped — use child `weight` for long text. No remaining space is distributed when any child already carries `weight`, matching the official flex-grow behaviour. On a flex row, `stretch` behaves like `start`. Omitted means `start`."
},
"align": {
"type": "string",
"enum": [
"start",
"center",
"end",
"stretch"
],
"description": "Cross-axis alignment. `start`, `center` and `end` control the vertical position of children. `stretch`, the default, gives children equal height and matches the official `align-items: stretch` semantics: container children (`Row`, `Column`, `Card`, and a vertical `Divider`) stretch to the full row height, while leaf and composite components such as `Text`, `Image` and `Button` keep their intrinsic height and are top-aligned, with their `weight`, spacing and visibility unaffected. While equal height is in effect, `justify` values `center`, `end`, `spaceBetween`, `spaceAround` and `spaceEvenly` on a stretched child `Column` become visible. ⚠️ Component ids must not take the form `<Row id>__stretch.<n>`, which is reserved for layout; a collision makes the whole row give up equal height. Under `Column` and `List`, `stretch` decides whether a child fills the available width or sizes to its content."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"backgroundColor": {
"type": "string",
"description": "Container background color in hexadecimal `#RRGGBB` or `#AARRGGBB` form, with Alpha first in the 8-digit form. **Only a single color string is accepted; a `{light,dark}` object is not supported**, so the color does not change with the theme. To use a different dark-theme color, send an updated component definition. If omitted, the background is transparent."
},
"cornerRadius": {
"type": "number",
"description": "Corner radius in px. If omitted, no explicit radius is set and the host container's default styling remains in effect. Set a non-negative number when a definite appearance is needed. Use a number; fractional values are truncated rather than rounded."
},
"borderWidth": {
"type": "number",
"description": "Stroke width, in px, must be a number. After configuration, a solid line type is automatically applied, and other line types cannot be specified. **It must be configured in pairs with `borderColor`** to draw a visible stroke."
},
"borderColor": {
"type": "string",
"description": "Stroke color, the value format is the same as `backgroundColor`. **Must be configured in pairs with `borderWidth`**; if only the color is provided without the width, the line will not be drawn."
},
"borderColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the border color. It has **higher priority than `borderColor`** and is resolved by the host for the **current client platform, theme, and tenant**. This is the only way to make the border follow light and dark themes: `borderColor` accepts a single value that remains unchanged in dark mode. The line-color families are `common_line_light_color` and `common_line_hard_color`; the host resolves their actual values and opacity. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `borderColor` without reporting an error."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Makes the entire container clickable. Uses the same Action semantics as `Button.action`: exactly one of `event` (reported to the agent) and `functionCall` (executed locally by the renderer) may be present."
},
"padding": {
"type": "number",
"minimum": 0,
"description": "Uniform padding on all four sides, unit px, must be a number and not less than 0. Containers with a background color generally need it; otherwise, the child content will be close to the edge of the color block. **Only uniform values for all four sides are supported**; individual sides or `paddingX`/`paddingY` are not supported yet. If not configured, this field will not be sent, consistent with historical version behavior. Omission is not equivalent to an explicit 0: a card root can still receive platform-specific insets from card layout, while this field adds no padding to a nested container when omitted."
},
"gap": {
"type": "number",
"minimum": 0,
"description": "Gap between children in px; must be a non-negative number. `Column` applies it vertically and `Row` horizontally; the first child is unaffected. **When omitted, hierarchy-specific defaults remain in effect**: 8 between top-level card blocks, 12 in nested columns, and 8 horizontally. Existing card spacing therefore remains unchanged."
},
"backgroundColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the container background, such as `common_bg_color`. The host resolves it for the **current client platform, theme, and tenant**, and it has **higher priority than `backgroundColor`**. This is the component's only way to make its background follow dark mode; `backgroundColor` accepts a single color value and does not change with the theme. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `backgroundColor` without reporting an error."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"Column": {
"type": "object",
"description": "Child nodes are stacked from top to bottom. As a card root with padding omitted and no full-width CardHeader, card layout supplies horizontal insets of 16 on desktop or 12 on mobile, with top and bottom insets of 10. On mobile, a final visible Markdown block changes the bottom inset to 14. Nested Columns do not automatically receive these root insets.",
"properties": {
"component": {
"const": "Column",
"description": "Component type identification field."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list. Children cannot be defined inline, they must be referred to by ID."
},
"justify": {
"type": "string",
"enum": [
"start",
"center",
"end",
"spaceBetween",
"spaceAround",
"spaceEvenly",
"stretch"
],
"description": "Main-axis layout. A `Column` is normally sized by its content and has no remaining space, so `spaceBetween`, `spaceAround`, `spaceEvenly` and `stretch` behave here exactly like an official flex column and produce the same result as `start` (accepted, no error). The only case with remaining height is when the column is a child of an equal-height row and is pulled to the row height (see `align` on Row): `center` and `end` then take effect normally, and `spaceBetween`, `spaceAround` and `spaceEvenly` distribute the remaining height under the same rules as Row (no distribution when any child already carries `weight`). ⚠️ Component ids must not take the form `<Column id>__justify.<n>`, which is reserved for layout; a collision disables `justify` on that column while equal height for the row is unaffected. Omitted means `start`."
},
"align": {
"type": "string",
"enum": [
"center",
"end",
"start",
"stretch"
],
"description": "Defines the alignment of children along the cross axis (horizontally). This is similar to the CSS 'align-items' property. Omitted means `stretch`; whether a child fills the available width also depends on its fixed dimensions and parent constraints."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"backgroundColor": {
"type": "string",
"description": "Container background color in hexadecimal `#RRGGBB` or `#AARRGGBB` form, with Alpha first in the 8-digit form. **Only a single color string is accepted; a `{light,dark}` object is not supported**, so the color does not change with the theme. To use a different dark-theme color, send an updated component definition. If omitted, the background is transparent."
},
"cornerRadius": {
"type": "number",
"description": "Corner radius in px. If omitted, no explicit radius is set and the host container's default styling remains in effect. Set a non-negative number when a definite appearance is needed. Use a number; fractional values are truncated rather than rounded."
},
"borderWidth": {
"type": "number",
"description": "Stroke width, in px, must be a number. After configuration, a solid line type is automatically applied, and other line types cannot be specified. **It must be configured in pairs with `borderColor`** to draw a visible stroke."
},
"borderColor": {
"type": "string",
"description": "Stroke color, the value format is the same as `backgroundColor`. **Must be configured in pairs with `borderWidth`**; if only the color is provided without the width, the line will not be drawn."
},
"borderColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the border color. It has **higher priority than `borderColor`** and is resolved by the host for the **current client platform, theme, and tenant**. This is the only way to make the border follow light and dark themes: `borderColor` accepts a single value that remains unchanged in dark mode. The line-color families are `common_line_light_color` and `common_line_hard_color`; the host resolves their actual values and opacity. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `borderColor` without reporting an error."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Makes the entire container clickable. Uses the same Action semantics as `Button.action`: exactly one of `event` (reported to the agent) and `functionCall` (executed locally by the renderer) may be present."
},
"padding": {
"type": "number",
"minimum": 0,
"description": "Uniform padding on all four sides, unit px, must be a number and not less than 0. Containers with a background color generally need it; otherwise, the child content will be close to the edge of the color block. **Only uniform values for all four sides are supported**; individual sides or `paddingX`/`paddingY` are not supported yet. If not configured, this field will not be sent, consistent with historical version behavior. Omission is not equivalent to an explicit 0: a card root can still receive platform-specific insets from card layout, while this field adds no padding to a nested container when omitted."
},
"gap": {
"type": "number",
"minimum": 0,
"description": "Gap between children in px; must be a non-negative number. `Column` applies it vertically and `Row` horizontally; the first child is unaffected. **When omitted, hierarchy-specific defaults remain in effect**: 8 between top-level card blocks, 12 in nested columns, and 8 horizontally. Existing card spacing therefore remains unchanged."
},
"backgroundColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the container background, such as `common_bg_color`. The host resolves it for the **current client platform, theme, and tenant**, and it has **higher priority than `backgroundColor`**. This is the component's only way to make its background follow dark mode; `backgroundColor` accepts a single color value and does not change with the theme. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `backgroundColor` without reporting an error."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"Button": {
"type": "object",
"description": "The public `Button` component. `disabled` dims the button and removes its click action; the implementation remains compatible with the `SingleButton` builder preset.",
"properties": {
"checks": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/CheckRule"
},
"description": "A list of checks to perform. These are function calls that must return a boolean indicating validity. **DingTalk implementation:** On click, the checks are evaluated against the latest DataModel. A failed check blocks the click and displays a toast. **This is the standard attachment point for form validation**: rules use `path` to reference the paths bound to input components, allowing the entire form to be validated at once."
},
"component": {
"const": "Button",
"description": "Component type identification field."
},
"child": {
"$ref": "./common-types-basic.json#/$defs/Child",
"description": "The ID of the child component. Use a 'Text' component for a labeled button. Only use an 'Icon' if the requirements explicitly ask for an icon-only button."
},
"variant": {
"type": "string",
"enum": [
"default",
"primary",
"borderless"
],
"description": "Button style hint; omitted means `default`. `primary` marks the main action, and `borderless` removes the background and border. When disabled is true, disabled colors take precedence."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Defines an interaction handler that can either trigger an agent-side event or execute a local renderer-side function. **DingTalk implementation:** Although required by the contract, a missing or invalid value **does not make the button disappear**. The button still renders, including its label and colors, and clicking it displays \"This action is not supported by the current version.\" See Section 1.4, \"Invalid actions do not affect rendering.\" Only `child` is required for rendering."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"disabled": {
"type": "boolean",
"description": "Whether the button is disabled; omitted means `false`. The current implementation uses disabled colors and attaches no click action when true; the button and its label remain visible."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"action",
"child",
"component"
]
}
},
"functions": {},
"$defs": {
"anyComponent": {
"oneOf": [
{
"$ref": "./catalog-components-common.json#/components/Button"
},
{
"$ref": "./catalog-components-common.json#/components/Card"
},
{
"$ref": "./catalog-components-common.json#/components/Column"
},
{
"$ref": "./catalog-components-common.json#/components/Divider"
},
{
"$ref": "./catalog-components-common.json#/components/Icon"
},
{
"$ref": "./catalog-components-common.json#/components/Image"
},
{
"$ref": "./catalog-components-common.json#/components/Markdown"
},
{
"$ref": "./catalog-components-common.json#/components/Row"
},
{
"$ref": "./catalog-components-common.json#/components/Tag"
},
{
"$ref": "./catalog-components-common.json#/components/Text"
}
],
"discriminator": {
"propertyName": "component"
}
},
"anyFunction": false
}
}
@@ -0,0 +1,726 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-components-composition.json",
"title": "DingTalk AICard Protocol V0.8 Composition and repeated layouts",
"description": "11 components and 0 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog. Modified by DingTalk from selected entries of the A2UI v1.0 Basic Catalog; some upstream definitions and descriptions are retained. See NOTICE.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {
"Link": {
"type": "object",
"description": "Clickable link text and navigation.",
"properties": {
"component": {
"const": "Link",
"description": "Public component type identifier."
},
"text": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Text displayed by the link. Supports literals, data bindings, and value functions. The server localizes multilingual copy before sending it."
},
"maxLine": {
"type": "integer",
"description": "Maximum number of display lines. Omitted means 2."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Action executed when the component is clicked; only effective if the component declares click capability."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"text"
]
},
"CardHeader": {
"type": "object",
"description": "Unified card header with a theme, dynamic title, dark-theme icon, and trailing content.",
"properties": {
"component": {
"const": "CardHeader",
"description": "Public component type identifier."
},
"title": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Card header title, supports literals, data binding, and function return values."
},
"theme": {
"anyOf": [
{
"type": "string",
"enum": [
"blue",
"red",
"orange",
"green",
"pink",
"purple",
"yellow",
"water",
"olive"
]
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
],
"description": "Semantic theme. **A single value determines four light/dark colours at once** — light and dark title colours plus light and dark header background colours. The design system fixes these pairings, so arbitrary hexadecimal colours are not accepted and the value is not split into separate fields; this is exactly why the field is an enum rather than a `colorToken`, whose contract is one token to one colour and cannot express a one-to-many relationship. The background uses the primary colour at 12%–16% opacity, and the renderer picks the light or dark variant for the active theme; the title colour is selected the same way. The default is `blue`. For compatibility, legacy cards may send a primary-colour hex value directly (`#0066FF`, `#00B853`, `#F2510C`, `#FF9200`). Unknown values fall back to `blue` without raising an error."
},
"icon": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Header LOGO image URL, rendered to the left of the title, 16px square, with a 6px spacing from the title. If not configured, the icon position will not be rendered."
},
"darkIcon": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Logo image URL for the dark theme. The light and dark URLs are sent together, and the renderer selects one for the current theme, updating immediately when the theme changes. If omitted, it falls back to `icon`."
},
"trailing": {
"$ref": "./common-types-basic.json#/$defs/ComponentId",
"description": "Slot on the **right side** of the title. Its value is the `id` of one component in the same surface: **a single string, not an array**. Slot content is right-aligned with a 6px gap from the title. The title occupies the remaining width and wraps when too long. Typical uses include source labels, status tags, and timestamps. To place multiple elements, put a `Row` in the slot and control spacing and alignment explicitly with its `gap` and `align`. If omitted, no trailing content is rendered."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"title"
]
},
"GridLayout": {
"type": "object",
"description": "Grid layout supporting static children and data templates. Omitting columns enables adaptive column selection from card content width; setting columns fixes the column count. See the corresponding fields for omitted row-height and column-gap behavior.",
"properties": {
"component": {
"const": "GridLayout",
"description": "Public component type identifier."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "List of child component IDs or dynamic child templates. In dynamic form, `componentId` points to the cell template, and `path` points to an array in the Data Model; the rendering results are the same for both forms, with cells laid out row by row according to `columns`."
},
"columns": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Number of columns per row. **When omitted, the layout adapts to the card width** using a minimum column width of 234 and at most 3 columns; if the width is unavailable, it uses 2 columns. When specified, the exact value is used and adaptive column selection is disabled."
},
"columnGap": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Column gap. May be a number or a card-protocol `DynamicNumber`. The default is 8 when `columns` is set and 12 in adaptive mode when `columns` is omitted."
},
"rowGap": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Row gap. May be a number or a card-protocol `DynamicNumber`; default 8."
},
"itemHeight": {
"type": "number",
"description": "Fixed cell height. When omitted or set to 0, no fixed cell height is supplied to the renderer; final height is determined by its layout. Set a positive number when a fixed height is required."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"Loop": {
"type": "object",
"description": "Data-driven repeated-rendering container. Repeated items are arranged vertically; dimensions follow the template content and parent layout.",
"properties": {
"component": {
"const": "Loop",
"description": "Public component type identifier."
},
"children": {
"description": "The template and data path structure is the same as the dynamic ChildList.",
"allOf": [
{
"$ref": "./common-types-basic.json#/$defs/ChildList"
},
{
"type": "object",
"properties": {
"componentId": {
"$ref": "./common-types-basic.json#/$defs/ComponentId",
"description": "Loop item template component ID."
},
"path": {
"type": "string",
"description": "The absolute JSON Pointer of the Data Model array."
}
},
"required": [
"componentId",
"path"
]
}
]
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"Stack": {
"type": "object",
"description": "Z-axis stacking container: A **base layer** determines the size, on which 0~N **overlay layers** are stacked.",
"properties": {
"component": {
"const": "Stack",
"description": "Public component type identifier."
},
"child": {
"$ref": "./common-types-basic.json#/$defs/Child",
"description": "The component ID of the **base layer**, single. It fills the entire Stack and **determines the container size**; overlay layers are positioned relative to it. To place multiple elements, wrap them with `Row` / `Column` before passing them here."
},
"overlays": {
"type": "array",
"items": {
"type": "object",
"properties": {
"child": {
"$ref": "./common-types-basic.json#/$defs/Child",
"description": "The component ID of this overlay layer."
},
"position": {
"type": "string",
"enum": [
"topStart",
"topCenter",
"topEnd",
"centerStart",
"center",
"centerEnd",
"bottomStart",
"bottomCenter",
"bottomEnd"
],
"description": "The placement of this overlay on the base layer takes values from the same set of nine-level two-dimensional alignment vocabulary as `ColumnLayout.contentAlignment`."
}
},
"required": [
"child",
"position"
]
},
"description": "**Overlay layer** list, stacked on top of the base layer from bottom to top according to the array order. Each item is an object, only accepting the keys `child` and `position`, both of which are required. If omitted, only the base child is shown and no overlays are created."
},
"backgroundColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the container background, with **higher priority than `backgroundColor`**. Its values and behavior match the same field on `Row` and `Column`."
},
"backgroundColor": {
"type": "string",
"description": "Container background color, hexadecimal `#RRGGBB` or `#AARRGGBB`. A semi-transparent overlay placed over the image is a typical use of a stacked container. If not set, it is transparent."
},
"cornerRadius": {
"type": "number",
"description": "Corner radius, in px, must be a number. If omitted, no explicit radius is set and host container defaults remain in effect."
},
"borderWidth": {
"type": "number",
"description": "Border width in px; must be a number. **A visible border requires `borderColor` to be configured as well.**"
},
"borderColor": {
"type": "string",
"description": "Stroke color, the value format is the same as `backgroundColor`."
},
"borderColorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the border color. It has **higher priority than `borderColor`** and is resolved by the host for the **current client platform, theme, and tenant**. This is the only way to make the border follow light and dark themes: `borderColor` accepts a single value that remains unchanged in dark mode. The line-color families are `common_line_light_color` and `common_line_hard_color`; the host resolves their actual values and opacity. If the host cannot resolve the token, it is treated as omitted and evaluation continues with `borderColor` without reporting an error."
},
"padding": {
"type": "number",
"description": "Uniform padding on all four sides in px. Use it to keep overlays away from the container edge. If omitted, this field adds no padding; a card root can still receive insets from card layout."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Makes the entire container clickable, using the same Action semantics as the `action` field on `Row` / `Column`."
},
"weight": {
"type": "number",
"description": "Flex layout weight."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"child",
"component"
]
},
"ScrollView": {
"type": "object",
"description": "A horizontal or vertical scroll container.",
"properties": {
"component": {
"const": "ScrollView",
"description": "Public component type identifier."
},
"direction": {
"type": "string",
"enum": [
"horizontal",
"vertical"
],
"description": "Scroll direction; omitted means `vertical`. Vertical scrolling additionally requires a positive maxHeight to bound the viewport; horizontal scrolling is constrained by available width."
},
"autoScrollOnUpdate": {
"type": "boolean",
"description": "Automatically scrolls to the end after content updates: to the far right horizontally or to the bottom vertically. Defaults to `false`. During streaming output, PC also blocks user scrolling so follow-scrolling is not interrupted. This applies only to **scrollable** configurations: `direction: \"horizontal\"`, or `direction: \"vertical\"` with `maxHeight`. An uncapped vertical list does not scroll, so this field has no effect there."
},
"maxHeight": {
"type": "number",
"description": "Viewport height limit. When omitted or set to 0, no height limit is set and vertical content expands without internal scrolling. A positive value limits viewport height in both horizontal and vertical modes. Vertical mode also enables internal scrolling; horizontal scrolling still depends on content exceeding the available width."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "List of child component IDs, all references must exist in the current surface."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Defines an interaction handler that can either trigger an agent-side event or execute a local renderer-side function."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component"
]
},
"Tabs": {
"type": "object",
"description": "Tab header and content panel.",
"properties": {
"component": {
"const": "Tabs",
"description": "Component type identification field."
},
"tabs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The tab title."
},
"child": {
"$ref": "./common-types-basic.json#/$defs/Child",
"description": "The ID of the child component."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "When switching to this tab, report: First, the renderer locally switches to the selected state, and then sends the event to the agent. **Each tab is configured independently**; tabs that are not configured only perform local switching and do not report. Only the `event` branch is supported; writing `functionCall` will not work (treated as `action` that is not configured)."
}
},
"required": [
"title",
"child"
]
},
"description": "An array of objects, where each object defines a tab with a title and a child component. The first array entry is initially selected. Subsequent selection is maintained locally; there is no public initial-tab selection field."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"tabs"
]
},
"List": {
"type": "object",
"description": "The public `List` component. `direction` unifies horizontal, vertical, and data-driven lists.",
"properties": {
"component": {
"const": "List",
"description": "Component type identification field."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list."
},
"direction": {
"type": "string",
"enum": [
"vertical",
"horizontal"
],
"description": "The direction in which the list items are laid out. Omitted means `vertical`."
},
"align": {
"type": "string",
"enum": [
"start",
"center",
"end",
"stretch"
],
"description": "Defines the alignment of children along the cross axis. Omitted means `stretch`."
},
"wrap": {
"type": "boolean",
"description": "Whether children wrap to the next line when they do not fit; default `false`. **Effective only with `direction: horizontal`.** A vertical container has unbounded height in a card, so wrapping can never occur; `wrap: true` on a vertical List is ignored and the component still renders normally. ⚠️ **Incompatible with child `weight`**: a weighted child fills the row and prevents wrapping. Do not assign child `weight` when wrapping is required."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"CollapsiblePanel": {
"type": "object",
"description": "Unified collapsible panel, collapsed by default. `variant` controls the expanded-content style: `indented` uses an indented left rule, while `reasoning` uses a flat agent-reasoning layout. `defaultExpanded` can expand the first frame, `maxHeight` enables internal scrolling after a height limit, and `textEffect` can add a shimmer effect to the title.",
"properties": {
"component": {
"const": "CollapsiblePanel",
"description": "Public component type identifier."
},
"title": {
"type": "string",
"minLength": 1,
"description": "Collapsible-panel title. The current integration requires non-empty text."
},
"textEffect": {
"type": "string",
"enum": [
"shimmer"
],
"description": "Text presentation effect. `shimmer` repeatedly sweeps a highlight across the text and is intended for streaming wait states such as \"Thinking...\". If omitted, no effect is applied. On `CollapsiblePanel`, it applies to the panel title. Supported on iOS, Android, HarmonyOS, Windows, macOS, and Web."
},
"icon": {
"type": "string",
"description": "Light-mode title icon. Accepts an image URL or a registered IconName such as Check_L_outlined."
},
"darkIcon": {
"type": "string",
"description": "Dark-mode title icon. Accepts an image URL or a registered IconName; falls back to icon when omitted."
},
"variant": {
"type": "string",
"enum": [
"indented",
"reasoning"
],
"description": "Expanded-content style. `indented`, the default, draws a thin vertical line on the left and indents the content to convey details subordinate to the title; it suits step details and tool-call lists. `reasoning` omits the line and aligns the content with the title, marking the panel as an agent execution or reasoning container; it suits cases where the title acts as a toggle and the content itself is first-class information. The field affects only the expanded content's appearance and this semantic marking: collapse behaviour, `defaultExpanded` and `maxHeight` scrolling are identical in both styles."
},
"defaultExpanded": {
"type": "boolean",
"description": "Whether the first frame is expanded, default is false (collapsed). The semantic of a collapsible panel is \"keep it closed first, click to open if you want to see it,\" so the default is collapsed; panels that really need to be expanded from the start should explicitly set it to true. The expanded/collapsed state after the user clicks the title is maintained locally by the renderer and is not affected by this field."
},
"maxHeight": {
"type": "number",
"description": "Maximum height of the expanded content area in px; default 300. Content that exceeds the limit scrolls inside the panel instead of extending the entire card. Shorter content uses only its actual height without leaving blank space. A value of 0 removes the height limit."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "List of child component IDs or data-driven templates."
},
"colorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name with **the highest color-resolution priority**. When resolved, `customLightColor` and `customDarkColor` are ignored. It uses the same resolution mechanism as `Text.colorToken`."
},
"customLightColor": {
"type": "string",
"description": "The title color in light mode, in hexadecimal. Only effective when `colorToken` is not matched."
},
"customDarkColor": {
"type": "string",
"description": "Title color in dark mode, in hexadecimal. Only effective when `colorToken` is not hit."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component",
"title"
]
},
"ColumnLayout": {
"type": "object",
"description": "Responsive columns from one to six.",
"properties": {
"component": {
"const": "ColumnLayout",
"description": "Public component type identifier."
},
"children": {
"$ref": "./common-types-basic.json#/$defs/ChildList",
"description": "List of child component IDs or data-driven templates."
},
"columns": {
"type": "integer",
"minimum": 1,
"maximum": 6,
"description": "Column-count hint at normal width, from 1 to 6; internally defaults to 2. The current horizontal layout allocates width to the children and their weights rather than grouping or wrapping by this field. Supply two child columns for a two-column layout."
},
"gap": {
"type": "number",
"description": "Column spacing. Omitted means 8; used as vertical spacing after responsive stacking."
},
"columnWidths": {
"type": "array",
"items": {
"anyOf": [
{
"type": "number"
},
{
"const": "auto"
}
]
},
"description": "Child-column weights or auto, corresponding to children in order. Used to allocate width only in horizontal layout; an explicit weight on a child takes precedence. If omitted, children without their own weight receive equal weights. This field no longer allocates width after responsive stacking into a vertical column."
},
"contentAlignment": {
"type": "string",
"enum": [
"topStart",
"topCenter",
"topEnd",
"centerStart",
"center",
"centerEnd",
"bottomStart",
"bottomCenter",
"bottomEnd"
],
"description": "Two-dimensional content alignment; the vocabulary is the same nine-level set as `Stack.overlays[].position`. Columns themselves are evenly weighted, so what this actually controls is **the alignment of each column along the cross axis (vertical)**: when column heights differ, it decides whether the shorter column is aligned to the top, centre or bottom."
},
"responsive": {
"type": "boolean",
"description": "**Responsive stacking on narrow cards**; defaults to false. When enabled with responsiveColumns omitted or set to 1, child columns stack vertically on narrow cards and gap becomes vertical spacing. On wider cards, the actual children and their weights retain a horizontal layout. See the responsive behavior section."
},
"responsiveColumns": {
"type": "integer",
"description": "The number of target columns on a narrow screen, default is `1`; it only takes effect when configured together with `responsive: true`. **Currently only `1` is available**—setting other values does not reduce the columns, keeping the original layout on narrow screens."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"ButtonGroup": {
"type": "object",
"description": "A row of buttons arranged side by side, each button comes with its own text and action. The button style is exactly the same as the standalone `Button` (the same set of capsule styles), the only difference is that multiple buttons are declared at once using an array, with unified control over spacing and wrapping.",
"properties": {
"component": {
"const": "ButtonGroup",
"description": "Component type identification field."
},
"buttons": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Button text. When missing or empty, the entire component degrades to a placeholder."
},
"variant": {
"type": "string",
"enum": [
"default",
"primary",
"borderless"
],
"description": "Button visual variant. Values have the same meaning as the standalone `Button` component's `variant`; default `default`. Values outside the vocabulary render as `default`."
},
"disabled": {
"type": "boolean",
"description": "Whether to disable, default `false`. Disabled items are grayed out **and do not have click events attached**."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Action when clicked. **If this is not configured, it is just an unclickable label**—it does not mint a token or enter the action list."
},
"metadata": {
"description": "Extension metadata for the current button. Host-action write-back uses this item's `dt_actionBindingsV1.action` and does not inherit parent-component metadata."
}
},
"required": [
"label"
],
"allOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostActionOwner"
}
]
},
"description": "Button definition list **cannot be an empty array**. An empty array will cause the entire component to degrade to a placeholder."
},
"responsive": {
"type": "boolean",
"description": "**Adaptive line wrapping for wide and narrow screens**, default `false`. See the 'Responsive Behavior' below."
},
"gap": {
"type": "number",
"description": "Horizontal gap between adjacent buttons in px; default `8`. No gap is added before the first button."
},
"weight": {
"type": "number",
"description": "Relative flex factor when used as a direct child of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"buttons",
"component"
]
}
},
"functions": {},
"$defs": {
"anyComponent": {
"oneOf": [
{
"$ref": "./catalog-components-composition.json#/components/ButtonGroup"
},
{
"$ref": "./catalog-components-composition.json#/components/CardHeader"
},
{
"$ref": "./catalog-components-composition.json#/components/CollapsiblePanel"
},
{
"$ref": "./catalog-components-composition.json#/components/ColumnLayout"
},
{
"$ref": "./catalog-components-composition.json#/components/GridLayout"
},
{
"$ref": "./catalog-components-composition.json#/components/Link"
},
{
"$ref": "./catalog-components-composition.json#/components/List"
},
{
"$ref": "./catalog-components-composition.json#/components/Loop"
},
{
"$ref": "./catalog-components-composition.json#/components/ScrollView"
},
{
"$ref": "./catalog-components-composition.json#/components/Stack"
},
{
"$ref": "./catalog-components-composition.json#/components/Tabs"
}
],
"discriminator": {
"propertyName": "component"
}
},
"anyFunction": false
}
}
@@ -0,0 +1,860 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-components-inputs.json",
"title": "DingTalk AICard Protocol V0.8 Inputs and pickers",
"description": "14 components and 0 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog. Modified by DingTalk from selected entries of the A2UI v1.0 Basic Catalog; some upstream definitions and descriptions are retained. See NOTICE.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {
"TextField": {
"type": "object",
"description": "The public `TextField` component. `variant` unifies short and multiline text input. Clicking opens the input panel and displays the result locally; the field can be disabled. The checks field is not supported. Attach input validation to the submit Button.checks and reference the value binding with a path.",
"properties": {
"component": {
"const": "TextField",
"description": "Component type identification field."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The text label for the input field."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The value of the text field. If omitted, the initial text is empty. Configure a DataBinding to submit user input; omitting value does not create a writeback path."
},
"placeholder": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The placeholder text for the input field. If omitted, it is empty; label is not automatically used as the placeholder."
},
"variant": {
"type": "string",
"enum": [
"shortText",
"longText"
],
"description": "Input variant; default `shortText`. `longText` opens a multiline input panel. DingTalk has no equivalent runtime input type for the base protocol's `number` and `obscured` variants, so this enum exposes only these two values."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"disabled": {
"type": "boolean",
"description": "Whether to disable, default is `false`. When set to `true`, **input events are not triggered** (clicking no longer opens the input panel), **and the text is grayed out** (use the disabled color if there is a value; if there is no value, it is already placeholder gray)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"label"
]
},
"ChoicePicker": {
"type": "object",
"description": "The public `ChoicePicker` component. `variant` unifies single and multiple selection, `displayStyle` selects row, chip, or dropdown presentation, and `disabled` dims the entire group.",
"properties": {
"checks": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/CheckRule"
},
"description": "Evaluated only when `action` is configured. Local state may already have been updated or echoed; a failed check blocks the subsequent Action and shows a toast, but does not guarantee preventing or rolling back the local selection. Attach form validation to the submit button."
},
"component": {
"const": "ChoicePicker",
"description": "Component type identification field."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The label for the group of options."
},
"variant": {
"type": "string",
"enum": [
"multipleSelection",
"mutuallyExclusive"
],
"description": "A hint for how the choice picker should be displayed and behave. Omitted means `mutuallyExclusive` (single selection); explicitly use `multipleSelection` for multiple selection."
},
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The text to display for this option."
},
"value": {
"type": "string",
"description": "The stable value associated with this option."
}
},
"required": [
"label",
"value"
]
},
"description": "The list of available options to choose from."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicStringList",
"description": "The list of currently selected values. This should be bound to a string array in the data model."
},
"displayStyle": {
"type": "string",
"enum": [
"checkbox",
"chips",
"dropdown"
],
"description": "Display style: `checkbox` lays out a vertical list of clickable rows; `chips` lays out a row of tags; `dropdown` renders one compact row that opens the native client picker. **If omitted, `dropdown` is used**. Values outside the enum also fall back to `dropdown` rather than a placeholder."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "**Effective only for `displayStyle: dropdown`, the default when omitted.** After the user confirms a selection in the native panel, the renderer first writes the new value to the path bound by `value` and then sends the event to the agent. Canceling the panel does not trigger the action. Inline styles (`checkbox` and `chips`) **do not trigger** `action` because they have no confirmation step. Their selected values are written to local state through the `value` binding and collected by the submit button's `context` through the parent path."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"disabled": {
"type": "boolean",
"description": "Whether to disable, default is `false`. When set to `true`, **click events are not triggered** (the selection panel will no longer open / options cannot be switched), **and the text, selected mark, and selected background color all switch to the disabled state**."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"options",
"value"
]
},
"CheckBox": {
"type": "object",
"description": "Boolean selection. The required value determines the initial checked state, and label is displayed with the checkbox.",
"properties": {
"checks": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/CheckRule"
},
"description": "Evaluated only when `action` is configured. Local state may already have been updated or echoed; a failed check blocks the subsequent Action and shows a toast, but does not guarantee preventing or rolling back the local selection. Attach form validation to the submit button."
},
"component": {
"const": "CheckBox",
"description": "Component type identification field."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The text to display next to the checkbox."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "The current state of the checkbox (true for checked, false for unchecked)."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Reports after the checked state changes. The renderer first toggles the value locally and writes it to the path bound by `value`, then sends the event to the agent. Only the `event` branch is supported; a `functionCall` branch is ignored as if `action` were omitted."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"label",
"value"
]
},
"Switch": {
"type": "object",
"description": "Switch state and size.",
"properties": {
"component": {
"const": "Switch",
"description": "Public component type identifier."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Whether the switch is selected. Omitted means false, initially off."
},
"disabled": {
"type": "boolean",
"description": "Whether the switch is disabled; default `false`. When `true`, the renderer sends `enabled=0`; the native client switch **cannot be toggled and is dimmed using the system style**."
},
"size": {
"type": "string",
"enum": [
"small",
"normal",
"large"
],
"description": "Size preset; default `normal`."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Reports after the switch state changes. The renderer first toggles the value locally, then sends the event to the agent. Only the `event` branch is supported; a `functionCall` branch is ignored as if `action` were omitted."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component"
]
},
"DateTimeInput": {
"type": "object",
"description": "Date and time picker entry.",
"properties": {
"checks": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/CheckRule"
},
"description": "Evaluated only when `action` is configured. Local state may already have been updated or echoed; a failed check blocks the subsequent Action and shows a toast, but does not guarantee preventing or rolling back the local selection. Attach form validation to the submit button."
},
"component": {
"const": "DateTimeInput",
"description": "Component type identification field."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The selected date and/or time value in ISO 8601 format. If not yet set, initialize with an empty string."
},
"enableDate": {
"type": "boolean",
"description": "If true, allows the user to select a date. Omitted means true."
},
"enableTime": {
"type": "boolean",
"description": "If true, allows the user to select a time. Omitted means false, so omitting both flags selects dates only. enableDate and enableTime cannot both be false."
},
"min": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The minimum allowed date/time in ISO 8601 format."
},
"max": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The maximum allowed date/time in ISO 8601 format."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The text label for the input field."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Reports after the user confirms a value in the date/time panel. The renderer first writes the new value to the path bound by `value` and then sends the event to the agent. Canceling the picker does not report an event."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"NumberInput": {
"type": "object",
"description": "Numeric input entry that opens an input panel and displays the result. It is not currently a stepper with increment/decrement buttons. The required value supplies the initial number.",
"properties": {
"component": {
"const": "NumberInput",
"description": "Public component type identifier."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Current value."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Input label used as the numeric input panel title. It also supplies the placeholder text when placeholder is omitted or an empty string; TextField does not automatically substitute label for placeholder."
},
"min": {
"type": "number",
"description": "Minimum. If omitted, no explicit lower bound is supplied to the input panel."
},
"max": {
"type": "number",
"description": "Maximum value. If omitted, no explicit upper bound is supplied to the input panel."
},
"precision": {
"type": "integer",
"description": "Decimal precision. Omitted means 0."
},
"placeholder": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Placeholder text."
},
"disabled": {
"type": "boolean",
"description": "Whether to disable, default is `false`. When it is `true`, **input events are not triggered** (clicking no longer opens the numeric input panel), **and the text is grayed out**. Implemented with the same form row as `TextField`."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Reports after the user completes an edit in the input panel. The renderer first writes the new value to the path bound by `value`, then sends the event to the agent. Only the `event` branch is supported; a `functionCall` branch is ignored as if `action` were omitted."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"Rating": {
"type": "object",
"description": "Star rating.",
"properties": {
"component": {
"const": "Rating",
"description": "Public component type identifier."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Rating value (0-5)."
},
"onTap": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Reports after the user changes the rating. The renderer **first writes the new rating to the path bound by `value`, then sends the event to the agent**. The rating is interactive only when this handler is configured; otherwise it is read-only. Only the `event` branch is supported. A `functionCall` branch is ignored as if `onTap` were omitted because a host function would occupy the star control's click slot and prevent the local rating change."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"Slider": {
"type": "object",
"description": "Numeric slider. DingTalk renders it as a **read-only progress bar** whose proportion is calculated from `value`, `min`, and `max`; it cannot be dragged.",
"properties": {
"checks": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/CheckRule"
},
"description": "Evaluated after a click only when `action` is configured. A failed check blocks the subsequent Action and shows a toast; no checks run without an action. Attach form validation to the submit button."
},
"component": {
"const": "Slider",
"description": "Component type identification field."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The label for the slider."
},
"min": {
"type": "number",
"description": "The minimum value of the slider. Omitted means 0."
},
"max": {
"type": "number",
"description": "The maximum value of the slider."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "The current value of the slider."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Report to the agent when clicking the progress bar."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"max",
"value"
]
},
"InputList": {
"type": "object",
"description": "Input list whose rows can be added and removed.",
"properties": {
"component": {
"const": "InputList",
"description": "Public component type identifier."
},
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Input-list title."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicStringList",
"description": "Addable and removable string array that should be bound to the Data Model. The array sent by the server determines the initial row count and values. After local additions, deletions, or edits, the submit button's `context` on the same card reads **the complete current local array**, including locally added rows, through this path."
},
"placeholder": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Placeholder hint for the new input item."
},
"addLabel": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Label for adding an item. If omitted, the runtime supplies its built-in text; explicitly set it for application-specific wording or language."
},
"variant": {
"type": "string",
"enum": [
"shortText",
"longText"
],
"description": "Input variant for each row; default `shortText`. `longText` renders a multiline text area, while other values use `shortText`. ⚠️ The `inline` and `dialog` values documented previously **were never implemented** and silently render as `shortText`."
},
"disabled": {
"type": "boolean",
"description": "Whether the list is disabled; default `false`. When `true`, **no input events are attached, the Add control and per-row delete buttons are not rendered, and the text and label are dimmed**. The row count remains fixed at the number initially sent by the server."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Reports when Add is clicked. The renderer first adds a row locally, then sends the event to the agent. Row deletion and per-row editing **do not report events**; they update only local state and are merged into the DataModel through the path bound by `value` on submission. Only the `event` branch is supported; a `functionCall` branch is ignored as if `action` were omitted."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"CheckboxListMulti": {
"type": "object",
"description": "Multi-select list and local state.",
"properties": {
"component": {
"const": "CheckboxListMulti",
"description": "Public component type identifier."
},
"children": {
"description": "Dynamic `ChildList` used to declare a bindable multi-select list.",
"allOf": [
{
"$ref": "./common-types-basic.json#/$defs/ChildList"
},
{
"type": "object",
"properties": {
"componentId": {
"$ref": "./common-types-basic.json#/$defs/ComponentId",
"description": "Subcomponent template reused for each row."
},
"path": {
"type": "string",
"description": "The absolute JSON Pointer of the Data Model array."
}
},
"required": [
"componentId",
"path"
]
}
]
},
"selectedIndexes": {
"type": "array",
"items": {
"type": "integer"
},
"description": "**Initial** selected row indexes, zero-based and sent one way. User changes do not update this field itself; configure `value` to capture the result. If omitted, no rows are initially selected."
},
"size": {
"type": "string",
"enum": [
"small",
"middle",
"large"
],
"description": "Option-row size; default `middle`."
},
"disabled": {
"type": "boolean",
"description": "Whether the control is disabled; default `false`. When `true`, **do not attach click events** (an attached handler still responds to clicks while the state stays unchanged, which is more confusing than being unclickable); **whether it is greyed out is decided by the renderer's checkbox**."
},
"contentCheckable": {
"type": "boolean",
"description": "Whether clicking the entire row toggles its selected state; default `true`."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "After the check status of any row is toggled, it will be reported (first change the local status on the renderer, then send the event to the agent). Only the `event` branch is supported; writing `functionCall` will not work (treated as unconfigured `action`)."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DataBinding",
"description": "Write-back path for the selection result. The value written back is **an array of selected row indexes**, with the same semantics as `selectedIndexes`. If omitted, no value is written back and the submit button cannot reference the selection. No write-back occurs when `disabled` is true."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"children",
"component"
]
},
"CheckableImageList": {
"type": "object",
"description": "Selectable layout for one to nine images, cropped to 4:3 with row and column gaps of 12. Column count follows card width and falls back to 2 when width is unavailable. Desktop hosts supporting layout-time reflow recalculate 1–5 columns from actual component width; other hosts use the width-based count.",
"properties": {
"component": {
"const": "CheckableImageList",
"description": "Public component type identifier."
},
"images": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Image URL."
},
"title": {
"type": "string",
"description": "Image title."
}
},
"required": [
"url"
]
}
]
},
"minItems": 1,
"maxItems": 9,
"description": "One to nine images. The object form requires `url` and accepts an optional string `title`. If any item has a title, the title area is reserved for every item. The selected index matches the item's original position."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DataBinding",
"description": "DataBinding that stores the selected image indexes."
},
"previewEnabled": {
"type": "boolean",
"description": "Whether image clicks can open a preview; omitted means true. Only loadable URLs participate in preview. Setting false disables image preview, not selection."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"images",
"value"
]
},
"UserPicker": {
"type": "object",
"description": "Opens the DingTalk contact picker to select users.",
"properties": {
"component": {
"const": "UserPicker",
"description": "Public component type identifier."
},
"placeholder": {
"type": "string",
"description": "Placeholder text. For an empty selection, the displayed prompt uses title first, then placeholder; omitting both uses the runtime's built-in prompt."
},
"title": {
"type": "string",
"description": "Picker-panel title."
},
"maxSelections": {
"type": "number",
"description": "Maximum selectable number of users (default 10); setting it to `1` means single selection, and greater than `1` means multiple selection."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DataBinding",
"description": "The DataBinding of the selected user. DingTalk currently retains the original result form of the host selector: a successful single selection writes back the user object, a successful multiple selection writes back an array of user objects, and clearing multiple selections writes back an empty array."
},
"disabled": {
"type": "boolean",
"description": "Whether to disable, default is `false`. When it is `true`, **do not trigger the click event** (no longer open the selection panel), **and the plus icon on the right is grayed out**."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Defines an interaction handler that can either trigger an agent-side event or execute a local renderer-side function."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"ConversationPicker": {
"type": "object",
"description": "Call the DingTalk conversation selector.",
"properties": {
"component": {
"const": "ConversationPicker",
"description": "Public component type identifier."
},
"placeholder": {
"type": "string",
"description": "Placeholder text. For an empty selection, the displayed prompt uses title first, then placeholder; omitting both uses the runtime's built-in prompt."
},
"title": {
"type": "string",
"description": "Picker-panel title."
},
"multiple": {
"type": "boolean",
"description": "Whether multiple selection is allowed. Omitting both multiple and maxSelections gives single selection. Explicit multiple=true or maxSelections greater than 1 enables multiple selection. Single-selection results may be an object or a one-element array depending on the host; multiple-selection results are arrays."
},
"maxSelections": {
"type": "number",
"description": "Maximum selection count. Omitted means 1 when multiple selection is not enabled; a value greater than 1 also enables multiple selection. With explicit multiple=true and no maxSelections, the host determines the limit. Set an explicit value when a fixed limit is required."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DataBinding",
"description": "DataBinding for selected conversations. DingTalk currently preserves the host picker's original result shape: successful single selection may write back a conversation object or a single-element object array; successful multiple selection writes back an array of conversation objects; confirming an empty selection writes back an empty array."
},
"disabled": {
"type": "boolean",
"description": "Whether the picker is disabled; default `false`. When `true`, **no click event is attached**, the conversation picker no longer opens, and **the plus icon on the right is dimmed**."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Defines an interaction handler that can either trigger an agent-side event or execute a local renderer-side function."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"ImageUpload": {
"type": "object",
"description": "Call DingTalk image upload, fill the upload result back into the `value` binding path, and list the uploaded files line by line under the button (deletable).",
"properties": {
"component": {
"const": "ImageUpload",
"description": "Public component type identifier."
},
"label": {
"type": "string",
"description": "Upload-button label. If omitted, the runtime supplies its built-in text. An empty string is not allowed; explicitly set the label for application-specific wording or language."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DataBinding",
"description": "DataBinding for the upload result, an array of image URLs. The initial value sent by the server renders the uploaded-file rows. After upload or deletion, the latest URL array remains in client-local state and is merged into the DataModel through this path **on submission**, allowing the same card's submit-button `context` to read the images actually uploaded by the user. A literal array is also allowed; in that case there is no write-back target, but file rows still reflect local upload results."
},
"disabled": {
"type": "boolean",
"description": "Whether upload is disabled; default `false`. When `true`, the renderer uses the **disabled style**, does not respond to upload actions, and omits delete buttons from uploaded-file rows."
},
"requiresAuth": {
"type": "boolean",
"description": "Whether to use authenticated upload; default `true`. When `true`, the write-back value is an authenticated media URL; when `false`, it is a regular URL."
},
"maxFiles": {
"type": "number",
"description": "The maximum number of images that can be selected at one time, default is `9`, must be a positive integer."
},
"action": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "**Reports after a successful upload**, not when the upload control is clicked. The renderer first appends the newly returned URLs to the path bound by `value`, then sends the event to the agent. Failed uploads and canceled image selection do not report an event. A successful retry of a failed row reports in the same way as the initial upload. Only the `event` branch is supported; a `functionCall` branch is ignored as if `action` were omitted."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
}
},
"functions": {},
"$defs": {
"anyComponent": {
"oneOf": [
{
"$ref": "./catalog-components-inputs.json#/components/CheckBox"
},
{
"$ref": "./catalog-components-inputs.json#/components/CheckableImageList"
},
{
"$ref": "./catalog-components-inputs.json#/components/CheckboxListMulti"
},
{
"$ref": "./catalog-components-inputs.json#/components/ChoicePicker"
},
{
"$ref": "./catalog-components-inputs.json#/components/ConversationPicker"
},
{
"$ref": "./catalog-components-inputs.json#/components/DateTimeInput"
},
{
"$ref": "./catalog-components-inputs.json#/components/ImageUpload"
},
{
"$ref": "./catalog-components-inputs.json#/components/InputList"
},
{
"$ref": "./catalog-components-inputs.json#/components/NumberInput"
},
{
"$ref": "./catalog-components-inputs.json#/components/Rating"
},
{
"$ref": "./catalog-components-inputs.json#/components/Slider"
},
{
"$ref": "./catalog-components-inputs.json#/components/Switch"
},
{
"$ref": "./catalog-components-inputs.json#/components/TextField"
},
{
"$ref": "./catalog-components-inputs.json#/components/UserPicker"
}
],
"discriminator": {
"propertyName": "component"
}
},
"anyFunction": false
}
}
@@ -0,0 +1,829 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-components-specialized.json",
"title": "DingTalk AICard Protocol V0.8 Data display and media",
"description": "12 components and 0 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog. Modified by DingTalk from selected entries of the A2UI v1.0 Basic Catalog; some upstream definitions and descriptions are retained. See NOTICE.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {
"ImageList": {
"type": "object",
"description": "Multiple image thumbnails and quantity aggregation. The current image area is built with height 200; width is constrained by the parent layout.",
"properties": {
"component": {
"const": "ImageList",
"description": "Public component type identifier."
},
"images": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"description": "An array of image URLs, containing at least one item."
},
"previewEnabled": {
"type": "boolean",
"description": "Whether clicking can open a preview; omitted means `true`. Preview is enabled only when the group contains a loadable image URL."
},
"fit": {
"type": "string",
"enum": [
"contain",
"cover",
"fill",
"none",
"scaleDown"
],
"description": "Image scaling using the CSS `object-fit` vocabulary. Omitted means containment, corresponding to `contain`, unlike Image's omitted crop-to-fill behavior. `contain`/`scaleDown`/`none` map to containment, `cover` to cropping, and `fill` to stretching. Do not send internal renderer scaling names."
},
"cornerRadius": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Image-group corner radius; omitted means `4`, unlike Image's default of `6`."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"images"
]
},
"Avatar": {
"type": "object",
"description": "Avatar image. Without size configuration it uses 48×48, with an outer corner radius equal to one quarter of the actual size. sizeType selects presets or customSize.",
"properties": {
"component": {
"const": "Avatar",
"description": "Public component type identifier."
},
"imageUrl": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Avatar image URL."
},
"sizeType": {
"type": "string",
"enum": [
"Standard",
"Custom"
],
"description": "Size type. Set it to `Standard` to use the `size` preset. Set it to `Custom`, or omit it, to use `customSize`; if `customSize` is also omitted, the runtime uses 48px. Other values do not conform to the public Schema."
},
"size": {
"type": "string",
"enum": [
"extraSmall",
"small",
"middle"
],
"description": "Size preset, effective only when `sizeType` is `Standard`: `extraSmall` = 20px, `small` = 30px, and `middle` = 48px. If omitted, `middle` is used. Values outside this vocabulary do not conform to the public Schema."
},
"customSize": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Custom size. Used when sizeType is not Standard; omitted means 48."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component"
]
},
"AvatarGroup": {
"type": "object",
"description": "Avatar stacking and overflow count.",
"properties": {
"component": {
"const": "AvatarGroup",
"description": "Public component type identifier."
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"imageUrl": {
"type": "string",
"description": "An accessible absolute or relative resource address."
},
"name": {
"type": "string",
"description": "Used to create a text avatar when no image is available; providing only `imageUrl` is also supported. At least one of `name` and `imageUrl` must be a non-empty string, as checked at runtime."
}
}
},
"description": "List of user objects."
},
"maxVisible": {
"type": "integer",
"description": "Maximum number of avatars displayed (show +N when exceeded). Omitted means 5."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"items"
]
},
"Table": {
"type": "object",
"description": "Table headers, zebra stripes, and paginated data.",
"properties": {
"component": {
"const": "Table",
"description": "Public component type identifier."
},
"data": {
"anyOf": [
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"type": "object",
"properties": {
"meta": {
"type": "array",
"items": {
"type": "object",
"properties": {
"colKey": {
"type": "string",
"description": "Column key. Each row object is looked up by this key to obtain the cell value for this column; when a row lacks the key, that cell renders empty without affecting other columns."
},
"colName": {
"type": "string",
"description": "Column name shown in the header. When the value is not a string, it falls back to `colKey` and the table still renders."
},
"type": {
"type": "string",
"enum": [
"text",
"tag",
"link",
"microApp",
"button",
"STRING",
"string",
"OBJECT",
"object",
"MICROAPP",
"microapp",
"BUTTON"
],
"description": "Column rendering types. It is recommended to use `text`, `tag`, `link`, `microApp`, `button`; other values are used for compatibility with existing cards. For numeric cells, use `text` or `STRING`. The cell value can be directly filled with a JSON number, and the renderer will convert it to text for display; `NUMBER` is not supported."
}
},
"required": [
"colKey",
"colName",
"type"
]
},
"minItems": 1,
"maxItems": 100,
"description": "Column definitions in display order, from 1 to 100 columns. Every column must declare `colKey`, `colName`, and `type`; if any one is missing, the entire table is replaced by the component fallback (the standard message when `fallbackMarkdown` is not set)."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {}
},
"minItems": 0,
"maxItems": 1000,
"description": "Row data, up to 1000 rows; each row must be an object whose keys correspond to `colKey` in `meta`. An empty array is allowed and renders a header-only table, but the field itself must still be sent."
}
},
"required": [
"meta",
"data"
]
}
],
"description": "Table data with meta (columns) and data (rows)."
},
"cellPadding": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Cell padding. Omitted means 12 on each horizontal side and 8 on each vertical side. An explicit number applies uniformly to all four sides."
},
"imageSize": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "The size of the image in the table. Omitted means 30."
},
"pageSize": {
"type": "integer",
"description": "Number of rows per page. Defaults to 10 and takes effect only when `pagination` is `true`. Values below 1 are invalid; removing this field restores the default."
},
"maxLine": {
"type": "integer",
"description": "Maximum number of display lines for **text-based data cells** (`STRING`, `OBJECT`, and `MICROAPP`); excess text is truncated. Defaults to 3. The table header is unaffected and always allows up to 3 lines. `BUTTON` cells are also unaffected because renderer buttons do not use a line-count setting. Values of 0 or less are normalized to 1; sending 0 directly to the renderer would render no lines and make the column text disappear."
},
"zebra": {
"type": "boolean",
"description": "Whether to enable zebra stripes (alternate row coloring). Omitted means false, with no alternating row colors."
},
"pagination": {
"type": "boolean",
"description": "Whether pagination is enabled; default `false`. **Pagination is entirely local**: the full dataset is sent once, and changing pages only updates the view without fetching more data from the server. The pagination control is not rendered when the data fits on one page because it would have no effect."
},
"pageIndex": {
"type": "integer",
"description": "**Initial** page index (0-based), default is 0. It only determines which page the first frame stops at, and subsequent page turns are controlled by the user. If it exceeds the total number of pages, it is clamped to the last page; negative values are considered invalid. If this field is removed, it defaults to rendering page 0."
},
"scrollAreaWidth": {
"type": "number",
"description": "Width of the horizontal scroll area (px), default `600`. When there are too many columns to fit on one screen, the table scrolls horizontally within this width. Must be a positive number."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"data"
]
},
"Chart": {
"type": "object",
"description": "Unified public charts: standard data, template rendering, and responsive sizing. **Charts are always clickable for details**, no configuration is needed or possible—the charts in the cards are only a few tens of px high and the values are hard to see; clicking to view the larger image is a basic capability of this component.",
"properties": {
"component": {
"const": "Chart",
"description": "Public component type identifier."
},
"data": {
"anyOf": [
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Chart types. Current repository real examples include lineChart, histogram, and pieChart."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"x": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
],
"description": "Horizontal axis or category values."
},
"y": {
"type": "number",
"description": "Numerical value."
},
"type": {
"type": "string",
"description": "Optional series name."
}
},
"required": [
"x",
"y"
]
},
"description": "Uses the type and constraints defined by the corresponding catalog Schema."
},
"config": {
"type": "object",
"description": "Optional chart configurations, such as axes, colors, and padding.",
"properties": {}
}
},
"required": [
"type",
"data"
]
}
],
"description": "Chart data binding or real chart data object."
},
"aspectRatio": {
"type": "string",
"enum": [
"1:1",
"2:1",
"4:3",
"16:9"
],
"description": "Responsive aspect ratio. Mutually exclusive with height; effective when height is not configured. When both height and aspectRatio are omitted, `2:1` is used. Do not automatically add this field when a fixed height is already configured."
},
"height": {
"type": "number",
"description": "Fixed height. Configuring this field enables a fixed height, and you cannot configure aspectRatio at the same time."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"data"
]
},
"ProgressBar": {
"type": "object",
"description": "The public progress component. `variant` unifies continuous bars and compact blocks under the same value, color, and light/dark-theme model. Without a resolved color configuration, the fill uses the host brand color, falling back to built-in light/dark colors if resolution is unavailable.",
"properties": {
"component": {
"const": "ProgressBar",
"description": "Public component type identifier."
},
"variant": {
"type": "string",
"enum": [
"bar",
"block"
],
"description": "bar is for a continuous progress bar; block is for a compact block-style progress. The renderer displays continuous or block progress according to this mode. Omitted means `bar`."
},
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Progress value, with a semantic range of 0 to 100; validation fails when the literal is out of bounds, and the data-bound value is restricted to the valid range by the renderer."
},
"showLabel": {
"type": "boolean",
"description": "Only effective in bar mode; whether to display percentage text. Omitted means true."
},
"labelPosition": {
"type": "string",
"enum": [
"start",
"inside",
"end"
],
"description": "Only effective in bar mode; the position of the percentage text is relative to the track, default `end` (right side of the track, spacing 12px). `start` places it on the left side, `inside` overlays it within the filled segment without taking additional horizontal space."
},
"colorToken": {
"$ref": "./common-types-visual.json#/$defs/ColorToken",
"description": "Only the 57 IDs in the common `ColorToken` enum are allowed. A DDesign token name for the progress-bar fill, with **the highest color-resolution priority**. When resolved, `customLightColor`, `customDarkColor`, and `color` are ignored. The host resolves it for the current client platform and theme through the same mechanism as `Text.colorToken`. If the host cannot resolve the token, evaluation falls back to the subsequent color sources."
},
"customLightColor": {
"type": "string",
"description": "Light-theme color shared by both modes. Supports `#RRGGBB` and `#AARRGGBB`."
},
"customDarkColor": {
"type": "string",
"description": "Dark-theme color shared by both modes; `customLightColor` must also be provided. The renderer selects and sends the light or dark value for the current theme. A theme change takes effect after the card's next refresh."
},
"rounded": {
"type": "boolean",
"description": "Only effective in bar mode; whether to use capsule rounded corners. Omitted means true."
},
"size": {
"type": "string",
"enum": [
"medium",
"small"
],
"description": "Effective only in `bar` mode: according to the design mock, `medium` uses a 6px track and `small` uses a 4px track. When `rounded` is `true`, the corner radius is half the track height. `block` mode is fixed at 32×32. Omitted means `medium`."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"value"
]
},
"ElapsedTime": {
"type": "object",
"description": "Elapsed-time counter.",
"properties": {
"component": {
"const": "ElapsedTime",
"description": "Public component type identifier."
},
"startTime": {
"type": "number",
"description": "Start timestamp in milliseconds. When omitted, 0 is passed to the renderer. Set an explicit timestamp when a definite start is required rather than relying on host handling of 0."
},
"prefix": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Left text. If omitted, no prefix text is shown."
},
"suffix": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Text on the right. If omitted, no suffix text is shown."
},
"gravity": {
"type": "string",
"enum": [
"left",
"center",
"right"
],
"description": "Alignment. Omitted means `left`."
},
"textStyleType": {
"type": "string",
"description": "Text style category. The current DingTalk implementation always uses `Standard`; setting this field does not switch to custom styling."
},
"textStyle": {
"type": "string",
"description": "Text style preset. The current DingTalk implementation always uses `paragraph`, regardless of this field."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component"
]
},
"Countdown": {
"type": "object",
"description": "Countdown and end event.",
"properties": {
"component": {
"const": "Countdown",
"description": "Public component type identifier."
},
"endTime": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "The Unix millisecond timestamp of the countdown target moment. Literals, DataBinding evaluation results, and FunctionCall return values must all be JSON numbers; numeric strings or ISO 8601 date-time strings are not accepted."
},
"prefix": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Text on the left side of the countdown. If omitted, no prefix text is shown."
},
"suffix": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Text on the right side of the countdown. If omitted, no suffix text is shown."
},
"gravity": {
"type": "string",
"enum": [
"left",
"center",
"right"
],
"description": "Alignment (left/center/right). Omitted means `left`."
},
"textStyleType": {
"type": "string",
"description": "Text style category. The current DingTalk implementation always uses `Standard`; setting this field does not switch to custom styling."
},
"textStyle": {
"type": "string",
"description": "Text style preset. The current DingTalk implementation always uses `paragraph`, regardless of this field."
},
"onComplete": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Action triggered when countdown reaches zero."
},
"onTap": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Action executed when the component is clicked; only effective if the component declares click capability."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"endTime"
]
},
"Video": {
"type": "object",
"description": "Cover, inline playback, and control bar. The current implementation disables autoplay and looping, enables inline playback and controls, and configures a 16:9 poster area with corner radius 6. These settings are determined by the renderer implementation.",
"properties": {
"component": {
"const": "Video",
"description": "Component type identification field."
},
"url": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The URL of the video to display."
},
"posterUrl": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The URL of the poster image to display before the video plays. If omitted, no custom poster URL is supplied; poster display is handled by the player."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"url"
]
},
"AudioPlayer": {
"type": "object",
"description": "Audio cover, title, and playback progress.",
"properties": {
"component": {
"const": "AudioPlayer",
"description": "Component type identification field."
},
"url": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The URL of the audio to be played."
},
"coverUrl": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Cover image URL. Both PC and mobile render it: `AudioWidget` places it to the left of the player on PC, while the rich-text audio node renders it on mobile. If omitted, the player shows no cover image."
},
"description": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "A description of the audio, such as a title or summary. If omitted, no custom title is supplied."
},
"artist": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Artist / Source attribution, displayed on the side of the title. Together with `description`, it forms the text area of the player; if not configured, this line will not be displayed."
},
"weight": {
"type": "number",
"description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"url"
]
},
"File": {
"type": "object",
"description": "File artifact. Its icon is derived from the file type; clicking the row opens the file, and a separate open button appears at the end of the row.",
"properties": {
"component": {
"const": "File",
"description": "Public component type identifier."
},
"fileName": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "The display file name with a suffix, which is also the only input for the renderer to automatically recognize the file icon and type."
},
"url": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "File download URL. May be an authenticated absolute URL or a relative URL resolvable by the current application."
},
"mimeType": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Optional MIME type used only for supplementary information and open behavior; it does not affect file-icon selection."
},
"size": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Optional file size, in bytes; the renderer is responsible for formatting it as KB, MB, or GB. If omitted, no file size is displayed."
},
"description": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Optional supplementary information, such as file source, generation status, or version. If omitted, auxiliary text is generated from file size and type; without a size, only the type is shown."
},
"previewUrl": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Online-preview page URL. When provided, clicking the row opens this URL; otherwise it falls back to `url`. Its semantics differ from `url`, which identifies the file itself. The download button at the end of the row **always** uses `url` and is unaffected by this field."
},
"onPreview": {
"$ref": "./common-types-basic.json#/$defs/Action",
"description": "Triggered when the row is clicked. ⚠️ Uses a two-step **open first, then report on success** flow: the renderer first opens `previewUrl`, or `url` when `previewUrl` is omitted, and sends the event to the agent only after the open succeeds. If the file cannot be opened, no event is reported. Therefore, **there is no need** to wait for an `openLink` response from the agent; navigation is immediate. If omitted, the row has no click action."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"fileName",
"url"
]
},
"ImageCarousel": {
"type": "object",
"description": "DingTalk image carousel component.",
"properties": {
"component": {
"const": "ImageCarousel",
"description": "Public component type identifier."
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Carousel image URL."
},
"linkUrl": {
"type": "string",
"description": "Optional URL opened when the carousel item is clicked."
},
"title": {
"type": "string",
"description": "Uses the type and constraints defined by the corresponding catalog Schema."
},
"description": {
"type": "string",
"description": "Uses the type and constraints defined by the corresponding catalog Schema."
}
},
"required": [
"url"
]
},
"description": "Carousel item. `url` is required; `linkUrl`, title, and description are optional."
},
"cornerRadius": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Corner radius in px; omitted means `12`. Desktop applies it to carousel images; mobile clips the image group with the outer container. Use a finite non-negative number; `0` gives square corners."
},
"autoplayIntervalMs": {
"type": "integer",
"description": "Auto-scroll interval in ms (0 = disabled). Omitted means 0, with automatic scrolling disabled."
},
"previewEnabled": {
"type": "boolean",
"description": "Whether clicking can open a preview; omitted means true. Preview is enabled only when a loadable image URL exists."
},
"fit": {
"type": "string",
"enum": [
"contain",
"cover",
"fill",
"none",
"scaleDown"
],
"description": "Image scaling method, the vocabulary is consistent with CSS `object-fit` (`contain`/`scaleDown`/`none` → full display, `cover` → cropped to fill, `fill` → stretched to fill). **Only sent when explicitly provided**; values outside the vocabulary (including internal names like `fitCenter`/`fitXY`/`centerCrop`) will be silently reverted to the default."
},
"weight": {
"type": "number",
"description": "Flex layout weight (used to allocate space proportionally)."
},
"fallbackMarkdown": {
"type": "string",
"description": "Markdown fallback content used when the current renderer cannot render the component."
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
}
},
"required": [
"component",
"items"
]
}
},
"functions": {},
"$defs": {
"anyComponent": {
"oneOf": [
{
"$ref": "./catalog-components-specialized.json#/components/AudioPlayer"
},
{
"$ref": "./catalog-components-specialized.json#/components/Avatar"
},
{
"$ref": "./catalog-components-specialized.json#/components/AvatarGroup"
},
{
"$ref": "./catalog-components-specialized.json#/components/Chart"
},
{
"$ref": "./catalog-components-specialized.json#/components/Countdown"
},
{
"$ref": "./catalog-components-specialized.json#/components/ElapsedTime"
},
{
"$ref": "./catalog-components-specialized.json#/components/File"
},
{
"$ref": "./catalog-components-specialized.json#/components/ImageCarousel"
},
{
"$ref": "./catalog-components-specialized.json#/components/ImageList"
},
{
"$ref": "./catalog-components-specialized.json#/components/ProgressBar"
},
{
"$ref": "./catalog-components-specialized.json#/components/Table"
},
{
"$ref": "./catalog-components-specialized.json#/components/Video"
}
],
"discriminator": {
"propertyName": "component"
}
},
"anyFunction": false
}
}
@@ -0,0 +1,530 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-functions-core.json",
"title": "DingTalk AICard Protocol V0.8 Core functions adapted by DingTalk from A2UI",
"description": "0 components and 14 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog. Modified by DingTalk from selected entries of the A2UI v1.0 Basic Catalog; some upstream definitions and descriptions are retained. See NOTICE.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThese 14 functions share upstream A2UI names but retain DingTalk definitions; this is not an unmodified official catalog.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {},
"functions": {
"required": {
"type": "object",
"description": "Checks that the value is not null, undefined, or empty.",
"returnType": "boolean",
"properties": {
"call": {
"const": "required"
},
"args": {
"type": "object",
"properties": {
"value": {
"description": "The value to check."
}
},
"required": [
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"regex": {
"type": "object",
"description": "Checks that the value matches a regular expression string.",
"returnType": "boolean",
"properties": {
"call": {
"const": "regex"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"pattern": {
"type": "string"
}
},
"required": [
"value",
"pattern"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"length": {
"type": "object",
"description": "Checks string length constraints.",
"returnType": "boolean",
"properties": {
"call": {
"const": "length"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"min": {
"type": "integer",
"minimum": 0
},
"max": {
"type": "integer",
"minimum": 0
}
},
"required": [
"value"
],
"anyOf": [
{
"required": [
"min"
]
},
{
"required": [
"max"
]
}
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"numeric": {
"type": "object",
"description": "Checks numeric range constraints.",
"returnType": "boolean",
"properties": {
"call": {
"const": "numeric"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"min": {
"type": "number"
},
"max": {
"type": "number"
}
},
"required": [
"value"
],
"anyOf": [
{
"required": [
"min"
]
},
{
"required": [
"max"
]
}
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"email": {
"type": "object",
"description": "Checks that the value is a valid email address.",
"returnType": "boolean",
"properties": {
"call": {
"const": "email"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
}
},
"required": [
"value"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"formatString": {
"type": "object",
"description": "Uses `${/path}` for data-path interpolation. Function calls such as `now()` and `formatDate(...)` are not supported inside placeholders. `args.value` may use a JSON-form data binding or value function; it is first evaluated to a string and then interpolated with data paths.",
"returnType": "string",
"properties": {
"call": {
"const": "formatString"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
}
},
"required": [
"value"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"formatNumber": {
"type": "object",
"description": "Formats a number with grouping and decimal precision.",
"returnType": "string",
"properties": {
"call": {
"const": "formatNumber"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"decimals": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"grouping": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
}
},
"required": [
"value"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"formatCurrency": {
"type": "object",
"description": "Formats a number as a currency string.",
"returnType": "string",
"properties": {
"call": {
"const": "formatCurrency"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"currency": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "ISO 4217 currency code (e.g. USD, EUR, CNY)."
},
"decimals": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"grouping": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
}
},
"required": [
"currency",
"value"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"formatDate": {
"type": "object",
"description": "Formats a timestamp into a string using Unicode TR35 date pattern.",
"returnType": "string",
"properties": {
"call": {
"const": "formatDate"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"format": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Unicode TR35 pattern (e.g. 'yyyy-MM-dd', 'HH:mm')."
}
},
"required": [
"format",
"value"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"pluralize": {
"type": "object",
"description": "Returns a localized string based on CLDR plural category of the count.",
"returnType": "string",
"properties": {
"call": {
"const": "pluralize"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"zero": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"one": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"two": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"few": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"many": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
},
"other": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
}
},
"required": [
"value",
"other"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"openUrl": {
"type": "object",
"description": "Opens the specified URL in a browser or handler.",
"returnType": "void",
"properties": {
"call": {
"const": "openUrl"
},
"args": {
"type": "object",
"properties": {
"url": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
}
},
"required": [
"url"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"and": {
"type": "object",
"description": "Performs logical AND on a list of boolean values.",
"returnType": "boolean",
"properties": {
"call": {
"const": "and"
},
"args": {
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
},
"minItems": 2
}
},
"required": [
"values"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"or": {
"type": "object",
"description": "Performs logical OR on a list of boolean values.",
"returnType": "boolean",
"properties": {
"call": {
"const": "or"
},
"args": {
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
},
"minItems": 2
}
},
"required": [
"values"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"not": {
"type": "object",
"description": "Performs logical NOT on a boolean value.",
"returnType": "boolean",
"properties": {
"call": {
"const": "not"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
}
},
"required": [
"value"
],
"unevaluatedProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
}
},
"$defs": {
"anyComponent": false,
"anyFunction": {
"oneOf": [
{
"$ref": "./catalog-functions-core.json#/functions/required"
},
{
"$ref": "./catalog-functions-core.json#/functions/regex"
},
{
"$ref": "./catalog-functions-core.json#/functions/length"
},
{
"$ref": "./catalog-functions-core.json#/functions/numeric"
},
{
"$ref": "./catalog-functions-core.json#/functions/email"
},
{
"$ref": "./catalog-functions-core.json#/functions/formatString"
},
{
"$ref": "./catalog-functions-core.json#/functions/formatNumber"
},
{
"$ref": "./catalog-functions-core.json#/functions/formatCurrency"
},
{
"$ref": "./catalog-functions-core.json#/functions/formatDate"
},
{
"$ref": "./catalog-functions-core.json#/functions/pluralize"
},
{
"$ref": "./catalog-functions-core.json#/functions/openUrl"
},
{
"$ref": "./catalog-functions-core.json#/functions/and"
},
{
"$ref": "./catalog-functions-core.json#/functions/or"
},
{
"$ref": "./catalog-functions-core.json#/functions/not"
}
]
}
}
}
@@ -0,0 +1,623 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-functions-expressions.json",
"title": "DingTalk AICard Protocol V0.8 DingTalk expressions and operators",
"description": "0 components and 18 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {},
"functions": {
"add": {
"type": "object",
"description": "DingTalk extension function. Represents adding two numbers together.",
"returnType": "number",
"properties": {
"call": {
"const": "add"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"sub": {
"type": "object",
"description": "DingTalk extension function. Represents subtracting two numbers.",
"returnType": "number",
"properties": {
"call": {
"const": "sub"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"mul": {
"type": "object",
"description": "DingTalk extension function. Multiplies two numbers.",
"returnType": "number",
"properties": {
"call": {
"const": "mul"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"div": {
"type": "object",
"description": "DingTalk extension function. Divides one number by another.",
"returnType": "number",
"properties": {
"call": {
"const": "div"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"mod": {
"type": "object",
"description": "DingTalk extension function. Computes the remainder of a numeric division.",
"returnType": "number",
"properties": {
"call": {
"const": "mod"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"eq": {
"type": "object",
"description": "Tests whether two values are equal. Supports comparison with `null`; use `eq(arrayFind(...), null)` to detect no match.",
"returnType": "boolean",
"properties": {
"call": {
"const": "eq"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"ne": {
"type": "object",
"description": "Tests whether two values are unequal. Supports comparison with `null`; use `ne(arrayFind(...), null)` to detect a match.",
"returnType": "boolean",
"properties": {
"call": {
"const": "ne"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"lt": {
"type": "object",
"description": "DingTalk extension function. Determines whether number A is less than number B.",
"returnType": "boolean",
"properties": {
"call": {
"const": "lt"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"lte": {
"type": "object",
"description": "DingTalk extension function. Determines whether number A is less than or equal to number B.",
"returnType": "boolean",
"properties": {
"call": {
"const": "lte"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"gt": {
"type": "object",
"description": "DingTalk extension function. Determines whether number A is greater than number B.",
"returnType": "boolean",
"properties": {
"call": {
"const": "gt"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"gte": {
"type": "object",
"description": "DingTalk extension function. Determines whether number A is greater than or equal to number B.",
"returnType": "boolean",
"properties": {
"call": {
"const": "gte"
},
"args": {
"type": "object",
"properties": {
"a": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
},
"b": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"cond": {
"type": "object",
"description": "DingTalk extension function. Conditional operator (ternary operator).",
"returnType": "any",
"properties": {
"call": {
"const": "cond"
},
"args": {
"type": "object",
"properties": {
"if": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
},
"then": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"else": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"else",
"if",
"then"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"toDouble": {
"type": "object",
"description": "DingTalk extension function. A function used to convert numbers into floating-point numbers.",
"returnType": "number",
"properties": {
"call": {
"const": "toDouble"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"toLong": {
"type": "object",
"description": "DingTalk extension function. A function used to convert numbers into integers.",
"returnType": "number",
"properties": {
"call": {
"const": "toLong"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"objectGet": {
"type": "object",
"description": "Reads an object property by `key`. `value` may be a plain object literal, data binding, or value-function result. A missing property returns `null`.",
"returnType": "any",
"properties": {
"call": {
"const": "objectGet"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"key": {
"$ref": "./common-types-basic.json#/$defs/DynamicString"
}
},
"required": [
"key",
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"arrayGet": {
"type": "object",
"description": "DingTalk extension function. Retrieves an item from an array.",
"returnType": "any",
"properties": {
"call": {
"const": "arrayGet"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"index": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber"
}
},
"required": [
"index",
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"arrayFind": {
"type": "object",
"description": "DingTalk extension function. Finds a matching item in an array.",
"returnType": "any",
"properties": {
"call": {
"const": "arrayFind"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
"target": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"target",
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"getLength": {
"type": "object",
"description": "DingTalk extension function. Returns the length of an array.",
"returnType": "number",
"properties": {
"call": {
"const": "getLength"
},
"args": {
"type": "object",
"properties": {
"value": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"required": [
"value"
],
"additionalProperties": false
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
}
},
"$defs": {
"anyComponent": false,
"anyFunction": {
"oneOf": [
{
"$ref": "./catalog-functions-expressions.json#/functions/add"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/sub"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/mul"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/div"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/mod"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/eq"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/ne"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/lt"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/lte"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/gt"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/gte"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/cond"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/toDouble"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/toLong"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/objectGet"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/arrayGet"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/arrayFind"
},
{
"$ref": "./catalog-functions-expressions.json#/functions/getLength"
}
]
}
}
}
@@ -0,0 +1,607 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/catalog-functions-host-actions.json",
"title": "DingTalk AICard Protocol V0.8 DingTalk host actions",
"description": "0 components and 7 functions. A reading shard of the DingTalk public catalog, not a separate runtime catalog.",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"protocolVersion": "1.0",
"instructions": "Read entries needed for the current task, then follow their $ref dependencies. Common types: ./common-types-basic.json, ./common-types-extended.json and ./common-types-visual.json. Do not load all shards into the model context by default.\nCommon components: ./catalog-components-common.json.\nComposition and repeated layouts: ./catalog-components-composition.json.\nInputs and pickers: ./catalog-components-inputs.json.\nData display and media: ./catalog-components-specialized.json.\nCore functions adapted by DingTalk from A2UI: ./catalog-functions-core.json.\nDingTalk expressions and operators: ./catalog-functions-expressions.json.\nDingTalk host actions: ./catalog-functions-host-actions.json.\nSelect examples from ./examples/README.md; message structures are in ./agent-to-renderer.json.\nThis is the public DingTalk AICard Protocol V0.8 package. Use only the components, fields, functions, and common-type public values listed in the files loaded and assembled for the current scope.\nfallbackMarkdown is an optional DingTalk extension for component-level fallback content. It is used only when the current renderer cannot render the component. Unknown fields and field-validation errors do not trigger it. When omitted, the renderer displays the standard upgrade message.\nSchema references are local file references and require no HTTP access. catalogId is a runtime catalog identifier, not a file path.\nThe #/$defs/anyComponent union describes catalog-specific fields only. Validate complete component instances through the message envelope or loadSchemas().validateComponent, which combines ComponentCommon with this union and rejects unknown fields. The catalog root is not a validation entry point for complete messages or component instances. Complete messages must also conform to the main protocol; dynamic result types, component-tree references, and platform capabilities are validated at runtime.\nExpressions use {\"call\":…, \"args\":…} without an additional profile declaration. A host action must explicitly set functionCall.catalogId to urn:dingtalk:a2ui:host:v1. A public catalog HTTP identifier from a surface or component cannot replace the host Function catalog. For promptText, initialValue is the prefilled-text parameter and text is a result field, not an input parameter.\nAttach form-validation checks to the Button that submits the form. Do not rely on standard event-response fields for automatic write-back.\nUserPicker, ConversationPicker, and ImageUpload require host authorization. Server-side callbacks must still enforce business authorization.",
"components": {},
"functions": {
"copyText": {
"type": "object",
"description": "Copies text.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {},\n \"required\": [],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "void",
"properties": {
"call": {
"const": "copyText"
},
"args": {
"additionalProperties": false,
"properties": {
"text": {
"anyOf": [
{
"maxLength": 65536,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"text"
],
"type": "object",
"anyOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
{
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
}
]
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"openChat": {
"type": "object",
"description": "Opens a conversation.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {},\n \"required\": [],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "void",
"properties": {
"call": {
"const": "openChat"
},
"args": {
"additionalProperties": false,
"properties": {
"openConversationId": {
"anyOf": [
{
"maxLength": 4096,
"minLength": 1,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"openConversationId"
],
"type": "object",
"anyOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
{
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
}
]
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"previewImages": {
"type": "object",
"description": "Previews 1–100 images. If `current` is provided, it must identify an item in the predetermined `urls` array.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {},\n \"required\": [],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "void",
"properties": {
"call": {
"const": "previewImages"
},
"args": {
"additionalProperties": false,
"properties": {
"current": {
"anyOf": [
{
"maxLength": 4096,
"minLength": 1,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"urls": {
"anyOf": [
{
"items": {
"anyOf": [
{
"maxLength": 4096,
"minLength": 1,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"maxItems": 100,
"minItems": 1,
"type": "array"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"urls"
],
"type": "object",
"anyOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
{
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
}
],
"allOf": [
{
"if": {
"required": [
"current"
]
},
"then": {
"properties": {
"current": {
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
"urls": {
"anyOf": [
{
"type": "array"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"allOf": [
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
},
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
}
]
}
]
}
}
}
}
]
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"previewVideo": {
"type": "object",
"description": "Previews a video.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {},\n \"required\": [],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "void",
"properties": {
"call": {
"const": "previewVideo"
},
"args": {
"additionalProperties": false,
"properties": {
"posterUrl": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"url": {
"anyOf": [
{
"maxLength": 4096,
"minLength": 1,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"url"
],
"type": "object",
"anyOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
{
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
}
]
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"promptText": {
"type": "object",
"description": "Opens a single-line input panel. Use `args.initialValue` for prefilled text; `text` is the result field returned after confirmation and cannot be used as an input parameter. Static bindings and string functions evaluated in advance are allowed; client-side expressions and `maxLength` are not supported. Canceling does not write back a value. See ./examples/host-action.json for a complete message example.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"text\": {\n \"description\": \"Text entered by the user and returned after confirmation. This is a result field, not the input parameter for prefilled text.\",\n \"maxLength\": 65536,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"text\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "object",
"properties": {
"call": {
"const": "promptText"
},
"args": {
"additionalProperties": false,
"properties": {
"initialValue": {
"anyOf": [
{
"maxLength": 65536,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
],
"description": "Text prefilled when the input panel opens. May be a string, data binding, or value function pre-evaluated to a string. If omitted, the input is initially empty."
},
"message": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
],
"description": "Prompt shown by the desktop input panel. It is not inserted into the input value, and mobile clients currently do not display it."
},
"title": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
],
"description": "The title of the input panel."
}
},
"required": [],
"type": "object",
"allOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
}
]
}
},
"required": [
"call"
],
"allowedCallers": "rendererOnly"
},
"showConfirm": {
"type": "object",
"description": "Shows a confirmation dialog. The call is rejected when `message` is empty and `title` is omitted. Canceling returns `confirmed=false`.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"confirmed\": {\n \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"confirmed\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "object",
"properties": {
"call": {
"const": "showConfirm"
},
"args": {
"additionalProperties": false,
"properties": {
"cancelText": {
"anyOf": [
{
"maxLength": 128,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"confirmText": {
"anyOf": [
{
"maxLength": 128,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"message": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"title": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"message"
],
"type": "object",
"anyOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
{
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
}
],
"allOf": [
{
"if": {
"properties": {
"message": {
"const": ""
}
},
"required": [
"message"
]
},
"then": {
"required": [
"title"
]
}
}
]
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
},
"showModal": {
"type": "object",
"description": "Shows a dialog with custom buttons. Currently 1–2 buttons are supported, and parameters must be evaluated in advance. The cancel button added by desktop clients returns an empty `selectedId`.\n\nResult schema:\n```json\n{\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"success\"\n },\n \"data\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"selectedId\": {\n \"maxLength\": 4096,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"selectedId\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"status\",\n \"data\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"const\": \"failed\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n}\n```",
"returnType": "object",
"properties": {
"call": {
"const": "showModal"
},
"args": {
"additionalProperties": false,
"properties": {
"buttons": {
"anyOf": [
{
"items": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"id": {
"anyOf": [
{
"maxLength": 4096,
"minLength": 1,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"title": {
"anyOf": [
{
"maxLength": 256,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"id",
"title"
],
"type": "object"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"maxItems": 2,
"minItems": 1,
"type": "array"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"message": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"title": {
"anyOf": [
{
"maxLength": 4096,
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
}
},
"required": [
"message",
"buttons"
],
"type": "object",
"allOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
}
]
}
},
"required": [
"call",
"args"
],
"allowedCallers": "rendererOnly"
}
},
"$defs": {
"anyComponent": false,
"anyFunction": {
"oneOf": [
{
"$ref": "./catalog-functions-host-actions.json#/functions/copyText"
},
{
"$ref": "./catalog-functions-host-actions.json#/functions/openChat"
},
{
"$ref": "./catalog-functions-host-actions.json#/functions/previewImages"
},
{
"$ref": "./catalog-functions-host-actions.json#/functions/previewVideo"
},
{
"$ref": "./catalog-functions-host-actions.json#/functions/promptText"
},
{
"$ref": "./catalog-functions-host-actions.json#/functions/showConfirm"
},
{
"$ref": "./catalog-functions-host-actions.json#/functions/showModal"
}
]
}
}
}
@@ -0,0 +1,537 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/common-types-basic.json",
"title": "DingTalk AICard Protocol V0.8 Basic Common Types",
"description": "Basic common types based on the current DingTalk definitions; sharing a name with an official type does not imply equivalence. See ./common-types-extended.json for action and expression extensions, and ./common-types-visual.json for colors, font sizes and icons. Basic components may depend on these types as well. The DingTalk AICard Protocol V0.8 is split into shards for selective loading. Consumers must assemble and validate the files required by the selected scope. Modified by DingTalk from A2UI v1.0 common_types.json; selected definitions and descriptions are retained. See NOTICE for provenance and changes.",
"$defs": {
"ComponentId": {
"type": "string",
"description": "The unique identifier for a component, used for both definitions and references within the same surface."
},
"AccessibilityAttributes": {
"type": "object",
"description": "Provides a name and detailed description for assistive technologies such as screen readers. ⚠️ Reserved by the protocol and currently ineffective: the field itself is valid and never causes a validation failure or a component fallback, but screen readers cannot reach the content configured here.",
"properties": {
"label": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "A short string, typically 1 to 3 words, used by assistive technologies to convey the purpose or intent of an element. For example, an input field might have an accessible label of 'User ID' or a button might be labeled 'Submit'."
},
"description": {
"$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Additional information provided by assistive technologies about an element such as instructions, format requirements, or result of an action. For example, a mute button might have a label of 'Mute' and a description of 'Silences notifications about this conversation'."
}
}
},
"ComponentCommon": {
"type": "object",
"properties": {
"id": {
"$ref": "./common-types-basic.json#/$defs/ComponentId"
},
"catalogId": {
"type": "string",
"description": "Component-level `catalogId` that overrides the surface default catalog."
},
"accessibility": {
"$ref": "./common-types-basic.json#/$defs/AccessibilityAttributes"
},
"visible": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean",
"description": "Visibility control (boolean expression or literal)."
},
"metadata": {}
},
"required": [
"id"
],
"allOf": [
{
"$ref": "./common-types-extended.json#/$defs/HostActionOwner"
}
]
},
"Child": {
"$ref": "./common-types-basic.json#/$defs/ComponentId",
"description": "A reference to a single child component ID."
},
"ChildList": {
"oneOf": [
{
"type": "array",
"items": {
"$ref": "./common-types-basic.json#/$defs/ComponentId"
},
"description": "A static list of child component IDs."
},
{
"type": "object",
"description": "A template for generating a dynamic list of children from a data model list. The `componentId` is the component to use as a template.",
"properties": {
"componentId": {
"$ref": "./common-types-basic.json#/$defs/ComponentId"
},
"path": {
"type": "string",
"description": "The path to the list of component property objects in the data model."
}
},
"required": [
"componentId",
"path"
],
"additionalProperties": false
}
]
},
"DataBinding": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Data model path; relative paths can be used within the template, and ~ can only be written as ~0 or ~1. Evaluation context, path normalization, and target existence are checked at runtime.",
"pattern": "^(?:[^~]|~[01])*$"
}
},
"required": [
"path"
],
"additionalProperties": false
},
"DynamicValue": {
"description": "General value: a string, number, boolean, array, `null`, plain object, data binding, or value-function result. An object containing `call` is parsed as a function call; an object whose only key is `path` is parsed as a binding.",
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array"
},
{
"type": "null"
},
{
"$ref": "./common-types-extended.json#/$defs/LiteralObject"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"DynamicString": {
"description": "Represents a value that can be either a string literal, a path to a string in the data model, or a function call returning a string.",
"oneOf": [
{
"type": "string"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"DynamicNumber": {
"description": "Numeric dynamic value: It can be a JSON number literal, a DataBinding pointing to a numeric value, or a FunctionCall that returns a numeric value. The runtime results of bindings and functions must also be JSON numbers; no implicit conversion from string to number is performed, and numeric strings and ISO 8601 date-time strings are both invalid.",
"oneOf": [
{
"type": "number"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"DynamicBoolean": {
"description": "A boolean value that can be a literal, a path, or a function call returning a boolean.",
"oneOf": [
{
"type": "boolean"
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"DynamicStringList": {
"description": "Represents a value that can be either a literal array of strings, a path to a string array in the data model, or a function call returning a string array.",
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"$ref": "./common-types-basic.json#/$defs/DataBinding"
},
{
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
]
},
"FunctionCommon": {
"type": "object",
"properties": {
"catalogId": {
"type": "string",
"description": "Function catalog identifier. If omitted, the default catalog of the containing surface is used. This is not a Schema file path."
}
},
"allOf": [
{
"if": {
"properties": {
"call": {
"const": "copyText"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
},
{
"if": {
"properties": {
"call": {
"const": "openChat"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
},
{
"if": {
"properties": {
"call": {
"const": "previewImages"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
},
{
"if": {
"properties": {
"call": {
"const": "previewVideo"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
},
{
"if": {
"properties": {
"call": {
"const": "promptText"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
},
{
"if": {
"properties": {
"call": {
"const": "showConfirm"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
},
{
"if": {
"properties": {
"call": {
"const": "showModal"
}
},
"required": [
"call"
]
},
"then": {
"properties": {
"catalogId": {
"const": "urn:dingtalk:a2ui:host:v1",
"description": "Required. Explicitly specifies the DingTalk host Function catalog. The public catalog HTTP identifier of the containing surface or component cannot replace this value."
}
},
"required": [
"catalogId"
]
}
}
]
},
"FunctionCall": {
"type": "object",
"description": "A2UI v1.0 FunctionCall: Call the renderer function registered in the current surface catalog.",
"properties": {
"call": {
"type": "string",
"description": "The name of the function to call."
},
"catalogId": {
"type": "string",
"description": "The catalog ID for this function, overriding any surface-level default catalogId."
},
"args": {
"type": "object",
"description": "Arguments passed to the function.",
"additionalProperties": {
"anyOf": [
{
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
},
{
"type": "object",
"description": "A literal object argument (e.g. configuration)."
}
]
}
}
},
"required": [
"call"
],
"unevaluatedProperties": false,
"oneOf": [
{
"$ref": "./catalog-functions-core.json#/$defs/anyFunction"
},
{
"$ref": "./catalog-functions-expressions.json#/$defs/anyFunction"
},
{
"$ref": "./catalog-functions-host-actions.json#/$defs/anyFunction"
},
{
"$ref": "./common-types-basic.json#/$defs/IndexSystemFunction"
}
],
"allOf": [
{
"$ref": "./common-types-basic.json#/$defs/FunctionCommon"
}
]
},
"CheckRule": {
"type": "object",
"description": "A single validation rule applied to an input component.",
"properties": {
"condition": {
"$ref": "./common-types-basic.json#/$defs/DynamicBoolean"
},
"message": {
"type": "string",
"description": "The error message to display if the check fails."
}
},
"required": [
"condition",
"message"
],
"additionalProperties": false
},
"Action": {
"description": "Defines an interaction handler that can either trigger an agent-side event or execute a local renderer-side function.",
"oneOf": [
{
"type": "object",
"description": "Triggers an agent-side event.",
"properties": {
"event": {
"type": "object",
"description": "The event to dispatch to the agent.",
"properties": {
"name": {
"type": "string",
"description": "The name of the action to be dispatched to the agent."
},
"context": {
"type": "object",
"description": "A JSON object containing the key-value pairs for the action context. Values can be literals or paths. Use literal values unless the value must be dynamically bound to the data model. Do NOT use paths for static IDs.",
"additionalProperties": {
"$ref": "./common-types-basic.json#/$defs/DynamicValue"
}
},
"wantResponse": {
"type": "boolean",
"description": "Response-request metadata. The current public integration does not guarantee the standard response loop; do not rely on `true` to generate a response automatically.",
"default": false
},
"responsePath": {
"type": "string",
"description": "Standard event-response-path metadata. The current public integration does not guarantee automatic write-back. Host-function results use `metadata.extensions.dt_actionBindingsV1`; business updates use `updateDataModel` or `updateComponents`."
}
},
"required": [
"name"
],
"additionalProperties": false
}
},
"required": [
"event"
],
"additionalProperties": false
},
{
"type": "object",
"description": "Executes a local renderer-side function.",
"properties": {
"functionCall": {
"$ref": "./common-types-basic.json#/$defs/FunctionCall"
}
},
"required": [
"functionCall"
],
"additionalProperties": false
}
]
},
"Surface": {
"title": "Surface Container Component",
"description": "Reserved message layer type, not connected to the package component validation entry; does not indicate that new component or theme capabilities have been opened. The reserved canonical container component representing an A2UI surface. The Surface component is immutable and always has 'child': 'root'.",
"type": "object",
"allowedParents": [],
"properties": {
"component": {
"const": "Surface"
},
"child": {
"const": "root"
}
},
"additionalProperties": false
},
"IndexSystemFunction": {
"type": "object",
"description": "Returns the 0-based index of the current item when rendering a dynamic list from a template. This function MUST ONLY be available when evaluating template items within a list context.",
"returnType": "number",
"properties": {
"call": {
"const": "@index"
},
"args": {
"type": "object",
"properties": {
"offset": {
"$ref": "./common-types-basic.json#/$defs/DynamicNumber",
"description": "Optional. An offset to add to the 0-based index (e.g., 1 for 1-based indexing). Defaults to 0.",
"default": 0
}
},
"unevaluatedProperties": false
}
},
"required": [
"call"
],
"unevaluatedProperties": false
},
"Extensions": {
"type": "object",
"description": "Optional extension metadata. Keys MUST be Unicode identifiers (UAX #31). Keys starting with 'a2ui_' are reserved for official extensions.",
"patternProperties": {
"^[\\p{XID_Start}_][\\p{XID_Continue}]*$": {}
},
"additionalProperties": false
}
}
}
@@ -0,0 +1,458 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/common-types-extended.json",
"title": "DingTalk AICard Protocol V0.8 Extended Common Types",
"description": "DingTalk extended common types for dynamic objects, host arguments and write-back constraints. Result schemas are documented with each host function. See ./common-types-basic.json for basic types and ./common-types-visual.json for colors, font sizes and icons. Read the referenced definitions when basic components use extended types. The DingTalk AICard Protocol V0.8 is split into shards for selective loading. Consumers must assemble and validate the files required by the selected scope.",
"$defs": {
"LiteralObject": {
"type": "object",
"description": "Plain object literal. Objects containing `call` are reserved for function calls, while objects whose only key is `path` are reserved for bindings, preventing ambiguous matches with dynamic expressions.",
"not": {
"anyOf": [
{
"required": [
"call"
]
},
{
"required": [
"path"
],
"maxProperties": 1
}
]
}
},
"HostStaticValueTree": {
"description": "Constraints on the evaluation phase of the host parameter subtree; the actual value type, path, and expression legality are still checked by the existing runtime.",
"additionalProperties": {
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
},
"items": {
"$ref": "./common-types-extended.json#/$defs/HostStaticValueTree"
}
},
"HostCompiledValueTree": {
"description": "Constraints on the evaluation phase of the host parameter subtree; the actual value type, path, and expression legality are still checked by the existing runtime.",
"additionalProperties": {
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
},
"items": {
"$ref": "./common-types-extended.json#/$defs/HostCompiledValueTree"
},
"not": {
"type": "object",
"properties": {
"call": {
"not": {
"enum": [
"eq",
"ne",
"lt",
"lte",
"gt",
"gte",
"add",
"sub",
"mul",
"div",
"mod",
"cond",
"and",
"or",
"not",
"objectGet",
"arrayGet",
"arrayFind",
"getLength",
"toDouble",
"toLong"
]
}
}
},
"required": [
"call"
]
}
},
"HostResultPath": {
"type": "string",
"maxLength": 256,
"pattern": "^/ui/(?=[^/]*[A-Za-z_-])[A-Za-z0-9_-]+(?:/(?=[^/]*[A-Za-z_-])[A-Za-z0-9_-]+){0,14}$",
"description": "Result write-back path. Only object keys under `/ui/` are allowed, with a maximum of 256 ASCII characters and 16 segments including `ui`. Empty segments, all-numeric segments, and escape sequences are prohibited."
},
"HostActionBinding": {
"type": "object",
"properties": {
"resultPath": {
"$ref": "./common-types-extended.json#/$defs/HostResultPath"
}
},
"required": [
"resultPath"
],
"additionalProperties": false
},
"HostActionOwner": {
"type": "object",
"allOf": [
{
"if": {
"properties": {
"action": {
"type": "object",
"properties": {
"functionCall": {
"type": "object",
"properties": {
"call": {
"enum": [
"copyText",
"openChat",
"previewImages",
"previewVideo",
"promptText",
"showConfirm",
"showModal"
]
}
},
"required": [
"call"
]
}
},
"required": [
"functionCall"
]
}
},
"required": [
"action"
]
},
"then": {
"properties": {
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"type": "object",
"properties": {
"extensions": {
"type": "object",
"patternProperties": {
"^dt_actionBindings(?!V1$)": false
},
"properties": {
"dt_actionBindingsV1": {
"type": "object",
"properties": {
"action": {
"$ref": "./common-types-extended.json#/$defs/HostActionBinding"
}
},
"required": [
"action"
]
}
}
}
}
}
]
}
}
}
},
{
"if": {
"properties": {
"onComplete": {
"type": "object",
"properties": {
"functionCall": {
"type": "object",
"properties": {
"call": {
"enum": [
"copyText",
"openChat",
"previewImages",
"previewVideo",
"promptText",
"showConfirm",
"showModal"
]
}
},
"required": [
"call"
]
}
},
"required": [
"functionCall"
]
}
},
"required": [
"onComplete"
]
},
"then": {
"properties": {
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"type": "object",
"properties": {
"extensions": {
"type": "object",
"patternProperties": {
"^dt_actionBindings(?!V1$)": false
},
"properties": {
"dt_actionBindingsV1": {
"type": "object",
"properties": {
"onComplete": {
"$ref": "./common-types-extended.json#/$defs/HostActionBinding"
}
},
"required": [
"onComplete"
]
}
}
}
}
}
]
}
}
}
},
{
"if": {
"properties": {
"onLinkClick": {
"type": "object",
"properties": {
"functionCall": {
"type": "object",
"properties": {
"call": {
"enum": [
"copyText",
"openChat",
"previewImages",
"previewVideo",
"promptText",
"showConfirm",
"showModal"
]
}
},
"required": [
"call"
]
}
},
"required": [
"functionCall"
]
}
},
"required": [
"onLinkClick"
]
},
"then": {
"properties": {
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"type": "object",
"properties": {
"extensions": {
"type": "object",
"patternProperties": {
"^dt_actionBindings(?!V1$)": false
},
"properties": {
"dt_actionBindingsV1": {
"type": "object",
"properties": {
"onLinkClick": {
"$ref": "./common-types-extended.json#/$defs/HostActionBinding"
}
},
"required": [
"onLinkClick"
]
}
}
}
}
}
]
}
}
}
},
{
"if": {
"properties": {
"onPreview": {
"type": "object",
"properties": {
"functionCall": {
"type": "object",
"properties": {
"call": {
"enum": [
"copyText",
"openChat",
"previewImages",
"previewVideo",
"promptText",
"showConfirm",
"showModal"
]
}
},
"required": [
"call"
]
}
},
"required": [
"functionCall"
]
}
},
"required": [
"onPreview"
]
},
"then": {
"properties": {
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"type": "object",
"properties": {
"extensions": {
"type": "object",
"patternProperties": {
"^dt_actionBindings(?!V1$)": false
},
"properties": {
"dt_actionBindingsV1": {
"type": "object",
"properties": {
"onPreview": {
"$ref": "./common-types-extended.json#/$defs/HostActionBinding"
}
},
"required": [
"onPreview"
]
}
}
}
}
}
]
}
}
}
},
{
"if": {
"properties": {
"onTap": {
"type": "object",
"properties": {
"functionCall": {
"type": "object",
"properties": {
"call": {
"enum": [
"copyText",
"openChat",
"previewImages",
"previewVideo",
"promptText",
"showConfirm",
"showModal"
]
}
},
"required": [
"call"
]
}
},
"required": [
"functionCall"
]
}
},
"required": [
"onTap"
]
},
"then": {
"properties": {
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"type": "object",
"properties": {
"extensions": {
"type": "object",
"patternProperties": {
"^dt_actionBindings(?!V1$)": false
},
"properties": {
"dt_actionBindingsV1": {
"type": "object",
"properties": {
"onTap": {
"$ref": "./common-types-extended.json#/$defs/HostActionBinding"
}
},
"required": [
"onTap"
]
}
}
}
}
}
]
}
}
}
}
]
}
}
}
@@ -0,0 +1,475 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dingtalk.com/card/a2ui/common-types-visual.json",
"title": "DingTalk AICard Protocol V0.8 Visual Common Types",
"description": "DingTalk visual common types defining ColorToken, SizeToken and IconName. Read entries for the color, font-size and icon fields actually used by the components; this grouping introduces no new style fields or runtime capabilities. The DingTalk AICard Protocol V0.8 is split into shards for selective loading. Consumers must assemble and validate the files required by the selected scope.",
"$defs": {
"SizeToken": {
"type": "string",
"description": "DDesign native font size Token ID, used for Text.sizeToken. Prefer selecting IDs with the __font_size suffix according to specific usage; the actual font size is parsed by the current host, and consistent cross-platform font size or line height is not guaranteed, nor will a UIFont style with the same name be automatically applied. Design names like font-size/body, style names without suffixes, and __line_height keys are not font size IDs here.",
"anyOf": [
{
"const": "common_largetitle_text_style__font_size",
"description": "Main title, used as the heading for a page or major content section."
},
{
"const": "common_supertitle_text_style__font_size",
"description": "Extra-large title, used as the main title for a page or module."
},
{
"const": "common_h1_text_style__font_size",
"description": "Level 1 heading, used for card or content area titles."
},
{
"const": "common_h2_text_style__font_size",
"description": "Level 2 heading, used for content-group headings."
},
{
"const": "common_h3_text_style__font_size",
"description": "Level 3 heading, used for smaller-level content titles."
},
{
"const": "common_h4_text_style__font_size",
"description": "Level 4 heading, used for subheadings or supplementary information under a heading."
},
{
"const": "common_body_text_style__font_size",
"description": "Main text, used for the primary content."
},
{
"const": "common_action_text_style__font_size",
"description": "Action text, used for buttons, links, and other operational copy."
},
{
"const": "common_description_text_style__font_size",
"description": "Auxiliary explanation, used for secondary information and supplementary notes."
},
{
"const": "common_footnote_text_style__font_size",
"description": "Footnotes, used for sources, notes, image captions, or status tags."
},
{
"const": "common_tiny_text_style__font_size",
"description": "Small text prompts, used as subtle hints in space-constrained scenarios."
},
{
"const": "common_subhead_text_style__font_size",
"description": "Subtitle, used for supplementary information below the title."
},
{
"type": "string",
"description": "Retain existing string inputs. The above items are selection descriptions for known native Tokens, not a closed enumeration. Other Tokens must be confirmed registered by the host. Schema accepting a string does not mean the Token is available; if the host cannot find it, it will fall back to the variant slot."
}
],
"examples": [
"common_largetitle_text_style__font_size",
"common_supertitle_text_style__font_size",
"common_h1_text_style__font_size",
"common_h2_text_style__font_size",
"common_h3_text_style__font_size",
"common_h4_text_style__font_size",
"common_body_text_style__font_size",
"common_action_text_style__font_size",
"common_description_text_style__font_size",
"common_footnote_text_style__font_size",
"common_tiny_text_style__font_size",
"common_subhead_text_style__font_size"
]
},
"ColorToken": {
"type": "string",
"description": "Only the 57 Token IDs listed in the table below are allowed, with case-sensitive names. The description explains their purpose and state semantics; the actual color values and transparency are interpreted by the host according to the current platform and theme, and this protocol does not fix the color values.",
"enum": [
"extended_yellow0_color",
"extended_yellow1_color",
"extended_yellow6_color",
"extended_orange0_color",
"extended_orange1_color",
"extended_orange6_color",
"extended_red0_color",
"extended_red1_color",
"extended_red6_color",
"extended_magenta0_color",
"extended_magenta1_color",
"extended_magenta6_color",
"extended_purple0_color",
"extended_purple1_color",
"extended_purple6_color",
"extended_deeppurple0_color",
"extended_deeppurple1_color",
"extended_deeppurple6_color",
"extended_blue0_color",
"extended_blue1_color",
"extended_blue6_color",
"extended_darkgreen0_color",
"extended_darkgreen1_color",
"extended_darkgreen6_color",
"extended_green0_color",
"extended_green1_color",
"extended_green6_color",
"common_level1_base_color",
"common_level2_base_color",
"common_level3_base_color",
"common_level4_base_color",
"common_stamp_color",
"common_link_color",
"common_bg_color",
"common_fg_z1_color",
"common_line_light_color",
"common_line_hard_color",
"common_red1_color",
"common_orange1_color",
"common_green1_color",
"common_white1_color",
"common_white2_color",
"common_white3_color",
"common_white4_color",
"common_white5_color",
"common_white6_color",
"common_black1_color",
"common_black2_color",
"common_black3_color",
"common_black4_color",
"common_black5_color",
"common_black6_color",
"theme_primary3_color",
"theme_primary2_color",
"theme_primary_hover_color",
"theme_primary1_color",
"theme_primary_press_color"
],
"oneOf": [
{
"const": "extended_yellow0_color",
"description": "Yellow background color; used for background filling in areas of this color scheme."
},
{
"const": "extended_yellow1_color",
"description": "Yellow border color; used for outlining areas of this color scheme."
},
{
"const": "extended_yellow6_color",
"description": "Yellow text color; used for text emphasis in this color scheme."
},
{
"const": "extended_orange0_color",
"description": "Orange background color; used for area background filling in this color scheme."
},
{
"const": "extended_orange1_color",
"description": "Orange border color; used for outlining areas of this color scheme."
},
{
"const": "extended_orange6_color",
"description": "Orange text color; used for text emphasis in this color scheme."
},
{
"const": "extended_red0_color",
"description": "Red background color; used for background filling in areas of this color series."
},
{
"const": "extended_red1_color",
"description": "Red border color; used for the stroke of areas in this color scheme."
},
{
"const": "extended_red6_color",
"description": "Red text color; used for emphasizing text in this color scheme."
},
{
"const": "extended_magenta0_color",
"description": "Magenta background color; used for background filling in areas of this color scheme."
},
{
"const": "extended_magenta1_color",
"description": "Magenta border color; used for outlining areas of this color scheme."
},
{
"const": "extended_magenta6_color",
"description": "Magenta text color; used for text emphasis in this color scheme."
},
{
"const": "extended_purple0_color",
"description": "Purple background color; used for background filling in areas of this color scheme."
},
{
"const": "extended_purple1_color",
"description": "Purple border color; used for the stroke of areas in this color scheme."
},
{
"const": "extended_purple6_color",
"description": "Purple text color; used for emphasizing text in this color scheme."
},
{
"const": "extended_deeppurple0_color",
"description": "Deep purple background color; used for background filling in areas of this color scheme."
},
{
"const": "extended_deeppurple1_color",
"description": "Deep purple border color; used for outlining areas of this color scheme."
},
{
"const": "extended_deeppurple6_color",
"description": "Deep purple text color; used for text emphasis in this color scheme."
},
{
"const": "extended_blue0_color",
"description": "Blue background color; used for background filling in areas of this color scheme."
},
{
"const": "extended_blue1_color",
"description": "Blue border color; used for the stroke of areas in this color scheme."
},
{
"const": "extended_blue6_color",
"description": "Blue text color; used for emphasizing text in this color scheme."
},
{
"const": "extended_darkgreen0_color",
"description": "Dark green background color; used for area background filling in this color scheme."
},
{
"const": "extended_darkgreen1_color",
"description": "Dark green border color; used for outlining areas of this color scheme."
},
{
"const": "extended_darkgreen6_color",
"description": "Dark green text color; used for text emphasis in this color scheme."
},
{
"const": "extended_green0_color",
"description": "Green background color; used for background filling in areas of this color scheme."
},
{
"const": "extended_green1_color",
"description": "Green border color; used for outlining areas of this color scheme."
},
{
"const": "extended_green6_color",
"description": "Green text color; used for text emphasis in this color scheme."
},
{
"const": "common_level1_base_color",
"description": "Primary text and icons; used for titles, body text, and other main information."
},
{
"const": "common_level2_base_color",
"description": "Secondary text and icons; used for secondary information and auxiliary icons."
},
{
"const": "common_level3_base_color",
"description": "Tertiary text and icons; used for weaker supplementary information."
},
{
"const": "common_level4_base_color",
"description": "Text and icon color in a disabled state; only conveys visual state."
},
{
"const": "common_stamp_color",
"description": "Watermark color; used for low-interference watermark content."
},
{
"const": "common_link_color",
"description": "Hyperlink text and highlight border color."
},
{
"const": "common_bg_color",
"description": "Background color of the area; used for the background of a page or content area."
},
{
"const": "common_fg_z1_color",
"description": "Foreground area background color; used for content areas on top of the base background."
},
{
"const": "common_line_light_color",
"description": "Weak divider; used for lighter content separation."
},
{
"const": "common_line_hard_color",
"description": "Strong divider line; used for content separation where a clearer boundary is needed."
},
{
"const": "common_red1_color",
"description": "Danger state color; used to convey dangerous or erroneous semantics."
},
{
"const": "common_orange1_color",
"description": "Warning state color; used for prompts that require the user's attention."
},
{
"const": "common_green1_color",
"description": "Success state color; used for success or completion feedback."
},
{
"const": "common_white1_color",
"description": "White mask; used for white overlay, actual transparency is determined by the host theme resources."
},
{
"const": "common_white2_color",
"description": "White mask (designed as 40% level); used for white overlays, actual transparency is determined by the host theme resources."
},
{
"const": "common_white3_color",
"description": "White mask (designed as 30% level); used for white overlays, actual transparency is determined by the host theme resources."
},
{
"const": "common_white4_color",
"description": "White mask (designed as 20% level); used for white overlays, actual transparency is interpreted according to the host theme resources."
},
{
"const": "common_white5_color",
"description": "White mask (designed as 10% level); used for white overlay, actual transparency is interpreted according to the host theme resources."
},
{
"const": "common_white6_color",
"description": "White mask (designed as 5% level); used for white overlay, actual transparency is interpreted according to the host theme resources."
},
{
"const": "common_black1_color",
"description": "Black mask; used for black overlay, actual transparency is determined by the host theme resources."
},
{
"const": "common_black2_color",
"description": "Black mask (designed as 40% level); used for black overlays, actual opacity is determined by the host theme resources."
},
{
"const": "common_black3_color",
"description": "Black mask (designed as 30% level); used for black overlays, actual transparency is resolved according to the host theme resources."
},
{
"const": "common_black4_color",
"description": "Black mask (designed as 20% level); used for black overlays, actual transparency is interpreted according to the host theme resources."
},
{
"const": "common_black5_color",
"description": "Black mask (designed as 10% level); used for black overlays, actual opacity is interpreted according to the host theme resources."
},
{
"const": "common_black6_color",
"description": "Black mask (designed as 5% level); used for black overlays, actual transparency is determined by the host theme resources."
},
{
"const": "theme_primary3_color",
"description": "Theme background color; used for the background of areas that carry the theme style."
},
{
"const": "theme_primary2_color",
"description": "Light theme color; used for lighter theme color highlights and auxiliary decoration."
},
{
"const": "theme_primary_hover_color",
"description": "Theme hover color; used for the visual feedback of the theme color when the pointer hovers."
},
{
"const": "theme_primary1_color",
"description": "Theme primary color; used for main theme emphasis."
},
{
"const": "theme_primary_press_color",
"description": "Theme pressed color; used for the visual feedback of the theme color when pressed."
}
]
},
"IconName": {
"type": "string",
"description": "Common type `IconName`: 24 case-sensitive public font-icon names referenced by `Icon.name`. The descriptions below preserve each icon's intended visual meaning and usage from the design mock.",
"oneOf": [
{
"const": "Search_L_outlined",
"description": "Search. Used for searching, searching content, searching files, searching images."
},
{
"const": "Language_L_outlined",
"description": "Language-switch icon. Used for web search in the design mock."
},
{
"const": "CodeProgram_L_outlined",
"description": "Code. Used to run commands in a terminal."
},
{
"const": "Picture_L_outlined",
"description": "Image. Used to generate images."
},
{
"const": "Folder_L_outlined",
"description": "Folder. Used for reading files."
},
{
"const": "Edit_L_outlined",
"description": "Edit or pen icon. Used for writing and creating files."
},
{
"const": "ListView_L_outlined",
"description": "List View 1. Used to list files."
},
{
"const": "ManagementBackground_L_outlined",
"description": "Admin console icon. The design mock uses it for browser access."
},
{
"const": "Tool_L_outlined",
"description": "Tools. Used to call tools, Workspace toolset, MCP toolset, DWS toolset, MCP invocation, and general fallback."
},
{
"const": "Delete_L_outlined",
"description": "Delete. Used for file deletion."
},
{
"const": "SpinLoading_L_outlined",
"description": "Loading spinner icon. Used for Skill loading and other loading states; the icon name itself does not enable a rotation animation."
},
{
"const": "UploadOne_L_outlined",
"description": "Upload. Used for file upload."
},
{
"const": "DownloadAndSave_L_outlined",
"description": "Download and save. Used for file downloads."
},
{
"const": "Check_L_outlined",
"description": "Check mark. Used to indicate completion or success."
},
{
"const": "Copy_L_outlined",
"description": "Copy. Used to copy text, content, or results."
},
{
"const": "Close_L_outlined",
"description": "Close, cross. Used to close pop-ups, panels, or pages."
},
{
"const": "More_L_outlined",
"description": "More, ellipsis, three dots. Used to open more action menus."
},
{
"const": "Error_L_outlined",
"description": "Warning, error reminder, circle with exclamation mark. Used to indicate operation failure or abnormal status."
},
{
"const": "InformationThere_L_outlined",
"description": "Information prompt, circular letter i. Used to display instructions or additional information."
},
{
"const": "Setting_L_outlined",
"description": "Settings, gear. Used to open settings or adjust configurations."
},
{
"const": "ExpandThere_L_outlined",
"description": "Expand. Used to enlarge the content area or expand the panel."
},
{
"const": "Collapse_L_outlined",
"description": "Collapse. Used to minimize the content area or collapse the panel."
},
{
"const": "Link_L_outlined",
"description": "Link, chain. Used to insert a link or open a related address."
},
{
"const": "Share_L_outlined",
"description": "Share, forward. Used for sharing content, conversations, or results."
}
]
}
}
}
@@ -0,0 +1,15 @@
# Choose an example
Read only the matching JSON. Adapt its content and layout to the task.
| Need | JSON example | Guidance |
|---|---|---|
| Form bindings, upload events, and submit checks | [form-interaction.json](form-interaction.json) | [Form](../../patterns/form.md) |
| Host dialog and result writeback | [host-action.json](host-action.json) | [Form](../../patterns/form.md#example-interaction-notes) |
| Running state with generation effects and tool icons | [agent-run-progress.json](agent-run-progress.json) | [Progress](../../patterns/progress.md) |
| Execution updates through completion | [agent-run.json](agent-run.json) | [Progress](../../patterns/progress.md) |
| Tabs, table pagination, charts, Loop, and Stack | [data-report.json](data-report.json) | [Report](../../patterns/report.md) |
Execution replay requires the [batch instructions](../../patterns/progress.md#examples-and-replay); sending the whole array at once may hide intermediate states.
These arrays initialize new cards. For existing cards, send only intended changes, not initialization. See [Construct and validate](../../../SKILL.md#construct-and-validate) for creation and update scenarios.
@@ -0,0 +1,204 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "open-progress",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "open-progress",
"path": "/",
"value": {}
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-progress",
"components": [
{
"id": "root",
"component": "Column",
"children": [
"executionPanel"
],
"gap": 6
},
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Thinking...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"defaultExpanded": false,
"textEffect": "shimmer"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking"
],
"gap": 6
},
{
"id": "thinking",
"component": "Text",
"text": "Plan: read the sample sales data first, then total orders and revenue by region.",
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-progress",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Reading...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"textEffect": "shimmer"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking",
"toolGroup_read"
],
"gap": 6
},
{
"id": "toolGroup_read",
"component": "CollapsiblePanel",
"title": "Reading...",
"children": [
"toolGroup_read_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"defaultExpanded": false,
"icon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_light.DlX94S3.gif",
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
},
{
"id": "toolGroup_read_item",
"component": "CollapsiblePanel",
"title": "Read sample data",
"children": [
"toolGroup_read_detail"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Read sample data",
"defaultExpanded": false,
"icon": "Folder_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```"
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-progress",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Analyzing...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"textEffect": "shimmer"
},
{
"id": "toolGroup_read",
"component": "CollapsiblePanel",
"title": "Read complete",
"children": [
"toolGroup_read_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"icon": "Check_L_outlined"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking",
"toolGroup_read",
"toolGroup_summary"
],
"gap": 6
},
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```\n\n**Result** · 4 rows × 4 columns\n\n```csv\nregion,month,orders,revenue\nEast,2026-07,1280,356400\nWest,2026-07,964,271900\nNorth,2026-07,742,198300\nSouth,2026-07,1105,309800\n```"
},
{
"id": "toolGroup_summary",
"component": "CollapsiblePanel",
"title": "Analyzing...",
"children": [
"toolGroup_summary_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"defaultExpanded": false,
"icon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_light.DlX94S3.gif",
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
},
{
"id": "toolGroup_summary_item",
"component": "CollapsiblePanel",
"title": "Calculate summary",
"children": [
"toolGroup_summary_detail"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Calculate summary",
"defaultExpanded": false,
"icon": "CodeProgram_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_summary_detail",
"component": "Markdown",
"content": "```python\nimport pandas as pd\n\ndf = pd.read_csv(\"data/sales_sample.csv\")\nsummary = (\n df.groupby(\"region\")[[\"orders\", \"revenue\"]]\n .sum()\n .sort_values(\"revenue\", ascending=False)\n)\n```"
}
]
}
}
]
@@ -0,0 +1,271 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "open-streaming",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "open-streaming",
"path": "/",
"value": {}
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-streaming",
"components": [
{
"id": "root",
"component": "Column",
"children": [
"executionPanel"
],
"gap": 6
},
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Thinking...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"defaultExpanded": false,
"textEffect": "shimmer"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking"
],
"gap": 6
},
{
"id": "thinking",
"component": "Text",
"text": "Plan: read the sample sales data first, then total orders and revenue by region.",
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-streaming",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Reading...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"textEffect": "shimmer"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking",
"toolGroup_read"
],
"gap": 6
},
{
"id": "toolGroup_read",
"component": "CollapsiblePanel",
"title": "Reading...",
"children": [
"toolGroup_read_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"defaultExpanded": false,
"icon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_light.DlX94S3.gif",
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
},
{
"id": "toolGroup_read_item",
"component": "CollapsiblePanel",
"title": "Read sample data",
"children": [
"toolGroup_read_detail"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Read sample data",
"defaultExpanded": false,
"icon": "Folder_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```"
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-streaming",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Analyzing...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"textEffect": "shimmer"
},
{
"id": "toolGroup_read",
"component": "CollapsiblePanel",
"title": "Read complete",
"children": [
"toolGroup_read_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"icon": "Check_L_outlined"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking",
"toolGroup_read",
"toolGroup_summary"
],
"gap": 6
},
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```\n\n**Result** · 4 rows × 4 columns\n\n```csv\nregion,month,orders,revenue\nEast,2026-07,1280,356400\nWest,2026-07,964,271900\nNorth,2026-07,742,198300\nSouth,2026-07,1105,309800\n```"
},
{
"id": "toolGroup_summary",
"component": "CollapsiblePanel",
"title": "Analyzing...",
"children": [
"toolGroup_summary_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"defaultExpanded": false,
"icon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_light.DlX94S3.gif",
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
},
{
"id": "toolGroup_summary_item",
"component": "CollapsiblePanel",
"title": "Calculate summary",
"children": [
"toolGroup_summary_detail"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Calculate summary",
"defaultExpanded": false,
"icon": "CodeProgram_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_summary_detail",
"component": "Markdown",
"content": "```python\nimport pandas as pd\n\ndf = pd.read_csv(\"data/sales_sample.csv\")\nsummary = (\n df.groupby(\"region\")[[\"orders\", \"revenue\"]]\n .sum()\n .sort_values(\"revenue\", ascending=False)\n)\n```"
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-streaming",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "Completed",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details"
},
{
"id": "toolGroup_summary",
"component": "CollapsiblePanel",
"title": "Analysis complete",
"children": [
"toolGroup_summary_item"
],
"colorToken": "common_level2_base_color",
"variant": "indented",
"fallbackMarkdown": "Execution details",
"icon": "Check_L_outlined"
},
{
"id": "root",
"component": "Column",
"children": [
"executionPanel",
"result"
],
"gap": 6
},
{
"id": "result",
"component": "Column",
"children": [
"answer",
"artifact"
],
"gap": 6
},
{
"id": "answer",
"component": "Markdown",
"content": "**July 2026 sales summary**\n\nTotal revenue **1,136,400** from **4,091** orders across 4 regions. East leads with 356,400 (31.4%).\n\n| Region | Orders | Revenue | Share |\n|---|---:|---:|---:|\n| East | 1,280 | 356,400 | 31.4% |\n| South | 1,105 | 309,800 | 27.3% |\n| West | 964 | 271,900 | 23.9% |\n| North | 742 | 198,300 | 17.4% |"
},
{
"id": "artifact",
"component": "File",
"fileName": "dummy.pdf",
"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
"mimeType": "application/pdf",
"description": "Public sample PDF; not a generated report"
},
{
"id": "toolGroup_summary_detail",
"component": "Markdown",
"content": "```python\nimport pandas as pd\n\ndf = pd.read_csv(\"data/sales_sample.csv\")\nsummary = (\n df.groupby(\"region\")[[\"orders\", \"revenue\"]]\n .sum()\n .sort_values(\"revenue\", ascending=False)\n)\n```\n\n**Result** · 4 rows × 3 columns\n\n```csv\nregion,orders,revenue\nEast,1280,356400\nSouth,1105,309800\nWest,964,271900\nNorth,742,198300\n```"
}
]
}
}
]
@@ -0,0 +1,155 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "open-report",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "open-report",
"path": "/",
"value": {
"rows": [
{
"name": "Engineering",
"count": 8
},
{
"name": "Design",
"count": 3
},
{
"name": "Testing",
"count": 2
}
],
"table": {
"meta": [
{
"colKey": "name",
"colName": "Department",
"type": "text"
},
{
"colKey": "count",
"colName": "Completed",
"type": "text"
}
],
"data": [
{
"name": "Engineering",
"count": 8
},
{
"name": "Design",
"count": 3
},
{
"name": "Testing",
"count": 2
}
]
},
"chart": {
"type": "histogram",
"data": [
{
"x": "Engineering",
"y": 8
},
{
"x": "Design",
"y": 3
},
{
"x": "Testing",
"y": 2
}
]
}
}
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-report",
"components": [
{
"id": "root",
"component": "Column",
"children": [
"tabs",
"summary"
]
},
{
"id": "tabs",
"component": "Tabs",
"tabs": [
{
"title": "Data details",
"child": "table"
},
{
"title": "Chart",
"child": "chart-stack"
}
]
},
{
"id": "table",
"component": "Table",
"data": {
"path": "/table"
},
"pagination": true,
"pageSize": 2
},
{
"id": "chart-stack",
"component": "Stack",
"child": "chart",
"overlays": [
{
"child": "badge",
"position": "topEnd"
}
]
},
{
"id": "chart",
"component": "Chart",
"data": {
"path": "/chart"
}
},
{
"id": "badge",
"component": "Text",
"text": "This week"
},
{
"id": "summary",
"component": "Loop",
"children": {
"componentId": "row",
"path": "/rows"
}
},
{
"id": "row",
"component": "Text",
"text": {
"path": "name"
},
"maxLine": 1
}
]
}
}
]
@@ -0,0 +1,209 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "open-form",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "open-form",
"path": "/",
"value": {
"form": {
"name": "",
"dept": [],
"agreed": false,
"notify": false,
"count": 1,
"images": []
}
}
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-form",
"components": [
{
"id": "root",
"component": "Column",
"children": [
"name",
"dept-title",
"dept",
"agreed",
"notify-row",
"count-title",
"count",
"images-title",
"images",
"submit"
]
},
{
"id": "name",
"component": "TextField",
"label": "Name",
"value": {
"path": "/form/name"
}
},
{
"id": "dept-title",
"component": "Text",
"text": "Department"
},
{
"id": "dept",
"component": "ChoicePicker",
"label": "Choose a department",
"variant": "mutuallyExclusive",
"options": [
{
"label": "Engineering",
"value": "dev"
},
{
"label": "Design",
"value": "design"
}
],
"value": {
"path": "/form/dept"
}
},
{
"id": "agreed",
"component": "CheckBox",
"label": "Agree to submit",
"value": {
"path": "/form/agreed"
},
"action": {
"event": {
"name": "agreement_changed",
"context": {
"agreed": {
"path": "/form/agreed"
}
}
}
}
},
{
"id": "notify-row",
"component": "Row",
"align": "center",
"children": [
"notify-label",
"notify"
]
},
{
"id": "notify-label",
"component": "Text",
"text": "Receive progress updates",
"weight": 1
},
{
"id": "notify",
"component": "Switch",
"value": {
"path": "/form/notify"
},
"action": {
"event": {
"name": "notification_changed",
"context": {
"notify": {
"path": "/form/notify"
}
}
}
}
},
{
"id": "count-title",
"component": "Text",
"text": "Quantity"
},
{
"id": "count",
"component": "NumberInput",
"label": "Quantity",
"placeholder": "Enter a number",
"value": {
"path": "/form/count"
}
},
{
"id": "images-title",
"component": "Text",
"text": "Attachments"
},
{
"id": "images",
"component": "ImageUpload",
"label": "Upload images",
"value": {
"path": "/form/images"
},
"action": {
"event": {
"name": "image_uploaded",
"context": {
"images": {
"path": "/form/images"
}
}
}
}
},
{
"id": "submit",
"component": "Button",
"child": "submit-label",
"variant": "primary",
"checks": [
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/name"
}
}
},
"message": "Enter a name"
},
{
"condition": {
"path": "/form/agreed"
},
"message": "Agree before submitting"
}
],
"action": {
"event": {
"name": "submit",
"context": {
"form": {
"path": "/form"
}
}
}
}
},
{
"id": "submit-label",
"component": "Text",
"text": "Submit"
}
]
}
}
]
@@ -0,0 +1,166 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "open-host-action",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "open-host-action",
"path": "/",
"value": {
"preset": {
"text": "Weekly progress"
},
"ui": {}
}
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-host-action",
"components": [
{
"id": "root",
"component": "Column",
"children": [
"edit",
"hint",
"result",
"actions",
"title-label",
"title-result",
"note-label",
"note-result"
]
},
{
"id": "edit",
"component": "Button",
"child": "edit-label",
"action": {
"functionCall": {
"catalogId": "urn:dingtalk:a2ui:host:v1",
"call": "promptText",
"args": {
"initialValue": {
"path": "/preset/text"
}
}
}
},
"metadata": {
"extensions": {
"dt_actionBindingsV1": {
"action": {
"resultPath": "/ui/prompt"
}
}
}
}
},
{
"id": "edit-label",
"component": "Text",
"text": "Edit content"
},
{
"id": "hint",
"component": "Text",
"text": "After confirmation, the result appears below. Cancel keeps the previous result; a failed call clears it.",
"maxLine": 3
},
{
"id": "result",
"component": "Text",
"text": {
"path": "/ui/prompt/data/text"
},
"maxLine": 3
},
{
"id": "actions",
"component": "ButtonGroup",
"buttons": [
{
"label": "Edit title",
"action": {
"functionCall": {
"catalogId": "urn:dingtalk:a2ui:host:v1",
"call": "promptText",
"args": {
"initialValue": {
"path": "/preset/text"
}
}
}
},
"metadata": {
"extensions": {
"dt_actionBindingsV1": {
"action": {
"resultPath": "/ui/title"
}
}
}
}
},
{
"label": "Edit note",
"action": {
"functionCall": {
"catalogId": "urn:dingtalk:a2ui:host:v1",
"call": "promptText",
"args": {
"initialValue": {
"path": "/preset/text"
}
}
}
},
"metadata": {
"extensions": {
"dt_actionBindingsV1": {
"action": {
"resultPath": "/ui/note"
}
}
}
}
}
]
},
{
"id": "title-label",
"component": "Text",
"text": "Title result"
},
{
"id": "title-result",
"component": "Text",
"text": {
"path": "/ui/title/data/text"
},
"maxLine": 2
},
{
"id": "note-label",
"component": "Text",
"text": "Note result"
},
{
"id": "note-result",
"component": "Text",
"text": {
"path": "/ui/note/data/text"
},
"maxLine": 3
}
]
}
}
]
@@ -0,0 +1,32 @@
# A2UI templates
Localized scene labels and summaries are intentional discovery copy. These complete local public examples use fictional data, without Alibaba business content or source business identities. Preserve supplied real-task facts when deriving a card. Each scene has one complete JSON for mobile and desktop; read only the selected file.
## Reuse workflow
Choose by information structure, data types, collection behavior, and required interactions, not scene name alone. For changes confined to bound data, locate `"updateDataModel"` and read its data first. Copy the complete JSON to a working file, edit intended values with a JSON parser, keep message Surface IDs consistent, and validate the complete result. A data excerpt alone is not a new card. Inspect components when changing literal text, layout, resources, or actions.
For an existing instance, keep its IDs and state and send only authorized deltas; do not replay initialization or overwrite user input. Consult the selected pattern only for the behavior being changed.
Preserve the selected template's frame and insets as written. Some templates set root `padding` to `0` and inset an inner content container; others omit root `padding` and rely on host insets. Do not add, remove, or zero root `padding` when reusing a template. The ordinary new-card defaults in design guidance do not replace an existing template's intentional styling.
## Reuse boundaries
`Fixed` records the highest referenced array index plus one: these are indexed component slots, and extra values do not add items. `Repeated` identifies collections used by component children templates, including Column and Row; it does not describe Table or Chart datasets. Inspect components when collection shape changes.
`Events` are application wiring points that require real business handlers. Client-side function calls, such as host actions like `copyText` or `showModal`, are not listed; inspect a control's action before reusing it for a business operation. `Input events` lists button events whose context includes bound input paths, including skip actions that retain input. It does not prove edited values reach that event on a client or that a business operation succeeds.
Data-only changes affect existing bindings. A new status field does not automatically disable buttons, change visibility, or add a completed state: inspect the component definitions for those bindings. These public examples do not supply a complete business state machine; production bindings and authorized data paths belong to the business runtime.
| Scene | Description | Related patterns | Reuse boundary | A2UI | Notes |
|---|---|---|---|---|---|
| 企业信息 | 展示企业概况、风险与交易信息,支持预览下载报告附件 | [detail](../patterns/detail.md), [content](../patterns/content.md) | Fixed: none; Repeated: `/report/background`, `/report/findings`; Events: `company_investigation_download_attachment`, `company_investigation_preview_attachment`; Input events: none | [company-investigation](company-investigation/a2ui.json) | — |
| 混合审批汇报 | 同时包含待审批和已处理,两个 Tabs 分别展示 | [report](../patterns/report.md), [detail](../patterns/detail.md) | Fixed: none; Repeated: `/approvals/handled`, `/approvals/pending`; Events: none; Input events: none | [expense-approval](expense-approval/a2ui.json) | — |
| 单类审批列表 | 仅一类审批,无 Tabs;items 支持单条或多条 | [report](../patterns/report.md), [detail](../patterns/detail.md) | Fixed: none; Repeated: `/approvals/items`; Events: none; Input events: none | [expense-approval-list](expense-approval-list/a2ui.json) | — |
| 会议邀约 | 选择主题、参会人及线上线下方式,编辑地点和议程,校验后提交会议创建请求 | [form](../patterns/form.md) | Fixed: `/meeting/topics`=2; Repeated: none; Events: `meeting_invitation_submit`; Input events: `meeting_invitation_submit` | [meeting-invitation](meeting-invitation/a2ui.json) | Submits current form data after validation; a service callback must create the meeting and confirm success. Verify conditional visibility and picker rendering on target clients. |
| 会后总结 | 整理会议结论、待办与相关事项,支持创建任务设置提醒 | [detail](../patterns/detail.md) | Fixed: `/summary/conclusions`=3, `/summary/conclusions/2/details`=3, `/summary/todos`=3; Repeated: none; Events: `post_meeting_create_todo`, `post_meeting_ignore`, `post_meeting_set_reminder`; Input events: none | [post-meeting-summary](post-meeting-summary/a2ui.json) | — |
| 晨间日报·日程 | 汇总今日日程、冲突与重点关注,支持响应邀请处理事务 | [detail](../patterns/detail.md) | Fixed: `/agenda`=4, `/approvals`=2, `/attention`=2, `/timeline/segments`=12, `/todos`=3; Repeated: none; Events: `daily_briefing_accept_event`, `daily_briefing_approve_approval`, `daily_briefing_reject_approval`, `daily_briefing_reject_event`, `daily_briefing_select_section`, `daily_briefing_skip`, `daily_briefing_start_processing`; Input events: none | [daily-agenda-briefing](daily-agenda-briefing/a2ui.json) | — |
| 今日待办事项 | 集中展示今日审批、待办与会前准备,支持审批及查看相关资料 | [detail](../patterns/detail.md) | Fixed: `/approvals`=2, `/attention`=3, `/preparations`=2, `/preparations/0/notes`=2, `/preparations/1/notes`=1, `/todos`=3; Repeated: none; Events: `daily_briefing_approve_approval`, `daily_briefing_download_document`, `daily_briefing_preview_document`, `daily_briefing_reject_approval`, `daily_briefing_select_section`, `daily_briefing_skip`, `daily_briefing_start_processing`; Input events: none | [daily-approval-briefing](daily-approval-briefing/a2ui.json) | — |
| 任务处理 | 勾选会议协调、沟通与准备事项,交由助手继续推进处理 | [form](../patterns/form.md) | Fixed: none; Repeated: `/ui/taskRow`; Events: `task_delegation_select`, `task_delegation_skip`, `task_delegation_submit`; Input events: `task_delegation_skip`, `task_delegation_submit` | [task-delegation](task-delegation/a2ui.json) | Checkbox rows store index arrays internally; selection and submission events expose boolean values. |
| 工作周报 | 整理本周完成、下周计划与风险事项,生成结构化工作周报 | [report](../patterns/report.md) | Fixed: `/report/completed`=3, `/report/completed/2/details`=3, `/report/planned`=3, `/report/planned/2/details`=3, `/report/risks`=2; Repeated: none; Events: none; Input events: none | [weekly-work-report](weekly-work-report/a2ui.json) | — |
| 面试反馈 | 确认面试优点、不足与待改进及结论,服务确认后新增职级与评论消息并锁定已提交表单 | [form](../patterns/form.md) | Fixed: none; Repeated: none; Events: `interview_feedback_submit`, `interview_review_confirm`; Input events: `interview_feedback_submit`, `interview_review_confirm` | [interview-feedback](interview-feedback/a2ui.json) | The two-message flow requires service callbacks to save results, lock submitted forms, and send the next message. |
@@ -0,0 +1,955 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-company-investigation-card-a2ui",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "summary-title-icon",
"component": "Text",
"text": "📝",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "summary-title-label",
"component": "Text",
"text": "综合结论",
"maxLine": 30,
"bold": false,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "summary-title",
"component": "Row",
"children": [
"summary-title-icon",
"summary-title-label"
],
"gap": 4,
"align": "center",
"weight": 1
},
{
"id": "risk-tag",
"component": "Tag",
"text": {
"path": "/report/risk"
},
"theme": "orange"
},
{
"id": "summary-heading",
"component": "Row",
"children": [
"summary-title",
"risk-tag"
],
"gap": 8,
"align": "center"
},
{
"id": "summary",
"component": "Markdown",
"content": {
"path": "/report/summary"
}
},
{
"id": "summary-card-content",
"component": "Column",
"children": [
"summary-heading",
"summary"
],
"gap": 8,
"align": "stretch"
},
{
"id": "summary-card",
"component": "Card",
"child": "summary-card-content",
"padding": 10,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "extended_yellow0_color",
"borderColorToken": "common_line_light_color",
"backgroundColor": "#1FFF9200"
},
{
"id": "company-facts-0-label",
"component": "Text",
"text": "社会信用代码",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-0-value",
"component": "Text",
"text": {
"path": "/company/registration"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-0",
"component": "Row",
"children": [
"company-facts-0-label",
"company-facts-0-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-1-label",
"component": "Text",
"text": "法定代表人",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-1-value",
"component": "Text",
"text": {
"path": "/company/representative"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-1",
"component": "Row",
"children": [
"company-facts-1-label",
"company-facts-1-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-2-label",
"component": "Text",
"text": "成立日期",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-2-value",
"component": "Text",
"text": {
"path": "/company/founded"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-2",
"component": "Row",
"children": [
"company-facts-2-label",
"company-facts-2-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-3-label",
"component": "Text",
"text": "经营状态",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-3-value",
"component": "Text",
"text": {
"path": "/company/state"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_link_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-3",
"component": "Row",
"children": [
"company-facts-3-label",
"company-facts-3-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-4-label",
"component": "Text",
"text": "注册资本",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-4-value",
"component": "Text",
"text": {
"path": "/company/capital"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-4",
"component": "Row",
"children": [
"company-facts-4-label",
"company-facts-4-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-5-label",
"component": "Text",
"text": "注册地址",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-5-value",
"component": "Text",
"text": {
"path": "/company/address"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-5",
"component": "Row",
"children": [
"company-facts-5-label",
"company-facts-5-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-6-label",
"component": "Text",
"text": "所属行业",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-6-value",
"component": "Text",
"text": {
"path": "/company/industry"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-6",
"component": "Row",
"children": [
"company-facts-6-label",
"company-facts-6-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-7-label",
"component": "Text",
"text": "参保人数",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-7-value",
"component": "Text",
"text": {
"path": "/company/employees"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-7",
"component": "Row",
"children": [
"company-facts-7-label",
"company-facts-7-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts",
"component": "Column",
"children": [
"company-facts-0",
"company-facts-1",
"company-facts-2",
"company-facts-3",
"company-facts-4",
"company-facts-5",
"company-facts-6",
"company-facts-7"
],
"gap": 6,
"align": "stretch"
},
{
"id": "facts-title-icon",
"component": "Text",
"text": "🪪",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "facts-title-label",
"component": "Text",
"text": "工商信息",
"maxLine": 30,
"bold": true,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "facts-title",
"component": "Row",
"children": [
"facts-title-icon",
"facts-title-label"
],
"gap": 4,
"align": "center"
},
{
"id": "company-card-content",
"component": "Column",
"children": [
"facts-title",
"company-facts"
],
"gap": 8,
"align": "stretch"
},
{
"id": "company-card",
"component": "Card",
"child": "company-card-content",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "common_fg_z1_color",
"borderColorToken": "common_line_light_color"
},
{
"id": "risk-title-icon",
"component": "Text",
"text": "⚠️",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "risk-title-label",
"component": "Text",
"text": "企业风险",
"maxLine": 30,
"bold": true,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "risk-title",
"component": "Row",
"children": [
"risk-title-icon",
"risk-title-label"
],
"gap": 4,
"align": "center"
},
{
"id": "risk-detail-icon",
"component": "Text",
"text": "❗",
"variant": "body",
"bold": false,
"maxLine": 1
},
{
"id": "risk-detail-label",
"component": "Text",
"text": {
"path": "/report/riskDetail"
},
"maxLine": 30,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "risk-detail",
"component": "Row",
"children": [
"risk-detail-icon",
"risk-detail-label"
],
"gap": 4,
"align": "start"
},
{
"id": "risk-callout-content",
"component": "Column",
"children": [
"risk-detail"
],
"gap": 8,
"align": "stretch"
},
{
"id": "risk-callout",
"component": "Card",
"child": "risk-callout-content",
"padding": 10,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "extended_yellow0_color",
"borderColorToken": "common_line_light_color",
"backgroundColor": "#1FFF9200"
},
{
"id": "findings-title",
"component": "Text",
"text": "风险明细",
"maxLine": 30,
"bold": true,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "findings",
"component": "Column",
"children": {
"componentId": "findings-item",
"path": "/report/findings"
},
"gap": 8,
"align": "stretch"
},
{
"id": "background-title",
"component": "Text",
"text": "补充信息",
"maxLine": 30,
"bold": true,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "background",
"component": "Column",
"children": {
"componentId": "background-item",
"path": "/report/background"
},
"gap": 8,
"align": "stretch"
},
{
"id": "risk-card-content",
"component": "Column",
"children": [
"risk-title",
"risk-callout",
"risk-details-group"
],
"gap": 8,
"align": "stretch"
},
{
"id": "risk-card",
"component": "Card",
"child": "risk-card-content",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "common_fg_z1_color",
"borderColorToken": "common_line_light_color"
},
{
"id": "transactions-section-icon",
"component": "Text",
"text": "🤝",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "transactions-section-label",
"component": "Text",
"text": "历史成交",
"maxLine": 30,
"bold": false,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "transactions-section",
"component": "Row",
"children": [
"transactions-section-icon",
"transactions-section-label"
],
"gap": 4,
"align": "center"
},
{
"id": "transactions-title",
"component": "Text",
"text": {
"path": "/report/transactionsTitle"
},
"maxLine": 30,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "transactions",
"component": "Table",
"data": {
"path": "/report/table"
}
},
{
"id": "transactions-card-content",
"component": "Column",
"children": [
"transactions-section",
"transactions-title",
"transactions"
],
"gap": 8,
"align": "stretch"
},
{
"id": "transactions-card",
"component": "Card",
"child": "transactions-card-content",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "common_fg_z1_color",
"borderColorToken": "common_line_light_color"
},
{
"id": "attachment-file-icon",
"component": "Image",
"url": "https://g.alicdn.com/dingding/card_a2ui/0.3.1/img/ding-file-icons/markdown_light.BlEB_2F.png",
"darkUrl": "https://g.alicdn.com/dingding/card_a2ui/0.3.1/img/ding-file-icons/markdown_dark.JXjoMuz.png",
"variant": "avatar",
"fit": "contain",
"cornerRadius": 0,
"previewEnabled": false
},
{
"id": "attachment-name",
"component": "Text",
"text": {
"path": "/attachment/fileName"
},
"maxLine": 1,
"bold": true,
"colorToken": "common_level1_base_color",
"variant": "body",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "attachment-description",
"component": "Text",
"text": {
"path": "/attachment/description"
},
"maxLine": 1,
"variant": "caption",
"colorToken": "common_level2_base_color",
"sizeToken": "common_footnote_text_style__font_size"
},
{
"id": "attachment-details",
"component": "Column",
"children": [
"attachment-name",
"attachment-description"
],
"gap": 2,
"align": "stretch",
"weight": 1
},
{
"id": "attachment-preview-icon",
"component": "Text",
"text": "",
"icon": {
"name": "Visibility_L_outlined"
},
"colorToken": "common_level2_base_color"
},
{
"id": "attachment-preview",
"component": "Row",
"children": [
"attachment-preview-icon"
],
"gap": 0,
"align": "center",
"action": {
"event": {
"name": "company_investigation_preview_attachment",
"context": {
"fileName": {
"path": "/attachment/fileName"
},
"url": {
"path": "/attachment/url"
}
}
}
}
},
{
"id": "attachment-download-icon",
"component": "Icon",
"name": "DownloadAndSave_L_outlined",
"colorToken": "common_level2_base_color"
},
{
"id": "attachment-download",
"component": "Row",
"children": [
"attachment-download-icon"
],
"gap": 0,
"align": "center",
"action": {
"event": {
"name": "company_investigation_download_attachment",
"context": {
"fileName": {
"path": "/attachment/fileName"
},
"url": {
"path": "/attachment/url"
}
}
}
}
},
{
"id": "attachment",
"component": "Row",
"children": [
"attachment-file-icon",
"attachment-details",
"attachment-preview",
"attachment-download"
],
"gap": 12,
"align": "center"
},
{
"id": "attachment-card",
"component": "Card",
"child": "attachment",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"backgroundColor": "#00000000"
},
{
"id": "title",
"component": "Text",
"text": {
"path": "/company/name"
},
"maxLine": 30,
"sizeToken": "common_h2_text_style__font_size",
"bold": true,
"colorToken": "common_level1_base_color"
},
{
"id": "source",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "> 📊${/report/source}"
}
}
},
{
"id": "frame",
"component": "Column",
"children": [
"body"
],
"gap": 0,
"align": "stretch",
"padding": 0,
"cornerRadius": 12
},
{
"id": "root",
"component": "Column",
"children": [
"frame"
],
"gap": 0,
"align": "stretch",
"padding": 0
},
{
"id": "findings-section",
"component": "Column",
"children": [
"findings-title",
"findings"
],
"gap": 8,
"align": "stretch"
},
{
"id": "background-section",
"component": "Column",
"children": [
"background-title",
"background"
],
"gap": 8,
"align": "stretch"
},
{
"id": "risk-details-group",
"component": "Column",
"children": [
"findings-section",
"background-section"
],
"gap": 16,
"align": "stretch"
},
{
"id": "body",
"component": "Column",
"children": [
"title",
"summary-card",
"company-card",
"risk-card",
"transactions-card",
"attachment-card",
"source"
],
"gap": 10,
"align": "stretch",
"padding": 12
},
{
"id": "findings-bullet",
"component": "Text",
"text": "•",
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_link_color",
"maxLine": 1
},
{
"id": "findings-text",
"component": "Text",
"text": {
"path": "text"
},
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_level1_base_color",
"maxLine": 30,
"weight": 1
},
{
"id": "findings-item",
"component": "Row",
"children": [
"findings-bullet",
"findings-text"
],
"gap": 12,
"align": "start"
},
{
"id": "background-bullet",
"component": "Text",
"text": "•",
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_link_color",
"maxLine": 1
},
{
"id": "background-text",
"component": "Text",
"text": {
"path": "text"
},
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_level1_base_color",
"maxLine": 30,
"weight": 1
},
{
"id": "background-item",
"component": "Row",
"children": [
"background-bullet",
"background-text"
],
"gap": 12,
"align": "start"
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-company-investigation-card-a2ui",
"path": "/",
"value": {
"company": {
"name": "示例企业甲",
"registration": "DEMO-COMPANY-001",
"representative": "用户甲",
"founded": "2020 年 1 月 1 日",
"state": "正常经营",
"capital": "500 万元",
"address": "示例地址(虚构,不用于联系)",
"industry": "企业软件服务",
"employees": "80 人"
},
"report": {
"risk": "低风险",
"summary": "以下为虚构企业的演示结论:存在待核实事项,**请完成核验后再决定是否合作。**",
"riskDetail": "演示风险:存在待核实的主体信息和合同材料。",
"findings": [
{
"text": "示例检查项一:合同材料待补齐。"
},
{
"text": "示例检查项二:主体信息待复核。"
},
{
"text": "所有内容均为合成数据,不代表真实企业。"
}
],
"background": [
{
"text": "示例企业已提交基础材料。"
},
{
"text": "后续需要补充核验结果。"
}
],
"transactionsTitle": "演示成交累计3单,共¥15,000元。",
"table": {
"meta": [
{
"colKey": "month",
"colName": "月份",
"type": "text"
},
{
"colKey": "product",
"colName": "产品",
"type": "text"
},
{
"colKey": "amount",
"colName": "金额",
"type": "text"
}
],
"data": [
{
"month": "1月",
"product": "示例产品",
"amount": "4,000"
},
{
"month": "3月",
"product": "示例产品",
"amount": "5,000"
},
{
"month": "5月",
"product": "示例产品",
"amount": "6,000"
}
]
},
"source": "数据来源:合成演示数据,非真实调查结果"
},
"attachment": {
"fileName": "历史报价文件.md",
"description": "Markdown文件",
"url": ""
}
}
}
}
]
@@ -0,0 +1,347 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-expense-approval-list-a2ui",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "intro",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "<a atId=\"${/recipient/id}\">${/recipient/name}</a> ${/summary}"
}
}
},
{
"id": "pending-title",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "${applicant/name} ${expense/type}报销${expense/amount}元"
}
},
"maxLine": 20,
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"bold": true,
"weight": 1
},
{
"id": "pending-recommendation",
"component": "Text",
"text": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "approve"
}
},
"then": "建议批准",
"else": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": "",
"else": "待核实"
}
}
}
},
"sizeToken": "common_footnote_text_style__font_size",
"colorToken": "common_link_color",
"visible": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": false,
"else": {
"path": "showRecommendation"
}
}
},
"maxLine": 1,
"bold": true
},
{
"id": "applicant-value",
"component": "Row",
"children": [
"pending-title",
"pending-recommendation"
],
"gap": 8,
"align": "center"
},
{
"id": "pending-reason",
"component": "Text",
"text": {
"path": "ai/reason"
},
"sizeToken": "common_body_text_style__font_size",
"colorToken": "common_level2_base_color",
"maxLine": 20
},
{
"id": "pending-divider",
"component": "Row",
"children": [
"pending-divider-line"
],
"gap": 0,
"align": "center",
"visible": {
"path": "showDivider"
}
},
{
"id": "pending-reject-label",
"component": "Text",
"text": "拒绝",
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-reject",
"component": "Row",
"weight": 1,
"children": [
"pending-reject-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-approve-label",
"component": "Text",
"text": "批准",
"colorToken": "common_link_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-approve",
"component": "Row",
"weight": 1,
"children": [
"pending-approve-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-actions",
"component": "Row",
"children": [
"pending-reject",
"pending-approve"
],
"gap": 10,
"align": "center",
"visible": {
"call": "eq",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "pending-item",
"component": "Column",
"children": [
"pending-divider",
"applicant-value",
"pending-reason",
"pending-actions",
"pending-result-row"
],
"gap": 16,
"align": "stretch"
},
{
"id": "pending-list",
"component": "Column",
"children": {
"componentId": "pending-item",
"path": "/approvals/items"
},
"gap": 16,
"align": "stretch"
},
{
"id": "pending-panel",
"component": "Column",
"padding": 12,
"cornerRadius": 16,
"borderWidth": 0.5,
"borderColorToken": "common_line_light_color",
"backgroundColorToken": "common_fg_z1_color",
"children": [
"pending-list"
],
"gap": 0,
"align": "stretch"
},
{
"id": "root",
"component": "Column",
"children": [
"intro",
"pending-panel"
],
"gap": 10,
"align": "stretch"
},
{
"id": "pending-status",
"component": "Text",
"text": {
"path": "resultLabel"
},
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-result",
"component": "Row",
"weight": 1,
"children": [
"pending-status"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-result-row",
"component": "Row",
"children": [
"pending-result"
],
"gap": 0,
"align": "center",
"visible": {
"call": "ne",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "pending-divider-line",
"component": "Divider",
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"axis": "horizontal",
"borderColor": "#1F111F2C",
"weight": 1
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-expense-approval-list-a2ui",
"path": "/",
"value": {
"identity": {
"requestId": "demo-approval-briefing"
},
"recipient": {
"id": "demo-approval-recipient",
"name": "林小夏"
},
"summary": "今天有2条待审批需要你确认。",
"approvals": {
"items": [
{
"id": "demo-expense-001",
"revision": 1,
"showRecommendation": true,
"showDivider": false,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-1",
"name": "顾宁"
},
"expense": {
"type": "出差",
"amount": 1000,
"currency": "CNY"
},
"ai": {
"decision": "approve",
"reason": "报销金额、出差行程和票据匹配,费用标准符合公司规定"
}
},
{
"id": "demo-expense-002",
"revision": 1,
"showRecommendation": true,
"showDivider": true,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-2",
"name": "周言"
},
"expense": {
"type": "餐饮",
"amount": 2400,
"currency": "CNY"
},
"ai": {
"decision": "reject",
"reason": "餐饮费用超出公司标准,且部分支出缺少有效票据,建议退回补充说明"
}
}
]
}
}
}
}
]
@@ -0,0 +1,572 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-expense-approval-card-a2ui",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "intro",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "<a atId=\"${/recipient/id}\">${/recipient/name}</a> ${/summary}"
}
}
},
{
"id": "pending-title",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "${applicant/name} ${expense/type}报销${expense/amount}元"
}
},
"maxLine": 20,
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"bold": true,
"weight": 1
},
{
"id": "pending-recommendation",
"component": "Text",
"text": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "approve"
}
},
"then": "建议批准",
"else": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": "",
"else": "待核实"
}
}
}
},
"sizeToken": "common_footnote_text_style__font_size",
"colorToken": "common_link_color",
"visible": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": false,
"else": {
"path": "showRecommendation"
}
}
},
"maxLine": 1,
"bold": true
},
{
"id": "applicant-value",
"component": "Row",
"children": [
"pending-title",
"pending-recommendation"
],
"gap": 8,
"align": "center"
},
{
"id": "pending-reason",
"component": "Text",
"text": {
"path": "ai/reason"
},
"sizeToken": "common_body_text_style__font_size",
"colorToken": "common_level2_base_color",
"maxLine": 20
},
{
"id": "pending-divider",
"component": "Row",
"children": [
"pending-divider-line"
],
"gap": 0,
"align": "center",
"visible": {
"path": "showDivider"
}
},
{
"id": "pending-reject-label",
"component": "Text",
"text": "拒绝",
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-reject",
"component": "Row",
"weight": 1,
"children": [
"pending-reject-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-approve-label",
"component": "Text",
"text": "批准",
"colorToken": "common_link_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-approve",
"component": "Row",
"weight": 1,
"children": [
"pending-approve-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-actions",
"component": "Row",
"children": [
"pending-reject",
"pending-approve"
],
"gap": 10,
"align": "center",
"visible": {
"call": "eq",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "pending-item",
"component": "Column",
"children": [
"pending-divider",
"applicant-value",
"pending-reason",
"pending-actions",
"pending-result-row"
],
"gap": 16,
"align": "stretch"
},
{
"id": "pending-list",
"component": "Column",
"children": {
"componentId": "pending-item",
"path": "/approvals/pending"
},
"gap": 16,
"align": "stretch"
},
{
"id": "pending-panel",
"component": "Column",
"padding": 12,
"cornerRadius": 16,
"borderWidth": 0.5,
"borderColorToken": "common_line_light_color",
"backgroundColorToken": "common_fg_z1_color",
"children": [
"pending-list"
],
"gap": 0,
"align": "stretch"
},
{
"id": "handled-title",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "${applicant/name} ${expense/type}报销${expense/amount}元"
}
},
"maxLine": 20,
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"bold": true,
"weight": 1
},
{
"id": "handled-recommendation",
"component": "Text",
"text": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "approve"
}
},
"then": "建议批准",
"else": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": "",
"else": "待核实"
}
}
}
},
"sizeToken": "common_footnote_text_style__font_size",
"colorToken": "common_link_color",
"visible": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": false,
"else": {
"path": "showRecommendation"
}
}
},
"maxLine": 1,
"bold": true
},
{
"id": "handled-header",
"component": "Row",
"children": [
"handled-title",
"handled-recommendation"
],
"gap": 8,
"align": "center"
},
{
"id": "handled-reason",
"component": "Text",
"text": {
"path": "ai/reason"
},
"sizeToken": "common_body_text_style__font_size",
"colorToken": "common_level2_base_color",
"maxLine": 20
},
{
"id": "handled-divider",
"component": "Row",
"children": [
"handled-divider-line"
],
"gap": 0,
"align": "center",
"visible": {
"path": "showDivider"
}
},
{
"id": "handled-status",
"component": "Text",
"text": {
"path": "resultLabel"
},
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size"
},
{
"id": "handled-item",
"component": "Column",
"children": [
"handled-divider",
"handled-header",
"handled-reason",
"handled-result-row"
],
"gap": 16,
"align": "stretch"
},
{
"id": "handled-list",
"component": "Column",
"children": {
"componentId": "handled-item",
"path": "/approvals/handled"
},
"gap": 16,
"align": "stretch"
},
{
"id": "handled-panel",
"component": "Column",
"padding": 12,
"cornerRadius": 16,
"borderWidth": 0.5,
"borderColorToken": "common_line_light_color",
"backgroundColorToken": "common_fg_z1_color",
"children": [
"handled-list"
],
"gap": 0,
"align": "stretch"
},
{
"id": "tabs",
"component": "Tabs",
"tabs": [
{
"title": {
"path": "/view/pendingLabel"
},
"child": "pending-panel"
},
{
"title": {
"path": "/view/handledLabel"
},
"child": "handled-panel"
}
]
},
{
"id": "root",
"component": "Column",
"children": [
"intro",
"tabs"
],
"gap": 10,
"align": "stretch"
},
{
"id": "pending-status",
"component": "Text",
"text": {
"path": "resultLabel"
},
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-result",
"component": "Row",
"weight": 1,
"children": [
"pending-status"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-result-row",
"component": "Row",
"children": [
"pending-result"
],
"gap": 0,
"align": "center",
"visible": {
"call": "ne",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "handled-result",
"component": "Row",
"weight": 1,
"children": [
"handled-status"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "handled-result-row",
"component": "Row",
"children": [
"handled-result"
],
"gap": 0,
"align": "center"
},
{
"id": "pending-divider-line",
"component": "Divider",
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"axis": "horizontal",
"borderColor": "#1F111F2C",
"weight": 1
},
{
"id": "handled-divider-line",
"component": "Divider",
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"axis": "horizontal",
"borderColor": "#1F111F2C",
"weight": 1
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-expense-approval-card-a2ui",
"path": "/",
"value": {
"identity": {
"requestId": "demo-approval-briefing"
},
"recipient": {
"id": "demo-approval-recipient",
"name": "林小夏"
},
"summary": "今天有3条审批需要跟你汇报,其中1条已按你的授权自动处理。",
"view": {
"pendingLabel": "待处理·2",
"handledLabel": "已为您处理·1"
},
"approvals": {
"pending": [
{
"id": "demo-expense-001",
"revision": 1,
"showRecommendation": true,
"showDivider": false,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-1",
"name": "顾宁"
},
"expense": {
"type": "出差",
"amount": 1000,
"currency": "CNY"
},
"ai": {
"decision": "approve",
"reason": "报销金额、出差行程和票据匹配,费用标准符合公司规定"
}
},
{
"id": "demo-expense-002",
"revision": 1,
"showRecommendation": true,
"showDivider": true,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-2",
"name": "周言"
},
"expense": {
"type": "餐饮",
"amount": 2400,
"currency": "CNY"
},
"ai": {
"decision": "reject",
"reason": "餐饮费用超出公司标准,且部分支出缺少有效票据,建议退回补充说明"
}
}
],
"handled": [
{
"id": "demo-expense-003",
"revision": 2,
"showRecommendation": false,
"showDivider": false,
"resultLabel": "已批准",
"status": "approved",
"applicant": {
"id": "demo-applicant-3",
"name": "陈安"
},
"expense": {
"type": "交通",
"amount": 120,
"currency": "CNY"
},
"ai": {
"decision": "approve",
"reason": "金额在授权范围内,票据及费用标准符合公司规定"
}
}
]
}
}
}
}
]
Loaded 100 of 2886 files, more files were not shown because too many files have changed in this diff. Show more