mocode-ai 1.3.5 → 1.3.7
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/i18n/index.js +1 -1
- package/dist/llm/index.js +10 -5
- package/dist/mcp/config.js +10 -1
- package/dist/mcp/index.js +7 -0
- package/dist/ui/batch.js +82 -19
- package/dist/ui/layout.js +1 -0
- package/package.json +1 -1
package/dist/i18n/index.js
CHANGED
|
@@ -113,7 +113,7 @@ const zhCN = {
|
|
|
113
113
|
'composer.hint': 'Enter 换行 · Ctrl+S 回填 · Esc 取消 · Ctrl+Z 撤销 · Ctrl+C/X/V 复制/剪切/粘贴 · Ctrl+A 全选 · Shift+方向键 选择',
|
|
114
114
|
'history.hint': '输入过滤 · ↑↓ 选择 · Enter 填入输入框 · Ctrl+Enter 直接发送 · Esc 取消',
|
|
115
115
|
'history.empty': '(无匹配的历史输入)',
|
|
116
|
-
'welcome.ideNotice': '在电脑原生终端运行 mocode 可获得比IDE集成终端更佳的操作体验',
|
|
116
|
+
'welcome.ideNotice': ' 在电脑原生终端运行 mocode 可获得比IDE集成终端更佳的操作体验',
|
|
117
117
|
'welcome.gettingStarted': '快速上手',
|
|
118
118
|
'welcome.start1': '直接输入消息,按 Enter 发送;Shift+Tab 切换 auto / plan 模式',
|
|
119
119
|
'welcome.start2': '输入 / 打开命令菜单;输入 /help 查看全部命令',
|
package/dist/llm/index.js
CHANGED
|
@@ -17,12 +17,12 @@ if (process.env.DEBUG === 'true') {
|
|
|
17
17
|
* 不重试 → 400 (bad request) / 401 (auth) / 其他 4xx / 用户中断 (AbortError / APIUserAbortError)
|
|
18
18
|
*
|
|
19
19
|
* 退避:指数 + ±20% jitter,首等 1s,翻倍,封顶 30s;若后端返回 Retry-After 头则优先按其值。
|
|
20
|
-
* 默认
|
|
20
|
+
* 默认 10 次尝试(1 初始 + 9 重试),要调改 RETRY_MAX_ATTEMPTS。
|
|
21
21
|
*
|
|
22
22
|
* SDK 内置 maxRetries 默认 2(对所有 5xx+网络错重试),与本策略叠加会双重重试 5xx —— 显式置 0 让
|
|
23
23
|
* 全部重试由 chat() 外层重试循环统一控,行为可预期。
|
|
24
24
|
*/
|
|
25
|
-
const RETRY_MAX_ATTEMPTS =
|
|
25
|
+
const RETRY_MAX_ATTEMPTS = 10;
|
|
26
26
|
const RETRY_BASE_MS = 1000;
|
|
27
27
|
const RETRY_MAX_MS = 30000;
|
|
28
28
|
const RETRY_JITTER = 0.2;
|
|
@@ -338,13 +338,18 @@ toolsOverride) {
|
|
|
338
338
|
throw err;
|
|
339
339
|
}
|
|
340
340
|
const wait = computeBackoff(attempt, getRetryAfterMs(err));
|
|
341
|
-
|
|
341
|
+
const retry = {
|
|
342
342
|
attempt,
|
|
343
343
|
nextAttempt: attempt + 1,
|
|
344
344
|
waitMs: wait,
|
|
345
345
|
code: retryErrorCode(err),
|
|
346
|
-
}
|
|
347
|
-
|
|
346
|
+
};
|
|
347
|
+
// 交给宿主的 onRetry 负责展示:TUI 可将其作为瞬时状态处理,不能再用 console
|
|
348
|
+
// 追加到内容历史,否则重连成功后会留下过期的失败提示。没有宿主时保留 stderr 日志。
|
|
349
|
+
if (handlers.onRetry)
|
|
350
|
+
handlers.onRetry(retry);
|
|
351
|
+
else
|
|
352
|
+
logRetry(attempt, err, wait);
|
|
348
353
|
// sleep 自己会在 signal abort 时抛 AbortError——透传,让 runAgentCore 的 catch 按中断处理。
|
|
349
354
|
await sleep(wait, signal);
|
|
350
355
|
}
|
package/dist/mcp/config.js
CHANGED
|
@@ -60,7 +60,16 @@ function parseServer(name, raw, source, warnings) {
|
|
|
60
60
|
}
|
|
61
61
|
const transport = normalizeTransport(raw.transport ?? raw.type, raw);
|
|
62
62
|
const requestTimeoutMs = positiveNumber(raw.requestTimeoutMs ?? raw.timeoutMs);
|
|
63
|
-
const
|
|
63
|
+
const includeTools = stringArray(raw.includeTools);
|
|
64
|
+
const excludeTools = stringArray(raw.excludeTools);
|
|
65
|
+
const common = {
|
|
66
|
+
name,
|
|
67
|
+
transport,
|
|
68
|
+
disabled: raw.disabled === true,
|
|
69
|
+
...(requestTimeoutMs ? { requestTimeoutMs } : {}),
|
|
70
|
+
...(includeTools ? { includeTools } : {}),
|
|
71
|
+
...(excludeTools ? { excludeTools } : {}),
|
|
72
|
+
};
|
|
64
73
|
if (transport === 'stdio') {
|
|
65
74
|
if (typeof raw.command !== 'string' || !raw.command.trim()) {
|
|
66
75
|
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/ui/batch.js
CHANGED
|
@@ -271,27 +271,84 @@ function buildEntryDetailLines(e, indent = ' ') {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
else if (e.resultSummary) {
|
|
274
|
-
|
|
274
|
+
// 用 · 而非 ↳:entry 行尾已内联同一串,详情区再画一遍箭头会被读成「又一条子结果」;
|
|
275
|
+
// · 只表达「这是结论文本」,与 diff 块、fullOutput 原始输出行在视觉上分属三档。
|
|
276
|
+
lines.push(sanitizeRow(`${indent}${ui.dim}${DETAIL_RESULT_MARK}${ui.reset}${ui.gray}${e.resultSummary}${ui.reset}`));
|
|
275
277
|
}
|
|
276
278
|
return lines;
|
|
277
279
|
}
|
|
278
|
-
/**
|
|
280
|
+
/**
|
|
281
|
+
* entry 第一层明细行的渲染指纹:指纹变则原位重画该行(驱动运行态结果回填)。
|
|
282
|
+
* 纳入 fullOutput **有无**(而非内容):hasEntryDetail 看它来决定画不画三角——
|
|
283
|
+
* 缺了它,「调用时无结果(空白占位)→ 结果回填」这一步不会重画,三角永远不出现。
|
|
284
|
+
* 只取有无不取内容:完整输出可能上万字符,拼进指纹是浪费,且其内容不影响 entry 行渲染。
|
|
285
|
+
*/
|
|
279
286
|
function entryLineDigest(e) {
|
|
280
|
-
return `${e.resultSummary}\u0001${e.diffBlock ? '1' : '0'}\u0001${e.failed ? '1' : '0'}`;
|
|
287
|
+
return `${e.resultSummary}\u0001${e.diffBlock ? '1' : '0'}\u0001${e.fullOutput ? '1' : '0'}\u0001${e.failed ? '1' : '0'}`;
|
|
281
288
|
}
|
|
282
|
-
/**
|
|
283
|
-
|
|
289
|
+
/**
|
|
290
|
+
* 第二层展开三角:折叠 ▸ / 展开 ▾。替代原先的 ├─ / └─。
|
|
291
|
+
*
|
|
292
|
+
* 根因是**字体实现差异,不是 Unicode 分类差异**——两者 East Asian Width 同为
|
|
293
|
+
* Ambiguous(U+2500–257F Box Drawing 与 U+25A0–25FF Geometric Shapes 整块都是 A):
|
|
294
|
+
* `render.ts: charWidth()` 对 Ambiguous 一律按 1 列算,但部分等宽字体为让竖线连续,
|
|
295
|
+
* 常把 Box Drawing 做成占 2 格的字形(尤其中文字体里 box drawing 取自全角字库)。
|
|
296
|
+
* → sanitizeRow/truncateAnsi 钳宽失准 → 物理行超 cols → 终端 auto-wrap
|
|
297
|
+
* → repaintViewport 的 CUP 寻址全错(整屏错位)。
|
|
298
|
+
*
|
|
299
|
+
* 换 Geometric Shapes 的依据是**实测**:同一终端下 `●`(U+25CF) 与 `◇`(U+25C7)
|
|
300
|
+
* 长期渲染正常,而 `├─`/`└─` 错位——即本机字体对 Geometric Shapes 按 1 列、
|
|
301
|
+
* 对 Box Drawing 按 2 列。故与 `●` 同族的 ▸/▾ 是安全选择。
|
|
302
|
+
*
|
|
303
|
+
* 注:Ambiguous 终究依赖字体。若要零风险,用 Latin-1 的 `·`(U+00B7, EAW=Na)。
|
|
304
|
+
*/
|
|
305
|
+
const CARET_COLLAPSED = '▸';
|
|
306
|
+
const CARET_EXPANDED = '▾';
|
|
307
|
+
/** 无详情可展开时占位,保持与 "▸ " 同宽,让工具名列对齐。 */
|
|
308
|
+
const CARET_NONE = ' ';
|
|
309
|
+
/** 详情行缩进:统一 7 空格(对齐 entry 行的 " ▸ " 之后再右移一列,形成层次)。 */
|
|
310
|
+
const DETAIL_INDENT = ' ';
|
|
311
|
+
/**
|
|
312
|
+
* 详情行结果标记:·(U+00B7 MIDDLE DOT, Latin-1 Supplement, **EAW = Narrow**)。
|
|
313
|
+
* 比 ▸/▾ 更稳——Narrow 是 Unicode 层面的硬保证,不依赖字体对 Ambiguous 的取舍。
|
|
314
|
+
*
|
|
315
|
+
* 不用 entry 行尾那个 `↳`:①entry 行已内联同一串结果,详情区再原样输出一遍纯属重复;
|
|
316
|
+
* ②`↳`(U+21B3) 属 Arrows 块、EAW 同样是 Ambiguous。
|
|
317
|
+
*/
|
|
318
|
+
const DETAIL_RESULT_MARK = '· ';
|
|
319
|
+
/** entry 是否有可展开的详情行。须与 buildEntryDetailLines 的分支一致,
|
|
320
|
+
* 否则会画出点了没反应的三角(toggleEntry 对空 details 直接 return)。 */
|
|
321
|
+
function hasEntryDetail(e) {
|
|
322
|
+
return Boolean(e.diffBlock || e.fullOutput || e.resultSummary);
|
|
323
|
+
}
|
|
324
|
+
/** entry 行的展开三角(含尾随空格与配色)。折叠 dim、展开 accent,一眼区分当前态。 */
|
|
325
|
+
function entryCaret(e, expanded) {
|
|
326
|
+
if (!hasEntryDetail(e))
|
|
327
|
+
return CARET_NONE;
|
|
328
|
+
return expanded ? `${ui.accent}${CARET_EXPANDED}${ui.reset} ` : `${ui.dim}${CARET_COLLAPSED}${ui.reset} `;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 单条第一层明细行。三角由 entry 自身的详情展开态决定(▸ 可展开 / ▾ 已展开 / 空白占位),
|
|
332
|
+
* 不再依赖 isLast —— 兄弟条目间没有嵌套语义,画分支符(├─/└─)纯属误导且引入宽度歧义。
|
|
333
|
+
*/
|
|
334
|
+
function buildEntryLine(b, index, extraIndent = '') {
|
|
335
|
+
const e = b.entries[index];
|
|
284
336
|
const result = e.resultSummary ? ` ${ui.gray}↳ ${e.resultSummary}${ui.reset}` : '';
|
|
285
|
-
const
|
|
337
|
+
const caret = entryCaret(e, b.expandedEntries.has(index));
|
|
286
338
|
const failure = e.failed ? `${ui.red}×${ui.reset} ` : '';
|
|
287
|
-
return sanitizeRow(`${extraIndent} ${
|
|
339
|
+
return sanitizeRow(`${extraIndent} ${caret}${failure}${ui.accent}${e.name}${ui.reset} ${ui.dim}${e.callSummary}${ui.reset}${result}`);
|
|
288
340
|
}
|
|
289
|
-
/** 第一层只展示有哪些调用及其简短结果,不展开完整输出。extraIndent 供子批嵌套加深缩进。
|
|
290
|
-
|
|
291
|
-
|
|
341
|
+
/** 第一层只展示有哪些调用及其简短结果,不展开完整输出。extraIndent 供子批嵌套加深缩进。
|
|
342
|
+
* fromIndex:运行态追加渲染时只产出 entries[fromIndex, ...) 的行(已渲染的不能重复输出)。 */
|
|
343
|
+
function buildExpandedLines(b, extraIndent = '', fromIndex = 0) {
|
|
344
|
+
const out = [];
|
|
345
|
+
for (let i = fromIndex; i < b.entries.length; i++)
|
|
346
|
+
out.push(buildEntryLine(b, i, extraIndent));
|
|
347
|
+
return out;
|
|
292
348
|
}
|
|
293
|
-
|
|
294
|
-
|
|
349
|
+
/** 详情行缩进(现在与 index 无关,统一常量;保留签名以免调用点全改)。 */
|
|
350
|
+
function entryDetailIndent(_entries, _index) {
|
|
351
|
+
return DETAIL_INDENT;
|
|
295
352
|
}
|
|
296
353
|
/** 在 batch 收尾时(onToolBatchEnd):写摘要行 + 登记 summaryAbsIdx;若已展开(回放场景)立即插详情。 */
|
|
297
354
|
export function endBatch(id, layout) {
|
|
@@ -404,7 +461,7 @@ function syncLiveExpanded(b, layout) {
|
|
|
404
461
|
const abs = findParentEntryAbsLine(b.id, i);
|
|
405
462
|
if (abs == null)
|
|
406
463
|
continue;
|
|
407
|
-
layout.contentReplaceLine(abs, buildEntryLine(
|
|
464
|
+
layout.contentReplaceLine(abs, buildEntryLine(b, i, b.indent ?? ''));
|
|
408
465
|
e.renderedDigest = digest;
|
|
409
466
|
}
|
|
410
467
|
// ② 新 entry(运行态 recordCall 后)追加到明细区末尾。
|
|
@@ -443,7 +500,7 @@ export function refreshBatchExpanded(id, layout) {
|
|
|
443
500
|
if (b.entries.length <= b.renderedCount)
|
|
444
501
|
return;
|
|
445
502
|
const newEntries = b.entries.slice(b.renderedCount);
|
|
446
|
-
const lines = buildExpandedLines(
|
|
503
|
+
const lines = buildExpandedLines(b, b.indent ?? '', b.renderedCount);
|
|
447
504
|
// 实时追加:不锚定视口,让新明细行自然出现在屏底。
|
|
448
505
|
// 组容器批的 entry 与子批摘要行交错,新 entry 必须插在当前块末尾,
|
|
449
506
|
// 不能简单用 summaryAbsIdx+renderedCount(否则 entry 会插到前一个子批摘要行之前)。
|
|
@@ -511,7 +568,7 @@ export function toggleBatch(id, layout) {
|
|
|
511
568
|
function expand(b, layout, live = false) {
|
|
512
569
|
if (b.summaryAbsIdx < 0)
|
|
513
570
|
return; // 摘要行未落盘时展开会把明细插到 buffer 头部
|
|
514
|
-
const lines = buildExpandedLines(b
|
|
571
|
+
const lines = buildExpandedLines(b, b.indent ?? '');
|
|
515
572
|
layout.contentInsertAfter(b.summaryAbsIdx, lines, !live);
|
|
516
573
|
expandedBatches.add(b.id);
|
|
517
574
|
b.renderedCount = b.entries.length;
|
|
@@ -548,7 +605,7 @@ export function expandSingleEntryFully(id, layout) {
|
|
|
548
605
|
if (!b || b.entries.length !== 1 || expandedBatches.has(id))
|
|
549
606
|
return;
|
|
550
607
|
const lines = [
|
|
551
|
-
...buildExpandedLines(b
|
|
608
|
+
...buildExpandedLines(b),
|
|
552
609
|
...buildEntryDetailLines(b.entries[0], entryDetailIndent(b.entries, 0)),
|
|
553
610
|
];
|
|
554
611
|
layout.contentInsertAfter(b.summaryAbsIdx, lines);
|
|
@@ -643,13 +700,19 @@ export function toggleEntry(batchId, entryIndex, layout) {
|
|
|
643
700
|
const details = buildEntryDetailLines(b.entries[entryIndex], entryDetailIndent(b.entries, entryIndex));
|
|
644
701
|
if (details.length === 0)
|
|
645
702
|
return;
|
|
646
|
-
|
|
647
|
-
|
|
703
|
+
const wasExpanded = b.expandedEntries.has(entryIndex);
|
|
704
|
+
// 先翻状态再重画三角:buildEntryLine 读 b.expandedEntries 决定 ▸/▾。
|
|
705
|
+
// 替换 entry 行本身(行数不变)不会触发 shiftBatchesAfter,故可在插/删详情前做。
|
|
706
|
+
if (wasExpanded)
|
|
648
707
|
b.expandedEntries.delete(entryIndex);
|
|
708
|
+
else
|
|
709
|
+
b.expandedEntries.add(entryIndex);
|
|
710
|
+
layout.contentReplaceLine(headerIdx, buildEntryLine(b, entryIndex, b.indent ?? ''));
|
|
711
|
+
if (wasExpanded) {
|
|
712
|
+
layout.contentDeleteFrom(headerIdx + 1, details.length);
|
|
649
713
|
}
|
|
650
714
|
else {
|
|
651
715
|
layout.contentInsertAfter(headerIdx, details);
|
|
652
|
-
b.expandedEntries.add(entryIndex);
|
|
653
716
|
}
|
|
654
717
|
}
|
|
655
718
|
/**
|
package/dist/ui/layout.js
CHANGED
|
@@ -1332,6 +1332,7 @@ function handleMouseEvent(e) {
|
|
|
1332
1332
|
m.toggleEntry(entry.batchId, entry.entryIndex, {
|
|
1333
1333
|
contentInsertAfter: (after, lines) => contentInsertAfter(after, lines),
|
|
1334
1334
|
contentDeleteFrom: (start, n) => contentDeleteFrom(start, n),
|
|
1335
|
+
contentReplaceLine: (absIdx, line) => contentReplaceLine(absIdx, line),
|
|
1335
1336
|
});
|
|
1336
1337
|
repaint();
|
|
1337
1338
|
return;
|