diff --git a/pkg/agent/instance_test.go b/pkg/agent/instance_test.go index e8649a82d..3b42d3d9e 100644 --- a/pkg/agent/instance_test.go +++ b/pkg/agent/instance_test.go @@ -127,7 +127,7 @@ func TestNewAgentInstance_ReadOnlyContainerOmitsWriteTools(t *testing.T) { } } - writeRes := agent.Tools.Execute(context.Background(), "write_file", map[string]interface{}{ + writeRes := agent.Tools.Execute(context.Background(), "write_file", map[string]any{ "path": "a.txt", "content": "hello", }) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index a99b97722..1706099a6 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -696,7 +696,14 @@ func (al *AgentLoop) runLLMIteration( } toolCtx := sandbox.WithSessionKey(ctx, opts.SessionKey) - toolResult := agent.Tools.ExecuteWithContext(toolCtx, tc.Name, tc.Arguments, opts.Channel, opts.ChatID, asyncCallback) + toolResult := agent.Tools.ExecuteWithContext( + toolCtx, + tc.Name, + tc.Arguments, + opts.Channel, + opts.ChatID, + asyncCallback, + ) // Send ForUser content to user immediately if not Silent if !toolResult.Silent && toolResult.ForUser != "" && opts.SendResponse { diff --git a/pkg/agent/sandbox/container.go b/pkg/agent/sandbox/container.go index 442b62ab0..4d221e5d4 100644 --- a/pkg/agent/sandbox/container.go +++ b/pkg/agent/sandbox/container.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/go-units" + "github.com/sipeed/picoclaw/pkg/config" ) @@ -192,7 +193,11 @@ func (c *ContainerSandbox) Exec(ctx context.Context, req ExecRequest) (*ExecResu }) } -func (c *ContainerSandbox) ExecStream(ctx context.Context, req ExecRequest, onEvent func(ExecEvent) error) (*ExecResult, error) { +func (c *ContainerSandbox) ExecStream( + ctx context.Context, + req ExecRequest, + onEvent func(ExecEvent) error, +) (*ExecResult, error) { if c.startErr != nil { return nil, c.startErr } @@ -240,7 +245,8 @@ func (c *ContainerSandbox) ExecStream(ctx context.Context, req ExecRequest, onEv onEvent: onEvent, buffer: &stderr, } - if _, err := stdcopy.StdCopy(stdoutWriter, stderrWriter, attach.Reader); err != nil && err != io.EOF { + _, err = stdcopy.StdCopy(stdoutWriter, stderrWriter, attach.Reader) + if err != nil && err != io.EOF { if execCtx.Err() != nil { return nil, execCtx.Err() } @@ -578,7 +584,7 @@ func (f *containerFS) WriteFile(ctx context.Context, p string, data []byte, mkdi tw := tar.NewWriter(&buf) if err := tw.WriteHeader(&tar.Header{ Name: base, - Mode: 0644, + Mode: 0o644, Size: int64(len(data)), }); err != nil { _ = tw.Close() diff --git a/pkg/agent/sandbox/container_integration_test.go b/pkg/agent/sandbox/container_integration_test.go index bee2c9821..e6b0722cf 100644 --- a/pkg/agent/sandbox/container_integration_test.go +++ b/pkg/agent/sandbox/container_integration_test.go @@ -27,7 +27,8 @@ func TestContainerSandbox_Integration_ExecReadWrite(t *testing.T) { } defer cli.Close() - if _, err := cli.Ping(ctx); err != nil { + _, err = cli.Ping(ctx) + if err != nil { t.Skipf("docker daemon unavailable: %v", err) } @@ -43,7 +44,8 @@ func TestContainerSandbox_Integration_ExecReadWrite(t *testing.T) { ContainerName: containerName, Workspace: workspace, }) - if err := sb.Start(ctx); err != nil { + err = sb.Start(ctx) + if err != nil { t.Fatalf("sandbox start failed: %v", err) } defer func() { @@ -54,7 +56,8 @@ func TestContainerSandbox_Integration_ExecReadWrite(t *testing.T) { }() content := []byte("hello from integration test") - if err := sb.Fs().WriteFile(ctx, "it/write.txt", content, true); err != nil { + err = sb.Fs().WriteFile(ctx, "it/write.txt", content, true) + if err != nil { t.Fatalf("write file failed: %v", err) } @@ -116,7 +119,8 @@ func TestContainerSandbox_Integration_WriteFileMkdirInContainerTmp(t *testing.T) } defer cli.Close() - if _, err := cli.Ping(ctx); err != nil { + _, err = cli.Ping(ctx) + if err != nil { t.Skipf("docker daemon unavailable: %v", err) } @@ -130,7 +134,8 @@ func TestContainerSandbox_Integration_WriteFileMkdirInContainerTmp(t *testing.T) Image: image, ContainerName: containerName, }) - if err := sb.Start(ctx); err != nil { + err = sb.Start(ctx) + if err != nil { t.Fatalf("sandbox start failed: %v", err) } defer func() { @@ -141,7 +146,8 @@ func TestContainerSandbox_Integration_WriteFileMkdirInContainerTmp(t *testing.T) }() content := []byte("mkdir path works") - if err := sb.Fs().WriteFile(ctx, "/workspace/it_mkdir/nested/file.txt", content, true); err != nil { + err = sb.Fs().WriteFile(ctx, "/workspace/it_mkdir/nested/file.txt", content, true) + if err != nil { t.Fatalf("write with mkdir failed: %v", err) } diff --git a/pkg/agent/sandbox/container_test.go b/pkg/agent/sandbox/container_test.go index 70072e900..a7cd315cb 100644 --- a/pkg/agent/sandbox/container_test.go +++ b/pkg/agent/sandbox/container_test.go @@ -377,12 +377,21 @@ func TestParseByteLimitAndHostConfig(t *testing.T) { t.Fatalf("unexpected pids limit: %#v", hc.Resources.PidsLimit) } if hc.Memory <= 0 || hc.MemorySwap <= 0 || hc.NanoCPUs <= 0 { - t.Fatalf("expected memory/swap/cpu limits set, got mem=%d swap=%d cpu=%d", hc.Memory, hc.MemorySwap, hc.NanoCPUs) + t.Fatalf( + "expected memory/swap/cpu limits set, got mem=%d swap=%d cpu=%d", + hc.Memory, + hc.MemorySwap, + hc.NanoCPUs, + ) } if len(hc.Tmpfs) != 2 || hc.Tmpfs["/run"] != "" { t.Fatalf("unexpected tmpfs map: %#v", hc.Tmpfs) } - if got := strings.Join(hc.SecurityOpt, ","); !strings.Contains(got, "seccomp=sec-profile.json") || !strings.Contains(got, "apparmor=apparmor-profile") { + if got := strings.Join( + hc.SecurityOpt, + ",", + ); !strings.Contains(got, "seccomp=sec-profile.json") || + !strings.Contains(got, "apparmor=apparmor-profile") { t.Fatalf("security options missing expected profiles: %v", hc.SecurityOpt) } if len(hc.Resources.Ulimits) != 2 { diff --git a/pkg/agent/sandbox/host.go b/pkg/agent/sandbox/host.go index fcbc813c7..c12c05ce9 100644 --- a/pkg/agent/sandbox/host.go +++ b/pkg/agent/sandbox/host.go @@ -63,7 +63,11 @@ func (h *HostSandbox) Exec(ctx context.Context, req ExecRequest) (*ExecResult, e }) } -func (h *HostSandbox) ExecStream(ctx context.Context, req ExecRequest, onEvent func(ExecEvent) error) (*ExecResult, error) { +func (h *HostSandbox) ExecStream( + ctx context.Context, + req ExecRequest, + onEvent func(ExecEvent) error, +) (*ExecResult, error) { if strings.TrimSpace(req.Command) == "" { return nil, fmt.Errorf("empty command") } @@ -225,11 +229,11 @@ func (h *hostFS) WriteFile(ctx context.Context, path string, data []byte, mkdir return err } if mkdir { - if err := os.MkdirAll(filepath.Dir(resolved), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(resolved), 0o755); err != nil { return err } } - return os.WriteFile(resolved, data, 0644) + return os.WriteFile(resolved, data, 0o644) } relPath, err := h.getSafeRelPath(path) @@ -239,12 +243,12 @@ func (h *hostFS) WriteFile(ctx context.Context, path string, data []byte, mkdir if mkdir { // MkdirAll natively resolves inside os.Root to avoid escapes. - if err := h.root.MkdirAll(filepath.Dir(relPath), 0755); err != nil { + if err := h.root.MkdirAll(filepath.Dir(relPath), 0o755); err != nil { return err } } // Uses OS-level guarantees to restrict the file writing within the root descriptor. - return h.root.WriteFile(relPath, data, 0644) + return h.root.WriteFile(relPath, data, 0o644) } // validatePath ensures the given path is within the workspace if restrict is true but does not ensure atomic TOCTOU protection. diff --git a/pkg/agent/sandbox/host_test.go b/pkg/agent/sandbox/host_test.go index e530627bd..96d6cd4dc 100644 --- a/pkg/agent/sandbox/host_test.go +++ b/pkg/agent/sandbox/host_test.go @@ -61,7 +61,8 @@ func TestHostSandbox_ExecAndFs(t *testing.T) { t.Fatalf("expected working dir restriction error, got: %v", err) } - if err := sb.Fs().WriteFile(context.Background(), "dir/a.txt", []byte("x"), true); err != nil { + err = sb.Fs().WriteFile(context.Background(), "dir/a.txt", []byte("x"), true) + if err != nil { t.Fatalf("WriteFile() error: %v", err) } b, err := sb.Fs().ReadFile(context.Background(), "dir/a.txt") @@ -285,7 +286,8 @@ func TestHostFS_ReadFileWriteFile_WithoutWorkspaceOrRoot(t *testing.T) { // Test case where root is nil explicitly sb2 := NewHostSandbox(root, true) sb2.fs.(*hostFS).root = nil - if err := sb2.Fs().WriteFile(context.Background(), "nil_root_test.txt", content, true); err != nil { + err = sb2.Fs().WriteFile(context.Background(), "nil_root_test.txt", content, true) + if err != nil { t.Fatalf("WriteFile failed: %v", err) } @@ -307,7 +309,8 @@ func TestValidatePathErrors(t *testing.T) { root := t.TempDir() // target parent is file, evalSymlinks should fail - if err := os.WriteFile(filepath.Join(root, "a.txt"), []byte("a"), 0644); err != nil { + err = os.WriteFile(filepath.Join(root, "a.txt"), []byte("a"), 0o644) + if err != nil { t.Fatal(err) } _, err = validatePath("a.txt/b.txt", root, true) diff --git a/pkg/agent/sandbox/manager.go b/pkg/agent/sandbox/manager.go index 2cabdaa58..ed4b83f54 100644 --- a/pkg/agent/sandbox/manager.go +++ b/pkg/agent/sandbox/manager.go @@ -332,7 +332,11 @@ func (m *scopedSandboxManager) Exec(ctx context.Context, req ExecRequest) (*Exec return sb.Exec(ctx, req) } -func (m *scopedSandboxManager) ExecStream(ctx context.Context, req ExecRequest, onEvent func(ExecEvent) error) (*ExecResult, error) { +func (m *scopedSandboxManager) ExecStream( + ctx context.Context, + req ExecRequest, + onEvent func(ExecEvent) error, +) (*ExecResult, error) { if !m.shouldSandbox(ctx) { return m.host.ExecStream(ctx, req, onEvent) } @@ -382,7 +386,8 @@ func (m *scopedSandboxManager) normalizeSessionKey(raw string) string { return main } if parsed := routing.ParseAgentSessionKey(trimmed); parsed != nil { - if routing.NormalizeAgentID(parsed.AgentID) == m.agentID && strings.EqualFold(strings.TrimSpace(parsed.Rest), "main") { + if routing.NormalizeAgentID(parsed.AgentID) == m.agentID && + strings.EqualFold(strings.TrimSpace(parsed.Rest), "main") { return main } } @@ -538,7 +543,11 @@ func (u *unavailableSandboxManager) Exec(ctx context.Context, req ExecRequest) ( }) } -func (u *unavailableSandboxManager) ExecStream(ctx context.Context, req ExecRequest, onEvent func(ExecEvent) error) (*ExecResult, error) { +func (u *unavailableSandboxManager) ExecStream( + ctx context.Context, + req ExecRequest, + onEvent func(ExecEvent) error, +) (*ExecResult, error) { return nil, u.err } diff --git a/pkg/agent/sandbox/registry.go b/pkg/agent/sandbox/registry.go index e50421767..4bd617cf9 100644 --- a/pkg/agent/sandbox/registry.go +++ b/pkg/agent/sandbox/registry.go @@ -34,12 +34,12 @@ type registryFileLock struct { func acquireRegistryFileLock(registryPath string) (*registryFileLock, error) { lockPath := registryPath + ".lock" - if err := os.MkdirAll(filepath.Dir(lockPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(lockPath), 0o755); err != nil { return nil, err } deadline := time.Now().Add(registryLockTimeout) for { - f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) if err == nil { _ = f.Close() return ®istryFileLock{path: lockPath}, nil @@ -89,7 +89,7 @@ func loadRegistry(path string) (*registryData, error) { } func saveRegistry(path string, data *registryData) error { - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } raw, err := json.MarshalIndent(data, "", " ") @@ -97,7 +97,7 @@ func saveRegistry(path string, data *registryData) error { return err } tmp := fmt.Sprintf("%s.%d.tmp", path, time.Now().UnixNano()) - if err := os.WriteFile(tmp, append(raw, '\n'), 0644); err != nil { + if err := os.WriteFile(tmp, append(raw, '\n'), 0o644); err != nil { return err } if err := os.Rename(tmp, path); err != nil { diff --git a/pkg/agent/sandbox/registry_test.go b/pkg/agent/sandbox/registry_test.go index a3756da87..c1305e02a 100644 --- a/pkg/agent/sandbox/registry_test.go +++ b/pkg/agent/sandbox/registry_test.go @@ -53,7 +53,8 @@ func TestRegistryUpsertAndRemove(t *testing.T) { t.Fatalf("createdAt preserved = %d, want %d", data.Entries[0].CreatedAtMs, now) } - if err := removeRegistryEntry(path, "c1"); err != nil { + err = removeRegistryEntry(path, "c1") + if err != nil { t.Fatalf("removeRegistryEntry failed: %v", err) } data, err = loadRegistry(path) diff --git a/pkg/config/config.go b/pkg/config/config.go index a9963a507..4e5a0aa8f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -167,16 +167,16 @@ type SessionConfig struct { } type AgentDefaults struct { - Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"` - RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"` - Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"` - Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` + Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"` + RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"` + Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"` + Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` ModelFallbacks []string `json:"model_fallbacks,omitempty"` - ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"` + ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"` ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` - MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` - Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` - MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` + MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` + Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` + MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` Sandbox AgentSandboxConfig `json:"sandbox"` } @@ -454,7 +454,7 @@ type ExecConfig struct { } type AgentSandboxPruneConfig struct { - IdleHours int `json:"idle_hours" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_PRUNE_IDLE_HOURS"` + IdleHours int `json:"idle_hours" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_PRUNE_IDLE_HOURS"` MaxAgeDays int `json:"max_age_days" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_PRUNE_MAX_AGE_DAYS"` } @@ -502,22 +502,22 @@ func (v AgentSandboxDockerUlimitValue) MarshalJSON() ([]byte, error) { } type AgentSandboxDockerConfig struct { - Image string `json:"image" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_IMAGE"` + Image string `json:"image" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_IMAGE"` ContainerPrefix string `json:"container_prefix" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_CONTAINER_PREFIX"` - Workdir string `json:"workdir" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_WORKDIR"` - ReadOnlyRoot bool `json:"read_only_root" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_READ_ONLY_ROOT"` + Workdir string `json:"workdir" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_WORKDIR"` + ReadOnlyRoot bool `json:"read_only_root" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_READ_ONLY_ROOT"` Tmpfs []string `json:"tmpfs"` - Network string `json:"network" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_NETWORK"` - User string `json:"user" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_USER"` + Network string `json:"network" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_NETWORK"` + User string `json:"user" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_USER"` CapDrop []string `json:"cap_drop"` Env map[string]string `json:"env"` - SetupCommand string `json:"setup_command" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_SETUP_COMMAND"` - PidsLimit int64 `json:"pids_limit" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_PIDS_LIMIT"` - Memory string `json:"memory" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_MEMORY"` - MemorySwap string `json:"memory_swap" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_MEMORY_SWAP"` - Cpus float64 `json:"cpus" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_CPUS"` + SetupCommand string `json:"setup_command" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_SETUP_COMMAND"` + PidsLimit int64 `json:"pids_limit" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_PIDS_LIMIT"` + Memory string `json:"memory" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_MEMORY"` + MemorySwap string `json:"memory_swap" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_MEMORY_SWAP"` + Cpus float64 `json:"cpus" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_CPUS"` Ulimits map[string]AgentSandboxDockerUlimitValue `json:"ulimits"` - SeccompProfile string `json:"seccomp_profile" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_SECCOMP_PROFILE"` + SeccompProfile string `json:"seccomp_profile" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_SECCOMP_PROFILE"` ApparmorProfile string `json:"apparmor_profile" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_DOCKER_APPARMOR_PROFILE"` DNS []string `json:"dns"` ExtraHosts []string `json:"extra_hosts"` @@ -525,10 +525,10 @@ type AgentSandboxDockerConfig struct { } type AgentSandboxConfig struct { - Mode string `json:"mode" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_MODE"` - Scope string `json:"scope" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_SCOPE"` + Mode string `json:"mode" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_MODE"` + Scope string `json:"scope" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_SCOPE"` WorkspaceAccess string `json:"workspace_access" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_WORKSPACE_ACCESS"` - WorkspaceRoot string `json:"workspace_root" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_WORKSPACE_ROOT"` + WorkspaceRoot string `json:"workspace_root" env:"PICOCLAW_AGENTS_DEFAULTS_SANDBOX_WORKSPACE_ROOT"` Docker AgentSandboxDockerConfig `json:"docker"` Prune AgentSandboxPruneConfig `json:"prune"` } diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index c910e4a48..f8b55f849 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -33,7 +33,15 @@ type CronTool struct { // NewCronTool creates a new CronTool. // execTimeout: 0 means no timeout, >0 sets the timeout duration. -func NewCronTool(cronService *cron.CronService, executor JobExecutor, msgBus *bus.MessageBus, workspace string, restrict bool, execTimeout time.Duration, config *config.Config) *CronTool { +func NewCronTool( + cronService *cron.CronService, + executor JobExecutor, + msgBus *bus.MessageBus, + workspace string, + restrict bool, + execTimeout time.Duration, + config *config.Config, +) *CronTool { sb := sandbox.NewFromConfig(workspace, restrict, config) guard := NewExecToolWithConfig(workspace, restrict, config) return &CronTool{ diff --git a/pkg/tools/cron_additional_test.go b/pkg/tools/cron_additional_test.go index fe19f8a1b..f5f27498d 100644 --- a/pkg/tools/cron_additional_test.go +++ b/pkg/tools/cron_additional_test.go @@ -29,7 +29,11 @@ func (s *cronStubSandbox) Exec(ctx context.Context, req sandbox.ExecRequest) (*s return s.ExecStream(ctx, req, nil) } -func (s *cronStubSandbox) ExecStream(ctx context.Context, req sandbox.ExecRequest, onEvent func(sandbox.ExecEvent) error) (*sandbox.ExecResult, error) { +func (s *cronStubSandbox) ExecStream( + ctx context.Context, + req sandbox.ExecRequest, + onEvent func(sandbox.ExecEvent) error, +) (*sandbox.ExecResult, error) { s.calls++ s.last = req if s.err != nil { @@ -38,12 +42,16 @@ func (s *cronStubSandbox) ExecStream(ctx context.Context, req sandbox.ExecReques if s.res != nil { if onEvent != nil { if s.res.Stdout != "" { - if err := onEvent(sandbox.ExecEvent{Type: sandbox.ExecEventStdout, Chunk: []byte(s.res.Stdout)}); err != nil { + if err := onEvent( + sandbox.ExecEvent{Type: sandbox.ExecEventStdout, Chunk: []byte(s.res.Stdout)}, + ); err != nil { return nil, err } } if s.res.Stderr != "" { - if err := onEvent(sandbox.ExecEvent{Type: sandbox.ExecEventStderr, Chunk: []byte(s.res.Stderr)}); err != nil { + if err := onEvent( + sandbox.ExecEvent{Type: sandbox.ExecEventStderr, Chunk: []byte(s.res.Stderr)}, + ); err != nil { return nil, err } } @@ -66,7 +74,10 @@ func (s *cronStubSandbox) ExecStream(ctx context.Context, req sandbox.ExecReques type noopExecutor struct{} -func (n *noopExecutor) ProcessDirectWithChannel(ctx context.Context, content, sessionKey, channel, chatID string) (string, error) { +func (n *noopExecutor) ProcessDirectWithChannel( + ctx context.Context, + content, sessionKey, channel, chatID string, +) (string, error) { return "ok", nil } diff --git a/pkg/tools/edit.go b/pkg/tools/edit.go index 68e6fdb5f..dc46ef790 100644 --- a/pkg/tools/edit.go +++ b/pkg/tools/edit.go @@ -104,11 +104,13 @@ func (t *EditFileTool) Execute(ctx context.Context, args map[string]any) *ToolRe newContent := strings.Replace(contentStr, oldText, newText, 1) if sb != nil { - if err := sb.Fs().WriteFile(ctx, path, []byte(newContent), true); err != nil { + err = sb.Fs().WriteFile(ctx, path, []byte(newContent), true) + if err != nil { return ErrorResult(fmt.Sprintf("failed to write file: %v", err)) } } else { - if err := os.WriteFile(resolvedPath, []byte(newContent), 0o644); err != nil { + err = os.WriteFile(resolvedPath, []byte(newContent), 0o644) + if err != nil { return ErrorResult(fmt.Sprintf("failed to write file: %v", err)) } } @@ -169,12 +171,14 @@ func (t *AppendFileTool) Execute(ctx context.Context, args map[string]any) *Tool sb := sandbox.SandboxFromContext(ctx) if sb != nil { // Implement Append using Read + Write if no Append in FsBridge - oldContent, err := sb.Fs().ReadFile(ctx, path) + var oldContent []byte + oldContent, err = sb.Fs().ReadFile(ctx, path) if err != nil && !strings.Contains(err.Error(), "no such file") { return ErrorResult(fmt.Sprintf("failed to read file for append: %v", err)) } newContent := string(oldContent) + content - if err := sb.Fs().WriteFile(ctx, path, []byte(newContent), true); err != nil { + err = sb.Fs().WriteFile(ctx, path, []byte(newContent), true) + if err != nil { return ErrorResult(fmt.Sprintf("failed to append (write) to file: %v", err)) } return SilentResult(fmt.Sprintf("Appended to %s", path)) diff --git a/pkg/tools/filesystem.go b/pkg/tools/filesystem.go index 7f5ab83eb..20e1dd2b5 100644 --- a/pkg/tools/filesystem.go +++ b/pkg/tools/filesystem.go @@ -252,7 +252,6 @@ func (t *ListDirTool) Execute(ctx context.Context, args map[string]any) *ToolRes } entries, err := os.ReadDir(resolvedPath) - if err != nil { return ErrorResult(fmt.Sprintf("failed to read directory: %v", err)) } diff --git a/pkg/tools/shell_test.go b/pkg/tools/shell_test.go index 693275438..096aec112 100644 --- a/pkg/tools/shell_test.go +++ b/pkg/tools/shell_test.go @@ -29,7 +29,11 @@ func (s *stubSandbox) Exec(ctx context.Context, req sandbox.ExecRequest) (*sandb return sandboxAggregateFromStub(ctx, req, s.ExecStream) } -func (s *stubSandbox) ExecStream(ctx context.Context, req sandbox.ExecRequest, onEvent func(sandbox.ExecEvent) error) (*sandbox.ExecResult, error) { +func (s *stubSandbox) ExecStream( + ctx context.Context, + req sandbox.ExecRequest, + onEvent func(sandbox.ExecEvent) error, +) (*sandbox.ExecResult, error) { s.lastReq = req if s.err != nil { return nil, s.err @@ -37,12 +41,16 @@ func (s *stubSandbox) ExecStream(ctx context.Context, req sandbox.ExecRequest, o if s.res != nil { if onEvent != nil { if s.res.Stdout != "" { - if err := onEvent(sandbox.ExecEvent{Type: sandbox.ExecEventStdout, Chunk: []byte(s.res.Stdout)}); err != nil { + if err := onEvent( + sandbox.ExecEvent{Type: sandbox.ExecEventStdout, Chunk: []byte(s.res.Stdout)}, + ); err != nil { return nil, err } } if s.res.Stderr != "" { - if err := onEvent(sandbox.ExecEvent{Type: sandbox.ExecEventStderr, Chunk: []byte(s.res.Stderr)}); err != nil { + if err := onEvent( + sandbox.ExecEvent{Type: sandbox.ExecEventStderr, Chunk: []byte(s.res.Stderr)}, + ); err != nil { return nil, err } } @@ -361,7 +369,7 @@ func TestShellTool_SandboxMapsHostWorkingDirToRelative(t *testing.T) { tool := NewExecTool(workspace, true) ctx := sandbox.WithSandbox(context.Background(), sb) - args := map[string]interface{}{ + args := map[string]any{ "command": "echo test", "working_dir": filepath.Join(workspace, "subdir"), } @@ -380,7 +388,7 @@ func TestShellTool_SandboxAllowsAbsoluteWorkspaceWorkingDir(t *testing.T) { tool := NewExecTool(workspace, true) ctx := sandbox.WithSandbox(context.Background(), sb) - args := map[string]interface{}{ + args := map[string]any{ "command": "echo test", "working_dir": "/workspace/subdir", } @@ -399,7 +407,7 @@ func TestShellTool_SandboxBlocksAbsoluteNonWorkspaceWorkingDirWhenRestricted(t * tool := NewExecTool(workspace, true) ctx := sandbox.WithSandbox(context.Background(), sb) - args := map[string]interface{}{ + args := map[string]any{ "command": "echo test", "working_dir": "/tmp/logs", } @@ -418,7 +426,7 @@ func TestShellTool_SandboxExecError(t *testing.T) { tool := NewExecTool(workspace, true) ctx := sandbox.WithSandbox(context.Background(), sb) - result := tool.Execute(ctx, map[string]interface{}{"command": "echo test"}) + result := tool.Execute(ctx, map[string]any{"command": "echo test"}) if !result.IsError { t.Fatal("expected sandbox error result") }