mirror of
https://github.com/affaan-m/ECC.git
synced 2026-11-09 01:26:00 +00:00
maint: integrate reviewed contribution #3798
This commit is contained in:
3 files changed
+124
No files matched your search
@@ -580,6 +580,17 @@ enum ScheduleCommands {
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Update only the instructions for one existing schedule
|
||||
Update {
|
||||
/// Existing schedule ID; never creates another schedule
|
||||
schedule_id: i64,
|
||||
/// Replacement task instructions (preserves timing and agent identity)
|
||||
#[arg(short, long)]
|
||||
task: String,
|
||||
/// Emit machine-readable JSON instead of the human summary
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// List scheduled tasks
|
||||
List {
|
||||
/// Emit machine-readable JSON instead of the human summary
|
||||
@@ -2708,6 +2719,17 @@ async fn main() -> Result<()> {
|
||||
);
|
||||
}
|
||||
}
|
||||
ScheduleCommands::Update { schedule_id, task, json } => {
|
||||
let schedule = session::manager::update_scheduled_task_text(&db, schedule_id, &task)?;
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&schedule)?);
|
||||
} else {
|
||||
println!(
|
||||
"Updated schedule {} without changing its next run {}",
|
||||
schedule.id, schedule.next_run_at.to_rfc3339()
|
||||
);
|
||||
}
|
||||
}
|
||||
ScheduleCommands::List { json } => {
|
||||
let schedules = session::manager::list_scheduled_tasks(&db)?;
|
||||
if json {
|
||||
@@ -9026,6 +9048,33 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_parses_native_schedule_update_without_identity_mutation_options() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"ecc", "schedule", "update", "16", "--task",
|
||||
"Read original ECC native READY items", "--json",
|
||||
])
|
||||
.expect("native schedule update must parse");
|
||||
match cli.command {
|
||||
Some(Commands::Schedule {
|
||||
command: ScheduleCommands::Update { schedule_id, task, json }
|
||||
}) => {
|
||||
assert_eq!(schedule_id, 16);
|
||||
assert!(task.contains("READY items"));
|
||||
assert!(json);
|
||||
}
|
||||
_ => panic!("expected native existing schedule update"),
|
||||
}
|
||||
assert!(Cli::try_parse_from([
|
||||
"ecc", "schedule", "update", "16", "--task",
|
||||
"New instructions", "--cron", "* * * * *",
|
||||
]).is_err(), "schedule update must not mutate cron");
|
||||
assert!(Cli::try_parse_from([
|
||||
"ecc", "schedule", "update", "16", "--task",
|
||||
"New instructions", "--agent", "codex",
|
||||
]).is_err(), "schedule update must not mutate the agent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_parses_remote_computer_use_command() {
|
||||
let cli = Cli::try_parse_from([
|
||||
|
||||
@@ -245,6 +245,20 @@ pub fn create_scheduled_task(
|
||||
)
|
||||
}
|
||||
|
||||
/// Replace only the instruction text on a native schedule. Keep ID, timing,
|
||||
/// agent/profile, worktree and other immutable scheduling fields unchanged.
|
||||
pub fn update_scheduled_task_text(
|
||||
db: &StateStore,
|
||||
schedule_id: i64,
|
||||
task: &str,
|
||||
) -> Result<ScheduledTask> {
|
||||
anyhow::ensure!(schedule_id > 0, "Schedule ID must be positive");
|
||||
anyhow::ensure!(!task.trim().is_empty(), "Schedule task must not be empty");
|
||||
anyhow::ensure!(task.len() <= 16 * 1024, "Schedule task exceeds 16 KiB");
|
||||
db.update_scheduled_task_text(schedule_id, task)?
|
||||
.with_context(|| format!("Scheduled task not found: {schedule_id}"))
|
||||
}
|
||||
|
||||
pub fn list_scheduled_tasks(db: &StateStore) -> Result<Vec<ScheduledTask>> {
|
||||
db.list_scheduled_tasks()
|
||||
}
|
||||
|
||||
@@ -1576,6 +1576,22 @@ impl StateStore {
|
||||
.ok_or_else(|| anyhow::anyhow!("Scheduled task {id} was not found after insert"))
|
||||
}
|
||||
|
||||
/// Update instructions in place; preserve the existing ID and due time.
|
||||
pub fn update_scheduled_task_text(
|
||||
&self,
|
||||
schedule_id: i64,
|
||||
task: &str,
|
||||
) -> Result<Option<ScheduledTask>> {
|
||||
let modified = self.conn.execute(
|
||||
"UPDATE scheduled_tasks SET task = ?2, updated_at = ?3 WHERE id = ?1",
|
||||
rusqlite::params![schedule_id, task, chrono::Utc::now().to_rfc3339()],
|
||||
)?;
|
||||
if modified == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
self.get_scheduled_task(schedule_id)
|
||||
}
|
||||
|
||||
pub fn list_scheduled_tasks(&self) -> Result<Vec<ScheduledTask>> {
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT id, cron_expr, task, agent_type, profile_name, working_dir, project, task_group,
|
||||
@@ -7296,6 +7312,51 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scheduled_task_update_keeps_original_identity_and_next_due_time() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-schedule-inplace-update")?;
|
||||
let db = StateStore::open(&tempdir.path().join("state.db"))?;
|
||||
let next = Utc::now() + ChronoDuration::minutes(2);
|
||||
let original = db.insert_scheduled_task(
|
||||
"*/2 * * * *",
|
||||
"Old instructions",
|
||||
"claude",
|
||||
Some("lead-profile"),
|
||||
tempdir.path(),
|
||||
"pace",
|
||||
"g0",
|
||||
false,
|
||||
next,
|
||||
)?;
|
||||
let previous_run = Utc::now() - ChronoDuration::minutes(2);
|
||||
db.record_scheduled_task_run(original.id, previous_run, next)?;
|
||||
let original = db
|
||||
.get_scheduled_task(original.id)?
|
||||
.context("recorded schedule must exist before update")?;
|
||||
assert_eq!(original.last_run_at, Some(previous_run));
|
||||
|
||||
let updated = crate::session::manager::update_scheduled_task_text(
|
||||
&db, original.id, "Read ECC native READY items",
|
||||
)?;
|
||||
assert_eq!(updated.id, original.id);
|
||||
assert_eq!(updated.task, "Read ECC native READY items");
|
||||
assert_eq!(updated.cron_expr, original.cron_expr);
|
||||
assert_eq!(updated.next_run_at, original.next_run_at);
|
||||
assert_eq!(updated.last_run_at, original.last_run_at);
|
||||
assert_eq!(updated.agent_type, original.agent_type);
|
||||
assert_eq!(updated.profile_name, original.profile_name);
|
||||
assert_eq!(updated.working_dir, original.working_dir);
|
||||
assert_eq!(updated.project, original.project);
|
||||
assert_eq!(updated.task_group, original.task_group);
|
||||
assert_eq!(updated.use_worktree, original.use_worktree);
|
||||
assert_eq!(db.list_scheduled_tasks()?.len(), 1);
|
||||
assert!(crate::session::manager::update_scheduled_task_text(&db, original.id, " ").is_err());
|
||||
assert!(crate::session::manager::update_scheduled_task_text(&db, original.id, &"x".repeat(16385)).is_err());
|
||||
assert!(crate::session::manager::update_scheduled_task_text(&db, original.id + 1, "valid task").is_err());
|
||||
assert_eq!(db.list_scheduled_tasks()?.len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_graph_detail_includes_incoming_and_outgoing_relations() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-context-relations")?;
|
||||
|
||||
Reference in new issue
Block a user