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
@@ -186,6 +186,10 @@ export class BrowserSession {
186
186
  async open(url) {
187
187
  if (!url)
188
188
  return { success: false, output: t("browser.url_required") };
189
+ // Security: block dangerous schemes (file:, chrome:, about:, data:).
190
+ if (/^(file|chrome|about|data|javascript|view-source):/i.test(url)) {
191
+ return { success: false, output: `[SECURITY BLOCKED] scheme not allowed: ${url}` };
192
+ }
189
193
  if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
190
194
  const isLocalhost = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/i.test(url);
191
195
  url = isLocalhost ? "http://" + url : "https://" + url;
@@ -1,5 +1,4 @@
1
- import { rmSync } from "fs";
2
- import { homedir } from "os";
1
+ import { rmSync, writeFileSync } from "fs";
3
2
  import { join, dirname } from "path";
4
3
  import { fileURLToPath } from "url";
5
4
  import { existsSync, readFileSync } from "fs";
@@ -7,10 +6,9 @@ import { t } from "../../i18n/index";
7
6
  import { pc } from "../../ui/colors";
8
7
  import { loadScenarios, filterByTags } from "./loader";
9
8
  import { runScenario, findMmaRoot } from "./runner";
10
- import { readManifest, upsertCertification, removeCertification } from "./manifest";
9
+ import { readManifest, upsertCertification, removeCertification, isFullyPassed } from "./manifest";
11
10
  const HERE = dirname(fileURLToPath(import.meta.url));
12
11
  const MMA_ROOT = findMmaRoot(HERE);
13
- const USER_SCENARIO_DIR = join(homedir(), ".mma", "certification", "scenarios");
14
12
  function readVersion() {
15
13
  const candidates = [join(MMA_ROOT, "package.json")];
16
14
  for (const p of candidates) {
@@ -35,12 +33,7 @@ export function parseTags(s) {
35
33
  }
36
34
  export async function certify(opts) {
37
35
  const providerUrl = opts.providerUrl || opts.config.provider.baseUrl;
38
- if (opts.tags.includes("security") && !opts.config.security?.enabled) {
39
- console.error(pc.red(t("cli.cert_security_required")));
40
- process.exitCode = 1;
41
- return;
42
- }
43
- const { scenarios, errors } = loadScenarios(USER_SCENARIO_DIR);
36
+ const { scenarios, errors } = loadScenarios(join(opts.projectDir, ".mma", "certification", "scenarios"));
44
37
  for (const e of errors)
45
38
  console.error(pc.yellow(` ${e}`));
46
39
  const selected = filterByTags(scenarios, opts.tags);
@@ -49,7 +42,7 @@ export async function certify(opts) {
49
42
  process.exitCode = 1;
50
43
  return;
51
44
  }
52
- const manifest = readManifest();
45
+ const manifest = readManifest(opts.projectDir);
53
46
  const existing = manifest.certifications.find((e) => e.model === opts.name && e.providerUrl === providerUrl);
54
47
  if (existing && !opts.force) {
55
48
  console.error(pc.yellow(t("cli.cert_exists", { model: opts.name })));
@@ -84,6 +77,7 @@ export async function certify(opts) {
84
77
  sandboxBase,
85
78
  defaultReps: opts.reps,
86
79
  defaultThreshold: 2,
80
+ baseConfig: opts.config,
87
81
  onRep: (id, rep, reps, passed, failures) => {
88
82
  const word = passed ? pc.green(t("cli.cert_rep_pass")) : pc.red(t("cli.cert_rep_fail"));
89
83
  console.log(`[${idx}/${total}] ${id} (${rep}/${reps})... ${word}`);
@@ -110,7 +104,7 @@ export async function certify(opts) {
110
104
  suite,
111
105
  results,
112
106
  };
113
- upsertCertification(entry);
107
+ upsertCertification(entry, opts.projectDir);
114
108
  console.log(t("cli.cert_done", {
115
109
  passed: String(suite.passed),
116
110
  failed: String(suite.failed),
@@ -118,9 +112,17 @@ export async function certify(opts) {
118
112
  total: String(suite.total),
119
113
  }));
120
114
  printResults(results);
115
+ if (isFullyPassed(entry)) {
116
+ console.log(pc.green(`\n✔ ${opts.name} is certified`));
117
+ }
118
+ else {
119
+ console.log(pc.yellow(`\n⚠ ${opts.name} is NOT certified — all scenarios must pass`));
120
+ }
121
+ // Always write report file
122
+ writeReport(entry, opts.projectDir);
121
123
  }
122
- export async function certStatus(name, config) {
123
- const m = readManifest();
124
+ export async function certStatus(name, config, projectDir) {
125
+ const m = readManifest(projectDir);
124
126
  const providerUrl = config.provider.baseUrl;
125
127
  const entry = m.certifications.find((e) => e.model === name && e.providerUrl === providerUrl);
126
128
  if (!entry) {
@@ -130,20 +132,25 @@ export async function certStatus(name, config) {
130
132
  console.log(`${t("cli.cert_provider_col")}: ${entry.providerUrl}`);
131
133
  console.log(`${t("cli.cert_version_col")}: ${entry.mmaVersion} ${t("cli.cert_date_col")}: ${entry.certifiedAt.slice(0, 10)}`);
132
134
  console.log(`${t("cli.cert_suite_col")}: ${entry.suite.passed} pass / ${entry.suite.failed} fail / ${entry.suite.skipped} skipped`);
135
+ const status = isFullyPassed(entry)
136
+ ? pc.green("✔ certified")
137
+ : pc.yellow("⚠ NOT certified");
138
+ console.log(`Status: ${status}`);
133
139
  printResults(entry.results);
134
140
  }
135
- export async function certList() {
136
- const m = readManifest();
141
+ export async function certList(projectDir) {
142
+ const m = readManifest(projectDir);
137
143
  if (m.certifications.length === 0) {
138
144
  console.log(t("cli.cert_empty"));
139
145
  return;
140
146
  }
141
147
  for (const e of m.certifications) {
142
- console.log(` ${pc.green("✔")} ${e.model} ${pc.dim(e.providerUrl)} ${e.mmaVersion} ${e.certifiedAt.slice(0, 10)} ${e.suite.passed}/${e.suite.total} pass`);
148
+ const mark = isFullyPassed(e) ? pc.green("✔") : pc.red("✘");
149
+ console.log(` ${mark} ${e.model} ${pc.dim(e.providerUrl)} ${e.mmaVersion} ${e.certifiedAt.slice(0, 10)} ${e.suite.passed}/${e.suite.total} pass`);
143
150
  }
144
151
  }
145
- export async function uncertify(name, config) {
146
- const removed = removeCertification(name, config.provider.baseUrl);
152
+ export async function uncertify(name, config, projectDir) {
153
+ const removed = removeCertification(name, config.provider.baseUrl, projectDir);
147
154
  if (removed)
148
155
  console.log(t("cli.cert_uncertified", { model: name }));
149
156
  else
@@ -170,5 +177,37 @@ function printResults(results) {
170
177
  console.log(` ${icon} ${r.id} ${detail}`);
171
178
  if (r.error)
172
179
  console.log(` ${pc.dim(r.error)}`);
180
+ if (r.diagnostics)
181
+ console.log(r.diagnostics);
182
+ }
183
+ }
184
+ function writeReport(entry, projectDir) {
185
+ const reportDir = join(projectDir, "certification");
186
+ const ts = entry.certifiedAt.replace(/[:.]/g, "-").slice(0, 19);
187
+ const filename = `report-${entry.model.replace(/[/\\:]/g, "_")}-${ts}.json`;
188
+ const reportPath = join(reportDir, filename);
189
+ const report = {
190
+ model: entry.model,
191
+ providerUrl: entry.providerUrl,
192
+ mmaVersion: entry.mmaVersion,
193
+ certifiedAt: entry.certifiedAt,
194
+ certified: isFullyPassed(entry),
195
+ suite: entry.suite,
196
+ results: entry.results.map((r) => ({
197
+ id: r.id,
198
+ title: r.title,
199
+ status: r.status,
200
+ passed: r.passed,
201
+ of: r.of,
202
+ error: r.error,
203
+ diagnostics: r.diagnostics,
204
+ })),
205
+ };
206
+ try {
207
+ writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
208
+ console.log(pc.dim(`\nReport: ${reportPath}`));
209
+ }
210
+ catch (e) {
211
+ console.error(pc.yellow(` Failed to write report: ${e.message}`));
173
212
  }
174
213
  }
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "fs";
2
2
  import { join } from "path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { BUILTIN_SCENARIOS } from "./scenarios";
5
- const TAGS = ["core", "security", "image", "network", "browser"];
5
+ const TAGS = ["core", "security", "image", "network", "browser", "moe"];
6
6
  const CHECK_TYPES = [
7
7
  "fileExists",
8
8
  "fileNotExists",
@@ -95,6 +95,7 @@ function normalizeScenario(data, file) {
95
95
  fixtures: Array.isArray(d.fixtures) ? d.fixtures : undefined,
96
96
  checks: Array.isArray(d.checks) ? d.checks : [],
97
97
  skipReason: typeof d.skipReason === "string" ? d.skipReason : undefined,
98
+ config: typeof d.config === "object" && d.config !== null ? d.config : undefined,
98
99
  };
99
100
  }
100
101
  export function filterByTags(scenarios, tags) {
@@ -1,8 +1,10 @@
1
1
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
2
- import { homedir } from "os";
3
2
  import { join } from "path";
4
- export const MANIFEST_PATH = join(homedir(), ".mma", "certifications.json");
5
- export function readManifest(path = MANIFEST_PATH) {
3
+ export function manifestPath(projectDir) {
4
+ return join(projectDir, "certification", "certifications.json");
5
+ }
6
+ export function readManifest(projectDir) {
7
+ const path = manifestPath(projectDir);
6
8
  try {
7
9
  if (existsSync(path)) {
8
10
  const raw = JSON.parse(readFileSync(path, "utf-8"));
@@ -14,26 +16,27 @@ export function readManifest(path = MANIFEST_PATH) {
14
16
  }
15
17
  return { version: 1, certifications: [] };
16
18
  }
17
- export function saveManifest(m, path = MANIFEST_PATH) {
18
- mkdirSync(join(homedir(), ".mma"), { recursive: true });
19
+ export function saveManifest(m, projectDir) {
20
+ const path = manifestPath(projectDir);
21
+ mkdirSync(join(projectDir, "certification"), { recursive: true });
19
22
  writeFileSync(path, JSON.stringify(m, null, 2), "utf-8");
20
23
  }
21
- export function upsertCertification(entry, path = MANIFEST_PATH) {
22
- const m = readManifest(path);
24
+ export function upsertCertification(entry, projectDir) {
25
+ const m = readManifest(projectDir);
23
26
  const idx = m.certifications.findIndex((e) => e.model === entry.model && e.providerUrl === entry.providerUrl);
24
27
  if (idx >= 0)
25
28
  m.certifications[idx] = entry;
26
29
  else
27
30
  m.certifications.push(entry);
28
- saveManifest(m, path);
31
+ saveManifest(m, projectDir);
29
32
  return m;
30
33
  }
31
- export function removeCertification(model, providerUrl, path = MANIFEST_PATH) {
32
- const m = readManifest(path);
34
+ export function removeCertification(model, providerUrl, projectDir) {
35
+ const m = readManifest(projectDir);
33
36
  const before = m.certifications.length;
34
37
  m.certifications = m.certifications.filter((e) => !(e.model === model && e.providerUrl === providerUrl));
35
38
  if (m.certifications.length !== before) {
36
- saveManifest(m, path);
39
+ saveManifest(m, projectDir);
37
40
  return true;
38
41
  }
39
42
  return false;
@@ -41,10 +44,15 @@ export function removeCertification(model, providerUrl, path = MANIFEST_PATH) {
41
44
  export function isStale(entry, currentVersion) {
42
45
  return entry.mmaVersion !== currentVersion;
43
46
  }
44
- export function getCertMark(model, providerUrl, currentVersion, path = MANIFEST_PATH) {
45
- const m = readManifest(path);
47
+ export function isFullyPassed(entry) {
48
+ return entry.suite.failed === 0 && entry.suite.passed > 0;
49
+ }
50
+ export function getCertMark(model, providerUrl, currentVersion, projectDir) {
51
+ const m = readManifest(projectDir);
46
52
  const entry = m.certifications.find((e) => e.model === model && e.providerUrl === providerUrl);
47
53
  if (!entry)
48
54
  return "none";
49
- return isStale(entry, currentVersion) ? "stale" : "certified";
55
+ if (isStale(entry, currentVersion))
56
+ return "stale";
57
+ return isFullyPassed(entry) ? "certified" : "none";
50
58
  }
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "child_process";
2
- import { existsSync, mkdirSync, rmSync, cpSync } from "fs";
2
+ import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readdirSync, readFileSync } from "fs";
3
3
  import { platform } from "os";
4
- import { join, resolve, dirname } from "path";
4
+ import { join, resolve, dirname, relative } from "path";
5
5
  import { checkSandbox } from "./fact-checker";
6
6
  export async function runScenario(scenario, opts) {
7
7
  if (scenario.mode === "skip") {
@@ -16,10 +16,11 @@ export async function runScenario(scenario, opts) {
16
16
  const reps = scenario.reps ?? opts.defaultReps;
17
17
  const threshold = Math.min(scenario.passThreshold ?? opts.defaultThreshold, reps);
18
18
  const runner = opts.runner ?? defaultRunner;
19
- const timeoutMs = opts.timeoutMs ?? 120_000;
19
+ const timeoutMs = opts.timeoutMs ?? 300_000;
20
20
  const entryPoint = resolveMmaEntry(opts.mmaRoot);
21
21
  let passed = 0;
22
22
  let firstError;
23
+ let lastFailedSandbox;
23
24
  for (let i = 1; i <= reps; i++) {
24
25
  const sandbox = join(opts.sandboxBase, `run-${scenario.id}-${i}`);
25
26
  let failures = [];
@@ -43,6 +44,14 @@ export async function runScenario(scenario, opts) {
43
44
  };
44
45
  if (opts.providerKey)
45
46
  env.MMA_PROVIDER_APIKEY = opts.providerKey;
47
+ // Write cert-specific config if scenario declares overrides
48
+ if (scenario.config && opts.baseConfig) {
49
+ const merged = deepMergeAny(opts.baseConfig, scenario.config);
50
+ const certConfigDir = join(sandbox, ".mma");
51
+ mkdirSync(certConfigDir, { recursive: true });
52
+ writeFileSync(join(certConfigDir, "config.json"), JSON.stringify(merged, null, 2), "utf-8");
53
+ env.MMA_CONFIG_DIR = certConfigDir;
54
+ }
46
55
  const res = await runner(env, opts.mmaRoot, args, timeoutMs);
47
56
  output = `${res.stdout}\n${res.stderr}`;
48
57
  exitCode = res.code ?? -1;
@@ -64,11 +73,17 @@ export async function runScenario(scenario, opts) {
64
73
  const pass = failures.length === 0;
65
74
  if (pass)
66
75
  passed++;
67
- else if (!firstError)
68
- firstError = failures.join("; ");
76
+ else {
77
+ if (!firstError)
78
+ firstError = failures.join("; ");
79
+ lastFailedSandbox = sandbox;
80
+ }
69
81
  opts.onRep?.(scenario.id, i, reps, pass, failures);
70
82
  }
71
83
  const status = passed >= threshold ? "pass" : "fail";
84
+ const diagnostics = status === "fail" && lastFailedSandbox
85
+ ? collectDiagnostics(lastFailedSandbox)
86
+ : undefined;
72
87
  return {
73
88
  id: scenario.id,
74
89
  title: scenario.title,
@@ -76,6 +91,7 @@ export async function runScenario(scenario, opts) {
76
91
  passed,
77
92
  of: reps,
78
93
  error: status === "pass" ? undefined : firstError,
94
+ diagnostics,
79
95
  };
80
96
  }
81
97
  function prepareSandbox(sandbox, scenario, mmaRoot) {
@@ -91,6 +107,57 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
91
107
  cpSync(src, dest);
92
108
  }
93
109
  }
110
+ /** Collect diagnostic info from a failed sandbox for actionable feedback. */
111
+ export function collectDiagnostics(sandbox) {
112
+ const lines = [];
113
+ // List user-created files (skip .mma internals)
114
+ const files = listFiles(sandbox, sandbox);
115
+ if (files.length > 0) {
116
+ lines.push(` Files created: ${files.join(", ")}`);
117
+ }
118
+ else {
119
+ lines.push(" Files created: (none)");
120
+ }
121
+ // Read active plan if present
122
+ const planPath = join(sandbox, ".mma", "plans", "active.json");
123
+ if (existsSync(planPath)) {
124
+ try {
125
+ const plan = JSON.parse(readFileSync(planPath, "utf-8"));
126
+ const steps = plan.steps ?? [];
127
+ const done = steps.filter((s) => s.status === "done").length;
128
+ const pending = steps.filter((s) => s.status === "pending" || s.status === "in_progress");
129
+ lines.push(` Plan: ${done}/${steps.length} steps done`);
130
+ if (pending.length > 0) {
131
+ lines.push(` Pending: ${pending.map((s) => `#${s.id} ${s.description}`).join("; ")}`);
132
+ }
133
+ }
134
+ catch {
135
+ /* corrupt plan — skip */
136
+ }
137
+ }
138
+ return lines.join("\n");
139
+ }
140
+ function listFiles(dir, root) {
141
+ const result = [];
142
+ try {
143
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
144
+ if (entry.name === ".mma")
145
+ continue;
146
+ const abs = join(dir, entry.name);
147
+ const rel = relative(root, abs).replace(/\\/g, "/");
148
+ if (entry.isDirectory()) {
149
+ result.push(...listFiles(abs, root));
150
+ }
151
+ else {
152
+ result.push(rel);
153
+ }
154
+ }
155
+ }
156
+ catch {
157
+ /* ignore */
158
+ }
159
+ return result;
160
+ }
94
161
  export function resolveMmaEntry(mmaRoot) {
95
162
  const dev = join(mmaRoot, "src", "cli", "main.ts");
96
163
  if (existsSync(dev))
@@ -157,3 +224,22 @@ function killTree(child) {
157
224
  }
158
225
  }
159
226
  }
227
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
+ function deepMergeAny(target, source) {
229
+ if (!source || typeof source !== "object")
230
+ return source;
231
+ if (!target || typeof target !== "object")
232
+ return source;
233
+ const result = { ...target };
234
+ for (const key of Object.keys(source)) {
235
+ const sv = source[key];
236
+ const tv = result[key];
237
+ if (sv && typeof sv === "object" && !Array.isArray(sv) && tv && typeof tv === "object" && !Array.isArray(tv)) {
238
+ result[key] = deepMergeAny(tv, sv);
239
+ }
240
+ else {
241
+ result[key] = sv;
242
+ }
243
+ }
244
+ return result;
245
+ }