Merge pull request #225 from yinwm/feat/cron-exec-timeout-config

feat(cron): add configurable execution timeout for cron jobs
This commit is contained in:
daming大铭 2026-02-17 21:12:59 +08:00 committed by GitHub
commit 7b9b8104c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 44 additions and 7 deletions

View file

@ -195,6 +195,9 @@ picoclaw onboard
"api_key": "YOUR_BRAVE_API_KEY", "api_key": "YOUR_BRAVE_API_KEY",
"max_results": 5 "max_results": 5
} }
},
"cron": {
"exec_timeout_minutes": 5
} }
}, },
"heartbeat": { "heartbeat": {
@ -697,6 +700,9 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
"search": { "search": {
"apiKey": "BSA..." "apiKey": "BSA..."
} }
},
"cron": {
"exec_timeout_minutes": 5
} }
}, },
"heartbeat": { "heartbeat": {

View file

@ -774,6 +774,9 @@ picoclaw agent -m "Hello"
"enabled": true, "enabled": true,
"max_results": 5 "max_results": 5
} }
},
"cron": {
"exec_timeout_minutes": 5
} }
}, },
"heartbeat": { "heartbeat": {

View file

@ -236,6 +236,9 @@ picoclaw onboard
"api_key": "YOUR_BRAVE_API_KEY", "api_key": "YOUR_BRAVE_API_KEY",
"max_results": 5 "max_results": 5
} }
},
"cron": {
"exec_timeout_minutes": 5
} }
} }
} }
@ -644,6 +647,9 @@ picoclaw agent -m "你好"
"search": { "search": {
"api_key": "BSA..." "api_key": "BSA..."
} }
},
"cron": {
"exec_timeout_minutes": 5
} }
}, },
"heartbeat": { "heartbeat": {

View file

@ -562,7 +562,8 @@ func gatewayCmd() {
}) })
// Setup cron tool and service // Setup cron tool and service
cronService := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath(), cfg.Agents.Defaults.RestrictToWorkspace) execTimeout := time.Duration(cfg.Tools.Cron.ExecTimeoutMinutes) * time.Minute
cronService := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath(), cfg.Agents.Defaults.RestrictToWorkspace, execTimeout)
heartbeatService := heartbeat.NewHeartbeatService( heartbeatService := heartbeat.NewHeartbeatService(
cfg.WorkspacePath(), cfg.WorkspacePath(),
@ -987,14 +988,14 @@ func getConfigPath() string {
return filepath.Join(home, ".picoclaw", "config.json") return filepath.Join(home, ".picoclaw", "config.json")
} }
func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string, restrict bool) *cron.CronService { func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string, restrict bool, execTimeout time.Duration) *cron.CronService {
cronStorePath := filepath.Join(workspace, "cron", "jobs.json") cronStorePath := filepath.Join(workspace, "cron", "jobs.json")
// Create cron service // Create cron service
cronService := cron.NewCronService(cronStorePath, nil) cronService := cron.NewCronService(cronStorePath, nil)
// Create and register CronTool // Create and register CronTool
cronTool := tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict) cronTool := tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout)
agentLoop.RegisterTool(cronTool) agentLoop.RegisterTool(cronTool)
// Set the onJob handler // Set the onJob handler

View file

@ -127,6 +127,9 @@
"api_key": "pplx-xxx", "api_key": "pplx-xxx",
"max_results": 5 "max_results": 5
} }
},
"cron": {
"exec_timeout_minutes": 5
} }
}, },
"heartbeat": { "heartbeat": {

View file

@ -218,8 +218,13 @@ type WebToolsConfig struct {
Perplexity PerplexityConfig `json:"perplexity"` Perplexity PerplexityConfig `json:"perplexity"`
} }
type CronToolsConfig struct {
ExecTimeoutMinutes int `json:"exec_timeout_minutes" env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES"` // 0 means no timeout
}
type ToolsConfig struct { type ToolsConfig struct {
Web WebToolsConfig `json:"web"` Web WebToolsConfig `json:"web"`
Cron CronToolsConfig `json:"cron"`
} }
func DefaultConfig() *Config { func DefaultConfig() *Config {
@ -334,6 +339,9 @@ func DefaultConfig() *Config {
MaxResults: 5, MaxResults: 5,
}, },
}, },
Cron: CronToolsConfig{
ExecTimeoutMinutes: 5, // default 5 minutes for LLM operations
},
}, },
Heartbeat: HeartbeatConfig{ Heartbeat: HeartbeatConfig{
Enabled: true, Enabled: true,

View file

@ -28,12 +28,15 @@ type CronTool struct {
} }
// NewCronTool creates a new CronTool // NewCronTool creates a new CronTool
func NewCronTool(cronService *cron.CronService, executor JobExecutor, msgBus *bus.MessageBus, workspace string, restrict bool) *CronTool { // execTimeout: 0 means no timeout, >0 sets the timeout duration
func NewCronTool(cronService *cron.CronService, executor JobExecutor, msgBus *bus.MessageBus, workspace string, restrict bool, execTimeout time.Duration) *CronTool {
execTool := NewExecTool(workspace, restrict)
execTool.SetTimeout(execTimeout) // 0 means no timeout
return &CronTool{ return &CronTool{
cronService: cronService, cronService: cronService,
executor: executor, executor: executor,
msgBus: msgBus, msgBus: msgBus,
execTool: NewExecTool(workspace, restrict), execTool: execTool,
} }
} }

View file

@ -89,7 +89,14 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *To
return ErrorResult(guardError) return ErrorResult(guardError)
} }
cmdCtx, cancel := context.WithTimeout(ctx, t.timeout) // timeout == 0 means no timeout
var cmdCtx context.Context
var cancel context.CancelFunc
if t.timeout > 0 {
cmdCtx, cancel = context.WithTimeout(ctx, t.timeout)
} else {
cmdCtx, cancel = context.WithCancel(ctx)
}
defer cancel() defer cancel()
var cmd *exec.Cmd var cmd *exec.Cmd