mocode-ai 0.1.4 → 0.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/README.md +196 -188
- package/dist/agent/core.js +302 -0
- package/dist/agent/index.js +62 -249
- package/dist/agent/mode.js +46 -0
- package/dist/agent/spawn.js +123 -0
- package/dist/commands/config.js +8 -26
- package/dist/config/file.js +48 -0
- package/dist/config/index.js +73 -30
- package/dist/llm/index.js +11 -9
- package/dist/memory/index.js +2 -2
- package/dist/memory/reflect.js +11 -11
- package/dist/memory/store.js +4 -4
- package/dist/repl/index.js +264 -108
- package/dist/session/compact.js +1 -1
- package/dist/skills/index.js +2 -2
- package/dist/tools/builtins/ask-human.js +7 -7
- package/dist/tools/builtins/codegraph.js +113 -0
- package/dist/tools/builtins/edit-file.js +3 -3
- package/dist/tools/builtins/glob.js +3 -2
- package/dist/tools/builtins/grep.js +4 -3
- package/dist/tools/builtins/index.js +7 -1
- package/dist/tools/builtins/memory-forget.js +2 -2
- package/dist/tools/builtins/memory-list.js +2 -2
- package/dist/tools/builtins/memory-save.js +7 -7
- package/dist/tools/builtins/memory-search.js +4 -4
- package/dist/tools/builtins/memory-update.js +6 -6
- package/dist/tools/builtins/read-file.js +5 -4
- package/dist/tools/builtins/run-command.js +40 -7
- package/dist/tools/builtins/switch-mode.js +46 -0
- package/dist/tools/builtins/task.js +62 -0
- package/dist/tools/builtins/use-skill.js +2 -2
- package/dist/tools/builtins/web-fetch.js +16 -3
- package/dist/tools/builtins/web-search.js +6 -6
- package/dist/tools/builtins/write-file.js +3 -3
- package/dist/tools/constants.js +16 -0
- package/dist/tools/registry.js +11 -4
- package/dist/ui/content.js +25 -17
- package/dist/ui/diff.js +28 -26
- package/dist/ui/intervention.js +24 -3
- package/dist/ui/layout.js +295 -81
- package/dist/ui/markdown.js +607 -0
- package/dist/ui/mouse.js +93 -0
- package/dist/ui/prompt.js +259 -5
- package/dist/ui/render.js +1 -1
- package/dist/ui/theme.js +146 -13
- package/package.json +2 -2
|
@@ -3,12 +3,12 @@ import { resolve, dirname } from 'node:path';
|
|
|
3
3
|
// ---------- write_file ----------
|
|
4
4
|
export const writeFileTool = {
|
|
5
5
|
name: 'write_file',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'Create or overwrite a file, creating parent directories as needed.',
|
|
7
7
|
parameters: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
|
-
path: { type: 'string', description: '
|
|
11
|
-
content: { type: 'string', description: '
|
|
10
|
+
path: { type: 'string', description: 'File path' },
|
|
11
|
+
content: { type: 'string', description: 'Full file content' },
|
|
12
12
|
},
|
|
13
13
|
required: ['path', 'content'],
|
|
14
14
|
},
|
package/dist/tools/constants.js
CHANGED
|
@@ -22,3 +22,19 @@ export const GC_DAYS = 90;
|
|
|
22
22
|
/** memory_search 结果(召回的记忆正文)的放宽上限:指令性内容,中截破坏语义,对齐 use_skill。 */
|
|
23
23
|
export const MAX_MEMORY_RESULT = 64000;
|
|
24
24
|
export const IGNORE = ['**/node_modules/**', '**/.git/**'];
|
|
25
|
+
// ── plan 模式(只读规划,不执行)──────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* plan 模式下从工具 schema 里剔除的工具(模型根本看不到 → 调不到):
|
|
28
|
+
* 写盘 / 命令 / 记忆写入类 + task(派生子 agent,plan 模式只读不可有副作用)。单一事实源,
|
|
29
|
+
* 被 llm(planChatTools)与 agent(防御 backstop)共用。
|
|
30
|
+
* 只读工具(read_file/glob/grep/codegraph/web_search/web_fetch/use_skill/ask_human/memory_search/memory_list)保留。
|
|
31
|
+
*/
|
|
32
|
+
export const PLAN_DISABLED_TOOLS = new Set([
|
|
33
|
+
'write_file',
|
|
34
|
+
'edit_file',
|
|
35
|
+
'run_command',
|
|
36
|
+
'memory_save',
|
|
37
|
+
'memory_update',
|
|
38
|
+
'memory_forget',
|
|
39
|
+
'task',
|
|
40
|
+
]);
|
package/dist/tools/registry.js
CHANGED
|
@@ -5,8 +5,13 @@ import { recordMutation } from '../rollback/index.js';
|
|
|
5
5
|
* 未来可在此合并 MCP 工具、用户自定义工具等(见 src/mcp/)。
|
|
6
6
|
*/
|
|
7
7
|
export const tools = builtinTools;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 按名调度工具,统一 try/catch + JSON 解析,返回字符串而非抛错。
|
|
10
|
+
* signal 透传给 tool.execute(经 ctx):长任务工具(run_command/web_fetch)abort 即时取消,
|
|
11
|
+
* 让用户 Ctrl+C 能跟手中断工具执行(而非等命令跑完 / 超时)。
|
|
12
|
+
* opts.skipRollback:子 agent 逻辑隔离用——跳过 recordMutation,子 agent 改动不进主回滚快照链。
|
|
13
|
+
*/
|
|
14
|
+
export async function executeTool(name, argsRaw, signal, opts) {
|
|
10
15
|
const tool = tools.find((t) => t.name === name);
|
|
11
16
|
if (!tool)
|
|
12
17
|
return `错误:未知工具 "${name}"`;
|
|
@@ -19,12 +24,14 @@ export async function executeTool(name, argsRaw) {
|
|
|
19
24
|
}
|
|
20
25
|
try {
|
|
21
26
|
// 撤销回滚用:write_file/edit_file 改动前记 before 快照(回滚时恢复到轮末状态)。
|
|
22
|
-
|
|
27
|
+
// 子 agent(skipRollback)跳过:其改动不进主回滚链,主 /rollback 不撤销(靠 git 兜底)。
|
|
28
|
+
if (!opts?.skipRollback &&
|
|
29
|
+
(name === 'write_file' || name === 'edit_file') &&
|
|
23
30
|
typeof args.path === 'string' &&
|
|
24
31
|
args.path) {
|
|
25
32
|
recordMutation(args.path);
|
|
26
33
|
}
|
|
27
|
-
return await tool.execute(args);
|
|
34
|
+
return await tool.execute(args, { signal, skipRollback: opts?.skipRollback });
|
|
28
35
|
}
|
|
29
36
|
catch (e) {
|
|
30
37
|
return `错误:工具 ${name} 执行失败: ${e instanceof Error ? e.message : String(e)}`;
|
package/dist/ui/content.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 内容物理行缓冲(Phase 2 滚动回滚用)。
|
|
3
3
|
*
|
|
4
4
|
* 存折行后的物理行,每行自洽带色,供 layout.repaintViewport 单行直出、无需继承 SGR 状态。
|
|
5
|
-
* contentWrite 逐字符喂入(feedChar / feedSgr / breakRow);
|
|
5
|
+
* contentWrite 逐字符喂入(feedChar / feedSgr / breakRow);setLines 替换当前段(md 流式渲染用)。
|
|
6
6
|
*
|
|
7
7
|
* **SGR 自洽模型**:每行存 `rowStartSgr + curRaw + \x1B[0m`:
|
|
8
8
|
* - rowStartSgr:本行起始时继承的活跃 SGR 前缀(来自上一行末状态);
|
|
@@ -23,7 +23,7 @@ let curSgr = ''; // 当前活跃 SGR 状态(行内演进;行末值 = 下行继
|
|
|
23
23
|
let rowStartSgr = ''; // 当前行起始继承的 SGR 前缀
|
|
24
24
|
let curRaw = ''; // 当前行原始字节(可见 + 行内 SGR 码)
|
|
25
25
|
let hasCurrent = false; // 是否有当前行(feed/breakRow 后;新空行也算)
|
|
26
|
-
let segMark = null; // 段起点快照(
|
|
26
|
+
let segMark = null; // 段起点快照(md 段:setLines 截断用,commitSegment 清)
|
|
27
27
|
export function reset() {
|
|
28
28
|
rows = [];
|
|
29
29
|
curSgr = '';
|
|
@@ -55,27 +55,35 @@ export function breakRow() {
|
|
|
55
55
|
rowStartSgr = curSgr; // 下行继承本行末状态
|
|
56
56
|
hasCurrent = true; // 新空行即当前行
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
58
|
+
/** 标记段起点:快照当前缓冲状态,供 setLines 截断定位段头(md 流式渲染每 chunk 截断重渲)。 */
|
|
59
59
|
export function beginSegment() {
|
|
60
60
|
segMark = { rowIdx: rows.length, rowStartSgr, curSgr, curRaw, hasCurrent };
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* 用预渲染的自洽 ANSI 行替换当前段(从 segMark 起),供 markdown 流式渲染:
|
|
64
|
+
* layout.contentWriteMd 每 chunk 把累积 text 经 renderMarkdown 渲成自洽行,调此替换缓冲段。
|
|
65
|
+
* 截断 rows 到 segMark.rowIdx(擦上次渲染)+ push 新行;curSgr 复位为默认(行末 reset);
|
|
66
|
+
* **保留 segMark**(下次 setLines 还要截断),由 commitSegment 清除。
|
|
67
|
+
* 行须各自自洽(行内 SGR 自带、行末 reset)——不经 feedChar/feedSgr,直接入 rows,
|
|
68
|
+
* 故 setLines 后 hasCurrent=false(snapshot 只返 rows;续写位由 layout 跟踪)。
|
|
66
69
|
*/
|
|
67
|
-
export function
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
export function setLines(lines) {
|
|
71
|
+
if (segMark) {
|
|
72
|
+
rows.length = segMark.rowIdx;
|
|
73
|
+
}
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
rows.push(line);
|
|
76
|
+
}
|
|
77
|
+
if (rows.length > MAX_ROWS + 512)
|
|
78
|
+
rows.splice(0, rows.length - MAX_ROWS);
|
|
79
|
+
curSgr = '';
|
|
80
|
+
rowStartSgr = '';
|
|
81
|
+
curRaw = '';
|
|
82
|
+
hasCurrent = false;
|
|
83
|
+
}
|
|
84
|
+
/** 提交段:清 segMark,后续写入不再被 setLines 截断(md 段结束、非 md 内容接续时调)。 */
|
|
85
|
+
export function commitSegment() {
|
|
77
86
|
segMark = null;
|
|
78
|
-
return committedErased + currentErased;
|
|
79
87
|
}
|
|
80
88
|
/** 快照(已提交行 + 当前行若有)。viewport 取行窗用。 */
|
|
81
89
|
function snapshot() {
|
package/dist/ui/diff.js
CHANGED
|
@@ -36,35 +36,37 @@ function langForPath(p) {
|
|
|
36
36
|
}
|
|
37
37
|
return LANG_BY_EXT[base];
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* 包一层:token 文本 + 颜色 + reset,自成闭区间(防 SGR 跨行泄漏)。
|
|
41
|
+
* 取 ColorKey 而非 ANSI 串——闭包调用时才读 `ui[key]`,故 setTheme 后 diff 高亮即跟随。 */
|
|
42
|
+
const paint = (key) => (s) => `${ui[key]}${s}${ui.reset}`;
|
|
41
43
|
/** 自定义主题:default=plain(未匹配文本不着色,避免默认主题把普通代码染黄),常见 token 用 mocode ui 色板。 */
|
|
42
44
|
const THEME = {
|
|
43
45
|
default: plain,
|
|
44
|
-
keyword: paint(
|
|
45
|
-
'built_in': paint(
|
|
46
|
-
type: paint(
|
|
47
|
-
literal: paint(
|
|
48
|
-
number: paint(
|
|
49
|
-
string: paint(
|
|
50
|
-
subst: paint(
|
|
51
|
-
comment: paint(
|
|
52
|
-
doctag: paint(
|
|
53
|
-
function: paint(
|
|
54
|
-
title: paint(
|
|
55
|
-
class: paint(
|
|
56
|
-
meta: paint(
|
|
57
|
-
'meta-keyword': paint(
|
|
58
|
-
regexp: paint(
|
|
59
|
-
attr: paint(
|
|
60
|
-
attribute: paint(
|
|
61
|
-
variable: paint(
|
|
62
|
-
tag: paint(
|
|
63
|
-
name: paint(
|
|
64
|
-
symbol: paint(
|
|
65
|
-
section: paint(
|
|
66
|
-
addition: paint(
|
|
67
|
-
deletion: paint(
|
|
46
|
+
keyword: paint('magenta'),
|
|
47
|
+
'built_in': paint('cyan'),
|
|
48
|
+
type: paint('cyan'),
|
|
49
|
+
literal: paint('cyan'),
|
|
50
|
+
number: paint('green'),
|
|
51
|
+
string: paint('green'),
|
|
52
|
+
subst: paint('green'),
|
|
53
|
+
comment: paint('gray'),
|
|
54
|
+
doctag: paint('gray'),
|
|
55
|
+
function: paint('blue'),
|
|
56
|
+
title: paint('blue'),
|
|
57
|
+
class: paint('brightCyan'),
|
|
58
|
+
meta: paint('gray'),
|
|
59
|
+
'meta-keyword': paint('magenta'),
|
|
60
|
+
regexp: paint('red'),
|
|
61
|
+
attr: paint('cyan'),
|
|
62
|
+
attribute: paint('cyan'),
|
|
63
|
+
variable: paint('red'),
|
|
64
|
+
tag: paint('red'),
|
|
65
|
+
name: paint('cyan'),
|
|
66
|
+
symbol: paint('cyan'),
|
|
67
|
+
section: paint('brightMagenta'),
|
|
68
|
+
addition: paint('green'),
|
|
69
|
+
deletion: paint('red'),
|
|
68
70
|
};
|
|
69
71
|
function highlightLine(text, lang) {
|
|
70
72
|
if (!lang || text === '')
|
package/dist/ui/intervention.js
CHANGED
|
@@ -3,6 +3,7 @@ import { stdin, stderr } from 'node:process';
|
|
|
3
3
|
import { ui } from './theme.js';
|
|
4
4
|
import { displayWidth, truncateDisplay } from './render.js';
|
|
5
5
|
import * as layout from './layout.js';
|
|
6
|
+
import * as mouse from './mouse.js';
|
|
6
7
|
import { Spinner } from './spinner.js';
|
|
7
8
|
const emitter = stdin;
|
|
8
9
|
/** choice 末尾的"自定义输入"项标签(纯 ASCII,宽度安全)。选中后切到 input 子态。 */
|
|
@@ -128,8 +129,9 @@ export async function promptIntervention(req) {
|
|
|
128
129
|
emitter.on('keypress', l);
|
|
129
130
|
savedListeners = [];
|
|
130
131
|
// 先 paintInput(dim,menu:null):擦菜单(用 lastMenuRows)+ 画 dim 占位底栏 + 复位 lastMenuRows=0;
|
|
131
|
-
//
|
|
132
|
-
//
|
|
132
|
+
// 再处理滚动:面板内可能滚过滚轮(scrollOffset>0)→ resetScroll 回尾让 agent 后续输出可见;
|
|
133
|
+
// 未滚则 repaintViewport 从 content 缓冲重画内容区(恢复被菜单覆盖的 ● ask_human 行)。
|
|
134
|
+
// 顺序不可反——resetScroll/repaintViewport 在菜单还在(lastMenuRows>0)时会用内容覆盖菜单区,擦不掉菜单。
|
|
133
135
|
layout.paintInput({
|
|
134
136
|
prompt: '❯ ',
|
|
135
137
|
lines: [''],
|
|
@@ -138,11 +140,24 @@ export async function promptIntervention(req) {
|
|
|
138
140
|
menu: null,
|
|
139
141
|
dim: true,
|
|
140
142
|
});
|
|
141
|
-
layout.
|
|
143
|
+
if (layout.isScrolled())
|
|
144
|
+
layout.resetScroll();
|
|
145
|
+
else
|
|
146
|
+
layout.repaintViewport();
|
|
142
147
|
}
|
|
143
148
|
function onKey(_str, key) {
|
|
144
149
|
if (resolved || !key)
|
|
145
150
|
return;
|
|
151
|
+
// SGR 鼠标 fragment:readline 拆碎 \x1B[<…M,经 mouse.consumeMouse 重组;
|
|
152
|
+
// 滚轮 → scrollBy(±5) 滚动查看 agent 输出内容(菜单只靠 ↑↓ 选,滚轮不选菜单);
|
|
153
|
+
// fragment 期间 suppress 吞掉防砸进 onKeyInput 的可打印插入。菜单经 scrollBy 末尾
|
|
154
|
+
// repaint()→paintInput(lastView) 重画,不被 viewport 滚动覆盖。
|
|
155
|
+
const _m = mouse.consumeMouse(key.sequence ?? '');
|
|
156
|
+
if (_m.suppress) {
|
|
157
|
+
if (_m.wheel)
|
|
158
|
+
layout.scrollBy(_m.wheel * 5);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
146
161
|
// Ctrl+C → 取消(不 reject SIGINT——否则经 executeTool 的 try/catch 变成 tool 错误串)
|
|
147
162
|
if (key.ctrl && key.name === 'c') {
|
|
148
163
|
finish({ action: 'cancelled' });
|
|
@@ -158,6 +173,12 @@ export async function promptIntervention(req) {
|
|
|
158
173
|
finish({ action: 'cancelled' });
|
|
159
174
|
return;
|
|
160
175
|
}
|
|
176
|
+
// PgUp/PgDn:翻页滚动查看 agent 输出内容(与 onRunningKey/prompt.ts 一致;↑↓ 仍归菜单导航,不抢)。
|
|
177
|
+
if (key.name === 'pageup' || key.name === 'pagedown') {
|
|
178
|
+
const pageH = layout.getGeo().contentBottom;
|
|
179
|
+
layout.scrollBy(key.name === 'pageup' ? pageH : -pageH);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
161
182
|
if (mode === 'choice') {
|
|
162
183
|
onKeyChoice(key);
|
|
163
184
|
}
|