432 lines
12 KiB
Markdown
432 lines
12 KiB
Markdown
# hxclaw 更新日志
|
||
|
||
## 版本记录
|
||
|
||
### v0.3.1 (2026-05-03)
|
||
|
||
- **升级 picoclaw 至 v0.2.7**
|
||
- 配置版本从 v2 升级至 v3
|
||
- 适配配置结构变更(Bindings → Dispatch, channels → channel_list)
|
||
- 同步更新相关间接依赖
|
||
|
||
- **新增 `/context` 命令**
|
||
- 显示当前会话上下文窗口使用情况
|
||
- 包含消息数、token 使用量、压缩阈值和剩余空间
|
||
- 在 interactiveMode 和 simpleInteractiveMode 中均支持
|
||
|
||
---
|
||
|
||
### v0.3.0 (2026-04-27)
|
||
|
||
- **Session 创建逻辑优化**
|
||
- Session 只在用户输入聊天消息时创建
|
||
- /new 命令只重置 currentSession,不立即创建
|
||
- 退出后重新进入算作新会话
|
||
- 配置项:auto_session(默认 true)
|
||
|
||
- **LLM 生成摘要**
|
||
- Chat 摘要:调用 LLM 生成极简文言文概述
|
||
- Session 摘要:每次对话后用 LLM 精简整合
|
||
- 配置项:summary_timeout(默认 30 秒)
|
||
- 容错处理:超时失败回退到简单截断
|
||
|
||
- **双记忆系统合并**
|
||
- 读取 picoclaw 的 MEMORY.md 作为长期记忆
|
||
- 合并到 hxclaw 的会话摘要上下文
|
||
- AI 同时看到长期记忆和会话摘要
|
||
|
||
- **独立上下文系统**
|
||
- 创建 GetContextPrompt() 返回会话摘要
|
||
- 注入到 ProcessDirect() 调用前
|
||
- 不再依赖 picoclaw session 管理
|
||
- 修复 recall 结果污染 session summary 问题
|
||
|
||
- **数据库层完善**
|
||
- 集成 libSQL (TursoDB)
|
||
- 创建 sessions 和 chats 表
|
||
- 实现 CRUD 操作
|
||
- 数据库保存在 `~/.config/hxclaw/hxclaw.db`
|
||
- 向量存储使用 binary 编码(float32)
|
||
|
||
- **向量检索功能**
|
||
- 硅基流动 BGE-M3 API 集成
|
||
- 向量生成和存储
|
||
- Cosine Similarity 计算
|
||
- SearchSimilar() 函数实现
|
||
- 4 个查询场景(RecallHistory, RecallTopic, RecallSession, RecallWithinSession)
|
||
|
||
- **三重检测机制**
|
||
- 关键词匹配(之前、聊过、记得等)
|
||
- 向量相似度自动检测(auto_recall + 阈值)
|
||
- /recall 命令强制触发
|
||
- 配置项:keywords, auto_recall, similarity_threshold, max_results
|
||
|
||
- **MongoDB 风格导出**
|
||
- 固定路径:`~/.config/hxclaw/export-data.json`
|
||
- chats 嵌套在 sessions 下
|
||
- 增量导出,同 session 累加
|
||
- 版本控制(version 字段)
|
||
|
||
- **UI 优化**
|
||
- 合并状态显示到单行(耗时 · 状态 · 消息数)
|
||
- 颜色设计:金色图标 + 灰色文字
|
||
- 暗绿色"会话已保存" / 暗红色"会话保存异常"
|
||
|
||
- **配置项更新**
|
||
- memory.recall 配置
|
||
- memory.vector.max_search_results
|
||
- memory.auto_export(替换 export_on_exit)
|
||
- 默认 max_search_results = 10
|
||
|
||
---
|
||
|
||
### v0.2.1
|
||
|
||
- 修复 TTS JSON 请求格式,兼容 Windows daemon
|
||
- 发送格式改为 `{"text": "内容"}`
|
||
|
||
---
|
||
|
||
### v0.2.0
|
||
|
||
- 新增 TTS 语音朗读功能
|
||
- 集成 mimo-tts client 功能,通过 TCP 连接本地 daemon
|
||
- 支持配置文件开关(tts.enabled)
|
||
- 支持命令行切换(/tts on/off/status)
|
||
- 支持临时 TTS 前缀(`T 消息` 临时开启)
|
||
- 动态提示符显示 TTS 状态(👀 🔊)
|
||
- 静默失败处理(网络异常时警告日志)
|
||
|
||
---
|
||
|
||
### v0.1.0
|
||
|
||
- 创建 hxclaw 项目
|
||
- 实现流式输出功能
|
||
- Markdown 渲染(glamour,自动代码高亮)
|
||
- 项目配置化(project.config.yml)
|
||
|
||
---
|
||
|
||
## 待实现功能
|
||
|
||
### v0.2.0 (当前)
|
||
|
||
- [x] TTS 语音朗读功能
|
||
- [x] 集成 mimo-tts client (TCP 连接)
|
||
- [x] 配置文件开关 (tts.enabled)
|
||
- [x] 命令行切换 (/tts on/off/status)
|
||
- [x] 临时 TTS 前缀 (T 消息)
|
||
- [x] 动态提示符显示状态
|
||
- [x] 静默失败处理
|
||
|
||
### v0.3.0 (当前)
|
||
|
||
- [x] 双记忆系统合并(picoclaw MEMORY.md + hxclaw 会话摘要)
|
||
- [x] 数据库层集成(libSQL)
|
||
- [x] 独立上下文系统(不再依赖 picoclaw session)
|
||
- [x] 会话摘要注入
|
||
- [x] UI 优化(合并显示、颜色设计)
|
||
- [x] 向量检索(硅基流动 API)
|
||
- [x] 4 个查询场景(RecallHistory, RecallTopic...)
|
||
- [x] 三重检测机制
|
||
- [x] MongoDB 风格导出
|
||
- [x] Session 创建逻辑优化(输入消息时自动创建)
|
||
- [x] /new 命令(只重置 currentSession)
|
||
- [x] LLM 生成摘要(文言文风格)
|
||
- [x] summary_timeout 配置
|
||
|
||
---
|
||
|
||
## 待实现功能
|
||
|
||
### v0.4.0 (计划)
|
||
|
||
- [ ] 命令行参数支持(--theme, --tts 等)
|
||
- [ ] 多语言支持
|
||
- [ ] 命令提示/补全功能(输入 / 显示内置命令列表)
|
||
|
||
---
|
||
|
||
## 目前进度
|
||
|
||
- [x] 创建项目目录结构
|
||
- [x] 编写讨论记录(taolun.md)
|
||
- [x] 编写更新日志(changelog.md)
|
||
- [x] 编写 AI 行为指南(agents.md)
|
||
- [x] 创建 go.mod
|
||
- [x] 实现 main.go 入口
|
||
- [x] 实现流式输出核心逻辑
|
||
- [x] 编译成功,生成 hxclaw 二进制
|
||
- [x] 添加 spinner 加载动画组件
|
||
- [x] 实现 Markdown 渲染(glamour)
|
||
- [x] 实现项目配置化(project.config.yml)
|
||
- [x] 实现 TTS 语音朗读功能
|
||
- [x] 集成 libSQL 数据库
|
||
- [x] 实现独立上下文系统
|
||
- [x] UI 状态合并显示
|
||
- [x] LLM 生成摘要(文言文风格)
|
||
- [x] Session 自动创建逻辑
|
||
- [x] 升级 picoclaw 至 v0.2.7
|
||
- [x] 实现 `/context` 命令
|
||
|
||
---
|
||
|
||
## 认知纠正(踩坑记录)
|
||
|
||
### Go replace 机制不需要发布到 registry
|
||
|
||
**问题**:最初担心需要像 npm 那样发布到 registry 才能被其他项目引用
|
||
|
||
**纠正**:Go 的 replace 机制可以直接指向:
|
||
- 本地路径(如 `../picoclaw`)
|
||
- GitHub 仓库 + tag(如 `github.com/sipeed/picoclaw v0.2.4`)
|
||
|
||
**知识点**:Go 模块不需要发布到任何 registry,GitHub 就是事实上的 registry
|
||
|
||
---
|
||
|
||
### hxclaw 不需要实现全部 picoclaw 功能
|
||
|
||
**问题**:最初担心需要自己实现 onboard、tools、mcp 等全部功能
|
||
|
||
**纠正**:hxclaw 是 CLI 增强层,只替换交互逻辑。picoclaw 的核心功能(agent loop、tools、mcp、skills)通过导入其 pkg 即可复用
|
||
|
||
**知识点**:采用组合优于继承的设计,需要什么功能就导入对应的包
|
||
|
||
---
|
||
|
||
### 流式输出需要判断 Provider 是否支持
|
||
|
||
**问题**:不是所有 Provider 都支持流式输出
|
||
|
||
**纠正**:需要使用类型断言判断 Provider 是否实现 `providers.StreamingProvider` 接口:
|
||
```go
|
||
if sp, ok := provider.(providers.StreamingProvider); ok {
|
||
// 使用 ChatStream
|
||
} else {
|
||
// 使用普通 Chat
|
||
}
|
||
```
|
||
|
||
**知识点**:picoclaw 的 Provider 设计使用了接口分离原则,流式是可选能力
|
||
|
||
---
|
||
|
||
### 终端渲染使用 charmbracelet 库
|
||
|
||
**问题**:如何实现 Markdown 终端渲染
|
||
|
||
**纠正**:使用 charmbracelet 家族:
|
||
- lipgloss:样式定义
|
||
- glow:代码高亮
|
||
|
||
**知识点**:charmbracelet 是 Go 终端UI 的事实标准,API 设计优雅
|
||
|
||
---
|
||
|
||
### 独立二进制部署方式
|
||
|
||
**问题**:hxclaw 和 picoclaw 的关系
|
||
|
||
**纠正**:hxclaw 作为独立二进制,用户可以同时保留两个命令:
|
||
- `picoclaw agent` 使用原版
|
||
- `hxclaw` 使用增强版
|
||
|
||
**知识点**:通过 go.mod replace 实现依赖绑定,用户无需安装 picoclaw 源码
|
||
|
||
---
|
||
|
||
### AgentRegistry 没有 BuildMessages 方法
|
||
|
||
**问题**:最初尝试调用 agentLoop.GetRegistry().BuildMessages() 构建消息
|
||
|
||
**纠正**:BuildMessages 属于 ContextBuilder,不是 AgentRegistry:
|
||
```go
|
||
// 正确方式
|
||
agentInstance.ContextBuilder.BuildMessages(history, summary, input, media, channel, chatID, senderID, senderDisplayName)
|
||
```
|
||
|
||
**知识点**:picoclaw 代码结构中,ContextBuilder 负责消息构建,AgentRegistry 负责 agent 管理
|
||
|
||
---
|
||
|
||
### ToolDefinitions 获取方式
|
||
|
||
**问题**:如何获取可用的工具定义列表
|
||
|
||
**纠正**:通过 ToolRegistry 的 ToProviderDefs 方法:
|
||
```go
|
||
toolDefs := agentInstance.Tools.ToProviderDefs()
|
||
```
|
||
|
||
**知识点**:ToolRegistry 维护工具注册,ToProviderDefs 转换为 provider 可用的格式
|
||
|
||
---
|
||
|
||
### 流式输出实时刷新
|
||
|
||
**问题**:流式输出时字符不是实时显示,要等很久才一次性出现
|
||
|
||
**纠正**:在 onChunk 回调中添加 `os.Stdout.Sync()` 强制刷新 stdout:
|
||
```go
|
||
func(token string) {
|
||
fmt.Print(token)
|
||
os.Stdout.Sync() // 强制刷新
|
||
}
|
||
```
|
||
|
||
**知识点**:Go 的 `fmt.Print` 使用缓冲输出,需要手动刷新才能实时显示
|
||
|
||
---
|
||
|
||
### Session 历史消息获取
|
||
|
||
**问题**:如何获取会话历史用于流式调用
|
||
|
||
**纠正**:通过 `SessionStore` 接口:
|
||
```go
|
||
history := agentInstance.Sessions.GetHistory(sessionKey)
|
||
summary := agentInstance.Sessions.GetSummary(sessionKey)
|
||
```
|
||
|
||
**知识点**:`AgentInstance.Sessions` 实现了 `SessionStore` 接口,支持 `GetHistory` 和 `GetSummary` 方法
|
||
|
||
---
|
||
|
||
### 流式调用后的消息保存
|
||
|
||
**问题**:流式调用绕过了 agent loop,消息没有保存到 session
|
||
|
||
**纠正**:流式调用后手动保存消息:
|
||
```go
|
||
agentInstance.Sessions.AddMessage(sessionKey, "user", input)
|
||
agentInstance.Sessions.AddMessage(sessionKey, "assistant", result)
|
||
```
|
||
|
||
**知识点**:`SessionStore` 接口提供 `AddMessage` 方法,支持 "user" 和 "assistant" 角色
|
||
|
||
---
|
||
|
||
### onChunk 回调接收累积文本导致重复输出
|
||
|
||
**问题**:picoclaw 的 `StreamingProvider` 接口定义:
|
||
```go
|
||
onChunk func(accumulated string)
|
||
```
|
||
|
||
注释说明:"onChunk receives the accumulated text so far (not individual deltas)"。每次回调时参数是累积的完整文本(如 "你好" → "你好!再次" → "你好!再次见到"),而不是增量。
|
||
|
||
**纠正**:使用 `printedLen` 跟踪已打印位置,只打印新增部分:
|
||
```go
|
||
var printedLen int
|
||
func(accumulated string) {
|
||
if len(accumulated) > printedLen {
|
||
fmt.Print(accumulated[printedLen:])
|
||
printedLen = len(accumulated)
|
||
}
|
||
}
|
||
```
|
||
|
||
**知识点**:picoclaw 故意设计为累积文本,这样可以在任意时刻获取完整内容用于调试
|
||
|
||
---
|
||
|
||
### 尝试 uilive 库但只显示最后一行
|
||
|
||
**问题**:为了实现同行流动效果,尝试使用 `github.com/gosuri/uilive` 库
|
||
|
||
**现象**:该库会覆盖每一行,只显示最后一行内容
|
||
|
||
**纠正**:移除 uilive,直接使用 `fmt.Print` + `os.Stdout.Sync()`,让终端自然处理换行
|
||
|
||
**知识点**:uilive 适用于进度条等场景,不适合长文本流式输出
|
||
|
||
---
|
||
|
||
### 流式输出期望同行流动但实际换行显示
|
||
|
||
**问题**:用户期望像 ollama 那样在同行逐字符流动
|
||
|
||
**最终方案**:
|
||
```go
|
||
fmt.Print(accumulated[printedLen:])
|
||
os.Stdout.Sync()
|
||
```
|
||
|
||
效果:
|
||
- 字符串自然累积增长
|
||
- 终端自动处理换行(满一行自动 wrap)
|
||
- 保留所有历史输出
|
||
- 每次刷新缓冲区确保立即显示
|
||
|
||
**知识点**:最简单的方案就是最有效的方案,不需要额外库
|
||
|
||
---
|
||
|
||
### spinner 组件的 model 更新必须使用返回值
|
||
|
||
**问题**:spinner 动画不动
|
||
|
||
**现象**:调用 spinner.Update(msg) 后动画不更新
|
||
|
||
**纠正**:spinner model 是值类型,需要使用返回值更新:
|
||
```go
|
||
// 错误写法
|
||
func (s *Spinner) tick() {
|
||
msg := s.spinner.Tick()
|
||
if msg, ok := msg.(spinner.TickMsg); ok {
|
||
s.spinner.Update(msg) // 动画不会动!
|
||
}
|
||
}
|
||
|
||
// 正确写法
|
||
func (s *Spinner) tick() {
|
||
msg := s.spinner.Tick()
|
||
if msg, ok := msg.(spinner.TickMsg); ok {
|
||
s.spinner, _ = s.spinner.Update(msg) // 必须使用返回值更新
|
||
}
|
||
}
|
||
```
|
||
|
||
**知识点**:bubbletea v2 的组件遵循 TEA 架构模式,Update 方法返回更新后的 model,需要显式使用返回值。
|
||
|
||
---
|
||
|
||
### spinner 和流式输出在同一行的冲突问题
|
||
|
||
**问题**:spinner 使用 `\r` 回到行首刷新,流式输出也在同一行打印,导致内容混在一起
|
||
|
||
**现象**:
|
||
```
|
||
⠋ 回答中... 好
|
||
⠋ 回答中... 注于
|
||
```
|
||
|
||
**纠正**:在第一个 token 时停止 spinner,让 spinner 输出 "思考完成." 并换行,然后再开始流式打印:
|
||
```go
|
||
if firstToken && len(accumulated) > 0 {
|
||
spinner.Stop() // 停止 spinner,会打印 "思考完成."
|
||
firstToken = false
|
||
}
|
||
```
|
||
|
||
**知识点**:spinner 和流式输出需要分时工作,不能同时占用同一行。
|
||
|
||
---
|
||
|
||
### spinner 动画位置和换行策略
|
||
|
||
**问题**:用户期望动画在前,文字在后,且需要正确的换行
|
||
|
||
**效果**:
|
||
```
|
||
思考中... ⠋ -> 用户期望 ⠋ 思考中...
|
||
思考完成. -> 用户期望 ⠋ 思考完成.
|
||
```
|
||
|
||
**纠正**:
|
||
- 动画在前,文字在后:`fmt.Printf("\r%s %s", s.spinner.View(), s.text)`
|
||
- 换行:"思考完成.\n" + 流式输出后 "fmt.Println()\n"
|
||
|
||
**知识点**:终端输出需要精确控制位置和换行,否则会导致格式错乱。 |