jinzd-ai-cli 0.1.65 → 0.1.67

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/CLAUDE.md CHANGED
@@ -1,1864 +1,1855 @@
1
- # ai-cli 项目说明
2
-
3
- ## 项目简介
4
-
5
- 一个跨平台的 REPL 风格 AI 对话工具,支持多个主流 AI 提供商(OpenAI、Claude、Gemini、DeepSeek、智谱清言、Kimi),
6
- 带有 **AI 工具调用(Agentic)** 能力,支持执行 bash 命令、读写文件、运行交互式程序、流式生成大文档。
7
- 代理支持(`proxy` 配置字段 + 环境变量),Gemini 完整支持(2.5 Pro/Flash,array items schema 修复)。
8
- **MCP 协议支持**:可接入外部 MCP 服务器,自动发现并注册工具,无缝融入 agentic 循环。
9
- 设计上可扩展至 Electron/Tauri 桌面 GUI。
10
-
11
- > 当前代码基线(2026-03-12):
12
- > - 版本:`v0.1.62`
13
- > - 默认 REPL 命令:35
14
- > - 当前注册的内置工具:17
15
- > - `stream_to_file` 已正式接入 `ToolRegistry`
16
- > - `ora` 依赖已从 `package.json` 移除,spinner 使用 `src/repl/renderer.ts` 中的自实现版本
17
-
18
- ## 技术栈
19
-
20
- - **语言**: TypeScript (ESM,`"type": "module"`,导入须用 `.js` 扩展名)
21
- - **运行时**: Node.js >= 20
22
- - **构建工具**: tsup `dist/index.js`
23
- - **包管理**: npm
24
-
25
- ## 项目结构
26
-
27
- ```
28
- src/
29
- ├── index.ts # CLI 入口点 (Commander bin 脚本)
30
- ├── core/
31
- │ ├── types.ts # 所有共享 TypeScript 接口(ChatRequest timeout/_extraMessages)
32
- │ ├── constants.ts # VERSION 等常量
33
- │ ├── errors.ts # AiCliError ProviderError / AuthError / RateLimitError 等
34
- └── event-bus.ts # 类型安全的 EventEmittersession.start/end, message.before/after
35
- ├── providers/
36
- ├── base.ts # 抽象 BaseProvider(chat/chatStream/validateApiKey/listModels/initialize)
37
- │ ├── registry.ts # ProviderRegistry(initialize 时注入 apiKey + baseUrl + timeout
38
- │ ├── openai-compatible.ts # DeepSeek/Zhipu/Kimi 共用基类;实现 chatWithTools + buildToolResultMessages
39
- ├── claude.ts # Anthropic SDK provider
40
- ├── gemini.ts # Google Generative AI provider(role: assistant → model)
41
- │ ├── openai.ts # OpenAI provider(GPT-5.4/5/4.1/4o/o3/o4-mini
42
- ├── deepseek.ts / zhipu.ts / kimi.ts # 继承 OpenAICompatibleProvider;deepseek/kimi 覆写 chatWithTools 实现虚假声明检测
43
- ├── config/
44
- │ ├── schema.ts # Zod schema(含 timeouts / customBaseUrls / defaultModels
45
- │ ├── config-manager.ts # 读写 ~/.aicli/config.json,三层优先级:env > file > default
46
- └── env-loader.ts # AICLI_API_KEY_* 等环境变量映射
47
- ├── session/
48
- │ ├── session.ts # Session(addMessage / clear / compact / fork / toJSON / fromJSON)
49
- │ └── session-manager.ts # CRUD + forkSession for ~/.aicli/history/*.json
50
- ├── repl/
51
- │ ├── repl.ts # REPL 循环(MAX_TOOL_ROUNDS=25,handleChatWithTools agentic loop)
52
- ├── renderer.ts # 终端输出(renderStream / renderResponse 均不加前置 \n)
53
- │ ├── theme.ts # 集中式主题系统(dark/light/custom 主题 + 语义色槽 Proxy 导出)
54
- ├── dev-state.ts # 开发状态交接(provider/model 切换时快照生成、save/load/clear)
55
- ├── setup-wizard.ts # @inquirer/prompts 首次运行交互式设置
56
- └── commands/
57
- └── index.ts # CommandRegistry + 35个命令(/help /about /provider /model /clear /compact /plan /session /system /context /status /search /undo /export /copy /cost /init /skill /tools /plugins /mcp /config /checkpoint /review /commands /test /scaffold /add-dir /memory /doctor /bug /think /diff /fork /exit)
58
- ├── custom-commands.ts # CustomCommandManager(~/.aicli/commands/*.md 用户自定义命令)
59
- ├── skills/
60
- ├── types.ts # Skill/SkillMeta 接口、parseSkillFile(YAML frontmatter 解析)
61
- │ └── manager.ts # SkillManager(加载/激活/停用技能,工具白名单过滤)
62
- ├── mcp/
63
- ├── types.ts # MCP 协议类型定义(JSON-RPC、工具 schema、服务器配置)
64
- ├── client.ts # McpClient(单个 MCP 服务器 STDIO 连接,JSON-RPC 通信)
65
- └── manager.ts # McpManager(多服务器管理,工具发现与注册,MCP→Tool 转换)
66
- └── tools/
67
- ├── types.ts # ToolDefinition / ToolCall / ToolResult / DangerLevel / getDangerLevel / isFileWriteTool
68
- ├── registry.ts # ToolRegistry(注册全部内置工具,共17个)
69
- ├── hooks.ts # 工具执行钩子(runHook,shell 命令模板替换 + execSync)
70
- ├── permissions.ts # 基于规则的工具权限控制(checkPermission,首匹配规则)
71
- ├── executor.ts # ToolExecutor(确认逻辑 + 批量文件写入预览 + batchConfirm + hooks + permissions)
72
- └── builtin/
73
- ├── bash.ts # bash 工具(Windows: PowerShell + Buffer→UTF-8;Unix: $SHELL)
74
- ├── read-file.ts # read_file 工具
75
- ├── write-file.ts # write_file 工具
76
- ├── list-dir.ts # list_dir 工具
77
- ├── run-interactive.ts # run_interactive 工具(spawn 直连可执行文件,stdin_lines 依次输入)
78
- ├── ask-user.ts # ask_user 工具(agentic 循环中向用户提问,等待文本回答)
79
- ├── write-todos.ts # write_todos 工具(任务拆解与进度跟踪,终端实时渲染)
80
- ├── google-search.ts # google_search 工具(Google Custom Search API 搜索网页)
81
- ├── stream-to-file.ts # stream_to_file 工具(流式生成大文档并直接写入文件)
82
- ├── spawn-agent.ts # spawn_agent 工具(独立子代理 agentic 循环 + SubAgentExecutor
83
- └── run-tests.ts # run_tests 工具(自动检测项目类型、运行测试、JUnit XML 解析、结构化报告)
84
- ```
85
-
86
- ## 常用命令
87
-
88
- ```bash
89
- npm run dev # 开发模式(tsx 热重载)
90
- npm run build # 构建到 dist/index.js(ESM)和 dist-cjs/index.cjs(CJS)
91
- node dist/index.js # 运行
92
-
93
- # 子命令
94
- node dist/index.js providers # 列出所有 provider 及配置状态
95
- node dist/index.js sessions # 列出最近会话
96
- node dist/index.js config # 运行配置向导
97
- ```
98
-
99
- ## 打包为独立可执行文件
100
-
101
- 使用 `@yao-pkg/pkg` 将项目打包为无需 Node.js 环境即可运行的单文件可执行程序(约 56MB)。
102
-
103
- ```bash
104
- npm run pack:win # Windows x64 → release/ai-cli-win.exe
105
- npm run pack:mac # macOS arm64 → release/ai-cli-mac
106
- npm run pack:mac-x64 # macOS x64 → release/ai-cli-mac-x64
107
- npm run pack:linux # Linux x64 → release/ai-cli-linux
108
- npm run pack:all # 同时打包所有平台
109
- ```
110
-
111
- ### 打包原理
112
-
113
- - tsup 输出两套产物:
114
- - `dist/index.js`(ESM,供 `node` 直接运行)
115
- - `dist-cjs/index.cjs`(CJS + `noExternal: /.*/` 内联所有依赖,供 pkg 打包)
116
- - pkg 使用 `--options no-deprecation` 内嵌到二进制,消除 punycode 弃用警告
117
- - macOS/Linux 产物需在对应平台执行 `chmod +x` 后运行
118
-
119
- ### 关键兼容性问题(已解决)
120
-
121
- **ora pkg exe 中 Segfault**:`ora` spinner 在 `@yao-pkg/pkg` 打包的 Node 22 exe 里会触发段错误(exit code 139)。
122
- **解决方案**:完全移除 `ora` 依赖,在 `src/repl/renderer.ts` 中用原生 `setInterval` + `process.stdout.write('\r\x1b[2K')` 实现轻量 spinner,非 TTY 环境自动降级为空操作。
123
-
124
- ## 配置存储
125
-
126
- - **配置文件**: `~/.aicli/config.json`
127
- - **全局上下文**: `~/.aicli/AICLI.md`(或 `CLAUDE.md`,全局层级上下文)
128
- - **持久记忆**: `~/.aicli/memory.md`(AI 通过 `save_memory` 工具写入,跨会话自动加载)
129
- - **开发状态快照**: `~/.aicli/dev-state.md`(provider/model 切换时由前一 AI 自动生成,新模型启动后注入 system prompt)
130
- - **对话历史**: `~/.aicli/history/*.json`
131
- - **插件目录**: `~/.aicli/plugins/`(已实现,默认关闭,需 `allowPlugins: true` 显式启用)
132
-
133
- 当前已配置(用户机器):
134
- ```json
135
- {
136
- "defaultProvider": "deepseek",
137
- "apiKeys": { "deepseek": "sk-d89cdb5490844071ad250996b04dc7b0" },
138
- "customBaseUrls": { "deepseek": "https://api.deepseek.com" },
139
- "timeouts": { "deepseek": 60000 }
140
- }
141
- ```
142
-
143
- ### MCP 服务器配置
144
-
145
- `config.json` 中声明 `mcpServers` 字段,启动时自动连接、发现工具并注册。格式兼容 Claude Desktop。
146
-
147
- ```json
148
- {
149
- "mcpServers": {
150
- "filesystem": {
151
- "command": "npx",
152
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "D:/projects"],
153
- "timeout": 30000
154
- },
155
- "github": {
156
- "command": "node",
157
- "args": ["path/to/github-server.js"],
158
- "env": { "GITHUB_TOKEN": "ghp_xxx" }
159
- }
160
- }
161
- }
162
- ```
163
-
164
- MCP 工具名格式:`mcp__<serverId>__<toolName>`,如 `mcp__filesystem__read_file`。
165
- 所有 MCP 工具默认 `safe` 级别(用户主动配置即表示信任)。
166
-
167
- ## 环境变量(优先级高于配置文件)
168
-
169
- ```
170
- AICLI_API_KEY_OPENAI OpenAI API Key
171
- AICLI_API_KEY_CLAUDE Claude API Key
172
- AICLI_API_KEY_GEMINI Gemini API Key
173
- AICLI_API_KEY_DEEPSEEK DeepSeek API Key
174
- AICLI_API_KEY_ZHIPU 智谱 API Key
175
- AICLI_API_KEY_KIMI Kimi API Key
176
- AICLI_API_KEY_GOOGLESEARCH Google Custom Search API Key
177
- AICLI_GOOGLE_CX Google Search Engine ID (cx)
178
- AICLI_PROVIDER 默认 Provider ID
179
- AICLI_NO_STREAM 设为 1 禁用流式输出
180
- ```
181
-
182
- ## 层级上下文文件系统
183
-
184
- 类似 Gemini CLI `GEMINI.md` 层级机制,ai-cli 支持三层级上下文文件自动发现与拼接:
185
-
186
- ### 层级结构(按顺序拼接注入 system prompt)
187
-
188
- | 层级 | 路径 | 用途 |
189
- |------|------|------|
190
- | 全局层 | `~/.aicli/AICLI.md` (或 `CLAUDE.md`) | 所有项目通用的个人偏好 |
191
- | 项目层 | `<git-root>/AICLI.md` (或 `CLAUDE.md`) | 项目级规则(提交到 git 供团队共享) |
192
- | 子目录层 | `<cwd>/AICLI.md` (或 `CLAUDE.md`) | 当前工作子目录的特定指令 |
193
-
194
- ### 规则
195
-
196
- - 每层按候选文件名 `AICLI.md CLAUDE.md` 优先级查找,找到第一个即停止
197
- - 项目层通过 `git rev-parse --show-toplevel` 确定 git 仓库根目录;不在 git 仓库中时 projectRoot = cwd
198
- - 子目录层仅当 cwd ≠ projectRoot 时才加载(避免重复)
199
- - 所有层级内容用 `\n\n---\n\n` 分隔后注入 system prompt
200
- - 配置 `contextFile: false` 可禁用所有层级;设为具体文件名可回退到单文件行为
201
-
202
- ### 相关命令
203
-
204
- - `/context` — 查看当前加载的各层级详情
205
- - `/context reload` 重新加载上下文文件
206
-
207
- ## Tool Use(Agentic 工具调用)架构
208
-
209
- ### 工具一览
210
-
211
- | 工具名 | 危险级别 | 说明 |
212
- |---|---|---|
213
- | `bash` | write/safe/destructive(按命令判断) | PowerShell(Win) / $SHELL(Unix) 执行命令,Windows 强制 UTF-8 |
214
- | `read_file` | safe | 读取文件内容 |
215
- | `write_file` | write(需确认) | 写入文件 |
216
- | `edit_file` | write(需确认) | 精确字符串替换编辑文件 |
217
- | `list_dir` | safe | 列出目录内容 |
218
- | `grep_files` | safe | 正则搜索文件内容 |
219
- | `glob_files` | safe | glob 模式匹配文件路径 |
220
- | `run_interactive` | safe | spawn 直连可执行文件 + stdin_lines 依次输入,用于交互式程序(猜数游戏等) |
221
- | `web_fetch` | safe | 抓取网页内容并转为 Markdown(含私有 IP 防 SSRF) |
222
- | `save_last_response` | write(需确认) | 保存上一次 AI 回答到文件(tee 流式写盘) |
223
- | `stream_to_file` | write(需确认) | 流式生成大文档并直接写入文件,规避长文本被 token 截断 |
224
- | `save_memory` | safe | 将重要信息追加到 `~/.aicli/memory.md`,跨会话持久化,启动时自动注入 system prompt |
225
- | `ask_user` | safe | AI 在 agentic 循环中向用户提问,等待文本回答后继续执行 |
226
- | `write_todos` | safe | AI 拆解复杂任务为子任务列表,终端实时渲染进度(pending/in_progress/completed) |
227
- | `google_search` | safe | Google Custom Search API 搜索网页,需配置 API Key + Search Engine ID (cx) |
228
- | `spawn_agent` | safe | 委派独立子代理执行特定任务(隔离对话 + agentic 循环,write 自动确认,destructive 阻止) |
229
- | `run_tests` | safe | 运行项目测试(自动检测 Maven/Gradle/npm/pytest/cargo/go),JUnit XML 解析,结构化报告 |
230
- | `mcp__*` | safe | MCP 服务器暴露的动态工具(命名格式:`mcp__<serverId>__<toolName>`) |
231
-
232
- ### 危险级别与确认机制
233
-
234
- `src/tools/types.ts` `getDangerLevel()` 判断:
235
- - `safe`自动执行,无需确认
236
- - `write`显示 `✎ Write operation:` 并等待用户按 `y/N`
237
- - `destructive` → 显示 `⚠ DESTRUCTIVE operation:` 并等待确认
238
-
239
- ### Agentic 循环(`repl.ts` → `handleChatWithTools`)
240
-
241
- ```
242
- 用户输入 rl.pause() + rl.output=null(静默 readline)
243
- → chatWithTools(messages + toolDefs) → AI 返回 { toolCalls } 或 { content }
244
- 若 toolCalls:executor.executeAll() buildToolResultMessages() 下一轮(最多 25 轮)
245
- 若 content:renderResponse() → rl.output=savedOutput + rl.resume() + showPrompt()
246
- ```
247
-
248
- ### stdin/readline 关键设计(重要!勿破坏)
249
-
250
- 1. **`rl.output = null`**:在 `line` handler 开始时执行,处理完毕后恢复。
251
- 防止 readline AI 处理期间因 spinner/输出 触发行缓冲区重绘,导致用户输入被重复打印。
252
-
253
- 2. **`confirm()` 读取单键**:使用 `process.stdin.setRawMode(true)` + `data` 事件读取单个字符。
254
- **禁止**:调用 `rl.question()`(会 pause readline,REPL 随后失去响应);
255
- **禁止**:额外调用 `stdin.resume()`(会产生残留字节 `yy`);
256
- **禁止**:在 executor 内操控 `rl.output`(由 repl.ts 统一管理)。
257
-
258
- 3. **`rl.prompt()` 而非 `stdout.write`**:readline 内部追踪 prompt 列宽,backspace 不会吃掉 prompt。
259
-
260
- 4. **`renderer.ts` `renderStream/renderResponse` 不加前置 `\n`**:
261
- 用户按回车后 readline 已换行,spinner stop 后也已换行,再加 `\n` 会触发 readline 重绘。
262
-
263
- 5. **`processing` 标志防止 `rl.on('close')` 误触发退出**:
264
- `line` handler async 的,readline 不等待它完成。若 AI 处理期间 stdin 意外关闭(如 spinner 操作),
265
- `close` 事件会提前 resolve Promise 导致 REPL 退出。用 `processing` 布尔标志,在 `close` handler 中判断,
266
- 仅在非处理中时才 resolve。
267
-
268
- 6. **全局异常捕获**(`src/index.ts`):`uncaughtException` + `unhandledRejection` 均打印完整堆栈到 stderr,
269
- 防止未知错误导致静默崩溃。ESM 中这两个 handler 必须写在文件顶部(import 语句之前)才能在模块加载期生效。
270
-
271
- ### Windows 编码处理
272
-
273
- - **bash 工具**:命令前注入 `[Console]::OutputEncoding = UTF8`;`execSync` 用 `encoding:'buffer'`,手动 `.toString('utf-8')` 解码。
274
- - **run_interactive 工具**:直接 `spawn(pythonExe, args)`,不经过任何 shell;环境变量 `PYTHONUTF8=1` + `PYTHONIOENCODING=utf-8`,stdout 用 `setEncoding('utf-8')`。
275
- - **关键**:不要用 `cmd /c` 或 `powershell -Command` 包装 spawn——这会截断 stdin 管道,交互式程序收不到输入。
276
-
277
- ### run_interactive 工具参数容错设计
278
-
279
- ```typescript
280
- // args 可能是数组(正确)或字符串(AI 传参错误时降级兼容)
281
- const cmdArgs = Array.isArray(rawArgs) ? rawArgs.map(String) : [rawArgs.trim()];
282
- // stdin_lines 可能是数组或逗号分隔字符串
283
- const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
284
- : rawStdin.split(',').map(s => s.trim());
285
- ```
286
-
287
- ## 添加新 Provider
288
-
289
- 1. 在 `src/providers/` 创建新文件(如 `openrouter.ts`)
290
- 2. 继承 `OpenAICompatibleProvider`(OpenAI 兼容)或 `BaseProvider`
291
- 3. 实现 `info` 属性(id, displayName, defaultModel, models 数组)和 `defaultBaseUrl`
292
- 4. 在 `src/providers/registry.ts` 的 `BUILT_IN_PROVIDERS` 数组中添加该类
293
-
294
- ## 添加新 Tool
295
-
296
- 1. `src/tools/builtin/` 创建新文件,实现 `Tool` 接口(`definition` + `execute(args)`)
297
- 2. 在 `src/tools/registry.ts` 的 `constructor` 中 `this.register(newTool)`
298
- 3. 在 `src/tools/types.ts` 的 `getDangerLevel()` 中为新工具设置危险级别
299
-
300
- ## GUI 扩展(Electron/Tauri)
301
-
302
- - `src/core/`、`src/providers/`、`src/session/`、`src/config/`、`src/tools/` 无终端依赖,可直接被 Electron 主进程导入
303
- - `src/repl/` 仅是 CLI 层,GUI 时完全替换此目录
304
- - `EventBus` 作为 Electron IPC 桥接
305
- - `package.json` 中的 `exports` 字段暴露核心 API 供第三方使用
306
-
307
- ## 代码风格
308
-
309
- - TypeScript strict 模式
310
- - 所有文件使用 `.js` 扩展名导入(ESM 要求)
311
- - Provider 错误统一转换为 `ProviderError` 子类
312
- - 不在核心层(`src/core/`)引入任何 CLI/终端相关依赖
313
-
314
- ## Gemini CLI 追赶路线(进行中)
315
-
316
- 对标 Gemini CLI 的功能差距,按优先级逐步实现:
317
-
318
- ### 已完成
319
- - [x] **层级上下文文件系统**(2026-02-22):全局 `~/.aicli/AICLI.md` + 项目 `<git-root>/AICLI.md` + 子目录 `<cwd>/AICLI.md` 三层级自动发现、拼接注入 system prompt。`/context` 命令显示各层详情。
320
- - [x] **持久记忆系统 `save_memory`**(2026-02-22):AI 可调用 `save_memory` 工具将重要信息追加到 `~/.aicli/memory.md`,跨会话自动加载注入 system prompt。支持时间戳、大小限制(10K 字符截取最新)。
321
- - [x] **开发状态交接系统**(2026-02-22):`/provider` `/model` 切换时自动调用当前 AI 生成结构化开发状态快照(7 段式:当前任务/已完成/进行中/关键决策/修改文件/下一步/备注),保存到 `~/.aicli/dev-state.md`,新模型启动时自动注入 system prompt 实现无缝交接。同值选择不触发任何变更。`/clear` `/session new` 清除快照。
322
- - [x] **`ask_user` 工具**(2026-02-23):AI agentic 循环中可调用 `ask_user` 暂停执行、向用户显示问题、等待文本输入后继续。复用 `confirm()` readline 模式(output 保存/恢复、resume/pause、once('line')),支持 Ctrl+C 取消。主循环 line handler 增加 `askUserContext.prompting` 守卫。
323
- - [x] **`write_todos` 工具**(2026-02-23):AI 拆解复杂任务为子任务列表,终端实时渲染进度。参数采用 JSON 字符串(因 ToolParameterSchema 不支持嵌套对象数组),容错处理 AI 直接传数组。`execute()` 内直接 `console.log()` 渲染(绕过 executor 8 行截断),模块级变量保持会话内状态。
324
- - [x] **Google 搜索集成**(2026-02-23):`google_search` 工具通过 Google Custom Search JSON API 搜索网页。需配置 API Key(`apiKeys['google-search']` 或 `AICLI_API_KEY_GOOGLESEARCH`)和 Search Engine ID(`googleSearchEngineId` 或 `AICLI_GOOGLE_CX`)。自动走全局 proxy,15s 超时,返回 Markdown 格式结果列表。配置向导新增 `Configure Google Search` 入口。
325
-
326
- - [x] **MCP 协议支持**(2026-02-23):轻量级 MCP 客户端(不依赖 SDK),STDIO 传输 + JSON-RPC 2.0 通信。`src/mcp/` 模块:`types.ts`(协议类型)、`client.ts`(单服务器连接)、`manager.ts`(多服务器管理 + Tool 转换)。配置兼容 Claude Desktop(`config.json` 的 `mcpServers` 字段)。启动自动连接、发现工具并注册到 ToolRegistry,工具名格式 `mcp__<serverId>__<toolName>`。新增 `/mcp` REPL 命令查看连接状态和工具列表。
327
- - [x] **Kimi XML 伪工具调用修复**(2026-02-25):Kimi-k2 在生成长内容时退化为文本 XML 伪工具调用。`kimi.ts` 覆写 `chatWithTools()`:方案 B system prompt 注入强制规范提示(预防),方案 A 解析文本中的 XML 工具标签并转换为真实 ToolCall 执行(兜底)。`parseXmlToolCalls()` 使用 indexOf 避免正则回溯,快速路径优化。
328
-
329
- ### P0 核心竞争力缺口(下一步最先实现)
330
- - [x] **`/compact` 上下文压缩**(2026-02-25):调用当前 provider 生成对话摘要,替换 session.messages 前 N 条为 user/assistant 摘要对,保留最近 4 条。`session.compact()` 方法 + `repl.compactSession()` + `/compact [instruction]` 命令。
331
- - [x] **Headless 非交互模式(`-p` flag)**(2026-02-25):`echo "..." | aicli -p "review"` 单轮对话后退出,`--json` 输出 `{content,provider,model,usage}`,支持 stdin 内容拼接,解锁 CI/CD 脚本场景。
332
- - [x] **Plan Mode 规划模式**(2026-02-25):`/plan` 进入只读规划(AI 只能用只读工具白名单),提示符显示 `[PLAN]` 黄色标记,`/plan execute` 切回正常模式,`/plan exit` 放弃计划。
333
- - [x] **Sub-agents 系统**(2026-02-27):`spawn_agent` 工具在同进程中运行独立 agentic 循环。子代理拥有隔离对话、过滤后的工具集(SUBAGENT_ALLOWED_TOOLS)、自动确认 write 操作、阻止 destructive 操作。SubAgentExecutor 带前缀终端输出。
334
-
335
- ### P1 重要差距(已全部完成 2026-02-28
336
- - [x] **Agent Skills 系统**(2026-02-28):`~/.aicli/skills/*.md` 可复用技能包,YAML frontmatter 声明 name/description/tools 白名单。`/skill list|<name>|off|reload` 命令。激活时注入 system prompt + 过滤工具集(Plan Mode 优先)。
337
- - [x] **`/init` 项目初始化**(2026-02-28):扫描项目类型(Node/Rust/Python/Go/Java 等)+ 目录结构树,调用当前 AI 生成结构化 AICLI.md。已存在时需 `--force` 覆盖。
338
- - [x] **`/copy` 剪贴板支持**(2026-02-28):跨平台复制最后 AI 回答到系统剪贴板(Windows: clip / macOS: pbcopy / Linux: xclip 或 xsel),无外部 npm 依赖。
339
- - [x] **多文件编辑预览**(2026-02-28):`executeAll()` 重构为三组分流(safe→fileWrite→other)。文件写入 2+ 个时走 `executeBatchFileWrites()` 批量 diff 预览 + `batchConfirm()`(`[a]pprove all / [r]eject all / [1,3,5]` 选择性 approve)。
340
- - [x] **Token 用量统计 `/cost`**(2026-02-28):显示 session 累计 input/output/total tokens + provider/model/消息数。`/cost reset` 重置计数器。
341
-
342
- ### P2 增强功能
343
- - [x] **Hooks 系统**(2026-02-28):pre/post tool execution shell 命令钩子(`config.hooks`),模板变量 `{tool}/{dangerLevel}/{args}/{status}`
344
- - [x] **Permission Rules**(2026-02-28):基于规则的工具权限控制(`config.permissionRules`),auto-approve/deny/confirm,首匹配规则
345
- - [x] **Checkpointing**(2026-02-28):`/checkpoint save/restore/list/delete`,检查点元数据随 session JSON 持久化
346
- - [x] **`/review` 代码审查**(2026-02-28):读取 git diff + 上下文,AI 生成结构化审查意见(`--staged`/`--detailed`)
347
- - [x] **Custom Commands**(2026-02-28):`~/.aicli/commands/*.md` 用户自定义命令,YAML frontmatter + `{{input}}/{{git-diff}}/{{git-context}}/{{file:path}}` 模板变量
348
- - [x] **项目级 `.mcp.json`**(2026-03-05):项目根目录 `.mcp.json` 自动发现,与全局 `config.json` `mcpServers` 合并(项目覆盖同名),`/mcp` 显示 `[global]`/`[project]` 来源标签
349
- - [x] **`--resume <id>` 启动参数**(2026-03-05):命令行直接恢复指定会话(前缀匹配),找不到时显示最近 5 session 提示
350
- - [x] **Word wrap 配置**(2026-03-05):`config.ui.wordWrap`,0=自动(终端宽度),>0=固定列宽。ANSI 转义码感知折行
351
- - [x] **主题/颜色自定义**(2026-03-05):`src/repl/theme.ts` 集中式主题系统,dark/light/custom 三主题 + 10 语义色槽 + Proxy 全局导出
352
-
353
- ## 已知待改进项
354
-
355
- ### 低优先级
356
- - [x] **macOS/Linux 完整测试**(2026-03-01):已在 Linux 环境完成测试,基本无问题。跨平台逻辑(`$SHELL` fallback、UTF-8 env vars)验证通过。
357
- - [x] **Token 用量显示**:已通过 `/cost` 命令实现(v0.1.23),显示 session 累计 input/output/total tokens。
358
- - [ ] **GitHub 仓库迁移**:当前在 gitee,npm 包受众需要 GitHub 托管。
359
- - [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
360
- - [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
361
-
362
- ## 本轮开发完成记录(2026-03-10,v0.1.57 v0.1.58)
363
-
364
- ### 代码质量修复:19 个低危问题修复 + 代码重构
365
-
366
- **审查完结篇**:继 v0.1.56 修复高危、v0.1.57 修复中危后,本轮修复全部低危问题,代码审查报告彻底清零。
367
-
368
- | # | 文件 | 修复内容 |
369
- |---|------|---------|
370
- | L1 | `src/tools/truncate.ts` (新) | 提取 `truncateOutput` 为共享模块,消除 executor.ts spawn-agent.ts 代码重复 |
371
- | L2 | `src/tools/executor.ts` | 改为从共享 `truncate.ts` 导入 |
372
- | L3 | `src/tools/builtin/spawn-agent.ts` | 15+ chalk→theme 迁移(header/footer/BLOCKED/toolCall/toolResult),移除 chalk 依赖 |
373
- | L4 | `src/tools/builtin/run-interactive.ts` | timeout 参数限制 1s–300s(与 bash 工具一致) |
374
- | L5 | `src/tools/builtin/grep-files.ts` | `statSync` 预检文件大小再读取,避免 OOM |
375
- | L6 | `src/tools/builtin/read-file.ts` | `buf.slice` `buf.subarray`(消除 Node.js 弃用警告) |
376
- | L7 | `src/tools/undo-stack.ts` | 提取 `pushEntry()` 统一入栈方法,消除 push/pushNewFile/pushNewDir 三处溢出检查重复 |
377
- | L8 | `src/tools/builtin/save-memory.ts` | `statSync` 获取文件大小,避免追加后重读整个文件 |
378
- | L9 | `src/tools/builtin/list-dir.ts` | 不再隐藏 .github/.vscode/.gitignore/.env* 等有用的点文件/目录 |
379
- | L10 | `src/repl/custom-commands.ts` | 复用 `skills/types.ts` 的 `parseSimpleYaml`,消除函数重复 |
380
- | L11 | `src/skills/types.ts` | `parseSimpleYaml` 导出为 public + 添加引号剥离 |
381
- | L12 | `src/tools/git-context.ts` | 移除 `2>/dev/null`(Windows 不兼容,`stdio:pipe` 已捕获 stderr) |
382
- | L13 | `src/repl/notify.ts` | Windows PowerShell 反引号转义修复 |
383
- | L14 | `src/tools/hooks.ts` | 模板变量 shell 转义(`shellEscape` 单引号包裹),防止命令注入 |
384
- | L15 | `src/mcp/client.ts` | 移除 `rejectAllPending` 中冗余的 `clearTimeout`(reject 回调已包含) |
385
- | L16 | `src/config/config-manager.ts` | 拆分 `toJSON()`→返回对象 + `toFormattedJSON()`→返回字符串,修复 `/config show` API Key 掩码失效 |
386
- | L17 | `src/tools/builtin/write-file.ts` | append 模式也记录 undo 条目(可撤销追加操作) |
387
- | L18 | `src/providers/gemini.ts` | 保留空白文本 parts(仅跳过空字符串),避免丢失有意义的空白 |
388
- | L19 | `src/repl/clipboard.ts` | PowerShell 单引号字符串中的路径转义修复 |
389
- | L20 | `src/tools/diff-utils.ts` | DP 表内存限制文档化(~2MB 上限) |
390
- | L21 | `src/tools/permissions.ts` | `pathPattern` 子串匹配行为文档化 |
391
-
392
- ### 变更文件汇总
393
-
394
- | 文件 | 变更类型 | 说明 |
395
- |------|---------|------|
396
- | `src/tools/truncate.ts` | 新增 | 共享 truncateOutput 函数 |
397
- | `src/tools/executor.ts` | 修改 | 导入共享 truncate.ts |
398
- | `src/tools/builtin/spawn-agent.ts` | 修改 | chalk→theme 全量迁移 + 共享 truncate |
399
- | `src/tools/builtin/run-interactive.ts` | 修改 | timeout 限制 |
400
- | `src/tools/builtin/grep-files.ts` | 修改 | statSync 预检 |
401
- | `src/tools/builtin/read-file.ts` | 修改 | buf.subarray |
402
- | `src/tools/undo-stack.ts` | 修改 | pushEntry DRY |
403
- | `src/tools/builtin/save-memory.ts` | 修改 | statSync 替代 readFile |
404
- | `src/tools/builtin/list-dir.ts` | 修改 | 有用点文件可见 |
405
- | `src/repl/custom-commands.ts` | 修改 | 复用 parseSimpleYaml |
406
- | `src/skills/types.ts` | 修改 | 导出 parseSimpleYaml |
407
- | `src/tools/git-context.ts` | 修改 | 移除 2>/dev/null |
408
- | `src/repl/notify.ts` | 修改 | PS 转义修复 |
409
- | `src/tools/hooks.ts` | 修改 | shell 转义 |
410
- | `src/mcp/client.ts` | 修改 | 冗余 clearTimeout |
411
- | `src/config/config-manager.ts` | 修改 | toJSON 拆分 |
412
- | `src/repl/commands/index.ts` | 修改 | 适配 toJSON 返回类型 |
413
- | `src/tools/builtin/write-file.ts` | 修改 | append undo |
414
- | `src/providers/gemini.ts` | 修改 | 空白 text parts |
415
- | `src/repl/clipboard.ts` | 修改 | 路径转义 |
416
- | `src/tools/diff-utils.ts` | 修改 | 文档化 |
417
- | `src/tools/permissions.ts` | 修改 | 文档化 |
418
- | `src/core/constants.ts` | 修改 | VERSION 0.1.57 → 0.1.58 |
419
- | `package.json` | 修改 | version 0.1.57 → 0.1.58 |
420
-
421
- ### 版本与收尾
422
- - `src/core/constants.ts`:VERSION `0.1.57` → `0.1.58`
423
- - `package.json`:version 同步
424
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
425
-
426
- ### 代码审查完结状态(v0.1.35+ 增量)
427
- - **高危 H1–H6**:全部已修复(v0.1.56)
428
- - **中危 M1–M16**:全部已修复(v0.1.57)
429
- - **低危 L1–L21**:全部已修复(v0.1.58)
430
- - **安全债务清零**
431
-
432
- ---
433
-
434
- ## 本轮开发完成记录(2026-03-10,v0.1.56 v0.1.57)
435
-
436
- ### 代码质量修复:16 个中危问题全部修复
437
-
438
- **审查续篇**:继 v0.1.56 修复 6 个高危后,本轮修复全部 16 个中危问题。
439
-
440
- | # | 文件 | 修复内容 |
441
- |---|------|---------|
442
- | M1 | `commands/index.ts` | `/config set` 原型污染防护(禁止 `__proto__`/`constructor`/`prototype` 路径) |
443
- | M2 | `commands/index.ts` | `/config show` API Key 掩码(显示 `sk-d***b0` 格式) |
444
- | M3 | `repl.ts` | `FREE_ROUND_TOOLS` 连续免费轮次上限(MAX_CONSECUTIVE_FREE_ROUNDS=5),防无限循环 |
445
- | M4 | `openai-compatible.ts` | 截断 JSON 修复时 stderr 警告(提示参数可能丢失) |
446
- | M5 | `claude.ts` | `chatWithToolsStream` done 事件兜底(`doneEmitted` 标志 + 流结束后补发) |
447
- | M6 | `claude.ts` | base64 图片 URL 跳过时 stderr 警告 |
448
- | M7 | `claude.ts` | AbortError/TimeoutError 不再被 `wrapError` 包装为 ProviderError(正确冒泡) |
449
- | M8 | `openai-compatible.ts` | HALLUCINATION_PATTERNS 收紧:要求文件扩展名或路径引号上下文,减少误报 |
450
- | M9 | `openai-compatible.ts` | 非流式降级路径保留 `reasoningContent`(DeepSeek-Reasoner 推理内容) |
451
- | M10 | `deepseek.ts` + `kimi.ts` | Plan C 重试后二次校验:仍虚假声明时 stderr 警告用户手动检查 |
452
- | M11 | `renderer.ts` | `printTable` ANSI 感知列对齐(用 `stripAnsi` 计算可见宽度) |
453
- | M12 | `renderer.ts` | `wrapText` ANSI 样式跨行延续(折行后重发活跃样式序列,行末发 reset) |
454
- | M13 | `theme.ts` | `resolveColor` 无效颜色名 stderr 警告(不再静默回退) |
455
- | M14 | `session.ts` | `fork()` 深拷贝 multimodal 消息(嵌套 content 数组不再共享引用) |
456
- | M15 | `bash.ts` | Undo 追踪限制文档化(仅 CWD 子级、仅常见创建命令、不追踪管道/脚本产生) |
457
- | M16 | `edit-file.ts` | `findSimilarLines` 500KB 文件大小保护(跳过大文件避免性能问题) |
458
-
459
- ### 变更文件汇总
460
-
461
- | 文件 | 变更类型 | 说明 |
462
- |------|---------|------|
463
- | `src/repl/commands/index.ts` | 修改 | M1 原型污染 + M2 API Key 掩码 |
464
- | `src/repl/repl.ts` | 修改 | M3 连续免费轮次上限 |
465
- | `src/providers/openai-compatible.ts` | 修改 | M4 JSON 修复警告 + M8 模式收紧 + M9 reasoningContent |
466
- | `src/providers/claude.ts` | 修改 | M5 done 兜底 + M6 图片警告 + M7 AbortError |
467
- | `src/providers/deepseek.ts` | 修改 | M10 重试二次校验 |
468
- | `src/providers/kimi.ts` | 修改 | M10 重试二次校验 |
469
- | `src/repl/renderer.ts` | 修改 | M11 表格对齐 + M12 折行样式 |
470
- | `src/repl/theme.ts` | 修改 | M13 无效颜色警告 |
471
- | `src/session/session.ts` | 修改 | M14 fork 深拷贝 |
472
- | `src/tools/builtin/bash.ts` | 修改 | M15 限制文档 |
473
- | `src/tools/builtin/edit-file.ts` | 修改 | M16 大文件保护 |
474
- | `src/core/constants.ts` | 修改 | VERSION 0.1.56 → 0.1.57 |
475
- | `package.json` | 修改 | version 0.1.56 → 0.1.57 |
476
-
477
- ### 版本与收尾
478
- - `src/core/constants.ts`:VERSION `0.1.56` → `0.1.57`
479
- - `package.json`:version 同步
480
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
481
- - 发布:`npm publish` `jinzd-ai-cli@0.1.57`
482
-
483
- ### 代码审查完结状态(v0.1.35+ 增量)
484
- - **高危 H1–H6**:全部已修复(v0.1.56)
485
- - **中危 M1–M16**:全部已修复(v0.1.57)
486
- - **低危 L1–L28**:待后续改进(多为代码风格、注释、微优化)
487
-
488
- ---
489
-
490
- ## 本轮开发完成记录(2026-03-10,v0.1.55 → v0.1.56)
491
-
492
- ### 安全修复:v0.1.35+ 增量代码审查 6 个高危问题全部修复
493
-
494
- **审查范围**:v0.1.35 v0.1.55 全部增量代码(15+ 文件),共发现 6 高危 + 16 中危 + 28 低危问题。
495
-
496
- **高危修复汇总**:
497
-
498
- | # | 文件 | 问题 | 修复 |
499
- |---|------|------|------|
500
- | H1 | `commands/index.ts:774` | `/undo` 崩溃:`args.trim()` 调用在 `string[]` 上 | `args.join(' ').trim()` |
501
- | H2 | `commands/index.ts:1872` | `/fork` 错误处理崩溃:`printError()` 不存在 | 改为 `renderError()` |
502
- | H3 | `claude.ts:374,386` | 流式 tool_use `input` 丢失:`input_json_delta` 未累积到 `rawContentBlocks` | 新增 `_inputJson` 累积字段,`content_block_stop` 时 JSON.parse 还原 input |
503
- | H4 | `repl.ts:1813` | 流式推理文本未存入 session:AI 在工具调用前的文本上下文丢失 | 新增 `_streamedText` 附着到 toolCalls,`buildToolResultMessages` 中作为 assistant content |
504
- | H5 | `kimi.ts:126` | XML 工具调用注入:`parseXmlToolCalls` 盲搜全文可误执行引用内容 | Plan A 仅在检测到幻觉(Plan C)后才启用 |
505
- | H6 | `kimi.ts:126-132` | Plan A 绕过 Plan C:XML 解析优先运行跳过了虚假声明的 `alreadyWrote` 安全检查 | 调整执行顺序:Plan C 检测 → Plan A XML 解析(仅在幻觉上下文中) |
506
-
507
- **变更文件**:
508
-
509
- | 文件 | 变更类型 | 说明 |
510
- |------|---------|------|
511
- | `src/repl/commands/index.ts` | 修改 | H1: `args.trim()` → `args.join(' ').trim()`;H2: `printError` → `renderError` |
512
- | `src/providers/claude.ts` | 修改 | H3: `_inputJson` 累积 + `content_block_stop` 时 JSON.parse 还原 |
513
- | `src/repl/repl.ts` | 修改 | H4: `_streamedText` 附着到 toolCalls 传递给 buildToolResultMessages |
514
- | `src/providers/openai-compatible.ts` | 修改 | H4: `buildToolResultMessages` 读取 `_streamedText` 作为 assistant content |
515
- | `src/providers/kimi.ts` | 修改 | H5+H6: Plan A 移到 Plan C 之后,仅在 `isHallucinated` 时启用 XML 解析 |
516
- | `src/core/constants.ts` | 修改 | VERSION 0.1.55 → 0.1.56 |
517
- | `package.json` | 修改 | version 0.1.55 → 0.1.56 |
518
-
519
- ### 版本与收尾
520
- - `src/core/constants.ts`:VERSION `0.1.55` → `0.1.56`
521
- - `package.json`:version 同步
522
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
523
- - 发布:`npm publish` → `jinzd-ai-cli@0.1.56`
524
-
525
- ### 剩余审查问题(中低危,后续改进)
526
-
527
- **中危(16 个)**:M1 `/config set` 原型污染、M2 `/config show` API Key 泄露、M3 `FREE_ROUND_TOOLS` 无限循环、M4 截断 JSON 静默丢参数、M5 Claude `done` 事件仅在 `message_delta` 时发射、M6 非 base64 图片静默丢弃、M7 AbortError 被包装为 ProviderError、M8 幻觉模式过宽、M9 非流式降级丢失 reasoningContent、M10 Plan C 重试不二次验证、M11 `printTable` ANSI 列对齐、M12 `wrapText` 断裂 ANSI 样式、M13 `resolveColor` 静默吞无效颜色名、M14 `fork()` 浅拷贝消息、M15 bash undo 追踪局限、M16 `edit-file` 相似行搜索无文件大小上限。
528
-
529
- **低危(28 个)**:详见代码审查报告。
530
-
531
- ---
532
-
533
- ## 本轮开发完成记录(2026-03-09,v0.1.54 → v0.1.55)
534
-
535
- ### Bug 修复:AI 在"阅读/理解"请求中自动执行任务
536
-
537
- **问题**:用户输入"请仔细阅读 @ynzjmk.md 及其相关的核心文档以了解当前项目"(纯阅读理解请求),AI 读完文档后自动开始执行项目中描述的任务(出题),而非总结理解后等待用户下一步指示。
538
-
539
- **根因**:system prompt 中无任何指导 AI 区分"理解类请求"与"执行类请求"的行为准则。AI 读取项目文档后,将项目描述的功能(出题)误解为用户的即时任务。
540
-
541
- **修复**:
542
-
543
- | 文件 | 变更类型 | 说明 |
544
- |------|---------|------|
545
- | `src/core/constants.ts` | 修改 | 新增 `AGENTIC_BEHAVIOR_GUIDELINE` 常量 |
546
- | `src/repl/repl.ts` | 修改 | `buildCurrentSystemPrompt()` 注入行为准则(在环境信息之后、持久记忆之前) |
547
- | `src/core/constants.ts` | 修改 | VERSION 0.1.54 → 0.1.55 |
548
- | `package.json` | 修改 | version 0.1.54 → 0.1.55 |
549
-
550
- **`AGENTIC_BEHAVIOR_GUIDELINE` 内容**:
551
- - 当用户要求"阅读/理解/了解/分析/审查/看一下"时,仅读取并总结,等待下一步指示
552
- - 只有用户明确要求"生成/创建/修改/运行/开始"时才执行写入/执行类工具
553
- - 不确定意图时使用 `ask_user` 向用户确认
554
-
555
- **注入位置**:system prompt 最前段(日期环境信息之后),确保始终生效。
556
-
557
- ### 版本与收尾
558
- - `src/core/constants.ts`:VERSION `0.1.54` `0.1.55`
559
- - `package.json`:version 同步
560
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
561
- - 发布:`npm publish` → `jinzd-ai-cli@0.1.55`
562
-
563
- ---
564
-
565
- ## 本轮开发完成记录(2026-03-09,v0.1.53 v0.1.54
566
-
567
- ### 新增功能:Streaming Tool Use — agentic 循环流式工具调用
568
-
569
- **背景**:`handleChatWithTools()` agentic 循环中,每轮调用 `provider.chatWithTools()` 使用 `stream: false`,必须等待 API 返回完整响应后才开始处理。AI 生成工具调用参数期间(5-30 秒),用户只看到 spinner,无任何内容反馈。
570
-
571
- **设计决策**:
572
- 1. 新增 `chatWithToolsStream()` 方法,不修改现有 `chatWithTools()`
573
- 2. 统一事件模型 `AsyncGenerator<ToolStreamEvent>`(8 种事件类型)
574
- 3. 等待全部工具调用完成后再执行(不做 early execution)
575
- 4. Phase 1 覆盖:OpenAI + Zhipu(继承基类)+ Claude;DeepSeek/Kimi 继续用非流式(虚假声明检测需完整响应)
576
-
577
- **变更文件**:
578
-
579
- | 文件 | 变更类型 | 说明 |
580
- |------|---------|------|
581
- | `src/core/types.ts` | 修改 | 新增 `ToolStreamEvent` 联合类型(8 变体)+ `StreamedToolCallResult` 接口 |
582
- | `src/providers/base.ts` | 修改 | 新增可选方法 `chatWithToolsStream?()` + 导入 `ToolStreamEvent`/`ToolDefinition` |
583
- | `src/providers/openai-compatible.ts` | 修改 | 新增 `enableStreamingToolCalls` 标志 + `chatWithToolsStream()` 完整实现(~140 行) |
584
- | `src/providers/claude.ts` | 修改 | 新增 `chatWithToolsStream()` 实现(~120 行),收集 `rawContentBlocks` 用于 `buildToolResultMessages` |
585
- | `src/providers/deepseek.ts` | 修改 | `enableStreamingToolCalls = false`(虚假声明检测需完整响应) |
586
- | `src/providers/kimi.ts` | 修改 | `enableStreamingToolCalls = false`(XML 伪调用 + 虚假声明检测需完整响应) |
587
- | `src/repl/repl.ts` | 修改 | 新增 `consumeToolStream()` 方法 + `handleChatWithTools()` 流式/非流式双路径 |
588
- | `src/repl/renderer.ts` | 修改 | `/about` 新增特性条目 |
589
- | `src/core/constants.ts` | 修改 | VERSION 0.1.53 → 0.1.54 |
590
- | `package.json` | 修改 | version 0.1.53 0.1.54 |
591
-
592
- **实现细节**:
593
-
594
- *类型层*(`types.ts`):
595
- - `ToolStreamEvent`:`text_delta` / `thinking_start` / `thinking_delta` / `thinking_end` / `tool_call_start` / `tool_call_delta` / `tool_call_end` / `done`
596
- - `StreamedToolCallResult`:`textContent` + `toolCalls` + `usage` + `rawContent`(Claude 专用)
597
-
598
- *OpenAI 兼容 Provider*(`openai-compatible.ts`):
599
- - `enableStreamingToolCalls = true` 保护标志,子类可 override 为 false 禁用
600
- - 流式路径:`stream: true` + `stream_options: { include_usage: true }`
601
- - `delta.tool_calls[i]` 首次出现(含 `id`+`name`)发 `tool_call_start`,后续发 `tool_call_delta`
602
- - 非流式降级路径:`enableStreamingToolCalls = false` 时调用 `chatWithTools()` 并转换为事件序列
603
-
604
- *Claude Provider*(`claude.ts`):
605
- - 使用 `this.client.messages.stream()` + AbortSignal 支持
606
- - 收集 `rawContentBlocks` 数组,通过 `done` 事件的 `rawContent` 传递给 `buildToolResultMessages`
607
- - 事件映射:`content_block_start`(tool_use) `tool_call_start`;`input_json_delta` `tool_call_delta`;`content_block_stop` `tool_call_end`
608
- - thinking 块正确处理(`thinking_start`/`thinking_delta`/`thinking_end`)
609
-
610
- *REPL 层*(`repl.ts`):
611
- - `consumeToolStream()`:消费事件生成器,`text_delta` 实时输出到 stdout(停 spinner),`tool_call_start` 显示 `⚙ Streaming: <name>...`,累积 arguments JSON 碎片,`tool_call_end` 时 JSON.parse
612
- - `handleChatWithTools()` 循环:检测 `supportsStreamingTools`(`useStreaming && typeof provider.chatWithToolsStream === 'function'`),流式路径用 `setupStreamInterrupt()`/`teardownStreamInterrupt()` 包裹支持 Escape/Ctrl+C 中断
613
- - `alreadyRendered` 标志防止文本内容双重渲染
614
-
615
- **用户体验对比**:
616
- - Before:Spinner 等待 10-30 秒 → 突然全部出现
617
- - After:短暂 Spinner → 文本实时流出 → 工具名即时显示 → 参数完整后执行
618
-
619
- ### 版本与收尾
620
- - `src/core/constants.ts`:VERSION `0.1.53` `0.1.54`
621
- - `package.json`:version 同步
622
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
623
- - 发布:`npm publish` → `jinzd-ai-cli@0.1.54`
624
-
625
- ---
626
-
627
- ## 本轮开发完成记录(2026-03-08,v0.1.51 → v0.1.52)
628
-
629
- ### Bug 修复:DeepSeek 虚假完成声明(方案 C)+ 虚假声明检测共享重构
630
-
631
- **问题**:DeepSeek 在多轮对话中生成多个文件时,偶尔会跳过后续文件的 `write_file` 工具调用,直接在回复文本中声称"✅ 文件已保存",实际上文件并未创建。这与 Kimi 的方案 C 问题(v0.1.41)完全相同——模型虚假声称已完成文件操作。
632
-
633
- **修复**:三层变更
634
-
635
- | 文件 | 变更 | 说明 |
636
- |------|------|------|
637
- | `src/providers/openai-compatible.ts` | 新增共享代码 | HALLUCINATION_PATTERNS(8 个模式)+ `detectsHallucinatedFileOp()` / `mergeUsage()` 两个 protected 方法 |
638
- | `src/providers/kimi.ts` | 简化(去重) | 移除私有 HALLUCINATION_PATTERNS、detectsHallucinatedFileOp、mergeUsage,改用父类 protected 方法 |
639
- | `src/providers/deepseek.ts` | 新增覆写 | 方案 B(system prompt 规范提示)+ 方案 C(虚假声明检测 + 自动重试) |
640
-
641
- **共享重构 HALLUCINATION_PATTERNS 提升到基类**:
642
-
643
- 原本 HALLUCINATION_PATTERNS 和检测/合并方法分别在 kimi.ts 中重复定义。本次将共享逻辑提升到 `openai-compatible.ts` 基类:
644
- - `HALLUCINATION_PATTERNS`:模块级常量,8 个正则模式(比原 Kimi 版增加 `/已保存/`(更宽泛)、`/已创建/`、`/✅.../` 三个新模式)
645
- - `detectsHallucinatedFileOp(content)`:protected 方法,子类直接调用 `this.detectsHallucinatedFileOp()`
646
- - `mergeUsage(a, b)`:protected 方法,合并两次 API 调用的 token 用量
647
-
648
- **DeepSeek 方案 B — system prompt 规范提示**:
649
-
650
- `DEEPSEEK_TOOL_CALL_REMINDER` 注入 system prompt 末尾,比 Kimi 版更简洁(无 XML 相关条款),新增"如果需要生成多个文件,必须对每个文件分别调用 write_file 工具,不可省略任何一个"条款。
651
-
652
- **DeepSeek 方案 C 虚假声明检测 + 自动重试**:
653
-
654
- Kimi 方案 C 逻辑一致:
655
- 1. 检测纯文本响应中的虚假完成声明模式
656
- 2. 注入纠正消息(AI 的虚假回复 + "你刚才没有实际调用 write_file 工具"纠正指令)到 `_extraMessages`
657
- 3. 重新调用 `super.chatWithTools()` 一次(防无限循环)
658
- 4. 合并两次调用的 token 用量
659
- 5. stderr 输出 `[deepseek] ⚠ 检测到虚假完成声明` 警告
660
-
661
- **与 Kimi 的区别**:DeepSeek 不存在 XML 伪工具调用问题(方案 A),因此 deepseek.ts 仅需方案 B + C 两道防线。
662
-
663
- ### 版本与收尾
664
- - `src/core/constants.ts`:VERSION `0.1.51` `0.1.52`
665
- - `package.json`:version 同步
666
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
667
-
668
- ### 本轮变更文件汇总
669
-
670
- | 文件 | 变更类型 | 说明 |
671
- |------|---------|------|
672
- | `src/providers/openai-compatible.ts` | 修改 | HALLUCINATION_PATTERNS + detectsHallucinatedFileOp + mergeUsage(共享) |
673
- | `src/providers/kimi.ts` | 修改 | 移除重复定义,使用父类 protected 方法 |
674
- | `src/providers/deepseek.ts` | 重写 | 方案 B(system prompt)+ 方案 C(虚假声明检测 + 重试) |
675
- | `src/core/constants.ts` | 修改 | VERSION 0.1.51 0.1.52 |
676
- | `package.json` | 修改 | version 0.1.51 → 0.1.52 |
677
-
678
- ---
679
-
680
- ## 本轮开发完成记录(2026-03-08,v0.1.49 v0.1.50)
681
-
682
- ### 代码质量:L1 低危修复 run-tests.ts package.json 细粒度错误处理
683
-
684
- **问题**:`detectProject()` 中 `JSON.parse(readFileSync('package.json'))` 错误处理粗糙——文件读取和 JSON 解析混在同一个 catch 中;解析失败时静默 fallthrough 可能误判为 Python/Go 项目;无 test script 时不尝试检测已安装的测试框架。
685
-
686
- **修复**(`src/tools/builtin/run-tests.ts`):
687
-
688
- 新增 `safeReadPackageJson(cwd)` 辅助函数——细粒度四层处理:
689
- 1. **文件读取错误**:区分 `EACCES`/`EPERM`(权限问题)和其他读取错误,分别给出不同提示
690
- 2. **UTF-8 BOM 处理**:自动剥离 BOM(Windows Notepad 等编辑器常见问题),防止 `JSON.parse` 失败
691
- 3. **空文件检测**:`raw.trim() === ''` 时给出明确 "package.json is empty" 提示
692
- 4. **JSON 解析错误细分**:区分语法错误(`Unexpected token`)、截断文件(`Unexpected end`)、根不是对象(数组或其他类型)
693
-
694
- 新增 `detectNodeTestFramework(cwd, pkg)` 辅助函数——当 package.json 有效但无 `scripts.test` 时:
695
- - `devDependencies` + `dependencies` + 配置文件检测 5 种测试框架:
696
- - **Vitest**:`vitest` 依赖 `vitest.config.{ts,js,mts}`
697
- - **Jest**:`jest` 依赖 `jest.config.{js,ts,mjs}`
698
- - **Mocha**:`mocha` 依赖 或 `.mocharc.{yml,yaml,json,js}`
699
- - **Ava**:`ava` 依赖
700
- - **Playwright**:`@playwright/test` 依赖
701
- - 检测到框架时直接使用 `npx <framework>` 命令,无需 `scripts.test`
702
-
703
- 更新 filter 参数处理——不同 Node 测试框架使用正确的 filter 语法:
704
- - vitest/jest → `-t "filter"`
705
- - mocha/playwright`--grep "filter"`
706
- - npm test → `-- --grep "filter"`(passthrough)
707
-
708
- **改善效果**:
709
- - package.json 格式错误时不再静默 fallthrough,返回 `npm (package.json error)` 仍识别为 Node 项目
710
- - 安装了 vitest/jest/mocha 但未配置 `scripts.test` 的项目现在能自动检测并运行测试
711
- - 错误信息更具体、更有指导性
712
-
713
- ### 版本与收尾
714
- - `src/core/constants.ts`:VERSION `0.1.49` `0.1.50`
715
- - `package.json`:version 同步
716
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
717
- - 代码审查报告 L1 条目标记为 ✅ 已修复
718
-
719
- ### 本轮变更文件汇总
720
-
721
- | 文件 | 变更类型 | 说明 |
722
- |------|---------|------|
723
- | `src/tools/builtin/run-tests.ts` | 修改 | safeReadPackageJson + detectNodeTestFramework + filter 语法更新 |
724
- | `src/core/constants.ts` | 修改 | VERSION 0.1.49 → 0.1.50 |
725
- | `package.json` | 修改 | version 0.1.49 → 0.1.50 |
726
-
727
- ---
728
-
729
- ## 本轮开发完成记录(2026-03-08,v0.1.47 v0.1.48)
730
-
731
- ### Tier 2 体验增强:/undo 增强 + /fork 对话分支
732
-
733
- **Feature 1:/undo 增强 bash 工具文件追踪 + 命令增强**
734
-
735
- | 文件 | 变更 |
736
- |------|------|
737
- | `src/tools/undo-stack.ts` | `UndoEntry` 新增 `isDirectory?: boolean` + `pushNewFile()` / `pushNewDir()` 方法 + `undo()` 目录分支 |
738
- | `src/tools/builtin/bash.ts` | 三辅助函数 + `execute()` 集成 |
739
- | `src/repl/commands/index.ts` | `/undo` 重写为 `/undo [list\|<n>]` |
740
-
741
- - **UndoStack 扩展**:新增 `pushNewFile(filePath, desc)` 和 `pushNewDir(dirPath, desc)` 方法,previousContent=null 表示新建(undo 时删除)。`undo()` 新增 `isDirectory` 分支,用 `rmdirSync` 尝试删除空目录,非空给出提示。
742
- - **Bash 工具文件追踪**(浅层 CWD 快照 + 命令模式解析):
743
- - `snapshotDir(dir)` — `readdirSync` 获取目录下所有条目的绝对路径 Set
744
- - `parseCreationTargets(command, cwd)` — 正则解析 touch/mkdir/echo>/cp/New-Item 等常见创建命令的目标路径
745
- - `pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd)` — 对比前后快照 + 解析目标,为新建文件/目录推入 undo 条目
746
- - 集成到 `execute()`:执行前快照 + 构建目标存在状态 Map;执行后(成功/失败均)调用 pushBashUndoEntries
747
- - **/undo 命令增强**:
748
- - `/undo` 撤销最近 1 次(向后兼容)
749
- - `/undo list` 显示完整 undo 栈(编号、类型标签 `[new]`/`[mod]`/`[dir]`、描述、时间)
750
- - `/undo <n>` 连续撤销最近 N 次操作,逐条显示进度
751
-
752
- **Feature 2:/fork 对话分支**
753
-
754
- | 文件 | 变更 |
755
- |------|------|
756
- | `src/session/session.ts` | 静态 `fork(original, newId, messageCount, newTitle?)` 方法 |
757
- | `src/session/session-manager.ts` | `forkSession(messageCount, title?)` 方法 |
758
- | `src/repl/commands/index.ts` | `/fork` 命令 + `CommandContext.forkSession` |
759
- | `src/repl/repl.ts` | ctx.forkSession 注入 + tab 补全(/undo list, /fork checkpoint 名称) |
760
-
761
- - **Session.fork()**:复制 messages[0..messageCount],保留范围内 checkpoints(深拷贝),新 UUID,title 默认 "Fork of <原标题>"
762
- - **SessionManager.forkSession()**:先保存原始 sessionSession.fork 创建分叉 → 设为当前 → 保存并返回
763
- - **/fork 命令**:
764
- - `/fork` 从当前位置分叉(复制全部消息)
765
- - `/fork <checkpoint-name>` 从指定 checkpoint 分叉(仅复制到该 checkpoint 的消息)
766
- - 显示原始/新 session ID、title、消息数、保留 checkpoint 数
767
- - 提示用户 `/session load <id>` 切回原始
768
- - **Tab 补全**:`/undo` 提供 `list`;`/fork` 提供当前 session 的 checkpoint 名称列表
769
-
770
- ### 版本与收尾
771
- - `src/core/constants.ts`:VERSION `0.1.47` → `0.1.48`
772
- - `package.json`:version 同步
773
- - `src/repl/renderer.ts`:命令计数 34 35,命令列表新增 `/fork`,新增 1 条特性(/fork 对话分支),更新 /undo 描述
774
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
775
- - 发布:`npm publish` `jinzd-ai-cli@0.1.48`
776
-
777
- ### 本轮变更文件汇总
778
-
779
- | 文件 | 变更类型 | 说明 |
780
- |------|---------|------|
781
- | `src/core/constants.ts` | 修改 | VERSION 0.1.47 → 0.1.48 |
782
- | `src/tools/undo-stack.ts` | 修改 | isDirectory 字段 + pushNewFile/pushNewDir + undo 目录支持 |
783
- | `src/tools/builtin/bash.ts` | 修改 | snapshotDir + parseCreationTargets + pushBashUndoEntries + execute 集成 |
784
- | `src/session/session.ts` | 修改 | 静态 fork() 方法 |
785
- | `src/session/session-manager.ts` | 修改 | forkSession() 方法 |
786
- | `src/repl/commands/index.ts` | 修改 | /undo 增强 + /fork 命令 + CommandContext.forkSession + /help 更新 |
787
- | `src/repl/repl.ts` | 修改 | ctx.forkSession 注入 + tab 补全(/undo, /fork) |
788
- | `src/repl/renderer.ts` | 修改 | /about 35 命令 + 特性更新 |
789
- | `package.json` | 修改 | version 0.1.47 → 0.1.48 |
790
-
791
- ### 下一步建议
792
-
793
- #### Tier 2 — 体验增强(剩余)
794
- 1. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复(safeReadPackageJson + detectNodeTestFramework)
795
- 2. **IDE 集成**:VS Code 扩展(架构已准备就绪,core/providers/tools 无终端依赖)
796
- 3. **OAuth/浏览器登录**:无需手动填 API Key,打开浏览器完成 OAuth 流程自动保存 token
797
-
798
- #### Tier 3 — 长远方向
799
- 4. **Web UI**:基于 EventBus + WebSocket 的浏览器界面,复用 core/providers/tools
800
- 5. **GitHub 仓库迁移**:从 gitee 迁移到 GitHub,npm 包受众更广
801
-
802
- ---
803
-
804
- ## 本轮开发完成记录(2026-03-07,v0.1.40 → v0.1.41)
805
-
806
- ### Bug 修复:Kimi 虚假完成声明(方案 C)
807
-
808
- **问题**:Kimi-k2 在多轮对话中,第二次及后续文件生成请求时,完全跳过 `write_file` 工具调用,直接在回复中虚假声称"✅ 文件已生成"并附上文件路径,实际上文件并未被创建。这与已修复的 XML 伪工具调用(方案 A)不同——这次 Kimi 没有输出任何 XML 标签,纯粹是文本级别的虚假声明。
809
-
810
- **修复**:`src/providers/kimi.ts` 新增**方案 C(虚假完成检测 + 自动重试)**,三道防线完整覆盖 Kimi 工具调用的所有已知失败模式:
811
-
812
- | 防线 | 覆盖场景 | 实现 |
813
- |------|---------|------|
814
- | 方案 B(预防)| system prompt 规范 | `KIMI_TOOL_CALL_REMINDER` 新增"严禁虚假完成声明"段落 |
815
- | 方案 A(兜底 1)| XML 伪工具调用 | `parseXmlToolCalls()` 检测并转换 |
816
- | **方案 C(兜底 2)**| **虚假完成声明** | **`detectsHallucinatedFileOp()` 检测 + 自动注入纠正消息重试** |
817
-
818
- 方案 C 实现细节:
819
- - `HALLUCINATION_PATTERNS`:6 个正则模式检测"文件路径: xxx"、"已生成完成!"、"已保存到"等虚假声明
820
- - `detectsHallucinatedFileOp(content)`:当响应为纯文本且匹配虚假声明模式时返回 true
821
- - 自动重试:将 AI 的虚假回复 + 纠正指令 (`"你刚才没有实际调用 write_file 工具,文件并未被创建!"`) 追加到 `_extraMessages`,重新调用 `super.chatWithTools()`
822
- - 重试后仍有 XML 伪调用时,走方案 A 解析(组合防线)
823
- - `mergeUsage()` 合并两次 API 调用的 token 用量
824
- - 只重试一次,防止无限循环
825
- - stderr 输出警告提示用户发生了虚假声明救援
826
-
827
- ### 版本与收尾
828
- - `src/core/constants.ts`:VERSION `0.1.40` `0.1.41`
829
- - `package.json`:version 同步
830
- - 构建验证:`npm run build` 零错误
831
-
832
- ### 本轮变更文件汇总
833
-
834
- | 文件 | 变更类型 | 说明 |
835
- |------|---------|------|
836
- | `src/core/constants.ts` | 修改 | VERSION 0.1.40 → 0.1.41 |
837
- | `src/providers/kimi.ts` | 重写 | 方案 C 虚假完成检测 + 自动重试 + HALLUCINATION_PATTERNS |
838
- | `package.json` | 修改 | version 0.1.40 0.1.41 |
839
-
840
- ---
841
-
842
- ## 本轮开发完成记录(2026-03-07,v0.1.38 → v0.1.40)
843
-
844
- ### Bug 修复(3 个使用中发现的问题 + 1 个系统改进)
845
-
846
- **Bug 1:`/init` 输出路径错误** 🔴
847
- - **问题**:在 `D:\xgitee\ai-courses\prjs\vocational` 运行 `/init`,AICLI.md 被写到 git 根目录 `D:\xgitee\ai-courses\AICLI.md` 而非 cwd
848
- - **根因**:`commands/index.ts` 1118 `const targetDir = gitRoot ?? cwd`,git 仓库内永远写到根目录
849
- - **修复**:改为 `const targetDir = cwd`,始终写到当前工作目录(子目录层),符合 `/init` 命令语义
850
-
851
- **Bug 2:工具轮次耗尽后无总结、无法继续** 🟠
852
- - **问题**:AI 用完 20 轮后只显示 `Error: Reached maximum tool call rounds (20). Stopping.`,无任何已完成工作的总结
853
- - **修复**:轮次耗尽后,给 AI 最后一次机会生成总结——传入空工具列表 + user 消息要求总结已完成/未完成工作 + 下一步建议。总结内容存入 session,用户可继续对话让 AI 接续
854
- - **文件**:`src/repl/repl.ts` 轮次耗尽后新增 `summaryExtra` + `chatWithTools(request, [])` 总结生成逻辑
855
-
856
- **Bug 3:`write_todos` 消耗宝贵工具轮次** 🟡
857
- - **问题**:用户示例中 3 `write_todos` 占用 15% 的工具轮次(3/20),纯进度展示浪费了实际工作容量
858
- - **修复**:
859
- - 新增 `FREE_ROUND_TOOLS = new Set(['write_todos'])`:本轮全部工具调用都属于免费工具时,`round--` 回退计数
860
- - MAX_TOOL_ROUNDS 20 **25**:更充裕的工具调用空间
861
-
862
- **Bug 4:AI Windows 上反复使用 Unix 命令导致 bash 工具失败** 🟠
863
- - **问题**:AI 使用 `date +%Y%m%d`、`grep`、`head`、`find | wc -l` 等 Unix 命令,在 Windows PowerShell 上全部失败,每次浪费 1-2 个工具轮次
864
- - **根因**:system prompt 中未告知 AI 当前操作系统和 shell 环境,AI 默认使用 Unix 命令
865
- - **修复**:`buildCurrentSystemPrompt()` 中注入操作系统信息 + shell 类型 + 工作目录。Windows 环境下明确提示"不要使用 Unix 命令,应使用 PowerShell 等效命令",列出常见替代(Select-String/Select-Object -First/Get-ChildItem/Get-Date -Format 等)
866
- - **文件**:`src/repl/repl.ts` `buildCurrentSystemPrompt()` 新增 `envInfo` 段落
867
-
868
- ### 版本与收尾
869
- - `src/core/constants.ts`:VERSION `0.1.38``0.1.40`
870
- - `package.json`:version 同步
871
- - 构建验证:`npm run build` 零错误
872
-
873
- ### 本轮变更文件汇总
874
-
875
- | 文件 | 变更类型 | 说明 |
876
- |------|---------|------|
877
- | `src/core/constants.ts` | 修改 | VERSION 0.1.38 → 0.1.40 |
878
- | `src/repl/repl.ts` | 修改 | MAX_TOOL_ROUNDS 20→25 + FREE_ROUND_TOOLS + 轮次耗尽总结 + OS/shell 信息注入 system prompt |
879
- | `src/repl/commands/index.ts` | 修改 | /init targetDir 从 gitRoot → cwd |
880
- | `package.json` | 修改 | version 0.1.380.1.40 |
881
-
882
- ### 下一步建议
883
- 1. ~~**`/config set` 快捷配置**~~:✅ 已实现(v0.1.49)
884
- 2. ~~**流式工具调用(Streaming Tool Use)**~~:✅ 已在 v0.1.54 实现(OpenAI/Claude 流式 + DeepSeek/Kimi 非流式降级)
885
- 3. ~~**`/diff` 命令**~~:✅ 已实现(v0.1.49)
886
-
887
- ---
888
-
889
- ## 本轮开发完成记录(2026-03-07,v0.1.37 v0.1.38)
890
-
891
- ### Tier 1 高价值功能:三项全部实现
892
-
893
- **Feature 1:Extended Thinking — Claude 深度推理模式**
894
-
895
- | 文件 | 变更 |
896
- |------|------|
897
- | `src/core/types.ts` | ChatRequest 新增 `thinkingBudget?: number` |
898
- | `src/config/schema.ts` | ModelParamsSchema 新增 `thinkingBudget: z.number().int().min(1024).optional()` |
899
- | `src/providers/claude.ts` | 完整重写,3 API 方法支持 thinking |
900
- | `src/repl/repl.ts` | `runtimeThinking` 运行时覆盖 + `[THINK]` 提示符标记 + thinkingBudget 传递 |
901
- | `src/repl/commands/index.ts` | 新增 `/think` 命令(on/off/status/toggle) |
902
-
903
- - `claude.ts` 核心改动:
904
- - 新增 `buildThinkingParams(request)` 辅助方法:thinking 启用时 `temperature` 必须为 `undefined`(API 强制 temperature=1),`budget_tokens` 最小 1024 默认 10000
905
- - 新增 `extractContent(blocks)` 辅助方法:从 `response.content` 提取 ThinkingBlock + TextBlock,thinking 用 `<think>...</think>` 标签包裹(复用 renderer 已有折叠逻辑)
906
- - `chat()` / `chatStream()` / `chatWithTools()`:三个 API 方法均传入 `thinking` + `temperature` 参数
907
- - 流式输出:`currentBlockType` 跟踪当前 content block 类型,`content_block_start` 时发 `<think>`,`content_block_stop` 时发 `</think>`,`thinking_delta` 事件直接输出 thinking 文本
908
- - 工具调用循环:`_rawContent` 属性附着在 `toolCalls` 数组上,保留原始 response.content 块(含 ThinkingBlock/RedactedThinkingBlock)
909
- - `buildToolResultMessages()`:优先使用 `_rawContent` 构建 assistantContent,保留 thinking/redacted_thinking/tool_use/text 四种块类型传回 API
910
- - `repl.ts`:
911
- - `runtimeThinking: boolean | null = null`(null=用配置值,true/false=运行时覆盖)
912
- - `getModelParams()` 返回 `thinkingBudget` + `runtimeThinking` 优先覆盖 `thinking`
913
- - 全部 4 API 调用新增 `thinkingBudget: modelParams.thinkingBudget`
914
- - `refreshPrompt()` thinking 启用时显示黄色 `[THINK]` 标记
915
- - CommandContext 注入 `getThinkingMode` / `setThinkingMode`
916
- - `/think` 命令:`/think` toggle 切换 | `/think on` 启用 | `/think off` 禁用 | `/think status` 显示状态
917
-
918
- **Feature 2:theme 迁移全覆盖**
919
-
920
- | 文件 | chalk→theme 迁移数 | 保留 chalk.white |
921
- |------|-------------------|-----------------|
922
- | `src/repl/commands/index.ts` | 116 | 4(中性色) |
923
- | `src/repl/repl.ts` | | 1(中性色) |
924
- | `src/repl/renderer.ts` | | 4(中性色) |
925
- | `src/tools/executor.ts` | ~15 | 3(中性色) |
926
- | `src/repl/setup-wizard.ts` | 26 | 0 |
927
-
928
- - 迁移映射规则:
929
- - `chalk.red` → `theme.error`(错误信息)
930
- - `chalk.yellow` → `theme.warning`(警告)或 `theme.toolCall`(工具标记,视上下文)
931
- - `chalk.green` → `theme.success`(成功确认)
932
- - `chalk.cyan` `theme.accent`(强调 ID/路径/值)
933
- - `chalk.dim` / `chalk.gray` `theme.dim`(次要文本)
934
- - `chalk.bold` / `chalk.bold.cyan` `theme.heading`(标题)
935
- - `chalk.magenta` `theme.toolCall`(工具/技能标记)
936
- - `chalk.white` → 保持不变(中性内容,不受主题影响)
937
- - 现在切换 `config.ui.theme` 为 `"light"` 或 `"custom"` 时,全部终端输出(命令/REPL/工具/向导)统一变化
938
-
939
- **Feature 3:edit_file 工具增强**(`src/tools/builtin/edit-file.ts`)
940
-
941
- - 新增 `similarityScore(a, b): number`:bigram(2-gram)字符串相似度算法,O(n) 复杂度,返回 0-1 分数
942
- - 新增 `findSimilarLines(fileContent, searchStr, maxResults=3): string[]`:当 `old_str` 匹配失败时,搜索文件中最相似的行(>50% 阈值),返回 "Line N: <text>" 格式建议
943
- - 新增 `findWhitespaceTolerant(fileLines, searchLines): { start, count } | null`:滑动窗口逐行 trim 匹配,支持忽略缩进差异
944
- - 新增 `ignore_whitespace` 可选参数(默认 false):启用时按行 trim 后匹配,保持唯一性检查
945
- - 增强错误消息格式:
946
- ```
947
- ERROR: old_str not found in file.
948
- File has 167 lines.
949
- Similar lines found (did you mean?):
950
- Line 42: const firstIndex = original.indexOf(oldStr);
951
- Line 88: if (firstIndex === -1) {
952
- Tip: If it's a whitespace/indentation issue, try setting ignore_whitespace: true
953
- Please read the file first and use exact text including whitespace/indentation.
954
- ```
955
-
956
- ### 版本与收尾
957
- - `src/core/constants.ts`:VERSION `0.1.37` → `0.1.38`
958
- - `package.json`:version 同步
959
- - `src/repl/renderer.ts`:`/about` 命令计数 32 33,命令列表新增 `/think`,新增 3 条特性条目(Extended Thinking / theme 全覆盖 / edit_file 智能提示)
960
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
961
- - 发布:`npm publish` `jinzd-ai-cli@0.1.38`
962
-
963
- ### 本轮变更文件汇总
964
-
965
- | 文件 | 变更类型 | 说明 |
966
- |------|---------|------|
967
- | `src/core/types.ts` | 修改 | ChatRequest 新增 `thinkingBudget` |
968
- | `src/core/constants.ts` | 修改 | VERSION 0.1.37 → 0.1.38 |
969
- | `src/config/schema.ts` | 修改 | ModelParamsSchema 新增 `thinkingBudget` |
970
- | `src/providers/claude.ts` | 重写 | Extended Thinking 完整支持(3 API 方法 + thinking 块处理) |
971
- | `src/repl/repl.ts` | 修改 | runtimeThinking + thinkingBudget + [THINK] 标记 + theme 迁移 |
972
- | `src/repl/renderer.ts` | 修改 | /about 更新(33 命令 + 3 特性) |
973
- | `src/repl/commands/index.ts` | 修改 | /think 命令 + 116 处 theme 迁移 |
974
- | `src/repl/setup-wizard.ts` | 修改 | 26 chalk→theme 全量迁移 |
975
- | `src/tools/executor.ts` | 修改 | ~15 处 chalk→theme 迁移 |
976
- | `src/tools/builtin/edit-file.ts` | 重写 | similarityScore + findSimilarLines + ignore_whitespace |
977
- | `package.json` | 修改 | version 0.1.37 → 0.1.38 |
978
-
979
- ### 下一步建议
980
-
981
- #### Tier 1 高价值功能(已全部完成)
982
- 1. ~~**`/config set` 快捷配置**~~:✅ 已实现(v0.1.49
983
- 2. ~~**流式工具调用(Streaming Tool Use)**~~:✅ 已在 v0.1.54 实现(OpenAI/Claude 流式 + DeepSeek/Kimi 非流式降级)
984
- 3. ~~**`/diff` 命令**~~:✅ 已实现(v0.1.49)
985
-
986
- #### Tier 2 体验增强
987
- 4. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复(safeReadPackageJson + detectNodeTestFramework)
988
- 5. **IDE 集成**:VS Code 扩展(架构已准备就绪,core/providers/tools 无终端依赖)
989
- 6. **OAuth/浏览器登录**:无需手动填 API Key,打开浏览器完成 OAuth 流程自动保存 token
990
- 7. ~~**`/undo` 增强**~~:✅ 已在 v0.1.48 实现(bash 文件追踪 + /undo list + /undo <n>)
991
- 8. ~~**对话分支(fork)**~~:✅ 已在 v0.1.48 实现(/fork [checkpoint-name])
992
-
993
- #### Tier 3 — 长远方向
994
- 9. **Web UI**:基于 EventBus + WebSocket 的浏览器界面,复用 core/providers/tools 层
995
- 10. **GitHub 仓库迁移**:从 gitee 迁移到 GitHub,npm 包受众更广
996
-
997
- ---
998
-
999
- ## 本轮开发完成记录(2026-03-05,v0.1.36 v0.1.37)
1000
-
1001
- ### P2 四项功能全部实现
1002
-
1003
- **Feature 1:项目级 `.mcp.json`**(`src/repl/repl.ts` + `src/repl/commands/index.ts` + `src/core/constants.ts`)
1004
- - `src/core/constants.ts`:新增 `MCP_PROJECT_CONFIG_NAME = '.mcp.json'`
1005
- - `src/repl/repl.ts`:
1006
- - 新增私有属性 `mcpServerSources = new Map<string, 'global' | 'project'>()`
1007
- - 新增 `loadProjectMcpConfig()` 方法:`getGitRoot(cwd)` 查找项目根读取 `<root>/.mcp.json` 基础校验(每个 server 须有 `command` 字段)→ 解析失败 stderr 警告不中断启动
1008
- - `start()` MCP 初始化重构:先获取全局 `mcpServers`,再获取项目 `.mcp.json`,合并(项目覆盖全局同名),记录来源到 `mcpServerSources`
1009
- - CommandContext 注入 `getMcpServerSource`
1010
- - `src/repl/commands/index.ts`:`/mcp` 命令显示每个服务器的 `[global]`/`[project]` 来源标签
1011
-
1012
- **Feature 2:`--resume <id>` 启动参数**(`src/index.ts` + `src/repl/repl.ts`)
1013
- - `src/index.ts`:新增 `--resume <id>` CLI 选项 + `startRepl` 参数传入
1014
- - `src/repl/repl.ts`:
1015
- - constructor options 扩展 `resumeSessionId?: string`
1016
- - `start()` `resumeSessionId` 时:`listSessions()` 前缀匹配 → 未找到显示最近 5 个 session + exit(1) → 找到则 `loadSession()` 恢复
1017
- - welcome 后显示 `📂 Resumed session: <id> (<N> messages, "<title>")`
1018
-
1019
- **Feature 3:Word wrap 配置**(`src/config/schema.ts` + `src/repl/renderer.ts` + `src/repl/repl.ts`)
1020
- - `src/config/schema.ts`:`ui` 对象新增 `wordWrap: z.number().int().min(0).default(0)`(0=自动,>0=固定列宽)
1021
- - `src/repl/renderer.ts`:
1022
- - 新增 `stripAnsi(s: string): string` — 去除 ANSI 转义码(正则匹配 ESC 序列)
1023
- - 新增 `wrapText(text: string, width: number | undefined): string` — 逐行处理,按单词边界折行,ANSI 码不计入宽度
1024
- - Renderer constructor 扩展 `options?: { wrapWidth?: number }`
1025
- - `renderResponse()` 对内容应用 `wrapText()`(流式模式不折行,由终端自然处理)
1026
-
1027
- **Feature 4:主题/颜色自定义**(`src/repl/theme.ts` + `src/config/schema.ts` + 多文件迁移)
1028
- - `src/config/schema.ts`:`ui` 对象新增 `theme: z.enum(['dark', 'light', 'custom']).default('dark')` + `colors` 对象(10 个可选色槽)
1029
- - **新文件** `src/repl/theme.ts`:
1030
- - `ThemeColors` 接口:10 个语义色槽(prompt/info/warning/error/success/dim/accent/toolCall/toolResult/heading)→ `ChalkInstance`
1031
- - `DARK_THEME`:精确匹配 v0.1.36 硬编码颜色(green/cyan/yellow/red/dim/magenta 等),确保零变化升级
1032
- - `LIGHT_THEME`:浅色终端优化(blue/blueBright/gray 等)
1033
- - `resolveColor(name: string)`:支持 chalk 颜色名 + `#hex` + `bold.cyan` 组合样式
1034
- - `buildCustomTheme(base, overrides)`:以 base 主题为底 + 自定义覆盖
1035
- - `initTheme(themeId, customColors?)`:设置全局主题
1036
- - `theme`:Proxy 导出,始终指向当前活跃主题
1037
- - 迁移覆盖:
1038
- - `src/repl/renderer.ts`:`printWelcome`(heading/prompt/dim)、`printPrompt`(prompt)、`renderStream`(accent)、`renderResponse`(accent)、`renderError`(error)、`printInfo`(warning)、`printSuccess`(success)
1039
- - `src/tools/executor.ts`:`printToolCall`(toolCall/accent/dim)、`printToolResult`(error/toolResult/warning)
1040
- - `src/repl/repl.ts`:constructor 中调用 `initTheme()`,传入 `wrapWidth` 给 Renderer
1041
-
1042
- ### 版本与收尾
1043
- - `src/core/constants.ts`:VERSION `0.1.36` → `0.1.37`
1044
- - `package.json`:version 同步
1045
- - `src/repl/renderer.ts`:`/about` 新增 4 条特性条目(项目级 .mcp.json / --resume / Word wrap / 主题系统)
1046
- - 构建验证:`npm run build` 零错误
1047
- - 发布:`npm publish` `jinzd-ai-cli@0.1.37`
1048
-
1049
- ### 本轮变更文件汇总
1050
-
1051
- | 文件 | 变更类型 | 说明 |
1052
- |------|---------|------|
1053
- | `src/core/constants.ts` | 修改 | VERSION 0.1.36 → 0.1.37,新增 MCP_PROJECT_CONFIG_NAME |
1054
- | `src/config/schema.ts` | 修改 | `ui.wordWrap` + `ui.theme` + `ui.colors` 三个新字段 |
1055
- | `src/repl/theme.ts` | 新增 | 集中式主题系统(ThemeColors + DARK/LIGHT + Proxy) |
1056
- | `src/repl/repl.ts` | 修改 | .mcp.json 加载 + --resume + theme 初始化 + mcpServerSources |
1057
- | `src/repl/renderer.ts` | 修改 | stripAnsi/wrapText 辅助函数 + theme 迁移 + /about 更新 |
1058
- | `src/tools/executor.ts` | 修改 | theme 迁移(printToolCall/printToolResult) |
1059
- | `src/repl/commands/index.ts` | 修改 | getMcpServerSource + /mcp 来源标签 |
1060
- | `src/index.ts` | 修改 | --resume CLI 选项 |
1061
- | `package.json` | 修改 | version 0.1.36 → 0.1.37 |
1062
-
1063
- ### 下一步建议
1064
- 1. ~~**Extended Thinking**~~:✅ 已在 v0.1.38 实现
1065
- 2. ~~**theme 迁移扩展**~~:✅ 已在 v0.1.38 全覆盖迁移
1066
- 3. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
1067
- 4. **IDE 集成**:VS Code 扩展(架构已准备就绪)
1068
- 5. **OAuth/浏览器登录**:无需手动填 API Key
1069
-
1070
- ---
1071
-
1072
- ## 本轮开发完成记录(2026-03-05,v0.1.34 v0.1.36)
1073
-
1074
- ### v0.1.35 P0 功能缺口 + P1 前三项
1075
-
1076
- **P0:并行工具调用**(`src/tools/executor.ts`)
1077
- - `executeAll()` safe 级别工具从顺序 `for` 循环改为 `Promise.all()` 并行执行,批量 safe 工具整体耗时降至最慢单个工具的时间
1078
-
1079
- **P0:`/add-dir` 动态目录上下文**(`src/repl/repl.ts` + `src/repl/commands/index.ts`)
1080
- - `Repl` 新增 `extraContextDirs` 数组 + `scanDirContent()`(目录树 + 文件内容,总限 40K 字符,单文件限 4K)
1081
- - `addExtraContextDir()` / `removeExtraContextDir()`:解析绝对路径,校验目录存在,存入数组
1082
- - `buildCurrentSystemPrompt()` 在 skill 段之前注入 `# Added Directory Context: <dir>` 段落
1083
- - `/add-dir <路径>`:添加目录;`/add-dir remove <路径>`:移除;`/add-dir`(无参):列出所有已添加目录
1084
-
1085
- **启动参数:`--allowed-tools` / `--blocked-tools`**(`src/index.ts` + `src/repl/repl.ts`)
1086
- - CLI 新增两个选项,逗号分隔工具名,转为 `Set<string>` 传入 `Repl` 构造函数
1087
- - `handleChatWithTools()` plan/skill 过滤之后叠加应用 allowedTools / blockedTools 过滤
1088
-
1089
- **P1:`/memory` 命令**(`src/repl/commands/index.ts`)
1090
- - `show`:打印 memory.md 内容;`add <内容>`:`appendFileSync` 追加带时间戳条目;`clear`:清空文件(需确认);`path`:显示文件路径
1091
-
1092
- **P1:`/doctor` 健康检查**(`src/repl/commands/index.ts`)
1093
- - 扫描所有 configured provider 的 API Key 状态(✓/✗)
1094
- - 检查 config.json 文件是否存在
1095
- - 调用 `getMcpManager().getStatus()` 显示 MCP 服务器连接状态
1096
- - 显示当前 session 消息数 + 估算 context 占用率(绿/黄/红)
1097
-
1098
- **文档与版本**
1099
- - `src/repl/commands/index.ts`:`CommandContext` 新增 `addContextDir` / `removeContextDir` / `listContextDirs` 三个回调
1100
- - `src/repl/renderer.ts`:命令计数 28 → 31,添加 `/add-dir` `/memory` `/doctor`,新增 5 条特性条目
1101
- - VERSION `0.1.34` → `0.1.35`,`package.json` 同步
1102
-
1103
- ---
1104
-
1105
- ### v0.1.36 P1 剩余三项全部完成
1106
-
1107
- **P1:`/bug` 反馈命令**(`src/repl/commands/index.ts`)
1108
- - 生成 Markdown 格式 Bug 报告模板,自动填入 VERSION / OS(`platform()`)/ Node.js 版本 / 当前 Provider / 当前 Model
1109
- - `--copy` 参数调用已有 `copyToClipboard()` 复制到剪贴板
1110
- - 无 `--copy` 时打印报告并显示提交链接(`REPO_URL/issues`)
1111
- - 新增 `VERSION` + `REPO_URL` 到 constants.ts import
1112
-
1113
- **P1:流式 JSON 输出**(`src/index.ts`)
1114
- - 新增 `--output-format <format>` CLI 选项,支持 `text`(默认)和 `streaming-json`
1115
- - `streaming-json` 模式:调用 `provider.chatStream()`,每个 delta 输出一行 NDJSON:
1116
- - `{"type":"delta","text":"..."}` —— 每个 chunk
1117
- - `{"type":"done","content":"...","provider":"...","model":"...","usage":{...}}` —— 最终行
1118
- - `--json` `streaming-json` 均强制不走普通 `useStream` 分支
1119
-
1120
- **P1:桌面通知**
1121
- - 新文件 `src/repl/notify.ts`:`sendNotification(title, body)` 跨平台非阻塞后台 spawn
1122
- - macOS:`osascript -e 'display notification "..." with title "..."'`
1123
- - Windows:PowerShell `System.Windows.Forms.NotifyIcon`(5 行脚本,`ShowBalloonTip(3000)`)
1124
- - Linux:`notify-send <title> <body>`
1125
- - 失败静默忽略(`try/catch` 包裹,不影响主流程)
1126
- - `src/config/schema.ts`:`ui` 对象新增 `notificationThreshold: z.number().default(10_000)`(单位 ms,0 = 禁用)
1127
- - `src/repl/repl.ts`:导入 `sendNotification`;`handleChat()` 在 `handleChatSimple`/`handleChatWithTools` 调用前记录 `t0 = Date.now()`,完成后对比阈值发送通知
1128
-
1129
- **文档与版本**
1130
- - `src/repl/commands/index.ts`:`/help` 新增 `/bug [--copy]` 条目
1131
- - `src/repl/renderer.ts`:命令计数 31 32,命令行列表加入 `/bug`,新增 3 条特性
1132
- - 新增 `USAGE.md`:21 章节完整用户使用说明文档(CLI / REPL 命令 / 工具 / 配置 / 各高级特性)
1133
- - VERSION `0.1.35` `0.1.36`,`package.json` 同步
1134
- - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
1135
-
1136
- ### 本轮变更文件汇总
1137
-
1138
- | 文件 | 变更类型 | 说明 |
1139
- |------|---------|------|
1140
- | `src/core/constants.ts` | 修改 | VERSION 0.1.34 → 0.1.36 |
1141
- | `src/config/schema.ts` | 修改 | `ui.notificationThreshold` 新增字段 |
1142
- | `src/tools/executor.ts` | 修改 | safe 工具并行执行(Promise.all) |
1143
- | `src/repl/repl.ts` | 修改 | extraContextDirs、allowedTools/blockedTools、sendNotification 集成 |
1144
- | `src/repl/notify.ts` | 新增 | 跨平台桌面通知实现 |
1145
- | `src/repl/commands/index.ts` | 修改 | /add-dir /memory /doctor /bug 四个命令 + /help 更新 |
1146
- | `src/repl/renderer.ts` | 修改 | /about 命令数 28→32,新增特性条目 |
1147
- | `src/index.ts` | 修改 | --allowed-tools / --blocked-tools / --output-format 三个新参数 |
1148
- | `package.json` | 修改 | version 0.1.34 → 0.1.36 |
1149
- | `USAGE.md` | 新增 | 21 章节完整用户使用说明文档 |
1150
-
1151
- ### 下一步建议(P2 优先)
1152
- 1. ~~**项目级 `.mcp.json`**~~:✅ 已在 v0.1.37 实现
1153
- 2. ~~**`--resume <id>` 启动参数**~~:✅ 已在 v0.1.37 实现
1154
- 3. ~~**Word wrap 配置**~~:✅ 已在 v0.1.37 实现
1155
- 4. ~~**主题/颜色自定义**~~:✅ 已在 v0.1.37 实现
1156
- 5. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
1157
-
1158
- ---
1159
-
1160
- ## 本轮开发完成记录(2026-03-03,v0.1.32 → v0.1.33)
1161
-
1162
- ### 高危安全修复:H1–H4 全部修复
1163
-
1164
- **H1MCP pendingRequests 内存泄漏**(`src/mcp/client.ts`):
1165
- `sendRequest()` 重构——resolve/reject 回调内含统一 `cleanup()` 函数(`pendingRequests.delete(id)` + `clearTimeout(timer)`),任何 Promise 完成路径都即时释放资源。`handleMessage()` 简化——不再重复调用 delete/clearTimeout(由回调内部处理)。
1166
-
1167
- **H2 — readline 竞态条件**(`src/tools/executor.ts`):
1168
- `confirm()` `batchConfirm()` 中 `completed` 布尔守卫从外部变量统一移入 `cleanup()` 函数内部(单一检查点模式)。`rl.once('line')` 注册用 `try/catch` 包裹,极端情况下(readline 已关闭)确保 `confirming` 标志不会卡死。
1169
-
1170
- **H3 — 工具执行错误语义混淆**(`src/tools/executor.ts`):
1171
- 所有用户取消/拒绝路径的 `content` 增加语义前缀:
1172
- - `[User cancelled]` — 用户在 confirm 对话框中按 N 或 Ctrl+C
1173
- - `[User rejected]` — 用户在 batchConfirm 中拒绝特定文件
1174
- - `[Permission denied]` 权限规则阻止
1175
- 所有前缀后附 "Do not retry without asking." 指示,防止 AI 无意义重试。
1176
-
1177
- **H4 — MCP 断开后无恢复机制**:
1178
- - `src/mcp/client.ts`:新增 `reconnect()` 方法——清理旧状态(rejectAllPending + killProcess + 重置缓冲区)后重新执行完整 `connect()` 流程
1179
- - `src/mcp/manager.ts`:新增 `reconnectServer(serverId)` 和 `reconnectAll()` 方法;`wrapMcpTool` 的 execute 函数在检测到断线时自动尝试一次重连,成功后继续调用
1180
- - `src/repl/commands/index.ts`:`/mcp reconnect [serverId]` 子命令——手动重连指定服务器或所有断开的服务器,成功后刷新 ToolRegistry 中的 MCP 工具
1181
-
1182
- **版本与收尾**
1183
- - `src/core/constants.ts`:VERSION `0.1.30` `0.1.33`
1184
- - `package.json`:version `0.1.32` → `0.1.33`
1185
-
1186
- ---
1187
-
1188
- ## 本轮开发完成记录(2026-03-03,v0.1.33 → v0.1.34)
1189
-
1190
- ### 中危安全修复:M5–M12 全面排查
1191
-
1192
- 对审计报告中 8 个中危问题逐一排查,发现 **M6–M11 已在先前版本修复或确认安全**,仅 **M5** **M12** 需要新代码修复。
1193
-
1194
- **M5 — bash `persistentCwd` 指向已删除目录**(`src/tools/builtin/bash.ts`):
1195
- - 问题:`persistentCwd` 模块级变量记住上次 `cd` 目标,但该目录可能在后续操作中被删除(如 `rm -rf`),导致下一次 bash 调用 `execSync` 报 `ENOENT`
1196
- - 修复:`execute()` 开头新增 `existsSync(persistentCwd)` 检查,不存在时自动回退到 `process.cwd()` 并打印 stderr 警告
1197
-
1198
- **M12 — MCP 工具 schema 嵌套结构丢失**(`src/mcp/manager.ts` + `src/tools/types.ts` + 三个 provider):
1199
- - 问题:MCP 工具的 JSON Schema 含嵌套 object/array 时,`convertDefinition()` 扁平化为简单类型,AI 生成的参数结构与实际 schema 不匹配导致调用失败
1200
- - 修复:
1201
- - `src/tools/types.ts`:`ToolParameterSchema` 新增 `properties?: Record<string, ToolParameterSchema>` 字段;`items` 类型从 `{ type: ToolParameterType }` 扩展为完整 `ToolParameterSchema`;新增 `schemaToJsonSchema()` 公共递归转换函数
1202
- - `src/mcp/manager.ts`:新增 `convertSchema()` 私有递归方法,替代原有扁平转换
1203
- - `src/providers/openai-compatible.ts`、`claude.ts`、`gemini.ts`:统一使用 `schemaToJsonSchema()` 构建 API 请求的工具参数 schema
1204
-
1205
- **M6–M11 排查结论**(无需修改):
1206
- | # | 结论 |
1207
- |---|------|
1208
- | M6 | web-fetch 手动 redirect 循环每一跳均做 SSRF 检查,已安全 |
1209
- | M7 | run-interactive `write()` 返回 false 时已有 `once('drain')` 背压处理 |
1210
- | M8 | session-manager `catch` 中已有 `stderr.write` 含文件名和错误信息 |
1211
- | M9 | mcp/client `killProcess()` 已有 `removeAllListeners()`(H4 修复附带) |
1212
- | M10 | permissions.ts 使用 `.includes()` 子串匹配而非正则,无 ReDoS 风险 |
1213
- | M11 | repl.ts 上下文加载已有 `resolve()` + `startsWith(cwd)` 路径穿越校验 |
1214
-
1215
- **版本与收尾**
1216
- - `src/core/constants.ts`:VERSION `0.1.33` → `0.1.34`
1217
- - `package.json`:version `0.1.33` → `0.1.34`
1218
- - 代码审查报告表格全部更新为已修复/已确认安全状态
1219
- - 构建通过:`npm run build` 零错误
1220
-
1221
- ### 安全审计完结状态
1222
- - **高危 H1–H4**:全部已修复(v0.1.33)
1223
- - **中危 M5–M12**:全部已修复或确认安全(v0.1.34)
1224
- - **低危 L1–L7**:L2–L7 已在 v0.1.30 修复/确认,L1 待后续改进
1225
- - **安全债务已清零**,可安全进入功能开发阶段
1226
-
1227
- ### 下一步建议
1228
- 1. **P0 功能缺口**:并行工具调用、`/add-dir` 命令
1229
- 2. **P1 功能缺口**:`/memory` 命令、`/doctor` 健康检查、`/bug` 反馈
1230
- 3. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
1231
-
1232
- ---
1233
-
1234
- ## 本轮开发完成记录(2026-03-01,v0.1.26 → v0.1.30)
1235
-
1236
- ### 安全修复续篇:低危 L2–L7
1237
-
1238
- **L2**(`src/tools/builtin/web-fetch.ts`):`htmlToText()` 函数新增 200 KB HTML 大小上限(`HTML_REGEX_LIMIT = 200_000`),超出则截断后再做正则替换,防止恶意大 HTML 导致正则性能崩溃。
1239
-
1240
- **L3**(`src/session/session-manager.ts`):新增 `safeDate(value: unknown): Date` 辅助函数,对无效日期字符串返回 `new Date(0)` 而非 `Invalid Date`,防止 `listSessions()` `searchMessages()` 的日期比较静默失败。
1241
-
1242
- **L4**(`src/tools/builtin/ask-user.ts` + `src/tools/builtin/google-search.ts`):为模块级全局上下文对象(`askUserContext` / `googleSearchContext`)添加架构说明注释,提示 GUI 多会话扩展时需重构为 per-session 状态。
1243
-
1244
- **L5**(`src/config/schema.ts`):为 `timeouts` 字段补充三层优先级文档注释:
1245
- 1. `modelParams[modelId].timeout` — 最高
1246
- 2. `timeouts[providerId]` provider 级默认
1247
- 3. 内置 provider 硬编码默认值 — 最低兜底
1248
-
1249
- **L6/L7**:确认已安全(`call.arguments['path']` 已用 `String(... ?? '')` 兜底;主循环 `line` handler 已有 try/catch)。
1250
-
1251
- ---
1252
-
1253
- ### 新增功能 1:多模态图片输入(P0)
1254
-
1255
- **背景**:用户希望通过 `@image.png 描述这张图` 语法将图片发送给各 AI Provider。
1256
-
1257
- **类型层**(已有,无需改动):`src/core/types.ts` `ImageContentPart { type: 'image_url', image_url: { url: 'data:mime;base64,...' } }` 与 OpenAI 格式完全兼容。
1258
-
1259
- **Claude Provider**(`src/providers/claude.ts`):
1260
- - 新增 `contentToClaudeParts()` 私有方法:将内部 `image_url` 格式转换为 Anthropic SDK 期望的 `{ type: 'image', source: { type: 'base64', media_type, data } }` 格式
1261
- - 应用到 `chat()`、`chatStream()`、`chatWithTools()` 的消息构建
1262
-
1263
- **Gemini Provider**(`src/providers/gemini.ts`):
1264
- - 移除错误的 `getContentText()` 调用(会丢弃图片)
1265
- - 新增 `contentToGeminiParts()` 私有方法:转换为 Gemini SDK 格式 `{ inlineData: { mimeType, data } }`
1266
- - 修复 `toGeminiHistory()`、`chat()`、`chatStream()`、`chatWithTools()` 的消息构建
1267
- - `chatWithTools()` 中变量 `lastMessage: string` 重命名为 `lastMsgParts: Part[]`,历史嵌入改为 `{ role: 'user', parts: lastMsgParts }`
1268
-
1269
- **OpenAI 兼容 Provider**:已就绪,格式一致,无需修改。
1270
-
1271
- **REPL 层**(`src/repl/repl.ts`):
1272
- - 新增常量 `MAX_IMAGE_BYTES = 10 * 1024 * 1024`(10 MB)
1273
- - `parseAtReferences()` 图片读取前用 `statSync` 校验大小,超限时加入 `refs` 类型为 `'toolarge'`,不内联
1274
- - `handleChat()` 新增 `toolarge` 分支:打印黄色警告 `⚠ Image too large (> 10 MB): <path>`
1275
- - 修复 `getVisionModelHint()`:Claude/Gemini 返回 `null`(原生支持,无需警告);DeepSeek 显示明确不支持提示;zhipu 推荐 `glm-4.6v`;kimi `moonshot-v1-*` 推荐对应 vision 版本
1276
-
1277
- ---
1278
-
1279
- ### 新增功能 2:Escape/Ctrl+C 中断 AI 流式输出(P0)
1280
-
1281
- **背景**:流式生成期间无法中断,必须等待 AI 返回完整内容。
1282
-
1283
- **架构设计**:两个流式生成入口——`handleChatSimple()` `handleChatWithTools()` tee streaming 分支(`save_last_response` 工具)——均支持中断。主路径(`chatWithTools()` → `renderResponse()`)为非流式,暂不支持。
1284
-
1285
- **`src/core/types.ts`**:`ChatRequest` 新增 `signal?: AbortSignal`,透传给 Provider API 调用。
1286
-
1287
- **Provider 层**:
1288
- - `claude.ts`:`messages.stream()` 第二参数传入 `{ signal: request.signal }`(Anthropic SDK 支持)
1289
- - `openai-compatible.ts`:流式 `create()` 第二参数传入 `{ signal: request.signal }`(OpenAI SDK 支持)
1290
- - `gemini.ts`:生成器循环开头检查 `if (request.signal?.aborted) break`(兼容性保险)
1291
-
1292
- **`src/repl/renderer.ts`**:`renderStream()` `options` 新增 `signal?: AbortSignal`:
1293
- - `for await` 循环开头检查 `signal.aborted` → 标记 `interrupted = true` `break`
1294
- - 捕获 SDK 抛出的 `AbortError`(name 检测)→ 同样标记 `interrupted`
1295
- - 中断时 `flushBuf()` 输出残留缓冲,打印灰色 `[interrupted]` 提示
1296
- - 返回值不变(`{ content, usage, tokensShown }`),`content` 为已生成的部分内容
1297
-
1298
- **`src/repl/repl.ts`**:
1299
- - 新增类属性 `streamAbortController: AbortController | null` + `_escHandler: ((d: Buffer) => void) | null`
1300
- - 新增 `setupStreamInterrupt()` 方法:创建 `AbortController`,`process.stdin.resume()`(绕过 `rl.pause()` 暂停),注册原始字节监听器检测纯 ESC(`0x1b`,单字节,区别于 ESC 序列)和 Ctrl+C(`0x03`)
1301
- - 新增 `teardownStreamInterrupt()` 方法:移除监听器,`process.stdin.pause()`,清空引用
1302
- - SIGINT 处理器最顶部新增分支:`streamAbortController` 非空时 `abort()` 并 return,不退出程序
1303
- - `handleChatSimple()` 流式分支和 tee streaming 分支均用 `setupStreamInterrupt()`/`teardownStreamInterrupt()` 包裹 + `signal` 传入
1304
-
1305
- **版本与收尾**
1306
- - `src/core/constants.ts`:VERSION `0.1.26` → `0.1.30`(合并前几次 bump)
1307
- - `package.json`:version `0.1.29` `0.1.30`
1308
-
1309
- ---
1310
-
1311
- ## 本轮开发完成记录(2026-03-01,v0.1.25 → v0.1.26)
1312
-
1313
- ### 新增功能:Context 自动管理 + 测试报告 + 脚手架
1314
-
1315
- **功能 1:Context 自动管理**
1316
- - `src/core/constants.ts`:新增 `CONTEXT_PRESSURE_THRESHOLD`(0.8) / `CONTEXT_WARNING_THRESHOLD`(0.6)
1317
- - `src/config/schema.ts`:新增 `autoCompact: z.boolean().default(true)` 配置项
1318
- - `src/repl/repl.ts`:新增 `estimateTokens()`(字符估算 token)、`estimateConversationTokens()`(system prompt + messages 总估算)、`getContextWindowSize()`、`checkContextPressure()`(超 80% 自动 compact)
1319
- - `handleChatSimple()` `handleChatWithTools()` 末尾自动调用 `checkContextPressure()`
1320
- - `/status` 命令新增 `Context%` 行:显示估算 token / context window(绿色 <60%,黄色 60-80%,红色 >80%)
1321
-
1322
- **功能 2:`run_tests` 工具 + `/test` 命令**
1323
- - 新增 `src/tools/builtin/run-tests.ts`:
1324
- - 自动检测项目类型:Maven → `mvn test`、Gradle → `gradle test`、npm `npm test`、pytest、cargo、go test
1325
- - JUnit XML 解析:扫描 `target/surefire-reports/*.xml`,提取 testsuite 属性 + 失败用例详情
1326
- - 通用文本解析:正则匹配 Maven/Jest/pytest/cargo/go 格式的测试结果摘要
1327
- - 彩色终端报告(直接 `console.log()`,绕过 executor 截断)
1328
- - 返回 Markdown 结构化报告给 AI
1329
- - 支持 `command`(自定义命令)和 `filter`(测试名过滤)参数
1330
- - `src/tools/registry.ts`:注册 `runTestsTool`
1331
- - `src/tools/types.ts`:`getDangerLevel()` 中 `run_tests` → `safe`
1332
- - `src/core/constants.ts`:`SUBAGENT_ALLOWED_TOOLS` 新增 `run_tests`,新增 `TEST_TIMEOUT = 300_000`
1333
- - `/test` REPL 命令:快捷方式调用 `executeTests()`
1334
-
1335
- **功能 3:`/scaffold` 脚手架命令**
1336
- - `src/repl/commands/index.ts`:新增 `/scaffold <description>` 命令
1337
- - 构建结构化 prompt → 通过 `ctx.sendAsChat()` 注入为用户消息 → AI 使用现有工具(bash/write_file/spawn_agent)自动创建项目骨架
1338
- - `src/repl/repl.ts`:新增 `sendAsChat(message)` 方法——添加 user message + 触发 handleChatWithTools
1339
- - CommandContext 新增 `estimateConversationTokens`、`getContextWindowSize`、`sendAsChat` 三个回调
1340
-
1341
- **版本与收尾**
1342
- - `src/core/constants.ts`:VERSION `0.1.25` → `0.1.26`
1343
- - `package.json`:version 同步
1344
- - `src/repl/renderer.ts`:/about 工具计数 15→16,命令计数 26→28,新增 3 条特性(Context 管理、run_tests、/scaffold)
1345
-
1346
- ---
1347
-
1348
- ## 本轮开发完成记录(2026-02-28,v0.1.24v0.1.25)
1349
-
1350
- ### 新增功能:Tab 自动补全 + 流式 Token 计数
1351
-
1352
- **功能 1:Tab 自动补全**
1353
- - `src/repl/repl.ts`:`readline.createInterface` 注入 `completer` 回调
1354
- - 新增 `completeInput(line)` 方法:根据输入上下文分发补全逻辑
1355
- - 命令名补全:`/pro<TAB>` `/provider`(内置 + 自定义命令)
1356
- - 子命令参数:`/provider <TAB>` provider 列表;`/model <TAB>` → 模型列表;`/checkpoint <TAB>` → save/restore/list/delete 等
1357
- - `@` 文件路径补全:`@src/re<TAB>` `@src/repl/`(递进目录补全,跳过隐藏文件)
1358
- - 新增 `completeFilePath(partial)` 辅助方法:`readdirSync` + `statSync` 扫描目录,目录追加 `/` 后缀
1359
-
1360
- **功能 2:流式 Token 计数(内联显示)**
1361
- - `src/repl/renderer.ts`:`renderStream()` 签名扩展 `showTokens?` + `sessionTotal?`
1362
- - 流结束后在 `\n\n` 之前立即内联显示 token 计数
1363
- - 有精确 usage(OpenAI/Gemini)→ 显示完整 `📊 in X + out Y = Z tokens │ session total: N`
1364
- - 无精确 usageClaude streaming)→ 基于字符数估算 `📊 ~X output tokens (estimated)`
1365
- - 返回值新增 `tokensShown: boolean` 标志,防止 REPL 重复调用 `renderUsage()`
1366
- - `src/repl/repl.ts`:`handleChatSimple()` + tee 流式路径传入 `showTokens`/`sessionTotal`,条件跳过后续 `renderUsage()`
1367
-
1368
- **Bug 修复:DeepSeek contextWindow**
1369
- - `src/providers/deepseek.ts`:`deepseek-chat`(V3)contextWindow 65536 → 131072(128K),与官方 API 文档一致。`deepseek-reasoner`(R1)保持 65536(64K)。
1370
-
1371
- **安全加固:web_fetch DNS SSRF 二次校验**
1372
- - `src/tools/builtin/web-fetch.ts`:新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名
1373
- - 域名解析到私有 IP(RFC1918/loopback/link-local)时阻断请求
1374
- - 初始 URL 和 redirect 目标均校验
1375
- - IP 字面量跳过 DNS 解析(已由 `isPrivateHost()` 覆盖)
1376
- - DNS 解析失败不拦截,让后续 fetch 自然报错
1377
-
1378
- **版本与收尾**
1379
- - `src/core/constants.ts`:VERSION `0.1.24` → `0.1.25`
1380
- - `package.json`:version 同步
1381
- - `src/repl/renderer.ts`:/about 新增 2 条特性条目
1382
-
1383
- ---
1384
-
1385
- ## 本轮开发完成记录(2026-02-28,v0.1.22 v0.1.23)
1386
-
1387
- ### P1 全部 5 个功能实现
1388
-
1389
- **背景**:P0 全部完成后,进入 P1 重要差距功能开发。一次性实现全部 5 项。
1390
-
1391
- ### 功能 1:`/copy` 剪贴板支持
1392
-
1393
- **实现**:`src/repl/commands/index.ts`
1394
- - `copyToClipboard()` 辅助函数:跨平台调用原生命令(Windows: `clip` / macOS: `pbcopy` / Linux: `xclip -selection clipboard`,fallback `xsel --clipboard --input`)
1395
- - 通过 `execSync` 管道写入,无外部 npm 依赖
1396
- - `CommandContext` 新增 `getLastResponse: () => string`
1397
- - `repl.ts` ctx 注入:`getLastResponse: () => lastResponseStore.content`
1398
-
1399
- ### 功能 2:`/cost` Token 用量统计
1400
-
1401
- **实现**:`src/repl/commands/index.ts`
1402
- - 显示 session 累计 input/output/total tokens + provider/model/消息数
1403
- - `/cost reset` 重置计数器
1404
- - 使用已有 `ctx.getSessionTokenUsage()` `ctx.resetSessionTokenUsage()`
1405
-
1406
- ### 功能 3:`/init` 项目初始化
1407
-
1408
- **实现**:`src/repl/commands/index.ts`
1409
- - `SCAN_SKIP_DIRS` Set(node_modules、.git、dist 等 20+ 目录)
1410
- - `ProjectInfo` 接口 + `scanDirTree()` 递归目录树(深度 4,每层 30 项)
1411
- - `scanProject()` 检测项目类型(package.json/Cargo.toml/pyproject.toml/go.mod 等)
1412
- - `buildInitPrompt()` 构造 AI 提示词,要求生成结构化 AICLI.md
1413
- - `CommandContext` 新增 `chatOnce(prompt, options?)` 方法
1414
- - `repl.ts` ctx 实现:调用 provider.chat() 非流式,temperature=0.3
1415
- - 已存在 AICLI.md 时需 `/init --force` 覆盖
1416
-
1417
- ### 功能 4:Agent Skills 系统
1418
-
1419
- **新文件**:
1420
- - `src/skills/types.ts`:`SkillMeta`(name/description/tools?)、`Skill`(meta/content/filePath)接口;`parseSimpleYaml()`(regex key:value)、`parseYamlArray()`(`[a, b, c]` 格式)、`parseSkillFile()`(读取 .md → 提取 YAML frontmatter → 返回 Skill)
1421
- - `src/skills/manager.ts`:`SkillManager` 类——`loadSkills()`(扫描 skillsDir,自动创建目录)、`activate(name)` / `deactivate()`、`getActivePromptContent()` / `getActiveToolFilter()`(返回工具名 Set)
1422
-
1423
- **集成**(`src/repl/repl.ts`):
1424
- - `private skillManager: SkillManager | null` 字段
1425
- - `start()` 中初始化 SkillManager、加载技能、显示数量
1426
- - `buildCurrentSystemPrompt()` 注入激活技能的 content(在项目上下文之后,plan mode 之前)
1427
- - `handleChatWithTools()` 工具过滤:Plan Mode > Skill 白名单 > 全部工具
1428
- - `refreshPrompt()` 显示 `[skill:name]` 洋红色标记
1429
-
1430
- **命令**(`/skill`):
1431
- - `/skill` 或 `/skill list`:列出所有技能
1432
- - `/skill <name>`:激活指定技能
1433
- - `/skill off`:停用当前技能
1434
- - `/skill reload`:重新扫描技能目录
1435
-
1436
- **常量**:`src/core/constants.ts` 新增 `SKILLS_DIR_NAME = 'skills'`
1437
-
1438
- ### 功能 5:多文件编辑预览(批量确认)
1439
-
1440
- **修改**:
1441
- - `src/tools/types.ts`:新增 `isFileWriteTool(name)` 判断 write_file / edit_file
1442
- - `src/tools/executor.ts`:`executeAll()` 重构为三组分流:
1443
- 1. `safeCalls`(safe 级别)→ 先执行(保证 mkdir 等前置操作完成)
1444
- 2. `fileWriteCalls`(isFileWriteTool && write)→ 单个走原有确认,2+ 个走批量
1445
- 3. `otherCalls`(剩余 write/destructive)→ 逐个原有确认流程
1446
- - 新增 `executeBatchFileWrites(calls)`:展示编号列表 + diff 预览`batchConfirm()`
1447
- - 新增 `batchConfirm(count)`:`[a]pprove all / [r]eject all / [1,3,5] approve specific`
1448
- - 使用 `rl.once('line')` 读整行(与 `confirm()` 模式一致)
1449
- - 解析逗号分隔数字编号,返回 `'all' | 'none' | Set<number>`
1450
- - 支持 Ctrl+C 取消(复用 `cancelConfirmFn`)
1451
-
1452
- ### 架构变更
1453
- - `src/repl/commands/index.ts`:CommandContext 新增 `getLastResponse` / `chatOnce` / `refreshPrompt` / `getSkillManager`;新增 /copy /cost /init /skill 共 4 个命令;/help 更新
1454
- - `src/repl/repl.ts`:SkillManager 集成 + 4 个新 ctx 方法
1455
- - `src/repl/renderer.ts`:`/about` REPL 命令 19 → 23,新增 3 条特性(Agent Skills / /init / 多文件编辑预览)
1456
-
1457
- ### 版本与发布
1458
- - `src/core/constants.ts`:VERSION `0.1.22` → `0.1.23`
1459
- - `package.json`:version `0.1.22` → `0.1.23`
1460
-
1461
- ---
1462
-
1463
- ## 本轮开发完成记录(2026-02-28,v0.1.23 → v0.1.24)
1464
-
1465
- ### P2 全部 5 功能实现
1466
-
1467
- **功能 1:Hooks 系统(pre/post tool execution)**
1468
- - 新增 `src/tools/hooks.ts`:`ToolHookConfig` 接口 + `runHook(template, vars)` 函数(模板变量替换 → execSync 5s 超时,失败 stderr 警告)
1469
- - `src/config/schema.ts`:新增 `hooks` 可选字段(`preToolExecution`/`postToolExecution`)
1470
- - `src/tools/executor.ts`:新增 `setConfig()` 方法;`execute()` 中 getDangerLevel 后调 pre hook,工具完成/失败后调 post hook
1471
-
1472
- **功能 2:Permission Rules(基于规则的工具权限)**
1473
- - 新增 `src/tools/permissions.ts`:`PermissionRule` 接口 + `checkPermission()` 纯函数(首匹配规则,tool 支持 `*` 通配,when 条件含 dangerLevel/pathPattern)
1474
- - `src/config/schema.ts`:新增 `permissionRules` 数组 + `defaultPermission`(默认 `confirm`)
1475
- - `src/tools/executor.ts`:execute() 中 getDangerLevel 后调 checkPermission → deny 直接拒绝 / auto-approve 跳过 confirm / confirm 走原有流程
1476
-
1477
- **功能 3:Checkpointing(会话检查点)**
1478
- - `src/session/session.ts`:新增 `CheckpointMeta` 接口(name/messageIndex/timestamp)、`checkpoints` 数组、4 个方法(createCheckpoint/restoreCheckpoint/listCheckpoints/deleteCheckpoint)、toJSON/fromJSON 更新
1479
- - `src/repl/commands/index.ts`:新增 `/checkpoint` 命令(save/restore/list/delete 子命令)
1480
-
1481
- **功能 4:`/review` 代码审查**
1482
- - `src/repl/commands/index.ts`:新增 `buildReviewPrompt()` 辅助函数(中文结构化审查 prompt)+ `/review` 命令(`--staged`/`--detailed`,diff 超 8000 字截断保头尾)
1483
-
1484
- **功能 5:Custom Commands(用户自定义命令)**
1485
- - 新增 `src/repl/custom-commands.ts`:`CustomCommandManager` 类(loadCommands/listCommands/getCommand)+ `expandTemplate()` 模板变量展开(`{{input}}/{{file:path}}/{{git-diff}}/{{git-context}}`)
1486
- - `src/core/constants.ts`:新增 `CUSTOM_COMMANDS_DIR_NAME = 'commands'`
1487
- - `src/repl/repl.ts`:CustomCommandManager 集成(初始化 + handleCommand fallback + ctx 方法)
1488
- - `src/repl/commands/index.ts`:新增 `/commands` 命令(list/reload)
1489
-
1490
- **版本与收尾**
1491
- - `src/core/constants.ts`:VERSION `0.1.23` → `0.1.24`
1492
- - `package.json`:version 同步
1493
- - `src/repl/renderer.ts`:/about 命令计数 23 26,新增 5 条 P2 特性条目
1494
- - 所有 P2 路线图条目标记为已完成
1495
-
1496
- ---
1497
-
1498
- ## 本轮开发完成记录(2026-02-27,v0.1.21 v0.1.22)
1499
-
1500
- ### 新增功能:Sub-Agent 子代理系统(`spawn_agent` 工具)
1501
-
1502
- **背景**:P0 路线图最后一个核心功能。允许主 AI 将复杂子任务委派给独立子代理执行,子代理拥有隔离对话和 agentic 循环。
1503
-
1504
- **实现**:
1505
- - 新增 `src/tools/builtin/spawn-agent.ts`:
1506
- - `spawnAgentContext` 共享上下文对象(遵循 streamToFileContext 模式,由 repl.ts 注入)
1507
- - `SubAgentExecutor` 类:子代理专用工具执行器,write 自动确认、destructive 直接阻止、带 ` ┃ ` 前缀的嵌套终端输出
1508
- - 独立 agentic 循环:chatWithTools executeAll → buildToolResultMessages,最多 max_rounds 轮
1509
- - 子代理 system prompt:继承父级 system prompt + 子代理模式说明(任务规则、工具限制)
1510
- - `src/core/constants.ts`:新增 `SUBAGENT_DEFAULT_MAX_ROUNDS`(10) / `SUBAGENT_MAX_ROUNDS_LIMIT`(15) / `SUBAGENT_ALLOWED_TOOLS`(11个工具白名单)
1511
- - `src/tools/registry.ts`:新增 `unregister(name)` 方法(供子代理创建过滤工具集);注册 `spawnAgentTool`
1512
- - `src/tools/types.ts`:`getDangerLevel` `spawn_agent` → `safe`
1513
- - `src/repl/repl.ts`:handleChatWithTools 中注入 spawnAgentContext(provider/model/systemPrompt/modelParams/configManager)
1514
-
1515
- **工具参数**:
1516
- - `task`(string, required):子代理任务描述,包含所有必要上下文
1517
- - `max_rounds`(number, optional, default 10, 限制 1-15):最大工具调用轮次
1518
-
1519
- **安全设计**:
1520
- - 递归防护:子代理工具集不含 `spawn_agent`
1521
- - 用户交互隔离:子代理无 `ask_user`、`save_memory`、`save_last_response`
1522
- - 破坏性操作阻止:`destructive` 级别工具调用直接返回错误
1523
- - 写操作自动确认:`write` 级别无需用户确认(主 AI 已明确委派)
1524
- - 轮次上限:max_rounds 限制在 1-15,防止无限循环
1525
-
1526
- **终端输出**:子代理工具调用和结果使用 ` ┃ ` 前缀 + 箱形边框(┏/┗),清晰区分父级与子代理输出
1527
-
1528
- ### 架构变更
1529
- - `src/repl/renderer.ts`:`/about` 工具计数 14 → 15,新增 `spawn_agent` 条目和 Sub-Agent 特性条目
1530
-
1531
- ---
1532
-
1533
- ## 本轮开发完成记录(2026-02-25,v0.1.18 → v0.1.20)
1534
-
1535
- ### Bug 修复:Kimi XML 伪工具调用(v0.1.19)
1536
-
1537
- **背景**:Kimi-k2 在 OpenAI function calling 接口下存在已知缺陷——当生成内容较长时,会在回复文本中输出 `<write_file>...</write_file>` 等 XML 格式的伪工具调用标签,而非通过 API 的 `tool_calls` 机制。ai-cli 无法捕获文本中的 XML,导致文件写入等操作静默丢失,任务失败。
1538
-
1539
- **根本原因**:Kimi-k2 训练时混合了旧版 XML 工具调用格式,当模型注意力涣散(长内容生成)时退化为文本输出工具调用,而非通过结构化 `tool_calls` 数组返回。
1540
-
1541
- **修复**:`src/providers/kimi.ts` 覆写 `chatWithTools()`,叠加两道防线:
1542
-
1543
- - **方案 B(预防)**:在 `request.systemPrompt` 末尾追加 `KIMI_TOOL_CALL_REMINDER`——中文强制规范提示,指示 Kimi 必须使用 API function calling,禁止文本中输出 XML 工具标签
1544
- - **方案 A(兜底)**:响应返回后,若 `content` 中含有 `<tool_name>...</tool_name>` 模式,调用私有 `parseXmlToolCalls()` 方法解析并转换为 `ToolCall[]`,注入到 agentic 执行循环
1545
-
1546
- **`parseXmlToolCalls()` 设计要点**:
1547
- - 使用 `indexOf` 而非正则,避免 14KB+ Markdown 内容时的正则回溯性能问题
1548
- - 快速路径:`!content.includes('</')` 直接跳过无 XML 的响应
1549
- - 按已知参数名(`tool.parameters` 的 key)逐一提取参数值,安全可靠
1550
- - tool call id 格式 `xml-fallback-{ts}-{idx}`,便于调试日志追踪
1551
- - stderr 输出警告提示用户发生了 XML 伪调用救援
1552
-
1553
- **架构约束**:仅在 `kimi.ts` 中覆写,不影响 DeepSeek / Zhipu / Claude / Gemini 等其他 Provider
1554
-
1555
- ### `/about` 特性列表更新(v0.1.20)
1556
-
1557
- **变更**:`src/repl/renderer.ts``printAbout()` 主要特性列表新增 4 项、更新 1 项:
1558
- - 项目上下文文件说明补充「三层级:全局/项目/子目录」
1559
- - Plan Mode(`/plan` 进入只读规划 → `/plan execute` 切回)
1560
- - Headless 模式(`-p` 单轮非交互 + stdin 管道 + `--json`)
1561
- - `/compact` 上下文压缩(摘要替换旧消息,保留最近 4 条)
1562
- - Kimi 工具调用可靠性增强(XML 伪调用自动检测并转换,v0.1.19)
1563
-
1564
- ---
1565
-
1566
- ## 本轮开发完成记录(2026-02-25,v0.1.17 v0.1.18)
1567
-
1568
- ### 新增功能:`/compact` 上下文压缩
1569
-
1570
- **背景**:长对话后 context window 接近上限,需要将旧消息压缩为摘要继续对话。
1571
-
1572
- **实现**:
1573
- - `src/session/session.ts`:新增 `compact(summaryMsg, ackMsg, keepLast)` 方法——将旧消息替换为 user/assistant 摘要对,保留末尾 `keepLast` 条消息,返回移除数量
1574
- - `src/repl/repl.ts`:新增私有 `compactSession(instruction?)` 方法:
1575
- - 常量 `COMPACT_KEEP_LAST = 4`、`MIN_MESSAGES_TO_COMPACT = 6`
1576
- - 调用当前 provider `chat()` 非流式生成摘要(temperature=0.3,maxTokens≤4096)
1577
- - 构造 user/assistant 摘要消息对,调用 `session.compact()`
1578
- - 注入 ctx:`compactSession`
1579
- - `src/repl/commands/index.ts`:`CommandContext` 新增 `compactSession` 回调;新增 `/compact [instruction]` 命令;更新 `/help`
1580
-
1581
- ### 新增功能:Headless 非交互模式(`-p` flag)
1582
-
1583
- **背景**:CI/CD 脚本、管道场景需要单轮非交互式调用,`echo "code" | aicli -p "review"` 即可。
1584
-
1585
- **实现**:
1586
- - `src/index.ts`:
1587
- - `--provider` 移除 `-p` 短选项(仅保留长形式),将 `-p` 分配给 `--prompt <text>`
1588
- - 新增 `--system <prompt>` 覆盖 system prompt
1589
- - 新增 `--json` flag:输出 JSON `{content, provider, model, usage}` 并退出
1590
- - `action()` handler:`options.prompt !== undefined` 时路由到 `runHeadless()`
1591
- - `readStdin()`:非 TTY 时读取完整 stdin,TTY 时返回空串
1592
- - `runHeadless()`:初始化 provider → 拼接 stdin+prompt → 调用 chat/chatStream → 输出 → exit(0)
1593
- - 流式模式:直接 `process.stdout.write(delta)` "Assistant:" 前缀,管道友好
1594
- - `--json` 模式:强制非流式,输出格式化 JSON
1595
-
1596
- ### 新增功能:Plan Mode 规划模式(`/plan`)
1597
-
1598
- **背景**:对标 Gemini CLI,让 AI 先做只读分析规划,用户确认后再执行写操作。
1599
-
1600
- **实现**:
1601
- - `src/core/constants.ts`:
1602
- - `PLAN_MODE_READONLY_TOOLS`:只读工具白名单 Set(read_file / list_dir / grep_files / glob_files / web_fetch / google_search / ask_user / write_todos)
1603
- - `PLAN_MODE_SYSTEM_ADDON`:注入 system prompt 末尾的中文说明(告知 AI 当前处于只读规划模式及允许/禁用工具)
1604
- - `src/repl/repl.ts`:
1605
- - 新增 `private planMode = false` 属性
1606
- - `refreshPrompt()`:plan mode 时显示 `[deepseek][PLAN] >(黄色 PLAN 标记)`
1607
- - `buildCurrentSystemPrompt()`:plan mode 时追加 `PLAN_MODE_SYSTEM_ADDON`(最后段)
1608
- - `handleChatWithTools()`:plan mode 时过滤 toolDefs 为只读白名单,AI 不可见禁用工具
1609
- - ctx 注入:`getPlanMode` / `setPlanMode`
1610
- - `src/repl/commands/index.ts`:`CommandContext` 新增 `getPlanMode` / `setPlanMode`;新增 `/plan` 命令(子命令:enter/execute/exit/cancel/status);更新 `/help`
1611
-
1612
- ### 架构变更
1613
- - `src/repl/renderer.ts`:`/about` REPL 命令计数 17 → 19,命令列表新增 `/compact` 和 `/plan`
1614
- - `src/repl/commands/index.ts`:命令注册总数 17 → 19
1615
-
1616
- ---
1617
-
1618
- ## 本轮开发完成记录(2026-02-22,v0.1.12 v0.1.13)
1619
-
1620
- ### 新增功能:开发状态交接系统(Dev State Handoff)
1621
-
1622
- **背景**:用户在开发过程中频繁切换 AI 模型,切换后新模型对之前的开发上下文一无所知,导致工作不连续。
1623
-
1624
- **实现**:
1625
- - 新增 `src/repl/dev-state.ts` 模块:快照提示词(SNAPSHOT_PROMPT)、save/load/clear 函数、`sessionHasMeaningfulContent` 会话内容判断
1626
- - 新增 `DEV_STATE_FILE_NAME` 常量(`src/core/constants.ts`)
1627
- - `/provider` `/model` 命令重构:
1628
- - **同值检测**:选择相同 provider/model 时显示 "Already using X",不触发任何变更(不创建新 session、不生成快照)
1629
- - **自动快照**:真正切换时,调用当前 AI 非流式生成 7 段式结构化快照 → 保存到 `~/.aicli/dev-state.md`
1630
- - `buildCurrentSystemPrompt()` 注入 dev-state:日期时间 → 持久记忆 → **开发状态快照** → 项目上下文
1631
- - `/clear` `/session new` 自动清除 dev-state.md,防止旧快照污染无关工作
1632
- - 启动时若存在 dev-state.md,显示 `🔄 Dev state handoff: loaded (N chars)`
1633
-
1634
- ### Bug 修复:maxTokens 未传递导致大内容生成被截断
1635
-
1636
- **问题**:用户使用智谱 glm-4-plus 生成 80 题模考试卷,在第 73 题处被截断。根因:`DEFAULT_MAX_TOKENS = 8192` 定义在 constants.ts 中但从未被使用,`getModelParams()` 在用户未配置 modelParams 时返回 `{}`,OpenAI 兼容 provider 收到 `max_tokens: undefined` 后由 API 服务端默认值(~4096)兜底。
1637
- **修复**:`getModelParams()` 中为 `maxTokens` 添加 `?? DEFAULT_MAX_TOKENS` 兜底,让已定义的常量真正生效。所有 provider 统一受益。
1638
-
1639
- ### 版本与发布
1640
- - `src/core/constants.ts`:VERSION `0.1.11` → `0.1.13`
1641
- - `package.json`:version `0.1.11` → `0.1.13`
1642
-
1643
- ---
1644
-
1645
- ## 本轮开发完成记录(2026-02-22,安全加固版 0.1.10)
1646
-
1647
- ### 背景
1648
- ai-cli 全量代码进行安全审计,共发现并修复 **3 个高危、4 个中危、4 个低危**漏洞,版本从 0.1.9 升至 0.1.10 发布。
1649
-
1650
- ### 修复:高危(H)
1651
-
1652
- **H1 `executor.ts`:`write` 级别操作未触发用户确认**
1653
- - 根本原因:`execute()` 条件分支逻辑缺陷,`write` 级别跳过了 `confirm()` 调用
1654
- - 修复:重构三路分支;`write` → printToolCall + printDiffPreview + confirm;`destructive` confirm + printToolCall;`safe` → printToolCall 后直接执行
1655
-
1656
- **H2 `types.ts`:`getDangerLevel()` bash 危险命令覆盖不完整**
1657
- - 漏洞:`rm -r`(不带 `-f`)、`rm file.txt`(无标志)、`--recursive`/`--force` 长标志均被识别为 `safe`;`del + mkdir` 组合绕过 destructive 判断;PowerShell 写操作(Set-Content、Out-File 等)未分类
1658
- - 修复:扩展正则覆盖上述所有情形;移除错误的 `del+mkdir` 豁免;新增 PowerShell 写操作 → `write`
1659
-
1660
- **H3 — `registry.ts` / `schema.ts` / `repl.ts`:插件系统无权限门控,任意 JS 文件自动加载执行**
1661
- - 漏洞:`loadPlugins()` 对 `~/.aicli/plugins/` 下所有 `.js` 文件无条件加载,权限等同当前用户
1662
- - 修复:
1663
- - `schema.ts` 新增 `allowPlugins: z.boolean().default(false)`(默认关闭)
1664
- - `loadPlugins(pluginsDir, allowPlugins)` 新增 flag 参数;未启用时打印警告并返回,不加载任何插件
1665
- - 启用时在 stderr 打印完整插件路径作为安全日志
1666
- - `repl.ts` 传入 `this.config.get('allowPlugins')` 控制
1667
-
1668
- ### 修复:中危(M)
1669
-
1670
- **M4 — `web-fetch.ts`:SSRF(服务端请求伪造)**
1671
- - 漏洞:AI 可指定任意 URL,攻击者可让工具访问内网服务(如 192.168.x.xmetadata API 等)
1672
- - 修复:新增 `isPrivateHost()` 函数,拦截 localhost / 0.0.0.0 / ::1 / fe80:: / 10.x / 127.x / 172.16-31.x / 192.168.x / 169.254.x;对初始 URL 和重定向后的最终 URL 均校验
1673
-
1674
- **M5 — `types.ts`:PowerShell 写操作未分类为 `write`**(并入 H2 修复)
1675
-
1676
- **M6 — `read-file.ts`:大文件读取触发 OOM**
1677
- - 漏洞:`readFileSync` size 检查之前执行,超大文件直接撑爆堆内存
1678
- - 修复:用 `statSync` 在读取前获取文件大小,超过 10MB(`MAX_FILE_BYTES`)直接报错返回
1679
-
1680
- **M8 — `read-file.ts`:读取敏感配置文件(API Key、SSH 私钥等)无警告**
1681
- - 修复:`getSensitiveWarning()` 检测以下路径:`~/.aicli/config.json`、`.env*`、`~/.ssh/id_*`、`~/.aws/credentials`;匹配时在文件内容前追加醒目警告(不阻断,保留调试能力)
1682
-
1683
- ### 修复:低危(L)
1684
-
1685
- **L8 — `bash.ts`:`fixWindowsDeleteCommand` 路径注入**
1686
- - 漏洞:pathValue 直接插入 `cmd /c rmdir /s /q "..."` 字符串,含 `"` 的路径可逃逸引号
1687
- - 修复:`const safePath = pathValue.replace(/"/g, '\\"');`
1688
-
1689
- **L9 — `bash.ts`:timeout 由 AI 任意控制,无上限**
1690
- - 修复:`Math.min(Math.max(Number(args['timeout'] ?? 30_000), 1000), 300_000)`,最小 1s,最大 300s
1691
-
1692
- **L10 `bash.ts`:`updateCwdFromCommand` 正则误匹配字符串内的 `cd`**
1693
- - 修复:更新正则为 `/(?:^|[;&|])\s*cd\s+(['"]?)([^\s;&|'"]+)\1/g`,捕获组 2 取路径(去除引号),仅匹配命令边界处的 `cd`
1694
-
1695
- **L11 — `bash.ts`:`persistentCwd` 全局状态注释缺失**
1696
- - 修复:添加架构说明注释,提示 GUI 多 session 扩展时需重构为 per-session 状态
1697
-
1698
- ### 版本与发布
1699
- - `src/core/constants.ts`:VERSION `0.1.9` `0.1.10`
1700
- - `package.json`:version `0.1.9` `0.1.10`
1701
- - `npm publish` `jinzd-ai-cli@0.1.10`(https://www.npmjs.com/package/jinzd-ai-cli)
1702
-
1703
- ---
1704
-
1705
- ## 本轮开发完成记录(2026-02-23)
1706
-
1707
- ### 新增功能
1708
- - **Gemini 完整支持**:`GeminiProvider` 接入代理(undici ProxyAgent)、baseUrl 透传、array 参数 `items` schema 修复(解决 400 Bad Request)、429/网络错误中文提示
1709
- - **代理配置**:`config.json` 新增 `proxy` 字段;`src/index.ts` `setupProxy()` 读取配置+环境变量(优先级:env > config);设置向导新增 `Configure proxy` 入口(支持设置/修改/清除)
1710
- - **`stream_to_file` 工具上线**:注册到 `ToolRegistry`;`repl.ts` `executeAll` 前注入 provider/model/messages/systemPrompt 上下文;`getDangerLevel` 添加 `write` 级别
1711
- - **`/config` REPL 命令**:REPL 内直接调用配置向导,无需退出;`repl.ts` 的 `runSetupWizard` 回调临时恢复 readline 状态供 inquirer 使用
1712
- - **README.md**:创建完整 npm 包文档(Provider 表、工具表、命令表、代理配置、项目上下文说明)
1713
- - **pkg 打包零警告**:`scripts/patch-sqlite.mjs` 在打包前替换 undici 内部 `require("sqlite")` 为 IIFE stub
1714
- - **版本升至 0.1.4**,发布至 npmjs
1715
-
1716
- ### 架构变更
1717
- - `src/config/schema.ts`:新增 `proxy?: string` 字段
1718
- - `src/repl/commands/index.ts`:`CommandContext` 新增 `runSetupWizard` 回调;新增 `/config` 命令;工具/命令计数更新(11工具、15命令)
1719
- - `src/repl/setup-wizard.ts`:`runFull()` 新增代理配置选项;`input` from `@inquirer/prompts` 用于代理 URL 输入
1720
-
1721
- ## 本轮开发完成记录(2026-02-22)
1722
-
1723
- ### 新增功能
1724
- - **全局超时统一为 300s**:`~/.aicli/config.json` 中 `timeouts.deepseek` 60000→300000、`timeouts.kimi` 600000→300000;`modelParams` 中所有模型的 `timeout` 统一为 300000(moonshot-v1-8k 新增 timeout 字段)
1725
- - **跨 session 历史搜索 `/search <keyword>`**:`SessionManager.searchMessages()` 遍历全部历史 JSON,不区分大小写全文匹配,每个 session 最多返回 3 条带上下文的匹配片段,全局最多 20 个 session;REPL 新增 `/search` 命令,结果按 updated 倒序展示,提示用 `/session load <id>` 加载
1726
- - **`run_interactive` 参数容错可观测化**:`args` `stdin_lines` 传入错误类型(string array)时,双路输出警告:① `process.stderr.write`(终端可见);② 工具返回值前缀追加警告文本(AI 可在工具结果中看到),便于持续观察 AI 实际调用行为
1727
- - **`/about` 命令升级**:新增工具列表(10个完整)、REPL 命令列表(14个)、主要特性区块;移除仓库地址(待迁移至 GitHub)
1728
- - **版本号升级至 0.1.2**,`constants.ts` 与 `package.json` 保持同步
1729
- - **发布至 npmjs**:`npm publish` → `jinzd-ai-cli@0.1.2`(https://www.npmjs.com/package/jinzd-ai-cli)
1730
-
1731
- ## 本轮开发完成记录(2026-02-22)
1732
-
1733
- ### 新增功能
1734
- - **层级上下文文件系统**:三层级(全局/项目/子目录)上下文自动发现与拼接,取代旧的单文件加载。新增 `getGitRoot()` 获取 git 仓库根目录,`findContextFile()` 按候选列表查找,`loadHierarchicalContext()` 三层加载。`ContextLayer` 接口导出供命令模块使用。`/context` 命令显示各层详情,`/status` 显示层级摘要。
1735
- - **持久记忆系统**:新增 `save_memory` 工具(`src/tools/builtin/save-memory.ts`),追加式写入 `~/.aicli/memory.md`,自动带时间戳。`repl.ts` 新增 `loadMemoryContent()` 方法,在 `buildCurrentSystemPrompt()` 中注入 `# Persistent Memory` 段落。启动时显示 `📝 Memory loaded: X entries (Y chars)`。超过 10,000 字符时截取末尾最新部分。
1736
-
1737
- ### 架构变更
1738
- - `constants.ts`:新增 `CONTEXT_FILE_CANDIDATES`、`MEMORY_FILE_NAME`、`MEMORY_MAX_CHARS` 常量
1739
- - `git-context.ts`:新增 `getGitRoot()` 导出函数
1740
- - `repl.ts`:`contextFilePath: string | null` `contextLayers: ContextLayer[]`;`loadProjectContext()` `loadHierarchicalContext()`;`buildCurrentSystemPrompt()` 重构为 parts 数组拼接模式(日期时间 → 记忆 → 上下文)
1741
- - `commands/index.ts`:`CommandContext.getContextFilePath()` `getContextLayers()`;`/context` `/status` 命令适配新数据结构
1742
-
1743
- ## 历史开发记录(2026-02-21)
1744
-
1745
- ### 新增功能
1746
- - **模型上下文长度展示**:`/model` 选择器 hint 显示 `ctx:128K`;`/status` 新增 `Ctx Win` 行;启动欢迎界面 Model 行显示 `(ctx: 128K)`
1747
- - **Claude/Gemini 工具调用(Agentic)**:`ClaudeProvider` 和 `GeminiProvider` 均实现 `chatWithTools` + `buildToolResultMessages`,现在三大 provider 系列均支持完整 agentic 工具调用能力
1748
- - **工具调用最终回答流式输出**:`handleChatWithTools` 最终 content 轮次改为 `chatStream` 真正流式,`streaming=true` 时打字机效果;`streaming=false` 时保持原有非流式路径
1749
- - **Ctrl+C 取消工具确认**:`confirm()` 等待期间按 Ctrl+C 视为"按 N 取消",不再异常退出 REPL;通过 `ToolExecutor.cancelConfirm()` + SIGINT handler 协作实现
1750
- - **System prompt 注入当前日期时间**:每次会话启动自动注入 `当前日期时间:2026年02月21日 星期六 08:30:00`,所有模型均可感知;使用手动数字拼接(不依赖 locale API,兼容 pkg 精简 ICU 环境)
1751
- - **Kimi 长文本模型超时配置**:`~/.aicli/config.json` 中 `kimi-k2-0711-preview`、`kimi-k2-turbo-preview`、`moonshot-v1-128k` 统一设置 300000ms(5分钟)超时,适合出题等长文本生成场景
1752
-
1753
- ### 架构变更
1754
- - `repl.ts`:引入 `ToolCapableProvider` 接口(duck typing),`handleChatWithTools` 不再依赖 `OpenAICompatibleProvider` 具体类,Claude/Gemini 自动识别并走 agentic 路径
1755
- - `executor.ts`:新增 `cancelConfirmFn` 私有回调 + `cancelConfirm()` 公开方法
1756
-
1757
- ## 已解决的重大问题记录
1758
-
1759
- | 问题 | 根本原因 | 解决方案 |
1760
- |------|---------|---------|
1761
- | exe 启动后发消息立即崩溃(Segfault) | `ora` 在 `@yao-pkg/pkg` Node 22 exe 中调用底层 TTY 接口触发段错误 | 完全移除 `ora`,用原生 `setInterval + stdout.write` 实现 spinner |
1762
- | punycode 弃用警告 | Node 22 对 `punycode` 内置模块发出 DEP0040 警告 | pkg 打包时加 `--options no-deprecation` |
1763
- | `yy` 双字符回显 | `rl.question()` 内部 echo + 手动 echo 叠加 | 改用 `setRawMode(true)` + `data` 事件读单字符,彻底不用 `rl.question()` |
1764
- | REPL 一轮后退出 | `rl.question()` 内部调用 `rl.pause()`,readline 失去响应 | 移除 `rl.question()`,仅用 raw stdin |
1765
- | 用户输入重复出现 | readline `terminal:true` + spinner 输出触发行缓冲区重绘 | AI 处理期间设 `rlAny.output = null` 禁止 readline 输出 |
1766
- | Windows 中文乱码 | PowerShell 默认 GBK 编码 | bash 工具注入 `[Console]::OutputEncoding=UTF8` + `encoding:'buffer'` 手动解码 |
1767
- | run_interactive stdin 截断 | 通过 shell 包装 spawn 时 stdin pipe 被截断 | 直接 `spawn(pythonExe, args)` 不经过任何 shell |
1768
- | Ctrl+C 在工具确认时异常退出 | SIGINT handler 直接调用 `handleExit()`,未检查 `confirm()` 状态 | SIGINT handler 先检查 `confirming` 标志,是则 `cancelConfirm()` 取消而非退出 |
1769
- | 日期注入在 pkg exe 中显示错误 | `toLocaleDateString/toLocaleTimeString` 依赖完整 ICU,pkg 精简版不支持 | 改为手动数字拼接:`${year}年${month}月${day}日 ${weekday} ${HH}:${mm}:${ss}` |
1770
-
1771
- ---
1772
-
1773
- ## 代码审查报告(2026-03-01)
1774
-
1775
- > 全面扫描 src/ 目录 35+ 个 TypeScript 源文件,共发现 **4 高危 + 8 中危 + 7 低危** 问题。
1776
-
1777
- ### 🔴 高危问题(全部已修复 v0.1.33)
1778
-
1779
- **H1 — MCP pendingRequests 内存泄漏** ✅
1780
- - **文件**:`src/mcp/client.ts`
1781
- - **描述**:`withTimeout` 外层超时后,`sendRequest` 内部的 pending 条目和 timer 滞留到内部超时才释放
1782
- - **修复**:`sendRequest` resolve/reject 回调内含统一 `cleanup()` 函数,任何路径(正常响应/内部超时/写入错误/外部 reject)都即时释放资源
1783
-
1784
- **H2 — readline 竞态条件:`confirming` 标志失效** ✅
1785
- - **文件**:`src/tools/executor.ts`、`src/tools/builtin/ask-user.ts`
1786
- - **描述**:`confirm()`/`batchConfirm()`/`ask_user` `once('line')` 在极端 edge case 下可能二次触发
1787
- - **修复**:`completed` 布尔守卫统一移入 `cleanup()` 内部(单一检查点);`try/catch` 包裹 `rl.once()` 注册防止 readline 已关闭时 `confirming` 卡死
1788
-
1789
- **H3 — 工具执行错误语义混淆(isError 混用)** ✅
1790
- - **文件**:`src/tools/executor.ts`
1791
- - **描述**:用户取消和权限拒绝的 content 缺少语义前缀,AI 难以区分取消/拒绝/真实错误
1792
- - **修复**:所有取消/拒绝路径 content `[User cancelled]`/`[User rejected]`/`[Permission denied]` 前缀 + "Do not retry without asking" 提示
1793
-
1794
- **H4 MCP 断开后无恢复机制**
1795
- - **文件**:`src/mcp/client.ts` + `src/mcp/manager.ts` + `src/repl/commands/index.ts`
1796
- - **描述**:服务器子进程意外退出后,工具可见但调用时 silent failure,需重启 CLI
1797
- - **修复**:`McpClient.reconnect()` 清理旧状态后重新执行完整连接流程;`wrapMcpTool` execute 断线时自动尝试一次重连;`McpManager.reconnectServer()/reconnectAll()`;`/mcp reconnect [serverId]` 命令手动重连
1798
-
1799
- ### 🟠 中危问题(全部已修复或确认安全 v0.1.34)
1800
-
1801
- | # | 状态 | 文件 | 说明 |
1802
- |---|------|------|------|
1803
- | M5 | ✅ v0.1.34 | `bash.ts` | `persistentCwd` 指向已删除目录时自动回退 `process.cwd()` |
1804
- | M6 | ✅ 已修复 | `web-fetch.ts` | 手动 redirect loop 每一跳均做 SSRF 检查 |
1805
- | M7 | 已修复 | `run-interactive.ts` | `write()` 返回 false 时 `once('drain')` 背压处理 |
1806
- | M8 | ✅ 已修复 | `session-manager.ts` | `catch` 中 `stderr.write` 含文件名和错误信息 |
1807
- | M9 | ✅ H4 附带 | `mcp/client.ts` | `killProcess()` 已有 `removeAllListeners()` |
1808
- | M10 | ✅ 非问题 | `permissions.ts` | 使用 `.includes()` 子串匹配,非正则,无 ReDoS 风险 |
1809
- | M11 | ✅ 已修复 | `repl.ts` | `resolve()` + `startsWith(cwd)` 路径穿越校验 |
1810
- | M12 | ✅ v0.1.34 | `mcp/manager.ts` | `convertSchema()` 递归转换嵌套 schema;`schemaToJsonSchema()` 供三 provider 统一使用 |
1811
-
1812
- ### 🟡 低危问题(后续改进)
1813
-
1814
- | # | 文件 | 问题描述 |
1815
- |---|------|---------|
1816
- | L1 | `src/tools/builtin/run-tests.ts` | ✅ v0.1.50 已修复:`safeReadPackageJson()` 细粒度错误处理 + BOM + 框架自动检测 |
1817
- | L2 | `src/tools/builtin/web-fetch.ts` | 恶意 HTML 大量 script 标签时正则性能下降 |
1818
- | L3 | `src/session/session.ts` | `new Date(d.created)` 非法字符串返回 Invalid Date,比较失败 |
1819
- | L4 | `src/tools/builtin/ask-user.ts` / `google-search.ts` | 模块级全局上下文,多会话架构下会串扰 |
1820
- | L5 | `src/config/schema.ts` | `modelParams.timeout` 与 provider 级 `timeout` 优先级不清晰 |
1821
- | L6 | `src/tools/executor.ts` | `call.arguments['path']` 未验证是否绝对路径,null 传入可能异常 |
1822
- | L7 | `src/repl/repl.ts` | 主循环 `line` handler 是 async 但无整体 try/catch,未捕获异常可能停响应 |
1823
-
1824
- ---
1825
-
1826
- ## Claude Code 功能对比差距(2026-03-01)
1827
-
1828
- ### ai-cli 已超越或持平 Claude Code 的功能
1829
-
1830
- - 多 Provider 支持(Claude Code 仅支持 Claude 系列)
1831
- - 三层级上下文文件(Claude Code 为双层)
1832
- - Dev State 开发状态交接
1833
- - Agent Skills 系统
1834
- - 企业级权限规则 + Hooks 系统
1835
- - 主题/颜色自定义系统(dark/light/custom + 10 语义色槽,全部终端输出已迁移)
1836
- - 项目级 MCP 配置(`.mcp.json` + 全局合并 + 来源追踪)
1837
- - Extended Thinking 深度推理(`/think` 运行时切换 + thinking 块折叠)
1838
- - edit_file 智能提示(匹配失败 did you mean + `ignore_whitespace` 容错模式)
1839
-
1840
- ### 缺失功能路线图(新增)
1841
-
1842
- #### P0 核心竞争力缺口
1843
- - [x] **中断生成**(v0.1.30):`Escape` 键或 `Ctrl+C` 立即停止 AI 流式输出,不退出程序,显示 `[interrupted]` 后恢复提示符;已生成内容保留到 session
1844
- - [x] **多模态输入(图片)**(v0.1.30):`@image.png 描述这张图` 语法发送图片;Claude/Gemini/OpenAI 兼容格式自动转换;10MB 大小限制;`getVisionModelHint()` 正确识别 Claude/Gemini 原生支持视觉
1845
- - [x] **`/add-dir` 命令**(v0.1.35):运行时动态添加目录到上下文(目录树 + 文件内容,限 40K 字符)
1846
- - [x] **并行工具调用**(v0.1.35):safe 级别工具改为 `Promise.all()` 并行执行
1847
-
1848
- #### P1重要差距(已全部完成 v0.1.36,Vim 模式跳过)
1849
- - [x] **`/memory` 命令**(v0.1.35):查看/手动追加/清空 memory.md,`/memory show|add|clear|path`
1850
- - [x] **`/doctor` 健康检查**(v0.1.35):诊断 API Key 状态、配置文件、MCP 连接、context 占用率
1851
- - [x] **`/bug` 反馈命令**(v0.1.36):生成含系统信息的 Bug 报告模板,`--copy` 复制到剪贴板
1852
- - [ ] **Vim 编辑模式**:命令行输入支持 vim 键绑定(已跳过,优先级低)
1853
- - [x] **桌面通知**(v0.1.36):`src/repl/notify.ts` 跨平台通知(osascript/PowerShell/notify-send),耗时超阈值自动触发,`config.ui.notificationThreshold`(默认 10000ms)
1854
- - [x] **`--allowed-tools` / `--blocked-tools` 启动参数**(v0.1.35):启动时白名单/黑名单限制 AI 可用工具
1855
- - [x] **流式 JSON 输出**(v0.1.36):`--output-format streaming-json`,NDJSON 每 chunk 一行 `{"type":"delta","text":"..."}` + 最终 `{"type":"done",...}`
1856
-
1857
- #### P2 — 体验增强(v0.1.37 完成 4 项,v0.1.38 完成 Extended Thinking)
1858
- - [x] **项目级 `.mcp.json`**(v0.1.37):项目根目录 `.mcp.json` 自动发现,与全局合并(项目覆盖同名),`/mcp` 显示来源标签
1859
- - [x] **`--resume <id>` 启动参数**(v0.1.37):命令行直接恢复指定会话(前缀匹配),找不到时显示最近 5 个 session
1860
- - [x] **Word wrap 配置**(v0.1.37):`config.ui.wordWrap`,ANSI 转义码感知折行
1861
- - [x] **主题/颜色自定义**(v0.1.37):dark/light/custom 三主题 + 10 语义色槽 + Proxy 全局导出
1862
- - [ ] **IDE 集成**:VS Code / JetBrains 扩展(架构已准备就绪)
1863
- - [ ] **OAuth/浏览器登录**:无需手动填 API Key
1864
- - [x] **Extended Thinking**(v0.1.38):Claude 深度推理模式集成,`/think` 运行时切换,thinking 块折叠显示
1
+ # ai-cli 项目说明
2
+
3
+ ## 项目简介
4
+
5
+ 一个跨平台的 REPL 风格 AI 对话工具,支持多个主流 AI 提供商(OpenAI、Claude、Gemini、DeepSeek、智谱清言、Kimi),
6
+ 带有 **AI 工具调用(Agentic)** 能力,支持执行 bash 命令、读写文件、运行交互式程序、流式生成大文档。
7
+ 代理支持(`proxy` 配置字段 + 环境变量),Gemini 完整支持(2.5 Pro/Flash,array items schema 修复)。
8
+ **MCP 协议支持**:可接入外部 MCP 服务器,自动发现并注册工具,无缝融入 agentic 循环。
9
+ 设计上可扩展至 Electron/Tauri 桌面 GUI。
10
+
11
+ ## 技术栈
12
+
13
+ - **语言**: TypeScript (ESM,`"type": "module"`,导入须用 `.js` 扩展名)
14
+ - **运行时**: Node.js >= 20
15
+ - **构建工具**: tsup `dist/index.js`
16
+ - **包管理**: npm
17
+
18
+ ## 项目结构
19
+
20
+ ```
21
+ src/
22
+ ├── index.ts # CLI 入口点 (Commander bin 脚本)
23
+ ├── core/
24
+ │ ├── types.ts # 所有共享 TypeScript 接口(ChatRequest 含 timeout/_extraMessages)
25
+ │ ├── constants.ts # VERSION 等常量
26
+ │ ├── errors.ts # AiCliError → ProviderError / AuthError / RateLimitError 等
27
+ │ └── event-bus.ts # 类型安全的 EventEmitter(session.start/end, message.before/after)
28
+ ├── providers/
29
+ ├── base.ts # 抽象 BaseProvider(chat/chatStream/validateApiKey/listModels/initialize)
30
+ ├── registry.ts # ProviderRegistry(initialize 时注入 apiKey + baseUrl + timeout)
31
+ │ ├── openai-compatible.ts # DeepSeek/Zhipu/Kimi 共用基类;实现 chatWithTools + buildToolResultMessages
32
+ │ ├── claude.ts # Anthropic SDK provider
33
+ │ ├── gemini.ts # Google Generative AI provider(role: assistant model)
34
+ ├── openai.ts # OpenAI providerGPT-5.4/5/4.1/4o/o3/o4-mini
35
+ ├── deepseek.ts / zhipu.ts / kimi.ts # 继承 OpenAICompatibleProvider;deepseek/kimi 覆写 chatWithTools 实现虚假声明检测
36
+ ├── config/
37
+ │ ├── schema.ts # Zod schema(含 timeouts / customBaseUrls / defaultModels
38
+ │ ├── config-manager.ts # 读写 ~/.aicli/config.json,三层优先级:env > file > default
39
+ └── env-loader.ts # AICLI_API_KEY_* 等环境变量映射
40
+ ├── session/
41
+ │ ├── session.ts # Session(addMessage / clear / compact / fork / toJSON / fromJSON
42
+ └── session-manager.ts # CRUD + forkSession for ~/.aicli/history/*.json
43
+ ├── repl/
44
+ │ ├── repl.ts # REPL 循环(MAX_TOOL_ROUNDS=20,handleChatWithTools agentic loop
45
+ │ ├── renderer.ts # 终端输出(renderStream / renderResponse 均不加前置 \n)
46
+ ├── theme.ts # 集中式主题系统(dark/light/custom 主题 + 语义色槽 Proxy 导出)
47
+ ├── dev-state.ts # 开发状态交接(provider/model 切换时快照生成、save/load/clear)
48
+ │ ├── setup-wizard.ts # @inquirer/prompts 首次运行交互式设置
49
+ │ └── commands/
50
+ │ └── index.ts # CommandRegistry + 35个命令(/help /about /provider /model /clear /compact /plan /session /system /context /status /search /undo /export /copy /cost /init /skill /tools /plugins /mcp /config /checkpoint /review /commands /test /scaffold /add-dir /memory /doctor /bug /think /diff /fork /exit)
51
+ │ ├── custom-commands.ts # CustomCommandManager(~/.aicli/commands/*.md 用户自定义命令)
52
+ ├── skills/
53
+ │ ├── types.ts # Skill/SkillMeta 接口、parseSkillFile(YAML frontmatter 解析)
54
+ └── manager.ts # SkillManager(加载/激活/停用技能,工具白名单过滤)
55
+ ├── mcp/
56
+ ├── types.ts # MCP 协议类型定义(JSON-RPC、工具 schema、服务器配置)
57
+ ├── client.ts # McpClient(单个 MCP 服务器 STDIO 连接,JSON-RPC 通信)
58
+ └── manager.ts # McpManager(多服务器管理,工具发现与注册,MCP→Tool 转换)
59
+ └── tools/
60
+ ├── types.ts # ToolDefinition / ToolCall / ToolResult / DangerLevel / getDangerLevel / isFileWriteTool
61
+ ├── registry.ts # ToolRegistry(注册全部内置工具,共15个)
62
+ ├── hooks.ts # 工具执行钩子(runHook,shell 命令模板替换 + execSync)
63
+ ├── permissions.ts # 基于规则的工具权限控制(checkPermission,首匹配规则)
64
+ ├── executor.ts # ToolExecutor(确认逻辑 + 批量文件写入预览 + batchConfirm + hooks + permissions)
65
+ └── builtin/
66
+ ├── bash.ts # bash 工具(Windows: PowerShell + Buffer→UTF-8;Unix: $SHELL)
67
+ ├── read-file.ts # read_file 工具
68
+ ├── write-file.ts # write_file 工具
69
+ ├── list-dir.ts # list_dir 工具
70
+ ├── run-interactive.ts # run_interactive 工具(spawn 直连可执行文件,stdin_lines 依次输入)
71
+ ├── ask-user.ts # ask_user 工具(agentic 循环中向用户提问,等待文本回答)
72
+ ├── write-todos.ts # write_todos 工具(任务拆解与进度跟踪,终端实时渲染)
73
+ ├── google-search.ts # google_search 工具(Google Custom Search API 搜索网页)
74
+ ├── spawn-agent.ts # spawn_agent 工具(独立子代理 agentic 循环 + SubAgentExecutor)
75
+ └── run-tests.ts # run_tests 工具(自动检测项目类型、运行测试、JUnit XML 解析、结构化报告)
76
+ ```
77
+
78
+ ## 常用命令
79
+
80
+ ```bash
81
+ npm run dev # 开发模式(tsx 热重载)
82
+ npm run build # 构建到 dist/index.js(ESM)和 dist-cjs/index.cjs(CJS
83
+ node dist/index.js # 运行
84
+
85
+ # 子命令
86
+ node dist/index.js providers # 列出所有 provider 及配置状态
87
+ node dist/index.js sessions # 列出最近会话
88
+ node dist/index.js config # 运行配置向导
89
+ ```
90
+
91
+ ## 打包为独立可执行文件
92
+
93
+ 使用 `@yao-pkg/pkg` 将项目打包为无需 Node.js 环境即可运行的单文件可执行程序(约 56MB)。
94
+
95
+ ```bash
96
+ npm run pack:win # Windows x64 → release/ai-cli-win.exe
97
+ npm run pack:mac # macOS arm64 → release/ai-cli-mac
98
+ npm run pack:mac-x64 # macOS x64 → release/ai-cli-mac-x64
99
+ npm run pack:linux # Linux x64 → release/ai-cli-linux
100
+ npm run pack:all # 同时打包所有平台
101
+ ```
102
+
103
+ ### 打包原理
104
+
105
+ - tsup 输出两套产物:
106
+ - `dist/index.js`(ESM,供 `node` 直接运行)
107
+ - `dist-cjs/index.cjs`(CJS + `noExternal: /.*/` 内联所有依赖,供 pkg 打包)
108
+ - pkg 使用 `--options no-deprecation` 内嵌到二进制,消除 punycode 弃用警告
109
+ - macOS/Linux 产物需在对应平台执行 `chmod +x` 后运行
110
+
111
+ ### 关键兼容性问题(已解决)
112
+
113
+ **ora 在 pkg exe 中 Segfault**:`ora` spinner 在 `@yao-pkg/pkg` 打包的 Node 22 exe 里会触发段错误(exit code 139)。
114
+ **解决方案**:完全移除 `ora` 依赖,在 `src/repl/renderer.ts` 中用原生 `setInterval` + `process.stdout.write('\r\x1b[2K')` 实现轻量 spinner,非 TTY 环境自动降级为空操作。
115
+
116
+ ## 配置存储
117
+
118
+ - **配置文件**: `~/.aicli/config.json`
119
+ - **全局上下文**: `~/.aicli/AICLI.md`(或 `CLAUDE.md`,全局层级上下文)
120
+ - **持久记忆**: `~/.aicli/memory.md`(AI 通过 `save_memory` 工具写入,跨会话自动加载)
121
+ - **开发状态快照**: `~/.aicli/dev-state.md`(provider/model 切换时由前一 AI 自动生成,新模型启动后注入 system prompt)
122
+ - **对话历史**: `~/.aicli/history/*.json`
123
+ - **插件目录**: `~/.aicli/plugins/`(暂未实现)
124
+
125
+ 配置示例:
126
+ ```json
127
+ {
128
+ "defaultProvider": "deepseek",
129
+ "apiKeys": { "deepseek": "sk-..." },
130
+ "customBaseUrls": { "deepseek": "https://api.deepseek.com" },
131
+ "timeouts": { "deepseek": 60000 }
132
+ }
133
+ ```
134
+
135
+ ### MCP 服务器配置
136
+
137
+ `config.json` 中声明 `mcpServers` 字段,启动时自动连接、发现工具并注册。格式兼容 Claude Desktop。
138
+
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "filesystem": {
143
+ "command": "npx",
144
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "D:/projects"],
145
+ "timeout": 30000
146
+ },
147
+ "github": {
148
+ "command": "node",
149
+ "args": ["path/to/github-server.js"],
150
+ "env": { "GITHUB_TOKEN": "ghp_xxx" }
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ MCP 工具名格式:`mcp__<serverId>__<toolName>`,如 `mcp__filesystem__read_file`。
157
+ 所有 MCP 工具默认 `safe` 级别(用户主动配置即表示信任)。
158
+
159
+ ## 环境变量(优先级高于配置文件)
160
+
161
+ ```
162
+ AICLI_API_KEY_OPENAI OpenAI API Key
163
+ AICLI_API_KEY_CLAUDE Claude API Key
164
+ AICLI_API_KEY_GEMINI Gemini API Key
165
+ AICLI_API_KEY_DEEPSEEK DeepSeek API Key
166
+ AICLI_API_KEY_ZHIPU 智谱 API Key
167
+ AICLI_API_KEY_KIMI Kimi API Key
168
+ AICLI_API_KEY_GOOGLESEARCH Google Custom Search API Key
169
+ AICLI_GOOGLE_CX Google Search Engine ID (cx)
170
+ AICLI_PROVIDER 默认 Provider ID
171
+ AICLI_NO_STREAM 设为 1 禁用流式输出
172
+ ```
173
+
174
+ ## 层级上下文文件系统
175
+
176
+ 类似 Gemini CLI `GEMINI.md` 层级机制,ai-cli 支持三层级上下文文件自动发现与拼接:
177
+
178
+ ### 层级结构(按顺序拼接注入 system prompt)
179
+
180
+ | 层级 | 路径 | 用途 |
181
+ |------|------|------|
182
+ | 全局层 | `~/.aicli/AICLI.md` (或 `CLAUDE.md`) | 所有项目通用的个人偏好 |
183
+ | 项目层 | `<git-root>/AICLI.md` (或 `CLAUDE.md`) | 项目级规则(提交到 git 供团队共享) |
184
+ | 子目录层 | `<cwd>/AICLI.md` (或 `CLAUDE.md`) | 当前工作子目录的特定指令 |
185
+
186
+ ### 规则
187
+
188
+ - 每层按候选文件名 `AICLI.md CLAUDE.md` 优先级查找,找到第一个即停止
189
+ - 项目层通过 `git rev-parse --show-toplevel` 确定 git 仓库根目录;不在 git 仓库中时 projectRoot = cwd
190
+ - 子目录层仅当 cwd projectRoot 时才加载(避免重复)
191
+ - 所有层级内容用 `\n\n---\n\n` 分隔后注入 system prompt
192
+ - 配置 `contextFile: false` 可禁用所有层级;设为具体文件名可回退到单文件行为
193
+
194
+ ### 相关命令
195
+
196
+ - `/context` 查看当前加载的各层级详情
197
+ - `/context reload` 重新加载上下文文件
198
+
199
+ ## Tool Use(Agentic 工具调用)架构
200
+
201
+ ### 工具一览
202
+
203
+ | 工具名 | 危险级别 | 说明 |
204
+ |---|---|---|
205
+ | `bash` | write/safe/destructive(按命令判断) | PowerShell(Win) / $SHELL(Unix) 执行命令,Windows 强制 UTF-8 |
206
+ | `read_file` | safe | 读取文件内容 |
207
+ | `write_file` | write(需确认) | 写入文件 |
208
+ | `edit_file` | write(需确认) | 精确字符串替换编辑文件 |
209
+ | `list_dir` | safe | 列出目录内容 |
210
+ | `grep_files` | safe | 正则搜索文件内容 |
211
+ | `glob_files` | safe | glob 模式匹配文件路径 |
212
+ | `run_interactive` | safe | spawn 直连可执行文件 + stdin_lines 依次输入,用于交互式程序(猜数游戏等) |
213
+ | `web_fetch` | safe | 抓取网页内容并转为 Markdown(含私有 IP SSRF) |
214
+ | `save_last_response` | write(需确认) | 保存上一次 AI 回答到文件(tee 流式写盘) |
215
+ | `save_memory` | safe | 将重要信息追加到 `~/.aicli/memory.md`,跨会话持久化,启动时自动注入 system prompt |
216
+ | `ask_user` | safe | AI 在 agentic 循环中向用户提问,等待文本回答后继续执行 |
217
+ | `write_todos` | safe | AI 拆解复杂任务为子任务列表,终端实时渲染进度(pending/in_progress/completed) |
218
+ | `google_search` | safe | Google Custom Search API 搜索网页,需配置 API Key + Search Engine ID (cx) |
219
+ | `spawn_agent` | safe | 委派独立子代理执行特定任务(隔离对话 + agentic 循环,write 自动确认,destructive 阻止) |
220
+ | `run_tests` | safe | 运行项目测试(自动检测 Maven/Gradle/npm/pytest/cargo/go),JUnit XML 解析,结构化报告 |
221
+ | `mcp__*` | safe | MCP 服务器暴露的动态工具(命名格式:`mcp__<serverId>__<toolName>`) |
222
+
223
+ ### 危险级别与确认机制
224
+
225
+ `src/tools/types.ts` `getDangerLevel()` 判断:
226
+ - `safe` 自动执行,无需确认
227
+ - `write` 显示 `✎ Write operation:` 并等待用户按 `y/N`
228
+ - `destructive` 显示 `⚠ DESTRUCTIVE operation:` 并等待确认
229
+
230
+ ### Agentic 循环(`repl.ts` `handleChatWithTools`)
231
+
232
+ ```
233
+ 用户输入 → rl.pause() + rl.output=null(静默 readline)
234
+ chatWithTools(messages + toolDefs) → AI 返回 { toolCalls } 或 { content }
235
+ toolCalls:executor.executeAll() buildToolResultMessages() → 下一轮(最多 20 轮)
236
+ content:renderResponse() rl.output=savedOutput + rl.resume() + showPrompt()
237
+ ```
238
+
239
+ ### stdin/readline 关键设计(重要!勿破坏)
240
+
241
+ 1. **`rl.output = null`**:在 `line` handler 开始时执行,处理完毕后恢复。
242
+ 防止 readline AI 处理期间因 spinner/输出 触发行缓冲区重绘,导致用户输入被重复打印。
243
+
244
+ 2. **`confirm()` 读取单键**:使用 `process.stdin.setRawMode(true)` + `data` 事件读取单个字符。
245
+ **禁止**:调用 `rl.question()`(会 pause readline,REPL 随后失去响应);
246
+ **禁止**:额外调用 `stdin.resume()`(会产生残留字节 `yy`);
247
+ **禁止**:在 executor 内操控 `rl.output`(由 repl.ts 统一管理)。
248
+
249
+ 3. **`rl.prompt()` 而非 `stdout.write`**:readline 内部追踪 prompt 列宽,backspace 不会吃掉 prompt。
250
+
251
+ 4. **`renderer.ts` `renderStream/renderResponse` 不加前置 `\n`**:
252
+ 用户按回车后 readline 已换行,spinner stop 后也已换行,再加 `\n` 会触发 readline 重绘。
253
+
254
+ 5. **`processing` 标志防止 `rl.on('close')` 误触发退出**:
255
+ `line` handler 是 async 的,readline 不等待它完成。若 AI 处理期间 stdin 意外关闭(如 spinner 操作),
256
+ `close` 事件会提前 resolve Promise 导致 REPL 退出。用 `processing` 布尔标志,在 `close` handler 中判断,
257
+ 仅在非处理中时才 resolve。
258
+
259
+ 6. **全局异常捕获**(`src/index.ts`):`uncaughtException` + `unhandledRejection` 均打印完整堆栈到 stderr,
260
+ 防止未知错误导致静默崩溃。ESM 中这两个 handler 必须写在文件顶部(import 语句之前)才能在模块加载期生效。
261
+
262
+ ### Windows 编码处理
263
+
264
+ - **bash 工具**:命令前注入 `[Console]::OutputEncoding = UTF8`;`execSync` `encoding:'buffer'`,手动 `.toString('utf-8')` 解码。
265
+ - **run_interactive 工具**:直接 `spawn(pythonExe, args)`,不经过任何 shell;环境变量 `PYTHONUTF8=1` + `PYTHONIOENCODING=utf-8`,stdout `setEncoding('utf-8')`。
266
+ - **关键**:不要用 `cmd /c` 或 `powershell -Command` 包装 spawn——这会截断 stdin 管道,交互式程序收不到输入。
267
+
268
+ ### run_interactive 工具参数容错设计
269
+
270
+ ```typescript
271
+ // args 可能是数组(正确)或字符串(AI 传参错误时降级兼容)
272
+ const cmdArgs = Array.isArray(rawArgs) ? rawArgs.map(String) : [rawArgs.trim()];
273
+ // stdin_lines 可能是数组或逗号分隔字符串
274
+ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
275
+ : rawStdin.split(',').map(s => s.trim());
276
+ ```
277
+
278
+ ## 添加新 Provider
279
+
280
+ 1. `src/providers/` 创建新文件(如 `openrouter.ts`)
281
+ 2. 继承 `OpenAICompatibleProvider`(OpenAI 兼容)或 `BaseProvider`
282
+ 3. 实现 `info` 属性(id, displayName, defaultModel, models 数组)和 `defaultBaseUrl`
283
+ 4. `src/providers/registry.ts` `BUILT_IN_PROVIDERS` 数组中添加该类
284
+
285
+ ## 添加新 Tool
286
+
287
+ 1. `src/tools/builtin/` 创建新文件,实现 `Tool` 接口(`definition` + `execute(args)`)
288
+ 2. 在 `src/tools/registry.ts` 的 `constructor` 中 `this.register(newTool)`
289
+ 3. 在 `src/tools/types.ts` `getDangerLevel()` 中为新工具设置危险级别
290
+
291
+ ## GUI 扩展(Electron/Tauri)
292
+
293
+ - `src/core/`、`src/providers/`、`src/session/`、`src/config/`、`src/tools/` 无终端依赖,可直接被 Electron 主进程导入
294
+ - `src/repl/` 仅是 CLI 层,GUI 时完全替换此目录
295
+ - `EventBus` 作为 Electron IPC 桥接
296
+ - `package.json` 中的 `exports` 字段暴露核心 API 供第三方使用
297
+
298
+ ## 代码风格
299
+
300
+ - TypeScript strict 模式
301
+ - 所有文件使用 `.js` 扩展名导入(ESM 要求)
302
+ - Provider 错误统一转换为 `ProviderError` 子类
303
+ - 不在核心层(`src/core/`)引入任何 CLI/终端相关依赖
304
+
305
+ ## Gemini CLI 追赶路线(进行中)
306
+
307
+ 对标 Gemini CLI 的功能差距,按优先级逐步实现:
308
+
309
+ ### 已完成
310
+ - [x] **层级上下文文件系统**(2026-02-22):全局 `~/.aicli/AICLI.md` + 项目 `<git-root>/AICLI.md` + 子目录 `<cwd>/AICLI.md` 三层级自动发现、拼接注入 system prompt。`/context` 命令显示各层详情。
311
+ - [x] **持久记忆系统 `save_memory`**(2026-02-22):AI 可调用 `save_memory` 工具将重要信息追加到 `~/.aicli/memory.md`,跨会话自动加载注入 system prompt。支持时间戳、大小限制(10K 字符截取最新)。
312
+ - [x] **开发状态交接系统**(2026-02-22):`/provider` 和 `/model` 切换时自动调用当前 AI 生成结构化开发状态快照(7 段式:当前任务/已完成/进行中/关键决策/修改文件/下一步/备注),保存到 `~/.aicli/dev-state.md`,新模型启动时自动注入 system prompt 实现无缝交接。同值选择不触发任何变更。`/clear` 和 `/session new` 清除快照。
313
+ - [x] **`ask_user` 工具**(2026-02-23):AI 在 agentic 循环中可调用 `ask_user` 暂停执行、向用户显示问题、等待文本输入后继续。复用 `confirm()` 的 readline 模式(output 保存/恢复、resume/pause、once('line')),支持 Ctrl+C 取消。主循环 line handler 增加 `askUserContext.prompting` 守卫。
314
+ - [x] **`write_todos` 工具**(2026-02-23):AI 拆解复杂任务为子任务列表,终端实时渲染进度。参数采用 JSON 字符串(因 ToolParameterSchema 不支持嵌套对象数组),容错处理 AI 直接传数组。`execute()` 内直接 `console.log()` 渲染(绕过 executor 8 行截断),模块级变量保持会话内状态。
315
+ - [x] **Google 搜索集成**(2026-02-23):`google_search` 工具通过 Google Custom Search JSON API 搜索网页。需配置 API Key(`apiKeys['google-search']` 或 `AICLI_API_KEY_GOOGLESEARCH`)和 Search Engine ID(`googleSearchEngineId` 或 `AICLI_GOOGLE_CX`)。自动走全局 proxy,15s 超时,返回 Markdown 格式结果列表。配置向导新增 `Configure Google Search` 入口。
316
+
317
+ - [x] **MCP 协议支持**(2026-02-23):轻量级 MCP 客户端(不依赖 SDK),STDIO 传输 + JSON-RPC 2.0 通信。`src/mcp/` 模块:`types.ts`(协议类型)、`client.ts`(单服务器连接)、`manager.ts`(多服务器管理 + Tool 转换)。配置兼容 Claude Desktop(`config.json` 的 `mcpServers` 字段)。启动自动连接、发现工具并注册到 ToolRegistry,工具名格式 `mcp__<serverId>__<toolName>`。新增 `/mcp` REPL 命令查看连接状态和工具列表。
318
+ - [x] **Kimi XML 伪工具调用修复**(2026-02-25):Kimi-k2 在生成长内容时退化为文本 XML 伪工具调用。`kimi.ts` 覆写 `chatWithTools()`:方案 B 在 system prompt 注入强制规范提示(预防),方案 A 解析文本中的 XML 工具标签并转换为真实 ToolCall 执行(兜底)。`parseXmlToolCalls()` 使用 indexOf 避免正则回溯,快速路径优化。
319
+
320
+ ### P0 核心竞争力缺口(下一步最先实现)
321
+ - [x] **`/compact` 上下文压缩**(2026-02-25):调用当前 provider 生成对话摘要,替换 session.messages N 条为 user/assistant 摘要对,保留最近 4 条。`session.compact()` 方法 + `repl.compactSession()` + `/compact [instruction]` 命令。
322
+ - [x] **Headless 非交互模式(`-p` flag)**(2026-02-25):`echo "..." | aicli -p "review"` 单轮对话后退出,`--json` 输出 `{content,provider,model,usage}`,支持 stdin 内容拼接,解锁 CI/CD 脚本场景。
323
+ - [x] **Plan Mode 规划模式**(2026-02-25):`/plan` 进入只读规划(AI 只能用只读工具白名单),提示符显示 `[PLAN]` 黄色标记,`/plan execute` 切回正常模式,`/plan exit` 放弃计划。
324
+ - [x] **Sub-agents 系统**(2026-02-27):`spawn_agent` 工具在同进程中运行独立 agentic 循环。子代理拥有隔离对话、过滤后的工具集(SUBAGENT_ALLOWED_TOOLS)、自动确认 write 操作、阻止 destructive 操作。SubAgentExecutor 带前缀终端输出。
325
+
326
+ ### P1 重要差距(已全部完成 2026-02-28)
327
+ - [x] **Agent Skills 系统**(2026-02-28):`~/.aicli/skills/*.md` 可复用技能包,YAML frontmatter 声明 name/description/tools 白名单。`/skill list|<name>|off|reload` 命令。激活时注入 system prompt + 过滤工具集(Plan Mode 优先)。
328
+ - [x] **`/init` 项目初始化**(2026-02-28):扫描项目类型(Node/Rust/Python/Go/Java 等)+ 目录结构树,调用当前 AI 生成结构化 AICLI.md。已存在时需 `--force` 覆盖。
329
+ - [x] **`/copy` 剪贴板支持**(2026-02-28):跨平台复制最后 AI 回答到系统剪贴板(Windows: clip / macOS: pbcopy / Linux: xclip 或 xsel),无外部 npm 依赖。
330
+ - [x] **多文件编辑预览**(2026-02-28):`executeAll()` 重构为三组分流(safe→fileWrite→other)。文件写入 2+ 个时走 `executeBatchFileWrites()` 批量 diff 预览 + `batchConfirm()`(`[a]pprove all / [r]eject all / [1,3,5]` 选择性 approve)。
331
+ - [x] **Token 用量统计 `/cost`**(2026-02-28):显示 session 累计 input/output/total tokens + provider/model/消息数。`/cost reset` 重置计数器。
332
+
333
+ ### P2 增强功能
334
+ - [x] **Hooks 系统**(2026-02-28):pre/post tool execution shell 命令钩子(`config.hooks`),模板变量 `{tool}/{dangerLevel}/{args}/{status}`
335
+ - [x] **Permission Rules**(2026-02-28):基于规则的工具权限控制(`config.permissionRules`),auto-approve/deny/confirm,首匹配规则
336
+ - [x] **Checkpointing**(2026-02-28):`/checkpoint save/restore/list/delete`,检查点元数据随 session JSON 持久化
337
+ - [x] **`/review` 代码审查**(2026-02-28):读取 git diff + 上下文,AI 生成结构化审查意见(`--staged`/`--detailed`)
338
+ - [x] **Custom Commands**(2026-02-28):`~/.aicli/commands/*.md` 用户自定义命令,YAML frontmatter + `{{input}}/{{git-diff}}/{{git-context}}/{{file:path}}` 模板变量
339
+ - [x] **项目级 `.mcp.json`**(2026-03-05):项目根目录 `.mcp.json` 自动发现,与全局 `config.json` `mcpServers` 合并(项目覆盖同名),`/mcp` 显示 `[global]`/`[project]` 来源标签
340
+ - [x] **`--resume <id>` 启动参数**(2026-03-05):命令行直接恢复指定会话(前缀匹配),找不到时显示最近 5 session 提示
341
+ - [x] **Word wrap 配置**(2026-03-05):`config.ui.wordWrap`,0=自动(终端宽度),>0=固定列宽。ANSI 转义码感知折行
342
+ - [x] **主题/颜色自定义**(2026-03-05):`src/repl/theme.ts` 集中式主题系统,dark/light/custom 三主题 + 10 语义色槽 + Proxy 全局导出
343
+
344
+ ## 已知待改进项
345
+
346
+ ### 低优先级
347
+ - [x] **macOS/Linux 完整测试**(2026-03-01):已在 Linux 环境完成测试,基本无问题。跨平台逻辑(`$SHELL` fallback、UTF-8 env vars)验证通过。
348
+ - [x] **Token 用量显示**:已通过 `/cost` 命令实现(v0.1.23),显示 session 累计 input/output/total tokens。
349
+ - [ ] **GitHub 仓库迁移**:当前在 gitee,npm 包受众需要 GitHub 托管。
350
+ - [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
351
+ - [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
352
+
353
+ ## 本轮开发完成记录(2026-03-10,v0.1.57 → v0.1.58)
354
+
355
+ ### 代码质量修复:19 个低危问题修复 + 代码重构
356
+
357
+ **审查完结篇**:继 v0.1.56 修复高危、v0.1.57 修复中危后,本轮修复全部低危问题,代码审查报告彻底清零。
358
+
359
+ | # | 文件 | 修复内容 |
360
+ |---|------|---------|
361
+ | L1 | `src/tools/truncate.ts` (新) | 提取 `truncateOutput` 为共享模块,消除 executor.ts 与 spawn-agent.ts 代码重复 |
362
+ | L2 | `src/tools/executor.ts` | 改为从共享 `truncate.ts` 导入 |
363
+ | L3 | `src/tools/builtin/spawn-agent.ts` | 15+ 处 chalk→theme 迁移(header/footer/BLOCKED/toolCall/toolResult),移除 chalk 依赖 |
364
+ | L4 | `src/tools/builtin/run-interactive.ts` | timeout 参数限制 1s–300s(与 bash 工具一致) |
365
+ | L5 | `src/tools/builtin/grep-files.ts` | `statSync` 预检文件大小再读取,避免 OOM |
366
+ | L6 | `src/tools/builtin/read-file.ts` | `buf.slice` → `buf.subarray`(消除 Node.js 弃用警告) |
367
+ | L7 | `src/tools/undo-stack.ts` | 提取 `pushEntry()` 统一入栈方法,消除 push/pushNewFile/pushNewDir 三处溢出检查重复 |
368
+ | L8 | `src/tools/builtin/save-memory.ts` | `statSync` 获取文件大小,避免追加后重读整个文件 |
369
+ | L9 | `src/tools/builtin/list-dir.ts` | 不再隐藏 .github/.vscode/.gitignore/.env* 等有用的点文件/目录 |
370
+ | L10 | `src/repl/custom-commands.ts` | 复用 `skills/types.ts` `parseSimpleYaml`,消除函数重复 |
371
+ | L11 | `src/skills/types.ts` | `parseSimpleYaml` 导出为 public + 添加引号剥离 |
372
+ | L12 | `src/tools/git-context.ts` | 移除 `2>/dev/null`(Windows 不兼容,`stdio:pipe` 已捕获 stderr) |
373
+ | L13 | `src/repl/notify.ts` | Windows PowerShell 反引号转义修复 |
374
+ | L14 | `src/tools/hooks.ts` | 模板变量 shell 转义(`shellEscape` 单引号包裹),防止命令注入 |
375
+ | L15 | `src/mcp/client.ts` | 移除 `rejectAllPending` 中冗余的 `clearTimeout`(reject 回调已包含) |
376
+ | L16 | `src/config/config-manager.ts` | 拆分 `toJSON()`→返回对象 + `toFormattedJSON()`→返回字符串,修复 `/config show` API Key 掩码失效 |
377
+ | L17 | `src/tools/builtin/write-file.ts` | append 模式也记录 undo 条目(可撤销追加操作) |
378
+ | L18 | `src/providers/gemini.ts` | 保留空白文本 parts(仅跳过空字符串),避免丢失有意义的空白 |
379
+ | L19 | `src/repl/clipboard.ts` | PowerShell 单引号字符串中的路径转义修复 |
380
+ | L20 | `src/tools/diff-utils.ts` | DP 表内存限制文档化(~2MB 上限) |
381
+ | L21 | `src/tools/permissions.ts` | `pathPattern` 子串匹配行为文档化 |
382
+
383
+ ### 变更文件汇总
384
+
385
+ | 文件 | 变更类型 | 说明 |
386
+ |------|---------|------|
387
+ | `src/tools/truncate.ts` | 新增 | 共享 truncateOutput 函数 |
388
+ | `src/tools/executor.ts` | 修改 | 导入共享 truncate.ts |
389
+ | `src/tools/builtin/spawn-agent.ts` | 修改 | chalk→theme 全量迁移 + 共享 truncate |
390
+ | `src/tools/builtin/run-interactive.ts` | 修改 | timeout 限制 |
391
+ | `src/tools/builtin/grep-files.ts` | 修改 | statSync 预检 |
392
+ | `src/tools/builtin/read-file.ts` | 修改 | buf.subarray |
393
+ | `src/tools/undo-stack.ts` | 修改 | pushEntry DRY |
394
+ | `src/tools/builtin/save-memory.ts` | 修改 | statSync 替代 readFile |
395
+ | `src/tools/builtin/list-dir.ts` | 修改 | 有用点文件可见 |
396
+ | `src/repl/custom-commands.ts` | 修改 | 复用 parseSimpleYaml |
397
+ | `src/skills/types.ts` | 修改 | 导出 parseSimpleYaml |
398
+ | `src/tools/git-context.ts` | 修改 | 移除 2>/dev/null |
399
+ | `src/repl/notify.ts` | 修改 | PS 转义修复 |
400
+ | `src/tools/hooks.ts` | 修改 | shell 转义 |
401
+ | `src/mcp/client.ts` | 修改 | 冗余 clearTimeout |
402
+ | `src/config/config-manager.ts` | 修改 | toJSON 拆分 |
403
+ | `src/repl/commands/index.ts` | 修改 | 适配 toJSON 返回类型 |
404
+ | `src/tools/builtin/write-file.ts` | 修改 | append undo |
405
+ | `src/providers/gemini.ts` | 修改 | 空白 text parts |
406
+ | `src/repl/clipboard.ts` | 修改 | 路径转义 |
407
+ | `src/tools/diff-utils.ts` | 修改 | 文档化 |
408
+ | `src/tools/permissions.ts` | 修改 | 文档化 |
409
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.57 → 0.1.58 |
410
+ | `package.json` | 修改 | version 0.1.57 → 0.1.58 |
411
+
412
+ ### 版本与收尾
413
+ - `src/core/constants.ts`:VERSION `0.1.57` `0.1.58`
414
+ - `package.json`:version 同步
415
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
416
+
417
+ ### 代码审查完结状态(v0.1.35+ 增量)
418
+ - **高危 H1–H6**:全部已修复(v0.1.56)
419
+ - **中危 M1–M16**:全部已修复(v0.1.57
420
+ - **低危 L1–L21**:全部已修复(v0.1.58)
421
+ - **安全债务清零**
422
+
423
+ ---
424
+
425
+ ## 本轮开发完成记录(2026-03-10,v0.1.56 → v0.1.57)
426
+
427
+ ### 代码质量修复:16 个中危问题全部修复
428
+
429
+ **审查续篇**:继 v0.1.56 修复 6 个高危后,本轮修复全部 16 个中危问题。
430
+
431
+ | # | 文件 | 修复内容 |
432
+ |---|------|---------|
433
+ | M1 | `commands/index.ts` | `/config set` 原型污染防护(禁止 `__proto__`/`constructor`/`prototype` 路径) |
434
+ | M2 | `commands/index.ts` | `/config show` API Key 掩码(显示 `sk-d***b0` 格式) |
435
+ | M3 | `repl.ts` | `FREE_ROUND_TOOLS` 连续免费轮次上限(MAX_CONSECUTIVE_FREE_ROUNDS=5),防无限循环 |
436
+ | M4 | `openai-compatible.ts` | 截断 JSON 修复时 stderr 警告(提示参数可能丢失) |
437
+ | M5 | `claude.ts` | `chatWithToolsStream` done 事件兜底(`doneEmitted` 标志 + 流结束后补发) |
438
+ | M6 | `claude.ts` | base64 图片 URL 跳过时 stderr 警告 |
439
+ | M7 | `claude.ts` | AbortError/TimeoutError 不再被 `wrapError` 包装为 ProviderError(正确冒泡) |
440
+ | M8 | `openai-compatible.ts` | HALLUCINATION_PATTERNS 收紧:要求文件扩展名或路径引号上下文,减少误报 |
441
+ | M9 | `openai-compatible.ts` | 非流式降级路径保留 `reasoningContent`(DeepSeek-Reasoner 推理内容) |
442
+ | M10 | `deepseek.ts` + `kimi.ts` | Plan C 重试后二次校验:仍虚假声明时 stderr 警告用户手动检查 |
443
+ | M11 | `renderer.ts` | `printTable` ANSI 感知列对齐(用 `stripAnsi` 计算可见宽度) |
444
+ | M12 | `renderer.ts` | `wrapText` ANSI 样式跨行延续(折行后重发活跃样式序列,行末发 reset) |
445
+ | M13 | `theme.ts` | `resolveColor` 无效颜色名 stderr 警告(不再静默回退) |
446
+ | M14 | `session.ts` | `fork()` 深拷贝 multimodal 消息(嵌套 content 数组不再共享引用) |
447
+ | M15 | `bash.ts` | Undo 追踪限制文档化(仅 CWD 子级、仅常见创建命令、不追踪管道/脚本产生) |
448
+ | M16 | `edit-file.ts` | `findSimilarLines` 500KB 文件大小保护(跳过大文件避免性能问题) |
449
+
450
+ ### 变更文件汇总
451
+
452
+ | 文件 | 变更类型 | 说明 |
453
+ |------|---------|------|
454
+ | `src/repl/commands/index.ts` | 修改 | M1 原型污染 + M2 API Key 掩码 |
455
+ | `src/repl/repl.ts` | 修改 | M3 连续免费轮次上限 |
456
+ | `src/providers/openai-compatible.ts` | 修改 | M4 JSON 修复警告 + M8 模式收紧 + M9 reasoningContent |
457
+ | `src/providers/claude.ts` | 修改 | M5 done 兜底 + M6 图片警告 + M7 AbortError |
458
+ | `src/providers/deepseek.ts` | 修改 | M10 重试二次校验 |
459
+ | `src/providers/kimi.ts` | 修改 | M10 重试二次校验 |
460
+ | `src/repl/renderer.ts` | 修改 | M11 表格对齐 + M12 折行样式 |
461
+ | `src/repl/theme.ts` | 修改 | M13 无效颜色警告 |
462
+ | `src/session/session.ts` | 修改 | M14 fork 深拷贝 |
463
+ | `src/tools/builtin/bash.ts` | 修改 | M15 限制文档 |
464
+ | `src/tools/builtin/edit-file.ts` | 修改 | M16 大文件保护 |
465
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.56 0.1.57 |
466
+ | `package.json` | 修改 | version 0.1.56 0.1.57 |
467
+
468
+ ### 版本与收尾
469
+ - `src/core/constants.ts`:VERSION `0.1.56` `0.1.57`
470
+ - `package.json`:version 同步
471
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
472
+ - 发布:`npm publish` → `jinzd-ai-cli@0.1.57`
473
+
474
+ ### 代码审查完结状态(v0.1.35+ 增量)
475
+ - **高危 H1–H6**:全部已修复(v0.1.56
476
+ - **中危 M1–M16**:全部已修复(v0.1.57)
477
+ - **低危 L1–L28**:待后续改进(多为代码风格、注释、微优化)
478
+
479
+ ---
480
+
481
+ ## 本轮开发完成记录(2026-03-10,v0.1.55v0.1.56)
482
+
483
+ ### 安全修复:v0.1.35+ 增量代码审查 — 6 个高危问题全部修复
484
+
485
+ **审查范围**:v0.1.35 v0.1.55 全部增量代码(15+ 文件),共发现 6 高危 + 16 中危 + 28 低危问题。
486
+
487
+ **高危修复汇总**:
488
+
489
+ | # | 文件 | 问题 | 修复 |
490
+ |---|------|------|------|
491
+ | H1 | `commands/index.ts:774` | `/undo` 崩溃:`args.trim()` 调用在 `string[]` 上 | `args.join(' ').trim()` |
492
+ | H2 | `commands/index.ts:1872` | `/fork` 错误处理崩溃:`printError()` 不存在 | 改为 `renderError()` |
493
+ | H3 | `claude.ts:374,386` | 流式 tool_use 的 `input` 丢失:`input_json_delta` 未累积到 `rawContentBlocks` | 新增 `_inputJson` 累积字段,`content_block_stop` 时 JSON.parse 还原 input |
494
+ | H4 | `repl.ts:1813` | 流式推理文本未存入 session:AI 在工具调用前的文本上下文丢失 | 新增 `_streamedText` 附着到 toolCalls,`buildToolResultMessages` 中作为 assistant content |
495
+ | H5 | `kimi.ts:126` | XML 工具调用注入:`parseXmlToolCalls` 盲搜全文可误执行引用内容 | Plan A 仅在检测到幻觉(Plan C)后才启用 |
496
+ | H6 | `kimi.ts:126-132` | Plan A 绕过 Plan C:XML 解析优先运行跳过了虚假声明的 `alreadyWrote` 安全检查 | 调整执行顺序:Plan C 检测 → Plan A XML 解析(仅在幻觉上下文中) |
497
+
498
+ **变更文件**:
499
+
500
+ | 文件 | 变更类型 | 说明 |
501
+ |------|---------|------|
502
+ | `src/repl/commands/index.ts` | 修改 | H1: `args.trim()` `args.join(' ').trim()`;H2: `printError` `renderError` |
503
+ | `src/providers/claude.ts` | 修改 | H3: `_inputJson` 累积 + `content_block_stop` JSON.parse 还原 |
504
+ | `src/repl/repl.ts` | 修改 | H4: `_streamedText` 附着到 toolCalls 传递给 buildToolResultMessages |
505
+ | `src/providers/openai-compatible.ts` | 修改 | H4: `buildToolResultMessages` 读取 `_streamedText` 作为 assistant content |
506
+ | `src/providers/kimi.ts` | 修改 | H5+H6: Plan A 移到 Plan C 之后,仅在 `isHallucinated` 时启用 XML 解析 |
507
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.55 → 0.1.56 |
508
+ | `package.json` | 修改 | version 0.1.55 → 0.1.56 |
509
+
510
+ ### 版本与收尾
511
+ - `src/core/constants.ts`:VERSION `0.1.55` → `0.1.56`
512
+ - `package.json`:version 同步
513
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
514
+ - 发布:`npm publish` `jinzd-ai-cli@0.1.56`
515
+
516
+ ### 剩余审查问题(中低危,后续改进)
517
+
518
+ **中危(16 个)**:M1 `/config set` 原型污染、M2 `/config show` API Key 泄露、M3 `FREE_ROUND_TOOLS` 无限循环、M4 截断 JSON 静默丢参数、M5 Claude `done` 事件仅在 `message_delta` 时发射、M6 非 base64 图片静默丢弃、M7 AbortError 被包装为 ProviderError、M8 幻觉模式过宽、M9 非流式降级丢失 reasoningContent、M10 Plan C 重试不二次验证、M11 `printTable` ANSI 列对齐、M12 `wrapText` 断裂 ANSI 样式、M13 `resolveColor` 静默吞无效颜色名、M14 `fork()` 浅拷贝消息、M15 bash undo 追踪局限、M16 `edit-file` 相似行搜索无文件大小上限。
519
+
520
+ **低危(28 个)**:详见代码审查报告。
521
+
522
+ ---
523
+
524
+ ## 本轮开发完成记录(2026-03-09,v0.1.54 → v0.1.55)
525
+
526
+ ### Bug 修复:AI 在"阅读/理解"请求中自动执行任务
527
+
528
+ **问题**:用户输入"请仔细阅读 @ynzjmk.md 及其相关的核心文档以了解当前项目"(纯阅读理解请求),AI 读完文档后自动开始执行项目中描述的任务(出题),而非总结理解后等待用户下一步指示。
529
+
530
+ **根因**:system prompt 中无任何指导 AI 区分"理解类请求"与"执行类请求"的行为准则。AI 读取项目文档后,将项目描述的功能(出题)误解为用户的即时任务。
531
+
532
+ **修复**:
533
+
534
+ | 文件 | 变更类型 | 说明 |
535
+ |------|---------|------|
536
+ | `src/core/constants.ts` | 修改 | 新增 `AGENTIC_BEHAVIOR_GUIDELINE` 常量 |
537
+ | `src/repl/repl.ts` | 修改 | `buildCurrentSystemPrompt()` 注入行为准则(在环境信息之后、持久记忆之前) |
538
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.54 → 0.1.55 |
539
+ | `package.json` | 修改 | version 0.1.54 → 0.1.55 |
540
+
541
+ **`AGENTIC_BEHAVIOR_GUIDELINE` 内容**:
542
+ - 当用户要求"阅读/理解/了解/分析/审查/看一下"时,仅读取并总结,等待下一步指示
543
+ - 只有用户明确要求"生成/创建/修改/运行/开始"时才执行写入/执行类工具
544
+ - 不确定意图时使用 `ask_user` 向用户确认
545
+
546
+ **注入位置**:system prompt 最前段(日期环境信息之后),确保始终生效。
547
+
548
+ ### 版本与收尾
549
+ - `src/core/constants.ts`:VERSION `0.1.54` → `0.1.55`
550
+ - `package.json`:version 同步
551
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
552
+ - 发布:`npm publish` → `jinzd-ai-cli@0.1.55`
553
+
554
+ ---
555
+
556
+ ## 本轮开发完成记录(2026-03-09,v0.1.53 → v0.1.54)
557
+
558
+ ### 新增功能:Streaming Tool Use — agentic 循环流式工具调用
559
+
560
+ **背景**:`handleChatWithTools()` agentic 循环中,每轮调用 `provider.chatWithTools()` 使用 `stream: false`,必须等待 API 返回完整响应后才开始处理。AI 生成工具调用参数期间(5-30 秒),用户只看到 spinner,无任何内容反馈。
561
+
562
+ **设计决策**:
563
+ 1. 新增 `chatWithToolsStream()` 方法,不修改现有 `chatWithTools()`
564
+ 2. 统一事件模型 `AsyncGenerator<ToolStreamEvent>`(8 种事件类型)
565
+ 3. 等待全部工具调用完成后再执行(不做 early execution
566
+ 4. Phase 1 覆盖:OpenAI + Zhipu(继承基类)+ Claude;DeepSeek/Kimi 继续用非流式(虚假声明检测需完整响应)
567
+
568
+ **变更文件**:
569
+
570
+ | 文件 | 变更类型 | 说明 |
571
+ |------|---------|------|
572
+ | `src/core/types.ts` | 修改 | 新增 `ToolStreamEvent` 联合类型(8 变体)+ `StreamedToolCallResult` 接口 |
573
+ | `src/providers/base.ts` | 修改 | 新增可选方法 `chatWithToolsStream?()` + 导入 `ToolStreamEvent`/`ToolDefinition` |
574
+ | `src/providers/openai-compatible.ts` | 修改 | 新增 `enableStreamingToolCalls` 标志 + `chatWithToolsStream()` 完整实现(~140 行) |
575
+ | `src/providers/claude.ts` | 修改 | 新增 `chatWithToolsStream()` 实现(~120 行),收集 `rawContentBlocks` 用于 `buildToolResultMessages` |
576
+ | `src/providers/deepseek.ts` | 修改 | `enableStreamingToolCalls = false`(虚假声明检测需完整响应) |
577
+ | `src/providers/kimi.ts` | 修改 | `enableStreamingToolCalls = false`(XML 伪调用 + 虚假声明检测需完整响应) |
578
+ | `src/repl/repl.ts` | 修改 | 新增 `consumeToolStream()` 方法 + `handleChatWithTools()` 流式/非流式双路径 |
579
+ | `src/repl/renderer.ts` | 修改 | `/about` 新增特性条目 |
580
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.53 → 0.1.54 |
581
+ | `package.json` | 修改 | version 0.1.53 0.1.54 |
582
+
583
+ **实现细节**:
584
+
585
+ *类型层*(`types.ts`):
586
+ - `ToolStreamEvent`:`text_delta` / `thinking_start` / `thinking_delta` / `thinking_end` / `tool_call_start` / `tool_call_delta` / `tool_call_end` / `done`
587
+ - `StreamedToolCallResult`:`textContent` + `toolCalls` + `usage` + `rawContent`(Claude 专用)
588
+
589
+ *OpenAI 兼容 Provider*(`openai-compatible.ts`):
590
+ - `enableStreamingToolCalls = true` 保护标志,子类可 override false 禁用
591
+ - 流式路径:`stream: true` + `stream_options: { include_usage: true }`
592
+ - `delta.tool_calls[i]` 首次出现(含 `id`+`name`)发 `tool_call_start`,后续发 `tool_call_delta`
593
+ - 非流式降级路径:`enableStreamingToolCalls = false` 时调用 `chatWithTools()` 并转换为事件序列
594
+
595
+ *Claude Provider*(`claude.ts`):
596
+ - 使用 `this.client.messages.stream()` + AbortSignal 支持
597
+ - 收集 `rawContentBlocks` 数组,通过 `done` 事件的 `rawContent` 传递给 `buildToolResultMessages`
598
+ - 事件映射:`content_block_start`(tool_use) → `tool_call_start`;`input_json_delta` → `tool_call_delta`;`content_block_stop` → `tool_call_end`
599
+ - thinking 块正确处理(`thinking_start`/`thinking_delta`/`thinking_end`)
600
+
601
+ *REPL 层*(`repl.ts`):
602
+ - `consumeToolStream()`:消费事件生成器,`text_delta` 实时输出到 stdout(停 spinner),`tool_call_start` 显示 `⚙ Streaming: <name>...`,累积 arguments JSON 碎片,`tool_call_end` 时 JSON.parse
603
+ - `handleChatWithTools()` 循环:检测 `supportsStreamingTools`(`useStreaming && typeof provider.chatWithToolsStream === 'function'`),流式路径用 `setupStreamInterrupt()`/`teardownStreamInterrupt()` 包裹支持 Escape/Ctrl+C 中断
604
+ - `alreadyRendered` 标志防止文本内容双重渲染
605
+
606
+ **用户体验对比**:
607
+ - Before:Spinner 等待 10-30 突然全部出现
608
+ - After:短暂 Spinner → 文本实时流出 → 工具名即时显示 → 参数完整后执行
609
+
610
+ ### 版本与收尾
611
+ - `src/core/constants.ts`:VERSION `0.1.53` `0.1.54`
612
+ - `package.json`:version 同步
613
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
614
+ - 发布:`npm publish` → `jinzd-ai-cli@0.1.54`
615
+
616
+ ---
617
+
618
+ ## 本轮开发完成记录(2026-03-08,v0.1.51 → v0.1.52)
619
+
620
+ ### Bug 修复:DeepSeek 虚假完成声明(方案 C)+ 虚假声明检测共享重构
621
+
622
+ **问题**:DeepSeek 在多轮对话中生成多个文件时,偶尔会跳过后续文件的 `write_file` 工具调用,直接在回复文本中声称"✅ 文件已保存",实际上文件并未创建。这与 Kimi 的方案 C 问题(v0.1.41)完全相同——模型虚假声称已完成文件操作。
623
+
624
+ **修复**:三层变更
625
+
626
+ | 文件 | 变更 | 说明 |
627
+ |------|------|------|
628
+ | `src/providers/openai-compatible.ts` | 新增共享代码 | HALLUCINATION_PATTERNS(8 个模式)+ `detectsHallucinatedFileOp()` / `mergeUsage()` 两个 protected 方法 |
629
+ | `src/providers/kimi.ts` | 简化(去重) | 移除私有 HALLUCINATION_PATTERNS、detectsHallucinatedFileOp、mergeUsage,改用父类 protected 方法 |
630
+ | `src/providers/deepseek.ts` | 新增覆写 | 方案 B(system prompt 规范提示)+ 方案 C(虚假声明检测 + 自动重试) |
631
+
632
+ **共享重构 — HALLUCINATION_PATTERNS 提升到基类**:
633
+
634
+ 原本 HALLUCINATION_PATTERNS 和检测/合并方法分别在 kimi.ts 中重复定义。本次将共享逻辑提升到 `openai-compatible.ts` 基类:
635
+ - `HALLUCINATION_PATTERNS`:模块级常量,8 个正则模式(比原 Kimi 版增加 `/已保存/`(更宽泛)、`/已创建/`、`/✅.../` 三个新模式)
636
+ - `detectsHallucinatedFileOp(content)`:protected 方法,子类直接调用 `this.detectsHallucinatedFileOp()`
637
+ - `mergeUsage(a, b)`:protected 方法,合并两次 API 调用的 token 用量
638
+
639
+ **DeepSeek 方案 B system prompt 规范提示**:
640
+
641
+ `DEEPSEEK_TOOL_CALL_REMINDER` 注入 system prompt 末尾,比 Kimi 版更简洁(无 XML 相关条款),新增"如果需要生成多个文件,必须对每个文件分别调用 write_file 工具,不可省略任何一个"条款。
642
+
643
+ **DeepSeek 方案 C 虚假声明检测 + 自动重试**:
644
+
645
+ Kimi 方案 C 逻辑一致:
646
+ 1. 检测纯文本响应中的虚假完成声明模式
647
+ 2. 注入纠正消息(AI 的虚假回复 + "你刚才没有实际调用 write_file 工具"纠正指令)到 `_extraMessages`
648
+ 3. 重新调用 `super.chatWithTools()` 一次(防无限循环)
649
+ 4. 合并两次调用的 token 用量
650
+ 5. stderr 输出 `[deepseek] 检测到虚假完成声明` 警告
651
+
652
+ **与 Kimi 的区别**:DeepSeek 不存在 XML 伪工具调用问题(方案 A),因此 deepseek.ts 仅需方案 B + C 两道防线。
653
+
654
+ ### 版本与收尾
655
+ - `src/core/constants.ts`:VERSION `0.1.51` → `0.1.52`
656
+ - `package.json`:version 同步
657
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
658
+
659
+ ### 本轮变更文件汇总
660
+
661
+ | 文件 | 变更类型 | 说明 |
662
+ |------|---------|------|
663
+ | `src/providers/openai-compatible.ts` | 修改 | HALLUCINATION_PATTERNS + detectsHallucinatedFileOp + mergeUsage(共享) |
664
+ | `src/providers/kimi.ts` | 修改 | 移除重复定义,使用父类 protected 方法 |
665
+ | `src/providers/deepseek.ts` | 重写 | 方案 B(system prompt)+ 方案 C(虚假声明检测 + 重试) |
666
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.51 → 0.1.52 |
667
+ | `package.json` | 修改 | version 0.1.51 → 0.1.52 |
668
+
669
+ ---
670
+
671
+ ## 本轮开发完成记录(2026-03-08,v0.1.49 → v0.1.50)
672
+
673
+ ### 代码质量:L1 低危修复 run-tests.ts package.json 细粒度错误处理
674
+
675
+ **问题**:`detectProject()` `JSON.parse(readFileSync('package.json'))` 错误处理粗糙——文件读取和 JSON 解析混在同一个 catch 中;解析失败时静默 fallthrough 可能误判为 Python/Go 项目;无 test script 时不尝试检测已安装的测试框架。
676
+
677
+ **修复**(`src/tools/builtin/run-tests.ts`):
678
+
679
+ 新增 `safeReadPackageJson(cwd)` 辅助函数——细粒度四层处理:
680
+ 1. **文件读取错误**:区分 `EACCES`/`EPERM`(权限问题)和其他读取错误,分别给出不同提示
681
+ 2. **UTF-8 BOM 处理**:自动剥离 BOM(Windows Notepad 等编辑器常见问题),防止 `JSON.parse` 失败
682
+ 3. **空文件检测**:`raw.trim() === ''` 时给出明确 "package.json is empty" 提示
683
+ 4. **JSON 解析错误细分**:区分语法错误(`Unexpected token`)、截断文件(`Unexpected end`)、根不是对象(数组或其他类型)
684
+
685
+ 新增 `detectNodeTestFramework(cwd, pkg)` 辅助函数——当 package.json 有效但无 `scripts.test` 时:
686
+ - 从 `devDependencies` + `dependencies` + 配置文件检测 5 种测试框架:
687
+ - **Vitest**:`vitest` 依赖 或 `vitest.config.{ts,js,mts}`
688
+ - **Jest**:`jest` 依赖 或 `jest.config.{js,ts,mjs}`
689
+ - **Mocha**:`mocha` 依赖 或 `.mocharc.{yml,yaml,json,js}`
690
+ - **Ava**:`ava` 依赖
691
+ - **Playwright**:`@playwright/test` 依赖
692
+ - 检测到框架时直接使用 `npx <framework>` 命令,无需 `scripts.test`
693
+
694
+ 更新 filter 参数处理——不同 Node 测试框架使用正确的 filter 语法:
695
+ - vitest/jest `-t "filter"`
696
+ - mocha/playwright `--grep "filter"`
697
+ - npm test `-- --grep "filter"`(passthrough)
698
+
699
+ **改善效果**:
700
+ - package.json 格式错误时不再静默 fallthrough,返回 `npm (package.json error)` 仍识别为 Node 项目
701
+ - 安装了 vitest/jest/mocha 但未配置 `scripts.test` 的项目现在能自动检测并运行测试
702
+ - 错误信息更具体、更有指导性
703
+
704
+ ### 版本与收尾
705
+ - `src/core/constants.ts`:VERSION `0.1.49` → `0.1.50`
706
+ - `package.json`:version 同步
707
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
708
+ - 代码审查报告 L1 条目标记为 ✅ 已修复
709
+
710
+ ### 本轮变更文件汇总
711
+
712
+ | 文件 | 变更类型 | 说明 |
713
+ |------|---------|------|
714
+ | `src/tools/builtin/run-tests.ts` | 修改 | safeReadPackageJson + detectNodeTestFramework + filter 语法更新 |
715
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.49 → 0.1.50 |
716
+ | `package.json` | 修改 | version 0.1.49 → 0.1.50 |
717
+
718
+ ---
719
+
720
+ ## 本轮开发完成记录(2026-03-08,v0.1.47 → v0.1.48)
721
+
722
+ ### Tier 2 体验增强:/undo 增强 + /fork 对话分支
723
+
724
+ **Feature 1:/undo 增强 bash 工具文件追踪 + 命令增强**
725
+
726
+ | 文件 | 变更 |
727
+ |------|------|
728
+ | `src/tools/undo-stack.ts` | `UndoEntry` 新增 `isDirectory?: boolean` + `pushNewFile()` / `pushNewDir()` 方法 + `undo()` 目录分支 |
729
+ | `src/tools/builtin/bash.ts` | 三辅助函数 + `execute()` 集成 |
730
+ | `src/repl/commands/index.ts` | `/undo` 重写为 `/undo [list\|<n>]` |
731
+
732
+ - **UndoStack 扩展**:新增 `pushNewFile(filePath, desc)` 和 `pushNewDir(dirPath, desc)` 方法,previousContent=null 表示新建(undo 时删除)。`undo()` 新增 `isDirectory` 分支,用 `rmdirSync` 尝试删除空目录,非空给出提示。
733
+ - **Bash 工具文件追踪**(浅层 CWD 快照 + 命令模式解析):
734
+ - `snapshotDir(dir)` — `readdirSync` 获取目录下所有条目的绝对路径 Set
735
+ - `parseCreationTargets(command, cwd)` 正则解析 touch/mkdir/echo>/cp/New-Item 等常见创建命令的目标路径
736
+ - `pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd)` — 对比前后快照 + 解析目标,为新建文件/目录推入 undo 条目
737
+ - 集成到 `execute()`:执行前快照 + 构建目标存在状态 Map;执行后(成功/失败均)调用 pushBashUndoEntries
738
+ - **/undo 命令增强**:
739
+ - `/undo` 撤销最近 1 次(向后兼容)
740
+ - `/undo list` — 显示完整 undo 栈(编号、类型标签 `[new]`/`[mod]`/`[dir]`、描述、时间)
741
+ - `/undo <n>` 连续撤销最近 N 次操作,逐条显示进度
742
+
743
+ **Feature 2:/fork 对话分支**
744
+
745
+ | 文件 | 变更 |
746
+ |------|------|
747
+ | `src/session/session.ts` | 静态 `fork(original, newId, messageCount, newTitle?)` 方法 |
748
+ | `src/session/session-manager.ts` | `forkSession(messageCount, title?)` 方法 |
749
+ | `src/repl/commands/index.ts` | `/fork` 命令 + `CommandContext.forkSession` |
750
+ | `src/repl/repl.ts` | ctx.forkSession 注入 + tab 补全(/undo list, /fork checkpoint 名称) |
751
+
752
+ - **Session.fork()**:复制 messages[0..messageCount],保留范围内 checkpoints(深拷贝),新 UUID,title 默认 "Fork of <原标题>"
753
+ - **SessionManager.forkSession()**:先保存原始 session → Session.fork 创建分叉 → 设为当前 → 保存并返回
754
+ - **/fork 命令**:
755
+ - `/fork` — 从当前位置分叉(复制全部消息)
756
+ - `/fork <checkpoint-name>` 从指定 checkpoint 分叉(仅复制到该 checkpoint 的消息)
757
+ - 显示原始/新 session ID、title、消息数、保留 checkpoint
758
+ - 提示用户 `/session load <id>` 切回原始
759
+ - **Tab 补全**:`/undo` 提供 `list`;`/fork` 提供当前 session checkpoint 名称列表
760
+
761
+ ### 版本与收尾
762
+ - `src/core/constants.ts`:VERSION `0.1.47``0.1.48`
763
+ - `package.json`:version 同步
764
+ - `src/repl/renderer.ts`:命令计数 34 → 35,命令列表新增 `/fork`,新增 1 条特性(/fork 对话分支),更新 /undo 描述
765
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
766
+ - 发布:`npm publish` `jinzd-ai-cli@0.1.48`
767
+
768
+ ### 本轮变更文件汇总
769
+
770
+ | 文件 | 变更类型 | 说明 |
771
+ |------|---------|------|
772
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.47 → 0.1.48 |
773
+ | `src/tools/undo-stack.ts` | 修改 | isDirectory 字段 + pushNewFile/pushNewDir + undo 目录支持 |
774
+ | `src/tools/builtin/bash.ts` | 修改 | snapshotDir + parseCreationTargets + pushBashUndoEntries + execute 集成 |
775
+ | `src/session/session.ts` | 修改 | 静态 fork() 方法 |
776
+ | `src/session/session-manager.ts` | 修改 | forkSession() 方法 |
777
+ | `src/repl/commands/index.ts` | 修改 | /undo 增强 + /fork 命令 + CommandContext.forkSession + /help 更新 |
778
+ | `src/repl/repl.ts` | 修改 | ctx.forkSession 注入 + tab 补全(/undo, /fork) |
779
+ | `src/repl/renderer.ts` | 修改 | /about 35 命令 + 特性更新 |
780
+ | `package.json` | 修改 | version 0.1.47 → 0.1.48 |
781
+
782
+ ### 下一步建议
783
+
784
+ #### Tier 2 体验增强(剩余)
785
+ 1. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复(safeReadPackageJson + detectNodeTestFramework)
786
+ 2. **IDE 集成**:VS Code 扩展(架构已准备就绪,core/providers/tools 无终端依赖)
787
+ 3. **OAuth/浏览器登录**:无需手动填 API Key,打开浏览器完成 OAuth 流程自动保存 token
788
+
789
+ #### Tier 3 长远方向
790
+ 4. **Web UI**:基于 EventBus + WebSocket 的浏览器界面,复用 core/providers/tools 层
791
+ 5. **GitHub 仓库迁移**:从 gitee 迁移到 GitHub,npm 包受众更广
792
+
793
+ ---
794
+
795
+ ## 本轮开发完成记录(2026-03-07,v0.1.40 v0.1.41)
796
+
797
+ ### Bug 修复:Kimi 虚假完成声明(方案 C)
798
+
799
+ **问题**:Kimi-k2 在多轮对话中,第二次及后续文件生成请求时,完全跳过 `write_file` 工具调用,直接在回复中虚假声称"✅ 文件已生成"并附上文件路径,实际上文件并未被创建。这与已修复的 XML 伪工具调用(方案 A)不同——这次 Kimi 没有输出任何 XML 标签,纯粹是文本级别的虚假声明。
800
+
801
+ **修复**:`src/providers/kimi.ts` 新增**方案 C(虚假完成检测 + 自动重试)**,三道防线完整覆盖 Kimi 工具调用的所有已知失败模式:
802
+
803
+ | 防线 | 覆盖场景 | 实现 |
804
+ |------|---------|------|
805
+ | 方案 B(预防)| system prompt 规范 | `KIMI_TOOL_CALL_REMINDER` 新增"严禁虚假完成声明"段落 |
806
+ | 方案 A(兜底 1)| XML 伪工具调用 | `parseXmlToolCalls()` 检测并转换 |
807
+ | **方案 C(兜底 2)**| **虚假完成声明** | **`detectsHallucinatedFileOp()` 检测 + 自动注入纠正消息重试** |
808
+
809
+ 方案 C 实现细节:
810
+ - `HALLUCINATION_PATTERNS`:6 个正则模式检测"文件路径: xxx"、"已生成完成!"、"已保存到"等虚假声明
811
+ - `detectsHallucinatedFileOp(content)`:当响应为纯文本且匹配虚假声明模式时返回 true
812
+ - 自动重试:将 AI 的虚假回复 + 纠正指令 (`"你刚才没有实际调用 write_file 工具,文件并未被创建!"`) 追加到 `_extraMessages`,重新调用 `super.chatWithTools()`
813
+ - 重试后仍有 XML 伪调用时,走方案 A 解析(组合防线)
814
+ - `mergeUsage()` 合并两次 API 调用的 token 用量
815
+ - 只重试一次,防止无限循环
816
+ - stderr 输出警告提示用户发生了虚假声明救援
817
+
818
+ ### 版本与收尾
819
+ - `src/core/constants.ts`:VERSION `0.1.40` → `0.1.41`
820
+ - `package.json`:version 同步
821
+ - 构建验证:`npm run build` 零错误
822
+
823
+ ### 本轮变更文件汇总
824
+
825
+ | 文件 | 变更类型 | 说明 |
826
+ |------|---------|------|
827
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.40 → 0.1.41 |
828
+ | `src/providers/kimi.ts` | 重写 | 方案 C 虚假完成检测 + 自动重试 + HALLUCINATION_PATTERNS |
829
+ | `package.json` | 修改 | version 0.1.40 → 0.1.41 |
830
+
831
+ ---
832
+
833
+ ## 本轮开发完成记录(2026-03-07,v0.1.38 → v0.1.40)
834
+
835
+ ### Bug 修复(3 个使用中发现的问题 + 1 个系统改进)
836
+
837
+ **Bug 1:`/init` 输出路径错误** 🔴
838
+ - **问题**:在 `D:\xgitee\ai-courses\prjs\vocational` 运行 `/init`,AICLI.md 被写到 git 根目录 `D:\xgitee\ai-courses\AICLI.md` 而非 cwd
839
+ - **根因**:`commands/index.ts` 第 1118 行 `const targetDir = gitRoot ?? cwd`,git 仓库内永远写到根目录
840
+ - **修复**:改为 `const targetDir = cwd`,始终写到当前工作目录(子目录层),符合 `/init` 命令语义
841
+
842
+ **Bug 2:工具轮次耗尽后无总结、无法继续** 🟠
843
+ - **问题**:AI 用完 20 轮后只显示 `Error: Reached maximum tool call rounds (20). Stopping.`,无任何已完成工作的总结
844
+ - **修复**:轮次耗尽后,给 AI 最后一次机会生成总结——传入空工具列表 + user 消息要求总结已完成/未完成工作 + 下一步建议。总结内容存入 session,用户可继续对话让 AI 接续
845
+ - **文件**:`src/repl/repl.ts` 轮次耗尽后新增 `summaryExtra` + `chatWithTools(request, [])` 总结生成逻辑
846
+
847
+ **Bug 3:`write_todos` 消耗宝贵工具轮次** 🟡
848
+ - **问题**:用户示例中 3 `write_todos` 占用 15% 的工具轮次(3/20),纯进度展示浪费了实际工作容量
849
+ - **修复**:
850
+ - 新增 `FREE_ROUND_TOOLS = new Set(['write_todos'])`:本轮全部工具调用都属于免费工具时,`round--` 回退计数
851
+ - MAX_TOOL_ROUNDS 从 20 → **25**:更充裕的工具调用空间
852
+
853
+ **Bug 4:AI Windows 上反复使用 Unix 命令导致 bash 工具失败** 🟠
854
+ - **问题**:AI 使用 `date +%Y%m%d`、`grep`、`head`、`find | wc -l` 等 Unix 命令,在 Windows PowerShell 上全部失败,每次浪费 1-2 个工具轮次
855
+ - **根因**:system prompt 中未告知 AI 当前操作系统和 shell 环境,AI 默认使用 Unix 命令
856
+ - **修复**:`buildCurrentSystemPrompt()` 中注入操作系统信息 + shell 类型 + 工作目录。Windows 环境下明确提示"不要使用 Unix 命令,应使用 PowerShell 等效命令",列出常见替代(Select-String/Select-Object -First/Get-ChildItem/Get-Date -Format 等)
857
+ - **文件**:`src/repl/repl.ts` `buildCurrentSystemPrompt()` 新增 `envInfo` 段落
858
+
859
+ ### 版本与收尾
860
+ - `src/core/constants.ts`:VERSION `0.1.38``0.1.40`
861
+ - `package.json`:version 同步
862
+ - 构建验证:`npm run build` 零错误
863
+
864
+ ### 本轮变更文件汇总
865
+
866
+ | 文件 | 变更类型 | 说明 |
867
+ |------|---------|------|
868
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.38 → 0.1.40 |
869
+ | `src/repl/repl.ts` | 修改 | MAX_TOOL_ROUNDS 2025 + FREE_ROUND_TOOLS + 轮次耗尽总结 + OS/shell 信息注入 system prompt |
870
+ | `src/repl/commands/index.ts` | 修改 | /init targetDir 从 gitRoot → cwd |
871
+ | `package.json` | 修改 | version 0.1.38 → 0.1.40 |
872
+
873
+ ### 下一步建议
874
+ 1. ~~**`/config set` 快捷配置**~~:✅ 已实现(v0.1.49)
875
+ 2. ~~**流式工具调用(Streaming Tool Use)**~~:✅ 已在 v0.1.54 实现(OpenAI/Claude 流式 + DeepSeek/Kimi 非流式降级)
876
+ 3. ~~**`/diff` 命令**~~:✅ 已实现(v0.1.49)
877
+
878
+ ---
879
+
880
+ ## 本轮开发完成记录(2026-03-07,v0.1.37v0.1.38)
881
+
882
+ ### Tier 1 高价值功能:三项全部实现
883
+
884
+ **Feature 1:Extended Thinking Claude 深度推理模式**
885
+
886
+ | 文件 | 变更 |
887
+ |------|------|
888
+ | `src/core/types.ts` | ChatRequest 新增 `thinkingBudget?: number` |
889
+ | `src/config/schema.ts` | ModelParamsSchema 新增 `thinkingBudget: z.number().int().min(1024).optional()` |
890
+ | `src/providers/claude.ts` | 完整重写,3 个 API 方法支持 thinking |
891
+ | `src/repl/repl.ts` | `runtimeThinking` 运行时覆盖 + `[THINK]` 提示符标记 + thinkingBudget 传递 |
892
+ | `src/repl/commands/index.ts` | 新增 `/think` 命令(on/off/status/toggle) |
893
+
894
+ - `claude.ts` 核心改动:
895
+ - 新增 `buildThinkingParams(request)` 辅助方法:thinking 启用时 `temperature` 必须为 `undefined`(API 强制 temperature=1),`budget_tokens` 最小 1024 默认 10000
896
+ - 新增 `extractContent(blocks)` 辅助方法:从 `response.content` 提取 ThinkingBlock + TextBlock,thinking 用 `<think>...</think>` 标签包裹(复用 renderer 已有折叠逻辑)
897
+ - `chat()` / `chatStream()` / `chatWithTools()`:三个 API 方法均传入 `thinking` + `temperature` 参数
898
+ - 流式输出:`currentBlockType` 跟踪当前 content block 类型,`content_block_start` 时发 `<think>`,`content_block_stop` 时发 `</think>`,`thinking_delta` 事件直接输出 thinking 文本
899
+ - 工具调用循环:`_rawContent` 属性附着在 `toolCalls` 数组上,保留原始 response.content 块(含 ThinkingBlock/RedactedThinkingBlock)
900
+ - `buildToolResultMessages()`:优先使用 `_rawContent` 构建 assistantContent,保留 thinking/redacted_thinking/tool_use/text 四种块类型传回 API
901
+ - `repl.ts`:
902
+ - `runtimeThinking: boolean | null = null`(null=用配置值,true/false=运行时覆盖)
903
+ - `getModelParams()` 返回 `thinkingBudget` + `runtimeThinking` 优先覆盖 `thinking`
904
+ - 全部 4 API 调用新增 `thinkingBudget: modelParams.thinkingBudget`
905
+ - `refreshPrompt()` thinking 启用时显示黄色 `[THINK]` 标记
906
+ - CommandContext 注入 `getThinkingMode` / `setThinkingMode`
907
+ - `/think` 命令:`/think` toggle 切换 | `/think on` 启用 | `/think off` 禁用 | `/think status` 显示状态
908
+
909
+ **Feature 2:theme 迁移全覆盖**
910
+
911
+ | 文件 | chalk→theme 迁移数 | 保留 chalk.white |
912
+ |------|-------------------|-----------------|
913
+ | `src/repl/commands/index.ts` | 116 | 4(中性色) |
914
+ | `src/repl/repl.ts` | | 1(中性色) |
915
+ | `src/repl/renderer.ts` | — | 4(中性色) |
916
+ | `src/tools/executor.ts` | ~15 | 3(中性色) |
917
+ | `src/repl/setup-wizard.ts` | 26 | 0 |
918
+
919
+ - 迁移映射规则:
920
+ - `chalk.red` `theme.error`(错误信息)
921
+ - `chalk.yellow` → `theme.warning`(警告)或 `theme.toolCall`(工具标记,视上下文)
922
+ - `chalk.green` `theme.success`(成功确认)
923
+ - `chalk.cyan` `theme.accent`(强调 ID/路径/值)
924
+ - `chalk.dim` / `chalk.gray` `theme.dim`(次要文本)
925
+ - `chalk.bold` / `chalk.bold.cyan` `theme.heading`(标题)
926
+ - `chalk.magenta` `theme.toolCall`(工具/技能标记)
927
+ - `chalk.white` → 保持不变(中性内容,不受主题影响)
928
+ - 现在切换 `config.ui.theme` 为 `"light"` 或 `"custom"` 时,全部终端输出(命令/REPL/工具/向导)统一变化
929
+
930
+ **Feature 3:edit_file 工具增强**(`src/tools/builtin/edit-file.ts`)
931
+
932
+ - 新增 `similarityScore(a, b): number`:bigram(2-gram)字符串相似度算法,O(n) 复杂度,返回 0-1 分数
933
+ - 新增 `findSimilarLines(fileContent, searchStr, maxResults=3): string[]`:当 `old_str` 匹配失败时,搜索文件中最相似的行(>50% 阈值),返回 "Line N: <text>" 格式建议
934
+ - 新增 `findWhitespaceTolerant(fileLines, searchLines): { start, count } | null`:滑动窗口逐行 trim 匹配,支持忽略缩进差异
935
+ - 新增 `ignore_whitespace` 可选参数(默认 false):启用时按行 trim 后匹配,保持唯一性检查
936
+ - 增强错误消息格式:
937
+ ```
938
+ ERROR: old_str not found in file.
939
+ File has 167 lines.
940
+ Similar lines found (did you mean?):
941
+ Line 42: const firstIndex = original.indexOf(oldStr);
942
+ Line 88: if (firstIndex === -1) {
943
+ Tip: If it's a whitespace/indentation issue, try setting ignore_whitespace: true
944
+ Please read the file first and use exact text including whitespace/indentation.
945
+ ```
946
+
947
+ ### 版本与收尾
948
+ - `src/core/constants.ts`:VERSION `0.1.37` → `0.1.38`
949
+ - `package.json`:version 同步
950
+ - `src/repl/renderer.ts`:`/about` 命令计数 32 33,命令列表新增 `/think`,新增 3 条特性条目(Extended Thinking / theme 全覆盖 / edit_file 智能提示)
951
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
952
+ - 发布:`npm publish` `jinzd-ai-cli@0.1.38`
953
+
954
+ ### 本轮变更文件汇总
955
+
956
+ | 文件 | 变更类型 | 说明 |
957
+ |------|---------|------|
958
+ | `src/core/types.ts` | 修改 | ChatRequest 新增 `thinkingBudget` |
959
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.37 0.1.38 |
960
+ | `src/config/schema.ts` | 修改 | ModelParamsSchema 新增 `thinkingBudget` |
961
+ | `src/providers/claude.ts` | 重写 | Extended Thinking 完整支持(3 API 方法 + thinking 块处理) |
962
+ | `src/repl/repl.ts` | 修改 | runtimeThinking + thinkingBudget + [THINK] 标记 + theme 迁移 |
963
+ | `src/repl/renderer.ts` | 修改 | /about 更新(33 命令 + 3 特性) |
964
+ | `src/repl/commands/index.ts` | 修改 | /think 命令 + 116 处 theme 迁移 |
965
+ | `src/repl/setup-wizard.ts` | 修改 | 26 处 chalk→theme 全量迁移 |
966
+ | `src/tools/executor.ts` | 修改 | ~15 处 chalk→theme 迁移 |
967
+ | `src/tools/builtin/edit-file.ts` | 重写 | similarityScore + findSimilarLines + ignore_whitespace |
968
+ | `package.json` | 修改 | version 0.1.37 → 0.1.38 |
969
+
970
+ ### 下一步建议
971
+
972
+ #### Tier 1 高价值功能(已全部完成)
973
+ 1. ~~**`/config set` 快捷配置**~~:✅ 已实现(v0.1.49)
974
+ 2. ~~**流式工具调用(Streaming Tool Use)**~~:✅ 已在 v0.1.54 实现(OpenAI/Claude 流式 + DeepSeek/Kimi 非流式降级)
975
+ 3. ~~**`/diff` 命令**~~:✅ 已实现(v0.1.49)
976
+
977
+ #### Tier 2 体验增强
978
+ 4. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复(safeReadPackageJson + detectNodeTestFramework)
979
+ 5. **IDE 集成**:VS Code 扩展(架构已准备就绪,core/providers/tools 无终端依赖)
980
+ 6. **OAuth/浏览器登录**:无需手动填 API Key,打开浏览器完成 OAuth 流程自动保存 token
981
+ 7. ~~**`/undo` 增强**~~:✅ 已在 v0.1.48 实现(bash 文件追踪 + /undo list + /undo <n>)
982
+ 8. ~~**对话分支(fork)**~~:✅ 已在 v0.1.48 实现(/fork [checkpoint-name]
983
+
984
+ #### Tier 3 长远方向
985
+ 9. **Web UI**:基于 EventBus + WebSocket 的浏览器界面,复用 core/providers/tools 层
986
+ 10. **GitHub 仓库迁移**:从 gitee 迁移到 GitHub,npm 包受众更广
987
+
988
+ ---
989
+
990
+ ## 本轮开发完成记录(2026-03-05,v0.1.36 v0.1.37)
991
+
992
+ ### P2 四项功能全部实现
993
+
994
+ **Feature 1:项目级 `.mcp.json`**(`src/repl/repl.ts` + `src/repl/commands/index.ts` + `src/core/constants.ts`)
995
+ - `src/core/constants.ts`:新增 `MCP_PROJECT_CONFIG_NAME = '.mcp.json'`
996
+ - `src/repl/repl.ts`:
997
+ - 新增私有属性 `mcpServerSources = new Map<string, 'global' | 'project'>()`
998
+ - 新增 `loadProjectMcpConfig()` 方法:`getGitRoot(cwd)` 查找项目根 → 读取 `<root>/.mcp.json` → 基础校验(每个 server 须有 `command` 字段)→ 解析失败 stderr 警告不中断启动
999
+ - `start()` 中 MCP 初始化重构:先获取全局 `mcpServers`,再获取项目 `.mcp.json`,合并(项目覆盖全局同名),记录来源到 `mcpServerSources`
1000
+ - CommandContext 注入 `getMcpServerSource`
1001
+ - `src/repl/commands/index.ts`:`/mcp` 命令显示每个服务器的 `[global]`/`[project]` 来源标签
1002
+
1003
+ **Feature 2:`--resume <id>` 启动参数**(`src/index.ts` + `src/repl/repl.ts`)
1004
+ - `src/index.ts`:新增 `--resume <id>` CLI 选项 + `startRepl` 参数传入
1005
+ - `src/repl/repl.ts`:
1006
+ - constructor options 扩展 `resumeSessionId?: string`
1007
+ - `start()` 有 `resumeSessionId` 时:`listSessions()` 前缀匹配未找到显示最近 5 session + exit(1) → 找到则 `loadSession()` 恢复
1008
+ - welcome 后显示 `📂 Resumed session: <id> (<N> messages, "<title>")`
1009
+
1010
+ **Feature 3:Word wrap 配置**(`src/config/schema.ts` + `src/repl/renderer.ts` + `src/repl/repl.ts`)
1011
+ - `src/config/schema.ts`:`ui` 对象新增 `wordWrap: z.number().int().min(0).default(0)`(0=自动,>0=固定列宽)
1012
+ - `src/repl/renderer.ts`:
1013
+ - 新增 `stripAnsi(s: string): string` 去除 ANSI 转义码(正则匹配 ESC 序列)
1014
+ - 新增 `wrapText(text: string, width: number | undefined): string` — 逐行处理,按单词边界折行,ANSI 码不计入宽度
1015
+ - Renderer constructor 扩展 `options?: { wrapWidth?: number }`
1016
+ - `renderResponse()` 对内容应用 `wrapText()`(流式模式不折行,由终端自然处理)
1017
+
1018
+ **Feature 4:主题/颜色自定义**(`src/repl/theme.ts` + `src/config/schema.ts` + 多文件迁移)
1019
+ - `src/config/schema.ts`:`ui` 对象新增 `theme: z.enum(['dark', 'light', 'custom']).default('dark')` + `colors` 对象(10 个可选色槽)
1020
+ - **新文件** `src/repl/theme.ts`:
1021
+ - `ThemeColors` 接口:10 个语义色槽(prompt/info/warning/error/success/dim/accent/toolCall/toolResult/heading)→ `ChalkInstance`
1022
+ - `DARK_THEME`:精确匹配 v0.1.36 硬编码颜色(green/cyan/yellow/red/dim/magenta 等),确保零变化升级
1023
+ - `LIGHT_THEME`:浅色终端优化(blue/blueBright/gray 等)
1024
+ - `resolveColor(name: string)`:支持 chalk 颜色名 + `#hex` + `bold.cyan` 组合样式
1025
+ - `buildCustomTheme(base, overrides)`:以 base 主题为底 + 自定义覆盖
1026
+ - `initTheme(themeId, customColors?)`:设置全局主题
1027
+ - `theme`:Proxy 导出,始终指向当前活跃主题
1028
+ - 迁移覆盖:
1029
+ - `src/repl/renderer.ts`:`printWelcome`(heading/prompt/dim)、`printPrompt`(prompt)、`renderStream`(accent)、`renderResponse`(accent)、`renderError`(error)、`printInfo`(warning)、`printSuccess`(success)
1030
+ - `src/tools/executor.ts`:`printToolCall`(toolCall/accent/dim)、`printToolResult`(error/toolResult/warning)
1031
+ - `src/repl/repl.ts`:constructor 中调用 `initTheme()`,传入 `wrapWidth` 给 Renderer
1032
+
1033
+ ### 版本与收尾
1034
+ - `src/core/constants.ts`:VERSION `0.1.36` `0.1.37`
1035
+ - `package.json`:version 同步
1036
+ - `src/repl/renderer.ts`:`/about` 新增 4 条特性条目(项目级 .mcp.json / --resume / Word wrap / 主题系统)
1037
+ - 构建验证:`npm run build` 零错误
1038
+ - 发布:`npm publish` → `jinzd-ai-cli@0.1.37`
1039
+
1040
+ ### 本轮变更文件汇总
1041
+
1042
+ | 文件 | 变更类型 | 说明 |
1043
+ |------|---------|------|
1044
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.36 → 0.1.37,新增 MCP_PROJECT_CONFIG_NAME |
1045
+ | `src/config/schema.ts` | 修改 | `ui.wordWrap` + `ui.theme` + `ui.colors` 三个新字段 |
1046
+ | `src/repl/theme.ts` | 新增 | 集中式主题系统(ThemeColors + DARK/LIGHT + Proxy) |
1047
+ | `src/repl/repl.ts` | 修改 | .mcp.json 加载 + --resume + theme 初始化 + mcpServerSources |
1048
+ | `src/repl/renderer.ts` | 修改 | stripAnsi/wrapText 辅助函数 + theme 迁移 + /about 更新 |
1049
+ | `src/tools/executor.ts` | 修改 | theme 迁移(printToolCall/printToolResult) |
1050
+ | `src/repl/commands/index.ts` | 修改 | getMcpServerSource + /mcp 来源标签 |
1051
+ | `src/index.ts` | 修改 | --resume CLI 选项 |
1052
+ | `package.json` | 修改 | version 0.1.36 → 0.1.37 |
1053
+
1054
+ ### 下一步建议
1055
+ 1. ~~**Extended Thinking**~~:✅ 已在 v0.1.38 实现
1056
+ 2. ~~**theme 迁移扩展**~~:✅ 已在 v0.1.38 全覆盖迁移
1057
+ 3. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
1058
+ 4. **IDE 集成**:VS Code 扩展(架构已准备就绪)
1059
+ 5. **OAuth/浏览器登录**:无需手动填 API Key
1060
+
1061
+ ---
1062
+
1063
+ ## 本轮开发完成记录(2026-03-05,v0.1.34 → v0.1.36)
1064
+
1065
+ ### v0.1.35 — P0 功能缺口 + P1 前三项
1066
+
1067
+ **P0:并行工具调用**(`src/tools/executor.ts`)
1068
+ - `executeAll()` 的 safe 级别工具从顺序 `for` 循环改为 `Promise.all()` 并行执行,批量 safe 工具整体耗时降至最慢单个工具的时间
1069
+
1070
+ **P0:`/add-dir` 动态目录上下文**(`src/repl/repl.ts` + `src/repl/commands/index.ts`)
1071
+ - `Repl` 新增 `extraContextDirs` 数组 + `scanDirContent()`(目录树 + 文件内容,总限 40K 字符,单文件限 4K)
1072
+ - `addExtraContextDir()` / `removeExtraContextDir()`:解析绝对路径,校验目录存在,存入数组
1073
+ - `buildCurrentSystemPrompt()` 在 skill 段之前注入 `# Added Directory Context: <dir>` 段落
1074
+ - `/add-dir <路径>`:添加目录;`/add-dir remove <路径>`:移除;`/add-dir`(无参):列出所有已添加目录
1075
+
1076
+ **启动参数:`--allowed-tools` / `--blocked-tools`**(`src/index.ts` + `src/repl/repl.ts`)
1077
+ - CLI 新增两个选项,逗号分隔工具名,转为 `Set<string>` 传入 `Repl` 构造函数
1078
+ - `handleChatWithTools()` 在 plan/skill 过滤之后叠加应用 allowedTools / blockedTools 过滤
1079
+
1080
+ **P1:`/memory` 命令**(`src/repl/commands/index.ts`)
1081
+ - `show`:打印 memory.md 内容;`add <内容>`:`appendFileSync` 追加带时间戳条目;`clear`:清空文件(需确认);`path`:显示文件路径
1082
+
1083
+ **P1:`/doctor` 健康检查**(`src/repl/commands/index.ts`)
1084
+ - 扫描所有 configured provider 的 API Key 状态(✓/✗)
1085
+ - 检查 config.json 文件是否存在
1086
+ - 调用 `getMcpManager().getStatus()` 显示 MCP 服务器连接状态
1087
+ - 显示当前 session 消息数 + 估算 context 占用率(绿/黄/红)
1088
+
1089
+ **文档与版本**
1090
+ - `src/repl/commands/index.ts`:`CommandContext` 新增 `addContextDir` / `removeContextDir` / `listContextDirs` 三个回调
1091
+ - `src/repl/renderer.ts`:命令计数 28 → 31,添加 `/add-dir` `/memory` `/doctor`,新增 5 条特性条目
1092
+ - VERSION `0.1.34` → `0.1.35`,`package.json` 同步
1093
+
1094
+ ---
1095
+
1096
+ ### v0.1.36 P1 剩余三项全部完成
1097
+
1098
+ **P1:`/bug` 反馈命令**(`src/repl/commands/index.ts`)
1099
+ - 生成 Markdown 格式 Bug 报告模板,自动填入 VERSION / OS(`platform()`)/ Node.js 版本 / 当前 Provider / 当前 Model
1100
+ - `--copy` 参数调用已有 `copyToClipboard()` 复制到剪贴板
1101
+ - `--copy` 时打印报告并显示提交链接(`REPO_URL/issues`)
1102
+ - 新增 `VERSION` + `REPO_URL` 到 constants.ts import
1103
+
1104
+ **P1:流式 JSON 输出**(`src/index.ts`)
1105
+ - 新增 `--output-format <format>` CLI 选项,支持 `text`(默认)和 `streaming-json`
1106
+ - `streaming-json` 模式:调用 `provider.chatStream()`,每个 delta 输出一行 NDJSON:
1107
+ - `{"type":"delta","text":"..."}` —— 每个 chunk
1108
+ - `{"type":"done","content":"...","provider":"...","model":"...","usage":{...}}` —— 最终行
1109
+ - `--json` `streaming-json` 均强制不走普通 `useStream` 分支
1110
+
1111
+ **P1:桌面通知**
1112
+ - 新文件 `src/repl/notify.ts`:`sendNotification(title, body)` 跨平台非阻塞后台 spawn
1113
+ - macOS:`osascript -e 'display notification "..." with title "..."'`
1114
+ - Windows:PowerShell `System.Windows.Forms.NotifyIcon`(5 行脚本,`ShowBalloonTip(3000)`)
1115
+ - Linux:`notify-send <title> <body>`
1116
+ - 失败静默忽略(`try/catch` 包裹,不影响主流程)
1117
+ - `src/config/schema.ts`:`ui` 对象新增 `notificationThreshold: z.number().default(10_000)`(单位 ms,0 = 禁用)
1118
+ - `src/repl/repl.ts`:导入 `sendNotification`;`handleChat()` `handleChatSimple`/`handleChatWithTools` 调用前记录 `t0 = Date.now()`,完成后对比阈值发送通知
1119
+
1120
+ **文档与版本**
1121
+ - `src/repl/commands/index.ts`:`/help` 新增 `/bug [--copy]` 条目
1122
+ - `src/repl/renderer.ts`:命令计数 31 32,命令行列表加入 `/bug`,新增 3 条特性
1123
+ - 新增 `USAGE.md`:21 章节完整用户使用说明文档(CLI / REPL 命令 / 工具 / 配置 / 各高级特性)
1124
+ - VERSION `0.1.35` → `0.1.36`,`package.json` 同步
1125
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
1126
+
1127
+ ### 本轮变更文件汇总
1128
+
1129
+ | 文件 | 变更类型 | 说明 |
1130
+ |------|---------|------|
1131
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.34 → 0.1.36 |
1132
+ | `src/config/schema.ts` | 修改 | `ui.notificationThreshold` 新增字段 |
1133
+ | `src/tools/executor.ts` | 修改 | safe 工具并行执行(Promise.all) |
1134
+ | `src/repl/repl.ts` | 修改 | extraContextDirs、allowedTools/blockedTools、sendNotification 集成 |
1135
+ | `src/repl/notify.ts` | 新增 | 跨平台桌面通知实现 |
1136
+ | `src/repl/commands/index.ts` | 修改 | /add-dir /memory /doctor /bug 四个命令 + /help 更新 |
1137
+ | `src/repl/renderer.ts` | 修改 | /about 命令数 28→32,新增特性条目 |
1138
+ | `src/index.ts` | 修改 | --allowed-tools / --blocked-tools / --output-format 三个新参数 |
1139
+ | `package.json` | 修改 | version 0.1.34 → 0.1.36 |
1140
+ | `USAGE.md` | 新增 | 21 章节完整用户使用说明文档 |
1141
+
1142
+ ### 下一步建议(P2 优先)
1143
+ 1. ~~**项目级 `.mcp.json`**~~:✅ 已在 v0.1.37 实现
1144
+ 2. ~~**`--resume <id>` 启动参数**~~:✅ 已在 v0.1.37 实现
1145
+ 3. ~~**Word wrap 配置**~~:✅ 已在 v0.1.37 实现
1146
+ 4. ~~**主题/颜色自定义**~~:✅ 已在 v0.1.37 实现
1147
+ 5. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
1148
+
1149
+ ---
1150
+
1151
+ ## 本轮开发完成记录(2026-03-03,v0.1.32 → v0.1.33)
1152
+
1153
+ ### 高危安全修复:H1–H4 全部修复
1154
+
1155
+ **H1 MCP pendingRequests 内存泄漏**(`src/mcp/client.ts`):
1156
+ `sendRequest()` 重构——resolve/reject 回调内含统一 `cleanup()` 函数(`pendingRequests.delete(id)` + `clearTimeout(timer)`),任何 Promise 完成路径都即时释放资源。`handleMessage()` 简化——不再重复调用 delete/clearTimeout(由回调内部处理)。
1157
+
1158
+ **H2 — readline 竞态条件**(`src/tools/executor.ts`):
1159
+ `confirm()` 和 `batchConfirm()` 中 `completed` 布尔守卫从外部变量统一移入 `cleanup()` 函数内部(单一检查点模式)。`rl.once('line')` 注册用 `try/catch` 包裹,极端情况下(readline 已关闭)确保 `confirming` 标志不会卡死。
1160
+
1161
+ **H3 — 工具执行错误语义混淆**(`src/tools/executor.ts`):
1162
+ 所有用户取消/拒绝路径的 `content` 增加语义前缀:
1163
+ - `[User cancelled]` — 用户在 confirm 对话框中按 N 或 Ctrl+C
1164
+ - `[User rejected]` 用户在 batchConfirm 中拒绝特定文件
1165
+ - `[Permission denied]` 权限规则阻止
1166
+ 所有前缀后附 "Do not retry without asking." 指示,防止 AI 无意义重试。
1167
+
1168
+ **H4 MCP 断开后无恢复机制**:
1169
+ - `src/mcp/client.ts`:新增 `reconnect()` 方法——清理旧状态(rejectAllPending + killProcess + 重置缓冲区)后重新执行完整 `connect()` 流程
1170
+ - `src/mcp/manager.ts`:新增 `reconnectServer(serverId)` 和 `reconnectAll()` 方法;`wrapMcpTool` 的 execute 函数在检测到断线时自动尝试一次重连,成功后继续调用
1171
+ - `src/repl/commands/index.ts`:`/mcp reconnect [serverId]` 子命令——手动重连指定服务器或所有断开的服务器,成功后刷新 ToolRegistry 中的 MCP 工具
1172
+
1173
+ **版本与收尾**
1174
+ - `src/core/constants.ts`:VERSION `0.1.30` `0.1.33`
1175
+ - `package.json`:version `0.1.32` `0.1.33`
1176
+
1177
+ ---
1178
+
1179
+ ## 本轮开发完成记录(2026-03-03,v0.1.33 v0.1.34)
1180
+
1181
+ ### 中危安全修复:M5–M12 全面排查
1182
+
1183
+ 对审计报告中 8 个中危问题逐一排查,发现 **M6–M11 已在先前版本修复或确认安全**,仅 **M5** 和 **M12** 需要新代码修复。
1184
+
1185
+ **M5 — bash `persistentCwd` 指向已删除目录**(`src/tools/builtin/bash.ts`):
1186
+ - 问题:`persistentCwd` 模块级变量记住上次 `cd` 目标,但该目录可能在后续操作中被删除(如 `rm -rf`),导致下一次 bash 调用 `execSync` 报 `ENOENT`
1187
+ - 修复:`execute()` 开头新增 `existsSync(persistentCwd)` 检查,不存在时自动回退到 `process.cwd()` 并打印 stderr 警告
1188
+
1189
+ **M12 — MCP 工具 schema 嵌套结构丢失**(`src/mcp/manager.ts` + `src/tools/types.ts` + 三个 provider):
1190
+ - 问题:MCP 工具的 JSON Schema 含嵌套 object/array 时,`convertDefinition()` 扁平化为简单类型,AI 生成的参数结构与实际 schema 不匹配导致调用失败
1191
+ - 修复:
1192
+ - `src/tools/types.ts`:`ToolParameterSchema` 新增 `properties?: Record<string, ToolParameterSchema>` 字段;`items` 类型从 `{ type: ToolParameterType }` 扩展为完整 `ToolParameterSchema`;新增 `schemaToJsonSchema()` 公共递归转换函数
1193
+ - `src/mcp/manager.ts`:新增 `convertSchema()` 私有递归方法,替代原有扁平转换
1194
+ - `src/providers/openai-compatible.ts`、`claude.ts`、`gemini.ts`:统一使用 `schemaToJsonSchema()` 构建 API 请求的工具参数 schema
1195
+
1196
+ **M6–M11 排查结论**(无需修改):
1197
+ | # | 结论 |
1198
+ |---|------|
1199
+ | M6 | web-fetch 手动 redirect 循环每一跳均做 SSRF 检查,已安全 |
1200
+ | M7 | run-interactive `write()` 返回 false 时已有 `once('drain')` 背压处理 |
1201
+ | M8 | session-manager `catch` 中已有 `stderr.write` 含文件名和错误信息 |
1202
+ | M9 | mcp/client `killProcess()` 已有 `removeAllListeners()`(H4 修复附带) |
1203
+ | M10 | permissions.ts 使用 `.includes()` 子串匹配而非正则,无 ReDoS 风险 |
1204
+ | M11 | repl.ts 上下文加载已有 `resolve()` + `startsWith(cwd)` 路径穿越校验 |
1205
+
1206
+ **版本与收尾**
1207
+ - `src/core/constants.ts`:VERSION `0.1.33` → `0.1.34`
1208
+ - `package.json`:version `0.1.33` `0.1.34`
1209
+ - 代码审查报告表格全部更新为已修复/已确认安全状态
1210
+ - 构建通过:`npm run build` 零错误
1211
+
1212
+ ### 安全审计完结状态
1213
+ - **高危 H1–H4**:全部已修复(v0.1.33)
1214
+ - **中危 M5–M12**:全部已修复或确认安全(v0.1.34)
1215
+ - **低危 L1–L7**:L2–L7 已在 v0.1.30 修复/确认,L1 待后续改进
1216
+ - **安全债务已清零**,可安全进入功能开发阶段
1217
+
1218
+ ### 下一步建议
1219
+ 1. **P0 功能缺口**:并行工具调用、`/add-dir` 命令
1220
+ 2. **P1 功能缺口**:`/memory` 命令、`/doctor` 健康检查、`/bug` 反馈
1221
+ 3. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
1222
+
1223
+ ---
1224
+
1225
+ ## 本轮开发完成记录(2026-03-01,v0.1.26 → v0.1.30)
1226
+
1227
+ ### 安全修复续篇:低危 L2–L7
1228
+
1229
+ **L2**(`src/tools/builtin/web-fetch.ts`):`htmlToText()` 函数新增 200 KB HTML 大小上限(`HTML_REGEX_LIMIT = 200_000`),超出则截断后再做正则替换,防止恶意大 HTML 导致正则性能崩溃。
1230
+
1231
+ **L3**(`src/session/session-manager.ts`):新增 `safeDate(value: unknown): Date` 辅助函数,对无效日期字符串返回 `new Date(0)` 而非 `Invalid Date`,防止 `listSessions()` 和 `searchMessages()` 的日期比较静默失败。
1232
+
1233
+ **L4**(`src/tools/builtin/ask-user.ts` + `src/tools/builtin/google-search.ts`):为模块级全局上下文对象(`askUserContext` / `googleSearchContext`)添加架构说明注释,提示 GUI 多会话扩展时需重构为 per-session 状态。
1234
+
1235
+ **L5**(`src/config/schema.ts`):为 `timeouts` 字段补充三层优先级文档注释:
1236
+ 1. `modelParams[modelId].timeout` — 最高
1237
+ 2. `timeouts[providerId]` — provider 级默认
1238
+ 3. 内置 provider 硬编码默认值 最低兜底
1239
+
1240
+ **L6/L7**:确认已安全(`call.arguments['path']` 已用 `String(... ?? '')` 兜底;主循环 `line` handler 已有 try/catch)。
1241
+
1242
+ ---
1243
+
1244
+ ### 新增功能 1:多模态图片输入(P0)
1245
+
1246
+ **背景**:用户希望通过 `@image.png 描述这张图` 语法将图片发送给各 AI Provider。
1247
+
1248
+ **类型层**(已有,无需改动):`src/core/types.ts` 的 `ImageContentPart { type: 'image_url', image_url: { url: 'data:mime;base64,...' } }` 与 OpenAI 格式完全兼容。
1249
+
1250
+ **Claude Provider**(`src/providers/claude.ts`):
1251
+ - 新增 `contentToClaudeParts()` 私有方法:将内部 `image_url` 格式转换为 Anthropic SDK 期望的 `{ type: 'image', source: { type: 'base64', media_type, data } }` 格式
1252
+ - 应用到 `chat()`、`chatStream()`、`chatWithTools()` 的消息构建
1253
+
1254
+ **Gemini Provider**(`src/providers/gemini.ts`):
1255
+ - 移除错误的 `getContentText()` 调用(会丢弃图片)
1256
+ - 新增 `contentToGeminiParts()` 私有方法:转换为 Gemini SDK 格式 `{ inlineData: { mimeType, data } }`
1257
+ - 修复 `toGeminiHistory()`、`chat()`、`chatStream()`、`chatWithTools()` 的消息构建
1258
+ - `chatWithTools()` 中变量 `lastMessage: string` 重命名为 `lastMsgParts: Part[]`,历史嵌入改为 `{ role: 'user', parts: lastMsgParts }`
1259
+
1260
+ **OpenAI 兼容 Provider**:已就绪,格式一致,无需修改。
1261
+
1262
+ **REPL 层**(`src/repl/repl.ts`):
1263
+ - 新增常量 `MAX_IMAGE_BYTES = 10 * 1024 * 1024`(10 MB)
1264
+ - `parseAtReferences()` 图片读取前用 `statSync` 校验大小,超限时加入 `refs` 类型为 `'toolarge'`,不内联
1265
+ - `handleChat()` 新增 `toolarge` 分支:打印黄色警告 `⚠ Image too large (> 10 MB): <path>`
1266
+ - 修复 `getVisionModelHint()`:Claude/Gemini 返回 `null`(原生支持,无需警告);DeepSeek 显示明确不支持提示;zhipu 推荐 `glm-4.6v`;kimi `moonshot-v1-*` 推荐对应 vision 版本
1267
+
1268
+ ---
1269
+
1270
+ ### 新增功能 2:Escape/Ctrl+C 中断 AI 流式输出(P0)
1271
+
1272
+ **背景**:流式生成期间无法中断,必须等待 AI 返回完整内容。
1273
+
1274
+ **架构设计**:两个流式生成入口——`handleChatSimple()` `handleChatWithTools()` tee streaming 分支(`save_last_response` 工具)——均支持中断。主路径(`chatWithTools()` `renderResponse()`)为非流式,暂不支持。
1275
+
1276
+ **`src/core/types.ts`**:`ChatRequest` 新增 `signal?: AbortSignal`,透传给 Provider API 调用。
1277
+
1278
+ **Provider 层**:
1279
+ - `claude.ts`:`messages.stream()` 第二参数传入 `{ signal: request.signal }`(Anthropic SDK 支持)
1280
+ - `openai-compatible.ts`:流式 `create()` 第二参数传入 `{ signal: request.signal }`(OpenAI SDK 支持)
1281
+ - `gemini.ts`:生成器循环开头检查 `if (request.signal?.aborted) break`(兼容性保险)
1282
+
1283
+ **`src/repl/renderer.ts`**:`renderStream()` `options` 新增 `signal?: AbortSignal`:
1284
+ - `for await` 循环开头检查 `signal.aborted` → 标记 `interrupted = true` 并 `break`
1285
+ - 捕获 SDK 抛出的 `AbortError`(name 检测)→ 同样标记 `interrupted`
1286
+ - 中断时 `flushBuf()` 输出残留缓冲,打印灰色 `[interrupted]` 提示
1287
+ - 返回值不变(`{ content, usage, tokensShown }`),`content` 为已生成的部分内容
1288
+
1289
+ **`src/repl/repl.ts`**:
1290
+ - 新增类属性 `streamAbortController: AbortController | null` + `_escHandler: ((d: Buffer) => void) | null`
1291
+ - 新增 `setupStreamInterrupt()` 方法:创建 `AbortController`,`process.stdin.resume()`(绕过 `rl.pause()` 暂停),注册原始字节监听器检测纯 ESC(`0x1b`,单字节,区别于 ESC 序列)和 Ctrl+C(`0x03`)
1292
+ - 新增 `teardownStreamInterrupt()` 方法:移除监听器,`process.stdin.pause()`,清空引用
1293
+ - SIGINT 处理器最顶部新增分支:`streamAbortController` 非空时 `abort()` 并 return,不退出程序
1294
+ - `handleChatSimple()` 流式分支和 tee streaming 分支均用 `setupStreamInterrupt()`/`teardownStreamInterrupt()` 包裹 + `signal` 传入
1295
+
1296
+ **版本与收尾**
1297
+ - `src/core/constants.ts`:VERSION `0.1.26` → `0.1.30`(合并前几次 bump)
1298
+ - `package.json`:version `0.1.29` → `0.1.30`
1299
+
1300
+ ---
1301
+
1302
+ ## 本轮开发完成记录(2026-03-01,v0.1.25 v0.1.26)
1303
+
1304
+ ### 新增功能:Context 自动管理 + 测试报告 + 脚手架
1305
+
1306
+ **功能 1:Context 自动管理**
1307
+ - `src/core/constants.ts`:新增 `CONTEXT_PRESSURE_THRESHOLD`(0.8) / `CONTEXT_WARNING_THRESHOLD`(0.6)
1308
+ - `src/config/schema.ts`:新增 `autoCompact: z.boolean().default(true)` 配置项
1309
+ - `src/repl/repl.ts`:新增 `estimateTokens()`(字符估算 token)、`estimateConversationTokens()`(system prompt + messages 总估算)、`getContextWindowSize()`、`checkContextPressure()`(超 80% 自动 compact)
1310
+ - 在 `handleChatSimple()` 和 `handleChatWithTools()` 末尾自动调用 `checkContextPressure()`
1311
+ - `/status` 命令新增 `Context%` 行:显示估算 token / context window(绿色 <60%,黄色 60-80%,红色 >80%)
1312
+
1313
+ **功能 2:`run_tests` 工具 + `/test` 命令**
1314
+ - 新增 `src/tools/builtin/run-tests.ts`:
1315
+ - 自动检测项目类型:Maven → `mvn test`、Gradle → `gradle test`、npm → `npm test`、pytest、cargo、go test
1316
+ - JUnit XML 解析:扫描 `target/surefire-reports/*.xml`,提取 testsuite 属性 + 失败用例详情
1317
+ - 通用文本解析:正则匹配 Maven/Jest/pytest/cargo/go 格式的测试结果摘要
1318
+ - 彩色终端报告(直接 `console.log()`,绕过 executor 截断)
1319
+ - 返回 Markdown 结构化报告给 AI
1320
+ - 支持 `command`(自定义命令)和 `filter`(测试名过滤)参数
1321
+ - `src/tools/registry.ts`:注册 `runTestsTool`
1322
+ - `src/tools/types.ts`:`getDangerLevel()` 中 `run_tests` `safe`
1323
+ - `src/core/constants.ts`:`SUBAGENT_ALLOWED_TOOLS` 新增 `run_tests`,新增 `TEST_TIMEOUT = 300_000`
1324
+ - `/test` REPL 命令:快捷方式调用 `executeTests()`
1325
+
1326
+ **功能 3:`/scaffold` 脚手架命令**
1327
+ - `src/repl/commands/index.ts`:新增 `/scaffold <description>` 命令
1328
+ - 构建结构化 prompt 通过 `ctx.sendAsChat()` 注入为用户消息 → AI 使用现有工具(bash/write_file/spawn_agent)自动创建项目骨架
1329
+ - `src/repl/repl.ts`:新增 `sendAsChat(message)` 方法——添加 user message + 触发 handleChatWithTools
1330
+ - CommandContext 新增 `estimateConversationTokens`、`getContextWindowSize`、`sendAsChat` 三个回调
1331
+
1332
+ **版本与收尾**
1333
+ - `src/core/constants.ts`:VERSION `0.1.25` `0.1.26`
1334
+ - `package.json`:version 同步
1335
+ - `src/repl/renderer.ts`:/about 工具计数 15→16,命令计数 26→28,新增 3 条特性(Context 管理、run_tests、/scaffold
1336
+
1337
+ ---
1338
+
1339
+ ## 本轮开发完成记录(2026-02-28,v0.1.24 v0.1.25)
1340
+
1341
+ ### 新增功能:Tab 自动补全 + 流式 Token 计数
1342
+
1343
+ **功能 1:Tab 自动补全**
1344
+ - `src/repl/repl.ts`:`readline.createInterface` 注入 `completer` 回调
1345
+ - 新增 `completeInput(line)` 方法:根据输入上下文分发补全逻辑
1346
+ - 命令名补全:`/pro<TAB>` → `/provider`(内置 + 自定义命令)
1347
+ - 子命令参数:`/provider <TAB>` → provider 列表;`/model <TAB>` → 模型列表;`/checkpoint <TAB>` → save/restore/list/delete 等
1348
+ - `@` 文件路径补全:`@src/re<TAB>` `@src/repl/`(递进目录补全,跳过隐藏文件)
1349
+ - 新增 `completeFilePath(partial)` 辅助方法:`readdirSync` + `statSync` 扫描目录,目录追加 `/` 后缀
1350
+
1351
+ **功能 2:流式 Token 计数(内联显示)**
1352
+ - `src/repl/renderer.ts`:`renderStream()` 签名扩展 `showTokens?` + `sessionTotal?`
1353
+ - 流结束后在 `\n\n` 之前立即内联显示 token 计数
1354
+ - 有精确 usage(OpenAI/Gemini)→ 显示完整 `📊 in X + out Y = Z tokens │ session total: N`
1355
+ - 无精确 usage(Claude streaming)→ 基于字符数估算 `📊 ~X output tokens (estimated)`
1356
+ - 返回值新增 `tokensShown: boolean` 标志,防止 REPL 重复调用 `renderUsage()`
1357
+ - `src/repl/repl.ts`:`handleChatSimple()` + tee 流式路径传入 `showTokens`/`sessionTotal`,条件跳过后续 `renderUsage()`
1358
+
1359
+ **Bug 修复:DeepSeek contextWindow**
1360
+ - `src/providers/deepseek.ts`:`deepseek-chat`(V3)contextWindow 65536 → 131072(128K),与官方 API 文档一致。`deepseek-reasoner`(R1)保持 65536(64K)。
1361
+
1362
+ **安全加固:web_fetch DNS SSRF 二次校验**
1363
+ - `src/tools/builtin/web-fetch.ts`:新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名
1364
+ - 域名解析到私有 IPRFC1918/loopback/link-local)时阻断请求
1365
+ - 初始 URL redirect 目标均校验
1366
+ - IP 字面量跳过 DNS 解析(已由 `isPrivateHost()` 覆盖)
1367
+ - DNS 解析失败不拦截,让后续 fetch 自然报错
1368
+
1369
+ **版本与收尾**
1370
+ - `src/core/constants.ts`:VERSION `0.1.24` → `0.1.25`
1371
+ - `package.json`:version 同步
1372
+ - `src/repl/renderer.ts`:/about 新增 2 条特性条目
1373
+
1374
+ ---
1375
+
1376
+ ## 本轮开发完成记录(2026-02-28,v0.1.22 v0.1.23)
1377
+
1378
+ ### P1 全部 5 个功能实现
1379
+
1380
+ **背景**:P0 全部完成后,进入 P1 重要差距功能开发。一次性实现全部 5 项。
1381
+
1382
+ ### 功能 1:`/copy` 剪贴板支持
1383
+
1384
+ **实现**:`src/repl/commands/index.ts`
1385
+ - `copyToClipboard()` 辅助函数:跨平台调用原生命令(Windows: `clip` / macOS: `pbcopy` / Linux: `xclip -selection clipboard`,fallback `xsel --clipboard --input`)
1386
+ - 通过 `execSync` 管道写入,无外部 npm 依赖
1387
+ - `CommandContext` 新增 `getLastResponse: () => string`
1388
+ - `repl.ts` ctx 注入:`getLastResponse: () => lastResponseStore.content`
1389
+
1390
+ ### 功能 2:`/cost` Token 用量统计
1391
+
1392
+ **实现**:`src/repl/commands/index.ts`
1393
+ - 显示 session 累计 input/output/total tokens + provider/model/消息数
1394
+ - `/cost reset` 重置计数器
1395
+ - 使用已有 `ctx.getSessionTokenUsage()` `ctx.resetSessionTokenUsage()`
1396
+
1397
+ ### 功能 3:`/init` 项目初始化
1398
+
1399
+ **实现**:`src/repl/commands/index.ts`
1400
+ - `SCAN_SKIP_DIRS` Set(node_modules、.git、dist 等 20+ 目录)
1401
+ - `ProjectInfo` 接口 + `scanDirTree()` 递归目录树(深度 4,每层 30 项)
1402
+ - `scanProject()` 检测项目类型(package.json/Cargo.toml/pyproject.toml/go.mod 等)
1403
+ - `buildInitPrompt()` 构造 AI 提示词,要求生成结构化 AICLI.md
1404
+ - `CommandContext` 新增 `chatOnce(prompt, options?)` 方法
1405
+ - `repl.ts` ctx 实现:调用 provider.chat() 非流式,temperature=0.3
1406
+ - 已存在 AICLI.md 时需 `/init --force` 覆盖
1407
+
1408
+ ### 功能 4:Agent Skills 系统
1409
+
1410
+ **新文件**:
1411
+ - `src/skills/types.ts`:`SkillMeta`(name/description/tools?)、`Skill`(meta/content/filePath)接口;`parseSimpleYaml()`(regex key:value)、`parseYamlArray()`(`[a, b, c]` 格式)、`parseSkillFile()`(读取 .md → 提取 YAML frontmatter → 返回 Skill)
1412
+ - `src/skills/manager.ts`:`SkillManager` 类——`loadSkills()`(扫描 skillsDir,自动创建目录)、`activate(name)` / `deactivate()`、`getActivePromptContent()` / `getActiveToolFilter()`(返回工具名 Set)
1413
+
1414
+ **集成**(`src/repl/repl.ts`):
1415
+ - `private skillManager: SkillManager | null` 字段
1416
+ - `start()` 中初始化 SkillManager、加载技能、显示数量
1417
+ - `buildCurrentSystemPrompt()` 注入激活技能的 content(在项目上下文之后,plan mode 之前)
1418
+ - `handleChatWithTools()` 工具过滤:Plan Mode > Skill 白名单 > 全部工具
1419
+ - `refreshPrompt()` 显示 `[skill:name]` 洋红色标记
1420
+
1421
+ **命令**(`/skill`):
1422
+ - `/skill` 或 `/skill list`:列出所有技能
1423
+ - `/skill <name>`:激活指定技能
1424
+ - `/skill off`:停用当前技能
1425
+ - `/skill reload`:重新扫描技能目录
1426
+
1427
+ **常量**:`src/core/constants.ts` 新增 `SKILLS_DIR_NAME = 'skills'`
1428
+
1429
+ ### 功能 5:多文件编辑预览(批量确认)
1430
+
1431
+ **修改**:
1432
+ - `src/tools/types.ts`:新增 `isFileWriteTool(name)` 判断 write_file / edit_file
1433
+ - `src/tools/executor.ts`:`executeAll()` 重构为三组分流:
1434
+ 1. `safeCalls`(safe 级别)→ 先执行(保证 mkdir 等前置操作完成)
1435
+ 2. `fileWriteCalls`(isFileWriteTool && write)→ 单个走原有确认,2+ 个走批量
1436
+ 3. `otherCalls`(剩余 write/destructive)→ 逐个原有确认流程
1437
+ - 新增 `executeBatchFileWrites(calls)`:展示编号列表 + diff 预览 → `batchConfirm()`
1438
+ - 新增 `batchConfirm(count)`:`[a]pprove all / [r]eject all / [1,3,5] approve specific`
1439
+ - 使用 `rl.once('line')` 读整行(与 `confirm()` 模式一致)
1440
+ - 解析逗号分隔数字编号,返回 `'all' | 'none' | Set<number>`
1441
+ - 支持 Ctrl+C 取消(复用 `cancelConfirmFn`)
1442
+
1443
+ ### 架构变更
1444
+ - `src/repl/commands/index.ts`:CommandContext 新增 `getLastResponse` / `chatOnce` / `refreshPrompt` / `getSkillManager`;新增 /copy /cost /init /skill 共 4 个命令;/help 更新
1445
+ - `src/repl/repl.ts`:SkillManager 集成 + 4 个新 ctx 方法
1446
+ - `src/repl/renderer.ts`:`/about` REPL 命令 1923,新增 3 条特性(Agent Skills / /init / 多文件编辑预览)
1447
+
1448
+ ### 版本与发布
1449
+ - `src/core/constants.ts`:VERSION `0.1.22` `0.1.23`
1450
+ - `package.json`:version `0.1.22` `0.1.23`
1451
+
1452
+ ---
1453
+
1454
+ ## 本轮开发完成记录(2026-02-28,v0.1.23 v0.1.24)
1455
+
1456
+ ### P2 全部 5 功能实现
1457
+
1458
+ **功能 1:Hooks 系统(pre/post tool execution)**
1459
+ - 新增 `src/tools/hooks.ts`:`ToolHookConfig` 接口 + `runHook(template, vars)` 函数(模板变量替换 execSync 5s 超时,失败 stderr 警告)
1460
+ - `src/config/schema.ts`:新增 `hooks` 可选字段(`preToolExecution`/`postToolExecution`)
1461
+ - `src/tools/executor.ts`:新增 `setConfig()` 方法;`execute()` 中 getDangerLevel 后调 pre hook,工具完成/失败后调 post hook
1462
+
1463
+ **功能 2:Permission Rules(基于规则的工具权限)**
1464
+ - 新增 `src/tools/permissions.ts`:`PermissionRule` 接口 + `checkPermission()` 纯函数(首匹配规则,tool 支持 `*` 通配,when 条件含 dangerLevel/pathPattern)
1465
+ - `src/config/schema.ts`:新增 `permissionRules` 数组 + `defaultPermission`(默认 `confirm`)
1466
+ - `src/tools/executor.ts`:execute() 中 getDangerLevel 后调 checkPermission → deny 直接拒绝 / auto-approve 跳过 confirm / confirm 走原有流程
1467
+
1468
+ **功能 3:Checkpointing(会话检查点)**
1469
+ - `src/session/session.ts`:新增 `CheckpointMeta` 接口(name/messageIndex/timestamp)、`checkpoints` 数组、4 个方法(createCheckpoint/restoreCheckpoint/listCheckpoints/deleteCheckpoint)、toJSON/fromJSON 更新
1470
+ - `src/repl/commands/index.ts`:新增 `/checkpoint` 命令(save/restore/list/delete 子命令)
1471
+
1472
+ **功能 4:`/review` 代码审查**
1473
+ - `src/repl/commands/index.ts`:新增 `buildReviewPrompt()` 辅助函数(中文结构化审查 prompt)+ `/review` 命令(`--staged`/`--detailed`,diff 8000 字截断保头尾)
1474
+
1475
+ **功能 5:Custom Commands(用户自定义命令)**
1476
+ - 新增 `src/repl/custom-commands.ts`:`CustomCommandManager` 类(loadCommands/listCommands/getCommand)+ `expandTemplate()` 模板变量展开(`{{input}}/{{file:path}}/{{git-diff}}/{{git-context}}`)
1477
+ - `src/core/constants.ts`:新增 `CUSTOM_COMMANDS_DIR_NAME = 'commands'`
1478
+ - `src/repl/repl.ts`:CustomCommandManager 集成(初始化 + handleCommand fallback + ctx 方法)
1479
+ - `src/repl/commands/index.ts`:新增 `/commands` 命令(list/reload)
1480
+
1481
+ **版本与收尾**
1482
+ - `src/core/constants.ts`:VERSION `0.1.23` `0.1.24`
1483
+ - `package.json`:version 同步
1484
+ - `src/repl/renderer.ts`:/about 命令计数 23 → 26,新增 5 条 P2 特性条目
1485
+ - 所有 P2 路线图条目标记为已完成
1486
+
1487
+ ---
1488
+
1489
+ ## 本轮开发完成记录(2026-02-27,v0.1.21 → v0.1.22)
1490
+
1491
+ ### 新增功能:Sub-Agent 子代理系统(`spawn_agent` 工具)
1492
+
1493
+ **背景**:P0 路线图最后一个核心功能。允许主 AI 将复杂子任务委派给独立子代理执行,子代理拥有隔离对话和 agentic 循环。
1494
+
1495
+ **实现**:
1496
+ - 新增 `src/tools/builtin/spawn-agent.ts`:
1497
+ - `spawnAgentContext` 共享上下文对象(遵循 streamToFileContext 模式,由 repl.ts 注入)
1498
+ - `SubAgentExecutor` 类:子代理专用工具执行器,write 自动确认、destructive 直接阻止、带 ` ┃ ` 前缀的嵌套终端输出
1499
+ - 独立 agentic 循环:chatWithTools → executeAll → buildToolResultMessages,最多 max_rounds 轮
1500
+ - 子代理 system prompt:继承父级 system prompt + 子代理模式说明(任务规则、工具限制)
1501
+ - `src/core/constants.ts`:新增 `SUBAGENT_DEFAULT_MAX_ROUNDS`(10) / `SUBAGENT_MAX_ROUNDS_LIMIT`(15) / `SUBAGENT_ALLOWED_TOOLS`(11个工具白名单)
1502
+ - `src/tools/registry.ts`:新增 `unregister(name)` 方法(供子代理创建过滤工具集);注册 `spawnAgentTool`
1503
+ - `src/tools/types.ts`:`getDangerLevel` 中 `spawn_agent` → `safe`
1504
+ - `src/repl/repl.ts`:handleChatWithTools 中注入 spawnAgentContext(provider/model/systemPrompt/modelParams/configManager)
1505
+
1506
+ **工具参数**:
1507
+ - `task`(string, required):子代理任务描述,包含所有必要上下文
1508
+ - `max_rounds`(number, optional, default 10, 限制 1-15):最大工具调用轮次
1509
+
1510
+ **安全设计**:
1511
+ - 递归防护:子代理工具集不含 `spawn_agent`
1512
+ - 用户交互隔离:子代理无 `ask_user`、`save_memory`、`save_last_response`
1513
+ - 破坏性操作阻止:`destructive` 级别工具调用直接返回错误
1514
+ - 写操作自动确认:`write` 级别无需用户确认(主 AI 已明确委派)
1515
+ - 轮次上限:max_rounds 限制在 1-15,防止无限循环
1516
+
1517
+ **终端输出**:子代理工具调用和结果使用 ` ` 前缀 + 箱形边框(┏/┗),清晰区分父级与子代理输出
1518
+
1519
+ ### 架构变更
1520
+ - `src/repl/renderer.ts`:`/about` 工具计数 14 → 15,新增 `spawn_agent` 条目和 Sub-Agent 特性条目
1521
+
1522
+ ---
1523
+
1524
+ ## 本轮开发完成记录(2026-02-25,v0.1.18 v0.1.20)
1525
+
1526
+ ### Bug 修复:Kimi XML 伪工具调用(v0.1.19)
1527
+
1528
+ **背景**:Kimi-k2 在 OpenAI function calling 接口下存在已知缺陷——当生成内容较长时,会在回复文本中输出 `<write_file>...</write_file>` 等 XML 格式的伪工具调用标签,而非通过 API 的 `tool_calls` 机制。ai-cli 无法捕获文本中的 XML,导致文件写入等操作静默丢失,任务失败。
1529
+
1530
+ **根本原因**:Kimi-k2 训练时混合了旧版 XML 工具调用格式,当模型注意力涣散(长内容生成)时退化为文本输出工具调用,而非通过结构化 `tool_calls` 数组返回。
1531
+
1532
+ **修复**:`src/providers/kimi.ts` 覆写 `chatWithTools()`,叠加两道防线:
1533
+
1534
+ - **方案 B(预防)**:在 `request.systemPrompt` 末尾追加 `KIMI_TOOL_CALL_REMINDER`——中文强制规范提示,指示 Kimi 必须使用 API function calling,禁止文本中输出 XML 工具标签
1535
+ - **方案 A(兜底)**:响应返回后,若 `content` 中含有 `<tool_name>...</tool_name>` 模式,调用私有 `parseXmlToolCalls()` 方法解析并转换为 `ToolCall[]`,注入到 agentic 执行循环
1536
+
1537
+ **`parseXmlToolCalls()` 设计要点**:
1538
+ - 使用 `indexOf` 而非正则,避免 14KB+ Markdown 内容时的正则回溯性能问题
1539
+ - 快速路径:`!content.includes('</')` 直接跳过无 XML 的响应
1540
+ - 按已知参数名(`tool.parameters` 的 key)逐一提取参数值,安全可靠
1541
+ - tool call id 格式 `xml-fallback-{ts}-{idx}`,便于调试日志追踪
1542
+ - stderr 输出警告提示用户发生了 XML 伪调用救援
1543
+
1544
+ **架构约束**:仅在 `kimi.ts` 中覆写,不影响 DeepSeek / Zhipu / Claude / Gemini 等其他 Provider
1545
+
1546
+ ### `/about` 特性列表更新(v0.1.20)
1547
+
1548
+ **变更**:`src/repl/renderer.ts` → `printAbout()` 主要特性列表新增 4 项、更新 1 项:
1549
+ - 项目上下文文件说明补充「三层级:全局/项目/子目录」
1550
+ - Plan Mode(`/plan` 进入只读规划 `/plan execute` 切回)
1551
+ - Headless 模式(`-p` 单轮非交互 + stdin 管道 + `--json`)
1552
+ - `/compact` 上下文压缩(摘要替换旧消息,保留最近 4 条)
1553
+ - Kimi 工具调用可靠性增强(XML 伪调用自动检测并转换,v0.1.19)
1554
+
1555
+ ---
1556
+
1557
+ ## 本轮开发完成记录(2026-02-25,v0.1.17v0.1.18)
1558
+
1559
+ ### 新增功能:`/compact` 上下文压缩
1560
+
1561
+ **背景**:长对话后 context window 接近上限,需要将旧消息压缩为摘要继续对话。
1562
+
1563
+ **实现**:
1564
+ - `src/session/session.ts`:新增 `compact(summaryMsg, ackMsg, keepLast)` 方法——将旧消息替换为 user/assistant 摘要对,保留末尾 `keepLast` 条消息,返回移除数量
1565
+ - `src/repl/repl.ts`:新增私有 `compactSession(instruction?)` 方法:
1566
+ - 常量 `COMPACT_KEEP_LAST = 4`、`MIN_MESSAGES_TO_COMPACT = 6`
1567
+ - 调用当前 provider `chat()` 非流式生成摘要(temperature=0.3,maxTokens≤4096)
1568
+ - 构造 user/assistant 摘要消息对,调用 `session.compact()`
1569
+ - 注入 ctx:`compactSession`
1570
+ - `src/repl/commands/index.ts`:`CommandContext` 新增 `compactSession` 回调;新增 `/compact [instruction]` 命令;更新 `/help`
1571
+
1572
+ ### 新增功能:Headless 非交互模式(`-p` flag)
1573
+
1574
+ **背景**:CI/CD 脚本、管道场景需要单轮非交互式调用,`echo "code" | aicli -p "review"` 即可。
1575
+
1576
+ **实现**:
1577
+ - `src/index.ts`:
1578
+ - `--provider` 移除 `-p` 短选项(仅保留长形式),将 `-p` 分配给 `--prompt <text>`
1579
+ - 新增 `--system <prompt>` 覆盖 system prompt
1580
+ - 新增 `--json` flag:输出 JSON `{content, provider, model, usage}` 并退出
1581
+ - `action()` handler:`options.prompt !== undefined` 时路由到 `runHeadless()`
1582
+ - `readStdin()`:非 TTY 时读取完整 stdin,TTY 时返回空串
1583
+ - `runHeadless()`:初始化 provider 拼接 stdin+prompt 调用 chat/chatStream → 输出 → exit(0)
1584
+ - 流式模式:直接 `process.stdout.write(delta)` 无 "Assistant:" 前缀,管道友好
1585
+ - `--json` 模式:强制非流式,输出格式化 JSON
1586
+
1587
+ ### 新增功能:Plan Mode 规划模式(`/plan`)
1588
+
1589
+ **背景**:对标 Gemini CLI,让 AI 先做只读分析规划,用户确认后再执行写操作。
1590
+
1591
+ **实现**:
1592
+ - `src/core/constants.ts`:
1593
+ - `PLAN_MODE_READONLY_TOOLS`:只读工具白名单 Set(read_file / list_dir / grep_files / glob_files / web_fetch / google_search / ask_user / write_todos)
1594
+ - `PLAN_MODE_SYSTEM_ADDON`:注入 system prompt 末尾的中文说明(告知 AI 当前处于只读规划模式及允许/禁用工具)
1595
+ - `src/repl/repl.ts`:
1596
+ - 新增 `private planMode = false` 属性
1597
+ - `refreshPrompt()`:plan mode 时显示 `[deepseek][PLAN] >(黄色 PLAN 标记)`
1598
+ - `buildCurrentSystemPrompt()`:plan mode 时追加 `PLAN_MODE_SYSTEM_ADDON`(最后段)
1599
+ - `handleChatWithTools()`:plan mode 时过滤 toolDefs 为只读白名单,AI 不可见禁用工具
1600
+ - ctx 注入:`getPlanMode` / `setPlanMode`
1601
+ - `src/repl/commands/index.ts`:`CommandContext` 新增 `getPlanMode` / `setPlanMode`;新增 `/plan` 命令(子命令:enter/execute/exit/cancel/status);更新 `/help`
1602
+
1603
+ ### 架构变更
1604
+ - `src/repl/renderer.ts`:`/about` REPL 命令计数 17 → 19,命令列表新增 `/compact` 和 `/plan`
1605
+ - `src/repl/commands/index.ts`:命令注册总数 17 19
1606
+
1607
+ ---
1608
+
1609
+ ## 本轮开发完成记录(2026-02-22,v0.1.12 v0.1.13)
1610
+
1611
+ ### 新增功能:开发状态交接系统(Dev State Handoff)
1612
+
1613
+ **背景**:用户在开发过程中频繁切换 AI 模型,切换后新模型对之前的开发上下文一无所知,导致工作不连续。
1614
+
1615
+ **实现**:
1616
+ - 新增 `src/repl/dev-state.ts` 模块:快照提示词(SNAPSHOT_PROMPT)、save/load/clear 函数、`sessionHasMeaningfulContent` 会话内容判断
1617
+ - 新增 `DEV_STATE_FILE_NAME` 常量(`src/core/constants.ts`)
1618
+ - `/provider` 和 `/model` 命令重构:
1619
+ - **同值检测**:选择相同 provider/model 时显示 "Already using X",不触发任何变更(不创建新 session、不生成快照)
1620
+ - **自动快照**:真正切换时,调用当前 AI 非流式生成 7 段式结构化快照 → 保存到 `~/.aicli/dev-state.md`
1621
+ - `buildCurrentSystemPrompt()` 注入 dev-state:日期时间 → 持久记忆 → **开发状态快照** → 项目上下文
1622
+ - `/clear` 和 `/session new` 自动清除 dev-state.md,防止旧快照污染无关工作
1623
+ - 启动时若存在 dev-state.md,显示 `🔄 Dev state handoff: loaded (N chars)`
1624
+
1625
+ ### Bug 修复:maxTokens 未传递导致大内容生成被截断
1626
+
1627
+ **问题**:用户使用智谱 glm-4-plus 生成 80 题模考试卷,在第 73 题处被截断。根因:`DEFAULT_MAX_TOKENS = 8192` 定义在 constants.ts 中但从未被使用,`getModelParams()` 在用户未配置 modelParams 时返回 `{}`,OpenAI 兼容 provider 收到 `max_tokens: undefined` 后由 API 服务端默认值(~4096)兜底。
1628
+ **修复**:`getModelParams()` 中为 `maxTokens` 添加 `?? DEFAULT_MAX_TOKENS` 兜底,让已定义的常量真正生效。所有 provider 统一受益。
1629
+
1630
+ ### 版本与发布
1631
+ - `src/core/constants.ts`:VERSION `0.1.11` `0.1.13`
1632
+ - `package.json`:version `0.1.11` `0.1.13`
1633
+
1634
+ ---
1635
+
1636
+ ## 本轮开发完成记录(2026-02-22,安全加固版 0.1.10)
1637
+
1638
+ ### 背景
1639
+ ai-cli 全量代码进行安全审计,共发现并修复 **3 个高危、4 个中危、4 个低危**漏洞,版本从 0.1.9 升至 0.1.10 发布。
1640
+
1641
+ ### 修复:高危(H)
1642
+
1643
+ **H1 — `executor.ts`:`write` 级别操作未触发用户确认**
1644
+ - 根本原因:`execute()` 条件分支逻辑缺陷,`write` 级别跳过了 `confirm()` 调用
1645
+ - 修复:重构三路分支;`write` → printToolCall + printDiffPreview + confirm;`destructive` → confirm + printToolCall;`safe` → printToolCall 后直接执行
1646
+
1647
+ **H2 — `types.ts`:`getDangerLevel()` bash 危险命令覆盖不完整**
1648
+ - 漏洞:`rm -r`(不带 `-f`)、`rm file.txt`(无标志)、`--recursive`/`--force` 长标志均被识别为 `safe`;`del + mkdir` 组合绕过 destructive 判断;PowerShell 写操作(Set-Content、Out-File 等)未分类
1649
+ - 修复:扩展正则覆盖上述所有情形;移除错误的 `del+mkdir` 豁免;新增 PowerShell 写操作 → `write`
1650
+
1651
+ **H3 — `registry.ts` / `schema.ts` / `repl.ts`:插件系统无权限门控,任意 JS 文件自动加载执行**
1652
+ - 漏洞:`loadPlugins()` 对 `~/.aicli/plugins/` 下所有 `.js` 文件无条件加载,权限等同当前用户
1653
+ - 修复:
1654
+ - `schema.ts` 新增 `allowPlugins: z.boolean().default(false)`(默认关闭)
1655
+ - `loadPlugins(pluginsDir, allowPlugins)` 新增 flag 参数;未启用时打印警告并返回,不加载任何插件
1656
+ - 启用时在 stderr 打印完整插件路径作为安全日志
1657
+ - `repl.ts` 传入 `this.config.get('allowPlugins')` 控制
1658
+
1659
+ ### 修复:中危(M)
1660
+
1661
+ **M4 `web-fetch.ts`:SSRF(服务端请求伪造)**
1662
+ - 漏洞:AI 可指定任意 URL,攻击者可让工具访问内网服务(如 192.168.x.x、metadata API 等)
1663
+ - 修复:新增 `isPrivateHost()` 函数,拦截 localhost / 0.0.0.0 / ::1 / fe80:: / 10.x / 127.x / 172.16-31.x / 192.168.x / 169.254.x;对初始 URL 和重定向后的最终 URL 均校验
1664
+
1665
+ **M5 `types.ts`:PowerShell 写操作未分类为 `write`**(并入 H2 修复)
1666
+
1667
+ **M6 — `read-file.ts`:大文件读取触发 OOM**
1668
+ - 漏洞:`readFileSync` 在 size 检查之前执行,超大文件直接撑爆堆内存
1669
+ - 修复:用 `statSync` 在读取前获取文件大小,超过 10MB(`MAX_FILE_BYTES`)直接报错返回
1670
+
1671
+ **M8 `read-file.ts`:读取敏感配置文件(API KeySSH 私钥等)无警告**
1672
+ - 修复:`getSensitiveWarning()` 检测以下路径:`~/.aicli/config.json`、`.env*`、`~/.ssh/id_*`、`~/.aws/credentials`;匹配时在文件内容前追加醒目警告(不阻断,保留调试能力)
1673
+
1674
+ ### 修复:低危(L)
1675
+
1676
+ **L8 — `bash.ts`:`fixWindowsDeleteCommand` 路径注入**
1677
+ - 漏洞:pathValue 直接插入 `cmd /c rmdir /s /q "..."` 字符串,含 `"` 的路径可逃逸引号
1678
+ - 修复:`const safePath = pathValue.replace(/"/g, '\\"');`
1679
+
1680
+ **L9 — `bash.ts`:timeout AI 任意控制,无上限**
1681
+ - 修复:`Math.min(Math.max(Number(args['timeout'] ?? 30_000), 1000), 300_000)`,最小 1s,最大 300s
1682
+
1683
+ **L10 — `bash.ts`:`updateCwdFromCommand` 正则误匹配字符串内的 `cd`**
1684
+ - 修复:更新正则为 `/(?:^|[;&|])\s*cd\s+(['"]?)([^\s;&|'"]+)\1/g`,捕获组 2 取路径(去除引号),仅匹配命令边界处的 `cd`
1685
+
1686
+ **L11 `bash.ts`:`persistentCwd` 全局状态注释缺失**
1687
+ - 修复:添加架构说明注释,提示 GUI session 扩展时需重构为 per-session 状态
1688
+
1689
+ ### 版本与发布
1690
+ - `src/core/constants.ts`:VERSION `0.1.9` `0.1.10`
1691
+ - `package.json`:version `0.1.9` → `0.1.10`
1692
+ - `npm publish` `jinzd-ai-cli@0.1.10`(https://www.npmjs.com/package/jinzd-ai-cli)
1693
+
1694
+ ---
1695
+
1696
+ ## 本轮开发完成记录(2026-02-23)
1697
+
1698
+ ### 新增功能
1699
+ - **Gemini 完整支持**:`GeminiProvider` 接入代理(undici ProxyAgent)、baseUrl 透传、array 参数 `items` schema 修复(解决 400 Bad Request)、429/网络错误中文提示
1700
+ - **代理配置**:`config.json` 新增 `proxy` 字段;`src/index.ts` `setupProxy()` 读取配置+环境变量(优先级:env > config);设置向导新增 `Configure proxy` 入口(支持设置/修改/清除)
1701
+ - **`stream_to_file` 工具上线**:注册到 `ToolRegistry`;`repl.ts` `executeAll` 前注入 provider/model/messages/systemPrompt 上下文;`getDangerLevel` 添加 `write` 级别
1702
+ - **`/config` REPL 命令**:REPL 内直接调用配置向导,无需退出;`repl.ts` 的 `runSetupWizard` 回调临时恢复 readline 状态供 inquirer 使用
1703
+ - **README.md**:创建完整 npm 包文档(Provider 表、工具表、命令表、代理配置、项目上下文说明)
1704
+ - **pkg 打包零警告**:`scripts/patch-sqlite.mjs` 在打包前替换 undici 内部 `require("sqlite")` 为 IIFE stub
1705
+ - **版本升至 0.1.4**,发布至 npmjs
1706
+
1707
+ ### 架构变更
1708
+ - `src/config/schema.ts`:新增 `proxy?: string` 字段
1709
+ - `src/repl/commands/index.ts`:`CommandContext` 新增 `runSetupWizard` 回调;新增 `/config` 命令;工具/命令计数更新(11工具、15命令)
1710
+ - `src/repl/setup-wizard.ts`:`runFull()` 新增代理配置选项;`input` from `@inquirer/prompts` 用于代理 URL 输入
1711
+
1712
+ ## 本轮开发完成记录(2026-02-22)
1713
+
1714
+ ### 新增功能
1715
+ - **全局超时统一为 300s**:`~/.aicli/config.json` 中 `timeouts.deepseek` 60000→300000、`timeouts.kimi` 600000→300000;`modelParams` 中所有模型的 `timeout` 统一为 300000(moonshot-v1-8k 新增 timeout 字段)
1716
+ - **跨 session 历史搜索 `/search <keyword>`**:`SessionManager.searchMessages()` 遍历全部历史 JSON,不区分大小写全文匹配,每个 session 最多返回 3 条带上下文的匹配片段,全局最多 20 个 session;REPL 新增 `/search` 命令,结果按 updated 倒序展示,提示用 `/session load <id>` 加载
1717
+ - **`run_interactive` 参数容错可观测化**:`args` 或 `stdin_lines` 传入错误类型(string 非 array)时,双路输出警告:① `process.stderr.write`(终端可见);② 工具返回值前缀追加警告文本(AI 可在工具结果中看到),便于持续观察 AI 实际调用行为
1718
+ - **`/about` 命令升级**:新增工具列表(10个完整)、REPL 命令列表(14个)、主要特性区块;移除仓库地址(待迁移至 GitHub)
1719
+ - **版本号升级至 0.1.2**,`constants.ts` `package.json` 保持同步
1720
+ - **发布至 npmjs**:`npm publish` → `jinzd-ai-cli@0.1.2`(https://www.npmjs.com/package/jinzd-ai-cli)
1721
+
1722
+ ## 本轮开发完成记录(2026-02-22)
1723
+
1724
+ ### 新增功能
1725
+ - **层级上下文文件系统**:三层级(全局/项目/子目录)上下文自动发现与拼接,取代旧的单文件加载。新增 `getGitRoot()` 获取 git 仓库根目录,`findContextFile()` 按候选列表查找,`loadHierarchicalContext()` 三层加载。`ContextLayer` 接口导出供命令模块使用。`/context` 命令显示各层详情,`/status` 显示层级摘要。
1726
+ - **持久记忆系统**:新增 `save_memory` 工具(`src/tools/builtin/save-memory.ts`),追加式写入 `~/.aicli/memory.md`,自动带时间戳。`repl.ts` 新增 `loadMemoryContent()` 方法,在 `buildCurrentSystemPrompt()` 中注入 `# Persistent Memory` 段落。启动时显示 `📝 Memory loaded: X entries (Y chars)`。超过 10,000 字符时截取末尾最新部分。
1727
+
1728
+ ### 架构变更
1729
+ - `constants.ts`:新增 `CONTEXT_FILE_CANDIDATES`、`MEMORY_FILE_NAME`、`MEMORY_MAX_CHARS` 常量
1730
+ - `git-context.ts`:新增 `getGitRoot()` 导出函数
1731
+ - `repl.ts`:`contextFilePath: string | null` → `contextLayers: ContextLayer[]`;`loadProjectContext()` → `loadHierarchicalContext()`;`buildCurrentSystemPrompt()` 重构为 parts 数组拼接模式(日期时间 → 记忆 → 上下文)
1732
+ - `commands/index.ts`:`CommandContext.getContextFilePath()` → `getContextLayers()`;`/context` 和 `/status` 命令适配新数据结构
1733
+
1734
+ ## 历史开发记录(2026-02-21)
1735
+
1736
+ ### 新增功能
1737
+ - **模型上下文长度展示**:`/model` 选择器 hint 显示 `ctx:128K`;`/status` 新增 `Ctx Win` 行;启动欢迎界面 Model 行显示 `(ctx: 128K)`
1738
+ - **Claude/Gemini 工具调用(Agentic)**:`ClaudeProvider` `GeminiProvider` 均实现 `chatWithTools` + `buildToolResultMessages`,现在三大 provider 系列均支持完整 agentic 工具调用能力
1739
+ - **工具调用最终回答流式输出**:`handleChatWithTools` 最终 content 轮次改为 `chatStream` 真正流式,`streaming=true` 时打字机效果;`streaming=false` 时保持原有非流式路径
1740
+ - **Ctrl+C 取消工具确认**:`confirm()` 等待期间按 Ctrl+C 视为"按 N 取消",不再异常退出 REPL;通过 `ToolExecutor.cancelConfirm()` + SIGINT handler 协作实现
1741
+ - **System prompt 注入当前日期时间**:每次会话启动自动注入 `当前日期时间:2026年02月21日 星期六 08:30:00`,所有模型均可感知;使用手动数字拼接(不依赖 locale API,兼容 pkg 精简 ICU 环境)
1742
+ - **Kimi 长文本模型超时配置**:`~/.aicli/config.json` 中 `kimi-k2-0711-preview`、`kimi-k2-turbo-preview`、`moonshot-v1-128k` 统一设置 300000ms(5分钟)超时,适合出题等长文本生成场景
1743
+
1744
+ ### 架构变更
1745
+ - `repl.ts`:引入 `ToolCapableProvider` 接口(duck typing),`handleChatWithTools` 不再依赖 `OpenAICompatibleProvider` 具体类,Claude/Gemini 自动识别并走 agentic 路径
1746
+ - `executor.ts`:新增 `cancelConfirmFn` 私有回调 + `cancelConfirm()` 公开方法
1747
+
1748
+ ## 已解决的重大问题记录
1749
+
1750
+ | 问题 | 根本原因 | 解决方案 |
1751
+ |------|---------|---------|
1752
+ | exe 启动后发消息立即崩溃(Segfault) | `ora` 在 `@yao-pkg/pkg` Node 22 exe 中调用底层 TTY 接口触发段错误 | 完全移除 `ora`,用原生 `setInterval + stdout.write` 实现 spinner |
1753
+ | punycode 弃用警告 | Node 22 对 `punycode` 内置模块发出 DEP0040 警告 | pkg 打包时加 `--options no-deprecation` |
1754
+ | `yy` 双字符回显 | `rl.question()` 内部 echo + 手动 echo 叠加 | 改用 `setRawMode(true)` + `data` 事件读单字符,彻底不用 `rl.question()` |
1755
+ | REPL 一轮后退出 | `rl.question()` 内部调用 `rl.pause()`,readline 失去响应 | 移除 `rl.question()`,仅用 raw stdin |
1756
+ | 用户输入重复出现 | readline `terminal:true` + spinner 输出触发行缓冲区重绘 | AI 处理期间设 `rlAny.output = null` 禁止 readline 输出 |
1757
+ | Windows 中文乱码 | PowerShell 默认 GBK 编码 | bash 工具注入 `[Console]::OutputEncoding=UTF8` + `encoding:'buffer'` 手动解码 |
1758
+ | run_interactive stdin 截断 | 通过 shell 包装 spawn 时 stdin pipe 被截断 | 直接 `spawn(pythonExe, args)` 不经过任何 shell |
1759
+ | Ctrl+C 在工具确认时异常退出 | SIGINT handler 直接调用 `handleExit()`,未检查 `confirm()` 状态 | SIGINT handler 先检查 `confirming` 标志,是则 `cancelConfirm()` 取消而非退出 |
1760
+ | 日期注入在 pkg exe 中显示错误 | `toLocaleDateString/toLocaleTimeString` 依赖完整 ICU,pkg 精简版不支持 | 改为手动数字拼接:`${year}年${month}月${day}日 ${weekday} ${HH}:${mm}:${ss}` |
1761
+
1762
+ ---
1763
+
1764
+ ## 代码审查报告(2026-03-01)
1765
+
1766
+ > 全面扫描 src/ 目录 35+ TypeScript 源文件,共发现 **4 高危 + 8 中危 + 7 低危** 问题。
1767
+
1768
+ ### 🔴 高危问题(全部已修复 v0.1.33)
1769
+
1770
+ **H1 — MCP pendingRequests 内存泄漏** ✅
1771
+ - **文件**:`src/mcp/client.ts`
1772
+ - **描述**:`withTimeout` 外层超时后,`sendRequest` 内部的 pending 条目和 timer 滞留到内部超时才释放
1773
+ - **修复**:`sendRequest` 中 resolve/reject 回调内含统一 `cleanup()` 函数,任何路径(正常响应/内部超时/写入错误/外部 reject)都即时释放资源
1774
+
1775
+ **H2 readline 竞态条件:`confirming` 标志失效**
1776
+ - **文件**:`src/tools/executor.ts`、`src/tools/builtin/ask-user.ts`
1777
+ - **描述**:`confirm()`/`batchConfirm()`/`ask_user` `once('line')` 在极端 edge case 下可能二次触发
1778
+ - **修复**:`completed` 布尔守卫统一移入 `cleanup()` 内部(单一检查点);`try/catch` 包裹 `rl.once()` 注册防止 readline 已关闭时 `confirming` 卡死
1779
+
1780
+ **H3 — 工具执行错误语义混淆(isError 混用)** ✅
1781
+ - **文件**:`src/tools/executor.ts`
1782
+ - **描述**:用户取消和权限拒绝的 content 缺少语义前缀,AI 难以区分取消/拒绝/真实错误
1783
+ - **修复**:所有取消/拒绝路径 content 加 `[User cancelled]`/`[User rejected]`/`[Permission denied]` 前缀 + "Do not retry without asking" 提示
1784
+
1785
+ **H4 — MCP 断开后无恢复机制** ✅
1786
+ - **文件**:`src/mcp/client.ts` + `src/mcp/manager.ts` + `src/repl/commands/index.ts`
1787
+ - **描述**:服务器子进程意外退出后,工具可见但调用时 silent failure,需重启 CLI
1788
+ - **修复**:`McpClient.reconnect()` 清理旧状态后重新执行完整连接流程;`wrapMcpTool` execute 断线时自动尝试一次重连;`McpManager.reconnectServer()/reconnectAll()`;`/mcp reconnect [serverId]` 命令手动重连
1789
+
1790
+ ### 🟠 中危问题(全部已修复或确认安全 v0.1.34)
1791
+
1792
+ | # | 状态 | 文件 | 说明 |
1793
+ |---|------|------|------|
1794
+ | M5 | v0.1.34 | `bash.ts` | `persistentCwd` 指向已删除目录时自动回退 `process.cwd()` |
1795
+ | M6 | ✅ 已修复 | `web-fetch.ts` | 手动 redirect loop 每一跳均做 SSRF 检查 |
1796
+ | M7 | ✅ 已修复 | `run-interactive.ts` | `write()` 返回 false 时 `once('drain')` 背压处理 |
1797
+ | M8 | ✅ 已修复 | `session-manager.ts` | `catch` `stderr.write` 含文件名和错误信息 |
1798
+ | M9 | ✅ H4 附带 | `mcp/client.ts` | `killProcess()` 已有 `removeAllListeners()` |
1799
+ | M10 | ✅ 非问题 | `permissions.ts` | 使用 `.includes()` 子串匹配,非正则,无 ReDoS 风险 |
1800
+ | M11 | ✅ 已修复 | `repl.ts` | `resolve()` + `startsWith(cwd)` 路径穿越校验 |
1801
+ | M12 | v0.1.34 | `mcp/manager.ts` | `convertSchema()` 递归转换嵌套 schema;`schemaToJsonSchema()` 供三 provider 统一使用 |
1802
+
1803
+ ### 🟡 低危问题(后续改进)
1804
+
1805
+ | # | 文件 | 问题描述 |
1806
+ |---|------|---------|
1807
+ | L1 | `src/tools/builtin/run-tests.ts` | v0.1.50 已修复:`safeReadPackageJson()` 细粒度错误处理 + BOM + 框架自动检测 |
1808
+ | L2 | `src/tools/builtin/web-fetch.ts` | 恶意 HTML 大量 script 标签时正则性能下降 |
1809
+ | L3 | `src/session/session.ts` | `new Date(d.created)` 非法字符串返回 Invalid Date,比较失败 |
1810
+ | L4 | `src/tools/builtin/ask-user.ts` / `google-search.ts` | 模块级全局上下文,多会话架构下会串扰 |
1811
+ | L5 | `src/config/schema.ts` | `modelParams.timeout` 与 provider 级 `timeout` 优先级不清晰 |
1812
+ | L6 | `src/tools/executor.ts` | `call.arguments['path']` 未验证是否绝对路径,null 传入可能异常 |
1813
+ | L7 | `src/repl/repl.ts` | 主循环 `line` handler 是 async 但无整体 try/catch,未捕获异常可能停响应 |
1814
+
1815
+ ---
1816
+
1817
+ ## Claude Code 功能对比差距(2026-03-01)
1818
+
1819
+ ### ai-cli 已超越或持平 Claude Code 的功能
1820
+
1821
+ - Provider 支持(Claude Code 仅支持 Claude 系列)
1822
+ - 三层级上下文文件(Claude Code 为双层)
1823
+ - Dev State 开发状态交接
1824
+ - Agent Skills 系统
1825
+ - 企业级权限规则 + Hooks 系统
1826
+ - 主题/颜色自定义系统(dark/light/custom + 10 语义色槽,全部终端输出已迁移)
1827
+ - 项目级 MCP 配置(`.mcp.json` + 全局合并 + 来源追踪)
1828
+ - Extended Thinking 深度推理(`/think` 运行时切换 + thinking 块折叠)
1829
+ - edit_file 智能提示(匹配失败 did you mean + `ignore_whitespace` 容错模式)
1830
+
1831
+ ### 缺失功能路线图(新增)
1832
+
1833
+ #### P0 核心竞争力缺口
1834
+ - [x] **中断生成**(v0.1.30):`Escape` 键或 `Ctrl+C` 立即停止 AI 流式输出,不退出程序,显示 `[interrupted]` 后恢复提示符;已生成内容保留到 session
1835
+ - [x] **多模态输入(图片)**(v0.1.30):`@image.png 描述这张图` 语法发送图片;Claude/Gemini/OpenAI 兼容格式自动转换;10MB 大小限制;`getVisionModelHint()` 正确识别 Claude/Gemini 原生支持视觉
1836
+ - [x] **`/add-dir` 命令**(v0.1.35):运行时动态添加目录到上下文(目录树 + 文件内容,限 40K 字符)
1837
+ - [x] **并行工具调用**(v0.1.35):safe 级别工具改为 `Promise.all()` 并行执行
1838
+
1839
+ #### P1 — 重要差距(已全部完成 v0.1.36,Vim 模式跳过)
1840
+ - [x] **`/memory` 命令**(v0.1.35):查看/手动追加/清空 memory.md,`/memory show|add|clear|path`
1841
+ - [x] **`/doctor` 健康检查**(v0.1.35):诊断 API Key 状态、配置文件、MCP 连接、context 占用率
1842
+ - [x] **`/bug` 反馈命令**(v0.1.36):生成含系统信息的 Bug 报告模板,`--copy` 复制到剪贴板
1843
+ - [ ] **Vim 编辑模式**:命令行输入支持 vim 键绑定(已跳过,优先级低)
1844
+ - [x] **桌面通知**(v0.1.36):`src/repl/notify.ts` 跨平台通知(osascript/PowerShell/notify-send),耗时超阈值自动触发,`config.ui.notificationThreshold`(默认 10000ms)
1845
+ - [x] **`--allowed-tools` / `--blocked-tools` 启动参数**(v0.1.35):启动时白名单/黑名单限制 AI 可用工具
1846
+ - [x] **流式 JSON 输出**(v0.1.36):`--output-format streaming-json`,NDJSON 每 chunk 一行 `{"type":"delta","text":"..."}` + 最终 `{"type":"done",...}`
1847
+
1848
+ #### P2体验增强(v0.1.37 完成 4 项,v0.1.38 完成 Extended Thinking)
1849
+ - [x] **项目级 `.mcp.json`**(v0.1.37):项目根目录 `.mcp.json` 自动发现,与全局合并(项目覆盖同名),`/mcp` 显示来源标签
1850
+ - [x] **`--resume <id>` 启动参数**(v0.1.37):命令行直接恢复指定会话(前缀匹配),找不到时显示最近 5 session
1851
+ - [x] **Word wrap 配置**(v0.1.37):`config.ui.wordWrap`,ANSI 转义码感知折行
1852
+ - [x] **主题/颜色自定义**(v0.1.37):dark/light/custom 三主题 + 10 语义色槽 + Proxy 全局导出
1853
+ - [ ] **IDE 集成**:VS Code / JetBrains 扩展(架构已准备就绪)
1854
+ - [ ] **OAuth/浏览器登录**:无需手动填 API Key
1855
+ - [x] **Extended Thinking**(v0.1.38):Claude 深度推理模式集成,`/think` 运行时切换,thinking 块折叠显示