mocode-ai 1.1.3 → 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/config/index.js +27 -16
- 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/config/index.js
CHANGED
|
@@ -178,8 +178,12 @@ export function buildBasePrompt(sessionId = getCurrentSessionId()) {
|
|
|
178
178
|
const memorySection = buildMemoryPromptSection();
|
|
179
179
|
const notepadSection = buildNotepadSection(sessionId);
|
|
180
180
|
// 静态主体:稳定段落集中在前,让支持 prompt caching 的后端能命中前缀缓存(#12)。
|
|
181
|
+
// 约束:staticBody 的前缀段(尤其 ## Core behavior 第一行)必须是纯静态文本,
|
|
182
|
+
// 不得嵌入会话级可变函数调用(如 t()/config.model)。否则 /language、/model
|
|
183
|
+
// 切换会让最敏感的前缀变化,破坏自动前缀缓存命中。可变值统一放到
|
|
184
|
+
// ## Termination & Reporting 段末尾(仍在切片边界之前,子 agent 仍能拿到)。
|
|
181
185
|
const staticBody = `## Core behavior
|
|
182
|
-
You are mocode, a terminal coding agent. Complete programming tasks through a "think → call tool → observe result → think again" loop until solved.
|
|
186
|
+
You are mocode, a terminal coding agent. Complete programming tasks through a "think → call tool → observe result → think again" loop until solved.
|
|
183
187
|
|
|
184
188
|
## Modes
|
|
185
189
|
- AUTO is the default: investigate and complete the task with the tools currently exposed.
|
|
@@ -213,26 +217,33 @@ ${buildCodegraphSection()}
|
|
|
213
217
|
- Stop immediately when no more tools are needed; give conclusions directly.
|
|
214
218
|
- **Do not stop prematurely during exploration**: if you started investigating but haven't gathered enough information to answer the user's question, keep calling tools. Only stop when you have sufficient evidence or hit a dead end.
|
|
215
219
|
- **No flattery / no preamble in conclusions**: skip "Sure", "好的", "我已经完成了" and similar no-information prefixes — jump straight to substance.
|
|
216
|
-
- Report honestly: say success when successful, say where you're stuck when failing, and mention anything skipped. Reference code in "path:line" format (e.g., src/index.ts:42). Keep it concise
|
|
217
|
-
|
|
218
|
-
//
|
|
220
|
+
- Report honestly: say success when successful, say where you're stuck when failing, and mention anything skipped. Reference code in "path:line" format (e.g., src/index.ts:42). Keep it concise.
|
|
221
|
+
${t('assistant.languageInstruction')}`;
|
|
222
|
+
// 动态段(置于末尾):memory 索引 + notepad 目录与使用说明。
|
|
223
|
+
// 按需注入(#13):仅当有内容才拼对应标题/说明,避免空标题与无文件时的模板噪声。
|
|
224
|
+
// - "## Project context" 仅当 memorySection/notepadSection 非空;
|
|
225
|
+
// - notepad 使用说明仅当 notes.md 文件存在(notepadSection 非空)—
|
|
226
|
+
// 无文件时连 marker 都不拼,既省 token 也让前缀缓存更稳。core 切片
|
|
227
|
+
// 回退到 MARKER_DYNAMIC_SECTION 或整段(见 buildMocodeCorePrompt)。
|
|
219
228
|
const dynamicParts = [];
|
|
220
229
|
const ctxContent = `${memorySection}${notepadSection}`.trimEnd();
|
|
221
230
|
if (ctxContent) {
|
|
222
231
|
dynamicParts.push(`## Project context (dynamic reference)\n${ctxContent}`);
|
|
223
232
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
if (notepadSection) {
|
|
234
|
+
dynamicParts.push(`## Session Notepad (\`.mocode/sessions/${sessionId ?? '<id>'}/notes.md\`)\n` +
|
|
235
|
+
'Use this compact, persistent working surface for tasks with at least three steps or context-loss risk; skip it for simple work.\n\n' +
|
|
236
|
+
'Keep at most one active plan:\n' +
|
|
237
|
+
'```\n' +
|
|
238
|
+
'## Plan: <title>\n' +
|
|
239
|
+
'Goal: <outcome>\n' +
|
|
240
|
+
'### Steps\n' +
|
|
241
|
+
'- [ ] 1. <verifiable step>\n' +
|
|
242
|
+
'### Progress\n' +
|
|
243
|
+
'- <completed phase and evidence>\n' +
|
|
244
|
+
'```\n' +
|
|
245
|
+
'Update checkboxes and Progress after each completed phase. Before the final reply, reconcile the plan with actual work, then rename it to `## Done:` or remove it. Keep other notes concise and session-specific; use memory for stable cross-session facts.');
|
|
246
|
+
}
|
|
236
247
|
return `${staticBody}\n\n${dynamicParts.join('\n\n')}`;
|
|
237
248
|
}
|
|
238
249
|
/** 静态主体结束 + 会话私有段起点标记,供 buildMocodeCorePrompt 稳健切片(#17)。 */
|
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;
|