Commit Graph
155 Commits
Author SHA1 Message Date
host 378cddfb1e align: 修正技能播种路径:codex-home/skills/ 才是用户技能目录
2026-10-30 主机实测(临时 codex-home,生产未动)纠正:
先播 .agents/skills/ 时 skills/list 只返回 5 个系统技能,
3 个自有技能一个没出现;改播 skills/ 后返回 8 个,
3 个自有技能以 scope=user 出现。系统技能本身就在
skills/.system/ 下。
- skill_seed DEST_SUBDIR 由 .agents/skills 改为 skills,
  模块文档与 worker 注释同步修正。
- 测试期望路径同步改为 skills/。
- 对照表补实测结论与附带发现:Codex 自带系统技能
  imagegen,图片生成在 Codex 本体里不是无技能。

验证:skill_seed + chatgpt skills + websearch worker
共 14 项全过。主机探针文件已全部清理,生产未动。
2026-10-30 01:16:49 +08:00
host cf9d17f035 align: 第二批:把技能包播种进租户 codex-home
仿 websearch MCP 播种,默认关:
- cloud/skill_seed.py:cloud/skills/ 的 SKILL.md 拷进租户
  codex-home/.agents/skills/(个人级技能路径,见模块
  文档的路径依据与诚实边界);幂等,租户改过的文件
  绝不覆盖(上报 differs),缺包不阻塞任务。
- cloud/worker.py:新增 --seed-skills,_tenant_dirs 里
  调用播种;老租户下一次任务自动合并;播种失败只记
  日志、不阻塞任务(技能是可选流程说明)。
- cloud/tests/test_skill_seed_worker.py:播种助手 + worker
  开关,仿 test_websearch_worker.py 的结构。
- docs/chatgpt-codex-alignment.md:补第二批说明与实测
  待办(真实 Docker 任务 skills/list + $live-info 调用,
  通过前不宣称生产任务已能用这些技能)。

验证:skill_seed + chatgpt skills + websearch worker
共 14 项全过。本批不改生产配置、不部署,autoscaler
加 --seed-skills 是部署时另行的主机步骤。
2026-10-30 01:16:49 +08:00
host 8a26d1973c align: ChatGPT Codex 对齐第一批:技能包 + 对照表
对照用户提供的 ChatGPT Codex 工具目录,区分可对齐与
不照搬:核心编程工作流已对齐,ChatGPT 托管专属
(CUA、宠物、Docs/Sites 连接器、图片生成模型)不在
开源宿主编排层范围,不凭空宣称对齐。

第一批只补技能层,不改 worker、不改生产行为:
- cloud/skills/live-info:天气/时区/股票/体育,查一级
  来源、带来源和快照时间,绝不编数字。
- cloud/skills/document-tools:LaTeX/Word/PDF/表格/
  演示文稿,先核查工具、缺了明说、产出后重读验证。
- cloud/skills/image-workflow:已有图片处理 + 生成工具
  已配置时才生成,未配置时明说、绝不顶替。
- docs/chatgpt-codex-alignment.md:逐项对照表与不照搬
  的诚实边界,方向为自动化编程任务平台。
- cloud/tests/test_chatgpt_codex_skills.py:校验技能
  frontmatter、名字与目录一致、正文非空、无密钥占位。

验证:新测试 2 项全过。播种进租户 codex-home、 worker
改动、CUA/连接器/图片模型均不在本批,另行评审。
2026-10-30 01:16:49 +08:00
host ce85043a24 trace: 标注被截断的链路消息,避免误读为模型未写完
2026-10-29 实弹:468 字 final_answer 在链路里只剩前 300 字、
断在 fu 后,控制台照原样当助手回复展示,用户误以为模型
没写完。截断本身是设计(防链路文件写爆),缺的是标记。

- host.py _summarize_trace_item:agentMessage 超 300 字时
  条目带 truncated=True,短消息不带,完整回复仍在任务
  result_text 里,不在链路条目里。
- 控制台:TraceEntry 加 truncated 字段,mapTrace 与
  traceLineText 在被截断的消息后标注“链路摘要已截断至
  300 字,完整回复见右侧任务结果”,console.html 为构建产物同步。
- 单测:tests/test_host_trace.py 新增截断标记用例(468 字
  截 300 字且带标记、短消息不带标记)。

验证:host 10 项、cloud test_task_trace 14 项全过;
React tsc+vite 构建通过,产物 322151 字节、含截断标注、
含 #150 的 total_tokens/首字/总耗时,无回退。
2026-10-30 01:01:50 +08:00
openqj fd9fe5f254 Merge pull request #153 from openqj/feat-auth-unknown-role
fix(auth): unknown project role denies instead of KeyError 500
2026-10-30 00:55:54 +08:00
host c823e32336 fix(auth): unknown project role denies instead of KeyError 500
check_permission now returns False for roles not in _ROLE_LEVEL
(e.g. a directly-written 'member' row, which set_member itself
rejects but legacy/direct DB rows can still carry), so the failure
mode is PermissionError/403, never an unhandled 500. Regression test
test_unknown_role_denied_not_500 fails with KeyError on old code.
2026-10-30 00:49:03 +08:00
host 994c4ea5d1 console: 任务详情自动显示成本明细
打开详情就自动拉 GET /tasks/{id}/cost(不再只靠
“查成本”按钮出 JSON),详情顶部新一行:美元、总
token(实际/估算、缓存、无价单列)、轮数、fallback
次数;美元小于 1 美分时用 4 位小数,避免小任务
显示成 $0.00 误导成免费;加载中/失败有各自的
弱化文案,成本拉失败不影响详情其它内容;原
“查成本”按钮不动,仍可手动拉原始 JSON。
服务端不用改:成本接口与 _get_task 的 token 聚合
都已在 #150 / 详情 meta 那单里就位。
2026-10-30 00:39:13 +08:00
host 0f7162934b console: 任务详情也显示首字、总耗时、token
列表行与详情共用同一口径:新抽 api/taskMeta.ts(首字/
总耗时/token 计算),TaskListSection 改 import 它(逻辑
原样不动),TaskDetailPanel 在状态行下方加一行:首字
方块 + 耗时 + token,title 里写口径。
详情的数据缺口一并补上:单任务接口 _get_task 此前走
_task_to_dict 默认 total_tokens=None,详情 token 只能
显示占位;现与列表一样用 _task_tokens_map 一次聚合
(单任务只多一条查询),详情直接出真数。
types.ts 里过期的“列表不回 token”注释同步更新。
新测试:单任务 GET /tasks/{id} 聚合回 total_tokens。
2026-10-30 00:39:13 +08:00
host 4cbdc41e20 fix: task files endpoint resolves tenant docker worktrees
GET /tasks/{id}/files previously returned 409 for all docker tasks
on the grounds that tenant docker shares /app/work per project.
Production inspection shows the worker creates a per-task
host-worktrees/<task-id> subdirectory under the tenant work dir
(62 such dirs under the wwww tenant), so per-task reads are
attributable after all: the endpoint now resolves tenant docker
tasks via tenants/<project>/work/host-worktrees/<task-id> (local
worktree as fallback), never serves the shared /app/work mount
itself, and still 409s cloud/cross-host tasks. Docs and the
harness2_tasks boundary notes are updated to match.
2026-10-29 23:16:01 +08:00
host 9eedee5a3d server: 任务列表一次聚合回 total_tokens
_list_tasks 在取附件的同时用 _task_tokens_map 一次查
cost_records 按 task_id 求和(GROUP BY),一屏只多一条
查询,不按任务逐个调成本接口。无记录计 0、表不存在时
全计 0 且列表不失败;_task_to_dict 新增 total_tokens
参数,默认 None(非列表调用方不把“未算”写成 0)。
前端 TaskListSection 的 token 栏位此前已接好 total_tokens,
补上后列表行直接显示真数,不再显示 token —;前端注释
与占位文案同步更新。
新测试 test_task_list_tokens.py 3 项:HTTP 列表聚合
(actual+estimated 加总、无记录为 0)、缺表不炸、
_task_to_dict 默认 None。
2026-10-29 18:27:53 +08:00
host 65a875ada9 console: 任务列表行显示首字、总耗时、token 栏位
任务列表每一行新增三项(筛选、搜索、选中高亮、状态点不动):
- 首字:行前小方块,prompt 第一个非空白字符(按码点取),
  无 prompt 时用任务 id 首字符兜底;原 prompt 文案不动。
- 总耗时:只用列表已回时间戳算。终态用 finished_at -
  started_at(与服务端 avg_duration_seconds 同口径);running
  显示“进行中 + 已用时长”,随列表刷新更新;无 started_at
  时用 created_at 兜底(含排队,title 注明口径)。格式:
  3分12秒、1小时5分、1天1小时。
- token:栏位已接好,total_tokens 优先,actual +
  estimated 分项加总次之;但列表接口 _task_to_dict 目前
  不回任何 token 字段,单任务真数只在 GET /tasks/{id}/cost
  里,所以当前显示“token —”,不编造、不逐个调 /cost
  拖慢列表。服务端在列表里补 total_tokens 后本行自动出真数。

诚实边界:worker 每次认领重写 started_at,多轮续跑任务
的耗时是末次认领时长,不是历次之和;严格累计需服务端
保留首次 started_at 或累加各轮时长。token 的服务端缺口:
需在 _task_to_dict / 列表查询里按 task_id 聚合 cost_records
的 total_tokens,不需要新端点。
构建:npm run build(tsc + vite singlefile)0 退出,
dist/index.html 原样拷到 cloud/console.html,产物含
issue-first、耗时、token —,未动其它确认框。
2026-10-29 18:24:07 +08:00
host 6d9cd24553 console: 新建任务选完全访问不再弹确认框
DispatchSection 提交时删掉 full 的 window.confirm:下拉里
选 full 本身就是显式选择,提交直接送 approval,不再二次
确认。计划模式与 full 互斥的拦截不动,其它确认框(取消/
合并/提 PR 等)不动。
构建产物 cloud/console.html 由 npm run build(tsc + vite
singlefile)重新生成,产物里已无“确认提交吗”,仅剩下拉
选项本身的“完全访问”文案。
2026-10-29 18:06:18 +08:00
host 7d400afa37 console: add goal-mode invincible option
DispatchSection gains a 'goal 模式无敌版' checkbox next to 'goal 模式':
checking it sends goal_mode=1 plus no_time_limit=1 (server already
accepts the field; the console just never exposed it). Plain goal
checkbox behavior is unchanged; invincible wins when both are
checked. Plan/coordinator mutual exclusion now covers invincible
too (it is a goal variant). TaskDetailPanel shows
'goal 模式无敌版(不限时)' when a task carries no_time_limit.
Help text states the honest boundary: worker/docker total timeouts
are lifted, the 600s single-turn host guard stays. Built with
npm run build (tsc+vite pass); console.html synced verbatim from
dist/index.html.
2026-10-29 17:58:50 +08:00
host 6f0eec71b6 worker: cover goal docker paths with prestart stage logging
PR #145 added docker_run_* lines only to _run_in_container, but
goal/goal-resume/goal-resume-breakpoint call _call_runner directly
(to keep full stdout for goal conclusions), so the production live
test (a goal task) produced zero docker_run_ lines. Extract a
_docker_call_staged helper and route all four task paths through it;
error lines log the class name then re-raise so each caller's
kill+failed handling is unchanged.

Also fix audit attribution: audit_log has no task_id column, and
worker_exec_done/error details carried no task id, so per-task
queries missed them (the rows existed). All three exec details now
start with task_id=. Tests: 2 new goal-path stage cases + a done
detail task-id assertion (8 total in test_prestart_stages).
2026-10-29 17:46:12 +08:00
host 9d90d97046 worker: prestart stage logging for dispatch stalls
Claim-to-dispatch stalls (cf. 2026-10-29 task A: 61s to interrupted
with zero detail) were invisible because docker run blocks with
captured output and watchdog kills were silent. Add three layers:
- worker_exec_start/done/error audit rows in _run_attempt (PG,
  survives --rm), start/done pairing shows whether an attempt died
  inside the executor call;
- docker_run_start/done/error log lines in DockerExecutor (container
  + image only, never the inner command/prompt);
- stdout tail (last 2000 chars) to the worker log on failed or
  interrupted results, and a log line on both watchdog kill paths.
Tests: cloud/tests/test_prestart_stages.py, 6 cases incl. a
no-prompt-in-logs assertion.
2026-10-29 17:07:53 +08:00
openqj 0129b0149a Merge pull request #144 from openqj/feat-neverstart-fresh
worker: 从未启动的任务自动续跑回落全新跑
2026-10-29 16:51:42 +08:00
host e74f20608b worker: auto-resume falls back to fresh goal for never-started tasks
2026-10-29 live test: task A was interrupted 61s after claim but had
never written anything (no host-state entry, no worktree, no trace,
no checkpoint); auto-resume ran goal-resume, which host rightly
refused, and the task failed. Refusal only makes sense for tasks
with progress but a lost breakpoint (those go through the P0
checkpoint rebuild); for a never-started task the only meaningful
outcome is a fresh run, identical to its first attempt (same task
id, same goal, worktree does not exist yet so nothing is
overwritten).

Change (claim path only, via _resolve_breakpoint_resume):
- rebuild from checkpoint first, unchanged;
- if rebuild fails AND _task_never_started holds (state entry,
  checkpoint row, worktree dir, trace file all absent), clear the
  breakpoint flag so the run takes the fresh goal path, with a
  worker_fresh_restart audit event;
- any evidence of prior execution, or any check failure,
  keeps the old refusal path (fail-closed); the auto-resume
  count cap and no-progress stall guard still bound retries.

Tests: cloud/tests/test_neverstart_fresh.py, 10 items (fallback
holds, entry/checkpoint/worktree/trace presence each block it,
corrupt state blocks it, rebuild still wins when a checkpoint
exists); test_goal_resume's breakpoint-routing test now sets up
a task with a checkpoint so it still exercises goal-resume.
Full suites: cloud 1178 (skipped 4), host 104, deploy 39, green.
2026-10-29 16:45:07 +08:00
host c20192bce8 harness2: per-role models, sweep batch, task file assets endpoint
- Role models: run_improvement_via_tasks gains role_models
  (probe/editor/selector/reflector) plus --model-<role> CLI flags;
  missing/empty falls back to shared --model, both empty means
  server default, so default behavior is unchanged.
- Sweep: run_sweep + examples/harness2_sweep.py manifest CLI with
  shared library_root (library restore), per-run failure isolation
  and sweep-report.json summary; preflight gates task creation.
- Deliverable assets: GET /tasks/{id}/files for local-executor
  tasks (safe path resolution, UTF-8 text <=256KB, listing <=200;
  docker/cloud/cross-host 409 with explicit reason, since tenant
  Docker shares one /app/work per project and per-task attribution
  would misattribute); probe rollout deliverable_files from the
  endpoint plus DELIVERABLE FILE reply blocks (endpoint wins on
  collision, bad blocks dropped), surfaced in
  deliverables/<label>/files/ context and full_result summary.
- Docs: harness2.md comparison table updated for all three items.
2026-10-29 16:32:58 +08:00
openqj 7425f2e778 Merge pull request #141 from openqj/feat-harness2-gaps
harness2: upstream gap fill — trivial fix, session budget, tag validation, preflight
2026-10-29 15:40:09 +08:00
host 4ed1479d7a harness2: upstream gap fill — trivial selection fix, session budget, run_tag validation, preflight check
- select_final trivial condition now k==1 and single-candidate pool,
  regardless of whether a selector is provided (previously gated on
  selector is None, so wired runs always paid for a useless selector
  task at k=1)
- Budget gains max_sessions gate (probe + stage-attempt sessions;
  fmt2 corrections and selector/reflector sessions exempt), the
  backstop for unpriced models where the USD gate never binds
- validate_run_tag (charset, no leading '-', no s<N> seed prefix)
  enforced at run_improvement_via_tasks and the CLI
- CLI --check preflight (base harness, source dir, prompt, tag,
  control-plane /health probe) without creating tasks
- ModeResult.to_dict now includes rollout summaries so a lost probe
  is visible in full_result instead of only on the server
- CLI plan counts the selector only when k>1
2026-10-29 15:31:58 +08:00
host 8c719c7e62 断点镜像状态标签回落:host-state 条目写 status 不是 goal_status
生产实弹:极小 goal 任务的 timeline 里 checkpoint goal_status 为
None——host.py 的任务条目只有 status(goal-running/goal-met),
没有 goal_status 键,镜像原样读 goal_status 永远是空。修法:
entry.get('goal_status') or entry.get('status')。轮次、thread_id、
state_sha 等关键字段不受影响(熔断只看轮次),纯标签 fidelity
修复;单测加 status-only 条目镜像断言。
2026-10-29 15:31:07 +08:00
host 585242fc8f 长任务 P0 三件:断点进库、错误分类+熔断、Run/Step 时间线
断点进库:task_checkpoints 表(PG 新库 + 老库幂等补表 + 迁移清单
对齐);worker 在 goal 任务执行期间每 30 秒把磁盘 host-state 断点
镜像进库,终局再补一次;断点续跑认领前,若 host-state 条目丢失
但库里有断点,先按库断点重建最小条目再跑,库里也没有断点时不
造假、走原有拒绝路径。

错误分类+熔断:sandbox 四处解析加永久错误分支(KeyError、配置
缺失等无结论异常退出判 failed 死信,不再判 interrupted;host 已
明言 interrupted 的不受影响,未知错误不分类);worker 自动恢复
加退避(10 秒起指数退避、封顶 300 秒)与无进展熔断(库断点轮次
连续 3 次不变即标 auto_resume_stalled 等人工)。

时间线:task_steps 表 + host 每轮写 host-steps 步骤文件 + worker
导入;GET /tasks/{id}/timeline 一次返回 steps 与 checkpoints;
认领、暂停、自动恢复、终局都记步骤。旧任务返回空数组,不编造。

单测:cloud/tests/test_p0_three.py 14 项(分类、熔断、镜像重建、
步骤导入去重、timeline 接口)。
2026-10-29 15:13:01 +08:00
openqj d56bd3a99e Merge pull request #138 from openqj/feat-harness2
harness2: recursive harness optimization wired to real codex tasks
2026-10-29 14:28:44 +08:00
host 42abe3cc5b harness2: recursive harness optimization wired to real codex tasks
Port Google Research's Harness^2 method layer (model weights frozen,
only the surrounding harness is optimized): Propose-Probe-Compose with
INV/DOM/AGG editors, parallel/sequential modes, per-domain experience
library, anonymous-product selection, k<=3 hard cap and budget gate.
Execution goes only through the existing control-plane API (upload ->
task with idempotency key -> poll -> trace/cost -> cancel on timeout);
no new server endpoints, no codex flags composed, tasks inherit the
normal executor/sandbox/quota/audit path.

Wiring lessons from four real rounds against production (project
wwww, ledger task): HttpTaskClient retries transient disconnects and
502/503/429 (3 attempts); editors get a worked file-block example, an
explicit <no-change> marker and one feedback retry (.fmt2) instead of
silently accepting notes-only replies; run_task unwraps the server's
result_text JSON envelope (text field holds the real reply) — without
this, line-anchored file-block parsing could never see blocks the
agents were actually emitting. Final round (real4) fully green:
all stages landed, selection non-fallback, experience入库, probe
delivers the correct signed total 85.15.

Tests: 35 harness tests (17 method + 18 wiring incl. fake-control-plane
full round, idempotent replay, retry, format retry, envelope round).
Deliberate deviations from upstream are documented in docs/harness2.md
(no network-unrestricted/sandbox-disabled defaults, auto_apply off,
no benchmark/scorer, probe deliverable = final reply text).
2026-10-29 14:21:18 +08:00
host a5f2e4e091 fix: 断点丢失时 goal-resume 拒绝成 failed,阻断自动恢复空转
2026-10-29 长任务实弹:Docker goal 断点续跑在 host-state 里找
不到任务时抛 KeyError,worker 把无结论异常退出判 interrupted,
自动恢复再排队、再 KeyError,12 秒一轮空转 49 次(人工暂停
拦下,第 50 次人工续跑又烧掉),文件 32 章原样未动。
修法:host.py goal-resume 先查任务在不在 state 里,不在就打印
拒绝行、正常退出;sandbox 三处解析(本地、Docker resume、
Docker 断点)见拒绝行即判 failed 终态,不进自动恢复循环。
单测:host 缺任务拒绝不炸 + 三处解析拒绝判 failed。
2026-10-29 14:14:13 +08:00
host ae3aff8602 feat: 长任务无敌补齐三件:连续失败上限 + worker退出审计 + 配额恢复自动续跑
1. host goal 循环连续失败护栏:3 轮连续失败即中断出口,
   任务记 interrupted、可断点续跑,不再无限空转
   (2026-10-29 实弹:断点后 274 轮全失败)。
2. worker 退出杀残留路径补审计 worker_shutdown_interrupted,
   detail 为任务 id,与 sweep_interrupted 同口径。
3. 配额中断自动恢复:每轮现查配额,仍超限/查不到时留
   在 interrupted 不重排队,下轮再查;现查为 None 时
   (新月份/额度上调/无价上限重置)才 CAS 回 queued。

单测:配额仍超限不动、配额恢复续跑、连续 3 轮失败停转。
2026-10-29 13:07:57 +08:00
host a68b63e0f1 console: API Key 区补生成/列表/撤销(调现有自助接口) 2026-10-29 12:51:00 +08:00
host 5b2bdc9bbf fix: Docker 断点续跑透传密钥环境(SUB2API_API_KEY 缺失空转修法)
Docker goal_resume_breakpoint_task 是唯一漏传
env=self._docker_cli_env(extra_env) 的 goal 方法:密钥只按名
进 docker run,值靠 CLI 环境带;漏传导致容器内 key 为空,
断点后每轮瞬间报 Missing SUB2API_API_KEY、无限空转
(2026-10-29 长任务实弹:274 轮全失败)。

单测:断言断点方法调用 runner 时 CLI 环境含密钥值,
命令里密钥只按名出现不带值。
2026-10-29 12:42:19 +08:00
host c9cfe22a36 feat: 长任务中断自动恢复,仅用户暂停才暂停,不限时
- 新增 tasks 列 paused_by_user/interrupted_reason/auto_resume_count/no_time_limit(幂等迁移,server+worker 双侧)
- POST /tasks/{id}/pause:用户暂停标 interrupted + paused_by_user=1;/cancel 同款标记;人工 /retry、/goal-resume 清标记
- sweep 标 sweep、配额标 quota、协调员孤儿标 coordinator_orphan、worker 退出标 worker_shutdown
- worker.run_loop 每轮后 auto_resume_interrupted:只接系统中断的 goal 任务,CAS 回 queued + goal_resume_pending=1(G10 断点续跑),用户暂停/配额/孤儿/非 goal 不接,上限 50 次防无限烧额度
- no_time_limit=1 的任务 worker/Docker 级不限时(其它任务仍默认 1800 秒;host 单轮 600 秒仍是单轮护栏)
- 前置 cherry-pick:G10 断点续跑透传 model_provider(否则 Docker 断点续跑在 sub2api 下必死,自动恢复无法实证;本 PR  supersedes #127,勿另行合并 #127)
2026-10-29 11:37:06 +08:00
host a19b1567bb fix: G10 断点续跑透传 model_provider(sub2api 续跑失败修法)
断点续跑容器不带 --model-provider 时 Server 的 provider 表里
没有 sub2api,thread/resume 报 provider not found(probe15 实弹)。
Docker/Local 断点方法加可选 model_provider 并转发,worker 两处
调用点传 gateway_provider;补单测断言断点命令含
--model-provider sub2api(旧代码下两新测试 1 失败 1 报错)。
2026-10-29 11:28:39 +08:00
host e5aa5c2acb console: 分栏改独立容器,修模块间空白间距
PR #130 的共享网格行是根因:一行高度由该行最高模块决定,
任务列表撑高第 2 行后,对话(第 3 行)只能从任务列表底下开始,
派发/环境等短模块下面空出一大截;align-items:start 只让盒子
不拉伸,拉不上来下一行。

修法:App.tsx 把模块包进三个独立 .dash-col(左:任务+项目,
中:派发+对话,右:环境+Workers+技能+自动化+分诊+管理区+
API Key),栏内 flex 纵向紧贴;中屏两栏(左+中上右下)、宽屏
三栏并排;窄屏用 display:contents + 每模块 order 恢复原单列
顺序。构建产物 316093 字节,sha 与构建输出一致。
2026-10-29 10:56:36 +08:00
host c89c157e5d console: 按宽度自适应分栏(一栏/两栏/三栏)
GitHub 风格不变,仅 tokens.css 改布局:窄屏 <900px 一栏
(按原 DOM 顺序堆叠),中屏 900–1399px 两栏(左:任务列表
+ 项目,右:派发 + 对话 + 详情 + 环境 + Workers + 技能 +
自动化 + 分诊 + 管理区 + API Key),宽屏 >=1400px 三栏
(左 310px:任务列表 + 项目,中弹性:派发 + 对话 + 详情,
右 330px:环境 + Workers + 技能 + 自动化 + 分诊 + 管理区
+ API Key),盒子按内容高度不拉伸,页面最大宽 1560px,
窄屏断点修正为 899px 避免与 900px 重叠。console 标记单测
本地 53 项通过。
2026-10-29 10:30:16 +08:00
host e90c885290 console: GitHub 风格(Primer)
保持一页式控制台排版与全部功能、API 不变,只换视觉:
深色顶栏 #24292f、Primer 浅色配色(边框 #d0d7de、链接蓝
#0969da、绿主按钮 #2da44e)、6px 圆角方框、系统字体与
等宽 ID/trace;任务列表改 issues 式(状态圆点 + 标题 +
灰色元信息);对话改评论时间线(白框 + 头部 + 正文,
竖线连接),灰/蓝气泡已移除;自动选项目与继续消息历史
不受影响。console 标记单测本地 53 项通过。
2026-10-29 10:13:30 +08:00
host 8b455c6554 console: 一页式控制台排版 + 数据卡片
保持 Muse 风格(白底、细线、灰/浅蓝气泡),不再四栏切视图:
顶栏(连接/健康/配额/刷新 + 锚点导航)+ 6 张数据卡片
(健康、配额、任务、Workers、项目、项目数据,均取自已有
store 数据,未加载不以 0 充数)+ 所有模块按顺序铺在一页
(派发、任务列表、对话+详情、项目、环境、Workers、技能、
自动化、分诊、管理区、API Key)。图标栏与右侧栏删除,
内容分别移到顶栏与各自模块下;今天列表与任务列表重复,
不再单独保留;对话为空时不再重复渲染派发表单。
含自动选第一个项目修复(主要聊天不再空)与继续消息
历史,console 标记单测本地 53 项通过。
2026-10-29 09:57:26 +08:00
host f02c513729 chat: 持久化继续消息,chat-history 交替历史
基线 main 811912c(PR #125 Muse 风格之后):服务端与单测与
9538825 相同;React 侧在 Muse 风格 ContinuePanel 上重放
chat-history 集成(新版输入框占位文案保持 Muse 风格的“消息”),
console.html 由新版 src 重新构建。

- 新表 task_continue_messages:POST /continue 的用户消息与状态改回
  queued 同一事务落盘,409 被拒不留 phantom 消息
- GET /tasks/{id}/chat-history:prompt + 继续 + 后续消息 + 链路按
  时间交错拼好返回;部署前继续以 legacy_continue_count 如实标注
- React ContinuePanel 优先用 chat-history,旧服务端自动回退
2026-10-29 07:01:57 +08:00
host 66790b5123 console: 按 Muse 网页端/桌面端视觉重做
仅视觉与布局改动,功能与 API 未改:全屏四栏(56px 图标栏 +
260px 列表 + 弹性对话 + 300px 右侧栏),左侧搜索 + 主要聊天
列表,中间灰/浅蓝气泡对话 + 圆角输入条,右侧头像 + 已连接
(真实 key/健康状态)+ 今天任务清单,下方仍可展开原详情。
旧 v3 松绿/朱印与顶部大标题栏已移除,产物为单文件
(316226 字节,sha f8fea987…,本地重建字节一致)。
console 标记单测未动,本地 53 项通过。
2026-10-29 06:52:34 +08:00
host ebc5624893 console: React 生产级重写(左列表 + 中对话 + 右详情)
旧单页 console.html 换成 Vite + React + TypeScript 单文件构建产物
(309525 字节,sha 90345ec8…,重建字节一致),源码在
cloud/console-react/,改页面只改 src 再 npm run build:console,
产物不手改。布局:左侧列表(模块导航 + 任务列表)、中间标准对话
(实时链路时间线 + 继续会话聊天框,共用 Chat 组件)、右侧详情
(元数据、操作、评审、审批、协调子任务、子代理、线程)。
API 与后端语义未改,key 只存浏览器 localStorage。
6 个 console 标记单测随实现改名更新(意图不变:壳确实调了
这些接口、turn 信息仍在详情里、旧 goal 输入仍不存在);
P1 标记单测未动且通过。
2026-10-29 06:32:38 +08:00
host ebfae7b992 worker: always start capacity pool threads in run_all
run_all previously sized its pool from the ready snapshot at round start (min(capacity, ready_count)). If a round started with only 1 ready task (e.g. a just-claimed PM), only 1 thread ran; that thread entered a marker wait (up to 3600s) after its model turn, and children created/retried mid-round had no free thread to claim them. run_loop is blocked joining that round, so no new round starts; if the other worker had already scaled down, the queued child waited out the full wait timeout unclaimed. Fix: always start capacity threads. Idle threads exit immediately when no task is ready and nothing is active, and poll every 0.2s while something is active, so mid-round arrivals are claimed. Test: run_all with capacity=2, 1 initial task whose mocked run_one creates a child then polls for it; without the fix the PM poll times out in 5s (1 result), with the fix both tasks dispatch in the same round (0.2s).
2026-10-29 05:41:35 +08:00
host 14757f15c4 feat: 子任务默认模型/强度(child_model/child_effort)
主任务可显式声明派生默认,或在 prompt 里写“子任务用 gpt-6-luna max”
由服务端解析填入;marker task-create 与协调员分工未显式写模型/强度时
按 块/分工 > 派生默认 > 父任务模型/强度 继承,fork 复制派生默认,
continue 可改派生默认;console 新建/继续表单与任务详情同步展示。
2026-10-29 05:27:22 +08:00
host ccb22c960d worker: heartbeat parent task during marker poll/wait loops
Marker waits run after _run_attempt has cleared _running, so the run_loop heartbeat thread no longer refreshes the waiting parent. sweep_stale_tasks (default 300s) then kills a parent waiting up to the 3600s marker timeout, as seen in the marker live test (PM2 swept mid wait-threads). Poll and wait loops now heartbeat the parent each iteration (interval clamped to <=60s, well under the 300s threshold), best-effort, without touching _running, so no new capacity leak and cancel still works (empty _running entry has no proc/container to kill). Tests: 2 new sweep-survival tests (stale heartbeat at -299s, 2s wait, sweep must spare the parent); both fail 2/2 on pre-fix code and pass with the fix.
2026-10-29 04:40:47 +08:00
host b411a97774 worker: release running slot after coordinator completes
Coordinator tasks bypass _run_attempt, so their _claim placeholder in _running was never cleared: each completed coordinator permanently reduced the worker process effective capacity until it could claim nothing, while heartbeat stayed fresh and the capacity gate returned silently. This caused the 2026-10-28 40-person stall (autoscale-1 had one leaked slot, then claimed coordinator A and filled up) and the old 18-person C-6 never-claim (autoscale-1 leaked both A and B). Fix: wrap the run_one coordinator branch in try/finally _running_clear, so normal and exception paths both release. Also log capacity-gate refusals (worker_claim_refused_capacity) throttled to once per 30s; throttling applies to the log only, refusal always returns False. Tests: coordinator completion releases slot, next claim succeeds, capacity-log throttle; both leak tests fail on pre-fix code.
2026-10-29 02:42:52 +08:00
host 37b47cadc6 autoscaler: guard scale-down against live coordinators and busy victims
Two veto layers in reconcile(), worker.py untouched:
- Global live-coordinator veto: a running task whose
  coordinator_parts parse (via cloud.coordinator.parse_parts)
  to a non-empty list and whose last_heartbeat is within
  120s blocks all scale-down this round; low_streak is
  frozen, not reset, so scale-down resumes on the next
  low-depth round once the veto lifts.
- Per-victim busy veto: a LIFO victim whose workers row
  is alive with running>0 is skipped, but only when the
  DB also has a live running task (ghost registry rows
  with no live DB task do not block).
- Coordinator-veto query failure fails closed (no stop,
  guard error recorded); victim-veto read failure fails
  open for that layer only.
- A missing/stale workers row is treated as idle.

Tests: 7 new ScaleDownGuardTestCase cases (live/stale
coordinator, non-coordinator, invalid parts, busy/idle
victims, ghost registry, streak freeze); cloud suite
1077 tests OK (4 skipped).
2026-10-29 00:19:30 +08:00
host 5d8403e756 fix(db): register auto-memory tables in _OR_REPLACE_PK
Postgres 翻译层对未登记主键的表把 INSERT OR REPLACE 退化为
ON CONFLICT DO NOTHING,致使 memory_settings、task_memory_flags、
memory_extraction_log 首写之后的所有更新静默丢弃(2026-10-28
生产实测:提取日志永远停在首写原因)。补三张表主键登记,并
在翻译层加回归测试(SQLite 原生 OR REPLACE 测不出来)。
2026-10-28 22:49:06 +08:00
host d7f0f1be96 fix: pass search env into Docker task containers (SEARXNG_URL was lost)
Docker tasks do not inherit the worker process environment, they only
get extra_env (-e). SEARXNG_URL in the worker unit EnvironmentFile
therefore never reached codex -> MCP inside the container, so the
auto chain skipped SearXNG and only DuckDuckGo was tried (live e2e:
'all search providers failed: duckduckgo (DNS)').

_docker_task_context now merges any set SEARXNG_URL /
PERPLEXITY_API_KEY / EXA_API_KEY / TAVILY_API_KEY / BRAVE_API_KEY
from the worker environment into extra_env (values never logged;
paid keys follow the same extra_env path as gateway keys).

Tests: 2 new docker-env tests; websearch + tenant isolation = 25
passing; full cloud suite 1038 passing (4 skipped).
2026-10-28 22:17:19 +08:00
openqj ffeed25f50 Merge pull request #115 from openqj/feat-rate-limit-retry-after
429 限流加 Retry-After 头
2026-10-28 20:22:11 +08:00
host 3b2fef95fa 任务内标记工具:task-create/task-poll/task-read 移植、wait-threads 接通、新增 task-cancel/task-retry
- 新模块 cloud/marker_tools.py:纯逻辑解析、校验、快照格式化、
  控制门禁、派生代数与直接上限计数
- sandbox 两处解析提取五种标记块;worker 在 5 处投递点与
  _deliver_peer_messages 同款位置执行,报告排成后续消息下一轮回父任务
- wait 每块 cap 到 8 个目标(与服务端 /wait 对齐,spawn 原先的 10 个收紧),
  每轮最多 2 块;取消/重试门禁:同项目、非自己、非 goal、
  派生链或同创建人,另查 create_task / approve_merge 权限,
  查不到时收紧到仅派生链内
- goal 与协调员任务不解析;单测 30 个,cloud 全量 1043 个、4 跳过、0 失败
2026-10-28 10:13:40 +00:00
host c43eaac4d3 429 限流加 Retry-After 头:值为限流窗口秒数,不改阈值与计数
- _send_json 加 extra_headers 参数,429 返回处传 Retry-After =
  limiter.window_seconds(默认 60),客户端据此退避一个完整窗口,
  不再以原速率 hammer 把自己钉在限流里(Postgres DbRateLimiter
  先 INSERT 再 COUNT,被拒请求本身也计数)。
- 单测:429 响应含 Retry-After 且值等于窗口秒数(默认 60 与注入
  窗口 7 各验一次)。
- 相关单测:test_prod_webhook_exec + test_rate_limit_db 21 个、
  test_server + test_server_endpoints 28 个,全过;cloud 全量未跑。
2026-10-28 10:12:57 +00:00
openqj eae9884b13 Merge pull request #111 from openqj/feat-coord-row-vanished
协调员行消失按中断处理:本体行消失即放槽,子任务行消失按 interrupted 合流
2026-10-28 17:46:23 +08:00
host b72a2abae4 feat: websearch MCP worker wiring + egress (public SearXNG route)
Worker --websearch-mcp (default off) seeds tenant codex-home with
websearch MCP on _tenant_dirs (container path); old tenants merge
on next task. Autoscaler unit gains the flag. Tinyproxy filter
adds Perplexity/Exa/Tavily/Brave/DuckDuckGo + exact
searx.diante.me (no diante.me wildcard, control plane stays out).
Tests: test_websearch_worker 4 + websearch_mcp 12 + tenant 7 = 23 OK.
Not pushed, not merged, not deployed; SearXNG host/DNS/nginx and
tinyproxy reload are host steps.
2026-10-28 17:26:31 +08:00
host f8acdffb4a feat: web search MCP v1 (oh-my-pi pattern, Python stdlib)
web_search auto chain Perplexity/Exa/Tavily/Brave/SearXNG/DuckDuckGo
with pinned-provider no-fallback, web_fetch with head+tail truncation,
codex-home seeding helper (idempotent, never writes keys).
Tests: cloud/tests/test_websearch_mcp.py 12/12 (mocked HTTP).
Not wired into worker, not deployed, paid backends not live-tested.
2026-10-28 17:14:22 +08:00