mocode-ai 1.2.4 → 1.2.6

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.
@@ -11,6 +11,11 @@ import { isToolErrorOutput } from './result.js';
11
11
  */
12
12
  const extensions = new Map();
13
13
  export const tools = [...builtinTools];
14
+ /** 名字 → 工具 的 O(1) 索引,与 tools 数组在每次 rebuild 时同步重建;findTool 走此索引。 */
15
+ let toolIndex = buildToolIndex(tools);
16
+ function buildToolIndex(list) {
17
+ return new Map(list.map((tool) => [tool.name, tool]));
18
+ }
14
19
  export function registerToolsExtension(sourceOrAdditions, maybeAdditions) {
15
20
  const source = typeof sourceOrAdditions === 'string' ? sourceOrAdditions : 'external';
16
21
  const additions = typeof sourceOrAdditions === 'string' ? (maybeAdditions ?? []) : sourceOrAdditions;
@@ -35,13 +40,14 @@ function rebuildTools() {
35
40
  next.push(tool);
36
41
  }
37
42
  tools.splice(0, tools.length, ...next);
43
+ toolIndex = buildToolIndex(next);
38
44
  }
39
45
  const DEFAULT_CAPABILITIES = Object.freeze({
40
46
  effect: 'unknown',
41
47
  concurrency: 'serial',
42
48
  });
43
49
  export function findTool(name) {
44
- return tools.find((tool) => tool.name === name);
50
+ return toolIndex.get(name);
45
51
  }
46
52
  /** 缺少声明或找不到工具时返回保守能力,绝不把未知扩展并发执行。 */
47
53
  export function getToolCapabilities(toolOrName) {
@@ -58,10 +64,13 @@ export function getToolResourceKeys(toolOrName, args) {
58
64
  }
59
65
  }
60
66
  /** resource-locked write 是可生成文件 diff/按路径记 rollback 的文件 mutation。 */
61
- export function isFileMutationTool(name) {
62
- const capabilities = getToolCapabilities(name);
67
+ export function isFileMutationCapabilities(capabilities) {
63
68
  return capabilities.effect === 'write' && capabilities.concurrency === 'resource-locked';
64
69
  }
70
+ /** 按工具名判定(兼容入口);热路径请直接复用已解析的 capabilities 走 isFileMutationCapabilities。 */
71
+ export function isFileMutationTool(name) {
72
+ return isFileMutationCapabilities(getToolCapabilities(name));
73
+ }
65
74
  function isStructuredOutcome(value) {
66
75
  return typeof value === 'object' && value !== null &&
67
76
  typeof value.status === 'string' && typeof value.code === 'string' &&
@@ -144,7 +153,7 @@ async function executeToolOnce(tool, args, signal, opts) {
144
153
  mutationVersionBefore = mutationBefore.version;
145
154
  // Transactional tools own their full write-set capture inside ChangeSet commit.
146
155
  const pathCapture = !capabilities.delegatesResourceLocks &&
147
- isFileMutationTool(tool.name) && typeof args.path === 'string' && args.path
156
+ isFileMutationCapabilities(capabilities) && typeof args.path === 'string' && args.path
148
157
  ? beginPathMutation(args.path)
149
158
  : null;
150
159
  capturedPath = pathCapture?.path;
package/dist/ui/diff.js CHANGED
@@ -254,7 +254,7 @@ export function renderFileChange(opts) {
254
254
  /** 头行 + 计数行 + 正文(折叠 + 截断 + 行号),每行尾 \n。 */
255
255
  function renderBody(head, counts, items, padW, startLine, lang) {
256
256
  const lines = [head, counts];
257
- const metaPrefix = `${BODY_INDENT}${' '.repeat(padW)} `; // 对齐到代码列(num + 空格 + gutter + 空格)
257
+ const metaPrefix = `${BODY_INDENT}`; // 与正文行左对齐(batch 展开时还会再加外层 indent,避免双重缩进导致提示行突兀)
258
258
  let oldLine = startLine;
259
259
  let newLine = startLine;
260
260
  let shown = 0;
package/dist/ui/layout.js CHANGED
@@ -1361,8 +1361,8 @@ function composeSpinnerLine(status, cols) {
1361
1361
  leadW = 1 + 1 + displayWidth(status.status) + (elapsed ? 1 + displayWidth(elapsed) : 0);
1362
1362
  }
1363
1363
  else if (spinning) {
1364
- // 运行态心跳帧(流式输出中):帧 + 生成中 + 走时
1365
- const label = '生成中';
1364
+ // 运行态心跳帧(流式输出中 / 命令态如 /rollback /compact /resume):帧 + 状态文字(优先)或生成中(兜底) + 走时
1365
+ const label = status.status || '生成中';
1366
1366
  const ePart = elapsed ? ` ${ui.dim}${elapsed}${ui.reset}` : '';
1367
1367
  lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset} ${ui.dim}${label}${ui.reset}${ePart}`;
1368
1368
  leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0);
@@ -1936,7 +1936,7 @@ function renderDimInputRow(prompt, text, placeholder, cols) {
1936
1936
  /**
1937
1937
  * 单行运行态滑窗:以光标为中心,向左右扩展填满 contentW-1(留 1 cell 给光标),
1938
1938
  * 返回可见子串与光标在子串内的显示列。保证光标恒可见,且不软折行(运行态输入框恒单行,
1939
- * 不触发 setRegion/ED,避免流式期间底栏抖动——见 scripts/check-layout.ts 断言)。
1939
+ * 不触发 setRegion/ED,避免流式期间底栏抖动)。
1940
1940
  */
1941
1941
  function windowSingleLine(text, cursor, contentW) {
1942
1942
  const n = text.length;
package/dist/ui/render.js CHANGED
@@ -2,7 +2,13 @@ import { stdout } from 'node:process';
2
2
  import { createRequire } from 'node:module';
3
3
  import { ui } from './theme.js';
4
4
  import { t } from '../i18n/index.js';
5
- const VERSION = createRequire(import.meta.url)('../../package.json').version;
5
+ let VERSION = '0.0.0';
6
+ try {
7
+ VERSION = createRequire(import.meta.url)('../../package.json').version;
8
+ }
9
+ catch {
10
+ // 非标准布局(测试编译产物、自定义打包)不含 package.json:版本号回落,不阻断模块加载。
11
+ }
6
12
  /**
7
13
  * 清空整屏 + 滚动缓冲(向上滚动可见的历史输出),光标归位。
8
14
  * 进入会话时调用,让终端只剩当前 agent 对话。非 TTY 时空操作。
package/dist/ui/theme.js CHANGED
@@ -29,7 +29,7 @@ const DEFAULT = {
29
29
  brightCyan: '\x1B[38;2;86;182;194m',
30
30
  brightMagenta: '\x1B[38;2;198;120;221m',
31
31
  accent: '\x1B[38;2;86;182;194m', // 与 cyan 同源(One Dark):logo/标题/输入框顶线/选中项统一承载
32
- userBg: '\x1B[48;2;44;49;60m', // One Dark current-line bg,深色终端上微妙可辨
32
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
33
33
  // diff 行底色:One Dark bg #282c34 上加 ~14% 亮度的对应色,够辨识但不刺眼
34
34
  addBg: '\x1B[48;2;44;62;42m', // 偏暗绿(One Dark green(152,195,121)暗化)
35
35
  delBg: '\x1B[48;2;62;38;42m', // 偏暗红(One Dark red(224,108,117)暗化)
@@ -54,7 +54,7 @@ const THEMES = {
54
54
  brightCyan: '\x1B[38;2;42;161;152m',
55
55
  brightMagenta: '\x1B[38;2;108;113;196m',
56
56
  accent: '\x1B[38;2;38;139;210m', // Solarized blue:浅底下更醒目的强调
57
- userBg: '\x1B[48;2;238;232;213m',
57
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
58
58
  // diff 行底色:Solarized Light base2(238,232,213)上贴同色族浅 tint,
59
59
  // 比直接用 base1 更柔,跟深 fg(red/green)对比充足
60
60
  addBg: '\x1B[48;2;220;235;205m',
@@ -72,7 +72,7 @@ const THEMES = {
72
72
  brightCyan: '\x1B[38;2;147;161;161m',
73
73
  brightMagenta: '\x1B[38;2;108;113;196m',
74
74
  accent: '\x1B[38;2;42;161;152m', // Solarized cyan(深底版):暗底上跳出
75
- userBg: '\x1B[48;2;7;54;66m',
75
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
76
76
  // diff 行底色:Solarized Dark base03(0,43,54)上加对应色族暗 tint
77
77
  addBg: '\x1B[48;2;20;50;38m',
78
78
  delBg: '\x1B[48;2;55;30;30m',
@@ -89,7 +89,7 @@ const THEMES = {
89
89
  brightCyan: '\x1B[38;2;142;192;124m',
90
90
  brightMagenta: '\x1B[38;2;211;134;155m',
91
91
  accent: '\x1B[38;2;250;189;47m', // Gruvbox yellow(主题色)
92
- userBg: '\x1B[48;2;40;40;40m',
92
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
93
93
  // diff 行底色:Gruvbox dark bg(40,40,40)上贴暗 bg0_a 风格
94
94
  addBg: '\x1B[48;2;40;55;30m',
95
95
  delBg: '\x1B[48;2;70;35;30m',
@@ -106,7 +106,7 @@ const THEMES = {
106
106
  brightCyan: '\x1B[38;2;136;192;208m',
107
107
  brightMagenta: '\x1B[38;2;180;142;173m',
108
108
  accent: '\x1B[38;2;136;192;208m', // Nord 浅冰蓝(主题色)
109
- userBg: '\x1B[48;2;67;76;94m',
109
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
110
110
  // diff 行底色:Nord polar night(46,52,64)上贴对应色族暗 tint
111
111
  addBg: '\x1B[48;2;46;66;52m',
112
112
  delBg: '\x1B[48;2;72;46;52m',
@@ -126,7 +126,7 @@ const THEMES = {
126
126
  brightCyan: '\x1B[38;2;160;220;220m',
127
127
  brightMagenta: '\x1B[38;2;245;165;215m',
128
128
  accent: '\x1B[38;2;255;170;60m', // 南瓜橙(主题主色:logo/标题/输入框顶线/选中项)
129
- userBg: '\x1B[48;2;55;42;32m', // 暖深棕(略亮于 #231c16 背景)
129
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
130
130
  // diff 行底色:暖深棕底上贴对应色族暗 tint,跟 fg 配对柔和可辨
131
131
  addBg: '\x1B[48;2;55;70;35m',
132
132
  delBg: '\x1B[48;2;78;42;32m',
@@ -145,7 +145,7 @@ const THEMES = {
145
145
  brightCyan: '\x1B[38;2;170;220;220m',
146
146
  brightMagenta: '\x1B[38;2;250;160;200m',
147
147
  accent: '\x1B[38;2;230;90;150m', // 玫粉(主题主色)
148
- userBg: '\x1B[48;2;50;32;46m', // 深紫底
148
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
149
149
  // diff 行底色:深紫底贴对应色族暗 tint,绿暗化偏橄榄、红暗化偏紫红
150
150
  addBg: '\x1B[48;2;50;60;42m',
151
151
  delBg: '\x1B[48;2;75;40;52m',
@@ -164,7 +164,7 @@ const THEMES = {
164
164
  brightCyan: '\x1B[38;2;140;230;210m',
165
165
  brightMagenta: '\x1B[38;2;210;170;230m',
166
166
  accent: '\x1B[38;2;80;210;140m', // 翡翠绿(主题主色)
167
- userBg: '\x1B[48;2;18;42;34m', // 深绿底
167
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
168
168
  // diff 行底色:深绿底贴对应色族暗 tint,绿暗化偏深绿、红暗化偏暗红
169
169
  addBg: '\x1B[48;2;30;55;40m',
170
170
  delBg: '\x1B[48;2;60;40;40m',
@@ -183,7 +183,7 @@ const THEMES = {
183
183
  brightCyan: '\x1B[38;2;170;210;200m',
184
184
  brightMagenta: '\x1B[38;2;240;180;210m',
185
185
  accent: '\x1B[38;2;255;200;80m', // 琥珀金黄(主题主色)
186
- userBg: '\x1B[48;2;43;31;14m', // 深棕底(比 orange 略浅,避免撞色)
186
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
187
187
  // diff 行底色:深棕底贴对应色族暗 tint,绿暗化偏橄榄、红暗化偏暗棕红
188
188
  addBg: '\x1B[48;2;50;55;25m',
189
189
  delBg: '\x1B[48;2;70;40;28m',
@@ -202,7 +202,7 @@ const THEMES = {
202
202
  brightCyan: '\x1B[38;2;180;230;230m',
203
203
  brightMagenta: '\x1B[38;2;210;180;250m',
204
204
  accent: '\x1B[38;2;180;150;230m', // 薰衣草紫(主题主色)
205
- userBg: '\x1B[48;2;31;26;46m', // 深紫底(比 rose 更冷)
205
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
206
206
  // diff 行底色:深紫底贴对应色族暗 tint,绿暗化偏冷绿、红暗化偏冷紫红
207
207
  addBg: '\x1B[48;2;38;46;42m',
208
208
  delBg: '\x1B[48;2;60;40;55m',
@@ -221,7 +221,7 @@ const THEMES = {
221
221
  brightCyan: '\x1B[38;2;170;225;215m',
222
222
  brightMagenta: '\x1B[38;2;250;170;210m',
223
223
  accent: '\x1B[38;2;255;120;100m', // 珊瑚红(主题主色)
224
- userBg: '\x1B[48;2;42;22;20m', // 深棕红底
224
+ userBg: '\x1B[48;2;72;78;90m', // 统一灰白用户消息底(黑底终端可见)
225
225
  // diff 行底色:深棕红底贴对应色族暗 tint,绿暗化偏橄榄、红暗化偏暗棕红
226
226
  addBg: '\x1B[48;2;50;55;30m',
227
227
  delBg: '\x1B[48;2;75;32;30m',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocode-ai",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "终端编码 agent:LLM + tool-call 循环 + 流式输出(含思考)+ 16 个工具,接任意 OpenAI 兼容后端。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,8 @@
21
21
  "scripts": {
22
22
  "start": "tsx src/index.ts",
23
23
  "build": "tsc -p tsconfig.build.json",
24
- "typecheck": "tsc --noEmit && tsc -p evals/tsconfig.json",
24
+ "test": "tsc -p tsconfig.test-build.json && node --test --experimental-test-isolation=none \"dist-tests/tests/*.test.js\"",
25
+ "typecheck": "tsc --noEmit && tsc -p tests/tsconfig.json && tsc -p evals/tsconfig.json",
25
26
  "eval:smoke": "tsx evals/smoke.ts && tsx evals/coding/smoke.ts",
26
27
  "eval:coding": "tsx evals/coding/runner.ts",
27
28
  "eval:coding:list": "tsx evals/coding/runner.ts --list",