feat: 命令台云端环境——选、建、管 + 任务绑定环境

环境是项目仓库的命名基准检出(项目 + 仓库快照 + 分支 + 执行器),
不是官方 Codex 云端环境(无公开调用口,不宣称能建它)。
- DB:environments 表(creating/ready/failed/archived,部分唯一索引
  保证同项目未归档名唯一、归档名可重用)+ tasks.environment_id,
  server/worker/db 三处迁移同步(含 #67 的 goal_mode,不丢新语义)。
- API:POST/GET /environments、GET /environments/{id}、
  POST /environments/{id}/archive;任务创建选环境须本项目已 ready、
  执行器一致或继承、cloud 执行器拒绝。
- Worker:run_all 每轮开头准备 creating 环境(真实 git clone +
  checkout 分支,幂等、无 preparing 卡死态,准备期归档 CAS 不覆盖);
  环境任务派发时 task[repo] 指到环境检出(租户 Docker 容器内路径 +
  envs 整目录挂载),任务仍一任务一 worktree。
- Console:环境卡(列表/新建/归档,未 ready 置灰)+ 新建任务环境下拉
  + 任务列表 [环境] 标记;公网 nginx 须同批代理 /environments。
- 单测:新增 test_task_environments.py 14 个;本地实弹(真实 git
  clone octocat/Hello-World,不跑模型)项目→环境→任务→归档全通。
This commit is contained in:
host committed 2026-10-27 04:07:54 +08:00
1 parent c86b450851
commit 3d45edc8b4
6 files changed
+1224 -11

No files matched your search

+20 -1
View File
@@ -837,6 +837,7 @@ POSTGRES_SCHEMA = (
"approval TEXT, "
"plan INTEGER, "
"forked_from TEXT, "
"environment_id TEXT, "
"UNIQUE(project_id, idempotency_key))",
"CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)",
"CREATE TABLE IF NOT EXISTS task_followups("
@@ -851,6 +852,23 @@ POSTGRES_SCHEMA = (
"updated_at DOUBLE PRECISION)",
"CREATE INDEX IF NOT EXISTS idx_followups_task"
" ON task_followups(task_id, status)",
"CREATE TABLE IF NOT EXISTS environments("
"id TEXT PRIMARY KEY, "
"project_id TEXT, "
"name TEXT, "
"branch TEXT, "
"executor TEXT, "
"status TEXT, "
"error_text TEXT, "
"repo_url TEXT, "
"created_by TEXT, "
"created_at DOUBLE PRECISION, "
"updated_at DOUBLE PRECISION)",
"CREATE INDEX IF NOT EXISTS idx_env_project"
" ON environments(project_id, status)",
"CREATE UNIQUE INDEX IF NOT EXISTS idx_env_name"
" ON environments(project_id, name)"
" WHERE status != 'archived'",
"CREATE TABLE IF NOT EXISTS summaries("
"id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
"project_id TEXT, "
@@ -931,7 +949,8 @@ def init_postgres_schema(conn):
for _name, _type in (("executor", "TEXT"), ("pr_url", "TEXT"),
("pr_number", "INTEGER"),
("worktree_host", "TEXT"),
("goal_mode", "INTEGER")):
("goal_mode", "INTEGER"),
("environment_id", "TEXT")):
if _name not in tcols:
conn.execute(
"ALTER TABLE tasks ADD COLUMN %s %s" % (_name, _type))