diff --git a/agent-network/bin/cli.ts b/agent-network/bin/cli.ts index 33b845e6..9025d76d 100644 --- a/agent-network/bin/cli.ts +++ b/agent-network/bin/cli.ts @@ -321,7 +321,12 @@ interface Profile { // Re-export from the pure helper module (src/normalize-runtime.ts) so // unit tests can import without dragging in CLI side-effects. -import { normalizeRuntime, type RuntimeName } from "../src/normalize-runtime"; +import { + normalizeRuntime, + runtimeSkipsCreateVendorPicker, + runtimeUsesAgentNode, + type RuntimeName, +} from "../src/normalize-runtime"; import { findEnvironAliasMatches } from "../src/environ-alias"; export { normalizeRuntime, type RuntimeName }; @@ -1099,6 +1104,7 @@ anet — AI Agent Network CLI (V2) Node Management: anet node create Create a new agent node + --runtime claude-agent-sdk | claude-code-cli | codex-sdk | codex-app-server | grok-build-acp | opencode-cli anet node start Start a node anet node start --all Start every node in cwd (= anet project up) anet node stop Stop a running node @@ -2045,7 +2051,7 @@ async function createInteractiveCommand() { This wizard creates one agent node for this project: - node config: .anet/nodes//config.json - - runtime: claude-code-cli / codex-sdk / claude-agent-sdk / grok-build-acp + - runtime: claude-code-cli / codex-sdk / codex-app-server / claude-agent-sdk / grok-build-acp / opencode-cli - optional Telegram channel: text + images from an allowlist user `); @@ -2246,7 +2252,7 @@ async function createCommand(idOverride?: string) { const id = idOverride || args[1]; if (!id) return createInteractiveCommand(); if (id.startsWith("--")) { - console.error("Usage: anet node create [--runtime claude-code-cli|codex-sdk|claude-agent-sdk|grok-build-acp] [--model ...] [--tools ...]"); + console.error("Usage: anet node create [--runtime claude-code-cli|codex-sdk|codex-app-server|claude-agent-sdk|grok-build-acp|opencode-cli] [--model ...] [--tools ...]"); console.error("Or run fully interactive: anet node create"); process.exit(1); } @@ -2291,7 +2297,7 @@ async function createCommand(idOverride?: string) { const credAlreadyProvided = !!process.env.ANTHROPIC_AUTH_TOKEN || !!process.env.ANTHROPIC_API_KEY || envFlagHasAuth; const explicitRuntime = opts.runtime ? normalizeRuntime(opts.runtime) : undefined; - const runtimeAlreadyExplicit = explicitRuntime === "codex-sdk" || explicitRuntime === "claude-code-cli" || explicitRuntime === "grok-build-acp"; + const runtimeAlreadyExplicit = explicitRuntime ? runtimeSkipsCreateVendorPicker(explicitRuntime) : false; const skipInteractive = credAlreadyProvided || runtimeAlreadyExplicit; // #133 selectRuntime — runtime-first, exported as a helper so create paths @@ -2305,6 +2311,7 @@ async function createCommand(idOverride?: string) { { value: "claude-agent-sdk", name: "claude-agent-sdk — 任意 OpenAI/Anthropic-compat vendor (intern / MiniMax / Claude / GLM / ...)" }, { value: "claude-code-cli", name: "claude-code-cli — Anthropic Claude (Max/Pro plan), 复用 `claude` CLI 登录态" }, { value: "codex-sdk", name: "codex-sdk — OpenAI Codex, 复用 `codex auth login` 登录态" }, + { value: "codex-app-server", name: "codex-app-server — OpenAI Codex TUI runtime (RFC-030 preview; production locked)" }, { value: "grok-build-acp", name: "grok-build-acp — Grok Build ACP, 复用 `grok` CLI 登录态" }, ], }); @@ -2327,6 +2334,8 @@ async function createCommand(idOverride?: string) { console.log("[anet] 请确保已安装 Claude Code CLI 并登录: claude auth login"); } else if (opts.runtime === "codex-sdk") { console.log("[anet] 请确保已执行: codex auth login"); + } else if (opts.runtime === "codex-app-server") { + console.log("[anet] 请确保已执行: codex auth login(RFC-030 preview;生产门仍锁定)"); } else if (opts.runtime === "grok-build-acp") { console.log("[anet] 请确保已安装并登录 Grok Build CLI: grok auth login"); } else { @@ -2854,12 +2863,7 @@ async function launchAgent(id: string, forceNewSession = false) { } } catch {} - if ( - runtime === "codex-sdk" || - runtime === "claude-agent-sdk" || - runtime === "grok-build-acp" || - runtime === "opencode-cli" - ) { + if (runtimeUsesAgentNode(runtime)) { // spawn agent-node const agentArgs = [ "--config", join(nodesDir(), nodeId, "config.json"), diff --git a/agent-network/src/normalize-runtime.test.ts b/agent-network/src/normalize-runtime.test.ts index d070f74c..bb09f8ec 100644 --- a/agent-network/src/normalize-runtime.test.ts +++ b/agent-network/src/normalize-runtime.test.ts @@ -8,7 +8,11 @@ // Explicit `claude-code-cli` choice still works. import { describe, expect, test } from "bun:test"; -import { normalizeRuntime } from "./normalize-runtime"; +import { + normalizeRuntime, + runtimeSkipsCreateVendorPicker, + runtimeUsesAgentNode, +} from "./normalize-runtime"; describe("normalizeRuntime — fallback default is claude-agent-sdk (Vincent no-Max)", () => { test("unknown string → claude-agent-sdk (was claude-code-cli pre-2026-06-28)", () => { @@ -137,3 +141,35 @@ describe("normalizeRuntime — profile object paths", () => { expect(normalizeRuntime({ runtime: "bogus-runtime-name" } as any)).toBe("claude-agent-sdk"); }); }); + +describe("runtimeUsesAgentNode — launcher routing", () => { + test("codex-app-server uses agent-node, never the Claude CLI branch", () => { + expect(runtimeUsesAgentNode("codex-app-server")).toBe(true); + }); + + test("all non-Claude-Code runtimes use agent-node", () => { + for (const runtime of [ + "claude-agent-sdk", + "codex-sdk", + "codex-app-server", + "grok-build-acp", + "opencode-cli", + ] as const) { + expect(runtimeUsesAgentNode(runtime)).toBe(true); + } + }); + + test("claude-code-cli keeps its dedicated launcher", () => { + expect(runtimeUsesAgentNode("claude-code-cli")).toBe(false); + }); +}); + +describe("runtimeSkipsCreateVendorPicker — explicit create flag", () => { + test("codex-app-server remains explicit in a TTY", () => { + expect(runtimeSkipsCreateVendorPicker("codex-app-server")).toBe(true); + }); + + test("claude-agent-sdk still enters its provider picker", () => { + expect(runtimeSkipsCreateVendorPicker("claude-agent-sdk")).toBe(false); + }); +}); diff --git a/agent-network/src/normalize-runtime.ts b/agent-network/src/normalize-runtime.ts index 59280c5e..6d878409 100644 --- a/agent-network/src/normalize-runtime.ts +++ b/agent-network/src/normalize-runtime.ts @@ -22,6 +22,23 @@ export type RuntimeName = /** Operator-facing default for any runtime slot that comes in empty / missing / unrecognized. */ export const DEFAULT_RUNTIME: RuntimeName = "claude-agent-sdk"; +/** + * Runtimes launched through the agent-node supervisor rather than by spawning + * the Claude Code CLI directly. Keep this predicate exhaustive so adding a + * runtime cannot silently fall into the Claude launcher branch. + */ +export function runtimeUsesAgentNode(runtime: RuntimeName): boolean { + return runtime !== "claude-code-cli"; +} + +/** Runtimes whose explicit CLI choice must bypass the API-vendor picker. */ +export function runtimeSkipsCreateVendorPicker(runtime: RuntimeName): boolean { + return runtime === "claude-code-cli" + || runtime === "codex-sdk" + || runtime === "codex-app-server" + || runtime === "grok-build-acp"; +} + // Subset of Profile fields this helper inspects. Keeping it narrow so // the test fixture doesn't need the full Profile shape and so callers // (bin/cli.ts uses the full Profile) can pass anything structurally diff --git a/agent-node/README.md b/agent-node/README.md index e69b0a29..3179933c 100644 --- a/agent-node/README.md +++ b/agent-node/README.md @@ -44,7 +44,7 @@ CLI flags: |---|---|---| | `--alias` | required | unique name in the hub | | `--hub` | `http://127.0.0.1:9200` | CommHub URL | -| `--runtime` | `claude-agent-sdk` | `claude-agent-sdk` / `codex-sdk` / `claude-code-cli` / `grok-build-acp` / `http-api` | +| `--runtime` | `claude-agent-sdk` | `claude-agent-sdk` / `codex-sdk` / `codex-app-server` / `claude-code-cli` / `grok-build-acp` / `http-api` | | `--model` | runtime default | passed through to the SDK | | `--tools` | (none) | `all` or comma-separated list | | `--max-turns` | `50` | upper bound per task | @@ -56,6 +56,7 @@ CLI flags: |---|---|---|---| | `claude-agent-sdk` | [@anthropic-ai/claude-agent-sdk](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) | verified | Anthropic-compatible API; works with MiniMax, DeepSeek, GLM, Kimi, Anthropic, OpenRouter, or custom endpoints | | `codex-sdk` | [@openai/codex-sdk](https://www.npmjs.com/package/@openai/codex-sdk) | unverified end-to-end | unit tests pass, no full E2E with real codex auth | +| `codex-app-server` | local `codex app-server` + TUI bridge | preview; production locked | canonical RFC-030 runtime. The current CLI entry starts the Phase-0 direct path; Policy Gateway, §8 review, merge and production approval remain locked | | `claude-code-cli` | local `claude` CLI | unverified end-to-end | runs locally for Claude Pro subscribers (v0.8.2 fixed the session-resume default-loss bug; see [changelog](https://anet.sh/en/changelog)) | | `grok-build-acp` | local `grok agent stdio` | stable runtime, native MCP injection boundary remains preview | requires Grok Build CLI login; stable for receive/reply, session persistence, and explicit CommHub delegation handled by agent-node | | `http-api` | OpenAI/Anthropic-compatible HTTP | experimental | reads `ANTHROPIC_*`, `OPENAI_*`, or `MINIMAX_CODING_API_KEY` environment variables | diff --git a/agent-node/src/cli.ts b/agent-node/src/cli.ts index 00bbd1e4..91e0f775 100644 --- a/agent-node/src/cli.ts +++ b/agent-node/src/cli.ts @@ -5,6 +5,7 @@ * Runtime: * --runtime claude-agent-sdk → Claude Agent SDK (Claude/MiniMax) * --runtime codex-sdk → Codex SDK (GPT-5.4) + * --runtime codex-app-server → Codex app-server/TUI runtime (RFC-030 preview) * --runtime grok-build-acp → Grok Build ACP (xAI) * * 配置加载: --config > CLI args > env > .anet/nodes//config.json > ~/.anet/config.json > defaults @@ -115,7 +116,7 @@ for (let i = 0; i < argv.length; i++) { 选项: --config 配置文件 (.anet/nodes//config.json) --alias Agent 别名 / CommHub alias (必需) - --runtime claude-agent-sdk (default) | codex-sdk | grok-build-acp + --runtime claude-agent-sdk (default) | codex-sdk | codex-app-server | grok-build-acp --model AI 模型 (codex 默认: gpt-5.5, claude-agent-sdk 默认: claude-sonnet-4-6) --hub CommHub URL --tools 工具列表,逗号分隔 ("all" = 全部) @@ -131,6 +132,7 @@ for (let i = 0; i < argv.length; i++) { Runtime: claude-agent-sdk Claude Agent SDK — Claude/MiniMax/Anthropic 兼容 API codex-sdk Codex SDK — GPT-5.4,复用 codex 登录态 + codex-app-server Codex app-server/TUI runtime — RFC-030 preview(生产门锁定) grok-build-acp Grok Build ACP — xAI Grok Build via "grok agent stdio" `); process.exit(0); diff --git a/docs/rfcs/RFC-030-codex-tui-bridge.md b/docs/rfcs/RFC-030-codex-tui-bridge.md index 7c2a2b51..fa7401cc 100644 --- a/docs/rfcs/RFC-030-codex-tui-bridge.md +++ b/docs/rfcs/RFC-030-codex-tui-bridge.md @@ -658,7 +658,11 @@ Hub 必须先在 loopback 完成管理员初始化,再置于 TLS 反向代理 ## 11. 启动与绑定 UX -以下命令是拟议产品 UX,当前主干尚未实现: +> **历史方案,禁止复制。** 本节的 `codex-sdk --codex-transport +> app-server` 形态已被 Vincent 2026-07-10 的独立 runtime 决策取代。 +> 当前 canonical CLI 见 §18.1a;生产门状态见文首与配套设计 §8。 + +以下命令仅保留为被取代的设计记录: ```bash # 创建一个普通 codex-sdk 节点,但选择共享 app-server transport @@ -870,6 +874,27 @@ codex app-server generate-json-schema --out ./schemas | 网络闭环:`send_task` → 桥 → 真 codex → `send_task` 回 | ✅ | 隔离 hub 真节点 e2e PASS | | `anet node create --runtime codex-app-server` | ✅ | 隔离 hub 真建节点,config 写 `runtime:"codex-app-server"` | +### 18.1a Canonical `anet` CLI(preview 路径) + +Canonical runtime 值是 **`codex-app-server`**,不是 `codex-sdk`。完成 +`codex auth login` 后,可复制粘贴: + +```bash +anet node create codex-human --runtime codex-app-server && anet node start codex-human +``` + +等价的分步命令是: + +```bash +anet node create codex-human --runtime codex-app-server +anet node start codex-human +``` + +`node create` 将 canonical 值写入节点配置;`node start` 通过 +`agent-node --runtime codex-app-server` 启动该 runtime。当前这条入口只启动 +§18 记录的 Phase-0 direct preview path;它**不代表** Policy Gateway、Wave 2、 +§8、merge、production 或 `latest` 已解锁。 + ### 18.1b 明确**未**落地(生产前置, 设计文档 §3.2/§4/§8) - ❌ **Policy Gateway(方案 B)**:唯一 upstream app-server 连接、TUI 经 gateway UDS、request-id 双向重写、事件转发。当前是 TUI 与桥**直接双连接**。