Update P2 Plan
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
- [【玩法创作】创作流程统一总计划-2026-05-30.md](./【玩法创作】创作流程统一总计划-2026-05-30.md):创作入口、统一创作页、统一生成页、结果页、发布、作品架、广场和运行态的阶段计划、进度记录、并行波次和可直接派发的任务包。
|
||||
- [【开发计划】EditorAgentMockAgentP1可执行开发计划-2026-06-15.md](./【开发计划】EditorAgentMockAgentP1可执行开发计划-2026-06-15.md):`/editor/agent` Mock Agent P1 的可执行波次、任务包、验收门禁和覆盖矩阵,完整承接技术落地计划。
|
||||
- [【开发计划】EditorAgentP2持久任务与SandboxRuntime计划-2026-06-16.md](./【开发计划】EditorAgentP2持久任务与SandboxRuntime计划-2026-06-16.md):`/editor/agent` P2 的持久 runtime job、worker / lease、日志恢复和 SandboxRuntime 抽象计划。
|
||||
|
||||
## 维护规则
|
||||
|
||||
|
||||
@@ -638,4 +638,6 @@ P2 优先接:
|
||||
- [ ] 构建日志分页与可重连 SSE。
|
||||
- [ ] 真实 Agent 接入,但继续产出同一结构化 patch。
|
||||
|
||||
P2 的可执行拆分已经单独沉淀到 [【开发计划】EditorAgentP2持久任务与SandboxRuntime计划-2026-06-16.md](./【开发计划】EditorAgentP2持久任务与SandboxRuntime计划-2026-06-16.md)。后续应先按该计划补 runtime job / worker / lease,再接 Docker sandbox、Shell 或真实 coding agent,避免跳过任务生命周期基础设施。
|
||||
|
||||
真实 Agent 接入前不得扩大 P1 的执行权限;任意依赖安装、HMR、dev server 和作品化发布必须进入后续独立评审。
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
# Editor Agent P2 持久任务与 SandboxRuntime 计划
|
||||
|
||||
更新时间:`2026-06-16`
|
||||
|
||||
## 计划定位
|
||||
|
||||
本文承接 `/editor/agent` Mock Agent P1。P1 已经跑通“用户输入 -> 结构化 patch -> snapshot -> runner 构建 -> artifact -> 独立 preview”的最小闭环;P2 的目标不是先做更聪明的 AI,也不是直接开放 Shell,而是把 P1 的一次性构建升级为可恢复、可取消、可审计的后台任务系统,并为后续 Docker sandbox、Shell 和真实 coding agent 留出稳定接口。
|
||||
|
||||
一句话:
|
||||
|
||||
```text
|
||||
P2 先把执行任务做可靠,再把执行环境逐步替换成更强沙箱。
|
||||
```
|
||||
|
||||
## 当前基线
|
||||
|
||||
P1 当前形态:
|
||||
|
||||
```text
|
||||
前端 /editor/agent
|
||||
-> Mock Agent 返回 WebProjectPatch
|
||||
-> api-server 校验 patch
|
||||
-> 保存 WebProjectSnapshot
|
||||
-> 创建 preview build
|
||||
-> api-server 触发一次性 web-project-runner
|
||||
-> runner 临时展开 snapshot 构建
|
||||
-> 写入 immutable artifact
|
||||
-> preview gateway 签发 previewUrl
|
||||
```
|
||||
|
||||
当前工作区关系:
|
||||
|
||||
- `snapshot` 是事实源,保存在 SpacetimeDB。
|
||||
- runner 构建时创建一次性临时目录,构建完成后删除。
|
||||
- artifact root 只保存构建产物,不作为用户工作区。
|
||||
- Agent 通过结构化 patch 连接工作区,不直接读写本机文件,也不执行 Shell。
|
||||
|
||||
## P2 目标
|
||||
|
||||
- 新增 Web Project 专用持久任务模型,支撑 preview build、后续 sandbox 命令和同步任务。
|
||||
- 将 preview build 从“请求内触发一次性 runner”迁移到 `api-server -> job -> worker` 模式。
|
||||
- 支持任务取消、stale、expired、runner crash 恢复。
|
||||
- 持久化构建日志,支持分页读取和 SSE 断线重连。
|
||||
- 抽象 `SandboxRuntime` 接口,为后续 Docker / gVisor / Kata / microVM 工作区替换留口。
|
||||
- 保持 snapshot 作为唯一事实源,sandbox 文件变化必须通过 diff / patch 校验后保存为新 snapshot。
|
||||
|
||||
## P2 非目标
|
||||
|
||||
P2 明确不做:
|
||||
|
||||
- 不开放用户或 Agent 的交互式 Shell。
|
||||
- 不允许 Agent 直接执行宿主机命令。
|
||||
- 不开放任意 npm 依赖、用户 registry、用户 lockfile 或自定义 build script。
|
||||
- 不做 HMR、长驻 dev server、WebSocket 端口代理。
|
||||
- 不做 Web project 作品化发布。
|
||||
- 不把 Docker workspace 直接提升为事实源。
|
||||
- 不把真实 coding agent 深度绑定到宿主文件系统。
|
||||
|
||||
这些能力进入后续阶段前必须单独补安全模型、验收清单和资源隔离方案。
|
||||
|
||||
## Snapshot 与 Sandbox 的关系
|
||||
|
||||
P2 继续保持以下关系:
|
||||
|
||||
```text
|
||||
snapshot = 项目版本化事实源
|
||||
sandbox = 某个 snapshot 展开后的隔离执行环境
|
||||
```
|
||||
|
||||
标准流转:
|
||||
|
||||
```text
|
||||
读取 active snapshot
|
||||
-> hydrate 到 sandbox workspace
|
||||
-> 在 sandbox 中 build / test / 后续 shell
|
||||
-> 收集 sandbox diff
|
||||
-> 转成 WebProjectPatch
|
||||
-> api-server 校验 patch
|
||||
-> 保存新 snapshot
|
||||
```
|
||||
|
||||
因此 P2 的第一步不是先做长驻 Docker workspace,而是先做能管理执行生命周期的持久 job。没有 job / worker / lease,sandbox 会缺少创建、回收、取消、超时、崩溃恢复和日志恢复的基础设施。
|
||||
|
||||
## 总体执行顺序
|
||||
|
||||
| 波次 | 主题 | 目标 |
|
||||
| --- | --- | --- |
|
||||
| P2a | 持久任务表与状态机 | 新增 `web_project_runtime_job`,固化状态、日志和 owner 校验 |
|
||||
| P2b | Worker / lease / controller | 让任务由 worker 领取、续租、完成、失败和过期重领 |
|
||||
| P2c | Preview build 迁移 | 将 P1 preview build 接到 runtime job,保留现有 runner 和 artifact 语义 |
|
||||
| P2d | SandboxRuntime 抽象 | 定义执行环境接口,第一版可用当前 temp-dir runner 实现 |
|
||||
| P2e | 日志和恢复 | 日志分页、SSE 重连、刷新恢复、取消和 stale 验收 |
|
||||
| P2f | 文档与门禁 | 更新架构文档、验收清单和 smoke 流程 |
|
||||
|
||||
## P2a:持久任务表与状态机
|
||||
|
||||
新增 SpacetimeDB 表建议:
|
||||
|
||||
```text
|
||||
web_project_runtime_job
|
||||
web_project_runtime_job_log
|
||||
```
|
||||
|
||||
`web_project_runtime_job` 建议字段:
|
||||
|
||||
- `job_id`
|
||||
- `project_id`
|
||||
- `snapshot_id`
|
||||
- `owner_user_id`
|
||||
- `job_kind`:P2 先支持 `preview_build`
|
||||
- `status`:`queued | running | succeeded | failed | cancelled | expired | stale`
|
||||
- `attempt`
|
||||
- `worker_id`
|
||||
- `lease_token`
|
||||
- `lease_expires_at`
|
||||
- `cancel_requested_at`
|
||||
- `stale_reason`
|
||||
- `artifact_id`
|
||||
- `preview_build_id`
|
||||
- `error_summary`
|
||||
- `created_at`
|
||||
- `started_at`
|
||||
- `finished_at`
|
||||
- `updated_at`
|
||||
|
||||
`web_project_runtime_job_log` 建议字段:
|
||||
|
||||
- `log_id`
|
||||
- `job_id`
|
||||
- `project_id`
|
||||
- `owner_user_id`
|
||||
- `sequence`
|
||||
- `level`
|
||||
- `message`
|
||||
- `created_at`
|
||||
|
||||
状态机规则:
|
||||
|
||||
- `queued` 只能被 worker claim 为 `running`,或被用户取消为 `cancelled`。
|
||||
- `running` 只能由持有当前 `worker_id + lease_token` 的 worker 完成、失败或续租。
|
||||
- lease 过期的 `running` 可以被重新 claim,`attempt` 递增。
|
||||
- 新 snapshot 创建后,旧 snapshot 的未终态 preview job 标记为 `stale` 或保持旧日志但不得推进 active preview。
|
||||
- `succeeded` 只有在 `snapshot_id == project.active_snapshot_id` 时才能推进 active preview。
|
||||
- `failed / cancelled / expired / stale` 永远不能覆盖上一版成功 preview。
|
||||
|
||||
## P2b:Worker / Lease / Controller
|
||||
|
||||
新增 Web Project runtime worker 角色,避免把 Web 工程构建塞进 `external_generation_job`。
|
||||
|
||||
建议职责:
|
||||
|
||||
- `api-server`:鉴权、创建 job、读取状态、取消任务、SSE / 日志查询。
|
||||
- `web-project-runtime-worker`:claim job、hydrate snapshot、调用 runner、写日志、回写状态。
|
||||
- `web-project-runtime-controller`:后续按队列压力管理 worker 数量;P2 本地可先手动或随 `api-server` 开发态启动。
|
||||
|
||||
worker claim 规则:
|
||||
|
||||
- 只领取 `queued` 或 lease 已过期的 `running`。
|
||||
- claim 时写入 `worker_id`、新的 `lease_token`、`lease_expires_at`、`started_at` 和 `attempt`。
|
||||
- renew / complete / fail 必须带同一个 `worker_id + lease_token`。
|
||||
- SpacetimeDB 事务使用数据库时间判断 lease,不信任 worker 本机绝对时间。
|
||||
|
||||
## P2c:Preview Build 迁移
|
||||
|
||||
P2 不推翻 P1 runner,而是把触发方式迁移为持久 job:
|
||||
|
||||
```text
|
||||
POST /api/runtime/web-project/projects/{projectId}/preview-builds
|
||||
-> 创建 web_project_preview_build
|
||||
-> 创建 web_project_runtime_job(kind=preview_build)
|
||||
-> worker claim job
|
||||
-> worker 调 web-project-runner
|
||||
-> 写 artifact / preview token / previewUrl
|
||||
-> 成功时推进 active preview
|
||||
```
|
||||
|
||||
兼容要求:
|
||||
|
||||
- 前端仍通过现有 preview build API 创建构建。
|
||||
- SSE 事件类型保持 P1 口径,底层事件来源改为 runtime job log / status。
|
||||
- preview URL 仍由后端返回,前端不拼接。
|
||||
- preview gateway 的独立 origin、CSP、CORS 和 iframe `sandbox="allow-scripts"` 保持不变。
|
||||
|
||||
## P2d:SandboxRuntime 抽象
|
||||
|
||||
P2 应先抽象接口,不急于直接开放 Docker Shell。
|
||||
|
||||
建议接口:
|
||||
|
||||
```text
|
||||
SandboxRuntime
|
||||
createWorkspace(projectId, snapshotId)
|
||||
hydrateSnapshot(workspaceId, snapshot)
|
||||
runBuild(workspaceId, jobContext)
|
||||
execCommand(workspaceId, commandSpec)
|
||||
collectDiff(workspaceId)
|
||||
destroyWorkspace(workspaceId)
|
||||
```
|
||||
|
||||
第一版实现:
|
||||
|
||||
- `TempDirBuildRuntime`:复用 P1 runner 的一次性临时目录构建。
|
||||
|
||||
后续实现:
|
||||
|
||||
- `DockerSandboxRuntime`:每个 project/session 独立容器或 volume。
|
||||
- `GVisorSandboxRuntime` / `KataSandboxRuntime` / `MicroVmSandboxRuntime`:生产安全要求提高后替换。
|
||||
|
||||
接口边界:
|
||||
|
||||
- sandbox 不持有平台密钥、用户 cookie、SpacetimeDB 直连 token 或 OSS 写权限。
|
||||
- sandbox 只能访问当前项目 workspace。
|
||||
- sandbox 文件变化不能直接落库,必须转成 patch 后走现有校验。
|
||||
- sandbox 网络默认 deny,后续如需 npm mirror 必须走白名单。
|
||||
|
||||
## P2e:日志、取消与恢复
|
||||
|
||||
日志要求:
|
||||
|
||||
- worker 写入持久日志表。
|
||||
- API 支持按 `jobId + cursor/sequence` 分页读取日志。
|
||||
- SSE 断线后,前端先读取 job 状态和缺失日志,再重新订阅。
|
||||
- 日志限长、脱敏,不能包含平台 token、完整宿主路径和环境变量 dump。
|
||||
|
||||
取消要求:
|
||||
|
||||
- 用户取消时写 `cancel_requested_at` 并把未开始 job 标为 `cancelled`。
|
||||
- running job 由 worker 检测取消请求后停止 runner 并回写 `cancelled`。
|
||||
- P2 若暂不支持强杀子进程,也必须明确返回“取消请求已记录 / 等待 runner 收敛”的状态。
|
||||
|
||||
刷新恢复要求:
|
||||
|
||||
- 打开 `/editor/agent?projectId=...` 后读取 project、active snapshot、active preview 和未终态 jobs。
|
||||
- 前端不会因为旧 job 成功覆盖新 snapshot 的 preview。
|
||||
- 页面刷新后日志和 build 状态以后端为准。
|
||||
|
||||
## 真实 Agent 与 Shell 的后续接入
|
||||
|
||||
P2 可以为真实 Agent 预留工具接口,但不默认接真实 coding agent。
|
||||
|
||||
推荐后续形态:
|
||||
|
||||
```text
|
||||
Agent service
|
||||
-> sandbox file/shell tools
|
||||
-> WebProjectPatch
|
||||
-> api-server 校验
|
||||
-> snapshot
|
||||
```
|
||||
|
||||
如果后续改编 Codex、opencode 等开源 coding agent:
|
||||
|
||||
- Agent 不能直接以宿主仓库为 cwd。
|
||||
- 读写文件工具必须指向 sandbox workspace 或 Sandbox FS API。
|
||||
- shell tool 必须通过 runtime job 在 sandbox worker 中执行。
|
||||
- agent 修改后的文件 diff 必须转成 `WebProjectPatch` 并校验后保存。
|
||||
|
||||
## 验收场景
|
||||
|
||||
P2 最小验收:
|
||||
|
||||
1. 打开 `/editor/agent`,创建或恢复 Web Project。
|
||||
2. 输入“做一个蓝色计数按钮页面”。
|
||||
3. 后端创建 `web_project_runtime_job(kind=preview_build)`。
|
||||
4. worker claim job 并写入 running 日志。
|
||||
5. runner 构建成功,job 进入 `succeeded`。
|
||||
6. preview URL 可访问,iframe 切到成功预览。
|
||||
7. 输入“破坏构建”,新 job 进入 `failed`。
|
||||
8. 上一版成功 preview 保留。
|
||||
9. 连续提交两次,旧 job 不覆盖新 snapshot preview。
|
||||
10. 刷新页面后恢复 project、active snapshot、active preview、job 状态和日志。
|
||||
11. 取消 queued job 后状态为 `cancelled`。
|
||||
12. 模拟 worker crash / lease 过期后任务可重领或进入确定失败 / expired 状态。
|
||||
|
||||
## 验证命令
|
||||
|
||||
涉及 schema:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate
|
||||
npm run check:spacetime-schema
|
||||
```
|
||||
|
||||
后端:
|
||||
|
||||
```bash
|
||||
cargo test -p spacetime-module web_project --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p spacetime-client web_project --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server web_project --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p web-project-runner --manifest-path server-rs/Cargo.toml
|
||||
cargo check -p api-server --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
前端:
|
||||
|
||||
```bash
|
||||
npm run test -- src/services/web-project src/components/editor/agent --reporter verbose
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
git diff --check
|
||||
```
|
||||
|
||||
浏览器 smoke:
|
||||
|
||||
```text
|
||||
打开 /editor/agent
|
||||
提交计数按钮指令
|
||||
等待 runtime job succeeded
|
||||
确认 iframe 预览和按钮点击
|
||||
提交破坏构建
|
||||
确认 failed job 不覆盖上一版 preview
|
||||
刷新页面确认状态和日志恢复
|
||||
```
|
||||
|
||||
## 风险与处理
|
||||
|
||||
| 风险 | 处理 |
|
||||
| --- | --- |
|
||||
| 直接先做 Docker workspace 导致生命周期失控 | 先做 runtime job / worker / lease,再接 SandboxRuntime |
|
||||
| sandbox 文件成为第二事实源 | snapshot 仍为事实源,sandbox diff 必须转 patch 并校验 |
|
||||
| 真实 Agent 绕过 patch 校验 | Agent 只产出 patch 或工具结果,保存仍走 api-server 校验 |
|
||||
| 旧 job 成功覆盖新 preview | 成功推进 active preview 必须校验 active snapshot |
|
||||
| worker crash 后 job 永久 running | lease 过期可重领或标记 expired |
|
||||
| 日志丢失导致刷新不可恢复 | 日志进入持久表,SSE 只做实时补充 |
|
||||
| 把 Web 工程任务塞进 external_generation_job | 使用 Web Project 专用 runtime job,避免语义污染 |
|
||||
|
||||
## 完成定义
|
||||
|
||||
P2 完成必须满足:
|
||||
|
||||
- `web_project_runtime_job` 和日志持久化落地。
|
||||
- preview build 已由 runtime worker 执行。
|
||||
- lease / claim / renew / complete / fail / expired 基础流转可测。
|
||||
- failed / cancelled / expired / stale 不覆盖 active preview。
|
||||
- 日志分页和 SSE 重连可恢复。
|
||||
- P1 的真实浏览器 happy path 和破坏构建保留预览继续通过。
|
||||
- `SandboxRuntime` 接口已形成,后续 Docker / Shell / coding agent 不需要推翻 P1/P2 控制面。
|
||||
Reference in New Issue
Block a user