mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-05 23:01:06 +08:00
feat: expand node artifact operations and retention
This commit is contained in:
@@ -295,7 +295,9 @@ type GatewayConfig struct {
|
||||
}
|
||||
|
||||
type GatewayNodesConfig struct {
|
||||
P2P GatewayNodesP2PConfig `json:"p2p,omitempty"`
|
||||
P2P GatewayNodesP2PConfig `json:"p2p,omitempty"`
|
||||
Dispatch GatewayNodesDispatchConfig `json:"dispatch,omitempty"`
|
||||
Artifacts GatewayNodesArtifactsConfig `json:"artifacts,omitempty"`
|
||||
}
|
||||
|
||||
type GatewayICEConfig struct {
|
||||
@@ -311,6 +313,25 @@ type GatewayNodesP2PConfig struct {
|
||||
ICEServers []GatewayICEConfig `json:"ice_servers,omitempty"`
|
||||
}
|
||||
|
||||
type GatewayNodesDispatchConfig struct {
|
||||
PreferLocal bool `json:"prefer_local,omitempty"`
|
||||
PreferP2P bool `json:"prefer_p2p,omitempty"`
|
||||
AllowRelayFallback bool `json:"allow_relay_fallback,omitempty"`
|
||||
ActionTags map[string][]string `json:"action_tags,omitempty"`
|
||||
AgentTags map[string][]string `json:"agent_tags,omitempty"`
|
||||
AllowActions map[string][]string `json:"allow_actions,omitempty"`
|
||||
DenyActions map[string][]string `json:"deny_actions,omitempty"`
|
||||
AllowAgents map[string][]string `json:"allow_agents,omitempty"`
|
||||
DenyAgents map[string][]string `json:"deny_agents,omitempty"`
|
||||
}
|
||||
|
||||
type GatewayNodesArtifactsConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
KeepLatest int `json:"keep_latest,omitempty"`
|
||||
RetainDays int `json:"retain_days,omitempty"`
|
||||
PruneOnRead bool `json:"prune_on_read,omitempty"`
|
||||
}
|
||||
|
||||
type CronConfig struct {
|
||||
MinSleepSec int `json:"min_sleep_sec" env:"CLAWGO_CRON_MIN_SLEEP_SEC"`
|
||||
MaxSleepSec int `json:"max_sleep_sec" env:"CLAWGO_CRON_MAX_SLEEP_SEC"`
|
||||
@@ -559,6 +580,23 @@ func DefaultConfig() *Config {
|
||||
STUNServers: []string{},
|
||||
ICEServers: []GatewayICEConfig{},
|
||||
},
|
||||
Dispatch: GatewayNodesDispatchConfig{
|
||||
PreferLocal: false,
|
||||
PreferP2P: true,
|
||||
AllowRelayFallback: true,
|
||||
ActionTags: map[string][]string{},
|
||||
AgentTags: map[string][]string{},
|
||||
AllowActions: map[string][]string{},
|
||||
DenyActions: map[string][]string{},
|
||||
AllowAgents: map[string][]string{},
|
||||
DenyAgents: map[string][]string{},
|
||||
},
|
||||
Artifacts: GatewayNodesArtifactsConfig{
|
||||
Enabled: false,
|
||||
KeepLatest: 500,
|
||||
RetainDays: 7,
|
||||
PruneOnRead: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Cron: CronConfig{
|
||||
|
||||
@@ -145,6 +145,21 @@ func Validate(cfg *Config) []error {
|
||||
}
|
||||
}
|
||||
}
|
||||
errs = append(errs, validateDispatchTagMap("gateway.nodes.dispatch.action_tags", cfg.Gateway.Nodes.Dispatch.ActionTags)...)
|
||||
errs = append(errs, validateDispatchTagMap("gateway.nodes.dispatch.agent_tags", cfg.Gateway.Nodes.Dispatch.AgentTags)...)
|
||||
errs = append(errs, validateDispatchTagMap("gateway.nodes.dispatch.allow_actions", cfg.Gateway.Nodes.Dispatch.AllowActions)...)
|
||||
errs = append(errs, validateDispatchTagMap("gateway.nodes.dispatch.deny_actions", cfg.Gateway.Nodes.Dispatch.DenyActions)...)
|
||||
errs = append(errs, validateDispatchTagMap("gateway.nodes.dispatch.allow_agents", cfg.Gateway.Nodes.Dispatch.AllowAgents)...)
|
||||
errs = append(errs, validateDispatchTagMap("gateway.nodes.dispatch.deny_agents", cfg.Gateway.Nodes.Dispatch.DenyAgents)...)
|
||||
if cfg.Gateway.Nodes.Artifacts.Enabled && cfg.Gateway.Nodes.Artifacts.KeepLatest <= 0 {
|
||||
errs = append(errs, fmt.Errorf("gateway.nodes.artifacts.keep_latest must be > 0 when enabled=true"))
|
||||
}
|
||||
if cfg.Gateway.Nodes.Artifacts.KeepLatest < 0 {
|
||||
errs = append(errs, fmt.Errorf("gateway.nodes.artifacts.keep_latest must be >= 0"))
|
||||
}
|
||||
if cfg.Gateway.Nodes.Artifacts.RetainDays < 0 {
|
||||
errs = append(errs, fmt.Errorf("gateway.nodes.artifacts.retain_days must be >= 0"))
|
||||
}
|
||||
if cfg.Cron.MinSleepSec <= 0 {
|
||||
errs = append(errs, fmt.Errorf("cron.min_sleep_sec must be > 0"))
|
||||
}
|
||||
@@ -239,6 +254,21 @@ func Validate(cfg *Config) []error {
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateDispatchTagMap(prefix string, mapping map[string][]string) []error {
|
||||
if len(mapping) == 0 {
|
||||
return nil
|
||||
}
|
||||
errs := make([]error, 0)
|
||||
for key, tags := range mapping {
|
||||
if strings.TrimSpace(key) == "" {
|
||||
errs = append(errs, fmt.Errorf("%s contains empty key", prefix))
|
||||
continue
|
||||
}
|
||||
errs = append(errs, validateNonEmptyStringList(fmt.Sprintf("%s.%s", prefix, key), tags)...)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateMCPTools(cfg *Config) []error {
|
||||
var errs []error
|
||||
mcp := cfg.Tools.MCP
|
||||
|
||||
@@ -177,3 +177,70 @@ func TestValidateGatewayNodeP2PIceServersRequireTurnCredentials(t *testing.T) {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGatewayNodeDispatchRejectsEmptyTagKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
cfg.Gateway.Nodes.Dispatch.ActionTags = map[string][]string{
|
||||
"": {"vision"},
|
||||
}
|
||||
|
||||
if errs := Validate(cfg); len(errs) == 0 {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGatewayNodeDispatchRejectsEmptyAllowNodeKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
cfg.Gateway.Nodes.Dispatch.AllowActions = map[string][]string{
|
||||
"": {"screen_snapshot"},
|
||||
}
|
||||
|
||||
if errs := Validate(cfg); len(errs) == 0 {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfigSetsNodeArtifactRetentionDefaults(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
if cfg.Gateway.Nodes.Artifacts.Enabled {
|
||||
t.Fatalf("expected node artifact retention disabled by default")
|
||||
}
|
||||
if cfg.Gateway.Nodes.Artifacts.KeepLatest != 500 {
|
||||
t.Fatalf("unexpected default keep_latest: %d", cfg.Gateway.Nodes.Artifacts.KeepLatest)
|
||||
}
|
||||
if cfg.Gateway.Nodes.Artifacts.RetainDays != 7 {
|
||||
t.Fatalf("unexpected default retain_days: %d", cfg.Gateway.Nodes.Artifacts.RetainDays)
|
||||
}
|
||||
if !cfg.Gateway.Nodes.Artifacts.PruneOnRead {
|
||||
t.Fatalf("expected prune_on_read enabled by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNodeArtifactRetentionRequiresPositiveKeepLatestWhenEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
cfg.Gateway.Nodes.Artifacts.Enabled = true
|
||||
cfg.Gateway.Nodes.Artifacts.KeepLatest = 0
|
||||
|
||||
if errs := Validate(cfg); len(errs) == 0 {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNodeArtifactRetentionRejectsNegativeRetainDays(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
cfg.Gateway.Nodes.Artifacts.RetainDays = -1
|
||||
|
||||
if errs := Validate(cfg); len(errs) == 0 {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user