diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index a6a7cb1..2ac7adb 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -806,7 +806,9 @@ func main() { needsCognition = false } researchBacked := false + researchAttempted := false if hasChatRoute(routePlan, chatRouteResearch) && semantic.Interpretation.Clarification != interaction.ClarificationRequired && shouldRunGeneralResearch(semantic.InformationRequestKind) { + researchAttempted = true if planID, idErr := ids.New(); idErr == nil { parts := interactiondomain.IndependentRequestParts(effectiveTaskMessage) replies := make([]string, 0, len(parts)) @@ -976,7 +978,7 @@ func main() { stableSemanticReply := mediaAttempted || identityReply != "" || memoryInstructionReply != "" || creativeReply != "" || (creativeFallbackReply != "" && !cognitiveAvailable) || semantic.InformationRequestKind != "" || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection if reviewErr != nil { log.Printf("cognitive response review unavailable: %v", reviewErr) - } else if !stableSemanticReply && (!researchBacked || reviewPreservesResearchEvidence(reviewedReply, planningReply)) { + } else if reviewMayReplacePlanningReply(stableSemanticReply, researchAttempted, researchBacked, reviewedReply, planningReply) { planningReply = reviewedReply } else if reviewedReply != planningReply { log.Printf("cognitive response review proposed stable semantic rewrite; preserving semantic reply") @@ -1872,6 +1874,19 @@ func reviewPreservesResearchEvidence(reviewed, draft string) bool { return true } +// reviewMayReplacePlanningReply keeps ownership of a concrete capability +// result with that capability. A review may improve source-backed research, +// but must not turn an honest no-result into an invented clarification. +func reviewMayReplacePlanningReply(stableSemanticReply, researchAttempted, researchBacked bool, reviewed, draft string) bool { + if stableSemanticReply { + return false + } + if !researchAttempted { + return true + } + return researchBacked && reviewPreservesResearchEvidence(reviewed, draft) +} + func humanMemoryInventory(history []interaction.HumanUtterance, assistantName string) string { rows := make([]string, 0, 4) if assistantName != "" { diff --git a/cmd/core-runtime/main_test.go b/cmd/core-runtime/main_test.go index 508efb7..1daa31f 100644 --- a/cmd/core-runtime/main_test.go +++ b/cmd/core-runtime/main_test.go @@ -46,6 +46,21 @@ func TestNearestFlightReplyFiltersUnrelatedSearchSnippets(t *testing.T) { } } +func TestResponseReviewPreservesAttemptedResearchOutcome(t *testing.T) { + if reviewMayReplacePlanningReply(false, true, false, "Уточните аэропорт.", "Пока не смог получить проверяемый ответ.") { + t.Fatal("review replaced an honest unbacked research outcome") + } + if !reviewMayReplacePlanningReply(false, true, true, "Расписание. Источник: https://example.test", "Расписание. Источник: https://example.test") { + t.Fatal("review should be able to improve a source-backed research outcome") + } + if reviewMayReplacePlanningReply(false, true, true, "Расписание без ссылки", "Расписание. Источник: https://example.test") { + t.Fatal("review dropped the research evidence") + } + if !reviewMayReplacePlanningReply(false, false, false, "Нормальный ответ", "Черновик") { + t.Fatal("ordinary conversation review was unexpectedly blocked") + } +} + func TestBearerTokenRequiresBearerScheme(t *testing.T) { request := httptest.NewRequest("GET", "/", nil) request.Header.Set("Authorization", "Basic value")