chore: rebuild CentOS7 release package

This commit is contained in:
shiyue
2026-06-29 14:05:55 +08:00
parent ac03e9a6e4
commit ff313807e6
8 changed files with 297 additions and 8 deletions

151
server.js
View File

@@ -11,6 +11,7 @@ const { createCodexAppWorkerClient } = require('./lib/codex-app-worker-client');
const { createCodexAppRuntime } = require('./lib/codex-app-runtime');
const { createCodexRolloutStore } = require('./lib/codex-rollouts');
const { TOOLS: CCWEB_MCP_TOOLS } = require('./lib/ccweb-mcp-server');
const CCWEB_MCP_SERVER_INFO = { name: 'ccweb', version: '1.0.0' };
if (process.argv.includes('--ccweb-mcp-server')) {
require('./lib/ccweb-mcp-server').runStdioServer();
@@ -68,6 +69,12 @@ function readPositiveIntEnv(name, fallback, options = {}) {
return Math.max(min, Math.min(max, raw));
}
function normalizeCodexAppCcwebMcpTransport(value) {
const raw = String(value || '').trim().toLowerCase();
if (raw === 'stdio') return 'stdio';
return 'streamable_http';
}
const PORT = parseInt(process.env.PORT) || 8002;
const CLAUDE_PATH = process.env.CLAUDE_PATH || 'claude';
const CODEX_PATH = process.env.CODEX_PATH || 'codex';
@@ -112,11 +119,14 @@ const MCP_CREATE_CONVERSATION_MAX_HOP_COUNT = readPositiveIntEnv('CC_WEB_MCP_CRE
const CODEX_TRANSIENT_RETRY_MAX_ATTEMPTS = readPositiveIntEnv('CC_WEB_CODEX_TRANSIENT_RETRY_MAX_ATTEMPTS', 3, { min: 1, max: 10 });
const CODEX_TRANSIENT_RETRY_BASE_DELAY_MS = readPositiveIntEnv('CC_WEB_CODEX_TRANSIENT_RETRY_BASE_DELAY_MS', 2000, { min: 100, max: 60000 });
const MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4000;
const CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV = 'CC_WEB_CODEX_APP_MCP_TOKEN';
const CODEX_APP_CCWEB_MCP_TRANSPORT = normalizeCodexAppCcwebMcpTransport(process.env.CC_WEB_CODEX_APP_CCWEB_MCP_TRANSPORT);
const CODEX_APP_WORKER_DISABLED = /^(0|false|no|off)$/i.test(String(process.env.CC_WEB_CODEX_APP_WORKER || ''));
const CODEX_APP_WORKER_ENABLED = !CODEX_APP_WORKER_DISABLED;
const CODEX_APP_PROCESS_ENV_STRIP_KEYS = [
'CC_WEB_MCP_URL',
'CC_WEB_MCP_TOKEN',
CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV,
'CC_WEB_SOURCE_SESSION_ID',
'CC_WEB_CROSS_HOP_COUNT',
'CODEX_THREAD_ID',
@@ -974,6 +984,8 @@ function normalizeCodexMcpServerConfig(name, rawConfig, source) {
const url = normalizeSkillMcpUrl(rawConfig.url || rawConfig.server_url || '');
if (!url) return null;
config.url = url;
const bearerTokenEnvVar = String(rawConfig.bearer_token_env_var || rawConfig.bearerTokenEnvVar || '').trim();
if (bearerTokenEnvVar) config.bearer_token_env_var = bearerTokenEnvVar;
}
for (const key of ['startup_timeout_sec', 'tool_timeout_sec']) {
if (Number.isFinite(rawConfig[key])) config[key] = rawConfig[key];
@@ -2297,7 +2309,31 @@ function summarizeSkillForMention(skill, configuredMcpNames = new Set()) {
}
function buildCcwebMcpRuntimeConfig(session, options = {}) {
const env = codexAppCcwebMcpEnv(session, options);
if (!session?.id || !INTERNAL_MCP_TOKEN) return null;
const rawHopCount = Number.parseInt(String(options.mcpContext?.hopCount || 0), 10);
const hopCount = Number.isFinite(rawHopCount) ? Math.max(0, rawHopCount) : 0;
if (normalizeAgent(options.agent || session?.agent || 'codex') === 'codexapp'
&& CODEX_APP_CCWEB_MCP_TRANSPORT !== 'stdio') {
const params = new URLSearchParams({
sourceSessionId: session.id,
sourceHopCount: String(hopCount),
});
return {
server: 'ccweb',
name: 'ccweb',
source: 'builtin',
type: 'streamable_http',
description: 'ccweb 内置共享 MCP server可用于跨会话协作。',
config: {
type: 'streamable_http',
url: `http://127.0.0.1:${PORT}/api/internal/mcp/stream?${params.toString()}`,
bearer_token_env_var: CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV,
startup_timeout_sec: 10,
tool_timeout_sec: 60,
},
};
}
const env = codexAppCcwebMcpEnv(session, { ...options, mcpContext: { hopCount } });
if (!env) return null;
const commandSpec = ccwebMcpServerCommandSpec();
return {
@@ -5076,6 +5112,110 @@ function callInternalMcpTool(tool, args, sourceSessionId, sourceHopCount) {
}
}
function mcpJsonRpcResult(id, result) {
return { jsonrpc: '2.0', id, result };
}
function mcpJsonRpcError(id, code, message, data) {
const error = { code, message };
if (data !== undefined) error.data = data;
return { jsonrpc: '2.0', id, error };
}
function mcpToolResponse(payload) {
const text = JSON.stringify(payload, null, 2);
return {
content: [{ type: 'text', text }],
structuredContent: payload,
isError: !payload?.ok,
};
}
function handleMcpJsonRpcMessage(message, context = {}) {
const hasId = Object.prototype.hasOwnProperty.call(message || {}, 'id');
if (!hasId) return null;
const id = message.id;
const method = String(message?.method || '');
try {
switch (method) {
case 'initialize':
return mcpJsonRpcResult(id, {
protocolVersion: message.params?.protocolVersion || '2024-11-05',
capabilities: { tools: {} },
serverInfo: CCWEB_MCP_SERVER_INFO,
});
case 'ping':
return mcpJsonRpcResult(id, {});
case 'tools/list':
return mcpJsonRpcResult(id, { tools: CCWEB_MCP_TOOLS });
case 'tools/call': {
const name = String(message.params?.name || '');
const args = message.params?.arguments || {};
if (!CCWEB_MCP_TOOLS.some((tool) => tool.name === name)) {
return mcpJsonRpcResult(id, mcpToolResponse(mcpToolError('unknown_tool', `未知工具: ${name}`)));
}
const payload = callInternalMcpTool(
name,
args,
context.sourceSessionId || '',
context.sourceHopCount || 0,
);
return mcpJsonRpcResult(id, mcpToolResponse(payload));
}
case 'resources/list':
return mcpJsonRpcResult(id, { resources: [] });
case 'prompts/list':
return mcpJsonRpcResult(id, { prompts: [] });
default:
return mcpJsonRpcError(id, -32601, `Method not found: ${method}`);
}
} catch (err) {
return mcpJsonRpcError(id, -32603, err?.message || 'Internal error');
}
}
async function handleSharedMcpHttpApi(req, res, url) {
if (req.method !== 'POST') {
res.writeHead(405, {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-cache',
Allow: 'POST',
});
res.end(JSON.stringify(mcpJsonRpcError(null, -32600, 'Only POST is supported.')));
return;
}
const token = getInternalMcpRequestToken(req);
if (!token || token !== INTERNAL_MCP_TOKEN) {
return jsonResponse(res, 401, mcpJsonRpcError(null, -32001, 'MCP 内部接口未授权。'));
}
let payload;
try {
payload = await readJsonBody(req);
} catch (err) {
return jsonResponse(res, 400, mcpJsonRpcError(null, -32700, err?.message || '请求体无效。'));
}
const context = {
sourceSessionId: sanitizeId(url.searchParams.get('sourceSessionId') || ''),
sourceHopCount: Number.parseInt(String(url.searchParams.get('sourceHopCount') || 0), 10) || 0,
};
const messages = Array.isArray(payload) ? payload : [payload];
const responses = messages
.map((message) => handleMcpJsonRpcMessage(message, context))
.filter(Boolean);
if (responses.length === 0) {
res.writeHead(202, { 'Cache-Control': 'no-cache' });
res.end();
return;
}
return jsonResponse(res, 200, Array.isArray(payload) ? responses : responses[0]);
}
async function handleInternalMcpApi(req, res) {
const token = getInternalMcpRequestToken(req);
if (!token || token !== INTERNAL_MCP_TOKEN) {
@@ -5845,6 +5985,10 @@ function recoverProcesses() {
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname === '/api/internal/mcp/stream') {
return handleSharedMcpHttpApi(req, res, url);
}
if (req.method === 'POST' && url.pathname === '/api/internal/mcp') {
return handleInternalMcpApi(req, res);
}
@@ -8930,6 +9074,9 @@ function buildCodexAppClientSpec() {
delete env.CC_WEB_PASSWORD;
delete env.CLAUDECODE;
delete env.CLAUDE_CODE;
if (CODEX_APP_CCWEB_MCP_TRANSPORT !== 'stdio') {
env[CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV] = INTERNAL_MCP_TOKEN;
}
if (runtimeConfig?.mode === 'custom') {
env.CODEX_HOME = runtimeConfig.homeDir;
env.OPENAI_API_KEY = runtimeConfig.apiKey;
@@ -8945,6 +9092,8 @@ function buildCodexAppClientSpec() {
codeHome: env.CODEX_HOME || '',
apiKeyHash: runtimeConfig?.apiKey ? crypto.createHash('sha256').update(runtimeConfig.apiKey).digest('hex') : '',
worker: CODEX_APP_WORKER_ENABLED,
ccwebMcpTransport: CODEX_APP_CCWEB_MCP_TRANSPORT,
internalMcpTokenHash: crypto.createHash('sha256').update(INTERNAL_MCP_TOKEN).digest('hex'),
});
return {