Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d14bcffbc |
No files matched your search
@@ -2,6 +2,8 @@
|
||||
|
||||
## 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`,保留预览标识。后续版本以目标分支的清单、市场索引、来源记录和发行说明为准。
|
||||
|
||||
这是适配器架构与行为设计,插件没有独立产品网页或通用配色。主程序网页的视觉规则由其根目录 DESIGN.md 管理;包内 [SKILL.md](plugins/cua-driver/SKILL.md) 指导调用方如何使用实际工具,不代替宿主权限或证据校验。
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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 的「插件」页面安装「电脑控制 · Cua」,授权并启用即可。安卓任务可根据提示词按需调用电脑控制,不需要单独配置 Codex MCP 或安装 Go、Node、Python。
|
||||
|
||||
@@ -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 网页联调范围在本分支另行记录。
|
||||
@@ -1,3 +1,5 @@
|
||||
> Rust 3 测试市场版本 `0.5.0+artemis.7`;独立 Python/MCP/dotenv 与外部工具由使用者准备。验证边界见 [RUST3-VERIFICATION.md](RUST3-VERIFICATION.md)。下文保留原 2.x 版本的历史范围。
|
||||
|
||||
# 内部团队首次部署(Windows x64)
|
||||
|
||||
接收方先准备当前用例需要的工具,随后插件自动发现和检查。无需逐项填写程序路径。插件 ZIP 包含适配代码、原 Skill、通用样例和说明,不携带第三方程序、驱动、本机 profile、账号或测试数据。
|
||||
|
||||
@@ -1,125 +1,137 @@
|
||||
{
|
||||
"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。"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"schema_version": 1,
|
||||
"plugin_id": "artemis-device-test",
|
||||
"plugin_version": "0.5.0+artemis.7",
|
||||
"content_sha256": "5ad107572dd67bba856d2a19c1c8d49b8c7189c95d6ed021e0af2961d1343412",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"installation": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"tool_contract": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"failure_partial": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"timeout_cancel": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"process_tree": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"file_binding": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"resource_conflicts": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"evidence_export": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"history_uninstall": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"secret_redaction": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"real_task": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
},
|
||||
"source_review": {
|
||||
"status": "unverified",
|
||||
"evidence_file": "RUST3-VERIFICATION.md",
|
||||
"evidence_sha256": "f65a272cd20c198353b35033b2c782ada2f98dc7979d96e5983a52e665effe7c",
|
||||
"notes": "Rust 3 测试市场预览:静态清单、组件许可、文件摘要及组包已核对;此项真实宿主/设备验证未运行,原2.x结果不外推。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,60 @@
|
||||
# 家庭安防多工具测试插件
|
||||
|
||||
将用户提供的 `artemis-device-test 0.5.0-internal` 接入 Artemis 2.0 插件系统,版本为 `0.5.0+artemis.6`。提供 20 个真实 MCP 工具、357 条保留用例路由和按需参考资料。工具成功、协议完成、业务通过分别记录。
|
||||
|
||||
## 安装
|
||||
|
||||
团队接收方先读 [首次部署与自检](DEPLOYMENT.md):准备完整依赖后,把目录加入 PATH 或只设置一次 `tool_root`,无需逐个填路径。保留 MediaInfo DLL 后端,自动发现匹配位数的 DLL;各能力独立报告就绪情况。插件仅依赖 `requirements.txt` 中的 MCP 与 dotenv,不附带 FFmpeg、Wireshark、Nmap、Binwalk、MediaInfo、厂商软件或驱动,安装器也不自动安装 Python requirements。
|
||||
|
||||
维护者在 Artemis-Plugins 仓库根目录,从已提交的源码打包:
|
||||
|
||||
```powershell
|
||||
python scripts/build_device_test.py --ref HEAD
|
||||
```
|
||||
|
||||
ZIP、SHA-256 和源码提交记录写入 `artifacts/device-test/`,ZIP 根层包含 `plugin.json`。宿主开发检查命令见 ARTEMIS 仓库的 `docs/plugin-development.md`。在网页“插件 → 高级接入”导入 ZIP 或本目录,打开设置填写 Python 的绝对路径,授权后启用。安装使用平台正常接口,不修改任务、历史或原 Skill,不要求重启平台。
|
||||
|
||||
新装默认未启用。插件不提供画面观察,纯电脑任务须同时选择已授权的 Cua 等视觉插件;Android 任务仍使用 Artemis 原有手机能力。插件不在任务中递归调用 `mobile_run_task`。
|
||||
|
||||
## 本机设置
|
||||
|
||||
| 字段 | 设置 |
|
||||
| --- | --- |
|
||||
| `python` | 当前 Artemis 环境的 Python 绝对路径;不要误用未安装 MCP 的系统 Python |
|
||||
| `profile` | 可选,本机工具配置 JSON 路径;结构见 `skill/assets/profile.example.json` |
|
||||
| `tool_root` | 可选,统一的已解压工具目录;profile 优先,其次该目录,再次宿主 PATH;MediaInfo 也支持自动发现 |
|
||||
| `rtsp_env_file` | 可选,本机受控 `.env` 文件路径;需要 RTSP 时保存 `DUT_RTSP_URL` 等变量,工具只传变量名 |
|
||||
| `operation_timeout_seconds` | 默认 45 秒,10..3300;宿主 `ARTEMIS_PLUGIN_TOOL_TIMEOUT_SECONDS` 至少比它大 10 秒,默认宿主 60 秒 |
|
||||
| `allow_firmware_write` | 默认 `false`;当前用例明确授权升级时才设为 `true`,调用仍需要 `execute=true` 等完整条件 |
|
||||
|
||||
profile 不保存密码或 URL。GUI 和网站没有伪造的业务 API;环境诊断列为需要真实界面/会话验证,安装包存在不能当成业务可用。原资料中的环境安装、人工登录和厂商专用能力边界仍适用。
|
||||
|
||||
本包仅声明 Windows x64,未声称其他平台通过验证。标准资源为 `desktop:local`、`lab:device-test`,与电脑插件共用桌面锁,并保守串行共享实验台。任务若只需手机且希望独立并行,可明确不选此插件。
|
||||
|
||||
## 使用示例
|
||||
|
||||
在 Artemis 正常任务中描述:
|
||||
|
||||
```text
|
||||
使用家庭安防多工具测试插件,先检查本机工具和用例路由。
|
||||
按本次指定的完整用例、摄像机 SN/实际上报型号、协议和手机序列号执行。
|
||||
先只读验证;缺失项记录为阻塞,不升级、不恢复出厂、不重提失败业务。
|
||||
PC/网站通过已授权电脑插件观察,已有登录态可复用,未登录交给人工完成。
|
||||
将实际观测、未验证项和仍需人工核验项保存到任务证据,生成独立联合资料包。
|
||||
```
|
||||
|
||||
工具与操作规则见 [插件 Skill](SKILL.md)。报告工具只接受本任务附件名;外部本机证据先用 `import_evidence` 复制校验。独立 ZIP 内保留可移植 Markdown/JSON 和证据相对路径;导出的单独 Markdown 与 JSON 用于预览,以 ZIP 为完整交付。单附件不超过 64 MiB,联合包输入总计不超过 48 MiB。
|
||||
|
||||
查询当前局域网设备可直接描述“通过配置工具查询当前局域网内的设备,核对身份和版本”。插件先用 `list_network_interfaces` 读取本机已连接的物理 IPv4 接口,再在实际接口上分别进行 V1/V2 只读发现;用户指定协议或接口时遵守该范围。无需预先知道摄像机 SN、型号或打开配置工具 GUI。`environment_doctor` 的 GUI `manual-interface` 状态不阻塞独立协议适配器。接口枚举不会发送设备发现包;实际发现和身份查询仍由正常 Artemis 任务持有资源后执行。
|
||||
|
||||
`artemis.2` 补齐本机网卡入口;`artemis.3` 增加可迁移的 DLL/运行时发现与分层自检。`environment_doctor(check_functional=true)` 会检查程序启动、DLL 加载及固定本地视频/PCAP/固件样本;接口与 COM 只枚举,不抓包或打开串口。默认自检仅发现文件。修改系统 PATH 后需从新环境重新启动 Artemis,再在插件内检查。保留原 Skill 资料与 vendor 协议实现;固件适配器、工具运行器及相关测试的修改见来源清单,固件写入条件不变。
|
||||
|
||||
## 验证与来源
|
||||
|
||||
[VERIFICATION.md](VERIFICATION.md) 记录本次实际验证层次;[source-manifest.json](source-manifest.json) 保存导入文件的原始 SHA-256。固件适配器、工具运行器及相关测试已修订,其余原始资料按来源清单记录;不包含缓存、个人配置、账号、原始测试证据、模型 Key 或原始参考源码 ZIP。
|
||||
|
||||
[SOURCE.md](SOURCE.md) 说明来源与许可。[RELEASE.md](RELEASE.md) 说明本修订的发布范围;以市场索引和对应不可变发行附件确认实际分发状态。
|
||||
|
||||
`artemis.6` 将环境自检与工具执行合并到同一 Windows Job 生命周期,限制管道输出;执行后清理异常保留已启动事实,回执返回未知或部分执行。市场记录见 `MARKET.json`,其通过范围以验证文档为准,不代表实机能力全部通过。
|
||||
> 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 条保留用例路由和按需参考资料。工具成功、协议完成、业务通过分别记录。
|
||||
|
||||
## 安装
|
||||
|
||||
团队接收方先读 [首次部署与自检](DEPLOYMENT.md):准备完整依赖后,把目录加入 PATH 或只设置一次 `tool_root`,无需逐个填路径。保留 MediaInfo DLL 后端,自动发现匹配位数的 DLL;各能力独立报告就绪情况。插件仅依赖 `requirements.txt` 中的 MCP 与 dotenv,不附带 FFmpeg、Wireshark、Nmap、Binwalk、MediaInfo、厂商软件或驱动,安装器也不自动安装 Python requirements。
|
||||
|
||||
维护者在 Artemis-Plugins 仓库根目录,从已提交的源码打包:
|
||||
|
||||
```powershell
|
||||
python scripts/build_device_test.py --ref HEAD
|
||||
```
|
||||
|
||||
ZIP、SHA-256 和源码提交记录写入 `artifacts/device-test/`,ZIP 根层包含 `plugin.json`。宿主开发检查命令见 ARTEMIS 仓库的 `docs/plugin-development.md`。在网页“插件 → 高级接入”导入 ZIP 或本目录,打开设置填写 Python 的绝对路径,授权后启用。安装使用平台正常接口,不修改任务、历史或原 Skill,不要求重启平台。
|
||||
|
||||
新装默认未启用。插件不提供画面观察,纯电脑任务须同时选择已授权的 Cua 等视觉插件;Android 任务仍使用 Artemis 原有手机能力。插件不在任务中递归调用 `mobile_run_task`。
|
||||
|
||||
## 本机设置
|
||||
|
||||
| 字段 | 设置 |
|
||||
| --- | --- |
|
||||
| `python` | 当前 Artemis 环境的 Python 绝对路径;不要误用未安装 MCP 的系统 Python |
|
||||
| `profile` | 可选,本机工具配置 JSON 路径;结构见 `skill/assets/profile.example.json` |
|
||||
| `tool_root` | 可选,统一的已解压工具目录;profile 优先,其次该目录,再次宿主 PATH;MediaInfo 也支持自动发现 |
|
||||
| `rtsp_env_file` | 可选,本机受控 `.env` 文件路径;需要 RTSP 时保存 `DUT_RTSP_URL` 等变量,工具只传变量名 |
|
||||
| `operation_timeout_seconds` | 默认 45 秒,10..3300;宿主 `ARTEMIS_PLUGIN_TOOL_TIMEOUT_SECONDS` 至少比它大 10 秒,默认宿主 60 秒 |
|
||||
| `allow_firmware_write` | 默认 `false`;当前用例明确授权升级时才设为 `true`,调用仍需要 `execute=true` 等完整条件 |
|
||||
|
||||
profile 不保存密码或 URL。GUI 和网站没有伪造的业务 API;环境诊断列为需要真实界面/会话验证,安装包存在不能当成业务可用。原资料中的环境安装、人工登录和厂商专用能力边界仍适用。
|
||||
|
||||
本包仅声明 Windows x64,未声称其他平台通过验证。标准资源为 `desktop:local`、`lab:device-test`,与电脑插件共用桌面锁,并保守串行共享实验台。任务若只需手机且希望独立并行,可明确不选此插件。
|
||||
|
||||
## 使用示例
|
||||
|
||||
在 Artemis 正常任务中描述:
|
||||
|
||||
```text
|
||||
使用家庭安防多工具测试插件,先检查本机工具和用例路由。
|
||||
按本次指定的完整用例、摄像机 SN/实际上报型号、协议和手机序列号执行。
|
||||
先只读验证;缺失项记录为阻塞,不升级、不恢复出厂、不重提失败业务。
|
||||
PC/网站通过已授权电脑插件观察,已有登录态可复用,未登录交给人工完成。
|
||||
将实际观测、未验证项和仍需人工核验项保存到任务证据,生成独立联合资料包。
|
||||
```
|
||||
|
||||
工具与操作规则见 [插件 Skill](SKILL.md)。报告工具只接受本任务附件名;外部本机证据先用 `import_evidence` 复制校验。独立 ZIP 内保留可移植 Markdown/JSON 和证据相对路径;导出的单独 Markdown 与 JSON 用于预览,以 ZIP 为完整交付。单附件不超过 64 MiB,联合包输入总计不超过 48 MiB。
|
||||
|
||||
查询当前局域网设备可直接描述“通过配置工具查询当前局域网内的设备,核对身份和版本”。插件先用 `list_network_interfaces` 读取本机已连接的物理 IPv4 接口,再在实际接口上分别进行 V1/V2 只读发现;用户指定协议或接口时遵守该范围。无需预先知道摄像机 SN、型号或打开配置工具 GUI。`environment_doctor` 的 GUI `manual-interface` 状态不阻塞独立协议适配器。接口枚举不会发送设备发现包;实际发现和身份查询仍由正常 Artemis 任务持有资源后执行。
|
||||
|
||||
`artemis.2` 补齐本机网卡入口;`artemis.3` 增加可迁移的 DLL/运行时发现与分层自检。`environment_doctor(check_functional=true)` 会检查程序启动、DLL 加载及固定本地视频/PCAP/固件样本;接口与 COM 只枚举,不抓包或打开串口。默认自检仅发现文件。修改系统 PATH 后需从新环境重新启动 Artemis,再在插件内检查。保留原 Skill 资料与 vendor 协议实现;固件适配器、工具运行器及相关测试的修改见来源清单,固件写入条件不变。
|
||||
|
||||
## 验证与来源
|
||||
|
||||
[VERIFICATION.md](VERIFICATION.md) 记录本次实际验证层次;[source-manifest.json](source-manifest.json) 保存导入文件的原始 SHA-256。固件适配器、工具运行器及相关测试已修订,其余原始资料按来源清单记录;不包含缓存、个人配置、账号、原始测试证据、模型 Key 或原始参考源码 ZIP。
|
||||
|
||||
[SOURCE.md](SOURCE.md) 说明来源与许可。[RELEASE.md](RELEASE.md) 说明本修订的发布范围;以市场索引和对应不可变发行附件确认实际分发状态。
|
||||
|
||||
`artemis.6` 将环境自检与工具执行合并到同一 Windows Job 生命周期,限制管道输出;执行后清理异常保留已启动事实,回执返回未知或部分执行。市场记录见 `MARKET.json`,其通过范围以验证文档为准,不代表实机能力全部通过。
|
||||
@@ -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 秘密只通过受控配置文件读取;固件写入默认关闭,未知结果不得重试。
|
||||
@@ -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
|
||||
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。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,336 +1,336 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"id": "artemis-device-test",
|
||||
"name": "家庭安防多工具测试",
|
||||
"version": "0.5.0+artemis.6",
|
||||
"description": "集成 artemis-device-test Skill:PATH/统一目录自动发现工具及匹配位数的 MediaInfo DLL、按能力独立自检、本机网卡枚举、357 条用例路由、媒体/抓包/串口、V1/V2 设备及联合证据。PC/网站复用视觉插件,手机沿用 Artemis;工具成功不等于用例通过。",
|
||||
"artemis_version": ">=2.0.0,<3.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": "Artemis Python 解释器",
|
||||
"description": "填写已安装 MCP 依赖的 Python 绝对路径。源码环境可使用项目 .venv/Scripts/python.exe。",
|
||||
"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;增加前须将宿主 ARTEMIS_PLUGIN_TOOL_TIMEOUT_SECONDS 设得至少大 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`。
|
||||
2. 在上游源码根目录应用本插件的 `patches/cua-driver-0.38.0-untitled-shell.patch`。补丁只修改 Windows 精确窗口解析,不放宽普通窗口发现。
|
||||
3. 下载官方 Windows x64 ZIP。在具备上述编译依赖的终端,从 Artemis-Plugins 根目录执行:
|
||||
打包器核对已发布 `0.38.0+artemis.1` ZIP SHA256 为 `c007da16d58c4165a1a344113e650e68bd92a69ad3ce4ca8285818c5426dc0f4`,逐文件比较桥接源码、工具与补丁,仅允许版本常量变化。Driver、UIA helper、SDK DLL 和游标工具直接保留已发布字节;不运行 Cargo、不重新构建或下载底层驱动。
|
||||
|
||||
```sh
|
||||
python scripts/build_cua_driver.py <官方ZIP> --upstream-source <上游源码根目录> --go <Go可执行文件>
|
||||
```
|
||||
Go 在独立缓存内运行测试、vet,并以 `CGO_ENABLED=0`、`-trimpath`、`-buildvcs=false` 构建新桥接程序。清单、包名、MCP 握手和来源记录统一为 `.2`。原 MIT 许可与 Go 标准库 BSD 许可随包保留。ZIP 条目使用固定顺序、时间与权限,拒绝覆盖既有候选。
|
||||
|
||||
构建脚本校验官方 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)。不自动安装、授权、推送或发布。
|
||||
@@ -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 开发预览。既有任务与旧安装包保留原版本,主程序升级不会自动更新或授权插件;当前任务结束后在插件市场单独更新。
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 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)
|
||||
|
||||
重新执行 Go 测试、vet 和构建,核对最终包内源码、兼容补丁及运行库 SHA256。10 月 26 日主程序集中回归中,此插件参与仅 PC 输入、双击、清空和两轮 PC 到 Android 的校验码传递,均有两端独立证据;该组正式任务共 9 项通过,另有准备探索超出指定页面范围而失败,未计入通过数。模型统一 gpt-6-luna/high,未将历史 OCU 对照性能数据作为此次发行性能结论。
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
//go:embed tools.json
|
||||
var toolsJSON []byte
|
||||
|
||||
const adapterVersion = "0.38.0+artemis.1"
|
||||
const adapterVersion = "0.38.0+artemis.2"
|
||||
|
||||
type obj = map[string]any
|
||||
type request struct {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"schema_version": 2,
|
||||
"id": "cua-driver",
|
||||
"name": "电脑控制 · Cua",
|
||||
"version": "0.38.0+artemis.1",
|
||||
"version": "0.38.0+artemis.2",
|
||||
"description": "通过 Cua Driver 的窗口截图与可访问性树操作 Windows 应用及浏览器窗口;复用 ARTEMIS 的任务调度、步骤证据与录像。",
|
||||
"artemis_version": ">=2.0.0,<3.0.0",
|
||||
"artemis_version": ">=3.0.0,<4.0.0",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
|
||||
@@ -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 提示词。
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
`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 历史数据。
|
||||
@@ -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、中文选项、选填属性和报告声明保持不变,无额外运行依赖。
|
||||
|
||||
- 启用插件后,批量模板附加预置条件、操作步骤、预期结果。未启用时仍是七列;新增列不进入模型提示词。
|
||||
- 左侧展示原始用例,用例编号和标题置顶;历史缺失字段明确提示。
|
||||
- 「AI执行准确性」改为「用例执行准确性」,保留字段 ID 和旧评审值。
|
||||
- 备注框缩短;增加上一条、下一条,按钮位于保存评审下方;用户修改后自动保存,切换前保存,失败留在当前任务,连续修改不会被旧回执覆盖。
|
||||
- 面板适用于所有执行模式、设备目标及步骤/视频视图。
|
||||
旧 `task-review-v1.1.0` 附件与 `catalog-v4.json` 的 `<3.0.0` 限制继续保留,未通过修改旧索引宣称旧包兼容。新包仅进入 `catalog-v5.json`,安装和启用由用户操作,不自动迁移 Python 历史数据。
|
||||
|
||||
发行标签为 task-review-v1.1.0。验证范围与结果见 [验证记录](VERIFICATION.md)。
|
||||
计划标签 `task-review-v1.1.1`,当前是未发布候选。声明式接口已按 3.0 宿主协议准备,实际页面、保存/导航及报告导出待主项目本轮验证,见 [验证记录](VERIFICATION.md)。
|
||||
@@ -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。
|
||||
- 前端 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 的严格面板模型验证旧索引仍可解析。官方旧地址迁移与自定义地址保留的回归通过。
|
||||
候选打包器检查版本、兼容范围、面板、稳定字段 ID、无 MCP 进程和零工具声明;产出新 ZIP 后逐条回读,记录最终 SHA256 和嵌入索引的清单一致性。实际执行结果以同目录 `build-receipt.json` 为准。
|
||||
|
||||
浏览器使用拦截 API 的示例数据,未填写真实任务的人工评审,未操作用户设备或调用模型。未做 macOS/Linux 实测,未运行原 192 项批次或高并发设备压力测试。
|
||||
Rust 宿主提供声明式表单接口。实际页面、自动保存、清空、冲突处理、导航及 Word/Excel/资料包报告待主项目 3.0 本轮统一验证;本次未操作页面、设备或业务任务,也未做性能测试。人工结果仍只能由用户填写,不改变模型原判或任务状态。
|
||||
|
||||
以下为旧版历史记录,不能作为本轮验证结果。
|
||||
|
||||
---
|
||||
|
||||
# 任务人工评审 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,因此不需要设备动作、取消进程或录像验收。
|
||||
1.1.0 在 ARTEMIS 2.4.0 的历史验证属于旧宿主,不作为 3.0 通过证据。旧发行包、标签及 catalog-v4.json 的 `<3.0.0` 约束保留;本次不迁移或兼容 Python 历史数据库。当前候选未推送、未发布。
|
||||
@@ -2,9 +2,9 @@
|
||||
"schema_version": 3,
|
||||
"id": "task-review",
|
||||
"name": "任务人工评审",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "在任务回放中查看导入的原始用例,逐条填写用例执行准确性与测试结果正确性,按需填写备注并随报告导出。",
|
||||
"artemis_version": ">=2.4.0,<3.0.0",
|
||||
"artemis_version": ">=3.0.0,<4.0.0",
|
||||
"platforms": [
|
||||
"win32",
|
||||
"linux",
|
||||
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user