mocode-ai 1.3.6 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/index.js +10 -0
- package/dist/i18n/index.js +25 -3
- package/dist/mcp/config.js +13 -1
- package/dist/mcp/index.js +7 -0
- package/dist/repl/index.js +46 -2
- package/dist/ui/layout.js +6 -14
- package/package.json +1 -1
package/dist/config/index.js
CHANGED
|
@@ -529,6 +529,7 @@ export const config = {
|
|
|
529
529
|
subAgentEnabled: process.env.MOCODE_SUBAGENT_ENABLED === 'true',
|
|
530
530
|
subAgentMaxSteps: Number(process.env.SUB_AGENT_MAX_STEPS) || Number(process.env.MAX_STEPS) || 1000,
|
|
531
531
|
frontendToolsEnabled: process.env.MOCODE_FRONTEND_TOOLS_ENABLED === 'true',
|
|
532
|
+
mcpEnabled: process.env.MOCODE_MCP_ENABLED !== 'false',
|
|
532
533
|
sessionDir: path.join(process.cwd(), '.mocode', 'sessions'),
|
|
533
534
|
searchApiKey: process.env.ANYSEARCH_API_KEY,
|
|
534
535
|
sandboxRoot: process.env.SANDBOX_ROOT || undefined,
|
|
@@ -613,6 +614,15 @@ export function updateFrontendToolsConfig(enabled) {
|
|
|
613
614
|
config.frontendToolsEnabled = enabled;
|
|
614
615
|
process.env.MOCODE_FRONTEND_TOOLS_ENABLED = enabled ? 'true' : 'false';
|
|
615
616
|
}
|
|
617
|
+
/** MCP 总开关;关闭时下次启动跳过 MCP 配置读取与服务连接。 */
|
|
618
|
+
export function isMcpEnabled() {
|
|
619
|
+
return config.mcpEnabled;
|
|
620
|
+
}
|
|
621
|
+
/** 运行时更新 MCP 开关;连接建立或释放、工具表刷新由 REPL 调用方执行。 */
|
|
622
|
+
export function updateMcpConfig(enabled) {
|
|
623
|
+
config.mcpEnabled = enabled;
|
|
624
|
+
process.env.MOCODE_MCP_ENABLED = enabled ? 'true' : 'false';
|
|
625
|
+
}
|
|
616
626
|
/**
|
|
617
627
|
* 记忆子系统总开关:单一来源。/memory_switch、/memory_status、buildSystemPrompt、
|
|
618
628
|
* tools/builtins/index.ts、tools/constants.ts 的 plan-mode 列表都从这里查。
|
package/dist/i18n/index.js
CHANGED
|
@@ -27,6 +27,10 @@ const zhCN = {
|
|
|
27
27
|
'commands.feOn': '开启前端工具簇',
|
|
28
28
|
'commands.feOff': '关闭前端工具簇',
|
|
29
29
|
'commands.feStatus': '查看前端工具簇状态',
|
|
30
|
+
'commands.mcp': 'MCP 服务开关(重启后生效)',
|
|
31
|
+
'commands.mcpOn': '开启 MCP 服务',
|
|
32
|
+
'commands.mcpOff': '关闭 MCP 服务',
|
|
33
|
+
'commands.mcpStatus': '查看 MCP 服务状态',
|
|
30
34
|
'commands.theme': '切换颜色主题(↑↓·Enter)',
|
|
31
35
|
'commands.model': '模型配置与预设管理',
|
|
32
36
|
'commands.modelConfigure': '配置新模型(向导)',
|
|
@@ -113,7 +117,7 @@ const zhCN = {
|
|
|
113
117
|
'composer.hint': 'Enter 换行 · Ctrl+S 回填 · Esc 取消 · Ctrl+Z 撤销 · Ctrl+C/X/V 复制/剪切/粘贴 · Ctrl+A 全选 · Shift+方向键 选择',
|
|
114
118
|
'history.hint': '输入过滤 · ↑↓ 选择 · Enter 填入输入框 · Ctrl+Enter 直接发送 · Esc 取消',
|
|
115
119
|
'history.empty': '(无匹配的历史输入)',
|
|
116
|
-
'welcome.ideNotice': '在电脑原生终端运行 mocode 可获得比IDE集成终端更佳的操作体验',
|
|
120
|
+
'welcome.ideNotice': ' 在电脑原生终端运行 mocode 可获得比IDE集成终端更佳的操作体验',
|
|
117
121
|
'welcome.gettingStarted': '快速上手',
|
|
118
122
|
'welcome.start1': '直接输入消息,按 Enter 发送;Shift+Tab 切换 auto / plan 模式',
|
|
119
123
|
'welcome.start2': '输入 / 打开命令菜单;输入 /help 查看全部命令',
|
|
@@ -175,7 +179,6 @@ const zhCN = {
|
|
|
175
179
|
'banner.disabled': '关闭',
|
|
176
180
|
'status.modeSwitch': 'Shift+Tab 切换',
|
|
177
181
|
'status.history': '历史 ↑{count}(PgDn 回底)',
|
|
178
|
-
'status.interruptHint': 'Esc / Ctrl+C 中断',
|
|
179
182
|
'status.measured': '实测',
|
|
180
183
|
'status.estimated': '估算',
|
|
181
184
|
'status.messages': '{count} 条消息',
|
|
@@ -260,6 +263,14 @@ const zhCN = {
|
|
|
260
263
|
'fe.changedOn': '已开启前端工具簇;browser / dev_server / screenshot / view_image 将从下一次模型请求起可用。',
|
|
261
264
|
'fe.changedOff': '已关闭前端工具簇;browser / dev_server / screenshot / view_image 已从模型工具表移除。',
|
|
262
265
|
'fe.usage': '用法:/fe on|off|status',
|
|
266
|
+
'mcp.status': 'MCP 服务:{state}',
|
|
267
|
+
'mcp.stateOn': '开启',
|
|
268
|
+
'mcp.stateOff': '关闭',
|
|
269
|
+
'mcp.changedOn': '已开启 MCP;已按当前配置连接服务并刷新工具表。',
|
|
270
|
+
'mcp.changedOff': '已关闭 MCP;已断开连接并从工具表移除 MCP 工具。',
|
|
271
|
+
'mcp.usage': '用法:/mcp on|off|status',
|
|
272
|
+
'mcp.unchanged': 'MCP 已是{state},未变更。',
|
|
273
|
+
'mcp.restartHint': '该设置已写入配置文件;下次启动将按此状态决定是否连接 MCP。',
|
|
263
274
|
'plan.ready': '计划已就绪',
|
|
264
275
|
'plan.approvalDetail': '切换到 auto 模式按上述计划执行?(plan 模式只读探查,执行需切 auto)',
|
|
265
276
|
'plan.execute': '切 auto 执行',
|
|
@@ -318,6 +329,10 @@ const en = {
|
|
|
318
329
|
'commands.feOn': 'Enable frontend tools',
|
|
319
330
|
'commands.feOff': 'Disable frontend tools',
|
|
320
331
|
'commands.feStatus': 'Show frontend tools status',
|
|
332
|
+
'commands.mcp': 'MCP service controls (applies immediately)',
|
|
333
|
+
'commands.mcpOn': 'Enable MCP services',
|
|
334
|
+
'commands.mcpOff': 'Disable MCP services',
|
|
335
|
+
'commands.mcpStatus': 'Show MCP service status',
|
|
321
336
|
'commands.theme': 'Switch color theme (↑↓·Enter)',
|
|
322
337
|
'commands.model': 'Model configuration and presets',
|
|
323
338
|
'commands.modelConfigure': 'Configure a new model (wizard)',
|
|
@@ -466,7 +481,6 @@ const en = {
|
|
|
466
481
|
'banner.disabled': 'Disabled',
|
|
467
482
|
'status.modeSwitch': 'Shift+Tab Switch',
|
|
468
483
|
'status.history': 'History ↑{count} (PgDn to bottom)',
|
|
469
|
-
'status.interruptHint': 'Esc / Ctrl+C to interrupt',
|
|
470
484
|
'status.measured': 'measured',
|
|
471
485
|
'status.estimated': 'estimated',
|
|
472
486
|
'status.messages': '{count} messages',
|
|
@@ -551,6 +565,14 @@ const en = {
|
|
|
551
565
|
'fe.changedOn': 'Frontend tools enabled; browser / dev_server / screenshot / view_image will be available from the next model request.',
|
|
552
566
|
'fe.changedOff': 'Frontend tools disabled; browser / dev_server / screenshot / view_image have been removed from the model tool list.',
|
|
553
567
|
'fe.usage': 'Usage: /fe on|off|status',
|
|
568
|
+
'mcp.status': 'MCP services: {state}',
|
|
569
|
+
'mcp.stateOn': 'enabled',
|
|
570
|
+
'mcp.stateOff': 'disabled',
|
|
571
|
+
'mcp.changedOn': 'MCP enabled; services have been connected from the current configuration and the tool list refreshed.',
|
|
572
|
+
'mcp.changedOff': 'MCP disabled; connections have been closed and MCP tools removed from the tool list.',
|
|
573
|
+
'mcp.usage': 'Usage: /mcp on|off|status',
|
|
574
|
+
'mcp.unchanged': 'MCP is already {state}; no change made.',
|
|
575
|
+
'mcp.restartHint': 'This setting is saved to the configuration file; future startups use it to decide whether to connect MCP.',
|
|
554
576
|
'plan.ready': 'Plan ready',
|
|
555
577
|
'plan.approvalDetail': 'Switch to auto mode and execute the plan above? (Plan mode is read-only; execution requires auto mode.)',
|
|
556
578
|
'plan.execute': 'Switch to auto and execute',
|
package/dist/mcp/config.js
CHANGED
|
@@ -7,6 +7,9 @@ import path from 'node:path';
|
|
|
7
7
|
* JSON 格式兼容常见的 { "mcpServers": { name: { command/url/... } } },也允许直接 server map。
|
|
8
8
|
*/
|
|
9
9
|
export function readMcpServers() {
|
|
10
|
+
// 总开关必须早于任何配置读取,以便即使用户级 MCP 配置存在也能快速启动。
|
|
11
|
+
if (process.env.MOCODE_MCP_ENABLED === 'false')
|
|
12
|
+
return { servers: [], warnings: [] };
|
|
10
13
|
const warnings = [];
|
|
11
14
|
const servers = new Map();
|
|
12
15
|
const fromFile = process.env.MCP_CONFIG_PATH;
|
|
@@ -60,7 +63,16 @@ function parseServer(name, raw, source, warnings) {
|
|
|
60
63
|
}
|
|
61
64
|
const transport = normalizeTransport(raw.transport ?? raw.type, raw);
|
|
62
65
|
const requestTimeoutMs = positiveNumber(raw.requestTimeoutMs ?? raw.timeoutMs);
|
|
63
|
-
const
|
|
66
|
+
const includeTools = stringArray(raw.includeTools);
|
|
67
|
+
const excludeTools = stringArray(raw.excludeTools);
|
|
68
|
+
const common = {
|
|
69
|
+
name,
|
|
70
|
+
transport,
|
|
71
|
+
disabled: raw.disabled === true,
|
|
72
|
+
...(requestTimeoutMs ? { requestTimeoutMs } : {}),
|
|
73
|
+
...(includeTools ? { includeTools } : {}),
|
|
74
|
+
...(excludeTools ? { excludeTools } : {}),
|
|
75
|
+
};
|
|
64
76
|
if (transport === 'stdio') {
|
|
65
77
|
if (typeof raw.command !== 'string' || !raw.command.trim()) {
|
|
66
78
|
warnings.push(`${source}.${name} (stdio) 缺少 command`);
|
package/dist/mcp/index.js
CHANGED
|
@@ -29,6 +29,8 @@ export function getMcpTools() {
|
|
|
29
29
|
for (const client of clients.values()) {
|
|
30
30
|
const serverPart = sanitizeName(client.name);
|
|
31
31
|
for (const remote of client.cachedTools) {
|
|
32
|
+
if (!shouldRegisterTool(client.spec, remote.name))
|
|
33
|
+
continue;
|
|
32
34
|
const localName = `mcp__${serverPart}__${sanitizeName(remote.name)}`;
|
|
33
35
|
if (usedNames.has(localName)) {
|
|
34
36
|
startupWarnings.push(`MCP ${client.name} 的工具 ${remote.name} 名称冲突,已忽略`);
|
|
@@ -67,6 +69,11 @@ export async function closeAllMcp() {
|
|
|
67
69
|
await Promise.allSettled(active.map((client) => client.close()));
|
|
68
70
|
}
|
|
69
71
|
export function getMcpWarnings() { return [...startupWarnings]; }
|
|
72
|
+
function shouldRegisterTool(spec, remoteName) {
|
|
73
|
+
if (spec.excludeTools?.includes(remoteName))
|
|
74
|
+
return false;
|
|
75
|
+
return !spec.includeTools || spec.includeTools.includes(remoteName);
|
|
76
|
+
}
|
|
70
77
|
function sanitizeName(name) {
|
|
71
78
|
return name.replace(/[^A-Za-z0-9_-]+/g, '_').replace(/^_+|_+$/g, '') || 'tool';
|
|
72
79
|
}
|
package/dist/repl/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import readline from 'node:readline/promises';
|
|
2
2
|
import { emitKeypressEvents } from 'node:readline';
|
|
3
3
|
import { stdin, stdout } from 'node:process';
|
|
4
|
-
import { config, updateModelConfig, isModelConfigured, updateMemoryConfig, isMemoryEnabled, isSubAgentEnabled, updateSubAgentConfig, isFrontendToolsEnabled, updateFrontendToolsConfig, updateLanguageConfig, languageFromShell, buildBasePrompt, getPlanModeSuffix, hasCodegraphIndex, DEFAULT_CONTEXT_WINDOW_TOKENS, pinSessionModel, } from '../config/index.js';
|
|
4
|
+
import { config, updateModelConfig, isModelConfigured, updateMemoryConfig, isMemoryEnabled, isSubAgentEnabled, updateSubAgentConfig, isFrontendToolsEnabled, updateFrontendToolsConfig, isMcpEnabled, updateMcpConfig, updateLanguageConfig, languageFromShell, buildBasePrompt, getPlanModeSuffix, hasCodegraphIndex, DEFAULT_CONTEXT_WINDOW_TOKENS, pinSessionModel, } from '../config/index.js';
|
|
5
5
|
import { getLanguage, normalizeLanguage, t, } from '../i18n/index.js';
|
|
6
6
|
import { DEFAULT_BUDGET_POLICY } from '../context/budget.js';
|
|
7
7
|
import { updateConfigKey, writeConfigKeys, CONFIG_PATH } from '../config/file.js';
|
|
@@ -84,6 +84,13 @@ function buildSlashCommands() {
|
|
|
84
84
|
{ name: 'status', value: '/fe status', desc: d('commands.feStatus') },
|
|
85
85
|
],
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
name: '/mcp', desc: d('commands.mcp'), children: [
|
|
89
|
+
{ name: 'on', value: '/mcp on', desc: d('commands.mcpOn') },
|
|
90
|
+
{ name: 'off', value: '/mcp off', desc: d('commands.mcpOff') },
|
|
91
|
+
{ name: 'status', value: '/mcp status', desc: d('commands.mcpStatus') },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
87
94
|
{ name: '/theme', desc: d('commands.theme') },
|
|
88
95
|
{
|
|
89
96
|
name: '/model', desc: d('commands.model'), children: [
|
|
@@ -157,7 +164,7 @@ const LLM_ERROR_HINT_KEYS = {
|
|
|
157
164
|
const HELP_GROUPS = [
|
|
158
165
|
{ key: 'help.groupFrequent', names: ['/help', '/clear', '/context', '/compact', '/resume', '/model'] },
|
|
159
166
|
{ key: 'help.groupSession', names: ['/sessions', '/rollback', '/memory', '/skills', '/skill', '/init'] },
|
|
160
|
-
{ key: 'help.groupConfig', names: ['/mode', '/subagent', '/fe', '/theme', '/pet', '/image', '/language', '/upgrade'] },
|
|
167
|
+
{ key: 'help.groupConfig', names: ['/mode', '/subagent', '/fe', '/mcp', '/theme', '/pet', '/image', '/language', '/upgrade'] },
|
|
161
168
|
];
|
|
162
169
|
/**
|
|
163
170
|
* 菜单树里全部可用命令名(含分支路径与叶子的真实命令),供未知命令纠错建议用。
|
|
@@ -2401,6 +2408,43 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
2401
2408
|
layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'fe.changedOn' : 'fe.changedOff')}${ui.reset}\n`);
|
|
2402
2409
|
continue;
|
|
2403
2410
|
}
|
|
2411
|
+
if (line === '/mcp' || line.startsWith('/mcp ')) {
|
|
2412
|
+
const arg = line.startsWith('/mcp ')
|
|
2413
|
+
? line.slice('/mcp '.length).trim().toLowerCase()
|
|
2414
|
+
: 'status';
|
|
2415
|
+
if (arg === '' || arg === 'status') {
|
|
2416
|
+
const enabled = isMcpEnabled();
|
|
2417
|
+
const state = t(enabled ? 'mcp.stateOn' : 'mcp.stateOff');
|
|
2418
|
+
layout.contentWrite(`${ui.accent}${t('mcp.status', { state })}${ui.reset}\n` +
|
|
2419
|
+
`${ui.dim}MOCODE_MCP_ENABLED=${enabled ? 'true' : 'false'} · ${CONFIG_PATH}${ui.reset}\n` +
|
|
2420
|
+
`${ui.dim}${t('mcp.restartHint')}${ui.reset}\n`);
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
if (arg !== 'on' && arg !== 'off') {
|
|
2424
|
+
layout.contentWrite(`${ui.yellow}${t('mcp.usage')}${ui.reset}\n`);
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
const enabled = arg === 'on';
|
|
2428
|
+
if (enabled === isMcpEnabled()) {
|
|
2429
|
+
layout.contentWrite(`${ui.dim}${t('mcp.unchanged', { state: t(enabled ? 'mcp.stateOn' : 'mcp.stateOff') })}${ui.reset}\n`);
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
updateMcpConfig(enabled);
|
|
2433
|
+
updateConfigKey('MOCODE_MCP_ENABLED', enabled ? 'true' : 'false');
|
|
2434
|
+
if (enabled) {
|
|
2435
|
+
await initializeAllMcp();
|
|
2436
|
+
registerToolsExtension('mcp', getMcpTools());
|
|
2437
|
+
}
|
|
2438
|
+
else {
|
|
2439
|
+
await closeAllMcp();
|
|
2440
|
+
registerToolsExtension('mcp', []);
|
|
2441
|
+
}
|
|
2442
|
+
refreshChatTools();
|
|
2443
|
+
history[0] = { role: 'system', content: buildSystemMessage(getAgentMode() === 'plan') };
|
|
2444
|
+
layout.rewriteBanner(bannerLines(banner()));
|
|
2445
|
+
layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'mcp.changedOn' : 'mcp.changedOff')}${ui.reset}\n`);
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2404
2448
|
if (line === '/memory_switch' ||
|
|
2405
2449
|
line.startsWith('/memory_switch ') ||
|
|
2406
2450
|
line === '/memory_status' ||
|
package/dist/ui/layout.js
CHANGED
|
@@ -1406,14 +1406,6 @@ function composeSpinnerLine(status, cols) {
|
|
|
1406
1406
|
const elapsed = (mode === 'running' && turnStart != null)
|
|
1407
1407
|
? fmtElapsed(Date.now() - turnStart)
|
|
1408
1408
|
: '';
|
|
1409
|
-
// 新手中断提示:每轮开始后前 6 秒,spinner 行尾随一次 dim「(Esc / Ctrl+C 中断)」,
|
|
1410
|
-
// 6 秒后自然消失(不常驻,不打扰老用户)。只在非滚动回看时画,回看态右段让给历史指示。
|
|
1411
|
-
const INTERRUPT_HINT_WINDOW_MS = 6000;
|
|
1412
|
-
const hintActive = !scrolled && mode === 'running' && turnStart != null
|
|
1413
|
-
&& Date.now() - turnStart < INTERRUPT_HINT_WINDOW_MS;
|
|
1414
|
-
const hintText = t('status.interruptHint');
|
|
1415
|
-
const hint = hintActive ? ` ${ui.dim}(${hintText})${ui.reset}` : '';
|
|
1416
|
-
const hintW = hintActive ? 4 + displayWidth(hintText) : 0; // 2 缩进 + 左右括号
|
|
1417
1409
|
// ── 左段:帧 + 状态 + 走时,全部紧跟 ──
|
|
1418
1410
|
let lead;
|
|
1419
1411
|
let leadW;
|
|
@@ -1432,17 +1424,17 @@ function composeSpinnerLine(status, cols) {
|
|
|
1432
1424
|
leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0);
|
|
1433
1425
|
}
|
|
1434
1426
|
else if (hasSpinner) {
|
|
1435
|
-
// spinner 激活(思考中/执行工具…):帧 + 状态 + 走时
|
|
1427
|
+
// spinner 激活(思考中/执行工具…):帧 + 状态 + 走时
|
|
1436
1428
|
const ePart = elapsed ? ` ${ui.dim}${elapsed}${ui.reset}` : '';
|
|
1437
|
-
lead = `${ui.bold}${ui.accent}${status.spinnerFrame}${ui.reset} ${ui.dim}${status.status}${ui.reset}${ePart}
|
|
1438
|
-
leadW = 1 + 1 + displayWidth(status.status) + (elapsed ? 1 + displayWidth(elapsed) : 0)
|
|
1429
|
+
lead = `${ui.bold}${ui.accent}${status.spinnerFrame}${ui.reset} ${ui.dim}${status.status}${ui.reset}${ePart}`;
|
|
1430
|
+
leadW = 1 + 1 + displayWidth(status.status) + (elapsed ? 1 + displayWidth(elapsed) : 0);
|
|
1439
1431
|
}
|
|
1440
1432
|
else if (spinning) {
|
|
1441
|
-
// 运行态心跳帧(流式输出中 / 命令态如 /rollback /compact /resume):帧 + 状态文字(优先)或生成中(兜底) + 走时
|
|
1433
|
+
// 运行态心跳帧(流式输出中 / 命令态如 /rollback /compact /resume):帧 + 状态文字(优先)或生成中(兜底) + 走时
|
|
1442
1434
|
const label = status.status || '生成中';
|
|
1443
1435
|
const ePart = elapsed ? ` ${ui.dim}${elapsed}${ui.reset}` : '';
|
|
1444
|
-
lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset} ${ui.dim}${label}${ui.reset}${ePart}
|
|
1445
|
-
leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0)
|
|
1436
|
+
lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset} ${ui.dim}${label}${ui.reset}${ePart}`;
|
|
1437
|
+
leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0);
|
|
1446
1438
|
}
|
|
1447
1439
|
else {
|
|
1448
1440
|
// INPUT 态:● + 状态文字(无走时)
|