修复 Codex MCP type 配置并更新发布包
This commit is contained in:
14
.planning/2026-10-08-codex-mcp-without-type/findings.md
Normal file
14
.planning/2026-10-08-codex-mcp-without-type/findings.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 已知事实
|
||||||
|
- 接续时 git 工作树干净;本次尚未修改业务源码。
|
||||||
|
- Codebase Memory 项目 home-cc-web 当前索引 ready。
|
||||||
|
- 历史定位:CLI 注入在 lib/agent-runtime.js;App 注入在 server.js;Gitea 构造在 lib/gitea-workflow-codex.js。
|
||||||
|
- 用户更正为 `/home/hdzx/.codex/config.toml`:TOML 可解析,3 个 MCP server 都无顶层 `type`,无需改写。
|
||||||
|
- 用户要求删除 Codex 出站配置的 type,同时保留旧输入兼容。
|
||||||
|
- 当前本机 Codex 版本为 0.161.0,不能沿用此前 0.157.1 的版本说法。
|
||||||
|
- 用户配置服务为 playwright/laya(url)和 fastctx(command),均不含 type。
|
||||||
|
- 修改前配置 SHA256:fbbdd99bda63d1776cc513d699936bcbb4b8cdc2ef1d913fba5414b7449725f9;交付前核对是否未变。
|
||||||
|
- 计划复审通过;按 Trellis 约定由实现子代理修改代码与回归,主代理管理用户配置核查和收尾。
|
||||||
|
- 已记录 Codex MCP 配置出口约定,防止后续再次把内部 type 直接注入线程配置。
|
||||||
|
- 共享出站函数只复制并删除服务器顶层 type,原对象及嵌套 env.type 保留;CLI 独立省略该参数。
|
||||||
|
- App 在线程配置返回前统一处理,Gitea thread.config 使用副本而 mcpServer 内部元数据保留。
|
||||||
|
- 回归已直接检查 CLI 的 ccweb/project 参数及 App 首次 start、resume、重载后的字段存在性,Gitea 检查内部元数据和其他连接字段保留。
|
||||||
13
.planning/2026-10-08-codex-mcp-without-type/progress.md
Normal file
13
.planning/2026-10-08-codex-mcp-without-type/progress.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 进度
|
||||||
|
- 2026-10-08:接续中断任务,核对工作树、读取跟踪技能、确认代码索引和指定配置可访问性。
|
||||||
|
- 已建立三步计划与独立任务记录;不重启服务。
|
||||||
|
- 用户更正配置路径,已确认 3 个 MCP server 无 `type`;完成检查阶段。
|
||||||
|
- FastCtx 两次中止后已用本机工具核实,未输出敏感配置。
|
||||||
|
- 已派发实现代理,覆盖 CLI、App 与 Gitea 三个出口;当前本机版本 0.161.0。
|
||||||
|
- 跟踪文档补丁因上下文不匹配和 PRD 尚不存在失败各一次,已按当前文件状态修正;业务源码未受影响。
|
||||||
|
- 代码修改完成,独立代码审查通过;补齐 CLI ccweb、App start/resume 的直接断言。
|
||||||
|
- `timeout 60s npm run regression` 已完成且退出码 0,日志为 `/tmp/ccweb-codex-mcp-type-regression.log`,输出 Regression checks passed。
|
||||||
|
- Gitea 单元检查及所有修改 JS 的语法检查通过,最后增补的回归和单元文件已再次语法检查;git diff --check 通过。
|
||||||
|
- 当前仅剩配置指纹复核及本任务 CSV 清理;全程未重启服务。
|
||||||
|
- 最终核对用户配置指纹未变、3 个服务均无 type;本次 CSV 全部完成并清理,任务收尾。
|
||||||
|
- 修复位于源文件;未构建发布包、未重启运行进程、未提交或推送。
|
||||||
34
.planning/2026-10-08-codex-mcp-without-type/task_plan.md
Normal file
34
.planning/2026-10-08-codex-mcp-without-type/task_plan.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Codex MCP 配置出口省略 type
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
兼容已有 cc-web MCP 配置,在传给 Codex CLI 和 App Server 时省略服务器顶层的 `type`;检查并仅在用户指定 `/home/hdzx/.codex/config.toml` 确有 MCP `type` 时清理。
|
||||||
|
|
||||||
|
## 约束
|
||||||
|
- 不重启、重载或停止 cc-web 服务。
|
||||||
|
- 保留内部传输类型元数据、旧输入解析以及 command/url/env/认证/超时等连接字段。
|
||||||
|
- 不清理 disable_response_storage 或其他告警字段,不替换为其他用户的配置。
|
||||||
|
- 不覆盖其他任务的计划文件,不提交或推送。
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
1. 核对当前实现、回归覆盖和指定 Codex 用户配置:completed
|
||||||
|
2. 在 Codex 出站配置中移除 type 并保留旧配置兼容:completed
|
||||||
|
3. 运行回归检查并清理本次任务跟踪文件:completed
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
- CLI 参数和 App Server thread/start、resume、fork 的 MCP 配置无顶层 type。
|
||||||
|
- 内置 ccweb、项目 MCP、Gitea MCP 的连接设置仍完整,旧含 type 配置可解析。
|
||||||
|
- 回归验证明确检查 type 字段不存在,不能只从 command/url 推导类型后断言。
|
||||||
|
- 指定用户配置当前可读,包含 3 个 MCP server,均无顶层 `type`;保持原文件不变。若后续需修改,在修改前创建同目录时间戳备份,仅改 MCP 段中的 `type`,并对比其他配置值保持不变。
|
||||||
|
- 执行 `timeout 60s npm run regression`、`timeout 60s node scripts/gitea-workflow-codex-unit.js`,对修改的 JS 运行 `node --check`,并检查 `git diff --check`。
|
||||||
|
- 验证矩阵:CLI ccweb/project 注入无 `.type`;App start/resume/fork 配置无显式 `type` 且 command/url/env/认证/超时完整;Gitea 线程配置无 `type`,内部类型元数据仍存在;含 `type` 的旧 TOML fixture 仍能解析。
|
||||||
|
- 完成后只删除本任务创建的临时 CSV;该清理属于用户已要求的任务跟踪流程,不需要重复请求授权。
|
||||||
|
|
||||||
|
## 已知限制与错误
|
||||||
|
- 用户更正路径为 `/home/hdzx/.codex/config.toml`;3 个 MCP server 均无显式 `type`,此项已经满足要求,未改写配置。
|
||||||
|
- 先前官方文档访问返回 HTTP 403;不重复已失败的 strict-config 子命令实验。
|
||||||
|
- FastCtx 连续两次调用中止,改用本机工具检查;不输出用户配置值。
|
||||||
|
|
||||||
|
## 交付状态
|
||||||
|
- 源码修复、必要协议断言、全量回归、Gitea 单元、语法和差异检查均通过。
|
||||||
|
- 用户配置 SHA256 与检查前相同,3 个 MCP 服务均无 type。
|
||||||
|
- 未重启服务;当前常驻进程尚未加载本次源码改动。
|
||||||
8
.trellis/spec/backend/codex-mcp-config.md
Normal file
8
.trellis/spec/backend/codex-mcp-config.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Codex MCP 配置输出
|
||||||
|
|
||||||
|
- cc-web 内部允许读取旧 MCP `type` 并保留传输类型元数据,用于工具目录和工作流判断。
|
||||||
|
- 发送给 Codex 的 MCP 服务器配置不输出顶层 `type`。Codex 通过 `command` 或 `url` 识别传输方式。
|
||||||
|
- 使用 `lib/codex-mcp-config.js` 生成出站副本,不直接删除内部对象上的字段;嵌套 `env.type` 等同名字段保留。
|
||||||
|
- CLI 的内置和项目 MCP 参数均省略 `type`;App Server 在公共线程配置出口统一处理,覆盖 start/resume/fork 和 Gitea 注入。
|
||||||
|
- Gitea 内部 `mcpServer` 元数据和对外 `config` 分开,线程配置使用出站副本。
|
||||||
|
- 回归直接检查实际参数或协议对象没有顶层 `type`,同时检查连接、环境、认证及超时字段保持完整。仅从 `command`/`url` 推导传输方式无法证明字段已经省略。
|
||||||
@@ -19,6 +19,7 @@ This directory contains guidelines for backend development. Fill in each file wi
|
|||||||
| [Error Handling](./error-handling.md) | Error types, handling strategies | To fill |
|
| [Error Handling](./error-handling.md) | Error types, handling strategies | To fill |
|
||||||
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill |
|
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill |
|
||||||
| [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels | To fill |
|
| [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels | To fill |
|
||||||
|
| [Codex MCP 配置输出](./codex-mcp-config.md) | 旧类型配置兼容及 Codex 配置出口 | 已记录 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
2
.trellis/tasks/10-08-codex-mcp-without-type/check.jsonl
Normal file
2
.trellis/tasks/10-08-codex-mcp-without-type/check.jsonl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||||
|
{"file": ".trellis/spec/backend/index.md", "reason": "质量检查规范入口"}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||||
|
{"file": ".trellis/spec/backend/index.md", "reason": "后端实现规范入口"}
|
||||||
25
.trellis/tasks/10-08-codex-mcp-without-type/prd.md
Normal file
25
.trellis/tasks/10-08-codex-mcp-without-type/prd.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Codex MCP 配置出口省略 type
|
||||||
|
|
||||||
|
## 用户已确认的需求
|
||||||
|
- cc-web 发送给 Codex 的 MCP 配置不再包含服务器顶层 `type`。
|
||||||
|
- 保留旧配置读取兼容及内部类型元数据,仅在出站配置省略该字段。
|
||||||
|
- 用户更正为 `/home/hdzx/.codex/config.toml`;已确认 3 个 MCP 服务均没有 `type`,无需改写。
|
||||||
|
|
||||||
|
## 范围与限制
|
||||||
|
- CLI:`lib/agent-runtime.js` 的内置 ccweb 与项目 MCP 参数。
|
||||||
|
- App Server:`server.js` 的 start/resume/fork 共用线程配置出口,包括 Gitea 分支。
|
||||||
|
- Gitea:保留 `buildGiteaMcpServerConfig()` 内部元数据,线程配置出口不含 `type`。
|
||||||
|
- 原有 command/url/env/env_vars/认证/超时字段保持不变,不删除嵌套 env 内名为 type 的变量。
|
||||||
|
- 不删除 disable_response_storage 或其他未知字段,不重启/重载/停止服务,不提交/推送。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- 在实际参数/协议载荷中断言服务器顶层没有 `type`,不能只推导传输类型。
|
||||||
|
- 旧含 type 的 TOML fixture 正常读取,连接和环境上下文字段保持完整。
|
||||||
|
- `timeout 60s npm run regression`。
|
||||||
|
- `timeout 60s node scripts/gitea-workflow-codex-unit.js`。
|
||||||
|
- 所有修改的 JS 文件运行 `node --check`,`git diff --check` 通过。
|
||||||
|
- 按需使用已有 focused target 对相关路径检查,不重复已通过且无新变化的测试。
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
- `.planning/2026-10-08-codex-mcp-without-type/task_plan.md`
|
||||||
|
- `.planning/2026-10-08-codex-mcp-without-type/findings.md`
|
||||||
26
.trellis/tasks/10-08-codex-mcp-without-type/task.json
Normal file
26
.trellis/tasks/10-08-codex-mcp-without-type/task.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"id": "codex-mcp-without-type",
|
||||||
|
"name": "codex-mcp-without-type",
|
||||||
|
"title": "Codex MCP type 配置出口兼容修复",
|
||||||
|
"description": "",
|
||||||
|
"status": "completed",
|
||||||
|
"dev_type": null,
|
||||||
|
"scope": null,
|
||||||
|
"package": null,
|
||||||
|
"priority": "P2",
|
||||||
|
"creator": "shiyue",
|
||||||
|
"assignee": "shiyue",
|
||||||
|
"createdAt": "2026-10-08",
|
||||||
|
"completedAt": "2026-10-08",
|
||||||
|
"branch": null,
|
||||||
|
"base_branch": "main",
|
||||||
|
"worktree_path": null,
|
||||||
|
"commit": null,
|
||||||
|
"pr_url": null,
|
||||||
|
"subtasks": [],
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"relatedFiles": [],
|
||||||
|
"notes": "",
|
||||||
|
"meta": {}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -105,7 +105,6 @@ function createAgentRuntime(deps) {
|
|||||||
? ccwebMcpServerArgs
|
? ccwebMcpServerArgs
|
||||||
: [ccwebMcpServerArg];
|
: [ccwebMcpServerArg];
|
||||||
args.push(
|
args.push(
|
||||||
'-c', 'mcp_servers.ccweb.type="stdio"',
|
|
||||||
'-c', `mcp_servers.ccweb.command=${tomlString(nodePath || 'node')}`,
|
'-c', `mcp_servers.ccweb.command=${tomlString(nodePath || 'node')}`,
|
||||||
'-c', `mcp_servers.ccweb.args=${tomlStringArray(serverArgs)}`,
|
'-c', `mcp_servers.ccweb.args=${tomlStringArray(serverArgs)}`,
|
||||||
'-c', `mcp_servers.ccweb.env_vars=${tomlStringArray(envVars)}`,
|
'-c', `mcp_servers.ccweb.env_vars=${tomlStringArray(envVars)}`,
|
||||||
@@ -119,7 +118,7 @@ function createAgentRuntime(deps) {
|
|||||||
const config = mcpServer?.config && typeof mcpServer.config === 'object' ? mcpServer.config : null;
|
const config = mcpServer?.config && typeof mcpServer.config === 'object' ? mcpServer.config : null;
|
||||||
if (!server || !config) return;
|
if (!server || !config) return;
|
||||||
const prefix = `mcp_servers.${tomlKeySegment(server)}`;
|
const prefix = `mcp_servers.${tomlKeySegment(server)}`;
|
||||||
for (const key of ['type', 'command', 'args', 'env', 'env_vars', 'url', 'bearer_token_env_var', 'startup_timeout_sec', 'tool_timeout_sec']) {
|
for (const key of ['command', 'args', 'env', 'env_vars', 'url', 'bearer_token_env_var', 'startup_timeout_sec', 'tool_timeout_sec']) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(config, key)) continue;
|
if (!Object.prototype.hasOwnProperty.call(config, key)) continue;
|
||||||
args.push('-c', `${prefix}.${key}=${tomlValue(config[key])}`);
|
args.push('-c', `${prefix}.${key}=${tomlValue(config[key])}`);
|
||||||
}
|
}
|
||||||
|
|||||||
16
lib/codex-mcp-config.js
Normal file
16
lib/codex-mcp-config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成发送给 Codex 的 MCP server 配置副本。
|
||||||
|
*
|
||||||
|
* 内部配置仍保留 type 作为传输类型元数据,Codex 出站协议只省略
|
||||||
|
* 服务器顶层 type;嵌套 env 等对象保持原样。
|
||||||
|
*/
|
||||||
|
function toCodexMcpServerConfig(config) {
|
||||||
|
if (!config || typeof config !== 'object' || Array.isArray(config)) return config;
|
||||||
|
const result = { ...config };
|
||||||
|
delete result.type;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { toCodexMcpServerConfig };
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { toCodexMcpServerConfig } = require('./codex-mcp-config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gitea Webhook 工作流与 Codex App 之间的薄适配层。
|
* Gitea Webhook 工作流与 Codex App 之间的薄适配层。
|
||||||
*
|
*
|
||||||
@@ -118,7 +120,7 @@ function buildGiteaThreadConfig(options = {}) {
|
|||||||
return {
|
return {
|
||||||
cwd,
|
cwd,
|
||||||
config: {
|
config: {
|
||||||
'mcp_servers.gitea': gitea,
|
'mcp_servers.gitea': toCodexMcpServerConfig(gitea),
|
||||||
},
|
},
|
||||||
// 便于调用方在需要时直接合并到 thread/start 参数;server.js 使用 config。
|
// 便于调用方在需要时直接合并到 thread/start 参数;server.js 使用 config。
|
||||||
mcpServer: gitea,
|
mcpServer: gitea,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/** Gitea Workflow Codex/App 协议适配器离线单测。 */
|
/** Gitea Workflow Codex/App 协议适配器离线单测。 */
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
const { toCodexMcpServerConfig } = require('../lib/codex-mcp-config');
|
||||||
const {
|
const {
|
||||||
buildGiteaMcpServerConfig,
|
buildGiteaMcpServerConfig,
|
||||||
buildGiteaThreadConfig,
|
buildGiteaThreadConfig,
|
||||||
@@ -17,6 +18,21 @@ const {
|
|||||||
} = require('../lib/gitea-workflow-codex');
|
} = require('../lib/gitea-workflow-codex');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
const legacyMcp = {
|
||||||
|
type: 'streamable_http',
|
||||||
|
url: 'https://mcp.example/tools',
|
||||||
|
bearer_token_env_var: 'MCP_TOKEN',
|
||||||
|
startup_timeout_sec: 20,
|
||||||
|
tool_timeout_sec: 120,
|
||||||
|
env: { type: '保留这个环境变量' },
|
||||||
|
};
|
||||||
|
const outgoingMcp = toCodexMcpServerConfig(legacyMcp);
|
||||||
|
assert.equal(Object.hasOwn(outgoingMcp, 'type'), false);
|
||||||
|
assert.equal(legacyMcp.type, 'streamable_http');
|
||||||
|
assert.notEqual(outgoingMcp, legacyMcp);
|
||||||
|
const { type: legacyType, ...connectionFields } = legacyMcp;
|
||||||
|
assert.deepEqual(outgoingMcp, connectionFields);
|
||||||
|
|
||||||
const mcp = buildGiteaMcpServerConfig({
|
const mcp = buildGiteaMcpServerConfig({
|
||||||
host: 'https://gitea.example/',
|
host: 'https://gitea.example/',
|
||||||
accessToken: 'secret-token',
|
accessToken: 'secret-token',
|
||||||
@@ -31,7 +47,10 @@ async function main() {
|
|||||||
host: 'https://gitea.example',
|
host: 'https://gitea.example',
|
||||||
accessToken: 'secret-token',
|
accessToken: 'secret-token',
|
||||||
});
|
});
|
||||||
assert.equal(thread.config['mcp_servers.gitea'].type, 'stdio');
|
assert.equal(Object.hasOwn(thread.config['mcp_servers.gitea'], 'type'), false);
|
||||||
|
assert.equal(thread.mcpServer.type, 'stdio');
|
||||||
|
assert.equal(thread.config['mcp_servers.gitea'].command, 'gitea-mcp');
|
||||||
|
assert.deepEqual(thread.config['mcp_servers.gitea'].args.slice(0, 4), ['-t', 'stdio', '-H', 'https://gitea.example']);
|
||||||
assert.equal(thread.config['mcp_servers.gitea'].env.GITEA_ACCESS_TOKEN, 'secret-token');
|
assert.equal(thread.config['mcp_servers.gitea'].env.GITEA_ACCESS_TOKEN, 'secret-token');
|
||||||
assert.equal(Object.hasOwn(thread.config, 'model'), false);
|
assert.equal(Object.hasOwn(thread.config, 'model'), false);
|
||||||
|
|
||||||
|
|||||||
@@ -690,6 +690,8 @@ function completeMcpToolTurn(thread, turnId) {
|
|||||||
webSearchMode: thread.config?.web_search || null,
|
webSearchMode: thread.config?.web_search || null,
|
||||||
hasCcwebMcpConfig: Boolean(ccwebConfig),
|
hasCcwebMcpConfig: Boolean(ccwebConfig),
|
||||||
hasProjectMcpConfig: Boolean(projectConfig),
|
hasProjectMcpConfig: Boolean(projectConfig),
|
||||||
|
ccwebHasTopLevelType: Boolean(ccwebConfig && Object.hasOwn(ccwebConfig, 'type')),
|
||||||
|
projectHasTopLevelType: Boolean(projectConfig && Object.hasOwn(projectConfig, 'type')),
|
||||||
ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)),
|
ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)),
|
||||||
ccwebUrl: ccwebConfig?.url || null,
|
ccwebUrl: ccwebConfig?.url || null,
|
||||||
ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null,
|
ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null,
|
||||||
|
|||||||
@@ -8540,6 +8540,7 @@ async function main() {
|
|||||||
.split('\n')
|
.split('\n')
|
||||||
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(crossTargetSession.sessionId.slice(0, 8)));
|
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(crossTargetSession.sessionId.slice(0, 8)));
|
||||||
assert(mcpSpawnLine && mcpSpawnLine.includes('mcp_servers.ccweb.command') && mcpSpawnLine.includes('mcp_servers.ccweb.env_vars'), 'Codex spawn should inject ccweb MCP config');
|
assert(mcpSpawnLine && mcpSpawnLine.includes('mcp_servers.ccweb.command') && mcpSpawnLine.includes('mcp_servers.ccweb.env_vars'), 'Codex spawn should inject ccweb MCP config');
|
||||||
|
assert(!mcpSpawnLine.includes('mcp_servers.ccweb.type'), 'Codex CLI ccweb MCP 配置应省略服务器顶层 type');
|
||||||
assert(mcpSpawnLine.includes('server.js') && mcpSpawnLine.includes('--ccweb-mcp-server'), 'Codex spawn should launch ccweb MCP through server.js in Node mode');
|
assert(mcpSpawnLine.includes('server.js') && mcpSpawnLine.includes('--ccweb-mcp-server'), 'Codex spawn should launch ccweb MCP through server.js in Node mode');
|
||||||
assert(!mcpSpawnLine.includes(internalMcpToken), 'Codex spawn log should not expose internal MCP token');
|
assert(!mcpSpawnLine.includes(internalMcpToken), 'Codex spawn log should not expose internal MCP token');
|
||||||
const projectMcpSpawnLine = processLogAfterMcp
|
const projectMcpSpawnLine = processLogAfterMcp
|
||||||
@@ -8547,6 +8548,7 @@ async function main() {
|
|||||||
.split('\n')
|
.split('\n')
|
||||||
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(codexSession.sessionId.slice(0, 8)));
|
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(codexSession.sessionId.slice(0, 8)));
|
||||||
assert(projectMcpSpawnLine && projectMcpSpawnLine.includes('mcp_servers.reg-project.command'), 'Codex spawn should inject project MCP config from session cwd');
|
assert(projectMcpSpawnLine && projectMcpSpawnLine.includes('mcp_servers.reg-project.command'), 'Codex spawn should inject project MCP config from session cwd');
|
||||||
|
assert(!projectMcpSpawnLine.includes('mcp_servers.reg-project.type'), 'Codex CLI project MCP config should omit the server top-level type');
|
||||||
|
|
||||||
ws.send(JSON.stringify({ type: 'list_cwd_suggestions' }));
|
ws.send(JSON.stringify({ type: 'list_cwd_suggestions' }));
|
||||||
const cwdSuggestions = await nextMessage(messages, ws, (msg) => msg.type === 'cwd_suggestions');
|
const cwdSuggestions = await nextMessage(messages, ws, (msg) => msg.type === 'cwd_suggestions');
|
||||||
@@ -8826,6 +8828,8 @@ async function main() {
|
|||||||
const codexAppEnabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
const codexAppEnabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
||||||
assert(/"threadStartWebSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start should pass web_search=live when Web Search is enabled');
|
assert(/"threadStartWebSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start should pass web_search=live when Web Search is enabled');
|
||||||
assert(/"webSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread config should keep web_search=live while Web Search is enabled');
|
assert(/"webSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread config should keep web_search=live while Web Search is enabled');
|
||||||
|
assert(/"ccwebHasTopLevelType": false/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start 的 ccweb 配置应省略顶层 type');
|
||||||
|
assert(/"projectHasTopLevelType": false/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start 的旧项目配置应省略顶层 type');
|
||||||
assert(!/"webSearchToolConfig"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App should use top-level web_search instead of tools.web_search as the mode switch');
|
assert(!/"webSearchToolConfig"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App should use top-level web_search instead of tools.web_search as the mode switch');
|
||||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||||
|
|
||||||
@@ -8857,6 +8861,8 @@ async function main() {
|
|||||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp dynamic after web search disabled prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
ws.send(JSON.stringify({ type: 'message', text: 'codexapp dynamic after web search disabled prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
||||||
const codexAppDisabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
const codexAppDisabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
||||||
assert(/"threadConfigMethod": "thread\/resume"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App existing thread should refresh config through thread/resume');
|
assert(/"threadConfigMethod": "thread\/resume"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App existing thread should refresh config through thread/resume');
|
||||||
|
assert(/"ccwebHasTopLevelType": false/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume 的 ccweb 配置应省略顶层 type');
|
||||||
|
assert(/"projectHasTopLevelType": false/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume 的项目配置应省略顶层 type');
|
||||||
assert(/"webSearchMode": "disabled"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume should pass web_search=disabled after Web Search is disabled');
|
assert(/"webSearchMode": "disabled"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume should pass web_search=disabled after Web Search is disabled');
|
||||||
assert(!/"webSearchToolConfig"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App should not use tools.web_search as the Web Search mode switch');
|
assert(!/"webSearchToolConfig"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App should not use tools.web_search as the Web Search mode switch');
|
||||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||||
@@ -9141,6 +9147,8 @@ async function main() {
|
|||||||
assert(/currentConversationId/.test(codexAppDynamicTool.result || ''), 'Codex App MCP tool should return ccweb conversation data');
|
assert(/currentConversationId/.test(codexAppDynamicTool.result || ''), 'Codex App MCP tool should return ccweb conversation data');
|
||||||
assert(/"hasCcwebMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass ccweb MCP config');
|
assert(/"hasCcwebMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass ccweb MCP config');
|
||||||
assert(/"hasProjectMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass project MCP config from session cwd');
|
assert(/"hasProjectMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass project MCP config from session cwd');
|
||||||
|
assert(/"ccwebHasTopLevelType": false/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP config should omit the server top-level type');
|
||||||
|
assert(/"projectHasTopLevelType": false/.test(codexAppDynamicTool.result || ''), 'Codex App project MCP config should omit the server top-level type');
|
||||||
assert(/"ccwebType": "streamable_http"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should default to shared streamable HTTP');
|
assert(/"ccwebType": "streamable_http"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should default to shared streamable HTTP');
|
||||||
assert(/"ccwebUrl": "http:\/\/127\.0\.0\.1:\d+\/api\/internal\/mcp\/stream\?/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should point to the shared cc-web HTTP endpoint');
|
assert(/"ccwebUrl": "http:\/\/127\.0\.0\.1:\d+\/api\/internal\/mcp\/stream\?/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should point to the shared cc-web HTTP endpoint');
|
||||||
assert(/"ccwebBearerTokenEnvVar": "CC_WEB_CODEX_APP_MCP_TOKEN"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should use bearer_token_env_var for the shared endpoint');
|
assert(/"ccwebBearerTokenEnvVar": "CC_WEB_CODEX_APP_MCP_TOKEN"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should use bearer_token_env_var for the shared endpoint');
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const os = require('os');
|
|||||||
const { spawn, spawnSync } = require('child_process');
|
const { spawn, spawnSync } = require('child_process');
|
||||||
const { WebSocketServer } = require('ws');
|
const { WebSocketServer } = require('ws');
|
||||||
const { createAgentRuntime } = require('./lib/agent-runtime');
|
const { createAgentRuntime } = require('./lib/agent-runtime');
|
||||||
|
const { toCodexMcpServerConfig } = require('./lib/codex-mcp-config');
|
||||||
const { createCodexAppServerClient } = require('./lib/codex-app-server-client');
|
const { createCodexAppServerClient } = require('./lib/codex-app-server-client');
|
||||||
const { createCodexAppWorkerClient } = require('./lib/codex-app-worker-client');
|
const { createCodexAppWorkerClient } = require('./lib/codex-app-worker-client');
|
||||||
const { createCodexAppRuntime } = require('./lib/codex-app-runtime');
|
const { createCodexAppRuntime } = require('./lib/codex-app-runtime');
|
||||||
@@ -14144,6 +14145,11 @@ function codexAppThreadConfig(session, options = {}) {
|
|||||||
cwd: session?.cwd || options.cwd || getDefaultSessionCwd(),
|
cwd: session?.cwd || options.cwd || getDefaultSessionCwd(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
for (const [key, value] of Object.entries(config)) {
|
||||||
|
if (key.startsWith('mcp_servers.') && value && typeof value === 'object') {
|
||||||
|
config[key] = toCodexMcpServerConfig(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user