Files
pikasTech-unidesk/docs/reference/microservices.md
T
Codex a242e3e3ec feat: expand scheduling, notifications, and queue runtime
- add scheduled task plumbing across backend core, CLI, and frontend surfaces

- add frontend notification UI and keep service pages using the repaired shared stylesheet

- refactor code queue runtime and update baidu netdisk/service integration docs
2026-05-13 08:43:43 +00:00

66 KiB
Raw Blame History

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-corebackend-core 再通过目标 provider-gateway 的 microservice.http 能力访问计算节点本机后端。
  • backend-core REST API、database 和计算节点用户服务后端都不得新增公网端口;公网入口仍只有 frontend 和 provider ingress。
  • microservice.http 只允许 provider-gateway 访问 http://127.0.0.1http://localhosthttp://host.docker.internal 这类节点本地地址;主 server 内置用户服务可使用同一 Compose 网络内的显式服务名,例如 todo-note:4211code-queue:4222。backend-core 还必须用 allowedPathPrefixesallowedMethods 同时限制可代理路径和 HTTP 方法。

Config Contract

config.jsonmicroservices 是用户服务的唯一登记来源。每个条目必须包含:

  • idnameproviderIddescription,用于 CLI、backend-core 和 frontend 统一识别。
  • repository.urlrepository.commitId,用于记录业务代码的外部权威来源;UniDesk 不 vendoring 业务全量代码。
  • repository.dockerfilerepository.composeFilerepository.composeServicerepository.containerName,用于说明部署应复用业务仓库自身维护的 Dockerfile/docker-compose。
  • backend.nodeBaseUrlbackend.nodeBindHostbackend.nodePortbackend.proxyModebackend.public=falsebackend.frontendOnly=truebackend.allowedMethodsbackend.allowedPathPrefixesbackend.healthPath,用于定义计算节点端口到 UniDesk frontend-only 代理的映射。
  • development.providerIddevelopment.sshPassthrough=truedevelopment.worktreePath,用于说明开发调试入口必须在计算节点上通过 UniDesk SSH 透传完成。
  • frontend.routefrontend.integrated=true,用于说明该业务前端已经整合到 UniDesk React 控制台,而不是继续公开业务自身前端。

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 和后端维护。
  • Providermain-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.jsondata/instances/*.todo.json*.history.jsonl 迁移到主 server PostgreSQL 的 todo_note_instancestodo_note_history 表。
  • 代理路径:只允许 /api/ 前缀;允许方法为 GETHEADPOSTDELETE,用于保持 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,并显式指向主 PostgreSQLdocker 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: 5totalTodos: 100completedTodos: 54 这一类可审计摘要,不能只依赖前端页面观察。

Todo Note 数据迁移后必须验证:microservice proxy todo-note /api/instances 至少能看到 CONSTAR大论文找工作小论文事务 五个迁移清单,总任务数不低于源数据的 100 条;再通过代理创建临时清单、添加任务、切换完成、撤销并删除临时清单,证明写入路径走 PostgreSQL 且不会污染长期数据。

Project Manager On Main Server

当前 Project Manager 作为 id=project-manager 的用户服务登记在 config.json

  • Providermain-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 表头为 序号合同号项目名称当前状况待完成付款情况其它
  • APIGET /healthGET|POST /api/projectsGET|PUT|DELETE /api/projects/{id}POST /api/import/excelPOST /api/import/projectsGET /api/projects/export.xlsx
  • 代理路径:只允许 /health/logs/api/ 前缀;允许方法为 GETHEADPOSTPUTDELETE
  • 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

  • Providermain-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_IDUNIDESK_BAIDU_NETDISK_CLIENT_SECRETUNIDESK_BAIDU_NETDISK_TOKEN_KEY 与可选 UNIDESK_BAIDU_NETDISK_APP_ROOT;当前默认工作根目录为 /,如需收回到应用目录可显式设为 /apps/<name>;不得把百度 AppSecret、token key、access token 或 refresh token 写入仓库文件。
  • 配置步骤:UNIDESK_BAIDU_NETDISK_TOKEN_KEY 可由本机生成;百度 client_idclient_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_... 目录。
  • APIGET /healthGET /api/auth/statusPOST /api/auth/device/startGET /api/auth/device/statusPOST /api/auth/refreshPOST /api/auth/logoutGET /api/accountGET /api/filesGET /api/files/metaPOST /api/foldersPOST /api/files/managePOST /api/transfers/upload-from-pathPOST /api/transfers/download-to-pathPOST /api/self-testGET /api/transfersGET|POST /api/transfers/{id}/cancel|retryGET /logs
  • 授权轮询:百度设备码轮询返回的 authorization_pendingslow_down 是正常中间态,后端必须把它们更新为 pending sessionslow_down 增加轮询间隔)而不是向前端抛 HTTP 错误;只有拒绝、过期或未知 OAuth 错误才进入 rejected/expired/failed。
  • 自测:POST /api/self-test 会在 staging 生成小文本、上传到配置工作根、通过 /api/files 找到 fs_id、下载回 staging 并校验 MD5;该端点不得回显 token/dlink,适合 CLI、前端按钮和交付验收使用。
  • 代理路径:只允许 /health/logs/api/ 前缀;允许方法为 GETHEADPOSTDELETE
  • 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/filebrowserfilebrowser/filebrowser:v2.63.3 镜像和 commit ca5e249e3c0c94159c2136a0cd431a424eb18472;主 server 不再运行 File Browser 容器,避免占用主 server CPU/内存和主机根目录遍历资源:

  • id=filebrowserProvider 为 D518,服务在 D518 节点本机绑定 4251provider-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-d601Provider 为 D601,服务在 D601 节点本机绑定 127.0.0.1:4251provider-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.syspagefile.sys 等受保护文件导致整个目录浏览失败。
  • 代理路径允许 /,允许方法为 GETHEADPOSTPUTPATCHDELETEFile Browser 的 /api/login/api/resources 和上传 API 需要透传 X-AuthRangeTus-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 自身端口不得直接暴露公网。

Code Queue On Main Server

当前 Code Queue 作为 id=code-queue 的用户服务登记在 config.json

  • Providermain-server,由本机 provider-gateway 通过 microservice.http 访问同一 Compose 网络内的 http://code-queue:4222
  • 代码引用:https://github.com/pikasTech/unidesk 与配置中的 repository.commitId;服务源码位于 src/components/microservices/code-queue,属于 UniDesk 自有控制面组件。
  • 部署引用:UniDesk 根仓库 docker-compose.yml 中的 code-queue serviceDockerfile 为 src/components/microservices/code-queue/Dockerfile,容器名为 code-queue-backend
  • 上线纪律:Code Queue 相关的前端或后端改进必须在同一任务内正式上线并验证公网 frontend 或 live API,不能只停留在源码、构建产物或“后续再上线”。重建 frontend 只替换无状态 WebUI 容器,不会触碰 code-queue-backend、PostgreSQL 队列或运行中 Codex thread,不能以“可能影响长期任务”为由延迟前端上线;code-queue-backend 本身带有 restart-recovery,允许按 server rebuild code-queue 或 Compose 重启/替换,停止、重启或重建后必须从持久化状态恢复运行中和排队任务。修改 Code Queue 自身时不得等待当前 Code Queue task 结束、等待 queue idle 或等待 0 running 后才重启;这会等待自己退出形成自锁。应直接执行受 Compose lock、build-first、no-deps force-recreate 和 post-up validation 保护的重启/重建路径,并用恢复后的 live API 或公网 frontend 证明任务和队列仍可读可继续。
  • 更名与灾备恢复:旧版 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-queuenodeBaseUrl=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_tasksunidesk_codex_queue_queuesunidesk_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-queueserver 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_KEYCRS_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 pushssh -T git@github.com 与 host 使用同一套 GitHub SSH key/known_hosts;不得把私钥复制进镜像或仓库。
  • Develop-ready 镜像:Code Queue 镜像必须在启动前预装 UniDesk/Pipeline 调试所需工具,至少包含 codexbunnodenpm/npxgitrgcurlpython3/pip3dockerdocker composedocker-composejqsshrsyncmakegcc/g++targzipunzip;不得依赖 Codex 任务运行时再 apt-get install 这些基础环境。
  • 远程开发容器与任务执行 ProviderCode Queue 必须能通过 live API 拉起 D601 等计算节点上的开发容器,入口为 POST /api/dev-containers/<providerId>/start,默认 Provider 为 D601。该流程由 Code Queue 调用 UniDesk ssh <providerId> 维护桥在目标节点创建 unidesk-codex-dev-<providerId>,并在主 server 与开发容器之间建立 ssh -w TUN 点对点链路;主 server 负责对开发容器的 TUN 源地址做 NAT/MASQUERADE,开发容器默认路由和 DNS 改走该 TUN,从而让 ping google.com、DNS、HTTP(S) 等出网都经主 server 全局代理,而不是依赖 D601 本地网络。提交 Code Queue 任务时必须支持选择执行 Provider:main-server 在本机 Code Queue 容器中执行且默认工作目录保持 /root/unidesk;其他 Provider 在对应 unidesk-codex-dev-<providerId> 容器中执行,默认工作目录为 /home/ubuntu,可按任务覆盖 cwd。远程任务启动前必须自动复用或拉起该 Provider 的开发容器、同步 Codex 配置和允许的运行时 provider 环境变量,并通过同一 master TUN/NAT 链路出网。验收必须保留三类日志:容器直连 google.com 在建隧道前失败、容器建隧道后 ping google.com 成功、主 server 上对应 UNIDESK-CODEX-DEV-<providerId> NAT 链或 tun<id> 计数在 ping 前后增长。开发容器代理密钥只生成到 .state/code-queue/dev-proxy/ 与目标节点用户目录,不得提交到仓库。
  • Codex 控制:服务内部启动 codex app-server --listen stdio://,用 JSON-RPC 调用 thread/startturn/startturn/steerturn/interrupt,并监听 turn/completed、assistant delta、reasoning delta、command output delta、file diff delta 等通知生成前端可轮询的 transcript。
  • 用户输入持久化:任务初始 prompt 以 basePrompt/displayPrompt 作为结构化来源,运行中追加的 turn/steer prompt 必须写入 promptHistorytranscript 构建时从这些结构化字段合成 Submitted promptSteer prompt,不能只依赖有 600 条上限的 raw output,否则长任务输出增长后会丢失关键人工指令。
  • 队列语义:POST /api/tasks/api/tasks/batch 入队,服务始终只运行一个 Codex turn;当前任务真正终止后才推进下一个任务。GET /api/tasksGET /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 推入 promptPOST /api/tasks/{id}/interruptDELETE /api/tasks/{id} 打断/取消;POST /api/tasks/{id}/retry 手动重试。队列 worker 必须隔离单个 task 的异常,不能因为某个 app-server、judge 异常或 judge 判定 fail 让后续 queued 任务停止;fail 只把当前任务标为 failed,随后必须继续扫描并推进下一个 queued/retry_wait 任务。当存在 queued/retry_wait 且 worker 空闲时,watchdog 必须自动重新调度。
  • 稳定性与重启恢复:Code Queue 的第一目标是长期稳定可用;部署修复或运维排障时不得因为担心容器重启会打断任务而拒绝重启、重建或替换 code-queue-backend。容器重启、服务进程重启和镜像替换后,队列、promptHistory、running/judging/retry_wait 任务和 active session 元数据必须从 PostgreSQL 恢复,并在已有 codexThreadId 可用时用 thread/resume 和 continuation prompt 无缝继续当前任务;如果原 app-server turn 已丢失,也必须把当前任务恢复到可 retry/continue 的状态,不能错误推进下一个任务或永久卡住。主 server 侧重建必须走 server rebuild code-queue,该 job 受 .state/locks/server-compose.lock 串行化约束,并且必须在 build 后执行 no-deps force-recreate 与 post-up health validation;禁止在 job 中先手工 docker rm 再依赖后续命令补救,因为中断窗口会让容器消失并触发 frontend direct microservice proxy failed。重启后出现 active task 丢失、手动 steer/interrupt 记录丢失、running 任务卡死、误判完成、跳过当前任务、容器消失或阻塞队列,均属于 Code Queue 的 P0 核心缺陷,必须先修复并补充 restart-recovery 验收,不能把“避免重启”作为交付策略。
  • 调度与 active run slotCode Queue 必须把“queue processor 正在等待/退避/轮询”和“实际占用 Codex/OpenCode 子进程运行槽”分开建模;CODE_QUEUE_MAX_ACTIVE_QUEUES 只限制真实 active run slot,不能把 retry backoff、等待内存下降或等待前序任务的 processingQueues 计入 active slot,否则设置全局 active slot 上限时,一个空等队列会把其他 runnable queue 永久饿死。多个 queue 同时等待 active slot 时必须显式维护 FIFO waiter 队列,避免某个长 retry/backoff 队列刚释放 slot 就立刻重抢,导致更早进入等待的 retry_wait 任务长期饥饿;/health 必须同时暴露真实 activeQueueIdsactiveRunSlotCount、等待中的 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 直读或聚合查询;随后只把 queuedrunningjudgingretry_wait 等调度必需任务载入 Bun 堆,并在 PostgreSQL 查询侧裁剪 hot output/events;最后用 dirty-only flush、append-only 输出归档、Codex SQLite 小批量导出、bun --smolmem_limit=600mmemswap_limit=1536mNODE_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/TraceCODE_QUEUE_MAX_ACTIVE_QUEUES=0 表示不按 queue 数量设置全局排队上限;如显式设置为正数,必须同时说明内存预算并补充内存压测验收。memory watchdog 必须以 cgroup working set 为主要判断,且在 swap 仍有余量时不得提前杀掉唯一 active run;否则 TypeScript/Playwright 这类短时高内存验证会被错误中断并让 retry 队列反复震荡。
  • 完成判定:app-server turn/completedturn.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.7complete|retry|fail 的判定是权威结果;当且仅当 MiniMax LLM 调用失效(未配置、额度/限流/网络/超时不可用、JSON 去噪与 repair 全部耗尽、或返回超预算反馈且修复耗尽)时,才允许启用非 LLM/fallback 判断。任何字符串匹配、正则、硬编码 safety override、hardCompletionBlockers/retryRequiredReasonsrecentOutput 中旧 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 overridehardCompletionBlockersretryRequiredReasons 这类本地门禁字段不得出现在发送给 MiniMax 的 executionRecord 中。只有 MiniMax 不可用或修复耗尽进入 fallback 时,才允许基于字符串/正则做保守 retry。
  • 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_nummpu_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 改动、传输中断和用户打断等样本,返回 hitstotalhitRate、每例 expecteddecision;该接口不得回显 MiniMax API key。
  • 模型选择:默认 Codex 模型是 gpt-5.5,内置模型队列包含 gpt-5.5gpt-5.4-minigpt-5.4gpt-5.5 的默认 reasoning effort 必须是 xhigh,可通过 CODE_QUEUE_MODEL_REASONING_EFFORTS 追加或覆盖模型级默认值;每个入队任务可通过前端模型下拉菜单或 API 覆盖 modelcwdreasoningEffortmaxAttemptsmaxAttempts 上限为 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 结果。
  • 状态与日志:main-server 默认工作目录为容器内 /root/unidesk,该路径映射主 server 的 ~/unidesk;同时保留 /workspace 映射以兼容历史任务。非主 server Provider 的任务默认工作目录为 /home/ubuntu,任务 JSON、列表、Trace 摘要和 CLI 查询都必须显示 providerId 与最终 cwd。Code Queue 的任务、queue、readAt/未读状态、attempt、judge、promptHistory、active session 元数据、控制状态和 ClaudeQQ 通知 outbox 一律以主 PostgreSQL 为权威,分别写入 unidesk_code_queue_tasksunidesk_code_queue_queuesunidesk_code_queue_notificationsDATABASE_URL 是必需配置,服务不得在 PostgreSQL 缺失或不可用时进入文件存储模式。.state/code-queue/state.json 不再作为任务或 queue 状态存储,不得重新引入本地 JSON fallback;服务启动必须以 PostgreSQL 为唯一来源恢复队列,并把 running/judging 任务恢复为 retry_wait。主 server 内存很少,Code Queue 必须把“内存是稀缺资源”作为核心设计约束:历史任务列表、详情、统计和只读 Trace 查询优先从 PostgreSQL 直读,进程内只保留当前 running/judging、queued、retry_wait 等调度必需热任务,不得把全部历史 task JSON 长期缓存到 Bun 堆;需要短期热缓存时必须有严格上限、可裁剪、可从 PostgreSQL 和 append-only 输出归档重建。WebUI 不得用 browser localStoragesessionStorage 或 IndexedDB 持久化 task/queue/readAt/unread 等业务状态;浏览器只能保留临时 UI 内存缓存,刷新后必须重新从后端读取 PostgreSQL 权威数据。Codex CLI-like output/Trace 的完整记录可以使用 append-only 文件作为日志型归档,但任务状态、未读状态和列表摘要不得依赖这些文件作为权威来源;/api/tasks/<id>/transcript/api/tasks/<id>/output 必须能分页重建完整历史,不得因为热状态裁剪而丢失早期 trace。热 task JSON 只保留可配置窗口以保证 /health/api/tasks 和 PostgreSQL flush 不被长任务拖死;主 server 为 Code Queue 放宽到 600M 容器预算后仍默认 CODE_QUEUE_IN_MEMORY_OUTPUT_RECORDS=10CODE_QUEUE_IN_MEMORY_EVENT_RECORDS=10,启动时必须在 PostgreSQL 查询侧裁剪 hot output/events,并只 flush dirty task,禁止启动后无条件重写全量历史 task JSON;更高预算才允许调大热窗口。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 回归且主进程内存可控,不得只在宿主机临时安装。日志写入 UniDesk logs/{YYYYMMDD}/{startStamp}_{YYYYMMDD}_{HH}_code-queue.jsonl,按小时切片并按日志族默认保留 1GiBCodex app-server 上游产生的 logs_*.sqlite 只能作为短暂缓冲,必须由 Code Queue 周期性导出为 logs/{YYYYMMDD}/{startStamp}_{YYYYMMDD}_{HH}_codex-app-server.jsonl,导出后删除/压缩已导出的 SQLite 行,避免重新形成 logs_2.sqlite 大文件;/logs 端点返回最近结构化日志。/healthqueue.storage.primary 必须恒为 postgres,并通过 queue.storage.postgresReadyqueue.devReady/api/dev-ready 暴露 PostgreSQL 可用性、develop-ready 自检、必需工具、Docker socket、docker compose、默认工作目录、Codex config 状态和 /root/.ssh 共享 SSH key 状态。Codex CLI-like 输出可能很大,服务必须节流状态持久化,禁止对每个 output delta 同步重写完整 state 导致 /health 和控制 API 卡死;容器 healthcheck 必须使用带超时的 HTTP 探针,不能留下堆积的无超时探针进程。
  • ClaudeQQ 通知:Code Queue 可通过 backend-core 的 claudeqq 用户服务代理调用 POST /api/push/text,在每个任务进入 succeededfailedcanceled 终态后向配置目标发送最终 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|groupCODE_QUEUE_NOTIFY_CLAUDEQQ_USER_IDCODE_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID 配置,默认私聊 645275593;代理基址、最终 response 最大字符数、单次超时和发送尝试次数分别由 CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URLCODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARSCODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MSCODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS 配置。任务终态和队列空闲通知必须先写入 PostgreSQL outbox 表 unidesk_code_queue_notifications 再异步发送;不得使用 .state/code-queue/claudeqq-notifications.jsonCODE_QUEUE_NOTIFY_CLAUDEQQ_OUTBOX_PATH 或任何本地 JSON 作为通知权威存储。发送失败、NapCat 离线、代理 502 或容器重启时不能丢通知,必须按 CODE_QUEUE_NOTIFY_CLAUDEQQ_RETRY_INTERVAL_MS 指数退避重试并跨进程/容器重启保留。/healthqueue.notifications.claudeqq 必须暴露非敏感配置、目标配置状态和 PostgreSQL outbox 统计;GET /api/notifications/claudeqq 返回 outbox 明细,POST /api/notifications/claudeqq/drain 手动触发发送,POST /api/notifications/claudeqq/backfill 可按 since 补入某次故障窗口内已终态任务,确保 QQ/NapCat 超时或离线不会让任务完成通知永久丢失。
  • 代理路径:只允许 /health/logs/api/ 前缀;允许方法为 GETHEADPOSTDELETE。Code Queue 只在 Compose 内网暴露 4222/tcp,不得映射或开放到公网。
  • UniDesk 前端:用户服务 / Code Queue React 页面负责展示队列卡片、任务 ID、复制任务 ID、引用按钮、任务耗时、默认模型、模型下拉、执行 Provider 下拉、Provider 对应默认工作目录、显式入队份数、引用任务 ID、清空输入、创建成功提示、MiniMax judge 状态、Codex CLI-like 输出流、attempt 终态、追加 prompt、打断和手动重试控件;整个 agent loop 消息流统一命名为专有名词 TraceTrace 包含 assistant message、user prompt、system event 和 tool callCode 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 不显示左侧项目符号缩进且永不折叠;点击队列卡片引用按钮必须自动把该任务 ID 写入提交表单的引用任务 ID 输入框;引用任务 ID 创建新任务时必须自动注入 bun scripts/cli.ts codex task <taskId> 的提示,让 Codex 读取初始 prompt、最后消息和工具摘要后继续;连续执行同一 prompt 应使用 入队份数 一次性生成多条队列任务,而不是依赖快速连点按钮;左侧 queue/session 卡片的 QUEUED 状态必须显示原因,例如 QUEUED(PREV TASK)QUEUED(MEM LIMIT)QUEUED(ACTIVE LIMIT);原始任务 JSON 只能通过显式 查看原始JSON 打开。

D601 User Services

当前 D601 同时承载以下 UniDesk 用户服务:

  • findjobFindJob 纯后端服务,UniDesk frontend 渲染岗位指标、岗位预览和草稿报告。
  • pipelinePipeline v2 控制与观测服务,UniDesk frontend 渲染组件矩阵、React Flow 控制图、epoch 甘特图、运行材料索引和 node 精细控制面板。
  • met-nonlinearMET Nonlinear 训练编排服务,UniDesk frontend 渲染 GPU/镜像、训练队列、Project config 预览、训练进度、ETA 和历史记录。
  • claudeqqClaudeQQ 纯后端 QQ 消息网关,UniDesk frontend 渲染 NapCat 连接、事件订阅、消息推送、最近 QQ 事件和发送记录。

FindJob On D601

当前 FindJob 作为 id=findjob 的用户服务登记在 config.json

  • ProviderD601
  • 开发工作树:/home/ubuntu/findjob,开发和调试必须通过 UniDesk SSH 透传进入 D601。
  • 代码引用:https://gitee.com/Lyon1998/findjob 与配置中的 repository.commitId
  • 部署引用:业务仓库自身 Dockerfiledocker-compose.ymlcomposeService=servercontainerName=findjob-server
  • 节点后端:D601 上 127.0.0.1:3254provider-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

  • ProviderD601
  • 开发工作树:/home/ubuntu/pipeline,开发和调试必须通过 UniDesk SSH 透传进入 D601。
  • 代码引用:https://github.com/pikasTech/pipeline 与配置中的 repository.commitId
  • 部署引用:业务仓库自身 Dockerfiledocker-compose.ymlcomposeService=pipeline-controlcontainerName=pipeline-v2-control
  • 节点后端:D601 上 127.0.0.1:18082provider-gateway 容器内通过 http://host.docker.internal:18082 访问。
  • 代理路径:只允许 /health/api/ 前缀;允许方法为 GETHEADPOST,其中 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/... 访问 Pipeline control backend;超大 snapshot 必须使用 __unideskArrayLimit=registry.components:<limit>,runs:<limit> 做展示级裁剪。node 控制入口必须走 Pipeline 后端 OA control API,前端不得直接写 .state、runner prompt 文件或命令队列;scorer 结果必须在 UniDesk Pipeline UI 中以结构化 score 卡片展示。Pipeline 控制与观测的最终态必须 100% 由 OA 事件流驱动,不得保留点对点控制、旧审核事件或旧 batch 推进逻辑,权威规则见 docs/reference/pipeline-oa-event-flow.md

Pipeline 的一个 epoch 是同一个 pipeline 从入口到终态完整执行一遍,UniDesk 前端把同一 pipelineId 下的多个 run 作为多个 epoch 管理。甘特图必须从 Pipeline snapshot 中的 startedAtfinishedAtdurationMs 和 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

  • ProviderD601
  • 开发工作树:/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/M50WSL 本机不安装 TensorFlow 训练环境。
  • 代码引用:https://github.com/pikasTech/met_nonlinear 与配置中的 repository.commitId
  • 部署引用:业务仓库内 docker-compose.unidesk.ymldocker/unidesk/Dockerfile.serverdocker/unidesk/Dockerfile.mlcomposeService=met-nonlinear-tscontainerName=met-nonlinear-ts
  • 节点后端:D601 上 127.0.0.1:3288provider-gateway 容器内通过 http://host.docker.internal:3288 访问。
  • 代理路径:只允许 /health/api/ 前缀;允许 GETHEADPOSTPUT,用于读取队列/历史、从已有 Project fork 新 Project、保存队列设置、加入待启动队列和启动队列。
  • UniDesk 前端:用户服务 / MET Nonlinear React 页面采用类似下载器的工作台交互,负责从项目库选择已有 Project、fork 新 Project、加入待启动队列、启动队列、调整最大并发、分标签展示当前队列/已完成/失败诊断/GPU 与镜像,并展示训练进度、ETA、训练速度 epoch/h、历史训练记录和显式原始 JSON 按钮。项目库必须按 projects/ex_projects/ 的真实目录层级渲染文件树,文件夹计数等于子树 Project 数;项目库和任务列表行都必须可点击打开结构化详情,详情以控件展示 config.jsondata/ 中的训练状态、模型参数量、模型层和指标,不默认展示裸 JSON。

MET Nonlinear 的长期服务边界写在业务仓库 ~/met_nonlinear/docs/reference/unidesk_microservice.mdmet-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.jsondata/ 训练状态、模型参数量和指标;待启动、排队中、训练中、已完成和失败诊断分标签可见;训练队列和已完成行显示 epoch/h 训练速度且可点击打开任务详情。最大并发必须按 UI 设置生效,运行中行显示训练进度和 ETA,目标 GPU 为 2080Ti2080Ti 显存余量低于 20% 时自动限制并发,并确认训练容器结束后不残留。批量规模由 UI 输入框决定,完整验收可以通过输入 Fork 数量=10训练轮数=200最大并发=3 执行,但不得把该规模做成专用硬编码按钮。CLI /api/queue/server-test 仅保留为后端兼容入口,不作为 frontend 操作入口。

ClaudeQQ On D601

当前 ClaudeQQ 作为 id=claudeqq 的用户服务登记在 config.json

  • ProviderD601
  • 开发工作树:/home/ubuntu/.agents/skills/claudeqq,后端、Dockerfile、订阅分发和 NapCat 连接调试必须通过 UniDesk SSH 透传在 D601 完成;主 server 本地只允许开发 UniDesk frontend 与代理登记。
  • 代码引用:https://gitee.com/lyon1998/agent_skills 与配置中的 repository.commitId,实际服务目录为仓库内 claudeqq/
  • 部署引用:业务目录内 Dockerfiledocker-compose.unidesk.ymlCompose service 为 claudeqqnapcat,容器名分别为 claudeqq-backendclaudeqq-napcat
  • 节点后端:D601 上 127.0.0.1:3290provider-gateway 容器内通过 http://host.docker.internal:3290 访问。
  • 代理路径:只允许 /health/logs/api/ 前缀;允许方法为 GETHEADPOSTDELETE
  • 服务模式: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:3000127.0.0.1:3001127.0.0.1:6099ClaudeQQ 容器通过 Compose 内网 napcat:3000/3001 访问。
  • NapCat 登录 APIGET /api/napcat/loginGET /api/napcat/status 返回容器化状态、HTTP/WS 连通性、登录状态和二维码 data URL;GET /api/napcat/qrcode 只返回二维码。二维码来源为共享挂载中的 /napcat/cache/qrcode.png,由 ClaudeQQ 后端转为 JSON data URL 后经 UniDesk 同源代理给前端展示。
  • 订阅 APIGET /api/events/recent 返回最近 QQ 事件,GET|POST /api/events/subscriptions 管理 webhook 订阅,DELETE /api/events/subscriptions/{id} 删除订阅;订阅回调使用 HTTP POST JSON,并在配置 secret 时携带 x-claudeqq-signature HMAC-SHA256。
  • 推送 APIPOST /api/push/text 接受 userIdgroupIdmessage,由 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 打开。

ClaudeQQ 在 UniDesk 语境中按消息网关后端服务管理:不得直接暴露 D601 的 3290300030016099 到公网,不得 iframe ClaudeQQ 旧 WebUI。浏览器只能通过 UniDesk frontend 的 /api/microservices/claudeqq/health/api/microservices/claudeqq/proxy/... 同源代理访问。

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 摘要包含 startedAtfinishedAtdurationMs;若 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/queuebun 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 claudeqqbun scripts/cli.ts microservice proxy claudeqq /api/napcat/loginbun scripts/cli.ts microservice proxy claudeqq /api/events/recentbun 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-notebun scripts/cli.ts microservice proxy todo-note /api/instances:验证主 server Todo Note 后端、PostgreSQL 存储和本机 provider-gateway 私有代理链路。
  • bun scripts/cli.ts microservice health code-queuebun scripts/cli.ts microservice proxy code-queue /api/tasks:验证主 server Code Queue 后端、PostgreSQL 强制持久化和本机 provider-gateway 私有代理链路;写入、追加 prompt、打断和 readAt/未读状态都必须由 backend 写入 PostgreSQLfrontend 不得用本地存储伪造成功状态。
  • bun scripts/cli.ts microservice health filebrowserbun scripts/cli.ts microservice health filebrowser-d601bun 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.tsxfindjob.tsxpipeline.tsxmet-nonlinear.tsxcode-queue.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,确认 findjobproviderId=D601public=falsefrontendOnly=true、仓库 URL、commit id、127.0.0.1:3254 映射和 findjob-server 容器摘要可见。
  • 在主 server 运行 bun scripts/cli.ts microservice list,确认 pipelineproviderId=D601public=falsefrontendOnly=true、仓库 URL、commit id、127.0.0.1:18082 映射和 pipeline-v2-control 容器摘要可见。
  • 在主 server 运行 bun scripts/cli.ts microservice list,确认 met-nonlinearproviderId=D601public=falsefrontendOnly=true、仓库 URL、commit id、127.0.0.1:3288 映射和 met-nonlinear-ts 容器摘要可见。
  • 在主 server 运行 bun scripts/cli.ts microservice list,确认 claudeqqproviderId=D601public=falsefrontendOnly=true、仓库 URL、commit id、127.0.0.1:3290 映射和 claudeqq-backend 容器摘要可见。
  • 在主 server 运行 bun scripts/cli.ts microservice list,确认 code-queueproviderId=main-serverpublic=falsefrontendOnly=true、UniDesk 仓库 URL、code-queue:4222 映射和 code-queue-backend 容器摘要可见。
  • 在主 server 运行 bun scripts/cli.ts microservice list,确认 filebrowserfilebrowser-d601 分别显示为 providerId=D518providerId=D601,均为 public=falsefrontendOnly=true,仓库 URL 为 https://github.com/filebrowser/filebrowser,后端映射为 host.docker.internal:4251,容器摘要分别为 unidesk-filebrowser-d518unidesk-filebrowser-d601;列表中不得再出现主 server filebrowser-main 容器。
  • 运行 bun scripts/cli.ts microservice health findjobbun scripts/cli.ts microservice proxy findjob /api/summary,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 FindJob 后端。
  • 运行 bun scripts/cli.ts microservice health pipelinebun 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-nonlinearbun scripts/cli.ts microservice proxy met-nonlinear /api/queuebun 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 claudeqqbun scripts/cli.ts microservice proxy claudeqq /api/napcat/loginbun scripts/cli.ts microservice proxy claudeqq /api/events/recentbun scripts/cli.ts microservice proxy claudeqq /api/events/subscriptions,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 ClaudeQQ 后端;在 D601 上 curl http://127.0.0.1:3290/health 应显示 service=claudeqqpureBackend=truenapcat.containerized=true、NapCat HTTP/WS 状态、二维码状态和订阅计数。
  • 运行 bun scripts/cli.ts microservice health todo-notebun 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-queuebun scripts/cli.ts microservice proxy code-queue /api/tasks,确认真实链路经过 backend-core、WebSocket、main-server provider-gateway 和主 server code-queue-backend 后端,并且 /healthqueue.storage.primary=postgresqueue.storage.postgresReady=true,不得出现 file fallbackqueue.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 configdocker inspect code-queue-backend 确认 memory 硬上限为 629145600 字节、memory+swap 上限为 1610612736 字节,运行 docker stats --no-stream code-queue-backend 确认常驻内存低于 600MiBOOMKilled=falseRestartCount 未异常增长,再运行 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。
  • 运行 bun scripts/cli.ts microservice health filebrowserbun scripts/cli.ts microservice health filebrowser-d601bun scripts/cli.ts microservice proxy filebrowser / --max-body-bytes 2000,确认 File Browser health 返回 status=OKWebUI HTML 包含 File BrowserD518/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/healthcurl 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 -f docker-compose.unidesk.yml up -d --build claudeqqclaudeqq-backendclaudeqq-napcat 都运行,curl http://127.0.0.1:3290/healthcurl http://127.0.0.1:3290/api/napcat/login 可用;不要把 ClaudeQQ 后端或 NapCat 调试服务部署到主 server。
  • 运行 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 OKFork Project启动队列当前队列、最大并发设置和 GPU/镜像面板,ClaudeQQ 页面必须显示 Health OKNapCat 容器登录QQ 事件订阅消息推送事件缓存 和私有代理说明,不能只停留在 loading 骨架;页面默认不得出现裸 JSON、JSONL 或逐行日志。