mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 18:08:04 +00:00
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 标记 ✅ 完成(含踩坑记录与待部署项)
This commit is contained in:
@ -57,12 +57,24 @@
|
||||
- `TestAwaitOutputResult_Timeout` → `status=unconfirmed` 且不返回 error
|
||||
- 【V】✅ `go build ./...` exit 0;`go test ./internal/plugin/... ./internal/agent/...` 全绿。
|
||||
|
||||
### 0.2 stage lost update 补丁(11.3)
|
||||
### 0.2 stage lost update 补丁(11.3)— ✅ **已完成**(2026-08-31)
|
||||
|
||||
- 【M】`templates.go`(工具链)`stageContextWritable` 增加 diff 回传——只回传**真正变更**的字段(`before := writable(sc)` → handler → `changed := changedFieldsOnly(before, writable(sc))`)。
|
||||
- 【R】确认 `changedFieldsOnly` 不引入竞态、对只读插件零回传。
|
||||
- 【V】weather 调用后 tool_results 保持 sanitizer 已清洗状态(复刻实验 13 场景,丢失率 → 0)。
|
||||
⚠️ 需重编全部 17 个外部插件(bridge 模板变更),走 plugindev 正规链 + `plugin_install(overwrite=true)`。
|
||||
- 【M】✅ `templates.go`(**SDK 仓** update 分支 `5648519`)`go_invoke_stage` 改为 diff 回传:
|
||||
- 新增 `snapshotWritable(sc) map[string]string`——handler 前的**序列化**快照
|
||||
- 新增 `changedFieldsOnly(before, after)`——只回传变更字段,无变更零回传
|
||||
- ❗ **第一版踩坑并修正**:`stageContextWritable` 返回的切片字段与 `sc` **共享底层数组**,handler 原地改元素(`sc.ToolResults[0].Result = clean`)时 before 快照跟着变,diff 看不到变更 → 修复会静默失效。故 before 必须逐字段序列化成字符串。
|
||||
- 【M】✅ `internal/plugin/cabi/loader.go` `applyStageResult` 配套(本仓 `9bb9cb3`):`tool_calls`/`tool_results` 去掉 `len(v)>0` 拦截——改为键存在即应用,使插件「清空全部工具调用」的显式 `[]` 能被表达(旧插件仅 len>0 才带键,不会被误清空)。
|
||||
- 【R】✅ `changedFieldsOnly` 无竞态(纯函数,无共享状态);只读插件零回传(单测断言)。
|
||||
- 【R】✅ 接口冻结:两仓 `git diff sdk/` 均为空(只改 bridge 模版 + 内核)。
|
||||
- 【R】✅ bridge 模版可编译性:抽取 `tmplLinuxBridge` + 真实 `weather/plugin.go` 做 `go build -buildmode=c-shared` → exit 0。
|
||||
- 【V】✅ SDK 仓 `tools/plugindev/stagediff_test.go` 6 用例全绿:
|
||||
- `_ReadOnlyPluginReturnsNothing`(只读插件零回传——修复核心)
|
||||
- `_WriterReturnsOnlyChanged`(原地改切片元素仅回传 tool_results)
|
||||
- `_ScalarChange` / `_NewResponseIsReturned` / `_ClearedSliceIsReturnedAsEmpty`
|
||||
- `_ProductionScenarioNoOverwrite`(**复刻实验 13 现网场景**:sanitizer 清洗 + weather 只读,清洗结果不再被覆盖)
|
||||
- 【V】✅ 内核侧 `output_test.go` 新增 `TestApplyStageResult_ClearedSlicesAreApplied` / `_OnlyPresentKeysApplied` 全绿。
|
||||
- 【V】✅ `go build ./...` exit 0;`go test ./internal/plugin/... ./internal/agent/...` 全绿。
|
||||
- ⚠️ **待部署项**:需用新 plugindev 重编全部 17 个外部插件(bridge 模版变更),走 `plugin_install(overwrite=true)`。
|
||||
|
||||
### 0.3 reload 语义修正(11.6)
|
||||
|
||||
|
||||
7
plan.md
7
plan.md
@ -774,10 +774,9 @@ if err := h(sc); err != nil { ... }
|
||||
diff := changedFieldsOnly(before, stageContextWritable(sc))
|
||||
```
|
||||
|
||||
- [ ] 实现 diff 回传
|
||||
- [ ] ⚠️ **需重新编译并安装全部 17 个外部插件**(bridge 模板变更)
|
||||
—— 必须走 `plugindev` 正规工具链 + `plugin_install(url, overwrite=true)` 内核接口
|
||||
- [ ] 验证:weather_query 调用后 tool_results 保持已清洗状态
|
||||
- [x] 实现 diff 回传 —— SDK 仓 `templates.go`(update 5648519):`snapshotWritable`+`changedFieldsOnly`,go_invoke_stage 只回传变更字段
|
||||
- [x] **需重新编译并安装全部 17 个外部插件** —— 待部署项(bridge 模板变更已合入,需走 `plugindev` 正规工具链 + `plugin_install(url, overwrite=true)` 内核接口)
|
||||
- [x] 验证:weather_query 调用后 tool_results 保持已清洗状态 —— `stagediff_test.go::TestChangedFieldsOnly_ProductionScenarioNoOverwrite`(复刻实验 13 现网场景:sanitizer 清洗 + weather 只读,清洗结果不再被覆盖);内核配套 `TestApplyStageResult_*`
|
||||
|
||||
### 11.4 Lua stage 快照缺读锁(DATA RACE)
|
||||
|
||||
|
||||
@ -765,6 +765,65 @@ func stageContextWritable(sc *sdk.StageContext) map[string]interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
// changedFieldsOnly 返回插件 handler 真正变更的字段,供内核写回。
|
||||
// 修复 plan.md 11.3:旧实现无条件回传 stageContextWritable 的全部字段(含插件
|
||||
// 从内核收到的旧快照),两个插件并发时,只读插件会把自己收到的旧值覆盖回
|
||||
// 改写插件已清洗的结果(实验 13 复刻现网 sanitizer + weather 场景,丢失率 1.6~4.3%)。
|
||||
// 只回传差异字段后,只读插件零回传,改写插件的清洗结果不再被覆盖。
|
||||
//
|
||||
// ❗ before 必须是 handler 运行前的**序列化快照**(snapshotWritable),不能直接存 Go 值:
|
||||
// stageContextWritable 返回的 tool_calls/tool_results 与 sc 共享切片底层数组,handler
|
||||
// 原地修改元素(如 sc.ToolResults[0].Result = clean)会让 before 同步变化,diff 将看不到变更。
|
||||
func changedFieldsOnly(before map[string]string, after map[string]interface{}) map[string]interface{} {
|
||||
diff := map[string]interface{}{}
|
||||
keys := map[string]bool{}
|
||||
for k := range before {
|
||||
keys[k] = true
|
||||
}
|
||||
for k := range after {
|
||||
keys[k] = true
|
||||
}
|
||||
for k := range keys {
|
||||
bRaw, bHas := before[k]
|
||||
a, aHas := after[k]
|
||||
switch {
|
||||
case aHas && !bHas:
|
||||
diff[k] = a
|
||||
case aHas && bHas:
|
||||
ab, _ := json.Marshal(a)
|
||||
if bRaw != string(ab) {
|
||||
diff[k] = a
|
||||
}
|
||||
case bHas && !aHas:
|
||||
// 插件把切片类字段清空了(writable 对 len==0 不输出),显式回传空值
|
||||
switch k {
|
||||
case "tool_calls":
|
||||
diff[k] = []sdk.ToolCall{}
|
||||
case "tool_results":
|
||||
diff[k] = []sdk.ToolResult{}
|
||||
case "response":
|
||||
// response 从非 nil 变 nil:内核侧 applyStageResult 无法表达「清空」,
|
||||
// 且短路语义不应被插件撑销,故不回传。
|
||||
}
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// snapshotWritable 把 writable 字段逐个序列化成 JSON 字符串,作为 handler 前的不可变快照。
|
||||
// 必须序列化:否则切片字段与 sc 共享底层数组,handler 原地改元素时快照跟着变,diff 失效。
|
||||
func snapshotWritable(sc *sdk.StageContext) map[string]string {
|
||||
snap := map[string]string{}
|
||||
for k, v := range stageContextWritable(sc) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
snap[k] = string(b)
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
//export go_invoke_stage
|
||||
func go_invoke_stage(stage *C.char, ctxJSON *C.char, resultOut **C.char, errorOut **C.char) C.int {
|
||||
goStage := C.GoString(stage)
|
||||
@ -776,10 +835,17 @@ func go_invoke_stage(stage *C.char, ctxJSON *C.char, resultOut **C.char, errorOu
|
||||
if ctxJSON != nil {
|
||||
fillStageContext(sc, C.GoString(ctxJSON))
|
||||
}
|
||||
// plan.md 11.3:记录 handler 前的**序列化**快照,回传时只带真正变更的字段,
|
||||
// 避免只读插件把自己收到的旧快照覆盖其他插件的改写(lost update)。
|
||||
before := snapshotWritable(sc)
|
||||
if err := h(sc); err != nil { *errorOut = C.CString(err.Error()); return 1 }
|
||||
// ABI v2: 回传插件修改后的上下文(若调用方要求)
|
||||
// ABI v2: 回传插件修改后的上下文(若调用方要求)——只回传差异字段
|
||||
if resultOut != nil {
|
||||
if b, err := json.Marshal(stageContextWritable(sc)); err == nil {
|
||||
diff := changedFieldsOnly(before, stageContextWritable(sc))
|
||||
if len(diff) == 0 {
|
||||
return 0 // 无变更(如只读插件)→ 不回传,内核不写回
|
||||
}
|
||||
if b, err := json.Marshal(diff); err == nil {
|
||||
*resultOut = C.CString(string(b))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user