Files
pikasTech-unidesk/docs/reference/microservices.md
T
2026-05-17 18:33:25 +00:00

390 lines
115 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UniDesk User Services Reference
UniDesk 用户服务是挂载到 UniDesk 核心服务上的、面向用户使用的非核心业务服务;底层配置、API、CLI 和 E2E check 名称仍保留 `microservice` 兼容命名。UniDesk 核心服务(frontend、backend-core、database、provider-gateway、主 server 控制入口)不得依赖某个用户服务存在;缺少部分或全部用户服务时,核心仍必须能启动、运行和完成基础运维。
用户服务容器可运行在计算节点 Docker 或主 server Docker 中,主 server 只保存仓库引用、commit id、Dockerfile/docker-compose 引用、provider 映射和前端集成配置,不把业务仓库整体复制进 UniDesk。
## Boundary
- 用户服务后端端口默认只绑定计算节点本机地址,例如 `127.0.0.1:<port>`,不得直接暴露公网。
- 浏览器只访问 UniDesk frontendfrontend 通过同源 `/api/microservices/*` 代理到 backend-core。`deployment.mode=unidesk-direct` 的用户服务由 backend-core 通过目标 provider-gateway 的 `microservice.http` 能力访问计算节点本机后端;`deployment.mode=internal-sidecar` 的主 server 内置控制面服务由 backend-core 直接访问同一 Compose 网络内的显式服务名;`deployment.mode=k3sctl-managed` 的用户服务只允许经 `k3sctl-adapter` 微服务进入 k3s 标准服务路由,backend-core 不得直接向业务容器所在 provider 下发 `microservice.http`
- backend-core REST API、database 和计算节点用户服务后端都不得新增公网端口;公网入口仍只有 frontend 和 provider ingress。
- `microservice.http` 只允许 provider-gateway 访问 `http://127.0.0.1``http://localhost``http://host.docker.internal` 这类节点本地地址,或明确登记为同一私有 Docker network 内的服务名;主 server 内置用户服务可使用同一 Compose 网络内的显式服务名,例如 `todo-note:4211`。k3s 代管服务不得把业务容器地址登记成 provider-gateway 直连目标,`backend.proxyMode` 必须使用 `k3sctl-adapter-http``backend.nodeBaseUrl` 可使用 `k3s://<service>` 这类逻辑服务名。backend-core 还必须用 `allowedPathPrefixes``allowedMethods` 同时限制可代理路径和 HTTP 方法。
## Config Contract
`config.json``microservices` 是用户服务的唯一登记来源。每个条目必须包含:
- `id``name``providerId``description`,用于 CLI、backend-core 和 frontend 统一识别。
- `repository.url``repository.commitId`,用于记录业务代码的外部权威来源;UniDesk 不 vendoring 业务全量代码。
- `repository.dockerfile``repository.composeFile``repository.composeService``repository.containerName`,用于说明部署应复用业务仓库自身维护的 Dockerfile/docker-compose。
- `backend.nodeBaseUrl``backend.nodeBindHost``backend.nodePort``backend.proxyMode``backend.public=false``backend.frontendOnly=true``backend.allowedMethods``backend.allowedPathPrefixes``backend.healthPath`,用于定义计算节点端口到 UniDesk frontend-only 代理的映射。
- `deployment.mode`,用于明确部署责任边界;`unidesk-direct` 表示 UniDesk 直接登记和探测目标 provider 上的容器,`internal-sidecar` 表示主 server Compose 内的轻量控制面/基础设施服务,`k3sctl-managed` 表示 UniDesk 只登记逻辑服务并经 `deployment.adapterServiceId` 指向的 `k3sctl-adapter` 访问,代管条目还必须写明 `k3sServiceId``namespace``expectedNodeIds` 和当前 `activeNodeId`
- `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。
业务仓库由业务系统自己维护,包括源码、Dockerfile、docker-compose、配置模板和业务测试。UniDesk 只引用业务仓库 URL、commit id、Dockerfile/docker-compose 路径和运行容器名;不得把业务全量代码复制到 `src/components/microservices/` 形成双维护。`src/components/microservices/` 只能放通用示例或 UniDesk 自有示例,不作为业务仓库镜像。
## Main Server User Services
主 server 只承载对统一入口、状态迁移或控制面自动化有明确必要的用户服务。该类服务仍遵守不暴露公网端口、前端统一 React 控件化展示的规则;业务持久状态必须写入主 PostgreSQL;`.state/` 只能保存日志归档、缓存或可重建工件,不能作为任务、队列、未读、通知 outbox 等权威状态来源。
### Todo Note On Main Server
当前 Todo Note 作为 `id=todo-note` 的用户服务登记在 `config.json`
- 来源工作树:D518 的 `/mnt/d/work/todo_note`;主 server 工作树固定放在 `/root/todo_note`,用于 Docker build 和后端维护。
- Provider`main-server`,由本机 provider-gateway 通过 `microservice.http` 访问同一 Compose 网络内的 `http://todo-note:4211`
- 代码引用:`https://gitee.com/Lyon1998/todo_note` 与配置中的 `repository.commitId`;UniDesk 仓库只记录引用,不 vendoring Todo Note 全量业务代码。
- 部署引用:`/root/todo_note/Dockerfile` 构建纯后端镜像,Compose service 为 `todo-note`,容器名为 `todo-note-backend`
- 数据库:Todo Note 不再使用 JSON 文件作为运行时权威存储;必须把 D518 `data/registry.json``data/instances/*.todo.json``*.history.jsonl` 迁移到主 server PostgreSQL 的 `todo_note_instances``todo_note_history` 表。
- 代理路径:只允许 `/api/` 前缀;允许方法为 `GET``HEAD``POST``DELETE`,用于保持 Todo Note 原有清单创建/删除、任务增删改、提醒、展开/收起、移动、撤销/重做等功能。
- UniDesk 前端:`用户服务 / Todo Note` React 页面负责展示清单列表、树形任务、筛选、提醒、拖放/上移下移、撤销/重做、字号控制和显式原始 JSON 按钮。
Todo Note 在 UniDesk 语境中按纯后端服务管理:不得继续公开 Todo Note 自身 Vite/Web 前端,也不得把 `4211` 映射为公网端口。浏览器只能通过 UniDesk frontend 的 `/api/microservices/todo-note/...` 同源代理访问 Todo Note 后端。
Todo Note 首次迁移或源 JSON 修复时,在主 server 通过 Docker 内网执行 `/root/todo_note/scripts/migrate-json-to-pg.ts`,并显式指向主 PostgreSQL`docker run --rm --network unidesk_default -v /root/todo_note:/app -w /app -e DATABASE_URL='postgres://unidesk:unidesk_dev_password@database:5432/unidesk' oven/bun:1-alpine bun scripts/migrate-json-to-pg.ts`。迁移脚本必须输出 `importedInstances: 5``totalTodos: 100``completedTodos: 54` 这一类可审计摘要,不能只依赖前端页面观察。
Todo Note 数据迁移后必须验证:`microservice proxy todo-note /api/instances` 至少能看到 `CONSTAR``大论文``找工作``小论文``事务` 五个迁移清单,总任务数不低于源数据的 100 条;再通过代理创建临时清单、添加任务、切换完成、撤销并删除临时清单,证明写入路径走 PostgreSQL 且不会污染长期数据。
### OA Event Flow On Main Server
当前 OA Event Flow 作为 `id=oa-event-flow` 的用户服务登记在 `config.json`
- Provider`main-server`,由 backend-core 直接访问同一 Compose 网络内的 `http://oa-event-flow:4255`,公网不发布 `4255`
- 代码引用:`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`;服务源码位于 `src/components/microservices/oa-event-flow`,属于 UniDesk 自有主 server 用户服务。
- 部署引用:UniDesk 根仓库 `docker-compose.yml` 中的 `oa-event-flow` serviceDockerfile 为 `src/components/microservices/oa-event-flow/Dockerfile`,容器名为 `oa-event-flow-backend`
- 数据库:事件表、projection offset、Trace/STEP stats 和 Trace step 投影写入主 PostgreSQL;服务启动时自动创建/补齐 schema,不依赖仅首次生效的 database init SQL。
- API`GET /health``POST /api/events``GET /api/events``GET /api/events/stream``GET /api/stats/trace``GET /api/diagnostics`
- 代理路径:只允许 `/health``/logs``/api/` 前缀;允许方法为 `GET``HEAD``POST`
- UniDesk 前端:`用户服务 / OA Event Flow` React 页面负责展示服务健康、事件表、tag 过滤、live stream 状态、Trace/STEP stats 表、Code Queue/Pipeline 标签入口和显式原始 JSON 按钮。
OA Event Flow 在 UniDesk 语境中按共享控制面基础设施管理:不得暴露公网端口,不得把事件或统计权威状态写入 `.state/`Code Queue 与 Pipeline 都必须通过该服务发布事实事件、订阅 tag stream 和读取统计中心。共享事件流、统计中心和完成门禁见 `docs/reference/oa-event-flow.md`
### Code Queue Manager On Main Server
`code-queue-mgr` 是主 server Compose 内的轻量 Code Queue 控制面,登记为 `deployment.mode=internal-sidecar`Provider 为 `main-server`,后端地址为 Compose 网络内 `http://code-queue-mgr:4278`。它不直接出现在前端业务标签中,而是作为稳定 `code-queue` 用户服务代理路径的内部控制面目标。
- 职责:队列 CRUD、任务提交、批量提交、任务移动、queued prompt edit、已读状态、历史摘要、overview、stats、summary、prompt、output/transcript/trace 的轻量 PostgreSQL 读取。
- 非职责:不运行 Codex/OpenCode,不包含 Playwright/Chromium,不持有 Docker socket,不创建 dev-container,不执行 judge,不管理 active run steer/interrupt,不做任务调度或 runner。
- 资源边界:目标常驻内存不超过 100 MB,默认 PostgreSQL pool 为 `CODE_QUEUE_MGR_DATABASE_POOL_MAX=2``CODE_QUEUE_TRACE_DATABASE_POOL_MAX=1``/health` 必须暴露 `role=master-control-plane``schemaReady`、连接池上限和 `noRunnerDependencies=true`
- 路由:CLI/WebUI 仍只访问 `/api/microservices/code-queue/proxy/...`backend-core 在内部把控制/读取路径转到 `code-queue-mgr`,把 active run、judge、dev-container、执行面健康和 scheduler 相关路径转到 D601 执行面。
- 行为兼容:提交与 queued prompt edit 必须保留 Code Queue 环境提示注入、`--reference-task-id`/引用输入解析和引用任务上下文注入,避免 master 控制面路径与 D601 原写服务语义分叉。
### Project Manager On Main Server
当前 Project Manager 作为 `id=project-manager` 的用户服务登记在 `config.json`
- Provider`main-server`,由本机 provider-gateway/直接内网代理访问同一 Compose 网络内的 `http://project-manager:4233`
- 代码引用:`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`;服务源码位于 `src/components/microservices/project-manager`,属于 UniDesk 自有主 server 用户服务。
- 部署引用:UniDesk 根仓库 `docker-compose.yml` 中的 `project-manager` serviceDockerfile 为 `src/components/microservices/project-manager/Dockerfile`,容器名为 `project-manager-backend`
- 数据库:项目清单写入主 PostgreSQL 表 `project_manager_projects`;服务启动时自动创建/补齐 schema,不依赖仅首次生效的 database init SQL。
- 初始数据来源:D601 Windows 文件 `C:\Users\liang\xwechat_files\wxid_01rxm0yxjksk12_345f\msg\file\2026-05\合作项目列表_I_20260309.xlsx`,通过 UniDesk SSH 透传读取到主 server 后,用 `/api/import/excel` 导入。当前 Excel 表头为 `序号``合同号``项目名称``当前状况``待完成``付款情况``其它`
- API`GET /health``GET|POST /api/projects``GET|PUT|DELETE /api/projects/{id}``POST /api/import/excel``POST /api/import/projects``GET /api/projects/export.xlsx`
- 代理路径:只允许 `/health``/logs``/api/` 前缀;允许方法为 `GET``HEAD``POST``PUT``DELETE`
- UniDesk 前端:`用户服务 / Project Manager` React 页面负责展示主 server 仓库引用、私有后端映射、项目指标、项目表格、筛选搜索、编辑表单、Excel 导入和 Excel 导出;完整原始 JSON 只能通过显式 `查看原始JSON` 打开。
Project Manager 在 UniDesk 语境中按纯后端服务管理:不得将 `4233` 映射为公网端口。浏览器只能通过 UniDesk frontend 的 `/api/microservices/project-manager/health``/api/microservices/project-manager/proxy/...` 同源代理访问项目管理后端。
### Baidu Netdisk On Main Server
当前 Baidu Netdisk 作为 `id=baidu-netdisk` 的用户服务登记在 `config.json`
- Provider`main-server`,由 backend-core 直接访问同一 Compose 网络内的 `http://baidu-netdisk:4244`,公网不发布 `4244`
- 代码引用:`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`;服务源码位于 `src/components/microservices/baidu-netdisk`,属于 UniDesk 自有主 server 用户服务。
- 部署引用:UniDesk 根仓库 `docker-compose.yml` 中的 `baidu-netdisk` serviceDockerfile 为 `src/components/microservices/baidu-netdisk/Dockerfile`,容器名为 `baidu-netdisk-backend`
- 配置密钥:Compose 只透传 `UNIDESK_BAIDU_NETDISK_CLIENT_ID``UNIDESK_BAIDU_NETDISK_CLIENT_SECRET``UNIDESK_BAIDU_NETDISK_TOKEN_KEY` 与可选 `UNIDESK_BAIDU_NETDISK_APP_ROOT`;当前默认工作根目录为 `/`,如需收回到应用目录可显式设为 `/apps/<name>`;不得把百度 AppSecret、token key、access token 或 refresh token 写入仓库文件。
- 配置步骤:`UNIDESK_BAIDU_NETDISK_TOKEN_KEY` 可由本机生成;百度 `client_id``client_secret` 必须由账号拥有者在百度网盘开放平台创建应用后提供,操作清单见 `docs/issue/baidu-netdisk-env-setup.md`
- 数据库:OAuth 设备码会话、账号摘要、加密 token、传输任务和事件写入主 PostgreSQL 表 `baidu_netdisk_*`;服务启动时自动创建/补齐 schema,不依赖仅首次生效的 database init SQL。
- 文件边界:v1 只支持容器 staging 目录 `/data/staging` 与百度网盘配置工作根之间的后台上传/下载任务;staging 目录由主 server `.state/baidu-netdisk/staging` 挂载,`.state/` 只保存可重建文件缓存,不作为 token 或任务权威状态。当前授权账号已实测可对百度网盘根目录 `/` 执行列表、上传、获取 dlink、下载和删除临时探针,因此 `UNIDESK_BAIDU_NETDISK_APP_ROOT` 默认直接设为 `/`;仅当该值配置为 `/apps/...` 时,后端才会确保应用目录存在,目录已存在时必须返回/记录 `errno=-8` 并继续,禁止使用会重命名的策略重复创建 `_YYYYMMDD_...` 目录。
- API`GET /health``GET /api/auth/status``POST /api/auth/device/start``GET /api/auth/device/status``POST /api/auth/refresh``POST /api/auth/logout``GET /api/account``GET /api/files``GET /api/files/meta``POST /api/folders``POST /api/files/manage``POST /api/transfers/upload-from-path``POST /api/transfers/download-to-path``POST /api/self-test``GET /api/transfers``GET|POST /api/transfers/{id}/cancel|retry``GET /logs`
- 授权轮询:百度设备码轮询返回的 `authorization_pending``slow_down` 是正常中间态,后端必须把它们更新为 pending session`slow_down` 增加轮询间隔)而不是向前端抛 HTTP 错误;只有拒绝、过期或未知 OAuth 错误才进入 rejected/expired/failed。
- 自测:`POST /api/self-test` 会在 staging 生成小文本、上传到配置工作根、通过 `/api/files` 找到 `fs_id`、下载回 staging 并校验 MD5;该端点不得回显 token/dlink,适合 CLI、前端按钮和交付验收使用。
- 代理路径:只允许 `/health``/logs``/api/` 前缀;允许方法为 `GET``HEAD``POST``DELETE`
- UniDesk 前端:`用户服务 / Baidu Netdisk` React 页面负责展示设备码登录卡、账号容量、配置工作根文件表、staging 上传/下载任务、上传/下载自测按钮与结果、脱敏日志和显式原始 JSON 按钮。
Baidu Netdisk 在 UniDesk 语境中按纯后端服务管理:不得暴露百度 token、dlink 或 staging 文件字节流给浏览器;浏览器只能通过 UniDesk frontend 的 `/api/microservices/baidu-netdisk/health``/api/microservices/baidu-netdisk/proxy/...` 同源代理访问控制面 JSON。
### File Browser Host Files
当前 File Browser 作为两组用户服务登记在 `config.json`,共用上游 `https://github.com/filebrowser/filebrowser``filebrowser/filebrowser:v2.63.3` 镜像和 commit `ca5e249e3c0c94159c2136a0cd431a424eb18472`;主 server 不再运行 File Browser 容器,避免占用主 server CPU/内存和主机根目录遍历资源:
- `id=filebrowser`Provider 为 `D518`,服务在 D518 节点本机绑定 `4251`provider-gateway 容器内通过 `http://host.docker.internal:4251` 访问;容器名为 `unidesk-filebrowser-d518`,挂载 D518 WSL host `/``/srv`,因此可浏览 `/mnt/c` 等 Windows 盘符。D518 Docker Desktop 的 `host.docker.internal` 指向 Windows host IP,实际部署需使用 `0.0.0.0:4251->8080` 才能让 provider-gateway 容器访问。
- `id=filebrowser-d601`Provider 为 `D601`,服务在 D601 节点本机绑定 `127.0.0.1:4251`provider-gateway 容器内通过 `http://host.docker.internal:4251` 访问;容器名为 `unidesk-filebrowser-d601`,挂载 D601 WSL host `/``/srv`,因此可浏览 `/mnt/c` 等 Windows 盘符。
- 启动参数必须包含 `--baseURL /api/microservices/<id>/proxy``--noauth``--disableExec``--disableTypeDetectionByHeader``--disableImageResolutionCalc``--disableThumbnails`;容器必须使用 `user: "0:0"``--user 0:0`,否则 upstream 镜像默认非 root 用户会导致 `/database/filebrowser.db` 写入失败。禁用头部类型探测、图片尺寸计算和缩略图可避免 Windows 盘根目录中的 `hiberfil.sys``pagefile.sys` 等受保护文件导致整个目录浏览失败。
- 代理路径允许 `/`,允许方法为 `GET``HEAD``POST``PUT``PATCH``DELETE`File Browser 的 `/api/login``/api/resources` 和上传 API 需要透传 `X-Auth``Range``Tus-Resumable` 等请求头。
- provider-gateway 容器必须配置 `extra_hosts: ["host.docker.internal:host-gateway"]`,确保 D601/D518 Linux Docker 容器内能访问 WSL host 侧 `127.0.0.1:4251` 映射。
- UniDesk 前端:`用户服务 / File Browser` React 页面展示 D518 主目标和 D601 备用目标的健康状态、仓库引用、私有后端映射,并以 iframe 嵌入对应 File Browser WebUI;页面必须提供截图导出入口,并对上游 WebUI 注入紧凑布局样式,避免 material icon 字体异常时 `folder` 文本遮挡文件名;File Browser 自身端口不得直接暴露公网。
### k3s Control Plane Adapter On D601
`k3sctl-adapter` 是 UniDesk 直管的控制面适配微服务;它本身仍按 `deployment.mode=unidesk-direct` 登记在 D601,由 UniDesk 负责健康检查、重建和前端可见性,但它管理的业务服务必须走 k3s 标准服务路由,不得再被 backend-core 直接下发到业务容器所在 provider。
- Provider`D601`,由 D601 provider-gateway 仅维护和访问 `k3sctl-adapter` 的本机私有端口 `127.0.0.1:4266`provider-gateway 不再作为 `code-queue` 业务请求的直接代理。
- 代码引用:`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`;服务源码位于 `src/components/microservices/k3sctl-adapter`,属于 UniDesk 自有控制面组件。
- 部署引用:UniDesk 仓库中的 `src/components/microservices/k3sctl-adapter/docker-compose.d601.yml`Dockerfile 为 `src/components/microservices/k3sctl-adapter/Dockerfile`,容器名为 `k3sctl-adapter`;当前 D601 Docker Desktop 包装必须挂载宿主 kubeconfig 与 WSL 维护 SSH key,并由容器入口脚本建立到 WSL host 原生 k3s API 的 SSH local tunnel。
- 控制桥边界:`k3sctl-adapter` 是 UniDesk 到 k3s 的控制桥,不是被 k3s 管理的业务 workload;它必须保持 `deployment.mode=unidesk-direct`,或迁移为等价的 UniDesk/systemd 直管宿主服务,不得改成 `k3sctl-managed` Deployment。原因是 UniDesk 依赖 adapter 做 k3s 服务代理、部署验证和故障诊断;如果 adapter 自身依赖 k3s Deployment、Service、CNI 或 kube-proxy 才能存活,k3s 网络故障时会失去修复 k3s 的入口,形成依赖顺序倒置。
- 原生 API 连接:D601 原生 k3s 的 kubeconfig 固定来自宿主 `/etc/rancher/k3s/k3s.yaml`adapter 内部挂载为 `/var/lib/unidesk/k3s/kubeconfig`;当 kubeconfig server 是 `127.0.0.1:6443` 时,adapter 容器必须通过受控 SSH local tunnel 把容器内 `127.0.0.1:6443` 转发到 WSL host `127.0.0.1:6443`,并设置 `K3SCTL_KUBE_API_CONNECT_HOST=127.0.0.1`。不得依赖 Docker Desktop 的 `network_mode: host`,因为它进入的是 Docker Desktop VM 网络而不是 D601 WSL Ubuntu 网络;也不得依赖 `host.docker.internal:6443`、旧 `rancher/k3s` 容器 IP、NodePort 或手工 service endpoint。
- k3s 实现:D601 控制面、D518 或其他计算资源节点上的 k3s agent/worker 都必须原生安装在节点 host OS 或 WSL 发行版内,以 `/usr/local/bin/k3s` 和 systemd `k3s.service`/`k3s-agent.service` 运行;不得用 Docker、Compose、`rancher/k3s` 长驻容器、kind/k3d 或其他容器化方式承载 k3s 控制面或 kubelet。Docker 只允许用于 provider-gateway、业务容器镜像构建、运行用户 workload 或临时提取 k3s 二进制/镜像 artifact,不能成为 k3s runtime 边界。验收时必须证明 `systemctl is-active k3s` 或 agent 服务正常、`kubectl get nodes -o wide` 看到真实节点 OS/内核、k3s containerd socket 位于 `/run/k3s/containerd/containerd.sock`,且不存在 active `rancher/k3s` 控制面容器。
- k3s 路由对象:`k3sctl-managed` 可以落到 k3s、k8s 或等价标准 Kubernetes 控制面,但必须使用 Kubernetes 原生命名空间、Deployment、Service、readiness/liveness probe、Kubernetes API service proxy 等规范对象;不得把裸容器端口、NodePort、SSH curl、provider-gateway `microservice.http` 或 host 直连地址伪装成 k3s 服务路由。WSL 节点的 hostPath 和 local-path 语义必须解析到 WSL host 文件系统;例如 D601 Code Queue Pod 的 `/workspace` 必须映射 WSL `/home/ubuntu`,不能映射到容器化 k3s 内部的 `/home/ubuntu`
- k3s 系统组件:D601 原生 k3s server 必须禁用非必要的 `traefik``servicelb``metrics-server`,只保留业务必需的 API server、CoreDNS 与 local-path provisionerCoreDNS 和 local-path provisioner 固定运行在 D601 控制面节点,避免跨 WSL/NAT 节点维护隧道限制导致系统 DNS/readiness 抖动。
- manifest:代管服务声明放在 `src/components/microservices/k3sctl-adapter/k3s/*.k3s.json`adapter 启动时通过 `K3SCTL_MANIFEST_PATHS` 读取;manifest 是当前计划内 k3s 实例、active instance、single writer、expected nodes 和 health policy 的权威来源。`K3SCTL_SERVICES_JSON` 不得承载 static HTTP 服务、不得覆盖同名服务、不得作为隐藏 fallback;如需追加服务也必须提供完整 `ManagedKubernetesService` manifest。
- API`GET /health` 只表示 adapter 控制面自身可用,并把代管服务 serving 健康作为 `managedServicesHealthy` 字段展示;`GET /api/control-plane` 返回控制面、manifest、kubectl/k3s snapshot 和代管服务状态;`GET /api/services` 返回代管服务列表;`GET|HEAD /api/services/<id>/health` 返回该 k3s 服务的 active serving 健康;`/api/services/<id>/proxy/*` 是业务请求进入 active service 的唯一代理入口。
- 代理路径:adapter 访问 active 业务服务的唯一正式路径是 Kubernetes API service proxy`/api/v1/namespaces/<namespace>/services/<service>:<port>/proxy/...`。当前 Code Queue 运行拓扑只把 D601 写入 `expectedNodeIds`;任何远端 standby/worker 节点必须先完成原生 k3s-agent、稳定控制面网络、镜像分发和 hostPath 语义验证,才能加入 manifest。业务请求不得退化为 provider-gateway 直连 Code Queue HTTP 端口。standby/worker 节点如果受 kubelet/service-proxy 可达性限制,可以在 manifest 中显式使用 `healthMode=pod-ready` 作为拓扑健康探针;这只读取 Kubernetes Pod readiness,不是业务代理路径,也不能替代 active Service proxy。
- 拓扑健康:`expectedNodeIds` 负责展示计划内节点;当前 Code Queue 目标拓扑为 D601 原生 k3s 单节点多服务,`presentNodeIds` 应包含 `D601``missingNodeIds=[]``topologyComplete=true``status=healthy`。不能把未完成原生 k3s 接入或仍依赖 Docker 化 k3s 的节点列为 expected node;只有显式 `requireAllInstancesHealthy=true` 的服务才允许把缺失 standby/worker 节点提升为整体不健康。
- 前端:`用户服务 / k3s Control` React 页面必须只通过 `/api/microservices/k3sctl-adapter/proxy/api/control-plane` 通信,展示控制面状态、manifest、D601 scheduler/read/write 实例、active instance、Kubernetes API service proxy/no-fallback 路径和显式原始 JSON 按钮;页面不得直接访问 provider-gateway、D601/D518 业务容器端口、NodePort 或 raw k3s/kubectl API。
### D601 Dev Namespace Foundation
D601 开发环境底座只允许创建 `unidesk-dev` namespace 与 dev 专用对象,manifest 固定为 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-foundation.k8s.yaml`。该 manifest 包含 `postgres-dev` 独立 PostgreSQL StatefulSet/Service/PVC、dev-only secret/config 模板、dev DB 初始化 SQL 和迁移 Job、ResourceQuota/LimitRange,以及 `unidesk-dev-db-guard`。它不得修改生产 `unidesk` namespace、生产 PostgreSQL、生产 PVC、生产 Deployment/Service/Secret 或主 server Docker Compose。
`postgres-dev` 是 dev backend-core 与 dev Code Queue 状态的默认唯一数据库。dev 运行时必须使用 `postgres-dev.unidesk-dev.svc.cluster.local:5432/unidesk_dev` 和 dev Provider 身份 `D601-dev`;不得共享生产 `d601-tcp-egress-gateway.../unidesk`。当前 Phase 2 只提供 manifest 脚本和 `dev-env validate` 的静态护栏;backend-core、Code Queue 和 Code Queue Manager 的运行时启动护栏需在后续阶段单独评审后接入。
验收入口:先运行 `bun scripts/cli.ts dev-env validate` 做静态资源与 DB URL 护栏检查;具备 D601 kubeconfig 时运行 `bun scripts/cli.ts dev-env validate --kubectl-dry-run` 做 Kubernetes client dry-run。首次或镜像缓存不确定时,先运行 `bun scripts/cli.ts dev-env prewarm-images`,把 `postgres:16-alpine` 和 local-path helper 所需的 `rancher/mirrored-library-busybox:1.36.1` 导入 D601 原生 k3s containerd;否则 D601 的 Docker 代理/缓存正常也不能保证 k3s/containerd 能实时拉到外部镜像。若实际 apply,只能 apply 到 `unidesk-dev`,随后用 `kubectl -n unidesk-dev get pods,svc,pvc` 验证 dev DB ready,并对比 apply 前后的 `kubectl -n unidesk get deploy,sts,svc,secret,pvc -o name` 证明生产 workload 未变化。
D601 上必须显式使用原生 k3s kubeconfig`KUBECONFIG=/etc/rancher/k3s/k3s.yaml`。默认 `kubectl` context 可能是 Docker Desktop,不能作为 UniDesk k3s deploy 或 dry-run 验收目标。
### D601 Dev Core Services
`backend-core-dev``frontend-dev` 的第一版 manifest 固定为 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml`。该 manifest 只允许创建 `unidesk-dev` 内的 `backend-core-dev``frontend-dev` Deployment/Service;不得修改生产主 server Compose、生产 `unidesk` namespace 或生产 backend/frontend。
`backend-core-dev` 必须从 `unidesk-dev-runtime-config``unidesk-dev-runtime-secrets` 注入 dev-only 配置,使用 `postgres-dev.../unidesk_dev`、dev Provider token、dev log path 和 `UNIDESK_DEPLOY_REF=origin/deploy/dev``frontend-dev` 必须把 `CORE_INTERNAL_URL` 指向 `backend-core-dev.unidesk-dev.svc.cluster.local:8080`,页面在 dev identity 下显示 DEV 标记,`/health` 返回 dev namespace、database、service id、deploy ref 和 commit metadata。生产环境未设置 dev identity 时,backend-core 和 frontend health payload 保持生产兼容形状。
`unidesk-dev-core.k8s.yaml` 当前使用 placeholder image/commit;正式 rollout 需要后续 `deploy apply --env dev` executor 从 `origin/deploy/dev:deploy.json` 替换 commit 并构建镜像。当前验收只做静态校验和 Kubernetes client dry-run,不能把 placeholder manifest 当成已上线。
### Code Queue k3s-Managed
当前对外 `id=code-queue` 是稳定用户服务 ID,实际按 master 控制面与 D601 执行面拆分。队列管理、提交、历史摘要、已读状态和轻量 Trace 读取默认由主 server `code-queue-mgr` 直管 PostgreSQLD601 k3s Code Queue 作为执行面代管,负责 scheduler/runner、dev-container、active run steer/interrupt、judge、输出/attempt/通知写回,并接入统一 `oa-event-flow` 发布 Trace/STEP 事实事件与读取统计中心:
- Orchestrator:稳定 `code-queue` ID 的控制/读取路径由 backend-core 分流到 `deployment.mode=internal-sidecar``code-queue-mgr`D601 执行面仍登记为 `deployment.mode=k3sctl-managed``deployment.adapterServiceId=k3sctl-adapter``deployment.k3sServiceId=code-queue``backend.proxyMode=k3sctl-adapter-http``backend.nodeBaseUrl=k3s://code-queue`。对外登记的 `code-queue` ID 保持稳定,frontend/CLI 不需要知道内部拆分。
- Direct path ban`code-queue` 不得再登记 `http://code-queue:4222``http://host.docker.internal:4222`、NodePort 或 provider-gateway `microservice.http` 作为业务代理目标;frontend 也不得使用旧 `/api/code-queue-direct` 兼容别名作为 Code Queue 页面数据源。provider-gateway 只允许用于维护 D601/D518、部署 adapter、部署 k3s/k8s 节点或诊断节点本机容器。
- Claim/move consistencymaster `code-queue-mgr` 和 D601 scheduler 都必须以 PostgreSQL 状态为权威;move 只允许未 claim 的 `queued`/`retry_wait` 行,merge 在 source/target queue 存在 `running`/`judging` 或 claim marker 时整体返回 409。稳定 `code-queue` 代理可用 `/api/queue-claim-move/self-test` 验证 claimed task move 会被拒绝且数据库仍保持 running/source queue 状态。
- D601 Service boundaryD601 内部可以继续保留 `code-queue-read``code-queue-write``code-queue-scheduler` 三个 Kubernetes Service 作为执行面兼容和过渡对象,但普通提交、queue CRUD、history、readAt 和轻量 overview 不得依赖 `code-queue-write` 或 D601 egress 可用;`code-queue-write` 不 ready 时,主 server `code-queue-mgr` 仍应保证 CLI/WebUI 的提交、列表和历史读取可用。需要 active run、dev-container、judge 或执行面健康的路径才进入 D601 scheduler。
- 服务拆分语义:`code-queue-read` 只承载 GET/HEAD 查询、overview、任务详情、Trace/output/transcript、统计和只读健康,可多副本滚动更新;它必须设置 `CODE_QUEUE_SERVICE_ROLE=read``CODE_QUEUE_SCHEDULER_ENABLED=false`,且不得接受入队、queue 变更、已读、重试、移动、追加 prompt 或打断这类 mutation。`code-queue-write` 承载入队、queue 创建/合并/更新、已读、手动重试、移动等命令写入,初期保持单副本和 `CODE_QUEUE_SERVICE_ROLE=write`,只把命令和任务状态写入 PostgreSQL,不启动 agent 子进程。`code-queue-scheduler` 是唯一拥有 scheduler 和 active run 的执行服务,设置 `CODE_QUEUE_SERVICE_ROLE=scheduler``CODE_QUEUE_SCHEDULER_ENABLED=true`,负责从 PostgreSQL 热任务集轮询新写入任务、推进队列、启动 Codex/OpenCode、处理 running task 的 steer/interrupt、发送终态通知和暴露执行端 `/health`。普通 Service 负载均衡不得把 mutation 打到 read,也不得把 running task 控制打到 write。
- 实例语义:D601 是当前唯一 active 执行节点,`code-queue-scheduler` 以一个 scheduler Pod 承载长生命周期 Codex/OpenCode 子进程并轮询主 PostgreSQL 中由 `code-queue-mgr` 写入的 queued/retry_wait 任务。D518 不属于当前 Code Queue k3s 拓扑;在没有原生 k3s-agent 与稳定 Kubernetes 网络前,不得把 D518 写回 `expectedNodeIds` 或恢复 `code-queue-d518` standby。D601 scheduler 默认关闭 `CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED`;历史 OA Trace/STEP 回填必须通过显式 `/api/oa/backfill` 运维动作触发,不能在每次 Pod 重启时自动批量发布旧事件。
- 滚动更新边界:master `code-queue-mgr` 保证 D601 抖动或执行面滚动更新期间普通提交、queue 管理和历史读取仍可用;但当前 D601 scheduler Pod 内仍直接承载正在运行的 agent 子进程,scheduler Pod 被替换时 active task 仍会进入 restart-recovery/retry 语义,不能宣称 running task 零中断。真正的长期目标是继续把调度器和执行器拆开:scheduler 只负责 claim task 并创建 Kubernetes Job/Pod 或独立 workerrunner 把输出、状态、attempt、事件和通知写回 PostgreSQL/OA Event Flow/归档;只有这样 controller/scheduler 滚动更新才不会影响正在执行的任务。
- Restart recoveryD601 scheduler 启动时必须把没有本地 active run 的 `running`/`judging` 任务恢复为 `retry_wait` 并先写回 PostgreSQL,再开启新一轮 scheduler 轮询;同时必须清理 `queued`/`retry_wait`/terminal 任务残留的 `activeTurnId`,否则 PG 中残留的 running 或旧 turn id 会阻塞队列且不会被执行。health/overview 中的 `activeTaskIds` 只代表当前进程真实持有的 agent run;数据库里仍处于 `running`/`judging` 但没有本地 run 的任务只能作为 scheduler 侧 `orphanedActiveTaskIds` 暴露,不能计入 active run slot。主 server 直管 `code-queue-mgr` 只有 PostgreSQL 视角,不得把数据库中的 `running`/`judging` 误报为真实 active run;只能作为 `databaseActiveTaskIds`/`executionStateSource=postgres-control-plane` 这类控制面状态返回。
- Transient dependency recoveryD601 scheduler/read/write 通过 provider egress 和 TCP gateway 访问主 PostgreSQL、OA Event Flow 与模型 API,必须把 `CONNECTION_CLOSED``CONNECT_TIMEOUT`、stale PostgreSQL client、provider egress 瞬时失败和 MiniMax judge provider 初始化失败视为可恢复运行时抖动。实现上应轮换失效数据库 client、重试或降级 judge provider 初始化、释放 active run slot 并继续扫描后续 queued/retry_wait 任务;不得因为一次连接关闭、一次 judge provider transient error 或滚动更新窗口让 scheduler 长期停止推进。
- 部署引用:Code Queue 镜像仍复用 `src/components/microservices/code-queue/Dockerfile`Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml``config.json` 对外记录 k3s manifest `src/components/microservices/k3sctl-adapter/k3s/code-queue.k3s.json`;主 server 根目录 `docker-compose.yml` 不包含 `code-queue` service,旧 D601 direct Compose 文件只作为迁移/本地诊断参考,不是正式运行入口。
- 主服务依赖映射:Code Queue 仍以主 PostgreSQL 为权威数据库,但 D601 k3s Pod 不能依赖公网直连 `74.48.78.17:15432/4255`。Pod 内 `DATABASE_URL``OA_EVENT_FLOW_BASE_URL` 必须指向集群内 `d601-tcp-egress-gateway` Service,再由该 gateway 通过 D601 provider-gateway egress proxy 的 HTTP CONNECT 转发到主 PostgreSQL 和 OA Event Flow;新增 TCP 依赖时扩展 `TCP_EGRESS_ROUTES`,不得在业务容器里新增一次性公网直连或 ad hoc 隧道。D601 active 实例的 `CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL` 必须使用集群内 ClaudeQQ Service `http://claudeqq.unidesk.svc.cluster.local:3290`,并把 `claudeqq`/`claudeqq.unidesk.svc.cluster.local` 加入 `NO_PROXY`,避免任务完成通知被默认出网代理错误转发。旧 `http://host.docker.internal:3290` 只允许作为迁移期诊断,不得作为 Code Queue k3s Pod 的正式通知路径。这些端口映射只服务受控节点运行时,必须用防火墙或等价策略限制来源,不得成为浏览器或任意公网客户端入口。
- K8s 探针与启动维护:Kubernetes liveness/startup probe 必须使用轻量 `/live`,readiness 和用户服务健康使用 `/health``/health` 不得执行全量任务聚合、历史回填或长事务索引维护,历史任务总览应由 `/api/tasks/overview` 读取 PostgreSQL。启动时允许后台执行队列元数据 flush、通知 outbox 读取、任务表索引维护和 overview warmup,但这些维护不得阻塞 Bun server、readiness endpoint 或 frontend overview;通知表索引和大批量 OA backfill 不得作为默认启动副作用。
- MiniMax/OpenCode 并发:`minimax-m2.7` 通过 OpenCode JSON 事件端口运行;每个 Code Queue task 必须使用独立的 OpenCode XDG data/config/cache/state 目录,禁止多队列并发任务共享同一个 OpenCode SQLite/WAL 状态目录,否则并发 smoke 会触发 `PRAGMA journal_mode = WAL` 之类的数据库锁或初始化错误。用于验证 k3s/k8s 链路的 MiniMax smoke 以“至少 4 个任务、分布到 2 个 queue、至少 2 个终态成功”为链路验收线;剩余失败如果是 OpenCode 最终回复捕获、业务任务判定或模型限流,应作为 Code Queue 执行可靠性问题单独排查,不能反推 k3s 代理链路失败。
- 默认出网代理:D601 active Code Queue Pod 必须默认把 `HTTP_PROXY``HTTPS_PROXY``ALL_PROXY` 注入给 Codex/OpenCode、`git``curl``npm` 等任务子进程;当前唯一上游是 D601 provider-gateway egress HTTP CONNECT 代理,并通过 Kubernetes `Service d601-provider-egress-proxy` 暴露给 `unidesk` namespace 内的 Pod。该 Service 通过 selector 指向 D601 上的 hostNetwork 桥接 Pod,桥接 Pod 在集群端监听 service port `18789`、在宿主侧只连接 `127.0.0.1:18789` 的 provider-gateway egress endpoint;不得再用手工 EndpointSlice、provider-gateway Docker bridge IP 或固定 `172.*` 地址作为长期拓扑。Pod 内代理 URL 使用 `http://d601-provider-egress-proxy.unidesk.svc.cluster.local:18789`provider-gateway 宿主端口仍只允许绑定 `127.0.0.1`,不得开放公网;桥接 Pod 或 provider-gateway 重建后必须用 Code Queue `/health.egressProxy.connected=true` 验证。这里的 provider-gateway 只承担出网代理,不承担 Code Queue 业务 HTTP 代理;业务访问仍只能走 Kubernetes API service proxy。k3s/k8s 原生 egress gateway、service mesh 或 CNI egress policy 只作为后续网络层增强方向,当前交付态不引入第二套出网控制面。远程开发/执行容器不得只依赖这些环境变量,必须在容器网络层用 TUN 默认路由和 OUTPUT 防火墙强制外网流量只能经 master TUN 出口。
- 出网代理无 fallback 纪律:Code Queue 的运行时配置只允许一个默认出网路径,即 provider-gateway egress proxy;不得在代码中同时保留 Code Queue 自建 WebSocket proxy、临时 shell proxy、D601 本地直连公网、主 server direct HTTP proxy 等隐式分支。任何新增网络 fallback 都必须先进入本参考文档并配套 `/health` 可见状态,否则视为残留旧路径。
- 上线纪律:Code Queue 相关的前端或后端改进必须在同一任务内正式上线并验证公网 frontend 或 live API,不能只停留在源码、构建产物或“后续再上线”。修改 Code Queue 自身时不得等待当前 Code Queue task 结束、等待 queue idle 或等待 `0 running` 后才重启;D601 active 实例的正式后端部署入口是 `bun scripts/cli.ts codex deploy <commitId>`,它按已 push 的 remote commit 做 build-first 镜像替换、k3s image import、manifest apply、rollout 和健康验证,并用 k3s adapter、Code Queue live API 或公网 frontend 证明任务和队列仍可读可继续。
- 期望状态部署:新的通用入口是 `bun scripts/cli.ts deploy apply --service code-queue`,它从 `deploy.json` 读取 repo 与 commit,再按 `docs/reference/deploy.md` 的 target-side build 规范在 D601 构建、导入 k3s、rollout 并验证 live commit。`codex deploy <commitId>` 是兼容入口,后续实现应复用同一个 reconciler,不得维护第二套部署语义。
- 更名与灾备恢复:旧版 Codex 队列服务名只允许作为兼容诊断和一次性迁移来源;`code-queue-backend` 容器自身 `/health` 正常但 `microservice health code-queue` 返回 provider 直连错误时,优先判定为 backend-core 仍加载旧 `MICROSERVICES_JSON` 或 adapter manifest 未刷新,必须刷新 `.state/docker-compose.env`、重建/替换 `backend-core``k3sctl-adapter`,随后用 `microservice list` 验证 `code-queue``runtime.orchestrator=k3sctl``backend.proxyMode=k3sctl-adapter-http` 和无业务容器直连摘要。正式 k3s 部署成功后,旧 direct Docker `code-queue-backend` 必须停止并移除,不能与 `code-queue-scheduler` 同时运行;否则会形成双 scheduler、双健康来源和错误的恢复判断。
- Codex 认证:容器必须从 D601 的 `/home/ubuntu/.codex/config.toml` 同步 Codex provider 配置到 D601 `.state/code-queue/codex-home/config.toml`,并只读挂载 `/home/ubuntu/.codex/auth.json` 到容器 `/root/.codex/auth.json` 后同步到 `.state/code-queue/codex-home/auth.json`,让 `codex app-server` 使用与 host 一致的 provider 登录态;同时通过 D601 `.state/code-queue-d601.env` 或 k8s `code-queue-env` secret 透传 `OPENAI_API_KEY``CRS_OAI_KEY` 等 provider 所需变量。这些 provider 环境变量和 auth 文件不得写入仓库,必须由 D601 运行时文件或 k8s secret 注入,确保容器重建和重启后不会丢失认证。新增 provider 的 `env_key` 时必须增加同类运行时透传和 Compose/k8s 持久化,禁止把 Codex 或 MiniMax 密钥写入仓库文件。Code Queue 容器必须只读挂载 D601 WSL host 的 SSH 目录到 `/root/.ssh`(默认 `/home/ubuntu/.ssh`),让容器内 `git push``ssh -T git@github.com` 与 WSL 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++``iptables``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>`,并在 Code Queue 所在节点与开发容器之间建立 `ssh -w` TUN 点对点链路;服务所在节点负责对开发容器的 TUN 源地址做 NAT/MASQUERADE,开发容器默认路由和 DNS 改走该 TUN,从而让 `ping google.com`、DNS、HTTP(S) 等出网都经主 server 全局代理,而不是依赖 D601 本地网络。提交 Code Queue 任务时必须支持选择执行 Provider:`D601` 在 D601 原生 k3s 的 active Code Queue scheduler/runner Pod 中本机执行,默认工作目录为 `/workspace`,并且 `/workspace` 必须映射 D601 WSL host 的 `/home/ubuntu`;同一个 hostPath 还必须挂载到容器内 `/home/ubuntu`,让 WSL home 里的绝对 symlink(例如 `/workspace/cq-deploy -> /home/ubuntu/unidesk-code-queue-deploy`)在任务中可解析,不能只看到 symlink 名而无法进入目标目录。`/root/unidesk``/app` 必须单独映射 `/home/ubuntu/cq-deploy` 作为服务部署仓库;其他 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 进程和旧 OUTPUT 跳转,缺失旧设备不能导致失败,冷启动运行时准备要有有界但足够的 timeout。TUN 建立后必须创建 `UD-CQ-EGRESS-<provider>` OUTPUT 链,规则只允许 loopback、既有连接、`tun<id>` 出口以及到 master server 的 SSH tunnel 控制连接,随后 reject 其他 IPv4/IPv6 出站包;这条网络层封口是开发/执行容器的权威外网边界,不能用 `HTTP_PROXY`/`NO_PROXY` 环境变量替代,容器镜像也必须使用已解析出的唯一 `unidesk-code-queue:<provider>` 或显式 `image`,缺失时直接失败,禁止 provider-gateway image、`latest` 或其他隐式镜像 fallback。验收必须保留三类日志:容器建隧道后 `ping google.com` 成功、强制指定原 Docker 网卡直连外网被 `sealed_direct_ping=blocked_expected` 拦截、服务所在节点上对应 `UNIDESK-CODEX-DEV-<providerId>` NAT 链或 `tun<id>` 计数在 ping 前后增长;涉及 WSL 工作区任务时还必须在开发容器内验证目标 `/mnt/...` 路径可读。`GET /api/dev-containers/<providerId>/status` 必须展示默认路由、`route_8_8_8_8``egressFirewallChain` 和 OUTPUT 链跳转。开发容器代理密钥只生成到 `.state/code-queue/dev-proxy/` 与目标节点用户目录,不得提交到仓库。
- 远程维护桥调用:Code Queue 已迁移到 D601 后,Code Queue 后端 Pod 内没有主 server 的 `unidesk-backend-core` 容器,不能再把 `bun scripts/cli.ts ssh ...` 实现为本地 `docker exec unidesk-backend-core`。Code Queue 后端发起的 provider 维护命令必须通过主 server frontend `/api/dispatch` 进入 backend-core,再由目标 provider-gateway 执行 `host.ssh`;需要传递脚本时必须使用 base64 临时文件,超过 Host SSH 单命令长度上限时分块上传到目标 `/tmp` 后再执行,避免恢复到本地 Docker broker、交互 stdin 或手工 shell fallback。
- 远程 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 或 frontend/core 用户服务代理等控制面请求等待远程 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>``GET|POST /api/tasks/{id}/judge?attempt=N` 与 CLI `bun scripts/cli.ts codex judge <taskId> --attempt N` 用于单步复现指定 attempt 的 judge,必须复用真实队列 worker 的上下文构建、prompt 压缩、MiniMax 调用、JSON 去噪/repair 和 fallback 路径;`dryRun=1`/`--dry-run` 只输出 prompt/payload 和重建诊断,不调用 MiniMax。`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、数据库 claim、judge 异常、judge 超时或 judge 判定 `fail` 让后续 queued 任务停止;`fail` 只把当前任务标为 failed,随后必须继续扫描并推进下一个 queued/retry_wait 任务。数据库 claim 必须有硬超时且失败时释放 active run slotjudge 必须有独立 watchdog,超时后走 fallback judge 并继续推进。当存在 queued/retry_wait 且 worker 空闲时,watchdog 必须自动重新调度。
- 稳定性与重启恢复:Code Queue 的第一目标是长期稳定可用;部署修复或运维排障时不得因为担心容器重启会打断任务而拒绝重启、重建或替换 active Pod。容器重启、服务进程重启和镜像替换后,队列、`promptHistory`、running/judging/retry_wait 任务和 active session 元数据必须从 PostgreSQL 恢复,并在已有 `codexThreadId` 可用时用 `thread/resume` 和 continuation prompt 无缝继续当前任务;如果原 app-server turn 已丢失,也必须把当前任务恢复到可 retry/continue 的状态,不能错误推进下一个任务或永久卡住。D601 侧重建必须走 `bun scripts/cli.ts codex deploy <commitId>`;禁止先手工 `docker rm` 或只手工 `docker compose up` 再依赖后续命令补救,因为中断窗口会让 Pod/容器消失并触发 frontend/core 用户服务代理失败。重启后出现 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 验证。
- 内存优化过程与防回归:Code Queue 已迁移到 D601,但内存治理仍必须按“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 队列反复震荡。
- 列表/详情延迟优化原则:Code Queue 控制面交互的长期目标是常规历史规模下首屏、`GET /api/tasks/overview``POST /api/tasks/<id>/read` 和分页加载均在 1s 内完成;性能面板出现十几秒级 `core_proxy` 或 Code Queue 用户服务代理慢操作时,必须优先按后端查询形态和前后端通信策略定位,不能把问题归因于 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 用户服务代理 502、`/api/tasks/overview`/trace 接口成批超时,或容器内 `/health` 在 active output 持续追加时也卡住,优先按 Bun event-loop starvation/backpressure 排查,而不是先改 React 渲染;修复必须证明热路径不再随 output/archive 历史线性增长。
- Trace STEP 权威来源:`GET /api/tasks/<id>/trace-steps``GET /api/tasks/<id>/trace-step` 必须直接从 `oa-event-flow` 读取 `trace-step-created` 事实事件,并在响应中暴露 `source=oa-event-flow`;不得在 OA 事件缺失、读取失败或本地 transcript 数量更多/更少时静默回退到 `task-transcript`、Codex session JSONL、output archive 或内存热状态。OA 事件不可用应显式失败或返回空事实集,避免 STEP 计数和执行过程摘要重新形成双路径分叉。
- 完成判定: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 输入必须做有界压缩,保留终态、最终回复、关键错误/命令/部署证据和摘要计数,避免长 transcript 让 MiniMax 请求超时;默认 `UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS=90000`。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 原因、task JSON、attempt summary 和 TraceView 中保留 MiniMax 失败阶段、是否超时、耗时、prompt/payload 大小、HTTP 状态、错误名和响应预览。
- 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 judge <taskId> --attempt N``/api/tasks/{id}/judge?attempt=N`,响应要包含 attempt 窗口、promptChars/payloadBytes、stored judge 对比、MiniMax 失败阶段和是否因历史 per-attempt events 缺失而降级为 retained events 重建。
- 模型选择:默认 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 结果。
- 状态与日志:`D601` 默认工作目录为容器内 `/workspace`,该路径映射 D601 WSL host 的 `/home/ubuntu`;容器内 `/home/ubuntu` 也必须映射同一个 hostPath,保证从 `/workspace` 看到的绝对 symlink 可以继续解析到真实文件。服务自身仓库路径 `/root/unidesk``/app` 单独映射 D601 WSL host 的 `/home/ubuntu/cq-deploy`。其他 Provider 的任务默认工作目录为 `/home/ubuntu`,任务 JSON、列表、Trace 摘要和 CLI 查询都必须显示 `providerId``executionMode` 与最终 `cwd``executionMode=default` 在 D601 本机 Code Queue 容器中运行 Codex/OpenCode`executionMode=windows-native` 只允许非本机 WSL Provider、Codex 模型和 `/mnt/<drive>` 工作目录,Code Queue 仍会启动远程执行容器,但容器只运行 stdio relay,经 WSL bridge 调用 Windows 宿主原生 `codex app-server --listen stdio://`。Code Queue 的任务、queue、`readAt`/未读状态、attempt、judge、`promptHistory`、active session 元数据、控制状态和 ClaudeQQ 通知 outbox 一律以主 PostgreSQL 为权威,分别写入 `unidesk_code_queue_tasks``unidesk_code_queue_queues``unidesk_code_queue_notifications``DATABASE_URL` 是必需配置,服务不得在 PostgreSQL 缺失或不可用时进入文件存储模式。`.state/code-queue/state.json` 不再作为任务或 queue 状态存储,不得重新引入本地 JSON fallback;服务启动必须以 PostgreSQL 为唯一来源恢复队列,并把 running/judging 任务恢复为 retry_wait。D601 资源比主 server 宽裕,但 Code Queue 仍必须把“内存是稀缺资源”作为核心设计约束:历史任务列表、详情、统计和只读 Trace 查询优先从 PostgreSQL 直读,进程内只保留当前 running/judging、queued、retry_wait 等调度必需热任务;需要短期热缓存时必须有严格上限、可裁剪、可从 PostgreSQL 和 append-only 输出归档重建。WebUI 不得用 browser `localStorage``sessionStorage` 或 IndexedDB 持久化 task/queue/readAt/unread 等业务状态;浏览器只能保留临时 UI 内存缓存,刷新后必须重新从后端读取 PostgreSQL 权威数据。Codex CLI-like output/Trace 的完整记录可以使用 append-only 文件作为日志型归档,但任务状态、未读状态和列表摘要不得依赖这些文件作为权威来源;`/api/tasks/<id>/transcript``/api/tasks/<id>/output` 必须能分页重建完整历史,不得因为热状态裁剪而丢失早期 trace。WebUI 必须支持多 queue 查看、显式创建 queue、提交时下拉选择 queue、提交时下拉选择执行 Provider 和执行模式,并支持把已创建且非 active 的任务移动到其他 queuequeue 内串行,queue 间默认并行且不互相排队;`CODE_QUEUE_MAX_ACTIVE_QUEUES` 仅作为显式配置的全局 active slot 上限,`0` 表示不按 queue 数量限流,内存不足时由 cgroup memory pressure 阻止新 run 并在任务响应中暴露 `QUEUED(MEM LIMIT)`。Code Queue 镜像必须内置 Playwright Chromium 浏览器与系统依赖,并使用 `bun --smol` 运行后端,保证队列任务能直接执行公网 frontend Playwright 回归且主进程内存可控。日志写入 D601 `.state/code-queue/logs` 挂载的 UniDesk JSONL 日志;Codex app-server 上游产生的 `logs_*.sqlite` 只能作为短暂缓冲,必须由 Code Queue 周期性导出为 JSONL,避免重新形成大 SQLite 文件;`/logs` 端点返回最近结构化日志。`/health``queue.storage.primary` 必须恒为 `postgres`,并通过 `queue.storage.postgresReady``queue.devReady``/api/dev-ready` 暴露 PostgreSQL 可用性、develop-ready 自检、必需工具、Docker socket、`docker compose`、默认工作目录、Codex config 状态和 `/root/.ssh` 共享 SSH key 状态;D601 本机默认执行必须能通过 `/root/.ssh` 复用 WSL host GitHub SSH key 执行 Git over SSH,跨 Provider SSH 或 `windows-native` 任务同样需要 `queue.devReady.ssh.ready=true`。Codex CLI-like 输出可能很大,服务必须节流状态持久化,禁止对每个 output delta 同步重写完整 state 导致 `/health` 和控制 API 卡死;容器 healthcheck 必须使用带超时的 HTTP 探针,不能留下堆积的无超时探针进程.
- ClaudeQQ 通知:Code Queue 在 D601 k3s 上通过 `CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL=http://claudeqq.unidesk.svc.cluster.local:3290` 调用 k3s 代管 ClaudeQQ 后端 `POST /api/push/text`,并在旧 ClaudeQQ 只暴露 `/api/send/text` 时按同一目标自动兼容该接口;在每个任务进入 `succeeded``failed``canceled` 终态后向配置目标发送最终 response,并附带 task id、queue、状态、模型、attempt、当前 running/queued/retry_wait 数和任务总耗时;当所有 queue 进入 `0 running / 0 queued` 空闲态时,必须单独发送一次空闲提醒。通知由 `CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED` 控制,目标由 `CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE=private|group``CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID``CODE_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID` 配置,默认私聊 `645275593`;代理基址、最终 response 最大字符数、单次超时和发送尝试次数分别由 `CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL``CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS``CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS``CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS` 配置。任务终态和队列空闲通知必须先写入 PostgreSQL outbox 表 `unidesk_code_queue_notifications` 再异步发送;不得使用 `.state/code-queue/claudeqq-notifications.json``CODE_QUEUE_NOTIFY_CLAUDEQQ_OUTBOX_PATH` 或任何本地 JSON 作为通知权威存储。发送失败、NapCat 离线、代理 502 或容器重启时不能丢通知,必须按 `CODE_QUEUE_NOTIFY_CLAUDEQQ_RETRY_INTERVAL_MS` 指数退避重试并跨进程/容器重启保留。`/health``queue.notifications.claudeqq` 必须暴露非敏感配置、目标配置状态和 PostgreSQL outbox 统计;`GET /api/notifications/claudeqq` 返回 outbox 明细,`POST /api/notifications/claudeqq/drain` 手动触发发送,`POST /api/notifications/claudeqq/backfill` 可按 `since` 补入某次故障窗口内已终态任务,确保 QQ/NapCat 超时或离线不会让任务完成通知永久丢失。
### ClaudeQQ k3s-Managed
当前 ClaudeQQ 作为 `id=claudeqq``k3sctl-managed` 用户服务登记在 `config.json`,业务实例由 D601 k3s 控制面代管:
- Orchestrator`deployment.mode=k3sctl-managed``deployment.adapterServiceId=k3sctl-adapter``deployment.k3sServiceId=claudeqq``backend.proxyMode=k3sctl-adapter-http``backend.nodeBaseUrl=k3s://claudeqq`backend-core 对 ClaudeQQ 的正式链路只能是 `frontend -> backend-core -> k3sctl-adapter -> Kubernetes API service proxy -> Kubernetes Service claudeqq:3290`
- 部署引用:ClaudeQQ 业务源码来自 `https://gitee.com/lyon1998/agent_skills``claudeqq` 子目录;镜像构建 Dockerfile 和 `0.0.0.0:3290` API 适配器由 UniDesk 仓库 `src/components/microservices/claudeqq/` 作为 k3s 部署资产注入,不能依赖 D601 未提交工作树文件。Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/claudeqq.k8s.yaml``config.json` 对外记录 k3s manifest `src/components/microservices/k3sctl-adapter/k3s/claudeqq.k3s.json`。旧 `docker-compose.unidesk.yml` 只作为迁移/本地诊断参考,不是正式运行入口。
- 运行对象:ClaudeQQ Pod 在 D601 上以同 Pod 双容器运行 `claudeqq``napcat`,仅通过 ClusterIP Service 暴露 `claudeqq:3290` 给 UniDesk 代理和 Code Queue 通知链路;NapCat 的 `3000/3001/6099` 只在 Pod 内可达,不得作为 NodePort、hostPort 或 provider-gateway 业务直连目标。
- 持久化路径:NapCat 登录态保存在 D601 hostPath `/home/ubuntu/.agents/skills/claudeqq/napcat/qq`,NapCat 配置和二维码缓存分别保存在 `napcat/config``napcat/cache`;ClaudeQQ 后端挂载同一业务目录中的 `config.json``bot_workspace``logs``.state` 和只读 `napcat`。这些路径不得改成匿名 volume,避免重建 Pod 后 QQ 登录态和事件/订阅状态丢失。
- 代理/API:只允许 `/health``/logs``/api/` 前缀;允许方法为 `GET``HEAD``POST``DELETE``POST /api/push/text` 接受 `userId``groupId``message`,由 ClaudeQQ 通过同 Pod NapCat HTTP API 发送 QQ 消息;NapCat 不可用时必须快速返回 `status=napcat_offline` 或可解释错误。
- UniDesk 前端:`用户服务 / ClaudeQQ` React 页面负责展示 D601、仓库引用、私有 k3s 后端映射、NapCat 登录二维码、NapCat HTTP/WS 状态、事件缓存、订阅表、订阅创建表单、消息推送表单、主用户私聊账号 `645275593`、最近 QQ 事件和已发送记录;完整原始 JSON 只能通过显式 `查看原始JSON` 打开。浏览器只能通过 UniDesk frontend 同源代理访问 ClaudeQQ,不得直接访问 D601 `3290/3000/3001/6099`,也不得 iframe ClaudeQQ 旧 WebUI。
### Decision Center k3s-Managed
当前 Decision Center 作为 `id=decision-center``k3sctl-managed` 用户服务登记在 `config.json`,用于沉淀 Codex/人工会议后的会议记录、决议、目标、问题分级、停放事项和证据。它只负责权威记录和展示,不承载通用聊天、LLM 会话窗口或自动参谋对话。
- Orchestrator`deployment.mode=k3sctl-managed``deployment.adapterServiceId=k3sctl-adapter``deployment.k3sServiceId=decision-center``backend.proxyMode=k3sctl-adapter-http``backend.nodeBaseUrl=k3s://decision-center`;正式链路只能是 `frontend/CLI -> backend-core -> k3sctl-adapter -> Kubernetes API service proxy -> Kubernetes Service decision-center:4277`
- 部署引用:后端源码位于 UniDesk 仓库 `src/components/microservices/decision-center`Dockerfile 为 `src/components/microservices/decision-center/Dockerfile`k3s manifest 为 `src/components/microservices/k3sctl-adapter/k3s/decision-center.k3s.json`Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/decision-center.k8s.yaml`,镜像名固定为 `unidesk-decision-center:d601`。主 server `docker-compose.yml` 不得加入该服务,也不得公开 `4277`
- 状态权威:Decision Center 必须写入主 PostgreSQL,当前表为 `decision_center_records`;不得使用浏览器 `localStorage`、IndexedDB、容器 writable layer 或本地 JSON 文件作为会议、决议、目标或问题状态权威。D601 Pod 通过集群内 `d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432` 访问主 PostgreSQL。
- 数据模型:第一版记录类型为 `meeting|decision|goal|blocker|debt|experiment`,等级为 `G0|G1|G2|G3|P0|P1|P2|P3|none`,状态为 `active|blocked|parked|done`,字段包含 `title`、Markdown `summary/body``linkedGoalId``tags``evidenceLinks``sourceSession``taskId``commitId``createdAt``updatedAt`
- API:只允许 `/health``/live``/logs``/api/` 前缀;允许 `GET``HEAD``POST``PUT``DELETE`。业务 API 包含 `GET /api/records``POST /api/records``GET|PUT|DELETE /api/records/:id``POST /api/meetings/import`,错误必须返回结构化 JSON,便于 CLI 与 frontend 诊断。
- CLI`bun scripts/cli.ts decision upload <markdown-file>``decision list``decision show <id>``decision health` 只能通过 backend-core 用户服务代理访问 Decision Center,不得直连 D601 Service、NodePort 或 provider-gateway `microservice.http`
- UniDesk 前端:`用户服务 / Decision Center` React 页面只展示高密度记录、筛选、当前 G0/G1 目标、P0/P1 blocker、停放事项和最近会议/决议;默认不得展示裸 JSON,完整原始数据只能通过 `查看原始JSON` 打开。
### MDTODO k3s-Managed
当前 MDTODO 作为 `id=mdtodo``k3sctl-managed` 用户服务登记在 `config.json`,用于把 D601 Windows 工作区 `F:\Work\vscode-mdtodo` 从 VS Code 扩展形态拆成 UniDesk 可代理的后端服务:
- Orchestrator`deployment.mode=k3sctl-managed``deployment.adapterServiceId=k3sctl-adapter``deployment.k3sServiceId=mdtodo``backend.proxyMode=k3sctl-adapter-http``backend.nodeBaseUrl=k3s://mdtodo`;正式链路只能是 `frontend -> backend-core -> k3sctl-adapter -> Kubernetes API service proxy -> Kubernetes Service mdtodo:4267`
- 代码与部署引用:后端源码位于 UniDesk 仓库 `src/components/microservices/mdtodo`Dockerfile 为 `src/components/microservices/mdtodo/Dockerfile`k3s manifest 为 `src/components/microservices/k3sctl-adapter/k3s/mdtodo.k3s.json`Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/mdtodo.k8s.yaml`,镜像名固定为 `unidesk-mdtodo:d601`
- 持久化边界:D601 的 `F:\Work\vscode-mdtodo` 先同步到 k3s 可见的 WSL hostPath `/home/ubuntu/cq-deploy/.state/mdtodo-workspace`Pod 将该目录挂载为 `/workspace`,后端直接读写 Markdown TODO 文件;`.state/mdtodo/logs` 只保存 JSONL 日志,不作为任务权威状态。该服务不得把原 VS Code webview 前端或 VSIX 构建产物作为浏览器入口。
- API`GET /health``GET /live``GET /logs``GET /api/files``GET /api/tasks?file=...``GET|PATCH|DELETE /api/tasks/{id}``POST /api/tasks``GET|PUT /api/content``POST /api/execute-command``/health` 必须证明 hostPath 可读并至少能扫描到 TODO Markdown 文件。
- 代理路径:只允许 `/health``/live``/logs``/api/` 前缀;允许方法为 `GET``HEAD``POST``PUT``PATCH``DELETE`。业务请求不得退化为 provider-gateway 直连、NodePort 或 D601 本机端口。
- UniDesk 前端:`用户服务 / MDTODO` React 页面负责展示文件列表、任务树、任务状态、标题/正文编辑、新增/删除任务、执行命令生成和显式原始 JSON 按钮;默认页面不得裸铺完整 Markdown 或 JSON。
## D601 User Services
当前 `D601` 同时承载以下 UniDesk 用户服务:
- `findjob`FindJob 纯后端服务,UniDesk frontend 渲染岗位指标、岗位预览和草稿报告。
- `pipeline`Pipeline v2 控制与观测服务,UniDesk frontend 渲染组件矩阵、React Flow 控制图、epoch 甘特图、运行材料索引和 node 精细控制面板。
- `met-nonlinear`MET Nonlinear 训练编排服务,UniDesk frontend 渲染 GPU/镜像、训练队列、Project config 预览、训练进度、ETA 和历史记录。
- `claudeqq`ClaudeQQ 纯后端 QQ 消息网关,UniDesk frontend 渲染 NapCat 连接、事件订阅、消息推送、最近 QQ 事件和发送记录。
- `decision-center`Decision Center 决策权威记录服务,D601 k3s 代管,状态写入主 PostgreSQLUniDesk frontend 渲染记录筛选、目标、blocker、停放事项和会议/决议。
### D601 Docker/k3s 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 Desktop daemon 和原生 k3s 就绪,把 `unidesk-provider-gateway-D601` 修正为 `restart always` 且 running,然后调用 `/home/ubuntu/.local/bin/unidesk-microservice-autorecover boot`;进入常驻 watchdog 后每 300 秒重复检查 provider-gateway、Docker 直管服务和 k3s 代管服务。
- 用户服务 autorecover`/home/ubuntu/.local/bin/unidesk-microservice-autorecover` 只在 Docker 和 k3s ready 后运行;Docker 直管服务仍用容器 `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 必须通过 k3s Deployment/Service 恢复和验证,不再用 `docker compose` 作为正式恢复入口。
- 验收命令:Docker/k3s 恢复后必须同时验证 `docker inspect --format '{{.HostConfig.RestartPolicy.Name}} {{.State.Status}}' met-nonlinear-ts``kubectl -n unidesk get deploy/pod/svc/endpoints claudeqq -o wide``bun scripts/cli.ts microservice health met-nonlinear``bun scripts/cli.ts microservice health claudeqq` 和公网 frontend 页面;ClaudeQQ 还必须经 UniDesk 代理验证 `/api/napcat/login``state=logged_in`、HTTP connected 和 WebSocket connected。
### FindJob On D601
当前 FindJob 作为 `id=findjob` 的用户服务登记在 `config.json`
- Provider`D601`
- 开发工作树:`/home/ubuntu/findjob`,开发和调试必须通过 UniDesk SSH 透传进入 D601。
- 代码引用:`https://gitee.com/Lyon1998/findjob` 与配置中的 `repository.commitId`
- 部署引用:业务仓库自身 `Dockerfile``docker-compose.yml``composeService=server``containerName=findjob-server`
- 节点后端:D601 上 `127.0.0.1:3254`provider-gateway 容器内通过 `http://host.docker.internal:3254` 访问。
- 代理路径:只允许 `/api/` 前缀;`/` 上的业务旧前端即使仍存在,也不作为 UniDesk 用户服务入口使用。
- UniDesk 前端:`用户服务 / FindJob` React 页面负责展示指标、岗位预览、草稿报告和原始 JSON 显式查看按钮。
FindJob 在 UniDesk 语境中按纯后端服务管理:默认页面不得 iframe 或跳转到 findjob 自身前端,也不得直接暴露 D601 的 `3254` 到公网。UniDesk frontend 只能通过 `/api/microservices/findjob/health``/api/microservices/findjob/proxy/api/...` 访问 FindJob 后端。
### Pipeline On D601
当前 Pipeline v2 作为 `id=pipeline` 的用户服务登记在 `config.json`
- Provider`D601`
- 开发工作树:`/home/ubuntu/pipeline`,开发和调试必须通过 UniDesk SSH 透传进入 D601。
- 代码引用:`https://github.com/pikasTech/pipeline` 与配置中的 `repository.commitId`
- 部署引用:业务仓库自身 `Dockerfile``docker-compose.yml``composeService=pipeline-control``containerName=pipeline-v2-control`
- 节点后端:D601 上 `127.0.0.1:18082`provider-gateway 容器内通过 `http://host.docker.internal:18082` 访问。
- 代理路径:只允许 `/health``/api/` 前缀;允许方法为 `GET``HEAD``POST`,其中 `POST` 仅用于 `/api/node-control/...` 这类 node 控制动作;Pipeline 自身 WebUI 前端已废弃,UniDesk 只访问 Pipeline control backend。
- UniDesk 前端:`用户服务 / Pipeline` React 页面负责展示 health、组件数量、React Flow pipeline 控制图框图、epoch 列表、epoch 甘特图、OA/procedure 结构化摘要、运行材料索引、点击 node 后的执行过程抓取、append prompt、guide 和 redo/restart 控件,以及显式原始 JSON 按钮。
Pipeline 在 UniDesk 语境中按控制与观测后端服务管理:默认页面不得 iframe 或跳转到 Pipeline 自身 WebUI,也不得直接暴露 D601 的 `18082` 到公网。UniDesk frontend 只能通过 `/api/microservices/pipeline/health``/api/microservices/pipeline/proxy/api/snapshot?...``/api/microservices/pipeline/proxy/api/node-control/...` 和供主 server `oa-event-flow` bridge 使用的 `/api/microservices/pipeline/proxy/api/oa-event-flow/diagnostics` 访问 Pipeline control backend;主 server `oa-event-flow` 的迁移 bridge 当前只允许单一路径读取 `/api/snapshot``/api/oa-event-flow/diagnostics`,发布 `pipeline-run-snapshot` 到统一事件表,不得再实现 ledger/snapshot 双路径 fallback。超大 snapshot 必须使用 `__unideskArrayLimit=registry.components:<limit>,runs:<limit>` 做展示级裁剪。node 控制入口必须走 Pipeline 后端 OA control API,前端不得直接写 `.state`、runner prompt 文件或命令队列;scorer 结果必须在 UniDesk Pipeline UI 中以结构化 score 卡片展示。Pipeline 控制与观测的最终态必须 100% 由统一 `oa-event-flow` 与 Pipeline OA 状态机驱动,不得保留点对点控制、旧审核事件或旧 batch 推进逻辑;共享事件服务规则见 `docs/reference/oa-event-flow.md`Pipeline 专有控制流规则见 `docs/reference/pipeline-oa-event-flow.md`
Pipeline 的一个 epoch 是同一个 pipeline 从入口到终态完整执行一遍,UniDesk 前端把同一 `pipelineId` 下的多个 run 作为多个 epoch 管理。甘特图必须从 Pipeline snapshot 中的 `startedAt``finishedAt``durationMs` 和 procedure run 摘要生成,不得按某个 pipeline 实例或 node 名称硬编码布局。甘特图纵轴按时间从上到下排列,左侧固定时间列,后续每列对应一个 node;当前可见时间窗口内没有工作区间的 node 列应自动隐藏。默认页面不得展示裸 JSON、JSONL、worker log 或 control event 行;运行材料和 node 过程只能作为一组一行的结构化索引展示,完整原始内容只能在操作员点击 `查看原始JSON` 后显示。
### MET Nonlinear On D601
当前 MET Nonlinear 作为 `id=met-nonlinear` 的用户服务登记在 `config.json`
- Provider`D601`
- 开发工作树:`/home/ubuntu/met_nonlinear`,后端、Dockerfile、训练队列和训练容器都必须通过 UniDesk SSH 透传在 D601 开发调试;主 server 本地只允许开发 UniDesk frontend 与代理登记。
- 数据挂载: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。
MET Nonlinear 的长期服务边界写在业务仓库 `~/met_nonlinear/docs/reference/unidesk_microservice.md``met-nonlinear-ts` 是长驻 Bun TypeScript 编排后端,`met-nonlinear-ml:tf26` 是按需训练镜像,每个训练任务用一个 `docker run --rm` 容器执行 `python cli.py -t <projectPath>`,训练完成后容器自动销毁。训练镜像 Dockerfile 必须使用中国大陆可达的软件源;当前固定使用 Huawei Cloud mirror 的 `nvidia/cuda:11.2.2-cudnn8-runtime-ubuntu20.04`、Aliyun apt mirror、Tsinghua PyPI mirror、Ubuntu Python 3.8 和 `tensorflow==2.6.0`,避免官方 TensorFlow 2.6 GPU 镜像 Python 3.6 与业务源码类型注解不兼容。
MET Nonlinear 验收必须通过公网 UniDesk frontend 的交互式 UI 完成:选择已有 source Project,设置训练轮数和最大并发,使用 `Fork Project` 创建新的 `projects/unidesk_forks/` Project,确认新 Project 只是被选中而不会直接训练,再加入待启动队列并点击 `启动队列`。验收时必须确认项目库的 `projects/``ex_projects/` 按文件树层级展开、文件夹 Project 计数与后端返回数量一致;点击项目行后详情显示 `config.json``data/` 训练状态、模型参数量和指标;待启动、排队中、训练中、已完成和失败诊断分标签可见;训练队列和已完成行显示 `epoch/h` 训练速度且可点击打开任务详情。最大并发必须按 UI 设置生效,运行中行显示训练进度和 ETA,目标 GPU 为 2080Ti2080Ti 显存余量低于 20% 时自动限制并发,并确认训练容器结束后不残留。批量规模由 UI 输入框决定,完整验收可以通过输入 `Fork 数量=10``训练轮数=200``最大并发=3` 执行,但不得把该规模做成专用硬编码按钮。CLI `/api/queue/server-test` 仅保留为后端兼容入口,不作为 frontend 操作入口。
### ClaudeQQ Development On D601
ClaudeQQ 的业务源码和持久化数据仍在 D601,但正式运行由 k3s 代管:
- Provider`D601`
- 开发工作树:`/home/ubuntu/.agents/skills/claudeqq`,后端、Dockerfile、订阅分发和 NapCat 连接调试必须通过 UniDesk SSH 透传在 D601 完成;主 server 本地只允许开发 UniDesk frontend、代理登记和 k3s manifest。
- 代码引用:`https://gitee.com/lyon1998/agent_skills` 与配置中的 `repository.commitId`,实际服务目录为仓库内 `claudeqq/`
- 部署引用:标准 desired state 为根目录 `deploy.json` 中的 `claudeqq` 服务;运行清单为 UniDesk 仓库内 `src/components/microservices/k3sctl-adapter/k3s/claudeqq.k3s.json``claudeqq.k8s.yaml`,镜像名固定 `unidesk-claudeqq:d601`;构建 Dockerfile 与 API 适配器由 `src/components/microservices/claudeqq/` 注入。业务仓库内 `docker-compose.unidesk.yml` 只作为迁移/本地诊断参考,不是正式部署入口。
- 运行配置:ClaudeQQ Pod 同时包含 `napcat``claudeqq` 两个容器;`claudeqq` 固定 `CLAUDEQQ_AUTO_REPLY=false``CLAUDEQQ_NAPCAT_HTTP_HOST=127.0.0.1``CLAUDEQQ_NAPCAT_WS_HOST=127.0.0.1``CLAUDEQQ_ONLINE_NOTICE_USER_ID=645275593``CLAUDEQQ_LOGIN_MONITOR_INTERVAL_MS`。NapCat 的 `3000/3001/6099` 仅 Pod 内访问。
- 节点后端:UniDesk 逻辑后端为 `k3s://unidesk/claudeqq:3290`,由 backend-core 经过 k3sctl-adapter 和 Kubernetes API service proxy 访问 Kubernetes Service `claudeqq:3290`;不得再通过 provider-gateway 业务代理或 D601 本机端口作为正式链路。
- 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 用户服务代理调用。
## CLI
- `bun scripts/cli.ts microservice list`:列出全部用户服务、provider 映射、仓库引用、后端映射和运行态容器摘要。
- `bun scripts/cli.ts microservice status findjob`:查看单个用户服务的配置与运行态。
- `bun scripts/cli.ts microservice health findjob`:通过 backend-core -> provider-gateway -> D601 本机后端链路探测 FindJob `/api/health`
- `bun scripts/cli.ts microservice proxy findjob /api/summary`:通过同一私有代理读取业务 API,适合人工验证,不用于公开业务端口。
- `bun scripts/cli.ts microservice health pipeline`:通过 backend-core -> provider-gateway -> D601 本机后端链路探测 Pipeline `/health`
- `bun scripts/cli.ts microservice proxy pipeline '/api/snapshot?__unideskArrayLimit=registry.components:8,runs:3'`:读取 Pipeline snapshot 的有界预览,适合人工验证,不用于公开业务端口;验证甘特图时应确认 run 和 procedureRun 摘要包含 `startedAt``finishedAt``durationMs`;若 body 仍超过 CLI 阈值,默认只输出 `bodyPreview`,需要完整 body 时显式追加 `--raw`
- Pipeline node 控制写入由 UniDesk frontend 调用同源 `/api/microservices/pipeline/proxy/api/node-control/...` 完成;通用 CLI `microservice proxy` 仍主要作为读取验证入口,不作为人工批量写入工具。
- `bun scripts/cli.ts microservice health met-nonlinear`:通过 backend-core -> provider-gateway -> D601 本机 TS 编排后端链路探测 MET Nonlinear `/health`
- `bun scripts/cli.ts microservice proxy met-nonlinear /api/queue``bun scripts/cli.ts microservice proxy met-nonlinear /api/images`:读取 MET Nonlinear 队列、GPU 策略和训练镜像状态,适合人工验证,不用于公开业务端口。
- `bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects?root=projects&limit=500'``bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects/config?path=projects/<name>' --raw`:验证项目库文件树输入和结构化项目详情;详情应包含 config、progress、data、model、metrics 字段,供前端渲染训练状态、模型参数量和指标。
- `bun scripts/cli.ts microservice health claudeqq``bun scripts/cli.ts microservice proxy claudeqq /api/napcat/login``bun scripts/cli.ts microservice proxy claudeqq /api/events/recent``bun scripts/cli.ts microservice proxy claudeqq /api/events/subscriptions`:验证 ClaudeQQ 后端、NapCat 容器登录、事件订阅和私有代理链路;消息推送使用 `POST /api/push/text`,不得开放 D601 `3290/3000/3001/6099` 公网端口。
- `bun scripts/cli.ts microservice health todo-note``bun scripts/cli.ts microservice proxy todo-note /api/instances`:验证主 server Todo Note 后端、PostgreSQL 存储和本机 provider-gateway 私有代理链路。
- `bun scripts/cli.ts microservice health oa-event-flow``bun scripts/cli.ts microservice proxy oa-event-flow /api/diagnostics --raw``bun scripts/cli.ts microservice proxy oa-event-flow '/api/events?tags=service:code-queue&limit=20' --raw`:验证统一 OA 事件流、事件表、tag 查询和统计中心。
- `bun scripts/cli.ts microservice health k3sctl-adapter``bun scripts/cli.ts microservice proxy k3sctl-adapter /api/control-plane --raw`:验证 D601 `unidesk-k3s` 控制面 adapter、manifest、D601 scheduler/read/write 实例状态、`presentNodeIds` 包含 `D601``missingNodeIds=[]` 和 no-fallback 运行路径。
- `bun scripts/cli.ts microservice health code-queue-mgr`:验证主 server 轻量 Code Queue 控制面,输出必须包含 `role=master-control-plane``schemaReady=true`、PostgreSQL pool 上限和 `noRunnerDependencies=true`
- `bun scripts/cli.ts microservice health code-queue``bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview`:验证稳定 `code-queue` 用户服务路径可用;普通 health/overview/任务摘要/队列管理默认由 backend-core 分流到主 server `code-queue-mgr`,提交和 readAt/未读状态都必须由后端写入 PostgreSQL,frontend 不得用本地存储伪造成功状态。需要 D601 执行面状态时,通过 `k3sctl-adapter /api/control-plane` 查看 scheduler/read/write ready endpoint,或访问执行面专属 dev-ready、judge、active run control 路径;输出不得出现 `serviceId=code-queue` 的 provider-gateway `microservice.http` 业务代理任务。
- `bun scripts/cli.ts microservice diagnostics code-queue`:拆分 k3sctl-managed 链路健康,返回 `providerGateway``httpTunnel``k3sctlAdapter``kubernetesApiServiceProxy``targetService` 五段状态。该命令仍通过 backend-core 用户服务代理访问,不允许浏览器或 CLI 绕到 k3s、NodePort、Pod IP 或 D601 本机业务端口。
- `bun scripts/cli.ts microservice tunnel-self-test code-queue`:触发一次预期失败的 provider HTTP tunnel 请求,用于确认失败响应包含 `requestId``stage``x-unidesk-request-id``x-unidesk-tunnel-error`;该自测只访问 provider 侧无效 loopback 端口,不创建 Code Queue 队列,也不绕过正式 backend-core 入口。
- `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`:验证 D518 主 File Browser 和 D601 备用 File Browser 私有代理链路;浏览器 WebUI 必须通过 `/api/microservices/filebrowser/proxy/``/api/microservices/filebrowser-d601/proxy/` 访问,不得直接开放 `4251` 公网端口。
- `bun scripts/cli.ts --main-server-ip 74.48.78.17 microservice health findjob`:在计算节点或其他非主 server 主机上通过公网 frontend remote CLI 进行同一验证,不需要主 server SSH key。
`debug dispatch D601 microservice.http --payload-json ...` 仅用于开发调试 provider-gateway 代理能力;正式验收和用户入口应优先使用 `microservice` 命令与 frontend 用户服务页面。
## Frontend Rules
用户服务前端必须整合到 `src/components/frontend/src/` 下的 TypeScript + React 模块中。`app.tsx` 只做 shell/router 和导入分发,业务页面必须拆成独立 TSX,例如 `todo-note.tsx``findjob.tsx``pipeline.tsx``met-nonlinear.tsx``code-queue.tsx``k3sctl.tsx`。默认展示必须是业务控件:指标卡、状态徽标、表格、草稿卡片、运行卡片、树形任务、表单控件、结构化材料索引、链接和字段摘要;只有操作员点击 `查看原始JSON` 时才允许打开原始 JSON 弹窗。日志、JSONL 和大块 JSON 不得在主界面按行展示,避免把裸数据伪装成 UI。
对于超大业务 JSONbackend-core 可把 `__unideskArrayLimit=<path>:<limit>` 作为 frontend-only 代理参数传给 provider-gateway,由 provider-gateway 在返回前裁剪指定 JSON 数组并写入 `_unidesk.arrayLimits` 元数据。该参数只用于控制 UniDesk 展示预览,不能替代业务后端自身分页 API 的长期设计。CLI 的 `microservice proxy` 还会对超过默认阈值的 body 做二次有界预览,防止人工验证时输出爆炸;只有显式 `--raw` 才允许倾倒完整 body。
## Verification
用户服务交付必须同时通过后端、CLI 和公网 frontend 验证:
- 在主 server 运行 `bun scripts/cli.ts microservice list`,确认 `findjob``providerId=D601``public=false``frontendOnly=true`、仓库 URL、commit id、`127.0.0.1:3254` 映射和 `findjob-server` 容器摘要可见。
- 在主 server 运行 `bun scripts/cli.ts microservice list`,确认 `pipeline``providerId=D601``public=false``frontendOnly=true`、仓库 URL、commit id、`127.0.0.1:18082` 映射和 `pipeline-v2-control` 容器摘要可见。
- 在主 server 运行 `bun scripts/cli.ts microservice list`,确认 `met-nonlinear``providerId=D601``public=false``frontendOnly=true`、仓库 URL、commit id、`127.0.0.1:3288` 映射和 `met-nonlinear-ts` 容器摘要可见。
- 在主 server 运行 `bun scripts/cli.ts microservice list`,确认 `claudeqq``providerId=D601``public=false``frontendOnly=true`、仓库 URL、commit id、`deployment.mode=k3sctl-managed``runtime.orchestrator=k3sctl``backend.proxyMode=k3sctl-adapter-http``backend.nodeBaseUrl=k3s://claudeqq``k3s://unidesk/claudeqq:3290` 逻辑 Service 映射可见,且不显示业务容器直连摘要。
- 在主 server 运行 `bun scripts/cli.ts microservice list`,确认 `code-queue-mgr``providerId=main-server``deployment.mode=internal-sidecar`、Compose 后端 `http://code-queue-mgr:4278``frontend.integrated=false`,并确认稳定 `code-queue` 对外 ID 的说明中体现控制/读取默认由 `code-queue-mgr` 承担、D601 `k3sctl-managed` 只承担执行面;`k3sctl-adapter` 仍为 `providerId=D601``deployment.mode=unidesk-direct`、后端私有端口 `127.0.0.1:4266`
- 在主 server 运行 `bun scripts/cli.ts microservice list`,确认 `filebrowser``filebrowser-d601` 分别显示为 `providerId=D518``providerId=D601`,均为 `public=false``frontendOnly=true`,仓库 URL 为 `https://github.com/filebrowser/filebrowser`,后端映射为 `host.docker.internal:4251`,容器摘要分别为 `unidesk-filebrowser-d518``unidesk-filebrowser-d601`;列表中不得再出现主 server `filebrowser-main` 容器。
- 运行 `bun scripts/cli.ts microservice health findjob``bun scripts/cli.ts microservice proxy findjob /api/summary`,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 FindJob 后端。
- 运行 `bun scripts/cli.ts microservice health pipeline``bun scripts/cli.ts microservice proxy pipeline '/api/snapshot?__unideskArrayLimit=registry.components:8,runs:3'`,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 Pipeline 后端,且 run/procedure 摘要包含甘特图所需时间字段。
- 运行 `bun scripts/cli.ts microservice health met-nonlinear``bun scripts/cli.ts microservice proxy met-nonlinear /api/queue``bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects?root=projects&limit=20'``bun scripts/cli.ts microservice proxy met-nonlinear /api/images`,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 MET Nonlinear TS 后端。
- 运行 `bun scripts/cli.ts microservice health claudeqq``bun scripts/cli.ts microservice proxy claudeqq /api/napcat/login``bun scripts/cli.ts microservice proxy claudeqq /api/events/recent``bun scripts/cli.ts microservice proxy claudeqq /api/events/subscriptions`,确认真实链路经过 backend-core、k3sctl-adapter、Kubernetes API service proxy 和 D601 Kubernetes Service `claudeqq:3290`health 应显示 `service=claudeqq``pureBackend=true``napcat.containerized=true`、NapCat HTTP/WS 状态、二维码状态和订阅计数。
- 运行 `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-mgr``bun scripts/cli.ts microservice health code-queue``bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview``bun scripts/cli.ts codex submit --dry-run ...`,确认稳定 `code-queue` 控制/读取路径经 backend-core 分流到主 server `code-queue-mgr`,不依赖 D601 `code-queue-write` ready endpoint。再运行 `bun scripts/cli.ts microservice health k3sctl-adapter``bun scripts/cli.ts microservice proxy k3sctl-adapter /api/control-plane --raw`,确认 D601 scheduler/read/write 三个内部 Service 的 ready endpoint 和 no-fallback 拓扑;adapter 验收还必须证明其作为 UniDesk 直管服务运行在 k3s 外部,Docker 形态下挂载宿主 `/etc/rancher/k3s/k3s.yaml``/run/host-ssh/id_ed25519`,通过容器内 SSH local tunnel 连接 WSL 原生 k3s API,且 D601/D518 上都没有 active `rancher/k3s` 容器。D601 scheduler `/health` 必须仍返回业务后端自己的 `role=scheduler``queue.storage.primary=postgres``queue.storage.postgresReady=true``queue.notifications.claudeqq.outbox.storage=postgres``egressProxy.connected=true`,不得被 adapter 聚合健康 JSON 替代。还必须在 active Code Queue Pod 内验证主 PostgreSQL 端口映射、主 OA Event Flow 端口映射、集群内 ClaudeQQ `http://claudeqq.unidesk.svc.cluster.local:3290/health``d601-provider-egress-proxy` 均可访问,并确认 `/workspace``/home/ubuntu` 指向同一 WSL home hostPath`/workspace/cq-deploy` 这类绝对 symlink 可以进入真实目录。再通过公网 frontend 提交一个 `gpt-5.5` 小任务,确认任务先由 master `code-queue-mgr` 入库、D601 scheduler 轮询执行、输出实时更新、结束后有 judge 判定,且运行中可追加 prompt 或打断。Code Queue 的重启恢复必须作为验收项:运行中任务存在时重启或重建 scheduler 实例后,任务必须从 PostgreSQL 恢复到可继续执行状态,不能丢失 active task、`promptHistory`、后续 queued 任务、readAt/未读状态或已入 outbox 的 ClaudeQQ 通知。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、master mgr、k3s adapter 执行面、PostgreSQL 队列和任务列表都指向稳定 `code-queue`。批量验收必须通过公网 frontend 设置 `入队份数=5` 或使用多段 prompt 分隔,一次性入队 5 条任务,并确认 5 条任务按顺序进入 running/judging/succeeded,而不是只运行第一条。
- Code Queue 内存防回归验收:凡是改动 Code Queue 的持久化、scheduler、输出/Trace、health、列表/详情查询、日志导出或容器运行参数,交付前必须在 D601 用 `kubectl -n unidesk get deploy,pod,svc,endpoints -o wide``kubectl -n unidesk describe deploy/code-queue` 或等价 Docker inspect 确认 memory/swap 硬上限符合预算,运行 `kubectl -n unidesk top pod` 或 Docker stats 确认常驻内存、`OOMKilled=false``RestartCount` 未异常增长,再运行 `bun scripts/cli.ts microservice health code-queue` 确认 `/health` 是轻量 readiness 且暴露 PostgreSQL/notification/outbox 状态。验收还必须覆盖有历史任务存在时的 `/api/tasks/overview`、单任务详情和 output/transcript 查询,证明热状态裁剪不会丢历史输出、也不会重新把全部历史 `task_json` 缓存在进程内;涉及 TypeScript/frontend 验证的任务应能在 D601 Code Queue memory/swap 预算中完成 `bun run --cwd src/components/frontend check` 这类短时高内存命令,而不是被 memory watchdog 反复 SIGTERM。
- Code Queue 延迟防回归验收:凡是改动 Code Queue 列表、overview、readAt、Trace/summary 懒加载、实时 output/SSE 事件发布、frontend 请求策略、backend-core 用户服务代理或 frontend Code Queue 请求路径,交付前必须在有历史任务数据且有 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`、D601 Pod `/health``/api/tasks/overview` curl、性能面板 `/api/performance``/api/frontend-performance` 失败/慢操作记录、`kubectl -n unidesk top pod` 或 Docker stats 补充后端耗时、代理 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。
- 在 D601 上用 `bun scripts/cli.ts ssh D601 ...` 调试 `~/met_nonlinear`,确认 `curl http://127.0.0.1:3288/health` 可用;最终验收必须回到公网 UniDesk frontend,通过项目库选择、Fork、加入待启动队列和启动队列完成,不要把 MET Nonlinear 后端、Docker build 或训练任务部署到主 server。
- 在 D601 上用 `bun scripts/cli.ts ssh D601 ...` 调试 `~/.agents/skills/claudeqq`,可使用业务仓库 `docker-compose.unidesk.yml` 做本地诊断,但正式部署必须回到 `bun scripts/cli.ts deploy apply --service claudeqq`、k3s Deployment `claudeqq` 和 UniDesk `microservice health/proxy` 验证;不要把 ClaudeQQ 后端或 NapCat 调试服务部署到主 server,也不要把诊断 Compose 当作正式运行态。
- 运行 `bun scripts/cli.ts e2e run`,确认用户服务相关检查 passed,并确认 Playwright 访问的是公网 `http://74.48.78.17:18081/`
- 登录公网 frontend,进入 `用户服务 / 服务目录``用户服务 / Todo Note``用户服务 / FindJob``用户服务 / Pipeline``用户服务 / MET Nonlinear``用户服务 / ClaudeQQ`,确认能看到主 server 与 D601 provider、仓库引用、后端私有映射、Todo Note 迁移清单与树形任务、FindJob 指标和岗位预览、Pipeline 组件矩阵、React Flow 控制图、epoch 列表、epoch 甘特图和运行材料索引、MET Nonlinear 队列/GPU/镜像/Project config/训练历史、ClaudeQQ NapCat 容器登录二维码/NapCat 状态/事件订阅/消息推送/最近 QQ 事件;Todo Note 页面必须能创建临时清单、添加任务并删除临时清单,删除前必须按唯一临时清单名称重新选中对应行,禁止用未确认的当前 active 清单执行删除,FindJob 页面必须显示真实数字指标、`HEALTH OK` 和非空岗位预览,Pipeline 页面必须显示 `Pipeline v2 工作台``Health OK`、组件数、epoch 甘特图和结构化运行材料索引,MET Nonlinear 页面必须显示 `Health OK``Fork Project``启动队列``当前队列`、最大并发设置和 GPU/镜像面板,ClaudeQQ 页面必须显示 `Health OK``NapCat 容器登录``QQ 事件订阅``消息推送``事件缓存` 和私有代理说明,不能只停留在 loading 骨架;页面默认不得出现裸 JSON、JSONL 或逐行日志。