min-agent 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +111 -28
  2. package/dist/agent.js +1119 -256
  3. package/dist/cli/commands/chat.js +10 -0
  4. package/dist/cli/commands/exec.js +32 -0
  5. package/dist/cli/commands/history.js +58 -0
  6. package/dist/cli/commands/index.js +224 -0
  7. package/dist/cli/commands/init.js +18 -0
  8. package/dist/cli/commands/mcp.js +173 -0
  9. package/dist/cli/commands/memory.js +69 -0
  10. package/dist/cli/commands/models.js +21 -0
  11. package/dist/cli/commands/permission.js +12 -0
  12. package/dist/cli/commands/rules.js +33 -0
  13. package/dist/cli/commands/sandbox.js +13 -0
  14. package/dist/cli/commands/serve.js +9 -0
  15. package/dist/cli/commands/setup.js +4 -0
  16. package/dist/cli/commands/shared.js +16 -0
  17. package/dist/cli/commands/skills.js +119 -0
  18. package/dist/cli/commands/update.js +7 -0
  19. package/dist/cli/commands/write-config.js +30 -0
  20. package/dist/cli/errors.js +36 -0
  21. package/dist/cli/exec-prompt.js +26 -0
  22. package/dist/cli/option-helpers.js +53 -0
  23. package/dist/cli/program.js +180 -0
  24. package/dist/cli.js +5 -888
  25. package/dist/code-mode.js +32 -14
  26. package/dist/compaction.js +347 -160
  27. package/dist/config.js +119 -10
  28. package/dist/confirm.js +56 -9
  29. package/dist/context-window.js +107 -39
  30. package/dist/doom-loop.js +264 -29
  31. package/dist/fetch-timeout.js +152 -0
  32. package/dist/http-approvals.js +60 -0
  33. package/dist/instructions.js +21 -0
  34. package/dist/logger.js +33 -4
  35. package/dist/markdown.js +37 -11
  36. package/dist/mcp.js +328 -30
  37. package/dist/memory.js +97 -56
  38. package/dist/output.js +7 -5
  39. package/dist/permission-cli.js +43 -0
  40. package/dist/plugins.js +46 -8
  41. package/dist/pricing.js +4 -4
  42. package/dist/provider.js +23 -6
  43. package/dist/question-format.js +60 -0
  44. package/dist/sandbox-cli.js +82 -0
  45. package/dist/sandbox.js +403 -0
  46. package/dist/save-throttle.js +45 -0
  47. package/dist/serve/common.js +404 -0
  48. package/dist/serve/routes-chat.js +347 -0
  49. package/dist/serve/routes-mcp.js +212 -0
  50. package/dist/serve/routes-memory.js +66 -0
  51. package/dist/serve/routes-meta.js +205 -0
  52. package/dist/serve/routes-sessions.js +61 -0
  53. package/dist/serve/routes-skills.js +70 -0
  54. package/dist/serve.js +33 -883
  55. package/dist/sessions.js +53 -9
  56. package/dist/skills.js +82 -18
  57. package/dist/title-gen.js +8 -2
  58. package/dist/token-display.js +36 -0
  59. package/dist/tool-display.js +5 -0
  60. package/dist/tool-output.js +1 -3
  61. package/dist/tools/apply_patch.js +85 -11
  62. package/dist/tools/atomic-file.js +35 -0
  63. package/dist/tools/backend.js +2 -2
  64. package/dist/tools/bash.js +57 -19
  65. package/dist/tools/code_search.js +7 -1
  66. package/dist/tools/edit.js +11 -10
  67. package/dist/tools/explore.js +74 -14
  68. package/dist/tools/glob.js +4 -0
  69. package/dist/tools/grep.js +17 -10
  70. package/dist/tools/index.js +6 -21
  71. package/dist/tools/question.js +28 -9
  72. package/dist/tools/read.js +6 -4
  73. package/dist/tools/search-searxng.js +223 -0
  74. package/dist/tools/search-serper.js +189 -0
  75. package/dist/tools/task.js +84 -30
  76. package/dist/tools/todo.js +120 -19
  77. package/dist/tools/web_fetch.js +11 -3
  78. package/dist/tools/web_search.js +66 -556
  79. package/dist/tools/write.js +23 -6
  80. package/dist/tui/App.js +63 -14
  81. package/dist/tui/ConfirmBar.js +45 -13
  82. package/dist/tui/InputBar.js +150 -35
  83. package/dist/tui/MessageList.js +266 -125
  84. package/dist/tui/ModelPicker.js +8 -3
  85. package/dist/tui/QuestionBar.js +51 -19
  86. package/dist/tui/SessionPicker.js +79 -0
  87. package/dist/tui/StatusBar.js +8 -14
  88. package/dist/tui/agent-runner.js +142 -22
  89. package/dist/tui/caret-pos.js +48 -5
  90. package/dist/tui/caret.js +1 -1
  91. package/dist/tui/click-count.js +13 -0
  92. package/dist/tui/drag-state.js +8 -3
  93. package/dist/tui/hydrate.js +129 -0
  94. package/dist/tui/index.js +42 -13
  95. package/dist/tui/input-history.js +92 -11
  96. package/dist/tui/layout.js +75 -4
  97. package/dist/tui/prompt-queue.js +24 -0
  98. package/dist/tui/selection.js +113 -21
  99. package/dist/tui/session-switch.js +28 -0
  100. package/dist/tui/slash-commands.js +22 -6
  101. package/dist/tui/slash-handler.js +233 -58
  102. package/dist/tui/text-width.js +38 -16
  103. package/dist/tui/token-info.js +7 -0
  104. package/dist/tui/tool-children.js +19 -0
  105. package/dist/tui/undo-stack.js +1 -1
  106. package/dist/tui/use-sgr-mouse.js +3 -1
  107. package/dist/tui-chat.js +276 -40
  108. package/dist/updater.js +88 -29
  109. package/dist/xml-search.js +194 -0
  110. package/docs/API.md +257 -25
  111. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +873 -0
  112. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +631 -0
  113. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +47 -0
  114. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +37 -0
  115. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +68 -0
  116. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +273 -0
  117. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +165 -0
  118. package/package.json +6 -1
  119. package/skills/self-config/SKILL.md +90 -0
  120. package/skills/self-config/reference.md +149 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Some OpenAI-compatible gateways (and a few models) dump built-in web search
3
+ * into the assistant text as <web_search>…</web_search> instead of a tool call,
4
+ * then stop. This module splits those blocks out of the stream so the agent can
5
+ * turn them into search_web tool results and keep going.
6
+ */
7
+ const OPEN_NAME = "<web_search";
8
+ const CLOSE_NAME = "</web_search";
9
+ const PARTIAL_TAG_HOLD = 80;
10
+ const UNCLOSED_MAX = 64 * 1024;
11
+ function collapse(s) {
12
+ return s.replace(/\s+/g, " ").trim();
13
+ }
14
+ function isNameBoundary(ch) {
15
+ return ch === undefined || ch === ">" || ch === "/" || ch === " " || ch === "\t" || ch === "\n" || ch === "\r";
16
+ }
17
+ function findOpen(buf) {
18
+ const l = buf.toLowerCase();
19
+ let from = 0;
20
+ while (from < l.length) {
21
+ const index = l.indexOf(OPEN_NAME, from);
22
+ if (index < 0)
23
+ return null;
24
+ if (!isNameBoundary(l[index + OPEN_NAME.length])) {
25
+ from = index + 1;
26
+ continue;
27
+ }
28
+ const gt = buf.indexOf(">", index);
29
+ if (gt < 0)
30
+ return null;
31
+ return { index, openLen: gt - index + 1, raw: buf.slice(index, gt + 1) };
32
+ }
33
+ return null;
34
+ }
35
+ function findClose(buf, from) {
36
+ const l = buf.toLowerCase();
37
+ let start = from;
38
+ while (start < l.length) {
39
+ const index = l.indexOf(CLOSE_NAME, start);
40
+ if (index < 0)
41
+ return null;
42
+ if (!isNameBoundary(l[index + CLOSE_NAME.length])) {
43
+ start = index + 1;
44
+ continue;
45
+ }
46
+ const gt = buf.indexOf(">", index);
47
+ if (gt < 0)
48
+ return null;
49
+ return { innerEnd: index, tagEnd: gt + 1 };
50
+ }
51
+ return null;
52
+ }
53
+ function queryFromOpenTag(raw) {
54
+ const m = /\bquery\s*=\s*"([^"]*)"/i.exec(raw) ?? /\bquery\s*=\s*'([^']*)'/i.exec(raw);
55
+ return m?.[1]?.trim() ?? "";
56
+ }
57
+ const RESULTS_HEADER = /^Search results for\s+"([^"]*)"\s*:/im;
58
+ export function classifyXmlSearchBody(inner, attrQuery = "") {
59
+ const trimmed = inner.trim();
60
+ if (!trimmed) {
61
+ const query = attrQuery.trim();
62
+ return query ? { kind: "invoke", query } : { kind: "results", query: "", body: "" };
63
+ }
64
+ const header = RESULTS_HEADER.exec(trimmed);
65
+ if (header) {
66
+ return { kind: "results", query: header[1].trim() || attrQuery, body: trimmed };
67
+ }
68
+ if (/^\d+\.\s+Title\s*:/m.test(trimmed) && /\bURL\s*:/m.test(trimmed)) {
69
+ return { kind: "results", query: attrQuery, body: trimmed };
70
+ }
71
+ const query = collapse(trimmed) || attrQuery;
72
+ if (query && query.length <= 500 && !/\bURL\s*:\s*https?:\/\//i.test(trimmed)) {
73
+ return { kind: "invoke", query };
74
+ }
75
+ return { kind: "results", query: attrQuery || query, body: trimmed };
76
+ }
77
+ function lastPotentialPartialOpen(buf) {
78
+ const start = Math.max(0, buf.length - PARTIAL_TAG_HOLD);
79
+ const tail = buf.slice(start);
80
+ const lt = tail.lastIndexOf("<");
81
+ if (lt < 0)
82
+ return -1;
83
+ const globalLt = start + lt;
84
+ const cand = buf.slice(globalLt).toLowerCase();
85
+ if (cand.length > 72)
86
+ return -1;
87
+ if (OPEN_NAME.startsWith(cand) || CLOSE_NAME.startsWith(cand))
88
+ return globalLt;
89
+ if (cand.startsWith(OPEN_NAME) && !cand.includes(">"))
90
+ return globalLt;
91
+ if (cand.startsWith(CLOSE_NAME) && !cand.includes(">"))
92
+ return globalLt;
93
+ return -1;
94
+ }
95
+ export class XmlSearchSplitter {
96
+ buf = "";
97
+ feed(chunk) {
98
+ this.buf += chunk;
99
+ return this.drain(false);
100
+ }
101
+ flush() {
102
+ return this.drain(true);
103
+ }
104
+ drain(isFinal) {
105
+ const out = [];
106
+ let hadBlock = false;
107
+ const pushDisplay = (text) => {
108
+ if (!text)
109
+ return;
110
+ const last = out[out.length - 1];
111
+ if (last?.type === "display")
112
+ last.text += text;
113
+ else
114
+ out.push({ type: "display", text });
115
+ };
116
+ while (this.buf.length > 0) {
117
+ const open = findOpen(this.buf);
118
+ if (!open) {
119
+ if (isFinal) {
120
+ pushDisplay(this.buf);
121
+ this.buf = "";
122
+ }
123
+ else {
124
+ const holdFrom = lastPotentialPartialOpen(this.buf);
125
+ if (holdFrom >= 0) {
126
+ pushDisplay(this.buf.slice(0, holdFrom));
127
+ this.buf = this.buf.slice(holdFrom);
128
+ }
129
+ else {
130
+ pushDisplay(this.buf);
131
+ this.buf = "";
132
+ }
133
+ }
134
+ break;
135
+ }
136
+ if (open.index > 0) {
137
+ let pre = this.buf.slice(0, open.index);
138
+ pre = pre.replace(/\n+$/, "");
139
+ if (pre)
140
+ pushDisplay(pre);
141
+ this.buf = this.buf.slice(open.index);
142
+ }
143
+ const close = findClose(this.buf, open.openLen);
144
+ if (!close) {
145
+ const innerLen = this.buf.length - open.openLen;
146
+ if (isFinal || innerLen > UNCLOSED_MAX) {
147
+ const inner = this.buf.slice(open.openLen);
148
+ out.push({ type: "search", block: classifyXmlSearchBody(inner, queryFromOpenTag(open.raw)) });
149
+ hadBlock = true;
150
+ this.buf = "";
151
+ }
152
+ break;
153
+ }
154
+ const inner = this.buf.slice(open.openLen, close.innerEnd);
155
+ out.push({ type: "search", block: classifyXmlSearchBody(inner, queryFromOpenTag(open.raw)) });
156
+ hadBlock = true;
157
+ this.buf = this.buf.slice(close.tagEnd).replace(/^\n+/, "");
158
+ }
159
+ if (hadBlock) {
160
+ const first = out[0];
161
+ if (first?.type === "display")
162
+ first.text = first.text.replace(/^\n+/, "");
163
+ }
164
+ return out.filter((sl) => sl.type === "search" || sl.text.length > 0);
165
+ }
166
+ }
167
+ export function extractXmlSearch(text) {
168
+ const splitter = new XmlSearchSplitter();
169
+ const slices = [...splitter.feed(text), ...splitter.flush()];
170
+ let display = "";
171
+ const blocks = [];
172
+ for (const sl of slices) {
173
+ if (sl.type === "display")
174
+ display += sl.text;
175
+ else
176
+ blocks.push(sl.block);
177
+ }
178
+ return { display, blocks, slices };
179
+ }
180
+ /** Drop the XML wrappers but keep the inner query/results, for sub-agent returns. */
181
+ export function unwrapXmlSearchTags(text) {
182
+ const { slices } = extractXmlSearch(text);
183
+ if (slices.every((sl) => sl.type === "display"))
184
+ return text;
185
+ return slices
186
+ .map((sl) => {
187
+ if (sl.type === "display")
188
+ return sl.text;
189
+ return sl.block.kind === "results" ? sl.block.body : sl.block.query;
190
+ })
191
+ .join("")
192
+ .replace(/^\n+/, "")
193
+ .replace(/\n+$/, "");
194
+ }
package/docs/API.md CHANGED
@@ -30,21 +30,36 @@ min-agent serve --host 0.0.0.0 --port 3000
30
30
  |------|------|------|
31
31
  | GET | `/health` | 存活检查 |
32
32
  | GET | `/v1/meta` | 运行环境 |
33
+ | GET | `/v1/update` | 检查 npm 是否有新版本(不执行安装) |
33
34
  | GET | `/v1/models` | 模型列表 |
34
35
  | GET | `/v1/context` | 上下文窗口信息 |
35
36
  | GET | `/v1/project` | 项目扫描 |
36
- | POST | `/v1/chat` | 对话 |
37
- | POST | `/v1/code` | Code 模式对话 |
37
+ | POST | `/v1/chat` | 对话(项目感知) |
38
+ | POST | `/v1/code` | `/v1/chat` 相同(兼容路径) |
38
39
  | POST | `/v1/paste` | 图片+文本对话 |
39
40
  | POST | `/v1/chat/compact` | 手动压缩会话 |
40
41
  | POST | `/v1/chat/reload-instructions` | 重载规则 |
42
+ | POST | `/v1/chat/redo` | 重发会话最后一条用户消息 |
41
43
  | GET | `/v1/sessions` | 列出会话 |
42
44
  | DELETE | `/v1/sessions/:id` | 删除会话 |
43
- | GET | `/v1/memory` | 列出记忆 |
45
+ | GET | `/v1/memory` | 列出记忆(含 `project_memories`) |
44
46
  | POST | `/v1/memory` | 添加记忆 |
45
47
  | GET | `/v1/memory/search?q=xxx` | 搜索记忆 |
46
48
  | DELETE | `/v1/memory/:index` | 删除记忆 |
49
+ | GET | `/v1/budget` | 当前预算上限 |
50
+ | POST | `/v1/budget` | 设置预算上限 |
51
+ | GET | `/v1/permission` | 当前确认模式 |
52
+ | POST | `/v1/permission` | 设置确认模式 |
53
+ | GET | `/v1/sandbox` | 当前隔离策略 |
54
+ | POST | `/v1/sandbox` | 设置隔离策略 |
55
+ | GET | `/v1/diff` | 工作区未提交变更 |
56
+ | POST | `/v1/approvals` | 回答危险操作确认 |
57
+ | POST | `/v1/answers` | 回答代理提问 |
47
58
  | GET | `/v1/mcp` | MCP 状态 |
59
+ | GET | `/v1/mcp/:name/resources` | 列出资源与 URI 模板 |
60
+ | GET | `/v1/mcp/:name/prompts` | 列出 prompt 模板 |
61
+ | POST | `/v1/mcp/:name/resources/read` | 读取一条资源 |
62
+ | POST | `/v1/mcp/:name/prompts/get` | 填充一条 prompt |
48
63
  | POST | `/v1/mcp` | 添加 MCP 服务器 |
49
64
  | DELETE | `/v1/mcp/:name` | 删除 MCP 服务器 |
50
65
  | GET | `/v1/skills` | 技能列表(含启用状态) |
@@ -71,9 +86,25 @@ min-agent serve --host 0.0.0.0 --port 3000
71
86
  ## `GET /v1/meta`
72
87
 
73
88
  ```json
74
- { "version": "0.1.0", "cwd": "/path/to/project", "instructions_chars": 1234 }
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": "询问确认" } }
75
90
  ```
76
91
 
92
+ ## `GET /v1/update`
93
+
94
+ 查询当前运行版本与 npm 上的最新版本。此接口只检查,不会执行 `npm install`。
95
+
96
+ ```json
97
+ { "current": "0.3.0", "latest": "0.3.1", "update_available": true }
98
+ ```
99
+
100
+ 无法访问 npm registry 时:
101
+
102
+ ```json
103
+ { "current": "0.3.0", "latest": null, "update_available": false, "error": "registry_unavailable" }
104
+ ```
105
+
106
+ CLI 升级请使用 `min-agent update`(执行 `npm install -g min-agent`)。
107
+
77
108
  ## `GET /v1/models`
78
109
 
79
110
  ```json
@@ -83,9 +114,11 @@ min-agent serve --host 0.0.0.0 --port 3000
83
114
  ## `GET /v1/context`
84
115
 
85
116
  ```json
86
- { "context_window": 128000, "model": "gpt-4o" }
117
+ { "context_window": 1000000, "source": "config", "model": "gpt-4o" }
87
118
  ```
88
119
 
120
+ `source` 为 `config`(配置中的 `contextWindow`)、`detected`(从接口读到)或 `fallback`(未读到,按 512k 处理)。
121
+
89
122
  ## `GET /v1/project`
90
123
 
91
124
  ```json
@@ -120,6 +153,14 @@ min-agent serve --host 0.0.0.0 --port 3000
120
153
  | `temperature` | number | 采样温度(透传模型 API) |
121
154
  | `maxTokens` | number | 最大输出 token 数 |
122
155
  | `topP` | number | 核采样参数 |
156
+ | `plan_mode` | boolean | 只读计划模式(去掉写入类工具) |
157
+ | `auto_approve` | boolean | 默认 `true`。设为 `false` 时必须 `stream: true`,并通过 `POST /v1/approvals` / `POST /v1/answers` 回调 |
158
+ | `sandbox` | string | 本轮隔离:`off` / `workspace` / `strict`。只能比服务当前策略更严,不能放宽 |
159
+ | `network` | string | 本轮联网:`allow` / `deny`。`deny` 只能收紧 |
160
+
161
+ `auto_approve: false` 且未开流式时返回 `400 auto_approve_requires_stream`。确认/提问最多等待 5 分钟,超时视为拒绝或跳过。客户端断开连接时未完成的确认视为拒绝。
162
+
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`)。
123
164
 
124
165
  ### 非流式响应
125
166
 
@@ -132,11 +173,23 @@ min-agent serve --host 0.0.0.0 --port 3000
132
173
  "session_id": "abc123",
133
174
  "step_count": 2,
134
175
  "usage": { "inputTokens": 1200, "outputTokens": 300 },
176
+ "context_tokens": 1200,
135
177
  "has_error": false,
136
- "aborted": false
178
+ "aborted": false,
179
+ "max_steps_reached": false,
180
+ "incomplete": false,
181
+ "budget_exceeded": false,
182
+ "continues": 0,
183
+ "stopped": false,
184
+ "empty_response": false,
185
+ "empty_attempts": 0,
186
+ "task_state": { "goal": "...", "todos": [], "nextId": 1 },
187
+ "project": { "languages": [...], "directory": "...", "...": "..." }
137
188
  }
138
189
  ```
139
190
 
191
+ `usage` 为本轮各次模型请求的 token 合计(计费口径;多步工具调用会把每次请求的 input 加总)。`context_tokens` 为最近一次请求占用的上下文大小,用来衡量窗口占用,不要把 `usage.inputTokens` 当占用百分比的分子。
192
+
140
193
  ### 流式 SSE 事件
141
194
 
142
195
  | type | 字段 | 说明 |
@@ -146,20 +199,18 @@ min-agent serve --host 0.0.0.0 --port 3000
146
199
  | `tool_call` | name, input | 工具调用 |
147
200
  | `tool_result` | name, output | 工具返回 |
148
201
  | `compaction` | line | 压缩进度 |
202
+ | `notice` | kind, attempt, max_attempts, delay_ms | 提示(目前只有 `kind: "empty_response"`,表示 provider 返回空流后即将重发同一请求) |
149
203
  | `error` | message | 错误 |
150
- | `done` | step_count, usage, has_error, aborted, max_steps_reached, budget_exceeded, messages, session_id | 结束 |
204
+ | `done` | step_count, usage, context_tokens, has_error, aborted, max_steps_reached, incomplete, budget_exceeded, continues, stopped, empty_response, empty_attempts, messages, session_id, task_state, project | 结束 |
205
+ | `confirm` | id, message | 等待确认(仅 `auto_approve: false`) |
206
+ | `question` | id, prompt, options | 等待回答(仅 `auto_approve: false`)。`options` 为字符串数组;对象选项(如 `{label, description}`)会先被规范成字符串再下发 |
151
207
  | `fatal` | message | 致命错误 |
152
208
 
153
209
  ---
154
210
 
155
211
  ## `POST /v1/code`
156
212
 
157
- Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/chat` 相同。
158
-
159
- 响应额外包含:
160
- ```json
161
- { "mode": "code", "project": { "languages": [...], ... }, ... }
162
- ```
213
+ `/v1/chat` 相同的兼容路径。请求体与响应结构一致(含 `project`)。
163
214
 
164
215
  ---
165
216
 
@@ -176,16 +227,17 @@ Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/
176
227
  | `provider` | string | 指定 provider(默认 activeProvider) |
177
228
  | `session_id` | string | 追加到会话 |
178
229
  | `stream` | boolean | SSE 流式 |
179
- | `code` | boolean | 使用 code 模式(项目感知) |
180
230
  | `temperature` | number | 采样温度 |
181
231
  | `maxTokens` | number | 最大输出 token 数 |
182
232
  | `topP` | number | 核采样参数 |
233
+ | `plan_mode` | boolean | 只读计划模式 |
234
+ | `auto_approve` | boolean | 同 `/v1/chat` |
183
235
 
184
- `stream: true` 时 SSE 事件与 `/v1/chat` 一致(含 `X-Accel-Buffering: no` 响应头、`done` 事件的 usage/has_error/aborted/max_steps_reached/messages 字段);非流式响应与 `/v1/chat` 非流式响应结构一致。
236
+ `stream: true` 时 SSE 事件与 `/v1/chat` 一致(含 `X-Accel-Buffering: no` 响应头、`done` 事件的 usage/context_tokens/has_error/aborted/max_steps_reached/incomplete/continues/stopped/empty_response/empty_attempts/task_state/messages 字段);非流式响应与 `/v1/chat` 非流式响应结构一致。
185
237
 
186
238
  `session_id` 不存在时返回 `404 session_not_found`(与 `/v1/chat` 一致)。
187
239
 
188
- `code: true` 时非流式响应额外包含 `mode: "code"` 与 `project` 字段。
240
+ 非流式响应与 `/v1/chat` 相同,始终包含 `project`。请求里多传 `code` 会被忽略。
189
241
 
190
242
  ---
191
243
 
@@ -201,7 +253,13 @@ Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/
201
253
  ## `GET /v1/sessions/:id`
202
254
 
203
255
  ```json
204
- { "session": { "meta": { "id": "abc", "title": "...", ... }, "messages": [...] } }
256
+ {
257
+ "session": {
258
+ "meta": { "id": "abc", "title": "...", "created": "...", "updated": "...", "messageCount": 2 },
259
+ "taskState": { "goal": "...", "todos": [{ "id": 1, "text": "...", "status": "in_progress" }], "nextId": 2 },
260
+ "messages": [...]
261
+ }
262
+ }
205
263
  ```
206
264
 
207
265
  ## `POST /v1/sessions/:id/rename`
@@ -224,6 +282,106 @@ Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/
224
282
 
225
283
  截断到最后一条 user 消息(保留该消息,删除其后的 assistant/tool 消息)。
226
284
 
285
+ ## `POST /v1/chat/redo`
286
+
287
+ 取出会话中最后一条用户文本,追加一轮后再跑。请求体与 `/v1/chat` 相同的可选字段(`stream`、`model`、`provider`、`plan_mode`、`auto_approve`、采样参数),`session_id` 必填。
288
+
289
+ ```json
290
+ // 请求
291
+ { "session_id": "abc123", "stream": false }
292
+ ```
293
+
294
+ 没有用户文本时返回 `400 no_user_message`。
295
+
296
+ ## `GET /v1/budget`
297
+
298
+ ```json
299
+ { "maxCostUSD": 5, "source": "global" }
300
+ ```
301
+
302
+ `source` 为 `project`、`global` 或 `null`(未设置)。`maxCostUSD` 为合并后的生效值。
303
+
304
+ ## `POST /v1/budget`
305
+
306
+ ```json
307
+ // 请求
308
+ { "maxCostUSD": 8, "scope": "project" }
309
+ // 响应
310
+ { "ok": true, "maxCostUSD": 8, "source": "project", "scope": "project" }
311
+ ```
312
+
313
+ `scope` 省略时写入全局。`maxCostUSD` 必须为正数。
314
+
315
+ ## `GET /v1/permission`
316
+
317
+ ```json
318
+ { "permission": "ask", "source": null, "label": "询问确认" }
319
+ ```
320
+
321
+ `permission` 为 `ask` / `accept-edits` / `allow-all`。`source` 为 `cli`、`project`、`global` 或 `null`(使用默认:询问确认)。
322
+
323
+ ## `POST /v1/permission`
324
+
325
+ ```json
326
+ // 请求
327
+ { "permission": "accept-edits", "scope": "project" }
328
+ // 响应
329
+ { "ok": true, "scope": "project", "permission": "accept-edits", "source": "project", "label": "自动允许改文件" }
330
+ ```
331
+
332
+ `scope` 省略时写入全局。`permission` 必须为 `ask` / `accept-edits` / `allow-all`。
333
+
334
+ ## `GET /v1/sandbox`
335
+
336
+ ```json
337
+ { "mode": "off", "network": "allow", "extraWriteRoots": [], "extraReadRoots": [], "source": null, "label": "未隔离,允许联网" }
338
+ ```
339
+
340
+ `source` 为 `cli`、`env`、`project`、`global` 或 `null`(使用默认:不隔离)。未配置时 `mode` 为 `off`。
341
+
342
+ ## `POST /v1/sandbox`
343
+
344
+ ```json
345
+ // 请求
346
+ { "mode": "workspace", "network": "deny", "scope": "project" }
347
+ // 响应
348
+ { "ok": true, "scope": "project", "mode": "workspace", "network": "deny", "extraWriteRoots": [], "extraReadRoots": [], "source": "project", "label": "工作区隔离,禁止联网" }
349
+ ```
350
+
351
+ `scope` 省略时写入全局。至少提供 `mode`、`network`、`extraWriteRoots`、`extraReadRoots` 之一。`mode` 为 `off` / `workspace` / `strict`;`network` 为 `allow` / `deny`。
352
+
353
+ ## `GET /v1/diff`
354
+
355
+ ```json
356
+ { "ok": true, "diff": "..." }
357
+ ```
358
+
359
+ 非 git 仓库或无变更时仍返回 200:`{ "ok": false, "reason": "not_git"|"no_changes"|"error", "message": "..." }`。
360
+
361
+ ## `POST /v1/approvals`
362
+
363
+ 配合 SSE `confirm` 事件。
364
+
365
+ ```json
366
+ // 请求
367
+ { "id": "…", "accepted": true }
368
+ // 响应
369
+ { "ok": true, "id": "…", "accepted": true }
370
+ ```
371
+
372
+ 未知 id 返回 `404 unknown_approval_id`。
373
+
374
+ ## `POST /v1/answers`
375
+
376
+ 配合 SSE `question` 事件。`answer` 为 `null` 表示跳过。
377
+
378
+ ```json
379
+ // 请求
380
+ { "id": "…", "answer": "use bun" }
381
+ ```
382
+
383
+ 未知 id 返回 `404 unknown_answer_id`。
384
+
227
385
  ## `GET /v1/skills`
228
386
 
229
387
  ```json
@@ -246,6 +404,7 @@ Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/
246
404
  ```
247
405
 
248
406
  禁用项额外带 `disabled_scope`(`global` 或 `project`)。
407
+ 随包装载的技能额外带 `builtin: true`;会话开始即注入正文的技能额外带 `always_load: true`(内置 `self-config` 即如此)。
249
408
  `stale_disabled` 列出两个范围的 `disabledSkills` 中没有对应技能的名字(拼写错误或技能已删除)。
250
409
 
251
410
  ## `GET /v1/skills/:name`
@@ -328,28 +487,42 @@ Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/
328
487
  ## `GET /v1/memory`
329
488
 
330
489
  ```json
331
- { "memories": [{ "content": "...", "tags": [...], "created": "..." }] }
490
+ {
491
+ "memories": [{ "content": "...", "tags": [...], "created": "..." }],
492
+ "project_memories": [{ "content": "...", "tags": [...], "created": "..." }]
493
+ }
332
494
  ```
333
495
 
496
+ `memories` 始终为全局;`project_memories` 为当前目录 `.min-agent/memory.json`。
497
+
334
498
  ## `POST /v1/memory`
335
499
 
336
500
  ```json
337
501
  // 请求
338
- { "content": "prefer TypeScript", "tags": ["preference"] }
502
+ { "content": "prefer TypeScript", "tags": ["preference"], "scope": "project" }
339
503
  // 响应
340
- { "ok": true, "memory": {...} }
504
+ { "ok": true, "memory": {...}, "scope": "project" }
341
505
  ```
342
506
 
507
+ `scope` 省略时写入全局(兼容旧客户端)。非法值返回 `400 invalid_scope`。
508
+
343
509
  ## `GET /v1/memory/search?q=typescript`
344
510
 
511
+ 可选 `scope=global|project`。省略时两个商店都搜。
512
+
345
513
  ```json
346
- { "results": [{ "content": "...", "tags": [...], "index": 0 }] }
514
+ {
515
+ "results": [{ "content": "...", "tags": [...], "index": 0 }],
516
+ "project_results": []
517
+ }
347
518
  ```
348
519
 
349
520
  ## `DELETE /v1/memory/:index`
350
521
 
522
+ 可选 `?scope=project`;默认全局。索引从 1 开始,按对应商店编号。
523
+
351
524
  ```json
352
- { "ok": true, "deleted": 1 }
525
+ { "ok": true, "deleted": 1, "scope": "global" }
353
526
  ```
354
527
 
355
528
  ---
@@ -359,12 +532,53 @@ Code 模式对话(项目感知 prompt + explore 工具)。请求体与 `/v1/
359
532
  ```json
360
533
  {
361
534
  "servers": {
362
- "filesystem": { "connected": true, "enabled": true, "tools": ["read_file", "write_file"] },
363
- "broken": { "connected": false, "enabled": true, "tools": [], "error": "connection timed out after 30000ms" }
535
+ "filesystem": { "connected": true, "enabled": true, "tools": ["read_file", "write_file"], "resource_count": 2, "prompt_count": 1 },
536
+ "broken": { "connected": false, "enabled": true, "tools": [], "resource_count": 0, "prompt_count": 0, "error": "connection timed out after 30000ms" }
364
537
  }
365
538
  }
366
539
  ```
367
540
 
541
+ `resource_count` / `prompt_count` 为连接时缓存的数量。未声明 resources/prompts 的服务器为 0。
542
+
543
+ ## `GET /v1/mcp/:name/resources`
544
+
545
+ ```json
546
+ {
547
+ "name": "filesystem",
548
+ "resources": [{ "uri": "file:///tmp/a.txt", "name": "a.txt", "mimeType": "text/plain" }],
549
+ "templates": [{ "uriTemplate": "file:///{path}", "name": "file" }]
550
+ }
551
+ ```
552
+
553
+ 未配置返回 `404 mcp_not_found`;已配置但未连接返回 `404 mcp_not_connected`。
554
+
555
+ ## `GET /v1/mcp/:name/prompts`
556
+
557
+ ```json
558
+ {
559
+ "name": "docs",
560
+ "prompts": [{ "name": "review", "description": "Review a file", "arguments": [{ "name": "path", "required": true }] }]
561
+ }
562
+ ```
563
+
564
+ ## `POST /v1/mcp/:name/resources/read`
565
+
566
+ ```json
567
+ // 请求
568
+ { "uri": "file:///tmp/a.txt" }
569
+ // 响应
570
+ { "ok": true, "name": "filesystem", "uri": "file:///tmp/a.txt", "content": "..." }
571
+ ```
572
+
573
+ ## `POST /v1/mcp/:name/prompts/get`
574
+
575
+ ```json
576
+ // 请求
577
+ { "prompt": "review", "arguments": { "path": "src/a.ts" } }
578
+ // 响应
579
+ { "ok": true, "name": "docs", "prompt": "review", "content": "user: ..." }
580
+ ```
581
+
368
582
  ## `POST /v1/mcp`
369
583
 
370
584
  添加或覆盖一个 MCP 服务器。保存后立即重连(`sync`),响应带实时状态。
@@ -431,7 +645,7 @@ curl -N http://127.0.0.1:8787/v1/chat \
431
645
  -H "Content-Type: application/json" \
432
646
  -d '{"message":"say hi","stream":true}'
433
647
 
434
- # Code 模式
648
+ # 兼容路径 /v1/code
435
649
  curl http://127.0.0.1:8787/v1/code \
436
650
  -H "Content-Type: application/json" \
437
651
  -d '{"message":"add error handling to login"}'
@@ -451,6 +665,24 @@ curl -X POST http://127.0.0.1:8787/v1/memory \
451
665
  -H "Content-Type: application/json" \
452
666
  -d '{"content":"prefer TypeScript","tags":["preference"]}'
453
667
 
668
+ # 设置项目预算
669
+ curl -X POST http://127.0.0.1:8787/v1/budget \
670
+ -H "Content-Type: application/json" \
671
+ -d '{"maxCostUSD":5,"scope":"project"}'
672
+
673
+ # 设置确认模式
674
+ curl -X POST http://127.0.0.1:8787/v1/permission \
675
+ -H "Content-Type: application/json" \
676
+ -d '{"permission":"accept-edits","scope":"project"}'
677
+
678
+ # 启用工作区隔离
679
+ curl -X POST http://127.0.0.1:8787/v1/sandbox \
680
+ -H "Content-Type: application/json" \
681
+ -d '{"mode":"workspace","scope":"project"}'
682
+
683
+ # 查看工作区变更
684
+ curl http://127.0.0.1:8787/v1/diff
685
+
454
686
  # 压缩会话
455
687
  curl -X POST http://127.0.0.1:8787/v1/chat/compact \
456
688
  -H "Content-Type: application/json" \