mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-17 02:21:31 +00:00
Post-review cleanup of Phase 4. Closes Finding #9 properly and corrects the Finding #13 documentation lie surfaced in the code-review report. Capability schema: - Replace `AgentOverridable bool` with `AgentOverridableAs string` on ParamSchema. Empty string = not overridable; non-empty = the generic key alias (`"speed"`, `"emotion"`, `"style"`). - Each provider declaration now carries the alias inline, so the generic↔native mapping has a single TS-readable source. Frontend: - Web `tts-override-block.tsx` drops the inline `GENERIC_TO_NATIVE` literal and derives the bidirectional adapter from the filtered capability params (each param self-describes its alias). Adapter tests rewritten around the new shape. - Desktop `AgentDetailPanel.tsx` drops the 45-line inline IIFE in favour of a new `<TtsOverrideFineTune>` component that uses the same alias-based mapping. Backend: - Move `AgentTTSParamsAllowedKeys` + `ValidateAgentTTSParams` to `internal/audio/agent_params_adapter.go`. HTTP `validate.go` and WS `gateway/methods/agents_update.go` both delegate, eliminating the duplicated `{speed, emotion, style}` literal. Cleanup: - Delete orphan i18n keys `MsgTtsParamInvalidJSON` and `MsgTtsParamDependsOn` from `keys.go` + en/vi/zh catalogs (no in-code references; DependsOn is FE-only, JSON parse failures already surface via slog). Documentation: - `prompt-settings-section.tsx` Finding #13 comment rewritten to honestly describe the best-effort merge into a fresh local copy of the cached `otherConfig` prop. Concurrent-tab clobber remains possible — server-side JSON-merge-patch endpoint planned for v2. Tests: 9 backend suites (race), web 217/217, desktop build clean, both Go build tags pass.
86 lines
3.2 KiB
Go
86 lines
3.2 KiB
Go
package http
|
|
|
|
import (
|
|
"log/slog"
|
|
"regexp"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/audio"
|
|
)
|
|
|
|
var slugRe = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`)
|
|
|
|
// isValidSlug checks whether s matches the slug format: lowercase alphanumeric + hyphens,
|
|
// cannot start or end with a hyphen.
|
|
func isValidSlug(s string) bool {
|
|
return slugRe.MatchString(s)
|
|
}
|
|
|
|
// filterAllowedKeys returns a new map containing only keys present in the allowlist.
|
|
// Defense-in-depth: prevents column injection and unauthorized field updates.
|
|
func filterAllowedKeys(updates map[string]any, allowed map[string]bool) map[string]any {
|
|
filtered := make(map[string]any, len(updates))
|
|
for k, v := range updates {
|
|
if allowed[k] {
|
|
filtered[k] = v
|
|
} else {
|
|
slog.Warn("security.filtered_unknown_field", "field", k)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
// validateAgentTTSParams is a thin wrapper around audio.ValidateAgentTTSParams
|
|
// so HTTP handlers can call it without importing the audio package directly.
|
|
// The allow-list is owned by internal/audio (single source of truth, Action D).
|
|
func validateAgentTTSParams(ttsParams map[string]any) error {
|
|
return audio.ValidateAgentTTSParams(ttsParams)
|
|
}
|
|
|
|
// --- Field allowlists for update endpoints ---
|
|
// Each map lists the columns that HTTP clients may update.
|
|
// Immutable fields (id, owner_id, created_at, deleted_at) are excluded.
|
|
|
|
var agentAllowedFields = map[string]bool{
|
|
"agent_key": true, "agent_type": true, "display_name": true,
|
|
"provider": true, "model": true, "status": true,
|
|
"context_window": true, "max_tool_iterations": true,
|
|
"workspace": true,
|
|
"frontmatter": true, "compaction_config": true,
|
|
"memory_config": true, "other_config": true, "tools_config": true,
|
|
"sandbox_config": true, "context_pruning": true,
|
|
"is_default": true, "budget_monthly_cents": true, "subagents_config": true,
|
|
// Promoted from other_config
|
|
"emoji": true, "agent_description": true, "thinking_level": true, "max_tokens": true,
|
|
"self_evolve": true, "skill_evolve": true, "skill_nudge_interval": true,
|
|
"reasoning_config": true, "workspace_sharing": true, "chatgpt_oauth_routing": true,
|
|
"shell_deny_groups": true, "kg_dedup_config": true,
|
|
}
|
|
|
|
var providerAllowedFields = map[string]bool{
|
|
"name": true, "provider_type": true, "api_key": true,
|
|
"api_base": true, "base_url": true, "default_model": true,
|
|
"extra_headers": true, "config": true, "enabled": true,
|
|
"display_name": true, "display_order": true, "settings": true,
|
|
}
|
|
|
|
var customToolAllowedFields = map[string]bool{
|
|
"name": true, "description": true, "command": true,
|
|
"parameters": true, "agent_id": true, "env": true,
|
|
"tags": true, "requires": true, "timeout_seconds": true,
|
|
"enabled": true,
|
|
}
|
|
|
|
var mcpServerAllowedFields = map[string]bool{
|
|
"name": true, "transport": true, "command": true, "args": true,
|
|
"url": true, "api_key": true, "env": true, "headers": true,
|
|
"enabled": true, "tool_prefix": true, "timeout_sec": true,
|
|
"agent_id": true, "config": true, "settings": true,
|
|
}
|
|
|
|
var channelInstanceAllowedFields = map[string]bool{
|
|
"name": true, "channel_type": true, "credentials": true, "agent_id": true,
|
|
"enabled": true, "group_policy": true, "allow_from": true,
|
|
"metadata": true, "webhook_secret": true, "config": true,
|
|
"display_name": true,
|
|
}
|