Merge pull request #58 from pikasTech/fix/v01-minimax-m3-profile

feat: 新增 minimax-m3 Codex backendProfile
This commit is contained in:
Lyon
2026-06-02 07:58:57 +08:00
committed by GitHub
24 changed files with 177 additions and 80 deletions
+3 -3
View File
@@ -44,8 +44,8 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Agent 执行基础设施。本仓
## Critical v0.1 Implementation Stack Rule
- P0: AgentRun `v0.1` 自研 runtime、CLI、manager、runner、backend adapter、Codex backend 和后续 scheduler 的优先实现语言是 Bun + TypeScript;官方 TypeScript CLI 入口是 `scripts/agentrun-cli.ts`G14/CI/人工非交互命令使用 `./scripts/agentrun` 启动同一入口,复杂逻辑拆入 `scripts/src/``src/`
- P0: `backendProfile=codex``backendProfile=deepseek` 都必须通过同一个 Codex CLI app-server stdio backend kind 执行,启动受控 `codex app-server --listen stdio://`,使用 JSON-RPC 方法 `initialize``thread/start``thread/resume``turn/start`DeepSeek 是 profile/config/SecretRef 选择,不是直接 Responses HTTP 代理、独立 fake provider 或文本 fallback。
- P0: `codex``deepseek` profile 必须使用 profile-scoped SecretRef 和 writable `CODEX_HOME`,不得互相 fallback、复用运行态文件或污染默认 `codex` profile;切换顺序必须可验证为 `codex -> deepseek -> codex` 均独立。
- P0: `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3` 都必须通过同一个 Codex CLI app-server stdio backend kind 执行,启动受控 `codex app-server --listen stdio://`,使用 JSON-RPC 方法 `initialize``thread/start``thread/resume``turn/start`DeepSeek 和 MiniMax-M3 都是 profile/config/SecretRef 选择,不是直接 Responses HTTP 代理、独立 fake provider 或文本 fallback。
- P0: `codex``deepseek``minimax-m3` profile 必须使用 profile-scoped SecretRef 和 writable `CODEX_HOME`,不得互相 fallback、复用运行态文件或污染默认 `codex` profile;切换顺序必须可验证为 `codex -> deepseek -> minimax-m3 -> codex` 均独立。
- P0: 实现 Codex stdio backend/profile 前必须参考 UniDesk Code Queue 的 `src/components/microservices/code-queue/src/code-agent/codex.ts``common.ts`,以及 HWLAB 的 `internal/cloud/codex-stdio-session.mjs``scripts/code-agent-chat-smoke.mjs``docs/reference/spec-v02-deepseek-proxy.md``docs/reference/code-agent-chat-readiness.md`;复用协议、redaction、trace、failure 分类、profile overlay 和 Secret projection 经验,不复制环境专用路径或明文密钥。
## 长期参考文档
@@ -62,7 +62,7 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Agent 执行基础设施。本仓
- `docs/reference/spec-v01-agentrun-mgr.md`v0.1 manager REST API、tenant boundary、runner claim 和 event/status authority。
- `docs/reference/spec-v01-agentrun-runner.md`v0.1 短生命周期 runner、claim/poll/report、日志和 failureKind。
- `docs/reference/spec-v01-backend-adapter.md`v0.1 backend adapter 合同、event normalization、failure mapping 和 redaction。
- `docs/reference/spec-v01-backend-codex.md`v0.1 Codex app-server stdio backend、`codex`/`deepseek` profile、`~/.codex` 测试凭据 Secret projection 和真实 turn 验收。
- `docs/reference/spec-v01-backend-codex.md`v0.1 Codex app-server stdio backend、`codex`/`deepseek`/`minimax-m3` profile、`~/.codex` 测试凭据 Secret projection 和真实 turn 验收。
- `docs/reference/spec-v01-cli.md`v0.1 AgentRun CLI 命令族、JSON 输出、短返回和日志可见。
- `docs/reference/spec-v01-scheduler.md`v0.1 自动 scheduler 的 deferred 边界。
- `docs/reference/architecture.md`:AgentRun 产品边界、服务架构、MVP 阶段、RESTful API 模型和数据模型。
+9
View File
@@ -47,6 +47,15 @@
"profile": "deepseek",
"readOnly": true,
"writableCopy": true
},
{
"name": "minimax-m3-secret-projection",
"secretRef": { "name": "agentrun-v01-provider-minimax-m3", "keys": ["auth.json", "config.toml"] },
"projectionPath": "/var/run/agentrun/secrets/minimax-m3-0",
"runtimeCopyPath": "/home/agentrun/.codex-minimax-m3",
"profile": "minimax-m3",
"readOnly": true,
"writableCopy": true
}
],
"env": [
+4 -4
View File
@@ -22,7 +22,7 @@ UniDesk 与 HWLAB 是 tenant/client。UniDesk 负责平台入口、provider 清
- `projectId`,例如 `pikasTech/unidesk``pikasTech/HWLAB`
- `workspaceRef`,用于定位 source/worktree/workspace
- `providerId`,例如 `G14``D601`
- `backendProfile``v0.1` allowlist 为 `codex``deepseek`Queue 首版废弃 MiniMax/OpenCode,只允许 Codex/Codex-compatible profile
- `backendProfile``v0.1` allowlist 为 `codex``deepseek``minimax-m3`Queue 首版废弃 MiniMax/OpenCode 直连路线,只允许 Codex/Codex-compatible profile
- `executionPolicy`,包含 sandbox、approval、timeout、network 和 secret scope
- `traceSink`,说明标准化 event 镜像到哪里。
@@ -38,7 +38,7 @@ agentrun-runner
短生命周期 per-run 或 per-attempt executorclaim 一个 run,连接一个 backend,写回 events/status
agentrun-backend-*
Codex/Codex-compatible 执行适配器;Queue 首版不接 MiniMax/OpenCode
Codex/Codex-compatible 执行适配器;Queue 首版不接 MiniMax/OpenCode 直连路线
agentrun-queue
task、attempt、summary、stats、read cursor、commander 聚合;输出和 trace 只返回 Session 引用
@@ -49,13 +49,13 @@ agentrun-scheduler
Manager 是稳定 API 和审计点。Runner 是执行者,不应成为业务客户端直接调用的公共 API。MVP 阶段 operator 可以人工启动 runner 进程或 Kubernetes Job,但 runner 仍必须从 `agentrun-mgr` claim run,并把所有事实写回 manager。
Backend adapter 隐藏具体工具协议。`v0.1` 和 AgentRun Queue 首版只把 Codex app-server stdio 作为正式执行路径,`codex``deepseek` 是同一 backend kind 下的 profile/config/SecretRef 选择。跨 backend kind 路由属于后续规格;MiniMax/OpenCode 不作为 Queue 首版能力。
Backend adapter 隐藏具体工具协议。`v0.1` 和 AgentRun Queue 首版只把 Codex app-server stdio 作为正式执行路径,`codex``deepseek``minimax-m3` 是同一 backend kind 下的 profile/config/SecretRef 选择。跨 backend kind 路由属于后续规格;MiniMax/OpenCode 直连路线不作为 Queue 首版能力。
## v0.1 实现技术栈
AgentRun `v0.1` 自研 runtime 优先使用 Bun + TypeScriptmanager、runner、backend adapter、Codex backend、CLI 和后续 scheduler 都按这一技术栈实现。`scripts/agentrun-cli.ts` 是官方 CLI 入口;复杂 CLI 逻辑进入 `scripts/src/`,服务和 runner 逻辑进入 `src/`。YAML manifest、Tekton/Argo CD 配置、Postgres 和 Kubernetes 仍按各自原生生态管理。
Codex stdio backend 固定采用 Codex CLI app-server JSON-RPC over stdio。实现必须启动受控 `codex app-server --listen stdio://`,执行 `initialize``thread/start``thread/resume``turn/start`,并把 stdout/stderr、notification、tool lifecycle、assistant output 和 terminal/error 状态归一化为 AgentRun events。`codex``deepseek` 是同一个 backend kind 下的 profile/config/SecretRef 选择;直接 Responses HTTP、OpenAI SDK wrapper、`codex exec` 一次性输出或文本 fallback 不能作为 `v0.1` Codex backend 的正式执行路径。
Codex stdio backend 固定采用 Codex CLI app-server JSON-RPC over stdio。实现必须启动受控 `codex app-server --listen stdio://`,执行 `initialize``thread/start``thread/resume``turn/start`,并把 stdout/stderr、notification、tool lifecycle、assistant output 和 terminal/error 状态归一化为 AgentRun events。`codex``deepseek``minimax-m3` 是同一个 backend kind 下的 profile/config/SecretRef 选择;直接 Responses HTTP、OpenAI SDK wrapper、`codex exec` 一次性输出或文本 fallback 不能作为 `v0.1` Codex backend 的正式执行路径。
实现参考优先级:UniDesk Code Queue 的 `src/components/microservices/code-queue/src/code-agent/codex.ts``common.ts`,以及 HWLAB v0.2 的 `internal/cloud/codex-stdio-session.mjs``scripts/code-agent-chat-smoke.mjs``docs/reference/spec-v02-deepseek-proxy.md``docs/reference/code-agent-chat-readiness.md`。AgentRun 复用其协议、trace、redaction、Secret projection、profile overlay、DeepSeek/Moon Bridge 分层诊断和 failure 分类经验,但不复制 tenant 业务规则、环境专用路径、bridge host、namespace 或密钥材料。
+4 -3
View File
@@ -86,7 +86,7 @@ Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB
| `projectId` | 必填,例如 `pikasTech/unidesk``pikasTech/HWLAB`。 |
| `workspaceRef` | 必填,描述 source/worktree/workspace,不由 runner 猜测。 |
| `providerId` | 必填,例如 `G14``D601`;只表示目标 provider,不直接授予业务权限。 |
| `backendProfile` | 必填,`v0.1` allowlist 为 `codex``deepseek`;两者共享 Codex stdio backend kind。 |
| `backendProfile` | 必填,`v0.1` allowlist 为 `codex``deepseek``minimax-m3`;三者共享 Codex stdio backend kind。 |
| `executionPolicy` | 必填或由 manager 显式补齐默认值,至少包含 sandbox、approval、timeout、network 和 secretScope。 |
| `traceSink` | 字段必须存在;可以为 `null` 或显式 sink。 |
@@ -96,7 +96,7 @@ Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB
`v0.1` 不实现独立 policy engine。Manager 只做基础边界收敛:
- 校验 tenant/project/provider/backendProfile 是否在 `v0.1` 允许集合内;当前 backendProfile 允许 `codex``deepseek`
- 校验 tenant/project/provider/backendProfile 是否在 `v0.1` 允许集合内;当前 backendProfile 允许 `codex``deepseek``minimax-m3`
- 校验 workspaceRef 形态存在且与 tenant 请求一致;不替 tenant 判断某个 repo 操作是否业务授权。
- 校验 executionPolicy 不扩大 sandbox、network、approval、timeout 和 secretScope。
- 校验 secretScope 只引用 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 中允许的 SecretRef,且存在与 `backendProfile` 同名的 provider credentialmanager 只校验引用形态,不读取 Secret 值。
@@ -137,7 +137,7 @@ Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB
### T2 Run schema 与 tenant boundary
阅读本文和 [spec-v01-services.md](spec-v01-services.md),然后调用 `POST /api/v1/runs` 创建包含 tenant/project/workspace/provider/backend/execution/trace 字段的 run。确认缺失字段、非法 tenant、非法 backend、`backendProfile=deepseek` 但缺少 matching provider credential、或扩大 secretScope 都返回结构化 failureKind,合法请求持久化后可用 `GET /api/v1/runs/:runId` 查询。
阅读本文和 [spec-v01-services.md](spec-v01-services.md),然后调用 `POST /api/v1/runs` 创建包含 tenant/project/workspace/provider/backend/execution/trace 字段的 run。确认缺失字段、非法 tenant、非法 backend、`backendProfile=deepseek``backendProfile=minimax-m3` 但缺少 matching provider credential、或扩大 secretScope 都返回结构化 failureKind,合法请求持久化后可用 `GET /api/v1/runs/:runId` 查询。
### T3 Command idempotency
@@ -166,6 +166,7 @@ Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB
| command/run terminal 分离 | 已实现最小闭环 | `PATCH /api/v1/commands/:commandId/status` 终结 command 并更新 SessionRef;普通 turn completed 不终结 runrun status 仅由 run cancel 或 runner 级不可恢复失败终结。 |
| Tenant policy boundary | 已实现最小边界 | v0.1 已做 schema、tenant/backend allowlist、executionPolicy 和 secretScope 结构校验;业务授权仍由 UniDesk/HWLAB 自己判定。 |
| `deepseek` backendProfile allowlist | 已实现/已通过主闭环 | Manager validation、backend capability 和 matching SecretRef 校验已支持 `deepseek`;真实 runtime 已经通过 CI/CD 发布并确认 Postgres migration `002_v01_backend_profiles` 应用。 |
| `minimax-m3` backendProfile allowlist | 已实现/待真实主闭环 | Manager validation、backend capability 和 matching SecretRef 校验已支持 `minimax-m3`;真实 runtime 需要通过 CI/CD 发布并完成 AgentRun CLI 手动验收。 |
| Postgres durable adapter | 已实现/已通过主闭环 | live runtime 通过 `DATABASE_URL` 使用 Postgres durable storememory store 仅用于显式 self-test/dev。见 [spec-v01-postgres.md](spec-v01-postgres.md)。 |
| Observability 最小合同 | 已实现主路径 | events append-only、command-scoped terminal status、failureKind、health/readiness store 状态、runner claim/lease/backend events 和 Secret/DSN redaction 已进入 manager;集中 trace 和部署级观测仍属后续工作。 |
| durable cancel API | 已实现最小闭环 | 已提供 run/command cancel APIpending command cancel 阻止新 runner Jobrunning runner 轮询 cancel 并中止 Codex stdio backend,终态使用 `cancelled`。 |
+4 -3
View File
@@ -28,7 +28,7 @@ Runner 启动参数必须显式包含:
Runner Secret 只能通过 Kubernetes Secret projection、ServiceAccount/RBAC 或受控 Secret API 读取获得。Codex 测试凭据投影规则见 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 和 [spec-v01-backend-codex.md](spec-v01-backend-codex.md)。
Kubernetes Job runner 必须把 credential source 与 runtime home 分开:Secret volume 只读挂在 `/var/run/agentrun/secrets/...``/home/agentrun``emptyDir` 提供可写空间,`CODEX_HOME` 指向当前 run/profile 的 writable runtime home`AGENTRUN_CODEX_SECRET_HOME` 指向当前 `backendProfile` 对应的只读 projection。runner/backend 在启动 provider 前只复制授权文件,不打印内容。`codex``deepseek` profile 不得共享同一个可写 runtime home,除非它们运行在不同的 per-run Kubernetes Job 且该目录由 Job 独占 emptyDir 提供。
Kubernetes Job runner 必须把 credential source 与 runtime home 分开:Secret volume 只读挂在 `/var/run/agentrun/secrets/...``/home/agentrun``emptyDir` 提供可写空间,`CODEX_HOME` 指向当前 run/profile 的 writable runtime home`AGENTRUN_CODEX_SECRET_HOME` 指向当前 `backendProfile` 对应的只读 projection。runner/backend 在启动 provider 前只复制授权文件,不打印内容。`codex``deepseek``minimax-m3` profile 不得共享同一个可写 runtime home,除非它们运行在不同的 per-run Kubernetes Job 且该目录由 Job 独占 emptyDir 提供。
RuntimeAssembly P0 中 `SessionRef` 可以显式为 `null`runner 不得把完整 `CODEX_HOME`、Secret projection 或节点 host path 当作 session store。`ResourceBundleRef` P0 收敛为 Git-onlyrunner 已支持把 `repoUrl + full commitId` checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录,并记录 commit/tree 摘要,不能把用户上传文件或 env dump 混入 Git-only bundle。
@@ -131,11 +131,11 @@ HWLAB v0.2 原有 Code Agent 在 cloud-api 进程内执行,失败时依赖本
### T4 Missing Secret failure
阅读本文和 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md),然后分别用缺失 `codex` SecretRef缺失 `deepseek` SecretRef 的 run 启动 runner。确认 runner 不调用 providerrun 失败为 `secret-unavailable` 或等价 failureKind,不 fallback 到另一个 profile,日志和事件不泄露 Secret 值。
阅读本文和 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md),然后分别用缺失 `codex` SecretRef缺失 `deepseek` SecretRef 与缺失 `minimax-m3` SecretRef 的 run 启动 runner。确认 runner 不调用 providerrun 失败为 `secret-unavailable` 或等价 failureKind,不 fallback 到另一个 profile,日志和事件不泄露 Secret 值。
### T5 Profile switching
阅读本文和 [spec-v01-backend-codex.md](spec-v01-backend-codex.md),然后按 `codex -> deepseek -> codex` 顺序启动个真实 runner Job。确认每个 Job 只挂载和复制当前 profile 的 SecretRef`CODEX_HOME` 互相隔离,且前后两个 `codex` run 不受 `deepseek` run 的 config/model/upstream 影响。
阅读本文和 [spec-v01-backend-codex.md](spec-v01-backend-codex.md),然后按 `codex -> deepseek -> minimax-m3 -> codex` 顺序启动个真实 runner Job。确认每个 Job 只挂载和复制当前 profile 的 SecretRef`CODEX_HOME` 互相隔离,且前后两个 `codex` run 不受 `deepseek``minimax-m3` run 的 config/model/upstream 影响。
### T6 Same-run runner command loop
@@ -154,3 +154,4 @@ HWLAB v0.2 原有 Code Agent 在 cloud-api 进程内执行,失败时依赖本
| 同 run/runner 多 turn | 已实现最小闭环 | runner 在同一 Job 中 materialize bundle 一次后循环 poll command;普通 turn completed 只终结 commandrun 保持可继续接后续 turn,直到 idle timeout 或 run terminal。 |
| runner redaction | 已实现主路径 | runner/backend event 和 Job 输出使用 redaction;复杂审计仍按 [spec-v01-validation.md](spec-v01-validation.md) 的人工验收抽查。 |
| `deepseek` profile runner selection | 已实现/已通过主闭环 | Runner Job 和 host runner 已按 run `backendProfile` 选择 matching SecretRef、projection、`CODEX_HOME` 和 backend metadata;真实 Kubernetes Job 已完成 `codex -> deepseek -> codex` 切换联调。 |
| `minimax-m3` profile runner selection | 已实现/待真实主闭环 | Runner Job 和 host runner 已按 run `backendProfile=minimax-m3` 选择 matching SecretRef、projection、`CODEX_HOME` 和 backend metadata;真实 Kubernetes Job 需要完成 MiniMax-M3 CLI 手动联调后收口。 |
+9 -7
View File
@@ -1,10 +1,10 @@
# v0.1 Backend Adapter 规格
Backend adapter 是 runner 与具体 Code Agent 工具之间的适配层。`v0.1` 的正式执行路径只保留 Codex/Codex-compatible backend profileMiniMax/OpenCode 废弃,不作为 fallback、judge backend 或 Queue 首版能力。Adapter 把输入、事件、错误和 terminal status 归一化为 AgentRun 公共模型。
Backend adapter 是 runner 与具体 Code Agent 工具之间的适配层。`v0.1` 的正式执行路径只保留 Codex/Codex-compatible backend profileMiniMax/OpenCode 直连路线废弃,不作为 fallback、judge backend 或 Queue 首版能力。Adapter 把输入、事件、错误和 terminal status 归一化为 AgentRun 公共模型。
## 在系统中的职责划分
- 根据 `backendProfile` 选择具体 backend profile`v0.1` 必须支持 `codex``deepseek`,两者共享同一个 `codex-app-server-stdio` backend kind。
- 根据 `backendProfile` 选择具体 backend profile`v0.1` 必须支持 `codex``deepseek``minimax-m3`,三者共享同一个 `codex-app-server-stdio` backend kind。
- 接收 manager 持久化后的 run、command 和 executionPolicy;不得自行扩大 workspace、network、approval 或 secret scope。
- 调用具体 backend,并把 backend 输出归一化为 AgentRun events。
- 负责 provider/auth/backend/protocol 错误到 failureKind 的映射。
@@ -28,7 +28,7 @@ Adapter 输入必须来自 manager 保存的 run/command 和 Kubernetes Secret p
Backend adapter 消费 RuntimeAssembly 中的 `BackendImageRef``ProfileRef` 结果,但不定义四要素字段;四要素权威见 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)。
`v0.1` 的第一真实 adapter 是 Codex stdio adapter。它必须走 Codex CLI app-server JSON-RPC over stdioadapter 合同把 Codex 的 thread、turn、notification、tool lifecycle 和 stderr/exit 信息归一化为 AgentRun 标准 events。`codex``deepseek` 只是该 adapter 的 profile/config/SecretRef 选择,不允许复制套协议实现。
`v0.1` 的第一真实 adapter 是 Codex stdio adapter。它必须走 Codex CLI app-server JSON-RPC over stdioadapter 合同把 Codex 的 thread、turn、notification、tool lifecycle 和 stderr/exit 信息归一化为 AgentRun 标准 events。`codex``deepseek``minimax-m3` 只是该 adapter 的 profile/config/SecretRef 选择,不允许复制套协议实现。
## HWLAB v0.2 Code Agent 能力吸收
@@ -40,7 +40,7 @@ Backend adapter 的第一阶段实现应吸收 HWLAB v0.2 已验证的 Codex std
| completed 判定 | `docs/reference/code-agent-chat-readiness.md` | 只有 Codex turn terminal completed 且 assistant reply 可聚合时才输出 completedassistant delta、item completed、stdout 或 transport close 不能单独完成。 |
| assistant stream 和 trace | `internal/cloud/code-agent-trace-store.ts``internal/cloud/codex-stdio-session-turn-state.ts` | assistant delta 只能作为 stream/progress 证据;每个非空 completed `agentMessage` item 必须输出一个 `assistant_message` event,保留 `itemId` 和顺序;最终 result reply 必须优先来自最后一个 completed `agentMessage` item,不能把 commentary/progress delta 与 final response 直接串接。event 必须保留 `threadId``turnId`、session 摘要和 redacted backend metadata。 |
| command/tool output bounded | `docs/reference/code-agent-chat-readiness.md``web/hwlab-cloud-web/app-trace.ts` | `tool_call``command_output` 必须记录状态、摘要、字节数、截断标记;完整大输出只能通过后续 log/artifact 引用。 |
| provider/profile 隔离 | `internal/cloud/code-agent-contract.ts` | `codex``deepseek` 共享同一 backend kind,但必须使用 profile-scoped SecretRef、model/base-url/config 和 writable runtime home。 |
| provider/profile 隔离 | `internal/cloud/code-agent-contract.ts` | `codex``deepseek``minimax-m3` 共享同一 backend kind,但必须使用 profile-scoped SecretRef、model/base-url/config 和 writable runtime home。 |
| Secret redaction | `internal/cloud/code-agent-trace-store.ts` | `OPENAI_API_KEY`、auth/config、token、password、kubeconfig、URL credential 不得进入 event、result、log 或 health。 |
## Backend Profile Registry
@@ -51,6 +51,7 @@ Backend adapter 的第一阶段实现应吸收 HWLAB v0.2 已验证的 Codex std
| --- | --- | --- | --- | --- | --- |
| `codex` | `codex-app-server-stdio` | `codex-app-server-jsonrpc-stdio` | `stdio` | `codex app-server --listen stdio://` | 已有主闭环,必须保持默认兼容。 |
| `deepseek` | `codex-app-server-stdio` | `codex-app-server-jsonrpc-stdio` | `stdio` | `codex app-server --listen stdio://` | 已实现 profile;必须用独立 SecretRef 和 profile-scoped `CODEX_HOME` 完成真实联调。 |
| `minimax-m3` | `codex-app-server-stdio` | `codex-app-server-jsonrpc-stdio` | `stdio` | `codex app-server --listen stdio://` | 已实现 profile;必须用独立 SecretRef 和 profile-scoped `CODEX_HOME` 完成真实联调。 |
Registry 只表达能力和选择边界,不读取 Secret 值。Manager 负责校验 `backendProfile` 是否在 allowlist 内,并校验 `executionPolicy.secretScope.providerCredentials` 是否存在匹配 profile 的 SecretRefrunner 只为当前 run 选择的 profile 准备 Secret projection 和 runtime home。
@@ -105,19 +106,20 @@ Adapter 必须把 backend 错误映射为稳定 failureKind
### T3 真实 backend 联调
阅读本文、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后分别用 `backendProfile=codex``backendProfile=deepseek` 完成真实最短 turn。确认 adapter 输出真实 assistant/backend_status/terminal_status events,事件中包含 profile/backendKind/protocol 摘要,且没有 Secret 泄露。
阅读本文、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后分别用 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3` 完成真实最短 turn。确认 adapter 输出真实 assistant/backend_status/terminal_status events,事件中包含 profile/backendKind/protocol 摘要,且没有 Secret 泄露。
### T4 Profile isolation 自测试
阅读本文,然后用 fake Codex app-server 和个不同的 profile Secret fixture 做自测试。确认 adapter 只选择 run 指定 profile 的 SecretRef 和 `CODEX_HOME``deepseek` 缺失时失败为 `secret-unavailable`,不会 fallback 到 `codex`
阅读本文,然后用 fake Codex app-server 和个不同的 profile Secret fixture 做自测试。确认 adapter 只选择 run 指定 profile 的 SecretRef 和 `CODEX_HOME``deepseek``minimax-m3` 缺失时失败为 `secret-unavailable`,不会 fallback 到 `codex`
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Backend adapter 合同 | 已定义 | 本文为 v0.1 adapter 权威。 |
| 通用 adapter 模块 | 已实现 profile 形态 | `src/backend/adapter.ts` 作为 runner 进程内 adapter 入口;`codex``deepseek` 均路由到同一 Codex stdio backend kind,不复制第二套协议实现。 |
| 通用 adapter 模块 | 已实现 profile 形态 | `src/backend/adapter.ts` 作为 runner 进程内 adapter 入口;`codex``deepseek``minimax-m3` 均路由到同一 Codex stdio backend kind,不复制第二套协议实现。 |
| event normalization | 已实现主路径 | Codex backend 已把 backend_status、assistant_message、tool_call、command_output、error 和 terminal_status 归一化为 manager events;复杂事件审计按人工验收抽查。 |
| failure mapping | 已实现主路径 | Codex backend 已覆盖 missing secret、auth/rate/availability、protocol、JSON parse、invalid response、spawn、timeout 和 cancel 分类;真实负向场景按 [spec-v01-validation.md](spec-v01-validation.md) T7 手动验收。 |
| `deepseek` profile | 已实现/已通过主闭环 | 已进入 registry、validation、runner Secret selection、backend_status metadata、CLI secret render 和 fake stdio 自测试;真实综合联调已按 [spec-v01-validation.md](spec-v01-validation.md) T8 覆盖 `codex -> deepseek -> codex` 切换。 |
| `minimax-m3` profile | 已实现/待真实主闭环 | 已进入 registry、validation、runner Secret selection、backend_status metadata、CLI secret render 和 fake stdio 自测试;真实综合联调需要按 [spec-v01-validation.md](spec-v01-validation.md) T8 覆盖 `codex -> deepseek -> minimax-m3 -> codex` 切换。 |
| 多 backend 路由 | Deferred | 跨 backend kind 的自动路由和 scheduler capacity selection 不进入 v0.1。 |
+14 -8
View File
@@ -1,10 +1,10 @@
# v0.1 Codex Stdio Backend/Profile 规格
Codex stdio backend 是 AgentRun `v0.1` 的第一真实 Code Agent backend kind。它用于证明 runner、backend adapter、Kubernetes Secret projection、真实 provider 调用、event normalization 和 terminal status 的完整闭环。`v0.1` 在同一个 backend kind 下支持 `codex``deepseek` 个 profile者共享 Codex CLI app-server stdio 协议,只通过 profile/config/SecretRef 隔离上游和模型。
Codex stdio backend 是 AgentRun `v0.1` 的第一真实 Code Agent backend kind。它用于证明 runner、backend adapter、Kubernetes Secret projection、真实 provider 调用、event normalization 和 terminal status 的完整闭环。`v0.1` 在同一个 backend kind 下支持 `codex``deepseek` `minimax-m3`个 profile者共享 Codex CLI app-server stdio 协议,只通过 profile/config/SecretRef 隔离上游和模型。
## 在系统中的职责划分
- 作为 `backendProfile=codex``backendProfile=deepseek` 的共同具体实现。
- 作为 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3` 的共同具体实现。
- 使用真实 Codex/Codex-compatible 配置执行最短 turn,不使用 fake provider 作为综合联调通过证据。
- 消费 Kubernetes Secret projection 提供的 profile 专属 Codex `auth.json``config.toml`
- 把 Codex 输出归一化为 AgentRun 标准 events 和 terminal status。
@@ -41,15 +41,16 @@ Adapter 通过 stdin 写入换行分隔 JSON-RPC 请求,通过 stdout 逐行
| --- | --- | --- | --- |
| `codex` | `agentrun-v01-provider-codex` | operator 当前 Codex `auth.json`/`config.toml` | 现有默认 profile;实现 DeepSeek 时不得改变其默认模型、config authority 或真实联调路径。 |
| `deepseek` | `agentrun-v01-provider-deepseek` | operator 准备的 DeepSeek-compatible Codex `auth.json`/`config.toml` | 使用同一 `codex app-server --listen stdio://` 协议,通过 `config.toml` 或等价 profile overlay 指向 DeepSeek-compatible upstream/model。 |
| `minimax-m3` | `agentrun-v01-provider-minimax-m3` | 从 HWLAB Code Queue 现有 MiniMax API key 派生的 MiniMax-M3 Codex `auth.json`/`config.toml` | 沿 DeepSeek 相同路径使用 `codex app-server --listen stdio://``config.toml` 指向 MiniMax OpenAI-compatible upstream,模型为 `MiniMax-M3`。 |
`deepseek` 的上游形态借鉴 HWLAB v0.2DeepSeek 是 provider profile,通过 Responses-compatible bridge、Moon Bridge 或等价稳定服务暴露给 Codex CLIAgentRun 不在 backend adapter 里手写 DeepSeek HTTP 转换器,也不把 DeepSeek 作为绕过 Codex app-server 的独立 backend kind。上游 base URL、模型和 provider 名称可以作为 redacted metadata 输出;API Key 和 `auth.json`/`config.toml` 原文不得输出。
`deepseek` 的上游形态借鉴 HWLAB v0.2DeepSeek 是 provider profile,通过 Responses-compatible bridge、Moon Bridge 或等价稳定服务暴露给 Codex CLIAgentRun 不在 backend adapter 里手写 DeepSeek HTTP 转换器,也不把 DeepSeek 作为绕过 Codex app-server 的独立 backend kind。`minimax-m3` 也遵循同一原则:MiniMax-M3 是 Codex-compatible provider profile,不恢复旧 UniDesk Code Queue 的 MiniMax/OpenCode 直连路线,不新增独立 HTTP backend,不作为 fallback 或 judge backend。上游 base URL、模型和 provider 名称可以作为 redacted metadata 输出;API Key 和 `auth.json`/`config.toml` 原文不得输出。
Profile 切换规则:
- `backendProfile` 是 run 的显式字段,manager 不得静默改写。
- runner/backend 只读取与 `backendProfile` 同名的 provider credential;缺失则 `secret-unavailable`
- 每次 run 必须使用 profile-scoped writable `CODEX_HOME`。Kubernetes Job 默认把选中 profile 的 Secret projection 复制到该 Job 独占的 `/home/agentrun/.codex-<profile>`host process 或复用进程必须使用 run/profile 独占目录,避免 `codex``deepseek` 互相污染。
- `deepseek` 不得 fallback 到 `codex` Secret、模型或 upstream`codex` 也不得读取 `deepseek` Secret。
- 每次 run 必须使用 profile-scoped writable `CODEX_HOME`。Kubernetes Job 默认把选中 profile 的 Secret projection 复制到该 Job 独占的 `/home/agentrun/.codex-<profile>`host process 或复用进程必须使用 run/profile 独占目录,避免 `codex``deepseek``minimax-m3` 互相污染。
- `deepseek``minimax-m3` 不得 fallback 到 `codex` Secret、模型或 upstream`codex` 也不得读取其他 profile Secret。
- command payload 中显式提供 model 时可以透传给 Codex turn;未显式提供时以 profile `config.toml` 为 authority,不在 adapter 中写死默认模型。
## 测试凭据来源
@@ -61,14 +62,14 @@ Profile 切换规则:
~/.codex/config.toml
```
这两个文件只能作为 Kubernetes Secret 创建或轮换的输入源,不能通过 hostPath 挂载进 Pod,不能复制进镜像,不能提交到 source branch、GitOps branch、artifact catalog、issue、PR、event、trace、日志或 CLI 输出。`codex``deepseek` 可以来自不同 operator profile 目录或显式文件参数,但进入 Kubernetes 后必须是不同 SecretRef,除非后续规格明确批准某个共享 SecretRef 场景。
这两个文件只能作为 Kubernetes Secret 创建或轮换的输入源,不能通过 hostPath 挂载进 Pod,不能复制进镜像,不能提交到 source branch、GitOps branch、artifact catalog、issue、PR、event、trace、日志或 CLI 输出。`codex``deepseek``minimax-m3` 可以来自不同 operator profile 目录或显式文件参数,但进入 Kubernetes 后必须是不同 SecretRef,除非后续规格明确批准某个共享 SecretRef 场景。`minimax-m3` 的 API key 输入源为 HWLAB Code Queue 现有 MiniMax secret;迁移时只允许把值写入 Kubernetes Secret,不得打印或落库。
`v0.1` 默认 Kubernetes Secret
| 对象 | v0.1 规格 |
| --- | --- |
| Namespace | `agentrun-v01` |
| Secret name | `agentrun-v01-provider-codex``agentrun-v01-provider-deepseek` |
| Secret name | `agentrun-v01-provider-codex``agentrun-v01-provider-deepseek``agentrun-v01-provider-minimax-m3` |
| Secret key | `auth.json`,来自 `~/.codex/auth.json` |
| Secret key | `config.toml`,来自 `~/.codex/config.toml` |
| Consumer | runner 或 backend adapter Pod |
@@ -105,6 +106,10 @@ Run 的 `executionPolicy.secretScope` 应引用与 `backendProfile` 匹配的 pr
阅读本文、HWLAB v0.2 DeepSeek profile 参考和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `backendProfile=deepseek` 创建真实 run 并提交一个最短 `turn` command。确认 runner 仍调用 `codex app-server --listen stdio://`,但使用 `agentrun-v01-provider-deepseek` 的 profile SecretRef 和独立 `CODEX_HOME`manager 可查询 profile 为 `deepseek` 的 backend_status、assistant 或 error event、terminal_status,且 Secret value 未泄露。
### T2c 真实 MiniMax-M3 profile 最短 turn
阅读本文、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `backendProfile=minimax-m3` 创建真实 run 并提交一个最短 `turn` command。确认 runner 仍调用 `codex app-server --listen stdio://`,但使用 `agentrun-v01-provider-minimax-m3` 的 profile SecretRef 和独立 `CODEX_HOME`manager 可查询 profile 为 `minimax-m3` 的 backend_status、assistant 或 error event、terminal_status,且 Secret value 未泄露。
### T3 Missing auth/config failure
阅读本文,然后分别移除或改名 Secret 中的 `auth.json``config.toml` key,启动真实 run。确认 adapter 在调用 provider 前失败为 `secret-unavailable`failure response 为 JSON,日志不包含 Secret value。
@@ -119,7 +124,7 @@ Run 的 `executionPolicy.secretScope` 应引用与 `backendProfile` 匹配的 pr
### T6 Profile switching isolation
阅读本文,然后在真实 `agentrun-v01` 运行面按顺序执行 `backendProfile=codex``backendProfile=deepseek``backendProfile=codex` 个最短 turn。确认第二个 run 使用 DeepSeek profile,前后两个 `codex` run 仍使用原 Codex profile者的 event、log、backend_status、model/upstream metadata 和 failureKind 不互相污染,且任何一个 profile SecretRef 缺失都不会 fallback 到另一个 profile。
阅读本文,然后在真实 `agentrun-v01` 运行面按顺序执行 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3``backendProfile=codex` 个最短 turn。确认第二个 run 使用 DeepSeek profile第三个 run 使用 MiniMax-M3 profile前后两个 `codex` run 仍使用原 Codex profile者的 event、log、backend_status、model/upstream metadata 和 failureKind 不互相污染,且任何一个 profile SecretRef 缺失都不会 fallback 到另一个 profile。
## 规格的实现情况
@@ -131,4 +136,5 @@ Run 的 `executionPolicy.secretScope` 应引用与 `backendProfile` 匹配的 pr
| 错误可观测与脱敏 | 已实现主路径 | child env、cwd、workspace 和 Codex home 只输出摘要;stderr tail 有界且标记截断;事件和 failure 统一走 redaction。 |
| 真实 provider turn | 已通过主闭环 | 真实 Codex provider turn 已经通过 RESTful API 和 CLI 综合联调;每次发布仍按 [spec-v01-validation.md](spec-v01-validation.md) 手动复验。 |
| `deepseek` profile | 已实现/已通过主闭环 | 代码已支持 `agentrun-v01-provider-deepseek`、独立 `CODEX_HOME`、同一 `codex app-server --listen stdio://` 协议和 profile metadata;真实 Kubernetes SecretRef、runner Job 和 Codex stdio turn 已通过主闭环。 |
| `minimax-m3` profile | 已实现/待真实主闭环 | 代码已支持 `agentrun-v01-provider-minimax-m3`、独立 `CODEX_HOME`、同一 `codex app-server --listen stdio://` 协议和 profile metadata;真实 Kubernetes SecretRef、runner Job 和 Codex stdio turn 需要完成 AgentRun CLI 手动验收。 |
| hostPath `~/.codex` | 不采用 | 只能通过 Kubernetes Secret projection 注入。 |
+1 -1
View File
@@ -143,7 +143,7 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
- `argocd/agentrun-v01` AppProject destination 只能包含 `agentrun-v01`
- `argocd/agentrun-g14-v01` source 必须指向 `v0.1-gitops:deploy/gitops/g14/runtime-v01`destination 必须是 `agentrun-v01`
- `v0.1` Secret、ServiceAccount、RBAC、PVC、ConfigMap 和 runtime config 必须独立命名或 namespace scope;文档、issue、trace 和 report 只记录 SecretRef 名称与 key,不记录值。
- `agentrun-mgr` 和 runner Job 只能通过 `spec-v01-secret-distribution.md` 定义的 SecretRef 注入 Postgres DSN 和 Codex stdio profile auth/config 文件;`codex` 默认 SecretRef 为 `agentrun-v01-provider-codex``deepseek` 默认 SecretRef 为 `agentrun-v01-provider-deepseek`。测试凭据来自 profile 专属 `auth.json``config.toml` 的 Kubernetes Secret projection,不得从 `deploy/deploy.json`、artifact catalog 或 generated manifest 中读取明文。
- `agentrun-mgr` 和 runner Job 只能通过 `spec-v01-secret-distribution.md` 定义的 SecretRef 注入 Postgres DSN 和 Codex stdio profile auth/config 文件;`codex` 默认 SecretRef 为 `agentrun-v01-provider-codex``deepseek` 默认 SecretRef 为 `agentrun-v01-provider-deepseek``minimax-m3` 默认 SecretRef 为 `agentrun-v01-provider-minimax-m3`。测试凭据来自 profile 专属 `auth.json``config.toml` 的 Kubernetes Secret projection,不得从 `deploy/deploy.json`、artifact catalog 或 generated manifest 中读取明文。
- Postgres `DATABASE_URL` Secret 必须使用实际创建的数据库名,v0.1 默认为 `agentrun_v01`;密码或其他 URL credential 必须 URL encode 后写入 DSN。Secret 值不进入 source/GitOpsruntime bootstrap 或 secret-management 流程负责创建与轮换。
- Codex provider Secret 在 GitOps manifest 中只能表现为 SecretRef 和只读 volume projectionrunner Job manifest 还必须包含 writable runtime home,用于把 Secret projection 复制到 `CODEX_HOME` 后运行 Codex。
- `agentrun_dev``agentrun_prod` 不得作为 `v0.1` namespace、Argo destination、Pipeline target 或验收目标。
+5 -4
View File
@@ -48,7 +48,7 @@ CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交
./scripts/agentrun runner job --dry-run --run-id <runId> --command-id <commandId> --image <image>
./scripts/agentrun runner jobs --run-id <runId> [--command-id <commandId>]
./scripts/agentrun runner job-status [runnerJobId] --run-id <runId>
./scripts/agentrun secrets codex render --dry-run [--profile codex|deepseek] [--codex-home <dir>]
./scripts/agentrun secrets codex render --dry-run [--profile codex|deepseek|minimax-m3] [--codex-home <dir>]
./scripts/agentrun backends list
./scripts/agentrun server start [--port <port>] [--host <host>] [--foreground]
./scripts/agentrun server status [--port <port>]
@@ -80,8 +80,8 @@ CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交
- `server status` 必须同时返回本地 pid/port/logPath 状态和 `/health/readiness` 结果;即使 readiness 失败,也要输出结构化 JSON 和 failure details。
- `server logs` 必须返回有界日志尾部、bytes、truncated 和 logPath;找不到日志文件时也必须返回非空 JSON。
- `server stop` 必须按 pidFile 与端口进程清理本地 manager,并返回 before/after 状态;不得要求人工用 `ps/kill/ss` 组合命令清理常见临时服务。
- `secrets codex render --dry-run` 返回 Codex stdio profile Secret 创建计划、输入文件 bytes/hash、SecretRef、manifest 摘要和 apply 命令形状;`--profile codex` 默认 Secret name 为 `agentrun-v01-provider-codex``--profile deepseek` 默认 Secret name 为 `agentrun-v01-provider-deepseek`;它不得输出 Secret value 或执行 Kubernetes 写操作。
- `backends list` 必须显示 `codex``deepseek` profile 的 backendKind、protocol、transport、command、requiredSecretKeys 和状态;不得因为 `deepseek` 尚未配置 Secret 就隐藏 capability。
- `secrets codex render --dry-run` 返回 Codex stdio profile Secret 创建计划、输入文件 bytes/hash、SecretRef、manifest 摘要和 apply 命令形状;`--profile codex` 默认 Secret name 为 `agentrun-v01-provider-codex``--profile deepseek` 默认 Secret name 为 `agentrun-v01-provider-deepseek``--profile minimax-m3` 默认 Secret name 为 `agentrun-v01-provider-minimax-m3`;它不得输出 Secret value 或执行 Kubernetes 写操作。
- `backends list` 必须显示 `codex``deepseek``minimax-m3` profile 的 backendKind、protocol、transport、command、requiredSecretKeys 和状态;不得因为某个 provider Secret 尚未配置就隐藏 capability。
- `queue dispatch` 是 Q2 的受控手动调度入口,只对单个 task 显式创建 attempt 和 Core run/command/runner job;不得伪装成自动 scheduler。
- `queue refresh` 只根据 Queue task 中保存的 Core run/command 引用回写 Queue attempt 状态,不读取 Core trace 反推 commander 或统计。
- `queue show` 必须返回 task/attempt summary、state、read cursor、stats 相关字段和 `sessionPath`;不得返回或代理完整 output/trace。
@@ -115,7 +115,7 @@ CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交
### T5 Backend profile CLI 切换
阅读本文、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后用正式 CLI 分别创建 `backendProfile=codex``backendProfile=deepseek` 的 run,按 `codex -> deepseek -> codex` 顺序执行真实 runner。确认 CLI 输出非空 JSONbackend_status 显示正确 profile/backendKind/protocol,缺失 `deepseek` SecretRef 时返回 `secret-unavailable`,不会 fallback 到 `codex`
阅读本文、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 [spec-v01-validation.md](spec-v01-validation.md),然后用正式 CLI 分别创建 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3` 的 run,按 `codex -> deepseek -> minimax-m3 -> codex` 顺序执行真实 runner。确认 CLI 输出非空 JSONbackend_status 显示正确 profile/backendKind/protocol,缺失对应 profile SecretRef 时返回 `secret-unavailable`,不会 fallback 到 `codex` 或其他 profile
### T6 Queue 与 Session CLI 分层
@@ -137,3 +137,4 @@ CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交
| Session CLI | 待实现 | 规格见 [spec-v01-queue.md](spec-v01-queue.md);输出和 trace 进入 Session 命令,Queue 命令不得代理 output/trace。 |
| CLI 测试规格 | 已定义/已验证主闭环 | 综合联调见 [spec-v01-validation.md](spec-v01-validation.md);每次发布仍按手动交互验收复跑。 |
| `deepseek` profile CLI | 已实现/已通过主闭环 | `secrets codex render --profile deepseek``backends list``runner start --backend``runner job` 和 JSON 错误可见性已实现;真实 CLI/RESTful 联调已通过 `codex -> deepseek -> codex` 切换主闭环。 |
| `minimax-m3` profile CLI | 已实现/待真实主闭环 | `secrets codex render --profile minimax-m3``backends list``runner start --backend``runner job` 和 JSON 错误可见性已实现;真实 CLI/RESTful 联调需要按 `codex -> deepseek -> minimax-m3 -> codex` 手动验收。 |
@@ -36,7 +36,7 @@
- `spec-v01-agentrun-mgr.md`manager REST API、tenant boundary、runner claim、event/status authority。
- `spec-v01-agentrun-runner.md`:短生命周期 runner、claim/poll/report、日志和 failureKind。
- `spec-v01-backend-adapter.md`backend adapter 合同、event normalization、failure mapping 和 redaction。
- `spec-v01-backend-codex.md`:第一真实 Codex app-server stdio backend、`codex`/`deepseek` profile、`~/.codex` 测试凭据 Secret projection 和真实 turn 验收。
- `spec-v01-backend-codex.md`:第一真实 Codex app-server stdio backend、`codex`/`deepseek`/`minimax-m3` profile、`~/.codex` 测试凭据 Secret projection 和真实 turn 验收。
- `spec-v01-cli.md`AgentRun CLI 命令族、JSON 输出、短返回、日志可见和测试规格。
- `spec-v01-scheduler.md`:自动 scheduler 的 deferred 边界。
- 未来新增单服务规格必须使用 `spec-v01-<service-or-capability>.md`
@@ -82,7 +82,7 @@ HWLAB canary 创建 run 时应使用以下字段口径:
| `tenantId` | `hwlab`。 |
| `projectId` | `pikasTech/HWLAB`。 |
| `providerId` | `G14`,只表示目标 provider,不授予 HWLAB 业务权限。 |
| `backendProfile` | `deepseek``codex`,由 HWLAB 显式选择;缺少 matching SecretRef 必须失败,不 fallback。 |
| `backendProfile` | `deepseek``codex``minimax-m3`,由 HWLAB 或调度方显式选择;缺少 matching SecretRef 必须失败,不 fallback。 |
| `workspaceRef` | 必须引用 ResourceBundleRef 中的 Git-only full commit;不得由 runner 猜 host path。 |
| `executionPolicy` | sandbox、network、timeout、secretScope 必须显式,不得由 HWLAB 扩大 AgentRun Secret 范围。 |
| `traceSink` | 可指向 HWLAB trace adapter;为 `null` 时 HWLAB 仍可通过 AgentRun events 轮询。 |
+3 -3
View File
@@ -9,7 +9,7 @@
- 第一版只做 RESTful API、CLI 和轻量轮询,不做 Event System、OA Event Flow、OA sink、GitHub sink、notification sink 或 integrations 表。
- Queue 和 Session 分层:Queue 查询返回 `sessionPath`;输出、trace、debug 和会话控制统一转到 Session API/CLI。
- Queue 列表、统计、已读状态和 commander 视图必须直接查询 Queue summary/stats/read 模型,不从 Core execution trace 或 Session trace 反推。
- 废弃 MiniMaxOpenCode 支持。第一版只保留 Codex/Codex-compatible backend profile,例如现有 `codex``deepseek` profile。
- 废弃 MiniMax/OpenCode 直连支持。第一版只保留 Codex/Codex-compatible backend profile,例如现有 `codex``deepseek``minimax-m3` profile。
- 不吸收 UniDesk 环境提示。执行上下文必须显式来自 workspace/resource/profile/tool/SecretRef 等 AgentRun 合同,不能继承旧 Code Queue 的隐式 env hint。
## 职责边界
@@ -121,7 +121,7 @@ Queue 首版新增或扩展的稳定表方向:
| attempt | `agentrun_queue_attempts` + Core run/command/runner job | attempt 引用真实执行资源 | 旧 attempt 输出结构 |
| `processQueue` / `runTask` | AgentRun Scheduler | 由 Scheduler 扫描 pending task/attempt 并创建 runner job | UniDesk Code Queue scheduler |
| `runCodexTurn` | Codex app-server stdio backend | 复用协议和 failure 分类经验 | 环境专用路径和明文凭据 |
| MiniMax/OpenCode 执行 | 无 | 废弃 | fallback、兼容配置、judge backend |
| MiniMax/OpenCode 直连执行 | 无 | 废弃 | fallback、兼容配置、judge backend |
| activeRuns / active slot | Scheduler capacity 和 lease | 合并进 AgentRun 调度/lease 模型 | 旧内存状态 authority |
| judge | `agentrun_queue_judge_runs` | 使用 Codex-compatible judge profile 或规则 judge | MiniMax judge |
| prompt history / steer / resume / cancel | Queue audit + Session/Core control | Queue 记录任务级控制意图,执行细节走 Session/Core | 直接向旧 runner 写进程状态 |
@@ -165,7 +165,7 @@ Queue Q2 的真实手动验收必须覆盖以下稳定边界:
- `queue show` 返回 `sessionPath`;输出、trace 和会话控制只能通过 Session API/CLI 完成。
- Queue overview、stats、read cursor 和 commander 视图来自 Queue summary/stats/read 表或等价 Queue 查询,不读取 Core trace 反推。
- 不存在 OA/Event/OA sink/GitHub/notification/integrations 首版表和首版必需流程。
- MiniMax/OpenCode 相关入口、配置、fallback 和 judge 路线不作为 AgentRun Queue 首版能力。
- MiniMax/OpenCode 直连入口、配置、fallback 和 judge 路线不作为 AgentRun Queue 首版能力MiniMax-M3 只能作为 Codex-compatible `minimax-m3` profile 使用
- 旧 UniDesk Code Queue 不接收新任务;历史数据不迁移到 AgentRun。
- CLI、API、日志、summary、trace 和 Session 输出不泄露 Secret value、token、DSN password 或 URL credential。
+7 -6
View File
@@ -42,7 +42,7 @@ P0 最小 JSON 形态:
| credential 类别 | 装配归属 | 运行时投影 | 规则 |
| --- | --- | --- | --- |
| Provider credential | `ProfileRef` / `executionPolicy.secretScope.providerCredentials[]` | profile-scoped 只读 Secret projection,再复制到 per-run writable `CODEX_HOME` | 只服务 `codex`/`deepseek` backend profile;缺失为 `secret-unavailable`,不得 fallback。 |
| Provider credential | `ProfileRef` / `executionPolicy.secretScope.providerCredentials[]` | profile-scoped 只读 Secret projection,再复制到 per-run writable `CODEX_HOME` | 只服务 `codex`/`deepseek`/`minimax-m3` backend profile;缺失为 `secret-unavailable`,不得 fallback。 |
| Git resource credential | `ResourceBundleRef.credentialRef` | 只服务 resource materialization 的 Git fetch/checkout | 只能用于拉取 `ResourceBundleRef.repoUrl` 对应代码,不得暴露给 agent shell 作为通用 GitHub token。 |
| Tool credential | `executionPolicy.secretScope.toolCredentials[]` | 由 runner 按 tool scope 投影为文件或 env,并只暴露给当前 run/command 允许的工具 | 用于 GitHub PR、issue、artifact registry 等 agent shell 工具能力;不等同于 AgentRun integration,不触发 GitHub sink/OA/Event 之类外部动作记录。 |
| Short-lived execution context | runner-job `transientEnv` | 单次 Job envresponse/dry-run/event 只显示 name/hash | 只用于业务 dispatcher 生成的短期上下文,例如 HWLAB device-pod session token;不得承载 provider credential、GitHub token、长期 SSH key 或可复用 API key。 |
@@ -81,7 +81,7 @@ HWLAB v0.2 原有 Code Agent 已经验证了 profile、session、workspace 和 S
| HWLAB v0.2 基线能力 | HWLAB 参考入口 | RuntimeAssembly 承接字段 | 承接规则 |
| --- | --- | --- | --- |
| provider profile 可切换 | `internal/cloud/code-agent-contract.ts` | `ProfileRef.profile``ProfileRef.secretRef` | `deepseek``codex` 只选择 profile/config/SecretRef,不复制 backend 协议;缺失 Secret 必须失败,不 fallback。 |
| provider profile 可切换 | `internal/cloud/code-agent-contract.ts` | `ProfileRef.profile``ProfileRef.secretRef` | `deepseek``minimax-m3``codex` 只选择 profile/config/SecretRef,不复制 backend 协议;缺失 Secret 必须失败,不 fallback。 |
| Codex app-server thread 复用 | `internal/cloud/codex-stdio-session.ts``internal/cloud/code-agent-session-registry.ts` | `SessionRef.sessionId``conversationId``threadId` | AgentRun 保存 backend thread/session 摘要;不保存 API KEY、`auth.json``config.toml` 或完整 `CODEX_HOME`。 |
| 固定 `/workspace/hwlab` 代码上下文 | `internal/cloud/code-agent-contract.ts` | `ResourceBundleRef.repoUrl``commitId` | 用 Git-only full commit 取代 HWLAB Pod 内固定路径;runner checkout 到隔离 workspace。 |
| writable `CODEX_HOME` 与 Secret 投影分离 | `docs/reference/code-agent-chat-readiness.md` | `ProfileRef` + runner runtime home | Secret 只读投影,复制到当前 run/profile writable runtime homeprofile 间不共享。 |
@@ -98,7 +98,7 @@ HWLAB v0.2 原有 Code Agent 已经验证了 profile、session、workspace 和 S
### ProfileRef
- `profile` 在 v0.1 只允许 `codex``deepseek`
- `profile` 在 v0.1 只允许 `codex``deepseek``minimax-m3`
- `secretRef` 只保存 Secret 名称和 key,不保存值。
- 当前 profile 只能读取当前 profile 的 SecretRef;缺失必须 `secret-unavailable`,不能 fallback 到另一个 profile。
- profile Secret 只读投影,backend 需要可写目录时复制到 per-run/profile runtime home。
@@ -145,8 +145,9 @@ HWLAB v0.2 原有 Code Agent 已经验证了 profile、session、workspace 和 S
- `codex` run 只挂载 `agentrun-v01-provider-codex`
- `deepseek` run 只挂载 `agentrun-v01-provider-deepseek`
- `codex -> deepseek -> codex` 切换后,`CODEX_HOME`、SecretRef、backend_status 不互相污染
- 删除或缺失 `deepseek` SecretRef 时必须 `secret-unavailable`,不能 fallback 到 `codex`
- `minimax-m3` run 只挂载 `agentrun-v01-provider-minimax-m3`
- `codex -> deepseek -> minimax-m3 -> codex` 切换后,`CODEX_HOME`、SecretRef、backend_status 不互相污染
- 删除或缺失 `deepseek`/`minimax-m3` SecretRef 时必须 `secret-unavailable`,不能 fallback 到 `codex`
- 所有输出不得包含 Secret value、`auth.json``config.toml` 明文。
### A2b Tool credential 验收
@@ -184,7 +185,7 @@ HWLAB v0.2 原有 Code Agent 已经验证了 profile、session、workspace 和 S
| 要素 | v0.1 状态 | 说明 |
| --- | --- | --- |
| `BackendImageRef` | 部分实现 | CI/CD 已使用 digest-pinned runtime image;当前 runner/backend 仍复用 agentrun 镜像。 |
| `ProfileRef` | 已实现/已通过主闭环 | `codex``deepseek` 已通过 SecretRef、writable runtime home 和真实 stdio turn 验证。 |
| `ProfileRef` | 已实现/待 MiniMax-M3 主闭环 | `codex``deepseek` 已通过 SecretRef、writable runtime home 和真实 stdio turn 验证`minimax-m3` 已进入 profile/SecretRef 装配,需要完成真实 CLI 手动验收。 |
| `SessionRef` | 已实现最小持久化 | manager 持久化 `sessionId/conversationId/threadId`run 创建会解析既有 sessionrunner 按 threadId resumesession 不保存 credential 文件,TTL/GC 后续细化。 |
| `ResourceBundleRef` | 已实现 Git-only materialization | `repoUrl + full commitId` 已进入 run schema 和 runner checkoutworkspace 受 `AGENTRUN_WORKSPACE_ROOT` 限制,event/result 记录 commit/tree/workspace 摘要。 |
| `toolCredentials` | 已实现最小 env projection | GitHub PR 等 agent shell/tool 授权通过装配 SPEC 的 SecretRef 进入 runnerv0.1 先支持 `tool=github``projection.kind=env`runner Job 使用 `valueFrom.secretKeyRef` 注入,不用 `transientEnv` 绕过。 |
+19 -8
View File
@@ -17,7 +17,7 @@
| Secret 类别 | 用途 | 默认消费者 | v0.1 规则 |
| --- | --- | --- | --- |
| Postgres DSN | manager 连接 durable store | `agentrun-mgr` | 只通过 `agentrun-v01-mgr-db/DATABASE_URL` 注入。 |
| Codex stdio profile 凭据文件 | 真实 Code Agent backend 调上游模型 | runner 或 backend adapter | `codex``deepseek` 均使用 `auth.json`/`config.toml` 文件形态,只通过 profile-scoped Kubernetes SecretRef 文件投影注入,不写入 run payload。 |
| Codex stdio profile 凭据文件 | 真实 Code Agent backend 调上游模型 | runner 或 backend adapter | `codex``deepseek``minimax-m3` 均使用 `auth.json`/`config.toml` 文件形态,只通过 profile-scoped Kubernetes SecretRef 文件投影注入,不写入 run payload。 |
| Git SSH deploy key | Tekton checkout source/GitOps promotionArgo 读取 GitOps branch | Tekton、Argo CD | 只存在于 `agentrun-ci``argocd` Secret;不进入 runtime Pod。 |
| Registry credential | push/pull private registry | Tekton、runtime imagePullSecret | 只作为 ServiceAccount/imagePullSecret 引用。 |
| Tool credential | GitHub PR、issue、artifact registry 等 agent shell/tool 授权 | runner/backend adapter | 必须通过 `executionPolicy.secretScope.toolCredentials[]` 的 SecretRef 装配进入运行时;不是 Queue integration,也不能用 `transientEnv` 承载长期 credential。 |
@@ -30,6 +30,7 @@
| Manager DB Secret | `agentrun-v01-mgr-db` key `DATABASE_URL` |
| Codex Provider Secret | `agentrun-v01-provider-codex` keys `auth.json``config.toml` |
| DeepSeek Provider Secret | `agentrun-v01-provider-deepseek` keys `auth.json``config.toml` |
| MiniMax-M3 Provider Secret | `agentrun-v01-provider-minimax-m3` keys `auth.json``config.toml` |
| Provider projection target | 只读 `/var/run/agentrun/secrets/<profile>-<index>/auth.json``config.toml`,再复制到当前 run/profile 的 writable `CODEX_HOME` |
| Provider config | 非敏感 base URL/model 可以来自 `config.toml` 或 ConfigMapcredential value 不得放入 ConfigMap。 |
| Tekton Git SSH Secret | `agentrun-ci/agentrun-git-ssh` |
@@ -47,13 +48,13 @@
~/.codex/config.toml
```
这两个文件只能作为 Kubernetes Secret 创建或轮换的输入源。`codex` profile 默认使用 operator 当前 Codex 配置;`deepseek` profile 使用 operator 准备的 DeepSeek-compatible Codex 配置,可以来自另一个 `--codex-home` 或显式 `--auth-file`/`--config-file`。禁止把宿主机 `~/.codex` 以 hostPath 挂入 runner/backend Pod,禁止复制进镜像,禁止提交到 source branch、GitOps branch、artifact catalog、issue、PR、event、trace、日志或 CLI 输出。
这两个文件只能作为 Kubernetes Secret 创建或轮换的输入源。`codex` profile 默认使用 operator 当前 Codex 配置;`deepseek` profile 使用 operator 准备的 DeepSeek-compatible Codex 配置,可以来自另一个 `--codex-home` 或显式 `--auth-file`/`--config-file``minimax-m3` profile 使用从 HWLAB Code Queue 现有 MiniMax API key 派生的 Codex 配置,模型固定为 `MiniMax-M3`。禁止把宿主机 `~/.codex` 以 hostPath 挂入 runner/backend Pod,禁止复制进镜像,禁止提交到 source branch、GitOps branch、artifact catalog、issue、PR、event、trace、日志或 CLI 输出。
默认 Secret projection 规则:
| 项目 | v0.1 规格 |
| --- | --- |
| Kubernetes Secret | `agentrun-v01/agentrun-v01-provider-codex``agentrun-v01/agentrun-v01-provider-deepseek` |
| Kubernetes Secret | `agentrun-v01/agentrun-v01-provider-codex``agentrun-v01/agentrun-v01-provider-deepseek``agentrun-v01/agentrun-v01-provider-minimax-m3` |
| Secret key | `auth.json`,来自 `~/.codex/auth.json` |
| Secret key | `config.toml`,来自 `~/.codex/config.toml` |
| Projection path | 只读 Secret projection 挂到 `/var/run/agentrun/secrets/<profile>-<index>/auth.json``config.toml`;该路径只作为 credential source。 |
@@ -87,6 +88,15 @@ Run 的 `executionPolicy.secretScope` 只能包含引用,不包含值。provid
"keys": ["auth.json", "config.toml"],
"mountPath": "~/.codex"
}
},
{
"profile": "minimax-m3",
"secretRef": {
"namespace": "agentrun-v01",
"name": "agentrun-v01-provider-minimax-m3",
"keys": ["auth.json", "config.toml"],
"mountPath": "~/.codex"
}
}
],
"allowCredentialEcho": false
@@ -99,7 +109,7 @@ Run 的 `executionPolicy.secretScope` 只能包含引用,不包含值。provid
- `secretRef.namespace` 默认只能是 run 所在 lane namespace 或明确批准的 platform namespace。
- manager 可以保存 `secretRef`,但不得读取 Secret 值后存库。
- runner/backend adapter 获得 Secret 的方式必须来自 Kubernetes env/file projection 或受限 Secret API 读取;Codex 默认从只读 Secret projection 复制 `auth.json``config.toml` 到 writable `CODEX_HOME` 后启动 app-server,不得通过 run payload、event、CLI 参数或日志传递。
- runner/backend adapter 只能选择与 run `backendProfile` 同名的 provider credential`backendProfile=deepseek` 缺少 `deepseek` SecretRef 时必须 `secret-unavailable`,不得 fallback 到 `codex`
- runner/backend adapter 只能选择与 run `backendProfile` 同名的 provider credential`backendProfile=deepseek` `backendProfile=minimax-m3` 缺少 matching SecretRef 时必须 `secret-unavailable`,不得 fallback 到 `codex` 或另一个 profile
- Secret projection 不能直接作为 `CODEX_HOME`。Codex app-server 会读取并可能维护默认配置、PATH 或运行态文件;把只读 Secret volume 直接挂到 `CODEX_HOME` 会造成启动期写入失败。v0.1 的固定边界是:Secret volume 只读、`/home/agentrun``emptyDir` 提供可写 runtime home、复制动作只发生在 runner/backend 容器内且不打印文件内容。
- SecretRef 不存在或 RBAC 不允许时,run 必须失败为结构化 `failureKind=secret-unavailable` 或等价错误,不得降级成无凭证重试风暴。
- `toolCredentials` 的 SecretRef/projection/redaction 规则以 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md) 为准;本文只约束 Secret value 不落库、不输出、不进入 Git source 或 GitOps Secret data。
@@ -142,16 +152,16 @@ Secret 创建和轮换不由 source branch 自动生成;source branch 只声
`v0.1` 提供只读 CLI 工具,用 operator 本地 `~/.codex/auth.json``~/.codex/config.toml` 形态的文件构造 Kubernetes Secret 创建计划:
```bash
./scripts/agentrun secrets codex render --dry-run [--profile codex|deepseek]
./scripts/agentrun secrets codex render --dry-run [--profile codex|deepseek|minimax-m3]
```
可选参数:
- `--codex-home <dir>`:覆盖默认 `~/.codex` 输入目录。
- `--profile <name>`:默认 `codex``deepseek` 使用同一文件形态但默认 Secret name 为 `agentrun-v01-provider-deepseek`
- `--profile <name>`:默认 `codex``deepseek``minimax-m3` 使用同一文件形态但默认 Secret name 分别`agentrun-v01-provider-deepseek``agentrun-v01-provider-minimax-m3`
- `--auth-file <path>` / `--config-file <path>`:分别覆盖输入文件路径。
- `--namespace <name>`:默认 `agentrun-v01`
- `--secret-name <name>`:默认随 profile 变化,`codex``agentrun-v01-provider-codex``deepseek``agentrun-v01-provider-deepseek`
- `--secret-name <name>`:默认随 profile 变化,`codex``agentrun-v01-provider-codex``deepseek``agentrun-v01-provider-deepseek``minimax-m3``agentrun-v01-provider-minimax-m3`
输出必须是 JSON,并且只包含 `namespace``secretName``keys`、每个输入文件的 `bytes``sha256`/`contentHash`、整体 hash、redaction 状态、apply 命令形状和 Secret manifest 摘要。输出不得包含 Secret value、`auth.json` 明文、`config.toml` 明文、base64 `data` 字段或可直接恢复 credential 的内容。工具只支持 `--dry-run`;不得执行 `kubectl apply`
@@ -172,7 +182,7 @@ Secret 创建和轮换不由 source branch 自动生成;source branch 只声
### T2 Runner credential projection
阅读本文,然后分别启动 `backendProfile=codex``backendProfile=deepseek` 的最小 backend runner dry-run,确认 Pod file projection 挂在 `/var/run/agentrun/secrets/...` 且只读,`/home/agentrun` 是 writable runtime homerunner/backend 只把当前 profile 授权文件复制到 `CODEX_HOME` 后再启动 Codex;event、日志和 CLI 输出只显示 redacted credential source,不显示文件内容。
阅读本文,然后分别启动 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3` 的最小 backend runner dry-run,确认 Pod file projection 挂在 `/var/run/agentrun/secrets/...` 且只读,`/home/agentrun` 是 writable runtime homerunner/backend 只把当前 profile 授权文件复制到 `CODEX_HOME` 后再启动 Codex;event、日志和 CLI 输出只显示 redacted credential source,不显示文件内容。
### T3 Missing secret failure
@@ -187,6 +197,7 @@ Secret 创建和轮换不由 source branch 自动生成;source branch 只声
| Codex Secret dry-run 工具 | 已实现 | `./scripts/agentrun secrets codex render --dry-run` 只输出 Secret 创建计划、hash 和 redacted manifest 摘要,不执行 apply。 |
| Codex auth/config file projection | 已实现主路径 | backend readiness 检查 `auth.json`/`config.toml` 可读性,缺失时返回 `secret-unavailable`;真实 runner Job 将只读 projection 复制到 writable `CODEX_HOME`。 |
| DeepSeek profile SecretRef | 已实现/已通过主闭环 | 已新增 `agentrun-v01-provider-deepseek` render、GitOps/RBAC 引用、Job projection、profile 选择和负向 missing-secret 自测试;真实 Secret 创建与 Kubernetes Job projection 已通过主闭环,轮换仍由 Kubernetes 密钥管理流程完成。 |
| MiniMax-M3 profile SecretRef | 已实现/待真实主闭环 | 已新增 `agentrun-v01-provider-minimax-m3` render、GitOps/RBAC 引用、Job projection、profile 选择和负向 missing-secret 自测试;真实 Secret 创建使用 HWLAB Code Queue 现有 MiniMax API key,轮换仍由 Kubernetes 密钥管理流程完成。 |
| Tool credential SecretRef | 已实现最小 env projection | `executionPolicy.secretScope.toolCredentials[]` 已支持 `tool=github``projection.kind=env`runner Job 通过 Kubernetes `secretKeyRef` 注入 envCLI、event、runner job response 和 dry-run 只显示 SecretRef/projection 元数据,不输出值。 |
| redaction 最小规则 | 已实现主路径 | Secret dry-run 工具、event、Job dry-run 输出、self-test 和真实主闭环均不打印 Secret value;复杂审计按 [spec-v01-validation.md](spec-v01-validation.md) 人工抽查。 |
| 外部 secret manager | 未采用 | 如需 Vault/ExternalSecrets/SOPS,后续单独更新规格。 |
+10 -8
View File
@@ -10,7 +10,7 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Code Agent 执行基础设施。`
- `agentrun-mgr` 是公共 RESTful API 和 durable facts authority,负责 run、command、event、runner、backend、lease 的持久状态和鉴权前置边界。
- `agentrun-runner` 是短生命周期 per-run 或 per-attempt 执行者,必须从 manager claim run,并把 event、heartbeat 和 terminal status 写回 manager。
- Backend adapter 隐藏具体 Agent 工具协议,`v0.1` 使用一个真实 Codex stdio backend kind 形成闭环,并在该 kind 下支持 `codex``deepseek` profile;其他 backend kind 不进入第一波实现。
- Backend adapter 隐藏具体 Agent 工具协议,`v0.1` 使用一个真实 Codex stdio backend kind 形成闭环,并在该 kind 下支持 `codex``deepseek``minimax-m3` profile;其他 backend kind 不进入第一波实现。
- AgentRun CLI 是受控操作入口,负责创建 run、提交 command、轮询 events、手动启动 runner 和查看 backend capabilityCLI 不等待完整模型 turn。
- RuntimeAssembly 是 runner/backend 启动前的装配 SPEC,负责把 backend image、profile credential、session、Git-only resource bundle 和 tool credential scope 统一成受控 Job 输入;装配权威规格见 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)。
- AgentRun Queue 是吸收 UniDesk Code Queue 的任务队列层,负责 task、attempt、retry、judge、summary、stats、read cursor 和 commander 聚合;Queue 查询只返回 Session 引用,输出和 trace 进入 Session API/CLI。权威规格见 [spec-v01-queue.md](spec-v01-queue.md)。
@@ -26,22 +26,23 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Code Agent 执行基础设施。`
AgentRun `v0.1` 的自研组件优先使用 Bun + TypeScript 实现:`agentrun-mgr``agentrun-runner`、backend adapter、Codex backend、AgentRun CLI 和后续 scheduler 都属于该边界。官方 TypeScript CLI 入口固定为 `scripts/agentrun-cli.ts`,入口只做参数解析和路由,复杂逻辑拆到 `scripts/src/``src/`G14/CI/人工非交互命令使用 `./scripts/agentrun` launcher 启动同一入口。Postgres、Kubernetes、Tekton、Argo CD、YAML manifest 和 shell 级容器启动命令属于外部运行面或部署面,不受“必须 TypeScript 实现”的约束。
`backendProfile=codex``backendProfile=deepseek``v0.1` 协议固定为同一个 Codex CLI app-server JSON-RPC over stdio backend kindrunner/backend adapter 启动受控 `codex app-server --listen stdio://` 子进程,经 stdin/stdout 发送换行分隔 JSON-RPC,请求顺序至少覆盖 `initialize``thread/start``thread/resume``turn/start``backendProfile` 只选择 profile/config/SecretRef`backendKind``protocol` 和进程生命周期仍是 `codex-app-server-stdio`。直接调用 Responses HTTP、OpenAI SDK、`codex exec` 一次性输出或文本 fallback 只能作为诊断/自测试辅助,不能作为 Codex backend 综合联调通过证据。
`backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3``v0.1` 协议固定为同一个 Codex CLI app-server JSON-RPC over stdio backend kindrunner/backend adapter 启动受控 `codex app-server --listen stdio://` 子进程,经 stdin/stdout 发送换行分隔 JSON-RPC,请求顺序至少覆盖 `initialize``thread/start``thread/resume``turn/start``backendProfile` 只选择 profile/config/SecretRef`backendKind``protocol` 和进程生命周期仍是 `codex-app-server-stdio`。直接调用 Responses HTTP、OpenAI SDK、`codex exec` 一次性输出或文本 fallback 只能作为诊断/自测试辅助,不能作为 Codex backend 综合联调通过证据。
实现参考必须优先读取并吸收两个成熟代码路径:UniDesk Code Queue 的 Bun/TS `src/components/microservices/code-queue/src/code-agent/codex.ts``common.ts`,以及 HWLAB v0.2 的 `internal/cloud/codex-stdio-session.mjs``scripts/code-agent-chat-smoke.mjs``docs/reference/spec-v02-deepseek-proxy.md``docs/reference/code-agent-chat-readiness.md`。AgentRun 复用的是 stdio JSON-RPC、session/turn 生命周期、trace、redaction、Secret projection、profile overlay、DeepSeek/Moon Bridge 分层诊断和 failureKind 经验,不复制 UniDesk/HWLAB 的环境专用路径、业务 prompt、bridge host、namespace 或明文凭据。
## Backend Profile 边界
`v0.1` 需要支持个可手动选择的 backend profile,但不引入完整多 backend 调度:
`v0.1` 需要支持个可手动选择的 backend profile,但不引入完整多 backend 调度:
| backendProfile | backendKind | v0.1 处理 | SecretRef | 说明 |
| --- | --- | --- | --- | --- |
| `codex` | `codex-app-server-stdio` | 保留,P0 | `agentrun-v01-provider-codex` | 现有 GPT/Codex profile,必须保持默认行为不回归。 |
| `deepseek` | `codex-app-server-stdio` | 新增,P0 | `agentrun-v01-provider-deepseek` | DeepSeek-compatible Codex profile;通过 profile 专属 `auth.json`/`config.toml` 或等价 SecretRef 配置上游、模型和 base URL。 |
| `minimax-m3` | `codex-app-server-stdio` | 新增,P0 | `agentrun-v01-provider-minimax-m3` | MiniMax-M3 OpenAI-compatible Codex profile;沿 DeepSeek 相同路径由 `auth.json`/`config.toml` 配置上游、模型和 base URL。 |
完整多 backend 路由仍然 deferred。`v0.1` 只允许 manager/runner 按 run 中的 `backendProfile` 显式选择 `codex``deepseek`,并在 capability 中报告者共享同一个 `protocol=codex-app-server-jsonrpc-stdio``transport=stdio`。MiniMax/OpenCode 废弃,不进入 AgentRun Queue 首版,也不作为 fallback 或 judge backend。
完整多 backend 路由仍然 deferred。`v0.1` 只允许 manager/runner 按 run 中的 `backendProfile` 显式选择 `codex``deepseek``minimax-m3`,并在 capability 中报告者共享同一个 `protocol=codex-app-server-jsonrpc-stdio``transport=stdio`MiniMax/OpenCode 直连路线废弃,不进入 AgentRun Queue 首版,也不作为 fallback 或 judge backend。
`codex``deepseek` 之间不得隐式 fallback:缺少 `deepseek` SecretRef 时必须失败为 `secret-unavailable`,不能改用 `codex` Secret`deepseek` 运行失败也不能重试到 `codex`。同一轮发布的综合联调必须证明 `codex -> deepseek -> codex` 的切换不会污染彼此的 SecretRef、`CODEX_HOME`、模型或 upstream 配置。
`codex``deepseek``minimax-m3` 之间不得隐式 fallback:缺少 `deepseek``minimax-m3` SecretRef 时必须失败为 `secret-unavailable`,不能改用 `codex` Secret任一 profile 运行失败也不能重试到另一个 profile。同一轮发布的综合联调必须证明 `codex -> deepseek -> minimax-m3 -> codex` 的切换不会污染彼此的 SecretRef、`CODEX_HOME`、模型或 upstream 配置。
## 内部架构
@@ -80,7 +81,7 @@ Runner inbound API 只允许本地或私有诊断,不作为业务客户端入
| `agentrun-mgr` | 长驻服务 | 保留,P0 | 公共 RESTful API、durable facts、idempotency、runner claim、event append 和 status authority。 | `spec-v01-agentrun-mgr.md` |
| `agentrun-runner` | 短生命周期执行入口 | 保留,P0 | per-run/per-attempt executorclaim run、poll command、调用 backend、写回 events/status。 | `spec-v01-agentrun-runner.md` |
| Backend adapter | 执行适配层 | 保留,P0 | 统一 backend capability、event normalization、error mapping 和 credential boundary。 | `spec-v01-backend-adapter.md` |
| Codex stdio backend profiles | 具体 Agent backend | 保留,P0 | `v0.1` 使用一个真实 Codex app-server stdio backend kind,必须支持 `codex``deepseek` 个 profile;完整多 backend 路由仍 deferred。 | `spec-v01-backend-codex.md` |
| Codex stdio backend profiles | 具体 Agent backend | 保留,P0 | `v0.1` 使用一个真实 Codex app-server stdio backend kind,必须支持 `codex``deepseek` `minimax-m3`个 profile;完整多 backend 路由仍 deferred。 | `spec-v01-backend-codex.md` |
| AgentRun CLI | CLI/Job 工具 | 保留,P0 | JSON 输出、短返回、run/command/event/runner/backend 操作入口。 | `spec-v01-cli.md` |
| Postgres durable store | 稳定外部服务 | 保留,P0 | 使用 `agentrun-v01-postgres` 保存 runs、commands、events、runners、backends、leases 和 migration ledger;不使用 file/sqlite 作为 v0.1 durable store。 | `spec-v01-postgres.md` |
| Secret distribution | 系统能力 | 保留,P0 | Provider credential 只通过 Kubernetes SecretRef、ServiceAccount/RBAC 和 runner env/file projection 分发;Codex 测试凭据使用 `~/.codex/auth.json``~/.codex/config.toml` 生成 Secret projectionsource、GitOps、logs 和 events 不保存明文。 | `spec-v01-secret-distribution.md` |
@@ -90,7 +91,7 @@ Runner inbound API 只允许本地或私有诊断,不作为业务客户端入
| Observability | 最小事件/日志合同 | 保留,P1 子项 | 作为 manager/runner 的 event、terminal status、failureKind、logPath 和 redaction 最小合同,不拆独立观测系统。 | 并入 `spec-v01-agentrun-mgr.md``spec-v01-agentrun-runner.md` |
| `agentrun-scheduler` | 长驻调度器 | Deferred | M1-M3 稳定后再实现自动 pending scan、capacity selection 和 runner Job 创建。 | `spec-v01-scheduler.md` |
| AgentRun Queue | 系统能力 | 新增,P0 规格 | 直接吸收 UniDesk Code Queue 的队列能力;第一版只做 RESTful API、CLI 和轻量轮询,不做 Event/OA/integrations,不迁移历史数据。 | `spec-v01-queue.md` |
| 多 backend 路由 | 系统能力 | Deferred | `v0.1` 不做跨 backend kind 的自动路由和调度;仅支持同一 Codex stdio backend kind 下的 `codex`/`deepseek` profile 手动选择。MiniMax/OpenCode 不进入 Queue 首版。 | 后续版本 spec |
| 多 backend 路由 | 系统能力 | Deferred | `v0.1` 不做跨 backend kind 的自动路由和调度;仅支持同一 Codex stdio backend kind 下的 `codex`/`deepseek`/`minimax-m3` profile 手动选择。MiniMax/OpenCode 直连路线不进入 Queue 首版。 | 后续版本 spec |
| UI | 前端 | Deferred | `v0.1` 不要求独立 UIUniDesk/HWLAB canary 可通过 CLI/API 验证。 | 后续版本 spec |
| judge/retry 自动化 | 系统能力 | Deferred | `v0.1` 只定义基础 terminal 和 failure visibility,不实现复杂 judge。 | 后续版本 spec |
@@ -134,7 +135,7 @@ Run create 的最小字段合同:
| `projectId` | 必填,例如 `pikasTech/unidesk``pikasTech/HWLAB`。 |
| `workspaceRef` | 必填,描述 source/worktree/workspace,不由 runner 猜测。 |
| `providerId` | 必填,例如 `G14``D601`;只表示目标 provider,不直接授予权限。 |
| `backendProfile` | 必填,`v0.1` allowlist 为 `codex``deepseek`;两者共享 `codex-app-server-stdio` backend kind。 |
| `backendProfile` | 必填,`v0.1` allowlist 为 `codex``deepseek``minimax-m3`;三者共享 `codex-app-server-stdio` backend kind。 |
| `executionPolicy` | 必填或由 manager 显式写入默认值,至少包含 sandbox、approval、timeout、network 和 secret scope。 |
| `traceSink` | 字段必须存在;可以为 `null` 或显式 sink,表示标准事件是否需要镜像给 tenant。 |
@@ -199,4 +200,5 @@ Manager 负责校验、保存和返回这些字段;runner 只能消费已保
| `agentrun-runner` 实现 | 已实现/已通过主闭环 | host process runner 与 Kubernetes Job 共用 `runOnce`runner 通过 manager API claim/poll/report,不直连 Postgres;真实 Kubernetes Job 已完成 Codex turn。 |
| `codex` profile | 已实现/已通过主闭环 | Codex app-server stdio backend 已有协议、失败分类、脱敏和 fake self-test;真实 Codex provider turn 已通过 RESTful API 与 CLI 主闭环。 |
| `deepseek` profile | 已实现/已通过主闭环 | DeepSeek 已作为同一 Codex stdio backend kind 的 profile/config/SecretRef 选择进入 v0.1;自测试覆盖 registry、runner Secret 选择、fake stdio turn 和无 fallback,真实 `agentrun-v01` 已通过 `codex -> deepseek -> codex` 切换联调。 |
| `minimax-m3` profile | 已实现/待真实主闭环 | MiniMax-M3 已作为同一 Codex stdio backend kind 的 profile/config/SecretRef 选择进入 v0.1;自测试覆盖 registry、runner Secret 选择、fake stdio turn 和无 fallback,真实 `agentrun-v01` 仍需用 AgentRun CLI 手动验收。 |
| 自动 scheduler | Deferred | 不作为 `v0.1` 第一阶段验收目标。 |
+10 -8
View File
@@ -23,7 +23,7 @@
- CLI:默认 JSON、空 stdout 失败、长操作短返回、错误结构化。
- Postgres adaptermigration、事务、run/command/event round-trip、重启后可查询。
- Secret 分发:SecretRef schema、missing secret failure、redaction。
- AgentRun Queuetask schema、attempt 状态机、summary/stats/read cursor、Queue 与 Session 引用边界、MiniMax/OpenCode 废弃入口和 redaction。
- AgentRun Queuetask schema、attempt 状态机、summary/stats/read cursor、Queue 与 Session 引用边界、MiniMax/OpenCode 直连入口废弃和 redaction。
- HWLAB v0.2 基线承接:可以用 fake backend/临时 manager 做组件自测试,覆盖 event contract、result completed 防误判、bounded output、runner job status、SessionRef profile 隔离、ResourceBundleRef 失败分类和 backend preflight redaction;这些自测试不能替代真实 `agentrun-v01` CLI 交互验收。
自测试应使用 Bun + TypeScript 运行,Codex 相关自测试可以使用 fake app-server JSON-RPC client 模拟 `initialize``thread/start``thread/resume``turn/start`、assistant 输出、协议错误、timeout 和 transport close。
@@ -47,7 +47,7 @@
- Codex Secret projection 必须先保持只读,再复制到 writable `CODEX_HOME` 后启动 app-server;综合联调不得把只读 Secret volume 直接当作 `CODEX_HOME` 的通过证据。
- 真实 `agentrun-mgr`、runner Job 或受控 runner process、真实 backend adapter。
- 至少一个真实 Code Agent provider turnCodex stdio backend 必须通过 `codex app-server --listen stdio://` 的 JSON-RPC stdio turn 完成,mock、fixture、source-only、dry-run、fake provider、直接 Responses HTTP 或 `codex exec` 一次性输出不能作为通过证据。如果 provider credential SecretRef 缺失,综合联调必须标记 blocked,不能降级为 mock pass。
- 若变更涉及 backend profile,综合联调必须分别覆盖 `backendProfile=codex``backendProfile=deepseek`,并按 `codex -> deepseek -> codex` 顺序证明 profile 切换不互相污染。
- 若变更涉及 backend profile,综合联调必须分别覆盖 `backendProfile=codex``backendProfile=deepseek``backendProfile=minimax-m3`,并按 `codex -> deepseek -> minimax-m3 -> codex` 顺序证明 profile 切换不互相污染。
综合联调最小闭环:
@@ -105,7 +105,7 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察
| cancel | 对 pending/running/terminal 分别调用 cancel | cancel 幂等,pending 不再启动 runnerrunning 收敛为 cancelled 或既有 terminalevents/result 可见。 |
| SessionRef | 连续两轮使用同一 sessionRef 或 conversation/session/thread 摘要 | 第二轮可 resume backend threadsession 不包含 credential 文件或完整 CODEX_HOME。 |
| ResourceBundleRef | 使用 `repoUrl + full commitId` 启动 runner | runner checkout 到允许 workspaceevent/result 能回答 repo、commit、workspace 摘要;不使用 branch/tag/HEAD 或 host path。 |
| ProfileRef/SecretRef | 分别验证 `codex``deepseek` profile | 只使用当前 profile SecretRef;缺失时 `secret-unavailable`,不 fallback,不泄露 Secret 值。 |
| ProfileRef/SecretRef | 分别验证 `codex``deepseek``minimax-m3` profile | 只使用当前 profile SecretRef;缺失时 `secret-unavailable`,不 fallback,不泄露 Secret 值。 |
| bounded output | 触发工具/命令输出摘要 | result/event 只含摘要、字节数、截断标记和必要引用,不把大 stdout/stderr 塞入单个 JSON 响应。 |
这组验收吸收 HWLAB v0.2 的成熟判定口径,但不验证 HWLAB 用户鉴权、device-pod 授权、Workbench UI、`traceId -> runId` 业务映射或 HWLAB result/trace schema 转换。
@@ -161,7 +161,7 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察
- 对同一 run 使用相同 idempotency key 提交相同 command 两次,再用相同 key 提交不同 payload,确认前两次返回同一个 command,第三次结构化失败。
- 对同一 pending run 启动两个真实 runner 或重复 claim,确认只有一个 owner 成功,失败方为 `runner-lease-conflict` 或等价 failureKind,且不继续调用 backend。
- 用不存在的 provider SecretRef 创建 run 并启动 runner,确认失败为 `secret-unavailable`,不会降级为 mock pass,也不会打印 Secret value。
-`backendProfile=deepseek` 但只提供 `codex` SecretRef 的 run 启动 runner,确认失败为 `secret-unavailable`,不会 fallback 到 `codex`
-`backendProfile=deepseek``backendProfile=minimax-m3` 但只提供 `codex` SecretRef 的 run 启动 runner,确认失败为 `secret-unavailable`,不会 fallback 到 `codex`
- 对同一 run 分页读取 events,确认 `seq` 单调、`afterSeq` 翻页无重复、重复读取同一页不会改变 durable facts。
T7 只定义人工验收的检查面和判定口径。若后续为减少人工操作引入 helper 命令,它只能输出手动步骤、当前证据或 dry-run 计划,不能把这些负向场景改造成阻断发布的自动门禁。
@@ -170,11 +170,12 @@ T7 只定义人工验收的检查面和判定口径。若后续为减少人工
阅读本文、[spec-v01-services.md](spec-v01-services.md)、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md),然后在真实 `agentrun-v01` 运行面用正式 CLI 和 RESTful API 手动验证以下内容:
- `GET /api/v1/backends``./scripts/agentrun backends list` 同时列出 `codex``deepseek`,并显示者共享 `backendKind=codex-app-server-stdio``protocol=codex-app-server-jsonrpc-stdio``transport=stdio`
- `GET /api/v1/backends``./scripts/agentrun backends list` 同时列出 `codex``deepseek``minimax-m3`,并显示者共享 `backendKind=codex-app-server-stdio``protocol=codex-app-server-jsonrpc-stdio``transport=stdio`
-`backendProfile=codex` 完成一个真实 app-server stdio turn,记录 runId、commandId、terminal_status 和 redacted backend_status。
-`backendProfile=deepseek` 完成一个真实 app-server stdio turn,确认 SecretRef 为 `agentrun-v01-provider-deepseek`runtime `CODEX_HOME``codex` run 隔离,assistant 回复非空或失败被正确归类为 provider blocker。
- 再次`backendProfile=codex` 完成真实 turn,确认没有继承 DeepSeek model/base URL/config
- 删除或替换 `deepseek` SecretRef 后复测,必须 `secret-unavailable`,不能使用 `codex` SecretRef 或默认 Codex config。
-`backendProfile=minimax-m3` 完成一个真实 app-server stdio turn,确认 SecretRef 为 `agentrun-v01-provider-minimax-m3`runtime `CODEX_HOME``codex`/`deepseek` run 隔离,assistant 回复非空或失败被正确归类为 provider blocker
- 再次用 `backendProfile=codex` 完成真实 turn,确认没有继承 DeepSeek 或 MiniMax-M3 model/base URL/config。
- 删除或替换 `deepseek`/`minimax-m3` SecretRef 后复测,必须 `secret-unavailable`,不能使用 `codex` SecretRef 或默认 Codex config。
T8 是涉及 backend profile 变更时的综合联调标准;不涉及 backend profile 的普通发布仍至少执行已有真实主闭环。
@@ -188,7 +189,7 @@ T8 是涉及 backend profile 变更时的综合联调标准;不涉及 backend
### T11 Queue 吸收 Code Queue 手动验收
阅读本文和 [spec-v01-queue.md](spec-v01-queue.md),然后在真实 `agentrun-v01` runtime 中用正式 CLI 手动验证 Queue 首版闭环:创建 Queue task,轮询 `queue list/show/stats/commander/read`,用 `queue dispatch` 触发真实 Codex/Codex-compatible attempt 到 terminal,再用 `queue refresh` 从 Core run/command 终态回写 Queue task/latestAttempt。确认 `queue show` 返回 `sessionPath`,输出和 trace 只通过 Session API/CLI 查询。该验收不得使用 mock,不写自动交互脚本,不做 Web/Playwright。确认 Queue overview、stats、read 和 commander 均来自 Queue summary/stats/read 模型,不从 Core trace 或 Session trace 反推;确认不存在首版 OA/Event/OA sink/integrations/notification/GitHub sink 依赖;确认 MiniMax/OpenCode 不作为 Queue 首版能力;确认旧 UniDesk Code Queue 不接收新任务且历史数据不迁移到 AgentRun。若使用 `host-path` workspace,必须先证明该路径在 runner 运行面可访问且不会破坏 backend command resolution;否则应使用 `opaque` 或 Git-only ResourceBundleRef 做 Queue dispatch 最小验收。
阅读本文和 [spec-v01-queue.md](spec-v01-queue.md),然后在真实 `agentrun-v01` runtime 中用正式 CLI 手动验证 Queue 首版闭环:创建 Queue task,轮询 `queue list/show/stats/commander/read`,用 `queue dispatch` 触发真实 Codex/Codex-compatible attempt 到 terminal,再用 `queue refresh` 从 Core run/command 终态回写 Queue task/latestAttempt。确认 `queue show` 返回 `sessionPath`,输出和 trace 只通过 Session API/CLI 查询。该验收不得使用 mock,不写自动交互脚本,不做 Web/Playwright。确认 Queue overview、stats、read 和 commander 均来自 Queue summary/stats/read 模型,不从 Core trace 或 Session trace 反推;确认不存在首版 OA/Event/OA sink/integrations/notification/GitHub sink 依赖;确认 MiniMax/OpenCode 直连路线不作为 Queue 首版能力MiniMax-M3 只能通过 Codex-compatible `minimax-m3` profile 使用;确认旧 UniDesk Code Queue 不接收新任务且历史数据不迁移到 AgentRun。若使用 `host-path` workspace,必须先证明该路径在 runner 运行面可访问且不会破坏 backend command resolution;否则应使用 `opaque` 或 Git-only ResourceBundleRef 做 Queue dispatch 最小验收。
## 规格的实现情况
@@ -202,6 +203,7 @@ T8 是涉及 backend profile 变更时的综合联调标准;不涉及 backend
| RESTful API 交互联调标准 | 已定义 | 必须直连真实 manager HTTP JSON API,验证服务合同和 durable facts。 |
| 真实主闭环 | 已通过 | 当前 v0.1 已通过真实 Tekton/Argo、Postgres、SecretRef、Kubernetes runner Job、Codex stdio turn、RESTful API 和 CLI 主闭环;每次发布仍需按本文手动复验。 |
| `deepseek` profile 切换验收 | 已通过主闭环 | 自测试和 CLI smoke 已覆盖 profile registry、Secret render、fake stdio turn、无 fallback 和结构化错误;真实 `agentrun-v01` 已按 T8 完成 `codex -> deepseek -> codex` 切换综合联调。后续涉及 backend profile 的发布仍必须按 T8 复验。 |
| `minimax-m3` profile 切换验收 | 已定义/待真实主闭环 | 自测试和 CLI smoke 覆盖 profile registry、Secret render、fake stdio turn、无 fallback 和结构化错误;真实 `agentrun-v01` 需要按 T8 完成 `codex -> deepseek -> minimax-m3 -> codex` 切换综合联调。 |
| RuntimeAssembly 四要素验收 | 已定义 | T9 收敛为四个最简问题:image digest、profile/SecretRef、session null/deferred、Git-only repo/full commitsession/resource materialization 后续实现时必须补真实联调。 |
| HWLAB 手动调度 canary 验收 | 已定义 | T10 规定 HWLAB dispatcher 通过手动 runner Job API 使用 AgentRun 的真实联调口径;自动 scheduler 不是前置条件。 |
| Queue 吸收 Code Queue 验收 | 已定义 | T11 规定 Queue RESTful/CLI/Session 分层的真实手动交互验收;mock 只允许在自测试层。 |
+2 -2
View File
@@ -457,7 +457,7 @@ function help(): JsonRecord {
"commands show <commandId> --run-id <runId>",
"commands result <commandId> --run-id <runId>",
"commands cancel <commandId> [--reason <text>]",
"runner start --run-id <runId> [--backend codex|deepseek]",
"runner start --run-id <runId> [--backend codex|deepseek|minimax-m3]",
"runner job --run-id <runId> --command-id <commandId> [--image <image>] [--runner-manager-url <url>] [--idempotency-key <key>]",
"runner job --dry-run --run-id <runId> --command-id <commandId> --image <image>",
"runner jobs --run-id <runId> [--command-id <commandId>]",
@@ -471,7 +471,7 @@ function help(): JsonRecord {
"queue cancel <taskId> [--reason <text>]",
"queue dispatch <taskId> [--json-file <dispatch.json>] [--idempotency-key <key>] [--image <image>] [--namespace <namespace>]",
"queue refresh <taskId>",
"secrets codex render --dry-run [--profile codex|deepseek] [--codex-home <dir>] [--namespace agentrun-v01] [--secret-name <name>]",
"secrets codex render --dry-run [--profile codex|deepseek|minimax-m3] [--codex-home <dir>] [--namespace agentrun-v01] [--secret-name <name>]",
"backends list",
"server start [--port <port>] [--host <host>] [--foreground]",
"server status [--port <port>]",
+12
View File
@@ -38,6 +38,18 @@ export const backendProfileSpecs: readonly BackendProfileSpec[] = [
profileIsolation: "profile-scoped-codex-home",
description: "DeepSeek-compatible profile through Codex app-server stdio",
},
{
profile: "minimax-m3",
backendKind: "codex-app-server-stdio",
protocol: "codex-app-server-jsonrpc-stdio",
transport: "stdio",
command: "codex app-server --listen stdio://",
status: "registered",
requiredSecretKeys: ["auth.json", "config.toml"],
defaultSecretName: "agentrun-v01-provider-minimax-m3",
profileIsolation: "profile-scoped-codex-home",
description: "MiniMax M3 OpenAI-compatible profile through Codex app-server stdio",
},
];
export const backendProfiles = backendProfileSpecs.map((item) => item.profile) as readonly BackendProfile[];
+1 -1
View File
@@ -22,7 +22,7 @@ export type FailureKind =
export type RunStatus = "pending" | "claimed" | "running" | "completed" | "failed" | "blocked" | "cancelled";
export type CommandState = "pending" | "acknowledged" | "completed" | "failed" | "cancelled";
export type TerminalStatus = "completed" | "failed" | "blocked" | "cancelled";
export type BackendProfile = "codex" | "deepseek";
export type BackendProfile = "codex" | "deepseek" | "minimax-m3";
export type QueueTaskState = "pending" | "running" | "completed" | "failed" | "blocked" | "cancelled";
export interface WorkspaceRef extends JsonRecord {
+15 -1
View File
@@ -51,6 +51,20 @@ const selfTest: SelfTestCase = async (context) => {
assertRunnerJobDoesNotMountProfile(deepseekRendered.manifest as JsonRecord, "codex-0");
assertNoSecretLeak(deepseekRendered);
const minimaxItem = await createRunWithCommand(client, { ...context, backendProfile: "minimax-m3" }, "minimax m3 job smoke", "selftest-minimax-m3-job-render", 15_000);
const minimaxRendered = renderRunnerJobDryRun({
run: await client.get(`/api/v1/runs/${minimaxItem.runId}`) as RunRecord,
commandId: minimaxItem.commandId,
managerUrl: server.baseUrl,
image: "127.0.0.1:5000/agentrun/agentrun-mgr@sha256:1111111111111111111111111111111111111111111111111111111111111111",
attemptId: "attempt_selftest_minimax_m3",
sourceCommit: "self-test",
});
assertRunnerJobUsesWritableCodexHome(minimaxRendered.manifest as JsonRecord, context.minimaxM3Home, "minimax-m3-0", "/var/run/agentrun/secrets/minimax-m3-0");
assertRunnerJobDoesNotMountProfile(minimaxRendered.manifest as JsonRecord, "codex-0");
assertRunnerJobDoesNotMountProfile(minimaxRendered.manifest as JsonRecord, "deepseek-0");
assertNoSecretLeak(minimaxRendered);
const fakeKubectl = path.join(context.tmp, "fake-kubectl.js");
const createdManifest = path.join(context.tmp, "created-runner-job.json");
await writeFile(fakeKubectl, `#!/usr/bin/env bun
@@ -98,7 +112,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
} finally {
await new Promise<void>((resolve) => serverWithKubectl.server.close(() => resolve()));
}
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env"] };
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
+15 -1
View File
@@ -44,10 +44,24 @@ const selfTest: SelfTestCase = async (context) => {
assert.ok(deepseekEvents.items?.some((event) => event.type === "backend_status" && JSON.stringify(event.payload).includes("deepseek")), "deepseek backend_status should include profile metadata");
assertNoSecretLeak(deepseekEvents);
const minimaxM3Home = path.join(context.tmp, "runtime-minimax-m3-home");
const minimaxM3 = await createRunWithCommand(client, { ...context, backendProfile: "minimax-m3" }, "hello minimax m3", "selftest-minimax-m3-turn", 15_000);
const minimaxM3Result = await runOnce({ managerUrl: server.baseUrl, runId: minimaxM3.runId, backendProfile: "minimax-m3", codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: minimaxM3Home, env: { CODEX_HOME: minimaxM3Home, AGENTRUN_CODEX_SECRET_HOME: context.minimaxM3Home }, oneShot: true });
assert.equal(minimaxM3Result.terminalStatus, "completed");
await access(path.join(minimaxM3Home, "auth.json"));
await access(path.join(minimaxM3Home, "config.toml"));
const minimaxM3Events = await client.get(`/api/v1/runs/${minimaxM3.runId}/events?afterSeq=0&limit=100`) as { items?: Array<{ type: string; payload: unknown }> };
assert.ok(minimaxM3Events.items?.some((event) => event.type === "backend_status" && JSON.stringify(event.payload).includes("minimax-m3")), "minimax-m3 backend_status should include profile metadata");
assertNoSecretLeak(minimaxM3Events);
await assert.rejects(
() => createRunWithCommand(client, { ...context, backendProfile: "deepseek", includeOnlyProfile: "codex" }, "missing deepseek", "selftest-deepseek-missing-secret", 15_000),
(error) => error instanceof Error && error.message.includes("requires a matching provider credential"),
);
await assert.rejects(
() => createRunWithCommand(client, { ...context, backendProfile: "minimax-m3", includeOnlyProfile: "deepseek" }, "missing minimax m3", "selftest-minimax-m3-missing-secret", 15_000),
(error) => error instanceof Error && error.message.includes("requires a matching provider credential"),
);
const configModel = await createRunWithCommand(client, context, "hello config model", "selftest-config-model", 15_000);
const configModelResult = await runOnce({ managerUrl: server.baseUrl, runId: configModel.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "reject-unexpected-model" }, oneShot: true });
@@ -111,7 +125,7 @@ const selfTest: SelfTestCase = async (context) => {
await runSecretFailureCase({ client, managerUrl: server.baseUrl, context });
await runSpawnFailureCase({ client, managerUrl: server.baseUrl, context });
return { name: "codex-stdio", tests: ["runner-lease-heartbeat", "codex-stdio-fake-turn", "codex-stdio-projected-writable-home", "codex-stdio-deepseek-profile-fake-turn", "codex-stdio-deepseek-missing-secret-no-fallback", "codex-stdio-config-model-authoritative", "codex-stdio-explicit-model-forwarded", "codex-stdio-final-agent-message-only", "codex-stdio-stale-thread-fallback", "codex-stdio-live-tool-events", "codex-stdio-missing-turn-result", "codex-stdio-provider-auth-failed", "codex-stdio-provider-rate-limited", "codex-stdio-provider-503-rpc-error", "codex-stdio-provider-503-terminal", "codex-stdio-provider-503-retry-event", "codex-stdio-invalid-json", "codex-stdio-timeout", "codex-stdio-secret-unavailable", "codex-stdio-spawn-failure"] };
return { name: "codex-stdio", tests: ["runner-lease-heartbeat", "codex-stdio-fake-turn", "codex-stdio-projected-writable-home", "codex-stdio-deepseek-profile-fake-turn", "codex-stdio-minimax-m3-profile-fake-turn", "codex-stdio-deepseek-missing-secret-no-fallback", "codex-stdio-minimax-m3-missing-secret-no-fallback", "codex-stdio-config-model-authoritative", "codex-stdio-explicit-model-forwarded", "codex-stdio-final-agent-message-only", "codex-stdio-stale-thread-fallback", "codex-stdio-live-tool-events", "codex-stdio-missing-turn-result", "codex-stdio-provider-auth-failed", "codex-stdio-provider-rate-limited", "codex-stdio-provider-503-rpc-error", "codex-stdio-provider-503-terminal", "codex-stdio-provider-503-retry-event", "codex-stdio-invalid-json", "codex-stdio-timeout", "codex-stdio-secret-unavailable", "codex-stdio-spawn-failure"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
+8 -1
View File
@@ -25,6 +25,13 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal(JSON.stringify(deepseekSecretPlan).includes("test-token-material-deepseek"), false);
assert.equal(JSON.stringify(deepseekSecretPlan).includes("deepseek-test"), false);
const minimaxM3SecretPlan = await renderCodexProviderSecretPlan({ profile: "minimax-m3", codexHome: context.minimaxM3Home, dryRun: true });
assert.equal(minimaxM3SecretPlan.secretName, "agentrun-v01-provider-minimax-m3");
assert.equal(minimaxM3SecretPlan.profile, "minimax-m3");
assert.equal(JSON.stringify(minimaxM3SecretPlan).includes("test-token-material-minimax-m3"), false);
assert.equal(JSON.stringify(minimaxM3SecretPlan).includes("MiniMax-M3"), false);
assert.equal(JSON.stringify(minimaxM3SecretPlan).includes("api.minimaxi.com"), false);
await assert.rejects(
() => renderCodexProviderSecretPlan({ codexHome: path.join(context.tmp, "missing-codex-home"), dryRun: true }),
(error) => error instanceof AgentRunError && error.failureKind === "secret-unavailable",
@@ -49,7 +56,7 @@ const selfTest: SelfTestCase = async (context) => {
(error) => error instanceof AgentRunError && error.failureKind === "schema-invalid",
);
return { name: "secret-render", tests: ["codex-secret-dry-run", "deepseek-secret-dry-run"] };
return { name: "secret-render", tests: ["codex-secret-dry-run", "deepseek-secret-dry-run", "minimax-m3-secret-dry-run"] };
};
export default selfTest;
@@ -9,7 +9,7 @@ import { ManagerClient } from "../../mgr/client.js";
import { runOnce } from "../../runner/run-once.js";
import { eventContractSummary } from "../../common/events.js";
import type { BackendProfile, JsonRecord, RunEvent } from "../../common/types.js";
import { assertNoSecretLeak, createRunWithCommand, type SelfTestCase, type SelfTestContext } from "../harness.js";
import { assertNoSecretLeak, createRunWithCommand, profileSecretHome, type SelfTestCase, type SelfTestContext } from "../harness.js";
const execFile = promisify(execFileCallback);
@@ -54,7 +54,8 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
async function assertBackendPreflight(client: ManagerClient): Promise<void> {
const response = await client.get("/api/v1/backends") as { items?: JsonRecord[] };
const items = response.items ?? [];
assert.ok(items.length >= 2, "codex/deepseek backend capabilities should be visible");
assert.ok(items.length >= 3, "codex/deepseek/minimax-m3 backend capabilities should be visible");
assert.ok(items.some((item) => item.profile === "minimax-m3"), "minimax-m3 backend capability should be visible");
for (const item of items) {
const preflight = item.preflight as JsonRecord;
const defaultSecretRef = item.defaultSecretRef as JsonRecord;
@@ -147,7 +148,7 @@ async function assertResourceBundleFailure(client: ManagerClient, context: SelfT
}
function runPayload(context: SelfTestContext, backendProfile: BackendProfile, sessionId: string): JsonRecord {
const secretHome = backendProfile === "deepseek" ? context.deepseekHome : context.codexHome;
const secretHome = profileSecretHome(context, backendProfile);
return {
tenantId: "hwlab",
projectId: "pikasTech/HWLAB",
+16 -3
View File
@@ -11,6 +11,7 @@ export interface SelfTestContext {
tmp: string;
codexHome: string;
deepseekHome: string;
minimaxM3Home: string;
workspace: string;
fakeCodexPath: string;
fakeCodexCommand: string;
@@ -25,20 +26,24 @@ export interface SelfTestResult {
export type SelfTestCase = (context: SelfTestContext) => Promise<SelfTestResult> | SelfTestResult;
type SelfTestRunContext = Pick<SelfTestContext, "workspace" | "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome">> & { backendProfile?: BackendProfile; includeOnlyProfile?: BackendProfile; toolCredentials?: JsonRecord[] };
type SelfTestRunContext = Pick<SelfTestContext, "workspace" | "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome" | "minimaxM3Home">> & { backendProfile?: BackendProfile; includeOnlyProfile?: BackendProfile; toolCredentials?: JsonRecord[] };
export async function createSelfTestContext(root: string): Promise<SelfTestContext> {
const tmp = await mkdtemp(path.join(os.tmpdir(), "agentrun-selftest-"));
const codexHome = path.join(tmp, "codex-home");
const deepseekHome = path.join(tmp, "deepseek-home");
const minimaxM3Home = path.join(tmp, "minimax-m3-home");
const workspace = path.join(tmp, "workspace");
await mkdir(codexHome, { recursive: true });
await mkdir(deepseekHome, { recursive: true });
await mkdir(minimaxM3Home, { recursive: true });
await mkdir(workspace, { recursive: true });
await writeFile(path.join(codexHome, "auth.json"), JSON.stringify({ token: "test-token-material" }));
await writeFile(path.join(codexHome, "config.toml"), "model = \"gpt-test\"\n");
await writeFile(path.join(deepseekHome, "auth.json"), JSON.stringify({ token: "test-token-material-deepseek" }));
await writeFile(path.join(deepseekHome, "config.toml"), "model = \"deepseek-test\"\n");
await writeFile(path.join(minimaxM3Home, "auth.json"), JSON.stringify({ token: "test-token-material-minimax-m3" }));
await writeFile(path.join(minimaxM3Home, "config.toml"), "model = \"MiniMax-M3\"\nmodel_provider = \"minimax\"\n[model_providers.minimax]\nname = \"MiniMax\"\nbase_url = \"https://api.minimaxi.com/v1\"\nenv_key = \"MINIMAX_API_KEY\"\nwire_api = \"chat\"\n");
await writeFile(path.join(workspace, "README.md"), "self-test workspace\n");
const fakeCodexPath = path.join(root, "src/selftest/fake-codex-app-server.ts");
return {
@@ -46,6 +51,7 @@ export async function createSelfTestContext(root: string): Promise<SelfTestConte
tmp,
codexHome,
deepseekHome,
minimaxM3Home,
workspace,
fakeCodexPath,
fakeCodexCommand: process.env.AGENTRUN_SELFTEST_CODEX_COMMAND ?? defaultFakeCommand(),
@@ -81,14 +87,14 @@ function toolCredentialScope(context: { toolCredentials?: JsonRecord[] }): JsonR
return context.toolCredentials ? { toolCredentials: context.toolCredentials } : {};
}
function providerCredentials(context: Pick<SelfTestContext, "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome">> & { includeOnlyProfile?: BackendProfile }, backendProfile: BackendProfile): JsonRecord[] {
function providerCredentials(context: Pick<SelfTestContext, "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome" | "minimaxM3Home">> & { includeOnlyProfile?: BackendProfile }, backendProfile: BackendProfile): JsonRecord[] {
const profiles: BackendProfile[] = context.includeOnlyProfile ? [context.includeOnlyProfile] : [backendProfile];
return profiles.map((profile) => ({
profile,
secretRef: {
name: backendProfileSpec(profile)?.defaultSecretName ?? `agentrun-v01-provider-${profile}`,
keys: ["auth.json", "config.toml"],
mountPath: profile === "deepseek" ? context.deepseekHome ?? context.codexHome : context.codexHome,
mountPath: profileSecretHome(context, profile),
},
}));
}
@@ -97,9 +103,16 @@ export function assertNoSecretLeak(value: unknown): void {
const text = JSON.stringify(value);
assert.equal(text.includes("test-token-material"), false);
assert.equal(text.includes("test-token-material-deepseek"), false);
assert.equal(text.includes("test-token-material-minimax-m3"), false);
assert.equal(text.includes("Bearer test-token"), false);
}
export function profileSecretHome(context: Pick<SelfTestContext, "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome" | "minimaxM3Home">>, profile: BackendProfile): string {
if (profile === "deepseek") return context.deepseekHome ?? context.codexHome;
if (profile === "minimax-m3") return context.minimaxM3Home ?? context.codexHome;
return context.codexHome;
}
function defaultFakeCommand(): string {
return process.versions.bun ? process.execPath : "npx";
}