From 36b9693d3121d8ac37ec3e77e3f486e9c3a52703 Mon Sep 17 00:00:00 2001 From: srcrs Date: Fri, 10 Apr 2026 23:16:00 +0800 Subject: [PATCH 1/3] fix(cron): make each job execution use an independent session Previously all executions of the same cron job reused the session key "cron-{jobID}", causing conversation history to accumulate across runs. Now each run gets a unique key "cron-{jobID}-{timestamp}", preventing cross-execution interference. --- pkg/tools/cron.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index c6ac3a129..8fd8c1d71 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -342,7 +342,7 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return "ok" } - sessionKey := fmt.Sprintf("cron-%s", job.ID) + sessionKey := fmt.Sprintf("cron-%s-%d", job.ID, time.Now().UnixMilli()) // Call agent with the job message response, err := t.executor.ProcessDirectWithChannel( From 2b73978c5f64df34619e5471f53dda322860ff19 Mon Sep 17 00:00:00 2001 From: srcrs Date: Sat, 11 Apr 2026 23:16:12 +0800 Subject: [PATCH 2/3] fix(cron): add agent: prefix to session key so resolveScopeKey preserves it Cron session keys "agent:cron-{id}-{uuid}" were being silently ignored by resolveScopeKey, which only recognizes keys prefixed with "agent:". This caused multiple executions of the same job to share a session. Also switch from timestamp to UUID to avoid collisions in concurrent scenarios. --- pkg/tools/cron.go | 3 ++- pkg/tools/cron_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 8fd8c1d71..8fabc95bb 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/constants" @@ -342,7 +343,7 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return "ok" } - sessionKey := fmt.Sprintf("cron-%s-%d", job.ID, time.Now().UnixMilli()) + sessionKey := fmt.Sprintf("agent:cron-%s-%s", job.ID, uuid.New().String()) // Call agent with the job message response, err := t.executor.ProcessDirectWithChannel( diff --git a/pkg/tools/cron_test.go b/pkg/tools/cron_test.go index c699908cd..694349b60 100644 --- a/pkg/tools/cron_test.go +++ b/pkg/tools/cron_test.go @@ -271,8 +271,8 @@ func TestCronTool_ExecuteJobPublishesAgentResponse(t *testing.T) { t.Fatalf("ExecuteJob() = %q, want ok", got) } - if executor.lastKey != "cron-job-1" { - t.Fatalf("sessionKey = %q, want cron-job-1", executor.lastKey) + if !strings.HasPrefix(executor.lastKey, "agent:cron-job-1-") { + t.Fatalf("sessionKey = %q, want agent:cron-job-1-{uuid}", executor.lastKey) } if executor.lastChan != "telegram" || executor.lastChatID != "chat-1" { t.Fatalf("executor target = %s/%s, want telegram/chat-1", executor.lastChan, executor.lastChatID) From d8e7a6129f0f3e43442a7b25e1e50b65bfa54aae Mon Sep 17 00:00:00 2001 From: srcrs Date: Wed, 15 Apr 2026 02:07:35 +0800 Subject: [PATCH 3/3] fix(cron): add blank line between default and localmodule imports for gci gci linter requires a blank line separating import sections (default vs localmodule). Missing separator caused CI failure. --- pkg/tools/cron.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 8fabc95bb..4f0cc7a23 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -7,6 +7,7 @@ import ( "time" "github.com/google/uuid" + "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/constants"