mocode-ai 1.0.14 → 1.1.1
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 +2 -19
- package/README.zh-CN.md +1 -1
- package/bin/mocode-agent-host.js +1 -1
- package/dist/agent/core.js +24 -5
- package/dist/agent/index.js +2 -0
- package/dist/agent/mode.js +9 -10
- package/dist/agent/spawn.js +9 -9
- package/dist/agent/work-discipline.js +19 -26
- package/dist/config/index.js +117 -226
- package/dist/context/artifacts.js +2 -2
- package/dist/context/classifier.js +3 -6
- package/dist/context/encoders/_util.js +2 -1
- package/dist/context/encoders/index.js +3 -4
- package/dist/context/lifecycle.js +3 -23
- package/dist/context/relevance.js +3 -22
- package/dist/host/protocol.js +19 -1
- package/dist/host/stdio.js +205 -0
- package/dist/i18n/index.js +0 -30
- package/dist/memory/index.js +3 -50
- package/dist/repl/index.js +18 -278
- package/dist/sandbox/policy.js +6 -4
- package/dist/session/notes-plan.js +41 -0
- package/dist/skills/builtin-skills.js +56 -0
- package/dist/skills/discover.js +7 -0
- package/dist/skills/index.js +23 -4
- package/dist/tools/builtins/glob.js +1 -1
- package/dist/tools/builtins/grep.js +1 -1
- package/dist/tools/builtins/index.js +0 -14
- package/dist/tools/builtins/read-file.js +1 -1
- package/dist/tools/constants.js +2 -1
- package/package.json +1 -1
package/dist/config/index.js
CHANGED
|
@@ -2,8 +2,6 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import dotenv from 'dotenv';
|
|
5
|
-
import { loadSnapshot } from '../project-snapshot/index.js';
|
|
6
|
-
import { buildProjectSkillSection } from '../project-skill/index.js';
|
|
7
5
|
import { getSandboxRoot } from '../sandbox/root.js';
|
|
8
6
|
import { getCurrentSessionId } from '../session/state.js';
|
|
9
7
|
import { buildWorkDisciplineSection, inferModelFamily } from '../agent/work-discipline.js';
|
|
@@ -64,57 +62,32 @@ export function isModelConfigured() {
|
|
|
64
62
|
const PLATFORM_NOTE = (() => {
|
|
65
63
|
if (process.platform === 'win32') {
|
|
66
64
|
return `## Environment (Windows)
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
- head/tail/find/grep/sed have no cmd.exe equivalent — use the dedicated tools (read_file for head/tail, glob for find, grep for grep), or invoke PowerShell via run_command if you need more.
|
|
70
|
-
- **Avoid \`run_command\` for file ops on Windows**: cmd /c re-parses paths with backslashes / spaces / quotes — fragile, and ~half of "agent can't find file" failures trace back to this. Use the dedicated tools (read_file/glob/grep) which take absolute Windows paths natively, no shell involved. In particular, NEVER \`dir\` / \`ls\` / \`Test-Path\` / \`if exist\` / \`python -c "os.path.exists(...)"\` — those waste turns on escaping. Use \`glob\` to list, and just call \`read_file\` to test existence (returns ENOENT as a clean error string). If you must shell out, use forward slashes (\`C:/foo/bar\`).
|
|
71
|
-
- Prefer the dedicated tools (read_file/glob/grep) over shell equivalents — they're cross-platform and already wired in.`;
|
|
65
|
+
- \`run_command\` uses \`cmd.exe /c\`: use cmd syntax and \`%VAR%\`; Unix builtins and command substitution are unavailable.
|
|
66
|
+
- Prefer read_file/glob/grep for file discovery and reading. When shell is necessary, use forward-slash paths or invoke PowerShell explicitly.`;
|
|
72
67
|
}
|
|
73
68
|
if (process.platform === 'darwin') {
|
|
74
69
|
return `## Environment (macOS)
|
|
75
|
-
-
|
|
76
|
-
- Pitfalls: sed -i needs an empty backup-ext arg (sed -i '' 's/x/y/' file); grep -P unavailable (use grep -E or the grep tool); find/readlink/date are BSD variants; readlink -f unsupported (use realpath, or greadlink -f if GNU coreutils installed via brew).
|
|
77
|
-
- Prefer the dedicated tools (read_file/glob/grep) over shell equivalents — they sidestep BSD/GNU differences.`;
|
|
70
|
+
- \`run_command\` uses bash with BSD utilities. Prefer read_file/glob/grep; account for BSD/GNU differences when shell commands are necessary.`;
|
|
78
71
|
}
|
|
79
72
|
return `## Environment (Linux/Unix)
|
|
80
|
-
-
|
|
81
|
-
- Still prefer the dedicated tools (read_file/glob/grep) over hand-rolled shell where they fit — they avoid quoting pitfalls and are already wired in.`;
|
|
73
|
+
- \`run_command\` uses bash. Prefer read_file/glob/grep when they fit; otherwise use standard POSIX/GNU syntax.`;
|
|
82
74
|
})();
|
|
83
75
|
/**
|
|
84
76
|
* 基础系统提示的"记忆段落":开 isMemoryEnabled() 时才拼。
|
|
85
77
|
* 默认关(新用户零侵入):这段 + 工具表里的 5 个 memory_* + 系统提示尾部的 Memory Index
|
|
86
78
|
* 都不出现;打开 /memory_switch 后下一次新建 system message 才注入。
|
|
87
79
|
*/
|
|
88
|
-
/**
|
|
89
|
-
* 项目快照段落:开 isProjectSnapshotEnabled() 且有快照时才拼。
|
|
90
|
-
* 告诉 LLM 项目已有哪些静态文件可 cache hit,减少无谓 read_file。
|
|
91
|
-
*/
|
|
92
|
-
function buildSnapshotSection() {
|
|
93
|
-
if (!config.projectSnapshotEnabled)
|
|
94
|
-
return '';
|
|
95
|
-
try {
|
|
96
|
-
const snap = loadSnapshot();
|
|
97
|
-
if (!snap)
|
|
98
|
-
return '';
|
|
99
|
-
// 快照内容已经是完整的 markdown,直接返回
|
|
100
|
-
return `\n${snap.content}\n`;
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return '';
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* 项目快照总开关查询器(read-file 工具用)。
|
|
108
|
-
*/
|
|
109
|
-
export function isProjectSnapshotEnabled() {
|
|
110
|
-
return config.projectSnapshotEnabled;
|
|
111
|
-
}
|
|
112
80
|
/**
|
|
113
81
|
* Session notepad 段落:读取 .mocode/sessions/<sessionId>/notes.md,只注入 ## 标题行作为目录摘要。
|
|
114
82
|
* Agent 用 write_file/edit_file/read_file 维护此文件,抗 compact(在 context window 之外)。
|
|
115
83
|
* 文件不存在或为空时返空串(零开销)。
|
|
84
|
+
*
|
|
85
|
+
* 输出按"活跃 / 已完成"两栏分桶,让 agent 一眼看到还有未结的工作:
|
|
86
|
+
* - Active: ## Plan: ... ## Open Questions ## <其它正在用的 topic>
|
|
87
|
+
* - Done: ## Done: ... (agent 在完成时把 topic 重命名为 "## Done: ...")
|
|
88
|
+
* 这样比纯目录列表更显眼,降低 agent 在长上下文里扫过去就忘了的概率。
|
|
116
89
|
*/
|
|
117
|
-
function buildNotepadSection(sessionId = getCurrentSessionId()) {
|
|
90
|
+
export function buildNotepadSection(sessionId = getCurrentSessionId()) {
|
|
118
91
|
if (!sessionId)
|
|
119
92
|
return '';
|
|
120
93
|
const root = getSandboxRoot() ?? process.cwd();
|
|
@@ -125,39 +98,54 @@ function buildNotepadSection(sessionId = getCurrentSessionId()) {
|
|
|
125
98
|
const content = fs.readFileSync(p, 'utf8').trim();
|
|
126
99
|
if (!content)
|
|
127
100
|
return '';
|
|
128
|
-
// 1) 提取 ## 标题行(最多 15
|
|
101
|
+
// 1) 提取 ## 标题行(最多 15 个,按文件出现顺序保留)
|
|
129
102
|
const headers = content.split('\n')
|
|
130
103
|
.filter(l => /^##\s/.test(l))
|
|
131
104
|
.slice(0, 15);
|
|
132
|
-
// 2)
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
105
|
+
// 2) 分桶:Done: 开头 → archived;其余 → active
|
|
106
|
+
// "## Plan:" 和 "## Open Questions" 视为永久 active(不需要改名为 Done)。
|
|
107
|
+
const archived = [];
|
|
108
|
+
const active = [];
|
|
109
|
+
for (const h of headers) {
|
|
110
|
+
if (/^##\s+Done:\s/.test(h))
|
|
111
|
+
archived.push(h);
|
|
112
|
+
else
|
|
113
|
+
active.push(h);
|
|
114
|
+
}
|
|
115
|
+
if (active.length === 0 && archived.length === 0)
|
|
140
116
|
return '';
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`## Session Notepad (your working notes — use read_file(".mocode/sessions/${sessionId}/notes.md") for details)`,
|
|
144
|
-
'Sections:',
|
|
145
|
-
...headers,
|
|
146
|
-
planChip,
|
|
117
|
+
const totalCount = active.length + archived.length;
|
|
118
|
+
const lines = [
|
|
147
119
|
'',
|
|
148
|
-
|
|
120
|
+
`## Session Notepad (${totalCount} section${totalCount === 1 ? '' : 's'} — read \`.mocode/sessions/${sessionId}/notes.md\` to recover full context; surviving compact is the whole point of this file)`,
|
|
121
|
+
`Active (${active.length}):`,
|
|
122
|
+
...(active.length ? active.map(h => ` - ${h.replace(/^##\s+/, '')}`) : [' - (none)']),
|
|
123
|
+
];
|
|
124
|
+
if (archived.length) {
|
|
125
|
+
lines.push(`Done (${archived.length}):`);
|
|
126
|
+
lines.push(...archived.map(h => ` - ${h.replace(/^##\s+/, '')}`));
|
|
127
|
+
}
|
|
128
|
+
lines.push('');
|
|
129
|
+
return lines.join('\n');
|
|
149
130
|
}
|
|
150
131
|
catch {
|
|
151
132
|
return '';
|
|
152
133
|
}
|
|
153
134
|
}
|
|
154
135
|
const SYSTEM_PROMPT_MEMORY_SECTION = `
|
|
155
|
-
## Memory (cross-session
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
136
|
+
## Memory (cross-session facts)
|
|
137
|
+
- The prompt may contain a title/summary index; retrieve details with memory_search or inspect all with memory_list.
|
|
138
|
+
- Save only stable, non-obvious cross-session facts. Search before saving; update an existing entry instead of duplicating it, and archive stale entries.`;
|
|
139
|
+
/** Inject only retrieval guidance; MOCODE.md contents stay outside the prompt until read on demand. */
|
|
140
|
+
function buildMemoryPromptSection() {
|
|
141
|
+
if (!isMemoryEnabled())
|
|
142
|
+
return '';
|
|
143
|
+
const projectMocode = path.join(process.cwd(), 'MOCODE.md');
|
|
144
|
+
const mocodeHint = fs.existsSync(projectMocode)
|
|
145
|
+
? '\n- `MOCODE.md` exists at the workspace root but is not preloaded. Read it with `read_file` only when the task may depend on project architecture, conventions, commands, prior decisions, or user preferences; skip it for greetings and unrelated simple requests. Current code and the user request override stale memory.'
|
|
146
|
+
: '';
|
|
147
|
+
return SYSTEM_PROMPT_MEMORY_SECTION + mocodeHint;
|
|
148
|
+
}
|
|
161
149
|
/**
|
|
162
150
|
* plan 模式追加到系统提示末尾的指令(切到 plan 模式时由 repl 拼进 history[0])。
|
|
163
151
|
* 与 SYSTEM_PROMPT 同语种(英文),指示:只读探查、产出步骤化计划、不执行、审批后回 auto。
|
|
@@ -165,167 +153,74 @@ const SYSTEM_PROMPT_MEMORY_SECTION = `
|
|
|
165
153
|
* memoryEnabled=false 时:memory_save/update/forget 三个写工具名字 + "memory-write tools" 这
|
|
166
154
|
* 句都不出现,且 read-only 列表里的 memory_search/memory_list 也移除——避免提示词里出现
|
|
167
155
|
* 根本不存在的工具名引起 LLM 调不到。
|
|
156
|
+
*
|
|
157
|
+
* .codegraph/ 存在性也动态决定:有索引时引导 LLM 优先用 codegraph skill(免去逐文件扫);
|
|
158
|
+
* 没索引时干脆不提,避免 LLM 调出失败。函数化(非 const)以便在 buildPlanModeSuffix 里现拼。
|
|
168
159
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
177
|
-
- The REPL approval prompt is only a fallback; do not rely on it.`;
|
|
178
|
-
function buildPlanModeSuffix() {
|
|
179
|
-
const memoryTools = isMemoryEnabled()
|
|
180
|
-
? 'memory_save, memory_update, memory_forget'
|
|
160
|
+
function buildPlanResearchRules() {
|
|
161
|
+
const cg = hasCodegraphIndex()
|
|
162
|
+
? ' Prefer the available codegraph skill for call paths and blast radius.'
|
|
181
163
|
: '';
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
164
|
+
return `
|
|
165
|
+
- Locate relevant code and conventions without repeating retrieved work.${cg}
|
|
166
|
+
- Return an actionable plan with affected files, ordered steps, edge cases, and verification.
|
|
167
|
+
- When ready, call \`ask_human\` with exactly: "按计划执行", "继续细化方案", and "取消 / 暂不执行". Approval requires the user to switch to /auto; never execute or switch modes silently.`;
|
|
168
|
+
}
|
|
169
|
+
function buildPlanModeSuffix() {
|
|
188
170
|
return `
|
|
189
171
|
|
|
190
172
|
## ⛯ PLAN MODE (active now)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
${PLAN_RESEARCH_RULES}`;
|
|
173
|
+
Investigate and design only. Use only the read-only tools currently exposed; do not execute commands or change files.
|
|
174
|
+
${buildPlanResearchRules()}`;
|
|
194
175
|
}
|
|
195
176
|
/** 兼容旧名字:repl 的 buildSystemMessage 仍引 PLAN_MODE_SUFFIX(变量)。运行时按需现拼。 */
|
|
196
177
|
export function buildBasePrompt(sessionId = getCurrentSessionId()) {
|
|
197
|
-
const
|
|
198
|
-
? '- Default is AUTO mode: you research and execute with all tools (read/edit/run_command/memory/web/skills).'
|
|
199
|
-
: '- Default is AUTO mode: you research and execute with all tools (read/edit/run_command/web/skills).';
|
|
200
|
-
const memorySection = isMemoryEnabled() ? SYSTEM_PROMPT_MEMORY_SECTION : '';
|
|
201
|
-
const planLine = isMemoryEnabled()
|
|
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.'
|
|
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.';
|
|
178
|
+
const memorySection = buildMemoryPromptSection();
|
|
204
179
|
return `## Core behavior
|
|
205
180
|
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')}
|
|
206
181
|
|
|
207
|
-
##
|
|
208
|
-
|
|
209
|
-
|
|
182
|
+
## Modes
|
|
183
|
+
- AUTO is the default: investigate and complete the task with the tools currently exposed.
|
|
184
|
+
- PLAN is read-only research and design; do not make changes until the user approves and switches back to AUTO.
|
|
210
185
|
|
|
211
186
|
${PLATFORM_NOTE}
|
|
212
187
|
|
|
213
188
|
${buildWorkDisciplineSection(inferModelFamily(config.model))}
|
|
214
189
|
|
|
215
|
-
## Tool details
|
|
216
|
-
### Token-efficient execution
|
|
217
|
-
- 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".
|
|
218
|
-
- 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.
|
|
219
|
-
- 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.
|
|
220
|
-
- Prefer one precise call over overlapping searches. If a call fails, inspect the error and change the approach instead of repeating it unchanged.
|
|
221
|
-
- 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.
|
|
222
|
-
- Read only what supports the next decision; verify once after a related edit set, not after every edit.
|
|
223
|
-
- Do not repeat an unchanged failing call; after three unproductive attempts, change tools or ask for the missing decision.
|
|
224
|
-
- For \`edit_file\`, derive \`old_string\` by copying the exact relevant lines from the latest successful \`read_file\` of that same path and pass that read's \`expected_hash\`; never reconstruct either 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 a conflict, re-read the exact region and retry once with the new text and hash; never retry identical arguments.
|
|
225
|
-
|
|
226
190
|
## Workflow
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
## Tool rules
|
|
233
|
-
- Precise path/symbol → go directly to \`read_file\` or \`codegraph node\`; use \`glob\`/\`grep\` only for discovery.
|
|
234
|
-
- Before editing, read the exact target region and copy both its artifact \`hash\` and verbatim text. Use \`edit_file\` with \`expected_hash\` for unique local replacements, and \`write_file\` with the latest hash for replacement (or null only for creation).
|
|
235
|
-
- Local edits require an exact unique match; use \`write_file\` for new/full files.
|
|
236
|
-
- Use \`glob\`/\`grep\` for discovery and \`run_command\` for execution or verification, not file existence checks. State intent before side effects.
|
|
237
|
-
- Call \`ask_human\` only when a real user decision is required; otherwise decide and proceed.
|
|
238
|
-
- Drop stale tool output when it no longer supports the current sub-task. Keep only evidence needed for the next decision.
|
|
239
|
-
- 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.
|
|
240
|
-
|
|
241
|
-
## Large file writes (avoid token-cap truncation)
|
|
242
|
-
- \`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.
|
|
243
|
-
- **For large files (rough threshold: >200 lines OR >5K tokens of content)**, default to one of these strategies instead of one giant \`write_file\`:
|
|
244
|
-
- **Skeleton + edit**: \`write_file\` a small skeleton (head + placeholders), then call \`edit_file\` repeatedly to append/replace sections — each edit stays well under the cap, and partial progress survives a stream error.
|
|
245
|
-
- **Shell heredoc**: \`run_command\` with \`cat > path <<'EOF' ... EOF\` (bash) or \`Set-Content -Path ... -Value @"..."@\` (PowerShell) — the file content bypasses the model's JSON output entirely, so no token cap applies. Prefer this for generated/structured content (JSON config, full HTML pages, large code dumps).
|
|
246
|
-
- For small files (≤200 lines, ≤5K tokens) just use \`write_file\` directly — no need to over-engineer.
|
|
191
|
+
- Use existing conversation and tool evidence before gathering more. Inspect only what supports the next decision; do not guess.
|
|
192
|
+
- Keep changes focused. After modifications, run the smallest relevant executable verification and report its result.
|
|
193
|
+
- Use web search only when freshness materially affects the answer.
|
|
194
|
+
${buildCodegraphSection()}
|
|
247
195
|
|
|
248
|
-
##
|
|
249
|
-
-
|
|
250
|
-
-
|
|
196
|
+
## Tool use
|
|
197
|
+
- Go directly to a known path or symbol; use discovery tools only when the location is unknown.
|
|
198
|
+
- Before an edit, use fresh exact file content and its hash. A mutation, compaction, resume, conflict, or external change makes prior edit context stale.
|
|
199
|
+
- Batch only independent calls. Never batch a read with an edit that depends on it; do not repeat overlapping reads or unchanged failed calls.
|
|
200
|
+
- On failure, inspect the full error, change the approach, and retry only with a reason. Drop stale tool output when it no longer supports the task.
|
|
201
|
+
- For generated content over roughly 200 lines or 5K tokens, use small staged writes rather than one oversized tool argument.
|
|
202
|
+
- Use \`ask_human\` only for a genuinely user-owned decision; otherwise choose the safest reversible option and proceed.
|
|
251
203
|
|
|
252
204
|
## Safety & Boundaries
|
|
253
|
-
-
|
|
254
|
-
-
|
|
205
|
+
- Get confirmation before irreversible or outward-facing actions such as deletion, push, production changes, or external requests, unless explicitly authorized.
|
|
206
|
+
- Stay within the authorized workspace and disclose anything skipped or unverifiable.
|
|
255
207
|
|
|
256
208
|
## Project context (dynamic reference)
|
|
257
|
-
${
|
|
258
|
-
|
|
259
|
-
## Session Notepad — working notes file
|
|
260
|
-
${sessionId
|
|
261
|
-
? `You maintain a working notepad at \`.mocode/sessions/${sessionId}/notes.md\` using write_file / edit_file / read_file.`
|
|
262
|
-
: 'You maintain a working notepad (path will be shown after the session starts).'}
|
|
263
|
-
This is your private working surface — write intermediate findings, decisions, open questions,
|
|
264
|
-
and anything you might need to recall later. The file survives context compaction.
|
|
265
|
-
|
|
266
|
-
### WHEN TO WRITE
|
|
267
|
-
The notepad is opt-in for complex work, not a routine task log. Use it only when the task has at least 3 meaningful steps, spans multiple investigation/implementation phases, or contains details that are genuinely at risk of being lost to context compaction.
|
|
268
|
-
|
|
269
|
-
Do NOT create, read, or update the notepad for simple tasks, including:
|
|
270
|
-
- Questions that can be answered directly
|
|
271
|
-
- One-step commands or lookups
|
|
272
|
-
- Small, localized edits that can be completed without intermediate notes
|
|
273
|
-
- Work that only needs a few tool calls and fits comfortably in the current context
|
|
209
|
+
${memorySection}${buildNotepadSection(sessionId)}
|
|
274
210
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
- Before making a consequential design decision → record reasoning and alternatives considered
|
|
278
|
-
- When accumulating data across many tool calls → store concise intermediates
|
|
279
|
-
- When you realize important information may be lost after compaction → write it down
|
|
280
|
-
- After completing a substantial phase → summarize what you learned
|
|
211
|
+
## Session Notepad (\`.mocode/sessions/${sessionId ?? '<id>'}/notes.md\`)
|
|
212
|
+
Use this compact, persistent working surface for tasks with at least three steps or context-loss risk; skip it for simple work.
|
|
281
213
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
- Chose: zod over joi
|
|
293
|
-
- Why: project already uses zod (config/index.ts:8), joi would add a dep
|
|
294
|
-
- Risk: none — zod already in dependency tree
|
|
295
|
-
|
|
296
|
-
## Open Questions
|
|
297
|
-
- [ ] Does the refresh token flow need TTL config too?
|
|
298
|
-
- [ ] Check if rate limiter interacts with auth middleware
|
|
299
|
-
|
|
300
|
-
### RULES
|
|
301
|
-
${sessionId
|
|
302
|
-
? `- Your notepad file path is: \`.mocode/sessions/${sessionId}/notes.md\`. Use this exact path for all read_file/write_file/edit_file operations on your notes.`
|
|
303
|
-
: '- Your notepad file path will be available after the session starts.'}
|
|
304
|
-
- Use write_file to create/overwrite; use edit_file to append or modify sections
|
|
305
|
-
- Keep the file concise — summarize, don't dump raw tool output
|
|
306
|
-
- At task completion, the file can be deleted or left for the user's reference
|
|
307
|
-
- Do NOT use this for cross-session knowledge (use memory_save for that)
|
|
308
|
-
|
|
309
|
-
### PLAN FORMAT (use for any task with ≥3 steps)
|
|
310
|
-
Write the plan as a top-level \`## Plan:\` section. The system extracts this for the status bar chip, so follow the format exactly.
|
|
311
|
-
|
|
312
|
-
## Plan: <task title>
|
|
313
|
-
|
|
314
|
-
Goal: <one-line goal>
|
|
315
|
-
|
|
316
|
-
### Steps
|
|
317
|
-
- [ ] 1. <step 1>
|
|
318
|
-
- [x] 2. <step 2>
|
|
319
|
-
- [ ] 3. <step 3>
|
|
320
|
-
|
|
321
|
-
### Progress
|
|
322
|
-
- <what you learned / did in this phase>
|
|
323
|
-
|
|
324
|
-
Rules:
|
|
325
|
-
- Only ONE active \`## Plan:\` section at a time.
|
|
326
|
-
- Mark steps \`[x]\` as you complete them; append a line to \`### Progress\` after each phase.
|
|
327
|
-
- Before your final response, reconcile every step with the work actually completed, then delete the plan section or rename it to \`## Done: <title>\`.
|
|
328
|
-
- The host hides an unchanged active plan when an agent turn ends as a safety fallback; this does not edit the notepad. Keep updating the plan during execution so live progress remains accurate.
|
|
214
|
+
Keep at most one active plan:
|
|
215
|
+
\`\`\`
|
|
216
|
+
## Plan: <title>
|
|
217
|
+
Goal: <outcome>
|
|
218
|
+
### Steps
|
|
219
|
+
- [ ] 1. <verifiable step>
|
|
220
|
+
### Progress
|
|
221
|
+
- <completed phase and evidence>
|
|
222
|
+
\`\`\`
|
|
223
|
+
Update checkboxes and Progress after each completed phase. Before the final reply, reconcile the plan with actual work, then rename it to \`## Done:\` or remove it. Keep other notes concise and session-specific; use memory for stable cross-session facts.
|
|
329
224
|
|
|
330
225
|
## Termination & Reporting
|
|
331
226
|
- Stop immediately when no more tools are needed; give conclusions directly.
|
|
@@ -394,10 +289,8 @@ export const config = {
|
|
|
394
289
|
theme: process.env.MOCODE_THEME || 'default',
|
|
395
290
|
themeFromShell,
|
|
396
291
|
llmKeysFromShell,
|
|
397
|
-
projectSnapshotEnabled: process.env.MOCODE_PROJECT_SNAPSHOT !== 'false',
|
|
398
292
|
permissionEnabled: process.env.MOCODE_PERMISSION !== 'false',
|
|
399
293
|
permissionNonInteractiveAllow: process.env.MOCODE_PERMISSION_NON_INTERACTIVE_ALLOW === 'true',
|
|
400
|
-
projectSkillEnabled: process.env.MOCODE_PROJECT_SKILL === 'true',
|
|
401
294
|
};
|
|
402
295
|
/**
|
|
403
296
|
* 运行时更新模型相关配置(/model 命令调)。
|
|
@@ -442,6 +335,32 @@ export function updateSubAgentConfig(enabled) {
|
|
|
442
335
|
export function isMemoryEnabled() {
|
|
443
336
|
return config.memoryEnabled;
|
|
444
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* .codegraph/ 索引存在性:仅查 cwd 顶层 .codegraph(目录或文件均可,codegraph CLI
|
|
340
|
+
* 自己会处理内部布局)。用于动态决定是否在系统提示里注入 codegraph skill 用法段——
|
|
341
|
+
* 没有索引的项目不应被提示「用 codegraph」以免 LLM 调出失败。失败静默返 false。
|
|
342
|
+
*/
|
|
343
|
+
export function hasCodegraphIndex() {
|
|
344
|
+
try {
|
|
345
|
+
return fs.existsSync(path.join(process.cwd(), '.codegraph'));
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 当 .codegraph/ 存在时拼进 auto 模式系统提示的 codegraph 段;否则返空串(零成本)。
|
|
353
|
+
* 单一来源:被 buildBasePrompt 注入,确保 basePrompt 不含死字符串。
|
|
354
|
+
*/
|
|
355
|
+
export function buildCodegraphSection() {
|
|
356
|
+
if (!hasCodegraphIndex())
|
|
357
|
+
return '';
|
|
358
|
+
return [
|
|
359
|
+
'',
|
|
360
|
+
'## Codegraph (project has .codegraph/ index)',
|
|
361
|
+
'- For unfamiliar code questions, prefer loading the `codegraph` skill (via use_skill) and querying it with run_command (`codegraph explore <entry>`, `codegraph node <symbol>`). Falls back to read_file / glob / grep when not applicable.',
|
|
362
|
+
].join('\n');
|
|
363
|
+
}
|
|
445
364
|
/**
|
|
446
365
|
* 切换记忆子系统开关(/memory_switch on|off 调)。
|
|
447
366
|
* - 更新 config 单例字段(其它模块下次调 isMemoryEnabled() 即拿新值)。
|
|
@@ -457,34 +376,6 @@ export function updateMemoryConfig(enabled) {
|
|
|
457
376
|
config.memoryEnabled = enabled;
|
|
458
377
|
process.env.MEMORY_ENABLED = enabled ? 'true' : 'false';
|
|
459
378
|
}
|
|
460
|
-
/**
|
|
461
|
-
* 项目专属 Skill 总开关:单一来源。/project_skill、buildBasePrompt、
|
|
462
|
-
* tools/builtins/index.ts 都从这里查。默认 false(零侵入)。
|
|
463
|
-
*/
|
|
464
|
-
export function isProjectSkillEnabled() {
|
|
465
|
-
return config.projectSkillEnabled;
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* 切换项目专属 Skill 开关(/project_skill on|off 调)。
|
|
469
|
-
* - 更新 config 单例字段(其它模块下次调 isProjectSkillEnabled() 即拿新值)。
|
|
470
|
-
* - 同步 process.env.MOCODE_PROJECT_SKILL(下次启动 loadEnvFiles 不被文件回填)。
|
|
471
|
-
* 持久化(写 ~/.mocode/config 的 MOCODE_PROJECT_SKILL 键)由调用方走 writeConfigKeys。
|
|
472
|
-
*/
|
|
473
|
-
export function updateProjectSkillConfig(enabled) {
|
|
474
|
-
config.projectSkillEnabled = enabled;
|
|
475
|
-
process.env.MOCODE_PROJECT_SKILL = enabled ? 'true' : 'false';
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* 切换项目快照开关(/snapshot on|off 调)。
|
|
479
|
-
* - 更新 config 单例字段(其它模块下次读 config.projectSnapshotEnabled 即拿新值:
|
|
480
|
-
* buildSnapshotSection 现拼现读、read-file 每次 execute 现读)。
|
|
481
|
-
* - 同步 process.env.MOCODE_PROJECT_SNAPSHOT(下次启动 loadEnvFiles 不会被文件回填)。
|
|
482
|
-
* 持久化(写 ~/.mocode/config 的 MOCODE_PROJECT_SNAPSHOT 键)由调用方走 updateConfigKey。
|
|
483
|
-
*/
|
|
484
|
-
export function updateSnapshotConfig(enabled) {
|
|
485
|
-
config.projectSnapshotEnabled = enabled;
|
|
486
|
-
process.env.MOCODE_PROJECT_SNAPSHOT = enabled ? 'true' : 'false';
|
|
487
|
-
}
|
|
488
379
|
/** 切换界面与模型回复语言;持久化由 REPL 调用 config/file.ts 完成。 */
|
|
489
380
|
export function updateLanguageConfig(language) {
|
|
490
381
|
setLanguage(language);
|
|
@@ -40,7 +40,7 @@ function callArgs(history, idx) {
|
|
|
40
40
|
function sourceType(tool) {
|
|
41
41
|
if (tool === 'read_file')
|
|
42
42
|
return 'read';
|
|
43
|
-
if (tool === 'grep' || tool === 'glob'
|
|
43
|
+
if (tool === 'grep' || tool === 'glob')
|
|
44
44
|
return 'search';
|
|
45
45
|
if (tool === 'run_command')
|
|
46
46
|
return 'diagnostic';
|
|
@@ -53,7 +53,7 @@ function pathsFromOutput(tool, output) {
|
|
|
53
53
|
if (normalized)
|
|
54
54
|
paths.add(normalized);
|
|
55
55
|
};
|
|
56
|
-
if (tool === 'grep' || tool === '
|
|
56
|
+
if (tool === 'grep' || tool === 'run_command') {
|
|
57
57
|
const expression = /^(.+?\.[A-Za-z0-9]+):(?:\d+|\s*\d+\s*(?:处匹配|matches?))/gmi;
|
|
58
58
|
let match;
|
|
59
59
|
while ((match = expression.exec(output)))
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
// Context Classifier:据工具名(强先验)+ 输出形状(启发)+ 兜底,选 ContextKind。
|
|
2
2
|
//
|
|
3
3
|
// 三级信号:
|
|
4
|
-
// 1) 名字强先验(BY_NAME
|
|
4
|
+
// 1) 名字强先验(BY_NAME 表,覆盖全部内置工具,确定性强)。
|
|
5
5
|
// 2) 形状启发(为 MCP 工具 / 未来工具 / 未登记工具兜底识别)。
|
|
6
6
|
// 3) 兜底 'passthrough'(不认识 = 不动,零行为变化)。
|
|
7
7
|
//
|
|
8
8
|
// 单一事实源风格(仿 tools/constants.ts 的 READ_TOOL_NAMES / PLAN_DISABLED_TOOLS)。
|
|
9
9
|
// 加新工具:在 BY_NAME 加一行;或靠形状启发自动识别。
|
|
10
|
-
/** 工具名 → ContextKind 的强先验表(
|
|
10
|
+
/** 工具名 → ContextKind 的强先验表(覆盖全部内置工具)。 */
|
|
11
11
|
const BY_NAME = {
|
|
12
12
|
// tree:路径列表 → 缩进树
|
|
13
13
|
glob: 'tree',
|
|
14
14
|
// search:file:line 分组
|
|
15
15
|
grep: 'search',
|
|
16
16
|
web_search: 'search',
|
|
17
|
-
// graph:CLI dump → 精炼图
|
|
18
|
-
codegraph: 'graph',
|
|
19
17
|
// log:分级 / 折叠 / 尾偏置
|
|
20
18
|
run_command: 'log',
|
|
21
19
|
// code:保行号(edit_file 依赖,最敏感)
|
|
@@ -31,7 +29,6 @@ const BY_NAME = {
|
|
|
31
29
|
edit_file: 'status',
|
|
32
30
|
write_file: 'status',
|
|
33
31
|
ask_human: 'status',
|
|
34
|
-
switch_mode: 'status',
|
|
35
32
|
drop_context: 'status',
|
|
36
33
|
memory_save: 'status',
|
|
37
34
|
memory_update: 'status',
|
|
@@ -47,7 +44,7 @@ function classifyByShape(output) {
|
|
|
47
44
|
// file:line: content 形(grep 风格)
|
|
48
45
|
if (/^[^\n:]+:\d+:[^\n]*$/m.test(output))
|
|
49
46
|
return 'search';
|
|
50
|
-
// [退出码 N] 前缀(run_command
|
|
47
|
+
// [退出码 N] 前缀(run_command 风格)
|
|
51
48
|
if (/^\[退出码 \d+\]/m.test(output))
|
|
52
49
|
return 'log';
|
|
53
50
|
// 路径列表:多行都是含分隔符的相对路径(glob 风格)
|
|
@@ -6,8 +6,9 @@ export function stripAnsi(s) {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* 折叠连续空行(只含空白字符的行)≥ threshold → 单个空行。
|
|
9
|
-
* 用于 prose / source dump(web_fetch / use_skill / task
|
|
9
|
+
* 用于 prose / source dump(web_fetch / use_skill / task):HTML→文本与 markdown 常留多余空行,
|
|
10
10
|
* 多空行与单空行语义等价,折叠无损。默认 threshold=3(只动真正过量的空行,常见 ≤2 空行不动)。
|
|
11
|
+
* 注:codegraph 已 skill 化(用 run_command 调 CLI,落到 log encoder),graph kind 现仅作保留值。
|
|
11
12
|
*
|
|
12
13
|
* 注意:用 `.trim() === ''` 判空——故 read_file 的 ` 2\t`(行号前缀 + tab,trim 后剩 `2`)不会被
|
|
13
14
|
* 视作空行。read_file 的空行折叠见 code encoder(前缀感知)。
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
// Phase 2:tree / search / log / table / memory(高价值低风险)。
|
|
5
5
|
// Phase 2.5:code / graph / doc / summary(覆盖剩余有 encoder 的 kind)。
|
|
6
6
|
// - code(read_file):仅折叠 ≥3 连续空行,行号保真(edit_file 依赖)。
|
|
7
|
-
// - graph(
|
|
8
|
-
//
|
|
7
|
+
// - graph(保留 kind,暂无 builtin 工具直接命中)/ doc(web_fetch, use_skill)/ summary(task):
|
|
8
|
+
// 去 ANSI + 折叠空行,保守不重构结构。
|
|
9
|
+
// - status(edit/write/ask_human/mem 增删改)无 encoder:本就是一行,无需编码。
|
|
9
10
|
//
|
|
10
11
|
// 加 encoder:新建 encoders/xxx.ts 导出 ContextEncoder,在此数组加一行。无需动 agent / llm / core。
|
|
11
12
|
import { passthroughEncoder } from './passthrough.js';
|
|
@@ -15,7 +16,6 @@ import { commandEncoder } from './command.js';
|
|
|
15
16
|
import { tableEncoder } from './table.js';
|
|
16
17
|
import { memoryEncoder } from './memory.js';
|
|
17
18
|
import { codeEncoder } from './code.js';
|
|
18
|
-
import { graphEncoder } from './graph.js';
|
|
19
19
|
import { docEncoder } from './doc.js';
|
|
20
20
|
import { summaryEncoder } from './summary.js';
|
|
21
21
|
export const builtinEncoders = [
|
|
@@ -26,7 +26,6 @@ export const builtinEncoders = [
|
|
|
26
26
|
tableEncoder,
|
|
27
27
|
memoryEncoder,
|
|
28
28
|
codeEncoder,
|
|
29
|
-
graphEncoder,
|
|
30
29
|
docEncoder,
|
|
31
30
|
summaryEncoder,
|
|
32
31
|
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Observation Lifecycle Engine:tool 消息的「观察者生命周期」状态机。
|
|
2
2
|
//
|
|
3
3
|
// 在 Relevance Pruner 之上的第二层被动裁剪。Relevance Pruner 只管 read_file 的「同 path 新旧替换 +
|
|
4
|
-
// mutation 覆写」,本层补足「grep/glob/
|
|
4
|
+
// mutation 覆写」,本层补足「grep/glob/web_search/web_fetch 这类观察类工具」的引用追踪。
|
|
5
5
|
//
|
|
6
6
|
// 四态机(LIVE → REFERENCED → OBSOLETE → STUB):
|
|
7
7
|
// - LIVE:刚 push 进 history 的工具结果,尚未被任何下游工具消费。
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// - STUB:已被替换为存根(物理上 content 变成「⌦[无消费者:...]」)。
|
|
11
11
|
//
|
|
12
12
|
// 观察类工具两阶段衰减(避免误伤):
|
|
13
|
-
// - grep/glob/
|
|
13
|
+
// - grep/glob/web_search/web_fetch 等「观察/检索类」工具两阶段衰减:
|
|
14
14
|
// Phase 1(10 步):LIVE → REFERENCED,保留完整内容(返回多个候选,剩余候选可能后续被消费)。
|
|
15
15
|
// Phase 2(+5 步):REFERENCED → DIGEST,替换为摘要存根(保留文件列表+命中数+参数,丢弃详情),
|
|
16
16
|
// 释放 ~90% token。states 仍为 REFERENCED,不引入新状态。
|
|
@@ -34,7 +34,6 @@ import { canonicalizePath, extractPath, isToolResultSuccess, lastUserIndex, toTe
|
|
|
34
34
|
const OBSERVER_TOOLS = new Set([
|
|
35
35
|
'grep',
|
|
36
36
|
'glob',
|
|
37
|
-
'codegraph',
|
|
38
37
|
'web_search',
|
|
39
38
|
'web_fetch',
|
|
40
39
|
]);
|
|
@@ -63,7 +62,6 @@ const OBSERVER_DIGEST_AGE = 5;
|
|
|
63
62
|
* - grep:content 是 `file:line: ...` 行,提取每行的 file 段(只保留绝对路径形态或与 pattern 匹配的)。
|
|
64
63
|
* 简化:把所有看起来像「相对路径 + 文件名」的 token 抽出,留 narrow。
|
|
65
64
|
* - glob:content 是路径列表,按行 / 空格拆。
|
|
66
|
-
* - codegraph:content 里通常含 `path/to/file.ts:line`,按行拆,提 file 段。
|
|
67
65
|
*
|
|
68
66
|
* 返回值:命中过的 path 字符串集合(已 dedup)。失败返空集。 */
|
|
69
67
|
function extractProducerPaths(toolName, content) {
|
|
@@ -99,14 +97,6 @@ function extractProducerPaths(toolName, content) {
|
|
|
99
97
|
addPath(t);
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
|
-
else if (toolName === 'codegraph') {
|
|
103
|
-
// codegraph 输出通常 `path\to\file.ts:line:col symbol` 或类似;按行 + 冒号分隔。
|
|
104
|
-
for (const line of content.split(/\r?\n/)) {
|
|
105
|
-
const m = /^([^\s:][^:]*?\.[A-Za-z0-9]+):(\d+):/.exec(line);
|
|
106
|
-
if (m)
|
|
107
|
-
addPath(m[1]);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
100
|
else if (toolName === 'web_search' || toolName === 'web_fetch') {
|
|
111
101
|
// 网络结果不在文件系统路径范畴;不参与 producer 路径索引(避免误匹配)。
|
|
112
102
|
}
|
|
@@ -287,7 +277,7 @@ export class LifecycleEngine {
|
|
|
287
277
|
})();
|
|
288
278
|
const path = canonicalizePath(extractPath(argsRaw));
|
|
289
279
|
if (path) {
|
|
290
|
-
// 1) 找该 path 的所有上游 producer(grep/glob/
|
|
280
|
+
// 1) 找该 path 的所有上游 producer(grep/glob/web_search/web_fetch)→ 标 REFERENCED。
|
|
291
281
|
const producers = this.producersByPath.get(path);
|
|
292
282
|
if (producers) {
|
|
293
283
|
for (const pidx of producers) {
|
|
@@ -478,16 +468,6 @@ export class LifecycleEngine {
|
|
|
478
468
|
} })();
|
|
479
469
|
return `${DIGEST_PREFIX}glob(${args}) — 历史结果文件 ${files},${origLen}→摘要]\n如需完整列表或最新信息请重新调用 glob`;
|
|
480
470
|
}
|
|
481
|
-
case 'codegraph': {
|
|
482
|
-
const args = (() => { try {
|
|
483
|
-
const a = JSON.parse(argsRaw);
|
|
484
|
-
return `"${a.query ?? ''}"`;
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
return '...';
|
|
488
|
-
} })();
|
|
489
|
-
return `${DIGEST_PREFIX}codegraph(${args}) — 历史结果文件 ${files},${origLen}→摘要]\n如需完整详情或最新信息请重新调用 codegraph`;
|
|
490
|
-
}
|
|
491
471
|
case 'web_search': {
|
|
492
472
|
// 统计结果数
|
|
493
473
|
let resultCount = 0;
|