micro-models-agent 0.40.1 → 0.41.2

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 (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +9 -19
  3. package/dist/cli/completer.js +36 -37
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +48 -23
  6. package/dist/cli/plugin-commands.js +36 -0
  7. package/dist/cli/repl-commands.js +40 -12
  8. package/dist/cli/repl.js +217 -87
  9. package/dist/cli/run-result.js +22 -0
  10. package/dist/cli/security-commands.js +5 -7
  11. package/dist/cli/setup.js +8 -26
  12. package/dist/config/config.js +52 -5
  13. package/dist/config/defaults.js +29 -5
  14. package/dist/config/experts.js +1 -1
  15. package/dist/config/index.js +3 -3
  16. package/dist/config/security.js +3 -10
  17. package/dist/core/agent-moe.js +2 -10
  18. package/dist/core/agent.js +273 -82
  19. package/dist/core/bootstrap.js +80 -13
  20. package/dist/core/index.js +2 -2
  21. package/dist/core/prompt-builder.js +23 -2
  22. package/dist/core/session-logger.js +46 -4
  23. package/dist/core/version.js +24 -0
  24. package/dist/i18n/en.json +75 -2
  25. package/dist/i18n/ru.json +74 -1
  26. package/dist/index.js +1 -1
  27. package/dist/llm/image-utils.js +4 -5
  28. package/dist/llm/index.js +4 -4
  29. package/dist/llm/model-loader.js +6 -6
  30. package/dist/llm/openai-compat.js +40 -34
  31. package/dist/llm/orchestrator.js +33 -29
  32. package/dist/llm/response.js +9 -9
  33. package/dist/logger/app-logger.js +1 -1
  34. package/dist/logger/index.js +1 -1
  35. package/dist/main.js +2489 -2186
  36. package/dist/migration/backup.js +13 -13
  37. package/dist/migration/detect.js +11 -11
  38. package/dist/migration/index.js +2 -2
  39. package/dist/modules/artifacts/store.js +61 -0
  40. package/dist/modules/browser/actions.js +34 -4
  41. package/dist/modules/browser/bridge-client.js +199 -0
  42. package/dist/modules/browser/bridge-path.js +10 -0
  43. package/dist/modules/browser/bridge-server.mjs +202 -202
  44. package/dist/modules/browser/cookie-store.js +6 -6
  45. package/dist/modules/browser/driver.js +136 -0
  46. package/dist/modules/browser/index.js +7 -5
  47. package/dist/modules/browser/module.js +8 -7
  48. package/dist/modules/browser/session.js +87 -84
  49. package/dist/modules/browser/snapshot.js +92 -58
  50. package/dist/modules/browser/types.js +4 -1
  51. package/dist/modules/certification/cli.js +2 -4
  52. package/dist/modules/certification/fact-checker.js +1 -3
  53. package/dist/modules/certification/loader.js +3 -9
  54. package/dist/modules/certification/runner.js +1 -4
  55. package/dist/modules/context/chunk-query.js +100 -0
  56. package/dist/modules/context/fact-extractor.js +162 -0
  57. package/dist/modules/context/history.js +15 -0
  58. package/dist/modules/context/index.js +1 -1
  59. package/dist/modules/context/manager.js +160 -86
  60. package/dist/modules/execution/audit-runners.js +152 -0
  61. package/dist/modules/execution/auditor.js +177 -25
  62. package/dist/modules/execution/execution-plugin.js +272 -0
  63. package/dist/modules/execution/module.js +201 -544
  64. package/dist/modules/execution/moe-executor.js +25 -0
  65. package/dist/modules/execution/plan-store.js +1 -3
  66. package/dist/modules/execution/plan-tool.js +508 -0
  67. package/dist/modules/execution/plan-validator.js +10 -10
  68. package/dist/modules/execution/planner.js +6 -1
  69. package/dist/modules/execution/stuck-detector.js +173 -10
  70. package/dist/modules/execution/verifier.js +86 -42
  71. package/dist/modules/execution/windows-commands.js +41 -0
  72. package/dist/modules/hallucination/confidence.js +8 -1
  73. package/dist/modules/hallucination/detector.js +2 -5
  74. package/dist/modules/hallucination/factual.js +3 -64
  75. package/dist/modules/hallucination/index.js +1 -1
  76. package/dist/modules/hallucination/js-identifiers.js +190 -0
  77. package/dist/modules/hallucination/llm-judge.js +1 -3
  78. package/dist/modules/indexer/cache.js +9 -7
  79. package/dist/modules/indexer/index.js +3 -3
  80. package/dist/modules/indexer/module.js +95 -42
  81. package/dist/modules/indexer/project-profile.js +183 -0
  82. package/dist/modules/indexer/walker.js +17 -17
  83. package/dist/modules/lsp/check-tool.js +58 -0
  84. package/dist/modules/lsp/client.js +74 -31
  85. package/dist/modules/lsp/command.js +60 -0
  86. package/dist/modules/lsp/config.js +87 -33
  87. package/dist/modules/lsp/index.js +3 -3
  88. package/dist/modules/lsp/module.js +185 -21
  89. package/dist/modules/lsp/probe.js +76 -0
  90. package/dist/modules/lsp/project-root.js +32 -0
  91. package/dist/modules/lsp/startup-check.js +141 -0
  92. package/dist/modules/mcp/module.js +2 -6
  93. package/dist/modules/memory/index.js +1 -1
  94. package/dist/modules/memory/module.js +71 -23
  95. package/dist/modules/memory/search.js +11 -9
  96. package/dist/modules/memory/store.js +13 -13
  97. package/dist/modules/pipelines/engine.js +10 -10
  98. package/dist/modules/pipelines/index.js +3 -3
  99. package/dist/modules/pipelines/parser.js +17 -14
  100. package/dist/modules/pipelines/template.js +1 -1
  101. package/dist/modules/plugins/builtin/lint-on-write.js +21 -16
  102. package/dist/modules/plugins/builtin/notify.js +3 -2
  103. package/dist/modules/plugins/index.js +1 -1
  104. package/dist/modules/plugins/loader.js +59 -17
  105. package/dist/modules/plugins/manager.js +73 -17
  106. package/dist/modules/processes/detect.js +34 -0
  107. package/dist/modules/processes/index.js +1 -1
  108. package/dist/modules/processes/registry.js +135 -46
  109. package/dist/modules/registry.js +4 -2
  110. package/dist/modules/security/audit-notifier.js +39 -39
  111. package/dist/modules/security/command-validator.js +2 -8
  112. package/dist/modules/security/data-sanitizer.js +1 -9
  113. package/dist/modules/security/encryption.js +58 -56
  114. package/dist/modules/security/network-validator.js +1 -9
  115. package/dist/modules/security/path-validator.js +1 -3
  116. package/dist/modules/security/security-policies.js +3 -19
  117. package/dist/modules/security/session-encryption.js +1 -1
  118. package/dist/modules/security/session-isolation.js +8 -8
  119. package/dist/modules/session/index.js +3 -3
  120. package/dist/modules/session/module.js +5 -5
  121. package/dist/modules/session/store.js +3 -9
  122. package/dist/modules/skills/matcher.js +27 -0
  123. package/dist/modules/skills/module.js +1 -2
  124. package/dist/modules/updater/checker.js +70 -6
  125. package/dist/modules/updater/index.js +2 -1
  126. package/dist/modules/updater/module.js +116 -0
  127. package/dist/modules/user-profile/compressor.js +2 -2
  128. package/dist/modules/user-profile/index.js +1 -1
  129. package/dist/modules/user-profile/profile.js +9 -9
  130. package/dist/tools/attach-image.js +1 -1
  131. package/dist/tools/bash.js +178 -19
  132. package/dist/tools/browser.js +46 -29
  133. package/dist/tools/chunk-query.js +99 -0
  134. package/dist/tools/download-file.js +116 -0
  135. package/dist/tools/enable-tools.js +58 -0
  136. package/dist/tools/executor.js +4 -5
  137. package/dist/tools/file-info.js +13 -12
  138. package/dist/tools/filter-tools.js +9 -2
  139. package/dist/tools/glob-tool.js +11 -11
  140. package/dist/tools/grep-tool.js +1 -3
  141. package/dist/tools/hidden-tools-block.js +37 -0
  142. package/dist/tools/index.js +13 -2
  143. package/dist/tools/list-dir.js +18 -17
  144. package/dist/tools/load-skill.js +1 -3
  145. package/dist/tools/path-utils.js +4 -4
  146. package/dist/tools/pipeline-run.js +25 -25
  147. package/dist/tools/process-kill.js +11 -11
  148. package/dist/tools/process-list.js +20 -22
  149. package/dist/tools/process-log.js +22 -18
  150. package/dist/tools/question.js +1 -3
  151. package/dist/tools/read-file.js +10 -2
  152. package/dist/tools/recall.js +44 -37
  153. package/dist/tools/registry.js +15 -4
  154. package/dist/tools/remember.js +29 -29
  155. package/dist/tools/scope-check.js +9 -9
  156. package/dist/tools/subagent.js +54 -9
  157. package/dist/tools/user-input.js +1 -1
  158. package/dist/tools/web-browse.js +3 -3
  159. package/dist/tools/web-fetch.js +3 -3
  160. package/dist/tools/web-search.js +3 -3
  161. package/dist/tools/write-file.js +1 -3
  162. package/dist/ui/box.js +1 -5
  163. package/dist/ui/index.js +6 -6
  164. package/dist/ui/line-editor.js +703 -0
  165. package/dist/ui/line-math.js +69 -0
  166. package/dist/ui/md-formatter.js +33 -33
  167. package/dist/ui/output.js +5 -5
  168. package/dist/ui/plan-view.js +103 -0
  169. package/dist/ui/renderer.js +15 -10
  170. package/dist/ui/table.js +1 -1
  171. package/package.json +48 -48
@@ -3,6 +3,7 @@ import { Logger } from "../logger/app-logger";
3
3
  import { OpenAICompatProvider } from "../llm/openai-compat";
4
4
  import { ModelLoader } from "../llm/model-loader";
5
5
  import { ToolRegistry, registerAllTools } from "../tools/index";
6
+ import { buildHiddenToolsBlock } from "../tools/hidden-tools-block";
6
7
  import { ToolExecutor } from "../tools/executor";
7
8
  import { ModuleRegistry } from "../modules/registry";
8
9
  import { PluginManager } from "../modules/plugins/manager";
@@ -13,19 +14,22 @@ import { ContextManager } from "../modules/context/manager";
13
14
  import { TokenCounter } from "../llm/token-counter";
14
15
  import { HallucinationDetector } from "../modules/hallucination/detector";
15
16
  import { ExecutionModule } from "../modules/execution/module";
16
- import { SessionStore, SessionManager, SessionModule, } from "../modules/session/index";
17
+ import { SessionStore, SessionManager, SessionModule } from "../modules/session/index";
17
18
  import { UserProfile } from "../modules/user-profile/profile";
18
19
  import { SkillsLoader, SkillsModule } from "../modules/skills/index";
19
20
  import { BrowserModule } from "../modules/browser/index";
20
- import { LspModule } from "../modules/lsp/index";
21
+ import { LspModule, DEFAULT_LSP_CONFIG } from "../modules/lsp/index";
22
+ import { runStartupHealthCheck } from "../modules/lsp/startup-check";
21
23
  import { IndexerModule } from "../modules/indexer/index";
22
24
  import { MCPModule } from "../modules/mcp/index";
23
25
  import { MemoryModule } from "../modules/memory/module";
26
+ import { MemoryStore } from "../modules/memory/store";
24
27
  import { setLocale } from "../i18n/index";
25
28
  import { Agent } from "./agent";
26
29
  import { homedir } from "os";
27
30
  import { join, resolve } from "path";
28
31
  import { existsSync, readFileSync, writeFileSync } from "fs";
32
+ import { readMmaVersion } from "./version";
29
33
  export function buildSystemInfo(config, baseDir, profileCompressed) {
30
34
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
31
35
  const isWin = profileCompressed.toLowerCase().includes("win32");
@@ -42,12 +46,20 @@ export function buildSystemInfo(config, baseDir, profileCompressed) {
42
46
  // files and tests (observed: model wasted 10+ iterations installing tsx
43
47
  // instead of running `bun file.ts`).
44
48
  lines.push(`Runtime: Bun is available — run TypeScript directly with "bun <file.ts>" and tests with "bun test" (no tsx/ts-node/npm install needed for that).`);
45
- lines.push(`Bash: use "workdir" param instead of "cd dir && cmd". One command per call. Long-running processes: pass "background: true" (id immediately), otherwise auto-backgrounded after a few seconds — check with process_log.`, `DEVELOPMENT RULES (strict): 1) install deps BEFORE writing source (npm/pip/cargo, verify lockfile exists; never import uninstalled packages); 2) framework/toolkit init BEFORE app code; 3) follow plan sequentially, after each step verify deliverables exist with real content, then "plan update step=N status=done"; 4) verify work on disk + command output — don't assume success; 5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, try a different approach, write files directly, ask the user.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact file paths with extensions, exact packages, exact commands). Vague steps ("Setup the project") forbidden. Cover init → deps → framework → code → verification. 5-8 steps.`);
49
+ lines.push(`Bash: use "workdir" param instead of "cd dir && cmd". One command per call. Long-running processes: pass "background: true" (id immediately), otherwise auto-backgrounded after a few seconds — check with process_log.`, `DEVELOPMENT RULES (strict): 1) install deps BEFORE writing source (npm/pip/cargo, verify lockfile exists; never import uninstalled packages); 2) framework/toolkit init BEFORE app code; 3) follow plan sequentially, after each step verify deliverables exist with real content, then "plan update step=N status=done"; 4) verify work on disk + command output — don't assume success; 5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, try a different approach, write files directly, ask the user.`, `VERIFY EVERY WRITE: after write_file or edit_file, if the tool result contains NO [LSP errors], [LSP warnings], [Syntax check failed], or [Project typecheck failed] feedback, call the "lsp_check" tool on the file you just wrote BEFORE marking the step done. Never assume a written file is valid — prove it.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact file paths with extensions, exact packages, exact commands). Vague steps ("Setup the project") forbidden. Cover init → deps → framework → code → verification. 5-8 steps.`, `For "check/fix errors" tasks, each verification step must name the exact command that proves the errors are gone — "bun run build", "bun test", or the "lsp_check" tool — and the step is done only after that check reports clean.`);
46
50
  if (config.autoPlan) {
47
51
  lines.push(`Plan rule (MANDATORY): any task creating files, installing packages, or requiring multiple actions MUST create a plan with the "plan" tool BEFORE starting. Each step = a concrete deliverable.`);
48
52
  }
49
- const hasMCP = config.mcpServers &&
50
- Object.values(config.mcpServers).some((s) => s.enabled !== false);
53
+ // When a bug is described in terms of runtime behavior (what happens when
54
+ // the app runs), the fix is only provable by REPRODUCING it — running the
55
+ // app/build/browser and comparing against actual output. The model decides
56
+ // when this applies (no keyword classification). Stale dev servers started
57
+ // before a config/deps change keep the OLD pipeline in memory and still
58
+ // throw the original error (observed: old vite on 5173-5176 kept failing
59
+ // postcss after the fix was on disk, while a fresh server on 5177 was
60
+ // clean) — the fix is only real when the USER's running server is clean.
61
+ lines.push(`Runtime verification: when a bug is described in terms of runtime behavior (what happens when the app runs), reproduce it — run the app / build / browser tool — and confirm the fix against actual runtime output before calling "plan update step=N status=done". Check existing servers first with process_list/process_log: a stale dev server started BEFORE your change still runs the old pipeline and keeps failing even after the files on disk are fixed. Restart or kill it (process_kill) and verify the exact endpoint/port the user reported — a fresh server on a new port does NOT prove the user's error is gone.`);
62
+ const hasMCP = config.mcpServers && Object.values(config.mcpServers).some((s) => s.enabled !== false);
51
63
  if (hasMCP) {
52
64
  lines.push(`MCP servers are available — use the named MCP tools (prefixed mcp__) to query external services.`);
53
65
  }
@@ -55,9 +67,7 @@ export function buildSystemInfo(config, baseDir, profileCompressed) {
55
67
  }
56
68
  export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
57
69
  const dir = configDir || join(homedir(), ".mma");
58
- const projectConfigPath = projectDir
59
- ? join(projectDir, ".mmrc")
60
- : join(process.cwd(), ".mmrc");
70
+ const projectConfigPath = projectDir ? join(projectDir, ".mmrc") : join(process.cwd(), ".mmrc");
61
71
  const config = loadConfig({ configDir: dir, projectConfigPath });
62
72
  setLocale(config.locale);
63
73
  // Update global audit notifier with config
@@ -126,7 +136,9 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
126
136
  const skillsBudget = Math.floor(config.contextWindow * config.skills.budget);
127
137
  const skillsModule = new SkillsModule(availableSkills, skillsBudget);
128
138
  const toolRegistry = new ToolRegistry();
129
- registerAllTools(toolRegistry, skillsModule);
139
+ registerAllTools(toolRegistry, skillsModule, {
140
+ enableOnDemand: config.tools?.enableOnDemand,
141
+ });
130
142
  const pluginManager = new PluginManager();
131
143
  const systemInfoContent = buildSystemInfo(config, baseDir, profile.compress());
132
144
  const systemInfoPrompt = {
@@ -153,6 +165,17 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
153
165
  sessionManager.create();
154
166
  }
155
167
  const tokenCounter = new TokenCounter(config.model);
168
+ // Mutable set of active tool tags, shared between the agent loop and the
169
+ // `enable_tools` tool. The loop re-reads it every iteration to recompute the
170
+ // LLM-visible tool set; enable_tools pushes tags into it. When on-demand
171
+ // enabling is disabled the tag set stays empty, which restores the legacy
172
+ // all-tools behavior (getAllForLLM returns everything for empty tags).
173
+ const enableOnDemand = config.tools?.enableOnDemand !== false;
174
+ const activeToolTags = enableOnDemand
175
+ ? config.tools?.defaultTags
176
+ ? [...config.tools.defaultTags]
177
+ : []
178
+ : [];
156
179
  const contextManager = new ContextManager(config.contextWindow, config.contextBudget, tokenCounter);
157
180
  const toolCtx = {
158
181
  config,
@@ -160,6 +183,7 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
160
183
  logger,
161
184
  exitOnComplete,
162
185
  contextManager,
186
+ activeToolTags,
163
187
  sessionId: sessionManager.getActiveMeta()?.id,
164
188
  sessionContext: sessionManager.getSessionContext() ?? undefined,
165
189
  recursionDepth: 0,
@@ -206,7 +230,8 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
206
230
  const mcpModule = new MCPModule(config);
207
231
  await mcpModule.initialize();
208
232
  moduleRegistry.register(mcpModule);
209
- const memoryModule = new MemoryModule(join(dir, "memory"));
233
+ const memoryStore = new MemoryStore(join(dir, "memory"));
234
+ const memoryModule = new MemoryModule(memoryStore);
210
235
  moduleRegistry.register(memoryModule);
211
236
  if (config.browser.enabled) {
212
237
  const browserModule = new BrowserModule();
@@ -224,6 +249,16 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
224
249
  lspPlugin.isBuiltin = true;
225
250
  pluginManager.register(lspPlugin);
226
251
  }
252
+ // Startup health check: scan project for existing errors so the model
253
+ // knows the baseline before it starts working. Skip for one-shot runs and
254
+ // test environments. Started in the background (never awaited) so the REPL
255
+ // banner is not blocked by `npx tsc` on the whole project; the Agent awaits
256
+ // it before the first LLM call via `lazyPromptBlocks` (already resolved by
257
+ // the time the user types in the interactive REPL).
258
+ let startupCheckBlock = null;
259
+ const startupCheckPromise = !exitOnComplete && process.env.NODE_ENV !== "test"
260
+ ? runStartupHealthCheck(config.lsp ?? DEFAULT_LSP_CONFIG, baseDir)
261
+ : Promise.resolve(null);
227
262
  const moduleTools = moduleRegistry.collectToolDefinitions();
228
263
  for (const tool of moduleTools) {
229
264
  toolRegistry.register(tool);
@@ -258,8 +293,15 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
258
293
  const pluginLoader = new PluginLoader();
259
294
  const globalPluginsDir = join(homedir(), ".mma", "plugins");
260
295
  const projectPluginsDir = join(baseDir, ".mma", "plugins");
261
- pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
262
- pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
296
+ const mmaVersion = readMmaVersion();
297
+ pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger, {
298
+ source: "global",
299
+ mmaVersion,
300
+ });
301
+ pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger, {
302
+ source: "project",
303
+ mmaVersion,
304
+ });
263
305
  contextManager.onCompact = (summary) => {
264
306
  const meta = sessionManager.getActiveMeta();
265
307
  if (!meta || !config.session.autoSave)
@@ -271,6 +313,11 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
271
313
  timestamp: new Date().toISOString(),
272
314
  });
273
315
  };
316
+ // Carry the active plan (id, progress, current step) into the compaction
317
+ // summary so a 9B model doesn't re-create the plan from scratch right after
318
+ // compaction (observed: ses_msvuao0h — plan_e07pb2 (1/6) discarded for a
319
+ // fresh plan_xzq9xe the same iteration the old plan was still visible).
320
+ contextManager.setPlanSummaryProvider(() => execModule.getPlanSummary());
274
321
  const agentsMdBlocks = [];
275
322
  const skipAgentsMd = noAgentsMd === true;
276
323
  if (!skipAgentsMd) {
@@ -295,7 +342,7 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
295
342
  }
296
343
  const promptBlocks = [
297
344
  systemInfoPrompt,
298
- ...moduleRegistry.collectPromptBlocks(),
345
+ ...moduleRegistry.collectPromptBlocks(["indexer"]),
299
346
  ...agentsMdBlocks,
300
347
  ];
301
348
  const agentDeps = {
@@ -307,6 +354,7 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
307
354
  hallucinationDetector,
308
355
  logger,
309
356
  baseDir,
357
+ toolTags: activeToolTags,
310
358
  promptBlocks,
311
359
  getDynamicPromptBlocks: () => {
312
360
  const blocks = [];
@@ -316,10 +364,29 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
316
364
  const skillsBlock = skillsModule.getSystemPromptBlock();
317
365
  if (skillsBlock)
318
366
  blocks.push(skillsBlock);
367
+ const mapBlock = indexerModule.getSystemPromptBlock();
368
+ if (mapBlock)
369
+ blocks.push(mapBlock);
370
+ const hiddenToolsBlock = buildHiddenToolsBlock(toolRegistry.getAll(), activeToolTags);
371
+ if (hiddenToolsBlock) {
372
+ blocks.push({
373
+ content: hiddenToolsBlock,
374
+ priority: "low",
375
+ essential: false,
376
+ estimatedTokens: Math.ceil(hiddenToolsBlock.length / 4),
377
+ });
378
+ }
319
379
  return blocks;
320
380
  },
321
381
  finalAudit: () => execModule.runFinalAudit(),
382
+ lazyPromptBlocks: async () => {
383
+ if (startupCheckBlock)
384
+ return [startupCheckBlock];
385
+ startupCheckBlock = (await startupCheckPromise) ?? null;
386
+ return startupCheckBlock ? [startupCheckBlock] : [];
387
+ },
322
388
  sessionManager,
389
+ memoryStore,
323
390
  exitOnComplete,
324
391
  };
325
392
  const agent = new Agent(agentDeps);
@@ -1,2 +1,2 @@
1
- export { PromptBuilder } from './prompt-builder';
2
- export { Agent } from './agent';
1
+ export { PromptBuilder } from "./prompt-builder";
2
+ export { Agent } from "./agent";
@@ -4,6 +4,10 @@ const PRIORITY_ORDER = {
4
4
  normal: 2,
5
5
  low: 3,
6
6
  };
7
+ function blockLabel(content) {
8
+ const firstLine = content.split("\n")[0].trim();
9
+ return firstLine.length > 70 ? firstLine.slice(0, 67) + "..." : firstLine;
10
+ }
7
11
  export class PromptBuilder {
8
12
  blocks = [];
9
13
  budget;
@@ -33,23 +37,40 @@ export class PromptBuilder {
33
37
  let usedTokens = 0;
34
38
  const included = [];
35
39
  const excluded = [];
40
+ const blocks = [];
36
41
  for (const block of essential) {
37
42
  included.push(block.content);
38
43
  usedTokens += block.estimatedTokens;
44
+ blocks.push({
45
+ label: blockLabel(block.content),
46
+ priority: block.priority,
47
+ essential: true,
48
+ tokens: block.estimatedTokens,
49
+ included: true,
50
+ });
39
51
  }
40
52
  for (const block of nonEssential) {
41
53
  const tokens = block.estimatedTokens;
42
- if (usedTokens + tokens <= this.budget) {
54
+ const fits = usedTokens + tokens <= this.budget;
55
+ if (fits) {
43
56
  included.push(block.content);
44
57
  usedTokens += tokens;
45
58
  }
46
59
  else {
47
60
  excluded.push(block.content);
48
61
  }
62
+ blocks.push({
63
+ label: blockLabel(block.content),
64
+ priority: block.priority,
65
+ essential: false,
66
+ tokens,
67
+ included: fits,
68
+ });
49
69
  }
50
70
  return {
51
- prompt: included.join('\n\n'),
71
+ prompt: included.join("\n\n"),
52
72
  excluded,
73
+ blocks,
53
74
  };
54
75
  }
55
76
  }
@@ -118,14 +118,56 @@ export class SessionLogger {
118
118
  iteration,
119
119
  });
120
120
  }
121
- logCompaction(content, iteration, contextTokens, contextLimit) {
121
+ logCompaction(info) {
122
122
  this.session?.appendLog({
123
123
  ts: new Date().toISOString(),
124
124
  type: "compaction",
125
- content,
125
+ content: `${info.reason}, iteration ${info.iteration}`,
126
+ iteration: info.iteration,
127
+ reason: info.reason,
128
+ tokensBefore: info.tokensBefore,
129
+ tokensAfter: info.tokensAfter,
130
+ qualityBefore: info.qualityBefore,
131
+ qualityAfter: info.qualityAfter,
132
+ messagesBefore: info.messagesBefore,
133
+ messagesAfter: info.messagesAfter,
134
+ removedTurns: info.removedTurns,
135
+ keptTurns: info.keptTurns,
136
+ summary: info.summary,
137
+ });
138
+ }
139
+ logContext(info) {
140
+ this.session?.appendLog({
141
+ ts: new Date().toISOString(),
142
+ type: "context",
143
+ content: info.kind === "start"
144
+ ? "context snapshot at session start"
145
+ : `context at iteration ${info.iteration}`,
146
+ iteration: info.iteration,
147
+ window: info.window,
148
+ systemBudget: info.systemBudget,
149
+ reserveBudget: info.reserveBudget,
150
+ historyBudget: info.historyBudget,
151
+ systemTokens: info.systemTokens,
152
+ toolTokens: info.toolTokens,
153
+ contextTokens: info.tokens,
154
+ quality: info.quality,
155
+ messageCount: info.messageCount,
156
+ compactionCount: info.compactionCount,
157
+ iterationsSinceCompaction: info.iterationsSinceCompaction,
158
+ blocks: info.blocks,
159
+ });
160
+ }
161
+ logLlmUsage(iteration, usage) {
162
+ this.session?.appendLog({
163
+ ts: new Date().toISOString(),
164
+ type: "llm_usage",
126
165
  iteration,
127
- ...(contextTokens !== undefined ? { contextTokens } : {}),
128
- ...(contextLimit !== undefined ? { contextLimit } : {}),
166
+ promptTokens: usage.promptTokens,
167
+ completionTokens: usage.completionTokens,
168
+ totalTokens: usage.totalTokens,
169
+ source: usage.source,
170
+ durationMs: usage.durationMs,
129
171
  });
130
172
  }
131
173
  logError(message) {
@@ -0,0 +1,24 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ /**
5
+ * Read the MMA version from package.json at runtime (never a static import —
6
+ * bundlers inline a stale value at build time, see design rule #12).
7
+ */
8
+ export function readMmaVersion() {
9
+ const here = dirname(fileURLToPath(import.meta.url));
10
+ const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
11
+ for (const p of candidates) {
12
+ if (existsSync(p)) {
13
+ try {
14
+ const raw = JSON.parse(readFileSync(p, "utf8"));
15
+ if (raw.version)
16
+ return raw.version;
17
+ }
18
+ catch {
19
+ // Broken package.json — fall through to the next candidate
20
+ }
21
+ }
22
+ }
23
+ return "0.0.0";
24
+ }
package/dist/i18n/en.json CHANGED
@@ -28,6 +28,7 @@
28
28
  "error.response_blocked": "Response blocked: {reason}",
29
29
  "error.max_iters": "Max iterations ({max}) reached",
30
30
  "error.empty_response": "Model returned an empty response after retries",
31
+ "error.audit_failed": "Task could not be verified as complete: {summary}",
31
32
  "error.grep_failed": "Grep failed: {message}",
32
33
  "error.search_failed": "Search failed: {message}",
33
34
  "error.fetch_failed": "Fetch failed: {message}",
@@ -86,9 +87,12 @@
86
87
  "tool.friendly.web_search": "Web search",
87
88
  "tool.friendly.web_fetch": "Fetching page",
88
89
  "tool.friendly.web_browse": "Browsing page",
90
+ "tool.friendly.download_file": "Downloading file",
89
91
  "tool.web_fetch_result": "Fetched page: {url} — {chars} chars, {lines} lines{truncated}",
90
92
  "tool.web_browse_result": "Browsed page: {url} — {chars} chars, {lines} lines{truncated}",
91
93
  "tool.web_search_result": "Search results for \"{query}\" — {count} results",
94
+ "tool.downloaded": "Downloaded {url} \u2192 {path} ({size} bytes, {type})",
95
+ "tool.download_too_large": "Download blocked: file exceeds the {max} bytes limit",
92
96
  "tool.friendly.browser": "Browser",
93
97
  "tool.friendly.subagent": "Sub-agent task",
94
98
  "tool.friendly.question": "Question to user",
@@ -96,8 +100,10 @@
96
100
  "tool.friendly.search_history": "Searching history",
97
101
  "tool.friendly.pipeline_run": "Running pipeline",
98
102
  "tool.friendly.mcp_call": "MCP call",
103
+ "tool.friendly.lsp_check": "Checking code",
99
104
  "tool.truncated": "[Truncated: {tokens} tokens removed]",
100
105
  "tool.subagent_queued": "Sub-agent task queued: {task}",
106
+ "tool.subagent_artifact": "Sub-agent completed. Full result saved to artifact: {path}\nIterations: {iterations}\nSummary:\n{summary}",
101
107
  "tool.pipeline_started": "Pipeline \"{name}\" execution started. Pipeline engine is a stub \u2014 run dispatched.",
102
108
  "tool.mcp_call": "MCP call: {server}/{tool} with {args}",
103
109
  "tool.action_required": "Action is required.",
@@ -118,6 +124,8 @@
118
124
  "tool.question.unanswered": "Unanswered",
119
125
  "tool.question.answered": "User has answered your questions: {formatted}. You can now continue with the user's answers in mind.",
120
126
  "tool.name_or_task": "Provide either \"name\" or \"task\" parameter",
127
+ "tool.chunk_query_no_query": "chunk_query: a query string is required.",
128
+ "tool.chunk_query_no_input": "chunk_query: provide input_path or text.",
121
129
  "tool.invalid_params": "Invalid parameters",
122
130
  "tool.skill_budget": "Skill \"{name}\" loaded ({tokens} tokens, {remaining} remaining in skills budget). Skill content is now in system prompt — no need to reload after context compaction.",
123
131
  "tool.skill_available_hint": "Available skills",
@@ -135,6 +143,7 @@
135
143
  "proc.started": "Started background process {id} (PID {pid}).\nCommand: {command}",
136
144
  "proc.promoted_hint": "Command still running after {ms} ms — moved to the background",
137
145
  "proc.manage_hint": "Check output: process_log id={id}. Stop it: process_kill id={id}. List all: process_list.",
146
+ "proc.output_preview": "First output:\n{lines}",
138
147
  "proc.none": "No background processes running.",
139
148
  "proc.not_found": "Process not found: {id}",
140
149
  "proc.killed": "Process {id} (PID {pid}) killed.",
@@ -157,12 +166,20 @@
157
166
  "plan.title_steps": "Plan \"{title}\" created with {count} steps",
158
167
  "plan.step_marked": "Step {step} marked as {status}",
159
168
  "plan.aborted": "Plan aborted",
169
+ "plan.completed_archived": "Plan {id} is complete ({done}/{total} steps) — archived. You may create a new plan for new work (plan create), or reply with your final answer.",
160
170
  "plan.unknown_action": "Unknown plan action: {action}",
161
171
  "plan.updated": "Plan updated: {title} ({steps} steps)",
162
172
  "plan.acknowledged": "Plan {action}: acknowledged",
163
173
  "plan.step_status": "Step {step}: {status}",
164
174
  "plan.no_active": "No active plan",
165
175
  "plan.step_not_found": "Step not found",
176
+ "plan.step_already_done": "Step {step} is already done — nothing to do. Plan progress:",
177
+ "plan.order_blocked": "Cannot mark step {step} done: step {first} (\"{desc}\") is not finished yet. Complete earlier steps first, or mark step {first} status=skipped if it is not needed.",
178
+ "plan.deliverables_missing": "Cannot mark step {step} done: the files it names do not exist yet: {files}. Create these files first (or mark the step status=skipped if they are not actually needed).",
179
+ "plan.kinds_mismatch": "kinds array length must match steps",
180
+ "plan.kinds_invalid": "Invalid step kind(s): {kinds}. Use \"create\" or \"delete\".",
181
+ "plan.kinds_not_array": "kinds must be an array of \"create\" or \"delete\"",
182
+ "plan.deliverables_remain": "Cannot mark step {step} done: the files it names still exist: {files}. This step is kind=delete — delete these files first (or set status=skipped if they should stay).",
166
183
  "plan.show_header": "Plan status:",
167
184
  "plan.show_empty": "(plan has no steps)",
168
185
  "plan.list_header": "Plans:",
@@ -172,9 +189,14 @@
172
189
  "plan.switched": "Switched to plan {id}: {title}",
173
190
  "plan.replanned": "Plan re-planned: {kept} completed steps kept, {steps} new steps added",
174
191
  "plan.replan_no_steps": "Provide new steps for re-planning",
192
+ "plan.active_in_progress": "Cannot create a new plan: active plan {id} already has progress ({done}/{total} done, current: step {current}). Resume it — use \"plan show\" to view, then continue working. To replace it, call \"plan abort\" first, then \"plan create\" again.",
193
+ "plan.id_ignored": "note: plan ids are auto-generated; use \"plan switch\" to activate an existing plan by id.",
194
+ "plan.existing_fresh": "note: the previous active plan had no progress and was preserved as a draft.",
175
195
  "todo.added": "Added {count} todo(s): {items}",
176
196
  "todo.marked_done": "Marked {count} item(s) as done",
177
197
  "todo.no_active": "No active todos",
198
+ "todo.no_items": "No todo items specified — provide items to mark done",
199
+ "todo.subtask_not_found": "No matching sub-task(s) found for: {items}",
178
200
  "todo.unknown_action": "Unknown todo action: {action}",
179
201
  "todo.acknowledged": "Todo acknowledged",
180
202
  "verify.passed": "Verification passed",
@@ -184,6 +206,14 @@
184
206
  "verify.script_passed": "Script '{script}' passed",
185
207
  "verify.script_failed": "Script '{script}' failed: {message}",
186
208
  "verify.syntax_error": "Syntax error in: {path}",
209
+ "verify.no_files": "Step {step} has no named files to verify — run a real check instead (bash: bun run build / bun test / lsp_check) and confirm the result.",
210
+ "lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
211
+ "lsp.check_disabled": "LSP checks are disabled in the config.",
212
+ "lsp.check_no_path": "Provide a path (file or directory) to check.",
213
+ "lsp.check_notfound": "Path not found: {path}",
214
+ "lsp.check_unsupported": "No LSP server configured for: {path}",
215
+ "lsp.check_clean": "No errors or warnings detected ({count} file(s) checked).",
216
+ "lsp.startup_header": "[Existing project errors (checked at session start) — fix these before continuing]:",
187
217
  "cli.description": "Micro Models Agent \u2014 AI coding agent for small models",
188
218
  "cli.init": "Run interactive setup wizard",
189
219
  "cli.config_saved": "Configuration saved to ~/.mma/config.json",
@@ -296,6 +326,13 @@
296
326
  "cli.security.recommended_for": "Recommended for",
297
327
  "cli.yes": "Yes",
298
328
  "cli.no": "No",
329
+ "cli.plugins.description": "Manage plugins",
330
+ "cli.plugins.list": "List loaded plugins (name, version, source)",
331
+ "cli.plugins.all": "Include builtin plugins",
332
+ "cli.plugins.only_external": "(builtin plugins omitted — use --all to show them)",
333
+ "cli.plugins.none": "No plugins loaded",
334
+ "cli.plugins.header": "Plugins: {count} loaded (MMA v{mma})",
335
+ "cli.plugins.builtin_mark": "●",
299
336
  "repl.help": "Show available commands",
300
337
  "repl.help_usage": "Usage: /help",
301
338
  "repl.exit": "Exit the REPL",
@@ -310,6 +347,8 @@
310
347
  "repl.reasoning_usage": "Usage: /reasoning",
311
348
  "repl.status": "Show agent status",
312
349
  "repl.status_usage": "Usage: /status",
350
+ "repl.plugins": "List loaded plugins",
351
+ "repl.plugins_usage": "Usage: /plugins [--all]",
313
352
  "repl.sessions": "List all sessions (* active)",
314
353
  "repl.sessions_usage": "Usage: /sessions",
315
354
  "repl.new": "Create a new session",
@@ -336,6 +375,7 @@
336
375
  "repl.skill_unknown_sub": "Unknown skill subcommand: {subcmd}",
337
376
  "repl.skill_usage": "Usage: /skill [list|loaded|load|unload|search]",
338
377
  "repl.agent": "Agent: ",
378
+ "repl.you": "You: ",
339
379
  "repl.interrupt": "Interrupted (Esc)",
340
380
  "repl.title": "MMA REPL v{version}",
341
381
  "repl.model": "Model:",
@@ -353,6 +393,10 @@
353
393
  "repl.skills_label": "Skills:",
354
394
  "repl.plugins_label": "Plugins:",
355
395
  "repl.mcp_label": "MCP:",
396
+ "repl.lsp_label": "LSP:",
397
+ "repl.lsp_timeout": "timeout",
398
+ "repl.lsp_failed": "did not start",
399
+ "repl.lsp_unknown": "unknown",
356
400
  "repl.work_dir": "Dir:",
357
401
  "repl.agents_label": "Instructions:",
358
402
  "repl.not_found": "not found",
@@ -427,6 +471,8 @@
427
471
  "exec.tool_errors_recovery": "Tool {tool} has failed {count} times in a row. Try an alternative: create files directly via write_file, use a different command, or if nothing works — skip this step via plan update step=N status=skipped with a note explaining why.",
428
472
  "exec.repetitive_tool": "Called {tool} {count} times with identical arguments and result. Try a different approach — create files directly, change arguments, or check process status via process_log.",
429
473
  "exec.consecutive_failures_recovery": "{count} consecutive tool failures. Create files directly via write_file instead of terminal commands. Check that dependencies are installed (npm install). Do not run build/tests until all files are created.",
474
+ "exec.read_only_loop": "No write/exec for {count} tool calls — the agent is only reading/exploring.",
475
+ "exec.read_only_loop_recovery": "{count} consecutive read-only tool calls (read_file/glob/grep/browser) with no writes. STOP exploring and make the edit the task requires: read the file, then call write_file or edit_file. If you cannot finish the task, ask the user instead of re-reading the same files.",
430
476
  "exec.plan_warning": "Current plan step {step} is \"{description}\", but {tool} is being called for files outside this step. Complete the current step first, then call plan update step={step} status=done before moving to the next step.",
431
477
  "exec.plan_blocked": "{max} consecutive calls outside the current step. Finish step {step} before proceeding — other steps should wait until this one is complete.",
432
478
  "exec.off_track": "Step {stepId} — \"{description}\" — but you are using {tool} on a different path. Return to the current step.",
@@ -436,13 +482,18 @@
436
482
  "exec.step_gate_ok": "[\u2713] Step {step} completed and verified. MOVING to step {nextStep}: \"{nextDesc}\". Work ONLY on this step.",
437
483
  "exec.step_gate_last": "[\u2713] Step {step} completed — that was the final step. Verify everything together and provide the final answer.]",
438
484
  "exec.audit_pass": "[\u2713] Task complete: {done}/{total} steps done, {files} files verified",
485
+ "exec.audit_pending": "[\u2717] Task incomplete: {done}/{total} steps done — remaining steps are not marked done",
439
486
  "exec.audit_fail": "[\u2717] Task incomplete: {done}/{total} steps done, {files} files missing",
487
+ "exec.audit_leftovers": "[\u2717] Task incomplete: {done}/{total} steps done, {files} files from delete-steps still exist",
488
+ "exec.audit_fail_tests": "[\u2717] Task incomplete: {done}/{total} steps done, tests FAILING: {failed} failed / {passed} passed — {detail}",
440
489
  "exec.audit_fail_typecheck": "[\u2717] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
441
490
  "exec.audit_incomplete": "[\u26a0 Final audit incomplete: {summary}. Task is NOT finished \u2014 continue working. Remaining steps: {steps}]",
442
491
  "exec.mass_edit_warning": "\u26a0\ufe0f Plan affects {count} files \u2014 review the full list before proceeding.",
443
492
  "exec.escalation": "\n\n\u26a0\ufe0f Agent stuck on step {stepId} ({description}). Escalating to user \u2014 please provide guidance.",
444
493
  "exec.hints": "\n[Hints]\n{hints}",
445
494
  "exec.file_rewrite_warning": "\u26a0\ufe0f File {file} has been rewritten {count} times. Consider a different approach \u2014 the current fix strategy is not working.",
495
+ "exec.forbidden_cmd": "STOP using \"{cmd}\" via the bash tool \u2014 it is not a native Windows cmd.exe command and has failed repeatedly this session. Use the dedicated tool instead: grep \u2192 the grep tool, ls/dir \u2192 list_dir, find \u2192 glob, rm \u2192 delete_file, sed \u2192 edit_file, touch \u2192 write_file, which \u2192 `where`, cp/mv \u2192 move_file, diff \u2192 read_file. Do NOT call bash for this purpose again.",
496
+ "exec.npm_exec_hint": "\"could not determine executable to run\" \u2014 no \"bin\" for that package/script. Use \"npm run <script>\" (script must exist in package.json) or \"bunx <pkg>\" for a package that declares a bin.",
446
497
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
447
498
  "hall.short_response": "Response too short or empty",
448
499
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
@@ -468,11 +519,17 @@
468
519
  "browser.no_page_short": "No page",
469
520
  "browser.no_elements": "(no interactive elements on page)",
470
521
  "browser.more_elements": "... and more elements not shown. Use scroll or search to find others.",
522
+ "browser.content_header": "Content:",
523
+ "browser.console_header": "Console:",
524
+ "browser.network_errors_header": "Network errors:",
525
+ "browser.truncated": "... (truncated)",
471
526
  "pipeline.invalid": "Invalid pipeline: name and steps required",
472
527
  "pipeline.step_missing_fields": "Step missing required fields (id, agent, prompt): {step}",
473
528
  "pipeline.circular": "Circular dependency: {stepId}",
474
529
  "plugin.loaded": "Loaded plugin: {name}",
475
530
  "plugin.skipped": "Skipped incompatible plugin: {entry} ({message})",
531
+ "plugin.incompatible": "Skipped plugin {name}: requires MMA v{min}, current is v{current}",
532
+ "plugin.dedup_older": "Plugin {name} v{version} skipped: a newer version is already loaded",
476
533
  "migration.detected": "[MMA] Detected old config. {summary}",
477
534
  "migration.summary": "[MMA] {summary}",
478
535
  "migration.config_bak": "- config \u2192 config.json.bak",
@@ -483,6 +540,7 @@
483
540
  "ui.success_prefix": "\u2713 ",
484
541
  "ui.warning_prefix": "\u26a0 ",
485
542
  "ui.thinking": "Thinking…",
543
+ "ui.step_context": "step {id}: {desc}",
486
544
  "indexer.map_header": "Project map",
487
545
  "indexer.top_directories": "Top directories",
488
546
  "indexer.files": "Files",
@@ -496,6 +554,10 @@
496
554
  "indexer.not_indexed": "Project has not been indexed yet",
497
555
  "indexer.find_results": "Found {count} matching files:\n{results}",
498
556
  "indexer.no_matches": "No matching files for \"{query}\"",
557
+ "indexer.duplicates": "Duplicate basenames (verify which file is the real source): {names}",
558
+ "indexer.stack_deps": "deps",
559
+ "indexer.stack_dev": "dev",
560
+ "indexer.stack_scripts": "scripts",
499
561
  "tool.friendly.project_map": "Project map",
500
562
  "config.decryption_warning": "Warning: Failed to decrypt config: {error}",
501
563
  "config.encryption_warning": "Warning: Failed to encrypt config: {error}",
@@ -520,6 +582,17 @@
520
582
  "ctx.compactions": "compactions: {count}",
521
583
  "ctx.quality": "quality: {percent}%",
522
584
  "ctx.delta_pos": "ctx +{tokens}",
523
- "ctx.delta_neg": "ctx -{tokens}",
524
- "ctx.delta_zero": "ctx ±0"
585
+ "ctx.delta_neg": "ctx -{tokens} ",
586
+ "ctx.delta_zero": "ctx +0",
587
+ "updater.check_error": "[updater] update check failed: {error}",
588
+ "updater.available": "[updater] Update available: {current} → {latest}. Run `npm install -g micro-models-agent` to upgrade.",
589
+ "updater.installing": "[updater] Installing {latest} globally (current: {current})…",
590
+ "updater.installed": "[updater] Installed {latest}. Restart MMA to use it (was {current}).",
591
+ "updater.install_failed": "[updater] Failed to install {latest}: {error}. Update manually with `npm install -g micro-models-agent`.",
592
+ "tools.enable_no_tags": "enable_tools requires at least one tag in the \"tags\" array.",
593
+ "tools.enable_no_executor": "Tool executor is not available — cannot enumerate enabled tools.",
594
+ "tools.enable_already_active": "Tool tags already active: {tags}.",
595
+ "tools.enable_added": "Enabled tool tags: {tags}. Available tools now: {tools}",
596
+ "tools.hidden_header": "Additional tools (enable on demand via enable_tools or route to subagent tool_tags):",
597
+ "tool.friendly.enable_tools": "Enable tools"
525
598
  }