mocode-ai 0.6.8 → 0.6.9

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 CHANGED
@@ -32,6 +32,11 @@ MoCode isn't a chat box with a coat of paint — it's an agent that actually get
32
32
  - **Optional desktop pet** — A small floating window (`/pet`) shows a stateful character that mirrors agent activity (idle / thinking / tool running / waiting for human). Works as a separate process over WebSocket; quit it with `/pet quit`. Sits beside the terminal, never blocks it.
33
33
  - **Slash commands** — `/exit` `/clear` `/context` `/skills` `/compact` `/resume` `/rollback` `/memory` `/reflect` `/init` `/theme` `/model` `/plan` `/auto` `/pet`, with dropdown filtering as you type.
34
34
 
35
+ ## Documentation
36
+
37
+ - [中文使用指南](./docs/usage.md) — 菜单式快速上手、命令速查、模式、会话、项目上下文与排障。
38
+ - [Project context](./docs/USAGE_SNAPSHOT_SKILL.md) — Snapshot and Project Skill reference.
39
+
35
40
  ## Installation
36
41
 
37
42
  Requires Node.js ≥ 18.
@@ -208,14 +213,14 @@ A skill's `description` is injected into the system prompt (progressive disclosu
208
213
 
209
214
  MoCode uses two complementary systems to help the agent understand your project:
210
215
 
211
- - **Project Snapshot** (automatic + LLM-enhanced): Scans your project files and generates a structured summary including project description, tech stack, key commands, module responsibilities, and directory tree. Built automatically on startup (Phase 1: sync scan ~100ms, Phase 2: async LLM summary ~5s). Stored in `.mocode/snapshot.json`, reused across sessions. Refresh manually with `/snapshot_refresh` after major changes.
212
- - **Project Skill** (manual + AI-assisted): A knowledge base you maintain capturing insights the agent can't auto-discover — design decisions, architectural patterns, pitfalls, conventions, and development workflow notes. Initialize with `/init` (AI explores your project and drafts the initial content), then edit `.mocode/project-skill.md` to refine. Update via the `project_skill_update` tool during conversations.
216
+ - **Project Snapshot** (automatic + LLM-enhanced, enabled by default): Scans your project files and generates a structured summary including project description, tech stack, key commands, module responsibilities, and directory tree. Stored in `.mocode/snapshot.json` and reused across sessions. Use `/snapshot` to toggle it and `/snapshot_refresh` after major changes.
217
+ - **Project Skill** (manual + AI-assisted, disabled by default): A knowledge base for design decisions, architectural patterns, pitfalls, conventions, and workflow notes. Enable it with `/project_skill on` (or `MOCODE_PROJECT_SKILL=true`), generate an initial draft with `/project_skill init`, then refine `.mocode/project-skill.md`. The agent can update it through `project_skill_update` during conversations.
213
218
 
214
219
  **Complementary principle**: Snapshot provides *what/where* (facts: files, structure, commands), Skill provides *why/how* (insights: decisions, behaviors, gotchas). No duplication, ~46% token savings compared to the previous approach.
215
220
 
216
- Both are enabled by default. Control via environment variables:
221
+ Control via environment variables:
217
222
  - `MOCODE_PROJECT_SNAPSHOT=false` — disable snapshot
218
- - `MOCODE_PROJECT_SKILL=false` — disable skill
223
+ - `MOCODE_PROJECT_SKILL=true` — enable Project Skill
219
224
 
220
225
  See [docs/USAGE_SNAPSHOT_SKILL.md](./docs/USAGE_SNAPSHOT_SKILL.md) for detailed usage.
221
226
 
package/README.zh-CN.md CHANGED
@@ -31,6 +31,11 @@ mocode 不是一个套壳聊天框,而是一个能真正动手干活的 agent:
31
31
  - **可选桌宠** — 独立悬浮窗(`/pet`)显示一个小角色,镜像 agent 活动(空闲 / 思考 / 跑工具 / 等人工),独立进程走 WebSocket,`/pet quit` 完全关闭。挂在终端外,绝不挡终端。
32
32
  - **斜杠命令** — `/exit` `/clear` `/context` `/skills` `/compact` `/resume` `/rollback` `/memory` `/reflect` `/init` `/theme` `/model` `/plan` `/auto` `/pet`,输入时下拉过滤
33
33
 
34
+ ## 使用文档
35
+
36
+ - [菜单式使用指南](./docs/usage.md) — 快速上手、命令速查、模式、会话、项目上下文与排障。
37
+ - [项目上下文:Snapshot 与 Project Skill](./docs/USAGE_SNAPSHOT_SKILL.md)
38
+
34
39
  ## 安装
35
40
 
36
41
  要求 Node.js ≥ 18。
@@ -150,7 +150,7 @@ function pushToolResult(history, tc, output, pruner, lifecycle, _scheduler, runt
150
150
  * 这些是纯字符串格式化,无副作用,共享安全。
151
151
  */
152
152
  export async function runAgentCore(opts) {
153
- const { history, userInput, signal, onContextUpdate, hooks, skipRollback } = opts;
153
+ const { history, userInput, signal, onContextUpdate, hooks } = opts;
154
154
  const runtimeContextState = opts.contextState ?? contextState;
155
155
  const maxSteps = opts.maxSteps ?? config.maxSteps;
156
156
  // 中断还原:LLM 中途可能调 switch_mode 切了模式,abort 时连同模式一起还原回轮首。
@@ -191,7 +191,7 @@ export async function runAgentCore(opts) {
191
191
  let savedHistory = history.slice();
192
192
  // drop_context 工具的上下文剔除回调:闭包捕获 history,原地剔除无关旧 tool 结果。
193
193
  // 保护由 dropContextFromHistory 内部保证:history[0](system)+ 当前轮(最后 user 及其后)永不剔除。
194
- // 子 agent 也在自己的 history 上操作(子 agent 独立 history);skipRollback 不影响此行为。
194
+ // 子 agent 也在自己的 history 上操作(子 agent 独立 history),文件回滚事务则与主轮共享。
195
195
  const dropContext = (filter) => dropContextFromHistory(history, filter);
196
196
  // 相关性裁剪 pruner:每个 runAgentCore 实例一个,纯静态、不调 LLM、自动判定 read_file 失效。
197
197
  // 开关关闭时为 null,所有 pushToolResult 调用走无 pruner 路径(零行为变化)。
@@ -352,7 +352,7 @@ export async function runAgentCore(opts) {
352
352
  for (const tc of batch)
353
353
  hooks.onToolHeader?.(tc);
354
354
  hooks.onToolStart?.(batch[0].name);
355
- const started = batch.map((tc) => executeTool(tc.name, tc.arguments, signal, { skipRollback, dropContext }));
355
+ const started = batch.map((tc) => executeTool(tc.name, tc.arguments, signal, { dropContext }));
356
356
  for (let k = 0; k < batch.length; k++) {
357
357
  const tc = batch[k];
358
358
  const output = await started[k];
@@ -413,7 +413,7 @@ export async function runAgentCore(opts) {
413
413
  i = j;
414
414
  continue; // 全部被拒绝,跳过执行
415
415
  }
416
- const started = allowedBatch.map((tc) => executeTool(tc.name, tc.arguments, signal, { skipRollback, dropContext }));
416
+ const started = allowedBatch.map((tc) => executeTool(tc.name, tc.arguments, signal, { dropContext }));
417
417
  // 先批量打印所有头 + 启 spinner(多 task 并发,spinner 只显一个,但 ● 头都打出来)
418
418
  for (const tc of allowedBatch) {
419
419
  hooks.onToolHeader?.(tc);
@@ -468,7 +468,7 @@ export async function runAgentCore(opts) {
468
468
  : null;
469
469
  const { preWriteOld, editStartLine } = readDiffContext(tc, mutationParsed);
470
470
  hooks.onToolStart?.(tc.name);
471
- const output = await executeTool(tc.name, tc.arguments, signal, { skipRollback, dropContext });
471
+ const output = await executeTool(tc.name, tc.arguments, signal, { dropContext });
472
472
  hooks.onToolDone?.();
473
473
  hooks.onToolResult?.(tc, output, mutationParsed, preWriteOld, editStartLine);
474
474
  // Thrashing:同上(history 附 hint,UI 干净)
@@ -12,6 +12,8 @@ import { beginTurn } from '../rollback/index.js';
12
12
  import { config } from '../config/index.js';
13
13
  import { runAgentCore, isMutationTool, } from './core.js';
14
14
  import { createPetHooks } from '../pet/state.js';
15
+ import { t } from '../i18n/index.js';
16
+ import { isToolErrorOutput } from '../tools/result.js';
15
17
  /** 当前 turn 的 batch id(runAgent 内闭包变量;一条 turn 一轮 tool batch 结束即清空)。 */
16
18
  let currentBatchId = null;
17
19
  /** 取 userInput 的首行:字符串直接 split;多模态 parts 找首个 text part 再 split。 */
@@ -40,7 +42,7 @@ function writeToolResult(tc, output, parsed, preWriteOld, editStartLine) {
40
42
  if (!currentBatchId)
41
43
  return;
42
44
  let diff = null;
43
- if (isMutationTool(tc.name) && parsed && !output.startsWith('错误')) {
45
+ if (isMutationTool(tc.name) && parsed && !isToolErrorOutput(output)) {
44
46
  diff = renderFileChange({
45
47
  path: String(parsed.path ?? ''),
46
48
  kind: tc.name === 'edit_file' ? 'edit' : 'write',
@@ -143,9 +145,9 @@ onContextUpdate) {
143
145
  hasPendingTextBoundary = false;
144
146
  }
145
147
  if (name)
146
- spinner.start(`生成 ${name}`);
148
+ spinner.start(t('agent.generating', { tool: name }));
147
149
  },
148
- onStepStart: () => spinner.start('思考中'),
150
+ onStepStart: () => spinner.start(t('agent.thinking')),
149
151
  onChatDone: () => spinner.stop(),
150
152
  onTextEnd: () => {
151
153
  if (lastChar && lastChar !== '\n') {
@@ -164,7 +166,7 @@ onContextUpdate) {
164
166
  toolBatchFollowsText = false;
165
167
  writeToolHeader(tc);
166
168
  },
167
- onToolStart: (name) => spinner.start(`执行 ${name}`),
169
+ onToolStart: (name) => spinner.start(t('agent.executing', { tool: name })),
168
170
  onToolDone: () => spinner.stop(),
169
171
  onToolResult: (tc, output, parsed, preWriteOld, editStartLine) => writeToolResult(tc, output, parsed, preWriteOld, editStartLine),
170
172
  onToolBatchEnd: () => {
@@ -172,23 +174,23 @@ onContextUpdate) {
172
174
  },
173
175
  onNoReply: () => {
174
176
  flushToolBatch();
175
- layout.contentWrite(`${ui.dim}(无回复)${ui.reset}\n`);
177
+ layout.contentWrite(`${ui.dim}${t('agent.noReply')}${ui.reset}\n`);
176
178
  },
177
179
  onMaxSteps: () => {
178
180
  flushToolBatch();
179
- layout.contentWrite(` ${ui.yellow}●${ui.reset} ${ui.yellow}达到最大步数(${config.maxSteps}),本轮停止。${ui.reset}\n`);
181
+ layout.contentWrite(` ${ui.yellow}●${ui.reset} ${ui.yellow}${t('agent.maxSteps', { count: config.maxSteps })}${ui.reset}\n`);
180
182
  },
181
183
  onAbort: () => {
182
184
  spinner.stop();
183
185
  if (lastChar && lastChar !== '\n')
184
186
  layout.contentWrite('\n');
185
187
  flushToolBatch();
186
- layout.contentWrite(`${ui.dim}(已中断)${ui.reset}\n`);
188
+ layout.contentWrite(`${ui.dim}${t('agent.aborted')}${ui.reset}\n`);
187
189
  },
188
190
  onDone: (elapsedMs, usage) => {
189
191
  flushToolBatch();
190
192
  const tok = formatTurnTokens(usage);
191
- layout.contentWrite(` ${ui.dim}✻ Worked for ${fmtElapsed(elapsedMs)}${tok}${ui.reset}\n`);
193
+ layout.contentWrite(` ${ui.dim}✻ ${t('agent.workedFor', { elapsed: fmtElapsed(elapsedMs) })}${tok}${ui.reset}\n`);
192
194
  // 内容区触底时,DECSTBM 增量滚屏可能只推进物理终端,未把 Worked 前已在
193
195
  // buffer 中的空行完整画出来;用户滚动/点击触发 repaint 后才“突然”出现。
194
196
  // 轮次收尾立即按 buffer 原子重画,使未满屏与触底滚屏的布局一致。
@@ -7,13 +7,10 @@
7
7
  // - 独立 history:不共享主对话,避免子任务的工具噪声污染主上下文。
8
8
  // - 系统提示复用主 agent 组装链(config.systemPrompt + memory 段 + skills 段)+ 子 agent 角色后缀。
9
9
  // - 工具子集:按白名单从 chatTools 过滤;无白名单 = 全量(但 task 工具调用方通常会限定只读)。
10
- // - 不调 beginTurn(不进主回滚链);skipRollback=true 使文件 mutation 跳过 recordMutation,
11
- // 子 agent 的改动不进主回滚快照链(靠 git 兜底,见下方 skipRollback 注释)。
10
+ // - 不调 beginTurn:子 agent 共享主 agent 当前轮次;其文件修改进入同一回滚事务。
12
11
  // - 步数上限默认更低(config.subAgentMaxSteps ?? 50),防子任务失控耗尽配额。
13
12
  // - 中断透传:opts.signal(主 agent 的 abort signal)透传给 runAgentCore → chat/executeTool,
14
13
  // 主 Ctrl+C 树杀子 agent(chat 流式 abort + run_command/web_fetch 即时取消)。
15
- // - 逻辑隔离(回滚):skipRollback=true,子 agent 的 write_file/edit_file 改动不进主回滚快照链,
16
- // 主 /rollback 不撤销子 agent 改动(靠 git 兜底)。子 agent 与主 agent 共享 cwd(文件改动可见)。
17
14
  import { chatTools } from '../llm/index.js';
18
15
  import { config, isMemoryEnabled } from '../config/index.js';
19
16
  import { effectiveSystemPrompt } from '../skills/index.js';
@@ -126,7 +123,6 @@ export async function spawnAgent(opts) {
126
123
  hooks,
127
124
  maxSteps,
128
125
  toolsOverride,
129
- skipRollback: true, // 逻辑隔离:子 agent 文件改动不进主回滚快照链,主 /rollback 不撤销(靠 git 兜底)
130
126
  contextState: localContextState,
131
127
  });
132
128
  return {
@@ -1,5 +1,6 @@
1
1
  import * as readline from 'node:readline';
2
2
  import { CONFIG_PATH, readConfigFile, writeConfigKeys } from '../config/file.js';
3
+ import { detectLanguage, setLanguage, t } from '../i18n/index.js';
3
4
  function ask(rl, q) {
4
5
  return new Promise((resolve) => {
5
6
  rl.question(q, (ans) => resolve(ans.trim()));
@@ -14,23 +15,24 @@ function ask(rl, q) {
14
15
  */
15
16
  export async function runConfigWizard() {
16
17
  const cur = readConfigFile();
18
+ setLanguage(detectLanguage(process.env.MOCODE_LANGUAGE ?? cur.MOCODE_LANGUAGE));
17
19
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
18
- console.log(`mocode 配置向导 写入 ${CONFIG_PATH}(Ctrl+C 取消)\n`);
19
- const baseURLIn = await ask(rl, `LLM_BASE_URL${cur.LLM_BASE_URL ? ` [${cur.LLM_BASE_URL}]` : ''}(如 https://open.bigmodel.cn/api/v3): `);
20
+ console.log(`${t('config.title', { path: CONFIG_PATH })}\n`);
21
+ const baseURLIn = await ask(rl, `LLM_BASE_URL${cur.LLM_BASE_URL ? ` [${cur.LLM_BASE_URL}]` : ''}${t('config.example')}: `);
20
22
  const baseURL = baseURLIn || cur.LLM_BASE_URL || '';
21
23
  if (!baseURL) {
22
- console.error('\n[config] LLM_BASE_URL 不能为空,已取消。');
24
+ console.error(t('config.required', { key: 'LLM_BASE_URL' }));
23
25
  rl.close();
24
26
  process.exit(1);
25
27
  }
26
- const apiKeyIn = await ask(rl, `LLM_API_KEY${cur.LLM_API_KEY ? ' [已设置,回车保留]' : ''}: `);
28
+ const apiKeyIn = await ask(rl, `LLM_API_KEY${cur.LLM_API_KEY ? t('config.keySet') : ''}: `);
27
29
  const apiKey = apiKeyIn || cur.LLM_API_KEY || '';
28
30
  if (!apiKey) {
29
- console.error('\n[config] LLM_API_KEY 不能为空,已取消。');
31
+ console.error(t('config.required', { key: 'LLM_API_KEY' }));
30
32
  rl.close();
31
33
  process.exit(1);
32
34
  }
33
- const modelIn = await ask(rl, `LLM_MODEL${cur.LLM_MODEL ? ` [${cur.LLM_MODEL}]` : ''}(回车默认 gpt-4o-mini): `);
35
+ const modelIn = await ask(rl, `LLM_MODEL${cur.LLM_MODEL ? ` [${cur.LLM_MODEL}]` : ''}${t('config.modelDefault')}: `);
34
36
  const model = modelIn || cur.LLM_MODEL || 'gpt-4o-mini';
35
37
  rl.close();
36
38
  // 合并:保留其它键,只覆盖三键。
@@ -39,5 +41,5 @@ export async function runConfigWizard() {
39
41
  LLM_API_KEY: apiKey,
40
42
  LLM_MODEL: model,
41
43
  });
42
- console.log(`\n已写入 ${CONFIG_PATH}。现在运行 \`mocode\` 即可启动(任意目录、任意终端)。`);
44
+ console.log(t('config.done', { path: CONFIG_PATH }));
43
45
  }
@@ -6,6 +6,7 @@ import { loadSnapshot } from '../project-snapshot/index.js';
6
6
  import { buildProjectSkillSection } from '../project-skill/index.js';
7
7
  import { getSandboxRoot } from '../sandbox/root.js';
8
8
  import { getCurrentSessionId } from '../session/state.js';
9
+ import { detectLanguage, setLanguage, t, } from '../i18n/index.js';
9
10
  /**
10
11
  * 按优先级加载配置文件并回填 process.env:
11
12
  * 候选(后者覆盖前者,优先级升序):<cwd>/.env(兼容旧用法,最低)→ ~/.mocode/config(全局)→ <cwd>/.mocode/config(项目级覆盖,最高)。
@@ -34,14 +35,16 @@ function loadEnvFiles() {
34
35
  process.env[k] = v;
35
36
  }
36
37
  }
37
- // 在 loadEnvFiles 回填前捕获:MOCODE_THEME 是否由 shell 设置(决定 /theme 写文件是否下次启动生效)。
38
+ // 在 loadEnvFiles 回填前捕获:MOCODE_THEME / MOCODE_LANGUAGE 是否由 shell 设置。
38
39
  const themeFromShell = process.env.MOCODE_THEME !== undefined;
40
+ export const languageFromShell = process.env.MOCODE_LANGUAGE !== undefined;
39
41
  // 在 loadEnvFiles 回填前捕获:哪些 LLM 键由 shell 设置(决定 /model 写文件是否下次启动生效)。
40
42
  // 仿 themeFromShell 模式:shell export 的环境变量在 loadEnvFiles 中不被回填(优先级最高),
41
43
  // 故 /model 写入 ~/.mocode/config 的同名键下次启动会被 shell 值覆盖——据此给 dim 警告。
42
44
  const LLM_ENV_KEYS = ['LLM_BASE_URL', 'LLM_API_KEY', 'LLM_MODEL', 'CONTEXT_WINDOW_TOKENS'];
43
45
  const llmKeysFromShell = LLM_ENV_KEYS.filter((k) => process.env[k] !== undefined);
44
46
  loadEnvFiles();
47
+ setLanguage(detectLanguage(process.env.MOCODE_LANGUAGE));
45
48
  /**
46
49
  * 取环境变量;缺则返回空字符串(不退出)。
47
50
  * 历史上缺 LLM_BASE_URL/LLM_API_KEY 会 process.exit(1),但 /model 命令已能在 REPL 内配置模型,
@@ -163,14 +166,14 @@ const SYSTEM_PROMPT_MEMORY_SECTION = `
163
166
  * 句都不出现,且 read-only 列表里的 memory_search/memory_list 也移除——避免提示词里出现
164
167
  * 根本不存在的工具名引起 LLM 调不到。
165
168
  */
166
- const PLAN_RESEARCH_RULES = `
167
- - Research enough to locate relevant code, trace call paths, and understand existing conventions. Use the codegraph-first Workflow, but do not repeat information already retrieved in this session.
168
- - Produce an actionable plan: files and reasons, ordered steps, edge cases, and verification (typecheck / tests / build).
169
- - When ready, MUST call the \`ask_human\` tool with a concise summary and exactly these options:
170
- 1. "按计划执行 (switch to auto and implement)" — call \`switch_mode("auto")\` in the same turn, then implement.
171
- 2. "继续细化方案 (stay in plan, refine)" — remain in plan and refine.
172
- 3. "取消 / 暂不执行 (abort)" — stop without switching mode.
173
- - Never silently switch or stop. Do not ask approval in plain text; \`ask_human\` is the approval channel.
169
+ const PLAN_RESEARCH_RULES = `
170
+ - Research enough to locate relevant code, trace call paths, and understand existing conventions. Use the codegraph-first Workflow, but do not repeat information already retrieved in this session.
171
+ - Produce an actionable plan: files and reasons, ordered steps, edge cases, and verification (typecheck / tests / build).
172
+ - When ready, MUST call the \`ask_human\` tool with a concise summary and exactly these options:
173
+ 1. "按计划执行 (switch to auto and implement)" — call \`switch_mode("auto")\` in the same turn, then implement.
174
+ 2. "继续细化方案 (stay in plan, refine)" — remain in plan and refine.
175
+ 3. "取消 / 暂不执行 (abort)" — stop without switching mode.
176
+ - Never silently switch or stop. Do not ask approval in plain text; \`ask_human\` is the approval channel.
174
177
  - The REPL approval prompt is only a fallback; do not rely on it.`;
175
178
  function buildPlanModeSuffix() {
176
179
  const memoryTools = isMemoryEnabled()
@@ -182,11 +185,11 @@ function buildPlanModeSuffix() {
182
185
  const removed = ['write_file', 'edit_file', 'run_command', memoryTools]
183
186
  .filter(Boolean)
184
187
  .join(', ');
185
- return `
186
-
187
- ## ⛯ PLAN MODE (active now)
188
- You are in PLAN mode: investigate and design only — do NOT execute or change anything.
189
- - Removed from your tool list: ${removed}. Use only these read-only tools: ${readOnlyTools}.
188
+ return `
189
+
190
+ ## ⛯ PLAN MODE (active now)
191
+ You are in PLAN mode: investigate and design only — do NOT execute or change anything.
192
+ - Removed from your tool list: ${removed}. Use only these read-only tools: ${readOnlyTools}.
190
193
  ${PLAN_RESEARCH_RULES}`;
191
194
  }
192
195
  /** 兼容旧名字:repl 的 buildSystemMessage 仍引 PLAN_MODE_SUFFIX(变量)。运行时按需现拼。 */
@@ -198,8 +201,8 @@ export function buildBasePrompt() {
198
201
  const planLine = isMemoryEnabled()
199
202
  ? '- For complex or multi-step tasks, the user may switch to PLAN mode (Shift+Tab): your editing/command/memory-write 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.'
200
203
  : '- 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.';
201
- return `## Core behavior
202
- You are mocode, a terminal coding agent. Complete programming tasks through a "think → call tool → observe result → think again" loop until solved. Reply to the user in Chinese.
204
+ return `## Core behavior
205
+ You are mocode, a terminal coding agent. Complete programming tasks through a "think → call tool → observe result → think again" loop until solved. ${t('assistant.languageInstruction')}
203
206
 
204
207
  ## 模式 (Modes)
205
208
  ${autoAllToolsLine}
@@ -207,31 +210,31 @@ ${planLine}
207
210
 
208
211
  ${PLATFORM_NOTE}
209
212
 
210
- ## Tool details
211
- ### Token-efficient execution
212
- - First check whether the answer is already in this conversation or a previous tool result. If yes, answer directly; do not re-run tools "to be safe".
213
- - Plan the complete sub-task before calling tools. Batch independent reads in one turn. Because tool calls in one response execute without intermediate model reasoning, never batch a read with an edit that depends on its result.
214
- - Do not read "just to see". Read only what supports the next decision. Re-read after a change, compaction, stale state, or uncertain line context.
215
- - Prefer one precise call over overlapping searches. If a call fails, inspect the error and change the approach instead of repeating it unchanged.
216
- - Batch only independent read-only calls. After their results arrive, make the dependent edit in the next turn; then batch independent edits and one final verification when their exact inputs are already known.
217
- - Read only what supports the next decision; verify once after a related edit set, not after every edit.
218
- - Do not repeat an unchanged failing call; after three unproductive attempts, change tools or ask for the missing decision.
219
- - For \`edit_file\`, derive \`old_string\` by copying the exact relevant lines from the latest successful \`read_file\` of that same path; never reconstruct it from memory, a summary, grep output, or a previous diff. That read becomes stale after any edit/write to the path, compaction/resume, or a possible external change. On an \`old_string\` mismatch, re-read the exact region and retry once with the newly returned text; never retry identical arguments.
213
+ ## Tool details
214
+ ### Token-efficient execution
215
+ - First check whether the answer is already in this conversation or a previous tool result. If yes, answer directly; do not re-run tools "to be safe".
216
+ - Plan the complete sub-task before calling tools. Batch independent reads in one turn. Because tool calls in one response execute without intermediate model reasoning, never batch a read with an edit that depends on its result.
217
+ - Do not read "just to see". Read only what supports the next decision. Re-read after a change, compaction, stale state, or uncertain line context.
218
+ - Prefer one precise call over overlapping searches. If a call fails, inspect the error and change the approach instead of repeating it unchanged.
219
+ - Batch only independent read-only calls. After their results arrive, make the dependent edit in the next turn; then batch independent edits and one final verification when their exact inputs are already known.
220
+ - Read only what supports the next decision; verify once after a related edit set, not after every edit.
221
+ - Do not repeat an unchanged failing call; after three unproductive attempts, change tools or ask for the missing decision.
222
+ - For \`edit_file\`, derive \`old_string\` by copying the exact relevant lines from the latest successful \`read_file\` of that same path; never reconstruct it from memory, a summary, grep output, or a previous diff. That read becomes stale after any edit/write to the path, compaction/resume, or a possible external change. On an \`old_string\` mismatch, re-read the exact region and retry once with the newly returned text; never retry identical arguments.
220
223
 
221
- ## Workflow
222
- - Understand requirements and current code before acting; do not guess.
223
- - If \`.codegraph/\` exists, use \`codegraph\` first for unfamiliar code questions. Use direct reads for known or recently changed files.
224
- - After modifications, run the smallest relevant verification, then typecheck/build when appropriate. Never claim success without evidence.
225
- - Use web search only when freshness materially affects the answer (new APIs, versions, security, current UI conventions).
224
+ ## Workflow
225
+ - Understand requirements and current code before acting; do not guess.
226
+ - If \`.codegraph/\` exists, use \`codegraph\` first for unfamiliar code questions. Use direct reads for known or recently changed files.
227
+ - After modifications, run the smallest relevant verification, then typecheck/build when appropriate. Never claim success without evidence.
228
+ - Use web search only when freshness materially affects the answer (new APIs, versions, security, current UI conventions).
226
229
 
227
- ## Tool rules
228
- - Precise path/symbol → go directly to \`read_file\` or \`codegraph node\`; use \`glob\`/\`grep\` only for discovery.
229
- - Before editing, read the exact target region and use its verbatim text as \`old_string\`. Use \`edit_file\` for unique local replacements and \`write_file\` for new/full files.
230
- - Local edits require an exact unique match; use \`write_file\` for new/full files.
231
- - Use \`glob\`/\`grep\` for discovery and \`run_command\` for execution or verification, not file existence checks. State intent before side effects.
232
- - Call \`ask_human\` only when a real user decision is required; otherwise decide and proceed.
233
- - Drop stale tool output when it no longer supports the current sub-task. Keep only evidence needed for the next decision.
234
- - Batch independent writes only when each input is already known and their order does not matter. Keep dependent mutations sequential. Combine a clear shell workflow in one command; follow up when its result creates a decision.
230
+ ## Tool rules
231
+ - Precise path/symbol → go directly to \`read_file\` or \`codegraph node\`; use \`glob\`/\`grep\` only for discovery.
232
+ - Before editing, read the exact target region and use its verbatim text as \`old_string\`. Use \`edit_file\` for unique local replacements and \`write_file\` for new/full files.
233
+ - Local edits require an exact unique match; use \`write_file\` for new/full files.
234
+ - Use \`glob\`/\`grep\` for discovery and \`run_command\` for execution or verification, not file existence checks. State intent before side effects.
235
+ - Call \`ask_human\` only when a real user decision is required; otherwise decide and proceed.
236
+ - Drop stale tool output when it no longer supports the current sub-task. Keep only evidence needed for the next decision.
237
+ - Batch independent writes only when each input is already known and their order does not matter. Keep dependent mutations sequential. Combine a clear shell workflow in one command; follow up when its result creates a decision.
235
238
 
236
239
  ## Large file writes (avoid token-cap truncation)
237
240
  - \`write_file\` / \`edit_file\` arguments are part of the model's JSON output — a single tool call's content > ~5K tokens risks mid-stream truncation when the model's max output (default 8K–16K tokens) is exceeded, producing a "arguments 不是合法 JSON" error. Even with \`MAX_TOKENS=32000\` set, huge files still risk truncation.
@@ -248,8 +251,8 @@ ${PLATFORM_NOTE}
248
251
  - Confirm with the user before irreversible or outward-facing operations (delete, overwrite existing files, push, request external services), unless explicitly authorized.
249
252
  - Operate only within authorized scope; when unsure, ask — don't guess.
250
253
 
251
- ## Project context (dynamic reference)
252
- ${buildSnapshotSection()}${config.projectSkillEnabled ? buildProjectSkillSection() : ''}${memorySection}${buildNotepadSection()}
254
+ ## Project context (dynamic reference)
255
+ ${buildSnapshotSection()}${config.projectSkillEnabled ? buildProjectSkillSection() : ''}${memorySection}${buildNotepadSection()}
253
256
 
254
257
  ## Session Notepad — working notes file
255
258
  ${getCurrentSessionId()
@@ -445,3 +448,8 @@ export function updateSnapshotConfig(enabled) {
445
448
  config.projectSnapshotEnabled = enabled;
446
449
  process.env.MOCODE_PROJECT_SNAPSHOT = enabled ? 'true' : 'false';
447
450
  }
451
+ /** 切换界面与模型回复语言;持久化由 REPL 调用 config/file.ts 完成。 */
452
+ export function updateLanguageConfig(language) {
453
+ setLanguage(language);
454
+ process.env.MOCODE_LANGUAGE = language;
455
+ }