micro-models-agent 0.41.2 → 0.43.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/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +19 -9
- package/dist/cli/completer.js +37 -36
- package/dist/cli/index.js +2 -2
- package/dist/cli/main.js +23 -48
- package/dist/cli/repl-commands.js +12 -40
- package/dist/cli/repl.js +87 -217
- package/dist/cli/security-commands.js +7 -5
- package/dist/cli/setup.js +26 -8
- package/dist/config/config.js +5 -52
- package/dist/config/defaults.js +5 -29
- package/dist/config/experts.js +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/security.js +10 -3
- package/dist/core/agent-moe.js +10 -2
- package/dist/core/agent.js +82 -273
- package/dist/core/bootstrap.js +13 -80
- package/dist/core/index.js +2 -2
- package/dist/core/prompt-builder.js +2 -23
- package/dist/core/session-logger.js +4 -46
- package/dist/i18n/en.json +2 -75
- package/dist/i18n/ru.json +1 -74
- package/dist/index.js +1 -1
- package/dist/llm/image-utils.js +5 -4
- package/dist/llm/index.js +4 -4
- package/dist/llm/model-loader.js +6 -6
- package/dist/llm/openai-compat.js +34 -40
- package/dist/llm/orchestrator.js +29 -33
- package/dist/llm/response.js +9 -9
- package/dist/logger/app-logger.js +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/main.js +648 -183
- package/dist/migration/backup.js +13 -13
- package/dist/migration/detect.js +11 -11
- package/dist/migration/index.js +2 -2
- package/dist/modules/browser/actions.js +4 -34
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/modules/browser/cookie-store.js +6 -6
- package/dist/modules/browser/index.js +5 -7
- package/dist/modules/browser/module.js +7 -8
- package/dist/modules/browser/session.js +84 -87
- package/dist/modules/browser/snapshot.js +58 -92
- package/dist/modules/browser/types.js +1 -4
- package/dist/modules/certification/cli.js +4 -2
- package/dist/modules/certification/fact-checker.js +3 -1
- package/dist/modules/certification/loader.js +9 -3
- package/dist/modules/certification/runner.js +4 -1
- package/dist/modules/context/index.js +1 -1
- package/dist/modules/context/manager.js +86 -160
- package/dist/modules/execution/auditor.js +25 -177
- package/dist/modules/execution/module.js +544 -201
- package/dist/modules/execution/moe-executor.js +0 -25
- package/dist/modules/execution/plan-store.js +3 -1
- package/dist/modules/execution/plan-validator.js +10 -10
- package/dist/modules/execution/planner.js +1 -6
- package/dist/modules/execution/stuck-detector.js +10 -173
- package/dist/modules/execution/verifier.js +42 -86
- package/dist/modules/hallucination/confidence.js +1 -8
- package/dist/modules/hallucination/detector.js +5 -2
- package/dist/modules/hallucination/factual.js +64 -3
- package/dist/modules/hallucination/index.js +1 -1
- package/dist/modules/hallucination/js-identifiers.js +0 -190
- package/dist/modules/hallucination/llm-judge.js +3 -1
- package/dist/modules/indexer/cache.js +7 -9
- package/dist/modules/indexer/index.js +3 -3
- package/dist/modules/indexer/module.js +42 -95
- package/dist/modules/indexer/walker.js +17 -17
- package/dist/modules/lsp/client.js +31 -74
- package/dist/modules/lsp/config.js +33 -87
- package/dist/modules/lsp/index.js +3 -3
- package/dist/modules/lsp/module.js +21 -185
- package/dist/modules/mcp/module.js +6 -2
- package/dist/modules/memory/index.js +1 -1
- package/dist/modules/memory/module.js +23 -71
- package/dist/modules/memory/search.js +9 -11
- package/dist/modules/memory/store.js +13 -13
- package/dist/modules/pipelines/engine.js +10 -10
- package/dist/modules/pipelines/index.js +3 -3
- package/dist/modules/pipelines/parser.js +14 -17
- package/dist/modules/pipelines/template.js +1 -1
- package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
- package/dist/modules/plugins/builtin/notify.js +2 -3
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/loader.js +17 -59
- package/dist/modules/plugins/manager.js +17 -73
- package/dist/modules/processes/index.js +1 -1
- package/dist/modules/processes/registry.js +46 -135
- package/dist/modules/registry.js +2 -4
- package/dist/modules/security/audit-notifier.js +39 -39
- package/dist/modules/security/command-validator.js +8 -2
- package/dist/modules/security/data-sanitizer.js +9 -1
- package/dist/modules/security/encryption.js +56 -58
- package/dist/modules/security/network-validator.js +9 -1
- package/dist/modules/security/path-validator.js +3 -1
- package/dist/modules/security/security-policies.js +19 -3
- package/dist/modules/security/session-encryption.js +1 -1
- package/dist/modules/security/session-isolation.js +8 -8
- package/dist/modules/session/index.js +3 -3
- package/dist/modules/session/module.js +5 -5
- package/dist/modules/session/store.js +9 -3
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/checker.js +6 -70
- package/dist/modules/updater/index.js +1 -2
- package/dist/modules/user-profile/compressor.js +2 -2
- package/dist/modules/user-profile/index.js +1 -1
- package/dist/modules/user-profile/profile.js +9 -9
- package/dist/tools/attach-image.js +1 -1
- package/dist/tools/bash.js +19 -178
- package/dist/tools/browser.js +29 -46
- package/dist/tools/executor.js +5 -4
- package/dist/tools/file-info.js +12 -13
- package/dist/tools/filter-tools.js +2 -9
- package/dist/tools/glob-tool.js +11 -11
- package/dist/tools/grep-tool.js +3 -1
- package/dist/tools/index.js +2 -13
- package/dist/tools/list-dir.js +17 -18
- package/dist/tools/load-skill.js +3 -1
- package/dist/tools/path-utils.js +4 -4
- package/dist/tools/pipeline-run.js +25 -25
- package/dist/tools/process-kill.js +11 -11
- package/dist/tools/process-list.js +22 -20
- package/dist/tools/process-log.js +18 -22
- package/dist/tools/question.js +3 -1
- package/dist/tools/read-file.js +2 -10
- package/dist/tools/recall.js +37 -44
- package/dist/tools/registry.js +4 -15
- package/dist/tools/remember.js +29 -29
- package/dist/tools/scope-check.js +9 -9
- package/dist/tools/subagent.js +9 -54
- package/dist/tools/user-input.js +1 -1
- package/dist/tools/web-browse.js +3 -3
- package/dist/tools/web-fetch.js +3 -3
- package/dist/tools/web-search.js +3 -3
- package/dist/tools/write-file.js +3 -1
- package/dist/ui/box.js +5 -1
- package/dist/ui/index.js +6 -6
- package/dist/ui/md-formatter.js +33 -33
- package/dist/ui/output.js +5 -5
- package/dist/ui/renderer.js +10 -15
- package/dist/ui/table.js +1 -1
- package/package.json +48 -48
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/run-result.js +0 -22
- package/dist/core/version.js +0 -24
- package/dist/modules/artifacts/store.js +0 -61
- package/dist/modules/browser/bridge-client.js +0 -199
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/driver.js +0 -136
- package/dist/modules/context/chunk-query.js +0 -100
- package/dist/modules/context/fact-extractor.js +0 -162
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/execution/audit-runners.js +0 -152
- package/dist/modules/execution/execution-plugin.js +0 -272
- package/dist/modules/execution/plan-tool.js +0 -508
- package/dist/modules/execution/windows-commands.js +0 -41
- package/dist/modules/indexer/project-profile.js +0 -183
- package/dist/modules/lsp/check-tool.js +0 -58
- package/dist/modules/lsp/command.js +0 -60
- package/dist/modules/lsp/probe.js +0 -76
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -141
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/updater/module.js +0 -116
- package/dist/tools/chunk-query.js +0 -99
- package/dist/tools/download-file.js +0 -116
- package/dist/tools/enable-tools.js +0 -58
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/ui/line-editor.js +0 -703
- package/dist/ui/line-math.js +0 -69
- package/dist/ui/plan-view.js +0 -103
package/dist/cli/repl.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import * as readline from "readline";
|
|
2
2
|
import { pc } from "../ui/colors";
|
|
3
|
-
import { LineEditor } from "../ui/line-editor";
|
|
4
3
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
5
4
|
import { join, dirname } from "path";
|
|
6
5
|
import { homedir } from "os";
|
|
7
6
|
import { fileURLToPath } from "url";
|
|
8
7
|
import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, } from "./completer";
|
|
9
8
|
import { Renderer } from "../ui/renderer";
|
|
10
|
-
import { box
|
|
11
|
-
import stringWidth from "string-width";
|
|
9
|
+
import { box } from "../ui/box";
|
|
12
10
|
import { t } from "../i18n/index";
|
|
13
11
|
import { registerAllCommands } from "./repl-commands";
|
|
14
|
-
import { probeLspServers } from "../modules/lsp/probe";
|
|
15
|
-
import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
|
|
16
|
-
import { readActivePlan, formatPlanChecklist, stepContextForTool } from "../ui/plan-view";
|
|
17
12
|
function readVersion() {
|
|
18
13
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const candidates = [
|
|
14
|
+
const candidates = [
|
|
15
|
+
join(here, "..", "..", "package.json"),
|
|
16
|
+
join(here, "..", "package.json"),
|
|
17
|
+
];
|
|
20
18
|
for (const p of candidates) {
|
|
21
19
|
if (existsSync(p)) {
|
|
22
20
|
try {
|
|
@@ -62,9 +60,6 @@ export class Repl {
|
|
|
62
60
|
baseDir;
|
|
63
61
|
noAgentsMd;
|
|
64
62
|
pendingClipboardImage = null;
|
|
65
|
-
exitOnClose = false;
|
|
66
|
-
activePlan = null;
|
|
67
|
-
lastPlanSig = "";
|
|
68
63
|
rl;
|
|
69
64
|
agent;
|
|
70
65
|
config;
|
|
@@ -72,10 +67,9 @@ export class Repl {
|
|
|
72
67
|
skillsModule;
|
|
73
68
|
pluginManager;
|
|
74
69
|
logger;
|
|
75
|
-
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger
|
|
70
|
+
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger) {
|
|
76
71
|
this.agent = agent;
|
|
77
72
|
this.config = config;
|
|
78
|
-
this.exitOnClose = exitOnClose === true;
|
|
79
73
|
this.sessionManager = sessionManager;
|
|
80
74
|
this.skillsModule = skillsModule;
|
|
81
75
|
this.pluginManager = pluginManager;
|
|
@@ -85,34 +79,20 @@ export class Repl {
|
|
|
85
79
|
this.noAgentsMd = noAgentsMd === true;
|
|
86
80
|
this.historyPath = join(homedir(), ".mma", "repl-history");
|
|
87
81
|
this.loadHistory();
|
|
88
|
-
this.rl =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: readline.createInterface({
|
|
103
|
-
input: process.stdin,
|
|
104
|
-
output: process.stdout,
|
|
105
|
-
prompt: pc.cyan(t("repl.you")),
|
|
106
|
-
history: this.history,
|
|
107
|
-
historySize: this.maxHistory,
|
|
108
|
-
tabSize: 2,
|
|
109
|
-
completer: (line) => {
|
|
110
|
-
const [matches, partial] = this.completer.complete(line);
|
|
111
|
-
if (matches.length > 0)
|
|
112
|
-
return [matches, partial];
|
|
113
|
-
return [[], line];
|
|
114
|
-
},
|
|
115
|
-
});
|
|
82
|
+
this.rl = readline.createInterface({
|
|
83
|
+
input: process.stdin,
|
|
84
|
+
output: process.stdout,
|
|
85
|
+
prompt: pc.cyan("> "),
|
|
86
|
+
history: this.history,
|
|
87
|
+
historySize: this.maxHistory,
|
|
88
|
+
tabSize: 2,
|
|
89
|
+
completer: (line) => {
|
|
90
|
+
const [matches, partial] = this.completer.complete(line);
|
|
91
|
+
if (matches.length > 0)
|
|
92
|
+
return [matches, partial];
|
|
93
|
+
return [[], line];
|
|
94
|
+
},
|
|
95
|
+
});
|
|
116
96
|
registerAllCommands(this);
|
|
117
97
|
this.setupCompleter();
|
|
118
98
|
this.setupListeners();
|
|
@@ -139,7 +119,13 @@ export class Repl {
|
|
|
139
119
|
this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
|
|
140
120
|
}
|
|
141
121
|
if (this.skillsModule) {
|
|
142
|
-
this.completer.registerProvider(new SubcommandProvider("skill", [
|
|
122
|
+
this.completer.registerProvider(new SubcommandProvider("skill", [
|
|
123
|
+
"list",
|
|
124
|
+
"loaded",
|
|
125
|
+
"load",
|
|
126
|
+
"unload",
|
|
127
|
+
"search",
|
|
128
|
+
]));
|
|
143
129
|
this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
|
|
144
130
|
}
|
|
145
131
|
}
|
|
@@ -178,12 +164,12 @@ export class Repl {
|
|
|
178
164
|
}
|
|
179
165
|
}
|
|
180
166
|
if (this.running) {
|
|
181
|
-
this.rl.setPrompt(pc.cyan(
|
|
167
|
+
this.rl.setPrompt(pc.cyan("> "));
|
|
182
168
|
this.rl.prompt();
|
|
183
169
|
}
|
|
184
170
|
}
|
|
185
171
|
else {
|
|
186
|
-
this.rl.setPrompt(pc.cyan(
|
|
172
|
+
this.rl.setPrompt(pc.cyan("... "));
|
|
187
173
|
this.rl.prompt();
|
|
188
174
|
}
|
|
189
175
|
return;
|
|
@@ -191,7 +177,7 @@ export class Repl {
|
|
|
191
177
|
if (this.isMultiLineInput(trimmed)) {
|
|
192
178
|
inMultiLine = true;
|
|
193
179
|
multiLineBuffer = trimmed;
|
|
194
|
-
this.rl.setPrompt(pc.cyan(
|
|
180
|
+
this.rl.setPrompt(pc.cyan("... "));
|
|
195
181
|
this.rl.prompt();
|
|
196
182
|
return;
|
|
197
183
|
}
|
|
@@ -211,22 +197,52 @@ export class Repl {
|
|
|
211
197
|
});
|
|
212
198
|
this.rl.on("close", () => {
|
|
213
199
|
this.running = false;
|
|
214
|
-
this.saveHistory();
|
|
215
|
-
this.agent.shutdown();
|
|
216
|
-
if (this.exitOnClose)
|
|
217
|
-
process.exit(0);
|
|
218
200
|
});
|
|
219
|
-
if (
|
|
220
|
-
process.stdin
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
201
|
+
if (process.stdin.isTTY) {
|
|
202
|
+
readline.emitKeypressEvents(process.stdin);
|
|
203
|
+
process.stdin.on("keypress", async (str, key) => {
|
|
204
|
+
if (key.name === "escape") {
|
|
205
|
+
// Bun/Node's readline collapses a fast double-Esc into a single
|
|
206
|
+
// keypress whose `sequence` contains two ESC bytes ("\x1b\x1b").
|
|
207
|
+
// Counting bytes (not events) catches both the collapsed case and
|
|
208
|
+
// the case where two separate escape keypresses land in the window.
|
|
209
|
+
const escBytes = key.sequence
|
|
210
|
+
? (key.sequence.match(/\x1b/g) || []).length
|
|
211
|
+
: 1;
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
|
|
214
|
+
this.lastEscTime = now;
|
|
215
|
+
if (escBytes >= 2 || withinWindow) {
|
|
216
|
+
this.lastEscTime = 0;
|
|
217
|
+
if (this.agentRunning) {
|
|
218
|
+
process.stdout.write(pc.yellow(`\n${t("repl.interrupt")}\n`));
|
|
219
|
+
this.agent.shutdown();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (key.ctrl && key.name === "v" && !this.agentRunning) {
|
|
225
|
+
try {
|
|
226
|
+
const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
|
|
227
|
+
const clipBuf = await readClipboardImage();
|
|
228
|
+
if (clipBuf) {
|
|
229
|
+
const { dataUrl } = await bufferToDataUrl(clipBuf);
|
|
230
|
+
this.pendingClipboardImage = dataUrl;
|
|
231
|
+
const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
|
|
232
|
+
console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
233
|
+
this.rl.prompt();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
|
|
237
|
+
this.rl.prompt();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// clipboard read failed — ignore, let terminal paste text normally
|
|
242
|
+
}
|
|
243
|
+
}
|
|
225
244
|
});
|
|
226
245
|
}
|
|
227
|
-
if (this.rl instanceof LineEditor) {
|
|
228
|
-
this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
|
|
229
|
-
}
|
|
230
246
|
let forceExitTimer = null;
|
|
231
247
|
process.on("SIGINT", () => {
|
|
232
248
|
if (this.agentRunning) {
|
|
@@ -242,55 +258,6 @@ export class Repl {
|
|
|
242
258
|
}
|
|
243
259
|
});
|
|
244
260
|
}
|
|
245
|
-
handleSpecialKey(str, key) {
|
|
246
|
-
if (key.name === "escape") {
|
|
247
|
-
// Bun's keypress decoder collapses a fast double-Esc into a single
|
|
248
|
-
// keypress whose `sequence` contains two ESC bytes ("\x1b\x1b").
|
|
249
|
-
// Counting bytes (not events) catches both the collapsed case and
|
|
250
|
-
// the case where two separate escape keypresses land in the window.
|
|
251
|
-
const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
|
|
252
|
-
const now = Date.now();
|
|
253
|
-
const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
|
|
254
|
-
this.lastEscTime = now;
|
|
255
|
-
if (escBytes >= 2 || withinWindow) {
|
|
256
|
-
this.lastEscTime = 0;
|
|
257
|
-
if (this.agentRunning) {
|
|
258
|
-
process.stdout.write(pc.yellow(`\n${t("repl.interrupt")}\n`));
|
|
259
|
-
this.agent.shutdown();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
if (key.ctrl && key.name === "v" && !this.agentRunning) {
|
|
265
|
-
this.pasteClipboardImage();
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
async pasteClipboardImage() {
|
|
271
|
-
try {
|
|
272
|
-
const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
|
|
273
|
-
const clipBuf = await readClipboardImage();
|
|
274
|
-
if (clipBuf) {
|
|
275
|
-
const { dataUrl } = await bufferToDataUrl(clipBuf);
|
|
276
|
-
this.pendingClipboardImage = dataUrl;
|
|
277
|
-
const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
|
|
278
|
-
console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
279
|
-
if (this.rl instanceof LineEditor)
|
|
280
|
-
this.rl.reset();
|
|
281
|
-
this.rl.prompt();
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
|
|
285
|
-
if (this.rl instanceof LineEditor)
|
|
286
|
-
this.rl.reset();
|
|
287
|
-
this.rl.prompt();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
catch {
|
|
291
|
-
// clipboard read failed — ignore, let terminal paste text normally
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
261
|
isMultiLineInput(line) {
|
|
295
262
|
if (line.endsWith("\\"))
|
|
296
263
|
return true;
|
|
@@ -316,26 +283,17 @@ export class Repl {
|
|
|
316
283
|
this.pendingClipboardImage = null;
|
|
317
284
|
}
|
|
318
285
|
this.logger?.logREPL("user", input);
|
|
319
|
-
|
|
320
|
-
this.rl.reset();
|
|
321
|
-
// Overwrite the empty prompt that reset() just drew with a divider,
|
|
322
|
-
// then label the agent's reply — separates the user's message from
|
|
323
|
-
// the agent's answer.
|
|
324
|
-
process.stdout.write("\r" + divider() + "\n" + pc.green(t("repl.agent")));
|
|
286
|
+
process.stdout.write("\n" + pc.green(t("repl.agent")));
|
|
325
287
|
const renderer = new Renderer({
|
|
326
288
|
spinner: this.config.ui?.spinner ?? true,
|
|
327
289
|
toolStyle: this.config.ui?.toolStyle ?? "inline",
|
|
328
290
|
});
|
|
329
|
-
this.refreshActivePlan(renderer);
|
|
330
291
|
const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
|
|
331
292
|
if (ev.type === "start") {
|
|
332
|
-
renderer.toolStart(ev.tool, ev.args
|
|
293
|
+
renderer.toolStart(ev.tool, ev.args);
|
|
333
294
|
}
|
|
334
295
|
else {
|
|
335
296
|
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
|
|
336
|
-
if (ev.tool === "plan" || ev.tool === "todo") {
|
|
337
|
-
this.refreshActivePlan(renderer);
|
|
338
|
-
}
|
|
339
297
|
}
|
|
340
298
|
}, (phase) => {
|
|
341
299
|
if (phase === "thinking") {
|
|
@@ -352,29 +310,11 @@ export class Repl {
|
|
|
352
310
|
console.error(pc.red(`${t("error.prefix")}${result.error}`));
|
|
353
311
|
}
|
|
354
312
|
this.showContextBar(result);
|
|
355
|
-
// Close the agent's turn: a divider before the next prompt keeps
|
|
356
|
-
// consecutive user/agent messages visually separated.
|
|
357
|
-
process.stdout.write("\n" + divider() + "\n");
|
|
358
313
|
}
|
|
359
314
|
finally {
|
|
360
315
|
this.agentRunning = false;
|
|
361
316
|
}
|
|
362
317
|
}
|
|
363
|
-
/** Re-read the active plan from disk; print a live checklist when it changed. */
|
|
364
|
-
refreshActivePlan(renderer) {
|
|
365
|
-
const plan = readActivePlan(this.baseDir);
|
|
366
|
-
const sig = plan ? JSON.stringify(plan.steps.map((s) => `${s.id}:${s.status}`)) : "";
|
|
367
|
-
if (!plan) {
|
|
368
|
-
this.activePlan = null;
|
|
369
|
-
this.lastPlanSig = "";
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
this.activePlan = plan;
|
|
373
|
-
if (sig !== this.lastPlanSig) {
|
|
374
|
-
this.lastPlanSig = sig;
|
|
375
|
-
renderer.planBlock(formatPlanChecklist(plan));
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
318
|
commands = new Map();
|
|
379
319
|
registerCommand(cmd) {
|
|
380
320
|
this.commands.set(cmd.name, cmd);
|
|
@@ -432,7 +372,9 @@ export class Repl {
|
|
|
432
372
|
continue;
|
|
433
373
|
console.log(pc.bold(t(`repl.group.${groupKey}`)));
|
|
434
374
|
for (const cmd of cmds) {
|
|
435
|
-
const aliases = cmd.aliases?.length
|
|
375
|
+
const aliases = cmd.aliases?.length
|
|
376
|
+
? ` (${pc.dim(cmd.aliases.join(", "))})`
|
|
377
|
+
: "";
|
|
436
378
|
console.log(` ${pc.cyan("/" + cmd.name)}${aliases} ${pc.dim(cmd.description)}`);
|
|
437
379
|
if (cmd.usage) {
|
|
438
380
|
console.log(` ${pc.dim(cmd.usage)}`);
|
|
@@ -465,7 +407,7 @@ export class Repl {
|
|
|
465
407
|
}
|
|
466
408
|
}
|
|
467
409
|
}
|
|
468
|
-
|
|
410
|
+
start() {
|
|
469
411
|
this.running = true;
|
|
470
412
|
const info = [];
|
|
471
413
|
const row = (label, value) => {
|
|
@@ -485,16 +427,12 @@ export class Repl {
|
|
|
485
427
|
row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
|
|
486
428
|
}
|
|
487
429
|
if (this.pluginManager) {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return `${plugin.name}${version}${origin}`;
|
|
495
|
-
})
|
|
496
|
-
.join(", ");
|
|
497
|
-
row(t("repl.plugins_label"), `${pc.white(String(infos.length))} active ${pc.dim(`(${pluginNames})`)}`);
|
|
430
|
+
const plugins = this.pluginManager
|
|
431
|
+
.getAllPlugins()
|
|
432
|
+
.filter((p) => !p.isBuiltin);
|
|
433
|
+
if (plugins.length > 0) {
|
|
434
|
+
const pluginNames = plugins.map((p) => p.name).join(", ");
|
|
435
|
+
row(t("repl.plugins_label"), `${pc.white(String(plugins.length))} active ${pc.dim(`(${pluginNames})`)}`);
|
|
498
436
|
}
|
|
499
437
|
}
|
|
500
438
|
const mcpServers = this.config.mcpServers || {};
|
|
@@ -530,82 +468,14 @@ export class Repl {
|
|
|
530
468
|
row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
|
|
531
469
|
}
|
|
532
470
|
const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
|
|
533
|
-
|
|
534
|
-
const isTty = process.stdout.isTTY === true;
|
|
535
|
-
const lspEnabled = isTty && (this.config.lsp ?? DEFAULT_LSP_CONFIG).enabled !== false;
|
|
536
|
-
if (lspEnabled) {
|
|
537
|
-
row(t("repl.lsp_label"), pc.dim("…"));
|
|
538
|
-
}
|
|
539
|
-
const boxLines = box(info, {
|
|
471
|
+
for (const line of box(info, {
|
|
540
472
|
title: t("repl.title", { version }),
|
|
541
473
|
width: headerWidth,
|
|
542
|
-
})
|
|
543
|
-
for (const line of boxLines) {
|
|
474
|
+
})) {
|
|
544
475
|
console.log(line);
|
|
545
476
|
}
|
|
546
477
|
console.log();
|
|
547
478
|
this.rl.prompt();
|
|
548
|
-
if (lspEnabled) {
|
|
549
|
-
this.probeLspBanner()
|
|
550
|
-
.then((lspSummary) => {
|
|
551
|
-
if (lspSummary)
|
|
552
|
-
this.updateLspRow(boxLines.length, headerWidth, lspSummary);
|
|
553
|
-
})
|
|
554
|
-
.catch(() => { });
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Replace the LSP banner row (rendered as "…") in place once the probe
|
|
559
|
-
* finishes. The LSP row is the last data row of the box, so it sits exactly
|
|
560
|
-
* two terminal rows above the prompt line — that offset is stable no matter
|
|
561
|
-
* how earlier rows wrap. Skipped (placeholder stays) when a rewrite would
|
|
562
|
-
* corrupt the screen: the agent is already running, the terminal is not a
|
|
563
|
-
* TTY, a box line wrapped, or the summary is too wide for a single row.
|
|
564
|
-
*/
|
|
565
|
-
updateLspRow(boxLineCount, headerWidth, summary) {
|
|
566
|
-
if (this.agentRunning || !process.stdout.isTTY)
|
|
567
|
-
return;
|
|
568
|
-
if ((process.stdout.columns ?? 96) < headerWidth)
|
|
569
|
-
return;
|
|
570
|
-
const inner = headerWidth - 4;
|
|
571
|
-
// Match box()'s wrapText normalization (leading space stripped, runs of
|
|
572
|
-
// whitespace collapsed) so the rewritten row is byte-identical to what
|
|
573
|
-
// box() would render for the same text.
|
|
574
|
-
const rowText = `${pc.yellow(t("repl.lsp_label"))} ${summary.replace(/\s+/g, " ").trim()}`;
|
|
575
|
-
if (stringWidth(rowText) > inner)
|
|
576
|
-
return;
|
|
577
|
-
const padded = rowText + " ".repeat(Math.max(0, inner - stringWidth(rowText)));
|
|
578
|
-
const line = pc.dim("│") + " " + padded + " " + pc.dim("│");
|
|
579
|
-
// From the prompt row (boxLineCount) up 2 to the LSP row (last data row,
|
|
580
|
-
// directly above the bottom border), clear it, write the new row, and
|
|
581
|
-
// return the cursor to the prompt row. The editor re-positions itself on
|
|
582
|
-
// the next keystroke, so any typed input is preserved.
|
|
583
|
-
process.stdout.write(`\x1b[2A\r\x1b[2K${line}\x1b[2B\r`);
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Probe the LSP servers applicable to the current project and format a
|
|
587
|
-
* one-line banner summary, e.g. "✓ typescript (1.2s)" or "✗ css — timeout".
|
|
588
|
-
* Returns null when LSP is disabled or no server applies.
|
|
589
|
-
*/
|
|
590
|
-
async probeLspBanner() {
|
|
591
|
-
const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;
|
|
592
|
-
try {
|
|
593
|
-
const summary = await probeLspServers(config, this.baseDir);
|
|
594
|
-
if (summary.noneApplicable)
|
|
595
|
-
return null;
|
|
596
|
-
const parts = [];
|
|
597
|
-
for (const r of summary.ok) {
|
|
598
|
-
parts.push(pc.green(`✓ ${r.language}${pc.dim(` (${(r.durationMs / 1000).toFixed(1)}s)`)}`));
|
|
599
|
-
}
|
|
600
|
-
for (const r of summary.failed) {
|
|
601
|
-
const reason = r.error?.includes("timeout") ? t("repl.lsp_timeout") : t("repl.lsp_failed");
|
|
602
|
-
parts.push(pc.red(`✗ ${r.language}${pc.dim(` — ${reason}`)}`));
|
|
603
|
-
}
|
|
604
|
-
return parts.join(" ");
|
|
605
|
-
}
|
|
606
|
-
catch {
|
|
607
|
-
return pc.yellow(t("repl.lsp_unknown"));
|
|
608
|
-
}
|
|
609
479
|
}
|
|
610
480
|
stop() {
|
|
611
481
|
this.running = false;
|
|
@@ -9,7 +9,9 @@ import { t } from "../i18n/index";
|
|
|
9
9
|
* Create security subcommand
|
|
10
10
|
*/
|
|
11
11
|
export function createSecurityCommand(program) {
|
|
12
|
-
const securityCmd = program
|
|
12
|
+
const securityCmd = program
|
|
13
|
+
.command("security")
|
|
14
|
+
.description(t("cli.security.description"));
|
|
13
15
|
// security status - show current security configuration
|
|
14
16
|
securityCmd
|
|
15
17
|
.command("status")
|
|
@@ -43,7 +45,7 @@ export function createSecurityCommand(program) {
|
|
|
43
45
|
console.log(t("cli.security.available_policies"));
|
|
44
46
|
console.log("");
|
|
45
47
|
for (const [preset, policy] of Object.entries(SECURITY_POLICIES)) {
|
|
46
|
-
if (preset ===
|
|
48
|
+
if (preset === 'custom')
|
|
47
49
|
continue;
|
|
48
50
|
const marker = " ";
|
|
49
51
|
console.log(` ${marker}${preset.padEnd(12)} ${policy.name}`);
|
|
@@ -60,7 +62,7 @@ export function createSecurityCommand(program) {
|
|
|
60
62
|
.action(async (preset) => {
|
|
61
63
|
const configPath = join(homedir(), ".mma", "config.json");
|
|
62
64
|
const { config: appConfig } = await bootstrap();
|
|
63
|
-
const validPresets = [
|
|
65
|
+
const validPresets = ['strict', 'balanced', 'permissive'];
|
|
64
66
|
if (!validPresets.includes(preset)) {
|
|
65
67
|
console.log(t("cli.security.invalid_preset", { presets: validPresets.join(", ") }));
|
|
66
68
|
return;
|
|
@@ -115,8 +117,8 @@ export function createSecurityCommand(program) {
|
|
|
115
117
|
appConfig.security = appConfig.security || {};
|
|
116
118
|
appConfig.security.auditNotifier = {
|
|
117
119
|
enabled: true,
|
|
118
|
-
minSeverity:
|
|
119
|
-
eventTypes: [
|
|
120
|
+
minSeverity: 'medium',
|
|
121
|
+
eventTypes: ['security_block', 'bash_command', 'file_operation', 'network_request'],
|
|
120
122
|
maxRetries: 3,
|
|
121
123
|
webhookTimeout: 5000,
|
|
122
124
|
};
|
package/dist/cli/setup.js
CHANGED
|
@@ -22,7 +22,9 @@ function menuList(items) {
|
|
|
22
22
|
}
|
|
23
23
|
function ask(rl, question, defaultValue) {
|
|
24
24
|
return new Promise((resolve) => {
|
|
25
|
-
const prompt = defaultValue
|
|
25
|
+
const prompt = defaultValue
|
|
26
|
+
? `${question} [${defaultValue}]: `
|
|
27
|
+
: `${question}: `;
|
|
26
28
|
rl.question(prompt, (answer) => {
|
|
27
29
|
resolve(answer.trim() || defaultValue || "");
|
|
28
30
|
});
|
|
@@ -174,7 +176,7 @@ export async function runSetup(externalRl) {
|
|
|
174
176
|
model = models[idx];
|
|
175
177
|
}
|
|
176
178
|
else {
|
|
177
|
-
model = await ask(rl, t("setup.model_name"), "
|
|
179
|
+
model = await ask(rl, t("setup.model_name"), "qwen3.5-9b");
|
|
178
180
|
}
|
|
179
181
|
const connected = await withSpinner(t("setup.testing_spinner", { model }), () => testChat(apiBase, apiKey, model));
|
|
180
182
|
if (connected) {
|
|
@@ -193,11 +195,17 @@ export async function runSetup(externalRl) {
|
|
|
193
195
|
let securityBashBlock = false;
|
|
194
196
|
let securityFlagsBlock = false;
|
|
195
197
|
let securityPathsDeny = true;
|
|
196
|
-
if (configureSecurity.toLowerCase() === "y" ||
|
|
197
|
-
|
|
198
|
+
if (configureSecurity.toLowerCase() === "y" ||
|
|
199
|
+
configureSecurity.toLowerCase() === "yes") {
|
|
200
|
+
securityBashBlock =
|
|
201
|
+
(await ask(rl, t("setup.security_bash_block"), "n")).toLowerCase() ===
|
|
202
|
+
"y";
|
|
198
203
|
securityFlagsBlock =
|
|
199
|
-
(await ask(rl, t("setup.security_flags_block"), "n")).toLowerCase() ===
|
|
200
|
-
|
|
204
|
+
(await ask(rl, t("setup.security_flags_block"), "n")).toLowerCase() ===
|
|
205
|
+
"y";
|
|
206
|
+
securityPathsDeny =
|
|
207
|
+
(await ask(rl, t("setup.security_paths_deny"), "Y")).toLowerCase() !==
|
|
208
|
+
"n";
|
|
201
209
|
}
|
|
202
210
|
if (ownRl)
|
|
203
211
|
rl.close();
|
|
@@ -221,8 +229,18 @@ export async function runSetup(externalRl) {
|
|
|
221
229
|
t("setup.summary_value"),
|
|
222
230
|
], [
|
|
223
231
|
[t("setup.provider_type"), provider, t("setup.model_name"), model],
|
|
224
|
-
[
|
|
225
|
-
|
|
232
|
+
[
|
|
233
|
+
"API Base URL",
|
|
234
|
+
apiBase,
|
|
235
|
+
t("setup.context_window"),
|
|
236
|
+
String(contextWindow),
|
|
237
|
+
],
|
|
238
|
+
[
|
|
239
|
+
t("setup.api_key"),
|
|
240
|
+
apiKey || "not-needed",
|
|
241
|
+
t("setup.max_iters"),
|
|
242
|
+
String(maxIterations),
|
|
243
|
+
],
|
|
226
244
|
[t("setup.ui_lang"), locale, "", ""],
|
|
227
245
|
], { maxColumns: 4 });
|
|
228
246
|
for (const l of summary)
|
package/dist/config/config.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from "fs";
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync, } from "fs";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { DEFAULTS } from "./defaults";
|
|
4
4
|
import { DEFAULT_SECURITY_CONFIG } from "./security";
|
|
5
|
-
import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
|
|
6
5
|
import { t } from "../i18n/index";
|
|
7
6
|
import { MigrationDetector } from "../migration/detect";
|
|
8
7
|
import { BackupManager } from "../migration/backup";
|
|
9
8
|
import { validateExpertConfig } from "./experts";
|
|
10
|
-
import { ConfigEncryptor } from "../modules/security/encryption";
|
|
9
|
+
import { ConfigEncryptor, } from "../modules/security/encryption";
|
|
11
10
|
/**
|
|
12
11
|
* Restore RegExp instances in dangerousPatterns that were serialized as {}
|
|
13
12
|
* (pre-0.8.0 configs) or as {__regex, source, flags} (new format).
|
|
@@ -70,7 +69,9 @@ function regexReplacer(_key, value) {
|
|
|
70
69
|
* JSON reviver that restores RegExp objects serialized by regexReplacer.
|
|
71
70
|
*/
|
|
72
71
|
function regexReviver(_key, value) {
|
|
73
|
-
if (value &&
|
|
72
|
+
if (value &&
|
|
73
|
+
typeof value === "object" &&
|
|
74
|
+
value.__regex === true) {
|
|
74
75
|
const { source, flags } = value;
|
|
75
76
|
try {
|
|
76
77
|
return new RegExp(source, flags);
|
|
@@ -81,46 +82,6 @@ function regexReviver(_key, value) {
|
|
|
81
82
|
}
|
|
82
83
|
return value;
|
|
83
84
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Legacy npx `args` for LSP servers that predate the fixed invocation
|
|
86
|
-
* (`--yes --package <server> --package typescript@5 …`). These exact arrays
|
|
87
|
-
* are unsafe: without `--yes`/`--package` the servers fail to bootstrap
|
|
88
|
-
* (`Could not find a valid TypeScript installation`) or the package name is
|
|
89
|
-
* wrong, so the server hangs until the initialize timeout. When a stored
|
|
90
|
-
* server's `args` matches one of these (or is missing) it is replaced with the
|
|
91
|
-
* current default args — user-customized arrays that differ are left alone.
|
|
92
|
-
*/
|
|
93
|
-
const LEGACY_LSP_SERVER_ARGS = {
|
|
94
|
-
typescript: { args: ["typescript-language-server", "--stdio"], timeout: 30000 },
|
|
95
|
-
javascript: { args: ["typescript-language-server", "--stdio"], timeout: 30000 },
|
|
96
|
-
json: { args: ["vscode-json-languageserver", "--stdio"], timeout: 30000 },
|
|
97
|
-
html: { args: ["vscode-html-languageserver", "--stdio"], timeout: 60000 },
|
|
98
|
-
css: { args: ["vscode-css-languageserver", "--stdio"], timeout: 60000 },
|
|
99
|
-
};
|
|
100
|
-
/**
|
|
101
|
-
* Replace stale pre-0.41.1 LSP server invocations (npx bootstrap args) with
|
|
102
|
-
* the current defaults, so an old `~/.mma/config.json` that overrides the
|
|
103
|
-
* fixed server args can't silently re-break every LSP server. In-memory only —
|
|
104
|
-
* mirrors the `restoreDangerousPatterns` precedent; the file keeps the old
|
|
105
|
-
* args and is normalized again on every load.
|
|
106
|
-
*/
|
|
107
|
-
function normalizeLspServerArgs(config) {
|
|
108
|
-
const servers = config.lsp?.servers;
|
|
109
|
-
if (!servers)
|
|
110
|
-
return;
|
|
111
|
-
for (const [lang, legacy] of Object.entries(LEGACY_LSP_SERVER_ARGS)) {
|
|
112
|
-
const server = servers[lang];
|
|
113
|
-
const def = DEFAULT_LSP_CONFIG.servers[lang];
|
|
114
|
-
if (!server || server.command !== "npx" || !def)
|
|
115
|
-
continue;
|
|
116
|
-
const storedArgs = Array.isArray(server.args) ? server.args : null;
|
|
117
|
-
const isLegacy = storedArgs === null || JSON.stringify(storedArgs) === JSON.stringify(legacy.args);
|
|
118
|
-
if (isLegacy) {
|
|
119
|
-
server.args = def.args;
|
|
120
|
-
server.timeout = def.timeout;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
85
|
function loadJSON(path) {
|
|
125
86
|
try {
|
|
126
87
|
if (existsSync(path)) {
|
|
@@ -199,8 +160,6 @@ export function loadConfig(options) {
|
|
|
199
160
|
if (config.security?.contentScan?.dangerousPatterns) {
|
|
200
161
|
config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
|
|
201
162
|
}
|
|
202
|
-
// Replace stale pre-0.41.1 LSP server args with the current defaults.
|
|
203
|
-
normalizeLspServerArgs(config);
|
|
204
163
|
config = applyEnvVars(config);
|
|
205
164
|
// Decrypt sensitive fields in the loaded config
|
|
206
165
|
try {
|
|
@@ -220,12 +179,6 @@ export function loadConfig(options) {
|
|
|
220
179
|
}
|
|
221
180
|
export function validateConfig(config, allToolTags) {
|
|
222
181
|
const errors = validateExpertConfig(config, allToolTags);
|
|
223
|
-
const knownTags = new Set(allToolTags);
|
|
224
|
-
for (const tag of config.tools?.defaultTags ?? []) {
|
|
225
|
-
if (!knownTags.has(tag)) {
|
|
226
|
-
errors.push(`tools.defaultTags references unknown tag "${tag}". Known tags: ${Array.from(knownTags).join(", ")}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
182
|
if (errors.length > 0) {
|
|
230
183
|
throw new Error(`Config validation failed:\n${errors.join("\n")}`);
|
|
231
184
|
}
|