From 1a6febffe735e95d7a07314d6f0d2d58f318c0f4 Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 08:28:41 +0000 Subject: [PATCH] harden exec defaults and gate bootstrap loading to first-run context --- config.example.json | 1 + pkg/agent/context.go | 24 +++++++++++++++++++++++- pkg/config/config.go | 14 ++++++++------ pkg/tools/shell.go | 20 +++++++++++--------- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/config.example.json b/config.example.json index 5605950..9731f27 100644 --- a/config.example.json +++ b/config.example.json @@ -143,6 +143,7 @@ "enabled": true, "working_dir": "", "timeout": 60000000000, + "auto_install_missing": false, "sandbox": { "enabled": false, "image": "alpine:3.20" diff --git a/pkg/agent/context.go b/pkg/agent/context.go index aa854d8..1a86f07 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -137,7 +137,6 @@ func (cb *ContextBuilder) LoadBootstrapFiles() string { bootstrapFiles := []string{ "AGENTS.md", "BOOT.md", - "BOOTSTRAP.md", "SOUL.md", "USER.md", "IDENTITY.md", @@ -152,9 +151,32 @@ func (cb *ContextBuilder) LoadBootstrapFiles() string { } } + // BOOTSTRAP.md is first-run guidance; load only when identity/user is not initialized. + if cb.shouldLoadBootstrap() { + if data, err := os.ReadFile(filepath.Join(cb.workspace, "BOOTSTRAP.md")); err == nil { + result += fmt.Sprintf("## %s\n\n%s\n\n", "BOOTSTRAP.md", string(data)) + } + } + return result } +func (cb *ContextBuilder) shouldLoadBootstrap() bool { + identityPath := filepath.Join(cb.workspace, "IDENTITY.md") + userPath := filepath.Join(cb.workspace, "USER.md") + identityData, idErr := os.ReadFile(identityPath) + userData, userErr := os.ReadFile(userPath) + if idErr != nil || userErr != nil { + return true + } + idText := strings.TrimSpace(string(identityData)) + userText := strings.TrimSpace(string(userData)) + if idText == "" || userText == "" { + return true + } + return false +} + func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary string, currentMessage string, media []string, channel, chatID, responseLanguage string) []providers.Message { messages := []providers.Message{} diff --git a/pkg/config/config.go b/pkg/config/config.go index 95bafc7..6c0cfe3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -217,10 +217,11 @@ type WebToolsConfig struct { } type ShellConfig struct { - Enabled bool `json:"enabled" env:"CLAWGO_TOOLS_SHELL_ENABLED"` - WorkingDir string `json:"working_dir" env:"CLAWGO_TOOLS_SHELL_WORKING_DIR"` - Timeout time.Duration `json:"timeout" env:"CLAWGO_TOOLS_SHELL_TIMEOUT"` - Sandbox SandboxConfig `json:"sandbox"` + Enabled bool `json:"enabled" env:"CLAWGO_TOOLS_SHELL_ENABLED"` + WorkingDir string `json:"working_dir" env:"CLAWGO_TOOLS_SHELL_WORKING_DIR"` + Timeout time.Duration `json:"timeout" env:"CLAWGO_TOOLS_SHELL_TIMEOUT"` + AutoInstallMissing bool `json:"auto_install_missing" env:"CLAWGO_TOOLS_SHELL_AUTO_INSTALL_MISSING"` + Sandbox SandboxConfig `json:"sandbox"` } type SandboxConfig struct { @@ -441,8 +442,9 @@ func DefaultConfig() *Config { }, }, Shell: ShellConfig{ - Enabled: true, - Timeout: 60 * time.Second, + Enabled: true, + Timeout: 60 * time.Second, + AutoInstallMissing: false, Sandbox: SandboxConfig{ Enabled: false, Image: "alpine:3.20", diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index bc6e219..706b8b1 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -18,18 +18,20 @@ import ( type ExecTool struct { workingDir string timeout time.Duration - sandboxEnabled bool - sandboxImage string - procManager *ProcessManager + sandboxEnabled bool + sandboxImage string + autoInstallMissing bool + procManager *ProcessManager } func NewExecTool(cfg config.ShellConfig, workspace string, pm *ProcessManager) *ExecTool { return &ExecTool{ - workingDir: workspace, - timeout: cfg.Timeout, - sandboxEnabled: cfg.Sandbox.Enabled, - sandboxImage: cfg.Sandbox.Image, - procManager: pm, + workingDir: workspace, + timeout: cfg.Timeout, + sandboxEnabled: cfg.Sandbox.Enabled, + sandboxImage: cfg.Sandbox.Image, + autoInstallMissing: cfg.AutoInstallMissing, + procManager: pm, } } @@ -142,7 +144,7 @@ func (t *ExecTool) executeCommand(ctx context.Context, command, cwd string) (str return fmt.Sprintf("Error: Command timed out after %v", t.timeout), nil } - if err != nil { + if err != nil && t.autoInstallMissing { if missingCmd := detectMissingCommandFromOutput(output); missingCmd != "" { if installLog, installed := t.tryAutoInstallMissingCommand(ctx, missingCmd, cwd); installed { output += "\n[AUTO-INSTALL]\n" + installLog