5836c2ce5c
refactor(memory): 拆除描述式媒体索引,媒体成为一等块并按原生向量融合
...
背景:此前媒体是靠「生成的描述文本」将就进记忆的——写 marker 进正文、
再由正则反解成 media_refs 与图库里的 type=Media 实体。这条链路有三个
致命缺陷:描述由异步模型生成(未生成前媒体等于不存在)、语义检索实质上
只搜描述文字、图库里的「媒体节点」是描述文本的投影而不是媒体本身。
本提交把这条链路整体拆除,媒体改为按自己的原生向量参与记忆:
一、描述链彻底删除(无残留、无兼容分支)
- media.Item 去掉 Description/DescribedBy 与对应列;
- 删除 Store.Describe / Store.Search / Store.Pending;
- 删除 Agent.mediaDescribeLoop / describePendingMedia 与配置项
core.memory.media.describe_on_ingest;
- SDK 侧 MediaAttachment 去掉 Description(见 SDK 仓独立提交)。
二、marker 机制删除,媒体归属改为结构化块边
- 删除 mediaMarkerLine/parseMediaMarkers/mediaEntityName/mediaTriplesFromText/
extractMediaDigests/sentenceWithMediaMarkers/docMediaContext;
- memory.Triple 新增 MediaDigests 结构化字段;句子文本保持原样,
不再被 marker 污染;
- 块以 sentence --contains--> block / document --contains--> block 结构边
挂到承载节点(新增 documents 表与 document 节点种类);
- 模型未给原句时用「主谓宾。」拼一句自然语言作落点,不造 marker 文本。
三、旧数据迁移(幂等)
- 新增 GraphDB.MigrateLegacyMediaEntities:把 type=Media 的旧实体按短 digest
还原成原生块、挂回原句子、删除旧实体与描述关系;Agent 启动时执行;
- CleanupOrphanedSentences 同时看关系引用与块边,避免把只靠块存活的句子
连同块边一起删掉。
四、向量融合:媒体按图本身被召回
- 新增 vector.FuseVectors(逐维求和 + L2 归一化);
- Doc.DenseVec = 文本向量 ⊕ 文档块的媒体向量(同 fingerprint 才融合),
新增 Doc.DenseFP,指纹变化触发重算;
- ContextEvent.DenseVec 同理融合事件块;事件新增 DenseFP,Prune 只在
同一统一空间内比稠密余弦;
- 跨模态视觉路只召回「仍被某层记忆块持有」的媒体,CAS 全库字节不再
直接充当记忆检索结果。
五、同时纳入本分支既有的嵌入基础改造(此前工作区未提交,缺它 HEAD 不可构建)
- internal/tfidf 懒回退包、千问三段式多模态 ONNX 空间的 Go 侧
(qwen/embedder.go、image.go、model_input.go)、CLIP 移除、
sdk.NewStore 分词器签名与调用点、embed 侧车 systemd 单元。
验证:go build ./... 、go vet ./...(含 -tags medialive)均通过;
在 HEAD 的独立 worktree 上重放本次暂存集后 go test -short ./internal/...
全部通过(端口冲突类用例在隔离环境中亦通过)。未提交工作区中与本改造
无关的改动(HarmonyOS、waiter、devicebridge、plan.md 等)。
2026-09-11 11:45:24 +08:00
dae01f9c06
refactor(memory): 移除 media_refs/引用计数,媒体成为一等记忆块
...
媒体此前是"文本块 + digest 引用 + owner 账本 + 独立 GC":ContextEvent.Media
记 digest,media_refs 表用 owner_kind/owner_id 保活,ref_count 决定 GC 能否清。
这与文本记忆块的管理方式不一致,也是本次一并纠正的核心偏差。
改为与文本块完全一致的生命周期:
1. 一等记忆块直接由所在层持有
- ContextEvent.Blocks / Doc.Blocks / GraphDB memory_blocks
- 块带 modality/digest/MIME/size/vector/fingerprint,文本、图片、视频同构
- Context→Document→Graph 迁移的是块本身(ID 不变),迁移后清空源容器,
同一块不同时存在于两层
2. 删除平行生命周期账本
- media.Store 去掉 media_refs 表、OwnerKind 常量、RefCount 字段、
AddRef/DropRef/DropOwner/Refs、ref_count 列与索引
- 删除 mediaGCLoop、GC(keep,minAge)、容量上限与 media.gc_* / media.max_mb 配置
- 媒体内容在块被永久删除时一并删除(media.Store.Delete + forgetPayloads),
与"删除文本块即删除内容"同一语义
3. L3 原生结构
- memory_blocks / memory_block_edges(contains/depicts/derived_from)
- 边端点必须是真实图节点,不再用 owner 字符串伪装关系
- BlocksForNode 支持 sentence --contains--> block 反查
4. SDK 与检索同步
- 插件附件/标记直接变成块,不再 AddRef
- 跨模态检索改用 QueryMediaScored(CAS 内不再有孤儿缓存需要过滤)
测试全部改写为块语义:删除 refcount/media_refs/GC 断言,新增块迁移、
单层不变量、Delete 语义与并发删除回归。
注:cmd/homed/main.go 同时携带工作区中既有的 CLIP→Qwen 模型目录接线改动。
2026-09-11 10:57:22 +08:00
6c2039f5c9
feat(vector): pluggable multimodal vector space
...
核心暴露 MultimodalEmbedder 接口,两条路径共享同一套 L0/L2/L3
向量缓存、media.Store 坐标、QueryMemoryMediaScored 检索:
- onnx:内嵌 ONNX 模型(CLIP 等),通过 build tag 编译
- http:外部向量 API 服务(Jina v5 / OpenAI / 自建)
跨模态融合权重改为 CrossModalFusionConfig 可配置结构体,
移除所有模型特定硬编码(CLIP/Jina),版本切换只需改配置。
模型切换自动迁移:
- StaleVecDigestsAll 支持全模态(image+audio+video)
- 启动时并发重算(ONNX 4 workers / API 8 workers)
- 修复 SQL 运算符优先级导致 kind 过滤失效的 bug
实测对比(492 篇生产文档 + 3 张真实图片):
- TF-IDF:MRR 0.457(精确匹配快,语义差)
- fastText:MRR 0.530(语义中等,延迟 8ms)
- Jina v5-omni:MRR 0.900(全面领先,延迟 40ms)
- 中文文本→图片:Jina MRR 0.833 vs CLIP 0.611
See docs/embedding-comparison.md for full benchmark.
2026-09-09 17:38:34 +08:00
98365f3a55
feat(clip): 多模态向量器(CLIP ONNX)——文本/图像 512 维共享空间 + 媒体向量写入与重算
...
- internal/memory/clip:CLIP ONNX 向量器(onnxruntime 构建标签控制,默认构建不链接 ONNX)
- clip.New(modelDir) 加载 text.onnx/vision.onnx(输出 text_embed/image_embed [batch,512])
- 实现 vector.Vectorizer + vector.MultimodalEmbedder(Vectorize/EmbedImage + Dense 变体)
- 词级 BPE tokenizer:merges 合并后词末片段带 </w> 查 vocab,与官方 encode 逐 id 对齐
- EmbedImage:解码→resize 224→NCHW→normalize→vision session
- Fingerprint(text+vision 文件 sha256)供模型切换检测
- stub 版(无 onnxruntime 标签)保持默认构建行为不变
- vector/store.go:新增 MultimodalEmbedder 接口
- media.Store:新增 StaleVecDigests(currentModel)——查 vec_model 不匹配/缺失的图片
- agent core:AgentConfig.ClipEmbedder + Agent.clipEmb 接线;
describePendingMedia 描述成功后 EmbedImageDense→SetVec;
新增 reembedStaleMedia 启动补算历史无向量图片
- config:core.memory.media.clip_model_dir(未配置退化为现有 fastText/TF-IDF 行为)
- cmd/homed:读 clip_model_dir 加载 CLIP,失败仅记日志不阻塞启动
测试:TestSmokeLoadAndEncode(文本语义 cat>dog 0.914>physics 0.740)、
TestCrossModalAlignment(red-image vs red-text 0.063>blue -0.009,与 Python 一致)、
TestTokEnd(与官方 encode 逐 id 对齐)、TestStaleVecDigests,含 -race 全绿
2026-09-09 10:23:31 +08:00
74a24f93d7
feat(memory): 媒体 GC 与描述生成两条后台循环
...
补齐媒体记忆的最后两块:容量上限真正生效,描述文本成为持久语义记忆。
## mediaGCLoop:让容量上限不再形同虚设
CAS 的 GC 只在被显式调用时执行,Put 路径不触发它。此前配置项
core.memory.media.max_mb 注册了却没有任何调用方——一次 see_video 抽 10 帧,
帧本身在工具结果被 Prune 后就没人引用了,若无人清理会一直堆在磁盘上。
现在按 gc_interval(默认 6h)周期调 GC(gc_min_age)。两个不变量:
- 有引用的内容永不删除,即使超容量(宁可超限也不断引用)
- gc_min_age(默认 1h)保护刚 Put 还没来得及 AddRef 的项——它们
refcount 也是 0
## mediaDescribeLoop:描述才是能活过 GC 的那部分
blob 会被容量 GC 淘汰,而描述留在 media 表里,并经 mediaSummaryForEvent
写进 L0 事件、随归档进 L2 文档、经蒸馏进 L3 图库。于是「那张紫蓝红三色
带图」在原始字节早已被清掉之后仍然可被检索到。
复用既有的视觉回退链(resolveModalFallback + chatModalFallbackBatch),
不新造一套模型调用。
三个刻意的决定:
- **走后台而非入库时同步**:视觉模型一次调用生产实测 9.6s。放在对话
路径上会让每张图都给回复加十几秒,而描述的价值是几个月后还能检索到,
不是这一轮——这一轮模型本来就直接看着图。
- **逐条而非批量**:批量拿回来是一整段文字,无法可靠切分回各自的
digest(模型未必按序号输出,也可能把两张图合并成一句)。宁可多几次
往返也要保证「描述 ↔ digest」的对应关系确定。
- **默认关闭**(describe_on_ingest=false):它消耗视觉模型配额。开启后
每 30s 最多处理 4 条,不跟对话抢额度。
失败处理分三类:
- 网络抖动/配额 → 不标记,下轮重试
- 空回复 → 视作失败(上游剥离媒体时通常回空,与 modalfallback 同理)
- 不可描述(kind=other、blob 已丢失)→ 标记 described_by=unsupported/
content-missing,退出队列
## 顺带修掉 Pending 的一个真缺陷
测试写出来才发现:Pending 原先只看 `description = ''`,于是被标记为
described_by=unsupported 但 description 仍空的项**每轮都会被重新取出来
重试**,永久占着 LIMIT 的名额,真正需要描述的新项永远轮不到。
改为同时要求 described_by 也为空。
这是「先写断言再看它是否成立」抓到的——原本我以为标记一下就够了。
## 测试
medialoop_test.go 7 例:两条循环在禁用时立即返回(nil store / 零间隔 /
describe 关闭三种形态,不留空转 goroutine)、GC 清孤儿保留有引用项、
minAge 保护新项、无可用源时不误标记、不可描述大类被标记后退出队列。
media_test.go 补 1 例专测 Pending 的排除逻辑。
全仓 go build / go vet / go test 通过,SDK 冻结 diff = 0。
2026-09-04 22:00:03 +08:00
f855893d1c
feat(memory): 媒体接入 L0/L2——digest 挂到对话事件,归档时引用随之转移
...
在 a822674 的 CAS 层之上把媒体真正接进记忆链路。此前 CAS 只是个孤立的
存储包,没有任何写入方。
## 媒体进入对话有两条路,两条都只把文字留给记忆
1. 用户直接发图 → processMediaInput → mediaToBlocks
ContextEvent.Input 只存 alt 文本("[从 qq 收到了 image]"),
base64 随 message 数组发给模型后就丢了。
2. 插件注入 → SetToolBlocks → process.go 的 mediaMsg
ToolResultItem.Output 只存那句 "[已将图片注入后续对话] /tmp/x.png"。
于是下一轮起,模型能看到的只剩一句路径或一句 alt。那个文件被删、被覆盖,
或者本来就是 /tmp 下的临时产物,连线索都断了。
现在两条路在同一处收口(captureBlockMedia):从 ContentBlock 的 data URL
取出字节存进 CAS,digest 挂到当轮 ContextEvent。
## 改动
internal/agent/core/mediaref.go(新)
- captureBlockMedia:ContentBlock → CAS。只处理 data URL——http(s) URL
拿不到字节就无法内容寻址,而「下载它再存」会把一次对话变成一次网络
请求(超时、鉴权、SSRF 全来了),不在本层解决。
- stage/drainMediaDigests:媒体在 process() 期间被捕获,而承载它的
ContextEvent 要等 process() 返回后才 Append——此刻还没有 owner_id,
故先缓存。与既有 pendingMedia 同一手法,同受 a.mu 保护。
- bindEventMedia:双向落地。evt.Media 让事件记得引了什么(随
context.json 持久化),media_refs 让 CAS 知道谁在引用(GC 的判断依据)。
只写一边的话,要么 GC 误删仍被引用的内容,要么孤儿永远清不掉。
- mediaSummaryForEvent:把已有描述拼成一行写进 Input。这是方案 C 的
落点——**描述文本才是持久语义记忆,blob 只是缓存**。blob 可能被容量
GC 淘汰,但描述会一直留在 L0/L2/L3 的文本里,让「那张紫蓝红三色带图」
几个月后仍可被检索。
ContextEvent 新增 ID 与 Media 两个字段,都是 omitempty:
- ID 懒生成,只有真要挂媒体时才赋值。绝大多数对话没有媒体,全量生成
会让每条事件都多一个字段进 context.json。
- 存量 context.json 读回来两字段皆空,不影响任何既有行为(有测试)。
RelevanceContext.Prune 归档时转移引用(transferMediaRefs):
**先挂到归档文档、再注销原事件引用**。顺序不能反——先销后挂会让引用
计数瞬时归零,若此刻后台 GC 正在跑就会把仍被记忆引用的内容当孤儿清掉。
为此把 Prune 内的局部类型 scored 提为包级 scoredEvent(局部类型无法
出现在方法签名上)。
media 包新增 OwnerContext/OwnerDocument/OwnerGraphSentence 常量:
owner_kind 进了主键,拼错一个字符就是一条永远对不上的孤立引用——
AddRef 不报错,DropOwner 也永远匹配不到。
## 配置
core.memory.media.enabled(默认 true)、.dir、.max_mb(2048)、
.gc_interval(6h)、.gc_min_age(1h)。
关闭后全链路静默跳过,对话行为与本特性上线前完全一致(有测试)。
mediaStore 为 nil 时同理——它是记忆增强,不是对话必需品,开不起来
只记一条 warning 不阻止启动。
## 测试(11 例)
入库与 MIME 归类、http URL 跳过、nil store 全链路 no-op、音视频混合、
stage/drain 清空语义、懒生成 ID、描述作为持久记忆、**归档转移期间内容
始终可读且 refcount 不归零**、无媒体存储时归档照常、context.json
向后兼容往返。
全仓 go build / go vet / go test 通过,SDK 冻结 diff = 0。
## 尚未接入
L3 图库的 graph_sentence owner(常量已备好,无写入方)、
描述生成的后台任务(Pending() 已就绪,尚无消费者)、
媒体 GC 的定时触发(配置项已注册,尚未接 ticker)。
2026-09-04 20:53:32 +08:00
09071dc235
fix(multimodal): 修多模态假成功 + 落地视觉回退链 + see_video 帧数语义
...
## 起因
生产盲测:模型调 multimodal_see_picture 后声称看到了图,实际一个字
都没收到。工具却返回「[已将图片注入后续对话]」。
链路:core.llm.model=AUTO → llmsproxy 按优先级选 big-pickle(prio=100)
→ 转 opencode zen。llmsproxy 的 opencode.lua 明写着:
-- zen 上游 schema 只接受 text content part(无视觉/音频能力)
if part.type ~= nil and part.type ~= "text" then -- 丢弃
判据:256x256 纯红 PNG,带图与不带图的 prompt_tokens 都是 256。
图片贡献零 token,即根本没进上游。
内核序列化与注入链本身是对的(Message.MarshalJSON 正确产出 content
数组,SetToolBlocks → IOManager → ConsumeToolBlocks → toolMsg.Blocks
全通)。缺的是「主模型能否消费这些块」这一判断——内核此前完全没有
多模态能力的概念(grep supportsVision|multimodal 在 agent/ 零命中)。
这与 v1.0.0 修的 output_send 假成功同类:告诉调用方成功而实际未送达。
## 1. 能力声明
新增 core.llm.sources.<name>.vision / .audio(走既有 sourceFieldDefs,
WebUI 配置页自动出现),types.LLMSource 与 api.BaseConfig 同步加字段。
新增 agentAPI.ModalProvider 接口 + ProviderSupportsVision/Audio 判定:
未实现该接口的 provider 一律按不支持处理。保守侧是刻意的——宁可多走
一次文字回退,也不能把图默默扔给会剥掉它的上游。
为何是声明而非探测:探测需额外真实调用且结果不稳定(取决于 AUTO 当次
路由到哪);而 200 响应 + 相同 token 数从响应侧无法区分「看到了但没
内容」和「被剥掉了」。
## 2. 回退链(modalfallback.go)
实现了 config/registry.go 里注册但从未被读取的 image/audio
fallback_provider + fallback_model(此前 0 处读取点)。
prepareToolBlocks 在 process.go 注入前判定:能直视就原样透传;不能就
调声明了该能力的源转写成文字,带 [由 X 转写,非当前模型直接感知] 标注。
几处刻意的设计:
- 逐模态判定,不一刀切。很多视觉模型能看图但听不到音频,全部降级会
白白把可直视的图变成二手描述
- 混合场景下转写文字作为 text 块并入 native,两部分同时到达模型
- 配置指向未声明能力的源时拒绝并继续找——照用只会重演静默剥离
- 未配 fallback_provider 但某源声明了 vision 时自动扫出来用;静默失败
比多找一个能用的源更糟
- 空回复算失败。上游剥掉媒体后模型往往回「我没看到图片」或空串,两种
都说明回退链也没真看到
- 多媒体块按模态合包为一次请求(见下)
## 3. 批量合包(生产实测驱动的返工)
首版逐块调用,生产 see_video 6 帧实测:4 帧里 3 帧超时,整轮 363 秒。
改为按模态合包一次请求后同一用例 131 秒、6/6 成功。
顺带把 modalFallbackTimeout 从 90s 提到 180s:生产经网关转
claude-opus-5 看一张 400x400 图要 ~81s,90s 贴着上限。
多张时 detail 默认 low 控体积,单张用 high 看细节;插件显式给了
detail 则尊重它。
## 4. see_video 帧数语义
fps=1/N 是频率(每 N 秒一帧)不是数量。20s 视频实测:
frames=4 → 5 帧、frames=10 → 2 帧、frames=1 → 20 帧,要得越多拿得越少;
长视频下 frames=4 会产出 时长/4 帧,靠 i>=9 的 break 兜着才没炸上下文,
而那个 break 用的是 ReadDir 索引,跳过条目后与实际帧数错位。
改为 ffprobe 取时长 → fps=N/时长 + -frames:v N 硬封顶。
0.4s/3s/20s/120s × frames=1/2/4/7/10 全部精确。
极短视频的坑:fps=1 在 0.4s 素材上产出 0 帧(不足一秒抽不出),所以
时长探测失败时不能退化成 fps=1,改为不传 -vf 只靠 -frames:v。
## 验证
- modalfallback_test.go 14 例:直视透传 / 回退转写 / 无源如实报告 /
未实现接口按不支持 / 混合模态拆分 / 空回复算失败 / 块数上限 /
多图合一次调用 / detail 策略 / 拒绝未声明能力的源 / 未配置时自动扫源
- go test ./... 全绿,go vet 无警告
- 生产盲测(答案预先封存、生成时不读):随机三色带 → 模型答
「紫、蓝、红」,与封存答案完全一致
- 负向验证:拿掉回退源后模型如实回答「没看到图片内容」并引用工具返回
的配置提示,且主动纠正了上一轮的答案
- 生产 see_video 6 帧:单次转写,模型正确描述测试图卡的计数器递增与
彩虹带滚动
2026-09-04 06:25:51 +08:00
a3a5cd4fee
fix(agent): 修正系统提示词的回复投递规则,补充事实性约束
...
问题一:提示词说反话,导致 qq 回复大量丢失。
v2 架构曾有「回复自动回投来源通道」的能力(IOManager.routes + RegisterOutputRoute),
但 304c3ae 'remove IO route mapping' 删掉了整套路由映射。此后:
- outputCh 唯一消费者(cmd/homed/main.go)只处理 memory_candidate,其余静默丢弃,
output.go 末尾的 EmitTextTo 兜底成为死路
- ResponseCh 只剩 webui /api/v1/chat、cli、clawhubadapter 三处同步 HTTP 用途,
不再承担渠道投递;qq 走 InjectInterruptText → interruptCh,ResponseCh 恒为 nil
- emitResponse 从不调 GetDevice(),纯文本对异步通道 = 丢弃
提示词却仍写着「直接返回纯文本即可送达,无需额外工具」「output_send 不是回复的必要步骤」。
实测近 12h 6 次 qq 输入仅 1 次送达,规律是 tools 含 output_send__qq 才到,否则全丢,
且 agent 自认为已回复。现改为明确区分同步/异步通道并要求显式 output_send__。
问题二:无事实性约束,工具失败时模型编造内容。
qq_get_message 30 次调用有 9 次返回 not_found:true(NapCat 响应解析失败),
模型未如实说明,转而虚构消息正文——包括一条不存在的 message_id=1321159191
(全 journal 零命中、不在任何调用记录里)配上完全虚构的正文
「我想搭一个 Dify 工作流,想做一个人脸识别系统 demo」,用户从未说过。
虚构内容经 formatMergedTimeline 回灌【对话时序】后被当作既有事实反复复述放大
(两条消息 reasoning_content 达 180KB / 220KB)。
现补充:工具返回 not_found/空结果必须如实说明不得猜测;【对话时序】是历史事实摘要
不是当前任务;无依据的人名/需求/数字/路径直接说不知道。
注:set() 用 INSERT OR IGNORE,改此默认值只影响新部署;
本机运行实例的 config.db core.agent.system_prompt 已同步更新(改前备份)。
验证:重新部署后实测 agent 明确回答 qq 需 output_send__qq 且纯文本会被丢弃;
对 message_id=1321159191 如实报告 not_found 并声明「正文完全不知道,绝不猜」。
2026-08-29 14:43:23 +08:00
147d0baaf9
fix: LLM 工具循环 400、中断消息注入、ConPTY 终端支持
...
- agent: 工具轮请求尾部补 user 占位(zen 网关强制),tool 消息正确配对
- agent: 工具提醒/中断以 system 角色注入并带 [中断消息] 前缀,不进用户履历;系统提示词说明中断消息格式
- agentcli: 基于 ConPTY 的交互式终端(ptywin fork),terminal_create/read/write/resize/close/watch
- webui: server 输出通道适配器(保留 reasoning_content/disable_thinking)
- GUI: 沉浸式标题栏、icon 圆角重制、mascot 等打磨
2026-08-14 00:48:40 +08:00
9d5a914941
Phase 0.1/1/3/6: 核心生产问题修复
...
Healthcheck 隔离 (Phase 0.1):
- 新增 internal/sdk/selftest.go: VirtualInstance 完全隔离自检空间
- PluginSDK.Selftest()/SelftestReset() 暴露隔离实例 (含 mutex)
- LLM 自检只读白名单 isSafeReadonlyTool 防写类工具污染生产
- 单测验证: healthcheck 后生产实例内容不变 + 无残留
- 存量清理: 删除 gotest/luatest 残留目录
GraphDB 去重 (Phase 1):
- migrateRelationUnique: 启动自动重建 relations 表加 UNIQUE 约束并去重
- Commit 改为存在性检查, 重复三元组仅刷新 confidence 不重复插入
- 3 个 dedup 单测全绿
配置时长解析 (Phase 3):
- parseDurationExtended 支持 2d/1w/3h 等人类可读单位
- GetDuration 全局生效, 防 2d 静默回退 30m
Agentcli 通知风暴治理 (Phase 6):
- 语义通知: 累积 notify_bytes(2KB) 或间隔 notify_interval(2s) 触发
- 生命周期即时通知: 启动/进程退出/EOF 立即通知
- 可配置 settings, 保留通知机制保证 agent 感知终端存在
- 运维止血: 已杀掉幽灵 PID 3716282 (bash git sparse clone 运行 16h)
Plan.md: 新增设计意图备忘(插件即App/分层记忆), 更新各 Phase 进度
2026-08-12 13:51:40 +08:00
f960fde785
agent: 更智能的 LLM provider 调度(byModel 精确路由 + AUTO 优先级链)
...
吸收 llmsproxy 的调度思想适配 HomeAgent“一源一模型”结构:
- RoutableProvider{Model,Priority} 次级接口(不破坏既有 Provider 实现)
- ProviderManager.OrderedProviders 改为按 (优先级 desc, 可用, 默认优先) 稳定排序,
AUTO/空模型走该优先级链
- 新增 ProviderManager.ResolveForModel:精确模型名路由到归属源,找不到回落 AUTO 链
- LuaAdaptedProvider 不再无条件覆写 req.Model;显式模型名原样转发
- LLMSource.Priority + core.llm.sources.<name>.priority 配置项
- process.go: 显式模型走 ResolveForModel,AUTO 走 OrderedProviders
- 新增路由单测(优先级排序 + byModel 解析)
验证: go test ./... 27 包 0 失败;Windows 交叉编译通过;部署后服务健康
2026-08-10 11:54:45 +08:00
f0dacef281
lua: 吸收 llmsproxy 适配器高级特性(worker 池/静态预提取/动态签名钩子)
...
- 适配器 worker 池化:单 LState+全局锁(串行瓶颈)→ 每 adapter 一个 gopher-lua
LState 池,按使用该 adapter 的源并发上限求和配置池大小,并发 transform 互不阻塞
- staticInfo 预提取:name/version/endpoint/headers 加载期编译缓存,Endpoint/Headers
读缓存不占 worker;加载即预编译首个 worker
- build_headers 动态钩子 + hmac/sha256/base64/tohex 全局:签名型上游(kimicode 等)可接入
- provider applyAdapterHeaders 接入动态头(url/method/body/api_key/timestamp/source 元数据),
未定义时回落静态 headers,缺省补 Authorization
- LLMSource.MaxConcurrent + core.llm.sources.<name>.max_concurrent,注册时汇总
VM.ConfigureConcurrency
- 新增 Lua VM 测试(load/transform/build_headers/并发)
验证: go test ./... 27 包 0 失败;Windows 交叉编译通过;部署后 9 adapter 全部预加载
2026-08-10 11:29:57 +08:00
8dcce5a3a9
webui 删除彻底化:清理残留 config/defs/provider 悬空
...
审计发现 webui 多处删除只清一处、留下鬼影:
- RemovePlugin: 仅 DROP config_<name> 与 defs,config 表 plugin.<name>.* 键永不删 →
补 DELETE config LIKE 'plugin.<name>.%'
- ConfigRegistry.Delete: 删 core.llm.sources.<name>.* 后遗留 ConfigDef、且删 core.llm.*
不更新 guard 快照(重启/failback 会把已删源复活)→ 同步清 source defs + 写 llm 快照
- llm_impl.ReloadFromConfig: 删除默认源后 core.llm.provider 悬空,SetDefault 指向不存在源
→ 仅当源仍注册时才 SetDefault
- clawhubadapter 测试 mockSettings 补 RemoveCore/RemovePlugin
- mcp 服务器删除仍为进程内需 reload 生效(符合既有 removeServerHandler 语义)
验证: go test ./... 26 包 0 失败;本机部署 sources=3、27 插件加载、对话正常
2026-08-10 09:51:30 +08:00
171e6f233b
llm: 统一源接入层修复(对照 llmsproxy)
...
- provider: 新增 OpenAI-compatible 响应/流兜底解析,Lua adapter 异常时也能解析
choices/message/tool_calls/usage(含 function.arguments 缺失、对象/字符串参数)
- 过滤无效 LLM 源(<nil>/空/缺 http(s) scheme),main 与 ReloadFromConfig 均跳过,
避免 mocktest 等坏源污染 fallback 与 healthcheck
- adapter(openai/deepseek/groq/mistral/github/kimicode): 修 tool_calls 对
nil function 的崩溃,兼容扁平/嵌套结构;openai 流透传 reasoning/tool_calls
- config: ToConfig 探活端点过滤无效 base_url,修复 supervisor 误报 LLM unreachable
2026-08-09 20:39:54 +08:00
bc9ef15eb0
三层回退恢复机制(L0写前留档/L1恢复梯子/L2离线回滚)+ guard 父守护
...
- L0: files 插件写受保护系统路径(/etc 等)前自动留档,AbstractBeforeWrite 到 data/file_baseline
- L1: failback 受限 worker 执行恢复梯子 probe→还原DNS/proxy→还原LLM配置+ReloadFromConfig→probe,N轮有界
- L2: tracker changeset 持久化原文 blob,guard 离线 RollbackFromDisk 回滚 agentfs;SystemSnapshot 支撑
- guard 父守护: 心跳 IPC(PING/ACK unix socket, 文件心跳回退)、失败计数、退出码协议(42/43/44)、最后手段
- 发行版路径适配: system.protected_paths/network_paths 可注入,默认面向主流 Linux
- Windows 兼容: guard.go/failback.go 加 //go:build linux, guard_windows.go 提供 no-op 桩
- 修复: guard.yaml last_resort 键冲突、changeset Content 不落盘导致离线回滚丢原文
Build 全绿, vet 干净, system/recovery/ipc/tracker 单元测试全过
2026-08-05 16:00:08 +08:00
b19eeca0cf
插件卸载清配置:删除配置项定义与插件配置表
...
- ConfigRegistry.RemovePlugin(name):删除 defs 中 plugin.<name>.* 配置项定义
(该插件 RegisterDef 注册的全部配置项)+ DROP 插件配置表 config_<name>
(含用户设置值);ListPlugins 基于 config_% 表枚举,卸载后插件配置区
在 WebUI/Settings API 中完全消失
- Registry.RemovePlugin 在 RemoveDisabledPlugin 基础上追加 RemovePlugin 调用;
语义与 onRemove 回调(清数据文件)互补:删除 = 数据文件 + 配置 + 注册全清,
disable/重载不清配置
2026-08-02 13:43:01 +08:00
09faa2874a
插件删除回调:onRemove 生命周期(仅卸载触发,重载不触发)
...
- 公共 SDK(third_party/homeagent-sdk):RegisterOnRemoveHandler/RunOnRemoveHandlers
(后注册先执行、幂等);内部 SDK PluginManager 接口新增 RemovePlugin
- registry.RemovePlugin:runStopHandlers → Stop → 清理 plugins/sdkRefs/instances
→ runOnRemoveHandlers → toolCleaner.UnregisterPluginTools → cfgReg.RemoveDisabledPlugin
- pluginmgr.removePlugin 先调 Registry.RemovePlugin 再删目录
- 配置清理:PluginSettings.Remove(key)(内部 SettingsAPI + settingsImpl)
- 演示:timer 插件 onRemove 删 max_duration 键;plugindev 模板同步 onRemove 示例;
SDK example/calendar cleanupData 删 events.json;manager plugins/uninstall 先停通道
2026-08-02 13:32:44 +08:00
c7ee45d6e1
refactor: remove core skill direct loading, skills owned by clawhubadapter only
...
- Drop skill.NewManager from homed bootstrap; skills dir no longer core-managed
- Remove GetInjectedPrompt system-prompt injection (skills are not first-class)
- Delete internal/skill package, SkillAPI, webui /api/v1/skills, status skills block
- ConfigRegistry: plugin config tables now created only via RegisterDef; arbitrary
scope Set/Get no longer implicitly creates config_<name> tables (fixes stray
config_today_task table from SKILL directory name being used as a scope)
2026-08-02 11:40:13 +08:00
1013aa0aa9
feat(sdk): add RegisterStopHandler/RunStopHandlers and wire stop cleanup into registry lifecycle
...
- SDK PluginSDK gains RegisterStopHandler(fn func()) + RunStopHandlers()
(LIFO, idempotent, cleared after running); synced to third_party copy
- Registry keeps per-plugin SDK refs (sdkRefs); StopAll/ReloadOne/
DisablePlugin run handlers before calling Stop()
- timer built-in plugin demonstrates handler-based shutdown cleanup
- z_bridge (linux/windows templates) runs handlers before plugin.Stop()
2026-08-02 10:23:40 +08:00
796a48dae5
plugin disable system: kernel→SDK PluginManager + WebUI/CLI
...
- New disabled_plugins table (name, disabled_at, disabled_by)
- SDK.PluginManager interface: DisablePlugin/EnablePlugin/ListDisabledPlugins
- Registry implements PluginManager, wired into PluginSDK
- WebUI: POST /api/v1/plugins/<name>/disable|enable + plugins page with toggle
- Disabling webui shows confirmation dialog
- CLI: /plugin disable <name> / /plugin enable <name>
- pluginmgr removePlugin sync-cleanup from disabled_plugins table
2026-07-29 15:07:32 +08:00
4218890aed
docs: 修复记忆分层引用错误 + 补充内置/外部插件说明 + 同步中英文文档
2026-07-28 21:56:53 +08:00
f91b20ee16
v0.7.3: 重构 Provider 层 + 计算层隔离 + Cleaner/NoMemory 架构
...
- 删除 OpenAIProvider/OllamaProvider 死代码,LuaAdaptedProvider 独存
- DisableThinking 从 ExtraBody 移到 CompletionRequest 顶层字段
- ContextWindow 从 Provider 签名移到 BaseConfig/ModelContextWindow() 统管
- 确认 CleanText 仅做基本空白 trim,QQ 模板剥离归插件 Cleaner
- Cleaner/NoMemory 仅作用于向量计算和 jieba 分词层,原文不变
- context.ContextEvent/Doc.Content 始终保存原文
- 删除 nlp/download.go 死代码
- media.go: context.Background() -> a.ctx 级联
- clawhubadapter: HTTP 超时
- cut.go: 跨平台 mod cache 路径 (GOMODCACHE->GOPATH->HomeDir)
- bridge_e2e_test: 移除未用 runtime import
- lua 适配器: disable_thinking 传参
2026-07-28 11:42:29 +08:00
2c5f9ff262
v0.7.2: 根目录清理 + Agent 心跳重构 + 内嵌 ONNX 模型
...
- 根目录清理: branding/docs/knowledge -> assets/, package/tools/deploy -> deploy/
- meta.go: Version 0.7.2, SDKCompatibleVersion 语义改为最高兼容
- Makefile: 版本回退 0.7.2
- registry.go: 系统提示词改用 meta.Version 格式化
- Agent 心跳: reorgGraph 拆分为三个独立循环(archive/merge/review),各自可配间隔
- GraphDB: 新增 sentences 表 + 关系句子溯源 + ClearSentenceID + CleanupOrphanedSentences
- Knowledge: 支持词嵌入向量化器
- NLP 四阶段流水线: Parse -> Extract -> Verify -> Fuse + SentenceRef
- 移除远程 HTTP 解析器(remote_parser.go)
- 新增内嵌 ONNX 模型(vocab + dep_parser.onnx):
+build onnxruntime: 全量 ONNX Runtime 推理
!build onnxruntime: 内嵌词表规则式降级解析器
- config: core.agent.onnx_model_path 替代 dep_parser_url
2026-07-28 09:56:26 +08:00
e992e1ff84
v0.7.2: fix cfgmgr plugin list and core config key prefix
2026-07-25 12:18:35 +08:00
24698e7c82
fix: include openclaw manager/pysimulator sources, remove from gitignore
...
manager/main.js, pysimulator/main.py, and simulator/main.js are
production source files for the OpenClaw sidecar system, not build
artifacts. Remove them from .gitignore so fresh clones can make build
without manual stub creation.
2026-07-19 11:07:18 +08:00
9b3a751d98
fix: correct system prompt — remove misleading auto-reply statement, agent must use output_send__ tool
2026-07-18 21:28:59 +08:00
c9e67d3d55
docs: 修正全部文档使其与源码实现一致
...
主仓库:
- 修复 4 份英文文档语言切换链接指向错误 (../zh/ → ../en/)
- ARCHITECTURE.md 标题 "三种加载方式" → "四种加载方式" (实际表格4行)
- PLUGIN_DEV.md 示例表: 添加 webfetch, 移除不存在的 luaplugintest/testlua
- PLUGIN_DEV.md 代码示例: InjectInput/InjectInterrupt → InjectText/InjectInterruptText
- PLUGIN_DEV.md 代码示例: Memory/Knowledge/LLM/Events 接口签名修正
- PLUGIN_DEV.md .hmap 内容统一, plugindev 编译去除 .exe 后缀
SDK 仓库:
- Plugin.Start(sdk *PluginSDK) 接口签名改为指针
- 方法表重写: 移除 CallLLM/QueryKnowledge/SetMemory 等不存在方法
- IOInjector 参数顺序修正为 (source, channel, text)
- 删除虚构 SDKConfig, 替换为实际 New() 构造函数签名
- .hmap 内容描述一致化
修正前一次会话中的 QQ/Bili 插件问题:
- qq napcat() 超时, fetchBotInfo 竞态, handleWebhook 同步阻塞
- bili CDN 直连失败, 添加 HTTP_PROXY 代理
2026-07-18 20:46:58 +08:00
10638f1308
feat: mascot integration - WebUI/GUI icons, chat avatar, system prompt, docs
2026-07-17 21:41:03 +08:00
69ceedf0ab
feat: register embedding_model_path in ConfigRegistry instead of JSON config
...
- Add core.agent.embedding_model_path to seedDBValues and seedCoreDefs
- Wire cfgReg.GetString in main.go to AgentConfig.EmbeddingModelPath
- Follow existing core.agent.* ConfigRegistry pattern
2026-07-17 21:07:54 +08:00
7f28b997e6
feat: output channel redesign - per-channel output gates, LLM chain events, SDKConfig
...
- Output channels generate per-channel tools: output_send__{name} (type=output) + output_send__{name}_help
- content is JSON string transparently passed to plugin handler for routing
- EventAgentLLMChain: full LLM response forwarded after each turn for webui/logs
- sdk.New refactored to SDKConfig struct (no more 13 positional args)
- RegisterOutputChannel adds desc param for JSON format documentation
- channelDevice simplified (no Tools method), desc field added
- Child agent permission updated for output_send__ prefix
- System prompt: output gates, multi-call, long messages split
- WebUI: subscribes to EventAgentLLMChain in SSE, no output channel
- Tests updated for new naming convention
2026-07-16 12:11:16 +08:00
fa91b24460
refactor: switch from modernc/sqlite back to mattn/go-sqlite3 (CGo)
2026-07-14 11:43:59 +08:00
c7e22fbe30
refactor: move webui.listen_addr from core.daemon to webui category
2026-07-14 11:22:46 +08:00
950090959f
feat: restructure plugin system, add Lua plugin support, update docs
2026-07-13 21:48:13 +08:00
1f1233b823
refactor: P0-P3 fixes, C1 cleanup, architecture diagrams, go.work upgrade
...
- P0-1: ProviderError type + ReportStatus for precise 401/403 detection
- P0-2: Remove -config flag from deploy/homeagent.service
- P2-1: 5s debounce on context.go Save()
- P2-2→C1: Delete output_set_channel entirely
- P2-3: Extract mediaDataURL/mediaChat helpers
- P2-4: Dedup defaultSources var
- P3: Delete dead packages (embed/tokenizer/container/snapshot)
- P3: Delete dead functions (messagesToMap, RunStageAll)
- CL: Update .gitignore, docs, Makefile, gojieba removal
- Config: Delete config/config.yaml, update docs
- Arch: Remove EmitOutputTo from emitResponse
- CL-1: go.work 1.19→1.21
- Docs: Add Mermaid architecture diagrams to README
- Docs: Add kernel-rebuild requires plugin-rebuild note to PLUGIN_DEV.md
2026-07-12 11:42:56 +08:00
df0abcd298
feat: files built-in plugin, doc rewrite, architecture cleanup
...
- Add files plugin as built-in (internal/plugins/files/) with read/write/edit/ls tools,
supporting overwrite/append/insert/create modes and offset/limit segmented reading
- Rewrite README.md with core domain separation and three-layer memory highlights
- Rewrite docs/OVERVIEW.md with per-subsystem file path references
- Rewrite docs/ARCHITECTURE.md (783→~300 lines), merge redundant sections
- Clean docs/PLUGIN_DEV.md: remove emoji, simplify SDK examples
- Fix provider Model pollution in LuaAdaptedProvider.Chat()
- Fix executeToolCall to return actual error vs quiet not-found
- Fix plugin.Open path caching with SHA256 temp-path workaround
- Add knowledge/homeagent_architecture demo entry
- Add config/personal/personal.md identity configuration
2026-07-06 14:33:09 +08:00
8cec92d947
feat: 路径配置化 + 裸二进制启动 + pluginmgr 内置插件
...
- types.go: 新增 PluginDirConfig 结构体嵌入 Config
- config/registry.go: 新增7个路径配置项 (core.plugin.dir 等) + ConfigDef 元数据
- cmd/homed/main.go: -data 默认自动检测二进制同级目录,使用 cfg.Plugin.Dir
- internal/plugins/pluginmgr/: 内置插件实现 (4工具 + HTTP API + 包校验)
- all.go: 注册 pluginmgr
- manifest.go: 扩展 PluginManifest 字段
- sdk/settings.go: RegisterDef / Defs 接口
- webui: 设置页自动发现 ConfigDef 元数据
- config/config.go, config/config.yaml: 清理 YAML 死代码
- sdk/plugin.go: IO 通道泛型化支持非文本类型
- waiter: CLI 支持 socket 发现和交互模式
2026-07-04 16:56:32 +08:00
239a22899b
fix: 模型思考模式配置 + Unicode 截断 + 审计修复 (13 files)
...
模型模式:
- 新增 LLMConfig/Source.ThinkingEnabled 配置,通过 ExtraBody
控制 DeepSeek thinking mode,默认关闭
- SeedDefaults/ToConfig 读写 core.llm.thinking_enabled
- deepseek.lua 移除硬编码 temperature=0
Unicode 截断:
- truncateStr 改按 rune 计数,修复中文截断乱码
审计修复 (Critical):
- graph.go: defer rows.Close 在 for 循环 → 显式 Close (连接池泄漏)
- cli/openclaw/plugin.go: bare type assertion → comma-ok (panic)
- channel.go: payload["type"].(string) → comma-ok (panic)
- webui/handler.go: .(string) → fmt.Sprint (panic)
- agent.go: 添加 nil provider 错误返回
审计修复 (High):
- events/bus.go: copy handler slice under RLock (data race)
- webui/handler.go: SSE 通过 channel 串行化写入 (data race)
- timer/plugin.go: time.Sleep → select with stopCh (Stop 阻塞)
- provider.go: stream ch <- 添加 select ctx.Done (goroutine 泄漏)
- main.go: outputCh goroutine 添加 ctx.Done 退出路径
2026-07-03 20:46:04 +08:00
2d314b3e9c
重构: 插件自注册 + .so 动态加载 + 中断打断机制
...
- 所有内置插件 init() 自注册 (plugin.RegisterFactory), 移除 main.go 硬编码
- 新增 .so 动态加载器 (internal/plugin/dynamic.go), 插件可编译为 plugin.so
- 新增 plugin.json 元数据 (internal/plugin/manifest.go)
- 新增 interceptLoop 独立 goroutine:
(a) cancelLLM() 取消进行中的 HTTP 请求
(b) interceptCh → drainInterrupt() 注入 [打断消息] 到 LLM 上下文
(c) InjectInput 空闲时触发新处理循环
- 新增 internal/plugins/all.go 空白导入触发所有内置插件 init()
- internal/sdk/ 作为 PluginSDK 正式 Go API
- internal/api/ → internal/plugins/webui/ 迁移
- 删除旧 cmd/cli/, 使用 cmd/waiter/ 替代
- 更新 PLAN.md / ARCHITECTURE.md / README.md 文档
2026-07-03 16:53:34 +08:00
4442c9cea4
适配多个 LLM 源 (anthropic/gemini/mistral/groq/github) + SQLite 配置收敛 + 测试插件
2026-07-03 14:03:51 +08:00
9c33b3b149
feat: ConfigRegistry + SettingsAPI for plugin config access
...
Core exposes a unified settings interface through Plugin SDK:
- internal/config/registry.go: thread-safe namespaced KV store
- Register/Get/Set/List/Delete with JSON persistence
- Flush() for explicit save to disk
- SettingsAPI in SDK: plugins call plugin.Settings().Get/Set/List
- Full access: read/write any key (core.*, plugin.<name>.*)
- plugin.Registry.SetConfigRegistry() wires it into SDK plugins
- main.go registers core.* keys at startup, flushes on shutdown
- 7 tests for ConfigRegistry
2026-07-03 08:32:41 +08:00