# Master Server 运维

本文记录 Master Server 的架构与判定规则。已安装的 Codex wrapper 负责命令语法，
本文负责 profile 边界、安全规则和验收标准。通用 server 生命周期与 GC 命令仍由
`unidesk-ops` skill 负责。禁止在本文记录 Secret、一次性故障流水或带日期的
changelog。

## 执行边界

- Master Server 是生产入口和控制面。
  - 本机默认只执行轻量源码修改、Git 操作、状态检查、健康检查、日志、诊断、
    JSON CLI 控制动作和本地 profile wrapper 维护。
- 重型构建和仓库级检查必须放到 G14、D601、CI/CD 或目标运行面。
- 本机 `/root/unidesk` checkout 可用于轻量 UniDesk CLI、`trans`、`tran`
  和 helper 修改。

## Codex Provider Profile

- `mycx` 与 `oncx` 是 `/usr/local/bin/` 下的稳定系统 Codex wrapper。
  - 两者复用同一个系统 `codex` binary。
  - 两者的认证 home 已隔离，长期约束见“系统 Wrapper 与共享状态”。
- `dscx`、`mxcx` 和 `acx` 是 `/root/.local/bin/` 下的隔离 profile
  wrapper。
  - 它们是 host 级工具，不属于 UniDesk service 代码。
- `acx` 是统一多模型入口。
  - `gocx` 保留为 OpenCode Zen Go 兼容入口。
  - 旧的 `dscx-go`、`dfcx-go` 和 `glcx-go` 只保留兼容入口。

- `dscx`：
  - 使用 `CODEX_HOME=/root/.codex-deepseek-v4-pro`；
  - 使用模型 `deepseek-v4-pro` 和 Codex 自定义 provider `deepseek`；
  - 使用本机 Moon Bridge `http://127.0.0.1:38440/v1`。
- `mxcx`：
  - 使用 `CODEX_HOME=/root/.codex-minimax-m3`；
  - 使用模型 `MiniMax-M3` 和 Codex 自定义 provider `minimax`；
  - 使用本机 Moon Bridge `http://127.0.0.1:38441/v1`。
- `acx`：
  - 使用 `CODEX_HOME=/root/.codex-acx`；
  - 默认模型是 `gpt-5.5-only`；
  - GPT alias 通过 Codex 自定义 provider 直连 OpenAI-compatible Responses 上游；
  - OpenCode Zen Go alias 仍通过本机 ACX router 到达 `gocx` / Moon Bridge。
- `gocx`：
  - 使用 `CODEX_HOME=/root/.codex-opencode-go-all`；
  - 默认模型是 `glm-5.1`；
  - 使用 Codex 自定义 provider `opencode`；
  - 使用本机 Moon Bridge `http://127.0.0.1:38447/v1`。
- `dscx-go`：
  - 使用 `CODEX_HOME=/root/.codex-opencode-go`；
  - 使用模型 `deepseek-v4-pro`；
  - 使用本机 Moon Bridge `http://127.0.0.1:38443/v1`。
- `dfcx-go`：
  - 使用 `CODEX_HOME=/root/.codex-opencode-flash`；
  - 使用模型 `deepseek-v4-flash`；
  - 使用本机 Moon Bridge `http://127.0.0.1:38444/v1`。
- `glcx-go`：
  - 使用 `CODEX_HOME=/root/.codex-opencode-glm`；
  - 使用模型 `glm-5.1`；
  - 使用本机 Moon Bridge `http://127.0.0.1:38446/v1`。

- `acx` 的 `model-catalog.json`：
  - 包含全部 OpenCode Zen Go upstream slug；
  - 包含 `gpt-5.5-only` 和 `gpt-5.5-sub2api`；
  - 允许在同一 profile 中通过 `/model` 或 `-m <model>` 切换。
- `acx` 的 GPT 路由：
  - `gpt-5.5-only` 直连 `only` OpenAI-compatible Responses endpoint；
  - `gpt-5.5-sub2api` 直连 Sub2API pool endpoint；
  - 两者的 upstream model 都是 `gpt-5.5`；
  - GPT 请求禁止经过 Moon Bridge 或 ACX router。
- GPT direct provider：
  - 必须通过 `ACX_GPT_DIRECT_API_KEY` 一类环境键接收 API key；
  - wrapper 从对应的 `/root/.codex/auth.json.<profile>` 读取；
  - 禁止回退到 `/root/.codex-acx/auth.json`；
  - `/root/.codex-acx/auth.json` 只允许保存本地 router dummy key。
- 隔离的 Moon Bridge 与 router wrapper：
  - 从 profile auth 文件读取 upstream API key；
  - 仅在兼容路径确有需要时由 wrapper 注入环境变量；
  - 生成配置放在 profile 的 `.tmp/`，权限必须是 `0600`；
  - 此兼容规则不适用于 `mycx` 的 Pika command-backed auth。
- 每个 profile：
  - 必须在 `config.toml` 中设置 `model_catalog_json`；
  - 必须指向包含当前模型的 profile-local `model-catalog.json`；
  - 缺失时 Codex 会回退到默认 metadata，并输出
    `Model metadata ... not found`。
- Profile context metadata：
  - 必须与真实 upstream limit 对齐；
  - 必须让 Codex auto-compact 在 provider 拒绝请求前触发。
- 当前 Master Server profile baseline：
  - `acx` 暴露的 GPT profile 使用
    `model_context_window = 272000` 和
    `model_auto_compact_token_limit = 240000`；
  - `deepseek-v4-pro` 与 `deepseek-v4-flash` 使用
    `model_context_window = 1000000` 和
    `model_auto_compact_token_limit = 900000`；
  - `glm-5.1`、`MiniMax-M3` 及其他本地 Moon Bridge profile 使用
    `model_context_window = 200000` 和
    `model_auto_compact_token_limit = 180000`。
- Wrapper 生成的 Moon Bridge / router metadata：
  - 必须与 profile `config.toml` 和 `model-catalog.json` 对齐；
  - 不一致时 Codex 与本地 admission layer 会对 compact 和请求大小产生分歧。
- Codex API 通道的 `NO_PROXY` / `no_proxy`：
  - 必须保留 `hyueapi.com` 和 `.hyueapi.com`。

### 系统 Wrapper 与共享状态（`mycx` / `oncx`）

- `mycx` 与 `oncx`：
  - 复用同一个系统 `codex` binary；
  - 使用不同的认证 `CODEX_HOME`；
  - 共享 SQLite session index 和 durable transcript。
- `/usr/local/bin/mycx`：
  - 使用 `CODEX_HOME=/root/.codex`；
  - 选择 profile `pika`；
  - 可写 profile 文件是 `/root/.codex/pika.config.toml`。
- `/usr/local/bin/oncx`：
  - 使用 `CODEX_HOME=/root/.codex-only`；
  - 使用 `CODEX_SQLITE_HOME=/root/.codex`；
  - 选择 profile `only`；
  - 可写 profile 文件是 `/root/.codex-only/only.config.toml`。
- `oncx` 的账号认证：
  - 只使用 `/root/.codex-only/auth.json`；
  - `cli_auth_credentials_store` 必须是 `file`；
  - `forced_login_method` 必须是 `chatgpt`；
  - `auth.json` 权限必须是 `0600`；
  - wrapper 必须清理 `OPENAI_API_KEY`、`CODEX_API_KEY`、
    `CODEX_ACCESS_TOKEN` 和 `CRS_OAI_KEY`；
  - `oncx login`、`oncx login status` 和 `oncx logout` 必须直接操作
    隔离 home，禁止附加仅适用于 runtime command 的 `--profile`。
- `oncx` 的共享状态：
  - `/root/.codex-only/sessions` 链接到 `/root/.codex/sessions`；
  - `/root/.codex-only/archived_sessions` 链接到
    `/root/.codex/archived_sessions`；
  - `history.jsonl`、`skills` 和 `plugins` 链接到共享 home；
  - shell snapshot、认证文件和 profile 配置保持隔离。
- `/root/.codex/config.toml` 与 `/root/.codex/auth.json`：
  - 由 `codex-pool configure-local` 管理；
  - 只代表 Sub2API consumer；
  - 禁止作为 `oncx` 的 ChatGPT 登录凭据。
- Webterm：
  - `config/platform-infra/webterm.yaml` 启动 `mycx`；
  - YAML 是 target、镜像、端口、启动命令、fallback shell、挂载和公网暴露的唯一真相；
  - 关闭公网暴露：
    - `publicExposure.enabled: false` 时，DNS、FRP 和 Caddy 字段不参与解析与验证；
    - `plan`、`status` 和 `validate` 必须明确返回 `skipped`。
  - target 校验：
    - 按 YAML 中的实际端口和 `TERMINAL_SHELL` 只读挂载判断；
    - 禁止把 `7683`、固定挂载数量或其他实例值写入代码门禁。
  - PTY 所有权：
    - `nsenter` 或 `chroot` 后必须由唯一的长期交互 Bash 持有 PTY；
    - 禁止让外层非交互 Bash 等待内层交互 Bash，再在内层退出后创建 fallback shell。
  - 启动命令中断：
    - 持有 PTY 的 Bash 必须在启动命令运行期间显式处理 `SIGINT`；
    - 前台子命令退出后继续执行 fallback，再恢复默认 handler；
    - fallback shell 必须由原进程 `exec`，禁止创建新的等待层。
  - Webterm launch check：
    - wrapper path 或启动方式变更必须保留原入口；
    - 从真实 Web/xterm 入口启动 `mycx`、发送 `Ctrl+C` 并执行 marker；
    - 确认会话仍为 `running`；
    - 确认没有 `initialize_job_control`、`no job control` 或非主动 `logout`。
  - foreground process group 验证：
    - 使用 Bash 内建 `read < /proc/$$/stat` 同时读取 `pgrp` 和 `tpgid`；
    - 禁止用外部 `ps` 读取后直接比较；
    - `ps` 执行期间会暂时成为 foreground process group，导致错误的不相等结果。
- Shell startup 文件：
  - 禁止定义同名 function 覆盖这些 executable；
  - 禁止在启动时重新生成 profile 文件；
  - wrapper 变更后，旧 Codex 进程必须退出；
  - 旧 shell 必须清除残留 function 或启动新 shell后再验收。

- Profile-v2 持久化：
  - `/model` 把 `model` 与 reasoning effort 写入当前 profile 文件；
  - `mycx` 与 `oncx` 必须独立记住最后一次选择；
  - `-m`、`-c model...` 和 `-c model_reasoning_effort...` 只允许临时覆盖；
  - 新启动、profile 切换和 resume 必须分别诊断。
- Codex 官方认证边界：
  - `--profile` 只覆盖 profile TOML，不隔离 `auth.json`；
  - file-based credential storage 的 `auth.json` 由 `CODEX_HOME` 决定；
  - 账号登录与 API-key consumer 必须使用不同的 `CODEX_HOME`；
  - 依据见
    [Codex Manual](https://developers.openai.com/codex/codex-manual.md#configuration-auth-and-models)。

- Session discovery：
  - 共享 SQLite 和 transcript 不保证 `/resume` 结果完全相同；
  - picker 还会按 provider ID、cwd、source 和 archive state 过滤；
  - provider ID 是持久化且大小写敏感的 session bucket key；
  - `--all` 只移除 cwd 限制，不移除 provider 限制；
  - Codex 升级后必须重新验证此契约；
  - resume 使用当前 profile 的有效 model、provider 和 reasoning override；
  - resume 不保证恢复 session 最后记录的模型。
- Rollout JSONL：
  - 是 durable history；
  - 可以重新填充 SQLite index；
  - 禁止只修改 `state_5.sqlite` 合并 provider bucket；
  - history migration 必须停止活动 Codex 进程；
  - 必须用结构化 JSON parser 修改 rollout metadata；
  - 必须事务化更新 SQLite index；
  - 必须保留 provenance 并在 reindex 后验证相同 session ID 集合。
- Provider identity：
  - 是兼容性契约；
  - 修改 `CODEX_HOME`、profile name、provider ID 或大小写前必须预检历史；
  - 会产生新隐藏 bucket 时必须告警或阻断；
  - 禁止让不同真实 backend 仅为合并 picker list 而冒充同一 provider。

- Pika 认证：
  - 必须使用 `[model_providers.Pika.auth]` 的 command-backed provider auth；
  - credential source 是 `/root/.codex/auth.json.pika`；
  - 文件权限必须是 `0600`；
  - 禁止把 Pika API key 导出到 wrapper 或 Codex 环境；
  - shell snapshot 权限必须是 `0600`；
  - shell snapshot 禁止包含可复用 credential；
  - 发现 plaintext snapshot 时必须删除副本并轮换 credential。

- Secret-safe 诊断只允许报告：
  - 有效 `CODEX_HOME`；
  - wrapper path；
  - 当前 profile 文件；
  - provider ID；
  - model 与 reasoning；
  - 按 provider 统计的 session 数量；
  - credential presence 或 fingerprint；
  - 相关文件权限。
- Profile 持久化验收：
  - 选择一个非 baseline `/model`；
  - 退出后重新启动相同 wrapper；
  - 确认选择保持；
  - 确认另一 profile 文件未变化；
  - 确认临时 CLI override 不写回 profile。
- `oncx` 认证验收：
  - `oncx login status` 必须显示 `Logged in using ChatGPT`；
  - 注入无效 API-key 环境变量后仍必须显示 ChatGPT 登录；
  - `oncx exec --ephemeral '<smoke prompt>'` 必须完成真实 Responses turn；
  - 共享 `codex login status` 必须继续显示 Sub2API API-key 登录；
  - 全程禁止输出 token 或完整 API key。
- `mycx` 认证验收：
  - 对应 API-key 环境变量缺失时，Pika command-backed auth 仍必须可用。
- `/resume` 差异诊断：
  - 先比较 `CODEX_HOME`、provider ID、cwd、source 和 archive filter；
  - 禁止在这些边界未核对前归因为 storage loss 或 cache。

相关调查证据与源码级依据保留在
[UniDesk issue #1640](https://github.com/pikasTech/unidesk/issues/1640)，本文是当前长期规则的
唯一权威入口。

## Moon Bridge

Moon Bridge 安装在 `/root/.local/bin/moonbridge`。该 binary 暴露 OpenAI Responses
endpoint，并把 Codex 请求桥接到 provider-specific upstream API。已安装的 profile
wrapper 负责 bridge 命令语法，本文只记录运行边界。

- 已安装 patch build 的本地源码：
  - 路径是 `/root/src/moon-bridge-sanitize`；
  - 修改时必须保持范围收敛；
  - 必须运行 package-level test；
  - 必须构建 replacement binary；
  - 必须备份旧 binary；
  - 必须重启受影响的 profile bridge；
  - 仍受 Master Server 重型构建限制。

### Profile 架构

- `dscx bridge-start`：
  - 渲染 profile 配置；
  - 在 `127.0.0.1:38440` 启动 Moon Bridge。
- `mxcx bridge-start`：
  - 渲染 profile 配置；
  - 在 `127.0.0.1:38441` 启动 Moon Bridge。
- `acx route-start`：
  - 渲染 ACX router 配置；
  - 在 `127.0.0.1:38448` 启动本地 routing service；
  - 只服务仍需 OpenCode Zen Go bridge path 的非 GPT alias。
- `gocx bridge-start`：
  - 渲染多模型 OpenCode Zen Go profile 配置；
  - 在 `127.0.0.1:38447` 启动 Moon Bridge。
- `dscx-go bridge-start`：
  - 在 `127.0.0.1:38443` 启动 Moon Bridge。
- `dfcx-go bridge-start`：
  - 在 `127.0.0.1:38444` 启动 Moon Bridge。
- `glcx-go bridge-start`：
  - 在 `127.0.0.1:38446` 启动 Moon Bridge。
- Wrapper 启动 bridge：
  - 使用 `setsid`；
  - 使用 profile-local PID 文件；
  - 日志写入 `<CODEX_HOME>/logs/moonbridge/`。
- `dscx` 路由：
  - 通过 Moon Bridge 使用 Anthropic-compatible upstream；
  - 使用 `deepseek_v4` extension。
- `mxcx` 路由：
  - 通过 Moon Bridge 使用 `openai-response` upstream passthrough。
- OpenCode Zen Go 路由：
  - `acx` 把模型路由到 `gocx` / Moon Bridge；
  - `gocx`、`dscx-go`、`dfcx-go` 和 `glcx-go` 使用
    `openai-chat` upstream；
  - upstream 是 `https://opencode.ai/zen/go/v1/chat/completions`；
  - Codex 侧保持 `wire_api = "responses"`。
- `acx` GPT 路由：
  - 必须直连 OpenAI-compatible Responses endpoint；
  - 禁止经过 Moon Bridge 或本地 ACX router；
  - GPT mode 下 `acx status` 必须报告 `mode=gpt-direct`；
  - 必须报告 `routerRequired=false`；
  - `127.0.0.1:38448` 不应存在 listener。
- OpenCode Zen Go model ID：
  - 必须使用 upstream slug，例如 `glm-5.1`；
  - `GLM-5.1` 一类 display name 不是 profile model identifier。
- OpenCode Zen Go bridge：
  - 禁止保留手写 bridge script；
  - 禁止保留静态替代 `moonbridge.config.yml`；
  - 唯一支持路径是 wrapper 生成的 `.tmp/moonbridge.generated.yml` 和
    `/root/.local/bin/moonbridge`；
  - 生成的 provider 配置必须显式设置 `user_agent`。
- Moon Bridge context-window admission：
  - oversized request 必须在本地 Responses path 被拒绝；
  - 预期状态是 HTTP `400`；
  - 错误类型是 `invalid_request_error`；
  - 错误 code 是 `context_length_exceeded`；
  - 禁止表现为 upstream-translated `502 Bad Gateway`。
- MiniMax：
  - 未经 tool-enabled smoke 证明可用，禁止把 Codex CLI 切到
    `openai-chat`。

### Profile 验收

- `*-go raw-smoke`：
  - 直接验证 upstream OpenCode Zen Go Chat Completions API。
- `*-go bridge-smoke`：
  - 验证本地 Moon Bridge 的 `/v1/responses` translation path。
- `*-go exec '在吗'`：
  - 验证真实 Codex profile；
  - 输出禁止包含 `Model metadata ... not found`；
  - 最新 session 必须使用 profile catalog 提供的
    `model_context_window`。
- ACX GPT 验收：
  - 使用 `acx status`、`acx models`、
    `acx gpt-only exec '在吗'`、`acx gpt-sub2api exec '在吗'` 和
    默认 `acx exec '在吗'`；
  - 必须完成真实 Codex Responses turn；
  - repeated 或 resume traffic 必须出现非零 `cached_input_tokens`；
  - 禁止依赖 `127.0.0.1:38448` listener。
- ACX OpenCode Zen Go alias 验收：
  - 使用 `acx route-start`、`acx route-status`、`acx models` 和
    `acx -m <opencode-model> exec '在吗'`；
  - 验证 router 到 `gocx` 的路径。
- `gocx` 模型验收：
  - 使用 `gocx raw-smoke [model]`；
  - 使用 `gocx bridge-smoke [model]`；
  - 使用 `gocx -m <model> exec '在吗'`；
  - 省略 `[model]` 时默认使用 `glm-5.1`。
- `ReasoningSummaryDelta without active item`：
  - 是 reasoning summary event 的独立 adapter noise；
  - 不等同于 model metadata 缺失；
  - 不能单独证明 profile 不可用。

### OpenAI Responses Tool Argument Sanitizer

- Moon Bridge：
  - 负责 MiniMax 风格错误 tool argument JSON 的在线 sanitizer；
  - 作用于 `openai-response` passthrough path；
  - 是第一道防线；
  - wrapper-level session cleanup 只处理 sanitizer 安装前已损坏的 JSONL。
- Sanitizer：
  - 递归扫描 `ResponsesRequest.input`；
  - upstream 前修复 tool-call `arguments`；
  - 在 completed event 中修复 response-side `arguments`；
  - 禁止修改 delta chunk；
  - 无法恢复时回退到 `{}`；
  - response rewrite 必须删除 upstream `Content-Length`。

## MiniMax Session 恢复

- `mxcx`：
  - 为损坏的 MiniMax-backed session JSONL 提供 cleanup 与 guard layer；
  - wrapper 负责命令语法；
  - 本节只记录行为规则。
- `mxcx resume`：
  - 调用 Codex 前自动执行 `session-clean` 和 `session-guard`；
  - 使用 `CODEX_HOME=/root/.codex-minimax-m3`。
- `session-clean`：
  - 只处理无效 tool-call `arguments`；
  - 包括 malformed JSON、MiniMax sentinel text 和 schema-invalid case；
  - 必须保留行顺序、非 tool message、reasoning、output、token record 和
    session metadata；
  - 禁止 compact、summarize、truncate 或 reorder transcript。
- 新的无效 `arguments`：
  - 必须由 Moon Bridge sanitizer 在线阻止；
  - `session-clean` 只用于恢复既有损坏 session。
- `session-guard`：
  - 注入 `mxcx-minimax-tool-and-apply-patch-guard-v2`；
  - 禁止重复注入。

## MiniMax Apply-Patch 操作

- MiniMax-backed session：
  - 必须使用与其他 agent 相同的 UniDesk 远端文本 patch 契约；
  - route 在前，operation 在后；
  - 默认使用 `apply-patch` v2。
- 稳定写入形式：
  - `trans <provider>:/absolute/workspace apply-patch <<'PATCH'`。
- 只读检查：
  - 使用 `trans <provider>:/absolute/workspace nl -ba file` 或同等有界命令。
- `apply-patch` 报告 `failed to find expected lines` 时：
  - 先读取目标当前精确内容；
  - 再用更小的 `Update File` hunk 重试；
  - 或使用 `@@ <unique anchor>`；
  - 或拆成多个小 hunk；
  - 这是正常 stale-context 恢复，禁止因此切换工具。
- 文本 patch 失败时禁止使用：
  - `download` / `upload`；
  - 远端 Python、Perl 或 sed heredoc；
  - `cat >` 或 `tee` 整文件覆盖；
  - `apply-patch-v1`。
- 仅在以下情况允许例外：
  - `apply-patch` 本身不可用；
  - 目标不是文本；
  - 目标是批量机械生成内容。
