mocode-ai 0.4.3 → 0.4.4
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 +21 -15
- package/dist/repl/index.js +56 -25
- package/dist/session/persist.js +15 -6
- package/dist/ui/layout.js +4 -3
- package/package.json +1 -1
package/dist/config/index.js
CHANGED
|
@@ -99,7 +99,7 @@ function buildPlanModeSuffix() {
|
|
|
99
99
|
## ⛯ PLAN MODE (active now)
|
|
100
100
|
You are in PLAN mode: investigate and design only — do NOT execute or change anything.
|
|
101
101
|
- Your editing / command tools (write_file, edit_file, run_command) have been REMOVED from your tool list. Use only the read-only tools available to you (read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human) to investigate.
|
|
102
|
-
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing.
|
|
102
|
+
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. (Codegraph is the default first action for code exploration — see Workflow in the base prompt.)
|
|
103
103
|
- Then produce a clear, actionable implementation plan: files to change (with paths), what to change in each and why, the ordered steps, edge cases to handle, and how to verify (typecheck / tests / build). Be specific enough to execute against.
|
|
104
104
|
- When the plan is complete and ready for review, you MUST call the \`ask_human\` tool to surface the plan to the user for approval — do NOT just output the plan as plain text and STOP. ask_human renders a real interactive selection panel inside the TUI; plain-text approval questions in your reply are hard to see and easy to miss.
|
|
105
105
|
- Pass the \`ask_human\` tool a concise plan summary (goal + files/areas to change + key risks + verification) and these three options so the user can decide in one click:
|
|
@@ -115,7 +115,7 @@ You are in PLAN mode: investigate and design only — do NOT execute or change a
|
|
|
115
115
|
## ⛯ PLAN MODE (active now)
|
|
116
116
|
You are in PLAN mode: investigate and design only — do NOT execute or change anything.
|
|
117
117
|
- Your editing / command / memory-write tools (write_file, edit_file, run_command, memory_save, memory_update, memory_forget) have been REMOVED from your tool list. Use only the read-only tools available to you (read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human, memory_search, memory_list) to investigate.
|
|
118
|
-
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing.
|
|
118
|
+
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. (Codegraph is the default first action for code exploration — see Workflow in the base prompt.)
|
|
119
119
|
- Then produce a clear, actionable implementation plan: files to change (with paths), what to change in each and why, the ordered steps, edge cases to handle, and how to verify (typecheck / tests / build). Be specific enough to execute against.
|
|
120
120
|
- When the plan is complete and ready for review, you MUST call the \`ask_human\` tool to surface the plan to the user for approval — do NOT just output the plan as plain text and STOP. ask_human renders a real interactive selection panel inside the TUI; plain-text approval questions in your reply are hard to see and easy to miss.
|
|
121
121
|
- Pass the \`ask_human\` tool a concise plan summary (goal + files/areas to change + key risks + verification) and these three options so the user can decide in one click:
|
|
@@ -137,13 +137,23 @@ export function buildBasePrompt() {
|
|
|
137
137
|
: '- For complex or multi-step tasks, the user may switch to PLAN mode (Shift+Tab): your editing/command tools are then removed from your tool list, and you must research with read-only tools only and produce a step-by-step plan (no execution). On approval the session returns to auto mode to execute the plan.';
|
|
138
138
|
return `You are mocode, a terminal coding agent. You complete programming tasks through a "think → call tool → observe result → think again" loop until the problem is solved. Reply to the user in Chinese.
|
|
139
139
|
|
|
140
|
+
## 模式 (Modes)
|
|
141
|
+
${autoAllToolsLine}
|
|
142
|
+
${planLine}
|
|
143
|
+
|
|
140
144
|
${PLATFORM_NOTE}
|
|
141
145
|
|
|
142
146
|
## Step / Turn Economy (read this first — saves LLM calls)
|
|
143
147
|
- **Minimize turns**: each user message costs at least one LLM call, and history grows every step until threshold-triggered compact fires (extra call). If a request contains ≥2 independent sub-goals (e.g. "改 X 然后再优化 Y"), ask the user to split them into separate turns rather than chaining both in one go. State this politely: "这条包含 N 个独立目标,建议拆成 N 次对话,以避免上下文膨胀。"
|
|
144
|
-
- **
|
|
148
|
+
- **Plan the full turn, then emit it as one batch — this is the single biggest step-saver**: before emitting anything, enumerate every read / edit / command you'll need for this sub-goal, then return them together as one set of tool_calls (reads run in parallel, writes/commands run in the order given). Don't emit one call, observe, then emit the next in a follow-up turn when you could have planned both upfront.
|
|
149
|
+
- ✅ one turn: \`[read_file A, read_file B, edit_file A, run_command 'npm test']\`
|
|
150
|
+
- ❌ four turns: \`[read_file A]\` → \`[read_file B]\` → \`[edit_file A]\` → \`[run_command 'npm test']\`
|
|
151
|
+
- **Batch read-only tools in parallel**: consecutive read-only tools (read_file, glob, grep, codegraph, web_search, web_fetch) auto-execute in parallel within one turn — this is the concrete read-side case of the rule above. Do NOT call them serially across turns when you could emit them together.
|
|
145
152
|
- **Decide before reading**: do not read files "just to see"; plan the 2-3 file paths you actually need, then emit them as one batched tool_calls turn.
|
|
153
|
+
- **Chain read→edit→verify in one turn**: when the edit is obvious after a read, call edit_file (and verify with run_command) in the SAME response — don't split into 3 separate turns.
|
|
154
|
+
- **Verify once at the end of an edit chain, not after every edit**: after batching a set of related edits, run a single typecheck / test / build command to verify the whole change together. Running a verify command after each individual edit_file call wastes turns — batch the edits, then verify once.
|
|
146
155
|
- **Don't repeat failed calls**: if the same tool call fails or returns the same content 3 times in this turn, switch strategy (use a different tool, ask the user, or re-read the tool description) — don't keep retrying the same shape.
|
|
156
|
+
- **Don't re-read a file you already have, unless it may have changed**: if you (or an earlier step in this session) already read a file's relevant content and nothing has touched it since, edit directly from that content instead of calling read_file again "to be safe". This does NOT apply when the file was edited (by you or externally) since your last read, when a prior edit may have shifted line numbers you're about to target, or right after a compact where you're unsure the surviving context is accurate — in those cases re-reading is expected and correct, not wasteful.
|
|
147
157
|
|
|
148
158
|
## Workflow
|
|
149
159
|
- Understand before acting: when unsure about requirements or code state, explore first; don't assume.
|
|
@@ -153,7 +163,7 @@ ${PLATFORM_NOTE}
|
|
|
153
163
|
|
|
154
164
|
## Tool Guidelines
|
|
155
165
|
- See each tool's own description for parameters and usage; this section covers selection strategy and pitfalls only.
|
|
156
|
-
- **
|
|
166
|
+
- **If the user gave a precise path or symbol, go directly**: read_file or codegraph node it — don't pre-validate with glob/grep.
|
|
157
167
|
- Before editing code, read_file to confirm actual content (with line numbers); don't guess from memory.
|
|
158
168
|
- For local edits use edit_file: old_string must be unique and match exactly (including indentation/newlines); include surrounding context lines to ensure uniqueness. Use write_file for new files or full rewrites.
|
|
159
169
|
- Use glob to find file paths, grep to search content. **Don't use run_command for file-level checks** (existence / listing / type) — those have no clean cmd.exe equivalent and Windows path escaping fails often. Use \`glob\` to list, and just call \`read_file\` to test existence (returns ENOENT as a clean error string). The earlier rule against \`run_command\` for cat/sed/find/grep still applies.
|
|
@@ -161,9 +171,8 @@ ${PLATFORM_NOTE}
|
|
|
161
171
|
- Use web_search for information beyond training data (new versions, news, real-time data, latest APIs); don't answer potentially outdated info from memory.
|
|
162
172
|
- Use web_fetch to read a specific URL (a link from search results, or a URL given by the user); it only fetches static HTML — if a JS-rendered page yields no body, switch to web_search (its results include cleaned body text).
|
|
163
173
|
- Call ask_human when you hit a decision point requiring user input (multiple implementation approaches, unclear intent, or needing extra info to proceed) — list options for the user to pick (they can also choose "custom input" to answer freely). Don't call it frequently when the task is clear and you can decide yourself; if the user cancels, switch approach or proceed with available info — don't re-ask the same question.
|
|
164
|
-
- **
|
|
165
|
-
- **
|
|
166
|
-
- **Batch independent tool calls in one turn**: the executor runs ALL returned tool calls before the next LLM call, so emitting [read_file, glob, read_file] together is dramatically cheaper than three separate turns. Default to bundling exploration reads and parallel writes.
|
|
174
|
+
- **Trim context when stale**: when an old tool result is dead weight (sub-goal done, no downstream consumer, or superseded by a later read), call drop_context to stub it. Otherwise rely on automatic pruning — don't carry stale reads into new sub-goals.
|
|
175
|
+
- **Batch writes and commands too, not just reads**: the executor runs ALL returned tool_calls (reads, writes, commands) before the next LLM call. Emit independent edit_file / write_file / run_command in one response when the chain is clear — don't serialize them across turns just because they have side effects. (The read-only batching note in Step Economy applies to writes the same way.)
|
|
167
176
|
- **Chain shell workflows in a single \`run_command\`**: use \`&&\`, \`;\`, \`|\`, \`>\`, heredocs to fold multi-step scripts (\`mkdir -p x && cat > x/file.ts <<'EOF' ... EOF && npm test\`) into one call. Only emit a follow-up turn when the result forces a decision (error, ambiguous output, branching logic).
|
|
168
177
|
|
|
169
178
|
## Large file writes (avoid token-cap truncation)
|
|
@@ -182,18 +191,15 @@ ${PLATFORM_NOTE}
|
|
|
182
191
|
- Operate only within authorized scope; when unsure, ask — don't guess.
|
|
183
192
|
|
|
184
193
|
${memorySection}
|
|
185
|
-
${autoAllToolsLine}
|
|
186
|
-
${planLine}
|
|
187
194
|
|
|
188
|
-
## Working notepad (todolist) —
|
|
189
|
-
- For
|
|
190
|
-
- The plan is file-backed (survives context compression
|
|
191
|
-
-
|
|
192
|
-
- **Lifecycle** (5 actions total): \`create\` / \`read\` / \`update\` / \`add_step\` / \`finish\` for normal flow. \`finish plan_status=finished\` AUTO-ARCHIVES the plan to \`.mocode/plans/archive/<id>.md\` (history preserved, active dir stays clean). To revisit old plans: \`list scope=archived\` (or \`all\`) + \`unarchive id=<id>\` to bring back. \`delete id=<id>\` permanently removes (any location); cannot delete the currently active plan.
|
|
193
|
-
- Don't over-use it: for a single edit or a quick lookup, \`todolist\` is overhead. The threshold is "this needs ≥3 steps OR I might forget the plan after context compaction."
|
|
195
|
+
## Working notepad (todolist) — for multi-step tasks
|
|
196
|
+
- For tasks spanning **≥2 independent modules** OR when the user asks for stepwise progress ("先计划再执行" / "plan then do" / "按步骤来"), call \`todolist create\` first to write the plan to \`.mocode/plans/<id>.md\`, then \`todolist update\` to mark progress as you go. For single-file edits or quick lookups, skip it.
|
|
197
|
+
- The plan is file-backed (survives context compression; user can see/edit), and the active plan summary is auto-injected into this system prompt each turn. Re-read via \`todolist read\` when unsure of your place.
|
|
198
|
+
- See the \`todolist\` tool description for the full action set (create / read / update / add_step / finish / list / unarchive / delete) and lifecycle.
|
|
194
199
|
|
|
195
200
|
## Termination & Reporting
|
|
196
201
|
- Stop immediately when no more tools are needed; give conclusions directly.
|
|
202
|
+
- **No flattery / no preamble in conclusions**: skip "Sure", "好的", "我已经完成了" and similar no-information prefixes — jump straight to substance.
|
|
197
203
|
- 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.`;
|
|
198
204
|
}
|
|
199
205
|
/**
|
package/dist/repl/index.js
CHANGED
|
@@ -33,7 +33,8 @@ const SLASH_COMMANDS = [
|
|
|
33
33
|
{ name: '/context', desc: '显示上下文用量条' },
|
|
34
34
|
{ name: '/skills', desc: '列出已发现的 skill' },
|
|
35
35
|
{ name: '/compact', desc: '压缩历史(可带焦点 /compact …)' },
|
|
36
|
-
{ name: '/resume', desc: '
|
|
36
|
+
{ name: '/resume', desc: '续接最近 10 个已保存会话(快速)' },
|
|
37
|
+
{ name: '/sessions', desc: '浏览全部已保存会话(慢,翻历史用)' },
|
|
37
38
|
{ name: '/rollback', desc: '菜单选轮次回滚(↑↓·Enter)' },
|
|
38
39
|
{ name: '/memory', desc: '记忆库:条目计数与近期索引(关闭时提示先开 /memory_switch)' },
|
|
39
40
|
{ name: '/memory_switch', desc: '切换记忆子系统开关(无参=切换;/on 或 /off 显式;持久化 MEMORY_ENABLED)' },
|
|
@@ -738,6 +739,31 @@ export async function startRepl(initialHistory, sessionId, updateNotice = null,
|
|
|
738
739
|
layout.contentWrite('\n'); // 轮次之间空行
|
|
739
740
|
return ok;
|
|
740
741
|
};
|
|
742
|
+
/** 把 picker 选中的会话加载进 REPL(刷 history + 重建 snapshots + 重画)。/resume / /sessions 共用。 */
|
|
743
|
+
async function resumeFromPick(pick) {
|
|
744
|
+
if (!pick)
|
|
745
|
+
return; // Esc / Ctrl+D 取消
|
|
746
|
+
const loaded = loadSession(pick.id);
|
|
747
|
+
if (!loaded || !loaded.history.length) {
|
|
748
|
+
layout.contentWrite(`${ui.yellow}(加载失败)${ui.reset}\n`);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (loaded.history[0]?.role === 'system') {
|
|
752
|
+
loaded.history[0] = { role: 'system', content: buildSystemMessage(false) };
|
|
753
|
+
}
|
|
754
|
+
history.length = 0;
|
|
755
|
+
history.push(...loaded.history);
|
|
756
|
+
setAgentMode('auto'); // 续接重置为 auto(mode 不落盘;listener 重写 history[0] 回 auto,与 loaded 幂等)
|
|
757
|
+
currentSessionId = loaded.id;
|
|
758
|
+
// 读回该会话的轮次/快照;无文件则从 history 重建 turns(无快照→旧轮次文件改动不可撤销)
|
|
759
|
+
if (!loadSnapshots(loaded.id))
|
|
760
|
+
rebuildFromHistory(history);
|
|
761
|
+
contextState.lastUsage = undefined;
|
|
762
|
+
lastTurnUsage = undefined; // 续接:旧会话的 token 累计已无意义,清空等下轮覆写
|
|
763
|
+
layout.clearContent();
|
|
764
|
+
renderHistory(history);
|
|
765
|
+
layout.contentWrite(`${ui.dim}(已续接会话 ${loaded.id})${ui.reset}\n`);
|
|
766
|
+
}
|
|
741
767
|
while (true) {
|
|
742
768
|
// INPUT 态:画底栏输入框 + 状态行,光标入输入框
|
|
743
769
|
refreshStatusBase(history);
|
|
@@ -1029,10 +1055,11 @@ export async function startRepl(initialHistory, sessionId, updateNotice = null,
|
|
|
1029
1055
|
}
|
|
1030
1056
|
continue;
|
|
1031
1057
|
}
|
|
1032
|
-
if (line === '/
|
|
1033
|
-
// /
|
|
1034
|
-
//
|
|
1035
|
-
|
|
1058
|
+
if (line === '/sessions') {
|
|
1059
|
+
// /sessions:浏览全部已保存会话(慢路径,readdir+全量 JSON.parse,目录 N 大时会有可感知卡顿)。
|
|
1060
|
+
// 默认走 /resume(仅最近 10 条,瞬开);要翻历史续接更早的会话才用这条。
|
|
1061
|
+
// picker 走全显(cap=items.length,无 a 展开提示),靠 picker 自身开窗(以选中为中心分屏)。
|
|
1062
|
+
const sessions = listSessions(); // 不传 limit = 全量
|
|
1036
1063
|
if (sessions.length === 0) {
|
|
1037
1064
|
layout.contentWrite(`${ui.dim}(没有已保存的会话)${ui.reset}\n`);
|
|
1038
1065
|
continue;
|
|
@@ -1044,33 +1071,37 @@ export async function startRepl(initialHistory, sessionId, updateNotice = null,
|
|
|
1044
1071
|
}));
|
|
1045
1072
|
let pick;
|
|
1046
1073
|
try {
|
|
1047
|
-
pick = await promptSessionPicker(items);
|
|
1074
|
+
pick = await promptSessionPicker(items, items.length);
|
|
1048
1075
|
}
|
|
1049
1076
|
catch {
|
|
1050
1077
|
continue; // Ctrl+C(SIGINT)→ 取消
|
|
1051
1078
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1079
|
+
await resumeFromPick(pick);
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
if (line === '/resume') {
|
|
1083
|
+
// /resume:打开会话菜单(↑/↓ 选,Enter 续接,Esc 取消)。只加载最近 10 条,
|
|
1084
|
+
// 避免 sessions 目录堆了几百个会话时 readdir+全量 JSON.parse 卡顿。
|
|
1085
|
+
// 仿 /rollback 菜单化(promptSessionPicker);选中项 cyan+bold + ▸ 高亮。
|
|
1086
|
+
// 要续接更早的会话请用 /sessions 翻全表,或 CLI `mocode --resume <id>`。
|
|
1087
|
+
const sessions = listSessions(10);
|
|
1088
|
+
if (sessions.length === 0) {
|
|
1089
|
+
layout.contentWrite(`${ui.dim}(没有已保存的会话)${ui.reset}\n`);
|
|
1057
1090
|
continue;
|
|
1058
1091
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1092
|
+
const items = sessions.map((s) => ({
|
|
1093
|
+
id: s.id,
|
|
1094
|
+
title: s.firstUser || '(无)',
|
|
1095
|
+
subtitle: `${s.id} ${s.model}`,
|
|
1096
|
+
}));
|
|
1097
|
+
let pick;
|
|
1098
|
+
try {
|
|
1099
|
+
pick = await promptSessionPicker(items);
|
|
1061
1100
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
// 读回该会话的轮次/快照;无文件则从 history 重建 turns(无快照→旧轮次文件改动不可撤销)
|
|
1067
|
-
if (!loadSnapshots(loaded.id))
|
|
1068
|
-
rebuildFromHistory(history);
|
|
1069
|
-
contextState.lastUsage = undefined;
|
|
1070
|
-
lastTurnUsage = undefined; // /resume:旧会话的 token 累计已无意义,清空等下轮覆写
|
|
1071
|
-
layout.clearContent();
|
|
1072
|
-
renderHistory(history);
|
|
1073
|
-
layout.contentWrite(`${ui.dim}(已续接会话 ${loaded.id})${ui.reset}\n`);
|
|
1101
|
+
catch {
|
|
1102
|
+
continue; // Ctrl+C(SIGINT)→ 取消
|
|
1103
|
+
}
|
|
1104
|
+
await resumeFromPick(pick);
|
|
1074
1105
|
continue;
|
|
1075
1106
|
}
|
|
1076
1107
|
if (line === '/theme' || line.startsWith('/theme ')) {
|
package/dist/session/persist.js
CHANGED
|
@@ -81,14 +81,24 @@ export function loadSession(id) {
|
|
|
81
81
|
return null;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
84
|
+
/** 列出最近会话,按 createdAt 降序。损坏文件跳过。
|
|
85
|
+
* - limit?: 仅返回前 N 条。会话文件名是 YYYYMMDD-HHmmss.json,字典序=时间序;
|
|
86
|
+
* 先按文件名降序取前 N,再只解析这 N 个文件(history 大字段全部跳过不读),避免
|
|
87
|
+
* /resume 在 sessions 目录堆了几百个文件时 readdirSync + 全量 JSON.parse 慢。
|
|
88
|
+
* - 不传 limit 时读全部(向后兼容,供裸 --resume 列全表用)。
|
|
89
|
+
*/
|
|
90
|
+
export function listSessions(limit) {
|
|
86
91
|
if (!existsSync(config.sessionDir))
|
|
87
92
|
return [];
|
|
93
|
+
// 过滤掉 .snapshots.json:ASCII 排序里 's'(115) > 'j'(106),后者排在前面,会让
|
|
94
|
+
// slice(0, limit) 取到一堆快照文件(JSON.parse 后 rec.id=undefined 被吞),真会话被挤掉。
|
|
95
|
+
const all = readdirSync(config.sessionDir)
|
|
96
|
+
.filter((f) => f.endsWith('.json') && !f.endsWith('.snapshots.json'))
|
|
97
|
+
.sort() // YYYYMMDD-HHmmss.json 字典序 ≡ 时间序(同 createdAt 升序)
|
|
98
|
+
.reverse(); // 降序:最新在前
|
|
99
|
+
const toRead = typeof limit === 'number' ? all.slice(0, Math.max(0, limit)) : all;
|
|
88
100
|
const out = [];
|
|
89
|
-
for (const f of
|
|
90
|
-
if (!f.endsWith('.json'))
|
|
91
|
-
continue;
|
|
101
|
+
for (const f of toRead) {
|
|
92
102
|
try {
|
|
93
103
|
const rec = JSON.parse(readFileSync(path.join(config.sessionDir, f), 'utf8'));
|
|
94
104
|
if (rec && typeof rec.id === 'string') {
|
|
@@ -104,6 +114,5 @@ export function listSessions() {
|
|
|
104
114
|
// 跳过损坏文件
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
|
-
out.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
|
|
108
117
|
return out;
|
|
109
118
|
}
|
package/dist/ui/layout.js
CHANGED
|
@@ -567,7 +567,7 @@ export function clearSelection() {
|
|
|
567
567
|
export function setPasteHandler(fn) {
|
|
568
568
|
pasteHandler = fn;
|
|
569
569
|
}
|
|
570
|
-
/** picker /
|
|
570
|
+
/** picker / 介入面板期间禁用鼠标选区与拖拽(避免 viewport 重画覆盖菜单);滚轮仍可用。面板退出后恢复。 */
|
|
571
571
|
export function setMouseEnabled(v) {
|
|
572
572
|
mouseEnabled = v;
|
|
573
573
|
if (!v) {
|
|
@@ -627,8 +627,9 @@ function handleMouseEvent(e) {
|
|
|
627
627
|
if (!active)
|
|
628
628
|
return;
|
|
629
629
|
if (e.type === 'wheel') {
|
|
630
|
-
|
|
631
|
-
|
|
630
|
+
// 滚轮始终可用:面板/picker 期间也允许上下查看 agent 输出,
|
|
631
|
+
// 与 onRunningKey 的 PgUp/PgDn 行为一致;mouseEnabled 仅管选区/拖拽。
|
|
632
|
+
scrollBy(e.dir * WHEEL_LINES);
|
|
632
633
|
return;
|
|
633
634
|
}
|
|
634
635
|
if (!mouseEnabled)
|