Compare commits

...
2 Commits
22 changed files with 583 additions and 48 deletions

No files matched your search

+31 -24
View File
@@ -2,15 +2,27 @@
## Project Overview
本仓库是基于 ARTEMIS 扩展的 Android AI 自动化测试平台,提供简体中文网页、OpenAI Responses 模型调用、批量任务、历史检索及 Word/Excel 报告。后端使用 Python 3.12+、FastAPI、LangGraph,前端使用 Angular,AI 客户端通过 MCP 接入。
本仓库是基于 ARTEMIS 扩展的自动化测试平台,提供简体中文网页、Flash/Pro 执行、交互探索、插件跨端操作、精选任务、批量任务及 Word/Excel 报告。后端使用 Python 3.12+、FastAPI、LangGraph,前端使用 Angular;模型统一使用 OpenAI Responses,AI 客户端通过轻量 MCP 桥接用户启动的主程序。
## Scope
- 本文件适用于整个仓库;子目录的 AGENTS.md 在对应目录内优先。
- 当前任务的用户指令优先于仓库指南。保留用户已有修改,只处理本次任务范围。
- 本文件指导开发;使用软件执行 Android 测试时,阅读 [自动化 Skill](skills/artemis-use/SKILL.md)。
- 主程序与 `Artemis-Plugins` 是独立仓库。跨仓库任务分别检查分支、未提交改动和远端状态;发布文档或修复时,不夹带已有未发布提交。必要时使用隔离工作树,只移植本次提交。
- 本文件指导开发;使用软件执行设备或跨端任务、排查任务时,阅读 [自动化 Skill](skills/artemis-use/SKILL.md)。
- 以下是项目原生命令;宿主要求 RTK 等包装器时按宿主规则调用,不把本机路径或工具写成项目安装前提。
## Read When Needed
| 修改范围 | 先读 |
| --- | --- |
| 执行、验收、探索、插件或证据的职责边界 | [DESIGN.md 的架构与行为约定](DESIGN.md#system-architecture) |
| 网页布局、样式或交互 | [视觉与交互设计](DESIGN.md#colors)及[前端指南](apps/showcase_ui/AGENTS.md) |
| 探索、示范、用例提炼与复跑 | [探索说明](docs/exploration-mode.md) |
| 插件协议、适配或市场协同 | [插件开发规范](docs/plugin-development.md)及插件仓库自身的 AGENTS.md、DESIGN.md |
| 远程 Python SDK | [SDK 设计](packages/artemis-client/DESIGN.md),保持标准库薄客户端边界 |
| 继续历史开发、确认版本或发布 | [迁入时的开发快照](docs/development-handoff.md)、[2.0.1 发行说明](docs/releases/2.0.1.md),再核对实时 Git 状态;`docs/plans/` 保留各自阶段的状态 |
## Quick Commands
在仓库根目录执行,前端命令见局部指南:
@@ -29,45 +41,39 @@
Windows 首次安装使用 `start.bat`,国内镜像使用 `start-cn.bat`。启动只提示源码更新;升级使用 `update.bat` / `update-cn.bat`,会终止本项目运行进程。开发中可传入 `-SkipUpdate` 跳过更新提示。
## Key Paths
- `artemis/agents/`、`artemis/runtime/`:Flash/Pro 执行、设备与任务运行时。
- `artemis/llm/`、`artemis/services/llm.py`:模型请求、超时、重试与恢复。
- `artemis/config/`、`.env.example`:运行配置、环境变量及默认值。
- `config/artemis.jsonc`、`artemis/resources/config/artemis.jsonc`:开发与打包使用的配置,默认值变更须同步。
- `apps/admin_console/`:HTTP API、统一队列、批次、历史及报告服务。
- `apps/showcase_ui/`:Angular 网页;遵循 [前端指南](apps/showcase_ui/AGENTS.md)。
- `mcp_server/`:MCP 工具与客户端使用规则。
- `packages/artemis-client/`:远程 Python SDK;`skills/artemis-use/`:随源码分发的 AI 使用技能。
- `scripts/`:安装、镜像、升级、检查及打包脚本。
- `tests/unit/`、`tests/tools/`、`packages/artemis-client/tests/`:默认测试范围。
## Architecture Boundaries
- 模型传输只使用 OpenAI Responses,不恢复 Gemini 专用调用或 Chat Completions 分支。API 模型名与 Flash/Pro 执行模式分开处理。
- 网页和工作区 MCP 工具复用后台配置、队列、历史和报告服务;不要另建调度器或第二套状态存储。
- 批次创建与执行分开;保持 request_id 幂等性。同设备串行,失败项不自动重跑;模型重试不能变成业务用例重提。
- 区分“执行完成”与“验证通过”。报告结论必须有实际证据,保留未验证、失败、跳过和中断状态。
- 网页、MCP 与 SDK 复用后台配置、队列、历史和报告服务;不另建调度器或第二套状态存储。MCP 不自行启动完整后台,主程序离线时仍可握手并明确提示连接状态。
- 批次创建与执行分开;保持 `request_id` 幂等性。同设备及共享桌面资源按现有锁串行,任务固定插件版本;失败项不自动重跑,模型重试不能变成业务用例重提,结果不明的写操作不盲目重发。
- 最终验收使用独立总预算,协调单次模型超时与重试;不能套用中间检查点的 180 秒时限。重试保留已取得的工具证据,用户停止或探索新指导仍可中断。
- 区分执行完成、工具成功和验证通过。正式结论必须经过完整解析与校验,取消或截断的流式内容不能显示为通过;报告、网页、MCP 与人工复验后的有效结论保持一致。
- 示范的实际来源贯穿分析、澄清、规划、执行、验收与用例提炼。补充网址不等于更换平台;用户明确要求改端或跨端时再按要求执行,手机证据不能替代电脑证据。
- 人工示范遵循窗口/屏幕录制配置。窗口模式跟随前台窗口,无有效窗口时保留缺口,不扩大到多屏桌面;普通录屏在只读最终验收前停止,恢复操作时再追加片段。
- 探索主动执行预算耗尽后保留进度并暂停,不直接判失败;探索成功、用例提炼成功和固定版本复跑通过分别记录。收藏保存独立模板与配置,不依赖原任务一直存在。
- 历史压缩、视觉摘要与工具上下文调整须保留关键文件回执和来源,维护稳定的多轮前缀;缓存率使用实际用量加权统计,不把缓存输入当作消失的 token,也不承诺固定收益。
- 设备动作回放已移除;只读历史步骤、截图和上下文回忆仍保留,不因 replay 命名误删这些能力。
- 密钥仅通过网页或环境配置写入,读取配置不得回显;MCP 不接受密钥。全局配置修改影响后续任务。
- 默认 high、请求超时 300 秒、失败后重试 5 次、间隔 10 秒,视频分析与录屏关闭。接口不支持思考强度时明确报错,不静默降级。
- 密钥仅通过网页或环境配置写入,读取配置不得回显;MCP 不接受密钥。全局配置修改影响后续任务,已保存配置优先于新安装默认值。
- 新安装普通任务默认 `gpt-6-luna / high`,探索默认 `gpt-6.1-sol / high`;请求超时 300 秒、失败后重试 5 次、间隔 10 秒,视频分析与普通录屏关闭。接口不支持思考强度时明确报错,不静默降级。
- 业务环境恢复由任务提示词提出,不新增默认业务回滚流程;连接、录像、子进程和资源锁的运行时收尾仍然必需。
## Conventions
- Python 使用绝对导入、4 空格缩进和双引号,由 Ruff 检查;不要放宽质量基线来掩盖新增问题。
- 新增可调参数集中进入配置层,并更新环境示例、网页/MCP 入口和必要文档,避免散落硬编码。
- 新增可调参数集中进入配置层,并更新环境示例、网页/MCP 入口和必要文档;默认值同步 `config/artemis.jsonc` 与 `artemis/resources/config/artemis.jsonc`。
- MCP 工具增删时同步工具注册、CLI 安装白名单、测试、Skill 和能力文档。
- 同步 Codex MCP/Skill 时保留已有工具白名单、禁用状态和审批策略;异常时保留原配置并告警,不生成非法 TOML 或阻断网页启动。
- Windows PowerShell 启动脚本保持兼容 Windows PowerShell 5.1,避免编码导致解析失败。升级只做 Git 快进,保护已有配置和本地修改。
- `update*.bat` 保持精简的 ASCII 入口;升级程序及其辅助脚本先复制到项目外执行。更新后通过新版 `start.ps1 -PrepareOnly -SkipUpdate` 同步环境,保留此内部调用约定,避免继续运行旧版安装函数。
- 前端遵循现有简体中文文案与视觉样式;不直接编辑构建产物。
- 前端遵循 DESIGN.md 与局部指南,复用现有组件和简体中文文案;不直接编辑构建产物。页面仍显示旧效果时,先核对实际服务的源码目录及构建来源。
## Definition of Done
- Python 行为改动运行默认测试、Ruff 两项检查、质量基线和核心类型检查;前端改动遵循局部指南的测试与构建要求。
- 文档改动核对命令、相对链接和功能说明;仅文档变更无需启动设备或调用付费模型。
- 文档改动核对命令、相对链接和功能说明;历史测试数量与未测范围见对应版本记录,不能冒充本次验证。仅文档变更无需启动设备或调用付费模型。
- 默认测试不依赖设备、密钥或私有服务;外部测试按现有 android、integration、e2e、cloud、manual 标记分层,不默认执行全量外部测试。
- 设备验证沿用用户指定设备;多设备且未指定时先确认。编写应用交互测试前,先通过 ARTEMIS 探索实际页面和操作路径。
- 提交前检查 `git diff --check` 和暂存范围;报告实际执行的验证及未完成项,不将未运行的检查写成通过。
- 提交前检查 `git diff --check` 和暂存范围,只暂存本次具体路径;报告实际执行的验证及未完成项,不将未运行的检查写成通过。
## Boundaries
@@ -79,5 +85,6 @@ Windows 首次安装使用 `start.bat`,国内镜像使用 `start-cn.bat`。启
## References
- [用户入口](README.md):安装、配置与日常使用。
- [设计约定](DESIGN.md):当前架构、行为与网页视觉规则。
- [AI 自动化](docs/ai-automation.md):MCP、Skill 及升级行为。
- [工作区说明](docs/automation-workspace.md):报告、批量导入和历史检索。
+139
View File
@@ -0,0 +1,139 @@
---
name: ARTEMIS 控制台
colors:
canvas: "#f8f9fa"
surface: "#ffffff"
text: "#1e293b"
text-strong: "#0f172a"
text-secondary: "#475569"
text-muted: "#64748b"
text-control: "#334155"
border: "#e2e8f0"
border-control: "#dbe3ec"
primary: "#1a73e8"
primary-hover: "#1557b0"
primary-soft: "#eff6ff"
exploration-action: "#4f46e5"
exploration-soft: "#eef2ff"
focus: "#a5b4fc"
success: "#047857"
success-soft: "#ecfdf5"
warning: "#92400e"
warning-soft: "#fffbeb"
error: "#b91c1c"
error-soft: "#fef2f2"
rounded:
compact: 8px
control: 10px
card: 16px
composer: 22px
spacing:
tight: 4px
icon: 6px
small: 8px
group: 12px
section: 16px
workspace: 24px
components:
model-selector:
backgroundColor: "{colors.surface}"
textColor: "{colors.text-control}"
rounded: "{rounded.control}"
demonstration-control:
height: 36px
exploration-composer:
backgroundColor: "{colors.surface}"
rounded: "{rounded.composer}"
---
# ARTEMIS 设计与交互约定
## Overview
ARTEMIS 面向使用自然语言执行、探索和复跑测试的人。当前产品包含 Flash、Pro、探索三种入口,按需调用安卓设备和电脑插件,提供批次、精选任务、历史、报告及证据。用户应能看懂当前在做什么、何时需要回答、哪些结果已有证据。
本文记录主程序的职责边界及 `apps/showcase_ui` 的现有视觉与交互规则,不提出新的主题改版。远程 SDK 的协议边界见[局部设计](packages/artemis-client/DESIGN.md);独立插件仓库维护适配器设计和构建规则。阶段性进度见[开发快照](docs/development-handoff.md),发行状态见[2.0.1 说明](docs/releases/2.0.1.md)。
## System architecture
| 层 | 职责与源码入口 |
| --- | --- |
| 网页、MCP、远程 SDK | 请求建模、交互与结果读取。`apps/showcase_ui`、`mcp_server`、`packages/artemis-client` 共同使用后台服务;MCP 和薄 SDK 不另启完整后台 |
| HTTP 与业务服务 | `apps/admin_console` 管理配置、统一队列、批次、收藏、历史、人工复验和导出;`TaskQueueService` 及现有资源锁负责执行准入与互斥 |
| 任务与模型 | `artemis/agents`、`artemis/runtime` 执行 Flash/Pro;`artemis/exploration` 管理交流、预算、示范与用例版本;`artemis/llm`、`artemis/services/llm.py` 统一 Responses 请求 |
| 插件与证据 | `artemis/plugins` 管理安装、会话、版本固定、来源、截图、录像和附件;插件执行工具动作,宿主管理任务生命周期和最终验收 |
| 配置与交付 | `artemis/config`、两份 `artemis.jsonc` 和环境配置提供默认值;`scripts` 负责启动、镜像、独立升级和源码打包 |
请求重试、业务执行与最终验收分别处理。提交使用稳定标识;创建批次或探索草稿不自动执行,查询不产生设备动作。执行准入和恢复遵守现有资源锁,任务固定所用插件版本,不能通过另建队列绕开互斥。
最终验收有独立总预算,按请求超时、重试和剩余任务时间协调,保留取证结果;完整有效的结构化结论才更新正式裁定。`completed`、工具成功、探索通过、用例提炼和复跑通过各自表达自己的状态。
示范来源保存在元数据中,贯穿分析、提问、规划、执行、验收和用例生成。补充网址不会隐式把电脑示范改到安卓;用户明确改端或提出跨端步骤时按其要求执行。证据必须对应真实来源、窗口、采集时间与操作。
录像使用用户选择的范围。人工示范的窗口模式跟随前台窗口,仅保留一路,无有效窗口时保留缺口;普通录屏在只读验收前停止。恢复操作再追加片段,时间映射以实际录像为准,不借用另一来源或时间的画面。
收藏保存独立模板与执行配置,用例复跑固定版本;删除原任务不破坏已保存模板。历史与导出的有效结论合并人工复验,不能用新裁定改写旧证据。
普通任务新安装默认 `gpt-6-luna / high`,探索单独使用 `gpt-6.1-sol / high`,保留已有配置。上下文参照上限 300k、默认历史压缩预算 80k,均不表示整条任务累计 token 限额。优化稳定前缀时保留关键文件回执、路径、校验和来源;费用收益依实际网关用量及计价判断。
## Colors
整体使用浅灰画布、白色内容面和深色正文。首页主要操作使用蓝色,探索发送及相关主操作保留现有靛色;绿色、琥珀色、红色用于结果与需要处理的状态。新控件复用所在区域的语义色。
| 用途 | 值与来源 |
| --- | --- |
| 画布 / 正文 | `#f8f9fa` / `#1e293b`,来自[全局样式](apps/showcase_ui/src/styles.scss) |
| 首页操作 / 悬停 | `#1a73e8` / `#1557b0`,来自[首页样式](apps/showcase_ui/src/app/pages/home/home.component.scss) |
| 探索操作 / 浅色状态面 | `#4f46e5` / `#eef2ff`,来自[探索面板](apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.scss) |
| 正文层级 / 边框 | 强调 `#0f172a`、次级 `#475569`、辅助 `#64748b`;常规边框 `#e2e8f0` |
| 成功、等待、错误 | 深色状态文字配对应浅色面;同时给出文字、图标或说明,结论不只依靠颜色 |
加载、空状态、断开、未验证与失败使用明确文案。执行完成可以有完成标识,测试裁定仍需独立展示;不能让绿色完成态替代测试通过结论。
## Typography
沿用全局字体栈:`Google Sans`、`Google Sans Text`、`Inter`、系统无衬线字体;中文使用系统可用字形。代码、路径及请求诊断使用现有等宽样式,保留换行与复制能力。
模型控件正文为 13px,探索输入正文为 14px,用例标题为 18px;辅助信息依现有组件使用 11–12px。保留字号、字重、留白形成的层次,不通过大段加粗堆叠状态。Markdown、表格、长链接和中文长标题应换行,不撑破容器。
图标沿用 Material Symbols Outlined,常规全局尺寸 20px;组件局部按原有尺寸调整。图标与相邻文字垂直居中,独立图标按钮提供可访问名称。
## Layout
- 首页负责创建任务与访问精选模板;工作区主区域展示当前任务的交流、执行步骤和结果,底部输入框始终服务当前任务。
- 编辑、模型设置和复跑参数按需展开,默认保持任务时间线可读。精选卡片在收起状态下等高,长标题、不同状态和展开内容不会遮挡操作入口。
- 通常以 4、6、8、12、16、24px 组织内部间距;沿用组件现有网格和断点。子容器允许收缩,长内容使用 `min-width: 0` 及合适换行。
- 探索面板在 600px 以下收紧外边距并允许操作换行;模型选择控件宽度跟随容器。窄屏仍可辨认状态、选择来源和发送指导。
- 列表后台刷新保留滚动、选择和折叠状态;首次加载与静默更新分开,避免周期性加载提示挤动内容。实时连接复用现有页面可见性管理。
## Elevation
卡片使用细边框和克制的阴影区分区域。首页现有玻璃效果、渐变和按钮悬停保留在原有区域,正文、报告、表单和选项保持清晰背景及文字对比。
探索输入区沿用 `0 6px 24px #0f172a08` 的阴影;焦点反馈沿用所在控件的轮廓和浅色描边。全局 CDK overlay 层级为 1200,新增弹层先使用既有 overlay 机制,不任意叠加更大的层级。
## Shapes
模型输入与选择框圆角 10px;紧凑控件常用 8px,内容卡片按组件使用 12–16px,探索输入区为 22px。保留现有徽标、标签与胶囊形控件的局部形状;这些值不要求把全部历史控件改成同一圆角。
## Components
| 组件或状态 | 必须维持的交互与尺寸 | 实现依据 |
| --- | --- | --- |
| 模型选择 | 预设选择加自定义输入;主模型、兜底和精选任务复用同一组件。最小高度 40px、内边距 10px 12px、圆角 10px,保留焦点与禁用态 | [model-selector](apps/showcase_ui/src/app/components/model-selector/model-selector.component.ts) |
| 人工示范控件 | 来源标签与控件水平对齐,选择框及开始按钮均为 36px 高、垂直居中;窄屏可换行。准备、录制、分析分别显示 | [exploration-panel](apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.scss) |
| 探索建议 | 推荐内容为用户口吻的可编辑回复;点击只填草稿,用户发送后才继续任务 | [探索组件](apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.ts) |
| 历史标题与星标 | 星标只占标题,第二行状态与编号正常左对齐;状态徽标沿用 20px 高及统一内边距 | [task-history](apps/showcase_ui/src/app/components/task-history/task-history.component.ts) |
| 验收状态 | 展示准备证据、核对、生成与校验等阶段及可用的耗时、预算、重试诊断;未完成的流式文字不变为正式通过标签 | [agent-stream](apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html) |
| 截图、录像与文件 | 显示真实来源和缺口;按时间定位并保留跳转前的播放/暂停状态,文件成功以真实保存回执为准 | [工作区说明](docs/automation-workspace.md) |
| 保存和错误反馈 | 配置加载、保存、空值、失败与密钥已配置状态有明确提示;空密钥保留已有值,连接测试与保存分开 | [模型配置](apps/showcase_ui/src/app/components/model-settings/model-settings.component.ts) |
这些规则对应已接受的现有行为。改动后结合实际层叠样式、渲染尺寸和相关状态检查,不能只核对一处声明。
## Do's and Don'ts
- 复用现有组件、API 类型、中文文案和状态语义。新增状态先说明它与执行、验收或人工操作的关系。
- 保持实际来源与证据一致,缺失时说明原因;不使用手机画面替代电脑操作,也不把工具成功当作业务通过。
- 保留可见焦点、禁用反馈和减少动画偏好;录制红点表示实际录制,等待或执行中使用各自文案。
- 源码已更新而页面仍旧时,先核对服务目录、构建与缓存;不通过反复增加样式覆盖掩盖旧构建问题。
- 视觉变化验证代表性宽度、长标题、加载、等待回答、错误与展开状态;文档格式检查不等于渲染验证。开发检查按[前端指南](apps/showcase_ui/AGENTS.md)执行。
+1 -1
View File
@@ -2,7 +2,7 @@
基于 [ARTEMIS](https://github.com/google/artemis) 扩展的 Android AI 自动化测试平台。用自然语言描述任务,即可通过简体中文网页或 MCP 驱动真机、模拟器执行操作并收集结果。模型统一使用 **OpenAI Responses API**。
**2.0.0 已发布**:新增交互探索、插件扩展和精选任务,增强跨端证据与 AI 接入。完整变化及升级步骤见 [2.0.0 发行说明](docs/releases/2.0.0.md)。
**2.0.1 已发布**:修复 Python 3.12 全新安装时网页服务无法启动的问题,并统一不同 Python 版本的 MCP 工具描述。修复与升级步骤见 [2.0.1 发行说明](docs/releases/2.0.1.md);探索、插件和精选任务等功能见 [2.0.0 发行说明](docs/releases/2.0.0.md)。
## 主要功能
+2
View File
@@ -1,5 +1,7 @@
"""Independent favorite snapshots dispatched through the existing task services."""
from __future__ import annotations
from copy import deepcopy
import asyncio
import hashlib
+8 -1
View File
@@ -2,12 +2,14 @@
## Local Purpose
本目录维护简体中文 Angular 网页,包括设备与任务操作、模型配置、报告、批量导入和历史检索。
本目录维护简体中文 Angular 网页,包括 Flash/Pro/探索、人工示范、插件、精选任务、模型配置、批次、历史、报告与跨端证据。
## Inherits
继承仓库根目录 AGENTS.md;本文件仅补充前端规则,当前用户指令优先。
修改 UI 前阅读根目录 [DESIGN.md](../../DESIGN.md) 的视觉与交互约定;本应用没有独立的局部 DESIGN.md。SDK 的同名文件描述远程协议,不是网页设计来源。
## Local Commands
在本目录执行,包管理器使用 npm 和现有 package-lock.json:
@@ -34,6 +36,11 @@ npm run build
- 使用相邻的 .spec.ts 测试交互和请求数据;不要只测试实现细节。
- 后端字段调整需同时核对类型、请求和响应;设备序列号、报告格式、批次编号等不能在前端猜测。
- UI 有明显变化时在浏览器验证实际布局和关键操作,构建通过不能代替可读性检查。
- 工作区底部输入框服务当前任务;探索建议只填入草稿,用户发送后再提交。用例编辑和配置按需展开,复跑固定保存版本。
- 主模型、兜底模型及精选任务复用现有模型选择组件;收藏使用已保存的独立配置,设备离线时给出可操作的选择提示。
- 星标仅占标题区域,后续状态行正常左对齐;人工示范来源与按钮保持等高、居中,窄屏可换行。执行、等待回答、示范准备/录制/分析、最终验收及未验证各有明确文字。
- 电脑步骤使用电脑证据,录像缺口明确展示。播放器按真实来源和时间定位,跳转保留原播放/暂停状态。
- 列表后台刷新保留选中项、滚动及折叠状态,不用反复插入的加载行挤动布局;实时连接沿用现有共享机制和页面可见性管理。
## Local Risks
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "artemis-showcase-ui",
"version": "2.0.0",
"version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "artemis-showcase-ui",
"version": "2.0.0",
"version": "2.0.1",
"dependencies": {
"@angular/cdk": "^22.1.5",
"@angular/common": "^22.1.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "artemis-showcase-ui",
"version": "2.0.0",
"version": "2.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
+4 -4
View File
@@ -145,16 +145,16 @@ class ReadinessEngine:
device check.
"""
cacheable = categories is None
request_started = time.monotonic()
cache_at_request = self._report_cache
if cacheable and not force_refresh:
cached = self._cached_report(self._REPORT_CACHE_TTL_SECONDS)
if cached is not None:
return cached
async with self._report_lock:
# A refresh that completed while this caller waited satisfies even a
# forced request that began before it, coalescing concurrent clicks.
if cacheable and self._report_cache_time >= request_started:
# A replacement report completed while this caller waited. Compare
# the snapshot itself: sequential requests can share a clock tick.
if cacheable and self._report_cache is not cache_at_request:
cached = self._cached_report(float("inf"))
if cached is not None:
return cached
+2
View File
@@ -1,5 +1,7 @@
"""Durable manual-demonstration commands, sharing the automation transaction."""
from __future__ import annotations
import time
from uuid import uuid4
+2
View File
@@ -4,6 +4,8 @@ The queue remains the sole scheduler. These records describe a conversation and
its immutable evidence/case versions, not a second execution state machine.
"""
from __future__ import annotations
from collections.abc import Callable
from contextlib import contextmanager
import hashlib
+4 -2
View File
@@ -27,7 +27,9 @@ if _REPO_ROOT not in sys.path:
# pylint: disable=wrong-import-position
from adbutils import AdbClient
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.fastmcp import Context
from artemis.mcp.tool_server import ArtemisFastMCP
logger = logging.getLogger(__name__)
@@ -86,7 +88,7 @@ def configure_stdio_mode() -> None:
# Create minimal MCP server
mcp = FastMCP("Android_ADB_Controller")
mcp = ArtemisFastMCP("Android_ADB_Controller")
_GLOBAL_CONTROLLER = None
_CONTROLLERS: dict[str, Any] = {}
+37
View File
@@ -0,0 +1,37 @@
"""Keep ARTEMIS tool descriptions stable across supported Python versions."""
from textwrap import dedent
from mcp.server.fastmcp import FastMCP
class ArtemisFastMCP(FastMCP):
"""Normalize docstring indentation before FastMCP publishes tool metadata."""
def add_tool(
self,
fn,
name=None,
title=None,
description=None,
annotations=None,
icons=None,
meta=None,
structured_output=None,
):
if not description:
# Python 3.13+ removes this indentation at compilation; 3.12 leaves
# it in __doc__. Retain blank lines, including the final newline,
# so the existing catalog and pinned schemas stay byte-for-byte equal.
first, separator, body = (fn.__doc__ or "").expandtabs().partition("\n")
description = first.lstrip() + separator + dedent(body)
super().add_tool(
fn,
name=name,
title=title,
description=description,
annotations=annotations,
icons=icons,
meta=meta,
structured_output=structured_output,
)
+146
View File
@@ -0,0 +1,146 @@
# 开发交接:2026-10-19
本文件保留 2026-10-19 从《Artemis开发主线-1017-2》迁入时的开发快照。历史结论已结合代码、发行说明和当时的本地 Git 引用核对;后续开发以[仓库指南](../AGENTS.md)、[设计约定](../DESIGN.md)和实时 Git 状态为准,以下提交号与分叉计数不随后续提交自动更新。
**迁入时插件仓库有本地改动及分叉,不能用该工作目录代表已发布版本。** 后续发布应明确本次范围,保留尚未归并的开发现场。
## 当前仓库与版本
两个项目是工作区中的独立 Git 仓库:主程序目录 `artemis`,相邻插件目录 `Artemis-Plugins`。后续分别检查各自的分支、改动和验证结果。
| 项目 | 本次核实的基线 | 工作区现场 |
| --- | --- | --- |
| 主程序 | `main`、本地 `eapil/main`、`v2.0.0` 标签解引用均为 `973ccae` | 已跟踪文件无修改;根目录存在未跟踪的 `.angular/` 缓存,不纳入提交 |
| 插件 | 本地 `main` 为 `1eb77e6`,本地 `origin/main` 为 `cf110b7`;本地分支领先 3 个、落后 6 个提交 | 有 OCU 修改、未跟踪的 Cua 适配源码及构建、滚动、基准测试文件,全部保留 |
以上远端分支名称指本地已有的跟踪引用,本次没有联网刷新。后续修改前需重新读取 Git 状态。
主程序 2.0.0 曾重新发布:首次合并提交为 `f4cb55c`,当前应以包含后续修复的 **`973ccae`** 为准。完整说明见 [2.0.0 发行说明](releases/2.0.0.md)。
插件存在三种不同状态,后续必须明确区分:
- 本地 `catalog.json` 仍列出 OCU `1.0.0`,本地 Cua 文档仍描述 `0.1.2` 候选。
- 本地 `origin/main` 的市场索引已改为 **Cua `0.36.0+artemis.4`**;OCU 已从市场入口下架,旧安装和历史保留。
- 用户机器实际安装的插件版本需另行查询,不能由仓库版本推定。
## 已完成的开发主线
| 范围 | 当前能力与约定 | 主要说明 |
| --- | --- | --- |
| 中文与模型调用 | 简体中文网页;模型传输统一 OpenAI Responses,移除 Gemini 原生分支;支持视觉、工具调用、流式及结构化结果 | [用户入口](../README.md) |
| 可保存配置 | 网页保存接口、模型、思考强度、超时重试和功能开关;保留已有配置,密钥查询只返回配置状态 | [配置示例](../.env.example) |
| 批量、历史与报告 | Excel 模板、校验与预览、按用例配置、循环与选中执行、暂停恢复;历史搜索分页、人工复验;Word、Excel、ZIP 与证据导出 | [工作区说明](automation-workspace.md) |
| 探索 | 持续交流、补充指导、人工示范、预算暂停继续;成功后提炼一个可编辑版本的用例,供 Flash/Pro 复跑或加入批次 | [探索说明](exploration-mode.md) |
| 插件与跨端证据 | 安装、授权、启用插件;统一资源调度、步骤来源、截图、录像、文件附件与证据索引 | [插件说明](plugins.md)、[开发规范](plugin-development.md) |
| 精选任务 | 收藏任务、探索用例和批次;独立保存模板与执行配置,首页复跑;旧收藏按实际记录补全配置 | [2.0.0 发行说明](releases/2.0.0.md) |
| MCP 与 Skill | 轻量连接用户已启动的主程序;覆盖执行、探索、收藏、批次、诊断与导出;启动时同步官方 Skill,并保留客户端工具策略 | [AI 接入](ai-automation.md)、[随仓库分发的 Skill](../skills/artemis-use/SKILL.md) |
| 性能与诊断 | 稳定多轮上下文与缓存前缀,减少重复摘要和无关手机截图;请求阶段与实际用量可追踪;复用未变化的终态资料包 | [2.0.0 发行说明](releases/2.0.0.md) |
| 安装与升级 | 官方源/国内镜像启动,独立快进升级,保护配置与历史,使用源码打包脚本 | [仓库指南](../AGENTS.md) |
设备动作回放曾在早期实现,随后已按用户要求移除。历史步骤、截图、只读回看与上下文回忆仍保留;不能因文件中出现 `replay` 就把这些能力当作待删代码,也不恢复旧动作回放方案。
## 最近修复及其原因
- **最终验收与请求预算冲突**:原先整段验收在 180 秒时取消,比单次请求 300 秒还短,使请求重试没有机会发生。现在最终验收有独立总预算,协调模型超时与重试,保留已有工具证据;只重试失败的模型请求,不重复设备业务动作。任务本身的执行时限仍有效。
- **完整结论后才显示通过**:有效结构化结果直接校验,减少重复模型格式转换;取消或截断的流式内容会丢弃。页面与下载诊断区分验收阶段、请求重试耗尽、总预算耗尽和用户中断。
- **电脑示范错误转到安卓执行**:实际示范来源贯穿分析、澄清、规划、执行、验收和用例提炼;规划同时获得可用插件信息。补充网址不等于更换平台,手机成功不能代替电脑验收。
- **窗口示范错误录制双屏**:人工示范遵循窗口/屏幕配置;窗口模式跟随当前前台窗口,只保留一路录像。无有效窗口时保留缺口,不扩大到整个桌面。
- **探索建议与布局**:建议使用用户口吻,点击先填入草稿,发送后才继续;星标只占标题位置,示范来源与按钮等高对齐。
历史上多次出现服务仍从旧工作树加载构建的问题。遇到“源码已改但页面没变”,先核对服务的实际源码目录和构建来源。本次确认当前虚拟环境中的 `artemis` 解析到主仓库;未检查或重启正在运行的服务。
## 默认设置与持续约束
| 设置 | 新安装默认值或上限 |
| --- | --- |
| 普通任务模型 / 思考强度 | `gpt-6-luna / high` |
| 探索模型 / 思考强度 | `gpt-6.1-sol / high`,单独保存 |
| 单次模型请求 | 300 秒;失败后最多重试 5 次,间隔 10 秒 |
| 视频分析 / 普通任务录屏 | 默认关闭;人工示范录制独立管理 |
| 探索主动执行预算 | 每段 1800 秒;等待回答和人工示范不计入,耗尽后暂停,可继续 |
| 人工示范时长 | 默认上限 900 秒,可延长;超时保存并暂停 |
| 上下文参照上限 / 默认历史压缩预算 | 300k / 80k token;不是整条任务累计用量限制 |
| 高效录像留档 | 默认长边 1280、15 fps;编码与回退遵循现有配置 |
已保存配置优先,不能把这些默认值强制写回用户环境。旧模型已从预设移除,仍可自定义输入;接口不支持指定思考强度时明确报错。
后续实现持续遵守:
- 网页、MCP、SDK 复用后台配置、队列、历史与报告服务;不另建业务调度器或第二套状态存储。MCP 连接用户启动的主程序。
- 创建批次与执行分开,提交保持 `request_id` 幂等;同设备和共享桌面资源按现有锁串行,任务固定插件版本。
- 模型请求重试不变成业务用例重提;结果未知的写操作先核对,不盲目重发。
- 执行结束、工具成功、独立业务验证、AI 最终裁定分别记录。缺少电脑证据、报告或关键检查项时不能直接判通过,也不改写旧任务结论来代替复测。
- 示范和截图保留实际来源;控件、截图编号、窗口几何或 DPI 变化后重新观察。录屏质量设置不降低模型定位截图质量。
- 只读最终验收前停止普通录屏;无录像时段明确保留缺口,历史丢失的画面不能补造。
- 业务环境恢复由任务提示词提出;不新增默认业务回滚系统。连接关闭、录制收尾、进程回收和资源锁释放仍由运行时负责。
- 密钥不进入 MCP 参数、查询结果、日志或报告。保护用户配置、历史与运行现场;新代码不包含个人设备号、绝对目录或凭据。
- 变更配置默认值时同步开发和打包配置;变更 MCP 工具时同步注册、安装白名单、测试、Skill 和能力文档。
缓存命中率按 API 实际用量加权统计,不能承诺固定命中率或把缓存输入当作消失的 token。历史公告中的“约 81%”是条件计算:假设输入命中率 90%、缓存输入单价为普通输入的 10%,输入费用可下降约 81%;不是总 token 或总账单的固定降幅。Cua 小样本中的约 26.1% token 差异也不能外推为普遍收益。
## 协同开发入口
| 修改目标 | 主仓库入口 |
| --- | --- |
| 模型传输、超时、重试与请求诊断 | `artemis/llm/`、`artemis/services/llm.py`、`apps/admin_console/services/request_diagnostics.py` |
| 默认值与配置保存 | `artemis/config/`、`config/artemis.jsonc`、`artemis/resources/config/artemis.jsonc`、`apps/admin_console/services/model_settings_service.py` |
| 探索、示范与用例提炼 | `artemis/exploration/`、`apps/admin_console/services/exploration_service.py` |
| 插件运行、截图、录像与附件 | `artemis/plugins/`、`apps/admin_console/routers/plugins.py` |
| 队列、批次、收藏、报告 | `apps/admin_console/services/`,优先复用现有服务 |
| 网页 | `apps/showcase_ui/`,先读[局部指南](../apps/showcase_ui/AGENTS.md) |
| MCP、远程 SDK、AI 使用规则 | `mcp_server/`、`packages/artemis-client/`、`skills/artemis-use/` |
| 插件适配包与市场 | 插件仓库的 `plugins/`、`scripts/`、`tests/`、`catalog.json`;先明确比较的是本地现场还是发布引用 |
下一次修改插件前,先比较分叉提交及全部本地改动,再按具体任务选择保留、迁移和合并范围。可在插件目录只读查看:
```powershell
rtk proxy git status --short --branch
rtk proxy git log --left-right --oneline main...origin/main
rtk proxy git diff --stat
rtk proxy git show origin/main:plugins/cua-driver/RELEASE.md
```
## 验证基线与仍需实测的范围
以下是**原开发记录及重新发行说明保存的结果,本次没有重跑**:后端默认测试 2943 项通过、2 项跳过、8 项按标记排除;前端 290 项通过,生产构建、Ruff 两项、质量基线与核心类型检查通过。最近的示范来源和录制范围修复未调用付费模型完整复跑业务任务。
后续不能据此宣称以下范围已全部验证:
- 用户原先失败的完整手机/电脑业务流程,以及最近修复后的真实示范、恢复、执行与验收闭环。
- 多 DPI、多显示器、复杂控制台、跨端长任务、长时间资源开销及并发压力。
- 安卓与电脑同时示范的完整组合、真实 15 分钟示范上限,以及外部 Agent 经安装后的 MCP 完整操作链。
- 网关缓存路由、辅助摘要调用的缓存收益和稳定费用降幅,需同任务实测及请求级记录支持。
Cua 仍为 Windows x64 预览。前台操作会占用用户桌面;PNG 保存成功不等于有效观察,少量通过任务也不证明所有浏览器、窗口或业务均可靠。
旧开发工作树和 `artifacts/worktree-archive` 已按用户要求删除,本次确认目录不存在。继续使用当前主目录及相邻插件目录,不重建旧归档作为开发前提。
涉及 Python 行为修改时,在主仓库执行规定检查:
```powershell
rtk proxy uv run pytest
rtk proxy uv run ruff format --check .
rtk proxy uv run ruff check .
rtk proxy uv run python scripts/quality_ratchet.py
rtk proxy uv run pyright --project pyright-core.json
rtk proxy git diff --check
```
前端修改在 `apps/showcase_ui` 执行,并验证实际布局与关键交互:
```powershell
rtk proxy npm test -- --watch=false --browsers=ChromeHeadless
rtk proxy npm run build
```
本机外部命令沿用 RTK 包装;PowerShell cmdlet 和控制流不套 RTK。默认测试不调用设备、私有服务或付费模型。实际应用交互先通过 ARTEMIS 探索,设备沿用用户指定对象。
## 本次交接的核对依据
| 证据 | 结论 | 后续使用方式 |
| --- | --- | --- |
| 用户迁入记录、[发行说明](releases/2.0.0.md)、主仓库 Git 日志及标签解引用 | 重发后的 2.0.0 基线为 `973ccae`,早期“未提交/未发布”状态已被后续记录覆盖 | 从当前主目录继续开发,按新需求修改 |
| 插件仓库 `git status`、分叉日志及 `git show origin/main:catalog.json` | 本地 OCU/Cua 候选现场与发布的 Cua `.4` 不一致 | 保留现场,先比较再处理分叉,不把旧候选文档当作最新市场状态 |
| 当前源码定位、配置默认值、[仓库指南](../AGENTS.md)与[前端指南](../apps/showcase_ui/AGENTS.md) | 架构边界、入口与验证命令可继续沿用 | 新任务先读对应局部规则,按变更范围验证 |
`docs/plans/` 是阶段性设计记录,其中“未发布”“待验证”等文字保留各自时点含义;当前发布状态以发行说明、对应版本源码及实际验证记录为准。
+27
View File
@@ -0,0 +1,27 @@
# ARTEMIS-Eapil 2.0.1
2.0.1 修复 Windows 全新安装使用 Python 3.12 时,网页构建成功后管理服务立即退出的问题。已有 2.0.0 安装也建议升级;探索、插件和精选任务等功能继续沿用 [2.0.0](2.0.0.md)。
## 修复内容
- **全新安装可以启动网页服务**:探索存储、人工示范和收藏服务中的 `list` 方法遮蔽了同名内置类型,导致 Python 3.12 在加载后续类型注解时抛出 `TypeError: 'function' object is not subscriptable`。统一延迟求值,并增加冷启动与运行时类型注解回归,覆盖较新 Python 延迟暴露同一问题的情况。
- **诊断强制刷新按请求生效**:连续请求落在同一个时钟刻度时,仍会重新扫描;并发请求继续合并,避免重复检查。
- **MCP 工具描述跨版本一致**:统一 Python 3.12 与较新版本编译后文档字符串的缩进差异,保持工具目录、固定协议快照及现有显式描述一致。
本版不要求升级 Python,也不更改已有配置、任务历史或插件版本。
## 升级与安装
1. 先结束正在运行的任务,再运行 `update.bat`;国内网络使用 `update-cn.bat`。升级程序会停止当前项目的服务与任务进程,并同步依赖环境。
2. 完成后运行 `start.bat` / `start-cn.bat`,等待网页构建并打开页面。启动脚本只检查更新,不代替升级脚本。
3. 全新安装下载 `artemis-eapil-2.0.1-source.zip`,解压后运行上述启动脚本。用附件 `SHA256SUMS.txt` 校验下载完整性。
源码包保留浅层 Git 更新能力,不包含依赖环境、用户密钥、任务历史或预编译网页。升级主程序不会自动升级已安装插件。
## 发布验证
- Python 3.12 与 3.14 的默认后端测试分别为 **2952 项通过、6 项跳过、8 项按标记排除**。新增启动回归、同一时钟刻度下的诊断刷新回归及跨版本 MCP 描述回归;现有工具目录与协议快照无需修改。
- 在 Python 3.12 隔离环境运行真实服务生命周期,首页、API 文档、收藏和探索接口均返回 HTTP 200,服务正常关闭。仅替代硬件预热和防休眠入口,数据库、队列、IPC、MCP 会话及 HTTP 服务沿用真实实现。
- 前端 **290 项测试通过**,2.0.1 生产构建成功。Ruff 格式与静态检查、质量基线和核心类型检查通过。
本次未调用付费模型或执行设备业务任务,未替用户升级或重启现有安装。
+2 -2
View File
@@ -16,7 +16,7 @@
import logging
from mcp.server.fastmcp import FastMCP
from artemis.mcp.tool_server import ArtemisFastMCP
logger = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ except Exception as exc:
logger.debug("FastMCP Settings.model_rebuild skipped: %s", exc, exc_info=True)
# Define the shared FastMCP instance for external IDE and agent clients.
mcp = FastMCP(
mcp = ArtemisFastMCP(
"artemis",
instructions=(
"ARTEMIS executes Android, computer and mixed tests with Flash, Pro or interactive exploration. The user starts the service; MCP only connects. Use the official artemis-use Skill for workflows and plugin development. "
+2 -2
View File
@@ -11,9 +11,9 @@ import logging
import threading
from typing import get_type_hints
from mcp.server.fastmcp import FastMCP
from starlette.responses import JSONResponse
from artemis.mcp.tool_server import ArtemisFastMCP
from artemis.service_registry import TOKEN_HEADER
@@ -85,7 +85,7 @@ def create_shared_mcp():
from mcp_server import tools
executor = ToolExecutor()
shared = FastMCP(
shared = ArtemisFastMCP(
"artemis",
instructions=definitions.instructions,
streamable_http_path="/",
+38 -5
View File
@@ -1,5 +1,10 @@
# Artemis Thin Client Design
This document describes the remote SDK, not the web console's visual design.
The shared runtime and UI boundaries are documented in the repository's
[DESIGN.md](../../DESIGN.md). Current behavior below includes the 2.0 exploration
adapter; versioned remote endpoints remain a protocol evolution proposal.
## Goals
Developer machines install only a small remote SDK, while the host connected to
@@ -34,9 +39,36 @@ session database, the client reconstructs its state from `queue` and
Experimental parameters live in `options`. Older servers ignore unknown fields,
while newer servers can consume them without requiring an immediate SDK release.
## Phase 2: Stable Remote API
### Guided exploration in 2.0
The server should expose versioned endpoints dedicated to remote clients:
`ArtemisClient.exploration()` uses the existing `/api/explorations` API shared
with the web workspace and MCP. It supports read-only list/get/case queries,
session lifecycle and messages, manual demonstrations, immutable case versions,
generation, reruns, and batch-draft creation. It adds no scheduler or local agent.
Mutations require a stable `request_id`, except pause/stop. Reusing a request ID
must not repeat the business operation. Read operations reject mutation fields;
creating a batch draft does not start it. Case generation returns a job whose
state is read from the exploration session, rather than blocking the client for
the entire model call.
Execution status, test verdict, case generation, and rerun results are separate.
`TaskResult.succeeded` currently reflects execution status only; it does not
prove that the test passed. Consumers must inspect the server's effective test
result and evidence, including `runs[].test_result` for exploration, and retain
unknown response fields through `raw`. A client polling timeout does not cancel
the server task; stopping it is a separate explicit operation.
The host owns source metadata, demonstration recording scope, execution budgets,
model retries, final acceptance, and artifact persistence. The client passes the
requested settings through the shared API and never reconstructs missing PC
evidence from Android screenshots or restarts a case after a request failure.
## Phase 2: Proposed stable remote API
The following endpoints are an evolution proposal, not the current submission
API. Do not replace the working compatibility adapter with these routes until
the host implements and advertises them:
```text
POST /api/v1/tasks
@@ -48,8 +80,9 @@ GET /api/v1/devices
GET /api/v1/capabilities
```
The administration API and remote SDK API should remain separate. The remote API
uses a consistent error envelope:
A future remote surface can separate administration access from task access
while delegating to the same backend services and scheduler. The proposed error
envelope is:
```json
{
@@ -66,7 +99,7 @@ uses a consistent error envelope:
1. `/api/v1` may add optional request and response fields, but must not change existing field semantics.
2. The client ignores unknown response fields and preserves the original response in each model's `raw` attribute.
3. Every new request field must have a server-side default.
4. The SDK checks `/api/v1/capabilities` before using a new feature.
4. `capabilities()` probes `/api/v1/capabilities` and falls back to the known baseline on 404; future feature gating must not pretend that this fallback advertises new host features.
5. Breaking changes ship under `/api/v2`; they are never applied in place.
6. The SDK and server follow semantic versioning independently.
+1 -1
View File
@@ -14,7 +14,7 @@
[project]
name = "artemis"
version = "2.0.0"
version = "2.0.1"
description = "Autonomous AI Assistant for Mobile Automation"
authors = [
+15 -2
View File
@@ -15,6 +15,7 @@
"""Unit tests for Artemis System Diagnostics & Readiness Engine."""
import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock
import pytest
@@ -173,7 +174,13 @@ async def test_dashboard_reuses_recent_confirmed_state_on_one_timeout(monkeypatc
@pytest.mark.asyncio
async def test_readiness_engine_coalesces_concurrent_full_scans():
@pytest.mark.parametrize("same_clock_tick", [False, True])
async def test_readiness_engine_coalesces_concurrent_full_scans(same_clock_tick, monkeypatch):
if same_clock_tick:
monkeypatch.setattr(
"artemis.core.diagnostics.engine.time",
SimpleNamespace(monotonic=lambda: 100.0, time=lambda: 100.0),
)
engine = ReadinessEngine()
result = ProbeResult(
id="test_probe",
@@ -200,7 +207,13 @@ async def test_readiness_engine_coalesces_concurrent_full_scans():
@pytest.mark.asyncio
async def test_readiness_engine_reuses_cache_until_forced():
@pytest.mark.parametrize("same_clock_tick", [False, True])
async def test_readiness_engine_reuses_cache_until_forced(same_clock_tick, monkeypatch):
if same_clock_tick:
monkeypatch.setattr(
"artemis.core.diagnostics.engine.time",
SimpleNamespace(monotonic=lambda: 100.0, time=lambda: 100.0),
)
engine = ReadinessEngine()
result = ProbeResult(
id="test_probe",
+57
View File
@@ -0,0 +1,57 @@
from mcp.types import ToolAnnotations
import pytest
from artemis.mcp.tool_server import ArtemisFastMCP
@pytest.mark.asyncio
@pytest.mark.parametrize("registration", ["decorator", "add_tool"])
@pytest.mark.parametrize(
("docstring", "expected"),
[
("Summary.\n\n Details.\n Nested.\n ", "Summary.\n\nDetails.\n Nested.\n"),
("Summary.\n\nDetails.\n Nested.\n", "Summary.\n\nDetails.\n Nested.\n"),
("One line.", "One line."),
],
)
async def test_tool_descriptions_match_across_docstring_formats(registration, docstring, expected):
server = ArtemisFastMCP("description-test")
def probe(count: int = 1) -> str:
return str(count)
probe.__doc__ = docstring
if registration == "decorator":
assert server.tool()(probe) is probe
else:
server.add_tool(probe)
(tool,) = await server.list_tools()
assert tool.description == expected
assert tool.inputSchema["properties"]["count"]["type"] == "integer"
assert probe.__doc__ == docstring
@pytest.mark.asyncio
async def test_explicit_tool_metadata_is_preserved():
server = ArtemisFastMCP("description-test")
def probe() -> str:
"""The fallback docstring."""
return "ok"
description = "Explicit description.\n Keep this indentation.\n"
server.add_tool(
probe,
"renamed_probe",
"Probe title",
description,
annotations=ToolAnnotations(readOnlyHint=True),
meta={"source": "test"},
structured_output=False,
)
(tool,) = await server.list_tools()
assert tool.name == "renamed_probe"
assert tool.title == "Probe title"
assert tool.description == description
assert tool.annotations.readOnlyHint is True
assert tool.meta == {"source": "test"}
+61
View File
@@ -0,0 +1,61 @@
"""Cold imports and runtime annotations must work on the installer's Python."""
from importlib import import_module
import inspect
from pathlib import Path
import subprocess
import sys
from textwrap import dedent
from typing import get_type_hints
import pytest
@pytest.mark.parametrize(
("module_name", "class_name"),
[
("artemis.exploration.store", "ExplorationStore"),
("artemis.exploration.demonstrations", "Demonstrations"),
("apps.admin_console.services.favorites", "FavoriteService"),
],
)
def test_startup_service_annotations_do_not_shadow_builtin_collections(module_name, class_name):
service = getattr(import_module(module_name), class_name)
# Python 3.14 defers these errors until annotations are read. Evaluating them
# also detects the import failure seen immediately on Python 3.12 and 3.13.
assert get_type_hints(service.list)["return"] == list[dict]
for method in vars(service).values():
if inspect.isfunction(method):
get_type_hints(method)
def test_ui_server_cold_import_serves_empty_workspace(tmp_path, monkeypatch):
monkeypatch.setenv("ARTEMIS_APP_DIR", str(tmp_path / "app"))
monkeypatch.setenv("ARTEMIS_TRACES_DIR", str(tmp_path / "traces"))
code = dedent("""
from fastapi.testclient import TestClient
from apps.admin_console.server import app
# No lifespan: validate startup imports and HTTP routes without device
# discovery, background workers, or a running user's service.
client = TestClient(app, base_url="http://localhost")
try:
for path in ("/api/automation/favorites", "/api/explorations"):
response = client.get(path)
assert response.status_code == 200, response.text
assert response.json() == [], response.text
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert "/api/explorations" in response.json()["paths"]
finally:
client.close()
""")
result = subprocess.run(
[sys.executable, "-X", "utf8", "-c", code],
cwd=Path(__file__).resolve().parents[2],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert result.returncode == 0, result.stdout + result.stderr
Generated
+1 -1
View File
@@ -206,7 +206,7 @@ wheels = [
[[package]]
name = "artemis"
version = "2.0.0"
version = "2.0.1"
source = { editable = "." }
dependencies = [
{ name = "adbutils" },