mocode-ai 1.7.9 → 1.7.11
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/agent/index.js +5 -4
- package/dist/agent/run-coordinator.js +6 -0
- package/dist/agent/stages/tool-dispatcher.js +1 -0
- package/dist/agent/tool-helpers.js +18 -1
- package/dist/config/index.js +4 -1
- package/dist/headless.js +8 -1
- package/dist/i18n/index.js +4 -0
- package/dist/index.js +1 -1
- package/dist/memory/graph.js +262 -1
- package/dist/memory/index.js +3 -1
- package/dist/memory/persona.js +142 -0
- package/dist/memory/recall-stats.js +61 -0
- package/dist/memory/recall.js +47 -0
- package/dist/memory/reflect.js +112 -17
- package/dist/memory/relation-vocab.js +84 -0
- package/dist/repl/message-format.js +23 -1
- package/dist/repl/runtime.js +13 -4
- package/dist/runtime/runtime.js +1 -1
- package/dist/session/compact-archive.js +232 -0
- package/dist/session/compact.js +19 -2
- package/dist/session/persist.js +4 -3
- package/dist/session/retention.js +33 -2
- package/dist/session/store.js +187 -3
- package/dist/tools/builtins/grep.js +5 -2
- package/dist/tools/builtins/memory-graph.js +2 -0
- package/dist/tools/builtins/memory-search.js +20 -14
- package/dist/tools/builtins/run-command.js +12 -3
- package/dist/tools/constants.js +4 -0
- package/dist/tools/ripgrep-client.js +11 -1
- package/dist/tools/tool-runtime.js +1 -0
- package/dist/ui/batch.js +89 -8
- package/package.json +1 -1
|
@@ -46,7 +46,7 @@ class BoundedCommandOutput {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
/** Execute a command with the same sandbox, output cap and cancellation semantics as run_command. */
|
|
49
|
-
export async function runCommandRaw(command, timeout = DEFAULT_COMMAND_TIMEOUT_MS, signal, cwd, shell) {
|
|
49
|
+
export async function runCommandRaw(command, timeout = DEFAULT_COMMAND_TIMEOUT_MS, signal, cwd, shell, onOutput) {
|
|
50
50
|
const startedAt = Date.now();
|
|
51
51
|
// 内部调用方(skill 注入等)也走同一钳制:防止任何路径把前台命令挂成无限期持锁。
|
|
52
52
|
const effectiveTimeout = clampCommandTimeout(timeout);
|
|
@@ -124,7 +124,16 @@ export async function runCommandRaw(command, timeout = DEFAULT_COMMAND_TIMEOUT_M
|
|
|
124
124
|
const onAbort = () => {
|
|
125
125
|
terminate('aborted');
|
|
126
126
|
};
|
|
127
|
-
const onChunk = (chunk) =>
|
|
127
|
+
const onChunk = (chunk) => {
|
|
128
|
+
const text = chunk.toString('utf8');
|
|
129
|
+
output.append(text);
|
|
130
|
+
try {
|
|
131
|
+
onOutput?.(text);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Output observers must not affect the command process lifecycle.
|
|
135
|
+
}
|
|
136
|
+
};
|
|
128
137
|
// 先起 timer 再挂事件:finish/onAbort 是闭包,里面要 clearTimeout(timer)。
|
|
129
138
|
// 把 timer 的初始化提到所有引用它的注册点之前,避免依赖"事件回调必然异步"这一前提。
|
|
130
139
|
const timer = setTimeout(() => {
|
|
@@ -221,7 +230,7 @@ export const runCommandTool = {
|
|
|
221
230
|
}
|
|
222
231
|
shell = parsed;
|
|
223
232
|
}
|
|
224
|
-
const result = await runCommandRaw(command, timeout, ctx?.signal, undefined, shell);
|
|
233
|
+
const result = await runCommandRaw(command, timeout, ctx?.signal, undefined, shell, ctx?.onOutput);
|
|
225
234
|
const outcome = commandOutcome(result);
|
|
226
235
|
// 钳制要显式告知:模型传了 30min 却按 10min 判超时,不说清它会以为是环境抽风而盲目重试。
|
|
227
236
|
if (args.timeout !== undefined && Number(args.timeout) !== timeout && result.status === 'timed_out') {
|
package/dist/tools/constants.js
CHANGED
|
@@ -29,6 +29,10 @@ export const MAX_OLD_TOOL_STUB = 600;
|
|
|
29
29
|
export const MAX_TRACE_BYTES = 512 * 1024;
|
|
30
30
|
/** 保留的历史 trace 轮转份数(trace.1 .. trace.N),更老的硬删。 */
|
|
31
31
|
export const MAX_TRACE_ROTATIONS = 3;
|
|
32
|
+
/** compacted/ 压缩归档目录总量上限(字节):旧区原文逐段累积,超了从最老 seg 淘汰。
|
|
33
|
+
* 段大小与窗口正相关(200k 窗口单段约几百 KB),10MiB 约覆盖十几~几十次压缩。
|
|
34
|
+
* 可用 MOCODE_MAX_COMPACTED_BYTES 覆盖(测试/小盘用)。 */
|
|
35
|
+
export const MAX_COMPACTED_ARCHIVE_BYTES = 10 * 1024 * 1024;
|
|
32
36
|
/** rollback snapshots 压缩前滚动窗口:无论是否 compact,仅保留最近 N 个 user turn 的快照。 */
|
|
33
37
|
export const MAX_SNAPSHOT_TURNS = 50;
|
|
34
38
|
// ── 上下文压缩摘要(见 session/compact.ts defaultSummarize)────────────────
|
|
@@ -69,6 +69,9 @@ export function runRipgrep(params, signal) {
|
|
|
69
69
|
// --no-require-git:rg 默认只在 git 仓库内才读 .gitignore;加上后非 git 项目的 .gitignore
|
|
70
70
|
// 同样生效(实测不加时 ignored/ 会被扫到)。
|
|
71
71
|
const args = ['--json', '--hidden', '--no-require-git', '--path-separator', '/'];
|
|
72
|
+
// glob 显式指向 gitignore 内的 .mocode(回捞压缩归档等)时放开 VCS ignore。
|
|
73
|
+
if (params.noIgnoreVcs)
|
|
74
|
+
args.push('--no-ignore-vcs');
|
|
72
75
|
if (params.context > 0)
|
|
73
76
|
args.push('-C', String(params.context));
|
|
74
77
|
if (params.glob && params.glob !== '**/*')
|
|
@@ -169,7 +172,14 @@ export function runRipgrep(params, signal) {
|
|
|
169
172
|
handleLine(buffer.trim());
|
|
170
173
|
// exit 2 + stderr = 正则/参数错误;无命中事件时按 regex_error 上报。
|
|
171
174
|
if (code === 2 && byPath.size === 0 && stderr.trim()) {
|
|
172
|
-
|
|
175
|
+
const message = stderr.trim();
|
|
176
|
+
// 过滤器把所有文件排除(典型:glob 指向 .gitignore 内的 .mocode 却没放开 ignore)
|
|
177
|
+
// 不是正则错误——按无匹配处理,避免误导模型去改 pattern。
|
|
178
|
+
if (/No files were searched/i.test(message)) {
|
|
179
|
+
finish({ status: 'no-match', scanned, truncated });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
finish({ status: 'regex_error', message });
|
|
173
183
|
return;
|
|
174
184
|
}
|
|
175
185
|
if (byPath.size === 0) {
|
package/dist/ui/batch.js
CHANGED
|
@@ -38,6 +38,8 @@ const callToBatch = new Map();
|
|
|
38
38
|
const groupChildIndexByCall = new Map();
|
|
39
39
|
/** 展开时完整输出的最大行数;超出截断,避免巨型输出撑爆 viewport。 */
|
|
40
40
|
const MAX_EXPAND_LINES = 200;
|
|
41
|
+
const MAX_LIVE_OUTPUT_CHARS = 256 * 1024;
|
|
42
|
+
const LIVE_OUTPUT_REFRESH_MS = 80;
|
|
41
43
|
/** 自洽行允许的最大显示宽(= 终端 cols)。buffer 行超 cols 会被终端 auto-wrap,
|
|
42
44
|
* 物理行与缓冲行失配 → repaintViewport 的 CUP 寻址全错(屏幕错乱)。 */
|
|
43
45
|
let maxCols = 200;
|
|
@@ -66,6 +68,12 @@ export function isMutationToolName(name) {
|
|
|
66
68
|
}
|
|
67
69
|
/** 通知 buffer 整体清空(clearContent / exitAltScreen / 新一轮 turn)——本模块状态同步归零。 */
|
|
68
70
|
export function reset() {
|
|
71
|
+
for (const record of batches.values()) {
|
|
72
|
+
for (const entry of record.entries) {
|
|
73
|
+
if (entry.liveRefreshTimer)
|
|
74
|
+
clearTimeout(entry.liveRefreshTimer);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
69
77
|
batches.clear();
|
|
70
78
|
absLineToBatchId.clear();
|
|
71
79
|
absLineToEntry.clear();
|
|
@@ -154,7 +162,61 @@ export function isBatchComplete(id) {
|
|
|
154
162
|
/** 记一条工具结果(diff 块或单行 preview);agent 在 onToolResult 时调,匹配最后一条未填的 entry。
|
|
155
163
|
* fullOutput:工具原始完整输出(纯文本),展开时显示;mutation 工具的 diff 块已自含无需传。
|
|
156
164
|
* callId:组容器批用,直接把结果填到对应 entry(避免多个 sub-agent 同名时反向匹配错位)。 */
|
|
157
|
-
|
|
165
|
+
function refreshEntryDetails(b, entryIndex, layout) {
|
|
166
|
+
if (!expandedBatches.has(b.id) || !b.expandedEntries.has(entryIndex))
|
|
167
|
+
return;
|
|
168
|
+
const headerIdx = [...absLineToEntry].find(([, target]) => target.batchId === b.id && target.entryIndex === entryIndex)?.[0];
|
|
169
|
+
if (headerIdx == null)
|
|
170
|
+
return;
|
|
171
|
+
const entry = b.entries[entryIndex];
|
|
172
|
+
const oldCount = entry.renderedDetailCount ?? 0;
|
|
173
|
+
const lines = buildEntryDetailLines(entry, entryDetailIndent(b.indent ?? ''));
|
|
174
|
+
const replacement = lines.slice(0, MAX_EXPAND_LINES);
|
|
175
|
+
if (oldCount > 0)
|
|
176
|
+
layout.contentDeleteFrom(headerIdx + 1, oldCount);
|
|
177
|
+
if (replacement.length > 0)
|
|
178
|
+
layout.contentInsertAfter(headerIdx, replacement, false);
|
|
179
|
+
entry.renderedDetailCount = replacement.length;
|
|
180
|
+
entry.renderedDigest = entryLineDigest(entry);
|
|
181
|
+
layout.contentReplaceLine(headerIdx, buildEntryLine(b, entryIndex, b.indent ?? ''));
|
|
182
|
+
}
|
|
183
|
+
export function recordOutput(callId, chunk, layout) {
|
|
184
|
+
const batchId = callToBatch.get(callId);
|
|
185
|
+
const b = batchId ? batches.get(batchId) : undefined;
|
|
186
|
+
const entryIndex = groupChildIndexByCall.get(callId);
|
|
187
|
+
if (!b || entryIndex == null || b.entries[entryIndex]?.name !== 'run_command')
|
|
188
|
+
return;
|
|
189
|
+
const entry = b.entries[entryIndex];
|
|
190
|
+
entry.liveOutput = `${entry.liveOutput ?? ''}${chunk}`;
|
|
191
|
+
if (entry.liveOutput.length > MAX_LIVE_OUTPUT_CHARS) {
|
|
192
|
+
entry.liveOutput = entry.liveOutput.slice(-MAX_LIVE_OUTPUT_CHARS);
|
|
193
|
+
}
|
|
194
|
+
if (!expandedBatches.has(b.id))
|
|
195
|
+
return;
|
|
196
|
+
if (!b.expandedEntries.has(entryIndex)) {
|
|
197
|
+
const headerIdx = [...absLineToEntry].find(([, target]) => target.batchId === b.id && target.entryIndex === entryIndex)?.[0];
|
|
198
|
+
if (headerIdx != null) {
|
|
199
|
+
entry.renderedDigest = entryLineDigest(entry);
|
|
200
|
+
layout.contentReplaceLine(headerIdx, buildEntryLine(b, entryIndex, b.indent ?? ''));
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (entry.liveRefreshTimer)
|
|
205
|
+
return;
|
|
206
|
+
const refresh = () => {
|
|
207
|
+
entry.liveRefreshTimer = undefined;
|
|
208
|
+
entry.liveRefreshAt = Date.now();
|
|
209
|
+
refreshEntryDetails(b, entryIndex, layout);
|
|
210
|
+
};
|
|
211
|
+
const wait = Math.max(0, LIVE_OUTPUT_REFRESH_MS - (Date.now() - (entry.liveRefreshAt ?? 0)));
|
|
212
|
+
if (wait === 0)
|
|
213
|
+
refresh();
|
|
214
|
+
else {
|
|
215
|
+
entry.liveRefreshTimer = setTimeout(refresh, wait);
|
|
216
|
+
entry.liveRefreshTimer.unref();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export function recordResult(id, name, resultSummary, diffBlock, fullOutput, failed = false, callId, layout) {
|
|
158
220
|
const b = batches.get(id);
|
|
159
221
|
if (!b || b.entries.length === 0)
|
|
160
222
|
return;
|
|
@@ -168,8 +230,14 @@ export function recordResult(id, name, resultSummary, diffBlock, fullOutput, fai
|
|
|
168
230
|
e.fullOutput = fullOutput;
|
|
169
231
|
e.failed = failed;
|
|
170
232
|
e.done = true;
|
|
233
|
+
if (e.liveRefreshTimer)
|
|
234
|
+
clearTimeout(e.liveRefreshTimer);
|
|
235
|
+
e.liveRefreshTimer = undefined;
|
|
236
|
+
e.liveOutput = undefined;
|
|
171
237
|
if (b.entries.every(isEntryDone))
|
|
172
238
|
b.finishedAt = Date.now();
|
|
239
|
+
if (layout)
|
|
240
|
+
refreshEntryDetails(b, idx, layout);
|
|
173
241
|
return;
|
|
174
242
|
}
|
|
175
243
|
}
|
|
@@ -181,8 +249,14 @@ export function recordResult(id, name, resultSummary, diffBlock, fullOutput, fai
|
|
|
181
249
|
b.entries[i].fullOutput = fullOutput;
|
|
182
250
|
b.entries[i].failed = failed;
|
|
183
251
|
b.entries[i].done = true;
|
|
252
|
+
if (b.entries[i].liveRefreshTimer)
|
|
253
|
+
clearTimeout(b.entries[i].liveRefreshTimer);
|
|
254
|
+
b.entries[i].liveRefreshTimer = undefined;
|
|
255
|
+
b.entries[i].liveOutput = undefined;
|
|
184
256
|
if (b.entries.every(isEntryDone))
|
|
185
257
|
b.finishedAt = Date.now();
|
|
258
|
+
if (layout)
|
|
259
|
+
refreshEntryDetails(b, i, layout);
|
|
186
260
|
return;
|
|
187
261
|
}
|
|
188
262
|
}
|
|
@@ -194,8 +268,14 @@ export function recordResult(id, name, resultSummary, diffBlock, fullOutput, fai
|
|
|
194
268
|
last.fullOutput = fullOutput;
|
|
195
269
|
last.failed = failed;
|
|
196
270
|
last.done = true;
|
|
271
|
+
if (last.liveRefreshTimer)
|
|
272
|
+
clearTimeout(last.liveRefreshTimer);
|
|
273
|
+
last.liveRefreshTimer = undefined;
|
|
274
|
+
last.liveOutput = undefined;
|
|
197
275
|
if (b.entries.every(isEntryDone))
|
|
198
276
|
b.finishedAt = Date.now();
|
|
277
|
+
if (layout)
|
|
278
|
+
refreshEntryDetails(b, b.entries.length - 1, layout);
|
|
199
279
|
}
|
|
200
280
|
}
|
|
201
281
|
// ── 摘要行文本生成 ──
|
|
@@ -465,11 +545,10 @@ function buildEntryDetailLines(e, indent = DETAIL_INDENT) {
|
|
|
465
545
|
lines.push(sanitizeRow(prefixed));
|
|
466
546
|
}
|
|
467
547
|
}
|
|
468
|
-
else if (e.fullOutput) {
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
const rawLines = e.fullOutput.split('\n');
|
|
548
|
+
else if (e.liveOutput || e.fullOutput) {
|
|
549
|
+
// 实时输出优先展示;结束后由 recordResult 替换成最终有界工具结果。
|
|
550
|
+
// 每行 sanitizeRow 会清除控制字符并钳宽,避免 ANSI/终端控制序列污染 TUI。
|
|
551
|
+
const rawLines = (e.liveOutput || e.fullOutput || '').split(/\r\n|\n|\r/);
|
|
473
552
|
const truncated = rawLines.length > MAX_EXPAND_LINES;
|
|
474
553
|
const displayLines = truncated ? rawLines.slice(0, MAX_EXPAND_LINES) : rawLines;
|
|
475
554
|
for (const line of displayLines) {
|
|
@@ -493,7 +572,7 @@ function buildEntryDetailLines(e, indent = DETAIL_INDENT) {
|
|
|
493
572
|
* 只取有无不取内容:完整输出可能上万字符,拼进指纹是浪费,且其内容不影响 entry 行渲染。
|
|
494
573
|
*/
|
|
495
574
|
function entryLineDigest(e) {
|
|
496
|
-
return `${e.resultSummary}\u0001${e.diffBlock ? '1' : '0'}\u0001${e.fullOutput ? '1' : '0'}\u0001${e.failed ? '1' : '0'}`;
|
|
575
|
+
return `${e.resultSummary}\u0001${e.diffBlock ? '1' : '0'}\u0001${e.fullOutput || e.liveOutput ? '1' : '0'}\u0001${e.failed ? '1' : '0'}`;
|
|
497
576
|
}
|
|
498
577
|
/**
|
|
499
578
|
* 第二层展开三角:折叠 ▸ / 展开 ▾。替代原先的 ├─ / └─。
|
|
@@ -537,7 +616,7 @@ const DETAIL_RESULT_MARK = '· ';
|
|
|
537
616
|
/** entry 是否有可展开的详情行。须与 buildEntryDetailLines 的分支一致,
|
|
538
617
|
* 否则会画出点了没反应的三角(toggleEntry 对空 details 直接 return)。 */
|
|
539
618
|
function hasEntryDetail(e) {
|
|
540
|
-
return Boolean(e.diffBlock || e.fullOutput || e.resultSummary);
|
|
619
|
+
return Boolean(e.diffBlock || e.fullOutput || e.liveOutput || e.resultSummary);
|
|
541
620
|
}
|
|
542
621
|
/** entry 行的展开三角(含尾随空格与配色)。折叠 dim、展开 accent,一眼区分当前态。 */
|
|
543
622
|
function entryCaret(e, expanded) {
|
|
@@ -938,9 +1017,11 @@ export function toggleEntry(batchId, entryIndex, layout) {
|
|
|
938
1017
|
layout.contentReplaceLine(headerIdx, buildEntryLine(b, entryIndex, b.indent ?? ''));
|
|
939
1018
|
if (wasExpanded) {
|
|
940
1019
|
layout.contentDeleteFrom(headerIdx + 1, details.length);
|
|
1020
|
+
b.entries[entryIndex].renderedDetailCount = 0;
|
|
941
1021
|
}
|
|
942
1022
|
else {
|
|
943
1023
|
layout.contentInsertAfter(headerIdx, details);
|
|
1024
|
+
b.entries[entryIndex].renderedDetailCount = Math.min(details.length, MAX_EXPAND_LINES);
|
|
944
1025
|
}
|
|
945
1026
|
}
|
|
946
1027
|
/**
|