mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-30 21:20:12 +02:00
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:
1 parent
3aa9274675
commit
38b96ec67a
30 files changed
+1372
-21
No files matched your search
@@ -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":
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user