Files
Artemis-Eapil/skills/artemis-use/references/plugin-sdk.md
T

11 KiB

插件运行辅助接口与市场检查

这些接口从 ARTEMIS 2.4.5 开始提供,位于 artemis.plugins.sdk;使用 ARTEMIS 2.4.5 或更新版本的 Python,并在插件清单中声明 artemis_version: ">=2.4.5,<3.0.0"。它们不属于远程薄客户端 artemis-client,不启动后台、模型或第二套任务队列。现有不依赖这些辅助接口的插件仍按自身清单兼容旧宿主;采用这些接口才需要提高最低版本。

已批准文件必须绑定实际使用的字节

from artemis.plugins.sdk import snapshot_file

snapshot = snapshot_file(authorized_path, approved_sha256)
# 此后只消费快照。连接建立、预检或等待期间原文件发生变化,也不会改变快照。
for chunk in snapshot.chunks():
    transport.sendall(chunk)

approved_sha256 必须来自已批准的内容身份,不能在执行时把新文件的摘要自动当作新的批准。制作快照、验证设备身份和写入授权都应在真正写操作前完成。使用 .data 或只读的 .chunks(),不要校验之后重新打开原路径;传输后才发现摘要不符不能撤销已经发生的写入。

snapshot_file 一次读取授权的普通文件并核对 SHA256,拒绝链接、特殊文件、大小超限和摘要不符。默认上限 64 MiB,副本是不可变内存 bytes;参数 max_bytes 仅作用于本次调用,选择更大上限须评估内存。它不负责用户授权或路径沙箱,插件仍要限制允许读取的范围。大文件如采用磁盘快照或流式方案,应提供等价的防修改保证并另行验证,不能恢复到“检查后重开路径”。

VerifiedFile.size、.sha256 对应快照本身。原路径同长度覆盖、替换、增长或截断后,快照仍保持原内容。对写入端到端的回归必须检查实际发送字节,不能只断言预检函数被调用。

外部工具的生命周期

from artemis.plugins.sdk import ManagedProcessError, run_process

try:
    result = await run_process(
        fixed_argv,
        cwd=authorized_directory,
        timeout=20,
        input_data=snapshot.data,
    )
except ManagedProcessError as exc:
    # 用稳定的 error_code 返回部分执行/未知;不回显命令或原始敏感输出,不自动重试。
    ...

fixed_argv 必须是业务层构造的固定参数列表,不能向模型暴露任意 shell;cwd 为明确的现有目录。可通过 env 提供所需的环境白名单;未提供时继承当前插件环境,插件应自行排除不需要的密钥和工具日志变量。宿主的凭据管理不会自动证明外部程序的输出安全。

timeout 必须为有限正数,作用于本次工具运行;标准输入最多 64 MiB。stdout、stderr 分别返回 bytes,共用默认 4 MiB 上限,可通过本次调用的 max_output_bytes 调整。输出超限立即停止;部分输出不作有效结果返回。ProcessResult.returncode 保留非零退出码,不据此生成业务用例通过结论。

运行器在 Windows 先将等待启动的引导进程加入不可继承的 Job Object,再允许启动实际工具,防止快速派生后代漏出归属;父进程退出时 Job Object 同样回收其树。POSIX 使用独立会话,正常退出、超时、取消和输出超限均回收所属进程组。它是生命周期辅助,不是恶意程序沙箱,不能承诺回收主动逃离会话的程序。

调用结束后不保留后台子进程。需要常驻驱动的插件应实现会话级归属、关闭与验证,不借本接口后台化工具。取消在清理后继续传播,不能吞掉 CancelledError。清理另有固定的有界等待;未确认完成时抛出 process_cleanup_unconfirmed,插件必须返回未知状态并核对资源,不声明已全部停止。

常见错误码:process_timeout、process_output_limit、process_launch_failed、process_ownership_failed、process_worker_interrupted、process_invalid_status、process_cleanup_unconfirmed。may_have_started=false 只表示已知工具尚未启动;其余情况不能授权重发写操作。运行器从不重试。原始输出可能包含密钥,返回给模型前按业务字段白名单处理。

宿主负责整体任务与 MCP 调用预算,插件负责内部操作及其后代。内部超时先发生时,必须在向宿主返回前收尾;宿主未超时、或只关闭 MCP 连接,都不能替代这一步。

可运行回归

<ARTEMIS_PYTHON> -B -m artemis.plugins.sdk_checks --json-output sdk-faults.json
<ARTEMIS_PYTHON> -B <受管工具模板目录>/test_contract.py

模板目录在源码仓库的 examples/plugins/managed-tool-demo,或本 Skill 的 assets/managed-tool-demo;先复制到用户指定开发目录。以下检查均使用 ARTEMIS 2.4.5 或更新版本的 Python。

第一条只在临时目录创建合成文件和自己的测试进程,覆盖文件同长度覆盖、截断/增长、错误摘要、二进制 stdin、非零退出码、正常结束/超时/取消后的后代回收、输出超限、附件摘要与路径拒绝,以及部分/未知执行不能授权重试。它不连接设备、不调用模型、不修改现有任务或配置。结果明确标为 sdk_faults_only。

第二条额外通过真实 stdio MCP 调用模板工具,核对有效输入、错误摘要、目录穿越拒绝及宿主附件回读,结果标为 template_offline_contract。模板见插件开发文档的受管工具示例。

改写插件后,必须扩展模板测试到自己的实际入口:在最后一次预检与真正消费文件之间替换输入、让实际工具创建后代再超时/取消,并检查实际传输和进程存活。SDK 自测通过不能证明插件调用正确。还需覆盖错误/部分执行、证据丢失或摘要不符、权限、资源互斥以及真实任务时间线。

开发检查与市场材料检查

统一入口为仓库/Skill 的 check_plugin.py,也可执行 python -m artemis.plugins.validation。两处脚本调用同一个实现,不单独维护规则。

# 日常本地开发,无需公开分发许可;--live 只握手,不派发工具。
<ARTEMIS_PYTHON> <检查脚本> <插件目录或ZIP> --live --values '{"python":"ARTEMIS解释器绝对路径"}'

# 在清洁候选目录生成草稿;所有结论未验证、许可未知,不覆盖已有 MARKET.json。
<ARTEMIS_PYTHON> <检查脚本> <插件目录> --prepare-market

# 对最终候选 ZIP 检查市场材料;包含只读 MCP 声明握手,不调用业务工具。
<ARTEMIS_PYTHON> <检查脚本> <最终ZIP> --market --values '{"python":"ARTEMIS解释器绝对路径"}' --json-output market-check.json

路径与 JSON 按当前 Shell 引用;密钥不放进 --values。--json-output 必须在候选目录外,不能覆盖输入 ZIP。ZIP 沿用宿主的安全解压和大小限制,检查不向已有插件库安装。--market 先核对材料,再握手,普通开发检查仍保持轻量。

市场检查拒绝缓存、.pyc、虚拟环境、版本库目录、个人 .env 配置、链接和冲突路径。环境样例可保留 .env.example。不要为通过检查而清理用户运行目录;从可分发输入构建独立候选。

MARKET.json

此文件随包提交,版本固定为 1,不增加 plugin.json 字段,旧宿主仍可安装符合其能力的插件。草稿由检查脚本生成,包含:

字段 要求
plugin_id、plugin_version 与候选清单完全一致
content_sha256 全部包内文件路径及 SHA256 的确定性摘要,只排除根层 MARKET.json 自身
traits.uses_subprocesses 明确 true/false,不能保留 null
traits.file_inputs_for_writes 是否消费文件执行写操作,明确 true/false
components 分开列原始实现、适配层、第三方依赖/参考资料的许可和来源
checks 下表全部验收项目,保留实际范围和证据

每个组件包含 name、paths、license、permission、license_file、source_file。paths 为精确包内相对路径或目录前缀,. 表示整个包;全部可分发文件须被覆盖。只有明确的 permission: "permitted" 才接受,local_only 和 unknown 均阻止市场提交。许可和来源文件必须实际在包内;适配层开源许可不会重新许可原始代码、用例、协议或内部资料。可使用合适的 LicenseRef,不强制统一开源,但具体分发授权必须可审查。

每条验收记录包含 status、evidence_file、evidence_sha256、notes。passed 必须指向真实的非空包内文档及其正确摘要,notes 说明验证环境、范围与结果;缺失、失败和未验证均拒绝。不能把历史记录、SDK 自测或模拟设备改写为本次真实业务通过。

验收项目 要记录的内容
installation 安装、升级、平台和依赖兼容
tool_contract 实际 MCP Schema、能力和参数限制
failure_partial 错误、部分执行、未知副作用与不盲目重试
timeout_cancel 实际工具入口超时与取消
process_tree 工具创建后代后,正常/超时/取消的回收验证
file_binding 已批准文件被替换、截断、增长时的实际消费字节
resource_conflicts 同设备/桌面资源互斥
evidence_export 导出索引、中文路径、摘要不符与缺失附件
history_uninstall 卸载/升级后历史仍可读
secret_redaction 配置、命令、输出和回执脱敏
real_task 经宿主正常任务的操作、时间线、暂停/恢复、证据和报告
source_review 对本次包的源码审查、来源与分发授权核对

仅在声明无相应行为时,process_tree、file_binding 可 not_applicable;无 MCP 时 tool_contract、timeout_cancel 可不适用;无声明资源时 resource_conflicts 可不适用。仍须在 notes 中给出理由,不能把未测写成不适用。其他项目须有真实通过记录。

更新任何文件后,旧材料会失效。先重跑受影响验证,再用 --fingerprint 查看新的内容摘要,并更新记录;不要只刷新摘要来沿用过期结论。握手结束后重新核对内容与市场记录,检查期间改动会使检查失败。最终 ZIP 检查结果另包含实际解压检查的同一份字节的 SHA256,不重新打开原路径来记录替换文件;公开发布时仍需核对固定附件、市场索引与无认证下载字节。

市场材料检查验证字段、覆盖范围、摘要和证据存在性,不证明声明真实、不替作者确认权利归属,也不自动发布。源码审查和实际业务验收仍由发布方核对;文档、静态检查、握手、SDK 故障自测与真实业务结论分别记录。