feat: support max reasoning effort and rebuild release

This commit is contained in:
shiyue
2026-08-11 08:11:58 +08:00
parent 70be7eb279
commit e7d28935ef
8 changed files with 107 additions and 12 deletions

View File

@@ -0,0 +1,58 @@
# 审计发现
## 初始关注面
- app-server 初始化与能力探测
- collaborationMode / goals / guided input
- thread/start、turn/start 参数形状
- MCP server 线程级注入
- 流式事件与 item 类型
- CLI 版本检测和降级逻辑
- mock / regression 覆盖
## 已确认基线
- 本机实际运行 `codex-cli 0.147.0`,路径为 `/home/hdzx/.local/lib/node_modules/@openai/codex/bin/codex.js`
- 官方 0.147.0 发布于 2026-08-07主要新增 Agent Plugins、会话 sections、`--approve-for-me`、MCP 2026-07-28并包含多项 app-server 协议调整。
- 与 cc-web 最相关的上游提交包括:`Box app-server event payloads``Update tests for current tool call and app-server event types`、持久化 thread sections、显式 user-input blocking、MCP 非阻塞启动和 paginated discovery。
- cc-web 当前通过 `thread/start.config["mcp_servers.*"]` 注入 MCP通过 `collaborationMode.settings` 传 model/reasoning/developer instructions存在 collaborationMode 不支持时的降级设计。
- codebase-memory 索引 `home-cc-web` 为 ready5152 nodes / 10588 edges
## 初步风险判断
- 0.147.0 的新增 CLI/TUI 功能多数不要求 cc-web 立即适配。
- 最值得核对的是 app-server event payload boxing 与 tool-call item 类型变化;这可能直接影响事件分发与前端状态投影。
- thread sections、plugin summaries、MCP 2026-07-28 属于可选新能力,若旧协议仍兼容则是功能缺口而非启动阻断。
## 协议核验
- 0.147.0 `generate-ts --experimental` 仍包含 cc-web 使用的 `thread/start``thread/resume``turn/start``experimentalFeature/enablement/set``collaborationMode/list``item/tool/requestUserInput` 和 approval server requests。
- `CollaborationMode` 仍为 `{ mode, settings }`cc-web 当前参数形状与生成协议一致。
- 0.147.0 新增 thread sections、plugin APIs、thread status active flags、MCP tool read-only hints 等字段/方法cc-web 暂未消费,但这些是增量能力。
- 上游 “Box app-server event payloads” 只改变 Rust 内部 `AppServerEvent` / `InProcessServerEvent` 的内存表示,远程 JSON-RPC 序列化未变。
- plaintext collaboration tool messages 新增的是 raw response item 的可选 `encrypted_function_args`cc-web 不依赖该字段,当前容错投影不会因此失败。
## 最终差距
### P1`request_user_input.isBlocking` 未适配
- 0.147.0 的 `item/tool/requestUserInput` 新增必填 `isBlocking``autoResolutionMs` 已弃用。
- Plan 模式发送 `isBlocking: true`客户端应持续等待明确回答Default 模式在实验特性开启时发送 `isBlocking: false`,客户端不应无限阻塞。
- cc-web 当前无条件创建 10 分钟 timer并且发给前端的消息不携带 `isBlocking`;因此 Default 模式非阻塞请求会被错误地当作阻塞请求处理。
- mock 没有发送 `isBlocking`,回归也只断言 questions导致现有测试无法发现 0.147.0 这项变化。
### P2MCP 2026-07-28 仅需预防性收口
- 0.147.0 中 `mcp_2026_07_28` 是 under-development 且默认 false当前不会影响 cc-web。
- cc-web MCP server 目前直接回显客户端传来的任意 `protocolVersion`,等于声明支持并未实现的未来协议;启用 2026 协议后存在协商不严谨风险。
- 建议显式维护支持版本集合,并对未知版本按 MCP 协商规则选定受支持版本;`tools/list` 的 cursor/nextCursor 可作为启用 2026 协议前的兼容门槛。
### P3可选产品能力
- thread sections、插件目录/安装、thread status active flags、MCP `readOnlyHint` 都是增量能力;不适配不影响现有聊天、协作、审批和 MCP 主链路。
## 验证结果
- `npm run regression`:通过。
- 0.147.0 experimental TS / JSON Schema生成成功。
- 本机 0.147.0 的 feature 状态:`goals` stable/on`plugins` stable/on`mcp_2026_07_28` under-development/off`default_mode_request_user_input` under-development/off。

View File

@@ -0,0 +1,7 @@
# 审计进度
- 2026-08-10开始 Codex CLI 0.147.0 适配审计已读取项目约定、OpenAI 文档技能和文件化规划技能。
- 2026-08-10Codex manual helper 因 developers.openai.com HEAD 403 失败,已记录并切换官方后备来源。
- 2026-08-10已从 OpenAI 官方 GitHub release/compare 获取 0.147.0 变更,并确认本机版本与 cc-web 主要接入函数。
- 2026-08-10已生成 0.147.0 experimental TypeScript/JSON Schema 并逐项核对 cc-web 使用的请求、通知和参数形状。
- 2026-08-10完整回归通过确认一个 P1 guided-input 语义差距、一个 P2 MCP 协商风险,其余 0.147.0 变化为兼容或可选能力。

View File

@@ -0,0 +1,25 @@
# Codex CLI 0.147.0 适配审计
## 目标
判断 cc-web 对 Codex CLI / app-server 0.147.0 是否已跟上,并给出有证据的兼容性结论与改造优先级。
## 阶段
- [x] 获取 0.147.0 官方/上游变更事实
- [x] 确认 cc-web 当前 Codex 接入架构与版本假设
- [x] 逐项核对协议、事件、配置和工具兼容性
- [x] 运行现有相关回归测试与最小协议探测
- [x] 汇总结论、风险与建议改造清单
## 约束
- 本轮只审计,不修改业务代码。
- 代码理解优先使用 codebase-memory-mcprg 仅作行号和文本校验。
- 不使用 graphify。
- 不覆盖用户现有改动。
## 错误记录
- 官方 Codex manual helper 的 HEAD 请求返回 HTTP 403不重复同一路径`openai-docs` 规则降级到官方 Docs MCP / 官方 GitHub 发布源。
- 首次用 cc-web client 做只读 app-server 探测时进程约 25 秒后无输出退出;不重复该封装路径,改用 schema 生成、现有回归和当前真实会话证据交叉验证。

View File

@@ -211,7 +211,7 @@ function createAgentRuntime(deps) {
// cc-web UI supports "gpt-5.4(high)" style selection, but Codex CLI expects:
// - model: "gpt-5.4"
// - reasoning effort: config key `model_reasoning_effort = "high"`
const m = raw.match(/^(.*)\((low|medium|high|xhigh|ultra)\)\s*$/i);
const m = raw.match(/^(.*)\((low|medium|high|xhigh|max|ultra)\)\s*$/i);
if (m) {
const base = String(m[1] || '').trim();
const lvl = String(m[2] || '').trim().toLowerCase();

View File

@@ -10796,6 +10796,7 @@
{ value: 'medium', label: 'medium', desc: '中等 thinking' },
{ value: 'high', label: 'high', desc: '更强 thinking' },
{ value: 'xhigh', label: 'xhigh', desc: '高强度 thinking' },
{ value: 'max', label: 'max', desc: '最大强度 thinking' },
{ value: 'ultra', label: 'ultra', desc: '最高强度 thinking' },
];
showOptionPicker('选择 Thinking 强度', thinkingOptions, current.level || '', (lvl) => {

View File

@@ -4621,12 +4621,16 @@ function assertMultiAgentV2CompatibilityContract() {
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
assert(
/CODEX_REASONING_LEVELS\s*=\s*new Set\(\[[^\]]*'ultra'/.test(serverSource),
'Codex config model parsing should accept the ultra reasoning level'
/CODEX_REASONING_LEVELS\s*=\s*new Set\(\[[^\]]*'max'[^\]]*'ultra'/.test(serverSource),
'Codex config model parsing should accept the max and ultra reasoning levels'
);
assert(
/low\|medium\|high\|xhigh\|ultra/.test(runtimeSource),
'Codex CLI model suffix parsing should accept ultra'
/low\|medium\|high\|xhigh\|max\|ultra/.test(runtimeSource),
'Codex CLI model suffix parsing should accept max and ultra'
);
assert(
frontendSource.includes("{ value: 'max', label: 'max'"),
'Codex model picker should expose max reasoning'
);
assert(
frontendSource.includes("{ value: 'ultra', label: 'ultra'"),
@@ -5665,7 +5669,7 @@ async function main() {
}, null, 2));
createFakeClaudeHistory(homeDir);
createFakeCodexConfig(homeDir, { reasoningEffort: 'ultra' });
createFakeCodexConfig(homeDir, { reasoningEffort: 'max' });
const codexFixture = createFakeCodexHistory(homeDir);
const codexAppImportFixture = createFakeCodexHistory(homeDir, {
threadId: 'codexapp-import-thread',
@@ -5815,7 +5819,7 @@ async function main() {
ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', cwd: codexInitCwd, mode: 'plan' }));
const codexSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codex' && msg.cwd === codexInitCwd);
assert(codexSession.mode === 'plan', 'Codex new_session should follow requested mode');
assert(codexSession.model === 'gpt-5.5(ultra)', 'Codex new_session should preserve ultra from ~/.codex/config.toml');
assert(codexSession.model === 'gpt-5.5(max)', 'Codex new_session should preserve max from ~/.codex/config.toml');
ws.send(JSON.stringify({ type: 'set_session_pinned', sessionId: codexSession.sessionId, pinned: true }));
const pinnedAck = await nextMessage(messages, ws, (msg) => msg.type === 'session_pinned' && msg.sessionId === codexSession.sessionId);
@@ -6441,7 +6445,7 @@ async function main() {
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(firstMessageSession.sessionId.slice(0, 8)));
assert(spawnLine && !spawnLine.includes('--search') && spawnLine.includes('--image'), 'Codex exec should attach images and not append unsupported --search flag');
const parsedSpawnLine = JSON.parse(spawnLine);
assert(parsedSpawnLine.args.includes('model_reasoning_effort="ultra"'), 'Codex exec should pass the ultra reasoning level through model_reasoning_effort');
assert(parsedSpawnLine.args.includes('model_reasoning_effort="max"'), 'Codex exec should pass the max reasoning level through model_reasoning_effort');
const allSpawnsForSession = processLog
.trim()
@@ -6506,7 +6510,7 @@ async function main() {
].join('\n'));
ws.send(JSON.stringify({ type: 'new_session', agent: 'codexapp', cwd: codexAppCwd, mode: 'yolo' }));
const codexAppSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codexapp' && msg.cwd === codexAppCwd);
assert(codexAppSession.model === 'gpt-5.5(ultra)', 'Codex App new_session should preserve the ultra default Codex model');
assert(codexAppSession.model === 'gpt-5.5(max)', 'Codex App new_session should preserve the max default Codex model');
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-skill', trigger: '$', query: 'reg', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));
const codexAppSkillComposer = await nextMessage(messages, ws, (msg) => msg.type === 'composer_suggestions' && msg.requestId === 'reg-codexapp-skill');
@@ -6522,7 +6526,7 @@ async function main() {
assert(/"hasDeveloperInstructions":true/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should include sub-agent developer instructions');
assert(/"hasSchemaDrivenSubagents":true/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should use runtime-schema-driven sub-agent guidance');
assert(/"hasLegacyV1Guidance":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should omit legacy V1 fork/wait guidance');
assert(/"reasoningEffort":"ultra"/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should pass ultra reasoning_effort');
assert(/"reasoningEffort":"max"/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration settings should pass max reasoning_effort');
assert(/"hasTopLevelModel":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration turn should not duplicate model at top level');
assert(/"hasTopLevelEffort":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration turn should not duplicate effort at top level');
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);

View File

@@ -797,7 +797,7 @@ const MCP_PROMPT_RESPONSE_MAX_CHARS = 20000;
// Codex 默认模型优先读取 ~/.codex/config.toml缺失时再回退到旧默认值。
const FALLBACK_CODEX_MODEL = 'gpt-5.4';
const CODEX_REASONING_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'ultra']);
const CODEX_REASONING_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
const CCWEB_TITLE_TOOL_INSTRUCTIONS = [
'Use the ccweb title tool sparingly. For a new chat, call "mcp__ccweb__ccweb_set_title" or "ccweb_set_title" once after the user\'s initial request is clear, and set a concise task title.',
'Do not rename the chat for routine progress, substeps, implementation details, or slightly better wording. Rename only when the user\'s primary objective changes substantially and the existing title would be misleading.',
@@ -10347,7 +10347,7 @@ function handleCodexAppServerExit(signature, info = {}) {
function codexAppModelSettings(session) {
const raw = String(session?.model || getDefaultCodexModel() || '').trim();
const match = raw.match(/^(.*)\((low|medium|high|xhigh|ultra)\)\s*$/i);
const match = raw.match(/^(.*)\((low|medium|high|xhigh|max|ultra)\)\s*$/i);
if (!match) return { model: raw || null, effort: null };
return {
model: String(match[1] || '').trim() || null,