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

smallnest / goclaw / 21933710626

12 Feb 2026 04:35AM UTC coverage: 3.106% (-1.4%) from 4.552%
21933710626

push

github

chaoyuepan
#10 re-implement the agent like pi-mono

0 of 953 new or added lines in 9 files covered. (0.0%)

145 existing lines in 5 files now uncovered.

586 of 18864 relevant lines covered (3.11%)

0.18 hits per line

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

0.0
/cli/root.go
1
package cli
2

3
import (
4
        "context"
5
        "fmt"
6
        "os"
7
        "os/signal"
8
        "syscall"
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/channels"
14
        "github.com/smallnest/goclaw/cli/commands"
15
        "github.com/smallnest/goclaw/config"
16
        "github.com/smallnest/goclaw/cron"
17
        "github.com/smallnest/goclaw/gateway"
18
        "github.com/smallnest/goclaw/internal/logger"
19
        "github.com/smallnest/goclaw/internal/workspace"
20
        "github.com/smallnest/goclaw/providers"
21
        "github.com/smallnest/goclaw/session"
22
        "github.com/spf13/cobra"
23
        "go.uber.org/zap"
24
)
25

26
var rootCmd = &cobra.Command{
27
        Use:   "goclaw",
28
        Short: "Go-based AI Agent framework",
29
        Long:  `goclaw is a Go language implementation of an AI Agent framework, inspired by nanobot.`,
30
}
31

32
var startCmd = &cobra.Command{
33
        Use:   "start",
34
        Short: "Start the goclaw agent",
35
        Run:   runStart,
36
}
37

38
var configCmd = &cobra.Command{
39
        Use:   "config",
40
        Short: "Configuration management",
41
}
42

43
var configShowCmd = &cobra.Command{
44
        Use:   "show",
45
        Short: "Show current configuration",
46
        Run:   runConfigShow,
47
}
48

49
var installCmd = &cobra.Command{
50
        Use:   "install",
51
        Short: "Install goclaw workspace templates",
52
        Run:   runInstall,
53
}
54

55
// Flags for install command
56
var (
57
        installConfigPath    string
58
        installWorkspacePath string
59
)
60

61
func init() {
×
62
        // Add install command flags
×
63
        installCmd.Flags().StringVar(&installConfigPath, "config", "", "Path to config file")
×
64
        installCmd.Flags().StringVar(&installWorkspacePath, "workspace", "", "Path to workspace directory (overrides config)")
×
65

×
66
        rootCmd.AddCommand(startCmd)
×
67
        rootCmd.AddCommand(installCmd)
×
68
        rootCmd.AddCommand(configCmd)
×
69
        configCmd.AddCommand(configShowCmd)
×
70
        rootCmd.AddCommand(agentsCmd)
×
71
        rootCmd.AddCommand(agentCmd)
×
72
        rootCmd.AddCommand(sessionsCmd)
×
73

×
74
        // Register memory and logs commands from commands package
×
75
        // Note: skills command is already registered in cli/skills.go
×
76
        rootCmd.AddCommand(commands.MemoryCmd)
×
77
        rootCmd.AddCommand(commands.LogsCmd)
×
78

×
79
        // Register browser, tui, gateway, health, status commands
×
80
        rootCmd.AddCommand(commands.BrowserCommand())
×
81
        rootCmd.AddCommand(commands.TUICommand())
×
82
        rootCmd.AddCommand(commands.GatewayCommand())
×
83
        rootCmd.AddCommand(commands.HealthCommand())
×
84
        rootCmd.AddCommand(commands.StatusCommand())
×
85

×
86
        // Register approvals, cron, system commands (registered via init)
×
87
        // These commands auto-register themselves
×
88
}
×
89

90
// Execute 执行 CLI
91
func Execute() error {
×
92
        return rootCmd.Execute()
×
93
}
×
94

95
// runStart 启动 Agent
96
func runStart(cmd *cobra.Command, args []string) {
×
97
        // 加载配置
×
98
        cfg, err := config.Load("")
×
99
        if err != nil {
×
100
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
101
                os.Exit(1)
×
102
        }
×
103

104
        // 初始化日志
105
        if err := logger.Init("info", false); err != nil {
×
106
                fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
×
107
                os.Exit(1)
×
108
        }
×
109
        defer func() { _ = logger.Sync() }()
×
110

111
        logger.Info("Starting goclaw agent")
×
112

×
113
        // 验证配置
×
114
        if err := config.Validate(cfg); err != nil {
×
115
                logger.Fatal("Invalid configuration", zap.Error(err))
×
116
        }
×
117

118
        // 获取 workspace 目录
119
        workspaceDir, err := config.GetWorkspacePath(cfg)
×
120
        if err != nil {
×
121
                logger.Fatal("Failed to get workspace path", zap.Error(err))
×
122
        }
×
123

124
        // 创建 workspace 管理器并确保文件存在
125
        workspaceMgr := workspace.NewManager(workspaceDir)
×
126
        if err := workspaceMgr.Ensure(); err != nil {
×
127
                logger.Warn("Failed to ensure workspace files", zap.Error(err))
×
128
        } else {
×
129
                logger.Info("Workspace ready", zap.String("path", workspaceDir))
×
130
        }
×
131

132
        // 创建消息总线
133
        messageBus := bus.NewMessageBus(100)
×
134
        defer messageBus.Close()
×
135

×
136
        // 创建会话管理器
×
137
        sessionDir := os.Getenv("HOME") + "/.goclaw/sessions"
×
138
        sessionMgr, err := session.NewManager(sessionDir)
×
139
        if err != nil {
×
140
                logger.Fatal("Failed to create session manager", zap.Error(err))
×
141
        }
×
142

143
        // 创建记忆存储
144
        memoryStore := agent.NewMemoryStore(workspaceDir)
×
145

×
146
        // 创建上下文构建器
×
147
        contextBuilder := agent.NewContextBuilder(memoryStore, workspaceDir)
×
148

×
149
        // 创建工具注册表
×
NEW
150
        toolRegistry := agent.NewToolRegistry()
×
151

×
152
        // 创建技能加载器
×
153
        skillsLoader := agent.NewSkillsLoader(workspaceDir, []string{})
×
154
        if err := skillsLoader.Discover(); err != nil {
×
155
                logger.Warn("Failed to discover skills", zap.Error(err))
×
156
        } else {
×
157
                skills := skillsLoader.List()
×
158
                if len(skills) > 0 {
×
159
                        logger.Info("Skills loaded", zap.Int("count", len(skills)))
×
160
                }
×
161
        }
162

163
        // 注册文件系统工具
164
        fsTool := tools.NewFileSystemTool(cfg.Tools.FileSystem.AllowedPaths, cfg.Tools.FileSystem.DeniedPaths, workspaceDir)
×
165
        for _, tool := range fsTool.GetTools() {
×
NEW
166
                if err := toolRegistry.RegisterExisting(tool); err != nil {
×
167
                        logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
168
                }
×
169
        }
170

171
        // 注册 use_skill 工具(用于两阶段技能加载)
NEW
172
        if err := toolRegistry.RegisterExisting(tools.NewUseSkillTool()); err != nil {
×
173
                logger.Warn("Failed to register use_skill tool", zap.Error(err))
×
174
        }
×
175

176
        // 注册 Shell 工具
177
        shellTool := tools.NewShellTool(
×
178
                cfg.Tools.Shell.Enabled,
×
179
                cfg.Tools.Shell.AllowedCmds,
×
180
                cfg.Tools.Shell.DeniedCmds,
×
181
                cfg.Tools.Shell.Timeout,
×
182
                cfg.Tools.Shell.WorkingDir,
×
183
                cfg.Tools.Shell.Sandbox,
×
184
        )
×
185
        for _, tool := range shellTool.GetTools() {
×
NEW
186
                if err := toolRegistry.RegisterExisting(tool); err != nil {
×
187
                        logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
188
                }
×
189
        }
190

191
        // 注册 Web 工具
192
        webTool := tools.NewWebTool(
×
193
                cfg.Tools.Web.SearchAPIKey,
×
194
                cfg.Tools.Web.SearchEngine,
×
195
                cfg.Tools.Web.Timeout,
×
196
        )
×
197
        for _, tool := range webTool.GetTools() {
×
NEW
198
                if err := toolRegistry.RegisterExisting(tool); err != nil {
×
199
                        logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
200
                }
×
201
        }
202

203
        // 注册智能搜索工具(支持 web search 失败时自动回退到 Google browser 搜索)
204
        browserTimeout := 30
×
205
        if cfg.Tools.Browser.Timeout > 0 {
×
206
                browserTimeout = cfg.Tools.Browser.Timeout
×
207
        }
×
NEW
208
        if err := toolRegistry.RegisterExisting(tools.NewSmartSearch(webTool, true, browserTimeout).GetTool()); err != nil {
×
209
                logger.Warn("Failed to register smart_search tool", zap.Error(err))
×
210
        }
×
211

212
        // 注册浏览器工具(如果启用)
213
        if cfg.Tools.Browser.Enabled {
×
214
                browserTool := tools.NewBrowserTool(
×
215
                        cfg.Tools.Browser.Headless,
×
216
                        cfg.Tools.Browser.Timeout,
×
217
                )
×
218
                for _, tool := range browserTool.GetTools() {
×
NEW
219
                        if err := toolRegistry.RegisterExisting(tool); err != nil {
×
220
                                logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
221
                        }
×
222
                }
223
                logger.Info("Browser tools registered")
×
224
        }
225

226
        // 创建 LLM 提供商
227
        provider, err := providers.NewProvider(cfg)
×
228
        if err != nil {
×
229
                logger.Fatal("Failed to create LLM provider", zap.Error(err))
×
230
        }
×
231
        defer provider.Close()
×
232

×
233
        // 创建上下文
×
234
        ctx, cancel := context.WithCancel(context.Background())
×
235
        defer cancel()
×
236

×
237
        // 创建通道管理器
×
238
        channelMgr := channels.NewManager(messageBus)
×
239
        if err := channelMgr.SetupFromConfig(cfg); err != nil {
×
240
                logger.Warn("Failed to setup channels from config", zap.Error(err))
×
241
        }
×
242

243
        // 创建网关服务器
244
        gatewayServer := gateway.NewServer(&cfg.Gateway, messageBus, channelMgr, sessionMgr)
×
245
        if err := gatewayServer.Start(ctx); err != nil {
×
246
                logger.Warn("Failed to start gateway server", zap.Error(err))
×
247
        }
×
248
        defer func() { _ = gatewayServer.Stop() }()
×
249

250
        // 创建调度器
251
        scheduler := cron.NewScheduler(messageBus, provider, sessionMgr)
×
252

×
NEW
253
        // 创建 Agent
×
NEW
254
        agentInstance, err := agent.NewAgent(&agent.NewAgentConfig{
×
255
                Bus:          messageBus,
×
256
                Provider:     provider,
×
257
                SessionMgr:   sessionMgr,
×
258
                Tools:        toolRegistry,
×
NEW
259
                Context:      contextBuilder,
×
260
                Workspace:    workspaceDir,
×
261
                MaxIteration: cfg.Agents.Defaults.MaxIterations,
×
NEW
262
        })
×
263
        if err != nil {
×
NEW
264
                logger.Fatal("Failed to create agent", zap.Error(err))
×
265
        }
×
266

267
        // 处理信号
268
        sigChan := make(chan os.Signal, 1)
×
269
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
×
270

×
271
        // 启动通道
×
272
        if err := channelMgr.Start(ctx); err != nil {
×
273
                logger.Error("Failed to start channels", zap.Error(err))
×
274
        }
×
275
        defer func() { _ = channelMgr.Stop() }()
×
276

277
        // 启动调度器
278
        if err := scheduler.Start(ctx); err != nil {
×
279
                logger.Error("Failed to start scheduler", zap.Error(err))
×
280
        }
×
281
        defer scheduler.Stop()
×
282

×
283
        // 启动出站消息分发
×
284
        logger.Info("About to start outbound message dispatcher")
×
285
        go func() {
×
286
                defer func() {
×
287
                        if r := recover(); r != nil {
×
288
                                logger.Error("Outbound message dispatcher panicked",
×
289
                                        zap.Any("panic", r))
×
290
                        }
×
291
                }()
292
                if err := channelMgr.DispatchOutbound(ctx); err != nil {
×
293
                        logger.Error("Outbound message dispatcher exited with error", zap.Error(err))
×
294
                } else {
×
295
                        logger.Info("Outbound message dispatcher exited normally")
×
296
                }
×
297
        }()
298

299
        // 启动 Agent
300
        go func() {
×
NEW
301
                if err := agentInstance.Start(ctx); err != nil {
×
NEW
302
                        logger.Error("Agent error", zap.Error(err))
×
UNCOV
303
                }
×
304
        }()
305

306
        // 等待信号
307
        <-sigChan
×
308
        logger.Info("Received shutdown signal")
×
309

×
310
        // 停止 Agent
×
NEW
311
        if err := agentInstance.Stop(); err != nil {
×
NEW
312
                logger.Error("Failed to stop agent", zap.Error(err))
×
UNCOV
313
        }
×
314

315
        logger.Info("goclaw agent stopped")
×
316
}
317

318
// runConfigShow 显示配置
319
func runConfigShow(cmd *cobra.Command, args []string) {
×
320
        cfg, err := config.Load("")
×
321
        if err != nil {
×
322
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
323
                os.Exit(1)
×
324
        }
×
325

326
        fmt.Println("Current Configuration:")
×
327
        fmt.Printf("  Model: %s\n", cfg.Agents.Defaults.Model)
×
328
        fmt.Printf("  Max Iterations: %d\n", cfg.Agents.Defaults.MaxIterations)
×
329
        fmt.Printf("  Temperature: %.1f\n", cfg.Agents.Defaults.Temperature)
×
330
}
331

332
// runInstall 安装 goclaw workspace 模板
333
func runInstall(cmd *cobra.Command, args []string) {
×
334
        // 加载配置
×
335
        cfg, err := config.Load(installConfigPath)
×
336
        if err != nil {
×
337
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
338
                os.Exit(1)
×
339
        }
×
340

341
        // 获取 workspace 目录
342
        workspaceDir := installWorkspacePath
×
343
        if workspaceDir == "" {
×
344
                workspaceDir, err = config.GetWorkspacePath(cfg)
×
345
                if err != nil {
×
346
                        fmt.Fprintf(os.Stderr, "Failed to get workspace path: %v\n", err)
×
347
                        os.Exit(1)
×
348
                }
×
349
        }
350

351
        // 创建 workspace 管理器并确保文件存在
352
        workspaceMgr := workspace.NewManager(workspaceDir)
×
353
        if err := workspaceMgr.Ensure(); err != nil {
×
354
                fmt.Fprintf(os.Stderr, "Failed to ensure workspace: %v\n", err)
×
355
                os.Exit(1)
×
356
        }
×
357

358
        fmt.Printf("Workspace installed successfully at: %s\n", workspaceDir)
×
359
        fmt.Println("\nWorkspace files:")
×
360
        files, err := workspaceMgr.ListFiles()
×
361
        if err != nil {
×
362
                fmt.Fprintf(os.Stderr, "Failed to list files: %v\n", err)
×
363
                return
×
364
        }
×
365
        for _, f := range files {
×
366
                fmt.Printf("  - %s\n", f)
×
367
        }
×
368

369
        memoryFiles, err := workspaceMgr.ListMemoryFiles()
×
370
        if err == nil && len(memoryFiles) > 0 {
×
371
                fmt.Println("\nMemory files:")
×
372
                for _, f := range memoryFiles {
×
373
                        fmt.Printf("  - memory/%s\n", f)
×
374
                }
×
375
        }
376

377
        fmt.Println("\nYou can now customize these files to define your agent's personality and behavior.")
×
378
}
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