mocode-ai 1.3.9 → 1.4.0

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 (134) hide show
  1. package/README.md +78 -70
  2. package/README.zh-CN.md +81 -73
  3. package/bin/mocode-agent-host.js +1 -1
  4. package/dist/agent/core.js +226 -293
  5. package/dist/agent/index.js +14 -10
  6. package/dist/agent/mode.js +3 -3
  7. package/dist/agent/runtime-context.js +45 -0
  8. package/dist/agent/spawn.js +101 -86
  9. package/dist/agent/tool-helpers.js +134 -0
  10. package/dist/agent/trace-state.js +118 -0
  11. package/dist/agents/coordinator.js +6 -1
  12. package/dist/changeset/index.js +9 -8
  13. package/dist/config/index.js +109 -77
  14. package/dist/config/presets.js +3 -1
  15. package/dist/config/profiles.js +125 -0
  16. package/dist/context/age-aware.js +2 -4
  17. package/dist/context/artifacts.js +6 -13
  18. package/dist/context/budget.js +6 -8
  19. package/dist/context/classifier.js +1 -2
  20. package/dist/context/encoders/code.js +2 -6
  21. package/dist/context/encoders/command.js +1 -3
  22. package/dist/context/encoders/search.js +1 -3
  23. package/dist/context/index.js +1 -1
  24. package/dist/context/lifecycle.js +5 -3
  25. package/dist/context/pipeline.js +1 -1
  26. package/dist/context/relevance.js +2 -4
  27. package/dist/context/token-calibration.js +13 -18
  28. package/dist/host/stdio.js +73 -12
  29. package/dist/i18n/index.js +96 -44
  30. package/dist/index.js +1 -1
  31. package/dist/llm/index.js +39 -23
  32. package/dist/llm/provider.js +23 -0
  33. package/dist/llm/providers/anthropic.js +12 -18
  34. package/dist/llm/tool-schema.js +48 -0
  35. package/dist/mcp/client.js +70 -25
  36. package/dist/mcp/config.js +1 -3
  37. package/dist/mcp/index.js +3 -1
  38. package/dist/memory/reflect.js +3 -6
  39. package/dist/memory/store.js +10 -12
  40. package/dist/permissions/index.js +66 -22
  41. package/dist/pet/bridge.js +2 -2
  42. package/dist/repl/commands/appearance.js +72 -0
  43. package/dist/repl/commands/compact.js +114 -0
  44. package/dist/repl/commands/context.js +17 -0
  45. package/dist/repl/commands/image.js +71 -0
  46. package/dist/repl/commands/memory.js +51 -0
  47. package/dist/repl/commands/mode.js +47 -0
  48. package/dist/repl/commands/model.js +435 -0
  49. package/dist/repl/commands/pet.js +70 -0
  50. package/dist/repl/commands/registry.js +46 -0
  51. package/dist/repl/commands/session.js +102 -0
  52. package/dist/repl/commands/skill.js +95 -0
  53. package/dist/repl/commands/system.js +202 -0
  54. package/dist/repl/commands/tool-group.js +233 -0
  55. package/dist/repl/commands/types.js +4 -0
  56. package/dist/repl/commands.js +324 -0
  57. package/dist/repl/index.js +5 -2577
  58. package/dist/repl/message-format.js +151 -0
  59. package/dist/repl/running-input.js +147 -0
  60. package/dist/repl/runtime.js +816 -0
  61. package/dist/repl/status-bar.js +161 -0
  62. package/dist/rollback/index.js +44 -14
  63. package/dist/runtime/browser-manager.js +1 -1
  64. package/dist/runtime/dev-server-manager.js +1 -1
  65. package/dist/runtime/input-injector.js +313 -0
  66. package/dist/runtime/screen-capture.js +89 -0
  67. package/dist/runtime/screen-pipeline.js +223 -0
  68. package/dist/sandbox/index.js +1 -1
  69. package/dist/sandbox/policy.js +8 -2
  70. package/dist/session/compact.js +22 -34
  71. package/dist/session/index.js +2 -2
  72. package/dist/session/notes.js +39 -5
  73. package/dist/session/persist.js +14 -8
  74. package/dist/session/scheduler.js +3 -4
  75. package/dist/session/trace-metrics.js +6 -3
  76. package/dist/skills/activation.js +10 -5
  77. package/dist/skills/discover.js +5 -17
  78. package/dist/skills/index.js +1 -1
  79. package/dist/skills/runner.js +28 -44
  80. package/dist/skills/toolmap.js +5 -2
  81. package/dist/tools/builtins/browser.js +17 -3
  82. package/dist/tools/builtins/computer.js +322 -0
  83. package/dist/tools/builtins/dev-server.js +1 -3
  84. package/dist/tools/builtins/edit-file.js +21 -7
  85. package/dist/tools/builtins/grep.js +1 -1
  86. package/dist/tools/builtins/index.js +28 -25
  87. package/dist/tools/builtins/memory-forget.js +1 -1
  88. package/dist/tools/builtins/memory-graph.js +4 -7
  89. package/dist/tools/builtins/memory-list.js +2 -6
  90. package/dist/tools/builtins/memory-save.js +8 -2
  91. package/dist/tools/builtins/memory-search.js +4 -4
  92. package/dist/tools/builtins/note-append.js +14 -3
  93. package/dist/tools/builtins/plan-update.js +40 -9
  94. package/dist/tools/builtins/run-command.js +8 -8
  95. package/dist/tools/builtins/screenshot.js +11 -87
  96. package/dist/tools/builtins/task.js +29 -40
  97. package/dist/tools/builtins/use-skill.js +1 -1
  98. package/dist/tools/builtins/view-image.js +5 -3
  99. package/dist/tools/builtins/web-fetch.js +1 -3
  100. package/dist/tools/builtins/web-search.js +3 -7
  101. package/dist/tools/builtins/write-file.js +4 -2
  102. package/dist/tools/constants.js +50 -37
  103. package/dist/tools/policy.js +221 -0
  104. package/dist/tools/registry.js +40 -19
  105. package/dist/tools/resource-lock.js +4 -2
  106. package/dist/tools/router.js +143 -0
  107. package/dist/tools/validation.js +6 -5
  108. package/dist/ui/batch.js +63 -23
  109. package/dist/ui/clipboard.js +17 -5
  110. package/dist/ui/content.js +166 -13
  111. package/dist/ui/diff.js +61 -19
  112. package/dist/ui/fuzzy.js +35 -2
  113. package/dist/ui/intervention.js +2 -4
  114. package/dist/ui/layout-internal/content-write.js +587 -0
  115. package/dist/ui/layout-internal/core.js +590 -0
  116. package/dist/ui/layout-internal/input-paint.js +365 -0
  117. package/dist/ui/layout-internal/screen.js +128 -0
  118. package/dist/ui/layout-internal/scroll.js +152 -0
  119. package/dist/ui/layout-internal/selection.js +229 -0
  120. package/dist/ui/layout-internal/state.js +82 -0
  121. package/dist/ui/layout-internal/statusbar.js +351 -0
  122. package/dist/ui/layout-types.js +1 -0
  123. package/dist/ui/layout.js +3 -2288
  124. package/dist/ui/markdown.js +10 -2
  125. package/dist/ui/prompt-internal/editor.js +945 -0
  126. package/dist/ui/prompt-internal/paste.js +52 -0
  127. package/dist/ui/prompt-internal/pickers.js +519 -0
  128. package/dist/ui/prompt-internal/types.js +1 -0
  129. package/dist/ui/prompt.js +4 -1506
  130. package/dist/ui/render.js +87 -2
  131. package/dist/verification/affected.js +11 -5
  132. package/dist/verification/discovery.js +1 -3
  133. package/dist/verification/profile.js +11 -3
  134. package/package.json +11 -2
package/dist/ui/render.js CHANGED
@@ -128,6 +128,79 @@ export function visColToCharCol(str, visCol) {
128
128
  }
129
129
  return i;
130
130
  }
131
+ function flattenedOffset(lines, point) {
132
+ const line = Math.max(0, Math.min(point.line, Math.max(0, lines.length - 1)));
133
+ let offset = 0;
134
+ for (let i = 0; i < line; i++)
135
+ offset += stripAnsi(lines[i]).length;
136
+ const plain = stripAnsi(lines[line] ?? '');
137
+ return offset + visColToCharCol(plain, point.col);
138
+ }
139
+ function pointAtFlattenedOffset(lines, offset, preferNextAtBoundary) {
140
+ if (lines.length === 0)
141
+ return { line: 0, col: 0 };
142
+ let remaining = Math.max(0, offset);
143
+ for (let line = 0; line < lines.length; line++) {
144
+ const plain = stripAnsi(lines[line]);
145
+ if (remaining < plain.length ||
146
+ (remaining === plain.length && (!preferNextAtBoundary || line === lines.length - 1))) {
147
+ return { line, col: displayWidth(plain.slice(0, remaining)) };
148
+ }
149
+ remaining -= plain.length;
150
+ }
151
+ const last = stripAnsi(lines[lines.length - 1]);
152
+ return { line: lines.length - 1, col: displayWidth(last) };
153
+ }
154
+ /**
155
+ * 同一段文本重新软折行后迁移一个“物理行 + 显示列”端点。
156
+ *
157
+ * 常规段落在去 ANSI、去物理换行后文本完全一致,直接按 UTF-16 边界精确迁移;列表、
158
+ * 引用等可能因续行缩进导致扁平文本不同,此时退化为“非空白字符序号 + 尾随空白数”映射,
159
+ * 使端点仍跟随同一个内容字符,而不是按物理行比例漂移。
160
+ */
161
+ export function remapWrappedPoint(oldLines, newLines, point) {
162
+ if (newLines.length === 0)
163
+ return { line: 0, col: 0 };
164
+ const oldPlain = oldLines.map(stripAnsi);
165
+ const newPlain = newLines.map(stripAnsi);
166
+ const oldFlat = oldPlain.join('');
167
+ const newFlat = newPlain.join('');
168
+ const oldOffset = flattenedOffset(oldLines, point);
169
+ const preferNext = point.line > 0 && point.col === 0;
170
+ if (oldFlat === newFlat) {
171
+ return pointAtFlattenedOffset(newLines, Math.min(oldOffset, newFlat.length), preferNext);
172
+ }
173
+ const prefix = oldFlat.slice(0, oldOffset);
174
+ let contentChars = 0;
175
+ let trailingWhitespace = 0;
176
+ for (const ch of prefix) {
177
+ if (/\s/u.test(ch))
178
+ trailingWhitespace++;
179
+ else {
180
+ contentChars++;
181
+ trailingWhitespace = 0;
182
+ }
183
+ }
184
+ let seen = 0;
185
+ let mappedOffset = 0;
186
+ while (mappedOffset < newFlat.length && seen < contentChars) {
187
+ const cp = newFlat.codePointAt(mappedOffset) ?? 0;
188
+ const ch = String.fromCodePoint(cp);
189
+ mappedOffset += ch.length;
190
+ if (!/\s/u.test(ch))
191
+ seen++;
192
+ }
193
+ let spaces = 0;
194
+ while (mappedOffset < newFlat.length && spaces < trailingWhitespace) {
195
+ const cp = newFlat.codePointAt(mappedOffset) ?? 0;
196
+ const ch = String.fromCodePoint(cp);
197
+ if (!/\s/u.test(ch))
198
+ break;
199
+ mappedOffset += ch.length;
200
+ spaces++;
201
+ }
202
+ return pointAtFlattenedOffset(newLines, mappedOffset, preferNext);
203
+ }
131
204
  /** 带色串的可见显示宽度(先去 ANSI 再按 displayWidth 度量)。 */
132
205
  export function ansiDisplayWidth(s) {
133
206
  return displayWidth(stripAnsi(s));
@@ -142,6 +215,19 @@ export function padEndAnsi(str, width) {
142
215
  const w = ansiDisplayWidth(str);
143
216
  return w >= width ? str : str + ' '.repeat(width - w);
144
217
  }
218
+ /**
219
+ * 用指定背景色把 ANSI 行补到目标宽度。
220
+ *
221
+ * 普通 padEndAnsi 会把空格追加在行末 reset 之后,视觉上仍是终端原底色;这里重新开启
222
+ * background 再补空格,供用户消息气泡在终端放宽后动态延伸到底。原串不改写,避免破坏
223
+ * 行内前景色或已有 reset;补段自身闭合,保证下一行不继承背景。
224
+ */
225
+ export function padEndAnsiBackground(str, width, background, reset = '\x1B[0m') {
226
+ const w = ansiDisplayWidth(str);
227
+ if (w >= width)
228
+ return str;
229
+ return `${str}${background}${' '.repeat(width - w)}${reset}`;
230
+ }
145
231
  /** 带色串按可见宽度截断(保留中间 ANSI 码,超出末尾加 …,补 reset 防 … 继承颜色)。 */
146
232
  export function truncateAnsi(str, width) {
147
233
  if (ansiDisplayWidth(str) <= width)
@@ -236,7 +322,6 @@ export function wrapByDisplayWidth(text, width) {
236
322
  out.push(cur);
237
323
  return out;
238
324
  }
239
- const BOX_W = 60; // 内容区显示宽度(logo 区 + 信息区)
240
325
  const MARGIN = ' '; // 盒外左缩进
241
326
  // ── MoCode 块字符 logo(4 行,neofetch 风)──
242
327
  const LOGO_W = 35; // logo 区显示宽度
@@ -316,7 +401,7 @@ export function summarizeToolCall(name, argsRaw) {
316
401
  const args = tryParse(argsRaw);
317
402
  if (!args)
318
403
  return truncateDisplay(argsRaw, 80);
319
- const s = (k) => typeof args[k] === 'string' ? args[k] : '';
404
+ const s = (k) => (typeof args[k] === 'string' ? args[k] : '');
320
405
  switch (name) {
321
406
  case 'read_file':
322
407
  case 'write_file':
@@ -1,8 +1,14 @@
1
1
  import path from 'node:path';
2
2
  const isWindows = process.platform === 'win32';
3
3
  const ROOT_CONFIG_NAMES = new Set([
4
- 'package.json', 'pnpm-workspace.yaml', 'pnpm-workspace.yml',
5
- 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb',
4
+ 'package.json',
5
+ 'pnpm-workspace.yaml',
6
+ 'pnpm-workspace.yml',
7
+ 'package-lock.json',
8
+ 'pnpm-lock.yaml',
9
+ 'yarn.lock',
10
+ 'bun.lock',
11
+ 'bun.lockb',
6
12
  ]);
7
13
  function comparisonKey(value) {
8
14
  const resolved = path.resolve(value);
@@ -76,9 +82,9 @@ function addReason(selections, packageProfile, reason) {
76
82
  selected = { package: packageProfile, reasons: [] };
77
83
  selections.set(key, selected);
78
84
  }
79
- if (!selected.reasons.some((item) => item.kind === reason.kind
80
- && item.changedPath === reason.changedPath
81
- && item.sourcePackage === reason.sourcePackage)) {
85
+ if (!selected.reasons.some((item) => item.kind === reason.kind &&
86
+ item.changedPath === reason.changedPath &&
87
+ item.sourcePackage === reason.sourcePackage)) {
82
88
  selected.reasons.push(reason);
83
89
  }
84
90
  }
@@ -21,9 +21,7 @@ function commandFor(profile, packageProfile, script) {
21
21
  }
22
22
  /** Discover every available V3 command in increasing-cost order. */
23
23
  export function discoverPackageValidationCommands(profile, packageProfile) {
24
- return LAYERED_ORDER
25
- .filter((script) => typeof packageProfile.scripts[script] === 'string')
26
- .map((script) => commandFor(profile, packageProfile, script));
24
+ return LAYERED_ORDER.filter((script) => typeof packageProfile.scripts[script] === 'string').map((script) => commandFor(profile, packageProfile, script));
27
25
  }
28
26
  /** Compatibility API retained for callers that intentionally want one command. */
29
27
  export function discoverPackageValidationCommand(profile, packageProfile) {
@@ -3,8 +3,14 @@ import path from 'node:path';
3
3
  import fg from 'fast-glob';
4
4
  const cache = new Map();
5
5
  const ROOT_INPUTS = [
6
- 'package.json', 'pnpm-workspace.yaml', 'pnpm-workspace.yml',
7
- 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb',
6
+ 'package.json',
7
+ 'pnpm-workspace.yaml',
8
+ 'pnpm-workspace.yml',
9
+ 'package-lock.json',
10
+ 'pnpm-lock.yaml',
11
+ 'yarn.lock',
12
+ 'bun.lock',
13
+ 'bun.lockb',
8
14
  ];
9
15
  const SOURCE_DIRS = ['src', 'lib', 'app'];
10
16
  const TEST_DIRS = ['test', 'tests', '__tests__', 'spec', 'src/test', 'src/tests', 'src/__tests__'];
@@ -134,7 +140,9 @@ function discoverConfigPaths(root, patterns) {
134
140
  return uniqueSorted(fg.sync(patterns, { cwd: root, ...GLOB_OPTIONS }).map((item) => path.resolve(item)));
135
141
  }
136
142
  function existingDirectories(root, relativePaths) {
137
- return relativePaths.map((item) => path.resolve(root, item)).filter((item) => {
143
+ return relativePaths
144
+ .map((item) => path.resolve(root, item))
145
+ .filter((item) => {
138
146
  try {
139
147
  return statSync(item).isDirectory();
140
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocode-ai",
3
- "version": "1.3.9",
3
+ "version": "1.4.0",
4
4
  "description": "终端编码 agent:LLM + tool-call 循环 + 流式输出(含思考)+ 16 个工具,接任意 OpenAI 兼容后端。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,6 +23,11 @@
23
23
  "build": "tsc -p tsconfig.build.json",
24
24
  "test": "tsc -p tsconfig.test-build.json && node --test --experimental-test-isolation=none \"dist-tests/tests/*.test.js\"",
25
25
  "typecheck": "tsc --noEmit && tsc -p tests/tsconfig.json && tsc -p evals/tsconfig.json",
26
+ "lint": "eslint .",
27
+ "lint:check": "eslint .",
28
+ "lint:fix": "eslint . --fix",
29
+ "format": "prettier --write .",
30
+ "format:check": "prettier --check .",
26
31
  "eval:smoke": "tsx evals/smoke.ts && tsx evals/coding/smoke.ts && tsx evals/work-discipline.ts",
27
32
  "eval:coding": "tsx evals/coding/runner.ts",
28
33
  "eval:coding:list": "tsx evals/coding/runner.ts --list",
@@ -40,9 +45,13 @@
40
45
  "devDependencies": {
41
46
  "@types/node": "^22.0.0",
42
47
  "@types/ws": "8.5.13",
48
+ "eslint": "^9.39.5",
43
49
  "fast-check": "3.23.1",
50
+ "globals": "^15.15.0",
51
+ "prettier": "3.6.2",
44
52
  "tsx": "^4.0.0",
45
- "typescript": "^5.0.0"
53
+ "typescript": "^5.0.0",
54
+ "typescript-eslint": "^8.69.0"
46
55
  },
47
56
  "repository": {
48
57
  "type": "git",