feat: harden code queue runtime UX

- add queue merge controls and no-cache overview loading\n- improve runtime probes, judge retries, and task trace rendering\n- extend CLI/E2E/docs coverage for code queue and user services
This commit is contained in:
Codex
2026-05-14 02:20:48 +00:00
parent 79154d9b3f
commit b36d7f94d7
26 changed files with 1323 additions and 349 deletions
+35 -4
View File
@@ -22,6 +22,18 @@ UniDesk 用户服务是挂载到 UniDesk 核心服务上的、面向用户使用
- `development.providerId``development.sshPassthrough=true``development.worktreePath`,用于说明开发调试入口必须在计算节点上通过 UniDesk SSH 透传完成。
- `frontend.route``frontend.integrated=true`,用于说明该业务前端已经整合到 UniDesk React 控制台,而不是继续公开业务自身前端。
## Runtime Configuration And Persistence Contract
每个长期运行的用户服务都必须把“登记配置、容器恢复、状态持久化、真实 health”作为同一交付项处理,不能只把容器拉起后登记到 `config.json`
- 配置来源:`config.json` 只登记 UniDesk 代理与前端入口,业务运行配置必须保存在目标节点的业务仓库、`.state/`、Docker env-file、Windows 用户目录或主 PostgreSQL 中;不得把 token、登录态、运行日志或节点私钥提交到 UniDesk 仓库。
- 容器恢复:长驻业务后端必须使用业务仓库自己的 Compose 或显式 `docker run` 固化容器名、镜像、端口、环境变量、healthcheck、`restart` policy 和持久化挂载;`repository.composeService``repository.containerName` 与实际 Docker 容器必须一致,便于 `microservice list/status`、Docker 状态页和自动恢复脚本互相校验。
- 重启策略:provider-gateway 必须使用 `restart: always`;普通用户服务至少使用 `restart: unless-stopped`,如果服务是节点核心依赖或无人值守入口,可以提升为 `always`,但必须说明手动停止后的恢复语义。仅靠 restart policy 只能覆盖 Docker daemon 已重新启动后的容器恢复,不能替代 Windows 登录任务、systemd、Docker Desktop 自启动或 WSL keepalive。
- 持久化边界:任务、队列、账号会话、订阅、token、未读状态和业务数据不得只存在容器 writable layer 或浏览器本地存储;应写入主 PostgreSQL、业务数据库、Docker named volume 或节点 host bind mount。`.state/` 默认只能放可重建缓存、日志、归档和服务自有小状态;如果某服务把 `.state/` 作为权威状态目录,必须在本节或服务小节明确字段、备份和重启恢复方式。
- 自恢复入口:计算节点上的服务应有节点本地的幂等 autorecover 脚本,在 Docker ready 后用 `docker inspect`、退出码和业务 HTTP readiness 判断是否需要 `docker compose -f <compose> up -d --force-recreate <service...>`;该脚本必须从节点本地计划任务、systemd、Docker Desktop 自启动后的 WSL keepalive 或等价守护触发,不依赖正在被恢复的 UniDesk provider-gateway SSH 会话。
- 真实 health`/health` 必须证明业务“可用”而不是“进程存在”。需要登录、外部连接、GPU、镜像、数据库或后台 worker 的服务,必须在 health body 中暴露这些依赖状态;依赖未满足时必须返回非 healthy 或 `ok=false`,不能让 `/ops/status/` 把不可发消息、不可训练或不可访问的服务计为可用。
- 验收记录:新增或迁移用户服务时,交付说明必须记录 Compose/docker run 路径、restart policy、端口绑定、持久化挂载、关键环境变量来源、health readiness 字段、容器重启或 Docker daemon 重启后的恢复验证,以及公网 UniDesk frontend 或 `microservice health/proxy` 的真实链路结果。
## Compute-Node Development Convention
主 server 本地开发边界固定为只开发 UniDesk frontend 与必要的 UniDesk 配置/代理登记;非 UniDesk 核心功能的后端、Dockerfile、GPU/训练容器、业务数据迁移和业务调试不得默认占用主 server 有限主机资源。涉及 findjob、pipeline、MET Nonlinear 这类业务功能时,应通过 `bun scripts/cli.ts ssh <PROVIDER_ID> ...` 或 remote CLI SSH 透传进入计算节点,在计算节点本地业务仓库中开发、构建和调试;开发完成后,只把业务服务以用户服务形式登记到 UniDesk。
@@ -106,15 +118,19 @@ Baidu Netdisk 在 UniDesk 语境中按纯后端服务管理:不得暴露百度
- 更名与灾备恢复:旧版 Codex 队列服务名只允许作为兼容诊断和一次性迁移来源;`code-queue-backend` 容器自身 `/health` 正常但 `microservice health code-queue` 返回 `microservice not found`、或服务目录仍只出现旧服务 ID 时,优先判定为 backend-core 仍加载旧 `MICROSERVICES_JSON`,必须刷新 `.state/docker-compose.env` 并显式重建/重建替换 `backend-core`,随后用 `microservice list` 验证 `id=code-queue``nodeBaseUrl=http://code-queue:4222` 和容器摘要。若更名后 `unidesk_code_queue_*` 为空而历史 `unidesk_codex_queue_*` 表仍有队列数据,恢复前必须先停止 `code-queue-backend`,备份 `.state/code-queue` 与当前 `unidesk_code_queue_*` 表,再把历史本地状态目录合并到 `.state/code-queue/`,并用 `docker exec -i unidesk-database psql ...` 这类保持 stdin 的方式把 `unidesk_codex_queue_tasks``unidesk_codex_queue_queues``unidesk_codex_queue_notifications` 迁移到对应 `unidesk_code_queue_*` 表;不得在确认 `/api/tasks``/api/queues` 和 output archive 可读前删除历史本地状态目录或旧 PostgreSQL 表。迁移完成后只允许用 `docker compose --env-file .state/docker-compose.env up -d --no-deps code-queue``server rebuild code-queue` 启动目标服务,禁止在灾备窗口里无意执行会连带重建 database/backend-core 的裸 `up -d code-queue`
- Codex 认证:容器只从主 server 的 `/root/.codex/config.toml` 同步 Codex provider 配置到 `.state/code-queue/codex-home`,并通过运行时环境透传 `OPENAI_API_KEY``CRS_OAI_KEY` 等 provider 所需变量;这些 provider 环境变量必须由 `writeComposeEnv` 写入 `.state/docker-compose.env` 并由 Compose 注入,确保 `server rebuild code-queue` 的外部 Docker job runner、自重建和容器重启后不会丢失认证。新增 provider 的 `env_key` 时必须增加同类运行时透传和 Compose env 持久化,禁止把 Codex 或 MiniMax 密钥写入仓库文件。Code Queue 开发容器必须只读挂载 host 的 root SSH 目录到 `/root/.ssh`(默认 `${UNIDESK_HOST_ROOT_SSH_DIR:-/root/.ssh}`),让容器内 `git push``ssh -T git@github.com` 与 host 使用同一套 GitHub SSH key/known_hosts;不得把私钥复制进镜像或仓库。
- Develop-ready 镜像:Code Queue 镜像必须在启动前预装 UniDesk/Pipeline 调试所需工具,至少包含 `codex``bun``node``npm`/`npx``git``rg``curl``python3`/`pip3``docker``docker compose``docker-compose``jq``ssh``rsync``make``gcc`/`g++``tar``gzip``unzip`;不得依赖 Codex 任务运行时再 `apt-get install` 这些基础环境。
- 远程开发容器与任务执行 ProviderCode Queue 必须能通过 live API 拉起 D601 等计算节点上的开发容器,入口为 `POST /api/dev-containers/<providerId>/start`,默认 Provider 为 `D601`。该流程由 Code Queue 调用 UniDesk `ssh <providerId>` 维护桥在目标节点创建 `unidesk-codex-dev-<providerId>`,并在主 server 与开发容器之间建立 `ssh -w` TUN 点对点链路;主 server 负责对开发容器的 TUN 源地址做 NAT/MASQUERADE,开发容器默认路由和 DNS 改走该 TUN,从而让 `ping google.com`、DNS、HTTP(S) 等出网都经主 server 全局代理,而不是依赖 D601 本地网络。提交 Code Queue 任务时必须支持选择执行 Provider:`main-server` 在本机 Code Queue 容器中执行且默认工作目录保持 `/root/unidesk`;其他 Provider 在对应 `unidesk-codex-dev-<providerId>` 容器中执行,默认工作目录为 `/home/ubuntu`,可按任务覆盖 `cwd`。远程任务启动前必须自动复用或拉起该 Provider 的开发容器、同步 Codex 配置和允许的运行时 provider 环境变量,并通过同一 master TUN/NAT 链路出网。验收必须保留三类日志:容器直连 `google.com` 在建隧道前失败、容器建隧道后 `ping google.com` 成功、主 server 上对应 `UNIDESK-CODEX-DEV-<providerId>` NAT 链或 `tun<id>` 计数在 ping 前后增长。开发容器代理密钥只生成到 `.state/code-queue/dev-proxy/` 与目标节点用户目录,不得提交到仓库。
- 远程开发容器与任务执行 ProviderCode Queue 必须能通过 live API 拉起 D601 等计算节点上的开发容器,入口为 `POST /api/dev-containers/<providerId>/start`,默认 Provider 为 `D601`。该流程由 Code Queue 调用 UniDesk `ssh <providerId>` 维护桥在目标节点创建 `unidesk-codex-dev-<providerId>`,并在主 server 与开发容器之间建立 `ssh -w` TUN 点对点链路;主 server 负责对开发容器的 TUN 源地址做 NAT/MASQUERADE,开发容器默认路由和 DNS 改走该 TUN,从而让 `ping google.com`、DNS、HTTP(S) 等出网都经主 server 全局代理,而不是依赖 D601 本地网络。提交 Code Queue 任务时必须支持选择执行 Provider:`main-server` 在本机 Code Queue 容器中执行且默认工作目录保持 `/root/unidesk`;其他 Provider 在对应 `unidesk-codex-dev-<providerId>` 容器中执行,默认工作目录为 `/home/ubuntu`,可按任务覆盖 `cwd`。远程任务启动前必须自动复用或拉起该 Provider 的开发容器、同步 Codex 配置和允许的运行时 provider 环境变量,并通过同一 master TUN/NAT 链路出网;目标 host 存在 `/mnt` 时,开发容器必须挂载 host `/mnt:/mnt`,确保 D601 这类 WSL 节点的 Windows 盘符路径如 `/mnt/f/Work/ConStart` 在任务容器内可见,避免 agent 因缺少真实工作区而搜索到无关项目。TUN 建立必须幂等处理 stale 状态:启动前清理旧 `tun<id>`、默认路由和旧 tunnel SSH 进程,缺失旧设备不能导致失败,冷启动运行时准备要有有界但足够的 timeout。验收必须保留三类日志:容器直连 `google.com` 在建隧道前失败、容器建隧道后 `ping google.com` 成功、主 server 上对应 `UNIDESK-CODEX-DEV-<providerId>` NAT 链或 `tun<id>` 计数在 ping 前后增长;涉及 WSL 工作区任务时还必须在开发容器内验证目标 `/mnt/...` 路径可读。开发容器代理密钥只生成到 `.state/code-queue/dev-proxy/` 与目标节点用户目录,不得提交到仓库。
- 远程 Provider 准备不得阻塞控制面:Code Queue 在请求处理、队列调度、远程开发容器准备、Host SSH/WSL SSH 透传、Codex/OpenCode 启动和日志导出路径中,禁止使用会长时间占用 Bun event loop 的同步子进程调用,例如针对远程 Provider 的 `spawnSync``execSync``execFileSync`。远程命令必须通过异步子进程执行,带显式 timeout、超时 kill、stdout/stderr 上限和任务 output 进度记录;远程准备失败只能让对应任务进入失败或 retry,不能让 `POST /api/tasks`、SSE `/api/events``/health`、overview 或前端 direct proxy 等控制面请求等待远程 SSH 结束。凡是改动 D601/远程 Provider 准备、`api/dev-containers/*`、任务入队启动或 `runCodeQueueSsh` 等路径,验收必须在一个远程 SSH/status/start 探针运行期间并发验证容器直连 `/health``/api/tasks/overview` 仍能在 1s 内返回,证明远程超时不会复发为全站刷新卡死。
- OpenCode 远程执行:`minimax-m2.7` 走 OpenCode JSON event port 时,本地和远程命令都必须显式执行 `opencode run ...`;远程 Docker exec 不得退化成 `exec run ...`,否则会在目标容器内变成 `bash: exec: run: not found`。OpenCode JSON stream 的终态判定以“当前进程退出码 + 当前 attempt 的最终 assistant response”为准:`exit=0` 且当前 attempt 产生非空最终回复时,即使上游没有发 `step_finish` 事件,也应视为正常 terminal;非零退出、无当前最终回复或传输关闭才进入 retry。每个 attempt 的 `finalResponse` 必须只来自当前 OpenCode/Codex turn,禁止在当前 turn 未产出最终回复时回退复用 task 上一次 `finalResponse`,否则会把旧任务内容误判为本轮完成。
- Codex 控制:服务内部启动 `codex app-server --listen stdio://`,用 JSON-RPC 调用 `thread/start``turn/start``turn/steer``turn/interrupt`,并监听 `turn/completed`、assistant delta、reasoning delta、command output delta、file diff delta 等通知生成前端可轮询的 transcript。
- 用户输入持久化:任务初始 prompt 以 `basePrompt/displayPrompt` 作为结构化来源,运行中追加的 `turn/steer` prompt 必须写入 `promptHistory`transcript 构建时从这些结构化字段合成 `Submitted prompt``Steer prompt`,不能只依赖有 600 条上限的 raw output,否则长任务输出增长后会丢失关键人工指令。
- 队列语义:`POST /api/tasks``/api/tasks/batch` 入队,服务始终只运行一个 Codex turn;当前任务真正终止后才推进下一个任务。`GET /api/tasks``GET /api/tasks/{id}` 返回队列、attempt、judge 和输出;`GET /api/tasks/{id}/summary` 返回按任务 ID 查询的结构化摘要,包括初始 prompt、最后 assistant message、工具调用摘要、attempt、judge、错误和耗时;CLI 入口是 `bun scripts/cli.ts codex task <taskId>``POST /api/tasks/{id}/steer` 向运行中 turn 推入 prompt`POST /api/tasks/{id}/interrupt``DELETE /api/tasks/{id}` 打断/取消;`POST /api/tasks/{id}/retry` 手动重试。队列 worker 必须隔离单个 task 的异常,不能因为某个 app-server、judge 异常或 judge 判定 `fail` 让后续 queued 任务停止;`fail` 只把当前任务标为 failed,随后必须继续扫描并推进下一个 queued/retry_wait 任务。当存在 queued/retry_wait 且 worker 空闲时,watchdog 必须自动重新调度。
- 稳定性与重启恢复:Code Queue 的第一目标是长期稳定可用;部署修复或运维排障时不得因为担心容器重启会打断任务而拒绝重启、重建或替换 `code-queue-backend`。容器重启、服务进程重启和镜像替换后,队列、`promptHistory`、running/judging/retry_wait 任务和 active session 元数据必须从 PostgreSQL 恢复,并在已有 `codexThreadId` 可用时用 `thread/resume` 和 continuation prompt 无缝继续当前任务;如果原 app-server turn 已丢失,也必须把当前任务恢复到可 retry/continue 的状态,不能错误推进下一个任务或永久卡住。主 server 侧重建必须走 `server rebuild code-queue`,该 job 受 `.state/locks/server-compose.lock` 串行化约束,并且必须在 build 后执行 no-deps force-recreate 与 post-up health validation;禁止在 job 中先手工 `docker rm` 再依赖后续命令补救,因为中断窗口会让容器消失并触发 frontend `direct microservice proxy failed`。重启后出现 active task 丢失、手动 steer/interrupt 记录丢失、running 任务卡死、误判完成、跳过当前任务、容器消失或阻塞队列,均属于 Code Queue 的 P0 核心缺陷,必须先修复并补充 restart-recovery 验收,不能把“避免重启”作为交付策略。
- 调度与 active run slotCode Queue 必须把“queue processor 正在等待/退避/轮询”和“实际占用 Codex/OpenCode 子进程运行槽”分开建模;`CODE_QUEUE_MAX_ACTIVE_QUEUES` 只限制真实 active run slot,不能把 retry backoff、等待内存下降或等待前序任务的 `processingQueues` 计入 active slot,否则设置全局 active slot 上限时,一个空等队列会把其他 runnable queue 永久饿死。多个 queue 同时等待 active slot 时必须显式维护 FIFO waiter 队列,避免某个长 retry/backoff 队列刚释放 slot 就立刻重抢,导致更早进入等待的 `retry_wait` 任务长期饥饿;`/health` 必须同时暴露真实 `activeQueueIds``activeRunSlotCount`、等待中的 `processingQueueIds` 和 active slot waiters,排障时以 active run slot 与 waiter 顺序判断是否真的有任务在跑、谁应下一个启动。restart-recovery 后的 `retry_wait` 任务若缺失 `codexThreadId`/OpenCode session id,不得无限拒绝 retry;必须用紧凑 recovery prompt 和原始任务摘要重新开一个 agent thread/session,让任务继续推进并在 Trace 中留下 recovery 证据。任何修改 scheduler、retry backoff、queue move、manual retry、shutdown recovery 或内存等待逻辑时,都必须保留“空等 processor 不占 active run slot”、“等待者 FIFO 不饥饿”和“缺失 thread/session 可恢复”的自测或 live 验证。
- 内存优化过程与防回归:主 server 内存预算很小,Code Queue 的内存治理必须按“PostgreSQL 权威源优先、进程热状态最小化、容器硬上限兜底”的顺序设计。长期可复用的优化路径是:先确认任务、queue、readAt、promptHistory、active session 和通知 outbox 均可从 PostgreSQL 恢复;再把历史任务列表、详情、统计、Trace/output 和 `/health` 的只读查询改为 PostgreSQL 直读或聚合查询;随后只把 `queued``running``judging``retry_wait` 等调度必需任务载入 Bun 堆,并在 PostgreSQL 查询侧裁剪 hot `output`/`events`;最后用 dirty-only flush、append-only 输出归档、Codex SQLite 小批量导出、`bun --smol``mem_limit=600m``memswap_limit=1536m``NODE_OPTIONS=--max-old-space-size=768` 和 cgroup memory watchdog 作为运行时防线。PostgreSQL 到进程的单次读取足够快,不能为了减少 SQL 查询把全部历史 `task_json`、Trace、output 或统计摘要常驻内存;任何新增缓存都必须有默认较小的环境变量上限、明确淘汰策略、可从 PostgreSQL 或 append-only 归档重建,且不得影响重启恢复。新增或修改 `/api/tasks`、overview、stats、summary、transcript、output、trace、health、flush、scheduler 和通知路径时,禁止在常规请求中调用会物化全量历史任务 JSON 的代码,禁止启动后无条件重写全量历史 task JSON,禁止用未设上限的 `Map`/数组保存历史 output/event/Trace`CODE_QUEUE_MAX_ACTIVE_QUEUES=0` 表示不按 queue 数量设置全局排队上限;如显式设置为正数,必须同时说明内存预算并补充内存压测验收。memory watchdog 必须以 cgroup working set 为主要判断,且在 swap 仍有余量时不得提前杀掉唯一 active run;否则 TypeScript/Playwright 这类短时高内存验证会被错误中断并让 retry 队列反复震荡。
- 完成判定:app-server `turn/completed``turn.status=completed|interrupted|failed` 只代表 Codex turn 已结束;即使 `completed` 也必须把原始任务、assistant 最终回复、command/file-change 事件、stderr tail 和 current attempt events 组成 execution record 交给 judge 判断是否真的完成。配置了 `UNIDESK_CODE_QUEUE_MINIMAX_API_KEY` 且 MiniMax 可用时,MiniMax `MiniMax-M2.7``complete|retry|fail` 的判定是权威结果;当且仅当 MiniMax LLM 调用失效(未配置、额度/限流/网络/超时不可用、JSON 去噪与 repair 全部耗尽、或返回超预算反馈且修复耗尽)时,才允许启用非 LLM/fallback 判断。任何字符串匹配、正则、硬编码 safety override、`hardCompletionBlockers`/`retryRequiredReasons``recentOutput` 中旧 attempt 的 429/exceeded retry limit 证据、或面向特定任务的保护逻辑,都不得覆盖、降级、提升或重写一次成功的 MiniMax 判定;尤其不能因为 attempt 1 的限流中断仍在历史输出里,就禁止 MiniMax 把 attempt 2 的正常完成判为 `complete`。MiniMax 返回必须先做 JSON 去噪,支持去除 Markdown fence、`json` 标签和从夹杂文本中提取平衡 JSON object;如果去噪后仍无法解析,服务必须把解析错误和上一轮去噪前原始回答反馈给 MiniMax 做 JSON repair 重试,重试次数由 `UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_REPAIR_ATTEMPTS` 控制,默认 `2`,耗尽后才进入 fallback,并在 fallback 原因中保留 MiniMax 失败信息
- Judge 权威边界:MiniMax 成功返回可解析、预算内的 judge JSON 后,Code Queue 必须直接采用该 `decision/reason/continuePrompt`,不得再执行本地 post-validation、协议级完成门禁或 safety override`hardCompletionBlockers``retryRequiredReasons` 这类本地门禁字段不得出现在发送给 MiniMax 的 `executionRecord` 中。只有 MiniMax 不可用或修复耗尽进入 fallback 时,才允许基于字符串/正则做保守 retry
- 列表/详情延迟优化原则:Code Queue 控制面交互的长期目标是常规历史规模下首屏、`GET /api/tasks/overview``POST /api/tasks/<id>/read` 和分页加载均在 1s 内完成;性能面板出现十几秒级 `code_queue_direct_proxy``core_proxy` 慢操作时,必须优先按后端查询形态和前后端通信策略定位,不能把问题归因于 React 渲染后只改 UI。后端优化顺序是:先为 queue、status、updated/created 时间、readAt/terminal unread 和常用筛选条件补齐 PostgreSQL 索引;再用 SQL `COUNT``GROUP BY`、条件聚合和分页 ID 查询生成 queue/status/stats/unread 摘要;随后按 ID 轻量加载当前页、selected、active 和 unread priority task,禁止为了列表或已读操作解析完整 Trace、output archive、Codex transcript 或物化全量历史 `task_json``read`/`read-all` 这类 mutation 必须是 SQL-only 更新并返回最小 patch/queue 计数,不能触发 overview 全量重算或重载所有任务;启动 warm 只能预热小体积聚合和索引路径,不得把历史任务作为常驻缓存。允许 frontend/backend 代理使用秒级、严格有界、mutation 自动失效的 overview micro-cache 来吸收重复刷新,但 cache 只能作为抖动保护,不能替代数据库索引、聚合查询和分页披露,也不能让 stale readAt/queue/status 状态跨设备可见
- Trace/实时输出热路径防回归:Code Queue 的 `appendOutput`、output archive append、`publishTaskEvent`、SSE `/api/events`、任务列表、overview、task meta 和 `/health` 都属于热路径,必须保持 O(1) 或明确小常数上界;这些路径不得同步调用完整 transcript 构建器、`taskFullOutput`、output archive 全量读取、Codex session/log 文件解析、完整 `task_json` 物化或任何会随历史输出长度增长的统计。输出追加时必须增量维护轻量持久化指标,至少包括 `stepCount``llmStepCount``outputMaxSeq` 或等价字段;列表、overview、meta、SSE 事件和 `/health` 只能读取这些指标或小体积 SQL 聚合。完整 Trace、`trace-summary``trace-steps``trace-step`、transcript/output 详情允许在显式详情请求中解析归档,但必须分页或有界、使用短 TTL 或容量受限缓存,并在 archive append 后失效。若 frontend 性能面板出现 Code Queue direct proxy 502、`/api/tasks/overview`/trace 接口成批超时,或容器内 `/health` 在 active output 持续追加时也卡住,优先按 Bun event-loop starvation/backpressure 排查,而不是先改 React 渲染;修复必须证明热路径不再随 output/archive 历史线性增长
- 完成判定:app-server `turn/completed``turn.status=completed|interrupted|failed` 只代表 Codex turn 已结束;即使 `completed` 也必须把原始任务、当前 attempt 的 assistant 最终回复、command/file-change 事件、stderr tail 和 current attempt events 组成 execution record 交给 judge 判断是否真的完成。MiniMax judge 之前和之后都必须保留少量协议级硬门禁:明确用户 interrupt 判为 fail;当前 attempt `terminalStatus=failed|null`、传输在终态前关闭、或当前 attempt 最终回复为空时判为 retry;这些门禁只保护“本轮 turn 是否可被验收”的事实,不得发明业务实现要求。协议门禁通过后,配置了 `UNIDESK_CODE_QUEUE_MINIMAX_API_KEY` 且 MiniMax 可用时,MiniMax `MiniMax-M2.7` 对业务是否 `complete|retry|fail` 的判定是权威结果;当且仅当 MiniMax LLM 调用失效(未配置、额度/限流/网络/超时不可用、JSON 去噪与 repair 全部耗尽、或返回超预算反馈且修复耗尽)时,才允许启用非 LLM/fallback 判断。MiniMax 返回必须先做 JSON 去噪,支持去除 Markdown fence、`json` 标签和从夹杂文本中提取平衡 JSON object;如果去噪后仍无法解析,服务必须把解析错误和上一轮去噪前原始回答反馈给 MiniMax 做 JSON repair 重试,重试次数由 `UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_REPAIR_ATTEMPTS` 控制,默认 `2`,耗尽后才进入 fallback,并在 fallback 原因中保留 MiniMax 失败信息。
- Judge 权威边界:MiniMax 成功返回可解析、预算内的 judge JSON 后,Code Queue 不得用旧 attempt 的 429/exceeded retry limit 证据、历史 output 字符串、面向特定任务的正则或 `hardCompletionBlockers`/`retryRequiredReasons` 覆盖一次协议有效的完成判定;尤其不能因为 attempt 1 的限流中断仍在历史输出里,就禁止 MiniMax 把 attempt 2 的正常完成判为 `complete`。允许的本地 safety override 必须限定为协议事实和系统交付纪律:用户显式打断、当前 attempt 未正常终止、当前 attempt 没有最终 assistant response、最终回复停在并发文件确认而非交付、或 runtime/UI/service 变更承认未部署验证。所有 override 都必须写入 `_safetyOverride`、生成紧凑 continuation prompt,并由自测或 judge probe 覆盖;不得把业务猜测伪装成本地硬门禁。
- Retry/推进语义:`retry` 不是新开一个独立任务或完全新 session;只要已有 `codexThreadId`,服务必须 `thread/resume` 原 thread 并 append 一个继续执行 prompt。continuation/judge feedback prompt 只应携带本轮缺口、恢复原因、验收要求和有界原始任务摘要,禁止重新注入完整引用上下文、历史 transcript 或长 JSON;服务重启恢复类 feedback 尤其必须保持短 prompt,依赖现有 thread 上文继续。超长 prompt 必须在 prompt 合成源头解决:每个 feedback/recovery/judge 生成器都要从结构化字段选择必要信息、去重合并缺口并提供按需查询入口,禁止先合成超长 prompt 再在末端用 substring/safePreview 一刀切硬截断;硬截断会静默丢失验收信息,风险高于长 prompt 本身。若 MiniMax `continuePrompt` 超出预算,必须要求 MiniMax 基于原始 judge 输入重新合成紧凑反馈,repair 耗尽后才可进入 fallback;不得把已生成的长 prompt 截尾后发送给 Codex。若 MiniMax 成功返回了预算内 `continuePrompt`,必须原样使用该反馈,不得再用 71-Freq、`period_sum/mpu_read_num``mpu_read_num`、历史限流中断等字符串识别把它覆盖成“简洁原始需求 continuation”。只有 judge 判定 `complete` 后,队列 worker 才把当前任务标为成功并推进下一个 queued/retry_wait 任务。非 LLM/fallback 判定产生的 `retry` 最多累计 `3` 次;达到上限后当前任务必须转为 `failed` 并记录原因,worker 继续推进后续 queued/retry_wait 任务,避免 fallback safety override 或硬编码判断造成无限循环。
- Judge 探针:`GET|POST /api/judge/probe` 使用同一套 judge 逻辑跑内置 synthetic execution records,覆盖正常完成、正常结束但只给计划、未上线/未部署的服务或 WebUI 改动、传输中断和用户打断等样本,返回 `hits``total``hitRate`、每例 `expected``decision`;该接口不得回显 MiniMax API key。
- 模型选择:默认 Codex 模型是 `gpt-5.5`,内置模型队列包含 `gpt-5.5``gpt-5.4-mini``gpt-5.4``gpt-5.5` 的默认 reasoning effort 必须是 `xhigh`,可通过 `CODE_QUEUE_MODEL_REASONING_EFFORTS` 追加或覆盖模型级默认值;每个入队任务可通过前端模型下拉菜单或 API 覆盖 `model``cwd``reasoningEffort``maxAttempts``maxAttempts` 上限为 `99`。Judge 判定 `retry` 或非用户取消类 `fail` 时必须继续已有 `codexThreadId`,不能新建 session;重试间隔使用指数退避,从 `1s` 开始,最大 `10min`。MiniMax 不可用而进入 fallback/non-LLM 判定时,当前 attempt 的 429、Too Many Requests、exceeded retry limit、overloaded、stream disconnected 等服务/限流错误应判定为 `retry`,不能当作完成;MiniMax 可用时,这些内容只能作为当前 attempt 的 factual evidence 提供给 MiniMax,不能通过硬编码覆盖 MiniMax 结果。
@@ -132,6 +148,16 @@ Baidu Netdisk 在 UniDesk 语境中按纯后端服务管理:不得暴露百度
- `met-nonlinear`MET Nonlinear 训练编排服务,UniDesk frontend 渲染 GPU/镜像、训练队列、Project config 预览、训练进度、ETA 和历史记录。
- `claudeqq`ClaudeQQ 纯后端 QQ 消息网关,UniDesk frontend 渲染 NapCat 连接、事件订阅、消息推送、最近 QQ 事件和发送记录。
### D601 Docker Restart Recovery
D601 是 Windows + WSL Ubuntu + Docker Desktop 节点,Docker Desktop 当前 `LiveRestore=false` 时,机器或 Docker daemon 重启会停止容器,恢复链路必须同时覆盖 Windows 登录、WSL keepalive、Docker daemon ready、provider-gateway 和业务用户服务:
- Windows 登录任务:计划任务 `UniDesk-D601-Autostart` 在用户 `DESKTOP-1MHOD9I\liang` 登录时运行 `C:\WINDOWS\System32\cmd.exe /c ""C:\Users\liang\AppData\Local\UniDesk\d601-autostart.cmd""`,工作目录为 `C:\Users\liang\AppData\Local\UniDesk`
- Windows launcher`C:\Users\liang\AppData\Local\UniDesk\d601-autostart.cmd` 先启动 `%ProgramFiles%\Docker\Docker\Docker Desktop.exe`,再执行 `C:\Windows\System32\wsl.exe -d Ubuntu -u ubuntu -- /bin/bash -lc "/home/ubuntu/.local/bin/unidesk-d601-autostart task"`D601 的 WSL distro 名必须写 `Ubuntu`,不能写成未验证的 `Ubuntu-22.04`
- WSL keepalive`/home/ubuntu/.local/bin/unidesk-d601-autostart` 使用 `~/.state/unidesk/d601-autostart.lock` 防重复,启动 WSL `sshd`,等待 `docker info`,把 `unidesk-provider-gateway-D601` 修正为 `restart always` 并启动,然后调用 `/home/ubuntu/.local/bin/unidesk-microservice-autorecover boot`;进入常驻 watchdog 后每 300 秒重复检查 provider-gateway 和用户服务。
- 用户服务 autorecover`/home/ubuntu/.local/bin/unidesk-microservice-autorecover` 只在 Docker ready 后运行;它用容器 `State.Running``State.ExitCode` 和轻量 HTTP 探针判断是否需要恢复,MET Nonlinear 失败时在 `/home/ubuntu/met_nonlinear` 执行 `docker compose -f docker-compose.unidesk.yml up -d --force-recreate met-nonlinear-ts`ClaudeQQ/NapCat 失败时在 `/home/ubuntu/.agents/skills/claudeqq` 执行 `docker compose -f docker-compose.unidesk.yml up -d --force-recreate napcat claudeqq`
- 验收命令:Docker 恢复后必须同时验证 `docker inspect --format '{{.HostConfig.RestartPolicy.Name}} {{.State.Status}}' met-nonlinear-ts claudeqq-backend claudeqq-napcat``bun scripts/cli.ts microservice health met-nonlinear``bun scripts/cli.ts microservice health claudeqq` 和公网 frontend 页面;ClaudeQQ 还必须验证 `/api/napcat/login``state=logged_in`、HTTP connected 和 WebSocket connected。
### FindJob On D601
当前 FindJob 作为 `id=findjob` 的用户服务登记在 `config.json`
@@ -171,6 +197,8 @@ Pipeline 的一个 epoch 是同一个 pipeline 从入口到终态完整执行一
- 数据挂载:D601 的 `/mnt/f/BaiduSyncdisk/data` 挂载为训练容器内 `/data/data`,并设置 `MET_DATA_BASE=/data`,让旧配置中的 `data/M50` 解析为 `/data/data/M50`WSL 本机不安装 TensorFlow 训练环境。
- 代码引用:`https://github.com/pikasTech/met_nonlinear` 与配置中的 `repository.commitId`
- 部署引用:业务仓库内 `docker-compose.unidesk.yml``docker/unidesk/Dockerfile.server``docker/unidesk/Dockerfile.ml``composeService=met-nonlinear-ts``containerName=met-nonlinear-ts`
- 运行配置:`docker-compose.unidesk.yml``met-nonlinear-ts` 必须固定 `container_name: met-nonlinear-ts``restart: unless-stopped``127.0.0.1:3288:3288``extra_hosts: ["host.docker.internal:host-gateway"]`,并把 `/var/run/docker.sock``/usr/lib/wsl/lib:ro``/dev/dxg` 挂入容器,让 TS 编排后端能按需拉起 GPU 训练容器并读取 WSL GPU runtime。
- 持久化路径:业务源码与状态通过 `/home/ubuntu/met_nonlinear:/workspace/met_nonlinear` 挂载,训练数据通过 `/mnt/f/BaiduSyncdisk/data:/data` 挂载;`MET_STATE_DIR=/workspace/met_nonlinear/.state/unidesk-met` 保存队列和服务状态,`MET_LOG_DIR=/workspace/met_nonlinear/logs/unidesk-met` 保存服务日志,`MET_HOST_ROOT``MET_HOST_DATA_ROOT` 必须指向对应 host 路径,避免训练容器从容器内路径误反推 host 文件。
- 节点后端:D601 上 `127.0.0.1:3288`provider-gateway 容器内通过 `http://host.docker.internal:3288` 访问。
- 代理路径:只允许 `/health``/api/` 前缀;允许 `GET``HEAD``POST``PUT`,用于读取队列/历史、从已有 Project fork 新 Project、保存队列设置、加入待启动队列和启动队列。
- UniDesk 前端:`用户服务 / MET Nonlinear` React 页面采用类似下载器的工作台交互,负责从项目库选择已有 Project、fork 新 Project、加入待启动队列、启动队列、调整最大并发、分标签展示当前队列/已完成/失败诊断/GPU 与镜像,并展示训练进度、ETA、训练速度 `epoch/h`、历史训练记录和显式原始 JSON 按钮。项目库必须按 `projects/``ex_projects/` 的真实目录层级渲染文件树,文件夹计数等于子树 Project 数;项目库和任务列表行都必须可点击打开结构化详情,详情以控件展示 `config.json``data/` 中的训练状态、模型参数量、模型层和指标,不默认展示裸 JSON。
@@ -187,10 +215,12 @@ MET Nonlinear 验收必须通过公网 UniDesk frontend 的交互式 UI 完成
- 开发工作树:`/home/ubuntu/.agents/skills/claudeqq`,后端、Dockerfile、订阅分发和 NapCat 连接调试必须通过 UniDesk SSH 透传在 D601 完成;主 server 本地只允许开发 UniDesk frontend 与代理登记。
- 代码引用:`https://gitee.com/lyon1998/agent_skills` 与配置中的 `repository.commitId`,实际服务目录为仓库内 `claudeqq/`
- 部署引用:业务目录内 `Dockerfile``docker-compose.unidesk.yml`Compose service 为 `claudeqq``napcat`,容器名分别为 `claudeqq-backend``claudeqq-napcat`
- 运行配置:`docker-compose.unidesk.yml` 必须同时定义 `napcat``claudeqq` 两个 service,均使用 `restart: unless-stopped``napcat` 固定 `ACCOUNT=${CLAUDEQQ_NAPCAT_ACCOUNT:-763382329}``WEBUI_PREFIX=/webui` 和本机端口 `127.0.0.1:3000/3001/6099``claudeqq` 固定 `127.0.0.1:3290:3290``CLAUDEQQ_AUTO_REPLY=false``CLAUDEQQ_NAPCAT_HTTP_HOST=napcat``CLAUDEQQ_NAPCAT_WS_HOST=napcat``CLAUDEQQ_ONLINE_NOTICE_USER_ID=645275593``CLAUDEQQ_LOGIN_MONITOR_INTERVAL_MS`
- 持久化路径:NapCat 登录态必须保存在业务目录下的 `./napcat/qq:/app/.config/QQ`,NapCat 配置和二维码缓存分别保存在 `./napcat/config:/app/napcat/config``./napcat/cache:/app/napcat/cache`ClaudeQQ 后端必须挂载 `./config.json:/app/config.json:ro``./bot_workspace:/bot_workspace``./logs:/app/logs``./.state:/app/.state``./napcat:/napcat:ro`。如果这些 host 目录丢失或改成匿名 volume,Docker 重启后 QQ 登录态和事件/订阅状态会丢失,不得判定为已具备自动登录。
- 节点后端:D601 上 `127.0.0.1:3290`provider-gateway 容器内通过 `http://host.docker.internal:3290` 访问。
- 代理路径:只允许 `/health``/logs``/api/` 前缀;允许方法为 `GET``HEAD``POST``DELETE`
- 服务模式:ClaudeQQ 在 UniDesk 中按纯后端运行,默认 `CLAUDEQQ_AUTO_REPLY=false`,只负责 NapCat HTTP/WS 连接、QQ 事件入站记录、HTTP webhook 订阅投递和 `/api/push/text` 消息推送,不把 ClaudeQQ 自身旧 WebUI 作为用户入口。NapCat 必须随同 `docker-compose.unidesk.yml` 容器化部署,D601 只绑定 `127.0.0.1:3000``127.0.0.1:3001``127.0.0.1:6099`ClaudeQQ 容器通过 Compose 内网 `napcat:3000/3001` 访问。
- NapCat 登录 API`GET /api/napcat/login``GET /api/napcat/status` 返回容器化状态、HTTP/WS 连通性、登录状态和二维码 data URL;`GET /api/napcat/qrcode` 只返回二维码。二维码来源为共享挂载中的 `/napcat/cache/qrcode.png`,由 ClaudeQQ 后端转为 JSON data URL 后经 UniDesk 同源代理给前端展示。
- NapCat 登录 API`GET /api/napcat/login``GET /api/napcat/status` 返回容器化状态、HTTP/WS 连通性、登录状态和二维码 data URL;`GET /api/napcat/qrcode` 只返回二维码。二维码来源为共享挂载中的 `/napcat/cache/qrcode.png`,由 ClaudeQQ 后端转为 JSON data URL 后经 UniDesk 同源代理给前端展示。`/health` 的 healthy 条件必须包含 `ready=true`、NapCat HTTP connected、NapCat WebSocket connected 和 `loginState=logged_in`;仅有二维码、NapCat 容器 running 或后端进程 running 不能算健康。
- 订阅 API`GET /api/events/recent` 返回最近 QQ 事件,`GET|POST /api/events/subscriptions` 管理 webhook 订阅,`DELETE /api/events/subscriptions/{id}` 删除订阅;订阅回调使用 HTTP POST JSON,并在配置 secret 时携带 `x-claudeqq-signature` HMAC-SHA256。
- 推送 API`POST /api/push/text` 接受 `userId``groupId``message`,由 ClaudeQQ 通过 NapCat HTTP API 发送 QQ 消息;NapCat 不可用时必须快速返回 `status=napcat_offline` 和具体连接错误;当前人工推送验收只允许发给主用户私聊账号 `645275593`,其他用户服务和 main server 应通过 UniDesk 用户服务代理调用,不得直连 D601 公网端口。
- UniDesk 前端:`用户服务 / ClaudeQQ` React 页面负责展示 D601 仓库引用、私有后端映射、NapCat 容器登录二维码、NapCat HTTP/WS 状态、事件缓存、订阅表、订阅创建表单、消息推送表单、主用户私聊账号 `645275593` 标记、最近 QQ 事件和已发送记录;完整原始 JSON 只能通过显式 `查看原始JSON` 打开。
@@ -240,6 +270,7 @@ ClaudeQQ 在 UniDesk 语境中按消息网关后端服务管理:不得直接
- 运行 `bun scripts/cli.ts microservice health todo-note``bun scripts/cli.ts microservice proxy todo-note /api/instances`,确认真实链路经过 backend-core、WebSocket、main-server provider-gateway 和主 server `todo-note-backend` 后端;输出中必须包含五个迁移清单和 PostgreSQL 存储健康状态。
- 运行 `bun scripts/cli.ts microservice health code-queue``bun scripts/cli.ts microservice proxy code-queue /api/tasks`,确认真实链路经过 backend-core、WebSocket、main-server provider-gateway 和主 server `code-queue-backend` 后端,并且 `/health``queue.storage.primary=postgres``queue.storage.postgresReady=true`,不得出现 file fallback`queue.notifications.claudeqq.outbox.storage=postgres` 且暴露 pending/failed/sent 统计。再通过公网 frontend 提交一个 `gpt-5.5` 小任务,确认队列串行推进、输出实时更新、结束后有 judge 判定,且运行中可追加 prompt 或打断。Code Queue 的重启恢复必须作为验收项:运行中任务存在时重启或重建 `code-queue-backend` 后,任务必须从 PostgreSQL 恢复到可继续执行状态,不能丢失 active task、`promptHistory`、后续 queued 任务、readAt/未读状态或已入 outbox 的 ClaudeQQ 通知;ClaudeQQ/NapCat 离线期间结束的任务必须能在 `/api/notifications/claudeqq` 中看到 pending/failed,并在登录恢复后通过 `POST /api/notifications/claudeqq/drain` 发送。Code Queue 服务名、表名前缀或持久化目录发生迁移后,还必须运行 `bun scripts/cli.ts e2e run --only microservice:catalog-code-queue,microservice:code-queue-status,microservice:code-queue-health,microservice:code-queue-tasks`,证明 backend-core catalog、私有代理、PostgreSQL 队列和任务列表都指向 `code-queue`。批量验收必须通过公网 frontend 设置 `入队份数=5` 或使用多段 prompt 分隔,一次性入队 5 条任务,并确认 5 条任务按顺序进入 running/judging/succeeded,而不是只运行第一条。
- Code Queue 内存防回归验收:凡是改动 Code Queue 的持久化、scheduler、输出/Trace、health、列表/详情查询、日志导出或容器运行参数,交付前必须用 `docker compose --env-file .state/docker-compose.env config``docker inspect code-queue-backend` 确认 memory 硬上限为 `629145600` 字节、memory+swap 上限为 `1610612736` 字节,运行 `docker stats --no-stream code-queue-backend` 确认常驻内存低于 `600MiB``OOMKilled=false``RestartCount` 未异常增长,再运行 `bun scripts/cli.ts microservice health code-queue` 确认 `/health` 通过 PostgreSQL 汇总队列而不是物化全量历史任务,并能看到 active run slot 与 waiter 状态。验收还必须覆盖有历史任务存在时的 `/api/tasks`、单任务详情和 output/transcript 查询,证明热状态裁剪不会丢历史输出、也不会重新把全部历史 `task_json` 缓存在进程内;涉及 TypeScript/frontend 验证的任务应能在该 600M memory + 1536M memswap 预算中完成 `bun run --cwd src/components/frontend check` 这类短时高内存命令,而不是被 memory watchdog 反复 SIGTERM。
- Code Queue 延迟防回归验收:凡是改动 Code Queue 列表、overview、readAt、Trace/summary 懒加载、实时 output/SSE 事件发布、frontend 请求策略、backend-core 用户服务代理或 frontend direct proxy,交付前必须在有历史任务数据且有 active output 流动的 live 环境验证 `GET /api/tasks/overview``POST /api/tasks/<id>/read`、选定 task 的 `trace-step` 和前端 `/app/code-queue/` 首屏均低于 1s 目标;可运行 `bun scripts/src/code-queue-perf.ts --json --target-ms 1000` 采集公网 frontend 下的首屏耗时、最慢 API 和 DOM 完成指标,并用 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview --raw`、容器直连 `/health``/api/tasks/overview` curl、性能面板 `/api/performance``/api/frontend-performance` 失败/慢操作记录、`docker stats --no-stream code-queue-backend` 补充后端耗时、代理 502 和内存/CPU 证据。验收结论必须同时说明是否使用了短 TTL cache、cache 如何被 mutation 或 archive append 失效、数据库索引/聚合是否命中、输出热路径是否只读增量指标,以及分页加载是否跳过 selected/active/stats;不能只展示 cache 命中后的单次快照。
- 运行 `bun scripts/cli.ts microservice health filebrowser``bun scripts/cli.ts microservice health filebrowser-d601``bun scripts/cli.ts microservice proxy filebrowser / --max-body-bytes 2000`,确认 File Browser health 返回 `status=OK`WebUI HTML 包含 `File Browser`D518/D601 通过 provider-gateway 访问节点本机 `4251`;随后在公网 frontend 的 `用户服务 / File Browser` 中确认 D518 为默认目标、可导出截图、iframe 紧凑布局不再有巨大 `folder` 标记遮挡文件名,并可浏览 `/mnt/c`
- 在 D601 上用 `bun scripts/cli.ts ssh D601 ...` 调试业务仓库和容器,确认 `curl http://127.0.0.1:3254/api/health` 可用;不要把调试服务部署到主 server。
- 在 D601 上用 `bun scripts/cli.ts ssh D601 ...` 调试业务仓库和容器,确认 `curl http://127.0.0.1:18082/health``curl http://127.0.0.1:18082/api/snapshot` 可用;不要把 Pipeline 调试服务部署到主 server。