micro-models-agent 0.43.3 → 0.45.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/README.md +312 -312
- package/bin/mma.mjs +41 -41
- package/dist/main.js +333 -66
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -333
- package/dist/cli/completer.js +0 -168
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -140
- package/dist/cli/repl-commands.js +0 -633
- package/dist/cli/repl.js +0 -486
- package/dist/cli/security-commands.js +0 -166
- package/dist/cli/setup.js +0 -249
- package/dist/config/config.js +0 -202
- package/dist/config/defaults.js +0 -100
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -200
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -110
- package/dist/core/agent.js +0 -695
- package/dist/core/bootstrap.js +0 -337
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -55
- package/dist/core/session-logger.js +0 -155
- package/dist/core/types.js +0 -1
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -525
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -525
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -144
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -353
- package/dist/llm/orchestrator.js +0 -194
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -143
- package/dist/logger/file-log.js +0 -151
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/browser/actions.js +0 -46
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/index.js +0 -5
- package/dist/modules/browser/module.js +0 -28
- package/dist/modules/browser/session.js +0 -335
- package/dist/modules/browser/snapshot.js +0 -114
- package/dist/modules/browser/types.js +0 -9
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -349
- package/dist/modules/execution/auditor.js +0 -66
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -779
- package/dist/modules/execution/moe-executor.js +0 -266
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -159
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -85
- package/dist/modules/execution/stuck-detector.js +0 -347
- package/dist/modules/execution/tracker.js +0 -67
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -178
- package/dist/modules/hallucination/confidence.js +0 -59
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -46
- package/dist/modules/hallucination/factual.js +0 -190
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -38
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -192
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -399
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -146
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -48
- package/dist/modules/memory/search.js +0 -40
- package/dist/modules/memory/store.js +0 -69
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -53
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -226
- package/dist/modules/plugins/builtin/notify.js +0 -8
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -28
- package/dist/modules/plugins/manager.js +0 -161
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -238
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/registry.js +0 -45
- package/dist/modules/security/audit-log.js +0 -136
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -211
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -97
- package/dist/modules/security/encryption.js +0 -240
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -79
- package/dist/modules/security/path-validator.js +0 -209
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -547
- package/dist/modules/security/session-encryption.js +0 -210
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -172
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -228
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/module.js +0 -130
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -32
- package/dist/modules/updater/index.js +0 -1
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -32
- package/dist/tools/attach-image.js +0 -89
- package/dist/tools/bash.js +0 -337
- package/dist/tools/browser.js +0 -97
- package/dist/tools/create-dir.js +0 -55
- package/dist/tools/delete-file.js +0 -62
- package/dist/tools/edit-file.js +0 -79
- package/dist/tools/executor.js +0 -145
- package/dist/tools/file-info.js +0 -45
- package/dist/tools/filter-tools.js +0 -10
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -86
- package/dist/tools/index.js +0 -67
- package/dist/tools/list-dir.js +0 -47
- package/dist/tools/load-skill.js +0 -44
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -85
- package/dist/tools/path-utils.js +0 -51
- package/dist/tools/pipeline-run.js +0 -144
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -29
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -41
- package/dist/tools/question.js +0 -142
- package/dist/tools/read-file.js +0 -83
- package/dist/tools/recall.js +0 -110
- package/dist/tools/registry.js +0 -36
- package/dist/tools/remember.js +0 -67
- package/dist/tools/scope-check.js +0 -30
- package/dist/tools/search-history.js +0 -84
- package/dist/tools/subagent.js +0 -151
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -86
- package/dist/tools/web-fetch.js +0 -98
- package/dist/tools/web-search.js +0 -78
- package/dist/tools/write-file.js +0 -83
- package/dist/ui/box.js +0 -81
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/renderer.js +0 -204
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
package/dist/core/agent.js
DELETED
|
@@ -1,695 +0,0 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { t } from "../i18n/index";
|
|
3
|
-
import { pc } from "../ui/colors";
|
|
4
|
-
import { PromptBuilder } from "./prompt-builder";
|
|
5
|
-
import { processRegistry } from "../modules/processes";
|
|
6
|
-
import { SessionLogger } from "./session-logger";
|
|
7
|
-
import { runWithMoE } from "./agent-moe";
|
|
8
|
-
import { MemoryStore } from "../modules/memory/store";
|
|
9
|
-
import { StepVerifier } from "../modules/execution/verifier";
|
|
10
|
-
const TOOL_RESULT_MAX_TOKENS_RATIO = 0.3;
|
|
11
|
-
const TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000;
|
|
12
|
-
const QUALITY_TRIGGER_THRESHOLD = 40;
|
|
13
|
-
/** True when the text looks like a raw JSON tool payload (garbage to display). */
|
|
14
|
-
function isToolCallJson(text) {
|
|
15
|
-
const trimmed = text.trim();
|
|
16
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
17
|
-
try {
|
|
18
|
-
JSON.parse(trimmed);
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
export class Agent {
|
|
28
|
-
deps;
|
|
29
|
-
systemPromptAdded = false;
|
|
30
|
-
shutdownRequested = false;
|
|
31
|
-
abortController = null;
|
|
32
|
-
lastCompactionShown = 0;
|
|
33
|
-
constructor(deps) {
|
|
34
|
-
this.deps = deps;
|
|
35
|
-
}
|
|
36
|
-
/** Expose context manager for REPL image attachment and other direct access. */
|
|
37
|
-
get contextManager() {
|
|
38
|
-
return this.deps.contextManager;
|
|
39
|
-
}
|
|
40
|
-
setScope() {
|
|
41
|
-
if (this.deps.scope) {
|
|
42
|
-
this.deps.toolExecutor.setScope(this.deps.scope);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
buildSystemPrompt() {
|
|
46
|
-
const systemBudget = Math.floor(this.deps.config.contextWindow *
|
|
47
|
-
this.deps.config.contextBudget.systemPrompt);
|
|
48
|
-
const builder = new PromptBuilder(systemBudget);
|
|
49
|
-
builder.addBlocks(this.deps.promptBlocks);
|
|
50
|
-
const dynamic = this.deps.getDynamicPromptBlocks?.() ?? [];
|
|
51
|
-
if (dynamic.length > 0) {
|
|
52
|
-
builder.addBlocks(dynamic);
|
|
53
|
-
}
|
|
54
|
-
const pluginBlocks = (this.deps.pluginManager.runOnBuildPrompt?.() ?? []).flatMap((content) => content && content.trim() !== ""
|
|
55
|
-
? [
|
|
56
|
-
{
|
|
57
|
-
content,
|
|
58
|
-
priority: "low",
|
|
59
|
-
essential: false,
|
|
60
|
-
estimatedTokens: this.deps.llmProvider.countTokens(content),
|
|
61
|
-
},
|
|
62
|
-
]
|
|
63
|
-
: []);
|
|
64
|
-
if (pluginBlocks.length > 0) {
|
|
65
|
-
builder.addBlocks(pluginBlocks);
|
|
66
|
-
}
|
|
67
|
-
return builder.build();
|
|
68
|
-
}
|
|
69
|
-
getSystemPromptInfo() {
|
|
70
|
-
const { prompt, excluded } = this.buildSystemPrompt();
|
|
71
|
-
const tokenCount = this.deps.llmProvider.countTokens(prompt);
|
|
72
|
-
return { text: prompt, tokenCount, excluded };
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Some OpenAI-compatible backends (llama.cpp) omit `usage` from responses,
|
|
76
|
-
* leaving apiPromptTokens/apiCompletionTokens at 0. Fall back to local
|
|
77
|
-
* estimates so JSON results still carry meaningful token metrics.
|
|
78
|
-
*/
|
|
79
|
-
resolveUsageTokens(apiPromptTokens, apiCompletionTokens, estimatedPromptTokens, completionChars) {
|
|
80
|
-
if (apiPromptTokens > 0 || apiCompletionTokens > 0) {
|
|
81
|
-
return {
|
|
82
|
-
prompt: apiPromptTokens,
|
|
83
|
-
completion: apiCompletionTokens,
|
|
84
|
-
total: apiPromptTokens + apiCompletionTokens,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
// ~4 chars per token is a reasonable heuristic when the backend gives
|
|
88
|
-
// us nothing (matches the pre-tiktoken fallback elsewhere in the code).
|
|
89
|
-
const prompt = Math.max(1, estimatedPromptTokens);
|
|
90
|
-
const completion = Math.max(0, Math.ceil(completionChars / 4));
|
|
91
|
-
return { prompt, completion, total: prompt + completion };
|
|
92
|
-
}
|
|
93
|
-
refreshSystemPrompt() {
|
|
94
|
-
const { prompt } = this.buildSystemPrompt();
|
|
95
|
-
const current = this.deps.contextManager
|
|
96
|
-
.getActiveHistory()
|
|
97
|
-
.find((m) => m.role === "system");
|
|
98
|
-
if (!current || current.content !== prompt) {
|
|
99
|
-
this.deps.contextManager.updateSystemPrompt?.(prompt);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
truncateToolOutput(output, budget, currentTokens) {
|
|
103
|
-
const remainingBudget = budget.history - currentTokens;
|
|
104
|
-
const maxCharsByBudget = Math.floor(remainingBudget * 0.5 * 2);
|
|
105
|
-
const maxCharsByRatio = Math.floor(budget.history * TOOL_RESULT_MAX_TOKENS_RATIO * 2);
|
|
106
|
-
const maxChars = Math.min(maxCharsByBudget, maxCharsByRatio, TOOL_RESULT_ABSOLUTE_MAX_CHARS);
|
|
107
|
-
if (output.length <= maxChars)
|
|
108
|
-
return output;
|
|
109
|
-
const truncated = output.slice(0, maxChars);
|
|
110
|
-
const removedChars = output.length - maxChars;
|
|
111
|
-
return (truncated +
|
|
112
|
-
`\n\n${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`);
|
|
113
|
-
}
|
|
114
|
-
emitPhase(iteration, phase, onPhase) {
|
|
115
|
-
this.deps.pluginManager.runOnPhase?.({ iteration, logger: this.deps.logger }, phase);
|
|
116
|
-
onPhase?.(phase);
|
|
117
|
-
}
|
|
118
|
-
async run(input, onChunk, onMeta, onTool, onPhase) {
|
|
119
|
-
this.setScope();
|
|
120
|
-
const { config, llmProvider, toolExecutor, pluginManager, contextManager, logger, sessionManager, baseDir, } = this.deps;
|
|
121
|
-
const slog = new SessionLogger(sessionManager, logger);
|
|
122
|
-
if (sessionManager && !sessionManager.getActive()) {
|
|
123
|
-
sessionManager.create();
|
|
124
|
-
logger.debug(`Session started: ${sessionManager.getActive()}`);
|
|
125
|
-
}
|
|
126
|
-
if (!this.systemPromptAdded &&
|
|
127
|
-
!contextManager.getActiveHistory().some((m) => m.role === "system")) {
|
|
128
|
-
const { prompt: systemPrompt, excluded } = this.buildSystemPrompt();
|
|
129
|
-
contextManager.addMessage({ role: "system", content: systemPrompt });
|
|
130
|
-
this.systemPromptAdded = true;
|
|
131
|
-
slog.logSystem(systemPrompt.slice(0, 2000));
|
|
132
|
-
if (excluded.length > 0) {
|
|
133
|
-
slog.logSystem(`[Excluded prompt blocks: ${excluded.length}]`);
|
|
134
|
-
}
|
|
135
|
-
pluginManager.runOnSessionStart({
|
|
136
|
-
logger,
|
|
137
|
-
sessionManager: sessionManager?.getActiveMeta(),
|
|
138
|
-
});
|
|
139
|
-
if (config.session?.baselineCheck !== false) {
|
|
140
|
-
const verifier = new StepVerifier(baseDir);
|
|
141
|
-
verifier
|
|
142
|
-
.runTypeCheck()
|
|
143
|
-
.then((tc) => {
|
|
144
|
-
if (!tc.passed) {
|
|
145
|
-
logger.warn(`Baseline typecheck has issues: ${tc.message?.slice(0, 500)}`);
|
|
146
|
-
onMeta?.(pc.yellow(`\n⚠ Baseline typecheck has issues\n`));
|
|
147
|
-
}
|
|
148
|
-
})
|
|
149
|
-
.catch(() => { });
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
contextManager.addMessage({ role: "user", content: input });
|
|
153
|
-
slog.logUser(input);
|
|
154
|
-
if (config.moe?.enabled) {
|
|
155
|
-
return runWithMoE({
|
|
156
|
-
config,
|
|
157
|
-
llmProvider,
|
|
158
|
-
toolExecutor,
|
|
159
|
-
logger,
|
|
160
|
-
baseDir: this.deps.baseDir,
|
|
161
|
-
}, input, () => this.executeSingleAgentLoop(input, onChunk, onMeta, onTool, onPhase), { onMeta, onTool, onPhase });
|
|
162
|
-
}
|
|
163
|
-
return this.executeSingleAgentLoop(input, onChunk, onMeta, onTool, onPhase);
|
|
164
|
-
}
|
|
165
|
-
async executeSingleAgentLoop(input, onChunk, onMeta, onTool, onPhase) {
|
|
166
|
-
const { config, llmProvider, toolExecutor, pluginManager, contextManager, hallucinationDetector, logger, sessionManager, baseDir, } = this.deps;
|
|
167
|
-
const slog = new SessionLogger(sessionManager, logger);
|
|
168
|
-
this.abortController = new AbortController();
|
|
169
|
-
let iteration = 0;
|
|
170
|
-
let lastText = "";
|
|
171
|
-
let hallucinationRetries = 0;
|
|
172
|
-
let lastToolSignature = "";
|
|
173
|
-
let apiPromptTokens = 0;
|
|
174
|
-
let apiCompletionTokens = 0;
|
|
175
|
-
let apiCompletionChars = 0;
|
|
176
|
-
const MAX_HALLUCINATION_RETRIES = 3;
|
|
177
|
-
let consecutiveToolFailures = 0;
|
|
178
|
-
const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
|
|
179
|
-
let auditRetries = 0;
|
|
180
|
-
const MAX_AUDIT_RETRIES = 3;
|
|
181
|
-
let emptyResponseRetries = 0;
|
|
182
|
-
const MAX_EMPTY_RESPONSE_RETRIES = 2;
|
|
183
|
-
let emptyResponseExhausted = false;
|
|
184
|
-
let repeatedToolCount = 0;
|
|
185
|
-
const MAX_REPEATED_TOOL_CALLS = 2;
|
|
186
|
-
// Account for tool definitions in context budget (they're sent via body.tools, not messages)
|
|
187
|
-
const allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
|
|
188
|
-
const toolTokenEstimate = allToolsForBudget.reduce((sum, t) => sum +
|
|
189
|
-
Math.ceil((t.description.length + JSON.stringify(t.parameters).length) / 4), 0);
|
|
190
|
-
contextManager.setToolTokens(toolTokenEstimate);
|
|
191
|
-
while (iteration < config.maxToolIterations && !this.shutdownRequested) {
|
|
192
|
-
iteration++;
|
|
193
|
-
contextManager.noteIteration();
|
|
194
|
-
pluginManager.runOnBeforeThink({
|
|
195
|
-
iteration,
|
|
196
|
-
logger,
|
|
197
|
-
lastUserMessage: input,
|
|
198
|
-
contextManager,
|
|
199
|
-
onMeta,
|
|
200
|
-
sessionLog: {
|
|
201
|
-
plan: (event, detail, iter) => slog.logPlan(event, detail, iter),
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
if (contextManager.needsCompaction()) {
|
|
205
|
-
contextManager.compact();
|
|
206
|
-
logger.debug("Context compacted");
|
|
207
|
-
slog.logCompaction(`regular compaction, iteration ${iteration}`, iteration);
|
|
208
|
-
}
|
|
209
|
-
const currentTokens = contextManager.getEstimatedTokens();
|
|
210
|
-
const budget = contextManager.getBudget();
|
|
211
|
-
const quality = contextManager.getQuality();
|
|
212
|
-
if (quality < QUALITY_TRIGGER_THRESHOLD &&
|
|
213
|
-
contextManager.getCompactionCount() > 0) {
|
|
214
|
-
contextManager.compact();
|
|
215
|
-
logger.warn(`Low context quality (${quality}%) — forced compaction`);
|
|
216
|
-
slog.logCompaction(`quality-triggered compaction (${quality}% < ${QUALITY_TRIGGER_THRESHOLD}%), iteration ${iteration}`, iteration, currentTokens, budget.history);
|
|
217
|
-
}
|
|
218
|
-
if (currentTokens > budget.history) {
|
|
219
|
-
contextManager.compact();
|
|
220
|
-
logger.warn(`Context overflow (${currentTokens} > ${budget.history}), forced compaction`);
|
|
221
|
-
slog.logCompaction(`forced compaction (${currentTokens} > ${budget.history}), iteration ${iteration}`, iteration, currentTokens, budget.history);
|
|
222
|
-
}
|
|
223
|
-
this.refreshSystemPrompt();
|
|
224
|
-
const history = contextManager.getActiveHistory();
|
|
225
|
-
slog.logToolDefs(allToolsForBudget.length, allToolsForBudget.map((t) => t.name), iteration);
|
|
226
|
-
let textContent = "";
|
|
227
|
-
let reasoningContent = "";
|
|
228
|
-
const toolCalls = [];
|
|
229
|
-
let sawToolCall = false;
|
|
230
|
-
let emittedReasoning = false;
|
|
231
|
-
const textChunks = [];
|
|
232
|
-
this.emitPhase(iteration, "thinking", onPhase);
|
|
233
|
-
const llmStart = Date.now();
|
|
234
|
-
logger.logLLMRequest(config.model, history.length, input, "agent");
|
|
235
|
-
try {
|
|
236
|
-
for await (const chunk of llmProvider.chat(history, allToolsForBudget, this.abortController?.signal)) {
|
|
237
|
-
if (this.shutdownRequested)
|
|
238
|
-
break;
|
|
239
|
-
if (chunk.type === "text" && chunk.content) {
|
|
240
|
-
if (emittedReasoning && !textContent) {
|
|
241
|
-
onMeta?.("\n\n");
|
|
242
|
-
}
|
|
243
|
-
textContent += chunk.content;
|
|
244
|
-
textChunks.push(chunk.content);
|
|
245
|
-
}
|
|
246
|
-
if (chunk.type === "reasoning" && chunk.content) {
|
|
247
|
-
reasoningContent += chunk.content;
|
|
248
|
-
if (config.showReasoning) {
|
|
249
|
-
const metaOut = pluginManager.runOnMeta({ iteration, logger }, chunk.content);
|
|
250
|
-
if (metaOut) {
|
|
251
|
-
onMeta?.(pc.dim(metaOut));
|
|
252
|
-
}
|
|
253
|
-
emittedReasoning = true;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (chunk.type === "tool_call" && chunk.toolCall) {
|
|
257
|
-
sawToolCall = true;
|
|
258
|
-
let parsedArgs;
|
|
259
|
-
try {
|
|
260
|
-
parsedArgs = JSON.parse(chunk.toolCall.arguments);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
parsedArgs = {};
|
|
264
|
-
}
|
|
265
|
-
toolCalls.push({
|
|
266
|
-
id: chunk.toolCall.id,
|
|
267
|
-
name: chunk.toolCall.name,
|
|
268
|
-
arguments: parsedArgs,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
if (chunk.type === "done" && chunk.usage) {
|
|
272
|
-
apiPromptTokens += chunk.usage.promptTokens;
|
|
273
|
-
apiCompletionTokens += chunk.usage.completionTokens;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch (err) {
|
|
278
|
-
if (this.shutdownRequested || err?.name === "AbortError") {
|
|
279
|
-
logger.info("LLM call aborted (interrupt)");
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
logger.logLLMResponse(config.model, textContent.length, Date.now() - llmStart, err.message, "agent");
|
|
283
|
-
logger.error(`LLM call failed: ${err.message}`);
|
|
284
|
-
slog.logError(err.message);
|
|
285
|
-
pluginManager.runOnError({ iteration, logger }, err);
|
|
286
|
-
return {
|
|
287
|
-
success: false,
|
|
288
|
-
text: lastText,
|
|
289
|
-
error: t("error.llm", { message: err.message }),
|
|
290
|
-
iterationCount: iteration,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
finally {
|
|
294
|
-
this.emitPhase(iteration, "done", onPhase);
|
|
295
|
-
}
|
|
296
|
-
// Track response length so token metrics stay meaningful even when
|
|
297
|
-
// the backend omits `usage` from the response.
|
|
298
|
-
apiCompletionChars += (textContent || reasoningContent).length;
|
|
299
|
-
logger.logLLMResponse(config.model, (textContent || reasoningContent).length, Date.now() - llmStart, undefined, "agent");
|
|
300
|
-
if (this.shutdownRequested) {
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
// Show the model's commentary text. When a tool call accompanies the
|
|
304
|
-
// response, keep the text too (opencode-like narration), unless it is
|
|
305
|
-
// a raw JSON payload that small models sometimes emit instead of
|
|
306
|
-
// describing the call. `toolComments: false` restores the old behavior
|
|
307
|
-
// of suppressing text next to a tool call.
|
|
308
|
-
const toolComments = this.deps.config.ui?.toolComments ?? true;
|
|
309
|
-
const showText = textChunks.length > 0 &&
|
|
310
|
-
(!sawToolCall || (toolComments && !isToolCallJson(textContent)));
|
|
311
|
-
if (showText) {
|
|
312
|
-
for (const chunk of textChunks) {
|
|
313
|
-
const textOut = pluginManager.runOnText({ iteration, logger }, chunk);
|
|
314
|
-
onChunk?.(textOut);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
let llmResponse = null;
|
|
318
|
-
if (sawToolCall) {
|
|
319
|
-
llmResponse = { type: "tool_call", calls: toolCalls };
|
|
320
|
-
}
|
|
321
|
-
else if (textContent) {
|
|
322
|
-
llmResponse = { type: "text", content: textContent };
|
|
323
|
-
}
|
|
324
|
-
else if (reasoningContent) {
|
|
325
|
-
llmResponse = { type: "reasoning", content: reasoningContent };
|
|
326
|
-
}
|
|
327
|
-
pluginManager.runOnAfterThink({ iteration, logger }, llmResponse);
|
|
328
|
-
if (this.deps.exitOnComplete && sawToolCall) {
|
|
329
|
-
const signature = toolCalls
|
|
330
|
-
.map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`)
|
|
331
|
-
.join("|");
|
|
332
|
-
if (signature && signature === lastToolSignature) {
|
|
333
|
-
// A repeated identical tool call is often the model re-running
|
|
334
|
-
// a command after a confusing result. Give it one more chance
|
|
335
|
-
// to produce a final text answer instead of stopping with
|
|
336
|
-
// text: "" (observed on 08-r4: bash re-run → empty result).
|
|
337
|
-
repeatedToolCount++;
|
|
338
|
-
if (repeatedToolCount >= MAX_REPEATED_TOOL_CALLS) {
|
|
339
|
-
logger.debug("Exit-on-complete: repeated identical tool call, stopping");
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
342
|
-
contextManager.addMessage({
|
|
343
|
-
role: "user",
|
|
344
|
-
content: `<system-summary>You just called the same tool with identical arguments. If the task is done, answer with a final text response NOW. If the command failed, try a different approach.</system-summary>`,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
lastToolSignature = signature;
|
|
348
|
-
}
|
|
349
|
-
if (sawToolCall) {
|
|
350
|
-
slog.logAssistant(textContent || "", reasoningContent, toolCalls, iteration);
|
|
351
|
-
}
|
|
352
|
-
if (sawToolCall) {
|
|
353
|
-
contextManager.addMessage({
|
|
354
|
-
role: "assistant",
|
|
355
|
-
content: textContent || "",
|
|
356
|
-
tool_calls: toolCalls.map((tc) => ({
|
|
357
|
-
id: tc.id,
|
|
358
|
-
type: "function",
|
|
359
|
-
function: {
|
|
360
|
-
name: tc.name,
|
|
361
|
-
arguments: JSON.stringify(tc.arguments),
|
|
362
|
-
},
|
|
363
|
-
})),
|
|
364
|
-
});
|
|
365
|
-
const summaries = [];
|
|
366
|
-
let anyToolFailed = false;
|
|
367
|
-
for (const call of toolCalls) {
|
|
368
|
-
this.setScope();
|
|
369
|
-
const startTime = Date.now();
|
|
370
|
-
pluginManager.runOnToolCall({
|
|
371
|
-
toolName: call.name,
|
|
372
|
-
args: call.arguments,
|
|
373
|
-
});
|
|
374
|
-
pluginManager.runOnToolStart({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments });
|
|
375
|
-
onTool?.({ type: "start", tool: call.name, args: call.arguments });
|
|
376
|
-
slog.logToolCall(call, iteration);
|
|
377
|
-
const tokensBeforeTool = contextManager.getEstimatedTokens();
|
|
378
|
-
const result = await toolExecutor.execute(call, this.abortController?.signal);
|
|
379
|
-
const duration = Date.now() - startTime;
|
|
380
|
-
if (!result.success)
|
|
381
|
-
anyToolFailed = true;
|
|
382
|
-
if (result.success && call.arguments.path) {
|
|
383
|
-
const filePath = String(call.arguments.path);
|
|
384
|
-
if (call.name === "write_file" || call.name === "edit_file") {
|
|
385
|
-
hallucinationDetector
|
|
386
|
-
.getConsistencyCheck()
|
|
387
|
-
.trackCreatedFile(filePath);
|
|
388
|
-
}
|
|
389
|
-
else if (call.name === "delete_file") {
|
|
390
|
-
hallucinationDetector
|
|
391
|
-
.getConsistencyCheck()
|
|
392
|
-
.trackDeletedFile(filePath);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
pluginManager.runOnToolEnd({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments }, result, duration);
|
|
396
|
-
if (result.display) {
|
|
397
|
-
onMeta?.("\n" + result.display + "\n");
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
const metaOut = pluginManager.runOnMeta({ iteration, logger }, result.output);
|
|
401
|
-
onMeta?.("\n" + pc.dim(metaOut) + "\n");
|
|
402
|
-
}
|
|
403
|
-
if (result.diff) {
|
|
404
|
-
onMeta?.("\n" + result.diff + "\n");
|
|
405
|
-
}
|
|
406
|
-
const currentTokens = contextManager.getEstimatedTokens();
|
|
407
|
-
const budget = contextManager.getBudget();
|
|
408
|
-
const truncatedOutput = this.truncateToolOutput(result.output, budget, currentTokens);
|
|
409
|
-
contextManager.addMessage({
|
|
410
|
-
role: "tool",
|
|
411
|
-
content: truncatedOutput,
|
|
412
|
-
name: call.name,
|
|
413
|
-
tool_call_id: call.id,
|
|
414
|
-
success: result.success,
|
|
415
|
-
arguments: call.arguments,
|
|
416
|
-
});
|
|
417
|
-
const tokensAfterTool = contextManager.getEstimatedTokens();
|
|
418
|
-
onTool?.({
|
|
419
|
-
type: "end",
|
|
420
|
-
tool: call.name,
|
|
421
|
-
args: call.arguments,
|
|
422
|
-
duration,
|
|
423
|
-
error: !result.success,
|
|
424
|
-
ctxDelta: tokensAfterTool - tokensBeforeTool,
|
|
425
|
-
});
|
|
426
|
-
summaries.push(`[Tool: ${call.name} (${JSON.stringify(call.arguments)}) → ${truncatedOutput.slice(0, 200)}]`);
|
|
427
|
-
if (config.session.autoSave) {
|
|
428
|
-
slog.logToolResult(call, result, duration, iteration);
|
|
429
|
-
}
|
|
430
|
-
if (contextManager.needsCompaction()) {
|
|
431
|
-
contextManager.compact();
|
|
432
|
-
logger.debug("Context compacted after tool result");
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
if (anyToolFailed) {
|
|
436
|
-
consecutiveToolFailures++;
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
consecutiveToolFailures = 0;
|
|
440
|
-
}
|
|
441
|
-
if (consecutiveToolFailures >= MAX_CONSECUTIVE_TOOL_FAILURES) {
|
|
442
|
-
const recoveryMsg = t("exec.consecutive_failures_recovery", {
|
|
443
|
-
count: consecutiveToolFailures,
|
|
444
|
-
});
|
|
445
|
-
logger.warn(`Consecutive tool failures: ${consecutiveToolFailures}`);
|
|
446
|
-
const taskSnippet = input.length > 200 ? input.slice(0, 200) + "..." : input;
|
|
447
|
-
const taskReminder = t("exec.task_reminder", { task: taskSnippet });
|
|
448
|
-
contextManager.addMessage({
|
|
449
|
-
role: "user",
|
|
450
|
-
content: `<system-summary>${recoveryMsg}\n${taskReminder}</system-summary>`,
|
|
451
|
-
});
|
|
452
|
-
if (sessionManager) {
|
|
453
|
-
const activeSession = sessionManager.getActiveMeta();
|
|
454
|
-
if (activeSession) {
|
|
455
|
-
const memDir = join(baseDir, ".mma", "memory");
|
|
456
|
-
const memStore = new MemoryStore(memDir);
|
|
457
|
-
memStore.appendRule("errors", `${consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
contextManager.addMessage({
|
|
462
|
-
role: "user",
|
|
463
|
-
content: `<system-summary>${summaries.join("\n")}</system-summary>`,
|
|
464
|
-
});
|
|
465
|
-
const ui = this.deps.config.ui;
|
|
466
|
-
if (ui?.showContextStats) {
|
|
467
|
-
const ctxTokens = contextManager.getEstimatedTokens();
|
|
468
|
-
const ctxBudget = contextManager.getBudget();
|
|
469
|
-
const ctxPct = Math.min(100, Math.round((ctxTokens / ctxBudget.history) * 100));
|
|
470
|
-
const barLen = 10;
|
|
471
|
-
const filled = Math.round((ctxPct / 100) * barLen);
|
|
472
|
-
const ctxBar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
|
|
473
|
-
const pctColor = ctxPct >= 75 ? pc.yellow : pc.dim;
|
|
474
|
-
const compCount = contextManager.getCompactionCount();
|
|
475
|
-
const quality = contextManager.getQuality();
|
|
476
|
-
const qualityColor = quality >= 70 ? pc.green : quality >= 40 ? pc.yellow : pc.red;
|
|
477
|
-
onMeta?.(`\n ${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)} ${pc.dim(`compactions: ${compCount}`)} ${qualityColor(`quality: ${quality}%`)}\n`);
|
|
478
|
-
}
|
|
479
|
-
else if (ui?.showCompaction) {
|
|
480
|
-
const compCount = contextManager.getCompactionCount();
|
|
481
|
-
if (compCount > this.lastCompactionShown) {
|
|
482
|
-
this.lastCompactionShown = compCount;
|
|
483
|
-
onMeta?.(pc.dim(`\n ⟳ Context compacted (${compCount})\n`));
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
hallucinationDetector.getConfidenceCheck().setPreviousResponse(lastText);
|
|
489
|
-
const hallucinationResult = await hallucinationDetector.validate(textContent);
|
|
490
|
-
if (hallucinationResult.status === "block") {
|
|
491
|
-
logger.warn(`Response blocked: ${hallucinationResult.reason}`);
|
|
492
|
-
return {
|
|
493
|
-
success: false,
|
|
494
|
-
text: lastText,
|
|
495
|
-
error: t("error.response_blocked", {
|
|
496
|
-
reason: hallucinationResult.reason || "",
|
|
497
|
-
}),
|
|
498
|
-
iterationCount: iteration,
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
if (hallucinationResult.status === "warn") {
|
|
502
|
-
logger.warn(`Hallucination warning: ${hallucinationResult.reason}`);
|
|
503
|
-
const warnLine = `${t("hall.uncertainty_prefix").trim()} ${hallucinationResult.reason ?? ""}`;
|
|
504
|
-
if (onMeta) {
|
|
505
|
-
onMeta(`\n${pc.yellow(warnLine)}\n`);
|
|
506
|
-
}
|
|
507
|
-
else if (onChunk) {
|
|
508
|
-
onChunk(`\n${warnLine}\n`);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
if (hallucinationResult.status === "retry") {
|
|
512
|
-
if (this.deps.exitOnComplete && textContent?.trim()) {
|
|
513
|
-
logger.debug("Exit-on-complete: stopping on first response");
|
|
514
|
-
// Save the response BEFORE breaking — lastText is still the
|
|
515
|
-
// previous (tool-only) iteration's text, so without this the
|
|
516
|
-
// final answer is lost (reported text: "").
|
|
517
|
-
lastText = textContent;
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
// NOTE: with exitOnComplete and an EMPTY text we deliberately do NOT
|
|
521
|
-
// break — the model produced no usable answer yet (same case as the
|
|
522
|
-
// empty-response guard below). Falling through to the retry path
|
|
523
|
-
// keeps us from finishing with text: "".
|
|
524
|
-
if (hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
|
|
525
|
-
logger.warn(`Hallucination retries exhausted (${MAX_HALLUCINATION_RETRIES}), returning error`);
|
|
526
|
-
return {
|
|
527
|
-
success: false,
|
|
528
|
-
text: lastText,
|
|
529
|
-
error: t("error.response_blocked", {
|
|
530
|
-
reason: t("hall.max_retries_exhausted"),
|
|
531
|
-
}),
|
|
532
|
-
iterationCount: iteration,
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
hallucinationRetries++;
|
|
536
|
-
logger.warn(`Hallucination retry (${hallucinationRetries}/${MAX_HALLUCINATION_RETRIES}): ${hallucinationResult.reason}`);
|
|
537
|
-
if (textContent) {
|
|
538
|
-
contextManager.addMessage({
|
|
539
|
-
role: "assistant",
|
|
540
|
-
content: textContent,
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
contextManager.addMessage({
|
|
544
|
-
role: "user",
|
|
545
|
-
content: `<system-summary>[Retry context: ${hallucinationResult.reason}. Original task: "${input}". You must either call a needed tool or provide a substantive response. Empty replies are not allowed.]</system-summary>`,
|
|
546
|
-
});
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
if (textContent) {
|
|
550
|
-
contextManager.addMessage({ role: "assistant", content: textContent });
|
|
551
|
-
if (config.session.autoSave) {
|
|
552
|
-
slog.saveAssistantMessage(textContent);
|
|
553
|
-
}
|
|
554
|
-
slog.logAssistant(textContent, reasoningContent, undefined, iteration);
|
|
555
|
-
}
|
|
556
|
-
lastText = textContent;
|
|
557
|
-
{
|
|
558
|
-
const decisionPatterns = [
|
|
559
|
-
...textContent.matchAll(/(?:plan|decided|decision|решено|план|решение):\s*(.+?)(?:\n|$)/gi),
|
|
560
|
-
];
|
|
561
|
-
for (const match of decisionPatterns) {
|
|
562
|
-
hallucinationDetector
|
|
563
|
-
.getConsistencyCheck()
|
|
564
|
-
.trackDecision(match[1].trim(), "agent_response");
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (!sawToolCall) {
|
|
568
|
-
// Guard: the model returned an EMPTY final response (no text, no
|
|
569
|
-
// tool calls — often just reasoning content after context
|
|
570
|
-
// compaction). Nudge it to produce a real answer instead of
|
|
571
|
-
// silently finishing with text: "".
|
|
572
|
-
if (!textContent?.trim()) {
|
|
573
|
-
if (emptyResponseRetries < MAX_EMPTY_RESPONSE_RETRIES) {
|
|
574
|
-
emptyResponseRetries++;
|
|
575
|
-
logger.warn(`Empty response on iteration ${iteration} (retry ${emptyResponseRetries}/${MAX_EMPTY_RESPONSE_RETRIES})`);
|
|
576
|
-
contextManager.addMessage({
|
|
577
|
-
role: "user",
|
|
578
|
-
content: `<system-summary>Your previous response was empty. Answer the user's task now with a final text response or call a tool. Do not reply with reasoning only.</system-summary>`,
|
|
579
|
-
});
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
emptyResponseExhausted = true;
|
|
583
|
-
logger.warn(`Empty response retries exhausted after ${MAX_EMPTY_RESPONSE_RETRIES} attempts`);
|
|
584
|
-
}
|
|
585
|
-
if (this.deps.finalAudit) {
|
|
586
|
-
const audit = await this.deps.finalAudit();
|
|
587
|
-
if (audit && !audit.passed) {
|
|
588
|
-
logger.warn(`Final audit incomplete: ${audit.summary}`);
|
|
589
|
-
const steps = audit.pendingSteps.slice(0, 5).join("; ") || "—";
|
|
590
|
-
contextManager.addMessage({
|
|
591
|
-
role: "user",
|
|
592
|
-
content: `<system-summary>${t("exec.audit_incomplete", {
|
|
593
|
-
summary: audit.summary,
|
|
594
|
-
steps,
|
|
595
|
-
})}</system-summary>`,
|
|
596
|
-
});
|
|
597
|
-
slog.logAudit(audit.summary, iteration);
|
|
598
|
-
auditRetries++;
|
|
599
|
-
if (auditRetries >= MAX_AUDIT_RETRIES ||
|
|
600
|
-
iteration >= config.maxToolIterations - 1) {
|
|
601
|
-
logger.warn(`Final audit still incomplete after ${auditRetries} retries — finishing anyway`);
|
|
602
|
-
break;
|
|
603
|
-
}
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
break;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const tokensUsed = contextManager.getEstimatedTokens();
|
|
611
|
-
const budget = contextManager.getBudget();
|
|
612
|
-
const usageTokens = this.resolveUsageTokens(apiPromptTokens, apiCompletionTokens, tokensUsed, apiCompletionChars);
|
|
613
|
-
if (iteration >= config.maxToolIterations) {
|
|
614
|
-
return {
|
|
615
|
-
success: false,
|
|
616
|
-
text: lastText,
|
|
617
|
-
error: t("error.max_iters", { max: config.maxToolIterations }),
|
|
618
|
-
iterationCount: iteration,
|
|
619
|
-
contextUsed: tokensUsed,
|
|
620
|
-
contextLimit: budget.history,
|
|
621
|
-
promptTokens: usageTokens.prompt,
|
|
622
|
-
completionTokens: usageTokens.completion,
|
|
623
|
-
totalTokens: usageTokens.total,
|
|
624
|
-
compactionCount: contextManager.getCompactionCount(),
|
|
625
|
-
contextQuality: contextManager.getQuality(),
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
return {
|
|
629
|
-
success: emptyResponseExhausted ? false : true,
|
|
630
|
-
text: lastText,
|
|
631
|
-
error: emptyResponseExhausted ? t("error.empty_response") : undefined,
|
|
632
|
-
iterationCount: iteration,
|
|
633
|
-
contextUsed: tokensUsed,
|
|
634
|
-
contextLimit: budget.history,
|
|
635
|
-
promptTokens: usageTokens.prompt,
|
|
636
|
-
completionTokens: usageTokens.completion,
|
|
637
|
-
totalTokens: usageTokens.total,
|
|
638
|
-
compactionCount: contextManager.getCompactionCount(),
|
|
639
|
-
contextQuality: contextManager.getQuality(),
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
clearContext() {
|
|
643
|
-
this.deps.contextManager.clear();
|
|
644
|
-
this.systemPromptAdded = false;
|
|
645
|
-
}
|
|
646
|
-
async reconfigure(config) {
|
|
647
|
-
const { OpenAICompatProvider } = await import("../llm/openai-compat");
|
|
648
|
-
const { TokenCounter } = await import("../llm/token-counter");
|
|
649
|
-
const newProvider = new OpenAICompatProvider({
|
|
650
|
-
model: config.model,
|
|
651
|
-
baseUrl: config.provider.baseUrl,
|
|
652
|
-
apiKey: config.provider.apiKey,
|
|
653
|
-
contextWindow: config.contextWindow,
|
|
654
|
-
retry: config.retry,
|
|
655
|
-
rateLimits: config.security?.rateLimits,
|
|
656
|
-
});
|
|
657
|
-
this.deps.llmProvider = newProvider;
|
|
658
|
-
this.deps.toolExecutor.updateProvider(newProvider);
|
|
659
|
-
this.deps.toolExecutor.ctx.llmProvider = newProvider;
|
|
660
|
-
const newTokenCounter = new TokenCounter(config.model);
|
|
661
|
-
this.deps.contextManager.resize(config.contextWindow, config.contextBudget, newTokenCounter);
|
|
662
|
-
this.deps.config = config;
|
|
663
|
-
}
|
|
664
|
-
setContext(messages) {
|
|
665
|
-
const { contextManager } = this.deps;
|
|
666
|
-
contextManager.clear();
|
|
667
|
-
const { prompt: systemPrompt } = this.buildSystemPrompt();
|
|
668
|
-
contextManager.addMessage({ role: "system", content: systemPrompt });
|
|
669
|
-
this.systemPromptAdded = true;
|
|
670
|
-
for (const msg of messages) {
|
|
671
|
-
if (msg.role === "system")
|
|
672
|
-
continue;
|
|
673
|
-
contextManager.addMessage({
|
|
674
|
-
role: msg.role,
|
|
675
|
-
content: msg.content,
|
|
676
|
-
name: msg.name,
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
shutdown() {
|
|
681
|
-
this.shutdownRequested = true;
|
|
682
|
-
this.abortController?.abort();
|
|
683
|
-
const { pluginManager, logger, sessionManager, contextManager } = this.deps;
|
|
684
|
-
contextManager.onCompact = null;
|
|
685
|
-
const killed = processRegistry.killAll();
|
|
686
|
-
if (killed > 0) {
|
|
687
|
-
logger.info(`Killed ${killed} background process(es) on shutdown`);
|
|
688
|
-
}
|
|
689
|
-
logger.closeSessionLog();
|
|
690
|
-
pluginManager.runOnSessionEnd({
|
|
691
|
-
logger,
|
|
692
|
-
sessionManager: sessionManager?.getActiveMeta(),
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|