@@ -390,13 +390,15 @@ func TestRuntimeHooksConfigItemDefaultsAndClone(t *testing.T) {
390390 ID : "warn-bash" ,
391391 Point : string (hooks .HookPointBeforeToolCall ),
392392 Handler : runtimeHookHandlerWarnOnToolCall ,
393- Params : map [string ]any {
393+ Match : map [string ]any {
394394 "tool_name" : "bash" ,
395- "tags" : []any {"warn" , "tool" },
395+ },
396+ Params : map [string ]any {
397+ "tags" : []any {"warn" , "tool" },
396398 },
397399 },
398400 },
399- }
401+ }
400402 cfg .ApplyDefaults (defaultRuntimeHooksConfig ())
401403
402404 item := cfg .Items [0 ]
@@ -489,6 +491,65 @@ func TestRuntimeHooksConfigValidateWarnOnToolCallRequiresTarget(t *testing.T) {
489491 }
490492}
491493
494+ func TestRuntimeHooksConfigValidateWarnOnToolCallAllowsMatchWithoutLegacyTargets (t * testing.T ) {
495+ t .Parallel ()
496+
497+ cfg := RuntimeHooksConfig {
498+ Enabled : boolPtr (true ),
499+ UserHooksEnabled : boolPtr (true ),
500+ DefaultTimeoutSec : 2 ,
501+ DefaultFailurePolicy : runtimeHookFailurePolicyWarnOnly ,
502+ Items : []RuntimeHookItemConfig {
503+ {
504+ ID : "warn-with-match" ,
505+ Point : string (hooks .HookPointBeforeToolCall ),
506+ Scope : runtimeHookScopeUser ,
507+ Kind : runtimeHookKindBuiltIn ,
508+ Mode : runtimeHookModeSync ,
509+ Handler : runtimeHookHandlerWarnOnToolCall ,
510+ TimeoutSec : 2 ,
511+ FailurePolicy : runtimeHookFailurePolicyWarnOnly ,
512+ Match : map [string ]any {
513+ "tool_name" : "bash" ,
514+ },
515+ },
516+ },
517+ }
518+ if err := cfg .Validate (); err != nil {
519+ t .Fatalf ("Validate() error = %v" , err )
520+ }
521+ }
522+
523+ func TestRuntimeHooksConfigValidateRejectsUnsupportedMatcherDimensionForPoint (t * testing.T ) {
524+ t .Parallel ()
525+
526+ cfg := RuntimeHooksConfig {
527+ Enabled : boolPtr (true ),
528+ UserHooksEnabled : boolPtr (true ),
529+ DefaultTimeoutSec : 2 ,
530+ DefaultFailurePolicy : runtimeHookFailurePolicyWarnOnly ,
531+ Items : []RuntimeHookItemConfig {
532+ {
533+ ID : "session-start-match" ,
534+ Point : string (hooks .HookPointSessionStart ),
535+ Scope : runtimeHookScopeUser ,
536+ Kind : runtimeHookKindBuiltIn ,
537+ Mode : runtimeHookModeSync ,
538+ Handler : runtimeHookHandlerAddContextNote ,
539+ TimeoutSec : 2 ,
540+ FailurePolicy : runtimeHookFailurePolicyWarnOnly ,
541+ Params : map [string ]any {"note" : "observe" },
542+ Match : map [string ]any {
543+ "tool_name" : "bash" ,
544+ },
545+ },
546+ },
547+ }
548+ if err := cfg .Validate (); err == nil {
549+ t .Fatal ("expected unsupported matcher dimension to fail validation" )
550+ }
551+ }
552+
492553func TestRuntimeHooksConfigEdgeBranches (t * testing.T ) {
493554 t .Parallel ()
494555
@@ -605,20 +666,19 @@ func TestRuntimeHooksConfigEdgeBranches(t *testing.T) {
605666 t .Fatal ("expected deep clone for nested map in slice" )
606667 }
607668
608- if hasWarnOnToolCallTargets (nil ) {
609- t .Fatal ("nil params should be false" )
610- }
611- if ! hasWarnOnToolCallTargets (map [string ]any {"tool_name" : "bash" }) {
612- t .Fatal ("tool_name should pass" )
613- }
614- if ! hasWarnOnToolCallTargets (map [string ]any {"tool_names" : []string {"" , "bash" }}) {
615- t .Fatal ("tool_names []string should pass" )
616- }
617- if ! hasWarnOnToolCallTargets (map [string ]any {"tool_names" : []any {"" , "bash" }}) {
618- t .Fatal ("tool_names []any should pass" )
669+
670+ matchCfg := RuntimeHookItemConfig {
671+ Match : map [string ]any {
672+ "tool_name_regex" : []any {`^bash$` },
673+ },
619674 }
620- if hasWarnOnToolCallTargets (map [string ]any {"tool_names" : "bash" }) {
621- t .Fatal ("tool_names scalar should fail" )
675+ clonedCfg := matchCfg .Clone ()
676+ clonedRegexes := clonedCfg .Match ["tool_name_regex" ].([]any )
677+ clonedRegexes [0 ] = "^filesystem$"
678+ clonedCfg .Match ["tool_name_regex" ] = clonedRegexes
679+ originalRegexes := matchCfg .Match ["tool_name_regex" ].([]any )
680+ if originalRegexes [0 ] == "^filesystem$" {
681+ t .Fatal ("expected match field to be deep-cloned" )
622682 }
623683 })
624684}
0 commit comments