micro-models-agent 0.63.3 → 1.5.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 +258 -1
- package/dist/cli/cache-line.js +30 -0
- package/dist/cli/command-suggest.js +38 -0
- package/dist/cli/commands.js +285 -60
- package/dist/cli/completer.js +16 -16
- package/dist/cli/json-payload.js +32 -0
- package/dist/cli/main.js +165 -77
- package/dist/cli/plugin-commands.js +5 -4
- package/dist/cli/relaunch.js +37 -0
- package/dist/cli/repl-commands.js +441 -307
- package/dist/cli/repl.js +360 -83
- package/dist/cli/run-result.js +12 -6
- package/dist/cli/security-commands.js +64 -60
- package/dist/cli/setup-order.js +57 -0
- package/dist/cli/setup-prompt.js +49 -0
- package/dist/cli/setup.js +52 -48
- package/dist/config/budget.js +48 -0
- package/dist/config/config.js +132 -70
- package/dist/config/defaults.js +37 -11
- package/dist/config/domains.js +9 -50
- package/dist/config/utils.js +56 -0
- package/dist/core/agent/audit-gate.js +49 -0
- package/dist/core/agent/compaction.js +89 -0
- package/dist/core/agent/constants.js +61 -0
- package/dist/core/agent/context-renderer.js +40 -0
- package/dist/core/agent/hallucination-gate.js +87 -0
- package/dist/core/agent/loop-state.js +53 -0
- package/dist/core/agent/prefix-monitor.js +101 -0
- package/dist/core/agent/reasoning-resolver.js +56 -0
- package/dist/core/agent/token-tracker.js +96 -0
- package/dist/core/agent/tool-batch.js +237 -0
- package/dist/core/agent/tool-output.js +62 -0
- package/dist/core/agent-moe.js +214 -69
- package/dist/core/agent.js +506 -546
- package/dist/core/bootstrap.js +297 -98
- package/dist/core/crash-handler.js +2 -1
- package/dist/core/prompt-builder.js +3 -0
- package/dist/core/prompt-overflow.js +307 -0
- package/dist/core/session-logger.js +34 -2
- package/dist/i18n/en.json +40 -5
- package/dist/i18n/ru.json +40 -5
- package/dist/index.js +5 -1
- package/dist/llm/cache-usage.js +76 -0
- package/dist/llm/image-utils.js +20 -16
- package/dist/llm/llm-errors.js +41 -0
- package/dist/llm/model-loader.js +30 -0
- package/dist/llm/openai-compat.js +287 -101
- package/dist/llm/orchestrator.js +140 -68
- package/dist/llm/provider-budget.js +68 -0
- package/dist/llm/provider.js +0 -1
- package/dist/llm/stream-state.js +26 -0
- package/dist/llm/token-counter.js +28 -0
- package/dist/logger/app-logger.js +12 -15
- package/dist/main.js +3311 -1192
- package/dist/migration/detect.js +3 -1
- package/dist/modules/browser/actions.js +0 -3
- package/dist/modules/browser/bridge-client.js +2 -0
- package/dist/modules/browser/driver.js +46 -4
- package/dist/modules/certification/cli.js +85 -42
- package/dist/modules/certification/loader.js +15 -1
- package/dist/modules/certification/manifest.js +126 -15
- package/dist/modules/certification/runner.js +4 -26
- package/dist/modules/certification/scenarios.js +184 -5
- package/dist/modules/certification/syntax-scenarios.js +51 -0
- package/dist/modules/context/chunk-query.js +25 -5
- package/dist/modules/context/fact-extractor.js +6 -2
- package/dist/modules/context/manager.js +23 -7
- package/dist/modules/execution/audit-runners.js +7 -1
- package/dist/modules/execution/auditor.js +3 -3
- package/dist/modules/execution/execution-plugin.js +22 -15
- package/dist/modules/execution/input-from.js +46 -0
- package/dist/modules/execution/module.js +107 -18
- package/dist/modules/execution/moe-executor.js +166 -54
- package/dist/modules/execution/plan-actions.js +524 -0
- package/dist/modules/execution/plan-steps.js +23 -0
- package/dist/modules/execution/plan-store.js +15 -3
- package/dist/modules/execution/plan-tool.js +6 -488
- package/dist/modules/execution/plan-validator.js +24 -0
- package/dist/modules/execution/stuck-detector.js +3 -18
- package/dist/modules/execution/tracker.js +14 -5
- package/dist/modules/execution/transient-error.js +30 -0
- package/dist/modules/execution/verifier.js +94 -7
- package/dist/modules/execution/windows-commands.js +11 -0
- package/dist/modules/hallucination/confidence.js +36 -23
- package/dist/modules/hallucination/consistency.js +3 -0
- package/dist/modules/hallucination/detector.js +8 -3
- package/dist/modules/hallucination/factual.js +26 -7
- package/dist/modules/hallucination/llm-judge.js +12 -2
- package/dist/modules/indexer/map-command.js +35 -0
- package/dist/modules/indexer/map-select.js +87 -0
- package/dist/modules/indexer/module.js +34 -22
- package/dist/modules/indexer/symbols.js +189 -0
- package/dist/modules/indexer/walker.js +96 -42
- package/dist/modules/lsp/check-tool.js +2 -1
- package/dist/modules/lsp/client.js +49 -32
- package/dist/modules/lsp/config.js +55 -2
- package/dist/modules/lsp/module.js +38 -5
- package/dist/modules/lsp/probe.js +4 -3
- package/dist/modules/lsp/project-root.js +41 -1
- package/dist/modules/lsp/startup-check.js +12 -4
- package/dist/modules/mcp/client.js +153 -104
- package/dist/modules/mcp/module.js +165 -41
- package/dist/modules/memory/module.js +4 -3
- package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
- package/dist/modules/plugins/manager.js +47 -84
- package/dist/modules/pricing/index.js +17 -7
- package/dist/modules/pricing/prices.js +30 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +56 -0
- package/dist/modules/processes/registry.js +2 -54
- package/dist/modules/providers/cache.js +23 -0
- package/dist/modules/providers/factory.js +28 -0
- package/dist/modules/providers/fallback.js +7 -5
- package/dist/modules/providers/health.js +2 -1
- package/dist/modules/providers/index.js +1 -0
- package/dist/modules/providers/manager.js +17 -2
- package/dist/modules/providers/presets.js +79 -6
- package/dist/modules/reasoning/policy.js +40 -0
- package/dist/modules/reasoning/probe.js +111 -0
- package/dist/modules/security/audit-notifier.js +42 -27
- package/dist/modules/security/command-validator.js +25 -20
- package/dist/modules/security/encryption.js +6 -12
- package/dist/modules/security/network-validator.js +76 -5
- package/dist/modules/security/path-validator.js +77 -34
- package/dist/modules/security/rate-limiter.js +11 -0
- package/dist/modules/security/security-policies.js +1 -1
- package/dist/modules/security/session-encryption.js +13 -2
- package/dist/modules/security/session-isolation.js +2 -9
- package/dist/modules/session/manager.js +11 -0
- package/dist/modules/session/module.js +11 -3
- package/dist/modules/session/store.js +41 -5
- package/dist/modules/skills/loader.js +7 -1
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/changelog-reader.js +94 -0
- package/dist/modules/updater/dev-detect.js +17 -0
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +14 -3
- package/dist/output/bus.js +32 -0
- package/dist/output/channel.js +233 -0
- package/dist/output/format.js +14 -0
- package/dist/output/index.js +7 -0
- package/dist/output/json-sink.js +22 -0
- package/dist/output/machine.js +8 -0
- package/dist/output/session-sink.js +27 -0
- package/dist/output/types.js +1 -0
- package/dist/tools/approve.js +6 -2
- package/dist/tools/attach-image.js +11 -11
- package/dist/tools/auto-fixer.js +198 -0
- package/dist/tools/bash.js +142 -89
- package/dist/tools/chunk-query.js +10 -6
- package/dist/tools/download-file.js +1 -1
- package/dist/tools/edit-file.js +20 -2
- package/dist/tools/executor.js +54 -9
- package/dist/tools/glob-tool.js +7 -0
- package/dist/tools/grep-tool.js +15 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/list-dir.js +3 -1
- package/dist/tools/load-skill.js +2 -1
- package/dist/tools/mcp-call.js +1 -1
- package/dist/tools/move-file.js +5 -4
- package/dist/tools/path-utils.js +7 -0
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/prompt-io.js +28 -0
- package/dist/tools/question.js +12 -12
- package/dist/tools/scope-request.js +91 -0
- package/dist/tools/session-info.js +44 -0
- package/dist/tools/set-thinking.js +71 -0
- package/dist/tools/subagent.js +50 -9
- package/dist/tools/syntax-validator.js +177 -0
- package/dist/tools/user-input.js +16 -9
- package/dist/tools/write-file.js +17 -1
- package/dist/ui/diff.js +10 -0
- package/dist/ui/line-editor.js +179 -26
- package/dist/ui/line-math.js +20 -3
- package/dist/ui/md-formatter.js +100 -10
- package/dist/ui/output.js +5 -4
- package/dist/ui/plan-view.js +2 -7
- package/dist/ui/renderer.js +89 -85
- package/dist/ui/spinner.js +14 -4
- package/dist/utils/error.js +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/retry.js +17 -0
- package/dist/utils/sleep.js +23 -0
- package/dist/utils/truncate.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// src/core/prompt-overflow.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { PromptBuilder } from "./prompt-builder";
|
|
6
|
+
import { estimateTokens } from "../llm/token-counter";
|
|
7
|
+
import { t } from "../i18n/index";
|
|
8
|
+
/** How many tokens the model-facing hint block costs (upper bound for allocation). */
|
|
9
|
+
export const HINT_BLOCK_TOKENS = 60;
|
|
10
|
+
/** Rough chars-per-token factor used for hard truncation fallback. */
|
|
11
|
+
const CHARS_PER_TOKEN = 4;
|
|
12
|
+
const KIND_ORDER = ["instructions", "project-map"];
|
|
13
|
+
const KIND_LABEL = {
|
|
14
|
+
instructions: "project instructions (AGENTS.md)",
|
|
15
|
+
"project-map": "project map",
|
|
16
|
+
};
|
|
17
|
+
const KIND_SOURCE_FILE = {
|
|
18
|
+
instructions: "AGENTS.md",
|
|
19
|
+
"project-map": "",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Static dry-run of the system-prompt budget: which kind-carrying blocks do
|
|
23
|
+
* not fit. Used by bootstrap for the startup banner and by Agent before the
|
|
24
|
+
* first LLM call (the real resolution).
|
|
25
|
+
*/
|
|
26
|
+
export function dryRunOverflow(allBlocks, systemBudget) {
|
|
27
|
+
const builder = new PromptBuilder(systemBudget);
|
|
28
|
+
builder.addBlocks(allBlocks);
|
|
29
|
+
const result = builder.build();
|
|
30
|
+
const includedTokens = result.blocks
|
|
31
|
+
.filter((b) => b.included)
|
|
32
|
+
.reduce((sum, b) => sum + b.tokens, 0);
|
|
33
|
+
const overflow = result.excludedBlocks.filter((b) => Boolean(b.kind));
|
|
34
|
+
return { includedTokens, overflow };
|
|
35
|
+
}
|
|
36
|
+
function cacheKey(kind, content, maxTokens) {
|
|
37
|
+
return createHash("sha256").update(`${kind}|${maxTokens}|${content}`).digest("hex").slice(0, 24);
|
|
38
|
+
}
|
|
39
|
+
function readCache(cacheDir, key) {
|
|
40
|
+
try {
|
|
41
|
+
const path = join(cacheDir, `${key}.md`);
|
|
42
|
+
if (!existsSync(path))
|
|
43
|
+
return null;
|
|
44
|
+
const raw = readFileSync(path, "utf-8").trim();
|
|
45
|
+
if (!raw)
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
if ((parsed.mode === "summary" || parsed.mode === "truncate") && typeof parsed.text === "string" && parsed.text) {
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Legacy plain-text entry — treat as a summary hit
|
|
55
|
+
return { mode: "summary", text: raw };
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Corrupted cache — fall through to re-summarize
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function writeCache(cacheDir, key, entry) {
|
|
65
|
+
try {
|
|
66
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
67
|
+
writeFileSync(join(cacheDir, `${key}.md`), JSON.stringify(entry), "utf-8");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Best-effort — cache failure must not crash the agent
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Collect a full text response from the streaming provider API. */
|
|
74
|
+
async function collectText(provider, prompt, maxTokens) {
|
|
75
|
+
let text = "";
|
|
76
|
+
// Reasoning must be off: a thinking model would burn the completion cap on
|
|
77
|
+
// <think> before emitting any summary content (observed on qwen3.5-9b).
|
|
78
|
+
// 2x headroom over the target: token estimates are heuristics, and the
|
|
79
|
+
// clamp below enforces the real cap no matter what the model returns.
|
|
80
|
+
for await (const chunk of provider.chat([{ role: "user", content: prompt }], undefined, undefined, {
|
|
81
|
+
maxTokens: maxTokens * 2,
|
|
82
|
+
reasoningEffort: "none",
|
|
83
|
+
})) {
|
|
84
|
+
if (chunk.type === "text" && chunk.content)
|
|
85
|
+
text += chunk.content;
|
|
86
|
+
}
|
|
87
|
+
return text.trim();
|
|
88
|
+
}
|
|
89
|
+
function truncateToTokens(content, maxTokens) {
|
|
90
|
+
const maxChars = Math.max(200, maxTokens * CHARS_PER_TOKEN);
|
|
91
|
+
if (content.length <= maxChars)
|
|
92
|
+
return content;
|
|
93
|
+
let cut = content.slice(0, maxChars);
|
|
94
|
+
const lastBreak = cut.lastIndexOf("\n");
|
|
95
|
+
if (lastBreak > maxChars * 0.5)
|
|
96
|
+
cut = cut.slice(0, lastBreak);
|
|
97
|
+
return `${cut.trimEnd()}\n\n[...truncated — read the full file for details...]`;
|
|
98
|
+
}
|
|
99
|
+
/** One-line first-line label for warnings/logs. */
|
|
100
|
+
function blockLabel(content) {
|
|
101
|
+
const first = content.split("\n")[0].trim();
|
|
102
|
+
return first.length > 60 ? `${first.slice(0, 57)}...` : first;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Summarize (or, failing that, truncate) prompt blocks that exceeded the
|
|
106
|
+
* system-prompt budget so the project instructions / project map still reach
|
|
107
|
+
* the model. Summaries are cached on disk keyed by kind + content + budget.
|
|
108
|
+
* Blocks are resolved in KIND_ORDER priority; the first gets the full
|
|
109
|
+
* remaining budget, later blocks get whatever is left.
|
|
110
|
+
*/
|
|
111
|
+
export async function resolvePromptOverflow(opts) {
|
|
112
|
+
const { overflow, includedTokens, systemBudget, provider, cacheDir, logger } = opts;
|
|
113
|
+
const sorted = [...overflow].sort((a, b) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind));
|
|
114
|
+
const replacements = [];
|
|
115
|
+
const warnings = [];
|
|
116
|
+
let used = includedTokens;
|
|
117
|
+
let unresolved = [];
|
|
118
|
+
// The bootstrap dry-run only warns that blocks "will be summarized before the
|
|
119
|
+
// first run". Without a notice here the first prompt appears to hang while
|
|
120
|
+
// the model rewrites AGENTS.md / the project map — surface it.
|
|
121
|
+
if (sorted.length > 0) {
|
|
122
|
+
logger?.warn(t("prompt.overflow.compressing", { count: String(sorted.length) }));
|
|
123
|
+
}
|
|
124
|
+
for (const block of sorted) {
|
|
125
|
+
const kind = block.kind;
|
|
126
|
+
const maxTokens = systemBudget - used - HINT_BLOCK_TOKENS;
|
|
127
|
+
if (maxTokens < 100) {
|
|
128
|
+
unresolved.push(kind);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let mode = "summary";
|
|
132
|
+
let text = "";
|
|
133
|
+
const key = cacheKey(kind, block.content, maxTokens);
|
|
134
|
+
const cached = readCache(cacheDir, key);
|
|
135
|
+
if (cached) {
|
|
136
|
+
text = cached.text;
|
|
137
|
+
mode = cached.mode;
|
|
138
|
+
logger?.debug(`Prompt overflow: cache hit for ${kind} (${key}, ${mode})`);
|
|
139
|
+
}
|
|
140
|
+
else if (provider) {
|
|
141
|
+
const targetChars = Math.floor(maxTokens * (CHARS_PER_TOKEN - 1));
|
|
142
|
+
logger?.warn(t("prompt.overflow.summarizing", {
|
|
143
|
+
label: blockLabel(block.content),
|
|
144
|
+
original: String(block.estimatedTokens),
|
|
145
|
+
budget: String(maxTokens),
|
|
146
|
+
}));
|
|
147
|
+
try {
|
|
148
|
+
const prompt = [
|
|
149
|
+
`Summarize the following ${KIND_LABEL[kind]} document.`,
|
|
150
|
+
// 3 chars/token (not 4): non-Latin text (Cyrillic etc.) is ~2-3 chars
|
|
151
|
+
// per token, so a 4x target lands the summary over the clamp.
|
|
152
|
+
`Target length: at most ${targetChars} characters (~${maxTokens} tokens). Be strict about it.`,
|
|
153
|
+
`Preserve concrete facts: file paths, commands, rules, naming conventions, workflow steps.`,
|
|
154
|
+
`Keep the structure (headings / bullet lists). Output ONLY the summary text.`,
|
|
155
|
+
``,
|
|
156
|
+
`--- DOCUMENT START ---`,
|
|
157
|
+
block.content,
|
|
158
|
+
`--- DOCUMENT END ---`,
|
|
159
|
+
].join("\n");
|
|
160
|
+
text = await collectText(provider, prompt, maxTokens);
|
|
161
|
+
// Small models routinely ignore length instructions (observed: the 9B
|
|
162
|
+
// model produced ~4700 chars against a 3170-char target). One
|
|
163
|
+
// self-compression retry on the overshoot — the shorter input makes
|
|
164
|
+
// compliance much likelier — before falling back to the hard clamp.
|
|
165
|
+
let retries = 0;
|
|
166
|
+
while (text && estimateTokens(text) > maxTokens && retries < 1) {
|
|
167
|
+
retries++;
|
|
168
|
+
logger?.debug(`Prompt overflow: ${kind} summary overshot (${estimateTokens(text)} > ${maxTokens} tok), compressing (retry ${retries})`);
|
|
169
|
+
text = await collectText(provider, [
|
|
170
|
+
`Compress the following text to at most ${targetChars} characters.`,
|
|
171
|
+
`Keep the structure (headings / bullet lists) and all concrete facts: file paths, commands, rules.`,
|
|
172
|
+
`Output ONLY the compressed text.`,
|
|
173
|
+
``,
|
|
174
|
+
`--- TEXT START ---`,
|
|
175
|
+
text,
|
|
176
|
+
`--- TEXT END ---`,
|
|
177
|
+
].join("\n"), maxTokens);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
logger?.warn(t("prompt.overflow.summarize_failed", {
|
|
182
|
+
label: blockLabel(block.content),
|
|
183
|
+
error: String(err?.message ?? err),
|
|
184
|
+
}));
|
|
185
|
+
text = "";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!text) {
|
|
189
|
+
// No provider or summarization failed — hard truncation fallback.
|
|
190
|
+
mode = "truncate";
|
|
191
|
+
logger?.warn(t("prompt.overflow.truncating", {
|
|
192
|
+
label: blockLabel(block.content),
|
|
193
|
+
original: String(block.estimatedTokens),
|
|
194
|
+
}));
|
|
195
|
+
text = truncateToTokens(block.content, maxTokens);
|
|
196
|
+
}
|
|
197
|
+
// LLMs overshoot: enforce the cap no matter what produced the text.
|
|
198
|
+
if (estimateTokens(text) > maxTokens) {
|
|
199
|
+
if (mode === "summary") {
|
|
200
|
+
mode = "truncate";
|
|
201
|
+
}
|
|
202
|
+
text = truncateToTokens(text, maxTokens);
|
|
203
|
+
}
|
|
204
|
+
const resolvedTokens = estimateTokens(text);
|
|
205
|
+
used += resolvedTokens;
|
|
206
|
+
writeCache(cacheDir, key, { mode, text });
|
|
207
|
+
replacements.push({
|
|
208
|
+
content: text,
|
|
209
|
+
priority: "high",
|
|
210
|
+
essential: false,
|
|
211
|
+
estimatedTokens: resolvedTokens,
|
|
212
|
+
kind,
|
|
213
|
+
});
|
|
214
|
+
warnings.push({
|
|
215
|
+
kind,
|
|
216
|
+
label: blockLabel(block.content),
|
|
217
|
+
originalTokens: block.estimatedTokens,
|
|
218
|
+
resolvedTokens,
|
|
219
|
+
mode,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const hintBlock = replacements.length > 0
|
|
223
|
+
? {
|
|
224
|
+
content: [
|
|
225
|
+
`[Instructions note] Some project blocks exceeded the system-prompt budget and were compressed:`,
|
|
226
|
+
...warnings.map((w) => `- ${KIND_LABEL[w.kind]}: ${w.originalTokens} tokens → ${w.resolvedTokens} tokens (${w.mode})`),
|
|
227
|
+
...(unresolved.length > 0
|
|
228
|
+
? unresolved.map((k) => `- ${KIND_LABEL[k]}: did not fit at all — read it yourself if needed`)
|
|
229
|
+
: []),
|
|
230
|
+
...warnings
|
|
231
|
+
.filter((w) => KIND_SOURCE_FILE[w.kind])
|
|
232
|
+
.map((w) => `Read the full ${KIND_SOURCE_FILE[w.kind]} with read_file if details are missing.`),
|
|
233
|
+
].join("\n"),
|
|
234
|
+
priority: "high",
|
|
235
|
+
essential: true,
|
|
236
|
+
estimatedTokens: HINT_BLOCK_TOKENS,
|
|
237
|
+
}
|
|
238
|
+
: null;
|
|
239
|
+
return { replacements, hintBlock, warnings };
|
|
240
|
+
}
|
|
241
|
+
export function resolveContextWindowSource(config) {
|
|
242
|
+
const entries = config.provider.entries ?? [];
|
|
243
|
+
const activeLabel = config.provider.active;
|
|
244
|
+
const activeEntry = entries.find((e) => e.label && e.label === activeLabel) ??
|
|
245
|
+
(entries.length === 1 ? entries[0] : undefined);
|
|
246
|
+
if (activeEntry?.contextWindow) {
|
|
247
|
+
return { source: "entry", label: activeEntry.label, value: activeEntry.contextWindow };
|
|
248
|
+
}
|
|
249
|
+
return { source: "global", value: config.contextWindow };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Human-facing hint on how to raise the context window, aware of whether the
|
|
253
|
+
* active value comes from the global config (`mma context <N>`) or from a
|
|
254
|
+
* provider entry override (must be edited in the provider domain file).
|
|
255
|
+
*/
|
|
256
|
+
export function contextWindowHint(config, configDir, recommended) {
|
|
257
|
+
const src = resolveContextWindowSource(config);
|
|
258
|
+
if (src.source === "global") {
|
|
259
|
+
return `run "mma context ${recommended}"`;
|
|
260
|
+
}
|
|
261
|
+
return `edit "${join(configDir, "config", "provider.json")}" → provider.entries[label="${src.label}"].contextWindow = ${recommended}`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Recommend the next standard context size whose system budget (10%)
|
|
265
|
+
* accommodates `neededSystemTokens`. Falls back to the largest known size.
|
|
266
|
+
*/
|
|
267
|
+
export function recommendContextSize(neededSystemTokens) {
|
|
268
|
+
const sizes = [8192, 16384, 32768, 65536, 131072, 262144];
|
|
269
|
+
return (sizes.find((s) => Math.floor(s * 0.1) >= neededSystemTokens) ?? sizes[sizes.length - 1]);
|
|
270
|
+
}
|
|
271
|
+
/** Context window needed so that `systemFraction` of it covers `neededTokens`. */
|
|
272
|
+
export function requiredContextWindow(neededTokens, systemFraction) {
|
|
273
|
+
const fraction = systemFraction > 0 ? systemFraction : 0.1;
|
|
274
|
+
return Math.ceil(neededTokens / fraction);
|
|
275
|
+
}
|
|
276
|
+
/** System-prompt share of the CURRENT window needed to cover `neededTokens`. */
|
|
277
|
+
export function requiredSystemFraction(neededTokens, contextWindow) {
|
|
278
|
+
if (contextWindow <= 0)
|
|
279
|
+
return 1;
|
|
280
|
+
return Math.min(1, Math.ceil((neededTokens / contextWindow) * 100) / 100);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Localized, concrete fix for an oversized system prompt. Instead of only
|
|
284
|
+
* snapping to a coarse standard size (which makes a 32K project jump straight
|
|
285
|
+
* to 131072), it states the raw requirement and both levers: a bigger window
|
|
286
|
+
* or a bigger share of the current window.
|
|
287
|
+
*/
|
|
288
|
+
export function overflowHint(config, configDir, neededTokens) {
|
|
289
|
+
const fraction = config.contextBudget?.systemPrompt ?? 0.1;
|
|
290
|
+
const budget = Math.floor(config.contextWindow * fraction);
|
|
291
|
+
const recommended = recommendContextSize(neededTokens);
|
|
292
|
+
const requiredWindow = requiredContextWindow(neededTokens, fraction);
|
|
293
|
+
const requiredFraction = requiredSystemFraction(neededTokens, config.contextWindow);
|
|
294
|
+
const how = configDir
|
|
295
|
+
? contextWindowHint(config, configDir, recommended)
|
|
296
|
+
: `increase contextWindow (e.g. to ${recommended})`;
|
|
297
|
+
return [
|
|
298
|
+
t("prompt.overflow.hint_needed", {
|
|
299
|
+
needed: neededTokens,
|
|
300
|
+
window: config.contextWindow,
|
|
301
|
+
fraction,
|
|
302
|
+
budget,
|
|
303
|
+
}),
|
|
304
|
+
t("prompt.overflow.hint_window", { required: requiredWindow, recommended, how }),
|
|
305
|
+
t("prompt.overflow.hint_fraction", { fraction: requiredFraction }),
|
|
306
|
+
].join(" ");
|
|
307
|
+
}
|
|
@@ -88,7 +88,7 @@ export class SessionLogger {
|
|
|
88
88
|
const caller = this.getCaller?.();
|
|
89
89
|
this.session?.appendMessage({
|
|
90
90
|
role: "assistant",
|
|
91
|
-
content
|
|
91
|
+
content,
|
|
92
92
|
timestamp: new Date().toISOString(),
|
|
93
93
|
provider: caller?.provider,
|
|
94
94
|
model: caller?.model,
|
|
@@ -117,7 +117,7 @@ export class SessionLogger {
|
|
|
117
117
|
const caller = this.getCaller?.();
|
|
118
118
|
this.session?.appendMessage({
|
|
119
119
|
role: "tool",
|
|
120
|
-
content: sanitizeLogMessage(result.output
|
|
120
|
+
content: sanitizeLogMessage(result.output),
|
|
121
121
|
name: call.name,
|
|
122
122
|
timestamp: new Date().toISOString(),
|
|
123
123
|
provider: caller?.provider,
|
|
@@ -191,6 +191,11 @@ export class SessionLogger {
|
|
|
191
191
|
totalTokens: usage.totalTokens,
|
|
192
192
|
source: usage.source,
|
|
193
193
|
durationMs: usage.durationMs,
|
|
194
|
+
cacheStable: usage.prefix ? Number(usage.prefix.stableRatio.toFixed(4)) : undefined,
|
|
195
|
+
cacheCause: usage.prefix?.cause,
|
|
196
|
+
cachedTokens: usage.cache?.cachedTokens,
|
|
197
|
+
cacheWriteTokens: usage.cache?.cacheWriteTokens,
|
|
198
|
+
cacheSource: usage.cache?.source,
|
|
194
199
|
});
|
|
195
200
|
}
|
|
196
201
|
logError(message) {
|
|
@@ -248,4 +253,31 @@ export class SessionLogger {
|
|
|
248
253
|
skills: data.skills,
|
|
249
254
|
});
|
|
250
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Log a reasoning control event (level set, policy evaluation).
|
|
258
|
+
*/
|
|
259
|
+
logReasoningControl(data) {
|
|
260
|
+
this.session?.appendLog({
|
|
261
|
+
ts: new Date().toISOString(),
|
|
262
|
+
type: "reasoning_control",
|
|
263
|
+
content: `reasoning level: ${data.level} (source: ${data.source ?? "policy"})`,
|
|
264
|
+
iteration: data.iteration,
|
|
265
|
+
level: data.level,
|
|
266
|
+
strategy: data.strategy,
|
|
267
|
+
probePassed: data.probePassed,
|
|
268
|
+
source: data.source,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Log a MoE observability event (moe_plan / moe_subtask / moe_replan /
|
|
273
|
+
* moe_verify). Emitted by runWithMoE and MoEExecutor (Plan 4.2).
|
|
274
|
+
*/
|
|
275
|
+
logMoE(event, data) {
|
|
276
|
+
this.session?.appendLog({
|
|
277
|
+
ts: new Date().toISOString(),
|
|
278
|
+
type: event,
|
|
279
|
+
content: data.detail ?? data.explanation ?? event,
|
|
280
|
+
...data,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
251
283
|
}
|
package/dist/i18n/en.json
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"file.is_directory": "Is a directory, use rmdir: {path}",
|
|
12
12
|
"file.replaced_in": "Replaced in {path}",
|
|
13
13
|
"file.string_not_found": "String not found in file:\n{str}\n\nUse read_file on {path} to see the current content before editing — the target may differ (whitespace, line endings) or was already replaced.",
|
|
14
|
+
"file.edit_args_invalid": "edit_file requires string \"old\" and \"new\" arguments. Received: old={oldType}, new={newType}.",
|
|
15
|
+
"file.read_failed": "Could not read {path}: {error}",
|
|
16
|
+
"file.write_failed": "Could not write {path}: {error}",
|
|
17
|
+
"file.protected_path_ask": "Protected path \"{path}\" (matches {pattern}). Do NOT modify it. Ask the user for explicit confirmation before proceeding and wait for their answer — do not retry around this.",
|
|
14
18
|
"file.moved": "Moved {from} → {to}",
|
|
15
19
|
"file.not_found_short": "Not found: {path}",
|
|
16
20
|
"file.notfound_resolved": "File not found: {path} (resolved to {resolved})\nThe path was joined onto the working directory because it does not exist as given. Run list_dir to find the correct path.",
|
|
@@ -47,6 +51,8 @@
|
|
|
47
51
|
"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",
|
|
48
52
|
"error.llm_provider_stream_error": "Provider returned an error mid-stream: {error}",
|
|
49
53
|
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
54
|
+
"error.context_log_init": "Context log is enabled but could not be initialized: {error}. No context diff will be written.",
|
|
55
|
+
"error.provider_build": "Could not build provider \"{name}\": {error}",
|
|
50
56
|
"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.",
|
|
51
57
|
"env.runtime_old": "Runtime version {version} is below the required engines {engine}.",
|
|
52
58
|
"env.tool_missing": "Tool not found on PATH: {tool}",
|
|
@@ -92,6 +98,8 @@
|
|
|
92
98
|
"tool.failed": "Tool {name} failed: {error}",
|
|
93
99
|
"tool.unknown": "Unknown tool: {name}",
|
|
94
100
|
"tool.blocked": "Blocked by plugin: {plugin}",
|
|
101
|
+
"tool.ui.security_blocked": "Blocked by security policy",
|
|
102
|
+
"tool.ui.protected_path": "Protected path — confirmation needed",
|
|
95
103
|
"tool.blocked_reason": "Blocked by plugin: {plugin}. Reason: {reason}",
|
|
96
104
|
"tool.using": "[{label}]",
|
|
97
105
|
"tool.friendly.write_file": "Writing file",
|
|
@@ -340,6 +348,7 @@
|
|
|
340
348
|
"cli.show_details": "Show session details",
|
|
341
349
|
"cli.delete_session": "Delete a session",
|
|
342
350
|
"cli.first_run": "First run detected. Running setup wizard...",
|
|
351
|
+
"cli.setup_saved_restart": "Settings saved. Restarting with the new configuration...",
|
|
343
352
|
"cli.unknown_cmd": "Unknown command: {name}.",
|
|
344
353
|
"cli.unknown_command": "Unknown command \"{input}\". Did you mean \"{suggestion}\"?",
|
|
345
354
|
"cli.help_hint": "Type /help for available commands.",
|
|
@@ -350,6 +359,8 @@
|
|
|
350
359
|
"cli.exit_on_complete": "Exit immediately when the task is done; interactive tools (question/approve) fail fast instead of blocking on stdin",
|
|
351
360
|
"cli.reasoning_level": "Set reasoning effort level (auto|none|low|medium|high|max)",
|
|
352
361
|
"cli.json": "Output the result as JSON (single-run mode)",
|
|
362
|
+
"cli.context_log": "Write the full LLM context to <session>/context.diff as a git-style diff (first snapshot full, then +/- changes)",
|
|
363
|
+
"cli.verbose": "Show full chat detail (tool bodies, lists); default is quiet",
|
|
353
364
|
"cli.security.description": "Manage security settings",
|
|
354
365
|
"cli.security.status": "Show current security configuration",
|
|
355
366
|
"cli.security.policies": "List available security policies",
|
|
@@ -410,6 +421,11 @@
|
|
|
410
421
|
"repl.reasoning_usage": "Usage: /reasoning [auto|none|low|medium|high|max]",
|
|
411
422
|
"repl.reasoning_level_set": "Reasoning level set to: {level}",
|
|
412
423
|
"repl.reasoning_invalid_level": "Invalid level '{level}'. Valid: {valid}",
|
|
424
|
+
"repl.verbose": "Chat verbosity: quiet, normal, or verbose",
|
|
425
|
+
"repl.verbose_usage": "Usage: /verbose [quiet|normal|verbose]",
|
|
426
|
+
"repl.verbose_set": "Chat verbosity: {level}",
|
|
427
|
+
"repl.verbose_invalid_level": "Invalid level '{level}'. Valid: {valid}",
|
|
428
|
+
"repl.last_action_none": "No recent action to show.",
|
|
413
429
|
"repl.status": "Show agent status",
|
|
414
430
|
"repl.status_usage": "Usage: /status",
|
|
415
431
|
"repl.plugins": "List loaded plugins",
|
|
@@ -441,6 +457,8 @@
|
|
|
441
457
|
"repl.skill_usage": "Usage: /skill [list|loaded|load|unload|search]",
|
|
442
458
|
"repl.agent": "Agent: ",
|
|
443
459
|
"repl.you": "You: ",
|
|
460
|
+
"repl.you_reasoning": "You [{level}]: ",
|
|
461
|
+
"repl.you_reasoning_cont": "You [{level}]… ",
|
|
444
462
|
"repl.interrupt": "Interrupted (Esc)",
|
|
445
463
|
"repl.title": "MMA REPL v{version}",
|
|
446
464
|
"repl.model": "Model:",
|
|
@@ -509,6 +527,7 @@
|
|
|
509
527
|
"setup.title": "\n MMA Setup Wizard v2\n",
|
|
510
528
|
"setup.select_provider": "\n Select provider type:",
|
|
511
529
|
"setup.select_provider_num": "\n Select provider (1-{max})",
|
|
530
|
+
"setup.select_language_num": "\n Select language (1-{max})",
|
|
512
531
|
"setup.scanning": " Scanning local LLM servers...",
|
|
513
532
|
"setup.found_servers": " Found {count} server(s):",
|
|
514
533
|
"setup.no_servers": " No local servers found. Enter URL manually.",
|
|
@@ -533,8 +552,6 @@
|
|
|
533
552
|
"setup.scanning_spinner": "Scanning local LLM servers…",
|
|
534
553
|
"setup.fetching_spinner": "Fetching model list…",
|
|
535
554
|
"setup.testing_spinner": "Testing chat with \"{model}\"…",
|
|
536
|
-
"setup.summary_setting": "Setting",
|
|
537
|
-
"setup.summary_value": "Value",
|
|
538
555
|
"setup.security_header": "\n --- Security ---",
|
|
539
556
|
"setup.security_status_off": " Security: OFF (default — all commands allowed)",
|
|
540
557
|
"setup.security_status_on": " Security: ON (balanced policy by default)",
|
|
@@ -578,6 +595,9 @@
|
|
|
578
595
|
"exec.audit_incomplete": "❗ Final audit incomplete: {summary}. Task is NOT finished. Remaining steps: {steps}. Resolve them - pick ONE path: (a) if the step's deliverable already exists on disk, mark it done (plan update step=N status=done); (b) if the step is no longer needed, mark it skipped; (c) if the plan drifted from reality (wrong file names, changed approach), re-plan to align it. Do NOT retry plan update repeatedly without changing anything.",
|
|
579
596
|
"exec.mass_edit_warning": "⚠️ Plan affects {count} files — review the full list before proceeding.",
|
|
580
597
|
"exec.escalation": "\n\n⚠️ Agent stuck on step {stepId} ({description}). Escalating to user — please provide guidance.",
|
|
598
|
+
"exec.escalation_directive": "You have been stuck for several attempts. Stop repeating the same action and pick ONE of these: (1) switch to a fundamentally different approach, (2) search the exact error text with web_search (or the error-search tool), (3) ask the user a short clarifying question, or (4) if the step cannot be completed, use plan update step=<n> status=skipped with a note (or re-plan). Explain your choice briefly.",
|
|
599
|
+
"exec.test_churn_nudge": "You have edited the test/spec file \"{file}\" several times in a row without changing any production code. If the tests fail, fix the PRODUCTION code under test (not the test) unless the test itself is genuinely wrong — and if so, explain why. If you intended test-first, now write the implementation.",
|
|
600
|
+
"exec.script_workaround_nudge": "A security restriction already blocked one of your commands. Writing a shell script (.bat/.cmd/.sh/.ps1) to run it another way does NOT lift the restriction and usually just fails. Instead: use write_file for file output, run one plain command per call, pass \"workdir\" instead of \"cd\", or ask the user.",
|
|
581
601
|
"exec.hints": "\n[Hints]\n{hints}",
|
|
582
602
|
"exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
|
|
583
603
|
"exec.forbidden_cmd": "STOP using \"{cmd}\" via the bash tool — it is not a native Windows cmd.exe command and has failed repeatedly this session. Use the dedicated tool instead: grep → the grep tool, ls/dir → list_dir, find → glob, rm → delete_file, sed → edit_file, touch → write_file, which → `where`, cp/mv → move_file, diff → read_file. Do NOT call bash for this purpose again.",
|
|
@@ -585,6 +605,7 @@
|
|
|
585
605
|
"exec.error_search_failed": "Web search returned nothing for \"{query}\".",
|
|
586
606
|
"exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
|
|
587
607
|
"exec.npm_exec_hint": "\"could not determine executable to run\" — no \"bin\" for that package/script. Use \"npm run <script>\" (script must exist in package.json) or \"bunx <pkg>\" for a package that declares a bin.",
|
|
608
|
+
"exec.interactive_hint": "the command needed interactive input but the shell has no TTY (prompt aborted). Re-run it non-interactively — pass every required flag/argument (e.g. `bun create vite <name> --template react-ts` instead of a bare interactive scaffold).",
|
|
588
609
|
"exec.hidden_tool_hint": "\"{tool}\" is not a shell command — it is an MMA tool that is currently hidden. Call the enable_tools tool with tags [\"shell\"] to unlock it; it becomes available on the next iteration.",
|
|
589
610
|
"exec.task_reminder": "Task: {task}. Continue making progress — do not repeat failed actions.",
|
|
590
611
|
"hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
|
|
@@ -629,10 +650,18 @@
|
|
|
629
650
|
"migration.dir_bak": "- .mma/ → .mma.bak/ ({count} files)",
|
|
630
651
|
"migration.migrated": "Migrated:",
|
|
631
652
|
"migration.not_needed": "No migration needed",
|
|
632
|
-
"ui.error_prefix": "
|
|
653
|
+
"ui.error_prefix": "✗ ",
|
|
633
654
|
"ui.success_prefix": "✓ ",
|
|
634
655
|
"ui.warning_prefix": "⚠ ",
|
|
635
656
|
"ui.thinking": "Thinking…",
|
|
657
|
+
"ui.thinking_label": "thinking",
|
|
658
|
+
"ui.thinking_label_1": "thinking",
|
|
659
|
+
"ui.thinking_label_2": "working it out",
|
|
660
|
+
"ui.thinking_label_3": "considering",
|
|
661
|
+
"ui.action_fallback": "Action",
|
|
662
|
+
"ui.actions_summary": "{count} actions",
|
|
663
|
+
"ui.plan_update": "step {step} → {status}",
|
|
664
|
+
"ui.todo_action": "{action} · {count}",
|
|
636
665
|
"ui.tool_running": "{tool}…",
|
|
637
666
|
"ui.step_context": "step {id}: {desc}",
|
|
638
667
|
"indexer.map_header": "Project map",
|
|
@@ -735,6 +764,7 @@
|
|
|
735
764
|
"cli.usage_account": "Account: {credits} purchased · {used} used",
|
|
736
765
|
"cli.usage_empty": "No balance information returned.",
|
|
737
766
|
"repl.ctrl_c_interrupt": "\n[Ctrl+C] Stopping agent... (press again to force)",
|
|
767
|
+
"repl.force_exit": "\nForcing exit — background cleanup may be incomplete.",
|
|
738
768
|
"exec.stop_directive": "STOP. Step {stepId} (\"{description}\") took {iterations} iterations with no progress. DO NOT continue this step. Immediately call: plan update step={stepId} status=done (if code works despite warnings) OR plan update step={stepId} status=skipped note=\"reason\". Do NOT make any other tool calls before updating the plan.",
|
|
739
769
|
"exec.test_runner_fail": "[test-runner] {framework}: {failed} test(s) FAILING, {passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.",
|
|
740
770
|
"exec.test_runner_pass": "[test-runner] {framework}: all {passed} test(s) passing.",
|
|
@@ -753,6 +783,11 @@
|
|
|
753
783
|
"moe.execution_complete": "Execution complete: {succeeded}/{total} succeeded",
|
|
754
784
|
"moe.cycles_exhausted": "Max re-plan cycles ({max}) reached — returning partial results",
|
|
755
785
|
"moe.partial_result": "Partial result",
|
|
786
|
+
"moe.planning": "🤖 Planning with MoE mode...\n",
|
|
787
|
+
"moe.plan_fallback": "⚠️ Plan validation failed. Falling back to single-agent mode.\n",
|
|
788
|
+
"moe.plan_autofixed": "🔧 Auto-fixed {count} plan issues.\n",
|
|
789
|
+
"moe.plan_failed": "MoE plan failed: {error} — falling back to single-agent",
|
|
790
|
+
"moe.no_orchestrator": "MoE enabled but no orchestrator model configured — falling back to single-agent. Set orchestrator.model to activate MoE.",
|
|
756
791
|
"moe.scope_approved": "🔓 Scope expanded for \"{subtask}\": {files}",
|
|
757
792
|
"moe.scope_rejected": "⛔ Scope request rejected for \"{subtask}\"",
|
|
758
793
|
"prompt.overflow.compressing": "System prompt overflow: compressing {count} block(s) before the first run — this may take a while on a local model.",
|
|
@@ -762,9 +797,9 @@
|
|
|
762
797
|
"prompt.overflow.hint_needed": "the prompt needs ~{needed} system tokens (current budget: {window} × {fraction} = {budget})",
|
|
763
798
|
"prompt.overflow.hint_window": "raise contextWindow to at least {required} (standard size {recommended}) — {how}",
|
|
764
799
|
"prompt.overflow.hint_fraction": "or keep the window and raise contextBudget.systemPrompt to ~{fraction} (run: mma context --system {fraction})",
|
|
765
|
-
"prompt.overflow.exceeded": "Prompt overflow:
|
|
800
|
+
"prompt.overflow.exceeded": "Prompt overflow: the system prompt needs ~{needed} tok (budget {budget} tok); \"{label}\" ({original} tok) was {mode} to {resolved} tok. To fit everything fully, {hint}.",
|
|
766
801
|
"prompt.overflow.failed": "Prompt overflow resolution failed: {error} — oversized blocks will be dropped.",
|
|
767
|
-
"prompt.overflow.startup": "Startup check: {
|
|
802
|
+
"prompt.overflow.startup": "Startup check: the system prompt needs ~{needed} tok (budget: {budget} tok); {block} ({original} tok) will be summarized/truncated before the first run. To include everything fully, {hint}.",
|
|
768
803
|
"tool.session_info.no_active": "No active session",
|
|
769
804
|
"tool.session_info.result": "Session: \"{name}\" ({id})\nModel: {model}\nProvider: {provider}\nContext window: {contextWindow} tokens\nMessages: {messages}\nCreated: {createdAt}\nUpdated: {updatedAt}",
|
|
770
805
|
"tool.session_info.context": "Context used: {used} / {budget} tokens ({percent}%)",
|