Implement schedule command enhancements and output handling for scheduled tasks

This commit is contained in:
Cheikh Seck committed 2026-04-18 18:16:22 +00:00
1 parent 6e1398fd8f
commit f71e0c5931
5 files changed
+116 -30

No files matched your search

+2 -1
View File
@@ -985,7 +985,8 @@ promptLoop:
fmt.Printf("[Round %d/%d] Got %d tool calls, isToolCallResponse=%v\n", round+1, maxToolRounds, len(toolCalls), isToolCallResponse)
if len(toolCalls) > 0 && isToolCallResponse {
totalToolCalls := len(toolCalls) + len(missingTools)
if totalToolCalls > 0 && isToolCallResponse {
prevNoTool = false
}
if !isToolCallResponse || len(toolCalls) == 0 {
+47
View File
@@ -17,6 +17,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/cheikh2shift/godex/internal/history"
"github.com/cheikh2shift/godex/internal/hive"
"github.com/cheikh2shift/godex/internal/scheduler"
)
var ErrPromptAborted = errors.New("prompt aborted")
@@ -74,6 +75,7 @@ type promptModel struct {
searchResults []string
statusMessage string
commitList []string
scheduleList []string
statusCh <-chan string
delegateCh <-chan hive.HiveStats
delegateCount int
@@ -550,6 +552,11 @@ func (m *promptModel) handleTab() {
return
}
}
if strings.HasPrefix(value, "/schedule ") && strings.Contains(value, " ") {
if m.handleScheduleTab(value) {
return
}
}
if value != "" && !strings.HasPrefix(value, "/") {
return
}
@@ -589,6 +596,46 @@ func (m *promptModel) handleTab() {
m.showCompletions = true
}
func (m *promptModel) handleScheduleTab(value string) bool {
parts := strings.Fields(value)
if len(parts) != 2 {
return false
}
prefix := strings.ToUpper(parts[1])
if sched == nil {
return false
}
tasks := sched.ListTasks()
if len(tasks) == 0 {
return false
}
var matches []string
for _, t := range tasks {
task, ok := t.(*scheduler.ScheduledTask)
if !ok {
continue
}
if strings.HasPrefix(task.ID, prefix) {
matches = append(matches, task.ID)
}
}
if len(matches) == 0 {
return false
}
if len(matches) == 1 && prefix != "" {
m.input.SetValue("/schedule " + matches[0])
m.input.SetCursor(len([]rune(m.input.Value())))
m.resetCompletion()
return true
}
m.scheduleList = nil
for _, id := range matches {
m.scheduleList = append(m.scheduleList, id)
}
m.resetCompletion()
return true
}
func (m *promptModel) handleCommitPullTab(value string) bool {
if m.historyDB == nil {
return false
+35 -11
View File
@@ -23,7 +23,7 @@ func (s *schedulerProviderGetter) GetProvider(cfg interface{}) (interface{}, err
providerName, _ := cfgMap["name"].(string)
providerModel, _ := cfgMap["model"].(string)
if providerType == "unknown" && s.provider != nil {
if (providerType == "unknown" || providerType == "current") && s.provider != nil {
providerType = s.provider.Type
providerName = s.provider.Name
providerModel = s.provider.Model
@@ -37,7 +37,6 @@ func (s *schedulerProviderGetter) GetProvider(cfg interface{}) (interface{}, err
}
func handleSchedule(input string) {
if sched == nil {
fmt.Println("[Scheduler] Not initialized")
return
@@ -49,11 +48,32 @@ func handleSchedule(input string) {
return
}
if len(parts) == 2 && len(parts[1]) == 4 {
id := strings.ToUpper(parts[1])
task := sched.GetTask(id)
if task == nil {
fmt.Printf("Task %s not found\n", id)
return
}
st, ok := task.(*scheduler.ScheduledTask)
if !ok {
fmt.Printf("Task %s not found\n", id)
return
}
if st.LastOutput == "" {
fmt.Printf("No output for task %s\n", id)
return
}
fmt.Printf("Output for task %s:\n%s\n", id, st.LastOutput)
return
}
if len(parts) >= 2 && (parts[1] == "help" || parts[1] == "-h" || parts[1] == "--help") {
fmt.Println("Scheduler - Schedule prompts to run at specific times or intervals")
fmt.Println("")
fmt.Println("Usage:")
fmt.Println(" /schedule - List all scheduled tasks")
fmt.Println(" /schedule <id> - Show last output of a task")
fmt.Println(" /schedule stop <id> - Stop a running task by 4-char ID")
fmt.Println(" /schedule remove <id> - Remove a task from the schedule by 4-char ID")
fmt.Println(" /schedule help - Show this help message")
@@ -100,6 +120,7 @@ func handleSchedule(input string) {
fmt.Println("Usage:")
fmt.Println(" /schedule - List all scheduled tasks")
fmt.Println(" /schedule <id> - Show last output of a task")
fmt.Println(" /schedule stop <id> - Stop a task by 4-char ID")
fmt.Println(" /schedule remove <id> - Remove a task by 4-char ID")
}
@@ -111,8 +132,8 @@ func listScheduledTasks() {
return
}
fmt.Printf("\n%-6s %-12s %-10s %-8s %-8s %s\n", "ID", "Created", "Interval", "Runs", "Last Run", "Status")
fmt.Println(strings.Repeat("-", 80))
fmt.Printf("\n%-6s %-8s %-8s %-8s %s\n", "ID", "Runs", "Interval", "Last Run", "Output")
fmt.Println(strings.Repeat("-", 70))
for _, t := range tasks {
task, ok := t.(*scheduler.ScheduledTask)
@@ -127,21 +148,24 @@ func listScheduledTasks() {
lastRun := "Never"
if !task.LastRun.IsZero() {
lastRun = task.LastRun.Format("2006-01-02 15:04")
lastRun = task.LastRun.Format("15:04")
}
status := "OK"
output := "OK"
if task.LastError != "" {
status = truncate(task.LastError, 30)
output = truncate(task.LastError, 40)
} else if task.LastOutput != "" {
lines := strings.Split(task.LastOutput, "\n")
lastLine := strings.TrimSpace(lines[len(lines)-1])
output = truncate(lastLine, 40)
}
fmt.Printf("%-6s %-12s %-10s %-8d %-8s %s\n",
fmt.Printf("%-6s %-8d %-8s %-8s %s\n",
task.ID,
task.CreatedAt.Format("2006-01-02"),
interval,
task.RunCount,
interval,
lastRun,
status,
output,
)
}
fmt.Println()
+6 -4
View File
@@ -71,7 +71,7 @@ func (s *SchedulerServer) Tools() []Tool {
currentTime := time.Now().Format("2006-01-02 15:04:05")
return []Tool{
{
Name: "schedule_task",
Name: "scheduler",
Description: fmt.Sprintf("Schedule a task to run a prompt at a specific time or at regular intervals. Current time: %s. Use interval_sec for repeating tasks (e.g., 60 for every minute, 3600 for every hour), or run_at for specific times (e.g., \"14:30\" for 2:30 PM daily, or \"now\" to execute immediately).", currentTime),
InputSchema: json.RawMessage(`{
"type": "object",
@@ -96,7 +96,7 @@ func (s *SchedulerServer) Tools() []Tool {
}
func (s *SchedulerServer) CallTool(ctx context.Context, name string, args map[string]interface{}) (string, error) {
if name != "schedule_task" {
if name != "scheduler" {
return "", fmt.Errorf("unknown tool: %s", name)
}
@@ -108,6 +108,8 @@ func (s *SchedulerServer) CallTool(ctx context.Context, name string, args map[st
intervalSec := 0
if iv, ok := args["interval_sec"].(float64); ok {
intervalSec = int(iv)
} else if iv, ok := args["run_every"].(float64); ok {
intervalSec = int(iv)
}
runAt := ""
@@ -122,14 +124,14 @@ func (s *SchedulerServer) CallTool(ctx context.Context, name string, args map[st
}
if isNow {
result, err := s.scheduler.scheduler.RunNow(prompt, "unknown", "unknown", "unknown")
result, err := s.scheduler.scheduler.RunNow(prompt, "current", "current", "current")
if err != nil {
return fmt.Sprintf("Error running task: %v", err), nil
}
return fmt.Sprintf("Task executed immediately.\n\nResult:\n%s", result), nil
}
task, err := s.scheduler.scheduler.AddTask(prompt, intervalSec, runAt, "unknown", "unknown", "unknown")
task, err := s.scheduler.scheduler.AddTask(prompt, intervalSec, runAt, "current", "current", "current")
if err != nil {
return fmt.Sprintf("Error scheduling task: %v", err), nil
}
+26 -14
View File
@@ -25,6 +25,7 @@ type ScheduledTask struct {
LastRun time.Time `json:"last_run"`
RunCount int `json:"run_count"`
LastError string `json:"last_error"`
LastOutput string `json:"last_output"`
ProviderType string `json:"provider_type"`
ProviderName string `json:"provider_name"`
ProviderModel string `json:"provider_model"`
@@ -112,6 +113,7 @@ func (s *Scheduler) initSchema() error {
last_run TEXT,
run_count INTEGER DEFAULT 0,
last_error TEXT,
last_output TEXT,
provider_type TEXT NOT NULL,
provider_name TEXT NOT NULL,
provider_model TEXT NOT NULL
@@ -125,7 +127,7 @@ func (s *Scheduler) initSchema() error {
func (s *Scheduler) loadTasks() error {
rows, err := s.db.Query(`
SELECT id, prompt, interval_sec, run_at, is_repeating, created_at,
last_run, run_count, last_error, provider_type, provider_name, provider_model
last_run, run_count, last_error, last_output, provider_type, provider_name, provider_model
FROM scheduled_tasks
`)
if err != nil {
@@ -135,10 +137,10 @@ func (s *Scheduler) loadTasks() error {
for rows.Next() {
var task ScheduledTask
var lastRun, createdAt, runAt sql.NullString
var lastRun, createdAt, runAt, lastOutput sql.NullString
err := rows.Scan(
&task.ID, &task.Prompt, &task.IntervalSec, &runAt, &task.IsRepeating,
&createdAt, &lastRun, &task.RunCount, &task.LastError,
&createdAt, &lastRun, &task.RunCount, &task.LastError, &lastOutput,
&task.ProviderType, &task.ProviderName, &task.ProviderModel,
)
if err != nil {
@@ -153,6 +155,9 @@ func (s *Scheduler) loadTasks() error {
if runAt.Valid {
task.RunAt = runAt.String
}
if lastOutput.Valid {
task.LastOutput = lastOutput.String
}
s.tasks[task.ID] = &task
}
return rows.Err()
@@ -257,11 +262,11 @@ func (s *Scheduler) RunNow(prompt string, providerType, providerName, providerMo
func (s *Scheduler) saveTask(task *ScheduledTask) error {
_, err := s.db.Exec(`
INSERT OR REPLACE INTO scheduled_tasks
(id, prompt, interval_sec, run_at, is_repeating, created_at, last_run, run_count, last_error, provider_type, provider_name, provider_model)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, prompt, interval_sec, run_at, is_repeating, created_at, last_run, run_count, last_error, last_output, provider_type, provider_name, provider_model)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, task.ID, task.Prompt, task.IntervalSec, task.RunAt, task.IsRepeating,
task.CreatedAt.Format(time.RFC3339), task.LastRun.Format(time.RFC3339),
task.RunCount, task.LastError, task.ProviderType, task.ProviderName, task.ProviderModel)
task.RunCount, task.LastError, task.LastOutput, task.ProviderType, task.ProviderName, task.ProviderModel)
return err
}
@@ -321,14 +326,19 @@ func (s *Scheduler) executeTask(task *ScheduledTask) {
return
}
cfg := &struct {
Type string `json:"type"`
Name string `json:"name"`
Model string `json:"model"`
}{
Type: task.ProviderType,
Name: task.ProviderName,
Model: task.ProviderModel,
var cfg interface{}
if task.ProviderType == "unknown" || task.ProviderType == "" {
cfg = map[string]interface{}{
"type": "current",
"name": "current",
"model": "current",
}
} else {
cfg = map[string]interface{}{
"type": task.ProviderType,
"name": task.ProviderName,
"model": task.ProviderModel,
}
}
prov, err := s.provider.GetProvider(cfg)
@@ -355,8 +365,10 @@ func (s *Scheduler) executeTask(task *ScheduledTask) {
result, err := provInterface.Send(taskCtx, task.Prompt)
if err != nil {
task.LastError = fmt.Sprintf("execution error: %v", err)
task.LastOutput = ""
} else {
task.LastError = ""
task.LastOutput = result
fmt.Printf("[Scheduler %s] Result: %s\n", task.ID, truncate(result, 200))
}