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) => {
|
package/dist/core/agent.js
CHANGED
|
@@ -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
|
-
|
|
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,
|