micro-models-agent 0.63.0 → 1.1.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.
- package/CHANGELOG.md +174 -0
- package/dist/cli/cache-line.js +30 -0
- package/dist/cli/command-suggest.js +38 -0
- package/dist/cli/commands.js +285 -60
- package/dist/cli/completer.js +16 -16
- package/dist/cli/json-payload.js +32 -0
- package/dist/cli/main.js +165 -77
- package/dist/cli/plugin-commands.js +5 -4
- package/dist/cli/relaunch.js +37 -0
- package/dist/cli/repl-commands.js +441 -307
- package/dist/cli/repl.js +360 -83
- package/dist/cli/run-result.js +12 -6
- package/dist/cli/security-commands.js +64 -60
- package/dist/cli/setup-order.js +57 -0
- package/dist/cli/setup-prompt.js +49 -0
- package/dist/cli/setup.js +52 -48
- package/dist/config/budget.js +48 -0
- package/dist/config/config.js +132 -70
- package/dist/config/defaults.js +37 -11
- package/dist/config/domains.js +9 -50
- package/dist/config/utils.js +56 -0
- package/dist/core/agent/audit-gate.js +49 -0
- package/dist/core/agent/compaction.js +89 -0
- package/dist/core/agent/constants.js +61 -0
- package/dist/core/agent/context-renderer.js +40 -0
- package/dist/core/agent/hallucination-gate.js +87 -0
- package/dist/core/agent/loop-state.js +53 -0
- package/dist/core/agent/prefix-monitor.js +101 -0
- package/dist/core/agent/reasoning-resolver.js +56 -0
- package/dist/core/agent/token-tracker.js +96 -0
- package/dist/core/agent/tool-batch.js +237 -0
- package/dist/core/agent/tool-output.js +62 -0
- package/dist/core/agent-moe.js +214 -69
- package/dist/core/agent.js +506 -546
- package/dist/core/bootstrap.js +297 -98
- package/dist/core/crash-handler.js +2 -1
- package/dist/core/prompt-builder.js +3 -0
- package/dist/core/prompt-overflow.js +307 -0
- package/dist/core/session-logger.js +34 -2
- package/dist/i18n/en.json +8 -4
- package/dist/i18n/ru.json +8 -4
- package/dist/index.js +5 -1
- package/dist/llm/cache-usage.js +76 -0
- package/dist/llm/image-utils.js +20 -16
- package/dist/llm/llm-errors.js +41 -0
- package/dist/llm/model-loader.js +30 -0
- package/dist/llm/openai-compat.js +287 -101
- package/dist/llm/orchestrator.js +140 -68
- package/dist/llm/provider-budget.js +68 -0
- package/dist/llm/provider.js +0 -1
- package/dist/llm/stream-state.js +26 -0
- package/dist/llm/token-counter.js +28 -0
- package/dist/logger/app-logger.js +12 -15
- package/dist/main.js +1755 -841
- package/dist/migration/detect.js +3 -1
- package/dist/modules/browser/actions.js +0 -3
- package/dist/modules/browser/bridge-client.js +2 -0
- package/dist/modules/browser/bridge-server.mjs +37 -4
- package/dist/modules/browser/driver.js +46 -4
- package/dist/modules/certification/cli.js +85 -42
- package/dist/modules/certification/loader.js +15 -1
- package/dist/modules/certification/manifest.js +126 -15
- package/dist/modules/certification/runner.js +4 -26
- package/dist/modules/certification/scenarios.js +184 -5
- package/dist/modules/certification/syntax-scenarios.js +51 -0
- package/dist/modules/context/chunk-query.js +25 -5
- package/dist/modules/context/fact-extractor.js +6 -2
- package/dist/modules/context/manager.js +23 -7
- package/dist/modules/execution/audit-runners.js +7 -1
- package/dist/modules/execution/auditor.js +3 -3
- package/dist/modules/execution/execution-plugin.js +22 -15
- package/dist/modules/execution/input-from.js +46 -0
- package/dist/modules/execution/module.js +107 -18
- package/dist/modules/execution/moe-executor.js +166 -54
- package/dist/modules/execution/plan-actions.js +524 -0
- package/dist/modules/execution/plan-steps.js +23 -0
- package/dist/modules/execution/plan-store.js +15 -3
- package/dist/modules/execution/plan-tool.js +6 -488
- package/dist/modules/execution/plan-validator.js +24 -0
- package/dist/modules/execution/stuck-detector.js +3 -18
- package/dist/modules/execution/tracker.js +14 -5
- package/dist/modules/execution/transient-error.js +30 -0
- package/dist/modules/execution/verifier.js +94 -7
- package/dist/modules/execution/windows-commands.js +11 -0
- package/dist/modules/hallucination/confidence.js +36 -23
- package/dist/modules/hallucination/consistency.js +3 -0
- package/dist/modules/hallucination/detector.js +8 -3
- package/dist/modules/hallucination/factual.js +26 -7
- package/dist/modules/hallucination/llm-judge.js +12 -2
- package/dist/modules/indexer/map-command.js +35 -0
- package/dist/modules/indexer/map-select.js +87 -0
- package/dist/modules/indexer/module.js +34 -22
- package/dist/modules/indexer/symbols.js +189 -0
- package/dist/modules/indexer/walker.js +96 -42
- package/dist/modules/lsp/check-tool.js +2 -1
- package/dist/modules/lsp/client.js +49 -32
- package/dist/modules/lsp/config.js +55 -2
- package/dist/modules/lsp/module.js +38 -5
- package/dist/modules/lsp/probe.js +4 -3
- package/dist/modules/lsp/project-root.js +41 -1
- package/dist/modules/lsp/startup-check.js +12 -4
- package/dist/modules/mcp/client.js +153 -104
- package/dist/modules/mcp/module.js +165 -41
- package/dist/modules/memory/module.js +4 -3
- package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
- package/dist/modules/plugins/manager.js +47 -84
- package/dist/modules/pricing/index.js +17 -7
- package/dist/modules/pricing/prices.js +30 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +56 -0
- package/dist/modules/processes/registry.js +2 -54
- package/dist/modules/providers/cache.js +23 -0
- package/dist/modules/providers/factory.js +28 -0
- package/dist/modules/providers/fallback.js +7 -5
- package/dist/modules/providers/health.js +2 -1
- package/dist/modules/providers/index.js +1 -0
- package/dist/modules/providers/manager.js +17 -2
- package/dist/modules/providers/presets.js +79 -6
- package/dist/modules/reasoning/policy.js +40 -0
- package/dist/modules/reasoning/probe.js +111 -0
- package/dist/modules/security/audit-notifier.js +42 -27
- package/dist/modules/security/command-validator.js +25 -20
- package/dist/modules/security/encryption.js +6 -12
- package/dist/modules/security/network-validator.js +76 -5
- package/dist/modules/security/path-validator.js +77 -34
- package/dist/modules/security/rate-limiter.js +11 -0
- package/dist/modules/security/security-policies.js +1 -1
- package/dist/modules/security/session-encryption.js +13 -2
- package/dist/modules/security/session-isolation.js +2 -9
- package/dist/modules/session/manager.js +11 -0
- package/dist/modules/session/module.js +11 -3
- package/dist/modules/session/store.js +41 -5
- package/dist/modules/skills/loader.js +7 -1
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/changelog-reader.js +94 -0
- package/dist/modules/updater/dev-detect.js +17 -0
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +14 -3
- package/dist/output/bus.js +32 -0
- package/dist/output/channel.js +233 -0
- package/dist/output/format.js +14 -0
- package/dist/output/index.js +7 -0
- package/dist/output/json-sink.js +22 -0
- package/dist/output/machine.js +8 -0
- package/dist/output/session-sink.js +27 -0
- package/dist/output/types.js +1 -0
- package/dist/tools/approve.js +6 -2
- package/dist/tools/attach-image.js +11 -11
- package/dist/tools/auto-fixer.js +198 -0
- package/dist/tools/bash.js +142 -89
- package/dist/tools/chunk-query.js +10 -6
- package/dist/tools/download-file.js +1 -1
- package/dist/tools/edit-file.js +20 -2
- package/dist/tools/executor.js +54 -9
- package/dist/tools/glob-tool.js +7 -0
- package/dist/tools/grep-tool.js +15 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/list-dir.js +3 -1
- package/dist/tools/load-skill.js +2 -1
- package/dist/tools/mcp-call.js +1 -1
- package/dist/tools/move-file.js +5 -4
- package/dist/tools/path-utils.js +7 -0
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/prompt-io.js +28 -0
- package/dist/tools/question.js +12 -12
- package/dist/tools/scope-request.js +91 -0
- package/dist/tools/session-info.js +44 -0
- package/dist/tools/set-thinking.js +71 -0
- package/dist/tools/subagent.js +50 -9
- package/dist/tools/syntax-validator.js +177 -0
- package/dist/tools/user-input.js +16 -9
- package/dist/tools/write-file.js +17 -1
- package/dist/ui/diff.js +10 -0
- package/dist/ui/line-editor.js +179 -26
- package/dist/ui/line-math.js +20 -3
- package/dist/ui/md-formatter.js +100 -10
- package/dist/ui/output.js +5 -4
- package/dist/ui/plan-view.js +2 -7
- package/dist/ui/renderer.js +89 -85
- package/dist/ui/spinner.js +14 -4
- package/dist/utils/error.js +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/retry.js +17 -0
- package/dist/utils/sleep.js +23 -0
- package/dist/utils/truncate.js +9 -0
- package/package.json +1 -1
package/dist/config/config.js
CHANGED
|
@@ -5,11 +5,14 @@ import { DEFAULTS } from "./defaults";
|
|
|
5
5
|
import { DEFAULT_SECURITY_CONFIG } from "./security";
|
|
6
6
|
import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
|
|
7
7
|
import { t } from "../i18n/index";
|
|
8
|
+
import { deepMerge, regexReplacer, regexReviver } from "./utils";
|
|
9
|
+
export { deepMerge };
|
|
8
10
|
import { MigrationDetector } from "../migration/detect";
|
|
9
11
|
import { BackupManager } from "../migration/backup";
|
|
10
12
|
import { validateExpertConfig } from "./experts";
|
|
11
13
|
import { ConfigEncryptor } from "../modules/security/encryption";
|
|
12
14
|
import { loadDomainFiles, saveDomainFiles } from "./domains";
|
|
15
|
+
import { defaultOutputBus } from "../output";
|
|
13
16
|
// ── Centralized path constants ──────────────────────────────────────────
|
|
14
17
|
export const CONFIG_DIR = join(homedir(), ".mma");
|
|
15
18
|
export const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
@@ -42,50 +45,6 @@ function restoreDangerousPatterns(patterns, defaults) {
|
|
|
42
45
|
return fallback[i] || fallback[0] || p;
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
|
-
function deepMerge(target, source) {
|
|
46
|
-
const result = { ...target };
|
|
47
|
-
for (const key of Object.keys(source)) {
|
|
48
|
-
const srcVal = source[key];
|
|
49
|
-
const tgtVal = target[key];
|
|
50
|
-
if (srcVal !== null &&
|
|
51
|
-
srcVal !== undefined &&
|
|
52
|
-
typeof srcVal === "object" &&
|
|
53
|
-
!Array.isArray(srcVal) &&
|
|
54
|
-
typeof tgtVal === "object" &&
|
|
55
|
-
!Array.isArray(tgtVal)) {
|
|
56
|
-
result[key] = deepMerge(tgtVal, srcVal);
|
|
57
|
-
}
|
|
58
|
-
else if (srcVal !== undefined) {
|
|
59
|
-
result[key] = srcVal;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* JSON replacer that serializes RegExp objects as {__regex, source, flags}
|
|
66
|
-
* so they survive JSON.stringify/parse round-trips.
|
|
67
|
-
*/
|
|
68
|
-
function regexReplacer(_key, value) {
|
|
69
|
-
if (value instanceof RegExp) {
|
|
70
|
-
return { __regex: true, source: value.source, flags: value.flags };
|
|
71
|
-
}
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* JSON reviver that restores RegExp objects serialized by regexReplacer.
|
|
76
|
-
*/
|
|
77
|
-
function regexReviver(_key, value) {
|
|
78
|
-
if (value && typeof value === "object" && value.__regex === true) {
|
|
79
|
-
const { source, flags } = value;
|
|
80
|
-
try {
|
|
81
|
-
return new RegExp(source, flags);
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return value;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
48
|
/**
|
|
90
49
|
* Legacy npx `args` for LSP servers that predate the fixed invocation.
|
|
91
50
|
*/
|
|
@@ -117,50 +76,133 @@ function normalizeLspServerArgs(config) {
|
|
|
117
76
|
}
|
|
118
77
|
}
|
|
119
78
|
function loadJSON(path) {
|
|
79
|
+
if (!existsSync(path))
|
|
80
|
+
return null;
|
|
120
81
|
try {
|
|
121
|
-
|
|
122
|
-
return JSON.parse(readFileSync(path, "utf-8"), regexReviver);
|
|
123
|
-
}
|
|
82
|
+
return JSON.parse(readFileSync(path, "utf-8"), regexReviver);
|
|
124
83
|
}
|
|
125
84
|
catch {
|
|
126
|
-
|
|
85
|
+
// Never silent: a typo'd config must not quietly wipe the user's settings.
|
|
86
|
+
defaultOutputBus.log("warn", "config", t("config.parse_failed", { path }));
|
|
127
87
|
}
|
|
128
88
|
return null;
|
|
129
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Apply MMA_* env overrides. Returns the overridden keys with their PRE-ENV
|
|
92
|
+
* values so `saveConfig` can avoid persisting session-scoped env values (C4).
|
|
93
|
+
*/
|
|
130
94
|
function applyEnvVars(config) {
|
|
131
95
|
const env = process.env;
|
|
96
|
+
const overrides = [];
|
|
132
97
|
const result = { ...config };
|
|
133
|
-
|
|
98
|
+
const record = (key, envValue) => {
|
|
99
|
+
const original = result[key];
|
|
100
|
+
if (original !== envValue)
|
|
101
|
+
overrides.push({ key, envValue, originalValue: original });
|
|
102
|
+
};
|
|
103
|
+
if (env.MMA_MODEL) {
|
|
104
|
+
record("model", env.MMA_MODEL);
|
|
134
105
|
result.model = env.MMA_MODEL;
|
|
135
|
-
|
|
106
|
+
}
|
|
107
|
+
const originalProvider = result.provider;
|
|
108
|
+
// normalizeEntries prefers entries[] and ignores the flat provider fields, so
|
|
109
|
+
// a flat-only env override is a silent no-op on multi-provider configs (the
|
|
110
|
+
// certification harness depends on these vars working). Resolve the active
|
|
111
|
+
// entry and patch it too.
|
|
112
|
+
const activeEntryIndex = (() => {
|
|
113
|
+
const entries = result.provider.entries;
|
|
114
|
+
if (!entries || entries.length === 0)
|
|
115
|
+
return -1;
|
|
116
|
+
const active = result.provider.active;
|
|
117
|
+
if (!active)
|
|
118
|
+
return 0;
|
|
119
|
+
const i = entries.findIndex((e) => e.label === active || e.type === active);
|
|
120
|
+
return i >= 0 ? i : 0;
|
|
121
|
+
})();
|
|
122
|
+
const patchActiveEntry = (patch) => {
|
|
123
|
+
if (activeEntryIndex < 0)
|
|
124
|
+
return;
|
|
125
|
+
const entries = result.provider.entries;
|
|
126
|
+
const next = [...entries];
|
|
127
|
+
next[activeEntryIndex] = { ...next[activeEntryIndex], ...patch };
|
|
128
|
+
result.provider = { ...result.provider, entries: next };
|
|
129
|
+
};
|
|
130
|
+
if (env.MMA_PROVIDER_TYPE) {
|
|
136
131
|
result.provider = { ...result.provider, type: env.MMA_PROVIDER_TYPE };
|
|
137
|
-
|
|
132
|
+
patchActiveEntry({ type: env.MMA_PROVIDER_TYPE });
|
|
133
|
+
}
|
|
134
|
+
if (env.MMA_PROVIDER_BASEURL) {
|
|
138
135
|
result.provider = { ...result.provider, baseUrl: env.MMA_PROVIDER_BASEURL };
|
|
139
|
-
|
|
136
|
+
patchActiveEntry({ baseUrl: env.MMA_PROVIDER_BASEURL });
|
|
137
|
+
}
|
|
138
|
+
if (env.MMA_PROVIDER_APIKEY) {
|
|
140
139
|
result.provider = { ...result.provider, apiKey: env.MMA_PROVIDER_APIKEY };
|
|
141
|
-
|
|
140
|
+
patchActiveEntry({ apiKey: env.MMA_PROVIDER_APIKEY });
|
|
141
|
+
}
|
|
142
|
+
if (env.MMA_PROVIDER_TYPE || env.MMA_PROVIDER_BASEURL || env.MMA_PROVIDER_APIKEY) {
|
|
143
|
+
// Record once with the FINAL env-mutated provider object so the identity
|
|
144
|
+
// check in stripEnvOverrides matches what the loaded config holds.
|
|
145
|
+
overrides.push({ key: "provider", envValue: result.provider, originalValue: originalProvider });
|
|
146
|
+
}
|
|
147
|
+
if (env.MMA_LOG_LEVEL) {
|
|
148
|
+
record("logLevel", env.MMA_LOG_LEVEL);
|
|
142
149
|
result.logLevel = env.MMA_LOG_LEVEL;
|
|
143
|
-
|
|
150
|
+
}
|
|
151
|
+
if (env.MMA_LOCALE) {
|
|
152
|
+
record("locale", env.MMA_LOCALE);
|
|
144
153
|
result.locale = env.MMA_LOCALE;
|
|
154
|
+
}
|
|
145
155
|
if (env.MMA_CONTEXT_WINDOW) {
|
|
146
156
|
const v = parseInt(env.MMA_CONTEXT_WINDOW, 10);
|
|
147
|
-
if (!isNaN(v))
|
|
157
|
+
if (!isNaN(v)) {
|
|
158
|
+
record("contextWindow", v);
|
|
148
159
|
result.contextWindow = v;
|
|
160
|
+
}
|
|
149
161
|
}
|
|
150
162
|
if (env.MMA_MAX_TOOL_ITERATIONS) {
|
|
151
163
|
const v = parseInt(env.MMA_MAX_TOOL_ITERATIONS, 10);
|
|
152
|
-
if (!isNaN(v))
|
|
164
|
+
if (!isNaN(v)) {
|
|
165
|
+
record("maxToolIterations", v);
|
|
153
166
|
result.maxToolIterations = v;
|
|
167
|
+
}
|
|
154
168
|
}
|
|
155
169
|
if (env.MMA_STUCK_THRESHOLD) {
|
|
156
170
|
const v = parseInt(env.MMA_STUCK_THRESHOLD, 10);
|
|
157
|
-
if (!isNaN(v))
|
|
171
|
+
if (!isNaN(v)) {
|
|
172
|
+
record("stuckThreshold", v);
|
|
158
173
|
result.stuckThreshold = v;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (env.MMA_AUTO_PLAN) {
|
|
177
|
+
const v = env.MMA_AUTO_PLAN === "true";
|
|
178
|
+
record("autoPlan", v);
|
|
179
|
+
result.autoPlan = v;
|
|
180
|
+
}
|
|
181
|
+
if (env.MMA_MOE_ENABLED) {
|
|
182
|
+
const next = { ...result.moe, enabled: env.MMA_MOE_ENABLED === "true" };
|
|
183
|
+
record("moe", next);
|
|
184
|
+
result.moe = next;
|
|
185
|
+
}
|
|
186
|
+
return { config: result, overrides };
|
|
187
|
+
}
|
|
188
|
+
/** Pre-env values for env-touched keys, keyed by the loaded config object. */
|
|
189
|
+
const envOverrideMap = new WeakMap();
|
|
190
|
+
/**
|
|
191
|
+
* Restore pre-env values before persisting — session-scoped env overrides
|
|
192
|
+
* (e.g. the cert harness baseUrl) must NOT leak into domain files. An explicit
|
|
193
|
+
* later change (setProvider/reconfigure replacing provider, /model use) shows
|
|
194
|
+
* up as a different object/value and is kept as-is.
|
|
195
|
+
*/
|
|
196
|
+
function stripEnvOverrides(config) {
|
|
197
|
+
const overrides = envOverrideMap.get(config);
|
|
198
|
+
if (!overrides || overrides.length === 0)
|
|
199
|
+
return config;
|
|
200
|
+
const result = { ...config };
|
|
201
|
+
for (const o of overrides) {
|
|
202
|
+
if (result[o.key] === o.envValue) {
|
|
203
|
+
result[o.key] = o.originalValue;
|
|
204
|
+
}
|
|
159
205
|
}
|
|
160
|
-
if (env.MMA_AUTO_PLAN)
|
|
161
|
-
result.autoPlan = env.MMA_AUTO_PLAN === "true";
|
|
162
|
-
if (env.MMA_MOE_ENABLED)
|
|
163
|
-
result.moe = { ...result.moe, enabled: env.MMA_MOE_ENABLED === "true" };
|
|
164
206
|
return result;
|
|
165
207
|
}
|
|
166
208
|
/**
|
|
@@ -184,15 +226,24 @@ export function loadConfig(options) {
|
|
|
184
226
|
const detector = new MigrationDetector(options.configDir);
|
|
185
227
|
if (detector.needsMigration()) {
|
|
186
228
|
const backup = new BackupManager(options.configDir);
|
|
229
|
+
// Full-directory backup (documented behavior) + config.json snapshot
|
|
230
|
+
// before we touch anything.
|
|
231
|
+
backup.backupAll();
|
|
187
232
|
backup.backupConfig();
|
|
188
233
|
const summary = backup.getBackupSummary();
|
|
189
|
-
|
|
234
|
+
defaultOutputBus.log("info", "config", t("migration.detected", { summary }));
|
|
190
235
|
try {
|
|
191
236
|
unlinkSync(globalPath);
|
|
192
237
|
}
|
|
193
|
-
catch {
|
|
238
|
+
catch (e) {
|
|
239
|
+
// Best-effort removal: loadConfig falls back to defaults either way,
|
|
240
|
+
// but a leftover v1 file would re-trigger migration on every startup.
|
|
241
|
+
defaultOutputBus.log("error", "config", `[config] failed to remove legacy config.json: ${e instanceof Error ? e.message : String(e)}`);
|
|
242
|
+
}
|
|
194
243
|
}
|
|
195
|
-
|
|
244
|
+
// Deep clone: DEFAULTS holds singleton objects (security, lsp, tools…)
|
|
245
|
+
// that must never be mutated by runtime config changes.
|
|
246
|
+
let config = structuredClone(DEFAULTS);
|
|
196
247
|
let legacyDetected = false;
|
|
197
248
|
// Step 1: legacy config.json (fallback)
|
|
198
249
|
const globalData = loadJSON(globalPath);
|
|
@@ -221,7 +272,8 @@ export function loadConfig(options) {
|
|
|
221
272
|
}
|
|
222
273
|
normalizeLspServerArgs(config);
|
|
223
274
|
// Step 6: env vars
|
|
224
|
-
config = applyEnvVars(config);
|
|
275
|
+
const { config: envConfig, overrides } = applyEnvVars(config);
|
|
276
|
+
config = envConfig;
|
|
225
277
|
// Step 7: decrypt
|
|
226
278
|
try {
|
|
227
279
|
const encryptor = new ConfigEncryptor();
|
|
@@ -229,10 +281,18 @@ export function loadConfig(options) {
|
|
|
229
281
|
for (const key of Object.keys(decrypted)) {
|
|
230
282
|
config[key] = decrypted[key];
|
|
231
283
|
}
|
|
284
|
+
// Decrypt rebuilds the object tree (new identities). Re-point the provider
|
|
285
|
+
// override at the post-decrypt provider so the save-time identity check in
|
|
286
|
+
// stripEnvOverrides still matches the loaded config.
|
|
287
|
+
const providerOverride = overrides.find((o) => o.key === "provider");
|
|
288
|
+
if (providerOverride)
|
|
289
|
+
providerOverride.envValue = config.provider;
|
|
232
290
|
}
|
|
233
291
|
catch (e) {
|
|
234
|
-
|
|
292
|
+
defaultOutputBus.log("warn", "config", t("config.decryption_warning", { error: e.message }));
|
|
235
293
|
}
|
|
294
|
+
if (overrides.length > 0)
|
|
295
|
+
envOverrideMap.set(config, overrides);
|
|
236
296
|
return { config, legacyDetected };
|
|
237
297
|
}
|
|
238
298
|
export function validateConfig(config, allToolTags) {
|
|
@@ -255,8 +315,10 @@ export function validateConfig(config, allToolTags) {
|
|
|
255
315
|
* @param configDir Optional config directory (e.g. ~/.mma). When provided, writes domain files.
|
|
256
316
|
*/
|
|
257
317
|
export function saveConfig(config, configPath, configDir) {
|
|
318
|
+
// Session-scoped MMA_* env overrides must not be persisted (C4).
|
|
319
|
+
const toWrite = stripEnvOverrides(config);
|
|
258
320
|
if (configDir) {
|
|
259
|
-
saveDomainFiles(
|
|
321
|
+
saveDomainFiles(toWrite, configDir);
|
|
260
322
|
return;
|
|
261
323
|
}
|
|
262
324
|
// Legacy single-file write (backward compat for tests / edge cases)
|
|
@@ -265,12 +327,12 @@ export function saveConfig(config, configPath, configDir) {
|
|
|
265
327
|
try {
|
|
266
328
|
const encryptor = new ConfigEncryptor();
|
|
267
329
|
const encryptedConfig = encryptor.encrypt({
|
|
268
|
-
...
|
|
330
|
+
...toWrite,
|
|
269
331
|
});
|
|
270
332
|
writeFileSync(configPath, JSON.stringify(encryptedConfig, regexReplacer, 2), "utf-8");
|
|
271
333
|
}
|
|
272
334
|
catch (e) {
|
|
273
|
-
|
|
274
|
-
writeFileSync(configPath, JSON.stringify(
|
|
335
|
+
defaultOutputBus.log("warn", "config", t("config.encryption_warning", { error: e.message }));
|
|
336
|
+
writeFileSync(configPath, JSON.stringify(toWrite, regexReplacer, 2), "utf-8");
|
|
275
337
|
}
|
|
276
338
|
}
|
package/dist/config/defaults.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_SECURITY_CONFIG } from "./security";
|
|
2
2
|
import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
|
|
3
|
+
/** Single source of truth for the default model (experts inherit it, C8). */
|
|
4
|
+
const DEFAULT_MODEL = "qwen/qwen3.5-9b";
|
|
3
5
|
export const DEFAULTS = {
|
|
4
6
|
version: "2.0.0",
|
|
5
|
-
model:
|
|
7
|
+
model: DEFAULT_MODEL,
|
|
6
8
|
provider: {
|
|
7
9
|
type: "openai-compat",
|
|
8
10
|
baseUrl: "http://localhost:1234/v1",
|
|
@@ -14,31 +16,42 @@ export const DEFAULTS = {
|
|
|
14
16
|
},
|
|
15
17
|
moe: {
|
|
16
18
|
enabled: false,
|
|
19
|
+
maxReplanCycles: 3,
|
|
17
20
|
},
|
|
18
21
|
tools: {
|
|
22
|
+
// TODO(2026-08-29): revisit tool-set visibility. enableOnDemand:false
|
|
23
|
+
// restores the full ~28-tool set (legacy behavior) — safe for small
|
|
24
|
+
// models but costs schema tokens per call. The on-demand narrowing
|
|
25
|
+
// (defaultTags + enable_tools) kept the footprint small but made the 9B
|
|
26
|
+
// model call hidden tools as shell commands / drift to MCP. Decide the
|
|
27
|
+
// right trade-off for small models and pick one mode.
|
|
19
28
|
defaultTags: ["file", "code"],
|
|
20
|
-
enableOnDemand:
|
|
29
|
+
enableOnDemand: false,
|
|
21
30
|
},
|
|
22
31
|
experts: {
|
|
23
32
|
code: {
|
|
24
|
-
model:
|
|
33
|
+
model: DEFAULT_MODEL,
|
|
25
34
|
tool_tags: ["file", "code", "shell"],
|
|
26
35
|
max_attempts: 3,
|
|
36
|
+
description: "file operations, code editing, shell commands",
|
|
27
37
|
},
|
|
28
38
|
research: {
|
|
29
|
-
model:
|
|
39
|
+
model: DEFAULT_MODEL,
|
|
30
40
|
tool_tags: ["research"],
|
|
31
41
|
max_attempts: 3,
|
|
42
|
+
description: "web search, web fetch, web browse, history search",
|
|
32
43
|
},
|
|
33
44
|
browser: {
|
|
34
|
-
model:
|
|
45
|
+
model: DEFAULT_MODEL,
|
|
35
46
|
tool_tags: ["browser", "vision"],
|
|
36
47
|
max_attempts: 3,
|
|
48
|
+
description: "browser automation, screenshots",
|
|
37
49
|
},
|
|
38
50
|
vision: {
|
|
39
|
-
model:
|
|
51
|
+
model: DEFAULT_MODEL,
|
|
40
52
|
tool_tags: ["browser", "vision", "file"],
|
|
41
53
|
max_attempts: 2,
|
|
54
|
+
description: "browser + file reading for visual tasks",
|
|
42
55
|
},
|
|
43
56
|
},
|
|
44
57
|
contextWindow: 32768,
|
|
@@ -47,6 +60,9 @@ export const DEFAULTS = {
|
|
|
47
60
|
responseReserve: 0.15,
|
|
48
61
|
compactionThreshold: 0.75,
|
|
49
62
|
},
|
|
63
|
+
instructions: {
|
|
64
|
+
summarize: true,
|
|
65
|
+
},
|
|
50
66
|
modelLoad: {
|
|
51
67
|
autoLoad: false,
|
|
52
68
|
flashAttention: true,
|
|
@@ -62,7 +78,7 @@ export const DEFAULTS = {
|
|
|
62
78
|
maxToolIterations: 1000,
|
|
63
79
|
stuckThreshold: 6,
|
|
64
80
|
autoPlan: true,
|
|
65
|
-
showReasoning:
|
|
81
|
+
showReasoning: true,
|
|
66
82
|
logLevel: "info",
|
|
67
83
|
locale: "en",
|
|
68
84
|
session: {
|
|
@@ -98,10 +114,9 @@ export const DEFAULTS = {
|
|
|
98
114
|
},
|
|
99
115
|
ui: {
|
|
100
116
|
spinner: true,
|
|
101
|
-
toolStyle: "inline",
|
|
102
|
-
toolComments: true,
|
|
103
117
|
showContextStats: false,
|
|
104
118
|
showCompaction: true,
|
|
119
|
+
verbose: false,
|
|
105
120
|
},
|
|
106
121
|
mcpServers: {
|
|
107
122
|
context7: {
|
|
@@ -124,6 +139,11 @@ export const DEFAULTS = {
|
|
|
124
139
|
skills: {
|
|
125
140
|
budget: 0.15,
|
|
126
141
|
},
|
|
142
|
+
hallucination: {
|
|
143
|
+
judge: {
|
|
144
|
+
enabled: false,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
127
147
|
subagent: {
|
|
128
148
|
resultMode: "text",
|
|
129
149
|
maxSummaryChars: 2000,
|
|
@@ -134,8 +154,14 @@ export const DEFAULTS = {
|
|
|
134
154
|
lsp: DEFAULT_LSP_CONFIG,
|
|
135
155
|
updater: {
|
|
136
156
|
enabled: true,
|
|
137
|
-
checkOnStart:
|
|
138
|
-
autoInstall:
|
|
157
|
+
checkOnStart: false,
|
|
158
|
+
autoInstall: false,
|
|
139
159
|
intervalMs: 0,
|
|
140
160
|
},
|
|
161
|
+
reasoning: {
|
|
162
|
+
mode: "auto",
|
|
163
|
+
min: "low",
|
|
164
|
+
max: "high",
|
|
165
|
+
overrideCooldown: 5,
|
|
166
|
+
},
|
|
141
167
|
};
|
package/dist/config/domains.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { t } from "../i18n/index";
|
|
4
|
+
import { deepMerge, regexReplacer, regexReviver } from "./utils";
|
|
5
|
+
import { defaultOutputBus } from "../output";
|
|
3
6
|
/**
|
|
4
7
|
* Mapping: domain name → AppConfig keys that belong to it.
|
|
5
8
|
* Each domain becomes one JSON file in ~/.mma/config/<domain>.json.
|
|
@@ -10,6 +13,7 @@ export const CONFIG_DOMAINS = {
|
|
|
10
13
|
"model",
|
|
11
14
|
"contextWindow",
|
|
12
15
|
"contextBudget",
|
|
16
|
+
"instructions",
|
|
13
17
|
"modelLoad",
|
|
14
18
|
"maxToolIterations",
|
|
15
19
|
"stuckThreshold",
|
|
@@ -17,6 +21,8 @@ export const CONFIG_DOMAINS = {
|
|
|
17
21
|
"showReasoning",
|
|
18
22
|
"logLevel",
|
|
19
23
|
"locale",
|
|
24
|
+
"reasoning",
|
|
25
|
+
"hallucination",
|
|
20
26
|
],
|
|
21
27
|
provider: ["provider", "orchestrator", "retry", "pricing"],
|
|
22
28
|
moe: ["moe", "experts"],
|
|
@@ -31,55 +37,6 @@ export const CONFIG_DOMAINS = {
|
|
|
31
37
|
skills: ["skills"],
|
|
32
38
|
updater: ["updater"],
|
|
33
39
|
};
|
|
34
|
-
/**
|
|
35
|
-
* JSON replacer that serializes RegExp objects as {__regex, source, flags}
|
|
36
|
-
* so they survive JSON.stringify/parse round-trips.
|
|
37
|
-
*/
|
|
38
|
-
function regexReplacer(_key, value) {
|
|
39
|
-
if (value instanceof RegExp) {
|
|
40
|
-
return { __regex: true, source: value.source, flags: value.flags };
|
|
41
|
-
}
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* JSON reviver that restores RegExp objects serialized by regexReplacer.
|
|
46
|
-
*/
|
|
47
|
-
function regexReviver(_key, value) {
|
|
48
|
-
if (value &&
|
|
49
|
-
typeof value === "object" &&
|
|
50
|
-
value.__regex === true) {
|
|
51
|
-
const { source, flags } = value;
|
|
52
|
-
try {
|
|
53
|
-
return new RegExp(source, flags);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Deep merge two objects. Arrays and scalars in source replace target.
|
|
63
|
-
*/
|
|
64
|
-
function deepMerge(target, source) {
|
|
65
|
-
const result = { ...target };
|
|
66
|
-
for (const key of Object.keys(source)) {
|
|
67
|
-
const srcVal = source[key];
|
|
68
|
-
const tgtVal = target[key];
|
|
69
|
-
if (srcVal !== null &&
|
|
70
|
-
srcVal !== undefined &&
|
|
71
|
-
typeof srcVal === "object" &&
|
|
72
|
-
!Array.isArray(srcVal) &&
|
|
73
|
-
typeof tgtVal === "object" &&
|
|
74
|
-
!Array.isArray(tgtVal)) {
|
|
75
|
-
result[key] = deepMerge(tgtVal, srcVal);
|
|
76
|
-
}
|
|
77
|
-
else if (srcVal !== undefined) {
|
|
78
|
-
result[key] = srcVal;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return result;
|
|
82
|
-
}
|
|
83
40
|
/**
|
|
84
41
|
* Extract a domain subset from a full AppConfig.
|
|
85
42
|
*/
|
|
@@ -138,7 +95,9 @@ export function loadDomainFiles(configDir) {
|
|
|
138
95
|
anyLoaded = true;
|
|
139
96
|
}
|
|
140
97
|
catch {
|
|
141
|
-
//
|
|
98
|
+
// Never silent: surface the offending path instead of silently ignoring
|
|
99
|
+
// a domain the user believes is active.
|
|
100
|
+
defaultOutputBus.log("warn", "config", t("config.parse_failed", { path: filePath }));
|
|
142
101
|
}
|
|
143
102
|
}
|
|
144
103
|
return anyLoaded ? result : null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/config/utils.ts
|
|
2
|
+
// Shared deep-merge + RegExp-safe JSON helpers. Previously duplicated across
|
|
3
|
+
// config.ts and domains.ts, where the two copies had drifted (config.ts
|
|
4
|
+
// assigned RegExp by reference; domains.ts spread it into {} and destroyed it).
|
|
5
|
+
export function isPlainObject(v) {
|
|
6
|
+
if (typeof v !== "object" || v === null)
|
|
7
|
+
return false;
|
|
8
|
+
const proto = Object.getPrototypeOf(v);
|
|
9
|
+
return proto === Object.prototype || proto === null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deep merge two objects. Arrays, scalars, and non-plain values (RegExp, Date)
|
|
13
|
+
* in source replace target by reference — spreading a RegExp would silently
|
|
14
|
+
* destroy it to {}.
|
|
15
|
+
*/
|
|
16
|
+
export function deepMerge(target, source) {
|
|
17
|
+
const result = { ...target };
|
|
18
|
+
for (const key of Object.keys(source)) {
|
|
19
|
+
const srcVal = source[key];
|
|
20
|
+
const tgtVal = target[key];
|
|
21
|
+
if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
|
|
22
|
+
result[key] = deepMerge(tgtVal, srcVal);
|
|
23
|
+
}
|
|
24
|
+
else if (srcVal !== undefined) {
|
|
25
|
+
result[key] = srcVal;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* JSON replacer that serializes RegExp objects as {__regex, source, flags}
|
|
32
|
+
* so they survive JSON.stringify/parse round-trips.
|
|
33
|
+
*/
|
|
34
|
+
export function regexReplacer(_key, value) {
|
|
35
|
+
if (value instanceof RegExp) {
|
|
36
|
+
return { __regex: true, source: value.source, flags: value.flags };
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* JSON reviver that restores RegExp objects serialized by regexReplacer.
|
|
42
|
+
*/
|
|
43
|
+
export function regexReviver(_key, value) {
|
|
44
|
+
if (value &&
|
|
45
|
+
typeof value === "object" &&
|
|
46
|
+
value.__regex === true) {
|
|
47
|
+
const { source, flags } = value;
|
|
48
|
+
try {
|
|
49
|
+
return new RegExp(source, flags);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { t } from "../../i18n/index";
|
|
2
|
+
import { MAX_AUDIT_RETRIES } from "./constants";
|
|
3
|
+
/**
|
|
4
|
+
* Финальный аудит-вентиль: проверяет, что текстовый ответ действительно
|
|
5
|
+
* выполнил задачу (при активном плане/скрипте), прежде чем принять его.
|
|
6
|
+
*
|
|
7
|
+
* Отклонение переспрашивает модель через <system-summary> и взводит
|
|
8
|
+
* suppressRepetitionRetry — повторный ответ на выполненную задачу
|
|
9
|
+
* естественно "повторяющийся" и не должен жечь бюджет халлюцинаций.
|
|
10
|
+
* Исчерпание повторов (MAX_AUDIT_RETRIES или упор в лимит итераций)
|
|
11
|
+
* переводит цикл в состояние auditFailed → success:false.
|
|
12
|
+
*/
|
|
13
|
+
export class AuditGate {
|
|
14
|
+
finalAudit;
|
|
15
|
+
constructor(finalAudit) {
|
|
16
|
+
this.finalAudit = finalAudit;
|
|
17
|
+
}
|
|
18
|
+
/** Возвращает null, если аудит не настроен (ответ принимается безусловно). */
|
|
19
|
+
async evaluate(deps) {
|
|
20
|
+
if (!this.finalAudit)
|
|
21
|
+
return null;
|
|
22
|
+
const audit = await this.finalAudit();
|
|
23
|
+
if (!audit || audit.passed)
|
|
24
|
+
return { action: "pass" };
|
|
25
|
+
const { state, logger, contextManager } = deps;
|
|
26
|
+
state.iterAuditRejection = true;
|
|
27
|
+
logger.warn(`Final audit incomplete: ${audit.summary}`);
|
|
28
|
+
const steps = audit.pendingSteps.slice(0, 5).join("; ") || "—";
|
|
29
|
+
contextManager.addMessage({
|
|
30
|
+
role: "user",
|
|
31
|
+
content: `<system-summary>${t("exec.audit_incomplete", {
|
|
32
|
+
summary: audit.summary,
|
|
33
|
+
steps,
|
|
34
|
+
})}</system-summary>`,
|
|
35
|
+
});
|
|
36
|
+
deps.slog.logAudit(audit.summary, deps.iteration);
|
|
37
|
+
state.auditRetries++;
|
|
38
|
+
state.lastAuditSummary = audit.summary;
|
|
39
|
+
if (state.auditRetries >= MAX_AUDIT_RETRIES || deps.iteration >= deps.maxIterations - 1) {
|
|
40
|
+
logger.warn(`Final audit still incomplete after ${state.auditRetries} retries — reporting failure`);
|
|
41
|
+
state.auditFailed = true;
|
|
42
|
+
return { action: "exhausted", summary: audit.summary };
|
|
43
|
+
}
|
|
44
|
+
// Следующий текстовый ответ — принудительный повтор уже выполненной
|
|
45
|
+
// задачи: разрешаем одну повторность без халлюцинационного повтора.
|
|
46
|
+
state.suppressRepetitionRetry = true;
|
|
47
|
+
return { action: "reject" };
|
|
48
|
+
}
|
|
49
|
+
}
|