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.
Files changed (69) hide show
  1. package/README.md +46 -2
  2. package/dist/agent.js +89 -29
  3. package/dist/cli/commands/chat.js +3 -0
  4. package/dist/cli/commands/ctx.js +7 -0
  5. package/dist/cli/commands/exec.js +3 -0
  6. package/dist/cli/commands/index.js +32 -7
  7. package/dist/cli/commands/memory.js +33 -15
  8. package/dist/cli/commands/setup.js +55 -3
  9. package/dist/cli/commands/shared.js +10 -1
  10. package/dist/cli/commands/think.js +12 -0
  11. package/dist/cli/commands/write-config.js +22 -0
  12. package/dist/cli/option-helpers.js +13 -1
  13. package/dist/cli/program.js +57 -14
  14. package/dist/cli/setup/detect.js +17 -0
  15. package/dist/cli/setup/flags.js +12 -0
  16. package/dist/cli/setup/flow.js +108 -0
  17. package/dist/cli/setup/provider-form.js +102 -0
  18. package/dist/cli/setup/ui.js +534 -0
  19. package/dist/code-mode.js +1 -1
  20. package/dist/config.js +93 -159
  21. package/dist/context-window.js +39 -49
  22. package/dist/ctx-cli.js +30 -0
  23. package/dist/ctx.js +80 -0
  24. package/dist/memory-cli.js +33 -0
  25. package/dist/memory.js +127 -46
  26. package/dist/model-catalog.js +285 -0
  27. package/dist/ollama-model.js +234 -0
  28. package/dist/ollama-openai-bridge.js +383 -0
  29. package/dist/permission-cli.js +1 -4
  30. package/dist/provider.js +4 -1
  31. package/dist/reasoning-stream.js +158 -0
  32. package/dist/sandbox-cli.js +1 -4
  33. package/dist/scope.js +23 -0
  34. package/dist/serve/common.js +22 -1
  35. package/dist/serve/routes-chat.js +21 -1
  36. package/dist/serve/routes-memory.js +31 -2
  37. package/dist/serve/routes-meta.js +69 -6
  38. package/dist/think-cli.js +36 -0
  39. package/dist/thinking-wire.js +239 -0
  40. package/dist/thinking.js +166 -0
  41. package/dist/token-display.js +10 -7
  42. package/dist/tools/todo.js +22 -8
  43. package/dist/tui/App.js +48 -8
  44. package/dist/tui/CtxPicker.js +68 -0
  45. package/dist/tui/InputBar.js +112 -37
  46. package/dist/tui/MessageList.js +53 -22
  47. package/dist/tui/StatusBar.js +7 -3
  48. package/dist/tui/ThinkPicker.js +75 -0
  49. package/dist/tui/bracketed-paste.js +37 -0
  50. package/dist/tui/caret-pos.js +10 -8
  51. package/dist/tui/index.js +13 -1
  52. package/dist/tui/layout.js +17 -0
  53. package/dist/tui/overlay-input.js +12 -0
  54. package/dist/tui/paste-draft.js +173 -0
  55. package/dist/tui/selection.js +8 -2
  56. package/dist/tui/slash-commands.js +24 -1
  57. package/dist/tui/slash-handler.js +88 -18
  58. package/dist/tui/text-width.js +6 -6
  59. package/dist/tui-chat.js +85 -7
  60. package/docs/API.md +69 -6
  61. package/docs/superpowers/plans/2026-08-23-cli-setup.md +501 -0
  62. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +475 -0
  63. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +450 -0
  64. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +282 -0
  65. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +174 -0
  66. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +140 -0
  67. package/package.json +1 -1
  68. package/skills/self-config/SKILL.md +7 -4
  69. 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 { getMemorySystemPrompt, getMemoryTools } from "./memory.js";
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 (current, not part of the conversation)";
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") || msg.includes("ECONNRESET")) {
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
- if (this.cbs.onThinkingDelta)
846
+ this.hadThinking = true;
847
+ if (this.cbs.onThinkingDelta) {
806
848
  invokeCallback(this.cbs.onThinkingDelta, t);
807
- else
808
- writeThinkingDelta(t);
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
- if (event.text)
1136
- this.renderer.feedReasoning(event.text);
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, msg);
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
- result = streamText({
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,
@@ -0,0 +1,7 @@
1
+ import { runCtxCli } from "../../ctx-cli.js";
2
+ export async function runCtxCommand(args) {
3
+ const result = runCtxCli({ positionals: args });
4
+ console.log(result.lines.join("\n"));
5
+ if (!result.ok)
6
+ process.exitCode = 1;
7
+ }
@@ -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 { runSetupCommand } from "./setup.js";
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.command("setup").description("Configure providers (interactively)").action(runSetupCommand);
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("Manage saved memories (uses --project/--global)");
59
- memory.action(async () => {
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
- await runMemoryList(opts.project ? "project" : opts.global ? "global" : undefined);
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 (let i = 0; i < memories.length; i++) {
12
- const m = memories[i];
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
- console.log("The agent will automatically save memories during conversations.");
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
- console.log(`✓ ${resolved === "project" ? "Project" : "Global"} memory saved: "${text}"`);
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
- for (const s of scopes) {
44
- for (const m of searchMemories(query, s)) {
45
- const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
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 { runSetup } from "../../config.js";
2
- export async function runSetupCommand() {
3
- await runSetup();
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
  }