min-agent 0.1.7 → 0.1.8

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,19 @@ 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
165
+ if (process.stdin.isTTY) {
166
+ process.stdin.on("data", (buf) => {
167
+ if (buf.length === 1 && buf[0] === 0x1b && abortController) {
168
+ killActiveProcesses();
169
+ abortController.abort();
170
+ abortController = null;
171
+ console.log("\n\x1b[90m(esc cancelled)\x1b[0m\n");
172
+ rl.prompt();
173
+ }
174
+ });
175
+ }
176
+ console.log("\x1b[90m输入消息开始对话,输入 /help 查看命令,Esc 取消运行,/exit 退出\x1b[0m\n");
161
177
  rl.prompt();
162
178
  // Multi-line paste detection: collect rapid successive lines
163
179
  let pasteBuffer = [];
@@ -251,9 +267,11 @@ export async function runCode(modelId, resumeSessionId) {
251
267
  output: process.stdout,
252
268
  prompt: "\x1b[32m❯ \x1b[0m",
253
269
  });
270
+ setConfirmReadline(rl);
254
271
  let abortController = null;
255
272
  process.on("SIGINT", () => {
256
273
  if (abortController) {
274
+ killActiveProcesses();
257
275
  abortController.abort();
258
276
  abortController = null;
259
277
  console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
@@ -264,7 +282,19 @@ export async function runCode(modelId, resumeSessionId) {
264
282
  rl.close();
265
283
  }
266
284
  });
267
- console.log("\x1b[90m输入任务开始编码,/help 查看命令,Ctrl+C 中断\x1b[0m\n");
285
+ // Listen for ESC key to cancel running agent task
286
+ if (process.stdin.isTTY) {
287
+ process.stdin.on("data", (buf) => {
288
+ if (buf.length === 1 && buf[0] === 0x1b && abortController) {
289
+ killActiveProcesses();
290
+ abortController.abort();
291
+ abortController = null;
292
+ console.log("\n\x1b[90m(esc cancelled)\x1b[0m\n");
293
+ rl.prompt();
294
+ }
295
+ });
296
+ }
297
+ console.log("\x1b[90m输入任务开始编码,/help 查看命令,Esc 取消运行,Ctrl+C 中断\x1b[0m\n");
268
298
  rl.prompt();
269
299
  let pasteBuffer = [];
270
300
  let pasteTimer = null;
package/dist/confirm.js CHANGED
@@ -5,11 +5,18 @@ 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)
@@ -21,6 +28,8 @@ export async function confirm(message) {
21
28
  process.stdin.setRawMode(wasRaw ?? false);
22
29
  // Echo the character and newline
23
30
  process.stdout.write(ch === "\r" || ch === "\n" ? "\n" : `${ch}\n`);
31
+ // Resume readline after confirmation
32
+ _rl?.resume();
24
33
  const answer = ch.trim().toLowerCase();
25
34
  resolve(answer === "y");
26
35
  };
@@ -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.8",
4
4
  "type": "module",
5
5
  "description": "Minimal AI coding agent with tool use, MCP, and skills support",
6
6
  "license": "MIT",