Compare commits

...
24 changed files with 1793 additions and 119 deletions

No files matched your search

+2
View File
@@ -2,6 +2,8 @@
## Overview ## Overview
ARTEMIS 3.x 测试分支使用独立 `catalog-v5.json`:Cua `0.38.0+artemis.2`、task-review `1.1.1` 与 artemis-device-test `0.5.0+artemis.7`。旧索引和 `<3.0.0` 约束不变;测试包保留各自未测范围,不代替 Rust 业务验收。来源与边界见 [3.0 测试市场说明](docs/rust3-compatibility.md)。
本仓库维护 ARTEMIS 插件适配与市场分发。当前发布基线是 Cua Driver 0.38.0 的 Windows x64 适配,市场版本为 `0.38.0+artemis.1`,保留预览标识。后续版本以目标分支的清单、市场索引、来源记录和发行说明为准。 本仓库维护 ARTEMIS 插件适配与市场分发。当前发布基线是 Cua Driver 0.38.0 的 Windows x64 适配,市场版本为 `0.38.0+artemis.1`,保留预览标识。后续版本以目标分支的清单、市场索引、来源记录和发行说明为准。
这是适配器架构与行为设计,插件没有独立产品网页或通用配色。主程序网页的视觉规则由其根目录 DESIGN.md 管理;包内 [SKILL.md](plugins/cua-driver/SKILL.md) 指导调用方如何使用实际工具,不代替宿主权限或证据校验。 这是适配器架构与行为设计,插件没有独立产品网页或通用配色。主程序网页的视觉规则由其根目录 DESIGN.md 管理;包内 [SKILL.md](plugins/cua-driver/SKILL.md) 指导调用方如何使用实际工具,不代替宿主权限或证据校验。
+2
View File
@@ -1,5 +1,7 @@
# Artemis Plugins # Artemis Plugins
ARTEMIS 3.x 使用独立测试分支 `codex/rust3-plugin-market` 和 `catalog-v5.json`,包含 Cua `0.38.0+artemis.2`、任务人工评审 `1.1.1`、device-test `0.5.0+artemis.7`。三个新版本各自记录验证范围;device-test 使用独立 Python worker。构建输入、来源和下载校验要求见 [3.0 测试市场说明](docs/rust3-compatibility.md)。下文保留对应 2.x 发行的历史说明。
ARTEMIS 的插件市场。配套 ARTEMIS 2.2.0 发布的电脑控制插件为 **Cua 0.38.0+artemis.1**,支持 Windows x64,通过窗口截图与可访问性树操作电脑应用和浏览器窗口,复用 ARTEMIS 的调度、资源互斥、步骤、截图附件和录像。 ARTEMIS 的插件市场。配套 ARTEMIS 2.2.0 发布的电脑控制插件为 **Cua 0.38.0+artemis.1**,支持 Windows x64,通过窗口截图与可访问性树操作电脑应用和浏览器窗口,复用 ARTEMIS 的调度、资源互斥、步骤、截图附件和录像。
在 ARTEMIS 的「插件」页面安装「电脑控制 · Cua」,授权并启用即可。安卓任务可根据提示词按需调用电脑控制,不需要单独配置 Codex MCP 或安装 Go、Node、Python。 在 ARTEMIS 的「插件」页面安装「电脑控制 · Cua」,授权并启用即可。安卓任务可根据提示词按需调用电脑控制,不需要单独配置 Codex MCP 或安装 Go、Node、Python。
+624
View File
@@ -0,0 +1,624 @@
{
"schema_version": 1,
"plugins": [
{
"manifest": {
"schema_version": 2,
"id": "cua-driver",
"name": "电脑控制 · Cua",
"version": "0.38.0+artemis.2",
"description": "通过 Cua Driver 的窗口截图与可访问性树操作 Windows 应用及浏览器窗口;复用 ARTEMIS 的任务调度、步骤证据与录像。",
"artemis_version": ">=3.0.0,<4.0.0",
"platforms": [
"win32"
],
"architectures": [
"amd64"
],
"source": "https://github.com/trycua/cua",
"license": "MIT",
"server": {
"transport": "stdio",
"command": "${plugin_dir}/bin/artemis-cua-adapter.exe"
},
"skill": "SKILL.md",
"targets": [
"computer",
"mixed"
],
"resources": [
"desktop:local"
],
"observation": {
"tool": "list_windows",
"arguments": {
"on_screen_only": true
},
"refresh_tools": [
"get_window_state"
]
},
"capabilities": {
"kind": "visual",
"recording": "windows",
"tools": {
"list_windows": {
"effect": "read",
"observation": true,
"idempotent": true,
"target_argument": "",
"cancellation": "unknown",
"evidence": [
"text"
]
},
"get_window_state": {
"effect": "read",
"observation": true,
"idempotent": true,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"verify_state": {
"effect": "read",
"observation": false,
"idempotent": true,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"click": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"drag": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"type_text": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"press_key": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"set_value": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"scroll": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"save_screenshot": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image",
"file"
]
},
"click_at": {
"effect": "write",
"observation": false,
"idempotent": false,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image"
]
},
"bring_to_front": {
"effect": "write",
"observation": false,
"idempotent": true,
"target_argument": "window_id",
"cancellation": "unknown",
"evidence": [
"text",
"image",
"state"
]
}
},
"required_tools": [
"list_windows",
"get_window_state",
"save_screenshot"
]
}
},
"release_stage": "preview",
"archive_url": "https://git.playones.com/chenmingxuan/Artemis-Plugins/releases/download/rust3-plugin-preview-20261101/cua-driver-0.38.0+artemis.2-windows-amd64.zip",
"sha256": "86a26b95618ebff90b59ab8be4d9a5985ef83a6d372405cfdd7ab5ee77ad3308",
"verification": "Windows x64 / ARTEMIS 3.x 候选。Go 确定性回归、vet、桥接构建和最终 ZIP 输入/回读校验通过;Driver/UIA/SDK/游标工具原样复用已发布 0.38.0+artemis.1 的固定文件与来源。3.0 实际握手、窗口发现、截图、动作、录像及业务任务待宿主统一验收;未进行性能测试。"
},
{
"manifest": {
"schema_version": 3,
"id": "task-review",
"name": "任务人工评审",
"version": "1.1.1",
"description": "在任务回放中查看导入的原始用例,逐条填写用例执行准确性与测试结果正确性,按需填写备注并随报告导出。",
"artemis_version": ">=3.0.0,<4.0.0",
"platforms": [
"win32",
"linux",
"darwin"
],
"architectures": [
"amd64",
"arm64"
],
"license": "Apache-2.0",
"targets": [],
"capabilities": {
"kind": "other",
"recording": "none",
"tools": {}
},
"workspace": {
"panels": [
{
"id": "requirements",
"title": "原始用例",
"side": "left",
"kind": "original_case",
"width": {
"preferred": 340,
"minimum": 240,
"maximum": 480
}
},
{
"id": "review",
"title": "人工评审",
"side": "right",
"kind": "form",
"width": {
"preferred": 360,
"minimum": 260,
"maximum": 520
}
}
],
"fields": [
{
"id": "execution_accuracy",
"label": "用例执行准确性",
"description": "核对实际操作是否符合用例步骤。",
"kind": "choice",
"options": [
"通过",
"失败"
]
},
{
"id": "execution_note",
"label": "用例执行准确性备注",
"kind": "text",
"max_length": 2000
},
{
"id": "result_correctness",
"label": "测试结果正确性",
"description": "核对测试结果是否符合用例的预期结果。",
"kind": "choice",
"options": [
"通过",
"失败"
]
},
{
"id": "result_note",
"label": "测试结果正确性备注",
"kind": "text",
"max_length": 2000
}
]
}
},
"release_stage": "preview",
"archive_url": "https://git.playones.com/chenmingxuan/Artemis-Plugins/releases/download/rust3-plugin-preview-20261101/task-review-1.1.1.zip",
"sha256": "37647b77c7a1384f64ed861f72050858052c187eb7c19cfb338516d5e24e9e0f",
"verification": "ARTEMIS 3.x 声明式工作区候选,无运行进程或额外依赖。清单字段/面板及最终 ZIP 回读校验通过;实际界面、自动保存、导航与报告导出待 3.0 宿主统一验收,不沿用 2.4 的历史测试结论。"
},
{
"manifest": {
"schema_version": 2,
"id": "artemis-device-test",
"name": "家庭安防多工具测试",
"version": "0.5.0+artemis.7",
"description": "集成 artemis-device-test Skill:PATH/统一目录自动发现工具及匹配位数的 MediaInfo DLL、按能力独立自检、本机网卡枚举、357 条用例路由、媒体/抓包/串口、V1/V2 设备及联合证据。PC/网站复用视觉插件,手机沿用 Artemis;工具成功不等于用例通过。",
"artemis_version": ">=3.0.0,<4.0.0",
"platforms": [
"win32"
],
"architectures": [
"amd64"
],
"license": "LicenseRef-Yitong-Artemis-Market; Apache-2.0-Adapter",
"server": {
"transport": "stdio",
"command": "${config.python}",
"args": [
"-B",
"${plugin_dir}/server.py"
],
"env": {
"ARTEMIS_DEVICE_TEST_PROFILE": "${config.profile}",
"ARTEMIS_DEVICE_TEST_TOOL_ROOT": "${config.tool_root}",
"ARTEMIS_DEVICE_TEST_ENV_FILE": "${config.rtsp_env_file}",
"ARTEMIS_DEVICE_TEST_TIMEOUT": "${config.operation_timeout_seconds}",
"ARTEMIS_DEVICE_TEST_ALLOW_FIRMWARE_WRITE": "${config.allow_firmware_write}"
}
},
"config": {
"python": {
"label": "插件 Python 解释器(独立准备)",
"description": "使用独立 Windows x64 Python,预先安装本插件 requirements.txt。Rust 主程序不提供 Python;安装插件不会运行 pip。",
"default": "python",
"required": true
},
"profile": {
"label": "本机工具 profile JSON",
"description": "可选,固定 tools/runtimes/libraries 路径;不保存凭据。留空时自动发现统一目录和宿主 PATH;显式错误路径不回退。",
"default": ""
},
"tool_root": {
"label": "已解压工具目录",
"description": "可选,本机已解压工具目录绝对路径;自动发现 EXE/匹配位数的 MediaInfo.dll,以及 binwalk-python/Scripts/python.exe。不安装程序。",
"default": ""
},
"rtsp_env_file": {
"label": "本机 RTSP 环境文件",
"description": "可选,受控 .env 文件的绝对路径;仅按工具指定的环境变量名读取,不回显凭据。",
"default": ""
},
"operation_timeout_seconds": {
"label": "单次操作预算(秒)",
"description": "默认45,范围10..3300;宿主插件工具时限至少大10秒,并受任务剩余预算约束。",
"default": "45"
},
"allow_firmware_write": {
"label": "允许显式固件写入",
"description": "默认 false。仅当前用例明确授权升级时设置 true;工具还要求 execute、完整身份、ROM SHA-256 和审阅摘要。",
"default": "false"
}
},
"skill": "SKILL.md",
"targets": [
"android",
"mixed",
"computer"
],
"resources": [
"desktop:local",
"lab:device-test"
],
"capabilities": {
"kind": "other",
"recording": "none",
"required_tools": [
"environment_doctor",
"lookup_case",
"probe_media",
"media_info",
"analyze_pcap",
"scan_ports",
"scan_firmware",
"device_capabilities",
"list_network_interfaces",
"discover_devices",
"identify_device",
"device_info",
"inspect_firmware",
"firmware_preflight",
"verify_firmware",
"capture_packets",
"capture_serial",
"upgrade_firmware",
"import_evidence",
"bundle_evidence"
],
"tools": {
"environment_doctor": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
]
},
"lookup_case": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
]
},
"probe_media": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "input_file"
},
"media_info": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "input_file"
},
"analyze_pcap": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "input_file"
},
"scan_ports": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"scan_firmware": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "input_file"
},
"device_capabilities": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
]
},
"list_network_interfaces": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
]
},
"discover_devices": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
]
},
"identify_device": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"device_info": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"inspect_firmware": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "rom_file"
},
"firmware_preflight": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"verify_firmware": {
"effect": "read",
"observation": false,
"idempotent": true,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"capture_packets": {
"effect": "write",
"observation": false,
"idempotent": false,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"capture_serial": {
"effect": "write",
"observation": false,
"idempotent": false,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "port"
},
"upgrade_firmware": {
"effect": "write",
"observation": false,
"idempotent": false,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "host"
},
"import_evidence": {
"effect": "write",
"observation": false,
"idempotent": false,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "input_file"
},
"bundle_evidence": {
"effect": "write",
"observation": false,
"idempotent": false,
"cancellation": "unknown",
"evidence": [
"text",
"state",
"file"
],
"target_argument": "run_id"
}
}
}
},
"release_stage": "preview",
"archive_url": "https://git.playones.com/chenmingxuan/Artemis-Plugins/releases/download/rust3-plugin-preview-20261101/artemis-device-test-0.5.0+artemis.7-windows-amd64.zip",
"sha256": "2af0a60b4e991fc8def707bd562268330790f276528fe66627de817f296c0174",
"verification": "ARTEMIS 3.x Windows x64 候选;独立 Python/MCP/dotenv 与外部工具由使用者准备。固定源码、MARKET 内容摘要和最终 ZIP 回读校验;完整许可及证据准入由 SDK 独立预检。Rust 安装、握手、取消、设备与业务验收范围以包内验证记录为准,不沿用旧宿主结论。"
}
]
}
+23
View File
@@ -0,0 +1,23 @@
# ARTEMIS 3.0 独立测试市场
测试分支 `codex/rust3-plugin-market` 基于 `987b5ff2d8c3516cf87e2f1abcac854732a8ccee`,不包含原插件开发目录的未发布提交。旧 `catalog.json`、`catalog-v3.json`、`catalog-v4.json` 和 2.x 附件保留。
| 当前插件 | 新不可变测试版本 | 宿主范围 | 用户运行依赖 |
| --- | --- | --- | --- |
| Cua | 0.38.0+artemis.2 | >=3.0.0,<4.0.0 | 自包含 Windows x64 Go 桥、Rust Driver/UIA、SDK DLL、游标工具 |
| task-review | 1.1.1 | >=3.0.0,<4.0.0 | 声明式任务评审,无额外进程 |
| artemis-device-test | 0.5.0+artemis.7 | >=3.0.0,<4.0.0 | 独立 Python、requirements.txt 和所需外部工具;安装不自动运行 pip 或授权 |
旧 OCU 已退休,源码和历史附件保留;内置 Android 能力随主程序提供,无需市场插件。
构建命令(输出目录和索引文件必须不存在):
```powershell
python scripts/package_rust3_compat.py --cua-base-archive <已校验的.1输入ZIP> --go <便携Go目录>/bin/go.exe --output <新候选目录> --catalog-output <新索引文件> --release-tag rust3-plugin-preview-20261101
```
Cua `.1` 输入 SHA256 必须为 `c007da16d58c4165a1a344113e650e68bd92a69ad3ce4ca8285818c5426dc0f4`。Go 源码除适配版本常量外逐文件对比,Driver/UIA/SDK 和游标工具复用已发布输入;provenance 区分复用和新编译。构建在独立缓存内运行 Go test/vet/build,不运行桌面或业务任务。
device-test 所有 Python 源码字节保留;RUST3-SOURCE.json 记录原提交和逐文件摘要。组件许可及材料/文件绑定通过 authoring SDK 检查;MARKET.json 的 Rust 业务项目明确为 unverified,原 2.x 结果不外推。测试市场以 preview 展示,不是正式审核通过声明。安装、启用与 profile 授权分离。
三个 ZIP、SHA256SUMS、catalog 与 bundle-map 必须匹配;HTTPS 下载地址共用指定预览发行标签。先固定源码并验证最终包,再上传新标签附件、回读 SHA,最后提交测试索引;不覆盖旧附件。下载和 Rust 网页联调范围在本分支另行记录。
+21
View File
@@ -0,0 +1,21 @@
# Rust 3 插件市场预览验证(2026-11-01)
固定源码:5d14bcffbc0cb6389c89d0e54b33895d5ab5f57c。标签 rust3-plugin-preview-20261101 指向该提交;构建时工作区干净。
已运行:打包器 6 项离线回归;Go test/vet/build;三个最终 ZIP 清单与索引一致性、SHA256 回读;device-test 包解压后 SDK 许可、材料/文件摘要绑定检查。12 项 Rust 业务准入仍为 unverified,未按正式审核通过发布。
预览发行已回读为 draft=false、prerelease=true。三个 ZIP 和四份配套材料共 7 个附件均已通过无认证 HTTPS 下载及 SHA256 对比;build-receipt.json 的 published=false 是构建时状态,发行状态以此记录和实际远端为准。
| 附件 | SHA256 |
| --- | --- |
| cua-driver-0.38.0+artemis.2-windows-amd64.zip | 86a26b95618ebff90b59ab8be4d9a5985ef83a6d372405cfdd7ab5ee77ad3308 |
| task-review-1.1.1.zip | 37647b77c7a1384f64ed861f72050858052c187eb7c19cfb338516d5e24e9e0f |
| artemis-device-test-0.5.0+artemis.7-windows-amd64.zip | 2af0a60b4e991fc8def707bd562268330790f276528fe66627de817f296c0174 |
| SHA256SUMS.txt | 30e44c5ab0b715c24a71dc21b396bbfc51aa9a3e5e4a1ae2ea5caa35309bc508 |
| catalog-v5.json | feedce75d6de176beb0d9151e51821b9bacb07c86de1db998f6892ab68f0eb45 |
| bundle-map.json | 2a838beb0a602d8910bc23f6ede5219bbfd2336ed0832ba9fa46e42f1e77c911 |
| build-receipt.json | 4dd61b18853584719373192babe2d4d026bc2ec87d086065eb6fb591b43c9a93 |
[预览发行](https://git.playones.com/chenmingxuan/Artemis-Plugins/releases/tag/rust3-plugin-preview-20261101)
Rust 3.0.0 宿主已通过 `/api/plugins/catalog` 严格解析此测试分支的三个条目,并在实际网页的插件市场显示三个开发预览及安装入口。仅验证读取与展示,未安装、启用或扩大 profile 授权;未运行付费模型、设备/桌面业务或录像联调。安装不自动执行 pip。
@@ -1,3 +1,5 @@
> Rust 3 测试市场版本 `0.5.0+artemis.7`;独立 Python/MCP/dotenv 与外部工具由使用者准备。验证边界见 [RUST3-VERIFICATION.md](RUST3-VERIFICATION.md)。下文保留原 2.x 版本的历史范围。
# 内部团队首次部署(Windows x64) # 内部团队首次部署(Windows x64)
接收方先准备当前用例需要的工具,随后插件自动发现和检查。无需逐项填写程序路径。插件 ZIP 包含适配代码、原 Skill、通用样例和说明,不携带第三方程序、驱动、本机 profile、账号或测试数据。 接收方先准备当前用例需要的工具,随后插件自动发现和检查。无需逐项填写程序路径。插件 ZIP 包含适配代码、原 Skill、通用样例和说明,不携带第三方程序、驱动、本机 profile、账号或测试数据。
+62 -50
View File
@@ -1,8 +1,8 @@
{ {
"schema_version": 1, "schema_version": 1,
"plugin_id": "artemis-device-test", "plugin_id": "artemis-device-test",
"plugin_version": "0.5.0+artemis.6", "plugin_version": "0.5.0+artemis.7",
"content_sha256": "83f19450c4b0bdf4bad9389770e3ab35f81855d4e9f5d2975934e395308aabaf", "content_sha256": "5ad107572dd67bba856d2a19c1c8d49b8c7189c95d6ed021e0af2961d1343412",
"traits": { "traits": {
"uses_subprocesses": true, "uses_subprocesses": true,
"file_inputs_for_writes": true "file_inputs_for_writes": true
@@ -46,80 +46,92 @@
"permission": "permitted", "permission": "permitted",
"license_file": "LICENSE", "license_file": "LICENSE",
"source_file": "SOURCE.md" "source_file": "SOURCE.md"
},
{
"name": "Rust 3 preview materials",
"paths": [
"UPSTREAM-MARKET.json",
"RUST3-SOURCE.json",
"RUST3-VERIFICATION.md"
],
"license": "Apache-2.0",
"permission": "permitted",
"license_file": "LICENSE",
"source_file": "SOURCE.md"
} }
], ],
"checks": { "checks": {
"installation": { "installation": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "实际宿主 API 安装、临时目录旧版升级和禁用/配置保留已验证;新装不自动授权。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"tool_contract": { "tool_contract": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "真实 stdio MCP 握手和全部 20 项定义检查;8 次隔离安全调用、10 份附件;134 项单元回归。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"failure_partial": { "failure_partial": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "清理核验故障及泛化 I/O 异常保留执行事实;未知写副作用不重试;缺失证据保持 inconclusive。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"timeout_cancel": { "timeout_cancel": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "合成工具超时及真实 MCP 自检取消;取消后工具和后代约 0.03 秒退出,未操作真实设备。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"process_tree": { "process_tree": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "Windows Job 合成进程验证正常/非零/超时/超限/拥有者退出及清理异常;不声称其他系统通过。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"file_binding": { "file_binding": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "固件批准字节保护快照对抗原路径替换/截断/增长;V2 模拟协议;没有真实烧录。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"resource_conflicts": { "resource_conflicts": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "临时锁目录验证 desktop:local 和 lab:device-test 各自互斥;运行时阻止固定版本更新和卸载。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"evidence_export": { "evidence_export": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "正常宿主离线任务导入并校验摘要;8 份附件读回;Word/Excel/全部资料 ZIP 容器与附件字节核验。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"history_uninstall": { "history_uninstall": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "独立临时目录卸载后原历史附件字节仍可读;未删除用户任务或历史。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"secret_redaction": { "secret_redaction": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "合成凭据输出脱敏及源码审查通过;未将个人配置、原始报告、抓包、环境目录打包。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"real_task": { "real_task": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "正常 ARTEMIS Pro/computer 离线证据任务完成,AI 正式结论 passed、5/5;暂停恢复实测;真实设备能力未测。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
}, },
"source_review": { "source_review": {
"status": "passed", "status": "unverified",
"evidence_file": "VERIFICATION.md", "evidence_file": "RUST3-VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a", "evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
"notes": "原候选 ZIP 摘要、当前文件来源清单、未改动 vendor 摘要及公司市场分发确认记录已核对;适配层 Apache-2.0。" "notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
} }
} }
} }
+2
View File
@@ -1,3 +1,5 @@
> Rust 3 测试市场版本 `0.5.0+artemis.7`;独立 Python/MCP/dotenv 与外部工具由使用者准备。验证边界见 [RUST3-VERIFICATION.md](RUST3-VERIFICATION.md)。下文保留原 2.x 版本的历史范围。
# 家庭安防多工具测试插件 # 家庭安防多工具测试插件
将用户提供的 `artemis-device-test 0.5.0-internal` 接入 Artemis 2.0 插件系统,版本为 `0.5.0+artemis.6`。提供 20 个真实 MCP 工具、357 条保留用例路由和按需参考资料。工具成功、协议完成、业务通过分别记录。 将用户提供的 `artemis-device-test 0.5.0-internal` 接入 Artemis 2.0 插件系统,版本为 `0.5.0+artemis.6`。提供 20 个真实 MCP 工具、357 条保留用例路由和按需参考资料。工具成功、协议完成、业务通过分别记录。
@@ -0,0 +1,29 @@
{
"schema_version": 1,
"source_commit": "987b5ff2d8c3516cf87e2f1abcac854732a8ccee",
"source_version": "0.5.0+artemis.6",
"version": "0.5.0+artemis.7",
"all_python_source_bytes_preserved": true,
"python_files_sha256": {
"dependency_checks.py": "0ea7db6b7f0a816a75e737b25e7bd2592303c69196ac6b9827b58fe18d10ab5d",
"dependency_runner.py": "3bbb648faab09fbc41065b43d89213f9fb29a274961bc9fd1a8ebd018f979ef2",
"device_runtime.py": "e17b51ddf9c871a03bd1b44a35e0b0a5c98847ad9e8b586c64b89380e9d15ff6",
"network_inventory.py": "a915a9acd05db5804421a812570dd852427bae571677600f271939e7bdf5b041",
"server.py": "3a8c481579dd38fbbc923d14c406ef98f7d16a9bea4cd3c1a4d774438fedd0a6",
"skill/scripts/device_adapter.py": "409f2a0d6f8e85b006aa950d0d8c53d5d0a8e4e713b61155a9a8a23e6205cf25",
"skill/scripts/evidence.py": "e85dae725d39a1fb862d59a8e423b51f6d6cd3fe63d0ce1138ab651e24a0e098",
"skill/scripts/process_lifecycle.py": "0106bc81a675e595a98f4c3f3201b8021e1f574ed3486873a6550b56b5d9e5cb",
"skill/scripts/tool_runner.py": "10f4e4085a6ea06bbd89cfdabc065c048b99bcf96617ea2f64c34eeaf7e612cd",
"skill/scripts/v1_protocol.py": "c512e2cb99a0ba9cd70ade1429a2e96b236c9026dea5a96dcaf0b5051c6972a0",
"skill/tests/test_device_adapter.py": "c83ebf61d5065eb4113a6ba7985fc0f5c336dcb7d1e47ec09ec15c188849284b",
"skill/tests/test_evidence.py": "d6cbbfab48af03958e1716631d1a1e6f034b56082cd113c32fbcf0b6f20cb6b0",
"skill/tests/test_lifecycle_review.py": "02adc99eec0d612c6e4e70e85dc778b60ced8903d6ff01884f87a815ea5c132f",
"skill/tests/test_review_regressions.py": "574e054005175ce33b4f1eabba135af61bb758372f467d043f209b3c0d326830",
"skill/tests/test_tool_runner.py": "44b52cbd0ab7880ae06958a7707a45875deba9bfdebc0ed1fbff5f6177659ec2",
"skill/tests/test_v1_protocol.py": "e727d725507efdc436829f75f87108fbea75b0b883394bd859cb340abdb8df6f",
"skill/tests/test_v1_upgrade.py": "17403f6835bcb1f2295d195fb2be8b3de94da0b01751b0ce60100ceba3d8ac67",
"skill/scripts/vendor/device_upgrade.py": "b50c12c982a6944a2112e22dfed87c0fdfdc311e6c6ff43a5ce9fa2a726b00ed",
"skill/scripts/vendor/v2_discover.py": "6d0783c2a4f34397b61f4d1a65f97c2300f492caf8c60c38b48c246a75774158"
},
"scope": "Rust 3 preview metadata, independent worker instructions, truthful verification materials"
}
@@ -0,0 +1,11 @@
# Rust 3 测试市场预览
版本 `0.5.0+artemis.7`,宿主范围 `>=3.0.0,<4.0.0`。
基于插件仓库提交 `987b5ff2d8c3516cf87e2f1abcac854732a8ccee` 的 `.6` 源码;所有 Python 运行源码字节保留,仅调整兼容范围、版本、独立解释器说明和验证材料。
静态准入验证覆盖清单、工具声明、组件分发许可、所有材料/文件 SHA256 和确定性 ZIP。
当前预览不宣称 Rust 下真实 MCP、取消收尾、外部工具、设备写操作或 AI 业务验收通过;原 VERIFICATION.md 和 UPSTREAM-MARKET.json 的 2.x 结果不充当 Rust 3 结果。
MARKET.json 对这些项目明确标记 unverified;测试索引以 preview 展示,不是正式审核通过声明。
主程序不依赖 Python;本插件是独立 worker,使用者须自行准备 Python、requirements.txt 和所需外部工具。安装与授权分开,安装不自动启用。
默认操作预算45秒,主程序插件超时至少多10秒。RTSP 秘密只通过受控配置文件读取;固件写入默认关闭,未知结果不得重试。
+2
View File
@@ -1,3 +1,5 @@
> Rust 3 测试市场版本 `0.5.0+artemis.7`;独立 Python/MCP/dotenv 与外部工具由使用者准备。验证边界见 [RUST3-VERIFICATION.md](RUST3-VERIFICATION.md)。下文保留原 2.x 版本的历史范围。
--- ---
name: artemis-device-test name: artemis-device-test
description: 在 Artemis 任务中联合媒体、抓包、串口、V1/V2 设备协议和独立证据包执行家庭安防测试;手机使用本任务的 Android 工具,PC/网站使用已授权电脑控制插件。 description: 在 Artemis 任务中联合媒体、抓包、串口、V1/V2 设备协议和独立证据包执行家庭安防测试;手机使用本任务的 Android 工具,PC/网站使用已授权电脑控制插件。
@@ -0,0 +1,125 @@
{
"schema_version": 1,
"plugin_id": "artemis-device-test",
"plugin_version": "0.5.0+artemis.6",
"content_sha256": "83f19450c4b0bdf4bad9389770e3ab35f81855d4e9f5d2975934e395308aabaf",
"traits": {
"uses_subprocesses": true,
"file_inputs_for_writes": true
},
"components": [
{
"name": "原 Skill、协议资料与用例路由",
"paths": [
"skill"
],
"license": "LicenseRef-Yitong-Artemis-Market",
"permission": "permitted",
"license_file": "LICENSE-ORIGINAL-SKILL.md",
"source_file": "SOURCE.md"
},
{
"name": "ARTEMIS 适配层和适配修复",
"paths": [
"dependency_checks.py",
"dependency_runner.py",
"DEPLOYMENT.md",
"device_runtime.py",
"LICENSE",
"LICENSE-ORIGINAL-SKILL.md",
"network_inventory.py",
"plugin.json",
"README.md",
"RELEASE.md",
"requirements.txt",
"server.py",
"SKILL.md",
"source-manifest.json",
"SOURCE.md",
"VERIFICATION.md",
"skill/scripts/process_lifecycle.py",
"skill/tests/test_lifecycle_review.py",
"skill/tests/test_review_regressions.py",
"skill/ruff.toml"
],
"license": "Apache-2.0",
"permission": "permitted",
"license_file": "LICENSE",
"source_file": "SOURCE.md"
}
],
"checks": {
"installation": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "实际宿主 API 安装、临时目录旧版升级和禁用/配置保留已验证;新装不自动授权。"
},
"tool_contract": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "真实 stdio MCP 握手和全部 20 项定义检查;8 次隔离安全调用、10 份附件;134 项单元回归。"
},
"failure_partial": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "清理核验故障及泛化 I/O 异常保留执行事实;未知写副作用不重试;缺失证据保持 inconclusive。"
},
"timeout_cancel": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "合成工具超时及真实 MCP 自检取消;取消后工具和后代约 0.03 秒退出,未操作真实设备。"
},
"process_tree": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "Windows Job 合成进程验证正常/非零/超时/超限/拥有者退出及清理异常;不声称其他系统通过。"
},
"file_binding": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "固件批准字节保护快照对抗原路径替换/截断/增长;V2 模拟协议;没有真实烧录。"
},
"resource_conflicts": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "临时锁目录验证 desktop:local 和 lab:device-test 各自互斥;运行时阻止固定版本更新和卸载。"
},
"evidence_export": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "正常宿主离线任务导入并校验摘要;8 份附件读回;Word/Excel/全部资料 ZIP 容器与附件字节核验。"
},
"history_uninstall": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "独立临时目录卸载后原历史附件字节仍可读;未删除用户任务或历史。"
},
"secret_redaction": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "合成凭据输出脱敏及源码审查通过;未将个人配置、原始报告、抓包、环境目录打包。"
},
"real_task": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "正常 ARTEMIS Pro/computer 离线证据任务完成,AI 正式结论 passed、5/5;暂停恢复实测;真实设备能力未测。"
},
"source_review": {
"status": "passed",
"evidence_file": "VERIFICATION.md",
"evidence_sha256": "4ca98fe306e266a491636bcd4f1de3fd792e452e765b73d658017ebb4084bf6a",
"notes": "原候选 ZIP 摘要、当前文件来源清单、未改动 vendor 摘要及公司市场分发确认记录已核对;适配层 Apache-2.0。"
}
}
}
+5 -5
View File
@@ -2,9 +2,9 @@
"schema_version": 2, "schema_version": 2,
"id": "artemis-device-test", "id": "artemis-device-test",
"name": "家庭安防多工具测试", "name": "家庭安防多工具测试",
"version": "0.5.0+artemis.6", "version": "0.5.0+artemis.7",
"description": "集成 artemis-device-test Skill:PATH/统一目录自动发现工具及匹配位数的 MediaInfo DLL、按能力独立自检、本机网卡枚举、357 条用例路由、媒体/抓包/串口、V1/V2 设备及联合证据。PC/网站复用视觉插件,手机沿用 Artemis;工具成功不等于用例通过。", "description": "集成 artemis-device-test Skill:PATH/统一目录自动发现工具及匹配位数的 MediaInfo DLL、按能力独立自检、本机网卡枚举、357 条用例路由、媒体/抓包/串口、V1/V2 设备及联合证据。PC/网站复用视觉插件,手机沿用 Artemis;工具成功不等于用例通过。",
"artemis_version": ">=2.0.0,<3.0.0", "artemis_version": ">=3.0.0,<4.0.0",
"platforms": [ "platforms": [
"win32" "win32"
], ],
@@ -29,8 +29,8 @@
}, },
"config": { "config": {
"python": { "python": {
"label": "Artemis Python 解释器", "label": "插件 Python 解释器(独立准备)",
"description": "填写已安装 MCP 依赖的 Python 绝对路径。源码环境可使用项目 .venv/Scripts/python.exe。", "description": "使用独立 Windows x64 Python,预先安装本插件 requirements.txt。Rust 主程序不提供 Python;安装插件不会运行 pip。",
"default": "python", "default": "python",
"required": true "required": true
}, },
@@ -51,7 +51,7 @@
}, },
"operation_timeout_seconds": { "operation_timeout_seconds": {
"label": "单次操作预算(秒)", "label": "单次操作预算(秒)",
"description": "默认 45。10..3300;增加前须将宿主 ARTEMIS_PLUGIN_TOOL_TIMEOUT_SECONDS 设得至少大 10 秒。任务总时限同样生效。", "description": "默认45,范围10..3300;宿主插件工具时限至少大10秒,并受任务剩余预算约束。",
"default": "45" "default": "45"
}, },
"allow_firmware_write": { "allow_firmware_write": {
+6 -10
View File
@@ -1,15 +1,11 @@
# 构建 0.38.0+artemis.1 # 构建 0.38.0+artemis.2
开发机需要 Python、Go、上游固定的 Rust 工具链、Windows C++ x64 工具链及对应 Spectre 运行库。用户安装插件 ZIP 不需要这些工具。不会运行 Cua 全局安装器。 本修订只调整 ARTEMIS 3.x 兼容声明与桥接握手版本,使用 `scripts/package_rust3_compat.py`。构建机需要 Python 3.12+ 标准库、官方 Windows amd64 便携 Go,以及已校验的 `.1` 插件 ZIP;最终用户无需这些开发工具。完整命令见仓库 `docs/rust3-compatibility.md`。
1. 获取官方 `cua-driver-rs-v0.38.0` 源码,核对 HEAD 为 `85e2b1318bd77ccf3cfa88ccdf875a4bb7eaed19`。保留完整源码;稀疏检出至少包括 `libs/cua-driver/rust`、`libs/cua`、`libs/images/sandbox-images.json`。 打包器核对已发布 `0.38.0+artemis.1` ZIP SHA256 为 `c007da16d58c4165a1a344113e650e68bd92a69ad3ce4ca8285818c5426dc0f4`,逐文件比较桥接源码、工具与补丁,仅允许版本常量变化。Driver、UIA helper、SDK DLL 和游标工具直接保留已发布字节;不运行 Cargo、不重新构建或下载底层驱动。
2. 在上游源码根目录应用本插件的 `patches/cua-driver-0.38.0-untitled-shell.patch`。补丁只修改 Windows 精确窗口解析,不放宽普通窗口发现。
3. 下载官方 Windows x64 ZIP。在具备上述编译依赖的终端,从 Artemis-Plugins 根目录执行:
```sh Go 在独立缓存内运行测试、vet,并以 `CGO_ENABLED=0`、`-trimpath`、`-buildvcs=false` 构建新桥接程序。清单、包名、MCP 握手和来源记录统一为 `.2`。原 MIT 许可与 Go 标准库 BSD 许可随包保留。ZIP 条目使用固定顺序、时间与权限,拒绝覆盖既有候选。
python scripts/build_cua_driver.py <官方ZIP> --upstream-source <上游源码根目录> --go <Go可执行文件>
```
构建脚本校验官方 ZIP SHA256、上游 HEAD、唯一修改文件与补丁内容,使用 `cargo build --release --locked` 重新构建 Driver、UIA helper 和 SDK DLL,再运行 Go 测试并构建适配器。仅游标主题工具沿用官方原包二进制。 `provenance.json` 保留固定上游 `85e2b1318bd77ccf3cfa88ccdf875a4bb7eaed19`、官方 ZIP SHA256 和原兼容补丁信息,再记录复用包/二进制、新桥和源码树摘要。未提交源码明确标记 dirty,不能当作已经固定的新提交。`scripts/build_cua_driver.py` 仍是完整上游重建入口,本轮没有运行它。
`provenance.json` 区分官方 ZIP 来源、兼容补丁及重新构建文件,记录各自 SHA256。打包后先运行确定性测试及授权的隔离窗口回归,再安装到本机或发布。市场索引与发布附件需另行更新,不由构建脚本自动修改。 打包与 ZIP 回读不代表 Rust 宿主联调完成;当前范围见 [VERIFICATION.md](VERIFICATION.md)。不自动安装、授权、推送或发布。
+7 -1
View File
@@ -1,4 +1,10 @@
# Cua 电脑控制 0.38.0+artemis.1 # Cua 电脑控制 0.38.0+artemis.2(3.0 候选)
2026-11-01:新增 ARTEMIS `>=3.0.0,<4.0.0` 专用不可变包。适配器仅更新版本常量,清单/MCP 握手统一为 `.2`;工具、窗口身份、坐标、焦点、回执和子进程行为保持 `.1` 实现。Driver、UIA、SDK DLL 和游标工具原样复用已校验的 `.1` 发行文件,不声称本次重新编译底层 Rust 运行库。来源细节见 `provenance.json` 与 [BUILD.md](BUILD.md)。
用户运行插件不需要 Python、Go、Rust 或 Node。现有 Windows x64、`desktop:local` 与 computer/mixed 范围不变;保留原许可和录制能力声明,但本轮不扩展或测试录像。旧 `.1` 包、发行附件及旧索引 `<3.0.0` 限制保留。新 `catalog-v5.json` 是待发布输入,实际宿主联调由主项目统一验证,不将以下 2.x 记录当作本次 3.0 结果。
## 固定运行库的历史来源:0.38.0+artemis.1
2026-10-27,与 ARTEMIS 2.2.0 配套发布。同步官方 [Cua Driver 0.38.0](https://github.com/trycua/cua/releases/tag/cua-driver-rs-v0.38.0),替换原 0.36.0 驱动。插件仍为 Windows x64 开发预览。既有任务与旧安装包保留原版本,主程序升级不会自动更新或授权插件;当前任务结束后在插件市场单独更新。 2026-10-27,与 ARTEMIS 2.2.0 配套发布。同步官方 [Cua Driver 0.38.0](https://github.com/trycua/cua/releases/tag/cua-driver-rs-v0.38.0),替换原 0.36.0 驱动。插件仍为 Windows x64 开发预览。既有任务与旧安装包保留原版本,主程序升级不会自动更新或授权插件;当前任务结束后在插件市场单独更新。
+8
View File
@@ -1,5 +1,13 @@
# Cua 验证记录 # Cua 验证记录
## 0.38.0+artemis.2 / ARTEMIS 3.x 候选(2026-11-01)
仅变更宿主版本约束与 Go 桥接版本常量,工具定义和动作实现保留。打包器要求 Go test/vet/build 成功,核对 `.1` 原包 SHA256、源码差异、固定运行库与兼容补丁,再逐条回读最终 ZIP;实际执行凭据见候选目录 `build-receipt.json`。底层 Driver/UIA/SDK/游标工具复用已发布字节,没有在本轮重新构建。
本次没有运行 Rust 宿主握手/发现、真实截图/动作、页面、设备、录像、业务或性能测试。3.0 安装、授权与正常任务联调由主项目统一验证,用户在功能完成后集中验收页面。候选没有推送或发布。
以下为旧宿主和旧插件的历史记录,只说明复用实现的来源,不作为 3.0 测试结果。
## 0.38.0+artemis.1 发布核对(2026-10-27) ## 0.38.0+artemis.1 发布核对(2026-10-27)
重新执行 Go 测试、vet 和构建,核对最终包内源码、兼容补丁及运行库 SHA256。10 月 26 日主程序集中回归中,此插件参与仅 PC 输入、双击、清空和两轮 PC 到 Android 的校验码传递,均有两端独立证据;该组正式任务共 9 项通过,另有准备探索超出指定页面范围而失败,未计入通过数。模型统一 gpt-6-luna/high,未将历史 OCU 对照性能数据作为此次发行性能结论。 重新执行 Go 测试、vet 和构建,核对最终包内源码、兼容补丁及运行库 SHA256。10 月 26 日主程序集中回归中,此插件参与仅 PC 输入、双击、清空和两轮 PC 到 Android 的校验码传递,均有两端独立证据;该组正式任务共 9 项通过,另有准备探索超出指定页面范围而失败,未计入通过数。模型统一 gpt-6-luna/high,未将历史 OCU 对照性能数据作为此次发行性能结论。
+1 -1
View File
@@ -25,7 +25,7 @@ import (
//go:embed tools.json //go:embed tools.json
var toolsJSON []byte var toolsJSON []byte
const adapterVersion = "0.38.0+artemis.1" const adapterVersion = "0.38.0+artemis.2"
type obj = map[string]any type obj = map[string]any
type request struct { type request struct {
+2 -2
View File
@@ -2,9 +2,9 @@
"schema_version": 2, "schema_version": 2,
"id": "cua-driver", "id": "cua-driver",
"name": "电脑控制 · Cua", "name": "电脑控制 · Cua",
"version": "0.38.0+artemis.1", "version": "0.38.0+artemis.2",
"description": "通过 Cua Driver 的窗口截图与可访问性树操作 Windows 应用及浏览器窗口;复用 ARTEMIS 的任务调度、步骤证据与录像。", "description": "通过 Cua Driver 的窗口截图与可访问性树操作 Windows 应用及浏览器窗口;复用 ARTEMIS 的任务调度、步骤证据与录像。",
"artemis_version": ">=2.0.0,<3.0.0", "artemis_version": ">=3.0.0,<4.0.0",
"platforms": [ "platforms": [
"win32" "win32"
], ],
+6 -4
View File
@@ -1,6 +1,6 @@
# 任务人工评审 1.1.0 # 任务人工评审 1.1.1
需要 ARTEMIS 2.4.0 或更高的 2.x 版本。升级主程序后,可在「插件 → 高级接入」导入本目录或发行 ZIP。2.2.x 不支持此接口。 本候选面向 ARTEMIS 3.x(`>=3.0.0,<4.0.0`),使用原有 schema 3 声明,不含 MCP、Python、脚本或运行进程。2.x 用户继续使用旧索引中的 1.1.0,不修改旧包兼容范围。
安装并启用后,下载的批量模板在原七列后附加“预置条件、操作步骤、预期结果”;也兼容旧七列表格。未启用插件时模板和校验仍保持七列。新增三列不进入 AI 提示词。 安装并启用后,下载的批量模板在原七列后附加“预置条件、操作步骤、预期结果”;也兼容旧七列表格。未启用插件时模板和校验仍保持七列。新增三列不进入 AI 提示词。
@@ -10,8 +10,10 @@
`workspace.panels[].width` 声明首选、最小和最大宽度(CSS 像素);宿主根据播放器和屏幕空间调整布局,窄屏可低于声明的最小值以避免溢出。默认收起、左右独立展开;窄屏一次展开一侧;单侧展开始终保持在回放左边或右边。 `workspace.panels[].width` 声明首选、最小和最大宽度(CSS 像素);宿主根据播放器和屏幕空间调整布局,窄屏可低于声明的最小值以避免溢出。默认收起、左右独立展开;窄屏一次展开一侧;单侧展开始终保持在回放左边或右边。
在仓库根目录运行 `python scripts/build_task_review.py` 生成安装 ZIP 和 SHA256SUMS.txt。产物默认在 `artifacts/task-review/`,不提交构建产物。发行标签为 `task-review-v1.1.0`,安装包及 SHA256 以 catalog-v4.json 为准。 3.0 联合候选使用 `python scripts/package_rust3_compat.py --help` 所示入口,输出新 ZIP、SHA256 与未发布的 `catalog-v5.json`,详见仓库 `docs/rust3-compatibility.md`。独立声明式打包仍可运行 `python scripts/build_task_review.py --output <新的输出目录>`。计划发行标签为 `task-review-v1.1.1`;当前未发布,不能将索引中的计划 URL 当作已有附件。
回放标题栏的侧栏按钮控制左右展开;两项评审使用「通过 / 失败」分段按钮,再次点击已选项可清空。导出保留相同中文值。 回放标题栏的侧栏按钮控制左右展开;两项评审使用「通过 / 失败」分段按钮,再次点击已选项可清空。导出保留相同中文值。
Flash/Pro/探索及安卓、电脑、跨端任务均可显示面板,步骤和视频模式均支持。右侧上一条/下一条按同批次已结束任务的执行顺序切换;独立任务使用当前历史列表。上一条/下一条位于保存评审下方。选择结果后立即自动保存,备注停止输入 600 毫秒后保存;切换前先保存,失败或冲突时保留当前任务和输入,手动保存可重试。打开或读取任务不触发保存,面板展开状态保持。备注框缩为一行起始高度,可手动拉高。原字段 ID 不变,1.0.0 评审继续读取。 Flash/Pro/探索及安卓、电脑、跨端任务均可显示面板,步骤和视频模式均支持。右侧上一条/下一条按同批次已结束任务的执行顺序切换;独立任务使用当前历史列表。上一条/下一条位于保存评审下方。选择结果后立即自动保存,备注停止输入 600 毫秒后保存;切换前先保存,失败或冲突时保留当前任务和输入,手动保存可重试。打开或读取任务不触发保存,面板展开状态保持。备注框缩为一行起始高度,可手动拉高。四个稳定字段 ID 不变,3.x 同一原生数据库中的评审由宿主保留。
以上是插件声明与宿主应提供的工作区行为。3.0 实际页面、自动保存、导航及报告导出由主项目统一验收;本候选不把 2.4 的历史验证记为 3.0 已通过。不会导入 Python 历史数据。
+4 -8
View File
@@ -1,11 +1,7 @@
# 任务人工评审 1.1.0 # 任务人工评审 1.1.1(3.0 候选)
需要 ARTEMIS 2.4.0+(2.x)。先更新宿主并重启,再通过「插件 → 高级接入」导入 task-review-1.1.0.zip 并启用。 使用新的不可变安装包支持 ARTEMIS `>=3.0.0,<4.0.0`。清单仍为 schema 3,原始用例/评审面板、四个字段 ID、中文选项、选填属性和报告声明保持不变,无额外运行依赖。
- 启用插件后,批量模板附加预置条件、操作步骤、预期结果。未启用时仍是七列;新增列不进入模型提示词。 旧 `task-review-v1.1.0` 附件与 `catalog-v4.json` 的 `<3.0.0` 限制继续保留,未通过修改旧索引宣称旧包兼容。新包仅进入 `catalog-v5.json`,安装和启用由用户操作,不自动迁移 Python 历史数据。
- 左侧展示原始用例,用例编号和标题置顶;历史缺失字段明确提示。
- 「AI执行准确性」改为「用例执行准确性」,保留字段 ID 和旧评审值。
- 备注框缩短;增加上一条、下一条,按钮位于保存评审下方;用户修改后自动保存,切换前保存,失败留在当前任务,连续修改不会被旧回执覆盖。
- 面板适用于所有执行模式、设备目标及步骤/视频视图。
发行标签为 task-review-v1.1.0。验证范围与结果见 [验证记录](VERIFICATION.md)。 计划标签 `task-review-v1.1.1`,当前是未发布候选。声明式接口已按 3.0 宿主协议准备,实际页面、保存/导航及报告导出待主项目本轮验证,见 [验证记录](VERIFICATION.md)。
+5 -36
View File
@@ -1,40 +1,9 @@
# 任务人工评审 1.1.0 验证 # 任务人工评审 1.1.1 / ARTEMIS 3.x 候选验证
日期:2026-10-28;宿主 ARTEMIS 2.4.0。本轮在 Windows 隔离环境完成。 日期:2026-11-01。清单为 schema 3,兼容范围 `>=3.0.0,<4.0.0`。保留原始用例、评审面板,以及 execution_accuracy、execution_note、result_correctness、result_note 四个字段,不新增运行进程、MCP、模型或设备能力。
- 宿主发布前最终默认确定性测试:3267 passed、2 skipped、8 deselected。 候选打包器检查版本、兼容范围、面板、稳定字段 ID、无 MCP 进程和零工具声明;产出新 ZIP 后逐条回读,记录最终 SHA256 和嵌入索引的清单一致性。实际执行结果以同目录 `build-receipt.json` 为准。
- 前端 ChromeHeadless:347 项通过;生产构建通过。
- 宿主 Ruff 格式/静态检查、质量基线、核心 Pyright、Git diff 空白检查通过。
- 新旧导入模板及插件启用门控、原始用例与 AI 提示词隔离、批次评审顺序通过。
- 结果选择立即自动保存、备注 600ms 防抖、清空、连续修改串行修订、旧回执与旧读取不覆盖新内容、关闭前保存、切换前保存、失败留在当前项通过。
- Windows Chrome 实际浏览器隔离夹具验证:视频视图中原始用例与评审面板;导航位于保存下方;自动保存、切换往返、宽屏/窄屏、深浅色;未出现页面脚本异常。
- 清单通过宿主 check_plugin.py 检查,字段 ID 保持不变。
- 2.4.0 通过独立 catalog-v4.json 获取 1.1.0;保留 catalog-v3.json 的 1.0.0,使用 2.3.0 的严格面板模型验证旧索引仍可解析。官方旧地址迁移与自定义地址保留的回归通过。
浏览器使用拦截 API 的示例数据,未填写真实任务的人工评审,未操作用户设备或调用模型。未做 macOS/Linux 实测,未运行原 192 项批次或高并发设备压力测试。 Rust 宿主提供声明式表单接口。实际页面、自动保存、清空、冲突处理、导航及 Word/Excel/资料包报告待主项目 3.0 本轮统一验证;本次未操作页面、设备或业务任务,也未做性能测试。人工结果仍只能由用户填写,不改变模型原判或任务状态。
以下为旧版历史记录,不能作为本轮验证结果。 1.1.0 在 ARTEMIS 2.4.0 的历史验证属于旧宿主,不作为 3.0 通过证据。旧发行包、标签及 catalog-v4.json 的 `<3.0.0` 约束保留;本次不迁移或兼容 Python 历史数据库。当前候选未推送、未发布。
---
# 任务人工评审 1.0.0 验证记录
日期:2026-10-27。发布目标:task-review-v1.0.0。
宿主:ARTEMIS 2.3.0。插件基于独立插件仓库 b780a60,未夹带其他未发布插件变更;清单最低宿主版本为 2.3.0,2.2.x 不支持。
## 已验证
- 宿主默认确定性测试:3197 passed,2 skipped,8 deselected。
- 前端 ChromeHeadless:336 项通过;生产构建通过。
- 宿主 Ruff 格式/静态检查、质量基线、核心 Pyright 通过。
- 清单和字段验证、仅启用插件贡献字段、纯界面插件不参与执行选择。
- 原始任务提示词 Markdown、部分填写、清空、失败重试保留输入、修订冲突、历史记录、切换任务不串写。
- 运行中只读,终态可评审;禁用/卸载隐藏贡献,重装恢复,删除任务/批次清理;复跑不继承。
- Word/Excel 单任务和批次汇总、ZIP 报告包含已启用贡献,未填为空;评审和插件状态变更使旧缓存失效。
- 使用生产前端与隔离示例任务进行浏览器验证:标题栏左右开关、普通浮窗/影院模式、浅色/深色、单侧在左右展开;600/740px 容器左右布局的浏览器回归通过。
- 实际通过网页保存中文「通过」,生成 Word/Excel 并确认值为中文,另一未填结果为空。
## 范围
验证使用 Windows 本地环境与临时数据库,不操作用户设备、业务应用或现有任务,不调用模型。未在 macOS/Linux 实测,不将此记录视为真实业务用例通过或市场发布证明。此插件无运行进程和 MCP,因此不需要设备动作、取消进程或录像验收。
+2 -2
View File
@@ -2,9 +2,9 @@
"schema_version": 3, "schema_version": 3,
"id": "task-review", "id": "task-review",
"name": "任务人工评审", "name": "任务人工评审",
"version": "1.1.0", "version": "1.1.1",
"description": "在任务回放中查看导入的原始用例,逐条填写用例执行准确性与测试结果正确性,按需填写备注并随报告导出。", "description": "在任务回放中查看导入的原始用例,逐条填写用例执行准确性与测试结果正确性,按需填写备注并随报告导出。",
"artemis_version": ">=2.4.0,<3.0.0", "artemis_version": ">=3.0.0,<4.0.0",
"platforms": [ "platforms": [
"win32", "win32",
"linux", "linux",
+627
View File
@@ -0,0 +1,627 @@
"""Assemble immutable Rust 3 plugin candidates from reviewed local inputs.
Only the small, dependency-free Go adapter is rebuilt. This script never runs
Cargo, npm, a Cua session, a device tool, an installer, or a network download.
"""
import argparse
import hashlib
import io
import json
import os
import platform
import re
import stat
import struct
import subprocess
import sys
import zipfile
from pathlib import Path, PurePosixPath
from urllib.parse import quote
ROOT = Path(__file__).resolve().parents[1]
CUA = ROOT / "plugins/cua-driver"
REVIEW = ROOT / "plugins/task-review"
DEVICE_TEST = ROOT / "plugins/artemis-device-test"
BASE_VERSION = "0.38.0+artemis.1"
CUA_VERSION = "0.38.0+artemis.2"
REVIEW_VERSION = "1.1.1"
DEVICE_TEST_VERSION = "0.5.0+artemis.7"
DEFAULT_RELEASE_TAG = "rust3-plugin-preview-20261101"
HOST_RANGE = ">=3.0.0,<4.0.0"
BASE_SHA256 = "c007da16d58c4165a1a344113e650e68bd92a69ad3ce4ca8285818c5426dc0f4"
RELEASES = "https://git.playones.com/chenmingxuan/Artemis-Plugins/releases/download"
BASE_URL = (
f"{RELEASES}/cua-driver-v{BASE_VERSION}/cua-driver-{BASE_VERSION}-windows-amd64.zip"
)
RUNTIME = (
"cua-driver.exe",
"cua-driver-uia.exe",
"cua_driver_sdk.dll",
"cua-cursor-theme.exe",
)
ZIP_DATE = (2026, 11, 1, 0, 0, 0)
def sha256(data):
return hashlib.sha256(data).hexdigest()
def encoded(value):
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
def read_json(path):
return json.loads(path.read_text(encoding="utf-8-sig"))
def require(condition, message):
if not condition:
raise ValueError(message)
def file_bytes(path):
require(
path.is_file() and not path.is_symlink(),
f"Missing or linked input: {path.name}",
)
return path.read_bytes()
def x64(data, name):
require(len(data) >= 64 and data[:2] == b"MZ", f"Not a PE binary: {name}")
offset = struct.unpack_from("<I", data, 0x3C)[0]
require(
offset + 6 <= len(data) and data[offset : offset + 4] == b"PE\0\0",
f"Invalid PE: {name}",
)
require(
struct.unpack_from("<H", data, offset + 4)[0] == 0x8664,
f"Not Windows x64: {name}",
)
def exclusive(path, data):
with path.open("xb") as destination:
destination.write(data)
def write_zip(path, entries):
require(not path.exists(), f"Refusing to overwrite an archive: {path.name}")
with zipfile.ZipFile(path, "x", compression=zipfile.ZIP_DEFLATED) as archive:
for name, data in sorted(entries.items()):
parts = PurePosixPath(name)
require(
not parts.is_absolute()
and ".." not in parts.parts
and "\\" not in name,
"Unsafe archive entry",
)
info = zipfile.ZipInfo(name, date_time=ZIP_DATE)
info.create_system = 3
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = 0o100644 << 16
archive.writestr(info, data)
with zipfile.ZipFile(path) as archive:
require(
archive.testzip() is None and set(archive.namelist()) == set(entries),
"Archive readback failed",
)
for name, data in entries.items():
require(
sha256(archive.read(name)) == sha256(data),
f"Archive input differs: {name}",
)
return sha256(path.read_bytes())
def source_files(directory):
files = sorted(directory.glob("*.go"))
files += [directory / "go.mod", directory / "tools.json"]
files += sorted((directory / "patches").glob("*.patch"))
return {path.relative_to(directory).as_posix(): file_bytes(path) for path in files}
def source_digest(files):
return sha256(encoded({name: sha256(data) for name, data in sorted(files.items())}))
def release_url(tag, filename):
require(
isinstance(tag, str)
and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._+-]{0,127}", tag),
"Release tag must be a single safe tag name",
)
require(PurePosixPath(filename).name == filename, "Invalid release filename")
return f"{RELEASES}/{quote(tag, safe='')}/{quote(filename, safe='-._+')}"
def device_test_files(directory):
"""Freeze distributable bytes; the root SDK preflight owns market admission."""
require(
not directory.is_symlink() and not directory.is_junction(), "Linked plugin root"
)
directory = directory.resolve(strict=True)
require(directory.is_dir(), "Device-test source must be a directory")
entries, seen, total = {}, set(), 0
forbidden = {
"__pycache__",
".git",
"node_modules",
".venv",
"venv",
".pytest_cache",
".ruff_cache",
}
for path in sorted(directory.rglob("*")):
name = path.relative_to(directory).as_posix()
require(
not path.is_symlink()
and not path.is_junction()
and path.resolve().is_relative_to(directory),
f"Linked device-test input: {name}",
)
require(name.casefold() not in seen, "Case-colliding device-test inputs")
seen.add(name.casefold())
require(len(seen) <= 20000, "Too many device-test inputs")
require(
not any(
part.casefold() in forbidden
for part in path.relative_to(directory).parts
),
f"Non-distributable device-test input: {name}",
)
if path.is_dir():
continue
lower = path.name.casefold()
require(
stat.S_ISREG(path.stat().st_mode)
and not lower.endswith((".pyc", ".pyo"))
and lower != ".env"
and not (
lower.startswith(".env.")
and not lower.endswith((".example", ".sample", ".template"))
),
f"Non-distributable device-test file: {name}",
)
data = file_bytes(path)
total += len(data)
require(total <= 512 * 1024 * 1024, "Device-test source exceeds size limit")
entries[name] = data
for name in (
"plugin.json",
"LICENSE",
"SOURCE.md",
"MARKET.json",
"VERIFICATION.md",
):
require(
bool(entries.get(name)),
f"Missing device-test distribution material: {name}",
)
manifest = json.loads(entries["plugin.json"])
require(
manifest["id"] == "artemis-device-test"
and manifest["version"] == DEVICE_TEST_VERSION
and manifest["artemis_version"] == HOST_RANGE,
"Unexpected device-test identity or compatibility",
)
require(bool(manifest.get("license", "").strip()), "Missing device-test license")
market = json.loads(entries["MARKET.json"])
hashes = {
name: sha256(data) for name, data in entries.items() if name != "MARKET.json"
}
fingerprint = sha256(
json.dumps(
hashes, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
)
require(
market["plugin_id"] == manifest["id"]
and market["plugin_version"] == manifest["version"]
and market["content_sha256"] == fingerprint,
"Device-test MARKET identity or content hash differs; rerun SDK preflight",
)
return manifest, entries
def bundle_map(entries, filenames, catalog):
return {
"schema_version": 1,
"catalog": "catalog-v5.json",
"catalog_sha256": sha256(catalog),
"packages": [
{
"id": entry["manifest"]["id"],
"version": entry["manifest"]["version"],
"file": filename,
"archive_url": entry["archive_url"],
"sha256": entry["sha256"],
}
for entry, filename in zip(entries, filenames, strict=True)
],
}
def check_contract(cua, review, sources):
require(
cua["id"] == "cua-driver" and cua["version"] == CUA_VERSION,
"Unexpected Cua version",
)
require(
review["id"] == "task-review" and review["version"] == REVIEW_VERSION,
"Unexpected review version",
)
require(
cua["artemis_version"] == review["artemis_version"] == HOST_RANGE,
"Host compatibility differs",
)
require(
cua["server"]
== {
"transport": "stdio",
"command": "${plugin_dir}/bin/artemis-cua-adapter.exe",
},
"Unexpected Cua startup",
)
require(
cua["resources"] == ["desktop:local"]
and cua["targets"] == ["computer", "mixed"],
"Cua resource scope differs",
)
require(
re.search(
rb'const adapterVersion = "' + re.escape(CUA_VERSION.encode()) + rb'"',
sources["main.go"],
),
"MCP handshake version differs",
)
tools = json.loads(sources["tools.json"])
actual = {tool["name"]: tool for tool in tools}
declared = cua["capabilities"]["tools"]
require(
len(actual) == len(tools) and set(actual) == set(declared),
"Tool manifest differs from embedded catalog",
)
for name, spec in declared.items():
tool = actual[name]
require(
not spec.get("observation")
or tool.get("annotations", {}).get("readOnlyHint") is True,
"Observation is not read-only",
)
target = spec.get("target_argument")
require(
not target or target in tool["inputSchema"].get("properties", {}),
"Target argument is absent",
)
require(
review["schema_version"] == 3 and review["targets"] == [],
"Review must remain declarative",
)
require(
not review.get("server")
and not review.get("skill")
and not review["capabilities"]["tools"],
"Review must not start a process",
)
require(
{p["kind"] for p in review["workspace"]["panels"]} == {"original_case", "form"},
"Review panels differ",
)
require(
[f["id"] for f in review["workspace"]["fields"]]
== [
"execution_accuracy",
"execution_note",
"result_correctness",
"result_note",
],
"Review field IDs differ",
)
def base_runtime(path, sources):
data = file_bytes(path)
require(sha256(data) == BASE_SHA256, "Published Cua base archive hash mismatch")
with zipfile.ZipFile(io.BytesIO(data)) as base:
names = base.namelist()
require(len(names) == len(set(names)), "Duplicate base archive entries")
require(
all(
not PurePosixPath(n).is_absolute()
and ".." not in PurePosixPath(n).parts
and "\\" not in n
for n in names
),
"Unsafe base archive path",
)
manifest = json.loads(base.read("plugin.json"))
require(
manifest["version"] == BASE_VERSION and manifest["id"] == "cua-driver",
"Wrong published base version",
)
for name, content in sources.items():
previous = base.read("source/" + name).replace(b"\r\n", b"\n")
expected = content.replace(b"\r\n", b"\n")
if name == "main.go":
expected = expected.replace(CUA_VERSION.encode(), BASE_VERSION.encode())
require(
previous == expected,
f"Go behavior differs from the reviewed base: {name}",
)
runtime = {"bin/" + name: base.read("bin/" + name) for name in RUNTIME}
provenance = json.loads(base.read("provenance.json"))
require(
provenance["plugin_version"] == BASE_VERSION,
"Published provenance version differs",
)
for name, expected in provenance["compatibility_patch"][
"rebuilt_binaries"
].items():
require(
sha256(runtime["bin/" + name]) == expected,
"Published runtime differs from provenance",
)
license_text = base.read("LICENSE.upstream")
require(
license_text == file_bytes(CUA / "LICENSE.upstream"),
"Upstream license changed",
)
for name, binary in runtime.items():
x64(binary, name)
return runtime, provenance
def build_adapter(go, output):
build = output / ".build"
env = {
**os.environ,
"CGO_ENABLED": "0",
"GOOS": "windows",
"GOARCH": "amd64",
"GOTOOLCHAIN": "local",
"GOENV": "off",
"GOPROXY": "off",
"GOSUMDB": "off",
"GOTELEMETRY": "off",
"GOFLAGS": "-mod=readonly",
}
for key, name in (
("GOCACHE", "cache"),
("GOMODCACHE", "modules"),
("GOPATH", "path"),
("GOTMPDIR", "temp"),
):
folder = build / name
folder.mkdir(parents=True, exist_ok=True)
env[key] = str(folder)
version = subprocess.check_output(
[str(go), "version"], env=env, text=True, timeout=30
).strip()
require(
re.fullmatch(r"go version go[0-9.]+ windows/amd64", version),
"Expected native, stable Windows amd64 Go",
)
commands = (
["test", "-count=1", "./..."],
["vet", "./..."],
[
"build",
"-trimpath",
"-buildvcs=false",
"-ldflags=-s -w",
"-o",
str(build / "artemis-cua-adapter.exe"),
".",
],
)
for command in commands:
subprocess.run([str(go), *command], cwd=CUA, env=env, check=True, timeout=300)
binary = file_bytes(build / "artemis-cua-adapter.exe")
x64(binary, "artemis-cua-adapter.exe")
go_license = file_bytes(go.parent.parent / "LICENSE")
return binary, go_license, version
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--cua-base-archive", type=Path, required=True)
parser.add_argument("--go", type=Path, required=True)
parser.add_argument(
"--output",
type=Path,
required=True,
help="New empty candidate directory; never overwrite an earlier candidate",
)
parser.add_argument("--catalog-output", type=Path, default=ROOT / "catalog-v5.json")
parser.add_argument("--device-test-source", type=Path, default=DEVICE_TEST)
parser.add_argument("--release-tag", default=DEFAULT_RELEASE_TAG)
args = parser.parse_args(argv)
release_url(args.release_tag, "preflight.zip")
output = args.output.resolve()
require(
not output.exists() or not any(output.iterdir()),
"Candidate output must be new or empty",
)
require(
not args.catalog_output.exists(),
"Refusing to overwrite catalog-v5; use a new --catalog-output for a later candidate",
)
cua, review = read_json(CUA / "plugin.json"), read_json(REVIEW / "plugin.json")
sources = source_files(CUA)
check_contract(cua, review, sources)
device_test, device_entries = device_test_files(args.device_test_source)
runtime, previous = base_runtime(args.cua_base_archive.resolve(), sources)
output.mkdir(parents=True, exist_ok=True)
adapter, go_license, go_version = build_adapter(args.go.resolve(), output)
require(source_files(CUA) == sources, "Adapter source changed during build")
require(
device_test_files(args.device_test_source) == (device_test, device_entries),
"Device-test source changed during build",
)
head = subprocess.check_output(
["git", "rev-parse", "HEAD"], cwd=ROOT, text=True
).strip()
dirty = bool(
subprocess.check_output(
["git", "status", "--porcelain=v1", "--untracked-files=normal"], cwd=ROOT
)
)
provenance = dict(previous)
provenance.update(
{
"plugin_version": CUA_VERSION,
"adapter_revision": 2,
"host_compatibility": HOST_RANGE,
"runtime_reuse": {
"plugin_version": BASE_VERSION,
"archive_url": BASE_URL,
"archive_sha256": BASE_SHA256,
"binaries": {name: sha256(data) for name, data in runtime.items()},
},
"adapter_build": {
"go_version": go_version,
"cgo_enabled": False,
"sha256": sha256(adapter),
"source_files_sha256": source_digest(sources),
"checkout_commit_at_build": head,
"checkout_dirty": dirty,
"fixed_source_commit_claimed": False,
},
"verification_scope": "Go test/vet/build and archive contracts only. No Rust host handshake, UI, recording, device, business or performance validation in this build.",
}
)
cua_entries = dict(runtime)
cua_entries.update(
{
"bin/artemis-cua-adapter.exe": adapter,
"LICENSE.Go": go_license,
"provenance.json": encoded(provenance),
}
)
for name in (
"plugin.json",
"SKILL.md",
"LICENSE.upstream",
"RELEASE.md",
"VERIFICATION.md",
"BUILD.md",
):
cua_entries[name] = file_bytes(CUA / name)
cua_entries.update({"source/" + name: data for name, data in sources.items()})
review_entries = {
name: file_bytes(REVIEW / name)
for name in (
"plugin.json",
"README.md",
"LICENSE",
"VERIFICATION.md",
"RELEASE.md",
)
}
require(
json.loads(cua_entries["plugin.json"]) == cua
and json.loads(review_entries["plugin.json"]) == review,
"Manifest changed during build",
)
cua_name = f"cua-driver-{CUA_VERSION}-windows-amd64.zip"
review_name = f"task-review-{REVIEW_VERSION}.zip"
device_name = f"artemis-device-test-{DEVICE_TEST_VERSION}-windows-amd64.zip"
cua_sha = write_zip(output / cua_name, cua_entries)
review_sha = write_zip(output / review_name, review_entries)
device_sha = write_zip(output / device_name, device_entries)
sums = f"{cua_sha} {cua_name}\n{review_sha} {review_name}\n{device_sha} {device_name}\n".encode(
"ascii"
)
exclusive(output / "SHA256SUMS.txt", sums)
entries = [
{
"manifest": cua,
"release_stage": "preview",
"archive_url": release_url(args.release_tag, cua_name),
"sha256": cua_sha,
"verification": "Windows x64 / ARTEMIS 3.x 候选。Go 确定性回归、vet、桥接构建和最终 ZIP 输入/回读校验通过;Driver/UIA/SDK/游标工具原样复用已发布 0.38.0+artemis.1 的固定文件与来源。3.0 实际握手、窗口发现、截图、动作、录像及业务任务待宿主统一验收;未进行性能测试。",
},
{
"manifest": review,
"release_stage": "preview",
"archive_url": release_url(args.release_tag, review_name),
"sha256": review_sha,
"verification": "ARTEMIS 3.x 声明式工作区候选,无运行进程或额外依赖。清单字段/面板及最终 ZIP 回读校验通过;实际界面、自动保存、导航与报告导出待 3.0 宿主统一验收,不沿用 2.4 的历史测试结论。",
},
{
"manifest": device_test,
"release_stage": "preview",
"archive_url": release_url(args.release_tag, device_name),
"sha256": device_sha,
"verification": "ARTEMIS 3.x Windows x64 候选;独立 Python/MCP/dotenv 与外部工具由使用者准备。固定源码、MARKET 内容摘要和最终 ZIP 回读校验;完整许可及证据准入由 SDK 独立预检。Rust 安装、握手、取消、设备与业务验收范围以包内验证记录为准,不沿用旧宿主结论。",
},
]
catalog = encoded({"schema_version": 1, "plugins": entries})
exclusive(output / "catalog-v5.candidate.json", catalog)
exclusive(output / "catalog-v5.json", catalog)
mapping = encoded(
bundle_map(entries, [cua_name, review_name, device_name], catalog)
)
exclusive(output / "bundle-map.json", mapping)
exclusive(args.catalog_output.resolve(), catalog)
receipt = {
"schema_version": 1,
"candidate_only": True,
"published": False,
"release_tag": args.release_tag,
"python_version": platform.python_version(),
"go_version": go_version,
"source_commit_at_build": head,
"source_dirty": dirty,
"adapter_source_sha256": source_digest(sources),
"device_test_source_sha256": source_digest(device_entries),
"base_archive_sha256": BASE_SHA256,
"archives": [
{"file": cua_name, "sha256": cua_sha},
{"file": review_name, "sha256": review_sha},
{"file": device_name, "sha256": device_sha},
],
"catalog_sha256": sha256(catalog),
"bundle_map_sha256": sha256(mapping),
"checks": [
"go test -count=1 ./...",
"go vet ./...",
"go build",
"manifest/tools/source/runtime contract",
"final ZIP readback",
"device-test MARKET identity/content hash (SDK admission is separate)",
],
"not_run": [
"Cargo",
"npm",
"Rust host handshake",
"device/UI/recording/business/performance tests",
],
}
exclusive(output / "build-receipt.json", encoded(receipt))
print(
json.dumps(
{
"candidate_directory": str(output),
"archives": receipt["archives"],
"catalog": str(args.catalog_output.resolve()),
"published": False,
},
ensure_ascii=False,
)
)
if __name__ == "__main__":
try:
main()
except (
ValueError,
OSError,
subprocess.SubprocessError,
zipfile.BadZipFile,
KeyError,
) as error:
sys.exit(f"Candidate assembly failed: {error}")
+215
View File
@@ -0,0 +1,215 @@
"""Offline packager regressions; Go compilation and SDK admission are separate."""
import importlib.util
import json
import tempfile
import unittest
import zipfile
from pathlib import Path
from unittest.mock import patch
SPEC = importlib.util.spec_from_file_location(
"package_rust3_compat",
Path(__file__).resolve().parents[1] / "scripts/package_rust3_compat.py",
)
packager = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(packager)
class Rust3PackagerTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
self.plugin = self.root / "device-test"
self.plugin.mkdir()
self.manifest = {
"schema_version": 2,
"id": "artemis-device-test",
"version": packager.DEVICE_TEST_VERSION,
"artemis_version": packager.HOST_RANGE,
"license": "Apache-2.0",
}
(self.plugin / "plugin.json").write_bytes(packager.encoded(self.manifest))
for name in ("LICENSE", "SOURCE.md", "VERIFICATION.md", "server.py"):
(self.plugin / name).write_bytes((name + "\n").encode())
self.update_market()
def update_market(self):
hashes = {
path.relative_to(self.plugin).as_posix(): packager.sha256(path.read_bytes())
for path in self.plugin.rglob("*")
if path.is_file() and path.name != "MARKET.json"
}
fingerprint = packager.sha256(
json.dumps(
hashes, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode()
)
(self.plugin / "MARKET.json").write_bytes(
packager.encoded(
{
"plugin_id": self.manifest["id"],
"plugin_version": self.manifest["version"],
"content_sha256": fingerprint,
}
)
)
def assemble(self, name, tag=packager.DEFAULT_RELEASE_TAG):
output = self.root / name
catalog = self.root / (name + "-catalog.json")
with (
patch.object(
packager,
"base_runtime",
return_value=({}, {"plugin_version": packager.BASE_VERSION}),
),
patch.object(
packager,
"build_adapter",
return_value=(b"adapter fixture", b"Go license fixture", "go fixture"),
),
patch.object(
packager.subprocess, "check_output", side_effect=["f" * 40, b""]
),
patch("builtins.print"),
):
packager.main(
[
"--cua-base-archive",
str(self.root / "base.zip"),
"--go",
str(self.root / "go.exe"),
"--output",
str(output),
"--catalog-output",
str(catalog),
"--device-test-source",
str(self.plugin),
"--release-tag",
tag,
]
)
return output, catalog
def test_three_archives_catalog_and_bundle_bind_the_same_final_bytes(self):
output, catalog_path = self.assemble("candidate")
catalog = json.loads(catalog_path.read_bytes())
mapping = json.loads((output / "bundle-map.json").read_bytes())
self.assertEqual(len(catalog["plugins"]), 3)
self.assertEqual(len(mapping["packages"]), 3)
self.assertEqual(
mapping["catalog_sha256"], packager.sha256(catalog_path.read_bytes())
)
self.assertEqual(
(output / mapping["catalog"]).read_bytes(), catalog_path.read_bytes()
)
for entry, record in zip(catalog["plugins"], mapping["packages"], strict=True):
archive = output / record["file"]
self.assertEqual(entry["sha256"], packager.sha256(archive.read_bytes()))
self.assertEqual(record["sha256"], entry["sha256"])
self.assertEqual(record["archive_url"], entry["archive_url"])
self.assertIn(
"/" + packager.DEFAULT_RELEASE_TAG + "/", entry["archive_url"]
)
self.assertTrue(entry["archive_url"].startswith(packager.RELEASES + "/"))
with zipfile.ZipFile(archive) as package:
self.assertEqual(
json.loads(package.read("plugin.json")), entry["manifest"]
)
device = mapping["packages"][2]
self.assertEqual(device["version"], "0.5.0+artemis.7")
with zipfile.ZipFile(output / device["file"]) as package:
self.assertEqual(
package.read("server.py"), (self.plugin / "server.py").read_bytes()
)
self.assertEqual(
package.read("MARKET.json"), (self.plugin / "MARKET.json").read_bytes()
)
sums = (output / "SHA256SUMS.txt").read_text("ascii").splitlines()
self.assertEqual(len(sums), 3)
def test_repeated_candidate_is_deterministic_and_custom_tag_is_shared(self):
first, _ = self.assemble("first", "rust3-preview-test")
second, _ = self.assemble("second", "rust3-preview-test")
for name in ("SHA256SUMS.txt", "catalog-v5.json", "bundle-map.json"):
self.assertEqual((first / name).read_bytes(), (second / name).read_bytes())
catalog = json.loads((first / "catalog-v5.json").read_bytes())
self.assertTrue(
all("/rust3-preview-test/" in e["archive_url"] for e in catalog["plugins"])
)
def test_rejects_stale_market_hash_and_mismatched_version(self):
(self.plugin / "server.py").write_bytes(b"changed")
with self.assertRaisesRegex(ValueError, "MARKET identity or content hash"):
packager.device_test_files(self.plugin)
self.manifest["version"] = "0.5.0+artemis.6"
(self.plugin / "plugin.json").write_bytes(packager.encoded(self.manifest))
self.update_market()
with self.assertRaisesRegex(ValueError, "identity or compatibility"):
packager.device_test_files(self.plugin)
def test_rejects_missing_distribution_material_and_runtime_files(self):
(self.plugin / "LICENSE").unlink()
with self.assertRaisesRegex(ValueError, "distribution material"):
packager.device_test_files(self.plugin)
(self.plugin / "LICENSE").write_bytes(b"license")
(self.plugin / ".env").write_bytes(b"private fixture")
with self.assertRaisesRegex(ValueError, "Non-distributable"):
packager.device_test_files(self.plugin)
def test_source_change_during_build_fails_before_any_archive(self):
output = self.root / "changing"
def change_source(*_):
(self.plugin / "server.py").write_bytes(b"changed during build")
return b"adapter", b"license", "fixture"
with (
patch.object(packager, "base_runtime", return_value=({}, {})),
patch.object(packager, "build_adapter", side_effect=change_source),
self.assertRaisesRegex(ValueError, "MARKET identity or content hash"),
):
packager.main(
[
"--cua-base-archive",
"base.zip",
"--go",
"go.exe",
"--output",
str(output),
"--catalog-output",
str(self.root / "catalog.json"),
"--device-test-source",
str(self.plugin),
]
)
self.assertEqual(list(output.glob("*.zip")), [])
def test_refuses_existing_outputs_and_unsafe_release_tags(self):
output, catalog = self.assemble("existing")
original = catalog.read_bytes()
with self.assertRaisesRegex(ValueError, "new or empty"):
packager.main(
[
"--cua-base-archive",
"base.zip",
"--go",
"go.exe",
"--output",
str(output),
"--catalog-output",
str(catalog),
]
)
self.assertEqual(catalog.read_bytes(), original)
with self.assertRaisesRegex(ValueError, "Refusing to overwrite an archive"):
packager.write_zip(output / "task-review-1.1.1.zip", {"file": b"changed"})
for tag in ("../main", "https://invalid", "", "x#fragment", "x y"):
with self.subTest(tag=tag), self.assertRaises(ValueError):
packager.release_url(tag, "plugin.zip")
if __name__ == "__main__":
unittest.main()