diff --git a/AGENTS.md b/AGENTS.md index 7cc70e7e..231a7720 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,10 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - D601 HWLAB DEV、D601 `dev-cd-apply`、`ci-publish` 和旧 `main` JS 脚本式 CI/CD 已退出 G14 发布入口;G14 Tekton 只能通过 `scripts/g14-artifact-publish.mjs` 作为集群内 build/push helper,再由 GitOps/Argo CD rollout,新开发、发布、验收、文档和运行面实验不得把 D601 或 legacy 脚本 CD 当作当前 HWLAB runtime source-of-truth。 - G14 k3s 操作必须通过 UniDesk route `G14:k3s` 执行;不得用 D601 kubeconfig、Docker Desktop Kubernetes、master server 本地 check/build 或旧 JS CD 结果作为 G14 DEV/PROD 通过证据。 +## P0 HyueAPI Direct NO_PROXY 规则 + +- `hyueapi.com` / `.hyueapi.com` 是 Codex API 通道的直连域名,必须始终保留在 `NO_PROXY` / `no_proxy` 中;DeepSeek、Codex API 或其他 provider profile 切换、G14 proxy 注入、旧 D601 egress 迁移和 pod/env render 都不得把 hyueapi 流量改成走 HTTP/SOCKS proxy。 + ## P0 GitHub Issue 写入纪律 - HWLAB #7、用户反馈、长期看板和指挥简报的 GitHub issue 正文写入必须走 UniDesk CLI:`cd /root/unidesk && bun scripts/cli.ts gh ...`;禁止直接用原生 `gh issue edit/create/comment` 写这些 issue。事故和工具补强需求见 [pikasTech/unidesk#142](https://github.com/pikasTech/unidesk/issues/142)。 diff --git a/docs/reference/code-agent-chat-readiness.md b/docs/reference/code-agent-chat-readiness.md index 66efdbff..ab7bc985 100644 --- a/docs/reference/code-agent-chat-readiness.md +++ b/docs/reference/code-agent-chat-readiness.md @@ -25,15 +25,19 @@ stdio session supervisor,并证明 `/workspace/hwlab` 可读写、`CODEX_HOME= 可创建和复用同一个 Codex thread/session。 Codex token boundary 仍由授权路径把 `OPENAI_API_KEY` 注入到 DEV runtime。DEV Pod -必须使用 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 指向受控 DEV egress/proxy 路径,不能 -直接指向 public `api.openai.com`。G14 默认 `deepseek` profile 指向 +不能直接指向 public `api.openai.com`。G14 默认 `deepseek` profile 指向 `http://hwlab-deepseek-proxy..svc.cluster.local:4000/v1/responses`;该 Service 必须先进入 `hwlab-deepseek-responses-bridge`,由 bridge 解压 Codex Responses 的 zstd request body、规范化 `/v1/models` 返回、丢弃非 `function` tool, 再转发到同 Pod 内 Moon Bridge 4001。Moon Bridge 是 DeepSeek profile 的真实 Responses 转换和 prompt cache 保留层;不要在 HWLAB 中手写完整转换器替代它。 -`codex-api` profile 仍可指向 -`http://172.26.26.227:17680/v1/responses` 作为旧 Responses 通道。 +`codex-api` profile 是独立的 Codex/OpenAI-compatible Responses 通道。G14 上 +`hwlab-cloud-api` 应把 `codex-api` base URL 指向同 Pod 的 `127.0.0.1` loopback +forwarder;forwarder 再直连 `hyueapi.com` / `.hyueapi.com`,并把这两个域名显式保留在 +`NO_PROXY` / `no_proxy`。这不是 DeepSeek bridge,也不是公网 `api.openai.com`,不能把 +hyueapi 流量改成 HTTP/SOCKS proxy。`http://172.26.26.227:17680/v1/responses` 只能作为 +D601 legacy Code Queue runner 或历史 egress 的对照线索;迁移到 G14 后不得把它当作 +G14 默认 `codex-api` base URL,也不得用 DeepSeek bridge 伪装 `codex-api` 通过。 Runner 不得尝试修补、读取、回显或替换该 Secret。若 DEV runtime 缺少该授权凭证注入, `provider_unavailable` 且 `error.missingEnv` 包含 `OPENAI_API_KEY` 必须判为 @@ -43,6 +47,103 @@ Runner 不得尝试修补、读取、回显或替换该 Secret。若 DEV runtime DEV egress/base-url 合同是否声明和保留;不得读取 Secret data,也不得把 Secret 值写入 report、issue、PR 或截图。 +## Provider 切换排查方法论 + +Provider/profile 切换故障必须先在目标 pod/host 上打通最小真实闭环,再进入完整 CI/CD、 +GitOps render 或正式发布。`deepseek`、`codex-api` 和未来 provider 共享 cloud-api 会话与 +Workbench UI,但排查时必须把 profile overlay、认证、网络、模型、Codex CLI/app-server +逐层拆开,避免用一个 profile 的成功掩盖另一个 profile 的退化。 + +最小闭环按以下顺序分层,任何一层失败都不能跳到正式 CI/CD 试错: + +1. 运行面确认:在 G14 `/root/hwlab` 与 G14 k3s 目标 pod 内确认当前分支、镜像、env overlay、 + `CODEX_HOME`、Codex 版本和 workspace;D601 只能作为 legacy 对照,不作为 G14 source truth。 +2. 凭证边界:只检查 Secret 引用、`auth.json` 顶层 key 和值长度,不打印 secret。Codex CLI 的 + `auth.json` 应能暴露 `OPENAI_API_KEY` 顶层 key;形如 `auth` 的不明结构必须先按 blocker 处理。 +3. 直连边界:`hyueapi.com` / `.hyueapi.com` 必须在 `NO_PROXY` 与 `no_proxy` 内。需要同时检查 + shell env 和 Codex/Rust trace;若 trace 显示 `network_proxy: None` 且直接连接 `hyueapi.com:443`, + 不能再把问题归类为 proxy 污染。 +4. 裸 Responses API:在同一个目标 pod 内用同一份 auth、同一模型、同一 base host 发 + `/responses` 或 `/v1/responses` 流式请求,确认网络、认证和模型是否可用。裸 API 通过只证明 + upstream 可达,不等于 Workbench 或 Codex runner 通过。 +5. Codex CLI 对照:用同一模型、同一 `CODEX_HOME`、同一 prompt 运行 `codex exec --json`;同时在 + D601 Code Queue runner 上做同模型对照,记录版本、config 形态、NO_PROXY、proxy env 和 transport + 摘要。D601 对照只用于定位差异,不能把 D601 路径写回 G14 默认运行态。 +6. Loopback forwarder 对照:如果裸 Responses API 通过、Codex CLI 直连失败,并且 trace 已确认 + `network_proxy: None`,必须在同一个目标 Pod 内增加只监听 `127.0.0.1` 的临时 forwarder,使用同一份 + auth、同一模型和同一 prompt 复测 `codex exec` 与 `/v1/agent/chat`。forwarder 只能把流量转到可配置的 + hyueapi upstream,不能硬编码 D601 IP,也不能复用 DeepSeek bridge。 +7. App-server 闭环:最终证据必须来自 repo-owned Codex app-server stdio 或明确批准的等价 + long-lived runner,返回 `completed` 且 `reply.content` 非空。Node/raw HTTPS、stub、bridge、 + source-only smoke、前端状态都不能升级成 DEV-LIVE reply pass。 + +排查记录应保留稳定结论和判定方法,不写 Secret、一次性 trace 全文或日期化流水账。遇到 +“裸 API 通过但 Codex CLI/app-server 失败”时,优先抓 Codex/Rust transport 日志并与 D601 成功 +路径比对:模型、service tier、`auth.json` 结构、base URL、是否直连、实际连接 IP、请求体大小、 +SSE 是否 completed。只有这些证据归一后,才修改源码、render、SecretRef 或发布配置。 + +## Codex API 转发根因 + +`codex-api` 在 G14 上采用 pod-local loopback forwarder 是一个受控传输边界修复,不是为了规避 +hyueapi 直连要求。根因判定按以下证据链成立: + +- 同一目标 Pod 内,使用同一份 auth、同一模型、同一 Responses payload,通过 Node HTTPS 直接请求 + `hyueapi.com` 能获得完整 SSE `response.completed`;这排除了模型、Secret、请求体、基本网络可达性和 + hyueapi 账号本身不可用。 +- 同一目标 Pod 内,Codex CLI/Rust 传输直连 `https://hyueapi.com/responses` 会在 streaming 阶段断开; + Rust trace 显示 `network_proxy: None` 且连接目标是 `hyueapi.com:443`,这排除了全局 proxy 污染和 + `NO_PROXY` 未生效作为主因。 +- 把 Codex CLI 的 base URL 改为同 Pod `127.0.0.1`,再由 Node forwarder 使用直连 HTTPS 转发到 + hyueapi,`codex exec` 与 `/v1/agent/chat` 均能完成并返回非空 assistant 回复;这把失败边界收敛到 + Codex CLI 的 Rust HTTPS/SSE transport 与 G14->hyueapi 边缘路径组合,而不是 Workbench、DeepSeek、 + 模型或 auth。 +- D601 Code Queue runner 使用同模型可成功,只能证明 D601 legacy 路径可作为对照;不能把 + `172.26.26.227:17680` 写回 G14 默认配置。 + +因此,在不修改 Codex CLI 二进制、不要求 hyueapi 改边缘行为、也不把 hyueapi 流量送进 HTTP/SOCKS +proxy 的前提下,pod-local Node forwarder 是当前可控的最小修复。它的职责只是替换 Codex CLI 失败的 +直接 HTTPS/SSE transport;hyueapi 仍由 forwarder 进程直连,`hyueapi.com` / `.hyueapi.com` 仍必须在 +`NO_PROXY` 和 `no_proxy` 中。 + +## 转发器迁移与隔离 + +Codex API forwarder 必须是 Pod 内部能力,推荐作为 `hwlab-cloud-api` 同 Pod sidecar 或等价的同 Pod +受控进程运行。`hwlab-cloud-api` 只连接 `http://127.0.0.1:`;forwarder 通过 env 配置 upstream, +默认 upstream host 为 `hyueapi.com`。以下约束保证它可迁移且不会污染其他运行面: + +- 禁止硬编码 provider host IP、D601 `172.26.26.227`、G14 节点 IP、namespace 名或 NodePort。可配置项只应是 + loopback listen port、upstream base URL、模型 profile env 和 Secret 引用。 +- forwarder 不创建 Kubernetes Service、Ingress、NodePort 或 host port;它只监听 Pod network namespace 内的 + `127.0.0.1`。同一 k3s 集群内 `hwlab-dev`、`hwlab-prod` 或其他 namespace 可以同时各自运行一个 + `hwlab-cloud-api` Pod 和同端口 forwarder,因为每个 Pod 都有独立 loopback。 +- 不要给 `hwlab-cloud-api` Pod 启用 `hostNetwork` 来承载 forwarder。若某个迁移目标必须使用 hostNetwork, + 必须重新评估端口冲突和隔离边界,不能沿用“Pod 内同端口无冲突”的结论。 +- 迁移到其他 k3s 时,只需要保证目标 Pod 能直连 `hyueapi.com:443`、SecretRef 仍以 `OPENAI_API_KEY` 注入、 + `CODEX_HOME/auth.json` 形态正确、`NO_PROXY/no_proxy` 包含 `hyueapi.com` 与 `.hyueapi.com`,以及 + loopback forwarder 进程跟 `hwlab-cloud-api` 在同一 Pod network namespace。 +- DEV、PROD 和临时 smoke Pod 的证据必须分开记录。DEV forwarder 通过不能自动证明 PROD 通过;PROD + 需要在 PROD namespace 的目标 Pod 内复跑最小 `/v1/agent/chat` 闭环。 + +## 自动化兼容性 + +Forwarder 不需要人工维护长驻进程。正式固化后,它应由 Kubernetes Deployment 管理生命周期:Pod 创建时随 +`hwlab-cloud-api` 启动,Pod 删除时一起退出,异常退出由 kubelet 按 Pod/容器 restart policy 重启。人工只允许在 +临时 smoke Pod 中手动启动 forwarder 做分层诊断;运行态不应依赖手动 `kubectl exec` 后台进程。 + +自动化门禁只需要做合同级检查,不需要读取 Secret 或调用外部模型: + +- desired state 中 `codex-api` profile base URL 指向 Pod-local loopback,不指向 D601 IP 或 + `api.openai.com`。 +- forwarder 的 upstream base URL 由 env 配置,默认 host 为 `hyueapi.com`,并且 `NO_PROXY/no_proxy` + 包含 `hyueapi.com` 与 `.hyueapi.com`。 +- `hwlab-cloud-api` Pod 模板包含 forwarder sidecar 或明确等价的同 Pod 受控进程;forwarder 不暴露 + Service、NodePort、Ingress 或 hostPort。 +- `deepseek` profile 仍指向 DeepSeek bridge/Moon Bridge;`codex-api` profile 不依赖 DeepSeek bridge。 + +完整 CI/CD、GitOps render 或发布流水线只能在目标 Pod 最小闭环已经通过、且当前 CI/CD 基础设施可用时继续。 +最小闭环没有通过时,不要通过反复推送 CI/CD 来探索 provider transport;CI/CD 只能固化已经在目标运行面证明可行的 +配置和代码。 + ## 判定标准 | 观测结果 | readiness | diff --git a/docs/reference/dev-runtime-boundary.md b/docs/reference/dev-runtime-boundary.md index 2b4a5636..1385200d 100644 --- a/docs/reference/dev-runtime-boundary.md +++ b/docs/reference/dev-runtime-boundary.md @@ -127,14 +127,21 @@ references, and non-secret egress/runtime settings: | Provider env | `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` | | Model env | `HWLAB_CODE_AGENT_MODEL=gpt-5.5` | | Provider Secret | `OPENAI_API_KEY` from `hwlab-code-agent-provider/openai-api-key` | -| DEV egress/base URL | `HWLAB_CODE_AGENT_OPENAI_BASE_URL=http://172.26.26.227:17680/v1/responses` | +| DEV egress/base URL | 按 profile 独立配置;`deepseek` 走集群 bridge,`codex-api` 走同 Pod loopback forwarder,forwarder 直连 hyueapi 且必须保留 `NO_PROXY` | | Workspace | `HWLAB_CODE_AGENT_CODEX_WORKSPACE=/workspace/hwlab` with `workspace-write` sandbox | | Codex state | `CODEX_HOME=/codex-home` | | Codex command | `HWLAB_CODE_AGENT_CODEX_COMMAND=/app/node_modules/.bin/codex` | -DEV pods must not call `https://api.openai.com/v1/responses` directly. The base -URL must use the approved DEV egress/proxy path so provider reachability is a -repeatable deployment contract instead of a one-off runtime patch. +DEV pods must not call `https://api.openai.com/v1/responses` directly. G14 +`codex-api` must send Codex CLI/app-server traffic to a same-Pod `127.0.0.1` +loopback forwarder; that forwarder direct-connects to `hyueapi.com` / +`.hyueapi.com` and keeps both domains in `NO_PROXY` and `no_proxy`. DeepSeek uses +the cluster bridge/Moon Bridge path. `http://172.26.26.227:17680/v1/responses` is +only a D601 legacy egress or Code Queue runner comparison clue after the migration +to G14, not a G14 default runtime contract. Provider reachability must be proven by +the target pod minimal closure described in [Code Agent Chat Readiness Runbook](code-agent-chat-readiness.md), +not by a one-off runtime patch or by pushing full CI/CD while the closure is still +failing. Reports, smokes, and health payloads may show Secret name/key presence, `missingEnv`, provider status, model, trace IDs, and redacted egress status. diff --git a/docs/reference/g14-gitops-cicd.md b/docs/reference/g14-gitops-cicd.md index 0378d39e..377daf74 100644 --- a/docs/reference/g14-gitops-cicd.md +++ b/docs/reference/g14-gitops-cicd.md @@ -59,9 +59,13 @@ G14 Code Agent 通过同一个 repo-owned Codex app-server stdio runner 承载 | Profile | 默认 | Model | Base URL | 说明 | | --- | --- | --- | --- | --- | | `deepseek` | 是 | `HWLAB_CODE_AGENT_DEEPSEEK_MODEL`,默认 `deepseek-chat` | `HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL`,默认 `http://hwlab-deepseek-proxy..svc.cluster.local:4000/v1/responses` | G14 集群内 DeepSeek Responses bridge;Service 4000 先进入 `hwlab-deepseek-responses-bridge`,再转发到同 Pod 内 Moon Bridge 4001。 | -| `codex-api` | 否 | `HWLAB_CODE_AGENT_CODEX_API_MODEL`,默认 `gpt-5.5` | `HWLAB_CODE_AGENT_CODEX_API_BASE_URL`,默认 `http://172.26.26.227:17680/v1/responses` | 保留旧 Codex/OpenAI-compatible Responses API 通道。 | +| `codex-api` | 否 | `HWLAB_CODE_AGENT_CODEX_API_MODEL`,默认 `gpt-5.5` | `HWLAB_CODE_AGENT_CODEX_API_BASE_URL`,G14 默认应指向同 Pod `127.0.0.1` loopback forwarder,forwarder upstream 为 hyueapi | 独立 Codex/OpenAI-compatible Responses API 通道;不得依赖 DeepSeek bridge。 | -`hwlab-cloud-api` 仍以 `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` 运行。`HWLAB_CODE_AGENT_MODEL` 与 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 可作为 runtime-default 兜底,但前端默认 profile 是 `deepseek`,用户可以切到 `codex-api`。Codex app-server 启动参数必须同时设置 provider base URL、provider `name`、`model` 和 `review_model`,否则 Codex/DeepSeek 链路可能在模型目录或 `/v1/responses` 阶段退化成 `model=None`。旧 `codex-api` profile 指向 `172.26.26.227`,cloud-api 的 `NO_PROXY/no_proxy` 必须包含该 host 或私网段,避免旧通道被 G14 外网代理误拦截。 +`hwlab-cloud-api` 仍以 `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` 运行。`HWLAB_CODE_AGENT_MODEL` 与 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 可作为 runtime-default 兜底,但前端默认 profile 是 `deepseek`,用户可以切到 `codex-api`。Codex app-server 启动参数必须同时设置 provider base URL、provider `name`、`model` 和 `review_model`,否则 Codex/DeepSeek 链路可能在模型目录或 `/v1/responses` 阶段退化成 `model=None`。G14 cloud-api 的 `NO_PROXY/no_proxy` 必须包含 `hyueapi.com` 和 `.hyueapi.com`;`codex-api` 的 hyueapi upstream 由同 Pod loopback forwarder 直连,不能被 proxy 注入污染。 + +G14 `codex-api` 不得把 `http://172.26.26.227:17680/v1/responses` 作为默认 base URL;该地址只保留为 D601 legacy Code Queue runner 或历史 egress 对照线索。G14 上的 `codex-api` 应先进入同 Pod `127.0.0.1` loopback forwarder,再由 forwarder 直连 `hyueapi.com` / `.hyueapi.com`;这两个域名必须同时进入 `NO_PROXY` 和 `no_proxy`。DeepSeek profile 可以通过集群内 bridge/Moon Bridge 转换,`codex-api` 不能用 DeepSeek bridge 伪装通过,也不能因为默认 profile 切到 DeepSeek 而删除或覆盖 `codex-api` 的独立模型、base URL、auth 和最小闭环验证。 + +Provider/profile 变更必须按 [Code Agent Chat Readiness Runbook](code-agent-chat-readiness.md) 的分层方法先做目标 pod 最小闭环:env/profile overlay、SecretRef/auth 结构、hyueapi direct NO_PROXY、裸 Responses API、D601 Code Queue runner 对照、loopback forwarder 对照、`codex exec --json`、最终 app-server stdio `completed` + 非空 reply。裸 HTTP/SSE 请求通过只能证明 upstream、认证和模型可用;没有 Codex CLI/app-server 通过前,不得进入正式 GitOps/CI/CD 发布,也不得把 Workbench 状态标成 DEV-LIVE reply pass。 Codex app-server 当前要求 provider `wire_api="responses"`,不得把 DeepSeek profile 切到旧 `chat` wire API。DeepSeek profile 的真实 Responses 转换层固定使用 Moon Bridge;不要在 HWLAB 里手写完整 Responses-to-Chat/Anthropic 转换器,因为这会破坏 Moon Bridge 对 prompt cache、tool-result 顺序和模型目录的成熟处理。Codex 发往 `/v1/responses` 的请求体可能带 `Content-Encoding: zstd`,而 Moon Bridge 不负责解压 Codex zstd body。因此 GitOps 中的 `hwlab-deepseek-proxy` Pod 必须包含 repo-owned `hwlab-deepseek-responses-bridge` sidecar:Service 端口 4000 指向 bridge,bridge 只做 zstd request body 解压、删除 `Content-Encoding`/重写 `Content-Length`、丢弃非 `function` tool 类型和 Codex-style `GET /v1/models` 目录适配,再把请求转发给 4001 的 Moon Bridge。DeepSeek API 只接受 `function` tools,bridge 必须在转发前丢弃 Codex Responses 请求里的非 `function` tool 类型(例如 `web_search`、`image_generation`),但不得移除 shell/apply-patch 等 function tools。模型、cache、tool 调用顺序、密钥和真实推理仍由 Moon Bridge 管理;bridge 不新增业务 gate,也不得把兼容失败伪装成 SOURCE/legacy blocker。