feat: add TypeSafe Jev as HITL audit backend

Let audit_agent approve or reject with one System One call instead of chat JSON, while keeping the OpenAI-compatible backend as an option.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ed1s0nZandCursor committed 2026-09-22 21:15:53 +08:00
1 parent 3aa9274675
commit 38b96ec67a
30 files changed
+1372 -21

No files matched your search

+50 -1
View File
@@ -1114,7 +1114,9 @@ type AgentConfig struct {
// tool_whitelist 可在侧栏「应用」时合并写入 config.yaml 并立即生效。
// audit_agent_prompt / audit_agent_prompt_review_edit 可在人机协同页编辑并立即生效;空则使用内置默认。
type HitlConfig struct {
// AuditModel 审计 Agent 专用模型;字段留空时继承 OpenAI 主配置,便于用小模型做审批。
// AuditBackend 审计 Agent 后端:openai(兼容协议聊天模型)或 typesafe(Jev 结构化裁决)。空值视为 openai。
AuditBackend string `yaml:"audit_backend,omitempty" json:"audit_backend,omitempty"`
// AuditModel 审计 Agent 专用模型。openai 后端空字段继承主模型;typesafe 后端 api_key 必填,不继承主模型密钥。
AuditModel OpenAIConfig `yaml:"audit_model,omitempty" json:"audit_model,omitempty"`
// ToolWhitelist 全局免审批工具名(与白名单内工具不触发 HITL 审批)。
ToolWhitelist []string `yaml:"tool_whitelist,omitempty" json:"tool_whitelist,omitempty"`
@@ -1176,6 +1178,37 @@ func (h HitlConfig) RetentionDaysEffective() int {
return *h.RetentionDays
}
const (
HitlAuditBackendOpenAI = "openai"
HitlAuditBackendTypeSafe = "typesafe"
TypeSafeDefaultBaseURL = "https://api.typesafe.ai"
TypeSafeDefaultModel = "jev-latest"
)
// EffectiveAuditBackend returns openai or typesafe. Omitted or unknown values default to openai.
func (h HitlConfig) EffectiveAuditBackend() string {
switch strings.ToLower(strings.TrimSpace(h.AuditBackend)) {
case HitlAuditBackendTypeSafe, "jev", "type-safe", "typesafe-ai":
return HitlAuditBackendTypeSafe
default:
return HitlAuditBackendOpenAI
}
}
// TypeSafeConfigEffective returns TypeSafe endpoint settings. Empty base_url/model use defaults; API key is never inherited from the main OpenAI channel.
func (h HitlConfig) TypeSafeConfigEffective() (baseURL, apiKey, model string) {
baseURL = strings.TrimSpace(h.AuditModel.BaseURL)
if baseURL == "" {
baseURL = TypeSafeDefaultBaseURL
}
apiKey = strings.TrimSpace(h.AuditModel.APIKey)
model = strings.TrimSpace(h.AuditModel.Model)
if model == "" {
model = TypeSafeDefaultModel
}
return strings.TrimSuffix(baseURL, "/"), apiKey, model
}
// AuditModelEffective returns the audit-agent model config with empty fields inherited from the main model config.
func (h HitlConfig) AuditModelEffective(main OpenAIConfig) OpenAIConfig {
out := main
@@ -1291,6 +1324,22 @@ func (c HitlConfig) EffectiveAuditAgentPromptForMode(mode string) string {
return DefaultHitlAuditAgentPrompt()
}
// JevOperatorPolicy returns a custom audit-strategy prompt for TypeSafe Jev.
// Built-in default prompts stay encoded as Jev questions and are not copied into state.
func (c HitlConfig) JevOperatorPolicy(mode string) string {
effective := strings.TrimSpace(c.EffectiveAuditAgentPromptForMode(mode))
var def string
if normalizeHitlModeForPrompt(mode) == "review_edit" {
def = strings.TrimSpace(DefaultHitlAuditAgentPromptReviewEdit())
} else {
def = strings.TrimSpace(DefaultHitlAuditAgentPrompt())
}
if effective == "" || effective == def {
return ""
}
return effective
}
func normalizeHitlModeForPrompt(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case "review_edit":
+28
View File
@@ -75,6 +75,34 @@ func TestLoadIgnoresLegacyAuthPasswordField(t *testing.T) {
}
}
func TestHitlEffectiveAuditBackend(t *testing.T) {
if got := (HitlConfig{}).EffectiveAuditBackend(); got != HitlAuditBackendOpenAI {
t.Fatalf("empty backend = %q, want openai", got)
}
if got := (HitlConfig{AuditBackend: "Jev"}).EffectiveAuditBackend(); got != HitlAuditBackendTypeSafe {
t.Fatalf("jev alias = %q, want typesafe", got)
}
if got := (HitlConfig{AuditBackend: "claude"}).EffectiveAuditBackend(); got != HitlAuditBackendOpenAI {
t.Fatalf("unknown backend = %q, want openai", got)
}
}
func TestHitlTypeSafeConfigEffectiveDoesNotInheritMainKey(t *testing.T) {
gotURL, gotKey, gotModel := (HitlConfig{
AuditBackend: "typesafe",
AuditModel: OpenAIConfig{APIKey: "ts-key"},
}).TypeSafeConfigEffective()
if gotURL != TypeSafeDefaultBaseURL {
t.Fatalf("base url = %q, want default", gotURL)
}
if gotKey != "ts-key" {
t.Fatalf("api key = %q, want ts-key", gotKey)
}
if gotModel != TypeSafeDefaultModel {
t.Fatalf("model = %q, want default", gotModel)
}
}
func TestHitlAuditModelEffectiveFallsBackToMainConfig(t *testing.T) {
main := OpenAIConfig{
Provider: "openai",
+12
View File
@@ -29,3 +29,15 @@ func TestDefaultHitlAuditAgentPromptReviewEditKeepsEditedArguments(t *testing.T)
t.Fatal("review-edit prompt must require a matched rule")
}
}
func TestJevOperatorPolicySkipsDefaultPrompt(t *testing.T) {
if got := (HitlConfig{}).JevOperatorPolicy("approval"); got != "" {
t.Fatalf("empty config should not send default prompt to Jev, got %q", got)
}
if got := (HitlConfig{AuditAgentPrompt: DefaultHitlAuditAgentPrompt()}).JevOperatorPolicy("approval"); got != "" {
t.Fatalf("default prompt should not be sent to Jev, got %q", got)
}
if got := (HitlConfig{AuditAgentPrompt: "拦截所有命令执行"}).JevOperatorPolicy("approval"); got != "拦截所有命令执行" {
t.Fatalf("custom prompt=%q", got)
}
}