micro-models-agent 0.14.3 → 0.15.1

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.
package/dist/cli/main.js CHANGED
@@ -36,6 +36,9 @@ async function main() {
36
36
  iterationCount: result.iterationCount,
37
37
  contextUsed: result.contextUsed ?? null,
38
38
  contextLimit: result.contextLimit ?? null,
39
+ promptTokens: result.promptTokens ?? null,
40
+ completionTokens: result.completionTokens ?? null,
41
+ totalTokens: result.totalTokens ?? null,
39
42
  }, null, 2));
40
43
  process.stdout.write("\n");
41
44
  process.exit(result.success ? 0 : 1);
package/dist/cli/repl.js CHANGED
@@ -358,7 +358,7 @@ export class Repl {
358
358
  action: (args) => {
359
359
  if (args.length === 0) {
360
360
  console.log(`/context ${t("repl.context")} ${this.config.contextWindow}`);
361
- console.log(t("repl.model_usage").replace("/model", "/context"));
361
+ console.log(`Usage: /context <size> (min 1024)`);
362
362
  return;
363
363
  }
364
364
  const size = parseInt(args[0], 10);
@@ -862,7 +862,12 @@ export class Repl {
862
862
  result.contextLimit !== undefined &&
863
863
  result.contextLimit > 0) {
864
864
  console.log();
865
- console.log(formatContextBar(result.contextUsed, result.contextLimit));
865
+ const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
866
+ console.log(ctxLine);
867
+ if (result.totalTokens !== undefined && result.totalTokens > 0) {
868
+ const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
869
+ console.log(apiLine);
870
+ }
866
871
  }
867
872
  }
868
873
  start() {
package/dist/cli/setup.js CHANGED
@@ -185,7 +185,7 @@ export async function runSetup() {
185
185
  }
186
186
  console.log(t("setup.agent_settings"));
187
187
  const contextWindow = parseInt(await ask(rl, t("setup.context_window"), "32768"));
188
- const maxIterations = parseInt(await ask(rl, t("setup.max_iters"), "25"));
188
+ const maxIterations = parseInt(await ask(rl, t("setup.max_iters"), "1000"));
189
189
  rl.close();
190
190
  const answers = {
191
191
  provider,
@@ -146,8 +146,11 @@ function forceSecurityUpdate(config) {
146
146
  ...DEFAULT_SECURITY_CONFIG.bash.dangerousFlags,
147
147
  ...(userBash.dangerousFlags || []),
148
148
  ])],
149
- // dangerousOperators: ALWAYS use defaults user overrides are dangerous
150
- dangerousOperators: [...DEFAULT_SECURITY_CONFIG.bash.dangerousOperators],
149
+ // dangerousOperators: merge user's additions with defaults (user can add but not remove critical ones)
150
+ dangerousOperators: [...new Set([
151
+ ...DEFAULT_SECURITY_CONFIG.bash.dangerousOperators,
152
+ ...(userBash.dangerousOperators || []),
153
+ ])],
151
154
  blockDangerousFlags: DEFAULT_SECURITY_CONFIG.bash.blockDangerousFlags,
152
155
  logCommands: DEFAULT_SECURITY_CONFIG.bash.logCommands,
153
156
  };
@@ -48,7 +48,7 @@ export const DEFAULTS = {
48
48
  baseDelay: 1000,
49
49
  maxDelay: 30000,
50
50
  },
51
- maxToolIterations: 25,
51
+ maxToolIterations: 1000,
52
52
  stuckThreshold: 8,
53
53
  autoPlan: true,
54
54
  showReasoning: false,
@@ -45,7 +45,8 @@ export const DEFAULT_SECURITY_CONFIG = {
45
45
  // If whitelist is non-empty, only these commands are allowed
46
46
  whitelist: [],
47
47
  // Block dangerous flags like --force, -rf, etc.
48
- blockDangerousFlags: true,
48
+ // Disabled by default — enable in config if needed.
49
+ blockDangerousFlags: false,
49
50
  // Log all executed commands to audit log
50
51
  logCommands: true,
51
52
  // Dangerous flags that are always blocked
@@ -65,7 +66,9 @@ export const DEFAULT_SECURITY_CONFIG = {
65
66
  "--no-confirm",
66
67
  ],
67
68
  // Dangerous operators that are always blocked
68
- dangerousOperators: [">", ">>", "2>", "2>>", "&", "`"],
69
+ // Note: "&" (background) is not blocked — "&&" (AND) is a legitimate shell operator.
70
+ // Single "&" for backgrounding is harmless and blocking it breaks "cd dir && npm install".
71
+ dangerousOperators: [">", ">>", "2>", "2>>", "`"],
69
72
  },
70
73
  paths: {
71
74
  // Glob patterns for paths that are always denied
@@ -110,6 +110,8 @@ export class Agent {
110
110
  let lastText = "";
111
111
  let hallucinationRetries = 0;
112
112
  let lastToolSignature = "";
113
+ let apiPromptTokens = 0;
114
+ let apiCompletionTokens = 0;
113
115
  const MAX_HALLUCINATION_RETRIES = 3;
114
116
  while (iteration < config.maxToolIterations) {
115
117
  iteration++;
@@ -177,6 +179,10 @@ export class Agent {
177
179
  arguments: parsedArgs,
178
180
  });
179
181
  }
182
+ if (chunk.type === "done" && chunk.usage) {
183
+ apiPromptTokens += chunk.usage.promptTokens;
184
+ apiCompletionTokens += chunk.usage.completionTokens;
185
+ }
180
186
  }
181
187
  }
182
188
  catch (err) {
@@ -381,6 +387,9 @@ export class Agent {
381
387
  iterationCount: iteration,
382
388
  contextUsed: tokensUsed,
383
389
  contextLimit: budget.history,
390
+ promptTokens: apiPromptTokens,
391
+ completionTokens: apiCompletionTokens,
392
+ totalTokens: apiPromptTokens + apiCompletionTokens,
384
393
  };
385
394
  }
386
395
  return {
@@ -389,6 +398,9 @@ export class Agent {
389
398
  iterationCount: iteration,
390
399
  contextUsed: tokensUsed,
391
400
  contextLimit: budget.history,
401
+ promptTokens: apiPromptTokens,
402
+ completionTokens: apiCompletionTokens,
403
+ totalTokens: apiPromptTokens + apiCompletionTokens,
392
404
  };
393
405
  }
394
406
  clearContext() {
@@ -33,7 +33,6 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
33
33
  `Workspace: ${baseDir}`,
34
34
  `${profileCompressed}`,
35
35
  `Reply in the user's language. Use tools for filesystem, bash, web access.`,
36
- `When the task is ambiguous or has multiple reasonable options, ask the user via the "question" tool instead of guessing.`,
37
36
  `When you need to act, call a tool immediately. Do not describe your plans in text — use tools to perform operations.`,
38
37
  `After every tool call, check whether the user's request is fully satisfied. If any files, commands, or checks are still missing, call the next needed tool right away. Do not stop with an empty or "done" response until the task is complete.`,
39
38
  `If you create a directory, continue creating the files that belong inside it. A created folder alone is not a completed task.`,
@@ -98,6 +98,7 @@ export class OpenAICompatProvider {
98
98
  const decoder = new TextDecoder();
99
99
  let buffer = "";
100
100
  const toolCallAccs = new Map();
101
+ let usage;
101
102
  try {
102
103
  while (true) {
103
104
  const { done, value } = await reader.read();
@@ -116,8 +117,17 @@ export class OpenAICompatProvider {
116
117
  try {
117
118
  const parsed = JSON.parse(data);
118
119
  const choice = parsed.choices?.[0];
119
- if (!choice)
120
+ if (!choice) {
121
+ // Usage comes in the last chunk with empty choices
122
+ if (parsed.usage) {
123
+ usage = {
124
+ promptTokens: parsed.usage.prompt_tokens ?? 0,
125
+ completionTokens: parsed.usage.completion_tokens ?? 0,
126
+ totalTokens: parsed.usage.total_tokens ?? 0,
127
+ };
128
+ }
120
129
  continue;
130
+ }
121
131
  const delta = choice.delta || {};
122
132
  const finishReason = choice.finish_reason;
123
133
  if (delta.reasoning_content) {
@@ -163,6 +173,9 @@ export class OpenAICompatProvider {
163
173
  }
164
174
  }
165
175
  }
176
+ if (usage) {
177
+ yield { type: "done", usage };
178
+ }
166
179
  }
167
180
  finally {
168
181
  clearTimeout(timeoutId);
@@ -232,6 +245,17 @@ export class OpenAICompatProvider {
232
245
  });
233
246
  }
234
247
  }
248
+ // Append usage from API response
249
+ if (data.usage) {
250
+ chunks.push({
251
+ type: "done",
252
+ usage: {
253
+ promptTokens: data.usage.prompt_tokens ?? 0,
254
+ completionTokens: data.usage.completion_tokens ?? 0,
255
+ totalTokens: data.usage.total_tokens ?? 0,
256
+ },
257
+ });
258
+ }
235
259
  return chunks;
236
260
  }
237
261
  catch (err) {
@@ -3,9 +3,9 @@ import { DEFAULT_SECURITY_CONFIG } from "../../config/security";
3
3
  const FALLBACK_BASH_CONFIG = {
4
4
  blacklist: ['rm', 'dd', 'chmod', 'wget', 'curl', 'scp', 'ssh', 'nc', 'netcat'],
5
5
  whitelist: [],
6
- blockDangerousFlags: true,
6
+ blockDangerousFlags: false,
7
7
  dangerousFlags: ['--force', '-rf', '--no-preserve-root'],
8
- dangerousOperators: ['>', '>>', '2>', '2>>', '|', '&&', '||', ';', '&', '`'],
8
+ dangerousOperators: ['>', '>>', '2>', '2>>', '|', '&&', '||', ';', '`'],
9
9
  logCommands: true,
10
10
  };
11
11
  export const DEFAULT_BASH_CONFIG = DEFAULT_SECURITY_CONFIG?.bash || FALLBACK_BASH_CONFIG;
@@ -55,6 +55,8 @@ function containsOperator(command, op) {
55
55
  const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
56
56
  // For single-char operators that are prefixes of multi-char ones,
57
57
  // ensure they don't match when part of a longer sequence.
58
+ // Note: "&" is no longer in dangerousOperators (&& is legitimate),
59
+ // but keep the special case for defense-in-depth if manually added.
58
60
  if (op === '&')
59
61
  return /(?<!&)&(?!&)/.test(command);
60
62
  if (op === '|')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.14.3",
3
+ "version": "0.15.1",
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": {