From 18bdbef815c1dcc1e537e110417fab839c8ab09b Mon Sep 17 00:00:00 2001 From: Harshdeep Sharma <24f2006619@ds.study.iitm.ac.in> Date: Wed, 11 Feb 2026 17:39:13 +0530 Subject: [PATCH 01/23] Fix typos and update API keys in README --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1cf7173..534e5aa 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ ### ๐Ÿœ Innovative Low-Footprint Deploy PicoClaw can be deployed on almost any Linux device! -- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assitant +- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assistant - $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), or $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) for Automated Server Maintenance - $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) or $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) for Smart Monitoring @@ -144,7 +144,7 @@ picoclaw onboard "providers": { "openrouter": { "api_key": "xxx", - "api_base": "https://open.bigmodel.cn/api/paas/v4" + "api_base": "https://openrouter.ai/api/v1" } }, "tools": { @@ -165,7 +165,7 @@ picoclaw onboard > **Note**: See `config.example.json` for a complete configuration template. -**3. Chat** +**4. Chat** ```bash picoclaw agent -m "What is 2+2?" @@ -396,17 +396,17 @@ picoclaw agent -m "Hello" }, "providers": { "openrouter": { - "apiKey": "sk-or-v1-xxx" + "api_key": "sk-or-v1-xxx" }, "groq": { - "apiKey": "gsk_xxx" + "api_key": "gsk_xxx" } }, "channels": { "telegram": { "enabled": true, "token": "123456:ABC...", - "allowFrom": ["123456789"] + "allow_from": ["123456789"] }, "discord": { "enabled": true, @@ -418,11 +418,11 @@ picoclaw agent -m "Hello" }, "feishu": { "enabled": false, - "appId": "cli_xxx", - "appSecret": "xxx", - "encryptKey": "", - "verificationToken": "", - "allowFrom": [] + "app_id": "cli_xxx", + "app_secret": "xxx", + "encrypt_key": "", + "verification_token": "", + "allow_from": [] }, "qq": { "enabled": false, @@ -434,7 +434,7 @@ picoclaw agent -m "Hello" "tools": { "web": { "search": { - "apiKey": "BSA..." + "api_key": "BSA..." } } } From 6d4d2bc61e0c06e0e83dd5d91b1aac30dea9e932 Mon Sep 17 00:00:00 2001 From: yinwm Date: Wed, 11 Feb 2026 12:28:37 +0800 Subject: [PATCH 02/23] feat: add cron tool integration with agent - Add adhocore/gronx dependency for cron expression parsing - Fix CronService race conditions and add cron expression support - Add CronTool with add/list/remove/enable/disable actions - Add ContextualTool interface for tools needing channel/chatID context - Add ProcessDirectWithChannel to AgentLoop for cron job execution - Register CronTool in gateway and wire up onJob handler - Fix slice bounds panic in addJob for short messages Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/main.go | 15 ++- go.mod | 1 + go.sum | 2 + pkg/agent/loop.go | 16 ++- pkg/cron/service.go | 136 +++++++++++++++++------ pkg/tools/base.go | 7 ++ pkg/tools/cron.go | 252 ++++++++++++++++++++++++++++++++++++++++++ pkg/tools/registry.go | 9 ++ 8 files changed, 401 insertions(+), 37 deletions(-) create mode 100644 pkg/tools/cron.go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 751cdda..60dd1b9 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -27,6 +27,7 @@ import ( "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/skills" + "github.com/sipeed/picoclaw/pkg/tools" "github.com/sipeed/picoclaw/pkg/voice" ) @@ -551,8 +552,20 @@ func gatewayCmd() { }) cronStorePath := filepath.Join(filepath.Dir(getConfigPath()), "cron", "jobs.json") + + // Create cron service first (onJob handler set after CronTool creation) cronService := cron.NewCronService(cronStorePath, nil) + // Create and register CronTool + cronTool := tools.NewCronTool(cronService, agentLoop) + agentLoop.RegisterTool(cronTool) + + // Now set the onJob handler for cron service + cronService.SetOnJob(func(job *cron.CronJob) (string, error) { + result := cronTool.ExecuteJob(context.Background(), job) + return result, nil + }) + heartbeatService := heartbeat.NewHeartbeatService( cfg.WorkspacePath(), nil, @@ -745,7 +758,7 @@ func cronHelp() { func cronListCmd(storePath string) { cs := cron.NewCronService(storePath, nil) - jobs := cs.ListJobs(false) + jobs := cs.ListJobs(true) // Show all jobs, including disabled if len(jobs) == 0 { fmt.Println("No scheduled jobs.") diff --git a/go.mod b/go.mod index 23cfa0e..832f1e8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/sipeed/picoclaw go 1.24.0 require ( + github.com/adhocore/gronx v1.19.6 github.com/bwmarrin/discordgo v0.29.0 github.com/caarlos0/env/v11 v11.3.1 github.com/chzyer/readline v1.5.1 diff --git a/go.sum b/go.sum index 2f9d5be..f1ce926 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc= +github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index d38848b..3ab9b7a 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -119,11 +119,19 @@ func (al *AgentLoop) Stop() { al.running = false } +func (al *AgentLoop) RegisterTool(tool tools.Tool) { + al.tools.Register(tool) +} + func (al *AgentLoop) ProcessDirect(ctx context.Context, content, sessionKey string) (string, error) { + return al.ProcessDirectWithChannel(ctx, content, sessionKey, "cli", "direct") +} + +func (al *AgentLoop) ProcessDirectWithChannel(ctx context.Context, content, sessionKey, channel, chatID string) (string, error) { msg := bus.InboundMessage{ - Channel: "cli", - SenderID: "user", - ChatID: "direct", + Channel: channel, + SenderID: "cron", + ChatID: chatID, Content: content, SessionKey: sessionKey, } @@ -439,7 +447,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe messages = append(messages, assistantMsg) for _, tc := range response.ToolCalls { - result, err := al.tools.Execute(ctx, tc.Name, tc.Arguments) + result, err := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, msg.Channel, msg.ChatID) if err != nil { result = fmt.Sprintf("Error: %v", err) } diff --git a/pkg/cron/service.go b/pkg/cron/service.go index 54f9dcc..c85ab2b 100644 --- a/pkg/cron/service.go +++ b/pkg/cron/service.go @@ -1,12 +1,17 @@ package cron import ( + "crypto/rand" + "encoding/hex" "encoding/json" "fmt" + "log" "os" "path/filepath" "sync" "time" + + "github.com/adhocore/gronx" ) type CronSchedule struct { @@ -58,6 +63,7 @@ type CronService struct { mu sync.RWMutex running bool stopChan chan struct{} + gronx *gronx.Gronx } func NewCronService(storePath string, onJob JobHandler) *CronService { @@ -65,7 +71,9 @@ func NewCronService(storePath string, onJob JobHandler) *CronService { storePath: storePath, onJob: onJob, stopChan: make(chan struct{}), + gronx: gronx.New(), } + // Initialize and load store on creation cs.loadStore() return cs } @@ -83,7 +91,7 @@ func (cs *CronService) Start() error { } cs.recomputeNextRuns() - if err := cs.saveStore(); err != nil { + if err := cs.saveStoreUnsafe(); err != nil { return fmt.Errorf("failed to save store: %w", err) } @@ -120,30 +128,47 @@ func (cs *CronService) runLoop() { } func (cs *CronService) checkJobs() { - cs.mu.RLock() + cs.mu.Lock() + if !cs.running { - cs.mu.RUnlock() + cs.mu.Unlock() return } now := time.Now().UnixMilli() var dueJobs []*CronJob + // Collect jobs that are due (we need to copy them to execute outside lock) for i := range cs.store.Jobs { job := &cs.store.Jobs[i] if job.Enabled && job.State.NextRunAtMS != nil && *job.State.NextRunAtMS <= now { - dueJobs = append(dueJobs, job) + // Create a shallow copy of the job for execution + jobCopy := *job + dueJobs = append(dueJobs, &jobCopy) } } - cs.mu.RUnlock() + // Update next run times for due jobs immediately (before executing) + for i := range cs.store.Jobs { + for _, dueJob := range dueJobs { + if cs.store.Jobs[i].ID == dueJob.ID { + // Reset NextRunAtMS temporarily so we don't re-execute + cs.store.Jobs[i].State.NextRunAtMS = nil + break + } + } + } + + if err := cs.saveStoreUnsafe(); err != nil { + log.Printf("[cron] failed to save store: %v", err) + } + + cs.mu.Unlock() + + // Execute jobs outside the lock for _, job := range dueJobs { cs.executeJob(job) } - - cs.mu.Lock() - defer cs.mu.Unlock() - cs.saveStore() } func (cs *CronService) executeJob(job *CronJob) { @@ -154,30 +179,42 @@ func (cs *CronService) executeJob(job *CronJob) { _, err = cs.onJob(job) } + // Now acquire lock to update state cs.mu.Lock() defer cs.mu.Unlock() - job.State.LastRunAtMS = &startTime - job.UpdatedAtMS = time.Now().UnixMilli() + // Find the job in store and update it + for i := range cs.store.Jobs { + if cs.store.Jobs[i].ID == job.ID { + cs.store.Jobs[i].State.LastRunAtMS = &startTime + cs.store.Jobs[i].UpdatedAtMS = time.Now().UnixMilli() - if err != nil { - job.State.LastStatus = "error" - job.State.LastError = err.Error() - } else { - job.State.LastStatus = "ok" - job.State.LastError = "" + if err != nil { + cs.store.Jobs[i].State.LastStatus = "error" + cs.store.Jobs[i].State.LastError = err.Error() + } else { + cs.store.Jobs[i].State.LastStatus = "ok" + cs.store.Jobs[i].State.LastError = "" + } + + // Compute next run time + if cs.store.Jobs[i].Schedule.Kind == "at" { + if cs.store.Jobs[i].DeleteAfterRun { + cs.removeJobUnsafe(job.ID) + } else { + cs.store.Jobs[i].Enabled = false + cs.store.Jobs[i].State.NextRunAtMS = nil + } + } else { + nextRun := cs.computeNextRun(&cs.store.Jobs[i].Schedule, time.Now().UnixMilli()) + cs.store.Jobs[i].State.NextRunAtMS = nextRun + } + break + } } - if job.Schedule.Kind == "at" { - if job.DeleteAfterRun { - cs.removeJobUnsafe(job.ID) - } else { - job.Enabled = false - job.State.NextRunAtMS = nil - } - } else { - nextRun := cs.computeNextRun(&job.Schedule, time.Now().UnixMilli()) - job.State.NextRunAtMS = nextRun + if err := cs.saveStoreUnsafe(); err != nil { + log.Printf("[cron] failed to save store: %v", err) } } @@ -197,6 +234,23 @@ func (cs *CronService) computeNextRun(schedule *CronSchedule, nowMS int64) *int6 return &next } + if schedule.Kind == "cron" { + if schedule.Expr == "" { + return nil + } + + // Use gronx to calculate next run time + now := time.UnixMilli(nowMS) + nextTime, err := gronx.NextTickAfter(schedule.Expr, now, false) + if err != nil { + log.Printf("[cron] failed to compute next run for expr '%s': %v", schedule.Expr, err) + return nil + } + + nextMS := nextTime.UnixMilli() + return &nextMS + } + return nil } @@ -223,9 +277,17 @@ func (cs *CronService) getNextWakeMS() *int64 { } func (cs *CronService) Load() error { + cs.mu.Lock() + defer cs.mu.Unlock() return cs.loadStore() } +func (cs *CronService) SetOnJob(handler JobHandler) { + cs.mu.Lock() + defer cs.mu.Unlock() + cs.onJob = handler +} + func (cs *CronService) loadStore() error { cs.store = &CronStore{ Version: 1, @@ -243,7 +305,7 @@ func (cs *CronService) loadStore() error { return json.Unmarshal(data, cs.store) } -func (cs *CronService) saveStore() error { +func (cs *CronService) saveStoreUnsafe() error { dir := filepath.Dir(cs.storePath) if err := os.MkdirAll(dir, 0755); err != nil { return err @@ -284,7 +346,7 @@ func (cs *CronService) AddJob(name string, schedule CronSchedule, message string } cs.store.Jobs = append(cs.store.Jobs, job) - if err := cs.saveStore(); err != nil { + if err := cs.saveStoreUnsafe(); err != nil { return nil, err } @@ -310,7 +372,9 @@ func (cs *CronService) removeJobUnsafe(jobID string) bool { removed := len(cs.store.Jobs) < before if removed { - cs.saveStore() + if err := cs.saveStoreUnsafe(); err != nil { + log.Printf("[cron] failed to save store after remove: %v", err) + } } return removed @@ -332,7 +396,9 @@ func (cs *CronService) EnableJob(jobID string, enabled bool) *CronJob { job.State.NextRunAtMS = nil } - cs.saveStore() + if err := cs.saveStoreUnsafe(); err != nil { + log.Printf("[cron] failed to save store after enable: %v", err) + } return job } } @@ -377,5 +443,11 @@ func (cs *CronService) Status() map[string]interface{} { } func generateID() string { - return fmt.Sprintf("%d", time.Now().UnixNano()) + // Use crypto/rand for better uniqueness under concurrent access + b := make([]byte, 8) + if _, err := rand.Read(b); err != nil { + // Fallback to time-based if crypto/rand fails + return fmt.Sprintf("%d", time.Now().UnixNano()) + } + return hex.EncodeToString(b) } diff --git a/pkg/tools/base.go b/pkg/tools/base.go index 1bf53f7..095ac69 100644 --- a/pkg/tools/base.go +++ b/pkg/tools/base.go @@ -9,6 +9,13 @@ type Tool interface { Execute(ctx context.Context, args map[string]interface{}) (string, error) } +// ContextualTool is an optional interface that tools can implement +// to receive the current message context (channel, chatID) +type ContextualTool interface { + Tool + SetContext(channel, chatID string) +} + func ToolToSchema(tool Tool) map[string]interface{} { return map[string]interface{}{ "type": "function", diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go new file mode 100644 index 0000000..65c97ce --- /dev/null +++ b/pkg/tools/cron.go @@ -0,0 +1,252 @@ +package tools + +import ( + "context" + "fmt" + "sync" + + "github.com/sipeed/picoclaw/pkg/cron" +) + +func truncateString(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen] +} + +// JobExecutor is the interface for executing cron jobs through the agent +type JobExecutor interface { + ProcessDirectWithChannel(ctx context.Context, content, sessionKey, channel, chatID string) (string, error) +} + +// CronTool provides scheduling capabilities for the agent +type CronTool struct { + cronService *cron.CronService + executor JobExecutor + channel string + chatID string + mu sync.RWMutex +} + +// NewCronTool creates a new CronTool +func NewCronTool(cronService *cron.CronService, executor JobExecutor) *CronTool { + return &CronTool{ + cronService: cronService, + executor: executor, + } +} + +// Name returns the tool name +func (t *CronTool) Name() string { + return "cron" +} + +// Description returns the tool description +func (t *CronTool) Description() string { + return "Schedule reminders and recurring tasks. Actions: add, list, remove, enable, disable." +} + +// Parameters returns the tool parameters schema +func (t *CronTool) Parameters() map[string]interface{} { + return map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "action": map[string]interface{}{ + "type": "string", + "enum": []string{"add", "list", "remove", "enable", "disable"}, + "description": "Action to perform", + }, + "message": map[string]interface{}{ + "type": "string", + "description": "Reminder message (for add)", + }, + "every_seconds": map[string]interface{}{ + "type": "integer", + "description": "Interval in seconds for recurring tasks", + }, + "cron_expr": map[string]interface{}{ + "type": "string", + "description": "Cron expression like '0 9 * * *' for scheduled tasks", + }, + "job_id": map[string]interface{}{ + "type": "string", + "description": "Job ID (for remove/enable/disable)", + }, + }, + "required": []string{"action"}, + } +} + +// SetContext sets the current session context for job creation +func (t *CronTool) SetContext(channel, chatID string) { + t.mu.Lock() + defer t.mu.Unlock() + t.channel = channel + t.chatID = chatID +} + +// Execute runs the tool with given arguments +func (t *CronTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) { + action, ok := args["action"].(string) + if !ok { + return "", fmt.Errorf("action is required") + } + + switch action { + case "add": + return t.addJob(args) + case "list": + return t.listJobs() + case "remove": + return t.removeJob(args) + case "enable": + return t.enableJob(args, true) + case "disable": + return t.enableJob(args, false) + default: + return "", fmt.Errorf("unknown action: %s", action) + } +} + +func (t *CronTool) addJob(args map[string]interface{}) (string, error) { + t.mu.RLock() + channel := t.channel + chatID := t.chatID + t.mu.RUnlock() + + if channel == "" || chatID == "" { + return "Error: no session context (channel/chat_id not set). Use this tool in an active conversation.", nil + } + + message, ok := args["message"].(string) + if !ok || message == "" { + return "Error: message is required for add", nil + } + + var schedule cron.CronSchedule + + // Check for every_seconds + everySeconds, hasEvery := args["every_seconds"].(float64) + cronExpr, hasCron := args["cron_expr"].(string) + + if !hasEvery && !hasCron { + return "Error: either every_seconds or cron_expr is required", nil + } + + if hasEvery { + everyMS := int64(everySeconds) * 1000 + schedule = cron.CronSchedule{ + Kind: "every", + EveryMS: &everyMS, + } + } else { + schedule = cron.CronSchedule{ + Kind: "cron", + Expr: cronExpr, + } + } + + job, err := t.cronService.AddJob( + truncateString(message, 30), + schedule, + message, + true, // deliver + channel, + chatID, + ) + if err != nil { + return fmt.Sprintf("Error adding job: %v", err), nil + } + + return fmt.Sprintf("Created job '%s' (id: %s)", job.Name, job.ID), nil +} + +func (t *CronTool) listJobs() (string, error) { + jobs := t.cronService.ListJobs(false) + + if len(jobs) == 0 { + return "No scheduled jobs.", nil + } + + result := "Scheduled jobs:\n" + for _, j := range jobs { + var scheduleInfo string + if j.Schedule.Kind == "every" && j.Schedule.EveryMS != nil { + scheduleInfo = fmt.Sprintf("every %ds", *j.Schedule.EveryMS/1000) + } else if j.Schedule.Kind == "cron" { + scheduleInfo = j.Schedule.Expr + } else if j.Schedule.Kind == "at" { + scheduleInfo = "one-time" + } else { + scheduleInfo = "unknown" + } + result += fmt.Sprintf("- %s (id: %s, %s)\n", j.Name, j.ID, scheduleInfo) + } + + return result, nil +} + +func (t *CronTool) removeJob(args map[string]interface{}) (string, error) { + jobID, ok := args["job_id"].(string) + if !ok || jobID == "" { + return "Error: job_id is required for remove", nil + } + + if t.cronService.RemoveJob(jobID) { + return fmt.Sprintf("Removed job %s", jobID), nil + } + return fmt.Sprintf("Job %s not found", jobID), nil +} + +func (t *CronTool) enableJob(args map[string]interface{}, enable bool) (string, error) { + jobID, ok := args["job_id"].(string) + if !ok || jobID == "" { + return "Error: job_id is required for enable/disable", nil + } + + job := t.cronService.EnableJob(jobID, enable) + if job == nil { + return fmt.Sprintf("Job %s not found", jobID), nil + } + + status := "enabled" + if !enable { + status = "disabled" + } + return fmt.Sprintf("Job '%s' %s", job.Name, status), nil +} + +// ExecuteJob executes a cron job through the agent +func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { + // Get channel/chatID from job payload + channel := job.Payload.Channel + chatID := job.Payload.To + + // Default values if not set + if channel == "" { + channel = "cli" + } + if chatID == "" { + chatID = "direct" + } + + sessionKey := fmt.Sprintf("cron-%s", job.ID) + + // Call agent with the job's message + response, err := t.executor.ProcessDirectWithChannel( + ctx, + job.Payload.Message, + sessionKey, + channel, + chatID, + ) + + if err != nil { + return fmt.Sprintf("Error: %v", err) + } + + // Response is automatically sent via MessageBus by AgentLoop + _ = response // Will be sent by AgentLoop + return "ok" +} diff --git a/pkg/tools/registry.go b/pkg/tools/registry.go index d181944..a769664 100644 --- a/pkg/tools/registry.go +++ b/pkg/tools/registry.go @@ -34,6 +34,10 @@ func (r *ToolRegistry) Get(name string) (Tool, bool) { } func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string]interface{}) (string, error) { + return r.ExecuteWithContext(ctx, name, args, "", "") +} + +func (r *ToolRegistry) ExecuteWithContext(ctx context.Context, name string, args map[string]interface{}, channel, chatID string) (string, error) { logger.InfoCF("tool", "Tool execution started", map[string]interface{}{ "tool": name, @@ -49,6 +53,11 @@ func (r *ToolRegistry) Execute(ctx context.Context, name string, args map[string return "", fmt.Errorf("tool '%s' not found", name) } + // If tool implements ContextualTool, set context + if contextualTool, ok := tool.(ContextualTool); ok && channel != "" && chatID != "" { + contextualTool.SetContext(channel, chatID) + } + start := time.Now() result, err := tool.Execute(ctx, args) duration := time.Since(start) From 4bc9e2d768a2863669b89e0fdb8b1526860dab6a Mon Sep 17 00:00:00 2001 From: yinwm Date: Wed, 11 Feb 2026 18:43:21 +0800 Subject: [PATCH 03/23] fix(cron): add one-time reminders and fix data paths to workspace - Add at_seconds parameter for one-time reminders (e.g., "remind me in 10 minutes") - Update every_seconds description to emphasize recurring-only usage - Route cron delivery: deliver=true sends directly, deliver=false uses agent - Fix cron data path from ~/.picoclaw/cron to workspace/cron - Fix sessions path from workspace/../sessions to workspace/sessions Co-Authored-By: Claude Opus 4.6 --- .gitignore | 16 +++++++++-- cmd/picoclaw/main.go | 45 +++++++++++++++++++----------- pkg/agent/context.go | 10 +++++-- pkg/agent/loop.go | 34 +++++++++++++++++++---- pkg/channels/base.go | 5 ++-- pkg/session/manager.go | 14 +++++++--- pkg/tools/cron.go | 63 ++++++++++++++++++++++++++++++++---------- 7 files changed, 141 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index dacb665..6ad4d78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Binaries bin/ *.exe *.dll @@ -5,12 +6,21 @@ bin/ *.dylib *.test *.out +/picoclaw +/picoclaw-test + +# Picoclaw specific .picoclaw/ config.json sessions/ +build/ + +# Coverage coverage.txt coverage.html -.DS_Store -build -picoclaw +# OS +.DS_Store + +# Ralph workspace +ralph/ diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 60dd1b9..0b13bc7 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -551,20 +551,8 @@ func gatewayCmd() { "skills_available": skillsInfo["available"], }) - cronStorePath := filepath.Join(filepath.Dir(getConfigPath()), "cron", "jobs.json") - - // Create cron service first (onJob handler set after CronTool creation) - cronService := cron.NewCronService(cronStorePath, nil) - - // Create and register CronTool - cronTool := tools.NewCronTool(cronService, agentLoop) - agentLoop.RegisterTool(cronTool) - - // Now set the onJob handler for cron service - cronService.SetOnJob(func(job *cron.CronJob) (string, error) { - result := cronTool.ExecuteJob(context.Background(), job) - return result, nil - }) + // Setup cron tool and service + cronService, _ := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath()) heartbeatService := heartbeat.NewHeartbeatService( cfg.WorkspacePath(), @@ -702,6 +690,25 @@ func getConfigPath() string { return filepath.Join(home, ".picoclaw", "config.json") } +func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string) (*cron.CronService, *tools.CronTool) { + cronStorePath := filepath.Join(workspace, "cron", "jobs.json") + + // Create cron service + cronService := cron.NewCronService(cronStorePath, nil) + + // Create and register CronTool + cronTool := tools.NewCronTool(cronService, agentLoop, msgBus) + agentLoop.RegisterTool(cronTool) + + // Set the onJob handler + cronService.SetOnJob(func(job *cron.CronJob) (string, error) { + result := cronTool.ExecuteJob(context.Background(), job) + return result, nil + }) + + return cronService, cronTool +} + func loadConfig() (*config.Config, error) { return config.LoadConfig(getConfigPath()) } @@ -714,8 +721,14 @@ func cronCmd() { subcommand := os.Args[2] - dataDir := filepath.Join(filepath.Dir(getConfigPath()), "cron") - cronStorePath := filepath.Join(dataDir, "jobs.json") + // Load config to get workspace path + cfg, err := loadConfig() + if err != nil { + fmt.Printf("Error loading config: %v\n", err) + return + } + + cronStorePath := filepath.Join(cfg.WorkspacePath(), "cron", "jobs.json") switch subcommand { case "list": diff --git a/pkg/agent/context.go b/pkg/agent/context.go index 7e8612e..d1b3397 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -69,8 +69,13 @@ Your workspace is at: %s %s -Always be helpful, accurate, and concise. When using tools, explain what you're doing. -When remembering something, write to %s/memory/MEMORY.md`, +## Important Rules + +1. **ALWAYS use tools** - When you need to perform an action (schedule reminders, send messages, execute commands, etc.), you MUST call the appropriate tool. Do NOT just say you'll do it or pretend to do it. + +2. **Be helpful and accurate** - When using tools, briefly explain what you're doing. + +3. **Memory** - When remembering something, write to %s/memory/MEMORY.md`, now, runtime, workspacePath, workspacePath, workspacePath, workspacePath, toolsSection, workspacePath) } @@ -86,6 +91,7 @@ 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("You have access to the following tools:\n\n") for _, s := range summaries { sb.WriteString(s) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 3ab9b7a..439428a 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -69,7 +69,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers editFileTool := tools.NewEditFileTool(workspace) toolsRegistry.Register(editFileTool) - sessionsManager := session.NewSessionManager(filepath.Join(filepath.Dir(cfg.WorkspacePath()), "sessions")) + sessionsManager := session.NewSessionManager(filepath.Join(workspace, "sessions")) return &AgentLoop{ bus: msgBus, @@ -179,6 +179,9 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) msg.ChatID, ) + // Save user message to session + al.sessions.AddMessage(msg.SessionKey, "user", msg.Content) + iteration := 0 var finalContent string @@ -277,6 +280,9 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) } messages = append(messages, assistantMsg) + // Save assistant message with tool calls to session + al.sessions.AddFullMessage(msg.SessionKey, assistantMsg) + for _, tc := range response.ToolCalls { // Log tool call with arguments preview argsJSON, _ := json.Marshal(tc.Arguments) @@ -287,7 +293,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) "iteration": iteration, }) - result, err := al.tools.Execute(ctx, tc.Name, tc.Arguments) + result, err := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, msg.Channel, msg.ChatID) if err != nil { result = fmt.Sprintf("Error: %v", err) } @@ -298,6 +304,9 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) ToolCallID: tc.ID, } messages = append(messages, toolResultMsg) + + // Save tool result message to session + al.sessions.AddFullMessage(msg.SessionKey, toolResultMsg) } } @@ -305,7 +314,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) finalContent = "I've completed processing but have no response to give." } - al.sessions.AddMessage(msg.SessionKey, "user", msg.Content) + // Save final assistant message to session al.sessions.AddMessage(msg.SessionKey, "assistant", finalContent) al.sessions.Save(al.sessions.GetOrCreate(msg.SessionKey)) @@ -370,6 +379,9 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe originChatID, ) + // Save user message to session with system message marker + al.sessions.AddMessage(sessionKey, "user", fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content)) + iteration := 0 var finalContent string @@ -446,6 +458,9 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe } messages = append(messages, assistantMsg) + // Save assistant message with tool calls to session + al.sessions.AddFullMessage(sessionKey, assistantMsg) + for _, tc := range response.ToolCalls { result, err := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, msg.Channel, msg.ChatID) if err != nil { @@ -458,6 +473,9 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe ToolCallID: tc.ID, } messages = append(messages, toolResultMsg) + + // Save tool result message to session + al.sessions.AddFullMessage(sessionKey, toolResultMsg) } } @@ -465,8 +483,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe finalContent = "Background task completed." } - // Save to session with system message marker - al.sessions.AddMessage(sessionKey, "user", fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content)) + // Save final assistant message to session al.sessions.AddMessage(sessionKey, "assistant", finalContent) al.sessions.Save(al.sessions.GetOrCreate(sessionKey)) @@ -476,6 +493,13 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe "final_length": len(finalContent), }) + // Send response back to the original channel + al.bus.PublishOutbound(bus.OutboundMessage{ + Channel: originChannel, + ChatID: originChatID, + Content: finalContent, + }) + return finalContent, nil } diff --git a/pkg/channels/base.go b/pkg/channels/base.go index 5361191..8abe7b5 100644 --- a/pkg/channels/base.go +++ b/pkg/channels/base.go @@ -61,7 +61,7 @@ func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []st return } - // ็”Ÿๆˆ SessionKey: channel:chatID + // Build session key: channel:chatID sessionKey := fmt.Sprintf("%s:%s", c.name, chatID) msg := bus.InboundMessage{ @@ -70,8 +70,9 @@ func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []st ChatID: chatID, Content: content, Media: media, - Metadata: metadata, SessionKey: sessionKey, + Metadata: metadata, + } } c.bus.PublishInbound(msg) diff --git a/pkg/session/manager.go b/pkg/session/manager.go index df86724..b4b8257 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -59,6 +59,15 @@ func (sm *SessionManager) GetOrCreate(key string) *Session { } func (sm *SessionManager) AddMessage(sessionKey, role, content string) { + sm.AddFullMessage(sessionKey, providers.Message{ + Role: role, + Content: content, + }) +} + +// AddFullMessage adds a complete message with tool calls and tool call ID to the session. +// This is used to save the full conversation flow including tool calls and tool results. +func (sm *SessionManager) AddFullMessage(sessionKey string, msg providers.Message) { sm.mu.Lock() defer sm.mu.Unlock() @@ -72,10 +81,7 @@ func (sm *SessionManager) AddMessage(sessionKey, role, content string) { sm.sessions[sessionKey] = session } - session.Messages = append(session.Messages, providers.Message{ - Role: role, - Content: content, - }) + session.Messages = append(session.Messages, msg) session.Updated = time.Now() } diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 65c97ce..87aaf35 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "sync" + "time" + "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/cron" ) @@ -24,16 +26,18 @@ type JobExecutor interface { type CronTool struct { cronService *cron.CronService executor JobExecutor + msgBus *bus.MessageBus channel string chatID string mu sync.RWMutex } // NewCronTool creates a new CronTool -func NewCronTool(cronService *cron.CronService, executor JobExecutor) *CronTool { +func NewCronTool(cronService *cron.CronService, executor JobExecutor, msgBus *bus.MessageBus) *CronTool { return &CronTool{ cronService: cronService, executor: executor, + msgBus: msgBus, } } @@ -44,7 +48,7 @@ func (t *CronTool) Name() string { // Description returns the tool description func (t *CronTool) Description() string { - return "Schedule reminders and recurring tasks. Actions: add, list, remove, enable, disable." + return "Schedule reminders and tasks. IMPORTANT: When user asks to be reminded or scheduled, you MUST call this tool. Use 'at_seconds' for one-time reminders (e.g., 'remind me in 10 minutes' โ†’ at_seconds=600). Use 'every_seconds' ONLY for recurring tasks (e.g., 'every 2 hours' โ†’ every_seconds=7200). Use 'cron_expr' for complex recurring schedules (e.g., '0 9 * * *' for daily at 9am)." } // Parameters returns the tool parameters schema @@ -55,24 +59,32 @@ func (t *CronTool) Parameters() map[string]interface{} { "action": map[string]interface{}{ "type": "string", "enum": []string{"add", "list", "remove", "enable", "disable"}, - "description": "Action to perform", + "description": "Action to perform. Use 'add' when user wants to schedule a reminder or task.", }, "message": map[string]interface{}{ "type": "string", - "description": "Reminder message (for add)", + "description": "The reminder/task message to display when triggered (required for add)", + }, + "at_seconds": map[string]interface{}{ + "type": "integer", + "description": "One-time reminder: seconds from now when to trigger (e.g., 600 for 10 minutes later). Use this for one-time reminders like 'remind me in 10 minutes'.", }, "every_seconds": map[string]interface{}{ "type": "integer", - "description": "Interval in seconds for recurring tasks", + "description": "Recurring interval in seconds (e.g., 3600 for every hour). Use this ONLY for recurring tasks like 'every 2 hours' or 'daily reminder'.", }, "cron_expr": map[string]interface{}{ "type": "string", - "description": "Cron expression like '0 9 * * *' for scheduled tasks", + "description": "Cron expression for complex recurring schedules (e.g., '0 9 * * *' for daily at 9am). Use this for complex recurring schedules.", }, "job_id": map[string]interface{}{ "type": "string", "description": "Job ID (for remove/enable/disable)", }, + "deliver": map[string]interface{}{ + "type": "boolean", + "description": "If true, send message directly to channel. If false, let agent process the message (for complex tasks). Default: true", + }, }, "required": []string{"action"}, } @@ -126,32 +138,44 @@ func (t *CronTool) addJob(args map[string]interface{}) (string, error) { var schedule cron.CronSchedule - // Check for every_seconds + // Check for at_seconds (one-time), every_seconds (recurring), or cron_expr + atSeconds, hasAt := args["at_seconds"].(float64) everySeconds, hasEvery := args["every_seconds"].(float64) cronExpr, hasCron := args["cron_expr"].(string) - if !hasEvery && !hasCron { - return "Error: either every_seconds or cron_expr is required", nil - } - - if hasEvery { + // Priority: at_seconds > every_seconds > cron_expr + if hasAt { + atMS := time.Now().UnixMilli() + int64(atSeconds)*1000 + schedule = cron.CronSchedule{ + Kind: "at", + AtMS: &atMS, + } + } else if hasEvery { everyMS := int64(everySeconds) * 1000 schedule = cron.CronSchedule{ Kind: "every", EveryMS: &everyMS, } - } else { + } else if hasCron { schedule = cron.CronSchedule{ Kind: "cron", Expr: cronExpr, } + } else { + return "Error: one of at_seconds, every_seconds, or cron_expr is required", nil + } + + // Read deliver parameter, default to true + deliver := true + if d, ok := args["deliver"].(bool); ok { + deliver = d } job, err := t.cronService.AddJob( truncateString(message, 30), schedule, message, - true, // deliver + deliver, channel, chatID, ) @@ -231,6 +255,17 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { chatID = "direct" } + // If deliver=true, send message directly without agent processing + if job.Payload.Deliver { + t.msgBus.PublishOutbound(bus.OutboundMessage{ + Channel: channel, + ChatID: chatID, + Content: job.Payload.Message, + }) + return "ok" + } + + // For deliver=false, process through agent (for complex tasks) sessionKey := fmt.Sprintf("cron-%s", job.ID) // Call agent with the job's message From af60ce26fc895b0d2e816e714e9b9130311886c1 Mon Sep 17 00:00:00 2001 From: yinwm Date: Wed, 11 Feb 2026 19:27:36 +0800 Subject: [PATCH 04/23] refactor(agent): improve code quality and restore summarization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code review fixes: - Use map for O(n) job lookup in cron service (was O(nยฒ)) - Set DeleteAfterRun=true for one-time cron tasks - Restore context compression/summarization to prevent context overflow - Add pkg/utils/string.go with Unicode-aware Truncate function - Simplify setupCronTool to return only CronService - Change Chinese comments to English in context.go Refactoring: - Replace toolsSummary callback with SetToolsRegistry setter pattern - This makes dependency injection clearer and easier to test Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/main.go | 6 +- pkg/agent/context.go | 19 ++++--- pkg/agent/loop.go | 132 ++++++++++++++++++++++++++++++++++++++++++- pkg/cron/service.go | 19 ++++--- pkg/utils/string.go | 16 ++++++ 5 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 pkg/utils/string.go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 0b13bc7..c14ec58 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -552,7 +552,7 @@ func gatewayCmd() { }) // Setup cron tool and service - cronService, _ := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath()) + cronService := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath()) heartbeatService := heartbeat.NewHeartbeatService( cfg.WorkspacePath(), @@ -690,7 +690,7 @@ func getConfigPath() string { return filepath.Join(home, ".picoclaw", "config.json") } -func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string) (*cron.CronService, *tools.CronTool) { +func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string) *cron.CronService { cronStorePath := filepath.Join(workspace, "cron", "jobs.json") // Create cron service @@ -706,7 +706,7 @@ func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace return result, nil }) - return cronService, cronTool + return cronService } func loadConfig() (*config.Config, error) { diff --git a/pkg/agent/context.go b/pkg/agent/context.go index d1b3397..e737fbd 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -11,13 +11,14 @@ import ( "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/skills" + "github.com/sipeed/picoclaw/pkg/tools" ) type ContextBuilder struct { workspace string skillsLoader *skills.SkillsLoader memory *MemoryStore - toolsSummary func() []string // Function to get tool summaries dynamically + tools *tools.ToolRegistry // Direct reference to tool registry } func getGlobalConfigDir() string { @@ -28,9 +29,9 @@ func getGlobalConfigDir() string { return filepath.Join(home, ".picoclaw") } -func NewContextBuilder(workspace string, toolsSummaryFunc func() []string) *ContextBuilder { - // builtin skills: ๅฝ“ๅ‰้กน็›ฎ็š„ skills ็›ฎๅฝ• - // ไฝฟ็”จๅฝ“ๅ‰ๅทฅไฝœ็›ฎๅฝ•ไธ‹็š„ skills/ ็›ฎๅฝ• +func NewContextBuilder(workspace string) *ContextBuilder { + // builtin skills: skills directory in current project + // Use the skills/ directory under the current working directory wd, _ := os.Getwd() builtinSkillsDir := filepath.Join(wd, "skills") globalSkillsDir := filepath.Join(getGlobalConfigDir(), "skills") @@ -39,10 +40,14 @@ func NewContextBuilder(workspace string, toolsSummaryFunc func() []string) *Cont workspace: workspace, skillsLoader: skills.NewSkillsLoader(workspace, globalSkillsDir, builtinSkillsDir), memory: NewMemoryStore(workspace), - toolsSummary: toolsSummaryFunc, } } +// SetToolsRegistry sets the tools registry for dynamic tool summary generation. +func (cb *ContextBuilder) SetToolsRegistry(registry *tools.ToolRegistry) { + cb.tools = registry +} + func (cb *ContextBuilder) getIdentity() string { now := time.Now().Format("2006-01-02 15:04 (Monday)") workspacePath, _ := filepath.Abs(filepath.Join(cb.workspace)) @@ -80,11 +85,11 @@ Your workspace is at: %s } func (cb *ContextBuilder) buildToolsSection() string { - if cb.toolsSummary == nil { + if cb.tools == nil { return "" } - summaries := cb.toolsSummary() + summaries := cb.tools.GetSummaries() if len(summaries) == 0 { return "" } diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 439428a..5cdd6a7 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -13,6 +13,8 @@ import ( "os" "path/filepath" "strings" + "sync" + "time" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" @@ -27,11 +29,13 @@ type AgentLoop struct { provider providers.LLMProvider workspace string model string + contextWindow int // Maximum context window size in tokens maxIterations int sessions *session.SessionManager contextBuilder *ContextBuilder tools *tools.ToolRegistry running bool + summarizing sync.Map // Tracks which sessions are currently being summarized } func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers.LLMProvider) *AgentLoop { @@ -71,16 +75,22 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers sessionsManager := session.NewSessionManager(filepath.Join(workspace, "sessions")) + // Create context builder and set tools registry + contextBuilder := NewContextBuilder(workspace) + contextBuilder.SetToolsRegistry(toolsRegistry) + return &AgentLoop{ bus: msgBus, provider: provider, workspace: workspace, model: cfg.Agents.Defaults.Model, + contextWindow: cfg.Agents.Defaults.MaxTokens, // Restore context window for summarization maxIterations: cfg.Agents.Defaults.MaxToolIterations, sessions: sessionsManager, - contextBuilder: NewContextBuilder(workspace, func() []string { return toolsRegistry.GetSummaries() }), + contextBuilder: contextBuilder, tools: toolsRegistry, running: false, + summarizing: sync.Map{}, } } @@ -318,6 +328,21 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) al.sessions.AddMessage(msg.SessionKey, "assistant", finalContent) al.sessions.Save(al.sessions.GetOrCreate(msg.SessionKey)) + // Context compression: Check if we need to summarize + // Trigger if history > 20 messages OR estimated tokens > 75% of context window + newHistory := al.sessions.GetHistory(msg.SessionKey) + tokenEstimate := al.estimateTokens(newHistory) + threshold := al.contextWindow * 75 / 100 + + if len(newHistory) > 20 || tokenEstimate > threshold { + if _, loading := al.summarizing.LoadOrStore(msg.SessionKey, true); !loading { + go func() { + defer al.summarizing.Delete(msg.SessionKey) + al.summarizeSession(msg.SessionKey) + }() + } + } + // Log response preview responsePreview := truncate(finalContent, 120) logger.InfoCF("agent", fmt.Sprintf("Response to %s:%s: %s", msg.Channel, msg.SenderID, responsePreview), @@ -595,3 +620,108 @@ func truncateString(s string, maxLen int) string { } return s[:maxLen-3] + "..." } + +// summarizeSession summarizes the conversation history for a session. +func (al *AgentLoop) summarizeSession(sessionKey string) { + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + history := al.sessions.GetHistory(sessionKey) + summary := al.sessions.GetSummary(sessionKey) + + // Keep last 4 messages for continuity + if len(history) <= 4 { + return + } + + toSummarize := history[:len(history)-4] + + // Oversized Message Guard + // Skip messages larger than 50% of context window to prevent summarizer overflow + maxMessageTokens := al.contextWindow / 2 + validMessages := make([]providers.Message, 0) + omitted := false + + for _, m := range toSummarize { + if m.Role != "user" && m.Role != "assistant" { + continue + } + // Estimate tokens for this message + msgTokens := len(m.Content) / 4 + if msgTokens > maxMessageTokens { + omitted = true + continue + } + validMessages = append(validMessages, m) + } + + if len(validMessages) == 0 { + return + } + + // Multi-Part Summarization + // Split into two parts if history is significant + var finalSummary string + if len(validMessages) > 10 { + mid := len(validMessages) / 2 + part1 := validMessages[:mid] + part2 := validMessages[mid:] + + s1, _ := al.summarizeBatch(ctx, part1, "") + s2, _ := al.summarizeBatch(ctx, part2, "") + + // Merge them + mergePrompt := fmt.Sprintf("Merge these two conversation summaries into one cohesive summary:\n\n1: %s\n\n2: %s", s1, s2) + resp, err := al.provider.Chat(ctx, []providers.Message{{Role: "user", Content: mergePrompt}}, nil, al.model, map[string]interface{}{ + "max_tokens": 1024, + "temperature": 0.3, + }) + if err == nil { + finalSummary = resp.Content + } else { + finalSummary = s1 + " " + s2 + } + } else { + finalSummary, _ = al.summarizeBatch(ctx, validMessages, summary) + } + + if omitted && finalSummary != "" { + finalSummary += "\n[Note: Some oversized messages were omitted from this summary for efficiency.]" + } + + if finalSummary != "" { + al.sessions.SetSummary(sessionKey, finalSummary) + al.sessions.TruncateHistory(sessionKey, 4) + al.sessions.Save(al.sessions.GetOrCreate(sessionKey)) + } +} + +// summarizeBatch summarizes a batch of messages. +func (al *AgentLoop) summarizeBatch(ctx context.Context, batch []providers.Message, existingSummary string) (string, error) { + prompt := "Provide a concise summary of this conversation segment, preserving core context and key points.\n" + if existingSummary != "" { + prompt += "Existing context: " + existingSummary + "\n" + } + prompt += "\nCONVERSATION:\n" + for _, m := range batch { + prompt += fmt.Sprintf("%s: %s\n", m.Role, m.Content) + } + + response, err := al.provider.Chat(ctx, []providers.Message{{Role: "user", Content: prompt}}, nil, al.model, map[string]interface{}{ + "max_tokens": 1024, + "temperature": 0.3, + }) + if err != nil { + return "", err + } + return response.Content, nil +} + +// estimateTokens estimates the number of tokens in a message list. +func (al *AgentLoop) estimateTokens(messages []providers.Message) int { + total := 0 + for _, m := range messages { + total += len(m.Content) / 4 // Simple heuristic: 4 chars per token + } + return total +} diff --git a/pkg/cron/service.go b/pkg/cron/service.go index c85ab2b..9434ed8 100644 --- a/pkg/cron/service.go +++ b/pkg/cron/service.go @@ -149,13 +149,15 @@ func (cs *CronService) checkJobs() { } // Update next run times for due jobs immediately (before executing) + // Use map for O(n) lookup instead of O(nยฒ) nested loop + dueMap := make(map[string]bool, len(dueJobs)) + for _, job := range dueJobs { + dueMap[job.ID] = true + } for i := range cs.store.Jobs { - for _, dueJob := range dueJobs { - if cs.store.Jobs[i].ID == dueJob.ID { - // Reset NextRunAtMS temporarily so we don't re-execute - cs.store.Jobs[i].State.NextRunAtMS = nil - break - } + if dueMap[cs.store.Jobs[i].ID] { + // Reset NextRunAtMS temporarily so we don't re-execute + cs.store.Jobs[i].State.NextRunAtMS = nil } } @@ -325,6 +327,9 @@ func (cs *CronService) AddJob(name string, schedule CronSchedule, message string now := time.Now().UnixMilli() + // One-time tasks (at) should be deleted after execution + deleteAfterRun := (schedule.Kind == "at") + job := CronJob{ ID: generateID(), Name: name, @@ -342,7 +347,7 @@ func (cs *CronService) AddJob(name string, schedule CronSchedule, message string }, CreatedAtMS: now, UpdatedAtMS: now, - DeleteAfterRun: false, + DeleteAfterRun: deleteAfterRun, } cs.store.Jobs = append(cs.store.Jobs, job) diff --git a/pkg/utils/string.go b/pkg/utils/string.go new file mode 100644 index 0000000..0d9837c --- /dev/null +++ b/pkg/utils/string.go @@ -0,0 +1,16 @@ +package utils + +// Truncate returns a truncated version of s with at most maxLen runes. +// Handles multi-byte Unicode characters properly. +// If the string is truncated, "..." is appended to indicate truncation. +func Truncate(s string, maxLen int) string { + runes := []rune(s) + if len(runes) <= maxLen { + return s + } + // Reserve 3 chars for "..." + if maxLen <= 3 { + return string(runes[:maxLen]) + } + return string(runes[:maxLen-3]) + "..." +} From c704990ceca03b8d2c97518c96e15f90cace6d1a Mon Sep 17 00:00:00 2001 From: yinwm Date: Wed, 11 Feb 2026 20:22:41 +0800 Subject: [PATCH 05/23] refactor(tools): remove duplicate truncate functions and add docs - Remove duplicate truncate/truncateString functions from loop.go and cron.go - Use utils.Truncate consistently across codebase - Add Workspace Layout section to README - Document cron/scheduled tasks functionality Co-Authored-By: Claude Opus 4.6 --- README.md | 29 ++++ pkg/agent/loop.go | 435 ++++++++++++++++++---------------------------- pkg/tools/cron.go | 13 +- 3 files changed, 204 insertions(+), 273 deletions(-) diff --git a/README.md b/README.md index 1cf7173..70e06ac 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,23 @@ picoclaw gateway Config file: `~/.picoclaw/config.json` +### Workspace Layout + +PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspace`): + +``` +~/.picoclaw/workspace/ +โ”œโ”€โ”€ sessions/ # Conversation sessions and history +โ”œโ”€โ”€ memory/ # Long-term memory (MEMORY.md) +โ”œโ”€โ”€ cron/ # Scheduled jobs database +โ”œโ”€โ”€ skills/ # Custom skills +โ”œโ”€โ”€ AGENTS.md # Agent behavior guide +โ”œโ”€โ”€ IDENTITY.md # Agent identity +โ”œโ”€โ”€ SOUL.md # Agent soul +โ”œโ”€โ”€ TOOLS.md # Tool descriptions +โ””โ”€โ”€ USER.md # User preferences +``` + ### Providers > [!NOTE] @@ -452,6 +469,18 @@ picoclaw agent -m "Hello" | `picoclaw agent` | Interactive chat mode | | `picoclaw gateway` | Start the gateway | | `picoclaw status` | Show status | +| `picoclaw cron list` | List all scheduled jobs | +| `picoclaw cron add ...` | Add a scheduled job | + +### Scheduled Tasks / Reminders + +PicoClaw supports scheduled reminders and recurring tasks through the `cron` tool: + +- **One-time reminders**: "Remind me in 10 minutes" โ†’ triggers once after 10min +- **Recurring tasks**: "Remind me every 2 hours" โ†’ triggers every 2 hours +- **Cron expressions**: "Remind me at 9am daily" โ†’ uses cron expression + +Jobs are stored in `~/.picoclaw/workspace/cron/` and processed automatically. ## ๐Ÿค Contribute & Roadmap diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 5cdd6a7..40c9ba7 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -22,6 +22,7 @@ import ( "github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/session" "github.com/sipeed/picoclaw/pkg/tools" + "github.com/sipeed/picoclaw/pkg/utils" ) type AgentLoop struct { @@ -38,6 +39,17 @@ type AgentLoop struct { summarizing sync.Map // Tracks which sessions are currently being summarized } +// processOptions configures how a message is processed +type processOptions struct { + SessionKey string // Session identifier for history/context + Channel string // Target channel for tool execution + ChatID string // Target chat ID for tool execution + UserMessage string // User message content (may include prefix) + DefaultResponse string // Response when LLM returns empty + EnableSummary bool // Whether to trigger summarization + SendResponse bool // Whether to send response via bus +} + func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers.LLMProvider) *AgentLoop { workspace := cfg.WorkspacePath() os.MkdirAll(workspace, 0755) @@ -151,7 +163,7 @@ func (al *AgentLoop) ProcessDirectWithChannel(ctx context.Context, content, sess func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) (string, error) { // Add message preview to log - preview := truncate(msg.Content, 80) + preview := utils.Truncate(msg.Content, 80) logger.InfoCF("agent", fmt.Sprintf("Processing message from %s:%s: %s", msg.Channel, msg.SenderID, preview), map[string]interface{}{ "channel": msg.Channel, @@ -165,193 +177,16 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) return al.processSystemMessage(ctx, msg) } - // Update tool contexts - if tool, ok := al.tools.Get("message"); ok { - if mt, ok := tool.(*tools.MessageTool); ok { - mt.SetContext(msg.Channel, msg.ChatID) - } - } - if tool, ok := al.tools.Get("spawn"); ok { - if st, ok := tool.(*tools.SpawnTool); ok { - st.SetContext(msg.Channel, msg.ChatID) - } - } - - history := al.sessions.GetHistory(msg.SessionKey) - summary := al.sessions.GetSummary(msg.SessionKey) - - messages := al.contextBuilder.BuildMessages( - history, - summary, - msg.Content, - nil, - msg.Channel, - msg.ChatID, - ) - - // Save user message to session - al.sessions.AddMessage(msg.SessionKey, "user", msg.Content) - - iteration := 0 - var finalContent string - - for iteration < al.maxIterations { - iteration++ - - logger.DebugCF("agent", "LLM iteration", - map[string]interface{}{ - "iteration": iteration, - "max": al.maxIterations, - }) - - toolDefs := al.tools.GetDefinitions() - providerToolDefs := make([]providers.ToolDefinition, 0, len(toolDefs)) - for _, td := range toolDefs { - providerToolDefs = append(providerToolDefs, providers.ToolDefinition{ - Type: td["type"].(string), - Function: providers.ToolFunctionDefinition{ - Name: td["function"].(map[string]interface{})["name"].(string), - Description: td["function"].(map[string]interface{})["description"].(string), - Parameters: td["function"].(map[string]interface{})["parameters"].(map[string]interface{}), - }, - }) - } - - // Log LLM request details - logger.DebugCF("agent", "LLM request", - map[string]interface{}{ - "iteration": iteration, - "model": al.model, - "messages_count": len(messages), - "tools_count": len(providerToolDefs), - "max_tokens": 8192, - "temperature": 0.7, - "system_prompt_len": len(messages[0].Content), - }) - - // Log full messages (detailed) - logger.DebugCF("agent", "Full LLM request", - map[string]interface{}{ - "iteration": iteration, - "messages_json": formatMessagesForLog(messages), - "tools_json": formatToolsForLog(providerToolDefs), - }) - - response, err := al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{ - "max_tokens": 8192, - "temperature": 0.7, - }) - - if err != nil { - logger.ErrorCF("agent", "LLM call failed", - map[string]interface{}{ - "iteration": iteration, - "error": err.Error(), - }) - return "", fmt.Errorf("LLM call failed: %w", err) - } - - if len(response.ToolCalls) == 0 { - finalContent = response.Content - logger.InfoCF("agent", "LLM response without tool calls (direct answer)", - map[string]interface{}{ - "iteration": iteration, - "content_chars": len(finalContent), - }) - break - } - - toolNames := make([]string, 0, len(response.ToolCalls)) - for _, tc := range response.ToolCalls { - toolNames = append(toolNames, tc.Name) - } - logger.InfoCF("agent", "LLM requested tool calls", - map[string]interface{}{ - "tools": toolNames, - "count": len(toolNames), - "iteration": iteration, - }) - - assistantMsg := providers.Message{ - Role: "assistant", - Content: response.Content, - } - - for _, tc := range response.ToolCalls { - argumentsJSON, _ := json.Marshal(tc.Arguments) - assistantMsg.ToolCalls = append(assistantMsg.ToolCalls, providers.ToolCall{ - ID: tc.ID, - Type: "function", - Function: &providers.FunctionCall{ - Name: tc.Name, - Arguments: string(argumentsJSON), - }, - }) - } - messages = append(messages, assistantMsg) - - // Save assistant message with tool calls to session - al.sessions.AddFullMessage(msg.SessionKey, assistantMsg) - - for _, tc := range response.ToolCalls { - // Log tool call with arguments preview - argsJSON, _ := json.Marshal(tc.Arguments) - argsPreview := truncate(string(argsJSON), 200) - logger.InfoCF("agent", fmt.Sprintf("Tool call: %s(%s)", tc.Name, argsPreview), - map[string]interface{}{ - "tool": tc.Name, - "iteration": iteration, - }) - - result, err := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, msg.Channel, msg.ChatID) - if err != nil { - result = fmt.Sprintf("Error: %v", err) - } - - toolResultMsg := providers.Message{ - Role: "tool", - Content: result, - ToolCallID: tc.ID, - } - messages = append(messages, toolResultMsg) - - // Save tool result message to session - al.sessions.AddFullMessage(msg.SessionKey, toolResultMsg) - } - } - - if finalContent == "" { - finalContent = "I've completed processing but have no response to give." - } - - // Save final assistant message to session - al.sessions.AddMessage(msg.SessionKey, "assistant", finalContent) - al.sessions.Save(al.sessions.GetOrCreate(msg.SessionKey)) - - // Context compression: Check if we need to summarize - // Trigger if history > 20 messages OR estimated tokens > 75% of context window - newHistory := al.sessions.GetHistory(msg.SessionKey) - tokenEstimate := al.estimateTokens(newHistory) - threshold := al.contextWindow * 75 / 100 - - if len(newHistory) > 20 || tokenEstimate > threshold { - if _, loading := al.summarizing.LoadOrStore(msg.SessionKey, true); !loading { - go func() { - defer al.summarizing.Delete(msg.SessionKey) - al.summarizeSession(msg.SessionKey) - }() - } - } - - // Log response preview - responsePreview := truncate(finalContent, 120) - logger.InfoCF("agent", fmt.Sprintf("Response to %s:%s: %s", msg.Channel, msg.SenderID, responsePreview), - map[string]interface{}{ - "iterations": iteration, - "final_length": len(finalContent), - }) - - return finalContent, nil + // Process as user message + return al.runAgentLoop(ctx, processOptions{ + SessionKey: msg.SessionKey, + Channel: msg.Channel, + ChatID: msg.ChatID, + UserMessage: msg.Content, + DefaultResponse: "I've completed processing but have no response to give.", + EnableSummary: true, + SendResponse: false, + }) } func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMessage) (string, error) { @@ -380,39 +215,96 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe // Use the origin session for context sessionKey := fmt.Sprintf("%s:%s", originChannel, originChatID) - // Update tool contexts to original channel/chatID - if tool, ok := al.tools.Get("message"); ok { - if mt, ok := tool.(*tools.MessageTool); ok { - mt.SetContext(originChannel, originChatID) - } - } - if tool, ok := al.tools.Get("spawn"); ok { - if st, ok := tool.(*tools.SpawnTool); ok { - st.SetContext(originChannel, originChatID) - } - } + // Process as system message with routing back to origin + return al.runAgentLoop(ctx, processOptions{ + SessionKey: sessionKey, + Channel: originChannel, + ChatID: originChatID, + UserMessage: fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content), + DefaultResponse: "Background task completed.", + EnableSummary: false, + SendResponse: true, // Send response back to original channel + }) +} - // Build messages with the announce content - history := al.sessions.GetHistory(sessionKey) - summary := al.sessions.GetSummary(sessionKey) +// runAgentLoop is the core message processing logic. +// It handles context building, LLM calls, tool execution, and response handling. +func (al *AgentLoop) runAgentLoop(ctx context.Context, opts processOptions) (string, error) { + // 1. Update tool contexts + al.updateToolContexts(opts.Channel, opts.ChatID) + + // 2. Build messages + history := al.sessions.GetHistory(opts.SessionKey) + summary := al.sessions.GetSummary(opts.SessionKey) messages := al.contextBuilder.BuildMessages( history, summary, - msg.Content, + opts.UserMessage, nil, - originChannel, - originChatID, + opts.Channel, + opts.ChatID, ) - // Save user message to session with system message marker - al.sessions.AddMessage(sessionKey, "user", fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content)) + // 3. Save user message to session + al.sessions.AddMessage(opts.SessionKey, "user", opts.UserMessage) + // 4. Run LLM iteration loop + finalContent, iteration, err := al.runLLMIteration(ctx, messages, opts) + if err != nil { + return "", err + } + + // 5. Handle empty response + if finalContent == "" { + finalContent = opts.DefaultResponse + } + + // 6. Save final assistant message to session + al.sessions.AddMessage(opts.SessionKey, "assistant", finalContent) + al.sessions.Save(al.sessions.GetOrCreate(opts.SessionKey)) + + // 7. Optional: summarization + if opts.EnableSummary { + al.maybeSummarize(opts.SessionKey) + } + + // 8. Optional: send response via bus + if opts.SendResponse { + al.bus.PublishOutbound(bus.OutboundMessage{ + Channel: opts.Channel, + ChatID: opts.ChatID, + Content: finalContent, + }) + } + + // 9. Log response + responsePreview := utils.Truncate(finalContent, 120) + logger.InfoCF("agent", fmt.Sprintf("Response: %s", responsePreview), + map[string]interface{}{ + "session_key": opts.SessionKey, + "iterations": iteration, + "final_length": len(finalContent), + }) + + return finalContent, nil +} + +// runLLMIteration executes the LLM call loop with tool handling. +// Returns the final content, iteration count, and any error. +func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.Message, opts processOptions) (string, int, error) { iteration := 0 var finalContent string for iteration < al.maxIterations { iteration++ + logger.DebugCF("agent", "LLM iteration", + map[string]interface{}{ + "iteration": iteration, + "max": al.maxIterations, + }) + + // Build tool definitions toolDefs := al.tools.GetDefinitions() providerToolDefs := make([]providers.ToolDefinition, 0, len(toolDefs)) for _, td := range toolDefs { @@ -429,12 +321,12 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe // Log LLM request details logger.DebugCF("agent", "LLM request", map[string]interface{}{ - "iteration": iteration, - "model": al.model, - "messages_count": len(messages), - "tools_count": len(providerToolDefs), - "max_tokens": 8192, - "temperature": 0.7, + "iteration": iteration, + "model": al.model, + "messages_count": len(messages), + "tools_count": len(providerToolDefs), + "max_tokens": 8192, + "temperature": 0.7, "system_prompt_len": len(messages[0].Content), }) @@ -446,30 +338,49 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe "tools_json": formatToolsForLog(providerToolDefs), }) + // Call LLM response, err := al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{ "max_tokens": 8192, "temperature": 0.7, }) if err != nil { - logger.ErrorCF("agent", "LLM call failed in system message", + logger.ErrorCF("agent", "LLM call failed", map[string]interface{}{ "iteration": iteration, "error": err.Error(), }) - return "", fmt.Errorf("LLM call failed: %w", err) + return "", iteration, fmt.Errorf("LLM call failed: %w", err) } + // Check if no tool calls - we're done if len(response.ToolCalls) == 0 { finalContent = response.Content + logger.InfoCF("agent", "LLM response without tool calls (direct answer)", + map[string]interface{}{ + "iteration": iteration, + "content_chars": len(finalContent), + }) break } + // Log tool calls + toolNames := make([]string, 0, len(response.ToolCalls)) + for _, tc := range response.ToolCalls { + toolNames = append(toolNames, tc.Name) + } + logger.InfoCF("agent", "LLM requested tool calls", + map[string]interface{}{ + "tools": toolNames, + "count": len(toolNames), + "iteration": iteration, + }) + + // Build assistant message with tool calls assistantMsg := providers.Message{ Role: "assistant", Content: response.Content, } - for _, tc := range response.ToolCalls { argumentsJSON, _ := json.Marshal(tc.Arguments) assistantMsg.ToolCalls = append(assistantMsg.ToolCalls, providers.ToolCall{ @@ -484,10 +395,20 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe messages = append(messages, assistantMsg) // Save assistant message with tool calls to session - al.sessions.AddFullMessage(sessionKey, assistantMsg) + al.sessions.AddFullMessage(opts.SessionKey, assistantMsg) + // Execute tool calls for _, tc := range response.ToolCalls { - result, err := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, msg.Channel, msg.ChatID) + // Log tool call with arguments preview + argsJSON, _ := json.Marshal(tc.Arguments) + argsPreview := utils.Truncate(string(argsJSON), 200) + logger.InfoCF("agent", fmt.Sprintf("Tool call: %s(%s)", tc.Name, argsPreview), + map[string]interface{}{ + "tool": tc.Name, + "iteration": iteration, + }) + + result, err := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, opts.Channel, opts.ChatID) if err != nil { result = fmt.Sprintf("Error: %v", err) } @@ -500,46 +421,41 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe messages = append(messages, toolResultMsg) // Save tool result message to session - al.sessions.AddFullMessage(sessionKey, toolResultMsg) + al.sessions.AddFullMessage(opts.SessionKey, toolResultMsg) } } - if finalContent == "" { - finalContent = "Background task completed." - } - - // Save final assistant message to session - al.sessions.AddMessage(sessionKey, "assistant", finalContent) - al.sessions.Save(al.sessions.GetOrCreate(sessionKey)) - - logger.InfoCF("agent", "System message processing completed", - map[string]interface{}{ - "iterations": iteration, - "final_length": len(finalContent), - }) - - // Send response back to the original channel - al.bus.PublishOutbound(bus.OutboundMessage{ - Channel: originChannel, - ChatID: originChatID, - Content: finalContent, - }) - - return finalContent, nil + return finalContent, iteration, nil } -// truncate returns a truncated version of s with at most maxLen characters. -// If the string is truncated, "..." is appended to indicate truncation. -// If the string fits within maxLen, it is returned unchanged. -func truncate(s string, maxLen int) string { - if len(s) <= maxLen { - return s +// updateToolContexts updates the context for tools that need channel/chatID info. +func (al *AgentLoop) updateToolContexts(channel, chatID string) { + if tool, ok := al.tools.Get("message"); ok { + if mt, ok := tool.(*tools.MessageTool); ok { + mt.SetContext(channel, chatID) + } } - // Reserve 3 chars for "..." - if maxLen <= 3 { - return s[:maxLen] + if tool, ok := al.tools.Get("spawn"); ok { + if st, ok := tool.(*tools.SpawnTool); ok { + st.SetContext(channel, chatID) + } + } +} + +// maybeSummarize triggers summarization if the session history exceeds thresholds. +func (al *AgentLoop) maybeSummarize(sessionKey string) { + newHistory := al.sessions.GetHistory(sessionKey) + tokenEstimate := al.estimateTokens(newHistory) + threshold := al.contextWindow * 75 / 100 + + if len(newHistory) > 20 || tokenEstimate > threshold { + if _, loading := al.summarizing.LoadOrStore(sessionKey, true); !loading { + go func() { + defer al.summarizing.Delete(sessionKey) + al.summarizeSession(sessionKey) + }() + } } - return s[:maxLen-3] + "..." } // GetStartupInfo returns information about loaded tools and skills for logging. @@ -574,12 +490,12 @@ func formatMessagesForLog(messages []providers.Message) string { for _, tc := range msg.ToolCalls { result += fmt.Sprintf(" - ID: %s, Type: %s, Name: %s\n", tc.ID, tc.Type, tc.Name) if tc.Function != nil { - result += fmt.Sprintf(" Arguments: %s\n", truncateString(tc.Function.Arguments, 200)) + result += fmt.Sprintf(" Arguments: %s\n", utils.Truncate(tc.Function.Arguments, 200)) } } } if msg.Content != "" { - content := truncateString(msg.Content, 200) + content := utils.Truncate(msg.Content, 200) result += fmt.Sprintf(" Content: %s\n", content) } if msg.ToolCallID != "" { @@ -603,24 +519,13 @@ func formatToolsForLog(tools []providers.ToolDefinition) string { result += fmt.Sprintf(" [%d] Type: %s, Name: %s\n", i, tool.Type, tool.Function.Name) result += fmt.Sprintf(" Description: %s\n", tool.Function.Description) if len(tool.Function.Parameters) > 0 { - result += fmt.Sprintf(" Parameters: %s\n", truncateString(fmt.Sprintf("%v", tool.Function.Parameters), 200)) + result += fmt.Sprintf(" Parameters: %s\n", utils.Truncate(fmt.Sprintf("%v", tool.Function.Parameters), 200)) } } result += "]" return result } -// truncateString truncates a string to max length -func truncateString(s string, maxLen int) string { - if len(s) <= maxLen { - return s - } - if maxLen <= 3 { - return s[:maxLen] - } - return s[:maxLen-3] + "..." -} - // summarizeSession summarizes the conversation history for a session. func (al *AgentLoop) summarizeSession(sessionKey string) { ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 87aaf35..53570a3 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -8,15 +8,9 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/cron" + "github.com/sipeed/picoclaw/pkg/utils" ) -func truncateString(s string, maxLen int) string { - if len(s) <= maxLen { - return s - } - return s[:maxLen] -} - // JobExecutor is the interface for executing cron jobs through the agent type JobExecutor interface { ProcessDirectWithChannel(ctx context.Context, content, sessionKey, channel, chatID string) (string, error) @@ -171,8 +165,11 @@ func (t *CronTool) addJob(args map[string]interface{}) (string, error) { deliver = d } + // Truncate message for job name (max 30 chars) + messagePreview := utils.Truncate(message, 30) + job, err := t.cronService.AddJob( - truncateString(message, 30), + messagePreview, schedule, message, deliver, From ae9cfc2f317a79da57a4b75a93695751d7c9efd8 Mon Sep 17 00:00:00 2001 From: Harshdeep Sharma <24f2006619@ds.study.iitm.ac.in> Date: Wed, 11 Feb 2026 20:41:40 +0800 Subject: [PATCH 06/23] Fix typos and update API keys in README --- Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index d2c9456..9cc2354 100644 --- a/Makefile +++ b/Makefile @@ -162,13 +162,12 @@ help: @echo "" @echo "Examples:" @echo " make build # Build for current platform" - @echo " make install # Install to /usr/local/bin" - @echo " make install-user # Install to ~/.local/bin" + @echo " make install # Install to ~/.local/bin" @echo " make uninstall # Remove from /usr/local/bin" @echo " make install-skills # Install skills to workspace" @echo "" @echo "Environment Variables:" - @echo " INSTALL_PREFIX # Installation prefix (default: /usr/local)" + @echo " INSTALL_PREFIX # Installation prefix (default: ~/.local)" @echo " WORKSPACE_DIR # Workspace directory (default: ~/.picoclaw/workspace)" @echo " VERSION # Version string (default: git describe)" @echo "" From 9ec84c6630a6b28845c4589b2ff8eca54d4aaf57 Mon Sep 17 00:00:00 2001 From: Harshdeep Sharma <24f2006619@ds.study.iitm.ac.in> Date: Wed, 11 Feb 2026 17:39:13 +0530 Subject: [PATCH 07/23] Fix typos and update API keys in README --- README.md | 24 ++++++++++++------------ pkg/channels/base.go | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 70e06ac..9778918 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ ### ๐Ÿœ Innovative Low-Footprint Deploy PicoClaw can be deployed on almost any Linux device! -- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assitant +- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assistant - $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), or $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) for Automated Server Maintenance - $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) or $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) for Smart Monitoring @@ -144,7 +144,7 @@ picoclaw onboard "providers": { "openrouter": { "api_key": "xxx", - "api_base": "https://open.bigmodel.cn/api/paas/v4" + "api_base": "https://openrouter.ai/api/v1" } }, "tools": { @@ -165,7 +165,7 @@ picoclaw onboard > **Note**: See `config.example.json` for a complete configuration template. -**3. Chat** +**4. Chat** ```bash picoclaw agent -m "What is 2+2?" @@ -413,17 +413,17 @@ picoclaw agent -m "Hello" }, "providers": { "openrouter": { - "apiKey": "sk-or-v1-xxx" + "api_key": "sk-or-v1-xxx" }, "groq": { - "apiKey": "gsk_xxx" + "api_key": "gsk_xxx" } }, "channels": { "telegram": { "enabled": true, "token": "123456:ABC...", - "allowFrom": ["123456789"] + "allow_from": ["123456789"] }, "discord": { "enabled": true, @@ -435,11 +435,11 @@ picoclaw agent -m "Hello" }, "feishu": { "enabled": false, - "appId": "cli_xxx", - "appSecret": "xxx", - "encryptKey": "", - "verificationToken": "", - "allowFrom": [] + "app_id": "cli_xxx", + "app_secret": "xxx", + "encrypt_key": "", + "verification_token": "", + "allow_from": [] }, "qq": { "enabled": false, @@ -451,7 +451,7 @@ picoclaw agent -m "Hello" "tools": { "web": { "search": { - "apiKey": "BSA..." + "api_key": "BSA..." } } } diff --git a/pkg/channels/base.go b/pkg/channels/base.go index 8abe7b5..3ade400 100644 --- a/pkg/channels/base.go +++ b/pkg/channels/base.go @@ -73,7 +73,6 @@ func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []st SessionKey: sessionKey, Metadata: metadata, } - } c.bus.PublishInbound(msg) } From 4f51441a3b5b6e51e4398a3a20f1247aae16b797 Mon Sep 17 00:00:00 2001 From: zepan Date: Wed, 11 Feb 2026 22:46:28 +0800 Subject: [PATCH 08/23] 1. update wechat group qrcode --- assets/wechat.png | Bin 141241 -> 142251 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/wechat.png b/assets/wechat.png index 61b329e2a5d3af640a0f52d09c03abc6a0bc9151..30e096258f5643f67544b143a4af4624aa144410 100644 GIT binary patch literal 142251 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
    f^fT&6 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�FagOyyuFd1B@7Z4Od$IsBg}i=Z59#_gMu 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

    {`u!R658BAjg^ zT5n?8;!11Vs03TagOlB!N3PLHGdbAK*o9Mrh$*Ix5VG^nK|W-0HE{_Rv*QE36dyLy z5H$jk#?(8ck5UYz@KYzdo`AIYeW5mSRK{=KHnB5dsF#qIS|j**p#v-it`UEP6u~2~ z{Jlo-X-%Q-+3w%d$y#GitaU}*OFMLKcSm%{5}h(rYj5i59RRLW9E7e#zu z3A=uce==P0%1Y3!^K7W&*C6#VIaOa3Jq{|hEfv&TgR1Heza;M(tD1#M!%2iPAtBxW z#$zPSx}vgfRpXmVoC8AIip+kQ<&#s&r%PNX{6O(D)Fhz1xeY5yZNiplo2{1^397E1 zXw`>@Q-_;kU*GI)ZfI#i3FSIuKY(}XM=SI)K`y3MV9VkpDz~<|y3K5fO1gVYy7$}T zH_-F>)qpc&Sojh#^fF*Mde)>GeNC+N#S6Bt0tP8H3E{+$!o-IDBV_re$`9hPyNpDK zG`&2bYSL4!eB11G5lm&rZ*p%nn97vik8I4P42YP2sin*rR(L?R>=y?W!QMo5bMqz6 zj*NE*J~#gO-1#HM371e9s*swxnfRC6A3Kd^i8BSGDa71@G>gq=XXa~bBxkLl*S@tV z-XT)U|8r_mzcY$yV_Xcf9+z#=w3$SfN~FBX;22EL46103)k6Dz?o;9dvq}HUlQD-* z+Bv~kX`@bY1-U)Wh^ODI_53;CyoyO5@>;uIxs{)sg(r(&2CahXwhjpu{xN$ zICI7eE)_7EAQ#MIJ|*G9!Lx&FBHlCkm~uBoj?_Gc_vwhX!8;H%$oNp{25VxD5%HMK zr+}pHQ4SmHQE!FV+8Y1N1~ZR>1jej^;&!4P(qOVPE)ZSFzFvf;Qn2`ml;d#9)r-EE z7P1nrC{(&NEH3T?tdvgssm!+{6=#3d{<6hunn1;qQ z?Vdw*)=Z3>)de62?ey2kki6`;w$L`R+EEI)Lazxoe+Gpfl2%=ej*rhn&{)7~H6kMQ z)?aUesz2s8yD-U$NcKz}?pM|>4wvrK8r-BrgCD@%? z`5NDh|LIQa?bD`Z2>yJm^hOqcg+SC-ynNsh~%#RXadT~|A9kQ33uyuH?V^%*s}V1N4#nUVkv0z3_9 zQgI&m_$ul0PY%oUBox2?ayMt3@Vw3@t8JkH4VxM&cl7M^!)fEO2ZkRis3IR%Q&R_E zf7rE=ES*hDU^R^!e_U|klO9dvA0owGJWpe3S24TQ(UC)wUKE3;b8JcAV++bdp+p1gzR0*&5pq>X9i-Ys` z-M%NLp1Cor4^0Dfsz?=oldH>&$YgTb;^a2|1L2W7*X-gUhia5KO?(Q(KNLtbBD&RT ze@82DC6UHXo#$rK3(j^vI4SSwV@3MFkZy_!>H{;>%h7_pP zEl|n)QcTy-Sot?eC4L2b4Lc{^g&oLCV38#uBzW<&b_Df{nqUjFEJEK2ed&|Z z=Oez34G^yYN5#=czuy`C&-7oBA%s&BkqZy)1yA{>Jbii>=6dHqMz4cvCeu5g+FqmY zXsp^3^?Io)H@o7>uxiK^#2cgGoW;TCp8nsoodw3Y%K4=Rx%j^))8ntB!(GNT(KC^#y1=GstxEQLY11dZ(eDZ%F z>Yr=~RF}^pWVISQ-q+(Ap5~t)<&KHyz{>(p>)OK^0Spu)#D-BEA-lI(srCPk#r->t z>%qD=tSm@$`C2d+co(8VPH0r-loGW-wU!58rO90Vw^%bq!)o@sxIEC@pL6wBx~`;K zxDK%|nv$=JHmTX7;v-m>j~Axnd6IeYaTv3kiDm>#(pX*r~UiVC}PO)ilmAY6I8O|h(%4?jMOzJ#S3Qg1y0kZhR!!4KX+;;eSE3@cP<`t zm_t)k0(5QQzeZYsbm8?e34$zdmaAL}x!dYnE#Ep;#b+=5S^1ItNX5Sf6KL%OH}Tx> zaXlc{78I}PHGd`X2nzA1KqP$rbrit}Q1j zuVQ|?{B^{2VzZ-PsRrH|KeDp^V^9-l=Y<}B#GtbOd@cP-DA|2`X4XyAr#&X~qvm+)-5d`L zpXKnwG8UX2wx+T6i;MK;l&^!v`bf2Z0ZYR*sqbg@!FOL}*Ga_5?JD#Js3m{hs`vHf znc042SN|5(sFiY8;C%;_msN-)+u~rD0@W%q0j8Zd!DzR_;m=L1P9*qJe;dbK38Vu@ z*u{AERe}b9?+nh3OHqQg$zQ603T$N2+^T$!Q$cC;gO;RJ;$O@VRO(C?KNF-`s0N}a z3R3PPMF$l!j2~}Avi4)Rv@I6TE1{<4o2^RYugX-6o2o0>U7q~)J^b*8U+;Fxz2z)A zEgBN69EO_?I~w}}YFIFDiH~}`ST8IkIN12s;M(o(@M{9LKqqI2O9C;F65RMYl;y!{ zT?%Q9rTNp(gsM;GA0(k0Dx+ML`__nM=J7WGgM*zHdFIgNvqEtLhj@%+7^dEji!jzuTOl%#*e-F zZKfqw-dx=J?sJFh?IrZo8mhn_9{0e8<6^2+bb9APu;=Tqt&_Smd7NiIr^z2#P4Cq2 zpY!bxl&3!jiDK(xmhq`8IgPmdZTZ|Q^aAx%e2vgBGp1+SHxeKrZ^mqxLw<%xU`dfZ zN;TW6*0`yG@kM?{a2a)GqalNFY2D*t&ulV+2imDs7;g@%_qk=)Fj=8g_c?>+xpl3cx(M-5x#<)wd}R#i^EXY z+qspwFi)l3VaFR5n&k3M%jpO1v9O+j`Bp!QJv_uE3c9TuSI9 za)MSRmMq%7s=ls5_ntOomMf4WX(!I9(`5dc;5vjNF{24k@U&+l+$DC+h?lf@Qrj%0 zsq#+@dluxn`kw!ms~yf3FxFr3J^9Yl%ERZw35N!$U7j-cN}0*ILU1oNg_T%U8`&Xe z;jx4Z{7;MrgCW+iyOPWML86G|X4%t3wpl@)v4L+yI$UJdg+=A5I4f*YbOFAlU^AV4 z_;@&h;4Rp#z#v=Pal`MSg5wqGL@k;JKYm=@?;f3*=w&ZV@)>^gPg>_5%BU#~%dQ*r zRg*tm`0+-r;)wq?N@E=81xSMQmzTIvNPMiNFmG+-xMFVVu(SCjvciaH#D7O>{!%c+ zXu46@gW!N?dBj)?nM~Izj#Fs`!pAw%6CTiO852DYYbS0Y0Ub2=Hb1PgzWj-4Yg1K2 zb@FQLy2Cs7`%L`4Fa1?joj_RE&H_enl*Q#F;C_bB&-cSa>2!U6{Pm-r{kp9I`g7Jl z7cWL&qy+6{7>o6G2!Gm$oP2^Cqa^IZlj>WdhOE7#K^wEJiI>~qPgJk95>|gdPR@A) z{4xYwKVIc;fKNiYAxt_D2xQr`Dg+fGGVQ~|wKZ#!S7L2#R+o9AW;OKniB45_ZrF?D zu8MX=B^)DB1oC1iOF`e|$hpe7KFz^I%3#9(?qp`T(FWO^(3b0Do5N^Cz-2p^&c$LW zdZSi5?Z-@y8;Sp?2xTR&c{|WZr@m~Yr-J~#B_ms7>I1_|tt~lsaqrj><>MCVO)`BMq>eddM;sbVRlTwAmi2YKikuxF8{YQU zsc$uqR%w$J0vv(b;;Cjy%x%q5xj7;3_P#IC&U5V}3zRFLGgt<6^ksttrq#Huw%Mbi z3u?{F$7~a?aUce$ch_2*?YK%-KkK2aZ%s-RHaB9aiyb}>B~)#Wc^N*x0#S`(Ix^gG zLN!*yB7dYedb5NZvUI;C_)n})ec+k7*p!J?2ZcBhe@nyZt#UfrC)lIobV#{}a8l&xTS5f1I9DHbyF-(+{Gmo<@d`Vm*emqR zs-j6YwU|@WaaeVB*RbBobVT`1ZP9WH?(p}#?90PSs$BJjrN~hOJkq3G0dun~FgHj7 zd$XXxU?{# zE)E9@ek~BJd3b;BgZ5hYiXOM2_(HJfFJS$hzJc}bd<5TES*%>yw#={n-t>>uz;=VC zKpZbw*6Xh_b4ne(B1nLU*g#$Ch>sX~HUXokykI&yX@?P81K2|BtX$SDW267_#4B23 zj?OmE7S>J0h7#o&GjyxwK^Rr4zFpn&@&1oNY5IN&h|`veLT_S%6hQ@@)7t_8e0Sn``j3O;1l za+3Q|*7}fOLus-h)Wy~Q%U1hhcJ*HUH+U2fZAAH~I;@o=3v`p$Un)rI=HDGr{#>lI4~zjIj~hS& z+P00vbg(Krg2?u0WC-)84g^h&2hIF^!4U3&WQy5jahjteuVaN6XimRWHZ{G}&s(#c zS#QsNjXF3nWWRef<*;}D^y7+Kxe>oS3AOfZgoTkll>a4 zxb@(>*Hv9bvJA*6H*lq-;jK!7v?pZEUvb3wG*56p!1-T1xZPhZ0DIPIhT&X2l#0N{ zTT5eQALhq^AB!?=`+1dSI1FSH;Nq}t(P+H+!MI+q33}1WrQK>t*Zm)T*>GW2&hl#< z`F3Gp6N#h{Pa_as1WSQWJsw9Ve7O2{!O>|{2x5OG8WbQ@>XX5dx)UxYY!lMpr zo=J&JT|d9Vx%vAmclP`e%oE8-Qo!fdi(9JZNy`5obMs;#_NFbjVXgZwnf`W8%y`Qu z%u>g&Yp*g{r0v1^&zgE=(W^hl5eqSoi>a3XBpnB3r7y#M*R zv{F~4toe=f$TcI~Ck!1>@Pz!f-Hvefc&!YC$B%S=LY3t5blu47(iVLW4-}X|g&lz5 z^xd9YU4Ws~ZcXgxG}VCzu~1}pw8iGUeL%Pl|F7vyw6uEn!p6ifrr!Z zn`KYPsarvwBK=79-#2o5cjDdEW_TXhc>FKAH2&ND0cY7|JFqdw#~?QwN2B&%jKjt5 z=2uuTYt*Pn+O~6ht=e%#$*na?>@;Am{w#ylN*u)n((%gZQX=__6Pq4?19=;@uoobo zj5$kotmc!x);g8atK%lx?lq$K`N?|pN>IMAOiZSokQz-6Xr-{5)ILW%EnVvIxl)X6 z`1pY&Yk8IKSNJu45HV(cF@_*LZi@~Pn5ZoyKI|8W{b4>xIw1{VTIjk8*-(3W#r74L z8?}O*iR$@*k1$MsJSiJ3YWLATP3O5k=k3gc0(F5)$N<=P$lvYDPIWoph>g?gSI!2R zL|p2sx?T-^@~xJ%RaYAM^UIE@9WR4(!1^S`)|9kZoax36EUpP9=e%DHx-k!Vn8W#& z%=#F|#YprgdzbV8W`o^`e>xqc>TgRjS4!XYYL~n7W5<#cDOUVLiHZxyu6V8XGO0lh+;1`R(33q}JFy;-%}$p|Qo!ox$)) zJxjE}x}e@Nq2F?lkfBn7EQ!b^>hq4S1ImK%gyadxrwq4G0$ox zufCvVdH;mkJ1gK7Qsul5stYEUD(6@0p7 z5{4{lg}n@;=_jHA^|O|tD9S#S|j z5VoUI2}>|L{}`lKCef=({bMUV$<=?o2m0`ymDN86?Y0~EcbsQNWH~`*=bxd5XbB#d z*{MiGLBHd-d+t)+{N31UpC_cY>c^uY?+XrOmRX_+99U{pxAOU3CU2$geb3n@opG7o zvE?;W2$>M{2+4{6i~j6|K~G1cgDsArF2GV_kq{h#o z%L4Y%^)XX!)+f(v2~!nc?t%4i7Js7%l_W!Z8R*_@^q8^fVPD2~6>JU{TlL!^IQzXR zt60p5JNKI9{|g6c-_d}arNj~r@Pp_w-#_T|*pRUj>tv6jCl=i$x9;j-l9L%p+n0%s zx4d-aW<&ZH1*QypJ_V~Dt2Brr#nu?Q1PXrQZai01>LB~Rk)q6?&PCR3I8)sd(3U9I z@<^*?e66ajDP@=Pu*UtXmgwWq{Fl0HqjFOh00aT~DKUK9%CB1?jl-_q=~?X0**j~e zDLSmZs(&*6ZlIT9<>uOAT7?zZYc~&lIO5R^K=JyPd!27*Q+Y+EeCL4suzt*RA#`#%t!tX}g)oh)Z- zh1)2_=hxNw6}JLR^Fl@tq_v|_SvMzrkhIq3GJ{TTeBnPoc2NFai=&lF2(=?CLZ$^d zqd)r@VI7b@{v}JgDhoQ~3``*P38DzV4@oF#pf;$`xh%F(jlJpK-=2)xp4vV6@H`kZ zWd6+fX_hv1S7O6PPCF?V0`~+9p8~3uD&*0pPYgGlo@baQ3}l6X#lT{ahUb~B6s|C8 zg2uOPYu@H+=dZQ$o*FKC38lZ+25BX|LhZOl<@S$^$a)$JcWN z&OH-@52(zHcm4;Wl`-}tIzP=Z;j?$gZQA8C`{{}c;KjZshlpaaXu-AlISUS>Maij3 zSd4P^=FP%J;JqUAzX8JwltA?|V2Yb@V_k4+hqGv99ntK->xc-{19f)eY_yo9E2tBW z>fUwMPgTvaLHgByn}Qk1)9de-V&s~dKDvwh1mDqJ^X6}QqGle`FO>br=HW$;@_JE` z^My#&q(r%RnH%?%4!xtqT1}QafnkKY2UEzs9tu6(1|MbTC|`&F7rpD1*Vb1iD@Ly* zWIB$w%;8?*jlU6OCw$VUkYZn3Cl^I~JkoU1RPid&S5nux=WTtEcBM4Y%8?_fml#Gl zs8xUM>ql?TE1xu9B=b2u3csev`%+7oB%~_HDj(6QvEpW@{U<1Um*Q3#bfIBp{f5=A z%XiQNfM-mBzrfGhtM7kRD>ZoOs_H5sOJR0YegLTfrMTF=3-s3P06d{_=xivkD-DwM z@?J#Z$NoJpqIKzz!B%B@Nxd?vcigP=I?}NH1;8$;1j$XRao`;;=FA)GGfr`yB7zNq z{MRL~qKO2KH?1o{&g5v2=D{Y`Bitu}RGhSWN&Ok%*sceJYk$4_O4f+18*wn$3jGfx z`!fajf#n0)y3bMi4gfkACo8D1g7I3t58@Ky;){ER{DQPqd~R2kvuHV&eUSG6#uo-& z_*|b)=OoMf(Z^JLTY7qvI(fQzy=(OP|00hM_Fgi{;Y2w!cgr==)34XCsICROuBj7p zH&ry4Fv(`r&(}wU6O_uD8I}7xcivEn`0?;R5U>m$g6nSEjDqE4ZCm7xj}T(qRj zvV7Qk9)Fv*VM~49GjrMY6~u__H6X{u@qaEtUhxpf0SA&WT{9QN+7^ck`F^0XWdic+ zfNrK*_|ckZQ^a5Y?fNwmUsbI21~{8Rm=$FcX7n%qN875Xrr zU_|@I68`xTH)3=o5U-9fz21{oKy@s{q<~p&u6n$$llQfHT zO zsVfNy@p2*v()&rUjg32iEGoSG>W-s2lgRs*J)&k|Q4S53MWtheeq-Mv$K-ro8ucfk zftcD5K)ZXmEFCenf!6@Sg#BzE2cMES-jB%QZ2P&FwRb%3wCF6=5U$TB?l~E0%(ms3 znmP;#kh^4K{Smul2YY8|SVURP`OU3s5^myY>r!fWbPucE3)m-eD0OUF=UUU8J>9lQ zOFq~))E7T=HEwD%bCi-F|4sLSGEsdX0#yKkFQ2Ce`6Yk)Ou=*fN9ZzhA4#Cf$)>=o zX#W^OFaNR>YYto5S|aj8XOwGy{(&UltX4_ z_lgSOj$OD6x8t6CmUQ}0k&!qydD7cp78bpMvLknRD6~~##v7%7n_Rp`_Kvy(i}*{G z%opn|sTiRSea)V%D+eK|xm)u8fwRn*wjgvY%6#DAwdeiznA#;R&4hpn-`G49js4FnRVRt`(4H8@YFH*F&KzP28WB>TNO+Y_pOW6lF{cTm@DU-k+#y80RJw|nCt z&J>>{pA?v5HLLAykEG~&bx%aZ`@1CBTP>-?bJuxbEQkfv<&0}@igkPa^emLQbXiZp zTct|Ei^aAd*N3dZSS1%bs;Dz={lA&C|FwUUbO8Df5uQ=lo#1USB`bDsU~!AW(^*s@ z^8NLzj+CXvC?zjRZOrz%vd~T$+UMskHL}bpd2^fBIb%xK+A8m?cv_#_ zi!Rjh%O<(Mv~y8m*^hdY*{>!kcfqe@SN&>$cf9>ZJ3$5M(kS0x2Sx**FrTCymZ10= zmN$MJXmHZPdEdf;zQ+GX?z=>U*xepjt(b zOby7tq5bt(+yD*P^GytnD)2{j>U%tzxVVXbW~s6r{rQ-c*NgLW-{I}Xw{YHYKu#nA zWN|UgK9#4?^YMi>4pBKX+s*r0jb3$&Et=&2;qI-%qH5oN@gbyF&;f83Y{i{V%`oZ|`gG>vytG_CEL@%*6?;^{ioC z&-1zO`x6Z^{RizVpk6Apo%!S`Fe6>2b(O!0j=xNAsjl~a(#;iH{*{%8J#X|3H%##@ zcIygqfRM%v-9A9y78j}tZ774KhxRH`=m^H6=kSH$)|Wf+x{;w(#?jTn%r5tPT1s6w zMs(}7eq?xZrK<0Cq$BM0a5&S_PRs9|m(NJbC&k}Sh_9O=_|?O!KTo;IW%F6h4QH`} zE4SrusRO5;sj-w^Z+kmGaJw#C{gTpbNII1(R)fyc1X$B8A-*?o@F1`&z@l;`F=s2$ z)#SR*5`^_3a!k{{ynFY1q|#OU9w_fH$bKJr3$2QZZkF8`<~IW_%72&z|Gdhb*3a3u zk1&)^QDQCRDb(>+{8o@p@!?hyF_QHszBx2xodwTee4J@ZHI;uRRVrbMPNeC1C6QiW z?+e3QO2n__2WVtc^-_T4ltf1D4?DhI#}oALK#= z2$68>n~%*;)<@X(&5mA|xM;bdubfXaE2{x`M3d8PT>K`oJ#p9kpCdf z*~9OMA-U-SS4`%Uy2x|Cx|ZKI^+|#J97G?(*sqEpZ$Uv(-JWxuT~^$f?b5Pcx!SaI ztqbxFt(jEmgb_og=i#hToJbupUj*UpNaJw?Jk(`fnDS+ieaakH?lU7@&ddPSC-s#= zATnLX9lFypU=J+pTA3^Dk^>mLej#>`foz`&pz!gl#B3(2FU?j755yDUqkXUe@({c> zCzNXkt9&iyUbLjPoU&sgdu0E|KUH5IESegF2o%KA_Ez@zc=0v?(@M4~wdk`EW>>k^ zx1}xwgL;jr$L<^b%+cebjuw0(?9{J(#v37FfN!#78!l`PuDnjJtNqUWC#HiuPX5^G zj)v`iJt)XY7L|zb!;Y2j2W56c=_FC7C+{Ws6OHnPbB*a5 zUWDMntXl9Z0i^rZJ<5CBT(fWAB?m{y0CbsM9VNNrXuw%2rY7(Hfn0e)sdLY-+kLH? zd)`{ivG%sz>L#<+)Jn4D$W@B=kDdVAEZ%Vju3YR=d06Lc6AL^f{k}<~kO-(pJmCQ$_*JhuNYl$^IgbCn3#f!JcMSN>% zI`_f8D1wXqm`Hpl^d*%bgi!PJ{@+`jjLf?lG>;?54_@reh?#3Yh$VpiVzTQi z=~SpBW=jACeSGx~i1C_6x{CtG%Gp2j`TUaj_tED3L!m-;DOu2IA0ca00m3h&eCHPN z@E=g?a0uo~^kqOJvZLj`yXv;q2Rj!Va<*K9y6))p>8ySmgH2U-$*E zR1ed*JH@=Fe|82!fi{Zzz|_2WU68f(zX5{EP;!7+{S?5vj;+O5<2BAzPtcwst7~43 zLYCsi<5lsds#?NtCgF$tzk^W$Y`>R1uMTH+c@FnVp{}6>aOvQZ#eP5kegSQXoGLy5 zsp@*u8Q7ovi&RzqgH-c+SE8gtshimdvcD}WyN0-W*NHO4UwHVZ^L62>k?ANY@J|5J zn%LgM;thEGek*e?+(H-dg)cqG!{2?<$x1aYA(^vCN@`#CTNAZXZ6|{=_xnAA`c$$q z3u9BOu4>F64IcbiN6gs!o-Tk_v3+%8l0p-7N#C4(9*MOK#z;S&upRd-EAu?$c2a3r zjC}nKN>Qok%k1%Fx8JrAx+y zHosqh^Se~U*HOX&k=p@;gjjyIF?zqt#KR%Wkv)Jt@CnZXM{66pH93ngvic+IBQxjI zEA=vZRR$qo=T6!8GV}YbhN}3!FJ-3t_H;qMKfkgibz3WXLmocCmR`kEPH-->m&2NG zjkgzs`D^vP1{6hID2S?@!dY3_dDaO^>+?@pdhP7W0b=|>JLUjMbWLkPh`Sf<`Anv$ zE=^RT`j&&ewKoy_CO|hIht}MSkqo?L0=WO7BKHeYnwyCp6Bke^Jz)-FWZo!L{23;S zYID$Kl~sH;1(7IZiB;bH-Nxq) znOCwBXAS1BW_2Zj4tvVtn$kI73lxS8e$g^k(ox6eR=fp`>t8S$CgFkg1BVowJmFMg zuaCU=2wM1~No(}W+xxC)_zc(^Leb{v36Rkx#?G{TjK}XJBn()JR0VRS%rS@+k>8J} zCeR52m>qhAzuH(8aa~#EG0A`cs>I~!#LtRFWw<3SE$GqX*g1SZd@`!Z9lRW@E5I6x zdV=XmDm}QYO+QzV+m>(1(BH9ZV`m+8~A>*?VVV=E6Ia-ibRQzc3pC{8E&T+<0$>^O<;)(V#Uj` zxSQUAV8C@fa0lg7vb=O{GwD_?tR<`$4%ZD)3E&dpUBou9|7ELlwcvzEOl3x2XK*gnKKZ8u@#Otpodpw*pc3u zT=r2(1u2geZFxbT91!KZA=KOn+3C->l=t%?(8OQ8ni^Yfj)6}|Q+^8F7* zaDXRUP0Q5<+2te^0!QL|gvjl$`QtcnaFOdW+mu9CUE6{HVEq<7)*nwP#fjUDmrKFr z-=Dc!cX(C~gY?!~v115|zcm~2p=gbSIWztB>)m(j8Ej$wzxen5z9N8AxU;tA@8>`c zrEcxwpiFIptMilEr(VTtwEoO#XIy8JHCK;bN@uj)_jT2B{a{zyqOnmQW4@JcM9lK! zF4h5Gk8t_jUj|SihUeUQ=B$nB;C@@wSqT53SwdH)gRs@pROysBF(7BWi0AbT+q2xL zRY4_*GeVsZoTPaKaT*Dquj_21v2q2}u6AIQhkHJN zD`&`*9c>4`vc0p$S)3Vw(9|%syjZ^%?cqlnBgAv%+uy3%KVNRBHVU}lM6|e`i%bfw zW79%ySi0gI6alPt7h&Y0yVeORO!j_5n9P7V?hYvM-wjio08aW+wC)Wq z9GK9Dp+hnIkhPL)0G=r20a`LJ+jUSYKY(5)|Nr=FSrg|4Xo)|>%(Zeim7*OP=FWiQ z#=-+gGGJ~B#O}JRjxahaice(y?hJY-UGWgy{URzlXH_oOSVkd)+?VbmX?2D6Wh=!D zpdcgJDm#7vF-!^ma(6#C_J#pX5$g_iS`(DE`eB)3*~R!}slMxmk@z@*2!+)nR0iDk zfvix+!r(903>fb1Odr9r`LCkiE11PfM}U^8TdBy1l{4NrFLD47G3LxHu2eoO3@(Oq z;ok0>=T$;A+zH=z$9wSsjIL9rL_(WhC0@J*{v>SO>NaL4tVR%5KD{PumA{*xtjO1T zfN3`6b)xGJB>znzV6Z5?`m0Xx&51I9wO*C#D@}0h_l8sU6-P?foaIR9t+ery!65P!e{(f-->#dAgvu7S6RZaW4Lqg2PRXTV%0!=`ejS zQpLp7ESRnTR0Fe#Q1n16fH(6CqabLG&|68U;k5`l6~@$_r5mNcxx^&lK0&w8vNX4V z_hYe_b+-Ilv-ElAvRqs@obh#2NApjQuWCMvw7w3)5d5senQ}tt_vUuEiSxHxpMaZLxDwOVxo;Ikswh zm4~X+WCqkSo0JrsEGH019i{S%cyH19HuYOd=+etPGO%b!kk|NeVtY*pP2PB%z%E?JFs>|Y2jn3?O zb_OpbXszwMJw_I|lppz)ntn@fY^vrnO`1$~#@mwjoYUjW`kf1%scr>6;48@ufL**DXu?X>8PGg|y9)zUcdO~8^3N&_k!UHpvB$V? zl~;?HS>Legyb`^=j~Kq68@}PrV>;v4^hnSb0714@WL6rKkt*EpKnC>T)nCt%x^@QE!E<@=N2jD#%_+{ma)^^?MO%P0{elM9M!I5!oiAD890^&qmj5 z_NR{e*VxNU5mkFz|DM#!&q+!k5DDrZkiGd;Q)oPWZL)PIF zCTkj5vS6t*-80Wn_QbtYM8M<+A%wS`^;^q+EXY-^xWj>PalDj2WSksf51YR{c#iR? z@C)@4?C#3+_DD4n=p|FyyaW-B8rLGa@6BjoNKJm2Zqn&9=^Fumbto_7~5G~DVcicFJnpN~zACI=-9G%(xS>%~omr;sOt z%=yGhm+L4=LUUY7T_yP~G@qFSbXLNPq`fIWJ~~8D{iv$X!0`#8r3rL19*Qqbjbce8 zPzwT`CrLt@YdWqyu!uHl@Lk_GEmw4t;ss09-^JP9mkdU+_^10bd*7vy>9|HAzc#zC zlbbMaJTzGgd&1p%ibS=l=Dl0icF7x4YIWOs+& z`7(td*;6E_m@*ZTXrNzbDo)!QrCR*-j->o0o4NHMil1SMZ~##1V{~r_t97sAfOdm%1@?IL^FbR?HI#5~zBqA5 z%|4YhW`daCI&zUfozRCWej~BPbq=WP5_%86tVgK&p=j@7e}`hTF55aTnHA~L-_k!T z%Mahp^8i9hKzGjL?Ki%;I=1@V)OdQ7UDJFvSN0NkWw9)h1!%5u%auI!C?IB_OU@zP zW8{vNCD8X+gSXN6k`lSqFdTXwJ`{WEsBfEM0_v#f1LG2nJQn?>*3P@dELKHG$o@Kl zJVfwH#Wg5>q(e9|H{|g6=93e1opr;_RkW=&bS8r*_j>dW1JLB4V${=G7o@V|@^JXg z5WW~*ZGUBNJj2R5o<9nf|K>tJF2CS%USXzslG2bNK}2eYCxcgmfOd0s^J&mjdp{ui zUU)}+SNRP272=}pX;6>VMwdCYew@*tcssl*Lgl>2y9c8udjWRQtdHC(2X2A=fn~QRj>az zl&0lGmWY~qpU;)oHA+y1|CW)9qKxju6V(*#EeRo;joGAkC##Zp(5k-{MxJCr4fK^S zJOA^2>l}=?2^@L~xd~f0SLUh<<$H1I_bmKv=8z-4Di4YN18M|vTs2g1|L+K<{#OxJ z?D{sTJ-Ls>t6~`fRTARsm%M&VkG`6$HP@!9?w0bUhLX+nL27HZ1_2v>l}eW_DVJ{o zg9N{S$ZPQmCVqHe;515tCTBfA(48t17VzM%ZHzR8ZN&^!QU*l;Sx@_P!;|y_{?PZ$HTw z#h=ZPp8)~%ioD|+(ed8Z>kc@==e0aP5=72MG@L4E+qY(=BzmRTnMj+n$l#S(R&h3CCoZ*$XX z*v5X(7?DW8rx#_|5YYU#-nHugivW?n-A3Y3ScUzt<|62n%Etuq8TdA?usIUSZ-z8v2=H(yfb-;mc=>ScQi7rZiFUR;Db zO`*2yv(yE@)S1{haofHi5hanw?aC`I*IKRFngVuR;+FKTAK(Kdpk>T5n1AoSQ60H_WI@c+)s-oWtx=aN_E` z>*9#Ovf+jb+xzZX4m52L&YpQ_Wx-N+Th9^+kyD0DxzXA~6K}OthdEw9R)hU&diGp`nqF3YYvo% z&w@ZKVdWd=>P-!G*lq`W@xEjWbDf6?CU27k9KtwTi3q|J*K%)vEk9xY1lyz?F>e}QOt*To zPjK;LR_1u7zB`EG@CxmKU~|n%tWs=pFR>W*a2pdO1dJ#7E8I4Z2886iavL@_@)(vz z0@qt?*MSGE4{iw1;G}oFh~CUPUzsgASwnET(#L`G96}y#IoGiiOgk&t%Gndx?O7dUPkQ_jUvBId?EA|g?_hhp@VW@|52;KrfXXLhkf0^bX3akZe{==8R>F`s zF0wq5X!2(?n*4fV+Ry`TwFo-Nr-IsE%VV9^@<6j(ax8BgkJ(yU-g{=L_UbQ1{b;}b z75ox}J*Sw1SiuYnFs$!W1D@0TU`sHX1dc`8Ck#^er_+3Irv7v%^%ArW0*RF}a3^$K z5903P<@Q_ej4zNqy>(}=9Q#Ilp`f)NA*C_MsYg`ys%m&Z~N1gW>9n4L=$@kKDFPh z8?OzV|K~UWSp%^}UU1LX-9$S40~(_=?)Ez`JT&}^a{;>1kD<0p4uCKN^8Fk>-aK;t zuBBn(_Ah#5igDkW!$;UZGpudtdfCZYVZ?p~5Lto$Kr0}-S0;sAUAJwRK&hLB2@{*~ zN}Tnrn4CTBRL$RXkUXDOQ9MXzJRe55;{5_fCb@J|H8@xIv7fq8?J(ht`G?fZy);@L zB*LGQk3?_B&hkkBv5Il%shl%31)yr3yL!I%uYi+gb`Y8fOoSFl?&3LR*UPYnj)04f zb9|sg#^a5MQUt_~|IfX;2jiz^rNnoiTD>HHeUn2iI+67sVAt;h2y)5+ei}RvgZy?& z1CtZRh0*AYerl4Aq#OrhLGlfY3u=G7Vdu!<#;cWcqlVmr)z-uraSd^gOu}CTfrkc=`9z3zpk6P|#2{w=1kkq; zy*D2b;8!@+8{o8UwWaf^zKjp=tpNp6T#Iwh+}v$s+YIe#$+N=pyA;Fe#+yk!ONLTi zDnm+^F_UzGG0C-Cct>oKXq>)_Kp9!=6Uj@`D@n^m=rt93^3TMem(<+EPzICAe>-H-U<`aBYo41wNJvWoUY z5wV~d6)UPMhb0Sh_@hd5m(3WSDce{FRd)4UgW;MMRYM&GrF@E)w=)x=yAPazNS(~< zmuCngr};LOR(xO~a1M0ypEM0H$`L13RiF!3sb2wx8zw*h8*o=78|hjhpEp%T2zUzY z{QeYRIaaI6Ai{jQs}^)x0_-PTH)(#KH$r2oR~vRHWo51prf+%SbaCB42kt1ma3AR^ z_4+=L9=yxqpcESgW03v%I_0<*983q$B$%O<9?0#&kL$rWtZVgUO=*ePow6WgH$eBG z&*ev}Mfzq7TF*kFY|PDF%F+E?=2R3P!W2E1it(-eHd2zDkbnltgK6P?^)b<0FSyth zoT)3IxhW7IA^0^hiYc4*g9Ofy+3k=tg%63whBm_&88S}&;rQR-x!S>h^LHaEup2*f z?~k7jKR?_awY^ekTIBUhC0RyTrwn@2AC~v5Ny)M-cAgBA(WHswZn##VhIEp|6?WsDru+A?r5y2?d^tIM|4Y@O2mxkTn$De=$o{MQP{ROh@fd4Hb z>*f8Aky%B4Obg~p7bH&Uc1wx%{o9|aW~$>B-bRcV{;2<0RV!0YHM@}ul0_!Axf5@P z#>&Ad1QPIrEZkBuxDBdPJ=lHmQH+-)Cu|^H$+|>1M6kN@3ExUbR6^{Jp)gjp3ZT8) z+lzcH=NW&k-#9fD!buep};$oBGjoxSQnF{(22Zb0U-NTU zBWd&IGX@(NiaXMk8Cjya*z5qT?0cVA0_XQguW*DJG!AU-_Xp!p9^(~w|9gcMUuA=e z0oex!&MiF7TtrO*y6rr4+!x8@()a~CJ!m8cFZIu;)hyW>3)j~FG zcN-P@$}AlmVTxa|9arGOB(Rg$IzLPBm7?ocWiQ&=+m{(n4eqGhgx9DMf~P|}p>svB z7d>YF4MngxRIwd@4RC2+T_ z7xxAy@mJOGcv9nm9c0x1`n1k$rz9{Ob`Iex5@ zIH$ru+pqVQ267+jdKnshC%R?+_Y=vA%)11{`V4@%(&c0xahnqq`xx%xvQCPE17XNM zWTNJQPl6v=R0w#B^oFm{o&HDk)zvmvbxS>R|E1ja$8{?Ex{)0%vgu_TQ;OvdGMT+@ zC>C<10MuO53th(Z=(db8W@TEVTRpN}y$tR|)oHNTa4?D%2jF3wj-Ot5>69R#5>JMdX}YL_T4 z^2tt!h#Nh;AhWmJ3dMGMNse@T-fNj0A37+tR4wDoctCFiETuyUOY zg?h@cyWBu*7cuCulFgz$-pDInyeT*^H>qt~N4+qfXjZLFhU)wgB%5xvQS%pQ7qRKl z=%pLKro)mbvS4%FJ${?tj^_1=JWf&G;yb$PuG9%41EhXiDj+8-*G84}PUYYd6Z2p7 zOU!jd@#1}bJd#p*#U}d?e&q$b`_kYHQJ11do~>e_*=&?Yj*Xa|=z@j}jRN<_udINh zmzan-&nET^IXkdzk1YuFNbUQf^=)8;0T4pN&_D!NKhW>+Uw(L#4h-TXudarryGcD= z0K112ZD)JmMQ>&6PVi1m02EJ=h^JE<(A^l-PwRXM^zuA6ASQel!|34H$Cc_Gw`lFy zl1Ez&GX6it;sh%Z&9cGk$FzHm^zR!QTHdQNoT}{q3VDaCJKm<&d~fX|iZO}k zpJwylM!h0X*E-8zbJCy!i0=Cwi`Xj5X%?I-x{}8Uxe1)CD^DfQ=|IMl!h)v1M~Ly z@1GoJCNA9L0Vj$ov6^0)!eyeM=`Q294tj9Srw)9xO zE%BDREyBciShy<-^*vf&?Z^1s1itm_X5vg zJFTKhjK2RR)}{MzB?Gp+b@C>*Z8P)b$~hx|ubwI^u_@h6|8@NsU4TruW-7)8UAkCD zoY4kW*m;XJHs-K?@s3a2;7qI;yZcU$u;1xivjw$&5ZcoLII{*#XQG{SCJ>}AS?^>U zeE8A?ajtKfzegRZN=AFCXJyd%o}fJ+i!*TwhyHu50!8U(raZjHuT zuFk^mtd_D-EG)K+)-8^Rhczv|ucDME%O!-ou(YHF8Q;SMw7-I@pzY_G|9G4BZwsJ* zmDUTy5;0}gcYy7UT>#<-@QsnBhX6&2U)?RJ?jO)Ss4AAU)5r>h7Rvk7?LXpD*t2!S z^T6Bm!%~v9wCE+puf`y>=YrJ!`pTMiDo|u-c$KQq4kQ}<2f0^FdKk(mv?~L$jJLAJf62P))vObF~%Iv9^7KfV9w^9y%N@k+PFg1jd1P*Ei zow^G>y*~j#3qdetOik|vP|Lv$>k;@pd)%)|#9oQ%vZx~mg}aQ$*_b_#AKrXK-2dQF z7^?8%*U|$;8&kFP{H4fym9U8LL*WdwWy2XG$=nZ^cTSoLvLiQ4OOTs&<`Ch@HPY6T3(hM2KcH|Ic zw}}{9jXiPdGywIaXn7GXjeWkeq{}mZx}6IUHQ{skQds}~i|ic_U5Mxz0PWz4oMSv$ zGM}b>=eScMK68)L+rmJgK*>^QdeLEGdA#A|rictXpl#WC^DuSeXOO@|--$K=p^tV1 zhV1k;6#6A_%KpFR(I>O0_HUZ`&C}adv zwfYv>wPe+6VCkqWbUsf}B_5JDqQOZvM6fg%L0Tt!YypW1bSo-|5Zq^7SzD{%lVLdnFAE@`DIc z>2lW;bm3u@>lav>&g5~kA@?DDn!xt5JOx+7i*(>p?R|_uNTk`NPAmumjbFh*wE|Jy z0IUm9LZK_2SZrm_g?)_v)iQi zmCqF1mS)5Q`#{y6D}fjo{ePhw;xYTY+U*so``cK8)W=u3R8RLa{vW+_Kc0SZC{+n zRkK>l3TMIj@2B^-ID`oG@u;#K)7P~rj2m1-cuXfe%c%mY{bw}z@ANS~(2(iJQ(SLl zx+(GC&fG|f=FBiod7xzv09{UKRnLO)sKfh>$+YDkiul6KXwREZ9egYN z6rb=T6v#m#5RCD;Ukzp^-lJLBbN!J-zehS4n>8{*HtEr84pqn0}+nbK^y`Lk~Wf}#vj=~P501OF=CgC3uGeS7t7z6#G4OO2v~1xN0RO< zN=-^?C9#>p5bnyulNkZUOtpWwsve1bn%9AK9FKgfYhQV7#e^P-YpbgU`0ex9)T040 zj>O^T^wj%(rssVZ0~vUQh?Z7I?8INtioDm-RjGMUVxh`7TLFk2RCTTMLGmDg{#yC5 zYxLT4bR*W3Q{zj6#@|%2i@OWv|2Y&Uf%;*;jaKmv%=|f~wYXRpgWIc|b!*g=I)LE} zF!nZmPs7LZHC3sXwPy6104u+CObYwv9-xg$uZs~X`+PDcFhymvOvHWOx4DUz_>sbD zMFA3_PU$dt3mD5X~%;TM3sv=JFW9nxB0oU4&49 zWQrnyrJoPDpv=$b#Y&L#?UmIicxqKO=n==RAIU7@O++XcpN0fzOgA4NhUoPC1M2Vn zaBlu4)6pj$#lzTfoq+I#8)um(#k2|rZU!>HoY5q$Re#P8k_F<|cKOF3c6$X$>nFX; zi52~JB7S5_Ztk3%fS=L6qTo%2yC0JJrQFA)*sm24M~*q>{Wd$a0^XgW?;99-m=S){ z*YsFASM&_DacadP&7dh2&y#_E`Ek$Jh&iSOFL~n$1_d9h;eWGb>Ha%O;1ahr^ri+t zLN3sAMqt?cR9A_O?yo2Enem~5A4ywWnO2fQ_xF<4l2&J)-!>OBQ-(kz`$`HsE=&N| zu1dXx@@r4h&Mh{8A8wq8o|6%T;PBKS!v5W-4J1*voyy}LehOCG^5*=0D_rF;yvw|o zZBPidXap0yfuBpOp@H^{{NOM}M=|K*m5Ohl2zwAI?~cIc6I)L;yJHg3{c8uMc_c6z z#63Ob+VeBz9VZAo4>;bA<-+W!nFbx#fuN$&viYLy6Us;?EUc8P*S;y1v8LM?ZaG!c zELA&ZKHtqO@-#wbBjIrgF9Qgt9pn!jqG_0!P6Q4>V14+>rP#`fgMYyNxl|8>Jy{i5 zjx|P!wX)>iQ4yOaSAxBMC0}Jq1cN=5p@g>ng!#C@nS#`F$&ZU#68aSD`eOY2z01Pm z*W_#2A3eD9tm_ z)yt{6pM0Q*+g!%lASIE%67w*|W?h@)kNULW_=P(&^hzE+Dp`xY?^2H1P+A|@xO_%-9i4>Bwo2bOOGc~CDT ze!yv_R$JH4Bh@+H}#lOk=GQK2-brtyA!qA^SYmm$P z62jLED!|ms2-=;8V{-irz`P1mn;G@#9%bdWI6u|t!fYFuGVdn)UZzV?r6jMl>f1d6@e>l6Fe(4@}|sf9RG0 zP7goD9D}`6Pgf##yzR;_2$^(CwwKF?4fp~xB3E5+cVW6yFsRB6CNB)y3b1$j!~jV- z)8xh5^1Vr!l*_~vKgv}a|Dk?)F?AZ5NAO`4hya>1(+Z#~&-`ZI?N+sNke8O^%y1pCACy@M844N!S^pwbSMSbtL_zy&3WX%)H0xYB3& zIG|lxH0>ad&6#_ctF>QA8z_PvOD79$;?o&By2FKMnp}L0CgQxZeD(5CiVesx#!A=S zy!jV;&Gpz9=xB?jMNOgVm1^BqRZl79JFP602*-<+w2`3@ZIFh|l>&gp4`6ED2^_2a z9EEB4dNEx6JySOq7fH$;I~7P<>;$?{2?KduO)9?>C_&`QMSorwAa9B9@Z7(3TTg?k z{H~gA5LQJ;c9S(f_EPvfx3#Gr>xs@;wA6i6Kamw!*JnPQ)?atAz>{&2_wQZhfgr|a z9ae=CJ{=5 zl;UB){Amy%416`^@YkR`GI|j~Q6zZ^MaB7mQ(+vkjwxf+=jpFS*&7xz6mL&)&a?~T z!IHr~EH!!2)vj?dCVVP%r#ioMl|XK3#%J#4I%BBm4db@zAK^Kwm6p#c9@!GllLB|q zQAj{oJrT(G&Gkba^OrKC+W9YKW*51jxo(8fjkFE?>O7+@RAiD)W-sd&^szK;1#dh| zv9uBs5P5v`rM3b?kmTd@;HL`tp~qneh&;{Q@5092OR{Xpn0~yU3?q{&%W3;P{;o2< zH<7}@nd1L-!YWwnJQ{1aHo~C&?d9c4tUFxPkL2c^#j+T~H{A~r?g7g6)V=JdJnWbx z5p-0$C*C49TQ{MVXZLdcB0Dk9evGS*0Uve85g0eG`Gf`eF}BrKkLSezn+X5=dzxw{ ze^^R-T$I^^l1uaQk+>6E87q3VS<7m%&RfPBRI!ym*a5i4^09B5?6YJ-aJw-8ePgPKDkXrPq`Bl%I3{vym zg5)qUbwgViGAY#U5zDQRo0Dyf4Ur+w0|Z8frF2-4VUP<#4d)w^=LA?L=48d}s{{E- zU4WMH)Yua9FbWttb6y3TO-PJD0@BYHzD5l##G@90)SMjOz9NZG2AGpsGkuVBbhZ2A z!(toqf>Zrg!WT6rImW>Ftfo2@(qq1vGM6fELWcw9oy(!n(SRhpS|M^sWs7aS>-=#n z5$XK3xTE8PGTUQf!r4*6y}v#jtc;j|^PozAyB1e~xl`WsFx~G}Y#lF9(3mnB@cp=$ z@f^sf`sKBdGYaUYBu!yYdg$d)Kv+e=lw#Mi8zpz)TyGZ9{LsPzyUz(gm&?JW7&Z8x z#V_Fyl(CfztM&r3v|JdBA$*3On!1dOaXTJz8|q57w;caO;x$2-ym)>ef`g!fz399Z zzg?UF`xRnyF9#l;e5w!)C-&pD{Mom8{WW~lXSvl6ixEhJTVw1r4>zH55xp{aAc}lE zx|%BiEv``JYq^T1lD;~=H61*PD$Q4a*}QtZcuTh&h@ojkK8)XOLm@2DQUEHO>l#pX zrS#h*b*OAx2!}_eqhmy-{eB)1#A$&oYMIXxabS^Y)EptD`qdHj* zCv7G^I05i=YfJhE;<_Ac=iH_W!xks0;G~O@ z$np)CknCJ&$;_)&mGx#CvLfc%V0LU2e$9q+c*}&1-7w2GPcv#3xe&o|Klh7fYgJ!f zi21qkP4*xq$>o2cvHv$VM+goSay!3rqE(H&A_HEW&pd!~br-M|=Rc&FE8XywD=5o* z1Gn^ImWx+emqy|dH0YQ?%}EujbCC! zgm_C8N~|BtR?0KKK=q-Ci#I-|FOVt-4X&~O_~qmsM_P@cnAc3I#z;+22*7dbU#Y###t&Ggn@pzi zlCw~{eXIA0E$Nr**1%=b`O2hC>OY{LjW2gbBIIKha-|wn*e-F9nY^I?Q#n*ZD?Xdn84HoG;|NEhDZ7fw3(I~1WdEb&DTTUo*py6m<-3x;=VXJ2_Wn& zjzCI!M|XX~!&KF|ymHmd_FelzU(Q)UA{u)$4zmlw&0Ttw)4eHbKrr2F-WB}NF=zD3 z&2K`08#6NnC2bZighATptx~>NhSdI&x$f|*r;-hQ-zTCzuh9p}741KOX4U+uJ1h`L zy`~!n&p0he>^CL8!Y}I`4^s$gV2ec%RAQsnNE1l)a?Ra`9!9K-rqPz%o#wxOl1hRB zeB5O?%YlKtApkVh0H8So08I!0G~>}cZx5Rp0d-a9rMdcahPt7Z(V)CIy(7fPQD#>> z+!4ttl zgGfN9{}G=27Ea2=8NK#eQ%`Iu>&;J4)D;ta?D(A4e4zL)<2%YloZomkK9g+P$Pb|& z#LUi$bkXue^V|ORBlvGEs1;Oox1I6gE3h=HF`m}(*EXX<(tV|yKiMfBs$Uq~%=Ew^TY#WTG1w+a`6O3;^%UugZ9=>1U;*Antrrkh;N!x-Wv;b# zaj-?1U#vfvc7l{e9YaB-bk+mvfb1LvEo`Ms*+OT;aFvg6&bAK?d7HeS;mQMot0r`B zQc>%z5n3w@8eT6e`L$?+0y2JuoqTGZs7~p5<+bbDaFegd2=nOE`ff&vTGGfL2R)vfYKXlJo0NH$Qh@i$R5s>F=YGJNYPw zx7L;v-6E99N2LiP6d4odW$88?)$|!nb%;1W5wu3#RBxp>o30fts{!CsfVP5NG01la<{R)3`_%-1?bUdSag|S$wlRfUl6II_q2{l#isB z?VOFg^|A7fWfMJ7iCJPHK^r;eAH?eH)vU*<5Kzss5&7+YkC2fE&&r9tqUIVSKG#xc z0xh#XMgN$~V4UV>8TP9PBGdf7HT!dFEW@{I5)sA&O9Igc3#liGs}>^ zQ1A)%iLE`9@`#-oKn>61h`*>S3#cKR>SA zOZ9$Ytd7PG5xTA~qS%RGDO5cj^5d+;8J(Z+lms&pFuctq?hPy#S@dpHV!^fC&{+Cr zc(Oh9HnkbiF2YmtdDW=Gy2P!wqkiq)zWh>OGHhst<|b2feDJM)zvoPs8ifWG_>%4| zNb3JsaLa*YNU|N@jWD{dv^K@Bc0=H$vpuAHNiC8DFS>V>`5*;M?5r#?AHBxUZ}Y?j zfOd^z?R=WX1u~ohB85q9#u9$az-a) z8^!}{-s~F{%7`-Ab_~9E#=gKe6hp#(h?F4C+49U`A6f%ZaqLDvF`_+OTk1MFie7oz z698Q4y8KQAv(yt!_ov*{dl5hLln{}}PPKS@0Nt8L)NE&BIEh6HaV+*DT#nS-GPrjy zn$(#`EFGO0+IeA-g7*h1isbM4Xv#^yo$z3XW~=ITxcQAP#1r<&mPYNc)%Aq>!fcl^6Oi`mcF#S7vn$vzJ`>>8GFD@ih=A@O-h#IF|BO{}S(1x?A>(F%^#h`d zM{n@k*S4vM=EpWI55HB=Z@;zpa<}ZP%oAWu^0L5|Ihxx>kgjX_{!$imPN$$U5f-yU z?LDi0!z?EwSLAn;tnHO1N?=W~_5BszbI@$Xt#xFcg2vSKB zJs^AyPLTWp0N(JS{u^0mtD5pO<@Miph+o)fjSU7ultd=;a3LB@Xx9;npwQ5U2xWMH z!R1gYCgOSK`^#%Ex2NhC^!i7@RMggKEVmZ+=UDIMQJtc^<=@anH)sOZTMu}PS118`E!X%!PX}SHH4PU27?@a^^BX0z2AGb z62Q*9hJ<*F=Y890kTlgp=?;I|OcpZtBIJpG>xVjBvBH zlM*|JmY{;w;^EhXR`rr#O+harqQlrijrcwrXCV}MTk9&FXym)QVW?tdjoifG!5S6Q z8xYC=M7MGf&HroXoxr)$u2*eaJuC59Qhl9Nnk#y1=UVf8UDwq%;Z6N)Z+^axcd0Rm zW$M-ZC-Hp`20SHBhYocJ0?~8xYXsTQ^NypD{PN3!W?yIV%y0YemcYx{JirCJ61vLB2Y_QjbLfS5^%mnGTDe#vFi$2t=z&7}7`Y7_@3G zS(l*Dmm)USoj=u4dezHurb#GV=N>b+4fWFXK*!Nvh*HUVEY$f+-Q_Pozh6;q>Y5Jp zqym{ytJnj~z%O@TX}|y&&(TJ-8X+bwxL)K@mP4HAtMPNvQV|RBKYphRl(MxKWj{_xv>Lh$613Z}7lL$B5Mo3G zz52xu&By%hZhoP|*zL^GgS?}>q^N0y_TDf2))LfGZOnQc`yQz4gfi(*^h8^X~h$qhIG;hj1;QKmQ)txNi@5?!XHRzr{0En*4 znxHx}JX%S_zEm-60*ZnQUtB~JB{n|%xy`-JFX+#^>l%Y$LQBlT>Fg_O`^sPu(&bOC z*m_@TwK`*H!1ITjw(Ht_2Dxz&K@(yaeD4Z>h$P@>10Aq4{VmAjI}Vvod9c;grvtD$ z|7XMggtOu_rZsg((gZW(xSSGbQ;>O$b^L``H?GRJ9b@yJMap5Ju+4Yuetef&2+Sjc7vx@*h|i1)TeL_c?IB{?AD6zw2kJxLE|-QXo$_J^ISW zpUWkE?ZQr}TCO4qw0cENYY90{s&*C$T0(#8KXf!Ck>h$NjuzB&A)g0F5Bj3>5l?6Z9iWz|V&zL6Ll z()(21cRg0Mb*gkR!t<+R&%vhYuxxe}kT3U0S!pRlKF>4GDJ?Few7zKyavW;`ij0m`$3e(qq=#zOdFt4>cOx?I-sp2g>GP(( z4YZWYSJ!q^nd9i9{G8e{1ifHu99)#>=JobO1E@niu0R_$H+9PCAoBA!^p*E0i_+xN&2Gi>%()PMV-OSc z*@&&ElZnqOsWU^@h@%pOfGDyt!S~)3%4h|@e@{8p&R#n_h?gs_-aw}DAZFpMzsmUu zy4NZ_J=unzJAbR*XI7dU6&)l%*XFW(^xH3+&aG69J+HjA(8~6) z&<#(kAPq0lFT&8wWYL_10*b|_MV;E9?uDSe;C+q9v;!pT_?F;KenU(sf-#WaVN$tb z*i)9L9k*f>IoRx&kagbA^~`PH7UH>3kJwEYCA$$XsHee>Bw`CF{ZGCS(_8BFcxpQ- zO2brpu90dUGZ^M625Aq}&=Z*+dbQLI4nBuIko^vl{5u7?i2`$D_NE(eF;5HY+g(-a zXm*-#VF-KdzO>-^zYrV%6$6~Wp%N^&85#7Dnu~*O9@ab1DzqiZj4*lF>0LE88e=t} zz|bU-`E5a4ERGn|Da7wdu2w371!I{s1eYe|d8;DSQ1D1EeWi!(tT(VV6Q|@~5`LTvy56Pn7qT>n1v6YRU=LAEQxw7AK=oNy z_7#<3(+W!F-e5)B=Z-?*{phD_3{U0s087!yZ-j5b1&zU5H(4%M(B}9TSbTb{bhAmg zy)InIA~#lf+M~lw=kqr(k{$pY@CU9=ir9Tf*!WOvF*UYo8uQv%F7$CG57mBVum`0UDEg^7sf`8u!l;2zyt)$7L)V z)9Y)e{@Q)O*xcgEp-~2!{rll#CjdR-lY>-nBvZ->pbM>?Id$z0~Q1@lvas$e-#}X!gOU9xpZ^yGN^cpr4OVV?%S}yJdr(`2tn$8T2SI~qfX$QC5C4|b+FWk&aXbtv*Py;xH3Q&(FdczPzE+vDBEXm?a* z#evu%*bLn~g#05v%-UC=zy(6i^mkxyeklN&)91LGM0^Vqv|Z!mAX&z?rnG8smq9tl z>MJ*d`qgbdkSsz>eAUP`{SOa*qstDT)=332+zGtrsweQ`r~~9O zk4(tIq+4R^^*XZw^@q8JTgfK(8xgzy(mv(gXH2!~v&$&Tgs z+}F;3wBGOk8qd4syYIKt-z+*mKYC2e4Mzv^k%cDOHU=j${jlPrX^^t_WH{;WrsDOzXt7GJAT$pk6%e)&*VJh|LIRg3nC9ksd-$0w(GylPE} zVNoA|4+b3@%lt>{Soaf3g0?cCP!3uB8smi>}4Q zhmYeAWqhRjvZX#}T|1p2``W+u7b${c! zZ<_p@QT1T{5d9Zdc;rs{Ee*ZlatFz$ODAq#m}lT6#gvx-BNt@ zGOe2&4Laq}5^RJ=m;*v6s5s#iNH)5pxS4y1B}s?BkeuWog#J=4s#fE5In|%N&NwP? zZj+5U^JX_$H9`x2X)%0y8e(VIQkZn6B#+djt=+CiD+tztA_^s5t1{0J5%L%;OPvsApvs~wvO0-Isl!T zJWJdQtC#fU$d9b3Q)NhH>ov)SMBFu7apySUOMTm;KUFeH4lr<>kyx+IE)>J(FfnoiA zrC0B2^z^jXLOJ@}9EyolQOwnv*8VOLC8j<@Eh@>O=*fr1?nw`x zZCpecx`7o7jUIq5m~+(3AsAK~nTa%gam8kDT&C6Co^>_6MS7{nJR11_lwzZvP&zJe zH51fQPH(6O|e2hsNKY>Eic2pCR;O;3TVUKm|34`BY8U-xFqeXFD4SDdY2#epQ@ zuw|r?fe@aW_46`mU3hG8eSAUrHX!qE5KB&=tMoF^8?6>q9Eq>C&ONLemd+hy z)W%d4h6*-kWE@&94unVoO`FE|ZJn^ckojIU6kx%wfoC>SPPJ`JmT`^1ZYwNLrDMXJ zNH28(bb1)1?DXXVEGy*@=v@k{!`)!RiyWABnDGYje}T zI~&&Ey#r7K@Q_kw7LScxx>j8(O4o&uJ7;csVj8DG9Ld!E@d zJLcTWOwUTI%v7x^IK2TTm-vh>A=beE7rol@7qZiXI)aNq+)#ub4Nl-4xLdUk=$|&@ zdLP$oC4_}A+gyjcZ$3SlW4ZRJJyyMYMX$Sv*YrQea7%unXpjt=Cq)R~b0?LD`x2RQ ze^fO*I8jx`dwJt|#YYR_xCW z;`J%Km~JHk?kL0=v3+9z8aqzu$z+RNI43sZm6~JB_^nKvI>`5ASXY0AKhTqrFwMN` z*(0v&<#jaU&d{_@e%Vthi9wQqjkk}gUmRkdb5$jIVoVCCe-&xSkZ8A7_?$58%SD&^ zM6Szdn`BTWTpX#vjlo(3>4-GRDc*B24&^#wV`pVpnq=s@Z{_mGQuQhAEHpAIZ0oFJ zC_s9>%ZNCXhM)#CxXGN=BTpX1b$U$c{t+%J>xnF5_0iHgAwN`r%SRi`Hc+>4j%<-$ z8deOwORcX)FEl@;y$v;pmGigpiz0u)y+b&&VYx_7*!?g-01GQX0wnU##srzdg7F_F zKIzD`jhQxgZT^roKdSiQQFm05*TIMXNPGTw`tv{WH~&<0z55KFR;x6wPZf^cw}?*cj#1w1*lf)F}r>$!eL(#4{bwy}Cn*i-r!4<7ssT5-$D z@E9pAhret*Ia=ZGEPsQ@Q9e2bzaD&WDT#@r}XclR(yK>n|n$acr%${7T?$J-+@Zb!6Pl$3C>>|s$O^% z0)LBvZvLB}*6#DWoVg-oC&U&#J(vB_f|pb@9mnQKbnugilrDDteX^6eQ;WnS*#|ru-8G3d>`#ch-fM7=V%U?i+Q$q zeMClx(P$8v<_SMvhf5}PTn+{Gjk##l^F{{JrA+0|>8%=K_1|~i$g5Wr&8Hjtp~qj- zC3?unM`u2ti&cx*E#M)|%`zjxfe=-1u2_GDi=2dl+EGF|u?Ncb)a$UwsT1|*=B8LQ zDn5Mu@N=pws!ZW^!8`M;^U2IHT@dDrO&nZqHVJ3;HWi;D!_uzIT>Ld$L-4qa{YxACjx&MQ1#;c-xVNL?L0U$KnY*#z0|yR;yUNTS`^%yd7i zPt>7F=IVp8%q@dUC1d^EasK>ekZ&bC%If|jqph+b7F;CXPsooBtSgGfgF3&ZQc81t zZ}_d#eN>)P?!SGpFF*sdm|M?E1XJi>yepli#K+n19g!FP)AjYJb@(HasrO_<6B88O z^!PoChjuZ9&Q@tjV_5=*sW^9kCMGt}@J9x49EV*Q!gpo@iave{peB}o zp{BA!a2P+R`47BR&hd%w89zTVYN#)#xQ=#)j}t_ioNN*&Ow z!2U*XwB=5&R87IW(qpU+znIu1uiUJ^ThcdB#};y*e|oFIX%@>00(0lbzmU-h`~eTn z<+w5Q!wPnn1^_FaW=*)`F>TcEGt`_0jk&Z_MiftHTGPWyzj-t(@j;s(cV%e6IMJN) zMT0hTR!_KbbUi-y+TDq3hU(!<-Q;eZAIN$Gfk@^)!T5-#UI*Bc0Oj-CeN5|lA4y8c zBv35kfwS|+16r_*hZG)zX4Ez(5q<;;Eg%vBuHjySH!No6zQHi2cz_j>P%D3sT;s1(2Qa zICCVBZ|G6!3xbU;k1yTQgE;Bv%=`w=OMqZ|L|VM@tpZ24QYk30)$vl4rZ{!V@){~V zaAW*Nc>fcl-@d4Iz^aJKwoUD>|Apk;{b%T03(Fw_FdIij6l?H0tuDdnd|=JIaV;z} zUunJq5~YIu4^zw6nuUO-{jeZzw=Sio=7*Nn9Q&&%*T4?!mYxe{?VKVdSO9V#JUv5T z!%c%eetI+D=^3s}@(z#@gw|%bO=ZejntI5kp@$DaIfV4KPX((9wS+pZtac$B=i)+4 z@k+fTb0D%7K^$SRSf)7gR{(|fW?d=5wJ;X@NjM2RZS8bv%IVt<-H3^cH1?#ovBA&a zHJuWH>w!)Sjl`ow%zTssW`_v&Z4PhH19O#bsJ@l)+*1s{mei#mtUpCQmPds{;l_Q6yo(CUThPWRyg zL?33!`!$svuH;J*-=JyNW zv!JKZQJ`kv$iUAu&;y;lVQKsbkh}Jo zT;IZraE?a1rv7s8dKb_9tfiqAd%>dwAw=@ey!zSCGaDD+>!}g^9QFQzf696T4m}xXJZqQElj+C?ek@p|a37Dof)moVskv z9$)Bp5Y8!h;(|&wv7CMernS7`JyHfxELhHUVFsE4Z7olp)p zi>%b8>Khq9t)$LOrvXmA!xlVDK|o$7rE@HIOwOz%WYEdsqKM3-frCyDff>}1yZT@y zy!&jslRQL;aK!4M>k;D}YBXXR8C15K%wk6RVo;yDUMV1SOq`tjRVRh=H_6xdUyqk;>*+Z%_^;-- z|BkmO{u}>>u^zqigomrdbR>BZ*^FuiT`S9twtm{aPq?5r)+2&>%_+*ythbn9oZ&;= zf~7)rq}^Ca#(|sy%@5=mRb}^FMv&#=<3~YV%5EPKagg(j6$z9P$N)nP8N| z5%I#SU~N+jdEwgUZh4wD6YZiZ^|I*i7<#lB0rAo+X|P7kW39FuC*L zYG6vq3HKmi>p2!j(Z>Ov;24!!eL@5m#l4(l1$%*wJ7QH6u1)qPOf?^3`S@^rJ|Ty? zqvFA)CTkqnUdW>dO;I=bA*wc1hK*2?>-4aoS_lS8in}wS(Dlf>D)f#~%zXmG`$use zK}a4oR`(8_7lHr4O5}fmmH1;+S=1aZRuUmSEL<5j9+~tw<99(yD6Zpg{#@bN z;Leesr2mfQ=BX=E?Nm$L9iQ0unwy)U+`@STRJ$xKY4lz~YpdUcw|8Xbqp<8`amk68 zmAaBCo_4e{ah#$LeTiA6AQ#P0rBmb-BvXJ4eml(+a>&q*t5Aa;gflQK6os^#dFv?orTUKUK>0nbc0U_>4sWU{o(^rl zC=y~0Bmr^Um>;H#U_^0+=Tzw^xDRuH4CeSgJN+*kIZ483--0i*NHE9uc8R<%7~|+y z9={*c{kTnrI%7V1uTOa~>j+C$C8Q0tc~@DS7?r7b`}nLTsOFp#y_gYx@k6Zo&*_V; z*1_?0*klbUz_}R^Q;x!25!?Qw&W941ZigYn>Lj@Rb&&8{&!VEi3EWrao(l#v$1-V6 z1WV~TU#g?$X|~alU(bjdB#FOn-E!qCB%JA4FkyC>qmwGQQ)2I@hk(KqL3^ zo=U}3YmwEWapTz%r1XZ7eU5~x;Z29F1S$#WE9KcnNun${+Aseo0Ca$6r|tfQ{J??4 zxez4I4w!8;=>K%7Ay&;SyeKqxsEvCetpCMs>$geK`&7fan65BA;GGT?+G29`5$T-d zCboIY&N!P&0-Nd6Vo2#Z&wee#VSe~S>rhlW0Mx`IfMd-0_DAa}?U=3mUZ_KAAe7I^ z%7VSCz{bLvx1Db zYDh<{WtF4Xq5AC4Z#f<0qeLF!*w$Gy2JJrhNX!~1X4~-&6u*KM2;^V(i}@O}_(Fv1 z@`m**9VfMq++I{kr?5?F%k^w?cqYw@$E177~nO z>Q(3bd5X!hA36gVWq)@+xBtn<^ZOw!b!K(*;vinW5TK497>ZV)3}U+jTu>_^NTWRW zFl&y4jRUHl${c)8Un;5iK67sK)* z)=nX|um6GsKoOmr;dhXt$XQQmK6Wvw4FNqU|Apuf_2%Z{@n(wD0NFN|3E65IZMEACW z>P8&0%!n`bjvL-bk5@et3c9kUS=6(I0|&A$@?QrsAZ}q)U{a9Tt{-?ASKF|A0JH9M zHl~o=8cz|ht$hAChAB<8i|pFJH#2XhDD6*bX`Uf9KRiJ<9vltpiyr-UoK+{v61|VY z08rKe@CNM1)+^|X;Bx68O_0HpetG#NVIu-1fZknRJUiR*Rk&XE6PAe!T|Kq&1JNND zbnc?klJ<1g074Xf3Ok@+_oE;{y8zMv1Q-$sUfAFZ7-HHk3hLu@lMoRhRep<=jU(x! zvw5e~{8nFQ9a}aVXgM|8Dg926&Gg*z?08xOvD1i$_c4<)QTbbM)kxW;ft*%VH3M>f)1dv*E z({X-4IQ0$w^AAP06EMlEqu^a~glwz)!N&0-Lw1lSu_P1YqWopz%^0R(iM?-Ky4=w; zR&CKCmow#jI&^Z3zv3$w1=)@fT>ei4!0&FH6waf}TW!`9Htgw26Eu$PW2PV5?xWz% z_#H4~+m6uRGO0`mfBSAy>)Y~(eRzvM&xvw62(|U;EBCTM!=~j9B(5)X1g{s!;z7u7 zCNEONIDwR4Tr3NAF?832aLGe^v%n|H(1_96|BY5PG`mf6`tWqX>Hrx-C~Fl|Burx8 ztvnk9)|<^nJ^GjF#Y%%{hecV+!yqBBD-$PF)6P;iOJi)YCt5w&OHn={Gc2u_~1uMZo7CR z3PR9F{&Z>@Fu!^!W$dU$du5%misj@h>V%?7p&!R6`?+!5_!W+d`a5PFC9SN_uiXv$ zNDC4C@d3y#su58L1~qk}ID^7y;ONQS=$qb`T|S5pDqpQJIei;;bDr66vHo36fHuG_=wIMM16Trw24>-1svqF!tt=LOx}euWLsf5~3{ zCwSlgeNFzV7>bY&s%dtd!Wv_$%30I@I@7FY4K!x5zf$jN_c+k$gh!}U<~!&A^hp-+ za+_6Zs~l>}i_yQUBH;VhskT#HQ8Lj#D%MW=3OFAcrZt&j$m!rp%WO z(AvX!LluzdS9)fv+9bv={3)fz9sRsf*|%6h|0!{cU>c0U2)H+n_UA- zbLDiE@3gglPGF&^BuEGA1t~DdJl$YSUr>g+ic&Q1`)=&V&8TiRxX5&8T z4`Tafy=3C)Y?3Nt=Y>n|E@#+fcz)_mHwM+N@{f)(Ri2$3vk%2;_66CE ztBroaS3=3j;PAxJA$A@AxQ1i4?$)0Jg)k@N5q~+=fjJPz0R)bS5@aG+WDCT-P^d1F z6-8dq`aco)?#DnbQu{Q5-RbnQscfWh8s*8pyPoSM)vh&$cOd~~X&^-eVv_{G3S<7w z3LDJOcecAi1+hd$K%Rap)Py^cuam@P;DW0;Cg11ktMC^i=M(x2^c=*8TAp!cC>qOY5F7r`(|_G6DoEZyObt@U$Cf z$3LWT)7l}g(J;nttK#;K3!e_H-T1f#Vj+P1qbWy*4EQWeqM)lOe#tfbE+a%#VV1!_ zsmlv0H~Kk=klRXKgsCxCihc@+$WtIP=*@jZlueP7F{d!2Q zlzH37D(Gyph=|7X*o6k|D99Fy7Y!j8zeHd6D6;6pIQV*nXFRxBk{kD{CrQbAio1L9 zgXkp5gJ962K{YaEG$>qGJ*-y7|8%mel>5qII|R(?4)L24vSh>F1Vo=TsU(Zch!JqS zM@liT8>&c!2TX7>i0CW`>I;-8G5dY)x6tiYz!P9Pr0Y(zW;JB$Lj4b``n2D6*H=Z| z6aR20eUs0m5@Mx=QpE-ukigauGw1-eG4Xve+ zD4)5I!!rlodl!$T;XBZ`PE))T!6lW3zSIw-D5Xv3bej(X#814lm{iylPgz$Iq~DQPk!t7@*x=+cGPN?md5&oFEg^?8vGNJ z=JppSx_v!E6sv1rseZImWe>VQ55Yc!U9kX%C;IK2EG)3UwcRg&9IfMT?fF>T$ywdT zM+7rVXT-(b=ff1_!`%VRqx7d4xAL7+ctGKIl>LA@{lQ&f;E|g9&Z+EpIxTHI>I7OO zHJr&2PCWAXH)0NKM*zg^dPof-cg}Pc%!2~ z;>$eVypLbcV1y@UjFh%K!f5D$-1R3}iG-TLMm(!pes}d!jDMVle9~eh z0iM<4ESP^SX`e7Svf|{6z8$Wck>mT!o4E!GWh{B6Q!Og^eTZb;p}|31hzM}capbH` zeiNKs`#{9zR4#vn9z<2Q|89spDlMld0Cp$~0)!f=0bCiB8MbeIhddarS3Ojf$$kop zs<1DSgvrpP=?*1s75XyO=xBQL$1WE6Bb%KiXDX^fyzUcjwO8ikK9rZqQ<(0JtOf)NrL?;HM1Uw%8Sj%Y~)UCLE;T$Ks8Z+-23zP~Y-^-Aso7))5s_*5kUqwchd z{NX{lk_kS3@yOHx<7=v&e`4kpj4z0VNZ$apXS<)zBs{RS{1~N@fV`0jw&xs(V)1wm zP~u1hiiPFzzm9YSy1ZRyYFcA0D+z7&kN$I9;e9~$qT2dDOfk6?HK>WGB~;(ui46L)Qhtdl+I!27k-++olC&F>e7u~~6ibSGK5 zgjl+YK&7GuxmGtVkKKL?-r*D0;DdNgnEOaJTX2$$F*B>AGZlnVEz=FXsRip-=nA5?c z{Jk%Y$3Mk3#ccLQ9qAMCt0-q@W(@tn%9;Q zGi4#GjZ`6Xohr}W?fXTaj!ibT=T7)>SB6W!y6r~Ssa}duFI>XKV7FW_4EWVFusR%PN>(dEhmT-DfL@9YVnu zc{dVtxiFQ8k5f~a_V@R#H~83TRmEr|wl%5q6e@cD#E-oW6~ED2s56xDsjZ77Yf1xn zkq6o#{$3HCap8M=pH0n3@oLK%+L^Yw#A3asFLE^Sdz!f0Cr{HU)8Zws;D*Otq-Xy^ zJTd1X$^VVa_5Y0phjATcOE+?Tm3jRa!m?;D+w!@}$$ZjZXQ}y$s(glB^rFBmU+i-! zyxj=#a+v@20kCw)SUqbN5X$Ag2=2b{E?-={gT#2wh- zDtU=|+W=^C@C;a>h_sPWeAsS>E%CEvMTIv7u%{N_Vkt3E22y5r#PK=G#S_vcw-Vx(8s0gQl6-chvU z?p}>0;na>kASCg!a&y1JOzNM3@SezunVhAQ!gx0>-6R0WQl z9HT>NC<3c#Qp`1jASWSc0h{!=AMhP{kbrI?_4L$pHSbR{g+&i~Qf5xNDwJp5ea#F2 zTD|79V0F_H?Kz*T(>lRqNt~GzZ$MJpXmY0~!ZH2p{lGph?g>@F&iJmAeo5?9%|27J zi=k?4A7kWE-_dnP!DHNyd>>FP*V(J;@dx>P7}kpr^P486MrH;*V;-h22XEzMLL-$a zOHvT&C6%;&_-+pP@~gA1hhBdngC>QrX;*SG3>@S?v}6}G;kszo3X&YyU*;P{j*P_xdYIvv!9W<}Cax;QTAEz^lecPR>TQ|UAMM2M@1C5~sOFdi%~IyY zXkSvmyOk=_fXUKLcfh1Fa&GCSN8I7jW>#@Gn}mR&VJN{bUa;^AcscS(c5amJnBthv z1T{prk5;E(2iFfj)}wCG=&2Cb^%(-Z@Xx`pe}|GfpIIg6e069Y0(2HV5H1 zh-w(HqNCaruT8|Ud{qF*SAh!iANi`v+4tR)qQ>wbwZgkcS&jTZP)H}4%1u$3yJ>t6 zM74BsXqp9QYcbU1tAwh!B3V@HL5R6n)fcs=9oDw@RFW*2hx*l(6!xqA(hfv+RsTZb zHu*3=q%fDW82o;vtPvFaoAwS86f^eC?w)(jpUFn2J#|ws37`vLYs=G_wJOmhm%F?6 z1Mk$kpME(5$_(v}`Bk!4I6XJg7c{-=Qhe>fiM~3}~VZd^g^U*=mxo@08d`$QKz(Z1+brr(?q7eI$%g zGzz%~*V4la_Hy>3(~)ehkbQq|OO`5{2Gd459OftkGoM7Z2T9Kb5L>zKQ~7Ot*?o5S za2mDB@TZF9Q#(|h>-X}q#VSFQtTv>^hw+%^dxI~tdX{`J`%KvOJbiCn&>5{o0a`g7 z0~9t1FzcHjTBAVlISPe?^mj1}=901-MRMhjjxecKQtJ8H0}i}Srv`#fG%!zIhTbFm z+WIvv`DVhhu%or?+`M1(4hLwbMN!&NTau&{3LXH$0WbvFw1@c6?^+|@aAI4?#L`!4 z23$G6L`tj&#UsNy?K=)}l!cg&@hPL!_fwt&@r%6Op(>1H3quQqg2VjLm6=i7)WVcS^lB`)5}ct@Eh@0&Z^x>|ESthJ7=Fkca_T*`t0 zkR5JE^8X7_t3Z+&0KsdlPlRa_m|qYImIWoMDoP0rS%zWkj|Ok++L!#{(Ua2S&!~LL zV4Pzd95{yAy0P8=AKq6N4!PFW!GZ~RQ})4vMdEyj8F0q&>0h7x>K^2We7C;r{N(9X zkMcHclkUAg6TkmVHlDy`*9ZKChy%}-!&BI!x-6PQ_}VhKhP21OuaS!*vrSL&k`Y7Y zkwry|gz0Mu-9=_6EDRpHTJj zu1y~bN)D*qodpkFk+2TL8oeqm>8H({?5(vrA(rQbLv*PJ+$2BX-r!we`_w)Ll)4Gb z(m74RVE8%*VN&ldZJFGLWnEgICA3D>hhxNNu~~zMl#>0k^HWnwa^Z`0mn^hOIOI1! zhJn$>QwjQ6VZR2IhoA>ZPO^_8rD+6nAa<5IsOl(<0>6IgsX}FrkT&DpjlPlQB{%uF zj`Bto6SUIHmN9dD8%_Od4P{vEnr%!XVq(dX)6&8%0gn&(^v^ZL4l0Sr z)@aUIWg*UT&%4P<{&8%4aa4$(Hzhp+zz(pG143Ca_p+nxK>lAAS6$ME3kIt-x5j!R z>6N;zs;MVNZxI=0I>Q4+%pUxOxQ<^ca`C?6?ke7SN=L+PB|kLIkvxSPMgrm#3bq{% z2zIG71&#A5eQ?;PP0N}?fwcuL*l(npg<^9bk9*f{@n|P7Qn@(Iy{_}Zi{;544~tfO zdBSOx%I_z`))UPGz^oInU%NK5FhA75_UQnhZAb$_RvGqU@K^GxihVF4T)sP~9h8~+ z7(%si%daTVh#AIlkpDj}ZWg?FctxL3BQ^wW^XaV(A9NlW^&UIvi|}`V0=sBp##{pc z)G`RWev+Q&X6d-ab@xTtnwrwir1y2DP-4}4e;|?)L z+<*?Ip4$wwU?cw8=4w1Mlx9|K@P+NVNq>2Ju|Yqo5r#%Ou}hAEQ=sNv{0nmmil06|F0s z6v6RW&oJ}$>A2C*r7FeBPPDXI|5@#|W|KLQl68yp>k!|?ILZYQqN%oY+3(kOOu-XY zwadiRo0{8GakaYh)O{EHSkg33lUAoO98Q%7+SmX!#VY6%;T1O4x?Rn-Irp{yMcjKv zHMy=`qd`hQI!Z55il~4{lNJbAD6%LviZlfj0qG(E0-;EeUIY|rN)r(xy%Ty7=}iHF z0MbiB4G^-BtKye;6cT2V_G*XP7vh)$ z(`oSO)l8UdJKen{V3y@IbqDmkP_j{a3ydrSk}U%{F|!{?vinj|P_+Dfe=R`3_$e}C z?t^2t&0J)9<9!Q8qU1~lbT!WY@Y>s1vWOUOpYrW9Dvj3uuI;`(-g5uwbHKZmm_c)_ z#;+VUylFptM1`U1V{t=-1uKY7*Xz%qU#T+S^@==np*m92YZps(Wo}zTgDPDWgk9q% z!gl~A(@iob^fgYgk2%RkX&R)McDq+RcA!hN_FFP`t@7lLfu@F6&%h%6m=JK?Mt9NP#g>@p5rM1pgAro9zuID%QS*SRm8(|D6u^H}-Jp5?- zqpE6ycwONrp>?RTx^-hGl(Gn#^RmnUnz#ysG{Ed@a(a&2V}#w!6yrvxe$VQ56tU}p z0Cmk7=8y(X{Fz_cxO(Nz^m*!dJ7GJUal|Vbw(ECU@z-w)v zBREO1wl)Q~^Lil1?YC8J+IhZ!{_xyT+AJ)?pZKgQ5&yR*5n<5XmPp)8OJF^)& z#f1-v@XUFZa$oA_uf=sS$<2o@>bzzZI#=kp;BLOU9`_BPv3*BMB*i4#gd@QhliK5z zZvHC%zXofjnUP}6epL9}tS3)EUu#yGn+|H9u})iHsH(Fk^^;ko0Hm+}rCb*YK$3Wq zImshR3-nTQCiA;1SJ1YGX<#78eQ`p#^m1Yz*X95m(fAHdFZv2bW^aB~Z1TR(L}u|d zVmWv7eV*Qrxf)pNYD%nx3&-LO2}zNR)3L*9r}(_f8@hr|0N&x&DsM8 z)(l zq~64SfU$hx*9qIw1cgcrz&$Db)L5b|ECihL>A)Oo{0W?QcBUDhDLIF{&@mUiS+1i) z|73#C?l9gvUSBx9JRi`S5p&FE?F5+nZAaniFDokUJhB0LgpZe_O=2?lRVd`j|6w+R z{>^NHXRrg!ZQHw`CO5-tbc;Z5fv2g(mmZn6abBxT=d3;5U!~}o5X&KsYE}=SV$$GM zpeWe>*5FTw`EPQQ!*A$F3Eh`odjNKgHlteNAgCVVbJt;p9L_Vj#%=Kv(K_2@w_@IZ zKRO^wVrz*7+vJ8&Vh})kz9(o8bge>^*I}g?(!nY=b(-|R_Y!(epN1&e$m56M|5YZy zFO~iB`mpVH=*N%I+AI35K`EqJ&>1g=?)rj~2On1eIEv}034mL~D2CrB`+2}fRQ7v} zk_P1?FIF9nm5>|a8$xVt;l3V%W&q7>I?_fd zCn4wLD+fX!)lLlr7U|zxHy?|FbK)|>4prhEk_PNK9A2;Ig#b$>$~PVej}Rq30kLfh z>|d%p0N5-3r#X$;@kp7ZP+1-RWMI*qHN{?BRLNa3(dZk$Y+9-nySU8M>p7j*@2fs8O1 zKw0>O!A^($3(WAfz$x1OhJK}W{ z>H~jRnm0ai3DjN>*Qi$LmVdlyo*dJ_N3`r%wnu5_MI-~v_(0Z;Yw{AAzq`wVi0S!u ztOxdx7-RILCDPQG#{CT0HZ+f(Qa;yJ1Z~R*>@W1g@ zK<4PWeFnqxiQA#GDna&~U$_fDmWy9uQw@HtahD|>B1#qH@8Y`P$2f+tZ6j#B+f$+0 z>s{*oC>C`q`@d&VcuQrT||IJ6ksfC=?S0)P(5_`f9p zW|!=st~zQk1j&1(fyESAL<{%I)%~;@0g>Ee!_D2;znR0YC=|=2#RWel6N*nZA)XhF zelCP>h;e;fSS@CEEpt%XEA(T&7Z+58qET^%FP1r$JYum>ArGGN;vM}P2sJ8d8kMT9p79LtxCHCQD|-~ko0(#*O#N& zVZwAGGR*HM?aerB6_?aOj6$zWq{lfJKzWFRD<`BurStKH@M2XccqVgv%<-$oW zo`H4hS0eJed|Z7`2VE?CrB$5JJE+6_o=ai>>?Q=|ibqGo)l#k`imY*N3Bh$dX(M%_ zZ|5_({pjk*l-punX9Y+rug!9I2eI>TB<5!UStm3YyP7?HD0mxk*wFzKfOF1lvh;mi zIb$(7H4(7r8_g=%-a(`jSA8UvYRlbWpjM3EpR)xNF$QhD%r>R|N!C&L&aYOX?U!3k zwAS%7dMXquQ7Bu&*gtaM+Y=|v#WkQ_F@@bpdI&k(Z$r?KptEUD9ZethTPKm9dzEOy zziz5#3qkZ7r0vH7$?Qb=Chy2(GT(r8dEJYx?|tG4(ioSVAM1T6Yq<#kqjct&W2Wfv zn6N854nLiXoYr|k64rSWR#=UbxFSFEht3s7JCq`^2PX+z*;@9N-8Y#2;%9%ulO^(7 z*5>nbv05{6V|<urvYF=eLfh?}uZP}4q5IhmJah0kl{zdNTlAW{>)cab!}K4~Q*xJZ>t z*Q?OEe>jIjhr_n+p1_{RKGr}YkHhTqRz1&dr4z{ z9Y3LEWw(Bg`OMeV@(9KjMecScY4Aj!!4sK+CwfvB)Utm@;@9U;ABSw#P6I-{hTd-| zB(6UW818R39FY-D=+!K=(tgH?=r_6m9Qghv`dxpBT^F6b(C;wqoo~*!&M~1@3c2$Qe9{h_~HNY6w3L z2Rkj206h-uBS+na>@jNYCE^*AKDgRr@_%_^N@<*yHgrtZld zG%Ae^O#=aIFdR^dX26%@(nZSE+tCY1N5|h))kzAEUNgjN3sK)UkTwRPF77Gu)zhzG zTY98>;FBuA9J38mEXc#k7X7W#wdQzQJHOo5O7a);_m27wns?Pyn`@^}p>*(lQDyY& zmf8)hrp7i?5V0(hGn*#N6Y&I!eCY z@S5+Rzo^AY7%`}5nDN0}7`-|DNzORz>vULk^?=RMt*;W!9uDpTV}C_Q*9Cr2VvY6U zSEt-|YA8PrMa;9$ZXT`!E+iB5StUrW9pHPlx%)zC#oew?n^7FlDHOG+_oHO+b1j^g zaweK`>^Pb>L|?vq&Xn`+M-t+-YG951te-r(OArWZgPsDK=NV;y^EkRuE%yBA5ATTk zKt2G&T*qTuF$~COM{kDv9kkiw6N~Wicco%dd}(} zzeF5T-KSUqM&84!r~ZbdD@~J6<$)wgi`SVbZG3y>j2+3;`IESk+*fP2?I;X|*%K2D zy$vIXO68I|t_;^Mi^>{JF>G8I$kNH!WRIVRq63$yk zYj-Jjfi63oDOm6 zaD{45UaZ>m!(D~TEv1yPxBMO(O`%Ji0e37$ZUE|X2Vht=1|v9aHdk0s4L>2wyXt;? zB-Z-PWZr$>@T%f;`YYCGJvFN6lRm*NaIVt?J?!AU_X}q-i;^dg)38g1W9ZGbX_8;V z+aXy?8e|`?J~%ikF>UkJ#`$~BY>b~XLE4XviyQO#fL^a2Kj*ROzab@?q*yEikmB}{ z6Lr+UUqXC{Pk_pO4n5-whB#lLe)t_EXA725H!osMVywmBO^BF6Y@lUY0n1(5=QN5` zE?fzbV;{!+$>SMYIRgfW%AE%+0jQ)8^^%Juk`S=k(wjYxYcvEwv#vRTdkIL_U zStQ<6zn%C0uZK_0)!Oqe}oj4pa_L61pEXmgL98G>RdPzQmqB5*bn+FZ=f8&wO$gzc3=h&(=7w& zm%Mev42Q=(finM8j%x)0n=K3V&~_|aoy^Q8gNZ{;;r_cPzLjfMKfGdGxSYN*tJTG8 z@zc1eA>Qu-PX}sHSkc6B9tRD9x(Y6n**Ib^9Z&x)ysJ>-i`tdZg5Z4xH`g7SdYLu> z4tz(e%IN{QT4i4&J(o(3!R%KqjVzr@>P=irF1P%ijV&8BqYp&V8Nqfamq8A@4f#Do zq`(hy&n3cTbK-&^`^ik%LFxA!X!qbo>IC`@KzBH1|LiGJzQxxwKVfPL0{Sj;k5@x3 z2TFT01WXz|vAj&4{2Q_@4DRJVd7>Zc<#?ovJniuIqiaE95$>;&heg9wbFM9C0>=FH z%YDqGJK&eZuaw+e;pS($LD_i@$XIW1YnV-K`mIhyZbP@c zC3W`Y;j%Z7vVTH;&;BodMOLn#DMHDBn^RKQAh>+bek@Pgw8=IO?z%k2Q*M7qyJGR zpBf^oe)t>Wi%x_1%{nrU0mB-+5+l| zjly%#T(5amPGrW69bxKC4K#RHMbgmg+QZ4|T5YyYjkAw>XQV_S>oakkMf7`EFs++` zBMS~n?y+#j_mOWSg3)UdU=#zhD^(mZnNZL?&Fq1EQE%eBu{nj1n=O8A)||JoDGm%0 z8o3p?6*_zM1Hs8PhK7h~P4X70dsrCNGvVdZ<#X17w-b5)TP_#b`ss=CSZYnF%7W12X`U4+OJc&!=#4Get=4YY*kX&O$mg}i6N+u> zILTLjH29>iDMbO9x19aL?_BWu{WeqO_G@l`l@i4!A%zRIH5Vzv2+7SE)X47p=9;bE ze4d_7RDN!K;^NiDI~dz1H-&^GN!PNO?caGt@BL&X<~}XcM854yjmX@8;Op<*));C z8(~R}^gGfX6A6ALy+WOVN5@BsME%r5gLG10BORNjgUXQOpuQ$sZbfnsB`_s5k>(BHBPXR%9 z&V>)SdJbisvB=h^cE8^Rre}IZPPnf}j)BDIlgZ^GaEYiggJ5@%u^(Ru;5&TPNGGE6 z`onaz^mPTQqd7UBCImwrY$l>R0?Y8M*yNX9p!~a>c-wKs=6BrTL6Z!&l!}c-?ZD;0 zb{6xx0V*0Jr$ti7Md{ohnM>%dsUG?jFC>U>IrV}%FUnaXjMHTm^}Y#yQ>37>yx&-^Puf6=%nrLy1(io zV4*2{%Qpkj%yR1%tM`UUif5)KD-JA_-+eHAZmoQ$InXsBpsNd>U}`~2^uk$E9%mCx zwZhvrTtv`Pzh6>jPOh~w10}8bS&BuT=_ck-@2LX!e0x>i*O)7o-xEJpr?5ZWcqtT2 zwX49Fnx0!($2!BzfaFnrgRe+n4;|N%#=hOEoy(k@uTUwCZYUoH2Ooz56cf?CV8v4@ zct0e|p&(RRYv7kYTat?2uuGO|a}9zDN#7y>4V;yvx05oa4`CDw_2S20rSKH_s?0EB zlj`P>2TmJf_lVs1xqHJ-&P60S?&_-8uJh3=$l+o|;mcKbb|8Qb-=qXYc7?nv(*3Vc z{Xpx5-geEn_mq6K@h|bzki2KLn0Aa-gE`12+E56vzRuCBbOzaC{;23I1id z=lQg5vxiz;40*2Ig2x|R@;>ou@(24Etr5PUREEpUZzDpLY{relAAyv@5?e)>7{`@) z3>EYHNC7B1pkoRH0Nvy1gVV%Xa@7QS>8=5E^iA}K;R47v4O*dd;S@QU%vWlNbojxA z1(SJv>*L>UYeKbEQR<@b%bllU9zpmE^6>ySz$Is3j25QLhKA!$V|eP@240Gv_SccA zjn{$*WX~|)UJ-jkECw5BDbS+RoQwfxHQ1GDWZs^a!z$oTUkOyc;jEO}pP;(CplLG9$bWH+xzsEJTTu63i;*Ti4o)kJ_s zP)0x=t7kI5%_eBB?Jvla45kEyR19q)$I$U$Uv~V{RX{cneddv(x>~Y=TQ^*--31m6 z5nfLwd#Ym9f4>ieekJW-O0ZVW2Q1~&M|42(D4fYOM&{nf#T?TE`jT=3#LvGxlzb)2 z$k_rMU7ZpgIU*m#t#hMVW96rQ)ryp^xSI}xx(T{Pl_a%^O9fl_F~BQ)0I!b*Gj&8!U>b{+J#CWk;rG3dLc9qDPvSauxdZ#QM)y$RU4GwcpB=a z4e^OHXY?{!*933#zesa`pj*~IQbG4e%<3NjyAt5^qeEnpq$Xl5GwyncCK!FnJXKnE^X8fehM0~w!zX$NpK!x7Nm2FC&Ne;+||tBGMJMzmlV;9Ql--zpyJW}5o=IN0!} z6>oL$%%A!}waa%(yYWKJH#}_O9$EWoG8oN&%+Vm|mIp{7a)2LzM2$!Uao4-24I9!R zf|Qn2Rgm+gyrV97ruqg1c@F=rF;JOBozG|?$}h;T$8zf?ToNG0Trftr45H$; z=S>zeM|xU7@%t^M&U3m-iZ58Y7&thV%YTOcGWHiM^QY?+YlQD%Zec)`#WCYR^bmIZ zs)HOJ?++h=>mR{4V1(<$NcZ9I^XTOVK4WcZKDh4)`Y%p(`gQ_2xLSLOw%6rrQvu{Z(P$UNX;~JqYa)F8ZdpqB*Xo zS;0KmkUv6_$2kiiZ}*NF6qgT|UCIjgF|SrD@V}Gw5C5^;>>+W6OwU&|+%A z1Td&d9{Ke{$U-KO65q?6l?cBY{2vV3sV>lJRe$~B;Kf0OxVy3x5AHvLC_yfPh$ssq zK)sSr4Z-DXGFW#GS6+iK7MQmMohSc|1S23&U&vwjt14Z}hH`Jgg z>4t=+dU|ZZ)&aT_f}MC?SOU)rXx1{i92($U&g)|$;n6Mou9-LDtsrYh`kLW&wL?mC zY#VbpNSbp=x?$>25b++HBIY1#lwhkEyYYm=O=?Uyoua8@QP-FC=oz|m8izTejydKU zrdU&A@G4HRG zj%M$q_b*HHL$(O=EnwB6|GJ&QrO-6);(~0~U|uf-+qbe77%6b{p^`nG*O_<_aZCWx z;N3qtF!IC%k$jp3pIgKT-N<;B~tS<{L?%a^snZ*T_8jO2$;hkaf4)w z-*0Y^OBr`QvxJ}4b;=Qptl1%9nQ1y#h+eRP)_0s4;s z0{uq-p$r5N8HaMfrws-c1b&q042-4X_2*mpzoy&6w5uo zt|OJ(1%xXGB+id~DcXqsVX=4izUP{MRx#CHCuMZK%r^97(-4^emDbh2A=$--utppp zeLS0i*H;7}2QrxlY5OvfwoemnEQb?Zw2}TVgyPt%#7SnsK1+QE$-o7XPZ;*^) zBG@(qlMaczK$4(n#AHwziPc(X3lu)emU`TM9rduPlqI?uaWoBo9|RYh75l|A%Wtu_(DVJCwk?ArPbU+%^hS0p5^gP*j~|*t2m76OaI;go zZGSC!Rz}!$s<~hxAZ_Bd3`0N{lpoorKzu*j2SneXjQ!*3Hjw1jd%{X=NlAs0CG&Ts2ijK%-Fi-TMRT)ZN3&(g-a-TP5I2sKNQWZcNHQk%+!=>@ib8nm# zE}`+X%3S2O(&ApBkqJl-tf~1wR??I$9++|W#_^{G9#$3Ozm_UvJN6<>k>B++@8)FR z)c{owbA0XWen^9`+0?@&tG%hcpYYMj`lvhO@PPW4rKc#)dGrZ^COoiA#vElFE=_+Q zt2+^7{&9cM`ByL7qY*UVxK91Bm*oCAPqj@^Qf{&OW zGOxr^Us+Q|F<)8t`p!2##yN+pLZbZc>w{QjpRobUVo6d=&E|0sV(+s7^rtL1w|KP! z=Lp=dN>??wH|3x z%BIg>+V%pp@Sjlor!@>311X?^j3C5oApu2Cbe7!|BU((##J37>eGJrp#0OCc_E5SO zg_{QIi|QDiG61Z69>j0BRVzTAq`m$9cE$q)rz=6kJ{H}Z9V5l}0s9ky&A%Zx%OPGb zu;qwAp_g}ZiLV=lX-ttWQ*+Wmhekd)#B1_dLSA6GgdewXgifiE>5}tf-dO`C&x_); zVtrJL3afFJX&yV8jhhGZ*l_f28+PYTCN-Li+&3|yN;$?XnmB`A*7ccg-sA!}EQFJ% zGWd~_e&~zAg;{Rv*4VG}8%zQ@ zt1u>#a~2VJDMWl(VwV3|*B+v^s8(`w=F|g!VOuHzx|wj&;7OKd#(_u;<~RhfeFH28 z(0=yh*)+5s9viypQD~1AZ0?c&M(mpRhwyg^LsYa1Zv)%j2(s3HYOUAi&tE+=c23jJLC?!EpM8g@VG$k_`?8ztA$KvJ#d`)jMqFq zMbZZGN5J=QWe-hDjL-wIYT_RCJcdcl1o?L3$02moT5IZ5FjvdB5B4|L05vHr*^*(J zaeOJsU&W#d8_6>kT!uJhsWbPMdNfLrMedVsk8;MlJ2aX-TS-tMUiY~0b+JaYqyB+N zisf;L6}S;+`l0X8;IlxW!AIeTJ|42vut&4Og>dMQ6wlW(BB z&!%!>f{Z(C7d3aBp95%6cYp`)iY^ur-yB5g1=*2&QCRYh1@Miuw4|L&$wX#@cZex(!+s?VYYk zelu20x=YY+>Gb1V-cP?!%99bYiNX{=oVXQ=dXyiN__NI8AUQJHJqXY4%S^zKb%$`C zM_|wR`011}{K=8(>nsF5(7_=DCKL4surg`RI4og#PzLzQK=}EbRnK^xYZ-oF&(D{J z$$Sr1r&hlVIjue)qgQ9Nc+2<9kvh7Ig?I~;UH~Q-M1G&vxz&u&e3cd#&BL+9*0+@6akBfM1Lu zAshrsZD|T}zOk*G(qYQFywVsb&1~I2w&3BH{HK%16?76&jiZ+mGnwqiW}I6hOg}jZ z;>(NUMcFRK+nKvDX*@^`5ol%c8O!#EfL}8J?m>XpwE)?&OWxd_cqm)u{yl6B~=Ve>f^oYi^E2@<|L_+ zp6yAacx1-yAOjswNjkT01Myc~zNtfIY;2*FD%3B4xGa4cMxzQ_=1Bzgr;x&|+KO16 zGi4=JhWt$2VV&@uRg2*Ny)dJHhayn~&GykE(QW+`ZL_+UC+_IHl;BLQ`yd3Ra+3+j z$lUp2E`-w$DD%~uxHE*3drZAv`ucpbgOmn8mrY#UkG-PV@IOw}i*S>f7$#(O;Y6$V zt)k4)16JA0-xRBX%;is)K(;UIhn1wmq(odTug~hsUuPTH4Cc7Gg>6&TpZ)QhV{&Lq zr@~_+iq2)Urby&|fS50n-f-8P*%YT+GP*R2sV@WtC zk}L$}7Ugp_4?aur9f@hQ;3PN>bE*mrNJws+zrmv;nARkdR!wDND++|d-x$5} z;Yql*6|CU_?>kDG=PucMygb5exG$$%u7IW0zoZ{Jm@a>u z1;2#TXPbJ4vCW_Ed%AEbyW%Eo31x`>y8vMUx^@{OGB*i{?~AF^s1Zr5_+X6|lxeKg zs(SxSypdg$>Y`}s3rrMQ`jo0TvXM20_}(mo6h1OU?mm|h?Z@A^RH+E40>0me=4ozp)=ZfoL>(j4FqjJ+>OKVf_ zolK1dF$QCl{YE-!e})z_Jwbk55+^}#(J%ZGEULT%woHdWF$a1!hOn@%uX9(ERAm%~{wHEl^5~V$6=C5c6Lq z!G3kc`Iws>h!?{UcGI-WJR=Jk0vQDsg=Ha#vg={2ovZ%ena!fp3ce8a3CS%WsJ)!N zoAL70Uhm$xB)siK!FES%PQYDF>Nk%L$C^$`qH!?~{!7eoatPFUprAl*PtG`UriS>c zd_i5pUn|a^Tv196`660h?JiuBc&--EdjSc<>5D02cuY_o0>@P(gda_C%oAy$dEdoX z%`dGFx0ze93sHO@%F;s5CeSAtFh!OHc8jHIIV|M`D{iPU)7z@L~2aU z!WfqgYuwDPdZ7}3cXDxyCPcd^F=X5WEE$Dv%FPhbB*O`wOHW0=^4pE*fa+c>fb;+6 zM)9@N<5|rt<~?U0T9mr7DLzP%jTHA6;5s!ka}>U*%8fF|@3->HKd2o_TKT>lv0&Er z!e6>0@CGS6P}OjMF2@gf(u$^g_l4za1*%0I1H>liO&Qq0+GA+j+{In(cqRu55=|Zm zQ8dD@Qg6wOrR+$COmhGiu^xKE;WfV@TY)$7Q<^XQWwp!SBED&zd*zf$%Ec;A2N8`x z0~y4bXoTi`8PxlZi)#Z_@ENl6`frFmmbL?)&k2Mu3Mc2T-N79kzF3xiui629ugZ-6EcQ z`Vzr};T89L6qdO?%q$bWKOHc24(}NbXP330A8gOaZ4qm8eJrlV{&aq0K4em*kMCuh zpAQdslD+d_>=?CTcaLCX5T}_36u8zP3*mGMdVYG&+#&*3&wI1~Z%A=j;kEGzot8@# z_rG0zzA?#cHoquWc+wp}y5t;+-nqQ|H-xxP9J9?_s__in`Sju!M=!4!m;3J)Hk^i@ z{;c-uLxA+}SeUAN*{KvxYRi=^4d|SSEGS@L7pgipn*-?fZSvX57VIIr?B9?c;KN+A zYRR}_28ONLEc`wdR7IDp-6Ua`_tL{=+EybKa>8N?_0NWj(0H-<;2r`)@t{({7 z(UU{~vT?3_BZ1i0=zv%B$S9`4N0=VJwVTouKAskJR+|Vln-fypw4@jtkhk1=vS+z> z7ICP!M1jTs(YXyM)R7W^YaNJ&XWI4UHOCvqeL`AEal{Cpcbugw{dW5Hn+~fSEt1&w zwT9#4BrFJ$a9?!?X3En2(1NiugosuF9{gs2^A)(=b4l6!pN{0NQm?Am--(bV=5MCL zk&e9%6&Q-+vL*z*Y#n8LDXZ#qRk}aCkIW(22yAObIWzXZp$X&rXddE3qbrH8Y}7+p z$`_|I53|VM@AvzA;+S!rb@@!T=r|nzp9R^mfIC( z%Rgl@BVuMcTB?4Tpf2Uj^F?k5ZC$9rvLc-|2p1BLlZ%!s#wsWhN4f6>i}hnZ@I@=L zDe;Vgdry`OvSb#D;|Gs&y31uF#VwGT%JYG++6KBa7DK+QliYLD4C(Vr9j)R*p4zmV zv8kDVXk20LiFR`sTK$clcngzkvW0TjbAxGjmLl{CL1@N%YND9v<-x*sXA^`O-1+; zSH(5?MVeW)9V;%jv)N?1bz$j0MCnOX^8XB@```Nhpmc7t<0ym4zLeu-O(+(Mv=Y4=Cm`ULLVLfx$yOL}B9(PFiRCJp_PMRTE# z_USl)$gP7!&&DZE{I5ClB_*b`ykFAKrJHCC#gR((#4p!*`Wr!34yeIDAx|TLI8W(g9!Akx*ydad*w~bKGmXX zRq8HdYZ2=Y=z|ORmJ&MJ%3l!k=h!`-ASO6%PDwSr>qI2J#FkjepzMce?_QsnWK&y$ zmq??f)C34~uyMv5PadmP5aRRdi5Ucz7HWphky2OhK6q~1 zMH$eT$+SJo+~yqYFvtHuvW_ODZSo#hZo}1&2SwORN0WFBY416?3BSH61YR^uu2544 zpBw@W-5h6CzT8@sc6Nr;JwzP}9c@q{td#jF64inxs*1#-=^T5KPk zZC{*yS$IxEswhSimpyk?lR4d2BYdJoSyou4jO$=@$>e#Cx zN839^+c;>u1n2CNw%wfIoL^DLA7B3^gA3}2m8aoz(V~Lg8B6u^(~3G9qR1XMocQE% zAngh*uyeJtu?CHn&1pfpP0wBE60ag|92h`<>~#Is0@g`-Kj!YPxu!y-?LdK?YH5Fl zA%W^Gyc)LDFOE0jM{jku{cQN2C7haI&KbHRA=XlZEbcEma-hH3Mk+uyUzr;wOqGFx zX}&{BaBjlK=@q5-&x^|f7ONa{ih1(IT@>MzZRSNrv(2R>2|}P?($j&BMu<(qd_4r! z;!3c^@#+gPhp<+O>&@}qfbVwLgTk9XSsdRHUMF-3@ApPF!a@6|NHJ?LYw@&aKNA&&&lK)j$sQN>Mt6KatLA4Wn|JFG@6!6J~ACcd9p3~xTwv1T*Fuz!}~cgG&4Uz60Gl5u<-T_1z}E^Dp8-)d;iGK1!l@_UmahXZSw~p=%QRMp zC4g}PYSb;pONbqb74-hD*nlW82WM_NvpXHJ>6nvo%jT+?i4MntG#lqPp$}a`3(KHa z=?L?2hiK;4u9PI?{QfS3!U}xp;LzpSDFo3JMhieqQXBB5yk+ql(aKEC9FWy2k?Vx- zZrOV~{$-DJ*=MNDBrf|r0H*d$?#-hVIdeCI;9PDlPK=?{A zs})pzzE>K}hERfR!-zn8@}Ku^`4FjTJmdiwh~=8x5UJeBX3VJrpKBQ1S|4k!4m}0T zcco+2;|$gWAXj1^&Of@~qM=mFswNO@rr8z~23BZC8h*_OAwKV}h_Ey{(=OZ?-apSY`Hcb|ANpR89$?LbY`*-aPMMkcM)6CJ#(*ow9JS9p1!+C z+8UiJJ^SU`N+DE1oijU_?iAotrIDk`fmAnUf1Ld=khTf>?IC2<0pyc`>^#4-`1Mo) zslkwv%}!4p+Jq)E??Pe$E{%F9xrO>WeV;bY;Ze5Y=5ag)FRamCf+ozsO2 zs5$xoLA;BfQ<^LOfiL@!{tZEt0#UO;-aN%R>$leUlx`)oACDGNJF|@CXkfkdX&g{6 zr$;0zZbvQBezX=~p6Wn(wE1+jNk%Ghwj7IU)mMcq8aO^cO&DkL>S#bjx|}k+knrtw z%k;$@aDAgTL83b@edozUu5^XQ)t5J#_A{CG`t}O(^OXi2u9+(?*EiUIZ?jfhf>%IA z`CTn6LYpUeZ}S+UCb?M(t%NJ6Ch`IX4*jPbd-4hW0$p|j+yu$J@Ig5H>_k0{ z1kdW-l>Wh>cu_YQFGVNO^NBqy5Pico_SY*n)*xY<+v_SH$ zsvvpbQ1cbs)aQfxM{|39o15->7g#cpMZY!-8f}$7meKvS|<-qQlzmHoJYNxxVNm1M=G* zuQbr$b1}XW8~M0Go~~eY00-hG3C%q(Zud&xnZ5&m}svxDee|7|gUCskDUNF>(6M8^}}o ztMk(~!RK%$AL8i%mocSiZ517T^_zMVl|TsSNP2DUK8I$}VvTgjqn3!KkS?O}Jy|tj zRwuW~bO|T1$|#?S*B0w4^rgQ(SVnJH(Z4gHqV)5<&n<^j ztvNNery+f(%Kk(a)LHL+>+iN1oX_I3!Tsfs6ny?#tR%TCGf z^`j&um8kOI6gETpXGyYDsqyS;d?~enX#dG7tf75Y`f3wcK|#ZRC%U-=FAt(dP=7-v zl_bd*YIG%aO9r1?mkzE=tgQ=iX*tfZe%!}to+<>X^j64;)=>c_d{Gu=<#$`!8=oH< zpqVH3bEbExbdf%#qA)|s1m?jbaK!d2UwHKE}ZER*dyOW^sF@!4Pv6_G#=%cpF zL-*%-f}?Q3NoZ#UN{J91N04c9`25gR{%5X)qlcYaK!pOOo51(0>d-2ob9?~@m6k4k z?SvYXP+}Ulq3-Rc*EJ{^KS`@-sKWv>nL%|e*mlPG@Jt=zZ%E)0mYA}Xe0ylk3F`v-dznZ+EUP+c276l zQSf5$XS;#s2D93R2N6!!f=O2)aDmVY4A*gym6s}`#aRuRv7^stv)qSHyQWi+ z4l!9MP3Nn}k?^a3Ni(J-X#Kp`{I2L~$BzxZqnL+~6*R68u!4tYH^5#XhbD&asyNTV zLU;OtuURPFuTJkJh7~#nvd1lUQ*tXbuV1=gn%C)?8cFss>YtJ>tA-V013gW@!|Na9 zpgtVjN#AsUsd-*uzER~J4p~F|k4x`hc!B9uHX6E)GD7E76lAT`o)2k8s!MlzK9y%U z9aRexjoZTOX`m6SOVh9kJ#6uQz>OOhC-UidFZ$tkGyX>*x+Jc_iBEj_kN}=lyCdx&Gz-X-VB2AJ$0M4n7;B1$Mu16eMbnD2KZh z$IZL;2p7UcF9qFF%w$&k?A@N2w`#~{yvlKHY}Ad#f*wRTSuttc2cGhBZsCymEKpv0 z`|BZsmgsAur_S@j1iDpJd{!{n0Kx~k1SU=$c5XA8BsbH?IIcMx#UtUlz7nPsmTtz6tcAUUQPoGTZ9)%;*Y5DXy|c-lN?9hw5)0N2(=F9E6urhIN1`+1 zdR+$o^NR`JcrmZyTTXod_T2d$88_^v`msK1&ayRz4v7ZnYP2+~WmehTmXpJv&aP5VN4-3X6@>NT3;m7+C;K4Cs@mi;=@mZDO*ml&L$pZBf6mF?HW zYtFbt$07v<)psC<>{tnJ5BFp1AG~Gb@zr1YYZJ8FEbt1P3!IbOYj~U1gXj zxbMf>6{p)9TUK`07P%Wmxk;D3`v}Y=e-FvSy=2g4B};LzJ&TA)R~+5-iC7@0VxE>r zU7}8#XyS872tNIaNhWevWmSgEfrv&dxdFuoqbQi^1&P_2Oql=ikDtK$5i=Ka$X5sW z-yrat`1vd>}Y@F)IMr)heRyA;$b!1kFV*M+hGHOU8aup=rU;aW;6s;HKXnBKZwXAQ&w5!jP< z&x2y~zQ*YTF)}MqEk;`QR@ra(0i{Wo2S8vvzzCQbN%n4rPM@(aQ_jm>;NJh`0nPWwl#Ucc4~6Z4Ho z(g(svI!Ceuvi^!-yr@!6OR&e~+oehO`mC~9d;%FgU?tgX-Gr9w$dBAT-?PBE6j7ydKkr2C$FIGv{p%9KI7rs36-4Fdfk?J%~5M(8489 z6i#p{BoKm2fZ*;9ga_{_i!*f5;^n_UCT_3nzL5`ibib^NRB&6C$hWFiCeUEapl8JwJ_2U0?DfbpDs;bKO)BU5u3$~<*N%P;eu zVs{9<&Pyv8Av)6JW(K8ysHwaFhH=Hf?XHeapY$R4|3Y9Nv^BkHZ|T%W#soEx=u`6n z2~WA&W66F#MX{dWu=>7`#$(!4+r69Y(dvMn)2a*7)ENm$rD^CTgZ`5k- zSN-LhH{PeQ@_+}xBee;RpmSv0cEaylo!scKViOU`tke$Gheey3nl-k7;mBN6?`*VYZ6WBUmdRH5^ zSTO{`@cm@SsJ_;9ncyx=40Tl!O^`b7c1RK#h&Qsj=ec4EdVAwt(s$a}`zl9~OC8CC z7Y(hA7vVzm&#C6up3t6S(D-cZU&4)^uO>m6{=gcEjCUK`3=kRo>&0)9l6%On5>C#4 z)+NDf{Y81+1!@XIwL2oTX=(jB!m1)A#NsBk5_~2r`o({a66SCER)axH$ze8{n#@fEYyZw^?nMdk(_ zB~p|)AN06w?l~FljewPT_?UvbaHct(<0f{bS`^@heU%h)HnV&+0R{Kb*4L(DJ9e_5 zDG-Gb8@auRcx|Q2g!CO)Kb%%!cZ7cogZ@5--Q0@mWEC4Ru#!6)IVVGD5+4`oX?UPR zc>M+ymnKkwYCRE;0%SMkla(h-fE=?9&1|fwZm#g8Xac(*=BTnCZH4h#r!TR{qF)vB z0x8`9*0R%1bHV5a(8^KlVW)h_r`bK^0)7u@e8)|>d4&*>Pz$O6+V81-Y{XUQSYJ&0 zM1{264KVI4rRm!+KVL-%IEQKlZnETjF??m-e?ex%voBW)a+iDH#6tuYD6*5C9W+2| zFgxbMbJLyeX15lfynzK3P9F4@&(fJsU*h3Hj&t%SK>$amrC7WvA1%6*ULVdK@B!q3 zcmm*|tT*E;OtRBv2@o;gO{43+44mo5(y@wB|6kZ=uo_|5O421Pn1+lu64Og`qWOc3 zg+sxs68t$Z;bubLqH8VgB8Mks3ZBF5wW0ijeRV_R)#n@Y?S-K<#TT)T1QkErM)NF@ zT*T+sd1x`bp6@48gzWnTQ}t25AKhl1Ze{Q?8-E~M4X5bh(p4=;HGc-Cu=gt6)zGf4 zM6)ISYNGJsb3aYG^M&gXo`n-$J6<3v)IIx*ZGzeLB{ld5T}NH!EjVBo*dJzny{hxe z3sYX5Q>XnY{3?gcePu3#SM`C|r1*ANUu?kwD*%2!A(2lN^KaHe^^=}ZMZK&u<~GR)9bb~l9${Ss%VhB2!DE)qtd-t9WR- zdfg7tHF3Ol2dr;&@uEV+W=4*r_zlR^j;ud!=1#3f*SynG%qDZD%F7cU3Z5G>NWBu^ z@-BVc`q}#iFhewCeprz79g*+Y zn2iSX9B(01YDCqQKY@U{3*rllBbc3t&8j;?kc`ZvF85FlXv0?P;7Ec>Hu+v>PPqWw zUErny1q;|$jZy(_X5-~97+c}LF8a#zKL))Ya76FpyEVpES7njG*Pi0ZAYEi&{7-d}|NV0xiVJPpHnuqd`^=O-&`%i?he)T^hWNQFv z^LUT8r|f7*(zY4r*89{>0(iL=mzF85Uq8f@|6%ac{zC-!vDR`r5qMGrB#W>^I+F7; z@}_jZwll2N zh@v_rLSdsJb~U}n4*Ou4|Eo;J347#Mj`D2ti1q$#yVnJrm9<@8u$e_w{}h$s{+42V zZpkAd@(=wpZ|t-*Ybv3iPbtz||CJyCy{fKSqUa)&rB$3yVddQ{|=b zn9DBma)aU#b+9wW3T9%*0$D!qqc*x9DBh}-*hqUDBmV?4rwYygo=l56+jh{_!6 zTa*iZbv_XoO@qGP@#?y$)Q?!uPq}gur zW)d|8>^)Z|H=wA?cV7+YE4P0PVc)je|82JJ@&>Ht%6$k`? z6x_&ldZcb9cqDvMwPe@$nE={o zdL}YKq@)%Zkg?&7aHmk_&usUXtbgj{tg$P zhym&Nu|Y@AOh*Tb=QldIpQ!<=_R)I;LE=Ik1GW%_2lkR~WIOsQop-^(+(U}^Af&Yh;~Bm3h- z$Df-K=$VtyxVJj5I-TRGNl?tv)WDTQ{P9qQJ4nB{PIXjsxG}tyL!+v4*oV$MVR&g( z-{j`)b9d$|Cx)g5vHI+A}t^NhVa23D0szcF6|Blp!V@;T#arF zV2Htl;5L(#Fw>8!pj~s^xQMQK6 zJohk9aY?c%ZOJWhpTlW3;nj;#Ap)T)Fz#Co6CsR>`#mtyhy7mj#z-C6{xrExpRVpQ z)01j!k*CuO$(sky&G)88E%eaI^wY0NfE*Pb{=93r&iCQPkWMpuMvOb-y%+~T+ghAF zwBcP4JmNjjW@k4gl{X5b<$SXyhZJMg{m&Gri5BVg!F;tGec#~4)PxSZbrhF;kj~r> z+Jd`krcvwdx`QEcNHOI23C%5yRhuDAB1&GJmrbg)%X--F7ZZKjPeQ;}6~G*w0A8W$ zlSbRUvPW&|=~tO)9~{G*ZEqpx>Kat6w?0!74vSLC?2G%aT4w?%VOPU)F;K;s};6;cDPBde!j@BILN*!?N57nOj>LVG9 zYrP)Pehorl%-qDh5W4++HRTTxqk?eD5Jghz#w3z`jPmVSedg%|a6WC@XqsP85}bUM zBo%!dwF%p7@z&2-+7lBv35+|K>vhhxdQZQ;e9T}k*$EnO#6ZUD(%M$`iF_dT2itvl zA_`g;2m$(ki!scaG@SO9NK)d6Zi;V+H%@B(BXYQ__D&JekHvJio=V~Xgm4GM-&e9wv3ohnwldo3&KaGj73(36n7fzi1Gu3jJjj2kg z%=DH4kf6gAFj^X%MQ&I}_V`Pt{be(CDgDGTA>d-=FaRn0*#uP z$XaUMu9%Hg++WoP=Eyc_OaZ~qo&HJ3GE_CFm9VZcM4sd21Up>}Gf^=e`g>vkOAs*{ zL#}zl@4t^P-v#XNC6PHK(ON$90^2ut#v`;-8krN0#5^!mj4Ggu z+faU#Dz|6+*_R%@cv&)vpOmg5A(xG}s#hES9|XQw5Xc*8G|0w6=1G6_k?qAGZ2THS zRf)Zqyb)W`fd6=4onZUoSnORix6Umca6RUZ^#|Xa(wZS@^od(%=U=qad|r1IUhB5e z=x6p`eyz6R1tsBa(!QJ+AwBe;#WDi>_}c#Gl_Gy?ID}g*^##C^(-5%aRQtCc z@xJ%_Q?!4;OrUEc!RJ%7KPEa8=Z>s>&JZ~6XwUu8Pl6^Mt}$$=+%$g48j2%}!G2u2 z*h}xotd1_Y>l3Hq`*#%be<=vHnDu}EYymP>bZ?5+0w+tC)u09K&cePfy_Ak^#fb9A<|GADVKsq=1&sC!R^x~h+bWGbC!0PV?WYib7 zKui=~QY)9&=Ls*-jnHA@2Qu?gWs~R39Cl=qOW_M2iR`}HUhbkZXPOaRN450W1RkK_bBpR z^kl3$q)|3Gj|?|Ce{L@EHRVD_<#F%3RqRVX7XX~9>urOtyE%2wmxbrM*&S8xUE^7u z54&zXk0vc%ld2;_Lle2fT)N>oz2giPV|{9G9*anJU{j`lCgZSOOpIJ%U9{oX!(+(o zWd^UWzA0VbZ1UHpx`Y-+$p>O6Mu+W!+ zQoMWqjeZ`f)6wl>l>7Fo;)rZ2&)I(Q=cV~TQMg`zY#c*kqrNSpLsZBzQ4Q3hFZ}WB zq2uDF3y`gwx}9F^NNj!{_bQf{!W4wq1;$*Ehg_(*mG~j-*}ksL*C3hcmYgF{610sy zrLbLCiV{0uxe3qTkaFWDcAcV>dP7#odCFyb{guMQv^g>=@{t}*qQh*9m&57vc~4au zw-D9>vmmtsm2P_Vd{imP_Df`%o)+j8cWvqulYw3iH(#@^b?{lB@W+HmP+HgOldP|h zA?ee&5Q|&7S_CHu@F|dC4%Rmi!Z2Sr+xi;+YtOHL74rh1X0IP7Zj>6Tjh8m_*<|X= zpw0iRVKI$HVxe5q!p15$?SXEnlIo6Htd|-rpOG2WEA>nFvc#F+M!ttGz)YHSM!UQX zcBNm|Dl@D@q*6nUcQMcBwkc%jkgW+_DXS6hIcj%+<3(x zh0n7WYY=@Ryz!BS(Dsso4E}Y}u4J9ix7xkn6vzxfKx51kaxMQ-e#BwT`3m0Z4~7=# zXSMH+x7BH0L@KS3OY3mKAyo)F4}ui`@#H+vM-0j!vd7z`5yH+`(DSoB^Bri zVq&ms^o1yty9jfde0=!_{ZqLiIgQ}_c4p-=v8SB3Jckl%HMpJK<2G`Li0jlN%DTGH z1UWp;t!)aD=b$x-k)w?>GoHftAiw)?MO&MG@7Yyn@Gkw+J+Fg%lgjXYb)6_rgo6Z) z-k&KfRy+vK`fW4Go8($ymJ=-n;u)3QUJtp1x6x-vuVI}hAL~=QzUz>Hu`mp ztTFurIa)-ItC%k*F4Hy(6O&QNRO*g=+KOd}2jf>v2H zaornP?PX^2HySZ-8FT=`r%-^QjL)!;W~O*YdZ;o=_)w^4?qk|i&O5JLWPul14(G2q zQgD5Y4N;t{u7LPOxnSq}Qhq4fSeE1tfJW;$Y<_0L)B5C#j8l&@QVMLP@v7V(MreGKx^`c@HMyeN2v6=$qTv}bko{O} zWD#jR?BmW|#ifO!O^Gi@+uHf~tu#NhgZf39yqWx~W>pSZb>gc$Ts*H&zG9L8+74#= z8cBmBSO0JVLILJm{#SRPK*Eh1eSFjVL@d2d5_~9vu}Y<+lQyJeZj6~V-<5&7I7t3V z2xSoA5J;Bce9SA&Z@PK8u3c&z19ACzPp95%wJ1AeFN}hmZe)qnF20s^k$(3|m*nw5 z^@-0=$&2ySIkF26G;bB(=vG~4KU1Ysfr4MqE_3*SN|4f}e1y%k)O+rpety*E#BuWI z^&m;IGjHW@^`^1tATpp|!}~|{9OCBrJjE9@R}gr8d1P_XW-I2k3h6(HSIzorLWRFTA>%um zVFBb-gYdcxhJ^Bh49q`AEC3ua-rq+|R&Iaecbe7Pv4YDhPBagRHJ~zkSySsua5Qi% z{L6xr+IMsI=k!H-FFW@g-rVHc&-jrwvAzNh0a>}Y9{^Xxm2t+Gm*lFpo=w0_48LJB zTmqC zMfwpzo0#NzmWRxskW(GPt%yNlZ_=nyD-+1=?T3s)# ztsZspeOYVDY|VrE&8#u7DRgtvg-?EJ^Y(b@0KuvcW&;!SyX>Gn;{;JV4SPKc8Cm=I z{F5LEI|jNtI{!hjt?owkBxTgxiL`1`re)kR_>BHF*f+9wU``gm;6LBJN9_`?f#+Fv zE#rsD)Y~!pD4~oBqH@LF%oaZA-i({Cs$#o&YSc_JOqtD^o*$XGp859>lg0~LQ_44+ z&Ze=h29M`NtMgg7ywvz;7UPLvDEBm_YxCIFX%{H#sTACumo{_FkWJSg)ou3$CVeJ= zcAr1t%XicU)xqhK+Ua1bcus99=cDrnbq+&xSfFj8*noAHvUZ5kj4`B3M1sjXyz|gG zYKv%5KGbgt44+4E9SnOh6WQ?=63LI!MHPi`Yp40*Zns80mcZ}(yf{zR@@i6_Ew^VR zCZZGcSJ6aAnx55B_xO@1KW4+_ohcynHx4-qS&Ai-61!$$NQ0cmUx$G5d#1IyCe_x) zI8EeB98#IbYS`>n4uOJ5KQ{W=ARY2s37y1mKS8*8ZSOBMEH6BXq7@t4Nr6U%upFu{ zM+g(Ze*SN81N#5^474r%|Hl`QQ4RE04D(<`lAtN#)s*}4AeZM>E>$~yq4d2To+`QO zKVFN#^ph}%Y*VuZq|q>0QkHYhVSSVrOS(j_*h{0%!g*-J`Q%q?c)bSeC?A&~60d}} z-J#CGRsw;Q_CNj5CIfo^)C1^ZcPY$)tYDG{mhRHH(o9o`4KhmWbpom0qr-=B36%d_ zOBa`x^FC%YYvllp*Ps^ET>Iw->eWdjGfVQ^if7F4d{vk9=NK!{3V*(!N&C-5_CZkD z4iIb^_cx#Um+YTp%k0kpxmB~NqPodw)+PSDf)rK06JMNMNm*=Ud~kjq{IkpGM;501 z1IzS30Lq^QTysaIsxfw`p}H>Vo$2T3Ai=w66?j!m_0pA+rO`_GvM$PqkOhryr3^@; ze$v|3oLg@Wto-utvvW?H4l##XG#$o|Nw!fm7j*1U@kACD0*a0AB2M@63<;smO2VAz zkUIPMk(qVR&ih^_ml%8W9y!X3ZkH914^Q{TD1pIl%#FvaVv2;V+7UWayF509uhTiU z^KV1?2Zic}iqq?0o8Lz9%f>Js>uALko)}y_6Lh)*gPMENxkd4=g9Es~Y)zXiDo{mIr=~oXyQQMfZ!ksGSVT&9iWEwqoIiG`jeL{-#UR3c=3FDpFc?X z6b@jdl&e95Wx!g0(JM{}y%O;F)Bv+601w)@Vh>fx05#*AV;s-XLk(k>6@0$%x8MP5 zx=HG#{Dm_Na_t|lyyZ0ouQIS2XOD!j=X0mx4X`HjYdE|bI2NvlT0&Rx@oJSl5)SWA zeei;{lG5VmiG-A=;U=5jKLQBzq*6}Mt)ma3_ab~=PLZR!>*;Q(qaWpB1w>?HT$lIJFI&Y9pfjuy}ua|8YGe)&g zu=sg=?N#)5xIYtqYPYFGM!Ftm^8b5YQOI~x-z=qG-1ECKBqkBl0|IHGDe~^i^dK*R zf_-%f&0dw-_fbCL2oZC?Hd3R^XInV*M}B+H8pR;ktWS8EQtB!~)7(Fpu2d=XHL4kHZN+vqY zXC32Xkw;1erK?~DrjyU4I&azFsyBWnz5k(-52Ky#l`8NYlO8@uo{Z|B;`BsVs|1xz zE`agXda%M%--rSMfzO=HOv*4T{weAV{mA*V9RMDv>=|1b<%;Y9vC7H-k>VRmkvxb;$?a3ji1c6V@P?j1~L>{IjUbmYudz_N2t39spZP6hC&CT2t#f~=U<>=%(8zG4A?>bWvI09HDBAXsft z?b5PWav+XjCXfqj;wfV4-xYY>f5_9mr_bP?f5c$yTKGV8%3hmp$3EOHPfyU5`=dye zq7vl65_|I=?JKNP=YZyd^%rba2^gHmcI;`|q}mN#q1&bgCPwZaA}ZH=jwIDex9mF{`WPn3P`7MN3fE zs0}|;CY0l5?dik@xCVtx8Gu*#p*}Ft&j1s>ct?YH*b2V!4P>>wgyL0myw1$TGVx2T zA@fftk>;4;V}vQVw^ZA#9Wd)~vE%zSnc}$xB_Um_Xgw8Ey<3OiT=ktX^Y0eRKo0PK z4BF&BZiu}|#G8o8UIUCtXCSGb)kcc8XW5HOowS(wQ1&BGO|E_h;=v#B9@BuQdLyxmhd zjrCy5j3_^9d+mg)j*74$<-wU!M+dFGLiG0n^l&EU6s^0bG1W)xG!~=~rA6mfH z^@$uk=VjX_AYx4MBWxnRK|^ih2E1!$+T>et zbr!X~*iLFn51)Y-yYsuMWN72e1$QNPEs?Uw`QveAlg-obR@2uscY;`hlURdew3Ca3 zg3wO*7>E#Lc*FUqba)T=%-p za65}c-5ln$UvrLk)Nbs`pSULc%Vx_{7{Z|riF2&1z6B#u&&~h3dymFxbN{YN|LyP9 zR4O&SXQr{(sGd~K*9n<9j~j>K1a2Y`u4a)PEgr2(k9DOSD*0=nE3~Rr=;mLB>$Y&U@f$9G zG6J>Pv)-%97J)IKXyjiXbvu7Yg008tm=Q*JSzbzKv|GZBgne!pAJ6Yfave3VBTawJ zBPhQf?l{t$f!LFvvdJ)6*9Tv9naObo0lc?Zt>S$X++h@ z>XS7Xy6#6uQ_VAHm4jZ{@#-p)RNRu3h(4crC8Cv5_@BAb=v z%W(e=Fp!^Dz26 zaXTzo`2CjD20}yCM~|?pAhH3XQTkI_of~$K&U31re2s9%L6aRX*$L^jc2sg1ZrG%w z_suc3m$+P=e8Tk^yNe8aaJE=`w2*_>fv25=z?#;@Me(-*gzcCfPAOVJJ`?dIz!d5w zbsc{umB7t*!V`u;#{x3HBt!O=ET*YCPIq-b&6Y}FQ`oF@rFyE3U+QyUDHX7{&Jx8BDRQHJG-{8K94zDdJxTfCyb&(;Tk$lg(xPCY z-^Q70DmgpaUV=?tTnhcgH8T0t?_cSvO?ET~LNN<{|CDIGee@6$bG^H$Fc>DNZJlO5 z;e)>I<>zvoy`XM$RxJHvh>jenb^^pm_H#tYY=du)ruuhJp${FDJR-bskN--bLh`CU zZBh)}L#7sh=txSUXZph5!!$jt8R*SI$R8hr3T^mGHwn4>v5RZI?}$eu<9h(w8u!N# z-lm?@nd+iYh)Ozj%GIz#-R3iR=QQyR@2p7@*JBd7?M8O8S(Y({AbxC2a^lQaD@UCh`YjGU(DIk%3~BVE@i$ zJF;+b?=BZ!TUR?`8c!xmM(V?Q`5AcvXl*{*FpAzmxbVBXm#JAW@)3r#8s}@vM zJb_zq(f#b;Yz=w*RRB@ffZ0B8+dkQ z8+5LXhF4e_ zBG;%=89>z8?#zNFJhgvV$2gi|!DFMo4!?`-yREmzes~F3MbmWZP61XfK)Q*|+N7<) z`rV_QO%}$vGGgqM+1uw$kipAF_cR4_sgSq9y>eeEHwkoP*aL+iOw8$cZYhQBQuH}0FMr0#J%h2G% z{d;ZweQ{L2f)e)4`Z#jtL^{2wZ?x6?qJVlP7v!e(z$XwazNnQbmcC_OK*sn zbvh4WJW}x)s!eWlVO;$1=AimTy20}0iQnqEq4Lr$dBtOR>6(ytUm}OkVt>UkzI)j6 zC!wQxX^Y9U?|%-vt*h+<1ZZ!67Xmqucdlz+LqGJ2p-eQ(P*v+bR#JG;??AJw`0*SX zR75Kclj81h?)kL!_u5dr-q^wH7@F@t#STUp*EXLCrFa%2-2jbTEripib;Jk36KSjU z!J|5!k=poEt}Y)_R(`|Qv4W*Hj7VXdu9s`+wH%3_1rLmVhc>zOU{OWk1$6K)GEu2b!DE>0wt|JG!kR92^Dg#huq3 zl^VM`t=fKB2>2>sI4MJt7gJuHywGcX-|rl07ZWhQjqNdqY- zMH9?)^=J<5*X`wcH*Kdyoai@no#d@?xycO=GUyGp5x)^=LrvbSz#o4vzW_N*?fBw# zoXF6h<-E^UV6FK07U-vf5Ef@A#NoAmMGIaGH+tvVDuua_j`^FTzPhMR+e|POw_1>g znJMtEXZ`}!$TlVrz?VtK$r+4vOu6X@(j=@?XuN1L%Z&i$U-1)uI;NY&$8q>d&1iY| zRkCyAt)`$q9o3qOtIs7WLa%`v*^R+GhHV*bU2TX$ew@D?rIk+VAB0JPvBsP6UTxPS zwXRb&2#4=`QId^cO|!L`By-3jiiwa}Hn3}=t$8dFk>D^9Frc1DTNFHhE)cC<<_?OH z5ImN=(<%i-J7Z5sV-Ld4Ign6Shmsy!x_h;bUVIIaz&A_hPrVO^8C- zD+fx4KZ3Rt6cFI5YSJ3>SaK0Ag@^GVng&+Tq!8w>srNEB`JSV#Df;c#N^}PLVH*_3 z%|OboSvwnL||K0=tdk_2%-UC74dfJANWd)5-3Bp8|f*BYESw$R~ z7}MlGj|qbxM&yt^-=qBTmH(TH!&G3$K?kpicaM0+w`hrI#X?jsTTQ}7M#sF3zlhJ3 zW(81usDzeJXWoW+vSRy)XRxg54i4oV6szgo}_Te19V6+Yw| z;CG<$7pN9Kdiw-yyW0GHd6^1qY%`j|THu8&NboAku&F74WyB51_LEkomCf1U_=!D+ zv|H3R{~UIH4eVcGS`U^_W7U6w(wkuScWoOI&kG8S8|xB-gWPiH3gpmkKmK{Y#>DUJ zDeIVR6zA}B-wm{Y5KxT_G0xVIOMI0oSC>Or5-_Zg8Dj@K%7LajmV5ibSlWEH}o5MVF=2>pA|5G{?0PndNxxj$ABST@r~d^yx)G^gxL zDu7~=vIiZxXova&;ZumUZIx^}1*$9=vTIT8{1*X%>V>ytN!WH?UJVzcr69emti~T; za6b<#cgCVrSc~dc*e3IE1j?F|hdLZ%q55t40 z;P?~R)BTlwH_!~X?EjfllgeR}l}4*5pBdzh15%RFxVM&5=a=C6A-5eH0U8k(ssYXi zK#=?MBG&w^2Cp`%(<127Hbb@kR7~ZmM056tw97u8cD47R1vm@3-$+KWn)AIr*!`xhtmPR^_4WJN z-!GVfMWkUPd&Y_-*N`rzYbr=4k^p2#r6B$E>YlsTG4}DL_ z*VqsNxVT5gjn?P^S3J|9L>sI)uBR_U89J?f0jO>3F3NA80-o+NaD-TS?qPs1A$?c; zcTuHzq0Wjhdv1r~0Jl_{zc2f^+Ty^Ax)}A6cA`qGsf~$5Q^$mO`$d_k7$ze;wId0_ z@pnmDB@4w+|HQ%71GZu_~ zAX@L`65-Ca?2)eXuv=Qsyl^OMS1?v0C#|Men%rc*vBYy{D1^55Ub&~(0kNADXKNKz ztp1WOA%BlV3%@M4z)AprfP`hyoN#ewq#^fJPK8JvJF@*&1XpC?D5WMPf6RUYTXBK@ zU~cbIt&SnqD`C#M7J!pf(nQKB&n2&uX$-^(_heg)Vdj}a{RQ7jqgdPxq$0SvM(c=@ z4#~~#=o4HR-M%mY{tg^K%14{16}txxGw&>aLY__l2mYenvG98|nS329e3iVN5BF~s zxwdXu8a8H%+*)VGe!;_SBi(t}xVR4L0naRb)f|~ThH-v@Ac%|iibxx!Q^?RMwv*wF z{|H9h#4tTN85re9_#DkUOwfGI1RGmMUu~p)Bs9gszV}U0BT7x~aWu;CaNv*V^Aw2; zd-;p2qC4+m$wI3cdnG!iKo|EiFmiwr)U<3thXGCq>7duoh}tSZgrOolhPOx{D(Ihx z)jxf|kHnoH6^UuS_Dc=LuUf@UIqm*o31?ap?pa04FhFvq4o*V`@W>R3o+Tb`xcfaJtN^McK22S6@qp+`DCM3Zb~pIO~) z*-c_(+2hUo?|Z^c-&R&maxl98#+?!z>e{>_mhQNFN8)lW!@h!NWi7WbJv#OH=)EonzFc7_?eQkplkgm)AnF0!#u-tC^BppSq7deo5 z3VU$Zh0QL)W_-yQB5EtSga(WdRqupmU_pnMI=pTMlj>kM?Mi|l#FhQWP}MHiA-boL zYKq^svWs}DAEuH@E!bMA;)R84xgisjGGbg?m74*;BMyXO5*$?P80v?9*GBdY?l_aD zDmaz)&e~9!Y|>M1_zSdT&^D54+um$wKj4Uy6$M+BN-m7NDdzQGakc(&cO?F31sa>7 zYmONmnAE3>`@!t!P~#c>TSs*b3h2fcbPj-RKTa9rMmPv%i3BFV;7pH0L6P zMbyCVKLFU-%AYmf@X=~y>iA+IRZc-)uuD&`?C4uv!9}x;8%%Q+(yo1he}VCT94b*eA1yajN`{i zSAA+_Ldq$#-Jii~100}DbO;Xpt&U zv)Ou{5zjXRh1ms`j*NdygoNw6A^RqBz}X7PiYp z^LS%;QZnqapyp={e%}iIF+72yMyf~c-bbC7f!n$+E`&k1yw{?5EFDC4j92O?@E*c+ zCh1gW1uA6Oort}miEkp`{CvwPn_CnLCbj)=#K?9`f(Bf}iHjrtU0xxV%7UsMZnanQ zXivh*&0g|2AxbhP(Da2z30@X4103niNAast z(C2yc40xU8!|gF~#L;B!MjxfcWXRA-A^(|`kb^ioyeq$|*5RQ|cQN4cE$ldQOWOkw z^@Zf?^uCi7r-CEAD$LW(NW0pPSt{a7ygCw>w2^l6V6UXU$35tFYVc9!#f-x24qzg zAbZ#HrV1^ilJL)J@f4S#CBE9Co4qJ$>MsXyOT4e^VWbNjjM8w^a>bdGod@5;Dz5uV z38EUX1sEpw@g*7 zYjVlZKK7B+dXGB2i+@?NA1fkEV529q9r3nEZsdzAfT79@1?75r7wnybdnsewB@}Z~ zm`ESdOYLy<_5})NNn)hNwxTsqaV2|vQmwuuaP0~trGStlwBOF`3)%Kehzl)_*(k?H zLy@q#GuT8&883MeeEwqv#}-vRau{xfeEuF5Lnnag6!|VzTlZm7ERiKSoa%dYTekcHuswkCP9>AoGKn zc%0(7em38n30|l2B#Dt_;nTi?l0)99Q>+r)=UA3wfrOM~7=5DNXo|_2Pq#rLP(<`+8Wezwj^ z!R1-ufsb8IW;n@&wxUjVTH@3?ct(OhCFss}2Q?7fO*y^n_ zR|~P*2TLU7@7!@l=%SLp^?JkCYw9MrE8bR%gcJ<;5-gqrC5e`x#7wh<+mg8%X6#%J z9Zmj422P8Z9b9{12-mfz^fshYTZ|+z102rbbvqLwwR017hIas-LKxh48YGxy+m8Ol zE9S)zVPD^lw7|QNtpDNtc&W|dkQx}vvY(-1f+~CKs!+tx6+&d2j9s_=N`jElggUpV zqz-vOr^A+yG!yLB$qs*m?Gh1wGlpcaBI`d+?#VoHdhlLLGrDtchpara^GWRR`X ztUAgoLxI3A1v@Q2X(;GiW%B3~REf3gN4C%9n#o+RdT*|k8&rd2Ci?Bcn?%#WzP&)x z6noDxq?%$s8)pk5e*00r!PgWgLI_9N%M2s|aw((vx>AXTX##_IKRjV6)1xG`HOJp4 zzBR|Wa*QL@+l#VN7{sawS^F<5OZ3VA{NC`P?;uU+ldpi9GfE(AUCs1b?g36E&eF1W z@TFxky9w;J#%*Q{+Qp!ms8G8oXV4m&cD1>TEwy%&Boh&}D$CWj?W23lmLPk20k81Z z9Z@i&_!QME^iBSW68GVA!QsW@8xg5NtiZauaBV!njiC`an9B64(#Mooz3kmOM}?73 z^4vLTPAT`(?C5*L5GZA%%uq}%(a_7qoY+|xkM}4`oQl=0Q#Cpq8P6K^D##c?fK_vC zZO}A7xSLX8Hw4A8c3!#k;cn_0b8BL48r_jg>)~bIf^`1Ea#r0ueOQo5*Y(8%3JB01 z{gV;$--&e+kKXhzv9mRQ=T_v1j+1lb0aCfXAWc~iA&eL-bYC&ibP0U1Vf&5APyFo- z5|s)|?&=hu(#tCfak_V5{x2C#hq5b>GJuZPtO7?egm1So9@*VL5T&6*i}<~A1hhHQ2E)YO{OXXd4v1H9~XD)M}} zWDE`PFvB2fyFdd<^Cg;-Xn+JF;Vt*{I4dawGW`Wr*VoVC%v+YA5O0otosA?irJH(L zqU&dPn0S2r5uwEhn``#B4Fq@l$0d>= zmrbx=MURTCH{K6w+ZnxH%)9AF4vQY!sZo|7ZM1nxWS@|;ur-bI1BH60IQKXxpR)BV z+i+`q+hTP{4d(9M5=F87Kp*mkj2!4z;C_rw%?BEXZ=lHYX_*Cz{oY0~Y@XTy6sKuH zxv_HcBlNS*$fEYMf@ML4OM5e}Wu?xDia(8>QxzaFzBNnk(i+BY{p=t~Y4FLZeq;6| z->udnjvd~I%g}63F^6V&ehB?EGlBJ%8H)KfXETs=ScM58{*)eHj>r@}gb2@A0hMF1_WRF~~UoZl*2v*GjQz zJKf2AAPrQ=xO-C?`)f||F4QUn)HiW?0PVpa!l_vmv@~1Y`E_031$3aOHDQ<6G?%41 zY^9qG5o>GGA~5IEzTBpopd$iCiTyB2L{;R^ya+h)$$+_qgNVG>Ub2%gWqQU{{yyUP z(LC#Ru*{1ddLtgqqL!_=mi?k7shFY^-%dd7`7QScMK@unoNSP=Oqlf>BxHxA*zVx5 z()bMDSjcPk$m6uOJ|E{&3uCBx(_w|&^W!F2o)46| zL8}7pug5oJ!^XQFJqh_e6N^f03DsT`1E!4Wl1KX9&CGU~GWZMR*i0k#;^Z;D%zGYj zwfThomO}&K@EERPbMWERgBtHow7TlZfzkJRm%Ru2IFjZTDBoYPYIixrv-|B^BAA2S z-ZzInd$B(RLTq@n1uN8uQrQ@gMB7S==^*<>@97hF5$JV{tN3l$7I~1D+T?1_4{k5a zcT=Vcplthc%nBF&1?t-Xx_R|~YE&&5KnCuXH19a0byZ;IDaSS*QIb#AyQM6~Qt{e9 z4f@VQy5e{=Ki6*ZttExM%+f)GB@#tPt<%DSo}NBOe_1Z~_orR)&1ovdJ9hEz4xM;e z&6_!fwmwnl@(U0O%e2aWzJh3%7 zzl`UGoIRf5EOsY|FOl_LmKFz_Hu$Z5w+-)^A)})^$@QoArw?Jk3f)ub{ovwhL5K%J*3 z`-^lCm*6^*4>&n4VKBF}Uj@999KpZymq9iiiR!D^9Ky$cfy5{3GHd+F%;TA&>AxSl zAjR|QFG#y01Z{KCTabN_pe(&`HF%MJ6wg((FpsoJMv|CJC;pXYr}inlsIySYjFLs*Tx#lXOiUoXAM0nlT&ye6-C5S5xz$65yt@vPh{~s`b5(RW#(z2 z(n+)bv*s`B>W{=^7NunTI03wcuKw%X>iT&z*KLZ`I}|DTWb^j))O}1rL!+Iy%L?GdE)M^`KoJ#qGqRe^01b@ImV{JEPl8H zccB(YilhEs*?#?_>+)z|Qos3$^J(Jcf`5YVueD#%`ReO;DRbR}u1oj7H)%>| z|D5;WS9rk0MPeFVOZ%Q3IOe}{)#sPjf_GM~RhW1_EaM>X2)~pcO`m{1_MCT%`?bH} zx5q{e775v5mwsJ)%$Z-vvu5=q!NY>Uaj4{%`#(R<1+Jt&>YJOKopp5W+J~L}EQ=Ut zF`T^m)iN)SZ|0Ie*Lja@>YK>-=hwAm)04&KrcL|6C%(Eo`Od6a7RkL&DjtVc^2IbZ z9ly5}xLs&{=4DTn@Lz@8wd{G9^uPQ1FUl5pHBn%}@uE52+@-<)M3)CH5mdS4GjY{v zHRcU6+kkB+Bhy^Fd)Ga$rghp^8(CY=^ZfR!I_P*>evNI?#4{gGZ2%rSIw!HIC;dv; z)+2^@96p@cy|{2|o5zVinhBi>O%{tJ`b@bNTON?rVAjiC7Asb`!jq@mNsMpR-w#>w zd_5Uu_FqqgOqcR)J@eM&+eT0A(lptuS6Yi&9Yoz3_^vR{U+bOy&Asi}hp1^!jP_k) z-^O0`H0{c|tsII??<`d}^u?Skm(lGMDS0dt9#K-wT=JbaTiWNj>WsYSFDh0)U@%}~ zWk@}|KC@_D*f-Vx3>9zxl@{ImbnVH++(Moud`=cQbs@gV*>*L*GgN@35#y4r8@6sq z%g(M?lw{zgXY3#ubmmf)_-5dREEUNaO5wQ+Z%d{fe0?J{Rx^G3r7x#%q{=zDgq5Tn zdUMjceaG70r@?7YWNNqy+gra$mpFBOHy>%cv+lmWOj3j49;^Pp?WKiEZ?mUQ%8_^e z&+uw>ofz;adDiK_=U=%t=}sQ-U+<(wlrpsT9YUwjM9>e}P` N?Hbd(C6fn8vJ From 6ccd9d0a99b7c1cd635edb9936372e58a153a14c Mon Sep 17 00:00:00 2001 From: Only_Xianzo <184820152+OnlyXianzo@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:20:34 +0530 Subject: [PATCH 09/23] Fix: prioritize explicit provider prefixes and update max_tokens schema --- pkg/providers/http_provider.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 0def923..12909df 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -50,7 +50,12 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too } if maxTokens, ok := options["max_tokens"].(int); ok { - requestBody["max_tokens"] = maxTokens + lowerModel := strings.ToLower(model) + if strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "o1") { + requestBody["max_completion_tokens"] = maxTokens + } else { + requestBody["max_tokens"] = maxTokens + } } if temperature, ok := options["temperature"].(float64); ok { @@ -181,35 +186,35 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { apiBase = "https://openrouter.ai/api/v1" } - case strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/"): + case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && cfg.Providers.Anthropic.APIKey != "": apiKey = cfg.Providers.Anthropic.APIKey apiBase = cfg.Providers.Anthropic.APIBase if apiBase == "" { apiBase = "https://api.anthropic.com/v1" } - case strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/"): + case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && cfg.Providers.OpenAI.APIKey != "": apiKey = cfg.Providers.OpenAI.APIKey apiBase = cfg.Providers.OpenAI.APIBase if apiBase == "" { apiBase = "https://api.openai.com/v1" } - case strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/"): + case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers.Gemini.APIKey != "": apiKey = cfg.Providers.Gemini.APIKey apiBase = cfg.Providers.Gemini.APIBase if apiBase == "" { apiBase = "https://generativelanguage.googleapis.com/v1beta" } - case strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai"): + case (strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai")) && cfg.Providers.Zhipu.APIKey != "": apiKey = cfg.Providers.Zhipu.APIKey apiBase = cfg.Providers.Zhipu.APIBase if apiBase == "" { apiBase = "https://open.bigmodel.cn/api/paas/v4" } - case strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/"): + case (strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/")) && cfg.Providers.Groq.APIKey != "": apiKey = cfg.Providers.Groq.APIKey apiBase = cfg.Providers.Groq.APIBase if apiBase == "" { @@ -242,4 +247,4 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } return NewHTTPProvider(apiKey, apiBase), nil -} +} \ No newline at end of file From f12c3379652935725d97c15c0f1af072434cc75b Mon Sep 17 00:00:00 2001 From: Together Date: Thu, 12 Feb 2026 00:46:48 +0800 Subject: [PATCH 10/23] Remove duplicate truncate functions, reuse utils.Truncate Multiple packages had their own private truncate implementations: - channels/telegram.go: truncateString (byte-based, no "...") - channels/dingtalk.go: truncateStringDingTalk (byte-based, no "...") - voice/transcriber.go: truncateText (byte-based, with "...") All three are functionally equivalent to the existing utils.Truncate, which already handles rune-safe truncation and appends "..." correctly. Replace all private copies with utils.Truncate and delete the dead code. --- pkg/channels/dingtalk.go | 13 +++---------- pkg/channels/discord.go | 3 ++- pkg/channels/feishu.go | 3 ++- pkg/channels/telegram.go | 10 ++-------- pkg/channels/whatsapp.go | 3 ++- pkg/voice/transcriber.go | 10 ++-------- 6 files changed, 13 insertions(+), 29 deletions(-) diff --git a/pkg/channels/dingtalk.go b/pkg/channels/dingtalk.go index 4114ff6..78491e7 100644 --- a/pkg/channels/dingtalk.go +++ b/pkg/channels/dingtalk.go @@ -13,6 +13,7 @@ import ( "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/utils" ) // DingTalkChannel implements the Channel interface for DingTalk (้’‰้’‰) @@ -107,7 +108,7 @@ func (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) err return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID) } - log.Printf("DingTalk message to %s: %s", msg.ChatID, truncateStringDingTalk(msg.Content, 100)) + log.Printf("DingTalk message to %s: %s", msg.ChatID, utils.Truncate(msg.Content, 100)) // Use the session webhook to send the reply return c.SendDirectReply(sessionWebhook, msg.Content) @@ -151,7 +152,7 @@ func (c *DingTalkChannel) onChatBotMessageReceived(ctx context.Context, data *ch "session_webhook": data.SessionWebhook, } - log.Printf("DingTalk message from %s (%s): %s", senderNick, senderID, truncateStringDingTalk(content, 50)) + log.Printf("DingTalk message from %s (%s): %s", senderNick, senderID, utils.Truncate(content, 50)) // Handle the message through the base channel c.HandleMessage(senderID, chatID, content, nil, metadata) @@ -183,11 +184,3 @@ func (c *DingTalkChannel) SendDirectReply(sessionWebhook, content string) error return nil } - -// truncateStringDingTalk truncates a string to max length for logging (avoiding name collision with telegram.go) -func truncateStringDingTalk(s string, maxLen int) string { - if len(s) <= maxLen { - return s - } - return s[:maxLen] -} diff --git a/pkg/channels/discord.go b/pkg/channels/discord.go index ba455f0..67e8d30 100644 --- a/pkg/channels/discord.go +++ b/pkg/channels/discord.go @@ -15,6 +15,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/utils" "github.com/sipeed/picoclaw/pkg/voice" ) @@ -172,7 +173,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag logger.DebugCF("discord", "Received message", map[string]interface{}{ "sender_name": senderName, "sender_id": senderID, - "preview": truncateString(content, 50), + "preview": utils.Truncate(content, 50), }) metadata := map[string]string{ diff --git a/pkg/channels/feishu.go b/pkg/channels/feishu.go index 014095e..11dbd67 100644 --- a/pkg/channels/feishu.go +++ b/pkg/channels/feishu.go @@ -15,6 +15,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/utils" ) type FeishuChannel struct { @@ -165,7 +166,7 @@ func (c *FeishuChannel) handleMessageReceive(_ context.Context, event *larkim.P2 logger.InfoCF("feishu", "Feishu message received", map[string]interface{}{ "sender_id": senderID, "chat_id": chatID, - "preview": truncateString(content, 80), + "preview": utils.Truncate(content, 80), }) c.HandleMessage(senderID, chatID, content, nil, metadata) diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 2a14127..761ed4c 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -17,6 +17,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/utils" "github.com/sipeed/picoclaw/pkg/voice" ) @@ -247,7 +248,7 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { content = "[empty message]" } - log.Printf("Telegram message from %s: %s...", senderID, truncateString(content, 50)) + log.Printf("Telegram message from %s: %s...", senderID, utils.Truncate(content, 50)) // Thinking indicator c.bot.Send(tgbotapi.NewChatAction(chatID, tgbotapi.ChatTyping)) @@ -394,13 +395,6 @@ func parseChatID(chatIDStr string) (int64, error) { return id, err } -func truncateString(s string, maxLen int) string { - if len(s) <= maxLen { - return s - } - return s[:maxLen] -} - func markdownToTelegramHTML(text string) string { if text == "" { return "" diff --git a/pkg/channels/whatsapp.go b/pkg/channels/whatsapp.go index c5ea4f1..c95e595 100644 --- a/pkg/channels/whatsapp.go +++ b/pkg/channels/whatsapp.go @@ -12,6 +12,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/utils" ) type WhatsAppChannel struct { @@ -177,7 +178,7 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]interface{}) { metadata["user_name"] = userName } - log.Printf("WhatsApp message from %s: %s...", senderID, truncateString(content, 50)) + log.Printf("WhatsApp message from %s: %s...", senderID, utils.Truncate(content, 50)) c.HandleMessage(senderID, chatID, content, mediaPaths, metadata) } diff --git a/pkg/voice/transcriber.go b/pkg/voice/transcriber.go index 9a09c5e..9af2ea6 100644 --- a/pkg/voice/transcriber.go +++ b/pkg/voice/transcriber.go @@ -13,6 +13,7 @@ import ( "time" "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/utils" ) type GroqTranscriber struct { @@ -145,7 +146,7 @@ func (t *GroqTranscriber) Transcribe(ctx context.Context, audioFilePath string) "text_length": len(result.Text), "language": result.Language, "duration_seconds": result.Duration, - "transcription_preview": truncateText(result.Text, 50), + "transcription_preview": utils.Truncate(result.Text, 50), }) return &result, nil @@ -156,10 +157,3 @@ func (t *GroqTranscriber) IsAvailable() bool { logger.DebugCF("voice", "Checking transcriber availability", map[string]interface{}{"available": available}) return available } - -func truncateText(text string, maxLen int) string { - if len(text) <= maxLen { - return text - } - return text[:maxLen] + "..." -} From eff0f491e9afcd24fb2dbecb1b58cb4c168571ee Mon Sep 17 00:00:00 2001 From: Together Date: Thu, 12 Feb 2026 01:26:22 +0800 Subject: [PATCH 11/23] fix(agent): use atomic.Bool for AgentLoop.running to prevent data race Run() and Stop() access the `running` field from different goroutines without synchronization. Replace the bare `bool` with `sync/atomic.Bool` to eliminate the data race. --- pkg/agent/loop.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 40c9ba7..cc14cea 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strings" "sync" + "sync/atomic" "time" "github.com/sipeed/picoclaw/pkg/bus" @@ -35,7 +36,7 @@ type AgentLoop struct { sessions *session.SessionManager contextBuilder *ContextBuilder tools *tools.ToolRegistry - running bool + running atomic.Bool summarizing sync.Map // Tracks which sessions are currently being summarized } @@ -101,15 +102,14 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers sessions: sessionsManager, contextBuilder: contextBuilder, tools: toolsRegistry, - running: false, summarizing: sync.Map{}, } } func (al *AgentLoop) Run(ctx context.Context) error { - al.running = true + al.running.Store(true) - for al.running { + for al.running.Load() { select { case <-ctx.Done(): return nil @@ -138,7 +138,7 @@ func (al *AgentLoop) Run(ctx context.Context) error { } func (al *AgentLoop) Stop() { - al.running = false + al.running.Store(false) } func (al *AgentLoop) RegisterTool(tool tools.Tool) { From 5efe8a202010fc9b6660be14ee6f2ce6e8d3df11 Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Wed, 11 Feb 2026 11:41:13 -0600 Subject: [PATCH 12/23] feat(auth): add OAuth and token-based login for OpenAI and Anthropic Add `picoclaw auth` CLI command supporting: - OpenAI OAuth2 (PKCE + browser callback or device code flow) - Anthropic paste-token flow - Token storage at ~/.picoclaw/auth.json with 0600 permissions - Auto-refresh for expired OAuth tokens in provider Closes #18 Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/main.go | 237 ++++++++++++++++++++++ pkg/auth/oauth.go | 358 +++++++++++++++++++++++++++++++++ pkg/auth/oauth_test.go | 199 ++++++++++++++++++ pkg/auth/pkce.go | 29 +++ pkg/auth/pkce_test.go | 51 +++++ pkg/auth/store.go | 112 +++++++++++ pkg/auth/store_test.go | 189 +++++++++++++++++ pkg/auth/token.go | 43 ++++ pkg/config/config.go | 5 +- pkg/providers/http_provider.go | 82 +++++++- 10 files changed, 1295 insertions(+), 10 deletions(-) create mode 100644 pkg/auth/oauth.go create mode 100644 pkg/auth/oauth_test.go create mode 100644 pkg/auth/pkce.go create mode 100644 pkg/auth/pkce_test.go create mode 100644 pkg/auth/store.go create mode 100644 pkg/auth/store_test.go create mode 100644 pkg/auth/token.go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index c14ec58..e1128fe 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -19,6 +19,7 @@ import ( "github.com/chzyer/readline" "github.com/sipeed/picoclaw/pkg/agent" + "github.com/sipeed/picoclaw/pkg/auth" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/channels" "github.com/sipeed/picoclaw/pkg/config" @@ -85,6 +86,8 @@ func main() { gatewayCmd() case "status": statusCmd() + case "auth": + authCmd() case "cron": cronCmd() case "skills": @@ -152,6 +155,7 @@ func printHelp() { fmt.Println("Commands:") fmt.Println(" onboard Initialize picoclaw configuration and workspace") fmt.Println(" agent Interact with the agent directly") + fmt.Println(" auth Manage authentication (login, logout, status)") fmt.Println(" gateway Start picoclaw gateway") fmt.Println(" status Show picoclaw status") fmt.Println(" cron Manage scheduled tasks") @@ -682,6 +686,239 @@ func statusCmd() { } else { fmt.Println("vLLM/Local: not set") } + + store, _ := auth.LoadStore() + if store != nil && len(store.Credentials) > 0 { + fmt.Println("\nOAuth/Token Auth:") + for provider, cred := range store.Credentials { + status := "authenticated" + if cred.IsExpired() { + status = "expired" + } else if cred.NeedsRefresh() { + status = "needs refresh" + } + fmt.Printf(" %s (%s): %s\n", provider, cred.AuthMethod, status) + } + } + } +} + +func authCmd() { + if len(os.Args) < 3 { + authHelp() + return + } + + switch os.Args[2] { + case "login": + authLoginCmd() + case "logout": + authLogoutCmd() + case "status": + authStatusCmd() + default: + fmt.Printf("Unknown auth command: %s\n", os.Args[2]) + authHelp() + } +} + +func authHelp() { + fmt.Println("\nAuth commands:") + fmt.Println(" login Login via OAuth or paste token") + fmt.Println(" logout Remove stored credentials") + fmt.Println(" status Show current auth status") + fmt.Println() + fmt.Println("Login options:") + fmt.Println(" --provider Provider to login with (openai, anthropic)") + fmt.Println(" --device-code Use device code flow (for headless environments)") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" picoclaw auth login --provider openai") + fmt.Println(" picoclaw auth login --provider openai --device-code") + fmt.Println(" picoclaw auth login --provider anthropic") + fmt.Println(" picoclaw auth logout --provider openai") + fmt.Println(" picoclaw auth status") +} + +func authLoginCmd() { + provider := "" + useDeviceCode := false + + args := os.Args[3:] + for i := 0; i < len(args); i++ { + switch args[i] { + case "--provider", "-p": + if i+1 < len(args) { + provider = args[i+1] + i++ + } + case "--device-code": + useDeviceCode = true + } + } + + if provider == "" { + fmt.Println("Error: --provider is required") + fmt.Println("Supported providers: openai, anthropic") + return + } + + switch provider { + case "openai": + authLoginOpenAI(useDeviceCode) + case "anthropic": + authLoginPasteToken(provider) + default: + fmt.Printf("Unsupported provider: %s\n", provider) + fmt.Println("Supported providers: openai, anthropic") + } +} + +func authLoginOpenAI(useDeviceCode bool) { + cfg := auth.OpenAIOAuthConfig() + + var cred *auth.AuthCredential + var err error + + if useDeviceCode { + cred, err = auth.LoginDeviceCode(cfg) + } else { + cred, err = auth.LoginBrowser(cfg) + } + + if err != nil { + fmt.Printf("Login failed: %v\n", err) + os.Exit(1) + } + + if err := auth.SetCredential("openai", cred); err != nil { + fmt.Printf("Failed to save credentials: %v\n", err) + os.Exit(1) + } + + appCfg, err := loadConfig() + if err == nil { + appCfg.Providers.OpenAI.AuthMethod = "oauth" + if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { + fmt.Printf("Warning: could not update config: %v\n", err) + } + } + + fmt.Println("Login successful!") + if cred.AccountID != "" { + fmt.Printf("Account: %s\n", cred.AccountID) + } +} + +func authLoginPasteToken(provider string) { + cred, err := auth.LoginPasteToken(provider, os.Stdin) + if err != nil { + fmt.Printf("Login failed: %v\n", err) + os.Exit(1) + } + + if err := auth.SetCredential(provider, cred); err != nil { + fmt.Printf("Failed to save credentials: %v\n", err) + os.Exit(1) + } + + appCfg, err := loadConfig() + if err == nil { + switch provider { + case "anthropic": + appCfg.Providers.Anthropic.AuthMethod = "token" + case "openai": + appCfg.Providers.OpenAI.AuthMethod = "token" + } + if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { + fmt.Printf("Warning: could not update config: %v\n", err) + } + } + + fmt.Printf("Token saved for %s!\n", provider) +} + +func authLogoutCmd() { + provider := "" + + args := os.Args[3:] + for i := 0; i < len(args); i++ { + switch args[i] { + case "--provider", "-p": + if i+1 < len(args) { + provider = args[i+1] + i++ + } + } + } + + if provider != "" { + if err := auth.DeleteCredential(provider); err != nil { + fmt.Printf("Failed to remove credentials: %v\n", err) + os.Exit(1) + } + + appCfg, err := loadConfig() + if err == nil { + switch provider { + case "openai": + appCfg.Providers.OpenAI.AuthMethod = "" + case "anthropic": + appCfg.Providers.Anthropic.AuthMethod = "" + } + config.SaveConfig(getConfigPath(), appCfg) + } + + fmt.Printf("Logged out from %s\n", provider) + } else { + if err := auth.DeleteAllCredentials(); err != nil { + fmt.Printf("Failed to remove credentials: %v\n", err) + os.Exit(1) + } + + appCfg, err := loadConfig() + if err == nil { + appCfg.Providers.OpenAI.AuthMethod = "" + appCfg.Providers.Anthropic.AuthMethod = "" + config.SaveConfig(getConfigPath(), appCfg) + } + + fmt.Println("Logged out from all providers") + } +} + +func authStatusCmd() { + store, err := auth.LoadStore() + if err != nil { + fmt.Printf("Error loading auth store: %v\n", err) + return + } + + if len(store.Credentials) == 0 { + fmt.Println("No authenticated providers.") + fmt.Println("Run: picoclaw auth login --provider ") + return + } + + fmt.Println("\nAuthenticated Providers:") + fmt.Println("------------------------") + for provider, cred := range store.Credentials { + status := "active" + if cred.IsExpired() { + status = "expired" + } else if cred.NeedsRefresh() { + status = "needs refresh" + } + + fmt.Printf(" %s:\n", provider) + fmt.Printf(" Method: %s\n", cred.AuthMethod) + fmt.Printf(" Status: %s\n", status) + if cred.AccountID != "" { + fmt.Printf(" Account: %s\n", cred.AccountID) + } + if !cred.ExpiresAt.IsZero() { + fmt.Printf(" Expires: %s\n", cred.ExpiresAt.Format("2006-01-02 15:04")) + } } } diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go new file mode 100644 index 0000000..94a79a6 --- /dev/null +++ b/pkg/auth/oauth.go @@ -0,0 +1,358 @@ +package auth + +import ( + "context" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os/exec" + "runtime" + "strings" + "time" +) + +type OAuthProviderConfig struct { + Issuer string + ClientID string + Scopes string + Port int +} + +func OpenAIOAuthConfig() OAuthProviderConfig { + return OAuthProviderConfig{ + Issuer: "https://auth.openai.com", + ClientID: "app_EMoamEEZ73f0CkXaXp7hrann", + Scopes: "openid profile email offline_access", + Port: 1455, + } +} + +func generateState() (string, error) { + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + return "", err + } + return hex.EncodeToString(buf), nil +} + +func LoginBrowser(cfg OAuthProviderConfig) (*AuthCredential, error) { + pkce, err := GeneratePKCE() + if err != nil { + return nil, fmt.Errorf("generating PKCE: %w", err) + } + + state, err := generateState() + if err != nil { + return nil, fmt.Errorf("generating state: %w", err) + } + + redirectURI := fmt.Sprintf("http://localhost:%d/auth/callback", cfg.Port) + + authURL := buildAuthorizeURL(cfg, pkce, state, redirectURI) + + resultCh := make(chan callbackResult, 1) + + mux := http.NewServeMux() + mux.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("state") != state { + resultCh <- callbackResult{err: fmt.Errorf("state mismatch")} + http.Error(w, "State mismatch", http.StatusBadRequest) + return + } + + code := r.URL.Query().Get("code") + if code == "" { + errMsg := r.URL.Query().Get("error") + resultCh <- callbackResult{err: fmt.Errorf("no code received: %s", errMsg)} + http.Error(w, "No authorization code received", http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "text/html") + fmt.Fprint(w, "

    Authentication successful!

    You can close this window.

    ") + resultCh <- callbackResult{code: code} + }) + + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", cfg.Port)) + if err != nil { + return nil, fmt.Errorf("starting callback server on port %d: %w", cfg.Port, err) + } + + server := &http.Server{Handler: mux} + go server.Serve(listener) + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + server.Shutdown(ctx) + }() + + if err := openBrowser(authURL); err != nil { + fmt.Printf("Could not open browser automatically.\nPlease open this URL manually:\n\n%s\n\n", authURL) + } + + fmt.Println("Waiting for authentication in browser...") + + select { + case result := <-resultCh: + if result.err != nil { + return nil, result.err + } + return exchangeCodeForTokens(cfg, result.code, pkce.CodeVerifier, redirectURI) + case <-time.After(5 * time.Minute): + return nil, fmt.Errorf("authentication timed out after 5 minutes") + } +} + +type callbackResult struct { + code string + err error +} + +func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) { + reqBody, _ := json.Marshal(map[string]string{ + "client_id": cfg.ClientID, + }) + + resp, err := http.Post( + cfg.Issuer+"/api/accounts/deviceauth/usercode", + "application/json", + strings.NewReader(string(reqBody)), + ) + if err != nil { + return nil, fmt.Errorf("requesting device code: %w", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("device code request failed: %s", string(body)) + } + + var deviceResp struct { + DeviceAuthID string `json:"device_auth_id"` + UserCode string `json:"user_code"` + Interval int `json:"interval"` + } + if err := json.Unmarshal(body, &deviceResp); err != nil { + return nil, fmt.Errorf("parsing device code response: %w", err) + } + + if deviceResp.Interval < 1 { + deviceResp.Interval = 5 + } + + fmt.Printf("\nTo authenticate, open this URL in your browser:\n\n %s/codex/device\n\nThen enter this code: %s\n\nWaiting for authentication...\n", + cfg.Issuer, deviceResp.UserCode) + + deadline := time.After(15 * time.Minute) + ticker := time.NewTicker(time.Duration(deviceResp.Interval) * time.Second) + defer ticker.Stop() + + for { + select { + case <-deadline: + return nil, fmt.Errorf("device code authentication timed out after 15 minutes") + case <-ticker.C: + cred, err := pollDeviceCode(cfg, deviceResp.DeviceAuthID, deviceResp.UserCode) + if err != nil { + continue + } + if cred != nil { + return cred, nil + } + } + } +} + +func pollDeviceCode(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*AuthCredential, error) { + reqBody, _ := json.Marshal(map[string]string{ + "device_auth_id": deviceAuthID, + "user_code": userCode, + }) + + resp, err := http.Post( + cfg.Issuer+"/api/accounts/deviceauth/token", + "application/json", + strings.NewReader(string(reqBody)), + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("pending") + } + + body, _ := io.ReadAll(resp.Body) + + var tokenResp struct { + AuthorizationCode string `json:"authorization_code"` + CodeChallenge string `json:"code_challenge"` + CodeVerifier string `json:"code_verifier"` + } + if err := json.Unmarshal(body, &tokenResp); err != nil { + return nil, err + } + + redirectURI := cfg.Issuer + "/deviceauth/callback" + return exchangeCodeForTokens(cfg, tokenResp.AuthorizationCode, tokenResp.CodeVerifier, redirectURI) +} + +func RefreshAccessToken(cred *AuthCredential, cfg OAuthProviderConfig) (*AuthCredential, error) { + if cred.RefreshToken == "" { + return nil, fmt.Errorf("no refresh token available") + } + + data := url.Values{ + "client_id": {cfg.ClientID}, + "grant_type": {"refresh_token"}, + "refresh_token": {cred.RefreshToken}, + "scope": {"openid profile email"}, + } + + resp, err := http.PostForm(cfg.Issuer+"/oauth/token", data) + if err != nil { + return nil, fmt.Errorf("refreshing token: %w", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("token refresh failed: %s", string(body)) + } + + return parseTokenResponse(body, cred.Provider) +} + +func BuildAuthorizeURL(cfg OAuthProviderConfig, pkce PKCECodes, state, redirectURI string) string { + return buildAuthorizeURL(cfg, pkce, state, redirectURI) +} + +func buildAuthorizeURL(cfg OAuthProviderConfig, pkce PKCECodes, state, redirectURI string) string { + params := url.Values{ + "response_type": {"code"}, + "client_id": {cfg.ClientID}, + "redirect_uri": {redirectURI}, + "scope": {cfg.Scopes}, + "code_challenge": {pkce.CodeChallenge}, + "code_challenge_method": {"S256"}, + "state": {state}, + } + return cfg.Issuer + "/authorize?" + params.Encode() +} + +func exchangeCodeForTokens(cfg OAuthProviderConfig, code, codeVerifier, redirectURI string) (*AuthCredential, error) { + data := url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "redirect_uri": {redirectURI}, + "client_id": {cfg.ClientID}, + "code_verifier": {codeVerifier}, + } + + resp, err := http.PostForm(cfg.Issuer+"/oauth/token", data) + if err != nil { + return nil, fmt.Errorf("exchanging code for tokens: %w", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("token exchange failed: %s", string(body)) + } + + return parseTokenResponse(body, "openai") +} + +func parseTokenResponse(body []byte, provider string) (*AuthCredential, error) { + var tokenResp struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` + IDToken string `json:"id_token"` + } + if err := json.Unmarshal(body, &tokenResp); err != nil { + return nil, fmt.Errorf("parsing token response: %w", err) + } + + if tokenResp.AccessToken == "" { + return nil, fmt.Errorf("no access token in response") + } + + var expiresAt time.Time + if tokenResp.ExpiresIn > 0 { + expiresAt = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second) + } + + cred := &AuthCredential{ + AccessToken: tokenResp.AccessToken, + RefreshToken: tokenResp.RefreshToken, + ExpiresAt: expiresAt, + Provider: provider, + AuthMethod: "oauth", + } + + if accountID := extractAccountID(tokenResp.AccessToken); accountID != "" { + cred.AccountID = accountID + } + + return cred, nil +} + +func extractAccountID(accessToken string) string { + parts := strings.Split(accessToken, ".") + if len(parts) < 2 { + return "" + } + + payload := parts[1] + switch len(payload) % 4 { + case 2: + payload += "==" + case 3: + payload += "=" + } + + decoded, err := base64URLDecode(payload) + if err != nil { + return "" + } + + var claims map[string]interface{} + if err := json.Unmarshal(decoded, &claims); err != nil { + return "" + } + + if authClaim, ok := claims["https://api.openai.com/auth"].(map[string]interface{}); ok { + if accountID, ok := authClaim["chatgpt_account_id"].(string); ok { + return accountID + } + } + + return "" +} + +func base64URLDecode(s string) ([]byte, error) { + s = strings.NewReplacer("-", "+", "_", "/").Replace(s) + return base64.StdEncoding.DecodeString(s) +} + +func openBrowser(url string) error { + switch runtime.GOOS { + case "darwin": + return exec.Command("open", url).Start() + case "linux": + return exec.Command("xdg-open", url).Start() + case "windows": + return exec.Command("cmd", "/c", "start", url).Start() + default: + return fmt.Errorf("unsupported platform: %s", runtime.GOOS) + } +} diff --git a/pkg/auth/oauth_test.go b/pkg/auth/oauth_test.go new file mode 100644 index 0000000..00b4c60 --- /dev/null +++ b/pkg/auth/oauth_test.go @@ -0,0 +1,199 @@ +package auth + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestBuildAuthorizeURL(t *testing.T) { + cfg := OAuthProviderConfig{ + Issuer: "https://auth.example.com", + ClientID: "test-client-id", + Scopes: "openid profile", + Port: 1455, + } + pkce := PKCECodes{ + CodeVerifier: "test-verifier", + CodeChallenge: "test-challenge", + } + + u := BuildAuthorizeURL(cfg, pkce, "test-state", "http://localhost:1455/auth/callback") + + if !strings.HasPrefix(u, "https://auth.example.com/authorize?") { + t.Errorf("URL does not start with expected prefix: %s", u) + } + if !strings.Contains(u, "client_id=test-client-id") { + t.Error("URL missing client_id") + } + if !strings.Contains(u, "code_challenge=test-challenge") { + t.Error("URL missing code_challenge") + } + if !strings.Contains(u, "code_challenge_method=S256") { + t.Error("URL missing code_challenge_method") + } + if !strings.Contains(u, "state=test-state") { + t.Error("URL missing state") + } + if !strings.Contains(u, "response_type=code") { + t.Error("URL missing response_type") + } +} + +func TestParseTokenResponse(t *testing.T) { + resp := map[string]interface{}{ + "access_token": "test-access-token", + "refresh_token": "test-refresh-token", + "expires_in": 3600, + "id_token": "test-id-token", + } + body, _ := json.Marshal(resp) + + cred, err := parseTokenResponse(body, "openai") + if err != nil { + t.Fatalf("parseTokenResponse() error: %v", err) + } + + if cred.AccessToken != "test-access-token" { + t.Errorf("AccessToken = %q, want %q", cred.AccessToken, "test-access-token") + } + if cred.RefreshToken != "test-refresh-token" { + t.Errorf("RefreshToken = %q, want %q", cred.RefreshToken, "test-refresh-token") + } + if cred.Provider != "openai" { + t.Errorf("Provider = %q, want %q", cred.Provider, "openai") + } + if cred.AuthMethod != "oauth" { + t.Errorf("AuthMethod = %q, want %q", cred.AuthMethod, "oauth") + } + if cred.ExpiresAt.IsZero() { + t.Error("ExpiresAt should not be zero") + } +} + +func TestParseTokenResponseNoAccessToken(t *testing.T) { + body := []byte(`{"refresh_token": "test"}`) + _, err := parseTokenResponse(body, "openai") + if err == nil { + t.Error("expected error for missing access_token") + } +} + +func TestExchangeCodeForTokens(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/oauth/token" { + http.Error(w, "not found", http.StatusNotFound) + return + } + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + r.ParseForm() + if r.FormValue("grant_type") != "authorization_code" { + http.Error(w, "invalid grant_type", http.StatusBadRequest) + return + } + + resp := map[string]interface{}{ + "access_token": "mock-access-token", + "refresh_token": "mock-refresh-token", + "expires_in": 3600, + } + json.NewEncoder(w).Encode(resp) + })) + defer server.Close() + + cfg := OAuthProviderConfig{ + Issuer: server.URL, + ClientID: "test-client", + Scopes: "openid", + Port: 1455, + } + + cred, err := exchangeCodeForTokens(cfg, "test-code", "test-verifier", "http://localhost:1455/auth/callback") + if err != nil { + t.Fatalf("exchangeCodeForTokens() error: %v", err) + } + + if cred.AccessToken != "mock-access-token" { + t.Errorf("AccessToken = %q, want %q", cred.AccessToken, "mock-access-token") + } +} + +func TestRefreshAccessToken(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/oauth/token" { + http.Error(w, "not found", http.StatusNotFound) + return + } + + r.ParseForm() + if r.FormValue("grant_type") != "refresh_token" { + http.Error(w, "invalid grant_type", http.StatusBadRequest) + return + } + + resp := map[string]interface{}{ + "access_token": "refreshed-access-token", + "refresh_token": "refreshed-refresh-token", + "expires_in": 3600, + } + json.NewEncoder(w).Encode(resp) + })) + defer server.Close() + + cfg := OAuthProviderConfig{ + Issuer: server.URL, + ClientID: "test-client", + } + + cred := &AuthCredential{ + AccessToken: "old-token", + RefreshToken: "old-refresh-token", + Provider: "openai", + AuthMethod: "oauth", + } + + refreshed, err := RefreshAccessToken(cred, cfg) + if err != nil { + t.Fatalf("RefreshAccessToken() error: %v", err) + } + + if refreshed.AccessToken != "refreshed-access-token" { + t.Errorf("AccessToken = %q, want %q", refreshed.AccessToken, "refreshed-access-token") + } + if refreshed.RefreshToken != "refreshed-refresh-token" { + t.Errorf("RefreshToken = %q, want %q", refreshed.RefreshToken, "refreshed-refresh-token") + } +} + +func TestRefreshAccessTokenNoRefreshToken(t *testing.T) { + cfg := OpenAIOAuthConfig() + cred := &AuthCredential{ + AccessToken: "old-token", + Provider: "openai", + AuthMethod: "oauth", + } + + _, err := RefreshAccessToken(cred, cfg) + if err == nil { + t.Error("expected error for missing refresh token") + } +} + +func TestOpenAIOAuthConfig(t *testing.T) { + cfg := OpenAIOAuthConfig() + if cfg.Issuer != "https://auth.openai.com" { + t.Errorf("Issuer = %q, want %q", cfg.Issuer, "https://auth.openai.com") + } + if cfg.ClientID == "" { + t.Error("ClientID is empty") + } + if cfg.Port != 1455 { + t.Errorf("Port = %d, want 1455", cfg.Port) + } +} diff --git a/pkg/auth/pkce.go b/pkg/auth/pkce.go new file mode 100644 index 0000000..499daf8 --- /dev/null +++ b/pkg/auth/pkce.go @@ -0,0 +1,29 @@ +package auth + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/base64" +) + +type PKCECodes struct { + CodeVerifier string + CodeChallenge string +} + +func GeneratePKCE() (PKCECodes, error) { + buf := make([]byte, 64) + if _, err := rand.Read(buf); err != nil { + return PKCECodes{}, err + } + + verifier := base64.RawURLEncoding.EncodeToString(buf) + + hash := sha256.Sum256([]byte(verifier)) + challenge := base64.RawURLEncoding.EncodeToString(hash[:]) + + return PKCECodes{ + CodeVerifier: verifier, + CodeChallenge: challenge, + }, nil +} diff --git a/pkg/auth/pkce_test.go b/pkg/auth/pkce_test.go new file mode 100644 index 0000000..74ed573 --- /dev/null +++ b/pkg/auth/pkce_test.go @@ -0,0 +1,51 @@ +package auth + +import ( + "crypto/sha256" + "encoding/base64" + "testing" +) + +func TestGeneratePKCE(t *testing.T) { + codes, err := GeneratePKCE() + if err != nil { + t.Fatalf("GeneratePKCE() error: %v", err) + } + + if codes.CodeVerifier == "" { + t.Fatal("CodeVerifier is empty") + } + if codes.CodeChallenge == "" { + t.Fatal("CodeChallenge is empty") + } + + verifierBytes, err := base64.RawURLEncoding.DecodeString(codes.CodeVerifier) + if err != nil { + t.Fatalf("CodeVerifier is not valid base64url: %v", err) + } + if len(verifierBytes) != 64 { + t.Errorf("CodeVerifier decoded length = %d, want 64", len(verifierBytes)) + } + + hash := sha256.Sum256([]byte(codes.CodeVerifier)) + expectedChallenge := base64.RawURLEncoding.EncodeToString(hash[:]) + if codes.CodeChallenge != expectedChallenge { + t.Errorf("CodeChallenge = %q, want SHA256 of verifier = %q", codes.CodeChallenge, expectedChallenge) + } +} + +func TestGeneratePKCEUniqueness(t *testing.T) { + codes1, err := GeneratePKCE() + if err != nil { + t.Fatalf("GeneratePKCE() error: %v", err) + } + + codes2, err := GeneratePKCE() + if err != nil { + t.Fatalf("GeneratePKCE() error: %v", err) + } + + if codes1.CodeVerifier == codes2.CodeVerifier { + t.Error("two GeneratePKCE() calls produced identical verifiers") + } +} diff --git a/pkg/auth/store.go b/pkg/auth/store.go new file mode 100644 index 0000000..2072492 --- /dev/null +++ b/pkg/auth/store.go @@ -0,0 +1,112 @@ +package auth + +import ( + "encoding/json" + "os" + "path/filepath" + "time" +) + +type AuthCredential struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token,omitempty"` + AccountID string `json:"account_id,omitempty"` + ExpiresAt time.Time `json:"expires_at,omitempty"` + Provider string `json:"provider"` + AuthMethod string `json:"auth_method"` +} + +type AuthStore struct { + Credentials map[string]*AuthCredential `json:"credentials"` +} + +func (c *AuthCredential) IsExpired() bool { + if c.ExpiresAt.IsZero() { + return false + } + return time.Now().After(c.ExpiresAt) +} + +func (c *AuthCredential) NeedsRefresh() bool { + if c.ExpiresAt.IsZero() { + return false + } + return time.Now().Add(5 * time.Minute).After(c.ExpiresAt) +} + +func authFilePath() string { + home, _ := os.UserHomeDir() + return filepath.Join(home, ".picoclaw", "auth.json") +} + +func LoadStore() (*AuthStore, error) { + path := authFilePath() + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return &AuthStore{Credentials: make(map[string]*AuthCredential)}, nil + } + return nil, err + } + + var store AuthStore + if err := json.Unmarshal(data, &store); err != nil { + return nil, err + } + if store.Credentials == nil { + store.Credentials = make(map[string]*AuthCredential) + } + return &store, nil +} + +func SaveStore(store *AuthStore) error { + path := authFilePath() + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + data, err := json.MarshalIndent(store, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0600) +} + +func GetCredential(provider string) (*AuthCredential, error) { + store, err := LoadStore() + if err != nil { + return nil, err + } + cred, ok := store.Credentials[provider] + if !ok { + return nil, nil + } + return cred, nil +} + +func SetCredential(provider string, cred *AuthCredential) error { + store, err := LoadStore() + if err != nil { + return err + } + store.Credentials[provider] = cred + return SaveStore(store) +} + +func DeleteCredential(provider string) error { + store, err := LoadStore() + if err != nil { + return err + } + delete(store.Credentials, provider) + return SaveStore(store) +} + +func DeleteAllCredentials() error { + path := authFilePath() + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return err + } + return nil +} diff --git a/pkg/auth/store_test.go b/pkg/auth/store_test.go new file mode 100644 index 0000000..d96b460 --- /dev/null +++ b/pkg/auth/store_test.go @@ -0,0 +1,189 @@ +package auth + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestAuthCredentialIsExpired(t *testing.T) { + tests := []struct { + name string + expiresAt time.Time + want bool + }{ + {"zero time", time.Time{}, false}, + {"future", time.Now().Add(time.Hour), false}, + {"past", time.Now().Add(-time.Hour), true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &AuthCredential{ExpiresAt: tt.expiresAt} + if got := c.IsExpired(); got != tt.want { + t.Errorf("IsExpired() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAuthCredentialNeedsRefresh(t *testing.T) { + tests := []struct { + name string + expiresAt time.Time + want bool + }{ + {"zero time", time.Time{}, false}, + {"far future", time.Now().Add(time.Hour), false}, + {"within 5 min", time.Now().Add(3 * time.Minute), true}, + {"already expired", time.Now().Add(-time.Minute), true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &AuthCredential{ExpiresAt: tt.expiresAt} + if got := c.NeedsRefresh(); got != tt.want { + t.Errorf("NeedsRefresh() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStoreRoundtrip(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + t.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", origHome) + + cred := &AuthCredential{ + AccessToken: "test-access-token", + RefreshToken: "test-refresh-token", + AccountID: "acct-123", + ExpiresAt: time.Now().Add(time.Hour).Truncate(time.Second), + Provider: "openai", + AuthMethod: "oauth", + } + + if err := SetCredential("openai", cred); err != nil { + t.Fatalf("SetCredential() error: %v", err) + } + + loaded, err := GetCredential("openai") + if err != nil { + t.Fatalf("GetCredential() error: %v", err) + } + if loaded == nil { + t.Fatal("GetCredential() returned nil") + } + if loaded.AccessToken != cred.AccessToken { + t.Errorf("AccessToken = %q, want %q", loaded.AccessToken, cred.AccessToken) + } + if loaded.RefreshToken != cred.RefreshToken { + t.Errorf("RefreshToken = %q, want %q", loaded.RefreshToken, cred.RefreshToken) + } + if loaded.Provider != cred.Provider { + t.Errorf("Provider = %q, want %q", loaded.Provider, cred.Provider) + } +} + +func TestStoreFilePermissions(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + t.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", origHome) + + cred := &AuthCredential{ + AccessToken: "secret-token", + Provider: "openai", + AuthMethod: "oauth", + } + if err := SetCredential("openai", cred); err != nil { + t.Fatalf("SetCredential() error: %v", err) + } + + path := filepath.Join(tmpDir, ".picoclaw", "auth.json") + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat() error: %v", err) + } + perm := info.Mode().Perm() + if perm != 0600 { + t.Errorf("file permissions = %o, want 0600", perm) + } +} + +func TestStoreMultiProvider(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + t.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", origHome) + + openaiCred := &AuthCredential{AccessToken: "openai-token", Provider: "openai", AuthMethod: "oauth"} + anthropicCred := &AuthCredential{AccessToken: "anthropic-token", Provider: "anthropic", AuthMethod: "token"} + + if err := SetCredential("openai", openaiCred); err != nil { + t.Fatalf("SetCredential(openai) error: %v", err) + } + if err := SetCredential("anthropic", anthropicCred); err != nil { + t.Fatalf("SetCredential(anthropic) error: %v", err) + } + + loaded, err := GetCredential("openai") + if err != nil { + t.Fatalf("GetCredential(openai) error: %v", err) + } + if loaded.AccessToken != "openai-token" { + t.Errorf("openai token = %q, want %q", loaded.AccessToken, "openai-token") + } + + loaded, err = GetCredential("anthropic") + if err != nil { + t.Fatalf("GetCredential(anthropic) error: %v", err) + } + if loaded.AccessToken != "anthropic-token" { + t.Errorf("anthropic token = %q, want %q", loaded.AccessToken, "anthropic-token") + } +} + +func TestDeleteCredential(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + t.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", origHome) + + cred := &AuthCredential{AccessToken: "to-delete", Provider: "openai", AuthMethod: "oauth"} + if err := SetCredential("openai", cred); err != nil { + t.Fatalf("SetCredential() error: %v", err) + } + + if err := DeleteCredential("openai"); err != nil { + t.Fatalf("DeleteCredential() error: %v", err) + } + + loaded, err := GetCredential("openai") + if err != nil { + t.Fatalf("GetCredential() error: %v", err) + } + if loaded != nil { + t.Error("expected nil after delete") + } +} + +func TestLoadStoreEmpty(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + t.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", origHome) + + store, err := LoadStore() + if err != nil { + t.Fatalf("LoadStore() error: %v", err) + } + if store == nil { + t.Fatal("LoadStore() returned nil") + } + if len(store.Credentials) != 0 { + t.Errorf("expected empty credentials, got %d", len(store.Credentials)) + } +} diff --git a/pkg/auth/token.go b/pkg/auth/token.go new file mode 100644 index 0000000..a5a13ff --- /dev/null +++ b/pkg/auth/token.go @@ -0,0 +1,43 @@ +package auth + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +func LoginPasteToken(provider string, r io.Reader) (*AuthCredential, error) { + fmt.Printf("Paste your API key or session token from %s:\n", providerDisplayName(provider)) + fmt.Print("> ") + + scanner := bufio.NewScanner(r) + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("reading token: %w", err) + } + return nil, fmt.Errorf("no input received") + } + + token := strings.TrimSpace(scanner.Text()) + if token == "" { + return nil, fmt.Errorf("token cannot be empty") + } + + return &AuthCredential{ + AccessToken: token, + Provider: provider, + AuthMethod: "token", + }, nil +} + +func providerDisplayName(provider string) string { + switch provider { + case "anthropic": + return "console.anthropic.com" + case "openai": + return "platform.openai.com" + default: + return provider + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 5b9c2b5..7fc6253 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -99,8 +99,9 @@ type ProvidersConfig struct { } type ProviderConfig struct { - APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"` - APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"` + APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"` + APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"` + AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"` } type GatewayConfig struct { diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 12909df..dab6132 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -15,13 +15,16 @@ import ( "net/http" "strings" + "github.com/sipeed/picoclaw/pkg/auth" "github.com/sipeed/picoclaw/pkg/config" ) type HTTPProvider struct { - apiKey string - apiBase string - httpClient *http.Client + apiKey string + apiBase string + httpClient *http.Client + tokenSource func() (string, error) + accountID string } func NewHTTPProvider(apiKey, apiBase string) *HTTPProvider { @@ -73,9 +76,17 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too } req.Header.Set("Content-Type", "application/json") - if p.apiKey != "" { - authHeader := "Bearer " + p.apiKey - req.Header.Set("Authorization", authHeader) + if p.tokenSource != nil { + token, err := p.tokenSource() + if err != nil { + return nil, fmt.Errorf("failed to get auth token: %w", err) + } + req.Header.Set("Authorization", "Bearer "+token) + if p.accountID != "" { + req.Header.Set("Chatgpt-Account-Id", p.accountID) + } + } else if p.apiKey != "" { + req.Header.Set("Authorization", "Bearer "+p.apiKey) } resp, err := p.httpClient.Do(req) @@ -170,6 +181,47 @@ func (p *HTTPProvider) GetDefaultModel() string { return "" } +func createOAuthTokenSource(provider string) func() (string, error) { + return func() (string, error) { + cred, err := auth.GetCredential(provider) + if err != nil { + return "", fmt.Errorf("loading auth credentials: %w", err) + } + if cred == nil { + return "", fmt.Errorf("no OAuth credentials for %s. Run: picoclaw auth login --provider %s", provider, provider) + } + + if cred.AuthMethod == "oauth" && cred.NeedsRefresh() && cred.RefreshToken != "" { + oauthCfg := auth.OpenAIOAuthConfig() + refreshed, err := auth.RefreshAccessToken(cred, oauthCfg) + if err != nil { + return "", fmt.Errorf("refreshing token: %w", err) + } + if err := auth.SetCredential(provider, refreshed); err != nil { + return "", fmt.Errorf("saving refreshed token: %w", err) + } + return refreshed.AccessToken, nil + } + + return cred.AccessToken, nil + } +} + +func createAuthProvider(providerName string, apiBase string) (LLMProvider, error) { + cred, err := auth.GetCredential(providerName) + if err != nil { + return nil, fmt.Errorf("loading auth credentials: %w", err) + } + if cred == nil { + return nil, fmt.Errorf("no credentials for %s. Run: picoclaw auth login --provider %s", providerName, providerName) + } + + p := NewHTTPProvider(cred.AccessToken, apiBase) + p.tokenSource = createOAuthTokenSource(providerName) + p.accountID = cred.AccountID + return p, nil +} + func CreateProvider(cfg *config.Config) (LLMProvider, error) { model := cfg.Agents.Defaults.Model @@ -186,14 +238,28 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { apiBase = "https://openrouter.ai/api/v1" } - case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && cfg.Providers.Anthropic.APIKey != "": + case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && (cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != ""): + if cfg.Providers.Anthropic.AuthMethod == "oauth" || cfg.Providers.Anthropic.AuthMethod == "token" { + ab := cfg.Providers.Anthropic.APIBase + if ab == "" { + ab = "https://api.anthropic.com/v1" + } + return createAuthProvider("anthropic", ab) + } apiKey = cfg.Providers.Anthropic.APIKey apiBase = cfg.Providers.Anthropic.APIBase if apiBase == "" { apiBase = "https://api.anthropic.com/v1" } - case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && cfg.Providers.OpenAI.APIKey != "": + case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && (cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != ""): + if cfg.Providers.OpenAI.AuthMethod == "oauth" || cfg.Providers.OpenAI.AuthMethod == "token" { + ab := cfg.Providers.OpenAI.APIBase + if ab == "" { + ab = "https://api.openai.com/v1" + } + return createAuthProvider("openai", ab) + } apiKey = cfg.Providers.OpenAI.APIKey apiBase = cfg.Providers.OpenAI.APIBase if apiBase == "" { From 3d54ec59e250612e6f50ea8eabd3c5269281f858 Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Wed, 11 Feb 2026 12:48:16 -0600 Subject: [PATCH 13/23] feat(migrate): add picoclaw migrate command for OpenClaw workspace migration Add a new `picoclaw migrate` CLI command that detects an existing OpenClaw installation and migrates workspace files and configuration to PicoClaw. Workspace markdown files (SOUL.md, AGENTS.md, USER.md, TOOLS.md, HEARTBEAT.md, memory/, skills/) are copied 1:1. Config keys are mapped from OpenClaw's camelCase JSON format to PicoClaw's snake_case format with provider and channel field mapping. Supports --dry-run, --refresh, --config-only, --workspace-only, --force flags. Existing PicoClaw files are never silently overwritten; backups are created. Closes #27 Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/main.go | 74 ++++ pkg/migrate/config.go | 377 ++++++++++++++++ pkg/migrate/migrate.go | 394 +++++++++++++++++ pkg/migrate/migrate_test.go | 854 ++++++++++++++++++++++++++++++++++++ pkg/migrate/workspace.go | 106 +++++ 5 files changed, 1805 insertions(+) create mode 100644 pkg/migrate/config.go create mode 100644 pkg/migrate/migrate.go create mode 100644 pkg/migrate/migrate_test.go create mode 100644 pkg/migrate/workspace.go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index c14ec58..ee794d6 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -25,6 +25,7 @@ import ( "github.com/sipeed/picoclaw/pkg/cron" "github.com/sipeed/picoclaw/pkg/heartbeat" "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/migrate" "github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/skills" "github.com/sipeed/picoclaw/pkg/tools" @@ -85,6 +86,8 @@ func main() { gatewayCmd() case "status": statusCmd() + case "migrate": + migrateCmd() case "cron": cronCmd() case "skills": @@ -155,6 +158,7 @@ func printHelp() { fmt.Println(" gateway Start picoclaw gateway") fmt.Println(" status Show picoclaw status") fmt.Println(" cron Manage scheduled tasks") + fmt.Println(" migrate Migrate from OpenClaw to PicoClaw") fmt.Println(" skills Manage skills (install, list, remove)") fmt.Println(" version Show version information") } @@ -360,6 +364,76 @@ This file stores important information that should persist across sessions. } } +func migrateCmd() { + if len(os.Args) > 2 && (os.Args[2] == "--help" || os.Args[2] == "-h") { + migrateHelp() + return + } + + opts := migrate.Options{} + + args := os.Args[2:] + for i := 0; i < len(args); i++ { + switch args[i] { + case "--dry-run": + opts.DryRun = true + case "--config-only": + opts.ConfigOnly = true + case "--workspace-only": + opts.WorkspaceOnly = true + case "--force": + opts.Force = true + case "--refresh": + opts.Refresh = true + case "--openclaw-home": + if i+1 < len(args) { + opts.OpenClawHome = args[i+1] + i++ + } + case "--picoclaw-home": + if i+1 < len(args) { + opts.PicoClawHome = args[i+1] + i++ + } + default: + fmt.Printf("Unknown flag: %s\n", args[i]) + migrateHelp() + os.Exit(1) + } + } + + result, err := migrate.Run(opts) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + if !opts.DryRun { + migrate.PrintSummary(result) + } +} + +func migrateHelp() { + fmt.Println("\nMigrate from OpenClaw to PicoClaw") + fmt.Println() + fmt.Println("Usage: picoclaw migrate [options]") + fmt.Println() + fmt.Println("Options:") + fmt.Println(" --dry-run Show what would be migrated without making changes") + fmt.Println(" --refresh Re-sync workspace files from OpenClaw (repeatable)") + fmt.Println(" --config-only Only migrate config, skip workspace files") + fmt.Println(" --workspace-only Only migrate workspace files, skip config") + fmt.Println(" --force Skip confirmation prompts") + fmt.Println(" --openclaw-home Override OpenClaw home directory (default: ~/.openclaw)") + fmt.Println(" --picoclaw-home Override PicoClaw home directory (default: ~/.picoclaw)") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" picoclaw migrate Detect and migrate from OpenClaw") + fmt.Println(" picoclaw migrate --dry-run Show what would be migrated") + fmt.Println(" picoclaw migrate --refresh Re-sync workspace files") + fmt.Println(" picoclaw migrate --force Migrate without confirmation") +} + func agentCmd() { message := "" sessionKey := "cli:default" diff --git a/pkg/migrate/config.go b/pkg/migrate/config.go new file mode 100644 index 0000000..d7fa633 --- /dev/null +++ b/pkg/migrate/config.go @@ -0,0 +1,377 @@ +package migrate + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "unicode" + + "github.com/sipeed/picoclaw/pkg/config" +) + +var supportedProviders = map[string]bool{ + "anthropic": true, + "openai": true, + "openrouter": true, + "groq": true, + "zhipu": true, + "vllm": true, + "gemini": true, +} + +var supportedChannels = map[string]bool{ + "telegram": true, + "discord": true, + "whatsapp": true, + "feishu": true, + "qq": true, + "dingtalk": true, + "maixcam": true, +} + +func findOpenClawConfig(openclawHome string) (string, error) { + candidates := []string{ + filepath.Join(openclawHome, "openclaw.json"), + filepath.Join(openclawHome, "config.json"), + } + for _, p := range candidates { + if _, err := os.Stat(p); err == nil { + return p, nil + } + } + return "", fmt.Errorf("no config file found in %s (tried openclaw.json, config.json)", openclawHome) +} + +func LoadOpenClawConfig(configPath string) (map[string]interface{}, error) { + data, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("reading OpenClaw config: %w", err) + } + + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return nil, fmt.Errorf("parsing OpenClaw config: %w", err) + } + + converted := convertKeysToSnake(raw) + result, ok := converted.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected config format") + } + return result, nil +} + +func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error) { + cfg := config.DefaultConfig() + var warnings []string + + if agents, ok := getMap(data, "agents"); ok { + if defaults, ok := getMap(agents, "defaults"); ok { + if v, ok := getString(defaults, "model"); ok { + cfg.Agents.Defaults.Model = v + } + if v, ok := getFloat(defaults, "max_tokens"); ok { + cfg.Agents.Defaults.MaxTokens = int(v) + } + if v, ok := getFloat(defaults, "temperature"); ok { + cfg.Agents.Defaults.Temperature = v + } + if v, ok := getFloat(defaults, "max_tool_iterations"); ok { + cfg.Agents.Defaults.MaxToolIterations = int(v) + } + if v, ok := getString(defaults, "workspace"); ok { + cfg.Agents.Defaults.Workspace = rewriteWorkspacePath(v) + } + } + } + + if providers, ok := getMap(data, "providers"); ok { + for name, val := range providers { + pMap, ok := val.(map[string]interface{}) + if !ok { + continue + } + apiKey, _ := getString(pMap, "api_key") + apiBase, _ := getString(pMap, "api_base") + + if !supportedProviders[name] { + if apiKey != "" || apiBase != "" { + warnings = append(warnings, fmt.Sprintf("Provider '%s' not supported in PicoClaw, skipping", name)) + } + continue + } + + pc := config.ProviderConfig{APIKey: apiKey, APIBase: apiBase} + switch name { + case "anthropic": + cfg.Providers.Anthropic = pc + case "openai": + cfg.Providers.OpenAI = pc + case "openrouter": + cfg.Providers.OpenRouter = pc + case "groq": + cfg.Providers.Groq = pc + case "zhipu": + cfg.Providers.Zhipu = pc + case "vllm": + cfg.Providers.VLLM = pc + case "gemini": + cfg.Providers.Gemini = pc + } + } + } + + if channels, ok := getMap(data, "channels"); ok { + for name, val := range channels { + cMap, ok := val.(map[string]interface{}) + if !ok { + continue + } + if !supportedChannels[name] { + warnings = append(warnings, fmt.Sprintf("Channel '%s' not supported in PicoClaw, skipping", name)) + continue + } + enabled, _ := getBool(cMap, "enabled") + allowFrom := getStringSlice(cMap, "allow_from") + + switch name { + case "telegram": + cfg.Channels.Telegram.Enabled = enabled + cfg.Channels.Telegram.AllowFrom = allowFrom + if v, ok := getString(cMap, "token"); ok { + cfg.Channels.Telegram.Token = v + } + case "discord": + cfg.Channels.Discord.Enabled = enabled + cfg.Channels.Discord.AllowFrom = allowFrom + if v, ok := getString(cMap, "token"); ok { + cfg.Channels.Discord.Token = v + } + case "whatsapp": + cfg.Channels.WhatsApp.Enabled = enabled + cfg.Channels.WhatsApp.AllowFrom = allowFrom + if v, ok := getString(cMap, "bridge_url"); ok { + cfg.Channels.WhatsApp.BridgeURL = v + } + case "feishu": + cfg.Channels.Feishu.Enabled = enabled + cfg.Channels.Feishu.AllowFrom = allowFrom + if v, ok := getString(cMap, "app_id"); ok { + cfg.Channels.Feishu.AppID = v + } + if v, ok := getString(cMap, "app_secret"); ok { + cfg.Channels.Feishu.AppSecret = v + } + if v, ok := getString(cMap, "encrypt_key"); ok { + cfg.Channels.Feishu.EncryptKey = v + } + if v, ok := getString(cMap, "verification_token"); ok { + cfg.Channels.Feishu.VerificationToken = v + } + case "qq": + cfg.Channels.QQ.Enabled = enabled + cfg.Channels.QQ.AllowFrom = allowFrom + if v, ok := getString(cMap, "app_id"); ok { + cfg.Channels.QQ.AppID = v + } + if v, ok := getString(cMap, "app_secret"); ok { + cfg.Channels.QQ.AppSecret = v + } + case "dingtalk": + cfg.Channels.DingTalk.Enabled = enabled + cfg.Channels.DingTalk.AllowFrom = allowFrom + if v, ok := getString(cMap, "client_id"); ok { + cfg.Channels.DingTalk.ClientID = v + } + if v, ok := getString(cMap, "client_secret"); ok { + cfg.Channels.DingTalk.ClientSecret = v + } + case "maixcam": + cfg.Channels.MaixCam.Enabled = enabled + cfg.Channels.MaixCam.AllowFrom = allowFrom + if v, ok := getString(cMap, "host"); ok { + cfg.Channels.MaixCam.Host = v + } + if v, ok := getFloat(cMap, "port"); ok { + cfg.Channels.MaixCam.Port = int(v) + } + } + } + } + + if gateway, ok := getMap(data, "gateway"); ok { + if v, ok := getString(gateway, "host"); ok { + cfg.Gateway.Host = v + } + if v, ok := getFloat(gateway, "port"); ok { + cfg.Gateway.Port = int(v) + } + } + + if tools, ok := getMap(data, "tools"); ok { + if web, ok := getMap(tools, "web"); ok { + if search, ok := getMap(web, "search"); ok { + if v, ok := getString(search, "api_key"); ok { + cfg.Tools.Web.Search.APIKey = v + } + if v, ok := getFloat(search, "max_results"); ok { + cfg.Tools.Web.Search.MaxResults = int(v) + } + } + } + } + + return cfg, warnings, nil +} + +func MergeConfig(existing, incoming *config.Config) *config.Config { + if existing.Providers.Anthropic.APIKey == "" { + existing.Providers.Anthropic = incoming.Providers.Anthropic + } + if existing.Providers.OpenAI.APIKey == "" { + existing.Providers.OpenAI = incoming.Providers.OpenAI + } + if existing.Providers.OpenRouter.APIKey == "" { + existing.Providers.OpenRouter = incoming.Providers.OpenRouter + } + if existing.Providers.Groq.APIKey == "" { + existing.Providers.Groq = incoming.Providers.Groq + } + if existing.Providers.Zhipu.APIKey == "" { + existing.Providers.Zhipu = incoming.Providers.Zhipu + } + if existing.Providers.VLLM.APIKey == "" && existing.Providers.VLLM.APIBase == "" { + existing.Providers.VLLM = incoming.Providers.VLLM + } + if existing.Providers.Gemini.APIKey == "" { + existing.Providers.Gemini = incoming.Providers.Gemini + } + + if !existing.Channels.Telegram.Enabled && incoming.Channels.Telegram.Enabled { + existing.Channels.Telegram = incoming.Channels.Telegram + } + if !existing.Channels.Discord.Enabled && incoming.Channels.Discord.Enabled { + existing.Channels.Discord = incoming.Channels.Discord + } + if !existing.Channels.WhatsApp.Enabled && incoming.Channels.WhatsApp.Enabled { + existing.Channels.WhatsApp = incoming.Channels.WhatsApp + } + if !existing.Channels.Feishu.Enabled && incoming.Channels.Feishu.Enabled { + existing.Channels.Feishu = incoming.Channels.Feishu + } + if !existing.Channels.QQ.Enabled && incoming.Channels.QQ.Enabled { + existing.Channels.QQ = incoming.Channels.QQ + } + if !existing.Channels.DingTalk.Enabled && incoming.Channels.DingTalk.Enabled { + existing.Channels.DingTalk = incoming.Channels.DingTalk + } + if !existing.Channels.MaixCam.Enabled && incoming.Channels.MaixCam.Enabled { + existing.Channels.MaixCam = incoming.Channels.MaixCam + } + + if existing.Tools.Web.Search.APIKey == "" { + existing.Tools.Web.Search = incoming.Tools.Web.Search + } + + return existing +} + +func camelToSnake(s string) string { + var result strings.Builder + for i, r := range s { + if unicode.IsUpper(r) { + if i > 0 { + prev := rune(s[i-1]) + if unicode.IsLower(prev) || unicode.IsDigit(prev) { + result.WriteRune('_') + } else if unicode.IsUpper(prev) && i+1 < len(s) && unicode.IsLower(rune(s[i+1])) { + result.WriteRune('_') + } + } + result.WriteRune(unicode.ToLower(r)) + } else { + result.WriteRune(r) + } + } + return result.String() +} + +func convertKeysToSnake(data interface{}) interface{} { + switch v := data.(type) { + case map[string]interface{}: + result := make(map[string]interface{}, len(v)) + for key, val := range v { + result[camelToSnake(key)] = convertKeysToSnake(val) + } + return result + case []interface{}: + result := make([]interface{}, len(v)) + for i, val := range v { + result[i] = convertKeysToSnake(val) + } + return result + default: + return data + } +} + +func rewriteWorkspacePath(path string) string { + path = strings.Replace(path, ".openclaw", ".picoclaw", 1) + return path +} + +func getMap(data map[string]interface{}, key string) (map[string]interface{}, bool) { + v, ok := data[key] + if !ok { + return nil, false + } + m, ok := v.(map[string]interface{}) + return m, ok +} + +func getString(data map[string]interface{}, key string) (string, bool) { + v, ok := data[key] + if !ok { + return "", false + } + s, ok := v.(string) + return s, ok +} + +func getFloat(data map[string]interface{}, key string) (float64, bool) { + v, ok := data[key] + if !ok { + return 0, false + } + f, ok := v.(float64) + return f, ok +} + +func getBool(data map[string]interface{}, key string) (bool, bool) { + v, ok := data[key] + if !ok { + return false, false + } + b, ok := v.(bool) + return b, ok +} + +func getStringSlice(data map[string]interface{}, key string) []string { + v, ok := data[key] + if !ok { + return []string{} + } + arr, ok := v.([]interface{}) + if !ok { + return []string{} + } + result := make([]string, 0, len(arr)) + for _, item := range arr { + if s, ok := item.(string); ok { + result = append(result, s) + } + } + return result +} diff --git a/pkg/migrate/migrate.go b/pkg/migrate/migrate.go new file mode 100644 index 0000000..921f821 --- /dev/null +++ b/pkg/migrate/migrate.go @@ -0,0 +1,394 @@ +package migrate + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/sipeed/picoclaw/pkg/config" +) + +type ActionType int + +const ( + ActionCopy ActionType = iota + ActionSkip + ActionBackup + ActionConvertConfig + ActionCreateDir + ActionMergeConfig +) + +type Options struct { + DryRun bool + ConfigOnly bool + WorkspaceOnly bool + Force bool + Refresh bool + OpenClawHome string + PicoClawHome string +} + +type Action struct { + Type ActionType + Source string + Destination string + Description string +} + +type Result struct { + FilesCopied int + FilesSkipped int + BackupsCreated int + ConfigMigrated bool + DirsCreated int + Warnings []string + Errors []error +} + +func Run(opts Options) (*Result, error) { + if opts.ConfigOnly && opts.WorkspaceOnly { + return nil, fmt.Errorf("--config-only and --workspace-only are mutually exclusive") + } + + if opts.Refresh { + opts.WorkspaceOnly = true + } + + openclawHome, err := resolveOpenClawHome(opts.OpenClawHome) + if err != nil { + return nil, err + } + + picoClawHome, err := resolvePicoClawHome(opts.PicoClawHome) + if err != nil { + return nil, err + } + + if _, err := os.Stat(openclawHome); os.IsNotExist(err) { + return nil, fmt.Errorf("OpenClaw installation not found at %s", openclawHome) + } + + actions, warnings, err := Plan(opts, openclawHome, picoClawHome) + if err != nil { + return nil, err + } + + fmt.Println("Migrating from OpenClaw to PicoClaw") + fmt.Printf(" Source: %s\n", openclawHome) + fmt.Printf(" Destination: %s\n", picoClawHome) + fmt.Println() + + if opts.DryRun { + PrintPlan(actions, warnings) + return &Result{Warnings: warnings}, nil + } + + if !opts.Force { + PrintPlan(actions, warnings) + if !Confirm() { + fmt.Println("Aborted.") + return &Result{Warnings: warnings}, nil + } + fmt.Println() + } + + result := Execute(actions, openclawHome, picoClawHome) + result.Warnings = warnings + return result, nil +} + +func Plan(opts Options, openclawHome, picoClawHome string) ([]Action, []string, error) { + var actions []Action + var warnings []string + + force := opts.Force || opts.Refresh + + if !opts.WorkspaceOnly { + configPath, err := findOpenClawConfig(openclawHome) + if err != nil { + if opts.ConfigOnly { + return nil, nil, err + } + warnings = append(warnings, fmt.Sprintf("Config migration skipped: %v", err)) + } else { + actions = append(actions, Action{ + Type: ActionConvertConfig, + Source: configPath, + Destination: filepath.Join(picoClawHome, "config.json"), + Description: "convert OpenClaw config to PicoClaw format", + }) + + data, err := LoadOpenClawConfig(configPath) + if err == nil { + _, configWarnings, _ := ConvertConfig(data) + warnings = append(warnings, configWarnings...) + } + } + } + + if !opts.ConfigOnly { + srcWorkspace := resolveWorkspace(openclawHome) + dstWorkspace := resolveWorkspace(picoClawHome) + + if _, err := os.Stat(srcWorkspace); err == nil { + wsActions, err := PlanWorkspaceMigration(srcWorkspace, dstWorkspace, force) + if err != nil { + return nil, nil, fmt.Errorf("planning workspace migration: %w", err) + } + actions = append(actions, wsActions...) + } else { + warnings = append(warnings, "OpenClaw workspace directory not found, skipping workspace migration") + } + } + + return actions, warnings, nil +} + +func Execute(actions []Action, openclawHome, picoClawHome string) *Result { + result := &Result{} + + for _, action := range actions { + switch action.Type { + case ActionConvertConfig: + if err := executeConfigMigration(action.Source, action.Destination, picoClawHome); err != nil { + result.Errors = append(result.Errors, fmt.Errorf("config migration: %w", err)) + fmt.Printf(" โœ— Config migration failed: %v\n", err) + } else { + result.ConfigMigrated = true + fmt.Printf(" โœ“ Converted config: %s\n", action.Destination) + } + case ActionCreateDir: + if err := os.MkdirAll(action.Destination, 0755); err != nil { + result.Errors = append(result.Errors, err) + } else { + result.DirsCreated++ + } + case ActionBackup: + bakPath := action.Destination + ".bak" + if err := copyFile(action.Destination, bakPath); err != nil { + result.Errors = append(result.Errors, fmt.Errorf("backup %s: %w", action.Destination, err)) + fmt.Printf(" โœ— Backup failed: %s\n", action.Destination) + continue + } + result.BackupsCreated++ + fmt.Printf(" โœ“ Backed up %s -> %s.bak\n", filepath.Base(action.Destination), filepath.Base(action.Destination)) + + if err := os.MkdirAll(filepath.Dir(action.Destination), 0755); err != nil { + result.Errors = append(result.Errors, err) + continue + } + if err := copyFile(action.Source, action.Destination); err != nil { + result.Errors = append(result.Errors, fmt.Errorf("copy %s: %w", action.Source, err)) + fmt.Printf(" โœ— Copy failed: %s\n", action.Source) + } else { + result.FilesCopied++ + fmt.Printf(" โœ“ Copied %s\n", relPath(action.Source, openclawHome)) + } + case ActionCopy: + if err := os.MkdirAll(filepath.Dir(action.Destination), 0755); err != nil { + result.Errors = append(result.Errors, err) + continue + } + if err := copyFile(action.Source, action.Destination); err != nil { + result.Errors = append(result.Errors, fmt.Errorf("copy %s: %w", action.Source, err)) + fmt.Printf(" โœ— Copy failed: %s\n", action.Source) + } else { + result.FilesCopied++ + fmt.Printf(" โœ“ Copied %s\n", relPath(action.Source, openclawHome)) + } + case ActionSkip: + result.FilesSkipped++ + } + } + + return result +} + +func executeConfigMigration(srcConfigPath, dstConfigPath, picoClawHome string) error { + data, err := LoadOpenClawConfig(srcConfigPath) + if err != nil { + return err + } + + incoming, _, err := ConvertConfig(data) + if err != nil { + return err + } + + if _, err := os.Stat(dstConfigPath); err == nil { + existing, err := config.LoadConfig(dstConfigPath) + if err != nil { + return fmt.Errorf("loading existing PicoClaw config: %w", err) + } + incoming = MergeConfig(existing, incoming) + } + + if err := os.MkdirAll(filepath.Dir(dstConfigPath), 0755); err != nil { + return err + } + return config.SaveConfig(dstConfigPath, incoming) +} + +func Confirm() bool { + fmt.Print("Proceed with migration? (y/n): ") + var response string + fmt.Scanln(&response) + return strings.ToLower(strings.TrimSpace(response)) == "y" +} + +func PrintPlan(actions []Action, warnings []string) { + fmt.Println("Planned actions:") + copies := 0 + skips := 0 + backups := 0 + configCount := 0 + + for _, action := range actions { + switch action.Type { + case ActionConvertConfig: + fmt.Printf(" [config] %s -> %s\n", action.Source, action.Destination) + configCount++ + case ActionCopy: + fmt.Printf(" [copy] %s\n", filepath.Base(action.Source)) + copies++ + case ActionBackup: + fmt.Printf(" [backup] %s (exists, will backup and overwrite)\n", filepath.Base(action.Destination)) + backups++ + copies++ + case ActionSkip: + if action.Description != "" { + fmt.Printf(" [skip] %s (%s)\n", filepath.Base(action.Source), action.Description) + } + skips++ + case ActionCreateDir: + fmt.Printf(" [mkdir] %s\n", action.Destination) + } + } + + if len(warnings) > 0 { + fmt.Println() + fmt.Println("Warnings:") + for _, w := range warnings { + fmt.Printf(" - %s\n", w) + } + } + + fmt.Println() + fmt.Printf("%d files to copy, %d configs to convert, %d backups needed, %d skipped\n", + copies, configCount, backups, skips) +} + +func PrintSummary(result *Result) { + fmt.Println() + parts := []string{} + if result.FilesCopied > 0 { + parts = append(parts, fmt.Sprintf("%d files copied", result.FilesCopied)) + } + if result.ConfigMigrated { + parts = append(parts, "1 config converted") + } + if result.BackupsCreated > 0 { + parts = append(parts, fmt.Sprintf("%d backups created", result.BackupsCreated)) + } + if result.FilesSkipped > 0 { + parts = append(parts, fmt.Sprintf("%d files skipped", result.FilesSkipped)) + } + + if len(parts) > 0 { + fmt.Printf("Migration complete! %s.\n", strings.Join(parts, ", ")) + } else { + fmt.Println("Migration complete! No actions taken.") + } + + if len(result.Errors) > 0 { + fmt.Println() + fmt.Printf("%d errors occurred:\n", len(result.Errors)) + for _, e := range result.Errors { + fmt.Printf(" - %v\n", e) + } + } +} + +func resolveOpenClawHome(override string) (string, error) { + if override != "" { + return expandHome(override), nil + } + if envHome := os.Getenv("OPENCLAW_HOME"); envHome != "" { + return expandHome(envHome), nil + } + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("resolving home directory: %w", err) + } + return filepath.Join(home, ".openclaw"), nil +} + +func resolvePicoClawHome(override string) (string, error) { + if override != "" { + return expandHome(override), nil + } + if envHome := os.Getenv("PICOCLAW_HOME"); envHome != "" { + return expandHome(envHome), nil + } + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("resolving home directory: %w", err) + } + return filepath.Join(home, ".picoclaw"), nil +} + +func resolveWorkspace(homeDir string) string { + return filepath.Join(homeDir, "workspace") +} + +func expandHome(path string) string { + if path == "" { + return path + } + if path[0] == '~' { + home, _ := os.UserHomeDir() + if len(path) > 1 && path[1] == '/' { + return home + path[1:] + } + return home + } + return path +} + +func backupFile(path string) error { + bakPath := path + ".bak" + return copyFile(path, bakPath) +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + info, err := srcFile.Stat() + if err != nil { + return err + } + + dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + return err +} + +func relPath(path, base string) string { + rel, err := filepath.Rel(base, path) + if err != nil { + return filepath.Base(path) + } + return rel +} diff --git a/pkg/migrate/migrate_test.go b/pkg/migrate/migrate_test.go new file mode 100644 index 0000000..d93ea28 --- /dev/null +++ b/pkg/migrate/migrate_test.go @@ -0,0 +1,854 @@ +package migrate + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/sipeed/picoclaw/pkg/config" +) + +func TestCamelToSnake(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + {"simple", "apiKey", "api_key"}, + {"two words", "apiBase", "api_base"}, + {"three words", "maxToolIterations", "max_tool_iterations"}, + {"already snake", "api_key", "api_key"}, + {"single word", "enabled", "enabled"}, + {"all lower", "model", "model"}, + {"consecutive caps", "apiURL", "api_url"}, + {"starts upper", "Model", "model"}, + {"bridge url", "bridgeUrl", "bridge_url"}, + {"client id", "clientId", "client_id"}, + {"app secret", "appSecret", "app_secret"}, + {"verification token", "verificationToken", "verification_token"}, + {"allow from", "allowFrom", "allow_from"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := camelToSnake(tt.input) + if got != tt.want { + t.Errorf("camelToSnake(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + +func TestConvertKeysToSnake(t *testing.T) { + input := map[string]interface{}{ + "apiKey": "test-key", + "apiBase": "https://example.com", + "nested": map[string]interface{}{ + "maxTokens": float64(8192), + "allowFrom": []interface{}{"user1", "user2"}, + "deeperLevel": map[string]interface{}{ + "clientId": "abc", + }, + }, + } + + result := convertKeysToSnake(input) + m, ok := result.(map[string]interface{}) + if !ok { + t.Fatal("expected map[string]interface{}") + } + + if _, ok := m["api_key"]; !ok { + t.Error("expected key 'api_key' after conversion") + } + if _, ok := m["api_base"]; !ok { + t.Error("expected key 'api_base' after conversion") + } + + nested, ok := m["nested"].(map[string]interface{}) + if !ok { + t.Fatal("expected nested map") + } + if _, ok := nested["max_tokens"]; !ok { + t.Error("expected key 'max_tokens' in nested map") + } + if _, ok := nested["allow_from"]; !ok { + t.Error("expected key 'allow_from' in nested map") + } + + deeper, ok := nested["deeper_level"].(map[string]interface{}) + if !ok { + t.Fatal("expected deeper_level map") + } + if _, ok := deeper["client_id"]; !ok { + t.Error("expected key 'client_id' in deeper level") + } +} + +func TestLoadOpenClawConfig(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "openclaw.json") + + openclawConfig := map[string]interface{}{ + "providers": map[string]interface{}{ + "anthropic": map[string]interface{}{ + "apiKey": "sk-ant-test123", + "apiBase": "https://api.anthropic.com", + }, + }, + "agents": map[string]interface{}{ + "defaults": map[string]interface{}{ + "maxTokens": float64(4096), + "model": "claude-3-opus", + }, + }, + } + + data, err := json.Marshal(openclawConfig) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(configPath, data, 0644); err != nil { + t.Fatal(err) + } + + result, err := LoadOpenClawConfig(configPath) + if err != nil { + t.Fatalf("LoadOpenClawConfig: %v", err) + } + + providers, ok := result["providers"].(map[string]interface{}) + if !ok { + t.Fatal("expected providers map") + } + anthropic, ok := providers["anthropic"].(map[string]interface{}) + if !ok { + t.Fatal("expected anthropic map") + } + if anthropic["api_key"] != "sk-ant-test123" { + t.Errorf("api_key = %v, want sk-ant-test123", anthropic["api_key"]) + } + + agents, ok := result["agents"].(map[string]interface{}) + if !ok { + t.Fatal("expected agents map") + } + defaults, ok := agents["defaults"].(map[string]interface{}) + if !ok { + t.Fatal("expected defaults map") + } + if defaults["max_tokens"] != float64(4096) { + t.Errorf("max_tokens = %v, want 4096", defaults["max_tokens"]) + } +} + +func TestConvertConfig(t *testing.T) { + t.Run("providers mapping", func(t *testing.T) { + data := map[string]interface{}{ + "providers": map[string]interface{}{ + "anthropic": map[string]interface{}{ + "api_key": "sk-ant-test", + "api_base": "https://api.anthropic.com", + }, + "openrouter": map[string]interface{}{ + "api_key": "sk-or-test", + }, + "groq": map[string]interface{}{ + "api_key": "gsk-test", + }, + }, + } + + cfg, warnings, err := ConvertConfig(data) + if err != nil { + t.Fatalf("ConvertConfig: %v", err) + } + if len(warnings) != 0 { + t.Errorf("expected no warnings, got %v", warnings) + } + if cfg.Providers.Anthropic.APIKey != "sk-ant-test" { + t.Errorf("Anthropic.APIKey = %q, want %q", cfg.Providers.Anthropic.APIKey, "sk-ant-test") + } + if cfg.Providers.OpenRouter.APIKey != "sk-or-test" { + t.Errorf("OpenRouter.APIKey = %q, want %q", cfg.Providers.OpenRouter.APIKey, "sk-or-test") + } + if cfg.Providers.Groq.APIKey != "gsk-test" { + t.Errorf("Groq.APIKey = %q, want %q", cfg.Providers.Groq.APIKey, "gsk-test") + } + }) + + t.Run("unsupported provider warning", func(t *testing.T) { + data := map[string]interface{}{ + "providers": map[string]interface{}{ + "deepseek": map[string]interface{}{ + "api_key": "sk-deep-test", + }, + }, + } + + _, warnings, err := ConvertConfig(data) + if err != nil { + t.Fatalf("ConvertConfig: %v", err) + } + if len(warnings) != 1 { + t.Fatalf("expected 1 warning, got %d", len(warnings)) + } + if warnings[0] != "Provider 'deepseek' not supported in PicoClaw, skipping" { + t.Errorf("unexpected warning: %s", warnings[0]) + } + }) + + t.Run("channels mapping", func(t *testing.T) { + data := map[string]interface{}{ + "channels": map[string]interface{}{ + "telegram": map[string]interface{}{ + "enabled": true, + "token": "tg-token-123", + "allow_from": []interface{}{"user1"}, + }, + "discord": map[string]interface{}{ + "enabled": true, + "token": "disc-token-456", + }, + }, + } + + cfg, _, err := ConvertConfig(data) + if err != nil { + t.Fatalf("ConvertConfig: %v", err) + } + if !cfg.Channels.Telegram.Enabled { + t.Error("Telegram should be enabled") + } + if cfg.Channels.Telegram.Token != "tg-token-123" { + t.Errorf("Telegram.Token = %q, want %q", cfg.Channels.Telegram.Token, "tg-token-123") + } + if len(cfg.Channels.Telegram.AllowFrom) != 1 || cfg.Channels.Telegram.AllowFrom[0] != "user1" { + t.Errorf("Telegram.AllowFrom = %v, want [user1]", cfg.Channels.Telegram.AllowFrom) + } + if !cfg.Channels.Discord.Enabled { + t.Error("Discord should be enabled") + } + }) + + t.Run("unsupported channel warning", func(t *testing.T) { + data := map[string]interface{}{ + "channels": map[string]interface{}{ + "email": map[string]interface{}{ + "enabled": true, + }, + }, + } + + _, warnings, err := ConvertConfig(data) + if err != nil { + t.Fatalf("ConvertConfig: %v", err) + } + if len(warnings) != 1 { + t.Fatalf("expected 1 warning, got %d", len(warnings)) + } + if warnings[0] != "Channel 'email' not supported in PicoClaw, skipping" { + t.Errorf("unexpected warning: %s", warnings[0]) + } + }) + + t.Run("agent defaults", func(t *testing.T) { + data := map[string]interface{}{ + "agents": map[string]interface{}{ + "defaults": map[string]interface{}{ + "model": "claude-3-opus", + "max_tokens": float64(4096), + "temperature": 0.5, + "max_tool_iterations": float64(10), + "workspace": "~/.openclaw/workspace", + }, + }, + } + + cfg, _, err := ConvertConfig(data) + if err != nil { + t.Fatalf("ConvertConfig: %v", err) + } + if cfg.Agents.Defaults.Model != "claude-3-opus" { + t.Errorf("Model = %q, want %q", cfg.Agents.Defaults.Model, "claude-3-opus") + } + if cfg.Agents.Defaults.MaxTokens != 4096 { + t.Errorf("MaxTokens = %d, want %d", cfg.Agents.Defaults.MaxTokens, 4096) + } + if cfg.Agents.Defaults.Temperature != 0.5 { + t.Errorf("Temperature = %f, want %f", cfg.Agents.Defaults.Temperature, 0.5) + } + if cfg.Agents.Defaults.Workspace != "~/.picoclaw/workspace" { + t.Errorf("Workspace = %q, want %q", cfg.Agents.Defaults.Workspace, "~/.picoclaw/workspace") + } + }) + + t.Run("empty config", func(t *testing.T) { + data := map[string]interface{}{} + + cfg, warnings, err := ConvertConfig(data) + if err != nil { + t.Fatalf("ConvertConfig: %v", err) + } + if len(warnings) != 0 { + t.Errorf("expected no warnings, got %v", warnings) + } + if cfg.Agents.Defaults.Model != "glm-4.7" { + t.Errorf("default model should be glm-4.7, got %q", cfg.Agents.Defaults.Model) + } + }) +} + +func TestMergeConfig(t *testing.T) { + t.Run("fills empty fields", func(t *testing.T) { + existing := config.DefaultConfig() + incoming := config.DefaultConfig() + incoming.Providers.Anthropic.APIKey = "sk-ant-incoming" + incoming.Providers.OpenRouter.APIKey = "sk-or-incoming" + + result := MergeConfig(existing, incoming) + if result.Providers.Anthropic.APIKey != "sk-ant-incoming" { + t.Errorf("Anthropic.APIKey = %q, want %q", result.Providers.Anthropic.APIKey, "sk-ant-incoming") + } + if result.Providers.OpenRouter.APIKey != "sk-or-incoming" { + t.Errorf("OpenRouter.APIKey = %q, want %q", result.Providers.OpenRouter.APIKey, "sk-or-incoming") + } + }) + + t.Run("preserves existing non-empty fields", func(t *testing.T) { + existing := config.DefaultConfig() + existing.Providers.Anthropic.APIKey = "sk-ant-existing" + + incoming := config.DefaultConfig() + incoming.Providers.Anthropic.APIKey = "sk-ant-incoming" + incoming.Providers.OpenAI.APIKey = "sk-oai-incoming" + + result := MergeConfig(existing, incoming) + if result.Providers.Anthropic.APIKey != "sk-ant-existing" { + t.Errorf("Anthropic.APIKey should be preserved, got %q", result.Providers.Anthropic.APIKey) + } + if result.Providers.OpenAI.APIKey != "sk-oai-incoming" { + t.Errorf("OpenAI.APIKey should be filled, got %q", result.Providers.OpenAI.APIKey) + } + }) + + t.Run("merges enabled channels", func(t *testing.T) { + existing := config.DefaultConfig() + incoming := config.DefaultConfig() + incoming.Channels.Telegram.Enabled = true + incoming.Channels.Telegram.Token = "tg-token" + + result := MergeConfig(existing, incoming) + if !result.Channels.Telegram.Enabled { + t.Error("Telegram should be enabled after merge") + } + if result.Channels.Telegram.Token != "tg-token" { + t.Errorf("Telegram.Token = %q, want %q", result.Channels.Telegram.Token, "tg-token") + } + }) + + t.Run("preserves existing enabled channels", func(t *testing.T) { + existing := config.DefaultConfig() + existing.Channels.Telegram.Enabled = true + existing.Channels.Telegram.Token = "existing-token" + + incoming := config.DefaultConfig() + incoming.Channels.Telegram.Enabled = true + incoming.Channels.Telegram.Token = "incoming-token" + + result := MergeConfig(existing, incoming) + if result.Channels.Telegram.Token != "existing-token" { + t.Errorf("Telegram.Token should be preserved, got %q", result.Channels.Telegram.Token) + } + }) +} + +func TestPlanWorkspaceMigration(t *testing.T) { + t.Run("copies available files", func(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + os.WriteFile(filepath.Join(srcDir, "AGENTS.md"), []byte("# Agents"), 0644) + os.WriteFile(filepath.Join(srcDir, "SOUL.md"), []byte("# Soul"), 0644) + os.WriteFile(filepath.Join(srcDir, "USER.md"), []byte("# User"), 0644) + + actions, err := PlanWorkspaceMigration(srcDir, dstDir, false) + if err != nil { + t.Fatalf("PlanWorkspaceMigration: %v", err) + } + + copyCount := 0 + skipCount := 0 + for _, a := range actions { + if a.Type == ActionCopy { + copyCount++ + } + if a.Type == ActionSkip { + skipCount++ + } + } + if copyCount != 3 { + t.Errorf("expected 3 copies, got %d", copyCount) + } + if skipCount != 2 { + t.Errorf("expected 2 skips (TOOLS.md, HEARTBEAT.md), got %d", skipCount) + } + }) + + t.Run("plans backup for existing destination files", func(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + os.WriteFile(filepath.Join(srcDir, "AGENTS.md"), []byte("# Agents from OpenClaw"), 0644) + os.WriteFile(filepath.Join(dstDir, "AGENTS.md"), []byte("# Existing Agents"), 0644) + + actions, err := PlanWorkspaceMigration(srcDir, dstDir, false) + if err != nil { + t.Fatalf("PlanWorkspaceMigration: %v", err) + } + + backupCount := 0 + for _, a := range actions { + if a.Type == ActionBackup && filepath.Base(a.Destination) == "AGENTS.md" { + backupCount++ + } + } + if backupCount != 1 { + t.Errorf("expected 1 backup action for AGENTS.md, got %d", backupCount) + } + }) + + t.Run("force skips backup", func(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + os.WriteFile(filepath.Join(srcDir, "AGENTS.md"), []byte("# Agents"), 0644) + os.WriteFile(filepath.Join(dstDir, "AGENTS.md"), []byte("# Existing"), 0644) + + actions, err := PlanWorkspaceMigration(srcDir, dstDir, true) + if err != nil { + t.Fatalf("PlanWorkspaceMigration: %v", err) + } + + for _, a := range actions { + if a.Type == ActionBackup { + t.Error("expected no backup actions with force=true") + } + } + }) + + t.Run("handles memory directory", func(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + memDir := filepath.Join(srcDir, "memory") + os.MkdirAll(memDir, 0755) + os.WriteFile(filepath.Join(memDir, "MEMORY.md"), []byte("# Memory"), 0644) + + actions, err := PlanWorkspaceMigration(srcDir, dstDir, false) + if err != nil { + t.Fatalf("PlanWorkspaceMigration: %v", err) + } + + hasCopy := false + hasDir := false + for _, a := range actions { + if a.Type == ActionCopy && filepath.Base(a.Source) == "MEMORY.md" { + hasCopy = true + } + if a.Type == ActionCreateDir { + hasDir = true + } + } + if !hasCopy { + t.Error("expected copy action for memory/MEMORY.md") + } + if !hasDir { + t.Error("expected create dir action for memory/") + } + }) + + t.Run("handles skills directory", func(t *testing.T) { + srcDir := t.TempDir() + dstDir := t.TempDir() + + skillDir := filepath.Join(srcDir, "skills", "weather") + os.MkdirAll(skillDir, 0755) + os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# Weather"), 0644) + + actions, err := PlanWorkspaceMigration(srcDir, dstDir, false) + if err != nil { + t.Fatalf("PlanWorkspaceMigration: %v", err) + } + + hasCopy := false + for _, a := range actions { + if a.Type == ActionCopy && filepath.Base(a.Source) == "SKILL.md" { + hasCopy = true + } + } + if !hasCopy { + t.Error("expected copy action for skills/weather/SKILL.md") + } + }) +} + +func TestFindOpenClawConfig(t *testing.T) { + t.Run("finds openclaw.json", func(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "openclaw.json") + os.WriteFile(configPath, []byte("{}"), 0644) + + found, err := findOpenClawConfig(tmpDir) + if err != nil { + t.Fatalf("findOpenClawConfig: %v", err) + } + if found != configPath { + t.Errorf("found %q, want %q", found, configPath) + } + }) + + t.Run("falls back to config.json", func(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.json") + os.WriteFile(configPath, []byte("{}"), 0644) + + found, err := findOpenClawConfig(tmpDir) + if err != nil { + t.Fatalf("findOpenClawConfig: %v", err) + } + if found != configPath { + t.Errorf("found %q, want %q", found, configPath) + } + }) + + t.Run("prefers openclaw.json over config.json", func(t *testing.T) { + tmpDir := t.TempDir() + openclawPath := filepath.Join(tmpDir, "openclaw.json") + os.WriteFile(openclawPath, []byte("{}"), 0644) + os.WriteFile(filepath.Join(tmpDir, "config.json"), []byte("{}"), 0644) + + found, err := findOpenClawConfig(tmpDir) + if err != nil { + t.Fatalf("findOpenClawConfig: %v", err) + } + if found != openclawPath { + t.Errorf("should prefer openclaw.json, got %q", found) + } + }) + + t.Run("error when no config found", func(t *testing.T) { + tmpDir := t.TempDir() + + _, err := findOpenClawConfig(tmpDir) + if err == nil { + t.Fatal("expected error when no config found") + } + }) +} + +func TestRewriteWorkspacePath(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + {"default path", "~/.openclaw/workspace", "~/.picoclaw/workspace"}, + {"custom path", "/custom/path", "/custom/path"}, + {"empty", "", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := rewriteWorkspacePath(tt.input) + if got != tt.want { + t.Errorf("rewriteWorkspacePath(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + +func TestRunDryRun(t *testing.T) { + openclawHome := t.TempDir() + picoClawHome := t.TempDir() + + wsDir := filepath.Join(openclawHome, "workspace") + os.MkdirAll(wsDir, 0755) + os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644) + os.WriteFile(filepath.Join(wsDir, "AGENTS.md"), []byte("# Agents"), 0644) + + configData := map[string]interface{}{ + "providers": map[string]interface{}{ + "anthropic": map[string]interface{}{ + "apiKey": "test-key", + }, + }, + } + data, _ := json.Marshal(configData) + os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644) + + opts := Options{ + DryRun: true, + OpenClawHome: openclawHome, + PicoClawHome: picoClawHome, + } + + result, err := Run(opts) + if err != nil { + t.Fatalf("Run: %v", err) + } + + picoWs := filepath.Join(picoClawHome, "workspace") + if _, err := os.Stat(filepath.Join(picoWs, "SOUL.md")); !os.IsNotExist(err) { + t.Error("dry run should not create files") + } + if _, err := os.Stat(filepath.Join(picoClawHome, "config.json")); !os.IsNotExist(err) { + t.Error("dry run should not create config") + } + + _ = result +} + +func TestRunFullMigration(t *testing.T) { + openclawHome := t.TempDir() + picoClawHome := t.TempDir() + + wsDir := filepath.Join(openclawHome, "workspace") + os.MkdirAll(wsDir, 0755) + os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul from OpenClaw"), 0644) + os.WriteFile(filepath.Join(wsDir, "AGENTS.md"), []byte("# Agents from OpenClaw"), 0644) + os.WriteFile(filepath.Join(wsDir, "USER.md"), []byte("# User from OpenClaw"), 0644) + + memDir := filepath.Join(wsDir, "memory") + os.MkdirAll(memDir, 0755) + os.WriteFile(filepath.Join(memDir, "MEMORY.md"), []byte("# Memory notes"), 0644) + + configData := map[string]interface{}{ + "providers": map[string]interface{}{ + "anthropic": map[string]interface{}{ + "apiKey": "sk-ant-migrate-test", + }, + "openrouter": map[string]interface{}{ + "apiKey": "sk-or-migrate-test", + }, + }, + "channels": map[string]interface{}{ + "telegram": map[string]interface{}{ + "enabled": true, + "token": "tg-migrate-test", + }, + }, + } + data, _ := json.Marshal(configData) + os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644) + + opts := Options{ + Force: true, + OpenClawHome: openclawHome, + PicoClawHome: picoClawHome, + } + + result, err := Run(opts) + if err != nil { + t.Fatalf("Run: %v", err) + } + + picoWs := filepath.Join(picoClawHome, "workspace") + + soulData, err := os.ReadFile(filepath.Join(picoWs, "SOUL.md")) + if err != nil { + t.Fatalf("reading SOUL.md: %v", err) + } + if string(soulData) != "# Soul from OpenClaw" { + t.Errorf("SOUL.md content = %q, want %q", string(soulData), "# Soul from OpenClaw") + } + + agentsData, err := os.ReadFile(filepath.Join(picoWs, "AGENTS.md")) + if err != nil { + t.Fatalf("reading AGENTS.md: %v", err) + } + if string(agentsData) != "# Agents from OpenClaw" { + t.Errorf("AGENTS.md content = %q", string(agentsData)) + } + + memData, err := os.ReadFile(filepath.Join(picoWs, "memory", "MEMORY.md")) + if err != nil { + t.Fatalf("reading memory/MEMORY.md: %v", err) + } + if string(memData) != "# Memory notes" { + t.Errorf("MEMORY.md content = %q", string(memData)) + } + + picoConfig, err := config.LoadConfig(filepath.Join(picoClawHome, "config.json")) + if err != nil { + t.Fatalf("loading PicoClaw config: %v", err) + } + if picoConfig.Providers.Anthropic.APIKey != "sk-ant-migrate-test" { + t.Errorf("Anthropic.APIKey = %q, want %q", picoConfig.Providers.Anthropic.APIKey, "sk-ant-migrate-test") + } + if picoConfig.Providers.OpenRouter.APIKey != "sk-or-migrate-test" { + t.Errorf("OpenRouter.APIKey = %q, want %q", picoConfig.Providers.OpenRouter.APIKey, "sk-or-migrate-test") + } + if !picoConfig.Channels.Telegram.Enabled { + t.Error("Telegram should be enabled") + } + if picoConfig.Channels.Telegram.Token != "tg-migrate-test" { + t.Errorf("Telegram.Token = %q, want %q", picoConfig.Channels.Telegram.Token, "tg-migrate-test") + } + + if result.FilesCopied < 3 { + t.Errorf("expected at least 3 files copied, got %d", result.FilesCopied) + } + if !result.ConfigMigrated { + t.Error("config should have been migrated") + } + if len(result.Errors) > 0 { + t.Errorf("expected no errors, got %v", result.Errors) + } +} + +func TestRunOpenClawNotFound(t *testing.T) { + opts := Options{ + OpenClawHome: "/nonexistent/path/to/openclaw", + PicoClawHome: t.TempDir(), + } + + _, err := Run(opts) + if err == nil { + t.Fatal("expected error when OpenClaw not found") + } +} + +func TestRunMutuallyExclusiveFlags(t *testing.T) { + opts := Options{ + ConfigOnly: true, + WorkspaceOnly: true, + } + + _, err := Run(opts) + if err == nil { + t.Fatal("expected error for mutually exclusive flags") + } +} + +func TestBackupFile(t *testing.T) { + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test.md") + os.WriteFile(filePath, []byte("original content"), 0644) + + if err := backupFile(filePath); err != nil { + t.Fatalf("backupFile: %v", err) + } + + bakPath := filePath + ".bak" + bakData, err := os.ReadFile(bakPath) + if err != nil { + t.Fatalf("reading backup: %v", err) + } + if string(bakData) != "original content" { + t.Errorf("backup content = %q, want %q", string(bakData), "original content") + } +} + +func TestCopyFile(t *testing.T) { + tmpDir := t.TempDir() + srcPath := filepath.Join(tmpDir, "src.md") + dstPath := filepath.Join(tmpDir, "dst.md") + + os.WriteFile(srcPath, []byte("file content"), 0644) + + if err := copyFile(srcPath, dstPath); err != nil { + t.Fatalf("copyFile: %v", err) + } + + data, err := os.ReadFile(dstPath) + if err != nil { + t.Fatalf("reading copy: %v", err) + } + if string(data) != "file content" { + t.Errorf("copy content = %q, want %q", string(data), "file content") + } +} + +func TestRunConfigOnly(t *testing.T) { + openclawHome := t.TempDir() + picoClawHome := t.TempDir() + + wsDir := filepath.Join(openclawHome, "workspace") + os.MkdirAll(wsDir, 0755) + os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644) + + configData := map[string]interface{}{ + "providers": map[string]interface{}{ + "anthropic": map[string]interface{}{ + "apiKey": "sk-config-only", + }, + }, + } + data, _ := json.Marshal(configData) + os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644) + + opts := Options{ + Force: true, + ConfigOnly: true, + OpenClawHome: openclawHome, + PicoClawHome: picoClawHome, + } + + result, err := Run(opts) + if err != nil { + t.Fatalf("Run: %v", err) + } + + if !result.ConfigMigrated { + t.Error("config should have been migrated") + } + + picoWs := filepath.Join(picoClawHome, "workspace") + if _, err := os.Stat(filepath.Join(picoWs, "SOUL.md")); !os.IsNotExist(err) { + t.Error("config-only should not copy workspace files") + } +} + +func TestRunWorkspaceOnly(t *testing.T) { + openclawHome := t.TempDir() + picoClawHome := t.TempDir() + + wsDir := filepath.Join(openclawHome, "workspace") + os.MkdirAll(wsDir, 0755) + os.WriteFile(filepath.Join(wsDir, "SOUL.md"), []byte("# Soul"), 0644) + + configData := map[string]interface{}{ + "providers": map[string]interface{}{ + "anthropic": map[string]interface{}{ + "apiKey": "sk-ws-only", + }, + }, + } + data, _ := json.Marshal(configData) + os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644) + + opts := Options{ + Force: true, + WorkspaceOnly: true, + OpenClawHome: openclawHome, + PicoClawHome: picoClawHome, + } + + result, err := Run(opts) + if err != nil { + t.Fatalf("Run: %v", err) + } + + if result.ConfigMigrated { + t.Error("workspace-only should not migrate config") + } + + picoWs := filepath.Join(picoClawHome, "workspace") + soulData, err := os.ReadFile(filepath.Join(picoWs, "SOUL.md")) + if err != nil { + t.Fatalf("reading SOUL.md: %v", err) + } + if string(soulData) != "# Soul" { + t.Errorf("SOUL.md content = %q", string(soulData)) + } +} diff --git a/pkg/migrate/workspace.go b/pkg/migrate/workspace.go new file mode 100644 index 0000000..f45748f --- /dev/null +++ b/pkg/migrate/workspace.go @@ -0,0 +1,106 @@ +package migrate + +import ( + "os" + "path/filepath" +) + +var migrateableFiles = []string{ + "AGENTS.md", + "SOUL.md", + "USER.md", + "TOOLS.md", + "HEARTBEAT.md", +} + +var migrateableDirs = []string{ + "memory", + "skills", +} + +func PlanWorkspaceMigration(srcWorkspace, dstWorkspace string, force bool) ([]Action, error) { + var actions []Action + + for _, filename := range migrateableFiles { + src := filepath.Join(srcWorkspace, filename) + dst := filepath.Join(dstWorkspace, filename) + action := planFileCopy(src, dst, force) + if action.Type != ActionSkip || action.Description != "" { + actions = append(actions, action) + } + } + + for _, dirname := range migrateableDirs { + srcDir := filepath.Join(srcWorkspace, dirname) + if _, err := os.Stat(srcDir); os.IsNotExist(err) { + continue + } + dirActions, err := planDirCopy(srcDir, filepath.Join(dstWorkspace, dirname), force) + if err != nil { + return nil, err + } + actions = append(actions, dirActions...) + } + + return actions, nil +} + +func planFileCopy(src, dst string, force bool) Action { + if _, err := os.Stat(src); os.IsNotExist(err) { + return Action{ + Type: ActionSkip, + Source: src, + Destination: dst, + Description: "source file not found", + } + } + + _, dstExists := os.Stat(dst) + if dstExists == nil && !force { + return Action{ + Type: ActionBackup, + Source: src, + Destination: dst, + Description: "destination exists, will backup and overwrite", + } + } + + return Action{ + Type: ActionCopy, + Source: src, + Destination: dst, + Description: "copy file", + } +} + +func planDirCopy(srcDir, dstDir string, force bool) ([]Action, error) { + var actions []Action + + err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(srcDir, path) + if err != nil { + return err + } + + dst := filepath.Join(dstDir, relPath) + + if info.IsDir() { + actions = append(actions, Action{ + Type: ActionCreateDir, + Destination: dst, + Description: "create directory", + }) + return nil + } + + action := planFileCopy(path, dst, force) + actions = append(actions, action) + return nil + }) + + return actions, err +} From 5eec80c6543b43345fcb512b3865f59e3ddc199a Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Wed, 11 Feb 2026 12:48:32 -0600 Subject: [PATCH 14/23] feat(channels): add Slack channel integration with Socket Mode Add Slack as a messaging channel using Socket Mode (WebSocket), bringing the total supported channels to 8. Features include bidirectional messaging, thread support with per-thread session context, @mention handling, ack reactions (:eyes:/:white_check_mark:), slash commands, file/attachment support with Groq Whisper audio transcription, and allowlist filtering by Slack user ID. Closes #31 Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/main.go | 6 + config.example.json | 6 + go.mod | 1 + go.sum | 8 +- pkg/channels/manager.go | 13 ++ pkg/channels/slack.go | 446 +++++++++++++++++++++++++++++++++++++ pkg/channels/slack_test.go | 193 ++++++++++++++++ pkg/config/config.go | 14 ++ 8 files changed, 685 insertions(+), 2 deletions(-) create mode 100644 pkg/channels/slack.go create mode 100644 pkg/channels/slack_test.go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index c14ec58..877b636 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -586,6 +586,12 @@ func gatewayCmd() { logger.InfoC("voice", "Groq transcription attached to Discord channel") } } + if slackChannel, ok := channelManager.GetChannel("slack"); ok { + if sc, ok := slackChannel.(*channels.SlackChannel); ok { + sc.SetTranscriber(transcriber) + logger.InfoC("voice", "Groq transcription attached to Slack channel") + } + } } enabledChannels := channelManager.GetEnabledChannels() diff --git a/config.example.json b/config.example.json index bc5c2bb..12dc473 100644 --- a/config.example.json +++ b/config.example.json @@ -43,6 +43,12 @@ "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "allow_from": [] + }, + "slack": { + "enabled": false, + "bot_token": "xoxb-YOUR-BOT-TOKEN", + "app_token": "xapp-YOUR-APP-TOKEN", + "allow_from": [] } }, "providers": { diff --git a/go.mod b/go.mod index 832f1e8..0411377 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/larksuite/oapi-sdk-go/v3 v3.5.3 github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 + github.com/slack-go/slack v0.17.3 github.com/tencent-connect/botgo v0.2.1 golang.org/x/oauth2 v0.35.0 ) diff --git a/go.sum b/go.sum index f1ce926..311caaa 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2m github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -77,6 +79,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g= +github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -84,8 +88,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tencent-connect/botgo v0.2.1 h1:+BrTt9Zh+awL28GWC4g5Na3nQaGRWb0N5IctS8WqBCk= github.com/tencent-connect/botgo v0.2.1/go.mod h1:oO1sG9ybhXNickvt+CVym5khwQ+uKhTR+IhTqEfOVsI= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= diff --git a/pkg/channels/manager.go b/pkg/channels/manager.go index bf98a4b..b0e1416 100644 --- a/pkg/channels/manager.go +++ b/pkg/channels/manager.go @@ -136,6 +136,19 @@ func (m *Manager) initChannels() error { } } + if m.config.Channels.Slack.Enabled && m.config.Channels.Slack.BotToken != "" { + logger.DebugC("channels", "Attempting to initialize Slack channel") + slackCh, err := NewSlackChannel(m.config.Channels.Slack, m.bus) + if err != nil { + logger.ErrorCF("channels", "Failed to initialize Slack channel", map[string]interface{}{ + "error": err.Error(), + }) + } else { + m.channels["slack"] = slackCh + logger.InfoC("channels", "Slack channel enabled successfully") + } + } + logger.InfoCF("channels", "Channel initialization completed", map[string]interface{}{ "enabled_channels": len(m.channels), }) diff --git a/pkg/channels/slack.go b/pkg/channels/slack.go new file mode 100644 index 0000000..9595453 --- /dev/null +++ b/pkg/channels/slack.go @@ -0,0 +1,446 @@ +package channels + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/slack-go/slack" + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" + + "github.com/sipeed/picoclaw/pkg/bus" + "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/voice" +) + +type SlackChannel struct { + *BaseChannel + config config.SlackConfig + api *slack.Client + socketClient *socketmode.Client + botUserID string + transcriber *voice.GroqTranscriber + ctx context.Context + cancel context.CancelFunc + pendingAcks sync.Map +} + +type slackMessageRef struct { + ChannelID string + Timestamp string +} + +func NewSlackChannel(cfg config.SlackConfig, messageBus *bus.MessageBus) (*SlackChannel, error) { + if cfg.BotToken == "" || cfg.AppToken == "" { + return nil, fmt.Errorf("slack bot_token and app_token are required") + } + + api := slack.New( + cfg.BotToken, + slack.OptionAppLevelToken(cfg.AppToken), + ) + + socketClient := socketmode.New(api) + + base := NewBaseChannel("slack", cfg, messageBus, cfg.AllowFrom) + + return &SlackChannel{ + BaseChannel: base, + config: cfg, + api: api, + socketClient: socketClient, + }, nil +} + +func (c *SlackChannel) SetTranscriber(transcriber *voice.GroqTranscriber) { + c.transcriber = transcriber +} + +func (c *SlackChannel) Start(ctx context.Context) error { + logger.InfoC("slack", "Starting Slack channel (Socket Mode)") + + c.ctx, c.cancel = context.WithCancel(ctx) + + authResp, err := c.api.AuthTest() + if err != nil { + return fmt.Errorf("slack auth test failed: %w", err) + } + c.botUserID = authResp.UserID + + logger.InfoCF("slack", "Slack bot connected", map[string]interface{}{ + "bot_user_id": c.botUserID, + "team": authResp.Team, + }) + + go c.eventLoop() + + go func() { + if err := c.socketClient.RunContext(c.ctx); err != nil { + if c.ctx.Err() == nil { + logger.ErrorCF("slack", "Socket Mode connection error", map[string]interface{}{ + "error": err.Error(), + }) + } + } + }() + + c.setRunning(true) + logger.InfoC("slack", "Slack channel started (Socket Mode)") + return nil +} + +func (c *SlackChannel) Stop(ctx context.Context) error { + logger.InfoC("slack", "Stopping Slack channel") + + if c.cancel != nil { + c.cancel() + } + + c.setRunning(false) + logger.InfoC("slack", "Slack channel stopped") + return nil +} + +func (c *SlackChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { + if !c.IsRunning() { + return fmt.Errorf("slack channel not running") + } + + channelID, threadTS := parseSlackChatID(msg.ChatID) + if channelID == "" { + return fmt.Errorf("invalid slack chat ID: %s", msg.ChatID) + } + + opts := []slack.MsgOption{ + slack.MsgOptionText(msg.Content, false), + } + + if threadTS != "" { + opts = append(opts, slack.MsgOptionTS(threadTS)) + } + + _, _, err := c.api.PostMessageContext(ctx, channelID, opts...) + if err != nil { + return fmt.Errorf("failed to send slack message: %w", err) + } + + if ref, ok := c.pendingAcks.LoadAndDelete(msg.ChatID); ok { + msgRef := ref.(slackMessageRef) + c.api.AddReaction("white_check_mark", slack.ItemRef{ + Channel: msgRef.ChannelID, + Timestamp: msgRef.Timestamp, + }) + } + + logger.DebugCF("slack", "Message sent", map[string]interface{}{ + "channel_id": channelID, + "thread_ts": threadTS, + }) + + return nil +} + +func (c *SlackChannel) eventLoop() { + for { + select { + case <-c.ctx.Done(): + return + case event, ok := <-c.socketClient.Events: + if !ok { + return + } + switch event.Type { + case socketmode.EventTypeEventsAPI: + c.handleEventsAPI(event) + case socketmode.EventTypeSlashCommand: + c.handleSlashCommand(event) + case socketmode.EventTypeInteractive: + if event.Request != nil { + c.socketClient.Ack(*event.Request) + } + } + } + } +} + +func (c *SlackChannel) handleEventsAPI(event socketmode.Event) { + if event.Request != nil { + c.socketClient.Ack(*event.Request) + } + + eventsAPIEvent, ok := event.Data.(slackevents.EventsAPIEvent) + if !ok { + return + } + + switch ev := eventsAPIEvent.InnerEvent.Data.(type) { + case *slackevents.MessageEvent: + c.handleMessageEvent(ev) + case *slackevents.AppMentionEvent: + c.handleAppMention(ev) + case *slackevents.ReactionAddedEvent: + c.handleReactionAdded(ev) + case *slackevents.ReactionRemovedEvent: + c.handleReactionRemoved(ev) + } +} + +func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { + if ev.User == c.botUserID || ev.User == "" { + return + } + if ev.BotID != "" { + return + } + if ev.SubType != "" && ev.SubType != "file_share" { + return + } + + senderID := ev.User + channelID := ev.Channel + threadTS := ev.ThreadTimeStamp + messageTS := ev.TimeStamp + + chatID := channelID + if threadTS != "" { + chatID = channelID + "/" + threadTS + } + + c.api.AddReaction("eyes", slack.ItemRef{ + Channel: channelID, + Timestamp: messageTS, + }) + + c.pendingAcks.Store(chatID, slackMessageRef{ + ChannelID: channelID, + Timestamp: messageTS, + }) + + content := ev.Text + content = c.stripBotMention(content) + + var mediaPaths []string + + if ev.Message != nil && len(ev.Message.Files) > 0 { + for _, file := range ev.Message.Files { + localPath := c.downloadSlackFile(file) + if localPath == "" { + continue + } + mediaPaths = append(mediaPaths, localPath) + + if isAudioFile(file.Name, file.Mimetype) && c.transcriber != nil && c.transcriber.IsAvailable() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + result, err := c.transcriber.Transcribe(ctx, localPath) + cancel() + + if err != nil { + logger.ErrorCF("slack", "Voice transcription failed", map[string]interface{}{"error": err.Error()}) + content += fmt.Sprintf("\n[audio: %s (transcription failed)]", file.Name) + } else { + content += fmt.Sprintf("\n[voice transcription: %s]", result.Text) + } + } else { + content += fmt.Sprintf("\n[file: %s]", file.Name) + } + } + } + + if strings.TrimSpace(content) == "" { + return + } + + metadata := map[string]string{ + "message_ts": messageTS, + "channel_id": channelID, + "thread_ts": threadTS, + "platform": "slack", + } + + logger.DebugCF("slack", "Received message", map[string]interface{}{ + "sender_id": senderID, + "chat_id": chatID, + "preview": truncateStringSlack(content, 50), + "has_thread": threadTS != "", + }) + + c.HandleMessage(senderID, chatID, content, mediaPaths, metadata) +} + +func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) { + if ev.User == c.botUserID { + return + } + + senderID := ev.User + channelID := ev.Channel + threadTS := ev.ThreadTimeStamp + messageTS := ev.TimeStamp + + var chatID string + if threadTS != "" { + chatID = channelID + "/" + threadTS + } else { + chatID = channelID + "/" + messageTS + } + + c.api.AddReaction("eyes", slack.ItemRef{ + Channel: channelID, + Timestamp: messageTS, + }) + + c.pendingAcks.Store(chatID, slackMessageRef{ + ChannelID: channelID, + Timestamp: messageTS, + }) + + content := c.stripBotMention(ev.Text) + + if strings.TrimSpace(content) == "" { + return + } + + metadata := map[string]string{ + "message_ts": messageTS, + "channel_id": channelID, + "thread_ts": threadTS, + "platform": "slack", + "is_mention": "true", + } + + c.HandleMessage(senderID, chatID, content, nil, metadata) +} + +func (c *SlackChannel) handleSlashCommand(event socketmode.Event) { + cmd, ok := event.Data.(slack.SlashCommand) + if !ok { + return + } + + if event.Request != nil { + c.socketClient.Ack(*event.Request) + } + + senderID := cmd.UserID + channelID := cmd.ChannelID + chatID := channelID + content := cmd.Text + + if strings.TrimSpace(content) == "" { + content = "help" + } + + metadata := map[string]string{ + "channel_id": channelID, + "platform": "slack", + "is_command": "true", + "trigger_id": cmd.TriggerID, + } + + logger.DebugCF("slack", "Slash command received", map[string]interface{}{ + "sender_id": senderID, + "command": cmd.Command, + "text": truncateStringSlack(content, 50), + }) + + c.HandleMessage(senderID, chatID, content, nil, metadata) +} + +func (c *SlackChannel) handleReactionAdded(ev *slackevents.ReactionAddedEvent) { + logger.DebugCF("slack", "Reaction added", map[string]interface{}{ + "reaction": ev.Reaction, + "user": ev.User, + "item_ts": ev.Item.Timestamp, + }) +} + +func (c *SlackChannel) handleReactionRemoved(ev *slackevents.ReactionRemovedEvent) { + logger.DebugCF("slack", "Reaction removed", map[string]interface{}{ + "reaction": ev.Reaction, + "user": ev.User, + "item_ts": ev.Item.Timestamp, + }) +} + +func (c *SlackChannel) downloadSlackFile(file slack.File) string { + mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") + if err := os.MkdirAll(mediaDir, 0755); err != nil { + logger.ErrorCF("slack", "Failed to create media directory", map[string]interface{}{"error": err.Error()}) + return "" + } + + downloadURL := file.URLPrivateDownload + if downloadURL == "" { + downloadURL = file.URLPrivate + } + if downloadURL == "" { + logger.ErrorCF("slack", "No download URL for file", map[string]interface{}{"file_id": file.ID}) + return "" + } + + localPath := filepath.Join(mediaDir, file.Name) + + req, err := http.NewRequest("GET", downloadURL, nil) + if err != nil { + logger.ErrorCF("slack", "Failed to create download request", map[string]interface{}{"error": err.Error()}) + return "" + } + req.Header.Set("Authorization", "Bearer "+c.config.BotToken) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + logger.ErrorCF("slack", "Failed to download file", map[string]interface{}{"error": err.Error()}) + return "" + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.ErrorCF("slack", "File download returned non-200 status", map[string]interface{}{"status": resp.StatusCode}) + return "" + } + + out, err := os.Create(localPath) + if err != nil { + logger.ErrorCF("slack", "Failed to create local file", map[string]interface{}{"error": err.Error()}) + return "" + } + defer out.Close() + + if _, err := io.Copy(out, resp.Body); err != nil { + logger.ErrorCF("slack", "Failed to write file", map[string]interface{}{"error": err.Error()}) + return "" + } + + logger.DebugCF("slack", "File downloaded", map[string]interface{}{"path": localPath, "name": file.Name}) + return localPath +} + +func (c *SlackChannel) stripBotMention(text string) string { + mention := fmt.Sprintf("<@%s>", c.botUserID) + text = strings.ReplaceAll(text, mention, "") + return strings.TrimSpace(text) +} + +func parseSlackChatID(chatID string) (channelID, threadTS string) { + parts := strings.SplitN(chatID, "/", 2) + channelID = parts[0] + if len(parts) > 1 { + threadTS = parts[1] + } + return +} + +func truncateStringSlack(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen] +} diff --git a/pkg/channels/slack_test.go b/pkg/channels/slack_test.go new file mode 100644 index 0000000..3de8e50 --- /dev/null +++ b/pkg/channels/slack_test.go @@ -0,0 +1,193 @@ +package channels + +import ( + "testing" + + "github.com/sipeed/picoclaw/pkg/bus" + "github.com/sipeed/picoclaw/pkg/config" +) + +func TestParseSlackChatID(t *testing.T) { + tests := []struct { + name string + chatID string + wantChanID string + wantThread string + }{ + { + name: "channel only", + chatID: "C123456", + wantChanID: "C123456", + wantThread: "", + }, + { + name: "channel with thread", + chatID: "C123456/1234567890.123456", + wantChanID: "C123456", + wantThread: "1234567890.123456", + }, + { + name: "DM channel", + chatID: "D987654", + wantChanID: "D987654", + wantThread: "", + }, + { + name: "empty string", + chatID: "", + wantChanID: "", + wantThread: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + chanID, threadTS := parseSlackChatID(tt.chatID) + if chanID != tt.wantChanID { + t.Errorf("parseSlackChatID(%q) channelID = %q, want %q", tt.chatID, chanID, tt.wantChanID) + } + if threadTS != tt.wantThread { + t.Errorf("parseSlackChatID(%q) threadTS = %q, want %q", tt.chatID, threadTS, tt.wantThread) + } + }) + } +} + +func TestStripBotMention(t *testing.T) { + ch := &SlackChannel{botUserID: "U12345BOT"} + + tests := []struct { + name string + input string + want string + }{ + { + name: "mention at start", + input: "<@U12345BOT> hello there", + want: "hello there", + }, + { + name: "mention in middle", + input: "hey <@U12345BOT> can you help", + want: "hey can you help", + }, + { + name: "no mention", + input: "hello world", + want: "hello world", + }, + { + name: "empty string", + input: "", + want: "", + }, + { + name: "only mention", + input: "<@U12345BOT>", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ch.stripBotMention(tt.input) + if got != tt.want { + t.Errorf("stripBotMention(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + +func TestNewSlackChannel(t *testing.T) { + msgBus := bus.NewMessageBus() + + t.Run("missing bot token", func(t *testing.T) { + cfg := config.SlackConfig{ + BotToken: "", + AppToken: "xapp-test", + } + _, err := NewSlackChannel(cfg, msgBus) + if err == nil { + t.Error("expected error for missing bot_token, got nil") + } + }) + + t.Run("missing app token", func(t *testing.T) { + cfg := config.SlackConfig{ + BotToken: "xoxb-test", + AppToken: "", + } + _, err := NewSlackChannel(cfg, msgBus) + if err == nil { + t.Error("expected error for missing app_token, got nil") + } + }) + + t.Run("valid config", func(t *testing.T) { + cfg := config.SlackConfig{ + BotToken: "xoxb-test", + AppToken: "xapp-test", + AllowFrom: []string{"U123"}, + } + ch, err := NewSlackChannel(cfg, msgBus) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if ch.Name() != "slack" { + t.Errorf("Name() = %q, want %q", ch.Name(), "slack") + } + if ch.IsRunning() { + t.Error("new channel should not be running") + } + }) +} + +func TestSlackChannelIsAllowed(t *testing.T) { + msgBus := bus.NewMessageBus() + + t.Run("empty allowlist allows all", func(t *testing.T) { + cfg := config.SlackConfig{ + BotToken: "xoxb-test", + AppToken: "xapp-test", + AllowFrom: []string{}, + } + ch, _ := NewSlackChannel(cfg, msgBus) + if !ch.IsAllowed("U_ANYONE") { + t.Error("empty allowlist should allow all users") + } + }) + + t.Run("allowlist restricts users", func(t *testing.T) { + cfg := config.SlackConfig{ + BotToken: "xoxb-test", + AppToken: "xapp-test", + AllowFrom: []string{"U_ALLOWED"}, + } + ch, _ := NewSlackChannel(cfg, msgBus) + if !ch.IsAllowed("U_ALLOWED") { + t.Error("allowed user should pass allowlist check") + } + if ch.IsAllowed("U_BLOCKED") { + t.Error("non-allowed user should be blocked") + } + }) +} + +func TestTruncateStringSlack(t *testing.T) { + tests := []struct { + input string + maxLen int + want string + }{ + {"hello", 10, "hello"}, + {"hello world", 5, "hello"}, + {"", 5, ""}, + } + + for _, tt := range tests { + got := truncateStringSlack(tt.input, tt.maxLen) + if got != tt.want { + t.Errorf("truncateStringSlack(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want) + } + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 5b9c2b5..14cea6f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -38,6 +38,7 @@ type ChannelsConfig struct { MaixCam MaixCamConfig `json:"maixcam"` QQ QQConfig `json:"qq"` DingTalk DingTalkConfig `json:"dingtalk"` + Slack SlackConfig `json:"slack"` } type WhatsAppConfig struct { @@ -88,6 +89,13 @@ type DingTalkConfig struct { AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"` } +type SlackConfig struct { + Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_SLACK_ENABLED"` + BotToken string `json:"bot_token" env:"PICOCLAW_CHANNELS_SLACK_BOT_TOKEN"` + AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_SLACK_APP_TOKEN"` + AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_SLACK_ALLOW_FROM"` +} + type ProvidersConfig struct { Anthropic ProviderConfig `json:"anthropic"` OpenAI ProviderConfig `json:"openai"` @@ -174,6 +182,12 @@ func DefaultConfig() *Config { ClientSecret: "", AllowFrom: []string{}, }, + Slack: SlackConfig{ + Enabled: false, + BotToken: "", + AppToken: "", + AllowFrom: []string{}, + }, }, Providers: ProvidersConfig{ Anthropic: ProviderConfig{}, From fbad753b2ac4df2b39fd77ec20ba044167349350 Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Wed, 11 Feb 2026 13:27:59 -0600 Subject: [PATCH 15/23] feat(providers): add SDK-based providers for subscription OAuth login Add ClaudeProvider (anthropic-sdk-go) and CodexProvider (openai-go) that use the correct subscription endpoints and API formats: - CodexProvider: chatgpt.com/backend-api/codex/responses (Responses API) with OAuth Bearer auth and Chatgpt-Account-Id header - ClaudeProvider: api.anthropic.com/v1/messages (Messages API) with Authorization: Bearer token auth Update CreateProvider() routing to use new SDK-based providers when auth_method is "oauth" or "token", removing the stopgap that sent subscription tokens to pay-per-token endpoints. Closes #18 Co-Authored-By: Claude Opus 4.6 --- go.mod | 3 + go.sum | 8 + pkg/providers/claude_provider.go | 207 ++++++++++++++++++++ pkg/providers/claude_provider_test.go | 210 ++++++++++++++++++++ pkg/providers/codex_provider.go | 248 ++++++++++++++++++++++++ pkg/providers/codex_provider_test.go | 264 ++++++++++++++++++++++++++ pkg/providers/http_provider.go | 78 ++------ 7 files changed, 960 insertions(+), 58 deletions(-) create mode 100644 pkg/providers/claude_provider.go create mode 100644 pkg/providers/claude_provider_test.go create mode 100644 pkg/providers/codex_provider.go create mode 100644 pkg/providers/codex_provider_test.go diff --git a/go.mod b/go.mod index 832f1e8..54c73fc 100644 --- a/go.mod +++ b/go.mod @@ -16,12 +16,15 @@ require ( ) require ( + github.com/anthropics/anthropic-sdk-go v1.22.1 // indirect github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/openai/openai-go v1.12.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect diff --git a/go.sum b/go.sum index f1ce926..96ff62e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc= github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/anthropics/anthropic-sdk-go v1.22.1 h1:xbsc3vJKCX/ELDZSpTNfz9wCgrFsamwFewPb1iI0Xh0= +github.com/anthropics/anthropic-sdk-go v1.22.1/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= @@ -72,6 +74,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 h1:Lb/Uzkiw2Ugt2Xf03J5wmv81PdkYOiWbI8CNBi1boC8= github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU= +github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= +github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -86,9 +90,11 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tencent-connect/botgo v0.2.1 h1:+BrTt9Zh+awL28GWC4g5Na3nQaGRWb0N5IctS8WqBCk= github.com/tencent-connect/botgo v0.2.1/go.mod h1:oO1sG9ybhXNickvt+CVym5khwQ+uKhTR+IhTqEfOVsI= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -97,6 +103,8 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/pkg/providers/claude_provider.go b/pkg/providers/claude_provider.go new file mode 100644 index 0000000..ae6aca9 --- /dev/null +++ b/pkg/providers/claude_provider.go @@ -0,0 +1,207 @@ +package providers + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/option" + "github.com/sipeed/picoclaw/pkg/auth" +) + +type ClaudeProvider struct { + client *anthropic.Client + tokenSource func() (string, error) +} + +func NewClaudeProvider(token string) *ClaudeProvider { + client := anthropic.NewClient( + option.WithAuthToken(token), + option.WithBaseURL("https://api.anthropic.com"), + ) + return &ClaudeProvider{client: &client} +} + +func NewClaudeProviderWithTokenSource(token string, tokenSource func() (string, error)) *ClaudeProvider { + p := NewClaudeProvider(token) + p.tokenSource = tokenSource + return p +} + +func (p *ClaudeProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { + var opts []option.RequestOption + if p.tokenSource != nil { + tok, err := p.tokenSource() + if err != nil { + return nil, fmt.Errorf("refreshing token: %w", err) + } + opts = append(opts, option.WithAuthToken(tok)) + } + + params, err := buildClaudeParams(messages, tools, model, options) + if err != nil { + return nil, err + } + + resp, err := p.client.Messages.New(ctx, params, opts...) + if err != nil { + return nil, fmt.Errorf("claude API call: %w", err) + } + + return parseClaudeResponse(resp), nil +} + +func (p *ClaudeProvider) GetDefaultModel() string { + return "claude-sonnet-4-5-20250929" +} + +func buildClaudeParams(messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (anthropic.MessageNewParams, error) { + var system []anthropic.TextBlockParam + var anthropicMessages []anthropic.MessageParam + + for _, msg := range messages { + switch msg.Role { + case "system": + system = append(system, anthropic.TextBlockParam{Text: msg.Content}) + case "user": + if msg.ToolCallID != "" { + anthropicMessages = append(anthropicMessages, + anthropic.NewUserMessage(anthropic.NewToolResultBlock(msg.ToolCallID, msg.Content, false)), + ) + } else { + anthropicMessages = append(anthropicMessages, + anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content)), + ) + } + case "assistant": + if len(msg.ToolCalls) > 0 { + var blocks []anthropic.ContentBlockParamUnion + if msg.Content != "" { + blocks = append(blocks, anthropic.NewTextBlock(msg.Content)) + } + for _, tc := range msg.ToolCalls { + blocks = append(blocks, anthropic.NewToolUseBlock(tc.ID, tc.Arguments, tc.Name)) + } + anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...)) + } else { + anthropicMessages = append(anthropicMessages, + anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content)), + ) + } + case "tool": + anthropicMessages = append(anthropicMessages, + anthropic.NewUserMessage(anthropic.NewToolResultBlock(msg.ToolCallID, msg.Content, false)), + ) + } + } + + maxTokens := int64(4096) + if mt, ok := options["max_tokens"].(int); ok { + maxTokens = int64(mt) + } + + params := anthropic.MessageNewParams{ + Model: anthropic.Model(model), + Messages: anthropicMessages, + MaxTokens: maxTokens, + } + + if len(system) > 0 { + params.System = system + } + + if temp, ok := options["temperature"].(float64); ok { + params.Temperature = anthropic.Float(temp) + } + + if len(tools) > 0 { + params.Tools = translateToolsForClaude(tools) + } + + return params, nil +} + +func translateToolsForClaude(tools []ToolDefinition) []anthropic.ToolUnionParam { + result := make([]anthropic.ToolUnionParam, 0, len(tools)) + for _, t := range tools { + tool := anthropic.ToolParam{ + Name: t.Function.Name, + InputSchema: anthropic.ToolInputSchemaParam{ + Properties: t.Function.Parameters["properties"], + }, + } + if desc := t.Function.Description; desc != "" { + tool.Description = anthropic.String(desc) + } + if req, ok := t.Function.Parameters["required"].([]interface{}); ok { + required := make([]string, 0, len(req)) + for _, r := range req { + if s, ok := r.(string); ok { + required = append(required, s) + } + } + tool.InputSchema.Required = required + } + result = append(result, anthropic.ToolUnionParam{OfTool: &tool}) + } + return result +} + +func parseClaudeResponse(resp *anthropic.Message) *LLMResponse { + var content string + var toolCalls []ToolCall + + for _, block := range resp.Content { + switch block.Type { + case "text": + tb := block.AsText() + content += tb.Text + case "tool_use": + tu := block.AsToolUse() + var args map[string]interface{} + if err := json.Unmarshal(tu.Input, &args); err != nil { + args = map[string]interface{}{"raw": string(tu.Input)} + } + toolCalls = append(toolCalls, ToolCall{ + ID: tu.ID, + Name: tu.Name, + Arguments: args, + }) + } + } + + finishReason := "stop" + switch resp.StopReason { + case anthropic.StopReasonToolUse: + finishReason = "tool_calls" + case anthropic.StopReasonMaxTokens: + finishReason = "length" + case anthropic.StopReasonEndTurn: + finishReason = "stop" + } + + return &LLMResponse{ + Content: content, + ToolCalls: toolCalls, + FinishReason: finishReason, + Usage: &UsageInfo{ + PromptTokens: int(resp.Usage.InputTokens), + CompletionTokens: int(resp.Usage.OutputTokens), + TotalTokens: int(resp.Usage.InputTokens + resp.Usage.OutputTokens), + }, + } +} + +func createClaudeTokenSource() func() (string, error) { + return func() (string, error) { + cred, err := auth.GetCredential("anthropic") + if err != nil { + return "", fmt.Errorf("loading auth credentials: %w", err) + } + if cred == nil { + return "", fmt.Errorf("no credentials for anthropic. Run: picoclaw auth login --provider anthropic") + } + return cred.AccessToken, nil + } +} diff --git a/pkg/providers/claude_provider_test.go b/pkg/providers/claude_provider_test.go new file mode 100644 index 0000000..bbad2d2 --- /dev/null +++ b/pkg/providers/claude_provider_test.go @@ -0,0 +1,210 @@ +package providers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/anthropics/anthropic-sdk-go" + anthropicoption "github.com/anthropics/anthropic-sdk-go/option" +) + +func TestBuildClaudeParams_BasicMessage(t *testing.T) { + messages := []Message{ + {Role: "user", Content: "Hello"}, + } + params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{ + "max_tokens": 1024, + }) + if err != nil { + t.Fatalf("buildClaudeParams() error: %v", err) + } + if string(params.Model) != "claude-sonnet-4-5-20250929" { + t.Errorf("Model = %q, want %q", params.Model, "claude-sonnet-4-5-20250929") + } + if params.MaxTokens != 1024 { + t.Errorf("MaxTokens = %d, want 1024", params.MaxTokens) + } + if len(params.Messages) != 1 { + t.Fatalf("len(Messages) = %d, want 1", len(params.Messages)) + } +} + +func TestBuildClaudeParams_SystemMessage(t *testing.T) { + messages := []Message{ + {Role: "system", Content: "You are helpful"}, + {Role: "user", Content: "Hi"}, + } + params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{}) + if err != nil { + t.Fatalf("buildClaudeParams() error: %v", err) + } + if len(params.System) != 1 { + t.Fatalf("len(System) = %d, want 1", len(params.System)) + } + if params.System[0].Text != "You are helpful" { + t.Errorf("System[0].Text = %q, want %q", params.System[0].Text, "You are helpful") + } + if len(params.Messages) != 1 { + t.Fatalf("len(Messages) = %d, want 1", len(params.Messages)) + } +} + +func TestBuildClaudeParams_ToolCallMessage(t *testing.T) { + messages := []Message{ + {Role: "user", Content: "What's the weather?"}, + { + Role: "assistant", + Content: "", + ToolCalls: []ToolCall{ + { + ID: "call_1", + Name: "get_weather", + Arguments: map[string]interface{}{"city": "SF"}, + }, + }, + }, + {Role: "tool", Content: `{"temp": 72}`, ToolCallID: "call_1"}, + } + params, err := buildClaudeParams(messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{}) + if err != nil { + t.Fatalf("buildClaudeParams() error: %v", err) + } + if len(params.Messages) != 3 { + t.Fatalf("len(Messages) = %d, want 3", len(params.Messages)) + } +} + +func TestBuildClaudeParams_WithTools(t *testing.T) { + tools := []ToolDefinition{ + { + Type: "function", + Function: ToolFunctionDefinition{ + Name: "get_weather", + Description: "Get weather for a city", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "city": map[string]interface{}{"type": "string"}, + }, + "required": []interface{}{"city"}, + }, + }, + }, + } + params, err := buildClaudeParams([]Message{{Role: "user", Content: "Hi"}}, tools, "claude-sonnet-4-5-20250929", map[string]interface{}{}) + if err != nil { + t.Fatalf("buildClaudeParams() error: %v", err) + } + if len(params.Tools) != 1 { + t.Fatalf("len(Tools) = %d, want 1", len(params.Tools)) + } +} + +func TestParseClaudeResponse_TextOnly(t *testing.T) { + resp := &anthropic.Message{ + Content: []anthropic.ContentBlockUnion{}, + Usage: anthropic.Usage{ + InputTokens: 10, + OutputTokens: 20, + }, + } + result := parseClaudeResponse(resp) + if result.Usage.PromptTokens != 10 { + t.Errorf("PromptTokens = %d, want 10", result.Usage.PromptTokens) + } + if result.Usage.CompletionTokens != 20 { + t.Errorf("CompletionTokens = %d, want 20", result.Usage.CompletionTokens) + } + if result.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "stop") + } +} + +func TestParseClaudeResponse_StopReasons(t *testing.T) { + tests := []struct { + stopReason anthropic.StopReason + want string + }{ + {anthropic.StopReasonEndTurn, "stop"}, + {anthropic.StopReasonMaxTokens, "length"}, + {anthropic.StopReasonToolUse, "tool_calls"}, + } + for _, tt := range tests { + resp := &anthropic.Message{ + StopReason: tt.stopReason, + } + result := parseClaudeResponse(resp) + if result.FinishReason != tt.want { + t.Errorf("StopReason %q: FinishReason = %q, want %q", tt.stopReason, result.FinishReason, tt.want) + } + } +} + +func TestClaudeProvider_ChatRoundTrip(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/v1/messages" { + http.Error(w, "not found", http.StatusNotFound) + return + } + if r.Header.Get("Authorization") != "Bearer test-token" { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + var reqBody map[string]interface{} + json.NewDecoder(r.Body).Decode(&reqBody) + + resp := map[string]interface{}{ + "id": "msg_test", + "type": "message", + "role": "assistant", + "model": reqBody["model"], + "stop_reason": "end_turn", + "content": []map[string]interface{}{ + {"type": "text", "text": "Hello! How can I help you?"}, + }, + "usage": map[string]interface{}{ + "input_tokens": 15, + "output_tokens": 8, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + })) + defer server.Close() + + provider := NewClaudeProvider("test-token") + provider.client = createAnthropicTestClient(server.URL, "test-token") + + messages := []Message{{Role: "user", Content: "Hello"}} + resp, err := provider.Chat(t.Context(), messages, nil, "claude-sonnet-4-5-20250929", map[string]interface{}{"max_tokens": 1024}) + if err != nil { + t.Fatalf("Chat() error: %v", err) + } + if resp.Content != "Hello! How can I help you?" { + t.Errorf("Content = %q, want %q", resp.Content, "Hello! How can I help you?") + } + if resp.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", resp.FinishReason, "stop") + } + if resp.Usage.PromptTokens != 15 { + t.Errorf("PromptTokens = %d, want 15", resp.Usage.PromptTokens) + } +} + +func TestClaudeProvider_GetDefaultModel(t *testing.T) { + p := NewClaudeProvider("test-token") + if got := p.GetDefaultModel(); got != "claude-sonnet-4-5-20250929" { + t.Errorf("GetDefaultModel() = %q, want %q", got, "claude-sonnet-4-5-20250929") + } +} + +func createAnthropicTestClient(baseURL, token string) *anthropic.Client { + c := anthropic.NewClient( + anthropicoption.WithAuthToken(token), + anthropicoption.WithBaseURL(baseURL), + ) + return &c +} diff --git a/pkg/providers/codex_provider.go b/pkg/providers/codex_provider.go new file mode 100644 index 0000000..a17ae22 --- /dev/null +++ b/pkg/providers/codex_provider.go @@ -0,0 +1,248 @@ +package providers + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/openai/openai-go" + "github.com/openai/openai-go/option" + "github.com/openai/openai-go/responses" + "github.com/sipeed/picoclaw/pkg/auth" +) + +type CodexProvider struct { + client *openai.Client + accountID string + tokenSource func() (string, string, error) +} + +func NewCodexProvider(token, accountID string) *CodexProvider { + opts := []option.RequestOption{ + option.WithBaseURL("https://chatgpt.com/backend-api/codex"), + option.WithAPIKey(token), + } + if accountID != "" { + opts = append(opts, option.WithHeader("Chatgpt-Account-Id", accountID)) + } + client := openai.NewClient(opts...) + return &CodexProvider{ + client: &client, + accountID: accountID, + } +} + +func NewCodexProviderWithTokenSource(token, accountID string, tokenSource func() (string, string, error)) *CodexProvider { + p := NewCodexProvider(token, accountID) + p.tokenSource = tokenSource + return p +} + +func (p *CodexProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { + var opts []option.RequestOption + if p.tokenSource != nil { + tok, accID, err := p.tokenSource() + if err != nil { + return nil, fmt.Errorf("refreshing token: %w", err) + } + opts = append(opts, option.WithAPIKey(tok)) + if accID != "" { + opts = append(opts, option.WithHeader("Chatgpt-Account-Id", accID)) + } + } + + params := buildCodexParams(messages, tools, model, options) + + resp, err := p.client.Responses.New(ctx, params, opts...) + if err != nil { + return nil, fmt.Errorf("codex API call: %w", err) + } + + return parseCodexResponse(resp), nil +} + +func (p *CodexProvider) GetDefaultModel() string { + return "gpt-4o" +} + +func buildCodexParams(messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) responses.ResponseNewParams { + var inputItems responses.ResponseInputParam + var instructions string + + for _, msg := range messages { + switch msg.Role { + case "system": + instructions = msg.Content + case "user": + if msg.ToolCallID != "" { + inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ + OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ + CallID: msg.ToolCallID, + Output: msg.Content, + }, + }) + } else { + inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ + OfMessage: &responses.EasyInputMessageParam{ + Role: responses.EasyInputMessageRoleUser, + Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, + }, + }) + } + case "assistant": + if len(msg.ToolCalls) > 0 { + if msg.Content != "" { + inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ + OfMessage: &responses.EasyInputMessageParam{ + Role: responses.EasyInputMessageRoleAssistant, + Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, + }, + }) + } + for _, tc := range msg.ToolCalls { + argsJSON, _ := json.Marshal(tc.Arguments) + inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ + OfFunctionCall: &responses.ResponseFunctionToolCallParam{ + CallID: tc.ID, + Name: tc.Name, + Arguments: string(argsJSON), + }, + }) + } + } else { + inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ + OfMessage: &responses.EasyInputMessageParam{ + Role: responses.EasyInputMessageRoleAssistant, + Content: responses.EasyInputMessageContentUnionParam{OfString: openai.Opt(msg.Content)}, + }, + }) + } + case "tool": + inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ + OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ + CallID: msg.ToolCallID, + Output: msg.Content, + }, + }) + } + } + + params := responses.ResponseNewParams{ + Model: model, + Input: responses.ResponseNewParamsInputUnion{ + OfInputItemList: inputItems, + }, + Store: openai.Opt(false), + } + + if instructions != "" { + params.Instructions = openai.Opt(instructions) + } + + if maxTokens, ok := options["max_tokens"].(int); ok { + params.MaxOutputTokens = openai.Opt(int64(maxTokens)) + } + + if temp, ok := options["temperature"].(float64); ok { + params.Temperature = openai.Opt(temp) + } + + if len(tools) > 0 { + params.Tools = translateToolsForCodex(tools) + } + + return params +} + +func translateToolsForCodex(tools []ToolDefinition) []responses.ToolUnionParam { + result := make([]responses.ToolUnionParam, 0, len(tools)) + for _, t := range tools { + ft := responses.FunctionToolParam{ + Name: t.Function.Name, + Parameters: t.Function.Parameters, + Strict: openai.Opt(false), + } + if t.Function.Description != "" { + ft.Description = openai.Opt(t.Function.Description) + } + result = append(result, responses.ToolUnionParam{OfFunction: &ft}) + } + return result +} + +func parseCodexResponse(resp *responses.Response) *LLMResponse { + var content strings.Builder + var toolCalls []ToolCall + + for _, item := range resp.Output { + switch item.Type { + case "message": + for _, c := range item.Content { + if c.Type == "output_text" { + content.WriteString(c.Text) + } + } + case "function_call": + var args map[string]interface{} + if err := json.Unmarshal([]byte(item.Arguments), &args); err != nil { + args = map[string]interface{}{"raw": item.Arguments} + } + toolCalls = append(toolCalls, ToolCall{ + ID: item.CallID, + Name: item.Name, + Arguments: args, + }) + } + } + + finishReason := "stop" + if len(toolCalls) > 0 { + finishReason = "tool_calls" + } + if resp.Status == "incomplete" { + finishReason = "length" + } + + var usage *UsageInfo + if resp.Usage.TotalTokens > 0 { + usage = &UsageInfo{ + PromptTokens: int(resp.Usage.InputTokens), + CompletionTokens: int(resp.Usage.OutputTokens), + TotalTokens: int(resp.Usage.TotalTokens), + } + } + + return &LLMResponse{ + Content: content.String(), + ToolCalls: toolCalls, + FinishReason: finishReason, + Usage: usage, + } +} + +func createCodexTokenSource() func() (string, string, error) { + return func() (string, string, error) { + cred, err := auth.GetCredential("openai") + if err != nil { + return "", "", fmt.Errorf("loading auth credentials: %w", err) + } + if cred == nil { + return "", "", fmt.Errorf("no credentials for openai. Run: picoclaw auth login --provider openai") + } + + if cred.AuthMethod == "oauth" && cred.NeedsRefresh() && cred.RefreshToken != "" { + oauthCfg := auth.OpenAIOAuthConfig() + refreshed, err := auth.RefreshAccessToken(cred, oauthCfg) + if err != nil { + return "", "", fmt.Errorf("refreshing token: %w", err) + } + if err := auth.SetCredential("openai", refreshed); err != nil { + return "", "", fmt.Errorf("saving refreshed token: %w", err) + } + return refreshed.AccessToken, refreshed.AccountID, nil + } + + return cred.AccessToken, cred.AccountID, nil + } +} diff --git a/pkg/providers/codex_provider_test.go b/pkg/providers/codex_provider_test.go new file mode 100644 index 0000000..e68a70b --- /dev/null +++ b/pkg/providers/codex_provider_test.go @@ -0,0 +1,264 @@ +package providers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/openai/openai-go" + openaiopt "github.com/openai/openai-go/option" + "github.com/openai/openai-go/responses" +) + +func TestBuildCodexParams_BasicMessage(t *testing.T) { + messages := []Message{ + {Role: "user", Content: "Hello"}, + } + params := buildCodexParams(messages, nil, "gpt-4o", map[string]interface{}{ + "max_tokens": 2048, + }) + if params.Model != "gpt-4o" { + t.Errorf("Model = %q, want %q", params.Model, "gpt-4o") + } +} + +func TestBuildCodexParams_SystemAsInstructions(t *testing.T) { + messages := []Message{ + {Role: "system", Content: "You are helpful"}, + {Role: "user", Content: "Hi"}, + } + params := buildCodexParams(messages, nil, "gpt-4o", map[string]interface{}{}) + if !params.Instructions.Valid() { + t.Fatal("Instructions should be set") + } + if params.Instructions.Or("") != "You are helpful" { + t.Errorf("Instructions = %q, want %q", params.Instructions.Or(""), "You are helpful") + } +} + +func TestBuildCodexParams_ToolCallConversation(t *testing.T) { + messages := []Message{ + {Role: "user", Content: "What's the weather?"}, + { + Role: "assistant", + ToolCalls: []ToolCall{ + {ID: "call_1", Name: "get_weather", Arguments: map[string]interface{}{"city": "SF"}}, + }, + }, + {Role: "tool", Content: `{"temp": 72}`, ToolCallID: "call_1"}, + } + params := buildCodexParams(messages, nil, "gpt-4o", map[string]interface{}{}) + if params.Input.OfInputItemList == nil { + t.Fatal("Input.OfInputItemList should not be nil") + } + if len(params.Input.OfInputItemList) != 3 { + t.Errorf("len(Input items) = %d, want 3", len(params.Input.OfInputItemList)) + } +} + +func TestBuildCodexParams_WithTools(t *testing.T) { + tools := []ToolDefinition{ + { + Type: "function", + Function: ToolFunctionDefinition{ + Name: "get_weather", + Description: "Get weather", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "city": map[string]interface{}{"type": "string"}, + }, + }, + }, + }, + } + params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, tools, "gpt-4o", map[string]interface{}{}) + if len(params.Tools) != 1 { + t.Fatalf("len(Tools) = %d, want 1", len(params.Tools)) + } + if params.Tools[0].OfFunction == nil { + t.Fatal("Tool should be a function tool") + } + if params.Tools[0].OfFunction.Name != "get_weather" { + t.Errorf("Tool name = %q, want %q", params.Tools[0].OfFunction.Name, "get_weather") + } +} + +func TestBuildCodexParams_StoreIsFalse(t *testing.T) { + params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "gpt-4o", map[string]interface{}{}) + if !params.Store.Valid() || params.Store.Or(true) != false { + t.Error("Store should be explicitly set to false") + } +} + +func TestParseCodexResponse_TextOutput(t *testing.T) { + respJSON := `{ + "id": "resp_test", + "object": "response", + "status": "completed", + "output": [ + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [ + {"type": "output_text", "text": "Hello there!"} + ] + } + ], + "usage": { + "input_tokens": 10, + "output_tokens": 5, + "total_tokens": 15, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0} + } + }` + + var resp responses.Response + if err := json.Unmarshal([]byte(respJSON), &resp); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + result := parseCodexResponse(&resp) + if result.Content != "Hello there!" { + t.Errorf("Content = %q, want %q", result.Content, "Hello there!") + } + if result.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "stop") + } + if result.Usage.TotalTokens != 15 { + t.Errorf("TotalTokens = %d, want 15", result.Usage.TotalTokens) + } +} + +func TestParseCodexResponse_FunctionCall(t *testing.T) { + respJSON := `{ + "id": "resp_test", + "object": "response", + "status": "completed", + "output": [ + { + "id": "fc_1", + "type": "function_call", + "call_id": "call_abc", + "name": "get_weather", + "arguments": "{\"city\":\"SF\"}", + "status": "completed" + } + ], + "usage": { + "input_tokens": 10, + "output_tokens": 8, + "total_tokens": 18, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens_details": {"reasoning_tokens": 0} + } + }` + + var resp responses.Response + if err := json.Unmarshal([]byte(respJSON), &resp); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + result := parseCodexResponse(&resp) + if len(result.ToolCalls) != 1 { + t.Fatalf("len(ToolCalls) = %d, want 1", len(result.ToolCalls)) + } + tc := result.ToolCalls[0] + if tc.Name != "get_weather" { + t.Errorf("ToolCall.Name = %q, want %q", tc.Name, "get_weather") + } + if tc.ID != "call_abc" { + t.Errorf("ToolCall.ID = %q, want %q", tc.ID, "call_abc") + } + if tc.Arguments["city"] != "SF" { + t.Errorf("ToolCall.Arguments[city] = %v, want SF", tc.Arguments["city"]) + } + if result.FinishReason != "tool_calls" { + t.Errorf("FinishReason = %q, want %q", result.FinishReason, "tool_calls") + } +} + +func TestCodexProvider_ChatRoundTrip(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/responses" { + http.Error(w, "not found: "+r.URL.Path, http.StatusNotFound) + return + } + if r.Header.Get("Authorization") != "Bearer test-token" { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + if r.Header.Get("Chatgpt-Account-Id") != "acc-123" { + http.Error(w, "missing account id", http.StatusBadRequest) + return + } + + resp := map[string]interface{}{ + "id": "resp_test", + "object": "response", + "status": "completed", + "output": []map[string]interface{}{ + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "status": "completed", + "content": []map[string]interface{}{ + {"type": "output_text", "text": "Hi from Codex!"}, + }, + }, + }, + "usage": map[string]interface{}{ + "input_tokens": 12, + "output_tokens": 6, + "total_tokens": 18, + "input_tokens_details": map[string]interface{}{"cached_tokens": 0}, + "output_tokens_details": map[string]interface{}{"reasoning_tokens": 0}, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + })) + defer server.Close() + + provider := NewCodexProvider("test-token", "acc-123") + provider.client = createOpenAITestClient(server.URL, "test-token", "acc-123") + + messages := []Message{{Role: "user", Content: "Hello"}} + resp, err := provider.Chat(t.Context(), messages, nil, "gpt-4o", map[string]interface{}{"max_tokens": 1024}) + if err != nil { + t.Fatalf("Chat() error: %v", err) + } + if resp.Content != "Hi from Codex!" { + t.Errorf("Content = %q, want %q", resp.Content, "Hi from Codex!") + } + if resp.FinishReason != "stop" { + t.Errorf("FinishReason = %q, want %q", resp.FinishReason, "stop") + } + if resp.Usage.TotalTokens != 18 { + t.Errorf("TotalTokens = %d, want 18", resp.Usage.TotalTokens) + } +} + +func TestCodexProvider_GetDefaultModel(t *testing.T) { + p := NewCodexProvider("test-token", "") + if got := p.GetDefaultModel(); got != "gpt-4o" { + t.Errorf("GetDefaultModel() = %q, want %q", got, "gpt-4o") + } +} + +func createOpenAITestClient(baseURL, token, accountID string) *openai.Client { + opts := []openaiopt.RequestOption{ + openaiopt.WithBaseURL(baseURL), + openaiopt.WithAPIKey(token), + } + if accountID != "" { + opts = append(opts, openaiopt.WithHeader("Chatgpt-Account-Id", accountID)) + } + c := openai.NewClient(opts...) + return &c +} diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index dab6132..f63c68c 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -20,11 +20,9 @@ import ( ) type HTTPProvider struct { - apiKey string - apiBase string - httpClient *http.Client - tokenSource func() (string, error) - accountID string + apiKey string + apiBase string + httpClient *http.Client } func NewHTTPProvider(apiKey, apiBase string) *HTTPProvider { @@ -76,16 +74,7 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too } req.Header.Set("Content-Type", "application/json") - if p.tokenSource != nil { - token, err := p.tokenSource() - if err != nil { - return nil, fmt.Errorf("failed to get auth token: %w", err) - } - req.Header.Set("Authorization", "Bearer "+token) - if p.accountID != "" { - req.Header.Set("Chatgpt-Account-Id", p.accountID) - } - } else if p.apiKey != "" { + if p.apiKey != "" { req.Header.Set("Authorization", "Bearer "+p.apiKey) } @@ -181,45 +170,26 @@ func (p *HTTPProvider) GetDefaultModel() string { return "" } -func createOAuthTokenSource(provider string) func() (string, error) { - return func() (string, error) { - cred, err := auth.GetCredential(provider) - if err != nil { - return "", fmt.Errorf("loading auth credentials: %w", err) - } - if cred == nil { - return "", fmt.Errorf("no OAuth credentials for %s. Run: picoclaw auth login --provider %s", provider, provider) - } - - if cred.AuthMethod == "oauth" && cred.NeedsRefresh() && cred.RefreshToken != "" { - oauthCfg := auth.OpenAIOAuthConfig() - refreshed, err := auth.RefreshAccessToken(cred, oauthCfg) - if err != nil { - return "", fmt.Errorf("refreshing token: %w", err) - } - if err := auth.SetCredential(provider, refreshed); err != nil { - return "", fmt.Errorf("saving refreshed token: %w", err) - } - return refreshed.AccessToken, nil - } - - return cred.AccessToken, nil - } -} - -func createAuthProvider(providerName string, apiBase string) (LLMProvider, error) { - cred, err := auth.GetCredential(providerName) +func createClaudeAuthProvider() (LLMProvider, error) { + cred, err := auth.GetCredential("anthropic") if err != nil { return nil, fmt.Errorf("loading auth credentials: %w", err) } if cred == nil { - return nil, fmt.Errorf("no credentials for %s. Run: picoclaw auth login --provider %s", providerName, providerName) + return nil, fmt.Errorf("no credentials for anthropic. Run: picoclaw auth login --provider anthropic") } + return NewClaudeProviderWithTokenSource(cred.AccessToken, createClaudeTokenSource()), nil +} - p := NewHTTPProvider(cred.AccessToken, apiBase) - p.tokenSource = createOAuthTokenSource(providerName) - p.accountID = cred.AccountID - return p, nil +func createCodexAuthProvider() (LLMProvider, error) { + cred, err := auth.GetCredential("openai") + if err != nil { + return nil, fmt.Errorf("loading auth credentials: %w", err) + } + if cred == nil { + return nil, fmt.Errorf("no credentials for openai. Run: picoclaw auth login --provider openai") + } + return NewCodexProviderWithTokenSource(cred.AccessToken, cred.AccountID, createCodexTokenSource()), nil } func CreateProvider(cfg *config.Config) (LLMProvider, error) { @@ -240,11 +210,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && (cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != ""): if cfg.Providers.Anthropic.AuthMethod == "oauth" || cfg.Providers.Anthropic.AuthMethod == "token" { - ab := cfg.Providers.Anthropic.APIBase - if ab == "" { - ab = "https://api.anthropic.com/v1" - } - return createAuthProvider("anthropic", ab) + return createClaudeAuthProvider() } apiKey = cfg.Providers.Anthropic.APIKey apiBase = cfg.Providers.Anthropic.APIBase @@ -254,11 +220,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && (cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != ""): if cfg.Providers.OpenAI.AuthMethod == "oauth" || cfg.Providers.OpenAI.AuthMethod == "token" { - ab := cfg.Providers.OpenAI.APIBase - if ab == "" { - ab = "https://api.openai.com/v1" - } - return createAuthProvider("openai", ab) + return createCodexAuthProvider() } apiKey = cfg.Providers.OpenAI.APIKey apiBase = cfg.Providers.OpenAI.APIBase From 83f6e44b024f8b4c77168ecdd2566d04ea215be4 Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Wed, 11 Feb 2026 13:39:19 -0600 Subject: [PATCH 16/23] chore(deps): upgrade openai-go from v1.12.0 to v3.21.0 Update to latest major version of the official OpenAI Go SDK. Fix breaking change: FunctionCallOutput.Output is now a union type (ResponseInputItemFunctionCallOutputOutputUnionParam) instead of string. Co-Authored-By: Claude Opus 4.6 --- go.mod | 4 ++-- go.sum | 7 +++---- pkg/providers/codex_provider.go | 10 +++++----- pkg/providers/codex_provider_test.go | 6 +++--- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 54c73fc..1765efd 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.0 require ( github.com/adhocore/gronx v1.19.6 + github.com/anthropics/anthropic-sdk-go v1.22.1 github.com/bwmarrin/discordgo v0.29.0 github.com/caarlos0/env/v11 v11.3.1 github.com/chzyer/readline v1.5.1 @@ -11,16 +12,15 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/larksuite/oapi-sdk-go/v3 v3.5.3 github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 + github.com/openai/openai-go/v3 v3.21.0 github.com/tencent-connect/botgo v0.2.1 golang.org/x/oauth2 v0.35.0 ) require ( - github.com/anthropics/anthropic-sdk-go v1.22.1 // indirect github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/openai/openai-go v1.12.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/go.sum b/go.sum index 96ff62e..459e661 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 h1:Lb/Uzkiw2Ugt2Xf03J5wmv81PdkYOiWbI8CNBi1boC8= github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU= -github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= -github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= +github.com/openai/openai-go/v3 v3.21.0 h1:3GpIR/W4q/v1uUOVuK3zYtQiF3DnRrZag/sxbtvEdtc= +github.com/openai/openai-go/v3 v3.21.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -88,9 +88,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tencent-connect/botgo v0.2.1 h1:+BrTt9Zh+awL28GWC4g5Na3nQaGRWb0N5IctS8WqBCk= github.com/tencent-connect/botgo v0.2.1/go.mod h1:oO1sG9ybhXNickvt+CVym5khwQ+uKhTR+IhTqEfOVsI= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= diff --git a/pkg/providers/codex_provider.go b/pkg/providers/codex_provider.go index a17ae22..3463389 100644 --- a/pkg/providers/codex_provider.go +++ b/pkg/providers/codex_provider.go @@ -6,9 +6,9 @@ import ( "fmt" "strings" - "github.com/openai/openai-go" - "github.com/openai/openai-go/option" - "github.com/openai/openai-go/responses" + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/option" + "github.com/openai/openai-go/v3/responses" "github.com/sipeed/picoclaw/pkg/auth" ) @@ -79,7 +79,7 @@ func buildCodexParams(messages []Message, tools []ToolDefinition, model string, inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ CallID: msg.ToolCallID, - Output: msg.Content, + Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{OfString: openai.Opt(msg.Content)}, }, }) } else { @@ -122,7 +122,7 @@ func buildCodexParams(messages []Message, tools []ToolDefinition, model string, inputItems = append(inputItems, responses.ResponseInputItemUnionParam{ OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{ CallID: msg.ToolCallID, - Output: msg.Content, + Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{OfString: openai.Opt(msg.Content)}, }, }) } diff --git a/pkg/providers/codex_provider_test.go b/pkg/providers/codex_provider_test.go index e68a70b..605183d 100644 --- a/pkg/providers/codex_provider_test.go +++ b/pkg/providers/codex_provider_test.go @@ -6,9 +6,9 @@ import ( "net/http/httptest" "testing" - "github.com/openai/openai-go" - openaiopt "github.com/openai/openai-go/option" - "github.com/openai/openai-go/responses" + "github.com/openai/openai-go/v3" + openaiopt "github.com/openai/openai-go/v3/option" + "github.com/openai/openai-go/v3/responses" ) func TestBuildCodexParams_BasicMessage(t *testing.T) { From ca189588e6d62f389742db2ac83e2c89158a3808 Mon Sep 17 00:00:00 2001 From: Artem Yadelskyi Date: Wed, 11 Feb 2026 23:51:18 +0200 Subject: [PATCH 17/23] feat(telegram): Use Telego instead of go-telegram-bot-api --- go.mod | 17 ++++++- go.sum | 41 +++++++++++++-- pkg/channels/telegram.go | 105 +++++++++++++++++---------------------- 3 files changed, 98 insertions(+), 65 deletions(-) diff --git a/go.mod b/go.mod index 832f1e8..c9c9883 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,40 @@ module github.com/sipeed/picoclaw -go 1.24.0 +go 1.25.7 require ( github.com/adhocore/gronx v1.19.6 github.com/bwmarrin/discordgo v0.29.0 github.com/caarlos0/env/v11 v11.3.1 github.com/chzyer/readline v1.5.1 - github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/gorilla/websocket v1.5.3 github.com/larksuite/oapi-sdk-go/v3 v3.5.3 + github.com/mymmrac/telego v1.6.0 github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 github.com/tencent-connect/botgo v0.2.1 golang.org/x/oauth2 v0.35.0 ) require ( + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grbit/go-json v0.11.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.69.0 // indirect + github.com/valyala/fastjson v1.6.7 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect diff --git a/go.sum b/go.sum index f1ce926..7bace91 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,16 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc= github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -13,6 +21,8 @@ github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -25,8 +35,6 @@ github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= -github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -51,9 +59,15 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc= +github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -62,6 +76,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/larksuite/oapi-sdk-go/v3 v3.5.3 h1:xvf8Dv29kBXC5/DNDCLhHkAFW8l/0LlQJimO5Zn+JUk= github.com/larksuite/oapi-sdk-go/v3 v3.5.3/go.mod h1:ZEplY+kwuIrj/nqw5uSCINNATcH3KdxSN7y+UxYY5fI= +github.com/mymmrac/telego v1.6.0 h1:Zc8rgyHozvd/7ZgyrigyHdAF9koHYMfilYfyB6wlFC0= +github.com/mymmrac/telego v1.6.0/go.mod h1:xt6ZWA8zi8KmuzryE1ImEdl9JSwjHNpM4yhC7D8hU4Y= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -80,12 +96,15 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tencent-connect/botgo v0.2.1 h1:+BrTt9Zh+awL28GWC4g5Na3nQaGRWb0N5IctS8WqBCk= github.com/tencent-connect/botgo v0.2.1/go.mod h1:oO1sG9ybhXNickvt+CVym5khwQ+uKhTR+IhTqEfOVsI= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -97,9 +116,23 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= +github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= +github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= +github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 2a14127..cb83947 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -13,7 +13,8 @@ import ( "sync" "time" - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/mymmrac/telego" + tu "github.com/mymmrac/telego/telegoutil" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" @@ -22,17 +23,16 @@ import ( type TelegramChannel struct { *BaseChannel - bot *tgbotapi.BotAPI + bot *telego.Bot config config.TelegramConfig chatIDs map[string]int64 - updates tgbotapi.UpdatesChannel transcriber *voice.GroqTranscriber placeholders sync.Map // chatID -> messageID stopThinking sync.Map // chatID -> chan struct{} } func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*TelegramChannel, error) { - bot, err := tgbotapi.NewBotAPI(cfg.Token) + bot, err := telego.NewBot(cfg.Token) if err != nil { return nil, fmt.Errorf("failed to create telegram bot: %w", err) } @@ -57,19 +57,15 @@ func (c *TelegramChannel) SetTranscriber(transcriber *voice.GroqTranscriber) { func (c *TelegramChannel) Start(ctx context.Context) error { log.Printf("Starting Telegram bot (polling mode)...") - u := tgbotapi.NewUpdate(0) - u.Timeout = 30 - - updates := c.bot.GetUpdatesChan(u) - c.updates = updates + updates, err := c.bot.UpdatesViaLongPolling(ctx, &telego.GetUpdatesParams{ + Timeout: 30, + }) + if err != nil { + return fmt.Errorf("failed to start long polling: %w", err) + } c.setRunning(true) - - botInfo, err := c.bot.GetMe() - if err != nil { - return fmt.Errorf("failed to get bot info: %w", err) - } - log.Printf("Telegram bot @%s connected", botInfo.UserName) + log.Printf("Telegram bot @%s connected", c.bot.Username()) go func() { for { @@ -82,7 +78,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error { return } if update.Message != nil { - c.handleMessage(update) + c.handleMessage(ctx, update) } } } @@ -94,12 +90,6 @@ func (c *TelegramChannel) Start(ctx context.Context) error { func (c *TelegramChannel) Stop(ctx context.Context) error { log.Println("Stopping Telegram bot...") c.setRunning(false) - - if c.updates != nil { - c.bot.StopReceivingUpdates() - c.updates = nil - } - return nil } @@ -124,30 +114,29 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err // Try to edit placeholder if pID, ok := c.placeholders.Load(msg.ChatID); ok { c.placeholders.Delete(msg.ChatID) - editMsg := tgbotapi.NewEditMessageText(chatID, pID.(int), htmlContent) - editMsg.ParseMode = tgbotapi.ModeHTML + editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), htmlContent) + editMsg.ParseMode = telego.ModeHTML - if _, err := c.bot.Send(editMsg); err == nil { + if _, err = c.bot.EditMessageText(ctx, editMsg); err == nil { return nil } // Fallback to new message if edit fails } - tgMsg := tgbotapi.NewMessage(chatID, htmlContent) - tgMsg.ParseMode = tgbotapi.ModeHTML + tgMsg := tu.Message(tu.ID(chatID), htmlContent) + tgMsg.ParseMode = telego.ModeHTML - if _, err := c.bot.Send(tgMsg); err != nil { + if _, err = c.bot.SendMessage(ctx, tgMsg); err != nil { log.Printf("HTML parse failed, falling back to plain text: %v", err) - tgMsg = tgbotapi.NewMessage(chatID, msg.Content) tgMsg.ParseMode = "" - _, err = c.bot.Send(tgMsg) + _, err = c.bot.SendMessage(ctx, tgMsg) return err } return nil } -func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { +func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Update) { message := update.Message if message == nil { return @@ -159,8 +148,8 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { } senderID := fmt.Sprintf("%d", user.ID) - if user.UserName != "" { - senderID = fmt.Sprintf("%d|%s", user.ID, user.UserName) + if user.Username != "" { + senderID = fmt.Sprintf("%d|%s", user.ID, user.Username) } chatID := message.Chat.ID @@ -182,7 +171,7 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { if message.Photo != nil && len(message.Photo) > 0 { photo := message.Photo[len(message.Photo)-1] - photoPath := c.downloadPhoto(photo.FileID) + photoPath := c.downloadPhoto(ctx, photo.FileID) if photoPath != "" { mediaPaths = append(mediaPaths, photoPath) if content != "" { @@ -193,7 +182,7 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { } if message.Voice != nil { - voicePath := c.downloadFile(message.Voice.FileID, ".ogg") + voicePath := c.downloadFile(ctx, message.Voice.FileID, ".ogg") if voicePath != "" { mediaPaths = append(mediaPaths, voicePath) @@ -222,7 +211,7 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { } if message.Audio != nil { - audioPath := c.downloadFile(message.Audio.FileID, ".mp3") + audioPath := c.downloadFile(ctx, message.Audio.FileID, ".mp3") if audioPath != "" { mediaPaths = append(mediaPaths, audioPath) if content != "" { @@ -233,7 +222,7 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { } if message.Document != nil { - docPath := c.downloadFile(message.Document.FileID, "") + docPath := c.downloadFile(ctx, message.Document.FileID, "") if docPath != "" { mediaPaths = append(mediaPaths, docPath) if content != "" { @@ -250,12 +239,15 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { log.Printf("Telegram message from %s: %s...", senderID, truncateString(content, 50)) // Thinking indicator - c.bot.Send(tgbotapi.NewChatAction(chatID, tgbotapi.ChatTyping)) + err := c.bot.SendChatAction(ctx, tu.ChatAction(tu.ID(chatID), telego.ChatActionTyping)) + if err != nil { + log.Printf("Failed to send chat action: %v", err) + } stopChan := make(chan struct{}) c.stopThinking.Store(fmt.Sprintf("%d", chatID), stopChan) - pMsg, err := c.bot.Send(tgbotapi.NewMessage(chatID, "Thinking... ๐Ÿ’ญ")) + pMsg, err := c.bot.SendMessage(ctx, tu.Message(tu.ID(chatID), "Thinking... ๐Ÿ’ญ")) if err == nil { pID := pMsg.MessageID c.placeholders.Store(fmt.Sprintf("%d", chatID), pID) @@ -273,8 +265,10 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { case <-ticker.C: i++ text := fmt.Sprintf("Thinking%s %s", dots[i%len(dots)], emotes[i%len(emotes)]) - edit := tgbotapi.NewEditMessageText(cid, mid, text) - c.bot.Send(edit) + _, editErr := c.bot.EditMessageText(ctx, tu.EditMessageText(tu.ID(chatID), mid, text)) + if editErr != nil { + log.Printf("Failed to edit thinking message: %v", editErr) + } } } }(chatID, pID, stopChan) @@ -283,7 +277,7 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { metadata := map[string]string{ "message_id": fmt.Sprintf("%d", message.MessageID), "user_id": fmt.Sprintf("%d", user.ID), - "username": user.UserName, + "username": user.Username, "first_name": user.FirstName, "is_group": fmt.Sprintf("%t", message.Chat.Type != "private"), } @@ -291,22 +285,22 @@ func (c *TelegramChannel) handleMessage(update tgbotapi.Update) { c.HandleMessage(senderID, fmt.Sprintf("%d", chatID), content, mediaPaths, metadata) } -func (c *TelegramChannel) downloadPhoto(fileID string) string { - file, err := c.bot.GetFile(tgbotapi.FileConfig{FileID: fileID}) +func (c *TelegramChannel) downloadPhoto(ctx context.Context, fileID string) string { + file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID}) if err != nil { log.Printf("Failed to get photo file: %v", err) return "" } - return c.downloadFileWithInfo(&file, ".jpg") + return c.downloadFileWithInfo(file, ".jpg") } -func (c *TelegramChannel) downloadFileWithInfo(file *tgbotapi.File, ext string) string { +func (c *TelegramChannel) downloadFileWithInfo(file *telego.File, ext string) string { if file.FilePath == "" { return "" } - url := file.Link(c.bot.Token) + url := c.bot.FileDownloadURL(file.FilePath) log.Printf("File URL: %s", url) mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") @@ -325,13 +319,6 @@ func (c *TelegramChannel) downloadFileWithInfo(file *tgbotapi.File, ext string) return localPath } -func min(a, b int) int { - if a < b { - return a - } - return b -} - func (c *TelegramChannel) downloadFromURL(url, localPath string) error { resp, err := http.Get(url) if err != nil { @@ -358,8 +345,8 @@ func (c *TelegramChannel) downloadFromURL(url, localPath string) error { return nil } -func (c *TelegramChannel) downloadFile(fileID, ext string) string { - file, err := c.bot.GetFile(tgbotapi.FileConfig{FileID: fileID}) +func (c *TelegramChannel) downloadFile(ctx context.Context, fileID, ext string) string { + file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID}) if err != nil { log.Printf("Failed to get file: %v", err) return "" @@ -369,18 +356,18 @@ func (c *TelegramChannel) downloadFile(fileID, ext string) string { return "" } - url := file.Link(c.bot.Token) + url := c.bot.FileDownloadURL(file.FilePath) log.Printf("File URL: %s", url) mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") - if err := os.MkdirAll(mediaDir, 0755); err != nil { + if err = os.MkdirAll(mediaDir, 0755); err != nil { log.Printf("Failed to create media directory: %v", err) return "" } localPath := filepath.Join(mediaDir, fileID[:16]+ext) - if err := c.downloadFromURL(url, localPath); err != nil { + if err = c.downloadFromURL(url, localPath); err != nil { log.Printf("Failed to download file: %v", err) return "" } From af3f6596f9044942bdfb75a5183ec61d607a42fb Mon Sep 17 00:00:00 2001 From: seth <117851610+Sethispr@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:53:16 -0800 Subject: [PATCH 18/23] chore: lint readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 17 - MD012 / no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] [error] [Fix] 62 - MD012 / no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] [error] [Fix] 221 - MD012 / no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] [error] [Fix] 266 - MD012 / no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] [error] [Fix] 368 - MD012 / no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] [error] [Fix] 493 - MD012 / no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] [error] [Fix] 39 - MD022 / blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## ๐Ÿ“ข News"] [error] [Fix] 63 - MD022 / blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## ๐Ÿฆพ Demonstration"] [error] [Fix] 64 - MD022 / blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "### ๐Ÿ› ๏ธ Standard Assistant Workflows"] [error] [Fix] 64 - MD022 / blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### ๐Ÿ› ๏ธ Standard Assistant Workflows"] [error] [Fix] 83 - MD022 / blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### ๐Ÿœ Innovative Low-Footprint Deploy"] [error] [Fix] 218 - MD031 / blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```"] [error] [Fix] 296 - MD031 / blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```"] [error] [Fix] 329 - MD031 / blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```"] [error] [Fix] 401 - MD031 / blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```"] [error] [Fix] 503 - MD031 / blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```json"] [error] [Fix] 226 - MD032 / blanks-around-lists Lists should be surrounded by blank lines [Context: "- Go to https://discord.com/de..."] [error] [Fix] 231 - MD032 / blanks-around-lists Lists should be surrounded by blank lines [Context: "- In the Bot settings, enable ..."] [error] [Fix] 235 - MD032 / blanks-around-lists Lists should be surrounded by blank lines [Context: "- Discord Settings โ†’ Advanced ..."] [error] [Fix] 253 - MD032 / blanks-around-lists Lists should be surrounded by blank lines [Context: "- OAuth2 โ†’ URL Generator"] [error] [Fix] 373 - MD032 / blanks-around-lists Lists should be surrounded by blank lines [Context: "- Get [API key](https://bigmod..."] [error] [Fix] 501 - MD032 / blanks-around-lists Lists should be surrounded by blank lines [Context: "1. Get a free API key at [http..."] [error] [Fix] --- README.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9778918..6c9c4bd 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ - --- ๐Ÿฆ PicoClaw is an ultra-lightweight personal AI Assistant inspired by [nanobot](https://github.com/HKUDS/nanobot), refactored from the ground up in Go through a self-bootstrapping process, where the AI agent itself drove the entire architectural migration and code optimization. @@ -37,6 +36,7 @@ ## ๐Ÿ“ข News + 2026-02-09 ๐ŸŽ‰ PicoClaw Launched! Built in 1 day to bring AI Agents to $10 hardware with <10MB RAM. ๐Ÿฆ ็šฎ็šฎ่™พ๏ผŒๆˆ‘ไปฌ่ตฐ๏ผ ## โœจ Features @@ -57,11 +57,13 @@ | **RAM** | >1GB |>100MB| **< 10MB** | | **Startup**
    (0.8GHz core) | >500s | >30s | **<1s** | | **Cost** | Mac Mini 599$ | Most Linux SBC
    ~50$ |**Any Linux Board**
    **As low as 10$** | + PicoClaw - ## ๐Ÿฆพ Demonstration + ### ๐Ÿ› ๏ธ Standard Assistant Workflows + @@ -81,13 +83,14 @@

    ๐Ÿงฉ Full-Stack Engineer

    ### ๐Ÿœ Innovative Low-Footprint Deploy + PicoClaw can be deployed on almost any Linux device! - $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assistant - $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), or $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) for Automated Server Maintenance - $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) or $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) for Smart Monitoring -https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4 + ๐ŸŒŸ More Deployment Cases Await๏ผ @@ -216,22 +219,25 @@ Talk to your picoclaw through Telegram, Discord, or DingTalk ```bash picoclaw gateway ``` - +
    Discord **1. Create a bot** -- Go to https://discord.com/developers/applications + +- Go to - Create an application โ†’ Bot โ†’ Add Bot - Copy the bot token **2. Enable intents** + - In the Bot settings, enable **MESSAGE CONTENT INTENT** - (Optional) Enable **SERVER MEMBERS INTENT** if you plan to use allow lists based on member data **3. Get your User ID** + - Discord Settings โ†’ Advanced โ†’ enable **Developer Mode** - Right-click your avatar โ†’ **Copy User ID** @@ -250,6 +256,7 @@ picoclaw gateway ``` **5. Invite the bot** + - OAuth2 โ†’ URL Generator - Scopes: `bot` - Bot Permissions: `Send Messages`, `Read Message History` @@ -263,7 +270,6 @@ picoclaw gateway
    -
    QQ @@ -294,6 +300,7 @@ picoclaw gateway ```bash picoclaw gateway ``` +
    @@ -327,6 +334,7 @@ picoclaw gateway ```bash picoclaw gateway ``` +
    ## โš™๏ธ Configuration @@ -365,11 +373,11 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa | `deepseek(To be tested)` | LLM (DeepSeek direct) | [platform.deepseek.com](https://platform.deepseek.com) | | `groq` | LLM + **Voice transcription** (Whisper) | [console.groq.com](https://console.groq.com) | -
    Zhipu **1. Get API key and base URL** + - Get [API key](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) **2. Configure** @@ -399,6 +407,7 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa ```bash picoclaw agent -m "Hello" ``` +
    @@ -486,11 +495,10 @@ Jobs are stored in `~/.picoclaw/workspace/cron/` and processed automatically. PRs welcome! The codebase is intentionally small and readable. ๐Ÿค— -discord: https://discord.gg/V4sAZ9XWpN +discord: PicoClaw - ## ๐Ÿ› Troubleshooting ### Web search says "API ้…็ฝฎ้—ฎ้ข˜" @@ -498,8 +506,10 @@ discord: https://discord.gg/V4sAZ9XWpN This is normal if you haven't configured a search API key yet. PicoClaw will provide helpful links for manual searching. To enable web search: + 1. Get a free API key at [https://brave.com/search/api](https://brave.com/search/api) (2000 free queries/month) 2. Add to `~/.picoclaw/config.json`: + ```json { "tools": { From 8ceef6e8a7750473c0bd185c2c69e2f597479527 Mon Sep 17 00:00:00 2001 From: li Date: Thu, 12 Feb 2026 08:14:49 +0800 Subject: [PATCH 19/23] better version --- Makefile | 3 ++- cmd/picoclaw/main.go | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 9cc2354..7babf6c 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,8 @@ MAIN_GO=$(CMD_DIR)/main.go # Version VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") BUILD_TIME=$(shell date +%FT%T%z) -LDFLAGS=-ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME)" +GO_VERSION=$(shell $(GO) version | awk '{print $$3}') +LDFLAGS=-ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.goVersion=$(GO_VERSION)" # Go variables GO?=go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index c14ec58..d443998 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -14,6 +14,7 @@ import ( "os" "os/signal" "path/filepath" + "runtime" "strings" "time" @@ -31,9 +32,28 @@ import ( "github.com/sipeed/picoclaw/pkg/voice" ) -const version = "0.1.0" +var ( + version = "0.1.0" + buildTime string + goVersion string +) + const logo = "๐Ÿฆž" +func printVersion() { + fmt.Printf("%s picoclaw v%s\n", logo, version) + if buildTime != "" { + fmt.Printf(" Build: %s\n", buildTime) + } + goVer := goVersion + if goVer == "" { + goVer = runtime.Version() + } + if goVer != "" { + fmt.Printf(" Go: %s\n", goVer) + } +} + func copyDirectory(src, dst string) error { return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -137,7 +157,7 @@ func main() { skillsHelp() } case "version", "--version", "-v": - fmt.Printf("%s picoclaw v%s\n", logo, version) + printVersion() default: fmt.Printf("Unknown command: %s\n", command) printHelp() @@ -771,7 +791,7 @@ func cronHelp() { func cronListCmd(storePath string) { cs := cron.NewCronService(storePath, nil) - jobs := cs.ListJobs(true) // Show all jobs, including disabled + jobs := cs.ListJobs(true) // Show all jobs, including disabled if len(jobs) == 0 { fmt.Println("No scheduled jobs.") From f4a8ff7571a81b5dc163574649c0f6ce280df631 Mon Sep 17 00:00:00 2001 From: Wutachi Date: Thu, 12 Feb 2026 01:01:23 -0300 Subject: [PATCH 20/23] Add provider field support for explicit provider selection - Add Provider field to AgentDefaults struct - Modify CreateProvider to use explicit provider field first, fallback to model name detection - Allows using models without provider prefix (e.g., llama-3.1-8b-instant instead of groq/llama-3.1-8b-instant) - Supports all providers: groq, openai, anthropic, openrouter, zhipu, gemini, vllm - Backward compatible with existing configs Fixes issue where models without provider prefix could not use configured API keys. --- pkg/config/config.go | 10 +++-- pkg/providers/http_provider.go | 75 ++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 7fc6253..6f1c86b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -24,6 +24,7 @@ type AgentsConfig struct { type AgentDefaults struct { Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"` + Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"` Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` @@ -82,10 +83,10 @@ type QQConfig struct { } type DingTalkConfig struct { - Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DINGTALK_ENABLED"` - ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_ID"` - ClientSecret string `json:"client_secret" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_SECRET"` - AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"` + Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DINGTALK_ENABLED"` + ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_ID"` + ClientSecret string `json:"client_secret" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_SECRET"` + AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"` } type ProvidersConfig struct { @@ -127,6 +128,7 @@ func DefaultConfig() *Config { Agents: AgentsConfig{ Defaults: AgentDefaults{ Workspace: "~/.picoclaw/workspace", + Provider: "", Model: "glm-4.7", MaxTokens: 8192, Temperature: 0.7, diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index f63c68c..e982e09 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -194,13 +194,81 @@ func createCodexAuthProvider() (LLMProvider, error) { func CreateProvider(cfg *config.Config) (LLMProvider, error) { model := cfg.Agents.Defaults.Model + providerName := strings.ToLower(cfg.Agents.Defaults.Provider) var apiKey, apiBase string lowerModel := strings.ToLower(model) - switch { - case strings.HasPrefix(model, "openrouter/") || strings.HasPrefix(model, "anthropic/") || strings.HasPrefix(model, "openai/") || strings.HasPrefix(model, "meta-llama/") || strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "google/"): + // First, try to use explicitly configured provider + if providerName != "" { + switch providerName { + case "groq": + if cfg.Providers.Groq.APIKey != "" { + apiKey = cfg.Providers.Groq.APIKey + apiBase = cfg.Providers.Groq.APIBase + if apiBase == "" { + apiBase = "https://api.groq.com/openai/v1" + } + } + case "openai", "gpt": + if cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != "" { + if cfg.Providers.OpenAI.AuthMethod == "oauth" || cfg.Providers.OpenAI.AuthMethod == "token" { + return createCodexAuthProvider() + } + apiKey = cfg.Providers.OpenAI.APIKey + apiBase = cfg.Providers.OpenAI.APIBase + if apiBase == "" { + apiBase = "https://api.openai.com/v1" + } + } + case "anthropic", "claude": + if cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != "" { + if cfg.Providers.Anthropic.AuthMethod == "oauth" || cfg.Providers.Anthropic.AuthMethod == "token" { + return createClaudeAuthProvider() + } + apiKey = cfg.Providers.Anthropic.APIKey + apiBase = cfg.Providers.Anthropic.APIBase + if apiBase == "" { + apiBase = "https://api.anthropic.com/v1" + } + } + case "openrouter": + if cfg.Providers.OpenRouter.APIKey != "" { + apiKey = cfg.Providers.OpenRouter.APIKey + if cfg.Providers.OpenRouter.APIBase != "" { + apiBase = cfg.Providers.OpenRouter.APIBase + } else { + apiBase = "https://openrouter.ai/api/v1" + } + } + case "zhipu", "glm": + if cfg.Providers.Zhipu.APIKey != "" { + apiKey = cfg.Providers.Zhipu.APIKey + apiBase = cfg.Providers.Zhipu.APIBase + if apiBase == "" { + apiBase = "https://open.bigmodel.cn/api/paas/v4" + } + } + case "gemini", "google": + if cfg.Providers.Gemini.APIKey != "" { + apiKey = cfg.Providers.Gemini.APIKey + apiBase = cfg.Providers.Gemini.APIBase + if apiBase == "" { + apiBase = "https://generativelanguage.googleapis.com/v1beta" + } + } + case "vllm": + if cfg.Providers.VLLM.APIBase != "" { + apiKey = cfg.Providers.VLLM.APIKey + apiBase = cfg.Providers.VLLM.APIBase + } + } + } + + // Fallback: detect provider from model name + if apiKey == "" && apiBase == "" { + switch { case strings.HasPrefix(model, "openrouter/") || strings.HasPrefix(model, "anthropic/") || strings.HasPrefix(model, "openai/") || strings.HasPrefix(model, "meta-llama/") || strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "google/"): apiKey = cfg.Providers.OpenRouter.APIKey if cfg.Providers.OpenRouter.APIBase != "" { apiBase = cfg.Providers.OpenRouter.APIBase @@ -265,6 +333,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { return nil, fmt.Errorf("no API key configured for model: %s", model) } } + } if apiKey == "" && !strings.HasPrefix(model, "bedrock/") { return nil, fmt.Errorf("no API key configured for provider (model: %s)", model) @@ -275,4 +344,4 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } return NewHTTPProvider(apiKey, apiBase), nil -} \ No newline at end of file +} From 5c8626f07bda41a5ab5197dfc9fff78688cec08e Mon Sep 17 00:00:00 2001 From: yinwm Date: Thu, 12 Feb 2026 12:46:28 +0800 Subject: [PATCH 21/23] refactor(channels): consolidate media handling and improve resource cleanup Extract common file download and audio detection logic to utils package, implement consistent temp file cleanup with defer, add allowlist checks before downloading attachments, and improve context management across Discord, Slack, and Telegram channels. Replace logging with structured logger and prevent context leaks in transcription and thinking animations. --- go.mod | 12 +-- go.sum | 18 ++-- pkg/channels/dingtalk.go | 27 +++-- pkg/channels/discord.go | 175 ++++++++++++++++----------------- pkg/channels/slack.go | 114 +++++++--------------- pkg/channels/slack_test.go | 19 ---- pkg/channels/telegram.go | 195 +++++++++++++++++++------------------ pkg/utils/media.go | 143 +++++++++++++++++++++++++++ 8 files changed, 402 insertions(+), 301 deletions(-) create mode 100644 pkg/utils/media.go diff --git a/go.mod b/go.mod index 362784e..f4c233e 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,13 @@ require ( github.com/bwmarrin/discordgo v0.29.0 github.com/caarlos0/env/v11 v11.3.1 github.com/chzyer/readline v1.5.1 + github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/larksuite/oapi-sdk-go/v3 v3.5.3 github.com/mymmrac/telego v1.6.0 github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 - github.com/slack-go/slack v0.17.3 github.com/openai/openai-go/v3 v3.21.0 + github.com/slack-go/slack v0.17.3 github.com/tencent-connect/botgo v0.2.1 golang.org/x/oauth2 v0.35.0 ) @@ -26,19 +27,18 @@ require ( github.com/cloudwego/base64x v0.1.6 // indirect github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/grbit/go-json v0.11.0 // indirect - github.com/klauspost/compress v1.18.2 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/compress v1.18.4 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.69.0 // indirect github.com/valyala/fastjson v1.6.7 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - github.com/tidwall/sjson v1.2.5 // indirect + golang.org/x/arch v0.24.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect diff --git a/go.sum b/go.sum index c6484ef..9174d28 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -66,10 +68,10 @@ github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/Z github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -123,6 +125,8 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -133,15 +137,13 @@ github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpB github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= +golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/pkg/channels/dingtalk.go b/pkg/channels/dingtalk.go index 78491e7..5c6f29f 100644 --- a/pkg/channels/dingtalk.go +++ b/pkg/channels/dingtalk.go @@ -6,13 +6,13 @@ package channels import ( "context" "fmt" - "log" "sync" "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/utils" ) @@ -48,7 +48,7 @@ func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) ( // Start initializes the DingTalk channel with Stream Mode func (c *DingTalkChannel) Start(ctx context.Context) error { - log.Printf("Starting DingTalk channel (Stream Mode)...") + logger.InfoC("dingtalk", "Starting DingTalk channel (Stream Mode)...") c.ctx, c.cancel = context.WithCancel(ctx) @@ -70,13 +70,13 @@ func (c *DingTalkChannel) Start(ctx context.Context) error { } c.setRunning(true) - log.Println("DingTalk channel started (Stream Mode)") + logger.InfoC("dingtalk", "DingTalk channel started (Stream Mode)") return nil } // Stop gracefully stops the DingTalk channel func (c *DingTalkChannel) Stop(ctx context.Context) error { - log.Println("Stopping DingTalk channel...") + logger.InfoC("dingtalk", "Stopping DingTalk channel...") if c.cancel != nil { c.cancel() @@ -87,7 +87,7 @@ func (c *DingTalkChannel) Stop(ctx context.Context) error { } c.setRunning(false) - log.Println("DingTalk channel stopped") + logger.InfoC("dingtalk", "DingTalk channel stopped") return nil } @@ -108,10 +108,13 @@ func (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) err return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID) } - log.Printf("DingTalk message to %s: %s", msg.ChatID, utils.Truncate(msg.Content, 100)) + logger.DebugCF("dingtalk", "Sending message", map[string]interface{}{ + "chat_id": msg.ChatID, + "preview": utils.Truncate(msg.Content, 100), + }) // Use the session webhook to send the reply - return c.SendDirectReply(sessionWebhook, msg.Content) + return c.SendDirectReply(ctx, sessionWebhook, msg.Content) } // onChatBotMessageReceived implements the IChatBotMessageHandler function signature @@ -152,7 +155,11 @@ func (c *DingTalkChannel) onChatBotMessageReceived(ctx context.Context, data *ch "session_webhook": data.SessionWebhook, } - log.Printf("DingTalk message from %s (%s): %s", senderNick, senderID, utils.Truncate(content, 50)) + logger.DebugCF("dingtalk", "Received message", map[string]interface{}{ + "sender_nick": senderNick, + "sender_id": senderID, + "preview": utils.Truncate(content, 50), + }) // Handle the message through the base channel c.HandleMessage(senderID, chatID, content, nil, metadata) @@ -163,7 +170,7 @@ func (c *DingTalkChannel) onChatBotMessageReceived(ctx context.Context, data *ch } // SendDirectReply sends a direct reply using the session webhook -func (c *DingTalkChannel) SendDirectReply(sessionWebhook, content string) error { +func (c *DingTalkChannel) SendDirectReply(ctx context.Context, sessionWebhook, content string) error { replier := chatbot.NewChatbotReplier() // Convert string content to []byte for the API @@ -172,7 +179,7 @@ func (c *DingTalkChannel) SendDirectReply(sessionWebhook, content string) error // Send markdown formatted reply err := replier.SimpleReplyMarkdown( - context.Background(), + ctx, sessionWebhook, titleBytes, contentBytes, diff --git a/pkg/channels/discord.go b/pkg/channels/discord.go index 67e8d30..e65c99e 100644 --- a/pkg/channels/discord.go +++ b/pkg/channels/discord.go @@ -3,12 +3,7 @@ package channels import ( "context" "fmt" - "io" - "log" - "net/http" "os" - "path/filepath" - "strings" "time" "github.com/bwmarrin/discordgo" @@ -19,11 +14,17 @@ import ( "github.com/sipeed/picoclaw/pkg/voice" ) +const ( + transcriptionTimeout = 30 * time.Second + sendTimeout = 10 * time.Second +) + type DiscordChannel struct { *BaseChannel session *discordgo.Session config config.DiscordConfig transcriber *voice.GroqTranscriber + ctx context.Context } func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) { @@ -39,6 +40,7 @@ func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordC session: session, config: cfg, transcriber: nil, + ctx: context.Background(), }, nil } @@ -46,9 +48,17 @@ func (c *DiscordChannel) SetTranscriber(transcriber *voice.GroqTranscriber) { c.transcriber = transcriber } +func (c *DiscordChannel) getContext() context.Context { + if c.ctx == nil { + return context.Background() + } + return c.ctx +} + func (c *DiscordChannel) Start(ctx context.Context) error { logger.InfoC("discord", "Starting Discord bot") + c.ctx = ctx c.session.AddHandler(c.handleMessage) if err := c.session.Open(); err != nil { @@ -61,7 +71,7 @@ func (c *DiscordChannel) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to get bot user: %w", err) } - logger.InfoCF("discord", "Discord bot connected", map[string]interface{}{ + logger.InfoCF("discord", "Discord bot connected", map[string]any{ "username": botUser.Username, "user_id": botUser.ID, }) @@ -92,11 +102,33 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro message := msg.Content - if _, err := c.session.ChannelMessageSend(channelID, message); err != nil { - return fmt.Errorf("failed to send discord message: %w", err) - } + // ไฝฟ็”จไผ ๅ…ฅ็š„ ctx ่ฟ›่กŒ่ถ…ๆ—ถๆŽงๅˆถ + sendCtx, cancel := context.WithTimeout(ctx, sendTimeout) + defer cancel() - return nil + done := make(chan error, 1) + go func() { + _, err := c.session.ChannelMessageSend(channelID, message) + done <- err + }() + + select { + case err := <-done: + if err != nil { + return fmt.Errorf("failed to send discord message: %w", err) + } + return nil + case <-sendCtx.Done(): + return fmt.Errorf("send message timeout: %w", sendCtx.Err()) + } +} + +// appendContent ๅฎ‰ๅ…จๅœฐ่ฟฝๅŠ ๅ†…ๅฎนๅˆฐ็Žฐๆœ‰ๆ–‡ๆœฌ +func appendContent(content, suffix string) string { + if content == "" { + return suffix + } + return content + "\n" + suffix } func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.MessageCreate) { @@ -108,6 +140,14 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag return } + // ๆฃ€ๆŸฅ็™ฝๅๅ•๏ผŒ้ฟๅ…ไธบ่ขซๆ‹’็ป็š„็”จๆˆทไธ‹่ฝฝ้™„ไปถๅ’Œ่ฝฌๅฝ• + if !c.IsAllowed(m.Author.ID) { + logger.DebugCF("discord", "Message rejected by allowlist", map[string]any{ + "user_id": m.Author.ID, + }) + return + } + senderID := m.Author.ID senderName := m.Author.Username if m.Author.Discriminator != "" && m.Author.Discriminator != "0" { @@ -115,50 +155,62 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag } content := m.Content - mediaPaths := []string{} + mediaPaths := make([]string, 0, len(m.Attachments)) + localFiles := make([]string, 0, len(m.Attachments)) + + // ็กฎไฟไธดๆ—ถๆ–‡ไปถๅœจๅ‡ฝๆ•ฐ่ฟ”ๅ›žๆ—ถ่ขซๆธ…็† + defer func() { + for _, file := range localFiles { + if err := os.Remove(file); err != nil { + logger.DebugCF("discord", "Failed to cleanup temp file", map[string]any{ + "file": file, + "error": err.Error(), + }) + } + } + }() for _, attachment := range m.Attachments { - isAudio := isAudioFile(attachment.Filename, attachment.ContentType) + isAudio := utils.IsAudioFile(attachment.Filename, attachment.ContentType) if isAudio { localPath := c.downloadAttachment(attachment.URL, attachment.Filename) if localPath != "" { - mediaPaths = append(mediaPaths, localPath) + localFiles = append(localFiles, localPath) transcribedText := "" if c.transcriber != nil && c.transcriber.IsAvailable() { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - + ctx, cancel := context.WithTimeout(c.getContext(), transcriptionTimeout) result, err := c.transcriber.Transcribe(ctx, localPath) + cancel() // ็ซ‹ๅณ้‡Šๆ”พcontext่ต„ๆบ๏ผŒ้ฟๅ…ๅœจforๅพช็Žฏไธญๆณ„ๆผ + if err != nil { - log.Printf("Voice transcription failed: %v", err) - transcribedText = fmt.Sprintf("[audio: %s (transcription failed)]", localPath) + logger.ErrorCF("discord", "Voice transcription failed", map[string]any{ + "error": err.Error(), + }) + transcribedText = fmt.Sprintf("[audio: %s (transcription failed)]", attachment.Filename) } else { transcribedText = fmt.Sprintf("[audio transcription: %s]", result.Text) - log.Printf("Audio transcribed successfully: %s", result.Text) + logger.DebugCF("discord", "Audio transcribed successfully", map[string]any{ + "text": result.Text, + }) } } else { - transcribedText = fmt.Sprintf("[audio: %s]", localPath) + transcribedText = fmt.Sprintf("[audio: %s]", attachment.Filename) } - if content != "" { - content += "\n" - } - content += transcribedText + content = appendContent(content, transcribedText) } else { + logger.WarnCF("discord", "Failed to download audio attachment", map[string]any{ + "url": attachment.URL, + "filename": attachment.Filename, + }) mediaPaths = append(mediaPaths, attachment.URL) - if content != "" { - content += "\n" - } - content += fmt.Sprintf("[attachment: %s]", attachment.URL) + content = appendContent(content, fmt.Sprintf("[attachment: %s]", attachment.URL)) } } else { mediaPaths = append(mediaPaths, attachment.URL) - if content != "" { - content += "\n" - } - content += fmt.Sprintf("[attachment: %s]", attachment.URL) + content = appendContent(content, fmt.Sprintf("[attachment: %s]", attachment.URL)) } } @@ -170,7 +222,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag content = "[media only]" } - logger.DebugCF("discord", "Received message", map[string]interface{}{ + logger.DebugCF("discord", "Received message", map[string]any{ "sender_name": senderName, "sender_id": senderID, "preview": utils.Truncate(content, 50), @@ -189,59 +241,8 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag c.HandleMessage(senderID, m.ChannelID, content, mediaPaths, metadata) } -func isAudioFile(filename, contentType string) bool { - audioExtensions := []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"} - audioTypes := []string{"audio/", "application/ogg", "application/x-ogg"} - - for _, ext := range audioExtensions { - if strings.HasSuffix(strings.ToLower(filename), ext) { - return true - } - } - - for _, audioType := range audioTypes { - if strings.HasPrefix(strings.ToLower(contentType), audioType) { - return true - } - } - - return false -} - func (c *DiscordChannel) downloadAttachment(url, filename string) string { - mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") - if err := os.MkdirAll(mediaDir, 0755); err != nil { - log.Printf("Failed to create media directory: %v", err) - return "" - } - - localPath := filepath.Join(mediaDir, filename) - - resp, err := http.Get(url) - if err != nil { - log.Printf("Failed to download attachment: %v", err) - return "" - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - log.Printf("Failed to download attachment, status: %d", resp.StatusCode) - return "" - } - - out, err := os.Create(localPath) - if err != nil { - log.Printf("Failed to create file: %v", err) - return "" - } - defer out.Close() - - _, err = io.Copy(out, resp.Body) - if err != nil { - log.Printf("Failed to write file: %v", err) - return "" - } - - log.Printf("Attachment downloaded successfully to: %s", localPath) - return localPath + return utils.DownloadFile(url, filename, utils.DownloadOptions{ + LoggerPrefix: "discord", + }) } diff --git a/pkg/channels/slack.go b/pkg/channels/slack.go index 9595453..b3ac12e 100644 --- a/pkg/channels/slack.go +++ b/pkg/channels/slack.go @@ -3,10 +3,7 @@ package channels import ( "context" "fmt" - "io" - "net/http" "os" - "path/filepath" "strings" "sync" "time" @@ -18,6 +15,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/utils" "github.com/sipeed/picoclaw/pkg/voice" ) @@ -186,10 +184,6 @@ func (c *SlackChannel) handleEventsAPI(event socketmode.Event) { c.handleMessageEvent(ev) case *slackevents.AppMentionEvent: c.handleAppMention(ev) - case *slackevents.ReactionAddedEvent: - c.handleReactionAdded(ev) - case *slackevents.ReactionRemovedEvent: - c.handleReactionRemoved(ev) } } @@ -204,6 +198,14 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { return } + // ๆฃ€ๆŸฅ็™ฝๅๅ•๏ผŒ้ฟๅ…ไธบ่ขซๆ‹’็ป็š„็”จๆˆทไธ‹่ฝฝ้™„ไปถ + if !c.IsAllowed(ev.User) { + logger.DebugCF("slack", "Message rejected by allowlist", map[string]interface{}{ + "user_id": ev.User, + }) + return + } + senderID := ev.User channelID := ev.Channel threadTS := ev.ThreadTimeStamp @@ -228,6 +230,19 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { content = c.stripBotMention(content) var mediaPaths []string + localFiles := []string{} // ่ทŸ่ธช้œ€่ฆๆธ…็†็š„ๆœฌๅœฐๆ–‡ไปถ + + // ็กฎไฟไธดๆ—ถๆ–‡ไปถๅœจๅ‡ฝๆ•ฐ่ฟ”ๅ›žๆ—ถ่ขซๆธ…็† + defer func() { + for _, file := range localFiles { + if err := os.Remove(file); err != nil { + logger.DebugCF("slack", "Failed to cleanup temp file", map[string]interface{}{ + "file": file, + "error": err.Error(), + }) + } + } + }() if ev.Message != nil && len(ev.Message.Files) > 0 { for _, file := range ev.Message.Files { @@ -235,12 +250,13 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { if localPath == "" { continue } + localFiles = append(localFiles, localPath) mediaPaths = append(mediaPaths, localPath) - if isAudioFile(file.Name, file.Mimetype) && c.transcriber != nil && c.transcriber.IsAvailable() { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + if utils.IsAudioFile(file.Name, file.Mimetype) && c.transcriber != nil && c.transcriber.IsAvailable() { + ctx, cancel := context.WithTimeout(c.ctx, 30*time.Second) + defer cancel() result, err := c.transcriber.Transcribe(ctx, localPath) - cancel() if err != nil { logger.ErrorCF("slack", "Voice transcription failed", map[string]interface{}{"error": err.Error()}) @@ -266,9 +282,9 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { } logger.DebugCF("slack", "Received message", map[string]interface{}{ - "sender_id": senderID, - "chat_id": chatID, - "preview": truncateStringSlack(content, 50), + "sender_id": senderID, + "chat_id": chatID, + "preview": utils.Truncate(content, 50), "has_thread": threadTS != "", }) @@ -348,35 +364,13 @@ func (c *SlackChannel) handleSlashCommand(event socketmode.Event) { logger.DebugCF("slack", "Slash command received", map[string]interface{}{ "sender_id": senderID, "command": cmd.Command, - "text": truncateStringSlack(content, 50), + "text": utils.Truncate(content, 50), }) c.HandleMessage(senderID, chatID, content, nil, metadata) } -func (c *SlackChannel) handleReactionAdded(ev *slackevents.ReactionAddedEvent) { - logger.DebugCF("slack", "Reaction added", map[string]interface{}{ - "reaction": ev.Reaction, - "user": ev.User, - "item_ts": ev.Item.Timestamp, - }) -} - -func (c *SlackChannel) handleReactionRemoved(ev *slackevents.ReactionRemovedEvent) { - logger.DebugCF("slack", "Reaction removed", map[string]interface{}{ - "reaction": ev.Reaction, - "user": ev.User, - "item_ts": ev.Item.Timestamp, - }) -} - func (c *SlackChannel) downloadSlackFile(file slack.File) string { - mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") - if err := os.MkdirAll(mediaDir, 0755); err != nil { - logger.ErrorCF("slack", "Failed to create media directory", map[string]interface{}{"error": err.Error()}) - return "" - } - downloadURL := file.URLPrivateDownload if downloadURL == "" { downloadURL = file.URLPrivate @@ -386,41 +380,12 @@ func (c *SlackChannel) downloadSlackFile(file slack.File) string { return "" } - localPath := filepath.Join(mediaDir, file.Name) - - req, err := http.NewRequest("GET", downloadURL, nil) - if err != nil { - logger.ErrorCF("slack", "Failed to create download request", map[string]interface{}{"error": err.Error()}) - return "" - } - req.Header.Set("Authorization", "Bearer "+c.config.BotToken) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - logger.ErrorCF("slack", "Failed to download file", map[string]interface{}{"error": err.Error()}) - return "" - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - logger.ErrorCF("slack", "File download returned non-200 status", map[string]interface{}{"status": resp.StatusCode}) - return "" - } - - out, err := os.Create(localPath) - if err != nil { - logger.ErrorCF("slack", "Failed to create local file", map[string]interface{}{"error": err.Error()}) - return "" - } - defer out.Close() - - if _, err := io.Copy(out, resp.Body); err != nil { - logger.ErrorCF("slack", "Failed to write file", map[string]interface{}{"error": err.Error()}) - return "" - } - - logger.DebugCF("slack", "File downloaded", map[string]interface{}{"path": localPath, "name": file.Name}) - return localPath + return utils.DownloadFile(downloadURL, file.Name, utils.DownloadOptions{ + LoggerPrefix: "slack", + ExtraHeaders: map[string]string{ + "Authorization": "Bearer " + c.config.BotToken, + }, + }) } func (c *SlackChannel) stripBotMention(text string) string { @@ -437,10 +402,3 @@ func parseSlackChatID(chatID string) (channelID, threadTS string) { } return } - -func truncateStringSlack(s string, maxLen int) string { - if len(s) <= maxLen { - return s - } - return s[:maxLen] -} diff --git a/pkg/channels/slack_test.go b/pkg/channels/slack_test.go index 3de8e50..3707c27 100644 --- a/pkg/channels/slack_test.go +++ b/pkg/channels/slack_test.go @@ -172,22 +172,3 @@ func TestSlackChannelIsAllowed(t *testing.T) { } }) } - -func TestTruncateStringSlack(t *testing.T) { - tests := []struct { - input string - maxLen int - want string - }{ - {"hello", 10, "hello"}, - {"hello world", 5, "hello"}, - {"", 5, ""}, - } - - for _, tt := range tests { - got := truncateStringSlack(tt.input, tt.maxLen) - if got != tt.want { - t.Errorf("truncateStringSlack(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want) - } - } -} diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 1c1b99d..95f6102 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -3,11 +3,7 @@ package channels import ( "context" "fmt" - "io" - "log" - "net/http" "os" - "path/filepath" "regexp" "strings" "sync" @@ -18,6 +14,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/utils" "github.com/sipeed/picoclaw/pkg/voice" ) @@ -29,7 +26,17 @@ type TelegramChannel struct { chatIDs map[string]int64 transcriber *voice.GroqTranscriber placeholders sync.Map // chatID -> messageID - stopThinking sync.Map // chatID -> chan struct{} + stopThinking sync.Map // chatID -> thinkingCancel +} + +type thinkingCancel struct { + fn context.CancelFunc +} + +func (c *thinkingCancel) Cancel() { + if c != nil && c.fn != nil { + c.fn() + } } func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*TelegramChannel, error) { @@ -56,7 +63,7 @@ func (c *TelegramChannel) SetTranscriber(transcriber *voice.GroqTranscriber) { } func (c *TelegramChannel) Start(ctx context.Context) error { - log.Printf("Starting Telegram bot (polling mode)...") + logger.InfoC("telegram", "Starting Telegram bot (polling mode)...") updates, err := c.bot.UpdatesViaLongPolling(ctx, &telego.GetUpdatesParams{ Timeout: 30, @@ -66,7 +73,9 @@ func (c *TelegramChannel) Start(ctx context.Context) error { } c.setRunning(true) - log.Printf("Telegram bot @%s connected", c.bot.Username()) + logger.InfoCF("telegram", "Telegram bot connected", map[string]interface{}{ + "username": c.bot.Username(), + }) go func() { for { @@ -75,7 +84,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error { return case update, ok := <-updates: if !ok { - log.Printf("Updates channel closed, reconnecting...") + logger.InfoC("telegram", "Updates channel closed, reconnecting...") return } if update.Message != nil { @@ -89,7 +98,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error { } func (c *TelegramChannel) Stop(ctx context.Context) error { - log.Println("Stopping Telegram bot...") + logger.InfoC("telegram", "Stopping Telegram bot...") c.setRunning(false) return nil } @@ -106,7 +115,9 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err // Stop thinking animation if stop, ok := c.stopThinking.Load(msg.ChatID); ok { - close(stop.(chan struct{})) + if cf, ok := stop.(*thinkingCancel); ok && cf != nil { + cf.Cancel() + } c.stopThinking.Delete(msg.ChatID) } @@ -128,7 +139,9 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err tgMsg.ParseMode = telego.ModeHTML if _, err = c.bot.SendMessage(ctx, tgMsg); err != nil { - log.Printf("HTML parse failed, falling back to plain text: %v", err) + logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]interface{}{ + "error": err.Error(), + }) tgMsg.ParseMode = "" _, err = c.bot.SendMessage(ctx, tgMsg) return err @@ -153,11 +166,32 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat senderID = fmt.Sprintf("%d|%s", user.ID, user.Username) } + // ๆฃ€ๆŸฅ็™ฝๅๅ•๏ผŒ้ฟๅ…ไธบ่ขซๆ‹’็ป็š„็”จๆˆทไธ‹่ฝฝ้™„ไปถ + if !c.IsAllowed(senderID) { + logger.DebugCF("telegram", "Message rejected by allowlist", map[string]interface{}{ + "user_id": senderID, + }) + return + } + chatID := message.Chat.ID c.chatIDs[senderID] = chatID content := "" mediaPaths := []string{} + localFiles := []string{} // ่ทŸ่ธช้œ€่ฆๆธ…็†็š„ๆœฌๅœฐๆ–‡ไปถ + + // ็กฎไฟไธดๆ—ถๆ–‡ไปถๅœจๅ‡ฝๆ•ฐ่ฟ”ๅ›žๆ—ถ่ขซๆธ…็† + defer func() { + for _, file := range localFiles { + if err := os.Remove(file); err != nil { + logger.DebugCF("telegram", "Failed to cleanup temp file", map[string]interface{}{ + "file": file, + "error": err.Error(), + }) + } + } + }() if message.Text != "" { content += message.Text @@ -174,34 +208,41 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat photo := message.Photo[len(message.Photo)-1] photoPath := c.downloadPhoto(ctx, photo.FileID) if photoPath != "" { + localFiles = append(localFiles, photoPath) mediaPaths = append(mediaPaths, photoPath) if content != "" { content += "\n" } - content += fmt.Sprintf("[image: %s]", photoPath) + content += fmt.Sprintf("[image: photo]") } } if message.Voice != nil { voicePath := c.downloadFile(ctx, message.Voice.FileID, ".ogg") if voicePath != "" { + localFiles = append(localFiles, voicePath) mediaPaths = append(mediaPaths, voicePath) transcribedText := "" if c.transcriber != nil && c.transcriber.IsAvailable() { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() result, err := c.transcriber.Transcribe(ctx, voicePath) if err != nil { - log.Printf("Voice transcription failed: %v", err) - transcribedText = fmt.Sprintf("[voice: %s (transcription failed)]", voicePath) + logger.ErrorCF("telegram", "Voice transcription failed", map[string]interface{}{ + "error": err.Error(), + "path": voicePath, + }) + transcribedText = fmt.Sprintf("[voice (transcription failed)]") } else { transcribedText = fmt.Sprintf("[voice transcription: %s]", result.Text) - log.Printf("Voice transcribed successfully: %s", result.Text) + logger.InfoCF("telegram", "Voice transcribed successfully", map[string]interface{}{ + "text": result.Text, + }) } } else { - transcribedText = fmt.Sprintf("[voice: %s]", voicePath) + transcribedText = fmt.Sprintf("[voice]") } if content != "" { @@ -214,22 +255,24 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat if message.Audio != nil { audioPath := c.downloadFile(ctx, message.Audio.FileID, ".mp3") if audioPath != "" { + localFiles = append(localFiles, audioPath) mediaPaths = append(mediaPaths, audioPath) if content != "" { content += "\n" } - content += fmt.Sprintf("[audio: %s]", audioPath) + content += fmt.Sprintf("[audio]") } } if message.Document != nil { docPath := c.downloadFile(ctx, message.Document.FileID, "") if docPath != "" { + localFiles = append(localFiles, docPath) mediaPaths = append(mediaPaths, docPath) if content != "" { content += "\n" } - content += fmt.Sprintf("[file: %s]", docPath) + content += fmt.Sprintf("[file]") } } @@ -237,23 +280,38 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat content = "[empty message]" } - log.Printf("Telegram message from %s: %s...", senderID, utils.Truncate(content, 50)) + logger.DebugCF("telegram", "Received message", map[string]interface{}{ + "sender_id": senderID, + "chat_id": fmt.Sprintf("%d", chatID), + "preview": utils.Truncate(content, 50), + }) // Thinking indicator err := c.bot.SendChatAction(ctx, tu.ChatAction(tu.ID(chatID), telego.ChatActionTyping)) if err != nil { - log.Printf("Failed to send chat action: %v", err) + logger.ErrorCF("telegram", "Failed to send chat action", map[string]interface{}{ + "error": err.Error(), + }) } - stopChan := make(chan struct{}) - c.stopThinking.Store(fmt.Sprintf("%d", chatID), stopChan) + // Stop any previous thinking animation + chatIDStr := fmt.Sprintf("%d", chatID) + if prevStop, ok := c.stopThinking.Load(chatIDStr); ok { + if cf, ok := prevStop.(*thinkingCancel); ok && cf != nil { + cf.Cancel() + } + } + + // Create new context for thinking animation with timeout + thinkCtx, thinkCancel := context.WithTimeout(ctx, 5*time.Minute) + c.stopThinking.Store(chatIDStr, &thinkingCancel{fn: thinkCancel}) pMsg, err := c.bot.SendMessage(ctx, tu.Message(tu.ID(chatID), "Thinking... ๐Ÿ’ญ")) if err == nil { pID := pMsg.MessageID - c.placeholders.Store(fmt.Sprintf("%d", chatID), pID) + c.placeholders.Store(chatIDStr, pID) - go func(cid int64, mid int, stop <-chan struct{}) { + go func(cid int64, mid int) { dots := []string{".", "..", "..."} emotes := []string{"๐Ÿ’ญ", "๐Ÿค”", "โ˜๏ธ"} i := 0 @@ -261,18 +319,20 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat defer ticker.Stop() for { select { - case <-stop: + case <-thinkCtx.Done(): return case <-ticker.C: i++ text := fmt.Sprintf("Thinking%s %s", dots[i%len(dots)], emotes[i%len(emotes)]) - _, editErr := c.bot.EditMessageText(ctx, tu.EditMessageText(tu.ID(chatID), mid, text)) + _, editErr := c.bot.EditMessageText(thinkCtx, tu.EditMessageText(tu.ID(chatID), mid, text)) if editErr != nil { - log.Printf("Failed to edit thinking message: %v", editErr) + logger.DebugCF("telegram", "Failed to edit thinking message", map[string]interface{}{ + "error": editErr.Error(), + }) } } } - }(chatID, pID, stopChan) + }(chatID, pID) } metadata := map[string]string{ @@ -289,7 +349,9 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat func (c *TelegramChannel) downloadPhoto(ctx context.Context, fileID string) string { file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID}) if err != nil { - log.Printf("Failed to get photo file: %v", err) + logger.ErrorCF("telegram", "Failed to get photo file", map[string]interface{}{ + "error": err.Error(), + }) return "" } @@ -302,78 +364,25 @@ func (c *TelegramChannel) downloadFileWithInfo(file *telego.File, ext string) st } url := c.bot.FileDownloadURL(file.FilePath) - log.Printf("File URL: %s", url) + logger.DebugCF("telegram", "File URL", map[string]interface{}{"url": url}) - mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") - if err := os.MkdirAll(mediaDir, 0755); err != nil { - log.Printf("Failed to create media directory: %v", err) - return "" - } - - localPath := filepath.Join(mediaDir, file.FilePath[:min(16, len(file.FilePath))]+ext) - - if err := c.downloadFromURL(url, localPath); err != nil { - log.Printf("Failed to download file: %v", err) - return "" - } - - return localPath -} - -func (c *TelegramChannel) downloadFromURL(url, localPath string) error { - resp, err := http.Get(url) - if err != nil { - return fmt.Errorf("failed to download: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("download failed with status: %d", resp.StatusCode) - } - - out, err := os.Create(localPath) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - defer out.Close() - - _, err = io.Copy(out, resp.Body) - if err != nil { - return fmt.Errorf("failed to write file: %w", err) - } - - log.Printf("File downloaded successfully to: %s", localPath) - return nil + // Use FilePath as filename for better identification + filename := file.FilePath + ext + return utils.DownloadFile(url, filename, utils.DownloadOptions{ + LoggerPrefix: "telegram", + }) } func (c *TelegramChannel) downloadFile(ctx context.Context, fileID, ext string) string { file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID}) if err != nil { - log.Printf("Failed to get file: %v", err) + logger.ErrorCF("telegram", "Failed to get file", map[string]interface{}{ + "error": err.Error(), + }) return "" } - if file.FilePath == "" { - return "" - } - - url := c.bot.FileDownloadURL(file.FilePath) - log.Printf("File URL: %s", url) - - mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") - if err = os.MkdirAll(mediaDir, 0755); err != nil { - log.Printf("Failed to create media directory: %v", err) - return "" - } - - localPath := filepath.Join(mediaDir, fileID[:16]+ext) - - if err = c.downloadFromURL(url, localPath); err != nil { - log.Printf("Failed to download file: %v", err) - return "" - } - - return localPath + return c.downloadFileWithInfo(file, ext) } func parseChatID(chatIDStr string) (int64, error) { diff --git a/pkg/utils/media.go b/pkg/utils/media.go new file mode 100644 index 0000000..6345da8 --- /dev/null +++ b/pkg/utils/media.go @@ -0,0 +1,143 @@ +package utils + +import ( + "io" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/google/uuid" + "github.com/sipeed/picoclaw/pkg/logger" +) + +// IsAudioFile checks if a file is an audio file based on its filename extension and content type. +func IsAudioFile(filename, contentType string) bool { + audioExtensions := []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"} + audioTypes := []string{"audio/", "application/ogg", "application/x-ogg"} + + for _, ext := range audioExtensions { + if strings.HasSuffix(strings.ToLower(filename), ext) { + return true + } + } + + for _, audioType := range audioTypes { + if strings.HasPrefix(strings.ToLower(contentType), audioType) { + return true + } + } + + return false +} + +// SanitizeFilename removes potentially dangerous characters from a filename +// and returns a safe version for local filesystem storage. +func SanitizeFilename(filename string) string { + // Get the base filename without path + base := filepath.Base(filename) + + // Remove any directory traversal attempts + base = strings.ReplaceAll(base, "..", "") + base = strings.ReplaceAll(base, "/", "_") + base = strings.ReplaceAll(base, "\\", "_") + + return base +} + +// DownloadOptions holds optional parameters for downloading files +type DownloadOptions struct { + Timeout time.Duration + ExtraHeaders map[string]string + LoggerPrefix string +} + +// DownloadFile downloads a file from URL to a local temp directory. +// Returns the local file path or empty string on error. +func DownloadFile(url, filename string, opts DownloadOptions) string { + // Set defaults + if opts.Timeout == 0 { + opts.Timeout = 60 * time.Second + } + if opts.LoggerPrefix == "" { + opts.LoggerPrefix = "utils" + } + + mediaDir := filepath.Join(os.TempDir(), "picoclaw_media") + if err := os.MkdirAll(mediaDir, 0700); err != nil { + logger.ErrorCF(opts.LoggerPrefix, "Failed to create media directory", map[string]interface{}{ + "error": err.Error(), + }) + return "" + } + + // Generate unique filename with UUID prefix to prevent conflicts + ext := filepath.Ext(filename) + safeName := SanitizeFilename(filename) + localPath := filepath.Join(mediaDir, uuid.New().String()[:8]+"_"+safeName+ext) + + // Create HTTP request + req, err := http.NewRequest("GET", url, nil) + if err != nil { + logger.ErrorCF(opts.LoggerPrefix, "Failed to create download request", map[string]interface{}{ + "error": err.Error(), + }) + return "" + } + + // Add extra headers (e.g., Authorization for Slack) + for key, value := range opts.ExtraHeaders { + req.Header.Set(key, value) + } + + client := &http.Client{Timeout: opts.Timeout} + resp, err := client.Do(req) + if err != nil { + logger.ErrorCF(opts.LoggerPrefix, "Failed to download file", map[string]interface{}{ + "error": err.Error(), + "url": url, + }) + return "" + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.ErrorCF(opts.LoggerPrefix, "File download returned non-200 status", map[string]interface{}{ + "status": resp.StatusCode, + "url": url, + }) + return "" + } + + out, err := os.Create(localPath) + if err != nil { + logger.ErrorCF(opts.LoggerPrefix, "Failed to create local file", map[string]interface{}{ + "error": err.Error(), + }) + return "" + } + defer out.Close() + + if _, err := io.Copy(out, resp.Body); err != nil { + out.Close() + os.Remove(localPath) + logger.ErrorCF(opts.LoggerPrefix, "Failed to write file", map[string]interface{}{ + "error": err.Error(), + }) + return "" + } + + logger.DebugCF(opts.LoggerPrefix, "File downloaded successfully", map[string]interface{}{ + "path": localPath, + }) + + return localPath +} + +// DownloadFileSimple is a simplified version of DownloadFile without options +func DownloadFileSimple(url, filename string) string { + return DownloadFile(url, filename, DownloadOptions{ + LoggerPrefix: "media", + }) +} From d7da39d62ba97f7b1d1d7b308713c64f653fe30d Mon Sep 17 00:00:00 2001 From: Wutachi Date: Thu, 12 Feb 2026 02:34:10 -0300 Subject: [PATCH 22/23] Fix telegram channel permission check --- pkg/channels/telegram.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 95f6102..73a4290 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -343,7 +343,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, update telego.Updat "is_group": fmt.Sprintf("%t", message.Chat.Type != "private"), } - c.HandleMessage(senderID, fmt.Sprintf("%d", chatID), content, mediaPaths, metadata) + c.HandleMessage(fmt.Sprintf("%d", user.ID), fmt.Sprintf("%d", chatID), content, mediaPaths, metadata) } func (c *TelegramChannel) downloadPhoto(ctx context.Context, fileID string) string { From 13fcbe6c5936a23a3729d680dacf9a533b75c4ce Mon Sep 17 00:00:00 2001 From: zepan Date: Thu, 12 Feb 2026 15:00:30 +0800 Subject: [PATCH 23/23] 1. update wechat group qrcode --- assets/wechat.png | Bin 142251 -> 144793 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/wechat.png b/assets/wechat.png index 30e096258f5643f67544b143a4af4624aa144410..4e9d0df416a3a2cff8d917c27a94b8a2f38d089d 100644 GIT binary patch literal 144793 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 Y{+mAypU6y-*fH`I42zLlmm5a@4-g!BlK=n! literal 142251 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

      f^fT&6 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