chore: rebuild CentOS7 release package
This commit is contained in:
Binary file not shown.
@@ -82,7 +82,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', 'startup_timeout_sec', 'tool_timeout_sec']) {
|
for (const key of ['type', '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])}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const ASSET_VERSION = '20260626-ccweb-prompt-compact-ui';
|
const ASSET_VERSION = '20260629-ccweb-prompt-dark-theme';
|
||||||
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
|
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
|
||||||
const RENDER_DEBOUNCE = 100;
|
const RENDER_DEBOUNCE = 100;
|
||||||
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
|
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
document.documentElement.dataset.dividerTime = dividerTime;
|
document.documentElement.dataset.dividerTime = dividerTime;
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="style.css?v=20260625-branch-bubble">
|
<link rel="stylesheet" href="style.css?v=20260629-ccweb-prompt-dark-theme">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -173,6 +173,6 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||||
<script src="app.js?v=20260625-branch-bubble"></script>
|
<script src="app.js?v=20260629-ccweb-prompt-dark-theme"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
125
public/style.css
125
public/style.css
@@ -6254,3 +6254,128 @@ html[data-theme='coolvibe'] .settings-back:hover {
|
|||||||
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .theme-card-swatch {
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .theme-card-swatch {
|
||||||
border-color: rgba(255, 255, 255, 0.16);
|
border-color: rgba(255, 255, 255, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-card {
|
||||||
|
background: linear-gradient(180deg, var(--dark-panel-bg), var(--bg-bubble-assistant));
|
||||||
|
border-color: var(--border-color);
|
||||||
|
border-left-color: var(--accent);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: var(--shadow-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-card.ccweb-prompt-focus {
|
||||||
|
animation: ccwebPromptFocusDark 1.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ccwebPromptFocusDark {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 var(--accent-light), var(--shadow-strong);
|
||||||
|
}
|
||||||
|
45% {
|
||||||
|
box-shadow: 0 0 0 5px var(--accent-light), var(--shadow-strong);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), var(--shadow-strong);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-header,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-footer {
|
||||||
|
background: rgba(0, 0, 0, 0.12);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-status,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-required,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option-badge {
|
||||||
|
background: var(--accent-light);
|
||||||
|
border-color: var(--theme-card-hover-border);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-card[data-status='submitted'] .ccweb-prompt-status {
|
||||||
|
background: rgba(103, 201, 135, 0.14);
|
||||||
|
border-color: rgba(103, 201, 135, 0.32);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-view-switcher,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab-nav-btn,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-question,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-selected-readonly,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer-readonly,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-secondary,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-action {
|
||||||
|
background: var(--dark-panel-soft);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-question {
|
||||||
|
background: rgba(255, 255, 255, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option::before {
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
border-color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option:hover,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab-nav-btn:hover,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-secondary:hover,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-action:hover {
|
||||||
|
background: var(--accent-light);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-tab.is-active,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option.is-selected {
|
||||||
|
background: var(--accent-light);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: inset 0 0 0 1px var(--theme-card-hover-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-option.is-selected::before {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer {
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer::placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
opacity: 0.86;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-answer:focus {
|
||||||
|
background: var(--bg-bubble-assistant);
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-submit:hover:not(:disabled) {
|
||||||
|
box-shadow: 0 8px 18px var(--accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-submit:disabled,
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .ccweb-prompt-secondary:disabled {
|
||||||
|
opacity: 0.56;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-badge {
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(html[data-theme='carbon'], html[data-theme='nocturne'], html[data-theme='cinder']) .pending-ccweb-prompt-dismiss:hover {
|
||||||
|
background: var(--accent-light);
|
||||||
|
}
|
||||||
|
|||||||
@@ -425,12 +425,24 @@ function completeMcpToolTurn(thread, turnId) {
|
|||||||
const ccwebConfig = thread.config?.['mcp_servers.ccweb'] || null;
|
const ccwebConfig = thread.config?.['mcp_servers.ccweb'] || null;
|
||||||
const projectConfig = thread.config?.['mcp_servers.reg-app-project'] || null;
|
const projectConfig = thread.config?.['mcp_servers.reg-app-project'] || null;
|
||||||
const env = ccwebConfig?.env || {};
|
const env = ccwebConfig?.env || {};
|
||||||
|
let urlSourceSessionId = null;
|
||||||
|
let urlSourceHopCount = null;
|
||||||
|
try {
|
||||||
|
if (ccwebConfig?.url) {
|
||||||
|
const parsedUrl = new URL(ccwebConfig.url);
|
||||||
|
urlSourceSessionId = parsedUrl.searchParams.get('sourceSessionId') || null;
|
||||||
|
urlSourceHopCount = parsedUrl.searchParams.get('sourceHopCount') || null;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
const payload = {
|
const payload = {
|
||||||
ok: true,
|
ok: true,
|
||||||
currentConversationId: env.CC_WEB_SOURCE_SESSION_ID || null,
|
currentConversationId: env.CC_WEB_SOURCE_SESSION_ID || urlSourceSessionId,
|
||||||
sourceHopCount: env.CC_WEB_CROSS_HOP_COUNT || null,
|
sourceHopCount: env.CC_WEB_CROSS_HOP_COUNT || urlSourceHopCount,
|
||||||
hasCcwebMcpConfig: Boolean(ccwebConfig),
|
hasCcwebMcpConfig: Boolean(ccwebConfig),
|
||||||
hasProjectMcpConfig: Boolean(projectConfig),
|
hasProjectMcpConfig: Boolean(projectConfig),
|
||||||
|
ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)),
|
||||||
|
ccwebUrl: ccwebConfig?.url || null,
|
||||||
|
ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null,
|
||||||
ccwebCommand: ccwebConfig?.command || null,
|
ccwebCommand: ccwebConfig?.command || null,
|
||||||
ccwebArgs: ccwebConfig?.args || null,
|
ccwebArgs: ccwebConfig?.args || null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1516,7 +1516,10 @@ 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(/server\.js/.test(codexAppDynamicTool.result || '') && /--ccweb-mcp-server/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP config should launch through server.js in Node mode');
|
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(/"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(!/--ccweb-mcp-server/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should not launch a per-thread stdio bridge by default');
|
||||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||||
|
|
||||||
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-empty-slash-prompt-user-mcp', trigger: '/', query: '', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));
|
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-empty-slash-prompt-user-mcp', trigger: '/', query: '', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));
|
||||||
|
|||||||
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 { createCodexAppRuntime } = require('./lib/codex-app-runtime');
|
||||||
const { createCodexRolloutStore } = require('./lib/codex-rollouts');
|
const { createCodexRolloutStore } = require('./lib/codex-rollouts');
|
||||||
const { TOOLS: CCWEB_MCP_TOOLS } = require('./lib/ccweb-mcp-server');
|
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')) {
|
if (process.argv.includes('--ccweb-mcp-server')) {
|
||||||
require('./lib/ccweb-mcp-server').runStdioServer();
|
require('./lib/ccweb-mcp-server').runStdioServer();
|
||||||
@@ -68,6 +69,12 @@ function readPositiveIntEnv(name, fallback, options = {}) {
|
|||||||
return Math.max(min, Math.min(max, raw));
|
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 PORT = parseInt(process.env.PORT) || 8002;
|
||||||
const CLAUDE_PATH = process.env.CLAUDE_PATH || 'claude';
|
const CLAUDE_PATH = process.env.CLAUDE_PATH || 'claude';
|
||||||
const CODEX_PATH = process.env.CODEX_PATH || 'codex';
|
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_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 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 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_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_WORKER_ENABLED = !CODEX_APP_WORKER_DISABLED;
|
||||||
const CODEX_APP_PROCESS_ENV_STRIP_KEYS = [
|
const CODEX_APP_PROCESS_ENV_STRIP_KEYS = [
|
||||||
'CC_WEB_MCP_URL',
|
'CC_WEB_MCP_URL',
|
||||||
'CC_WEB_MCP_TOKEN',
|
'CC_WEB_MCP_TOKEN',
|
||||||
|
CODEX_APP_CCWEB_MCP_BEARER_TOKEN_ENV,
|
||||||
'CC_WEB_SOURCE_SESSION_ID',
|
'CC_WEB_SOURCE_SESSION_ID',
|
||||||
'CC_WEB_CROSS_HOP_COUNT',
|
'CC_WEB_CROSS_HOP_COUNT',
|
||||||
'CODEX_THREAD_ID',
|
'CODEX_THREAD_ID',
|
||||||
@@ -974,6 +984,8 @@ function normalizeCodexMcpServerConfig(name, rawConfig, source) {
|
|||||||
const url = normalizeSkillMcpUrl(rawConfig.url || rawConfig.server_url || '');
|
const url = normalizeSkillMcpUrl(rawConfig.url || rawConfig.server_url || '');
|
||||||
if (!url) return null;
|
if (!url) return null;
|
||||||
config.url = url;
|
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']) {
|
for (const key of ['startup_timeout_sec', 'tool_timeout_sec']) {
|
||||||
if (Number.isFinite(rawConfig[key])) config[key] = rawConfig[key];
|
if (Number.isFinite(rawConfig[key])) config[key] = rawConfig[key];
|
||||||
@@ -2297,7 +2309,31 @@ function summarizeSkillForMention(skill, configuredMcpNames = new Set()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildCcwebMcpRuntimeConfig(session, options = {}) {
|
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;
|
if (!env) return null;
|
||||||
const commandSpec = ccwebMcpServerCommandSpec();
|
const commandSpec = ccwebMcpServerCommandSpec();
|
||||||
return {
|
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) {
|
async function handleInternalMcpApi(req, res) {
|
||||||
const token = getInternalMcpRequestToken(req);
|
const token = getInternalMcpRequestToken(req);
|
||||||
if (!token || token !== INTERNAL_MCP_TOKEN) {
|
if (!token || token !== INTERNAL_MCP_TOKEN) {
|
||||||
@@ -5845,6 +5985,10 @@ function recoverProcesses() {
|
|||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
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') {
|
if (req.method === 'POST' && url.pathname === '/api/internal/mcp') {
|
||||||
return handleInternalMcpApi(req, res);
|
return handleInternalMcpApi(req, res);
|
||||||
}
|
}
|
||||||
@@ -8930,6 +9074,9 @@ function buildCodexAppClientSpec() {
|
|||||||
delete env.CC_WEB_PASSWORD;
|
delete env.CC_WEB_PASSWORD;
|
||||||
delete env.CLAUDECODE;
|
delete env.CLAUDECODE;
|
||||||
delete env.CLAUDE_CODE;
|
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') {
|
if (runtimeConfig?.mode === 'custom') {
|
||||||
env.CODEX_HOME = runtimeConfig.homeDir;
|
env.CODEX_HOME = runtimeConfig.homeDir;
|
||||||
env.OPENAI_API_KEY = runtimeConfig.apiKey;
|
env.OPENAI_API_KEY = runtimeConfig.apiKey;
|
||||||
@@ -8945,6 +9092,8 @@ function buildCodexAppClientSpec() {
|
|||||||
codeHome: env.CODEX_HOME || '',
|
codeHome: env.CODEX_HOME || '',
|
||||||
apiKeyHash: runtimeConfig?.apiKey ? crypto.createHash('sha256').update(runtimeConfig.apiKey).digest('hex') : '',
|
apiKeyHash: runtimeConfig?.apiKey ? crypto.createHash('sha256').update(runtimeConfig.apiKey).digest('hex') : '',
|
||||||
worker: CODEX_APP_WORKER_ENABLED,
|
worker: CODEX_APP_WORKER_ENABLED,
|
||||||
|
ccwebMcpTransport: CODEX_APP_CCWEB_MCP_TRANSPORT,
|
||||||
|
internalMcpTokenHash: crypto.createHash('sha256').update(INTERNAL_MCP_TOKEN).digest('hex'),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user