8847ce4cfd
fix(gui): 修发行打包的图标与资源漏装(Linux 包会缺星图依赖)
...
## 背景
上一提交修了 electron-packager 那条路径的 exe 图标。本提交处理另外两处
同类问题——**都是静默失败**:改了没人知道,坏了要等用户装上才发现。
## 1) package.json build 段(electron-builder 路径)
· files 白名单漏了 icon.ico / icon-tray.png / icon-tray@2x.png
⇒ 这些文件不会被打进包,托盘与窗口图标在发行版里丢失
· 补 win.target/win.icon;注意 electron-builder 的 icon 路径相对
**项目目录**(package.json 所在)解析,不是相对输出目录
(最初写成 'build/icon.ico',那会找不到文件 —— 已用存在性判据钉住)
· icon.ico 含 256x256,满足 electron-builder 对 win 图标的要求
## 2) deploy/packaging/package-linux.sh(Linux 发行实际走这条)
原实现手工 cp 七个文件:
main.js / preload.js / package.json
renderer/{index.html, app.js, style.css, mascot.svg}
实测漏掉:
· renderer/mascot.svg —— 源码里根本不存在(真名 mascot.webp)
· icon.svg / icon.ico / icon-tray*.png
· renderer/icon.svg
· renderer/vendor/* ★★ 本次本地化新增的四个第三方库
(three/OrbitControls/marked/purify)
⇒ Linux 发行包装完星图与 Markdown 渲染直接坏,
且**没有任何构建报错**
改为:
· renderer 按目录整体同步(cp -r "$gui_dir/renderer/.")
· 根级 *.svg / *.ico / icon-tray*.png 同步
· 新增必备文件校验:vendor 四库与 icon.svg 缺任一 ⇒ 报错并删目录跳过,
沿用本脚本既有的"宁可不发,也不发装了跑不起来的包"原则
## 判据
新增 cmd/gui/package-config.test.mjs(进 npm test),14 条断言覆盖三条
打包路径的配置一致性。已做变异测试:Makefile 去 --icon、files 去 icon.ico、
package-linux.sh 还原逐文件 cp,三者均判红。
判据:cmd/gui npm test 全绿(60 条)。
2026-10-02 10:25:43 +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
53d9af446c
feat(gui): 星图接上 /memory/graph/pulse + 前端依赖本地化 + 端点对齐门禁
...
GUI 与服务端 WebUI 插件共享同一套 REST 接口,但两边独立演进、没有任何
强制手段。这次核实发现三处真实漂移(服务端 42 路由 / GUI 只用 29)。
1) 星图接上服务端专为它造的轻量活动端点
服务端 handleMemoryGraphPulse 的注释写了动机:生产实例全量图谱
408KB / 1151 节点,为了「知道哪些节点是新的」而每 N 秒拉全量是把
带宽和 JSON.parse 全花在重复数据上;pulse 只回 id+name+type+
mention_count+updated_at,几百字节 ~ 几 KB,差两个数量级。
WebUI dashboard 接了它,GUI 此前只在初始化拉一次全量且完全不轮询
⇒ 星图停在打开那一刻的快照,agent 后来学的东西它永远看不到。
现接入:/runtime 3s + /memory/graph/pulse 10s,两路轮询幂等、
切连接时停;新实体标「生长」;SSE 的 tool_call/stage/agent_output
也会触发脉冲(用工具名与回复前 60 字当 hint,让点亮落到本轮相关实体)。
pulse 检出「全量图里没有的新实体」时置脏,由下一次 renderChatStarmap
惰性重拉全量 —— 不在脉冲回调里直接拉,否则会把轻量活动源变成每 10s
拉一次 408KB 全量,正好是 pulse 端点要消除的浪费。
2) 前端依赖本地化,顺带修掉一处安全边界
四个库从 internal/plugins/webui/static/ 复制到 renderer/vendor/
(字节一致,由门禁钉住),index.html 不再引用任何公网 CDN。
与服务端同一理由(见 webui/starmap_vendor_test.go 的注释):
HomeAgent 支持离线/内网部署,换 CDN 只是把同一个赌注重下一遍。
★ renderMd() 的净化器缺失分支不再回退到手写正则(剥 <script>/on*=/
javascript:)—— 那不是完备的 HTML sanitizer,漏 <iframe srcdoc>、
SVG 内联事件、data: URI 等,而它渲染的是模型输出与记忆文本,
都算不可信输入。现在直接退纯文本:库已本地化,走不到该分支。
3) 新增 endpoint-align.test.mjs(进 npm test / CI)
扫描 app.js 的 api("...") 与 handler.go 的 mux.HandleFunc 对账,
7 条判据。与既有判据同一路子:读真实源码,不抄逻辑重写。
反向差集只报告不门禁(管理面端点接不接是产品决策),
只硬钉「服务端已明确为其设计」的 pulse —— 即本判据的由来。
已做变异测试:改错 pulse 路径、改回 CDN 均能判红。
protocol-align.test.mjs 是真浏览器判据(需 Electron+Xvfb+真后端),
CI 明确不跑,所以这类漂移此前无人发现。
顺带:connectSSE 由隐式全局赋值改为 function 声明(加 "use strict"
会在文件靠后处 ReferenceError,报错点离调用点很远)。
判据:cmd/gui 下 npm test 全绿(4 个 .mjs / 25 条)。
2026-10-01 18:23:20 +08:00
7151f1db85
feat(gui): 协议对齐 —— 补齐 6 个只读诊断端点 + 人设/反代面板
...
GUI 原来只用 22 个端点,服务端有 47 个。补齐**只读诊断类**:
agents / network / tracker / config / persona / proxy(+services)。
## 刻意不接 /login 与 /logout
那是 cookie 会话认证流程,而 GUI 走 `X-API-Key` 头(见 `api()`)。
接了不是"对齐",是接错。
## 三处新增
**总览诊断卡片**(renderDiagPanel):Agent 健康、LLM 可达、网络端点数、
文件变更集、数据目录、心跳间隔。全部走 `(x && x.y)` 安全取值 ——
任一端点没取到(老内核无该路由、连接断开)只显示 "-",不抛错。
**人设面板**(renderPersona):实测结构是
`{current_prompt, file_override, initialized}`。我第一版按 map 遍历,
结果只会显示三个字段名 —— 真机验证时才发现。改成展示提示词全文 +
两个状态卡。**只读**,编辑涉及保存/回滚/并发覆盖,与"协议对齐"是两件事。
**反代面板**(renderProxy):base_domain / mode / total / manual +
服务列表(`✓ gateway → 127.0.0.1:9890`)。实测字段是
`{name, host, path, url, target, ok, auth, websocket}`,不是我第一版假设的
`subdomain`。
## 加载策略
只加进 `refreshAll`,**不加** `refreshDataOnly`(后者每 15 秒一轮,
诊断数据不必高频轮询)。7 个新端点实测只 +3ms。
## 判据 protocol-align.test.mjs(10 条,真 Electron)
- 7 个 state 槽都取到真实数据
- 总览出现诊断卡片
- 人设面板**真的显示提示词内容**(不只查元素存在)
- 反代面板**真的列出服务**(查 `→` 出现)
### 判据踩的三个坑
1. **默认假 key 导致 9 项全红** —— webui 对错误凭据返回 200 + 登录页 HTML
(`looks_like_login_page` 能识别),于是所有取数失败。看起来像
「代码坏了」,实际只是认证缺失。改为默认从 `config.db` 读真 key。
2. **判据绕过了应用路径** —— 表达式里直接调 `refreshDiagData()`,
于是把应用里的 `await refreshDiagData()` 注释掉,判据**仍全绿**。
改为走应用自己的 `refreshAll()`。
3. **变异后 GUI 仍加载旧代码** —— `ensure()` 看到端口有页面就复用,
注入变异后没重启 GUI ⇒ 又一次假绿。**变异测试必须先杀掉 GUI 进程。**
变异测试(注释掉调用点)⇒ 9 项变红,坐实判据验的是真路径。
## 门禁
- `npm run test-live`:22/22 通过(12 性能+滚动 + 10 协议对齐)
- `npm test`、`make test-gui`:全通过
- `go test ./...`:43 包 ok、0 FAIL
2026-09-28 10:45:54 +08:00
17fee7fabd
perf(gui): 聊天页增量渲染 + 修「打开不在最新消息」(10× 提速)
...
用户报「聊天页面卡得让人没有用的欲望」+「打开 app 和 webui,没有停在
最新消息处,还要反复滑动」。真机实测(Xvfb + Electron + CDP)定位到两个
根因,都修了。
## 根因 1:renderChat 每次全量重建 innerHTML
200 条消息 = 3500 个 DOM 节点全部销毁重建。拆分测量:
200 条:整体 234.6ms,其中 renderMd 25.4ms(**11%**)
⇒ markdown 渲染只占 11%,**89% 在 DOM 写入与布局**。
流式追加时每个放行的 chunk 都走这条路(200 条时每 chunk 6.6ms),
聊到几百条就是 0.5 秒/次 —— 这就是体感。
**改法**:按 `data-msgkey` 复用节点,四种策略按代价从低到高:
尾部追加(最常见)→ 头部前插(loadOlderChat)→ 局部替换 → 兜底整棵重建。
`data-msgkey` = role + 序号 + 内容长度 + 首尾片段。
★ **不能靠下标定位**:`loadOlderChat` 会 `unshift` 前插消息,下标整体位移。
实测:
| 消息数 | 改前 | 改后 | 改善 |
| --- | --- | --- | --- |
| 50 | 59ms | **8.2ms** | 7.2× |
| 200 | 235ms | **23.5ms** | 10× |
| 400 | 474ms | **38.2ms** | 12× |
关键是**次线性**了:400 条只比 200 条多 15ms(改前多 240ms)。
## 根因 2:滚动没落地
scrollTo 被调: 1, 参数: {top: 23446, behavior: "smooth"}
scrollTop: 0 ← 调了,但没生效
可滚动上限: 22838
smooth 立即值 0、300ms 后只到 6894(上限 22838)⇒ **既慢又没到位**;
手动 `scrollTop = scrollHeight` **立即 22838 一次到位**。
原因:紧邻的 DOM 全量变更让 smooth 动画的起点算在**旧**布局上。
重建后本就不该有动画 —— 用户要的是「立刻看到最新」。
**改法**:`msgsEl.scrollTop = msgsEl.scrollHeight`。
## 判据 chat-perf.test.mjs(10 条,真 Electron 跑)
新增 `npm run test-live`(需 Xvfb + electron,**不进 make test** ——
它要起真浏览器、30 秒启动,不适合当门禁)。
- 性能:200 条 < 100ms(**产品体感阈值**,不是 benchmark 数字)
- 次线性:单条成本不随规模上升
- 滚动:打开即在底部、300ms 后不被带偏
- **正确性:增量不丢消息**(5 种增删改路径)—— 比性能更重要
### 写判据时踩的坑(都记在文件里)
1. **第一版测出「0ms / 0 DOM 节点」** —— `buildChatLayout()` 在**无后端连接**时
走「请先添加连接」分支、聊天区压根没建 ⇒ **测不到**而非「不卡」。
2. **`ensureGui` 定义了但从未被调用** —— 重写文件时把调用丢了,
而定义还在,看起来一切正常。
3. **`detached: true` 只脱离进程组、不脱离会话** —— 脚本结尾 `process.exit()`
把刚起来的 GUI 带走(症状:`[tray] READY` 打了,判据却报「找不到页面」)。
改用 `setsid`。
4. **`JSON.parse(e.data)` 裸调** —— CDP 的 onmessage 也会收到非 JSON 帧,
抛在回调里既冒泡不到 await 也等不到 resolve ⇒ 整个判据挂死。
5. 我自己的滚动探针 `el.innerHTML=''` 让 `scrollHeight` 变 0,
「到位」判定是假象。改用「保留内容、只改滚动方式」重测。
## 变异测试
把 `applyIncrementalChatRender(msgsEl, html)` 改回 `msgsEl.innerHTML = html`
⇒ 判据立刻红(实测 268ms + 严格线性)。
## 门禁
- `npm run test-live`:10/10 通过
- `npm test`、`make test-gui`:全通过
- `go test ./...`:43 包 ok、0 FAIL
2026-09-28 10:26:58 +08:00
37ae621605
test(gui): 行为判据 + 接进 make test(此前无人能跑)
...
## 为什么加行为判据
`sse-backoff.test.mjs` 检查源码**形状**(有没有清零、上限自不自洽)。
但形状对 ≠ 行为对:把清零写到 `reader` 取流**之后**,形状检查照样通过,
而实际仍在用旧计数重连。
新判据 `sse-backoff-behavior.test.mjs` 从**真实源码**抽出退避表达式并
在 `node:vm` 沙箱里求值,用假状态记录实际等待。实测量化:
历史累计 8 次后建连成功再断流 → 等 1000ms(改前会是 32000ms)
连续 6 次「建连成功→断流」 → 1000,1000,1000,1000,1000,1000ms
## 变异测试(都抓到)
| 变异 | 形状判据 | 行为判据 |
| --- | --- | --- |
| 清零挪进 setTimeout 内 | 通过 | **红** ← 只有行为抓得到 |
| 删掉「建连后」清零 | **红** | 通过 ← 暴露了行为判据的盲区 |
| pump 上限改回 60000 | **红** | — |
| 30ms 改回 800ms | **红** | — |
第二行促成了「行为 0」:建连后清零原本不在被验证的路径上
(抽取锚点只抓 pump 那一处),补了独立检查。
## ★ 判据本身踩的坑(都写进文件注释)
1. **别包假 setTimeout**:`exprSrc` 本身就是延迟数值
(`setTimeout(fn, <延迟>)` 的第二个参数),包一层让结果恒为 null,
三项全红。
2. **别用 `new Function`**:等价于 eval,是安全反模式。改用 `node:vm`
的 `runInNewContext`(官方受限环境,拿不到宿主作用域,带 1000ms 超时)。
3. **一个正则兼容两种幂运算形态会取错捕获组**:`Math.pow(2,x)` 比 `2 ** x`
多一层括号 ⇒ 组数差 1 ⇒ `r[length-1]` 取到 NaN。
最终形态是**定位与取值分离**:正则只定位(不捕获数字),数字单独取。
4. **`String.raw` 拼接正则不可用**:`\\.` 保持字面双反斜杠(去找字面的
"\."),且拼接后捕获组编号不可控。
## ★ 这些判据此前没有任何入口会跑
`cmd/gui` 是**纯 Electron 目录**(0 个 `.go`、无 `go.mod`),Go 通配会
跳过它 ⇒ 判据挂着也没人执行。现已接入:
- `cmd/gui/package.json` 加 `"test"`
- `Makefile` 加 `test-gui` 目标,并挂进 `test`
- 无 node 时显式 SKIP 而不是静默通过
```bash
make test-gui # 或 cd cmd/gui && npm test
```
## 顺带说明
`go test ./cmd/gui` 报 `no Go files [setup failed]` **不是回归**:
该目录 0 个 `.go` 文件,只有**显式点名**才报。仓库门禁用的三种形态
(`go test ./...`、`go test ./cmd/...`、Makefile 的 `test`)全部通过。
2026-09-28 08:16:51 +08:00
46ce92618c
chore(version): 客户端版本与内核对齐(唯一事实源 internal/meta.Version)
...
此前三份版本号互不相干:内核 1.4.0、GUI 1.0.0、鸿蒙 1.1.1。手工各改各的
必然漂移,所以把「对齐」做成机械动作而不是约定:
- deploy/scripts/sync-client-versions.sh:从 internal/meta.Version 读版本,
同步 cmd/gui/package.json 与鸿蒙 AppScope/app.json5(versionName +
versionCode=X*1e6+Y*1e3+Z);--check 给 CI/Makefile 做漂移门禁。
- Makefile 新增 sync-client-versions / check-client-versions;build-cli 也注入
LDFLAGS,waiter 与 homed 同版本。
- 鸿蒙:新增 common/AppVersion.ets,从 bundleManager 读安装包 versionName,
BridgeProtocol/BridgeCaps 里两处硬编码 '1.1.1' 改为读它——版本只剩
app.json5 一份,杜绝第二真相。
- waiter:`-version` 打印版本,启动横幅与设备桥 hello 的 version 字段
直接引用 internal/meta,与内核天然同源。
对齐后:GUI 1.4.0 / 鸿蒙 1.4.0(1004000) / waiter 1.4.0 / 内核 1.4.0。
2026-09-15 11:03:26 +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
4d38abe37b
gui: 修复总网关认证(host域cookie注入+session.fetch登录+sl-session合并) & 连接持久化完整字段+网关徽标; vendor 本地 three.js 备用
2026-08-16 08:17:34 +08:00
451154bcb9
feat: Linux packaging, SDK v0.7.1, fix rawmode conflict
...
- go.mod: SDK pseudo-version -> v0.7.1 (remote tag), go.sum updated
- cmd/waiter/rawmode_linux.go: remove (conflicts with raw_unix.go)
- cmd/gui/package.json: add rpm/AppImage Linux targets
- package/package-linux.sh: cross-distro packaging script (deb/tar.gz/rpm)
- package/linux/: DEBIAN control files, postinst/prerm, RPM spec
2026-07-20 11:03:43 +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
8d73c08977
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
41bd4cfb02
feat(gui): add electron-packager for app bundling
2026-07-17 12:56:31 +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