min-agent 0.2.0 → 0.3.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/README.md +146 -18
- package/dist/agent.js +293 -408
- package/dist/assistant-stream.js +11 -7
- package/dist/cli.js +403 -140
- package/dist/clipboard.js +59 -23
- package/dist/code-mode.js +3 -3
- package/dist/compaction.js +182 -81
- package/dist/config.js +186 -35
- package/dist/confirm.js +55 -6
- package/dist/context-window.js +67 -54
- package/dist/doom-loop.js +19 -12
- package/dist/http.js +119 -0
- package/dist/instructions.js +51 -33
- package/dist/logger.js +66 -0
- package/dist/markdown.js +3 -44
- package/dist/mcp.js +547 -100
- package/dist/memory.js +48 -6
- package/dist/output.js +36 -27
- package/dist/paste-handler.js +3 -3
- package/dist/plugins.js +33 -6
- package/dist/pricing.js +119 -0
- package/dist/provider.js +17 -15
- package/dist/serve.js +658 -369
- package/dist/sessions.js +151 -13
- package/dist/skills.js +466 -76
- package/dist/synthetic.js +7 -0
- package/dist/title-gen.js +2 -1
- package/dist/tool-display.js +173 -0
- package/dist/tool-output.js +54 -45
- package/dist/tools/apply_patch.js +191 -0
- package/dist/tools/backend.js +61 -0
- package/dist/tools/bash.js +147 -70
- package/dist/tools/code_search.js +6 -5
- package/dist/tools/edit.js +23 -7
- package/dist/tools/explore.js +80 -12
- package/dist/tools/glob.js +3 -3
- package/dist/tools/grep.js +146 -14
- package/dist/tools/index.js +7 -7
- package/dist/tools/question.js +4 -22
- package/dist/tools/read.js +71 -11
- package/dist/tools/task.js +33 -20
- package/dist/tools/todo.js +83 -73
- package/dist/tools/web_fetch.js +150 -46
- package/dist/tools/web_search.js +706 -28
- package/dist/tools/write.js +13 -7
- package/dist/tui/App.js +40 -6
- package/dist/tui/ConfirmBar.js +24 -3
- package/dist/tui/InputBar.js +390 -45
- package/dist/tui/MessageList.js +533 -20
- package/dist/tui/ModelPicker.js +108 -0
- package/dist/tui/QuestionBar.js +104 -0
- package/dist/tui/StatusBar.js +19 -11
- package/dist/tui/agent-runner.js +103 -0
- package/dist/tui/caret-pos.js +134 -0
- package/dist/tui/caret.js +69 -0
- package/dist/tui/diff-view.js +61 -0
- package/dist/tui/drag-state.js +44 -0
- package/dist/tui/index.js +153 -24
- package/dist/tui/input-history.js +44 -0
- package/dist/tui/layout.js +17 -0
- package/dist/tui/mouse.js +46 -0
- package/dist/tui/selection.js +134 -0
- package/dist/tui/slash-commands.js +90 -0
- package/dist/tui/slash-handler.js +370 -0
- package/dist/tui/text-width.js +91 -0
- package/dist/tui/theme.js +12 -0
- package/dist/tui/undo-stack.js +14 -0
- package/dist/tui/use-sgr-mouse.js +27 -0
- package/dist/tui-chat.js +111 -331
- package/dist/updater.js +57 -0
- package/docs/API.md +160 -14
- package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +1510 -0
- package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +2105 -0
- package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +1595 -0
- package/docs/superpowers/plans/2026-08-16-input-caret.md +782 -0
- package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +183 -0
- package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +220 -0
- package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +196 -0
- package/docs/superpowers/specs/2026-08-16-input-caret-design.md +63 -0
- package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +116 -0
- package/package.json +7 -8
package/dist/tui-chat.js
CHANGED
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
import { TuiRenderer } from "./tui/index.js";
|
|
2
|
-
import { initMcp, shutdownMcp
|
|
3
|
-
import { discoverSkills,
|
|
2
|
+
import { initMcp, shutdownMcp } from "./mcp.js";
|
|
3
|
+
import { discoverSkills, getAllSkills, getStaleDisabledSkills } from "./skills.js";
|
|
4
|
+
import { setSlashArgProvider } from "./tui/slash-commands.js";
|
|
4
5
|
import { loadInstructions } from "./instructions.js";
|
|
5
|
-
import { TokenTracker
|
|
6
|
-
import {
|
|
6
|
+
import { TokenTracker } from "./compaction.js";
|
|
7
|
+
import { loadImageParts } from "./agent.js";
|
|
8
|
+
import { createUndoStack } from "./tui/undo-stack.js";
|
|
7
9
|
import { killActiveProcesses } from "./tools/bash.js";
|
|
8
|
-
import { setTuiConfirm } from "./confirm.js";
|
|
9
|
-
import {
|
|
10
|
-
import { loadConfig } from "./config.js";
|
|
11
|
-
import { resolveModel } from "./provider.js";
|
|
10
|
+
import { setTuiConfirm, setTuiQuestion } from "./confirm.js";
|
|
11
|
+
import { loadConfig, getActiveProvider } from "./config.js";
|
|
12
12
|
import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
|
|
13
|
+
import { handleSlashCommand, sysMsg } from "./tui/slash-handler.js";
|
|
14
|
+
import { createAgentRunner } from "./tui/agent-runner.js";
|
|
13
15
|
let confirmResolver = null;
|
|
16
|
+
let questionResolver = null;
|
|
17
|
+
function registerSkillCompletion() {
|
|
18
|
+
setSlashArgProvider("skills", (tokens) => {
|
|
19
|
+
const action = tokens[0];
|
|
20
|
+
if (action === undefined) {
|
|
21
|
+
return [
|
|
22
|
+
{ value: "list", description: "列出全部 skills" },
|
|
23
|
+
{ value: "info", description: "查看 skill 详情" },
|
|
24
|
+
{ value: "enable", description: "启用 skill" },
|
|
25
|
+
{ value: "disable", description: "禁用 skill" },
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
if (action === "info")
|
|
29
|
+
return getAllSkills().map((s) => ({ value: s.name, description: s.description }));
|
|
30
|
+
if (action !== "enable" && action !== "disable")
|
|
31
|
+
return [];
|
|
32
|
+
const all = getAllSkills();
|
|
33
|
+
const pool = action === "enable"
|
|
34
|
+
? [...all.filter((s) => !s.enabled).map((s) => ({ value: s.name, description: s.description })), ...getStaleDisabledSkills().map((name) => ({ value: name, description: "(配置中的孤儿条目)" }))]
|
|
35
|
+
: all.filter((s) => s.enabled).map((s) => ({ value: s.name, description: s.description }));
|
|
36
|
+
return [...pool.filter((o) => !tokens.includes(o.value)), ...(tokens.includes("--project") ? [] : [{ value: "--project", description: "仅对当前项目生效" }])];
|
|
37
|
+
});
|
|
38
|
+
}
|
|
14
39
|
export async function runTui(opts) {
|
|
15
|
-
const { modelId, resumeSessionId, mode } = opts;
|
|
40
|
+
const { modelId, resumeSessionId, mode, images, providerName } = opts;
|
|
16
41
|
await initMcp();
|
|
17
42
|
discoverSkills();
|
|
43
|
+
registerSkillCompletion();
|
|
18
44
|
const instructions = await loadInstructions();
|
|
19
45
|
const config = loadConfig();
|
|
20
|
-
// Code mode: scan project and build code-specific system prompt
|
|
21
46
|
let codeSystemPrompt;
|
|
22
47
|
if (mode === "code") {
|
|
23
48
|
const project = scanProject();
|
|
@@ -25,9 +50,12 @@ export async function runTui(opts) {
|
|
|
25
50
|
}
|
|
26
51
|
let messages = [];
|
|
27
52
|
let sessionId = resumeSessionId;
|
|
53
|
+
let currentModel = modelId ?? getActiveProvider(config)?.defaultModel;
|
|
54
|
+
const currentProvider = providerName;
|
|
28
55
|
const tracker = new TokenTracker();
|
|
29
|
-
let
|
|
30
|
-
|
|
56
|
+
let undoStack = createUndoStack();
|
|
57
|
+
let lastUserMessage = null;
|
|
58
|
+
let planMode = false;
|
|
31
59
|
if (resumeSessionId) {
|
|
32
60
|
const { loadSession } = await import("./sessions.js");
|
|
33
61
|
const session = loadSession(resumeSessionId);
|
|
@@ -36,26 +64,20 @@ export async function runTui(opts) {
|
|
|
36
64
|
sessionId = resumeSessionId;
|
|
37
65
|
}
|
|
38
66
|
}
|
|
39
|
-
|
|
40
|
-
let executeAgent;
|
|
67
|
+
let runner = null;
|
|
41
68
|
const tui = new TuiRenderer({
|
|
42
69
|
onSubmit: async (text) => {
|
|
43
70
|
if (text.startsWith("/")) {
|
|
44
|
-
const result = await handleSlashCommand(text, tui, messages,
|
|
45
|
-
if (result === "run")
|
|
46
|
-
await
|
|
47
|
-
}
|
|
71
|
+
const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => { currentModel = model; tui.setModel(model); }, requestExit, { undoStack, setUndoStack: (s) => { undoStack = s; }, lastUserMessage, setLastUserMessage: (t) => { lastUserMessage = t; }, sessionId, setSessionId: (id) => { sessionId = id; }, planMode, togglePlanMode: () => { planMode = !planMode; } });
|
|
72
|
+
if (result === "run" && runner)
|
|
73
|
+
await runner.run();
|
|
48
74
|
return;
|
|
49
75
|
}
|
|
50
|
-
|
|
51
|
-
tui.addMessage({
|
|
52
|
-
id: `user-${Date.now()}`,
|
|
53
|
-
role: "user",
|
|
54
|
-
content: text,
|
|
55
|
-
timestamp: Date.now(),
|
|
56
|
-
});
|
|
76
|
+
lastUserMessage = text;
|
|
77
|
+
tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
|
|
57
78
|
messages.push({ role: "user", content: text });
|
|
58
|
-
|
|
79
|
+
if (runner)
|
|
80
|
+
await runner.run();
|
|
59
81
|
},
|
|
60
82
|
onConfirm: (accepted) => {
|
|
61
83
|
tui.hideConfirm();
|
|
@@ -64,321 +86,79 @@ export async function runTui(opts) {
|
|
|
64
86
|
confirmResolver = null;
|
|
65
87
|
}
|
|
66
88
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tui.setRunning(false);
|
|
73
|
-
tui.addMessage({
|
|
74
|
-
id: `sys-${Date.now()}`,
|
|
75
|
-
role: "system",
|
|
76
|
-
content: "(已取消)",
|
|
77
|
-
timestamp: Date.now(),
|
|
78
|
-
});
|
|
89
|
+
onQuestionAnswer: (answer) => {
|
|
90
|
+
tui.hideQuestion();
|
|
91
|
+
if (questionResolver) {
|
|
92
|
+
questionResolver(answer);
|
|
93
|
+
questionResolver = null;
|
|
79
94
|
}
|
|
80
95
|
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
content: `错误: ${msg}`,
|
|
118
|
-
timestamp: Date.now(),
|
|
119
|
-
});
|
|
120
|
-
},
|
|
121
|
-
onRunFinish: async (info) => {
|
|
122
|
-
abortController = null;
|
|
123
|
-
tui.setRunning(false);
|
|
124
|
-
if (info.usage) {
|
|
125
|
-
const input = info.usage.inputTokens ?? 0;
|
|
126
|
-
const output = info.usage.outputTokens ?? 0;
|
|
127
|
-
const total = input + output;
|
|
128
|
-
const ctxWindow = await getContextWindow(modelId);
|
|
129
|
-
const pct = ctxWindow > 0 ? Math.round((input / ctxWindow) * 100) : 0;
|
|
130
|
-
tui.setTokenInfo(input, output, ctxWindow);
|
|
131
|
-
tui.addMessage({
|
|
132
|
-
id: `done-${Date.now()}`,
|
|
133
|
-
role: "system",
|
|
134
|
-
content: `Done in ${info.stepCount} step(s) | Tokens: ${input} in / ${output} out / ${total} total | Context: ${pct}%`,
|
|
135
|
-
timestamp: Date.now(),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
if (mode === "code" && codeSystemPrompt) {
|
|
141
|
-
await runOnceWithSystem(messages, codeSystemPrompt, modelId, abortController?.signal, callbacks, tracker);
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
await runOnce(messages, instructions, modelId, abortController?.signal, callbacks, tracker);
|
|
96
|
+
onModelPick: (model) => {
|
|
97
|
+
tui.hideModelPicker();
|
|
98
|
+
const cfg = loadConfig();
|
|
99
|
+
const provider = getActiveProvider(cfg);
|
|
100
|
+
if (provider) {
|
|
101
|
+
provider.defaultModel = model;
|
|
102
|
+
import("./config.js").then((m) => m.saveConfig(cfg));
|
|
103
|
+
}
|
|
104
|
+
currentModel = model;
|
|
105
|
+
tui.setModel(model);
|
|
106
|
+
sysMsg(tui, `✓ 默认模型已设为: ${model}`);
|
|
107
|
+
},
|
|
108
|
+
onModelCancel: () => { tui.hideModelPicker(); },
|
|
109
|
+
onExit: () => { requestExit(); },
|
|
110
|
+
}, modelId ?? getActiveProvider(config)?.defaultModel);
|
|
111
|
+
runner = createAgentRunner({ tui, messages, tracker, getModel: () => currentModel, getProvider: () => currentProvider, getPlanMode: () => planMode, getUndoStack: () => undoStack, setUndoStack: (s) => { undoStack = s; }, mode, codeSystemPrompt });
|
|
112
|
+
setTuiConfirm((message) => new Promise((resolve) => { confirmResolver = resolve; tui.showConfirm(message); }));
|
|
113
|
+
setTuiQuestion((prompt, options) => new Promise((resolve) => { questionResolver = resolve; tui.showQuestion(prompt, options); }));
|
|
114
|
+
let exitResolve;
|
|
115
|
+
const exitPromise = new Promise((resolve) => { exitResolve = resolve; });
|
|
116
|
+
const requestExit = () => {
|
|
117
|
+
if (runner?.getController()) {
|
|
118
|
+
killActiveProcesses();
|
|
119
|
+
if (confirmResolver) {
|
|
120
|
+
confirmResolver(false);
|
|
121
|
+
confirmResolver = null;
|
|
122
|
+
tui.hideConfirm();
|
|
123
|
+
}
|
|
124
|
+
if (questionResolver) {
|
|
125
|
+
questionResolver(null);
|
|
126
|
+
questionResolver = null;
|
|
127
|
+
tui.hideQuestion();
|
|
128
|
+
}
|
|
129
|
+
runner.getController().abort();
|
|
130
|
+
tui.setRunning(false);
|
|
131
|
+
tui.addMessage({ id: `sys-${Date.now()}`, role: "system", content: "(已取消)", timestamp: Date.now() });
|
|
145
132
|
}
|
|
146
|
-
|
|
147
|
-
|
|
133
|
+
else
|
|
134
|
+
exitResolve?.();
|
|
148
135
|
};
|
|
149
|
-
// Override the confirm module to use TUI overlay
|
|
150
|
-
setTuiConfirm((message) => {
|
|
151
|
-
return new Promise((resolve) => {
|
|
152
|
-
confirmResolver = resolve;
|
|
153
|
-
tui.showConfirm(message);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
136
|
tui.start();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
tui
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
137
|
+
if (images && images.length > 0) {
|
|
138
|
+
const notify = (kind, imgPath) => {
|
|
139
|
+
if (kind === "not_found")
|
|
140
|
+
sysMsg(tui, `⚠ 图片不存在: ${imgPath}`);
|
|
141
|
+
else if (kind === "too_large")
|
|
142
|
+
sysMsg(tui, `⚠ 图片超过 5 MB 限制: ${imgPath}`);
|
|
143
|
+
else
|
|
144
|
+
sysMsg(tui, `📎 ${imgPath}`);
|
|
145
|
+
};
|
|
146
|
+
const parts = [{ type: "text", text: "" }, ...loadImageParts(images, notify)];
|
|
147
|
+
if (parts.length > 1) {
|
|
148
|
+
messages.push({ role: "user", content: parts });
|
|
149
|
+
tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: `[📎 图片] ${images.join(", ")}`, timestamp: Date.now() });
|
|
150
|
+
await runner.run();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
process.on("SIGINT", requestExit);
|
|
154
|
+
await exitPromise;
|
|
155
|
+
process.off("SIGINT", requestExit);
|
|
172
156
|
if (messages.length > 0) {
|
|
173
157
|
const { saveSession } = await import("./sessions.js");
|
|
174
158
|
sessionId = saveSession(messages, sessionId);
|
|
175
159
|
}
|
|
176
160
|
setTuiConfirm(null);
|
|
161
|
+
setTuiQuestion(null);
|
|
177
162
|
tui.destroy();
|
|
178
163
|
await shutdownMcp();
|
|
179
164
|
}
|
|
180
|
-
async function handleSlashCommand(input, tui, messages, modelId, tracker) {
|
|
181
|
-
const [cmd, ...rest] = input.slice(1).split(/\s+/);
|
|
182
|
-
const arg = rest.join(" ");
|
|
183
|
-
switch (cmd) {
|
|
184
|
-
case "exit":
|
|
185
|
-
case "quit":
|
|
186
|
-
case "q":
|
|
187
|
-
process.emit("SIGINT", "SIGINT");
|
|
188
|
-
break;
|
|
189
|
-
case "clear":
|
|
190
|
-
messages.length = 0;
|
|
191
|
-
tui.update({ messages: [] });
|
|
192
|
-
sysMsg(tui, "✓ 对话已清除");
|
|
193
|
-
break;
|
|
194
|
-
case "compact": {
|
|
195
|
-
if (messages.length < 4) {
|
|
196
|
-
sysMsg(tui, "消息太少,无需压缩");
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
sysMsg(tui, "⟳ 正在压缩上下文...");
|
|
200
|
-
const model = resolveModel(modelId);
|
|
201
|
-
const result = await compactMessages(messages, model, { keepRecentTurns: 2, autoContinue: false });
|
|
202
|
-
messages.length = 0;
|
|
203
|
-
messages.push(...result.messages);
|
|
204
|
-
if (tracker)
|
|
205
|
-
tracker.resetContext();
|
|
206
|
-
sysMsg(tui, `✓ 已压缩 (约 ${estimateTokens(messages)} tokens)`);
|
|
207
|
-
}
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
case "model": {
|
|
211
|
-
if (arg) {
|
|
212
|
-
const config = await import("./config.js");
|
|
213
|
-
const cfg = config.loadConfig();
|
|
214
|
-
if (cfg.provider) {
|
|
215
|
-
cfg.provider.defaultModel = arg;
|
|
216
|
-
config.saveConfig(cfg);
|
|
217
|
-
sysMsg(tui, `✓ 默认模型已设为: ${arg}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
const config = loadConfig();
|
|
222
|
-
sysMsg(tui, `当前模型: ${config.provider?.defaultModel ?? "未设置"}`);
|
|
223
|
-
}
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
case "models": {
|
|
227
|
-
const { fetchModels } = await import("./config.js");
|
|
228
|
-
const cfg = loadConfig();
|
|
229
|
-
if (!cfg.provider?.baseURL || !cfg.provider?.apiKey) {
|
|
230
|
-
sysMsg(tui, "未配置 Provider,运行: min-agent setup");
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
sysMsg(tui, "正在获取模型列表...");
|
|
234
|
-
const models = await fetchModels(cfg.provider.baseURL, cfg.provider.apiKey);
|
|
235
|
-
if (models.length === 0) {
|
|
236
|
-
sysMsg(tui, "无法获取模型列表");
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
const lines = models.map((m) => {
|
|
240
|
-
const marker = m === cfg.provider?.defaultModel ? " ← 默认" : "";
|
|
241
|
-
return ` ${m}${marker}`;
|
|
242
|
-
});
|
|
243
|
-
sysMsg(tui, `可用模型 (${models.length}):\n${lines.join("\n")}`);
|
|
244
|
-
}
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
case "memory": {
|
|
248
|
-
if (arg) {
|
|
249
|
-
const { addMemory } = await import("./memory.js");
|
|
250
|
-
addMemory(arg);
|
|
251
|
-
sysMsg(tui, `✓ 已保存记忆: "${arg}"`);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
const { loadMemories } = await import("./memory.js");
|
|
255
|
-
const memories = loadMemories();
|
|
256
|
-
if (memories.length === 0) {
|
|
257
|
-
sysMsg(tui, "无已保存的记忆");
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
const lines = memories.map((m, i) => {
|
|
261
|
-
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
262
|
-
return ` #${i + 1}: ${m.content}${tags}`;
|
|
263
|
-
});
|
|
264
|
-
sysMsg(tui, `记忆 (${memories.length}):\n${lines.join("\n")}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
case "path":
|
|
270
|
-
case "pwd":
|
|
271
|
-
sysMsg(tui, process.cwd());
|
|
272
|
-
break;
|
|
273
|
-
case "paste": {
|
|
274
|
-
const { getClipboardImage } = await import("./clipboard.js");
|
|
275
|
-
const img = getClipboardImage();
|
|
276
|
-
if (!img) {
|
|
277
|
-
sysMsg(tui, "剪贴板中未找到图片");
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
sysMsg(tui, `📎 剪贴板图片 (${(img.data.length / 1024).toFixed(1)} KB)`);
|
|
281
|
-
const text = arg || "What's in this image?";
|
|
282
|
-
const content = [
|
|
283
|
-
{ type: "text", text },
|
|
284
|
-
{ type: "image", image: img.data, mimeType: img.mimeType },
|
|
285
|
-
];
|
|
286
|
-
messages.push({ role: "user", content });
|
|
287
|
-
tui.addMessage({
|
|
288
|
-
id: `user-${Date.now()}`,
|
|
289
|
-
role: "user",
|
|
290
|
-
content: `${text} [📎 图片]`,
|
|
291
|
-
timestamp: Date.now(),
|
|
292
|
-
});
|
|
293
|
-
// Signal that we need to run the agent
|
|
294
|
-
return "run";
|
|
295
|
-
}
|
|
296
|
-
case "tokens": {
|
|
297
|
-
if (tracker && tracker.lastInputTokens > 0) {
|
|
298
|
-
const ctxWindow = await getContextWindow(modelId);
|
|
299
|
-
const pct = Math.round((tracker.lastInputTokens / ctxWindow) * 100);
|
|
300
|
-
sysMsg(tui, [
|
|
301
|
-
`Context: ${tracker.lastInputTokens} / ${ctxWindow} tokens (${pct}%)`,
|
|
302
|
-
`Total: ${tracker.totalInputTokens} in / ${tracker.totalOutputTokens} out`,
|
|
303
|
-
`Messages: ${messages.length}`,
|
|
304
|
-
].join("\n"));
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
sysMsg(tui, `估算 tokens: ${estimateTokens(messages)} | Messages: ${messages.length}`);
|
|
308
|
-
}
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
case "mcp": {
|
|
312
|
-
const config = loadMcpConfig();
|
|
313
|
-
const servers = Object.entries(config.mcpServers);
|
|
314
|
-
if (servers.length === 0) {
|
|
315
|
-
sysMsg(tui, "无 MCP 服务器配置");
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
const status = getMcpStatus();
|
|
319
|
-
const lines = servers.map(([name, cfg]) => {
|
|
320
|
-
const disabled = cfg.enabled === false;
|
|
321
|
-
const connected = status[name]?.connected ?? false;
|
|
322
|
-
const toolCount = status[name]?.tools.length ?? 0;
|
|
323
|
-
const state = disabled ? "disabled" : connected ? "connected" : "disconnected";
|
|
324
|
-
return ` ${name}: ${state}${toolCount > 0 ? ` (${toolCount} tools)` : ""}`;
|
|
325
|
-
});
|
|
326
|
-
sysMsg(tui, `MCP 服务器 (${servers.length}):\n${lines.join("\n")}`);
|
|
327
|
-
}
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
case "skills": {
|
|
331
|
-
discoverSkills({ silent: true });
|
|
332
|
-
const skills = getSkills();
|
|
333
|
-
const cfg = loadConfig();
|
|
334
|
-
const disabledNames = cfg.disabledSkills ?? [];
|
|
335
|
-
if (skills.length === 0 && disabledNames.length === 0) {
|
|
336
|
-
sysMsg(tui, "无可用 skills。添加 SKILL.md 到 .min-agent/skills/<name>/");
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
const lines = [];
|
|
340
|
-
for (const s of skills) {
|
|
341
|
-
lines.push(` ${s.name} enabled`);
|
|
342
|
-
}
|
|
343
|
-
for (const n of disabledNames) {
|
|
344
|
-
lines.push(` ${n} disabled`);
|
|
345
|
-
}
|
|
346
|
-
sysMsg(tui, `Skills:\n${lines.join("\n")}`);
|
|
347
|
-
}
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
case "help":
|
|
351
|
-
sysMsg(tui, [
|
|
352
|
-
"命令:",
|
|
353
|
-
" /clear 清除对话历史",
|
|
354
|
-
" /compact 压缩上下文",
|
|
355
|
-
" /model [n] 显示/切换模型",
|
|
356
|
-
" /models 列出可用模型",
|
|
357
|
-
" /memory [t] 查看/保存记忆",
|
|
358
|
-
" /tokens 显示 token 用量",
|
|
359
|
-
" /mcp 显示 MCP 状态",
|
|
360
|
-
" /skills 显示可用 skills",
|
|
361
|
-
" /path 显示工作目录",
|
|
362
|
-
" /paste [t] 粘贴剪贴板图片 + 可选提示",
|
|
363
|
-
" /help 显示帮助",
|
|
364
|
-
" /exit 退出",
|
|
365
|
-
"",
|
|
366
|
-
"快捷键:",
|
|
367
|
-
" Esc 取消当前任务",
|
|
368
|
-
" Ctrl+J 输入框换行",
|
|
369
|
-
" Ctrl+C 退出程序",
|
|
370
|
-
" PageUp/Down 滚动消息",
|
|
371
|
-
].join("\n"));
|
|
372
|
-
break;
|
|
373
|
-
default:
|
|
374
|
-
sysMsg(tui, `未知命令: /${cmd},输入 /help 查看可用命令`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
function sysMsg(tui, content) {
|
|
378
|
-
tui.addMessage({
|
|
379
|
-
id: `sys-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
380
|
-
role: "system",
|
|
381
|
-
content,
|
|
382
|
-
timestamp: Date.now(),
|
|
383
|
-
});
|
|
384
|
-
}
|
package/dist/updater.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
export function localVersion() {
|
|
6
|
+
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
7
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
8
|
+
return pkg.version ?? "0.0.0";
|
|
9
|
+
}
|
|
10
|
+
export function compareVersions(a, b) {
|
|
11
|
+
const pa = a.split("-")[0].split(".").map(Number);
|
|
12
|
+
const pb = b.split("-")[0].split(".").map(Number);
|
|
13
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
14
|
+
const va = pa[i] ?? 0;
|
|
15
|
+
const vb = pb[i] ?? 0;
|
|
16
|
+
if (va !== vb)
|
|
17
|
+
return va - vb;
|
|
18
|
+
}
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
export async function fetchLatestVersion() {
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch("https://registry.npmjs.org/min-agent/latest", {
|
|
24
|
+
signal: AbortSignal.timeout(10000),
|
|
25
|
+
headers: { Accept: "application/json" },
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
return null;
|
|
29
|
+
const data = (await response.json());
|
|
30
|
+
return data.version ?? null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function runUpdate() {
|
|
37
|
+
const current = localVersion();
|
|
38
|
+
console.log(`当前版本: ${current}`);
|
|
39
|
+
const latest = await fetchLatestVersion();
|
|
40
|
+
if (!latest) {
|
|
41
|
+
console.error("无法检查新版本(网络不可用)。");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (compareVersions(current, latest) >= 0) {
|
|
45
|
+
console.log(`已是最新版本 (${latest})`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(`发现新版本: ${latest}`);
|
|
49
|
+
console.log("正在升级 min-agent...");
|
|
50
|
+
try {
|
|
51
|
+
execSync("npm install -g min-agent@latest", { stdio: "inherit" });
|
|
52
|
+
console.log(`✓ 已升级到 ${latest},请重启终端后生效`);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
console.error("自动升级失败。请手动执行: npm install -g min-agent@latest");
|
|
56
|
+
}
|
|
57
|
+
}
|