|
|
3988c19880
|
fix(gui): 设备桥补 WebSocket keepalive,修「设备可见但命令下发不到」
## 实测故障(2026-10-05)
设备能力全部调用失败,但设备**在面板里能看到**。服务端实测:
POST /api/v1/device/push {"device_id":"gui-JianF","command":"..."}
→ {"error":"device gui-JianF not online"}
同一时刻 NAS 上的 waiter 完全正常(last_seen 持续刷新、push 返回 ok)。
## 根因
GUI **从不发WebSocket ping**。全文件搜不到,是唯一没有 keepalive 的设备端。
链路中间是 frp + TLS 反代,空闲超时(常见 30~60s)会静默关掉连接
—— 没有 close 事件、没有错误日志,只是数据不再流动。
服务端 handleWS 的 defer 于是执行 markOffline(curID) → 从 conns 删除。
于是形成这个割裂状态:
· devices 表里有gui-JianF ⇒ webui 面板显示在线
· conns 表里没有 ⇒ PushCmd 报 not online
「可见但不可用」正是这个割裂的表象。registry.go:735 那段 ping/pong
注释修的是「**回** pong 不能因未 bind 而失败」,与「客户端有没有发」
是两个方向的坑 —— 那段代码一直在,却从未被 GUI 用上。
## 修复
connectDeviceWS 在 opened 后启动 25s 周期的 ping(0x89 控制帧,客户端
必须加掩码)。25s 压在常见 30s 空闲阈值之下。
两条清理路径都 clearInterval:close() 与 sock.on("close")。缺任一条都会
泄漏定时器并向已销毁 socket 写入(进程不退出)。
## 验证
修复前:gui-JianF 不在 devices 列表,push → not online
修复后:90 秒后仍在列表且 online:true,push → {"status":"ok"}
(同一时刻不再掉线,keepalive 生效)
门禁新增 6 条 keepalive 判据(共 15 条):
存在 sendDevicePing / 0x89 控制帧 / opened 后启动定时器 / 间隔 ≤30s /
close 清理 / close 事件清理。
变异测试:删掉 setInterval(复现故障形态)⇒ 定时器与间隔两条转红。
|
2026-10-05 13:51:20 +08:00 |
|
|
|
906821920b
|
fix(gui): 清理由改名遗留的重复开机自启项
## 现象
本机 HKCU\...\Run 里同时存在两条指向**同一个 exe** 的自启动项:
electron.app.HomeAgent = "E:\program\homeagent\...\homeagent-gui.exe"
electron.app.homeagent-gui = E:\program\homeagent\...\homeagent-gui.exe
表面看是两个自启动,实际是同一份程序的重复 ⇒ 开机启动两次。
## 根因
Electron 的 app.setLoginItemSettings() 用「electron.app.<exe 名>」作为
注册表项名。本产物改过名(HomeAgent.exe → homeagent-gui.exe),于是写入
新项,而旧项 Electron 不会清理 ⇒ 每改一次名就多留一条。
原 applyAutoLaunch 既不去重、也不清理历史项。
## 修法
新增 cleanupStaleLoginItems():
· 只删「指向当前 process.execPath」的 electron.app.* 项 —— 不误伤别的
Electron 应用(本机还有 electron.app.TencentDocs 同为该前缀)
· 保留当前 exe 名对应的那条(Electron 期望的名字)
· 在 app.whenReady() 里也跑一次:否则旧项要等用户手动开关自启才被清
## 实测(非静态检查)
人为再造一条 electron.app.HomeAgent 指向同一 exe,启动 GUI 后:
启动前 2 条 → 启动后 1 条
日志: [autolaunch] removed stale login item: electron.app.HomeAgent
判据未改动,npm test 131 条全绿。
|
2026-10-04 15:25:11 +08:00 |
|
|
|
57bf942474
|
feat(gui): 注入改 SendInput(默认)+ 用户操作时暂缓;拆解 Coopanion 桌宠动作系统
## 拆解 Coopanion(用户指出的参考项目)
新增 DESKTOP-PET-NOTES.md,记录 Pal-AI-Lab/Coopanion 的桌宠动作系统
(packages/cortico-world-desktop-pet):
· 4 个工具:pet_say(气泡说话,可夹带动作标记) / pet_ask(提问,异步回答) /
pet_walk_to(走到 0-1 比例或 left/center/right/cursor,含走到鼠标位置) /
pet_act(只做动作; sit/sleep 是持续态)
· 动作词表 VOCAB:10 个表情 + 13 个动作,每个词多个中文别名,
英文 id 与中文别名进同一张 Map;未知词丢弃而非报错
· 脚本标记:【词,词】= 先做动作再换新气泡;<词> = 打字到那里时做、不换气泡。
让文字与动作在时间轴上对齐 —— 实现成本低但表现力强
· 设计要点:身体是 agent 唯一可见输出通道(用户看不见消息正文);
互动事件不单独唤醒模型,跟着下批事件到
可借鉴(已写入文档待办):姿态表达状态、动作与文字对齐、持续态、
动作解析容错、待机自主行为。不建议照搬:桌宠承载全部对话输出、ASR、戳摸甩。
## 注入层改 SendInput(参考它的实现)
原 PostMessage 路径只能操作原生 Win32 程序 —— Chromium/Electron 自绘界面
与游戏会忽略合成消息。这不是「平台硬限制」,是所选 API 的限制。
Coopanion 用 SendInput 注入系统输入队列,可靠性高得多。
新增三档模式 gui-prefs.deviceBridge.agentCursor:
sendinput(默认) SendInput,可靠;会动真光标,故先等用户停手
overlay PostMessage,不动用户鼠标;只能操作原生程序
real 保留旧代码路径
另有 deferMs:用户活动时最多等多久(默认 3000,上限 10000)
★ SendInput 绝对坐标是 0–65535 归一化值,不是像素。按虚拟桌面归一化
并带 MOUSEEVENTV_VIRTUALDESK,多屏下也正确(Coopanion 只按主屏归一化)。
★ 文本用 KEYEVENTF.UNICODE 逐字符发,不受键盘布局与输入法影响(比 WM_CHAR 可靠)。
## 修「用户活动感知」把自己也算进去的 bug
GetLastInputInfo 是**全局**的 —— agent 自己的 SendInput 也被算作「用户输入」。
实测:deferMs 设 0 仍被暂缓,回执写「开始时空闲仅 0ms」,即上一条命令的
注入刚把 idleMs 清零。若不排除自身注入,会永远处于「用户正在操作」状态。
按 Coopanion 的三重判定修正:
1. ownTick 自己最后一次注入时刻,之后 40ms 内的输入忽略
2. ownCursor 自己放下的光标位置,偏离 >2px 说明是用户动的
3. 无符号差值 GetTickCount 每 2^32ms 回绕,必须按无符号比较
## 修同一条命令被执行两遍
sendinput 分支把逻辑包进 runAction 箭头函数,里面的 return 只退出它自己,
于是执行完继续 fall through 到旧 real 路径。实测 8 条命令回了 16 个
cmd_result,且真光标被额外抢一次。补上外层 return。
## 判据
computeruse.test.mjs 47 条(npm test 共 131 条全绿),新增 4 条钉住本轮
踩过的坑,均做变异验证:
· 三档模式存在(overlay/real 只在条件里,不能要求赋值形式)
· 用户活动排除自身注入;且盯 send() 里的**调用点** —— 只查函数名时
删掉调用仍会误报绿(已实测并改正)
· SendInput 坐标归一化 + VIRTUALDESK
· sendinput 分支必须 return(防双执行)
## 待办(已写入 GUI-PLAN.md)
· loadGuiPrefs 不透传 agentCursor,导致 deferMs 配置读取不生效(实测)
· 显式调 SetProcessDpiAwarenessContext(-4),不依赖 Electron 默认值
· mascot 姿态:思考/等待/出错
· 真实点击效果仍未验证(当前环境无可注入的可见窗口)
|
2026-10-02 15:27:33 +08:00 |
|
|
|
f0a7ce23b1
|
feat(gui): 模式 A agent 独立光标 + 后台注入;修坐标换算;固化计划与交接文档
## 模式 A:agent 独立光标(不抢用户鼠标)
现有 computeruse 走 SetCursorPos + mouse_event,agent 一干活就抢走用户
真实鼠标,用户无法同时使用电脑。现改为:GUI 自绘 agent 专属光标,实际
输入经 PostMessage 直接投递到目标窗口句柄,用户鼠标全程不动。
新增:
cmd/gui/agent-cursor.js 透明置顶层窗口 + mascot 指针 + 移动/点击脉冲
动画;记录上次命中的 hwnd 供键盘动作使用
cmd/gui/agent-inject.js Windows 后台注入:WindowFromPoint 定位、
客户区坐标换算、PostMessage 投递鼠标/键盘/
滚轮/文本;拖拽分步移动
配置 gui-prefs.deviceBridge.agentCursor.mode = "overlay"(默认) | "real"。
明确的设计边界(不是实现缺陷,是平台限制):
· 只对「接受窗口消息的程序」可靠。原生 Win32 程序可靠;Chromium/
Electron 自绘界面、游戏、DirectX 会忽略合成消息。
· 不做真实鼠标降级。用户既选了不抢鼠标,就不该偷偷改成操作真鼠标,
失败即 status=error 并说明原因。
· 键盘类动作依赖先前的鼠标操作(后台注入不动真光标,
GetCursorPos 拿到的是用户自己的光标位置)。
端到端 8/8 正确响应;「真实点击是否落到目标窗口」因当前环境无可注入的
可见窗口而未验证,已在 GUI-PLAN.md 列为待办。
## 修坐标换算(我自己上一轮引入的 bug)
上一轮我加了 rawX / scaleFactor 的换算,理由是「SetCursorPos 期望 DIP」。
**那个前提是错的** —— Electron 主进程是 per-monitor DPI-aware,Win32 坐标
不做虚拟化。实测:
Electron screen API (DIP) : 1260 x 840 scaleFactor=2
GetSystemMetrics : 2520 x 1680
SetCursorPos(200,200) -> GetCursorPos 读回 (200,200) 恒等,不缩放
加 /scale 会把原本正确的点击改坏:200% 屏上点 (600,400) 被送到
(400,267),越靠右下偏得越远。已改回 physical ? rawX : rawX*scale,
drag 终点同步修正(原先两处换算不一致)。
易误判之处:在普通 node 进程里测同一函数读到的是 1260x840(虚拟化值),
会以为 Win32 用 DIP —— 必须在 Electron 内测。
## 判据
computeruse.test.mjs 43 条(npm test 共 127 条全绿),新增 5 条钉住
模式 A 的核心承诺:
· agent-inject / agent-cursor 均无 SetCursorPos 调用(查前先剥注释,
因为注释里会引用该名字解释「为什么不用它」)
· koffi 指针参数用 Buffer(普通对象不写回,会拿错 hwnd)
· overlay 默认开启且可切回 real
· 自绘光标层 setIgnoreMouseEvents 穿透
核心不变量已做变异验证:注入 SetCursorPos 后判据立即转红。
另修正坐标判据方向(原判据断言 /scale 是对的,与实测相反)。
## 文档
GUI-PLAN.md 改造计划与进度的单一事实来源:已完成/待办、坐标
实测结论与踩坑、koffi 3.x 使用约定、端到端测试方法、
环境注意事项(含本机无 Go 工具链、waiter 改动未编译验证)
SERVER-HANDOFF.md 服务端需同步的部分:放开 action 白名单(schema enum
+ switch default 双重限制,不改则 11 个新动作下发不到)、
透传 tox/toy/steps/dx/ms、统一 scroll 方向语义、
写入坐标约定防重蹈
|
2026-10-02 14:12:32 +08:00 |
|
|
|
d6dd84e1c8
|
fix(gui): 启动期误判服务端未启动;keybd_event 类型名致组合键全废
## 修 GUI 反复误判「服务端未启动」
isServerRunning() 硬编码 http://localhost:8080/。实测该部署下服务端在
192.168.2.60:8080(homed 跑在 WSL 里):
localhost:8080 -> 000 连不上
192.168.2.60:8080 -> 302 正常
于是每次启动都:探测失败 → 误判服务没起 → 去 startHomed() 拉起 GUI 旁
并不存在的 homed.exe(findHomed 返回 null 就 skip)→ 再 waitForServer()
干等 8s 超时 → 打印 "homed failed to start within timeout"。
gui.log 里这条 ERR 累计出现 53 次,是日志里唯一的错误项。
改法:
· serverProbeTargets() 按「connections.json 里已配置的连接(当前连接
优先)→ localhost」顺序探测,命中任一即视为在线
· 服务可达时直接 skip homed 自动拉起
· 无本地 homed 可执行文件时不再空等超时,直接说明情况
· probeOne 加超时保护,避免启动被挂住
实测:修复后启动日志为 "server reachable, skip homed autostart",
该 ERR 不再出现。
## 修 keybd_event 类型名(组合键整体失效)
koffi 不认 Win32 的 `byte`:
"void keybd_event(byte bVk, ...)"
-> Error: Unknown or invalid type name 'byte'
"void keybd_event(uint8 bVk, ...)" -> 正常
原代码用的正是 `byte`,且整段包在空 catch 里,于是 keybd_event 恒为 null,
所有组合键(keypress/hotkey)都返回 "keybd_event unavailable"。
这个 bug 很难被发现:单键 click/move/scroll 走 mouse_event,不受影响。
端到端实测确认:修复前 hotkey ctrl+shift+t 报
computeruse hotkey: keybd_event unavailable
另外签名解析失败现在会打日志,不再静默。
## 判据
computeruse.test.mjs 扩到 38 条(进 npm test,全量 121 条全绿),新增:
· keybd_event 签名类型(且已做变异验证:改回 byte 判据立即转红)
· 服务探测不只探 localhost / localhost 仅兜底 / 仅当有 homed 才拉起
· 服务可达时 skip autostart / 探测有超时保护
|
2026-10-02 13:03:17 +08:00 |
|
|
|
40119e8116
|
feat(gui+waiter): computeruse 补高级操作、修 HiDPI 坐标;记录 PiDeck 借鉴
## computeruse 高级操作
此前两端(GUI 本机桥 / waiter 设备侧)都只有 9 个基础 action
(click/move/scroll/keypress/type…),与业界 computer-use 的基本盘差距明显。
现补到 18 个,两端对齐:
drag 拖拽(分步移动,见下)
hover 只悬停不点击(触发 tooltip / 悬浮菜单)
mousedown/up 按下与释放分离,可配对做「按住」
tripleclick 三击选中整行
middleclick 中键
hotkey/combo 组合键(ctrl+c 这类)
wait/sleep 显式等待,等 UI/动画完成再下一步
display 显示器几何(bounds + scaleFactor + primary),多屏定位必需
★ 拖拽**分步移动**(默认 12 步,上限 60)而不是一步到位:canvas 拖拽、
列表排序、滑动条这类应用依赖中间 mousemove 事件,一步到位会失效。
## 修 HiDPI 坐标错位(GUI 侧)
截图给模型的是**物理像素**,而 SetCursorPos / xdotool / cliclick 期望
**逻辑点(DIP)**。旧代码只加了 display bounds 的原点偏移,完全没换算
scaleFactor ⇒ 150% 缩放屏上点击系统性偏移(越靠右下偏得越多)。
现在按目标屏 scaleFactor 换算,并提供 `physical:false` 让调用方声明
「坐标已是逻辑点」以免二次换算。
## 组合键归一化(waiter 侧)
旧实现把模型给的 key 字符串原样丢给 `xdotool key`,于是 "Ctrl+C"、
"ctrl + c"、"CTRL-C" 全部失效。新增 normKeySpec:统一 + 与空格分隔、
统一小写、去重复修饰键。
顺带修 waiter 的 `type`:加 `--clearmodifiers`,避免残留的 Ctrl/Alt
把后续输入变成快捷键(旧实现一次输入就可能污染后续所有按键)。
## scroll 修正(两端)
旧实现固定滚 `click 4/5`(上/下),忽略方向语义也不支持横向。
现按 dy 正负给方向、按量级分步、支持 dx 横向滚动。
## 显式声明借鉴 PiDeck
用户要求声明 GUI 借鉴了 PiDeck 的设计与实现。做法:
1. 新增 `cmd/gui/DESIGN-NOTES.md` —— 逐项列出借鉴自 PiDeck 的设计
(三点渐次明暗 / 运行态呼吸光晕 / 工具调用分组卡)、PiDeck 的原始
实现(@keyframes 名与 CSS 片段)、HomeAgent 的落地位置、以及**动机**,
并明确区分「本仓自行决策」的部分(如「只有 1 个工具时不分组」是本地决定,
不是 PiDeck 做法)。
2. app.js / style.css 的相关代码处补出处注释。
3. 判据 computeruse.test.mjs 钉住这几条声明,防止后续丢失。
参考方式为阅读 PiDeck 打包产物 app.asar 中的 CSS 实现后**独立重写**,
未复制其代码或资源文件。
## 判据
新增 cmd/gui/computeruse.test.mjs(进 npm test),30 条断言:
· 两端各 9 项高级操作
· HiDPI 换算与 physical 开关
· 组合键归一化、type 的 --clearmodifiers
· 拖拽分步移动(两端)
· PiDeck 借鉴声明四处(含 DESIGN-NOTES.md 存在)
判据:cmd/gui npm test 全绿(114 条)。
|
2026-10-02 11:10:53 +08:00 |
|
|
|
bf06ad029a
|
feat(gui): 加载动画改三点式 + 新增系统通知
## 1) 加载动画:边框旋转 → 三点渐亮
旧实现是 border + rotate 的 spinner,用在 .loading(16px) /
.loading-spinner(32px) / .live-spinner(15px) 三处。在消息气泡里刺眼,
且语义不对:旋转暗示"正在加载某个确定的东西",而 agent 思考没有进度。
参考 PiDeck(@deepseek-ai/dsh-web-frontend 与 mermaid bundle 里的
@keyframe 实现)改为「等距三点 + 透明度递减 + 轻微上浮」:
haDots 三点错峰明暗(1/0.6/0.35/0.15)
haDotFloat 单点上浮 + 缩放
haSweep 横向扫光
旧类名 .loading / .loading-spinner 保留为别名指向新动画(还有 6+ 处
调用点,逐处改 HTML 容易漏),并解除其 animation 以免残留旋转。
7 处调用点统一换成 <i>x3 结构。
## 2) 系统通知(此前完全不存在)
主进程无 Notification、preload 无接口、渲染层只有页面内 toast
(用户不盯着窗口就看不到)。补齐一条完整链路:
main.js notify:show / notify:supported 两个 IPC + 原生 Notification
preload notify.{show,supported,onClicked}
app.js notifyTurn / notifyTurnOnce / onNotifyClicked
设计取舍(都写进注释):
· **一轮只通知一次**。SSE 的 tool_call / stage / agent_output 都可能
判定"一轮结束",用 turnSig 去重,否则连弹好几次。
· **不在流式过程中逐 token 通知**。一轮可能几十个 delta,那是噪音;
用户要的是"有结果了"。
· **窗口在前台且聚焦时不弹**。正看着界面时弹窗只会烦人。
静默启动(窗口 hidden)正好走"要弹"这条 —— 这也是静默后唯一
能让用户知道"agent 回了"的途径。
· **挂在 SSE 的 agent_output 收尾处**,不挂 endChatTurn():
后者开头 `if (!state.chatLoading) return`,而旁观路径
(agent 响应 memo/QQ/email,GUI 只收 SSE)chatLoading 恒 false,
根本走不到那里。挂 SSE 对"自己发"与"旁观"两条路径都生效。
· **点击通知会 show() + 回传渲染进程**,切到 chat 视图并滚到该条。
点了却不知道该看哪句话等于没通知。
· 正文超 180 字截断,避免通知被撑爆。
## 判据
新增 cmd/gui/loading-notify.test.mjs(进 npm test),24 条断言覆盖
动画关键帧/错峰 delay/别名解除/调用点结构,以及通知链路的六段
(主进程 IPC、preload 桥、去重、挂载位置、点击跳转、可用性探测)。
## 实测
重新打包安装:186 文件 0 处不匹配,exe 188834816 字节
启动正常:silentStart 生效、login 200、device/online 200、
graph/pulse 轮询、0 个 401 / JS 错误。
判据:cmd/gui npm test 全绿(84 条)。
|
2026-10-02 10:40:35 +08:00 |
|
|
|
58ad6831f5
|
fix(gui): 静默启动开关此前是死设置,永远不生效
## 现象
设置页有「静默启动」开关,文案写着「启动时不显示主窗口,驻留托盘
后台运行」,勾选后启动 GUI 照样弹窗。
实测(本机,gui-prefs.json 里 silentStart=true):窗口标题 HomeAgent
照常出现。
## 根因
silentStart 从未被消费:
· main.js 的 loadGuiPrefs() 把它读进对象
· 渲染层设置页读写它
· **但主进程没有任何地方读它做判断** —— whenReady 无条件 createWindow()
是个纯粹的界面开关,接线缺失。
## 改动
1) createWindow(show = true):支持「创建但不显示」。
BrowserWindow 初始 show:false,非静默时经 ready-to-show 显示
(避免先显示再隐藏的闪烁)。
★ 为什么静默时**仍然创建窗口**、而不是不创建:
不创建 ⇒ 渲染进程不启动 ⇒ app.js 整个不执行 ⇒ SSE 通道、
设备桥轮询、聊天全部失效,GUI 退化成纯托盘图标;且
showMainWindow() 只做 show()、不会创建窗口,app.on("activate")
又只在 macOS 触发 —— Windows 上托盘点「显示主界面」将毫无反应,
进程变成唤不起来的僵尸。已按此结论实现(评审时确认为「创建后隐藏」)。
2) whenReady 读 silentStart 并据此 createWindow(!silent)。
与 applyAutoLaunch 里的 openAsHidden 是两条不同路径:那个告诉 OS
「开机自启时如何启动」,这里决定本进程要不要显示,对手动启动同样生效。
3) showMainWindow() 增加兜底:窗口不存在时重建;隐藏时先 show 再 focus。
消除上述「唤不起来」的隐患。
4) loadGuiPrefs() 补去 UTF-8 BOM。
★ 这是排查中发现的**第二个真实缺陷**:Windows 工具
(PowerShell Set-Content -Encoding UTF8、记事本)改过 gui-prefs.json
会写入 BOM,而 JSON.parse 遇 BOM 直接抛错 → 走 catch → 返回**默认
prefs** ⇒ silentStart/exitToTray/deviceBridge 全部被静默重置,
表现为「开关保存了但不起作用」。loadConnections 早就有去 BOM 处理、
这里没有,两个读取点不一致。
(本次实测正是被自己的判据脚本用 Set-Content 改 prefs 后踩到的。)
## 判据
新增 cmd/gui/silent-start.test.mjs(进 npm test),14 条断言:
· whenReady 确实读 silentStart 并决定 show
· 静默路径仍创建窗口(不许退化成不建窗)
· showMainWindow 能在窗口缺失时重建
· loadGuiPrefs 去 BOM,且 loadConnections 的既有处理不回退
已做变异测试:还原成无条件 createWindow()、抽掉 BOM 处理,均判红。
## 实测(本机 Electron,真实 prefs)
silentStart=true → processes=8 visibleWindows=0,日志出现
[startup] silentStart=on: window created hidden
silentStart=false → processes=4 visibleWindows=1(标题 HomeAgent)
判据:cmd/gui npm test 全绿(46 条)。
|
2026-10-02 09:47:11 +08:00 |
|
|
|
e3c81269b6
|
fix(gui): 连接配置不再被空/半截文件覆写,保存改原子写
## 现象
connections.json 变成 {"connections":[],"currentId":null},
用户的连接列表全部丢失(2026-10-01 实测)。
## 根因:两个缺陷叠加
1) loadConnections() 的损坏恢复分支无法区分两种情况:
· 文件内容确实损坏 → 备份+重建(合理)
· 读到空/半截内容 → **也走同一条路,覆写成空配置**
第二类真实会发生:saveConnections() 用非原子 writeFileSync 截断重写,
落地过程存在「已截断、未写完」窗口;此刻任何一次读取拿到半截内容,
JSON.parse 抛错 → 用户配置被永久销毁,且没有二次确认。
实测(沙箱跑真实函数):空文件 -> 返回空配置,
**并把文件改写成 {"connections":[],"currentId":null}**。
2) 损坏备份只写一个固定的 .bak,反复损坏会把上一份真实配置覆盖掉,
于是「唯一的退路」也丢了。
## 改动
1) loadConnections():raw.trim()==="" 视为「尚未配置」,
直接返回空配置,**不覆写、不备份**(BOM/纯空白同样处理)。
2) saveConnections():改为原子写 —— 先写同目录 .tmp-<pid>,
再 renameSync 覆盖。rename 在同卷内原子,读者只会看到改写前
或改写后的完整文件,从根上消除产生半截内容的竞态;失败时清理 tmp。
3) 真损坏的备份名带时间戳(connections.json.corrupt-<ISO>.bak),
反复损坏不再互相覆盖。
## 判据
新增 cmd/gui/connections-io.test.mjs(进 npm test):
在 node:vm 里抽出 main.js 的**真实函数**执行,11 条断言覆盖
合法/空/纯空白/损坏/原子写残留。已做变异测试:删掉空文件守卫、
把备份名改回固定 .bak,均被判红。
## 实测(本机 Electron + 真实凭据)
正常启动:connections.json MD5 前后一致(E8DD588F...),0 个 tmp 残留
人工清空后启动:文件被自愈填充回真实配置,而非覆写成空
判据:cmd/gui npm test 全绿(32 条)。
|
2026-10-01 23:16:49 +08:00 |
|
|
|
60f51f40e0
|
fix(gui): 设备接口补设备令牌,设备页不再恒 401
## 现象
GUI 设备页长期空白。日志每 15s 一条:
GET 401 /api/v1/device/online
## 根因:/api/v1/device/ 命名空间下并存两套鉴权
· /device/gateway 注册在 webui 插件,走 requireAPI,认 session cookie
· /device/online、/device/、/device/push
注册在 remotedevice 插件,走 requireToken,
**只认 X-API-Key 头或 ?token=,完全不认 cookie**
实测可区分:cookie 401 与错 token 401 的响应体都是纯文本
"unauthorized",而 webui 鉴权返回 JSON {"error":...}。
所以 GUI 只带 cookie 时:gateway 200、online 401 —— 同一连接同一
时刻,现象自相矛盾。
## 三种能工作的客户端做法一致,GUI 两样都没做
· WebUI 浏览器:cookie 进内核,reverseToUpstream 注入
deviceGatewayToken(handler_device.go:118)
· waiter:客户端自己带 X-API-Key(gateway_discover.go:103)
· GUI:既没注入、也没自带 ⇒ 设备页恒 401,且异常被 catch
吞掉置空数组,界面与「确实没有设备」完全一样
## 改动
1) main.js:device-bridge:get 回传 token 明文(此前只有 tokenSet
布尔,渲染进程永远拿不到)。明文本就存在本机 gui-prefs.json,
不新增密钥存储面,且不落日志、不进 URL。
2) app.js:新增 isDeviceTokenPath / deviceApiKey,对设备接入面
端点附加 X-API-Key(优先连接自身 apiKey,退回设备桥 token)。
3) app.js:新增 ensureDeviceToken,并**在首次 /device/* 请求之前**
调用 —— 此前令牌加载排在 /device/online 之后,顺序正好反了。
4) app.js:401 时记下可读原因(deviceAuthHint),设备页顶部渲染
红色提示卡,不再把鉴权失败伪装成「没有设备」。
5) app.js:网关发现加缓存,并从每 15s 的 refreshAll 轮询里移出
(改为按需 + 切连接/手动刷新时重取)。网关地址是服务端配置的
慢变量,15s 拉一次没有意义;此前它与 online 同秒重复调用。
## 实测(本机 Electron,192.168.2.60:8080,真实凭据)
修复前:device/online 恒 401,设备列表空
修复后:device/gateway 1 次(之后走缓存)
device/online 每 15s,全部 200,返回 waiter-fnnas /
waiter-mainnas 两台设备
401 计数 0
判据:cmd/gui npm test 全绿。
|
2026-10-01 20:24:55 +08:00 |
|
|
|
10dc1deaec
|
fix(gui): 设备桥 bind 结果判 ok + 暴露登记状态,与鸿蒙端对齐
GUI 主进程与 Go 客户端同病: 只打日志、不看 ok。
服务端 bind 被拒时回 {"ok":false,"error":"bind rejected"} 并关闭连接,
GUI 既不报错也不重连 ⇒ 设备静默失联(TCP/WS 通但从未登记进网关)。
另:成功时服务端不含 device 字段,原日志用 `msg.device || deviceBridgeId`
兜底才显得像成功,掩盖了「从未真的读 ok」。
鸿蒙端 DeviceBridge.ets:209 本来就是正确实现(检查 ok、区分 connected
与 bound),本次把 GUI 与 Go 客户端对齐到同一语义:
新增 deviceBridgeBound / deviceBridgeBindError,bind 被拒打 error 级日志
并说明常见原因(令牌不匹配 / 设备未授权)。
|
2026-09-26 13:49:59 +08:00 |
|
|
|
f18587a01c
|
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(614e88f 接口变更漏改测试)。
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 |
|
|
|
17a2c7b7ee
|
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 |
|
|
|
b0d6f10522
|
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 |
|
|
|
fb52e4a534
|
gui: hello caps 补全 screensee/clipboardsee/clipboardsue 能力声明
|
2026-08-21 20:03:49 +08:00 |
|
|
|
ab8bcd7478
|
gui: 新增 clipboardsee/clipboardsue 剪切板读写能力
- clipboardsee: 读取设备剪切板文字(clipboard.readText), 回执 output 为文字, content=ok/empty
- clipboardsue: 写入文字到剪切板(clipboard.writeText), 回执含写入字节数+预览
- 与服务端 c021828 配对, 支持 computeruse keypress ctrl+v 自动粘贴
|
2026-08-21 16:43:53 +08:00 |
|
|
|
577968e9e9
|
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 |
|
|
|
5fd532af38
|
gui: 新增 screensee 能力(agent 查看远程设备屏幕)
- executeHomeagentCmd 加 case screensee: desktopCapturer 截屏 → toJPEG(80) → data:image/jpeg;base64 回执
- 屏幕选择沿用 gui-prefs.deviceBridge.screensueDisplay, 越界回退主屏
- 输出格式与 camerasue 抓拍一致, 服务端视觉模型可直接分析
- 空源/空缩略图/异常均有 error 回执
对应服务端 28019a6 screensee 工具, 已用 mock 网关端到端验证:
截屏 display 1 → jpeg 21049 字节 → status=ok
|
2026-08-21 16:40:18 +08:00 |
|
|
|
adc99b976f
|
gui: speakeruse 支持声卡选择(设备通道可配输出设备)
- playDeviceAudio 按 gui-prefs.deviceBridge.audio.device 用 aplay -D <dev> 播放
- 新增 audio:list IPC(aplay -L 枚举) + preload 暴露 audio.list
- renderer 设备通道新增 speakeruse 声卡下拉, 保存到 prefs
- 排除 hw: 裸设备(mono->stereo 需转换), 只留 default/pipewire/pulse/plughw
- 修复 USB 声卡无声音: mono WAV 播到仅立体声设备失败, 改用 plughw 自动转换
已用 mock 网关 + 源码直跑验证: voice -> via plughw:CARD=Audio,DEV=0 播放成功
|
2026-08-21 16:40:18 +08:00 |
|
|
|
564826f658
|
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 |
|
|
|
49ca8d6222
|
gui: 自动重登/定时授权/副屏修复 + 排障(大量JS报错根因是cookie过期)
排障: "大量JS报错"真实根因 = webui cookie 会话24h过期 → 所有 api() 请求经网关 302 → 返回登录页HTML(200非401) → renderer 拿HTML当JSON解析 → 界面持续报错/乱码
修复(api() 自动重登):
- 401 或检测登录页HTML(THEME_PLACEHOLDER/统一门户登录)时自动 syncConnAuth 重登后重试一次
- 防递归锁 _haReloginLock
- JSON解析容错(HTML不再误报)
定时撤销/恢复授权:
- prefs.deviceBridge.authSchedule {enabled, revokeTime, restoreTime}, 支持跨天(23:00-07:00)
- 主进程每分钟检查, 到点经 webui 反代 /api/v1/device/auth 撤销/恢复(防抖状态机)
- 设备页UI: 开启开关 + 撤销/恢复时间输入(保存并应用生效)
- setDeviceAuthorized 优先 authRule, 否则从 connections.json 取 URL+cookie
副屏修复:
- displays:list 对空 label(' ') trim 后显示 显示器N
- 修复 loadGuiPrefs 只返回3字段导致 screensueDisplay/exec/authSchedule 被抹掉(保存后变默认的根因)
其他:
- 移除远程调试端口(9223, 排障用)
- 保留 window.onerror/onunhandledrejection 透传(gui.log 可见 JS 错误, 便于运维)
|
2026-08-20 13:29:21 +08:00 |
|
|
|
6e691710b2
|
gui: 设备命令/能力完整实现 + 二进制分块 + 消息ID去重 + 交互优化
设备命令执行对齐服务端 cmd_type 契约:
- onDeviceMsg 解析 msg.cmd_type: homeagent->能力分发 / shell->白名单执行
- 统一回执 sendCmdResult(兼容双参), 全链路回执修复
- 修复 pi-lens auto-fix 把 trayLastCmd 改 const 导致的 TypeError(命令未执行/无回执根因)
homeagent 能力:
- screensue: 独立窗口显示文字/HTML, 可配默认屏幕(displays:list IPC), 支持时长参数 "screensue [秒] 内容", 默认常驻
- camerasue: 抓拍单张 jpeg(base64文本); camerasue <N秒>=录像 mp4(libx264), 二进制分块经设备通道回传
- speakeruse: 接收服务端二进制音频(WS 0x2), 聚合后播放(aplay/paplay/ffplay/afplay/SoundPlayer)
二进制分块协议(设备<->网关):
- 录像(设备->网关): cmd_data_start/0x2帧/cmd_data_end
- 音频(网关->设备): cmd_speech_start/0x2帧/cmd_speech_end
- 设备端 WS 读写侧都支持 0x2 帧; N/A 服务端 readFrame 需配套(已发群)
消息重放/交互:
- sendChat 带 client_msg_id 唯一ID(服务端可去重), 超时明确提示勿重复发送
- 托盘菜单: 信息项 enabled(不再灰字)+200ms防抖+连接缓存, 首建立即弹出
- 托盘显示连接/设备桥/远控活动状态
UI:
- 设备页本机卡片: 设备通道配置(网关/ws_token/screensue屏幕/cmdrun目录/沙箱)
- 设备列表经 webui 反代 /api/v1/device/online 拉取
- 连接管理移除 device 类型(设备独立为 GUI 组件), toggleConnType/saveConnForm 清理
- 字体对比度提升(text-muted/secondary重调色), 内联小字 10/11px->12/13px
preload: 新增 displays.list IPC
|
2026-08-20 09:07:41 +08:00 |
|
|
|
32f2193e35
|
gui: 托盘菜单增强(连接/设备桥/远控状态) + 设备页 dot 修复
托盘右键菜单(rebuildTrayMenu, 动态刷新):
- 后端连接: 名称 + URL + [已连接]/[未连接](authRule 判定)
- 设备桥: [已连接]/[未连接] + 设备ID + 网关地址
- 远控活动: 上次远控命令 + 结果(输出前40字) + 总次数(收到 cmd 时记录, 回执后更新)
- 刷新时机: 启动 / connections add/update/delete/setCurrent / hello_ack/bind_ack / cmd 到达与回执
- 纯文本不用 emoji
设备页方形外框修复(根因: .dot-* 只定义 background 无尺寸, 直接包文字成整块色块):
- app.js: 状态/授权改为 <span class="dot-green"></span>+文字(本机卡片+设备列表4处)
- style.css: .dot-* 补 inline-block 8x8 圆角, 空 span 呈现小圆点
|
2026-08-18 09:37:05 +08:00 |
|
|
|
80764b4a4a
|
gui: 设备桥完整链路修复(URL解耦+wss TLS+cookie回退+onDeviceMsg)
问题: 设备桥此前硬编码 127.0.0.1:9890 + /api/v1/device/ws 路径, 无法连远程 homed wss; startDeviceBridge 内部仍有 conn.type!=='device' 旧条件导致 prefs 驱动永不启动; onDeviceMsg 缺失导致引用未定义。
修复:
- connectDeviceWS: 支持完整 ws(s):// 端点 URL(含路径/端口), 不再硬编码 host/path; wss/https 默认 443, ws/http 默认 9890
- connectDeviceWS: TLS 支持(devTls), cookie 注入优先 connections.json 完整登录串(authRule 浏览器快照可能不完整)
- startDeviceBridge: 移除 conn.type!=='device' 拦截, 兼容 prefs 驱动 {url/token}; whenReady/device-bridge:set 已对应
- 补全 onDeviceMsg: 处理 hello_ack/bind_ack/cmd(白名单 argsSafe 执行+cmd_result 回执)
- no-proxy-server 开关(本机 clash 代理干扰 wss), 错误日志带完整响应体便于排障
卡点(已发群): 雷池 SafeLine 按 TLS 指纹(JA3)拦截 Electron BoringSSL → 403; node/python OpenSSL 同请求 101。需服务端雷池放行 /api/v1/device/ws 或 Electron 指纹白名单。
|
2026-08-18 08:51:56 +08:00 |
|
|
|
9eca20c6dd
|
gui: 性能修复(星图/reasoning/渲染循环)+设备桥配置解耦(独立prefs+IPC)
性能(卡死修复, 已部署本机验证 GPU 91%->14%):
- reasoning_content 懒加载: 折叠正文不再 marked.parse, 点击展开时才解析
- 星图动画仅"星图"子面板激活才跑, 12fps限制, 关抗锯齿+pixelRatio=1, 粒子3000->1200
- renderAll/refreshAll 非chat视图跳过聊天整块重建
- switchView(chat) 多段延迟滚动到底部(修复进入停在顶部)
设备桥(配置与连接解耦, 为远程homed被控做准备):
- 设备桥改由 gui-prefs.json 的 deviceBridge{enabled,gateway,token} 驱动, 不再依赖 type=device 连接
- 新增 IPC device-bridge:get/set (状态查询+动态启停), preload 暴露 deviceBridge API
- whenReady 按 prefs 启动设备桥
待后续: 设备页UI入口(renderDevices 本机卡片仍被 conn.type!==device 条件挡住, 需改为始终显示+调用 device-bridge:set)
|
2026-08-17 17:35:47 +08:00 |
|
|
|
c36ac7e3a2
|
device gateway: remotedevice 插件(设备接入网关) + GUI/waiter 受控设备桥 + 设备页/授权开关/托盘/退出进托盘 + 白屏修复(惰性Tray) + deviceinfo 工具
|
2026-08-17 09:34:25 +08:00 |
|
|
|
4d38abe37b
|
gui: 修复总网关认证(host域cookie注入+session.fetch登录+sl-session合并) & 连接持久化完整字段+网关徽标; vendor 本地 three.js 备用
|
2026-08-16 08:17:34 +08:00 |
|
|
|
2dc52086a4
|
gui: renderChat 增加防护与日志; log:r 改 invoke; 修 SSE 流式丢失气泡; chat 布局 overflow 修复
|
2026-08-15 09:03:07 +08:00 |
|
|
|
098579014c
|
gui: 多主题外观(6色板/背景图缓存)/连接认证(总网关登录窗自动抓Cookie)/中英切换/插件禁用
|
2026-08-14 16:14:20 +08:00 |
|
|
|
8160bbf3e9
|
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 |
|
|
|
f3579df6dc
|
feat: Windows NSIS installer, embedded icons, GUI auto-launch backend
- Add package/installer.nsi (Full/Server/Client) and toolchain.nsi
- Embed icon.ico (rounded corners) into homed.exe/waiter.exe via .syso
- GUI auto-launches homed.exe from parent dir (Windows only)
- GUI fallback connections.json from app resource dir
- Add initconfig command for config.db seeding
- Replace waiter rawmode* with raw_{unix,windows,other}.go
- Fix .gitignore: /homed instead of homed, add login.json
- Update icon.svg with rounded rect, new mascot.svg
|
2026-07-19 23:46:16 +08:00 |
|
|
|
66b86f6369
|
feat: mascot integration - WebUI/GUI icons, chat avatar, system prompt, docs
|
2026-07-17 21:41:03 +08:00 |
|
|
|
3a7136c4b8
|
feat: align GUI and CLI with WebUI - move GUI to cmd/gui/, add REPL+mgmt to waiter, enhance GUI features
|
2026-07-16 20:28:56 +08:00 |
|