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 +9 -13
- package/dist/assistant-stream.js +14 -1
- package/dist/confirm.js +15 -6
- package/dist/markdown.js +147 -3
- package/dist/tools/index.js +21 -1
- package/package.json +1 -1
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 {
|
|
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
|
|
213
|
+
// Auto-save session on exit
|
|
214
214
|
if (messages.length > 0) {
|
|
215
|
-
const {
|
|
216
|
-
|
|
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 {
|
|
320
|
-
|
|
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 =
|
|
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
|
|
738
|
-
const builtinTools =
|
|
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();
|
package/dist/assistant-stream.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
12
|
+
process.stdout.write(`\n\x1b[33m⚠ ${message} [y/N] \x1b[0m`);
|
|
14
13
|
return new Promise((resolve) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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);
|
package/dist/tools/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|