min-agent 0.1.7 → 0.1.9

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/agent.js CHANGED
@@ -14,6 +14,8 @@ import { DoomLoopDetector } from "./doom-loop.js";
14
14
  import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
15
15
  import { ThinkingBodySplitter, stripThinkingFromAssistantText } from "./assistant-stream.js";
16
16
  import { printHeader, printDivider, printToolCall, printToolResult, printDone } from "./output.js";
17
+ import { killActiveProcesses } from "./tools/bash.js";
18
+ import { setConfirmReadline } from "./confirm.js";
17
19
  import readline from "readline";
18
20
  const MAX_STEPS = 30;
19
21
  function dimStyle() {
@@ -142,10 +144,12 @@ export async function runChat(modelId, resumeSessionId) {
142
144
  output: process.stdout,
143
145
  prompt: "\x1b[36m> \x1b[0m",
144
146
  });
147
+ setConfirmReadline(rl);
145
148
  // Handle Ctrl+C: abort current generation, don't exit
146
149
  let abortController = null;
147
150
  process.on("SIGINT", () => {
148
151
  if (abortController) {
152
+ killActiveProcesses();
149
153
  abortController.abort();
150
154
  abortController = null;
151
155
  console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
@@ -157,7 +161,20 @@ export async function runChat(modelId, resumeSessionId) {
157
161
  rl.close();
158
162
  }
159
163
  });
160
- console.log("\x1b[90m输入消息开始对话,输入 /help 查看命令,/exit 退出\x1b[0m\n");
164
+ // Listen for ESC key to cancel running agent task via keypress events
165
+ if (process.stdin.isTTY) {
166
+ readline.emitKeypressEvents(process.stdin, rl);
167
+ process.stdin.on("keypress", (_ch, key) => {
168
+ if (key && key.name === "escape" && abortController) {
169
+ killActiveProcesses();
170
+ abortController.abort();
171
+ abortController = null;
172
+ console.log("\n\x1b[90m(esc cancelled)\x1b[0m\n");
173
+ rl.prompt();
174
+ }
175
+ });
176
+ }
177
+ console.log("\x1b[90m输入消息开始对话,输入 /help 查看命令,Esc 取消运行,/exit 退出\x1b[0m\n");
161
178
  rl.prompt();
162
179
  // Multi-line paste detection: collect rapid successive lines
163
180
  let pasteBuffer = [];
@@ -251,9 +268,11 @@ export async function runCode(modelId, resumeSessionId) {
251
268
  output: process.stdout,
252
269
  prompt: "\x1b[32m❯ \x1b[0m",
253
270
  });
271
+ setConfirmReadline(rl);
254
272
  let abortController = null;
255
273
  process.on("SIGINT", () => {
256
274
  if (abortController) {
275
+ killActiveProcesses();
257
276
  abortController.abort();
258
277
  abortController = null;
259
278
  console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
@@ -264,7 +283,20 @@ export async function runCode(modelId, resumeSessionId) {
264
283
  rl.close();
265
284
  }
266
285
  });
267
- console.log("\x1b[90m输入任务开始编码,/help 查看命令,Ctrl+C 中断\x1b[0m\n");
286
+ // Listen for ESC key to cancel running agent task via keypress events
287
+ if (process.stdin.isTTY) {
288
+ readline.emitKeypressEvents(process.stdin, rl);
289
+ process.stdin.on("keypress", (_ch, key) => {
290
+ if (key && key.name === "escape" && abortController) {
291
+ killActiveProcesses();
292
+ abortController.abort();
293
+ abortController = null;
294
+ console.log("\n\x1b[90m(esc cancelled)\x1b[0m\n");
295
+ rl.prompt();
296
+ }
297
+ });
298
+ }
299
+ console.log("\x1b[90m输入任务开始编码,/help 查看命令,Esc 取消运行,Ctrl+C 中断\x1b[0m\n");
268
300
  rl.prompt();
269
301
  let pasteBuffer = [];
270
302
  let pasteTimer = null;
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { discoverSkills, getSkills } from "./skills.js";
4
4
  import { loadMemories, addMemory, deleteMemory, searchMemories } from "./memory.js";
5
5
  import { runSetup, isConfigured, loadConfig, saveConfig, fetchModels, getConfigDir, getRulesFile } from "./config.js";
6
6
  import { setAutoApprove } from "./confirm.js";
7
- import { existsSync, writeFileSync, mkdirSync } from "fs";
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
8
8
  import path from "path";
9
9
  const rawArgs = process.argv.slice(2);
10
10
  // Extract leading global flags only, so subcommands can still use "-y"
@@ -108,6 +108,12 @@ async function main() {
108
108
  printUsage();
109
109
  process.exit(0);
110
110
  }
111
+ if (args[0] === "--version" || args[0] === "-v") {
112
+ const pkgPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../package.json");
113
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
114
+ console.log(pkg.version);
115
+ process.exit(0);
116
+ }
111
117
  const command = args[0];
112
118
  switch (command) {
113
119
  case "setup": {
package/dist/confirm.js CHANGED
@@ -5,15 +5,24 @@ export function setAutoApprove(value) {
5
5
  export function isAutoApprove() {
6
6
  return autoApprove;
7
7
  }
8
+ /** Optional readline interface to pause/resume during confirmation prompts. */
9
+ let _rl = null;
10
+ export function setConfirmReadline(rl) {
11
+ _rl = rl;
12
+ }
8
13
  /** Ask user for confirmation. Returns true if approved. */
9
14
  export async function confirm(message) {
10
15
  if (autoApprove)
11
16
  return true;
12
- process.stdout.write(`\n\x1b[33m⚠ ${message} [y/N] \x1b[0m`);
17
+ // Pause readline so it doesn't consume/echo the keystroke
18
+ _rl?.pause();
19
+ process.stdout.write(`\n\n\x1b[1;34m⚠ ${message} [y/N] \x1b[0m\n\n`);
13
20
  return new Promise((resolve) => {
14
21
  const wasRaw = process.stdin.isRaw;
15
22
  if (process.stdin.isTTY)
16
23
  process.stdin.setRawMode(true);
24
+ // Ensure stdin is flowing so we can receive data even after readline pause
25
+ process.stdin.resume();
17
26
  const onData = (buf) => {
18
27
  const ch = buf.toString();
19
28
  process.stdin.removeListener("data", onData);
@@ -21,6 +30,8 @@ export async function confirm(message) {
21
30
  process.stdin.setRawMode(wasRaw ?? false);
22
31
  // Echo the character and newline
23
32
  process.stdout.write(ch === "\r" || ch === "\n" ? "\n" : `${ch}\n`);
33
+ // Resume readline after confirmation
34
+ _rl?.resume();
24
35
  const answer = ch.trim().toLowerCase();
25
36
  resolve(answer === "y");
26
37
  };
@@ -1,6 +1,15 @@
1
1
  import { tool, jsonSchema } from "ai";
2
2
  import { spawn } from "child_process";
3
3
  import { confirm, isDangerousCommand, isAutoApprove } from "../confirm.js";
4
+ /** Track active child processes so they can be killed on abort (e.g. ESC). */
5
+ const activeProcesses = new Set();
6
+ /** Kill all active child processes spawned by the bash tool. */
7
+ export function killActiveProcesses() {
8
+ for (const proc of activeProcesses) {
9
+ killProcess(proc.pid);
10
+ }
11
+ activeProcesses.clear();
12
+ }
4
13
  export const bashTool = tool({
5
14
  description: "Run a shell command. Use this for system operations, running builds, tests, git commands, etc. The command runs in the current working directory. You SHOULD set a timeout based on how long you expect the command to take. If no timeout is set, the command runs until it finishes or the user manually interrupts (Ctrl+C).",
6
15
  inputSchema: jsonSchema({
@@ -31,6 +40,7 @@ export const bashTool = tool({
31
40
  detached: process.platform !== "win32",
32
41
  env: { ...process.env, ...(isWin ? { PYTHONIOENCODING: "utf-8" } : {}) },
33
42
  });
43
+ activeProcesses.add(proc);
34
44
  proc.stdout?.on("data", (chunk) => chunks.push(chunk));
35
45
  proc.stderr?.on("data", (chunk) => chunks.push(chunk));
36
46
  // Timeout kill (only if timeout is specified)
@@ -52,6 +62,7 @@ export const bashTool = tool({
52
62
  };
53
63
  process.on("SIGINT", sigintHandler);
54
64
  proc.on("close", (code) => {
65
+ activeProcesses.delete(proc);
55
66
  process.removeListener("SIGINT", sigintHandler);
56
67
  if (timer)
57
68
  clearTimeout(timer);
@@ -66,6 +77,7 @@ export const bashTool = tool({
66
77
  }
67
78
  });
68
79
  proc.on("error", (err) => {
80
+ activeProcesses.delete(proc);
69
81
  process.removeListener("SIGINT", sigintHandler);
70
82
  if (timer)
71
83
  clearTimeout(timer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "min-agent",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "Minimal AI coding agent with tool use, MCP, and skills support",
6
6
  "license": "MIT",