diff --git a/apps/edge/internal/openai/hot_path_selector.go b/apps/edge/internal/openai/hot_path_selector.go index d0f84fff..043815f9 100644 --- a/apps/edge/internal/openai/hot_path_selector.go +++ b/apps/edge/internal/openai/hot_path_selector.go @@ -342,7 +342,7 @@ func reservedPathSourcesFromToolCall(tc normalizedToolCall) []string { } add(tc.Path) if tc.Arguments != nil { - collectReservedStrings(tc.Arguments, add) + collectReservedArgumentStrings(tc.Arguments, add) if tc.RawArgs == "" { return paths } @@ -351,7 +351,7 @@ func reservedPathSourcesFromToolCall(tc normalizedToolCall) []string { decoder.UseNumber() if decoder.Decode(&decoded) == nil && decoded != nil { if !reflect.DeepEqual(decoded, tc.Arguments) { - collectReservedStrings(decoded, add) + collectReservedArgumentStrings(decoded, add) } return paths } @@ -365,24 +365,28 @@ func reservedPathSourcesFromToolCall(tc normalizedToolCall) []string { decoder := json.NewDecoder(strings.NewReader(tc.RawArgs)) decoder.UseNumber() if decoder.Decode(&decoded) == nil { - collectReservedStrings(decoded, add) + collectReservedArgumentStrings(decoded, add) } else { add(tc.RawArgs) } return paths } -func collectReservedStrings(value any, add func(string)) { +func collectReservedArgumentStrings(value any, add func(string)) { switch typed := value.(type) { case string: add(typed) case map[string]any: - for _, item := range typed { - collectReservedStrings(item, add) + for key, item := range typed { + switch strings.ToLower(strings.TrimSpace(key)) { + case "content", "plan_content", "review_content": + continue + } + collectReservedArgumentStrings(item, add) } case []any: for _, item := range typed { - collectReservedStrings(item, add) + collectReservedArgumentStrings(item, add) } } } diff --git a/apps/edge/internal/openai/hot_path_selector_test.go b/apps/edge/internal/openai/hot_path_selector_test.go index 183dadd6..cf86c59d 100644 --- a/apps/edge/internal/openai/hot_path_selector_test.go +++ b/apps/edge/internal/openai/hot_path_selector_test.go @@ -42,6 +42,13 @@ func TestHotPathSelectorDecisionMatrix(t *testing.T) { {ID: "call_review", Name: "write_file", Arguments: map[string]any{"path": issued.ReviewPath}}, }}, }, + { + name: "ExactPairContentMayMentionReservedPaths", preset: preset, gate: validGate, wantMode: modeLight, wantReason: reasonLightExactPair, + output: normalizedStageOutput{ToolCalls: []normalizedToolCall{ + {ID: "call_plan", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath, "content": "Read " + issued.ReviewPath + " before handoff."}}, + {ID: "call_review", Name: "write_file", Arguments: map[string]any{"path": issued.ReviewPath, "content": "Template for " + issued.PlanPath}}, + }}, + }, { name: "PartialPair", preset: preset, gate: validGate, wantReason: reasonMalformedPartialPair, wantErr: true, output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_plan", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath}}}},