micro-models-agent 2.14.0 → 2.15.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 +17 -0
- package/dist/assets/app.js +3 -1
- package/dist/assets/index.html +1 -0
- package/dist/assets/js/dom.js +1 -0
- package/dist/assets/js/feed.js +79 -6
- package/dist/assets/js/settings.js +21 -1
- package/dist/assets/styles.css +34 -0
- package/dist/i18n/en.json +10 -0
- package/dist/i18n/ru.json +10 -0
- package/dist/main.js +260 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.15.0] - 2026-10-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Web UI: stale-translations badge** (`src/modules/webui/i18n-stale.ts`, `i18n-api.ts`, `host-state.ts`, `core/host.ts`, `assets/index.html`, `js/dom.js`, `js/settings.js`, `app.js`, `styles.css`): `GET /api/i18n` and the state snapshot carry `i18nStale` (dict files on disk newer than the server process); the topbar shows a yellow badge with a reload action instead of silently rendering raw keys. New keys `web.i18n.stale`/`web.i18n.reload` (en+ru). Tests: `tests/webui-i18n.test.ts`, `tests/webui-usage.test.ts`, `tests/webui-assets.test.ts`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Plan auto-advance no longer completes modify-steps on stale files** (`src/modules/execution/module.ts`): `advancePlanIfStepComplete` and final-audit `autoCloseEvidenceSteps` now require `provablyStale()` to be false — every named file must be created/modified after `plan.createdAt` (fail-open on bad dates and unreadable mtimes, delete-kind unchanged). Previously a step naming a long-existing file auto-completed before a line of code was written (observed: 6 plan steps auto-closed while the agent was still on step 1). Tests: `tests/execution-plan-deliverables.test.ts`.
|
|
18
|
+
- **Critic evidence includes git-tracked changes** (`src/modules/critic/evidence.ts`): `buildEvidencePack` unions tool-arg file lists with `git status --porcelain` (staged + unstaged + untracked, capped, fail-open outside repos), so edits made outside file tools (helper scripts via bash, heredocs) no longer produce false `concern` verdicts about missing changes. Tests: `tests/critic-evidence.test.ts`.
|
|
19
|
+
- **Repeated-failure memory writes are diagnosed, model misses skipped** (`src/core/agent/tool-batch.ts`, `loop-state.ts`): `diagnoseRepeatedFailures()` records distinct error headlines with signature-based cause/solution (edit_file anchor miss → CRLF/stale-read hypothesis) and skips global-memory writes for model errors (invented test paths, exit-code-only grep misses). Previously boilerplate causes and the model's own wrong guesses landed in `errors.md`. Tests: `tests/tool-failure-diagnosis.test.ts`.
|
|
20
|
+
- **Factual check resolves path suffixes** (`src/modules/hallucination/factual.ts`): `webui/state.ts` now matches `src/modules/webui/state.ts` via the cached basename index (no new FS walks), fixing false "mentioned files not found" warnings on short paths. Tests: `tests/hallucination-factual.test.ts`.
|
|
21
|
+
- **`edit_file` tolerates LF anchors in CRLF files** (`src/tools/edit-file.ts`): `spliceNormalized()` matches on normalized line endings and splices the original, keeping the file's ending style (no more "string not found" loops on Windows checkouts). Tests: `tests/edit-file.test.ts`.
|
|
22
|
+
- **Web usage re-render covers the context breakdown** (`src/modules/webui/assets/js/feed.js`): `h.contextBreakdown` added to the `renderUsage` signature so part shifts at constant used/limit repaint the segmented bar and legend. Guard: `tests/webui-assets.test.ts`.
|
|
23
|
+
- **Terminal context-budget part words are localized** (`src/cli/repl.ts`, `src/i18n/en.json`, `ru.json`): the startup banner showed untranslated latin `sys/res/hist` in both locales; now `repl.ctx_part_system/reserve/history` (`system/reserve/history` + `система/резерв/история`). Web legend (usage parts) intentionally unchanged. Tests: `tests/i18n.test.ts`.
|
|
24
|
+
- **Verification hygiene is model-facing** (`src/core/system-prompt.ts`, `src/tools/browser.ts`): verification `mma web` servers must run isolated (`MMA_CONFIG_DIR`), never against the user's live server; without vision capabilities the browser description steers to snapshot text and DOM/API assertions. Tests: `tests/core/bootstrap.test.ts`, `tests/tools/browser.test.ts`.
|
|
25
|
+
|
|
9
26
|
## [2.14.0] - 2026-10-01
|
|
10
27
|
|
|
11
28
|
### Changed
|
package/dist/assets/app.js
CHANGED
|
@@ -15,7 +15,7 @@ loadCommands,
|
|
|
15
15
|
} from "./js/tabs.js";
|
|
16
16
|
import {
|
|
17
17
|
renderSelectors, renderSettings, loadConfig, loadModels, loadProviders,
|
|
18
|
-
loadMemory, loadSkills, loadPlugins, applyTheme, currentTheme, applyFontSize, setConnStatus,
|
|
18
|
+
loadMemory, loadSkills, loadPlugins, applyTheme, currentTheme, applyFontSize, setConnStatus, setStaleBar,
|
|
19
19
|
} from "./js/settings.js";
|
|
20
20
|
import {
|
|
21
21
|
initRail, setPlan as railPlan, railEvent, applyRailI18n,
|
|
@@ -88,6 +88,7 @@ function renderState(snapshot, busy) {
|
|
|
88
88
|
sidebarSessionChanged(sessionId);
|
|
89
89
|
renderWorkspace(S.state.host);
|
|
90
90
|
renderSessionsDropdown();
|
|
91
|
+
setStaleBar(!!(snapshot && snapshot.i18nStale));
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
async function refresh() {
|
|
@@ -259,6 +260,7 @@ function applyStaticI18n() {
|
|
|
259
260
|
applySidebarI18n();
|
|
260
261
|
if (msgs.querySelector(".empty")) showEmptyFeed();
|
|
261
262
|
if (connStatus && !connStatus.hidden) setConnStatus(true);
|
|
263
|
+
setStaleBar(undefined, true);
|
|
262
264
|
}
|
|
263
265
|
|
|
264
266
|
async function boot() {
|
package/dist/assets/index.html
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
<button class="icon-btn" id="toggleSidebar" title="sidebar" aria-label="sidebar" aria-expanded="false">▤</button>
|
|
30
30
|
<button class="icon-btn" id="openSettings" title="settings" aria-label="settings" aria-haspopup="dialog" aria-expanded="false">⚙</button>
|
|
31
31
|
<span class="conn" id="connStatus" role="status" aria-live="polite" hidden></span>
|
|
32
|
+
<span class="conn stale" id="staleBar" role="status" hidden></span>
|
|
32
33
|
</div>
|
|
33
34
|
|
|
34
35
|
<div class="bar-sub" id="sessBar">
|
package/dist/assets/js/dom.js
CHANGED
|
@@ -14,6 +14,7 @@ export const attachStrip = document.getElementById("attachStrip");
|
|
|
14
14
|
export const attachErr = document.getElementById("attachErr");
|
|
15
15
|
export const imgPicker = document.getElementById("imgPicker");
|
|
16
16
|
export const connStatus = document.getElementById("connStatus");
|
|
17
|
+
export const staleBar = document.getElementById("staleBar");
|
|
17
18
|
export const liveEl = document.getElementById("live");
|
|
18
19
|
export const sendBtn = document.getElementById("sendBtn");
|
|
19
20
|
|
package/dist/assets/js/feed.js
CHANGED
|
@@ -578,6 +578,30 @@ export function onToolEnd(data) {
|
|
|
578
578
|
S.toolNodes.delete(data.toolCallId);
|
|
579
579
|
const args = S.pendingArgs.get(data.toolCallId) || {};
|
|
580
580
|
S.pendingArgs.delete(data.toolCallId);
|
|
581
|
+
// File tools render as a single filerow (viewer link + copy): the `.act`
|
|
582
|
+
// line from `onToolStart` is replaced, otherwise the feed shows the same
|
|
583
|
+
// action twice (act + filerow). Without a path `cardFor` renders nothing,
|
|
584
|
+
// so keep the generic act line in that case.
|
|
585
|
+
const filePath = args.path || args.file || args.dir;
|
|
586
|
+
if (FILE_TOOLS.indexOf(data.name) !== -1 && filePath) {
|
|
587
|
+
if (line && line.parentNode) line.remove();
|
|
588
|
+
if (verbosity() === "quiet") {
|
|
589
|
+
if (data.success) {
|
|
590
|
+
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
591
|
+
if (card) S.runNodes.push(card);
|
|
592
|
+
S.runDuration += typeof data.durationMs === "number" ? data.durationMs : 0;
|
|
593
|
+
} else {
|
|
594
|
+
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
595
|
+
const cause = firstOutputLine(data.output);
|
|
596
|
+
if (cause) insertAfter(card || line, el("div", "detail", "↳ " + cause));
|
|
597
|
+
if (card) S.runNodes.push(card);
|
|
598
|
+
}
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const card = cardFor(data.name, args, data.sessionId, data.success);
|
|
602
|
+
renderToolOutput(card || line, data.output);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
581
605
|
if (verbosity() === "quiet") {
|
|
582
606
|
// Quiet hides tool bodies. Track successful nodes so a run of read-only
|
|
583
607
|
// actions can fold at the next segment; a failure keeps its line and adds
|
|
@@ -1015,6 +1039,58 @@ function usageRow(label, value, cls) {
|
|
|
1015
1039
|
return row;
|
|
1016
1040
|
}
|
|
1017
1041
|
|
|
1042
|
+
// Segmented context bar: system · tools · history · reserve · free, widths
|
|
1043
|
+
// relative to the full context window so the parts always sum to one bar.
|
|
1044
|
+
function contextSegBar(h, used, limit) {
|
|
1045
|
+
const bar = el("div", "bar segbar");
|
|
1046
|
+
const bd = h.contextBreakdown;
|
|
1047
|
+
if (!bd || typeof bd.window !== "number" || bd.window <= 0) {
|
|
1048
|
+
// Legacy fallback (pre-breakdown payloads): single used/limit fill.
|
|
1049
|
+
const fill = el("i");
|
|
1050
|
+
fill.style.width = Math.min(100, Math.round((used / limit) * 100)) + "%";
|
|
1051
|
+
bar.appendChild(fill);
|
|
1052
|
+
return bar;
|
|
1053
|
+
}
|
|
1054
|
+
const denom = Math.max(bd.window, bd.used + bd.reserve);
|
|
1055
|
+
const parts = [
|
|
1056
|
+
["seg-system", bd.system],
|
|
1057
|
+
["seg-tools", bd.tools],
|
|
1058
|
+
["seg-history", bd.history],
|
|
1059
|
+
["seg-reserve", bd.reserve],
|
|
1060
|
+
["seg-free", bd.free],
|
|
1061
|
+
];
|
|
1062
|
+
for (const [cls, n] of parts) {
|
|
1063
|
+
if (!n || n <= 0) continue;
|
|
1064
|
+
const seg = el("i", cls);
|
|
1065
|
+
seg.style.width = (n / denom) * 100 + "%";
|
|
1066
|
+
bar.appendChild(seg);
|
|
1067
|
+
}
|
|
1068
|
+
return bar;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Dim legend under the segmented bar: one dot+label+value per context part.
|
|
1072
|
+
function contextLegend(h) {
|
|
1073
|
+
const box = el("div", "seglegend");
|
|
1074
|
+
const bd = h.contextBreakdown;
|
|
1075
|
+
if (!bd) return box;
|
|
1076
|
+
const items = [
|
|
1077
|
+
["seg-system", "web.usage.part_system", bd.system],
|
|
1078
|
+
["seg-tools", "web.usage.part_tools", bd.tools],
|
|
1079
|
+
["seg-history", "web.usage.part_history", bd.history],
|
|
1080
|
+
["seg-reserve", "web.usage.part_reserve", bd.reserve],
|
|
1081
|
+
["seg-free", "web.usage.part_free", bd.free],
|
|
1082
|
+
];
|
|
1083
|
+
for (const [cls, labelKey, n] of items) {
|
|
1084
|
+
if (!n || n <= 0) continue;
|
|
1085
|
+
const item = el("span", "segl-item");
|
|
1086
|
+
item.appendChild(el("i", "segl-dot " + cls));
|
|
1087
|
+
item.appendChild(el("span", "segl-label", t(labelKey)));
|
|
1088
|
+
item.appendChild(el("b", "", fmtTokens(n)));
|
|
1089
|
+
box.appendChild(item);
|
|
1090
|
+
}
|
|
1091
|
+
return box;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1018
1094
|
// Session row with a copy button next to the id (icon-only, ✓ on success).
|
|
1019
1095
|
function usageSessionRow(sessionId) {
|
|
1020
1096
|
const row = el("div", "kv");
|
|
@@ -1045,7 +1121,7 @@ export function renderUsage(h) {
|
|
|
1045
1121
|
const cfg = S.state.config || {};
|
|
1046
1122
|
const sig = JSON.stringify([
|
|
1047
1123
|
h.sessionId, h.model, h.contextUsed, h.contextLimit, h.cacheHitRate, h.costUsd,
|
|
1048
|
-
h.compactionCount, h.contextQuality, h.contextProbe, cfg.model, cfg.contextWindow,
|
|
1124
|
+
h.compactionCount, h.contextQuality, h.contextProbe, h.contextBreakdown, cfg.model, cfg.contextWindow,
|
|
1049
1125
|
I18N.locale,
|
|
1050
1126
|
]);
|
|
1051
1127
|
if (sig === S.lastUsageSig) return;
|
|
@@ -1058,11 +1134,8 @@ export function renderUsage(h) {
|
|
|
1058
1134
|
const limit = typeof h.contextLimit === "number" ? h.contextLimit : cfg.contextWindow || null;
|
|
1059
1135
|
usageEl.appendChild(usageRow(t("web.usage.context"), used != null && limit ? fmtTokens(used) + " / " + fmtTokens(limit) : "—"));
|
|
1060
1136
|
if (used != null && limit) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
fill.style.width = Math.min(100, Math.round((used / limit) * 100)) + "%";
|
|
1064
|
-
bar.appendChild(fill);
|
|
1065
|
-
usageEl.appendChild(bar);
|
|
1137
|
+
usageEl.appendChild(contextSegBar(h, used, limit));
|
|
1138
|
+
usageEl.appendChild(contextLegend(h));
|
|
1066
1139
|
}
|
|
1067
1140
|
const hit = typeof h.cacheHitRate === "number" ? h.cacheHitRate : null;
|
|
1068
1141
|
usageEl.appendChild(usageRow(t("web.usage.cache"), hit != null ? Math.round(hit * 100) + "%" : "—", hit != null ? "" : "dim"));
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// share from tabs; confirm/toast from dialog. Never imports app (the locale
|
|
4
4
|
// refresh reaches the app shell through the store hooks registry).
|
|
5
5
|
import {
|
|
6
|
-
el, icon, link, inlineTextNodes, connStatus, makeToggle,
|
|
6
|
+
el, icon, link, inlineTextNodes, connStatus, staleBar, makeToggle,
|
|
7
7
|
} from "./dom.js";
|
|
8
8
|
import { api } from "./api.js";
|
|
9
9
|
import { t, loadI18n } from "./i18n.js";
|
|
@@ -324,6 +324,26 @@ export function setConnStatus(disconnected) {
|
|
|
324
324
|
connStatus.textContent = disconnected ? "⋯ " + t("web.conn.reconnecting") : "";
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// Stale-translations banner: dict files on disk are newer than the running
|
|
328
|
+
// server process, so the chrome may show raw keys. Rebuilt only on S.state
|
|
329
|
+
// change (not on every 2s poll) to keep the reload-button focus stable;
|
|
330
|
+
// `force` re-renders the label after a locale switch.
|
|
331
|
+
let lastStale = null;
|
|
332
|
+
export function setStaleBar(stale, force) {
|
|
333
|
+
if (!staleBar) return;
|
|
334
|
+
// undefined = перерисовать последнее состояние (смена локали).
|
|
335
|
+
const on = stale === undefined ? !!lastStale : !!stale;
|
|
336
|
+
if (!force && on === lastStale) return;
|
|
337
|
+
lastStale = on;
|
|
338
|
+
staleBar.hidden = !on;
|
|
339
|
+
staleBar.textContent = "";
|
|
340
|
+
if (!on) return;
|
|
341
|
+
staleBar.appendChild(el("span", "", "⚠ " + t("web.i18n.stale") + " "));
|
|
342
|
+
staleBar.appendChild(link(t("web.i18n.reload"), "", () => location.reload()));
|
|
343
|
+
}
|
|
344
|
+
return lastStale;
|
|
345
|
+
}
|
|
346
|
+
|
|
327
347
|
// Panel → named sections lives in `settings-controls.js` (PANEL_SECTIONS).
|
|
328
348
|
// Custom panels (model/ui/browser…) render a bespoke body first and then their
|
|
329
349
|
// sections; purely generic panels (general/context/…) render sections only.
|
package/dist/assets/styles.css
CHANGED
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
--checker-border: #2e2e2e;
|
|
55
55
|
--ph-light: #eaeef2;
|
|
56
56
|
--track: #262626;
|
|
57
|
+
/* Context-breakdown segments (system/tools/history/reserve/free). */
|
|
58
|
+
--seg-system: #a78bfa;
|
|
59
|
+
--seg-tools: #38bdf8;
|
|
60
|
+
--seg-history: #34d399;
|
|
61
|
+
--seg-reserve: #fbbf24;
|
|
62
|
+
--seg-free: #3f3f46;
|
|
57
63
|
--plan-line: #2e2e2e;
|
|
58
64
|
--plan-bar-fg: #a3a3a3;
|
|
59
65
|
--sec-h: #a3a3a3;
|
|
@@ -124,6 +130,12 @@ body.light {
|
|
|
124
130
|
--checker-border: #d0d7de;
|
|
125
131
|
--ph-light: #eaeef2;
|
|
126
132
|
--track: #eaeef2;
|
|
133
|
+
/* Context-breakdown segments (system/tools/history/reserve/free). */
|
|
134
|
+
--seg-system: #7c3aed;
|
|
135
|
+
--seg-tools: #0284c7;
|
|
136
|
+
--seg-history: #059669;
|
|
137
|
+
--seg-reserve: #d97706;
|
|
138
|
+
--seg-free: #d0d7de;
|
|
127
139
|
--plan-line: #d0d7de;
|
|
128
140
|
--plan-bar-fg: #57606a;
|
|
129
141
|
--sec-h: #57606a;
|
|
@@ -167,6 +179,8 @@ body[data-font="large"] { font-size: 17px; }
|
|
|
167
179
|
#openSettings { margin-left: auto; margin-right: 6px; }
|
|
168
180
|
#toggleSidebar { display: none; }
|
|
169
181
|
.conn { color: var(--dim-a); font-size: 11.5px; white-space: nowrap; }
|
|
182
|
+
.conn.stale { color: var(--yellow); }
|
|
183
|
+
.conn.stale .linkbtn { font-size: 11.5px; }
|
|
170
184
|
.tabstrip {
|
|
171
185
|
display: flex; gap: 4px; align-items: center; overflow-x: auto;
|
|
172
186
|
flex: 1; min-width: 0;
|
|
@@ -841,6 +855,26 @@ body.light .kv .wn { color: var(--yellow); }
|
|
|
841
855
|
.bar { height: 4px; background: var(--track); border-radius: 2px; margin-top: 6px; overflow: hidden; }
|
|
842
856
|
.bar i { display: block; height: 100%; width: 0; background: var(--green-fill); transition: width .2s; }
|
|
843
857
|
|
|
858
|
+
/* Segmented context bar: flex row so widths add up to the full window. */
|
|
859
|
+
.bar.segbar { display: flex; }
|
|
860
|
+
.bar.segbar i { width: auto; flex: 0 0 auto; transition: width .25s; }
|
|
861
|
+
.bar.segbar .seg-system { background: var(--seg-system); }
|
|
862
|
+
.bar.segbar .seg-tools { background: var(--seg-tools); }
|
|
863
|
+
.bar.segbar .seg-history { background: var(--seg-history); }
|
|
864
|
+
.bar.segbar .seg-reserve { background: var(--seg-reserve); }
|
|
865
|
+
.bar.segbar .seg-free { background: var(--seg-free); }
|
|
866
|
+
/* Legend of context parts (dim dot + label + tokens): rows, no boxes. */
|
|
867
|
+
.seglegend { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 5px; }
|
|
868
|
+
.segl-item { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dim-b); }
|
|
869
|
+
.segl-dot { width: 7px; height: 7px; border-radius: 2px; flex: 0 0 auto; }
|
|
870
|
+
.segl-dot.seg-system { background: var(--seg-system); }
|
|
871
|
+
.segl-dot.seg-tools { background: var(--seg-tools); }
|
|
872
|
+
.segl-dot.seg-history { background: var(--seg-history); }
|
|
873
|
+
.segl-dot.seg-reserve { background: var(--seg-reserve); }
|
|
874
|
+
.segl-dot.seg-free { background: var(--seg-free); }
|
|
875
|
+
.segl-item b { color: var(--muted); font-weight: 600; }
|
|
876
|
+
.segl-label { color: var(--dim-b); }
|
|
877
|
+
|
|
844
878
|
/* ---- settings modal + nested dialogs (provider, confirm) ---- */
|
|
845
879
|
.overlay {
|
|
846
880
|
display: none; position: fixed; inset: 0; z-index: 50;
|
package/dist/i18n/en.json
CHANGED
|
@@ -592,6 +592,9 @@
|
|
|
592
592
|
"repl.model": "Model:",
|
|
593
593
|
"repl.provider": "Provider:",
|
|
594
594
|
"repl.context": "Context:",
|
|
595
|
+
"repl.ctx_part_system": "system",
|
|
596
|
+
"repl.ctx_part_reserve": "reserve",
|
|
597
|
+
"repl.ctx_part_history": "history",
|
|
595
598
|
"repl.sysprompt_label": "System prompt:",
|
|
596
599
|
"repl.sysprompt_size": "{used} / {budget} tokens",
|
|
597
600
|
"repl.sysprompt_desc": "System prompt size and blocks (use --full for the text)",
|
|
@@ -1052,6 +1055,8 @@
|
|
|
1052
1055
|
"web.sessbar.prev": "previous session",
|
|
1053
1056
|
"web.sessbar.next": "next session",
|
|
1054
1057
|
"web.conn.reconnecting": "reconnecting…",
|
|
1058
|
+
"web.i18n.stale": "translations updated — restart the server",
|
|
1059
|
+
"web.i18n.reload": "reload page",
|
|
1055
1060
|
"web.sidebar.plan": "plan",
|
|
1056
1061
|
"web.sidebar.session": "session",
|
|
1057
1062
|
"web.sidebar.usage": "usage",
|
|
@@ -1118,6 +1123,11 @@
|
|
|
1118
1123
|
"web.usage.cache": "cache",
|
|
1119
1124
|
"web.usage.cost": "cost",
|
|
1120
1125
|
"web.usage.compactions": "compactions",
|
|
1126
|
+
"web.usage.part_system": "instructions",
|
|
1127
|
+
"web.usage.part_tools": "tools",
|
|
1128
|
+
"web.usage.part_history": "dialogue",
|
|
1129
|
+
"web.usage.part_reserve": "response reserve",
|
|
1130
|
+
"web.usage.part_free": "free",
|
|
1121
1131
|
"web.usage.quality": "quality",
|
|
1122
1132
|
"web.usage.probe": "probe",
|
|
1123
1133
|
"web.usage.probe_loaded": "{actual} loaded",
|
package/dist/i18n/ru.json
CHANGED
|
@@ -589,6 +589,9 @@
|
|
|
589
589
|
"repl.model": "Модель:",
|
|
590
590
|
"repl.provider": "Провайдер:",
|
|
591
591
|
"repl.context": "Контекст:",
|
|
592
|
+
"repl.ctx_part_system": "система",
|
|
593
|
+
"repl.ctx_part_reserve": "резерв",
|
|
594
|
+
"repl.ctx_part_history": "история",
|
|
592
595
|
"repl.sysprompt_label": "Системный промпт:",
|
|
593
596
|
"repl.sysprompt_size": "{used} / {budget} токенов",
|
|
594
597
|
"repl.sysprompt_desc": "Размер системного промпта и блоки (текст — через --full)",
|
|
@@ -1055,6 +1058,8 @@
|
|
|
1055
1058
|
"web.sessbar.prev": "предыдущая сессия",
|
|
1056
1059
|
"web.sessbar.next": "следующая сессия",
|
|
1057
1060
|
"web.conn.reconnecting": "переподключение…",
|
|
1061
|
+
"web.i18n.stale": "переводы обновлены — перезапустите сервер",
|
|
1062
|
+
"web.i18n.reload": "обновить страницу",
|
|
1058
1063
|
"web.sidebar.plan": "план",
|
|
1059
1064
|
"web.sidebar.session": "сессия",
|
|
1060
1065
|
"web.sidebar.usage": "использование",
|
|
@@ -1121,6 +1126,11 @@
|
|
|
1121
1126
|
"web.usage.cache": "кеш",
|
|
1122
1127
|
"web.usage.cost": "стоимость",
|
|
1123
1128
|
"web.usage.compactions": "сжатия",
|
|
1129
|
+
"web.usage.part_system": "инструкции",
|
|
1130
|
+
"web.usage.part_tools": "инструменты",
|
|
1131
|
+
"web.usage.part_history": "диалог",
|
|
1132
|
+
"web.usage.part_reserve": "резерв ответа",
|
|
1133
|
+
"web.usage.part_free": "свободно",
|
|
1124
1134
|
"web.usage.quality": "качество",
|
|
1125
1135
|
"web.usage.probe": "проб",
|
|
1126
1136
|
"web.usage.probe_loaded": "загружено {actual}",
|
package/dist/main.js
CHANGED
|
@@ -3073,6 +3073,9 @@ Available commands:`,
|
|
|
3073
3073
|
"repl.model": "Model:",
|
|
3074
3074
|
"repl.provider": "Provider:",
|
|
3075
3075
|
"repl.context": "Context:",
|
|
3076
|
+
"repl.ctx_part_system": "system",
|
|
3077
|
+
"repl.ctx_part_reserve": "reserve",
|
|
3078
|
+
"repl.ctx_part_history": "history",
|
|
3076
3079
|
"repl.sysprompt_label": "System prompt:",
|
|
3077
3080
|
"repl.sysprompt_size": "{used} / {budget} tokens",
|
|
3078
3081
|
"repl.sysprompt_desc": "System prompt size and blocks (use --full for the text)",
|
|
@@ -3577,6 +3580,8 @@ Updated: {updatedAt}`,
|
|
|
3577
3580
|
"web.sessbar.prev": "previous session",
|
|
3578
3581
|
"web.sessbar.next": "next session",
|
|
3579
3582
|
"web.conn.reconnecting": "reconnecting…",
|
|
3583
|
+
"web.i18n.stale": "translations updated — restart the server",
|
|
3584
|
+
"web.i18n.reload": "reload page",
|
|
3580
3585
|
"web.sidebar.plan": "plan",
|
|
3581
3586
|
"web.sidebar.session": "session",
|
|
3582
3587
|
"web.sidebar.usage": "usage",
|
|
@@ -3643,6 +3648,11 @@ Updated: {updatedAt}`,
|
|
|
3643
3648
|
"web.usage.cache": "cache",
|
|
3644
3649
|
"web.usage.cost": "cost",
|
|
3645
3650
|
"web.usage.compactions": "compactions",
|
|
3651
|
+
"web.usage.part_system": "instructions",
|
|
3652
|
+
"web.usage.part_tools": "tools",
|
|
3653
|
+
"web.usage.part_history": "dialogue",
|
|
3654
|
+
"web.usage.part_reserve": "response reserve",
|
|
3655
|
+
"web.usage.part_free": "free",
|
|
3646
3656
|
"web.usage.quality": "quality",
|
|
3647
3657
|
"web.usage.probe": "probe",
|
|
3648
3658
|
"web.usage.probe_loaded": "{actual} loaded",
|
|
@@ -4972,6 +4982,9 @@ var init_ru = __esm(() => {
|
|
|
4972
4982
|
"repl.model": "Модель:",
|
|
4973
4983
|
"repl.provider": "Провайдер:",
|
|
4974
4984
|
"repl.context": "Контекст:",
|
|
4985
|
+
"repl.ctx_part_system": "система",
|
|
4986
|
+
"repl.ctx_part_reserve": "резерв",
|
|
4987
|
+
"repl.ctx_part_history": "история",
|
|
4975
4988
|
"repl.sysprompt_label": "Системный промпт:",
|
|
4976
4989
|
"repl.sysprompt_size": "{used} / {budget} токенов",
|
|
4977
4990
|
"repl.sysprompt_desc": "Размер системного промпта и блоки (текст — через --full)",
|
|
@@ -5483,6 +5496,8 @@ var init_ru = __esm(() => {
|
|
|
5483
5496
|
"web.sessbar.prev": "предыдущая сессия",
|
|
5484
5497
|
"web.sessbar.next": "следующая сессия",
|
|
5485
5498
|
"web.conn.reconnecting": "переподключение…",
|
|
5499
|
+
"web.i18n.stale": "переводы обновлены — перезапустите сервер",
|
|
5500
|
+
"web.i18n.reload": "обновить страницу",
|
|
5486
5501
|
"web.sidebar.plan": "план",
|
|
5487
5502
|
"web.sidebar.session": "сессия",
|
|
5488
5503
|
"web.sidebar.usage": "использование",
|
|
@@ -5549,6 +5564,11 @@ var init_ru = __esm(() => {
|
|
|
5549
5564
|
"web.usage.cache": "кеш",
|
|
5550
5565
|
"web.usage.cost": "стоимость",
|
|
5551
5566
|
"web.usage.compactions": "сжатия",
|
|
5567
|
+
"web.usage.part_system": "инструкции",
|
|
5568
|
+
"web.usage.part_tools": "инструменты",
|
|
5569
|
+
"web.usage.part_history": "диалог",
|
|
5570
|
+
"web.usage.part_reserve": "резерв ответа",
|
|
5571
|
+
"web.usage.part_free": "свободно",
|
|
5552
5572
|
"web.usage.quality": "качество",
|
|
5553
5573
|
"web.usage.probe": "проб",
|
|
5554
5574
|
"web.usage.probe_loaded": "загружено {actual}",
|
|
@@ -11093,7 +11113,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
11093
11113
|
if (config.autoPlan) {
|
|
11094
11114
|
lines.push(`PLAN (MANDATORY): file creation, package installs, or multi-action tasks MUST create a "plan" BEFORE starting.`);
|
|
11095
11115
|
}
|
|
11096
|
-
lines.push(`Runtime verification: when a bug is described in terms of runtime behavior`, `(what happens when the app runs), reproduce it —`, `run the app / build / browser tool — confirm the fix against actual runtime output`, `before calling "plan_step step=N status=done".`, `Check existing servers first (process_list/process_log): a stale dev server`, `runs the old pipeline and keeps failing after the fix.`, `Restart/kill it (process_kill) and verify the exact endpoint/port reported —`, `a fresh port does NOT prove the user's error is gone.`);
|
|
11116
|
+
lines.push(`Runtime verification: when a bug is described in terms of runtime behavior`, `(what happens when the app runs), reproduce it —`, `run the app / build / browser tool — confirm the fix against actual runtime output`, `before calling "plan_step step=N status=done".`, `Check existing servers first (process_list/process_log): a stale dev server`, `runs the old pipeline and keeps failing after the fix.`, `Restart/kill it (process_kill) and verify the exact endpoint/port reported —`, `a fresh port does NOT prove the user's error is gone.`, `Verification "mma web" servers run isolated (MMA_CONFIG_DIR=<empty tmp dir>) —`, `never verify against the user's live server/sessions/state.`);
|
|
11097
11117
|
const sec = config.security;
|
|
11098
11118
|
if (sec?.enabled) {
|
|
11099
11119
|
const secLines = [
|
|
@@ -14404,6 +14424,40 @@ var init_write_file = __esm(() => {
|
|
|
14404
14424
|
// src/tools/edit-file.ts
|
|
14405
14425
|
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
14406
14426
|
import { resolve as resolve6 } from "path";
|
|
14427
|
+
function spliceNormalized(content, oldStr, newStr) {
|
|
14428
|
+
const normOld = oldStr.replace(/\r\n/g, `
|
|
14429
|
+
`);
|
|
14430
|
+
const norm = [];
|
|
14431
|
+
const map = [];
|
|
14432
|
+
for (let i = 0;i < content.length; i++) {
|
|
14433
|
+
if (content[i] === "\r" && content[i + 1] === `
|
|
14434
|
+
`) {
|
|
14435
|
+
norm.push(`
|
|
14436
|
+
`);
|
|
14437
|
+
map.push(i);
|
|
14438
|
+
i++;
|
|
14439
|
+
} else {
|
|
14440
|
+
norm.push(content[i]);
|
|
14441
|
+
map.push(i);
|
|
14442
|
+
}
|
|
14443
|
+
}
|
|
14444
|
+
const normContent = norm.join("");
|
|
14445
|
+
const idx = normContent.indexOf(normOld);
|
|
14446
|
+
if (idx === -1 || normOld.length === 0)
|
|
14447
|
+
return null;
|
|
14448
|
+
const startOrig = map[idx];
|
|
14449
|
+
const endNorm = idx + normOld.length;
|
|
14450
|
+
const endOrig = endNorm < map.length ? map[endNorm] : content.length;
|
|
14451
|
+
let insert = newStr;
|
|
14452
|
+
if (content.includes(`\r
|
|
14453
|
+
`) && !newStr.includes(`\r
|
|
14454
|
+
`) && newStr.includes(`
|
|
14455
|
+
`)) {
|
|
14456
|
+
insert = newStr.replace(/\n/g, `\r
|
|
14457
|
+
`);
|
|
14458
|
+
}
|
|
14459
|
+
return content.slice(0, startOrig) + insert + content.slice(endOrig);
|
|
14460
|
+
}
|
|
14407
14461
|
var editFileTool;
|
|
14408
14462
|
var init_edit_file = __esm(() => {
|
|
14409
14463
|
init_i18n();
|
|
@@ -14488,13 +14542,19 @@ var init_edit_file = __esm(() => {
|
|
|
14488
14542
|
output: t("file.read_failed", { path, error: errMsg(err) })
|
|
14489
14543
|
};
|
|
14490
14544
|
}
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
14495
|
-
|
|
14545
|
+
let updated;
|
|
14546
|
+
if (content.includes(oldStr)) {
|
|
14547
|
+
updated = content.replace(oldStr, () => newStr);
|
|
14548
|
+
} else {
|
|
14549
|
+
const spliced = spliceNormalized(content, oldStr, newStr);
|
|
14550
|
+
if (!spliced) {
|
|
14551
|
+
return {
|
|
14552
|
+
success: false,
|
|
14553
|
+
output: t("file.string_not_found", { str: oldStr, path })
|
|
14554
|
+
};
|
|
14555
|
+
}
|
|
14556
|
+
updated = spliced;
|
|
14496
14557
|
}
|
|
14497
|
-
const updated = content.replace(oldStr, () => newStr);
|
|
14498
14558
|
if (securityConfig?.enabled && securityConfig?.contentScan?.enabled) {
|
|
14499
14559
|
const scanResult = scanContent(updated, path, securityConfig.contentScan);
|
|
14500
14560
|
if (!scanResult.allowed) {
|
|
@@ -21460,6 +21520,27 @@ function queueToolImages(sink, images) {
|
|
|
21460
21520
|
}
|
|
21461
21521
|
|
|
21462
21522
|
// src/core/agent/tool-batch.ts
|
|
21523
|
+
function diagnoseRepeatedFailures(tool, headlines) {
|
|
21524
|
+
const genericCause = "Repeated failures suggest a systemic problem (config, environment, or a broken tool), not a one-off";
|
|
21525
|
+
const genericSolution = "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool";
|
|
21526
|
+
const modelMiss = tool === "bash" ? [/did not match any test files?/i, /no such file/i, /cannot find (the )?path/i] : [];
|
|
21527
|
+
if (headlines.length > 0 && headlines.every((h) => /^\(exit code \d+\)$/.test(h.trim()) || modelMiss.some((re) => re.test(h)))) {
|
|
21528
|
+
return {
|
|
21529
|
+
skip: true,
|
|
21530
|
+
cause: "Model named a wrong target (path/filter); the tool itself works",
|
|
21531
|
+
solution: "Re-check the target path or filter instead of blaming the tool"
|
|
21532
|
+
};
|
|
21533
|
+
}
|
|
21534
|
+
if (tool === "edit_file" && headlines.some((h) => /string not found|не найдена/i.test(h))) {
|
|
21535
|
+
return {
|
|
21536
|
+
skip: false,
|
|
21537
|
+
cause: "Anchor mismatch between the model anchor and file content — often CRLF vs LF line endings or a stale read, not a broken tool",
|
|
21538
|
+
solution: "Re-read the exact block with read_file and retry with a short unique single-line anchor"
|
|
21539
|
+
};
|
|
21540
|
+
}
|
|
21541
|
+
return { skip: false, cause: genericCause, solution: genericSolution };
|
|
21542
|
+
}
|
|
21543
|
+
|
|
21463
21544
|
class ToolBatchExecutor {
|
|
21464
21545
|
deps;
|
|
21465
21546
|
constructor(deps) {
|
|
@@ -21619,15 +21700,22 @@ ${result.diff}
|
|
|
21619
21700
|
this.deps.compactionService.compactAfterTool(state);
|
|
21620
21701
|
if (!result.success && !result.blocked && !infoTools.has(call.name)) {
|
|
21621
21702
|
const key = call.name;
|
|
21622
|
-
const prev = state.toolFailureCounts.get(key) ?? { count: 0, error: "" };
|
|
21703
|
+
const prev = state.toolFailureCounts.get(key) ?? { count: 0, error: "", samples: [] };
|
|
21623
21704
|
prev.count++;
|
|
21624
|
-
|
|
21705
|
+
const errText = String(result.output ?? "");
|
|
21706
|
+
prev.error = errText.slice(0, 200);
|
|
21707
|
+
const headline = errText.split(`
|
|
21708
|
+
`)[0].slice(0, 160).trim();
|
|
21709
|
+
if (headline && !prev.samples.includes(headline) && prev.samples.length < 3) {
|
|
21710
|
+
prev.samples.push(headline);
|
|
21711
|
+
}
|
|
21625
21712
|
state.toolFailureCounts.set(key, prev);
|
|
21626
21713
|
if (prev.count >= MIN_REPEATED_TOOL_FAILURES && !state.memoryRuleRecorded.has(key)) {
|
|
21627
21714
|
state.memoryRuleRecorded.add(key);
|
|
21628
21715
|
const memStore = this.deps.memoryStore;
|
|
21629
|
-
|
|
21630
|
-
|
|
21716
|
+
const diagnosis = diagnoseRepeatedFailures(key, prev.samples);
|
|
21717
|
+
if (memStore && !diagnosis.skip) {
|
|
21718
|
+
memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.samples.join(" | ") || prev.error})`, diagnosis.cause, diagnosis.solution);
|
|
21631
21719
|
logger3.diagnostic("warn", `Recorded repeated ${key} failures to memory (${prev.count}x)`);
|
|
21632
21720
|
}
|
|
21633
21721
|
}
|
|
@@ -22459,6 +22547,7 @@ var init_provider = __esm(() => {
|
|
|
22459
22547
|
|
|
22460
22548
|
// src/modules/critic/evidence.ts
|
|
22461
22549
|
import { readFileSync as readFileSync26 } from "fs";
|
|
22550
|
+
import { execFileSync } from "node:child_process";
|
|
22462
22551
|
import { resolve as resolve15 } from "path";
|
|
22463
22552
|
function isWorkMutation(name) {
|
|
22464
22553
|
return MUTATING_TOOLS.has(name) || name === "bash";
|
|
@@ -22476,6 +22565,10 @@ function buildEvidencePack(input) {
|
|
|
22476
22565
|
if (cmd)
|
|
22477
22566
|
commands.push(cmd);
|
|
22478
22567
|
}
|
|
22568
|
+
if (input.baseDir) {
|
|
22569
|
+
for (const f of gitChangedFiles(input.baseDir))
|
|
22570
|
+
changedFiles.add(f);
|
|
22571
|
+
}
|
|
22479
22572
|
return {
|
|
22480
22573
|
task: input.task,
|
|
22481
22574
|
planSummary: input.planSummary,
|
|
@@ -22500,6 +22593,45 @@ function changedFileOf(ev) {
|
|
|
22500
22593
|
return;
|
|
22501
22594
|
return typeof ev.args?.path === "string" ? ev.args.path : undefined;
|
|
22502
22595
|
}
|
|
22596
|
+
function parsePorcelainLine(line) {
|
|
22597
|
+
if (line.length < 4)
|
|
22598
|
+
return null;
|
|
22599
|
+
let p = line.slice(3).trim();
|
|
22600
|
+
const arrow = p.indexOf(" -> ");
|
|
22601
|
+
if (arrow !== -1)
|
|
22602
|
+
p = p.slice(arrow + 4).trim();
|
|
22603
|
+
if (p.length >= 2 && p.startsWith('"') && p.endsWith('"')) {
|
|
22604
|
+
try {
|
|
22605
|
+
p = JSON.parse(p);
|
|
22606
|
+
} catch {
|
|
22607
|
+
p = p.slice(1, -1);
|
|
22608
|
+
}
|
|
22609
|
+
}
|
|
22610
|
+
return p || null;
|
|
22611
|
+
}
|
|
22612
|
+
function gitChangedFiles(baseDir) {
|
|
22613
|
+
let out;
|
|
22614
|
+
try {
|
|
22615
|
+
out = execFileSync("git", ["status", "--porcelain"], {
|
|
22616
|
+
cwd: resolve15(baseDir),
|
|
22617
|
+
encoding: "utf-8",
|
|
22618
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
22619
|
+
timeout: 1e4
|
|
22620
|
+
});
|
|
22621
|
+
} catch {
|
|
22622
|
+
return [];
|
|
22623
|
+
}
|
|
22624
|
+
const files = [];
|
|
22625
|
+
for (const line of out.split(`
|
|
22626
|
+
`)) {
|
|
22627
|
+
const p = parsePorcelainLine(line);
|
|
22628
|
+
if (p)
|
|
22629
|
+
files.push(p);
|
|
22630
|
+
if (files.length >= MAX_GIT_FILES)
|
|
22631
|
+
break;
|
|
22632
|
+
}
|
|
22633
|
+
return files;
|
|
22634
|
+
}
|
|
22503
22635
|
function commandOf(ev) {
|
|
22504
22636
|
if (ev.tool !== "bash" || typeof ev.args?.command !== "string")
|
|
22505
22637
|
return;
|
|
@@ -22523,7 +22655,7 @@ function readExcerpts(baseDir, paths) {
|
|
|
22523
22655
|
}
|
|
22524
22656
|
return out;
|
|
22525
22657
|
}
|
|
22526
|
-
var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200, MAX_ANSWER_CHARS = 2000;
|
|
22658
|
+
var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200, MAX_ANSWER_CHARS = 2000, MAX_GIT_FILES = 20;
|
|
22527
22659
|
var init_evidence = __esm(() => {
|
|
22528
22660
|
init_tool_output();
|
|
22529
22661
|
init_context_log();
|
|
@@ -23660,7 +23792,8 @@ class Agent {
|
|
|
23660
23792
|
costBreakdown: this.costTracker.breakdown(),
|
|
23661
23793
|
cache: cacheStats,
|
|
23662
23794
|
compactionCount: contextManager.getCompactionCount(),
|
|
23663
|
-
contextQuality: contextManager.getQuality()
|
|
23795
|
+
contextQuality: contextManager.getQuality(),
|
|
23796
|
+
contextBreakdown: contextManager.getBreakdown?.()
|
|
23664
23797
|
};
|
|
23665
23798
|
}
|
|
23666
23799
|
return {
|
|
@@ -23678,6 +23811,7 @@ class Agent {
|
|
|
23678
23811
|
cache: cacheStats,
|
|
23679
23812
|
compactionCount: contextManager.getCompactionCount(),
|
|
23680
23813
|
contextQuality: contextManager.getQuality(),
|
|
23814
|
+
contextBreakdown: contextManager.getBreakdown?.(),
|
|
23681
23815
|
llmDurationMs: state.totalLlmDuration
|
|
23682
23816
|
};
|
|
23683
23817
|
}
|
|
@@ -24290,6 +24424,28 @@ ${lines.join(`
|
|
|
24290
24424
|
summary: this.compactedBlock ?? ""
|
|
24291
24425
|
};
|
|
24292
24426
|
}
|
|
24427
|
+
systemTokens() {
|
|
24428
|
+
const sys = this.messages.find((m) => m.role === "system");
|
|
24429
|
+
return sys ? this.estimateMessageTokensCached(sys) : 0;
|
|
24430
|
+
}
|
|
24431
|
+
getBreakdown() {
|
|
24432
|
+
const used = this.getEstimatedTokens();
|
|
24433
|
+
const system = this.systemTokens();
|
|
24434
|
+
const tools = this.toolTokens;
|
|
24435
|
+
const history = Math.max(0, used - system - tools);
|
|
24436
|
+
const b = this.budget;
|
|
24437
|
+
return {
|
|
24438
|
+
system,
|
|
24439
|
+
tools,
|
|
24440
|
+
history,
|
|
24441
|
+
used,
|
|
24442
|
+
systemBudget: b.systemPrompt,
|
|
24443
|
+
historyBudget: b.history,
|
|
24444
|
+
reserve: b.responseReserve,
|
|
24445
|
+
free: Math.max(0, this.contextWindow - used - b.responseReserve),
|
|
24446
|
+
window: this.contextWindow
|
|
24447
|
+
};
|
|
24448
|
+
}
|
|
24293
24449
|
getSnapshot() {
|
|
24294
24450
|
return {
|
|
24295
24451
|
window: this.contextWindow,
|
|
@@ -24299,7 +24455,8 @@ ${lines.join(`
|
|
|
24299
24455
|
messageCount: this.messages.length,
|
|
24300
24456
|
quality: this.getQuality(),
|
|
24301
24457
|
compactionCount: this.compactionCount,
|
|
24302
|
-
iterationsSinceCompaction: this.iterationsSinceCompaction
|
|
24458
|
+
iterationsSinceCompaction: this.iterationsSinceCompaction,
|
|
24459
|
+
breakdown: this.getBreakdown()
|
|
24303
24460
|
};
|
|
24304
24461
|
}
|
|
24305
24462
|
updateSystemPrompt(content) {
|
|
@@ -24504,7 +24661,33 @@ var init_factual = __esm(() => {
|
|
|
24504
24661
|
if (!fp.includes("/") && !fp.includes("\\")) {
|
|
24505
24662
|
return this.bareNameExists(fp);
|
|
24506
24663
|
}
|
|
24507
|
-
return
|
|
24664
|
+
return this.suffixExists(fp);
|
|
24665
|
+
}
|
|
24666
|
+
suffixExists(fp) {
|
|
24667
|
+
const norm = fp.replace(/\\/g, "/");
|
|
24668
|
+
const tail = `/${norm}`;
|
|
24669
|
+
const base = norm.split("/").pop() ?? norm;
|
|
24670
|
+
const fwd = (p) => p.replace(/\\/g, "/");
|
|
24671
|
+
for (const known of this.knownFiles) {
|
|
24672
|
+
if (fwd(known).endsWith(tail))
|
|
24673
|
+
return true;
|
|
24674
|
+
}
|
|
24675
|
+
const checkIndex = () => {
|
|
24676
|
+
for (const full of this.getNameIndex().get(base) ?? []) {
|
|
24677
|
+
if (fwd(full).endsWith(tail))
|
|
24678
|
+
return true;
|
|
24679
|
+
}
|
|
24680
|
+
return false;
|
|
24681
|
+
};
|
|
24682
|
+
if (checkIndex())
|
|
24683
|
+
return true;
|
|
24684
|
+
const now = Date.now();
|
|
24685
|
+
if (now - this.lastRescan < FactualCheck.RESCAN_COOLDOWN_MS)
|
|
24686
|
+
return false;
|
|
24687
|
+
this.lastRescan = now;
|
|
24688
|
+
this.nameCache = null;
|
|
24689
|
+
this.nameCacheTime = 0;
|
|
24690
|
+
return checkIndex();
|
|
24508
24691
|
}
|
|
24509
24692
|
dotfileVariants(fp) {
|
|
24510
24693
|
const variants = [fp];
|
|
@@ -28751,6 +28934,7 @@ function createBrowserTool() {
|
|
|
28751
28934
|
"Do NOT use it for search queries: search engines (Google, Yandex, Bing) block headless browsers with CAPTCHA/429/redirect pages — use the web_search tool for searching.",
|
|
28752
28935
|
"Each snapshot returns: a numbered list of interactive elements, the visible page text (Content section), browser console messages, and network errors.",
|
|
28753
28936
|
"Use the Content section to understand what the page says; use element numbers as targets for click/type.",
|
|
28937
|
+
"Without vision capabilities screenshots return only a text note — verify visuals through snapshot text, and for local web UI prefer DOM/API assertions (fetch /api/* endpoints, grep built assets) over driving the browser.",
|
|
28754
28938
|
"Actions: open (url), click (target), type (target, text), scroll (direction), back, forward, screenshot, snapshot, close, wait (ms)."
|
|
28755
28939
|
].join(" "),
|
|
28756
28940
|
parameters: {
|
|
@@ -32393,11 +32577,26 @@ var init_plan_tool = __esm(() => {
|
|
|
32393
32577
|
});
|
|
32394
32578
|
|
|
32395
32579
|
// src/modules/execution/module.ts
|
|
32396
|
-
import { existsSync as existsSync51, readFileSync as readFileSync37 } from "fs";
|
|
32580
|
+
import { existsSync as existsSync51, readFileSync as readFileSync37, statSync as statSync11 } from "fs";
|
|
32397
32581
|
import { resolve as resolve22 } from "path";
|
|
32398
32582
|
function planPathToken(p) {
|
|
32399
32583
|
return toForwardSlash(p).toLowerCase();
|
|
32400
32584
|
}
|
|
32585
|
+
function provablyStale(resolved, sinceMs) {
|
|
32586
|
+
if (Number.isNaN(sinceMs))
|
|
32587
|
+
return false;
|
|
32588
|
+
for (const r of resolved) {
|
|
32589
|
+
if (!r)
|
|
32590
|
+
return false;
|
|
32591
|
+
try {
|
|
32592
|
+
if (statSync11(r).mtimeMs > sinceMs)
|
|
32593
|
+
return false;
|
|
32594
|
+
} catch {
|
|
32595
|
+
return false;
|
|
32596
|
+
}
|
|
32597
|
+
}
|
|
32598
|
+
return true;
|
|
32599
|
+
}
|
|
32401
32600
|
|
|
32402
32601
|
class ExecutionModule {
|
|
32403
32602
|
name = "execution";
|
|
@@ -32641,6 +32840,8 @@ class ExecutionModule {
|
|
|
32641
32840
|
});
|
|
32642
32841
|
if (empty)
|
|
32643
32842
|
continue;
|
|
32843
|
+
if (provablyStale(resolved, Date.parse(plan.createdAt)))
|
|
32844
|
+
continue;
|
|
32644
32845
|
}
|
|
32645
32846
|
step.status = "done";
|
|
32646
32847
|
step.note = `auto-closed by final audit: ${step.kind === "delete" ? "all named files removed" : "all named files exist with content"} (${tokens.join(", ")})`;
|
|
@@ -32868,6 +33069,11 @@ class ExecutionModule {
|
|
|
32868
33069
|
const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
|
|
32869
33070
|
if (!satisfied)
|
|
32870
33071
|
return;
|
|
33072
|
+
if (step.kind !== "delete") {
|
|
33073
|
+
const plan = this.tracker?.getPlan();
|
|
33074
|
+
if (plan && provablyStale(resolved, Date.parse(plan.createdAt)))
|
|
33075
|
+
return;
|
|
33076
|
+
}
|
|
32871
33077
|
if (step.kind !== "delete") {
|
|
32872
33078
|
const emptyFiles = [];
|
|
32873
33079
|
for (const r of resolved) {
|
|
@@ -44567,7 +44773,7 @@ var init_loader3 = __esm(() => {
|
|
|
44567
44773
|
});
|
|
44568
44774
|
|
|
44569
44775
|
// src/modules/certification/fact-checker.ts
|
|
44570
|
-
import { existsSync as existsSync56, readFileSync as readFileSync40, statSync as
|
|
44776
|
+
import { existsSync as existsSync56, readFileSync as readFileSync40, statSync as statSync12 } from "fs";
|
|
44571
44777
|
import { join as join50 } from "path";
|
|
44572
44778
|
function checkSandbox(sandboxDir, checks, exitCode, output) {
|
|
44573
44779
|
const failures = [];
|
|
@@ -44613,14 +44819,14 @@ function runCheck2(sandboxDir, check, exitCode, output) {
|
|
|
44613
44819
|
}
|
|
44614
44820
|
function isFile(p) {
|
|
44615
44821
|
try {
|
|
44616
|
-
return existsSync56(p) &&
|
|
44822
|
+
return existsSync56(p) && statSync12(p).isFile();
|
|
44617
44823
|
} catch {
|
|
44618
44824
|
return false;
|
|
44619
44825
|
}
|
|
44620
44826
|
}
|
|
44621
44827
|
function isDir(p) {
|
|
44622
44828
|
try {
|
|
44623
|
-
return existsSync56(p) &&
|
|
44829
|
+
return existsSync56(p) && statSync12(p).isDirectory();
|
|
44624
44830
|
} catch {
|
|
44625
44831
|
return false;
|
|
44626
44832
|
}
|
|
@@ -49242,7 +49448,7 @@ import { homedir as homedir8 } from "os";
|
|
|
49242
49448
|
// src/cli/completer.ts
|
|
49243
49449
|
init_registry2();
|
|
49244
49450
|
init_i18n();
|
|
49245
|
-
import { readdirSync as readdirSync19, statSync as
|
|
49451
|
+
import { readdirSync as readdirSync19, statSync as statSync13 } from "fs";
|
|
49246
49452
|
import { join as join54 } from "path";
|
|
49247
49453
|
function buildSlashCompletions(commands) {
|
|
49248
49454
|
return Array.from(commands.entries()).map(([name, cmd]) => ({
|
|
@@ -49316,7 +49522,7 @@ class FilePathProvider {
|
|
|
49316
49522
|
const matches = [];
|
|
49317
49523
|
for (const entry of entries) {
|
|
49318
49524
|
const entryPath = join54(fullPath, entry);
|
|
49319
|
-
const stat2 =
|
|
49525
|
+
const stat2 = statSync13(entryPath);
|
|
49320
49526
|
const completion = prefix + entry + (stat2.isDirectory() ? "/" : "");
|
|
49321
49527
|
if (completion.startsWith(ctx.partial)) {
|
|
49322
49528
|
matches.push(completion);
|
|
@@ -51320,7 +51526,7 @@ ${divider()}`);
|
|
|
51320
51526
|
row(t("repl.model"), pc.white(this.config.model));
|
|
51321
51527
|
const endpoint = activeProviderEndpoint(this.config.provider);
|
|
51322
51528
|
row(t("repl.provider"), `${endpoint.type} → ${pc.dim(endpoint.baseUrl)}`);
|
|
51323
|
-
row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(
|
|
51529
|
+
row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(${t("repl.ctx_part_system")}:${sysBudget} ${t("repl.ctx_part_reserve")}:${resBudget} ${t("repl.ctx_part_history")}:${histBudget})`)}`);
|
|
51324
51530
|
const si = this.agent.getSystemPromptInfo();
|
|
51325
51531
|
row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
|
|
51326
51532
|
if (this.skillsModule) {
|
|
@@ -51544,7 +51750,7 @@ init_output();
|
|
|
51544
51750
|
init_i18n();
|
|
51545
51751
|
init_colors();
|
|
51546
51752
|
import { existsSync as existsSync63 } from "node:fs";
|
|
51547
|
-
import { join as
|
|
51753
|
+
import { join as join61 } from "node:path";
|
|
51548
51754
|
|
|
51549
51755
|
// src/modules/webui/module.ts
|
|
51550
51756
|
init_config2();
|
|
@@ -52760,10 +52966,28 @@ function handleModelsGet(res, s) {
|
|
|
52760
52966
|
|
|
52761
52967
|
// src/modules/webui/i18n-api.ts
|
|
52762
52968
|
init_i18n();
|
|
52969
|
+
|
|
52970
|
+
// src/modules/webui/i18n-stale.ts
|
|
52971
|
+
import { statSync as statSync14 } from "fs";
|
|
52972
|
+
import { join as join60 } from "path";
|
|
52973
|
+
var serverStartedAt = Date.now();
|
|
52974
|
+
var dictFiles = ["en.json", "ru.json"].map((f) => join60(import.meta.dirname, "..", "..", "i18n", f));
|
|
52975
|
+
function isI18nStale() {
|
|
52976
|
+
for (const p of dictFiles) {
|
|
52977
|
+
try {
|
|
52978
|
+
if (statSync14(p).mtimeMs > serverStartedAt)
|
|
52979
|
+
return true;
|
|
52980
|
+
} catch {}
|
|
52981
|
+
}
|
|
52982
|
+
return false;
|
|
52983
|
+
}
|
|
52984
|
+
|
|
52985
|
+
// src/modules/webui/i18n-api.ts
|
|
52763
52986
|
function handleI18nGet(res, s) {
|
|
52764
52987
|
sendJson(res, 200, {
|
|
52765
52988
|
locale: s.config?.locale ?? "en",
|
|
52766
|
-
dicts: getFlatDicts()
|
|
52989
|
+
dicts: getFlatDicts(),
|
|
52990
|
+
i18nStale: isI18nStale()
|
|
52767
52991
|
});
|
|
52768
52992
|
}
|
|
52769
52993
|
|
|
@@ -54584,6 +54808,8 @@ function captureUsage(result, fallbackLimit) {
|
|
|
54584
54808
|
}
|
|
54585
54809
|
if (typeof result.contextQuality === "number")
|
|
54586
54810
|
usage.contextQuality = result.contextQuality;
|
|
54811
|
+
if (result.contextBreakdown)
|
|
54812
|
+
usage.contextBreakdown = result.contextBreakdown;
|
|
54587
54813
|
return usage;
|
|
54588
54814
|
}
|
|
54589
54815
|
function planEventFor(toolName, execModule) {
|
|
@@ -54606,10 +54832,12 @@ function buildHostState(deps, busy) {
|
|
|
54606
54832
|
compactionCount: deps.usage?.compactionCount,
|
|
54607
54833
|
cacheHitRate: deps.usage?.cacheHitRate,
|
|
54608
54834
|
contextQuality: deps.usage?.contextQuality,
|
|
54835
|
+
contextBreakdown: deps.usage?.contextBreakdown,
|
|
54609
54836
|
contextProbe: deps.contextProbe,
|
|
54610
54837
|
workspace: deps.workspace,
|
|
54611
54838
|
instructions: deps.instructions,
|
|
54612
54839
|
projectMap: deps.projectMap ?? undefined,
|
|
54840
|
+
i18nStale: isI18nStale(),
|
|
54613
54841
|
plan: planSnapshotFromModule(adaptActivePlan(deps.execModule?.getActivePlan()))
|
|
54614
54842
|
};
|
|
54615
54843
|
}
|
|
@@ -54811,7 +55039,7 @@ async function runWeb(opts) {
|
|
|
54811
55039
|
process.env[MMA_ENV.WEBUI_OPEN] = "0";
|
|
54812
55040
|
const mmaDir = globalMmaDir();
|
|
54813
55041
|
const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
|
|
54814
|
-
const virgin = !existsSync63(
|
|
55042
|
+
const virgin = !existsSync63(join61(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
|
|
54815
55043
|
const {
|
|
54816
55044
|
agent,
|
|
54817
55045
|
config,
|
|
@@ -54933,7 +55161,7 @@ init_config2();
|
|
|
54933
55161
|
init_i18n();
|
|
54934
55162
|
init_colors();
|
|
54935
55163
|
import { existsSync as existsSync64, readFileSync as readFileSync45 } from "fs";
|
|
54936
|
-
import { join as
|
|
55164
|
+
import { join as join63, dirname as dirname30 } from "path";
|
|
54937
55165
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
54938
55166
|
|
|
54939
55167
|
// src/modules/updater/index.ts
|
|
@@ -55055,7 +55283,7 @@ init_i18n();
|
|
|
55055
55283
|
init_utils();
|
|
55056
55284
|
init_paths();
|
|
55057
55285
|
import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync30 } from "fs";
|
|
55058
|
-
import { join as
|
|
55286
|
+
import { join as join62 } from "path";
|
|
55059
55287
|
import { homedir as homedir9 } from "os";
|
|
55060
55288
|
var CRASH_LOG_DIR = globalLogsDir();
|
|
55061
55289
|
var CRASH_LOG_FILE = "crash.jsonl";
|
|
@@ -55073,7 +55301,7 @@ function formatCrashEntry(type2, err) {
|
|
|
55073
55301
|
function writeCrashEntry(dir, entry) {
|
|
55074
55302
|
try {
|
|
55075
55303
|
mkdirSync30(dir, { recursive: true });
|
|
55076
|
-
appendFileSync8(
|
|
55304
|
+
appendFileSync8(join62(dir, CRASH_LOG_FILE), `${JSON.stringify(entry)}
|
|
55077
55305
|
`, "utf-8");
|
|
55078
55306
|
} catch {}
|
|
55079
55307
|
}
|
|
@@ -55144,7 +55372,7 @@ function resolveEarlyLocale() {
|
|
|
55144
55372
|
if (env3)
|
|
55145
55373
|
return env3;
|
|
55146
55374
|
const dir = globalMmaDir();
|
|
55147
|
-
for (const p of [
|
|
55375
|
+
for (const p of [join63(dir, "config", "core.json"), join63(dir, "config.json")]) {
|
|
55148
55376
|
try {
|
|
55149
55377
|
const parsed = JSON.parse(readFileSync45(p, "utf-8"));
|
|
55150
55378
|
if (typeof parsed.locale === "string")
|
|
@@ -55244,7 +55472,7 @@ async function main() {
|
|
|
55244
55472
|
const prompt = program2.args.join(" ");
|
|
55245
55473
|
const mmaDir = globalMmaDir();
|
|
55246
55474
|
const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
|
|
55247
|
-
const virgin = !existsSync64(
|
|
55475
|
+
const virgin = !existsSync64(join63(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
|
|
55248
55476
|
const { agent, config, baseDir, legacyDetected, contextProbe } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete, reasoningLevel, contextLog);
|
|
55249
55477
|
if (verboseOpt)
|
|
55250
55478
|
config.ui.verbosity = "verbose";
|
|
@@ -55335,7 +55563,7 @@ async function main() {
|
|
|
55335
55563
|
if (onboardingStart === "init")
|
|
55336
55564
|
process.env[MMA_ENV.START_INIT] = "1";
|
|
55337
55565
|
const mmaDir = globalMmaDir();
|
|
55338
|
-
const legacyConfigPath =
|
|
55566
|
+
const legacyConfigPath = join63(mmaDir, "config.json");
|
|
55339
55567
|
let hasAnyConfig = existsSync64(legacyConfigPath);
|
|
55340
55568
|
if (!hasAnyConfig) {
|
|
55341
55569
|
try {
|
|
@@ -55343,7 +55571,7 @@ async function main() {
|
|
|
55343
55571
|
hasAnyConfig = hasDomainFiles2(mmaDir);
|
|
55344
55572
|
} catch {}
|
|
55345
55573
|
}
|
|
55346
|
-
const projectConfigPath = projectDir ?
|
|
55574
|
+
const projectConfigPath = projectDir ? join63(projectDir, ".mmrc") : join63(process.cwd(), ".mmrc");
|
|
55347
55575
|
if (!hasAnyConfig && !exitOnComplete && readEnv("POST_SETUP") !== "1") {
|
|
55348
55576
|
getDefaultChannel().writeLine(`
|
|
55349
55577
|
${t("cli.first_run")}
|