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
This commit is contained in:
viettranx committed 2026-03-08 22:40:09 +07:00
1 parent 9d0af657e5
commit d839e034af
6 files changed
+43 -14

No files matched your search

+4
View File
@@ -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 ""
}
+8 -6
View File
@@ -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
+2
View File
@@ -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.
+27 -8
View File
@@ -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()))
}
}
}
+1
View File
@@ -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: "" },
];
@@ -27,6 +27,7 @@ const typeBadge: Record<string, { label: string; variant: "default" | "secondary
cohere: { label: "Cohere", variant: "secondary" },
perplexity: { label: "Perplexity", variant: "secondary" },
chatgpt_oauth: { label: "ChatGPT (OAuth)", variant: "default" },
yescale: { label: "YesScale", variant: "secondary" },
};
export function ProvidersPage() {