micro-models-agent 0.7.9 → 0.8.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.
Files changed (150) hide show
  1. package/dist/cli/commands.js +173 -0
  2. package/dist/cli/completer.js +168 -0
  3. package/dist/cli/index.js +2 -0
  4. package/dist/cli/main.js +95 -0
  5. package/dist/cli/repl.js +762 -0
  6. package/dist/cli/security-commands.js +166 -0
  7. package/dist/cli/setup.js +214 -0
  8. package/dist/config/config.js +123 -0
  9. package/dist/config/defaults.js +91 -0
  10. package/dist/config/experts.js +15 -0
  11. package/dist/config/index.js +3 -0
  12. package/dist/config/security.js +187 -0
  13. package/dist/config/types.js +1 -0
  14. package/dist/core/agent.js +626 -0
  15. package/dist/core/bootstrap.js +307 -0
  16. package/dist/core/index.js +2 -0
  17. package/dist/core/prompt-builder.js +55 -0
  18. package/dist/core/types.js +1 -0
  19. package/dist/i18n/en.json +405 -0
  20. package/dist/i18n/index.js +43 -0
  21. package/dist/i18n/ru.json +405 -0
  22. package/dist/index.js +22 -0
  23. package/dist/llm/index.js +4 -0
  24. package/dist/llm/model-loader.js +78 -0
  25. package/dist/llm/openai-compat.js +277 -0
  26. package/dist/llm/orchestrator.js +194 -0
  27. package/dist/llm/provider.js +2 -0
  28. package/dist/llm/response.js +39 -0
  29. package/dist/llm/token-counter.js +37 -0
  30. package/dist/llm/types.js +1 -0
  31. package/dist/logger/app-logger.js +76 -0
  32. package/dist/logger/index.js +1 -0
  33. package/dist/migration/backup.js +45 -0
  34. package/dist/migration/detect.js +50 -0
  35. package/dist/migration/index.js +2 -0
  36. package/dist/modules/browser/actions.js +46 -0
  37. package/dist/modules/browser/cookie-store.js +24 -0
  38. package/dist/modules/browser/index.js +5 -0
  39. package/dist/modules/browser/module.js +28 -0
  40. package/dist/modules/browser/session.js +287 -0
  41. package/dist/modules/browser/snapshot.js +114 -0
  42. package/dist/modules/browser/types.js +9 -0
  43. package/dist/modules/context/history.js +15 -0
  44. package/dist/modules/context/index.js +1 -0
  45. package/dist/modules/context/manager.js +179 -0
  46. package/dist/modules/execution/auditor.js +72 -0
  47. package/dist/modules/execution/index.js +6 -0
  48. package/dist/modules/execution/module.js +334 -0
  49. package/dist/modules/execution/moe-executor.js +196 -0
  50. package/dist/modules/execution/plan-validator.js +153 -0
  51. package/dist/modules/execution/planner.js +35 -0
  52. package/dist/modules/execution/stuck-detector.js +113 -0
  53. package/dist/modules/execution/tracker.js +53 -0
  54. package/dist/modules/execution/types.js +1 -0
  55. package/dist/modules/execution/verifier.js +149 -0
  56. package/dist/modules/hallucination/confidence.js +47 -0
  57. package/dist/modules/hallucination/consistency.js +32 -0
  58. package/dist/modules/hallucination/detector.js +41 -0
  59. package/dist/modules/hallucination/factual.js +128 -0
  60. package/dist/modules/hallucination/index.js +4 -0
  61. package/dist/modules/index.js +5 -0
  62. package/dist/modules/indexer/cache.js +38 -0
  63. package/dist/modules/indexer/index.js +3 -0
  64. package/dist/modules/indexer/module.js +192 -0
  65. package/dist/modules/indexer/walker.js +101 -0
  66. package/dist/modules/mcp/client.js +393 -0
  67. package/dist/modules/mcp/index.js +3 -0
  68. package/dist/modules/mcp/module.js +146 -0
  69. package/dist/modules/mcp/registry.js +15 -0
  70. package/dist/modules/memory/index.js +1 -0
  71. package/dist/modules/memory/search.js +26 -0
  72. package/dist/modules/memory/store.js +38 -0
  73. package/dist/modules/pipelines/engine.js +60 -0
  74. package/dist/modules/pipelines/index.js +3 -0
  75. package/dist/modules/pipelines/parser.js +53 -0
  76. package/dist/modules/pipelines/template.js +14 -0
  77. package/dist/modules/plugins/builtin/lint-on-write.js +121 -0
  78. package/dist/modules/plugins/builtin/notify.js +8 -0
  79. package/dist/modules/plugins/index.js +1 -0
  80. package/dist/modules/plugins/loader.js +28 -0
  81. package/dist/modules/plugins/manager.js +161 -0
  82. package/dist/modules/plugins/types.js +1 -0
  83. package/dist/modules/registry.js +45 -0
  84. package/dist/modules/security/audit-log.js +108 -0
  85. package/dist/modules/security/audit-notifier.js +292 -0
  86. package/dist/modules/security/command-validator.js +91 -0
  87. package/dist/modules/security/content-scanner.js +52 -0
  88. package/dist/modules/security/data-sanitizer.js +97 -0
  89. package/dist/modules/security/encryption.js +218 -0
  90. package/dist/modules/security/index.js +14 -0
  91. package/dist/modules/security/network-validator.js +79 -0
  92. package/dist/modules/security/path-validator.js +155 -0
  93. package/dist/modules/security/rate-limiter.js +119 -0
  94. package/dist/modules/security/security-policies.js +393 -0
  95. package/dist/modules/security/session-encryption.js +193 -0
  96. package/dist/modules/security/session-isolation.js +95 -0
  97. package/dist/modules/session/index.js +3 -0
  98. package/dist/modules/session/manager.js +167 -0
  99. package/dist/modules/session/module.js +28 -0
  100. package/dist/modules/session/store.js +174 -0
  101. package/dist/modules/session/types.js +1 -0
  102. package/dist/modules/skills/index.js +3 -0
  103. package/dist/modules/skills/loader.js +72 -0
  104. package/dist/modules/skills/matcher.js +27 -0
  105. package/dist/modules/skills/module.js +180 -0
  106. package/dist/modules/types.js +1 -0
  107. package/dist/modules/updater/checker.js +32 -0
  108. package/dist/modules/updater/index.js +1 -0
  109. package/dist/modules/user-profile/compressor.js +16 -0
  110. package/dist/modules/user-profile/index.js +1 -0
  111. package/dist/modules/user-profile/profile.js +68 -0
  112. package/dist/tools/approve.js +32 -0
  113. package/dist/tools/bash.js +77 -0
  114. package/dist/tools/browser.js +97 -0
  115. package/dist/tools/create-dir.js +57 -0
  116. package/dist/tools/delete-file.js +64 -0
  117. package/dist/tools/edit-file.js +78 -0
  118. package/dist/tools/executor.js +83 -0
  119. package/dist/tools/file-info.js +46 -0
  120. package/dist/tools/filter-tools.js +10 -0
  121. package/dist/tools/glob-tool.js +19 -0
  122. package/dist/tools/grep-tool.js +51 -0
  123. package/dist/tools/index.js +44 -0
  124. package/dist/tools/list-dir.js +40 -0
  125. package/dist/tools/load-skill.js +48 -0
  126. package/dist/tools/mcp-call.js +68 -0
  127. package/dist/tools/move-file.js +84 -0
  128. package/dist/tools/pipeline-run.js +39 -0
  129. package/dist/tools/question.js +142 -0
  130. package/dist/tools/read-file.js +65 -0
  131. package/dist/tools/registry.js +36 -0
  132. package/dist/tools/scope-check.js +30 -0
  133. package/dist/tools/search-history.js +64 -0
  134. package/dist/tools/subagent.js +130 -0
  135. package/dist/tools/types.js +1 -0
  136. package/dist/tools/user-input.js +123 -0
  137. package/dist/tools/web-browse.js +51 -0
  138. package/dist/tools/web-fetch.js +62 -0
  139. package/dist/tools/web-search.js +59 -0
  140. package/dist/tools/write-file.js +80 -0
  141. package/dist/ui/box.js +81 -0
  142. package/dist/ui/colors.js +4 -0
  143. package/dist/ui/diff.js +185 -0
  144. package/dist/ui/index.js +6 -0
  145. package/dist/ui/md-formatter.js +212 -0
  146. package/dist/ui/output.js +13 -0
  147. package/dist/ui/renderer.js +141 -0
  148. package/dist/ui/spinner.js +70 -0
  149. package/dist/ui/table.js +144 -0
  150. package/package.json +1 -1
@@ -0,0 +1,626 @@
1
+ import { t } from "../i18n/index";
2
+ import { pc } from "../ui/colors";
3
+ import { PromptBuilder } from "./prompt-builder";
4
+ import { OrchestratorClient } from "../llm/orchestrator";
5
+ import { validatePlan, applyAutoFixes, } from "../modules/execution/plan-validator";
6
+ import { MoEExecutor } from "../modules/execution/moe-executor";
7
+ import { StepVerifier } from "../modules/execution/verifier";
8
+ const TOOL_RESULT_MAX_TOKENS_RATIO = 0.3;
9
+ const TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000;
10
+ export class Agent {
11
+ deps;
12
+ systemPromptAdded = false;
13
+ constructor(deps) {
14
+ this.deps = deps;
15
+ }
16
+ setScope() {
17
+ const executor = this.deps.toolExecutor;
18
+ if (executor && executor.ctx && this.deps.scope) {
19
+ executor.ctx.scope = this.deps.scope;
20
+ }
21
+ }
22
+ buildSystemPrompt() {
23
+ const systemBudget = Math.floor(this.deps.config.contextWindow *
24
+ this.deps.config.contextBudget.systemPrompt);
25
+ const builder = new PromptBuilder(systemBudget);
26
+ builder.addBlocks(this.deps.promptBlocks);
27
+ const dynamic = this.deps.getDynamicPromptBlocks?.() ?? [];
28
+ if (dynamic.length > 0) {
29
+ builder.addBlocks(dynamic);
30
+ }
31
+ const pluginBlocks = (this.deps.pluginManager.runOnBuildPrompt?.() ?? [])
32
+ .filter((s) => Boolean(s && s.trim() !== ""))
33
+ .map((content) => ({
34
+ content,
35
+ priority: "low",
36
+ essential: false,
37
+ estimatedTokens: this.deps.llmProvider.countTokens(content),
38
+ }));
39
+ if (pluginBlocks.length > 0) {
40
+ builder.addBlocks(pluginBlocks);
41
+ }
42
+ return builder.build();
43
+ }
44
+ /** Refresh the system prompt in context if dynamic blocks changed. */
45
+ refreshSystemPrompt() {
46
+ const { prompt } = this.buildSystemPrompt();
47
+ const current = this.deps.contextManager
48
+ .getActiveHistory()
49
+ .find((m) => m.role === "system");
50
+ if (!current || current.content !== prompt) {
51
+ this.deps.contextManager.updateSystemPrompt?.(prompt);
52
+ }
53
+ }
54
+ truncateToolOutput(output, budget, currentTokens) {
55
+ const remainingBudget = budget.history - currentTokens;
56
+ const maxCharsByBudget = Math.floor(remainingBudget * 0.5 * 2);
57
+ const maxCharsByRatio = Math.floor(budget.history * TOOL_RESULT_MAX_TOKENS_RATIO * 2);
58
+ const maxChars = Math.min(maxCharsByBudget, maxCharsByRatio, TOOL_RESULT_ABSOLUTE_MAX_CHARS);
59
+ if (output.length <= maxChars)
60
+ return output;
61
+ const truncated = output.slice(0, maxChars);
62
+ const removedChars = output.length - maxChars;
63
+ return (truncated +
64
+ `\n\n${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`);
65
+ }
66
+ emitPhase(iteration, phase, onPhase) {
67
+ this.deps.pluginManager.runOnPhase?.({ iteration, logger: this.deps.logger }, phase);
68
+ onPhase?.(phase);
69
+ }
70
+ async run(input, onChunk, onMeta, onTool, onPhase) {
71
+ this.setScope();
72
+ const { config, llmProvider, toolExecutor, pluginManager, contextManager, hallucinationDetector, logger, sessionManager, } = this.deps;
73
+ if (sessionManager && !sessionManager.getActive()) {
74
+ sessionManager.create();
75
+ logger.debug(`Session started: ${sessionManager.getActive()}`);
76
+ }
77
+ if (!this.systemPromptAdded &&
78
+ !contextManager.getActiveHistory().some((m) => m.role === "system")) {
79
+ const { prompt: systemPrompt, excluded } = this.buildSystemPrompt();
80
+ contextManager.addMessage({ role: "system", content: systemPrompt });
81
+ this.systemPromptAdded = true;
82
+ if (sessionManager) {
83
+ sessionManager.appendLog({
84
+ ts: new Date().toISOString(),
85
+ type: "system",
86
+ content: systemPrompt.slice(0, 2000),
87
+ });
88
+ if (excluded.length > 0) {
89
+ sessionManager.appendLog({
90
+ ts: new Date().toISOString(),
91
+ type: "system",
92
+ content: `[Excluded prompt blocks: ${excluded.length}]`,
93
+ });
94
+ }
95
+ }
96
+ pluginManager.runOnSessionStart({
97
+ logger,
98
+ sessionManager: sessionManager?.getActiveMeta(),
99
+ });
100
+ }
101
+ contextManager.addMessage({ role: "user", content: input });
102
+ if (sessionManager) {
103
+ sessionManager.appendMessage({
104
+ role: "user",
105
+ content: input,
106
+ timestamp: new Date().toISOString(),
107
+ });
108
+ sessionManager.appendLog({
109
+ ts: new Date().toISOString(),
110
+ type: "user",
111
+ content: input,
112
+ });
113
+ }
114
+ if (config.moe?.enabled) {
115
+ return this.runWithMoE(input, onChunk, onMeta, onTool, onPhase);
116
+ }
117
+ return this.executeSingleAgentLoop(input, onChunk, onMeta, onTool, onPhase);
118
+ }
119
+ async executeSingleAgentLoop(input, onChunk, onMeta, onTool, onPhase) {
120
+ const { config, llmProvider, toolExecutor, pluginManager, contextManager, hallucinationDetector, logger, sessionManager, } = this.deps;
121
+ let iteration = 0;
122
+ let lastText = "";
123
+ let hallucinationRetries = 0;
124
+ let lastToolSignature = "";
125
+ const MAX_HALLUCINATION_RETRIES = 3;
126
+ while (iteration < config.maxToolIterations) {
127
+ iteration++;
128
+ pluginManager.runOnBeforeThink({
129
+ iteration,
130
+ logger,
131
+ lastUserMessage: input,
132
+ contextManager,
133
+ onMeta,
134
+ });
135
+ if (contextManager.needsCompaction()) {
136
+ contextManager.compact();
137
+ logger.debug("Context compacted");
138
+ if (sessionManager) {
139
+ sessionManager.appendLog({
140
+ ts: new Date().toISOString(),
141
+ type: "compaction",
142
+ content: `regular compaction, iteration ${iteration}`,
143
+ iteration,
144
+ });
145
+ }
146
+ }
147
+ const currentTokens = contextManager.getEstimatedTokens();
148
+ const budget = contextManager.getBudget();
149
+ if (currentTokens > budget.history) {
150
+ contextManager.compact();
151
+ logger.warn(`Context overflow (${currentTokens} > ${budget.history}), forced compaction`);
152
+ if (sessionManager) {
153
+ sessionManager.appendLog({
154
+ ts: new Date().toISOString(),
155
+ type: "compaction",
156
+ content: `forced compaction (${currentTokens} > ${budget.history}), iteration ${iteration}`,
157
+ iteration,
158
+ contextTokens: currentTokens,
159
+ contextLimit: budget.history,
160
+ });
161
+ }
162
+ }
163
+ this.refreshSystemPrompt();
164
+ const history = contextManager.getActiveHistory();
165
+ const allTools = toolExecutor.getToolDefinitions(this.deps.toolTags);
166
+ if (sessionManager) {
167
+ sessionManager.appendLog({
168
+ ts: new Date().toISOString(),
169
+ type: "tool_defs",
170
+ toolCount: allTools.length,
171
+ toolNames: allTools.map((t) => t.name),
172
+ iteration,
173
+ });
174
+ }
175
+ let textContent = "";
176
+ let reasoningContent = "";
177
+ const toolCalls = [];
178
+ let sawToolCall = false;
179
+ let emittedReasoning = false;
180
+ this.emitPhase(iteration, "thinking", onPhase);
181
+ try {
182
+ for await (const chunk of llmProvider.chat(history, allTools)) {
183
+ if (chunk.type === "text" && chunk.content) {
184
+ if (emittedReasoning && !textContent) {
185
+ onMeta?.("\n\n");
186
+ }
187
+ textContent += chunk.content;
188
+ const textOut = pluginManager.runOnText({ iteration, logger }, chunk.content);
189
+ onChunk?.(textOut);
190
+ }
191
+ if (chunk.type === "reasoning" && chunk.content) {
192
+ reasoningContent += chunk.content;
193
+ if (config.showReasoning) {
194
+ const metaOut = pluginManager.runOnMeta({ iteration, logger }, chunk.content);
195
+ if (metaOut) {
196
+ onMeta?.(pc.dim(metaOut));
197
+ }
198
+ emittedReasoning = true;
199
+ }
200
+ }
201
+ if (chunk.type === "tool_call" && chunk.toolCall) {
202
+ sawToolCall = true;
203
+ let parsedArgs;
204
+ try {
205
+ parsedArgs = JSON.parse(chunk.toolCall.arguments);
206
+ }
207
+ catch {
208
+ parsedArgs = {};
209
+ }
210
+ toolCalls.push({
211
+ id: chunk.toolCall.id,
212
+ name: chunk.toolCall.name,
213
+ arguments: parsedArgs,
214
+ });
215
+ }
216
+ }
217
+ }
218
+ catch (err) {
219
+ logger.error(`LLM call failed: ${err.message}`);
220
+ if (sessionManager) {
221
+ sessionManager.appendLog({
222
+ ts: new Date().toISOString(),
223
+ type: "error",
224
+ content: err.message,
225
+ });
226
+ }
227
+ pluginManager.runOnError({ iteration, logger }, err);
228
+ return {
229
+ success: false,
230
+ text: lastText,
231
+ error: t("error.llm", { message: err.message }),
232
+ iterationCount: iteration,
233
+ };
234
+ }
235
+ finally {
236
+ this.emitPhase(iteration, "done", onPhase);
237
+ }
238
+ let llmResponse = null;
239
+ if (sawToolCall) {
240
+ llmResponse = { type: "tool_call", calls: toolCalls };
241
+ }
242
+ else if (textContent) {
243
+ llmResponse = { type: "text", content: textContent };
244
+ }
245
+ else if (reasoningContent) {
246
+ llmResponse = { type: "reasoning", content: reasoningContent };
247
+ }
248
+ pluginManager.runOnAfterThink({ iteration, logger }, llmResponse);
249
+ if (this.deps.exitOnComplete && sawToolCall) {
250
+ const signature = toolCalls
251
+ .map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`)
252
+ .join("|");
253
+ if (signature && signature === lastToolSignature) {
254
+ logger.debug("Exit-on-complete: repeated identical tool call, stopping");
255
+ break;
256
+ }
257
+ lastToolSignature = signature;
258
+ }
259
+ if (sessionManager) {
260
+ if (reasoningContent) {
261
+ sessionManager.appendLog({
262
+ ts: new Date().toISOString(),
263
+ type: "reasoning",
264
+ content: reasoningContent,
265
+ iteration,
266
+ });
267
+ }
268
+ if (sawToolCall) {
269
+ sessionManager.appendLog({
270
+ ts: new Date().toISOString(),
271
+ type: "assistant",
272
+ content: textContent || "",
273
+ tool_calls: toolCalls.map((tc) => ({
274
+ id: tc.id,
275
+ name: tc.name,
276
+ arguments: tc.arguments,
277
+ })),
278
+ iteration,
279
+ });
280
+ }
281
+ }
282
+ if (sawToolCall) {
283
+ contextManager.addMessage({
284
+ role: "assistant",
285
+ content: textContent || "",
286
+ tool_calls: toolCalls.map((tc) => ({
287
+ id: tc.id,
288
+ type: "function",
289
+ function: {
290
+ name: tc.name,
291
+ arguments: JSON.stringify(tc.arguments),
292
+ },
293
+ })),
294
+ });
295
+ const summaries = [];
296
+ for (const call of toolCalls) {
297
+ this.setScope();
298
+ const startTime = Date.now();
299
+ pluginManager.runOnToolCall({
300
+ toolName: call.name,
301
+ args: call.arguments,
302
+ });
303
+ pluginManager.runOnToolStart({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments });
304
+ onTool?.({ type: "start", tool: call.name, args: call.arguments });
305
+ if (sessionManager) {
306
+ sessionManager.appendLog({
307
+ ts: new Date().toISOString(),
308
+ type: "tool_call",
309
+ tool: call.name,
310
+ tool_call_id: call.id,
311
+ args: call.arguments,
312
+ iteration,
313
+ });
314
+ }
315
+ const result = await toolExecutor.execute(call);
316
+ const duration = Date.now() - startTime;
317
+ pluginManager.runOnToolEnd({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments }, result, duration);
318
+ if (result.display) {
319
+ onMeta?.("\n" + result.display + "\n");
320
+ }
321
+ const metaOut = pluginManager.runOnMeta({ iteration, logger }, result.output);
322
+ onMeta?.("\n" + pc.dim(metaOut) + "\n");
323
+ if (result.diff) {
324
+ onMeta?.("\n" + result.diff + "\n");
325
+ }
326
+ onTool?.({
327
+ type: "end",
328
+ tool: call.name,
329
+ args: call.arguments,
330
+ duration,
331
+ error: !result.success,
332
+ });
333
+ const currentTokens = contextManager.getEstimatedTokens();
334
+ const budget = contextManager.getBudget();
335
+ const truncatedOutput = this.truncateToolOutput(result.output, budget, currentTokens);
336
+ contextManager.addMessage({
337
+ role: "tool",
338
+ content: truncatedOutput,
339
+ name: call.name,
340
+ tool_call_id: call.id,
341
+ });
342
+ summaries.push(`[Tool: ${call.name} (${JSON.stringify(call.arguments)}) → ${truncatedOutput.slice(0, 200)}]`);
343
+ if (sessionManager && config.session.autoSave) {
344
+ sessionManager.appendMessage({
345
+ role: "tool",
346
+ content: truncatedOutput.slice(0, 500),
347
+ name: call.name,
348
+ timestamp: new Date().toISOString(),
349
+ });
350
+ sessionManager.appendLog({
351
+ ts: new Date().toISOString(),
352
+ type: "tool_result",
353
+ tool: call.name,
354
+ tool_call_id: call.id,
355
+ success: result.success,
356
+ content: result.output.slice(0, 1000),
357
+ diff: result.diff,
358
+ duration,
359
+ iteration,
360
+ });
361
+ }
362
+ if (contextManager.needsCompaction()) {
363
+ contextManager.compact();
364
+ logger.debug("Context compacted after tool result");
365
+ }
366
+ }
367
+ contextManager.addMessage({
368
+ role: "user",
369
+ content: `<system-summary>${summaries.join("\n")}</system-summary>`,
370
+ });
371
+ continue;
372
+ }
373
+ const hallucinationResult = hallucinationDetector.validate(textContent);
374
+ if (hallucinationResult.status === "block") {
375
+ logger.warn(`Response blocked: ${hallucinationResult.reason}`);
376
+ return {
377
+ success: false,
378
+ text: lastText,
379
+ error: t("error.response_blocked", {
380
+ reason: hallucinationResult.reason || "",
381
+ }),
382
+ iterationCount: iteration,
383
+ };
384
+ }
385
+ if (hallucinationResult.status === "warn") {
386
+ logger.warn(`Hallucination warning: ${hallucinationResult.reason}`);
387
+ const warnPrefix = t("hall.uncertainty_prefix");
388
+ if (onChunk) {
389
+ onChunk(warnPrefix);
390
+ }
391
+ lastText = warnPrefix + lastText;
392
+ }
393
+ if (hallucinationResult.status === "retry") {
394
+ if (this.deps.exitOnComplete) {
395
+ logger.debug("Exit-on-complete: stopping on first response");
396
+ break;
397
+ }
398
+ if (hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
399
+ logger.warn(`Hallucination retries exhausted (${MAX_HALLUCINATION_RETRIES}), returning error`);
400
+ return {
401
+ success: false,
402
+ text: lastText,
403
+ error: t("error.response_blocked", {
404
+ reason: t("hall.max_retries_exhausted"),
405
+ }),
406
+ iterationCount: iteration,
407
+ };
408
+ }
409
+ hallucinationRetries++;
410
+ logger.warn(`Hallucination retry (${hallucinationRetries}/${MAX_HALLUCINATION_RETRIES}): ${hallucinationResult.reason}`);
411
+ if (textContent) {
412
+ contextManager.addMessage({
413
+ role: "assistant",
414
+ content: textContent,
415
+ });
416
+ }
417
+ contextManager.addMessage({
418
+ role: "user",
419
+ 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>`,
420
+ });
421
+ continue;
422
+ }
423
+ if (textContent) {
424
+ contextManager.addMessage({ role: "assistant", content: textContent });
425
+ if (sessionManager && config.session.autoSave) {
426
+ sessionManager.appendMessage({
427
+ role: "assistant",
428
+ content: textContent.slice(0, 500),
429
+ timestamp: new Date().toISOString(),
430
+ });
431
+ }
432
+ }
433
+ if (sessionManager) {
434
+ if (reasoningContent) {
435
+ sessionManager.appendLog({
436
+ ts: new Date().toISOString(),
437
+ type: "reasoning",
438
+ content: reasoningContent,
439
+ iteration,
440
+ });
441
+ }
442
+ if (textContent) {
443
+ sessionManager.appendLog({
444
+ ts: new Date().toISOString(),
445
+ type: "assistant",
446
+ content: textContent,
447
+ iteration,
448
+ });
449
+ }
450
+ else if (reasoningContent) {
451
+ sessionManager.appendLog({
452
+ ts: new Date().toISOString(),
453
+ type: "assistant",
454
+ content: reasoningContent,
455
+ iteration,
456
+ });
457
+ }
458
+ }
459
+ lastText = textContent;
460
+ if (!sawToolCall) {
461
+ if (this.deps.finalAudit) {
462
+ const audit = await this.deps.finalAudit();
463
+ if (audit && !audit.passed) {
464
+ logger.warn(`Final audit incomplete: ${audit.summary}`);
465
+ const steps = audit.pendingSteps.slice(0, 5).join("; ") || "—";
466
+ contextManager.addMessage({
467
+ role: "user",
468
+ content: `<system-summary>${t("exec.audit_incomplete", {
469
+ summary: audit.summary,
470
+ steps,
471
+ })}</system-summary>`,
472
+ });
473
+ if (sessionManager) {
474
+ sessionManager.appendLog({
475
+ ts: new Date().toISOString(),
476
+ type: "audit",
477
+ content: audit.summary,
478
+ iteration,
479
+ });
480
+ }
481
+ if (iteration >= config.maxToolIterations - 1) {
482
+ break;
483
+ }
484
+ continue;
485
+ }
486
+ }
487
+ break;
488
+ }
489
+ }
490
+ const tokensUsed = contextManager.getEstimatedTokens();
491
+ const budget = contextManager.getBudget();
492
+ if (iteration >= config.maxToolIterations) {
493
+ return {
494
+ success: false,
495
+ text: lastText,
496
+ error: t("error.max_iters", { max: config.maxToolIterations }),
497
+ iterationCount: iteration,
498
+ contextUsed: tokensUsed,
499
+ contextLimit: budget.history,
500
+ };
501
+ }
502
+ return {
503
+ success: true,
504
+ text: lastText,
505
+ iterationCount: iteration,
506
+ contextUsed: tokensUsed,
507
+ contextLimit: budget.history,
508
+ };
509
+ }
510
+ async runWithMoE(input, _onChunk, onMeta, onTool, onPhase) {
511
+ const { config, llmProvider, logger } = this.deps;
512
+ const orchestrator = new OrchestratorClient({
513
+ model: config.orchestrator.model,
514
+ provider: config.orchestrator.provider,
515
+ }, llmProvider);
516
+ if (!orchestrator.isEnabled()) {
517
+ logger.debug("MoE enabled but no orchestrator model configured — falling back to single-agent");
518
+ return this.executeSingleAgentLoop(input, _onChunk, onMeta, onTool, onPhase);
519
+ }
520
+ onMeta?.("🤖 Planning with MoE mode...\n");
521
+ this.emitPhase(0, "thinking", onPhase);
522
+ const planResult = await orchestrator.plan(input);
523
+ this.emitPhase(0, "done", onPhase);
524
+ if ("error" in planResult) {
525
+ logger.warn(`MoE plan failed: ${planResult.error} — falling back to single-agent`);
526
+ return this.executeSingleAgentLoop(input, _onChunk, onMeta, onTool);
527
+ }
528
+ const { plan } = planResult;
529
+ onMeta?.(`📋 Plan created: "${plan.title}" (${plan.subtasks.length} subtasks)\n`);
530
+ const validation = validatePlan(plan, config);
531
+ if (!validation.valid) {
532
+ const applied = applyAutoFixes(plan, validation.autoFixes);
533
+ const retry = validatePlan(applied, config);
534
+ if (!retry.valid) {
535
+ logger.warn(`MoE plan validation failed: ${retry.errors.join("; ")} — falling back to single-agent`);
536
+ onMeta?.(`⚠️ Plan validation failed. Falling back to single-agent mode.\n`);
537
+ return this.executeSingleAgentLoop(input, _onChunk, onMeta, onTool);
538
+ }
539
+ Object.assign(plan, applied);
540
+ onMeta?.(`🔧 Auto-fixed ${validation.autoFixes.length} plan issues.\n`);
541
+ }
542
+ const moeDeps = {
543
+ config,
544
+ toolRegistry: this.deps.toolExecutor.registry,
545
+ toolExecutor: this.deps.toolExecutor,
546
+ llmProvider,
547
+ logger,
548
+ baseDir: this.deps.baseDir,
549
+ };
550
+ const executor = new MoEExecutor(moeDeps);
551
+ onMeta?.(`⚙️ Executing ${plan.subtasks.length} subtasks...\n`);
552
+ const planResults = await executor.executePlan(plan);
553
+ onMeta?.(`✅ Execution complete: ${planResults.results.filter((r) => r.success).length}/${planResults.results.length} succeeded\n`);
554
+ const verifier = new StepVerifier(this.deps.baseDir);
555
+ const knownTags = [
556
+ "file",
557
+ "code",
558
+ "shell",
559
+ "research",
560
+ "browser",
561
+ "vision",
562
+ "core",
563
+ ];
564
+ const verification = await verifier.verifyMoEManifest(plan, config, knownTags);
565
+ this.emitPhase(0, "thinking", onPhase);
566
+ const verifyResult = await orchestrator.verifyAndMerge({
567
+ plan,
568
+ results: planResults.results.map((r) => ({
569
+ subtaskId: r.subtaskId,
570
+ success: r.success,
571
+ summary: r.summary,
572
+ result: r.result,
573
+ error: r.error,
574
+ })),
575
+ verifierErrors: verification.errors,
576
+ verifierWarnings: verification.warnings,
577
+ });
578
+ this.emitPhase(0, "done", onPhase);
579
+ const outputLines = [`## MoE Execution Results\n`];
580
+ for (const r of planResults.results) {
581
+ const icon = r.success ? "✅" : "❌";
582
+ outputLines.push(`${icon} **${r.subtaskId}**: ${r.summary} (${r.durationMs}ms)`);
583
+ }
584
+ outputLines.push("");
585
+ if (verifyResult.type === "final") {
586
+ outputLines.push(`**Result:** ${verifyResult.finalAnswer || "Complete"}`);
587
+ }
588
+ else {
589
+ outputLines.push(`**Re-plan requested:** ${verifyResult.explanation || ""}`);
590
+ }
591
+ const failedCount = planResults.results.filter((r) => !r.success).length;
592
+ return {
593
+ success: failedCount === 0 && verification.success,
594
+ text: outputLines.join("\n"),
595
+ iterationCount: planResults.results.length,
596
+ };
597
+ }
598
+ clearContext() {
599
+ this.deps.contextManager.clear();
600
+ this.systemPromptAdded = false;
601
+ }
602
+ setContext(messages) {
603
+ const { contextManager } = this.deps;
604
+ contextManager.clear();
605
+ const { prompt: systemPrompt } = this.buildSystemPrompt();
606
+ contextManager.addMessage({ role: "system", content: systemPrompt });
607
+ this.systemPromptAdded = true;
608
+ for (const msg of messages) {
609
+ if (msg.role === "system")
610
+ continue;
611
+ contextManager.addMessage({
612
+ role: msg.role,
613
+ content: msg.content,
614
+ name: msg.name,
615
+ });
616
+ }
617
+ }
618
+ shutdown() {
619
+ const { pluginManager, logger, sessionManager, contextManager } = this.deps;
620
+ contextManager.onCompact = null;
621
+ pluginManager.runOnSessionEnd({
622
+ logger,
623
+ sessionManager: sessionManager?.getActiveMeta(),
624
+ });
625
+ }
626
+ }