feat(code-queue): register minimax-m3 alongside minimax-m2.7

Add minimax-m3 as a sibling model on the same path as the existing
minimax-m2.7 support. M3 is the new PROD default; M2.7 remains available
as a one-line rollback target via MINIMAX_MODEL.

Changes:
- code-agent/common.ts: add minimaxM3Model constant, include in
  defaultCodeModels, normalize aliases (minimax-m3 / m3), route to
  opencode port, sibling entry in codeModelProviderSourceContract.
- code-agent/opencode.ts: add M3 branch in openCodeModelId, register
  both M2.7 and M3 in openCodeConfigContent, inject MINIMAX_M3_MODEL
  in local/remote env, M3 branch in missingOpenCodeCredentialMessage.
- index.ts + types.ts: add minimaxM3Model to RuntimeConfig, read
  MINIMAX_M3_MODEL env, propagate to remoteCodexEnvKeys.
- queue-api.ts: include minimaxM3Model in Pick for
  codeModelProviderSourceContract callers.
- scripts/src/code-queue.ts: add minimaxM3SubmitModel, normalize/port
  routing, default route recommendation now points to M3, M2.7 kept
  as minimaxM2FallbackCandidate.
- scripts/src/docker.ts: default UNIDESK_CODE_QUEUE_MINIMAX_M3_MODEL
  to MiniMax-M3.
- k8s yaml (D601 + G14): CODE_QUEUE_MODELS gains minimax-m3, three
  deployments each set MINIMAX_MODEL=MiniMax-M3 and
  MINIMAX_M3_MODEL=MiniMax-M3.
- docker-compose.d601.yml: same env defaults for local compose.
- docs/reference/{code-queue-supervision,microservices,
  windows-passthrough}.md: update model contracts to M3 default +
  M2.7 fallback.
- scripts/code-queue-submit-routing-contract-test.ts: low-risk
  recommendation now asserts minimax-m3; registry asserts both M3
  and M2.7 fallback in modelPorts/opencodeModels; provider source
  contract test passes minimaxM3Model.

Refs: #189
This commit is contained in:
Codex
2026-06-01 06:51:28 +00:00
parent 0b1c012ba5
commit 201a8c8af2
14 changed files with 102 additions and 33 deletions
+3 -3
View File
@@ -96,9 +96,9 @@ Code Queue 派单模型按成本、可信度和 blast radius 分层:GPT-5.5/Co
当前提交合同由 `bun scripts/cli.ts codex submit` 暴露:prompt 必须来自位置参数、`--prompt-file``--prompt-stdin`;可选字段包括 `--queue/--queue-id``--provider-id/--provider``--cwd/--workdir``--model``--reasoning-effort``--execution-mode/--mode``--max-attempts``--reference-task-id/--reference/--ref`。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 应使用 `--prompt-stdin``--prompt-file`,例如 `cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run``bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`;位置参数只适合短单行 smoke prompt。提交前先用 `--dry-run` 检查完整 payload,确认后移除 `--dry-run``--execution-mode` 只表示 Code Queue runtime placement,有效值是 `default``windows-native`;像 `full-access` 这类 sandbox-like 值必须在 response 中显示 requested/effective mapping,并提示真实权限看服务级 `runnerPermissions.sandbox` / `approvalPolicy`,当前不支持每任务 sandbox override。真实提交成功只返回低噪声写入确认、task id、队列和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt;需要复核正文时用返回的 `codex task <taskId>` 渐进展开。这些字段写入任务 payload 后由 `code-queue-mgr` 入 PostgreSQL,核心任务字段包括 `queue_id``provider_id``execution_mode``model``cwd``prompt/base_prompt``reference_task_ids``reasoning_effort``max_attempts``task_json``task_json` 还保留 `requestedExecutionMode` 以便审计 requested/effective 差异;队列记录至少有 `id/name/created_at/updated_at`。模型治理应优先看任务 payload 和数据库字段,不靠 worker final response 自报。
真实 `codex submit` 确认输出的 `queue` 是低噪声监督摘要:`queuedTaskIds.items` 必须强制包含本次新建且仍为 queued/retry_wait 的任务 ID`activeTaskIds` 在主 server 控制面 `activeTaskIds=[]``counts.running/judging>0` 时必须回退到 PostgreSQL `databaseActiveTaskIds` 或执行诊断中的 active IDs;这些 ID 列表都只能作为带 `count/returned/omitted/truncated/source` 的有界预览,权威并发口径来自 `counts``countContext`。当预览没有展开所有 ID 时,`listPreviewPolicy` 必须明确说明 omitted counts 和 raw 查看命令,避免指挥侧误判 15-runner 目标。
运行态默认模型仍是 `gpt-5.5``CODE_QUEUE_MODELS` 当前长期合同至少包含 GPT-5.5、GPT-5.4、GPT-5.4 Mini、DeepSeek Chat 和 MiniMax M2.7`deepseek`/`deepseek-chat``minimax-m2.7` 会走 OpenCode port,其余模型走 Codex port。只有当执行面 `/health` 或等价配置已经显示 DeepSeek 模型可用、并完成轻量 runner smoke 后,才允许真实提交 `--model deepseek-chat`
运行态默认模型仍是 `gpt-5.5``CODE_QUEUE_MODELS` 当前长期合同至少包含 GPT-5.5、GPT-5.4、GPT-5.4 Mini、DeepSeek Chat、MiniMax M3(生产默认)和 MiniMax M2.7(回滚)`deepseek`/`deepseek-chat``minimax-m3``minimax-m2.7` 会走 OpenCode port,其余模型走 Codex port。PROD 集群把 `MINIMAX_MODEL` 切到 `MiniMax-M3`judge 与 opencode 跟随;M2.7 路径完整保留,回滚只需 `MINIMAX_MODEL=MiniMax-M2.7` + rollout restart。只有当执行面 `/health` 或等价配置已经显示 DeepSeek 模型可用、并完成轻量 runner smoke 后,才允许真实提交 `--model deepseek-chat`
`codex submit --dry-run` 是派单前的轻量 preflight。它输出 `routingRecommendation``policyContract` 和模型注册表,帮助指挥官看到推荐 runner/model、风险信号、缺失的 prompt guard、模型分层、并发上限、`opencodeModels``modelPorts`;它不会修改真实提交 payload,也不会替代指挥官判断。真实派单是否使用 `--model minimax-m2.7``--model deepseek-chat``--model gpt-5.5` 仍由指挥官显式决定。
`codex submit --dry-run` 是派单前的轻量 preflight。它输出 `routingRecommendation``policyContract` 和模型注册表,帮助指挥官看到推荐 runner/model、风险信号、缺失的 prompt guard、模型分层、并发上限、`opencodeModels``modelPorts`;它不会修改真实提交 payload,也不会替代指挥官判断。真实派单是否使用 `--model minimax-m3``--model minimax-m2.7``--model deepseek-chat``--model gpt-5.5` 仍由指挥官显式决定。
`codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` 是同一套派单前 guardrail 的本地 dry-run 入口,用于检查 runner prompt 是否声明了 `DEV test class`、是否列出允许的 live mutation、禁止动作和 closeout 字段。它只返回分类、缺失或矛盾项和有界 evidence,不提交任务、不连接 live service、不打印完整 prompt。`codex submit --dry-run``codex steer --dry-run` 会嵌入同一 `promptLint` 结果;`dispatchDisposition=needs-authorization` 时,指挥官必须补齐授权或把 prompt 降到 `read-only` 范围后再派发/steer。
@@ -114,7 +114,7 @@ Device Pod 类 DS 验收不能只看最终回复。指挥官必须用 `codex tas
| --- | --- | --- | --- |
| GPT-5.5/Codex | 高风险、复杂、跨模块、运行态、CI/CD、release、deploy、安全、最终质量裁决 | 多信号诊断、可回滚边界、必要的轻量或 dev 验证 | 不因成本把运行态和生产风险降级 |
| DeepSeek/OpenCode | 中等复杂度的前端功能、局部用户服务模块、局部 CLI/helper、明确 contract guard 或 unit test | prompt 自包含、写入范围窄、无生产/密钥/DB 写入、验证命令明确、指挥官审阅 | 不处理 Code Queue runtime、backend-core、provider-gateway、k3sctl-adapter、release/v1 或部署变更 |
| MiniMax/OpenCode | 只读调查、文档、简单前端/样式、低风险样板、轻量 dry-run/preflight 和小范围测试补齐 | issue 只作辅助引用、必须给出 diff/路径/命令证据、完成后保持未读待审 | 不处理共享核心、隐式远端状态、生产、密钥、DB、重启、复杂 bug 或最终裁决 |
| MiniMax/OpenCode (M3 默认 / M2.7 回滚) | 只读调查、文档、简单前端/样式、低风险样板、轻量 dry-run/preflight 和小范围测试补齐 | issue 只作辅助引用、必须给出 diff/路径/命令证据、完成后保持未读待审 | 不处理共享核心、隐式远端状态、生产、密钥、DB、重启、复杂 bug 或最终裁决 |
| 指挥官/人工 | 真实生产动作、运行中任务控制、密钥/数据库/破坏性 Git、批量已读和高风险恢复 | 用户授权、只读诊断、恢复方案、记录 issue/#20/#24 | 不把执行权交给普通 worker |
外部 token provider、模型 API 和上游服务限流遵循本文监控章节的退避规则;`policyContract.externalProvider429` 只是把同一规则暴露给 dry-run 调度判断。
+2 -2
View File
@@ -213,7 +213,7 @@ D601 上必须显式使用原生 k3s kubeconfig`KUBECONFIG=/etc/rancher/k3s/k
- 部署引用: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 代理链路失败。
- MiniMax/OpenCode 并发:`minimax-m3`(生产默认)与 `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 实例的后端部署必须经未来受控 target-side CD 路径执行 build-first 镜像替换、k3s image import、manifest apply、rollout 和健康验证,并用 k3s adapter、Code Queue live API 或公网 frontend 证明任务和队列仍可读可继续。
@@ -225,7 +225,7 @@ D601 上必须显式使用原生 k3s kubeconfig`KUBECONFIG=/etc/rancher/k3s/k
- 远程开发容器与任务执行 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 runner 发起的 provider 维护命令必须通过主 server frontend authenticated `/ws/ssh` 流式代理进入 backend-core SSH bridge,再由目标 provider-gateway 执行 Host SSH/WSL SSHstdout/stderr 直接流回 runner,不能经过 `/api/dispatch` task polling 或 JSON compact。需要传递脚本、`py``apply-patch` 时也使用同一条 stdin 流式通道,避免恢复到本地 Docker broker、手工 base64 分块上传、交互 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`,否则会把旧任务内容误判为本轮完成。
- OpenCode 远程执行:`minimax-m3`(默认)与 `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 必须自动重新调度。
+1 -1
View File
@@ -198,7 +198,7 @@ Code Queue 支持在提交任务时选择 `executionMode=windows-native`。该
约束:
- 只支持非主 server WSL Provider 和 Codex 模型;`minimax-m2.7` / OpenCode port 不走该模式。
- 只支持非主 server WSL Provider 和 Codex 模型;`minimax-m3`(默认)/ `minimax-m2.7`(回滚) / OpenCode port 不走该模式。
- 工作目录必须在 `/mnt/<drive>/...` 下,供 `win-cmd` 转换为 Windows 盘符 cwdD601 默认提示为 `/mnt/f/Work/ConStart`
- Windows 侧必须已安装 `codex`,且 WSL wrapper `win-cmd` 可用;可用 `bun scripts/cli.ts ssh D601 -- 'export PATH="$HOME/.local/bin:$PATH"; win-cmd "where codex && codex --version"'` 验证。
- 任务 JSON、列表、Trace 摘要和前端卡片必须显示 `executionMode`,便于区分默认容器 Codex 与 Windows 原生 Codex。
@@ -44,7 +44,7 @@ export function runCodeQueueSubmitRoutingContract(): JsonRecord {
const lowRisk = codexSubmitRoutingRecommendationForTest(lowRiskPrompt);
assertCondition(lowRisk.route === "minimax-opencode", "low-risk self-contained prompt should be a MiniMax candidate", lowRisk);
assertCondition(lowRisk.recommendedRunner === "opencode", "MiniMax candidate should recommend OpenCode", lowRisk);
assertCondition(lowRisk.recommendedModel === "minimax-m2.7", "MiniMax candidate should recommend minimax-m2.7", lowRisk);
assertCondition(lowRisk.recommendedModel === "minimax-m3", "MiniMax candidate should recommend minimax-m3 as new default", lowRisk);
assertCondition(asRecord(lowRisk.riskControls).promptSelfContained === true, "low-risk prompt should be self-contained", lowRisk);
assertCondition(asRecord(lowRisk.riskControls).issueIsNotOnlySource === true, "issue must not be the only source", lowRisk);
@@ -73,22 +73,25 @@ export function runCodeQueueSubmitRoutingContract(): JsonRecord {
assertCondition(externalProvider429.commanderAction === "wait-while-exponential-backoff-is-healthy", "429 policy should tell commander to wait on healthy backoff", policyContract);
assertCondition(Array.isArray(externalProvider429.interveneWhen), "429 policy should expose intervention conditions", policyContract);
const registry = codexSubmitModelRegistryForTest(["gpt-5.5", "deepseek", "minimax-m2.7"]);
const registry = codexSubmitModelRegistryForTest(["gpt-5.5", "deepseek", "minimax-m3", "minimax-m2.7"]);
const modelPorts = asRecord(registry.modelPorts);
assertCondition(modelPorts["deepseek-chat"] === "opencode", "modelPorts should route deepseek-chat to OpenCode", registry);
assertCondition(modelPorts["minimax-m2.7"] === "opencode", "modelPorts should keep MiniMax on OpenCode", registry);
assertCondition(modelPorts["minimax-m3"] === "opencode", "modelPorts should keep minimax-m3 on OpenCode", registry);
assertCondition(modelPorts["minimax-m2.7"] === "opencode", "modelPorts should keep minimax-m2.7 on OpenCode as fallback", registry);
assertCondition(modelPorts["gpt-5.5"] === "codex", "modelPorts should keep default GPT on Codex", registry);
assertCondition(registry.opencodeModels.includes("deepseek-chat"), "opencodeModels should include deepseek-chat", registry);
assertCondition(registry.opencodeModels.includes("minimax-m2.7"), "opencodeModels should include MiniMax", registry);
assertCondition(registry.opencodeModels.includes("minimax-m3"), "opencodeModels should include minimax-m3", registry);
assertCondition(registry.opencodeModels.includes("minimax-m2.7"), "opencodeModels should include minimax-m2.7 as fallback", registry);
assertCondition(registry.codexModels.includes("gpt-5.5"), "codexModels should include default GPT", registry);
const providerSource = codeModelProviderSourceContract({
codeModels: ["gpt-5.5", "deepseek", "minimax-m2.7"],
codeModels: ["gpt-5.5", "deepseek", "minimax-m3", "minimax-m2.7"],
deepseekApiBase: "https://api.deepseek.example",
deepseekApiKey: "ds-secret-must-not-print",
deepseekModel: "deepseek-chat",
minimaxApiBase: "https://api.minimax.example",
minimaxApiKey: "",
minimaxModel: "MiniMax-M2.7",
minimaxM3Model: "MiniMax-M3",
}, {
DEEPSEEK_API_KEY: "ds-secret-must-not-print",
DEEPSEEK_API_BASE: "https://api.deepseek.example",
@@ -121,10 +124,10 @@ export function runCodeQueueSubmitRoutingContract(): JsonRecord {
return {
ok: true,
checks: [
"low-risk self-contained prompts recommend minimax-m2.7/OpenCode",
"low-risk self-contained prompts recommend minimax-m3/OpenCode (new default)",
"runtime/core work recommends GPT-5.5/Codex",
"medium bounded frontend work recommends deepseek-chat/OpenCode",
"model registry maps deepseek-chat and minimax-m2.7 to OpenCode and GPT-5.5 to Codex",
"model registry maps deepseek-chat, minimax-m3, and minimax-m2.7 fallback to OpenCode and GPT-5.5 to Codex",
"model provider source contract exposes DeepSeek refs/presence without secret values",
"dry-run policy contract exposes model-tier concurrency",
"prod/restart/secret/DB work is commander-only",
+16 -7
View File
@@ -51,6 +51,7 @@ const detailInitialPromptPreviewChars = 1200;
const detailBasePromptPreviewChars = 800;
const detailLastAssistantPreviewChars = 1200;
const minimaxSubmitModel = "minimax-m2.7";
const minimaxM3SubmitModel = "minimax-m3";
const deepseekSubmitModel = "deepseek-chat";
const gptSubmitModel = "gpt-5.5";
const submitLockWaitMs = 60_000;
@@ -1300,6 +1301,7 @@ function normalizeSubmitModel(value: string | null | undefined): string {
const lower = raw.toLowerCase();
const leaf = lower.includes("/") ? lower.split("/").at(-1) ?? lower : lower;
if (leaf === minimaxSubmitModel || leaf === "m2.7") return minimaxSubmitModel;
if (leaf === minimaxM3SubmitModel || leaf === "m3") return minimaxM3SubmitModel;
if (leaf === deepseekSubmitModel || leaf === "deepseek") return deepseekSubmitModel;
return raw;
}
@@ -1322,7 +1324,7 @@ function submitModelRegistry(models: string[] = sharedDefaultCodeModels): {
function submitRunnerForModel(model: string | null | undefined): "opencode" | "codex" | null {
const normalized = normalizeSubmitModel(model);
if (normalized.length === 0) return null;
return normalized === minimaxSubmitModel || normalized === deepseekSubmitModel ? "opencode" : "codex";
return normalized === minimaxSubmitModel || normalized === minimaxM3SubmitModel || normalized === deepseekSubmitModel ? "opencode" : "codex";
}
function regexEvidence(text: string, patterns: RegExp[], limit = 6): string[] {
@@ -1574,11 +1576,17 @@ function submitPolicyContract(): SubmitRoutingRecommendation["policyContract"] {
requiredGuards: ["self-contained prompt", "limited write scope", "contract/unit verification", "commander review"],
},
{
model: minimaxSubmitModel,
model: minimaxM3SubmitModel,
runner: "opencode",
taskRisk: "simple-low-risk",
requiredGuards: ["issue is auxiliary only", "evidence required", "no prod/secrets/DB writes", "diff and test review"],
},
{
model: minimaxSubmitModel,
runner: "opencode",
taskRisk: "simple-low-risk-fallback",
requiredGuards: ["issue is auxiliary only", "evidence required", "no prod/secrets/DB writes", "diff and test review"],
},
],
externalProvider429: {
commanderAction: "wait-while-exponential-backoff-is-healthy",
@@ -1741,15 +1749,15 @@ function submitRoutingRecommendation(options: CodexSubmitOptions): SubmitRouting
} else if (promptSelfContained && issueIsNotOnlySource && evidenceRequiredByPrompt && lowRiskEvidence.length > 0) {
route = "minimax-opencode";
recommendedRunner = "opencode";
recommendedModel = minimaxSubmitModel;
recommendedModel = minimaxM3SubmitModel;
confidence = "high";
reason = "The prompt looks self-contained, low-risk, and asks for verifiable evidence; it is a MiniMax/OpenCode candidate if the runner smoke is currently green.";
reason = "The prompt looks self-contained, low-risk, and asks for verifiable evidence; it is a MiniMax M3/OpenCode candidate if the runner smoke is currently green.";
} else if (lowRiskEvidence.length > 0 && issueIsNotOnlySource && noProdRestartSecretOrDbWrite) {
route = "minimax-opencode";
recommendedRunner = "opencode";
recommendedModel = minimaxSubmitModel;
recommendedModel = minimaxM3SubmitModel;
confidence = "medium";
reason = "The prompt has low-risk signals, but the commander should tighten self-contained context and evidence requirements before relying on MiniMax.";
reason = "The prompt has low-risk signals, but the commander should tighten self-contained context and evidence requirements before relying on MiniMax M3.";
}
const explicitNote = model.length === 0
@@ -7963,7 +7971,8 @@ function codexSubmitTask(args: string[]): unknown {
},
commands: {
submitAsRequested: "remove --dry-run to submit exactly this payload",
minimaxCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${minimaxSubmitModel} --dry-run`,
minimaxCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${minimaxM3SubmitModel} --dry-run`,
minimaxM2FallbackCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${minimaxSubmitModel} --dry-run`,
deepseekCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${deepseekSubmitModel} --dry-run`,
gptCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${gptSubmitModel} --dry-run`,
},
+1
View File
@@ -241,6 +241,7 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
UNIDESK_TODO_NOTE_REMINDER_CLAUDEQQ_SEND_ATTEMPTS: runtimeSecret("UNIDESK_TODO_NOTE_REMINDER_CLAUDEQQ_SEND_ATTEMPTS") || "3",
UNIDESK_CODE_QUEUE_MINIMAX_API_KEY: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_API_KEY") || runtimeSecret("MINIMAX_API_KEY"),
UNIDESK_CODE_QUEUE_MINIMAX_MODEL: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_MODEL") || runtimeSecret("MINIMAX_MODEL") || "MiniMax-M2.7",
UNIDESK_CODE_QUEUE_MINIMAX_M3_MODEL: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_M3_MODEL") || runtimeSecret("MINIMAX_M3_MODEL") || "MiniMax-M3",
UNIDESK_CODE_QUEUE_MINIMAX_API_BASE: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_API_BASE") || runtimeSecret("MINIMAX_API_BASE") || "https://api.minimaxi.com/v1",
UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS: runtimeSecretWithDefault("UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS", "90000", "60000"),
UNIDESK_CODE_QUEUE_REMOTE_WORKDIR: codeQueueEnvValue("UNIDESK_CODE_QUEUE_REMOTE_WORKDIR", codeQueueRemoteWorkdir),
@@ -29,7 +29,9 @@ services:
CODE_QUEUE_OPENCODE_XDG_DIR: "/var/lib/unidesk/code-queue/opencode-xdg"
CODE_QUEUE_SOURCE_CODEX_CONFIG: "/root/.codex/config.toml"
CODE_QUEUE_DEFAULT_MODEL: "${CODE_QUEUE_DEFAULT_MODEL:-gpt-5.5}"
CODE_QUEUE_MODELS: "${CODE_QUEUE_MODELS:-gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7}"
CODE_QUEUE_MODELS: "${CODE_QUEUE_MODELS:-gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7}"
MINIMAX_MODEL: "${MINIMAX_MODEL:-MiniMax-M3}"
MINIMAX_M3_MODEL: "${MINIMAX_M3_MODEL:-MiniMax-M3}"
CODE_QUEUE_MODEL_REASONING_EFFORTS: "${CODE_QUEUE_MODEL_REASONING_EFFORTS:-gpt-5.5=xhigh}"
CODE_QUEUE_SANDBOX: "${CODE_QUEUE_SANDBOX:-danger-full-access}"
CODE_QUEUE_APPROVAL_POLICY: "${CODE_QUEUE_APPROVAL_POLICY:-never}"
@@ -33,8 +33,9 @@ export interface ActiveRunSlotWaiter {
const gitProxyEnvKeys = ["CODE_QUEUE_EGRESS_PROXY_URL", "HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"];
export const minimaxM27Model = "minimax-m2.7";
export const minimaxM3Model = "minimax-m3";
export const deepseekChatModel = "deepseek-chat";
export const defaultCodeModels = ["gpt-5.5", "gpt-5.4-mini", "gpt-5.4", deepseekChatModel, minimaxM27Model];
export const defaultCodeModels = ["gpt-5.5", "gpt-5.4-mini", "gpt-5.4", deepseekChatModel, minimaxM3Model, minimaxM27Model];
export const opencodeNpmPackage = "opencode-ai@1.14.48";
export const defaultCodeExecutionMode: CodeExecutionMode = "default";
export const codeExecutionModes: CodeExecutionMode[] = ["default", "windows-native"];
@@ -49,6 +50,7 @@ export interface CodeModelProviderSourceConfig {
minimaxApiBase: string;
minimaxApiKey: string;
minimaxModel: string;
minimaxM3Model: string;
}
function proxyUrlFromEnv(env: NodeJS.ProcessEnv): string {
@@ -90,13 +92,14 @@ export function normalizeCodeModel(value: string): string {
const lower = raw.toLowerCase();
const leaf = lower.includes("/") ? lower.split("/").at(-1) ?? lower : lower;
if (leaf === "minimax-m2.7" || leaf === "m2.7") return minimaxM27Model;
if (leaf === "minimax-m3" || leaf === "m3") return minimaxM3Model;
if (leaf === "deepseek" || leaf === "deepseek-chat") return deepseekChatModel;
return raw;
}
export function codeAgentPortForModel(model: string): CodeAgentPortKind {
const normalized = normalizeCodeModel(model);
return normalized === minimaxM27Model || normalized === deepseekChatModel ? "opencode" : "codex";
return normalized === minimaxM27Model || normalized === minimaxM3Model || normalized === deepseekChatModel ? "opencode" : "codex";
}
export function normalizeCodeExecutionMode(value: unknown): CodeExecutionMode {
@@ -199,6 +202,19 @@ export function codeModelProviderSourceContract(config: CodeModelProviderSourceC
model: configRef("MINIMAX_MODEL", config.minimaxModel, env),
},
},
minimaxM3: {
runner: "opencode",
publicModel: minimaxM3Model,
providerModel: config.minimaxM3Model.trim() || "MiniMax-M3",
credentialSource: {
apiKey: {
...configRef("MINIMAX_API_KEY", config.minimaxApiKey, env),
present: config.minimaxApiKey.trim().length > 0,
},
baseURL: configRef("MINIMAX_API_BASE", config.minimaxApiBase, env),
model: configRef("MINIMAX_M3_MODEL", config.minimaxM3Model, env),
},
},
},
};
}
@@ -6,11 +6,11 @@ import { resolve } from "node:path";
import * as readline from "node:readline";
import type { AppServerExit, CodexEventSummary, CodexRunResult, JsonValue, QueueTask, RuntimeConfig, TerminalStatus } from "../types";
import type { ActiveRun, CodeAgentClient } from "./common";
import { codeAgentGitConfigEntries, deepseekChatModel, extractRecord, minimaxM27Model, normalizeCodeModel, stripAnsi, withCodeAgentGitConfigEnv } from "./common";
import { codeAgentGitConfigEntries, deepseekChatModel, extractRecord, minimaxM27Model, minimaxM3Model, normalizeCodeModel, stripAnsi, withCodeAgentGitConfigEnv } from "./common";
import { classifyRunnerError, runnerErrorClassificationJson } from "../runner-error-classifier";
export interface OpenCodePortContext {
config: Pick<RuntimeConfig, "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultWorkdir" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "resolvedRunnerSkillsPath" | "turnNoActivityTimeoutMs">;
config: Pick<RuntimeConfig, "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultWorkdir" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "minimaxM3Model" | "resolvedRunnerSkillsPath" | "turnNoActivityTimeoutMs">;
activeRuns: Map<string, ActiveRun>;
addEvent: (task: QueueTask, event: CodexEventSummary) => void;
appendOutput: (task: QueueTask, channel: "system" | "assistant" | "reasoning" | "command" | "diff" | "tool" | "error", text: string, method?: string, itemId?: string, append?: boolean) => unknown;
@@ -74,6 +74,10 @@ function openCodeModelId(model: string): string {
const providerModel = ctx().config.minimaxModel.trim() || "MiniMax-M2.7";
return `minimax/${providerModel}`;
}
if (normalized === minimaxM3Model) {
const providerModel = ctx().config.minimaxM3Model.trim() || "MiniMax-M3";
return `minimax/${providerModel}`;
}
if (normalized === deepseekChatModel) {
const providerModel = ctx().config.deepseekModel.trim() || "deepseek-chat";
return `deepseek/${providerModel}`;
@@ -83,6 +87,7 @@ function openCodeModelId(model: string): string {
function openCodeConfigContent(): string {
const providerModel = ctx().config.minimaxModel.trim() || "MiniMax-M2.7";
const providerModelM3 = ctx().config.minimaxM3Model.trim() || "MiniMax-M3";
const deepseekModel = ctx().config.deepseekModel.trim() || "deepseek-chat";
return JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -99,6 +104,10 @@ function openCodeConfigContent(): string {
name: minimaxM27Model,
limit: { context: 200000, output: 16384 },
},
[providerModelM3]: {
name: minimaxM3Model,
limit: { context: 200000, output: 16384 },
},
},
},
deepseek: {
@@ -139,6 +148,7 @@ function openCodeEnv(task: QueueTask): NodeJS.ProcessEnv {
MINIMAX_API_KEY: ctx().config.minimaxApiKey,
MINIMAX_API_BASE: ctx().config.minimaxApiBase,
MINIMAX_MODEL: ctx().config.minimaxModel,
MINIMAX_M3_MODEL: ctx().config.minimaxM3Model,
OPENCODE_CONFIG_CONTENT: openCodeConfigContent(),
UNIDESK_SKILLS_PATH: ctx().config.resolvedRunnerSkillsPath(),
});
@@ -165,6 +175,7 @@ function remoteOpenCodeRunCommand(task: QueueTask, prompt: string): string {
`export DEEPSEEK_MODEL=${ctx().shellQuote(ctx().config.deepseekModel)}`,
`export MINIMAX_API_BASE=${ctx().shellQuote(ctx().config.minimaxApiBase)}`,
`export MINIMAX_MODEL=${ctx().shellQuote(ctx().config.minimaxModel)}`,
`export MINIMAX_M3_MODEL=${ctx().shellQuote(ctx().config.minimaxM3Model)}`,
`export OPENCODE_CONFIG_CONTENT=${ctx().shellQuote(openCodeConfigContent())}`,
`export UNIDESK_SKILLS_PATH=${ctx().shellQuote(ctx().config.resolvedRunnerSkillsPath())}`,
...gitConfigEntries.map(([key, value], index) => `export GIT_CONFIG_KEY_$((\${GIT_CONFIG_COUNT:-0}+${index}))=${ctx().shellQuote(key)} GIT_CONFIG_VALUE_$((\${GIT_CONFIG_COUNT:-0}+${index}))=${ctx().shellQuote(value)}`),
@@ -383,6 +394,7 @@ function classifyOpenCodeRunnerFailure(task: QueueTask, result: Pick<CodexRunRes
function missingOpenCodeCredentialMessage(model: string): string | null {
const normalized = normalizeCodeModel(model);
if (normalized === minimaxM27Model && ctx().config.minimaxApiKey.length === 0) return "MINIMAX_API_KEY is required for opencode model minimax-m2.7.";
if (normalized === minimaxM3Model && ctx().config.minimaxApiKey.length === 0) return "MINIMAX_API_KEY is required for opencode model minimax-m3.";
if (normalized === deepseekChatModel && ctx().config.deepseekApiKey.length === 0) return "DEEPSEEK_API_KEY is required for opencode model deepseek-chat.";
return null;
}
@@ -401,7 +401,7 @@ function readConfig(): RuntimeConfig {
mainProviderId,
remoteDefaultWorkdir,
executionProviderIds,
remoteCodexEnvKeys: envList("CODE_QUEUE_REMOTE_CODEX_ENV_KEYS", ["OPENAI_API_KEY", "CRS_OAI_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE", "DEEPSEEK_API_KEY", "DEEPSEEK_API_BASE", "DEEPSEEK_MODEL", "MINIMAX_API_KEY", "MINIMAX_API_BASE", "MINIMAX_MODEL", "GH_TOKEN", "GITHUB_TOKEN", "GH_HOST", "GITHUB_API_URL", "GH_REPO"]),
remoteCodexEnvKeys: envList("CODE_QUEUE_REMOTE_CODEX_ENV_KEYS", ["OPENAI_API_KEY", "CRS_OAI_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE", "DEEPSEEK_API_KEY", "DEEPSEEK_API_BASE", "DEEPSEEK_MODEL", "MINIMAX_API_KEY", "MINIMAX_API_BASE", "MINIMAX_MODEL", "MINIMAX_M3_MODEL", "GH_TOKEN", "GITHUB_TOKEN", "GH_HOST", "GITHUB_API_URL", "GH_REPO"]),
skillsPath: envString("UNIDESK_SKILLS_PATH", "/root/.agents/skills"),
runnerSkillsSourcePath: envString("CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH", "/home/ubuntu/.agents/skills"),
resolvedRunnerSkillsPath,
@@ -428,6 +428,7 @@ function readConfig(): RuntimeConfig {
minimaxApiKey: envString("MINIMAX_API_KEY", ""),
minimaxApiBase: envString("MINIMAX_API_BASE", "https://api.minimaxi.com/v1").replace(/\/+$/u, ""),
minimaxModel: envString("MINIMAX_MODEL", "MiniMax-M2.7"),
minimaxM3Model: envString("MINIMAX_M3_MODEL", "MiniMax-M3"),
judgeTimeoutMs: envNumber("MINIMAX_JUDGE_TIMEOUT_MS", 90_000),
judgeRepairAttempts: Math.max(0, Math.min(5, envNumber("MINIMAX_JUDGE_REPAIR_ATTEMPTS", 2))),
judgeMaxTokens: Math.max(800, Math.min(4000, envNumber("MINIMAX_JUDGE_MAX_TOKENS", 1800))),
@@ -14,7 +14,7 @@ import type { ActiveRun, ActiveRunSlotWaiter } from "./code-agent/common";
import type { JsonValue, QueueRecord, QueuedStatusReason, QueueTask, RuntimeConfig, TaskStatus, TranscriptLine } from "./types";
export interface QueueApiContext {
config: Pick<RuntimeConfig, "approvalPolicy" | "codeModels" | "codexModels" | "codexSqliteLogExportBatchSize" | "codexSqliteLogExportEnabled" | "codexSqliteLogExportIntervalMs" | "codexSqliteLogMaxBytes" | "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultModel" | "defaultReasoningEffort" | "defaultWorkdir" | "judgeMaxTokens" | "judgeRepairAttempts" | "mainProviderId" | "maxActiveQueues" | "maxInMemoryEventRecords" | "maxInMemoryOutputRecords" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "modelReasoningEfforts" | "notifyClaudeQqBaseUrl" | "notifyClaudeQqEnabled" | "notifyClaudeQqMaxResponseChars" | "notifyClaudeQqRetryIntervalMs" | "notifyClaudeQqSendAttempts" | "notifyClaudeQqTargetType" | "notifyClaudeQqTimeoutMs" | "outputArchiveDir" | "remoteDefaultWorkdir" | "sandbox" | "schedulerEnabled" | "schedulerPollIntervalMs" | "windowsNativeCodexDefaultWorkdir">;
config: Pick<RuntimeConfig, "approvalPolicy" | "codeModels" | "codexModels" | "codexSqliteLogExportBatchSize" | "codexSqliteLogExportEnabled" | "codexSqliteLogExportIntervalMs" | "codexSqliteLogMaxBytes" | "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultModel" | "defaultReasoningEffort" | "defaultWorkdir" | "judgeMaxTokens" | "judgeRepairAttempts" | "mainProviderId" | "maxActiveQueues" | "maxInMemoryEventRecords" | "maxInMemoryOutputRecords" | "minimaxApiBase" | "minimaxApiKey" | "minimaxM3Model" | "minimaxModel" | "modelReasoningEfforts" | "notifyClaudeQqBaseUrl" | "notifyClaudeQqEnabled" | "notifyClaudeQqMaxResponseChars" | "notifyClaudeQqRetryIntervalMs" | "notifyClaudeQqSendAttempts" | "notifyClaudeQqTargetType" | "notifyClaudeQqTimeoutMs" | "outputArchiveDir" | "remoteDefaultWorkdir" | "sandbox" | "schedulerEnabled" | "schedulerPollIntervalMs" | "windowsNativeCodexDefaultWorkdir">;
activeRunSlotQueueIds: () => string[];
activeRunSlotWaiterSummaries: () => JsonValue[];
activeRuns: Map<string, ActiveRun>;
@@ -149,6 +149,7 @@ export interface RuntimeConfig {
minimaxApiKey: string;
minimaxApiBase: string;
minimaxModel: string;
minimaxM3Model: string;
judgeTimeoutMs: number;
judgeRepairAttempts: number;
judgeMaxTokens: number;
@@ -87,7 +87,11 @@ spec:
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7"
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7"
- name: MINIMAX_MODEL
value: "MiniMax-M3"
- name: MINIMAX_M3_MODEL
value: "MiniMax-M3"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
@@ -335,7 +339,11 @@ spec:
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7"
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7"
- name: MINIMAX_MODEL
value: "MiniMax-M3"
- name: MINIMAX_M3_MODEL
value: "MiniMax-M3"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
@@ -1045,7 +1053,11 @@ spec:
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7"
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7"
- name: MINIMAX_MODEL
value: "MiniMax-M3"
- name: MINIMAX_M3_MODEL
value: "MiniMax-M3"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
@@ -87,7 +87,11 @@ spec:
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7"
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7"
- name: MINIMAX_MODEL
value: "MiniMax-M3"
- name: MINIMAX_M3_MODEL
value: "MiniMax-M3"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
@@ -335,7 +339,11 @@ spec:
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7"
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7"
- name: MINIMAX_MODEL
value: "MiniMax-M3"
- name: MINIMAX_M3_MODEL
value: "MiniMax-M3"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
@@ -1042,7 +1050,11 @@ spec:
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m2.7"
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,deepseek-chat,minimax-m3,minimax-m2.7"
- name: MINIMAX_MODEL
value: "MiniMax-M3"
- name: MINIMAX_M3_MODEL
value: "MiniMax-M3"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX