micro-models-agent 2.37.0 → 2.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/dist/assets/js/feed/turn.js +4 -4
- package/dist/i18n/en.json +1 -1
- package/dist/i18n/ru.json +1 -1
- package/dist/main.js +346 -346
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ 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
|
+
## [2.38.0] - 2026-10-09
|
|
8
|
+
|
|
9
|
+
Harness honesty for read-only work: the loop guard no longer treats "no mutation" as
|
|
10
|
+
"no progress", every forced stop now yields a best-effort final answer instead of a silent
|
|
11
|
+
kill, and the web chat shows why a turn stopped. Frontend seams landed so a future TUI can
|
|
12
|
+
reuse the command registry and session snapshot, and `grep` on Windows was hardened.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Host snapshot projection moved to `core`** (`src/core/host-state.ts`): `buildHostState`/`captureUsage`/`liveContextUsage`/`planEventFor`/`UsageSnapshot` lived in `src/modules/webui/host-state.ts`, yet `mma serve` already imported `planEventFor` from there - a structural subset of the web backend acting as a shared contract. `src/modules/webui/host-state.ts` is now a thin web wrapper keeping `WebIndexer`/`WebLsp` and the web-only `i18nStale` probe.
|
|
17
|
+
- **`ReplContext` split into services and IO** (`src/cli/command-context.ts`): the old 20-member interface mixed frontend-agnostic services (config/agent/modules) with single-shot terminal IO (`rl: LineInput`, `output: OutputChannel`), so a TUI that redraws frames could not reuse the command registry. Now `CommandServices` (frontend-agnostic) + `CommandOutput` (2 required methods) + `CommandInput` (3 methods). `ReplContext` is an alias. `/wizard` and `/config reset --all` ask `ctx.input.withInput(...)`/`ctx.input.confirm(...)` instead of touching the terminal; `Spinner` takes only `writeOverlay`.
|
|
18
|
+
- **Declarative slash-command catalog** (`src/core/commands/`): three descriptions drifted silently - the hand-synced `COMMAND_GROUPS` table (29 lines), the REPL registrations, and the web `HELP` list. All 29 commands are now data (`spec.ts`) plus a registry with per-surface availability (`catalog.ts`). Actions are deliberately not unified - terminal prints ANSI, the browser emits plain SSE lines, which is the `CommandOutput` adapter's job. `/help` builds from the catalog; `COMMAND_GROUPS` is deleted. `isAvailable` defaults to terminal + TUI so a future TUI gets the whole registry.
|
|
19
|
+
- **Forced final answer instead of a silent kill** (`src/core/agent.ts`, `tool-batch.ts`): every harness terminal (duplicate budget, failed-batch honest-stop, `maxToolIterations` exhaustion) now makes one tool-less LLM call and accepts the text as the final answer (critic/audit/claim gates skipped, answer persisted to history, tokens/cost counted); `AgentResult.stopReason` carries the trigger. An empty forced answer is still a failure.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Anti-drift guards**: `tests/core/command-catalog.test.ts` asserts the catalog covers exactly the commands the REPL registers (both directions) and that web-marked commands match the real web dispatcher; `tests/cli/repl-help.test.ts` pins the user-visible `/help` output; `tests/cli/command-context.test.ts` locks the surface a new frontend must implement.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Read-only tasks are no longer hard-stopped** (`src/core/agent/tool-batch.ts`): the guard treated "no work mutation" as "no progress", so a genuinely read-only turn (analysis) always climbed the streak and was killed at 12 batches before the model could answer (`ses_mv0pirf5_hnmbxj`). Progress is now exact tool-call repetition (new `canonicalArgsKey`, `src/core/agent/canonical-args.ts`), so broad exploration with new files/patterns never trips it.
|
|
28
|
+
- **Web shows why a turn stopped** (`webui/session/{run-agent,forward}.ts`, `assets/js/feed/turn.ts`): the harness reason (`AgentResult.stopReason` / `error`) was dropped on the web path, leaving a generic `turn failed`. It is now forwarded through SSE `turn_end` and rendered.
|
|
29
|
+
- **`grep` with `|` on Windows** (`src/tools/grep-tool.ts`): an npm `.cmd`/`.ps1` `rg` shim runs through `cmd.exe` under Bun, so alternation patterns were read as pipes and the search failed with no JS fallback. Shim/spawn failures (exit 255/9009, shell signature) are now treated as engine-unavailable and fall through to the JS walker.
|
|
30
|
+
- **Stuck-detector read-only nudge removed** (`src/modules/execution/stuck-detector.ts`, `execution-plugin.ts`): the second "no mutation = stuck" heuristic (10 read-only calls → `exec.read_only_loop`, "No write/exec for N tool calls") fired on legitimate read-only analysis. Removed — real loops stay covered by repetition sampling, step iteration, failure streaks and the duplicate-tool budget. Keys `exec.read_only_loop`, `exec.read_only_loop_recovery`, `exec.hint_read_only` dropped (en+ru).
|
|
31
|
+
|
|
7
32
|
## [2.37.0] - 2026-10-09
|
|
8
33
|
|
|
9
34
|
`mma serve` now describes its own API: a live OpenAPI 3.1 document plus a
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Most agents (Devin, Cursor, Copilot) are glued to cloud models or pricey APIs
|
|
|
35
35
|
|
|
36
36
|
- **6-state agent loop** (`INIT → THINK → ACT → OBSERVE → OUTPUT → ERROR`) — clean, predictable, debuggable
|
|
37
37
|
- **Context budget management** — per-model budgets with sliding-window compaction that preserves facts, decisions, and errors, keeps a `[Tool ledger]` of what was already read, and calibrates its token estimate against the provider's real `prompt_tokens`
|
|
38
|
-
- **Honest-stop guards** — failed tool batches
|
|
38
|
+
- **Honest-stop guards** — failed tool batches and duplicate-tool loops stop the loop with a truthful status instead of letting a small model grind (honest stop, duplicate-tool budget, forced tool-less final answer)
|
|
39
39
|
- **Hallucination detection** — fact checks (file paths), consistency checks (decision rollbacks), confidence checks, plus claim verification: "created file X" / "output says Y" / "the price is Z" are checked against real tool output
|
|
40
40
|
- **Execution guarantees** — LLM-driven auto-planning (no keyword heuristics), stuck detection, off-track warnings, automatic plan advancement
|
|
41
41
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{msgs as
|
|
2
|
-
`).length>12){o.classList.add("collapsed");const
|
|
1
|
+
import{msgs as _,el as i,icon as w,announce as a}from"../dom.js";import{t as d,pluralCount as A}from"../i18n.js";import{streamStateFor as C,tabs as k,session as g}from"../store.js";import{FILE_TOOLS as x,OPENABLE_TOOLS as F,LINK_TOOLS as B,categoryOf as y,labelOf as I,argText as U,formatMs as S,verbosity as b,firstOutputLine as L}from"../format.js";import{copyText as q}from"../../md.js";import{imageCard as P}from"./image-card.js";import{activeFeed as j,liveFeed as D,feedForEvent as h,clearBusy as O,clearEmpty as T,appendTo as f,insertAfter as N,appendActionsRow as $,flushRun as K,scrollToBottom as z}from"./core.js";import{finishMd as E,finishThink as R}from"./stream.js";export function onToolStart(e){const n=h(e.sessionId);if(O(n),!n)return;T(n),E(n),R(n);const o=C(n);o.pendingArgs.set(e.toolCallId,e.args||{});const r=i("div","act "+y(e.name));r.appendChild(i("span","g","◇")),r.appendChild(i("span","lab",I(e.name)));const l=U(e.args);l&&(r.appendChild(i("span","sep","·")),r.appendChild(i("span","args",l)));const t=i("span","ok","");r.appendChild(t),r.classList.add("running"),f(n,r),o.toolNodes.set(e.toolCallId,{status:t,line:r}),y(e.name)!=="recon"&&(o.runReconOnly=!1),o.runCount++}function M(e,n){if(typeof n!="string"||!n)return;const o=i("div","toolout"),r=i("div","code");if(r.textContent=n,o.appendChild(r),n.length>600||n.split(`
|
|
2
|
+
`).length>12){o.classList.add("collapsed");const p="▸ "+A("web.tool.output",n.length),s=i("span","more",p);s.addEventListener("click",()=>{const c=o.classList.toggle("collapsed");s.textContent=c?p:"▾ "+d("web.tool.collapse")}),o.appendChild(s)}const t=e&&e.parentNode?null:D();e&&e.parentNode?e.parentNode.insertBefore(o,e.nextSibling):f(t,o)}function G(e){if(typeof e!="string"||!e)return null;try{const n=new URL(e,location.href);if(n.protocol==="http:"||n.protocol==="https:"||n.protocol==="file:")return n.href}catch{return null}return null}function H(e){const n=String(e??"").replace(/\\/g,"/");try{const o=g&&g.host&&g.host.workspace?String(g.host.workspace).replace(/\\/g,"/").replace(/\/+$/,""):"";if(o&&(n===o||n.indexOf(o+"/")===0)){const r=n===o?"":n.slice(o.length+1);if(r)return r}}catch{}return n}function m(e,n,o,r){const l=h(o);if(!l)return null;if(x.indexOf(e)!==-1){const t=n.path||n.file||n.dir;if(!t)return null;const p=H(t),s=i("div","filerow "+y(e));if(s.appendChild(i("span","g","◇")),s.appendChild(i("span","lab",I(e))),s.appendChild(i("span","sep"," · ")),F.indexOf(e)!==-1){const u=i("a","path",p||String(t));u.href="/api/file?path="+encodeURIComponent(String(t)),u.target="_blank",u.rel="noopener noreferrer",u.title=d("web.card.open_title")+" · "+String(t),s.appendChild(u)}else{const u=i("span","tail",p||String(t));u.title=String(t),s.appendChild(u)}const c=i("button","copy-btn");return c.type="button",c.title=d("web.card.copy_title"),c.setAttribute("aria-label",d("web.card.copy_title")),c.appendChild(w("copy")),c.addEventListener("click",()=>{q(String(t)).then(u=>{if(c.replaceChildren(w(u?"check":"copy")),!u){const v=s.querySelector(".st");v&&(v.textContent=" ⚠")}window.setTimeout(()=>c.replaceChildren(w("copy")),1400)})}),s.appendChild(c),s.appendChild(i("span","st",r===!1?" ✗":r===!0?" ✓":"")),r===!1&&(s.dataset.fail="1"),/\.drawio(\.xml)?$/i.test(String(t))&&b()==="verbose"&&s.appendChild(i("span","detail","↳ "+d("web.card.drawio_hint"))),f(l,s),s}if(B.indexOf(e)!==-1&&n.url){const t=i("div","linkline");t.appendChild(document.createTextNode("↳ "));const p=G(n.url);if(p){const s=i("a","",n.url);s.href=p,s.target="_blank",s.rel="noopener noreferrer",t.appendChild(s);try{const c=new URL(p).hostname;c&&t.appendChild(i("span","dom"," · "+c))}catch{}}else t.appendChild(i("span","",n.url));return f(l,t),t}return null}function J(e){if(!e.success||!Array.isArray(e.images)||!e.images.length)return;const n=h(e.sessionId);if(n)for(const o of e.images){const r=P(String(e.name||""),String(o));r&&f(n,r)}}export function onToolEnd(e){Q(e),J(e)}function Q(e){const n=h(e.sessionId),o=n?C(n):null,r=o?o.toolNodes.get(e.toolCallId):void 0,l=r?r.line:null;if(r&&o){r.line.classList.remove("running");const s=S(e.durationMs);r.status.textContent=e.success?" ✓"+(s?" "+s:""):" ✗"+(s?" "+s:""),e.success||(r.status.classList.add("fail"),o.runFails++,o.runReconOnly=!1),o.toolNodes.delete(e.toolCallId)}const t=(o?o.pendingArgs.get(e.toolCallId):void 0)||{};o&&o.pendingArgs.delete(e.toolCallId);const p=t.path||t.file||t.dir;if(x.indexOf(e.name)!==-1&&p){if(l&&l.parentNode&&l.remove(),b()==="quiet"){if(e.success){const c=m(e.name,t,e.sessionId,e.success);c&&o&&o.runNodes.push(c),o&&(o.runDuration+=typeof e.durationMs=="number"?e.durationMs:0)}else{const c=m(e.name,t,e.sessionId,e.success),u=L(e.output);u&&N(c||l,i("div","detail","↳ "+u)),c&&o&&o.runNodes.push(c)}return}const s=m(e.name,t,e.sessionId,e.success);M(s||l,e.output);return}if(b()==="quiet"){if(e.success){l&&o&&o.runNodes.push(l);const s=m(e.name,t,e.sessionId,e.success);s&&o&&o.runNodes.push(s),o&&(o.runDuration+=typeof e.durationMs=="number"?e.durationMs:0)}else{const s=L(e.output);s&&N(l,i("div","detail","↳ "+s))}return}e.success&&m(e.name,t,e.sessionId,e.success),M(l,e.output)}export function onTurnEnd(e){g.busy=!1;const n=h(e.sessionId);O(n),R(n),E(n),K(n);const o=n?C(n):null;o&&o.toolNodes.forEach(t=>{t&&t.line&&n&&t.line.parentNode===n&&t.line.classList.remove("running")});const r=k.tabs.find(t=>t.id===e.sessionId);if(r&&(r.planCard=null),!n||!o){resetTurnText(n);return}if(e.success===!1){const t=e.error||(e.interrupted?d("web.turn.stopped"):d("web.turn.failed")),p=e.interrupted?"warnline":"errline",s=e.interrupted?"⚠ ":"✗ ";f(n,i("div",p,s+t)),a(t)}else{if(e.stopReason){const p=d("web.turn.stop_"+e.stopReason);f(n,i("div","warnline","⚠ "+p)),a(p)}const t=[];typeof e.toolCalls=="number"&&e.toolCalls>0&&t.push(d("web.turn.tools",{count:e.toolCalls})),typeof e.durationMs=="number"&&t.push(S(e.durationMs)),t.length&&f(n,i("div","metric","⟡ "+t.join(" · "))),a(d("web.turn.end",{count:e.toolCalls||0}))}const l=o.turnRoots.map(t=>t.innerText||t.textContent||"").join(`
|
|
3
3
|
|
|
4
|
-
`);$(n,l,o.turnMd),resetTurnText(n)}export function resetTurnText(e){if(!e)return;const n=C(e);n.turnMd="",n.turnRoots=[]}export function onErrorEvent(e){if(!e||!e.error)return;const n=h(e.sessionId);n&&
|
|
5
|
-
`).forEach(
|
|
4
|
+
`);$(n,l,o.turnMd),resetTurnText(n)}export function resetTurnText(e){if(!e)return;const n=C(e);n.turnMd="",n.turnRoots=[]}export function onErrorEvent(e){if(!e||!e.error)return;const n=h(e.sessionId);n&&f(n,i("div","errline","✗ "+e.error)),a(String(e.error))}export function onCommandOutput(e,n){const o=h(n)||_;T(o),String(e).split(`
|
|
5
|
+
`).forEach(r=>f(o,i("div","cmdout",r))),z()}export function onLog(e){if(!e||typeof e.text!="string"||!e.text||e.diagnostic&&b()!=="verbose")return;const n=h(e.sessionId)||j();if(!n)return;const o=e.level,r=o==="error"?"errline":o==="warn"?"warnline":"detail",l=o==="error"?"✗ ":o==="warn"?"⚠ ":"↳ ",t=i("div",r);t.textContent=l+e.text,f(n,t),(o==="error"||o==="warn")&&a(e.text)}
|