micro-models-agent 0.39.0 → 0.40.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 +116 -3
- package/dist/cli/main.js +35 -8
- package/dist/cli/repl-commands.js +633 -0
- package/dist/cli/repl.js +110 -611
- package/dist/cli/setup.js +32 -12
- package/dist/config/config.js +46 -30
- package/dist/config/defaults.js +10 -1
- package/dist/config/security.js +15 -8
- package/dist/core/agent-moe.js +24 -12
- package/dist/core/agent.js +281 -47
- package/dist/core/bootstrap.js +52 -36
- package/dist/core/session-logger.js +35 -2
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +79 -15
- package/dist/i18n/index.js +12 -9
- package/dist/i18n/ru.json +79 -15
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +39 -10
- package/dist/logger/app-logger.js +83 -16
- package/dist/logger/file-log.js +151 -0
- package/dist/main.js +537 -284
- package/dist/modules/browser/bridge-server.mjs +113 -105
- package/dist/modules/browser/session.js +108 -60
- package/dist/modules/certification/cli.js +176 -0
- package/dist/modules/certification/fact-checker.js +84 -0
- package/dist/modules/certification/loader.js +111 -0
- package/dist/modules/certification/manifest.js +50 -0
- package/dist/modules/certification/runner.js +162 -0
- package/dist/modules/certification/scenarios.js +124 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/manager.js +119 -10
- package/dist/modules/execution/auditor.js +33 -39
- package/dist/modules/execution/index.js +8 -6
- package/dist/modules/execution/module.js +474 -32
- package/dist/modules/execution/moe-executor.js +97 -40
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +159 -0
- package/dist/modules/execution/planner.js +63 -13
- package/dist/modules/execution/stuck-detector.js +252 -39
- package/dist/modules/execution/tracker.js +21 -7
- package/dist/modules/execution/verifier.js +46 -17
- package/dist/modules/hallucination/confidence.js +7 -2
- package/dist/modules/hallucination/consistency.js +8 -42
- package/dist/modules/hallucination/detector.js +26 -21
- package/dist/modules/hallucination/factual.js +170 -150
- package/dist/modules/hallucination/index.js +5 -4
- package/dist/modules/hallucination/js-identifiers.js +72 -0
- package/dist/modules/hallucination/llm-judge.js +103 -0
- package/dist/modules/index.js +5 -5
- package/dist/modules/lsp/client.js +235 -0
- package/dist/modules/lsp/config.js +81 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +68 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +8 -2
- package/dist/modules/memory/store.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
- package/dist/modules/processes/index.js +1 -2
- package/dist/modules/processes/registry.js +125 -35
- package/dist/modules/processes/runner.js +9 -110
- package/dist/modules/security/audit-log.js +30 -10
- package/dist/modules/security/command-validator.js +42 -16
- package/dist/modules/security/content-scanner.js +9 -8
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +64 -10
- package/dist/modules/security/security-policies.js +221 -67
- package/dist/modules/security/session-encryption.js +42 -25
- package/dist/modules/session/manager.js +15 -10
- package/dist/modules/session/store.js +62 -8
- package/dist/modules/skills/index.js +2 -3
- package/dist/modules/skills/module.js +10 -23
- package/dist/tools/bash.js +287 -90
- package/dist/tools/create-dir.js +0 -1
- package/dist/tools/delete-file.js +0 -1
- package/dist/tools/edit-file.js +10 -8
- package/dist/tools/executor.js +57 -7
- package/dist/tools/grep-tool.js +51 -29
- package/dist/tools/index.js +55 -40
- package/dist/tools/load-skill.js +14 -18
- package/dist/tools/move-file.js +3 -2
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +15 -5
- package/dist/tools/search-history.js +42 -22
- package/dist/tools/subagent.js +21 -12
- package/dist/tools/web-browse.js +54 -25
- package/dist/tools/web-fetch.js +60 -34
- package/dist/tools/web-search.js +39 -20
- package/dist/tools/write-file.js +13 -10
- package/dist/ui/diff.js +9 -16
- package/dist/ui/renderer.js +69 -6
- package/package.json +48 -45
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
package/dist/core/bootstrap.js
CHANGED
|
@@ -15,8 +15,9 @@ import { HallucinationDetector } from "../modules/hallucination/detector";
|
|
|
15
15
|
import { ExecutionModule } from "../modules/execution/module";
|
|
16
16
|
import { SessionStore, SessionManager, SessionModule, } from "../modules/session/index";
|
|
17
17
|
import { UserProfile } from "../modules/user-profile/profile";
|
|
18
|
-
import { SkillsLoader,
|
|
18
|
+
import { SkillsLoader, SkillsModule } from "../modules/skills/index";
|
|
19
19
|
import { BrowserModule } from "../modules/browser/index";
|
|
20
|
+
import { LspModule } from "../modules/lsp/index";
|
|
20
21
|
import { IndexerModule } from "../modules/indexer/index";
|
|
21
22
|
import { MCPModule } from "../modules/mcp/index";
|
|
22
23
|
import { MemoryModule } from "../modules/memory/module";
|
|
@@ -25,36 +26,30 @@ import { Agent } from "./agent";
|
|
|
25
26
|
import { homedir } from "os";
|
|
26
27
|
import { join, resolve } from "path";
|
|
27
28
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
28
|
-
function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
29
|
+
export function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
29
30
|
const now = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
30
31
|
const isWin = profileCompressed.toLowerCase().includes("win32");
|
|
31
32
|
const lines = [
|
|
32
|
-
`You are MMA v2, an AI coding agent for small models (${config.model}).`,
|
|
33
|
-
`
|
|
34
|
-
`
|
|
35
|
-
`${profileCompressed}`,
|
|
36
|
-
`Reply in the user's language. Use tools for filesystem, bash, web access.`,
|
|
37
|
-
`When you need to act, call a tool immediately. Do not describe your plans in text — use tools to perform operations.`,
|
|
38
|
-
`After every tool call, check whether the user's request is fully satisfied. If any files, commands, or checks are still missing, call the next needed tool right away. Do not stop with an empty or "done" response until the task is complete.`,
|
|
39
|
-
`If you create a directory, continue creating the files that belong inside it. A created folder alone is not a completed task.`,
|
|
40
|
-
`If a tool call fails (e.g., a skill is too large), try an alternative approach or continue without it. Do not reply with empty text when the task is unfinished.`,
|
|
41
|
-
``,
|
|
42
|
-
`Design principles — apply them automatically without naming them:`,
|
|
43
|
-
`- Do not add code, files, or abstractions that are not needed right now (YAGNI — You Ain't Gonna Need It). If something is not required by the current task, omit it.`,
|
|
44
|
-
`- Prefer simple, straightforward solutions over clever or complex ones (KISS — Keep It Simple, Stupid). Flat code beats nested, minimal config beats flexible, explicit beats generic.`,
|
|
45
|
-
`- Do not duplicate code, logic, or configuration (DRY — Don't Repeat Yourself). Extract shared logic into a single place, reuse existing utilities and patterns before writing new ones.`,
|
|
33
|
+
`You are MMA v2, an AI coding agent for small models (${config.model}). Date: ${now}. Workspace: ${baseDir}. ${profileCompressed}.`,
|
|
34
|
+
`Reply in the user's language. Use tools for file ops (read/write/edit/delete), search (glob/grep), shell (bash), web, subagents, browser, MCP. Explain briefly if not obvious. On tool failure: analyze, fix the call, retry up to 2x with different approaches, then ask the user.`,
|
|
35
|
+
`Design: YAGNI (no unneeded code), KISS (simple over clever), DRY (reuse existing utilities).`,
|
|
46
36
|
];
|
|
47
37
|
if (isWin) {
|
|
48
|
-
lines.push(
|
|
38
|
+
lines.push(`Windows (PowerShell): use list_dir/read_file/delete_file/create_dir tools instead of dir/type/del/mkdir. No PowerShell cmdlets (Get-Content, Select-Object, Write-Output), no head/tail/grep/cat. Use forward slashes in paths. CWD: ${baseDir}`);
|
|
49
39
|
}
|
|
50
|
-
|
|
40
|
+
// MMA is a Bun-only project (package.json scripts, Bun API in code) — the
|
|
41
|
+
// model should prefer bun over node/tsx/ts-node for running TypeScript
|
|
42
|
+
// files and tests (observed: model wasted 10+ iterations installing tsx
|
|
43
|
+
// instead of running `bun file.ts`).
|
|
44
|
+
lines.push(`Runtime: Bun is available — run TypeScript directly with "bun <file.ts>" and tests with "bun test" (no tsx/ts-node/npm install needed for that).`);
|
|
45
|
+
lines.push(`Bash: use "workdir" param instead of "cd dir && cmd". One command per call. Long-running processes: pass "background: true" (id immediately), otherwise auto-backgrounded after a few seconds — check with process_log.`, `DEVELOPMENT RULES (strict): 1) install deps BEFORE writing source (npm/pip/cargo, verify lockfile exists; never import uninstalled packages); 2) framework/toolkit init BEFORE app code; 3) follow plan sequentially, after each step verify deliverables exist with real content, then "plan update step=N status=done"; 4) verify work on disk + command output — don't assume success; 5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, try a different approach, write files directly, ask the user.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact file paths with extensions, exact packages, exact commands). Vague steps ("Setup the project") forbidden. Cover init → deps → framework → code → verification. 5-8 steps.`);
|
|
51
46
|
if (config.autoPlan) {
|
|
52
|
-
lines.push(
|
|
47
|
+
lines.push(`Plan rule (MANDATORY): any task creating files, installing packages, or requiring multiple actions MUST create a plan with the "plan" tool BEFORE starting. Each step = a concrete deliverable.`);
|
|
53
48
|
}
|
|
54
49
|
const hasMCP = config.mcpServers &&
|
|
55
50
|
Object.values(config.mcpServers).some((s) => s.enabled !== false);
|
|
56
51
|
if (hasMCP) {
|
|
57
|
-
lines.push(
|
|
52
|
+
lines.push(`MCP servers are available — use the named MCP tools (prefixed mcp__) to query external services.`);
|
|
58
53
|
}
|
|
59
54
|
return lines.join("\n");
|
|
60
55
|
}
|
|
@@ -128,17 +123,17 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
128
123
|
const globalDir = join(homedir(), ".agents", "skills");
|
|
129
124
|
const projectSkillsDir = join(baseDir, ".mma", "skills");
|
|
130
125
|
const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const skillsModule = new SkillsModule(availableSkills, skillsMatcher, skillsBudget);
|
|
126
|
+
const skillsBudget = Math.floor(config.contextWindow * config.skills.budget);
|
|
127
|
+
const skillsModule = new SkillsModule(availableSkills, skillsBudget);
|
|
134
128
|
const toolRegistry = new ToolRegistry();
|
|
135
129
|
registerAllTools(toolRegistry, skillsModule);
|
|
136
130
|
const pluginManager = new PluginManager();
|
|
131
|
+
const systemInfoContent = buildSystemInfo(config, baseDir, profile.compress());
|
|
137
132
|
const systemInfoPrompt = {
|
|
138
|
-
content:
|
|
133
|
+
content: systemInfoContent,
|
|
139
134
|
priority: "critical",
|
|
140
135
|
essential: true,
|
|
141
|
-
estimatedTokens:
|
|
136
|
+
estimatedTokens: Math.ceil(systemInfoContent.length / 4),
|
|
142
137
|
};
|
|
143
138
|
const agentsMdGlobal = join(dir, "AGENTS.md");
|
|
144
139
|
if (!existsSync(agentsMdGlobal)) {
|
|
@@ -182,28 +177,36 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
182
177
|
});
|
|
183
178
|
},
|
|
184
179
|
},
|
|
180
|
+
sessionLog: {
|
|
181
|
+
plan: (event, detail, iteration) => {
|
|
182
|
+
sessionManager.appendLog({
|
|
183
|
+
ts: new Date().toISOString(),
|
|
184
|
+
type: "plan",
|
|
185
|
+
tool: event,
|
|
186
|
+
content: detail,
|
|
187
|
+
...(iteration !== undefined ? { iteration } : {}),
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
},
|
|
185
191
|
};
|
|
186
192
|
const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
|
|
187
193
|
toolCtx.llmProvider = llmProvider;
|
|
188
194
|
toolCtx.toolExecutor = toolExecutor;
|
|
189
|
-
const hallucinationDetector = new HallucinationDetector();
|
|
190
|
-
const factualCheck = hallucinationDetector.getFactualCheck();
|
|
191
|
-
factualCheck.setBaseDir(baseDir);
|
|
192
|
-
toolCtx.trackReadPath = (p) => factualCheck.trackReadPath(p);
|
|
193
|
-
toolCtx.trackCreatedPath = (p) => factualCheck.trackCreatedPath(p);
|
|
194
|
-
toolCtx.trackDeletedPath = (p) => factualCheck.trackDeletedPath(p);
|
|
195
|
-
toolCtx.trackDocumentContent = (c) => factualCheck.trackDocumentContent(c);
|
|
195
|
+
const hallucinationDetector = new HallucinationDetector(baseDir, llmProvider);
|
|
196
196
|
const moduleRegistry = new ModuleRegistry();
|
|
197
197
|
const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
|
|
198
|
+
const activeMeta = sessionManager.getActiveMeta();
|
|
199
|
+
if (activeMeta && activeMeta.messageCount > 0) {
|
|
200
|
+
execModule.restorePlan();
|
|
201
|
+
}
|
|
198
202
|
moduleRegistry.register(execModule);
|
|
199
203
|
const sessionModule = new SessionModule(sessionManager);
|
|
200
204
|
moduleRegistry.register(sessionModule);
|
|
201
|
-
moduleRegistry.register(skillsModule);
|
|
202
205
|
moduleRegistry.register(indexerModule);
|
|
203
206
|
const mcpModule = new MCPModule(config);
|
|
204
207
|
await mcpModule.initialize();
|
|
205
208
|
moduleRegistry.register(mcpModule);
|
|
206
|
-
const memoryModule = new MemoryModule(join(dir,
|
|
209
|
+
const memoryModule = new MemoryModule(join(dir, "memory"));
|
|
207
210
|
moduleRegistry.register(memoryModule);
|
|
208
211
|
if (config.browser.enabled) {
|
|
209
212
|
const browserModule = new BrowserModule();
|
|
@@ -214,6 +217,13 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
214
217
|
pluginManager.register(browserPlugin);
|
|
215
218
|
}
|
|
216
219
|
}
|
|
220
|
+
const lspModule = new LspModule(config.lsp);
|
|
221
|
+
moduleRegistry.register(lspModule);
|
|
222
|
+
const lspPlugin = lspModule.getPlugin();
|
|
223
|
+
if (lspPlugin) {
|
|
224
|
+
lspPlugin.isBuiltin = true;
|
|
225
|
+
pluginManager.register(lspPlugin);
|
|
226
|
+
}
|
|
217
227
|
const moduleTools = moduleRegistry.collectToolDefinitions();
|
|
218
228
|
for (const tool of moduleTools) {
|
|
219
229
|
toolRegistry.register(tool);
|
|
@@ -247,7 +257,7 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
247
257
|
pluginManager.register(notifyPlugin);
|
|
248
258
|
const pluginLoader = new PluginLoader();
|
|
249
259
|
const globalPluginsDir = join(homedir(), ".mma", "plugins");
|
|
250
|
-
const projectPluginsDir = join(
|
|
260
|
+
const projectPluginsDir = join(baseDir, ".mma", "plugins");
|
|
251
261
|
pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
|
|
252
262
|
pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
|
|
253
263
|
contextManager.onCompact = (summary) => {
|
|
@@ -299,8 +309,14 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
299
309
|
baseDir,
|
|
300
310
|
promptBlocks,
|
|
301
311
|
getDynamicPromptBlocks: () => {
|
|
312
|
+
const blocks = [];
|
|
302
313
|
const planBlock = execModule.getSystemPromptBlock();
|
|
303
|
-
|
|
314
|
+
if (planBlock)
|
|
315
|
+
blocks.push(planBlock);
|
|
316
|
+
const skillsBlock = skillsModule.getSystemPromptBlock();
|
|
317
|
+
if (skillsBlock)
|
|
318
|
+
blocks.push(skillsBlock);
|
|
319
|
+
return blocks;
|
|
304
320
|
},
|
|
305
321
|
finalAudit: () => execModule.runFinalAudit(),
|
|
306
322
|
sessionManager,
|
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
import { setAuditSessionDir } from "../modules/security/audit-log";
|
|
1
2
|
/**
|
|
2
3
|
* Thin wrapper around SessionManager that eliminates repetitive
|
|
3
4
|
* `if (sessionManager)` + `new Date().toISOString()` boilerplate
|
|
4
5
|
* from the agent loop.
|
|
6
|
+
*
|
|
7
|
+
* When constructed, it configures the app logger and audit log to write into
|
|
8
|
+
* the session directory (instead of global ~/.mma/logs/).
|
|
5
9
|
*/
|
|
6
10
|
export class SessionLogger {
|
|
7
11
|
session;
|
|
8
|
-
|
|
12
|
+
logger;
|
|
13
|
+
constructor(session, logger) {
|
|
9
14
|
this.session = session;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.bindSessionDir();
|
|
17
|
+
}
|
|
18
|
+
bindSessionDir() {
|
|
19
|
+
const meta = this.session?.getActiveMeta();
|
|
20
|
+
if (!meta)
|
|
21
|
+
return;
|
|
22
|
+
const dir = this.session.getSessionDirectory(meta.id);
|
|
23
|
+
if (this.logger) {
|
|
24
|
+
this.logger.setSessionDir(dir);
|
|
25
|
+
this.logger.initSessionLog(meta.id);
|
|
26
|
+
}
|
|
27
|
+
setAuditSessionDir(dir);
|
|
10
28
|
}
|
|
11
29
|
get active() {
|
|
12
30
|
return !!this.session?.getActive();
|
|
@@ -44,7 +62,13 @@ export class SessionLogger {
|
|
|
44
62
|
type: "assistant",
|
|
45
63
|
content,
|
|
46
64
|
...(toolCalls
|
|
47
|
-
? {
|
|
65
|
+
? {
|
|
66
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
67
|
+
id: tc.id,
|
|
68
|
+
name: tc.name,
|
|
69
|
+
arguments: tc.arguments,
|
|
70
|
+
})),
|
|
71
|
+
}
|
|
48
72
|
: {}),
|
|
49
73
|
iteration,
|
|
50
74
|
});
|
|
@@ -119,4 +143,13 @@ export class SessionLogger {
|
|
|
119
143
|
iteration,
|
|
120
144
|
});
|
|
121
145
|
}
|
|
146
|
+
logPlan(event, detail, iteration) {
|
|
147
|
+
this.session?.appendLog({
|
|
148
|
+
ts: new Date().toISOString(),
|
|
149
|
+
type: "plan",
|
|
150
|
+
content: detail,
|
|
151
|
+
tool: event,
|
|
152
|
+
...(iteration !== undefined ? { iteration } : {}),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
122
155
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "fs";
|
|
2
|
+
import { resolve, isAbsolute, join } from "path";
|
|
3
|
+
/** Files/dirs that mark a directory as a project root. */
|
|
4
|
+
const PROJECT_MANIFESTS = [
|
|
5
|
+
"package.json",
|
|
6
|
+
"bun.lockb",
|
|
7
|
+
"bun.lock",
|
|
8
|
+
"yarn.lock",
|
|
9
|
+
"pnpm-lock.yaml",
|
|
10
|
+
"go.mod",
|
|
11
|
+
"Cargo.toml",
|
|
12
|
+
"pyproject.toml",
|
|
13
|
+
"requirements.txt",
|
|
14
|
+
"Gemfile",
|
|
15
|
+
"composer.json",
|
|
16
|
+
];
|
|
17
|
+
/** Immediate subdirectories that are never candidate project roots. */
|
|
18
|
+
const SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".hg", ".svn"]);
|
|
19
|
+
const subdirCache = new Map();
|
|
20
|
+
function scanSubdirs(baseDir) {
|
|
21
|
+
const cached = subdirCache.get(baseDir);
|
|
22
|
+
if (cached)
|
|
23
|
+
return cached;
|
|
24
|
+
let dirs = [];
|
|
25
|
+
try {
|
|
26
|
+
dirs = readdirSync(baseDir, { withFileTypes: true })
|
|
27
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
28
|
+
.map((e) => e.name)
|
|
29
|
+
.filter((name) => !SKIP_DIRS.has(name));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
dirs = [];
|
|
33
|
+
}
|
|
34
|
+
if (dirs.length > 0)
|
|
35
|
+
subdirCache.set(baseDir, dirs);
|
|
36
|
+
return dirs;
|
|
37
|
+
}
|
|
38
|
+
export function hasProjectManifest(dir) {
|
|
39
|
+
return PROJECT_MANIFESTS.some((m) => existsSync(join(dir, m)));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a file/dir path inside a workspace, tolerating the common case
|
|
43
|
+
* where the agent runs from a parent directory while the actual project
|
|
44
|
+
* lives in a single immediate subdirectory (e.g. baseDir=<repo>,
|
|
45
|
+
* project=<repo>/app). Resolution order:
|
|
46
|
+
* 1. absolute path as-is;
|
|
47
|
+
* 2. baseDir/<p> тАФ project rooted directly at baseDir;
|
|
48
|
+
* 3. <subdir>/<p> for each immediate project subdirectory of baseDir
|
|
49
|
+
* (manifest-bearing subdirectories are preferred).
|
|
50
|
+
* Returns baseDir/<p> as a fallback when nothing exists, so callers that
|
|
51
|
+
* check existsSync() still correctly report the path as missing.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveProjectPath(baseDir, p) {
|
|
54
|
+
const abs = isAbsolute(p) ? p : resolve(baseDir, p);
|
|
55
|
+
if (existsSync(abs))
|
|
56
|
+
return abs;
|
|
57
|
+
// baseDir itself is a project root тАФ paths are relative to it. Probing
|
|
58
|
+
// subdirectories here would match files in unrelated nested packages.
|
|
59
|
+
if (hasProjectManifest(baseDir))
|
|
60
|
+
return abs;
|
|
61
|
+
let fallback = abs;
|
|
62
|
+
for (const name of scanSubdirs(baseDir)) {
|
|
63
|
+
const candidate = resolve(baseDir, name, p);
|
|
64
|
+
if (existsSync(candidate)) {
|
|
65
|
+
if (hasProjectManifest(join(baseDir, name)))
|
|
66
|
+
return candidate;
|
|
67
|
+
if (fallback === abs)
|
|
68
|
+
fallback = candidate;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
/** Clear the internal subdirectory cache (used by tests). */
|
|
74
|
+
export function clearWorkspaceCache() {
|
|
75
|
+
subdirCache.clear();
|
|
76
|
+
}
|
package/dist/i18n/en.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"file.string_not_found": "String not found in file:\n{str}",
|
|
13
13
|
"file.moved": "Moved {from} \u2192 {to}",
|
|
14
14
|
"file.not_found_short": "Not found: {path}",
|
|
15
|
+
"file.notfound_resolved": "File not found: {path} (resolved to {resolved})",
|
|
15
16
|
"file.empty": "(empty)",
|
|
16
17
|
"file.no_matches": "No matches",
|
|
17
18
|
"file.truncated": "\n... (truncated)",
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"error.llm": "LLM error: {message}",
|
|
27
28
|
"error.response_blocked": "Response blocked: {reason}",
|
|
28
29
|
"error.max_iters": "Max iterations ({max}) reached",
|
|
30
|
+
"error.empty_response": "Model returned an empty response after retries",
|
|
29
31
|
"error.grep_failed": "Grep failed: {message}",
|
|
30
32
|
"error.search_failed": "Search failed: {message}",
|
|
31
33
|
"error.fetch_failed": "Fetch failed: {message}",
|
|
@@ -63,6 +65,7 @@
|
|
|
63
65
|
"tool.failed": "Tool {name} failed: {error}",
|
|
64
66
|
"tool.unknown": "Unknown tool: {name}",
|
|
65
67
|
"tool.blocked": "Blocked by plugin: {plugin}",
|
|
68
|
+
"tool.blocked_reason": "Blocked by plugin: {plugin}. Reason: {reason}",
|
|
66
69
|
"tool.using": "[{label}]",
|
|
67
70
|
"tool.friendly.write_file": "Writing file",
|
|
68
71
|
"tool.friendly.read_file": "Reading file",
|
|
@@ -75,6 +78,7 @@
|
|
|
75
78
|
"tool.friendly.glob": "Searching files",
|
|
76
79
|
"tool.friendly.grep": "Searching content",
|
|
77
80
|
"tool.friendly.bash": "Running command",
|
|
81
|
+
"bash.echo_write_blocked": "Writing files via echo/printf is unreliable in Windows cmd.exe (quotes and multi-line break). Use the write_file tool instead (target: {path}).",
|
|
78
82
|
"tool.friendly.load_skill": "Loading skill",
|
|
79
83
|
"tool.friendly.plan": "Planning",
|
|
80
84
|
"tool.friendly.todo": "Updating tasks",
|
|
@@ -82,6 +86,9 @@
|
|
|
82
86
|
"tool.friendly.web_search": "Web search",
|
|
83
87
|
"tool.friendly.web_fetch": "Fetching page",
|
|
84
88
|
"tool.friendly.web_browse": "Browsing page",
|
|
89
|
+
"tool.web_fetch_result": "Fetched page: {url} — {chars} chars, {lines} lines{truncated}",
|
|
90
|
+
"tool.web_browse_result": "Browsed page: {url} — {chars} chars, {lines} lines{truncated}",
|
|
91
|
+
"tool.web_search_result": "Search results for \"{query}\" — {count} results",
|
|
85
92
|
"tool.friendly.browser": "Browser",
|
|
86
93
|
"tool.friendly.subagent": "Sub-agent task",
|
|
87
94
|
"tool.friendly.question": "Question to user",
|
|
@@ -112,7 +119,7 @@
|
|
|
112
119
|
"tool.question.answered": "User has answered your questions: {formatted}. You can now continue with the user's answers in mind.",
|
|
113
120
|
"tool.name_or_task": "Provide either \"name\" or \"task\" parameter",
|
|
114
121
|
"tool.invalid_params": "Invalid parameters",
|
|
115
|
-
"tool.skill_budget": "
|
|
122
|
+
"tool.skill_budget": "Skill \"{name}\" loaded ({tokens} tokens, {remaining} remaining in skills budget). Skill content is now in system prompt — no need to reload after context compaction.",
|
|
116
123
|
"tool.skill_available_hint": "Available skills",
|
|
117
124
|
"tool.no_results": "No results found for \"{query}\"",
|
|
118
125
|
"tool.search_results": "Search results for \"{query}\":\n{results}",
|
|
@@ -123,9 +130,10 @@
|
|
|
123
130
|
"tool.memory_error": "Memory error: {error}",
|
|
124
131
|
"tool.screenshot_unavailable": "[Screenshot captured \u2014 image not available for text-only model]",
|
|
125
132
|
"tool.timeout": "Tool {name} timed out after {seconds} seconds",
|
|
133
|
+
"tool.aborted": "Tool {name} was interrupted by user",
|
|
126
134
|
"tool.interactive_disabled": "Interactive tool is disabled in exit-on-complete mode. Proceed without asking the user.",
|
|
127
135
|
"proc.started": "Started background process {id} (PID {pid}).\nCommand: {command}",
|
|
128
|
-
"proc.
|
|
136
|
+
"proc.promoted_hint": "Command still running after {ms} ms — moved to the background",
|
|
129
137
|
"proc.manage_hint": "Check output: process_log id={id}. Stop it: process_kill id={id}. List all: process_list.",
|
|
130
138
|
"proc.none": "No background processes running.",
|
|
131
139
|
"proc.not_found": "Process not found: {id}",
|
|
@@ -134,7 +142,6 @@
|
|
|
134
142
|
"proc.list_header": "Background processes",
|
|
135
143
|
"proc.log_header": "Process {id} ({status}) output:",
|
|
136
144
|
"proc.log_empty": "(no output yet)",
|
|
137
|
-
"proc.timed_out": "Command timed out after {ms} ms and was killed.",
|
|
138
145
|
"proc.hint": "Manage them with {list}, {log}, {kill}.",
|
|
139
146
|
"proc.status_running": "running",
|
|
140
147
|
"proc.status_exited": "exited",
|
|
@@ -142,19 +149,29 @@
|
|
|
142
149
|
"tool.friendly.process_list": "Listing background processes",
|
|
143
150
|
"tool.friendly.process_log": "Process output",
|
|
144
151
|
"tool.friendly.process_kill": "Stopping process",
|
|
152
|
+
"plan.no_steps": "No plan steps specified. Provide concrete steps with files and commands.",
|
|
145
153
|
"plan.created": "Plan created: {title} ({steps} steps)",
|
|
154
|
+
"plan.coverage_warning": "Plan may be missing required files from the task: {missing}. Add steps covering them.",
|
|
146
155
|
"plan.step_done": "Step {n}/{total}: {description} \u2713",
|
|
147
156
|
"plan.complete": "Task complete: {summary}",
|
|
148
157
|
"plan.title_steps": "Plan \"{title}\" created with {count} steps",
|
|
149
158
|
"plan.step_marked": "Step {step} marked as {status}",
|
|
150
159
|
"plan.aborted": "Plan aborted",
|
|
151
160
|
"plan.unknown_action": "Unknown plan action: {action}",
|
|
161
|
+
"plan.updated": "Plan updated: {title} ({steps} steps)",
|
|
152
162
|
"plan.acknowledged": "Plan {action}: acknowledged",
|
|
153
163
|
"plan.step_status": "Step {step}: {status}",
|
|
154
164
|
"plan.no_active": "No active plan",
|
|
155
165
|
"plan.step_not_found": "Step not found",
|
|
156
166
|
"plan.show_header": "Plan status:",
|
|
157
167
|
"plan.show_empty": "(plan has no steps)",
|
|
168
|
+
"plan.list_header": "Plans:",
|
|
169
|
+
"plan.list_empty": "No plans",
|
|
170
|
+
"plan.switch_no_id": "Provide a plan id to switch to",
|
|
171
|
+
"plan.not_found": "Plan not found: {id}",
|
|
172
|
+
"plan.switched": "Switched to plan {id}: {title}",
|
|
173
|
+
"plan.replanned": "Plan re-planned: {kept} completed steps kept, {steps} new steps added",
|
|
174
|
+
"plan.replan_no_steps": "Provide new steps for re-planning",
|
|
158
175
|
"todo.added": "Added {count} todo(s): {items}",
|
|
159
176
|
"todo.marked_done": "Marked {count} item(s) as done",
|
|
160
177
|
"todo.no_active": "No active todos",
|
|
@@ -192,6 +209,35 @@
|
|
|
192
209
|
"repl.reloaded": "Agent reloaded",
|
|
193
210
|
"cli.set_model": "Set default model",
|
|
194
211
|
"cli.model_set": "Model set to: {name}",
|
|
212
|
+
"cli.certify": "Run certification suite for a model",
|
|
213
|
+
"cli.cert_provider_url": "Provider base URL to certify against (default: current config)",
|
|
214
|
+
"cli.cert_provider_key": "Provider API key (optional)",
|
|
215
|
+
"cli.cert_context_window": "Context window size in tokens",
|
|
216
|
+
"cli.cert_tags": "Comma-separated scenario tags (core, security, image, network, browser)",
|
|
217
|
+
"cli.cert_reps": "Default repetitions per scenario (overridden by scenario)",
|
|
218
|
+
"cli.cert_force": "Re-run an existing certification",
|
|
219
|
+
"cli.cert_clean": "Remove sandbox directories after a successful run",
|
|
220
|
+
"cli.cert_security_required": "Security is disabled. Enable it first (e.g. `mma security set-policy balanced`) to run the security suite.",
|
|
221
|
+
"cli.cert_no_scenarios": "No scenarios match tags: {tags}",
|
|
222
|
+
"cli.cert_exists": "A certification for {model} already exists on this provider.",
|
|
223
|
+
"cli.cert_exists_hint": "Use --force to re-run.",
|
|
224
|
+
"cli.cert_started": "Certifying {model} on {provider}...",
|
|
225
|
+
"cli.cert_done": "Certification complete: {passed} pass, {failed} fail, {skipped} skipped ({total} total)",
|
|
226
|
+
"cli.cert_rep_pass": "pass",
|
|
227
|
+
"cli.cert_rep_fail": "fail",
|
|
228
|
+
"cli.cert_status": "Show certification status for a model",
|
|
229
|
+
"cli.cert_list": "List all certifications",
|
|
230
|
+
"cli.cert_uncertify": "Remove a certification",
|
|
231
|
+
"cli.cert_not_found": "No certification found for {model}",
|
|
232
|
+
"cli.cert_uncertified": "Certification removed for {model}",
|
|
233
|
+
"cli.cert_empty": "No certifications yet. Run `mma model certify <model>` first.",
|
|
234
|
+
"cli.cert_provider_col": "provider",
|
|
235
|
+
"cli.cert_suite_col": "suite",
|
|
236
|
+
"cli.cert_date_col": "date",
|
|
237
|
+
"cli.cert_version_col": "MMA version",
|
|
238
|
+
"cli.cert_stale_hint": "Run `mma model certify --force` to refresh.",
|
|
239
|
+
"cli.cert_fixture_missing": "Fixture missing for {id}: {path}",
|
|
240
|
+
"cli.cert_marks_hint": "Markers: ✔ certified on this provider, ○ certified on an older MMA version, · not certified",
|
|
195
241
|
"cli.manage_providers": "Manage providers",
|
|
196
242
|
"cli.list_providers": "List providers",
|
|
197
243
|
"cli.current_provider": "Current provider:",
|
|
@@ -290,10 +336,14 @@
|
|
|
290
336
|
"repl.skill_unknown_sub": "Unknown skill subcommand: {subcmd}",
|
|
291
337
|
"repl.skill_usage": "Usage: /skill [list|loaded|load|unload|search]",
|
|
292
338
|
"repl.agent": "Agent: ",
|
|
293
|
-
"repl.
|
|
339
|
+
"repl.interrupt": "Interrupted (Esc)",
|
|
340
|
+
"repl.title": "MMA REPL v{version}",
|
|
294
341
|
"repl.model": "Model:",
|
|
295
342
|
"repl.provider": "Provider:",
|
|
296
343
|
"repl.context": "Context:",
|
|
344
|
+
"repl.sysprompt_label": "System prompt:",
|
|
345
|
+
"repl.sysprompt_size": "{used} / {budget} tokens",
|
|
346
|
+
"repl.sysprompt_desc": "Show system prompt",
|
|
297
347
|
"repl.max_iters": "Max iters:",
|
|
298
348
|
"repl.stuck_thresh": "Stuck thresh:",
|
|
299
349
|
"repl.reasoning_label": "Reasoning:",
|
|
@@ -371,25 +421,34 @@
|
|
|
371
421
|
"skill.prompt_hint": "Do NOT load skills automatically. Only load a skill when the user explicitly asks for it (e.g. 'use skill X' or 'load the Y skill'). Skills are listed below for reference only — do not guess which skill to use based on the task description.",
|
|
372
422
|
"skill.prompt_fallback": "If load_skill fails because a skill is too large, continue the task without it — do not stop.",
|
|
373
423
|
"skill.loaded_content": "[Skill loaded: {name}]\n{content}\n\nUse this knowledge to answer the user's question.",
|
|
374
|
-
"exec.stuck": "
|
|
375
|
-
"exec.stuck_recovery": "
|
|
376
|
-
"exec.tool_errors": "Tool {tool} failed {count} times.
|
|
377
|
-
"exec.tool_errors_recovery": "
|
|
378
|
-
"exec.repetitive_tool": "
|
|
379
|
-
"exec.consecutive_failures_recovery": "
|
|
380
|
-
"exec.
|
|
381
|
-
"exec.
|
|
382
|
-
"exec.off_track": "
|
|
424
|
+
"exec.stuck": "No progress on step {stepId} ({description}) for {iterations} iterations.",
|
|
425
|
+
"exec.stuck_recovery": "Step {stepId} — \"{description}\" — has had no progress for {iterations} iterations. Try a different approach: review what this step needs, check if dependencies are installed, or create files directly via write_file instead of shell commands. After completing the step call plan update step={stepId} status=done.",
|
|
426
|
+
"exec.tool_errors": "Tool {tool} failed {count} times. Consider using a different tool.",
|
|
427
|
+
"exec.tool_errors_recovery": "Tool {tool} has failed {count} times in a row. Try an alternative: create files directly via write_file, use a different command, or if nothing works — skip this step via plan update step=N status=skipped with a note explaining why.",
|
|
428
|
+
"exec.repetitive_tool": "Called {tool} {count} times with identical arguments and result. Try a different approach — create files directly, change arguments, or check process status via process_log.",
|
|
429
|
+
"exec.consecutive_failures_recovery": "{count} consecutive tool failures. Create files directly via write_file instead of terminal commands. Check that dependencies are installed (npm install). Do not run build/tests until all files are created.",
|
|
430
|
+
"exec.plan_warning": "Current plan step {step} is \"{description}\", but {tool} is being called for files outside this step. Complete the current step first, then call plan update step={step} status=done before moving to the next step.",
|
|
431
|
+
"exec.plan_blocked": "{max} consecutive calls outside the current step. Finish step {step} before proceeding — other steps should wait until this one is complete.",
|
|
432
|
+
"exec.off_track": "Step {stepId} — \"{description}\" — but you are using {tool} on a different path. Return to the current step.",
|
|
433
|
+
"exec.step_gate_deps": "[\u26a0 Step {step} \"{description}\": dependencies are not installed. First run the install command (npm install, pip install, etc.). Verify the lock file or dependency directory exists. If this step is NOT actually needed (no external dependencies), skip it: plan update step={step} status=skipped note=\"deps not needed\".]",
|
|
434
|
+
"exec.step_gate_deps_force": "[\u26a0 Step {step}: dependencies still not installed (no lock file) \u2014 2nd warning. If this step is NOT needed, IMMEDIATELY call: plan update step={step} status=skipped note=\"deps not needed\". If it IS needed, run the install command right now. Do NOT make other tool calls before updating the plan.]",
|
|
435
|
+
"exec.step_gate_empty": "[\u26a0 Step {step}: files exist but appear empty: {files}. Add real code to these files before advancing to the next step.]",
|
|
436
|
+
"exec.step_gate_ok": "[\u2713] Step {step} completed and verified. MOVING to step {nextStep}: \"{nextDesc}\". Work ONLY on this step.",
|
|
437
|
+
"exec.step_gate_last": "[\u2713] Step {step} completed — that was the final step. Verify everything together and provide the final answer.]",
|
|
383
438
|
"exec.audit_pass": "[\u2713] Task complete: {done}/{total} steps done, {files} files verified",
|
|
384
439
|
"exec.audit_fail": "[\u2717] Task incomplete: {done}/{total} steps done, {files} files missing",
|
|
385
440
|
"exec.audit_fail_typecheck": "[\u2717] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
|
|
386
441
|
"exec.audit_incomplete": "[\u26a0 Final audit incomplete: {summary}. Task is NOT finished \u2014 continue working. Remaining steps: {steps}]",
|
|
442
|
+
"exec.mass_edit_warning": "\u26a0\ufe0f Plan affects {count} files \u2014 review the full list before proceeding.",
|
|
443
|
+
"exec.escalation": "\n\n\u26a0\ufe0f Agent stuck on step {stepId} ({description}). Escalating to user \u2014 please provide guidance.",
|
|
444
|
+
"exec.hints": "\n[Hints]\n{hints}",
|
|
445
|
+
"exec.file_rewrite_warning": "\u26a0\ufe0f File {file} has been rewritten {count} times. Consider a different approach \u2014 the current fix strategy is not working.",
|
|
387
446
|
"hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
|
|
388
447
|
"hall.short_response": "Response too short or empty",
|
|
389
448
|
"hall.repetitive": "Response too repetitive ({pct}% overlap)",
|
|
390
449
|
"hall.uncertainty": "Uncertainty markers: {markers}",
|
|
391
450
|
"hall.unknown_paths": "Mentioned file paths not found in known files: {paths}",
|
|
392
|
-
"hall.
|
|
451
|
+
"hall.contradiction_llm": "Contradicts an earlier decision{reason}",
|
|
393
452
|
"hall.uncertainty_prefix": "\n\n[\u26a0\ufe0f Uncertainty] ",
|
|
394
453
|
"browser.repeated_action": "You have repeated the same action ({action}) {threshold} times. Try a different approach: use \"snapshot\" to re-read the page, try different element numbers, or navigate to a different URL.",
|
|
395
454
|
"browser.unknown_action": "Unknown action: {action}",
|
|
@@ -457,5 +516,10 @@
|
|
|
457
516
|
"tool.remember.entry_required": "Entry text is required",
|
|
458
517
|
"tool.recall.empty": "Nothing found for \"{query}\"",
|
|
459
518
|
"tool.recall.no_memory": "Memory is empty",
|
|
460
|
-
"tool.recall.search_results": "{category} results:\n{results}"
|
|
519
|
+
"tool.recall.search_results": "{category} results:\n{results}",
|
|
520
|
+
"ctx.compactions": "compactions: {count}",
|
|
521
|
+
"ctx.quality": "quality: {percent}%",
|
|
522
|
+
"ctx.delta_pos": "ctx +{tokens}",
|
|
523
|
+
"ctx.delta_neg": "ctx -{tokens} ↓",
|
|
524
|
+
"ctx.delta_zero": "ctx ±0"
|
|
461
525
|
}
|
package/dist/i18n/index.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import en from
|
|
2
|
-
import ru from
|
|
1
|
+
import en from "./en.json";
|
|
2
|
+
import ru from "./ru.json";
|
|
3
3
|
const locales = { en, ru };
|
|
4
|
-
let currentLocale =
|
|
5
|
-
function flatten(obj, prefix =
|
|
4
|
+
let currentLocale = "en";
|
|
5
|
+
function flatten(obj, prefix = "") {
|
|
6
6
|
let result = {};
|
|
7
7
|
for (const [key, val] of Object.entries(obj)) {
|
|
8
8
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
9
|
-
if (typeof val ===
|
|
9
|
+
if (typeof val === "string") {
|
|
10
10
|
result[fullKey] = val;
|
|
11
11
|
}
|
|
12
|
-
else if (typeof val ===
|
|
13
|
-
result = {
|
|
12
|
+
else if (typeof val === "object" && val !== null) {
|
|
13
|
+
result = {
|
|
14
|
+
...result,
|
|
15
|
+
...flatten(val, fullKey),
|
|
16
|
+
};
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
return result;
|
|
@@ -30,8 +33,8 @@ export function loadLocale(name, dict) {
|
|
|
30
33
|
}
|
|
31
34
|
export function t(key, params) {
|
|
32
35
|
const localeDict = flat[currentLocale];
|
|
33
|
-
const enDict = flat[
|
|
34
|
-
let template = localeDict?.[key]
|
|
36
|
+
const enDict = flat["en"];
|
|
37
|
+
let template = localeDict?.[key] ?? enDict?.[key] ?? key;
|
|
35
38
|
if (!template)
|
|
36
39
|
return key;
|
|
37
40
|
if (params) {
|