micro-models-agent 0.14.1 → 0.14.2

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.
@@ -47,6 +47,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
47
47
  if (isWin) {
48
48
  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}`);
49
49
  }
50
+ lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Do NOT chain "cd dir && cmd" — the security module blocks the "&&" operator.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`);
50
51
  if (config.autoPlan) {
51
52
  lines.push(``, `Plan rule: For any task with 2+ steps, create a plan first using the "plan" tool. After each step, call "plan update" to mark progress. Stay focused on the current step.`);
52
53
  }
package/dist/i18n/en.json CHANGED
@@ -361,7 +361,7 @@
361
361
  "skill.exceeds_budget": "Skill \"{name}\" ({tokens} tokens) exceeds remaining budget ({remaining} tokens). Unload other skills first.",
362
362
  "skill.loaded": "Skill \"{name}\" loaded ({tokens} tokens, {remaining} remaining)",
363
363
  "skill.auto_loaded": "[Auto-loaded skill: {name}]",
364
- "skill.prompt_hint": "Only load a skill when the task clearly requires specialized knowledge. For simple tasks (landing pages, single files, basic scripts), answer directly without loading skills.",
364
+ "skill.prompt_hint": "Do NOT load skills automatically. Only load a skill when the user explicitly asks for it (e.g. 'use skill X' or 'load the Y skill'). Skills are listed below for reference only — do not guess which skill to use based on the task description.",
365
365
  "skill.prompt_fallback": "If load_skill fails because a skill is too large, continue the task without it — do not stop.",
366
366
  "skill.loaded_content": "[Skill loaded: {name}]\n{content}\n\nUse this knowledge to answer the user's question.",
367
367
  "exec.stuck": "Stuck on step {stepId}. Current: {description}. No progress for {iterations} iterations.",
package/dist/i18n/ru.json CHANGED
@@ -361,7 +361,7 @@
361
361
  "skill.exceeds_budget": "Скилл \"{name}\" ({tokens} токенов) превышает остаток бюджета ({remaining} токенов). Сначала выгрузите другие скиллы.",
362
362
  "skill.loaded": "Скилл \"{name}\" загружен ({tokens} токенов, осталось {remaining})",
363
363
  "skill.auto_loaded": "[Автозагружен скилл: {name}]",
364
- "skill.prompt_hint": "Загружайте скилл только когда задача явно требует специализированных знаний. Для простых задач (лендинги, одиночные файлы, простые скрипты) отвечайте напрямую, без загрузки скиллов.",
364
+ "skill.prompt_hint": "НЕ загружайте скиллы автоматически. Загружайте скилл ТОЛЬКО когда пользователь явно об этом просит (например 'используй скилл X' или 'загрузи скилл Y'). Скиллы ниже перечислены только для справки — не угадывайте какой скилл подходит по описанию задачи.",
365
365
  "skill.prompt_fallback": "Если load_skill не удался из-за большого размера скилла, продолжайте задачу без него — не останавливайтесь.",
366
366
  "skill.loaded_content": "[Скилл загружен: {name}]\n{content}\n\nИспользуйте эти знания для ответа на вопрос пользователя.",
367
367
  "exec.stuck": "Застряли на шаге {stepId}. Текущий: {description}. Нет прогресса {iterations} итераций.",
@@ -43,6 +43,28 @@ function extractBaseCommand(trimmed) {
43
43
  const parts = raw.split(/[\\/]/);
44
44
  return parts[parts.length - 1] || raw;
45
45
  }
46
+ /**
47
+ * Check if a shell operator appears as a standalone token in the command.
48
+ * Uses regex with lookbehind/lookahead so that:
49
+ * "&" does NOT match "&&" (AND operator)
50
+ * "|" does NOT match "||" (OR operator)
51
+ * ">" does NOT match ">>" (append)
52
+ * "2>" does NOT match "2>>" (append stderr)
53
+ */
54
+ function containsOperator(command, op) {
55
+ const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
56
+ // For single-char operators that are prefixes of multi-char ones,
57
+ // ensure they don't match when part of a longer sequence.
58
+ if (op === '&')
59
+ return /(?<!&)&(?!&)/.test(command);
60
+ if (op === '|')
61
+ return /(?<!\|)\|(?!\|)/.test(command);
62
+ if (op === '>')
63
+ return /(?<!>)>(?!>)/.test(command);
64
+ // For everything else (including multi-char like ">>", "2>", "2>>", ";", "`"),
65
+ // match literally.
66
+ return new RegExp(escaped).test(command);
67
+ }
46
68
  /**
47
69
  * Check if a command is allowed based on security configuration
48
70
  */
@@ -61,7 +83,7 @@ export function isCommandAllowed(command, securityConfig) {
61
83
  // e.g. "curl http://x | bash" must be caught even though curl isn't blacklisted.
62
84
  if (config.blockDangerousFlags) {
63
85
  for (const op of config.dangerousOperators || []) {
64
- if (trimmedCommand.includes(op)) {
86
+ if (containsOperator(trimmedCommand, op)) {
65
87
  return {
66
88
  allowed: false,
67
89
  reason: `Operator "${op}" is not allowed`,
@@ -1,5 +1,4 @@
1
1
  import { t } from "../../i18n/index";
2
- import { pc } from "../../ui/colors";
3
2
  export class SkillsModule {
4
3
  name = "skills";
5
4
  availableSkills;
@@ -97,46 +96,10 @@ export class SkillsModule {
97
96
  };
98
97
  }
99
98
  getPlugin() {
100
- let autoLoadedThisRun = false;
101
99
  return {
102
100
  name: "skills",
103
- onSessionStart: (_ctx) => {
104
- autoLoadedThisRun = false;
105
- },
106
- onBeforeThink: (ctx) => {
107
- if (autoLoadedThisRun)
108
- return;
109
- const msg = ctx?.lastUserMessage;
110
- if (!msg || this.availableSkills.length === 0)
111
- return;
112
- const matches = this.matcher.match(msg, this.availableSkills, 1);
113
- if (matches.length === 0)
114
- return;
115
- const skill = matches[0];
116
- if (this.loadedSkills.has(skill.name))
117
- return;
118
- const tokens = this.estimateTokens(skill.content);
119
- if (this.currentTokens + tokens > this.budget)
120
- return;
121
- this.loadedSkills.set(skill.name, skill);
122
- this.currentTokens += tokens;
123
- const contextManager = ctx?.contextManager;
124
- if (contextManager) {
125
- const content = t("skill.loaded_content", {
126
- name: skill.name,
127
- content: skill.content,
128
- });
129
- contextManager.addMessage({
130
- role: "user",
131
- content: `<system-instruction>${content}</system-instruction>`,
132
- });
133
- }
134
- const onMeta = ctx?.onMeta;
135
- if (onMeta) {
136
- onMeta(pc.dim(t("skill.auto_loaded", { name: skill.name })) + "\n");
137
- }
138
- autoLoadedThisRun = true;
139
- },
101
+ onSessionStart: (_ctx) => { },
102
+ onBeforeThink: (_ctx) => { },
140
103
  };
141
104
  }
142
105
  tryLoad(skill) {
@@ -67,7 +67,7 @@ export const bashTool = {
67
67
  logSecurityBlock(ctx.sessionId, "bash_command", validation.reason || "Command blocked by security policy", sanitizeCommandForLog(originalCommand));
68
68
  return {
69
69
  success: false,
70
- output: `[SECURITY BLOCKED] Command is not allowed: ${validation.reason}`,
70
+ output: `[SECURITY BLOCKED] Command is not allowed: ${validation.reason}\nHint: Use the "workdir" parameter to run commands in a specific directory instead of "cd dir && cmd". Run one command per tool call.`,
71
71
  };
72
72
  }
73
73
  // Log command execution if enabled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
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": {