diff --git a/intercept/intercept.go b/intercept/intercept.go index 21a376b..629811d 100644 --- a/intercept/intercept.go +++ b/intercept/intercept.go @@ -316,6 +316,7 @@ const ( // Judge default values. const ( defaultJudgeTimeoutSecs = 15 + defaultJudgeRetryCount = 3 defaultJudgeFailAction = "ask" defaultJudgeAskTimeoutSecs = 300 defaultJudgeAskTimeoutAction = "deny" @@ -346,7 +347,7 @@ func (i *Interceptor) judgeConfig() JudgeConfig { Enabled: i.db.GetBool(settingJudgeEnabled, false), ProfileID: int64(i.getSettingInt(settingJudgeProfileID, 0)), TimeoutSeconds: i.getSettingInt(settingJudgeTimeoutSecs, defaultJudgeTimeoutSecs), - RetryCount: min(max(i.getSettingInt(settingJudgeRetryCount, 0), 0), MaxJudgeRetryCount), + RetryCount: min(max(i.getSettingInt(settingJudgeRetryCount, defaultJudgeRetryCount), 0), MaxJudgeRetryCount), RetryIntervalMS: min(max(i.getSettingInt(settingJudgeRetryIntervalMS, defaultJudgeRetryIntervalMS), 0), MaxJudgeRetryIntervalMS), FailAction: i.getSettingChoice(settingJudgeFailAction, defaultJudgeFailAction, "allow", "ask", "deny"), AskTimeoutSeconds: i.getSettingInt(settingJudgeAskTimeoutSecs, defaultJudgeAskTimeoutSecs), diff --git a/intercept/judge_retry_test.go b/intercept/judge_retry_test.go index b71c715..6e0cdc5 100644 --- a/intercept/judge_retry_test.go +++ b/intercept/judge_retry_test.go @@ -59,10 +59,11 @@ func TestJudgeRetryConfigDefaultsAndBounds(t *testing.T) { settings map[string]string count, interval int }{ - {"legacy", nil, 0, 1000}, - {"configured", map[string]string{settingJudgeRetryCount: "3", settingJudgeRetryIntervalMS: "25"}, 3, 25}, + {"default", nil, 3, 1000}, + {"configured", map[string]string{settingJudgeRetryCount: "2", settingJudgeRetryIntervalMS: "25"}, 2, 25}, + {"disabled", map[string]string{settingJudgeRetryCount: "0"}, 0, 1000}, {"immediate", map[string]string{settingJudgeRetryCount: "1", settingJudgeRetryIntervalMS: "0"}, 1, 0}, - {"malformed", map[string]string{settingJudgeRetryCount: "bad", settingJudgeRetryIntervalMS: "bad"}, 0, 1000}, + {"malformed", map[string]string{settingJudgeRetryCount: "bad", settingJudgeRetryIntervalMS: "bad"}, 3, 1000}, {"negative", map[string]string{settingJudgeRetryCount: "-1", settingJudgeRetryIntervalMS: "-1"}, 0, 0}, {"excessive", map[string]string{settingJudgeRetryCount: "999", settingJudgeRetryIntervalMS: "999999999"}, 20, 3600000}, } { @@ -149,16 +150,33 @@ func TestJudgeRetriesInvalidVerdict(t *testing.T) { } } -func TestJudgeLegacyConfigDoesNotRetry(t *testing.T) { - i := retryTestInterceptor(t, map[string]string{settingJudgeEnabled: "true", settingJudgeFailAction: "deny"}) - calls := 0 - i.SetReviewer(func(context.Context, int64, string, ReviewInput) (Decision, error) { - calls++ - return Decision{}, errors.New("failed") - }) - got, _ := i.Judge(t.Context(), "Bash", json.RawMessage(`{"command":"echo ok"}`)) - if calls != 1 || got.Action != "deny" || !got.ModelFallback { - t.Fatalf("calls=%d out=%+v", calls, got) +func TestJudgeDefaultRetriesAndExplicitDisable(t *testing.T) { + for _, tc := range []struct { + name string + settings map[string]string + wantCalls int + }{ + {"default", nil, 4}, + {"disabled", map[string]string{settingJudgeRetryCount: "0"}, 1}, + } { + t.Run(tc.name, func(t *testing.T) { + settings := map[string]string{ + settingJudgeEnabled: "true", settingJudgeFailAction: "deny", settingJudgeRetryIntervalMS: "0", + } + for key, value := range tc.settings { + settings[key] = value + } + i := retryTestInterceptor(t, settings) + calls := 0 + i.SetReviewer(func(context.Context, int64, string, ReviewInput) (Decision, error) { + calls++ + return Decision{}, errors.New("failed") + }) + got, _ := i.Judge(t.Context(), "Bash", json.RawMessage(`{"command":"echo ok"}`)) + if calls != tc.wantCalls || got.Action != "deny" || !got.ModelFallback { + t.Fatalf("calls=%d out=%+v", calls, got) + } + }) } } diff --git a/web/src/app/(main)/system/intercept/page.tsx b/web/src/app/(main)/system/intercept/page.tsx index 90eab2f..39b54b6 100644 --- a/web/src/app/(main)/system/intercept/page.tsx +++ b/web/src/app/(main)/system/intercept/page.tsx @@ -201,7 +201,7 @@ const defaultJudge = (): JudgeConfig => ({ profile_id: 0, prompt: "", timeout_seconds: 15, - retry_count: 0, + retry_count: 3, retry_interval_ms: 1000, fail_action: "ask", ask_timeout_seconds: 300, diff --git a/web/src/lib/mock/handler.ts b/web/src/lib/mock/handler.ts index 83f27ac..2f15a69 100644 --- a/web/src/lib/mock/handler.ts +++ b/web/src/lib/mock/handler.ts @@ -2512,7 +2512,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re profile_id: 0, prompt: "", timeout_seconds: 15, - retry_count: 0, + retry_count: 3, retry_interval_ms: 1000, fail_action: "ask", ask_timeout_seconds: 300,