micro-models-agent 0.18.3 → 0.19.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.
@@ -34,10 +34,9 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
34
34
  `Workspace: ${baseDir}`,
35
35
  `${profileCompressed}`,
36
36
  `Reply in the user's language. Use tools for filesystem, bash, web access.`,
37
- `When you need to act, call a tool immediately. Do not describe your plans in text use tools to perform operations.`,
38
- `After every tool call, check whether the user's request is fully satisfied. If any files, commands, or checks are still missing, call the next needed tool right away. Do not stop with an empty or "done" response until the task is complete.`,
39
- `If you create a directory, continue creating the files that belong inside it. A created folder alone is not a completed task.`,
40
- `If a tool call fails (e.g., a skill is too large), try an alternative approach or continue without it. Do not reply with empty text when the task is unfinished.`,
37
+ `Use tools when needed. Explain briefly what you're doing if it's not obvious.`,
38
+ `Answer the user's question directly. Only take action when the user explicitly asks you to change, create, or fix something.`,
39
+ `If a tool call fails, report the error and ask the user how to proceed.`,
41
40
  ``,
42
41
  `Design principles — apply them automatically without naming them:`,
43
42
  `- Do not add code, files, or abstractions that are not needed right now (YAGNI — You Ain't Gonna Need It). If something is not required by the current task, omit it.`,
@@ -49,7 +48,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
49
48
  }
50
49
  lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Do NOT chain "cd dir && cmd" — the security module blocks the "&&" operator.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`);
51
50
  if (config.autoPlan) {
52
- lines.push(``, `Plan rule: For any task with 2+ steps, create a plan first using the "plan" tool. After each step, call "plan update" to mark progress. Stay focused on the current step.`);
51
+ lines.push(``, `Plan rule: For complex tasks with 3+ steps, you may create a plan using the "plan" tool. For simple questions, just answer directly.`);
53
52
  }
54
53
  const hasMCP = config.mcpServers &&
55
54
  Object.values(config.mcpServers).some((s) => s.enabled !== false);
@@ -187,12 +186,6 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
187
186
  toolCtx.llmProvider = llmProvider;
188
187
  toolCtx.toolExecutor = toolExecutor;
189
188
  const hallucinationDetector = new HallucinationDetector();
190
- const factualCheck = hallucinationDetector.getFactualCheck();
191
- factualCheck.setBaseDir(baseDir);
192
- toolCtx.trackReadPath = (p) => factualCheck.trackReadPath(p);
193
- toolCtx.trackCreatedPath = (p) => factualCheck.trackCreatedPath(p);
194
- toolCtx.trackDeletedPath = (p) => factualCheck.trackDeletedPath(p);
195
- toolCtx.trackDocumentContent = (c) => factualCheck.trackDocumentContent(c);
196
189
  const moduleRegistry = new ModuleRegistry();
197
190
  const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
198
191
  moduleRegistry.register(execModule);
@@ -203,7 +196,7 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
203
196
  const mcpModule = new MCPModule(config);
204
197
  await mcpModule.initialize();
205
198
  moduleRegistry.register(mcpModule);
206
- const memoryModule = new MemoryModule(join(dir, 'memory'));
199
+ const memoryModule = new MemoryModule(join(dir, "memory"));
207
200
  moduleRegistry.register(memoryModule);
208
201
  if (config.browser.enabled) {
209
202
  const browserModule = new BrowserModule();
@@ -30,14 +30,19 @@ export class ConfidenceCheck {
30
30
  }
31
31
  }
32
32
  // Language-agnostic: very low word diversity (same words repeated)
33
- const words = response.toLowerCase().split(/\s+/).filter(w => w.length > 2);
33
+ const words = response
34
+ .toLowerCase()
35
+ .split(/\s+/)
36
+ .filter((w) => w.length > 2);
34
37
  if (words.length >= 10) {
35
38
  const unique = new Set(words);
36
39
  const diversity = unique.size / words.length;
37
40
  if (diversity < 0.25) {
38
41
  return {
39
42
  status: "warn",
40
- reason: t("hall.repetitive", { pct: Math.round((1 - diversity) * 100) }),
43
+ reason: t("hall.repetitive", {
44
+ pct: Math.round((1 - diversity) * 100),
45
+ }),
41
46
  };
42
47
  }
43
48
  }
@@ -1,4 +1,4 @@
1
- import { t } from '../../i18n/index';
1
+ import { t } from "../../i18n/index";
2
2
  export class ConsistencyCheck {
3
3
  decisions = [];
4
4
  createdFiles = new Set();
@@ -18,8 +18,11 @@ export class ConsistencyCheck {
18
18
  validate(response) {
19
19
  const lower = response.toLowerCase();
20
20
  for (const d of this.decisions) {
21
- const decisionWords = d.decision.toLowerCase().split(/\s+/).filter(w => w.length > 3);
22
- const contradicts = decisionWords.some(word => {
21
+ const decisionWords = d.decision
22
+ .toLowerCase()
23
+ .split(/\s+/)
24
+ .filter((w) => w.length > 3);
25
+ const contradicts = decisionWords.some((word) => {
23
26
  // English patterns
24
27
  if (lower.includes(`instead of ${word}`))
25
28
  return true;
@@ -50,11 +53,14 @@ export class ConsistencyCheck {
50
53
  });
51
54
  if (contradicts) {
52
55
  return {
53
- status: 'warn',
54
- reason: t('hall.contradiction', { decision: d.decision, location: d.location }),
56
+ status: "warn",
57
+ reason: t("hall.contradiction", {
58
+ decision: d.decision,
59
+ location: d.location,
60
+ }),
55
61
  };
56
62
  }
57
63
  }
58
- return { status: 'pass' };
64
+ return { status: "pass" };
59
65
  }
60
66
  }
@@ -1,18 +1,12 @@
1
- import { FactualCheck } from './factual';
2
- import { ConsistencyCheck } from './consistency';
3
- import { ConfidenceCheck } from './confidence';
1
+ import { ConsistencyCheck } from "./consistency";
2
+ import { ConfidenceCheck } from "./confidence";
4
3
  export class HallucinationDetector {
5
- factual;
6
4
  consistency;
7
5
  confidence;
8
6
  constructor() {
9
- this.factual = new FactualCheck();
10
7
  this.consistency = new ConsistencyCheck();
11
8
  this.confidence = new ConfidenceCheck();
12
9
  }
13
- getFactualCheck() {
14
- return this.factual;
15
- }
16
10
  getConsistencyCheck() {
17
11
  return this.consistency;
18
12
  }
@@ -21,21 +15,19 @@ export class HallucinationDetector {
21
15
  }
22
16
  validate(response) {
23
17
  const confidenceResult = this.confidence.validate(response);
24
- if (confidenceResult.status === 'retry' || confidenceResult.status === 'block') {
18
+ if (confidenceResult.status === "retry" ||
19
+ confidenceResult.status === "block") {
25
20
  return confidenceResult;
26
21
  }
27
- const factualResult = this.factual.validate(response);
28
22
  const consistencyResult = this.consistency.validate(response);
29
23
  const warnings = [];
30
- if (factualResult.status === 'warn')
31
- warnings.push(factualResult.reason || '');
32
- if (consistencyResult.status === 'warn')
33
- warnings.push(consistencyResult.reason || '');
34
- if (confidenceResult.status === 'warn')
35
- warnings.push(confidenceResult.reason || '');
24
+ if (consistencyResult.status === "warn")
25
+ warnings.push(consistencyResult.reason || "");
26
+ if (confidenceResult.status === "warn")
27
+ warnings.push(confidenceResult.reason || "");
36
28
  if (warnings.length > 0) {
37
- return { status: 'warn', reason: warnings.join('; ') };
29
+ return { status: "warn", reason: warnings.join("; ") };
38
30
  }
39
- return { status: 'pass' };
31
+ return { status: "pass" };
40
32
  }
41
33
  }
@@ -55,14 +55,379 @@ const FILE_EXTENSIONS = new Set([
55
55
  ]);
56
56
  const VERSION_PATTERN = /^\d+(\.\d+)*$/;
57
57
  const COMMON_WORDS = new Set([
58
- "node.js", "Node.js",
59
- "console.log", "console.error", "console.warn", "console.info",
60
- "Math.floor", "Math.ceil", "Math.round", "Math.max", "Math.min",
61
- "JSON.parse", "JSON.stringify",
62
- "Object.keys", "Object.values", "Object.entries",
63
- "Array.from", "Array.isArray",
64
- "Date.now", "Date.parse",
65
- "RegExp", "Promise",
58
+ "node.js",
59
+ "Node.js",
60
+ "console.log",
61
+ "console.error",
62
+ "console.warn",
63
+ "console.info",
64
+ "Math.floor",
65
+ "Math.ceil",
66
+ "Math.round",
67
+ "Math.max",
68
+ "Math.min",
69
+ "JSON.parse",
70
+ "JSON.stringify",
71
+ "Object.keys",
72
+ "Object.values",
73
+ "Object.entries",
74
+ "Array.from",
75
+ "Array.isArray",
76
+ "Date.now",
77
+ "Date.parse",
78
+ "RegExp",
79
+ "Promise",
80
+ ]);
81
+ /**
82
+ * Words that indicate the model is talking about a file or file operation.
83
+ * Used to filter out false positives where a dotted term is just a
84
+ * technology name (e.g. "React/Next.js component") rather than a file path.
85
+ *
86
+ * Includes base forms plus common English and Russian conjugations.
87
+ */
88
+ const FILE_OPERATION_CONTEXT = new Set([
89
+ // English file operation verbs
90
+ "create",
91
+ "created",
92
+ "creating",
93
+ "update",
94
+ "updated",
95
+ "updating",
96
+ "edit",
97
+ "edited",
98
+ "editing",
99
+ "modify",
100
+ "modified",
101
+ "modifying",
102
+ "change",
103
+ "changed",
104
+ "changing",
105
+ "delete",
106
+ "deleted",
107
+ "deleting",
108
+ "remove",
109
+ "removed",
110
+ "removing",
111
+ "write",
112
+ "wrote",
113
+ "written",
114
+ "writing",
115
+ "read",
116
+ "reading",
117
+ "check",
118
+ "checked",
119
+ "checking",
120
+ "open",
121
+ "opened",
122
+ "opening",
123
+ "fix",
124
+ "fixed",
125
+ "fixing",
126
+ "rename",
127
+ "renamed",
128
+ "renaming",
129
+ "move",
130
+ "moved",
131
+ "moving",
132
+ "copy",
133
+ "copied",
134
+ "copying",
135
+ "make",
136
+ "made",
137
+ "making",
138
+ "build",
139
+ "built",
140
+ "building",
141
+ "run",
142
+ "ran",
143
+ "running",
144
+ "test",
145
+ "tested",
146
+ "testing",
147
+ "import",
148
+ "imported",
149
+ "importing",
150
+ "export",
151
+ "exported",
152
+ "exporting",
153
+ // English file nouns
154
+ "file",
155
+ "files",
156
+ "path",
157
+ "paths",
158
+ "directory",
159
+ "directories",
160
+ "folder",
161
+ "folders",
162
+ "module",
163
+ "modules",
164
+ "script",
165
+ "scripts",
166
+ "source",
167
+ "sources",
168
+ "code",
169
+ // Russian file operation verbs
170
+ "создать",
171
+ "создал",
172
+ "создала",
173
+ "создало",
174
+ "создаю",
175
+ "создаем",
176
+ "создает",
177
+ "создан",
178
+ "создание",
179
+ "создается",
180
+ "обновить",
181
+ "обновил",
182
+ "обновила",
183
+ "обновило",
184
+ "обновляю",
185
+ "обновляем",
186
+ "обновляет",
187
+ "обновлен",
188
+ "обновление",
189
+ "обновляется",
190
+ "изменить",
191
+ "изменил",
192
+ "изменила",
193
+ "изменило",
194
+ "изменяю",
195
+ "изменяем",
196
+ "изменяет",
197
+ "изменен",
198
+ "изменение",
199
+ "изменяется",
200
+ "редактировать",
201
+ "редактировал",
202
+ "редактировала",
203
+ "редактирую",
204
+ "редактируем",
205
+ "редактирует",
206
+ "редактирован",
207
+ "редактирование",
208
+ "редактируется",
209
+ "удалить",
210
+ "удалил",
211
+ "удалила",
212
+ "удалило",
213
+ "удаляю",
214
+ "удаляем",
215
+ "удаляет",
216
+ "удален",
217
+ "удаление",
218
+ "удаляется",
219
+ "добавить",
220
+ "добавил",
221
+ "добавила",
222
+ "добавило",
223
+ "добавляю",
224
+ "добавляем",
225
+ "добавляет",
226
+ "добавлен",
227
+ "добавление",
228
+ "добавляется",
229
+ "написать",
230
+ "написал",
231
+ "написала",
232
+ "написало",
233
+ "напишу",
234
+ "написано",
235
+ "писать",
236
+ "пишу",
237
+ "пишем",
238
+ "пишет",
239
+ "написание",
240
+ "пишется",
241
+ "записать",
242
+ "записал",
243
+ "записала",
244
+ "записываю",
245
+ "записываем",
246
+ "записывает",
247
+ "записан",
248
+ "запись",
249
+ "записывается",
250
+ "прочитать",
251
+ "прочитал",
252
+ "прочитала",
253
+ "прочитало",
254
+ "прочитаю",
255
+ "прочитано",
256
+ "читать",
257
+ "читаю",
258
+ "читаем",
259
+ "читает",
260
+ "чтение",
261
+ "читается",
262
+ "проверить",
263
+ "проверил",
264
+ "проверила",
265
+ "проверило",
266
+ "проверю",
267
+ "проверено",
268
+ "проверка",
269
+ "проверяю",
270
+ "проверяем",
271
+ "проверяет",
272
+ "проверяется",
273
+ "открыть",
274
+ "открыл",
275
+ "открыла",
276
+ "открыло",
277
+ "открою",
278
+ "открыто",
279
+ "открытие",
280
+ "открываю",
281
+ "открываем",
282
+ "открывает",
283
+ "открывается",
284
+ "найти",
285
+ "нашел",
286
+ "нашла",
287
+ "нашло",
288
+ "найду",
289
+ "найдено",
290
+ "поиск",
291
+ "ищу",
292
+ "ищем",
293
+ "ищет",
294
+ "ищется",
295
+ "исправить",
296
+ "исправил",
297
+ "исправила",
298
+ "исправило",
299
+ "исправлю",
300
+ "исправлено",
301
+ "исправление",
302
+ "исправляю",
303
+ "исправляем",
304
+ "исправляет",
305
+ "исправляется",
306
+ "переместить",
307
+ "переместил",
308
+ "переместила",
309
+ "переместило",
310
+ "перемещу",
311
+ "перемещен",
312
+ "перемещение",
313
+ "перемещаю",
314
+ "перемещаем",
315
+ "перемещает",
316
+ "перемещается",
317
+ "переименовать",
318
+ "переименовал",
319
+ "переименовала",
320
+ "переименую",
321
+ "переименован",
322
+ "переименование",
323
+ "переименовываю",
324
+ "переименовываем",
325
+ "переименовывает",
326
+ "переименовывается",
327
+ "скопировать",
328
+ "скопировал",
329
+ "скопировала",
330
+ "скопировало",
331
+ "скопирую",
332
+ "скопирован",
333
+ "копирование",
334
+ "копирую",
335
+ "копируем",
336
+ "копирует",
337
+ "копируется",
338
+ "собрать",
339
+ "собрал",
340
+ "собрала",
341
+ "собран",
342
+ "сборка",
343
+ "собираю",
344
+ "собираем",
345
+ "собирает",
346
+ "собирается",
347
+ "запустить",
348
+ "запустил",
349
+ "запустила",
350
+ "запустило",
351
+ "запущен",
352
+ "запуск",
353
+ "запускаю",
354
+ "запускаем",
355
+ "запускает",
356
+ "запускается",
357
+ "тестировать",
358
+ "тестировал",
359
+ "тестировала",
360
+ "тестирую",
361
+ "тестируем",
362
+ "тестирует",
363
+ "тестирован",
364
+ "тестирование",
365
+ "тестируется",
366
+ "импортировать",
367
+ "импортировал",
368
+ "импортировала",
369
+ "импортирую",
370
+ "импортируем",
371
+ "импортирует",
372
+ "импортирован",
373
+ "импорт",
374
+ "импортируется",
375
+ "экспортировать",
376
+ "экспортировал",
377
+ "экспортировала",
378
+ "экспортирую",
379
+ "экспортируем",
380
+ "экспортирует",
381
+ "экспортирован",
382
+ "экспорт",
383
+ "экспортируется",
384
+ // Russian file nouns
385
+ "файл",
386
+ "файла",
387
+ "файле",
388
+ "файлы",
389
+ "файлов",
390
+ "файлам",
391
+ "файлами",
392
+ "путь",
393
+ "пути",
394
+ "путей",
395
+ "путем",
396
+ "путям",
397
+ "путями",
398
+ "директория",
399
+ "директории",
400
+ "директорию",
401
+ "директорий",
402
+ "директориях",
403
+ "директориями",
404
+ "папка",
405
+ "папке",
406
+ "папки",
407
+ "папок",
408
+ "папкам",
409
+ "папками",
410
+ "модуль",
411
+ "модуля",
412
+ "модуле",
413
+ "модули",
414
+ "модулей",
415
+ "модулям",
416
+ "модулями",
417
+ "скрипт",
418
+ "скрипта",
419
+ "скрипте",
420
+ "скрипты",
421
+ "скриптов",
422
+ "скриптам",
423
+ "скриптами",
424
+ "код",
425
+ "кода",
426
+ "коде",
427
+ "коды",
428
+ "кодов",
429
+ "кодам",
430
+ "кодами",
66
431
  ]);
67
432
  export class FactualCheck {
68
433
  knownPaths = new Set();
@@ -104,7 +469,7 @@ export class FactualCheck {
104
469
  let match;
105
470
  while ((match = pathPatterns.exec(content)) !== null) {
106
471
  const file = match[1];
107
- if (file.includes('/') || file.includes('\\')) {
472
+ if (file.includes("/") || file.includes("\\")) {
108
473
  this.knownPaths.add(file);
109
474
  }
110
475
  const base = file.split(/[/\\]/).pop();
@@ -116,7 +481,9 @@ export class FactualCheck {
116
481
  try {
117
482
  // Skip absolute paths — they're either system paths or outside the project.
118
483
  // On Windows, existsSync('/...') can hang on certain paths.
119
- if (path.startsWith("/") || path.startsWith("~") || /^[A-Za-z]:/.test(path)) {
484
+ if (path.startsWith("/") ||
485
+ path.startsWith("~") ||
486
+ /^[A-Za-z]:/.test(path)) {
120
487
  return false;
121
488
  }
122
489
  return existsSync(join(this.baseDir, path));
@@ -125,6 +492,28 @@ export class FactualCheck {
125
492
  return false;
126
493
  }
127
494
  }
495
+ /**
496
+ * Check whether a matched path-like string appears in a context that suggests
497
+ * the model is actually talking about a file operation, rather than just
498
+ * mentioning a technology or framework name (e.g. "React/Next.js").
499
+ *
500
+ * Looks at the 8 words immediately preceding the match.
501
+ */
502
+ hasFileOperationContext(text, match) {
503
+ const escaped = match.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
504
+ const regex = new RegExp(escaped, "gi");
505
+ let result;
506
+ while ((result = regex.exec(text)) !== null) {
507
+ const start = result.index;
508
+ const before = text.slice(Math.max(0, start - 100), start);
509
+ const words = [...before.matchAll(/[a-zA-Zа-яА-ЯёЁ0-9]+/g)].map((m) => m[0].toLowerCase());
510
+ const lastWords = words.slice(-8);
511
+ if (lastWords.some((w) => FILE_OPERATION_CONTEXT.has(w))) {
512
+ return true;
513
+ }
514
+ }
515
+ return false;
516
+ }
128
517
  validate(response) {
129
518
  const pathRegex = /[\w\-./]+\.\w+/g;
130
519
  const mentionedPaths = response.match(pathRegex) || [];
@@ -154,6 +543,8 @@ export class FactualCheck {
154
543
  if (urlPattern.test(response))
155
544
  return false;
156
545
  }
546
+ if (!this.hasFileOperationContext(response, p))
547
+ return false;
157
548
  return true;
158
549
  });
159
550
  if (unknownPaths.length > 0) {
@@ -1,4 +1,3 @@
1
- export { HallucinationDetector } from './detector';
2
- export { FactualCheck } from './factual';
3
- export { ConsistencyCheck } from './consistency';
4
- export { ConfidenceCheck } from './confidence';
1
+ export { HallucinationDetector, } from "./detector";
2
+ export { ConsistencyCheck } from "./consistency";
3
+ export { ConfidenceCheck } from "./confidence";
@@ -50,7 +50,6 @@ export const createDirTool = {
50
50
  ctx.fileOperationsCount = currentCount + 1;
51
51
  // Log directory creation
52
52
  logFileWrite(ctx.sessionId, path, true, "Directory created");
53
- ctx.trackCreatedPath?.(path);
54
53
  return { success: true, output: t("file.created", { path }) };
55
54
  },
56
55
  };
@@ -57,7 +57,6 @@ export const deleteFileTool = {
57
57
  ctx.fileOperationsCount = currentCount + 1;
58
58
  // Log successful file deletion
59
59
  logFileDelete(ctx.sessionId, path, true);
60
- ctx.trackDeletedPath?.(path);
61
60
  return { success: true, output: t("file.deleted", { path }), diff };
62
61
  },
63
62
  };
@@ -44,10 +44,7 @@ export const readFileTool = {
44
44
  if (!existsSync(resolved)) {
45
45
  return { success: false, output: t("file.notfound", { path }) };
46
46
  }
47
- ctx.trackReadPath?.(path);
48
47
  const content = readFileSync(resolved, "utf-8");
49
- // Track file paths mentioned in the document (e.g. structure.md, README)
50
- ctx.trackDocumentContent?.(content);
51
48
  const lines = content.split("\n");
52
49
  const total = lines.length;
53
50
  const offset = args.offset || 1;
@@ -73,8 +73,7 @@ export const writeFileTool = {
73
73
  // Increment file operations counter
74
74
  ctx.fileOperationsCount = currentCount + 1;
75
75
  // Log successful file write
76
- logFileWrite(ctx.sessionId, path, true, `File ${fileExists ? 'updated' : 'created'}`);
77
- ctx.trackCreatedPath?.(path);
76
+ logFileWrite(ctx.sessionId, path, true, `File ${fileExists ? "updated" : "created"}`);
78
77
  return { success: true, output: t("file.written", { path }), diff };
79
78
  },
80
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.18.3",
3
+ "version": "0.19.0",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {