mocode-ai 1.6.1 → 1.6.2

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.
@@ -101,18 +101,17 @@ export async function runModelTurn(input) {
101
101
  const ephemeralReminder = [
102
102
  runPolicy.reminder,
103
103
  !opts.suppressOpeningAnalysis && step === 0
104
- ? '## Opening analysis\nBegin your FIRST response of this turn with a brief analysis of the request and your planned approach (1-3 sentences, no filler), THEN start tool calls. This opening is the only place where pre-tool prose is expected; after it, work quietly with no narration between tool calls.'
104
+ ? '## Opening analysis\nStart your first response of this turn with a brief analysis of the request and approach (1-3 sentences, no filler), then start tool calls. This is the only expected pre-tool prose; afterwards work quietly, no narration between calls.'
105
105
  : '',
106
106
  historyRebuilt
107
- ? '## Post-compaction recovery\n' +
108
- 'Context was compacted before this request. Recover before doing anything else, in this order:\n' +
109
- '1. Read the session summary at the top of the history: `## Completed` is already done — do not redo or re-verify it. `## In Progress` / `## Next Steps` tell you exactly where work stopped and what is next.\n' +
110
- '2. Read `## Session state` below (refreshed every step from notes.md / gui-actions.log): the active plan is authoritative — `[x]` steps are finished, resume from the first `[ ]`. A `## Compaction Snapshot` section there is the progress checkpoint written at this compaction. A `## GUI actions` section lists every GUI action already performed with its observed result: do not repeat an action that appears there, unless the latest screenshot contradicts it (then the screenshot wins — treat that line as attempted but unverified).\n' +
107
+ ? '## Post-compaction recovery\nContext was compacted before this request. Recover first, in this order:\n' +
108
+ '1. Read the session summary at the top of the history: `## Completed` is done — do not redo or re-verify it; `## In Progress` / `## Next Steps` say where work stopped and what is next.\n' +
109
+ '2. Read `## Session state` below (refreshed from notes.md / gui-actions.log): the active plan is authoritative — `[x]` steps are done, resume from the first `[ ]`. `## Compaction Snapshot` is the checkpoint written at this compaction. `## GUI actions` lists every GUI action already performed with its result: do not repeat one that appears there, unless the latest screenshot contradicts it (the screenshot wins — treat that line as attempted but unverified).\n' +
111
110
  (sessionStateText
112
111
  ? ''
113
- : '(No active plan or snapshot was found in notes.md — reconstruct what is done purely from the summary and treat its `## Completed` as ground truth.)\n') +
114
- '3. Before any file edit, read_file the target fresh to get the current content hash — never edit from memory of pre-compaction content.\n' +
115
- '4. Before re-running a search/read you think you already did, check the summary and notes first: only repeat it if the result is genuinely missing or the target has changed.'
112
+ : '(No active plan or snapshot in notes.md — reconstruct progress from the summary; treat its `## Completed` as ground truth.)\n') +
113
+ '3. Before any file edit, read_file the target fresh for its current hash — never edit from pre-compaction memory.\n' +
114
+ '4. Before re-running a search/read you think you already did, check the summary and notes first; repeat only if the result is genuinely missing or the target changed.'
116
115
  : '',
117
116
  sessionStateText,
118
117
  ]
@@ -322,7 +322,7 @@ export function buildSessionStateReminder(sessionId = getCurrentSessionId()) {
322
322
  const sources = [notes || plan ? 'notes.md' : '', guiActions ? 'gui-actions.log' : ''].filter(Boolean).join(' + ');
323
323
  const parts = [
324
324
  `## Session state (current, from ${sources})`,
325
- 'This block mirrors the live session state and is refreshed every step; treat it as authoritative, and ignore any older copy earlier in this conversation.',
325
+ 'Mirrors live session state, refreshed every step; authoritative — ignore older copies earlier in this conversation.',
326
326
  ...(plan ? [plan] : []),
327
327
  ...(notes ? [notes] : []),
328
328
  ...(guiActions ? [guiActions] : []),
@@ -331,23 +331,69 @@ export function buildSessionStateReminder(sessionId = getCurrentSessionId()) {
331
331
  }
332
332
  /** AGENTS.md 自动导入正文上限:system 位于 history[0] 且 compactHistory 不压缩 system,超长需截断防占窗口(见 memory/README.md)。 */
333
333
  const MAX_AGENTS_IMPORT_CHARS = 20000;
334
+ /**
335
+ * 不常驻注入的章节(压成指针行,read_file 按需取全文):
336
+ * - 目录结构 / Directory structure / Project layout —— 架构探索任务用 codegraph/探查工具现查更准;
337
+ * - 扩展点 / Extension points —— 只在「加新工具/命令/模块」类任务才需要,恰好是 skill 的定义。
338
+ * 常驻价值密度最高的「项目/命令/约定」(市场实证 arXiv 2511.12884:build/run 62.3%、conventions 主流)全文保留。
339
+ * 章节标题大小写不敏感,兼容英文写法的 AGENTS.md。
340
+ */
341
+ const AGENTS_INJECTION_INDEX_SECTIONS = [
342
+ '目录结构',
343
+ '扩展点',
344
+ 'directory structure',
345
+ 'project layout',
346
+ 'extension points',
347
+ ];
348
+ /**
349
+ * AGENTS.md 按章节过滤注入:命中 {@link AGENTS_INJECTION_INDEX_SECTIONS} 的 H2 章节整段压缩成一行指针,
350
+ * 其余章节(preamble、## 项目、## 命令、## 约定及未知章节)逐字保留。
351
+ * H1 及更深层级不动;空文件/无章节文件原样返回。导出供单测直接断言。
352
+ */
353
+ export function filterAgentsSectionsForInjection(content) {
354
+ const lines = content.split('\n');
355
+ const out = [];
356
+ let inIndexedSection = false;
357
+ for (const line of lines) {
358
+ const h2 = /^##\s+(.*)$/.exec(line);
359
+ if (h2) {
360
+ const title = h2[1].trim().toLowerCase();
361
+ // 前缀匹配容忍「目录结构(monorepo)」「Directory Structure — monorepo」等后缀写法;
362
+ // 仅当后缀紧邻(空格/括号/冒号/破折号)时命中,避免误伤「约定与目录结构习惯」这类反向词序。
363
+ inIndexedSection = AGENTS_INJECTION_INDEX_SECTIONS.some((s) => title === s || new RegExp(`^${s}[\\s(:\\u2014\\uff08\\(]`).test(title));
364
+ if (inIndexedSection) {
365
+ out.push(`- ${h2[1].trim()}: (not injected — read_file AGENTS.md on demand)`);
366
+ continue;
367
+ }
368
+ }
369
+ if (!inIndexedSection)
370
+ out.push(line);
371
+ }
372
+ return out.join('\n').replace(/\n{3,}/g, '\n\n').trim();
373
+ }
334
374
  /**
335
375
  * 工作区根 AGENTS.md 自动导入段:与 memory 开关完全无关——
336
376
  * 只要 <cwd>/AGENTS.md 存在就把正文直接拼进 prompt(超 {@link MAX_AGENTS_IMPORT_CHARS} 截断+末尾提示),
337
377
  * 不再只指路让模型按需 read_file。读失败静默跳过(返空串)。
378
+ *
379
+ * 瘦身(方案A):目录结构/扩展点两章节不常驻,压成指针行——模型真做架构/扩展任务时
380
+ * 一次 read_file 取全文(渐进披露,与 Skills 清单同构);截断上限作用于过滤后的正文。
338
381
  */
339
382
  function buildAgentsImportSection() {
340
383
  try {
341
384
  const projectAgents = path.join(process.cwd(), 'AGENTS.md');
342
385
  if (!fs.existsSync(projectAgents))
343
386
  return '';
344
- const content = fs.readFileSync(projectAgents, 'utf8').trim();
345
- if (!content)
387
+ const raw = fs.readFileSync(projectAgents, 'utf8').trim();
388
+ if (!raw)
346
389
  return '';
347
- const body = content.length > MAX_AGENTS_IMPORT_CHARS
348
- ? `${content.slice(0, MAX_AGENTS_IMPORT_CHARS)}\n…[AGENTS.md truncated: first ${MAX_AGENTS_IMPORT_CHARS} characters injected]`
349
- : content;
350
- return `\n## Project memory (AGENTS.md, auto-imported)\n${body}\n- AGENTS.md may be stale: current code and the user request override stale memory.`;
390
+ const filtered = filterAgentsSectionsForInjection(raw);
391
+ const body = filtered.length > MAX_AGENTS_IMPORT_CHARS
392
+ ? `${filtered.slice(0, MAX_AGENTS_IMPORT_CHARS)}\n…[AGENTS.md truncated: first ${MAX_AGENTS_IMPORT_CHARS} characters injected]`
393
+ : filtered;
394
+ return (`\n## Project memory (AGENTS.md, auto-imported)\n${body}\n` +
395
+ '- AGENTS.md may be stale: current code and the user request override stale memory.\n' +
396
+ '- Discovered a stable, non-obvious project fact worth persisting? write_file(append=true) one line to `.mocode/agents-draft.md`; the user merges drafts into AGENTS.md via /init.');
351
397
  }
352
398
  catch {
353
399
  return ''; // 读失败静默跳过:不让导入破坏 prompt 构建
@@ -398,14 +444,14 @@ export function buildBasePrompt(sessionId = getCurrentSessionId()) {
398
444
  // 回复语言不写入提示词:模型按用户当轮提问语言自动识别(Voice 段的
399
445
  // "Match the user's style and language" 已覆盖),/language 只切换终端 UI 文案。
400
446
  const staticBody = `## Identity
401
- You are mocode, a terminal coding agent.
447
+ You are mocode, a terminal coding agent created by Wan Engineer.
402
448
 
403
449
  ## Core behavior
404
450
  Complete programming tasks through an "analyze → call tool → observe result → decide next step" loop until solved.
405
451
 
406
452
  ## Modes
407
- - AUTO is the default: investigate and complete the task with the tools currently exposed.
408
- - PLAN is read-only research and design; do not make changes until the user approves and switches back to AUTO.
453
+ - AUTO (default): complete tasks with the tools currently exposed.
454
+ - PLAN: read-only design; no changes until the user approves and switches back.
409
455
 
410
456
  ## Workflow
411
457
  - Understand: use existing conversation and tool evidence before gathering more.
@@ -17,40 +17,60 @@ import { buildSlashCommands, slashHelpLines, HELP_GROUPS } from '../commands.js'
17
17
  import { startRunningListener, stopRunningListener } from '../running-input.js';
18
18
  import { checkVersion, fetchLatestVersion, getCurrentVersion, runUpgradeForeground } from '../../commands/upgrade.js';
19
19
  import { unhandled, next, exit, forward } from './types.js';
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
20
22
  /**
21
- * /init 指令:发给 agent 扫描项目并生成 AGENTS.md。已存在则让 agent 读后更新(不丢失事实)。写完供 memory 子系统下轮加载。
23
+ * /init 指令:发给 agent 扫描项目并生成 AGENTS.md。整体重写(非增量追加),预算分配制:
24
+ * 总长 ≤4000 字且 ≤ 旧文件字数(代码侧动态注入具体数字),装不下的高价值事实进
25
+ * .mocode/agents-draft.md 草稿而非硬塞,消除「删事实 vs 守预算」两难。写完供 memory 子系统下轮加载。
22
26
  *
23
- * 函数化(非 const):.codegraph/ 索引是否存在的探测放在调用瞬间,没索引时不提 codegraph,
27
+ * 函数化(非 const):.codegraph/ 索引与旧文件字数的探测放在调用瞬间,没索引时不提 codegraph,
24
28
  * 避免 LLM 调出失败。/init 是冷启动动作,IO 开销可忽略。
25
29
  */
26
30
  function buildInitPrompt() {
27
31
  const cg = hasCodegraphIndex()
28
32
  ? '- 若有 .codegraph/:用 use_skill 加载 codegraph skill 后用 run_command 调 codegraph explore "<架构或入口符号>" 一次拿相关源码+调用路径,别逐文件读!!!\n'
29
33
  : '';
34
+ // 动态注入旧文件字数:具体数字的遵循率远高于抽象原则(「≤4000」);首次生成时无旧文件则只约束绝对上限。
35
+ let oldSizeNote = '';
36
+ try {
37
+ const old = fs.readFileSync(path.join(process.cwd(), 'AGENTS.md'), 'utf8');
38
+ if (old.trim())
39
+ oldSizeNote = `旧 AGENTS.md 为 ${old.length} 字(新文件净字数必须 ≤ ${Math.min(4000, old.length)}:既守 4000 上限也守零和,超出即继续删低价值条目直到达标)。`;
40
+ }
41
+ catch {
42
+ // 无旧文件:首次生成,只受 4000 字绝对上限约束。
43
+ }
30
44
  return `分析当前项目(process.cwd()),生成 AGENTS.md 项目记忆文件,供 mocode 后续会话自动加载——目标是让后续会话无需重新摸索就能上手。
31
45
 
32
46
  先探查(尽量少调用拿全貌):
33
47
  ${cg}- read_file package.json(或 Cargo.toml/pyproject.toml/go.mod 等):scripts、依赖、入口、模块类型。
34
48
  - glob 顶层目录;read_file 入口文件 + 各子系统 index.ts/README。
35
- - 若 AGENTS.md 已存在:read_file 读它,在其基础上更新(补缺、修正过时),不丢已有准确事实。
49
+ - 若 AGENTS.md 已存在:read_file 读它。**整体重写,不是增量追加**——不要把旧文当底稿做增量,从下面的预算出发重新分配每一条;以当前代码为准,过时/弱事实直接删,准确且仍高价值的可沿用。
50
+ - 若 .mocode/agents-draft.md 存在:read_file 读它,把草稿里的稳定事实合入对应章节,完成后用 write_file 覆盖为空(或删除该文件)。
36
51
 
37
52
  AGENTS.md 按以下结构写(每节简短,只写稳定、非显然的事实):
38
53
  ## 项目
39
54
  一两句:是什么、技术栈、运行环境。
40
55
  ## 命令
41
- install / dev / build / test / typecheck / lint 等——从 package.json scripts 提炼,写原样命令行(如 \`npm run typecheck\`);没有的注明"无测试"/"无 lint"。
56
+ install / dev / build / test / typecheck / lint 等——从 package.json scripts 提炼,写原样命令行(如 \`npm run build\`);没有的注明"无测试"/"无 lint"。
42
57
  ## 目录结构
43
- 顶层各目录与子系统职责,一句话/个;不逐文件列。
58
+ 顶层各目录与子系统职责,一句话/个;不逐文件列。此节系统提示不会常驻注入(按需 read_file),够用即可。
44
59
  ## 约定
45
60
  从代码与现有文档提炼的硬约定:模块系统(ESM?)、命名、错误处理、工具/函数契约、易踩坑点。只写非显然、会让人踩坑的;不写"保持简洁"这种正确废话。
46
61
  ## 扩展点
47
- 加工具/命令/provider/模块的接缝(改哪个文件、加在哪)。
62
+ 加工具/命令/provider/模块的接缝(改哪个文件、加在哪)。此节同样不常驻注入,保持简短。
63
+
64
+ 预算(硬上限,按字分配,参考):
65
+ - 总长 ≤ 4000 字${oldSizeNote ? `,且 ${oldSizeNote}` : '(首次生成)'}。参考分配:项目 ≤200、命令 ≤650、目录结构 ≤400、约定 ≤2000、扩展点 ≤400。
66
+ - **预算装不下但确有高价值的事实不丢**:write_file(append=true) 追加到 \`.mocode/agents-draft.md\`(一行一条,注明所属章节),下次 /init 再评估合入。禁止为了塞进预算牺牲准确性,也禁止超预算硬塞。
48
67
 
49
68
  硬要求:
50
69
  - 从实际代码提炼,引用具体文件名/命令/符号;不编造、不泛泛。
51
- - 总长 ≤ 3000 字;只写后续会话有用的稳定事实,不写易变项(当前 bug、临时文件、未决 TODO)。
52
- - 用 write_file 写入项目根 AGENTS.md。
53
- - 写完简述:写了哪几节 + 从代码里发现的 2-3 条非显然关键约定(供用户校验)。`;
70
+ - 只写后续会话有用的稳定事实,不写易变项(当前 bug、临时文件、未决 TODO)。
71
+ - 写入前 run_command \`wc -m AGENTS.md\` 不可用(文件还没写);改用你草稿的字数估算,并按 ${oldSizeNote ? '上述净删目标' : '4000 字上限'}自查,超了先删再写。
72
+ - 用 write_file 写入项目根 AGENTS.md;**写完立即 read_file AGENTS.md 复查实际字数**,超限则编辑收窄后重写,直到达标。
73
+ - 最终简述必须含对账三件套(供用户验收,缺一不可):①新旧字数对比(旧 X → 新 Y,无旧文件写"首建 Y");②删掉的条目清单(标题级);③新增的条目清单(标题级)+ 从代码里发现的 2-3 条非显然关键约定。`;
54
74
  }
55
75
  export const systemCommands = [
56
76
  (ctx) => {
@@ -111,10 +111,9 @@ export const askHumanTool = {
111
111
  name: 'ask_human',
112
112
  description: [
113
113
  'Ask the user a question and wait for their response.',
114
- ' CHOICES: pass 2-4 concrete options via `options`, each as { label, description? }.',
114
+ ' CHOICES: pass 2-4 concrete options via `options`, each as { label, description? }; every non-empty option requires a non-empty `label`.',
115
115
  ' FREE-TEXT: pass `options: []` when the answer cannot be reduced to choices (e.g. "paste the error message").',
116
- ' Every non-empty option requires a non-empty `label`; never emit [{}], omit label, or omit `options`.',
117
- ' DO NOT call when the task is clear and you can pick a sensible default.',
116
+ ' DO NOT call when the task is clear and a sensible default exists.',
118
117
  ].join(' '),
119
118
  parameters: {
120
119
  type: 'object',
@@ -13,23 +13,15 @@ function conflict(path, details) {
13
13
  }
14
14
  export const editFileTool = {
15
15
  name: 'edit_file',
16
- description: `Replace content in a file transactionally. Supports two modes:
16
+ description: `Replace content in a file transactionally. Two modes:
17
17
 
18
- **String replacement mode (default):** Provide old_string that occurs exactly once in the file. The old_string must be copied verbatim from a fresh read_file output — do NOT reconstruct from memory, summaries, or grep output, as these lose whitespace/indentation details. Common failure modes: trailing whitespace, tabs vs spaces, indentation changes, line-ending mismatches (CRLF vs LF).
18
+ **String replacement (default):** old_string must occur EXACTLY once, copied verbatim from a fresh read_file output — never reconstruct from memory, summaries, or grep output (whitespace/indentation/line-ending details are lost there).
19
19
 
20
- **Line-range mode:** Provide line_start and line_end (1-based, inclusive) instead of old_string. Use this when the exact text is hard to reproduce or when replacing a large block.
20
+ **Line-range:** line_start/line_end (1-based, inclusive) instead of old_string — for large blocks, repeated patterns, or hard-to-reproduce text.
21
21
 
22
- expected_hash is required (sha256 from read_file artifact header) and must match the current file hash. If the file changed after your read, the edit is rejected. Recovery: call read_file again on the same path and copy both the new hash and exact text.
22
+ expected_hash (sha256 from read_file artifact header) is required and must match the current file; changed-since-read edits are rejected — re-read and retry with the new hash.
23
23
 
24
- **When to use which mode:**
25
- - String replacement: small, unique text fragments (function signatures, config keys, error messages)
26
- - Line-range: large blocks, repeated patterns, or when whitespace precision is critical
27
-
28
- **Anti-patterns (will fail):**
29
- - old_string reconstructed from memory or a summary
30
- - old_string copied from a previous tool call that may be stale
31
- - old_string that appears multiple times (add more context to make it unique)
32
- - expected_hash from a different file or an old read_file call`,
24
+ Anti-patterns (will fail): old_string from memory/summary/stale call; multiple occurrences (add context to disambiguate); hash from another file or old read.`,
33
25
  risk: 'confirm',
34
26
  parameters: {
35
27
  type: 'object',
@@ -4,8 +4,8 @@ import { getSandboxRoot, isInsideRoot } from '../../sandbox/index.js';
4
4
  // ---------- glob ----------
5
5
  export const globTool = {
6
6
  name: 'glob',
7
- description: 'Find files matching a glob pattern (e.g. **/*.ts). Auto-excludes node_modules/.git.' +
8
- ' For architecture or call chains, prefer loading the `codegraph` skill (use_skill).',
7
+ description: 'Find files matching a glob pattern (e.g. **/*.ts). Auto-excludes node_modules/.git. ' +
8
+ 'For architecture or call chains, prefer the codegraph skill.',
9
9
  parameters: {
10
10
  type: 'object',
11
11
  properties: {
@@ -60,13 +60,9 @@ export const grepTool = {
60
60
  name: 'grep',
61
61
  description: 'Search file contents by regex (recursive, excludes node_modules/.git/dist).\n' +
62
62
  'Output: per-file header "<path>: N matches, lines [l1, l2, ...]" + matched lines with ORIGINAL INDENTATION kept.\n' +
63
- 'Pass context=2..5 to get neighbouring lines inline (like ripgrep -C) — use it INSTEAD of following every hit ' +
64
- 'with a read_file round-trip. Context lines use an `L<n>-` prefix and blocks are separated by ` --`.\n' +
65
- 'Still use read_file(offset=X, limit=Y) when you need a whole region or exact text for an edit — ' +
66
- 'do NOT read entire files after grepping, and do NOT reconstruct an edit_file old_string from grep output ' +
67
- '(long lines are clipped).\n' +
68
- 'Independent read_file/grep/glob calls may be issued in the same response and run concurrently. ' +
69
- 'For call chains across many files, prefer loading the `codegraph` skill (use_skill).',
63
+ 'Pass context=2..5 to get neighbouring lines inline (like ripgrep -C) — use it INSTEAD of following every hit with a read_file round-trip.\n' +
64
+ 'Still use read_file(offset=X, limit=Y) for a whole region or exact edit text — never reconstruct an edit_file old_string from grep output (long lines are clipped). ' +
65
+ 'For call chains across many files, prefer the codegraph skill.',
70
66
  parameters: {
71
67
  type: 'object',
72
68
  properties: {
@@ -36,14 +36,10 @@ function normalizeSection(raw) {
36
36
  }
37
37
  export const noteAppendTool = {
38
38
  name: 'note_append',
39
- description: 'Append a decision-grade note (a finding, decision, open question, or risk) to the session notepad ' +
40
- '(`.mocode/sessions/<id>/notes.md`) so it survives context compaction and stays resident in the prompt. ' +
41
- 'Use this for NON-OBVIOUS, lasting-value discoveries — subtle constraints, decisions with downstream impact, ' +
42
- 'open questions that block a choice, or risks that affect later steps. Do NOT use it for routine progress ' +
43
- '(that is the plan via `plan_update`) or for stable cross-session facts (that is `memory_save`). ' +
44
- 'Notes you write here persist across compaction within this session and are re-injected into the prompt ' +
45
- 'automatically, so the agent keeps remembering what it found/decided. Call it the moment you make the ' +
46
- 'discovery or decision — do not batch to the end.',
39
+ description: 'Append ONE decision-grade note (finding / decision / open question / risk) to the session notepad (notes.md); it survives compaction and is re-injected into the prompt automatically. ' +
40
+ 'Only NON-OBVIOUS, lasting-value discoveries: subtle constraints, decisions with downstream impact, open questions blocking a choice, risks affecting later steps. ' +
41
+ 'NOT routine progress (that is plan_update) and NOT stable cross-session facts (that is memory_save). ' +
42
+ 'Call the moment you make the discovery or decision — do not batch to the end. One item per call.',
47
43
  risk: 'safe',
48
44
  parameters: {
49
45
  type: 'object',
@@ -18,12 +18,7 @@ function normalizeStatus(raw) {
18
18
  }
19
19
  export const planUpdateTool = {
20
20
  name: 'plan_update',
21
- description: 'Record and update the session execution plan (the `## Plan:` block in `.mocode/sessions/<id>/notes.md`). ' +
22
- 'Use for any task with 3+ steps or context-loss risk. This REPLACES the whole plan each call, so always pass the full steps array. ' +
23
- 'Rules: at most one step may be in_progress; mark a step completed as soon as its work is done — do not batch updates to end of turn. ' +
24
- 'Give every step a short `title` (≤20 chars, e.g. "编写测试" / "修 status bar") that shows in the status bar, ' +
25
- 'plus a `content` that is self-contained enough to survive context compaction: name the target file/symbol, the change, and how to verify. ' +
26
- 'When every step is completed the plan auto-settles to `## Done:`. Creates notes.md if missing. Safe to call in PLAN mode (writes only the session notepad, never project files).',
21
+ description: 'Record and update the session execution plan (the `## Plan:` block in notes.md). Use for any task with 3+ steps or context-loss risk. REPLACES the whole plan each call — always pass the full steps array. At most one step in_progress; mark a step completed as soon as its work is done, not batched to end of turn. Each step: short `title` (≤20 chars, shown in the status bar) + `content` self-contained enough to survive compaction (target file/symbol, change, verification). All steps completed → auto-settles to `## Done:`. Creates notes.md if missing. Safe in PLAN mode (writes only the session notepad).',
27
22
  risk: 'safe',
28
23
  parameters: {
29
24
  type: 'object',
@@ -36,18 +36,11 @@ function failure(code, message) {
36
36
  export const readFileTool = {
37
37
  name: 'read_file',
38
38
  description: 'Read a file: text with line numbers, images as visual model input.\n' +
39
- 'Text — read before editing. For files >500 lines: grep first to locate regions, then call read_file ' +
40
- 'multiple times with offset+limit (e.g. offset=350, limit=120). Do NOT read an entire large file in one call.\n' +
41
- 'When you need several regions of the SAME file, issue those read_file calls together in one ' +
42
- 'response (they run concurrently) instead of paging through it one page after another — ' +
43
- 'avoid sequential offset+=limit walks of the same file.\n' +
44
- 'For files ≤500 lines you may read the whole file in one call. Independent region reads ' +
45
- 'may be issued in the same response — they run concurrently, saving a round-trip each.\n' +
46
- 'Images — PNG/JPEG/GIF/WebP are detected by MAGIC BYTES (the extension does not matter) and attached as ' +
47
- 'visual model input; pass detail=low|high to control resolution, and oversized PNGs are downscaled automatically.\n' +
48
- 'Other binary files are REJECTED with an explanation instead of being dumped as garbled text. ' +
49
- 'If you truly need their content, use run_command with a proper tool (e.g. `file`, `strings`, a disassembler).\n' +
50
- 'For architecture or call-chain questions, prefer loading the `codegraph` skill (use_skill) over reading files one at a time.',
39
+ 'Text — read before editing. Files >500 lines: grep first, then read_file with offset+limit (e.g. offset=350, limit=120); never read a whole large file in one call. ' +
40
+ 'Need several regions of the SAME file? Issue those read_file calls together in one response (they run concurrently) instead of sequential offset+=limit walks. ' +
41
+ 'Images — PNG/JPEG/GIF/WebP detected by MAGIC BYTES (extension ignored), attached as visual input; detail=low|high controls resolution, oversized PNGs downscale automatically. ' +
42
+ 'Other binaries are REJECTED with an explanation — use run_command with a proper tool (`file`, `strings`, disassembler) if you need their content. ' +
43
+ 'Architecture/call-chain questions: prefer the codegraph skill over reading files one at a time.',
51
44
  parameters: {
52
45
  type: 'object',
53
46
  properties: {
@@ -164,15 +164,9 @@ function commandOutcome(result) {
164
164
  // ---------- run_command ----------
165
165
  export const runCommandTool = {
166
166
  name: 'run_command',
167
- description: 'Run a FOREGROUND shell command, merging stdout+stderr. Default timeout 120s, hard cap 10min. ' +
168
- 'For tests, builds, git, etc. Pass shell=cmd|powershell|bash to choose the interpreter — the platform ' +
169
- 'default (and any MOCODE_SHELL override) is stated in the system prompt. Non-interactive cmd cannot run `timeout /t` — ' +
170
- 'pass shell=powershell (`Start-Sleep`) or shell=bash (`sleep`) when a wait is needed.\n' +
171
- 'Anything that must keep running after this call returns — dev server, inference/model service, watcher, ' +
172
- 'log tail — belongs to dev_server instead: it survives across tool calls and gives you an id for ' +
173
- 'incremental log reads and process-tree kill. Do NOT detach with `start /b`, `nohup`, `&` or similar here: ' +
174
- 'you lose both the logs and the handle.\n' +
175
- "Multiple independent run_command calls may be issued in one response to save model round-trips; they execute serially, so do not depend one on another's output within the same message.",
167
+ description: 'Run a FOREGROUND shell command, merging stdout+stderr. Default timeout 120s, hard cap 10min; pass shell=cmd|powershell|bash to pick the interpreter (platform default and MOCODE_SHELL override are stated in the system prompt). Non-interactive cmd cannot run `timeout /t` — use shell=powershell (`Start-Sleep`) or shell=bash (`sleep`) for waits.\n' +
168
+ 'Anything that must keep running after this call returns — dev server, model service, watcher, log tail — belongs to dev_server (survives across calls; gives an id for incremental logs and process-tree kill). Do NOT detach via `start /b`, `nohup`, `&`: you lose both the logs and the handle.\n' +
169
+ 'Multiple independent calls may be issued in one response (they run serially, in order); do not depend one on another\'s output within the same message.',
176
170
  risk: 'dangerous',
177
171
  parameters: {
178
172
  type: 'object',
@@ -13,9 +13,9 @@ import { activateSkill } from '../../skills/activation.js';
13
13
  const MAX_SKILL_FILE = 200_000;
14
14
  export const useSkillTool = {
15
15
  name: 'use_skill',
16
- description: 'Load the full SKILL.md instructions for a given skill. See the skill list in the system prompt for when to use each. ' +
16
+ description: 'Load the full SKILL.md instructions for a given skill (see the skill list in the system prompt for when to use each). ' +
17
17
  'Supports args (renders $ARGUMENTS / $1.. / ${SKILL_DIR}) and file (reads a bundled reference file). ' +
18
- 'For skills marked [fork], this returns a guide to call run_skill instead of loading the body inline.',
18
+ 'Skills marked [fork] return a guide to call run_skill instead of loading the body inline.',
19
19
  parameters: {
20
20
  type: 'object',
21
21
  properties: {
@@ -77,8 +77,7 @@ async function attemptFetch(target, via, signal) {
77
77
  // ---------- web_fetch ----------
78
78
  export const webFetchTool = {
79
79
  name: 'web_fetch',
80
- description: 'Fetch a URL and clean HTML to body text. Use to read a link from search results or a URL given by the user.',
81
- parameters: {
80
+ description: 'Fetch a URL and clean HTML to body text. Use to read a link from search results or a URL given by the user.', parameters: {
82
81
  type: 'object',
83
82
  properties: {
84
83
  url: { type: 'string', description: 'Full URL to fetch; must be http/https' },
@@ -6,8 +6,7 @@ const MAX_CONTENT_CHARS = 800;
6
6
  // ---------- web_search ----------
7
7
  export const webSearchTool = {
8
8
  name: 'web_search',
9
- description: 'Search the web (AnySearch). Returns title/url/snippet/body per result. Optional tag for sub-domain capability.',
10
- parameters: {
9
+ description: 'Search the web (AnySearch). Returns title/url/snippet/body per result. Optional tag for sub-domain capability.', parameters: {
11
10
  type: 'object',
12
11
  properties: {
13
12
  query: { type: 'string', description: 'Search query' },
@@ -28,10 +28,8 @@ function invalid(path, message) {
28
28
  }
29
29
  export const writeFileTool = {
30
30
  name: 'write_file',
31
- description: 'Create or replace one file transactionally. expected_hash may be omitted (or null) only for create-only writes to a path that must not exist; overwriting requires the hash from a fresh read_file artifact header.\n' +
32
- 'To ADD to an existing file (logs, growing docs, staged generation of a long file), pass append=true instead of re-sending the whole content: only the new text goes in `content`. ' +
33
- 'append needs NO expected_hash and NO prior read_file (concurrency is handled by the file lock); it creates the file when missing, and it appends bytes VERBATIM — ' +
34
- 'if the file does not end with a newline, start your content with "\\n" or the last line will merge with yours.',
31
+ description: 'Create or replace one file transactionally. expected_hash may be omitted (or null) only for create-only writes to a path that must not exist; overwriting requires the hash from a fresh read_file.\n' +
32
+ 'To ADD to an existing file, pass append=true — only the new text goes in `content`; NO expected_hash and NO prior read_file needed (lock-protected; creates the file when missing). Appends VERBATIM: if the file does not end with a newline, start your content with "\\n" or the last line will merge with yours.',
35
33
  risk: 'confirm',
36
34
  parameters: {
37
35
  type: 'object',
@@ -237,7 +237,7 @@ export class ToolPolicyController {
237
237
  '## Tool route (current turn)',
238
238
  `Policy ${snapshot.id} v${snapshot.version}; active groups: ${active}.`,
239
239
  `Router reason: ${snapshot.reason}`,
240
- 'Use only the tools currently exposed. If a required capability is missing, call add_tool_groups alone; dependent calls must wait until the next step.',
240
+ 'Use only the exposed tools. Missing capability? Call add_tool_groups alone; dependent calls wait for the next step.',
241
241
  ];
242
242
  if (remaining.length)
243
243
  lines.push(`Groups still available: ${remaining.join(', ')}.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocode-ai",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "终端编码 agent:LLM + tool-call 循环 + 流式输出(含思考)+ 25 个工具,接任意 OpenAI 兼容后端。",
5
5
  "type": "module",
6
6
  "bin": {