micro-models-agent 0.43.2 → 0.44.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 +148 -53
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2580,6 +2580,7 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2580
2580
  "verify.script_passed": "Script '{script}' passed",
2581
2581
  "verify.script_failed": "Script '{script}' failed: {message}",
2582
2582
  "verify.syntax_error": "Syntax error in: {path}",
2583
+ "verify.file_still_exists": "File should be deleted but still exists: {path}",
2583
2584
  "verify.no_files": "Step {step} has no named files to verify — run a real check instead (bash: bun run build / bun test / lsp_check) and confirm the result.",
2584
2585
  "lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
2585
2586
  "lsp.check_disabled": "LSP checks are disabled in the config.",
@@ -3237,6 +3238,7 @@ var init_ru = __esm(() => {
3237
3238
  "verify.script_passed": "Скрипт '{script}' пройден",
3238
3239
  "verify.script_failed": "Скрипт '{script}' не пройден: {message}",
3239
3240
  "verify.syntax_error": "Синтаксическая ошибка в: {path}",
3241
+ "verify.file_still_exists": "Файл должен быть удалён, но всё ещё существует: {path}",
3240
3242
  "verify.no_files": "Шаг {step} не содержит именованных файлов для проверки — выполни реальную проверку (bun run build / bun test / lsp_check) и подтверди результат.",
3241
3243
  "lsp.unavailable": "статические проверки недоступны (сервер не найден). Проверяйте через собственный build/test проекта.",
3242
3244
  "lsp.check_disabled": "LSP-проверки отключены в конфигурации.",
@@ -6290,6 +6292,7 @@ var init_read_file = __esm(() => {
6290
6292
  init_path_utils();
6291
6293
  readFileTool = {
6292
6294
  name: "read_file",
6295
+ icon: "\uD83D\uDC40",
6293
6296
  description: `Read a file from the filesystem. Reads up to ${DEFAULT_LIMIT} lines at a time by default; use offset to page through large files.`,
6294
6297
  tags: ["file", "code"],
6295
6298
  boundedOutput: true,
@@ -6659,6 +6662,7 @@ var init_write_file = __esm(() => {
6659
6662
  init_path_utils();
6660
6663
  writeFileTool = {
6661
6664
  name: "write_file",
6665
+ icon: "\uD83D\uDCDD",
6662
6666
  description: "Create or overwrite a file with given content. Creates intermediate directories if needed.",
6663
6667
  tags: ["file", "code"],
6664
6668
  parameters: {
@@ -6734,6 +6738,7 @@ var init_edit_file = __esm(() => {
6734
6738
  init_path_utils();
6735
6739
  editFileTool = {
6736
6740
  name: "edit_file",
6741
+ icon: "✏️",
6737
6742
  description: "Find and replace text in an existing file. Uses exact string match (not regex).",
6738
6743
  tags: ["file", "code"],
6739
6744
  parameters: {
@@ -6807,6 +6812,7 @@ var init_glob_tool = __esm(() => {
6807
6812
  init_i18n();
6808
6813
  globTool = {
6809
6814
  name: "glob",
6815
+ icon: "\uD83D\uDD0E",
6810
6816
  description: `Search for files matching a glob pattern. Shows up to ${MAX_PREVIEW_LINES} results by default. Uses standard glob syntax (e.g., **/*.ts, src/**/*.test.ts).`,
6811
6817
  tags: ["file", "code", "research"],
6812
6818
  parameters: {
@@ -6848,6 +6854,7 @@ var init_grep_tool = __esm(() => {
6848
6854
  init_audit_log();
6849
6855
  grepTool = {
6850
6856
  name: "grep",
6857
+ icon: "\uD83D\uDD0D",
6851
6858
  description: `Search file contents using a regular expression. Shows up to ${MAX_PREVIEW_LINES} matching lines by default. Uses ripgrep (rg) if available, otherwise falls back to grep -r.`,
6852
6859
  tags: ["file", "code", "research"],
6853
6860
  parameters: {
@@ -6923,6 +6930,7 @@ var init_list_dir = __esm(() => {
6923
6930
  init_path_utils();
6924
6931
  listDirTool = {
6925
6932
  name: "list_dir",
6933
+ icon: "\uD83D\uDCC2",
6926
6934
  description: `List files and directories in a given path. Shows up to ${MAX_PREVIEW_LINES} entries by default.`,
6927
6935
  tags: ["file"],
6928
6936
  boundedOutput: true,
@@ -6975,6 +6983,7 @@ var init_create_dir = __esm(() => {
6975
6983
  init_path_utils();
6976
6984
  createDirTool = {
6977
6985
  name: "create_dir",
6986
+ icon: "\uD83D\uDCC1",
6978
6987
  description: "Create a directory (and any intermediate directories).",
6979
6988
  tags: ["file"],
6980
6989
  parameters: {
@@ -7029,6 +7038,7 @@ var init_delete_file = __esm(() => {
7029
7038
  init_path_utils();
7030
7039
  deleteFileTool = {
7031
7040
  name: "delete_file",
7041
+ icon: "\uD83D\uDDD1️",
7032
7042
  description: "Delete a file from the filesystem.",
7033
7043
  tags: ["file"],
7034
7044
  parameters: {
@@ -7090,6 +7100,7 @@ var init_move_file = __esm(() => {
7090
7100
  init_path_utils();
7091
7101
  moveFileTool = {
7092
7102
  name: "move_file",
7103
+ icon: "\uD83D\uDCE6",
7093
7104
  description: "Move or rename a file or directory.",
7094
7105
  tags: ["file"],
7095
7106
  parameters: {
@@ -7168,6 +7179,7 @@ var init_file_info = __esm(() => {
7168
7179
  init_path_utils();
7169
7180
  fileInfoTool = {
7170
7181
  name: "file_info",
7182
+ icon: "ℹ️",
7171
7183
  description: "Get metadata about a file or directory (size, creation date, modification date).",
7172
7184
  tags: ["file"],
7173
7185
  boundedOutput: true,
@@ -7842,6 +7854,7 @@ var init_bash = __esm(() => {
7842
7854
  NPM_EXEC_RE = /could not determine executable to run/i;
7843
7855
  bashTool = {
7844
7856
  name: "bash",
7857
+ icon: "\uD83D\uDCBB",
7845
7858
  description: `Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).
7846
7859
 
7847
7860
  Windows notes: the shell is cmd.exe — PowerShell cmdlets (Write-Host, Get-Content, Select-String, Out-File) and bash heredocs (cat << EOF) do NOT work there. Use the read_file/write_file tools instead of cat/echo redirection. Sequential commands: use && (a leading ; is auto-converted to &).`,
@@ -8010,6 +8023,7 @@ var init_process_list = __esm(() => {
8010
8023
  init_i18n();
8011
8024
  processListTool = {
8012
8025
  name: "process_list",
8026
+ icon: "\uD83D\uDCCA",
8013
8027
  description: "List background processes started via the bash tool (dev servers, watchers, long-running commands). Shows id, pid, command, status, and recent output. Use with process_log and process_kill to inspect or stop them.",
8014
8028
  tags: ["shell"],
8015
8029
  parameters: {
@@ -8054,6 +8068,7 @@ var init_process_log = __esm(() => {
8054
8068
  DEFAULT_TAIL = MAX_PREVIEW_LINES;
8055
8069
  processLogTool = {
8056
8070
  name: "process_log",
8071
+ icon: "\uD83D\uDCDC",
8057
8072
  description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
8058
8073
  tags: ["shell"],
8059
8074
  boundedOutput: true,
@@ -8099,6 +8114,7 @@ var init_process_kill = __esm(() => {
8099
8114
  init_i18n();
8100
8115
  processKillTool = {
8101
8116
  name: "process_kill",
8117
+ icon: "⛔",
8102
8118
  description: "Stop a background process started via bash (dev server, watcher). Kills the whole process tree (children included). Use the id returned by bash or process_list.",
8103
8119
  tags: ["shell"],
8104
8120
  parameters: {
@@ -9670,6 +9686,9 @@ class StuckDetector {
9670
9686
  this.consecutiveFailures = 0;
9671
9687
  this.lastFailedTool = "";
9672
9688
  this.escalationCount = 0;
9689
+ if (this.iterationsOnCurrentStep > 0) {
9690
+ this.iterationsOnCurrentStep = Math.max(0, this.iterationsOnCurrentStep - 1);
9691
+ }
9673
9692
  }
9674
9693
  setCurrentStep(stepId, description) {
9675
9694
  if (stepId !== this.currentStepId) {
@@ -11061,7 +11080,7 @@ class StepVerifier {
11061
11080
  details
11062
11081
  };
11063
11082
  }
11064
- async verifyStep(stepDescription) {
11083
+ async verifyStep(stepDescription, kind) {
11065
11084
  const fileMatches = extractFileLikeTokens(stripUrls(stepDescription));
11066
11085
  if (fileMatches.length === 0) {
11067
11086
  return { passed: false, syntaxValid: true, failed: [], noFiles: true };
@@ -11070,7 +11089,15 @@ class StepVerifier {
11070
11089
  let syntaxValid = true;
11071
11090
  for (const filePath of fileMatches) {
11072
11091
  const resolved = findExistingFile(this.baseDir, filePath);
11073
- if (!resolved) {
11092
+ const exists = !!resolved;
11093
+ if (kind === "delete") {
11094
+ results.push({
11095
+ passed: !exists,
11096
+ message: exists ? t("verify.file_still_exists", { path: filePath }) : t("verify.file_not_found", { path: filePath })
11097
+ });
11098
+ continue;
11099
+ }
11100
+ if (!exists) {
11074
11101
  results.push({ passed: false, message: t("verify.file_not_found", { path: filePath }) });
11075
11102
  continue;
11076
11103
  }
@@ -11523,6 +11550,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
11523
11550
  if (result) {
11524
11551
  logger.debug("Context compacted");
11525
11552
  slog.logCompaction({ reason: "interval", iteration, ...result });
11553
+ hallucinationDetector.addKnownFiles(contextManager.getKnownFiles());
11526
11554
  }
11527
11555
  }
11528
11556
  const currentTokens = contextManager.getEstimatedTokens();
@@ -11538,6 +11566,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
11538
11566
  iteration,
11539
11567
  ...result
11540
11568
  });
11569
+ hallucinationDetector.addKnownFiles(contextManager.getKnownFiles());
11541
11570
  }
11542
11571
  }
11543
11572
  if (currentTokens > budget2.history) {
@@ -11549,6 +11578,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
11549
11578
  iteration,
11550
11579
  ...result
11551
11580
  });
11581
+ hallucinationDetector.addKnownFiles(contextManager.getKnownFiles());
11552
11582
  }
11553
11583
  }
11554
11584
  this.refreshSystemPrompt();
@@ -11700,6 +11730,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
11700
11730
  });
11701
11731
  const summaries = [];
11702
11732
  let anyToolFailed = false;
11733
+ const infoTools = new Set(["file_info"]);
11703
11734
  for (const call of toolCalls) {
11704
11735
  this.setScope();
11705
11736
  const startTime = Date.now();
@@ -11708,14 +11739,15 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
11708
11739
  args: call.arguments
11709
11740
  });
11710
11741
  pluginManager.runOnToolStart({ iteration, logger, contextManager }, { id: call.id, name: call.name, arguments: call.arguments });
11711
- onTool?.({ type: "start", tool: call.name, args: call.arguments });
11742
+ const toolIcon = this.deps.toolExecutor.getRegistry().get(call.name)?.icon;
11743
+ onTool?.({ type: "start", tool: call.name, args: call.arguments, icon: toolIcon });
11712
11744
  slog.logToolCall(call, iteration);
11713
11745
  const tokensBeforeTool = contextManager.getEstimatedTokens();
11714
11746
  const result = await toolExecutor.execute(call, this.abortController?.signal);
11715
11747
  if (this.shutdownRequested)
11716
11748
  break;
11717
11749
  const duration = Date.now() - startTime;
11718
- if (!result.success)
11750
+ if (!result.success && !infoTools.has(call.name))
11719
11751
  anyToolFailed = true;
11720
11752
  if (result.success && call.arguments.path) {
11721
11753
  const filePath = String(call.arguments.path);
@@ -11774,9 +11806,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
11774
11806
  iteration,
11775
11807
  ...result2
11776
11808
  });
11809
+ hallucinationDetector.addKnownFiles(contextManager.getKnownFiles());
11777
11810
  }
11778
11811
  }
11779
- if (!result.success) {
11812
+ if (!result.success && !infoTools.has(call.name)) {
11780
11813
  const key = call.name;
11781
11814
  const prev = toolFailureCounts.get(key) ?? { count: 0, error: "" };
11782
11815
  prev.count++;
@@ -12171,6 +12204,10 @@ class FactExtractor {
12171
12204
  return null;
12172
12205
  return `[Files: ${liveFiles.slice(-15).join("; ")}]`;
12173
12206
  }
12207
+ getKnownFiles() {
12208
+ const deletedSet = new Set(this.deletedFacts);
12209
+ return this.fileFacts.filter((p) => !deletedSet.has(p));
12210
+ }
12174
12211
  deletedLine() {
12175
12212
  if (this.deletedFacts.length === 0)
12176
12213
  return null;
@@ -12295,6 +12332,9 @@ class ContextManager {
12295
12332
  getIterationsSinceCompaction() {
12296
12333
  return this.iterationsSinceCompaction;
12297
12334
  }
12335
+ getKnownFiles() {
12336
+ return this.facts.getKnownFiles();
12337
+ }
12298
12338
  noteIteration() {
12299
12339
  this.iterationsSinceCompaction++;
12300
12340
  }
@@ -12634,9 +12674,14 @@ class FactualCheck {
12634
12674
  static NAME_CACHE_TTL_MS = 5000;
12635
12675
  static RESCAN_COOLDOWN_MS = 2000;
12636
12676
  static MAX_INDEXED_FILES = 20000;
12677
+ knownFiles = new Set;
12637
12678
  constructor(baseDir) {
12638
12679
  this.baseDir = baseDir;
12639
12680
  }
12681
+ addKnownFiles(files) {
12682
+ for (const f of files)
12683
+ this.knownFiles.add(f);
12684
+ }
12640
12685
  validate(response) {
12641
12686
  const filePaths = this.extractFilePaths(response);
12642
12687
  if (filePaths.length === 0)
@@ -12660,6 +12705,8 @@ class FactualCheck {
12660
12705
  pathExists(fp) {
12661
12706
  if (isAbsolute2(fp))
12662
12707
  return existsSync23(fp);
12708
+ if (this.knownFiles.has(fp))
12709
+ return true;
12663
12710
  if (!fp.includes("/") && !fp.includes("\\")) {
12664
12711
  return this.bareNameExists(fp);
12665
12712
  }
@@ -12670,6 +12717,11 @@ class FactualCheck {
12670
12717
  return true;
12671
12718
  if (this.indexHas(name))
12672
12719
  return true;
12720
+ for (const known of this.knownFiles) {
12721
+ if (known.endsWith(`/${name}`) || known.endsWith(`\\${name}`) || known === name) {
12722
+ return true;
12723
+ }
12724
+ }
12673
12725
  const now = Date.now();
12674
12726
  if (now - this.lastRescan < FactualCheck.RESCAN_COOLDOWN_MS)
12675
12727
  return false;
@@ -12849,6 +12901,9 @@ class HallucinationDetector {
12849
12901
  getConfidenceCheck() {
12850
12902
  return this.confidence;
12851
12903
  }
12904
+ addKnownFiles(files) {
12905
+ this.factual?.addKnownFiles(files);
12906
+ }
12852
12907
  async validate(response) {
12853
12908
  const confidenceResult = this.confidence.validate(response);
12854
12909
  if (confidenceResult.status === "retry" || confidenceResult.status === "block") {
@@ -13195,6 +13250,7 @@ var init_subagent = __esm(() => {
13195
13250
  init_store();
13196
13251
  subagentTool = {
13197
13252
  name: "subagent",
13253
+ icon: "\uD83E\uDD16",
13198
13254
  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.",
13199
13255
  tags: ["code"],
13200
13256
  parameters: {
@@ -13493,6 +13549,7 @@ var init_chunk_query = __esm(() => {
13493
13549
  init_scope_check();
13494
13550
  chunkQueryTool = {
13495
13551
  name: "chunk_query",
13552
+ icon: "\uD83E\uDDE9",
13496
13553
  description: "Answer a query over a large text (file or inline) by splitting it into chunks and querying the model per chunk in parallel, then optionally synthesizing a final answer. Use when the input is too large for the context window.",
13497
13554
  tags: ["research", "code"],
13498
13555
  timeoutMs: 300000,
@@ -13692,6 +13749,7 @@ var init_web_search = __esm(() => {
13692
13749
  init_session_isolation();
13693
13750
  webSearchTool = {
13694
13751
  name: "web_search",
13752
+ icon: "\uD83C\uDF0D",
13695
13753
  description: "Search the web for information. Returns search results with titles and snippets. Use this for search queries instead of opening search engines (Google/Yandex/Bing) in the browser — they block headless browsers.",
13696
13754
  tags: ["research"],
13697
13755
  parameters: {
@@ -13759,6 +13817,7 @@ var init_web_fetch = __esm(() => {
13759
13817
  init_security();
13760
13818
  webFetchTool = {
13761
13819
  name: "web_fetch",
13820
+ icon: "⬇️",
13762
13821
  description: 'Fetch a URL and convert its content to markdown. Use for reading documentation, APIs, web pages. This is a plain HTTP request without a browser engine — it does NOT run JavaScript, so JS-rendered single-page apps (weather sites, dashboards, modern SPAs) return empty or near-empty content; use the "browser" tool for those.',
13763
13822
  tags: ["research"],
13764
13823
  parameters: {
@@ -13845,6 +13904,7 @@ var init_web_browse = __esm(() => {
13845
13904
  init_session_isolation();
13846
13905
  webBrowseTool = {
13847
13906
  name: "web_browse",
13907
+ icon: "\uD83C\uDF10",
13848
13908
  description: `Fetch and read a web page. Returns the page content as plain text (max ${MAX_PREVIEW_LINES} lines / ${MAX_CHARS2} chars). This is a plain HTTP request — it does NOT execute JavaScript, so JS-rendered single-page apps (weather sites, dashboards, modern SPAs) return empty or near-empty content. For such pages use the "browser" tool instead.`,
13849
13909
  tags: ["research"],
13850
13910
  parameters: {
@@ -13929,6 +13989,7 @@ var init_download_file = __esm(() => {
13929
13989
  MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
13930
13990
  downloadFileTool = {
13931
13991
  name: "download_file",
13992
+ icon: "⬇️",
13932
13993
  description: "Download a file (image, archive, binary, font, etc.) from a URL and save it to disk. Returns only the path, size and content type — read the saved file with read_file/attach_image afterwards. Use this for binary files; web_fetch/web_browse return text only and cannot save bytes.",
13933
13994
  tags: ["file", "research"],
13934
13995
  parameters: {
@@ -14032,6 +14093,7 @@ var init_download_file = __esm(() => {
14032
14093
  function createLoadSkillTool(skillsModule) {
14033
14094
  return {
14034
14095
  name: "load_skill",
14096
+ icon: "\uD83C\uDFAF",
14035
14097
  tags: ["core"],
14036
14098
  description: "Load a skill by name. Skills provide specialized instructions and workflows. When loaded, the skill content is injected into the system prompt (not conversation history) so it persists through context compaction. Only use when the task clearly requires domain-specific knowledge.",
14037
14099
  parameters: {
@@ -14263,6 +14325,7 @@ var init_pipeline_run = __esm(() => {
14263
14325
  engine = new PipelineEngine;
14264
14326
  pipelineRunTool = {
14265
14327
  name: "pipeline_run",
14328
+ icon: "▶️",
14266
14329
  description: "Run a named pipeline with YAML definition. Creates a DAG of sub-agents that execute in dependency order.",
14267
14330
  tags: ["shell", "code"],
14268
14331
  parameters: {
@@ -14794,6 +14857,7 @@ var init_mcp_call = __esm(() => {
14794
14857
  registry = new MCPRegistry;
14795
14858
  mcpCallTool = {
14796
14859
  name: "mcp_call",
14860
+ icon: "\uD83D\uDD17",
14797
14861
  description: "Call a tool on an MCP (Model Context Protocol) server. MCP servers provide external capabilities like databases, APIs, or specialized tools.",
14798
14862
  tags: ["code", "research"],
14799
14863
  parameters: {
@@ -14863,6 +14927,7 @@ var init_search_history = __esm(() => {
14863
14927
  init_i18n();
14864
14928
  searchHistoryTool = {
14865
14929
  name: "search_history",
14930
+ icon: "\uD83D\uDD75️",
14866
14931
  description: "Search through session history for past interactions across all sessions.",
14867
14932
  tags: ["research"],
14868
14933
  parameters: {
@@ -15059,6 +15124,7 @@ var init_remember = __esm(() => {
15059
15124
  CATEGORIES = ["preferences", "conventions", "decisions", "errors", "facts"];
15060
15125
  rememberTool = {
15061
15126
  name: "remember",
15127
+ icon: "\uD83E\uDDE0",
15062
15128
  description: "Remember information across sessions. Use for user preferences, facts, conventions, decisions, or errors.",
15063
15129
  tags: ["memory"],
15064
15130
  parameters: {
@@ -15186,6 +15252,7 @@ var init_recall = __esm(() => {
15186
15252
  CATEGORIES2 = ["preferences", "conventions", "decisions", "errors", "facts"];
15187
15253
  recallTool = {
15188
15254
  name: "recall",
15255
+ icon: "\uD83D\uDCBE",
15189
15256
  description: "Recall stored information from memory. Search across preferences, facts, conventions, decisions, errors.",
15190
15257
  tags: ["memory"],
15191
15258
  parameters: {
@@ -16215,6 +16282,7 @@ function formatScreenshotResult(snapshot, screenshot, supportsVision) {
16215
16282
  function createBrowserTool() {
16216
16283
  return {
16217
16284
  name: "browser",
16285
+ icon: "\uD83C\uDF10",
16218
16286
  tags: ["browser", "vision"],
16219
16287
  description: [
16220
16288
  "Control a headless browser. Navigate pages, click elements, type text, scroll, take screenshots.",
@@ -16419,6 +16487,7 @@ var init_attach_image = __esm(() => {
16419
16487
  init_audit_log();
16420
16488
  attachImageTool = {
16421
16489
  name: "attach_image",
16490
+ icon: "\uD83D\uDDBC️",
16422
16491
  description: "Attach an image to the conversation from a file path, URL, or clipboard. The image will be included in the next message sent to the model. Supports PNG, JPEG, GIF, WebP.",
16423
16492
  tags: ["vision", "image"],
16424
16493
  parameters: {
@@ -16504,6 +16573,7 @@ var init_enable_tools = __esm(() => {
16504
16573
  init_i18n();
16505
16574
  enableToolsTool = {
16506
16575
  name: "enable_tools",
16576
+ icon: "\uD83D\uDD0C",
16507
16577
  description: "Enable hidden tool categories for the current session. Only the essential tools (file/code/shell) are active by default. Call this with the tags you need and they become available from the next turn. Available tags: file, code, shell, research, browser, vision, image, memory, core. Returns the newly available tools. To run a task in a separate context instead, use subagent with tool_tags.",
16508
16578
  alwaysOn: true,
16509
16579
  parameters: {
@@ -17545,30 +17615,29 @@ function createExecutionPlugin(deps) {
17545
17615
  if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
17546
17616
  const recovery = deps.stuckDetector.getRecoveryMessage();
17547
17617
  if (recovery && ctx.contextManager) {
17548
- const lastError = deps.stuckDetector.getLastErrorOutput();
17549
- const skillHint = lastError ? `
17550
- If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
17551
- const actionableHints = deps.stuckDetector.getActionableHints();
17552
- const actionableHintStr = actionableHints.length > 0 ? `
17553
- ${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
17554
- `) })}` : "";
17555
- const alternative = deps.stuckDetector.getToolAlternative();
17556
- const altHint = alternative ? `
17557
- Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
17558
- ctx.contextManager.addMessage({
17559
- role: "user",
17560
- content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
17561
- });
17562
- }
17563
- const hints = deps.stuckDetector.getHints();
17564
- if (hints.length > 0 && ctx.contextManager) {
17565
- const hintMsg = t("exec.hints", {
17566
- hints: hints.map((h) => `- ${h}`).join(`
17567
- `)
17568
- });
17618
+ let tscContext = "";
17619
+ if (deps.state.typecheckFailures.size > 0) {
17620
+ const currentStep = deps.trackerRef.current?.getCurrentStep();
17621
+ if (currentStep) {
17622
+ const stepFiles = extractFileLikeTokens(stripUrls(currentStep.description));
17623
+ for (const [file, error] of deps.state.typecheckFailures) {
17624
+ if (!file || stepFiles.some((f) => file.endsWith(f) || f.endsWith(file))) {
17625
+ tscContext = `
17626
+ Last compile error: ${error}`;
17627
+ break;
17628
+ }
17629
+ }
17630
+ if (!tscContext) {
17631
+ const first = deps.state.typecheckFailures.entries().next().value;
17632
+ if (first)
17633
+ tscContext = `
17634
+ Last compile error: ${first[1]}`;
17635
+ }
17636
+ }
17637
+ }
17569
17638
  ctx.contextManager.addMessage({
17570
17639
  role: "user",
17571
- content: `<system-summary>${hintMsg}</system-summary>`
17640
+ content: `<system-summary>${recovery}${tscContext}</system-summary>`
17572
17641
  });
17573
17642
  }
17574
17643
  deps.stuckDetector.recordEscalation();
@@ -17627,6 +17696,24 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17627
17696
  if (call.name === "bash") {
17628
17697
  deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
17629
17698
  }
17699
+ if (call.name === "delete_file" && result.success && deps.trackerRef.current) {
17700
+ const deletedPath = String(call.arguments?.path ?? "");
17701
+ if (deletedPath) {
17702
+ const plan = deps.trackerRef.current.getPlan();
17703
+ const tokens = extractFileLikeTokens(stripUrls(deletedPath));
17704
+ for (const step of plan.steps) {
17705
+ if (step.status !== "pending" && step.status !== "in_progress")
17706
+ continue;
17707
+ const stepTokens = extractFileLikeTokens(stripUrls(step.description));
17708
+ const overlap = tokens.some((t2) => stepTokens.some((st) => t2.endsWith(st) || st.endsWith(t2)));
17709
+ if (overlap && step.kind !== "delete") {
17710
+ step.kind = "delete";
17711
+ deps.store.saveActive(plan);
17712
+ break;
17713
+ }
17714
+ }
17715
+ }
17716
+ }
17630
17717
  const toolText = String(result.output ?? "");
17631
17718
  const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
17632
17719
  if (hasTypeError) {
@@ -17659,19 +17746,6 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17659
17746
  if (!hasTypeError) {
17660
17747
  deps.stuckDetector.recordToolError(call.name, result.output);
17661
17748
  }
17662
- const actionableHints = deps.stuckDetector.getActionableHints();
17663
- const alternative = deps.stuckDetector.getToolAlternative();
17664
- if (actionableHints.length > 0 || alternative) {
17665
- const parts = [...actionableHints];
17666
- if (alternative) {
17667
- parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
17668
- }
17669
- deps.pendingMessages.push({
17670
- role: "user",
17671
- content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
17672
- `) })}</system-summary>`
17673
- });
17674
- }
17675
17749
  } else {
17676
17750
  deps.stuckDetector.recordToolSuccess();
17677
17751
  if (call.name === "bash" && result.success) {
@@ -17687,11 +17761,6 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17687
17761
  role: "user",
17688
17762
  content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`
17689
17763
  });
17690
- } else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
17691
- deps.pendingMessages.push({
17692
- role: "user",
17693
- content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`
17694
- });
17695
17764
  }
17696
17765
  }
17697
17766
  }
@@ -17729,6 +17798,7 @@ function createPlanToolDefinitions(deps) {
17729
17798
  return [
17730
17799
  {
17731
17800
  name: "plan",
17801
+ icon: "\uD83D\uDCCB",
17732
17802
  alwaysOn: true,
17733
17803
  description: `Create, update, show, abort, list, switch, delete, purge, or re-plan multi-step plans.
17734
17804
 
@@ -18005,6 +18075,20 @@ ${progress2}`,
18005
18075
  };
18006
18076
  }
18007
18077
  }
18078
+ if (status === "done" && target.kind !== "delete") {
18079
+ const missing = deps.missingStepDeliverables(target);
18080
+ if (missing.length > 0) {
18081
+ const existing = deps.stillExistingDeliverables(target);
18082
+ const allGone = deps.hasStepDeliverables(target) && existing.length === 0;
18083
+ if (allGone) {
18084
+ const wasDeleted = deps.wasFileDeleted ? missing.every((f) => deps.wasFileDeleted(f)) : false;
18085
+ if (wasDeleted) {
18086
+ target.kind = "delete";
18087
+ deps.store.saveActive(tracker.getPlan());
18088
+ }
18089
+ }
18090
+ }
18091
+ }
18008
18092
  if (status === "done") {
18009
18093
  if (target.kind === "delete") {
18010
18094
  const leftovers = deps.stillExistingDeliverables(target);
@@ -18174,6 +18258,7 @@ ${progress}${vacuousNote}`,
18174
18258
  },
18175
18259
  {
18176
18260
  name: "todo",
18261
+ icon: "☑️",
18177
18262
  alwaysOn: true,
18178
18263
  description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
18179
18264
  parameters: {
@@ -18270,6 +18355,7 @@ ${lines.join(`
18270
18355
  },
18271
18356
  {
18272
18357
  name: "verify",
18358
+ icon: "✅",
18273
18359
  alwaysOn: true,
18274
18360
  description: "Run verification for the current step. Checks files mentioned in the step description.",
18275
18361
  parameters: {
@@ -18285,7 +18371,7 @@ ${lines.join(`
18285
18371
  const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
18286
18372
  if (!step)
18287
18373
  return { success: false, output: t("plan.step_not_found") };
18288
- const result = await deps.verifier.verifyStep(step.description);
18374
+ const result = await deps.verifier.verifyStep(step.description, step.kind);
18289
18375
  if (result.noFiles) {
18290
18376
  return {
18291
18377
  success: true,
@@ -18330,6 +18416,7 @@ class ExecutionModule {
18330
18416
  retryWebSearchSignatures = new Set;
18331
18417
  lastErrorSearchAt = 0;
18332
18418
  errorSearchCooldownMs;
18419
+ hallucinationDetector = null;
18333
18420
  state = {
18334
18421
  lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
18335
18422
  consecutivePlanWarnings: 0,
@@ -18368,6 +18455,9 @@ class ExecutionModule {
18368
18455
  this.completedPlan = null;
18369
18456
  this._auditSkipsRemaining = 0;
18370
18457
  }
18458
+ setHallucinationDetector(detector) {
18459
+ this.hallucinationDetector = detector;
18460
+ }
18371
18461
  recordCompleted(plan) {
18372
18462
  this.completedPlan = plan;
18373
18463
  }
@@ -18522,7 +18612,8 @@ class ExecutionModule {
18522
18612
  parseKinds: (a, st) => this.parseKinds(a, st),
18523
18613
  getTaskText: (c) => this.getTaskText(c),
18524
18614
  typecheckFailures: this.state.typecheckFailures,
18525
- baseDir: this.baseDir
18615
+ baseDir: this.baseDir,
18616
+ wasFileDeleted: this.hallucinationDetector ? (path) => this.hallucinationDetector.getConsistencyCheck().getDeletedFiles().includes(path) : undefined
18526
18617
  });
18527
18618
  }
18528
18619
  getPlugin() {
@@ -18532,6 +18623,7 @@ class ExecutionModule {
18532
18623
  pendingMessages: this.pendingMessages,
18533
18624
  forbiddenBashFailures: this.forbiddenBashFailures,
18534
18625
  state: this.state,
18626
+ store: this.store,
18535
18627
  checkPlanAlignment: (call) => this.checkPlanAlignment(call),
18536
18628
  advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl),
18537
18629
  maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call)
@@ -20054,6 +20146,7 @@ ${items}`;
20054
20146
  return [
20055
20147
  {
20056
20148
  name: "lsp_check",
20149
+ icon: "\uD83E\uDE7A",
20057
20150
  description: 'Run static checks (LSP diagnostics) on a file or directory. For "check for errors" tasks: pass a file to check it, or a directory to check its source files (capped at 15). Returns [LSP errors] / [LSP warnings] for files with a configured LSP server.',
20058
20151
  tags: ["code", "check"],
20059
20152
  alwaysOn: true,
@@ -20788,6 +20881,7 @@ ${t("indexer.and_more", { count: result.files.length - 100 })}` : "";
20788
20881
  createProjectMapTool() {
20789
20882
  return {
20790
20883
  name: "project_map",
20884
+ icon: "\uD83D\uDDFA️",
20791
20885
  alwaysOn: true,
20792
20886
  description: t("indexer.project_map_desc"),
20793
20887
  parameters: {
@@ -21314,6 +21408,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
21314
21408
  execModule.restorePlan();
21315
21409
  }
21316
21410
  moduleRegistry.register(execModule);
21411
+ execModule.setHallucinationDetector(hallucinationDetector);
21317
21412
  const sessionModule = new SessionModule(sessionManager);
21318
21413
  moduleRegistry.register(sessionModule);
21319
21414
  moduleRegistry.register(indexerModule);
@@ -32673,20 +32768,20 @@ class Renderer {
32673
32768
  thinkingEnd() {
32674
32769
  this.spinner.stop();
32675
32770
  }
32676
- toolStart(tool, args, stepContext) {
32771
+ toolStart(tool, args, stepContext, icon) {
32677
32772
  this.endCard();
32678
32773
  this.spinner.stop();
32679
32774
  const summary = summarizeArgs2(args);
32680
32775
  const step = stepContext ? ` ${pc2.cyan(`← ${stepContext}`)}` : "";
32776
+ const marker = icon || toolMarker(tool);
32681
32777
  if (!this.rich) {
32682
32778
  this.out.write(`
32683
- ${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}
32779
+ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}
32684
32780
  `);
32685
32781
  return;
32686
32782
  }
32687
32783
  this.card = { tool, args, body: [], start: Date.now() };
32688
32784
  if (this.toolStyle === "inline") {
32689
- const marker = toolMarker(tool);
32690
32785
  this.out.write(`
32691
32786
  ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}
32692
32787
  `);
@@ -32695,7 +32790,7 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
32695
32790
  }
32696
32791
  return;
32697
32792
  }
32698
- this.spinner.start(`${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}`);
32793
+ this.spinner.start(`${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}`);
32699
32794
  }
32700
32795
  planBlock(lines) {
32701
32796
  this.endCard();
@@ -33255,7 +33350,7 @@ ${t("image.clipboard_empty")}`));
33255
33350
  this.refreshActivePlan(renderer);
33256
33351
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
33257
33352
  if (ev.type === "start") {
33258
- renderer.toolStart(ev.tool, ev.args, stepContextForTool(this.activePlan, ev.tool, ev.args));
33353
+ renderer.toolStart(ev.tool, ev.args, stepContextForTool(this.activePlan, ev.tool, ev.args), ev.icon);
33259
33354
  } else {
33260
33355
  renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
33261
33356
  if (ev.tool === "plan" || ev.tool === "todo") {
@@ -33701,7 +33796,7 @@ async function main() {
33701
33796
  });
33702
33797
  const result = await agent.run(prompt, (chunk) => renderer.text(chunk), (meta) => renderer.meta(meta), (ev) => {
33703
33798
  if (ev.type === "start") {
33704
- renderer.toolStart(ev.tool, ev.args);
33799
+ renderer.toolStart(ev.tool, ev.args, undefined, ev.icon);
33705
33800
  } else {
33706
33801
  renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
33707
33802
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.43.2",
3
+ "version": "0.44.0",
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": {