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
openqj
075908fe58
Merge pull request #112 from openqj/feat-websearch-mcp
...
feat: web search MCP v1 (oh-my-pi pattern, Python stdlib)
2026-10-28 17:21:19 +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
host
c38ee8fbe9
协调员行消失按中断处理:本体行消失即放槽,子任务行消失按 interrupted 合流
...
- _run_coordinator 等待循环里 get_task 为 None 时按取消跳出,
不再等到 deadline 占槽;终局 CAS 写不回去,最终为 interrupted,
不写回、不抛错、不当完成。
- _coord_child_rows 对已删子任务合成 interrupted 行并注明行已消失,
等齐判断不再拖到超时,合流报告标 interrupted 且整体不为 completed,
其它子任务不受影响;docstring 旧说法同步改成新语义。
- 新增 cloud/tests/test_coordinator_row_vanished.py,3 个单测:
本体行消失、子任务行消失、正常等齐;cloud 全量 1020 测通过。
2026-10-28 08:57:05 +00:00
openqj
5026e75b2c
Merge pull request #109 from openqj/feat-goal-fail-resume
...
goal 异常退出记 interrupted,可断点续跑
2026-10-28 15:53:35 +08:00
host
94137c4a29
goal 异常退出记 interrupted,可断点续跑
...
长 goal 实弹(2026-10-28):宿主机内存挤兑致回合挂起 59 分钟,恢复时 goal 进程异常退出、无结论行,云端旧语义(returncode 非 0 一律 failed)把可续跑的断点判成终局。
修法:host.py run_goal_loop 回合异常统一记 interrupted 并打印 goal interrupted after rounds 标记行;cloud/sandbox.py 四处 goal 解析同步新语义(标记或无结论非 0 退出 -> interrupted,有结论的非 0 退出仍 failed);worker 终局审计名与状态对齐。单测:sandbox 新测 5 个、host 新测 1 个、更新旧测 1 个;三套件 cloud 1017、host 61、deploy 29 全绿。只本地提交,未推未部署。
2026-10-28 15:18:26 +08:00
openqj
d129f7bf8c
Merge pull request #108 from openqj/feat-auto-memory
...
云端身份记忆 + 自动提取注入:官方 memories 对齐(确定性 v1)
2026-10-28 12:19:17 +08:00
host
cbc90f9d30
自动记忆:官方 memories 对齐的提取+注入闭环(确定性 v1)
...
开关默认关(generate/use),设置接口 GET/PUT /users/me/auto-memory-settings,任务级覆盖 POST /tasks/{id}/auto-memory-flags,手动触发 POST /users/me/auto-memory/run,日志 GET /users/me/auto-memory-log。
提取:worker run_loop 轮次边界扫 completed 任务,闲置/时长/长度/外部上下文/配额剩余百分比五道门,脱敏后写 project 级 identity_memories,同一任务去重、同一目标 supersede 合并;只写 project 级,不跨项目泄漏。
注入:worker run_one 首轮把最新各 5 条用户/项目记忆拼进内存 prompt 副本(goal 任务同步改内存 goal),库原 prompt 不动;续跑/断点/协调员不注入,注入失败 fail-open。
v1 诚实边界:extract/consolidation 模型只存设置不调模型;MCP/web 线程内调用暂无 tasks 表标记,外部上下文门只认任务间消息与协调员子任务;无控制台 UI。
2026-10-28 12:12:10 +08:00
host
b93666fbe8
合并云端身份与记忆 PR#102(6e9d6dc)到最新 main ea5ab62
2026-10-28 11:58:49 +08:00
openqj
ea5ab6207c
Merge pull request #93 from openqj/feat-cost-notify-3
...
成本预算与失败通知第三批:G7 分层、G8 Docker 轮询掐断、G9 worker 忽略 host 预算、G13/G14 归属
2026-10-28 11:49:44 +08:00
host
9f49b97cd3
test: G14/open 旧口径测试按 P1 口径更新
...
- test_system_quota_exempt_server → test_system_quota_not_exempt_server:system 按普通属主算,quota 0 时照常拦(402)。
- test_system_task_exempt_from_quota → test_system_task_not_exempt_from_quota:system quota 0 时认领门拦,标 interrupted。
- test_poll_quota_lookup_failure_open → test_poll_quota_lookup_failure_open_returns_check_failed:open 异常时回 quota_check_failed(边界拒绝、轮询经 _quota_hit 过滤只警告),closed 回 quota_check_unavailable。
2026-10-28 11:25:17 +08:00
host
e31fcc6cba
feat: G7 分层 fail-open/fail-closed + G8 Docker 轮询掐断 + G9 worker 忽略 host 预算 + G13/G14 归属(第三批)
...
- G7:认领门 closed 时查配额异常回 quota_check_unavailable、标 interrupted(可重试/续跑);轮询与轮次边界用 open,异常时回 quota_check_failed——新回合边界拒绝,执行中轮询经 _quota_hit 闭包过滤此串、只记 warning 不杀在跑任务,确认超限才掐断;查询函数本身不吞异常冒充不限额。
- G8:Docker 路径加 _docker_call_with_quota_poll(后台线程跑阻塞式 docker 调用,主循环每 quota_poll_interval_seconds 缺省 30 秒查一次,确认超限按容器名真杀容器,结果改写 quota_exceeded 走统一收尾:interrupted、成本照记、续跑消息保留,配额命中后不走 ALREADY_EXISTS 回落);同步路径保持边界检查。
- G9:worker 路径默认忽略 host max_daily_turns(host.py 顶层 --ignore-host-budget 只清该项,Local/Docker 执行器在子命令前追加,worker CLI 缺省开、--no-ignore-host-budget 关);standalone 直接跑 host.py 时预算照常。
- G13/G14:/pr 不再走 system 豁免,建任务前按请求用户查配额(402/503 均不排队不改状态),队列路 pr_jobs.created_by 记请求用户(老表 ALTER 补列),worker 跑 pr 任务时审计操作人用 created_by(老行回落 system)。注意:G14 原案的 system 豁免(服务端与 worker 写死)与 P1 冲突(P1:system 按普通属主走默认额度,不另设无限额系统预算),本次 rebase 未合入该豁免,system 豁免需另行决策。
- 配额口径与 P0/P1 对齐:无配额行按默认额度(非不限额),只有显式 NULL 行才不限额;无价上限只计 source='actual' 行;缺属主(含父任务回查仍无)拒绝新回合。
- README 配额节同步以上口径与 G15 接受现状(best-effort 超支、UTC 月切分、在跑任务只在轮询/轮次边界响应配额改动)。G4 与 G15 不在本批。
2026-10-28 11:14:10 +08:00
host
5883169c82
feat: goal 断点续跑端点(G10)+ 摘要持久化与推送 outbox(G11/G12)
...
G10:POST /tasks/{id}/goal-resume,仅 interrupted 的 goal 任务,
走 host.py goal-resume(无 --goal-text)从 goal_rounds_done 下一轮
断点续跑,保留原线程与 worktree,状态回 queued 并置
goal_resume_pending(worker 认领后清掉),写 task_goal_resumed
审计;goal-met/unmet、failed、非 goal 一律 409;/continue 开新
循环语义不动。Local/Docker 两路新增 goal_resume_breakpoint_task,
不支持断点的执行器明确失败,绝不回落成 goal 或开新循环。
G11:digest 非失败事件持久化进 push_digest_events,worker 循环
每小时按(项目、订阅)分组汇总发一条,失败类仍即时发。
G12:webhook/slack 投递失败写 push_outbox,worker 循环后台重试
(最多 3 次、默认 60 秒退避),3 次仍失败删行并记日志,不丢
任务状态;缺凭证与无 sender 不进 outbox;文档写明 at-least-once
下 webhook/slack 可能重复,接收方须去重。
测试:cloud 769(4 跳过)、host 68、deploy 19 全过。
2026-10-28 11:09:29 +08:00
openqj
ac553509ea
Merge pull request #107 from openqj/feat-thread-unarchive-noresume
...
fix: thread/unarchive 不再预 resume
2026-10-28 09:46:57 +08:00
host
f186c60e4e
fix: thread/unarchive 不再预 resume(已归档 thread 直接调 thread/unarchive)
2026-10-28 08:33:04 +08:00
openqj
5c9beb8a6d
Merge pull request #106 from openqj/feat-thread-unarchive
...
fix: thread/unarchive 走已归档解析(--archived 列表)
2026-10-28 06:30:17 +08:00
host
5fd4e23b68
fix: thread/unarchive 走已归档解析(--archived 列表)
...
probe9 实测:thread/archive 200 后 thread/unarchive 稳定 404。
根因是 #103 时代的逻辑 bug:unarchive 走普通解析,普通
thread-list 不传 --archived 时只回非归档 thread,刚归档的
thread 永远解析不到。修法只改服务端:_list_task_threads 加
archived 模式(传 --archived,home×provider 穷尽不变),
_resolve 与 _pick_latest_thread 加归档开关,_task_thread_mutate
加 resolve_archived 参数只供 unarchive;rename/archive/compact
仍走非归档解析。生产只读验证:同一 worktree 普通列表 0 条、
archived 列表恰好 1 条(probe9 归档的 thread),其 archived
字段为 None。
2026-10-28 06:10:02 +08:00
openqj
f5d6d6b9f0
Merge pull request #105 from openqj/feat-thread-resolve
...
fix: 线程解析按 home×provider 穷尽回退(共享 home + sub2api)
2026-10-28 05:45:43 +08:00
host
613ff4e7c4
fix: 线程解析按 home×provider 穷尽回退(共享 home + sub2api)
...
生产实测:app-server 按进程 provider 过滤 thread/list,
不带 sub2api 注入看不到 sub2api 下建的 thread;本地
worker 又用共享 home 而非租户 home。旧解析只查租户
home + 缺省 provider,本地任务一律 404。
修法(只改服务端,不动派发):_run_host_cli 支持顶层
--model-provider;_list_task_threads 按 home(租户→共享,
共享取 host-config-host.json 的 codex_home)× provider
(缺省→sub2api,仅配了 SUB2API_BASE_URL 时)穷尽,首个
非空即停;_resolve_task_thread 把 provider 带给后续的
thread-read/rename/archive CLI;_task_thread_read 改用
解析出的 home + provider(此前误用租户 home)。
三套件:cloud 957 OK(4 跳过)、host 91 OK、deploy 29 OK。
新测试 9 个。未动派发:本地改租户 home 会丢共享 home
里的 skills,另案处理。
2026-10-28 05:36:18 +08:00
openqj
86eb43f1ca
Merge pull request #104 from openqj/feat-thread-lifecycle
...
feat: 线程生命周期对齐 15 项(bindings/refs/快照/心跳绑定/移交落点)
2026-10-28 05:04:53 +08:00
host
cf08c4cf37
feat: 线程生命周期对齐 15 项(bindings/refs/快照/心跳绑定/移交落点)
...
对照 Codex app 第二批:GET /tasks/{id}/bindings(项目/
环境/主机/worktree/PR/附件/血缘总表)、/refs 外部引用
增删查、/worktree/archive 快照(branch/head/status/diff/
untracked 路径)与 restore(目录在才解归档、目录删了
409 附快照)、自动化 bind + heartbeat(走 thread/message
同款投递路,cron 绑定不唤醒)、移交 completed 后任务
worktree_host 跟随切换、pr/checks 响应补 head_sha、
thread/read 的 tool 条目补 server/status。
诚实边界见 docs/thread-lifecycle-align.md:已有 worktree
附加、自动化变化静默、建议器、服务端定时触发器未实现;
Memory 对位是待合的 PR #102,本批不重复实现。
三套件:cloud 948 OK(4 跳过)、host 91 OK、deploy 29 OK
2026-10-28 04:56:22 +08:00
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
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
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