refactor(cron): normalize payload columns into dedicated DB fields (#33)

Extract wake_heartbeat and stateless from JSON payload into first-class
columns on cron_jobs. Adds migration 000033 with backfill from existing
payload data. Updates PG + SQLite stores, RPC handlers, and UI i18n.
This commit is contained in:
viettranx committed 2026-03-30 15:19:43 +07:00
1 parent d10236241e
commit 24717b0f51
28 files changed
+268 -131

No files matched your search

+16 -13
View File
@@ -49,7 +49,7 @@ func makeCronJobHandler(sched *scheduler.Scheduler, msgBus *bus.MessageBus, cfg
}
sessionKey := sessions.BuildCronSessionKey(agentID, job.ID)
channel := job.Payload.Channel
channel := job.DeliverChannel
if channel == "" {
channel = "cron"
}
@@ -63,12 +63,12 @@ func makeCronJobHandler(sched *scheduler.Scheduler, msgBus *bus.MessageBus, cfg
// Build cron context so the agent knows delivery target and requester.
var extraPrompt string
if job.Payload.Deliver && job.Payload.Channel != "" && job.Payload.To != "" {
if job.Deliver && job.DeliverChannel != "" && job.DeliverTo != "" {
extraPrompt = fmt.Sprintf(
"[Cron Job]\nThis is scheduled job \"%s\" (ID: %s).\n"+
"Requester: user %s on channel \"%s\" (chat %s).\n"+
"Your response will be automatically delivered to that chat — just produce the content directly.",
job.Name, job.ID, job.UserID, job.Payload.Channel, job.Payload.To,
job.Name, job.ID, job.UserID, job.DeliverChannel, job.DeliverTo,
)
} else {
extraPrompt = fmt.Sprintf(
@@ -84,8 +84,11 @@ func makeCronJobHandler(sched *scheduler.Scheduler, msgBus *bus.MessageBus, cfg
// Reset session before each cron run to prevent tool errors from previous
// runs from polluting the context and blocking future executions (#294).
// Save() persists the empty session to DB so stale data won't reload after restart.
sessionMgr.Reset(cronCtx, sessionKey)
sessionMgr.Save(cronCtx, sessionKey)
// Stateless jobs skip this — they intentionally carry no session history.
if !job.Stateless {
sessionMgr.Reset(cronCtx, sessionKey)
sessionMgr.Save(cronCtx, sessionKey)
}
// Schedule through cron lane — scheduler handles agent resolution and concurrency
outCh := sched.Schedule(cronCtx, scheduler.LaneCron, agent.RunRequest{
@@ -93,7 +96,7 @@ func makeCronJobHandler(sched *scheduler.Scheduler, msgBus *bus.MessageBus, cfg
Message: job.Payload.Message,
Channel: channel,
ChannelType: channelType,
ChatID: job.Payload.To,
ChatID: job.DeliverTo,
PeerKind: peerKind,
UserID: job.UserID,
RunID: fmt.Sprintf("cron:%s", job.ID),
@@ -112,20 +115,20 @@ func makeCronJobHandler(sched *scheduler.Scheduler, msgBus *bus.MessageBus, cfg
result := outcome.Result
// If job wants delivery to a channel, send the agent response to the target chat.
if job.Payload.Deliver && job.Payload.Channel != "" && job.Payload.To != "" {
if job.Deliver && job.DeliverChannel != "" && job.DeliverTo != "" {
outMsg := bus.OutboundMessage{
Channel: job.Payload.Channel,
ChatID: job.Payload.To,
Channel: job.DeliverChannel,
ChatID: job.DeliverTo,
Content: result.Content,
}
if peerKind == "group" {
outMsg.Metadata = map[string]string{"group_id": job.Payload.To}
outMsg.Metadata = map[string]string{"group_id": job.DeliverTo}
}
appendMediaToOutbound(&outMsg, result.Media)
msgBus.PublishOutbound(outMsg)
} else if job.Payload.Deliver {
} else if job.Deliver {
slog.Warn("cron: delivery configured but channel/chatID missing — output discarded",
"job_id", job.ID, "job_name", job.Name, "channel", job.Payload.Channel, "to", job.Payload.To)
"job_id", job.ID, "job_name", job.Name, "channel", job.DeliverChannel, "to", job.DeliverTo)
}
cronResult := &store.CronJobResult{
@@ -138,7 +141,7 @@ func makeCronJobHandler(sched *scheduler.Scheduler, msgBus *bus.MessageBus, cfg
// wakeMode: trigger heartbeat after cron job completes.
// Use original job.AgentID (UUID) — cronHeartbeatWakeFn expects UUID for ticker.Wake().
if job.Payload.WakeHeartbeat && cronHeartbeatWakeFn != nil {
if job.WakeHeartbeat && cronHeartbeatWakeFn != nil {
cronHeartbeatWakeFn(job.AgentID)
}