min-agent 0.1.8 → 0.2.0
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 +6 -421
- package/dist/cli.js +16 -5
- package/dist/compaction.js +91 -29
- package/dist/confirm.js +26 -5
- package/dist/skills.js +29 -19
- package/dist/tui/App.js +15 -0
- package/dist/tui/ConfirmBar.js +13 -0
- package/dist/tui/InputBar.js +83 -0
- package/dist/tui/MessageList.js +33 -0
- package/dist/tui/Spinner.js +6 -0
- package/dist/tui/StatusBar.js +16 -0
- package/dist/tui/index.js +94 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui-chat.js +384 -0
- package/package.json +5 -1
package/dist/agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync, existsSync } from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { resolveModel } from "./provider.js";
|
|
5
5
|
import { createChatTools, createCodeTools } from "./tools/index.js";
|
|
6
|
-
import { initMcp, shutdownMcp, getMcpTools
|
|
6
|
+
import { initMcp, shutdownMcp, getMcpTools } from "./mcp.js";
|
|
7
7
|
import { discoverSkills, getSkillsTool, getSkillsSystemPrompt, getSkills } from "./skills.js";
|
|
8
8
|
import { loadInstructions } from "./instructions.js";
|
|
9
9
|
import { getMemorySystemPrompt, getMemoryTools } from "./memory.js";
|
|
@@ -11,12 +11,8 @@ import { needsCompaction, compactMessages, estimateTokens, TokenTracker } from "
|
|
|
11
11
|
import { loadPluginTools } from "./plugins.js";
|
|
12
12
|
import { MarkdownRenderer } from "./markdown.js";
|
|
13
13
|
import { DoomLoopDetector } from "./doom-loop.js";
|
|
14
|
-
import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
|
|
15
14
|
import { ThinkingBodySplitter, stripThinkingFromAssistantText } from "./assistant-stream.js";
|
|
16
15
|
import { printHeader, printDivider, printToolCall, printToolResult, printDone } from "./output.js";
|
|
17
|
-
import { killActiveProcesses } from "./tools/bash.js";
|
|
18
|
-
import { setConfirmReadline } from "./confirm.js";
|
|
19
|
-
import readline from "readline";
|
|
20
16
|
const MAX_STEPS = 30;
|
|
21
17
|
function dimStyle() {
|
|
22
18
|
if ("NO_COLOR" in process.env)
|
|
@@ -117,243 +113,6 @@ export async function runAgent(message, modelId, imagePaths) {
|
|
|
117
113
|
await runOnce(messages, instructions, modelId, undefined, undefined, tracker);
|
|
118
114
|
await shutdownMcp();
|
|
119
115
|
}
|
|
120
|
-
/** Interactive multi-turn chat session */
|
|
121
|
-
export async function runChat(modelId, resumeSessionId) {
|
|
122
|
-
printHeader(modelId);
|
|
123
|
-
printDivider();
|
|
124
|
-
printInitLoading();
|
|
125
|
-
await initMcp();
|
|
126
|
-
discoverSkills();
|
|
127
|
-
const instructions = await loadInstructions();
|
|
128
|
-
printInitReady();
|
|
129
|
-
let messages = [];
|
|
130
|
-
let sessionId = resumeSessionId;
|
|
131
|
-
const tracker = new TokenTracker();
|
|
132
|
-
// Resume existing session
|
|
133
|
-
if (resumeSessionId) {
|
|
134
|
-
const { loadSession } = await import("./sessions.js");
|
|
135
|
-
const session = loadSession(resumeSessionId);
|
|
136
|
-
if (session) {
|
|
137
|
-
messages = session.messages;
|
|
138
|
-
sessionId = resumeSessionId;
|
|
139
|
-
console.log(`\x1b[90m Resumed session: ${session.meta.title} (${messages.length} messages)\x1b[0m`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const rl = readline.createInterface({
|
|
143
|
-
input: process.stdin,
|
|
144
|
-
output: process.stdout,
|
|
145
|
-
prompt: "\x1b[36m> \x1b[0m",
|
|
146
|
-
});
|
|
147
|
-
setConfirmReadline(rl);
|
|
148
|
-
// Handle Ctrl+C: abort current generation, don't exit
|
|
149
|
-
let abortController = null;
|
|
150
|
-
process.on("SIGINT", () => {
|
|
151
|
-
if (abortController) {
|
|
152
|
-
killActiveProcesses();
|
|
153
|
-
abortController.abort();
|
|
154
|
-
abortController = null;
|
|
155
|
-
console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
|
|
156
|
-
rl.prompt();
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// No active generation, exit
|
|
160
|
-
console.log();
|
|
161
|
-
rl.close();
|
|
162
|
-
}
|
|
163
|
-
});
|
|
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");
|
|
177
|
-
rl.prompt();
|
|
178
|
-
// Multi-line paste detection: collect rapid successive lines
|
|
179
|
-
let pasteBuffer = [];
|
|
180
|
-
let pasteTimer = null;
|
|
181
|
-
const PASTE_DEBOUNCE_MS = 50;
|
|
182
|
-
const processInput = async (text) => {
|
|
183
|
-
const input = text.trim();
|
|
184
|
-
if (!input) {
|
|
185
|
-
rl.prompt();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (input.startsWith("/")) {
|
|
189
|
-
const handled = await handleSlashCommand(input, messages, instructions, modelId, rl, tracker);
|
|
190
|
-
if (handled === "exit") {
|
|
191
|
-
rl.close();
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (handled === "paste") {
|
|
195
|
-
console.log();
|
|
196
|
-
abortController = new AbortController();
|
|
197
|
-
await runOnce(messages, instructions, modelId, abortController.signal, undefined, tracker);
|
|
198
|
-
abortController = null;
|
|
199
|
-
console.log();
|
|
200
|
-
}
|
|
201
|
-
rl.prompt();
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
// Show paste feedback for large inputs
|
|
205
|
-
const { processPastedInput, printPasteFeedback } = await import("./paste-handler.js");
|
|
206
|
-
const pasteResult = processPastedInput(input);
|
|
207
|
-
printPasteFeedback(pasteResult);
|
|
208
|
-
console.log();
|
|
209
|
-
messages.push({ role: "user", content: pasteResult.fullText });
|
|
210
|
-
abortController = new AbortController();
|
|
211
|
-
await runOnce(messages, instructions, modelId, abortController.signal, undefined, tracker);
|
|
212
|
-
abortController = null;
|
|
213
|
-
console.log();
|
|
214
|
-
rl.prompt();
|
|
215
|
-
};
|
|
216
|
-
rl.on("line", (line) => {
|
|
217
|
-
pasteBuffer.push(line);
|
|
218
|
-
if (pasteTimer)
|
|
219
|
-
clearTimeout(pasteTimer);
|
|
220
|
-
pasteTimer = setTimeout(() => {
|
|
221
|
-
const combined = pasteBuffer.join("\n");
|
|
222
|
-
pasteBuffer = [];
|
|
223
|
-
pasteTimer = null;
|
|
224
|
-
processInput(combined);
|
|
225
|
-
}, PASTE_DEBOUNCE_MS);
|
|
226
|
-
});
|
|
227
|
-
// Wait for close
|
|
228
|
-
await new Promise((resolve) => rl.on("close", resolve));
|
|
229
|
-
// Auto-save session on exit
|
|
230
|
-
if (messages.length > 0) {
|
|
231
|
-
const { saveSession } = await import("./sessions.js");
|
|
232
|
-
sessionId = saveSession(messages, sessionId);
|
|
233
|
-
console.log(`\x1b[90m Session saved: ${sessionId}\x1b[0m`);
|
|
234
|
-
}
|
|
235
|
-
printDivider();
|
|
236
|
-
console.log("\x1b[90mBye!\x1b[0m");
|
|
237
|
-
await shutdownMcp();
|
|
238
|
-
}
|
|
239
|
-
/** AI Coding mode: project-aware interactive session */
|
|
240
|
-
export async function runCode(modelId, resumeSessionId) {
|
|
241
|
-
printHeader(modelId);
|
|
242
|
-
console.log("\x1b[90m Mode: code\x1b[0m");
|
|
243
|
-
printDivider();
|
|
244
|
-
// Scan project
|
|
245
|
-
console.log("\x1b[90m⟳ Scanning project...\x1b[0m");
|
|
246
|
-
const project = scanProject();
|
|
247
|
-
console.log(`\x1b[90m ${project.languages.join(", ") || "Unknown language"}${project.framework ? ` (${project.framework})` : ""} | ${project.isGitRepo ? `git:${project.branch}` : "no git"}\x1b[0m`);
|
|
248
|
-
await initMcp();
|
|
249
|
-
discoverSkills();
|
|
250
|
-
const instructions = await loadInstructions();
|
|
251
|
-
const codePrompt = buildCodeSystemPrompt(project, instructions);
|
|
252
|
-
console.log("\x1b[90m✓ Ready\x1b[0m");
|
|
253
|
-
let messages = [];
|
|
254
|
-
let sessionId = resumeSessionId;
|
|
255
|
-
const tracker = new TokenTracker();
|
|
256
|
-
if (resumeSessionId) {
|
|
257
|
-
const { loadSession } = await import("./sessions.js");
|
|
258
|
-
const session = loadSession(resumeSessionId);
|
|
259
|
-
if (session) {
|
|
260
|
-
messages = session.messages;
|
|
261
|
-
sessionId = resumeSessionId;
|
|
262
|
-
console.log(`\x1b[90m Resumed: ${session.meta.title} (${messages.length} msgs)\x1b[0m`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const rl = readline.createInterface({
|
|
266
|
-
input: process.stdin,
|
|
267
|
-
output: process.stdout,
|
|
268
|
-
prompt: "\x1b[32m❯ \x1b[0m",
|
|
269
|
-
});
|
|
270
|
-
setConfirmReadline(rl);
|
|
271
|
-
let abortController = null;
|
|
272
|
-
process.on("SIGINT", () => {
|
|
273
|
-
if (abortController) {
|
|
274
|
-
killActiveProcesses();
|
|
275
|
-
abortController.abort();
|
|
276
|
-
abortController = null;
|
|
277
|
-
console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
|
|
278
|
-
rl.prompt();
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
console.log();
|
|
282
|
-
rl.close();
|
|
283
|
-
}
|
|
284
|
-
});
|
|
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");
|
|
298
|
-
rl.prompt();
|
|
299
|
-
let pasteBuffer = [];
|
|
300
|
-
let pasteTimer = null;
|
|
301
|
-
const PASTE_DEBOUNCE_MS = 50;
|
|
302
|
-
const processCodeInput = async (text) => {
|
|
303
|
-
const input = text.trim();
|
|
304
|
-
if (!input) {
|
|
305
|
-
rl.prompt();
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
if (input.startsWith("/")) {
|
|
309
|
-
const handled = await handleSlashCommand(input, messages, [codePrompt], modelId, rl, tracker);
|
|
310
|
-
if (handled === "exit") {
|
|
311
|
-
rl.close();
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (handled === "paste") {
|
|
315
|
-
console.log();
|
|
316
|
-
abortController = new AbortController();
|
|
317
|
-
await runOnceWithSystem(messages, codePrompt, modelId, abortController.signal, undefined, tracker);
|
|
318
|
-
abortController = null;
|
|
319
|
-
console.log();
|
|
320
|
-
}
|
|
321
|
-
rl.prompt();
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const { processPastedInput, printPasteFeedback } = await import("./paste-handler.js");
|
|
325
|
-
const pasteResult = processPastedInput(input);
|
|
326
|
-
printPasteFeedback(pasteResult);
|
|
327
|
-
console.log();
|
|
328
|
-
messages.push({ role: "user", content: pasteResult.fullText });
|
|
329
|
-
abortController = new AbortController();
|
|
330
|
-
await runOnceWithSystem(messages, codePrompt, modelId, abortController.signal, undefined, tracker);
|
|
331
|
-
abortController = null;
|
|
332
|
-
console.log();
|
|
333
|
-
rl.prompt();
|
|
334
|
-
};
|
|
335
|
-
rl.on("line", (line) => {
|
|
336
|
-
pasteBuffer.push(line);
|
|
337
|
-
if (pasteTimer)
|
|
338
|
-
clearTimeout(pasteTimer);
|
|
339
|
-
pasteTimer = setTimeout(() => {
|
|
340
|
-
const combined = pasteBuffer.join("\n");
|
|
341
|
-
pasteBuffer = [];
|
|
342
|
-
pasteTimer = null;
|
|
343
|
-
processCodeInput(combined);
|
|
344
|
-
}, PASTE_DEBOUNCE_MS);
|
|
345
|
-
});
|
|
346
|
-
await new Promise((resolve) => rl.on("close", resolve));
|
|
347
|
-
if (messages.length > 0) {
|
|
348
|
-
const { saveSession } = await import("./sessions.js");
|
|
349
|
-
sessionId = saveSession(messages, sessionId);
|
|
350
|
-
console.log(`\x1b[90m Session saved: ${sessionId}\x1b[0m`);
|
|
351
|
-
}
|
|
352
|
-
rl.close();
|
|
353
|
-
printDivider();
|
|
354
|
-
console.log("\x1b[90mBye!\x1b[0m");
|
|
355
|
-
await shutdownMcp();
|
|
356
|
-
}
|
|
357
116
|
/** runOnce variant that accepts a pre-built system prompt (for code mode) */
|
|
358
117
|
export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSignal, callbacks, tracker) {
|
|
359
118
|
const model = resolveModel(modelId);
|
|
@@ -365,7 +124,8 @@ export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSi
|
|
|
365
124
|
messages.length = 0;
|
|
366
125
|
messages.push(...result.messages);
|
|
367
126
|
if (result.shouldContinue) {
|
|
368
|
-
|
|
127
|
+
const continueText = result.replayText || "Continue with your task.";
|
|
128
|
+
messages.push({ role: "user", content: continueText });
|
|
369
129
|
}
|
|
370
130
|
if (tracker)
|
|
371
131
|
tracker.resetContext();
|
|
@@ -556,180 +316,6 @@ export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSi
|
|
|
556
316
|
}
|
|
557
317
|
}
|
|
558
318
|
}
|
|
559
|
-
async function handleSlashCommand(input, messages, instructions, modelId, rl, tracker) {
|
|
560
|
-
const [cmd, ...rest] = input.slice(1).split(/\s+/);
|
|
561
|
-
const arg = rest.join(" ");
|
|
562
|
-
switch (cmd) {
|
|
563
|
-
case "exit":
|
|
564
|
-
case "quit":
|
|
565
|
-
case "q":
|
|
566
|
-
console.log("\x1b[90m⟳ Exiting...\x1b[0m");
|
|
567
|
-
return "exit";
|
|
568
|
-
case "clear":
|
|
569
|
-
messages.length = 0;
|
|
570
|
-
console.log("\x1b[90m ✓ Conversation cleared\x1b[0m");
|
|
571
|
-
return "handled";
|
|
572
|
-
case "compact":
|
|
573
|
-
if (messages.length < 4) {
|
|
574
|
-
console.log("\x1b[90m Not enough messages to compact\x1b[0m");
|
|
575
|
-
}
|
|
576
|
-
else {
|
|
577
|
-
console.log("\x1b[90m ⟳ Compacting...\x1b[0m");
|
|
578
|
-
const model = resolveModel(modelId);
|
|
579
|
-
const result = await compactMessages(messages, model, { keepRecentTurns: 2, autoContinue: false });
|
|
580
|
-
messages.length = 0;
|
|
581
|
-
messages.push(...result.messages);
|
|
582
|
-
if (tracker)
|
|
583
|
-
tracker.resetContext();
|
|
584
|
-
console.log(`\x1b[90m ✓ Compacted (${estimateTokens(messages)} tokens estimated)\x1b[0m`);
|
|
585
|
-
}
|
|
586
|
-
return "handled";
|
|
587
|
-
case "model":
|
|
588
|
-
if (arg) {
|
|
589
|
-
const config = await import("./config.js");
|
|
590
|
-
const cfg = config.loadConfig();
|
|
591
|
-
if (cfg.provider) {
|
|
592
|
-
cfg.provider.defaultModel = arg;
|
|
593
|
-
config.saveConfig(cfg);
|
|
594
|
-
console.log(`\x1b[90m ✓ Default model set to: ${arg}\x1b[0m`);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
const config = await import("./config.js");
|
|
599
|
-
const cfg = config.loadConfig();
|
|
600
|
-
console.log(`\x1b[90m Current model: ${cfg.provider?.defaultModel ?? "not set"}\x1b[0m`);
|
|
601
|
-
}
|
|
602
|
-
return "handled";
|
|
603
|
-
case "models": {
|
|
604
|
-
const config = await import("./config.js");
|
|
605
|
-
const cfg = config.loadConfig();
|
|
606
|
-
if (!cfg.provider?.baseURL || !cfg.provider?.apiKey) {
|
|
607
|
-
console.log("\x1b[90m Not configured. Run: min-agent setup\x1b[0m");
|
|
608
|
-
return "handled";
|
|
609
|
-
}
|
|
610
|
-
console.log("\x1b[90m Fetching models...\x1b[0m");
|
|
611
|
-
const models = await config.fetchModels(cfg.provider.baseURL, cfg.provider.apiKey);
|
|
612
|
-
if (models.length === 0) {
|
|
613
|
-
console.log("\x1b[90m No models found or unable to fetch model list\x1b[0m");
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
console.log(`\x1b[90m Available models (${models.length}):\x1b[0m`);
|
|
617
|
-
for (const m of models) {
|
|
618
|
-
const marker = m === cfg.provider.defaultModel ? " ← default" : "";
|
|
619
|
-
console.log(`\x1b[90m - ${m}${marker}\x1b[0m`);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
return "handled";
|
|
623
|
-
}
|
|
624
|
-
case "memory":
|
|
625
|
-
if (arg) {
|
|
626
|
-
const { addMemory } = await import("./memory.js");
|
|
627
|
-
addMemory(arg);
|
|
628
|
-
console.log(`\x1b[90m ✓ Memory saved: "${arg}"\x1b[0m`);
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
const { loadMemories } = await import("./memory.js");
|
|
632
|
-
const memories = loadMemories();
|
|
633
|
-
if (memories.length === 0) {
|
|
634
|
-
console.log("\x1b[90m No memories stored\x1b[0m");
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
for (let i = 0; i < memories.length; i++) {
|
|
638
|
-
const m = memories[i];
|
|
639
|
-
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
640
|
-
console.log(`\x1b[90m #${i + 1}: ${m.content}${tags}\x1b[0m`);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
return "handled";
|
|
645
|
-
case "skills": {
|
|
646
|
-
discoverSkills();
|
|
647
|
-
const skills = getSkills();
|
|
648
|
-
if (skills.length === 0) {
|
|
649
|
-
console.log("\x1b[90m No skills available\x1b[0m");
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
for (const skill of skills) {
|
|
653
|
-
console.log(`\x1b[90m ${skill.name} enabled\x1b[0m`);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
return "handled";
|
|
657
|
-
}
|
|
658
|
-
case "mcp": {
|
|
659
|
-
const config = loadMcpConfig();
|
|
660
|
-
const servers = Object.entries(config.mcpServers);
|
|
661
|
-
if (servers.length === 0) {
|
|
662
|
-
console.log("\x1b[90m No MCP servers configured\x1b[0m");
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
const status = getMcpStatus();
|
|
666
|
-
console.log(`\x1b[90m MCP servers (${servers.length}):\x1b[0m`);
|
|
667
|
-
for (const [name, cfg] of servers) {
|
|
668
|
-
const disabled = cfg.enabled === false;
|
|
669
|
-
const connected = status[name]?.connected ?? false;
|
|
670
|
-
const toolCount = status[name]?.tools.length ?? 0;
|
|
671
|
-
const state = disabled ? "disabled" : connected ? "connected" : "disconnected";
|
|
672
|
-
const toolsText = toolCount > 0 ? `, ${toolCount} tools` : "";
|
|
673
|
-
console.log(`\x1b[90m - ${name}: ${state}${toolsText}\x1b[0m`);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
return "handled";
|
|
677
|
-
}
|
|
678
|
-
case "tokens": {
|
|
679
|
-
if (tracker && tracker.lastInputTokens > 0) {
|
|
680
|
-
const { getContextWindow } = await import("./context-window.js");
|
|
681
|
-
const ctxWindow = await getContextWindow(modelId);
|
|
682
|
-
const pct = Math.round((tracker.lastInputTokens / ctxWindow) * 100);
|
|
683
|
-
console.log(`\x1b[90m Context: ${tracker.lastInputTokens} / ${ctxWindow} tokens (${pct}%)\x1b[0m`);
|
|
684
|
-
console.log(`\x1b[90m Total: ${tracker.totalInputTokens} in / ${tracker.totalOutputTokens} out\x1b[0m`);
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
console.log(`\x1b[90m Estimated tokens in context: ${estimateTokens(messages)}\x1b[0m`);
|
|
688
|
-
}
|
|
689
|
-
console.log(`\x1b[90m Messages: ${messages.length}\x1b[0m`);
|
|
690
|
-
return "handled";
|
|
691
|
-
}
|
|
692
|
-
case "path":
|
|
693
|
-
case "pwd":
|
|
694
|
-
console.log(`\x1b[90m ${process.cwd()}\x1b[0m`);
|
|
695
|
-
return "handled";
|
|
696
|
-
case "paste": {
|
|
697
|
-
const { getClipboardImage } = await import("./clipboard.js");
|
|
698
|
-
const img = getClipboardImage();
|
|
699
|
-
if (!img) {
|
|
700
|
-
console.log("\x1b[90m No image found in clipboard\x1b[0m");
|
|
701
|
-
return "handled";
|
|
702
|
-
}
|
|
703
|
-
console.log(`\x1b[90m 📎 Clipboard image (${(img.data.length / 1024).toFixed(1)} KB)\x1b[0m`);
|
|
704
|
-
const text = arg || "What's in this image?";
|
|
705
|
-
const content = [
|
|
706
|
-
{ type: "text", text },
|
|
707
|
-
{ type: "image", image: img.data, mimeType: img.mimeType },
|
|
708
|
-
];
|
|
709
|
-
messages.push({ role: "user", content });
|
|
710
|
-
// Return a special signal to trigger runOnce
|
|
711
|
-
return "paste";
|
|
712
|
-
}
|
|
713
|
-
case "help":
|
|
714
|
-
console.log(`\x1b[90m Slash commands:
|
|
715
|
-
/clear Clear conversation history
|
|
716
|
-
/compact Force context compaction
|
|
717
|
-
/model [name] Show or change current model
|
|
718
|
-
/models List available models from provider
|
|
719
|
-
/memory [text] List memories or save a new one
|
|
720
|
-
/skills List discovered skills
|
|
721
|
-
/mcp List MCP servers and connection status
|
|
722
|
-
/tokens Show estimated token usage
|
|
723
|
-
/path Show current working directory
|
|
724
|
-
/paste [text] Paste clipboard image + optional prompt
|
|
725
|
-
/help Show this help
|
|
726
|
-
/exit Exit the chat\x1b[0m`);
|
|
727
|
-
return "handled";
|
|
728
|
-
default:
|
|
729
|
-
console.log(`\x1b[90m Unknown command: /${cmd}. Type /help for available commands.\x1b[0m`);
|
|
730
|
-
return "handled";
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
319
|
export async function runOnce(messages, instructions, modelId, abortSignal, callbacks, tracker) {
|
|
734
320
|
const model = resolveModel(modelId);
|
|
735
321
|
const api = !!callbacks;
|
|
@@ -747,10 +333,9 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
|
|
|
747
333
|
messages.push(...result.messages);
|
|
748
334
|
// Auto-continue: inject a message so the agent keeps working
|
|
749
335
|
if (result.shouldContinue) {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
});
|
|
336
|
+
const continueText = result.replayText ||
|
|
337
|
+
"Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.";
|
|
338
|
+
messages.push({ role: "user", content: continueText });
|
|
754
339
|
}
|
|
755
340
|
if (tracker)
|
|
756
341
|
tracker.resetContext();
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { runAgent
|
|
1
|
+
import { runAgent } from "./agent.js";
|
|
2
2
|
import { loadMcpConfig, saveMcpConfig, checkMcpServer, checkAllMcpServers, formatMcpServerBinding, } from "./mcp.js";
|
|
3
3
|
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"
|
|
@@ -47,6 +47,10 @@ Options:
|
|
|
47
47
|
--image, -i <path> Attach an image (can be used multiple times)
|
|
48
48
|
--yes, -y Auto-approve all confirmations (dangerous commands, file overwrites)
|
|
49
49
|
|
|
50
|
+
Permission modes (set in ~/.min-agent/config.json):
|
|
51
|
+
"permission": "ask" Prompt before dangerous operations (default)
|
|
52
|
+
"permission": "allow-all" Auto-approve everything (same as --yes permanently)
|
|
53
|
+
|
|
50
54
|
Rules (loaded as system instructions):
|
|
51
55
|
Global: ~/.min-agent/rules.md
|
|
52
56
|
Project: ./AGENTS.md or ./RULES.md or ./.min-agent/AGENTS.md
|
|
@@ -108,6 +112,12 @@ async function main() {
|
|
|
108
112
|
printUsage();
|
|
109
113
|
process.exit(0);
|
|
110
114
|
}
|
|
115
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
116
|
+
const pkgPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../package.json");
|
|
117
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
118
|
+
console.log(pkg.version);
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
111
121
|
const command = args[0];
|
|
112
122
|
switch (command) {
|
|
113
123
|
case "setup": {
|
|
@@ -160,7 +170,8 @@ async function main() {
|
|
|
160
170
|
}
|
|
161
171
|
const message = chatArgs.join(" ");
|
|
162
172
|
if (!message) {
|
|
163
|
-
await
|
|
173
|
+
const { runTui } = await import("./tui-chat.js");
|
|
174
|
+
await runTui({ modelId: modelOverride, resumeSessionId: resumeId, mode: "chat" });
|
|
164
175
|
}
|
|
165
176
|
else {
|
|
166
177
|
await runAgent(message, modelOverride, images.length > 0 ? images : undefined);
|
|
@@ -182,8 +193,8 @@ async function main() {
|
|
|
182
193
|
resumeId = args[++i];
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
|
-
const {
|
|
186
|
-
await
|
|
196
|
+
const { runTui } = await import("./tui-chat.js");
|
|
197
|
+
await runTui({ modelId: modelOverride, resumeSessionId: resumeId, mode: "code" });
|
|
187
198
|
break;
|
|
188
199
|
}
|
|
189
200
|
case "serve": {
|