min-agent 0.5.0 → 0.6.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.
- package/README.md +32 -18
- package/dist/cli.js +538 -12
- package/docs/API.md +25 -8
- package/package.json +6 -6
- package/skills/self-config/SKILL.md +7 -3
- package/skills/self-config/reference.md +8 -4
- package/dist/agent.js +0 -1342
- package/dist/assistant-stream.js +0 -131
- package/dist/cli/commands/chat.js +0 -13
- package/dist/cli/commands/exec.js +0 -35
- package/dist/cli/commands/history.js +0 -58
- package/dist/cli/commands/index.js +0 -241
- package/dist/cli/commands/init.js +0 -18
- package/dist/cli/commands/mcp.js +0 -173
- package/dist/cli/commands/memory.js +0 -87
- package/dist/cli/commands/models.js +0 -21
- package/dist/cli/commands/permission.js +0 -12
- package/dist/cli/commands/rules.js +0 -33
- package/dist/cli/commands/sandbox.js +0 -13
- package/dist/cli/commands/serve.js +0 -9
- package/dist/cli/commands/setup.js +0 -4
- package/dist/cli/commands/shared.js +0 -16
- package/dist/cli/commands/skills.js +0 -119
- package/dist/cli/commands/think.js +0 -12
- package/dist/cli/commands/update.js +0 -7
- package/dist/cli/commands/write-config.js +0 -52
- package/dist/cli/errors.js +0 -36
- package/dist/cli/exec-prompt.js +0 -26
- package/dist/cli/option-helpers.js +0 -65
- package/dist/cli/program.js +0 -217
- package/dist/clipboard.js +0 -142
- package/dist/code-mode.js +0 -184
- package/dist/compaction.js +0 -644
- package/dist/config.js +0 -470
- package/dist/confirm.js +0 -172
- package/dist/context-window.js +0 -246
- package/dist/doom-loop.js +0 -278
- package/dist/fetch-timeout.js +0 -152
- package/dist/http-approvals.js +0 -60
- package/dist/http.js +0 -119
- package/dist/instructions.js +0 -196
- package/dist/logger.js +0 -95
- package/dist/markdown.js +0 -259
- package/dist/mcp.js +0 -1036
- package/dist/memory-cli.js +0 -33
- package/dist/memory.js +0 -295
- package/dist/model-catalog.js +0 -285
- package/dist/output.js +0 -76
- package/dist/paste-handler.js +0 -41
- package/dist/permission-cli.js +0 -40
- package/dist/plugins.js +0 -131
- package/dist/pricing.js +0 -119
- package/dist/provider.js +0 -63
- package/dist/question-format.js +0 -60
- package/dist/reasoning-stream.js +0 -158
- package/dist/sandbox-cli.js +0 -79
- package/dist/sandbox.js +0 -403
- package/dist/save-throttle.js +0 -45
- package/dist/scope.js +0 -23
- package/dist/serve/common.js +0 -425
- package/dist/serve/routes-chat.js +0 -367
- package/dist/serve/routes-mcp.js +0 -212
- package/dist/serve/routes-memory.js +0 -95
- package/dist/serve/routes-meta.js +0 -233
- package/dist/serve/routes-sessions.js +0 -61
- package/dist/serve/routes-skills.js +0 -70
- package/dist/serve.js +0 -138
- package/dist/sessions.js +0 -265
- package/dist/skills.js +0 -599
- package/dist/structured-output.js +0 -29
- package/dist/synthetic.js +0 -7
- package/dist/think-cli.js +0 -36
- package/dist/thinking-wire.js +0 -228
- package/dist/thinking.js +0 -142
- package/dist/title-gen.js +0 -55
- package/dist/token-display.js +0 -39
- package/dist/tool-display.js +0 -178
- package/dist/tool-output.js +0 -126
- package/dist/tools/apply_patch.js +0 -265
- package/dist/tools/atomic-file.js +0 -35
- package/dist/tools/backend.js +0 -61
- package/dist/tools/bash.js +0 -231
- package/dist/tools/code_search.js +0 -98
- package/dist/tools/edit.js +0 -68
- package/dist/tools/explore.js +0 -232
- package/dist/tools/glob.js +0 -40
- package/dist/tools/grep.js +0 -174
- package/dist/tools/index.js +0 -34
- package/dist/tools/question.js +0 -54
- package/dist/tools/read.js +0 -109
- package/dist/tools/search-searxng.js +0 -223
- package/dist/tools/search-serper.js +0 -189
- package/dist/tools/task.js +0 -165
- package/dist/tools/todo.js +0 -213
- package/dist/tools/web_fetch.js +0 -195
- package/dist/tools/web_search.js +0 -228
- package/dist/tools/write.js +0 -55
- package/dist/tui/App.js +0 -126
- package/dist/tui/ConfirmBar.js +0 -66
- package/dist/tui/InputBar.js +0 -616
- package/dist/tui/MessageList.js +0 -718
- package/dist/tui/ModelPicker.js +0 -113
- package/dist/tui/QuestionBar.js +0 -136
- package/dist/tui/SessionPicker.js +0 -79
- package/dist/tui/Spinner.js +0 -6
- package/dist/tui/StatusBar.js +0 -22
- package/dist/tui/ThinkPicker.js +0 -77
- package/dist/tui/agent-runner.js +0 -223
- package/dist/tui/bracketed-paste.js +0 -37
- package/dist/tui/caret-pos.js +0 -179
- package/dist/tui/caret.js +0 -69
- package/dist/tui/click-count.js +0 -13
- package/dist/tui/diff-view.js +0 -61
- package/dist/tui/drag-state.js +0 -49
- package/dist/tui/hydrate.js +0 -129
- package/dist/tui/index.js +0 -258
- package/dist/tui/input-history.js +0 -125
- package/dist/tui/layout.js +0 -105
- package/dist/tui/mouse.js +0 -46
- package/dist/tui/overlay-input.js +0 -12
- package/dist/tui/paste-draft.js +0 -173
- package/dist/tui/prompt-queue.js +0 -24
- package/dist/tui/selection.js +0 -232
- package/dist/tui/session-switch.js +0 -28
- package/dist/tui/slash-commands.js +0 -123
- package/dist/tui/slash-handler.js +0 -589
- package/dist/tui/text-width.js +0 -113
- package/dist/tui/theme.js +0 -12
- package/dist/tui/token-info.js +0 -7
- package/dist/tui/tool-children.js +0 -19
- package/dist/tui/types.js +0 -1
- package/dist/tui/undo-stack.js +0 -14
- package/dist/tui/use-sgr-mouse.js +0 -29
- package/dist/tui-chat.js +0 -456
- package/dist/updater.js +0 -116
- package/dist/xml-search.js +0 -194
- package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
- package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
- package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
- package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
- package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
- package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
- package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
- package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
- package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
- package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
- package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
- package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
- package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# 第二批:CLI + 工具 + HTTP API — 设计文档
|
|
2
|
-
|
|
3
|
-
**日期:** 2026-08-16
|
|
4
|
-
**状态:** 已批准
|
|
5
|
-
**范围:** 30 项改进清单中的第二批(17 项):CLI 增强、工具增强、HTTP API 补齐
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 背景
|
|
10
|
-
|
|
11
|
-
min-agent 功能审查发现的 30 项缺失按"价值/风险"分三批实施。本 spec 覆盖第二批:CLI 命令补全、工具能力增强、HTTP API 完整性。第一批(TUI 交互)已完成并合并到 master。
|
|
12
|
-
|
|
13
|
-
## 设计决策(已与用户确认)
|
|
14
|
-
|
|
15
|
-
| 决策 | 结论 |
|
|
16
|
-
|------|------|
|
|
17
|
-
| update 命令 | 检查 npm registry 版本后自动执行 `npm install -g min-agent@latest` |
|
|
18
|
-
| apply_patch | 完整 unified diff(解析 + 应用 + 冲突报告) |
|
|
19
|
-
| 会话导出格式 | 完整 session JSON(meta + messages,与 sessions/ 目录同格式) |
|
|
20
|
-
|
|
21
|
-
## 模块设计
|
|
22
|
-
|
|
23
|
-
### 1. CLI 增强
|
|
24
|
-
|
|
25
|
-
#### 1.1 `min-agent history delete <id>` / `rename <id> <title>`
|
|
26
|
-
|
|
27
|
-
- `src/cli.ts` history 分支扩展:
|
|
28
|
-
- `history delete <id>` → `deleteSession(id)`,成功提示 ✓,失败报"会话不存在"(exit 1)
|
|
29
|
-
- `history rename <id> <title>` → `renameSession(id, title)`,同样处理
|
|
30
|
-
- 复用第一批已实现的 `src/sessions.ts` 函数,无新模块
|
|
31
|
-
|
|
32
|
-
#### 1.2 `min-agent mcp add --env KEY=VALUE`
|
|
33
|
-
|
|
34
|
-
- `parseMcpAddArgs` 增加 `--env` 解析(可重复,`KEY=VALUE` 格式,非法格式报错)
|
|
35
|
-
- 构建 `entry.environment = { KEY: "VALUE", ... }`(仅当有 env 时设置)
|
|
36
|
-
- `mcp info` 已支持显示 environment 键列表,无需改
|
|
37
|
-
|
|
38
|
-
#### 1.3 `min-agent update` 自动升级
|
|
39
|
-
|
|
40
|
-
- 新逻辑(放 `src/cli.ts` 或独立 `src/updater.ts`;因含 fetch + execSync 逻辑,独立模块 `src/updater.ts` 更清晰):
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
export async function runUpdate(): Promise<void>
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
- 流程:
|
|
47
|
-
1. 读取本地版本(现有 `--version` 的读 package.json 逻辑)
|
|
48
|
-
2. fetch `https://registry.npmjs.org/min-agent/latest`(10s 超时),取 `version`
|
|
49
|
-
3. 本地已最新 → 提示并返回
|
|
50
|
-
4. 有新版 → 打印版本信息,执行 `npm install -g min-agent@latest`(`execSync(..., { stdio: "inherit" })`),成功提示重启
|
|
51
|
-
5. 网络失败 → 提示无法检查
|
|
52
|
-
- 注意事项:npm 不可用时报错提示改用 `npm install -g min-agent@latest` 手动执行
|
|
53
|
-
|
|
54
|
-
#### 1.4 `min-agent history export <id> [-o file]`
|
|
55
|
-
|
|
56
|
-
- `history export <id>`:`loadSession(id)`,无会话报错 exit 1
|
|
57
|
-
- 输出完整 SessionData JSON(`JSON.stringify(data, null, 2)`);`-o <file>` 写文件并提示路径,否则 stdout
|
|
58
|
-
- 复用 `loadSession`,无新模块
|
|
59
|
-
|
|
60
|
-
### 2. 工具增强
|
|
61
|
-
|
|
62
|
-
#### 2.1 bash 支持 `cwd` 参数
|
|
63
|
-
|
|
64
|
-
- `src/tools/bash.ts`:
|
|
65
|
-
- `BashInput` 加可选 `cwd?: string`
|
|
66
|
-
- `executeBash(command, timeout, cwd?)` 签名扩展,spawn 用 `cwd: cwd ? path.resolve(process.cwd(), cwd) : process.cwd()`
|
|
67
|
-
- 工具描述补充"cwd:在指定目录执行(相对当前目录或绝对路径)"
|
|
68
|
-
- `src/tools/explore.ts` 的 `readOnlyBashTool` 同步支持(executeBash 已扩展,只需 schema 加 cwd)
|
|
69
|
-
- 不引入确认流程变化(cwd 本身无危险)
|
|
70
|
-
|
|
71
|
-
#### 2.2 `apply_patch` 工具(新模块 `src/tools/apply_patch.ts`)
|
|
72
|
-
|
|
73
|
-
Unified diff 解析与应用:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
interface ApplyPatchInput {
|
|
77
|
-
diff: string
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**解析器**(纯函数,可测试):
|
|
82
|
-
- 扫描 `--- <path>` / `+++ <path>` 文件头(忽略 `a/`、`b/` 前缀;`/dev/null` 表示创建新文件)
|
|
83
|
-
- 扫描 `@@ -start,count +start,count @@` hunks(行号可省略 count)
|
|
84
|
-
- hunk 内行:`' '` 上下文、`-` 删除、`+` 新增、`\` 续行标记(No newline at end of file 忽略处理)
|
|
85
|
-
- 多文件、多 hunk 支持
|
|
86
|
-
|
|
87
|
-
**应用器**:
|
|
88
|
-
- 目标文件不存在且 diff 标记为新增(`/dev/null` 或 b/ 文件无 a/)→ 创建
|
|
89
|
-
- 目标文件不存在且无新增标记 → 报错"文件不存在"
|
|
90
|
-
- 逐 hunk 应用:按上下文匹配定位(允许行偏移——匹配时计算目标位置;简化策略:从 hunk 期望位置附近 ±3 行内查找匹配起点,找不到则冲突)
|
|
91
|
-
- 冲突 → 返回错误信息(哪个文件、哪个 hunk 行号范围),不部分写入
|
|
92
|
-
- 应用顺序:每个文件独立应用,全部成功才写(原子性:先全量解析验证,再写文件)
|
|
93
|
-
- 写文件前 confirm(危险操作,遵循项目规则)
|
|
94
|
-
- 输出:每个文件的变更统计(`-N/+M` 行数)
|
|
95
|
-
|
|
96
|
-
#### 2.3 write 支持 `append`
|
|
97
|
-
|
|
98
|
-
- `src/tools/write.ts`:`WriteInput` 加可选 `append?: boolean`
|
|
99
|
-
- `append: true` 时:`writeFile(resolved, content, { flag: "a" })`,跳过"覆盖现有文件"确认(追加不需要)
|
|
100
|
-
- 描述更新:追加模式说明
|
|
101
|
-
|
|
102
|
-
#### 2.4 web_search 参数扩展
|
|
103
|
-
|
|
104
|
-
- `src/tools/web_search.ts`:`WebSearchInput` 加 `language?: string`、`time_range?: string`
|
|
105
|
-
- 透传 SearXNG:`language`(ISO 639-1,如 `zh`、`en`)、`time_range`(`day`/`week`/`month`/`year`,校验白名单)
|
|
106
|
-
- language 白名单校验(简化:非空字符串直接透传,SearXNG 自己处理无效值);time_range 白名单校验
|
|
107
|
-
|
|
108
|
-
#### 2.5 chat 模式工具扩展
|
|
109
|
-
|
|
110
|
-
- `src/tools/index.ts` `createChatTools()`:加 `todo: todoTool`
|
|
111
|
-
- `src/agent.ts` `buildTools()`:explore 工厂改为两种模式都加:
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
if (mode === "code") {
|
|
115
|
-
const { createTaskTool } = await import("./tools/task.js")
|
|
116
|
-
const { createExploreTool } = await import("./tools/explore.js")
|
|
117
|
-
allTools["task"] = createTaskTool(modelId, abortSignal)
|
|
118
|
-
allTools["explore"] = createExploreTool(modelId, abortSignal)
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
改为:
|
|
123
|
-
|
|
124
|
-
```ts
|
|
125
|
-
const { createExploreTool } = await import("./tools/explore.js")
|
|
126
|
-
allTools["explore"] = createExploreTool(modelId, abortSignal)
|
|
127
|
-
if (mode === "code") {
|
|
128
|
-
const { createTaskTool } = await import("./tools/task.js")
|
|
129
|
-
allTools["task"] = createTaskTool(modelId, abortSignal)
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
- task 保留 code 模式;explore 两种模式都有;todo 两种模式都有
|
|
134
|
-
|
|
135
|
-
### 3. HTTP API 补齐
|
|
136
|
-
|
|
137
|
-
#### 3.1 `GET /v1/sessions/:id`
|
|
138
|
-
|
|
139
|
-
- `loadSession(id)` → 404(不存在)/ 200(`{ session: { meta, messages } }`)
|
|
140
|
-
|
|
141
|
-
#### 3.2 `POST /v1/sessions/:id/rename`
|
|
142
|
-
|
|
143
|
-
- body `{ title: string }`(必填非空,否则 400)
|
|
144
|
-
- `renameSession(id, title)` → false 时 404
|
|
145
|
-
- 成功 200 `{ ok: true, session_id, title }`
|
|
146
|
-
|
|
147
|
-
#### 3.3 `POST /v1/skills/:name/enable|disable`
|
|
148
|
-
|
|
149
|
-
- enable:从 `config.disabledSkills` 移除 name
|
|
150
|
-
- disable:加入(去重)
|
|
151
|
-
- 保存 config,200 `{ ok: true, name, enabled: bool }`
|
|
152
|
-
|
|
153
|
-
#### 3.4 `POST /v1/mcp/:name/enable|disable`
|
|
154
|
-
|
|
155
|
-
- 更新 `config.mcpServers[name].enabled`(不存在 404)
|
|
156
|
-
- 200 `{ ok: true, name, enabled: bool }`
|
|
157
|
-
|
|
158
|
-
#### 3.5 `/v1/paste` 支持 code 模式
|
|
159
|
-
|
|
160
|
-
- body 加 `code?: boolean`
|
|
161
|
-
- code 时:`scanProject()` + `buildCodeSystemPrompt()`,用 `runOnceWithSystem`,响应带 `mode: "code"` 与 `project` 字段(与 /v1/code 一致)
|
|
162
|
-
- stream 与 JSON 两种模式都支持
|
|
163
|
-
|
|
164
|
-
#### 3.6 `GET /v1/cost`
|
|
165
|
-
|
|
166
|
-
- 参数:`model`(可选,默认配置默认模型)、`input`、`output`(token 数,可选)
|
|
167
|
-
- 响应:`{ model, price: ModelPrice | null, cost_usd: number | null }`
|
|
168
|
-
- `getModelPrice(model)` + `estimateCost`
|
|
169
|
-
- 复用第一批的 pricing.ts
|
|
170
|
-
|
|
171
|
-
#### 3.7 `POST /v1/chat/undo`
|
|
172
|
-
|
|
173
|
-
- body `{ session_id }`(必填,404 检查)
|
|
174
|
-
- 语义:截断 messages 到最后一条 user 消息之后(保留该 user 消息,删除其后所有 assistant/tool 消息)——与 TUI /undo 的"保留 user 消息"语义一致
|
|
175
|
-
- 实现:从后往前找 `role === "user"` 的索引 `i`,`messages = messages.slice(0, i + 1)`;无 user 消息 → 400
|
|
176
|
-
- 保存并返回 `{ ok: true, message_count, messages }`
|
|
177
|
-
|
|
178
|
-
#### 3.8 采样参数透传
|
|
179
|
-
|
|
180
|
-
- `ChatBody` 加 `temperature?: number`、`maxTokens?: number`、`topP?: number`
|
|
181
|
-
- `src/agent.ts` `runOnce`/`runOnceWithSystem`/`runOnceCore` 加可选 `options?: { temperature?: number; maxTokens?: number; topP?: number }`
|
|
182
|
-
- `streamText({ ..., temperature, maxTokens, topP })` 条件展开(undefined 不传,保持默认)
|
|
183
|
-
- serve.ts 两个聊天 handler(/v1/chat、/v1/code、/v1/paste)透传
|
|
184
|
-
- 注意:AI SDK 的 streamText 参数是 `temperature?: number`、`maxTokens?: number`(Vercel AI SDK v6 是 `maxTokens`)、`topP?: number`
|
|
185
|
-
|
|
186
|
-
### 4. 文档同步
|
|
187
|
-
|
|
188
|
-
- `docs/API.md`:新端点表格 + 各端点说明、采样参数字段、cost 端点
|
|
189
|
-
- `README.md`:Commands 区补 `history delete/rename/export`、`mcp add --env`、`update`
|
|
190
|
-
- `src/cli.ts` printUsage 同步
|
|
191
|
-
|
|
192
|
-
## 错误处理
|
|
193
|
-
|
|
194
|
-
| 场景 | 行为 |
|
|
195
|
-
|------|------|
|
|
196
|
-
| update 网络失败 | 提示无法检查新版本,不崩溃 |
|
|
197
|
-
| update npm 失败 | 报错并提示手动 `npm install -g min-agent@latest` |
|
|
198
|
-
| apply_patch 冲突 | 明确报告文件与 hunk 位置,拒绝写入 |
|
|
199
|
-
| apply_patch 目标文件不存在 | 报错(除非 diff 标记新增) |
|
|
200
|
-
| history delete/rename/export 目标不存在 | 报错 exit 1 |
|
|
201
|
-
| mcp add --env 非法格式 | 报错并提示 `--env KEY=VALUE` |
|
|
202
|
-
| 采样参数非法值 | 透传给模型 API(不校验),由 provider 拒绝 |
|
|
203
|
-
|
|
204
|
-
## 测试
|
|
205
|
-
|
|
206
|
-
| 文件 | 覆盖 |
|
|
207
|
-
|------|------|
|
|
208
|
-
| `tests/apply-patch.test.ts`(新) | 解析(文件头/hunk/行)、应用(修改/新增/删除)、新文件创建、上下文偏移匹配、冲突报告、原子性(冲突不部分写入) |
|
|
209
|
-
| `tests/web-search.test.ts`(改) | language/time_range 参数透传(mock fetch) |
|
|
210
|
-
| `tests/serve.test.ts`(改) | 新端点:sessions/:id、rename、skills enable/disable、mcp enable/disable、cost、chat/undo、paste code、采样参数透传 |
|
|
211
|
-
| bash cwd | 直接执行验证(`executeBash("pwd", undefined, tmpdir)` 输出为 tmpdir) |
|
|
212
|
-
| CLI 新命令 | 冒烟验证 |
|
|
213
|
-
|
|
214
|
-
验证命令(`packages/min-agent` 下):`bun test`、`bun run typecheck`(仓库根)、手动冒烟。
|
|
215
|
-
|
|
216
|
-
## 文档同步清单
|
|
217
|
-
|
|
218
|
-
- `docs/API.md`:新端点 + 采样参数 + cost
|
|
219
|
-
- `README.md`:history 子命令、mcp --env、update、export
|
|
220
|
-
- `src/cli.ts` printUsage
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# 第三批:配置 + 工程 — 设计文档
|
|
2
|
-
|
|
3
|
-
**日期:** 2026-08-16
|
|
4
|
-
**状态:** 已批准
|
|
5
|
-
**范围:** 30 项改进清单中的第三批(7 项):多 provider、采样参数、预算上限、日志系统、CI、/plan 计划模式、文档完善
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 背景
|
|
10
|
-
|
|
11
|
-
min-agent 功能审查发现的 30 项缺失按三批实施。前两批(TUI 交互、CLI/工具/API)已完成并合并到 master。本批为配置体系与工程基础设施。
|
|
12
|
-
|
|
13
|
-
## 设计决策(已与用户确认)
|
|
14
|
-
|
|
15
|
-
| 决策 | 结论 |
|
|
16
|
-
|------|------|
|
|
17
|
-
| 多 provider 切换 | 破坏式改造:`providers[]` + `activeProvider`,自动迁移旧配置;CLI `--provider <name>` + TUI `/provider <name>` |
|
|
18
|
-
| 预算超限行为 | 回合结束检查 + 提示(不硬中断流式输出) |
|
|
19
|
-
| 日志粒度 | 运行日志 + 日期滚动(保留 7 天) |
|
|
20
|
-
|
|
21
|
-
## 模块设计
|
|
22
|
-
|
|
23
|
-
### 1. 多 Provider(破坏式改造 + 自动迁移)
|
|
24
|
-
|
|
25
|
-
#### 1.1 配置结构(src/config.ts)
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
export interface ProviderConfig {
|
|
29
|
-
name?: string
|
|
30
|
-
type?: "openai-compatible" | "openai" | "ollama"
|
|
31
|
-
baseURL: string
|
|
32
|
-
apiKey: string
|
|
33
|
-
defaultModel?: string
|
|
34
|
-
contextWindow?: number
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface AppConfig {
|
|
38
|
-
providers?: ProviderConfig[]
|
|
39
|
-
activeProvider?: string
|
|
40
|
-
instructions?: string[]
|
|
41
|
-
disabledSkills?: string[]
|
|
42
|
-
permission?: "allow-all" | "ask"
|
|
43
|
-
compaction?: { model?: string; autoContinue?: boolean }
|
|
44
|
-
webSearchURL?: string
|
|
45
|
-
webFetchURL?: string
|
|
46
|
-
sampling?: { temperature?: number; maxTokens?: number; topP?: number }
|
|
47
|
-
budget?: { maxCostUSD?: number }
|
|
48
|
-
pricing?: Record<string, { inputPerMillion: number; outputPerMillion: number }>
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**移除** `provider?: ProviderConfig`(旧字段)。
|
|
53
|
-
|
|
54
|
-
#### 1.2 自动迁移
|
|
55
|
-
|
|
56
|
-
- `migrateLegacyConfig(config: AppConfig): { config: AppConfig; migrated: boolean }`
|
|
57
|
-
- 检测 `config.provider`(旧单对象)存在 → 转为 `providers: [{ name: "default", ...旧字段 }]` + `activeProvider: "default"`,删除 `provider` 字段,`migrated = true`
|
|
58
|
-
- `loadConfig()` 调用迁移并在 migrated 时 `saveConfig` 写回磁盘(一次性迁移;cachedConfig 缓存迁移后对象)
|
|
59
|
-
- 导出 `getActiveProvider(config?: AppConfig): ProviderConfig | undefined`(activeProvider 名称匹配;未设 activeProvider 且 providers 长度 1 → 返回该 provider;无匹配 → undefined)
|
|
60
|
-
|
|
61
|
-
#### 1.3 provider 解析(src/provider.ts)
|
|
62
|
-
|
|
63
|
-
- `resolveModelForProvider(provider: ProviderConfig, modelId?)` 签名不变(参数类型改为新接口)
|
|
64
|
-
- `resolveModel(modelId?, providerName?)`:取 `getActiveProvider`,providerName 覆盖时按名字查找(找不到报错 `Provider "x" not found`)
|
|
65
|
-
- `getProviderNames(config): string[]`(供 CLI/TUI 展示)
|
|
66
|
-
|
|
67
|
-
#### 1.4 CLI(src/cli.ts)
|
|
68
|
-
|
|
69
|
-
- `parseFlags` 加 `--provider <name>`(`-p`?不,`-p` 已是 port。用 `--provider` 长参数)
|
|
70
|
-
- `chat`/`code` 分支:`resolveModel(modelId, providerOverride)` 生效——通过 runTui/runAgent 传 providerName,最终进 resolveModel
|
|
71
|
-
- runTui TuiOptions 加 `providerName?: string`;runAgent 加参数
|
|
72
|
-
- `models` 命令:显示 activeProvider(或 --provider 指定)的模型列表
|
|
73
|
-
- `setup` 重构:列出现有 providers(名称 + URL + 默认模型 + 当前标记),选项:切换默认 / 添加新 provider / 修改当前;新 provider 信息收集复用现有流程(type/URL/key/model)
|
|
74
|
-
- `mcp`/`skills`/`history` 等不涉及 provider,不变
|
|
75
|
-
|
|
76
|
-
#### 1.5 TUI(src/tui-chat.ts)
|
|
77
|
-
|
|
78
|
-
- `/provider`:无参列出 providers(当前标记 ←),有参切换 activeProvider(saveConfig + sysMsg)
|
|
79
|
-
- `/model`:显示当前 provider 的模型列表(fetchModels 用 activeProvider 的 baseURL/apiKey)
|
|
80
|
-
- 状态栏/启动信息展示当前 provider 名(可选,保持精简)
|
|
81
|
-
|
|
82
|
-
#### 1.6 serve(src/serve.ts)
|
|
83
|
-
|
|
84
|
-
- 所有 `config.provider?.xxx` 引用改为 `getActiveProvider(config)?.xxx`
|
|
85
|
-
- `/v1/models`、`/v1/context` 等同理
|
|
86
|
-
- `body.provider` 可选:指定 provider 名(覆盖 activeProvider)
|
|
87
|
-
|
|
88
|
-
### 2. 采样参数 config(src/config.ts + src/agent.ts)
|
|
89
|
-
|
|
90
|
-
- `AppConfig.sampling?: { temperature?: number; maxTokens?: number; topP?: number }`
|
|
91
|
-
- `runOnceCore`:options 为空(或字段 undefined)时回退 `loadConfig().sampling` 对应字段
|
|
92
|
-
- 优先级:API 请求显式 options > config.sampling > 模型默认
|
|
93
|
-
- 实现:在 runOnceCore 开头合并 `const effective = { temperature: options?.temperature ?? sampling?.temperature, ... }`(仅当字段非 undefined 时传 streamText)
|
|
94
|
-
|
|
95
|
-
### 3. 预算上限(src/agent.ts + tui-chat.ts + serve.ts)
|
|
96
|
-
|
|
97
|
-
- `AppConfig.budget?: { maxCostUSD?: number }`
|
|
98
|
-
- 成本累计:tracker 已有 totalInputTokens/totalOutputTokens;新增 `tracker.usage` 或直接在 runOnce 结束后估算
|
|
99
|
-
- `runOnceCore` 结束(onRunFinish 前):`const maxCost = loadConfig().budget?.maxCostUSD`;若 `estimateCost({ totalInput, totalOutput }, price) > maxCost` → 置 `budgetExceeded = true`,onRunFinish 的 info 加 `budgetExceeded?: boolean` 字段
|
|
100
|
-
- TUI onRunFinish:budgetExceeded 时完成消息追加"⚠ 已达到预算上限(约 $X / $Y),/budget 查看或调高"
|
|
101
|
-
- `/budget` 命令:无参显示 `已用约 $X / 上限 $Y(未设置则提示)`;`/budget <n>` 设置上限
|
|
102
|
-
- serve:done 事件加 `budget_exceeded` 字段
|
|
103
|
-
- 注意:estimateCost 需要 getModelPrice(异步)——runOnceCore 结束时 await
|
|
104
|
-
|
|
105
|
-
### 4. 日志系统(新模块 src/logger.ts)
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
export function initLogger(): void // 创建 ~/.min-agent/logs/ 目录 + 清理 7 天前日志
|
|
109
|
-
export function log(level: "info" | "warn" | "error", msg: string): void // 追加到当天日志文件
|
|
110
|
-
export function logToolCall(name: string, input: unknown): void
|
|
111
|
-
export function logToolResult(name: string, output: unknown): void
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
- 文件:`~/.min-agent/logs/min-agent.YYYY-MM-DD.log`(日期滚动,用 `getConfigDir()`)
|
|
115
|
-
- 追加写(`writeFileSync(..., { flag: "a" })`),日志行格式:`[HH:mm:ss] [level] msg`(JSON 参数摘要截断 200 字符)
|
|
116
|
-
- 清理:initLogger 时删除 7 天前的日志文件
|
|
117
|
-
- 记录点:
|
|
118
|
-
- agent.ts `runOnceCore` 开头:`log("info", `run start mode=${mode} model=${model.modelId} messages=${messages.length}`)`
|
|
119
|
-
- tool-call 事件:`logToolCall`
|
|
120
|
-
- tool-result 事件:`logToolResult`
|
|
121
|
-
- 错误:`log("error", ...)`(onStreamError / catch 路径)
|
|
122
|
-
- runOnceCore 结束:`log("info", `run end steps=${stepCount} tokens=...`)`
|
|
123
|
-
- cli.ts 启动:`log("info", "min-agent started")`
|
|
124
|
-
- 不记录消息内容(隐私),只记录数量与工具参数摘要
|
|
125
|
-
|
|
126
|
-
### 5. CI(新文件 .github/workflows/ci.yml)
|
|
127
|
-
|
|
128
|
-
```yaml
|
|
129
|
-
name: CI
|
|
130
|
-
on:
|
|
131
|
-
push:
|
|
132
|
-
branches: [master]
|
|
133
|
-
pull_request:
|
|
134
|
-
|
|
135
|
-
jobs:
|
|
136
|
-
test:
|
|
137
|
-
runs-on: ubuntu-latest
|
|
138
|
-
steps:
|
|
139
|
-
- uses: actions/checkout@v4
|
|
140
|
-
- uses: oven-sh/setup-bun@v2
|
|
141
|
-
with:
|
|
142
|
-
bun-version: latest
|
|
143
|
-
- run: bun install --frozen-lockfile
|
|
144
|
-
- run: bun run typecheck
|
|
145
|
-
- run: cd packages/min-agent && bun test
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
(仓库根为 bun workspaces monorepo;typecheck 脚本在根 package.json)
|
|
149
|
-
|
|
150
|
-
### 6. /plan 计划模式(src/agent.ts + tui-chat.ts)
|
|
151
|
-
|
|
152
|
-
- `RunOptions` 加 `planMode?: boolean`
|
|
153
|
-
- `runOnceCore` 的 `buildTools` 结果在 planMode 时过滤:
|
|
154
|
-
- 移除:`bash`、`write`、`edit`、`apply_patch`(写/执行类)
|
|
155
|
-
- 保留:`read`、`glob`、`grep`、`web_search`、`web_fetch`、`todo`、`question`、`explore`(内部只读 bash)、`skill`、mcp 工具、memory 工具、plugin 工具(保留——插件只读性未知,但删除会让插件用户困惑;保留并说明)
|
|
156
|
-
- TUI `/plan`:切换 planMode 状态(`let planMode = false`):
|
|
157
|
-
- 进入:sysMsg 提示"计划模式:仅只读工具可用,输出计划后再次输入 /plan 确认并执行"
|
|
158
|
-
- 退出:sysMsg 提示"已退出计划模式"
|
|
159
|
-
- executeAgent 调 runOnce 时传 `{ ...(planMode ? { planMode: true } : {}) }`(与 options 合并)
|
|
160
|
-
- `/redo` 等不受影响
|
|
161
|
-
|
|
162
|
-
### 7. 文档完善
|
|
163
|
-
|
|
164
|
-
- `README.md`:Configuration 区更新(providers[] 结构 + 迁移说明 + sampling/budget 字段)、Commands 区加 `--provider`、交互命令加 `/provider` `/budget` `/plan`、日志位置说明
|
|
165
|
-
- `docs/API.md`:`/v1/chat` 请求加 `provider` 字段;done 事件加 `budget_exceeded`
|
|
166
|
-
- `src/cli.ts` printUsage:`--provider`、setup 新流程说明
|
|
167
|
-
- `/help` 与 `slash-commands.ts` 菜单同步(/provider、/budget、/plan)
|
|
168
|
-
|
|
169
|
-
## 错误处理
|
|
170
|
-
|
|
171
|
-
| 场景 | 行为 |
|
|
172
|
-
|------|------|
|
|
173
|
-
| --provider 指定不存在 | 报错 `Provider "x" not found` exit 1 |
|
|
174
|
-
| 无 providers 且无旧配置 | isConfigured false,"Run: min-agent setup" |
|
|
175
|
-
| 预算超限 | 回合结束提示,不中断;/budget 调高后继续 |
|
|
176
|
-
| 日志写入失败 | 静默忽略(log 内 try/catch),不阻塞主流程 |
|
|
177
|
-
| 迁移写回失败 | 内存中继续使用迁移后配置,忽略写回错误 |
|
|
178
|
-
|
|
179
|
-
## 测试
|
|
180
|
-
|
|
181
|
-
| 文件 | 覆盖 |
|
|
182
|
-
|------|------|
|
|
183
|
-
| `tests/config.test.ts`(改) | 旧格式自动迁移(provider → providers[])、activeProvider 解析、getActiveProvider |
|
|
184
|
-
| `tests/provider.test.ts`(改) | resolveModel 按 activeProvider / providerName 覆盖 / 不存在报错 |
|
|
185
|
-
| `tests/logger.test.ts`(新) | 日志写入格式、日期滚动文件名、7 天清理(mock 时间或直接创建旧文件) |
|
|
186
|
-
| `tests/serve.test.ts`(改) | body.provider 覆盖、done 事件 budget_exceeded |
|
|
187
|
-
| 预算逻辑 | runOnceCore 结束检查(纯函数提取 `shouldStopForBudget(totalTokens, price, maxCostUSD)` 可测) |
|
|
188
|
-
| 采样回退 | config.sampling 生效(无 API options 时)—— 函数级验证 |
|
|
189
|
-
|
|
190
|
-
验证命令(`packages/min-agent` 下):`bun test`、`bun run typecheck`(仓库根)、手动冒烟。
|
|
191
|
-
|
|
192
|
-
## 文档同步清单
|
|
193
|
-
|
|
194
|
-
- `README.md`:Configuration 区、Commands、交互命令、日志
|
|
195
|
-
- `docs/API.md`:provider 字段、budget_exceeded
|
|
196
|
-
- `src/cli.ts` printUsage、TUI `/help`、`src/tui/slash-commands.ts` 菜单
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# 输入框光标编辑设计
|
|
2
|
-
|
|
3
|
-
日期:2026-08-16
|
|
4
|
-
状态:已批准(用户确认设计)
|
|
5
|
-
|
|
6
|
-
## 问题
|
|
7
|
-
|
|
8
|
-
min-agent TUI 输入框的光标固定在文本末尾:
|
|
9
|
-
- 左右键被显式忽略(InputBar.tsx:172),无法移动光标
|
|
10
|
-
- 点击输入框无任何反应(未启用终端鼠标协议)
|
|
11
|
-
- 用户无法在文本中间插入/删除内容
|
|
12
|
-
|
|
13
|
-
## 目标
|
|
14
|
-
|
|
15
|
-
1. 左右键移动光标(跨折行边界可达全文)
|
|
16
|
-
2. 点击输入框定位光标
|
|
17
|
-
3. 所有编辑操作(输入/退格/删除)在光标处进行
|
|
18
|
-
|
|
19
|
-
## 设计
|
|
20
|
-
|
|
21
|
-
### 1. 光标状态
|
|
22
|
-
|
|
23
|
-
InputBar 新增 `caretIndex` state,基于 code point 数组索引(`Array.from(value)`),兼容 CJK/emoji。
|
|
24
|
-
|
|
25
|
-
- 左右键:`caretIndex ± 1`;行首左移 → 上一行尾,行尾右移 → 下一行头
|
|
26
|
-
- 输入字符:插入到 `caretIndex`
|
|
27
|
-
- 退格:删 `caretIndex - 1`;Delete:删 `caretIndex`
|
|
28
|
-
- Enter/提交、斜杠菜单逻辑不变
|
|
29
|
-
- menuOpen 时:上下键选菜单,左右键不移动光标(保持现状)
|
|
30
|
-
|
|
31
|
-
### 2. 点击定位(SGR 鼠标协议)
|
|
32
|
-
|
|
33
|
-
- 启用:`\x1b[?1000h\x1b[?1006h`(按下事件 + SGR 格式);卸载/卸载组件时:`\x1b[?1000l\x1b[?1006l`
|
|
34
|
-
- 鼠标序列在 InputBar 已有的 raw stdin 监听中解析(与 Kitty Shift+Enter 共用通道);useInput 中过滤残留片段(类似 `isKittySequenceFragment`)
|
|
35
|
-
- 输入框绝对位置:输入框渲染在 App footer,其起始行 = `terminalRows - 输入框视觉行数 + 1`;点击行落在输入框内容区内才处理
|
|
36
|
-
- 坐标换算:点击列 → 行内字符索引,逐字符按 `displayWidth` 累加,找到最接近点击列的边界(全角字符取最近边界)
|
|
37
|
-
|
|
38
|
-
### 3. 光标渲染与硬件光标
|
|
39
|
-
|
|
40
|
-
- `█` 块移动到 `caretIndex` 处(渲染该行时插入);空值时在 gutter 后显示 `█`
|
|
41
|
-
- caret.ts 的 rowsAbove 计算:光标不再恒在最后一行,`rowsAbove = 输入框总行数 - 光标行号 + border/hint/menu 行数`;caret.ts 本身无需修改(接受任意 rowsAbove/column)
|
|
42
|
-
|
|
43
|
-
### 4. 文件改动
|
|
44
|
-
|
|
45
|
-
| 文件 | 改动 |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `src/tui/InputBar.tsx` | caretIndex state、左右键/插入/退格逻辑、`█` 渲染位置、鼠标启用与解析 |
|
|
48
|
-
| `src/tui/mouse.ts`(新) | 鼠标协议启用/禁用序列、`parseSgrMouse()` 纯函数 |
|
|
49
|
-
| `src/tui/caret-pos.ts`(新) | 视觉行 → 字符索引换算、光标跨行移动等纯函数 |
|
|
50
|
-
|
|
51
|
-
### 5. 测试
|
|
52
|
-
|
|
53
|
-
- `parseSgrMouse` 序列解析(按下事件、坐标提取、非鼠标序列返回 null)
|
|
54
|
-
- 点击坐标 → caretIndex 换算(含 CJK 宽度、折行、空行)
|
|
55
|
-
- 左右键跨行移动边界(行首/行尾)
|
|
56
|
-
- 插入/退格/删除在光标处
|
|
57
|
-
|
|
58
|
-
## 不做(YAGNI)
|
|
59
|
-
|
|
60
|
-
- 鼠标选中文本、拖拽选择
|
|
61
|
-
- 点击斜杠菜单项选择
|
|
62
|
-
- 上下键行间移动(左右键跨行已覆盖)
|
|
63
|
-
- Home/End、Ctrl+A/E 等快捷键
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# 消息区鼠标拖拽选择与自动复制设计
|
|
2
|
-
|
|
3
|
-
日期:2026-08-17
|
|
4
|
-
状态:已批准(用户确认方案 A:应用内拖拽选择 + 自动复制)
|
|
5
|
-
|
|
6
|
-
## 问题
|
|
7
|
-
|
|
8
|
-
min-agent TUI 启用了 SGR 鼠标跟踪(1000 模式),终端原生拖拽选择被鼠标协议接管,
|
|
9
|
-
消息区(内容区)文本无法用鼠标选取、复制。参考 opencode / claude code 的交互:
|
|
10
|
-
在消息区按住左键拖拽高亮文本,松开自动复制到系统剪贴板。
|
|
11
|
-
|
|
12
|
-
## 目标
|
|
13
|
-
|
|
14
|
-
1. 消息区按住左键拖拽 → 选区实时高亮(反显)
|
|
15
|
-
2. 松开 → 自动复制到系统剪贴板(不占用 Ctrl+C,它仍用于退出)
|
|
16
|
-
3. 选区保持高亮直到下次点击/按键,状态栏短暂提示"已复制"
|
|
17
|
-
4. 原有功能不回归:单击展开工具行、滚轮滚动、输入框点击定位光标、Ctrl+C 退出
|
|
18
|
-
|
|
19
|
-
## 设计
|
|
20
|
-
|
|
21
|
-
### 1. 鼠标协议升级(`src/tui/mouse.ts`)
|
|
22
|
-
|
|
23
|
-
- `MOUSE_ENABLE` = `\x1b[?1000h\x1b[?1002h\x1b[?1006h`(1002 = 按住按钮时报告拖拽移动)
|
|
24
|
-
- `MOUSE_DISABLE` 同步加 `?1002l`
|
|
25
|
-
- 新增 `MOTION = 32` 常量:SGR 移动事件按钮码 = 基础按钮 + 32;修饰键(Shift=4 等)
|
|
26
|
-
与基础按钮同在一个字段,`button & 15 === 0` 判断为无修饰键的纯左键
|
|
27
|
-
|
|
28
|
-
### 2. 剪贴板写入(`src/clipboard.ts`)
|
|
29
|
-
|
|
30
|
-
新增 `writeClipboard(text: string): boolean`,平台分发与现有读剪贴板一致:
|
|
31
|
-
- macOS:`pbcopy`(execSync input 管道,避免 shell 引号问题)
|
|
32
|
-
- Linux:`xclip -selection clipboard`,失败回退 `xsel --clipboard --input`
|
|
33
|
-
- Windows:PowerShell `Set-Clipboard -Value ([Console]::In.ReadToEnd())`
|
|
34
|
-
|
|
35
|
-
失败静默返回 false,不弹错。
|
|
36
|
-
|
|
37
|
-
### 3. 选区纯函数模块(`src/tui/selection.ts`,新)
|
|
38
|
-
|
|
39
|
-
全部按 `charWidth` 处理 CJK 宽度,输入为 ANSI 行与 0-based 显示列:
|
|
40
|
-
|
|
41
|
-
- `stripAnsi(line)`:去掉 `\x1b[...m` 颜色码
|
|
42
|
-
- `applySelection(line, start, end)`:在选区边界注入 `\x1b[7m` / `\x1b[27m`(反显),
|
|
43
|
-
保留原有样式;选区内遇到 `\x1b[0m`(RESET)时在其后重注入 `\x1b[7m`,保证跨样式
|
|
44
|
-
重置时高亮不断裂。与选区有交叠的字符(含全角)整字符高亮,不动字符边界
|
|
45
|
-
- `extractText(line, start, end)`:按显示列切出选区纯文本(剥 ANSI)
|
|
46
|
-
- `selectionRanges(rows, anchor, current)`:把拖拽锚点/当前点(0-based 坐标:
|
|
47
|
-
行 = `areaRows` 索引,列 = 行内显示列,由 MessageList 从终端坐标换算)归一为
|
|
48
|
-
与 `rows` 对齐的逐行选区,流式语义:
|
|
49
|
-
- 同行:`[min(col), max(col))`
|
|
50
|
-
- 跨行(向下拖):首行 `[anchor.col, 行尾)`,中间行整行,末行 `[0, current.col)`
|
|
51
|
-
- 跨行(向上拖):对称反转
|
|
52
|
-
- `rows` 中 null(指示行/空白行)跳过;坐标夹在消息区范围内
|
|
53
|
-
- 返回 `(start,end | null)[]`,与 `rows` 对齐
|
|
54
|
-
|
|
55
|
-
### 4. MessageList 集成(`src/tui/MessageList.tsx`)
|
|
56
|
-
|
|
57
|
-
**状态**:`selection: { anchor: Cell; cur: Cell } | null`(终端 1-based 坐标);
|
|
58
|
-
`pendingPress` ref 记录按下点,用于区分单击与拖拽——按下只记录,出现移动事件才
|
|
59
|
-
激活选区,松开未移动才算点击(工具行展开,逻辑从"按下即触发"改为"松开且未移动")。
|
|
60
|
-
|
|
61
|
-
**行映射**:渲染时用与 MessageRow 相同的逻辑(`sliceLines`)构建 `areaRows:
|
|
62
|
-
(string | null)[]`,与终端行一一对应(1 行 = 1 项,指示行/用户消息空行 = null)。
|
|
63
|
-
点击与选区换算共用该映射,选区随每次渲染实时重算(流式输出时选区跟随内容)。
|
|
64
|
-
|
|
65
|
-
**坐标换算**:行号(1-based)→ `areaRows` 索引(减 1);列 = 终端列 - 2(全部行有
|
|
66
|
-
`paddingLeft={1}`,第一字符在终端列 2),夹到行宽内。
|
|
67
|
-
|
|
68
|
-
**高亮渲染**:每个消息 part 把选区范围换算为"shown 行空间",以新 prop
|
|
69
|
-
`selection?: (CellRange | null)[]` 传入 `MessageRow`,按角色逐行注入反显
|
|
70
|
-
(用户消息首行的 "> "、工具行首行的 "⚡ name ▸ " 前缀单独处理,选区可跨前缀)。
|
|
71
|
-
memo 比较追加 `prev.selection === next.selection`;无选区时传 `undefined`,
|
|
72
|
-
身份稳定,不影响流式渲染性能。
|
|
73
|
-
|
|
74
|
-
**松手复制**:提取各选中行文本(剥 ANSI、按列切片)以 `\n` 拼接 →
|
|
75
|
-
`writeClipboard` → 选区保持高亮,直到下次点击(按下即清)、按键或滚轮滚动时清除;
|
|
76
|
-
复制成功且文本非空时调用 `onCopyNotice` 回调。
|
|
77
|
-
|
|
78
|
-
**事件过滤**:仅处理无修饰键(`button & 15 === 0`)且落在消息区(`row ≤ areaRows.length`)
|
|
79
|
-
的事件;带修饰键(Shift)的拖拽不接管,留给支持原生 Shift 选择的终端。
|
|
80
|
-
|
|
81
|
-
### 5. 复制反馈
|
|
82
|
-
|
|
83
|
-
- `TuiState` 增 `copyNotice?: string`;`TuiRenderer.showCopyNotice(text)` 设置并在
|
|
84
|
-
2.5s 后自动清除
|
|
85
|
-
- App 增 `onCopyNotice` 回调 prop 传给 MessageList(实现为 TuiRenderer.showCopyNotice)
|
|
86
|
-
- StatusBar:把提示渲染在分隔线行的右端(占用分隔线自身宽度,不增加行数,布局不跳动)
|
|
87
|
-
|
|
88
|
-
### 6. 文件改动
|
|
89
|
-
|
|
90
|
-
| 文件 | 改动 |
|
|
91
|
-
|---|---|
|
|
92
|
-
| `src/tui/mouse.ts` | 启用/禁用序列加 1002、`MOTION` 常量 |
|
|
93
|
-
| `src/tui/selection.ts`(新) | 选区纯函数:stripAnsi/applySelection/extractText/selectionRanges |
|
|
94
|
-
| `src/clipboard.ts` | `writeClipboard()` |
|
|
95
|
-
| `src/tui/MessageList.tsx` | selection state、press/drag/release 事件流、areaRows 映射、MessageRow 高亮渲染、onCopyNotice |
|
|
96
|
-
| `src/tui/types.ts` | `TuiState.copyNotice` |
|
|
97
|
-
| `src/tui/index.tsx` | `TuiRenderer.showCopyNotice()`、App 增 onCopyNotice prop |
|
|
98
|
-
| `src/tui/App.tsx` | 传 onCopyNotice 给 MessageList |
|
|
99
|
-
| `src/tui/StatusBar.tsx` | 分隔线行右端渲染"✓ 已复制"提示 |
|
|
100
|
-
| `src/tui-chat.ts` | `/help` 快捷键区加"鼠标拖拽 选择文本并自动复制" |
|
|
101
|
-
|
|
102
|
-
### 7. 测试
|
|
103
|
-
|
|
104
|
-
- `tests/selection.test.ts`(新):stripAnsi / extractText(含 CJK 边界)/
|
|
105
|
-
applySelection(注入、RESET 重注入、空选区不变)/ selectionRanges(同行、
|
|
106
|
-
向下、向上、null 行跳过、越界夹取)
|
|
107
|
-
- `tests/mouse.test.ts`:补拖拽移动事件解析(button 32)、启用序列含 `?1002h`
|
|
108
|
-
- 运行 `bun run typecheck`(仓库根)与 `bun test`(packages/min-agent)
|
|
109
|
-
|
|
110
|
-
## 不做(YAGNI)
|
|
111
|
-
|
|
112
|
-
- 双击选词、三击选段
|
|
113
|
-
- 键盘选择模式(tmux 风格)
|
|
114
|
-
- 跨行选区自动去除 "> " / "⚡ name ▸ " 前缀(复制所见即所得)
|
|
115
|
-
- 复制保留 Markdown 格式/富文本
|
|
116
|
-
- 选区随流式输出"追字"(选区锚定终端坐标,内容变化时重算)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# 配置与 HTTP 对齐(第 2 批)— 设计文档
|
|
2
|
-
|
|
3
|
-
**日期:** 2026-08-20
|
|
4
|
-
**状态:** 已批准(范围来自五批拆分;语义对齐现有 TUI)
|
|
5
|
-
**范围:** 项目级 config/记忆、`exec` 写 history 与 `--resume`、HTTP 的 plan/budget/diff/redo 与确认/提问回调
|
|
6
|
-
|
|
7
|
-
后续批次不在本文:MCP resources/prompts、沙箱、exec JSON 流、原生 Anthropic/Google、hooks、Browser/LSP、主题。
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 设计决策
|
|
12
|
-
|
|
13
|
-
| 决策 | 结论 |
|
|
14
|
-
|------|------|
|
|
15
|
-
| 项目配置可覆盖字段 | `activeProvider`、`defaultModel`、`sampling`、`budget`、`permission`、`compaction`;**不**写 providers/apiKey |
|
|
16
|
-
| 合并规则 | 项目字段有值则覆盖全局;sampling/budget/compaction 浅合并 |
|
|
17
|
-
| 密钥 | 仍只存在 `~/.min-agent/config.json` |
|
|
18
|
-
| 项目记忆 | `.min-agent/memory.json`;system prompt 先项目后全局 |
|
|
19
|
-
| 记忆默认写入 | 存在 `.min-agent/` 则默认 project,否则 global;可显式 `scope` |
|
|
20
|
-
| exec 会话 | 每次结束后 `saveSession`;`--resume <id>` 加载后追加本轮消息 |
|
|
21
|
-
| HTTP plan | `POST /v1/chat` 增加 `plan_mode` |
|
|
22
|
-
| HTTP budget/diff/redo | 独立端点,语义对齐 `/budget` `/diff` `/redo` |
|
|
23
|
-
| 确认/提问 | serve 默认仍自动批准;请求 `auto_approve: false` 且必须 `stream: true`;SSE `confirm`/`question` + `POST /v1/approvals` `/v1/answers`;超时 5 分钟视为拒绝/跳过 |
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## 1. 项目级配置
|
|
28
|
-
|
|
29
|
-
`ProjectConfig` 增加上述字段。`mergeProjectOverGlobal` + `getEffectiveConfig()`。
|
|
30
|
-
|
|
31
|
-
`getActiveProvider` / 采样 / 预算 / 权限 / compaction 读 effective config。`saveConfig` 仍写全局;`saveProjectConfig` 写项目。TUI `/budget [n] [--project]` 可写项目。
|
|
32
|
-
|
|
33
|
-
## 2. 项目级记忆
|
|
34
|
-
|
|
35
|
-
文件格式与全局相同。列表/删除按 scope 编索引。HTTP `GET /v1/memory` 增加 `project_memories`(原 `memories` 仍为全局)。
|
|
36
|
-
|
|
37
|
-
## 3. exec
|
|
38
|
-
|
|
39
|
-
`runAgent` 支持 `resumeSessionId`。结束后打印 `min-agent exec --resume <id>`。会话不存在则退出错误。
|
|
40
|
-
|
|
41
|
-
## 4. HTTP
|
|
42
|
-
|
|
43
|
-
- `plan_mode` → `RunOptions.planMode`
|
|
44
|
-
- `GET/POST /v1/budget`(POST 可 `scope: project`)
|
|
45
|
-
- `GET /v1/diff`
|
|
46
|
-
- `POST /v1/chat/redo` `{ session_id }`:取出最后一条用户文本再跑一轮
|
|
47
|
-
- 确认门闩:`AsyncLocalStorage`,不改变 process 级 `-y`;SSE 等待配套 POST
|