Commit Graph
194 Commits
Author SHA1 Message Date
openqj aee60fcb7d Merge pull request #103 from openqj/feat-thread-wait-ops
feat: 线程等待与状态读取 8 项(wait/read/message/handoff/worktree/pr-checks/automation/sleep)
2026-10-28 04:43:02 +08:00
host 755029f220 feat: 线程等待与状态读取 8 项(wait/read/message/handoff/worktree/pr-checks/automation/sleep)
对照 Codex app:wait_threads 首变+afterCursor+8上限、
read_thread 详情摘要、send_message_to_thread 复活失败任务、
handoff 登记与上报轮询、worktree 派生状态、PR checks JSON+
check-runs 日志与注释、automation GET/PATCH、clock.sleep
(API+host)。诚实边界见 docs/thread-wait-ops.md:无
worktree 阶段流、无 automation 变化静默、无独立测试结果
记录、handoff 服务端不执行迁移、wait 仅数据库级变化检测。

三套件:cloud 940 OK(4 跳过)、host 91 OK、deploy 29 OK
2026-10-28 04:33:20 +08:00
openqj 38c0797cb3 Merge pull request #100 from openqj/feat-residual-a
残留批次A:成员删除、PR needs_local_worker 落库、推送 DNS TOCTOU
2026-10-28 03:27:59 +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
openqj 6e3eb7ba11 Merge pull request #99 from openqj/feat-p2
feat: P2 控制台与运维批(成员管理、任务筛选、链路下载、线程操作与 skills/MCP、迁移对齐、monitor 告警、备份回滚、Caddy 选项)
2026-10-28 00:52:55 +08: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
openqj 97f019d1dd Merge pull request #98 from openqj/feat-p1-security
feat: P1 安全批(执行中配额检查、成本实账、goal/协调员语义、PR 通道、孤儿签名、控制面与控制台加固)
2026-10-28 00:36:54 +08: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
openqj d254240d84 Merge pull request #96 from openqj/feat-p0-security
feat: P0 安全批(真全局管理员、双人配额门、默认配额、fix-review 收紧、App 配置限管理员、取消真杀任务)
2026-10-28 00:17:37 +08: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
openqj d0ca1f7f60 Merge pull request #97 from openqj/feat-login-retry
登录限流下开通突发不再硬失败(节流 + 429 重试)
2026-10-27 23:34:41 +08: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
openqj 494c3847d4 Merge pull request #94 from openqj/feat-regperf
perf: 注册开通复用进程内管理员会话
2026-10-27 22:46:50 +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
openqj cc0c707ccd Merge pull request #90 from openqj/feat-cost-notify-1
成本预算与失败通知第一批(G1/G2/G5/G6/无价上报)
2026-10-27 21:58:55 +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
openqj c2f82da5e0 Merge pull request #92 from openqj/feat-sub2api-hotfix
fix: docker 默认 runner 支持 env + 并发开通竞态复用
2026-10-27 18:10:50 +08: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
openqj 99efa93bd3 Merge pull request #88 from openqj/feat-sub2api
sub2api 网关接入:每用户独立账号 + 任务注入 + 网关可配置
2026-10-27 17:30:14 +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
openqj 437684902a Merge pull request #86 from openqj/feat-multiuser-hardening
多用户生产级缺口批:sweep 鉴权、默认配额、默认 executor、 无价 token 上限
2026-10-27 17:22:40 +08:00
host 89c8fb1a79 Key 修法:agent shell 不再继承密钥类环境变量
- 实弹证实:默认配置下 agent shell 继承 server 进程 env,local 与
  docker 两路都能拿到共享 relay key(v3 标记文件法双路 PRESENT)。
- 修法用 codex 原生 shell_environment_policy.exclude:server 自己
  的中转调用走进程 env(env_key),不受 shell 策略影响,两者可
  分开;inherit 保持默认 all,PATH、代理变量等不动;gh 用文件
  登录不用 env,故不排 *_TOKEN。
- host.py Server._start 经 _server_command 统一加 -c 旗标,local
  与 docker 共用一处,覆盖两路;-c 语法已用本机二进制实测
  (合法值启动正常,非法值立即报 invalid shell environment
  policy 退出)。
- 测试:新增 _server_command 断言;全量 host 65、cloud 668
  (4 跳过)、deploy 19 全过
2026-10-27 17:15:09 +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
openqj e786e1f85d Merge pull request #89 from openqj/feat-p1
P1 补齐:自动评审队列、自动化+分诊、并发 caps、运行时审批队列
2026-10-27 16:00:53 +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
openqj 29efc6c61f Merge pull request #85 from openqj/feat-task-turn-ids
feat: 任务列表显示 turn_id 与 root_turn_id
2026-10-27 15:44:33 +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
openqj 559a7dcb9f Merge pull request #84 from openqj/feat-peer-messages
feat: 任务间互发消息(API + agent 标记块,不中断任务)
2026-10-27 14:30:14 +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
openqj 4901a1090b Merge pull request #83 from openqj/feat-tenant-trace
fix: 租户 Docker 任务链路可见(租户目录优先、共享目录兜底)
2026-10-27 13:01:35 +08:00
host a2863b2b10 fix: 租户 Docker 任务链路可见(租户目录优先、共享目录兜底)
此前服务端只读共享链路目录,租户隔离的 Docker 任务链路文件落在 tenants/<项目>/work/host-traces/,/trace 与 /agents 会误报为空。改后按任务解析目录列表(租户优先、共享兜底),父链路与子链路文件多目录合并读;直接发送的续写跟到已有链路文件所在的目录,不把一条任务的链路拆成两处。
2026-10-27 12:58:24 +08:00
openqj 79a1d56856 Merge pull request #82 from openqj/feat-subagent-chain
feat: 命令台子代理链路(主任务/模型子代理/委派子任务 + 过程调用实时同步)
2026-10-27 11:54:41 +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
openqj 34cd0eeba7 Merge pull request #81 from openqj/feat-orphan-worker
fix: worker 代码变化自退 + 部署时清孤儿 worker
2026-10-27 11:26:47 +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
openqj c007fafcc7 Merge pull request #78 from openqj/feat-quota-hardening
worker: enforce quotas during execution
2026-10-27 11:13: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
openqj ad8bf280d0 Merge pull request #77 from openqj/feat-staging-cleanup
fix: 提前失败路也清附件暂存
2026-10-27 11:00:27 +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 b0772d0eac Merge pull request #75 from openqj/feat-deploy-key
feat: deploy 无密码时走密钥直连
2026-10-27 05:50:52 +08:00
openqj 0a862c11f7 Merge pull request #74 from openqj/feat-repo-picker2
GitHub 仓库选择菜单:安装仓库列表 + PATCH 补仓库 + 控制台下拉
2026-10-27 05:48:08 +08:00