micro-models-agent 0.24.3 → 0.24.4

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 +147 -13
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2125,9 +2125,9 @@ var init_defaults = __esm(() => {
2125
2125
  offloadKvCacheToGpu: true
2126
2126
  },
2127
2127
  retry: {
2128
- maxRetries: 3,
2129
- baseDelay: 1000,
2130
- maxDelay: 30000
2128
+ maxRetries: 5,
2129
+ baseDelay: 5000,
2130
+ maxDelay: 120000
2131
2131
  },
2132
2132
  maxToolIterations: 1000,
2133
2133
  stuckThreshold: 8,
@@ -4884,7 +4884,8 @@ function runCommand(command, options = {}) {
4884
4884
  cwd,
4885
4885
  callId,
4886
4886
  timeoutMs = 120000,
4887
- maxBuffer = 10 * 1024 * 1024
4887
+ maxBuffer = 10 * 1024 * 1024,
4888
+ onOutput
4888
4889
  } = options;
4889
4890
  return new Promise((resolve) => {
4890
4891
  let timedOut = false;
@@ -4903,6 +4904,7 @@ function runCommand(command, options = {}) {
4903
4904
  const decodeChunk = (chunk) => decoder ? decoder.decode(chunk, { stream: true }) : chunk.toString("utf-8");
4904
4905
  child.stdout?.on("data", (chunk) => {
4905
4906
  const text = decodeChunk(chunk);
4907
+ onOutput?.(text);
4906
4908
  const next = stdoutRef.value.length + text.length;
4907
4909
  if (next > maxBuffer) {
4908
4910
  stdoutRef.value = stdoutRef.value.slice(stdoutRef.value.length + text.length - maxBuffer) + text;
@@ -4912,6 +4914,7 @@ function runCommand(command, options = {}) {
4912
4914
  });
4913
4915
  child.stderr?.on("data", (chunk) => {
4914
4916
  const text = decodeChunk(chunk);
4917
+ onOutput?.(text);
4915
4918
  const next = stderrRef.value.length + text.length;
4916
4919
  if (next > maxBuffer) {
4917
4920
  stderrRef.value = stderrRef.value.slice(stderrRef.value.length + text.length - maxBuffer) + text;
@@ -4945,7 +4948,13 @@ function runCommand(command, options = {}) {
4945
4948
  clearTimeout(timer);
4946
4949
  if (callId)
4947
4950
  activeChildren.delete(callId);
4948
- resolve({ stdout: stdoutRef.value, stderr: stderrRef.value, code, signal, timedOut });
4951
+ resolve({
4952
+ stdout: stdoutRef.value,
4953
+ stderr: stderrRef.value,
4954
+ code,
4955
+ signal,
4956
+ timedOut
4957
+ });
4949
4958
  });
4950
4959
  });
4951
4960
  }
@@ -4954,11 +4963,61 @@ var init_runner = __esm(() => {
4954
4963
  activeChildren = new Map;
4955
4964
  });
4956
4965
 
4966
+ // src/tools/cache.ts
4967
+ class ToolResultCache {
4968
+ cache = new Map;
4969
+ maxEntries;
4970
+ defaultTtlMs;
4971
+ constructor(maxEntries = 50, defaultTtlMs = 60000) {
4972
+ this.maxEntries = maxEntries;
4973
+ this.defaultTtlMs = defaultTtlMs;
4974
+ }
4975
+ get(key) {
4976
+ const entry = this.cache.get(key);
4977
+ if (!entry)
4978
+ return;
4979
+ if (Date.now() > entry.expiresAt) {
4980
+ this.cache.delete(key);
4981
+ return;
4982
+ }
4983
+ this.cache.delete(key);
4984
+ this.cache.set(key, entry);
4985
+ return entry.value;
4986
+ }
4987
+ set(key, value, ttlMs) {
4988
+ if (this.cache.size >= this.maxEntries) {
4989
+ const firstKey = this.cache.keys().next().value;
4990
+ if (firstKey !== undefined) {
4991
+ this.cache.delete(firstKey);
4992
+ }
4993
+ }
4994
+ this.cache.set(key, {
4995
+ value,
4996
+ expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs)
4997
+ });
4998
+ }
4999
+ has(key) {
5000
+ return this.get(key) !== undefined;
5001
+ }
5002
+ clear() {
5003
+ this.cache.clear();
5004
+ }
5005
+ get size() {
5006
+ return this.cache.size;
5007
+ }
5008
+ }
5009
+ function hashKey(toolName, args) {
5010
+ const payload = toolName + JSON.stringify(args);
5011
+ const hash = Bun.hash(payload).toString(16).slice(0, 16);
5012
+ return hash;
5013
+ }
5014
+
4957
5015
  // src/tools/executor.ts
4958
5016
  class ToolExecutor {
4959
5017
  registry;
4960
5018
  ctx;
4961
5019
  pluginManager;
5020
+ cache = new ToolResultCache(50, CACHE_TTL_MS);
4962
5021
  constructor(registry, ctx, pluginManager) {
4963
5022
  this.registry = registry;
4964
5023
  this.ctx = ctx;
@@ -4973,6 +5032,18 @@ class ToolExecutor {
4973
5032
  toolCallId: call.id
4974
5033
  };
4975
5034
  }
5035
+ if (CACHEABLE_TOOLS.has(call.name)) {
5036
+ const key = hashKey(call.name, call.arguments);
5037
+ const cached = this.cache.get(key);
5038
+ if (cached !== undefined) {
5039
+ this.ctx.logger.debug(`Tool ${call.name}: CACHE HIT`);
5040
+ return {
5041
+ success: true,
5042
+ output: cached,
5043
+ toolCallId: call.id
5044
+ };
5045
+ }
5046
+ }
4976
5047
  for (const plugin of this.pluginManager.getAllPlugins()) {
4977
5048
  if (plugin.onBeforeTool) {
4978
5049
  try {
@@ -5043,6 +5114,11 @@ class ToolExecutor {
5043
5114
  }
5044
5115
  }
5045
5116
  }
5117
+ if (result.success && CACHEABLE_TOOLS.has(call.name)) {
5118
+ const key = hashKey(call.name, call.arguments);
5119
+ this.cache.set(key, result.output);
5120
+ this.ctx.logger.debug(`Tool ${call.name}: CACHED`);
5121
+ }
5046
5122
  this.ctx.logger.debug(`Tool ${call.name}: ${result.success ? "OK" : "FAIL"}`);
5047
5123
  return result;
5048
5124
  }
@@ -5052,6 +5128,9 @@ class ToolExecutor {
5052
5128
  getRegistry() {
5053
5129
  return this.registry;
5054
5130
  }
5131
+ getContext() {
5132
+ return this.ctx;
5133
+ }
5055
5134
  setScope(scope) {
5056
5135
  this.ctx.scope = scope;
5057
5136
  }
@@ -5059,10 +5138,11 @@ class ToolExecutor {
5059
5138
  this.ctx.llmProvider = provider;
5060
5139
  }
5061
5140
  }
5062
- var TOOL_EXECUTION_TIMEOUT_MS = 60000;
5141
+ var TOOL_EXECUTION_TIMEOUT_MS = 60000, CACHEABLE_TOOLS, CACHE_TTL_MS = 60000;
5063
5142
  var init_executor = __esm(() => {
5064
5143
  init_runner();
5065
5144
  init_i18n();
5145
+ CACHEABLE_TOOLS = new Set(["web_fetch"]);
5066
5146
  });
5067
5147
 
5068
5148
  // src/modules/security/path-validator.ts
@@ -5593,7 +5673,7 @@ function safeResolvePath(baseDir, userPath) {
5593
5673
  var init_path_utils = () => {};
5594
5674
 
5595
5675
  // src/tools/preview.ts
5596
- var MAX_PREVIEW_LINES = 15;
5676
+ var MAX_PREVIEW_LINES = 100;
5597
5677
 
5598
5678
  // src/tools/read-file.ts
5599
5679
  import { readFileSync as readFileSync5, existsSync as existsSync9 } from "fs";
@@ -6957,8 +7037,14 @@ var init_bash = __esm(() => {
6957
7037
  type: "object",
6958
7038
  properties: {
6959
7039
  command: { type: "string", description: "Shell command to execute" },
6960
- workdir: { type: "string", description: "Working directory (default: baseDir)" },
6961
- background: { type: "boolean", description: "Start the command in the background and return immediately with a process id (default: auto-detect long-running commands)" }
7040
+ workdir: {
7041
+ type: "string",
7042
+ description: "Working directory (default: baseDir)"
7043
+ },
7044
+ background: {
7045
+ type: "boolean",
7046
+ description: "Start the command in the background and return immediately with a process id (default: auto-detect long-running commands)"
7047
+ }
6962
7048
  },
6963
7049
  required: ["command"]
6964
7050
  },
@@ -7005,7 +7091,8 @@ ${t("proc.manage_hint", {
7005
7091
  const res = await runCommand(command, {
7006
7092
  cwd: workdir,
7007
7093
  callId: ctx.activeCallId,
7008
- timeoutMs: BASH_TIMEOUT_MS
7094
+ timeoutMs: BASH_TIMEOUT_MS,
7095
+ onOutput: ctx.onMeta
7009
7096
  });
7010
7097
  const parts = [res.stdout.trimEnd(), res.stderr.trimEnd()].filter(Boolean);
7011
7098
  let output = parts.join(`
@@ -9065,6 +9152,16 @@ var init_store = __esm(() => {
9065
9152
 
9066
9153
  // src/core/agent.ts
9067
9154
  import { join as join10 } from "path";
9155
+ function canRunInParallel(calls) {
9156
+ if (calls.length <= 1)
9157
+ return false;
9158
+ for (const call of calls) {
9159
+ if (SIDE_EFFECT_TOOLS.has(call.name) || WRITE_TOOLS.has(call.name)) {
9160
+ return false;
9161
+ }
9162
+ }
9163
+ return true;
9164
+ }
9068
9165
 
9069
9166
  class Agent {
9070
9167
  deps;
@@ -9327,7 +9424,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9327
9424
  });
9328
9425
  const summaries = [];
9329
9426
  let anyToolFailed = false;
9330
- for (const call of toolCalls) {
9427
+ const executeToolCall = async (call) => {
9331
9428
  this.setScope();
9332
9429
  const startTime = Date.now();
9333
9430
  pluginManager.runOnToolCall({
@@ -9338,11 +9435,28 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9338
9435
  onTool?.({ type: "start", tool: call.name, args: call.arguments });
9339
9436
  slog.logToolCall(call, iteration);
9340
9437
  const tokensBeforeTool = contextManager.getEstimatedTokens();
9438
+ const toolCtx = this.deps.toolExecutor?.getContext?.();
9439
+ const prevOnMeta = toolCtx?.onMeta;
9440
+ if (toolCtx) {
9441
+ toolCtx.onMeta = onMeta;
9442
+ }
9341
9443
  const result = await toolExecutor.execute(call, this.abortController?.signal);
9444
+ if (toolCtx) {
9445
+ toolCtx.onMeta = prevOnMeta;
9446
+ }
9342
9447
  const duration = Date.now() - startTime;
9448
+ pluginManager.runOnToolEnd({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments }, result, duration);
9449
+ return { call, result, duration, tokensBefore: tokensBeforeTool };
9450
+ };
9451
+ const processToolResult = (entry) => {
9452
+ const {
9453
+ call,
9454
+ result,
9455
+ duration,
9456
+ tokensBefore: tokensBeforeTool
9457
+ } = entry;
9343
9458
  if (!result.success)
9344
9459
  anyToolFailed = true;
9345
- pluginManager.runOnToolEnd({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments }, result, duration);
9346
9460
  if (result.display) {
9347
9461
  onMeta?.(`
9348
9462
  ` + result.display + `
@@ -9386,6 +9500,17 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9386
9500
  contextManager.compact();
9387
9501
  logger.debug("Context compacted after tool result");
9388
9502
  }
9503
+ };
9504
+ if (canRunInParallel(toolCalls)) {
9505
+ const entries = await Promise.all(toolCalls.map(executeToolCall));
9506
+ for (const entry of entries) {
9507
+ processToolResult(entry);
9508
+ }
9509
+ } else {
9510
+ for (const call of toolCalls) {
9511
+ const entry = await executeToolCall(call);
9512
+ processToolResult(entry);
9513
+ }
9389
9514
  }
9390
9515
  if (anyToolFailed) {
9391
9516
  consecutiveToolFailures++;
@@ -9596,7 +9721,7 @@ ${taskReminder}</system-summary>`
9596
9721
  });
9597
9722
  }
9598
9723
  }
9599
- var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000;
9724
+ var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, WRITE_TOOLS, SIDE_EFFECT_TOOLS;
9600
9725
  var init_agent = __esm(() => {
9601
9726
  init_i18n();
9602
9727
  init_colors();
@@ -9604,6 +9729,14 @@ var init_agent = __esm(() => {
9604
9729
  init_processes();
9605
9730
  init_agent_moe();
9606
9731
  init_store();
9732
+ WRITE_TOOLS = new Set([
9733
+ "write_file",
9734
+ "edit_file",
9735
+ "delete_file",
9736
+ "move_file",
9737
+ "create_dir"
9738
+ ]);
9739
+ SIDE_EFFECT_TOOLS = new Set(["bash", "subagent", "pipeline_run"]);
9607
9740
  });
9608
9741
 
9609
9742
  // src/modules/context/manager.ts
@@ -15309,6 +15442,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
15309
15442
  lines.push(``, `Windows environment — use Windows-compatible commands:`, `- Use "dir" instead of "ls". Use "dir /b" for bare listing.`, `- Use "type" or "Get-Content" instead of "cat".`, `- Use "cd" instead of "pwd". Use "echo %cd%" to print working directory.`, `- Use "copy" instead of "cp", "move" instead of "mv", "del" instead of "rm".`, `- Do not use "mkdir -p" — Windows mkdir creates intermediate dirs by default. Use the create_dir tool instead.`, `- Use forward slashes (/) or escaped backslashes (\\\\) in file paths.`, `- Your working directory is: ${baseDir}`);
15310
15443
  }
15311
15444
  lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Prefer workdir over "cd dir && cmd" chaining.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`, `- Background processes (dev servers, watchers, long-running npm install): use the "background: true" parameter or the tool will auto-detect and background them. Check output with process_log.`);
15445
+ lines.push(``, `Parallel tool calls:`, `- When you need to read multiple files, call read_file for ALL of them in a SINGLE response (one tool_calls array with multiple read_file calls). They will execute in parallel — much faster than reading one at a time.`, `- Example: to read 3 files, return one assistant message with 3 read_file tool_calls. Do NOT read them one by one in separate iterations.`, `- This works for: read_file, glob, grep, file_info, list_dir — any read-only operations.`, `- Do NOT parallelize write operations (write_file, edit_file, delete_file, bash) — these run sequentially.`);
15312
15446
  lines.push(``, `=== DEVELOPMENT RULES — follow these strictly ===`, ``, `1. DEPENDENCIES FIRST: Before writing any source code, ALWAYS install project dependencies (e.g., "npm install", "pip install -r requirements.txt", "cargo build", "go mod tidy"). Verify the package manager's lock file or dependency directory exists. Never write code that imports/uses packages that aren't installed yet.`, `2. TOOLKIT/FWK FIRST: If the task specifies a framework or UI library, initialize and configure it BEFORE writing application code. Run its project init command first, then add components/modules. Never write your own version of what the framework already provides.`, `3. ONE STEP AT A TIME: Follow the plan sequentially. Complete step N before starting step N+1. When a step is done: verify the deliverables exist and have real content (not empty), then call "plan update step=N status=done". Do not redo completed work.`, `4. VERIFY YOUR WORK: After creating/modifying files, verify they exist on disk. After installing dependencies, verify the package manager completed successfully. After any command, check its output for errors. Don't assume operations succeeded.`, `5. NO PREMATURE WORK: Do not create files for future steps. Do not add imports/references to packages or modules that haven't been installed yet. Do not reference files or components that don't exist yet. Build incrementally — one layer at a time.`, `6. WHEN STUCK: If a command fails 2+ times, STOP and try a different approach. Write files directly instead of using commands. Ask the user for help. Never repeat the same failing command more than twice.`, ``, `=== PLAN QUALITY RULES — your plan MUST follow these ===`, ``, `- Each step must describe CONCRETE deliverables: exact filenames with paths, exact packages to install, exact CLI commands to run. Avoid vague steps — be specific.`, `- A step like "Настройка проекта" or "Setup the project" is too vague — describe what exactly needs to be configured or set up.`, `- A step like "Создать src/components/Header.tsx с навигацией и логотипом, добавить в src/App.tsx импорт <Header />" is GOOD.`, `- Include file extensions (.tsx, .css, .json) and directory paths. Every step must mention at least one file or command.`, `- The plan must cover EVERYTHING needed: init → deps → framework setup → code → verification.`, `- Number of steps: 5-8 for a typical task. Too few means you're being vague. Too many means you're over-splitting.`);
15313
15447
  if (config.autoPlan) {
15314
15448
  lines.push(``, `Plan rule (MANDATORY): For ANY task that requires creating files, installing packages, or multiple actions — you MUST create a plan using the "plan" tool BEFORE starting work. Each step must describe a concrete deliverable (specific files to create, packages to install, commands to run). Do not combine unrelated work into one step.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.24.3",
3
+ "version": "0.24.4",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {