2d5d246606
fix(webui): SSE writer 批量合并 flush 修复流式 delta 丢包延迟
...
【根因】SSE writeCh 缓冲仅 64 且 writer 每条 delta 单独 flush。
reasoning/content 增量是高频小包(单轮 200+ 条),socket
写慢时 writeCh 迅速填满,delta 大量 DROPPED——浏览器收不到
逐 token 增量,只能等最终 agent_output 整段到达,体感明显延迟。
实测一轮 16s 纯文本回复:content_delta DROPPED 202 次、
reasoning_delta DROPPED 345 次,前端全程无流式渲染。
【修复】
- writeCh 缓冲 64 → 512
- writer 加 16ms 批量合并窗口:窗口内收集的增量一次性 flush,
或满 64 条立即 flush;done 退出前 flush 残留。
flush 次数从 N 降到约 N/64,socket 写压力骤降。
修复后实测 0 DROPPED,delta 全部实时送达前端。
2026-08-26 17:59:33 +08:00
ddef1956b5
fix(agent): 流式并行 tool_call 按 JSON index 分桶,修复空参数调用
...
【根因】内核流式解析层丢弃了上游 SSE 分片的 OpenAI index 字段:
- openAIToolCall 结构体无 index 字段,JSON 解析即丢
- homed 的 openai.lua 转换为扁平结构时同样未透传 index
- accumulateStream 退而用 Go range slice 序号做累积桶 key,
但每个 SSE chunk 只含一个 tool_call 元素,序号恒为 0
于是并行多工具调用(index=0,1,2,3)的所有分片全部写入同一个桶:
name 相互覆盖、args 碎片混拼成非法 JSON → parseToolArgsJSON
失败返回空 map → 工具以空参数被调用(spawn_child 报'请提供 task'、
cmd_run 报'command is required'等),agent 只能串行重试自愈。
单工具场景只有一个 index 无污染,故简单请求一直正常;
pi 直连同一 llmsproxy 正常(其实现标准按 index 累积)。
【修复】
- ToolCall 增加 StreamIndex(json:stream_index),openAIToolCall
解析上游 index 并透传;openai.lua 输出 stream_index 字段
- accumulateStream 以 tc.StreamIndex 为累积 key
- flushToolCall 区分三种空参:未收到分片/碎片非合法 JSON/合法空
对象({}),分别打诊断日志,避免误报
- 回归测试 TestAccumulateStreamParallelToolCallsByIndex 模拟
4 路并行分片流验证按 index 正确分组与参数完整性
另含 spawn_child max_turns 参数、child_result 运行中状态区分、
provider 层非流式空参诊断日志。
2026-08-26 16:10:02 +08:00
c945eace1a
fix(webui): 附件死锁 + qq 插件文件发送收敛到 output 通道
...
1. webui 附件死锁修复(output_send__webui 发图 60s 超时根因)
EventAgentOutput 订阅者已持 chatMu,附件分支调 addChatMsg(内部
再次 Lock)——sync.Mutex 不可重入导致 Publish 永久阻塞,工具超时、
图片永远发不出来。改为持锁状态下直接操作 chatHistory+persist。
2. qq 插件 image/file 分支收敛到 output 通道
output_send__qq 的 type=image/file 此前只透传 payload 给 CQ 码,
发本地文件必须绕道 upload_group_file 独立工具。现在与 voice 分支
同模式:本地路径拷入 NapCat 共享目录转 file:///app/files/<name> URI,
http(s)/file:// URL 保持透传。upload_group_file 保留作为群文件柜
专用入口。
验证:工具链重编译 → qq.hmap 重打包 → pluginmgr overwrite 安装
(config_kept=true)→ agent 经 output_send__qq 用本地路径发图成功,
mascot.webp 已落共享目录。
2026-08-26 11:14:03 +08:00
cb76828e43
fix(cmd/files/webui): shell 语义修复 + 根沙箱误判 + 上传注入走 interrupt + UI 区分附件来源
...
1. cmd_run 改经 /bin/bash -c 执行完整 shell 语法
旧实现 shellUnquote 拆词后直接 exec:'pwd; ls /' 变成执行名为
'pwd;' 的程序(exit -1)、heredoc 被截断、管道/命令替换全部失效——
agent 多次反馈命令解析奇怪即此。危险命令拦截(kill homed 等)保留。
2. files 沙箱根目录判断修复
pathWithinSandbox 在 base='/' 时 prefix 变 '//',所有绝对路径误判
逃逸(生产实锤:files.dir=/ 下 files_read/write/ls 全部报 outside
sandbox)。根沙箱直接放行。
3. webui 文件上传注入改走 interrupt(system 角色)
文件元信息不再混入用户消息气泡;用户附言作为正常消息先行注入,
文件说明紧随其后以 no_memory interrupt 补充——对齐 terminal_watch/
timer 工具提醒模式,聊天流保持干净。
4. 前端附件卡片按 role 区分来源
user=右侧+『你发送的』标签+accent 底色;assistant=左侧+『小宅发送的』。
📌 emoji 按钮换为 SVG 图标,前端 emoji 清零。
2026-08-26 10:24:47 +08:00
534232b768
fix(webui): 附件消息历史持久化 + 用户文件上传(对齐 qq 插件收文件设计)
...
1. 附件展示链路补全
- ChatMsg 新增 Attachment 字段(type/url/size/name),channel_output
订阅提取 output_type/url/size 存档——修复刷新后附件变纯文本路径
(如 '/tmp/homeagent.png')的问题
- EventRawInput 订阅支持 upload_* 字段:用户上传的消息也带附件卡片
2. 用户→agent 文件上传(POST /api/v1/chat/file)
设计对齐 qq 插件收文件模式:
- multipart(file + message) 落盘 <data>/uploads/<原文件名>(重名加
毫秒后缀,路径穿越消毒),单文件上限 64MB
- 注入文本「[用户通过 webui 发送了图片: 名字 (大小)] + 保存路径」,
agent 用 files_read 等工具按路径消费
- 回复走与普通消息相同的 SSE 流式管道
- GET /uploads/<name> 下载,与 /files/ 同一鉴权与安全模型
3. 前端
- 输入区 📎 按钮;拖拽到聊天区直接发送;粘贴截图即发送
- 本地预览用 URL.createObjectURL 即时显示
2026-08-26 10:03:46 +08:00
0afa84a13f
feat(webui): agent 可向 webui 发送图片/文件,前端内联展示与下载
...
输出通道能力升级:webui 通道从 CapText(1) 扩展为
CapText|CapFile|CapImage(7),agent 经 output_send__webui 即可发送
image/file(此前仅文本)。
服务端:
- stageWebFile 把本地路径文件拷贝到 <data>/webui_files/<hex>.<ext>
(随机名防猜测、危险扩展名强制 .bin),http(s) URL 直接透传不落盘
- 新增 GET /files/<name>(requireWeb 与 dashboard 同鉴权):扩展名
白名单映射 Content-Type,图片/音视频 inline、其余 attachment 下载,
nosniff + 路径穿越拒绝
- SSE agent_output 事件携带 output_type/url/size 字段
前端(dashboard.html):
- channel_output 识别附件消息:image 渲染内联预览(点击原图)、
file 渲染下载卡片(含大小);formatBytes 人性化显示
典型场景:agent 把 remotedevice 回传的录像/截图(device_media/*.mp4)
直接发给 webui,用户在聊天里看到视频预览或一键下载。
新增 TestStageWebFileAndDownload / TestHandleFilesAuth 覆盖。
2026-08-26 09:17:07 +08:00
fad490dca0
fix(remotedevice): 媒体回传落盘 + webui SSE panic + GUI 相机跨平台
...
1. remotedevice 媒体落盘(核心改动)
设备录像/照片二进制聚合后写入 <data>/device_media/<reqID>.<ext>,
cmd_result 返回 file 路径,不再 base64 内联——10s 录像数 MB 的
base64 会撑爆 LLM 上下文与工具结果管道。未配置目录时保持旧内联行为。
新增 TestWSBinaryMediaToFile 覆盖。
2. webui SSE 'send on closed channel' panic(生产单日 4924 次)
handleChatEvents 的 defer close(writeCh) 与 Subscribe 回调闭包竞态:
handler 退出后总线仍可能异步触发回调向已关闭 channel 发送。
改为 writer goroutine select on done 退出,不 close channel;
defer 中等待 writerDone 保证无残余写入。
顺带补 mockPluginMgr.StopAndUnload(ae42e48 接口变更漏改测试)。
3. GUI camerasue 平台分支
ffmpeg 参数原硬编码 Linux v4l2(/dev/video0),Windows 上必然失败。
现按平台探测:win32=dshow(枚举设备名取第一个视频设备)、
darwin=avfoundation、linux=v4l2;录像编码 Windows 交给 mp4 muxer 默认。
2026-08-26 01:25:40 +08:00
168c88593d
fix(remotedevice): WS 握手 Accept 改用 SHA-1(RFC6455 合规)
...
wsAccept 误用 sha256 计算 Sec-WebSocket-Accept,RFC6455 §4.2.2 规定
必须为 base64(SHA1(key + GUID))。后果:所有标准 WS 客户端(浏览器/
Electron/各语言标准库)校验 Accept 失败后立即断开连接,设备永远无法
完成 hello 注册——devicedetect 恒返回空列表,核心看不到任何设备,
而设备端本地授权状态正常,形成'已授权但核心看不见'的表象。
验证:openssl sha1 对照 + 干净实例端到端握手 Accept 完全一致。
2026-08-25 23:48:20 +08:00
ae42e486de
feat(pluginmgr): 插件更新接口(upgrade/downgrade 保留配置)+ skill_install overwrite
...
内核 Registry 拆出 StopAndUnload:
- 停止并从注册表移除插件但保留 config_<name> 表
- 不触发 onRemove 回调(那是删除专用语义)
- RemovePlugin 改为追加清理配置表示清除,更新场景调 StopAndUnload
pluginmgr:
- installFromData/installFromURL/installFromPath 加 overwrite 参数
- 已存在+overwrite=true:StopAndUnload→备份旧目录→解压新包→失败回滚→
返回 action=upgraded/downgraded/reinstalled+previous_version+config_kept
- 已存在+overwrite=false:返回 error+hint(指向 overwrite 用法)
- cmpVersion 点分版本号数字比较(非字典序)
- 测试覆盖:首次安装→重装拒绝→升级保留配置→降级→失败回滚
skill_install 加 overwrite 参数:
- 同名技能存在时先卸载旧实例+删除目录再安装新包
SDK PluginMgr 接口同步加 StopAndUnload(name string) error
工具链 plugindev 已重建到 /usr/local/bin(7/29→8/25 版本)
QQ 插件诊断日志版(webhook recv 到达+isAtBot 失败日志)已打包并
通过 upgrade 接口热更新部署,配置保留验证通过。
2026-08-25 22:02:17 +08:00
5f126d4d10
feat(skillmgr): 原生技能管理器插件 + OpenClaw 兼容层职责分离
...
新增 internal/plugins/skillmgr(native skill 全生命周期 owner):
- skill_list/info/load/unload/enable/disable/create/export/install
- skill_create 两步式:先生成骨架模板,LLM 补全后传 content 覆盖写入
(plugin.ValidateSKILLContent 校验)并自动加载生效
- .skm 分发包(tar.gz):packSkill/unpackSkill 含 TarSlip 防护
(拒绝绝对路径/../逃逸、强制单根目录、校验包内 SKILL.md)
- skills 目录扫描:纯 SKILL.md/skill.json 条目归本插件;
sidecar(main.js/main.py)/OC plugin(openclaw.plugin.json) 留给兼容层
clawhubadapter 职责分离(OpenClaw 兼容层不再持有 native skill):
- 删除 p.skills 字段与 default 分支 LoadSKILL 逻辑
- 发现纯 SKILL 条目改为发布 events.EventSkillDetected 移交事件,
由 skillmgr 订阅注册;启动时序 c<s 下全扫兜底,事件用于热新增
- claw_list/plugin_info 不再输出 SKILL 段,统一走 skill_list
方案B prompt 注入:
- agentCore 新增 SkillIndexProvider 接口 + SetSkillIndexProvider
- buildSystemPrompt 注入【可用技能】轻量索引(名称+版本+描述),
LLM 匹配场景时主动 skill_info 拉全文按文档执行
- main.go 在插件加载后将 skillmgr 实例接线到 agent
内核小修:
- extractDescription 跳过 YAML frontmatter 块(此前所有带 frontmatter
的 SKILL.md 描述都被误判为 '---')
- extractField 剥离 YAML 成对引号(version: "1.0" 不再带尾引号)
- plugin.ValidateSKILLContent 导出供生成侧校验
2026-08-25 20:25:57 +08:00
51eb98e0ae
fix(mcp+adapter): 流式 tool_calls 解析修复 + MCP transport 超时保护
...
openai adapter transform_stream_chunk:
- OpenAI 流式分片是嵌套格式 function.{name,arguments},原样透传后
json.Unmarshal 到扁平 ToolCall{name,arguments} 时 name 恒为空,
accumulateStream flushToolCall 因 acc.name=="" 静默丢弃整个工具调用
(流式路径自上线起 tool_calls 全部丢失的根因)
- 现在正确解包 function.name → name,function.arguments → raw_arguments
(保留原始 JSON 字符串分片,由 accumulateStream 按 index 拼接)
- 不按 name 过滤分片:OpenAI 流式续传块 name 为空但携带 arguments 分片
mcp stdio/sse transport:
- stdio Send() 无超时:server 进程卡死时插件加载永久阻塞
- sse http.Client 无超时:远程 server 网络抖动/无响应时永久阻塞,
导致 webui 等后续插件全部无法启动(生产实例偶发启动卡死根因)
- stdio 加 60s 请求超时;sse client 加 30s 整体 + 10s 拨号超时
2026-08-25 18:59:00 +08:00
79b7766ed4
fix: 流式渲染回合生命周期 + LLM 瞬断重试与 SSE body 兜底
...
问题一(webui 不是真流式):
- sendChat 的 finally 在 POST 结束(15s ackTimer abort)时就复位
chatLoading,但 agent 生成窗口 15~190s,后续 SSE delta 全部走
全量重建路径、停止按钮提前消失、用户误发重复消息。
- GUI app.js 完全没有 content_delta/reasoning_delta 监听器,
只能等聚合帧一次性显示。
修复:三端统一回合生命周期——POST 只是触发,收尾由 SSE 驱动:
- dashboard/GUI 新增 endChatTurn/armTurnWatchdog;拿到同步兜底
响应立即收尾,否则保持回合打开等 agent_output final / reset 帧 /
120s watchdog 兜底
- GUI 补齐 delta 监听器;agent_output 聚合分支 += 改覆盖;
reasoning 聚合帧改覆盖(多轮工具调用时旧逻辑会重复累加)
- agent_output 误杀分支(final 无 source 即 return 丢弃新输出)
改为内容比较去重,多轮连发时新一轮回复不再被吞
- waiter reasoning_delta reset 从清空全部消息改为 sealLastAgent
问题二(三条只成功一条):
- handleChat 60s ctx 含排队时间,agent 串行处理下第 N 条必超时
(实测第 3 条 62s 超时 504);放宽到 300s(客户端 abort 时立即取消)
- LLM 单 provider 瞬断无重试:process.go provider 循环内加同源
重试(2 次、退避 2s),401/403 凭证错误与用户中断不重试
- llmsproxy auto 链在非流式请求下可能返回 SSE body(上游恢复后
吐已生成的 chunk 流),非流式解析报 invalid character 'd' 丢掉
整段回复;新增 parseOpenAICompatibleSSEBody 拼接为完整响应
- 顺带修 normalizeStreamToolCalls 分片续传 bug:name 不重发时
argsRaw 被顶层 Arguments(nil) 覆盖丢失 function.arguments
验证:
- 连发 3 条 + 单条共 4 条全部成功(首条 190s 重试扛住瞬断)
- sse_body_test.go 锁定 SSE body 解析契约(content/usage/tool call 分片)
2026-08-25 12:24:11 +08:00
061d2ae320
feat(streaming): token-level delta events + interrupt for CLI/WebUI/GUI
...
Expose the LLM token-level streaming deltas (EventReasoningDelta /
EventContentDelta) to every client channel and add user-initiated
interrupt (cancel generation / send interrupt message) to all three
frontends, preserving the existing interrupt-injection semantics.
SDK/events:
- EventReasoningDelta, EventContentDelta constants exported in the
public/internal SDK event alias tables.
CLI plugin:
- handleChat subscribes to both delta events and forwards
reasoning_delta / content_delta JSON frames (channel-filtered);
aggregated reasoning/tool_call/response frames still fire as before.
- New /stop (alias /interrupt) builtin injects an interrupt via
InjectInterrupt(cliSource, cliChannel) - matches interceptLoop
semantics: cancels an active stream and re-injects the message as
a [中断消息] for a restarted turn; with no active LLM it behaves
as a plain input.
Waiter client (line mode + TUI):
- streamRender accumulates delta chunks and redraws the current line;
a reset frame (stream abandoned, e.g. user interrupt) flushes the
partial buffer so the next turn does not concatenate onto stale
content. Aggregated frames terminate the delta line and render the
final text (old servers without deltas behave exactly as before).
- TUI merges content_delta into the in-flight agent message and seals
it (final flag) on response/tool_call/error so subsequent deltas
never append to a finished message.
WebUI:
- SSE handler subscribes to the two delta events but does NOT record
them into the replay ring - reconnection replays only aggregated
events (the final truth), avoiding duplicate delta accumulation.
- POST /api/v1/chat/interrupt calls InjectInterrupt(webui, webui)
with optional message; fronted by a Stop button shown only while
a generation is in flight.
dashboard.html / GUI app.js:
- Stop button next to Send (hidden until chatLoading); interruptChat
POSTs /chat/interrupt. Delta listeners append incrementally;
agent_output (aggregated) now replaces (not appends) the in-flight
content and marks _final; reset frames finalize the partial message.
process.go:
- chatStreamWithFallback preserves the context.Canceled/
DeadlineExceeded contract: a user interrupt returns the canceled
error (never a partial-content success) so the existing continue
branch restarts the turn with the [中断消息]. A reset
EventContentDelta is published so connected clients drop stale
partial renderings before the new turn begins.
Verified: /stop 'msg' via waiter triggers 'interrupt from cli/cli' in
interceptLoop; unit TestChatStreamCancelPreservesInterrupt confirms the
canceled error propagates instead of being swallowed.
2026-08-25 10:50:37 +08:00
ece06b0375
feat(cli): streaming process output with npm-style spinner
...
CLI 对话现在像 npm 安装一样先显示 braille 加载动画,然后逐步吐出
推理内容和工具调用状态,最后输出最终响应。
协议扩展(JSON 行,向后兼容):
- {"type":"reasoning","content":...} 推理过程帧
- {"type":"tool_call","tool":...,"status":...,"result":...} 工具调用帧
- response / error 仍为终结帧,语义不变
服务端(internal/plugins/cli):
- handleChat: 通过 SDK 订阅 EventReasoning/EventToolCall(按 channel=="cli"
过滤),InjectTextSync 阻塞期间实时转发事件到 socket;connWriter 互斥
保护并发写。纯插件层实现,不触碰内核。
- 不订阅 EventAgentOutput:内核先写 ResponseCh 再 publish 该事件,
订阅会导致响应重复。
客户端(cmd/waiter):
- startSpinner: npm 风格 braille 转圈(80ms),幂等 stop(),非 TTY 自动禁用
- SendChatStream: 循环读帧直至终结帧,onEvent 回调渲染过程帧
- printServerOutput: reasoning 灰色 · 前缀;tool_call ✔/✘ 状态行 + 结果预览
- 交互模式发送后自动起 spinner,首帧到达即停;oneshot 同理
- 向后兼容旧服务器(无类型行直接作为最终输出)
端到端验证:本地 homed 测试实例 + llmsproxy,oneshot 与交互模式均正确
渲染 推理→工具调用→最终响应 完整链路。
另外修正 dashboard.html renderReasoningCard 流式态使用 preview 结构
(与 GUI 渲染器一致,配合此前 renderChatStreamChunk 增量更新)。
2026-08-24 23:34:48 +08:00
121a2b9ace
fix(webui): 聊天流式增量渲染(移植 GUI renderChatStreamChunk 方案)
...
问题:webui 流式推理阶段虽有 90ms 防抖,但每次防抖到期仍是
全量 innerHTML 重建,视觉上「一下渲染一大块」。
修复:移植 GUI 的增量渲染方案——
- rerenderChat() 在流式中(chatLoading && 最后一条未 _final)走
增量路径:90ms 合并 chunk 后只调 renderChatStreamChunk()
- renderChatStreamChunk 仅更新最后一条消息节点:
· 正文 >200字符 或 >300ms 才 renderMd(节流 markdown parse)
· 小增量纯文本 createTextNode 追加,零 parse 开销
· 思考预览只刷 .reasoning-preview 文本
· 结构变化时兜底全量 renderChat()
- 工具卡/历史等非流式变化仍走全量路径
2026-08-24 22:40:48 +08:00
8157772132
fix(webui): sendChat 超时后 SSE 兜底渲染(对齐 GUI 模式)
...
问题:agent 长任务(159s)超过 15s ack 超时后,旧代码 return 导致
finally 清空 loading 并重置按钮,用户以为失败;SSE 回复虽到但
用户可能已离开/刷新页面。
修复(完全对齐 GUI renderer 的 sendChat 模式):
- 超时后 r=null 不 return,流程继续
- finally 总是清 loading + 恢复按钮(loading 只是 UI 提示)
- toast 明确提示「请求超时(可能已发送,请稍候勿重复发送)」
- r 有值则直接填充最终回复;r=null 则靠 SSE agent_output 流式渲染
2026-08-24 22:36:45 +08:00
e1a94fc896
fix(webui): 聊天渲染优化 + 触发式 POST
...
- 删除死的 tool_result SSE 监听器(后端不发布此事件类型)
- 流式渲染防抖 90ms:SSE 高频 chunk 合并为一次重建,流式期间跳过
星图/终端/命令历史等无关渲染(renderChat 签名短路 + 防抖双重优化)
- sendChat 改触发式 POST:15s 短超时仅确认受理,超时后不报错,
回复靠 SSE 流式渲染(对齐 GUI 行为,解决长 LLM 工具链 60s 超时报错)
- sendChat 用户操作走 rerenderChat(true) 全量重渲;SSE 流式走
rerenderChat() 防抖仅聊天
2026-08-24 21:53:53 +08:00
ce53e8816b
chore(webui): 清理前端死代码
...
- 删除 8 个未引用 JS 函数: confirmDialog/showToast/timeAgo/statCard/
systemTheme/getStarmapBg/resetStarmapCamera/toggleStarmapAuto
(实际使用的是 toast()/内联卡片构建/setTheme)
- 删除星图 hover info 空转块(starmap-info 容器不存在,infoEl 永远 null,
sm-info-name/type/mentions/links 子元素查询全部无效)
- 删除死 CSS: #starmap-container/#starmap-stats/#starmap-info/
#starmap-loading/.starmap-toggle(对应 HTML 元素已不存在)
- 删除重复的 #sm-container-chat height:480px(被后续 260px 覆盖)
共 -266 行,JS/CSS 语法验证通过
2026-08-24 21:01:06 +08:00
2ee007edc9
chore: 清理垃圾文件与构建产物
...
- 删除 internal/plugins/webui/dashboard2.html(遗留压缩实验代码,含乱码,零引用)
- 删除根目录旧二进制 homed/waiter(build/ 已有最新版)
- 删除 build/ 实验二进制 homed2/3/4/-audio/-img/-lua/-multi/-sched/_v2
- 删除 deploy/build 重复发布包 + dist 打包产物
- 删除 .gopath(旧 SDK v0.7.2 模块缓存,已 replace 到 third_party 本地副本)
2026-08-24 20:18:59 +08:00
ba5785036a
feat: 设备鉴权迁移至客户端 + 插件卸载保护
...
安全修复(客户端鉴权):
- remotedevice 服务端移除授权状态存储(authorized map/SetAuthorized/handleDeviceAuth)
- DeviceMeta.Authorized 改为设备 hello 自报,服务端仅透传展示
- device_ctl_* 工具移除服务端授权检查,无条件转发,设备端自行决定是否执行
- 共享设备桥库 Bridge 新增本地 authorized 状态,未授权收到 cmd 直接拒绝
- waiter: --device-authorized / device_authorized 配置控制本地授权
- GUI: 授权存 gui-prefs 本地文件;设备页仅本机可切换开关
- webui /device/auth 旧路径返回 410 Gone
- 根因:agent 可经 config_set 篡改服务端授权配置自行授权设备
插件管理强化:
- 内置插件禁止卸载(IsBuiltinPlugin + 409),外部插件卸载即时生效
- 卸载不存在插件返回 404;移除误导性 reload_required 提示
- webui 插件路由:名称白名单校验防路径穿越、保留字路径保护
2026-08-24 19:26:11 +08:00
5163ce51a7
feat: SSE Last-Event-ID 断线重放 + GUI 表单防刷新
...
- webui handler: 新增 sseEventRing 环状缓冲区(200条),断线重连按 Last-Event-ID 重放遗漏事件
- GUI app.js: doRenderAll 检测连接表单打开时改走 refreshDataOnly,修复 15s 定时器擦掉用户输入的 bug
- 附带 dashboard.html/index.html 前端调整 + handler_sse_test.go 单测
2026-08-24 16:22:28 +08:00
4dcd3623cb
feat: 设备能力矩阵校验 + 设备主动上报事件通道
...
回应架构讨论: 外接设备各自声明能力, 且补齐设备→agent 单向推送缺口。
1. 能力矩阵 (registry.go):
- capabilityTools 映射: caps 声明 → 可用工具
screen/screensue/screensee, computeruse, clipboard(see/sue),
camera/camerasue, speaker/speakeruse
- SupportsTool 校验: screensee/computeruse/clipboard* 执行前检查
目标设备是否声明对应能力, 未声明直接报错(不再下发到设备端才失败)
- 兼容规则: 声明 cmd/cmdrun 等历史值 → 全能力;
未声明任何已知能力 → 全能力(旧设备兼容);
有已知能力声明则严格匹配
2. 设备主动上报事件 (WS op=event):
- 设备可推 {op:event, type, detail/payload} 无需回执
- 插件层 SetEventHandler 回调: 格式化为人类可读文本,
经 SDK InjectInput 异步注入 agent(source=device/{id}, 回复路由回同通道)
- 同设备同类型事件 10s 节流防传感器风暴
- 场景: 摄像头识别未知人员驻留主动告警, agent 收到后自主处置
测试: 能力矩阵8场景 + 摄像头调screensee被拒 + 事件上报回调(含device_id回退)
2026-08-21 19:58:37 +08:00
c0e92cceb1
feat: clipboardsee/clipboardsue 工具 — agent 读写远程设备剪切板
...
与 screensue/screensee 同模式配对命名:
- clipboardsee: 读取设备剪切板当前内容(用户最近复制的文字)
- clipboardsue: 写入文字到设备剪切板(用户可直接 Ctrl+V 粘贴)
设计要点:
- 协议: homeagent-clipboardsee / homeagent-clipboardsue <文字>
- clipboardsee 回执 output 字段为剪切板文字, 响应含 content/empty 字段
- clipboardsue 响应含 written 字节数 + preview 截断预览(60 rune)
- 隐私提示写入工具描述: 剪切板可能含密码, 仅在用户明确要求时读取
- 典型组合写入描述: clipboardsue + computeruse keypress ctrl+v 自动粘贴
- 未授权/离线/超时完整错误路径
GUI 配套需求(已发群): executeHomeagentCmd 加 case clipboardsee/clipboardsue,
Electron clipboard.readText()/writeText(text) 一行实现。
测试: 端到端读写+参数校验+未授权拒绝 全通过
2026-08-21 16:31:23 +08:00
cacd9a8572
feat: computeruse 工具 — agent 结构化操控远程鼠标/键盘
...
配合 GUI b4e5b39 (homeagent-computeruse 能力):
- 新增 computeruse 工具, LLM 传结构化参数(action/x/y/dy/key/text/button)
- 服务端序列化为 GUI 约定的 JSON 参数格式下发, 避免 LLM 手拼字符串出错
- action 校验: click/doubleclick/rightclick/move 需坐标; scroll 需 dy;
keypress 需 key; type 需 text; 未知 action 报错
- 典型工作流写入工具描述: screensee 看屏 → computeruse 操作 → screensee 确认
- 未授权/离线完整错误路径; 结果留档 cmdresult
测试: 端到端验证 JSON 参数格式下发+回执+参数校验错误路径
2026-08-21 12:32:07 +08:00
cfe5a1a7f1
feat: screensue 默认 5s 超时,agent 可指定时长或永不超时
...
- GUI main.js: screensue 默认 duration 从 0(常驻) 改为 5 秒自动关闭
- 命令带数字 token 指定时长: "screensue 30 内容"=显示30秒
- 命令带 0 表示永不超时: "screensue 0 重要公告"=常驻直到用户手动关闭
- gui-prefs.screensueDuration 用户配置默认值(未配置时 5),命令参数优先级最高
- 设置页(app.js)新增「默认时长(秒)」输入框(placeholder 提示 0=常驻)
- 回执文案区分 for Ns / persistent until closed
- 服务端 device_ctl_cmdrun 工具描述同步更新(5秒默认/指定秒数/0永不超时)
2026-08-21 12:10:28 +08:00
5b0cd45093
feat: screensee 工具 — agent 查看远程设备屏幕内容
...
与 screensue(向用户屏幕显示)配对: screensue 是给用户看, screensee 是 agent 看。
服务端实现:
1. remotedevice 新增 screensee 工具:
- 下发 homeagent-screensee 命令 → 设备截屏回传 jpeg base64
- seeHandler 回调(agent 核心注入)用视觉模型自动描述屏幕内容
- 未授权/离线/超时完整错误路径; 结果留档 cmdresult
2. SDK LLMMessage 扩展多模态 Blocks(text/image_url):
- llm_impl 转换为 agentAPI.ContentBlock, 视觉模型可看图
3. describeScreen: 默认提示词描述窗口/文字/界面状态;
provider 参数可指定视觉源(临时切换后恢复)
GUI 端需配套(已发群): onDeviceMsg 加 case "screensee",
desktopCapturer 截屏 → jpeg base64 data URL 回执(同 camerasue 抓拍模式)。
测试: 端到端模拟设备截屏回传+视觉回调验证; 全项目 go test 通过
2026-08-21 11:31:45 +08:00
f2e3215c77
feat: remotedevice 二进制分块协议(录像回传+音频下发) + 工具描述增强
...
回应 GUI c29abe9 需服务端配套项([阻塞]两项 + [中]一项):
1. readFrame 支持 0x2 二进制帧(之前遇 0x2 直接断连):
- 返回 opcode, 二进制帧上限放宽至 8MB
2. handleWS 二进制聚合协议(设备→网关, 录像回传):
- cmd_data_start 开启按 req_id 聚合 → 0x2 帧追加 → cmd_data_end 聚合完成
- 结果存 cmdresult(data_base64 字段), device_ctl_cmdresult 可取回
- 超限防护(声明 total×2 或硬上限 64MB)
3. PushData 下发协议(网关→设备, 音频/TTS):
- cmd_speech_start → 0x2 分块(8KB) → cmd_speech_end
- GUI 端已实现接收侧(speakeruse 播放链路打通)
4. device_ctl_cmdrun 工具描述补齐:
- screensue 带参示例(<内容>/<秒> <内容>)
- camerasue 录像说明(cmdresult 含 data_base64)
- speakeruse 文字朗读
测试: 最小 WS 客户端端到端验证二进制分块上传/PushData 下发/离线报错
2026-08-21 10:53:38 +08:00
8d368913a9
feat: webui 消息重放双重防护(client_msg_id 去重 + agent 内容级去重)
...
回应群聊 08-19 消息轰炸诊断(GUI SSE 重连导致消息重放):
1. webui 层 client_msg_id 单飞去重(与 GUI c29abe9 配套):
- /api/v1/chat 解析 client_msg_id, 同 ID 重放等待首次结果直接复用
- 响应带 deduplicated=true 标记; 无 ID 旧客户端完全兼容
- FIFO 缓存上限 256 条防泄漏
2. agent 核心层内容级短窗口去重(兜底无 ID 客户端):
- isDuplicateInput: source+content 为 key, 10s 窗口内重复丢弃
- 持续轰炸时刷新时间戳保持拦截; 过期项自动清理
测试: webui 去重三场景 + agent 核心去重行为验证, 全项目 go test 通过
2026-08-21 10:23:38 +08:00
768c73889e
feat: webui chat 接口支持设备身份(device_id/device_name)
...
- handleChat body 增加 device_id/device_name 可选字段
- 来源编码: 带设备时 webui/{device_id}(agent 可见来源), 无设备保持 webui(兼容)
- injectSourceContext: 检测 device_id 时注入「当前输入来自设备[名](id)」上下文
- 验证: 带 device_id=gui-pc-002 消息 agent 正确回答来源客厅电脑; 无 device_id 兼容
2026-08-18 11:49:57 +08:00
707a01a2dd
fix: SSE 心跳直接写 w+flush(事件密集/队列满仍保活长连接)
...
- 原心跳经 writeCh 发送, 事件密集时队列满被 drop, 远程 nginx 网关长时间无字节导致 504/半开
- 改为主循环直接 Fprintf(w, ": heartbeat\n\n") + Flush, 绕过 writeCh, 保证 15s 恒有心跳
- 对应 pi-desktop: GUI 回复丢失(SSE 3分钟被 ERR_INCOMPLETE_CHUNKED_ENCODING 切断)
2026-08-18 09:12:52 +08:00
653dd34299
feat: 插件工具按需拉取(get_plugin_tools) + 设备命令类型拆分(shell-cmd/homeagent-cmd)
...
1. 提示词去污染: buildToolCatalog 从全量工具定义改为按插件分组摘要
(插件名 + 工具数 + 能力概览), 完整工具定义由新工具 get_plugin_tools(plugin_name)
动态拉取; 新增 executeGetPluginTools 支持按插件过滤 stageHost/io 工具
2. 设备命令类型: device_ctl_cmdrun 的 command 支持前缀区分
- shell-cmd <cmd> -> 设备端执行原生 shell
- homeagent-<cap> -> 设备端 HomeAgent 内置能力(如 camerasue/screensue)
- homeagent-cmd <cap> -> 同上(兼容写法)
PushCmd 增加 cmd_type 字段下发给设备端分发
3. 测试: TestExecuteGetPluginTools 验证按插件拉取/全部摘要/未知插件
2026-08-18 09:07:21 +08:00
b2156a0987
fix: webui 设备网关 WS 反代改用 net.Dial 手动升级握手
...
- 之前 http.DefaultClient/Transport 不支持 ws:// scheme 与 101 升级, WS 经反代返回 502
- 改为 net.Dial 直连网关 + 手动构造 WS 升级请求(X-API-Key 注入) + http.ReadResponse 读 101
- hijack 后双向字节透传(WS 帧不动), 客户端->上游/上游->客户端
- 实测: 经 webui:8080/api/v1/device/ws 反代, 101 握手成功, 网关 Sec-Websocket-Accept 正确
- 对应 pi-desktop 排查记录: 远程 WS 设备通道现在可经远程 homed 使用
2026-08-17 18:16:57 +08:00
dd60f41aa1
feat: webui 设备网关 WS 反代(hijack 双向透传) + GUI 设备页本机卡片始终显示
...
- handleDeviceGatewayProxy: 识别 Upgrade:websocket 请求, 用 http.Transport(保留升级连接)
+ hijack 双向字节透传, 支持远程 homed 的 WS 设备通道(REST 已可用, WS 之前被 DefaultClient 卡死)
- renderDevices: 本机 GUI 设备卡片不再被 conn.type!=='device' 挡住,
由 gui-prefs 的 deviceBridge 驱动(独立于连接), renderInit 从 device-bridge:get 拉取本机身份
- 对应 pi-desktop 排查记录(WS 反代不可用/本机卡片被挡)修复
2026-08-17 18:02:16 +08:00
a9ec8f5b2b
Merge branch 'main' of gitcode.com:JianFeeeee/HomeAgent
2026-08-17 09:34:42 +08:00
ef5f010e87
device gateway: remotedevice 插件(设备接入网关) + GUI/waiter 受控设备桥 + 设备页/授权开关/托盘/退出进托盘 + 白屏修复(惰性Tray) + deviceinfo 工具
2026-08-17 09:34:25 +08:00
2338f3f4f3
fix: 插件增量重载 + clawhubadapter ipcGoroutine 优雅退出
...
- Registry.Reload 改为增量: 对比插件入口文件(plugin.so/main.lua) SHA256, 仅重载有变更的插件, 未变更保持运行, 消除 plgreload 触发全量 StopAll+Load 导致的重复加载与内置插件状态错乱
- Registry 新增 pluginHashes 记录 + pluginEntryHash 辅助
- clawhubadapter.ipcGoroutine 无退出条件导致重载后旧 goroutine 永久残留(线程累积): 增加 stopCh/stopOnce, Stop() 关闭, ipcGoroutine 用 select 监听退出
- 新增 TestRegistryIncrementalReload 验证无变更跳过/变更重载
2026-08-17 08:53:39 +08:00
eb577afed2
fix: healthcheck auto-check 幂等重启(stopCh 可重建, 修复 plgreload 后定时器失效)
2026-08-16 18:27:25 +08:00
c52331cd5e
gui+webui: 聊天界面升级(PiDeck风格思考/工具卡片+滴入动画) & 流式卡顿修复(防抖局部增量渲染) & 主题系统修复(启动恢复色板/弹窗溢出wrap)
2026-08-16 09:57:29 +08:00
1bc2bf5d8d
fix: WebUI 配置数字科学计数法 + 增强输出通道引导提示词
...
- webui settings PUT: 整数型 float64(如 QQ 号)规范化为 int64 存储,
避免 fmt.Sprint 以科学计数法(2.198972886e+09)存库导致读取解析失败
- 系统提示词【输出规则】重写: 明确消息不会自动路由,
区分同步通道(直接返回文本)与异步通道(必须调用 output_send__{通道}),
引导模型优先回复当前输入通道并带正确 meta(user_id/group_id)
2026-08-15 17:24:14 +08:00
2e47ceed8b
healthcheck: 存量改进; mcp: sse 改进
2026-08-14 16:15:24 +08:00
c820280f16
webui: 多色板主题与外观弹层(主题色/背景图/模糊)
2026-08-14 16:15:24 +08:00
76d86d4dba
fix: webui 聊天面板打开时定位到最新消息(switchTab/渲染签名命中分支/图片加载后均强制滚底)
2026-08-14 12:38:24 +08:00
d3bd4c2fe6
fix: output_send 气泡时序(chatFinalIdx 插入)与 channel_output 后流式丢失
2026-08-14 00:58:41 +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
816597caac
fix: WebUI and handler bug fixes
...
Backend:
- handleChat/handleOpenAICompletions: added 60s timeout via context.WithTimeout
- handleAgents POST: added config.Put() to persist agent config
- handleKnowledge GET: fixed error handling (was swallowing Search errors)
- addChatMsg: reduced persistence to latest 50 messages (was 200)
- SSE: added Last-Event-ID parsing for reconnection support
Frontend:
- api(): added HTTP status code checking (throw on non-ok responses)
- setInterval(renderAll, 15000): replaced with smartRefresh() that only refreshes data and redraws active non-form tabs
- Added btn-warning/btn-success CSS classes
- SSE: fixed reconnection防叠加 in onerror handler
Tests: all passing
2026-08-13 10:18:53 +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
6d64543918
feat: 内置 ai_image 插件接入 llmsproxy 生图端点
...
- 新增 internal/plugins/ai_image(开箱即用,纯 Go,无外部依赖):
- 注册 ai_image_generate 工具,POST base_url + /images/generations
- 配置项 base_url(默认 http://127.0.0.1:8081/v1 )/api_key(留空回落 core.llm.api_key)/model/size
- 兼容 OpenAI /v1/images/generations 响应(url 与 b64_json)
- plugins/all.go 注册 ai_image
- 生图上游由 llmsproxy 侧后续配置(HomeAgent 直达其端点)
验证: go build/test 全绿;部署后 ai_image 加载、工具注册、agent 可调用
2026-08-10 10:35:47 +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
cd2a27a8cc
webui: 修复 '删除源/服务器' 只置 <nil> 不真删的问题
...
前端 deleteSource/deleteMCPServer 通过 PUT value:null 删除,但后端 handleSettings
PUT 只 SetCore/SetPlugin(fmt.Sprint(nil) → 字面 '<nil>'),导致 core.llm.sources.<name>.*
等键残留不可达的 <nil> 行,污染 LLM 源与探活。
- SettingsAPI 新增 RemoveCore/RemovePlugin;settingsImpl 接入 ConfigRegistry.Delete
- webui handleSettings PUT:body.Value==null 时改走删除分支(核心表/插件表均适配)
- 现状验证:mocktest.* 残留已从本机 config.db 清除,sources=3,无无效源
2026-08-10 09:35:29 +08:00
a62f5ba0fa
agentcli: Windows ConPTY 适配分支(原生实现替代剔除桩)+ cabi loader.c build tag 修复
...
- agentcli 平台无关化:plugin.go 拆为公共层 + ptyTerm 接口,新增
pty_linux.go(/dev/ptmx 原实现搬移,行为不变)与 pty_windows.go
(ConPTY 原生实现,CreatePseudoConsole + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
注入子进程,双管道读写/ResizePseudoConsole/GetExitCodeProcess,零第三方依赖)
- plugin_stub.go 收紧为 !linux && !windows(darwin 等仍走桩)
- plugin_test.go build tag 放宽到 linux || windows
- cabi/loader.c 补 linux || darwin build tag,修复 Windows 下
cgo 禁用时残留 C 源文件的编译错误
- 验证:Linux 全量构建+测试通过;Windows CGO 交叉编译 + go vet 全包零错误
2026-08-07 12:32:45 +08:00