mocode-ai 0.5.4 → 0.5.6
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/core.js +34 -49
- package/dist/agent/index.js +96 -52
- package/dist/agent/spawn.js +7 -2
- package/dist/config/index.js +0 -1
- package/dist/context/budget.js +22 -33
- package/dist/context/index.js +1 -1
- package/dist/context/lifecycle.js +1 -55
- package/dist/context/relevance.js +1 -62
- package/dist/context/utils.js +68 -0
- package/dist/project-skill/initializer.js +83 -83
- package/dist/project-snapshot/llm-snapshot.js +69 -69
- package/dist/repl/index.js +28 -22
- package/dist/rollback/index.js +1 -13
- package/dist/session/compact.js +20 -24
- package/dist/session/drop.js +1 -43
- package/dist/session/index.js +1 -1
- package/dist/session/scheduler.js +8 -8
- package/dist/tools/builtins/todolist.js +2 -4
- package/dist/ui/batch.js +142 -207
- package/dist/ui/content.js +22 -0
- package/dist/ui/layout.js +55 -37
- package/dist/ui/markdown.js +8 -2
- package/package.json +1 -1
package/dist/ui/layout.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { stdin, stdout } from 'node:process';
|
|
2
|
-
import { charWidth, displayWidth, truncateDisplay, truncateDisplayHead, ansiDisplayWidth, wrapByDisplayWidth, fmtElapsed, stripAnsi, sliceByDisplayCol, } from './render.js';
|
|
2
|
+
import { charWidth, displayWidth, truncateDisplay, truncateDisplayHead, truncateAnsi, ansiDisplayWidth, wrapByDisplayWidth, fmtElapsed, stripAnsi, sliceByDisplayCol, } from './render.js';
|
|
3
3
|
import { ui } from './theme.js';
|
|
4
4
|
import * as content from './content.js';
|
|
5
5
|
import * as mouse from './mouse.js';
|
|
6
|
+
import { shiftBatchesAfter } from './batch.js';
|
|
6
7
|
import { copyToClipboard, readClipboard } from './clipboard.js';
|
|
7
8
|
import { renderMarkdown } from './markdown.js';
|
|
8
9
|
// ── 内部状态 ──
|
|
@@ -47,7 +48,6 @@ let sigwinchHandler = null;
|
|
|
47
48
|
// 非 md 写(contentWrite)先 commitMd 收尾(清 segMark,后续写不再被 setLines 截断)。
|
|
48
49
|
let mdActive = false;
|
|
49
50
|
let mdBuf = '';
|
|
50
|
-
let mdLeadingBlank = false;
|
|
51
51
|
let selection = null;
|
|
52
52
|
let selecting = false; // 左键按下中(press→release 之间)
|
|
53
53
|
let mouseEnabled = true; // 导航菜单(picker)期间置 false:只吞报表不做选区/滚动,防菜单被 viewport 重画覆盖
|
|
@@ -313,11 +313,10 @@ export function contentWrite(s) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
/** 进入 markdown 流式段:标记段起点(layout 续写位 + content.beginSegment),清 accumulator。 */
|
|
316
|
-
function beginMdSegment(
|
|
316
|
+
function beginMdSegment() {
|
|
317
317
|
segmentStartRow = contentRow;
|
|
318
318
|
content.beginSegment();
|
|
319
319
|
mdBuf = '';
|
|
320
|
-
mdLeadingBlank = leadingBlank;
|
|
321
320
|
mdActive = true;
|
|
322
321
|
}
|
|
323
322
|
/** 提交 markdown 段:清 accumulator + content.commitSegment(后续非 md 写不再被 setLines 截断)。 */
|
|
@@ -326,7 +325,6 @@ function commitMd() {
|
|
|
326
325
|
return;
|
|
327
326
|
mdActive = false;
|
|
328
327
|
mdBuf = '';
|
|
329
|
-
mdLeadingBlank = false;
|
|
330
328
|
content.commitSegment();
|
|
331
329
|
}
|
|
332
330
|
// ── Banner(启动横幅/模式切换横幅)固定顶部行 ──
|
|
@@ -406,15 +404,13 @@ export function bannerHeight() {
|
|
|
406
404
|
* 物理重画用 repaintViewport(全内容区,原子一次 write 无闪烁)— md 段是缓冲尾,viewport 显尾即显段。
|
|
407
405
|
* 滚动回看(scrollOffset>0)只更新缓冲不物理写(回尾时显);打字中照常物理写——单次 write 结尾 cup 回输入框,IME 锚定不动。
|
|
408
406
|
*/
|
|
409
|
-
export function contentWriteMd(s
|
|
407
|
+
export function contentWriteMd(s) {
|
|
410
408
|
if (!active || !ui.isTTY) {
|
|
411
409
|
stdout.write(s);
|
|
412
410
|
return;
|
|
413
411
|
}
|
|
414
412
|
if (!mdActive)
|
|
415
|
-
beginMdSegment(
|
|
416
|
-
else if (leadingBlank)
|
|
417
|
-
mdLeadingBlank = true;
|
|
413
|
+
beginMdSegment();
|
|
418
414
|
mdBuf += s;
|
|
419
415
|
const g = getGeo();
|
|
420
416
|
// 滚动回看冻结(同 contentWrite):scrollOffset>0 时 setLines 替换段会改缓冲行数,若 offset 不变,
|
|
@@ -422,12 +418,7 @@ export function contentWriteMd(s, leadingBlank = false) {
|
|
|
422
418
|
// offset += delta(可负:setLines 重渲染可能缩行)冻住视图。md 段在尾,窗口在上方,冻结后不重叠。
|
|
423
419
|
const scrolled = scrollOffset > 0;
|
|
424
420
|
const totalBefore = scrolled ? content.totalRows() : 0;
|
|
425
|
-
|
|
426
|
-
const markdownSource = mdLeadingBlank
|
|
427
|
-
? mdBuf.replace(/^(?:[ \t]*\r?\n)+/, '')
|
|
428
|
-
: mdBuf;
|
|
429
|
-
const rendered = renderMarkdown(markdownSource, g.cols);
|
|
430
|
-
const lines = mdLeadingBlank ? [ui.reset, ...rendered] : rendered;
|
|
421
|
+
const lines = renderMarkdown(mdBuf, g.cols);
|
|
431
422
|
content.setLines(lines);
|
|
432
423
|
const segRows = lines.length;
|
|
433
424
|
const available = g.contentBottom - segmentStartRow + 1;
|
|
@@ -479,8 +470,6 @@ export function clearContent() {
|
|
|
479
470
|
scrollLockUntil = 0;
|
|
480
471
|
mdActive = false;
|
|
481
472
|
mdBuf = '';
|
|
482
|
-
mdLeadingBlank = false;
|
|
483
|
-
mdLeadingBlank = false;
|
|
484
473
|
content.reset();
|
|
485
474
|
notifyContentReset(); // batch 渲染器同步重置(batch 摘要行索引全部失效)
|
|
486
475
|
stdout.write(esc.home);
|
|
@@ -553,8 +542,9 @@ export function contentInsertAfter(after, lines) {
|
|
|
553
542
|
scrollOffset = Math.min(delta, Math.max(0, content.totalRows() - g.contentBottom));
|
|
554
543
|
}
|
|
555
544
|
}
|
|
556
|
-
// batch
|
|
557
|
-
|
|
545
|
+
// 必须同步平移 batch 索引。若延迟到 dynamic import.then,下一条 mutation 可能已经
|
|
546
|
+
// 按插入后的 buffer 创建;旧回调会再平移它一次,导致详情插到空行之后。
|
|
547
|
+
shiftBatchesAfter(after, delta);
|
|
558
548
|
repaintViewport();
|
|
559
549
|
}
|
|
560
550
|
/**
|
|
@@ -582,20 +572,42 @@ export function contentDeleteFrom(startIdx, n) {
|
|
|
582
572
|
if (scrolled) {
|
|
583
573
|
scrollOffset = Math.max(0, scrollOffset - delta);
|
|
584
574
|
}
|
|
585
|
-
// batch 摘要行索引平移(用 -delta 表示后段索引前移)
|
|
586
|
-
|
|
575
|
+
// batch 摘要行索引平移(用 -delta 表示后段索引前移),同插入路径必须同步。
|
|
576
|
+
shiftBatchesAfter(startIdx, -delta);
|
|
587
577
|
repaintViewport();
|
|
588
578
|
}
|
|
589
579
|
/** 当前内容物理行总数(含当前未提交行)。供 batch 渲染器在 endBatch 时定位摘要行索引。 */
|
|
590
580
|
export function totalRows() {
|
|
591
581
|
return content.totalRows();
|
|
592
582
|
}
|
|
593
|
-
/**
|
|
594
|
-
export function
|
|
595
|
-
|
|
596
|
-
|
|
583
|
+
/** 正文→mutation 首摘要前,把尾部间距强制归一为一条视觉空行。 */
|
|
584
|
+
export function normalizeMutationBoundary() {
|
|
585
|
+
if (!active || !ui.isTTY)
|
|
586
|
+
return;
|
|
587
|
+
if (mdActive)
|
|
588
|
+
commitMd();
|
|
589
|
+
const totalBefore = content.totalRows();
|
|
590
|
+
content.normalizeTrailingBlankRows(1);
|
|
591
|
+
const g = getGeo();
|
|
592
|
+
const delta = content.totalRows() - totalBefore;
|
|
593
|
+
if (scrollOffset > 0 && delta !== 0) {
|
|
594
|
+
scrollOffset = Math.max(0, Math.min(scrollOffset + delta, Math.max(0, content.totalRows() - g.contentBottom)));
|
|
595
|
+
}
|
|
596
|
+
contentRow = Math.min(content.committedRows() + 1, g.contentBottom);
|
|
597
|
+
contentCol = 1;
|
|
598
|
+
if (scrollOffset === 0)
|
|
597
599
|
repaintViewport();
|
|
598
600
|
}
|
|
601
|
+
/** 原地刷新一条内容行(行数不变),用于运行中的工具 batch 更新计数。 */
|
|
602
|
+
export function contentReplaceLine(absIdx, line) {
|
|
603
|
+
if (!active)
|
|
604
|
+
return;
|
|
605
|
+
// 原地更新的 batch 摘要可能随追加工具而变长。缓冲区仍把它当作一行,但若直接
|
|
606
|
+
// 交给终端超过 cols,终端会自动折行,造成视觉上多出空行且续写位与缓冲失步。
|
|
607
|
+
// 在写回缓冲前按 ANSI 可见宽度截断,确保“一条逻辑行 = 一条物理行”。
|
|
608
|
+
content.replaceLine(absIdx, truncateAnsi(line, getGeo().cols));
|
|
609
|
+
repaintViewport();
|
|
610
|
+
}
|
|
599
611
|
/** 清空内容区时通知 batch 渲染器重置(摘要行映射与展开态)。 */
|
|
600
612
|
export function notifyContentReset() {
|
|
601
613
|
// 动态 import 避免循环;模块级 reset() 只清映射,不动 batch 内部数据(id 与 entries 仍可重用)
|
|
@@ -1153,18 +1165,25 @@ function handleMouseEvent(e) {
|
|
|
1153
1165
|
void (async () => {
|
|
1154
1166
|
try {
|
|
1155
1167
|
const m = await import('./batch.js');
|
|
1156
|
-
const
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1168
|
+
const entry = m.findEntryByAbsLine(absClick);
|
|
1169
|
+
if (entry) {
|
|
1170
|
+
// 先清选区再改 buffer:contentInsert/Delete 内会立即 repaintViewport;若此时仍保留
|
|
1171
|
+
// 旧绝对行选区,会短暂画出一帧错位高亮,随后二次重画,视觉上就是抖动。
|
|
1172
|
+
selection = null;
|
|
1173
|
+
m.toggleEntry(entry.batchId, entry.entryIndex, {
|
|
1159
1174
|
contentInsertAfter: (after, lines) => contentInsertAfter(after, lines),
|
|
1160
1175
|
contentDeleteFrom: (start, n) => contentDeleteFrom(start, n),
|
|
1161
|
-
};
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1176
|
+
});
|
|
1177
|
+
repaint();
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const id = m.findBatchByAbsLine(absClick);
|
|
1181
|
+
if (id) {
|
|
1166
1182
|
selection = null;
|
|
1167
|
-
|
|
1183
|
+
m.toggleBatch(id, {
|
|
1184
|
+
contentInsertAfter: (after, lines) => contentInsertAfter(after, lines),
|
|
1185
|
+
contentDeleteFrom: (start, n) => contentDeleteFrom(start, n),
|
|
1186
|
+
});
|
|
1168
1187
|
repaint();
|
|
1169
1188
|
return;
|
|
1170
1189
|
}
|
|
@@ -1274,11 +1293,11 @@ function composeModelLine(status, cols) {
|
|
|
1274
1293
|
const ctxW = ansiDisplayWidth(ctx);
|
|
1275
1294
|
// 左段:模式标识 + 切换提示(灰)+ 本轮 token chip。token chip 仅展示总量,用 mid 灰,不抢主色。
|
|
1276
1295
|
const modeTag = status.modeTag ?? '';
|
|
1277
|
-
const modeColor = modeTag === '
|
|
1296
|
+
const modeColor = modeTag === 'Plan' ? ui.yellow : ui.accent;
|
|
1278
1297
|
const modePart = modeTag ? `${modeColor}${modeTag}${ui.reset}` : '';
|
|
1279
1298
|
const modeW = modeTag ? displayWidth(modeTag) : 0;
|
|
1280
1299
|
// 切换提示:告诉用户怎么切模式。灰(dim)降优先级,不与 modeTag 抢色;只在有 modeTag 时出现。
|
|
1281
|
-
const HINT = 'Shift+Tab
|
|
1300
|
+
const HINT = 'Shift+Tab 切换';
|
|
1282
1301
|
const hintW = modeTag ? displayWidth(HINT) : 0;
|
|
1283
1302
|
const hintPart = modeTag ? `${ui.dim}${HINT}${ui.reset}` : '';
|
|
1284
1303
|
const tokChip = formatTurnTokenChip(status.lastTurnUsage);
|
|
@@ -1872,7 +1891,6 @@ export function enterAltScreen() {
|
|
|
1872
1891
|
scrollLockUntil = 0;
|
|
1873
1892
|
mdActive = false;
|
|
1874
1893
|
mdBuf = '';
|
|
1875
|
-
mdLeadingBlank = false;
|
|
1876
1894
|
selection = null;
|
|
1877
1895
|
selecting = false;
|
|
1878
1896
|
content.reset();
|
package/dist/ui/markdown.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// clipAnsiLine 兜底,绝不溢出。颜色一律嵌入式 ANSI(ui.*)写进字符串。
|
|
14
14
|
import { highlight, supportsLanguage } from 'cli-highlight';
|
|
15
15
|
import { ui, getThemeVersion } from './theme.js';
|
|
16
|
-
import { charWidth, displayWidth, ansiDisplayWidth } from './render.js';
|
|
16
|
+
import { charWidth, displayWidth, ansiDisplayWidth, stripAnsi } from './render.js';
|
|
17
17
|
const RESET = '\x1b[0m';
|
|
18
18
|
// theme 无 italic/strike,自备(TTY 感知:!isTTY 退化为空串,同 ui.* 契约)。
|
|
19
19
|
const ITALIC = ui.isTTY ? '\x1b[3m' : '';
|
|
@@ -574,11 +574,17 @@ function renderMarkdownImpl(text, cols) {
|
|
|
574
574
|
flushPara();
|
|
575
575
|
if (inFence)
|
|
576
576
|
flushCode(); // EOF 仍 inFence:流式中未闭合 fence → 照常 emit 进行中代码块
|
|
577
|
+
// 独立 markdown 段的外部间距由 agent/batch 边界统一管理。流式后端可能把
|
|
578
|
+
// "\n\n" 与首段正文拆成不同 chunk;仅清洗首个正文 chunk 不够,因为 mdBuf
|
|
579
|
+
// 累积重渲染时这些换行仍会变成段首空行。这里最终兜底,正文段永不自带前导空行。
|
|
580
|
+
const isVisualBlank = (line) => stripAnsi(line).trim().length === 0;
|
|
581
|
+
while (out.length > 0 && isVisualBlank(out[0]))
|
|
582
|
+
out.shift();
|
|
577
583
|
// 末尾不留空行:agent onText 后接 onToolCall 的 contentWrite('\n') 会补 1 空行分隔正文与 ● 行;
|
|
578
584
|
// 若 md 末尾自带空行(段落/代码块后)则叠成 2 空行。裁掉末尾连续空行,让 onToolCall / 轮末
|
|
579
585
|
// contentWrite('\n') 恰好补 1 行(与改造前 raw 文本行为一致)。块间空行(flushPara/flushCode 中段
|
|
580
586
|
// push 的)不受影响——只裁末尾。
|
|
581
|
-
while (out.length > 0 && out[out.length - 1]
|
|
587
|
+
while (out.length > 0 && isVisualBlank(out[out.length - 1]))
|
|
582
588
|
out.pop();
|
|
583
589
|
return out;
|
|
584
590
|
}
|