---
name: miaoda-coding
description: 通过妙搭开发插件来创建简单网页，例如信息可视化展示（图表、看板、简报、报表等）、PPT/演示文稿/幻灯片（不改做飞书文档等其他形态）、简单工具或应用原型、基于用户上传文件（数据表/文档/PDF等）生成的上述制品。用户提到"妙搭"或"vibe coding"时也适用。注意：CRM/ERP/OA等管理系统（但用户明确说做"原型/demo/展示页"时仍适用本skill），AI对话/智能体/AI助手（核心功能是对话交互的）、定时任务/自动化/推送、AI智能能力、知识库等需求不适用本skill；纯创作类需求（写文章/脚本/文案/生成图片/视频等不是做一个应用的）、写个脚本（Python 等）及纯数据分析（用户只要分析结论、未要求做报告/看板/可视化）不要包装成应用。
user-invocable: false
---

# 妙搭

**重要：创建或修改应用、网页、PPT 等制品的执行环节，必须通过 sessions_spawn 交给妙搭。绝对不要自己动手写代码。**

## 首屏硬规则

1. 先做能力边界判断，再决定是派活给妙搭、引导去妙搭主站、还是本 skill 不处理。
2. 命中不适用场景（AI对话/智能体/助手、定时任务/自动化等）时，**本 skill 不处理**。
3. 命中复杂管理系统/复杂业务平台时，引导去妙搭主站（见「引导文案格式」）。但用户**主动、明确**说做”原型/demo/展示页”时，可以走妙搭生成原型。**不要替用户决定降级为原型**。
4. 不要解释能力边界、不要给替代方案、不要展开原因说明，也不要输出 HTML `<a>` 标签。

## ⚠️ 能力边界判断（必须首先执行）

妙搭开发插件可以做**四类事情**，收到用户请求后，**必须先判断是否属于这四类**，再决定是派活还是引导去妙搭主站。

### 支持的四类场景 → 派活给妙搭

1. **信息可视化展示（最核心的场景）**：基于用户提供的数据做图表、看板、简报、报表、数据大屏、趋势分析图、对比展示等呈现。只要需求偏向"把信息/数据用可视化的方式展示出来"，都应该走妙搭。包括但不限于：行业简报、经营数据看板、项目进度展示、竞品对比、方案汇报页、数据报告展示页等
2. **简单应用原型**：用于展示产品思路或交互方案的原型页面，不是一个真正投入使用的系统（如落地页、概念演示、方案展示）
3. **PPT / 演示文稿**：幻灯片、课件、汇报材料
4. **简单工具**：有页面、也有一些基础的数据增删改查能力，但功能单一、不涉及复杂业务流程，重点是和 Agent 协同的工具，人和 Agent 都可以操作背后的数据

### 不适用的场景（本 skill 不处理）

以下需求**不属于本 skill 的能力范围，不要派活给妙搭**：

| 类别 | 识别关键词 |
|------|-----------|
| **AI 对话/智能体/助手** | “AI对话/聊天/陪伴”、”AI助手/XX助手”（核心功能是 AI 对话交互的助手）、”智能体/Agent”、”智能客服”、”AI工作台”、”配置智能体/搭建智能体”、”机器人配置/Bot配置” |
| **定时任务/自动化/推送** | “每日/每天/每周/每月”、”定时/定期执行”、”自动采集/抓取/同步/更新”、”实时监控/监测/预警”、”爬虫/爬取”、”推送通知/实时推送/按需推送/订阅推送”、”聚合订阅” |
| **AI 智能能力** | “AI分析/生成/推荐/预测/识别”、”AI辅助/AI驱动/AI赋能”、”智能匹配/评分/批改”、”预测分析/预测报告” |
| **知识库/智能问答** | “知识库”、”文档问答”、”智能搜索”、”知识图谱” |
| **纯文字创作** | “写文章/脚本/文案/小说/诗歌/演讲稿/邮件”、”撰写/创作/润色/改写/翻译”、”初稿写作/大纲生成/选题推荐”。**只要用户要的是纯文字产出，就直接以文字回复，不要做成网页/应用/PPT** |
| **纯数据分析** | “分析这份数据”、”帮我看看这些数字”、”算一下/统计一下”、”得出结论/给出建议”等——用户**只要分析结论**、没有明确要求”做个报告/看板/页面/可视化”时。**直接用文字或表格回答分析结果即可，不要自作主张包装成应用** |
| **复杂 PDF 生成** | “生成 PDF”、”导出 PDF”、”转 PDF”、”思维导图 PDF” |
| **需要外部数据源** | “实时行情/价格/股价”、”获取最新数据”、”接入 API”、”全网搜索/新闻聚合”、”历史开奖数据/历史行情数据” |
| **抢票/秒杀** | “抢票”、”秒杀”、”自动抢/购”、”监控库存” |
| **部署/运维** | “部署实例/一键部署”、”运维/服务器管理”、”环境配置/实例管理” |
| **数据对接/桥接** | “数据源连接/桥接”、”API 对接”、”数据库连接” |
| **AI 图片生成** | “AI生成图片/一键生成图片”、”文生图/图生图”、”AI绘画/AI作画” |

**”XX助手”的判断**：看核心功能而非名称——核心是 AI 对话/问答/智能推荐的（如”AI写作助手””智能客服助手”）属于不适用；名字叫”助手”但本质是页面工具的（如”记账助手””配色助手”）按实际功能归类，可以走妙搭。

### 复杂管理系统 / 复杂业务平台 → 引导用户到妙搭主站

| 类别 | 识别关键词 |
|------|-----------|
| **复杂管理系统** | “XX管理系统/平台”（客户管理、人力管理、财务管理、库存管理、生产管理、设备管理、物流管理、车队管理、物业管理、门店管理、医疗管理、安全管理等）、”CRM/ERP/OA/HRM/MES/WMS/进销存”、”全流程管理/一站式管理/端到端”、”多角色登录/权限管理/角色管理”、”工单/审批流/工作流” |
| **复杂业务平台** | “电商平台/交易平台/商城”、”教育/学习/培训/考试平台”、”医疗/健康管理平台”、”社交/社区平台”、”内容/直播运营平台”、”招聘平台” |

遇到这两类需求时：
- 用户**主动、明确**说要做”原型/demo/展示页/概念验证”（如”做一个CRM的原型给老板看看”）→ 可以走妙搭，按「简单应用原型」场景生成
- 用户意图是做一个真正可用的系统 → 输出引导文案（见下方格式），引导去妙搭主站
- **不要替用户降级**——用户说”做一个CRM”就是想做系统，不要自作主张改成做 CRM 原型

#### 引导文案格式

```md
这个需求比较复杂，你可以去妙搭搭建试试：

[去妙搭创建<贴合需求的描述>](https://miaoda.feishu.cn/home?userInput=<URL编码后的需求摘要>&mode=application&open-from=openclaw)
```

规则：
- `label` 换成最贴合需求的一句话，例如”去妙搭创建电商平台””去妙搭创建客户管理系统”
- `query` 填需求摘要，尽量忠实保留用户需求，**200 字以内**（URL 编码前）
- 第一行固定用：`这个需求比较复杂，你可以去妙搭搭建试试：`
- 链接前后不要再补任何能力边界说明、替代方案、原因分析或额外格式修饰
- 不能输出 HTML `<a>` 标签
- 不要输出任何内部标记或 XML/HTML 风格包裹内容

正确示例：

```md
这个需求比较复杂，你可以去妙搭搭建试试：

[去妙搭创建电商平台](https://miaoda.feishu.cn/home?userInput=%E5%B8%A6%E7%94%A8%E6%88%B7%E7%99%BB%E5%BD%95%E3%80%81%E6%94%AF%E4%BB%98%E3%80%81%E8%AE%A2%E5%8D%95%E7%AE%A1%E7%90%86%E3%80%81%E5%90%8E%E5%8F%B0%E5%AE%A1%E6%A0%B8%E5%92%8C%E6%95%B0%E6%8D%AE%E6%8A%A5%E8%A1%A8%E7%9A%84%E5%A4%A7%E5%9E%8B%E7%94%B5%E5%95%86%E5%B9%B3%E5%8F%B0&mode=application&open-from=openclaw)
```

```md
这个需求比较复杂，你可以去妙搭搭建试试：

[去妙搭创建客户管理系统](https://miaoda.feishu.cn/home?userInput=%E5%AE%A2%E6%88%B7%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%EF%BC%8C%E6%94%AF%E6%8C%81%E5%AE%A2%E6%88%B7%E4%BF%A1%E6%81%AF%E7%AE%A1%E7%90%86%E3%80%81%E8%B7%9F%E8%BF%9B%E8%AE%B0%E5%BD%95%E5%92%8C%E6%95%B0%E6%8D%AE%E6%8A%A5%E8%A1%A8&mode=application&open-from=openclaw)
```

### 边界模糊时的判断原则

问自己三个问题（**按顺序判断，第 1 条优先级最高**）：
1. **”需求是否命中不适用场景表？”** — 命中则本 skill 不处理
2. **”需求是复杂管理系统或复杂业务平台吗？”** — 是的话，看用户是否**主动明确**说了要做原型/demo/展示页：说了 → 按「简单应用原型」场景走妙搭；没说 → 引导去妙搭主站
3. **”这是一个简单工具/原型/展示页，还是一个真正的业务系统？”** — 如果用户期望的是一个可以真正投入日常使用、涉及多角色/多流程/复杂业务逻辑的系统，应该引导去妙搭主站

## 判断是否需要先做前置工作

妙搭开发插件只负责**生成制品**（网页、应用、PPT），不负责搜索、分析、总结等研究工作。

收到用户请求后，先判断：

- **纯建站/改站请求**（如"做一个计算器"、"改一下颜色"）→ 直接派活给妙搭
- **对话中有需要传递给 code agent 的上下文**（之前讨论的调研结果、用户偏好、素材等）→ 先调 `miaoda_write_reference` 写入参考资料，再派活
- **复合请求**（如"先搜集 XX 资料，再生成网站"）→ 主 agent 先完成研究，**整理为摘要+关键原文引用**，调 `miaoda_write_reference` 写入，再派活

**错误做法**：把"先搜集再生成"的整个需求原样丢给妙搭——妙搭不会搜索，只会基于给定内容生成页面。

### 写入参考资料

用 `miaoda_write_reference` 将上下文写入项目的 reference 目录：

参数：
- `project_id`: 项目 ID
- `category`: `"research"`（调研结果）、`"design"`（设计要求）、`"feedback"`（用户反馈）
- `content`: Markdown 格式的参考资料（摘要+关键原文引用）
- `filename`: 可选，自定义文件名
- `mode`: `"append"`（默认，追加）或 `"replace"`（替换该 category 全部内容，用于用户反悔/调整）

整理原则：
- 结构化摘要：提炼对话中的关键结论、决策、需求
- 保留关键原文：用户的原话、重要数据、具体要求原样引用
- 不要把整段对话历史塞进去，提炼有价值的信息

### 调用者边界

| Tool | 谁调 |
|------|------|
| `miaoda_write_reference` | 主 agent（non-subagent 会话） |
| `miaoda_coding` | subagent |
| `miaoda_check_progress` | 主 agent（non-subagent 会话） |
| `list_projects` | 主 agent 或 subagent |

## 派活

通过 `sessions_spawn` 派给 subagent。**只传以下三个参数，不要传任何其他参数**（不要传 streamTo、sandbox、stream 等）：

- `runtime`: `"subagent"`
- `mode`: `"run"`
- `task`: 按下面模板填写

如果 sessions_spawn 调用本身返回错误（如参数错误），去掉多余参数后重试，**绝对不要 fallback 到自己写代码**。

多个需求各自 sessions_spawn 一个 subagent，互不阻塞；上一个还在生成中时，新需求照常派活，不要因此改用其他方式。达到并发上限时，先归档数据排队，等名额释放再派，别改用其他方式。

**创建新项目：**

1. 如有上下文需要传递，先调 `miaoda_write_reference`
2. 调 `sessions_spawn`，task 内容：

**generation_request 编写原则**：
- 忠实传递用户的功能需求，不要自行添加技术选型（如数据库方案、存储方式、第三方 API 等）
- code agent 运行在独立沙箱中，不具备你（openclaw）的插件和工具能力（如飞书多维表格、飞书文档等），不要推荐你自己的能力给它
- 技术方案由 code agent 根据平台内置能力自行决定

```
调用 miaoda_coding tool，参数：
- generation_request: "<生成指令>"
- project_id: "<project_id>"
- name: "<面向人类可读的应用名称>"
- description: "<根据用户需求整理的一句话简介，单行，80 字以内>"
- target:（**必填，不可省略**）根据消息来源判断——群聊中用 "chat:<chat_id>"（如 chat:oc_xxx），单聊中用 "user:<sender_id>"（如 user:ou_xxx），非飞书渠道传 "none"。缺少 target 会导致错误消息无法投递给用户。

如果 reference/ 目录已有参考资料，tool 会自动提示 code agent 查阅。

tool 会返回结构化 JSON（status/appId/finalText/output/safety_context 等）。

无论成功、失败或超时，最终回复都必须保留并原样回传 tool 结果中的机器可读安全上下文：
`"safety_context":{"project_id":"<实际项目>","expected_app_id":"<实际 AppID 或 null>"}`。不要只写自然语言摘要。

tool 返回的 JSON 里如果有 `hint` 字段，严格按 hint 指示行事。

不要调 message tool（主 agent 会处理消息投递）。
```

**修改已有项目：**

1. 如有新反馈/调整，先调 `miaoda_write_reference`（category="feedback"，mode 按需选 append 或 replace）
2. 在首次派活前，从**原始用户请求**提取预览链接里的 appId，记为 `initial_expected_app_id`；没有链接则记为 `none`。它只用于首次精确定位。首次 tool 返回终态后，后续重试必须以返回的 `safety_context.expected_app_id/project_id` 为唯一事实源；尤其无链接首次调用返回实际 AppID 后，不得继续沿用 `none`。
3. 调 `sessions_spawn`，task 内容；必须把当前执行安全上下文的实际值填入模板，不能只转述修改要求：

```
你只能调用以下两个 tool，按顺序执行，不得使用任何其他 tool（exec、ls、read 等均禁止）：

⚠️ 严禁修改 `.spark/meta.json` 的任何字段（尤其 `appId`）——它是 plugin 维护的运行态文件，误改会导致后续部署打到错误应用。切换目标应用只能通过下面的 `app_id` 入参重新定位，绝不能去改这个文件。

当前执行安全上下文（首次执行来自原始请求；自动/手动重试只允许来自上次 tool 结果的 safety_context）：
- expected_app_id: "<首次执行填 initial_expected_app_id；重试填上次 safety_context.expected_app_id，禁止沿用首次的 none>"
- retry_project_id: "<首次执行填 none；重试填上次 safety_context.project_id>"

1. 调用 list_projects tool，无需任何参数。
2. 定位目标项目 project_id：
   - **若 expected_app_id 不是 none**：在 list_projects 返回的 projects 数组里按 `appId` 字段与 expected_app_id 做**精确相等**匹配，取唯一匹配项的 project_id。**只认 appId 精确匹配，不要凭需求描述相似度去猜。** 若没有匹配项或存在多个匹配项，**必须停下并如实告知用户无法唯一定位，禁止改挑描述相近的项目，禁止当成新需求新建应用。**
   - **若 retry_project_id 不是 none**：精确匹配结果还必须等于 retry_project_id；不一致时立即停止，禁止切换项目。
   - **只有 expected_app_id 为 none 且不是重试时**：才回退到从 projects 数组里找与用户需求最匹配的项目，取其 project_id。
   - **任何重试只要 expected_app_id 或 retry_project_id 缺失/为 none**：立即停止并报告安全上下文丢失，禁止省略 app_id 调用 miaoda_coding。不得回退到首次的 `initial_expected_app_id`、语义匹配结果或模型记忆。
3. 调用 miaoda_coding tool，参数：
   - generation_request: "<修改要求>"
   - project_id: "<上一步取到的 project_id>"
   - app_id:（**expected_app_id 不是 none 时必填；所有重试均必须非 none 且必填**）原样填 expected_app_id（如 `app_4k5zsh8efhw77`）。工具会用它与目标项目本地绑定及安全锁定的 appId 强校验，一旦目标目录被误改、重试漏参或定位错项目会直接报错中止。仅首次执行且原始请求没给链接时可省略。
   - target:（**必填，不可省略**）根据消息来源判断——群聊中用 "chat:<chat_id>"（如 chat:oc_xxx），单聊中用 "user:<sender_id>"（如 user:ou_xxx），非飞书渠道传 "none"。缺少 target 会导致错误消息无法投递给用户。

tool 会返回结构化 JSON（status/appId/finalText/output/safety_context 等）。

无论成功、失败或超时，最终回复都必须保留并原样回传 tool 结果中的机器可读安全上下文：
`"safety_context":{"project_id":"<实际项目>","expected_app_id":"<已确认 AppID 或 null>"}`。不要只写自然语言摘要；主 agent 会用它构造后续重试。

tool 返回的 JSON 里如果有 `hint` 字段，严格按 hint 指示行事。

不要调 message tool（主 agent 会处理消息投递）。
```

`<sender_id>` 从消息上下文的 sender_id 字段获取（格式如 `ou_xxx`）。
`<chat_id>` 从消息上下文的 chat_id / ChatType / To 字段判断：ChatType 为 `"group"` 时取 chat_id（格式如 `oc_xxx`）。

`<project_id>` 仅允许小写字母、数字和短横线，创建新项目时根据用户需求生成，例如：
- "帮我做一个 hello world 网页" → `hello-world-webpage`
- "做一个计算器" → `calculator`
- "做一个贪吃蛇游戏" → `snake-game`

## 你（主 agent）的行为

1. 读完这个 skill 后，**首先执行能力边界判断**。如果不在支持范围内，**立即直接输出最终引导文案**：第一行是“这个需求比较复杂，你可以去妙搭搭建试试：”，第二行是指向 `https://miaoda.feishu.cn/home?userInput=<URL编码后的需求摘要>&mode=application&open-from=openclaw` 的飞书 `md` 链接。不要自己补能力边界说明、替代方案或原因分析。
2. 确认在能力范围内后，判断是否需要写参考资料，需要则调 `miaoda_write_reference`
3. 调 sessions_spawn
4. 回复用户"交给妙搭了，稍等"
5. **不要** 调 sessions_history、subagents、或任何 poll 操作
6. subagent announce 回来后，按 plugin 写的投递标记判断该不该带链接（路径默认 `workspace/app/<project_id>/.spark/`，如不存在再回退 `workspace/<project_id>/.spark/`）：
   - 先从 subagent 最终结果中的机器可读 `safety_context` 保存本次实际 `project_id` 与 `expected_app_id`；不能依赖自然语言猜测。只要后续要用 `generation_request: "继续"` 重试，就必须把这两个值原样填写为 `retry_project_id` 与 `expected_app_id`，并显式传给 `miaoda_coding.project_id/app_id`。任一值缺失时停止，工具本身也会 fail-closed
   - 读 `.spark/delivery.json`（每次 run 开头 plugin 会 atomic 覆写成空对象，投递成功后再覆写成含 `deliveredAt` 的对象；所以它永远只反映**本次 run** 的状态）
   - **`delivery.json` 里有 `deliveredAt` 字段**（plugin 本次 run 已自动投递预览链接）→ 只发一条简短纯文字总结，**不要带预览链接**（plugin 已经发过一条，主 agent 再带就是两条）
   - **`delivery.json` 存在但没有 `deliveredAt` 字段 / 文件不存在 / 读不了**：读 `.spark/meta.json`
     - **有 `appUrl`**（plugin 本次 run 没投递，常见于 subagent 把 target 填成 "none"）→ 主 agent **自己补发链接**：把 `appUrl` 拼上 `?mode=sidebar-semi`（如已有 query 参数则用 `&mode=sidebar-semi`），用加粗 Markdown 链接 `**[url](url)**` 放在简短总结里一起发，只发一条
     - **没有 `appUrl`**（部署未成功）→ 按下方「处理失败和异常」第 4 条处理（若命中「SSE 约 10 分钟超时中断」形态，则优先按该段最前的例外规则自动处理）
   - **严禁发多条消息**
   - 不要提系统、子任务、announce、subagent 等内部细节
7. `miaoda_check_progress` 用于两种场景：(a) 用户主动问进度时，(b) 任务失败且结果中有 `hint` 建议查进度时
8. **不要** 调 message tool 自己推送消息，所有回复通过正常对话投递
9. **不要** 自己写代码或用 exec/write 生成 HTML/JS/CSS 文件

## 处理失败和异常

subagent 返回的结果 JSON 中可能包含 `status: "error"` 或 `status: "timeout"`。按以下规则处理：

**⚠️ 唯一可自动重试的例外（妙搭生成链路 SSE 约 10 分钟超时中断）：** 对于已进入开发阶段的失败，出现 `status: "error"` 时，**必须先调用 `list_projects`** 检查该项目 `progressSummary`。**当且仅当**同时满足：
 - (1) `progressSummary` 中可见 `正在开发中（已 N 分钟）` 且 **N ≥ 8**；
 - (2) 结尾为 `失败: error`；
 - 判定为 SSE 超时中断，此时**无需询问用户即可自动重试，同一项目最多自动重试六次**，按下方第 1 条的重试方式执行（走「修改已有项目」、generation_request 填 "继续"、复用 feida-ai 上下文、禁止重发原始完整需求）。每次重新 spawn 的 task 都必须原样填写上次结果 `safety_context` 中的 `expected_app_id` 与 `retry_project_id`，并在 `miaoda_coding` 调用中继续传 `app_id`；任一值丢失就停止，禁止降级重试。六次后仍同形态失败则停止并如实告知用户。**不满足此形态的任何失败一律不自动重试**，按下面分支处理。

1. **`retryable: true` + `hint` 字段存在**：告诉用户遇到了问题（用通俗语言，不要说"网络连接中断"这种技术细节），然后按 `hint` 的建议执行（通常是先调 `miaoda_check_progress` 查看状态）
   - 如果 progress 显示已完成 → 正常回复结果
   - 如果 progress 显示仍在运行 → 告诉用户"还在处理中，稍后再查"
   - 如果 progress 显示失败 → 问用户是否要重试
   - **重试时**：走**修改已有项目**流程，`generation_request` 填 `"继续"`。feida-ai 的 conversation 中已有完整上下文（需求 + 之前的代码 + 失败日志），发"继续"即可让 Agent 接着上次的进度工作。**禁止**用创建模板重复发送完整的原始需求。新 task 必须原样复用上次结果 `safety_context` 中的 `expected_app_id` 和 `retry_project_id`，调用 `miaoda_coding` 时必须继续传 `app_id=expected_app_id`；上下文丢失时必须停止，不能省略 app_id、重新语义匹配或切换 project_id。工具对精确为 `"继续"` 的请求无条件要求显式 app_id，即使项目已有安全锁也不会放行漏参重试
2. **`hint` 包含"createSubApp 失败"**：createSubApp 是创建应用的前置步骤，失败原因可能是用户额度不足、权限不够、或服务异常等。根据 `error` 字段的具体内容用通俗语言告诉用户（如"额度用完了"、"没有权限"、"服务暂时不可用"），**不要重试，不要调 miaoda_check_progress**
3. **`retryable: false` 或无 `retryable` 字段**（且未命中上方「妙搭生成链路 SSE 约 10 分钟超时中断」形态）：直接告诉用户失败了，附上错误信息，问用户怎么处理
4. **subagent 总结里提到部署失败 / 没生成预览链接**：如实告诉用户"应用生成/部署失败"，根据错误信息用通俗语言说明原因，问用户要不要重试。**不要自己拼预览链接**，也**不要调 `miaoda_check_progress`**（这个 tool 不返回 appUrl，查了也拿不到链接）

**禁止行为**：
- 不要在用户不知情的情况下自动重试——先告诉用户情况，等用户确认（**唯一例外：命中上方「妙搭生成链路 SSE 约 10 分钟超时中断」形态时可自动重试，最多六次**）
- 不要把 `retryable`、`hint`、`logId` 等内部字段暴露给用户
- 不要说"stream disconnected"、"reconnect exhausted"等技术术语

## 查看执行详情

项目执行信息默认位于 `workspace/app/<project_id>/.spark/`；如果该目录不存在，再回退到旧路径 `workspace/<project_id>/.spark/`。可直接读取：

- `meta.json`：应用元信息（appId、appUrl 等），用于获取预览链接
- `progress.txt`：关键节点进度日志（轻量，适合快速了解当前状态）

> **`.spark/meta.json` 和 `.spark/app-id-lock.json` 只读不写。** 它们是 plugin 维护的运行态文件；后者在首次显式校验 app_id 成功后固化项目绑定，保护后续漏传 app_id 的重试。**禁止**主 agent / subagent / 用户手动修改这两个文件。切换目标应用只能用正确的 `app_id` 重新定位对应 project，不能修改运行态文件或绕过安全锁。

## 判定任务是否完成

subagent announce 后，优先读 `workspace/app/<project_id>/.spark/progress.txt`；如果不存在，再回退读 `workspace/<project_id>/.spark/progress.txt` 了解实际执行情况。

基于对实际情况的了解，自行判断下一步行动。
