From d839e034af7f40efd5f496f100dd84c2326c38d0 Mon Sep 17 00:00:00 2001 From: viettranx Date: Sun, 8 Mar 2026 22:40:09 +0700 Subject: [PATCH] feat(tools): add exec path exemptions and tool arguments in events - Add AllowPathExemptions to ExecTool for fine-grained deny bypass (skills-store) - Include tool call arguments in tool.result event payloads --- cmd/onboard_managed.go | 4 +++ internal/agent/loop.go | 14 ++++---- internal/store/provider_store.go | 2 ++ internal/tools/shell.go | 35 ++++++++++++++----- ui/web/src/constants/providers.ts | 1 + ui/web/src/pages/providers/providers-page.tsx | 1 + 6 files changed, 43 insertions(+), 14 deletions(-) diff --git a/cmd/onboard_managed.go b/cmd/onboard_managed.go index 1a81be6b..02cceeb3 100644 --- a/cmd/onboard_managed.go +++ b/cmd/onboard_managed.go @@ -387,6 +387,8 @@ func resolveProviderType(name string) string { return store.ProviderCohere case "perplexity": return store.ProviderPerplexity + case "yescale": + return store.ProviderYesScale default: return store.ProviderOpenAICompat } @@ -417,6 +419,8 @@ func resolveProviderAPIBase(providerName string) string { return "https://api.cohere.com/v2" case "perplexity": return "https://api.perplexity.ai" + case "yescale": + return "https://api.yescale.one/v1" default: return "" } diff --git a/internal/agent/loop.go b/internal/agent/loop.go index dcb86381..a9721cb6 100644 --- a/internal/agent/loop.go +++ b/internal/agent/loop.go @@ -760,9 +760,10 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error) } toolResultPayload := map[string]interface{}{ - "name": tc.Name, - "id": tc.ID, - "is_error": result.IsError, + "name": tc.Name, + "id": tc.ID, + "is_error": result.IsError, + "arguments": tc.Arguments, } if result.IsError && result.ForLLM != "" { toolResultPayload["content"] = result.ForLLM @@ -899,9 +900,10 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error) } parToolResultPayload := map[string]interface{}{ - "name": r.tc.Name, - "id": r.tc.ID, - "is_error": r.result.IsError, + "name": r.tc.Name, + "id": r.tc.ID, + "is_error": r.result.IsError, + "arguments": r.tc.Arguments, } if r.result.IsError && r.result.ForLLM != "" { parToolResultPayload["content"] = r.result.ForLLM diff --git a/internal/store/provider_store.go b/internal/store/provider_store.go index 9130d75a..9ac4ada2 100644 --- a/internal/store/provider_store.go +++ b/internal/store/provider_store.go @@ -25,6 +25,7 @@ const ( ProviderChatGPTOAuth = "chatgpt_oauth" ProviderClaudeCLI = "claude_cli" ProviderSuno = "suno" + ProviderYesScale = "yescale" ) // ValidProviderTypes lists all accepted provider_type values. @@ -45,6 +46,7 @@ var ValidProviderTypes = map[string]bool{ ProviderChatGPTOAuth: true, ProviderClaudeCLI: true, ProviderSuno: true, + ProviderYesScale: true, } // LLMProviderData represents an LLM provider configuration. diff --git a/internal/tools/shell.go b/internal/tools/shell.go index a1cdc1d1..d563aa8b 100644 --- a/internal/tools/shell.go +++ b/internal/tools/shell.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "regexp" + "strings" "time" "github.com/nextlevelbuilder/goclaw/internal/sandbox" @@ -129,13 +130,14 @@ var defaultDenyPatterns = []*regexp.Regexp{ // ExecTool executes shell commands, optionally inside a sandbox container. type ExecTool struct { - workingDir string - timeout time.Duration - denyPatterns []*regexp.Regexp - restrict bool - sandboxMgr sandbox.Manager // nil = no sandbox, execute on host - approvalMgr *ExecApprovalManager // nil = no approval needed - agentID string // for approval request context + workingDir string + timeout time.Duration + denyPatterns []*regexp.Regexp + denyExemptions []string // substrings that exempt a command from deny (e.g. ".goclaw/skills-store/") + restrict bool + sandboxMgr sandbox.Manager // nil = no sandbox, execute on host + approvalMgr *ExecApprovalManager // nil = no approval needed + agentID string // for approval request context } // NewExecTool creates an exec tool that runs commands directly on the host. @@ -171,6 +173,13 @@ func (t *ExecTool) DenyPaths(paths ...string) { } } +// AllowPathExemptions adds substrings that exempt a command from deny pattern matches. +// E.g. AllowPathExemptions(".goclaw/skills-store/") lets commands referencing +// skills-store pass even though ".goclaw/" is denied. +func (t *ExecTool) AllowPathExemptions(substrings ...string) { + t.denyExemptions = append(t.denyExemptions, substrings...) +} + // SetApprovalManager sets the exec approval manager for this tool. func (t *ExecTool) SetApprovalManager(mgr *ExecApprovalManager, agentID string) { t.approvalMgr = mgr @@ -205,7 +214,17 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *Re // Check for dangerous commands (applies to both host and sandbox) for _, pattern := range t.denyPatterns { if pattern.MatchString(command) { - return ErrorResult(fmt.Sprintf("command denied by safety policy: matches pattern %s", pattern.String())) + // Check if any exemption applies (e.g. skills-store within .goclaw) + exempt := false + for _, ex := range t.denyExemptions { + if strings.Contains(command, ex) { + exempt = true + break + } + } + if !exempt { + return ErrorResult(fmt.Sprintf("command denied by safety policy: matches pattern %s", pattern.String())) + } } } diff --git a/ui/web/src/constants/providers.ts b/ui/web/src/constants/providers.ts index 14974c1f..3453a3fa 100644 --- a/ui/web/src/constants/providers.ts +++ b/ui/web/src/constants/providers.ts @@ -20,5 +20,6 @@ export const PROVIDER_TYPES: ProviderTypeInfo[] = [ { value: "perplexity", label: "Perplexity", apiBase: "https://api.perplexity.ai", placeholder: "" }, { value: "dashscope", label: "DashScope (Qwen)", apiBase: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", placeholder: "" }, { value: "bailian", label: "Bailian Coding", apiBase: "https://coding-intl.dashscope.aliyuncs.com/v1", placeholder: "" }, + { value: "yescale", label: "YesScale", apiBase: "https://api.yescale.one/v1", placeholder: "" }, { value: "claude_cli", label: "Claude CLI (Local)", apiBase: "", placeholder: "" }, ]; diff --git a/ui/web/src/pages/providers/providers-page.tsx b/ui/web/src/pages/providers/providers-page.tsx index 04583872..8e8221ce 100644 --- a/ui/web/src/pages/providers/providers-page.tsx +++ b/ui/web/src/pages/providers/providers-page.tsx @@ -27,6 +27,7 @@ const typeBadge: Record