schedule: add paseo schedule update to edit schedules in place (#694)

* schedule: add `paseo schedule update` to edit schedules in place

Editing a schedule today requires delete+recreate, losing run history
and the schedule id. This adds an additive RPC and CLI command that
patches name, prompt, cadence, new-agent target fields, max-runs, and
expires-in without touching runs or in-flight executions. nextRunAt is
recomputed only when the cadence actually changes.

* schedule(cli): share cadence flag parser between create and update

Both paths were turning --every/--cron into a ScheduleCadence with
near-identical code. Extract `parseCadenceFromFlags` so the literals
and the exclusivity check live in one place; create wraps it to
require a value, update lets it stay optional.
This commit is contained in:
Mohamed Boudra
2026-05-05 19:56:52 +07:00
parent 82cf11aaae
commit 400934f19c
13 changed files with 887 additions and 8 deletions
@@ -392,6 +392,10 @@ type ScheduleRunOncePayload = Extract<
SessionOutboundMessage,
{ type: "schedule/run-once/response" }
>["payload"];
type ScheduleUpdatePayload = Extract<
SessionOutboundMessage,
{ type: "schedule/update/response" }
>["payload"];
export type FetchAgentTimelinePayload = FetchAgentTimelineResponseMessage["payload"];
export type FetchAgentTimelineDirection = FetchAgentTimelinePayload["direction"];
@@ -552,6 +556,30 @@ export interface InspectScheduleOptions {
id: string;
requestId?: string;
}
export interface UpdateScheduleNewAgentConfig {
provider?: string;
model?: string | null;
modeId?: string | null;
cwd?: string;
}
export interface UpdateScheduleOptions {
id: string;
name?: string | null;
prompt?: string;
cadence?:
| {
type: "every";
everyMs: number;
}
| {
type: "cron";
expression: string;
};
newAgentConfig?: UpdateScheduleNewAgentConfig;
maxRuns?: number | null;
expiresAt?: string | null;
requestId?: string;
}
type ListAvailableEditorsPayload = ListAvailableEditorsResponseMessage["payload"];
type OpenInEditorPayload = OpenInEditorResponseMessage["payload"];
type OpenProjectPayload = OpenProjectResponseMessage["payload"];
@@ -3762,6 +3790,24 @@ export class DaemonClient {
});
}
async scheduleUpdate(options: UpdateScheduleOptions): Promise<ScheduleUpdatePayload> {
return this.sendCorrelatedSessionRequest({
requestId: options.requestId,
message: {
type: "schedule/update",
scheduleId: options.id,
...(options.name !== undefined ? { name: options.name } : {}),
...(options.prompt !== undefined ? { prompt: options.prompt } : {}),
...(options.cadence !== undefined ? { cadence: options.cadence } : {}),
...(options.newAgentConfig !== undefined ? { newAgentConfig: options.newAgentConfig } : {}),
...(options.maxRuns !== undefined ? { maxRuns: options.maxRuns } : {}),
...(options.expiresAt !== undefined ? { expiresAt: options.expiresAt } : {}),
},
responseType: "schedule/update/response",
timeout: 10000,
});
}
async loopRun(options: RunLoopOptions): Promise<LoopRunPayload> {
return this.sendCorrelatedSessionRequest({
requestId: options.requestId,