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
9 changes: 0 additions & 9 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ linters:
- wsl_v5

# TODO: Disabled, because they are failing at the moment, we should fix them and enable (step by step)
- bodyclose
- contextcheck
- dogsled
- embeddedstructfieldcheck
- errcheck
- errchkjson
Expand All @@ -45,21 +43,16 @@ linters:
- gocritic
- gocyclo
- godox
- goprintffuncname
- gosec
- ineffassign
- lll
- maintidx
- misspell
- mnd
- modernize
- nakedret
- nestif
- nilnil
- paralleltest
- perfsprint
- prealloc
- predeclared
- revive
- staticcheck
- tagalign
Expand All @@ -68,8 +61,6 @@ linters:
- unparam
- usestdlibvars
- usetesting
- wastedassign
- whitespace
settings:
errcheck:
check-type-assertions: true
Expand Down
10 changes: 4 additions & 6 deletions cmd/picoclaw/internal/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,13 @@ func FormatVersion() string {
}

// FormatBuildInfo returns build time and go version info
func FormatBuildInfo() (build string, goVer string) {
if buildTime != "" {
build = buildTime
}
goVer = goVersion
func FormatBuildInfo() (string, string) {
build := buildTime
goVer := goVersion
if goVer == "" {
goVer = runtime.Version()
}
return
return build, goVer
}

// GetVersion returns the version string
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) {
droppedCount := mid
keptConversation := conversation[mid:]

newHistory := make([]providers.Message, 0)
newHistory := make([]providers.Message, 0, 1+len(keptConversation)+1)

// Append compression note to the original system prompt instead of adding a new system message
// This avoids having two consecutive system messages which some APIs (like Zhipu) reject
Expand Down
2 changes: 1 addition & 1 deletion pkg/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func LoginBrowser(cfg OAuthProviderConfig) (*AuthCredential, error) {
return exchangeCodeForTokens(cfg, result.code, pkce.CodeVerifier, redirectURI)
case manualInput := <-manualCh:
if manualInput == "" {
return nil, fmt.Errorf("manual input cancelled")
return nil, fmt.Errorf("manual input canceled")
}
// Extract code from URL if it's a full URL
code := manualInput
Expand Down
2 changes: 1 addition & 1 deletion pkg/channels/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
if localPath != "" {
localFiles = append(localFiles, localPath)

transcribedText := ""
var transcribedText string
if c.transcriber != nil && c.transcriber.IsAvailable() {
ctx, cancel := context.WithTimeout(c.getContext(), transcriptionTimeout)
result, err := c.transcriber.Transcribe(ctx, localPath)
Expand Down
8 changes: 5 additions & 3 deletions pkg/channels/onebot.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ func (c *OneBotChannel) connect() error {
header["Authorization"] = []string{"Bearer " + c.config.AccessToken}
}

conn, _, err := dialer.Dial(c.config.WSUrl, header)
conn, resp, err := dialer.Dial(c.config.WSUrl, header)
if resp != nil {
resp.Body.Close()
}
if err != nil {
return err
}
Expand Down Expand Up @@ -310,7 +313,7 @@ func (c *OneBotChannel) sendAPIRequest(action string, params any, timeout time.D
case <-time.After(timeout):
return nil, fmt.Errorf("API request %s timed out after %v", action, timeout)
case <-c.ctx.Done():
return nil, fmt.Errorf("context cancelled")
return nil, fmt.Errorf("context canceled")
}
}

Expand Down Expand Up @@ -695,7 +698,6 @@ func (c *OneBotChannel) parseMessageSegments(raw json.RawMessage, selfID int64)
textParts = append(textParts, "[forward message]")

default:

}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/channels/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,5 +439,5 @@ func parseSlackChatID(chatID string) (channelID, threadTS string) {
if len(parts) > 1 {
threadTS = parts[1]
}
return
return channelID, threadTS
}
2 changes: 1 addition & 1 deletion pkg/channels/telegram.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
localFiles = append(localFiles, voicePath)
mediaPaths = append(mediaPaths, voicePath)

transcribedText := ""
var transcribedText string
if c.transcriber != nil && c.transcriber.IsAvailable() {
transcriberCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
Expand Down
5 changes: 4 additions & 1 deletion pkg/channels/whatsapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ func (c *WhatsAppChannel) Start(ctx context.Context) error {
dialer := websocket.DefaultDialer
dialer.HandshakeTimeout = 10 * time.Second

conn, _, err := dialer.Dial(c.url, nil)
conn, resp, err := dialer.Dial(c.url, nil)
if resp != nil {
resp.Body.Close()
}
if err != nil {
return fmt.Errorf("failed to connect to WhatsApp bridge: %w", err)
}
Expand Down
46 changes: 23 additions & 23 deletions pkg/heartbeat/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (hs *HeartbeatService) executeHeartbeat() {
}

if handler == nil {
hs.logError("Heartbeat handler not configured")
hs.logErrorf("Heartbeat handler not configured")
return
}

Expand All @@ -175,23 +175,23 @@ func (hs *HeartbeatService) executeHeartbeat() {
channel, chatID := hs.parseLastChannel(lastChannel)

// Debug log for channel resolution
hs.logInfo("Resolved channel: %s, chatID: %s (from lastChannel: %s)", channel, chatID, lastChannel)
hs.logInfof("Resolved channel: %s, chatID: %s (from lastChannel: %s)", channel, chatID, lastChannel)

result := handler(prompt, channel, chatID)

if result == nil {
hs.logInfo("Heartbeat handler returned nil result")
hs.logInfof("Heartbeat handler returned nil result")
return
}

// Handle different result types
if result.IsError {
hs.logError("Heartbeat error: %s", result.ForLLM)
hs.logErrorf("Heartbeat error: %s", result.ForLLM)
return
}

if result.Async {
hs.logInfo("Async task started: %s", result.ForLLM)
hs.logInfof("Async task started: %s", result.ForLLM)
logger.InfoCF("heartbeat", "Async heartbeat task started",
map[string]any{
"message": result.ForLLM,
Expand All @@ -201,7 +201,7 @@ func (hs *HeartbeatService) executeHeartbeat() {

// Check if silent
if result.Silent {
hs.logInfo("Heartbeat OK - silent")
hs.logInfof("Heartbeat OK - silent")
return
}

Expand All @@ -212,7 +212,7 @@ func (hs *HeartbeatService) executeHeartbeat() {
hs.sendResponse(result.ForLLM)
}

hs.logInfo("Heartbeat completed: %s", result.ForLLM)
hs.logInfof("Heartbeat completed: %s", result.ForLLM)
}

// buildPrompt builds the heartbeat prompt from HEARTBEAT.md
Expand All @@ -225,7 +225,7 @@ func (hs *HeartbeatService) buildPrompt() string {
hs.createDefaultHeartbeatTemplate()
return ""
}
hs.logError("Error reading HEARTBEAT.md: %v", err)
hs.logErrorf("Error reading HEARTBEAT.md: %v", err)
return ""
}

Expand Down Expand Up @@ -276,9 +276,9 @@ Add your heartbeat tasks below this line:
`

if err := os.WriteFile(heartbeatPath, []byte(defaultContent), 0o644); err != nil {
hs.logError("Failed to create default HEARTBEAT.md: %v", err)
hs.logErrorf("Failed to create default HEARTBEAT.md: %v", err)
} else {
hs.logInfo("Created default HEARTBEAT.md template")
hs.logInfof("Created default HEARTBEAT.md template")
}
}

Expand All @@ -289,14 +289,14 @@ func (hs *HeartbeatService) sendResponse(response string) {
hs.mu.RUnlock()

if msgBus == nil {
hs.logInfo("No message bus configured, heartbeat result not sent")
hs.logInfof("No message bus configured, heartbeat result not sent")
return
}

// Get last channel from state
lastChannel := hs.state.GetLastChannel()
if lastChannel == "" {
hs.logInfo("No last channel recorded, heartbeat result not sent")
hs.logInfof("No last channel recorded, heartbeat result not sent")
return
}

Expand All @@ -313,7 +313,7 @@ func (hs *HeartbeatService) sendResponse(response string) {
Content: response,
})

hs.logInfo("Heartbeat result sent to %s", platform)
hs.logInfof("Heartbeat result sent to %s", platform)
}

// parseLastChannel parses the last channel string into platform and userID.
Expand All @@ -326,33 +326,33 @@ func (hs *HeartbeatService) parseLastChannel(lastChannel string) (platform, user
// Parse channel format: "platform:user_id" (e.g., "telegram:123456")
parts := strings.SplitN(lastChannel, ":", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
hs.logError("Invalid last channel format: %s", lastChannel)
hs.logErrorf("Invalid last channel format: %s", lastChannel)
return "", ""
}

platform, userID = parts[0], parts[1]

// Skip internal channels
if constants.IsInternalChannel(platform) {
hs.logInfo("Skipping internal channel: %s", platform)
hs.logInfof("Skipping internal channel: %s", platform)
return "", ""
}

return platform, userID
}

// logInfo logs an informational message to the heartbeat log
func (hs *HeartbeatService) logInfo(format string, args ...any) {
hs.log("INFO", format, args...)
// logInfof logs an informational message to the heartbeat log
func (hs *HeartbeatService) logInfof(format string, args ...any) {
hs.logf("INFO", format, args...)
}

// logError logs an error message to the heartbeat log
func (hs *HeartbeatService) logError(format string, args ...any) {
hs.log("ERROR", format, args...)
// logErrorf logs an error message to the heartbeat log
func (hs *HeartbeatService) logErrorf(format string, args ...any) {
hs.logf("ERROR", format, args...)
}

// log writes a message to the heartbeat log file
func (hs *HeartbeatService) log(level, format string, args ...any) {
// logf writes a message to the heartbeat log file
func (hs *HeartbeatService) logf(level, format string, args ...any) {
logFile := filepath.Join(hs.workspace, "heartbeat.log")
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/heartbeat/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func TestLogPath(t *testing.T) {
hs := NewHeartbeatService(tmpDir, 30, true)

// Write a log entry
hs.log("INFO", "Test log entry")
hs.logf("INFO", "Test log entry")

// Verify log file exists at workspace root
expectedLogPath := filepath.Join(tmpDir, "heartbeat.log")
Expand Down
2 changes: 1 addition & 1 deletion pkg/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func formatComponent(component string) string {
}

func formatFields(fields map[string]any) string {
var parts []string
parts := make([]string, 0, len(fields))
for k, v := range fields {
parts = append(parts, fmt.Sprintf("%s=%v", k, v))
}
Expand Down
16 changes: 10 additions & 6 deletions pkg/providers/codex_cli_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ func TestReadCodexCliCredentials_Valid(t *testing.T) {
}
}

// readCodexCliCredentialsErr calls ReadCodexCliCredentials and returns only the
// error, for tests that only need to assert on failure.
func readCodexCliCredentialsErr() error {
_, _, _, err := ReadCodexCliCredentials() //nolint:dogsled
return err
}

func TestReadCodexCliCredentials_MissingFile(t *testing.T) {
tmpDir := t.TempDir()
t.Setenv("CODEX_HOME", tmpDir)

_, _, _, err := ReadCodexCliCredentials()
if err == nil {
if err := readCodexCliCredentialsErr(); err == nil {
t.Fatal("expected error for missing auth.json")
}
}
Expand All @@ -64,8 +70,7 @@ func TestReadCodexCliCredentials_EmptyToken(t *testing.T) {

t.Setenv("CODEX_HOME", tmpDir)

_, _, _, err := ReadCodexCliCredentials()
if err == nil {
if err := readCodexCliCredentialsErr(); err == nil {
t.Fatal("expected error for empty access_token")
}
}
Expand All @@ -80,8 +85,7 @@ func TestReadCodexCliCredentials_InvalidJSON(t *testing.T) {

t.Setenv("CODEX_HOME", tmpDir)

_, _, _, err := ReadCodexCliCredentials()
if err == nil {
if err := readCodexCliCredentialsErr(); err == nil {
t.Fatal("expected error for invalid JSON")
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/providers/codex_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ func (p *CodexProvider) Chat(
if evt.Type == "response.completed" || evt.Type == "response.failed" || evt.Type == "response.incomplete" {
evtResp := evt.Response
if evtResp.ID != "" {
copy := evtResp
resp = &copy
evtRespCopy := evtResp
resp = &evtRespCopy
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/providers/github_copilot_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func NewGitHubCopilotProvider(uri string, connectMode string, model string) (*Gi
Hooks: &copilot.SessionHooks{},
})
if err != nil {

client.Stop()
return nil, fmt.Errorf("create session failed: %w", err)
}
Expand Down Expand Up @@ -101,7 +100,7 @@ func (p *GitHubCopilotProvider) Chat(
return nil, fmt.Errorf("provider closed")
}

resp, err := session.SendAndWait(ctx, copilot.MessageOptions{
resp, _ := session.SendAndWait(ctx, copilot.MessageOptions{
Prompt: string(fullcontent),
})

Expand Down
3 changes: 1 addition & 2 deletions pkg/tools/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,9 @@ func NewExecTool(workingDir string, restrict bool) *ExecTool {
func NewExecToolWithConfig(workingDir string, restrict bool, config *config.Config) *ExecTool {
denyPatterns := make([]*regexp.Regexp, 0)

enableDenyPatterns := true
if config != nil {
execConfig := config.Tools.Exec
enableDenyPatterns = execConfig.EnableDenyPatterns
enableDenyPatterns := execConfig.EnableDenyPatterns
if enableDenyPatterns {
denyPatterns = append(denyPatterns, defaultDenyPatterns...)
if len(execConfig.CustomDenyPatterns) > 0 {
Expand Down
Loading