Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,10 @@ jobs:
with:
version: v2.10.1

# TODO: Remove once linter is properly configured
fmt-check:
name: Formatting
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod

- name: Check formatting
run: |
make fmt
git diff --exit-code || (echo "::error::Code is not formatted. Run 'make fmt' and commit the changes." && exit 1)

# TODO: Remove once linter is properly configured
vet:
name: Vet
runs-on: ubuntu-latest
needs: fmt-check
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -65,7 +46,6 @@ jobs:
test:
name: Tests
runs-on: ubuntu-latest
needs: fmt-check
steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down
9 changes: 4 additions & 5 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,11 @@ issues:

formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
# TODO: Disabled, because they are failing at the moment, we should fix them and enable (step by step)
# - gci
# - gofmt
# - gofumpt
# - golines
- golines
settings:
gci:
sections:
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ LDFLAGS=-ldflags "-X main.version=$(VERSION) -X main.gitCommit=$(GIT_COMMIT) -X
GO?=go
GOFLAGS?=-v -tags stdjson

# Golangci-lint
GOLANGCI_LINT?=golangci-lint

# Installation
INSTALL_PREFIX?=$(HOME)/.local
INSTALL_BIN_DIR=$(INSTALL_PREFIX)/bin
Expand Down Expand Up @@ -126,13 +129,17 @@ clean:
vet:
@$(GO) vet ./...

## fmt: Format Go code
## test: Test Go code
test:
@$(GO) test ./...

## fmt: Format Go code
fmt:
@$(GO) fmt ./...
@$(GOLANGCI_LINT) fmt

## lint: Run linters
lint:
@$(GOLANGCI_LINT) run

## deps: Download dependencies
deps:
Expand Down
10 changes: 5 additions & 5 deletions cmd/picoclaw/cmd_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"github.com/chzyer/readline"

"github.com/sipeed/picoclaw/pkg/agent"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/logger"
Expand Down Expand Up @@ -74,10 +75,10 @@ func agentCmd() {
// Print agent startup info (only for interactive mode)
startupInfo := agentLoop.GetStartupInfo()
logger.InfoCF("agent", "Agent initialized",
map[string]interface{}{
"tools_count": startupInfo["tools"].(map[string]interface{})["count"],
"skills_total": startupInfo["skills"].(map[string]interface{})["total"],
"skills_available": startupInfo["skills"].(map[string]interface{})["available"],
map[string]any{
"tools_count": startupInfo["tools"].(map[string]any)["count"],
"skills_total": startupInfo["skills"].(map[string]any)["total"],
"skills_available": startupInfo["skills"].(map[string]any)["available"],
})

if message != "" {
Expand All @@ -104,7 +105,6 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
InterruptPrompt: "^C",
EOFPrompt: "exit",
})

if err != nil {
fmt.Printf("Error initializing readline: %v\n", err)
fmt.Println("Falling back to simple input mode...")
Expand Down
26 changes: 20 additions & 6 deletions cmd/picoclaw/cmd_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,31 @@ func gatewayCmd() {
// Print agent startup info
fmt.Println("\nπŸ“¦ Agent Status:")
startupInfo := agentLoop.GetStartupInfo()
toolsInfo := startupInfo["tools"].(map[string]interface{})
skillsInfo := startupInfo["skills"].(map[string]interface{})
toolsInfo := startupInfo["tools"].(map[string]any)
skillsInfo := startupInfo["skills"].(map[string]any)
fmt.Printf(" β€’ Tools: %d loaded\n", toolsInfo["count"])
fmt.Printf(" β€’ Skills: %d/%d available\n",
skillsInfo["available"],
skillsInfo["total"])

// Log to file as well
logger.InfoCF("agent", "Agent initialized",
map[string]interface{}{
map[string]any{
"tools_count": toolsInfo["count"],
"skills_total": skillsInfo["total"],
"skills_available": skillsInfo["available"],
})

// Setup cron tool and service
execTimeout := time.Duration(cfg.Tools.Cron.ExecTimeoutMinutes) * time.Minute
cronService := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath(), cfg.Agents.Defaults.RestrictToWorkspace, execTimeout, cfg)
cronService := setupCronTool(
agentLoop,
msgBus,
cfg.WorkspacePath(),
cfg.Agents.Defaults.RestrictToWorkspace,
execTimeout,
cfg,
)

heartbeatService := heartbeat.NewHeartbeatService(
cfg.WorkspacePath(),
Expand Down Expand Up @@ -181,7 +188,7 @@ func gatewayCmd() {
healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
go func() {
if err := healthServer.Start(); err != nil && err != http.ErrServerClosed {
logger.ErrorCF("health", "Health server error", map[string]interface{}{"error": err.Error()})
logger.ErrorCF("health", "Health server error", map[string]any{"error": err.Error()})
}
}()
fmt.Printf("βœ“ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
Expand All @@ -203,7 +210,14 @@ func gatewayCmd() {
fmt.Println("βœ“ Gateway stopped")
}

func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string, restrict bool, execTimeout time.Duration, cfg *config.Config) *cron.CronService {
func setupCronTool(
agentLoop *agent.AgentLoop,
msgBus *bus.MessageBus,
workspace string,
restrict bool,
execTimeout time.Duration,
cfg *config.Config,
) *cron.CronService {
cronStorePath := filepath.Join(workspace, "cron", "jobs.json")

// Create cron service
Expand Down
6 changes: 3 additions & 3 deletions cmd/picoclaw/cmd_onboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func onboard() {

func copyEmbeddedToTarget(targetDir string) error {
// Ensure target directory exists
if err := os.MkdirAll(targetDir, 0755); err != nil {
if err := os.MkdirAll(targetDir, 0o755); err != nil {
return fmt.Errorf("Failed to create target directory: %w", err)
}

Expand Down Expand Up @@ -85,12 +85,12 @@ func copyEmbeddedToTarget(targetDir string) error {
targetPath := filepath.Join(targetDir, new_path)

// Ensure target file's directory exists
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
return fmt.Errorf("Failed to create directory %s: %w", filepath.Dir(targetPath), err)
}

// Write file
if err := os.WriteFile(targetPath, data, 0644); err != nil {
if err := os.WriteFile(targetPath, data, 0o644); err != nil {
return fmt.Errorf("Failed to write file %s: %w", targetPath, err)
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/picoclaw/cmd_skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func skillsInstallFromRegistry(cfg *config.Config, registryName, slug string) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

if err := os.MkdirAll(filepath.Join(workspace, "skills"), 0755); err != nil {
if err := os.MkdirAll(filepath.Join(workspace, "skills"), 0o755); err != nil {
fmt.Printf("\u2717 Failed to create skills directory: %v\n", err)
os.Exit(1)
}
Expand Down Expand Up @@ -193,7 +193,7 @@ func skillsInstallBuiltinCmd(workspace string) {
continue
}

if err := os.MkdirAll(workspacePath, 0755); err != nil {
if err := os.MkdirAll(workspacePath, 0o755); err != nil {
fmt.Printf("βœ— Failed to create directory for %s: %v\n", skillName, err)
continue
}
Expand Down
43 changes: 31 additions & 12 deletions pkg/agent/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ func (cb *ContextBuilder) buildToolsSection() string {

var sb strings.Builder
sb.WriteString("## Available Tools\n\n")
sb.WriteString("**CRITICAL**: You MUST use tools to perform actions. Do NOT pretend to execute commands or schedule tasks.\n\n")
sb.WriteString(
"**CRITICAL**: You MUST use tools to perform actions. Do NOT pretend to execute commands or schedule tasks.\n\n",
)
sb.WriteString("You have access to the following tools:\n\n")
for _, s := range summaries {
sb.WriteString(s)
Expand Down Expand Up @@ -157,7 +159,13 @@ func (cb *ContextBuilder) LoadBootstrapFiles() string {
return sb.String()
}

func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary string, currentMessage string, media []string, channel, chatID string) []providers.Message {
func (cb *ContextBuilder) BuildMessages(
history []providers.Message,
summary string,
currentMessage string,
media []string,
channel, chatID string,
) []providers.Message {
messages := []providers.Message{}

systemPrompt := cb.BuildSystemPrompt()
Expand All @@ -169,7 +177,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str

// Log system prompt summary for debugging (debug mode only)
logger.DebugCF("agent", "System prompt built",
map[string]interface{}{
map[string]any{
"total_chars": len(systemPrompt),
"total_lines": strings.Count(systemPrompt, "\n") + 1,
"section_count": strings.Count(systemPrompt, "\n\n---\n\n") + 1,
Expand All @@ -181,7 +189,7 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str
preview = preview[:500] + "... (truncated)"
}
logger.DebugCF("agent", "System prompt preview",
map[string]interface{}{
map[string]any{
"preview": preview,
})

Expand Down Expand Up @@ -218,25 +226,29 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
switch msg.Role {
case "tool":
if len(sanitized) == 0 {
logger.DebugCF("agent", "Dropping orphaned leading tool message", map[string]interface{}{})
logger.DebugCF("agent", "Dropping orphaned leading tool message", map[string]any{})
continue
}
last := sanitized[len(sanitized)-1]
if last.Role != "assistant" || len(last.ToolCalls) == 0 {
logger.DebugCF("agent", "Dropping orphaned tool message", map[string]interface{}{})
logger.DebugCF("agent", "Dropping orphaned tool message", map[string]any{})
continue
}
sanitized = append(sanitized, msg)

case "assistant":
if len(msg.ToolCalls) > 0 {
if len(sanitized) == 0 {
logger.DebugCF("agent", "Dropping assistant tool-call turn at history start", map[string]interface{}{})
logger.DebugCF("agent", "Dropping assistant tool-call turn at history start", map[string]any{})
continue
}
prev := sanitized[len(sanitized)-1]
if prev.Role != "user" && prev.Role != "tool" {
logger.DebugCF("agent", "Dropping assistant tool-call turn with invalid predecessor", map[string]interface{}{"prev_role": prev.Role})
logger.DebugCF(
"agent",
"Dropping assistant tool-call turn with invalid predecessor",
map[string]any{"prev_role": prev.Role},
)
continue
}
}
Expand All @@ -250,7 +262,10 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
return sanitized
}

func (cb *ContextBuilder) AddToolResult(messages []providers.Message, toolCallID, toolName, result string) []providers.Message {
func (cb *ContextBuilder) AddToolResult(
messages []providers.Message,
toolCallID, toolName, result string,
) []providers.Message {
messages = append(messages, providers.Message{
Role: "tool",
Content: result,
Expand All @@ -259,7 +274,11 @@ func (cb *ContextBuilder) AddToolResult(messages []providers.Message, toolCallID
return messages
}

func (cb *ContextBuilder) AddAssistantMessage(messages []providers.Message, content string, toolCalls []map[string]interface{}) []providers.Message {
func (cb *ContextBuilder) AddAssistantMessage(
messages []providers.Message,
content string,
toolCalls []map[string]any,
) []providers.Message {
msg := providers.Message{
Role: "assistant",
Content: content,
Expand Down Expand Up @@ -289,13 +308,13 @@ func (cb *ContextBuilder) loadSkills() string {
}

// GetSkillsInfo returns information about loaded skills.
func (cb *ContextBuilder) GetSkillsInfo() map[string]interface{} {
func (cb *ContextBuilder) GetSkillsInfo() map[string]any {
allSkills := cb.skillsLoader.ListSkills()
skillNames := make([]string, 0, len(allSkills))
for _, s := range allSkills {
skillNames = append(skillNames, s.Name)
}
return map[string]interface{}{
return map[string]any{
"total": len(allSkills),
"available": len(allSkills),
"names": skillNames,
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func NewAgentInstance(
provider providers.LLMProvider,
) *AgentInstance {
workspace := resolveAgentWorkspace(agentCfg, defaults)
os.MkdirAll(workspace, 0755)
os.MkdirAll(workspace, 0o755)

model := resolveAgentModel(agentCfg, defaults)
fallbacks := resolveAgentFallbacks(agentCfg, defaults)
Expand Down
Loading