chore: rebuild CentOS7 release package
This commit is contained in:
151
server.js
151
server.js
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user