|
|
631963fdc7
|
feat(site): 总起页改为「真正的 AgentOS 长这样」+ 修页脚状态栏不可点
## 总起页:从对比改为展示(用户要求)
前几版是对照表(「别人说 X → 我们要做到 Y」)。用户指出重点不是对比,
而是**把自己作为「真正的 AgentOS 样例」展示出来**。改为四张并列卡片,
每条给「机制 + 可测数字」,不做比较。
标题「真正的 AgentOS,长这样」;四题:隔离 / 调度 / 通信 / 资源 ——
操作系统躲不开的四道题,逐题给答案。
## 文案改了第三轮(用户指「读着太难受」)
按「短句、有节奏、不堆从句」重写四段正文。举一例:
旧:插件不是进程内的一个库,是内核 spawn 的独立进程。崩了就把它的
工具、钩子、通道一并摘掉,其余照跑。
新:插件不在内核里,是另一个进程。崩了就把它注册的东西一并摘掉,其余照跑。
## 修页脚「状态」栏不可点(用户报的 bug)
三行原为 `<span class="muted">` 死文本,点不动。改为链接:
- 最新发布:v1.3.x 线 → /releases
- main 在研:1.4.0 → blob/main/internal/meta/meta.go(版本号的实际来源)
- 许可:AGPL-3.0-only → blob/main/LICENSE
## 一处自查纠正
我一度在卡片里写「插件崩溃 → 恢复 <1s」,那是照抄 README 的旧说法。
查源码 `dynamic_proc.go` 发现退避实为 **1s / 2s / 3s**(`procRestartBackoff=1s`,
5 分钟内崩 3 次 `procMaxRestarts=3` 即停手等人),**<1s 不成立**。
改为如实写明退避序列与停手机上阈值。README 那句待另开一轮核实。
## 验证
- 双主题:4 卡片、控制台错误 0、横向溢出 0
- 一滑一页仍生效(6 次滑动偏差恒为 72px = scroll-padding-top)
- JS 禁用 24/24 可见;reduce 下 snap 自动关闭
- 390/768/1440 溢出均 0
- 页脚 9 个 gitcode 目标逐个对照 origin/main 的树:**全部存在**
(不只看 HTTP 200 —— gitcode 对错误路径也返回 200,此前踩过)
|
2026-09-20 10:11:02 +08:00 |
|
|
|
4078f3ac0a
|
feat(site): 重写首屏文案 + 交错图文布局 + 一滑一页
## 口号与文笔(用户指「不够响亮、部分文笔不好」)
Hero 改为「是…更是…」句式:
是记得住的管家 / 更是从不让你干等的搭档
## 五个设计决定:从卡片改为交错图文
原来 4 张卡片平铺。改为 5 行交错(文/图左右互换),每行配一张内联 SVG
示意图,纯 CSS + 主题令牌,深浅自适应,零外部依赖。
**换掉一条、新增一条**(用户指出「插件跑在独立进程」不算特色 —— MCP、LSP
都这么做,不是差异点):
| | 内容 | 依据 |
|---|---|---|
| ② | 部署,从未如此便捷 | 实测插件 `statically linked`、`not a dynamic executable` |
| ③ | 数据如水,随流,随改,随走 | 共享内存 + 相对偏移零拷贝;实测整段写入读回 3.5µs |
标题按用户给的句式写(②③ 原文照用),正文不给形容词、给可核验的做法与数字。
## 一滑一页
`scroll-snap-type: y mandatory` + 每节 `min-height: 100svh`。
为此把 features 的 5 条决定各拆成独立 section(原 2.74 屏塞 5 行,
mandatory 下会锁死底部),architecture 的流程图也单独成节。
现 13 节,实测连续 8 次滑动精确停在第 1..8 节,间距 828px = 一屏。
## 两处实测纠正(都是我先判断错、再被数据推翻)
1. **`proximity` 做不到「一滑一页」**:实测滑 500px 落点就是 500,离最近
节边界 395px,不触发吸附 —— 只是「有时粘一下」。改用 mandatory。
2. **我误报 architecture「底部锁死」**:按 `h > innerHeight` 判定,忽略了
溢出行仍可滚动。用真实 wheel 实测 13 节末元素全部可达(含该节 828 < 900)。
所以没有锁死,压缩 vertical rhythm 是顺带的,不是修复。
## 验证
- 深浅双主题:13 节 / 5 交错行 / 5 示意图、控制台错误 0、横向溢出 0
- 一滑一页:8 次滑动停在 8 个不同节,落点间距精确 828px
- 72px 落点偏移经查是 `scroll-padding-top`(导航高 67px),确保标题不被遮挡 —— 有意为之
- JS 禁用 24/24 可见;reduce 下 snap 自动关闭(`prefers-reduced-motion`)
- 390/768 无 snap(窄屏强制一屏反而难受);1024/1440 启用
- 真人式滚动(wheel 与 400px 步进两种)未揭示元素均为 0
注:本轮前期用了几个 Python 补丁脚本改 HTML,用户指出「不好」。后续改为
直接编辑以产出可审阅的 diff,脚本已删除。
|
2026-09-20 09:46:18 +08:00 |
|
|
|
0c9a3900b8
|
fix(release): .hmap 纳入发布产物白名单 + 路径解析
## 白名单(真问题)
`upload_assets.py` 的 ARTIFACT_SUFFIXES 只有 .tar.gz/.zip/.deb/.rpm/.pkg/_win64.exe,
**没有 .hmap** —— 即使插件包已经构建好放在 dist/ 下,上传时也会被静默跳过。
这正是「release 里一个插件包都没有」的直接原因之一。
补 `.hmap` 与 `SHA256SUMS.plugins`(插件包的汇总校验和,与内核包的 SHA256SUMS 分开,
避免混用)。实测 is_artifact() 现能正确识别两者、仍跳过 README.md。
## 测试路径解析
`HMAP_BUNDLE_DIR=dist/plugins` 这种相对仓根的写法原先会失败:测试的 cwd 是包目录
(internal/plugins/pluginmgr),相对路径解析到包内,报 "no such file or directory",
看起来像产物不存在。改为相对路径按仓根解析(向上找含 go.mod 的目录)。
实测三种调用都正确:相对路径、绝对路径、不设时 skip。
|
2026-09-20 09:07:33 +08:00 |
|
|
|
a35f2126a1
|
test(pluginmgr): 校验发布用插件包能被内核真实安装
配套 SDK 仓新增的 scripts/build_plugin_bundles.sh:**能构建出来 ≠ 内核装得上**,
这个测试用内核自己的 extractPackage 把产物真解一遍,验证三种包形态都落成规范入口。
覆盖的三种形态(都由真实产物验证过):
- 多平台 bundle:`plugin.bin.<os>.<arch>` → 按当前平台挑出并**重命名为 plugin.bin**
- 单平台包(qq 的 plg.json 是 bundle:false):只有 `plugin.bin`
- Lua 包(luademo):入口是 `main.lua`,不编译 Go
不设 HMAP_BUNDLE_DIR 时 skip(不作为常规 CI 的必跑项,避免依赖 hmapdev 工具链):
HMAP_BUNDLE_DIR=/path/to/plugins go test ./internal/plugins/pluginmgr/ \
-run TestBuildPluginBundlesInstallable -v
实测 21 个真实产物全部通过(含 Lua 与单平台两种非 bundle 形态)。
|
2026-09-20 09:02:42 +08:00 |
|
|
|
9b26db45bc
|
fix(site): 逐条对照源码修正描述(含两处真错误)
上一版有几处表述与源码不符。这轮把页面上每条可核验的说法都对着代码重新查一遍,
改掉 15 处,其中两处是**事实错误**而非措辞问题。
## 事实错误
**① L4 的归属说反了(FAQ)**
原文让读者「用更高级别的中断(如 L4:内核与内核级插件)」插队,暗示插件能用 L4。
源码 `scheduler.go` 的 `clampPluginLevel` 把 **>L3 一律夹到 L3**,注释也写明
「L4 由内核独占(panic、内核事件 selfip)」。照原文写插件会静默拿到 L3。
改为:插件可声明 L1–L3,L4 是内核保留的「立即打断」。
**② 驻留子的父侧动作列错**
组件表写「父可查看/收发/压缩/回收」。"收"不存在 —— 源码的动作集是
`list | create | send | inspect | compress | reclaim | destroy`,
子持有状态面由**父 pull**(resident.go 开篇注释:父持登记表,子持 inputch 处理表,
父 pull 不打断子)。改成「查看/发送/压缩/回收/销毁,子是父拉取而非推送」。
## 措辞不准确(12 处)
- **Context 层**「最近若干条受保护」→ 源码 `pCount := 10` **写死十条**;
「预训练词向量 → 余弦相似度,TF-IDF 回退」→ 实为优先稠密向量余弦、
未配置时退到稀疏词向量(TF-IDF / fastText);「自动下沉」→ 归档进 Document 层
- **PluginSDK「四通道」**→ 不是四个"通道",是三面接口(工具/钩子/事件)+ 输出通道声明
- **管道「7 个阶段钩子」**→ 会被读成都在管道内。实际分布是进管道前 1(on_input)、
轮次中 4、收尾 2(before/after_output),两处都标明
- **sanitizer** 只写了"清工具调用残留",漏了它更常做的是洗坏 UTF-8/U+FFFD/ANSI
(而这类字节会被模型复读),且不注册工具只挂钩子
- **rss**「推送通知」→ 实际是按间隔轮询 + 中断注入;补上"订阅时记历史条目,
所以订一个源不会把旧文章全推一遍"
- **memo** 补上可核验的机制:每 5 分钟检查未完成待办
- **ocr / bili** 补外部依赖(tesseract + chi_sim / yt-dlp)—— 不写清楚装完才发现缺
- **mc**「两阶段激活」原样照抄没解释;实为「想连着(意图)」与「确实连着(连接)」
两个状态分开,所以 bridge 被 kill -9 后能自动重登恢复会话
- **qq** 一句话太单薄,补 20 工具 + 权限模型要点(身份绑帧、取交集、前缀拒绝)
- **a2a / music / weather** 分别补:两个方向与端点、只读无副作用、NoMemory 取舍
## 顺带修掉两个我上一轮引入的 HTML 缺陷
用行替换时失手:Context 卡丢了一个 `</p>`、mc 卡多了一个 `</span>`。
这次写了栈式配对检查才发现(简单的计数对比看不出来)。
## 验证
- 栈式标签配对:p/span/div/button/code/section/h2/h3/details/ul/ol/a/li **全部平衡**
(修复前 p 差 1、span 差 -1)
- 事实终检 10/10:内置插件 16(all.go 导入数)、LLM 适配器 9 且**逐个名字对上**、
Go 行 109241→109k、go.mod 1.25.0、L4 归属、resident 动作、Stage 分布、备案号
- 浏览器回归:深浅错误 0、JS 禁用 47/47 可见、reduce 动效停、390/768/1440 溢出 0、
滚到底未揭示元素 0
- mc 工具数:本写「12 个动作工具」,实测 `tp+"act"` 去重后 activate/deactivate/status
之外是 **11** 个,已改
|
2026-09-20 08:48:15 +08:00 |
|
|
|
47052cb115
|
fix(site): 更正媒体机制描述 + 页脚补备案号
## 描述性错误(用户指出)
1. **「引用计数 GC」已不存在**。「有引用绝不删」「媒体靠引用计数 GC」
两处都在讲一个已废弃的账本 —— 实测源码里已无 media_refs/ref_count,
`internal/memory/media/media.go` 明确写「这不是 GC,也不看引用计数」。
现行规则是「删除持有它的记忆块即删内容」,与文本块同一套
(medialoop.go 的 payloadHeld 只在确认无块共享时才删字节)。
2. **「描述才是持久语义」整张卡已过时**。旧实现靠视觉模型生成的描述当索引;
现已弃用 —— `mediaref.go` 写明标签「不再包含任何生成的描述文本」,
图片改按统一空间向量检索,`graphmedia.go` 还带一个把旧描述式实体
迁移成原生记忆块的迁移函数。卡片改为「图片靠自己的向量被检索」。
## 备案号
页脚补 豫ICP备2024074105号-1 与 豫公网安备41070202001579号,
链接到 beian.miit.gov.cn / beian.mps.gov.cn。取值来源是现网
门户配置(/root/portal/dashy/conf.yml),未凭记忆编造。
## 验证
深浅双主题下渲染正确、两条链接 href 实测无误、无 JS 错误。
|
2026-09-20 00:00:23 +08:00 |
|
|
|
09298886a2
|
feat(site): 「一条消息进来之后」改为真正的流程图
原来是 ASCII <pre> 图。它有三个问题:
1. **画不出循环**。真实执行序是 7 步状态机,其中工具循环要回到开头
再来一轮 —— ASCII 只能表达上下关系,这一点只能靠文字暗示。
2. **7 个钩子排成一行是错的**。on_input 在进管道前跑,
before/after_output 在**全部轮次结束后**才跑一次;把它们与管道内的
钩子并列,读起来像一条直线。
3. 漏掉了 post_action 之后才发生的工具调用,以及"上下文裁剪 + 相关记忆召回"
这一步(在 after_toolcall 里)。
## 现在的结构
五层节点 + 分支 + 循环体:
外部输入 → 输入调度器(三条分支:入队列 / 抢占 / 转投)
→ 处理管道 →〔① pre_action ② LLM ③ post_action ④ before_toolcall
⑤ 执行工具 ⑥ after_toolcall〕↻ 循环 → 三层记忆 → 输出通道
事实全部对照源码核过(不是照抄旧图):
- 7 个 Stage 常量取自 SDK `third_party/homeagent-sdk/sdk/plugin.go`
- 顺序取自内核 `internal/agent/core/task.go` 的 Step 状态机
(StepPrepare→StepLLM→StepToolBegin→StepToolExec→StepToolAfter→StepTurnEnd)
- 脚本末尾补一句说明 on_input / before_output / after_output 的时机
## 视觉
节点用色与三层记忆的三色一致(蓝=Context/青=管道/金=Graph,紫=转投);
连接线上的光点错峰下行,序号依次点亮。全部是内联 SVG-free 的纯 CSS,
无外部依赖。
## 关键取舍
- **删掉了贯穿全图的中轴线**:节点背景是半透明令牌,轴线会直接透出来,
实测在「处理管道」里穿过整个编号列表,看着像画错了。连接线本身就是主轴。
- **循环回边改为内嵌徽标**:先做成从框底绕出的弧线,但它会压到下一条
连接线 —— 同样像画错。
- **给连接线补了静态箭头**:动画关掉时(reduce)方向也要看得出来。
## 验证(独立 headless 实跑)
深浅双主题控制台错误 0、页面溢出 0;图内溢出 0(390/620/900);
**JS 禁用下 14 个节点全部可见、7 个钩子名齐全**(流程图是内容不是装饰);
reduce 下光点/图标动画确为 none 而静态箭头仍在(宽 7px/2px);
**7 个钩子名与 SDK 常量逐一比对通过**,防止文案漂移;
滚动到底未揭示元素 0。
|
2026-09-19 22:38:06 +08:00 |
|
|
|
db8534e315
|
feat(site): 文案精简 + 插件可点击 + 版面精致化
## 文案(净减约 25%,信息量不变)
删的是解释性赘语与重复限定,不是信息:
- 「内核不直接读写任何外部世界…于是「内核有多可信」与…」→「内核不碰任何外部世界…可以分开评估」
- 「大多数框架先写功能再补边界。HomeAgent 反过来:先把边界和调度定死,再往上加能力。」
→「先定边界与调度,再加能力。」
- FAQ 六条逐条收紧;副标题从句子改回短语
- AI 声明与许可段去重复(两段都在讲同一件事)
## 插件徽章从装饰变为可交互(这是用户报的「无法点击」)
每个徽章现在是真按钮:点开显示该插件的**版本 + 用途**(取自各 plugin.json,
共 20 个),可多开、可收起,末尾「展开全部 20 个」一次全开。
键盘可达(Enter/Space),选中态用 aria-pressed 表达。
初版是纯 <span>,带 hover 效果却不可点 —— 看起来能点但点了没反应。
## 修正一处事实错误
统计卡原写「**36 外部插件**」。实测 36 是**加载总数**(16 内置 + 20 外部);
外部插件实为 20 个。同时:
- 「110k Go 代码行」→ 109k(实测 109,241)
- 「10 LLM 协议适配器」→ 9(server.lua 是 zen 网关脚本,不是厂商适配器)
每张卡补一行小字说明口径,避免再被误读。
## 版面
section 统一 4.5rem 节奏、卡片内边距与标题间距收敛、组件表代码列定宽对齐、
统计卡加口径小字、三层记忆卡收紧。插件选中态从实心青底(20 个齐亮像一堵墙)
改为淡青底 + 主色描边,并给 color-mix 加了 rgba 回退。
## 验证(独立 headless 实跑,全部通过)
深浅双主题 console 错误 0;JS 禁用 47/47 可见且插件卡默认全隐(0 张);
reduce 下粒子停、全可见;主题切换刷新保持、首绘无闪白;
390/768/1440 横向溢出均 0;移动端点插件正常展开;
插件交互逐项验过:单击展开 → 再点收起 → 多开 3 张 → 全展开 20 张 → 收起。
★ 一度报「19 个元素未揭示」,查证是我测试脚本没滚动所致 ——
逐步滚到底后实测 0 个未揭示,非真回归。
另把取数命令写进 site/README.md,并注明「36 = 加载总数」这个易错点。
|
2026-09-19 22:24:03 +08:00 |
|
|
|
fab27a1194
|
feat(site): 深浅双主题 + 动效层,并按用户要求移除立绘
## 深浅双主题
跟随系统偏好,导航栏按钮可手动切换(存 localStorage)。首绘前在 <head> 里
定好 data-theme,无闪白(实测 reload 首绘即正确背景色)。
语义色全部令牌化,:root[data-theme="light"] 只覆盖取值;品牌三色两主题共用
(对应三层记忆,换主题不该换语义)。
## 动效层(1 → 11 个 keyframes)
极光漂移 + 细网格背景、粒子网络(近邻连线,密度按面积自适应、上限 72)、
三色滚动进度条、标题渐变流动、分块上错落入场、卡片聚光 + 3D 微倾、
三层记忆色条自上而下灌注、架构图流光带 + 节点脉冲、数字滚动到位、
分节标题下划线展开。
三条硬约束(都吃过亏):
1. **内容默认可读** —— 初始隐藏只在 .js-fx 下生效,而 .js-fx 仅当 JS 真跑起来才加。
实测 JS 禁用时 30/30 元素可见(旧版把 opacity:0 写默认样式里 → 26/30 永久不可见)。
2. **尊重 prefers-reduced-motion** —— 不启粒子、不画进度条、元素直接可见。
3. **装饰不得产生滚动条** —— canvas 改用 documentElement.clientWidth
(window.innerWidth 含滚动条,实测多出 15px 撑出横向滚动),body 加 overflow-x: clip 兜底。
## 移除立绘(用户要求)
删掉 HTML/CSS/JS/资源/令牌全部痕迹,Hero 改单栏。README 记下为什么最终不放图:
原图是不透明 WebP(mode=RGB 实测),白底与角色白裙子同色,flood-fill 会渗进轮廓
让约 42% 身体透明 —— 这类素材要么出透明图,要么就别放。
## 顺带修掉 3 个真 bug(都是主题化后暴露/复核出来的)
1. **代码块换行全丢** —— .code 缺 white-space: pre,实测整段命令挤成一行。
(这个 bug 在我这次改动之前就存在)
2. **浅色下导航看不清** —— header 背景硬编码 rgba(11,16,32,.78),改用 --nav-bg。
3. **浅色下立绘处有灰块** —— .mascot::after 硬编码深色,改用 --mascot-fade
(该规则已随立绘一并删除)。
另把 .badge / .btn-ghost:hover / 按钮光泽里 3 处 rgba(255,255,255,…) 令牌化为
--hover / --sheen,否则浅色下是白压白。
## 验证(共享 Chromium 实跑)
深/浅首屏 + 记忆段 + 架构段截图逐张看过;console 错误 0;坏图 0;
JS 禁用 30/30 可见;reduce 全可见且粒子/进度条已停;主题切换 → 刷新后保持;
390/768/1440 三档横向溢出均为 0;CSS 花括号平衡、8 个 keyframes 无孤儿。
|
2026-09-19 21:49:59 +08:00 |
|
|
|
923d5d595f
|
feat(site): 新增产品官网落地页(单文件 · 零构建 · 零外部依赖)
用户要求写一个官网介绍页面。做成纯静态单文件,与仓库 WebUI 的既有做法一致
(原生 HTML/CSS/JS,无打包步骤)。
## 内容(每一条都对着源码/运行实例核实过)
- Hero:一句话定位(常驻型个人 Agent 框架)+ 看板娘立绘
- 四个设计决定:内核零 IO / 插件独立进程 / 输入有级别 / 忙时有人顶班
- 三层记忆:Context → Document → Graph,含媒体一等节点、描述即语义记忆、统一多模态空间
- 架构:一条消息进来之后的完整路径图 + 核心组件表
- 数字(**实测值,非估算**):36 外部插件 / 340 工具 / 110k Go 行 / 10 LLM 适配器
- 上手命令、插件徽章墙、6 条 FAQ、页脚(文档/深入/项目/状态)
## 两个设计决定,都有理由
1. **配色取自品牌指南**:蓝/青/金正好对应三层记忆,故三层记忆那节直接用三色做色条。
2. **立绘按「有意的圆角卡面」呈现,不抠图**:原图是白底 + 蓝紫渐变外框,
而白底与角色的白裙子同色 —— 连通域分析显示 flood-fill 会让 42% 的身体变透明
(围裙、发丝高光被吃掉)。改为圆角 + 发光边框 + 底部渐隐,方形图与深色页自然衔接。
## 修掉一个真实的可访问性缺陷
初版把 `opacity:0` 写在**默认样式**里、由 IntersectionObserver 加 `.in` 揭示。
实测:30 个 .reveal 元素里 26 个停在不可见 —— **JS 被禁用或报错时整页永久空白**。
改为渐进增强:内容默认可见,仅当 JS 真跑起来才加 `.js-reveal` 接管动画。
复测两种场景均 30/30 可见(正常滚动 + 禁用 JS)。
## 验证(用共享 Chromium 实跑,不只是看代码)
- 控制台错误 0;两张图均加载(logo 400x400、立绘 1024x1024)
- 移动端 390px:无横向溢出,导航折叠,立绘置顶
- FAQ 手风琴展开正常(open=true)
- a11y:图片 alt 齐全、单一 h1、lang=zh-CN、无空文本链接
- 标签配对全 OK;34.6 KB;**无任何外部依赖**(无 CDN/字体/JS 库)
|
2026-09-19 21:15:00 +08:00 |
|
|
|
71faf8d9ad
|
docs(readme): 按源码修正 README 的过时事实(中英同步)
上一轮只补了 v1.3.x 变更日志,没系统核对全文。本次逐条对照源码,修掉 5 处硬错误:
1. **消息时序图漏掉输入调度器**(最严重):还画着 `IO->>EV: inputCh` 直连
eventLoop,而当前输入必须先进调度器。补 participant 与调度阶段
(两类别+四级中断、同级不排队/更高级抢占、转投分诊助手)。
2. **图里的 `drainInterrupts` 已不存在**:实测该函数在源码中查无此项,
改为「安全点:中断求值/让位」(真实机制见 scheduler.go)。
3. **内置插件数 11 → 18**:漏列 ai_image / data / localuse / multimodal /
remotedevice / skillmgr(实测 `ls internal/plugins/` = 18)。
4. **Lua 适配器 8 → 10**:漏列 ollama / server(实测 = 10)。
5. **`agent/api/` 描述错误**:它只有 provider.go,不含 Lua 适配器
(适配器在 internal/lua/adapters/);改为如实的「provider.go 调 vm」。
另修一处**自相矛盾**:构建章节写「依赖 Linux/Windows」,而下载章节说
homed 已放弃 Windows 原生(`package-windows.sh` 明确「不往 Windows 装 homed」,
只建 waiter.exe + 引导 WSL2)。改为「依赖 Linux」并说明 Windows/macOS 的真实边界。
并给「设计要点」补上两个当前核心机制(此前只有域分离与三层记忆):
输入调度(两类别+四级中断)与驻留子/分诊助手。
验证:全仓文档断链 0;6 个 mermaid 图块配对全 OK;上述数字逐条实测复核。
|
2026-09-19 20:59:42 +08:00 |
|
|
|
c0274b71d5
|
docs: 删除迁移期临时文档,现行内容搬进正式文档
用户指出迁移评估那批是**过程性临时文档**,迁移已完成就该退场。
## 删除(38 个文件)
- docs/zh/架构迁移评估.md(1621 行)—— 评估稿。开头的「❗现网正在发生的问题」
(output_send 永远成功 / cgo 超时泄漏 26 次 / stage 污染)**全部已修复**,
留着是误导性告警。其 §三「目标架构」已被 ARCHITECTURE.md 完整覆盖
(且后者更细,含子进程生命周期管理)。
- docs/zh/plugin-interface-matrix.md(428 行)—— 迁移基线矩阵。
- docs/zh/experiments/(36 文件)—— 18 项可行性实验,验证的是"该不该迁移",
迁移早已完成;实测无任何构建/测试依赖它。
## 现行内容先搬走(不能随临时文档一起丢)
- plugin-interface-matrix §九「接口扩展规则」→ 搬进 docs/git-branching.md 新增 §八
(只增不减/签名不改、新增必须"插件调用内核实现"方向、hmapdev 模板必须同步接线
否则全体插件编译失败、"接口纯追加"≠"无需重编"、合回 main 的同步清单)。
- git-branching §六 原写「接口冻结是合回门禁」—— 冻结是**迁移期**约束,v1.1.x 起
已到期,改为标注失效并指向 §八。
## 引用清理
8 处引用全部改指现行文档:plan.md ×3、两篇设计文档各 ×1、
4 处源码注释(proc/shm.go、proc/process.go、dynamic_proc.go、entry_dispatch_test.go、
proc/bench_test.go)。仅 third_party(SDK 独立仓)保留 1 处,不动。
## 验证
- `go build ./...` 通过;`go test ./internal/plugin/...` 两个包全绿
- 本项目文档**断链 0**(另 2 处断链在 oh_modules 第三方依赖内)
|
2026-09-19 19:22:48 +08:00 |
|
|
|
7213edd181
|
docs: 全面按当前源码更新文档 + 删除已过时文档
## 删除(内容已落地/已被替换,保留只会误导)
- demo.md ................... failback 与 recoverydiag 均已实现,0 引用
- docs/defect-qq-output-send-loop.md .. 已修复(本身也标了「已修复」),0 引用
- docs/embedding-comparison.md ....... 一次性选型报告,仅被 agent 产物引用
- docs/zh/plan.md ............ 描述的旧 nav 布局已重写、死配置已清,全部完成
- docs/zh/plugin-migration-plan.md ... 迁移已上生产,纯过程稿(Part 0~6 全完成)
## 更新(按当前源码核对)
- assets/docs/{zh,en}/ARCHITECTURE.md(README 指向的用户文档,最重要):
把只讲 cancel/intercept 的旧「中断机制」章节重写为「输入调度器与中断机制」——
补上两类别 + 四级中断(L1~L4,默认 L1、外部插件 L4 夹到 L3)+ 抢占/挂起/中断栈
+ 饥饿防护(PreemptCount 提升,封顶 L4)+ 抢占冷却(2s)+ 停止语义(cancelBudget)
+ 驻留子/分诊助手/残余任务;新增「上下文预算」章节(窗口 ≠ 工作面,600K 封顶,
预算是上限非填充目标)。中英章节数现已对齐(各 13 节)。
- assets/docs/{zh,en}/PLUGIN_DEV.md:插件示例表补 6 个缺失项
(acp/deepsearch/plugindev/recoverydiag/vanblog/vikunja);qq 工具数 17 → 20(实测)。
- README.md / README_EN.md:补 v1.3.x 线(此前只到 v1.2.0,而 1.3.x 已发布 12 个 patch)——
驻留式子 agent、输出通道寻址、输入调度器、轻量内核 profile、积压及时反馈。
- plan.md:开头两个「⚠️ 紧急/正在持续污染」是过期告警(实测 残留 = 0),
改为「已解决」并加文档定位说明;§13 仍是活跃路线图故保留。
- docs/zh/plugin-interface-matrix.md + 两处源码注释:清理指向已删文档的断链。
全仓 md 断链检查:仅剩 1 处,位于 third_party 的 oh_modules(第三方依赖,非本项目)。
|
2026-09-19 19:14:55 +08:00 |
|
|
|
8acd3ce1a8
|
fix(offload): 内核说明不能被再转投(自我循环)+ offload_owned 未接线
★ 线上实测两个缺陷:
1. **自我循环**:转投会在队列留一条 [系统] 说明(source=kernel),
而转投条件把这条说明也算进「积压够了」⇒ 每次转投都产生下一轮要转投的东西。
实测 5 秒内连续触发两次,分诊助手不断收到「N 条积压已转投」这类噪音。
修法:takeQueuedInputs 排除 isKernelNotice(source=kernel)。
2. **offload_owned 永远为 false**:我加了 ResidentInfo 字段、加了状态面映射,
却漏了在 rc.info() 里赋值 ⇒ 线上转投子明明存在,读出来是 null。
这类「加了字段但没接线」不会报错,只会让父的判断悄悄失效
(父据此决定回收策略,读到 false 就会把临时助手当成正式子)。
测试 +3:说明不转投 / 循环必须终止 / offload_owned 会被上报。
前两条已实测「禁用守卫会失败、恢复后通过」,是真回归测试。
|
2026-09-19 17:47:44 +08:00 |
|
|
|
943eef01cf
|
feat(resident): 分诊助手定位 + 残余任务由父显式决定
用户澄清(重要定性):这不是「内核替父决定」,而是**及时反馈** ——
主 agent 忙时不该让用户干等十几分钟。子 agent 是**分诊助手**:
简单的直接处理并回复,需要主 agent 的立刻回「忙碌中,请稍候」、不勉强作答。
三处补齐:
1. 分诊助手的职责提示词(之前完全没给 ⇒ 子不知道自己为什么存在):
两条路(直接办 / 报忙碌)、拿不准时报忙碌、必须 output_send 到原通道。
2. 驻留子继承父的 SystemPrompt(之前没传 ⇒ 子只用一句兜底文案,
拿不到「异步通道必须显式 output_send,否则回复被静默丢弃」这条铁律。
webui 这类同步通道能回是因为走 ResponseCh,掩盖了这个缺陷)。
3. 残余任务由父显式决定(用户要求):reclaim/destroy 时子手头未处理的消息
不再由内核悄悄处置 —— 内核只负责列清楚,父用 residual=keep/drop 决定。
之前 pendingEvents 只收带 ResponseCh 的,异步(qq)残余任务完全不在内,
被销毁时静默消失、用户零反馈且日志无痕。
配套:
- scheduler.takeAllPendingEvents:取走全部未执行事件(不筛通道)
- ApplyResidual(keep|drop):keep 转回父队列(保留 ResponseCh),
drop 逐条记日志 + 给同步调用方补终态(否则 cli/a2a 永久挂起)
- 状态面暴露 offload_owned,让父分清「我建的子」与「内核临时拉的助手」
- 工具 schema 加 residual 参数并说明 drop 的代价
这也是用户观察到的「机制很自然」的落点:分诊助手就在同一张登记表里,
父能 inspect/send/compress/reclaim/destroy,控制面 6 动作按 id 生效不区分来源。
测试 +7(残余 keep 转回且保留 ResponseCh / drop 通知同步调用方 /
空残余如实报告 / 分诊提示词 / 继承 SystemPrompt),
其中 drop 那条已实测「对着静默丢弃的旧实现会失败」。全套绿。
|
2026-09-19 17:43:16 +08:00 |
|
|
|
01909bb914
|
fix(offload): 转投必须保留 ResponseCh,否则同步调用方永久挂起
线上实测第二个 bug:转投生效、子也正常处理(日志各 ~3s),但 webui 的 HTTP
请求一直挂着不返回,最终 504。
根因:第一版用 InjectInputTo 转发,它会**重建** InputEvent ⇒ ResponseCh 被丢掉。
而 cli / a2a / webui 这类**同步**调用方正阻塞等这个 channel。
仓库反复警告过同一件事(Agent.Stop 的注释:「带 ResponseCh 的同步注入方
(cli / clawhubadapter 均无超时)会永久挂起」)。
修法:改走既有的跨 agent 投递原语 DeliverRouted —— 它推**原事件**,保留
ResponseCh/RequestID,只往 payload 里补转投标注。
回归测试 TestForwardKeepsResponseCh 断言**最强的那条性质**:真的等同步回执回来。
(不用「读子的 InputChan」来断言:SpawnResident 会启动子自己的调度循环,
它会与测试抢同一个 channel,那样写出来的测试是 flaky 的 —— 我第一版就是这样,
实测挂死过一次。)
已实测该测试对着错误实现会失败(10s 超时)、修后通过。
|
2026-09-19 17:16:24 +08:00 |
|
|
|
fe1d2672d8
|
fix(offload): 积压可能全堵在 io 输入 channel,不在就绪队列
线上实测发现上一版**永不触发**:主 agent 跑着 6×45s 的长任务、我连发 4 条消息,
scheduler 始终显示 queue=0、residents=0,转投一次都没发生。
根因:schedulerLoop 是**同步执行**任务的,所以「正忙」期间它根本回不到循环顶部
去调 pumpInbox —— 后到的输入全堆在 io.inputCh(容量 256)里,压根没进 sched.queue。
而 takeQueuedInputs 只看 s.queue ⇒ 恒取不到东西。
★ 仓库里早记过同一个坑:armStop 的注释写着「pending 是还没被 pumpInbox 搬进队列
的那一段……只数 s.queue 会得到 0(实测),配额随之失效」。我重犯了它。
修法:转投前先 drainInboxToQueue() 把 channel 里的输入搬进队列。
与 pumpInbox 的区别是**不要求 hasRoom** —— pumpInbox 满时会停下保留背压,
而转投场景恰恰是「队列空、输入堵在 channel」(调度器回不到 pumpInbox)。
队列上限仍由 enqueue 把关,放不下的给同步调用方 skipped 终态(不丢、不阻塞)。
回归测试 TestOffloadSeesInputsStuckInChannel 精确复现该现场状态:
已实测它对着修复前的逻辑**会失败**(期望 3 实际 0),修后通过 —— 是真回归测试。
|
2026-09-19 17:05:38 +08:00 |
|
|
|
69446a2649
|
feat(scheduler): 主 agent 忙时把积压任务自动转投给驻留子
问题(2026-09-19 线上实测):主 agent 被长任务占住时(现场:12 分 8 秒、69 次
工具调用),后来到达的消息全部以 level insufficient 排进中断队列干等 —— 同级
中断不能抢占同级运行任务(canPreempt),只能等前一个跑完。而内核本有驻留子
(独立 agent + 独立调度器)可并行干活。
行为(用户 2026-09-19 明确要求):
- 触发:运行任务持续 > offload_busy_after(5m) 且积压 >= offload_min_pending(3)
- 拉起/复用「转投专用」驻留子,把积压的纯排队输入转投过去
- 在原队列位置留下说明「[系统] N 条积压任务已转投给驻留子 agent X 处理…」
通道配置(按用户口径,与人工创建的子刻意不同):
- 不配 inputch(内核的干活 agent,不接收插件用户输入)
- 持有全部输出通道(结果要能发回 qq/webui 等正确通道)
三个设计要点(都是实测撞出来的,写进代码注释与设计文档 §7.1):
1. 检查必须在**独立 goroutine**:schedulerLoop 同步执行任务,放它里面在
「正忙」期间根本回不到循环顶部 ⇒ 永不触发(我第一版就写错了,测试才发现)。
2. 只转投 TaskQueued 纯排队输入:中断任务带级别语义、self 任务与父的记忆面绑定。
3. 转投失败/关闭时必须把任务**放回队列前端**:吞一条输入比多处理一条更糟。
这是设计 §7「决策在父的模型手里」的**刻意例外**(父正忙、物理上无法决策,
而积压任务本来就是空的),已在文档中显式记录,且默认关闭、由部署方显式打开。
测试 11 条:只取排队输入 / 不足量不取 / 放回不丢任务 / 说明自解释 / 默认关闭 /
空闲不触发 / 端到端转投 / 上限不增殖 / 独立 goroutine 确实会触发。
|
2026-09-19 16:59:47 +08:00 |
|
|
|
e273924511
|
fix(llm): 参数无法解析时给出真因,不再静默丢弃整条调用
★ 上次修复误判了成因。真实根因(本次运行日志 34/34 同形):
{"command": "…完好的长命令…", "timeout": 20s}
command 一字节没错,只是 timeout 值少了引号 —— cmd_run 的 schema 把 timeout
声明成 string、示例写着 "10s, 1m, 30s",模型照抄格式却忘了引号。
finish_reason=length 出现 0 次 ⇒ 上次那条"截断"分支从不生效。
旧行为把**整个参数**丢掉,模型只看到 "command is required",看不出坏在 timeout,
只能原样重试。实测本次运行 cmd_run 失败率 35%(34 败 / 71 成),
12 分钟的任务里更是 48% 时间耗在这上面 —— 每次失败都付一次完整 LLM 往返。
三处改动:
1. repairToolArgsJSON:解析失败时先试窄修复 —— 只给"值位置上未加引号的带单位
数字"补引号,且修完必须真能解析成功才接受。不碰合法 JSON、不动正文里的 20s、
不会把真截断"修好"。
2. 修复仍失败时不再静默降级成空 map,改为带 __arg_error 交给模型,并按成因
分流文案:截断→拆小参数;JSON 写坏→提醒带单位的值要加引号。
3. 统一键名 __arg_error(原 __truncated_error 只覆盖截断,语义过窄)。
同一缺陷面不止 cmd:agentcli/healthcheck/timer 都有 string 类型却以
"5m, 1h" 作示例的参数,此修复一并覆盖。
回归测试:真实日志样本修复、保守性(不碰合法/正文/截断)、
端到端(修复后 timeout 仍能被 time.ParseDuration 接受)。
|
2026-09-19 16:49:16 +08:00 |
|
|
|
53e7106985
|
fix(llm): 参数解析失败不再丢弃完好字段(改错值格式,不是截断)
★ 上次修复误判了成因。真实根因(日志 11/11 同形):
{"command": "…完好的长命令…", "timeout": 20s}
command 一字节没错,只是 timeout 值少了引号 —— 而 cmd_run 的 schema 把
timeout 声明成 string、示例写着 "10s, 1m, 30s",模型照抄格式却忘了引号。
实测 finish_reason=length 出现 0 次,所以上次那条"截断"分支从不生效。
旧行为把**整个参数**丢掉:模型只看到 "command is required",看不出是 timeout
写坏了,只能原样重试 —— 12 分钟的任务里 30 次失败 / 32 次成功(48% 浪费),
每次失败都付一次完整 LLM 往返。
改法:parseToolArgsJSON 失败时先试 repairToolArgsJSON,只做一件很窄的事 ——
给"值位置上未加引号的带单位数字"补引号,且修完必须真能解析成功才接受。
因此不会改坏合法 JSON、不会动字符串正文里的 20s、不会把真截断"修好"。
真实日志样本 + 保守性 + 反伪造三组回归测试已钉死。
|
2026-09-19 16:47:13 +08:00 |
|
|
|
6ddef5e49f
|
feat(llm): 声明真实上下文窗口 + 工作区间与窗口分离
问题:core.llm.model=AUTO,而 ModelContextWindow("auto") 匹配不到任何分支、
掉进 default 32768 —— 该源真实窗口是 1M(实测 990,034 token 的 prompt 通过),
内核却按小 30 倍的窗口算全部预算。
三处改动:
1. ModelContextWindow 补 deepseek-v4/v3 → 1M;推断不出时打日志(静默降级是
这次问题的成因,不能再默默退回一个小值)。
2. sourceFieldDefs 补 context_window 声明:它早已被 readInt 读进 LLMSource
并透传到 provider,但没进这张表 ⇒ WebUI 里看不见也改不了。
3. ComputeTokenBudget 新增 maxTargetTokens=600000:窗口 1M 不等于按 838K
(80%)干活。标称窗口≠有效窗口,600K 是该源最优工作区间,所以把
「窗口上限」(会不会被上游拒)与「工作区间」(预算分配)分开。
配套 core.llm.max_tokens 4096→32768(实测长输出样本达 18,272 token,
16384 仍会截断;上限是 cap 不是目标,短问答零成本)。
测试:tokenbudget_test.go 钉死封顶生效且小窗口不受影响;
context_window_test.go 钉死推断值与显式声明优先级。
|
2026-09-19 14:24:31 +08:00 |
|
|
|
95292aff8e
|
test(llm): 钉死截断必须短路工具分派
补一条端到端断言:截断的 tool call 绝不能拿着空 map 走到 files_write
(那会回 'path is required',模型据此原样重试)。断言 executeToolCallInner
的短路 + 指引可执行。
|
2026-09-19 13:53:56 +08:00 |
|
|
|
2722d76095
|
fix(llm): max_tokens 截断不再静默降级成空参数
长参数工具调用(整段脚本/大 JSON)被 core.llm.max_tokens 从中间切断时,
上游回 finish_reason=length,而旧实现把这个信号整个丢掉:残缺 JSON 解析失败
后静默降级成空 map,工具只看到参数为空并报 'path is required'。模型因此完全
看不出真因,原样重试四遍、次次撞同一堵墙(2026-09-19 实测 4 次 files_write 失败)。
注:files_read 并未失败——是写挂之后模型反复重写把读卷进同一轮,看起来像两者都报错。
改法:
- finish_reason=length 时不再静默降级,改为塞入 __truncated_error 指引,
告诉模型「参数被截断 + 请拆成多次调用/追加写 + 勿原样重试」;
- executeToolCallInner 见到该标记即短路,不拿空参数去调工具;
- 非截断的残缺 JSON 保持旧行为(避免把「厂商不回 finish_reason」误判成截断)。
回归测试 2 条钉死这两面。
|
2026-09-19 13:49:21 +08:00 |
|
|
|
01113664b4
|
fix(obs): 抢占日志改在判决点打(修自伤)+ scheduler 事件接进 SSE
## 修我上一版的自伤
上一版把 preempt 日志打在 `executeNewTask`,但那时 `nextRef` 已经把
`s.running` 换成了抢占者自己,于是输出成了
preempt start: task#2 ... -> victim task#2 (cli)
victim 打印的是入侵者本人。判据必须落在 `registerInterrupt`——那一刻
running 还是真正的受害者。改为在抢占判决点打:
[agent] preempt: task#2 class=interrupt level=3 from cli (L3) preempts task#1 class=queued level=0 (qq)
## 补上「入队而非抢占」的日志
中断到了却没生效,此前完全不可解释。现在两种成因分开写:
[agent] interrupt queued: ... vs ... (qq) — running in critical section; queue=N
[agent] interrupt queued: ... vs ... (qq) — preempt cooldown; queue=N
[agent] interrupt queued: ... vs ... (qq) — level insufficient; queue=N
没有这条,`interrupt from X` 打过之后任务为什么没让位就只能猜。
## scheduler 事件接进 SSE
`EventScheduler` 此前既不在 `handler_chat.go` 的 subTypes、也没有任何订阅者
(全仓 grep 零命中)——内核里 suspend/resume 只 publishEvent,于是事件发出来
就掉地上,对内对外都不可见。加进 subTypes 后前端/客户端能看到抢占链。
## 验证
- preempt_logging_test.go 增一条:victim 与入侵者必须是不同来源(qq vs cli),
且排队输入对排队任务 `canPreempt` 必为假。
- 实测输出含 `cli (L3) preempts task#1 class=queued level=0 (qq)`。
- 全量 `go test ./internal/... ./cmd/...` 与 `go vet ./internal/...` 全绿。
|
2026-09-19 11:57:50 +08:00 |
|
|
|
b1ec278136
|
feat(obs): 抢占日志说出「受害者是谁」——suspend/resume 此前完全不落日志
排查「我的任务怎么被莫名打断了」时撞上的观测缺口。
## 缺口
`executeNewTask` 里挂起、`resumeTask` 里恢复,两处都**只发事件、不写日志**:
a.sched.suspend(t, f)
a.publishEvent(events.EventScheduler, map[string]any{"action": "suspend", ...})
于是生产日志里只有两行:`interrupt from X` 与 `LLM request cancelled by
preemption` —— **看不到受害者是谁、被谁挤下去、后来有没有恢复**。后果是实测过的:
按时间先后猜凶手,把时间上相邻的输入误认成抢占者。
## 改动
- `sourceOf(task, frame)`:取可辨识来源(`evt.Source` 优先,回退 OutputChannel,
自循环任务给 `self:<channel>`)。取 Source 而**不是** OutputChannel:
前者回答「谁送来的」(qq / homeagent-mail-bridge / timer / child/xxx),
后者只回答投递到哪个通道;多数场景同名,但因果链上要的是前者。
- `describeTask(task)`:`task#N class=queued|interrupt level=L`。
- `suspendDepth()`:日志专用,走锁而不是让日志点直接摸 `suspendStack`。
- 三个日志点:抢占开始(含 victim)、挂起(含来源与栈深)、恢复。
输出形状:
[agent] preempt start: task#2 class=interrupt level=4 from cli -> victim task#1 class=queued level=0 (qq)
[agent] suspend: task#1 class=queued level=0 (qq) yields to an interrupt; suspendStack=0
[agent] resume: task#1 class=queued level=0 (qq) resumes after the interrupt finished
## 验证
- 新增 preempt_logging_test.go:抢占后栈深 0→1、sourceOf 取到 qq、self/nil 不 panic。
- 实测日志(TestPreempt_HigherPreemptsAndResumes)三条齐全,能一眼看出
是 `cli` 的 L4 挤掉了 `qq` 的排队任务、随后 qq 恢复。
- `go test ./internal/... ./cmd/...` 全绿。
|
2026-09-19 11:31:04 +08:00 |
|
|
|
d202f2ceec
|
feat(ohos): screensue 改用 Web 组件渲染 HTML(RichText 撑不住)
用户实测截图:推送内容把整段 HTML 源码当字符串显示(含 <style>、@keyframes、
内联 <svg>、radial-gradient)。RichText 只认极小标签子集,这些一律不渲染。
用户明确要求「引入 webview」。
## 修法
- 新增 `common/ScreensueHtml.ets`(从 BridgeCaps 抽出:后者加进 HTML 逻辑后
超 520 行,越了工程「单文件 ≤400 行」的约定;且「screensue 怎么解析/渲染」与
「设备能力怎么实现」本是两件事)。
- `ScreensuePage.ets`:HTML 走 **Web**,纯文本仍走 Text。
- 加载用 `loadData(base64)`:encoding 非 base64 时按 URL 规则转义,几 KB 的
完整文档会撞长度/转义问题。自写 `base64Utf8`(UTF-8 手编字节,含代理对合成)
—— 直接把 UTF-16 码元交给 Base64Helper 会让中文变乱码。
- 非完整文档补一层 shell(meta viewport + 主题前景色),完整文档原样加载。
## 顺带修掉一个真实缺陷(实测发现)
`looksLikeHtml` 旧判据要求「首个非空字符就是 '<'」。而 agent 传参常把整段文档
连引号一起给(`'<html>…'`)——截图里那个孤立的 `'` 就是这么来的,判据因此
**判否并退回纯文本**,所以看到的是源码。改为扫第一个「像标签开头」的 '<'
(跳过引号/前导文字),且只在其后紧跟字母或 '/' 时才算,避免误判 "a < b"。
Node 复刻同一算法验证了 7 个样例(含截图实况、<3 表情、比较符)。
## 安全(我因为引入 WebView 而必须自己把关)
内容来自 agent(第三方)。显式关闭:
`javaScriptAccess(false)` / `fileAccess(false)` / `domStorageAccess(false)` /
`onlineImageAccess(false)` / `zoomAccess(false)`。
★ **javaScriptAccess 的默认值是 true** —— 不显式关掉等于让远端内容在客户端执行脚本。
已实测取证:推入带 `<script>` 与 `<img onerror>` 的页面,屏上稳定显示 `JS-OFF`,
两条执行路径都没跑起来。
## 另一个实测发现的缺陷
`onControllerAttached` 只在挂载时触发一次,而 ScreensuePage 在 `if (visible)`
里常驻 —— 连续两条 screensue 只改 @Prop、组件不重建,Web 一直显示**上一条**
内容(实测:倒计时变成 33s 但画面还是旧 HTML)。改 `@Prop @Watch('onDataChanged')`
显式重载,并记录 attached 状态避免过早 loadData(会抛 17100001)。
## 验证(模拟器真机链路)
把工程内连接指向本机服务、开启「允许 agent 控制本机」授权,经
`device_ctl_cmdrun` → 设备桥 → screensue 推入用户截图里那段原样 HTML:
- 渲染成功:radial-gradient 背景、内联 SVG 兔子、CSS 发光文字、两行文案
(修复前同一段内容显示为满屏标签源码)
- 连推第二条 → 画面正确刷新为 SECOND PUSH
- JS 探测 → JS-OFF(脚本被拦)
模拟器只能装 unsigned 包(signed 报 READ_PASTEBOARD 授权失败,与既有记录一致)。
|
2026-09-18 12:01:33 +08:00 |
|
|
|
895948b24e
|
fix(stop): 配额须计入停在输入 channel 的待处理消息
实测:停止后排队消息仍逐条跑完。原因是 armStop 只数 sched.queue,
而用户按下停止时调度器正忙于当前任务,其余消息大多还没被 pumpInbox
搬进队列、仍停在 inputCh ⇒ queued=0、配额归零。
- IOManager.PendingInputs():暴露 channel 中待处理条数。
- armStop(pending int):queued = len(s.queue) + pending。
- 补 TestStop_ArmCountsPendingChannelInputs 锁死该口径。
|
2026-09-18 11:39:04 +08:00 |
|
|
|
698ff7ddd5
|
fix(stop): 修自伤——interceptLoop 里 takeStop() 被 || 短路提前消费
上一版把 stop 标记在 interceptLoop 里消费掉了一部分:
`if n := armStop(); n > 0 || takeStop() { ... }`,queued=0 时短路到
takeStop(),标记先被吃掉,stepLLM 永远看不到 → 取消后照样重跑一轮。
实测:日志正确打出 stop requested ... queued=0,但生成仍跑到自然结束(5000+ 字全文落库)。
改为只 arm 不 take(takeStop 只由 stepLLM 消费),并补一条**经真实
interceptLoop** 的用例:它必须 a.Start()(第一版测试只调 New(),
interceptLoop 根本没跑,假绿)。已验证该用例在注入此 bug 时失败、修复后通过。
|
2026-09-18 11:32:20 +08:00 |
|
|
|
ccc2ac2d4d
|
fix(stop): 停止按钮真正生效——停止 ≠ 空中断;鸿蒙 screensue 支持 HTML
两处鸿蒙端缺陷 + 一个跨端(WebUI/GUI/鸿蒙)的停止语义缺陷。
## 症状(实测取证)
1. **鸿蒙终止按钮按下没反应**。POST /chat/interrupt 带空 body,接口回 200
`{"status":"interrupted"}`,但 journalctl 零中断日志、生成继续跑到自然结束。
2. **鸿蒙 screensue 不解析 HTML**,把标签当普通字符串显示。
## 根因
停止按钮走的是「空内容中断」,而 interceptLoop 有一行
`if text == "" { continue }` —— 空内容被判为「无事发生」直接丢弃。
所以停止指令从未到达调度器;接口那个 200 是不诚实的。
另查明两条会放大症状的既有问题(停止后仍在跑):
- `chatStreamWithFallback`:流式连接失败时无条件回退非流式 `Chat`。
上下文已取消时这等于**再发一次完整请求**(停止后模型继续生成)。
- `stepLLM`:`context.Canceled` 一律 `outcomeContinue` 重跑本步。
这是给「被更高中断抢占」用的(现场要交出去、稍后继续),
但用户按停止是「不要了」,重跑就是停止没生效。
## 修法(按用户明确的设计)
停止 = ①立即结束当前 LLM 推理(不重试、不恢复);
②对**停止那一刻已排队**的 x 条消息,后续在 pre-action 阶段依次短路。
- scheduler:新增 `armStop`(登记快照配额并返回当时排队深度)/`takeStop`/
`consumeCancel`。配额取快照值(停止后新到的输入不受影响),
重复按停止取 max 不累加(两个客户端同时按不该翻倍)。
- `interceptLoop`:读 `stop` 标记。停止时 armStop + cancelCurrentLLM;
**纯停止不再进中断队列**(旧实现把它当空中断入队,所以停完还会活)。
带注释的停止(`/stop 换个话题`)仍走中断路径。
- `stepLLM`:取消 + `takeStop()` → 直接 `outcomeDone`(不再重跑)。
- `stepPrepare`:`consumeCancel()` 命中即在 pre-action 短路收尾。
- `chatStreamWithFallback`:以 **ctx.Err()** 为判据拒绝回退(不是「错误是不是
Canceled」——很多 provider 用 Canceled 表示「不支持流式」,那种必须继续回退,
否则会把探测误判成取消;这条区分是跑全量测试时才暴露的)。
- WebUI handler / CLI `/stop`:空消息时带 `stop:true`。
## 鸿蒙端
- `BridgeCaps.ets`:新增 `looksLikeHtml`(首字符 '<' + 字母开头标签名,
避免误判 "<3" 这类文本)、`screensueHtml`、`escapeHtmlText`。
- `ScreensuePage.ets`:HTML 走 **RichText**(只解析 HTML 子集、无脚本无网络),
纯文本仍走 Text。不用 Web 组件:agent 下发的是第三方内容,
Web 默认带 javaScriptAccess/fileAccess,等于让远端内容在客户端执行脚本。
注入主题前景色,避免 RichText 用系统默认色导致深色主题下黑字不可见。
- `ChatSession.ets`:`interruptChat` 改发 `{stop:true}`(含类型声明,
ArkTS 禁止无类型对象字面量),并在本地即时复位忙态 + 提示「已停止」。
## 验证
- 新增 `stop_semantics_test.go`:停止终结任务不重试(provider 调用次数恒为 1)、
配额是快照(x 条短路、随后新到的不受影响)、重复 arm 取 max。
- `go test ./internal/... ./cmd/...` 全绿。
- 鸿蒙 HAP 构建通过;unsigned 包已装进模拟器(signed 包受
READ_PASTEBOARD 授权限制装不上,与既有记录一致)。
|
2026-09-18 11:26:12 +08:00 |
|
|
|
831bd2290b
|
fix(ohos): 聊天改用 seq/after 增量轮询,修「不滚动/己方消息不显示/新消息不加载」
jianf 报的三个现象其实同一个根因:WebUI 前端在 commit 9711177 已改为
「暴露数据查询 API + 前端轮询 patch 视图」,聊天记录走 /chat/history?after=<seq>
增量游标 + seq 对账;鸿蒙端一直只做**首屏全量加载**,没跟上这套口径。
1) 页面不加载新的聊天信息(根因)
鸿蒙只在 aboutToAppear 拉一次 /chat/history?limit=40,之后除了 SSE 就没有
任何拉取。SSE 只在「本端发起的那一轮」推事件,其他端/其他渠道(QQ、
WebUI 浏览器)发来的消息永远不会出现在鸿蒙页面上。线上实测:鸿蒙
IP(61.54.104.206, auth=api-key)最后一次请求停在 9/17 22:34,此后
只有浏览器 session 在轮询。
2) App 发出的消息不显示
原来靠 mergeHistoryWithLocal 按「正文内容」去重:本地乐观 user 消息
与服务端回显内容一致就被判重复丢弃;同一句话发两次同样误删。
改为按 seq 对账(新增 reconcileServerMsgs,对齐 WebUI applyServerMessages):
服务端带 seq → 有则原地更新、无则认领本地无 seq 的同类乐观消息;
旧后端无 seq 才退化为内容比对。
3) 加载完不滚动、停在最新消息处
@Watch 只在值**变化**时触发,不触发初始值。ChatPage.aboutToAppear 里
loadHistory() 是异步的,若它在 ChatStream 构造之前就完成,
requestScroll 递增的 chatScrollRev 就成了「挂载前已发生的变化」——
onScrollReq 永不被调,于是停在顶部/中间。
修:ChatStream.aboutToAppear 见已有消息就自己滚一次;scrollToBottom
的重试从 50/260ms 扩到 50..800ms(长历史布局慢,两次不够),
并用世代号作废旧一轮定时器,避免与新滚动打架。
配套:
- Model.ChatMessage 加 seq;ChatHistory 解析 seq 与 last_seq(游标缺失时
回退本页最大 seq,保证不倒退)。
- 新增 pollIncremental():after 增量 + limit=1 尾部探测(工具卡/最终文本是
原地改写已有 seq,不产生新 seq,只靠 after 拿不到),3s 节奏与 WebUI 一致。
- startPolling 挂在 connect() 而非 loadHistory 末尾:首屏失败也能自愈。
- disconnect() 停轮询。
验证:hvigor assembleHap BUILD SUCCESSFUL;make check-client-versions 一致;
go build/vet/test 全量零失败。
|
2026-09-18 10:49:30 +08:00 |
|
|
|
1f5af1dccf
|
feat(cli,ohos): 补齐两处能力缺口——CLI /memory context|tools、鸿蒙 camerasue 录像回传
核对「CLI 与 WebUI 插件能力对齐」时发现两个此前遗漏的缺口,一并补齐。
1) CLI /memory 缺 context 与 tools(internal/plugins/cli/plugin.go)
WebUI 有 GET /api/v1/memory/context 与 /api/v1/memory/tools,CLI 只有
query/graph/text。IndexerAPI 本就对内部插件开放(BuildContext/
FormatContext/GetToolDefinitions/BuildToolPrompt),不是 SDK 缺口。
补上后与 WebUI 同源:context 打印「实际会注入什么上下文」,
tools 打印工具定义 + 工具提示词。
waiter 同步接上两条远端路由与 help 文案。
2) 鸿蒙 camerasue 录像(BridgeCaps/BridgeRouter/DeviceBridge.ets)
此前 `camerasue <N秒>` 直接返回「暂不支持录像回传」。查 SDK 后发现
cameraPicker 本身就有 PickerMediaType.VIDEO 与 PickerProfile.videoDuration
—— 录像完全可行,只是回传通道没接。
现改为:VIDEO 模式取回 mp4,经 DeviceBridge.sendDataChunked 按
cmd_data_start/分块/cmd_data_end 回传(与 GUI/CLI 录像路径一致),
网关聚合后落盘成文件、agent 拿路径;照片仍走小体积 base64 内联。
上限 64MB、时长 1~300s,超限明确报错而不是把 WS/上下文撑爆。
依赖方向处理:BridgeCaps 需要「往本请求回传字节」,但 DeviceBridge 为取
CapResult 已 import BridgeCaps,反向 import 会成环。改为 BridgeRouter
注入 DataChunkSender 回调(它同时持有 deviceBridge 与 reqId),
BridgeCaps 不碰 socket。新增 CapResult.chunked 标记「结果已由能力分块
发完」,DeviceBridge 据此不再回 cmd_result,避免网关把已完成请求与后续
分块错配。
验证:hvigor assembleHap BUILD SUCCESSFUL(ArkTS 编译通过,改动文件零告警);
make check-client-versions 一致;go vet 干净;全量 go test ./internal/... ./cmd/... 零失败。
|
2026-09-18 10:04:53 +08:00 |
|
|
|
9de3b365a6
|
fix(agentcli): 修 4 个真实缺陷——停机死锁、超时泄漏、僵尸堆积、孙进程逃逸
jianf 提示 agentcli 可能有问题,系统性审了一遍(含 -race 与线上实证),
确认并修复 4 个互相叠加的真实缺陷,每个都配了「去掉修复即失败」的回归测试。
1) 停机/热重载死锁(plugin_stop_test.go)
Stop() 先 p.wg.Wait() 再 Close 终端,而 readLoop 自己也记在 p.wg 上、
只监 t.stopCh 不监 p.stopCh。只要有一个终端开着,wg.Wait() 就永不返回。
后果:插件卸载/热重载(StopAndUnload/ReloadOne)与停机全挂死,且
registry 持锁时是整个内核一起挂。
修:先关活跃终端(move 出 map 后在锁外 Close),再 wg.Wait();
readLoop 顶部加 p.stopCh 探测;Stop() 用 sync.Once 保证幂等。
2) 终端超时后资源全泄漏(plugin_lifecycle_test.go)
readLoop 的 IsExpired 分支只 delete(sessions) 后 return,既不 Kill 也不
Close。终端已被移出 sessions,cleanupLoop 也再看不到它,进程/PTY fd/
reader 协程无人回收。实测:timeout=1s 的 sleep 300 超时后进程仍在跑。
修:readLoop 加 defer releaseResources(),保证「只要退出就释放」。
3) 子进程从不回收 → <defunct> 僵尸堆积(pty_linux.go + plugin_lifecycle_test.go)
newCommandPty 只 Start 从不 Wait。线上实测 homed 名下已有一个
[sh] <defunct> 僵尸子进程。
修:linuxPty 加 Wait()(sync.Once 保证只 Wait 一次),
releaseResources 通过可选接口 Wait() error 调用(Windows ConPTY 不实现则跳过)。
4) Kill 只杀直接子进程,孙进程逃逸(pty_linux.go + plugin_lifecycle_test.go)
newCommandPty 用 Setsid,sh 是新进程组领头,真正的命令(sleep/vim)是
其孙进程且同组。只 Kill(sh) 会留下孤儿继续跑。实测:`sleep 300; echo done`
只杀 leader 后 sleep 仍在(被 init 收养)。
修:改为 syscall.Kill(-pid, SIGKILL) 杀整个进程组,失败再回落单进程 Kill。
测试设计要点:回归用例必须让「sh 保留为父进程 + 孙进程显式 trap "" HUP」,
否则单个 sleep 会被 sh exec 掉、关 PTY 的 SIGHUP 又会顺手带走孙进程,
两个缺陷都测不出来(这两种情况都实际踩过并修正了用例)。
全量 go test ./internal/... ./cmd/... 通过,agentcli 单包 -race 通过。
|
2026-09-17 20:24:03 +08:00 |
|
|
|
ec13eb391a
|
fix(agentcli): 终端退出前补推残留输出,短命令输出不再丢失
线上验证「内核开、两个插件接」时发现的真实缺陷:`echo`、`ls` 这类在首个
200ms ticker 之前就结束的短命令,readLoop 走到 `!terminalRunning(t)` 分支
直接 return,残留在 stream 里的输出从未 flush。
症状:输出只留在 session.buf 里——agent 用 terminal_read 能看到,但
terminal_output 事件永远发不出去,于是内核权威视图(以及 WebUI/CLI 的
/terminals)的 output 恒为空。实测 term_2(echo HELLO_KERNEL_REGISTRY)
在 /terminals 里 output="" 而 agent 同期 terminal_read 拿到了正文。
修法:
- 抽出 flushTermStream(s, t),ticker 与所有退出路径共用同一条推送路径
(避免以后再出现「某条退出路径忘了 flush」)。
- readLoop 顶部加 defer:defer flushTermStream 后于 defer emitTermState
声明 → LIFO 下先 flush 再报停止,保证「最后一段输出」先于 running=false
到达订阅者。
测试:plugin_flush_test.go 新增 TestReadLoopFlushesOutputOnExit——走
EventBus 捕获事件,推入输出后立即让进程退出(远早于 ticker),断言输出
已补推且末态 running=false。已验证去掉修复即 FAIL、加回即 PASS。
注:handleRead(clear=true) 会主动 Reset stream(避免与读取结果重复),
属既有设计;本修复针对的是「未被读取就退出」的路径。
|
2026-09-17 20:00:47 +08:00 |
|
|
|
e70d2171ee
|
refactor(terminal): 内核开终端/命令历史权威视图,WebUI 与 CLI 都改接内核
按「内核开,两个插件接」重构终端与命令历史的数据归属。
背景:此前 WebUI 与 CLI 各订 EventToolCall/EventTerminalOutput 攅一份状态,
同一件事两份推导,还各自踩过同一个坑——工具 result 是 Go 的 map 文本
(map[cols:80 ... id:term_2 ...]),断言成 map[string]interface{} 永远失败,
terminal_create 的 id 回填不生效,/terminals 因此恒空(WebUI 也一样)。
实测确认:WebUI 自己的 /api/v1/terminals 与 /api/v1/cmd/history 同样是空的。
内核开(权威唯一真相):
- internal/agent/core/terminal_registry.go:TerminalRegistry 归并两类事件——
EventToolCall(terminal_create/close、cmd_run,id/command 从 args 或 Go map
文本回填)与 EventTerminalOutput(agentcli 生命周期 + 输出,含 64KB 缓冲上限、
100 条命令历史、50 个终端上限)。
- internal/sdk/terminal.go:新增 TerminalAPI(ListTerminals/CmdHistory)与
TerminalStatus/CmdExecStatus DTO。**不塞进 KernelStatus**:那是全量快照,
前端每 3 秒轮询 /kernel,背上每终端最多 64KB 输出会让轮询成本爆炸;
终端输出是按需拉取的明细,另开接口。
- Agent 订阅自己的事件总线(subscribeTerminalRegistry),且**只根 agent 建**
(驻留子共用同一总线,每个子都建会 N+1 份重复记账)。
- SDKConfig/Registry/bootstrap 接线:pluginReg.SetTerminalAPI(agent)。
生产者补全(agentcli):终端无输出时 ticker 不发事件,内核就无从知道终端
存在。新增 emitTermState,在 handleCreate/handleClose/readLoop 退出(超时/
进程结束/读取错误/stopCh)显式上报 running 状态,并给输出事件补 command 字段。
handleClose 改为接收 *sdk.PluginSDK 以便上报。
两个插件接(消费方):
- WebUI:删掉本地 termStates/cmdHistory/subscribeTerminalStream/handleToolEvent
及不再使用的 getStr;/terminals 与 /cmd/history 直接读 s.Terminal()。
- CLI:删掉上一轮刚加的 subscribeToolEvents 与 cliTermState/cliCmdExec;
/terminals 与 /cmd/history 直接读 s.Terminal()。两条路(local/remote)都通。
测试:新增 terminal_registry_test.go,锁死 Go map 文本解析(旧缺陷根因)、
生命周期、CLI 直调路径(无 EventToolCall 仅凭 output 事件建条目)、历史与
终端数量上限。全量 go test ./internal/... ./cmd/... 通过。
|
2026-09-17 18:56:15 +08:00 |
|
|
|
3cce605722
|
feat(cli): /terminals、/cmd/history、/terminal 对齐 WebUI(事件面 + ToolAPI,无需新接口)
去看了一遍源码,纠正上轮判断:终端与命令历史也不是 WebUI 插件私有。
- 终端会话:agentcli 插件持有,发 EventTerminalOutput;WebUI 只是订阅该事件
自己攒视图。命令历史:WebUI 订阅 EventToolCall 的 cmd_run 攒的。
- 于是 CLI 插件订阅同样两个事件即可同口径:/terminals、/cmd/history。
- 开/写/读/关终端:SDK 的 ToolAPI.ExecuteTool 已允许跨插件调用工具,
CLI 直接调 agentcli 的 terminal_create/write/read/close,新增 /terminal 子命令。
waiter 侧同步:/terminals、/cmd/history 两条路(local/remote)都接;/terminal
仅在 local 可用(远端 WebUI 无对应 REST 端点,明确提示而不是当聊天发出去)。
|
2026-09-15 15:31:32 +08:00 |
|
|
|
0227fc2d4d
|
feat(cli): /persona 与 /agents 对齐 WebUI(复用已开放的 SDK 面)
- /persona:读写 core.agent.personal_prompt / core.internal.persona_initialized;
插件 SDK 的 Settings() 满足 internal/config.PersonaKV,与 WebUI 同一实现。
GET 等价返回 initialized/current_prompt/file_override;/persona set <mode> [内容] 写。
- /agents:改用 supervisor.ListAgents()(WebUI /agents 同源),此前只回一个
agent_id、驻留子信息全丢。
- waiter 侧 /persona 在 local/remote 两条路都接上,/help 补齐。
|
2026-09-15 15:25:20 +08:00 |
|
|
|
91c25fa536
|
feat(cli): /runtime 接上(runtime 本就经 KernelStatus 对内部插件开放)
纠正上一轮的判断:runtime 不是“SDK 未暴露”。s.Status().GetKernelStatus()
里的 Scheduler / Residents / Channels / InputChannels 就是 WebUI /runtime
的数据源,内部插件同样拿得到——CLI 只是漏接了这条命令。
- CLI 插件新增 /runtime,输出与 GET /api/v1/runtime 同口径。
- waiter 侧 /runtime 在 local(发 CLI 插件)与 remote(走 REST)两条路都接上,/help 补齐。
|
2026-09-15 14:29:29 +08:00 |
|
|
|
bcaa8f3f31
|
feat(cli): /plugin install 真正可用(直连 pluginmgr 回环端点,与 WebUI 同实现)
原来 CLI 的 /plugin install 只打印“请去 WebUI”。插件安装逻辑在 pluginmgr
插件里(回环 HTTP,默认 127.0.0.1:9876,无鉴权),WebUI 也是转发到它;
CLI 插件改为直连同一端点,能力对齐。
|
2026-09-15 12:17:34 +08:00 |
|
|
|
5333a33e20
|
feat(cli): CLI 插件能力对齐 WebUI(memory/knowledge/config/tracker/adapters/network)
原来 CLI 插件只覆盖 WebUI 的一小部分:/memory 只有 query、/knowledge 只有
list、没有 config/tracker/adapters/network,结构化输出还各拼一套文本格式。
按 WebUI 的 REST 面对齐:
- /memory query|graph|text [n] (对应 /memory、/memory/graph、/memory/text)
- /knowledge | delete <name> | stats(对应 GET/DELETE /knowledge)
- /config (对应 GET /config)
- /tracker | rollback (对应 GET /tracker、POST /tracker/rollback)
- /adapters | remove <name> (对应 GET/DELETE /adapters)
- /network (对应 GET /network)
- 统一 writeJSONContent:结构化数据一律缩进 JSON,与 WebUI 同口径。
waiter 侧同步:把上述命令在 local(发 CLI 插件)与 remote(走 REST)两条路
都接上,/help 补齐;两条路语义一致。
|
2026-09-15 12:14:25 +08:00 |
|
|
|
95b1950bb3
|
fix(ohos): 历史刷新不再整表替换,避免刚发出的消息凭空消失
sync_required 触发的 reloadHistory 会与刚发出的 POST 竞争:若历史快照
里还没有这条 user 消息,整表替换会让它消失(“客户端侧发出的消息不显示”)。
改为合并:历史为权威,但保留本地两类消息追加在末尾——
- user 且 source 为空(乐观消息)且内容未出现在历史里;
- assistant 且 !isFinal(仍在流式输出)。
|
2026-09-15 12:09:58 +08:00 |
|
|
|
307a6faee7
|
fix(ohos): 设置页插件/工具数不显示 + 聊天自动滚底时机
设置页计数(实测:后端返回 plugins=35/tools=260,卡片却一直显示 '-'):
- 根因是 ArkUI 的 @Builder 按值传参是“快照”语义——父组件因
@StorageProp 变化重渲染时不会用新值重跑 builder,数值永远停在
首次渲染的 0。把 KPI 小卡从 @Builder 方法改为独立 @Component
(@Prop 单向下发),父组件重渲染时子组件拿到新值并重绘。
- 已上模拟器验证:设置页显示 插件 35 / 工具 260。
聊天自动滚动:
- scrollToBottom 原来只在 50ms 后滚一次;长历史/长思考卡的布局在
消息数组更新后的若干帧才稳定,一次滚动会落在“当时”的底部,
最后一条被输入区挡住。改为 50ms 与 260ms 各滚一次,480ms 后
恢复正常滚动态。
|
2026-09-15 11:56:18 +08:00 |
|
|
|
baddaf387e
|
merge: 回流场景式关联召回 + 记忆整备 + 客户端修复(feature/recall-policy)
- 场景式关联召回:声明/涌现双通道、场面指纹聚类、场景前缀/相似度召回
- 记忆整备:doc→graph 闸门、噪音/孤立清理、关系去重、原句回显、memoryPass 收敛
- 本轮修复:衰减真半衰期、索引同步基线、Ensence 死参/死分支、冗余索引
- 客户端:鸿蒙未连接连接入口 + camerasue;waiter 斜杠命令本地语义修复
- 版本:GUI/鸿蒙/waiter 与内核统一 1.4.0(make check-client-versions)
- 客户端版本同步文档 §四
|
2026-09-15 11:13:04 +08:00 |
|
|
|
15497ee0a2
|
feat(ohos+waiter): 补 camerasue 能力;修 waiter 本地斜杠命令被当聊天文本
ohos camerasue:
- LOCAL_DEVICE_CAPS 增 camerasue;BridgeRouter 增 camerasue 路由。
- 实现走系统相机选择器 cameraPicker(三方应用无法无界面直驱摄像头),
结果落应用沙箱(saveUri=filesDir),不写系统媒体库、不需 READ_IMAGEVIDEO。
- 录像(camerasue <N秒>)明确返回“暂不支持录像回传”,而不是回一个超长
base64 撑爆上下文;二进制分块回传待接线。
waiter 本地/远端命令语义统一:
- 修 bug:/settings、/settings set、/plugin install/remove/info、
/memory query、/knowledge delete 本地分支发的是 cmd[1:](丢掉前导 /),
于是 CLI 插件不认、被当成聊天文本丢给 LLM。改为原样发(保留 /)。
- 补 /stop、/interrupt:/help 一直写着但 handleBuiltin 没实现,会落到
“当普通消息发给 Agent”。远端走 POST /chat/interrupt,本地交给 CLI 插件。
- 补 /plugin disable|enable:远端插件管理 REST 动作,本地 CLI 插件。
- /help 文案改为“local 与 remote 行为一致”并列出新命令。
|
2026-09-15 11:12:27 +08:00 |
|
|
|
c151d391ee
|
docs(release): §四 补客户端版本必须与内核同步 + make 门禁
|
2026-09-15 11:04:31 +08:00 |
|
|
|
065732f42e
|
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 |
|
|
|
a9ad97240b
|
fix(ohos): 未连接后端时给出不可错过的连接入口
问题:全新安装(未配置后端)时,聊天页只有空列表+输入框,用户找不到
任何连后端的入口;设置页的连接入口在列表里也容易被略过。
改动:
- 聊天空态按连接状态分流:未连接显示「尚未连接后端服务」+「去设置连接」
按钮;已连接显示「开始新的对话」。
- 跨页信号(AppStorage:K_HAS_CONN / K_REQUESTED_TAB / K_SETTINGS_SUB):
按钮 → Index 切到设置 Tab → SettingsPage 直接打开「后端连接」二级页。
- 设置页在未连接时主动把连接表单推到面前:窄屏 onNavigationModeChange(Stack)
直接 push,宽屏右栏默认页从「运行状态」改为「后端连接」。
- 连接增删改切后广播 K_HAS_CONN,聊天空态即时切换文案与入口。
已在手机(窄屏)与折叠展开(宽屏)模拟器验证:空态按钮可达、点击后
落到带「+ 添加」的连接表单;冷启动点设置 Tab 亦自动打开连接页。
|
2026-09-15 10:52:48 +08:00 |
|
|
|
acc94723fd
|
fix(memory): 打通场景/索引/召回残余矛盾点,清理死代码与冗余索引
- DecaySceneRefs 真半衰期:新增 scene_refs.decayed_at 作计时起点,
每个引用至多每 halfLife 衰减一次。此前只按 created_at 判龄 + 每次
心跳对半砍,30 天阈值配 60 分钟心跳会在几小时内清空老关联(不是半
衰期是骤死);时间基准改走 SQLite datetime('now'),不再与 Go 本地
时间混用。
- Indexer.syncIfStale 基线口径与 Sync 对齐(min(实体数, 全量召回上限)):
实体数超过上限时原实现永远不相等,每 retrainInterval 全量重训一次。
- EnsureScene 去掉从不使用的 Situation 参数;修正 EnterSceneWithHint /
resolveTurnScenes / 测试里「声明场景会学整轮指纹」的过时注释(实际
刻意不学,否则会吃死被动路)。
- SituationFeature.Weight 补 peer_group → wFeatPeer:此前落到 default
话题级 0.4,群聊身份被降级成软信号。
- RecallBySituation 注释改为与实现一致(相似度只决定命中哪些场景,
不参与每条关系排序)。
- 移除只被测试使用的 EmergentScenes(SceneStats 已含 origin/strength/
features,完全覆盖)。
- 清理与 UNIQUE 隐含索引重复的 idx_entity_name / idx_sentences_text。
- 新增 TestSyncIfStaleBaseline / TestPeerGroupWeight,衰减测试补「同一
半衰期内不重复衰减」用例。
go build/vet 干净,internal/... 全绿。
|
2026-09-15 10:20:40 +08:00 |
|
|
|
49695c38f3
|
feat(memory): 场景双通道——主动声明与被动涌现并存,且互不吞噬
按「声明式的也要支持,相当于主动被动两条路」落实。此前两者只是恰好并存,
没有边界,实测会互相吃掉(下面的坑就是)。
- Triple.Scenes []string(多值):一轮写下的记忆**两条路都挂**。
只挂一条会丢东西——只挂声明则细粒度唤起丢失,只挂涌现则首次交互
(场景还没长出来)没有兜底。单值 Scene 保留兼容。
- TurnScene:Primary 用于写(优先涌现场景,首次退到声明场景兜底),
Keys 是两条路的并集,用于召回(声明+涌动的场景一起进 RecallByScene)。
- EnterSceneWithHint:主动路 EnsureScene(声明即建场景,不等第二次),
被动路 EnterScene(指纹聚类)。写侧由 executeToolCall 把本轮场景集合
传给 memory_commit,模型不需要知道"场景"这回事。
踩到并修掉的坑(两条路互相吞噬):
最初让声明场景也吸收**整轮指纹**,于是 chan:qq 的相似度永远是 1.0,
把后续所有同类轮次全部吃掉 → 被动路再也长不出更细的场面,
实测 turn2.Emergent=true 但 Primary 仍是 chan:qq、没有 auto: 场景。
修法:给场景加 origin(declared/emergent):
- 被动聚类只认 origin='emergent' 的场景(声明场景不进相似度空间);
- 声明场景的特征**只从键自身解析**(chan:qq/peer:group_1 → {chan:qq, peer:group_1}),
白名单 kind(chan/peer/peer_group/tool/topic/part),不猜——
「老大2026-09-04_12:27_qq私聊图片」里的 12:27 也是 kind:value 形态,
放进特征空间就是往相似度里灌垃圾(有测试钉住)。
- 声明路的泛化靠**层级键前缀**(chan:qq 覆盖 chan:qq/peer:x),机制各归各。
- memgc -scene-stats 增加 [declared|emergent] 与 strength/features 两栏,
可直接观察两条路各自在长什么。
新增/改写用例:
- TestDeclaredAndEmergentBothLearn:首次交互兜底到声明场景 → 第 2 轮长出
细粒度涌现场景且**优先用于写入** → 声明场景不进相似度空间(防止压死被动路)
但仍走声明键取回 → 两条路都进召回集合 → 声明键特征解析与白名单。
- TestEffectiveScenes:多值+单值合并去重保序。
go build/vet 干净,go test -count=1 ./... 全绿。
|
2026-09-15 09:37:29 +08:00 |
|
|
|
bb7e7979ae
|
fix(memory): SceneStats 的 strength/features 不再说谎
- 旧行经 ALTER 加列后 strength 为 NULL,直接 SELECT 会显示成 0(实际是 1 次);
改为 COALESCE(strength,1),并补上 features 计数(场景长出了几个特征)。
- 两栏一起看才能判断「场景是不是真在涌现」,而不是被一次性写出来的。
|
2026-09-15 09:29:46 +08:00 |
|
|
|
d4f9a12db0
|
feat(memory): 场景从「声明」改为「涌现」——场面指纹自己长成场景
上一版场景是声明/派生的:调用方写 scene="chan:qq",或由通道机械派生。
那不是涌现,是贴标签——标签谁定、怎么定全靠人。按「像人一样:干了什么事,
后续类似场面自动唤起对应记忆」的要求重做。
机制(全部取自运行时可观察量,无需模型配合、无需人工标注):
- **场面指纹 Situation**:每轮采集 `chan:xx / peer:xx / peer_group:xx /
tool:xx / topic:xx / part:xx`。权重按种类:通道与对象最强(1.0),
工具次之(0.8),话题是软信号(0.4),时段最弱(0.2)。
- **归属判定用加权 Jaccard**(不是字符串相等):共享特征权重和 / 并集权重和。
加权是必须的——`chan:qq` 与 `topic:排班` 的证据力差 2.5 倍,不加权会让
一次偶然的话题重合把两个不同场面并成一个。
- **涌现**:同类指纹重复到 minSceneEvidence=2 次才长出场景
(首次只登记 situation_evidence 足迹)。一次性的交互不是「场面」,
给它建场景会让库被一次性事件撑满、之后每次路过都召回一堆只发生过一次的事。
- **强化**:场景每次重现 strength+1、并入新特征。
- **唤起**:RecallBySituation 按**相似度**取回(阈值 0.35,比归属阈值 0.5 低
——想不起来是损失,多想起一条只是多几行上下文),与措辞无关。
- **遗忘**:DecaySceneRefs 按半衰期让久未重现的关联淡出,低于 floor 直接删;
已接进 archive 心跳(半衰期 30 天,比「这个月没做过这类事」更久)。
三个必须讲清的边界:
1. 一轮只解析一次场景(TaskFrame 缓存)——多解析一次就多记一次强度,
「工具调得多」会被误读成「这个场面更常出现」。
2. 声明与涌现**并存**:声明是「我知道这是哪个场面」(插件注入点最清楚),
涌现是「这轮看起来像哪个场面」。两者都进召回。
3. 记忆挂载全自动:memory_commit 没写 scene 时落到本轮涌现场景,
模型不需要知道场景这回事。
验证:go build/vet 干净,go test -count=1 ./... 全绿。
新增用例(核心证据):
- TestSceneEmergesFromRepetition:首次不建场景 → 第 2 次同类场面长出场景 →
同场面**不同话题**仍并入同一场景 → 换通道的场面自己长出独立场景(共 2 个)→
强度随重现增长、特征多条。全程没有任何人声明过场景键。
- TestSceneRecallsBySituationNotWording:场面里写下的规则,换措辞后仍被
自动唤起(含原句),无关场面不唤起。
- TestSceneRefDecay:一个半衰期权重减半、第二个半衰期低于 floor 被清掉,
仍在重现的场景不受影响。
- TestSituationFeaturesFor:指纹维度齐全、归一化、数值 group_id 转换、nil 安全。
|
2026-09-15 09:27:39 +08:00 |
|