min-agent 0.3.0 → 0.4.1
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 +154 -216
- package/dist/agent.js +1119 -256
- package/dist/cli/commands/chat.js +10 -0
- package/dist/cli/commands/exec.js +32 -0
- package/dist/cli/commands/history.js +58 -0
- package/dist/cli/commands/index.js +224 -0
- package/dist/cli/commands/init.js +18 -0
- package/dist/cli/commands/mcp.js +173 -0
- package/dist/cli/commands/memory.js +69 -0
- package/dist/cli/commands/models.js +21 -0
- package/dist/cli/commands/permission.js +12 -0
- package/dist/cli/commands/rules.js +33 -0
- package/dist/cli/commands/sandbox.js +13 -0
- package/dist/cli/commands/serve.js +9 -0
- package/dist/cli/commands/setup.js +4 -0
- package/dist/cli/commands/shared.js +16 -0
- package/dist/cli/commands/skills.js +119 -0
- package/dist/cli/commands/update.js +7 -0
- package/dist/cli/commands/write-config.js +30 -0
- package/dist/cli/errors.js +36 -0
- package/dist/cli/exec-prompt.js +26 -0
- package/dist/cli/option-helpers.js +53 -0
- package/dist/cli/program.js +180 -0
- package/dist/cli.js +5 -888
- package/dist/code-mode.js +32 -14
- package/dist/compaction.js +347 -160
- package/dist/config.js +119 -10
- package/dist/confirm.js +56 -9
- package/dist/context-window.js +107 -39
- package/dist/doom-loop.js +264 -29
- package/dist/fetch-timeout.js +152 -0
- package/dist/http-approvals.js +60 -0
- package/dist/instructions.js +21 -0
- package/dist/logger.js +33 -4
- package/dist/markdown.js +37 -11
- package/dist/mcp.js +328 -30
- package/dist/memory.js +97 -56
- package/dist/output.js +7 -5
- package/dist/permission-cli.js +43 -0
- package/dist/plugins.js +46 -8
- package/dist/pricing.js +4 -4
- package/dist/provider.js +23 -6
- package/dist/question-format.js +60 -0
- package/dist/sandbox-cli.js +82 -0
- package/dist/sandbox.js +403 -0
- package/dist/save-throttle.js +45 -0
- package/dist/serve/common.js +404 -0
- package/dist/serve/routes-chat.js +347 -0
- package/dist/serve/routes-mcp.js +212 -0
- package/dist/serve/routes-memory.js +66 -0
- package/dist/serve/routes-meta.js +205 -0
- package/dist/serve/routes-sessions.js +61 -0
- package/dist/serve/routes-skills.js +70 -0
- package/dist/serve.js +33 -883
- package/dist/sessions.js +53 -9
- package/dist/skills.js +82 -18
- package/dist/title-gen.js +8 -2
- package/dist/token-display.js +36 -0
- package/dist/tool-display.js +5 -0
- package/dist/tool-output.js +1 -3
- package/dist/tools/apply_patch.js +85 -11
- package/dist/tools/atomic-file.js +35 -0
- package/dist/tools/backend.js +2 -2
- package/dist/tools/bash.js +57 -19
- package/dist/tools/code_search.js +7 -1
- package/dist/tools/edit.js +11 -10
- package/dist/tools/explore.js +74 -14
- package/dist/tools/glob.js +4 -0
- package/dist/tools/grep.js +17 -10
- package/dist/tools/index.js +6 -21
- package/dist/tools/question.js +28 -9
- package/dist/tools/read.js +6 -4
- package/dist/tools/search-searxng.js +223 -0
- package/dist/tools/search-serper.js +189 -0
- package/dist/tools/task.js +84 -30
- package/dist/tools/todo.js +120 -19
- package/dist/tools/web_fetch.js +11 -3
- package/dist/tools/web_search.js +66 -556
- package/dist/tools/write.js +23 -6
- package/dist/tui/App.js +63 -14
- package/dist/tui/ConfirmBar.js +45 -13
- package/dist/tui/InputBar.js +150 -35
- package/dist/tui/MessageList.js +266 -125
- package/dist/tui/ModelPicker.js +8 -3
- package/dist/tui/QuestionBar.js +51 -19
- package/dist/tui/SessionPicker.js +79 -0
- package/dist/tui/StatusBar.js +8 -14
- package/dist/tui/agent-runner.js +142 -22
- package/dist/tui/caret-pos.js +48 -5
- package/dist/tui/caret.js +1 -1
- package/dist/tui/click-count.js +13 -0
- package/dist/tui/drag-state.js +8 -3
- package/dist/tui/hydrate.js +129 -0
- package/dist/tui/index.js +42 -13
- package/dist/tui/input-history.js +92 -11
- package/dist/tui/layout.js +75 -4
- package/dist/tui/prompt-queue.js +24 -0
- package/dist/tui/selection.js +113 -21
- package/dist/tui/session-switch.js +28 -0
- package/dist/tui/slash-commands.js +22 -6
- package/dist/tui/slash-handler.js +233 -58
- package/dist/tui/text-width.js +38 -16
- package/dist/tui/token-info.js +7 -0
- package/dist/tui/tool-children.js +19 -0
- package/dist/tui/undo-stack.js +1 -1
- package/dist/tui/use-sgr-mouse.js +3 -1
- package/dist/tui-chat.js +276 -40
- package/dist/updater.js +88 -29
- package/dist/xml-search.js +194 -0
- package/docs/API.md +257 -25
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +873 -0
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +631 -0
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +47 -0
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +37 -0
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +68 -0
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +273 -0
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +165 -0
- package/package.json +6 -1
- package/skills/self-config/SKILL.md +90 -0
- package/skills/self-config/reference.md +149 -0
package/dist/tui-chat.js
CHANGED
|
@@ -4,16 +4,74 @@ import { discoverSkills, getAllSkills, getStaleDisabledSkills } from "./skills.j
|
|
|
4
4
|
import { setSlashArgProvider } from "./tui/slash-commands.js";
|
|
5
5
|
import { loadInstructions } from "./instructions.js";
|
|
6
6
|
import { TokenTracker } from "./compaction.js";
|
|
7
|
+
import { emptyTaskState, copyTaskState, resetTaskState } from "./tools/todo.js";
|
|
7
8
|
import { loadImageParts } from "./agent.js";
|
|
8
|
-
import { createUndoStack } from "./tui/undo-stack.js";
|
|
9
|
+
import { createUndoStack, clearStack } from "./tui/undo-stack.js";
|
|
9
10
|
import { killActiveProcesses } from "./tools/bash.js";
|
|
10
11
|
import { setTuiConfirm, setTuiQuestion } from "./confirm.js";
|
|
11
12
|
import { loadConfig, getActiveProvider } from "./config.js";
|
|
12
13
|
import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
|
|
13
14
|
import { handleSlashCommand, sysMsg } from "./tui/slash-handler.js";
|
|
14
15
|
import { createAgentRunner } from "./tui/agent-runner.js";
|
|
16
|
+
import { listSessions, loadSession, saveSession } from "./sessions.js";
|
|
17
|
+
import { hydrateMessages, lastUserText } from "./tui/hydrate.js";
|
|
18
|
+
import { createPromptQueue } from "./tui/prompt-queue.js";
|
|
19
|
+
import { prepareSwitch, switchFailMessage, prepareNew, newFailMessage } from "./tui/session-switch.js";
|
|
15
20
|
let confirmResolver = null;
|
|
16
21
|
let questionResolver = null;
|
|
22
|
+
/** Only one confirm/question overlay can be shown at a time; parallel tool calls queue up. */
|
|
23
|
+
const promptQueue = createPromptQueue();
|
|
24
|
+
function registerSessionCompletion() {
|
|
25
|
+
const provider = () => listSessions().map((s) => ({ value: s.id, description: s.title }));
|
|
26
|
+
setSlashArgProvider("sessions", provider);
|
|
27
|
+
setSlashArgProvider("resume", provider);
|
|
28
|
+
}
|
|
29
|
+
function registerPermissionCompletion() {
|
|
30
|
+
setSlashArgProvider("permission", (tokens) => {
|
|
31
|
+
const flags = [
|
|
32
|
+
{ value: "--project", description: "写入当前项目" },
|
|
33
|
+
{ value: "--global", description: "写入全局配置" },
|
|
34
|
+
];
|
|
35
|
+
const used = new Set(tokens);
|
|
36
|
+
const remainingFlags = flags.filter((f) => !used.has(f.value));
|
|
37
|
+
if (tokens.length === 0) {
|
|
38
|
+
return [
|
|
39
|
+
{ value: "ask", description: "危险操作前询问(默认)" },
|
|
40
|
+
{ value: "accept-edits", description: "自动允许改文件,危险命令仍询问" },
|
|
41
|
+
{ value: "allow-all", description: "全部自动允许" },
|
|
42
|
+
...remainingFlags,
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
return remainingFlags;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function registerSandboxCompletion() {
|
|
49
|
+
setSlashArgProvider("sandbox", (tokens) => {
|
|
50
|
+
const flags = [
|
|
51
|
+
{ value: "--project", description: "写入当前项目" },
|
|
52
|
+
{ value: "--global", description: "写入全局配置" },
|
|
53
|
+
];
|
|
54
|
+
const used = new Set(tokens);
|
|
55
|
+
const remainingFlags = flags.filter((f) => !used.has(f.value));
|
|
56
|
+
if (tokens.length === 0) {
|
|
57
|
+
return [
|
|
58
|
+
{ value: "off", description: "不隔离(默认)" },
|
|
59
|
+
{ value: "workspace", description: "只允许写入工作目录" },
|
|
60
|
+
{ value: "strict", description: "读写都限制在工作目录" },
|
|
61
|
+
{ value: "network", description: "设置是否允许联网" },
|
|
62
|
+
...remainingFlags,
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
if (tokens[0] === "network") {
|
|
66
|
+
const nets = [
|
|
67
|
+
{ value: "allow", description: "允许联网" },
|
|
68
|
+
{ value: "deny", description: "禁止联网" },
|
|
69
|
+
];
|
|
70
|
+
return [...nets.filter((n) => !used.has(n.value)), ...remainingFlags];
|
|
71
|
+
}
|
|
72
|
+
return remainingFlags;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
17
75
|
function registerSkillCompletion() {
|
|
18
76
|
setSlashArgProvider("skills", (tokens) => {
|
|
19
77
|
const action = tokens[0];
|
|
@@ -31,53 +89,93 @@ function registerSkillCompletion() {
|
|
|
31
89
|
return [];
|
|
32
90
|
const all = getAllSkills();
|
|
33
91
|
const pool = action === "enable"
|
|
34
|
-
? [
|
|
92
|
+
? [
|
|
93
|
+
...all.filter((s) => !s.enabled).map((s) => ({ value: s.name, description: s.description })),
|
|
94
|
+
...getStaleDisabledSkills().map((name) => ({ value: name, description: "(配置中的孤儿条目)" })),
|
|
95
|
+
]
|
|
35
96
|
: all.filter((s) => s.enabled).map((s) => ({ value: s.name, description: s.description }));
|
|
36
|
-
return [
|
|
97
|
+
return [
|
|
98
|
+
...pool.filter((o) => !tokens.includes(o.value)),
|
|
99
|
+
...(tokens.includes("--project") ? [] : [{ value: "--project", description: "仅对当前项目生效" }]),
|
|
100
|
+
];
|
|
37
101
|
});
|
|
38
102
|
}
|
|
39
103
|
export async function runTui(opts) {
|
|
40
|
-
const { modelId, resumeSessionId,
|
|
104
|
+
const { modelId, resumeSessionId, images, providerName, initialPrompt } = opts;
|
|
41
105
|
await initMcp();
|
|
42
106
|
discoverSkills();
|
|
43
107
|
registerSkillCompletion();
|
|
108
|
+
registerPermissionCompletion();
|
|
109
|
+
registerSandboxCompletion();
|
|
110
|
+
registerSessionCompletion();
|
|
44
111
|
const instructions = await loadInstructions();
|
|
45
112
|
const config = loadConfig();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const project = scanProject();
|
|
49
|
-
codeSystemPrompt = buildCodeSystemPrompt(project, instructions);
|
|
50
|
-
}
|
|
113
|
+
const project = scanProject();
|
|
114
|
+
let systemPrompt = buildCodeSystemPrompt(project, instructions);
|
|
51
115
|
let messages = [];
|
|
52
116
|
let sessionId = resumeSessionId;
|
|
53
117
|
let currentModel = modelId ?? getActiveProvider(config)?.defaultModel;
|
|
54
118
|
const currentProvider = providerName;
|
|
55
119
|
const tracker = new TokenTracker();
|
|
120
|
+
const taskState = emptyTaskState();
|
|
56
121
|
let undoStack = createUndoStack();
|
|
57
122
|
let lastUserMessage = null;
|
|
58
123
|
let planMode = false;
|
|
59
124
|
if (resumeSessionId) {
|
|
60
|
-
const { loadSession } = await import("./sessions.js");
|
|
61
125
|
const session = loadSession(resumeSessionId);
|
|
62
126
|
if (session) {
|
|
63
127
|
messages = session.messages;
|
|
64
128
|
sessionId = resumeSessionId;
|
|
129
|
+
copyTaskState(session.taskState, taskState);
|
|
65
130
|
}
|
|
66
131
|
}
|
|
67
132
|
let runner = null;
|
|
68
133
|
const tui = new TuiRenderer({
|
|
69
134
|
onSubmit: async (text) => {
|
|
70
135
|
if (text.startsWith("/")) {
|
|
71
|
-
const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => {
|
|
72
|
-
|
|
136
|
+
const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => {
|
|
137
|
+
currentModel = model;
|
|
138
|
+
tui.setModel(model);
|
|
139
|
+
}, requestExit, {
|
|
140
|
+
undoStack,
|
|
141
|
+
setUndoStack: (s) => {
|
|
142
|
+
undoStack = s;
|
|
143
|
+
},
|
|
144
|
+
lastUserMessage,
|
|
145
|
+
setLastUserMessage: (t) => {
|
|
146
|
+
lastUserMessage = t;
|
|
147
|
+
},
|
|
148
|
+
sessionId,
|
|
149
|
+
setSessionId: (id) => {
|
|
150
|
+
sessionId = id;
|
|
151
|
+
},
|
|
152
|
+
planMode,
|
|
153
|
+
togglePlanMode: () => {
|
|
154
|
+
planMode = !planMode;
|
|
155
|
+
},
|
|
156
|
+
reloadSystemPrompt: async () => {
|
|
157
|
+
const next = await loadInstructions();
|
|
158
|
+
systemPrompt = buildCodeSystemPrompt(scanProject(), next);
|
|
159
|
+
sysMsg(tui, `✓ 已重载规则(${next.join("").length} 字符)`);
|
|
160
|
+
},
|
|
161
|
+
applySession,
|
|
162
|
+
startNewSession,
|
|
163
|
+
openSessionPicker,
|
|
164
|
+
taskState,
|
|
165
|
+
});
|
|
166
|
+
if (result === "run" && runner) {
|
|
73
167
|
await runner.run();
|
|
168
|
+
persistSession();
|
|
169
|
+
}
|
|
74
170
|
return;
|
|
75
171
|
}
|
|
76
172
|
lastUserMessage = text;
|
|
77
173
|
tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
|
|
78
174
|
messages.push({ role: "user", content: text });
|
|
79
|
-
if (runner)
|
|
175
|
+
if (runner) {
|
|
80
176
|
await runner.run();
|
|
177
|
+
persistSession();
|
|
178
|
+
}
|
|
81
179
|
},
|
|
82
180
|
onConfirm: (accepted) => {
|
|
83
181
|
tui.hideConfirm();
|
|
@@ -105,17 +203,130 @@ export async function runTui(opts) {
|
|
|
105
203
|
tui.setModel(model);
|
|
106
204
|
sysMsg(tui, `✓ 默认模型已设为: ${model}`);
|
|
107
205
|
},
|
|
108
|
-
onModelCancel: () => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
206
|
+
onModelCancel: () => {
|
|
207
|
+
tui.hideModelPicker();
|
|
208
|
+
},
|
|
209
|
+
onSessionPick: (id) => {
|
|
210
|
+
tui.hideSessionPicker();
|
|
211
|
+
applySession(id);
|
|
212
|
+
},
|
|
213
|
+
onSessionCancel: () => {
|
|
214
|
+
tui.hideSessionPicker();
|
|
215
|
+
},
|
|
216
|
+
onToggleSelectionMode: () => {
|
|
217
|
+
tui.toggleSelectionMode();
|
|
218
|
+
},
|
|
219
|
+
onExitSelectionMode: () => {
|
|
220
|
+
tui.setSelectionMode(false);
|
|
221
|
+
},
|
|
222
|
+
onExit: () => {
|
|
223
|
+
requestExit();
|
|
224
|
+
},
|
|
225
|
+
}, modelId ?? getActiveProvider(config)?.defaultModel, sessionId);
|
|
226
|
+
runner = createAgentRunner({
|
|
227
|
+
tui,
|
|
228
|
+
messages,
|
|
229
|
+
tracker,
|
|
230
|
+
getModel: () => currentModel,
|
|
231
|
+
getProvider: () => currentProvider,
|
|
232
|
+
getPlanMode: () => planMode,
|
|
233
|
+
getUndoStack: () => undoStack,
|
|
234
|
+
setUndoStack: (s) => {
|
|
235
|
+
undoStack = s;
|
|
236
|
+
},
|
|
237
|
+
getSystemPrompt: () => systemPrompt,
|
|
238
|
+
getTaskState: () => taskState,
|
|
239
|
+
persist: () => persistSession(),
|
|
240
|
+
});
|
|
241
|
+
function persistSession() {
|
|
242
|
+
if (messages.length === 0)
|
|
243
|
+
return;
|
|
244
|
+
try {
|
|
245
|
+
sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
|
|
246
|
+
}
|
|
247
|
+
catch { }
|
|
248
|
+
}
|
|
249
|
+
async function refreshTokenBar() {
|
|
250
|
+
const { snapshotTokenInfo } = await import("./tui/token-info.js");
|
|
251
|
+
tui.setTokenInfo(await snapshotTokenInfo(tracker, currentModel, messages));
|
|
252
|
+
}
|
|
253
|
+
function applySession(id) {
|
|
254
|
+
const ready = prepareSwitch({
|
|
255
|
+
running: Boolean(runner?.getController()),
|
|
256
|
+
currentId: sessionId,
|
|
257
|
+
targetId: id,
|
|
258
|
+
exists: Boolean(loadSession(id)),
|
|
259
|
+
});
|
|
260
|
+
if (!ready.ok) {
|
|
261
|
+
sysMsg(tui, switchFailMessage(ready.reason));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (messages.length > 0)
|
|
265
|
+
sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
|
|
266
|
+
const session = loadSession(id);
|
|
267
|
+
if (!session) {
|
|
268
|
+
sysMsg(tui, switchFailMessage("missing"));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
messages.length = 0;
|
|
272
|
+
messages.push(...session.messages);
|
|
273
|
+
copyTaskState(session.taskState, taskState);
|
|
274
|
+
sessionId = id;
|
|
275
|
+
undoStack = clearStack(undoStack);
|
|
276
|
+
tracker.reset();
|
|
277
|
+
lastUserMessage = lastUserText(session.messages);
|
|
278
|
+
tui.setSessionId(id);
|
|
279
|
+
tui.update({ messages: hydrateMessages(session.messages) });
|
|
280
|
+
void refreshTokenBar();
|
|
281
|
+
sysMsg(tui, `✓ 已切换到: ${session.meta.title}`);
|
|
282
|
+
}
|
|
283
|
+
function startNewSession() {
|
|
284
|
+
const ready = prepareNew({
|
|
285
|
+
running: Boolean(runner?.getController()),
|
|
286
|
+
sessionId,
|
|
287
|
+
messageCount: messages.length,
|
|
288
|
+
});
|
|
289
|
+
if (!ready.ok) {
|
|
290
|
+
sysMsg(tui, newFailMessage(ready.reason));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (messages.length > 0)
|
|
294
|
+
sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
|
|
295
|
+
messages.length = 0;
|
|
296
|
+
resetTaskState(taskState);
|
|
297
|
+
sessionId = undefined;
|
|
298
|
+
undoStack = clearStack(undoStack);
|
|
299
|
+
tracker.reset();
|
|
300
|
+
lastUserMessage = null;
|
|
301
|
+
tui.setSessionId(undefined);
|
|
302
|
+
tui.update({ messages: [], tokenInfo: undefined });
|
|
303
|
+
sysMsg(tui, "✓ 已新建会话");
|
|
304
|
+
}
|
|
305
|
+
function openSessionPicker() {
|
|
306
|
+
if (listSessions().length === 0) {
|
|
307
|
+
sysMsg(tui, "无已保存的会话");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
tui.showSessionPicker();
|
|
311
|
+
}
|
|
312
|
+
setTuiConfirm((message) => promptQueue.run(() => new Promise((resolve) => {
|
|
313
|
+
confirmResolver = resolve;
|
|
314
|
+
tui.showConfirm(message);
|
|
315
|
+
}), () => false));
|
|
316
|
+
setTuiQuestion((prompt, options) => promptQueue.run(() => new Promise((resolve) => {
|
|
317
|
+
questionResolver = resolve;
|
|
318
|
+
tui.showQuestion(prompt, options);
|
|
319
|
+
}), () => null));
|
|
114
320
|
let exitResolve;
|
|
115
|
-
const exitPromise = new Promise((resolve) => {
|
|
321
|
+
const exitPromise = new Promise((resolve) => {
|
|
322
|
+
exitResolve = resolve;
|
|
323
|
+
});
|
|
116
324
|
const requestExit = () => {
|
|
325
|
+
tui.setSelectionMode(false);
|
|
326
|
+
persistSession();
|
|
117
327
|
if (runner?.getController()) {
|
|
118
328
|
killActiveProcesses();
|
|
329
|
+
promptQueue.cancelAll();
|
|
119
330
|
if (confirmResolver) {
|
|
120
331
|
confirmResolver(false);
|
|
121
332
|
confirmResolver = null;
|
|
@@ -134,31 +345,56 @@ export async function runTui(opts) {
|
|
|
134
345
|
exitResolve?.();
|
|
135
346
|
};
|
|
136
347
|
tui.start();
|
|
348
|
+
if (messages.length > 0)
|
|
349
|
+
tui.update({ messages: hydrateMessages(messages) });
|
|
350
|
+
if (messages.length > 0)
|
|
351
|
+
await refreshTokenBar();
|
|
352
|
+
const prompt = initialPrompt?.trim() ?? "";
|
|
353
|
+
const notify = (kind, imgPath) => {
|
|
354
|
+
if (kind === "not_found")
|
|
355
|
+
sysMsg(tui, `⚠ 图片不存在: ${imgPath}`);
|
|
356
|
+
else if (kind === "too_large")
|
|
357
|
+
sysMsg(tui, `⚠ 图片超过 5 MB 限制: ${imgPath}`);
|
|
358
|
+
else
|
|
359
|
+
sysMsg(tui, `📎 ${imgPath}`);
|
|
360
|
+
};
|
|
137
361
|
if (images && images.length > 0) {
|
|
138
|
-
const
|
|
139
|
-
|
|
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) {
|
|
362
|
+
const parts = [{ type: "text", text: prompt }, ...loadImageParts(images, notify)];
|
|
363
|
+
if (parts.length > 1 || prompt) {
|
|
148
364
|
messages.push({ role: "user", content: parts });
|
|
149
|
-
tui.addMessage({
|
|
365
|
+
tui.addMessage({
|
|
366
|
+
id: `user-${Date.now()}`,
|
|
367
|
+
role: "user",
|
|
368
|
+
content: prompt ? prompt : `[📎 图片] ${images.join(", ")}`,
|
|
369
|
+
timestamp: Date.now(),
|
|
370
|
+
});
|
|
371
|
+
if (prompt)
|
|
372
|
+
lastUserMessage = prompt;
|
|
150
373
|
await runner.run();
|
|
374
|
+
persistSession();
|
|
151
375
|
}
|
|
152
376
|
}
|
|
153
|
-
|
|
377
|
+
else if (prompt) {
|
|
378
|
+
lastUserMessage = prompt;
|
|
379
|
+
tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: prompt, timestamp: Date.now() });
|
|
380
|
+
messages.push({ role: "user", content: prompt });
|
|
381
|
+
await runner.run();
|
|
382
|
+
persistSession();
|
|
383
|
+
}
|
|
384
|
+
const onSigint = () => {
|
|
385
|
+
if (runner?.getController())
|
|
386
|
+
requestExit();
|
|
387
|
+
};
|
|
388
|
+
process.on("SIGINT", onSigint);
|
|
154
389
|
await exitPromise;
|
|
155
|
-
process.off("SIGINT",
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
390
|
+
process.off("SIGINT", onSigint);
|
|
391
|
+
try {
|
|
392
|
+
persistSession();
|
|
393
|
+
}
|
|
394
|
+
finally {
|
|
395
|
+
setTuiConfirm(null);
|
|
396
|
+
setTuiQuestion(null);
|
|
397
|
+
tui.destroy();
|
|
398
|
+
await shutdownMcp();
|
|
159
399
|
}
|
|
160
|
-
setTuiConfirm(null);
|
|
161
|
-
setTuiQuestion(null);
|
|
162
|
-
tui.destroy();
|
|
163
|
-
await shutdownMcp();
|
|
164
400
|
}
|
package/dist/updater.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import {
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
export const UPDATE_PACKAGE = "min-agent";
|
|
6
|
+
export const NPM_LATEST_URL = `https://registry.npmjs.org/${UPDATE_PACKAGE}/latest`;
|
|
7
|
+
export const UPDATE_NPM_BIN = "npm";
|
|
8
|
+
export const UPDATE_NPM_ARGS = ["install", "-g", UPDATE_PACKAGE];
|
|
9
|
+
const VERSION_RE = /^\d+\.\d+\.\d+/;
|
|
10
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
11
|
+
export function updateInstallCommand() {
|
|
12
|
+
return [UPDATE_NPM_BIN, ...UPDATE_NPM_ARGS].join(" ");
|
|
13
|
+
}
|
|
5
14
|
export function localVersion() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
try {
|
|
16
|
+
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
17
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
18
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "0.0.0";
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return "0.0.0";
|
|
22
|
+
}
|
|
9
23
|
}
|
|
10
24
|
export function compareVersions(a, b) {
|
|
11
25
|
const pa = a.split("-")[0].split(".").map(Number);
|
|
@@ -13,45 +27,90 @@ export function compareVersions(a, b) {
|
|
|
13
27
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
14
28
|
const va = pa[i] ?? 0;
|
|
15
29
|
const vb = pb[i] ?? 0;
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
const na = Number.isFinite(va) ? va : 0;
|
|
31
|
+
const nb = Number.isFinite(vb) ? vb : 0;
|
|
32
|
+
if (na !== nb)
|
|
33
|
+
return na - nb;
|
|
18
34
|
}
|
|
19
35
|
return 0;
|
|
20
36
|
}
|
|
21
|
-
|
|
37
|
+
function parseVersion(value) {
|
|
38
|
+
if (typeof value !== "string" || !VERSION_RE.test(value))
|
|
39
|
+
return null;
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export async function fetchLatestVersion(fetchFn = fetch) {
|
|
43
|
+
let response;
|
|
22
44
|
try {
|
|
23
|
-
|
|
24
|
-
signal: AbortSignal.timeout(
|
|
45
|
+
response = await fetchFn(NPM_LATEST_URL, {
|
|
46
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
25
47
|
headers: { Accept: "application/json" },
|
|
26
48
|
});
|
|
27
|
-
if (!response.ok)
|
|
28
|
-
return null;
|
|
29
|
-
const data = (await response.json());
|
|
30
|
-
return data.version ?? null;
|
|
31
49
|
}
|
|
32
50
|
catch {
|
|
33
51
|
return null;
|
|
34
52
|
}
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
return null;
|
|
55
|
+
let data;
|
|
56
|
+
try {
|
|
57
|
+
data = await response.json();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (!data || typeof data !== "object" || !("version" in data))
|
|
63
|
+
return null;
|
|
64
|
+
return parseVersion(data.version);
|
|
65
|
+
}
|
|
66
|
+
export async function checkForUpdate(opts) {
|
|
67
|
+
const current = opts?.currentVersion ?? localVersion();
|
|
68
|
+
const latest = await fetchLatestVersion(opts?.fetchFn);
|
|
69
|
+
return {
|
|
70
|
+
current,
|
|
71
|
+
latest,
|
|
72
|
+
updateAvailable: latest !== null && compareVersions(current, latest) < 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function installLatest() {
|
|
76
|
+
const result = spawnSync(UPDATE_NPM_BIN, [...UPDATE_NPM_ARGS], {
|
|
77
|
+
stdio: "inherit",
|
|
78
|
+
shell: process.platform === "win32",
|
|
79
|
+
env: process.env,
|
|
80
|
+
});
|
|
81
|
+
if (result.error) {
|
|
82
|
+
const code = result.error.code;
|
|
83
|
+
return { ok: false, missing: code === "ENOENT" };
|
|
84
|
+
}
|
|
85
|
+
return { ok: result.status === 0 };
|
|
35
86
|
}
|
|
36
|
-
export async function runUpdate() {
|
|
37
|
-
const
|
|
38
|
-
console.
|
|
39
|
-
const
|
|
87
|
+
export async function runUpdate(io = {}) {
|
|
88
|
+
const log = io.log ?? ((msg) => console.log(msg));
|
|
89
|
+
const error = io.error ?? ((msg) => console.error(msg));
|
|
90
|
+
const current = io.currentVersion?.() ?? localVersion();
|
|
91
|
+
const cmd = updateInstallCommand();
|
|
92
|
+
log(`当前版本: ${current}`);
|
|
93
|
+
const latest = io.fetchLatest ? await io.fetchLatest() : await fetchLatestVersion();
|
|
40
94
|
if (!latest) {
|
|
41
|
-
|
|
42
|
-
return;
|
|
95
|
+
error(`无法检查新版本(网络不可用)。请稍后重试,或手动执行: ${cmd}`);
|
|
96
|
+
return { status: "check-failed", current, latest: null };
|
|
43
97
|
}
|
|
44
98
|
if (compareVersions(current, latest) >= 0) {
|
|
45
|
-
|
|
46
|
-
return;
|
|
99
|
+
log(`已是最新版本 (${latest})`);
|
|
100
|
+
return { status: "up-to-date", current, latest };
|
|
47
101
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
102
|
+
log(`发现新版本: ${latest}`);
|
|
103
|
+
log(`正在执行 ${cmd} ...`);
|
|
104
|
+
const installed = (io.install ?? installLatest)();
|
|
105
|
+
if (!installed.ok) {
|
|
106
|
+
if (installed.missing) {
|
|
107
|
+
error(`未找到 npm。请先安装 Node.js,然后执行: ${cmd}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
error(`自动升级失败。请手动执行: ${cmd}`);
|
|
111
|
+
}
|
|
112
|
+
return { status: "install-failed", current, latest };
|
|
56
113
|
}
|
|
114
|
+
log(`✓ 已升级到 ${latest}`);
|
|
115
|
+
return { status: "updated", current, latest };
|
|
57
116
|
}
|