micro-models-agent 0.63.0 → 0.63.3
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 +27 -0
- package/dist/i18n/en.json +1 -0
- package/dist/i18n/ru.json +1 -0
- package/dist/main.js +151 -43
- package/dist/modules/browser/bridge-server.mjs +37 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.63.3] - 2026-09-17
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Plan-alignment false positives hard-blocked legitimate writes**: `checkPlanAlignment` (`src/modules/execution/module.ts`) could accumulate 3 "outside the current step" warnings and block a tool call the agent needed for the current step (observed in `ses_mu5wed4r`: the current step's own `src/main.ts` was blocked). Causes fixed:
|
|
11
|
+
- **Path separators were not normalized** (root cause): model tool calls pass absolute Windows paths (`C:\…\src\main.ts`) while plan steps name files relatively (`src/main.ts`); the raw substring comparison never intersected backslash vs forward slash, so *every* write was treated as off-path. Both sides now go through `planPathToken` (forward slashes + lowercase) before matching.
|
|
12
|
+
- The completed-step rewrite exemption compared paths one-directionally (`finishedPaths.some((s) => s.includes(p))`), so a done step naming `main.ts` did not exempt a rewrite of `src/main.ts` — the overlap check is now bidirectional, mirroring the current-step comparison.
|
|
13
|
+
- Read-only diagnostic/structural tools were not exempt: `lsp_check` (mandated by the system prompt after every write) and `project_map` now join the allow-list (`PLAN_ALIGNMENT_EXEMPT_TOOLS`), alongside the other filesystem-reads, web/memory/session reads, process introspection and interactive prompts. Only file-mutating tools are pinned to the current step's file tokens.
|
|
14
|
+
- **Plugin-blocked calls were treated as real tool failures**: a guard refusal (`onBeforeTool` → `false`/string) is now flagged `blocked` on `ToolResult` and propagated to the context message (`src/tools/executor.ts`, `src/core/agent/tool-batch.ts`, `src/modules/context/manager.ts`). Blocked calls no longer feed the compaction "Already tried & failed — do NOT repeat" memory, the per-tool repeated-failure rule, or the consecutive-failure recovery counter — so a transient guard (or a guard bug) cannot convince the agent to never retry a correct action.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Browser launch overrides**: `MMA_BROWSER_EXECUTABLE` (absolute path) and `MMA_BROWSER_CHANNEL` (`chrome`/`msedge`/`chromium`) let the browser tool use an installed system browser instead of Playwright's pinned Chromium. When the bundled revision is missing (offline / blocked CDN) it now falls back to `chrome`, then `msedge`, automatically (`src/modules/browser/driver.ts`, `bridge-server.mjs`).
|
|
18
|
+
|
|
19
|
+
## [0.63.2] - 2026-09-17
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Long answers were silently cut by the completion cap**: `max_tokens` (default 4096) is sent on every request; when the model hit it while already streaming text, `finish_reason: "length"` was ignored and the truncated text was accepted as the final answer. The provider now emits a non-fatal `warning` chunk (`src/llm/openai-compat.ts`, streaming and non-streaming paths) that the agent renders in the chat — the answer still arrives, but the user sees it was cut off. Tool-call/empty-response truncation keeps the existing recoverable-error path.
|
|
23
|
+
- **Resumed sessions lost their history**: `saveAssistantMessage` and `logToolResult` (`src/core/session-logger.ts`) persisted only the first 500 chars of each assistant/tool message, so `/resume` seeded the model with truncated context. Full content is stored now; the context budget still trims what actually goes to the LLM.
|
|
24
|
+
|
|
25
|
+
## [0.63.1] - 2026-09-17
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **`ui.verbose`**: opt-in chatty mode for internal orchestration status. MoE planning / re-plan / execution / scope one-liners (`src/core/agent-moe.ts`) are now behind this flag (default `false`) instead of always being written to the chat.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- **Failed tool output is no longer echoed verbatim**: the chat used to print the full model-facing `result.output` of any tool without a `display` — including `Hint:` / recovery directives / "Do NOT …" instructions written for the agent. Only the first non-empty line (the gist of the error) is shown now, in red (`compactToolError`, `src/core/agent/tool-output.ts`).
|
|
32
|
+
- **Live busy spinners for tool runs**: the renderer animates while `read_file` / `write_file` / `edit_file` / `bash` / … execute and restarts the spinner on every LLM "thinking" phase. Previously the spinner stopped at the first tool call and the rest of the run looked frozen. Interactive tools (`question`, `approve`) never spin; dead `BUSY_TOOLS` set removed.
|
|
33
|
+
|
|
7
34
|
## [0.63.0] - 2026-09-17
|
|
8
35
|
|
|
9
36
|
### Added
|
package/dist/i18n/en.json
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"error.llm_stream_idle_toolcall": "LLM stream stalled after a tool_call started — no data for {timeout}ms. The provider most likely buffers SSE instead of streaming tool-call argument deltas (seen with LM Studio). Raise \"retry.noDataTimeoutMs\" in ~/.mma/config.json (restart required)",
|
|
45
45
|
"error.llm_truncated": "Response hit the completion token limit ({tokens}) and was cut off before any content arrived. Split the task into smaller outputs or raise \"maxCompletionTokens\" in the config",
|
|
46
46
|
"error.llm_truncated_toolcall": "Response hit the completion token limit ({tokens}) in the middle of a tool_call — its arguments were cut off. Write the file in smaller chunks (several write_file/edit_file calls) or raise \"maxCompletionTokens\" in the config",
|
|
47
|
+
"error.llm_output_truncated": "The response hit the completion token limit ({tokens}) and was cut off — this answer is incomplete. Raise \"maxCompletionTokens\" in the config to allow longer replies",
|
|
47
48
|
"error.llm_provider_stream_error": "Provider returned an error mid-stream: {error}",
|
|
48
49
|
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
49
50
|
"env.runtime_node": "Running under Node (v{version}) — clipboard image paste, subagent performance and LSP spawn on Windows degrade. Install Bun (https://bun.sh) for full features.",
|
package/dist/i18n/ru.json
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"error.llm_stream_idle_toolcall": "Поток LLM завис после начала tool_call — нет данных {timeout}мс. Скорее всего провайдер буферизирует SSE вместо потоковой передачи аргументов tool_call (наблюдается в LM Studio). Увеличьте \"retry.noDataTimeoutMs\" в ~/.mma/config.json (нужен рестарт)",
|
|
44
44
|
"error.llm_truncated": "Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан до какого-либо содержимого. Разбейте задачу на меньшие порции вывода или увеличьте \"maxCompletionTokens\" в конфиге",
|
|
45
45
|
"error.llm_truncated_toolcall": "Ответ упёрся в лимит токенов генерации ({tokens}) посреди tool_call — его аргументы обрезаны. Пишите файл частями (несколько вызовов write_file/edit_file) или увеличьте \"maxCompletionTokens\" в конфиге",
|
|
46
|
+
"error.llm_output_truncated": "Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан — этот ответ неполный. Увеличьте \"maxCompletionTokens\" в конфиге, чтобы разрешить длинные ответы",
|
|
46
47
|
"error.llm_provider_stream_error": "Провайдер вернул ошибку посреди стрима: {error}",
|
|
47
48
|
"error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
|
|
48
49
|
"env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
|
package/dist/main.js
CHANGED
|
@@ -2373,7 +2373,8 @@ var init_defaults = __esm(() => {
|
|
|
2373
2373
|
ui: {
|
|
2374
2374
|
spinner: true,
|
|
2375
2375
|
showContextStats: false,
|
|
2376
|
-
showCompaction: true
|
|
2376
|
+
showCompaction: true,
|
|
2377
|
+
verbose: false
|
|
2377
2378
|
},
|
|
2378
2379
|
mcpServers: {
|
|
2379
2380
|
context7: {
|
|
@@ -2484,6 +2485,7 @@ The path was joined onto the working directory because it does not exist as give
|
|
|
2484
2485
|
"error.llm_stream_idle_toolcall": 'LLM stream stalled after a tool_call started — no data for {timeout}ms. The provider most likely buffers SSE instead of streaming tool-call argument deltas (seen with LM Studio). Raise "retry.noDataTimeoutMs" in ~/.mma/config.json (restart required)',
|
|
2485
2486
|
"error.llm_truncated": 'Response hit the completion token limit ({tokens}) and was cut off before any content arrived. Split the task into smaller outputs or raise "maxCompletionTokens" in the config',
|
|
2486
2487
|
"error.llm_truncated_toolcall": 'Response hit the completion token limit ({tokens}) in the middle of a tool_call — its arguments were cut off. Write the file in smaller chunks (several write_file/edit_file calls) or raise "maxCompletionTokens" in the config',
|
|
2488
|
+
"error.llm_output_truncated": 'The response hit the completion token limit ({tokens}) and was cut off — this answer is incomplete. Raise "maxCompletionTokens" in the config to allow longer replies',
|
|
2487
2489
|
"error.llm_provider_stream_error": "Provider returned an error mid-stream: {error}",
|
|
2488
2490
|
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
2489
2491
|
"env.runtime_node": "Running under Node (v{version}) — clipboard image paste, subagent performance and LSP spawn on Windows degrade. Install Bun (https://bun.sh) for full features.",
|
|
@@ -3320,6 +3322,7 @@ var init_ru = __esm(() => {
|
|
|
3320
3322
|
"error.llm_stream_idle_toolcall": 'Поток LLM завис после начала tool_call — нет данных {timeout}мс. Скорее всего провайдер буферизирует SSE вместо потоковой передачи аргументов tool_call (наблюдается в LM Studio). Увеличьте "retry.noDataTimeoutMs" в ~/.mma/config.json (нужен рестарт)',
|
|
3321
3323
|
"error.llm_truncated": 'Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан до какого-либо содержимого. Разбейте задачу на меньшие порции вывода или увеличьте "maxCompletionTokens" в конфиге',
|
|
3322
3324
|
"error.llm_truncated_toolcall": 'Ответ упёрся в лимит токенов генерации ({tokens}) посреди tool_call — его аргументы обрезаны. Пишите файл частями (несколько вызовов write_file/edit_file) или увеличьте "maxCompletionTokens" в конфиге',
|
|
3325
|
+
"error.llm_output_truncated": 'Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан — этот ответ неполный. Увеличьте "maxCompletionTokens" в конфиге, чтобы разрешить длинные ответы',
|
|
3323
3326
|
"error.llm_provider_stream_error": "Провайдер вернул ошибку посреди стрима: {error}",
|
|
3324
3327
|
"error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
|
|
3325
3328
|
"env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
|
|
@@ -6361,6 +6364,10 @@ class OpenAICompatProvider {
|
|
|
6361
6364
|
tokens: maxTokens
|
|
6362
6365
|
}), { terminal: true, recoverable: true });
|
|
6363
6366
|
}
|
|
6367
|
+
yield {
|
|
6368
|
+
type: "warning",
|
|
6369
|
+
content: t("error.llm_output_truncated", { tokens: maxTokens })
|
|
6370
|
+
};
|
|
6364
6371
|
}
|
|
6365
6372
|
} finally {
|
|
6366
6373
|
cleanup();
|
|
@@ -6420,12 +6427,13 @@ class OpenAICompatProvider {
|
|
|
6420
6427
|
};
|
|
6421
6428
|
}
|
|
6422
6429
|
async doNonStreaming(messages, tools, signal, options) {
|
|
6430
|
+
const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096;
|
|
6423
6431
|
const body = buildRequestBody({
|
|
6424
6432
|
model: this.model,
|
|
6425
6433
|
messages,
|
|
6426
6434
|
tools,
|
|
6427
6435
|
stream: false,
|
|
6428
|
-
maxTokens
|
|
6436
|
+
maxTokens,
|
|
6429
6437
|
reasoningEffort: options?.reasoningEffort,
|
|
6430
6438
|
reasoningStrategy: options?.reasoningStrategy,
|
|
6431
6439
|
...this.cacheHints()
|
|
@@ -6475,6 +6483,12 @@ class OpenAICompatProvider {
|
|
|
6475
6483
|
});
|
|
6476
6484
|
}
|
|
6477
6485
|
}
|
|
6486
|
+
if (choice.finish_reason === "length" && msg.content && !msg.tool_calls) {
|
|
6487
|
+
chunks.push({
|
|
6488
|
+
type: "warning",
|
|
6489
|
+
content: t("error.llm_output_truncated", { tokens: maxTokens })
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6478
6492
|
if (data.usage) {
|
|
6479
6493
|
chunks.push({
|
|
6480
6494
|
type: "done",
|
|
@@ -7285,6 +7299,7 @@ class ToolExecutor {
|
|
|
7285
7299
|
const reason = typeof proceed === "string" ? proceed : undefined;
|
|
7286
7300
|
return {
|
|
7287
7301
|
success: false,
|
|
7302
|
+
blocked: true,
|
|
7288
7303
|
output: reason ? t("tool.blocked_reason", { plugin: pluginName, reason }) : t("tool.blocked", { plugin: pluginName }),
|
|
7289
7304
|
toolCallId: call.id
|
|
7290
7305
|
};
|
|
@@ -10419,7 +10434,7 @@ class SessionLogger {
|
|
|
10419
10434
|
const caller = this.getCaller?.();
|
|
10420
10435
|
this.session?.appendMessage({
|
|
10421
10436
|
role: "assistant",
|
|
10422
|
-
content
|
|
10437
|
+
content,
|
|
10423
10438
|
timestamp: new Date().toISOString(),
|
|
10424
10439
|
provider: caller?.provider,
|
|
10425
10440
|
model: caller?.model
|
|
@@ -10448,7 +10463,7 @@ class SessionLogger {
|
|
|
10448
10463
|
const caller = this.getCaller?.();
|
|
10449
10464
|
this.session?.appendMessage({
|
|
10450
10465
|
role: "tool",
|
|
10451
|
-
content: sanitizeLogMessage(result.output
|
|
10466
|
+
content: sanitizeLogMessage(result.output),
|
|
10452
10467
|
name: call.name,
|
|
10453
10468
|
timestamp: new Date().toISOString(),
|
|
10454
10469
|
provider: caller?.provider,
|
|
@@ -13892,6 +13907,11 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13892
13907
|
if (signal?.aborted)
|
|
13893
13908
|
throw newAbortError();
|
|
13894
13909
|
};
|
|
13910
|
+
const verbose = config.ui?.verbose === true;
|
|
13911
|
+
const status = (message) => {
|
|
13912
|
+
if (verbose)
|
|
13913
|
+
onMeta?.(message);
|
|
13914
|
+
};
|
|
13895
13915
|
ensureNotAborted();
|
|
13896
13916
|
let orchestrator = deps.orchestratorOverride;
|
|
13897
13917
|
if (!orchestrator) {
|
|
@@ -13909,7 +13929,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13909
13929
|
logger.warn("MoE enabled but no orchestrator model configured — falling back to single-agent. Set orchestrator.model to activate MoE.");
|
|
13910
13930
|
return fallback();
|
|
13911
13931
|
}
|
|
13912
|
-
|
|
13932
|
+
status(`\uD83E\uDD16 Planning with MoE mode...
|
|
13913
13933
|
`);
|
|
13914
13934
|
onPhase?.("thinking");
|
|
13915
13935
|
const planResult = await orchestrator.plan(input, undefined, signal);
|
|
@@ -13931,7 +13951,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13931
13951
|
for (let cycle = 1;cycle <= maxCycles; cycle++) {
|
|
13932
13952
|
ensureNotAborted();
|
|
13933
13953
|
if (cycle > 1) {
|
|
13934
|
-
|
|
13954
|
+
status(t("moe.replan_started", { cycle, max: maxCycles }) + `
|
|
13935
13955
|
`);
|
|
13936
13956
|
emit("moe_replan", {
|
|
13937
13957
|
cycle,
|
|
@@ -13946,7 +13966,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13946
13966
|
if (!retry.valid) {
|
|
13947
13967
|
logger.warn(`MoE plan validation failed: ${retry.errors.join("; ")}`);
|
|
13948
13968
|
if (cycle === 1) {
|
|
13949
|
-
|
|
13969
|
+
status(`⚠️ Plan validation failed. Falling back to single-agent mode.
|
|
13950
13970
|
`);
|
|
13951
13971
|
return fallback();
|
|
13952
13972
|
}
|
|
@@ -13954,7 +13974,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13954
13974
|
}
|
|
13955
13975
|
currentPlan = applied;
|
|
13956
13976
|
if (validation.autoFixes.length > 0) {
|
|
13957
|
-
|
|
13977
|
+
status(`\uD83D\uDD27 Auto-fixed ${validation.autoFixes.length} plan issues.
|
|
13958
13978
|
`);
|
|
13959
13979
|
}
|
|
13960
13980
|
}
|
|
@@ -13971,19 +13991,19 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13971
13991
|
onScopeRequest: async (subtaskId, req) => {
|
|
13972
13992
|
const decision = await orchestrator.resolveScopeRequest(subtaskId, req, signal);
|
|
13973
13993
|
if (decision.action === "approve") {
|
|
13974
|
-
|
|
13994
|
+
status(t("moe.scope_approved", {
|
|
13975
13995
|
subtask: subtaskId,
|
|
13976
13996
|
files: [...decision.write, ...decision.read].join(", ")
|
|
13977
13997
|
}) + `
|
|
13978
13998
|
`);
|
|
13979
13999
|
} else {
|
|
13980
|
-
|
|
14000
|
+
status(t("moe.scope_rejected", { subtask: subtaskId }) + `
|
|
13981
14001
|
`);
|
|
13982
14002
|
}
|
|
13983
14003
|
return decision;
|
|
13984
14004
|
}
|
|
13985
14005
|
});
|
|
13986
|
-
|
|
14006
|
+
status(t("moe.executing", { count: String(currentPlan.subtasks.length), cycle: String(cycle) }) + `
|
|
13987
14007
|
`);
|
|
13988
14008
|
const skipIds = new Set;
|
|
13989
14009
|
const carriedResults = [];
|
|
@@ -14019,7 +14039,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
14019
14039
|
mergedResults.set(r.subtaskId, r);
|
|
14020
14040
|
}
|
|
14021
14041
|
const succeeded = [...mergedResults.values()].filter((r) => r.success).length;
|
|
14022
|
-
|
|
14042
|
+
status(`✅ ${t("moe.execution_complete", { succeeded: String(succeeded), total: String(mergedResults.size) })}
|
|
14023
14043
|
`);
|
|
14024
14044
|
const verifier = deps.verifierOverride ?? new StepVerifier(baseDir);
|
|
14025
14045
|
const knownTags = collectKnownToolTags(toolExecutor);
|
|
@@ -14691,6 +14711,17 @@ function summarizeToolArgs(args) {
|
|
|
14691
14711
|
return ` (${s})`;
|
|
14692
14712
|
return ` (${s.slice(0, TOOL_ARGS_SUMMARY_MAX_CHARS)}…(${s.length} chars total))`;
|
|
14693
14713
|
}
|
|
14714
|
+
function compactToolError(output, max = 200) {
|
|
14715
|
+
const text = String(output ?? "");
|
|
14716
|
+
for (const line of text.split(`
|
|
14717
|
+
`)) {
|
|
14718
|
+
const trimmed = line.trim();
|
|
14719
|
+
if (!trimmed)
|
|
14720
|
+
continue;
|
|
14721
|
+
return trimmed.length > max ? `${trimmed.slice(0, max - 1)}…` : trimmed;
|
|
14722
|
+
}
|
|
14723
|
+
return "";
|
|
14724
|
+
}
|
|
14694
14725
|
var init_tool_output = __esm(() => {
|
|
14695
14726
|
init_i18n();
|
|
14696
14727
|
});
|
|
@@ -14766,7 +14797,7 @@ class ToolBatchExecutor {
|
|
|
14766
14797
|
if (this.deps.isShutdownRequested())
|
|
14767
14798
|
break;
|
|
14768
14799
|
const duration = Date.now() - startTime;
|
|
14769
|
-
if (!result.success && !infoTools.has(call.name))
|
|
14800
|
+
if (!result.success && !result.blocked && !infoTools.has(call.name))
|
|
14770
14801
|
anyToolFailed = true;
|
|
14771
14802
|
if (result.success && call.name === "plan") {
|
|
14772
14803
|
const action = String(call.arguments.action ?? "");
|
|
@@ -14788,10 +14819,17 @@ class ToolBatchExecutor {
|
|
|
14788
14819
|
` + result.display + `
|
|
14789
14820
|
`);
|
|
14790
14821
|
}
|
|
14791
|
-
} else {
|
|
14822
|
+
} else if (result.success) {
|
|
14792
14823
|
const metaOut = pluginManager.runOnMeta({ iteration, logger, contextManager }, result.output);
|
|
14793
14824
|
onMeta?.(`
|
|
14794
14825
|
` + pc2.dim(metaOut) + `
|
|
14826
|
+
`);
|
|
14827
|
+
} else {
|
|
14828
|
+
const line = compactToolError(result.output);
|
|
14829
|
+
const metaOut = line ? pluginManager.runOnMeta({ iteration, logger, contextManager }, line) : "";
|
|
14830
|
+
if (metaOut)
|
|
14831
|
+
onMeta?.(`
|
|
14832
|
+
` + pc2.red(metaOut) + `
|
|
14795
14833
|
`);
|
|
14796
14834
|
}
|
|
14797
14835
|
if (result.diff) {
|
|
@@ -14808,6 +14846,7 @@ class ToolBatchExecutor {
|
|
|
14808
14846
|
name: call.name,
|
|
14809
14847
|
tool_call_id: call.id,
|
|
14810
14848
|
success: result.success,
|
|
14849
|
+
blocked: result.blocked,
|
|
14811
14850
|
arguments: call.arguments
|
|
14812
14851
|
});
|
|
14813
14852
|
answeredToolCallIds.add(call.id);
|
|
@@ -14826,7 +14865,7 @@ class ToolBatchExecutor {
|
|
|
14826
14865
|
slog?.logToolResult(call, result, duration, iteration);
|
|
14827
14866
|
}
|
|
14828
14867
|
this.deps.compactionService.compactAfterTool(state);
|
|
14829
|
-
if (!result.success && !infoTools.has(call.name)) {
|
|
14868
|
+
if (!result.success && !result.blocked && !infoTools.has(call.name)) {
|
|
14830
14869
|
const key = call.name;
|
|
14831
14870
|
const prev = state.toolFailureCounts.get(key) ?? { count: 0, error: "" };
|
|
14832
14871
|
prev.count++;
|
|
@@ -15697,6 +15736,13 @@ class Agent {
|
|
|
15697
15736
|
if (chunk.type === "done" && chunk.usage) {
|
|
15698
15737
|
tokenTracker.recordApiUsage(state, baseline, chunk.usage);
|
|
15699
15738
|
}
|
|
15739
|
+
if (chunk.type === "warning" && chunk.content) {
|
|
15740
|
+
logger.warn(`LLM warning: ${chunk.content}`);
|
|
15741
|
+
onChunk?.(`
|
|
15742
|
+
|
|
15743
|
+
> ⚠️ ${chunk.content}
|
|
15744
|
+
`);
|
|
15745
|
+
}
|
|
15700
15746
|
}
|
|
15701
15747
|
} catch (err) {
|
|
15702
15748
|
if (this.shutdownRequested || err?.name === "AbortError") {
|
|
@@ -16189,6 +16235,8 @@ function extractTriedAndFailed(messages) {
|
|
|
16189
16235
|
const failures = new Map;
|
|
16190
16236
|
for (const msg of messages) {
|
|
16191
16237
|
if (msg.role === "tool" && msg.name && msg.success === false) {
|
|
16238
|
+
if (msg.blocked)
|
|
16239
|
+
continue;
|
|
16192
16240
|
const key = `${msg.name}:${summarizeArgs(msg.arguments)}`;
|
|
16193
16241
|
const existing = failures.get(key);
|
|
16194
16242
|
const errorText = truncate(typeof msg.content === "string" ? msg.content : getMessageText(msg.content), 100);
|
|
@@ -19543,6 +19591,8 @@ class BridgeDriver {
|
|
|
19543
19591
|
headless: cfg.headless,
|
|
19544
19592
|
viewport: cfg.viewport,
|
|
19545
19593
|
timeoutMs: cfg.timeoutMs,
|
|
19594
|
+
executablePath: cfg.overrides?.executablePath,
|
|
19595
|
+
channel: cfg.overrides?.channel,
|
|
19546
19596
|
maxConsoleLineChars: this.maxConsoleLineChars
|
|
19547
19597
|
});
|
|
19548
19598
|
}
|
|
@@ -19665,6 +19715,41 @@ var init_bridge_client = __esm(() => {
|
|
|
19665
19715
|
|
|
19666
19716
|
// src/modules/browser/driver.ts
|
|
19667
19717
|
import { chromium } from "playwright";
|
|
19718
|
+
function resolveBrowserLaunchOverrides(env = process.env) {
|
|
19719
|
+
const executablePath = env.MMA_BROWSER_EXECUTABLE?.trim();
|
|
19720
|
+
const channel = env.MMA_BROWSER_CHANNEL?.trim();
|
|
19721
|
+
const out = {};
|
|
19722
|
+
if (executablePath)
|
|
19723
|
+
out.executablePath = executablePath;
|
|
19724
|
+
if (channel)
|
|
19725
|
+
out.channel = channel;
|
|
19726
|
+
return out;
|
|
19727
|
+
}
|
|
19728
|
+
function isMissingBrowserError(err) {
|
|
19729
|
+
return /Executable doesn't exist|is not found|looks like Playwright was just installed/i.test(String(err?.message ?? err));
|
|
19730
|
+
}
|
|
19731
|
+
async function launchChromiumWithFallback(launch, cfg, overrides = {}) {
|
|
19732
|
+
const base = { headless: cfg.headless, timeout: cfg.timeoutMs, ...overrides };
|
|
19733
|
+
try {
|
|
19734
|
+
return await launch(base);
|
|
19735
|
+
} catch (err) {
|
|
19736
|
+
const explicit = overrides.executablePath || overrides.channel;
|
|
19737
|
+
if (explicit || !isMissingBrowserError(err))
|
|
19738
|
+
throw err;
|
|
19739
|
+
let lastErr = err;
|
|
19740
|
+
for (const channel of SYSTEM_CHANNEL_FALLBACKS) {
|
|
19741
|
+
try {
|
|
19742
|
+
return await launch({ ...base, channel });
|
|
19743
|
+
} catch (e) {
|
|
19744
|
+
lastErr = e;
|
|
19745
|
+
}
|
|
19746
|
+
}
|
|
19747
|
+
throw lastErr;
|
|
19748
|
+
}
|
|
19749
|
+
}
|
|
19750
|
+
function launchChromium(cfg, overrides) {
|
|
19751
|
+
return launchChromiumWithFallback((opts) => chromium.launch(opts), cfg, overrides);
|
|
19752
|
+
}
|
|
19668
19753
|
|
|
19669
19754
|
class PlaywrightDriver {
|
|
19670
19755
|
browser = null;
|
|
@@ -19675,10 +19760,7 @@ class PlaywrightDriver {
|
|
|
19675
19760
|
async launch(cfg) {
|
|
19676
19761
|
if (this.browser)
|
|
19677
19762
|
return;
|
|
19678
|
-
this.browser = await
|
|
19679
|
-
headless: cfg.headless,
|
|
19680
|
-
timeout: cfg.timeoutMs
|
|
19681
|
-
});
|
|
19763
|
+
this.browser = await launchChromium(cfg, cfg.overrides ?? {});
|
|
19682
19764
|
this.context = await this.browser.newContext({ viewport: cfg.viewport });
|
|
19683
19765
|
this.page = await this.context.newPage();
|
|
19684
19766
|
this.page.setDefaultTimeout(cfg.timeoutMs);
|
|
@@ -19767,7 +19849,8 @@ function createBrowserDriver(config) {
|
|
|
19767
19849
|
const common = {
|
|
19768
19850
|
headless: config.headless,
|
|
19769
19851
|
viewport: { width: config.viewportWidth, height: config.viewportHeight },
|
|
19770
|
-
timeoutMs: config.navigationTimeout
|
|
19852
|
+
timeoutMs: config.navigationTimeout,
|
|
19853
|
+
overrides: resolveBrowserLaunchOverrides()
|
|
19771
19854
|
};
|
|
19772
19855
|
return (async () => {
|
|
19773
19856
|
if (process.versions.bun) {
|
|
@@ -19789,9 +19872,10 @@ async function launchBridge(config, common) {
|
|
|
19789
19872
|
await bridge.launch(common);
|
|
19790
19873
|
return bridge;
|
|
19791
19874
|
}
|
|
19792
|
-
var DIRECT_LAUNCH_TIMEOUT_MS = 8000;
|
|
19875
|
+
var DIRECT_LAUNCH_TIMEOUT_MS = 8000, SYSTEM_CHANNEL_FALLBACKS;
|
|
19793
19876
|
var init_driver = __esm(() => {
|
|
19794
19877
|
init_session();
|
|
19878
|
+
SYSTEM_CHANNEL_FALLBACKS = ["chrome", "msedge"];
|
|
19795
19879
|
});
|
|
19796
19880
|
|
|
19797
19881
|
// src/modules/browser/types.ts
|
|
@@ -22985,6 +23069,9 @@ var init_plan_tool = __esm(() => {
|
|
|
22985
23069
|
// src/modules/execution/module.ts
|
|
22986
23070
|
import { existsSync as existsSync39, readFileSync as readFileSync23 } from "fs";
|
|
22987
23071
|
import { resolve as resolve18 } from "path";
|
|
23072
|
+
function planPathToken(p) {
|
|
23073
|
+
return toForwardSlash(p).toLowerCase();
|
|
23074
|
+
}
|
|
22988
23075
|
|
|
22989
23076
|
class ExecutionModule {
|
|
22990
23077
|
name = "execution";
|
|
@@ -23263,38 +23350,28 @@ class ExecutionModule {
|
|
|
23263
23350
|
const step = this.tracker.getCurrentStep();
|
|
23264
23351
|
if (!step)
|
|
23265
23352
|
return null;
|
|
23266
|
-
|
|
23267
|
-
"plan",
|
|
23268
|
-
"todo",
|
|
23269
|
-
"verify",
|
|
23270
|
-
"list_dir",
|
|
23271
|
-
"read_file",
|
|
23272
|
-
"glob",
|
|
23273
|
-
"grep",
|
|
23274
|
-
"file_info",
|
|
23275
|
-
"load_skill"
|
|
23276
|
-
];
|
|
23277
|
-
if (allowedAlways.includes(call.name))
|
|
23353
|
+
if (PLAN_ALIGNMENT_EXEMPT_TOOLS.has(call.name))
|
|
23278
23354
|
return null;
|
|
23279
|
-
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map(
|
|
23355
|
+
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map(planPathToken);
|
|
23280
23356
|
if (stepPaths.length === 0)
|
|
23281
23357
|
return null;
|
|
23282
23358
|
const argStr = this.pathArgStrings(call.arguments).join(" ");
|
|
23283
|
-
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map(
|
|
23359
|
+
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map(planPathToken);
|
|
23284
23360
|
if (callPaths.length === 0)
|
|
23285
23361
|
return null;
|
|
23286
23362
|
const plan = this.tracker.getPlan();
|
|
23287
23363
|
const finishedPaths = new Set;
|
|
23288
23364
|
for (const s of plan.steps) {
|
|
23289
23365
|
if (s.status === "done" || s.status === "skipped") {
|
|
23290
|
-
extractFileLikeTokens(stripUrls(s.description)).map(
|
|
23366
|
+
extractFileLikeTokens(stripUrls(s.description)).map(planPathToken).forEach((p) => finishedPaths.add(p));
|
|
23291
23367
|
}
|
|
23292
23368
|
}
|
|
23369
|
+
const matchesAny = (p, tokens) => tokens.some((t2) => t2 === p || t2.includes(p) || p.includes(t2));
|
|
23370
|
+
const finished = [...finishedPaths];
|
|
23293
23371
|
const offPath = callPaths.some((p) => {
|
|
23294
|
-
if (
|
|
23372
|
+
if (matchesAny(p, finished))
|
|
23295
23373
|
return false;
|
|
23296
|
-
|
|
23297
|
-
return !stepPaths.some((s) => p.includes(s) || s.includes(p));
|
|
23374
|
+
return !matchesAny(p, stepPaths);
|
|
23298
23375
|
});
|
|
23299
23376
|
if (!offPath)
|
|
23300
23377
|
return null;
|
|
@@ -23466,7 +23543,7 @@ class ExecutionModule {
|
|
|
23466
23543
|
return getMessageText(firstUser.content).trim();
|
|
23467
23544
|
}
|
|
23468
23545
|
}
|
|
23469
|
-
var ERROR_SEARCH_MIN_INTERVAL_MS = 30000;
|
|
23546
|
+
var ERROR_SEARCH_MIN_INTERVAL_MS = 30000, PLAN_ALIGNMENT_EXEMPT_TOOLS;
|
|
23470
23547
|
var init_module = __esm(() => {
|
|
23471
23548
|
init_i18n();
|
|
23472
23549
|
init_tracker();
|
|
@@ -23475,10 +23552,36 @@ var init_module = __esm(() => {
|
|
|
23475
23552
|
init_auditor();
|
|
23476
23553
|
init_plan_store();
|
|
23477
23554
|
init_js_identifiers();
|
|
23555
|
+
init_path_utils();
|
|
23478
23556
|
init_web_search();
|
|
23479
23557
|
init_session_isolation();
|
|
23480
23558
|
init_plan_tool();
|
|
23481
23559
|
init_execution_plugin();
|
|
23560
|
+
PLAN_ALIGNMENT_EXEMPT_TOOLS = new Set([
|
|
23561
|
+
"plan",
|
|
23562
|
+
"todo",
|
|
23563
|
+
"verify",
|
|
23564
|
+
"enable_tools",
|
|
23565
|
+
"list_dir",
|
|
23566
|
+
"read_file",
|
|
23567
|
+
"glob",
|
|
23568
|
+
"grep",
|
|
23569
|
+
"file_info",
|
|
23570
|
+
"lsp_check",
|
|
23571
|
+
"project_map",
|
|
23572
|
+
"chunk_query",
|
|
23573
|
+
"web_search",
|
|
23574
|
+
"web_fetch",
|
|
23575
|
+
"web_browse",
|
|
23576
|
+
"recall",
|
|
23577
|
+
"search_history",
|
|
23578
|
+
"session_info",
|
|
23579
|
+
"process_list",
|
|
23580
|
+
"process_log",
|
|
23581
|
+
"question",
|
|
23582
|
+
"approve",
|
|
23583
|
+
"load_skill"
|
|
23584
|
+
]);
|
|
23482
23585
|
});
|
|
23483
23586
|
|
|
23484
23587
|
// src/modules/security/session-encryption.ts
|
|
@@ -39921,7 +40024,7 @@ function toDisplayPath(baseDir, p) {
|
|
|
39921
40024
|
}
|
|
39922
40025
|
var GUTTER = " ";
|
|
39923
40026
|
var MAX_OUTPUT_LINES = 50;
|
|
39924
|
-
var
|
|
40027
|
+
var SPINNERLESS_TOOLS = new Set(["question", "approve"]);
|
|
39925
40028
|
function toolMarker(tool) {
|
|
39926
40029
|
switch (tool) {
|
|
39927
40030
|
case "write_file":
|
|
@@ -39995,7 +40098,7 @@ class Renderer {
|
|
|
39995
40098
|
`), this.width, (text) => this.out.write(text));
|
|
39996
40099
|
}
|
|
39997
40100
|
showLoader() {
|
|
39998
|
-
this.spinner.start("thinking");
|
|
40101
|
+
this.spinner.start(t("ui.thinking"));
|
|
39999
40102
|
}
|
|
40000
40103
|
text(chunk) {
|
|
40001
40104
|
this.endCard();
|
|
@@ -40049,6 +40152,7 @@ ${pc2.dim("→")} Thought: `);
|
|
|
40049
40152
|
this.thoughtStarted = true;
|
|
40050
40153
|
this.thoughtStartMs = Date.now();
|
|
40051
40154
|
this.thoughtHeaderPrinted = false;
|
|
40155
|
+
this.spinner.start(t("ui.thinking"));
|
|
40052
40156
|
}
|
|
40053
40157
|
thinkingEnd() {
|
|
40054
40158
|
this.spinner.stop();
|
|
@@ -40068,14 +40172,18 @@ ${pc2.dim("→")} Thought: `);
|
|
|
40068
40172
|
const displayArgs = PATH_TOOLS.has(tool) && typeof args.path === "string" ? { ...args, path: toDisplayPath(this.baseDir, args.path) } : args;
|
|
40069
40173
|
const summary = summarizeArgs2(displayArgs);
|
|
40070
40174
|
const marker = icon || toolMarker(tool);
|
|
40175
|
+
const label = friendlyTool(tool);
|
|
40071
40176
|
this.card = { tool, args, start: Date.now() };
|
|
40072
40177
|
if (stepContext) {
|
|
40073
40178
|
this.out.write(`
|
|
40074
40179
|
${pc2.dim("↓")} ${pc2.cyan(stepContext)}
|
|
40075
40180
|
`);
|
|
40076
40181
|
}
|
|
40077
|
-
this.out.write(`${pc2.dim(marker)} ${
|
|
40182
|
+
this.out.write(`${pc2.dim(marker)} ${label}${summary ? ` ${pc2.dim(summary)}` : ""}
|
|
40078
40183
|
`);
|
|
40184
|
+
if (!SPINNERLESS_TOOLS.has(tool)) {
|
|
40185
|
+
this.spinner.start(`${label}${summary ? ` ${summary}` : ""}`);
|
|
40186
|
+
}
|
|
40079
40187
|
}
|
|
40080
40188
|
planBlock(lines) {
|
|
40081
40189
|
this.endCard();
|
|
@@ -41,13 +41,46 @@ function readInt(v, fallback) {
|
|
|
41
41
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// System browsers to try when the bundled Chromium revision is absent (CDN
|
|
45
|
+
// blocked / offline). Mirrors driver.ts so both transports behave the same.
|
|
46
|
+
const SYSTEM_CHANNEL_FALLBACKS = ["chrome", "msedge"];
|
|
47
|
+
|
|
48
|
+
function isMissingBrowserError(err) {
|
|
49
|
+
return /Executable doesn't exist|is not found|looks like Playwright was just installed/i.test(
|
|
50
|
+
String(err && err.message ? err.message : err),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function launchChromium(params, extra) {
|
|
55
|
+
const base = {
|
|
56
|
+
headless: params?.headless !== false,
|
|
57
|
+
timeout: readInt(params?.timeoutMs, 30000),
|
|
58
|
+
...extra,
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
return await chromium.launch(base);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// An explicit executable/channel is the user's choice — fail loudly.
|
|
64
|
+
if (base.executablePath || base.channel || !isMissingBrowserError(err)) throw err;
|
|
65
|
+
let lastErr = err;
|
|
66
|
+
for (const channel of SYSTEM_CHANNEL_FALLBACKS) {
|
|
67
|
+
try {
|
|
68
|
+
return await chromium.launch({ ...base, channel });
|
|
69
|
+
} catch (e) {
|
|
70
|
+
lastErr = e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw lastErr;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
async function handleLaunch(params) {
|
|
45
78
|
if (browser) return { ok: true };
|
|
46
79
|
maxLineChars = readInt(params?.maxConsoleLineChars, 400);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
const extra = {};
|
|
81
|
+
if (params?.executablePath) extra.executablePath = String(params.executablePath);
|
|
82
|
+
if (params?.channel) extra.channel = String(params.channel);
|
|
83
|
+
browser = await launchChromium(params, extra);
|
|
51
84
|
context = await browser.newContext({
|
|
52
85
|
viewport: params?.viewport || { width: 1280, height: 720 },
|
|
53
86
|
});
|