mocode-ai 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +196 -188
  2. package/dist/agent/core.js +302 -0
  3. package/dist/agent/index.js +62 -249
  4. package/dist/agent/mode.js +46 -0
  5. package/dist/agent/spawn.js +123 -0
  6. package/dist/commands/config.js +8 -26
  7. package/dist/config/file.js +48 -0
  8. package/dist/config/index.js +73 -30
  9. package/dist/llm/index.js +11 -9
  10. package/dist/memory/index.js +2 -2
  11. package/dist/memory/reflect.js +11 -11
  12. package/dist/memory/store.js +4 -4
  13. package/dist/repl/index.js +264 -108
  14. package/dist/session/compact.js +1 -1
  15. package/dist/skills/index.js +2 -2
  16. package/dist/tools/builtins/ask-human.js +7 -7
  17. package/dist/tools/builtins/codegraph.js +113 -0
  18. package/dist/tools/builtins/edit-file.js +3 -3
  19. package/dist/tools/builtins/glob.js +3 -2
  20. package/dist/tools/builtins/grep.js +4 -3
  21. package/dist/tools/builtins/index.js +7 -1
  22. package/dist/tools/builtins/memory-forget.js +2 -2
  23. package/dist/tools/builtins/memory-list.js +2 -2
  24. package/dist/tools/builtins/memory-save.js +7 -7
  25. package/dist/tools/builtins/memory-search.js +4 -4
  26. package/dist/tools/builtins/memory-update.js +6 -6
  27. package/dist/tools/builtins/read-file.js +5 -4
  28. package/dist/tools/builtins/run-command.js +40 -7
  29. package/dist/tools/builtins/switch-mode.js +46 -0
  30. package/dist/tools/builtins/task.js +62 -0
  31. package/dist/tools/builtins/use-skill.js +2 -2
  32. package/dist/tools/builtins/web-fetch.js +16 -3
  33. package/dist/tools/builtins/web-search.js +6 -6
  34. package/dist/tools/builtins/write-file.js +3 -3
  35. package/dist/tools/constants.js +16 -0
  36. package/dist/tools/registry.js +11 -4
  37. package/dist/ui/content.js +25 -17
  38. package/dist/ui/diff.js +28 -26
  39. package/dist/ui/intervention.js +24 -3
  40. package/dist/ui/layout.js +295 -81
  41. package/dist/ui/markdown.js +607 -0
  42. package/dist/ui/mouse.js +93 -0
  43. package/dist/ui/prompt.js +259 -5
  44. package/dist/ui/render.js +1 -1
  45. package/dist/ui/theme.js +146 -13
  46. package/package.json +2 -2
@@ -28,6 +28,8 @@ function loadEnvFiles() {
28
28
  process.env[k] = v;
29
29
  }
30
30
  }
31
+ // 在 loadEnvFiles 回填前捕获:MOCODE_THEME 是否由 shell 设置(决定 /theme 写文件是否下次启动生效)。
32
+ const themeFromShell = process.env.MOCODE_THEME !== undefined;
31
33
  loadEnvFiles();
32
34
  function requireEnv(key) {
33
35
  const v = process.env[key];
@@ -37,42 +39,79 @@ function requireEnv(key) {
37
39
  }
38
40
  return v;
39
41
  }
40
- const SYSTEM_PROMPT = `你是 mocode,一个终端编码 agent。你以"思考 → 调用工具 → 观察结果 → 再思考"的循环完成编程任务,直到问题解决。面向中文用户,回复用中文。
42
+ const PLATFORM_NOTE = (() => {
43
+ if (process.platform === 'win32') {
44
+ return `## Environment (Windows)
45
+ - You are on Windows; run_command runs commands via cmd.exe (/c). Unix shell builtins are NOT available here.
46
+ - Windows equivalents: which→where, cat→type, ls→dir, rm→del/rd, cp→copy, mv→move. cmd.exe uses %VAR% (not $VAR); pipes (|) and redirects (>, >>) work, but no $(...) command substitution or backticks.
47
+ - 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.
48
+ - Prefer the dedicated tools (read_file/glob/grep) over shell equivalents — they're cross-platform and already wired in.`;
49
+ }
50
+ if (process.platform === 'darwin') {
51
+ return `## Environment (macOS)
52
+ - You are on macOS; run_command runs via bash -c (user default shell may be zsh). BSD coreutils, not GNU.
53
+ - 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).
54
+ - Prefer the dedicated tools (read_file/glob/grep) over shell equivalents — they sidestep BSD/GNU differences.`;
55
+ }
56
+ return `## Environment (Linux/Unix)
57
+ - You are on ${process.platform}; run_command runs via bash -c. GNU coreutils — standard POSIX/GNU shell syntax is safe.
58
+ - 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.`;
59
+ })();
60
+ const SYSTEM_PROMPT = `You are mocode, a terminal coding agent. You complete programming tasks through a "think → call tool → observe result → think again" loop until the problem is solved. Reply to the user in Chinese.
61
+
62
+ ${PLATFORM_NOTE}
63
+
64
+ ## Workflow
65
+ - Understand before acting: when unsure about requirements or code state, explore first; don't assume.
66
+ - Small steps: break tasks into verifiable sub-steps. Before each step, think clearly about what to change and why.
67
+ - Verify after change: run typecheck / tests / build via run_command to confirm it works. Never claim done without verification.
41
68
 
42
- ## 工作流
43
- - 先理解再动手:不确定需求或代码现状时,先 read_file / grep / glob 探索,不凭空假设。
44
- - 小步推进:任务拆成可验证的子步骤,每步动手前想清楚改什么、为什么。
45
- - 改完即验证:用 run_command 跑 typecheck / 测试 / 构建确认有效,未验证不声称完成。
69
+ ## Tool Guidelines
70
+ - See each tool's own description for parameters and usage; this section covers selection strategy and pitfalls only.
71
+ - **Prefer codegraph for code exploration**: when understanding/locating code, tracing call chains, or assessing impact of changes, if a .codegraph/ index exists, use the codegraph tool first (explore to query by question, node to look up a single symbol) — it returns relevant source + call paths in one shot, more accurate and economical than piecing together via read_file/grep. Fall back to read_file / grep / glob only when codegraph is unavailable (no index), misses, you need to see just-changed content, or you're editing a single known small file. Build the index first with \`codegraph init\` if none exists.
72
+ - Before editing code, read_file to confirm actual content (with line numbers); don't guess from memory.
73
+ - For local edits use edit_file: old_string must be unique and match exactly (including indentation/newlines); include surrounding context lines to ensure uniqueness. Use write_file for new files or full rewrites.
74
+ - Use glob to find file paths, grep to search content; don't use run_command to pipe cat / sed / find / grep.
75
+ - run_command runs per platform (cmd on Windows, bash elsewhere); state intent before running commands with side effects (deleting files, installing packages, git push, resets, etc.).
76
+ - Use web_search for information beyond training data (new versions, news, real-time data, latest APIs); don't answer potentially outdated info from memory.
77
+ - Use web_fetch to read a specific URL (a link from search results, or a URL given by the user); it only fetches static HTML — if a JS-rendered page yields no body, switch to web_search (its results include cleaned body text).
78
+ - Call ask_human when you hit a decision point requiring user input (multiple implementation approaches, unclear intent, or needing extra info to proceed) — list options for the user to pick (they can also choose "custom input" to answer freely). Don't call it frequently when the task is clear and you can decide yourself; if the user cancels, switch approach or proceed with available info — don't re-ask the same question.
46
79
 
47
- ## 工具准则
48
- - 参数与用法见各工具自带说明;这里只讲选择策略与易踩坑。
49
- - 改代码前先 read_file 确认实际内容(带行号),不凭记忆猜。
50
- - 局部改用 edit_file:old_string 须唯一且精确匹配(含缩进/换行),多带上下文行确保唯一;新建或整体重写用 write_file。
51
- - 找路径用 glob,找内容用 grep;不要用 run_command 拼 cat / sed / find / grep。
52
- - 若当前目录存在 .codegraph/(已建代码索引),理解/定位代码、查调用链、看改动影响面时必须先 codegraph 再动手:run_command 跑 codegraph explore "<符号或问题>"(一次拿相关符号源码 + 调用路径)或 codegraph node <符号或文件>(单符号源码 + 调用者)。不要逐文件 glob/read_file/grep 去拼凑理解——那是 codegraph 已替你做完的事。仅当 codegraph 找不到、未索引、要看刚改的最新内容、或改单个已知小文件时才用 read_file/grep/glob。详见 use_skill codegraph。
53
- - run_command 按平台执行(Win 用 cmd、其他用 bash);有副作用的命令(删文件、装包、git push、重置等)执行前先简述意图。
54
- - 需要训练数据之外的最新信息(新版本、新闻、实时数据、最新 API)时用 web_search 联网搜索,不要凭记忆答可能过时的内容。
55
- - 要读取某个具体 URL 的内容(搜索结果里的链接、用户给的 URL)时用 web_fetch 抓取;它只抓静态 HTML,JS 渲染页面拿不到正文时改用 web_search(其结果自带清洗后的正文)。
56
- - 遇到需要用户决策的岔路(多种实现方案、不确定用户意图、需要额外信息才能继续),调 ask_human 列出选项让用户选(用户也可选"自定义输入"自由作答)。不要在任务明确、能自行决定时频繁打扰用户;用户取消后换方案或基于已有信息推进,不原地重复问。
80
+ ## Failure Handling
81
+ - Tools return errors as strings (edit_file no match or non-unique, run_command non-zero exit, etc.). Analyze the root cause, adjust, then retry — don't resend the same call verbatim.
82
+ - When a command errors, read the actual output before judging; don't skip it.
57
83
 
58
- ## 失败处理
59
- - 工具以字符串返回错误(edit_file 未匹配或不唯一、run_command 非零退出码等)。先分析根因,调整后重试,不要原样重发同一条调用。
60
- - 命令报错时把真实输出读进去再判断,别跳过。
84
+ ## Safety & Boundaries
85
+ - Confirm with the user before irreversible or outward-facing operations (delete, overwrite existing files, push, request external services), unless explicitly authorized.
86
+ - Operate only within authorized scope; when unsure, ask — don't guess.
61
87
 
62
- ## 安全与边界
63
- - 不可逆或外向操作(删除、覆盖既有文件、推送、请求外部服务)执行前向用户确认,除非已获明确授权。
64
- - 只在授权范围内操作;不确定就问,别猜。
88
+ ## Memory (cross-session long-term facts)
89
+ - A "memory index" (id/title/summary only) is injected into the system prompt. Retrieve full body via memory_search (pass id or keyword); use memory_list to see the entire index.
90
+ - Store non-obvious, cross-session-useful facts/decisions/pitfalls (architecture conventions, gotchas, user preferences, decisions made) with memory_save — only long-term stable items, not current bugs / temp files / undecided TODOs.
91
+ - If an existing memory is outdated or contradicts new facts, correct it in-place with memory_update(id, …) (don't create a duplicate); archive clearly-stale ones with memory_forget(id).
92
+ - Before saving, memory_search to check for an existing similar entry to avoid duplicates. Better to store less than to store trivially correct information.
93
+ - A background reflection pass periodically mines and organizes memories from the session (no manual action needed), but key facts you proactively save are more reliable.
65
94
 
66
- ## 记忆(跨会话长期事实)
67
- - 系统提示已注入「记忆索引」(仅 id/标题/摘要)。需要某条正文时调 memory_search(传 id 或关键词)取;memory_list 看全部索引。
68
- - 遇到非显然、跨会话有用的事实/决策/坑(架构约定、易踩坑、用户偏好、已做决策),用 memory_save 存——只存长期稳定项,不存当前 bug / 临时文件 / 未决 TODO。
69
- - 发现已存记忆过时或与新事实矛盾,用 memory_update(id, …) 原地纠正(别新建重复条);明确失效的用 memory_forget(id) 归档。
70
- - 存前先 memory_search 看是否已有同类条,避免重复。宁可少记,不记正确废话。
71
- - 后台反思 pass 会定期从会话里挖掘并整理记忆(无需你手动),但你主动存的关键事实更可靠。
95
+ ## Plan vs Auto modes
96
+ - Default is AUTO mode: you research and execute with all tools (read/edit/run_command/memory/web/skills).
97
+ - 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.
98
+
99
+ ## Termination & Reporting
100
+ - Stop immediately when no more tools are needed; give conclusions directly.
101
+ - Report honestly: say success when successful, say where you're stuck when failing, and mention anything skipped. Reference code in "path:line" format (e.g., src/index.ts:42). Keep it concise.`;
102
+ /**
103
+ * plan 模式追加到系统提示末尾的指令(切到 plan 模式时由 repl 拼进 history[0])。
104
+ * 与 SYSTEM_PROMPT 同语种(英文),指示:只读探查、产出步骤化计划、不执行、审批后回 auto。
105
+ */
106
+ export const PLAN_MODE_SUFFIX = `
72
107
 
73
- ## 终止与汇报
74
- - 无需更多工具时立即停止,直接给结论。
75
- - 如实汇报:成功说成功,失败说卡在哪,跳过的也要说。引用代码用 "path:行号" 格式(如 src/index.ts:42)。保持简洁。`;
108
+ ## ⛯ PLAN MODE (active now)
109
+ You are in PLAN mode: investigate and design only — do NOT execute or change anything.
110
+ - Your editing / command / memory-write tools (write_file, edit_file, run_command, memory_save, memory_update, memory_forget) have been REMOVED from your tool list. Use only the read-only tools available to you (read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human, memory_search, memory_list) to investigate.
111
+ - Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. Prefer codegraph when a .codegraph/ index exists.
112
+ - Then produce a clear, actionable implementation plan: files to change (with paths), what to change in each and why, the ordered steps, edge cases to handle, and how to verify (typecheck / tests / build). Be specific enough to execute against.
113
+ - Present the plan as your final reply and STOP, unless the user explicitly asked you to "plan first then execute" / "先 plan 再 auto" / autonomous execution: in that case, after presenting the plan, call the switch_mode tool with mode="auto" to switch back to auto mode WITHIN THE SAME TURN and continue implementing the plan yourself (your write/edit/command/memory-write tools become available again immediately). The user will see no approval prompt because you self-switched.
114
+ - If the user entered plan mode manually (via /plan or Shift+Tab) for a safety review and did NOT ask for autonomous execution, do NOT call switch_mode — present the plan and STOP; the user will approve via a prompt and execution happens in a follow-up turn.`;
76
115
  export const config = {
77
116
  baseURL: requireEnv('LLM_BASE_URL'),
78
117
  apiKey: requireEnv('LLM_API_KEY'),
@@ -85,7 +124,11 @@ export const config = {
85
124
  autoCompact: process.env.AUTO_COMPACT !== 'false',
86
125
  autoReflect: process.env.AUTO_REFLECT !== 'false',
87
126
  reflectEveryN: Number(process.env.REFLECT_EVERY_N) || 5,
127
+ maxSteps: Number(process.env.MAX_STEPS) || 200,
128
+ subAgentMaxSteps: Number(process.env.SUB_AGENT_MAX_STEPS) || 50,
88
129
  sessionDir: path.join(process.cwd(), '.mocode', 'sessions'),
89
130
  searchApiKey: process.env.ANYSEARCH_API_KEY,
90
131
  searchBaseUrl: process.env.ANYSEARCH_BASE_URL || 'https://api.anysearch.com',
132
+ theme: process.env.MOCODE_THEME || 'default',
133
+ themeFromShell,
91
134
  };
package/dist/llm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import OpenAI from 'openai';
2
2
  import { config } from '../config/index.js';
3
3
  import { tools } from '../tools/registry.js';
4
+ import { PLAN_DISABLED_TOOLS } from '../tools/constants.js';
4
5
  const client = new OpenAI({
5
6
  baseURL: config.baseURL,
6
7
  apiKey: config.apiKey,
@@ -16,18 +17,23 @@ export const chatTools = tools.map((t) => ({
16
17
  },
17
18
  }));
18
19
  /**
19
- * 流式调一次 LLM:增量回调文本 / 思考,内部累加 tool_calls 片段。
20
- * 思考内容走 delta.reasoning_content(DeepSeek / GLM / Qwen 等推理模型,
21
- * SDK 类型无此字段,用 as any 取;不支持的模型则无思考,只流文本)。
20
+ * plan 模式用的受限工具 schema:剔除写盘 / 命令 / 记忆写入类(PLAN_DISABLED_TOOLS),
21
+ * 模型在 plan 模式下只看得到只读工具 → 调不到会改文件的工具。runAgent 在 plan 模式传给 chat()。
22
+ */
23
+ export const planChatTools = chatTools.filter((t) => !PLAN_DISABLED_TOOLS.has(t.function.name));
24
+ /**
25
+ * 流式调一次 LLM:增量回调文本,内部累加 tool_calls 片段。
22
26
  * tool_calls 跨 chunk 按 index 累加(id / name / arguments 拼接)。
23
27
  * include_usage 时末尾 chunk 携带 usage,先读再 continue(末尾 chunk 无 delta)。
24
28
  */
25
- export async function chat(messages, handlers = {}, signal) {
29
+ export async function chat(messages, handlers = {}, signal,
30
+ /** 覆盖默认工具 schema;plan 模式传 planChatTools(只读子集),缺省=全量 chatTools。 */
31
+ toolsOverride) {
26
32
  // signal 透传给 SDK 第二参(RequestOptions);abort 后 for await 抛错,chat 不 catch,透传 runAgent 处理。
27
33
  const stream = await client.chat.completions.create({
28
34
  model: config.model,
29
35
  messages,
30
- tools: chatTools,
36
+ tools: toolsOverride ?? chatTools,
31
37
  stream: true,
32
38
  ...(config.maxTokens ? { max_tokens: config.maxTokens } : {}),
33
39
  ...(config.includeUsage ? { stream_options: { include_usage: true } } : {}),
@@ -48,10 +54,6 @@ export async function chat(messages, handlers = {}, signal) {
48
54
  const delta = chunk.choices[0]?.delta;
49
55
  if (!delta)
50
56
  continue; // 末尾 usage-only chunk 等无 delta
51
- // 思考内容(非标准字段,SDK 类型无)
52
- const reasoning = delta.reasoning_content ?? delta.reasoning;
53
- if (reasoning)
54
- handlers.onThinking?.(reasoning);
55
57
  if (delta.content) {
56
58
  content += delta.content;
57
59
  hasContent = true;
@@ -38,8 +38,8 @@ export function buildMemorySection() {
38
38
  return [
39
39
  '',
40
40
  '',
41
- '## 项目记忆(MOCODE.md)',
42
- '以下是项目记忆(架构 / 约定 / 命令等跨会话长期事实),据此行动:',
41
+ '## Project Memory (MOCODE.md)',
42
+ 'The following is project memory (architecture / conventions / commands and other cross-session long-term facts). Act accordingly:',
43
43
  mem,
44
44
  ].join('\n');
45
45
  }
@@ -92,17 +92,17 @@ function buildMemorySample() {
92
92
  .join('\n---\n');
93
93
  }
94
94
  const TYPES = 'decision | fact | pitfall | reference | feedback';
95
- const REFLECT_SYS = `你是 mocode 的记忆反思器。审阅近期会话与现有记忆,产出**仅**值得长期记住的更新。
96
- 严格输出 JSON(无 markdown 代码块、无解释文字):{"saves":[{"type":"...","name":"...","summary":"...","body":"..."}],"updates":[{"id":"...","reason":"...","summary":"...","body":"..."}],"forgets":[{"id":"...","reason":"..."}]}
97
- 空数组合法(无可记则三个数组都空)。
98
- 规则:
99
- ① 只记非显然、跨会话有用的事实/决策/坑;不记当前 bug、临时文件、未决 TODO、易变项;
100
- ② 宁可少记,不记正确废话(如"保持简洁");
101
- ③ updates/forgets 的 id 必须来自下方「现有记忆」列表;不在此列的不要编 id;
102
- ④ saves 的 name 须简洁且与现有不撞;type ∈ {${TYPES}};
103
- ⑤ 若现有记忆与新事实矛盾或过时,update 旧条(改 summary/body)而非新建重复条;
104
- ⑥ forgets 用于明显已失效 / 被新条取代的记忆(归档,非硬删)。`;
105
- const REFLECT_USER = (transcript, sample) => `## 近期会话\n${transcript}\n\n## 现有记忆\n${sample}\n\n产出 JSON:`;
95
+ const REFLECT_SYS = `You are mocode's memory reflector. Review the recent session and existing memories, producing **only** updates worth remembering long-term.
96
+ Output strictly JSON (no markdown code blocks, no explanatory text): {"saves":[{"type":"...","name":"...","summary":"...","body":"..."}],"updates":[{"id":"...","reason":"...","summary":"...","body":"..."}],"forgets":[{"id":"...","reason":"..."}]}
97
+ Empty arrays are valid (if nothing is worth saving, all three arrays are empty).
98
+ Rules:
99
+ ① Only store non-obvious, cross-session-useful facts/decisions/pitfalls; do not store current bugs, temp files, undecided TODOs, or volatile items;
100
+ ② Better to store less than to store trivially correct info (e.g. "keep it concise");
101
+ ③ ids in updates/forgets must come from the "existing memories" list below; do not fabricate ids not listed there;
102
+ ④ names in saves must be concise and not collide with existing ones; type ∈ {${TYPES}};
103
+ ⑤ If an existing memory contradicts new facts or is outdated, update the old entry (modify summary/body) rather than creating a duplicate;
104
+ ⑥ forgets are for memories clearly stale / superseded by a new entry (archive, not hard-delete).`;
105
+ const REFLECT_USER = (transcript, sample) => `## Recent session\n${transcript}\n\n## Existing memories\n${sample}\n\nProduce JSON:`;
106
106
  function parsePlan(content) {
107
107
  if (!content)
108
108
  return null;
@@ -325,14 +325,14 @@ export function buildMemoryIndexSection() {
325
325
  const shown = active.slice(0, MAX_INDEX_ENTRIES);
326
326
  const lines = shown.map((e) => `- ${e.id}: ${e.name} — ${e.summary} (${e.type})`);
327
327
  const tail = active.length > MAX_INDEX_ENTRIES
328
- ? `\n\n…(共 ${active.length} 条,只显前 ${MAX_INDEX_ENTRIES};用 memory_search <id 或关键词> 查更多)`
328
+ ? `\n\n…(${active.length} total, showing first ${MAX_INDEX_ENTRIES}; use memory_search <id or keyword> for more)`
329
329
  : '';
330
330
  return [
331
331
  '',
332
332
  '',
333
- '## 记忆索引(按需 memory_search 取详情)',
334
- '以下是已保存的记忆条目(标题/摘要)。需要正文时调 memory_search(传 id 或关键词);用 memory_list 看全部,'
335
- + 'memory_update 改、memory_forget 忘。本列表为启动快照,会话期间新增的不在此——用 memory_list/memory_search 查最新。',
333
+ '## Memory Index (retrieve full body via memory_search)',
334
+ 'The following are saved memory entries (title/summary only). Retrieve full body via memory_search (pass id or keyword); use memory_list to see all,'
335
+ + ' memory_update to modify, memory_forget to archive. This list is a startup snapshot; entries added during the session are not listed here — use memory_list/memory_search to find them.',
336
336
  ...lines,
337
337
  tail,
338
338
  ].join('\n');