mocode-ai 1.1.4 → 1.1.5
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/llm/index.js +7 -0
- package/dist/repl/index.js +10 -0
- package/dist/ui/batch.js +6 -1
- package/dist/ui/layout.js +55 -0
- package/package.json +1 -1
package/dist/llm/index.js
CHANGED
|
@@ -3,6 +3,13 @@ import { config, isSubAgentEnabled } from '../config/index.js';
|
|
|
3
3
|
import { tools } from '../tools/registry.js';
|
|
4
4
|
import { getPlanDisabledTools } from '../tools/constants.js';
|
|
5
5
|
import { ThinkTagFilter } from './think-filter.js';
|
|
6
|
+
// 强制关闭第三方调试日志泄漏:openai SDK 在 process.env.DEBUG === 'true' 时用裸
|
|
7
|
+
// console.log 把请求/响应直写 stdout,会污染 TUI 输入框(并泄露 headers/URL)。
|
|
8
|
+
// 仅拦截 'true' 这一开关值——保留 namespace 形式的 DEBUG(如 DEBUG=express:*) 调试能力。
|
|
9
|
+
// 必须在 OpenAI 客户端实例化之前执行,确保任何实例都不再触发 debug 输出。
|
|
10
|
+
if (process.env.DEBUG === 'true') {
|
|
11
|
+
process.env.DEBUG = 'false';
|
|
12
|
+
}
|
|
6
13
|
/**
|
|
7
14
|
* LLM 调用重试策略:
|
|
8
15
|
* 可重试 → 429 (rate limit) / 5xx (server) / APIConnectionError / Node 网络错 (ETIMEDOUT 等)
|
package/dist/repl/index.js
CHANGED
|
@@ -760,6 +760,8 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
760
760
|
layout.contentMode();
|
|
761
761
|
if (history.some((m) => m.role === 'user')) {
|
|
762
762
|
renderHistory(history);
|
|
763
|
+
// 强制回尾:同 /resume 命令,renderHistory 展开详情会设 scrollOffset>0,需复位避免闪烁。
|
|
764
|
+
layout.resetScroll();
|
|
763
765
|
}
|
|
764
766
|
else {
|
|
765
767
|
layout.writeBanner(bannerLines(banner()));
|
|
@@ -907,6 +909,11 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
907
909
|
// rollbackFlow 原本漏了这一行,renderHistory 末尾的 batch 摘要行 / assistant 文本
|
|
908
910
|
// 收口后,续写位未稳到新空行,下一次 echoInput 的 ❯ 气泡会黏在最后一条输出后面。
|
|
909
911
|
layout.contentWrite('\n');
|
|
912
|
+
// 回滚后强制回尾:确保 scrollOffset=0,避免后续 showLiveBatch 在冻结视口下
|
|
913
|
+
// 调用 repaintViewport 导致工具信息闪烁/滚动消失(rollback 后用户继续输入触发新 agent
|
|
914
|
+
// 运行时,若 scrollOffset 意外 >0,contentWrite 只喂缓冲不物理写,showLiveBatch 的
|
|
915
|
+
// repaintViewport 画出不含新摘要的冻结窗口 → 工具信息"闪现后消失")。
|
|
916
|
+
layout.resetScroll();
|
|
910
917
|
pendingPrefill = prefillText.split('\n');
|
|
911
918
|
};
|
|
912
919
|
/**
|
|
@@ -1040,6 +1047,9 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
1040
1047
|
lastTurnUsage = undefined; // 续接:旧会话的 token 累计已无意义,清空等下轮覆写
|
|
1041
1048
|
layout.clearContent();
|
|
1042
1049
|
renderHistory(history);
|
|
1050
|
+
// 强制回尾:renderHistory 展开 mutation 工具详情会经 contentInsertAfter 设置 scrollOffset>0;
|
|
1051
|
+
// 若不复位,后续 showLiveBatch 在冻结视口下 repaintViewport 会导致工具信息闪烁/滚动消失。
|
|
1052
|
+
layout.resetScroll();
|
|
1043
1053
|
// 末尾 \n\n:与后续用户消息(❯ bubble)之间空一行。
|
|
1044
1054
|
layout.contentWrite(`${ui.dim}${t('repl.resumed', { id: loaded.id })}${ui.reset}\n\n`);
|
|
1045
1055
|
}
|
package/dist/ui/batch.js
CHANGED
|
@@ -194,7 +194,12 @@ export function showLiveBatch(id, layout) {
|
|
|
194
194
|
// 首条摘要通过增量 contentWrite 落屏时,markdown→普通内容的边界可能只更新了
|
|
195
195
|
// buffer/续写位;直到第二个 header 的 contentReplaceLine 或后续正文重绘才完全可见。
|
|
196
196
|
// 立即按 buffer 原子重画,确保慢工具执行期间摘要前的空行已经显示。
|
|
197
|
-
|
|
197
|
+
// 但滚动回看时(scrollOffset>0) contentWrite 只喂缓冲+冻结视口,此时 repaintViewport
|
|
198
|
+
// 会画出不含新摘要的冻结窗口(新摘要在窗口之下),造成闪烁;跳过重画,
|
|
199
|
+
// 等用户回底(scrollOffset=0)时自然可见。修复 rollback 后工具信息滚动消失问题。
|
|
200
|
+
if (!layout.isScrolled?.()) {
|
|
201
|
+
layout.repaintViewport?.();
|
|
202
|
+
}
|
|
198
203
|
}
|
|
199
204
|
else {
|
|
200
205
|
layout.contentReplaceLine(b.summaryAbsIdx, summary);
|
package/dist/ui/layout.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stdin, stdout } from 'node:process';
|
|
2
|
+
import { inspect } from 'node:util';
|
|
2
3
|
import { charWidth, displayWidth, truncateDisplay, truncateDisplayHead, truncateAnsi, ansiDisplayWidth, wrapByDisplayWidth, fmtElapsed, stripAnsi, sliceByDisplayCol, } from './render.js';
|
|
3
4
|
import { ui, applyTerminalBackground, resetTerminalBackground } from './theme.js';
|
|
4
5
|
import * as content from './content.js';
|
|
@@ -9,6 +10,58 @@ import { renderMarkdown } from './markdown.js';
|
|
|
9
10
|
import { t } from '../i18n/index.js';
|
|
10
11
|
// ── 内部状态 ──
|
|
11
12
|
let active = false;
|
|
13
|
+
// ── 裸 console 防御:第三方库(如 openai SDK)可能用 console.log 直写 stdout,
|
|
14
|
+
// 在 RUNNING 态会落到光标所在的底栏输入框,污染输入。进入 TUI 后把 console.*
|
|
15
|
+
// 劫持到 contentWrite,统一进内容区(运行态下 contentWrite 末尾会把真光标归位输入框),
|
|
16
|
+
// 既不再泄漏到输入框,也不会破坏 TUI 布局。TUI 外(active=false)不劫持,
|
|
17
|
+
// 恢复原始 console,保证 host 子进程 JSON 协议 / 退出日志正常。
|
|
18
|
+
let consoleHookInstalled = false;
|
|
19
|
+
const origConsole = {
|
|
20
|
+
log: console.log,
|
|
21
|
+
error: console.error,
|
|
22
|
+
warn: console.warn,
|
|
23
|
+
info: console.info,
|
|
24
|
+
};
|
|
25
|
+
function routeConsoleToContent(method) {
|
|
26
|
+
const c = console;
|
|
27
|
+
c[method] = (...args) => {
|
|
28
|
+
if (active && ui.isTTY) {
|
|
29
|
+
let s;
|
|
30
|
+
try {
|
|
31
|
+
s = args
|
|
32
|
+
.map((a) => (typeof a === 'string' ? a : inspect(a, { depth: 4 })))
|
|
33
|
+
.join(' ');
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
s = String(args[0]);
|
|
37
|
+
}
|
|
38
|
+
if (!s.endsWith('\n'))
|
|
39
|
+
s += '\n';
|
|
40
|
+
contentWrite(s);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
origConsole[method](...args);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function installConsoleGuard() {
|
|
48
|
+
if (consoleHookInstalled)
|
|
49
|
+
return;
|
|
50
|
+
routeConsoleToContent('log');
|
|
51
|
+
routeConsoleToContent('error');
|
|
52
|
+
routeConsoleToContent('warn');
|
|
53
|
+
routeConsoleToContent('info');
|
|
54
|
+
consoleHookInstalled = true;
|
|
55
|
+
}
|
|
56
|
+
function uninstallConsoleGuard() {
|
|
57
|
+
if (!consoleHookInstalled)
|
|
58
|
+
return;
|
|
59
|
+
console.log = origConsole.log;
|
|
60
|
+
console.error = origConsole.error;
|
|
61
|
+
console.warn = origConsole.warn;
|
|
62
|
+
console.info = origConsole.info;
|
|
63
|
+
consoleHookInstalled = false;
|
|
64
|
+
}
|
|
12
65
|
let mode = 'input';
|
|
13
66
|
let footerH = 6; // 1 虚拟空行 + 1 spinner行 + 1 上线 + 输入行数 + 1 下线 + 1 model行(两行式底栏)
|
|
14
67
|
let contentRow = 1; // 续写位行(1-based,屏坐标,[1,contentBottom])
|
|
@@ -1944,12 +1997,14 @@ export function enterAltScreen() {
|
|
|
1944
1997
|
resizeTimer?.unref?.();
|
|
1945
1998
|
};
|
|
1946
1999
|
process.on('SIGWINCH', sigwinchHandler);
|
|
2000
|
+
installConsoleGuard(); // 进入 TUI 即接管裸 console 输出,防第三方日志污染输入框
|
|
1947
2001
|
}
|
|
1948
2002
|
/** 复原:复位 margins + 显光标 + 退 alt + 还原 raw。幂等。 */
|
|
1949
2003
|
export function exitAltScreen() {
|
|
1950
2004
|
if (!active)
|
|
1951
2005
|
return;
|
|
1952
2006
|
active = false;
|
|
2007
|
+
uninstallConsoleGuard(); // 退出 TUI 恢复原始 console(不影响 host 子进程 / 退出日志)
|
|
1953
2008
|
resetTerminalBackground();
|
|
1954
2009
|
stopTurnTimer(); // 兜底清走时计时器(防异常退出泄漏)
|
|
1955
2010
|
turnStart = null;
|