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:
@@ -22,7 +22,7 @@ UniDesk 的统一 CLI 入口是根目录 `scripts/cli.ts`,运行方式固定
|
||||
- `codex task <taskId>` 通过 Code Queue 私有代理按任务 ID 查询结构化执行摘要;默认只返回有界 prompt/response 预览、执行 Provider、工作目录、最后 assistant message、最近工具调用摘要、attempt、judge、错误、耗时和 trace 翻页提示,适合在新队列任务中引用历史 session 且避免噪声爆炸。
|
||||
- `codex task <taskId> --trace --tail|--from-start|--after-seq N|--before-seq N --limit N` 按页拉取 Code Queue 的逻辑 trace;响应会返回 `nextAfterSeq`、`previousBeforeSeq`、`hasMore`、`hasBefore` 和下一页/上一页命令,默认 `--trace` 取最新一页,需要完整 prompt/最后 response 时加 `--full`。
|
||||
- `codex output <taskId> --tail|--from-start|--after-seq N|--before-seq N --limit N [--full-text]` 按原始 output seq 分页读取底层记录;当 trace 行提示 `commandOmittedLines`、`bodyOmittedLines` 或 `rawSeqs` 时,用该命令按 seq 补取完整信息,默认仍有单条文本预览上限,显式 `--full-text` 才返回该页全文。
|
||||
- Code Queue 多队列 lane 由 `codex` 命令命名空间管理:`queues` 列表、`queue create <queueId>` 创建、`move <taskId> --queue <queueId>` 迁移;同一个 queue 内部串行执行,不同 queue 之间并行执行,迁移 queued/retry_wait 任务后会立即调度目标 queue。
|
||||
- Code Queue 多队列 lane 由 `codex` 命令命名空间管理:`queues` 列表、`queue create <queueId>` 创建、`queue merge <sourceQueueId> --into <targetQueueId>` 合并、`move <taskId> --queue <queueId>` 迁移;同一个 queue 内部串行执行,不同 queue 之间并行执行。合并只移动任务归属,必须保留源 queue 记录且不提供删除语义;合并后的目标 queue 按任务原 `queueEnteredAt`/`createdAt` 时间顺序串行。迁移 queued/retry_wait 任务后会立即调度目标 queue。
|
||||
- `job list` 与 `job status` 查询 `.state/jobs/` 文件系统状态,是异步命令的可观测入口。
|
||||
- `debug health`、`debug dispatch` 与 `debug task` 走真实内部 core、WebSocket、数据库、provider、系统指标、Docker 状态和 Host SSH 维护桥流程,只用于开发调试,不写入 `TEST.md` 的正式验收步骤。
|
||||
- `e2e run [--only pattern[,pattern...]] [--skip pattern[,pattern...]]` 使用 publicHost 派生的公开 frontend/provider ingress URL,并通过 Docker 内网验证 core API、PostgreSQL、provider self-connection、系统指标曲线、Docker 状态快照、provider.upgrade 预检和 Playwright 前端页面,是交付前的自动化 E2E 门禁;CLI 默认输出 check 状态摘要,完整诊断写入 `resultPath`,日常迭代应优先用 `--only` / `--skip` 跑最小必要集合。
|
||||
|
||||
@@ -43,6 +43,10 @@ frontend 的 Docker 上线顺序为:先运行必要的本地校验,例如 `b
|
||||
|
||||
正式流程不得依赖人工 `docker rm` 兜底;手工删除旧容器后若 job、Docker client 或 daemon 在 `up` 前中断,会直接造成 `direct microservice proxy failed`。`server rebuild <service>` 必须是可观测 job:build-first、Compose lock、no-deps force-recreate、post-up validation、保留 named volume。Code Queue 等长任务服务即使被重建也必须依赖服务自身 restart-recovery 恢复任务,不能用“避免重建”掩盖恢复缺陷。
|
||||
|
||||
## User Service Restart Recovery
|
||||
|
||||
主 server 用户服务和计算节点用户服务都必须按 `docs/reference/microservices.md` 的 Runtime Configuration And Persistence Contract 设计。主 server Compose 内的用户服务通过固定 project、`restart` policy、健康检查、PostgreSQL 权威状态和 `server rebuild <service>` 的 no-deps force-recreate 恢复;计算节点用户服务通过节点本地 Compose/docker run、host bind mount 或业务数据库持久化、Docker restart policy、节点级 autorecover 脚本和 provider-gateway 私有代理验收恢复。上线说明不能只写“容器已启动”,必须说明 Docker daemon 或机器重启后如何重新进入 ready 状态,以及哪些路径/表保存了登录、队列、token、订阅和业务数据。
|
||||
|
||||
## Health Criteria
|
||||
|
||||
服务跑通的最低标准是:backend-core 内网 `/health` 返回 ok,frontend 公网 `/health` 返回 ok,provider ingress 公网 `/health` 返回 ok,database 在容器内 `pg_isready` 可用,Todo Note 后端 `/api/health` 返回 `storage=postgres`,Code Queue `/health` 返回队列摘要、默认模型和 `queue.storage`,Project Manager `/health` 返回 `storage.primary=postgres` 和项目数量,backend-core `/api/performance` 返回性能指标,`/api/nodes` 中出现 `main-server` provider 且状态为 `online`,`/api/nodes/system-status` 中出现 `main-server` 的 CPU/内存/硬盘采样,`/api/nodes/docker-status` 中出现 `main-server` 的 Docker 快照。交付前还必须运行 `bun scripts/cli.ts e2e run`,并以 `docs/reference/e2e.md` 的门禁作为最终判定。
|
||||
|
||||
+10
-2
@@ -35,7 +35,8 @@ Typical targeted commands:
|
||||
- Core API: `docker exec unidesk-backend-core` calls internal `GET /api/overview`, which must report `dbReady: true`, `pgdata.volumeName=unidesk_pgdata_10gb`, a positive PostgreSQL database byte count, and at least one online node; internal `GET /api/performance` must report component request statistics, internal operation statistics, PGDATA usage and Code Queue PostgreSQL storage metadata.
|
||||
- Provider self-connection: internal `GET /api/nodes` must contain `main-server` with `status: online`, `labels.providerGatewayVersion` equal to `src/components/provider-gateway/package.json`, `labels.providerGatewayUpgradePolicy: "always-enabled"`, `labels.providerGatewayRestartPolicyOk: true`, `labels.providerGatewayPidModeOk: true`, and `labels.providerGatewayRuntimeGuardOk: true`; internal `GET /api/nodes/system-status` must contain CPU/memory/disk samples plus a non-empty process resource list sorted by memory by default; internal `GET /api/nodes/docker-status` must contain a Docker snapshot for `main-server`; every running `provider-gateway` container visible in Docker snapshots must report `restartPolicy: "always"` and `pidMode: "host"`; public provider ingress `/health` must return ok.
|
||||
- Provider remote control: internal `/api/dispatch` must successfully complete a real `provider.upgrade` task in `mode: "plan"` so the upgrade path is validated without recreating the running gateway during E2E.
|
||||
- User services: internal `/api/microservices` must include `todo-note` and `code-queue` on `main-server`, canonical `filebrowser` on `D518`, plus `findjob`, `pipeline`, `met-nonlinear` and `filebrowser-d601` on `D601` with `public=false`; `/api/microservices/todo-note/health` must report `storage=postgres`, `/api/microservices/todo-note/proxy/api/instances` must expose the migrated Todo Note lists, and a temporary Todo Note list create/add/toggle/undo/delete cycle must succeed through the real provider-gateway proxy; `/api/microservices/code-queue/health` must return a Code Queue summary with default model `gpt-5.5`, and `/api/microservices/code-queue/proxy/api/tasks` must return queue state through the same proxy; `/api/microservices/filebrowser/health`, `/api/microservices/filebrowser-d601/health` and `/api/microservices/filebrowser/proxy/` must prove File Browser health and WebUI access through UniDesk proxy; `/api/microservices/findjob/health` and `/api/microservices/findjob/proxy/api/summary` must succeed through the real provider-gateway proxy; `/api/microservices/findjob/proxy/api/jobs?__unideskArrayLimit=jobs:5` must return a bounded preview with `_unidesk.arrayLimits` metadata; `/api/microservices/pipeline/health`, `/api/microservices/pipeline/proxy/api/snapshot?__unideskArrayLimit=registry.components:8,runs:3` and `/api/microservices/pipeline/proxy/api/oa-event-flow/diagnostics` must return Pipeline health, registry/run previews and OA event-flow evidence; `/api/microservices/met-nonlinear/health`, `/api/microservices/met-nonlinear/proxy/api/queue`, `/api/microservices/met-nonlinear/proxy/api/projects?root=projects&limit=500`, `/api/microservices/met-nonlinear/proxy/api/projects?root=ex_projects&limit=500`, `/api/microservices/met-nonlinear/proxy/api/projects/config?path=<projectPath>` and `/api/microservices/met-nonlinear/proxy/api/images` must return the D601 TS backend health, queue/GPU policy, full project tree inputs, structured project detail and ready `met-nonlinear-ml:tf26` image status.
|
||||
- User services: internal `/api/microservices` must include `todo-note` and `code-queue` on `main-server`, canonical `filebrowser` on `D518`, plus `findjob`, `pipeline`, `met-nonlinear`, `claudeqq` and `filebrowser-d601` on `D601` with `public=false`; `/api/microservices/todo-note/health` must report `storage=postgres`, `/api/microservices/todo-note/proxy/api/instances` must expose the migrated Todo Note lists, and a temporary Todo Note list create/add/toggle/undo/delete cycle must succeed through the real provider-gateway proxy; `/api/microservices/code-queue/health` must return a Code Queue summary with default model `gpt-5.5`, and `/api/microservices/code-queue/proxy/api/tasks` must return queue state through the same proxy; `/api/microservices/filebrowser/health`, `/api/microservices/filebrowser-d601/health` and `/api/microservices/filebrowser/proxy/` must prove File Browser health and WebUI access through UniDesk proxy; `/api/microservices/findjob/health` and `/api/microservices/findjob/proxy/api/summary` must succeed through the real provider-gateway proxy; `/api/microservices/findjob/proxy/api/jobs?__unideskArrayLimit=jobs:5` must return a bounded preview with `_unidesk.arrayLimits` metadata; `/api/microservices/pipeline/health`, `/api/microservices/pipeline/proxy/api/snapshot?__unideskArrayLimit=registry.components:8,runs:3` and `/api/microservices/pipeline/proxy/api/oa-event-flow/diagnostics` must return Pipeline health, registry/run previews and OA event-flow evidence; `/api/microservices/met-nonlinear/health`, `/api/microservices/met-nonlinear/proxy/api/queue`, `/api/microservices/met-nonlinear/proxy/api/projects?root=projects&limit=500`, `/api/microservices/met-nonlinear/proxy/api/projects?root=ex_projects&limit=500`, `/api/microservices/met-nonlinear/proxy/api/projects/config?path=<projectPath>` and `/api/microservices/met-nonlinear/proxy/api/images` must return the D601 TS backend health, queue/GPU policy, full project tree inputs, structured project detail and ready `met-nonlinear-ml:tf26` image status.
|
||||
- ClaudeQQ availability: `/api/microservices/claudeqq/health` must only pass when `ready=true`, NapCat HTTP and WebSocket are connected, and `napcat.loginState=logged_in`; `/api/microservices/claudeqq/proxy/api/napcat/login` must show the same logged-in account state and `/api/microservices/claudeqq/proxy/api/events/recent` must prove the backend can read the persistent event cache. A QR-code-only or not-logged-in NapCat state must be treated as unhealthy.
|
||||
- Database: the command writes an `unidesk_e2e_markers` row through `docker exec unidesk-database psql`, confirms provider state is stored in PostgreSQL, and checks Todo Note rows exist in `todo_note_instances` using the same named volume.
|
||||
- Pipeline OA event flow: `microservice:pipeline-oa-event-flow` must prove both no-audit and monitor-audit runs are driven by OA events end to end. The event stream must show `node-finished` as a neutral fact with `pipeline:{pipelineId}` and `epoch:{runId}` tags, OA policy as the source of downstream/audit decisions, monitor decisions as OA control events, and runner control-result evidence. E2E must fail if delivery still depends on a legacy detail audit policy flag as policy authority, independent legacy audit-request points, a legacy batch completion gate, direct monitor-to-runner calls, or frontend/CLI writes to Pipeline `.state`.
|
||||
- The same Pipeline OA diagnostics must fail on legacy file-transport residuals. Procedure containers, monitor sessions, UI/Gantt DTO builders and CLI fetches must consume prompt/control/stop/display evidence only from the OA event ledger and normalized HTTP read APIs; `control-prompts.jsonl`, `monitor-prompts.jsonl`, `monitor-control`, `control-events.jsonl`, monitor stop files, `.state/pipeline-runs/{runId}/control/commands/`, `PIPELINE_*_APPEND_FILE`, local JSONL append/read helpers, and monitor `/pipeline-state` mounts are forbidden in runtime source.
|
||||
@@ -44,6 +45,7 @@ Typical targeted commands:
|
||||
- Frontend dense-layout regression gate: whenever a frontend change touches Pipeline 右侧边栏、Trace timeline、详情抽屉、甘特图坐标或其他高信息密度面板, Playwright acceptance must inspect both `总高度` and `横向滚动条`. For Pipeline specifically, the OpenCode Trace session head must carry shared agent/model/session facts and the Trace body must use the same Code Queue `TraceView` styling; Playwright must fail if old `.pipeline-opencode-step`, `.pipeline-opencode-flow`, `.pipeline-step-message-card` or `.pipeline-opencode-part` user-visible styles reappear, if the Trace container introduces an internal horizontal scrollbar, or if `frontend:pipeline-gantt-frontend-y-accuracy` fails to prove the frontend `frontend-y` layout maps ticks, markers and execution bars from timestamps to y coordinates within tolerance.
|
||||
- OpenCode Trace must use Code Queue Trace styling and must not render the deprecated Pipeline continuous step connector; Playwright should fail if `.pipeline-opencode-flow`, `.pipeline-opencode-step` or any equivalent continuous connector/card returns to the user-visible Trace.
|
||||
- User service frontend assertions must wait for real backend data, not only the page skeleton. For Todo Note this means the page must show the migrated lists `CONSTAR`、`大论文`、`找工作`、`小论文`、`事务`, support creating a temporary list and task through the frontend, and delete that temporary list afterwards. The temporary list must be selected again by its unique generated name before deletion so E2E never deletes a migrated source list by accident. For FindJob this means the page must show a numeric `岗位总量`, `HEALTH OK`, and a non-empty `PREVIEW` count such as `40/1463 PREVIEW`; for Pipeline this means the page must show `Pipeline v2 工作台`, `Health OK`, a numeric component count, a non-empty React Flow control graph, `控制图`, `Epoch 甘特图`, and after clicking a Gantt execution line it must show `OpenCode Trace` rendered by the shared Code Queue-style Trace component with messages and tool-call groups; for MET Nonlinear this means the page must show `MET Nonlinear 训练编排`, `Health OK`, `Fork Project`, `加入待启动队列`, `启动队列`, `当前队列`, 最大并发设置、task queue and GPU/image panels, and must not show the removed hard-coded `创建10个10轮任务` frontend entry. The MET Nonlinear project library must render `projects/` and `ex_projects/` as a true path tree with folder Project counts; clicking a project row must open a structured detail panel containing `config.json`, `data/ 训练状态`, `模型参数`, `指标` and a parameter count such as `Total Params`; clicking a completed/current/failed job row must open a structured job detail and both the row and detail must show `epoch/h`. Full MET Nonlinear acceptance is driven by public frontend controls: choose a visible source Project, set batch size, epochs and max concurrency in inputs, fork into `projects/unidesk_forks/`, stage the selected forks, start the queue, and verify completed rows plus automatic `metnl-train-*` container removal; loading placeholders like `--` or empty states are not sufficient for E2E success.
|
||||
- For ClaudeQQ this means the page must show `Health OK`, `NapCat 容器登录`, `NAPCAT HTTP OK`, `NAPCAT WS OK`, logged-in state such as `已登录 logged_in`, event cache, subscriptions and message push controls. A page that only shows a QR code, stale raw JSON, or a running backend without logged-in NapCat is not acceptable.
|
||||
|
||||
## Frontend JSON Rule
|
||||
|
||||
@@ -53,7 +55,7 @@ Remote update records in the frontend are covered by the same rule: `provider.up
|
||||
|
||||
Provider operation availability is also covered by the structured rendering rule. `host.ssh` availability must be displayed as badges or equivalent controls derived from capabilities and `hostSsh*` labels, and remote update availability must be displayed from `provider.upgrade` capability plus the `always-enabled` policy; these fields must not require opening raw Provider JSON.
|
||||
|
||||
User service pages are covered by the same rule. `Todo Note` must show lists, task tree, filters, reminder input, movement controls, undo/redo and metrics as controls; `Code Queue` must show queue cards, live transcript, model/cwd/max attempt inputs, judge decision, attempt table, append prompt, interrupt and retry controls; `File Browser` must show D518 as the default target, D601 as an alternate target, a screenshot export action, and an embedded upstream WebUI frame served through `/api/microservices/<id>/proxy/` with compact file rows that do not let material-icon fallback text cover file metadata; `FindJob` must show metrics, jobs and drafts as cards/tables; `Pipeline` must show component classes, React Flow graph nodes/edges, run cards, Gantt execution lines and OpenCode Trace timelines as controls; `MET Nonlinear` must show queue rows, GPU/image cards, a real path tree for the project library, structured project/job detail panels, project config preview, `data/` training state, model parameter count, metrics, progress bars, ETA, `epoch/h` speed and history diagnostics as controls; the full user-service config, summary, snapshot, jobs preview, drafts and run JSON can only appear after an explicit `查看原始JSON` click.
|
||||
User service pages are covered by the same rule. `Todo Note` must show lists, task tree, filters, reminder input, movement controls, undo/redo and metrics as controls; `Code Queue` must show queue cards, live transcript, model/cwd/max attempt inputs, judge decision, attempt table, append prompt, interrupt and retry controls; `File Browser` must show D518 as the default target, D601 as an alternate target, a screenshot export action, and an embedded upstream WebUI frame served through `/api/microservices/<id>/proxy/` with compact file rows that do not let material-icon fallback text cover file metadata; `FindJob` must show metrics, jobs and drafts as cards/tables; `Pipeline` must show component classes, React Flow graph nodes/edges, run cards, Gantt execution lines and OpenCode Trace timelines as controls; `MET Nonlinear` must show queue rows, GPU/image cards, a real path tree for the project library, structured project/job detail panels, project config preview, `data/` training state, model parameter count, metrics, progress bars, ETA, `epoch/h` speed and history diagnostics as controls; `ClaudeQQ` must show NapCat HTTP/WS/login badges, QR/login panel, event cache, subscriptions and message push controls; the full user-service config, summary, snapshot, jobs preview, drafts and run JSON can only appear after an explicit `查看原始JSON` click.
|
||||
|
||||
## Public Boundary Rule
|
||||
|
||||
@@ -63,6 +65,12 @@ The public frontend URL and provider ingress URL are the only public network int
|
||||
|
||||
The PostgreSQL data volume is the named Docker volume `unidesk_pgdata_10gb`. CLI server control commands must never use `docker compose down -v`, `docker volume rm`, or any equivalent data-volume removal. To validate persistence, insert a marker row into `unidesk_e2e_markers`, run `bun scripts/cli.ts server start` or a full stop/start cycle, and verify the marker row still exists.
|
||||
|
||||
## User Service Restart-Recovery Rule
|
||||
|
||||
Any new user service, service migration, or change to a service's Compose/docker run configuration must prove it can recover after container restart and Docker daemon restart. The delivery evidence must include the service's `config.json` id/provider/container mapping, restart policy, host-bound private port, persistent mounts or PostgreSQL tables, health readiness fields, and at least one post-restart `bun scripts/cli.ts microservice health <id>` plus a representative `microservice proxy` check through the real provider-gateway path.
|
||||
|
||||
D601 services have an extra gate because Windows, WSL and Docker Desktop are separate supervisors: record the Windows scheduled task or equivalent keepalive, run `docker inspect` to confirm `met-nonlinear-ts`, `claudeqq-backend`, `claudeqq-napcat` and any changed service have non-empty restart policies and host bind mounts for durable state, then verify MET Nonlinear queue/image health and ClaudeQQ logged-in NapCat HTTP/WebSocket state after the restart. A service that only becomes `running` but loses login, queue, token, subscription, data directory or pending work is not restart-recovery complete.
|
||||
|
||||
## Delivery Gate
|
||||
|
||||
Before claiming delivery, run these checks and keep their JSON output or screenshot path available for review:
|
||||
|
||||
@@ -10,11 +10,21 @@ frontend 应用源码必须使用 TypeScript + React,禁止在 `src/components
|
||||
|
||||
## Deployment Contract
|
||||
|
||||
任何会影响公网 WebUI 的 frontend 源码、样式或导航变更,都必须在同一任务内完成 Docker 上线;只运行 TypeScript 检查、`Bun.build` 或修改仓库文件不代表公网生效。frontend 容器启动时才从镜像内复制的 `src/components/frontend/src/app.tsx` 及其 TSX imports 构建 `/app.js`,当前 Compose 不对 frontend 源码做宿主机 bind mount,因此运行中的 `unidesk-frontend` 不会自动读取工作区新文件。
|
||||
任何会影响公网 WebUI 的 frontend 源码、样式或导航变更,都必须在同一任务内完成 Docker 上线;只运行 TypeScript 检查、`Bun.build` 或修改仓库文件不代表公网生效。
|
||||
|
||||
标准上线命令固定为在仓库根目录执行 `bun scripts/cli.ts server rebuild frontend`。该命令会先构建 `frontend` 镜像,再在固定 Compose project 下通过 `up -d --no-deps --force-recreate frontend` 替换单个容器,并等待健康检查通过;不要用“本地 bundle 校验通过”代替这一步。上线后必须用 `bun scripts/cli.ts job status latest` 或具体 job id 确认 `status=succeeded`,再用 `bun scripts/cli.ts server status`、公网 `http://74.48.78.17:18081/health` 和对应页面/深链接验证真实公网页面已经加载新 bundle。
|
||||
**关键规则**:frontend 容器启动时才从镜像内复制的 `src/components/frontend/src/app.tsx` 及其 TSX imports 构建 `/app.js`,运行中的 `unidesk-frontend` 不会自动读取工作区新文件。
|
||||
|
||||
如果用户报告公网仍是旧界面,优先排查两件事:第一,确认最近一次 `server rebuild frontend` 的 job 已成功且 `unidesk-frontend` 容器创建时间晚于代码修改时间;第二,浏览器可能保留旧 `/app.js`,应使用无缓存刷新或重新打开无痕窗口,并可用 `curl -H 'Cache-Control: no-cache' http://74.48.78.17:18081/app.js` 检查新 bundle 标记。涉及右键/中键打开新标签页的导航改动,还必须在公网页面 DOM 中确认左侧主模块和顶部子标签是带 `href` 的 `<a>`,普通左键仍走 SPA 路由。
|
||||
**上线流程**:
|
||||
1. 修改源码/CSS
|
||||
2. 执行 `bun scripts/cli.ts server rebuild frontend`
|
||||
3. 用 `bun scripts/cli.ts job status latest` 确认 `status=succeeded`
|
||||
4. 用 E2E `frontend:layout-overflow` 验证改动已生效
|
||||
|
||||
禁止用"本地 bundle 校验通过"代替 `server rebuild frontend`;禁止跳过 E2E 验证直接交付。
|
||||
|
||||
前端修改必须通过 `bun scripts/cli.ts server rebuild frontend` 上线。确认 job status=succeeded 后,用 E2E `frontend:layout-overflow` 验证改动已生效。
|
||||
|
||||
如果公网仍是旧界面:1. 确认 job 已成功且容器创建时间晚于代码修改;2. 强制刷新或开无痕窗口;3. 用 curl -H "Cache-Control: no-cache" http://74.48.78.17:18081/app.js 查新 bundle。
|
||||
|
||||
## Time Zone Contract
|
||||
|
||||
@@ -78,10 +88,11 @@ frontend shell 必须把左侧主模块与顶部子标签编译为统一的 URL
|
||||
- `FindJob` 子标签必须把 D601 findjob 后端渲染为 UniDesk React 控件,包括岗位指标、岗位预览、草稿报告和显式原始 JSON 按钮。
|
||||
- `ClaudeQQ` 子标签必须把 D601 ClaudeQQ 后端渲染为 UniDesk React 控件,包括 NapCat 容器登录二维码、NapCat HTTP/WS 状态、事件缓存、QQ 事件订阅表、订阅创建表单、消息推送表单、主用户私聊账号 `645275593` 标记、最近 QQ 事件、已发送记录和显式原始 JSON 按钮。
|
||||
- `Baidu Netdisk` 子标签必须把主 server `baidu-netdisk-backend` 后端渲染为 UniDesk React 控件,包括 OAuth 设备码二维码/用户码登录、账号容量、配置工作根文件浏览(当前默认百度网盘根目录 `/`)、staging 目录上传/下载任务、上传/下载自测按钮与 MD5 结果、脱敏安全说明、日志摘要和显式原始 JSON 按钮;不得把 access token、refresh token、dlink 或 staging 文件字节流裸露到浏览器。
|
||||
- `Code Queue` 子标签必须把主 server `code-queue-backend` 后端渲染为 UniDesk React 控件,包括多 queue lane、queue 内串行、queue 间并行、任务 ID/复制任务 ID、引用按钮、任务耗时、任务提交/批量提交、引用任务 ID、创建成功提示、清空输入、模型下拉、显式入队份数、默认模型 `gpt-5.5`、MiniMax judge 状态、Codex CLI-like 输出流、attempt 终态、运行中追加 prompt、打断、手动重试和显式原始 JSON 按钮;Codex CLI-like 输出流必须始终保留任务的初始 `Submitted prompt` 和运行中 `Steer prompt`;整个 agent loop 消息流统一命名为专有名词 `Trace`,`Trace` 包含 assistant message、user prompt、system event 和 tool call;Code Queue 与 Pipeline/OpenCode messages 必须共用 `src/components/frontend/src/trace.tsx` 的 Trace 公共组件、统一 Trace item 接口和 codex/opencode port 适配层;连续 read/edit/run 工具调用只是在 Trace 内折叠为可展开工具调用组,汇总格式至少包含 `xx read, xx edit, xx run`,并展示读取文件、编辑文件、运行命令和耗时摘要;最近 3 个工具调用保持展开,工具调用内容不得自动换行且必须在工具调用块内部横向滚动,工具调用组展开后不得再增加额外左侧缩进;message 与 prompt 必须自动换行,普通 message 不显示左侧项目符号缩进且永不折叠;Trace 首屏可以是摘要预览,但终态任务被选中后必须自动在后台加载完整 Trace,手动“加载完整 Trace”也必须从 Code Queue output archive 分页补齐早期 trace,不得把 preview 的 `hasMore=false` 当成完整历史;即使热状态为控制体积裁剪了早期 raw output,也要从结构化 `basePrompt/displayPrompt/promptHistory` 和 archive 合成完整用户输入与 agent trace,并且初始 prompt 默认显示注入前 prompt 而不是引用注入全文;当初始 prompt 含引用注入时,引用内容必须默认折叠,并只在 Trace 的初始消息中提供可展开的“最终传入 Codex 的真实完整 prompt”,不得再渲染独立 Prompt 全量卡片;多轮引用注入必须按上游/最早上下文在前、直接引用在后的顺序排列,每一轮必须有明确 `Reference Round N/M` 分割线和时间范围,不能用固定 6 轮截断引用链;点击队列引用按钮必须自动把该任务 ID 写入提交表单的引用输入框,引用任务 ID 创建新任务时必须自动注入 `bun scripts/cli.ts codex task <taskId>` 的提示;连续执行同一 prompt 应通过入队份数一次性生成多条任务,避免快速连点造成操作员误判。
|
||||
- `Code Queue` 子标签必须把主 server `code-queue-backend` 后端渲染为 UniDesk React 控件,包括多 queue lane、queue 内串行、queue 间并行、queue 合并(只能合并任务归属、不能删除源 queue,合并后按原 queueEnteredAt/createdAt 时间顺序串行)、任务 ID/复制任务 ID、引用按钮、任务耗时、任务提交/批量提交、引用任务 ID、创建成功提示、清空输入、模型下拉、显式入队份数、默认模型 `gpt-5.5`、MiniMax judge 状态、Codex CLI-like 输出流、attempt 终态、运行中追加 prompt、打断、手动重试和显式原始 JSON 按钮;Codex CLI-like 输出流必须始终保留任务的初始 `Submitted prompt` 和运行中 `Steer prompt`;整个 agent loop 消息流统一命名为专有名词 `Trace`,`Trace` 包含 assistant message、user prompt、system event 和 tool call;Code Queue 与 Pipeline/OpenCode messages 必须共用 `src/components/frontend/src/trace.tsx` 的 Trace 公共组件、统一 Trace item 接口和 codex/opencode port 适配层;连续 read/edit/run 工具调用只是在 Trace 内折叠为可展开工具调用组,汇总格式至少包含 `xx read, xx edit, xx run`,并展示读取文件、编辑文件、运行命令和耗时摘要;最近 3 个工具调用保持展开,工具调用内容不得自动换行且必须在工具调用块内部横向滚动,工具调用组展开后不得再增加额外左侧缩进;message 与 prompt 必须自动换行,普通 message 不显示左侧项目符号缩进且永不折叠;Trace 首屏可以是摘要预览,但终态任务被选中后必须自动在后台加载完整 Trace,手动“加载完整 Trace”也必须从 Code Queue output archive 分页补齐早期 trace,不得把 preview 的 `hasMore=false` 当成完整历史;即使热状态为控制体积裁剪了早期 raw output,也要从结构化 `basePrompt/displayPrompt/promptHistory` 和 archive 合成完整用户输入与 agent trace,并且初始 prompt 默认显示注入前 prompt 而不是引用注入全文;当初始 prompt 含引用注入时,引用内容必须默认折叠,并只在 Trace 的初始消息中提供可展开的“最终传入 Codex 的真实完整 prompt”,不得再渲染独立 Prompt 全量卡片;多轮引用注入必须按上游/最早上下文在前、直接引用在后的顺序排列,每一轮必须有明确 `Reference Round N/M` 分割线和时间范围,不能用固定 6 轮截断引用链;点击队列引用按钮必须自动把该任务 ID 写入提交表单的引用输入框,引用任务 ID 创建新任务时必须自动注入 `bun scripts/cli.ts codex task <taskId>` 的提示;连续执行同一 prompt 应通过入队份数一次性生成多条任务,避免快速连点造成操作员误判。
|
||||
- `Code Queue` 前端改进必须在同一任务内重建并上线公网 frontend,不能只修改源码或本地 bundle;重建 frontend 是无状态 WebUI 替换,不会导致 Code Queue 长期任务失败。已结束未读任务只能在 task card 边角显示类似未读消息的 `codex-unread-badge` 圆点和“标为已读”操作,不得把整张卡片改成红色/琥珀色失败态边框、背景或胶囊标签;状态栏的“结束未读”提示也不得使用失败态红色。
|
||||
- `Code Queue` 前端必须把 PostgreSQL-backed backend API 作为 task、queue、readAt/未读状态和 attempt 状态的唯一数据来源;不得用 `localStorage`、`sessionStorage` 或 IndexedDB 持久化这些业务状态,也不得在后端标记已读失败时伪造本地成功。前端允许保留 React 内存态、请求 in-flight guard 和本轮页面缓存,但刷新页面或切换设备后的状态必须完全由后端 PostgreSQL 数据恢复。
|
||||
- `Code Queue` 的 queue/session 左侧边栏必须提供 task 关键词搜索,并采用顶部对齐和内容高度优先布局:搜索栏、列表、分组和 task card 都不得用居中、space-between、stretch 或隐式等高网格去拉满侧栏高度;item 少时允许下半部分留空,不能把单个 item 拉高来铺满;每个 task card 必须显示 `最近更新: ...前` 这类相对更新时间,便于判断运行中的 Trace 是否卡住;`queued` task card 的状态徽标必须显示排队原因,例如 `QUEUED(PREV TASK)`、`QUEUED(MEM LIMIT)`。提交任务时必须立即锁定 prompt、引用 ID、queue、模型、工作目录、最大尝试和入队份数等输入控件,显示等待状态,并用前端 in-flight guard 阻止重复点击造成重复入队;当解析到多个待入队任务时必须显式要求用户勾选批量确认,防止 `---` 分隔或入队份数误操作导致错误传入多个任务。Trace 面板的主滚动条使用全站细窄现代滚动条;工具调用块内部的横向滚动必须可滚动但隐藏横向滚动条,避免移动端阅读被滚动条占用。公共 `TraceView` 的自动滚动必须采用 follow-tail 语义:只有当前滚动位置在底部附近时才跟随新增输出;用户手动向上滚动后立即暂停自动滚动,异步刷新不得把视图拉回底部,直到用户再次滚动到最底部才恢复自动跟随。
|
||||
- `Code Queue` 前后端通信必须采用渐进式披露:首屏只请求 queue/task 轻量摘要、必要的 selected preview 和小体积统计,不得默认拉取完整 transcript、raw output、原始 JSON 或全部历史任务;加载下一页或搜索分页时必须显式传递 `selected=0`、`includeActive=0`、`stats=0` 等价开关,避免每一页重复请求 selected/active/stats;点击/选中 task 后再按需加载 summary、prompt part、trace step、raw output 或完整 Trace。`read`/`mark all read` 应调用专用 mutation 并用后端返回的 patch 更新当前内存态,不能为了隐藏未读圆点而强制刷新完整 overview;请求仍需遵守 PostgreSQL 权威源,失败时不得本地伪造已读。Code Queue 性能问题应优先通过缩小 API 响应、分页/cursor、去重 in-flight 请求、短 TTL 且 mutation 失效的页面缓存和后端 SQL 聚合解决,避免以重写渲染层或把大 JSON 藏在 DOM/React state 中规避慢请求。
|
||||
- `Code Queue` 的 queue/session 左侧边栏必须提供 task 关键词搜索,并采用顶部对齐和内容高度优先布局:搜索栏、列表、分组和 task card 都不得用居中、space-between、stretch 或隐式等高网格去拉满侧栏高度;item 少时允许下半部分留空,不能把单个 item 拉高来铺满;每个 task card 必须显示 `最近更新: ...前` 这类相对更新时间,便于判断运行中的 Trace 是否卡住;`queued` task card 的状态徽标必须显示排队原因,例如 `QUEUED(PREV TASK)`、`QUEUED(MEM LIMIT)`。SSE/事件驱动刷新只能更新队列列表与当前选中任务的详情,不得因为其他任务产生 `task-step` 或 `task-updated` 而自动切换当前选中的 task;只有用户点击任务、提交新任务或显式切换 queue 才能改变选中任务。提交任务时必须立即锁定 prompt、引用 ID、queue、模型、工作目录、最大尝试和入队份数等输入控件,显示等待状态,并用前端 in-flight guard 阻止重复点击造成重复入队;当解析到多个待入队任务时必须显式要求用户勾选批量确认,防止 `---` 分隔或入队份数误操作导致错误传入多个任务。Trace 面板的主滚动条使用全站细窄现代滚动条;工具调用块内部的横向滚动必须可滚动但隐藏横向滚动条,避免移动端阅读被滚动条占用。公共 `TraceView` 的自动滚动必须采用 follow-tail 语义:只有当前滚动位置在底部附近时才跟随新增输出;用户手动向上滚动后立即暂停自动滚动,异步刷新不得把视图拉回底部,直到用户再次滚动到最底部才恢复自动跟随。
|
||||
- 用户服务页面不得 iframe 业务旧前端、Todo Note 原 Vite 前端或 Pipeline 自身 WebUI,不得把用户服务后端端口暴露为浏览器直连 URL,也不得把业务 API 的 JSON 裸铺在页面上。
|
||||
- `Pipeline` 子标签是 D601 `/home/ubuntu/pipeline` 的 UniDesk host UI。
|
||||
- Pipeline 仓库自带 WebUI 前端已经废弃;UniDesk frontend 是唯一用户可见的 Pipeline UI。
|
||||
|
||||
@@ -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` 这些基础环境。
|
||||
- 远程开发容器与任务执行 Provider:Code 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/` 与目标节点用户目录,不得提交到仓库。
|
||||
- 远程开发容器与任务执行 Provider:Code 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 slot:Code 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。
|
||||
|
||||
@@ -29,3 +29,9 @@ backend-core 必须把 queued、dispatched、running 视为待处理任务,并
|
||||
backend-core 必须提供 `/api/performance`,返回滚动窗口内的 HTTP 组件请求统计、最近失败请求、内部操作统计、最近慢操作、进程内存、PGDATA 用量和 Code Queue PostgreSQL 存储摘要。组件统计必须包含请求数、失败数、失败率、平均延迟和 P95,内部操作统计必须包含服务名、操作名、次数、平均延迟和 P95;失败和慢操作记录必须保留时间、状态、耗时、路径或细节,避免只给汇总数字而无法定位。
|
||||
|
||||
frontend Bun server 必须提供同源 `/api/frontend-performance`,记录 webui 静态资源、登录/session、API 代理和 frontend->core 代理操作耗时。浏览器中的 `运行总览 / 性能面板` 必须把 frontend 与 backend-core 指标合并展示为 Bwebui 曲线、组件汇总、最近失败请求、内部操作汇总和最近慢操作;完整性能 JSON 只能通过显式 `查看原始JSON` 打开。
|
||||
|
||||
性能优化必须先用这些指标锁定慢操作名称、路径、耗时和代理层级,再改后端查询或前后端通信策略;不得只凭主观体感改 UI。Code Queue 这类控制面页面出现 `code_queue_direct_proxy`、`core_proxy`、`GET /api/tasks/overview`、`POST /api/tasks/<id>/read` 等超过 1s 的慢操作时,应保留优化前后的性能面板证据,并同时记录 live API 耗时、容器内存、`/health` 存储摘要和是否仍通过 PostgreSQL/append-only archive 重建历史数据。短 TTL cache、warmup 或页面内存缓存只能作为重复请求抖动保护,性能证据必须证明数据库索引/聚合、分页和渐进式披露本身已把核心路径降到目标内,不能用长缓存遮蔽慢 SQL 或全量 JSON 物化。
|
||||
|
||||
当最近失败请求集中出现 frontend `webui_api_proxy` 502,路径为 `/api/code-queue-direct/...` 的 overview、trace 或 summary,且 `code-queue-backend` 容器仍在运行时,必须区分“上游进程不可达”和“上游 event loop 被热路径同步工作饿死”。排障顺序是同时查看 `/api/frontend-performance`、`/api/performance`、容器直连 `/health`/overview/trace-step curl、`docker stats`、容器 `RestartCount`/`OOMKilled` 和 Code Queue 日志;如果容器直连 `/health` 也超时,应优先检查实时 output/SSE 发布、archive 读取、transcript 构建、统计计算和远程 Provider 准备/SSH 子进程是否在 active output 追加或任务入队启动时阻塞 event loop,而不是先调整 frontend 渲染或代理超时。涉及 D601 等远程 Provider 时,还要检查 `runCodeQueueSsh`/开发容器准备是否仍存在同步子进程、无 timeout 的 SSH、无上限 stdout/stderr 或 stale TUN 重建等待;修复后必须在远程准备探针运行期间并发证明容器直连 `/health` 与 `/api/tasks/overview` 仍快速返回。
|
||||
|
||||
Code Queue task 明明产出最终回复却反复 `retry_wait` 时,应优先用任务详情里的 latest attempt 字段核查 `terminalStatus`、`transportClosedBeforeTerminal`、`appServerExitCode`、`finalResponseChars`、`judge.raw._safetyOverride` 和 attempt output。OpenCode 远程任务中,`opencode completed status=completed exit=0` 加当前 attempt 非空 assistant 输出应对应 `terminalStatus=completed`、`transportClosedBeforeTerminal=false`;如果因为缺少 `step_finish` 事件仍触发 `_safetyOverride=terminal_not_completed`,说明协议终态归一化有回归。相反,当前 attempt 没有最终 assistant response 时即使 tool/read/bash 证据完整,也必须 retry,不能用旧 `task.finalResponse` 或 reasoning/tool evidence 代替可见最终回复。
|
||||
|
||||
@@ -46,6 +46,8 @@ WSL 本身会在没有前台进程时被 Windows 回收;如果该节点要作
|
||||
|
||||
Docker daemon 重启后的恢复验收必须看两个层级。第一层是 Docker 自身:`docker inspect --format '{{.HostConfig.RestartPolicy.Name}} {{.HostConfig.PidMode}} {{.State.Status}}' unidesk-provider-gateway-<ID>` 必须返回 `always host running`;D601/D518 这类 Docker Desktop daemon 上还要记录 `docker info --format '{{.Name}} {{.LiveRestoreEnabled}}'`,当前 `LiveRestore=false` 意味着 daemon 重启会停止容器,恢复完全依赖 restart policy,因此不能把容器曾经是 running 当作已具备重启恢复能力。第二层是宿主/WSL 常驻:节点应有 systemd、Windows 计划任务、Docker Desktop 自启动或等价 keepalive 证明 Docker daemon 会随机器/WSL 启动;如果 `systemctl list-unit-files '*unidesk*'` 为空,必须在验收记录中明确该节点暂时只依赖 Docker Desktop daemon 自身,不能声称有 WSL 内 systemd watchdog。provider-gateway 新版本会在启动与 heartbeat 中自检当前容器 restart policy,发现不是 `always` 时通过 Docker socket 尝试 `docker update --restart always <self>` 并在 labels 中上报 `providerGatewayRestartPolicyOk`、`providerGatewayPidModeOk` 与 `providerGatewayRuntimeGuardOk`;这只是最后一道自愈,不能替代 Compose/systemd 的正确配置。
|
||||
|
||||
D601 的已验证常驻链路是 Windows 登录计划任务 `UniDesk-D601-Autostart` -> `C:\Users\liang\AppData\Local\UniDesk\d601-autostart.cmd` -> `wsl.exe -d Ubuntu -u ubuntu -- /bin/bash -lc "/home/ubuntu/.local/bin/unidesk-d601-autostart task"`。WSL 脚本负责启动 sshd、等待 Docker Desktop daemon、确保 `unidesk-provider-gateway-D601` 为 `restart=always` 且 running,并调用 `/home/ubuntu/.local/bin/unidesk-microservice-autorecover` 恢复 D601 用户服务。后续 WSL 节点可以复用这个模式,但必须把 Windows 用户、distro 名、provider ID、业务 autorecover 脚本和日志目录替换为节点自己的值;该模式只用于节点启动后的守护恢复,provider-gateway 版本升级仍必须走 `provider.upgrade mode=schedule`。
|
||||
|
||||
## WSL Network And Proxy Bootstrap
|
||||
|
||||
WSL 节点出网异常时,先把代理设置固化到目标 WSL 用户的 `~/.bashrc`,而不是只在当前 shell 临时 `export`。长期可复用的写法是在每次交互 shell 启动时从 `/etc/resolv.conf` 读取 Windows 宿主在 WSL NAT 中的 nameserver IP,再导出 `http_proxy`、`https_proxy`、`HTTP_PROXY`、`HTTPS_PROXY`、`all_proxy` 和 `ALL_PROXY` 指向 `http://<windows-host-ip>:7890` / `socks5://<windows-host-ip>:7890`,并保留 `no_proxy=localhost,127.0.0.1,::1,host.docker.internal`。不要把某次启动看到的宿主 IP 当成永远不变的常量;动态读取可以避免 WSL 网络重建后代理失效。
|
||||
|
||||
Reference in New Issue
Block a user