chore: update macOS deployment target to 14.0 and commit pending changes

This commit is contained in:
Haitao Pan 2026-06-25 09:52:33 +08:00
parent 7765a7211d
commit 09352b35a0
23 changed files with 1162 additions and 49 deletions

View File

@ -79,6 +79,12 @@ mobile:
build_modes: [debug, profile, release]
description: Mobile runtime logs view
ui_surface: settings_page
help:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
description: Mobile setup help and installation guide
ui_surface: settings_page
account_access:
enabled: true
release_tier: stable
@ -186,6 +192,12 @@ desktop:
build_modes: [debug, profile, release]
description: Desktop runtime logs view
ui_surface: settings_page
help:
enabled: true
release_tier: stable
build_modes: [debug, profile, release]
description: Desktop setup help and installation guide
ui_surface: settings_page
account_access:
enabled: true
release_tier: stable

View File

@ -0,0 +1,65 @@
# XWorkmate App Review Test Cases环境与覆盖矩阵
> 适用目录:`xworkmate-app/docs/cases/`
>
> 目标:为 Apple 审核、公网 Bridge、自建 Bridge、本地 Bridge、典型 AI 工作流提供稳定、可复现、可回归的测试用例。
## 1. 测试账号与环境变量
### 1.1 云端账号
| 项目 | 值 |
|---|---|
| 账号类型 | 只读评审账号Apple 审核专用) |
| 服务地址 | `https://accounts.svc.plus` |
| 邮箱 / 账号 | `review@svc.plus` |
| 密码 | 从安全变量读取:`XWORKMATE_REVIEW_PASSWORD` |
> 不建议将真实密码提交到仓库。Apple 审核备注、CI Secret、本地 `.env.local` 可保存真实值。
### 1.2 公网 xworkmate-bridge正式 Token 组合
```bash
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
BRIDGE_AUTH_TOKEN=${BRIDGE_AUTH_TOKEN}
```
### 1.3 公网 xworkmate-bridgeReview Token 组合
```bash
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
BRIDGE_REVIEW_AUTH_TOKEN=${BRIDGE_REVIEW_AUTH_TOKEN}
```
### 1.4 本地 xworkmate-bridge开发组合
```bash
BRIDGE_SERVER_URL=http://127.0.0.1:8787
BRIDGE_AUTH_TOKEN=${BRIDGE_AUTH_TOKEN}
```
## 2. 通用验收标准
- App 能完成账号登录、Bridge 连接、Token 校验、会话初始化。
- 网络异常、Token 失效、Bridge 不可达时App 不崩溃,并给出明确错误提示。
- 长任务执行期间,状态流、日志流、任务结果、取消重试行为保持稳定。
- 同一任务重复执行 3 次,产物结构和关键字段稳定,不出现空白页、卡死、重复提交、状态错乱。
- App 从前台切后台再恢复,任务状态可以继续读取或明确提示已中断。
## 3. 五类典型任务覆盖矩阵
| 用例 | 核心链路 | 主要覆盖点 | 稳定性关注 |
|---|---|---|---|
| AI 资讯 Markdown | 搜索 / 摘要 / Markdown 生成 | 长文本、引用、结构化输出 | 流式输出中断、重复标题、空结果兜底 |
| 图片制作视频 | 图片输入 / 脚本 / 分镜 / 视频指令 | 多模态任务、素材引用、步骤编排 | 附件丢失、任务超时、进度状态错乱 |
| 7 张连续图片 | 连续图文 / 风格一致 / 批量生成 | 多步骤连续产物 | 序号错乱、风格漂移、单张失败重试 |
| 多平台软文矩阵 | 公众号 / 小红书 / 视频号 / 推文 | 同主题多平台改写 | 语气失控、长度失控、平台字段缺失 |
| 章节拆解生成图文 PDF | 长文拆解 / 图文页 / PDF 导出 | 长链路 Artifact | 内存占用、导出失败、页面丢失 |
## 4. 回归优先级
P0登录、Bridge 连接、Token 校验、会话创建、任务状态流。
P1长任务恢复、取消、重试、错误提示、Artifact 列表刷新。
P2多平台文本质量、图片风格一致性、PDF 排版一致性。

View File

@ -0,0 +1,43 @@
# Case 01AI 资讯 Markdown 生成
## 目标
验证 XWorkmate App 在连接公网或本地 xworkmate-bridge 后能稳定执行“AI 资讯收集 → 摘要 → Markdown 文档生成”的典型文本任务。
## 前置条件
- 已登录只读评审账号。
- Bridge 已连接成功。
- 当前会话可创建任务并接收状态流。
## 输入提示词
```text
请整理最近 AI 领域值得技术人关注的 5 条资讯,输出 Markdown。
要求:
1. 每条包含标题、发生时间、核心变化、对开发者的影响。
2. 不要堆砌新闻,要提炼趋势。
3. 最后给出 3 条行动建议。
```
## 执行步骤
1. 打开 XWorkmate App。
2. 使用 Review 账号登录。
3. 选择公网 Bridge 或本地 Bridge。
4. 新建任务并输入提示词。
5. 等待任务完成,查看 Markdown 结果。
6. 复制或导出结果,确认格式完整。
## 预期结果
- 任务能进入 Running 状态,并持续返回日志或进度。
- Markdown 结构完整,包含标题、列表、结论。
- 不出现空白结果、重复输出、乱码、半截 Markdown。
- 网络短暂波动后App 能恢复状态或明确提示失败原因。
## 稳定性检查
- 连续执行 3 次,任务状态不串线。
- 切后台 30 秒后返回,结果仍可查看。
- Bridge Token 错误时,能显示授权失败,而不是无限 Loading。

View File

@ -0,0 +1,44 @@
# Case 02图片制作视频任务
## 目标
验证“图片素材 → 视频脚本 → 分镜规划 → 生成指令”的多模态长链路任务是否稳定。
## 前置条件
- App 已登录。
- Bridge 连接成功。
- 至少准备 1 张测试图片。
## 输入提示词
```text
基于这张图片,帮我生成一个 30 秒短视频方案。
要求:
1. 输出视频标题。
2. 拆成 5 个分镜。
3. 每个分镜包含画面描述、旁白、字幕、转场建议。
4. 风格适合技术公众号和视频号。
5. 最后输出可交给视频生成工具的结构化指令。
```
## 执行步骤
1. 上传或选择一张测试图片。
2. 新建图片制作视频任务。
3. 输入提示词并开始执行。
4. 观察上传、任务创建、执行、结果展示全过程。
5. 复制分镜结果,确认结构完整。
## 预期结果
- 图片能被正确绑定到任务。
- 任务执行过程中不丢失附件引用。
- 输出包含标题、5 个分镜、旁白、字幕、转场建议。
- 若图片上传失败,应明确提示,不应继续生成无图任务。
## 稳定性检查
- 大图、普通图各执行 1 次。
- 弱网环境下,上传失败可重试。
- 用户取消任务后,不应继续追加输出。

View File

@ -0,0 +1,37 @@
# Case 037 张连续图片生成
## 目标
验证连续 7 张图文图片任务在风格一致性、顺序稳定性、失败重试方面的表现。
## 输入提示词
```text
围绕“AI-Native 技术人成长路线图”,规划 7 张连续图片。
要求:
1. 每张图有页码、标题、核心观点、3 个关键词。
2. 蓝白科技风,适合公众号竖版图。
3. 7 张图之间逻辑连续,不要重复。
4. 输出每张图的文案与设计提示词。
```
## 执行步骤
1. 新建连续图片任务。
2. 输入提示词。
3. 等待系统生成 7 页图文规划或图片提示词。
4. 检查每一页是否完整。
5. 记录任务耗时、失败页、重试结果。
## 预期结果
- 输出严格包含 7 张,不多不少。
- 每张图都有页码、标题、核心观点、关键词。
- 主题连续,表达不重复。
- 单页生成失败时,可单页重试,不影响已完成页面。
## 稳定性检查
- 执行期间切换页面,任务状态不丢失。
- 第 4 张之后仍保持同一风格约束。
- 重试后页码顺序不乱。

View File

@ -0,0 +1,39 @@
# Case 04多平台软文矩阵生成
## 目标
验证同一主题在公众号、小红书、视频号、朋友圈、推文等多平台之间的改写稳定性。
## 输入提示词
```text
主题:企业为什么最终会走向平台工程。
请生成多平台软文矩阵:
1. 公众号 500 字观点文。
2. 小红书 300 字种草式表达。
3. 视频号 60 秒口播稿。
4. 朋友圈 100 字转发文案。
5. 推文 280 字以内。
要求:同一观点,不同平台语气;不要标题党;适合技术人阅读。
```
## 执行步骤
1. 新建软文矩阵任务。
2. 输入主题和平台要求。
3. 等待任务完成。
4. 分平台检查文案结构、长度和语气。
5. 导出或复制结果。
## 预期结果
- 输出包含 5 个平台版本。
- 每个平台有清晰标题或字段标识。
- 公众号版本偏观点深度,小红书更轻,视频号适合口播。
- 不出现平台漏项、字段混乱、同文复制。
## 稳定性检查
- 长度限制能被大体遵守。
- 多次生成不丢平台字段。
- 结果页刷新后仍能看到完整产物。

View File

@ -0,0 +1,41 @@
# Case 05章节拆解生成图文 PDF
## 目标
验证“长章节输入 → 图文拆解 → 页面规划 → PDF 产物”的长链路 Artifact 任务稳定性。
## 输入提示词
```text
请把下面这章内容拆解成一份 7 页图文 PDF。
要求:
1. 第 1 页是封面观点。
2. 第 2-6 页每页一个核心小节。
3. 第 7 页是总结和行动建议。
4. 每页包含标题、主文案、视觉建议、关键词。
5. 最后输出 PDF 结构目录。
章节内容:
企业上云不是一次资源搬迁,而是业务边界、成本结构、治理能力和交付模式的重建……
```
## 执行步骤
1. 新建章节拆解任务。
2. 粘贴长文本章节。
3. 执行图文 PDF 规划任务。
4. 检查页面数量、章节顺序、封面和总结页。
5. 若支持导出 PDF执行导出并打开检查。
## 预期结果
- 页面规划完整,严格 7 页。
- 每页包含标题、主文案、视觉建议、关键词。
- PDF 导出成功时,页面不丢失、不空白、不乱序。
- PDF 导出失败时,错误信息清晰,并保留中间图文结构。
## 稳定性检查
- 长文本输入不会导致 App 卡死。
- 导出期间切后台,回到前台后状态可见。
- Artifact 列表刷新后PDF 或中间结果仍可访问。

View File

@ -5,6 +5,7 @@
## 当前入口
- [核心功能集成测试手动 Case](./core-integration-manual-cases.md)
- [云端账号与 XWorkmate Bridge 连接手动 Case](./cloud-account-and-bridge-manual-cases.md)
- [云原生 Service Mesh 网络科普视频调研场景测试用例](./service-mesh-evolution-video-scenario/README.md)
- [OpenClaw Gateway 5 并发 E2E 回归场景](./openclaw-gateway-e2e-regression/README.md)

View File

@ -0,0 +1,489 @@
# 云端账号与 XWorkmate Bridge 连接手动 Case
本文档整理 Apple 审核专用只读账号、svc.plus 云端同步、以及公网 / 本地 `xworkmate-bridge` 接入的手动验证用例。
## 1. 测试账号与连接参数
### 1.1 云端账号
| 项目 | 内容 |
|------|------|
| 账号类型 | 只读评审账号Apple 审核专用) |
| 服务地址 | `https://accounts.svc.plus` |
| 邮箱 / 账号 | `review@svc.plus` |
| 密码 | `***REMOVED-CREDENTIAL***` |
### 1.2 公网 xworkmate-bridge 组合 1
| 环境变量 | 值 |
|----------|----|
| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` |
| `BRIDGE_AUTH_TOKEN` | `***REMOVED-CREDENTIAL***` |
### 1.3 公网 xworkmate-bridge 组合 2
| 环境变量 | 值 |
|----------|----|
| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` |
| `BRIDGE_REVIEW_AUTH_TOKEN` | `***REMOVED-CREDENTIAL***` |
### 1.4 本地 xworkmate-bridge
| 环境变量 | 值 |
|----------|----|
| `BRIDGE_SERVER_URL` | `http://127.0.0.1:8787` |
| `BRIDGE_AUTH_TOKEN` | `***REMOVED-CREDENTIAL***` |
---
## 2. 通用证据记录要求
每个 case 执行后建议记录:
- 当前 App 版本 / 构建号
- 当前平台与网络环境
- 当前入口:`Settings -> Integrations`
- 当前页签:`svc.plus 云端同步` 或 `AI 智能体工作空间`
- 服务地址 / Bridge 地址
- token 类型:`BRIDGE_AUTH_TOKEN` 或 `BRIDGE_REVIEW_AUTH_TOKEN`
- 连接测试结果摘要
- 是否出现 secret 明文
- 截图点:保存前、保存后、重新进入设置页
---
## 3. 五类典型任务
以下五类任务用于验证 App 登录后能否通过当前连接方式稳定创建任务、执行技能、生成文件产物并回写到当前线程。
| 任务编号 | 类型 | 验收产物 |
|----------|------|----------|
| CASE-001 | 采集最新 AI 资讯 | `.md` 文件 |
| CASE-002 | 附件图片制作视频 | 视频文件,附件图片被使用 |
| CASE-003 | 安全身份演进连续图片 | 7 张连续风格图片 |
| CASE-004 | 多平台软文矩阵 | 多个 `.md` 文件 |
| CASE-005 | 章节拆解 + Codex + GPT images2 + PDF | 汇总排版后的 PDF |
### `TASK-CASE-001` 采集最新 AI 资讯并保存 Markdown
- 输入提示词
```text
采集最新AI资讯保存在md文件
```
- 期望结果
- 任务能联网采集最新 AI 资讯
- 结果保存为 Markdown 文件
- 线程结果区展示文件产物
- Markdown 内包含标题、来源、摘要和时间信息
- 建议记录项
- 任务线程 ID
- 输出 `.md` 文件路径
- 资讯来源数量
- 截图点:任务完成后的 artifact 区域
### `TASK-CASE-002` 附件图片制作视频
- 前置条件
- 当前线程上传至少 1 张图片附件
- 输入提示词
```text
制作视频,附件带有图片
```
- 期望结果
- 任务识别并使用用户上传的图片附件
- 输出视频文件
- 失败时错误信息明确说明缺少图片、视频生成失败或依赖不可用
- 产物归属当前线程 workspace
- 建议记录项
- 附件图片文件名
- 输出视频路径
- 视频时长和分辨率
- 截图点:附件与视频产物
### `TASK-CASE-003` 安全身份演进连续 7 张图片
- 输入提示词
```text
从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进,连续制作 7 张一系列图片
```
- 期望结果
- 输出 7 张图片
- 7 张图片主题分别覆盖单机权限、网络边界、Web 安全、云身份、Zero Trust、AI Agent 身份、AI 模型与知识保护
- 图片风格、尺寸、命名方式保持连续一致
- 线程结果区能看到完整图片系列
- 建议记录项
- 7 张图片路径
- 图片尺寸
- 是否存在缺图或主题错位
- 截图点:图片系列列表
### `TASK-CASE-004` 安全身份演进多平台软文矩阵
- 输入提示词
```text
围绕 从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进
1. 输出Markdown格式文件 微信公众号短图文 400-600字 插入关键词的软文
2. 输出Markdown格式文件 小红书风格 600-800字 插入钩子话题的软文
3. 输出Markdown格式文件 X文案串 小于144字的英语 鲜明的观点
4. 输出Markdown格式文件 微信公众号文章 800-1200字左右
5. 输出Markdown格式文件 头条号长文 800-1200字左右
```
- 期望结果
- 输出 Markdown 格式文件
- 至少包含微信公众号短图文、小红书风格、X 文案串、微信公众号文章、头条号长文五类内容
- 字数、语言和平台风格符合输入要求
- X 文案串为英语且单条小于 144 字符
- 内容围绕同一条安全身份演进主线,不跑题
- 建议记录项
- 输出文件路径
- 每个平台内容字数
- X 文案字符数
- 截图点Markdown 产物列表
### `TASK-CASE-005` 安全身份演进章节拆解生成图文 PDF
- 输入提示词
```text
从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进
拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 -> 输出 PDF
```
- 期望结果
- 任务先拆分章节,再逐章生成内容
- 每章调用 Codex 生成或整理章节文本
- 每章调用 GPT images2 生成配图
- 最终汇总排版为 PDF
- PDF 中章节顺序与演进主线一致,图片与章节内容匹配
- 建议记录项
- 章节清单
- 每章图片路径
- 输出 PDF 路径
- PDF 页数
- 截图点PDF artifact 与预览页
---
## 4. 连接云端账号
### `MANUAL-CLOUD-001` 只读评审账号登录
- 前置条件
- App 可访问 `https://accounts.svc.plus`
- 当前未登录,或已退出其他账号
- 操作步骤
1. 打开 `Settings -> Integrations`
2. 切换到 `svc.plus 云端同步`
3. 在 `服务地址` 输入 `https://accounts.svc.plus`
4. 在 `邮箱或账号` 输入 `review@svc.plus`
5. 在 `密码` 输入 `***REMOVED-CREDENTIAL***`
6. 点击 `登录`
7. 等待账号同步完成
- 期望结果
- 登录成功,账号状态稳定显示为已登录或同步完成
- App 不展示密码明文
- 若账号侧托管 bridge 配置可用,设置页可同步到对应连接配置
- 只读评审账号不能触发破坏性写入或管理动作
- 建议记录项
- 登录账号
- 服务地址
- 登录结果摘要
- 同步后的 bridge endpoint
- 截图点:登录成功后的 `svc.plus 云端同步`
### `MANUAL-CLOUD-002` 退出后重新登录保持稳定
- 前置条件
- 已完成 `MANUAL-CLOUD-001`
- 操作步骤
1. 在设置页退出当前账号
2. 关闭或返回设置页
3. 再次进入 `Settings -> Integrations -> svc.plus 云端同步`
4. 使用 `review@svc.plus` / `***REMOVED-CREDENTIAL***` 重新登录
5. 观察同步状态与本地配置状态
- 期望结果
- 退出后不会继续显示已登录状态
- 重新登录成功
- 重新登录后同步状态可恢复
- 本地已保存的手动 bridge override 不应被异常覆盖
- 建议记录项
- 退出前后账号状态
- 重新登录结果
- 同步前后 endpoint 对比
### `MANUAL-CLOUD-TASKS-001` 云端账号登录后执行五类典型任务
- 前置条件
- 已完成 `MANUAL-CLOUD-001`
- 云端同步状态稳定
- 当前线程可创建新任务
- 操作步骤
1. 使用 `review@svc.plus` 登录 `svc.plus 云端同步`
2. 确认账号同步完成
3. 新建或进入一个测试线程
4. 依次执行 `TASK-CASE-001``TASK-CASE-005`
5. 每个任务完成后记录产物路径和结果摘要
- 期望结果
- 五类任务均能在云端账号连接上下文下启动
- 任务产物均归属当前 App 线程
- 云端账号只读属性不影响正常评审用任务执行
- 执行过程中不暴露密码、session 或 bridge token 明文
- 建议记录项
- 登录账号
- 五类任务的线程 ID
- 五类任务的产物路径
- 失败任务的错误摘要
---
## 5. 连接公网 xworkmate-bridge
### `MANUAL-BRIDGE-REMOTE-001` 公网 bridge 使用 `BRIDGE_AUTH_TOKEN`
- 前置条件
- 当前网络可访问 `https://xworkmate-bridge.svc.plus`
- 准备公网组合 1 的 `BRIDGE_AUTH_TOKEN`
- 操作步骤
1. 打开 `Settings -> Integrations`
2. 切换到 `AI 智能体工作空间`
3. 在 `Bridge 地址` 输入 `https://xworkmate-bridge.svc.plus`
4. 在 `鉴权令牌 (TOKEN)` 输入 `***REMOVED-CREDENTIAL***`
5. 点击 `保存配置`
6. 重新进入设置页确认配置仍然存在
7. 发起一次需要 AI 智能体工作空间的任务,确认可建立连接
- 期望结果
- 配置保存成功
- 重新进入设置页时 endpoint 保持为公网 bridge 地址
- token 不以明文展示
- 任务请求走公网 bridge而不是本地 `127.0.0.1`
- 建议记录项
- Bridge 地址
- token 类型:`BRIDGE_AUTH_TOKEN`
- 保存结果
- 任务执行结果摘要
- 截图点:`AI 智能体工作空间` 保存后的页面
### `MANUAL-BRIDGE-REMOTE-002` 公网 bridge 使用 `BRIDGE_REVIEW_AUTH_TOKEN`
- 前置条件
- 当前网络可访问 `https://xworkmate-bridge.svc.plus`
- 准备公网组合 2 的 `BRIDGE_REVIEW_AUTH_TOKEN`
- 操作步骤
1. 打开 `Settings -> Integrations -> AI 智能体工作空间`
2. 在 `Bridge 地址` 输入 `https://xworkmate-bridge.svc.plus`
3. 在 `鉴权令牌 (TOKEN)` 输入 `***REMOVED-CREDENTIAL***`
4. 点击 `保存配置`
5. 重新进入设置页确认配置稳定
6. 发起一次 AI 智能体工作空间任务
- 期望结果
- 使用 review token 也能保存并建立连接
- token 不以明文展示
- 任务侧不会把 review token 写入日志明文
- 失败时错误信息能区分网络不可达、鉴权失败和服务异常
- 建议记录项
- Bridge 地址
- token 类型:`BRIDGE_REVIEW_AUTH_TOKEN`
- 保存结果
- 连接或任务结果摘要
- 是否在页面或日志看到 secret 明文
### `MANUAL-BRIDGE-REMOTE-TASKS-001` 公网 bridge 组合 1 执行五类典型任务
- 前置条件
- 已完成 `MANUAL-BRIDGE-REMOTE-001`
- 当前配置使用 `BRIDGE_AUTH_TOKEN`
- 操作步骤
1. 确认 `Bridge 地址``https://xworkmate-bridge.svc.plus`
2. 确认 token 类型为 `BRIDGE_AUTH_TOKEN`
3. 新建或进入一个测试线程
4. 依次执行 `TASK-CASE-001``TASK-CASE-005`
5. 每个任务完成后重新进入设置页,确认公网 bridge 配置未丢失
- 期望结果
- 五类任务均通过公网 bridge 组合 1 执行
- 生成 Markdown、视频、图片系列和 PDF 产物
- 任务不会回退到本地 `127.0.0.1`
- bridge token 不出现在页面、任务摘要或普通日志明文中
- 建议记录项
- token 类型
- 每类任务产物路径
- bridge 连接结果
- 是否出现 endpoint 回退
### `MANUAL-BRIDGE-REMOTE-TASKS-002` 公网 bridge 组合 2 执行五类典型任务
- 前置条件
- 已完成 `MANUAL-BRIDGE-REMOTE-002`
- 当前配置使用 `BRIDGE_REVIEW_AUTH_TOKEN`
- 操作步骤
1. 确认 `Bridge 地址``https://xworkmate-bridge.svc.plus`
2. 确认 token 类型为 `BRIDGE_REVIEW_AUTH_TOKEN`
3. 新建或进入一个测试线程
4. 依次执行 `TASK-CASE-001``TASK-CASE-005`
5. 对比五类任务的启动、执行、产物回写是否与组合 1 一致
- 期望结果
- review token 能支持五类典型评审任务
- Markdown、视频、图片系列和 PDF 均能作为产物回写
- 若某类任务受权限限制失败,错误信息应明确说明鉴权或能力限制
- 不泄漏 `BRIDGE_REVIEW_AUTH_TOKEN` 明文
- 建议记录项
- token 类型
- 五类任务结果摘要
- 失败任务错误码或错误文案
- 截图点:最终产物列表
---
## 6. 连接本地 xworkmate-bridge
### `MANUAL-BRIDGE-LOCAL-001` 本地 bridge 使用 `BRIDGE_AUTH_TOKEN`
- 前置条件
- 本机已启动 `xworkmate-bridge`
- `http://127.0.0.1:8787` 可访问
- 准备本地组合的 `BRIDGE_AUTH_TOKEN`
- 操作步骤
1. 打开 `Settings -> Integrations -> AI 智能体工作空间`
2. 在 `Bridge 地址` 输入 `http://127.0.0.1:8787`
3. 在 `鉴权令牌 (TOKEN)` 输入 `***REMOVED-CREDENTIAL***`
4. 点击 `保存配置`
5. 发起一次 AI 智能体工作空间任务
6. 对照本地 bridge 日志确认请求到达
- 期望结果
- 配置保存成功
- 任务请求命中本地 bridge
- 页面不会把公网 bridge 与本地 bridge endpoint 混用
- 重新进入设置页后仍显示本地 bridge 地址
- 建议记录项
- 本地 bridge 监听地址
- App 保存结果
- 本地 bridge 日志摘要
- 任务结果摘要
### `MANUAL-BRIDGE-LOCAL-002` 本地 bridge 未启动时的错误提示
- 前置条件
- 本地 `xworkmate-bridge` 未启动
- 设置页使用 `http://127.0.0.1:8787`
- 操作步骤
1. 打开 `Settings -> Integrations -> AI 智能体工作空间`
2. 保存本地 bridge 地址与 token
3. 发起一次 AI 智能体工作空间任务
4. 观察页面错误提示
- 期望结果
- 保存配置可完成,或给出明确的连接失败提示
- 任务失败信息明确指向本地 bridge 不可达
- 不会自动回退到公网 bridge
- 不会清空用户刚输入的本地配置
- 建议记录项
- 错误提示文案
- 任务失败摘要
- 设置页配置是否保留
### `MANUAL-BRIDGE-LOCAL-TASKS-001` 本地 bridge 执行五类典型任务
- 前置条件
- 已完成 `MANUAL-BRIDGE-LOCAL-001`
- 本地 `xworkmate-bridge` 保持运行
- 操作步骤
1. 确认 `Bridge 地址``http://127.0.0.1:8787`
2. 新建或进入一个测试线程
3. 依次执行 `TASK-CASE-001``TASK-CASE-005`
4. 每个任务完成后对照本地 bridge 日志
5. 重新进入设置页确认本地 bridge 地址仍然保留
- 期望结果
- 五类任务请求均命中本地 bridge
- 任务产物正常回写到当前线程
- 断网或公网不可用时,本地 bridge 任务仍按本地能力给出明确结果
- 不会自动切换到公网 bridge
- 建议记录项
- 本地 bridge 日志摘要
- 五类任务产物路径
- 是否发生公网 endpoint 混用
- 截图点:设置页与任务产物
---
## 7. 云端同步与手动配置共存
### `MANUAL-BRIDGE-MIXED-001` 云端账号登录后切换公网 bridge 手动配置
- 前置条件
- 已使用 `review@svc.plus` 登录
- 云端同步状态稳定
- 操作步骤
1. 登录 `svc.plus 云端同步`
2. 切换到 `AI 智能体工作空间`
3. 手动输入公网 bridge 组合 1
4. 点击 `保存配置`
5. 返回主页面后重新进入设置页
- 期望结果
- 手动 bridge 配置保存成功
- 云端账号登录状态不丢失
- 本地手动配置不会被同一轮页面刷新异常覆盖
- 页面能区分云端同步状态与 AI 智能体工作空间连接状态
- 建议记录项
- 登录账号
- 同步状态
- 手动配置 endpoint
- 重新进入设置页后的 endpoint
### `MANUAL-BRIDGE-MIXED-002` 公网 bridge 与本地 bridge 来回切换
- 前置条件
- 公网 bridge 可访问
- 本地 bridge 可按需启动
- 操作步骤
1. 保存公网 bridge 组合 1
2. 发起一次任务并记录结果
3. 切换为本地 bridge 组合
4. 发起一次任务并记录结果
5. 再次切换回公网 bridge 组合 2
6. 重新进入设置页确认最终配置
- 期望结果
- 每次切换后 endpoint 与 token 类型都按用户最后一次保存生效
- 任务请求不会继续使用旧 endpoint
- 页面不会出现公网 / 本地配置交叉污染
- 最终配置以最后一次保存为准
- 建议记录项
- 三次保存的 endpoint
- 三次任务结果摘要
- 最终设置页截图
---
## 8. 回归覆盖矩阵
| 测试编号 | 云端账号 | 公网 Bridge | 本地 Bridge | Secret 隐藏 | 配置持久化 |
|----------|:--------:|:-----------:|:-----------:|:-----------:|:----------:|
| MANUAL-CLOUD-001 | ✅ | ✅ | - | ✅ | ✅ |
| MANUAL-CLOUD-002 | ✅ | ✅ | - | ✅ | ✅ |
| MANUAL-CLOUD-TASKS-001 | ✅ | ✅ | - | ✅ | ✅ |
| MANUAL-BRIDGE-REMOTE-001 | - | ✅ | - | ✅ | ✅ |
| MANUAL-BRIDGE-REMOTE-002 | - | ✅ | - | ✅ | ✅ |
| MANUAL-BRIDGE-REMOTE-TASKS-001 | - | ✅ | - | ✅ | ✅ |
| MANUAL-BRIDGE-REMOTE-TASKS-002 | - | ✅ | - | ✅ | ✅ |
| MANUAL-BRIDGE-LOCAL-001 | - | - | ✅ | ✅ | ✅ |
| MANUAL-BRIDGE-LOCAL-002 | - | - | ✅ | ✅ | ✅ |
| MANUAL-BRIDGE-LOCAL-TASKS-001 | - | - | ✅ | ✅ | ✅ |
| MANUAL-BRIDGE-MIXED-001 | ✅ | ✅ | - | ✅ | ✅ |
| MANUAL-BRIDGE-MIXED-002 | - | ✅ | ✅ | ✅ | ✅ |
## 9. 典型任务覆盖矩阵
| 连接方式 | CASE-001 AI 资讯 MD | CASE-002 图片视频 | CASE-003 7 图系列 | CASE-004 软文矩阵 | CASE-005 图文 PDF |
|----------|:-------------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|
| 云端账号 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 公网 bridge 组合 1 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 公网 bridge 组合 2 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 本地 bridge | ✅ | ✅ | ✅ | ✅ | ✅ |
> 注意:以上 token 为评审 / 测试用途。执行测试时不得将 token 明文贴入公开 issue、公开日志或截图备注。

View File

@ -0,0 +1,41 @@
# Codex /goalXWorkmate App 稳定性修复
```text
/goal
你在 /Users/shenlan/workspaces/ai-workspace-lab/xworkmate-app 仓库中持续执行稳定性修复。
请参考:
- /Users/shenlan/workspaces/ai-workspace-lab/xworkspace-console/docs/case/
- /Users/shenlan/workspaces/ai-workspace-lab/xworkmate-app/docs/cases/
目标不是重构 UI而是围绕 Review 账号、公网 xworkmate-bridge、本地 xworkmate-bridge 和 5 类典型任务用例,修复 App 在登录、Bridge 连接、Token 校验、任务创建、状态流、长任务恢复、取消重试、Artifact 展示中的稳定性问题。
环境覆盖:
1. 云端只读评审账号accounts.svc.plus / review@svc.plus密码从 XWORKMATE_REVIEW_PASSWORD 读取。
2. 公网 BridgeBRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus支持 BRIDGE_AUTH_TOKEN。
3. 公网 Review BridgeBRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus支持 BRIDGE_REVIEW_AUTH_TOKEN。
4. 本地 BridgeBRIDGE_SERVER_URL=http://127.0.0.1:8787支持 BRIDGE_AUTH_TOKEN。
典型任务用例:
1. AI 资讯 Markdown。
2. 图片制作视频。
3. 7 张连续图片。
4. 多平台软文矩阵。
5. 章节拆解生成图文 PDF。
执行要求:
1. 先阅读 docs/cases 下的用例和覆盖矩阵,梳理当前 App 中对应链路。
2. 不改变现有主视觉和核心交互,优先修复崩溃、卡死、无限 Loading、状态丢失、错误提示不清晰、Token 分支混乱、长任务恢复失败等问题。
3. 优先补齐连接层、任务状态层、错误处理层、日志可观测性和最小回归测试。
4. 对每一处修复,说明触发场景、根因、修改文件、验证方式。
5. 每轮只做小步提交,避免大面积重构。
6. 禁止把真实密码或 Token 写入仓库统一通过环境变量、Secret 或本地 .env.local 读取。
7. 如果发现用例与当前实现不一致,优先补充兼容层或清晰错误提示,不要直接删除功能入口。
验收标准:
- 三种 Bridge 连接模式均能给出明确成功或失败状态。
- Review 账号登录失败、Token 错误、Bridge 不可达时 App 不崩溃。
- 五类典型任务至少能完成任务创建、状态展示、失败提示和结果/Artifact 展示的主链路验证。
- 长任务切后台再恢复后,状态不会错乱。
- 连续执行 3 次同类任务,不出现任务串线、重复提交、空白结果页。
```

View File

@ -49,6 +49,7 @@ abstract final class UiFeatureKeys {
static const settingsArchivedTasks = 'settings.archived_tasks';
static const settingsRemoteDesktop = 'settings.remote_desktop';
static const settingsLogs = 'settings.logs';
static const settingsHelp = 'settings.help';
static const settingsAccountAccess = 'settings.account_access';
static const settingsVaultServer = 'settings.vault_server';
static const settingsExperimentalCanvas = 'settings.experimental_canvas';
@ -370,6 +371,7 @@ class UiFeatureAccess {
UiFeatureKeys.settingsArchivedTasks: SettingsTab.archivedTasks,
UiFeatureKeys.settingsRemoteDesktop: SettingsTab.remoteDesktop,
UiFeatureKeys.settingsLogs: SettingsTab.logs,
UiFeatureKeys.settingsHelp: SettingsTab.help,
};
bool isEnabledPath(String path) {

View File

@ -6,11 +6,15 @@ import 'desktop_client.dart';
import 'desktop_input_handler.dart';
import '../../app/app_controller.dart';
import '../../runtime/gateway_acp_client.dart';
import '../../runtime/runtime_models.dart';
import '../../widgets/surface_card.dart';
import '../../i18n/app_language.dart';
import '../workspace_management/workspace_management_panel.dart';
import '../workspace_management/workspace_management_i18n.dart';
bool desktopRemoteWorkspaceConnectionEnabled(RuntimeConnectionMode mode) =>
mode == RuntimeConnectionMode.remote;
class DesktopView extends StatefulWidget {
const DesktopView({
super.key,
@ -239,8 +243,8 @@ class _DesktopViewState extends State<DesktopView> {
SnackBar(
content: Text(
appText(
'连接AI工作空间失败: $message',
'Failed to connect AI Workspace: $message',
'连接远程 AI 工作空间失败: $message',
'Failed to connect Remote AI Workspace: $message',
),
),
backgroundColor: Colors.redAccent,
@ -252,7 +256,10 @@ class _DesktopViewState extends State<DesktopView> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
appText('连接AI工作空间失败: $e', 'Failed to connect AI Workspace: $e'),
appText(
'连接远程 AI 工作空间失败: $e',
'Failed to connect Remote AI Workspace: $e',
),
),
backgroundColor: Colors.redAccent,
),
@ -284,6 +291,9 @@ class _DesktopViewState extends State<DesktopView> {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final hasVideoFrame = _hasVideoFrame;
final canConnectRemoteWorkspace = desktopRemoteWorkspaceConnectionEnabled(
widget.controller.connection.mode,
);
return Padding(
padding: const EdgeInsets.all(16.0),
@ -305,7 +315,9 @@ class _DesktopViewState extends State<DesktopView> {
children: [
// Connection Button
ElevatedButton.icon(
onPressed: _toggleConnection,
onPressed: canConnectRemoteWorkspace
? _toggleConnection
: null,
style: ElevatedButton.styleFrom(
backgroundColor: _connectionState == 'connected'
? Colors.redAccent
@ -332,8 +344,8 @@ class _DesktopViewState extends State<DesktopView> {
: (_connectionState == 'connecting'
? appText('正在连接...', 'Connecting...')
: appText(
'连接AI工作空间',
'Connect AI Workspace',
'连接远程 AI 工作空间',
'Connect Remote AI Workspace',
)),
style: const TextStyle(fontWeight: FontWeight.bold),
),
@ -563,8 +575,8 @@ class _DesktopViewState extends State<DesktopView> {
'Establishing WebRTC connection, please wait...',
)
: appText(
'未开启 AI 工作空间流。点击“连接AI工作空间”启动视频流。',
'AI Workspace stream not enabled. Click "Connect AI Workspace" to start the video stream.',
'未开启远程 AI 工作空间流。点击“连接远程 AI 工作空间”启动视频流。',
'Remote AI Workspace stream not enabled. Click "Connect Remote AI Workspace" to start the video stream.',
),
style: TextStyle(
color: theme.colorScheme.onSurface

View File

@ -10,6 +10,7 @@ import '../../runtime/runtime_controllers.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import 'mobile_settings_page_widgets.dart';
import '../settings/settings_help_panel.dart';
class MobileSettingsPage extends StatefulWidget {
const MobileSettingsPage({super.key, required this.controller});
@ -185,12 +186,14 @@ class _MobileSettingsPageState extends State<MobileSettingsPage> {
await controller.refreshSingleAgentCapabilitiesInternal(
forceRefresh: true,
);
} catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
} catch (e, stackTrace) {
debugPrint('Error: $e\n$stackTrace');
// Account login should not fail only because runtime refresh is transient.
}
try {
await controller.refreshAcpCapabilitiesInternal(forceRefresh: true);
} catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
} catch (e, stackTrace) {
debugPrint('Error: $e\n$stackTrace');
// Runtime capabilities can be refreshed again from Assistant.
}
}
@ -274,6 +277,8 @@ class _MobileSettingsPageState extends State<MobileSettingsPage> {
],
if (currentTab == SettingsTab.archivedTasks)
_ArchivedTasksSection(controller: controller)
else if (currentTab == SettingsTab.help)
const SettingsHelpPanel()
else
_AccountSection(
settings: settings,

View File

@ -26,7 +26,9 @@ class MobileSettingsTabSelectorInternal extends StatelessWidget {
icon: Icon(
tab == SettingsTab.archivedTasks
? Icons.inventory_2_outlined
: Icons.hub_outlined,
: (tab == SettingsTab.help
? Icons.help_outline_rounded
: Icons.hub_outlined),
),
label: Text(tab.label),
),

View File

@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../i18n/app_language.dart';
import '../../theme/app_palette.dart';
class SettingsHelpPanel extends StatelessWidget {
const SettingsHelpPanel({super.key});
static const _sections = <_HelpSection>[
_HelpSection(
title: '1. 快速安装(一键部署)',
codeLabel: 'bash',
code: '''
curl -sfL https://install.svc.plus/ai-workspace | bash -
''',
),
_HelpSection(
title: '2. 带 API Key 安装',
codeLabel: 'bash',
code: '''
export DEEPSEEK_API_KEY="<your-deepseek-api-key>"
export NVIDIA_API_KEY="<your-nvidia-api-key>"
export OLLAMA_API_KEY="<your-ollama-api-key>"
curl -sfL https://install.svc.plus/ai-workspace | bash -
''',
),
_HelpSection(
title: '3. 卸载(保留数据)',
codeLabel: 'bash',
code: '''
curl -sfL https://install.svc.plus/ai-workspace | bash -s -- uninstall
''',
),
_HelpSection(
title: '4. 彻底卸载(清除所有数据)',
codeLabel: 'bash',
code: '''
curl -sfL https://install.svc.plus/ai-workspace | bash -s -- uninstall --purge
''',
),
];
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final palette = context.palette;
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('帮助', 'Help'),
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 10),
Text(
appText(
'测试提示词模板,本文档提供了用于测试 setup-ai-workspace-all-in-one.sh 的标准化提示词模板,可直接复制粘贴到终端执行。',
'Test prompt templates for setup-ai-workspace-all-in-one.sh. Copy and paste directly into your terminal.',
),
style: theme.textTheme.bodyMedium?.copyWith(
color: palette.textSecondary,
height: 1.45,
),
),
const SizedBox(height: 18),
for (final section in _sections) ...[
_HelpCodeSectionCard(section: section),
const SizedBox(height: 18),
],
_InfoBlock(
title: appText('环境变量参考', 'Environment variables'),
child: Table(
columnWidths: const <int, TableColumnWidth>{
0: FlexColumnWidth(1.1),
1: FlexColumnWidth(1.4),
2: FlexColumnWidth(0.7),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
_tableRow('DEEPSEEK_API_KEY', 'DeepSeek 模型 API 密钥', '可选'),
_tableRow('NVIDIA_API_KEY', 'NVIDIA NIM API 密钥', '可选'),
_tableRow('OLLAMA_API_KEY', 'Ollama 服务 API 密钥', '可选'),
_tableRow('PLAYBOOK_DIR', '本地 Playbook 目录路径(开发调试用)', '可选'),
],
),
),
const SizedBox(height: 18),
_InfoBlock(
title: appText('支持平台', 'Supported platforms'),
child: Table(
columnWidths: const <int, TableColumnWidth>{
0: FlexColumnWidth(1.1),
1: FlexColumnWidth(0.7),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
_tableRow('macOS (Apple Silicon / Intel)', '已测试'),
_tableRow('Debian 11/12', '已测试'),
_tableRow('Ubuntu 22.04/24.04', '已测试'),
_tableRow('其他 Linux 发行版', '未测试'),
],
),
),
],
),
);
}
TableRow _tableRow(String a, String b, [String? c]) {
return TableRow(
children: [_tableCell(a), _tableCell(b), if (c != null) _tableCell(c)],
);
}
Widget _tableCell(String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 6),
child: Text(text),
);
}
}
class _InfoBlock extends StatelessWidget {
const _InfoBlock({required this.title, required this.child});
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final palette = context.palette;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: palette.surfaceSecondary,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: palette.strokeSoft),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.titleMedium),
const SizedBox(height: 12),
child,
],
),
);
}
}
class _HelpSection {
const _HelpSection({
required this.title,
required this.codeLabel,
required this.code,
});
final String title;
final String codeLabel;
final String code;
}
class _HelpCodeSectionCard extends StatelessWidget {
const _HelpCodeSectionCard({required this.section});
final _HelpSection section;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final palette = context.palette;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
section.title,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 12),
Container(
width: double.infinity,
decoration: BoxDecoration(
color: palette.surfacePrimary,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: palette.strokeSoft),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 18, 18, 8),
child: Row(
children: [
Text(
section.codeLabel,
style: theme.textTheme.titleMedium?.copyWith(
color: palette.textSecondary,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
IconButton(
tooltip: appText('复制', 'Copy'),
onPressed: () => Clipboard.setData(
ClipboardData(text: section.code.trim()),
),
icon: const Icon(Icons.content_copy_rounded),
),
],
),
),
const Divider(height: 1),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(18),
child: SelectableText(
section.code.trimRight(),
style: theme.textTheme.bodyLarge?.copyWith(
fontFamily: 'monospace',
height: 1.7,
),
),
),
],
),
),
],
);
}
}

View File

@ -17,6 +17,7 @@ import '../../widgets/surface_card.dart';
import 'settings_account_panel.dart';
import 'settings_about_panel.dart';
import 'settings_archived_tasks_panel.dart';
import 'settings_help_panel.dart';
import 'settings_logs_panel.dart';
import 'settings_remote_desktop_panel.dart';
@ -556,6 +557,11 @@ class _SettingsPageState extends State<SettingsPage> {
onStop: (sessionKey) => _stopArchivedTask(sessionKey),
),
),
] else if (currentTab == SettingsTab.help) ...[
SurfaceCard(
key: const ValueKey('settings-help-panel-card'),
child: const SettingsHelpPanel(),
),
] else if (currentTab == SettingsTab.remoteDesktop) ...[
SurfaceCard(
key: const ValueKey('settings-remote-desktop-panel-card'),
@ -603,9 +609,11 @@ class _SettingsTabSelector extends StatelessWidget {
? Icons.desktop_windows_outlined
: (tab == SettingsTab.logs
? Icons.terminal_outlined
: (tab == SettingsTab.archivedTasks
? Icons.inventory_2_outlined
: Icons.hub_outlined)),
: (tab == SettingsTab.help
? Icons.help_outline_rounded
: (tab == SettingsTab.archivedTasks
? Icons.inventory_2_outlined
: Icons.hub_outlined))),
),
label: Text(tab.label),
),

View File

@ -149,7 +149,7 @@ extension AssistantModeCopy on AssistantMode {
};
}
enum SettingsTab { gateway, archivedTasks, remoteDesktop, logs }
enum SettingsTab { gateway, archivedTasks, remoteDesktop, logs, help }
extension SettingsTabCopy on SettingsTab {
String get label => switch (this) {
@ -157,6 +157,7 @@ extension SettingsTabCopy on SettingsTab {
SettingsTab.archivedTasks => appText('归档任务', 'Archived tasks'),
SettingsTab.remoteDesktop => appText('AI工作空间', 'AI Workspace'),
SettingsTab.logs => appText('运行日志', 'Runtime Logs'),
SettingsTab.help => appText('帮助', 'Help'),
};
}

View File

@ -1,4 +1,4 @@
platform :osx, '10.15'
platform :osx, '14.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -97,7 +97,7 @@ post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '11.5'
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '14.0'
next unless ['Pods-Runner', 'Pods-RunnerTests', 'WebRTC-SDK', 'flutter_webrtc'].include?(target.name)

View File

@ -61,6 +61,6 @@ SPEC CHECKSUMS:
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
PODFILE CHECKSUM: 0ff086e365498790bfa0f77c18fab96518c27b1e
PODFILE CHECKSUM: 1eb7d5d1472c632b8f775dd34562291c20ae818a
COCOAPODS: 1.16.2

View File

@ -511,7 +511,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
OTHER_CFLAGS = (
"$(inherited)",
@ -539,7 +539,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
OTHER_CFLAGS = (
"$(inherited)",
@ -567,7 +567,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
OTHER_CFLAGS = (
"$(inherited)",
@ -629,7 +629,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@ -665,7 +665,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.5;
MACOSX_DEPLOYMENT_TARGET = 14.0;
OTHER_CFLAGS = (
"$(inherited)",
"-Wno-ignored-attributes",
@ -688,7 +688,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
@ -740,7 +740,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -791,7 +791,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@ -827,7 +827,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.5;
MACOSX_DEPLOYMENT_TARGET = 14.0;
OTHER_CFLAGS = (
"$(inherited)",
"-Wno-ignored-attributes",
@ -867,7 +867,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.5;
MACOSX_DEPLOYMENT_TARGET = 14.0;
OTHER_CFLAGS = (
"$(inherited)",
"-Wno-ignored-attributes",
@ -890,7 +890,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@ -899,7 +899,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;

View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/features/settings/settings_help_panel.dart';
import 'package:xworkmate/theme/app_theme.dart';
void main() {
testWidgets('renders setup help sections and metadata tables', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
home: const Scaffold(body: SettingsHelpPanel()),
),
);
expect(find.text('帮助'), findsWidgets);
expect(find.text('1. 快速安装(一键部署)'), findsOneWidget);
expect(find.text('2. 带 API Key 安装'), findsOneWidget);
expect(find.text('环境变量参考'), findsOneWidget);
expect(find.text('支持平台'), findsOneWidget);
expect(find.text('DEEPSEEK_API_KEY'), findsOneWidget);
expect(find.text('macOS (Apple Silicon / Intel)'), findsOneWidget);
});
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/app/app_controller.dart';
import 'package:xworkmate/features/desktop/desktop_view.dart';
import 'package:xworkmate/features/settings/settings_remote_desktop_panel.dart';
import 'package:xworkmate/runtime/secure_config_store.dart';
import 'package:xworkmate/runtime/runtime_models.dart';
@ -8,12 +9,27 @@ import 'package:xworkmate/theme/app_theme.dart';
import 'package:xworkmate/widgets/surface_card.dart';
void main() {
test('remote workspace connection is enabled only in remote mode', () {
expect(
desktopRemoteWorkspaceConnectionEnabled(RuntimeConnectionMode.remote),
isTrue,
);
expect(
desktopRemoteWorkspaceConnectionEnabled(
RuntimeConnectionMode.unconfigured,
),
isFalse,
);
});
group('SettingsRemoteDesktopPanel', () {
testWidgets('renders the panel title and connection dashboard', (tester) async {
testWidgets('renders the panel title and connection dashboard', (
tester,
) async {
// Set desktop window size
tester.view.physicalSize = const Size(1280, 900);
tester.view.devicePixelRatio = 1.0;
final store = _MemorySecureConfigStore();
final controller = _NoopRefreshAppController(store: store);
addTearDown(() {
@ -30,20 +46,13 @@ void main() {
// Verify the panel headers and titles
expect(find.text('AI工作空间'), findsOneWidget);
expect(find.text('连接AI工作空间'), findsOneWidget);
expect(find.text('连接远程 AI 工作空间'), findsOneWidget);
final connectButton = tester.widget<ElevatedButton>(
find.widgetWithText(ElevatedButton, '连接远程 AI 工作空间'),
);
expect(connectButton.onPressed, isNull);
expect(find.text('工作空间管理'), findsOneWidget);
// Verify advanced options are hidden initially
expect(find.text('GPU 加速'), findsNothing);
// Tap to expand advanced options
await tester.tap(find.text('高级选项'));
await tester.pumpAndSettle();
// Verify advanced options appear
expect(find.text('GPU 加速'), findsOneWidget);
expect(find.widgetWithText(TextField, 'Display'), findsOneWidget);
expect(find.text('Display'), findsOneWidget);
expect(find.text('工作空间管理'), findsOneWidget);
});
});
@ -54,10 +63,7 @@ Widget _buildTestApp({required Widget child}) {
theme: AppTheme.light(),
home: Material(
child: Center(
child: SizedBox(
width: 1100,
child: SurfaceCard(child: child),
),
child: SizedBox(width: 1100, child: SurfaceCard(child: child)),
),
),
);
@ -65,7 +71,7 @@ Widget _buildTestApp({required Widget child}) {
class _NoopRefreshAppController extends AppController {
_NoopRefreshAppController({required SecureConfigStore store})
: super(environmentOverride: const <String, String>{}, store: store);
: super(environmentOverride: const <String, String>{}, store: store);
Future<void> refreshAcpCapabilitiesInternal({
bool forceRefresh = false,

View File

@ -31,6 +31,7 @@ void main() {
desktop.availableSettingsTabs,
isNot(contains(SettingsTab.remoteDesktop)),
);
expect(desktop.availableSettingsTabs, contains(SettingsTab.help));
expect(
desktop.sanitizeSettingsTab(SettingsTab.remoteDesktop),
SettingsTab.gateway,