micro-models-agent 0.16.4 → 0.16.6

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/repl.js CHANGED
@@ -59,6 +59,7 @@ export class Repl {
59
59
  configDir;
60
60
  baseDir;
61
61
  noAgentsMd;
62
+ pendingClipboardImage = null;
62
63
  constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd) {
63
64
  this.agent = agent;
64
65
  this.config = config;
@@ -738,7 +739,7 @@ export class Repl {
738
739
  });
739
740
  if (process.stdin.isTTY) {
740
741
  readline.emitKeypressEvents(process.stdin);
741
- process.stdin.on("keypress", (str, key) => {
742
+ process.stdin.on("keypress", async (str, key) => {
742
743
  if (key.name === "escape") {
743
744
  const now = Date.now();
744
745
  if (now - this.lastEscTime < this.doubleEscDelay) {
@@ -751,8 +752,38 @@ export class Repl {
751
752
  }
752
753
  this.lastEscTime = now;
753
754
  }
755
+ // Ctrl+V: try to paste image from clipboard
756
+ if (key.ctrl && key.name === "v" && !this.agentRunning) {
757
+ try {
758
+ const { readClipboardImage, loadFileAsDataUrl } = await import("../llm/image-utils");
759
+ const clipPath = await readClipboardImage();
760
+ if (clipPath) {
761
+ const { dataUrl } = loadFileAsDataUrl(clipPath);
762
+ this.pendingClipboardImage = dataUrl;
763
+ const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
764
+ console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
765
+ this.rl.prompt();
766
+ }
767
+ }
768
+ catch {
769
+ // clipboard read failed — ignore, let terminal paste text normally
770
+ }
771
+ }
754
772
  });
755
773
  }
774
+ // SIGINT: first Ctrl+C sends graceful shutdown, second forces exit
775
+ process.on("SIGINT", () => {
776
+ if (this.agentRunning) {
777
+ console.log(pc.yellow("\n[Ctrl+C] Остановка агента... (ещё раз — принудительно)"));
778
+ this.agent.shutdown();
779
+ this.agentRunning = false;
780
+ // Force exit after 2s if graceful shutdown hangs
781
+ setTimeout(() => process.exit(1), 2000).unref();
782
+ }
783
+ else {
784
+ process.exit(0);
785
+ }
786
+ });
756
787
  }
757
788
  isMultiLineInput(line) {
758
789
  if (line.endsWith("\\"))
@@ -769,6 +800,17 @@ export class Repl {
769
800
  this.agentRunning = true;
770
801
  this.rl.pause();
771
802
  try {
803
+ // Attach pending clipboard image if Ctrl+V was pressed
804
+ if (this.pendingClipboardImage) {
805
+ const contextManager = this.agent.contextManager;
806
+ if (contextManager) {
807
+ contextManager.addPendingImage({
808
+ type: "image_url",
809
+ image_url: { url: this.pendingClipboardImage },
810
+ });
811
+ }
812
+ this.pendingClipboardImage = null;
813
+ }
772
814
  process.stdout.write("\n" + pc.green(t("repl.agent")));
773
815
  const renderer = new Renderer({ spinner: this.config.ui?.spinner ?? true });
774
816
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
@@ -9,6 +9,7 @@ const TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000;
9
9
  export class Agent {
10
10
  deps;
11
11
  systemPromptAdded = false;
12
+ shutdownRequested = false;
12
13
  constructor(deps) {
13
14
  this.deps = deps;
14
15
  }
@@ -115,7 +116,7 @@ export class Agent {
115
116
  const MAX_HALLUCINATION_RETRIES = 3;
116
117
  let consecutiveToolFailures = 0;
117
118
  const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
118
- while (iteration < config.maxToolIterations) {
119
+ while (iteration < config.maxToolIterations && !this.shutdownRequested) {
119
120
  iteration++;
120
121
  pluginManager.runOnBeforeThink({
121
122
  iteration,
@@ -445,6 +446,7 @@ export class Agent {
445
446
  }
446
447
  }
447
448
  shutdown() {
449
+ this.shutdownRequested = true;
448
450
  const { pluginManager, logger, sessionManager, contextManager } = this.deps;
449
451
  contextManager.onCompact = null;
450
452
  const killed = processRegistry.killAll();
@@ -43,7 +43,9 @@ function extractBaseCommand(trimmed) {
43
43
  // Extract basename from path (e.g. /usr/bin/rm → rm)
44
44
  const raw = tokens[i];
45
45
  const parts = raw.split(/[\\/]/);
46
- return parts[parts.length - 1] || raw;
46
+ const basename = parts[parts.length - 1] || raw;
47
+ // Strip .exe/.cmd/.bat extensions for blacklist matching (e.g. powershell.exe → powershell)
48
+ return basename.replace(/\.(exe|cmd|bat)$/i, '') || basename;
47
49
  }
48
50
  /**
49
51
  * Check if a shell operator appears as a standalone token in the command.
@@ -114,7 +116,7 @@ export function isCommandAllowed(command, securityConfig) {
114
116
  };
115
117
  }
116
118
  // Also check the raw first token in case it's a simple name
117
- const rawFirst = trimmedCommand.split(/\s+/)[0];
119
+ const rawFirst = trimmedCommand.split(/\s+/)[0].replace(/\.(exe|cmd|bat)$/i, '');
118
120
  if (rawFirst !== baseCommand && config.blacklist.includes(rawFirst)) {
119
121
  return {
120
122
  allowed: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
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": {