micro-models-agent 0.51.1 → 0.52.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 (107) hide show
  1. package/dist/cli/commands.js +162 -38
  2. package/dist/cli/completer.js +5 -5
  3. package/dist/cli/main.js +42 -54
  4. package/dist/cli/repl-commands.js +138 -38
  5. package/dist/cli/repl.js +175 -89
  6. package/dist/cli/run-result.js +11 -0
  7. package/dist/cli/security-commands.js +6 -6
  8. package/dist/cli/setup.js +21 -15
  9. package/dist/config/config.js +54 -27
  10. package/dist/config/defaults.js +17 -0
  11. package/dist/config/domains.js +179 -0
  12. package/dist/config/index.js +2 -1
  13. package/dist/config/security.js +28 -8
  14. package/dist/core/agent.js +162 -30
  15. package/dist/core/bootstrap.js +94 -17
  16. package/dist/core/crash-handler.js +51 -0
  17. package/dist/core/environment.js +199 -0
  18. package/dist/core/session-logger.js +60 -6
  19. package/dist/core/version.js +2 -0
  20. package/dist/i18n/en.json +120 -39
  21. package/dist/i18n/ru.json +91 -10
  22. package/dist/llm/openai-compat.js +191 -53
  23. package/dist/llm/orchestrator.js +5 -3
  24. package/dist/logger/app-logger.js +50 -4
  25. package/dist/main.js +1288 -635
  26. package/dist/modules/browser/session.js +4 -0
  27. package/dist/modules/certification/cli.js +58 -19
  28. package/dist/modules/certification/loader.js +2 -1
  29. package/dist/modules/certification/manifest.js +22 -14
  30. package/dist/modules/certification/runner.js +91 -5
  31. package/dist/modules/certification/scenarios.js +290 -7
  32. package/dist/modules/context/fact-extractor.js +6 -0
  33. package/dist/modules/context/manager.js +19 -2
  34. package/dist/modules/execution/audit-runners.js +61 -7
  35. package/dist/modules/execution/execution-plugin.js +219 -60
  36. package/dist/modules/execution/module.js +207 -18
  37. package/dist/modules/execution/moe-executor.js +33 -20
  38. package/dist/modules/execution/plan-store.js +39 -0
  39. package/dist/modules/execution/plan-tool.js +188 -19
  40. package/dist/modules/execution/planner.js +27 -23
  41. package/dist/modules/execution/stuck-detector.js +244 -8
  42. package/dist/modules/execution/tracker.js +8 -6
  43. package/dist/modules/execution/verifier.js +15 -2
  44. package/dist/modules/hallucination/detector.js +4 -0
  45. package/dist/modules/hallucination/factual.js +45 -5
  46. package/dist/modules/indexer/module.js +1 -0
  47. package/dist/modules/lsp/client.js +123 -12
  48. package/dist/modules/lsp/index.js +1 -1
  49. package/dist/modules/lsp/module.js +30 -2
  50. package/dist/modules/lsp/probe.js +11 -1
  51. package/dist/modules/lsp/startup-check.js +5 -2
  52. package/dist/modules/plugins/builtin/lint-on-write.js +144 -41
  53. package/dist/modules/plugins/manager.js +57 -13
  54. package/dist/modules/pricing/index.js +61 -0
  55. package/dist/modules/pricing/prices.js +129 -0
  56. package/dist/modules/providers/create.js +22 -0
  57. package/dist/modules/providers/fallback.js +79 -0
  58. package/dist/modules/providers/health.js +46 -0
  59. package/dist/modules/providers/index.js +5 -0
  60. package/dist/modules/providers/manager.js +161 -0
  61. package/dist/modules/providers/presets.js +128 -0
  62. package/dist/modules/providers/registry.js +22 -0
  63. package/dist/modules/providers/types.js +1 -0
  64. package/dist/modules/registry.js +1 -0
  65. package/dist/modules/security/command-validator.js +14 -0
  66. package/dist/modules/security/encryption.js +6 -6
  67. package/dist/modules/security/network-validator.js +17 -0
  68. package/dist/modules/security/path-validator.js +22 -26
  69. package/dist/modules/session/store.js +10 -10
  70. package/dist/tools/approve.js +1 -0
  71. package/dist/tools/attach-image.js +12 -0
  72. package/dist/tools/bash.js +27 -4
  73. package/dist/tools/browser.js +1 -0
  74. package/dist/tools/chunk-query.js +1 -0
  75. package/dist/tools/create-dir.js +1 -0
  76. package/dist/tools/delete-file.js +1 -0
  77. package/dist/tools/download-file.js +1 -0
  78. package/dist/tools/edit-file.js +2 -1
  79. package/dist/tools/enable-tools.js +1 -0
  80. package/dist/tools/executor.js +17 -7
  81. package/dist/tools/file-info.js +1 -0
  82. package/dist/tools/glob-tool.js +1 -0
  83. package/dist/tools/grep-tool.js +54 -13
  84. package/dist/tools/list-dir.js +1 -0
  85. package/dist/tools/load-skill.js +1 -0
  86. package/dist/tools/mcp-call.js +1 -0
  87. package/dist/tools/move-file.js +1 -0
  88. package/dist/tools/path-utils.js +51 -1
  89. package/dist/tools/pipeline-run.js +1 -0
  90. package/dist/tools/process-kill.js +11 -0
  91. package/dist/tools/process-list.js +1 -0
  92. package/dist/tools/process-log.js +9 -0
  93. package/dist/tools/question.js +1 -0
  94. package/dist/tools/read-file.js +94 -6
  95. package/dist/tools/recall.js +1 -0
  96. package/dist/tools/remember.js +1 -0
  97. package/dist/tools/scope-check.js +7 -5
  98. package/dist/tools/search-history.js +1 -0
  99. package/dist/tools/subagent.js +4 -4
  100. package/dist/tools/web-browse.js +1 -0
  101. package/dist/tools/web-fetch.js +27 -6
  102. package/dist/tools/web-search.js +70 -43
  103. package/dist/tools/write-file.js +1 -0
  104. package/dist/ui/line-editor.js +142 -23
  105. package/dist/ui/line-math.js +8 -4
  106. package/dist/ui/renderer.js +57 -7
  107. package/package.json +50 -48
@@ -0,0 +1,199 @@
1
+ import { existsSync, readFileSync, readdirSync } from "fs";
2
+ import { spawnSync } from "child_process";
3
+ import { createRequire } from "module";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { arch, homedir, hostname, platform, release } from "os";
7
+ import { env } from "process";
8
+ import { readMmaVersion } from "./version";
9
+ import { sanitizeUrl } from "../modules/security/network-validator";
10
+ import { t } from "../i18n/index";
11
+ /** Tools probed for availability on PATH (real executables only — no .cmd shims). */
12
+ const TOOL_CHECKS = ["bun", "node", "git", "python"];
13
+ function readEngineRequirement() {
14
+ const here = dirname(fileURLToPath(import.meta.url));
15
+ const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
16
+ for (const p of candidates) {
17
+ if (!existsSync(p))
18
+ continue;
19
+ try {
20
+ const raw = JSON.parse(readFileSync(p, "utf8"));
21
+ if (raw.engines?.node)
22
+ return String(raw.engines.node);
23
+ }
24
+ catch {
25
+ // Broken package.json — fall through
26
+ }
27
+ }
28
+ return ">=20";
29
+ }
30
+ /** Numeric tuple comparison: does `version` meet the minimum in `requirement` (e.g. `>=20`)? */
31
+ export function satisfiesMinimum(version, requirement) {
32
+ const minMatch = requirement.match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
33
+ if (!minMatch)
34
+ return true;
35
+ const min = [
36
+ parseInt(minMatch[1], 10),
37
+ minMatch[2] ? parseInt(minMatch[2], 10) : 0,
38
+ minMatch[3] ? parseInt(minMatch[3], 10) : 0,
39
+ ];
40
+ const parts = version.replace(/^v/i, "").split(".");
41
+ const got = [
42
+ parts[0] ? parseInt(parts[0], 10) : 0,
43
+ parts[1] ? parseInt(parts[1], 10) : 0,
44
+ parts[2] ? parseInt(parts[2], 10) : 0,
45
+ ];
46
+ for (let i = 0; i < 3; i++) {
47
+ if (got[i] > min[i])
48
+ return true;
49
+ if (got[i] < min[i])
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+ export function detectRuntime() {
55
+ const bun = globalThis.Bun;
56
+ const isBun = typeof bun !== "undefined" && typeof bun?.version !== "undefined";
57
+ const engine = readEngineRequirement();
58
+ const runtime = isBun ? "bun" : "node";
59
+ const runtimeVersion = isBun ? String(bun.version) : process.version;
60
+ return {
61
+ runtime,
62
+ runtimeVersion,
63
+ nodeVersion: process.version,
64
+ engine,
65
+ // engines.node applies to the Node runtime. Bun satisfies it through its
66
+ // embedded Node (process.version) — never compare the Bun version itself
67
+ // against a Node requirement (Bun 1.x would always look "too old").
68
+ engineOk: satisfiesMinimum(process.version, engine),
69
+ };
70
+ }
71
+ function toolVersion(cmd) {
72
+ try {
73
+ const res = spawnSync(cmd, ["--version"], {
74
+ encoding: "utf8",
75
+ timeout: 3000,
76
+ windowsHide: true,
77
+ stdio: ["ignore", "pipe", "pipe"],
78
+ });
79
+ if (res.error || res.status !== 0)
80
+ return "missing";
81
+ const out = ((res.stdout || "") + (res.stderr || "")).trim();
82
+ return out.split(/\r?\n/)[0].slice(0, 40) || "ok";
83
+ }
84
+ catch {
85
+ return "missing";
86
+ }
87
+ }
88
+ /** Where Playwright keeps downloaded browser binaries (env override wins). */
89
+ export function playwrightBrowsersDir() {
90
+ if (process.env.PLAYWRIGHT_BROWSERS_PATH)
91
+ return process.env.PLAYWRIGHT_BROWSERS_PATH;
92
+ return process.platform === "win32"
93
+ ? join(homedir(), "AppData", "Local", "ms-playwright")
94
+ : join(homedir(), ".cache", "ms-playwright");
95
+ }
96
+ /** Playwright package presence/version + whether browser binaries are downloaded. */
97
+ export function playwrightInfo() {
98
+ let installed = false;
99
+ let version = "";
100
+ const browsersDir = playwrightBrowsersDir();
101
+ try {
102
+ // Rule #12: resolve from disk at runtime — works in both src/ and dist/
103
+ // layouts, and under both Bun and Node (the bridge uses createRequire too).
104
+ const require = createRequire(import.meta.url);
105
+ const pkgPath = require.resolve("playwright/package.json");
106
+ installed = existsSync(pkgPath);
107
+ version = JSON.parse(readFileSync(pkgPath, "utf8")).version || "";
108
+ }
109
+ catch {
110
+ installed = false;
111
+ }
112
+ let browsersInstalled = false;
113
+ try {
114
+ if (existsSync(browsersDir)) {
115
+ browsersInstalled = readdirSync(browsersDir).some((d) => /chrom/i.test(d));
116
+ }
117
+ }
118
+ catch {
119
+ browsersInstalled = false;
120
+ }
121
+ return { installed, version, browsersDir, browsersInstalled };
122
+ }
123
+ /** Pure requirements check — produces warnings from a report (no side effects). */
124
+ export function checkEnvironmentRequirements(report) {
125
+ const warnings = [];
126
+ if (report.runtime.runtime === "node") {
127
+ warnings.push(t("env.runtime_node", { version: report.runtime.nodeVersion }));
128
+ }
129
+ if (!report.runtime.engineOk) {
130
+ warnings.push(t("env.runtime_old", {
131
+ version: report.runtime.nodeVersion,
132
+ engine: report.runtime.engine,
133
+ }));
134
+ }
135
+ if (report.tools.bun === "missing") {
136
+ warnings.push(t("env.tool_missing", { tool: "bun" }));
137
+ }
138
+ if (report.tools.git === "missing") {
139
+ warnings.push(t("env.tool_missing", { tool: "git" }));
140
+ }
141
+ if (report.features.browser && report.playwright) {
142
+ if (!report.playwright.installed) {
143
+ warnings.push(t("env.playwright_missing"));
144
+ }
145
+ else if (!report.playwright.browsersInstalled) {
146
+ warnings.push(t("env.playwright_browsers_missing", { dir: report.playwright.browsersDir }));
147
+ }
148
+ }
149
+ return warnings;
150
+ }
151
+ export function collectEnvironment(opts) {
152
+ const runtime = detectRuntime();
153
+ const tools = {};
154
+ if (opts.scanTools) {
155
+ for (const tool of TOOL_CHECKS)
156
+ tools[tool] = toolVersion(tool);
157
+ }
158
+ const provider = opts.config
159
+ ? {
160
+ model: opts.config.model,
161
+ baseUrl: sanitizeUrl(opts.config.provider?.baseUrl || ""),
162
+ contextWindow: opts.config.contextWindow,
163
+ retry: {
164
+ maxRetries: opts.config.retry?.maxRetries ?? 0,
165
+ baseDelay: opts.config.retry?.baseDelay ?? 0,
166
+ maxDelay: opts.config.retry?.maxDelay ?? 0,
167
+ maxStreamRetries: opts.config.retry?.maxStreamRetries ?? 0,
168
+ noDataTimeoutMs: opts.config.retry?.noDataTimeoutMs ?? 0,
169
+ },
170
+ }
171
+ : null;
172
+ const report = {
173
+ ts: new Date().toISOString(),
174
+ mmaVersion: readMmaVersion(),
175
+ runtime,
176
+ os: {
177
+ platform: platform(),
178
+ arch: arch(),
179
+ release: release(),
180
+ hostname: hostname(),
181
+ shell: env.SHELL || env.ComSpec || "unknown",
182
+ home: homedir(),
183
+ cwd: process.cwd(),
184
+ },
185
+ paths: { configDir: opts.configDir, baseDir: opts.baseDir || process.cwd() },
186
+ tools,
187
+ provider,
188
+ features: { browser: Boolean(opts.config?.browser?.enabled) },
189
+ playwright: playwrightInfo(),
190
+ warnings: [],
191
+ };
192
+ report.warnings = checkEnvironmentRequirements(report);
193
+ return report;
194
+ }
195
+ /** Compact human line + full structured report into the JSONL log. */
196
+ export function logEnvironment(report, logger) {
197
+ logger.info(`Environment: ${report.os.platform} ${report.os.arch} | ${report.runtime.runtime} ${report.runtime.runtimeVersion} (node ${report.runtime.nodeVersion}) | mma ${report.mmaVersion} | ${report.provider?.model ?? "no provider"}`);
198
+ logger.logStructured("environment", report);
199
+ }
@@ -1,4 +1,7 @@
1
1
  import { setAuditSessionDir } from "../modules/security/audit-log";
2
+ import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
3
+ import { join } from "path";
4
+ import { readFileSync, existsSync } from "fs";
2
5
  /**
3
6
  * Thin wrapper around SessionManager that eliminates repetitive
4
7
  * `if (sessionManager)` + `new Date().toISOString()` boilerplate
@@ -9,9 +12,11 @@ import { setAuditSessionDir } from "../modules/security/audit-log";
9
12
  */
10
13
  export class SessionLogger {
11
14
  session;
15
+ getCaller;
12
16
  logger;
13
- constructor(session, logger) {
17
+ constructor(session, logger, getCaller) {
14
18
  this.session = session;
19
+ this.getCaller = getCaller;
15
20
  this.logger = logger;
16
21
  this.bindSessionDir();
17
22
  }
@@ -37,24 +42,28 @@ export class SessionLogger {
37
42
  });
38
43
  }
39
44
  logUser(content) {
45
+ const sanitized = sanitizeLogMessage(content);
40
46
  this.session?.appendMessage({
41
47
  role: "user",
42
- content,
48
+ content: sanitized,
43
49
  timestamp: new Date().toISOString(),
44
50
  });
45
51
  this.session?.appendLog({
46
52
  ts: new Date().toISOString(),
47
53
  type: "user",
48
- content,
54
+ content: sanitized,
49
55
  });
50
56
  }
51
57
  logAssistant(content, reasoning, toolCalls, iteration) {
58
+ const caller = this.getCaller?.();
52
59
  if (reasoning) {
53
60
  this.session?.appendLog({
54
61
  ts: new Date().toISOString(),
55
62
  type: "reasoning",
56
63
  content: reasoning,
57
64
  iteration,
65
+ provider: caller?.provider,
66
+ model: caller?.model,
58
67
  });
59
68
  }
60
69
  this.session?.appendLog({
@@ -71,13 +80,18 @@ export class SessionLogger {
71
80
  }
72
81
  : {}),
73
82
  iteration,
83
+ provider: caller?.provider,
84
+ model: caller?.model,
74
85
  });
75
86
  }
76
87
  saveAssistantMessage(content) {
88
+ const caller = this.getCaller?.();
77
89
  this.session?.appendMessage({
78
90
  role: "assistant",
79
91
  content: content.slice(0, 500),
80
92
  timestamp: new Date().toISOString(),
93
+ provider: caller?.provider,
94
+ model: caller?.model,
81
95
  });
82
96
  }
83
97
  logToolDefs(toolCount, toolNames, iteration) {
@@ -95,16 +109,19 @@ export class SessionLogger {
95
109
  type: "tool_call",
96
110
  tool: call.name,
97
111
  tool_call_id: call.id,
98
- args: call.arguments,
112
+ args: JSON.parse(sanitizeLogMessage(JSON.stringify(call.arguments))),
99
113
  iteration,
100
114
  });
101
115
  }
102
116
  logToolResult(call, result, duration, iteration) {
117
+ const caller = this.getCaller?.();
103
118
  this.session?.appendMessage({
104
119
  role: "tool",
105
- content: result.output.slice(0, 500),
120
+ content: sanitizeLogMessage(result.output.slice(0, 500)),
106
121
  name: call.name,
107
122
  timestamp: new Date().toISOString(),
123
+ provider: caller?.provider,
124
+ model: caller?.model,
108
125
  });
109
126
  this.session?.appendLog({
110
127
  ts: new Date().toISOString(),
@@ -112,10 +129,12 @@ export class SessionLogger {
112
129
  tool: call.name,
113
130
  tool_call_id: call.id,
114
131
  success: result.success,
115
- content: result.output.slice(0, 1000),
132
+ content: sanitizeLogMessage(result.output.slice(0, 1000)),
116
133
  diff: result.diff,
117
134
  duration,
118
135
  iteration,
136
+ provider: caller?.provider,
137
+ model: caller?.model,
119
138
  });
120
139
  }
121
140
  logCompaction(info) {
@@ -144,6 +163,8 @@ export class SessionLogger {
144
163
  ? "context snapshot at session start"
145
164
  : `context at iteration ${info.iteration}`,
146
165
  iteration: info.iteration,
166
+ provider: info.provider,
167
+ model: info.model,
147
168
  window: info.window,
148
169
  systemBudget: info.systemBudget,
149
170
  reserveBudget: info.reserveBudget,
@@ -163,6 +184,8 @@ export class SessionLogger {
163
184
  ts: new Date().toISOString(),
164
185
  type: "llm_usage",
165
186
  iteration,
187
+ provider: usage.provider,
188
+ model: usage.model,
166
189
  promptTokens: usage.promptTokens,
167
190
  completionTokens: usage.completionTokens,
168
191
  totalTokens: usage.totalTokens,
@@ -194,4 +217,35 @@ export class SessionLogger {
194
217
  ...(iteration !== undefined ? { iteration } : {}),
195
218
  });
196
219
  }
220
+ /**
221
+ * Log session startup diagnostics: environment, LSP probe, plugins, skills.
222
+ * Should be the FIRST entry in session.jsonl so the file is self-describing.
223
+ * Skips if a session_start entry already exists (e.g. logged by REPL).
224
+ */
225
+ logSessionStart(data) {
226
+ // Skip if session_start already logged (REPL may have logged it with LSP probe)
227
+ const meta = this.session?.getActiveMeta();
228
+ if (meta) {
229
+ const logPath = join(this.session.getSessionDirectory(meta.id), "session.jsonl");
230
+ try {
231
+ if (existsSync(logPath)) {
232
+ const content = readFileSync(logPath, "utf-8");
233
+ if (content.includes('"type":"session_start"'))
234
+ return;
235
+ }
236
+ }
237
+ catch {
238
+ // File read error — proceed to write
239
+ }
240
+ }
241
+ this.session?.appendLog({
242
+ ts: new Date().toISOString(),
243
+ type: "session_start",
244
+ content: "session started",
245
+ environment: data.environment,
246
+ lspProbe: data.lspProbe,
247
+ plugins: data.plugins,
248
+ skills: data.skills,
249
+ });
250
+ }
197
251
  }
@@ -22,3 +22,5 @@ export function readMmaVersion() {
22
22
  }
23
23
  return "0.0.0";
24
24
  }
25
+ /** Legacy alias — use readMmaVersion(). */
26
+ export const readVersion = readMmaVersion;