min-agent 0.4.1 → 0.5.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 +46 -2
- package/dist/agent.js +89 -29
- package/dist/cli/commands/chat.js +3 -0
- package/dist/cli/commands/ctx.js +7 -0
- package/dist/cli/commands/exec.js +3 -0
- package/dist/cli/commands/index.js +32 -7
- package/dist/cli/commands/memory.js +33 -15
- package/dist/cli/commands/setup.js +55 -3
- package/dist/cli/commands/shared.js +10 -1
- package/dist/cli/commands/think.js +12 -0
- package/dist/cli/commands/write-config.js +22 -0
- package/dist/cli/option-helpers.js +13 -1
- package/dist/cli/program.js +57 -14
- package/dist/cli/setup/detect.js +17 -0
- package/dist/cli/setup/flags.js +12 -0
- package/dist/cli/setup/flow.js +108 -0
- package/dist/cli/setup/provider-form.js +102 -0
- package/dist/cli/setup/ui.js +534 -0
- package/dist/code-mode.js +1 -1
- package/dist/config.js +93 -159
- package/dist/context-window.js +39 -49
- package/dist/ctx-cli.js +30 -0
- package/dist/ctx.js +80 -0
- package/dist/memory-cli.js +33 -0
- package/dist/memory.js +127 -46
- package/dist/model-catalog.js +285 -0
- package/dist/ollama-model.js +234 -0
- package/dist/ollama-openai-bridge.js +383 -0
- package/dist/permission-cli.js +1 -4
- package/dist/provider.js +4 -1
- package/dist/reasoning-stream.js +158 -0
- package/dist/sandbox-cli.js +1 -4
- package/dist/scope.js +23 -0
- package/dist/serve/common.js +22 -1
- package/dist/serve/routes-chat.js +21 -1
- package/dist/serve/routes-memory.js +31 -2
- package/dist/serve/routes-meta.js +69 -6
- package/dist/think-cli.js +36 -0
- package/dist/thinking-wire.js +239 -0
- package/dist/thinking.js +166 -0
- package/dist/token-display.js +10 -7
- package/dist/tools/todo.js +22 -8
- package/dist/tui/App.js +48 -8
- package/dist/tui/CtxPicker.js +68 -0
- package/dist/tui/InputBar.js +112 -37
- package/dist/tui/MessageList.js +53 -22
- package/dist/tui/StatusBar.js +7 -3
- package/dist/tui/ThinkPicker.js +75 -0
- package/dist/tui/bracketed-paste.js +37 -0
- package/dist/tui/caret-pos.js +10 -8
- package/dist/tui/index.js +13 -1
- package/dist/tui/layout.js +17 -0
- package/dist/tui/overlay-input.js +12 -0
- package/dist/tui/paste-draft.js +173 -0
- package/dist/tui/selection.js +8 -2
- package/dist/tui/slash-commands.js +24 -1
- package/dist/tui/slash-handler.js +88 -18
- package/dist/tui/text-width.js +6 -6
- package/dist/tui-chat.js +85 -7
- package/docs/API.md +69 -6
- package/docs/superpowers/plans/2026-08-23-cli-setup.md +501 -0
- package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +475 -0
- package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +450 -0
- package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +282 -0
- package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +174 -0
- package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +140 -0
- package/package.json +1 -1
- package/skills/self-config/SKILL.md +7 -4
- package/skills/self-config/reference.md +12 -6
package/README.md
CHANGED
|
@@ -67,6 +67,8 @@ Type `/` to open a command menu. Use **↑ / ↓** to recall previous messages.
|
|
|
67
67
|
| `/model` | Switch model |
|
|
68
68
|
| `/provider` | Switch provider |
|
|
69
69
|
| `/plan` | Toggle plan mode (look, don't touch) |
|
|
70
|
+
| `/think` | Choose thinking intensity |
|
|
71
|
+
| `/memory` | Turn memory on/off, or save a note |
|
|
70
72
|
| `/undo` | Undo the last turn |
|
|
71
73
|
| `/diff` | Show file changes made so far |
|
|
72
74
|
| `/sessions` | List or switch sessions |
|
|
@@ -74,10 +76,11 @@ Type `/` to open a command menu. Use **↑ / ↓** to recall previous messages.
|
|
|
74
76
|
| `/mcp` | Show connected MCP servers |
|
|
75
77
|
| `/skills` | Manage skills |
|
|
76
78
|
| `/tokens` | Usage and estimated cost |
|
|
79
|
+
| `/ctx` | Choose local-model context window |
|
|
77
80
|
| `/help` | Show all commands |
|
|
78
81
|
| `/exit` | Exit |
|
|
79
82
|
|
|
80
|
-
Shortcuts: **Ctrl+C** clears the input (press twice to exit) · **Ctrl+B** selects text to copy · mouse drag-select also copies on release.
|
|
83
|
+
Shortcuts: **Ctrl+C** clears the input (press twice to exit) · **Ctrl+V** pastes an image · a multi-line paste becomes a snippet · **Ctrl+B** selects text to copy · mouse drag-select also copies on release · click a tool or thinking block to expand or collapse it.
|
|
81
84
|
|
|
82
85
|
## Staying in Control
|
|
83
86
|
|
|
@@ -98,10 +101,29 @@ min-agent sandbox off # No restriction (default)
|
|
|
98
101
|
min-agent sandbox workspace # Only allow changes inside the current folder
|
|
99
102
|
```
|
|
100
103
|
|
|
104
|
+
## Memory
|
|
105
|
+
|
|
106
|
+
Remembered notes are off by default. Turn them on when you want facts to persist across sessions:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
min-agent memory # Show the switch and stored notes
|
|
110
|
+
min-agent memory on # Turn memory on (writes config)
|
|
111
|
+
min-agent --memory on # Same; with a message, this session only
|
|
112
|
+
min-agent memory add "prefer bun"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
In chat, `/memory on` or `/memory off` toggles it; `/memory` lists notes; `/memory some text` saves one.
|
|
116
|
+
|
|
101
117
|
## Configuration
|
|
102
118
|
|
|
103
119
|
Run `min-agent setup` for a guided setup — this is the easiest way to add or switch AI providers.
|
|
104
120
|
|
|
121
|
+
```bash
|
|
122
|
+
min-agent setup --type ollama
|
|
123
|
+
min-agent setup --type openai-compatible --url https://api.example.com/v1 --api-key "$KEY" --name groq --default-model llama-3.1-70b
|
|
124
|
+
min-agent setup --switch ollama
|
|
125
|
+
```
|
|
126
|
+
|
|
105
127
|
Settings live in `~/.min-agent/config.json` (global). A project can have its own `.min-agent/config.json` that only applies there.
|
|
106
128
|
|
|
107
129
|
## Extending It
|
|
@@ -170,6 +192,8 @@ min-agent --provider anthropic "你的消息"
|
|
|
170
192
|
| `/model` | 切换模型 |
|
|
171
193
|
| `/provider` | 切换服务商 |
|
|
172
194
|
| `/plan` | 切换"计划模式"(只看不改) |
|
|
195
|
+
| `/think` | 选择思考强度 |
|
|
196
|
+
| `/memory` | 开关记忆,或保存一条 |
|
|
173
197
|
| `/undo` | 撤销上一轮操作 |
|
|
174
198
|
| `/diff` | 查看当前已产生的文件改动 |
|
|
175
199
|
| `/sessions` | 查看或切换会话 |
|
|
@@ -177,10 +201,11 @@ min-agent --provider anthropic "你的消息"
|
|
|
177
201
|
| `/mcp` | 查看已连接的 MCP 服务 |
|
|
178
202
|
| `/skills` | 管理技能 |
|
|
179
203
|
| `/tokens` | 查看用量与预估费用 |
|
|
204
|
+
| `/ctx` | 选择本地模型上下文窗口 |
|
|
180
205
|
| `/help` | 查看全部命令 |
|
|
181
206
|
| `/exit` | 退出 |
|
|
182
207
|
|
|
183
|
-
快捷键:**Ctrl+C** 清空输入框(连按两次退出)· **Ctrl+B** 选中文本以复制 ·
|
|
208
|
+
快捷键:**Ctrl+C** 清空输入框(连按两次退出)· **Ctrl+V** 粘贴图片 · 多行粘贴会收成片段 · **Ctrl+B** 选中文本以复制 · 也支持鼠标拖拽选中并自动复制 · 点击工具或思考块可展开或收起。
|
|
184
209
|
|
|
185
210
|
## 掌控风险
|
|
186
211
|
|
|
@@ -201,10 +226,29 @@ min-agent sandbox off # 不限制(默认)
|
|
|
201
226
|
min-agent sandbox workspace # 仅允许修改当前项目文件夹内的内容
|
|
202
227
|
```
|
|
203
228
|
|
|
229
|
+
## 记忆
|
|
230
|
+
|
|
231
|
+
跨会话记忆默认关闭。需要记住偏好或项目约定时再打开:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
min-agent memory # 查看开关与已保存内容
|
|
235
|
+
min-agent memory on # 开启记忆(写入配置)
|
|
236
|
+
min-agent --memory on # 同上;若同时带消息则只对本轮生效
|
|
237
|
+
min-agent memory add "prefer bun"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
对话里用 `/memory on` 或 `/memory off` 开关;`/memory` 查看;`/memory 一段话` 保存一条。
|
|
241
|
+
|
|
204
242
|
## 配置
|
|
205
243
|
|
|
206
244
|
运行 `min-agent setup` 即可通过引导式流程完成配置,这是添加或切换 AI 服务商最简单的方式。
|
|
207
245
|
|
|
246
|
+
```bash
|
|
247
|
+
min-agent setup --type ollama
|
|
248
|
+
min-agent setup --type openai-compatible --url https://api.example.com/v1 --api-key "$KEY" --name groq --default-model llama-3.1-70b
|
|
249
|
+
min-agent setup --switch ollama
|
|
250
|
+
```
|
|
251
|
+
|
|
208
252
|
配置保存在 `~/.min-agent/config.json`(全局生效)。也可以在某个项目下单独创建 `.min-agent/config.json`,仅对该项目生效。
|
|
209
253
|
|
|
210
254
|
## 扩展能力
|
package/dist/agent.js
CHANGED
|
@@ -10,7 +10,8 @@ import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
|
|
|
10
10
|
import { initMcp, shutdownMcp, getMcpTools, getMcpCatalogTools, getMcpReadOnlyToolIds } from "./mcp.js";
|
|
11
11
|
import { discoverSkills, attachSkills, collectLoadedSkillNames } from "./skills.js";
|
|
12
12
|
import { loadInstructions } from "./instructions.js";
|
|
13
|
-
import {
|
|
13
|
+
import { resolveThinkingRequest, thinkingRequestStore } from "./thinking.js";
|
|
14
|
+
import { getMemorySystemPrompt, getMemoryTools, isMemoryEnabled } from "./memory.js";
|
|
14
15
|
import { needsCompaction, compactMessages, estimateTokens, estimateStringTokens, estimateOverheadTokens, TokenTracker, COMPACTION_RATIO, PRUNE_PRESSURE_RATIO, PRUNE_TARGET_RATIO, applyToolPrune, pruneToolOutputs, } from "./compaction.js";
|
|
15
16
|
import { loadPluginTools, getPluginReadOnlyIds } from "./plugins.js";
|
|
16
17
|
import { MarkdownRenderer } from "./markdown.js";
|
|
@@ -20,6 +21,7 @@ import { XmlSearchSplitter } from "./xml-search.js";
|
|
|
20
21
|
import { printHeader, printDivider, printToolCall, printToolResult, printDone } from "./output.js";
|
|
21
22
|
import { createTodoTool, captureGoal, copyTaskState, emptyTaskState, formatTaskStatePrompt, } from "./tools/todo.js";
|
|
22
23
|
import { getContextWindowInfo } from "./context-window.js";
|
|
24
|
+
import { formatTokenCount } from "./token-display.js";
|
|
23
25
|
import { log, logToolCall, logToolResult, startRunLog, nextRunPass, endRunLog } from "./logger.js";
|
|
24
26
|
import { markSyntheticMessage, isSyntheticMessage } from "./synthetic.js";
|
|
25
27
|
import { runWithInstructionTracker, resetActiveInstructionTracker } from "./instructions.js";
|
|
@@ -90,14 +92,6 @@ function printInitReady() {
|
|
|
90
92
|
const { dim, reset } = DIM_STYLE;
|
|
91
93
|
console.log(`${dim}✓ 就绪${reset}`);
|
|
92
94
|
}
|
|
93
|
-
/** Stream thinking to stderr (dim). Set MIN_AGENT_SHOW_THINKING=0 to hide. */
|
|
94
|
-
function writeThinkingDelta(text) {
|
|
95
|
-
if (!text)
|
|
96
|
-
return;
|
|
97
|
-
if (process.env.MIN_AGENT_SHOW_THINKING === "0" || process.env.MIN_AGENT_SHOW_THINKING === "false")
|
|
98
|
-
return;
|
|
99
|
-
process.stderr.write(`${DIM_STYLE.dim}${text}${DIM_STYLE.reset}`);
|
|
100
|
-
}
|
|
101
95
|
export function loadImageParts(imagePaths, notify) {
|
|
102
96
|
const parts = [];
|
|
103
97
|
for (const imgPath of imagePaths) {
|
|
@@ -222,12 +216,12 @@ export function pushTurn(messages, assistantText, toolCalls, toolResults) {
|
|
|
222
216
|
* once per run avoids re-reading plugin manifests and re-wrapping MCP tools on
|
|
223
217
|
* every continue.
|
|
224
218
|
*/
|
|
225
|
-
async function buildRunTools(modelId, abortSignal, planMode, tracker, taskState, subAgent) {
|
|
219
|
+
async function buildRunTools(modelId, abortSignal, planMode, tracker, taskState, subAgent, memory) {
|
|
226
220
|
const builtinTools = createTools();
|
|
227
221
|
builtinTools.todo = createTodoTool({ store: taskState });
|
|
228
222
|
const mcpTools = getMcpTools();
|
|
229
223
|
const catalogTools = getMcpCatalogTools();
|
|
230
|
-
const memoryTools = getMemoryTools();
|
|
224
|
+
const memoryTools = isMemoryEnabled(memory) ? getMemoryTools() : {};
|
|
231
225
|
const pluginTools = await loadPluginTools();
|
|
232
226
|
const allTools = {
|
|
233
227
|
...builtinTools,
|
|
@@ -262,28 +256,48 @@ async function buildRunTools(modelId, abortSignal, planMode, tracker, taskState,
|
|
|
262
256
|
}
|
|
263
257
|
return allTools;
|
|
264
258
|
}
|
|
265
|
-
function buildIterationPrompts(allTools, messages, taskState, loopGuard) {
|
|
259
|
+
function buildIterationPrompts(allTools, messages, taskState, loopGuard, memory) {
|
|
266
260
|
const skillsPrompt = attachSkills(allTools, collectLoadedSkillNames(messages));
|
|
267
261
|
return {
|
|
268
262
|
stable: [skillsPrompt].filter((s) => s.length > 0),
|
|
269
|
-
volatile: [getMemorySystemPrompt(), formatTaskStatePrompt(taskState), loopGuard.promptHint()].filter((s) => s.length > 0),
|
|
263
|
+
volatile: [getMemorySystemPrompt(memory), formatTaskStatePrompt(taskState), loopGuard.promptHint()].filter((s) => s.length > 0),
|
|
270
264
|
};
|
|
271
265
|
}
|
|
272
266
|
/** Ephemeral per-request message carrying the volatile prompt sections. */
|
|
273
|
-
export const SESSION_STATE_HEADER = "## Session state (
|
|
267
|
+
export const SESSION_STATE_HEADER = "## Session state (reference only — not a to-do list, not part of the conversation)";
|
|
274
268
|
function sessionStateMessage(volatile) {
|
|
275
269
|
if (volatile.length === 0)
|
|
276
270
|
return null;
|
|
277
271
|
return { role: "system", content: [SESSION_STATE_HEADER, ...volatile].join("\n\n") };
|
|
278
272
|
}
|
|
273
|
+
export function parseContextOverflow(msg) {
|
|
274
|
+
if (!isContextOverflowError(msg))
|
|
275
|
+
return null;
|
|
276
|
+
const prompt = msg.match(/n_prompt_tokens["\s:=]+(\d+)/i)?.[1] ?? msg.match(/request\s*\((\d+)\s*tokens?\)/i)?.[1];
|
|
277
|
+
const ctx = msg.match(/n_ctx["\s:=]+(\d+)/i)?.[1] ?? msg.match(/context size\s*\((\d+)\s*tokens?\)/i)?.[1];
|
|
278
|
+
return {
|
|
279
|
+
...(prompt ? { promptTokens: Number(prompt) } : {}),
|
|
280
|
+
...(ctx ? { ctxTokens: Number(ctx) } : {}),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function formatContextOverflowMessage(msg) {
|
|
284
|
+
const info = parseContextOverflow(msg);
|
|
285
|
+
const prompt = info?.promptTokens != null ? formatTokenCount(info.promptTokens) : null;
|
|
286
|
+
const ctx = info?.ctxTokens != null ? formatTokenCount(info.ctxTokens) : null;
|
|
287
|
+
if (prompt && ctx)
|
|
288
|
+
return `请求内容约 ${prompt} token,超过当前上下文窗口 ${ctx}。请把窗口调大后再试。`;
|
|
289
|
+
return "请求内容超过当前上下文窗口。请把窗口调大后再试。";
|
|
290
|
+
}
|
|
279
291
|
function formatErrorMessage(msg) {
|
|
292
|
+
if (isContextOverflowError(msg))
|
|
293
|
+
return formatContextOverflowMessage(msg);
|
|
280
294
|
if (msg.includes("API key") || msg.includes("Unauthorized") || msg.includes("Forbidden")) {
|
|
281
295
|
return "Authentication error: Check your API key.";
|
|
282
296
|
}
|
|
283
297
|
if (msg.includes("429") || msg.includes("rate limit") || msg.includes("Rate limit")) {
|
|
284
298
|
return "Rate limited after retries. Please wait and try again.";
|
|
285
299
|
}
|
|
286
|
-
if (msg.includes("timeout") || msg.includes("ETIMEDOUT")
|
|
300
|
+
if (msg.includes("timeout") || msg.includes("ETIMEDOUT")) {
|
|
287
301
|
return `Network error (retries exhausted): ${msg}`;
|
|
288
302
|
}
|
|
289
303
|
return msg;
|
|
@@ -299,7 +313,7 @@ function safeText(value) {
|
|
|
299
313
|
}
|
|
300
314
|
}
|
|
301
315
|
function isContextOverflowError(msg) {
|
|
302
|
-
return /context[\s_-]*(length|window|size)|context_length_exceeded|maximum context|too many tokens|prompt (is )?too long|reduce the (prompt )?length|token limit exceeded/i.test(msg);
|
|
316
|
+
return /exceed_context_size_error|context[\s_-]*(length|window|size)|context_length_exceeded|maximum context|too many tokens|prompt (is )?too long|reduce the (prompt )?length|token limit exceeded/i.test(msg);
|
|
303
317
|
}
|
|
304
318
|
function invokeCallback(fn, ...args) {
|
|
305
319
|
if (!fn)
|
|
@@ -518,7 +532,7 @@ async function runOnceCoreLoop(messages, systemPrompt, modelId, abortSignal, cal
|
|
|
518
532
|
const allTools = await buildRunTools(modelId, abortSignal, options?.planMode ?? false, tracker, taskState, {
|
|
519
533
|
loopGuard,
|
|
520
534
|
shouldStop: () => checkBudget() || overTurnBudget(totalSteps),
|
|
521
|
-
});
|
|
535
|
+
}, options?.memory);
|
|
522
536
|
const finish = async (opts) => {
|
|
523
537
|
log("info", `run end steps=${totalSteps} continues=${continues} tokens_in=${lastUsage?.inputTokens ?? 0} tokens_out=${lastUsage?.outputTokens ?? 0}`);
|
|
524
538
|
endRunLog();
|
|
@@ -592,6 +606,12 @@ async function runOnceCoreLoop(messages, systemPrompt, modelId, abortSignal, cal
|
|
|
592
606
|
temperature,
|
|
593
607
|
maxTokens,
|
|
594
608
|
topP,
|
|
609
|
+
thinking: options?.thinking,
|
|
610
|
+
memory: options?.memory,
|
|
611
|
+
providerType: (options?.providerName
|
|
612
|
+
? cfg.providers?.find((p) => p.name === options.providerName)
|
|
613
|
+
: getActiveProvider(cfg))?.type,
|
|
614
|
+
modelId,
|
|
595
615
|
checkBudget,
|
|
596
616
|
overTurnBudget: (stepsInPass) => overTurnBudget(totalSteps + stepsInPass),
|
|
597
617
|
pruneBudget,
|
|
@@ -607,7 +627,7 @@ async function runOnceCoreLoop(messages, systemPrompt, modelId, abortSignal, cal
|
|
|
607
627
|
log("error", msg);
|
|
608
628
|
hasError = true;
|
|
609
629
|
if (cbs.onStreamError)
|
|
610
|
-
invokeCallback(cbs.onStreamError, msg);
|
|
630
|
+
invokeCallback(cbs.onStreamError, formatErrorMessage(msg));
|
|
611
631
|
else
|
|
612
632
|
console.error(`\x1b[31m${formatErrorMessage(msg)}\x1b[0m`);
|
|
613
633
|
return finish({ aborted: Boolean(abortSignal?.aborted), maxStepsReached: false });
|
|
@@ -631,6 +651,25 @@ async function runOnceCoreLoop(messages, systemPrompt, modelId, abortSignal, cal
|
|
|
631
651
|
applyToolPrune(messages, pruneBudget());
|
|
632
652
|
continue;
|
|
633
653
|
}
|
|
654
|
+
// Prompt (system + tools + messages) does not fit the loaded window.
|
|
655
|
+
// Retrying the same request cannot help; compaction only helps if history is long.
|
|
656
|
+
if (inner.contextPressure && inner.stepCount === 0 && !inner.lastStepHadTools) {
|
|
657
|
+
const compacted = await applyCompaction(messages, model, tracker, cbs, callbacks, { ...compactCfg, force: true }, pruneBudget());
|
|
658
|
+
if (compacted) {
|
|
659
|
+
continues++;
|
|
660
|
+
if (continues > maxContinues) {
|
|
661
|
+
return finish({ aborted: false, maxStepsReached: true, incomplete: true });
|
|
662
|
+
}
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
hasError = true;
|
|
666
|
+
const display = formatErrorMessage(inner.overflowError ?? "");
|
|
667
|
+
if (cbs.onStreamError)
|
|
668
|
+
invokeCallback(cbs.onStreamError, display);
|
|
669
|
+
else
|
|
670
|
+
console.error(`\x1b[31m${display}\x1b[0m`);
|
|
671
|
+
return finish({ aborted: false, maxStepsReached: false });
|
|
672
|
+
}
|
|
634
673
|
// The provider produced nothing at all (no text, no reasoning-backed reply,
|
|
635
674
|
// no tool call): a transport / gateway hiccup rather than model intent.
|
|
636
675
|
// Re-send the same request with backoff instead of nudging the model.
|
|
@@ -795,6 +834,8 @@ class StreamRenderer {
|
|
|
795
834
|
thinkingSplit = new ThinkingBodySplitter();
|
|
796
835
|
xmlSplit = new XmlSearchSplitter();
|
|
797
836
|
rawText = "";
|
|
837
|
+
hadThinking = false;
|
|
838
|
+
thinkingOpen = false;
|
|
798
839
|
constructor(cbs, interactive) {
|
|
799
840
|
this.cbs = cbs;
|
|
800
841
|
this.interactive = interactive;
|
|
@@ -802,10 +843,18 @@ class StreamRenderer {
|
|
|
802
843
|
emitThinking(t) {
|
|
803
844
|
if (!t)
|
|
804
845
|
return;
|
|
805
|
-
|
|
846
|
+
this.hadThinking = true;
|
|
847
|
+
if (this.cbs.onThinkingDelta) {
|
|
806
848
|
invokeCallback(this.cbs.onThinkingDelta, t);
|
|
807
|
-
|
|
808
|
-
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (process.env.MIN_AGENT_SHOW_THINKING === "0" || process.env.MIN_AGENT_SHOW_THINKING === "false")
|
|
852
|
+
return;
|
|
853
|
+
if (!this.thinkingOpen) {
|
|
854
|
+
this.thinkingOpen = true;
|
|
855
|
+
process.stderr.write(`\n${DIM_STYLE.dim}💭 思考${DIM_STYLE.reset}\n`);
|
|
856
|
+
}
|
|
857
|
+
process.stderr.write(`${DIM_STYLE.dim}${t}${DIM_STYLE.reset}`);
|
|
809
858
|
}
|
|
810
859
|
emitDisplay(delta) {
|
|
811
860
|
try {
|
|
@@ -919,6 +968,7 @@ class InnerStreamMachine {
|
|
|
919
968
|
stepFinishSeen = false;
|
|
920
969
|
stepUsage;
|
|
921
970
|
totalUsage;
|
|
971
|
+
overflowError;
|
|
922
972
|
constructor(params) {
|
|
923
973
|
this.params = params;
|
|
924
974
|
this.assembler = new TurnAssembler(params.messages, (info) => {
|
|
@@ -1132,8 +1182,9 @@ class InnerStreamMachine {
|
|
|
1132
1182
|
this.totalUsage = event.totalUsage;
|
|
1133
1183
|
return false;
|
|
1134
1184
|
case "reasoning-delta": {
|
|
1135
|
-
|
|
1136
|
-
|
|
1185
|
+
const text = typeof event.text === "string" ? event.text : "";
|
|
1186
|
+
if (text)
|
|
1187
|
+
this.renderer.feedReasoning(text);
|
|
1137
1188
|
return false;
|
|
1138
1189
|
}
|
|
1139
1190
|
case "text-delta": {
|
|
@@ -1192,6 +1243,7 @@ class InnerStreamMachine {
|
|
|
1192
1243
|
}
|
|
1193
1244
|
if (isContextOverflowError(errorMsg)) {
|
|
1194
1245
|
this.stop.context = true;
|
|
1246
|
+
this.overflowError = errorMsg;
|
|
1195
1247
|
log("warn", `context overflow: ${errorMsg}`);
|
|
1196
1248
|
await this.flushOutputAndStep();
|
|
1197
1249
|
this.innerController.abort();
|
|
@@ -1200,7 +1252,7 @@ class InnerStreamMachine {
|
|
|
1200
1252
|
this.hasError = true;
|
|
1201
1253
|
log("error", `stream error: ${errorMsg}`);
|
|
1202
1254
|
if (cbs.onStreamError)
|
|
1203
|
-
invokeCallback(cbs.onStreamError, errorMsg);
|
|
1255
|
+
invokeCallback(cbs.onStreamError, formatErrorMessage(errorMsg));
|
|
1204
1256
|
else
|
|
1205
1257
|
console.error(`\x1b[31m${formatErrorMessage(errorMsg)}\x1b[0m`);
|
|
1206
1258
|
return false;
|
|
@@ -1222,7 +1274,8 @@ class InnerStreamMachine {
|
|
|
1222
1274
|
wrapUp: this.stop.wrapUp,
|
|
1223
1275
|
lastStepHadTools: this.assembler.lastStepHadTools,
|
|
1224
1276
|
xmlToolFollowUp: this.xmlSearchRecovered && !this.answerAfterXml && !this.stop.doom,
|
|
1225
|
-
emptyCompletion: !this.assembler.hadAssistantText && !this.assembler.hadTools && !this.xmlSearchRecovered,
|
|
1277
|
+
emptyCompletion: !this.stop.context && !this.assembler.hadAssistantText && !this.assembler.hadTools && !this.xmlSearchRecovered,
|
|
1278
|
+
overflowError: this.overflowError,
|
|
1226
1279
|
providerStall: this.stop.stall,
|
|
1227
1280
|
stepCount: this.assembler.innerSteps,
|
|
1228
1281
|
usage,
|
|
@@ -1247,7 +1300,7 @@ class InnerStreamMachine {
|
|
|
1247
1300
|
// The provider went quiet and we cut the request. Nothing was produced →
|
|
1248
1301
|
// let the empty-reply retry path re-send it; otherwise keep the partial
|
|
1249
1302
|
// turn and continue like any other tool-only step.
|
|
1250
|
-
const produced = this.assembler.hadAssistantText || this.assembler.hadTools;
|
|
1303
|
+
const produced = this.assembler.hadAssistantText || this.assembler.hadTools || this.renderer.hadThinking;
|
|
1251
1304
|
log("warn", `provider stall: ${describeError(err)}`);
|
|
1252
1305
|
this.stop.stall = true;
|
|
1253
1306
|
return this.snapshot(usage, { hasError: false, userAborted: false, emptyCompletion: !produced });
|
|
@@ -1259,12 +1312,13 @@ class InnerStreamMachine {
|
|
|
1259
1312
|
log("error", msg);
|
|
1260
1313
|
if (isContextOverflowError(msg)) {
|
|
1261
1314
|
this.stop.context = true;
|
|
1315
|
+
this.overflowError = msg;
|
|
1262
1316
|
return this.snapshot(usage, { hasError: false, contextPressure: true, userAborted: false });
|
|
1263
1317
|
}
|
|
1264
1318
|
this.hasError = true;
|
|
1265
1319
|
const display = formatErrorMessage(msg);
|
|
1266
1320
|
if (this.params.cbs.onStreamError)
|
|
1267
|
-
invokeCallback(this.params.cbs.onStreamError,
|
|
1321
|
+
invokeCallback(this.params.cbs.onStreamError, display);
|
|
1268
1322
|
else if (display !== msg)
|
|
1269
1323
|
console.error(`\x1b[31m${display}\x1b[0m`);
|
|
1270
1324
|
else
|
|
@@ -1277,7 +1331,7 @@ async function runInnerStream(params) {
|
|
|
1277
1331
|
let result;
|
|
1278
1332
|
try {
|
|
1279
1333
|
machine.attachAbort();
|
|
1280
|
-
const prompts = buildIterationPrompts(params.allTools, params.messages, params.taskState, params.loopGuard);
|
|
1334
|
+
const prompts = buildIterationPrompts(params.allTools, params.messages, params.taskState, params.loopGuard, params.memory);
|
|
1281
1335
|
const system = [params.systemPrompt, ...prompts.stable].join("\n\n");
|
|
1282
1336
|
applyToolPrune(params.messages, params.pruneBudget());
|
|
1283
1337
|
// Volatile state rides along as a trailing message so the cached prefix
|
|
@@ -1287,7 +1341,12 @@ async function runInnerStream(params) {
|
|
|
1287
1341
|
params.overhead.tokens =
|
|
1288
1342
|
estimateOverheadTokens(system, params.allTools) +
|
|
1289
1343
|
(stateMessage ? estimateOverheadTokens(String(stateMessage.content)) : 0);
|
|
1290
|
-
|
|
1344
|
+
const thinking = resolveThinkingRequest({
|
|
1345
|
+
runOverride: params.thinking,
|
|
1346
|
+
providerType: params.providerType,
|
|
1347
|
+
modelId: params.modelId,
|
|
1348
|
+
});
|
|
1349
|
+
const startStream = () => streamText({
|
|
1291
1350
|
model: params.model,
|
|
1292
1351
|
system,
|
|
1293
1352
|
messages: requestMessages,
|
|
@@ -1305,6 +1364,7 @@ async function runInnerStream(params) {
|
|
|
1305
1364
|
machine.onStepError(error);
|
|
1306
1365
|
},
|
|
1307
1366
|
});
|
|
1367
|
+
result = thinkingRequestStore.run(thinking, startStream);
|
|
1308
1368
|
for await (const event of result.fullStream) {
|
|
1309
1369
|
if (await machine.handleEvent(event))
|
|
1310
1370
|
break;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { startTuiSession } from "./shared.js";
|
|
2
|
+
import { applySessionOverrides, applySessionRuntimeOverrides } from "../program.js";
|
|
2
3
|
export async function runChat(promptTokens, opts) {
|
|
4
|
+
applySessionOverrides(opts);
|
|
5
|
+
await applySessionRuntimeOverrides(opts);
|
|
3
6
|
await startTuiSession({
|
|
4
7
|
model: opts.model,
|
|
5
8
|
provider: opts.provider,
|
|
@@ -2,10 +2,13 @@ import { runAgent } from "../../agent.js";
|
|
|
2
2
|
import { isConfigured } from "../../config.js";
|
|
3
3
|
import { resolveExecPrompt } from "../exec-prompt.js";
|
|
4
4
|
import { CliError } from "../errors.js";
|
|
5
|
+
import { applySessionOverrides, applySessionRuntimeOverrides } from "../program.js";
|
|
5
6
|
export async function runExec(messageTokens, opts) {
|
|
6
7
|
if (!isConfigured()) {
|
|
7
8
|
throw new CliError("Not configured.", { hint: "Run: min-agent setup" });
|
|
8
9
|
}
|
|
10
|
+
applySessionOverrides(opts);
|
|
11
|
+
await applySessionRuntimeOverrides(opts);
|
|
9
12
|
const stdinIsTty = Boolean(process.stdin.isTTY);
|
|
10
13
|
const joined = messageTokens.join(" ");
|
|
11
14
|
const shouldReadStdin = joined === "-" || !stdinIsTty;
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { Option } from "commander";
|
|
2
2
|
import { resolveScope, parsePositiveIntArg } from "../option-helpers.js";
|
|
3
|
-
import {
|
|
3
|
+
import { registerSetupCommand } from "./setup.js";
|
|
4
4
|
import { runModelsCommand } from "./models.js";
|
|
5
5
|
import { runChat } from "./chat.js";
|
|
6
6
|
import { runExec } from "./exec.js";
|
|
7
7
|
import { runServeCommand } from "./serve.js";
|
|
8
8
|
import { runSandboxCommand } from "./sandbox.js";
|
|
9
9
|
import { runPermissionCommand } from "./permission.js";
|
|
10
|
+
import { runThinkCommand } from "./think.js";
|
|
11
|
+
import { runCtxCommand } from "./ctx.js";
|
|
10
12
|
import { runInitCommand } from "./init.js";
|
|
11
13
|
import { runRulesShow, runRulesEdit } from "./rules.js";
|
|
12
14
|
import { runUpdateCommand } from "./update.js";
|
|
13
15
|
import { runHistoryList, runHistoryDelete, runHistoryRename, runHistoryExport } from "./history.js";
|
|
14
|
-
import { runMemoryList, runMemoryAdd, runMemorySearch, runMemoryDelete } from "./memory.js";
|
|
16
|
+
import { runMemoryList, runMemoryAdd, runMemorySearch, runMemoryDelete, runMemoryMode } from "./memory.js";
|
|
15
17
|
import { runMcpAdd, runMcpRemove, runMcpList, runMcpInfo, runMcpCheck, runMcpToggle, } from "./mcp.js";
|
|
16
18
|
import { runSkillsList, runSkillsInfo, runSkillsToggle, runSkillsNew } from "./skills.js";
|
|
17
19
|
/**
|
|
18
20
|
* All `--project`/`--global`/`--model`/`--provider`/`--resume`/`--image`/`--sandbox`/
|
|
19
|
-
* `--network`/`--permission` flags live on the root `program` (see addSessionOptions),
|
|
21
|
+
* `--network`/`--permission`/`--think`/`--memory` flags live on the root `program` (see addSessionOptions),
|
|
20
22
|
* so they can appear before or after a subcommand name. Subcommands read them via
|
|
21
23
|
* `program.opts<SessionOptions>()` instead of re-declaring the same options — commander
|
|
22
24
|
* resolves options against the first command in the chain that declares them, so a
|
|
@@ -44,7 +46,7 @@ export function registerCommands(program) {
|
|
|
44
46
|
await runExec(message, rootOpts());
|
|
45
47
|
});
|
|
46
48
|
// --- Setup & config ----------------------------------------------------
|
|
47
|
-
program
|
|
49
|
+
registerSetupCommand(program, rootOpts);
|
|
48
50
|
program.command("init").description("Initialize .min-agent/ in current directory").action(runInitCommand);
|
|
49
51
|
program
|
|
50
52
|
.command("models")
|
|
@@ -55,10 +57,18 @@ export function registerCommands(program) {
|
|
|
55
57
|
const rules = program.command("rules").description("Show or edit instruction rules");
|
|
56
58
|
rules.action(runRulesShow);
|
|
57
59
|
rules.command("edit").description("Edit the global rules file").action(runRulesEdit);
|
|
58
|
-
const memory = program.command("memory").description("
|
|
59
|
-
memory.
|
|
60
|
+
const memory = program.command("memory").description("Show or set memory, and manage saved memories");
|
|
61
|
+
memory.argument("[state]", "on or off");
|
|
62
|
+
memory.action(async (state) => {
|
|
60
63
|
const opts = rootOpts();
|
|
61
|
-
|
|
64
|
+
const scope = opts.project ? "project" : opts.global ? "global" : undefined;
|
|
65
|
+
if (await runMemoryMode(state, scope, opts.memory))
|
|
66
|
+
return;
|
|
67
|
+
if (state && state !== "list") {
|
|
68
|
+
const { CliError } = await import("../errors.js");
|
|
69
|
+
throw new CliError("Usage: min-agent memory [on|off|list|add|search|delete] [--project|--global]");
|
|
70
|
+
}
|
|
71
|
+
await runMemoryList(scope);
|
|
62
72
|
});
|
|
63
73
|
memory
|
|
64
74
|
.command("add")
|
|
@@ -108,6 +118,21 @@ export function registerCommands(program) {
|
|
|
108
118
|
const opts = rootOpts();
|
|
109
119
|
await runPermissionCommand(mode ? [mode] : [], opts, opts);
|
|
110
120
|
});
|
|
121
|
+
program
|
|
122
|
+
.command("think")
|
|
123
|
+
.description("Show or set thinking intensity (uses --project/--global)")
|
|
124
|
+
.argument("[level]", "off, low, medium, high, or max")
|
|
125
|
+
.action(async (level) => {
|
|
126
|
+
const opts = rootOpts();
|
|
127
|
+
await runThinkCommand(level ? [level] : [], opts, opts);
|
|
128
|
+
});
|
|
129
|
+
program
|
|
130
|
+
.command("ctx")
|
|
131
|
+
.description("Show or set the Ollama context window level")
|
|
132
|
+
.argument("[level]", "2k, 4k, 8k, 12k, 16k, 32k, 64k, 128k, 256k, or auto")
|
|
133
|
+
.action(async (level) => {
|
|
134
|
+
await runCtxCommand(level ? [level] : []);
|
|
135
|
+
});
|
|
111
136
|
// --- Integrations --------------------------------------------------------
|
|
112
137
|
const mcp = program.command("mcp").description("Manage MCP servers");
|
|
113
138
|
mcp
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { loadMemories, addMemory, deleteMemory, searchMemories, defaultMemoryScope, } from "../../memory.js";
|
|
1
|
+
import { loadMemories, addMemory, deleteMemory, searchMemories, defaultMemoryScope, formatMemoryLine, parseMemoryMode, resolveMemoryMode, memoryModeLabel, memorySourceLabel, isMemoryEnabled, } from "../../memory.js";
|
|
2
|
+
import { runMemoryCli } from "../../memory-cli.js";
|
|
2
3
|
import { CliError } from "../errors.js";
|
|
3
4
|
function printList(which) {
|
|
4
5
|
const memories = loadMemories(which);
|
|
@@ -8,23 +9,42 @@ function printList(which) {
|
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
console.log(`${label} (${memories.length}):`);
|
|
11
|
-
for (
|
|
12
|
-
|
|
13
|
-
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
14
|
-
const date = m.created.split("T")[0];
|
|
15
|
-
console.log(` #${i + 1}: ${m.content}${tags} (${date})`);
|
|
12
|
+
for (const line of memories.map((m, i) => formatMemoryLine(m, i, { date: true }))) {
|
|
13
|
+
console.log(line);
|
|
16
14
|
}
|
|
17
15
|
}
|
|
16
|
+
export async function runMemoryMode(state, scope, flagMode) {
|
|
17
|
+
if (!state && flagMode === undefined)
|
|
18
|
+
return false;
|
|
19
|
+
if (state && state !== "list" && !parseMemoryMode(state))
|
|
20
|
+
return false;
|
|
21
|
+
if (state === "list")
|
|
22
|
+
return false;
|
|
23
|
+
const result = runMemoryCli({
|
|
24
|
+
positionals: state ? [state] : [],
|
|
25
|
+
flagMode,
|
|
26
|
+
scope,
|
|
27
|
+
});
|
|
28
|
+
console.log(result.lines.join("\n"));
|
|
29
|
+
if (!result.ok)
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
18
33
|
export async function runMemoryList(scope) {
|
|
34
|
+
const { memory, source } = resolveMemoryMode();
|
|
35
|
+
console.log(`Current memory: ${memoryModeLabel(memory)} (${memorySourceLabel(source)})`);
|
|
19
36
|
if (!scope && loadMemories("project").length === 0 && loadMemories("global").length === 0) {
|
|
20
37
|
console.log("No memories stored.");
|
|
21
|
-
|
|
38
|
+
if (!isMemoryEnabled())
|
|
39
|
+
console.log("Turn on with: min-agent memory on");
|
|
22
40
|
console.log('Or add manually: min-agent memory add "prefer TypeScript over JavaScript"');
|
|
23
41
|
return;
|
|
24
42
|
}
|
|
25
43
|
const scopes = scope ? [scope] : ["project", "global"];
|
|
26
44
|
for (const s of scopes)
|
|
27
45
|
printList(s);
|
|
46
|
+
if (!isMemoryEnabled())
|
|
47
|
+
console.log("Memories are stored but not used in chat until you run: min-agent memory on");
|
|
28
48
|
}
|
|
29
49
|
export async function runMemoryAdd(textTokens, scope) {
|
|
30
50
|
const text = textTokens.join(" ");
|
|
@@ -32,20 +52,18 @@ export async function runMemoryAdd(textTokens, scope) {
|
|
|
32
52
|
throw new CliError("Usage: min-agent memory add <text> [--project|--global]");
|
|
33
53
|
const resolved = scope ?? defaultMemoryScope();
|
|
34
54
|
addMemory(text, [], resolved);
|
|
35
|
-
|
|
55
|
+
const hint = isMemoryEnabled() ? "" : " (memory is off — turn on with: min-agent memory on)";
|
|
56
|
+
console.log(`✓ ${resolved === "project" ? "Project" : "Global"} memory saved: "${text}"${hint}`);
|
|
36
57
|
}
|
|
37
58
|
export async function runMemorySearch(queryTokens, scope) {
|
|
38
59
|
const query = queryTokens.join(" ");
|
|
39
60
|
if (!query)
|
|
40
61
|
throw new CliError("Usage: min-agent memory search <query> [--project|--global]");
|
|
41
62
|
const scopes = scope ? [scope] : ["project", "global"];
|
|
42
|
-
const lines =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
lines.push(` [${s}] #${m.index + 1}: ${m.content}${tags}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
63
|
+
const lines = scopes.flatMap((s) => searchMemories(query, s).map((m) => {
|
|
64
|
+
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
65
|
+
return ` [${s}] #${m.index + 1}: ${m.content}${tags}`;
|
|
66
|
+
}));
|
|
49
67
|
if (lines.length === 0) {
|
|
50
68
|
console.log(`No memories matching "${query}"`);
|
|
51
69
|
}
|
|
@@ -1,4 +1,56 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { Option } from "commander";
|
|
3
|
+
import { fetchModelsLive, getConfigDir, loadConfig, saveConfig } from "../../config.js";
|
|
4
|
+
import { parsePositiveIntArg } from "../option-helpers.js";
|
|
5
|
+
import { parseProviderType } from "../setup/flags.js";
|
|
6
|
+
import { runSetup } from "../setup/flow.js";
|
|
7
|
+
export function registerSetupCommand(program, rootOpts) {
|
|
8
|
+
program
|
|
9
|
+
.command("setup")
|
|
10
|
+
.description("Configure providers (interactively, or with flags)")
|
|
11
|
+
.addOption(new Option("--type <type>", "openai-compatible, openai, or ollama").argParser(parseProviderType))
|
|
12
|
+
.option("--url <url>", "API base URL")
|
|
13
|
+
.option("--api-key <key>", "API key")
|
|
14
|
+
.option("--name <name>", "Provider name")
|
|
15
|
+
.option("--default-model <id>", "Default model to save")
|
|
16
|
+
.addOption(new Option("--context-window <n>", "Context window in tokens").argParser(parsePositiveIntArg("--context-window")))
|
|
17
|
+
.option("--switch <name>", "Set the active provider")
|
|
18
|
+
.option("--remove <name>", "Remove a provider")
|
|
19
|
+
.option("-y, --yes", "Skip overwrite and delete confirmation")
|
|
20
|
+
.action(async (opts) => {
|
|
21
|
+
await runSetupCommand(opts, { yes: Boolean(opts.yes || rootOpts().yes) });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function createSetupDeps() {
|
|
25
|
+
return {
|
|
26
|
+
isTty: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
27
|
+
load: loadConfig,
|
|
28
|
+
save: saveConfig,
|
|
29
|
+
fetchLive: fetchModelsLive,
|
|
30
|
+
log: (message) => {
|
|
31
|
+
console.log(message);
|
|
32
|
+
},
|
|
33
|
+
configPath: () => path.join(getConfigDir(), "config.json"),
|
|
34
|
+
runWizard: async (wizardMode) => {
|
|
35
|
+
const { renderSetupWizard } = await import("../setup/ui.js");
|
|
36
|
+
return renderSetupWizard(wizardMode);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export async function offerInteractiveSetup(mode) {
|
|
41
|
+
await runSetup({}, { ...createSetupDeps(), isTty: true }, mode);
|
|
42
|
+
}
|
|
43
|
+
export async function runSetupCommand(opts, session) {
|
|
44
|
+
const flags = {
|
|
45
|
+
type: opts.type,
|
|
46
|
+
url: opts.url,
|
|
47
|
+
apiKey: opts.apiKey,
|
|
48
|
+
name: opts.name,
|
|
49
|
+
defaultModel: opts.defaultModel,
|
|
50
|
+
contextWindow: opts.contextWindow,
|
|
51
|
+
switch: opts.switch,
|
|
52
|
+
remove: opts.remove,
|
|
53
|
+
yes: Boolean(session.yes || opts.yes),
|
|
54
|
+
};
|
|
55
|
+
await runSetup(flags, createSetupDeps());
|
|
4
56
|
}
|