min-agent 0.1.6 → 0.1.7

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
@@ -2,7 +2,7 @@ import { streamText, stepCountIs } from "ai";
2
2
  import { readFileSync, existsSync } from "fs";
3
3
  import path from "path";
4
4
  import { resolveModel } from "./provider.js";
5
- import { createTools } from "./tools/index.js";
5
+ import { createChatTools, createCodeTools } from "./tools/index.js";
6
6
  import { initMcp, shutdownMcp, getMcpTools, loadMcpConfig, getMcpStatus } from "./mcp.js";
7
7
  import { discoverSkills, getSkillsTool, getSkillsSystemPrompt, getSkills } from "./skills.js";
8
8
  import { loadInstructions } from "./instructions.js";
@@ -210,11 +210,10 @@ export async function runChat(modelId, resumeSessionId) {
210
210
  });
211
211
  // Wait for close
212
212
  await new Promise((resolve) => rl.on("close", resolve));
213
- // Auto-save session on exit with LLM-generated title
213
+ // Auto-save session on exit
214
214
  if (messages.length > 0) {
215
- const { saveSessionWithTitle } = await import("./sessions.js");
216
- const model = resolveModel(modelId);
217
- sessionId = await saveSessionWithTitle(messages, model, sessionId);
215
+ const { saveSession } = await import("./sessions.js");
216
+ sessionId = saveSession(messages, sessionId);
218
217
  console.log(`\x1b[90m Session saved: ${sessionId}\x1b[0m`);
219
218
  }
220
219
  printDivider();
@@ -316,9 +315,8 @@ export async function runCode(modelId, resumeSessionId) {
316
315
  });
317
316
  await new Promise((resolve) => rl.on("close", resolve));
318
317
  if (messages.length > 0) {
319
- const { saveSessionWithTitle } = await import("./sessions.js");
320
- const model = resolveModel(modelId);
321
- sessionId = await saveSessionWithTitle(messages, model, sessionId);
318
+ const { saveSession } = await import("./sessions.js");
319
+ sessionId = saveSession(messages, sessionId);
322
320
  console.log(`\x1b[90m Session saved: ${sessionId}\x1b[0m`);
323
321
  }
324
322
  rl.close();
@@ -344,7 +342,7 @@ export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSi
344
342
  console.log(`\x1b[90m ✓ Compacted (${estimateTokens(messages)} tokens estimated)\x1b[0m`);
345
343
  }
346
344
  }
347
- const builtinTools = createTools();
345
+ const builtinTools = createCodeTools();
348
346
  const mcpTools = getMcpTools();
349
347
  const memoryTools = getMemoryTools();
350
348
  const pluginTools = await loadPluginTools();
@@ -734,12 +732,11 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
734
732
  }
735
733
  }
736
734
  }
737
- // Merge all tools: builtin + MCP + skill + memory + plugins + task
738
- const builtinTools = createTools();
735
+ // Merge tools: chat builtin + MCP + skill + memory + plugins (no task/explore in chat mode)
736
+ const builtinTools = createChatTools();
739
737
  const mcpTools = getMcpTools();
740
738
  const memoryTools = getMemoryTools();
741
739
  const pluginTools = await loadPluginTools();
742
- const { createTaskTool } = await import("./tools/task.js");
743
740
  const skills = getSkills();
744
741
  const allTools = { ...builtinTools, ...memoryTools, ...pluginTools };
745
742
  for (const [id, t] of Object.entries(mcpTools)) {
@@ -748,7 +745,6 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
748
745
  if (skills.length > 0) {
749
746
  allTools["skill"] = getSkillsTool();
750
747
  }
751
- allTools["task"] = createTaskTool(modelId);
752
748
  let stepCount = 0;
753
749
  let hasError = false;
754
750
  const doomLoop = new DoomLoopDetector();
@@ -41,6 +41,7 @@ export class ThinkingBodySplitter {
41
41
  drain(isFinal) {
42
42
  let display = "";
43
43
  let thinking = "";
44
+ let hadThinking = false;
44
45
  while (this.buf.length > 0) {
45
46
  const open = findFirstOpen(this.buf);
46
47
  if (!open) {
@@ -62,7 +63,11 @@ export class ThinkingBodySplitter {
62
63
  break;
63
64
  }
64
65
  if (open.index > 0) {
65
- display += this.buf.slice(0, open.index);
66
+ // Strip trailing newlines before thinking block
67
+ let pre = this.buf.slice(0, open.index);
68
+ pre = pre.replace(/\n+$/, "");
69
+ if (pre)
70
+ display += pre;
66
71
  this.buf = this.buf.slice(open.index);
67
72
  }
68
73
  const low = lower(this.buf);
@@ -81,8 +86,16 @@ export class ThinkingBodySplitter {
81
86
  }
82
87
  const inner = this.buf.slice(afterOpen, closeRel);
83
88
  thinking += inner;
89
+ hadThinking = true;
84
90
  this.buf = this.buf.slice(closeRel + open.tag.close.length);
85
91
  }
92
+ // Strip leading newlines from display that follow a thinking block
93
+ if (hadThinking && display.length === 0 && this.buf.startsWith("\n")) {
94
+ // Will be handled on next feed
95
+ }
96
+ if (hadThinking) {
97
+ display = display.replace(/^\n+/, "");
98
+ }
86
99
  return { display, thinking };
87
100
  }
88
101
  }
package/dist/confirm.js CHANGED
@@ -1,4 +1,3 @@
1
- import readline from "readline";
2
1
  let autoApprove = false;
3
2
  export function setAutoApprove(value) {
4
3
  autoApprove = value;
@@ -10,12 +9,22 @@ export function isAutoApprove() {
10
9
  export async function confirm(message) {
11
10
  if (autoApprove)
12
11
  return true;
13
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
12
+ process.stdout.write(`\n\x1b[33m⚠ ${message} [y/N] \x1b[0m`);
14
13
  return new Promise((resolve) => {
15
- rl.question(`\x1b[33m⚠ ${message} [y/N] \x1b[0m`, (answer) => {
16
- rl.close();
17
- resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
18
- });
14
+ const wasRaw = process.stdin.isRaw;
15
+ if (process.stdin.isTTY)
16
+ process.stdin.setRawMode(true);
17
+ const onData = (buf) => {
18
+ const ch = buf.toString();
19
+ process.stdin.removeListener("data", onData);
20
+ if (process.stdin.isTTY)
21
+ process.stdin.setRawMode(wasRaw ?? false);
22
+ // Echo the character and newline
23
+ process.stdout.write(ch === "\r" || ch === "\n" ? "\n" : `${ch}\n`);
24
+ const answer = ch.trim().toLowerCase();
25
+ resolve(answer === "y");
26
+ };
27
+ process.stdin.on("data", onData);
19
28
  });
20
29
  }
21
30
  /** Check if a shell command is potentially dangerous */
package/dist/markdown.js CHANGED
@@ -37,6 +37,8 @@ export class MarkdownRenderer {
37
37
  buffer = "";
38
38
  inCodeBlock = false;
39
39
  codeLang = "";
40
+ tableRows = [];
41
+ inTable = false;
40
42
  /** Process a text delta and return formatted output */
41
43
  write(text) {
42
44
  this.buffer += text;
@@ -49,19 +51,161 @@ export class MarkdownRenderer {
49
51
  break;
50
52
  const line = this.buffer.slice(0, nlIdx);
51
53
  this.buffer = this.buffer.slice(nlIdx + 1);
54
+ // Table handling: collect rows, render when table ends
55
+ if (this.isTableRow(line)) {
56
+ if (this.isTableSeparator(line)) {
57
+ this.inTable = true;
58
+ continue;
59
+ }
60
+ this.inTable = true;
61
+ this.tableRows.push(this.parseTableRow(line));
62
+ continue;
63
+ }
64
+ // Table just ended — flush it
65
+ if (this.inTable) {
66
+ output += this.renderTable(c);
67
+ this.tableRows = [];
68
+ this.inTable = false;
69
+ }
52
70
  output += this.formatLine(line, c) + "\n";
53
71
  }
72
+ // Stream partial line immediately for real-time feel
73
+ // Hold back only if it could be start of table or code fence
74
+ if (!this.inTable && !this.inCodeBlock && this.buffer.length > 0) {
75
+ if (!this.buffer.startsWith("|") && !this.buffer.startsWith("`")) {
76
+ const partial = this.buffer;
77
+ this.buffer = "";
78
+ output += this.formatInline(partial, c);
79
+ }
80
+ }
81
+ // If we're in a table and buffer starts with "|", hold it (waiting for \n)
82
+ // If we're in a table and buffer does NOT start with "|", the table ended mid-stream
83
+ if (this.inTable && this.buffer.length > 0 && !this.buffer.startsWith("|")) {
84
+ output += this.renderTable(c);
85
+ this.tableRows = [];
86
+ this.inTable = false;
87
+ // Now output the non-table buffer content
88
+ if (this.buffer.length > 0 && !this.buffer.startsWith("`")) {
89
+ const partial = this.buffer;
90
+ this.buffer = "";
91
+ output += this.formatInline(partial, c);
92
+ }
93
+ }
54
94
  return output;
55
95
  }
56
96
  /** Flush remaining buffer */
57
97
  flush() {
58
- if (!this.buffer)
59
- return "";
60
98
  const c = useColor() ? C : Z;
61
- const out = this.formatLine(this.buffer, c);
99
+ let out = "";
100
+ // If buffer has a pending table row, add it
101
+ if (this.inTable && this.buffer.length > 0) {
102
+ if (this.isTableRow(this.buffer)) {
103
+ if (!this.isTableSeparator(this.buffer)) {
104
+ this.tableRows.push(this.parseTableRow(this.buffer));
105
+ }
106
+ this.buffer = "";
107
+ }
108
+ }
109
+ // Flush pending table
110
+ if (this.inTable && this.tableRows.length > 0) {
111
+ out += this.renderTable(c);
112
+ this.tableRows = [];
113
+ this.inTable = false;
114
+ }
115
+ if (!this.buffer)
116
+ return out;
117
+ out += this.formatLine(this.buffer, c);
62
118
  this.buffer = "";
63
119
  return out;
64
120
  }
121
+ isTableRow(line) {
122
+ const trimmed = line.trim();
123
+ return trimmed.startsWith("|") && trimmed.endsWith("|") && trimmed.includes("|", 1);
124
+ }
125
+ isTableSeparator(line) {
126
+ return /^\s*\|[\s:]*-+[\s:|-]*\|\s*$/.test(line);
127
+ }
128
+ parseTableRow(line) {
129
+ return line.trim().slice(1, -1).split("|").map((cell) => cell.trim());
130
+ }
131
+ /** Get display width of a string (CJK/emoji chars = 2, others = 1) */
132
+ displayWidth(str) {
133
+ let width = 0;
134
+ for (const ch of str) {
135
+ const code = ch.codePointAt(0) ?? 0;
136
+ if (
137
+ // CJK
138
+ (code >= 0x1100 && code <= 0x115f) ||
139
+ (code >= 0x2e80 && code <= 0x303e) ||
140
+ (code >= 0x3040 && code <= 0x33bf) ||
141
+ (code >= 0x3400 && code <= 0x4dbf) ||
142
+ (code >= 0x4e00 && code <= 0x9fff) ||
143
+ (code >= 0xa000 && code <= 0xa4cf) ||
144
+ (code >= 0xac00 && code <= 0xd7af) ||
145
+ (code >= 0xf900 && code <= 0xfaff) ||
146
+ (code >= 0xfe30 && code <= 0xfe6f) ||
147
+ (code >= 0xff01 && code <= 0xff60) ||
148
+ (code >= 0xffe0 && code <= 0xffe6) ||
149
+ (code >= 0x20000 && code <= 0x2fffd) ||
150
+ (code >= 0x30000 && code <= 0x3fffd) ||
151
+ // Emoji
152
+ (code >= 0x1f300 && code <= 0x1f9ff) || // Misc Symbols, Emoticons, Dingbats, etc.
153
+ (code >= 0x1fa00 && code <= 0x1faff) || // Chess, Extended-A
154
+ (code >= 0x2600 && code <= 0x27bf) || // Misc Symbols, Dingbats
155
+ (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors (skip width)
156
+ (code >= 0x200d && code <= 0x200d) || // ZWJ (skip width)
157
+ (code >= 0x1f1e0 && code <= 0x1f1ff) // Regional Indicators (flags)
158
+ ) {
159
+ // Variation selectors and ZWJ are zero-width joiners
160
+ if ((code >= 0xfe00 && code <= 0xfe0f) || code === 0x200d) {
161
+ width += 0;
162
+ }
163
+ else {
164
+ width += 2;
165
+ }
166
+ }
167
+ else {
168
+ width += 1;
169
+ }
170
+ }
171
+ return width;
172
+ }
173
+ /** Pad string to target display width */
174
+ padToWidth(str, targetWidth) {
175
+ const currentWidth = this.displayWidth(str);
176
+ const padding = targetWidth - currentWidth;
177
+ return padding > 0 ? str + " ".repeat(padding) : str;
178
+ }
179
+ renderTable(c) {
180
+ if (this.tableRows.length === 0)
181
+ return "";
182
+ // Calculate column widths based on display width (CJK-aware)
183
+ const colCount = Math.max(...this.tableRows.map((r) => r.length));
184
+ const widths = Array(colCount).fill(0);
185
+ for (const row of this.tableRows) {
186
+ for (let i = 0; i < row.length; i++) {
187
+ widths[i] = Math.max(widths[i], this.displayWidth(row[i] ?? ""));
188
+ }
189
+ }
190
+ const lines = [];
191
+ const top = `${c.dim}┌${widths.map((w) => "─".repeat(w + 2)).join("┬")}┐${c.reset}`;
192
+ const mid = `${c.dim}├${widths.map((w) => "─".repeat(w + 2)).join("┼")}┤${c.reset}`;
193
+ const bot = `${c.dim}└${widths.map((w) => "─".repeat(w + 2)).join("┴")}┘${c.reset}`;
194
+ lines.push(top);
195
+ for (let r = 0; r < this.tableRows.length; r++) {
196
+ const row = this.tableRows[r];
197
+ const cells = widths.map((w, i) => {
198
+ const cell = row[i] ?? "";
199
+ const padded = this.padToWidth(cell, w);
200
+ return r === 0 ? `${c.bold}${padded}${c.reset}` : padded;
201
+ });
202
+ lines.push(`${c.dim}│${c.reset} ${cells.join(` ${c.dim}│${c.reset} `)} ${c.dim}│${c.reset}`);
203
+ if (r === 0)
204
+ lines.push(mid);
205
+ }
206
+ lines.push(bot);
207
+ return lines.join("\n") + "\n";
208
+ }
65
209
  formatInline(line, c) {
66
210
  // Split by inline code spans; format outside segments only
67
211
  const parts = line.split(/(`[^`]*`)/g);
@@ -9,7 +9,23 @@ import { webFetchTool } from "./web_fetch.js";
9
9
  import { todoTool } from "./todo.js";
10
10
  import { questionTool } from "./question.js";
11
11
  import { codeSearchTool } from "./code_search.js";
12
- export function createTools() {
12
+ /** Chat mode: general assistant tools (fewer tools = less token overhead) */
13
+ export function createChatTools() {
14
+ const tools = {
15
+ bash: bashTool,
16
+ read: readTool,
17
+ write: writeTool,
18
+ edit: editTool,
19
+ glob: globTool,
20
+ grep: grepTool,
21
+ web_search: webSearchTool,
22
+ web_fetch: webFetchTool,
23
+ question: questionTool,
24
+ };
25
+ return tools;
26
+ }
27
+ /** Code mode: full tool set including task tracking and code search */
28
+ export function createCodeTools() {
13
29
  const tools = {
14
30
  bash: bashTool,
15
31
  read: readTool,
@@ -27,3 +43,7 @@ export function createTools() {
27
43
  }
28
44
  return tools;
29
45
  }
46
+ /** @deprecated Use createChatTools or createCodeTools */
47
+ export function createTools() {
48
+ return createChatTools();
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "min-agent",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Minimal AI coding agent with tool use, MCP, and skills support",
6
6
  "license": "MIT",