From 9bb9cb3b1a1b2c6eaae04b91681c9d92126fc1aa Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 31 Aug 2026 12:30:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(cabi):=20applyStageResult=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20diff=20=E5=9B=9E=E4=BC=A0=EF=BC=88plan=2011.3=20?= =?UTF-8?q?=E9=85=8D=E5=A5=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 外部插件 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/... 全绿 --- internal/plugin/cabi/loader.go | 7 +++-- internal/plugin/cabi/output_test.go | 45 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/internal/plugin/cabi/loader.go b/internal/plugin/cabi/loader.go index dd62b10..6e666d5 100644 --- a/internal/plugin/cabi/loader.go +++ b/internal/plugin/cabi/loader.go @@ -360,7 +360,10 @@ func applyStageResult(sc *sdk.StageContext, resultJSON string) { vv := v sc.Response = &vv } - if v, ok := m["tool_calls"].([]interface{}); ok && len(v) > 0 { + if v, ok := m["tool_calls"].([]interface{}); ok { + // 注意不要加 len(v)>0 条件:ABI v2 diff 回传(plan.md 11.3)下,插件拒绝全部 + // 工具调用时会显式回传 `[]`,必须能表达「清空」。旧插件(全量回传)仅在 + // len>0 时才带该键,因此不会因此变更而被误清空。 if b, err := json.Marshal(v); err == nil { var tcs []sdk.ToolCall if json.Unmarshal(b, &tcs) == nil { @@ -368,7 +371,7 @@ func applyStageResult(sc *sdk.StageContext, resultJSON string) { } } } - if v, ok := m["tool_results"].([]interface{}); ok && len(v) > 0 { + if v, ok := m["tool_results"].([]interface{}); ok { if b, err := json.Marshal(v); err == nil { var trs []sdk.ToolResult if json.Unmarshal(b, &trs) == nil { diff --git a/internal/plugin/cabi/output_test.go b/internal/plugin/cabi/output_test.go index 2385224..2c54437 100644 --- a/internal/plugin/cabi/output_test.go +++ b/internal/plugin/cabi/output_test.go @@ -5,9 +5,11 @@ import ( "strings" "testing" "time" + + sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" ) -// awaitOutputResult 的 decision 核心: +// output_send 不再假成功(plan.md 11.1):sent / error / unconfirmed 三态。 func TestAwaitOutputResult_Success(t *testing.T) { res, err := awaitOutputResultWith(0, "qq", `{"x":1}`, func(pid int32, ch, args string) error { @@ -47,3 +49,44 @@ func TestAwaitOutputResult_Timeout(t *testing.T) { t.Fatalf("expected status=unconfirmed, got %v", m["status"]) } } + +// applyStageResult 必须能表达「插件清空了 tool_calls/tool_results」—— +// ABI v2 diff 回传(plan.md 11.3)下插件拒绝全部工具调用时会显式回传 []。 +func TestApplyStageResult_ClearedSlicesAreApplied(t *testing.T) { + sc := &sdk.StageContext{ + ToolCalls: []sdk.ToolCall{{ID: "t1", Name: "cmd_run"}}, + ToolResults: []sdk.ToolResult{{CallID: "t1", Name: "cmd_run", Result: "x"}}, + } + applyStageResult(sc, `{"tool_calls":[],"tool_results":[]}`) + if len(sc.ToolCalls) != 0 { + t.Fatalf("tool_calls 应被清空,实际 %v", sc.ToolCalls) + } + if len(sc.ToolResults) != 0 { + t.Fatalf("tool_results 应被清空,实际 %v", sc.ToolResults) + } +} + +// diff 回传只带变更字段:未出现的键不得被改动(避免旧快照覆盖)。 +func TestApplyStageResult_OnlyPresentKeysApplied(t *testing.T) { + sc := &sdk.StageContext{ + RawMessage: "原始输入", + LLMText: "原始LLM", + FinalText: "原始最终", + ToolResults: []sdk.ToolResult{{CallID: "c1", Result: "已清洗"}}, + } + // 只回传 final_text 的变更 + applyStageResult(sc, `{"final_text":"新最终"}`) + + if sc.FinalText != "新最终" { + t.Fatalf("final_text 应被应用,实际 %q", sc.FinalText) + } + if sc.RawMessage != "原始输入" { + t.Errorf("raw_message 未回传却被改动: %q", sc.RawMessage) + } + if sc.LLMText != "原始LLM" { + t.Errorf("llm_text 未回传却被改动: %q", sc.LLMText) + } + if len(sc.ToolResults) != 1 || sc.ToolResults[0].Result != "已清洗" { + t.Errorf("tool_results 未回传却被改动: %v", sc.ToolResults) + } +}