micro-models-agent 0.54.0 → 0.55.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 (2) hide show
  1. package/dist/main.js +369 -29
  2. package/package.json +50 -50
package/dist/main.js CHANGED
@@ -2356,6 +2356,12 @@ var init_defaults = __esm(() => {
2356
2356
  checkOnStart: true,
2357
2357
  autoInstall: true,
2358
2358
  intervalMs: 0
2359
+ },
2360
+ reasoning: {
2361
+ mode: "auto",
2362
+ min: "low",
2363
+ max: "high",
2364
+ overrideCooldown: 5
2359
2365
  }
2360
2366
  };
2361
2367
  });
@@ -2642,7 +2648,7 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2642
2648
  "cli.model_fetch_failed": "Failed to fetch models: {error}",
2643
2649
  "cli.available_models": "Available models:",
2644
2650
  "cli.cert_label": "supported — certified on this provider",
2645
- "cli.cert_stale_label": "certified on an older MMA version — re-run certify --force",
2651
+ "cli.cert_stale_label": "certified on an older MMA version — recommended",
2646
2652
  "cli.no_models_found": "No models found from provider",
2647
2653
  "cli.fetching_models": "Fetching model list...",
2648
2654
  "cli.manage_context": "Manage context window",
@@ -2708,6 +2714,7 @@ Available commands:`,
2708
2714
  "cli.no_agents_md": "Disable loading AGENTS.md files into system prompt",
2709
2715
  "cli.dir": "Working directory for the agent (defaults to current directory)",
2710
2716
  "cli.exit_on_complete": "Exit immediately when the task is done; interactive tools (question/approve) fail fast instead of blocking on stdin",
2717
+ "cli.reasoning_level": "Set reasoning effort level (auto|none|low|medium|high|max)",
2711
2718
  "cli.json": "Output the result as JSON (single-run mode)",
2712
2719
  "cli.security.description": "Manage security settings",
2713
2720
  "cli.security.status": "Show current security configuration",
@@ -2766,7 +2773,9 @@ Available commands:`,
2766
2773
  "repl.config": "Show current configuration",
2767
2774
  "repl.config_usage": "Usage: /config",
2768
2775
  "repl.reasoning": "Toggle showing model reasoning/thinking",
2769
- "repl.reasoning_usage": "Usage: /reasoning",
2776
+ "repl.reasoning_usage": "Usage: /reasoning [auto|none|low|medium|high|max]",
2777
+ "repl.reasoning_level_set": "Reasoning level set to: {level}",
2778
+ "repl.reasoning_invalid_level": "Invalid level '{level}'. Valid: {valid}",
2770
2779
  "repl.status": "Show agent status",
2771
2780
  "repl.status_usage": "Usage: /status",
2772
2781
  "repl.plugins": "List loaded plugins",
@@ -3067,6 +3076,15 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
3067
3076
  "tools.enable_added": "Enabled tool tags: {tags}. Available tools now: {tools}",
3068
3077
  "tools.hidden_header": "Additional tools (enable on demand via enable_tools or route to subagent tool_tags):",
3069
3078
  "tool.friendly.enable_tools": "Enable tools",
3079
+ "tool.friendly.set_thinking": "Set reasoning level",
3080
+ "tool.thinking_set": "Reasoning level: {level} ({reason})",
3081
+ "tool.thinking_set_clamped": "Reasoning level: {level} (clamped from request, {reason})",
3082
+ "tool.thinking_cooldown": "Reasoning override deferred ({remaining} iterations until available). Current: {current}",
3083
+ "tool.thinking_invalid_level": "Invalid reasoning level: {level}. Use: none, low, high, max",
3084
+ "reasoning.probe_failed": "Reasoning effort mechanism not supported by this backend. Dynamic control disabled.",
3085
+ "reasoning.mode_set": "Reasoning mode: {mode}",
3086
+ "reasoning.current": "Reasoning: {level} ({mode})",
3087
+ "reasoning.invalid_mode": "Invalid reasoning mode: {mode}. Use: auto, default, none, low, medium, high, max",
3070
3088
  "cli.provider_base_hint": "Base URL set to: {baseUrl}",
3071
3089
  "cli.add_provider": "Add a provider to the config",
3072
3090
  "cli.provider_no_url": 'No base URL for provider "{name}". Pass --url.',
@@ -3373,7 +3391,7 @@ var init_ru = __esm(() => {
3373
3391
  "cli.model_fetch_failed": "Не удалось получить модели: {error}",
3374
3392
  "cli.available_models": "Доступные модели:",
3375
3393
  "cli.cert_label": "поддерживается — сертифицирована на этом провайдере",
3376
- "cli.cert_stale_label": "сертифицирована на старой версии MMA — перезапустите certify --force",
3394
+ "cli.cert_stale_label": "сертифицирована на старой версии MMA — рекомендуется",
3377
3395
  "cli.no_models_found": "Модели не найдены у провайдера",
3378
3396
  "cli.fetching_models": "Загрузка списка моделей...",
3379
3397
  "cli.manage_context": "Управление контекстным окном",
@@ -3435,6 +3453,7 @@ var init_ru = __esm(() => {
3435
3453
  "cli.no_agents_md": "Отключить загрузку AGENTS.md в системный промпт",
3436
3454
  "cli.dir": "Рабочая директория агента (по умолчанию — текущая)",
3437
3455
  "cli.exit_on_complete": "Завершить сразу после выполнения задачи; интерактивные тулзы (question/approve) не блокируют stdin, а возвращают ошибку",
3456
+ "cli.reasoning_level": "Установить уровень рассуждений (auto|none|low|medium|high|max)",
3438
3457
  "cli.json": "Вывести результат в формате JSON (режим однократного запуска)",
3439
3458
  "cli.security.description": "Управление настройками безопасности",
3440
3459
  "cli.security.status": "Показать текущую конфигурацию безопасности",
@@ -3493,7 +3512,9 @@ var init_ru = __esm(() => {
3493
3512
  "repl.config": "Показать текущую конфигурацию",
3494
3513
  "repl.config_usage": "Использование: /config",
3495
3514
  "repl.reasoning": "Переключить показ рассуждений модели",
3496
- "repl.reasoning_usage": "Использование: /reasoning",
3515
+ "repl.reasoning_usage": "Использование: /reasoning [auto|none|low|medium|high|max]",
3516
+ "repl.reasoning_level_set": "Уровень рассуждений установлен: {level}",
3517
+ "repl.reasoning_invalid_level": "Неверный уровень '{level}'. Допустимые: {valid}",
3497
3518
  "repl.status": "Показать статус агента",
3498
3519
  "repl.status_usage": "Использование: /status",
3499
3520
  "repl.plugins": "Список загруженных плагинов",
@@ -3801,6 +3822,15 @@ var init_ru = __esm(() => {
3801
3822
  "tools.enable_added": "Включены теги тулов: {tags}. Теперь доступны тулы: {tools}",
3802
3823
  "tools.hidden_header": "Дополнительные тулы (включите по требованию через enable_tools или маршрутизируйте через subagent tool_tags):",
3803
3824
  "tool.friendly.enable_tools": "Включить тулы",
3825
+ "tool.friendly.set_thinking": "Уровень рассуждений",
3826
+ "tool.thinking_set": "Уровень рассуждений: {level} ({reason})",
3827
+ "tool.thinking_set_clamped": "Уровень рассуждений: {level} (ограничен конфигом, {reason})",
3828
+ "tool.thinking_cooldown": "Override отложен ({remaining} итераций до доступа). Текущий: {current}",
3829
+ "tool.thinking_invalid_level": "Неверный уровень рассуждений: {level}. Допустимые: none, low, high, max",
3830
+ "reasoning.probe_failed": "Бэкенд не поддерживает управление reasoning effort. Динамическое управление отключено.",
3831
+ "reasoning.mode_set": "Режим рассуждений: {mode}",
3832
+ "reasoning.current": "Рассуждения: {level} ({mode})",
3833
+ "reasoning.invalid_mode": "Неверный режим рассуждений: {mode}. Допустимые: auto, default, none, low, medium, high, max",
3804
3834
  "cli.provider_base_hint": "Базовый URL установлен: {baseUrl}",
3805
3835
  "cli.add_provider": "Добавить провайдера в конфиг",
3806
3836
  "cli.provider_no_url": 'Нет базового URL для провайдера "{name}". Укажите --url.',
@@ -4293,7 +4323,8 @@ var init_domains = __esm(() => {
4293
4323
  "autoPlan",
4294
4324
  "showReasoning",
4295
4325
  "logLevel",
4296
- "locale"
4326
+ "locale",
4327
+ "reasoning"
4297
4328
  ],
4298
4329
  provider: ["provider", "orchestrator", "retry", "pricing"],
4299
4330
  moe: ["moe", "experts"],
@@ -5507,8 +5538,14 @@ function buildRequestBody(opts) {
5507
5538
  };
5508
5539
  if (opts.maxTokens !== undefined)
5509
5540
  body.max_tokens = opts.maxTokens;
5510
- if (opts.reasoningEffort) {
5511
- body.reasoning_effort = opts.reasoningEffort;
5541
+ const strategy = opts.reasoningStrategy ?? "openai-effort";
5542
+ const level = opts.reasoningEffort;
5543
+ if (strategy === "openai-effort" && level && level !== "default") {
5544
+ body.reasoning_effort = level;
5545
+ } else if (strategy === "template-kwarg") {
5546
+ body.chat_template_kwargs = { enable_thinking: level !== "none" };
5547
+ } else if (strategy === "prompt-tag" && level && level !== "default") {
5548
+ body.reasoning_effort = level;
5512
5549
  }
5513
5550
  if (opts.tools && opts.tools.length > 0) {
5514
5551
  body.tools = opts.tools.map((t2) => ({
@@ -5550,7 +5587,11 @@ class OpenAICompatProvider {
5550
5587
  throw new Error(`Rate limit exceeded: ${this.rateLimiter.getConfig().maxRequestsPerMinute} requests per minute`);
5551
5588
  }
5552
5589
  this.rateLimiter.recordRequest();
5553
- const streamResult = this.doStream(messages, tools, signal, options);
5590
+ let effectiveMessages = messages;
5591
+ if (options?.reasoningStrategy === "prompt-tag" && options?.reasoningEffort === "none") {
5592
+ effectiveMessages = this.applyPromptTag(messages, "/no_think");
5593
+ }
5594
+ const streamResult = this.doStream(effectiveMessages, tools, signal, options);
5554
5595
  let hasToolCall = false;
5555
5596
  let hasText = false;
5556
5597
  for await (const chunk of streamResult) {
@@ -5561,12 +5602,29 @@ class OpenAICompatProvider {
5561
5602
  yield chunk;
5562
5603
  }
5563
5604
  if (!hasToolCall && !hasText) {
5564
- const fallback = await this.doNonStreaming(messages, tools, signal, options);
5605
+ const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options);
5565
5606
  for (const chunk of fallback) {
5566
5607
  yield chunk;
5567
5608
  }
5568
5609
  }
5569
5610
  }
5611
+ applyPromptTag(messages, tag) {
5612
+ const copy = [...messages];
5613
+ for (let i = copy.length - 1;i >= 0; i--) {
5614
+ if (copy[i].role === "user") {
5615
+ const msg = { ...copy[i] };
5616
+ if (typeof msg.content === "string") {
5617
+ msg.content = msg.content + `
5618
+ ` + tag;
5619
+ } else {
5620
+ msg.content = [...msg.content, { type: "text", text: tag }];
5621
+ }
5622
+ copy[i] = msg;
5623
+ break;
5624
+ }
5625
+ }
5626
+ return copy;
5627
+ }
5570
5628
  async* doStream(messages, tools, signal, options) {
5571
5629
  const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
5572
5630
  const streamRetries = maxStreamRetries ?? 2;
@@ -5601,7 +5659,8 @@ class OpenAICompatProvider {
5601
5659
  tools,
5602
5660
  stream: true,
5603
5661
  maxTokens,
5604
- reasoningEffort: options?.reasoningEffort
5662
+ reasoningEffort: options?.reasoningEffort,
5663
+ reasoningStrategy: options?.reasoningStrategy
5605
5664
  });
5606
5665
  const { headers, abortSignal, cleanup, isTimeout, flagTimeout, controller } = this.buildRequestSetup(signal);
5607
5666
  let response;
@@ -5806,7 +5865,8 @@ class OpenAICompatProvider {
5806
5865
  tools,
5807
5866
  stream: false,
5808
5867
  maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5809
- reasoningEffort: options?.reasoningEffort
5868
+ reasoningEffort: options?.reasoningEffort,
5869
+ reasoningStrategy: options?.reasoningStrategy
5810
5870
  });
5811
5871
  const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal);
5812
5872
  try {
@@ -5990,7 +6050,7 @@ var init_presets = __esm(() => {
5990
6050
  streaming: true,
5991
6051
  tools: true,
5992
6052
  vision: false,
5993
- reasoningEffort: false,
6053
+ reasoningStrategy: "prompt-tag",
5994
6054
  listModels: true,
5995
6055
  requiresKey: false,
5996
6056
  auth: "bearer"
@@ -6005,7 +6065,7 @@ var init_presets = __esm(() => {
6005
6065
  streaming: true,
6006
6066
  tools: true,
6007
6067
  vision: true,
6008
- reasoningEffort: false,
6068
+ reasoningStrategy: "openai-effort",
6009
6069
  listModels: true,
6010
6070
  requiresKey: true,
6011
6071
  auth: "bearer"
@@ -6020,7 +6080,7 @@ var init_presets = __esm(() => {
6020
6080
  streaming: true,
6021
6081
  tools: true,
6022
6082
  vision: true,
6023
- reasoningEffort: true,
6083
+ reasoningStrategy: "openai-effort",
6024
6084
  listModels: true,
6025
6085
  requiresKey: true,
6026
6086
  auth: "bearer"
@@ -6035,7 +6095,7 @@ var init_presets = __esm(() => {
6035
6095
  streaming: false,
6036
6096
  tools: false,
6037
6097
  vision: true,
6038
- reasoningEffort: false,
6098
+ reasoningStrategy: "none",
6039
6099
  listModels: false,
6040
6100
  requiresKey: true,
6041
6101
  auth: "header"
@@ -6050,7 +6110,7 @@ var init_presets = __esm(() => {
6050
6110
  streaming: true,
6051
6111
  tools: true,
6052
6112
  vision: false,
6053
- reasoningEffort: false,
6113
+ reasoningStrategy: "openai-effort",
6054
6114
  listModels: true,
6055
6115
  requiresKey: false,
6056
6116
  auth: "bearer"
@@ -6065,7 +6125,7 @@ var init_presets = __esm(() => {
6065
6125
  streaming: true,
6066
6126
  tools: true,
6067
6127
  vision: false,
6068
- reasoningEffort: false,
6128
+ reasoningStrategy: "openai-effort",
6069
6129
  listModels: true,
6070
6130
  requiresKey: false,
6071
6131
  auth: "bearer"
@@ -6562,6 +6622,8 @@ class ToolExecutor {
6562
6622
  const timeoutPromise = new Promise((_, reject) => {
6563
6623
  setTimeout(() => {
6564
6624
  killByCallId(call.id);
6625
+ this.ctx.cleanup?.();
6626
+ this.ctx.cleanup = undefined;
6565
6627
  reject(new Error(t("tool.timeout", {
6566
6628
  name: call.name,
6567
6629
  seconds: Math.round(timeoutMs / 1000)
@@ -6576,6 +6638,8 @@ class ToolExecutor {
6576
6638
  const abortPromise = new Promise((_, reject) => {
6577
6639
  const onAbort = () => {
6578
6640
  killByCallId(call.id);
6641
+ this.ctx.cleanup?.();
6642
+ this.ctx.cleanup = undefined;
6579
6643
  reject(new Error(t("tool.aborted", { name: call.name })));
6580
6644
  };
6581
6645
  if (signal.aborted) {
@@ -9544,6 +9608,18 @@ class SessionLogger {
9544
9608
  skills: data.skills
9545
9609
  });
9546
9610
  }
9611
+ logReasoningControl(data) {
9612
+ this.session?.appendLog({
9613
+ ts: new Date().toISOString(),
9614
+ type: "reasoning_control",
9615
+ content: `reasoning level: ${data.level} (source: ${data.source ?? "policy"})`,
9616
+ iteration: data.iteration,
9617
+ level: data.level,
9618
+ strategy: data.strategy,
9619
+ probePassed: data.probePassed,
9620
+ source: data.source
9621
+ });
9622
+ }
9547
9623
  }
9548
9624
  var init_session_logger = __esm(() => {
9549
9625
  init_audit_log();
@@ -12651,6 +12727,42 @@ var init_pricing = __esm(() => {
12651
12727
  init_prices();
12652
12728
  });
12653
12729
 
12730
+ // src/modules/reasoning/policy.ts
12731
+ function createPolicyState() {
12732
+ return { iterationsSinceRaise: 0, boostRemaining: 0 };
12733
+ }
12734
+ function evaluateReasoningPolicy(input, state) {
12735
+ if (input.hallucinationRetry || input.auditRejection || input.recoverableLlmError) {
12736
+ state.iterationsSinceRaise = 0;
12737
+ return { level: "high", reason: input.hallucinationRetry ? "policy:hallucination-retry" : input.auditRejection ? "policy:audit-rejection" : "policy:recoverable-llm-error" };
12738
+ }
12739
+ if (input.planCreated) {
12740
+ state.boostRemaining = 3;
12741
+ state.iterationsSinceRaise = 0;
12742
+ return { level: "high", reason: "policy:plan-created" };
12743
+ }
12744
+ if (state.boostRemaining > 0) {
12745
+ state.boostRemaining--;
12746
+ state.iterationsSinceRaise = 0;
12747
+ return { level: "high", reason: "policy:plan-boost" };
12748
+ }
12749
+ if (input.consecutiveSuccesses >= 2) {
12750
+ state.iterationsSinceRaise++;
12751
+ return { level: "low", reason: "policy:routine-write" };
12752
+ }
12753
+ if (input.isRepetitive) {
12754
+ state.iterationsSinceRaise++;
12755
+ return { level: "low", reason: "policy:repetitive-tools" };
12756
+ }
12757
+ if (state.iterationsSinceRaise >= DECAY_THRESHOLD) {
12758
+ state.iterationsSinceRaise = 0;
12759
+ return { level: input.baseline, reason: "policy:decay" };
12760
+ }
12761
+ state.iterationsSinceRaise++;
12762
+ return { level: input.baseline, reason: "policy:default" };
12763
+ }
12764
+ var DECAY_THRESHOLD = 5;
12765
+
12654
12766
  // src/core/agent.ts
12655
12767
  function isToolCallJson(text) {
12656
12768
  const trimmed = text.trim();
@@ -12678,13 +12790,28 @@ class Agent {
12678
12790
  abortController = null;
12679
12791
  lastCompactionShown = 0;
12680
12792
  costTracker;
12793
+ policyState;
12794
+ reasoningState;
12795
+ _currentIteration = 0;
12681
12796
  constructor(deps) {
12682
12797
  this.deps = deps;
12683
12798
  this.costTracker = new CostTracker(deps.config.model, deps.config.pricing);
12799
+ this.policyState = createPolicyState();
12800
+ const initialLevel = (deps.config.reasoning?.mode !== "auto" ? deps.config.reasoning?.mode : undefined) ?? "medium";
12801
+ this.reasoningState = { level: initialLevel, overrideIteration: -100 };
12684
12802
  }
12685
12803
  get contextManager() {
12686
12804
  return this.deps.contextManager;
12687
12805
  }
12806
+ get reasoningLevel() {
12807
+ return this.reasoningState.level;
12808
+ }
12809
+ setReasoningLevel(level) {
12810
+ this.reasoningState.level = level;
12811
+ }
12812
+ get currentIteration() {
12813
+ return this._currentIteration;
12814
+ }
12688
12815
  callerProvenance() {
12689
12816
  return {
12690
12817
  provider: this.deps.config.provider?.type || "unknown",
@@ -12912,13 +13039,22 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12912
13039
  let repeatedToolCount = 0;
12913
13040
  const MAX_REPEATED_TOOL_CALLS = 2;
12914
13041
  let llmErrorRetries = 0;
13042
+ let iterHallucinationRetry = false;
13043
+ let iterAuditRejection = false;
13044
+ let iterRecoverableLlmError = false;
13045
+ let iterPlanCreated = false;
12915
13046
  let allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
12916
13047
  let boundedToolNames = new Set(allToolsForBudget.filter((t2) => t2.boundedOutput).map((t2) => t2.name));
12917
13048
  let toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
12918
13049
  contextManager.setToolTokens(toolTokenEstimate);
12919
13050
  while (iteration < config.maxToolIterations && !this.shutdownRequested) {
12920
13051
  iteration++;
13052
+ this._currentIteration = iteration;
12921
13053
  contextManager.noteIteration();
13054
+ iterHallucinationRetry = false;
13055
+ iterAuditRejection = false;
13056
+ iterRecoverableLlmError = false;
13057
+ iterPlanCreated = false;
12922
13058
  allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
12923
13059
  boundedToolNames = new Set(allToolsForBudget.filter((t2) => t2.boundedOutput).map((t2) => t2.name));
12924
13060
  toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
@@ -12989,8 +13125,35 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12989
13125
  const promptBefore = apiPromptTokens;
12990
13126
  const completionBefore = apiCompletionTokens;
12991
13127
  logger.logLLMRequest(config.model, history.length, input, "agent");
13128
+ const reasoningConfig = config.reasoning;
13129
+ let effectiveReasoningLevel;
13130
+ let reasoningStrategy = this.deps.reasoningStrategy;
13131
+ if (reasoningConfig && reasoningConfig.mode === "auto" && this.deps.reasoningProbePassed) {
13132
+ const policyResult = evaluateReasoningPolicy({
13133
+ hallucinationRetry: iterHallucinationRetry,
13134
+ auditRejection: iterAuditRejection,
13135
+ recoverableLlmError: iterRecoverableLlmError,
13136
+ planCreated: iterPlanCreated,
13137
+ consecutiveSuccesses: 0,
13138
+ isRepetitive: false,
13139
+ baseline: "medium"
13140
+ }, this.policyState);
13141
+ effectiveReasoningLevel = policyResult.level;
13142
+ } else if (reasoningConfig && reasoningConfig.mode !== "auto") {
13143
+ effectiveReasoningLevel = reasoningConfig.mode;
13144
+ }
13145
+ if (effectiveReasoningLevel) {
13146
+ slog.logReasoningControl({
13147
+ iteration,
13148
+ level: effectiveReasoningLevel,
13149
+ strategy: reasoningStrategy,
13150
+ probePassed: this.deps.reasoningProbePassed,
13151
+ source: this.reasoningState.overrideIteration === iteration ? "agent_override" : "policy"
13152
+ });
13153
+ contextManager.thinkingLevel = effectiveReasoningLevel;
13154
+ }
12992
13155
  try {
12993
- for await (const chunk of llmProvider.chat(history, allToolsForBudget, this.abortController?.signal)) {
13156
+ for await (const chunk of llmProvider.chat(history, allToolsForBudget, this.abortController?.signal, effectiveReasoningLevel ? { reasoningEffort: effectiveReasoningLevel, reasoningStrategy } : undefined)) {
12994
13157
  if (this.shutdownRequested)
12995
13158
  break;
12996
13159
  if (chunk.type === "text" && chunk.content) {
@@ -13037,6 +13200,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
13037
13200
  break;
13038
13201
  }
13039
13202
  if (err?.recoverableLlm && llmErrorRetries < MAX_LLM_ERROR_RETRIES) {
13203
+ iterRecoverableLlmError = true;
13040
13204
  llmErrorRetries++;
13041
13205
  logger.warn(`Recoverable LLM error, feeding back (${llmErrorRetries}/${MAX_LLM_ERROR_RETRIES}): ${err.message}`);
13042
13206
  slog.logError(err.message);
@@ -13313,6 +13477,7 @@ ${warnLine}
13313
13477
  }
13314
13478
  }
13315
13479
  if (hallucinationResult.status === "retry") {
13480
+ iterHallucinationRetry = true;
13316
13481
  if (this.deps.exitOnComplete && textContent?.trim() && !this.deps.finalAudit) {
13317
13482
  logger.debug("Exit-on-complete: stopping on first response");
13318
13483
  lastText = textContent;
@@ -13391,6 +13556,7 @@ ${warnLine}
13391
13556
  if (this.deps.finalAudit) {
13392
13557
  const audit = await this.deps.finalAudit();
13393
13558
  if (audit && !audit.passed) {
13559
+ iterAuditRejection = true;
13394
13560
  logger.warn(`Final audit incomplete: ${audit.summary}`);
13395
13561
  const steps = audit.pendingSteps.slice(0, 5).join("; ") || "—";
13396
13562
  contextManager.addMessage({
@@ -13746,6 +13912,7 @@ class ContextManager {
13746
13912
  tokenCounter;
13747
13913
  pendingImageParts = [];
13748
13914
  toolTokens = 0;
13915
+ thinkingLevel = null;
13749
13916
  onCompact = null;
13750
13917
  planSummaryProvider = null;
13751
13918
  constructor(contextWindow, contextBudget, tokenCounter) {
@@ -13939,6 +14106,8 @@ class ContextManager {
13939
14106
  const errorsLine = this.facts.errorsLine();
13940
14107
  if (errorsLine)
13941
14108
  parts.push(errorsLine);
14109
+ if (this.thinkingLevel)
14110
+ parts.push(`[Thinking: ${this.thinkingLevel}]`);
13942
14111
  const triedAndFailed = extractTriedAndFailed(oldTurns);
13943
14112
  if (triedAndFailed.length > 0) {
13944
14113
  const lines = triedAndFailed.map((t2) => `- ${t2.tool}(${t2.args}): ${t2.error} (failed ${t2.count}x)`);
@@ -14759,6 +14928,7 @@ var init_subagent = __esm(() => {
14759
14928
  icon: "\uD83E\uDD16",
14760
14929
  description: "Spawn an isolated sub-agent to work on a task independently. The sub-agent has its own context and executes autonomously. Use for parallel work or complex sub-tasks.",
14761
14930
  tags: ["code"],
14931
+ timeoutMs: 180000,
14762
14932
  parameters: {
14763
14933
  type: "object",
14764
14934
  properties: {
@@ -14851,6 +15021,7 @@ var init_subagent = __esm(() => {
14851
15021
  const hallucinationDetector = new HallucinationDetector(ctx.baseDir, ctx.llmProvider);
14852
15022
  const parentScope = ctx.scope;
14853
15023
  ctx.toolExecutor.setScope(scope || { allowed_files: [], read_only_files: [] });
15024
+ ctx.cleanup = () => ctx.toolExecutor?.setScope(parentScope);
14854
15025
  const systemPrompt = {
14855
15026
  content: buildSubagentSystemPrompt(context, ctx.config.subagent?.stableSystemPrompt !== false),
14856
15027
  priority: "critical",
@@ -14912,6 +15083,7 @@ Iterations: ${result.iterationCount}`
14912
15083
  };
14913
15084
  } finally {
14914
15085
  ctx.toolExecutor.setScope(parentScope);
15086
+ ctx.cleanup = undefined;
14915
15087
  }
14916
15088
  } catch (e) {
14917
15089
  return { success: false, output: `Sub-agent error: ${e.message}` };
@@ -15091,7 +15263,7 @@ var init_chunk_query = __esm(() => {
15091
15263
  },
15092
15264
  reasoning_effort: {
15093
15265
  type: "string",
15094
- enum: ["none", "low", "medium", "high"],
15266
+ enum: ["none", "low", "medium", "high", "max"],
15095
15267
  description: 'Reasoning effort for chunk/synthesis calls (default "none"). "none" prevents long thinking chains from eating the token budget on reasoning models.'
15096
15268
  },
15097
15269
  synthesize: {
@@ -15131,7 +15303,7 @@ var init_chunk_query = __esm(() => {
15131
15303
  synthesize: typeof args.synthesize === "boolean" ? args.synthesize : undefined,
15132
15304
  maxChunkTokens: typeof args.max_chunk_tokens === "number" ? args.max_chunk_tokens : undefined,
15133
15305
  maxSynthesisTokens: typeof args.max_synthesis_tokens === "number" ? args.max_synthesis_tokens : undefined,
15134
- reasoningEffort: ["none", "low", "medium", "high"].includes(args.reasoning_effort) ? args.reasoning_effort : undefined,
15306
+ reasoningEffort: ["none", "low", "medium", "high", "max"].includes(args.reasoning_effort) ? args.reasoning_effort : undefined,
15135
15307
  logger: ctx.logger
15136
15308
  });
15137
15309
  ctx.logger.debug(`chunk_query: ${res.chunks} chunk(s), ${res.totalChars} chars, max parallel ${res.maxParallelObserved}`);
@@ -23341,6 +23513,126 @@ function syncGlobalManifest(override, bundledPathOverride) {
23341
23513
  var certKey = (e) => `${e.model}::${e.providerUrl}`;
23342
23514
  var init_manifest = () => {};
23343
23515
 
23516
+ // src/modules/reasoning/probe.ts
23517
+ var exports_probe = {};
23518
+ __export(exports_probe, {
23519
+ setCachedProbeResult: () => setCachedProbeResult,
23520
+ resetProbeCache: () => resetProbeCache,
23521
+ probeReasoningSupport: () => probeReasoningSupport,
23522
+ getCachedProbeResult: () => getCachedProbeResult,
23523
+ cacheKey: () => cacheKey
23524
+ });
23525
+ async function probeReasoningSupport(provider, strategy, signal) {
23526
+ if (strategy === "none")
23527
+ return false;
23528
+ try {
23529
+ let hasReasoning = false;
23530
+ for await (const chunk of provider.chat(PROBE_MESSAGES, undefined, signal, {
23531
+ maxTokens: 32,
23532
+ reasoningEffort: "none",
23533
+ reasoningStrategy: strategy
23534
+ })) {
23535
+ if (chunk.type === "reasoning")
23536
+ hasReasoning = true;
23537
+ }
23538
+ return !hasReasoning;
23539
+ } catch {
23540
+ return false;
23541
+ }
23542
+ }
23543
+ function cacheKey(baseUrl, model) {
23544
+ return `${baseUrl}|${model}`;
23545
+ }
23546
+ function getCachedProbeResult(key) {
23547
+ return probeCache.get(key);
23548
+ }
23549
+ function setCachedProbeResult(key, result) {
23550
+ probeCache.set(key, result);
23551
+ }
23552
+ function resetProbeCache() {
23553
+ probeCache.clear();
23554
+ }
23555
+ var PROBE_MESSAGES, probeCache;
23556
+ var init_probe = __esm(() => {
23557
+ PROBE_MESSAGES = [
23558
+ { role: "user", content: "Reply with exactly: ok" }
23559
+ ];
23560
+ probeCache = new Map;
23561
+ });
23562
+
23563
+ // src/tools/set-thinking.ts
23564
+ var exports_set_thinking = {};
23565
+ __export(exports_set_thinking, {
23566
+ createSetThinkingTool: () => createSetThinkingTool
23567
+ });
23568
+ function createSetThinkingTool(state, config, getCurrentIteration) {
23569
+ return {
23570
+ name: "set_thinking",
23571
+ description: "Set reasoning effort level for the next LLM call. Normally managed automatically by the harness. Use only to force deeper thinking for genuinely hard subproblems or shallower for mechanical batches.",
23572
+ icon: "\uD83E\uDDE0",
23573
+ tags: [],
23574
+ alwaysOn: true,
23575
+ parameters: {
23576
+ type: "object",
23577
+ properties: {
23578
+ level: {
23579
+ type: "string",
23580
+ enum: ["none", "low", "high", "max"],
23581
+ description: "Reasoning effort level to set"
23582
+ },
23583
+ reason: {
23584
+ type: "string",
23585
+ description: "Why changing the level"
23586
+ }
23587
+ },
23588
+ required: ["level"]
23589
+ },
23590
+ handler: async (_ctx, args) => {
23591
+ const requested = args.level;
23592
+ const reason = args.reason || "agent override";
23593
+ if (!VALID_LEVELS.includes(requested) && requested !== "auto") {
23594
+ return {
23595
+ success: false,
23596
+ output: t("tool.thinking_invalid_level", { level: requested })
23597
+ };
23598
+ }
23599
+ const currentIter = getCurrentIteration();
23600
+ const elapsed = currentIter - state.overrideIteration;
23601
+ if (elapsed > 0 && elapsed < config.overrideCooldown) {
23602
+ return {
23603
+ success: true,
23604
+ output: t("tool.thinking_cooldown", {
23605
+ remaining: config.overrideCooldown - elapsed,
23606
+ current: state.level
23607
+ })
23608
+ };
23609
+ }
23610
+ let level = requested;
23611
+ const levelOrder = ["none", "low", "medium", "high", "max"];
23612
+ const minIdx = levelOrder.indexOf(config.min);
23613
+ const maxIdx = levelOrder.indexOf(config.max);
23614
+ const reqIdx = levelOrder.indexOf(level);
23615
+ let clamped = false;
23616
+ if (reqIdx >= 0 && reqIdx < minIdx) {
23617
+ level = config.min;
23618
+ clamped = true;
23619
+ } else if (reqIdx > maxIdx) {
23620
+ level = config.max;
23621
+ clamped = true;
23622
+ }
23623
+ state.level = level;
23624
+ state.overrideIteration = currentIter;
23625
+ const msg = clamped ? t("tool.thinking_set_clamped", { level, reason }) : t("tool.thinking_set", { level, reason });
23626
+ return { success: true, output: msg };
23627
+ }
23628
+ };
23629
+ }
23630
+ var VALID_LEVELS;
23631
+ var init_set_thinking = __esm(() => {
23632
+ init_i18n();
23633
+ VALID_LEVELS = ["default", "none", "low", "medium", "high", "max"];
23634
+ });
23635
+
23344
23636
  // src/core/bootstrap.ts
23345
23637
  var exports_bootstrap = {};
23346
23638
  __export(exports_bootstrap, {
@@ -23401,11 +23693,17 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
23401
23693
  return lines.join(`
23402
23694
  `);
23403
23695
  }
23404
- async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
23696
+ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete, reasoningLevel) {
23405
23697
  const dir = configDir || process.env.MMA_CONFIG_DIR || join41(homedir14(), ".mma");
23406
23698
  const projectConfigPath = projectDir ? join41(projectDir, ".mmrc") : join41(process.cwd(), ".mmrc");
23407
23699
  const { config, legacyDetected } = loadConfig({ configDir: dir, projectConfigPath });
23408
23700
  setLocale(config.locale);
23701
+ if (reasoningLevel && reasoningLevel !== "auto") {
23702
+ if (!config.reasoning)
23703
+ config.reasoning = { mode: reasoningLevel, min: "low", max: "high", overrideCooldown: 3 };
23704
+ else
23705
+ config.reasoning.mode = reasoningLevel;
23706
+ }
23409
23707
  try {
23410
23708
  const { globalAuditNotifier: globalAuditNotifier2 } = await Promise.resolve().then(() => (init_audit_notifier(), exports_audit_notifier));
23411
23709
  if (config.security?.auditNotifier) {
@@ -23457,6 +23755,29 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
23457
23755
  logger.warn(`provider failover: ${from} -> ${to} (${error.message.slice(0, 120)})`);
23458
23756
  });
23459
23757
  }
23758
+ const providerSpec = BUILTIN_PROVIDERS.find((p) => p.type === config.provider.type);
23759
+ const reasoningStrategy = providerSpec?.capabilities.reasoningStrategy ?? "none";
23760
+ let reasoningProbePassed = false;
23761
+ if (config.reasoning && config.reasoning.mode !== "none" && reasoningStrategy !== "none") {
23762
+ try {
23763
+ const { probeReasoningSupport: probeReasoningSupport2, getCachedProbeResult: getCachedProbeResult2, setCachedProbeResult: setCachedProbeResult2 } = await Promise.resolve().then(() => (init_probe(), exports_probe));
23764
+ const cacheK = `${config.provider.baseUrl ?? ""}|${config.model}`;
23765
+ const cached = getCachedProbeResult2(cacheK);
23766
+ if (cached !== undefined) {
23767
+ reasoningProbePassed = cached;
23768
+ } else {
23769
+ reasoningProbePassed = await probeReasoningSupport2(llmProvider, reasoningStrategy);
23770
+ setCachedProbeResult2(cacheK, reasoningProbePassed);
23771
+ }
23772
+ if (reasoningProbePassed) {
23773
+ logger.info(`Reasoning probe: strategy=${reasoningStrategy} → mechanism respected`);
23774
+ } else {
23775
+ logger.info(`Reasoning probe: strategy=${reasoningStrategy} → mechanism IGNORED`);
23776
+ }
23777
+ } catch {
23778
+ reasoningProbePassed = false;
23779
+ }
23780
+ }
23460
23781
  const baseDir = projectDir ? resolve23(projectDir) : process.cwd();
23461
23782
  const envReport = collectEnvironment({
23462
23783
  configDir: dir,
@@ -23723,9 +24044,16 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
23723
24044
  sessionManager,
23724
24045
  memoryStore,
23725
24046
  moduleRegistry,
23726
- exitOnComplete
24047
+ exitOnComplete,
24048
+ reasoningProbePassed,
24049
+ reasoningStrategy
23727
24050
  };
23728
24051
  const agent = new Agent(agentDeps);
24052
+ if (config.reasoning) {
24053
+ const { createSetThinkingTool: createSetThinkingTool2 } = await Promise.resolve().then(() => (init_set_thinking(), exports_set_thinking));
24054
+ const setThinkingTool = createSetThinkingTool2(agent.reasoningState, config.reasoning, () => agent.currentIteration);
24055
+ toolRegistry.register(setThinkingTool);
24056
+ }
23729
24057
  return {
23730
24058
  agent,
23731
24059
  config,
@@ -23746,6 +24074,7 @@ var init_bootstrap = __esm(() => {
23746
24074
  init_config2();
23747
24075
  init_app_logger();
23748
24076
  init_providers();
24077
+ init_presets();
23749
24078
  init_tools();
23750
24079
  init_hidden_tools_block();
23751
24080
  init_executor();
@@ -31528,7 +31857,8 @@ var init_scenarios = __esm(() => {
31528
31857
  }
31529
31858
  };
31530
31859
  PROVIDER_BASE = {
31531
- provider: { maxCompletionTokens: 16384 }
31860
+ provider: { maxCompletionTokens: 16384 },
31861
+ reasoning: { mode: "none", min: "low", max: "high", overrideCooldown: 3 }
31532
31862
  };
31533
31863
  SCENARIO_DEFS = [
31534
31864
  {
@@ -32856,10 +33186,19 @@ function registerMmaCommands(ctx) {
32856
33186
  name: "reasoning",
32857
33187
  description: t("repl.reasoning"),
32858
33188
  usage: t("repl.reasoning_usage"),
32859
- action: () => {
32860
- ctx.config.showReasoning = !ctx.config.showReasoning;
32861
- const status = ctx.config.showReasoning ? pc2.green(t("repl.show")) : pc2.dim(t("repl.hide"));
32862
- console.log(t("repl.reasoning_status", { status }));
33189
+ action: (args) => {
33190
+ const level = args[0]?.trim().toLowerCase();
33191
+ const VALID_LEVELS2 = ["auto", "none", "low", "medium", "high", "max"];
33192
+ if (!level) {
33193
+ ctx.config.showReasoning = !ctx.config.showReasoning;
33194
+ const status = ctx.config.showReasoning ? pc2.green(t("repl.show")) : pc2.dim(t("repl.hide"));
33195
+ console.log(t("repl.reasoning_status", { status }));
33196
+ } else if (VALID_LEVELS2.includes(level)) {
33197
+ ctx.agent.setReasoningLevel(level);
33198
+ console.log(pc2.green(t("repl.reasoning_level_set", { level })));
33199
+ } else {
33200
+ console.log(pc2.yellow(t("repl.reasoning_invalid_level", { level, valid: VALID_LEVELS2.join(", ") })));
33201
+ }
32863
33202
  }
32864
33203
  });
32865
33204
  ctx.registerCommand({
@@ -34101,7 +34440,7 @@ init_presets();
34101
34440
  init_version();
34102
34441
  var version = readMmaVersion();
34103
34442
  function createProgram() {
34104
- const program2 = new Command().name("mma").description(t("cli.description")).version(version).option("--no-agents-md", t("cli.no_agents_md")).option("-d, --dir <path>", t("cli.dir")).option("-e, --exit-on-complete", t("cli.exit_on_complete")).option("-j, --json", t("cli.json"));
34443
+ const program2 = new Command().name("mma").description(t("cli.description")).version(version).option("--no-agents-md", t("cli.no_agents_md")).option("-d, --dir <path>", t("cli.dir")).option("-e, --exit-on-complete", t("cli.exit_on_complete")).option("-j, --json", t("cli.json")).option("--reasoning <level>", t("cli.reasoning_level"), "auto");
34105
34444
  program2.command("init").description(t("cli.init")).action(async () => {
34106
34445
  const answers = await runSetup();
34107
34446
  const configPath = join47(homedir16(), ".mma", "config.json");
@@ -36832,13 +37171,14 @@ async function main() {
36832
37171
  const projectDir = opts.dir;
36833
37172
  const exitOnComplete = opts.exitOnComplete === true;
36834
37173
  const jsonMode = opts.json === true;
37174
+ const reasoningLevel = opts.reasoning;
36835
37175
  const isSubcommand = program2.args.length > 0 && cmdNames.has(program2.args[0]);
36836
37176
  if (isSubcommand) {
36837
37177
  return;
36838
37178
  }
36839
37179
  if (program2.args.length > 0) {
36840
37180
  const prompt = program2.args.join(" ");
36841
- const { agent, config, baseDir, legacyDetected } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
37181
+ const { agent, config, baseDir, legacyDetected } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete, reasoningLevel);
36842
37182
  if (legacyDetected) {
36843
37183
  console.error(pc2.yellow(` ${t("config.legacy_hint")}`));
36844
37184
  }
package/package.json CHANGED
@@ -1,50 +1,50 @@
1
- {
2
- "name": "micro-models-agent",
3
- "version": "0.54.0",
4
- "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
- "type": "module",
6
- "bin": {
7
- "mma": "bin/mma.mjs"
8
- },
9
- "files": [
10
- "dist/",
11
- "bin/"
12
- ],
13
- "engines": {
14
- "node": ">=20"
15
- },
16
- "scripts": {
17
- "mma": "bun run src/cli/main.ts",
18
- "build": "bun run build:tsc && bun run build:copy-assets",
19
- "build:tsc": "tsc -p tsconfig.build.json",
20
- "build:copy-assets": "bun run scripts/copy-assets.ts",
21
- "build:prod": "bun run 'build:bundle' && bun run 'build:copy-assets'",
22
- "build:clean": "cmd /c \"if exist dist rmdir /s /q dist\"",
23
- "build:bundle": "bun build ./src/cli/main.ts --outfile ./dist/main.js --target node --format esm --external playwright",
24
- "dev": "bun --watch src/cli/main.ts",
25
- "typecheck": "tsc --noEmit",
26
- "test": "bun test",
27
- "test:watch": "bun test --watch",
28
- "test:integration": "vitest run --config vitest.integration.config.ts",
29
- "format": "prettier --write \"src/**/*.{ts,js,mjs,json}\" \"tests/**/*.{ts,js,json}\" \"scripts/**/*.ts\" \"*.{ts,json}\"",
30
- "format:check": "prettier --check \"src/**/*.{ts,js,mjs,json}\" \"tests/**/*.{ts,js,json}\" \"scripts/**/*.ts\" \"*.{ts,json}\""
31
- },
32
- "dependencies": {
33
- "commander": "^12.0.0",
34
- "js-tiktoken": "^1.0.0",
35
- "jsonrepair": "^3.15.0",
36
- "picocolors": "^1.1.1",
37
- "playwright": "^1.62.0",
38
- "string-width": "^8.2.2",
39
- "yaml": "^2.9.0"
40
- },
41
- "devDependencies": {
42
- "@types/bun": "^1.4.0",
43
- "@types/node": "^26.3.0",
44
- "@vitest/coverage-v8": "^4.1.11",
45
- "prettier": "^3.9.6",
46
- "tsconfig-paths": "^4.2.0",
47
- "typescript": "^7.0.2",
48
- "vitest": "^4.1.11"
49
- }
50
- }
1
+ {
2
+ "name": "micro-models-agent",
3
+ "version": "0.55.0",
4
+ "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
+ "type": "module",
6
+ "bin": {
7
+ "mma": "bin/mma.mjs"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "bin/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "scripts": {
17
+ "mma": "bun run src/cli/main.ts",
18
+ "build": "bun run build:tsc && bun run build:copy-assets",
19
+ "build:tsc": "tsc -p tsconfig.build.json",
20
+ "build:copy-assets": "bun run scripts/copy-assets.ts",
21
+ "build:prod": "bun run 'build:bundle' && bun run 'build:copy-assets'",
22
+ "build:clean": "cmd /c \"if exist dist rmdir /s /q dist\"",
23
+ "build:bundle": "bun build ./src/cli/main.ts --outfile ./dist/main.js --target node --format esm --external playwright",
24
+ "dev": "bun --watch src/cli/main.ts",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "bun test",
27
+ "test:watch": "bun test --watch",
28
+ "test:integration": "vitest run --config vitest.integration.config.ts",
29
+ "format": "prettier --write \"src/**/*.{ts,js,mjs,json}\" \"tests/**/*.{ts,js,json}\" \"scripts/**/*.ts\" \"*.{ts,json}\"",
30
+ "format:check": "prettier --check \"src/**/*.{ts,js,mjs,json}\" \"tests/**/*.{ts,js,json}\" \"scripts/**/*.ts\" \"*.{ts,json}\""
31
+ },
32
+ "dependencies": {
33
+ "commander": "^12.0.0",
34
+ "js-tiktoken": "^1.0.0",
35
+ "jsonrepair": "^3.15.0",
36
+ "picocolors": "^1.1.1",
37
+ "playwright": "^1.62.0",
38
+ "string-width": "^8.2.2",
39
+ "yaml": "^2.9.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/bun": "^1.4.0",
43
+ "@types/node": "^26.3.0",
44
+ "@vitest/coverage-v8": "^4.1.11",
45
+ "prettier": "^3.9.6",
46
+ "tsconfig-paths": "^4.2.0",
47
+ "typescript": "^5.9.3",
48
+ "vitest": "^4.1.11"
49
+ }
50
+ }