Commit Graph
244 Commits
Author SHA1 Message Date
host 6e9d6dc5e8 云端身份与记忆:用户画像+分级记忆,不动 thread
- 新增 cloud/identity.py:user_profiles(显示名/时区/偏好,偏好禁密钥键)
  与 identity_memories(user 级仅本人可见、project 级项目成员可见,
  kind fact/preference/decision/convention,supersede 链+物理删除)。
- server 新增 /users/me/profile、/users/me/memories、/projects/{id}/
  identity-memories、/users/me/identity-context,项目记忆先过项目
  read/create_task 门;上下文只显式取用,不自动注入任务/thread。
- PG schema 与迁移清单同步新增两张 TEXT 主键表,序列清单不动;
  server 补 do_PUT 入口(/users/me/profile 首个 PUT 路由)。
- 新测试 11 个;三套件 cloud 937 OK(4 跳过)、host 89 OK、deploy 29 OK。
- 未动:host.py、worker、CODEX_HOME、旧 summaries 表、控制台 UI;
  未推、未建 PR、未部署。
2026-10-28 03:43:02 +08:00
host 9bb9a50e4e 残留批次A:成员删除、PR needs_local_worker 落库、推送 DNS TOCTOU
- 成员删除:DELETE /projects/{id}/members/{user_id},限 owner 或
  全局管理员,禁删唯一 owner,记 remove_member 审计
  (AuthStore.remove_member + server 分发,沿用 PATCH 守卫口径)
- PR needs_local_worker 落库:非 local 扫到 queued 任务时 CAS
  写行状态为 needs_local_worker;claim/find_open 同步认该状态,
  标记过的行仍可被 local worker 认领、不被搁置;另修
  claim/find_open 对裸 sqlite3 tuple 行的 dict() 兜底
  (TypeError/ValueError)
- 推送 DNS TOCTOU:发送改走建连时解析+判段的连接类,解析
  一次、只拨判过的 IP,Host/SNI 仍为原域名;opener 直接构造
  以排除 ProxyHandler 改写(build_opener 默认会塞代理)
2026-10-27 19:10:49 +00:00
host f81a1048de fix: P2 workers 单测跟随 P1 的管理员可见完整清单语义(owner 只见聚合,完整清单改用夹具全局管理员查,不降门) 2026-10-27 16:42:10 +00:00
host 64995fafab P2-ops: migration alignment, deploy test list, monitor alerts/single-host, backup/rollback closure, Caddy template
- migrations: MIGRATION_TABLES now equals POSTGRES_SCHEMA tables
  (uploads, task_attachments, task_followups, environments added);
  SEQUENCE_TABLES matches IDENTITY tables; task_followups
  sender_task_id column ensured before insert; UNMIGRATED_TABLES
  and migration_coverage() report tables with no PG schema instead
  of silently skipping them; CLI prints the coverage report.
- deploy: DEFAULT_TEST_SUITES gains deploy/tests (discover -s only,
  never package path); regression tests pin the default list.
- monitor: MONITOR_SINGLE_HOST=1 makes the server check single-host;
  default stays dual to avoid silent downgrade; server_mode is in
  the JSON. Optional fail-only alert outlet (webhook or mail,
  default off, alert body is pass/fail JSON only, no secrets).
- backup/rollback: README and restore-drill document what is
  recoverable (DB, code) vs not (worktrees, traces, uploads, host
  configs); rollback.sh shares the server-unit list and dual-port
  health success standard with host_deploy.sh (sync test guards it).
- public entry: Caddyfile uses native {$DOMAIN}/{$ACME_EMAIL}
  variables plus a caddy.service template; README documents the
  steps and the current BT-nginx production status.
2026-10-27 16:37:28 +00:00
host ef532a38c2 feat: 线程操作与 skills/MCP 管理面暴露(host CLI + 服务端 API + 控制台)
线程:host.py 新增 thread-list/read/rename/archive/unarchive/compact 子命令(纯 JSON stdout 契约,read/rename/unarchive/compact 先 resume);服务端按任务定 scope(thread/list 的 cwd 过滤解析,Docker/cloud/异机 409,改操作仅终态,审计 thread_*);控制台线程区(元数据、列表、重命名、归档、取消归档、压缩,终态门控)。

skills/MCP:host.py 新增 skills-list/apps-list/mcp-servers-list/mcp-servers-reload/mcp-server-oauth-login 子命令;服务端按项目 scope(读 read,改 manage_project,审计 mcp_*);控制台技能与 MCP 区(列表、重载、OAuth 发起展示,绝不谎称登录完成)。

测试:host 84、cloud 687(4 跳过)、deploy 19 全过。诚实边界:Docker 任务线程在容器内故 409;无仓库任务推不出 cwd 时 404;OAuth 只做到发起透传,完成态靠协议通知;merged/merge-failed 改操作 409(比控制台门控窄,服务端报错原文展示)。
2026-10-27 16:37:28 +00:00
host 154c18e25d P2 console slice A: project members, admin panel, task filters, trace download, worker liveness
- Members: GET/POST /projects/{id}/members, PATCH /projects/{id}/members/{user_id};
  owner or CODEX_GLOBAL_ADMINS only, audit via set_member; invite by user_id
  or username, duplicate 409, sole-owner demotion 409.
- Console admin panel reuses /admin/quotas, /admin/sweep, /admin/backup;
  admin probe reuses /admin/quotas 403-vs-400 ordering, no new endpoint.
- Task list: frontend status/executor filters + id/prompt search over the
  full fetch; 10s refresh unchanged.
- Trace: kind filter on render, download button fetches full /trace
  (not the 1000-row render truncation); workers show relative last_seen
  with the 120s alive threshold.
- Tests: cloud/tests/test_p2_console.py, 15 tests.
2026-10-27 16:37:12 +00:00
host 1402dc67fd P1 security batch: quota execution checks, cost accounting, goal/coordinator semantics, PR channel, orphan signature, control-plane and console hardening
- Docker quota watchdog + fail-closed quota checks, system tasks need real owner
- max_cost_usd actually accumulated, cancel records cost, estimated rows excluded from unpriced cap, cache_write declared non-billed
- goal-resume keeps original verify on breakpoint resume, goal retry falls back to resume, goal_max_rounds capped at 100 at create and execution
- coordinator children inherit parent model/effort, orphans marked interrupted with audit
- PR jobs on docker workers return explicit needs_local_worker instead of silent empty
- worker code signature covers 6 files and is checked before claim, fail-closed
- sub2api lazy provisioning with rotation, backup whitelist + 0600, push SSRF block, repos/workers desensitized, key/project audits and limits, console buttons/quota/status wording, coordinator/automation quota reserve
2026-10-27 16:27:50 +00:00
host 7091a2d0d2 P0 security batch: true global admin, quota gates on all spending paths, default quota for quota-less users, fix-review author trust + docker/owner attribution, GitHub App setup issuer binding, real cancel kill, wider shell env exclusion
- CODEX_GLOBAL_ADMINS replaces maintainer-derived global admin;
  admin endpoints (quotas, sweep, backup, push dispatch) fail-closed
  403 when unset, with audit on allow and deny.
- continue/followups/messages/retry check both caller and original
  creator quotas (USD + unpriced token cap), 402 on either overrun.
- Quota-less users now get the #86 default quota instead of unlimited;
  init_db backfills and OAuth create/bind/existing-login branches
  INSERT OR IGNORE a default row; explicit NULL row stays unlimited.
  user_quotas added to migration tables.
- /fix-review: untrusted authors rejected before dedup claim, no task;
  trusted tasks force executor=docker, created_by=project owner, owner
  quota gate, prompt is a fixed template plus a 2000-char excerpt.
- GitHub App setup state only issued by global admins, state rows bind
  issued_by (ALTER-compatible for old DBs), callback rejects issuers
  no longer in the admin list without consuming the state.
- Cancel: popen poll checks DB status every second, Docker path runs a
  cancel watchdog that kills by container name, write-back is CAS-guarded
  so late results never overwrite a new status.
- SHELL_ENV_EXCLUDE adds *_TOKEN, *_AUTH*, *_CREDENTIAL*, *API_KEY*;
  comment states the residual risk: same-UID /proc environ remains
  readable, true isolation needs UID separation or hidepid.
- Tests: new cloud/tests/test_p0_security.py (17 cases); old tests
  updated to the new semantics (admin env, default quota, trusted
  fix-review author) without weakening any gate.
2026-10-27 15:35:23 +00:00
host 8c27407b4a fix: 登录限流下开通突发不再硬失败(节流 + 429 重试)
网关登录端点 20 次/分钟/源 IP,100 路突发开通首测仅 19
成功:同步重试波(2/4/8/16/32 秒)把线程推到下一窗口
开头重测也只到 39 成功。本批两层:
- 进程内登录节流:60 秒窗口最多 15 次,满了等最早名额
  滑出窗口再进(留 5 次给其它进程与人工登录);注册是
  best-effort,失败仍有 worker 首任务懒开通兜底,故不定
  时提前放弃;
- 429 退避重试保留:用户登录与管理员登录遇 429 时
  2/4/8/16/32 秒 + 抖动重试,最多 6 次,兜底跨进程竞争;
  管理员登录最终失败才清共享缓存。
测试:新增 100 并发单测(预算临时抬高,只验并发正确性)
与 3 个 429 重试单测、1 个节流窗口单测(fake clock);
单测 setUp/tearDown 清空进程级节流名额,免跨测累积拖慢
套件;全量 cloud 800(4 跳过)、host 69、deploy 19 全绿;
真网关 100 路突发重测 100/100 成功、墙钟 362 秒、临时
用户 100/100 已清。残留:硬保证需共享 Redis 节流,控制
面目前没有 Redis 客户端,未做。
2026-10-27 23:09:22 +08:00
host 706d5a8ca4 fix: 并发突发下共享管理员会话只登录一次
首版共享会话在缓存未命中时各线程各自登录:突发冷缓存下
N 路并发开通会在网关刷出 N 次 bcrypt。改为锁内双重检查
登录(RLock 可重入,_login 成功后写回同一缓存):第一路
登录,其余线程等锁后直接播种。实例本身仍不跨线程,401
时照常走刷新->重登录自愈。
测试:FakeApi 加锁(假实现自身线程安全,失败指向真代码);
新增 10 线程同起并发单测,断言 10 路全成功且管理员登录
恰好 1 次;全量 cloud 795(4 跳过)、host 69、deploy 19
全绿;真网关 10 路突发实测 0 错误、墙钟 0.774s、临时号
已清。
2026-10-27 22:24:21 +08:00
host a9ff7cb498 perf: 注册开通复用进程内管理员会话
实测(真网关):单次开通链路约 0.29s,其中管理员登录 111ms、
建号 80ms、用户登录 79ms、查组 5ms、发 key 10ms、授余额 9ms。
外部 OAuth 两步为必需串行调用;查组/授余额仅个位数毫秒,
不值得为其加缓存复杂度,故本批只做一项:同一进程内第二
次起的开通复用管理员 token 元组(实例不跨线程,401 时走
刷新->重登录自愈,密码轮换后自动用新密码),免一次登录。
真网关对照:冷开通 0.294s,热开通 0.179s(约 -39%),
对照组每次新登录两次均为 0.255s 左右。
测试:新增共享会话复用/过期重取 2 个;全量 cloud 794
(4 跳过)、host 69、deploy 19 全绿。
2026-10-27 22:16:43 +08:00
host e4b5545a24 feat: 成本预算与失败通知第一批(G1/G2/G5/G6/无价上报)
- G1:修正 _check_quota docstring(Local popen 30 秒轮询掐断、
  Docker/同步路径只有边界检查),README 配额节同步;followup
  创建不查配额是已声明设计,保持不动。
- G2:notify_failure 对插值 shlex.quote,模板错误记日志不静默
  吞,命令返回值/超时/异常记 host-run.log;dispatch 前置预算门
  与 goal 预算中断补 budget-exceeded 通知(没配命令时只写日志)。
- G5/G6:重试与续跑的配额检查按任务创建者查(费用记创建者名下),
  维护者替他人操作不再用错人的额度;续跑 API 侧补查,超限回
  402 且不改状态,不再先 200 排队再悄悄 interrupted。
- 无价上报:配额自查/管理端点返回增加 unpriced_actual_tokens
  (actual 且 usd_micros 为 NULL,与 CostStore 口径一致),只读
  上报、不计价、不拦任务;不给 gpt-5.6-terra 编造价格。
测试:host 68、cloud 760(4 跳过)、deploy 19 全过。
2026-10-27 10:13:51 +00:00
host 4936db3c47 fix: docker 默认 runner 支持 env + 并发开通竞态复用
翻转实弹暴露的两个 #88 缺口:
- _docker_default_runner 不接受 env,_call_runner 遇 env 即 loud fail,
  导致 sub2api 路径下所有 docker 任务失败(xcmapi 路径不传 env,
  此前未暴露)。默认 runner 现接受 env 并与进程环境合并后传给
  docker CLI 子进程(-e <名> 由此解析,值绝不进 argv)。
- 同一用户两任务并发开通时建号撞 email 已存在:现轮询复用
  胜出者入库的行,等不到再原错抛出。
测试:sub2api 16、docker 沙箱 29;全量 cloud 789(4 跳过)、
host 65、deploy 19 全绿。
2026-10-27 17:59:04 +08:00
host dbfa026a4e sub2api 开通授初始余额:余额为 0 的 key 在网关侧直接 403 2026-10-27 17:22:54 +08:00
host ec47328d3c sub2api 网关接入:每用户独立账号 + 任务注入 + 网关可配置
- cloud/sub2api.py:管理 API 客户端(管理员登录/token 刷新/
  建用户/以用户发 key/删用户/查组/网关模型目录)+ 幂等开通
  ensure_user_gateway_key(入库失败尽力清理孤儿 key 与账号)
- worker:网关门(sub2api 时按 created_by 幂等开通,失败任务
  直接 failed;协调员子任务跟父任务属主)+ 默认模型走网关
  目录首个;三条执行路径(Local popen/同步、Docker 同步)注
  入任务级 env 与 --model-provider;NO_PROXY 追加网关 host
- host.py:--model-provider,sub2api 时以 -c 注入 provider 表
  (不改租户 config.toml);未配 SUB2API_BASE_URL 时 loud fail
- sandbox:Local/Docker 执行器传 --model-provider 与任务 env;
  docker 密钥只传名(-e SUB2API_API_KEY),值由 CLI 子进程
  环境提供,绝不进 argv;runner 不支持 env 时 loud fail
- server:OAuth 建号 best-effort 开通;/relay/models 走调用者
  自己的网关模型目录;init_db 补 user_gateway_keys 表
- 测试:新单测 11 个;全量 cloud 679(4 跳过)、host 65、
  deploy 19 全过
- 诚实边界:真模型调用实弹需上游账号挂组后才做;切流前
  xcmapi 保持现网
2026-10-27 17:22:54 +08:00
host 7e081211db 多用户生产级缺口批:sweep 鉴权、默认配额、默认 executor、 无价 token 上限
- POST /admin/sweep 改走任一项目 maintainer+ 门槛(与备份管理同口径),否则 403,记 admin_sweep 审计;threshold 下限钳 60 秒
- OAuth 建号 created=True 时写默认配额行(默认 $10/月,CLOUD_DEFAULT_QUOTA_USD_MICROS 可调,INSERT OR IGNORE 幂等);老用户无配额行不受影响
- 建任务未指定且无环境继承时默认 executor=docker(分支继承为空时同理);CLOUD_DEFAULT_EXECUTOR 可覆盖,空字符串恢复旧行为;命令台下拉默认改为 docker
- 无价模型 token 上限:价目表外模型(默认 terra)成本 usd 为 NULL,USD 配额永不生效;有配额行的用户当月无价 token 达 CLOUD_QUOTA_UNPRICED_TOKEN_CAP(默认 2000 万/月)即停,服务端 402、worker 认领/重试/后续消息同口径;报表仍记 unpriced,不折假美元
- 测试:新增 test_multiuser_gaps 11 个 + test_worker_quota 3 个;全量 cloud 668、host 64、deploy 19 全过
2026-10-27 17:15:09 +08:00
openqj c1f9313c10 Merge pull request #87 from openqj/feat-coordinator
feat: 协调员任务(并行子任务、等齐、合流汇总)
2026-10-27 16:25:32 +08:00
host e0cecd70aa feat: P1 补齐(自动评审队列、自动化+分诊、并发 caps、运行时审批队列) 2026-10-27 15:53:50 +08:00
host 7c46ac8e5b feat: 协调员任务(并行子任务、等齐、合流汇总)
建任务时传 coordinator_parts(分工列表,1-20 项,分工名唯一)即为协调员任务:本身不跑模型回合,worker 认领后一次建齐子任务(普通任务,并行被各 worker 认领),等全部终态后合流成汇总报告写回。子任务带 parent_task_id 与 coordinator_part,控制台可用 parent_task_id 查到;协调员重跑按分工名复用已完成子任务、失败/中断的回 queued 重跑;取消与超时尽力取消未终态子任务。子任务的配额/key/执行器门照常生效,不构成绕过。协调员任务拒绝与 goal/plan/验收命令同用。控制台:任务列表 [协调]/[协调子任务] 标记、详情分工与子任务区、新建表单子任务 JSON 输入。新单测 26 个。
2026-10-27 15:45:17 +08:00
host dbcc8e641a feat: 任务列表显示 turn_id 与 root_turn_id
host.py dispatch 打印补 turn_id/root_turn_id;执行器解析(local/Docker)透传;worker 写回 tasks 新列(新值非空才覆盖);server 迁移与接口字段;控制台任务列表新增 turn/root turn 两列(短显+悬停全文);新单测 9 个。
2026-10-27 14:52:00 +08:00
host ef30558f7c feat: 任务间互发消息(API + agent 标记块,不中断任务)
同项目任务互发消息、对方做完可回消息:API POST /tasks/<目标>/messages
(from_task_id,目标项目 create_task + 发送项目 read 鉴权)与 agent
回合输出标记块(worker 回合后解析投递,不用联网、不用凭证,本地
与 Docker 都通)。投递不中断任务:对方运行中/排队中排成后续消息
(轮次边界执行,direct 优先);对方终态则续跑复活(resume_message +
queued),A→B→A 往返不受回合时序影响。拒绝项:自己、跨项目、goal
目标、已合并、复活位被占;每轮最多 5 块,仅 completed 轮次投递;
目标执行时配额门照常生效。控制台后续消息列显示发送方,新增给
其他任务发消息框(落已代理的 /tasks 下,不用改 nginx)。

测试:新增 test_peer_messages(15)、test_peer_messages_server(8)、
test_worker_peer(4);全量 cloud 654、host 64、deploy 19 全过(cloud 4 跳过)。
2026-10-27 14:12:28 +08:00
host a2863b2b10 fix: 租户 Docker 任务链路可见(租户目录优先、共享目录兜底)
此前服务端只读共享链路目录,租户隔离的 Docker 任务链路文件落在 tenants/<项目>/work/host-traces/,/trace 与 /agents 会误报为空。改后按任务解析目录列表(租户优先、共享兜底),父链路与子链路文件多目录合并读;直接发送的续写跟到已有链路文件所在的目录,不把一条任务的链路拆成两处。
2026-10-27 12:58:24 +08:00
host c4d3ec9bff feat: 命令台子代理链路(主任务/模型子代理/委派子任务 + 过程调用实时同步)
host:链路条目带 thread 归属;delegate 向父链路文件写 agent-start/done 标记。服务端:GET /tasks/{id}/agents 聚合父链路 + 子链路文件成代理链(状态口径:委派以结束标记为准、无标记即进行中;模型子代理无结束信号时父任务运行中为进行中、否则未知)。命令台:新增子代理链路区,随 SSE 流防抖刷新;扁平链路也渲染 agent 标记。
2026-10-27 11:51:11 +08:00
host 57f8852634 fix: worker 代码变化自退 + 部署时清孤儿 worker
2026-10-27 暂存清理实弹:05:51 的旧 docker worker 成孤儿后
继续用旧代码抢任务、按老路失败且不删暂存;autoscaler 用
start_new_session=True 拉 worker、部署重启杀不到非 unit 进程。

两层修:
1. worker 常驻循环每轮边界比对 worker.py 文件签名
   (mtime_ns + size),部署换目录后必变,变了就以 75
   (EX_TEMPFAIL,失败类)退出——检查点在轮次边界,绝不
   中断在跑任务。退出后:unit 托管的由 systemd on-failure
   重拉新版,autoscaler 托管的由 reconcile 重拉,孤儿直接
   消失。附带好处:以后部署不用再手动重启本地 worker。
2. host_deploy.sh 换目录后、启动服务前清孤儿:按 cgroup
   过滤杀非 .service 的 cloud.worker 进程(桥接修补前就存
   在的孤儿),先 TERM(跑完当前任务再退),30 秒未退的
   再 KILL;unit 托管的(如 host-1)不动。pgrep 用
   [c]loud.worker 写法,避免检查命令自匹配。

测试:test_worker.py 新增 2 个(签名变了则 75 退出且不认领、
签名不变则跑到 max_seconds);反向验证:撤掉 worker 修复
时正好前者变红。清扫过滤器主机干跑:两个现役 worker 均
为 unit cgroup,全 KEEP,杀 0 个。全量 deploy 19、host
62、cloud 619 全过。
2026-10-27 11:23:49 +08:00
host ad014393ee worker: enforce quotas during execution (claim/retry/fallback/followup/poll kill)
- claim-time gate: queued task whose quota was spent while waiting is
  marked interrupted with a quota reason, no cost row, resume kept
- retry/fallback boundaries and every followup round check DB month
  usage plus this run's accumulated usage (priced via cost table)
- local popen poll loop kills the process group on quota (30s throttle);
  Docker/sync paths get boundary checks only (documented)
- quota stops finalize as interrupted + quota_exceeded error, keep the
  actual cost row, no push, followups restored to pending
2026-10-27 11:06:40 +08:00
openqj c189df4a20 Merge pull request #76 from openqj/fix-handler-500
fix: 请求处理未捕获异常回 500 JSON,不再断连变 nginx 502
2026-10-27 11:01:31 +08:00
host 99de5c77f8 fix: 提前失败路也清附件暂存(仓库解析失败/缺 key 不再残留 bundle)
2026-10-27 附件实弹:仓库解析失败的任务不走正常终局清理,
bundle 目录永久残留(首个失败任务的暂存目录须手动删)。

根因:run_one 里缺 key 门与仓库解析失败两路提前返回,够不
到正常终局后的暂存清理;只有走完 _run_attempt 的任务才删。

修法:把终局清理抽成 _cleanup_task_seed 唯一入口,三路(正
常终局、缺 key 门、仓库解析失败)都走它;取消仍走 server
侧删,互不冲突;删失败不影响任务结果本身。

测试:test_worker.py 新增 2 个(仓库解析失败、缺 key 时
bundle 目录都被删);反向验证:撤掉 worker 修复时正好这
2 个变红,有修复时全绿。全量 deploy 19、host 62、cloud
610 全过。
2026-10-27 10:57:22 +08:00
host df8c493281 fix: 请求处理未捕获异常回 500 JSON,不再断连变 nginx 502
2026-10-27 死锁事件的次生问题:_dispatch 里未捕获异常逃出
handle_one_request 后连接无响应,前端只看到 nginx 502、拿不到
任何可读错误;死锁本身修掉后,同类内部异常仍会以 502 露出来。

兜底(_dispatch_safe,四个 do_* 方法统一走它):
- 任何异常先记 _server_log.exception(堆栈进服务端日志),
  再回固定 {"error": "internal_error"},不泄漏堆栈给客户端;
- _send_json/_send_html/SSE 发头后即标记已响应,之后异常
  只记日志、不二次发送(响应一旦开始不能再发状态行);
- _responded 每请求重置,同一连接 keep-alive 复用不串请求。

单测:异常请求得 500 JSON(非断连)+ 服务端记日志;
同一连接上 500 之后再请求仍 200。
2026-10-27 10:56:08 +08:00
openqj 0a862c11f7 Merge pull request #74 from openqj/feat-repo-picker2
GitHub 仓库选择菜单:安装仓库列表 + PATCH 补仓库 + 控制台下拉
2026-10-27 05:48:08 +08:00
host 6ba671e051 GitHub 仓库选择菜单:安装仓库列表 + PATCH 补仓库 + 控制台下拉 2026-10-27 05:43:56 +08:00
host 8bda29eac3 fix: 限流清理移出请求路径,根治 rate_limit_hits 死锁
allow() 原先每请求在同一事务里 DELETE 旧行 + INSERT + COUNT,
控制台并发加载 + 双 server 下两个事务在 rate_limit_hits 表上
死锁,未捕获异常直接断连、nginx 报 502(2026-10-27 实测,
环境与链路两区同时 502 均源于此,与各自端点逻辑无关)。

结构修复:
- allow() 只做 INSERT + 窗口内 COUNT(COUNT 按 ts > now-window
  过滤,过期行残留不影响计数正确性);建表/索引每实例只做一次。
- 过期行改由后台线程 sweep_rate_limit_hits() 每 60 秒清一次;
  Postgres 下拿事务级 advisory lock,另一台 server 正在清时
  直接跳过,清理者之间也不死锁;清理失败只记 warning。
- main() 里对 DbRateLimiter 起 sweeper 线程(内存版不需要)。

单测:allow 不再删旧行、窗口计数忽略过期行、sweep 只删过期
行、空库 sweep 不炸。
2026-10-27 05:40:30 +08:00
host 970b181bab 租户配额与计费:按用户月度 USD 上限 + 建任务/分支/重试准入 402 + 管理/自查端点 + 控制台展示 2026-10-27 05:18:37 +08:00
host f9be9205ab fix: goal_resume_task 只在 LocalExecutor 上调,其余回落 goal_task
与附件分支同款隐患:CloudExecutor 基类与假执行器没有
goal_resume_task,直接调会 AttributeError;回落到 goal_task 让
它们按原语义失败(CloudExecutor 即“不支持 goal”)。
2026-10-27 04:40:34 +08:00
host 3a4997d346 feat: 旧任务继续会话——终端任务带新消息 + 全部选项同一会话续跑
server:POST /tasks/{id}/continue(终端非 merged 任务重排队,
resume_message 列存本轮消息,不传选项沿用原值,plan/goal 冲突
与新建一致,goal_mode=1 时转新 goal 语义;CAS 防并发改状态,
审计 task_continued);tasks.resume_message 三处迁移 + PG 建表。
worker:认领后本轮 prompt 换成续跑消息(只换内存副本),
goal 续跑走 goal-resume 而不是 goal,跑完清 resume_message
(进程被杀时不清,下次认领重跑同一条消息)。
host:goal-resume --goal-text(旧任务接续 goal 模式:跳过状态
与记录守卫,旧线程上从第 1 轮开新目标循环,新语义不限轮次;
无线程时拒绝)。
sandbox:Local/Docker goal_resume_task + popen goal-resume kind;
附带 popen goal 命令改成新语义(无验收不传 --verify,轮次 0
原样传,此前 goal 任务到本地 worker 会直接报错)。
console:详情页终端非 merged 任务出继续会话表单(消息 +
模型/强度/权限/计划/goal,模型下拉复用中转列表)。
测试:新增 test_task_continue.py 11 个 + test_host_continue.py 2 个。
2026-10-27 04:40:11 +08:00
host 9306c957f3 fix: seed 参数只传给 LocalExecutor,假执行器/CloudExecutor 旧签名不再炸
worker 曾无条件给同步路径的 goal_task/dispatch_task 传 seed_dir,
CloudExecutor 基类与测试假执行器只有 (task) 一个参数,直接
TypeError,炸掉 test_project_repo 2 个 + test_cloud_executor_goal_unsupported
1 个(后者本应报“不支持 goal”)。改成 isinstance(LocalExecutor)
才传 seed_dir,其余走原签名;Docker 路径已有 is_docker 守卫。
2026-10-27 04:30:43 +08:00
host 7c444a688b feat: 任务附件——server/worker/console 链路打通(+ popen goal 新语义补漏)
server:POST /uploads(multipart,413 预检、路径/数量/大小校验、
24h 过期包、used 防一包多用)+ 建任务绑定(同事务写附件行 +
包置 used,cloud 执行器 400)+ 列表/详情带 attachments +
取消删暂存 + 分支带走附件清单;db.py PG 建表 + init_db 补表。
worker:_task_seed_dir(无附件/目录已删静默 None)+ dispatch/
goal 三路(同步本地、popen、Docker 只读挂载 /tmp/codex-seed)
+ cloud 执行器兜底失败 + 终局删暂存。
console:文件/文件夹/图片三路输入,先上传拿 upload_id 再建任务,
列表 [附件×n]、详情列附件路径。
附带:popen 的 goal 命令此前强制要求 verify_command,goal 新语义
任务到本地 worker(走 popen)会直接报错,改成与同步 goal_task
一致(无验收不传 --verify,轮次 0 原样传)。
测试:新增 test_task_attachments.py 15 个。
2026-10-27 04:30:43 +08:00
host 9537e6f0d2 WIP: 任务附件——host seed-dir 与上传模块(未完,server/worker/console 待续) 2026-10-27 04:29:40 +08:00
host 3d45edc8b4 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,不跑模型)项目→环境→任务→归档全通。
2026-10-27 04:07:54 +08:00
openqjandhost c86b450851 feat: goal 模式新语义——prompt 即目标,无验收命令,不限轮次,模型宣告完成 (#67)
console 打勾 goal 模式时不再有专用 goal 文本、验收命令、轮次
三个输入(已移除,换成说明行),只发 goal_mode=1;server 侧
goal_mode=1 时 goal 存 prompt、验收命令空、轮次存 0,带验收
命令或与计划模式同用时 400,旧的带验收命令任务照原样可用。

host.py goal 循环:verify 为空时不跑验测,轮次提示里写明
标记约定;模型在完整跑完的回合末尾单独一行输出“目标已
达成”即达标(超时回合的部分文本含标记也不算);--max-rounds
0 为不限轮次,goal-resume 续跑按 0 处理(不用 or 兜底成 3)。
刹车只有取消与预算耗尽(后者记 interrupted、可续跑)。

迁移与 worker:tasks 加 goal_mode 列(server 迁移、worker 列
补齐、PG schema 三处同步);worker 按 goal_mode 或
verify_command 判定 goal 路径,重试与后续消息排空对新语义
同样只跑一次/不排空;Local/Docker goal_task 的 --verify 可选、
--max-rounds 0 原样传。

Co-authored-by: host <host@test.local>
2026-10-27 03:54:48 +08:00
openqjandhost 4179fb1a0a fix: 命令台初始化有 key 即加载模型列表 (#66)
回访用户 key 已在 localStorage 里:此前模型列表只在点“保存”
时加载一次,重开页面下拉永远只有默认项(2026-10-27 实测)。
初始化序列补 if (getKey()) loadRelayModels();单测补初始化
序列含该调用的标记断言。

另:公网 /relay/models 曾因 nginx 缺 /relay/ 代理返回 404,
与本修无关,nginx 侧另行补 location。

Co-authored-by: host <host@test.local>
2026-10-27 03:33:35 +08:00
openqjandhost f4443dca4f feat: 计划模式 + 后续消息 + 会话分支接云端 (#65)
- 计划模式:tasks 加 plan 列;plan 与 approval=full / goal 冲突时
  400;执行器在 approval 参数后追加 --sandbox read-only(本地
  dispatch/run/goal/popen 与 Docker 内层命令全覆盖),host 任务
  存下 sandbox 后续轮次自动继承只读。
- 后续消息:新表 task_followups;POST/GET /tasks/{id}/followups
  (仅 queued/running 的非 goal 任务;direct 立即往链路追加
  phase=followup-direct 条目,本轮跑完后优先于排队执行;排队
  按 direct 优先、队内先来先服务逐条再跑一轮,同一 thread;
  一条失败即停、剩余 pending 留给重试入口;取消时回 pending)。
- 会话分支:POST /tasks/{id}/fork(仅终态任务;继承 executor/
  模型/强度/权限/计划,goal 字段置空);worker 先 fork 再跑首轮,
  首轮失败不自动重试、不重复分叉,失败后走重试入口重跑首轮。
- 修任务级模型被覆盖:worker 此前无条件写成主模型,控制台
  选的模型实际不生效;现为任务级 > worker 主模型 > 配置。
- Docker 租户准备抽成 _docker_task_context,供普通尝试与 fork 共用。
- console:新建任务计划勾选(含冲突拦截)、详情计划徽标与
  分支来源、分支按钮与表单、后续消息区、列表 [计划]/[分支] 标记。
- 测试:cloud/tests/test_task_links.py 17 个(server 校验与继承、
  worker 模型优先、只读命令、排空顺序与停排、goal 不排空、
  fork 单次性);全量 cloud 542、host 49、deploy 14。

Co-authored-by: host <host@test.local>
2026-10-27 03:17:51 +08:00
host 1128fd3076 feat: 长任务补齐——流式进度 + 跨进程中断 + worker 超时约定
- 长等待流式进度:run_turn 改 1 秒切片轮询(_wait_turn),每满 --progress-interval(默认 30 秒,0 关闭,CLI > 配置 > 默认)往 stderr 打 turn progress 并往链路文件记 kind=progress;进度绝不走 stdout,不破坏 dispatch JSON 与 goal 结论行/用量行解析(经 cloud 真正则验过)
- 跨进程中断:interrupt 直接不生效时写 <state_dir>/interrupt-requests/<task>.json,属主等 turn 时每秒轮询到后用自己的 live 连接发 turn/interrupt 并记 interrupted;10 秒内未响应也不删文件,属主下次轮询仍处理;接线覆盖 dispatch/run/goal/goal-resume/delegate/parallel/fix-review/run-due;旧 server 不接受 interrupt_file 时退回不传再跑
- cloud worker 超时约定:--task-timeout 0 归一化为 None(不限时,与 host turn_timeout 同约定,Docker 路径仍受 docker_timeout 约束),默认 1800 不变;超时仍记 failed(现有单测合同不动)

测试:host 56 全过(新增 7)、cloud 526 全过(4 跳过,新增 1 个 0=不限时)。
2026-10-27 03:05:15 +08:00
openqjandhost fc016fd459 style: 命令台 GUI 美化 (#62)
只改样式与布局骨架,不动 JS 逻辑与接口:
- 卡片化分区(API Key / 健康 / 项目+Workers 两栏 / 新建任务 / 任务列表 / 任务详情),柔和底色 + 圆角 + 细阴影
- 按钮分主次:保存 key 与提交任务为主按钮(蓝),取消为红色弱化按钮;输入框获焦有蓝圈
- 任务状态改圆点 + 彩色文字(running 蓝、completed 绿、failed 红、merged 紫、interrupted 橙),行悬停高亮
- 表格去网格线、表头浅灰,详情/链路区改浅灰圆角框;窄屏单栏自适应
- 验证:HTML 标签平衡、全部 41 个 JS 用 id 齐全、接口标记齐全、零外部引用、JS 经 node --check、test_console 4/4

Co-authored-by: host <host@test.local>
2026-10-27 02:45:08 +08:00
openqjandhost 799f7c3edb feat: 任务实时链路 SSE (#61)
host.py:回合通知摘要成链路条目,增量追加到 <state>/host-traces/<task>.jsonl(上限 5000 行);dispatch/run/goal/delegate 设链路文件
cloud/server.py:GET /tasks/{id}/trace 快照 + /trace/stream SSE(snapshot → entry → done;worktree 在别机时空链路 + 说明)
console:详情链路区,运行中 fetch 流式渲染(EventSource 不能带 X-API-Key 头),终态走快照;DOM 上限 1000 条
单测:test_host_trace 7 + test_task_trace 6;host 49、cloud 525、deploy 14 全过

Co-authored-by: host <host@test.local>
2026-10-27 02:27:15 +08:00
openqjandhost 6022f1dada chore: default model gpt-6.1-sol -> gpt-5.6-terra (#60)
Co-authored-by: host <host@test.local>
2026-10-27 02:00:09 +08:00
openqjandhost 8b2f3968dc feat: per-task model/effort/approval + relay models endpoint (#59)
Co-authored-by: host <host@test.local>
2026-10-27 01:44:53 +08:00
openqjandhost cff152c5a2 fix: console reloads data right after saving key (#58)
Co-authored-by: host <host@test.local>
2026-10-27 01:26:19 +08:00
host 3c3b59faae docs: console public entry via api.diante.me (nginx locations) 2026-10-27 01:11:30 +08:00
openqjandhost e40c2c33bb feat: web command console v1 (GET /console) (#57)
Co-authored-by: host <host@test.local>
2026-10-27 01:02:24 +08:00
openqjandhost 06b9fd12fd HA stage 2: DB-shared rate limiting + second server unit + monitor both-servers check (#52)
Co-authored-by: host <host@test.local>
2026-10-27 00:15:16 +08:00