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/dist/tui-chat.js CHANGED
@@ -9,7 +9,7 @@ import { loadImageParts } from "./agent.js";
9
9
  import { createUndoStack, clearStack } from "./tui/undo-stack.js";
10
10
  import { killActiveProcesses } from "./tools/bash.js";
11
11
  import { setTuiConfirm, setTuiQuestion } from "./confirm.js";
12
- import { loadConfig, getActiveProvider } from "./config.js";
12
+ import { loadConfig, getActiveProvider, getEffectiveConfig } from "./config.js";
13
13
  import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
14
14
  import { handleSlashCommand, sysMsg } from "./tui/slash-handler.js";
15
15
  import { createAgentRunner } from "./tui/agent-runner.js";
@@ -17,6 +17,8 @@ import { listSessions, loadSession, saveSession } from "./sessions.js";
17
17
  import { hydrateMessages, lastUserText } from "./tui/hydrate.js";
18
18
  import { createPromptQueue } from "./tui/prompt-queue.js";
19
19
  import { prepareSwitch, switchFailMessage, prepareNew, newFailMessage } from "./tui/session-switch.js";
20
+ import { setThinkingEffort, setThinkingOverride, thinkingChoicesForModel, thinkingEffortLabel, refreshThinkingChoices, } from "./thinking.js";
21
+ import { CTX_CHOICES, ctxChoiceLabel, setOllamaContextChoice } from "./ctx.js";
20
22
  let confirmResolver = null;
21
23
  let questionResolver = null;
22
24
  /** Only one confirm/question overlay can be shown at a time; parallel tool calls queue up. */
@@ -26,6 +28,48 @@ function registerSessionCompletion() {
26
28
  setSlashArgProvider("sessions", provider);
27
29
  setSlashArgProvider("resume", provider);
28
30
  }
31
+ function registerCtxCompletion() {
32
+ setSlashArgProvider("ctx", (tokens) => {
33
+ if (tokens.length > 0)
34
+ return [];
35
+ return CTX_CHOICES.map((value) => (value === "auto" ? { value, description: "按模型探测" } : { value }));
36
+ });
37
+ }
38
+ function registerThinkCompletion() {
39
+ const provider = (tokens) => {
40
+ const flags = [
41
+ { value: "--project", description: "写入当前项目" },
42
+ { value: "--global", description: "写入全局配置" },
43
+ ];
44
+ const used = new Set(tokens);
45
+ const remainingFlags = flags.filter((f) => !used.has(f.value));
46
+ if (tokens.length === 0) {
47
+ const model = getActiveProvider(getEffectiveConfig())?.defaultModel;
48
+ return [...thinkingChoicesForModel(model).map((value) => ({ value })), ...remainingFlags];
49
+ }
50
+ return remainingFlags;
51
+ };
52
+ setSlashArgProvider("think", provider);
53
+ setSlashArgProvider("thinking", provider);
54
+ }
55
+ function registerMemoryCompletion() {
56
+ setSlashArgProvider("memory", (tokens) => {
57
+ const flags = [
58
+ { value: "--project", description: "写入当前项目" },
59
+ { value: "--global", description: "写入全局配置" },
60
+ ];
61
+ const used = new Set(tokens);
62
+ const remainingFlags = flags.filter((f) => !used.has(f.value));
63
+ if (tokens.length === 0) {
64
+ return [
65
+ { value: "on", description: "开启记忆(跨会话保留)" },
66
+ { value: "off", description: "关闭记忆(默认)" },
67
+ ...remainingFlags,
68
+ ];
69
+ }
70
+ return remainingFlags;
71
+ });
72
+ }
29
73
  function registerPermissionCompletion() {
30
74
  setSlashArgProvider("permission", (tokens) => {
31
75
  const flags = [
@@ -106,6 +150,9 @@ export async function runTui(opts) {
106
150
  discoverSkills();
107
151
  registerSkillCompletion();
108
152
  registerPermissionCompletion();
153
+ registerThinkCompletion();
154
+ registerCtxCompletion();
155
+ registerMemoryCompletion();
109
156
  registerSandboxCompletion();
110
157
  registerSessionCompletion();
111
158
  const instructions = await loadInstructions();
@@ -116,6 +163,8 @@ export async function runTui(opts) {
116
163
  let sessionId = resumeSessionId;
117
164
  let currentModel = modelId ?? getActiveProvider(config)?.defaultModel;
118
165
  const currentProvider = providerName;
166
+ if (currentModel)
167
+ void refreshThinkingChoices(currentModel);
119
168
  const tracker = new TokenTracker();
120
169
  const taskState = emptyTaskState();
121
170
  let undoStack = createUndoStack();
@@ -131,11 +180,13 @@ export async function runTui(opts) {
131
180
  }
132
181
  let runner = null;
133
182
  const tui = new TuiRenderer({
134
- onSubmit: async (text) => {
135
- if (text.startsWith("/")) {
183
+ onSubmit: async (prompt) => {
184
+ const text = prompt.display;
185
+ if (text.trim().startsWith("/")) {
136
186
  const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => {
137
187
  currentModel = model;
138
188
  tui.setModel(model);
189
+ void refreshTokenBar();
139
190
  }, requestExit, {
140
191
  undoStack,
141
192
  setUndoStack: (s) => {
@@ -162,6 +213,7 @@ export async function runTui(opts) {
162
213
  startNewSession,
163
214
  openSessionPicker,
164
215
  taskState,
216
+ refreshTokenBar,
165
217
  });
166
218
  if (result === "run" && runner) {
167
219
  await runner.run();
@@ -171,12 +223,15 @@ export async function runTui(opts) {
171
223
  }
172
224
  lastUserMessage = text;
173
225
  tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
174
- messages.push({ role: "user", content: text });
226
+ messages.push({ role: "user", content: prompt.content });
175
227
  if (runner) {
176
228
  await runner.run();
177
229
  persistSession();
178
230
  }
179
231
  },
232
+ onNotice: (notice) => {
233
+ sysMsg(tui, notice);
234
+ },
180
235
  onConfirm: (accepted) => {
181
236
  tui.hideConfirm();
182
237
  if (confirmResolver) {
@@ -201,11 +256,34 @@ export async function runTui(opts) {
201
256
  }
202
257
  currentModel = model;
203
258
  tui.setModel(model);
259
+ void refreshTokenBar();
204
260
  sysMsg(tui, `✓ 默认模型已设为: ${model}`);
205
261
  },
206
262
  onModelCancel: () => {
207
263
  tui.hideModelPicker();
208
264
  },
265
+ onThinkPick: (effort, scope) => {
266
+ setThinkingEffort(effort, scope);
267
+ setThinkingOverride(effort);
268
+ tui.hideThinkPicker();
269
+ sysMsg(tui, `✓ 思考强度已设为 ${thinkingEffortLabel(effort)}(${scope === "project" ? "项目" : "全局"})`);
270
+ },
271
+ onThinkCancel: () => {
272
+ tui.hideThinkPicker();
273
+ },
274
+ onCtxPick: (choice) => {
275
+ const result = setOllamaContextChoice(choice);
276
+ tui.hideCtxPicker();
277
+ if (!result.ok) {
278
+ sysMsg(tui, "窗口档位仅本地模型可用");
279
+ return;
280
+ }
281
+ void refreshTokenBar();
282
+ sysMsg(tui, `✓ 上下文窗口已设为${ctxChoiceLabel(choice)}`);
283
+ },
284
+ onCtxCancel: () => {
285
+ tui.hideCtxPicker();
286
+ },
209
287
  onSessionPick: (id) => {
210
288
  tui.hideSessionPicker();
211
289
  applySession(id);
@@ -299,7 +377,8 @@ export async function runTui(opts) {
299
377
  tracker.reset();
300
378
  lastUserMessage = null;
301
379
  tui.setSessionId(undefined);
302
- tui.update({ messages: [], tokenInfo: undefined });
380
+ tui.update({ messages: [] });
381
+ void refreshTokenBar();
303
382
  sysMsg(tui, "✓ 已新建会话");
304
383
  }
305
384
  function openSessionPicker() {
@@ -347,8 +426,7 @@ export async function runTui(opts) {
347
426
  tui.start();
348
427
  if (messages.length > 0)
349
428
  tui.update({ messages: hydrateMessages(messages) });
350
- if (messages.length > 0)
351
- await refreshTokenBar();
429
+ await refreshTokenBar();
352
430
  const prompt = initialPrompt?.trim() ?? "";
353
431
  const notify = (kind, imgPath) => {
354
432
  if (kind === "not_found")
package/docs/API.md CHANGED
@@ -33,6 +33,7 @@ min-agent serve --host 0.0.0.0 --port 3000
33
33
  | GET | `/v1/update` | 检查 npm 是否有新版本(不执行安装) |
34
34
  | GET | `/v1/models` | 模型列表 |
35
35
  | GET | `/v1/context` | 上下文窗口信息 |
36
+ | POST | `/v1/context` | 设置本地模型上下文档位 |
36
37
  | GET | `/v1/project` | 项目扫描 |
37
38
  | POST | `/v1/chat` | 对话(项目感知) |
38
39
  | POST | `/v1/code` | 与 `/v1/chat` 相同(兼容路径) |
@@ -42,14 +43,18 @@ min-agent serve --host 0.0.0.0 --port 3000
42
43
  | POST | `/v1/chat/redo` | 重发会话最后一条用户消息 |
43
44
  | GET | `/v1/sessions` | 列出会话 |
44
45
  | DELETE | `/v1/sessions/:id` | 删除会话 |
45
- | GET | `/v1/memory` | 列出记忆(含 `project_memories`) |
46
+ | GET | `/v1/memory` | 列出记忆(含开关状态与 `project_memories`) |
46
47
  | POST | `/v1/memory` | 添加记忆 |
47
48
  | GET | `/v1/memory/search?q=xxx` | 搜索记忆 |
48
49
  | DELETE | `/v1/memory/:index` | 删除记忆 |
50
+ | GET | `/v1/memory/mode` | 当前记忆开关 |
51
+ | POST | `/v1/memory/mode` | 设置记忆开关 |
49
52
  | GET | `/v1/budget` | 当前预算上限 |
50
53
  | POST | `/v1/budget` | 设置预算上限 |
51
54
  | GET | `/v1/permission` | 当前确认模式 |
52
55
  | POST | `/v1/permission` | 设置确认模式 |
56
+ | GET | `/v1/thinking` | 当前思考强度 |
57
+ | POST | `/v1/thinking` | 设置思考强度 |
53
58
  | GET | `/v1/sandbox` | 当前隔离策略 |
54
59
  | POST | `/v1/sandbox` | 设置隔离策略 |
55
60
  | GET | `/v1/diff` | 工作区未提交变更 |
@@ -86,7 +91,7 @@ min-agent serve --host 0.0.0.0 --port 3000
86
91
  ## `GET /v1/meta`
87
92
 
88
93
  ```json
89
- { "version": "0.1.0", "cwd": "/path/to/project", "instructions_chars": 1234, "sandbox": { "mode": "off", "network": "allow", "extraWriteRoots": [], "extraReadRoots": [], "source": null, "label": "未隔离,允许联网" }, "permission": { "permission": "ask", "source": null, "label": "询问确认" } }
94
+ { "version": "0.1.0", "cwd": "/path/to/project", "instructions_chars": 1234, "sandbox": { "mode": "off", "network": "allow", "extraWriteRoots": [], "extraReadRoots": [], "source": null, "label": "未隔离,允许联网" }, "permission": { "permission": "ask", "source": null, "label": "询问确认" }, "thinking": { "thinking": "medium", "source": null, "label": "medium" }, "memory": { "memory": "off", "source": null, "label": "off" } }
90
95
  ```
91
96
 
92
97
  ## `GET /v1/update`
@@ -114,10 +119,26 @@ CLI 升级请使用 `min-agent update`(执行 `npm install -g min-agent`)。
114
119
  ## `GET /v1/context`
115
120
 
116
121
  ```json
117
- { "context_window": 1000000, "source": "config", "model": "gpt-4o" }
122
+ { "context_window": 1000000, "source": "config", "model": "gpt-4o", "configurable": false, "level": null }
118
123
  ```
119
124
 
120
- `source` 为 `config`(配置中的 `contextWindow`)、`detected`(从接口读到)或 `fallback`(未读到,按 512k 处理)。
125
+ `source` 为 `config`(配置中的 `contextWindow`)、`detected`(从接口读到)或 `fallback`(未读到:Ollama 按 2048,其余按 512k)。Ollama 的 detected 值是运行时窗口(优先 Modelfile `num_ctx`,否则不超过 32768),不是架构标称上限。对话请求走原生 `/api/chat` 并带上该窗口;只改 OpenAI 兼容接口的 `num_ctx` 不会生效。
126
+
127
+ 当前为 Ollama 时额外返回 `configurable: true` 与 `level`(`2k`…`256k` 或 `auto`)。其它 provider 为 `configurable: false`、`level: null`。
128
+
129
+ ## `POST /v1/context`
130
+
131
+ 仅 Ollama provider。将当前 provider 的 `contextWindow` 写成对应档位;`auto` 删除覆盖,回到探测值。
132
+
133
+ ```json
134
+ { "level": "8k" }
135
+ ```
136
+
137
+ ```json
138
+ { "ok": true, "context_window": 8192, "source": "config", "model": "qwen3:4b", "configurable": true, "level": "8k" }
139
+ ```
140
+
141
+ 非法档位返回 `400 invalid_context_level`。当前不是 Ollama 时返回 `400 ctx_not_supported`。
121
142
 
122
143
  ## `GET /v1/project`
123
144
 
@@ -157,10 +178,12 @@ CLI 升级请使用 `min-agent update`(执行 `npm install -g min-agent`)。
157
178
  | `auto_approve` | boolean | 默认 `true`。设为 `false` 时必须 `stream: true`,并通过 `POST /v1/approvals` / `POST /v1/answers` 回调 |
158
179
  | `sandbox` | string | 本轮隔离:`off` / `workspace` / `strict`。只能比服务当前策略更严,不能放宽 |
159
180
  | `network` | string | 本轮联网:`allow` / `deny`。`deny` 只能收紧 |
181
+ | `thinking` | string | 本轮思考强度:`off` / `low` / `medium` / `high` / `max`。不传则用配置,未配置时为 `medium` |
182
+ | `memory` | string | 本轮是否使用记忆:`on` / `off`。不传则用配置,未配置时为 `off` |
160
183
 
161
184
  `auto_approve: false` 且未开流式时返回 `400 auto_approve_requires_stream`。确认/提问最多等待 5 分钟,超时视为拒绝或跳过。客户端断开连接时未完成的确认视为拒绝。
162
185
 
163
- `max_steps_reached` 为 `true` 表示本轮已用尽自动续跑次数(上下文压缩/空回复等),或显式设置了 `agent.maxSteps` 且已到达该上限。默认不按固定步数切断一轮生成。`incomplete` 为 `true` 表示模型在调用工具后没有给出完整回复,或连续多次空回复。`stopped` 为 `true` 表示因同一操作反复执行而主动结束本轮。`empty_response` 为 `true` 表示 provider 连续返回空流(HTTP 200 但没有任何内容,多为上游超时/限流),`empty_attempts` 为已尝试次数;这类空回复会以原请求重发重试(首次立即,之后指数退避),可用 `agent.maxEmptyAttempts`(默认 4)与 `agent.emptyRetryDelayMs`(默认 1000)调整。发送下一条消息即可继续。`continues` 为本轮实际自动续跑次数。`task_state` 为当前任务目标与待办列表,会写入会话并在压缩后保留。网页检索连续过久、或检索后尚未写出用户要求的结果时,代理会改为基于已有资料产出结果;本轮内再调用搜索/抓取会收到停止检索并直接产出的提示,而不是新的检索结果(即使 `autoContinue` 为 `false`)。
186
+ `max_steps_reached` 为 `true` 表示本轮已用尽自动续跑次数(上下文压缩/空回复等),或显式设置了 `agent.maxSteps` 且已到达该上限。默认不按固定步数切断一轮生成。`incomplete` 为 `true` 表示模型在调用工具后没有给出完整回复,或连续多次空回复。`stopped` 为 `true` 表示因同一操作反复执行而主动结束本轮。`empty_response` 为 `true` 表示 provider 连续返回空流(HTTP 200 但没有任何内容,多为上游超时/限流),`empty_attempts` 为已尝试次数;这类空回复会以原请求重发重试(首次立即,之后指数退避),可用 `agent.maxEmptyAttempts`(默认 4)与 `agent.emptyRetryDelayMs`(默认 1000)调整。发送下一条消息即可继续。`continues` 为本轮实际自动续跑次数。`task_state` 为当前任务目标与待办列表,会写入会话并在压缩后保留;纯问候不会记为任务目标。网页检索连续过久、或检索后尚未写出用户要求的结果时,代理会改为基于已有资料产出结果;本轮内再调用搜索/抓取会收到停止检索并直接产出的提示,而不是新的检索结果(即使 `autoContinue` 为 `false`)。
164
187
 
165
188
  ### 非流式响应
166
189
 
@@ -331,6 +354,25 @@ CLI 升级请使用 `min-agent update`(执行 `npm install -g min-agent`)。
331
354
 
332
355
  `scope` 省略时写入全局。`permission` 必须为 `ask` / `accept-edits` / `allow-all`。
333
356
 
357
+ ## `GET /v1/thinking`
358
+
359
+ ```json
360
+ { "thinking": "max", "source": "global", "label": "max" }
361
+ ```
362
+
363
+ `thinking` 为 `off` / `low` / `medium` / `high` / `max`。未配置时为 `medium`,`source` 为 `null`。已配置时 `source` 为 `cli`、`project` 或 `global`。
364
+
365
+ ## `POST /v1/thinking`
366
+
367
+ ```json
368
+ { "thinking": "max", "scope": "project" }
369
+ ```
370
+ ```json
371
+ { "ok": true, "scope": "project", "thinking": "max", "source": "project", "label": "max" }
372
+ ```
373
+
374
+ `scope` 省略时写入全局,并清除项目配置里的 `thinking` 覆盖,避免旧的项目值(例如 `off`)在重启后继续生效。`thinking` 必须为 `off` / `low` / `medium` / `high` / `max`(`none` → `off`,`xhigh` / `extra-high` → `max`)。
375
+
334
376
  ## `GET /v1/sandbox`
335
377
 
336
378
  ```json
@@ -488,12 +530,33 @@ CLI 升级请使用 `min-agent update`(执行 `npm install -g min-agent`)。
488
530
 
489
531
  ```json
490
532
  {
533
+ "memory": "off",
534
+ "source": null,
535
+ "label": "off",
491
536
  "memories": [{ "content": "...", "tags": [...], "created": "..." }],
492
537
  "project_memories": [{ "content": "...", "tags": [...], "created": "..." }]
493
538
  }
494
539
  ```
495
540
 
496
- `memories` 始终为全局;`project_memories` 为当前目录 `.min-agent/memory.json`。
541
+ `memories` 始终为全局;`project_memories` 为当前目录 `.min-agent/memory.json`。`memory` 为 `on` / `off`。未配置时为 `off`,`source` 为 `null`。已配置时 `source` 为 `cli`、`project` 或 `global`。关闭时仍可读写条目,但对话不会注入记忆,也不会暴露 memory 工具。
542
+
543
+ ## `GET /v1/memory/mode`
544
+
545
+ ```json
546
+ { "memory": "off", "source": null, "label": "off" }
547
+ ```
548
+
549
+ ## `POST /v1/memory/mode`
550
+
551
+ ```json
552
+ { "memory": "on", "scope": "project" }
553
+ ```
554
+
555
+ ```json
556
+ { "ok": true, "scope": "project", "memory": "on", "source": "project", "label": "on" }
557
+ ```
558
+
559
+ `scope` 省略时写入全局,并清除项目配置里的 `memory` 覆盖。`memory` 必须为 `on` / `off`(`true` / `enable` → `on`,`false` / `disable` → `off`)。
497
560
 
498
561
  ## `POST /v1/memory`
499
562