Commit Graph

264 Commits

Author SHA1 Message Date
5fccc31afc fix(plugin): DisablePlugin 禁止禁用未安装插件,防止脏写 disabled_plugins
问题:POST /api/v1/plugins/<name>/disable 对不存在的插件也会把它写进
disabled_plugins 表(registry.go DisablePlugin 无条件 AddDisabledPlugin),
产生脏数据堆积,且同名插件日后真实安装会被误判为已禁用。

修复:
- registry.go 新增 pluginInstalled(name):已加载 / 已注册工厂(内置) / 插件目录存在,
  任一命中视为已安装
- DisablePlugin 开头校验:未安装返回 'plugin X not installed',不写 disabled_plugins
- webui handler:未安装→404,已禁用→409(原都返回500);enable 失败含'failed'→404
- 新增 registry_disable_test.go:覆盖未安装拒绝/内置判定/目录存在判定/普通文件不算

本机端到端验证:disable 不存在插件返回404且表无脏数据;真实插件 disable/enable 正常
2026-08-27 23:29:49 +08:00
2564e53342 feat(webui): 请求IP日志记录中间件
- clientIP(): 提取真实客户端IP,优先 X-Forwarded-For(取第一跳) / X-Real-IP,回退 RemoteAddr
- logged(): 最外层中间件,覆盖全部路由,记录方法/路径/IP/认证方式/状态码/耗时
- 认证方式判定: api-key(X-API-Key/Bearer) / session(cookie) / none
- SSE长连接(/chat/events)启动即记,不阻塞等待完成
- statusWriter 捕获响应状态码,支持 Flush/Hijack 透传
- 用于排查'谁调用了什么接口'(如插件禁用等变更操作)
2026-08-27 23:04:48 +08:00
b902d61bb9 fix(gui): SSE消息同步不及时,同步webui修复
- 新增 syncChatFromHistory():增量同步,仅追加新消息不重建已有DOM→无闪烁
- handleSSEEvent 监听 sync_required 事件 → 增量补拉历史
- SSE 断连(pump退出)后先 syncChatFromHistory 补偿再重连
- startUptimeTicker 增加30s轮询兜底(补偿跨渠道消息丢失,CLI连接跳过)
2026-08-27 11:29:20 +08:00
4def5e9ed4 fix(webui): SSE消息同步不及时 + 后端缓冲加固
handler.go:
- writeCh 512→2048,新增 sendSSE() 函数(100ms短超时重试替代立即丢弃)
- After(id) 为空时发送 sync_required 事件通知前端补拉历史
- 批量 flush 阈值 64→128

dashboard.html:
- 新增 syncChatFromHistory():增量同步,仅追加新消息DOM节点,不重建已有消息→无闪烁
- 监听 sync_required 事件触发增量补拉
- SSE onerror 立即 close 阻止双连接竞态,2s后手动重连(原5s)
- init 顺序:先 loadChatHistory 再 connectSSE(避免事件与历史加载竞态)
- 30s轮询兜底(补偿SSE断连窗口期丢失的跨渠道消息)
2026-08-27 11:10:53 +08:00
14f3605f6e chore: sdk v0.9.2 vendored副本同步 + go.mod 依赖更新
- third_party/homeagent-sdk/meta/meta.go: 0.9.1 → 0.9.2
- go.mod: require v0.9.2(replace 保留,指向本地 vendored 目录确保可编译性)
- CoreVersion 同步更新
2026-08-27 09:36:49 +08:00
c44ec0f210 feat(waiter): daemon模式 + localuse本机外设插件
waiter 新增 --daemon 后台驻留模式 (daemon.go):
  - 维持 homed 连接,TUI实例经 Unix socket 接入
  - 单客户端串行模型:每个TUI独占homed响应,新客户端回放缓冲(256行)
  - 设备桥场景可无 homed 运行(纯设备桥驻留)
  - 设备桥看护循环:WS断开自动重连(3-5s间隔)
  - conn.go 新增 daemonConn 类型,dial() 优先检测 daemon

localuse 插件 (internal/plugins/localuse/):
  - local_screensee / local_camerasue / local_speakeruse
  - local_screensue / local_clipboardsee / local_clipboardsue / local_computeruse
  - 跨平台实现(Linux/macOS/Windows),能力与 waiter device.go 对齐
  - headless服务器上缺依赖工具自动返回安装提示

SDK sync: third_party SetToolBlocks 多模态类型同步
README 补充 daemon 模式使用文档
2026-08-27 09:27:15 +08:00
b777322b95 feat(multimodal): 内置多模态感知插件 + process.go 原生支持 tool message 多模态块
【新插件 internal/plugins/multimodal】
- see_picture(path): 读取本地图片/URL,base64 注入 image_url block,
  模型在下一轮 LLM 请求的 tool message 里直接看到图(1024×1024 图约 8500 token)。
  自动识别 MIME,限 3MB 防爆 context。
- see_video(path, frames): ffmpeg 提取关键帧,多帧作为 image_url block 注入。
  默认 4 帧,最大 10 帧,每帧限 2MB。
- listen(path): 读取音频文件,转为 audio_url block 注入,支持 mp3/wav/ogg/m4a。
  限 5MB。

【内核多模态 tool message 支持】
- agent/api 新增 ToolOutput 类型(为后续 handler 直接返回 blocks 预留)
- SDK 公共层新增 ContentBlock/ImageURL/AudioURL(OpenAI 多模态格式)
- IOManager 新增 SetToolBlocks/ConsumeToolBlocks(interface{} 避免循环依赖)
- PluginSDK.SetToolBlocks(blocks) 插件工具调用后注入 blocks
- ioAdapter 桥接 IOInjector.SetToolBlocks
- process.go 工具执行后消费 pending blocks → 追加到 tool message 的 Blocks 字段
  → MarshalJSON 输出 content 数组格式 → LLM 看到图/音频

【验证】
multimodal_see_picture 注入 1024×1024 PNG 后 llmsproxy 统计:
  prompt_tokens=44407(含 ~8500 image token),模型正确描述了图片内容。
2026-08-27 08:39:21 +08:00
f0cdbdb030 feat(sdk): SettingsAPI.DataDir() 插件专属数据目录 + ai_image 本地交付
【SDK DataDir API】
- SettingsAPI 新增 DataDir() string:返回插件专属数据目录
  <data>/plugin_data/<name>(内核保证存在),解决此前插件只能
  靠 GetCore("daemon.data_dir") 手工解析的缺陷
- settingsImpl 新增 dataDir 字段 + SetDataDir;Registry buildSDK
  注入(<data>/plugin_data/<name> 并 MkdirAll);main.go 接线
- cabi 新增 CORE_SETTINGS_DATA_DIR (id 51);plugindev dispatchSettings
  模板补 DataDir() 实现

【ai_image 交付本地路径】
- 生成后下载临时 S3 URL 到插件数据目录,返回本地文件路径(永久不
  过期),而非 1 小时过期的 S3 URL。带 UA 规避图床对无 UA 客户端拦截
  (此前 agent 裸 curl 验证被拒导致误报失败)
- 返回 local_paths 字段 + 提示用 output_send(type=image) 展示

端到端:ai_image_generate → plugin_data/ai_image/*.png 有效 PNG(1024²),
经 llmsproxy→siliconflow 生成。
2026-08-26 21:40:29 +08:00
d0fc7e03c1 feat(ai_image): base_url 设置项支持自定义 OpenAI 兼容网关 v1.1.0
generateOpenAI 原硬编码上游为 https://api.openai.com,无法接入
本机 llmsproxy(ModelRouter) 等标准 OpenAI 兼容网关。新增:

- 设置项 base_url(string,默认空):为空保持官方直连;非空时
  上游改为 {base_url}/v1/images/generations(约定不带 /v1 尾缀,
  拼接时自动去重避免 /v1/v1)
- Plugin.baseURL 字段 Start 时读入;注册 ConfigDef(category
  ai_image)供 WebUI 配置页展示

部署:plugindev 打包 1.1.0 → pluginmgr overwrite:true 升级安装
config_kept=true → plgreload 热加载。

配置(category ai_image):api_key=sk-gw-local-0001,
base_url=http://127.0.0.1:8081, provider=openai,
model=Kwai-Kolors/Kolors, size=1024x1024

验收:agent 实调 ai_image_generate 出图成功——llmsproxy audit
记录 type=image src=siliconflow model=AUTO ok=true(经网关非直连),
返回临时 S3 URL 下载为有效 PNG (1024x1024)。
2026-08-26 19:46:59 +08:00
5fb15f5045 feat(a2a/acp): 会话历史查询 session.get + 客户端 session_id 透传
a2a:
- tasks.get 从空壳改为按 session_id 返回会话内近 N 条消息(默认10);
  新增 session.get 别名同语义
- a2a_query(出站)接受 session_id 参数透传给目标 agent,
  响应回显 session_id + 延续提示
- A2AParams/A2AResult 加 session_id 字段;工具描述补说明

acp:
- 新增 JSON-RPC method session/get:按 session_id 返回近 N 条消息
- acp_query(出站)接受 session_id 透传给 session/new,
  响应回显 + 延续提示
- params 结构体加 limit 字段

端到端验证(回环本机):
  a2a tasks.send→建会话;session.get→返回[user/agent]交替消息列表
  同 session 第二轮延续上下文正确(记数字→答数字)
  acp session/new + session/get 同样通过
2026-08-26 19:30:32 +08:00
14aa0c880b fix(a2a/acp): 回复闭环 + 会话延续 + 同步注入(不再抢占打断)
【问题】
1. 入站请求用 InjectInterruptText 抢占打断当前对话,立即回 202 submitted,
   agent 的回复 emit 到未注册的 channel(a2a/acp)→ 请求方永远拿不到回复文本,
   只能干等超时。(acp 的 session.Replying 从未被填真回复 → SSE 永远 "(未收到回复)")
2. 无法指定/延续 session:a2a 无 session 概念;acp session/new 每次新建、
   不接受调用方 session_id,多轮上下文断裂。
3. a2a/acp 通道未注册为输出设备 → emitResponse 的回复无落点,
   output_list_channels 也不可见 → agent 困惑"回复该发到哪"。

【修复】
- 入站改用 SDK InjectInputSync 同步注入:阻塞等待 agent 处理完成,
  直接把最终回复文本返回给 HTTP 请求方(不再回 202)。
  这是 A2A/ACP 协议的合理形态——客户端控制超时,服务端同步返回。
- session 支持:a2a tasks.send 与 acp session/new 均接受 params.session_id,
  指定则延续已有会话(拼上下文前缀),不指定则新建并返回 session_id。
  单会话保留最多 10 轮历史防膨胀;30min GC 清理 2h 未用会话。
- a2a/acp 注册为输出通道(RegisterOutputChannel):回复有落点,
  output_list_channels 可见,agent 可主动 output_send 推消息。
- 注入提示词明确"直接以文本回复即可,无需调 output_send"——
  agent 不再把回复走 queued 入队而返回干净文本。

端到端验证:
  单轮:status=completed, reply=真实回复文本(非 submitted)
  多轮:同 session_id 第二轮准确复述第一轮问题(上下文生效)
  a2a v1.2.0 / acp v1.1.0 安装 config_kept=true
2026-08-26 19:16:46 +08:00
a82b5b1626 fix(webui): /files/ /uploads/ 静态路由接受 X-API-Key 鉴权
requireWeb 此前只认 cookie session,ArkTS/GUI 等 API key 客户端
加载 agent 输出的附件 URL(/files/xxx、/uploads/xxx)一律 302 到
/login。现 requireWeb 先校验 validAPIKey 放行非浏览器客户端;
无凭证仍 302 登录页,行为不变。

handleFiles/handleUploads 已有严格防穿越(拒 / \ ..),暴露给
key 客户端安全面可控。

端到端验证:X-API-Key 访问 files/uploads 均 200,无凭证 302。
2026-08-26 18:54:26 +08:00
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
41c46d131e fix(plugins): bili output_dir 系统目录黑名单 + recoverydiag db_path 沙箱限制
P3 bili: output_dir 配置项此前未校验,yt-dlp 可被配置写到任意系统目录。
加系统目录黑名单(/、/etc、/usr、/var 等),命中直接拒绝执行。

P4 recoverydiag: db_path 参数 LLM 可控,可探测读取任意 sqlite 文件。
现强制限制在 data 目录内(前缀校验),越界返回提示。

两插件重打包升版(bili 1.2.0 / recoverydiag 0.2.0)安装验证
config_kept=true,内核重启加载正常。
2026-08-26 16:57:34 +08:00
75447aa7f8 fix(plugins): 全插件审查修复(qq/a2a/memo/calendar/rss/browser)
17 个生产插件全量审查:编译/vet 全过、无硬编码密钥、内核
executeToolCall 有 panic recover + 超时兜底。发现并修复:

P1 qq: downloadURL 裸 http.Get 无超时 → 120s client(挂起泄漏)
P2 a2a: inbound http.Server 无超时 → Read 30s/Write 120s/Idle 60s
   (慢速连接占用 goroutine)
P5 memo/calendar/rss: 数据持久化直写 → atomicWriteJSON temp+rename
   (崩溃截断 JSON 丢全部数据)
P6 qq: 3 处后台 goroutine(已读标记/rcon转发/下载任务)加 recover
   (工具调用外 panic 会带崩 homed 进程)
P7 browser: dump-dom failback Kill 后补 wait 回收僵尸进程

已知可接受项:bili output_dir 用户可控(本机单用户)、recoverydiag
db_path 可读任意 sqlite(诊断工具固有权限,argv 传参无注入)。

全部经 plugindev 重打包 v+0.1 安装验证 config_kept=true。
2026-08-26 16:46:06 +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
6009ce801f feat(agent): spawn_child 支持 max_turns + 并行策略引导
1. spawn_child 新增 max_turns 参数(1-30,默认 5)
   子 Agent 工具轮数此前硬编码 5,复杂任务跑不完即截断。现可按任务
   复杂度调整;返回消息带轮数上限提示。

2. 工具描述与 system prompt 增加并行策略引导
   明确'多个互不依赖的子任务应并行 spawn 多个子 Agent,不要串行
   逐个执行;长耗时任务交给子 Agent 避免阻塞对话'——针对生产实例
   观察到的 agent 倾向自己串行处理所有子任务的问题。

小宅自定义 prompt 同步补充并行策略段。
2026-08-26 13:38:27 +08:00
c431af0902 fix(browser): render 改走共享后端(带登录态),v2.2.1
browser_render 原实现每次独立 chromium --dump-dom 冷启动:不带共享
profile 登录态、每次 ~2s 启动开销。现改为主路径经 CDP 9222 开临时
标签页渲染(Title+OuterHTML)后即关——登录态与 interactive 会话一致;
后端不可用时保持 dump-dom failback(补 30s 超时防挂死)。
返回新增 mode 字段(backend-tab / local-dump-dom)便于 agent 判断。

修复过程中清理 python 重写残留的重复函数定义。
2026-08-26 12:38:53 +08:00
bb0c274563 feat(browser): systemd 托管共享浏览器后端 + 全机 agent 标签页架构 v2.2.0
浏览器插件重新定位为本机所有 agent 的统一浏览器操作壳:

1. 主路径:homeagent-browser.service(systemd 托管)
   - chromium --headless --remote-debugging-port=9222
     --user-data-dir=<data>/browser_profiles/shared
   - 独立于 homed 生命周期,崩溃自动重启,登录态持久保存
   - 本机所有 agent(HomeAgent/pi/opencode/deepseekharness 等)
     共享同一实例:登录一次全机可用

2. 每 agent 一个标签页(CDP Target 隔离),同 source 复用已有标签页

3. browser_start 探测链:CDP 9222 在线 → 直连;服务已装未跑 →
   systemctl start 等待就绪;未安装 → 返回 need_install+guide 引导

4. 新工具 browser_install:探测 chromium 二进制 → 写 systemd 单元 →
   daemon-reload + enable --now → 验证 CDP → 返回全机共享使用指南
   (含其他 agent 经 connectOverCDP 接入的说明);无 root 权限时
   返回手动安装命令清单

5. failback:无法联网装 chromium 的机器,重试 start 自动降级本地
   spawn 临时模式(登录态不持久,仅保证功能可用)

工具链打包 v2.2.0 已部署验证:need_install 引导 / install 装服 /
start 复用与开新标签页全链路通过。
2026-08-26 12:22:15 +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
8c5b35a6b1 chore: bump version to v0.9.1
- Version: 0.9.0 -> 0.9.1
- SDKCompatibleVersion: 0.9.0 -> 0.9.1
- go.mod require homeagent-sdk: v0.8.0 -> v0.9.1
- SDK 版本独立提交(third_party/homeagent-sdk/meta/meta.go)

CABINum 仍为 900(minor=9 不变),ABI 向前兼容。
v0.9.1
2026-08-25 13:15:30 +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
28a6d3f09c feat(agent): token-level streaming in core process loop
Replace the blocking Chat() call in process() with
chatStreamWithFallback: ChatStream first, accumulate chunks, fall back
to non-stream Chat on connect failure or empty-stream failure.

Why: the non-streaming path blocked for the ENTIRE LLM generation (up
to the 180s HTTP timeout). Reasoning models thinking 60-120s plus AUTO
chain failover regularly exceeded it -> context canceled -> full turn
wasted. With streaming the first chunk arrives in ~1-3s and any
flowing token keeps the connection alive; total generation time is no
longer bounded by an overall timeout.

Compatibility (external behavior unchanged):
  - process() signature/return values unchanged
  - Aggregated events (EventReasoning / EventAgentLLMChain) still fire
    once per turn with full text after stream completion - existing
    plugin subscribers see identical payloads as before
  - New incremental events EventReasoningDelta / EventContentDelta are
    additive; old subscribers ignore unknown event types
  - Tool execution loop, memory pipeline, stage pipeline untouched

Streaming details:
  - Tool call fragments accumulated per OpenAI streaming convention:
    id/name arrive on the first fragment, arguments as raw JSON string
    shards across fragments; merged and parsed once at stream end
  - normalizeStreamToolCalls keeps nameless argument shards (the
    non-stream normalizer drops them); ToolCall gains RawArguments to
    carry shard text
  - Interrupt mid-stream returns partial content instead of discarding
    the whole generation

Verified end-to-end against llmsproxy: plain chat streams correctly;
curl confirms tool-call shard wire format ({" + command" + :"date"}
-> {"command":"date"}); unit tests cover shard merging and
content/reasoning accumulation.
2026-08-25 09:30:49 +08:00
7d6c0bb90b feat(provider): complete ChatStream with llmsproxy-grade streaming
Rewrite LuaAdaptedProvider.ChatStream to match the maturity of
llmsproxy's streaming implementation:

HTTP layer:
  - Dedicated stream HTTP client with no overall timeout (SSE must not
    be cut by the 180s Chat timeout); only a 30s dial timeout
  - Uses applyAdapterHeaders (supports build_headers dynamic signing
    hook), matching the non-streaming Chat path

Non-200 response handling:
  - New TransformError Lua hook (adapter.transform_error) for per-source
    protocol knowledge in error messages
  - Safe fallback truncation of raw error bodies (prevents HTML dump
    leakage to clients)

SSE parsing enhancements:
  - parseOpenAICompatibleStreamChunkFull: handles token usage in the
    final chunk (prompt_tokens/prompt, total_tokens/total dual keys),
    prompt cache detail fields, and empty-string finish_reason filtering
    (sensenova sends "" on every chunk)
  - Replaced old SSEScanner with bufio.Scanner (larger buffer, fewer
    allocations)

Stream integrity:
  - errorOnlyChunk detection: holds back the first chunk to reject
    degenerate streams (e.g. zen free pool's finish_reason:"network_error"
    with empty content) before any byte reaches the caller
  - [DONE] dedup: adapters that already emit a terminating done chunk
    with the real finish_reason don't get a second reason-less done
  - Clean EOF sends a final Done:true if no done was seen

Struct changes:
  - StreamChunk: added FinishReason and Usage fields for callers
  - LuaAdaptedProvider: added streamClient (lazy) + streamMu

Tested: curl against llmsproxy SSE confirms reasoning_content parsing
is correct (delta.reasoning_content), usage chunk handling works, and
[DONE] termination is properly emitted.
2026-08-25 08:30:53 +08:00
d1e502d367 fix(agent): self-input channel carries target output channel flag
The selfInputCh previously treated ALL internal messages as memory
consolidation tasks (hardcoded _consolidation_ output channel), which
silently discarded child-agent completion notifications:

  - processConsolidation never appends to conversation context, so the
    parent agent could not see that its child had finished
  - it also discards the LLM response without emitting to any output
    channel, so nothing reached the user
  - net effect: notifications vanished; parent never called child_result

Restore the intended design: each self-input message now carries a
target output channel. Only consolidation tasks (_consolidation_) go
through the no-memory path (no context write, no emit). Child
notifications carry the parent's original output channel and are
processed as normal input: appended to context, LLM sees them and can
call child_result, and the response is emitted back to the user.

Changes:
  - new selfInputMsg{text, channel} type + channelConsolidation const
  - selfInputCh: chan string -> chan selfInputMsg
  - injectSelf (consolidation) keeps _consolidation_; new
    injectSelfChannel for flagged messages
  - handleSelfInput routes on msg.channel instead of hardcoding
  - executeSpawnChild captures a.currentOutputChannel and passes it to
    runChildTask so the notification returns to the originating channel
    (falls back to "cli" when unset or consolidation)
  - executeChildResultTool: remove dead double-lock/re-check block

Verified end-to-end with tmux PTY against llmsproxy:
spawn_child -> child done -> notification processed via normal path
(log shows 'input from system -> response, tools=[child_result]'),
parent agent retrieved the child result successfully.
2026-08-25 07:52:51 +08:00
dc0ba690c6 feat(waiter): Bubble Tea TUI modernization
Replace the line-based REPL with a full-screen Bubble Tea TUI in
interactive mode (non-TTY still falls back to the line editor).

Layout (deveco-code inspired, no emoji):
  - Top status bar: HomeAgent brand + connection dot + mode + addr
  - Scrollable chat viewport with role-based rendering:
      You (purple)   user messages
      小宅 (orange)  agent responses
      · reasoning    dim gray italic, streaming-merged
      [ok]/[fail]    tool calls with status + truncated result
      [sys]          builtin command output
      [err]          errors
  - Rounded-border input box with placeholder
  - Bottom status bar: spinner while busy / hints + connection state

Key design points:
  - reader generation counter prevents stale errors from the old
    reader being mistaken for the new one after reconnect
  - handleSubmit always returns waitServer when reader is alive,
    so server responses to builtin commands like /status are received
  - History stored as *History (was copying sync.Mutex by value)
  - Chinese CJK wide-char aware word wrap with hanging indent
  - /clear /exit /quit handled in TUI; other /cmds still go through
    handleBuiltin with output captured to message area

Verified via tmux PTY: /help, /status, /clear, real chat with LLM
(reasoning -> tool_call -> response chain all render correctly).
2026-08-25 01:01:22 +08:00
dev
22de000f23 fix: bump llm http client timeout 120s→180s for llmsproxy AUTO chain failover
The local llmsproxy AUTO chain tries 6+ slots across 3 tiers sequentially.
Each failed tier incurs busyWait (2s) + upstream timeout, so a full chain
exhaustion can exceed 120s. The llmsproxy logs showed 143 'context canceled'
errors for the homeagent key — the client gave up before the chain finished.

180s gives the chain enough room to complete before the client timeout fires.
Also remove stale backup files under /usr/local/bin/.
2026-08-25 00:34:25 +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
fb8a1b8ce3 docs: README 添加 AI 辅助编程声明 2026-08-24 09:42:41 +08:00
a024dc3f5f feat: 设备桥共享库 + CLI全能力补齐 + GUI omniparse/computeruse 重构
- 抽取设备桥 WS 协议层为共享库 (internal/devicebridge/client/)
- CLI 补齐 11 项 caps 能力(screensee/screensue/speakeruse/camerasue/...)
- GUI 新增 omniparse 能力(Windows UIA 窗口解析)
- GUI computeruse 改用 koffi 直接调用 user32.dll,不再依赖 PowerShell C# 编译
- GUI computeruse JSON 解析兼容非标准格式 {x:500,y:300}
- 新增 mock-server 用于本地测试设备桥协议
- 新增 GUI DLL 桥接模块 (devicebridge_dll.js)
2026-08-23 20:17:38 +08:00
a014f449f6 gui: hello caps 补全 screensee/clipboardsee/clipboardsue 能力声明 2026-08-21 20:03:49 +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
b6c1ef15d2 gui: 新增 clipboardsee/clipboardsue 剪切板读写能力
- clipboardsee: 读取设备剪切板文字(clipboard.readText), 回执 output 为文字, content=ok/empty
- clipboardsue: 写入文字到剪切板(clipboard.writeText), 回执含写入字节数+预览
- 与服务端 c0e92cc 配对, 支持 computeruse keypress ctrl+v 自动粘贴
2026-08-21 16:43:53 +08:00
d88b9b2bc3 gui: 新增 computeruse 能力(agent 跨平台控制鼠标键盘)
- executeHomeagentCmd 加 case computeruse: 参数 JSON {x,y,action,button,text,key}
- action: click/move/doubleclick/rightclick/scroll/keypress/type
- 跨平台: Linux=xdotool, macOS=cliclick(及osascript输入), Windows=PowerShell user32
- 坐标基于 screensueDisplay 目标屏幕原点偏移
- caps 增补 computeruse

已装本机 xdotool 备测
2026-08-21 16:40:18 +08:00