• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

smallnest / goclaw / 22357241380

24 Feb 2026 03:19PM UTC coverage: 4.288% (-0.01%) from 4.3%
22357241380

push

github

smallnest
#23 fix windows issue

0 of 92 new or added lines in 7 files covered. (0.0%)

1 existing line in 1 file now uncovered.

1059 of 24697 relevant lines covered (4.29%)

0.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cli/agent.go
1
package cli
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "os"
8
        "time"
9

10
        "github.com/smallnest/goclaw/agent"
11
        "github.com/smallnest/goclaw/agent/tools"
12
        "github.com/smallnest/goclaw/bus"
13
        "github.com/smallnest/goclaw/config"
14
        "github.com/smallnest/goclaw/internal/logger"
15
        "github.com/smallnest/goclaw/providers"
16
        "github.com/smallnest/goclaw/session"
17
        "github.com/spf13/cobra"
18
        "go.uber.org/zap"
19
)
20

21
var agentCmd = &cobra.Command{
22
        Use:   "agent",
23
        Short: "Run one agent turn",
24
        Long:  `Execute a single agent interaction with a message and optional parameters.`,
25
        Run:   runAgent,
26
}
27

28
// Flags for agent command
29
var (
30
        agentMessage   string
31
        agentTo        string
32
        agentSessionID string
33
        agentThinking  bool
34
        agentVerbose   bool
35
        agentChannel   string
36
        agentLocal     bool
37
        agentDeliver   bool
38
        agentJSON      bool
39
        agentTimeout   int
40
)
41

42
func init() {
×
43
        agentCmd.Flags().StringVar(&agentMessage, "message", "", "Message to send to the agent (required)")
×
44
        agentCmd.Flags().StringVar(&agentTo, "to", "", "Target agent name")
×
45
        agentCmd.Flags().StringVar(&agentSessionID, "session-id", "", "Session ID to use")
×
46
        agentCmd.Flags().BoolVar(&agentThinking, "thinking", false, "Show thinking process")
×
47
        agentCmd.Flags().BoolVar(&agentVerbose, "verbose", false, "Enable verbose output")
×
48
        agentCmd.Flags().StringVar(&agentChannel, "channel", "cli", "Channel to use (cli, telegram, etc.)")
×
49
        agentCmd.Flags().BoolVar(&agentLocal, "local", false, "Run in local mode without connecting to channels")
×
50
        agentCmd.Flags().BoolVar(&agentDeliver, "deliver", false, "Deliver response through the channel")
×
51
        agentCmd.Flags().BoolVar(&agentJSON, "json", false, "Output in JSON format")
×
52
        agentCmd.Flags().IntVar(&agentTimeout, "timeout", 120, "Timeout in seconds")
×
53

×
54
        _ = agentCmd.MarkFlagRequired("message")
×
55
}
×
56

57
// runAgent executes a single agent turn
58
func runAgent(cmd *cobra.Command, args []string) {
×
59
        // Validate message
×
60
        if agentMessage == "" {
×
61
                fmt.Fprintf(os.Stderr, "Error: --message is required\n")
×
62
                os.Exit(1)
×
63
        }
×
64

65
        // Initialize logger if verbose or thinking mode is enabled
66
        if agentVerbose || agentThinking {
×
67
                if err := logger.Init("debug", false); err != nil {
×
68
                        fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
×
69
                        os.Exit(1)
×
70
                }
×
71
                defer func() { _ = logger.Sync() }()
×
72
        }
73

74
        // Load configuration
75
        cfg, err := config.Load("")
×
76
        if err != nil {
×
77
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
78
                os.Exit(1)
×
79
        }
×
80

81
        // Create workspace
NEW
82
        homeDir, err := os.UserHomeDir()
×
NEW
83
        if err != nil {
×
NEW
84
                fmt.Fprintf(os.Stderr, "Failed to get home directory: %v\n", err)
×
NEW
85
                os.Exit(1)
×
NEW
86
        }
×
NEW
87
        workspace := homeDir + "/.goclaw/workspace"
×
88
        if err := os.MkdirAll(workspace, 0755); err != nil {
×
89
                fmt.Fprintf(os.Stderr, "Failed to create workspace: %v\n", err)
×
90
                os.Exit(1)
×
91
        }
×
92

93
        // Create message bus
94
        messageBus := bus.NewMessageBus(100)
×
95
        defer messageBus.Close()
×
96

×
97
        // Create session manager
×
NEW
98
        sessionDir := homeDir + "/.goclaw/sessions"
×
99
        sessionMgr, err := session.NewManager(sessionDir)
×
100
        if err != nil {
×
101
                fmt.Fprintf(os.Stderr, "Failed to create session manager: %v\n", err)
×
102
                os.Exit(1)
×
103
        }
×
104

105
        // Create memory store
106
        memoryStore := agent.NewMemoryStore(workspace)
×
107
        if err := memoryStore.EnsureBootstrapFiles(); err != nil {
×
108
                if agentVerbose {
×
109
                        fmt.Fprintf(os.Stderr, "Warning: Failed to create bootstrap files: %v\n", err)
×
110
                }
×
111
        }
112

113
        // Create context builder
114
        contextBuilder := agent.NewContextBuilder(memoryStore, workspace)
×
115

×
116
        // Create tool registry
×
117
        toolRegistry := agent.NewToolRegistry()
×
118

×
119
        // Register file system tool
×
120
        fsTool := tools.NewFileSystemTool(cfg.Tools.FileSystem.AllowedPaths, cfg.Tools.FileSystem.DeniedPaths, workspace)
×
121
        for _, tool := range fsTool.GetTools() {
×
122
                if err := toolRegistry.RegisterExisting(tool); err != nil && agentVerbose {
×
123
                        fmt.Fprintf(os.Stderr, "Warning: Failed to register tool %s: %v\n", tool.Name(), err)
×
124
                }
×
125
        }
126

127
        // Register shell tool
128
        shellTool := tools.NewShellTool(
×
129
                cfg.Tools.Shell.Enabled,
×
130
                cfg.Tools.Shell.AllowedCmds,
×
131
                cfg.Tools.Shell.DeniedCmds,
×
132
                cfg.Tools.Shell.Timeout,
×
133
                cfg.Tools.Shell.WorkingDir,
×
134
                cfg.Tools.Shell.Sandbox,
×
135
        )
×
136
        for _, tool := range shellTool.GetTools() {
×
137
                if err := toolRegistry.RegisterExisting(tool); err != nil && agentVerbose {
×
138
                        fmt.Fprintf(os.Stderr, "Warning: Failed to register tool %s: %v\n", tool.Name(), err)
×
139
                }
×
140
        }
141

142
        // Register web tool
143
        webTool := tools.NewWebTool(
×
144
                cfg.Tools.Web.SearchAPIKey,
×
145
                cfg.Tools.Web.SearchEngine,
×
146
                cfg.Tools.Web.Timeout,
×
147
        )
×
148
        for _, tool := range webTool.GetTools() {
×
149
                if err := toolRegistry.RegisterExisting(tool); err != nil && agentVerbose {
×
150
                        fmt.Fprintf(os.Stderr, "Warning: Failed to register tool %s: %v\n", tool.Name(), err)
×
151
                }
×
152
        }
153

154
        // Register smart search tool
155
        browserTimeout := 30
×
156
        if cfg.Tools.Browser.Timeout > 0 {
×
157
                browserTimeout = cfg.Tools.Browser.Timeout
×
158
        }
×
159
        if err := toolRegistry.RegisterExisting(tools.NewSmartSearch(webTool, true, browserTimeout).GetTool()); err != nil && agentVerbose {
×
160
                fmt.Fprintf(os.Stderr, "Warning: Failed to register smart_search: %v\n", err)
×
161
        }
×
162

163
        // Register browser tool if enabled
164
        if cfg.Tools.Browser.Enabled {
×
165
                browserTool := tools.NewBrowserTool(
×
166
                        cfg.Tools.Browser.Headless,
×
167
                        cfg.Tools.Browser.Timeout,
×
168
                )
×
169
                for _, tool := range browserTool.GetTools() {
×
170
                        if err := toolRegistry.RegisterExisting(tool); err != nil && agentVerbose {
×
171
                                fmt.Fprintf(os.Stderr, "Warning: Failed to register browser tool %s: %v\n", tool.Name(), err)
×
172
                        }
×
173
                }
174
        }
175

176
        // Register use_skill tool
177
        if err := toolRegistry.RegisterExisting(tools.NewUseSkillTool()); err != nil && agentVerbose {
×
178
                fmt.Fprintf(os.Stderr, "Warning: Failed to register use_skill: %v\n", err)
×
179
        }
×
180

181
        // Create skills loader
182
        skillsLoader := agent.NewSkillsLoader(workspace, []string{})
×
183
        if err := skillsLoader.Discover(); err != nil && agentVerbose {
×
184
                fmt.Fprintf(os.Stderr, "Warning: Failed to discover skills: %v\n", err)
×
185
        }
×
186

187
        // Create LLM provider
188
        provider, err := providers.NewProvider(cfg)
×
189
        if err != nil {
×
190
                fmt.Fprintf(os.Stderr, "Failed to create LLM provider: %v\n", err)
×
191
                os.Exit(1)
×
192
        }
×
193
        defer provider.Close()
×
194

×
195
        // Create context with timeout
×
196
        ctx, cancel := context.WithTimeout(context.Background(), time.Duration(agentTimeout)*time.Second)
×
197
        defer cancel()
×
198

×
199
        // Determine session key
×
200
        sessionKey := agentSessionID
×
201
        if sessionKey == "" {
×
202
                sessionKey = agentChannel + ":default"
×
203
        }
×
204

205
        // Create new agent first
206
        agentInstance, err := agent.NewAgent(&agent.NewAgentConfig{
×
207
                Bus:          messageBus,
×
208
                Provider:     provider,
×
209
                SessionMgr:   sessionMgr,
×
210
                Tools:        toolRegistry,
×
211
                Context:      contextBuilder,
×
212
                Workspace:    workspace,
×
213
                MaxIteration: cfg.Agents.Defaults.MaxIterations,
×
214
        })
×
215
        if err != nil {
×
216
                fmt.Fprintf(os.Stderr, "Failed to create agent: %v\n", err)
×
217
                os.Exit(1)
×
218
        }
×
219

220
        // Publish message to bus for processing
221
        inboundMsg := &bus.InboundMessage{
×
222
                Channel:   agentChannel,
×
223
                SenderID:  "cli",
×
224
                ChatID:    "default",
×
225
                Content:   agentMessage,
×
226
                Timestamp: time.Now(),
×
227
        }
×
228

×
229
        if err := messageBus.PublishInbound(ctx, inboundMsg); err != nil {
×
230
                if agentJSON {
×
231
                        errorResult := map[string]interface{}{
×
232
                                "error":   err.Error(),
×
233
                                "success": false,
×
234
                        }
×
235
                        data, _ := json.MarshalIndent(errorResult, "", "  ")
×
236
                        fmt.Println(string(data))
×
237
                } else {
×
238
                        fmt.Fprintf(os.Stderr, "Error publishing message: %v\n", err)
×
239
                }
×
240
                os.Exit(1)
×
241
        }
242

243
        // Start the agent to process the message
244
        go func() {
×
245
                if err := agentInstance.Start(ctx); err != nil && err != context.Canceled && err != context.DeadlineExceeded {
×
246
                        logger.Error("Agent error", zap.Error(err))
×
247
                }
×
248
        }()
249

250
        // Consume outbound message
251
        outbound, err := messageBus.ConsumeOutbound(ctx)
×
252
        if err != nil {
×
253
                if agentJSON {
×
254
                        errorResult := map[string]interface{}{
×
255
                                "error":   err.Error(),
×
256
                                "success": false,
×
257
                        }
×
258
                        data, _ := json.MarshalIndent(errorResult, "", "  ")
×
259
                        fmt.Println(string(data))
×
260
                } else {
×
261
                        fmt.Fprintf(os.Stderr, "Error consuming response: %v\n", err)
×
262
                }
×
263
                os.Exit(1)
×
264
        }
265

266
        response := outbound.Content
×
267

×
268
        // Stop the agent
×
269
        if err := agentInstance.Stop(); err != nil && agentVerbose {
×
270
                fmt.Fprintf(os.Stderr, "Warning: Failed to stop agent: %v\n", err)
×
271
        }
×
272

273
        // Note: Messages are already saved to session by Agent.handleInboundMessage
274

275
        // Output response
276
        if agentJSON {
×
277
                result := map[string]interface{}{
×
278
                        "response": response,
×
279
                        "success":  true,
×
280
                        "session":  sessionKey,
×
281
                }
×
282
                data, err := json.MarshalIndent(result, "", "  ")
×
283
                if err != nil {
×
284
                        fmt.Fprintf(os.Stderr, "Error marshaling JSON: %v\n", err)
×
285
                        os.Exit(1)
×
286
                }
×
287
                fmt.Println(string(data))
×
288
        } else {
×
289
                if agentThinking {
×
290
                        fmt.Println("\n💡 Response:")
×
291
                }
×
292
                fmt.Println(response)
×
293
        }
294

295
        // Deliver through channel if requested
296
        if agentDeliver && !agentLocal {
×
297
                if err := deliverResponse(ctx, messageBus, response); err != nil && agentVerbose {
×
298
                        fmt.Fprintf(os.Stderr, "Warning: Failed to deliver response: %v\n", err)
×
299
                }
×
300
        }
301
}
302

303
// deliverResponse delivers the response through the configured channel
304
func deliverResponse(ctx context.Context, messageBus *bus.MessageBus, content string) error {
×
305
        return messageBus.PublishOutbound(ctx, &bus.OutboundMessage{
×
306
                Channel:   agentChannel,
×
307
                ChatID:    "default",
×
308
                Content:   content,
×
309
                Timestamp: time.Now(),
×
310
        })
×
311
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc