feat: expand node artifact operations and retention

This commit is contained in:
lpf
2026-03-09 10:46:22 +08:00
parent be2e025fe5
commit ba3be33c91
22 changed files with 2724 additions and 151 deletions

View File

@@ -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{

View File

@@ -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

View File

@@ -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")
}
}