|
|
d027c964e2
|
proc: Windows 共享内存 + 事件通知适配(Part 6.2 内核侧)
补齐内核侧的 Windows 创建端,与 6.1 的插件侧打开端配对。三平台
(linux/darwin/windows)现在都能构建 internal/plugin/proc。
## Windows 走命名内核对象(无 fd 继承语义)
os/exec 的 ExtraFiles 在 Windows 实现里不被支持,故:
- shmalloc_windows.go:CreateFileMappingW(INVALID_HANDLE_VALUE + 命名 →
系统页文件支撑的匿名段,不落盘)+ MapViewOfFile
- evtfd_windows.go:CreateEventW 命名 Event 对象 + SetEvent 通知
- shmpass_windows.go:把段名/对象名经环境变量注入子进程
(HOMEAGENT_SHM_STAGE / HOMEAGENT_SHM_EVTRING / HOMEAGENT_EVT_EVENT)
名字带 PID + 递增序号:多个 homed 实例并存时不能撞名。
Event 与 eventfd 的语义差异:Event 是二元信号,多次 SetEvent 只对应一次
唤醒,不累积。不影响正确性——消费者被唤醒后按 readSeq 追 writeSeq 批量
drain,丢的是"唤醒次数"不是"事件";事件环本身就允许溢出丢弃并让消费者
知道丢了(dropped 计数),通知面从来不是可靠投递语义。
## 传递机制抽象为 shmpass_*.go
Plugin.Start 不再直接构造 ExtraFiles 列表,改为问 Host 要:
Env: p.host.procEnvForShm() // Windows 返回段名,Unix 返回 nil
ExtraFiles: p.host.procExtraFilesForShm() // Unix 返回 fd 列表,Windows 返回 nil
平台差异被收敛到这一对函数,Plugin/coreHandler/stage 全部平台无关。
## macOS pipe 生命周期修正
原实现只返回读端 fd,写端 *os.File 无人持有 → 可能被 GC 回收 →
读端收到 EOF 而非阻塞 → 消费循环变忙转。改为 pipePair 表同时持有两端,
evtfdClose 一并关闭。
## E2E 测试跟进模板拆分
模板从单文件拆成三个(主体 + unix/windows 挂载),测试需要一并落盘,
否则编译报 attachStageShm undefined。procRuntimeTemplates 表必须与
SDK 仓 proc_runtime.go 的 procRuntimeFiles 一致。
验证:三平台 go build ./internal/plugin/... 通过(gojieba 的 cgo 依赖
导致 internal/memory 在非 linux 失败,与本次无关);
go test -race ./internal/plugin/... 全绿,含 2 项真实模板 E2E。
Ref: docs/zh/架构迁移评估.md §9.2、docs/zh/plugin-migration-plan.md Part 6
|
2026-09-02 19:07:14 +08:00 |
|
|
|
53a148cb54
|
docs: Part 5 标记核心已完成,进度快照更新事件环
Part 5 通知面内核侧 + 模板侧均已完成,端到端测试通过。
事件订阅从 C ABI 的空实现(case 23/24)变成真正可用。
测试数量更新:proc 38 项 + plugin 16 项(含 -race)。
下一步转 Part 6 逐插件迁移。
|
2026-09-02 17:07:43 +08:00 |
|
|
|
5bbfcc02fb
|
plugin: 事件环内核侧实现(§3.6 Part 5 核心)
事件环(EvtRing)是子进程首次获得事件订阅能力的基础设施。
此前 case 23/24 明确返回未实现,现在经事件环真正可用。
核心设计(§3.6,实验 4 已验证 post-and-forget 加速比 2218x):
- 事件环放**独立共享段**(不与 StageContext 混放):stage compact 会清 arena,
事件要独立于 stage 生命周期。Host 持有两块 memfd:fd 3 = StageContext,
fd 4 = 事件环段,fd 5 = eventfd。
- 无锁数据结构:内核 WritePush 追加写 slot,子进程 EvtConsumer 消费。
writeSeq 原子递增(Bus.Publish 并发调用),readSeq 每订阅者独立。
- eventfd 通知:Linux 用 unix.Eventfd(计数合并,1000 token 只唤醒几次),
macOS 用 os.Pipe(阻塞模式走 netpoller,只 park goroutine,实验 1 验证
200 等待者仅 +1 OS 线程)。两者行为一致:Read 阻塞直到有新事件。
- 溢出语义:落后超 cap 时跳到最新,丢弃计数记入 dropped(消费者知道丢了)。
不静默覆盖最旧(写端直接覆盖 slot,读端靠 seq 判断跳过)。
- 事件类型编码:pubsdk.EventType 字符串 ↔ uint32 位索引(编译时映射表),
typeMask 位掩码过滤(1<<idx)。
Host 改动:
- NewHost 同时创建事件环段和 eventfd(惰创建,一次分配)。
- Host 持有 evtSubscriber 接口(EvtRingSubscriber),由 Registry 注入
EventRing 实现——proc 包不依赖 internal/plugin(避免循环依赖)。
corehandler 改动:
- events.subscribe(原 case 23):子进程传事件类型列表,coreHandler
通过 evtRing 接口调用 EvtRingSubscribe,注册到 Bus 上。
事件经 EventRing 写入环后由子进程 mmap 读取。
- events.unsubscribe(原 case 24):当前由内核统一清理(子进程 Stop 时)。
Registry 改动:
- ensureProcHost 在创建 Host 后同时创建 EventRing(Bus → EvtRing → eventfd),
并通过 Host.SetEvtSubscriber 注入给 coreHandler。
测试 3 项:
- BasicWriteAndConsume:Host 创建 → EventRing 写入 → 消费者读到
- OverflowStillDelivers:写入超过 cap 后消费者仍能读到最新事件
- TypeMaskFiltering:typeMask 只订阅 tool_call,agent_output 被过滤
验证:go build ./... 通过;go test -race ./internal/plugin/... 全绿;
既有事件环测试 3/3 通过;proc 包测试未受影响。
Ref: docs/zh/架构迁移评估.md §3.6、docs/zh/plugin-migration-plan.md Part 5
|
2026-09-02 16:48:27 +08:00 |
|
|
|
4f14d0947f
|
docs: 更新迁移计划进度(Part 2/3/4 完成标记)
Part 2(子进程通道)、Part 3(plugindev .bin 构建)、Part 4(RunStage 接线)
标记为已完成,下一步转 Part 5 通知面。
记录两处与原计划的偏差及原因:
- Part 3 模板落地方式从 templates.go 的 raw string 换成真实 .go 源文件 +
//go:embed —— 900+ 行代码塞在字符串里写错只能等生成插件时才炸。
- Part 2 最初每插件一块共享段,等于副本模型换壳,已改为全部插件共享同一 memfd。
另记 lifecycle.autoRestart 缺口:公开 SDK 的 SetAutoRestart 是纯 setter
无 hook,隔着进程边界内核读不到,需模板在 Start 返回后显式上报。
|
2026-09-02 13:12:08 +08:00 |
|
|
|
11c1bbcebb
|
plugin: 子进程通道接通 registry(proc 通道端到端可运行)
Part 3 收尾。tryLoadProc 从桩位变成真实加载路径,plugin.bin 插件现在
经 registry 完整跑起来:spawn → 握手(共享段 fd 3)→ init/start →
反向注册 → 工具调用 → stage 共享内存读改写。
registry 侧:
- Registry 持有 procHost(惰性创建,**全部 .bin 插件共用一块段**)。
每插件一段会让「内核 ctx → 段 → 插件改 → 回读 ctx」在多插件下退化成
副本模型,lost update 原样复现(§8.4 实测 35.8~36.8%)。
- tryDynamic 分派到 Registry.loadProc;tryLoadProc 退为纯静态校验
(构造需要 Host,只有 Registry 有)。
- StopAll 在锁外释放共享段:插件还持有映射时拆段,它们下一次访问就是
SIGBUS;且持锁调用会与 onProcCrash 回调产生锁序风险。
- onProcCrash 把子进程退出转成 EventSystem 事件,不在回调里直接重载
(重载需 registry 锁,而回调可能来自持锁路径的 goroutine)。
proc_core.go —— 权限梯度的类型系统落点(§3.8):
- procCore 用**命名字段**持有 *isdk.PluginSDK,不是嵌入。嵌入会提升全部
方法,外部插件就能经类型断言拿到 Supervisor/Tracker/Adapter/Indexer/
Status/Selftest。命名字段下只有显式写出的方法存在——权限梯度从
「C ABI 表达能力的意外产物」变成显式声明并强制的策略。
- 能力访问器把内部超集接口收窄到公开面(isdk.KnowledgeAPI 内嵌
pubsdk.KnowledgeAPI 再加 Stats/Remove,isdk.MemoryAPI 加 GraphData,
isdk.LLMAPI 加 Chat/ReloadFromConfig);nil 保护避免类型化 nil 让
corehandler 的判空失效。
- procPluginAdapter 转接 Start(*isdk.PluginSDK) → Start(proc.CoreSDK),
Close 对 closeDynamic 可见故重载能真 kill 子进程(对比 dlclose 对
Go c-shared 是 no-op,§1.1)。
共享段分配按平台拆分(原先 host.go 直接调 unix.MemfdCreate,darwin/windows
交叉编译失败):Linux memfd;macOS 立即 unlink 的临时文件(无 memfd_create,
但语义一致:无残留、fd 可经 ExtraFiles 传递、子进程 mmap 同一 inode);
其余平台明确报错而非静默降级成「无共享段」——那会让 stage 静默失去数据面。
测试 +13 项:
- e2e_template_test.go 用**真实 plugindev 模板**(而非 testdata 手写假插件)
编译插件跑全链路,验证「模板 ↔ 内核」协议/布局真的对齐,不只是内核自己
跟自己对齐。含 lifecycle.autoRestart 上报、工具调用、stage 读改写、
FinalText 回传(C ABI 下 after_toolcall 看不到此字段,§8.3 10→16)、
只读插件不覆盖改写插件。
- proc_load_test.go 验证 Host 唯一性/惰性、chmod +x 错误提示、
Close 可见性,以及 procCore 不暴露内核内部机制的断言。
验证:go build ./... 通过;go test -race ./internal/plugin/... 全绿;
全仓 go test 无新增失败;git diff third_party/homeagent-sdk/sdk/ 为空。
既有告警 cabi/loader.go:156 unsafe.Pointer 非本次引入。
Ref: docs/zh/架构迁移评估.md §3.3/§3.4/§3.8、docs/zh/plugin-migration-plan.md Part 3
|
2026-09-02 13:03:57 +08:00 |
|
|
|
82dcc86173
|
feat(proc): Plugin 加载器 + 51 method handler + RunStage 接共享段(Part 2 完成 / Part 4 闭环)
corehandler.go —— cabi/loader.go 51 个 case 体的整块平移(§3.2):
- 参数从「s1/s2/s3 + i1/i2 五个固定槽」改为结构化 JSON,语义不变
- CoreSDK 接口刻意只含外部插件应得能力:无 Selftest/Supervisor/Tracker/
Status/Adapter/Config/Tool/Indexer/OutputChan/Publish
→ 权限梯度从「C ABI 表达能力的意外产物」变成「显式声明并强制的策略」(§3.8)
- 事件订阅(case 23/24)与 SetToolBlocks 明确返回未实现,不再像 C ABI 那样静默成功
(静默成功后收不到事件比报错更难排查)
- ToolDef.Cleaner / ChannelDef.Cleaner 是函数,跨进程置 nil(§3.5 回调型资源)
host.go —— 共享段所有权中心:
- ❗ 全部子进程插件共享**同一块 memfd**。若每插件一段,
「内核 ctx → 段 → 插件改 → 回读 ctx」在多插件下退化成副本模型,
最后回读者覆盖前者,§8.4 的 35.8~36.8% lost update 原样复现
- stageMu 串行化整次 stage 对段的独占(内核可能并发触发 RunStage)
- 首个进入者写入段,最后离开者回读 + Compact(此时无插件持锁,满足 §3.3 前提)
stage.go —— RunStage 接线(风险 3.4 落点):
- 并发扇出保留(§0.2 第 1 条:并发扇出是原始设计,不是缺陷)
- 插件失败时 ForceReleaseLock,避免后续插件死锁(实验 9,无需 robust mutex)
plugin.go —— registry 可加载的插件实体:
- Start: spawn(fd 3 传共享段)→ 握手 → plugin.init → plugin.start
- Close: **真 kill + wait**,对比 cabi 的 Close 只做 dlclose 而后者是 no-op(§1.1)
- invokeOutput **同步等真实结果**,失败上报 error —— §9.4 根治
验证(34 项测试全绿,含 -race,全部用真实子进程):
- 单插件 stage 读改写经共享段回到内核 StageContext
- sanitizer(改写) + weather(只读) 并发:清洗结果不被覆盖(现网场景)
- **5 个独立进程并发 append 同一 FinalText:5 个标记全部保留,零丢失零撕裂**
(实验 8 在真实 RPC + 真实 RunStage 下的复刻)
- 工具注册可调用 / 输出通道真实失败上报 / start 期间反向调用
- 未知 method 与未实现能力被拒绝 / stage 外加锁被拒绝
接口冻结: git diff third_party/homeagent-sdk/sdk/ 为空
|
2026-09-02 11:34:33 +08:00 |
|
|
|
d62430a71b
|
feat(proc): 子进程通道 —— RPC 协议 + 进程管理(Part 2 核心)
协议面(protocol.go,§3.2 method id 平移为 method 名):
- NDJSON 帧,双向复用同一对 stdio;ID>0 需应答,ID==0 为通知(post-and-forget)
- 51 个 C ABI method id 全部平移为可读 method 名并标注原编号对照
编号本身扔掉——加能力不用改两边常量表,不再有 47 夹在 7 和 8 之间的痕迹
- case 25(CORE_FREE_STRING) 无对应 method:进程模型下各自 GC,概念消失
- case 23/24(事件订阅) 与 io.setToolBlocks 今日均为空实现「给不了」,
子进程下首次真正可给(§3.8 能力对齐)
- 新增 stage.lock/stage.unlock(C ABI 下不存在跨进程锁概念)
- StageInvokeParams 不含 StageContext 数据本身——数据在共享段,只带 stage 名 + seq
进程面(process.go,§2.3 保留现有生命周期机制):
- Spawn: 启动 + 握手(协议版本不匹配显式拒绝,不半兼容运行)
- readLoop: NDJSON 分派应答/插件反向请求,1MB 单帧上限(大 payload 走 arena)
- CallContext: ctx 取消时立即返回**且清理 pending 条目**
对比 cgo:超时只让调用方返回,goroutine 永久卡在 C 调用里(现网泄漏 26 次)
- Notify: ID=0 不占 pending 表,满足约束 B(流式逐 token 发布不得等待消费者)
- markExited: EOF/退出 → 唤醒全部在途调用 → onExit 回调
这是「把 panic 捕获换成进程退出检测」的落点,plugin_health 逻辑完全复用
- Stop: plugin.stop → 宽限期 → 超时 Kill;Kill 后 OS 回收全部资源,零泄漏
- serveRequest 带 panic 隔离:内核 handler panic 不带崩 readLoop
验证(10 项,真实子进程而非 mock,含 -race):
- 握手/工具调用/错误上报(插件失败调用方收到 error,非假成功)
- 插件反向调用内核(tool.register + settings.get 双向往返)
- **崩溃隔离**:插件 panic → 子进程 exit 2,内核存活、收到 onExit、在途调用不挂死
- 优雅停止 / **Kill 卡死插件**(ctx 超时返回 + pending 清零 + 资源回收)
- 通知不等应答(100 条 < 1s)/ 50 并发调用应答不串 / 协议版本不匹配拒绝
接口冻结: git diff third_party/homeagent-sdk/sdk/ 为空
|
2026-09-02 10:59:59 +08:00 |
|
|
|
bfa95ba320
|
docs(plan): Part 1 完成 + Part 4 核心完成标记,0.3/0.4 标记跳过
- Part 0.3/0.4 标记 ⏭️ 跳过并记录理由(子进程模型下问题整体消失,不给待删代码打补丁)
- Part 1 加载分派骨架 ✅ 完成(修改/审查/验证三步逐条勾选)
- Part 4 共享内存数据面 ✅ 核心完成(段/编解码/锁仲裁,RunStage 接线待 Part 2)
- 目录加进度快照
|
2026-09-02 10:43:40 +08:00 |
|
|
|
610e9d0bbb
|
feat(plugin): entry 双通道分派 + 共享内存 stage 数据面(Part 1 + Part 4 核心)
Part 1 加载分派骨架(迁移可逐插件推进、随时回退的前提):
- dynamic.go: 新增 binEntry/skillEntry 常量 + entryKind 枚举 + classifyEntry/detectEntryKind
manifest entry 优先级最高(改回 plugin.so 即回退 cabi);无 manifest 时按目录探测,.bin 优先
- registry.go: tryDynamic 按 entry 分派 proc/cabi 双通道;
entry 声明 .bin 但二进制缺失时报明确错误,不静默回退(否则'已迁移插件跑回旧通道'极难排查)
- registry.go: pluginEntryHash 候选顺序与 detectEntryKind 对齐(.bin 优先),
否则增量重载会用错文件算 hash
- dynamic_proc_{unix,windows}.go: tryLoadProc 桩位(权限/类型校验已实现,进程管理属 Part 2)
Part 4 共享内存数据面(迁移评估 §3.3/§3.4/§3.7,最关键一环):
- proc/shm.go: 段布局(Header + ShmStageCtx 描述符数组 + append-only arena)
相对偏移设计——各进程 mmap 到不同虚拟地址仍能正确解引用
arena 用尽显式报错而非静默截断(§4.4 风险登记);Compact() 回收 append-only 垃圾
- proc/shmcodec.go: StageContext 16 字段跨进程编解码
字段级描述符消除 lost update:只改 FinalText 的插件不触碰 ToolResults 描述符
WriteDirty 只写脏字段——只读插件零写入,不可能覆盖他人改写
Snapshot 存序列化字符串(切片共享底层数组的坑,C ABI 侧修 11.3 时已踩过)
Extra 4 键提升为具名字段;Response 用标志位表达 nil vs 空串
- proc/lock.go: 锁仲裁回归内核(§3.7 已裁定,零 cgo)
ForceRelease 实现实验 9 的崩溃自愈——排除 robust pthread_mutex 必要性
重复加锁显式拒绝(否则死锁 30s);等待超时有补偿 goroutine 防锁泄漏
验证:
- proc 包 16 项测试全绿(含 -race):全字段往返/只读零写回/原地改切片识别/
现网 sanitizer+weather 场景/5插件×40轮并发零丢失/arena 耗尽报错/压实不破坏字段/
锁互斥·串扰拒绝·崩溃自愈·临界区串行化
- entry 分派 9 项测试全绿;go build ./... exit 0;接口冻结 git diff sdk/ 为空
|
2026-09-02 10:41:18 +08:00 |
|
|
|
fe2fdc9692
|
docs: 固化当前分支对齐记录(update→feature/plugin-proc-migration)
|
2026-08-31 12:37:43 +08:00 |
|
|
|
69a138c1af
|
docs: Git 分支管理规范(main 长命 + feature/release/hotfix cherry-pick 流程)
- main 唯一长命、永远可部署;现网永远部署 release tag 构建
- feature/xxx 从 main 开出合回;release/vX.Y.Z 切出打 tag 构建
- hotfix 提交 release 分支 + 版本号分离提交,只 cherry-pick 修复回 main
- 明确'不需合并 release 回 main'(hotfix 已逐个 pick 回,避免冲突)
- 两仓(TrueAgent + homeagent-sdk)同用本规范
|
2026-08-31 12:36:44 +08:00 |
|
|
|
2e2602f437
|
docs(plan): Part 0.1/0.2 勾选 + 迁移计划进度标记
- plan.md 11.1 (3 checkbox)、11.3 (3 checkbox) 全部勾选
- plugin-migration-plan.md: 0.1/0.2 标记 ✅ 完成(含踩坑记录与待部署项)
|
2026-08-31 12:33:24 +08:00 |
|
|
|
9bb9cb3b1a
|
fix(cabi): applyStageResult 支持 diff 回传(plan 11.3 配套)
外部插件 bridge 模板改为只回传变更字段后(SDK 仓 5648519),内核侧配套:
- applyStageResult 的 tool_calls/tool_results 去掉 len(v)>0 拦截——改为键存在即应用,
使插件「清空全部工具调用」的显式回传 [] 能被表达(旧插件仅 len>0 才带键,不误清空)
- 逐字段应用,未回传的键保持原值(diff 语义:只改变更字段,不覆盖他人改写)
- output_test.go 新增 TestApplyStageResult_ClearedSlicesAreApplied / _OnlyPresentKeysApplied
验证: go build exit 0; go test ./internal/plugin/... ./internal/agent/... 全绿
|
2026-08-31 12:30:04 +08:00 |
|
|
|
b74ee15321
|
fix(cabi): output_send 等待真实发送结果,消除假成功(plan 11.1 / Part 0.1)
根因:CORE_REGISTER_OUTPUT_CH handler 无条件返回 {status:queued}+err=nil,
模型永远收到「已发送」,实际失败(如 meta 缺 user_id)只写日志,模型无法感知不会重试。
现网近 7 天成功 44 次、失败 2 次全部谎报成功。
改动:
- loader.go: 新增 awaitOutputResult(+可注入版 awaitOutputResultWith)+ outputSendTimeout=10s
goroutine 执行 cgo 发送 + 带超时 channel 等结果 → sent / error / unconfirmed 三态
handler 由 executeOutputSendTool 从 Go 侧调起,非 cgo 栈,不构成 cgo 嵌套
- output.go: executeOutputSendTool 识别 unconfirmed|queued,回报「发送结果未确认」而非「已发送」
- output_test.go: Success/Failure/Timeout 三用例
验证: go build exit 0; go test ./internal/plugin/... ./internal/agent/... 全绿
接口冻结: git diff third_party/homeagent-sdk/sdk/ 为空
|
2026-08-31 12:16:39 +08:00 |
|
|
|
8a1a7df406
|
docs(plugin-arch): 外部插件多进程化适配计划(修改→审查→验证三步微循环)
7 个 Part,每 Part 一个微循环:
- Part 0 脆弱基线先行(11.1/11.3/11.6,现网止血,不依赖迁移)
- Part 1 加载分派骨架(entry 双通道共存,可回退前提)
- Part 2 子进程通道原型(spawn/JSON-RPC/procPlugin,参考 sidecar.go)
- Part 3 plugindev 工具链改造(.bin 产物,SDK 仓)
- Part 4 共享内存数据面(StageContext 并发改写,最高风险)
- Part 5 通知面(EvtRing + eventfd,post-and-forget)
- Part 6 迁移收尾(17 插件 + 删 cabi + 权限显式化)
每 Part 含修改对象/审查要点/验证标准 + 13 项最终验收 + 风险回退表
|
2026-08-31 12:01:11 +08:00 |
|
|
|
fa99f6b4eb
|
docs(plugin-arch): 外部插件接口不变矩阵(多进程化整改基线 v1)
钉死「暴露给外部插件的接口不变」约束的合同面:
- 合同面A: 公开SDK类型/接口(sdk/plugin.go等,纯Go无cgo)
- 合同面B: bridge 51个method id ↔ SDK方法映射表(RPC平移清单)
- 合同面C: StageContext 跨ABI 10字段 → 共享内存16字段(能力扩展)
- 外部插件实测触达面 ⊆ 公开SDK合同面(接口不变成立的依据)
- 迁移后新获得能力/刻意不给项/检查点
|
2026-08-31 11:52:31 +08:00 |
|
|
|
304cad0648
|
docs(plugin-arch): 归档插件架构迁移评估 + plan 第11节整改计划
- docs/zh/架构迁移评估.md: C ABI→子进程+共享内存完整迁移论证(1621行)
- docs/zh/experiments/: 18项可复跑可行性实验(架构评估的所有数字来源)
- plan.md §11: 11.1~11.9 插件架构缺陷修复清单(唯一权威编号)
- main 保持干净,本批次为 update 特性分支的整改起点
|
2026-08-31 11:45:52 +08:00 |
|
|
|
48b5c2401c
|
feat(ohos): MotionBase 统一按压动效 + 分层图标随主题切换
## 动效统一
各组件重复实现按压反馈(@State pressed + scale + animation + onTouch 四件套),
时长各写魔数导致全局手感不一致。
- 新增 components/MotionBase.ets:通用动效的"父组件"。ArkUI V1 的 @Component
struct 无法继承他人 build,改用组合表达继承——调用组件把内容经 @BuilderParam
内容插槽传入,MotionBase 在包装节点统一挂动效修饰器。
pressEnabled 默认关(纯展示容器零开销);fillWidth=false 供气泡内卡片按内容
自适应宽度;flexWeight 供等分排列按钮参与剩余空间分配。
onPress 只作按压瞬时轻量钩子,导航/提交语义仍由调用组件 onClick 负责,
避免"按下即触发"的手感偏差。
- Constants.ets 新增动效 token:ANIM_FAST(150) / ANIM_NORMAL(220) /
ANIM_ENTER(280) / ANIM_SLOW(400) / PRESS_SCALE(0.97)。
取值依据:状态切换 150-250ms(>300ms 显拖沓),大位移进出场 300-400ms
才不突兀;曲线统一 EaseOut 起步快收尾缓。
- NavRow / StatusSummaryCard / AttachmentCard / 插件卡片等公共组件接入
MotionBase,移除各自的按压四件套。组件专属动效(聊天输入框上弹、加号菜单
浮起、折叠面板展开、toast 进出场)保留在各组件内,不塞进父组件。
## 图标与启动页随主题切换
原先直接指向位图 app_icon.png,浅色底被烧进图标,深色模式下桌面与启动页跳脱。
- 改用分层图标 layered_image:foreground 为字形,background(沉淀色)在
base/ 与 dark/ 各一份,随系统主题切换。app.json5 与 module.json5 的
icon 均指向 :layered_image。
- startWindowIcon 改用透明底 start_icon.png,配合 start_window_background
的 base(#F1F3F5) / dark(#000000) 两份取值,浅深模式遮罩与图标都能对上。
## 验证
清空 entry/build 后全量重编:hvigorw assembleHap BUILD SUCCESSFUL(9.8s),
零 ArkTS 错误。提交内容已确认不含 build/ oh_modules/ .hap 与签名材料。
|
2026-08-30 17:23:40 +08:00 |
|
|
|
eba300aec2
|
chore: 同步 vendored SDK(qq v1.2.0 + plugindev bridge 修复)
third_party/homeagent-sdk 同步至 SDK 仓 61f307b:
- fix(plugindev): bridge 模板补 dispatchIO.SetToolBlocks
SDK v0.9.2 给 IOInjector 加了该方法但 C ABI bridge 模板未同步,
导致任何外部插件编译失败(missing method SetToolBlocks)
- feat(qq) v1.2.0: msg_id→get_history 7 天兜底(不缓存正文)
+ qq_list_chats / qq_mark_read 会话列表(按最新消息排序 + 未读数)
+ 中断模板补 fallback 路径与私聊 user_id
.gitignore 补 HarmonyOS 构建工具链缓存(.hvigor-home/ .npm-cache/ .ohpm/),
这些由 HVIGOR_USER_HOME/ohpm 生成,非源码。
go build ./cmd/homed/ 通过。
|
2026-08-30 17:14:07 +08:00 |
|
|
|
a3a5cd4fee
|
fix(agent): 修正系统提示词的回复投递规则,补充事实性约束
问题一:提示词说反话,导致 qq 回复大量丢失。
v2 架构曾有「回复自动回投来源通道」的能力(IOManager.routes + RegisterOutputRoute),
但 304c3ae 'remove IO route mapping' 删掉了整套路由映射。此后:
- outputCh 唯一消费者(cmd/homed/main.go)只处理 memory_candidate,其余静默丢弃,
output.go 末尾的 EmitTextTo 兜底成为死路
- ResponseCh 只剩 webui /api/v1/chat、cli、clawhubadapter 三处同步 HTTP 用途,
不再承担渠道投递;qq 走 InjectInterruptText → interruptCh,ResponseCh 恒为 nil
- emitResponse 从不调 GetDevice(),纯文本对异步通道 = 丢弃
提示词却仍写着「直接返回纯文本即可送达,无需额外工具」「output_send 不是回复的必要步骤」。
实测近 12h 6 次 qq 输入仅 1 次送达,规律是 tools 含 output_send__qq 才到,否则全丢,
且 agent 自认为已回复。现改为明确区分同步/异步通道并要求显式 output_send__。
问题二:无事实性约束,工具失败时模型编造内容。
qq_get_message 30 次调用有 9 次返回 not_found:true(NapCat 响应解析失败),
模型未如实说明,转而虚构消息正文——包括一条不存在的 message_id=1321159191
(全 journal 零命中、不在任何调用记录里)配上完全虚构的正文
「我想搭一个 Dify 工作流,想做一个人脸识别系统 demo」,用户从未说过。
虚构内容经 formatMergedTimeline 回灌【对话时序】后被当作既有事实反复复述放大
(两条消息 reasoning_content 达 180KB / 220KB)。
现补充:工具返回 not_found/空结果必须如实说明不得猜测;【对话时序】是历史事实摘要
不是当前任务;无依据的人名/需求/数字/路径直接说不知道。
注:set() 用 INSERT OR IGNORE,改此默认值只影响新部署;
本机运行实例的 config.db core.agent.system_prompt 已同步更新(改前备份)。
验证:重新部署后实测 agent 明确回答 qq 需 output_send__qq 且纯文本会被丢弃;
对 message_id=1321159191 如实报告 not_found 并声明「正文完全不知道,绝不猜」。
|
2026-08-29 14:43:23 +08:00 |
|
|
|
8510a2f2eb
|
fix(ohos): 移除硬编码后端凭据,地址规范化默认 https
- Model.ets: defaultConnection 不再内置 url/apiKey,改为空白模板
- 新增 normalizeBaseUrl:补协议(默认 https)、去尾斜杠、去误粘的 /api/v1
裸域名走 http 会被反代 302 到门户站,客户端只拿到 404,表现为"连接直接失败"
- ConnStore: 增删改与读取存量数据时统一规范化 url
- 去掉 ensureDefaultConnection,首启不再预置连接,未配置时走统一提示
|
2026-08-29 13:49:30 +08:00 |
|
|
|
46e942f0c2
|
feat(ohos): 鸿蒙端聊天历史分段懒加载 + 首次提交完整工程
原有 cmd/ohos/HomeAgent 是未入库的鸿蒙原生 ArkTS 工程,本次随改动一并入库,
保证他人 clone 后可直接编译(含 .gitignore 排除 build/oh_modules/签名材料,
提供 build-profile.json5.example 模板)。
本次功能改动(与 WebUI / GUI 三端对齐):
- /chat/history 首屏只拉最新 CHAT_PAGE_SIZE(40) 条,1.26MB → 48.5KB
- 抽出 parseHistoryPayload() 复用解析,记录 chatOffset/chatHasMore
- 新增 loadOlderChat():向上滚动触顶(yOffset<60)懒加载更早页
- 工具调用 args/result 与 reasoning_content 完整还原,不做裁剪
构建验证:hvigorw assembleHap BUILD SUCCESSFUL(7.8s,ChatPage 零告警)
|
2026-08-29 10:25:12 +08:00 |
|
|
|
3de6b0426f
|
revert(webui): 移除 chat/history 的 lean 裁剪模式
工具调用详情(args/result)与 reasoning_content 是排查问题和还原上下文的
关键信息,不应裁剪下发。瘦身只保留分页一条路径(limit/before 控制条数)。
- 删除 lean 查询参数与 leanChatMsgs()
- 删除 ChatToolCall.Truncated 字段
- 分页仍生效:limit=40 首屏 1287.9KB → 48.5KB,且 tool_calls args/result
与 reasoning_content 完整下发
|
2026-08-28 23:38:11 +08:00 |
|
|
|
a0f7c9b5eb
|
perf(webui): /chat/history 分段懒加载 + lean 瘦身模式
问题:/api/v1/chat/history 无分页返回全量 1.26MB(200条),移动端/弱网首屏很慢。
实测体积构成:tool_calls args/result 占 71.8%,reasoning_content 11.6%,content 仅 3.4%。
后端 handler.go:
- 新增查询参数 limit(1..maxChatHistory)/ before(游标)/ lean(瘦身)
- 全部可选,省略时返回全量 → 向后兼容旧客户端
- 响应加 total/offset/has_more,供前端判断能否继续向上加载
- lean=1 裁剪 tool_calls 的 args/result 并置 truncated 标记、省略 reasoning_content
- ChatToolCall 新增 Truncated 字段(前端可显示'详情需展开加载'而非误判执行失败)
前端 WebUI + GUI(两端同步):
- 首屏只拉最新 40 条(CHAT_PAGE_SIZE),1.26MB → 48.5KB(lean 26KB)
- 新增 loadOlderChat():滚动触顶(<60px)自动拉上一页,插入后按 scrollHeight 差值补偿
滚动位置避免视口跳动
- state 新增 chatOffset/chatTotal/chatHasMore
- syncChatFromHistory 改用重叠区对齐(分段后不能再用长度比较判断新增),
找不到重叠点安全退化为全量刷新最新页
实测:无参数 1287.9KB / limit=40 48.5KB / limit=40&lean=1 26.0KB;
before 游标翻页、limit 越界/非法值、before=0 等边界均正确
|
2026-08-28 23:21:13 +08:00 |
|
|
|
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 |
|